diff --git a/__fixtures__/generated/generated.json b/__fixtures__/generated/generated.json
index a9ad7570..b2030dbf 100644
--- a/__fixtures__/generated/generated.json
+++ b/__fixtures__/generated/generated.json
@@ -21220,6 +21220,43 @@
"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/xmlserialize-indent-1.sql": "SELECT xmlserialize(DOCUMENT '42' AS text INDENT)",
+ "misc/xmlserialize-indent-2.sql": "SELECT xmlserialize(CONTENT '42' AS text INDENT)",
+ "misc/xmlserialize-indent-3.sql": "SELECT xmlserialize(DOCUMENT '42' AS text NO INDENT)",
+ "misc/xmlserialize-indent-4.sql": "SELECT xmlserialize(CONTENT doc AS varchar INDENT) FROM xmltest",
+ "misc/transaction-chain-1.sql": "START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE",
+ "misc/transaction-chain-2.sql": "COMMIT AND CHAIN",
+ "misc/transaction-chain-3.sql": "ROLLBACK AND CHAIN",
+ "misc/transaction-chain-4.sql": "COMMIT AND NO CHAIN",
+ "misc/transaction-chain-5.sql": "ROLLBACK AND NO CHAIN",
+ "misc/transaction-chain-6.sql": "END AND CHAIN",
+ "misc/transaction-chain-7.sql": "ABORT AND CHAIN",
+ "misc/tablespace-clauses-1.sql": "CREATE TABLE testschema.foo (i int) TABLESPACE regress_tblspace",
+ "misc/tablespace-clauses-2.sql": "CREATE TABLE t (a int) ON COMMIT DROP TABLESPACE ts",
+ "misc/tablespace-clauses-3.sql": "CREATE TABLE t (a int) USING heap TABLESPACE ts",
+ "misc/tablespace-clauses-4.sql": "CREATE TABLE testschema.dflt (a int PRIMARY KEY USING INDEX TABLESPACE regress_tblspace) PARTITION BY LIST (a) TABLESPACE pg_default",
+ "misc/tablespace-clauses-5.sql": "CREATE TABLE testschema.part_1 PARTITION OF testschema.part FOR VALUES IN (1) TABLESPACE \"My Space\"",
+ "misc/tablespace-clauses-6.sql": "CREATE TABLE t (a int, b int, PRIMARY KEY (a) INCLUDE (b) WITH (fillfactor=10) USING INDEX TABLESPACE ts DEFERRABLE)",
+ "misc/tablespace-clauses-7.sql": "CREATE TABLE t (a int UNIQUE NULLS NOT DISTINCT USING INDEX TABLESPACE \"My Space\")",
+ "misc/tablespace-clauses-8.sql": "CREATE TABLE t (a int, CONSTRAINT u UNIQUE (a) INCLUDE (a) WITH (fillfactor=50))",
+ "misc/tablespace-clauses-9.sql": "CREATE TABLE t (a int, EXCLUDE USING gist (a WITH =) INCLUDE (a) WITH (fillfactor=10) USING INDEX TABLESPACE ts WHERE (a > 0))",
+ "misc/tablespace-clauses-10.sql": "ALTER TABLE testschema.atable ADD CONSTRAINT anindex_t UNIQUE (column1) USING INDEX TABLESPACE regress_tblspace",
+ "misc/tablespace-clauses-11.sql": "ALTER TABLE t ADD CONSTRAINT pk PRIMARY KEY USING INDEX \"My Index\"",
+ "misc/tablespace-clauses-12.sql": "ALTER TABLE t ADD UNIQUE USING INDEX idx DEFERRABLE INITIALLY DEFERRED",
+ "misc/tablespace-clauses-13.sql": "CREATE TABLESPACE \"My Space\" OWNER regress_user LOCATION '/tmp' WITH (random_page_cost = 3, seq_page_cost = '1.5')",
+ "misc/tablespace-clauses-14.sql": "ALTER TABLESPACE \"My Space\" SET (random_page_cost = 1.5, effective_io_concurrency = 2)",
+ "misc/tablespace-clauses-15.sql": "ALTER TABLESPACE \"My Space\" RESET (random_page_cost)",
+ "misc/tablespace-clauses-16.sql": "DROP TABLESPACE IF EXISTS \"My Space\"",
+ "misc/show-quoting-1.sql": "SHOW \"TimeZone\"",
+ "misc/show-quoting-2.sql": "SHOW \"Foo\".\"Bar\"",
+ "misc/show-quoting-3.sql": "SHOW \"my.ext\".setting",
+ "misc/show-quoting-4.sql": "SHOW \"ALL\"",
+ "misc/show-quoting-5.sql": "SHOW ALL",
+ "misc/show-quoting-6.sql": "SHOW TIME ZONE",
+ "misc/show-quoting-7.sql": "SHOW TRANSACTION ISOLATION LEVEL",
+ "misc/show-quoting-8.sql": "SHOW SESSION AUTHORIZATION",
+ "misc/show-quoting-9.sql": "SHOW search_path",
+ "misc/show-quoting-10.sql": "SHOW plpgsql.extra_warnings",
"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",
@@ -21719,6 +21756,14 @@
"misc/generated-columns-6.sql": "ALTER TABLE generated_misc_test\nALTER COLUMN c SET EXPRESSION AS (a - b)",
"misc/generated-columns-7.sql": "ALTER TABLE generated_misc_test\nALTER COLUMN c DROP EXPRESSION",
"misc/generated-columns-8.sql": "ALTER TABLE generated_misc_test\nALTER COLUMN c DROP EXPRESSION IF EXISTS",
+ "misc/fetch-with-ties-1.sql": "SELECT thousand FROM onek WHERE thousand < 5 ORDER BY thousand FETCH FIRST 2 ROW WITH TIES",
+ "misc/fetch-with-ties-2.sql": "SELECT thousand FROM onek WHERE thousand < 5 ORDER BY thousand FETCH FIRST ROWS WITH TIES",
+ "misc/fetch-with-ties-3.sql": "SELECT thousand FROM onek WHERE thousand < 5 ORDER BY thousand OFFSET 2 ROWS FETCH NEXT (1 + 1) ROWS WITH TIES",
+ "misc/fetch-with-ties-4.sql": "CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST 5 ROWS WITH TIES OFFSET 10",
+ "misc/fetch-with-ties-5.sql": "CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST (NULL+1) ROWS WITH TIES",
+ "misc/fetch-with-ties-6.sql": "CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST NULL ROWS ONLY",
+ "misc/fetch-with-ties-7.sql": "SELECT * FROM t ORDER BY a FETCH FIRST $1 ROWS WITH TIES",
+ "misc/fetch-with-ties-8.sql": "SELECT * FROM t ORDER BY a FETCH FIRST -1 ROWS WITH TIES",
"misc/extract-1.sql": "SELECT EXTRACT(EPOCH FROM now())",
"misc/extract-2.sql": "SELECT EXTRACT(EPOCH FROM DATE '1970-01-01')",
"misc/extract-3.sql": "SELECT EXTRACT(EPOCH FROM TIMESTAMP '1970-01-01')",
@@ -21737,6 +21782,10 @@
"misc/extract-16.sql": "SELECT EXTRACT(TIMEZONE FROM CURRENT_TIMESTAMP)",
"misc/extract-17.sql": "SELECT EXTRACT(TIMEZONE_HOUR FROM CURRENT_TIMESTAMP)",
"misc/extract-18.sql": "SELECT EXTRACT(TIMEZONE_MINUTE FROM CURRENT_TIMESTAMP)",
+ "misc/copy-option-values-1.sql": "COPY t FROM STDIN (HEADER false)",
+ "misc/copy-option-values-2.sql": "COPY t FROM STDIN (HEADER true, FREEZE false)",
+ "misc/copy-option-values-3.sql": "COPY t FROM STDIN (FORMAT \"CSV\")",
+ "misc/copy-option-values-4.sql": "COPY t FROM STDIN (FORMAT 'csv', HEADER off)",
"misc/cascades-1.sql": "DROP TABLE IF EXISTS some_table CASCADE",
"misc/cascades-2.sql": "DROP VIEW IF EXISTS some_view CASCADE",
"misc/cascades-3.sql": "DROP MATERIALIZED VIEW IF EXISTS some_mat_view CASCADE",
@@ -21765,6 +21814,9 @@
"misc/booleans-cast-1.sql": "SELECT * FROM myschema.mytable WHERE a = TRUE",
"misc/booleans-cast-2.sql": "SELECT * FROM myschema.mytable WHERE a = CAST('t' AS boolean)",
"misc/booleans-cast-3.sql": "SELECT * FROM myschema.mytable WHERE a = 't'::boolean",
+ "misc/alter-trigger-rename-1.sql": "ALTER TRIGGER p ON grandp RENAME TO q",
+ "misc/alter-trigger-rename-2.sql": "ALTER TRIGGER \"Mixed Trig\" ON \"My Schema\".\"My Table\" RENAME TO \"New Trig\"",
+ "misc/alter-trigger-rename-3.sql": "ALTER TRIGGER modified_a ON main_table RENAME TO modified_modified_a",
"latest/postgres/create_view-1.sql": "CREATE FUNCTION interpt_pp(path, path)\n RETURNS point\n AS 'regresslib'\n LANGUAGE C STRICT",
"latest/postgres/create_view-2.sql": "CREATE TABLE real_city (\n\tpop\t\t\tint4,\n\tcname\t\ttext,\n\toutline \tpath\n)",
"latest/postgres/create_view-3.sql": "COPY real_city FROM 'filename'",
diff --git a/__fixtures__/kitchen-sink/misc/alter-trigger-rename.sql b/__fixtures__/kitchen-sink/misc/alter-trigger-rename.sql
new file mode 100644
index 00000000..dc37c35e
--- /dev/null
+++ b/__fixtures__/kitchen-sink/misc/alter-trigger-rename.sql
@@ -0,0 +1,5 @@
+-- ALTER TRIGGER name ON table RENAME TO (RenameStmt OBJECT_TRIGGER keeps subname)
+-- Ref: PostgreSQL regress triggers.sql
+ALTER TRIGGER p ON grandp RENAME TO q;
+ALTER TRIGGER "Mixed Trig" ON "My Schema"."My Table" RENAME TO "New Trig";
+ALTER TRIGGER modified_a ON main_table RENAME TO modified_modified_a;
diff --git a/__fixtures__/kitchen-sink/misc/copy-option-values.sql b/__fixtures__/kitchen-sink/misc/copy-option-values.sql
new file mode 100644
index 00000000..674955a7
--- /dev/null
+++ b/__fixtures__/kitchen-sink/misc/copy-option-values.sql
@@ -0,0 +1,5 @@
+-- COPY option values keep their meaning
+COPY t FROM STDIN (HEADER false);
+COPY t FROM STDIN (HEADER true, FREEZE false);
+COPY t FROM STDIN (FORMAT "CSV");
+COPY t FROM STDIN (FORMAT 'csv', HEADER off);
diff --git a/__fixtures__/kitchen-sink/misc/fetch-with-ties.sql b/__fixtures__/kitchen-sink/misc/fetch-with-ties.sql
new file mode 100644
index 00000000..d19b3470
--- /dev/null
+++ b/__fixtures__/kitchen-sink/misc/fetch-with-ties.sql
@@ -0,0 +1,10 @@
+-- FETCH FIRST n ROWS WITH TIES (SelectStmt.limitOption)
+-- Ref: PostgreSQL regress limit.sql
+SELECT thousand FROM onek WHERE thousand < 5 ORDER BY thousand FETCH FIRST 2 ROW WITH TIES;
+SELECT thousand FROM onek WHERE thousand < 5 ORDER BY thousand FETCH FIRST ROWS WITH TIES;
+SELECT thousand FROM onek WHERE thousand < 5 ORDER BY thousand OFFSET 2 ROWS FETCH NEXT (1 + 1) ROWS WITH TIES;
+CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST 5 ROWS WITH TIES OFFSET 10;
+CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST (NULL+1) ROWS WITH TIES;
+CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST NULL ROWS ONLY;
+SELECT * FROM t ORDER BY a FETCH FIRST $1 ROWS WITH TIES;
+SELECT * FROM t ORDER BY a FETCH FIRST -1 ROWS WITH TIES;
diff --git a/__fixtures__/kitchen-sink/misc/show-quoting.sql b/__fixtures__/kitchen-sink/misc/show-quoting.sql
new file mode 100644
index 00000000..f4101d16
--- /dev/null
+++ b/__fixtures__/kitchen-sink/misc/show-quoting.sql
@@ -0,0 +1,11 @@
+-- SHOW keeps identifier quoting (VariableShowStmt.name)
+SHOW "TimeZone";
+SHOW "Foo"."Bar";
+SHOW "my.ext".setting;
+SHOW "ALL";
+SHOW ALL;
+SHOW TIME ZONE;
+SHOW TRANSACTION ISOLATION LEVEL;
+SHOW SESSION AUTHORIZATION;
+SHOW search_path;
+SHOW plpgsql.extra_warnings;
diff --git a/__fixtures__/kitchen-sink/misc/tablespace-clauses.sql b/__fixtures__/kitchen-sink/misc/tablespace-clauses.sql
new file mode 100644
index 00000000..1eb0fab0
--- /dev/null
+++ b/__fixtures__/kitchen-sink/misc/tablespace-clauses.sql
@@ -0,0 +1,18 @@
+-- TABLESPACE / USING INDEX TABLESPACE / INCLUDE / WITH on table and constraint definitions
+-- Ref: PostgreSQL regress tablespace.sql, create_table.sql, constraints.sql
+CREATE TABLE testschema.foo (i int) TABLESPACE regress_tblspace;
+CREATE TABLE t (a int) ON COMMIT DROP TABLESPACE ts;
+CREATE TABLE t (a int) USING heap TABLESPACE ts;
+CREATE TABLE testschema.dflt (a int PRIMARY KEY USING INDEX TABLESPACE regress_tblspace) PARTITION BY LIST (a) TABLESPACE pg_default;
+CREATE TABLE testschema.part_1 PARTITION OF testschema.part FOR VALUES IN (1) TABLESPACE "My Space";
+CREATE TABLE t (a int, b int, PRIMARY KEY (a) INCLUDE (b) WITH (fillfactor=10) USING INDEX TABLESPACE ts DEFERRABLE);
+CREATE TABLE t (a int UNIQUE NULLS NOT DISTINCT USING INDEX TABLESPACE "My Space");
+CREATE TABLE t (a int, CONSTRAINT u UNIQUE (a) INCLUDE (a) WITH (fillfactor=50));
+CREATE TABLE t (a int, EXCLUDE USING gist (a WITH =) INCLUDE (a) WITH (fillfactor=10) USING INDEX TABLESPACE ts WHERE (a > 0));
+ALTER TABLE testschema.atable ADD CONSTRAINT anindex_t UNIQUE (column1) USING INDEX TABLESPACE regress_tblspace;
+ALTER TABLE t ADD CONSTRAINT pk PRIMARY KEY USING INDEX "My Index";
+ALTER TABLE t ADD UNIQUE USING INDEX idx DEFERRABLE INITIALLY DEFERRED;
+CREATE TABLESPACE "My Space" OWNER regress_user LOCATION '/tmp' WITH (random_page_cost = 3, seq_page_cost = '1.5');
+ALTER TABLESPACE "My Space" SET (random_page_cost = 1.5, effective_io_concurrency = 2);
+ALTER TABLESPACE "My Space" RESET (random_page_cost);
+DROP TABLESPACE IF EXISTS "My Space";
diff --git a/__fixtures__/kitchen-sink/misc/transaction-chain.sql b/__fixtures__/kitchen-sink/misc/transaction-chain.sql
new file mode 100644
index 00000000..8205f519
--- /dev/null
+++ b/__fixtures__/kitchen-sink/misc/transaction-chain.sql
@@ -0,0 +1,9 @@
+-- COMMIT/ROLLBACK AND CHAIN (TransactionStmt.chain)
+-- Ref: PostgreSQL regress transactions.sql
+START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE;
+COMMIT AND CHAIN;
+ROLLBACK AND CHAIN;
+COMMIT AND NO CHAIN;
+ROLLBACK AND NO CHAIN;
+END AND CHAIN;
+ABORT AND CHAIN;
diff --git a/__fixtures__/kitchen-sink/misc/xmlserialize-indent.sql b/__fixtures__/kitchen-sink/misc/xmlserialize-indent.sql
new file mode 100644
index 00000000..025ce37f
--- /dev/null
+++ b/__fixtures__/kitchen-sink/misc/xmlserialize-indent.sql
@@ -0,0 +1,6 @@
+-- XMLSERIALIZE ... INDENT (XmlSerialize.indent)
+-- Ref: PostgreSQL regress xml.sql
+SELECT xmlserialize(DOCUMENT '42' AS text INDENT);
+SELECT xmlserialize(CONTENT '42' AS text INDENT);
+SELECT xmlserialize(DOCUMENT '42' AS text NO INDENT);
+SELECT xmlserialize(CONTENT doc AS varchar INDENT) FROM xmltest;
diff --git a/packages/deparser/__tests__/kitchen-sink/misc-alter-trigger-rename.test.ts b/packages/deparser/__tests__/kitchen-sink/misc-alter-trigger-rename.test.ts
new file mode 100644
index 00000000..aa8b9f69
--- /dev/null
+++ b/packages/deparser/__tests__/kitchen-sink/misc-alter-trigger-rename.test.ts
@@ -0,0 +1,11 @@
+
+import { FixtureTestUtils } from '../../test-utils';
+const fixtures = new FixtureTestUtils();
+
+it('misc-alter-trigger-rename', async () => {
+ await fixtures.runFixtureTests([
+ "misc/alter-trigger-rename-1.sql",
+ "misc/alter-trigger-rename-2.sql",
+ "misc/alter-trigger-rename-3.sql"
+]);
+});
diff --git a/packages/deparser/__tests__/kitchen-sink/misc-copy-option-values.test.ts b/packages/deparser/__tests__/kitchen-sink/misc-copy-option-values.test.ts
new file mode 100644
index 00000000..5442ff20
--- /dev/null
+++ b/packages/deparser/__tests__/kitchen-sink/misc-copy-option-values.test.ts
@@ -0,0 +1,12 @@
+
+import { FixtureTestUtils } from '../../test-utils';
+const fixtures = new FixtureTestUtils();
+
+it('misc-copy-option-values', async () => {
+ await fixtures.runFixtureTests([
+ "misc/copy-option-values-1.sql",
+ "misc/copy-option-values-2.sql",
+ "misc/copy-option-values-3.sql",
+ "misc/copy-option-values-4.sql"
+]);
+});
diff --git a/packages/deparser/__tests__/kitchen-sink/misc-fetch-with-ties.test.ts b/packages/deparser/__tests__/kitchen-sink/misc-fetch-with-ties.test.ts
new file mode 100644
index 00000000..acd1d65b
--- /dev/null
+++ b/packages/deparser/__tests__/kitchen-sink/misc-fetch-with-ties.test.ts
@@ -0,0 +1,16 @@
+
+import { FixtureTestUtils } from '../../test-utils';
+const fixtures = new FixtureTestUtils();
+
+it('misc-fetch-with-ties', async () => {
+ await fixtures.runFixtureTests([
+ "misc/fetch-with-ties-1.sql",
+ "misc/fetch-with-ties-2.sql",
+ "misc/fetch-with-ties-3.sql",
+ "misc/fetch-with-ties-4.sql",
+ "misc/fetch-with-ties-5.sql",
+ "misc/fetch-with-ties-6.sql",
+ "misc/fetch-with-ties-7.sql",
+ "misc/fetch-with-ties-8.sql"
+]);
+});
diff --git a/packages/deparser/__tests__/kitchen-sink/misc-show-quoting.test.ts b/packages/deparser/__tests__/kitchen-sink/misc-show-quoting.test.ts
new file mode 100644
index 00000000..b4def202
--- /dev/null
+++ b/packages/deparser/__tests__/kitchen-sink/misc-show-quoting.test.ts
@@ -0,0 +1,18 @@
+
+import { FixtureTestUtils } from '../../test-utils';
+const fixtures = new FixtureTestUtils();
+
+it('misc-show-quoting', async () => {
+ await fixtures.runFixtureTests([
+ "misc/show-quoting-1.sql",
+ "misc/show-quoting-2.sql",
+ "misc/show-quoting-3.sql",
+ "misc/show-quoting-4.sql",
+ "misc/show-quoting-5.sql",
+ "misc/show-quoting-6.sql",
+ "misc/show-quoting-7.sql",
+ "misc/show-quoting-8.sql",
+ "misc/show-quoting-9.sql",
+ "misc/show-quoting-10.sql"
+]);
+});
diff --git a/packages/deparser/__tests__/kitchen-sink/misc-tablespace-clauses.test.ts b/packages/deparser/__tests__/kitchen-sink/misc-tablespace-clauses.test.ts
new file mode 100644
index 00000000..295fae50
--- /dev/null
+++ b/packages/deparser/__tests__/kitchen-sink/misc-tablespace-clauses.test.ts
@@ -0,0 +1,24 @@
+
+import { FixtureTestUtils } from '../../test-utils';
+const fixtures = new FixtureTestUtils();
+
+it('misc-tablespace-clauses', async () => {
+ await fixtures.runFixtureTests([
+ "misc/tablespace-clauses-1.sql",
+ "misc/tablespace-clauses-2.sql",
+ "misc/tablespace-clauses-3.sql",
+ "misc/tablespace-clauses-4.sql",
+ "misc/tablespace-clauses-5.sql",
+ "misc/tablespace-clauses-6.sql",
+ "misc/tablespace-clauses-7.sql",
+ "misc/tablespace-clauses-8.sql",
+ "misc/tablespace-clauses-9.sql",
+ "misc/tablespace-clauses-10.sql",
+ "misc/tablespace-clauses-11.sql",
+ "misc/tablespace-clauses-12.sql",
+ "misc/tablespace-clauses-13.sql",
+ "misc/tablespace-clauses-14.sql",
+ "misc/tablespace-clauses-15.sql",
+ "misc/tablespace-clauses-16.sql"
+]);
+});
diff --git a/packages/deparser/__tests__/kitchen-sink/misc-transaction-chain.test.ts b/packages/deparser/__tests__/kitchen-sink/misc-transaction-chain.test.ts
new file mode 100644
index 00000000..30e58df2
--- /dev/null
+++ b/packages/deparser/__tests__/kitchen-sink/misc-transaction-chain.test.ts
@@ -0,0 +1,15 @@
+
+import { FixtureTestUtils } from '../../test-utils';
+const fixtures = new FixtureTestUtils();
+
+it('misc-transaction-chain', async () => {
+ await fixtures.runFixtureTests([
+ "misc/transaction-chain-1.sql",
+ "misc/transaction-chain-2.sql",
+ "misc/transaction-chain-3.sql",
+ "misc/transaction-chain-4.sql",
+ "misc/transaction-chain-5.sql",
+ "misc/transaction-chain-6.sql",
+ "misc/transaction-chain-7.sql"
+]);
+});
diff --git a/packages/deparser/__tests__/kitchen-sink/misc-xmlserialize-indent.test.ts b/packages/deparser/__tests__/kitchen-sink/misc-xmlserialize-indent.test.ts
new file mode 100644
index 00000000..3a188442
--- /dev/null
+++ b/packages/deparser/__tests__/kitchen-sink/misc-xmlserialize-indent.test.ts
@@ -0,0 +1,12 @@
+
+import { FixtureTestUtils } from '../../test-utils';
+const fixtures = new FixtureTestUtils();
+
+it('misc-xmlserialize-indent', async () => {
+ await fixtures.runFixtureTests([
+ "misc/xmlserialize-indent-1.sql",
+ "misc/xmlserialize-indent-2.sql",
+ "misc/xmlserialize-indent-3.sql",
+ "misc/xmlserialize-indent-4.sql"
+]);
+});
diff --git a/packages/deparser/__tests__/misc/copy-boolean-options.test.ts b/packages/deparser/__tests__/misc/copy-boolean-options.test.ts
new file mode 100644
index 00000000..e6af5ad0
--- /dev/null
+++ b/packages/deparser/__tests__/misc/copy-boolean-options.test.ts
@@ -0,0 +1,21 @@
+import { Deparser } from '../../src';
+
+// The parser only emits Boolean(true) COPY option args (e.g. `CSV HEADER`), and the
+// JSON AST omits `boolval` when it is false, so a false Boolean must be hand-built.
+const copyWith = (defname: string, arg: any) => ({
+ CopyStmt: {
+ relation: { relname: 't', inh: true, relpersistence: 'p' },
+ is_from: true,
+ options: [{ DefElem: { defname, arg, defaction: 'DEFELEM_UNSPEC' } }]
+ }
+});
+
+describe('COPY Boolean option args', () => {
+ it.each([
+ ['false (boolval omitted)', { Boolean: {} }, 'COPY t FROM STDIN WITH (HEADER false)'],
+ ['false', { Boolean: { boolval: false } }, 'COPY t FROM STDIN WITH (HEADER false)'],
+ ['true', { Boolean: { boolval: true } }, 'COPY t FROM STDIN WITH (HEADER)']
+ ])('keeps a %s Boolean value', (_label, arg, expected) => {
+ expect(Deparser.deparse(copyWith('header', arg) as any)).toBe(expected);
+ });
+});
diff --git a/packages/deparser/src/deparser.ts b/packages/deparser/src/deparser.ts
index 94c7f625..619a6941 100644
--- a/packages/deparser/src/deparser.ts
+++ b/packages/deparser/src/deparser.ts
@@ -717,12 +717,28 @@ export class Deparser implements DeparserVisitor {
}
}
- if (node.limitCount) {
- output.push('LIMIT ' + this.visit(node.limitCount as Node, context));
- }
+ if (node.limitOption === 'LIMIT_OPTION_WITH_TIES') {
+ if (node.limitOffset) {
+ output.push('OFFSET ' + this.visit(node.limitOffset as Node, context) + ' ROWS');
+ }
+ const count = node.limitCount as Node | undefined;
+ if (count) {
+ let countStr = this.visit(count, context);
+ if (!('A_Const' in count) || countStr.startsWith('-')) {
+ countStr = `(${countStr})`;
+ }
+ output.push(`FETCH FIRST ${countStr} ROWS WITH TIES`);
+ } else {
+ output.push('FETCH FIRST ROWS WITH TIES');
+ }
+ } else {
+ if (node.limitCount) {
+ output.push('LIMIT ' + this.visit(node.limitCount as Node, context));
+ }
- if (node.limitOffset) {
- output.push('OFFSET ' + this.visit(node.limitOffset as Node, context));
+ if (node.limitOffset) {
+ output.push('OFFSET ' + this.visit(node.limitOffset as Node, context));
+ }
}
if (node.lockingClause) {
@@ -2808,6 +2824,10 @@ export class Deparser implements DeparserVisitor {
output.push(this.quoteIfNeeded(node.accessMethod));
}
+ if (node.tablespacename) {
+ output.push('TABLESPACE', QuoteUtils.quoteIdentifier(node.tablespacename));
+ }
+
return output.join(' ');
}
@@ -3018,10 +3038,7 @@ export class Deparser implements DeparserVisitor {
}
output.push(`(${keyItems.join(', ')})`);
}
- if (node.indexname) {
- output.push('USING INDEX');
- output.push(node.indexname);
- }
+ this.pushIndexConstraintOptions(node, output, context);
break;
case 'CONSTR_UNIQUE':
if (context.isPretty() && !context.isColumnConstraint) {
@@ -3040,10 +3057,7 @@ export class Deparser implements DeparserVisitor {
}
output.push(`(${keyItems.join(', ')})`);
}
- if (node.indexname) {
- output.push('USING INDEX');
- output.push(node.indexname);
- }
+ this.pushIndexConstraintOptions(node, output, context);
break;
case 'CONSTR_FOREIGN':
// Only add "FOREIGN KEY" for table-level constraints, not column-level constraints
@@ -3227,6 +3241,7 @@ export class Deparser implements DeparserVisitor {
});
output.push(`(${exclusionElements.join(', ')})`);
}
+ this.pushIndexConstraintOptions(node, output, context);
if (node.where_clause) {
output.push('WHERE');
output.push(context.parens(this.visit(node.where_clause as any, context)));
@@ -3280,6 +3295,25 @@ export class Deparser implements DeparserVisitor {
return output.join(' ');
}
+ private pushIndexConstraintOptions(node: t.Constraint, output: string[], context: DeparserContext): void {
+ if (node.indexname) {
+ output.push('USING INDEX', QuoteUtils.quoteIdentifier(node.indexname));
+ return;
+ }
+ if (node.including && node.including.length > 0) {
+ const cols = ListUtils.unwrapList(node.including).map(col => this.visit(col, context));
+ output.push(`INCLUDE (${cols.join(', ')})`);
+ }
+ if (node.options && node.options.length > 0) {
+ const optionContext = context.spawn('IndexStmt');
+ const opts = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionContext));
+ output.push(`WITH (${opts.join(', ')})`);
+ }
+ if (node.indexspace) {
+ output.push('USING INDEX TABLESPACE', QuoteUtils.quoteIdentifier(node.indexspace));
+ }
+ }
+
SubLink(node: t.SubLink, context: DeparserContext): string {
const subselect = context.parens(this.visit(node.subselect, context));
@@ -4347,6 +4381,10 @@ export class Deparser implements DeparserVisitor {
}
}
+ if (node.chain) {
+ output.push('AND CHAIN');
+ }
+
return output.join(' ');
}
@@ -4466,10 +4504,10 @@ export class Deparser implements DeparserVisitor {
}
VariableShowStmt(node: t.VariableShowStmt, context: DeparserContext): string {
- if (node.name === 'ALL') {
+ if (node.name === 'all') {
return 'SHOW ALL';
}
- return `SHOW ${node.name}`;
+ return `SHOW ${QuoteUtils.quoteDottedName(node.name.split('.'))}`;
}
CreateSchemaStmt(node: t.CreateSchemaStmt, context: DeparserContext): string {
@@ -6187,7 +6225,7 @@ export class Deparser implements DeparserVisitor {
}
if (context.parentNodeTypes.includes('CreateTableSpaceStmt') || context.parentNodeTypes.includes('AlterTableSpaceOptionsStmt')) {
- return `${node.defname.toUpperCase()} ${argValue}`;
+ return `${node.defname} = ${this.formatOptionValue(node.arg, argValue)}`;
}
if (context.parentNodeTypes.includes('ExplainStmt')) {
@@ -6397,8 +6435,8 @@ export class Deparser implements DeparserVisitor {
return optName;
}
const copyArgType = this.getNodeType(node.arg);
- if (copyArgType === 'Boolean' && this.getNodeData(node.arg).boolval !== false) {
- return optName;
+ if (copyArgType === 'Boolean') {
+ return this.getNodeData(node.arg).boolval ? optName : `${optName} false`;
}
if (copyArgType === 'List') {
const items = ListUtils.unwrapList(this.getNodeData(node.arg).items)
@@ -6407,7 +6445,7 @@ export class Deparser implements DeparserVisitor {
}
if (copyArgType === 'String') {
const sval: string = this.getNodeData(node.arg).sval;
- if (node.defname === 'format') {
+ if (node.defname === 'format' && QuoteUtils.quoteIdentifier(sval) === sval) {
return `${optName} ${sval}`;
}
return `${optName} ${QuoteUtils.escape(sval)}`;
@@ -6525,7 +6563,7 @@ export class Deparser implements DeparserVisitor {
const output: string[] = ['CREATE', 'TABLESPACE'];
if (node.tablespacename) {
- output.push(node.tablespacename);
+ output.push(QuoteUtils.quoteIdentifier(node.tablespacename));
}
if (node.owner) {
@@ -6558,7 +6596,7 @@ export class Deparser implements DeparserVisitor {
}
if (node.tablespacename) {
- output.push(node.tablespacename);
+ output.push(QuoteUtils.quoteIdentifier(node.tablespacename));
}
return output.join(' ');
@@ -6568,7 +6606,7 @@ export class Deparser implements DeparserVisitor {
const output: string[] = ['ALTER', 'TABLESPACE'];
if (node.tablespacename) {
- output.push(node.tablespacename);
+ output.push(QuoteUtils.quoteIdentifier(node.tablespacename));
}
if (node.isReset) {
@@ -8485,6 +8523,9 @@ export class Deparser implements DeparserVisitor {
break;
case 'OBJECT_TRIGGER':
output.push('TRIGGER');
+ if (node.subname) {
+ output.push(QuoteUtils.quoteIdentifier(node.subname));
+ }
break;
case 'OBJECT_TSCONFIGURATION':
output.push('TEXT SEARCH CONFIGURATION');
@@ -8530,8 +8571,8 @@ export class Deparser implements DeparserVisitor {
} else if (node.relation) {
const rangeVarContext = context.spawn('RenameStmt', { objtype: node.relationType });
- // Add ON keyword for policy operations
- if (node.renameType === 'OBJECT_POLICY') {
+ // Add ON keyword for policy and trigger operations
+ if (node.renameType === 'OBJECT_POLICY' || node.renameType === 'OBJECT_TRIGGER') {
output.push('ON');
}
@@ -11417,6 +11458,10 @@ export class Deparser implements DeparserVisitor {
output.push(this.visit(node.expr as any, context));
output.push('AS');
output.push(this.TypeName(node.typeName, context));
+
+ if (node.indent) {
+ output.push('INDENT');
+ }
}
output.push(')');