Skip to content

Commit

Permalink
Support parsing and tagging lateral joins (#149)
Browse files Browse the repository at this point in the history
Postgres has a LATERAL keyword that modifies one of the `from_item`s in a selection. It doesn't have to be an explicit join statement but is most often used as such. This adds support for parsing the LATERAL keyword in front of each from_item type that supports it.

See https://www.postgresql.org/docs/current/sql-select.html

Fixes #122
  • Loading branch information
airhorns authored Jan 8, 2024
1 parent 7564142 commit 727463b
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 9 deletions.
3 changes: 3 additions & 0 deletions src/syntax/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ export type From = FromTable
export interface FromCall extends ExprCall, PGNode {
alias?: TableAliasName;
join?: JoinClause | nil;
lateral?: true;
withOrdinality?: boolean;
};

Expand All @@ -716,13 +717,15 @@ export interface QNameMapped extends QNameAliased {
export interface FromTable extends PGNode {
type: 'table',
name: QNameMapped;
lateral?: true;
join?: JoinClause | nil;
}

export interface FromStatement extends PGNode {
type: 'statement';
statement: SelectStatement;
alias: string;
lateral?: true;
columnNames?: Name[] | nil;
db?: null | nil;
join?: JoinClause | nil;
Expand Down
20 changes: 11 additions & 9 deletions src/syntax/select.ne
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,11 @@ stb_table -> table_ref stb_opts:? {% x => {


# Selects on subselects MUST have an alias
stb_statement -> selection_paren stb_opts {% x => track(x, {
stb_statement -> %kw_lateral:? selection_paren stb_opts {% x => track(x, {
type: 'statement',
statement: unwrap(x[0]),
...x[1],
statement: unwrap(x[1]),
...x[0] && { lateral: true },
...x[2],
}) %}


Expand All @@ -94,16 +95,18 @@ select_values -> kw_values insert_values {% x => track(x, {
}) %}


stb_call -> expr_function_call kw_withordinality:? stb_call_alias:? {% x => {
const withOrdinality = x[1];
const alias = x[2];
stb_call -> %kw_lateral:? expr_function_call kw_withordinality:? stb_call_alias:? {% x => {
const lateral = x[0];
const withOrdinality = x[2];
const alias = x[3];

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

return track(x, {
...x[0],
...x[1],
...lateral && { lateral: true },
... withOrdinality && { withOrdinality: true },
alias: alias ? asNameWithColumns(alias[0], alias[1]) : undefined,
});
Expand Down Expand Up @@ -140,7 +143,6 @@ select_join_op
| (%kw_full %kw_outer:? {% x => box(x, 'FULL JOIN') %})



# SELECT x,y as YY,z
select_what -> %kw_select select_distinct:? select_expr_list_aliased:? {% x => track(x, {
columns: x[2],
Expand Down
117 changes: 117 additions & 0 deletions src/syntax/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,123 @@ describe('Select statements', () => {
]
});

checkSelect(`
select * from test
inner join lateral (
select * from test2
where test2.foo = test1.bar
) test2_inner on true
`, {
type: 'select',
columns: [{ expr: star }],
from: [tbl('test'),
{
alias: "test2_inner",
join: {
on: {
type: "boolean",
value: true
},
type: "INNER JOIN",
},
statement: {
columns: [
{
expr: {
name: "*",
type: "ref"
}
}
],
from: [
{
name: {
name: "test2"
},
type: "table"
}
],
type: "select",
where: {
left: {
name: "foo",
table: {
name: "test2"
},
type: "ref"
},
op: "=",
right: {
name: "bar",
table: {
name: "test1"
},
type: "ref",
},
type: "binary"
}
},
type: "statement",
lateral: true,
}
]
});

checkSelect(`
SELECT m.name AS mname, pname
FROM manufacturers m, LATERAL get_product_names(m.id) pname;
`, {
"columns": [
{
"expr": {
"type": "ref",
"table": {
"name": "m"
},
"name": "name"
},
"alias": {
"name": "mname"
}
},
{
"expr": {
"type": "ref",
"name": "pname"
}
}
],
"from": [
{
"type": "table",
"name": {
"name": "manufacturers",
"alias": "m"
}
},
{
"type": "call",
"function": {
"name": "get_product_names"
},
"args": [
{
"type": "ref",
"table": {
"name": "m"
},
"name": "id"
}
],
"lateral": true,
"alias": {
"name": "pname"
}
}
],
"type": "select"
});

checkSelect(['select current_schema()'], {
type: 'select',
columns: [{
Expand Down
6 changes: 6 additions & 0 deletions src/to-sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,9 @@ const visitor = astVisitor<IAstFullVisitor>(m => ({
fromCall: s => {

join(m, s.join, () => {
if (s.lateral) {
ret.push("LATERAL ")
}
m.call(s);
if (s.withOrdinality) {
ret.push(' WITH ORDINALITY')
Expand Down Expand Up @@ -1074,6 +1077,9 @@ const visitor = astVisitor<IAstFullVisitor>(m => ({

// todo: use 's.db' if defined
join(m, s.join, () => {
if (s.lateral) {
ret.push("LATERAL ")
}
ret.push('(');
m.select(s.statement);
ret.push(') ');
Expand Down

0 comments on commit 727463b

Please sign in to comment.