mirror of https://github.com/Wilfred/difftastic/
1288 lines
36 KiB
JavaScript
1288 lines
36 KiB
JavaScript
const PREC = {
|
|
primary: 8,
|
|
unary: 7,
|
|
exp: 6,
|
|
multiplicative: 5,
|
|
additive: 4,
|
|
comparative: 3,
|
|
and: 2,
|
|
or: 1,
|
|
};
|
|
const multiplicative_operators = ["*", "/", "%", "<<", ">>", "&"];
|
|
const additive_operators = ["+", "-", "|", "#"];
|
|
const comparative_operators = [
|
|
"<",
|
|
"<=",
|
|
"<>",
|
|
"!=",
|
|
"=",
|
|
">",
|
|
">=",
|
|
"~",
|
|
"!~",
|
|
"~*",
|
|
"!~*",
|
|
];
|
|
|
|
// Generate case insentitive match for SQL keyword
|
|
// In case of multiple word keyword provide a seq matcher
|
|
function kw(keyword) {
|
|
if (keyword.toUpperCase() != keyword) {
|
|
throw new Error(`Expected upper case keyword got ${keyword}`);
|
|
}
|
|
const words = keyword.split(" ");
|
|
const regExps = words.map(createCaseInsensitiveRegex);
|
|
|
|
return regExps.length == 1
|
|
? alias(regExps[0], keyword)
|
|
: alias(seq(...regExps), keyword.replace(/ /g, "_"));
|
|
}
|
|
|
|
function createOrReplace(item) {
|
|
if (item.toUpperCase() != item) {
|
|
throw new Error(`Expected upper case item got ${item}`);
|
|
}
|
|
return alias(
|
|
seq(
|
|
createCaseInsensitiveRegex("CREATE"),
|
|
field("replace", optional(createCaseInsensitiveRegex("OR REPLACE"))),
|
|
createCaseInsensitiveRegex(item),
|
|
),
|
|
`CREATE_OR_REPLACE_${item}`,
|
|
);
|
|
}
|
|
|
|
function createCaseInsensitiveRegex(word) {
|
|
return new RegExp(
|
|
word
|
|
.split("")
|
|
.map(letter => `[${letter.toLowerCase()}${letter.toUpperCase()}]`)
|
|
.join(""),
|
|
);
|
|
}
|
|
|
|
function kv(key, value) {
|
|
return alias(
|
|
value === null
|
|
? createCaseInsensitiveRegex(key)
|
|
: seq(createCaseInsensitiveRegex(key), "=", field("value", value)),
|
|
key.toLowerCase(),
|
|
);
|
|
}
|
|
|
|
module.exports = grammar({
|
|
name: "sql",
|
|
extras: $ => [$.comment, /[\s\f\uFEFF\u2060\u200B]|\\\r?\n/],
|
|
externals: $ => [
|
|
$._dollar_quoted_string_tag,
|
|
$._dollar_quoted_string_content,
|
|
$._dollar_quoted_string_end_tag,
|
|
],
|
|
|
|
rules: {
|
|
source_file: $ => repeat($._statement),
|
|
|
|
_statement: $ =>
|
|
seq(
|
|
choice(
|
|
$.pg_command,
|
|
$.begin_statement,
|
|
$.commit_statement,
|
|
$.rollback_statement,
|
|
$.select_statement,
|
|
$.update_statement,
|
|
$.insert_statement,
|
|
$.delete_statement,
|
|
$.set_statement,
|
|
$.grant_statement,
|
|
$.drop_statement,
|
|
$.create_statement,
|
|
$.alter_statement,
|
|
$.truncate_statement,
|
|
$.create_type_statement,
|
|
$.create_domain_statement,
|
|
$.create_index_statement,
|
|
$.create_table_statement,
|
|
$.create_schema_statement,
|
|
$.create_role_statement,
|
|
$.create_extension_statement,
|
|
$.create_trigger_statement,
|
|
$.create_function_statement,
|
|
$.comment_statement,
|
|
$.create_view_statement,
|
|
$.create_materialized_view_statement,
|
|
),
|
|
optional(";"),
|
|
),
|
|
|
|
_simple_statement: $ =>
|
|
prec.right(
|
|
seq(
|
|
choice(
|
|
$.pg_command,
|
|
$.select_statement,
|
|
$.update_statement,
|
|
$.insert_statement,
|
|
$.delete_statement,
|
|
$.set_statement,
|
|
$.grant_statement,
|
|
$.drop_statement,
|
|
$.create_statement,
|
|
$.alter_statement,
|
|
$.create_type_statement,
|
|
$.create_domain_statement,
|
|
$.create_table_statement,
|
|
$.create_index_statement,
|
|
$.create_schema_statement,
|
|
$.create_role_statement,
|
|
$.create_extension_statement,
|
|
$.return_statement,
|
|
$.declare_statement,
|
|
$.create_view_statement,
|
|
$.create_materialized_view_statement,
|
|
),
|
|
optional(";"),
|
|
),
|
|
),
|
|
|
|
with_clause: $ =>
|
|
seq(kw("WITH"), optional(kw("RECURSIVE")), commaSep1($.cte)),
|
|
|
|
cte: $ =>
|
|
seq(
|
|
$.identifier,
|
|
kw("AS"),
|
|
optional(seq(optional(kw("NOT")), kw("MATERIALIZED"))),
|
|
"(",
|
|
choice(
|
|
$.select_statement,
|
|
$.delete_statement,
|
|
$.insert_statement,
|
|
$.update_statement,
|
|
),
|
|
")",
|
|
),
|
|
|
|
select_statement: $ => seq(optional($.with_clause), $._select_statement),
|
|
insert_statement: $ => seq(optional($.with_clause), $._insert_statement),
|
|
update_statement: $ => seq(optional($.with_clause), $._update_statement),
|
|
delete_statement: $ => seq(optional($.with_clause), $._delete_statement),
|
|
|
|
truncate_statement: $ =>
|
|
seq(
|
|
kw("TRUNCATE"),
|
|
optional(kw("TABLE")),
|
|
optional(kw("ONLY")),
|
|
commaSep1($._identifier),
|
|
),
|
|
|
|
comment_statement: $ =>
|
|
seq(
|
|
kw("COMMENT ON"),
|
|
choice(
|
|
seq(
|
|
choice(kw("COLUMN"), kw("EXTENSION"), kw("SCHEMA"), kw("TABLE")),
|
|
$._identifier,
|
|
),
|
|
seq(kw("FUNCTION"), $.function_call),
|
|
),
|
|
kw("IS"),
|
|
choice($.string, $.NULL),
|
|
),
|
|
|
|
begin_statement: $ =>
|
|
seq(kw("BEGIN"), optional(choice(kw("WORK"), kw("TRANSACTION")))),
|
|
commit_statement: $ =>
|
|
seq(kw("COMMIT"), optional(choice(kw("WORK"), kw("TRANSACTION")))),
|
|
rollback_statement: $ =>
|
|
seq(kw("ROLLBACK"), optional(choice(kw("WORK"), kw("TRANSACTION")))),
|
|
|
|
create_statement: $ =>
|
|
seq(
|
|
kw("CREATE"),
|
|
optional(choice(kw("TEMP"), kw("TEMPORARY"))),
|
|
choice(alias($.sequence, $.create_sequence)),
|
|
),
|
|
alter_statement: $ =>
|
|
seq(
|
|
kw("ALTER"),
|
|
choice(
|
|
alias($.sequence, $.alter_sequence),
|
|
$.alter_table,
|
|
alias($.alter_schema, $.schema),
|
|
),
|
|
),
|
|
alter_table: $ =>
|
|
seq(
|
|
kw("TABLE"),
|
|
optional(kw("IF EXISTS")),
|
|
optional(kw("ONLY")),
|
|
$._identifier,
|
|
choice($.alter_table_action, $.alter_table_rename_column),
|
|
),
|
|
alter_schema_rename_action: $ => seq(kw("RENAME TO"), $._identifier),
|
|
alter_owner_action: $ =>
|
|
seq(
|
|
kw("OWNER TO"),
|
|
choice($._identifier, "CURRENT_USER", "CURRENT_ROLE", "SESSION_USER"),
|
|
),
|
|
alter_schema: $ =>
|
|
seq(
|
|
kw("SCHEMA"),
|
|
$._identifier,
|
|
choice(
|
|
alias($.alter_schema_rename_action, $.rename),
|
|
alias($.alter_owner_action, $.alter_owner),
|
|
),
|
|
),
|
|
alter_table_action_alter_column: $ =>
|
|
seq(
|
|
kw("ALTER COLUMN"),
|
|
$._identifier,
|
|
kw("SET DEFAULT"),
|
|
$._column_default_expression,
|
|
),
|
|
alter_table_action_add: $ =>
|
|
seq(
|
|
kw("ADD"),
|
|
choice(seq(kw("COLUMN"), $.table_column), $._table_constraint),
|
|
),
|
|
alter_table_action_set: $ => seq(kw("SET"), $._expression),
|
|
alter_table_rename_column: $ =>
|
|
seq(
|
|
kw("RENAME"),
|
|
optional(kw("COLUMN")),
|
|
$._identifier,
|
|
kw("TO"),
|
|
$._identifier,
|
|
),
|
|
alter_table_action: $ =>
|
|
choice(
|
|
$.alter_table_action_add,
|
|
$.alter_table_action_alter_column,
|
|
$.alter_table_action_set,
|
|
alias($.alter_owner_action, $.alter_owner),
|
|
),
|
|
sequence: $ =>
|
|
prec.right(
|
|
seq(
|
|
kw("SEQUENCE"),
|
|
optional(seq(kw("IF"), optional(kw("NOT")), kw("EXISTS"))),
|
|
$._identifier,
|
|
optional(seq(kw("AS"), $.type)),
|
|
repeat(
|
|
choice(
|
|
seq(kw("START"), kw("WITH"), $.number),
|
|
seq(kw("INCREMENT"), optional(kw("BY")), $.number),
|
|
seq(kw("NO"), choice(kw("MINVALUE"), kw("MAXVALUE"))),
|
|
seq(kw("CACHE"), $.number),
|
|
seq(kw("OWNED BY"), choice($._identifier)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
pg_command: $ => seq(/\\[a-zA-Z]+/, /.*/),
|
|
|
|
_compound_statement: $ =>
|
|
prec.right(
|
|
seq(
|
|
optional(seq(field("begin_label", $.identifier), ":")),
|
|
kw("BEGIN"),
|
|
optional(kw("ATOMIC")),
|
|
repeat1($._simple_statement),
|
|
kw("END"),
|
|
optional(field("end_label", $.identifier)),
|
|
optional(";"),
|
|
),
|
|
),
|
|
return_statement: $ =>
|
|
seq(kw("RETURN"), choice($._expression, $.select_statement)),
|
|
declare_statement: $ =>
|
|
seq(kw("DECLARE"), $.identifier, $._type, optional($.default_clause)),
|
|
|
|
create_function_statement: $ =>
|
|
prec.right(
|
|
seq(
|
|
choice(createOrReplace("FUNCTION"), createOrReplace("PROCEDURE")),
|
|
$._identifier,
|
|
$.create_function_parameters,
|
|
optional(seq(kw("RETURNS"), $._create_function_return_type)),
|
|
repeat(
|
|
choice(
|
|
$._function_language,
|
|
seq(kw("TRANSFORM FOR TYPE"), commaSep1($.identifier)),
|
|
kw("WINDOW"),
|
|
seq(optional(kw("NOT")), kw("LEAKPROOF")),
|
|
seq(kw("COST"), $.number),
|
|
seq(kw("ROWS"), $.number),
|
|
seq(kw("SUPPORT"), $.identifier),
|
|
$.external_hint,
|
|
$.optimizer_hint,
|
|
$.parallel_hint,
|
|
$.null_hint,
|
|
$.deterministic_hint,
|
|
$.sql_hint,
|
|
$.sql_security_hint,
|
|
$.function_body,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
external_hint: $ =>
|
|
choice(
|
|
seq(optional(kw("EXTERNAL")), kw("SECURITY INVOKER")),
|
|
seq(optional(kw("EXTERNAL")), kw("SECURITY DEFINER")),
|
|
),
|
|
optimizer_hint: $ => choice(kw("VOLATILE"), kw("IMMUTABLE"), kw("STABLE")),
|
|
parallel_hint: $ =>
|
|
seq(kw("PARALLEL"), choice(kw("SAFE"), kw("UNSAFE"), kw("RESTRICTED"))),
|
|
null_hint: $ =>
|
|
choice(
|
|
kw("CALLED ON NULL INPUT"),
|
|
kw("RETURNS NULL ON NULL INPUT"),
|
|
kw("STRICT"),
|
|
),
|
|
// MySQL hints
|
|
deterministic_hint: $ => seq(optional(kw("NOT")), kw("DETERMINISTIC")),
|
|
sql_hint: $ =>
|
|
choice(
|
|
kw("CONTAINS SQL"),
|
|
kw("NO SQL"),
|
|
kw("READS SQL DATA"),
|
|
kw("MODIFIES SQL DATA"),
|
|
),
|
|
sql_security_hint: $ =>
|
|
seq(kw("SQL SECURITY"), choice(kw("DEFINER"), kw("INVOKER"))),
|
|
|
|
_function_language: $ =>
|
|
seq(
|
|
kw("LANGUAGE"),
|
|
alias(choice(/[a-zA-Z]+/, /'[a-zA-Z]+'/), $.language),
|
|
),
|
|
_create_function_return_type: $ =>
|
|
prec.right(choice($.setof, $._type, $.constrained_type)),
|
|
setof: $ =>
|
|
prec.right(seq(kw("SETOF"), choice($._type, $.constrained_type))),
|
|
constrained_type: $ => seq($._type, $.null_constraint),
|
|
create_function_parameter: $ =>
|
|
seq(
|
|
field(
|
|
"argmode",
|
|
optional(choice(kw("IN"), kw("OUT"), kw("INOUT"), kw("VARIADIC"))),
|
|
),
|
|
optional($.identifier),
|
|
choice($._type, $.constrained_type),
|
|
optional(seq("=", alias($._expression, $.default))),
|
|
),
|
|
create_function_parameters: $ =>
|
|
seq("(", optional(commaSep1($.create_function_parameter)), ")"),
|
|
|
|
function_body: $ =>
|
|
choice(
|
|
$._simple_statement,
|
|
$._compound_statement,
|
|
seq(kw("AS"), field("script", $.string)),
|
|
seq(
|
|
kw("AS"),
|
|
field("obj_file", $.string),
|
|
field("link_symbol", $.string),
|
|
),
|
|
),
|
|
|
|
create_trigger_statement: $ =>
|
|
seq(
|
|
kw("CREATE"),
|
|
optional(kw("OR REPLACE")),
|
|
optional(kw("CONSTRAINT")),
|
|
kw("TRIGGER"),
|
|
optional(kw("IF NOT EXISTS")),
|
|
field("name", $.identifier),
|
|
$.trigger_time,
|
|
$.trigger_event,
|
|
kw("ON"),
|
|
field("on_table", $._identifier),
|
|
optional($.trigger_reference),
|
|
optional($.trigger_preferencing),
|
|
optional(
|
|
seq(
|
|
kw("FOR"),
|
|
optional(kw("EACH")),
|
|
choice(kw("ROW"), kw("STATEMENT")),
|
|
),
|
|
),
|
|
optional($.trigger_condition),
|
|
optional($.trigger_order),
|
|
$.trigger_body,
|
|
),
|
|
trigger_reference: $ => seq(kw("FROM"), $._identifier),
|
|
trigger_preferencing: $ =>
|
|
seq(
|
|
kw("REFERENCING"),
|
|
repeat1(
|
|
seq(
|
|
choice(kw("NEW"), kw("OLD")),
|
|
kw("TABLE"),
|
|
optional(kw("AS")),
|
|
$.identifier,
|
|
),
|
|
),
|
|
),
|
|
trigger_time: $ => choice(kw("BEFORE"), kw("AFTER"), kw("INSTEAD OF")),
|
|
trigger_event: $ =>
|
|
choice(
|
|
kw("INSERT"),
|
|
kw("DELETE"),
|
|
kw("TRUNCATE"),
|
|
seq(kw("UPDATE"), optional(seq(kw("OF"), repeat1($._identifier)))),
|
|
),
|
|
// PostgreSQL trigger condition
|
|
trigger_condition: $ => seq(kw("WHEN"), $._expression),
|
|
// MySQL trigger order
|
|
trigger_order: $ =>
|
|
seq(choice(kw("FOLLOWS"), kw("PRECEDES")), $._identifier),
|
|
trigger_body: $ =>
|
|
choice(
|
|
// PostgreSQL style trigger body
|
|
seq(
|
|
kw("EXECUTE"),
|
|
choice(kw("FUNCTION"), kw("PROCEDURE")),
|
|
seq(
|
|
field("function", $.identifier),
|
|
"(",
|
|
optional(field("arguments", commaSep1($.string))),
|
|
")",
|
|
),
|
|
),
|
|
// MySQL style trigger body
|
|
$._simple_statement,
|
|
$._compound_statement,
|
|
),
|
|
|
|
create_extension_statement: $ =>
|
|
prec.right(
|
|
seq(
|
|
kw("CREATE EXTENSION"),
|
|
optional(kw("IF NOT EXISTS")),
|
|
$._identifier,
|
|
optional(kw("WITH")),
|
|
repeat(
|
|
choice(
|
|
seq(kw("SCHEMA"), alias($._identifier, $.schema)),
|
|
seq(kw("VERSION"), alias($.string, $.version)),
|
|
kw("CASCADE"),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
create_role_statement: $ =>
|
|
prec.right(
|
|
seq(
|
|
kw("CREATE ROLE"),
|
|
$._identifier,
|
|
optional(kw("WITH")),
|
|
optional($._identifier),
|
|
),
|
|
),
|
|
create_schema_statement: $ =>
|
|
seq(kw("CREATE SCHEMA"), optional(kw("IF NOT EXISTS")), $._identifier),
|
|
drop_statement: $ =>
|
|
seq(
|
|
kw("DROP"),
|
|
field(
|
|
"kind",
|
|
choice(
|
|
kw("TABLE"),
|
|
kw("VIEW"),
|
|
kw("INDEX"),
|
|
kw("EXTENSION"),
|
|
kw("TABLESPACE"),
|
|
kw("MATERIALIZED VIEW"),
|
|
),
|
|
),
|
|
optional(kw("CONCURRENTLY")),
|
|
optional(kw("IF EXISTS")),
|
|
field("target", commaSep1($._identifier)),
|
|
optional(choice(kw("CASCADE"), kw("RESTRICT"))),
|
|
),
|
|
set_statement: $ =>
|
|
seq(
|
|
kw("SET"),
|
|
field("scope", optional(choice(kw("SESSION"), kw("LOCAL")))),
|
|
$.identifier,
|
|
choice("=", kw("TO")),
|
|
choice($._expression, kw("DEFAULT")),
|
|
),
|
|
grant_statement: $ =>
|
|
prec.right(
|
|
seq(
|
|
kw("GRANT"),
|
|
choice(
|
|
seq(kw("ALL"), optional(kw("PRIVILEGES"))),
|
|
repeat(
|
|
choice(
|
|
kw("SELECT"),
|
|
kw("INSERT"),
|
|
kw("UPDATE"),
|
|
kw("DELETE"),
|
|
kw("TRUNCATE"),
|
|
kw("REFERENCES"),
|
|
kw("TRIGGER"),
|
|
kw("USAGE"),
|
|
),
|
|
),
|
|
),
|
|
kw("ON"),
|
|
field(
|
|
"type",
|
|
optional(
|
|
choice(kw("SCHEMA"), kw("DATABASE"), kw("SEQUENCE"), kw("TABLE")),
|
|
),
|
|
),
|
|
$._identifier,
|
|
kw("TO"),
|
|
choice(seq(optional(kw("GROUP")), $.identifier), kw("PUBLIC")),
|
|
optional(kw("WITH GRANT OPTION")),
|
|
),
|
|
),
|
|
create_domain_statement: $ =>
|
|
prec.right(
|
|
seq(
|
|
kw("CREATE DOMAIN"),
|
|
$._identifier,
|
|
optional(
|
|
seq(
|
|
kw("AS"),
|
|
$._type,
|
|
repeat(choice($.null_constraint, $.check_constraint)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
create_type_statement: $ =>
|
|
prec.right(
|
|
seq(
|
|
kw("CREATE TYPE"),
|
|
field("type_name", $._identifier),
|
|
optional(
|
|
choice(
|
|
$.type_spec_composite,
|
|
$.type_spec_enum,
|
|
$.type_spec_range,
|
|
$.type_spec_base,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
type_spec_composite: $ =>
|
|
seq(
|
|
kw("AS"),
|
|
"(",
|
|
commaSep1(seq($.identifier, choice($._type, $.constrained_type))),
|
|
")",
|
|
),
|
|
type_spec_enum: $ =>
|
|
seq(kw("AS"), kw("ENUM"), "(", commaSep($.string), ")"),
|
|
type_spec_range: $ =>
|
|
seq(
|
|
kw("AS"),
|
|
kw("RANGE"),
|
|
"(",
|
|
commaSep(
|
|
choice(
|
|
...[
|
|
"SUBTYPE",
|
|
"SUBTYPE_OPCLASS",
|
|
"COLLATION",
|
|
"CANONICAL",
|
|
"SUBTYPE_DIFF",
|
|
"MULTIRANGE_TYPE_NAME",
|
|
].map(k => kv(k, $._identifier)),
|
|
),
|
|
),
|
|
")",
|
|
),
|
|
type_spec_base: $ =>
|
|
seq(
|
|
"(",
|
|
commaSep(
|
|
choice(
|
|
...[
|
|
["INPUT", $._identifier],
|
|
["OUTPUT", $._identifier],
|
|
["RECEIVE", $._identifier],
|
|
["SEND", $._identifier],
|
|
["TYPMOD_IN", $._identifier],
|
|
["TYPMOD_OUT", $._identifier],
|
|
["ANALYZE", $._identifier],
|
|
["SUBSCRIPT", $._identifier],
|
|
["INTERNALLENGTH", choice($.number, kw("VARIABLE"))],
|
|
["PASSEDBYVALUE", null],
|
|
["ALIGNMENT", $._identifier],
|
|
["STORAGE", $._identifier],
|
|
["LIKE", $._identifier],
|
|
["CATEGORY", $.string],
|
|
["PREFERRED", $.string],
|
|
["DEFAULT", $._expression],
|
|
["ELEMENT", $._identifier],
|
|
["DELIMITER", $.string],
|
|
["COLLATABLE", $._identifier],
|
|
].map(([k, v]) => kv(k, v)),
|
|
),
|
|
),
|
|
")",
|
|
),
|
|
|
|
create_index_with_clause: $ =>
|
|
seq(
|
|
kw("WITH"),
|
|
"(",
|
|
field("storage_parameter", $.identifier),
|
|
"=",
|
|
// TODO: Option value be special set e.g. accepting enum with ON, OFF, VALUE
|
|
$._expression,
|
|
")",
|
|
),
|
|
create_index_include_clause: $ =>
|
|
seq(kw("INCLUDE"), "(", commaSep1($.identifier), ")"),
|
|
create_index_statement: $ =>
|
|
prec.right(
|
|
seq(
|
|
kw("CREATE"),
|
|
optional($.unique_constraint),
|
|
kw("INDEX"),
|
|
optional(kw("CONCURRENTLY")),
|
|
optional(
|
|
seq(optional(kw("IF NOT EXISTS")), field("name", $.identifier)),
|
|
),
|
|
kw("ON"),
|
|
optional(kw("ONLY")),
|
|
field("table_name", $._identifier),
|
|
optional($.using_clause),
|
|
$.index_table_parameters,
|
|
optional($.create_index_include_clause),
|
|
optional($.create_index_with_clause),
|
|
optional($.where_clause),
|
|
),
|
|
),
|
|
table_column: $ =>
|
|
prec.right(
|
|
seq(
|
|
field("name", $._identifier),
|
|
field("type", $._type),
|
|
repeat(
|
|
choice(
|
|
$.default_clause,
|
|
$.primary_key_constraint,
|
|
$.check_constraint,
|
|
$.references_constraint,
|
|
$.unique_constraint,
|
|
$.null_constraint,
|
|
$.named_constraint,
|
|
$.direction_constraint,
|
|
$.auto_increment_constraint,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
auto_increment_constraint: _ => kw("AUTO_INCREMENT"),
|
|
direction_constraint: _ => choice(kw("ASC"), kw("DESC")),
|
|
named_constraint: $ => seq("CONSTRAINT", $.identifier),
|
|
_column_default_expression: $ =>
|
|
choice(
|
|
$._parenthesized_expression,
|
|
$.string,
|
|
$.number,
|
|
$.identifier,
|
|
$.function_call,
|
|
),
|
|
default_clause: $ =>
|
|
seq(
|
|
kw("DEFAULT"),
|
|
// TODO: this should be specific variable-free expression https://www.postgresql.org/docs/9.1/sql-createtable.html
|
|
// TODO: simple expression to use for check and default
|
|
choice($._column_default_expression, $.type_cast),
|
|
),
|
|
table_parameters: $ =>
|
|
seq(
|
|
"(",
|
|
optional(commaSep1(choice($.table_column, $._table_constraint))),
|
|
")",
|
|
),
|
|
mode: $ => choice(kw("NOT DEFERRABLE"), kw("DEFERRABLE")),
|
|
initial_mode: $ =>
|
|
seq(kw("INITIALLY"), choice(kw("DEFERRED"), kw("IMMEDIATE"))),
|
|
_table_constraint: $ =>
|
|
prec.right(
|
|
seq(
|
|
optional(seq(kw("CONSTRAINT"), field("name", $._identifier))),
|
|
choice(
|
|
alias($.table_constraint_foreign_key, $.foreign_key),
|
|
alias($.table_constraint_unique, $.unique),
|
|
alias($.table_constraint_primary_key, $.primary_key),
|
|
alias($.table_constraint_check, $.check),
|
|
alias($.table_constraint_exclude, $.exclude),
|
|
),
|
|
optional($.mode),
|
|
optional($.initial_mode),
|
|
),
|
|
),
|
|
table_constraint_check: $ => seq(kw("CHECK"), $._expression),
|
|
op_class: $ => $._identifier,
|
|
exclude_entry: $ =>
|
|
seq(
|
|
$._identifier,
|
|
optional($.op_class),
|
|
optional(seq(kw("WITH"), $.binary_operator)),
|
|
),
|
|
table_constraint_exclude: $ =>
|
|
seq(
|
|
kw("EXCLUDE"),
|
|
optional(seq(kw("USING"), $._identifier)),
|
|
"(",
|
|
commaSep1($.exclude_entry),
|
|
")",
|
|
),
|
|
table_constraint_foreign_key: $ =>
|
|
seq(
|
|
kw("FOREIGN KEY"),
|
|
"(",
|
|
commaSep1($.identifier),
|
|
")",
|
|
$.references_constraint,
|
|
),
|
|
table_constraint_unique: $ =>
|
|
seq(kw("UNIQUE"), "(", commaSep1($._identifier), ")"),
|
|
table_constraint_primary_key: $ =>
|
|
seq(kw("PRIMARY KEY"), "(", commaSep1($._identifier), ")"),
|
|
primary_key_constraint: $ => kw("PRIMARY KEY"),
|
|
create_table_statement: $ =>
|
|
seq(
|
|
kw("CREATE"),
|
|
optional(choice(kw("TEMPORARY"), kw("TEMP"))),
|
|
kw("TABLE"),
|
|
optional(kw("IF NOT EXISTS")),
|
|
$._identifier,
|
|
choice(seq(kw("AS"), $.select_statement), $.table_parameters),
|
|
optional(kw("WITHOUT OIDS")),
|
|
),
|
|
using_clause: $ => seq(kw("USING"), field("method", $.identifier)),
|
|
index_table_parameters: $ =>
|
|
seq(
|
|
"(",
|
|
commaSep1(
|
|
seq(
|
|
choice($._expression, $.ordered_expression),
|
|
optional($.op_class),
|
|
),
|
|
),
|
|
")",
|
|
),
|
|
|
|
create_view_statement: $ =>
|
|
prec.right(
|
|
seq(
|
|
kw("CREATE"),
|
|
optional(createCaseInsensitiveRegex("OR REPLACE")),
|
|
optional(choice(kw("TEMPORARY"), kw("TEMP"))),
|
|
kw("VIEW"),
|
|
$._identifier,
|
|
optional($.view_columns),
|
|
optional($.view_options),
|
|
$.view_body,
|
|
optional($.view_check_option),
|
|
),
|
|
),
|
|
view_columns: $ => seq("(", commaSep1($._identifier), ")"),
|
|
// PostgreSQL currently only support the SECURITY_BARRIER option
|
|
view_option: $ => choice($._identifier, $.assigment_expression),
|
|
view_options: $ => seq(kw("WITH"), "(", commaSep1($.view_option), ")"),
|
|
// MySQL support
|
|
view_check_option: $ =>
|
|
seq(
|
|
kw("WITH"),
|
|
optional(choice(kw("CASCADED"), kw("LOCAL"))),
|
|
kw("CHECK OPTION"),
|
|
),
|
|
view_body: $ =>
|
|
seq(
|
|
kw("AS"),
|
|
choice($.select_statement, $.select_subexpression, $.values_clause),
|
|
),
|
|
|
|
create_materialized_view_statement: $ =>
|
|
prec.right(
|
|
seq(
|
|
kw("CREATE MATERIALIZED VIEW"),
|
|
optional(kw("IF NOT EXISTS")),
|
|
$._identifier,
|
|
optional($.view_columns),
|
|
optional($.using_clause),
|
|
optional($.view_options),
|
|
optional($.tablespace_hint),
|
|
$.view_body,
|
|
optional($.data_hint),
|
|
),
|
|
),
|
|
tablespace_hint: $ => seq(kw("TABLESPACE"), $._identifier),
|
|
data_hint: $ => seq(kw("WITH"), optional(kw("NO")), kw("DATA")),
|
|
|
|
// SELECT
|
|
_select_statement: $ =>
|
|
prec.right(
|
|
seq(
|
|
$.select_clause,
|
|
optional($.from_clause),
|
|
optional(repeat($.join_clause)),
|
|
optional($.where_clause),
|
|
optional($.group_by_clause),
|
|
optional(commaSep1($.window_clause)),
|
|
optional($.order_by_clause),
|
|
optional($.limit_clause),
|
|
optional($.offset_clause),
|
|
),
|
|
),
|
|
|
|
group_by_clause: $ =>
|
|
seq(
|
|
kw("GROUP BY"),
|
|
commaSep1($.group_expression),
|
|
optional($.having_clause),
|
|
),
|
|
having_clause: $ => seq(kw("HAVING"), $._expression),
|
|
group_expression: $ =>
|
|
choice(
|
|
$._expression,
|
|
$.grouping_sets_clause,
|
|
$.rollup_clause,
|
|
$.cube_clause,
|
|
),
|
|
grouping_sets_clause: $ =>
|
|
seq(kw("GROUPING SETS"), "(", commaSep1($.expression_list), ")"),
|
|
rollup_clause: $ =>
|
|
seq(
|
|
kw("ROLLUP"),
|
|
"(",
|
|
commaSep1(choice($._expression, $.expression_list)),
|
|
")",
|
|
),
|
|
cube_clause: $ =>
|
|
seq(
|
|
kw("CUBE"),
|
|
"(",
|
|
commaSep1(choice($._expression, $.expression_list)),
|
|
")",
|
|
),
|
|
expression_list: $ => seq("(", optional(commaSep1($._expression)), ")"),
|
|
order_expression: $ =>
|
|
seq(
|
|
$._expression,
|
|
optional(alias(choice(kw("ASC"), kw("DESC")), $.order)),
|
|
optional(
|
|
alias(
|
|
seq(kw("NULLS"), choice(kw("FIRST"), kw("LAST"))),
|
|
$.nulls_order,
|
|
),
|
|
),
|
|
),
|
|
window_clause: $ =>
|
|
seq(kw("WINDOW"), $.identifier, kw("AS"), $.window_definition),
|
|
order_by_clause: $ => seq(kw("ORDER BY"), commaSep1($.order_expression)),
|
|
limit_clause: $ =>
|
|
seq(
|
|
kw("LIMIT"),
|
|
choice($.number, kw("ALL")),
|
|
optional(seq(",", $.number)), // MySQL LIMIT a, b
|
|
),
|
|
offset_clause: $ =>
|
|
prec.right(
|
|
seq(kw("OFFSET"), $.number, optional(choice(kw("ROW"), kw("ROWS")))),
|
|
),
|
|
fetch_clause: $ =>
|
|
seq(
|
|
kw("FETCH"),
|
|
choice(kw("FIRST"), kw("NEXT")),
|
|
optional($.number),
|
|
choice(kw("ROW"), kw("ROWS")),
|
|
kw("ONLY"),
|
|
),
|
|
where_clause: $ => seq(kw("WHERE"), $._expression),
|
|
_aliased_expression: $ =>
|
|
seq(
|
|
$._expression,
|
|
optional(kw("AS")),
|
|
$.identifier,
|
|
optional(seq("(", commaSep1($.identifier), ")")),
|
|
),
|
|
_aliasable_expression: $ =>
|
|
prec.right(choice($._expression, alias($._aliased_expression, $.alias))),
|
|
select_clause_body: $ =>
|
|
commaSep1(
|
|
seq(
|
|
$._aliasable_expression,
|
|
optional(seq(kw("INTO"), field("into", $.identifier))),
|
|
),
|
|
),
|
|
select_clause: $ =>
|
|
prec.right(seq(kw("SELECT"), optional($.select_clause_body))),
|
|
from_clause: $ => seq(kw("FROM"), commaSep1($._aliasable_expression)),
|
|
join_type: $ =>
|
|
seq(
|
|
choice(
|
|
kw("INNER"),
|
|
seq(
|
|
choice(kw("LEFT"), kw("RIGHT"), kw("FULL")),
|
|
optional(kw("OUTER")),
|
|
),
|
|
),
|
|
),
|
|
join_clause: $ =>
|
|
seq(
|
|
optional($.join_type),
|
|
kw("JOIN"),
|
|
$._aliasable_expression,
|
|
kw("ON"),
|
|
$._expression,
|
|
),
|
|
select_subexpression: $ =>
|
|
prec(1, seq(optional(kw("LATERAL")), "(", $.select_statement, ")")),
|
|
|
|
// UPDATE
|
|
_update_statement: $ =>
|
|
seq(
|
|
kw("UPDATE"),
|
|
$.identifier,
|
|
$.set_clause,
|
|
optional($.from_clause),
|
|
optional($.where_clause),
|
|
),
|
|
set_clause: $ => seq(kw("SET"), $.set_clause_body),
|
|
set_clause_body: $ => seq(commaSep1($.assigment_expression)),
|
|
assigment_expression: $ => seq($._identifier, "=", $._expression),
|
|
|
|
// INSERT
|
|
_insert_statement: $ =>
|
|
seq(
|
|
kw("INSERT"),
|
|
kw("INTO"),
|
|
$._identifier,
|
|
optional(seq("(", commaSep1($._identifier), ")")),
|
|
choice($.values_clause, $.select_statement, $.set_clause),
|
|
),
|
|
values_clause: $ =>
|
|
seq(
|
|
kw("VALUES"),
|
|
commaSep1($.values_clause_item),
|
|
optional($.order_by_clause),
|
|
optional($.limit_clause),
|
|
optional($.offset_clause),
|
|
optional($.fetch_clause),
|
|
),
|
|
values_clause_item: $ => seq("(", commaSep1($._expression), ")"),
|
|
|
|
// DELETE
|
|
// TODO: support returning clauses
|
|
_delete_statement: $ =>
|
|
seq(kw("DELETE"), $.from_clause, optional($.where_clause)),
|
|
|
|
conditional_expression: $ =>
|
|
seq(
|
|
kw("CASE"),
|
|
repeat1(seq(kw("WHEN"), $._expression, kw("THEN"), $._expression)),
|
|
optional(seq(kw("ELSE"), $._expression)),
|
|
kw("END"),
|
|
),
|
|
|
|
in_expression: $ =>
|
|
prec.left(
|
|
PREC.comparative,
|
|
seq($._expression, optional(kw("NOT")), kw("IN"), $.tuple),
|
|
),
|
|
tuple: $ =>
|
|
seq(
|
|
// TODO: maybe collapse with function arguments, but make sure to preserve clarity
|
|
"(",
|
|
field("elements", commaSep1($._expression)),
|
|
")",
|
|
),
|
|
// TODO: named constraints
|
|
references_constraint: $ =>
|
|
seq(
|
|
kw("REFERENCES"),
|
|
$._identifier,
|
|
optional(seq("(", commaSep1($.identifier), ")")),
|
|
// seems like a case for https://github.com/tree-sitter/tree-sitter/issues/130
|
|
repeat(choice($.on_update_action, $.on_delete_action)),
|
|
),
|
|
on_update_action: $ =>
|
|
seq(kw("ON UPDATE"), field("action", $._constraint_action)),
|
|
on_delete_action: $ =>
|
|
seq(kw("ON DELETE"), field("action", $._constraint_action)),
|
|
_constraint_action: $ =>
|
|
choice(kw("RESTRICT"), kw("CASCADE"), kw("SET NULL")),
|
|
unique_constraint: $ => kw("UNIQUE"),
|
|
null_constraint: $ => seq(optional(kw("NOT")), $.NULL),
|
|
check_constraint: $ => seq(kw("CHECK"), $._expression),
|
|
_constraint: $ =>
|
|
seq(
|
|
choice($.null_constraint, $.check_constraint),
|
|
optional($.check_constraint),
|
|
),
|
|
function_call: $ =>
|
|
seq(
|
|
optional(kw("LATERAL")),
|
|
field("function", $._identifier),
|
|
"(",
|
|
optional(field("arguments", $._function_call_arguments)),
|
|
")",
|
|
optional($.within_group_clause),
|
|
optional($.filter_clause),
|
|
optional($.over_clause),
|
|
),
|
|
_function_call_arguments: $ =>
|
|
seq(
|
|
optional(choice(kw("ALL"), kw("DISTINCT"))),
|
|
commaSep1($._expression),
|
|
optional($.order_by_clause),
|
|
),
|
|
within_group_clause: $ =>
|
|
seq(kw("WITHIN GROUP"), "(", $.order_by_clause, ")"),
|
|
filter_clause: $ => seq(kw("FILTER"), "(", $.where_clause, ")"),
|
|
over_clause: $ =>
|
|
seq(kw("OVER"), choice($.identifier, $.window_definition)),
|
|
window_definition: $ =>
|
|
seq(
|
|
"(",
|
|
optional($.partition_by_clause),
|
|
optional($.order_by_clause),
|
|
optional($.frame_clause),
|
|
")",
|
|
),
|
|
partition_by_clause: $ => seq(kw("PARTITION BY"), commaSep1($._expression)),
|
|
frame_clause: $ =>
|
|
choice(
|
|
seq(
|
|
$.frame_kind,
|
|
field("frame_start", $.frame_bound),
|
|
optional($.frame_exclusion),
|
|
),
|
|
seq(
|
|
$.frame_kind,
|
|
kw("BETWEEN"),
|
|
field("frame_start", $.frame_bound),
|
|
kw("AND"),
|
|
field("frame_end", $.frame_bound),
|
|
optional($.frame_exclusion),
|
|
),
|
|
),
|
|
frame_kind: $ => choice(kw("RANGE"), kw("ROWS"), kw("GROUPS")),
|
|
frame_bound: $ =>
|
|
choice(
|
|
kw("UNBOUNDED PRECEDING"),
|
|
seq($._expression, kw("PRECEDING")),
|
|
kw("CURRENT ROW"),
|
|
seq($._expression, kw("FOLLOWING")),
|
|
kw("UNBOUNDED FOLLOWING"),
|
|
),
|
|
frame_exclusion: $ =>
|
|
choice(
|
|
kw("EXCLUDE CURRENT ROW"),
|
|
kw("EXCLUDE GROUP"),
|
|
kw("EXCLUDE TIES"),
|
|
kw("EXCLUDE NO OTHERS"),
|
|
),
|
|
|
|
_parenthesized_expression: $ =>
|
|
prec.left(PREC.unary, seq("(", $._expression, ")")),
|
|
is_expression: $ =>
|
|
prec.left(
|
|
PREC.comparative,
|
|
seq(
|
|
$._expression,
|
|
kw("IS"),
|
|
optional(kw("NOT")),
|
|
choice($.NULL, $.TRUE, $.FALSE, $.distinct_from),
|
|
),
|
|
),
|
|
distinct_from: $ => prec.left(seq(kw("DISTINCT FROM"), $._expression)),
|
|
boolean_expression: $ =>
|
|
choice(
|
|
prec.left(PREC.unary, seq(kw("NOT"), $._expression)),
|
|
prec.left(PREC.and, seq($._expression, kw("AND"), $._expression)),
|
|
prec.left(PREC.or, seq($._expression, kw("OR"), $._expression)),
|
|
),
|
|
at_time_zone_expression: $ =>
|
|
prec.left(
|
|
PREC.primary,
|
|
seq($._expression, kw("AT TIME ZONE"), $._expression),
|
|
),
|
|
NULL: $ => kw("NULL"),
|
|
TRUE: $ => kw("TRUE"),
|
|
FALSE: $ => kw("FALSE"),
|
|
|
|
number: $ => {
|
|
const digits = repeat1(/[0-9]+_?/);
|
|
const exponent = seq(/[eE][\+-]?/, digits);
|
|
|
|
return token(
|
|
seq(
|
|
choice(
|
|
seq(digits, ".", optional(digits), optional(exponent)),
|
|
seq(optional(digits), ".", digits, optional(exponent)),
|
|
seq(digits, exponent),
|
|
seq(digits),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
|
|
_unquoted_identifier: $ => /[a-zA-Z0-9_]+/,
|
|
_quoted_identifier: $ =>
|
|
choice(
|
|
seq("`", field("name", /[^`]*/), "`"), // MySQL style quoting
|
|
seq('"', field("name", /(""|[^"])*/), '"'), // ANSI QUOTES
|
|
),
|
|
identifier: $ => choice($._unquoted_identifier, $._quoted_identifier),
|
|
dotted_name: $ => prec.left(PREC.primary, sep2($.identifier, ".")),
|
|
_identifier: $ => choice($.identifier, $.dotted_name),
|
|
string: $ =>
|
|
choice(
|
|
seq("'", field("content", alias(/(''|[^'])*/, $.content)), "'"),
|
|
seq(
|
|
$._dollar_quoted_string_tag,
|
|
field("content", alias($._dollar_quoted_string_content, $.content)),
|
|
$._dollar_quoted_string_end_tag,
|
|
),
|
|
),
|
|
json_access: $ =>
|
|
seq(
|
|
$._expression,
|
|
choice("->", "->>", "#>", "#>>"),
|
|
choice($.string, $.number),
|
|
),
|
|
ordered_expression: $ =>
|
|
seq($._expression, field("order", choice(kw("ASC"), kw("DESC")))),
|
|
type: $ =>
|
|
prec.right(
|
|
seq(
|
|
$._identifier,
|
|
optional(kw("VARYING")), // CHARACTER/BIT VARYING
|
|
optional(kw("PRECISION")), // DOUBLE PRECISION
|
|
optional(seq("(", commaSep1($.number), ")")),
|
|
optional(seq(choice(kw("WITH"), kw("WITHOUT")), kw("TIME ZONE"))), // TIME/TIMESTAMP (n) WITH/WITHOUT TIME ZONE
|
|
),
|
|
),
|
|
array_type: $ =>
|
|
prec.right(seq($._type, repeat1(seq("[", optional($.number), "]")))),
|
|
_type: $ => choice($.type, $.array_type),
|
|
type_cast: $ =>
|
|
seq(
|
|
// TODO: should be moved to basic expression or something
|
|
choice(
|
|
$._parenthesized_expression,
|
|
$.string,
|
|
$._identifier,
|
|
$.function_call,
|
|
),
|
|
"::",
|
|
field("type", $._type),
|
|
),
|
|
|
|
// http://stackoverflow.com/questions/13014947/regex-to-match-a-c-style-multiline-comment/36328890#36328890
|
|
comment: $ =>
|
|
token(
|
|
choice(seq("--", /.*/), seq("/*", /[^*]*\*+([^/*][^*]*\*+)*/, "/")),
|
|
),
|
|
array_element_access: $ =>
|
|
seq(choice($.identifier, $.argument_reference), "[", $._expression, "]"),
|
|
|
|
unary_expression: $ =>
|
|
prec(
|
|
PREC.unary,
|
|
seq(
|
|
field(
|
|
"operator",
|
|
choice(
|
|
"+",
|
|
"-",
|
|
"!!", // Factorial op (Removed in Postgres >= 14)
|
|
"~", // Bitwise not
|
|
"@", // Absolute value
|
|
"|/", // square root
|
|
"||/", // cube root
|
|
),
|
|
),
|
|
field("operand", $._expression),
|
|
),
|
|
),
|
|
|
|
binary_expression: $ => {
|
|
const table = [
|
|
[PREC.exp, "^"],
|
|
[PREC.multiplicative, choice(...multiplicative_operators)],
|
|
[PREC.additive, choice(...additive_operators)],
|
|
[PREC.comparative, choice(...comparative_operators)],
|
|
];
|
|
|
|
return choice(
|
|
...table.map(([precedence, operator]) =>
|
|
prec.left(
|
|
precedence,
|
|
seq(
|
|
field("left", $._expression),
|
|
field("operator", operator),
|
|
field("right", $._expression),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
|
|
binary_operator: $ => choice("=", "&&", "||"),
|
|
asterisk_expression: $ => choice("*", seq($._identifier, ".*")),
|
|
interval_expression: $ => seq(token(prec(1, kw("INTERVAL"))), $.string),
|
|
argument_reference: $ => seq("$", /\d+/),
|
|
_expression: $ =>
|
|
choice(
|
|
$.interval_expression,
|
|
$.function_call,
|
|
$.string,
|
|
$.json_access,
|
|
$.TRUE,
|
|
$.FALSE,
|
|
$.NULL,
|
|
$.asterisk_expression,
|
|
$._identifier,
|
|
$.number,
|
|
$.in_expression,
|
|
$.is_expression,
|
|
$.boolean_expression,
|
|
$._parenthesized_expression,
|
|
$.type_cast,
|
|
$.unary_expression,
|
|
$.binary_expression,
|
|
$.conditional_expression,
|
|
$.array_element_access,
|
|
$.argument_reference,
|
|
$.select_subexpression,
|
|
$.at_time_zone_expression,
|
|
),
|
|
},
|
|
});
|
|
|
|
function commaSep1(rule) {
|
|
return sep1(rule, ",");
|
|
}
|
|
|
|
function commaSep(rule) {
|
|
return optional(sep1(rule, ","));
|
|
}
|
|
|
|
function sep1(rule, separator) {
|
|
return seq(rule, repeat(seq(separator, rule)));
|
|
}
|
|
|
|
function sep2(rule, separator) {
|
|
return seq(rule, repeat1(seq(separator, rule)));
|
|
}
|