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
32 changes: 32 additions & 0 deletions __fixtures__/generated/generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -21548,6 +21548,38 @@
"misc/merge-seclabel-6.sql": "SECURITY LABEL FOR anon ON COLUMN myschema.users.name IS 'MASKED WITH FUNCTION anon.fake_name()'",
"misc/merge-seclabel-7.sql": "SECURITY LABEL FOR anon ON TABLE myschema.users IS 'sensitive'",
"misc/merge-seclabel-8.sql": "SECURITY LABEL ON FUNCTION myschema.fn() IS 'labeled'",
"misc/literal-escaping-1.sql": "PREPARE TRANSACTION 'it''s'",
"misc/literal-escaping-2.sql": "COMMIT PREPARED 'it''s'",
"misc/literal-escaping-3.sql": "ROLLBACK PREPARED 'it''s'",
"misc/literal-escaping-4.sql": "NOTIFY ch, 'it''s'",
"misc/literal-escaping-5.sql": "LOAD 'it''s.so'",
"misc/literal-escaping-6.sql": "CREATE TABLESPACE ts LOCATION '/tmp/it''s'",
"misc/literal-escaping-7.sql": "SECURITY LABEL ON TABLE t IS 'it''s'",
"misc/literal-escaping-8.sql": "CREATE SUBSCRIPTION s CONNECTION 'host=it''s' PUBLICATION p",
"misc/literal-escaping-9.sql": "CREATE CONVERSION c FOR 'it''s' TO 'UTF8' FROM f",
"misc/literal-escaping-10.sql": "SET search_path = 'it''s'",
"misc/literal-escaping-11.sql": "CREATE INDEX i ON t USING gist (c opc (opt = 'it''s'))",
"misc/literal-escaping-12.sql": "CREATE INDEX i ON t (c) WITH (opt = 'it''s')",
"misc/literal-escaping-13.sql": "CREATE TABLE t (a int) WITH (opt = 'it''s')",
"misc/literal-escaping-14.sql": "ALTER TABLE t SET (opt = 'it''s')",
"misc/literal-escaping-15.sql": "CREATE FOREIGN DATA WRAPPER w OPTIONS (opt 'it''s')",
"misc/literal-escaping-16.sql": "ALTER FOREIGN DATA WRAPPER w OPTIONS (ADD opt 'it''s')",
"misc/literal-escaping-17.sql": "CREATE SERVER s FOREIGN DATA WRAPPER w OPTIONS (host 'it''s')",
"misc/literal-escaping-18.sql": "CREATE USER MAPPING FOR u SERVER s OPTIONS (password 'it''s')",
"misc/literal-escaping-19.sql": "CREATE FOREIGN TABLE ft (a int OPTIONS (col 'it''s')) SERVER s",
"misc/literal-escaping-20.sql": "CREATE ROLE r PASSWORD 'it''s'",
"misc/literal-escaping-21.sql": "CREATE ROLE r VALID UNTIL 'it''s'",
"misc/literal-escaping-22.sql": "ALTER EXTENSION e UPDATE TO 'it''s'",
"misc/literal-escaping-23.sql": "CREATE EVENT TRIGGER e ON ddl_command_start WHEN TAG IN ('it''s', 'CREATE TABLE') EXECUTE FUNCTION f()",
"misc/literal-escaping-24.sql": "CREATE TYPE ty (input = i, output = o, category = '''')",
"misc/literal-escaping-25.sql": "CREATE AGGREGATE agg (int) (sfunc = f, stype = int, initcond = 'it''s')",
"misc/literal-escaping-26.sql": "CREATE COLLATION c (locale = 'it''s')",
"misc/literal-escaping-27.sql": "CREATE TRIGGER tr BEFORE INSERT ON t FOR EACH ROW EXECUTE FUNCTION f('it''s', 'b')",
"misc/literal-escaping-28.sql": "SELECT * FROM XMLTABLE('/r' PASSING doc COLUMNS a int PATH 'it''s')",
"misc/literal-escaping-29.sql": "SELECT * FROM XMLTABLE('/r' PASSING doc COLUMNS a int PATH 'a', b text PATH 'b''c' DEFAULT 'd' NOT NULL, n FOR ORDINALITY)",
"misc/literal-escaping-30.sql": "SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x' AS x, DEFAULT 'http://d'), '/x:r' PASSING (SELECT doc FROM t) COLUMNS a int) AS xt",
"misc/literal-escaping-31.sql": "SELECT * FROM t, LATERAL XMLTABLE('/r' PASSING t.doc COLUMNS a int PATH 'a') xt",
"misc/literal-escaping-32.sql": "SELECT 1_000.000_1, 1_000.5e1_0, 0x_FFFF_FFFF_FFFF_FFFF",
"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])?)*$' )",
Expand Down
38 changes: 38 additions & 0 deletions __fixtures__/kitchen-sink/misc/literal-escaping.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-- String values containing single quotes must stay inside one literal
-- Ref: constructive-io/pgsql-parser#355
PREPARE TRANSACTION 'it''s';
COMMIT PREPARED 'it''s';
ROLLBACK PREPARED 'it''s';
NOTIFY ch, 'it''s';
LOAD 'it''s.so';
CREATE TABLESPACE ts LOCATION '/tmp/it''s';
SECURITY LABEL ON TABLE t IS 'it''s';
CREATE SUBSCRIPTION s CONNECTION 'host=it''s' PUBLICATION p;
CREATE CONVERSION c FOR 'it''s' TO 'UTF8' FROM f;
SET search_path = 'it''s';
CREATE INDEX i ON t USING gist (c opc (opt = 'it''s'));
CREATE INDEX i ON t (c) WITH (opt = 'it''s');
CREATE TABLE t (a int) WITH (opt = 'it''s');
ALTER TABLE t SET (opt = 'it''s');
CREATE FOREIGN DATA WRAPPER w OPTIONS (opt 'it''s');
ALTER FOREIGN DATA WRAPPER w OPTIONS (ADD opt 'it''s');
CREATE SERVER s FOREIGN DATA WRAPPER w OPTIONS (host 'it''s');
CREATE USER MAPPING FOR u SERVER s OPTIONS (password 'it''s');
CREATE FOREIGN TABLE ft (a int OPTIONS (col 'it''s')) SERVER s;
CREATE ROLE r PASSWORD 'it''s';
CREATE ROLE r VALID UNTIL 'it''s';
ALTER EXTENSION e UPDATE TO 'it''s';
CREATE EVENT TRIGGER e ON ddl_command_start WHEN TAG IN ('it''s', 'CREATE TABLE') EXECUTE FUNCTION f();
CREATE TYPE ty (input = i, output = o, category = '''');
CREATE AGGREGATE agg (int) (sfunc = f, stype = int, initcond = 'it''s');
CREATE COLLATION c (locale = 'it''s');
CREATE TRIGGER tr BEFORE INSERT ON t FOR EACH ROW EXECUTE FUNCTION f('it''s', 'b');

-- XMLTABLE
SELECT * FROM XMLTABLE('/r' PASSING doc COLUMNS a int PATH 'it''s');
SELECT * FROM XMLTABLE('/r' PASSING doc COLUMNS a int PATH 'a', b text PATH 'b''c' DEFAULT 'd' NOT NULL, n FOR ORDINALITY);
SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x' AS x, DEFAULT 'http://d'), '/x:r' PASSING (SELECT doc FROM t) COLUMNS a int) AS xt;
SELECT * FROM t, LATERAL XMLTABLE('/r' PASSING t.doc COLUMNS a int PATH 'a') xt;

-- Numeric literals with underscores
SELECT 1_000.000_1, 1_000.5e1_0, 0x_FFFF_FFFF_FFFF_FFFF;
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

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

it('misc-literal-escaping', async () => {
await fixtures.runFixtureTests([
"misc/literal-escaping-1.sql",
"misc/literal-escaping-2.sql",
"misc/literal-escaping-3.sql",
"misc/literal-escaping-4.sql",
"misc/literal-escaping-5.sql",
"misc/literal-escaping-6.sql",
"misc/literal-escaping-7.sql",
"misc/literal-escaping-8.sql",
"misc/literal-escaping-9.sql",
"misc/literal-escaping-10.sql",
"misc/literal-escaping-11.sql",
"misc/literal-escaping-12.sql",
"misc/literal-escaping-13.sql",
"misc/literal-escaping-14.sql",
"misc/literal-escaping-15.sql",
"misc/literal-escaping-16.sql",
"misc/literal-escaping-17.sql",
"misc/literal-escaping-18.sql",
"misc/literal-escaping-19.sql",
"misc/literal-escaping-20.sql",
"misc/literal-escaping-21.sql",
"misc/literal-escaping-22.sql",
"misc/literal-escaping-23.sql",
"misc/literal-escaping-24.sql",
"misc/literal-escaping-25.sql",
"misc/literal-escaping-26.sql",
"misc/literal-escaping-27.sql",
"misc/literal-escaping-28.sql",
"misc/literal-escaping-29.sql",
"misc/literal-escaping-30.sql",
"misc/literal-escaping-31.sql",
"misc/literal-escaping-32.sql"
]);
});
46 changes: 46 additions & 0 deletions packages/deparser/__tests__/misc/literal-escaping.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Deparser } from '../../src';

// Values that parsed SQL can never produce, so they can't live in a
// kitchen-sink fixture (see __fixtures__/kitchen-sink/misc/literal-escaping.sql).

describe('numeric literals in hand-built ASTs', () => {
const selectConst = (aConst: any) => ({
SelectStmt: {
targetList: [{ ResTarget: { val: { A_Const: aConst } } }],
limitOption: 'LIMIT_OPTION_DEFAULT',
op: 'SETOP_NONE'
}
});

it.each([
['wrapped fval', { fval: { fval: '1; DROP TABLE t' } }],
['unwrapped fval', { fval: '1 OR 1=1' }],
['val.Float', { val: { Float: { fval: '1)--' } } }],
['wrapped ival', { ival: { ival: '1; DROP TABLE t' } }],
['unwrapped ival', { ival: '1 OR 1=1' }],
['val.Integer', { val: { Integer: { ival: '1)--' } } }],
['doubled underscore', { fval: { fval: '1__0.5' } }],
['trailing underscore', { fval: { fval: '1_.5' } }],
['trailing hex underscore', { fval: { fval: '0xFF_' } }]
])('rejects non-numeric %s', (_label, aConst) => {
expect(() => Deparser.deparse(selectConst(aConst) as any)).toThrow(/Invalid (numeric|integer) literal/);
});

it('rejects non-numeric Float and Integer nodes', () => {
const deparser = new Deparser([]);
expect(() => deparser.Float({ fval: '1; DROP TABLE t' }, {} as any)).toThrow(/Invalid numeric literal/);
expect(() => deparser.Integer({ ival: '1; DROP TABLE t' as any }, {} as any)).toThrow(/Invalid integer literal/);
});
});

describe('TableFunc (JSON_TABLE)', () => {
it('does not re-quote the row path', () => {
const deparser = new Deparser([]);
const sql = deparser.TableFunc({
functype: 'TFT_JSON_TABLE',
docexpr: { ColumnRef: { fields: [{ String: { sval: 'doc' } }] } },
rowexpr: { A_Const: { sval: { sval: "$.a' OR '1'='1" } } }
} as any, {} as any);
expect(sql).toBe("JSON_TABLE (doc) , '$.a'' OR ''1''=''1'");
});
});
Loading
Loading