Skip to content
Merged
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
6 changes: 6 additions & 0 deletions __fixtures__/generated/generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -21220,6 +21220,12 @@
"original/alter/alter-table-column-7.sql": "ALTER TABLE public.books\nADD COLUMN tags TEXT[] DEFAULT '{}'",
"original/alter/alter-table-column-8.sql": "CREATE TYPE mood AS ENUM ('happy', 'sad', 'neutral')",
"original/alter/alter-table-column-9.sql": "ALTER TABLE public.profiles\nADD COLUMN current_mood mood DEFAULT 'neutral'",
"misc/rename-qualified-and-sequence-privs-1.sql": "ALTER TYPE app.t2 RENAME TO t",
"misc/rename-qualified-and-sequence-privs-2.sql": "ALTER DOMAIN app.d2 RENAME TO d",
"misc/rename-qualified-and-sequence-privs-3.sql": "GRANT ALL ON SEQUENCE app.seq TO bob",
"misc/rename-qualified-and-sequence-privs-4.sql": "GRANT USAGE, SELECT ON SEQUENCE app.seq TO bob",
"misc/rename-qualified-and-sequence-privs-5.sql": "REVOKE ALL ON SEQUENCE app.seq FROM bob",
"misc/rename-qualified-and-sequence-privs-6.sql": "REVOKE UPDATE ON SEQUENCE app.seq FROM bob RESTRICT",
"misc/quotes_etc-1.sql": "CREATE USER MAPPING FOR local_user SERVER \"foreign_server\" OPTIONS (user 'remote_user', password 'secret123')",
"misc/quotes_etc-2.sql": "CREATE USER MAPPING FOR local_user SERVER foreign_server OPTIONS (user 'remote_user', password 'secret123')",
"misc/quotes_etc-3.sql": "SELECT E'Line 1\\nLine 2'",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- Qualified ALTER TYPE / ALTER DOMAIN RENAME must dot-join the name
-- Ref: constructive-io/pgsql-parser#328
ALTER TYPE app.t2 RENAME TO t;
ALTER DOMAIN app.d2 RENAME TO d;

-- GRANT/REVOKE ON SEQUENCE must keep the SEQUENCE keyword
-- Ref: constructive-io/pgsql-parser#328
GRANT ALL ON SEQUENCE app.seq TO bob;
GRANT USAGE, SELECT ON SEQUENCE app.seq TO bob;
REVOKE ALL ON SEQUENCE app.seq FROM bob;
REVOKE UPDATE ON SEQUENCE app.seq FROM bob RESTRICT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

import { FixtureTestUtils } from '../../test-utils';
const fixtures = new FixtureTestUtils();

it('misc-rename-qualified-and-sequence-privs', async () => {
await fixtures.runFixtureTests([
"misc/rename-qualified-and-sequence-privs-1.sql",
"misc/rename-qualified-and-sequence-privs-2.sql",
"misc/rename-qualified-and-sequence-privs-3.sql",
"misc/rename-qualified-and-sequence-privs-4.sql",
"misc/rename-qualified-and-sequence-privs-5.sql",
"misc/rename-qualified-and-sequence-privs-6.sql"
]);
});
10 changes: 10 additions & 0 deletions packages/deparser/src/deparser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8419,6 +8419,14 @@ export class Deparser implements DeparserVisitor {
} else {
output.push(this.visit(node.object, context));
}
} else if ((node.renameType === 'OBJECT_TYPE' || node.renameType === 'OBJECT_DOMAIN') && (node.object as any).List) {
// Qualified type names - join List parts with dots
const items = ListUtils.unwrapList(node.object as any);
const parts = items
.map((item: any) => item.String?.sval)
.filter((s: any) => typeof s === 'string')
.map((s: string) => this.quoteIfNeeded(s));
output.push(parts.join('.'));
} else if (node.renameType === 'OBJECT_SCHEMA' && (node.object as any).List) {
// Handle schema names - extract from List structure
const items = ListUtils.unwrapList(node.object as any);
Expand Down Expand Up @@ -8561,6 +8569,8 @@ export class Deparser implements DeparserVisitor {
output.push('SCHEMA');
} else if (node.objtype === 'OBJECT_LANGUAGE') {
output.push('LANGUAGE');
} else if (node.objtype === 'OBJECT_SEQUENCE') {
output.push('SEQUENCE');
} else if (node.objtype === 'OBJECT_FUNCTION') {
output.push('FUNCTION');
} else if (node.objtype === 'OBJECT_PROCEDURE') {
Expand Down
42 changes: 41 additions & 1 deletion packages/scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ const { sql: verifySql } = verifyFor(facts);

Nothing outside the supported vocabulary is ever guessed at: `revertFor` emits a `-- revert not derivable: <reason>` comment plus a warning; `verifyFor` emits nothing plus a warning. The list is exported as `SUPPORTED_STATEMENTS` (and `SUPPORTED_NODE_TAGS`).

## Node-level API

For consumers that compose inverses at the AST level (semantic diffing, migration generation) without round-tripping through deparsed text:

```ts
import { invertStatement, existenceCheck } from '@pgsql/scripts';

const inverse = invertStatement(facts[0]); // AST statement nodes, [] = nothing to revert, null = not derivable
const checks = existenceCheck(facts[0]); // SelectStmt check nodes, [] = nothing to check, null = not derivable
```

`invertStatement` returns the per-statement inverse as wrapped AST nodes (e.g. `{ DropStmt: {...} }`); `existenceCheck` returns the raise-on-failure checks as `SelectStmt` nodes. Both return `null` instead of guessing when derivation is not possible — including partially underivable multi-command statements.

## Supported statements

| Statement | Revert | Verify |
Expand All @@ -52,5 +65,32 @@ Nothing outside the supported vocabulary is ever guessed at: `revertFor` emits a
| `GRANT` privileges (tables, sequences, functions, schemas) | `REVOKE` same privileges | `has_table_privilege` / `has_function_privilege` / `has_schema_privilege` |
| `GRANT role TO role` | `REVOKE role FROM role` | `pg_auth_members` |
| `COMMENT ON` | `COMMENT ON ... IS NULL` | — |
| `CREATE MATERIALIZED VIEW` / `CREATE TABLE AS` | `DROP MATERIALIZED VIEW` / `DROP TABLE` | `to_regclass` |
| `CREATE SERVER` | `DROP SERVER` | `pg_foreign_server` |
| `CREATE FOREIGN TABLE` | `DROP FOREIGN TABLE` | `to_regclass` |
| `CREATE USER MAPPING` | `DROP USER MAPPING` | `pg_user_mappings` |
| `CREATE COLLATION` | `DROP COLLATION` | `pg_collation` |
| `CREATE AGGREGATE` | `DROP AGGREGATE` with input signature | `to_regprocedure` |
| `CREATE OPERATOR` (binary) | `DROP OPERATOR (left, right)` | `to_regoperator` |
| `CREATE CAST` | `DROP CAST (source AS target)` | `pg_cast` |
| `CREATE PUBLICATION` | `DROP PUBLICATION` | `pg_publication` |
| `CREATE SUBSCRIPTION` | `DROP SUBSCRIPTION` | `pg_subscription` |
| `CREATE STATISTICS` | `DROP STATISTICS` | `pg_statistic_ext` |
| `CREATE EVENT TRIGGER` | `DROP EVENT TRIGGER` | `pg_event_trigger` |
| `CREATE RULE` | `DROP RULE ... ON table` | `pg_rules` |
| `ALTER TYPE ... ADD VALUE` | — (Postgres has no `DROP VALUE`; warns) | `pg_enum` |
| `ALTER TABLE ... ATTACH PARTITION` | `DETACH PARTITION` | `pg_inherits` |
| `ALTER DEFAULT PRIVILEGES ... GRANT` | `ALTER DEFAULT PRIVILEGES ... REVOKE` | `pg_default_acl` + `aclexplode` |
| `SECURITY LABEL` | `SECURITY LABEL ... IS NULL` | — |
| `CREATE FOREIGN DATA WRAPPER` | `DROP FOREIGN DATA WRAPPER` | `pg_foreign_data_wrapper` |
| `CREATE CONVERSION` | `DROP CONVERSION` | `pg_conversion` |
| `CREATE ACCESS METHOD` | `DROP ACCESS METHOD` | `pg_am` |
| `CREATE TRANSFORM` | `DROP TRANSFORM FOR type LANGUAGE lang` | `pg_transform` |
| `CREATE OPERATOR CLASS` / `FAMILY` | `DROP ... USING am` | `pg_opclass` / `pg_opfamily` |
| `CREATE TEXT SEARCH CONFIGURATION` / `DICTIONARY` / `PARSER` / `TEMPLATE` | matching `DROP` | `pg_ts_config` / `pg_ts_dict` / `pg_ts_parser` / `pg_ts_template` |
| `CREATE TABLESPACE` | `DROP TABLESPACE` | `pg_tablespace` |
| `ALTER ... RENAME TO` | rename back (both names are in the statement) | object exists under new name |
| `ALTER ... SET SCHEMA` (qualified source) | move back (both schemas are in the statement) | object exists in new schema |
| `GRANT ALL` | `REVOKE ALL` | expands to the object type's concrete privilege list |

Not derivable (warned, never guessed): `REVOKE`, unnamed constraints, `ALTER ... SET` with unknown prior value, arbitrary DML, dynamic SQL.
Not derivable (warned, never guessed): `REVOKE`, unnamed constraints, `ALTER ... SET` with unknown prior value, `ALTER ... OWNER TO` (prior owner unknown), `SET SCHEMA` on unqualified names, arbitrary DML, dynamic SQL, prefix operators.
Loading
Loading