Skip to content

Commit

Permalink
Support CTE, CASE, etc.
Browse files Browse the repository at this point in the history
New supportings:
  - CTEs syntax;
  - DELETE statement;
  - ALTER TABLE RENAME COLUMN;
  - Conditional expressions;
  - LIMIT clause;
  - AT TIME ZONE expression;

Improvements:
  - The ORDER BY clause;
  - The JOIN clause;
  - The select_subexpression;
  - The UPDATE statement;
  - Array type;
  - $.type: multi-words types (VARYING, PRECISION, WITH/WITHOUT TIME ZONE);
  - The $.field_access expression to $.json_access expression;
  • Loading branch information
pplam committed Jun 13, 2022
1 parent 2ec2fed commit 8d09bf6
Show file tree
Hide file tree
Showing 9 changed files with 121,400 additions and 57,727 deletions.
162 changes: 131 additions & 31 deletions grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function createCaseInsensitiveRegex(word) {
);
}


module.exports = grammar({
name: "sql",
extras: $ => [$.comment, /[\s\f\uFEFF\u2060\u200B]|\\\r?\n/],
Expand All @@ -77,8 +78,9 @@ module.exports = grammar({
$.rollback_statement,
$.select_statement,
$.update_statement,
$.set_statement,
$.insert_statement,
$.delete_statement,
$.set_statement,
$.grant_statement,
$.drop_statement,
$.create_statement,
Expand All @@ -102,6 +104,31 @@ module.exports = grammar({
rollback_statement: $ =>
seq(kw("ROLLBACK"), optional(choice(kw("WORK"), kw("TRANSACTION")))),

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),

create_statement: $ =>
seq(
kw("CREATE"),
Expand All @@ -119,7 +146,10 @@ module.exports = grammar({
optional(kw("IF EXISTS")),
optional(kw("ONLY")),
$._identifier,
$.alter_table_action,
choice(
$.alter_table_action,
$.alter_table_rename_column,
)
),
alter_table_action_alter_column: $ =>
seq(
Expand All @@ -134,6 +164,14 @@ module.exports = grammar({
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,
Expand Down Expand Up @@ -208,12 +246,12 @@ module.exports = grammar({
create_extension_statement: $ =>
seq(kw("CREATE EXTENSION"), optional(kw("IF NOT EXISTS")), $._identifier),
create_role_statement: $ =>
seq(
prec.left(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: $ =>
Expand All @@ -232,7 +270,7 @@ module.exports = grammar({
choice($._expression, kw("DEFAULT")),
),
grant_statement: $ =>
seq(
prec.left(seq(
kw("GRANT"),
choice(
seq(kw("ALL"), optional(kw("PRIVILEGES"))),
Expand Down Expand Up @@ -260,7 +298,7 @@ module.exports = grammar({
kw("TO"),
choice(seq(optional(kw("GROUP")), $.identifier), kw("PUBLIC")),
optional(kw("WITH GRANT OPTION")),
),
)),
create_domain_statement: $ =>
seq(
kw("CREATE DOMAIN"),
Expand Down Expand Up @@ -288,7 +326,7 @@ module.exports = grammar({
create_index_include_clause: $ =>
seq(kw("INCLUDE"), "(", commaSep1($.identifier), ")"),
create_index_statement: $ =>
seq(
prec.right(seq(
kw("CREATE"),
optional($.unique_constraint),
kw("INDEX"),
Expand All @@ -300,9 +338,9 @@ module.exports = grammar({
optional($.create_index_include_clause),
optional($.create_index_with_clause),
optional($.where_clause),
),
)),
table_column: $ =>
seq(
prec.left(seq(
field("name", $._identifier),
field("type", $._type),
repeat(
Expand All @@ -316,14 +354,11 @@ module.exports = grammar({
$.named_constraint,
$.direction_constraint,
$.auto_increment_constraint,
$.time_zone_constraint,
),
),
),
)),
auto_increment_constraint: _ => kw("AUTO_INCREMENT"),
direction_constraint: _ => choice(kw("ASC"), kw("DESC")),
time_zone_constraint: _ =>
seq(choice(kw("WITH"), kw("WITHOUT")), kw("TIME ZONE")),
named_constraint: $ => seq("CONSTRAINT", $.identifier),
_column_default_expression: $ =>
choice(
Expand Down Expand Up @@ -393,7 +428,10 @@ module.exports = grammar({
kw("TABLE"),
optional(kw("IF NOT EXISTS")),
$._identifier,
$.table_parameters,
choice(
seq(kw("AS"), $.select_statement),
$.table_parameters,
)
),
using_clause: $ => seq(kw("USING"), field("method", $.identifier)),
index_table_parameters: $ =>
Expand All @@ -409,19 +447,31 @@ module.exports = grammar({
),

// SELECT
select_statement: $ =>
_select_statement: $ =>
seq(
$.select_clause,
optional($.from_clause),
optional(repeat($.join_clause)),
optional($.where_clause),
optional($.group_by_clause),
optional($.order_by_clause),
optional($.limit_clause),
),
group_by_clause_body: $ => commaSep1($._expression),
group_by_clause: $ => seq(kw("GROUP BY"), $.group_by_clause_body),
order_by_clause_body: $ => commaSep1($._expression),
order_by_clause: $ => seq(kw("ORDER BY"), $.order_by_clause_body),
order_by_clause: $ => seq(
kw("ORDER BY"),
$.order_by_clause_body,
optional(field("order", choice(
kw("ASC"),
kw("DESC")
)))
),
limit_clause: $ => seq(kw("LIMIT"), $.number, optional(seq(
choice(kw("OFFSET"), ","), // MySQL LIMIT a, b
$.number
))),
where_clause: $ => seq(kw("WHERE"), $._expression),
_aliased_expression: $ =>
seq($._expression, optional(kw("AS")), $.identifier),
Expand All @@ -445,16 +495,20 @@ module.exports = grammar({
seq(
optional($.join_type),
kw("JOIN"),
$._identifier,
$._aliasable_expression,
kw("ON"),
$._expression,
),
select_subexpression: $ => seq("(", $.select_statement, ")"),
select_subexpression: $ => prec(1, seq(optional(kw('LATERAL')), "(", $.select_statement, ")")),

// UPDATE
update_statement: $ =>
seq(kw("UPDATE"), $.identifier, $.set_clause, optional($.where_clause)),

_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),
Expand All @@ -469,8 +523,28 @@ module.exports = grammar({
),
values_clause: $ => seq(kw("VALUES"), "(", $.values_clause_body, ")"),
values_clause_body: $ => commaSep1($._expression),
in_expression: $ =>
prec.left(1, seq($._expression, optional(kw("NOT")), kw("IN"), $.tuple)),

// 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
Expand Down Expand Up @@ -513,11 +587,10 @@ module.exports = grammar({
_parenthesized_expression: $ => seq("(", $._expression, ")"),
is_expression: $ =>
prec.left(
1,
PREC.comparative,
seq(
$._expression,
kw("IS"),
optional(kw("NOT")),
kw("IS"), optional(kw("NOT")),
choice($.NULL, $.TRUE, $.FALSE, $.distinct_from),
),
),
Expand All @@ -528,6 +601,11 @@ module.exports = grammar({
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"),
Expand All @@ -542,29 +620,49 @@ module.exports = grammar({
identifier: $ => choice($._unquoted_identifier, $._quoted_identifier),
dotted_name: $ => prec.left(PREC.primary, sep2($.identifier, ".")),
_identifier: $ => choice($.identifier, $.dotted_name),
type: $ => seq($._identifier, optional(seq("(", $.number, ")"))),
string: $ =>
choice(
seq("'", field("content", /[^']*/), "'"),
seq("$$", field("content", /(\$?[^$]+)+/), "$$"), // FIXME empty string test, maybe read a bit more into c comments answer
),
field_access: $ => seq($.identifier, "->>", $.string),
json_access: $ => seq(
$._expression,
choice(
"->",
"->>",
"#>",
"#>>",
),
choice($.string, $.number)
),
ordered_expression: $ =>
seq($._expression, field("order", choice(kw("ASC"), kw("DESC")))),
array_type: $ => seq($._type, "[", "]"),

type: $ => prec.right(seq(
$._identifier,
optional(kw("VARYING")), // CHARACTER/BIT VARYING
optional(kw("PRECISION")), // DOUBLE PRECISION
optional(seq("(", $.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,
$._identifier,
$.function_call,
),
"::",
field("type", $._type),
),

// http://stackoverflow.com/questions/13014947/regex-to-match-a-c-style-multiline-comment/36328890#36328890
comment: $ =>
token(
Expand Down Expand Up @@ -624,7 +722,7 @@ module.exports = grammar({
$.interval_expression,
$.function_call,
$.string,
$.field_access,
$.json_access,
$.TRUE,
$.FALSE,
$.NULL,
Expand All @@ -638,9 +736,11 @@ module.exports = grammar({
$.type_cast,
$.unary_expression,
$.binary_expression,
$.conditional_expression,
$.array_element_access,
$.argument_reference,
$.select_subexpression,
$.at_time_zone_expression,
),
},
});
Expand Down
Loading

0 comments on commit 8d09bf6

Please sign in to comment.