-
Notifications
You must be signed in to change notification settings - Fork 32
fix: rewrite SET search_path in function definitions for temp schema (#335) #336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f3418ce
aec0e8f
ad8a414
a8b68dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -174,6 +174,91 @@ func stripSchemaQualifications(sql string, schemaName string) string { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // replaceSchemaInSearchPath replaces the target schema name in SET search_path clauses | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // within function/procedure definitions. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Purpose: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // When functions or procedures have SET search_path = public, pg_temp (or similar), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // PostgreSQL uses that search_path during function body validation (for SQL-language functions) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // and execution. When applying to a temporary schema, we need to replace the target schema | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // in these clauses so that table references in function bodies can be resolved. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Example (when target schema is "public" and temp schema is "pgschema_tmp_xxx"): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // SET search_path = public, pg_temp -> SET search_path = "pgschema_tmp_xxx", pg_temp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // SET search_path TO public -> SET search_path TO "pgschema_tmp_xxx" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This handles both = and TO syntax, quoted and unquoted schema names (case-insensitive), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // and preserves other schemas in the comma-separated list. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Limitations: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // - Like stripSchemaQualifications and replaceSchemaInDefaultPrivileges, this function | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // operates on the raw SQL string without dollar-quote awareness. A SET search_path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // inside a $$-quoted function body (e.g., dynamic SQL) would also be rewritten. In | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // practice this is not an issue because such usage is extremely rare, and the round-trip | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // through database inspection and normalizeSchemaNames restores the original schema name. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // - When targetSchema is "public", replacing it removes "public" from the function's | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // search_path. If the function body references extension objects installed in "public" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // (e.g., citext), they may not be found. Most extension objects (uuid, jsonb, etc.) live | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // in pg_catalog which is always searched, so this is rarely an issue in practice. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func replaceSchemaInSearchPath(sql string, targetSchema, tempSchema string) string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if targetSchema == "" || tempSchema == "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return sql | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| replacement := fmt.Sprintf(`"%s"`, tempSchema) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+200
to
+209
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // - When targetSchema is "public", replacing it removes "public" from the function's | |
| // search_path. If the function body references extension objects installed in "public" | |
| // (e.g., citext), they may not be found. Most extension objects (uuid, jsonb, etc.) live | |
| // in pg_catalog which is always searched, so this is rarely an issue in practice. | |
| func replaceSchemaInSearchPath(sql string, targetSchema, tempSchema string) string { | |
| if targetSchema == "" || tempSchema == "" { | |
| return sql | |
| } | |
| replacement := fmt.Sprintf(`"%s"`, tempSchema) | |
| // - When targetSchema is "public", we rewrite it to include both the temp schema and | |
| // "public" (e.g., public -> "pgschema_tmp_xxx", public). This keeps the temp schema | |
| // first for table resolution while preserving "public" as a fallback so extension | |
| // objects installed there remain resolvable during function validation. | |
| func replaceSchemaInSearchPath(sql string, targetSchema, tempSchema string) string { | |
| if targetSchema == "" || tempSchema == "" { | |
| return sql | |
| } | |
| replacement := fmt.Sprintf(`"%s"`, tempSchema) | |
| if strings.EqualFold(targetSchema, "public") { | |
| // Preserve "public" as a fallback so that extension objects installed in the | |
| // public schema remain visible during function creation/validation. | |
| replacement = fmt.Sprintf(`"%s", public`, tempSchema) | |
| } |
Copilot
AI
Mar 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replaceSchemaInSearchPath does schema token replacement case-sensitively (both the quoted and unquoted patterns). For unquoted identifiers, PostgreSQL folds case, so inputs like SET search_path = PUBLIC, pg_temp (or Public) still refer to public but won’t be rewritten and can reintroduce the same temp-schema validation failure. Consider making the schema match case-insensitive for unquoted tokens (and avoid matching inside double quotes), or parse the comma-separated search_path list and rewrite tokens by comparing normalized identifier names.
Copilot
AI
Mar 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic replaces the target schema token with the temp schema, which means the target schema is removed from the function’s search_path entirely. That can break creation-time validation for functions that rely on objects that exist only in the target schema (commonly extensions installed into public, e.g. gen_random_uuid() from pgcrypto). Consider rewriting to include the temp schema (e.g., insert it before the target schema) while keeping the original schemas as fallbacks, and then normalize/dedupe fn.SearchPath so generated DDL matches the original intent.
| for i, token := range tokens { | |
| trimmed := strings.TrimSpace(token) | |
| if strings.HasPrefix(trimmed, `"`) && strings.HasSuffix(trimmed, `"`) { | |
| // Quoted identifier: case-sensitive exact match. | |
| // "public" matches targetSchema "public", but "PUBLIC" does not | |
| // (in PostgreSQL, quoted identifiers preserve case). | |
| inner := trimmed[1 : len(trimmed)-1] | |
| if inner == targetSchema { | |
| tokens[i] = strings.Replace(token, trimmed, replacement, 1) | |
| } | |
| } else { | |
| // Unquoted identifier: case-insensitive match. | |
| // PostgreSQL folds unquoted identifiers to lowercase. | |
| if strings.EqualFold(trimmed, targetSchema) { | |
| tokens[i] = strings.Replace(token, trimmed, replacement, 1) | |
| } | |
| } | |
| } | |
| return prefix + strings.Join(tokens, ",") + suffix | |
| var newTokens []string | |
| for _, token := range tokens { | |
| trimmed := strings.TrimSpace(token) | |
| matched := false | |
| if strings.HasPrefix(trimmed, `"`) && strings.HasSuffix(trimmed, `"`) { | |
| // Quoted identifier: case-sensitive exact match. | |
| // "public" matches targetSchema "public", but "PUBLIC" does not | |
| // (in PostgreSQL, quoted identifiers preserve case). | |
| inner := trimmed[1 : len(trimmed)-1] | |
| if inner == targetSchema { | |
| newTokens = append(newTokens, replacement) | |
| matched = true | |
| } | |
| } else { | |
| // Unquoted identifier: case-insensitive match. | |
| // PostgreSQL folds unquoted identifiers to lowercase. | |
| if strings.EqualFold(trimmed, targetSchema) { | |
| newTokens = append(newTokens, replacement) | |
| matched = true | |
| } | |
| } | |
| // Always keep the original token to preserve the original search_path semantics. | |
| newTokens = append(newTokens, token) | |
| } | |
| return prefix + strings.Join(newTokens, ",") + suffix |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SET search_path inside function body string literals
The searchPathPattern regex matches any SET search_path occurrence in the SQL, including those appearing as string content inside $$-quoted function bodies. For example, a PL/pgSQL function that uses dynamic SQL like EXECUTE 'SET search_path = public' would have the string literal's content incorrectly rewritten to EXECUTE 'SET search_path = "pgschema_tmp_xxx"'.
The issue arises because the regex [^\n;]+ captures up to the end of the line, and it will happily match inside $$…$$ bodies. While the common use case (function-level SET search_path = public, pg_temp as a function option) is handled correctly, any SET search_path that appears as part of a quoted string literal within a function body will also be modified, potentially corrupting dynamic SQL.
Consider guarding against this by only matching outside dollar-quoted blocks, or at minimum documenting this known limitation explicitly in the function's comment.
Copilot
AI
Mar 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When targetSchema == "public", replacing the matching entry with the temp schema removes public from the function's search_path (e.g. public, pg_temp → "pgschema_tmp...", pg_temp). This can break function creation/validation in the temp schema if the function body relies on extension objects installed in public (the session search_path explicitly keeps public as a fallback for this reason). Consider preserving public as a later fallback (while still prioritizing the temp schema), and then normalizing/de-duplicating the resulting search_path so the generated migration DDL remains unchanged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No unit tests for replaceSchemaInSearchPath
The new replaceSchemaInSearchPath function has no dedicated unit tests. The only coverage comes from the integration test at testdata/diff/create_function/issue_335_search_path_rewrite/. Neither stripSchemaQualifications nor replaceSchemaInDefaultPrivileges have unit tests either, but given the regex complexity here (two-pass quoted/unquoted replacement inside a ReplaceAllStringFunc callback), a targeted table-driven test would help catch edge cases such as:
SET search_path TO public(theTOsyntax)SET search_path = "public", pg_temp(quoted target schema)- Multiple functions in the same SQL file
- Schema name absent from the search path (should be a no-op)
- Empty
targetSchemaortempSchema(already guarded, but worth asserting)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,123 @@ | ||||||||||||||||||||
| package postgres | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import ( | ||||||||||||||||||||
| "testing" | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func TestReplaceSchemaInSearchPath(t *testing.T) { | ||||||||||||||||||||
| tests := []struct { | ||||||||||||||||||||
| name string | ||||||||||||||||||||
| sql string | ||||||||||||||||||||
| targetSchema string | ||||||||||||||||||||
| tempSchema string | ||||||||||||||||||||
| expected string | ||||||||||||||||||||
| }{ | ||||||||||||||||||||
| { | ||||||||||||||||||||
| name: "unquoted with equals", | ||||||||||||||||||||
| sql: "SET search_path = public, pg_temp", | ||||||||||||||||||||
| targetSchema: "public", | ||||||||||||||||||||
| tempSchema: "pgschema_tmp_20260302_000000_abcd1234", | ||||||||||||||||||||
| expected: `SET search_path = "pgschema_tmp_20260302_000000_abcd1234", pg_temp`, | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| name: "unquoted with TO", | ||||||||||||||||||||
| sql: "SET search_path TO public", | ||||||||||||||||||||
| targetSchema: "public", | ||||||||||||||||||||
| tempSchema: "pgschema_tmp_20260302_000000_abcd1234", | ||||||||||||||||||||
| expected: `SET search_path TO "pgschema_tmp_20260302_000000_abcd1234"`, | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| name: "quoted target schema", | ||||||||||||||||||||
| sql: `SET search_path = "public", pg_temp`, | ||||||||||||||||||||
| targetSchema: "public", | ||||||||||||||||||||
| tempSchema: "pgschema_tmp_20260302_000000_abcd1234", | ||||||||||||||||||||
| expected: `SET search_path = "pgschema_tmp_20260302_000000_abcd1234", pg_temp`, | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| name: "case insensitive schema match", | ||||||||||||||||||||
| sql: "SET search_path = PUBLIC, pg_temp", | ||||||||||||||||||||
| targetSchema: "public", | ||||||||||||||||||||
| tempSchema: "pgschema_tmp_20260302_000000_abcd1234", | ||||||||||||||||||||
| expected: `SET search_path = "pgschema_tmp_20260302_000000_abcd1234", pg_temp`, | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| name: "mixed case schema", | ||||||||||||||||||||
| sql: "SET search_path = Public, pg_temp", | ||||||||||||||||||||
| targetSchema: "public", | ||||||||||||||||||||
| tempSchema: "pgschema_tmp_20260302_000000_abcd1234", | ||||||||||||||||||||
| expected: `SET search_path = "pgschema_tmp_20260302_000000_abcd1234", pg_temp`, | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| name: "schema not in search_path is no-op", | ||||||||||||||||||||
| sql: "SET search_path = pg_catalog, pg_temp", | ||||||||||||||||||||
| targetSchema: "public", | ||||||||||||||||||||
| tempSchema: "pgschema_tmp_20260302_000000_abcd1234", | ||||||||||||||||||||
| expected: "SET search_path = pg_catalog, pg_temp", | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| name: "multiple functions in same SQL", | ||||||||||||||||||||
| sql: "CREATE FUNCTION f1() RETURNS void LANGUAGE sql SET search_path = public AS $$ SELECT 1; $$;\nCREATE FUNCTION f2() RETURNS void LANGUAGE sql SET search_path = public, pg_temp AS $$ SELECT 2; $$;", | ||||||||||||||||||||
| targetSchema: "public", | ||||||||||||||||||||
| tempSchema: "pgschema_tmp_xxx", | ||||||||||||||||||||
|
Comment on lines
+57
to
+61
|
||||||||||||||||||||
| expected: "CREATE FUNCTION f1() RETURNS void LANGUAGE sql SET search_path = \"pgschema_tmp_xxx\" AS $$ SELECT 1; $$;\nCREATE FUNCTION f2() RETURNS void LANGUAGE sql SET search_path = \"pgschema_tmp_xxx\", pg_temp AS $$ SELECT 2; $$;", | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| name: "empty target schema returns unchanged", | ||||||||||||||||||||
| sql: "SET search_path = public, pg_temp", | ||||||||||||||||||||
| targetSchema: "", | ||||||||||||||||||||
| tempSchema: "pgschema_tmp_xxx", | ||||||||||||||||||||
| expected: "SET search_path = public, pg_temp", | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| name: "empty temp schema returns unchanged", | ||||||||||||||||||||
| sql: "SET search_path = public, pg_temp", | ||||||||||||||||||||
| targetSchema: "public", | ||||||||||||||||||||
| tempSchema: "", | ||||||||||||||||||||
| expected: "SET search_path = public, pg_temp", | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| name: "no search_path in SQL is no-op", | ||||||||||||||||||||
| sql: "CREATE TABLE foo (id int);", | ||||||||||||||||||||
| targetSchema: "public", | ||||||||||||||||||||
| tempSchema: "pgschema_tmp_xxx", | ||||||||||||||||||||
| expected: "CREATE TABLE foo (id int);", | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| name: "non-public target schema", | ||||||||||||||||||||
| sql: "SET search_path = myschema, public", | ||||||||||||||||||||
| targetSchema: "myschema", | ||||||||||||||||||||
| tempSchema: "pgschema_tmp_xxx", | ||||||||||||||||||||
| expected: `SET search_path = "pgschema_tmp_xxx", public`, | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| name: "does not match partial schema names", | ||||||||||||||||||||
| sql: "SET search_path = public_data, pg_temp", | ||||||||||||||||||||
| targetSchema: "public", | ||||||||||||||||||||
| tempSchema: "pgschema_tmp_xxx", | ||||||||||||||||||||
| expected: "SET search_path = public_data, pg_temp", | ||||||||||||||||||||
| }, | ||||||||||||||||||||
|
||||||||||||||||||||
| }, | |
| }, | |
| { | |
| name: "does not replace quoted schema with different case", | |
| sql: `SET search_path = "PUBLIC", pg_temp`, | |
| targetSchema: "public", | |
| tempSchema: "pgschema_tmp_xxx", | |
| expected: `SET search_path = "PUBLIC", pg_temp`, | |
| }, |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| CREATE TABLE IF NOT EXISTS person_accounts ( | ||
| id uuid DEFAULT gen_random_uuid(), | ||
| first_name text, | ||
| last_name text, | ||
| email_address text NOT NULL, | ||
| created_at timestamptz DEFAULT now() NOT NULL, | ||
| modified_at timestamptz DEFAULT now() NOT NULL, | ||
| CONSTRAINT person_accounts_pkey PRIMARY KEY (id), | ||
| CONSTRAINT person_accounts_email_address_key UNIQUE (email_address) | ||
| ); | ||
|
|
||
| CREATE OR REPLACE FUNCTION auth_lookup_account_by_email( | ||
| input_email text | ||
| ) | ||
| RETURNS text | ||
| LANGUAGE sql | ||
| STABLE | ||
| SECURITY DEFINER | ||
| SET search_path = public, pg_temp | ||
| AS $$ | ||
| SELECT | ||
| pa.id::text AS person_account_id | ||
| FROM person_accounts pa | ||
| WHERE lower(pa.email_address) = lower(trim(input_email)) | ||
| LIMIT 1; | ||
| $$; | ||
|
|
||
| REVOKE EXECUTE ON FUNCTION auth_lookup_account_by_email(input_email text) FROM PUBLIC; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| CREATE TABLE person_accounts ( | ||
| id uuid DEFAULT gen_random_uuid(), | ||
| first_name text, | ||
| last_name text, | ||
| email_address text NOT NULL, | ||
| created_at timestamptz DEFAULT now() NOT NULL, | ||
| modified_at timestamptz DEFAULT now() NOT NULL, | ||
| CONSTRAINT person_accounts_pkey PRIMARY KEY (id), | ||
| CONSTRAINT person_accounts_email_address_key UNIQUE (email_address) | ||
| ); | ||
|
|
||
| -- SQL-language function with SET search_path = public, pg_temp | ||
| -- that references a table. PostgreSQL validates SQL function bodies at | ||
| -- creation time using the function's own search_path, not the session's. | ||
| -- This reproduces issue #335 where the function's search_path isn't | ||
| -- rewritten to point to the temporary schema. | ||
| CREATE OR REPLACE FUNCTION auth_lookup_account_by_email(input_email text) | ||
| RETURNS text | ||
| LANGUAGE sql | ||
| STABLE | ||
| SECURITY DEFINER | ||
| SET search_path = public, pg_temp | ||
| AS $$ | ||
| SELECT | ||
| pa.id::text AS person_account_id | ||
| FROM person_accounts pa | ||
| WHERE lower(pa.email_address) = lower(trim(input_email)) | ||
| LIMIT 1; | ||
| $$; | ||
|
|
||
| REVOKE ALL ON FUNCTION auth_lookup_account_by_email(text) FROM PUBLIC; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| -- Empty schema (no functions or tables) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| { | ||
| "version": "1.0.0", | ||
| "pgschema_version": "1.7.3", | ||
| "created_at": "1970-01-01T00:00:00Z", | ||
| "source_fingerprint": { | ||
| "hash": "965b1131737c955e24c7f827c55bd78e4cb49a75adfd04229e0ba297376f5085" | ||
| }, | ||
| "groups": [ | ||
| { | ||
| "steps": [ | ||
| { | ||
| "sql": "CREATE TABLE IF NOT EXISTS person_accounts (\n id uuid DEFAULT gen_random_uuid(),\n first_name text,\n last_name text,\n email_address text NOT NULL,\n created_at timestamptz DEFAULT now() NOT NULL,\n modified_at timestamptz DEFAULT now() NOT NULL,\n CONSTRAINT person_accounts_pkey PRIMARY KEY (id),\n CONSTRAINT person_accounts_email_address_key UNIQUE (email_address)\n);", | ||
| "type": "table", | ||
| "operation": "create", | ||
| "path": "public.person_accounts" | ||
| }, | ||
| { | ||
| "sql": "CREATE OR REPLACE FUNCTION auth_lookup_account_by_email(\n input_email text\n)\nRETURNS text\nLANGUAGE sql\nSTABLE\nSECURITY DEFINER\nSET search_path = public, pg_temp\nAS $$\n SELECT\n pa.id::text AS person_account_id\n FROM person_accounts pa\n WHERE lower(pa.email_address) = lower(trim(input_email))\n LIMIT 1;\n$$;", | ||
| "type": "function", | ||
| "operation": "create", | ||
| "path": "public.auth_lookup_account_by_email" | ||
| }, | ||
| { | ||
| "sql": "REVOKE EXECUTE ON FUNCTION auth_lookup_account_by_email(input_email text) FROM PUBLIC;", | ||
| "type": "revoked_default_privilege", | ||
| "operation": "create", | ||
| "path": "revoked_default.FUNCTION.auth_lookup_account_by_email(input_email text)" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| CREATE TABLE IF NOT EXISTS person_accounts ( | ||
| id uuid DEFAULT gen_random_uuid(), | ||
| first_name text, | ||
| last_name text, | ||
| email_address text NOT NULL, | ||
| created_at timestamptz DEFAULT now() NOT NULL, | ||
| modified_at timestamptz DEFAULT now() NOT NULL, | ||
| CONSTRAINT person_accounts_pkey PRIMARY KEY (id), | ||
| CONSTRAINT person_accounts_email_address_key UNIQUE (email_address) | ||
| ); | ||
|
|
||
| CREATE OR REPLACE FUNCTION auth_lookup_account_by_email( | ||
| input_email text | ||
| ) | ||
| RETURNS text | ||
| LANGUAGE sql | ||
| STABLE | ||
| SECURITY DEFINER | ||
| SET search_path = public, pg_temp | ||
| AS $$ | ||
| SELECT | ||
| pa.id::text AS person_account_id | ||
| FROM person_accounts pa | ||
| WHERE lower(pa.email_address) = lower(trim(input_email)) | ||
| LIMIT 1; | ||
| $$; | ||
|
|
||
| REVOKE EXECUTE ON FUNCTION auth_lookup_account_by_email(input_email text) FROM PUBLIC; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment about extension objects is misleading:
uuid/jsonbare built-in types (not extension objects), and many extension-provided functions/types are commonly installed in the target schema (oftenpublic). Since this rewrite can remove the target schema from the function’ssearch_path, the comment’s implication that this is “rarely an issue” may not hold. Please adjust the comment (and ideally the rewrite behavior) to reflect the actual risk.