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.3.1
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.4.0
Choose a head ref
  • 3 commits
  • 15 files changed
  • 2 contributors

Commits on Jun 15, 2022

  1. Adding support for:

     * WITH ORDINALITY when selecting from a function
     * assigning column names to alias of function when selecting from it
    
    Also fixed too-permissive of syntax that allowed selecting from aggregate function
    mjburghoffer authored and oguimbal committed Jun 15, 2022
    Copy the full SHA
    b194784 View commit details
  2. UPDATE... FROM support

    oguimbal committed Jun 15, 2022
    Copy the full SHA
    376f71d View commit details
  3. Copy the full SHA
    20e5aec View commit details
Showing with 312 additions and 37 deletions.
  1. +4 −0 .deno/ast-mapper.ts
  2. +6 −1 .deno/syntax/ast.ts
  3. +63 −19 .deno/syntax/main.ne.ts
  4. +20 −1 .deno/to-sql.ts
  5. +1 −1 package.json
  6. +4 −0 src/ast-mapper.ts
  7. +6 −1 src/syntax/ast.ts
  8. +13 −1 src/syntax/base.ne
  9. +12 −1 src/syntax/expr.ne
  10. +22 −7 src/syntax/select.ne
  11. +104 −0 src/syntax/select.spec.ts
  12. +1 −1 src/syntax/spec-utils.ts
  13. +5 −2 src/syntax/update.ne
  14. +31 −1 src/syntax/update.spec.ts
  15. +20 −1 src/to-sql.ts
4 changes: 4 additions & 0 deletions .deno/ast-mapper.ts
Original file line number Diff line number Diff line change
@@ -445,6 +445,9 @@ export class AstDefaultMapper implements IAstMapper {
if (!table) {
return null; // nothing to update
}

const from = val.from && this.from(val.from);

const where = val.where && this.expr(val.where);

const sets = arrayNilMap(val.sets, x => this.set(x));
@@ -457,6 +460,7 @@ export class AstDefaultMapper implements IAstMapper {
table,
where,
sets,
from,
returning,
});
}
7 changes: 6 additions & 1 deletion .deno/syntax/ast.ts
Original file line number Diff line number Diff line change
@@ -468,6 +468,9 @@ export interface Name extends PGNode {
name: string;
}

export interface TableAliasName extends Name, PGNode {
columns?: Name[];
}

export interface QName extends Name, PGNode {
schema?: string;
@@ -618,6 +621,7 @@ export interface UpdateStatement extends PGNode {
table: QNameAliased;
sets: SetStatement[];
where?: Expr | nil;
from?: From | nil;
returning?: SelectedColumn[] | nil;
}

@@ -637,8 +641,9 @@ export type From = FromTable


export interface FromCall extends ExprCall, PGNode {
alias?: Name;
alias?: TableAliasName;
join?: JoinClause | nil;
withOrdinality?: boolean;
};


82 changes: 63 additions & 19 deletions .deno/syntax/main.ne.ts
Original file line number Diff line number Diff line change
@@ -17,12 +17,12 @@ declare var kw_unique: any;
declare var quoted_word: any;
declare var word: any;
declare var kw_not: any;
declare var kw_with: any;
declare var kw_null: any;
declare var kw_array: any;
declare var lbracket: any;
declare var rbracket: any;
declare var kw_precision: any;
declare var kw_with: any;
declare var kw_as: any;
declare var kw_current_schema: any;
declare var kw_all: any;
@@ -250,6 +250,7 @@ declare var kw_on: any;
declare var kw_returning: any;
declare var kw_do: any;
declare var kw_where: any;
declare var kw_from: any;
declare var kw_returning: any;
declare var op_eq: any;
declare var kw_table: any;
@@ -316,8 +317,18 @@ import {track, box, unbox, doubleQuoted} from '../lexer.ts';
}

function asName(val: any): any {
return asNameWithColumns(val, undefined);
}

function asNameWithColumns(val: any, columns: any[] | undefined): any {
const name = toStr(val);
return track(val, {name});
if (!columns || columns.length === 0) {
return track(val, {name});
}
return track(val, {
name,
columns: columns.map(c => ({name: toStr(c)})),
});
}

function asLit(val: any): any {
@@ -550,6 +561,7 @@ const grammar: Grammar = {
{"name": "kw_name", "symbols": [(lexerAny.has("word") ? {type: "word"} : word)], "postprocess": notReservedKw('name')},
{"name": "kw_enum", "symbols": [(lexerAny.has("word") ? {type: "word"} : word)], "postprocess": notReservedKw('enum')},
{"name": "kw_show", "symbols": [(lexerAny.has("word") ? {type: "word"} : word)], "postprocess": notReservedKw('show')},
{"name": "kw_ordinality", "symbols": [(lexerAny.has("word") ? {type: "word"} : word)], "postprocess": notReservedKw('ordinality')},
{"name": "kw_overriding", "symbols": [(lexerAny.has("word") ? {type: "word"} : word)], "postprocess": notReservedKw('overriding')},
{"name": "kw_over", "symbols": [(lexerAny.has("word") ? {type: "word"} : word)], "postprocess": notReservedKw('over')},
{"name": "kw_system", "symbols": [(lexerAny.has("word") ? {type: "word"} : word)], "postprocess": notReservedKw('system')},
@@ -569,6 +581,7 @@ const grammar: Grammar = {
{"name": "kw_refresh", "symbols": [(lexerAny.has("word") ? {type: "word"} : word)], "postprocess": notReservedKw('refresh')},
{"name": "kw_ifnotexists", "symbols": ["kw_if", (lexerAny.has("kw_not") ? {type: "kw_not"} : kw_not), "kw_exists"]},
{"name": "kw_ifexists", "symbols": ["kw_if", "kw_exists"]},
{"name": "kw_withordinality", "symbols": [(lexerAny.has("kw_with") ? {type: "kw_with"} : kw_with), "kw_ordinality"]},
{"name": "kw_not_null", "symbols": [(lexerAny.has("kw_not") ? {type: "kw_not"} : kw_not), (lexerAny.has("kw_null") ? {type: "kw_null"} : kw_null)]},
{"name": "kw_primary_key", "symbols": [(lexerAny.has("kw_primary") ? {type: "kw_primary"} : kw_primary), "kw_key"]},
{"name": "data_type$ebnf$1$subexpression$1$macrocall$2", "symbols": ["int"]},
@@ -853,18 +866,37 @@ const grammar: Grammar = {
type: 'values',
values: x[1],
}) },
{"name": "stb_call$ebnf$1$subexpression$1$ebnf$1", "symbols": [(lexerAny.has("kw_as") ? {type: "kw_as"} : kw_as)], "postprocess": id},
{"name": "stb_call$ebnf$1$subexpression$1$ebnf$1", "symbols": [], "postprocess": () => null},
{"name": "stb_call$ebnf$1$subexpression$1", "symbols": ["stb_call$ebnf$1$subexpression$1$ebnf$1", "ident"], "postprocess": last},
{"name": "stb_call$ebnf$1", "symbols": ["stb_call$ebnf$1$subexpression$1"], "postprocess": id},
{"name": "stb_call$ebnf$1", "symbols": ["kw_withordinality"], "postprocess": id},
{"name": "stb_call$ebnf$1", "symbols": [], "postprocess": () => null},
{"name": "stb_call", "symbols": ["expr_call", "stb_call$ebnf$1"], "postprocess": x =>
!x[1]
? x[0]
: track(x, {
...x[0],
alias: asName(x[1]),
}) },
{"name": "stb_call$ebnf$2", "symbols": ["stb_call_alias"], "postprocess": id},
{"name": "stb_call$ebnf$2", "symbols": [], "postprocess": () => null},
{"name": "stb_call", "symbols": ["expr_function_call", "stb_call$ebnf$1", "stb_call$ebnf$2"], "postprocess": x => {
const withOrdinality = x[1];
const alias = x[2];

if (!withOrdinality && !alias) {
return x[0];
}

return track(x, {
...x[0],
withOrdinality: withOrdinality ? true : undefined,
alias: alias ? asNameWithColumns(alias[0], alias[1]) : undefined,
});
} },
{"name": "stb_call_alias$subexpression$1$ebnf$1", "symbols": [(lexerAny.has("kw_as") ? {type: "kw_as"} : kw_as)], "postprocess": id},
{"name": "stb_call_alias$subexpression$1$ebnf$1", "symbols": [], "postprocess": () => null},
{"name": "stb_call_alias$subexpression$1", "symbols": ["stb_call_alias$subexpression$1$ebnf$1", "ident"], "postprocess": last},
{"name": "stb_call_alias$ebnf$1", "symbols": ["stb_call_alias_list"], "postprocess": id},
{"name": "stb_call_alias$ebnf$1", "symbols": [], "postprocess": () => null},
{"name": "stb_call_alias", "symbols": ["stb_call_alias$subexpression$1", "stb_call_alias$ebnf$1"]},
{"name": "stb_call_alias_list", "symbols": ["lparen", "stb_call_alias_list_raw", "rparen"], "postprocess": get(1)},
{"name": "stb_call_alias_list_raw$ebnf$1", "symbols": []},
{"name": "stb_call_alias_list_raw$ebnf$1$subexpression$1", "symbols": ["comma", "ident"], "postprocess": last},
{"name": "stb_call_alias_list_raw$ebnf$1", "symbols": ["stb_call_alias_list_raw$ebnf$1", "stb_call_alias_list_raw$ebnf$1$subexpression$1"], "postprocess": (d) => d[0].concat([d[1]])},
{"name": "stb_call_alias_list_raw", "symbols": ["ident", "stb_call_alias_list_raw$ebnf$1"], "postprocess": ([head, tail]) => {
return [head, ...(tail || [])];
} },
{"name": "select_table_join$ebnf$1", "symbols": ["select_table_join_clause"], "postprocess": id},
{"name": "select_table_join$ebnf$1", "symbols": [], "postprocess": () => null},
{"name": "select_table_join", "symbols": ["select_join_op", (lexerAny.has("kw_join") ? {type: "kw_join"} : kw_join), "select_from_subject", "select_table_join$ebnf$1"], "postprocess": x => track(x, {
@@ -1432,6 +1464,13 @@ const grammar: Grammar = {
})
})
} },
{"name": "expr_function_call$ebnf$1", "symbols": ["expr_list_raw"], "postprocess": id},
{"name": "expr_function_call$ebnf$1", "symbols": [], "postprocess": () => null},
{"name": "expr_function_call", "symbols": ["expr_fn_name", "lparen", "expr_function_call$ebnf$1", "rparen"], "postprocess": x => track(x, {
type: 'call',
function: unwrap(x[0]),
args: x[2] || [],
}) },
{"name": "expr_call$ebnf$1$subexpression$1", "symbols": [(lexerAny.has("kw_all") ? {type: "kw_all"} : kw_all)]},
{"name": "expr_call$ebnf$1$subexpression$1", "symbols": [(lexerAny.has("kw_distinct") ? {type: "kw_distinct"} : kw_distinct)]},
{"name": "expr_call$ebnf$1", "symbols": ["expr_call$ebnf$1$subexpression$1"], "postprocess": id},
@@ -2067,19 +2106,24 @@ const grammar: Grammar = {
do: { sets: x[1] },
...x[2] && { where: x[2] },
}) },
{"name": "update_statement$ebnf$1", "symbols": ["select_where"], "postprocess": id},
{"name": "update_statement$ebnf$1$subexpression$1", "symbols": [(lexerAny.has("kw_from") ? {type: "kw_from"} : kw_from), "select_from_subject"], "postprocess": last},
{"name": "update_statement$ebnf$1", "symbols": ["update_statement$ebnf$1$subexpression$1"], "postprocess": id},
{"name": "update_statement$ebnf$1", "symbols": [], "postprocess": () => null},
{"name": "update_statement$ebnf$2$subexpression$1", "symbols": [(lexerAny.has("kw_returning") ? {type: "kw_returning"} : kw_returning), "select_expr_list_aliased"], "postprocess": last},
{"name": "update_statement$ebnf$2", "symbols": ["update_statement$ebnf$2$subexpression$1"], "postprocess": id},
{"name": "update_statement$ebnf$2", "symbols": ["select_where"], "postprocess": id},
{"name": "update_statement$ebnf$2", "symbols": [], "postprocess": () => null},
{"name": "update_statement", "symbols": ["kw_update", "table_ref_aliased", "kw_set", "update_set_list", "update_statement$ebnf$1", "update_statement$ebnf$2"], "postprocess": x => {
const where = unwrap(x[4]);
const returning = x[5];
{"name": "update_statement$ebnf$3$subexpression$1", "symbols": [(lexerAny.has("kw_returning") ? {type: "kw_returning"} : kw_returning), "select_expr_list_aliased"], "postprocess": last},
{"name": "update_statement$ebnf$3", "symbols": ["update_statement$ebnf$3$subexpression$1"], "postprocess": id},
{"name": "update_statement$ebnf$3", "symbols": [], "postprocess": () => null},
{"name": "update_statement", "symbols": ["kw_update", "table_ref_aliased", "kw_set", "update_set_list", "update_statement$ebnf$1", "update_statement$ebnf$2", "update_statement$ebnf$3"], "postprocess": x => {
const from = unwrap(x[4]);
const where = unwrap(x[5]);
const returning = x[6];
return track(x, {
type: 'update',
table: unwrap(x[1]),
sets: x[3],
...where ? {where} : {},
...from ? {from} : {},
...returning ? {returning} : {},
});
} },
21 changes: 20 additions & 1 deletion .deno/to-sql.ts
Original file line number Diff line number Diff line change
@@ -1025,8 +1025,22 @@ const visitor = astVisitor<IAstFullVisitor>(m => ({

join(m, s.join, () => {
m.call(s);
if (s.withOrdinality) {
ret.push(' WITH ORDINALITY')
}
if (s.alias) {
ret.push(' AS ', name(s.alias), ' ');
ret.push(' AS ', name<Name>(s.alias), ' ');
const len = s.alias.columns?.length ?? 0;
if (len > 0) {
ret.push('(')
for (let ix = 0; ix < len; ++ix) {
if (ix !== 0) {
ret.push(', ')
}
ret.push(name(s.alias.columns![ix]));
}
ret.push(')')
}
}
});

@@ -1422,6 +1436,11 @@ const visitor = astVisitor<IAstFullVisitor>(m => ({
ret.push(' SET ');
list(u.sets, s => m.set(s), false);
ret.push(' ');
if (u.from) {
ret.push('FROM ');
m.from(u.from);
ret.push(' ');
}
if (u.where) {
ret.push('WHERE ');
m.expr(u.where);
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.3.1",
"version": "10.4.0",
"description": "Yet another simple Postgres SQL parser/modifier",
"main": "index.js",
"repository": "https://github.com/oguimbal/pgsql-ast-parser",
4 changes: 4 additions & 0 deletions src/ast-mapper.ts
Original file line number Diff line number Diff line change
@@ -445,6 +445,9 @@ export class AstDefaultMapper implements IAstMapper {
if (!table) {
return null; // nothing to update
}

const from = val.from && this.from(val.from);

const where = val.where && this.expr(val.where);

const sets = arrayNilMap(val.sets, x => this.set(x));
@@ -457,6 +460,7 @@ export class AstDefaultMapper implements IAstMapper {
table,
where,
sets,
from,
returning,
});
}
7 changes: 6 additions & 1 deletion src/syntax/ast.ts
Original file line number Diff line number Diff line change
@@ -468,6 +468,9 @@ export interface Name extends PGNode {
name: string;
}

export interface TableAliasName extends Name, PGNode {
columns?: Name[];
}

export interface QName extends Name, PGNode {
schema?: string;
@@ -618,6 +621,7 @@ export interface UpdateStatement extends PGNode {
table: QNameAliased;
sets: SetStatement[];
where?: Expr | nil;
from?: From | nil;
returning?: SelectedColumn[] | nil;
}

@@ -637,8 +641,9 @@ export type From = FromTable


export interface FromCall extends ExprCall, PGNode {
alias?: Name;
alias?: TableAliasName;
join?: JoinClause | nil;
withOrdinality?: boolean;
};


14 changes: 13 additions & 1 deletion src/syntax/base.ne
Original file line number Diff line number Diff line change
@@ -12,8 +12,18 @@
}

function asName(val: any): any {
return asNameWithColumns(val, undefined);
}

function asNameWithColumns(val: any, columns: any[] | undefined): any {
const name = toStr(val);
return track(val, {name});
if (!columns || columns.length === 0) {
return track(val, {name});
}
return track(val, {
name,
columns: columns.map(c => ({name: toStr(c)})),
});
}

function asLit(val: any): any {
@@ -211,6 +221,7 @@ kw_identity -> %word {% notReservedKw('identity') %}
kw_name -> %word {% notReservedKw('name') %}
kw_enum -> %word {% notReservedKw('enum') %}
kw_show -> %word {% notReservedKw('show') %}
kw_ordinality -> %word {% notReservedKw('ordinality') %}
kw_overriding -> %word {% notReservedKw('overriding') %}
kw_over -> %word {% notReservedKw('over') %}
kw_system -> %word {% notReservedKw('system') %}
@@ -233,6 +244,7 @@ kw_refresh -> %word {% notReservedKw('refresh') %}
# === Composite keywords
kw_ifnotexists -> kw_if %kw_not kw_exists
kw_ifexists -> kw_if kw_exists
kw_withordinality -> %kw_with kw_ordinality
kw_not_null -> %kw_not %kw_null
kw_primary_key -> %kw_primary kw_key

13 changes: 12 additions & 1 deletion src/syntax/expr.ne
Original file line number Diff line number Diff line change
@@ -183,7 +183,18 @@ expr_subarray_items
})
} %}


# Cannot select from aggregate functions. Syntactically however, there is no way
# to determine that a function is an aggregate. At least we can rule out using
# DISTINCT, ALL, ORDER BY, and FILTER as part of the expression.
expr_function_call -> expr_fn_name
lparen
expr_list_raw:?
rparen
{% x => track(x, {
type: 'call',
function: unwrap(x[0]),
args: x[2] || [],
}) %}

expr_call -> expr_fn_name
lparen
29 changes: 22 additions & 7 deletions src/syntax/select.ne
Original file line number Diff line number Diff line change
@@ -86,13 +86,28 @@ select_values -> kw_values insert_values {% x => track(x, {
}) %}


stb_call -> expr_call (%kw_as:? ident {% last %}):? {% x =>
!x[1]
? x[0]
: track(x, {
...x[0],
alias: asName(x[1]),
}) %}
stb_call -> expr_function_call kw_withordinality:? stb_call_alias:? {% x => {
const withOrdinality = x[1];
const alias = x[2];

if (!withOrdinality && !alias) {
return x[0];
}

return track(x, {
...x[0],
withOrdinality: withOrdinality ? true : undefined,
alias: alias ? asNameWithColumns(alias[0], alias[1]) : undefined,
});
} %}

stb_call_alias -> (%kw_as:? ident {% last %}) stb_call_alias_list:?

stb_call_alias_list -> lparen stb_call_alias_list_raw rparen {% get(1) %}

stb_call_alias_list_raw -> ident (comma ident {% last %}):* {% ([head, tail]) => {
return [head, ...(tail || [])];
} %}

select_table_join
-> select_join_op %kw_join select_from_subject select_table_join_clause:? {% x => track(x, {
Loading