Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions server/config/parameters_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,17 @@ var postgresConfigParameters = map[string]sql.SystemVariable{
ResetVal: int64(1),
Scope: GetPgsqlScope(PsqlScopeSession),
},
"force_parallel_mode": &Parameter{
Name: "force_parallel_mode",
Default: "off",
Category: "Developer Options",
ShortDesc: "Forces use of parallel query facilities.",
Context: ParameterContextUser,
Type: types.NewSystemEnumType("force_parallel_mode", "off", "on", "regress"),
Source: ParameterSourceDefault,
ResetVal: "off",
Scope: GetPgsqlScope(PsqlScopeSession),
},
"from_collapse_limit": &Parameter{
Name: "from_collapse_limit",
Default: int64(8),
Expand Down Expand Up @@ -1827,6 +1838,28 @@ var postgresConfigParameters = map[string]sql.SystemVariable{
ResetVal: "FILE:/usr/local/etc/postgresql/krb5.keytab",
Scope: GetPgsqlScope(PsqlScopeSession),
},
"lc_collate": &Parameter{
Name: "lc_collate",
Default: "en_US.UTF-8",
Category: "Preset Options",
ShortDesc: "Shows the collation order locale.",
Context: ParameterContextInternal,
Type: types.NewSystemStringType("lc_collate"),
Source: ParameterSourceDefault,
ResetVal: "en_US.UTF-8",
Scope: GetPgsqlScope(PsqlScopeSession),
},
"lc_ctype": &Parameter{
Name: "lc_ctype",
Default: "en_US.UTF-8",
Category: "Preset Options",
ShortDesc: "Shows the character classification and case conversion locale.",
Context: ParameterContextInternal,
Type: types.NewSystemStringType("lc_ctype"),
Source: ParameterSourceDefault,
ResetVal: "en_US.UTF-8",
Scope: GetPgsqlScope(PsqlScopeSession),
},
"lc_messages": &Parameter{
Name: "lc_messages",
Default: "en_US.UTF-8",
Expand Down Expand Up @@ -2787,6 +2820,17 @@ var postgresConfigParameters = map[string]sql.SystemVariable{
ResetVal: "",
Scope: GetPgsqlScope(PsqlScopeSession),
},
"promote_trigger_file": &Parameter{
Name: "promote_trigger_file",
Default: "",
Category: "Replication / Standby Servers",
ShortDesc: "Specifies a file name whose presence ends recovery in the standby.",
Context: ParameterContextSighup,
Type: types.NewSystemStringType("promote_trigger_file"),
Source: ParameterSourceDefault,
ResetVal: "",
Scope: GetPgsqlScope(PsqlScopeSession),
},
"quote_all_identifiers": &Parameter{
Name: "quote_all_identifiers",
Default: int8(0),
Expand Down Expand Up @@ -2997,6 +3041,17 @@ var postgresConfigParameters = map[string]sql.SystemVariable{
ResetVal: "",
Scope: GetPgsqlScope(PsqlScopeSession),
},
"restrict_nonsystem_relation_kind": &Parameter{
Name: "restrict_nonsystem_relation_kind",
Default: "",
Category: "Client Connection Defaults / Statement Behavior",
ShortDesc: "Prohibits access to non-system relations of specified kinds.",
Context: ParameterContextUser,
Type: types.NewSystemStringType("restrict_nonsystem_relation_kind"),
Source: ParameterSourceDefault,
ResetVal: "",
Scope: GetPgsqlScope(PsqlScopeSession),
},
"row_security": &Parameter{
Name: "row_security",
Default: int8(1),
Expand Down Expand Up @@ -3909,6 +3964,18 @@ var postgresConfigParameters = map[string]sql.SystemVariable{
ResetVal: int64(2),
Scope: GetPgsqlScope(PsqlScopeSession),
},
"vacuum_defer_cleanup_age": &Parameter{
Name: "vacuum_defer_cleanup_age",
Default: int64(0),
Category: "Replication / Primary Server",
ShortDesc: "Number of transactions by which VACUUM and HOT cleanup should be deferred, if any.",
Context: ParameterContextSighup,
Type: types.NewSystemIntType("vacuum_defer_cleanup_age", 0, 1073741823, false),
Source: ParameterSourceDefault,
// min: 0 , max: 1000000
ResetVal: int64(0),
Scope: GetPgsqlScope(PsqlScopeSession),
},
"vacuum_failsafe_age": &Parameter{
Name: "vacuum_failsafe_age",
Default: int64(1600000000),
Expand Down
50 changes: 50 additions & 0 deletions server/functions/aclexplode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package functions

import (
"github.com/dolthub/go-mysql-server/sql"

"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)

// initAclexplode registers the functions to the catalog.
func initAclexplode() {
framework.RegisterFunction(aclexplode) // TODO: This breaks pgAdmin 4 because of the unsupported aclitem[] type
}

// aclexplodeName is the name for aclexplode function.
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()
~~~

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)
	})
}
~~~

Name: aclexplodeName,
Return: pgtypes.Record, // SETOF record
Parameters: [1]*pgtypes.DoltgresType{pgtypes.TextArray}, // TODO: type aclitem[]
Strict: true,
Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
return nil, nil
},
OutParams: aclexplodeOutArgs,
}

// aclexplodeOutArgs is the schema for aclexplode table function. Each column is OUT argument.
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},
}
104 changes: 104 additions & 0 deletions server/functions/has_table_privilege.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package functions

import (
"github.com/dolthub/go-mysql-server/sql"

"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)

// initHasTablePrivilege registers the functions to the catalog.
func initHasTablePrivilege() {
framework.RegisterFunction(has_table_privilege_name_text_text)
framework.RegisterFunction(has_table_privilege_name_oid_text)
framework.RegisterFunction(has_table_privilege_oid_text_text)
framework.RegisterFunction(has_table_privilege_oid_oid_text)
framework.RegisterFunction(has_table_privilege_text_text)
framework.RegisterFunction(has_table_privilege_oid_text)
}

// 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()
~~~

Name: "has_table_privilege",
Return: pgtypes.Bool,
Parameters: [3]*pgtypes.DoltgresType{pgtypes.Name, pgtypes.Text, pgtypes.Text},
Strict: true,
Callable: func(ctx *sql.Context, _ [4]*pgtypes.DoltgresType, val1, val2, val3 any) (any, error) {
// TODO does user have privilege for schema
return true, nil
},
}

// has_table_privilege_name_oid_text represents the PostgreSQL function of the same name, taking the same parameters.
var has_table_privilege_name_oid_text = framework.Function3{
Name: "has_table_privilege",
Return: pgtypes.Bool,
Parameters: [3]*pgtypes.DoltgresType{pgtypes.Name, pgtypes.Oid, pgtypes.Text},
Strict: true,
Callable: func(ctx *sql.Context, _ [4]*pgtypes.DoltgresType, val1, val2, val3 any) (any, error) {
// TODO does user have privilege for schema
return true, nil
},
}

// has_table_privilege_oid_text_text represents the PostgreSQL function of the same name, taking the same parameters.
var has_table_privilege_oid_text_text = framework.Function3{
Name: "has_table_privilege",
Return: pgtypes.Bool,
Parameters: [3]*pgtypes.DoltgresType{pgtypes.Oid, pgtypes.Text, pgtypes.Text},
Strict: true,
Callable: func(ctx *sql.Context, _ [4]*pgtypes.DoltgresType, val1, val2, val3 any) (any, error) {
// TODO does user have privilege for schema
return true, nil
},
}

// has_table_privilege_oid_oid_text represents the PostgreSQL function of the same name, taking the same parameters.
var has_table_privilege_oid_oid_text = framework.Function3{
Name: "has_table_privilege",
Return: pgtypes.Bool,
Parameters: [3]*pgtypes.DoltgresType{pgtypes.Oid, pgtypes.Oid, pgtypes.Text},
Strict: true,
Callable: func(ctx *sql.Context, _ [4]*pgtypes.DoltgresType, val1, val2, val3 any) (any, error) {
// TODO does user have privilege for schema
return true, nil
},
}

// has_table_privilege_text_text represents the PostgreSQL function of the same name, taking the same parameters.
var has_table_privilege_text_text = framework.Function2{
Name: "has_table_privilege",
Return: pgtypes.Bool,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Text, pgtypes.Text},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
// TODO does current user have privilege for schema
return true, nil
},
}

// has_table_privilege_oid_text represents the PostgreSQL function of the same name, taking the same parameters.
var has_table_privilege_oid_text = framework.Function2{
Name: "has_table_privilege",
Return: pgtypes.Bool,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Oid, pgtypes.Text},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
// TODO does current user have privilege for schema
return true, nil
},
}
4 changes: 4 additions & 0 deletions server/functions/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func initTypeFunctions() {
func Init() {
initTypeFunctions()
initAbs()
initAclexplode()
initAcos()
initAcosd()
initAcosh()
Expand Down Expand Up @@ -125,6 +126,7 @@ func Init() {
initGetdatabaseencoding()
initHasDatabasePrivilege()
initHasSchemaPrivilege()
initHasTablePrivilege()
initInitcap()
initLcm()
initLeft()
Expand All @@ -145,6 +147,7 @@ func Init() {
initOctetLength()
initPgAvailableExtensionVersions()
initPgBackendPid()
initPgBlockingPids()
initPgCharToEncoding()
initPgCollationIsVisible()
initPgConversionIsVisible()
Expand All @@ -158,6 +161,7 @@ func Init() {
initPgGetFunctionResult()
initPgGetFunctionSqlbody()
initPgGetIndexDef()
initPgGetKeywords()
initPgGetPartKeyDef()
initPgGetRuledef()
initPgGetTriggerDef()
Expand Down
39 changes: 39 additions & 0 deletions server/functions/pg_blocking_pids.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package functions

import (
"github.com/dolthub/go-mysql-server/sql"

"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)

// initPgBlockingPids registers the functions to the catalog.
func initPgBlockingPids() {
framework.RegisterFunction(pg_blocking_pids)
}

// pg_blocking_pids represents the PostgreSQL system catalog information function.
var pg_blocking_pids = framework.Function1{
Name: "pg_blocking_pids",
Return: pgtypes.Int32Array,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.Int32},
Strict: true,
Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
// TODO
return nil, nil
},
}
Loading
Loading