Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: oguimbal/pgsql-ast-parser
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 10.4.0
Choose a base ref
...
head repository: oguimbal/pgsql-ast-parser
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 10.5.0
Choose a head ref
  • 6 commits
  • 13 files changed
  • 2 contributors

Commits on Jun 20, 2022

  1. test: add failing case illustrating issue #83

    gthb authored and oguimbal committed Jun 20, 2022
    Copy the full SHA
    6397102 View commit details
  2. Copy the full SHA
    b39e82b View commit details
  3. Copy the full SHA
    5111433 View commit details
  4. Copy the full SHA
    2959194 View commit details
  5. bump

    oguimbal committed Jun 20, 2022
    Copy the full SHA
    25f9dfb View commit details
  6. Copy the full SHA
    21fc0fc View commit details
Showing with 179 additions and 77 deletions.
  1. +10 −1 .deno/ast-mapper.ts
  2. +10 −1 .deno/syntax/ast.ts
  3. +19 −8 .deno/syntax/main.ne.ts
  4. +15 −27 .deno/to-sql.ts
  5. +1 −1 package.json
  6. +26 −2 src/ast-mapper.spec.ts
  7. +10 −1 src/ast-mapper.ts
  8. +10 −1 src/syntax/ast.ts
  9. +13 −6 src/syntax/create-table.ne
  10. +29 −0 src/syntax/create-table.spec.ts
  11. +3 −2 src/syntax/delete.ne
  12. +18 −0 src/syntax/delete.spec.ts
  13. +15 −27 src/to-sql.ts
11 changes: 10 additions & 1 deletion .deno/ast-mapper.ts
Original file line number Diff line number Diff line change
@@ -167,7 +167,7 @@ export function arrayNilMap<T extends Object>(this: void, collection: T[] | nil,
for (let i = 0; i < collection.length; i++) {
const orig = collection[i];
const val = mapper(orig);
if (!val || val !== orig) {
if (!changed && (!val || val !== orig)) {
changed = true;
ret = collection.slice(0, i);
}
@@ -593,6 +593,15 @@ export class AstDefaultMapper implements IAstMapper {
expr: def,
});
}
case 'reference': {
const foreignTable = this.tableRef(c.foreignTable);
if (!foreignTable) {
return null;
}
return assignChanged(c, {
foreignTable,
});
}
default:
throw NotSupported.never(c);
}
11 changes: 10 additions & 1 deletion .deno/syntax/ast.ts
Original file line number Diff line number Diff line change
@@ -189,6 +189,7 @@ export interface TruncateTableStatement extends PGNode {
type: 'truncate table';
tables: QName[];
identity?: 'restart' | 'continue';
cascade?: 'cascade' | 'restrict';
}

export interface DropStatement extends PGNode {
@@ -501,6 +502,7 @@ export type ColumnConstraint
= ColumnConstraintSimple
| ColumnConstraintDefault
| AlterColumnAddGenerated
| ColumnConstraintReference
| ColumnConstraintCheck;

export interface ColumnConstraintSimple extends PGNode {
@@ -511,15 +513,22 @@ export interface ColumnConstraintSimple extends PGNode {
constraintName?: Name;
}

export interface ColumnConstraintReference extends TableReference, PGNode {
type: 'reference';
}

export interface ColumnConstraintDefault extends PGNode {
type: 'default';
default: Expr;
constraintName?: Name;
}

export interface ColumnConstraintForeignKey extends PGNode {
export interface ColumnConstraintForeignKey extends TableReference, PGNode {
type: 'foreign key';
constraintName?: Name;
}

export interface TableReference {
foreignTable: QName;
foreignColumns: Name[];
onDelete?: ConstraintAction;
27 changes: 19 additions & 8 deletions .deno/syntax/main.ne.ts
Original file line number Diff line number Diff line change
@@ -1711,15 +1711,20 @@ const grammar: Grammar = {
type: 'check',
expr: unwrap(x[1]),
}) },
{"name": "createtable_constraint_foreignkey$ebnf$1", "symbols": []},
{"name": "createtable_constraint_foreignkey$ebnf$1", "symbols": ["createtable_constraint_foreignkey$ebnf$1", "createtable_constraint_foreignkey_onsometing"], "postprocess": (d) => d[0].concat([d[1]])},
{"name": "createtable_constraint_foreignkey", "symbols": [(lexerAny.has("kw_foreign") ? {type: "kw_foreign"} : kw_foreign), "kw_key", "collist_paren", (lexerAny.has("kw_references") ? {type: "kw_references"} : kw_references), "table_ref", "collist_paren", "createtable_constraint_foreignkey$ebnf$1"], "postprocess": (x: any[]) => {
{"name": "createtable_constraint_foreignkey", "symbols": [(lexerAny.has("kw_foreign") ? {type: "kw_foreign"} : kw_foreign), "kw_key", "collist_paren", "createtable_references"], "postprocess": (x: any[]) => {
return track(x, {
type: 'foreign key',
localColumns: x[2].map(asName),
foreignTable: unwrap(x[4]),
foreignColumns: x[5].map(asName),
...x[6].reduce((a: any, b: any) => ({...a, ...b}), {}),
...x[3],
});
} },
{"name": "createtable_references$ebnf$1", "symbols": []},
{"name": "createtable_references$ebnf$1", "symbols": ["createtable_references$ebnf$1", "createtable_constraint_foreignkey_onsometing"], "postprocess": (d) => d[0].concat([d[1]])},
{"name": "createtable_references", "symbols": [(lexerAny.has("kw_references") ? {type: "kw_references"} : kw_references), "table_ref", "collist_paren", "createtable_references$ebnf$1"], "postprocess": (x: any[]) => {
return track(x, {
foreignTable: unwrap(x[1]),
foreignColumns: x[2].map(asName),
...x[3].reduce((a: any, b: any) => ({...a, ...b}), {}),
});
} },
{"name": "createtable_constraint_foreignkey_onsometing", "symbols": [(lexerAny.has("kw_on") ? {type: "kw_on"} : kw_on), "kw_delete", "createtable_constraint_on_action"], "postprocess": x => track(x, {onDelete: last(x)})},
@@ -1791,6 +1796,7 @@ const grammar: Grammar = {
{"name": "createtable_column_constraint_def", "symbols": [(lexerAny.has("kw_null") ? {type: "kw_null"} : kw_null)], "postprocess": x => track(x, { type: 'null' })},
{"name": "createtable_column_constraint_def", "symbols": [(lexerAny.has("kw_default") ? {type: "kw_default"} : kw_default), "expr"], "postprocess": x => track(x, { type: 'default', default: unwrap(x[1]) })},
{"name": "createtable_column_constraint_def", "symbols": [(lexerAny.has("kw_check") ? {type: "kw_check"} : kw_check), "expr_paren"], "postprocess": x => track(x, { type: 'check', expr: unwrap(x[1]) })},
{"name": "createtable_column_constraint_def", "symbols": ["createtable_references"], "postprocess": x => track(x, { type: 'reference', ...unwrap(x) })},
{"name": "createtable_column_constraint_def", "symbols": ["altercol_generated"]},
{"name": "createtable_collate", "symbols": [(lexerAny.has("kw_collate") ? {type: "kw_collate"} : kw_collate), "qualified_name"]},
{"name": "createtable_opts$subexpression$1", "symbols": ["word"], "postprocess": kw('inherits')},
@@ -2316,10 +2322,15 @@ const grammar: Grammar = {
{"name": "delete_truncate$ebnf$1$subexpression$1", "symbols": ["delete_truncate$ebnf$1$subexpression$1$subexpression$1", "kw_identity"]},
{"name": "delete_truncate$ebnf$1", "symbols": ["delete_truncate$ebnf$1$subexpression$1"], "postprocess": id},
{"name": "delete_truncate$ebnf$1", "symbols": [], "postprocess": () => null},
{"name": "delete_truncate", "symbols": ["delete_truncate$subexpression$1", "delete_truncate$macrocall$1", "delete_truncate$ebnf$1"], "postprocess": x => track(x, {
{"name": "delete_truncate$ebnf$2$subexpression$1", "symbols": ["kw_restrict"]},
{"name": "delete_truncate$ebnf$2$subexpression$1", "symbols": ["kw_cascade"]},
{"name": "delete_truncate$ebnf$2", "symbols": ["delete_truncate$ebnf$2$subexpression$1"], "postprocess": id},
{"name": "delete_truncate$ebnf$2", "symbols": [], "postprocess": () => null},
{"name": "delete_truncate", "symbols": ["delete_truncate$subexpression$1", "delete_truncate$macrocall$1", "delete_truncate$ebnf$1", "delete_truncate$ebnf$2"], "postprocess": x => track(x, {
type: 'truncate table',
tables: x[1],
...x[2] && { identity: toStr(x[2][0]) }
...x[2] && { identity: toStr(x[2][0]) },
... x[3] && {cascade: toStr(x[3]) },
}) },
{"name": "create_sequence_statement$ebnf$1$subexpression$1", "symbols": ["kw_temp"]},
{"name": "create_sequence_statement$ebnf$1$subexpression$1", "symbols": ["kw_temporary"]},
42 changes: 15 additions & 27 deletions .deno/to-sql.ts
Original file line number Diff line number Diff line change
@@ -49,12 +49,14 @@ function list<T>(elems: T[], act: (e: T) => any, addParen: boolean) {


function addConstraint(c: ColumnConstraint | TableConstraint, m: IAstVisitor) {
ret.push(c.type);
switch (c.type) {
case 'foreign key':
ret.push('('
ret.push(' foreign key ('
, ...c.localColumns.map(name).join(', ')
, ') REFERENCES ');
, ')');
// 👈 There is no "break" here... that's not an error, we want to fall throught the 'reference' case
case 'reference':
ret.push(' REFERENCES ');
m.tableRef(c.foreignTable);
ret.push('('
, ...c.foreignColumns.map(name).join(', ')
@@ -71,20 +73,23 @@ function addConstraint(c: ColumnConstraint | TableConstraint, m: IAstVisitor) {
break;
case 'primary key':
case 'unique':
ret.push(' ', c.type, ' ');
if ('columns' in c) {
ret.push('('
, ...c.columns.map(name).join(', ')
, ') ');
}
break;
case 'check':
ret.push(' check ');
m.expr(c.expr);
break;
case 'not null':
case 'null':
ret.push(' ', c.type, ' ');
break;
case 'default':
ret.push(' DEFAULT ');
ret.push(' default ');
m.expr(c.default);
break;
case 'add generated':
@@ -94,6 +99,7 @@ function addConstraint(c: ColumnConstraint | TableConstraint, m: IAstVisitor) {
default:
throw NotSupported.never(c)
}
ret.push(' ');
}
function visitQualifiedName(cs: QName, forceDoubleQuote?: boolean) {
if (cs.schema) {
@@ -636,31 +642,10 @@ const visitor = astVisitor<IAstFullVisitor>(m => ({
},

constraint: cst => {
if (cst.constraintName) {
if ('constraintName' in cst && cst.constraintName) {
ret.push(' CONSTRAINT ', name(cst.constraintName), ' ');
}
switch (cst.type) {
case 'not null':
case 'null':
case 'primary key':
case 'unique':
ret.push(' ', cst.type, ' ');
return;
case 'default':
ret.push(' DEFAULT ');
m.expr(cst.default);
break;
case 'check':
ret.push(' CHECK ');
m.expr(cst.expr);
break;
case 'add generated':
ret.push(' GENERATED ');
visitGenerated(m, cst);
break;
default:
throw NotSupported.never(cst);
}
addConstraint(cst, m);
},

do: d => {
@@ -983,6 +968,9 @@ const visitor = astVisitor<IAstFullVisitor>(m => ({
break;
}
}
if (t.cascade) {
ret.push(' ', t.cascade, ' ');
}
},

delete: t => {
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pgsql-ast-parser",
"version": "10.4.0",
"version": "10.5.0",
"description": "Yet another simple Postgres SQL parser/modifier",
"main": "index.js",
"repository": "https://github.com/oguimbal/pgsql-ast-parser",
28 changes: 26 additions & 2 deletions src/ast-mapper.spec.ts
Original file line number Diff line number Diff line change
@@ -27,6 +27,15 @@ describe('Ast mapper', () => {
})
}

function testExprs(statement: string, map: MapperBuilder, exprs: Expr[]) {
const toMap = parse(statement);
const mapped = astMapper(map).statement(toMap);
assert.deepEqual(mapped, {
type: 'select',
columns: exprs.map(expr => ({ expr })),
})
}

it('maps a select constant', () => {
testExpr('select 42', b => ({
constant: c => assignChanged(c, {
@@ -123,6 +132,22 @@ describe('Ast mapper', () => {
});
})

it('maps multiple calls', () => {
testExprs('select fn(a), fn(b), fn(c)', () => ({
call: c => c.args[0],
}), [
{
type: 'ref',
name: 'a',
}, {
type: 'ref',
name: 'b',
}, {
type: 'ref',
name: 'c',
}
]);
})

it('maps array literal', () => {
testExpr('select (a,b)', b => ({
@@ -140,8 +165,7 @@ describe('Ast mapper', () => {
type: 'ref',
name: 'foo',
});
})

});

it('maps deep', () => {
// create a mapper
11 changes: 10 additions & 1 deletion src/ast-mapper.ts
Original file line number Diff line number Diff line change
@@ -167,7 +167,7 @@ export function arrayNilMap<T extends Object>(this: void, collection: T[] | nil,
for (let i = 0; i < collection.length; i++) {
const orig = collection[i];
const val = mapper(orig);
if (!val || val !== orig) {
if (!changed && (!val || val !== orig)) {
changed = true;
ret = collection.slice(0, i);
}
@@ -593,6 +593,15 @@ export class AstDefaultMapper implements IAstMapper {
expr: def,
});
}
case 'reference': {
const foreignTable = this.tableRef(c.foreignTable);
if (!foreignTable) {
return null;
}
return assignChanged(c, {
foreignTable,
});
}
default:
throw NotSupported.never(c);
}
11 changes: 10 additions & 1 deletion src/syntax/ast.ts
Original file line number Diff line number Diff line change
@@ -189,6 +189,7 @@ export interface TruncateTableStatement extends PGNode {
type: 'truncate table';
tables: QName[];
identity?: 'restart' | 'continue';
cascade?: 'cascade' | 'restrict';
}

export interface DropStatement extends PGNode {
@@ -501,6 +502,7 @@ export type ColumnConstraint
= ColumnConstraintSimple
| ColumnConstraintDefault
| AlterColumnAddGenerated
| ColumnConstraintReference
| ColumnConstraintCheck;

export interface ColumnConstraintSimple extends PGNode {
@@ -511,15 +513,22 @@ export interface ColumnConstraintSimple extends PGNode {
constraintName?: Name;
}

export interface ColumnConstraintReference extends TableReference, PGNode {
type: 'reference';
}

export interface ColumnConstraintDefault extends PGNode {
type: 'default';
default: Expr;
constraintName?: Name;
}

export interface ColumnConstraintForeignKey extends PGNode {
export interface ColumnConstraintForeignKey extends TableReference, PGNode {
type: 'foreign key';
constraintName?: Name;
}

export interface TableReference {
foreignTable: QName;
foreignColumns: Name[];
onDelete?: ConstraintAction;
19 changes: 13 additions & 6 deletions src/syntax/create-table.ne
Original file line number Diff line number Diff line change
@@ -83,16 +83,22 @@ createtable_constraint_def_check
}) %}

createtable_constraint_foreignkey
-> %kw_foreign kw_key collist_paren
%kw_references table_ref collist_paren
createtable_constraint_foreignkey_onsometing:*
-> %kw_foreign kw_key collist_paren createtable_references
{% (x: any[]) => {
return track(x, {
type: 'foreign key',
localColumns: x[2].map(asName),
foreignTable: unwrap(x[4]),
foreignColumns: x[5].map(asName),
...x[6].reduce((a: any, b: any) => ({...a, ...b}), {}),
...x[3],
});
} %}

createtable_references -> %kw_references table_ref collist_paren
createtable_constraint_foreignkey_onsometing:*
{% (x: any[]) => {
return track(x, {
foreignTable: unwrap(x[1]),
foreignColumns: x[2].map(asName),
...x[3].reduce((a: any, b: any) => ({...a, ...b}), {}),
});
} %}

@@ -155,6 +161,7 @@ createtable_column_constraint_def
| %kw_null {% x => track(x, { type: 'null' }) %}
| %kw_default expr {% x => track(x, { type: 'default', default: unwrap(x[1]) }) %}
| %kw_check expr_paren {% x => track(x, { type: 'check', expr: unwrap(x[1]) }) %}
| createtable_references {% x => track(x, { type: 'reference', ...unwrap(x) }) %}
| altercol_generated

createtable_collate
Loading