Skip to content
Open
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
18 changes: 18 additions & 0 deletions src/ast-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface IAstPartialMapper {
drop?: (val: a.DropStatement) => a.Statement | nil
show?: (val: a.ShowStatement) => a.Statement | nil
createTable?: (val: a.CreateTableStatement) => a.Statement | nil
createTableAs?: (val: a.CreateTableAsStatement) => a.Statement | nil
truncateTable?: (val: a.TruncateTableStatement) => a.Statement | nil
createExtension?: (val: a.CreateExtensionStatement) => a.Statement | nil
set?: (st: a.SetStatement) => a.SetStatement | nil
Expand Down Expand Up @@ -229,6 +230,8 @@ export class AstDefaultMapper implements IAstMapper {
return this.createIndex(val);
case 'create table':
return this.createTable(val);
case 'create table as':
return this.createTableAs(val);
case 'truncate table':
return this.truncateTable(val);
case 'delete':
Expand Down Expand Up @@ -585,6 +588,21 @@ export class AstDefaultMapper implements IAstMapper {
});
}

createTableAs(val: a.CreateTableAsStatement): a.Statement | nil {
const query = this.select(val.query);
if (!query) {
return null;
}
const ref = this.tableRef(val.name);
if (!ref) {
return null;
}
return assignChanged(val, {
query,
name: ref,
});
}

likeTable(col: a.CreateColumnsLikeTable): a.CreateColumnDef | a.CreateColumnsLikeTable | nil {
const like = this.tableRef(col.like);
if (!like) {
Expand Down
13 changes: 13 additions & 0 deletions src/syntax/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export function locationOf(node: PGNode): NodeLocation {

export type Statement = SelectStatement
| CreateTableStatement
| CreateTableAsStatement
| CreateSequenceStatement
| CreateIndexStatement
| CreateExtensionStatement
Expand Down Expand Up @@ -505,6 +506,18 @@ export interface CreateTableStatement extends PGNode {
inherits?: QName[];
}

export interface CreateTableAsStatement extends PGNode {
type: 'create table as';
name: QName;
temporary?: boolean;
unlogged?: boolean;
locality?: 'global' | 'local';
ifNotExists?: true;
columnNames?: Name[];
query: SelectStatement;
withData?: boolean;
}

export interface CreateColumnsLikeTable extends PGNode {
kind: 'like table';
like: QName;
Expand Down
23 changes: 23 additions & 0 deletions src/syntax/create-table.ne
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,29 @@ createtable_statement -> %kw_create
} %}


# https://www.postgresql.org/docs/current/sql-createtableas.html
createtableas_statement -> %kw_create
createtable_modifiers:?
%kw_table
kw_ifnotexists:?
qname
(lparen array_of[ident] rparen {% get(1) %}):?
%kw_as
selection
(%kw_with kw_no:? kw_data):?
{% x => {

return track(x, {
type: 'create table as',
... !!x[3] ? { ifNotExists: true } : {},
name: x[4],
... x[5] && { columnNames: x[5].map(asName) },
...unwrap(x[1]),
query: x[7],
... x[8] && { withData: toStr(x[8][1]) !== 'no' },
});
} %}

createtable_modifiers
-> kw_unlogged {% x => x[0] ? { unlogged: true } : {} %}
| m_locglob
Expand Down
91 changes: 90 additions & 1 deletion src/syntax/create-table.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'mocha';
import 'chai';
import { checkCreateTable, checkCreateTableLoc, checkInvalid } from './spec-utils';
import { checkCreateTable, checkCreateTableLoc, checkCreateTableAs, checkCreateTableAsLoc, checkInvalid } from './spec-utils';


describe('Create table', () => {
Expand Down Expand Up @@ -758,4 +758,93 @@ describe('Create table', () => {
},
]
})

checkCreateTableAs(['create table foo as select * from bar', 'CREATE TABLE foo AS SELECT * FROM bar'], {
type: 'create table as',
name: { name: 'foo' },
query: {
type: 'select',
columns: [{ expr: { type: 'ref', name: '*' } }],
from: [{ type: 'table', name: { name: 'bar' } }],
},
});

checkCreateTableAsLoc(['CREATE TABLE foo AS SELECT * FROM bar'], {
_location: { start: 0, end: 37 },
type: 'create table as',
name: {
_location: { start: 13, end: 16 },
name: 'foo',
},
query: {
_location: { start: 20, end: 37 },
type: 'select',
columns: [{
_location: { start: 27, end: 28 },
expr: {
_location: { start: 27, end: 28 },
type: 'ref',
name: '*',
},
}],
from: [{
_location: { start: 34, end: 37 },
type: 'table',
name: {
_location: { start: 34, end: 37 },
name: 'bar',
},
}],
},
});

checkCreateTableAs(['CREATE TABLE public.foo AS SELECT * FROM bar'], {
type: 'create table as',
name: { name: 'foo', schema: 'public' },
query: {
type: 'select',
columns: [{ expr: { type: 'ref', name: '*' } }],
from: [{ type: 'table', name: { name: 'bar' } }],
},
});

// typed column definitions are not valid in a "CREATE TABLE ... AS" column list
// (this also guards against the grammar becoming ambiguous between "CREATE TABLE" and "CREATE TABLE AS")
checkInvalid('CREATE TABLE foo (a text, b text) AS SELECT 1');

checkCreateTableAs(['CREATE TABLE IF NOT EXISTS foo (a, b) AS SELECT x, y FROM bar WITH NO DATA'], {
type: 'create table as',
ifNotExists: true,
name: { name: 'foo' },
columnNames: [{ name: 'a' }, { name: 'b' }],
query: {
type: 'select',
columns: [{ expr: { type: 'ref', name: 'x' } }, { expr: { type: 'ref', name: 'y' } }],
from: [{ type: 'table', name: { name: 'bar' } }],
},
withData: false,
});

checkCreateTableAs(['CREATE TEMPORARY TABLE foo AS SELECT * FROM bar WITH DATA'], {
type: 'create table as',
temporary: true,
name: { name: 'foo' },
query: {
type: 'select',
columns: [{ expr: { type: 'ref', name: '*' } }],
from: [{ type: 'table', name: { name: 'bar' } }],
},
withData: true,
});

// ensure that statement is still parsed as a regular "CREATE TABLE" when there is no "as"
checkCreateTable(['CREATE TABLE foo (a text)'], {
type: 'create table',
name: { name: 'foo' },
columns: [{
kind: 'column',
name: { name: 'a' },
dataType: { name: 'text' },
}],
});
});
1 change: 1 addition & 0 deletions src/syntax/main.ne
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ statement -> statement_noprep | prepare | deallocate
statement_noprep
-> selection
| createtable_statement
| createtableas_statement
| createextension_statement
| createindex_statement
| simplestatements_all
Expand Down
8 changes: 7 additions & 1 deletion src/syntax/spec-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Parser, Grammar } from 'nearley';
import { expect, assert } from 'chai';
import grammar from '../syntax/main.ne';
import { trimNullish } from '../utils';
import { Expr, SelectStatement, CreateTableStatement, CreateIndexStatement, Statement, InsertStatement, UpdateStatement, AlterTableStatement, DeleteStatement, CreateExtensionStatement, CreateSequenceStatement, AlterSequenceStatement, SelectedColumn, Interval, BinaryOperator, ExprBinary, Name, ExprInteger, FromTable, QName, AlterIndexStatement, ExprNumeric } from './ast';
import { Expr, SelectStatement, CreateTableStatement, CreateTableAsStatement, CreateIndexStatement, Statement, InsertStatement, UpdateStatement, AlterTableStatement, DeleteStatement, CreateExtensionStatement, CreateSequenceStatement, AlterSequenceStatement, SelectedColumn, Interval, BinaryOperator, ExprBinary, Name, ExprInteger, FromTable, QName, AlterIndexStatement, ExprNumeric } from './ast';
import { astMapper, IAstMapper } from '../ast-mapper';
import { toSql, IAstToSql } from '../to-sql';
import { parseIntervalLiteral } from '../parser';
Expand All @@ -23,6 +23,12 @@ export function checkCreateTable(value: string | string[], expected: CreateTable
export function checkCreateTableLoc(value: string | string[], expected: CreateTableStatement) {
checkTree(value, expected, (p, m) => m.statement(p), undefined, true);
}
export function checkCreateTableAs(value: string | string[], expected: CreateTableAsStatement) {
checkTree(value, expected, (p, m) => m.statement(p));
}
export function checkCreateTableAsLoc(value: string | string[], expected: CreateTableAsStatement) {
checkTree(value, expected, (p, m) => m.statement(p), undefined, true);
}
export function checkCreateIndex(value: string | string[], expected: CreateIndexStatement) {
checkTree(value, expected, (p, m) => m.statement(p));
}
Expand Down
23 changes: 23 additions & 0 deletions src/to-sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,29 @@ const visitor = astVisitor<IAstFullVisitor>(m => ({
}
},

createTableAs: t => {
ret.push('CREATE ');
if (t.locality) {
ret.push(t.locality.toUpperCase(), ' ');
}
if (t.temporary) {
ret.push('TEMPORARY ');
}
if (t.unlogged) {
ret.push('UNLOGGED ');
}
ret.push(t.ifNotExists ? 'TABLE IF NOT EXISTS ' : 'TABLE ');
m.tableRef(t.name);
if (t.columnNames) {
list(t.columnNames, c => ret.push(name(c)), true);
}
ret.push(' AS ');
m.select(t.query);
if (t.withData !== undefined) {
ret.push(t.withData ? ' WITH DATA' : ' WITH NO DATA');
}
},

likeTable: l => {
ret.push(' LIKE ');
m.tableRef(l.like);
Expand Down