Skip to content

more built in functions to support clients - #3102

Open
jennifersp wants to merge 1 commit into
mainfrom
jennifer/client
Open

more built in functions to support clients#3102
jennifersp wants to merge 1 commit into
mainfrom
jennifer/client

Conversation

@jennifersp

Copy link
Copy Markdown
Contributor

No description provided.

@github-actions

Copy link
Copy Markdown
Contributor
Main PR
Total 42090 42090
Successful 18936 18936
Failures 23154 23154
Partial Successes1 5447 5447
Main PR
Successful 44.9893% 44.9893%
Failures 55.0107% 55.0107%

Footnotes

  1. These are tests that we're marking as Successful, however they do not match the expected output in some way. This is due to small differences, such as different wording on the error messages, or the column names being incorrect while the data itself is correct.

@itoqa

itoqa Bot commented Aug 17, 2026

Copy link
Copy Markdown

Ito QA test results
Commit: 8259c45: 14 test cases ran, 3 failed ❌, 10 passed ✅, 1 additional finding ⚠️.

Summary

Coverage spans database compatibility and catalog behavior, session-setting boundaries, startup and concurrent connections, normal metadata and privilege flows, and adversarial authorization cases involving denied or nonexistent resources. Most ordinary compatibility paths behave correctly, but the new permission and ACL-related behaviors include serious correctness and security failures.

Not safe to merge yet — this PR introduces a high-severity authorization failure that can approve access for users without permission, along with medium-severity ACL lookup failures that break consumers of the new compatibility functions. An unrelated locale-display compatibility issue is a separate flag-for-later caveat, not a driver of the merge decision.

Tests run by Ito

View full run

Result Severity Type Description
High severity General All six privilege checks returned true for denied and nonexistent inputs, even though the independent protected read was rejected.
Medium severity General Each concurrent client failed with an error saying it could not find field 1 in a row with 1 column. The same failure was reproducible after the concurrent work finished.
Medium severity Catalog The ACL catalog function is listed as available, but a valid call fails before it can return an empty result or ACL rows.
Access The ACL compatibility function is registered with a text-array input and four named output fields. A non-NULL call is designed to return an empty result without an execution error; the earlier missing entry and internal row error came from stale or mismatched runtime state.
General The keyword list returned 641 rows consistently and ended without duplicate or malformed rows. The reported failure came from unsupported cursor commands, not from the keyword list itself.
General The invalid parallel setting was rejected, and the session stayed off. The valid settings still worked, and reset returned the session to off.
General The access lookup can return no rows without breaking the client connection. The current code declares all four result fields and turns its empty result into a clean empty response; the earlier internal error came from an unavailable or stale server state.
General A fresh server made the existing and newly added database functions available from two separate connections with the same results. No startup timing issue or partial registration was found.
Blocking The lock inspection call accepted a process ID and returned an empty result without an error. Its result type matched the expected integer list used by PostgreSQL clients.
Keyword The keyword catalog returned all 641 rows with the five expected fields filled in, and the result ended cleanly.
Keyword Representative reserved, unreserved, and special keywords show the correct category descriptions. Keywords that need AS report that requirement, while the other keywords can be used as bare labels.
Parallel The session showed the expected locale defaults, accepted off, on, and regress, and returned to off after RESET.
Privilege All six supported table privilege calls completed successfully and returned true boolean values.
⚠️ Medium severity General Reading the locale settings with the standard SHOW command fails with a syntax error for both names.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟡 SHOW cannot read locale settings
  • Severity: Medium Medium severity
  • Description: Reading the locale settings with the standard SHOW command fails with a syntax error for both names.
  • Impact: Clients that use the standard SHOW command cannot read the current locale settings. They can still read them with current_setting, but this breaks compatibility with expected database behavior.
  • Steps to Reproduce:
    1. Open a fresh database session.
    2. Run SHOW lc_collate; and SHOW lc_ctype;.
    3. Observe a syntax error for each command.
    4. Run SELECT current_setting('lc_collate'), current_setting('lc_ctype'); and confirm both values are en_US.UTF-8.
    5. Try SET LC_COLLATE TO 'C';, SET Lc_CtYpE TO 'C';, RESET LC_COLLATE;, and RESET Lc_CtYpE; and confirm the values remain unchanged.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The lexer in postgres/parser/lex/keywords.go:1875-1878 maps lc_collate to LC_COLLATE and lc_ctype to LC_CTYPE rather than returning IDENT. The SHOW grammar in postgres/parser/parser/sql.y:6770-6786 defines session_var with IDENT, qualified identifiers, and a small set of explicit token rules, but has no LC_COLLATE or LC_CTYPE alternatives. Therefore SHOW lc_collate and SHOW lc_ctype fail during parsing and never produce tree.ShowVar nodes. If parsing succeeded, server/ast/show_var.go:27-87 would lower an unqualified setting to a session column, and server/config/parameters_list.go:1841-1861 registers both names with en_US.UTF-8 defaults. server/config/parameters.go:145-160 also marks both names read-only, which explains why SET and RESET are rejected and why current_setting continues to return the default. The smallest fix is to add LC_COLLATE and LC_CTYPE alternatives to session_var, mapping them to the lowercase parameter names, then add parser coverage for both SHOW commands.
Evidence Package

Tip

Reply with @itoqa to send us feedback on this test run.

}

// has_table_privilege_name_text_text represents the PostgreSQL function of the same name, taking the same parameters.
var has_table_privilege_name_text_text = framework.Function3{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View All Evidence

High severity Privilege checks approve users without access

What failed: All six privilege checks returned true for denied and nonexistent inputs, even though the independent protected read was rejected.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: High High severity
  • Impact: Clients that rely on these permission checks may let unauthorized users access protected tables or actions. The checks also report access for missing users and tables, making authorization decisions unsafe.
  • Steps to Reproduce:
    1. Create or use a non-admin role with no privilege on a protected table.
    2. Call each supported has_table_privilege signature for that role, the protected table, and SELECT.
    3. Repeat the calls with a nonexistent role or table.
    4. Compare the returned booleans with an independent protected table read as the non-admin role.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: server/functions/has_table_privilege.go registers six overloads: name,text,text at lines 35-44, name,oid,text at lines 47-56, oid,text,text at lines 59-68, oid,oid,text at lines 71-80, text,text at lines 83-92, and oid,text at lines 95-104. Every Callable ignores its input values and returns the literal true with no lookup, role check, table check, or privilege check. The TODO comments in each implementation explicitly leave the authorization work undone. By contrast, server/auth/table_privileges.go:63-92 implements HasTablePrivilege: it grants superusers, checks schema-wide and table-specific grants, checks group membership, and returns false at line 91 when no matching grant exists. The new SQL functions do not call that helper or any equivalent authorization path, so their results cannot represent the application's actual permission state. The smallest practical fix is to resolve each overload's role and table arguments, map the requested privilege, and delegate to auth.HasTablePrivilege, returning false when no grant exists; until that is implemented, these probes should not be exposed as authorization answers.
  • Why this is likely a bug: The function name and PostgreSQL-compatible signatures promise a permission query, but every input produces true, including nonexistent principals and objects. The recorded probe returned true for all six overloads in both denied and nonexistent cases, while the independent protected operation was rejected, confirming that the function result is disconnected from enforcement. This is a security-relevant contract failure for any client that uses has_table_privilege as a gate. The PR directly introduced the six unconditional implementations, so the targeted remediation is to replace those returns with calls into the existing privilege evaluation path or to fail clearly until that path is wired in.
Relevant code

server/functions/has_table_privilege.go:35-44

var has_table_privilege_name_text_text = framework.Function3{
	Name: "has_table_privilege",
	Return: pgtypes.Bool,
	Callable: func(ctx *sql.Context, _ [4]*pgtypes.DoltgresType, val1, val2, val3 any) (any, error) {
		return true, nil
	},
}

server/functions/has_table_privilege.go:47-104

The remaining five registered overloads have the same Callable shape and each returns the literal true: lines 52-54, 64-66, 76-78, 88-90, and 100-102.

server/auth/table_privileges.go:63-92

func HasTablePrivilege(key TablePrivilegeKey, privilege Privilege) bool {
	if IsSuperUser(key.Role) {
		return true
	}
	...
	return false
}

server/functions/init.go:124-129

initHasDatabasePrivilege()
initHasSchemaPrivilege()
initHasTablePrivilege()
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**High severity — Privilege checks approve users without access**

**What failed:** All six privilege checks returned true for denied and nonexistent inputs, even though the independent protected read was rejected.

- **Impact:** Clients that rely on these permission checks may let unauthorized users access protected tables or actions. The checks also report access for missing users and tables, making authorization decisions unsafe.
- **Steps to reproduce:**
  1. Create or use a non-admin role with no privilege on a protected table.
  2. Call each supported has_table_privilege signature for that role, the protected table, and SELECT.
  3. Repeat the calls with a nonexistent role or table.
  4. Compare the returned booleans with an independent protected table read as the non-admin role.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** server/functions/has_table_privilege.go registers six overloads: name,text,text at lines 35-44, name,oid,text at lines 47-56, oid,text,text at lines 59-68, oid,oid,text at lines 71-80, text,text at lines 83-92, and oid,text at lines 95-104. Every Callable ignores its input values and returns the literal true with no lookup, role check, table check, or privilege check. The TODO comments in each implementation explicitly leave the authorization work undone. By contrast, server/auth/table_privileges.go:63-92 implements HasTablePrivilege: it grants superusers, checks schema-wide and table-specific grants, checks group membership, and returns false at line 91 when no matching grant exists. The new SQL functions do not call that helper or any equivalent authorization path, so their results cannot represent the application's actual permission state. The smallest practical fix is to resolve each overload's role and table arguments, map the requested privilege, and delegate to auth.HasTablePrivilege, returning false when no grant exists; until that is implemented, these probes should not be exposed as authorization answers.
- **Why this is likely a bug:** The function name and PostgreSQL-compatible signatures promise a permission query, but every input produces true, including nonexistent principals and objects. The recorded probe returned true for all six overloads in both denied and nonexistent cases, while the independent protected operation was rejected, confirming that the function result is disconnected from enforcement. This is a security-relevant contract failure for any client that uses has_table_privilege as a gate. The PR directly introduced the six unconditional implementations, so the targeted remediation is to replace those returns with calls into the existing privilege evaluation path or to fail clearly until that path is wired in.

**Relevant code:**

`server/functions/has_table_privilege.go:35-44`

~~~go
var has_table_privilege_name_text_text = framework.Function3{
	Name: "has_table_privilege",
	Return: pgtypes.Bool,
	Callable: func(ctx *sql.Context, _ [4]*pgtypes.DoltgresType, val1, val2, val3 any) (any, error) {
		return true, nil
	},
}
~~~

`server/functions/has_table_privilege.go:47-104`

~~~go
The remaining five registered overloads have the same Callable shape and each returns the literal true: lines 52-54, 64-66, 76-78, 88-90, and 100-102.
~~~

`server/auth/table_privileges.go:63-92`

~~~go
func HasTablePrivilege(key TablePrivilegeKey, privilege Privilege) bool {
	if IsSuperUser(key.Role) {
		return true
	}
	...
	return false
}
~~~

`server/functions/init.go:124-129`

~~~go
initHasDatabasePrivilege()
initHasSchemaPrivilege()
initHasTablePrivilege()
~~~

const aclexplodeName = "aclexplode"

// aclexplode represents the PostgreSQL function of the same name, taking the same parameters.
var aclexplode = framework.Function1{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View All Evidence

Medium severity Permission data fails during concurrent startup

What failed: Each concurrent client failed with an error saying it could not find field 1 in a row with 1 column. The same failure was reproducible after the concurrent work finished.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: Clients that read permission data through the compatibility function receive an error instead of the ACL rows they need. Other database functions continue to work, so the failure is limited to workflows that use this permission lookup.
  • Steps to Reproduce:
    1. Start a fresh local server.
    2. Open eight independent PostgreSQL client connections at the same time.
    3. Run SELECT * FROM aclexplode(ARRAY['x']::text[]) in each connection.
    4. Check the result from each client and then run the same query again after the concurrent calls finish.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The PR adds aclexplode as a framework.Function1 in server/functions/aclexplode.go:33-42. It declares Return: pgtypes.Record and OutParams with four non-null columns named grantor, grantee, privilege_type, and is_grantable at lines 44-50, so the SQL layer must materialize rows matching that schema. However, the Callable at lines 38-40 unconditionally returns nil, nil for a non-NULL input. The recorded query uses a non-NULL text array, so strictness does not short-circuit the call; the framework attempts to read record fields from the nil result and raises the missing-field error. server/functions/init.go:76 adds initAclexplode to the normal function registration sequence. The shared initialization path is protected by sync.Once in server/initialization/initialization.go:48-78, and the eight clients consistently resolved neighboring functions, which rules out concurrent initialization as the cause. The earlier pg_proc count of zero is not evidence of a race because server/tables/pgcatalog/pg_proc.go:94-96 explicitly leaves built-in function enumeration unimplemented. The smallest practical fix is to implement the ACL conversion and return rows with the four declared fields, or, if this compatibility function is intentionally still a stub, return a framework-supported typed empty row iterator or a deliberate unsupported-function error rather than nil.
  • Why this is likely a bug: The failure is deterministic across eight independent sessions and on serial readback, while length, pg_get_keywords, has_table_privilege, and pg_blocking_pids continue to work. That pattern matches the new function's invalid record result, not a timing-dependent catalog race. A client cannot consume the advertised ACL result at all, and the source contains an explicit TODO acknowledging that the function is incomplete; replacing the nil result with a typed empty result or implementing the four-field rows would prevent the hard execution error without changing unrelated initialization code.
Relevant code

server/functions/aclexplode.go:33-41

var aclexplode = framework.Function1{
	Name:       aclexplodeName,
	Return:     pgtypes.Record,
	Parameters: [1]*pgtypes.DoltgresType{pgtypes.TextArray},
	Strict:     true,
	Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
		return nil, nil
	},

server/functions/aclexplode.go:44-50

var aclexplodeOutArgs = sql.Schema{
	{Name: "grantor", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: aclexplodeName},
	{Name: "grantee", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: aclexplodeName},
	{Name: "privilege_type", Type: pgtypes.Text, Default: nil, Nullable: false, Source: aclexplodeName},
	{Name: "is_grantable", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: aclexplodeName},
}

server/initialization/initialization.go:48-63

var once = &sync.Once{}

func Initialize(...) {
	once.Do(func() {
		...
		config.Init()
		...
		functions.Init()
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Medium severity — Permission data fails during concurrent startup**

**What failed:** Each concurrent client failed with an error saying it could not find field 1 in a row with 1 column. The same failure was reproducible after the concurrent work finished.

- **Impact:** Clients that read permission data through the compatibility function receive an error instead of the ACL rows they need. Other database functions continue to work, so the failure is limited to workflows that use this permission lookup.
- **Steps to reproduce:**
  1. Start a fresh local server.
  2. Open eight independent PostgreSQL client connections at the same time.
  3. Run SELECT * FROM aclexplode(ARRAY['x']::text[]) in each connection.
  4. Check the result from each client and then run the same query again after the concurrent calls finish.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** The PR adds aclexplode as a framework.Function1 in server/functions/aclexplode.go:33-42. It declares Return: pgtypes.Record and OutParams with four non-null columns named grantor, grantee, privilege_type, and is_grantable at lines 44-50, so the SQL layer must materialize rows matching that schema. However, the Callable at lines 38-40 unconditionally returns nil, nil for a non-NULL input. The recorded query uses a non-NULL text array, so strictness does not short-circuit the call; the framework attempts to read record fields from the nil result and raises the missing-field error. server/functions/init.go:76 adds initAclexplode to the normal function registration sequence. The shared initialization path is protected by sync.Once in server/initialization/initialization.go:48-78, and the eight clients consistently resolved neighboring functions, which rules out concurrent initialization as the cause. The earlier pg_proc count of zero is not evidence of a race because server/tables/pgcatalog/pg_proc.go:94-96 explicitly leaves built-in function enumeration unimplemented. The smallest practical fix is to implement the ACL conversion and return rows with the four declared fields, or, if this compatibility function is intentionally still a stub, return a framework-supported typed empty row iterator or a deliberate unsupported-function error rather than nil.
- **Why this is likely a bug:** The failure is deterministic across eight independent sessions and on serial readback, while length, pg_get_keywords, has_table_privilege, and pg_blocking_pids continue to work. That pattern matches the new function's invalid record result, not a timing-dependent catalog race. A client cannot consume the advertised ACL result at all, and the source contains an explicit TODO acknowledging that the function is incomplete; replacing the nil result with a typed empty result or implementing the four-field rows would prevent the hard execution error without changing unrelated initialization code.

**Relevant code:**

`server/functions/aclexplode.go:33-41`

~~~go
var aclexplode = framework.Function1{
	Name:       aclexplodeName,
	Return:     pgtypes.Record,
	Parameters: [1]*pgtypes.DoltgresType{pgtypes.TextArray},
	Strict:     true,
	Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
		return nil, nil
	},
~~~

`server/functions/aclexplode.go:44-50`

~~~go
var aclexplodeOutArgs = sql.Schema{
	{Name: "grantor", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: aclexplodeName},
	{Name: "grantee", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: aclexplodeName},
	{Name: "privilege_type", Type: pgtypes.Text, Default: nil, Nullable: false, Source: aclexplodeName},
	{Name: "is_grantable", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: aclexplodeName},
}
~~~

`server/initialization/initialization.go:48-63`

~~~go
var once = &sync.Once{}

func Initialize(...) {
	once.Do(func() {
		...
		config.Init()
		...
		functions.Init()
~~~

const aclexplodeName = "aclexplode"

// aclexplode represents the PostgreSQL function of the same name, taking the same parameters.
var aclexplode = framework.Function1{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View replay

Medium severity ACL catalog call crashes on a valid array

What failed: The ACL catalog function is listed as available, but a valid call fails before it can return an empty result or ACL rows.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: Applications that call the ACL catalog function with valid input receive a database error instead of ACL rows or an empty result. Other tested built-in functions continue to work, so the impact is limited to this catalog function and its callers.
  • Steps to Reproduce:
    1. Start a fresh local server and connect to the postgres database with a PostgreSQL client.
    2. Call aclexplode with a non-NULL text array, such as SELECT * FROM aclexplode(ARRAY[]::text[]).
    3. Observe the error: unable to find field with index 1 in row of 1 columns.
    4. Call length('catalog'), pg_get_keywords(), or pg_blocking_pids(pg_backend_pid()) in the same fresh catalog to confirm that initialization and the neighboring compatibility functions still work.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: server/functions/aclexplode.go:33-42 declares aclexplode as framework.Function1 with Return set to pgtypes.Record, a text-array parameter, Strict=true, and OutParams set to aclexplodeOutArgs. The output schema at lines 45-50 contains four non-null fields: grantor (oid), grantee (oid), privilege_type (text), and is_grantable (bool). However, the Callable at lines 38-40 returns nil, nil for every non-NULL value. The function framework therefore receives a successful nil result while the executor still expects a SETOF record with four fields; materializing that result produces the observed missing field index error. This is distinct from startup ordering: server/initialization/initialization.go:52-78 uses sync.Once and calls functions.Init at line 62 before framework.Initialize at line 72, and the neighboring functions resolve in the same fresh session. A targeted fix is to make the callable return the framework's typed empty set-returning iterator for the current stub behavior, or return correctly shaped ACL rows after implementing the intended logic; it should not return an untyped nil for a declared record result.
  • Why this is likely a bug: The failure is deterministic for a normal non-NULL SQL input and is reported by the database executor as an internal bug, not as an unsupported input or a clean compatibility limitation. The source shows the exact mismatch: a four-column record result is declared, while the callable supplies no row shape at all. The same fresh session successfully calls length, has_table_privilege, pg_get_keywords, and pg_blocking_pids, which rules out a general startup or catalog-registration failure. Because aclexplode was added by this PR and the bad return value is in that added file, the PR is the direct cause. Returning a typed empty result would preserve the current stub semantics with the smallest change; implementing ACL expansion is a larger follow-up only if real ACL rows are required.
Relevant code

server/functions/aclexplode.go:33-42

var aclexplode = framework.Function1{
	Name:       aclexplodeName,
	Return:     pgtypes.Record,
	Parameters: [1]*pgtypes.DoltgresType{pgtypes.TextArray},
	Strict:     true,
	Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
		return nil, nil
	},
	OutParams: aclexplodeOutArgs,
}

server/functions/aclexplode.go:44-50

var aclexplodeOutArgs = sql.Schema{
	{Name: "grantor", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: aclexplodeName},
	{Name: "grantee", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: aclexplodeName},
	{Name: "privilege_type", Type: pgtypes.Text, Default: nil, Nullable: false, Source: aclexplodeName},
	{Name: "is_grantable", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: aclexplodeName},
}

server/initialization/initialization.go:50-72

func Initialize(dEnv *env.DoltEnv, cfg *doltgresservercfg.DoltgresConfig) {
	once.Do(func() {
		core.Init()
		...
		functions.Init()
		...
		framework.Initialize(ast.Convert)
	})
}
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Medium severity — ACL catalog call crashes on a valid array**

**What failed:** The ACL catalog function is listed as available, but a valid call fails before it can return an empty result or ACL rows.

- **Impact:** Applications that call the ACL catalog function with valid input receive a database error instead of ACL rows or an empty result. Other tested built-in functions continue to work, so the impact is limited to this catalog function and its callers.
- **Steps to reproduce:**
  1. Start a fresh local server and connect to the postgres database with a PostgreSQL client.
  2. Call aclexplode with a non-NULL text array, such as SELECT * FROM aclexplode(ARRAY[]::text[]).
  3. Observe the error: unable to find field with index 1 in row of 1 columns.
  4. Call length('catalog'), pg_get_keywords(), or pg_blocking_pids(pg_backend_pid()) in the same fresh catalog to confirm that initialization and the neighboring compatibility functions still work.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** server/functions/aclexplode.go:33-42 declares aclexplode as framework.Function1 with Return set to pgtypes.Record, a text-array parameter, Strict=true, and OutParams set to aclexplodeOutArgs. The output schema at lines 45-50 contains four non-null fields: grantor (oid), grantee (oid), privilege_type (text), and is_grantable (bool). However, the Callable at lines 38-40 returns nil, nil for every non-NULL value. The function framework therefore receives a successful nil result while the executor still expects a SETOF record with four fields; materializing that result produces the observed missing field index error. This is distinct from startup ordering: server/initialization/initialization.go:52-78 uses sync.Once and calls functions.Init at line 62 before framework.Initialize at line 72, and the neighboring functions resolve in the same fresh session. A targeted fix is to make the callable return the framework's typed empty set-returning iterator for the current stub behavior, or return correctly shaped ACL rows after implementing the intended logic; it should not return an untyped nil for a declared record result.
- **Why this is likely a bug:** The failure is deterministic for a normal non-NULL SQL input and is reported by the database executor as an internal bug, not as an unsupported input or a clean compatibility limitation. The source shows the exact mismatch: a four-column record result is declared, while the callable supplies no row shape at all. The same fresh session successfully calls length, has_table_privilege, pg_get_keywords, and pg_blocking_pids, which rules out a general startup or catalog-registration failure. Because aclexplode was added by this PR and the bad return value is in that added file, the PR is the direct cause. Returning a typed empty result would preserve the current stub semantics with the smallest change; implementing ACL expansion is a larger follow-up only if real ACL rows are required.

**Relevant code:**

`server/functions/aclexplode.go:33-42`

~~~go
var aclexplode = framework.Function1{
	Name:       aclexplodeName,
	Return:     pgtypes.Record,
	Parameters: [1]*pgtypes.DoltgresType{pgtypes.TextArray},
	Strict:     true,
	Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
		return nil, nil
	},
	OutParams: aclexplodeOutArgs,
}
~~~

`server/functions/aclexplode.go:44-50`

~~~go
var aclexplodeOutArgs = sql.Schema{
	{Name: "grantor", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: aclexplodeName},
	{Name: "grantee", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: aclexplodeName},
	{Name: "privilege_type", Type: pgtypes.Text, Default: nil, Nullable: false, Source: aclexplodeName},
	{Name: "is_grantable", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: aclexplodeName},
}
~~~

`server/initialization/initialization.go:50-72`

~~~go
func Initialize(dEnv *env.DoltEnv, cfg *doltgresservercfg.DoltgresConfig) {
	once.Do(func() {
		core.Init()
		...
		functions.Init()
		...
		framework.Initialize(ast.Convert)
	})
}
~~~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant