diff --git a/__fixtures__/generated/generated.json b/__fixtures__/generated/generated.json index 982b01a3..53dd4b6d 100644 --- a/__fixtures__/generated/generated.json +++ b/__fixtures__/generated/generated.json @@ -21250,6 +21250,20 @@ "misc/pg_catalog-timestamp-etc-11.sql": "CREATE TABLE public.\"EXAMPLE_INTERVAL_PRECISION\" (\n \"DURATION\" interval(2)\n)", "misc/pg_catalog-timestamp-etc-12.sql": "CREATE TABLE public.\"EXAMPLE_DATE\" (\n \"BIRTHDAY\" date\n)", "misc/pg_catalog-timestamp-etc-13.sql": "CREATE TABLE public.\"EXAMPLE_BOOL\" (\n \"IS_ACTIVE\" boolean\n)", + "misc/missing-types-1.sql": "ALTER PUBLICATION \"my_publication\" OWNER TO \"postgres\"", + "misc/missing-types-2.sql": "ALTER OPERATOR FAMILY my_opfamily USING btree OWNER TO new_owner", + "misc/missing-types-3.sql": "ALTER OPERATOR CLASS my_opclass USING btree OWNER TO new_owner", + "misc/missing-types-4.sql": "ALTER EXTENSION hstore UPDATE TO '2.0'", + "misc/missing-types-5.sql": "ALTER EXTENSION hstore SET SCHEMA utils", + "misc/missing-types-6.sql": "ALTER EXTENSION hstore ADD FUNCTION populate_record(anyelement, hstore)", + "misc/missing-types-7.sql": "ALTER FOREIGN TABLE my_foreign_table OWNER TO new_owner", + "misc/missing-types-8.sql": "ALTER LARGE OBJECT 12345 OWNER TO new_owner", + "misc/missing-types-9.sql": "ALTER MATERIALIZED VIEW my_matview OWNER TO new_owner", + "misc/missing-types-10.sql": "ALTER PUBLICATION my_publication OWNER TO new_owner", + "misc/missing-types-11.sql": "ALTER FUNCTION my_function(integer) OWNER TO new_owner", + "misc/missing-types-12.sql": "ALTER SUBSCRIPTION my_subscription OWNER TO new_owner", + "misc/missing-types-13.sql": "ALTER STATISTICS my_statistics OWNER TO new_owner", + "misc/missing-types-14.sql": "ALTER TABLESPACE my_tablespace OWNER TO new_owner", "misc/launchql-ext-types-1.sql": "CREATE DOMAIN attachment AS jsonb CHECK ( value ?& ARRAY['url', 'mime'] AND (value->>'url') ~ '^(https?)://[^\\s/$.?#].[^\\s]*$' )", "misc/launchql-ext-types-2.sql": "COMMENT ON DOMAIN attachment IS E'@name launchqlInternalTypeAttachment'", "misc/launchql-ext-types-3.sql": "CREATE DOMAIN email AS citext CHECK ( value ~ '^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$' )", diff --git a/__fixtures__/kitchen-sink/misc/missing-types.sql b/__fixtures__/kitchen-sink/misc/missing-types.sql new file mode 100644 index 00000000..db28c331 --- /dev/null +++ b/__fixtures__/kitchen-sink/misc/missing-types.sql @@ -0,0 +1,85 @@ +-- https://github.com/launchql/pgsql-parser/issues/219 +ALTER PUBLICATION "my_publication" OWNER TO "postgres"; + +-- Missing ObjectType cases for AlterOwnerStmt testing +-- OBJECT_ACCESS_METHOD +-- ALTER ACCESS METHOD my_access_method OWNER TO new_owner; + +-- OBJECT_AMOP (operator in operator family) +ALTER OPERATOR FAMILY my_opfamily USING btree OWNER TO new_owner; + +-- OBJECT_AMPROC (procedure in operator family) +ALTER OPERATOR CLASS my_opclass USING btree OWNER TO new_owner; + +-- OBJECT_ATTRIBUTE (attribute of composite type) +-- ALTER TYPE my_composite_type ATTRIBUTE my_attribute OWNER TO new_owner; + +-- OBJECT_CAST +-- ALTER CAST (text AS integer) OWNER TO new_owner; + +-- OBJECT_COLUMN (column of composite type) +-- ALTER TYPE my_composite_type ATTRIBUTE my_column OWNER TO new_owner; + +-- OBJECT_DEFAULT (default value - typically not alterable via OWNER TO, but included for completeness) +-- Note: Defaults don't have owners, this may not be valid SQL + +-- OBJECT_DEFACL (default ACL - typically not alterable via OWNER TO) +-- Note: Default ACLs don't have owners, this may not be valid SQL + +-- OBJECT_DOMCONSTRAINT +-- ALTER DOMAIN my_domain CONSTRAINT my_constraint OWNER TO new_owner; + +-- -- OBJECT_EXTENSION +-- ALTER EXTENSION hstore OWNER TO new_owner; +ALTER EXTENSION hstore UPDATE TO '2.0'; +ALTER EXTENSION hstore SET SCHEMA utils; +ALTER EXTENSION hstore ADD FUNCTION populate_record(anyelement, hstore); + +-- -- OBJECT_FOREIGN_TABLE +ALTER FOREIGN TABLE my_foreign_table OWNER TO new_owner; + +-- -- OBJECT_LARGEOBJECT +ALTER LARGE OBJECT 12345 OWNER TO new_owner; + +-- -- OBJECT_MATVIEW +ALTER MATERIALIZED VIEW my_matview OWNER TO new_owner; + +-- -- OBJECT_POLICY +-- ALTER POLICY my_policy ON my_table OWNER TO new_owner; +-- ALTER POLICY policy_name ON table_name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER }; + +-- -- OBJECT_PUBLICATION_NAMESPACE (publication for schema) +ALTER PUBLICATION my_publication OWNER TO new_owner; + +-- -- OBJECT_ROUTINE (generic routine - function or procedure) +ALTER FUNCTION my_function(integer) OWNER TO new_owner; + +-- -- OBJECT_RULE +-- ALTER RULE my_rule ON my_table OWNER TO new_owner; + +-- -- OBJECT_SUBSCRIPTION +ALTER SUBSCRIPTION my_subscription OWNER TO new_owner; + +-- -- OBJECT_STATISTIC_EXT +ALTER STATISTICS my_statistics OWNER TO new_owner; + +-- -- OBJECT_TABCONSTRAINT +-- ALTER TABLE my_table CONSTRAINT my_constraint OWNER TO new_owner; + +-- -- OBJECT_TABLESPACE +ALTER TABLESPACE my_tablespace OWNER TO new_owner; + +-- -- OBJECT_TRANSFORM +-- ALTER TRANSFORM FOR hstore LANGUAGE plpgsql OWNER TO new_owner; + +-- -- OBJECT_TRIGGER +-- ALTER TRIGGER my_trigger ON my_table OWNER TO new_owner; + +-- -- OBJECT_TSPARSER +-- ALTER TEXT SEARCH PARSER my_parser OWNER TO new_owner; + +-- -- OBJECT_TSTEMPLATE +-- ALTER TEXT SEARCH TEMPLATE my_template OWNER TO new_owner; + +-- -- OBJECT_USER_MAPPING +-- ALTER USER MAPPING FOR my_user SERVER my_server OWNER TO new_owner; \ No newline at end of file diff --git a/packages/deparser/__tests__/kitchen-sink/misc-missing-types.test.ts b/packages/deparser/__tests__/kitchen-sink/misc-missing-types.test.ts new file mode 100644 index 00000000..acbb1659 --- /dev/null +++ b/packages/deparser/__tests__/kitchen-sink/misc-missing-types.test.ts @@ -0,0 +1,22 @@ + +import { FixtureTestUtils } from '../../test-utils'; +const fixtures = new FixtureTestUtils(); + +it('misc-missing-types', async () => { + await fixtures.runFixtureTests([ + "misc/missing-types-1.sql", + "misc/missing-types-2.sql", + "misc/missing-types-3.sql", + "misc/missing-types-4.sql", + "misc/missing-types-5.sql", + "misc/missing-types-6.sql", + "misc/missing-types-7.sql", + "misc/missing-types-8.sql", + "misc/missing-types-9.sql", + "misc/missing-types-10.sql", + "misc/missing-types-11.sql", + "misc/missing-types-12.sql", + "misc/missing-types-13.sql", + "misc/missing-types-14.sql" +]); +}); diff --git a/packages/deparser/src/deparser.ts b/packages/deparser/src/deparser.ts index 16d3e884..cb58d76b 100644 --- a/packages/deparser/src/deparser.ts +++ b/packages/deparser/src/deparser.ts @@ -195,6 +195,121 @@ export class Deparser implements DeparserVisitor { return delimiter; } + /** + * Maps ObjectType enum values to their corresponding SQL keywords + * Used by AlterOwnerStmt, AlterObjectSchemaStmt, and other statements that need object type keywords + */ + private getObjectTypeKeyword(objectType: string): string { + switch (objectType) { + case 'OBJECT_TABLE': + return 'TABLE'; + case 'OBJECT_VIEW': + return 'VIEW'; + case 'OBJECT_INDEX': + return 'INDEX'; + case 'OBJECT_SEQUENCE': + return 'SEQUENCE'; + case 'OBJECT_FUNCTION': + return 'FUNCTION'; + case 'OBJECT_PROCEDURE': + return 'PROCEDURE'; + case 'OBJECT_SCHEMA': + return 'SCHEMA'; + case 'OBJECT_DATABASE': + return 'DATABASE'; + case 'OBJECT_DOMAIN': + return 'DOMAIN'; + case 'OBJECT_AGGREGATE': + return 'AGGREGATE'; + case 'OBJECT_CONVERSION': + return 'CONVERSION'; + case 'OBJECT_LANGUAGE': + return 'LANGUAGE'; + case 'OBJECT_OPERATOR': + return 'OPERATOR'; + case 'OBJECT_OPFAMILY': + return 'OPERATOR FAMILY'; + case 'OBJECT_OPCLASS': + return 'OPERATOR CLASS'; + case 'OBJECT_TSDICTIONARY': + return 'TEXT SEARCH DICTIONARY'; + case 'OBJECT_TSCONFIGURATION': + return 'TEXT SEARCH CONFIGURATION'; + case 'OBJECT_EVENT_TRIGGER': + return 'EVENT TRIGGER'; + case 'OBJECT_FDW': + return 'FOREIGN DATA WRAPPER'; + case 'OBJECT_FOREIGN_SERVER': + return 'SERVER'; + case 'OBJECT_TYPE': + return 'TYPE'; + case 'OBJECT_COLLATION': + return 'COLLATION'; + case 'OBJECT_PUBLICATION': + return 'PUBLICATION'; + case 'OBJECT_ACCESS_METHOD': + return 'ACCESS METHOD'; + case 'OBJECT_AMOP': + return 'OPERATOR CLASS'; + case 'OBJECT_AMPROC': + return 'OPERATOR CLASS'; + case 'OBJECT_ATTRIBUTE': + return 'ATTRIBUTE'; + case 'OBJECT_CAST': + return 'CAST'; + case 'OBJECT_COLUMN': + return 'COLUMN'; + case 'OBJECT_DEFAULT': + return 'DEFAULT'; + case 'OBJECT_DEFACL': + return 'DEFAULT PRIVILEGES'; + case 'OBJECT_DOMCONSTRAINT': + return 'DOMAIN'; + case 'OBJECT_EXTENSION': + return 'EXTENSION'; + case 'OBJECT_FOREIGN_TABLE': + return 'FOREIGN TABLE'; + case 'OBJECT_LARGEOBJECT': + return 'LARGE OBJECT'; + case 'OBJECT_MATVIEW': + return 'MATERIALIZED VIEW'; + case 'OBJECT_PARAMETER_ACL': + return 'PARAMETER'; + case 'OBJECT_POLICY': + return 'POLICY'; + case 'OBJECT_PUBLICATION_NAMESPACE': + return 'PUBLICATION'; + case 'OBJECT_PUBLICATION_REL': + return 'PUBLICATION'; + case 'OBJECT_ROLE': + return 'ROLE'; + case 'OBJECT_ROUTINE': + return 'ROUTINE'; + case 'OBJECT_RULE': + return 'RULE'; + case 'OBJECT_STATISTIC_EXT': + return 'STATISTICS'; + case 'OBJECT_SUBSCRIPTION': + return 'SUBSCRIPTION'; + case 'OBJECT_TABCONSTRAINT': + return 'CONSTRAINT'; + case 'OBJECT_TABLESPACE': + return 'TABLESPACE'; + case 'OBJECT_TRANSFORM': + return 'TRANSFORM'; + case 'OBJECT_TRIGGER': + return 'TRIGGER'; + case 'OBJECT_TSPARSER': + return 'TEXT SEARCH PARSER'; + case 'OBJECT_TSTEMPLATE': + return 'TEXT SEARCH TEMPLATE'; + case 'OBJECT_USER_MAPPING': + return 'USER MAPPING'; + default: + throw new Error(`Unsupported objectType: ${objectType}`); + } + } + deparse(node: Node, context?: DeparserContext): string | null { if (node == null) { return null; @@ -5986,11 +6101,15 @@ export class Deparser implements DeparserVisitor { if (context.parentNodeTypes.includes('CreateExtensionStmt') || context.parentNodeTypes.includes('AlterExtensionStmt') || context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) { // AlterExtensionStmt specific cases if (context.parentNodeTypes.includes('AlterExtensionStmt')) { - if (node.defname === 'to') { - return `TO ${argValue}`; + if (node.defname === 'new_version') { + // argValue is unquoted due to DefElem context, so we need to quote it + const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'") + ? `'${argValue}'` + : argValue; + return `UPDATE TO ${quotedValue}`; } if (node.defname === 'schema') { - return `SCHEMA ${argValue}`; + return `SET SCHEMA ${argValue}`; } } @@ -6300,6 +6419,38 @@ export class Deparser implements DeparserVisitor { return output.join(' '); } + AlterExtensionContentsStmt(node: t.AlterExtensionContentsStmt, context: DeparserContext): string { + const output: string[] = ['ALTER', 'EXTENSION']; + + if (node.extname) { + output.push(this.quoteIfNeeded(node.extname)); + } + + // Handle action: 1 = ADD, -1 = DROP + if (node.action === 1) { + output.push('ADD'); + } else if (node.action === -1) { + output.push('DROP'); + } + + // Add object type keyword + if (node.objtype) { + try { + output.push(this.getObjectTypeKeyword(node.objtype)); + } catch { + // Fallback to the raw objtype if not supported + output.push(node.objtype.toString()); + } + } + + // Add the object itself + if (node.object) { + output.push(this.visit(node.object, context)); + } + + return output.join(' '); + } + CreateFdwStmt(node: t.CreateFdwStmt, context: DeparserContext): string { const output: string[] = ['CREATE', 'FOREIGN', 'DATA', 'WRAPPER']; @@ -8219,76 +8370,7 @@ export class Deparser implements DeparserVisitor { throw new Error('AlterOwnerStmt requires objectType'); } - switch (node.objectType) { - case 'OBJECT_TABLE': - output.push('TABLE'); - break; - case 'OBJECT_VIEW': - output.push('VIEW'); - break; - case 'OBJECT_INDEX': - output.push('INDEX'); - break; - case 'OBJECT_SEQUENCE': - output.push('SEQUENCE'); - break; - case 'OBJECT_FUNCTION': - output.push('FUNCTION'); - break; - case 'OBJECT_PROCEDURE': - output.push('PROCEDURE'); - break; - case 'OBJECT_SCHEMA': - output.push('SCHEMA'); - break; - case 'OBJECT_DATABASE': - output.push('DATABASE'); - break; - case 'OBJECT_DOMAIN': - output.push('DOMAIN'); - break; - case 'OBJECT_AGGREGATE': - output.push('AGGREGATE'); - break; - case 'OBJECT_CONVERSION': - output.push('CONVERSION'); - break; - case 'OBJECT_LANGUAGE': - output.push('LANGUAGE'); - break; - case 'OBJECT_OPERATOR': - output.push('OPERATOR'); - break; - case 'OBJECT_OPFAMILY': - output.push('OPERATOR FAMILY'); - break; - case 'OBJECT_OPCLASS': - output.push('OPERATOR CLASS'); - break; - case 'OBJECT_TSDICTIONARY': - output.push('TEXT SEARCH DICTIONARY'); - break; - case 'OBJECT_TSCONFIGURATION': - output.push('TEXT SEARCH CONFIGURATION'); - break; - case 'OBJECT_EVENT_TRIGGER': - output.push('EVENT TRIGGER'); - break; - case 'OBJECT_FDW': - output.push('FOREIGN DATA WRAPPER'); - break; - case 'OBJECT_FOREIGN_SERVER': - output.push('SERVER'); - break; - case 'OBJECT_TYPE': - output.push('TYPE'); - break; - case 'OBJECT_COLLATION': - output.push('COLLATION'); - break; - default: - throw new Error(`Unsupported AlterOwnerStmt objectType: ${node.objectType}`); - } + output.push(this.getObjectTypeKeyword(node.objectType)); if (node.relation) { output.push(this.RangeVar(node.relation, context)); @@ -11156,66 +11238,11 @@ export class Deparser implements DeparserVisitor { AlterObjectSchemaStmt(node: t.AlterObjectSchemaStmt, context: DeparserContext): string { const output: string[] = ['ALTER']; - switch (node.objectType) { - case 'OBJECT_TABLE': - output.push('TABLE'); - break; - case 'OBJECT_VIEW': - output.push('VIEW'); - break; - case 'OBJECT_FUNCTION': - output.push('FUNCTION'); - break; - case 'OBJECT_TYPE': - output.push('TYPE'); - break; - case 'OBJECT_DOMAIN': - output.push('DOMAIN'); - break; - case 'OBJECT_SEQUENCE': - output.push('SEQUENCE'); - break; - case 'OBJECT_OPCLASS': - output.push('OPERATOR CLASS'); - break; - case 'OBJECT_OPFAMILY': - output.push('OPERATOR FAMILY'); - break; - case 'OBJECT_OPERATOR': - output.push('OPERATOR'); - break; - case 'OBJECT_TYPE': - output.push('TYPE'); - break; - case 'OBJECT_COLLATION': - output.push('COLLATION'); - break; - case 'OBJECT_CONVERSION': - output.push('CONVERSION'); - break; - case 'OBJECT_TSPARSER': - output.push('TEXT SEARCH PARSER'); - break; - case 'OBJECT_TSCONFIGURATION': - output.push('TEXT SEARCH CONFIGURATION'); - break; - case 'OBJECT_TSTEMPLATE': - output.push('TEXT SEARCH TEMPLATE'); - break; - case 'OBJECT_TSDICTIONARY': - output.push('TEXT SEARCH DICTIONARY'); - break; - case 'OBJECT_AGGREGATE': - output.push('AGGREGATE'); - break; - case 'OBJECT_FOREIGN_TABLE': - output.push('FOREIGN TABLE'); - break; - case 'OBJECT_MATVIEW': - output.push('MATERIALIZED VIEW'); - break; - default: - output.push(node.objectType.toString()); + try { + output.push(this.getObjectTypeKeyword(node.objectType)); + } catch { + // Fallback to objectType string if not supported + output.push(node.objectType.toString()); } if (node.missing_ok) {