363 lines
12 KiB
JavaScript
363 lines
12 KiB
JavaScript
module.exports = grammar({
|
|
name: "psql",
|
|
|
|
extras: ($) => [/\s\n/, /\s/, $.line_comment, $.block_comment],
|
|
|
|
conflicts: ($) => [[$.keyword_char, $.keyword_varchar]],
|
|
|
|
word: ($) => $._identifier,
|
|
|
|
rules: {
|
|
source_file: ($) => repeat(choice($.statement)),
|
|
|
|
statement: ($) => seq(optional($._ddl_statement), ";"),
|
|
|
|
_ddl_statement: ($) => choice($._create_statement),
|
|
|
|
_create_statement: ($) => choice($.create_table),
|
|
|
|
// References: https://www.postgresql.org/docs/15/sql-createtable.html
|
|
create_table: ($) =>
|
|
seq(
|
|
$.keyword_create,
|
|
optional(choice($.keyword_temporary, $.keyword_unlogged)),
|
|
$.keyword_table,
|
|
optional($._if_not_exists),
|
|
$.table_reference,
|
|
$.column_definitions
|
|
// TODO: INHERITS
|
|
// TODO: PARTITION BY
|
|
// TODO: USING
|
|
// TODO: WITH
|
|
// TODO: ON COMMIT
|
|
// TODO: TABLESPACE
|
|
),
|
|
|
|
column_definitions: ($) =>
|
|
parens(
|
|
optional(commaSep1(choice($.column_definition, $.table_constraint)))
|
|
),
|
|
|
|
column_definition: ($) =>
|
|
seq(field("name", $.identifier), $.type, repeat($.column_constraint)),
|
|
|
|
column_constraint: ($) =>
|
|
seq(
|
|
optional(seq($.keyword_constraint, field("name", $.identifier))),
|
|
choice(
|
|
$._not_null,
|
|
$.keyword_null,
|
|
seq($.keyword_default, $._expression),
|
|
// TODO: add index_parameters in UNIQUE, PRIMARY KEY
|
|
$._unique_constraint,
|
|
$._primary_key,
|
|
seq(
|
|
$.keyword_references,
|
|
$.table_reference,
|
|
optional($.ref_column),
|
|
optional($._foreign_key_match),
|
|
optional(
|
|
choice(
|
|
seq($._foreign_key_on_delete, $._foreign_key_on_update),
|
|
seq($._foreign_key_on_update, $._foreign_key_on_delete)
|
|
)
|
|
)
|
|
)
|
|
// TODO: CHECK
|
|
// TODO: GENERATED
|
|
)
|
|
// TODO: DEFERRABLE
|
|
),
|
|
|
|
ref_column: ($) => parens(field("name", $.identifier)),
|
|
|
|
table_constraint: ($) =>
|
|
seq(
|
|
optional(seq($.keyword_constraint, field("name", $.identifier))),
|
|
choice(
|
|
seq($._unique_constraint, $.column_list),
|
|
seq($._primary_key, $.column_list),
|
|
seq(
|
|
$._foreign_key,
|
|
$.column_list,
|
|
$.keyword_references,
|
|
$.table_reference,
|
|
optional(alias($.column_list, $.ref_column_list)),
|
|
optional($._foreign_key_match),
|
|
optional(
|
|
choice(
|
|
seq($._foreign_key_on_delete, $._foreign_key_on_update),
|
|
seq($._foreign_key_on_update, $._foreign_key_on_delete)
|
|
)
|
|
)
|
|
)
|
|
// TODO: CHECK
|
|
// TODO: EXCLUDE
|
|
)
|
|
// TODO: DEFERRABLE
|
|
),
|
|
|
|
column_list: ($) => parens(commaSep1(field("name", $.identifier))),
|
|
|
|
_unique_constraint: ($) =>
|
|
seq(
|
|
$.keyword_unique,
|
|
optional(
|
|
seq($.keyword_nulls, optional($.keyword_not), $.keyword_distinct)
|
|
)
|
|
),
|
|
|
|
_foreign_key_match: ($) =>
|
|
seq(
|
|
$.keyword_match,
|
|
choice($.keyword_full, $.keyword_partial, $.keyword_simple)
|
|
),
|
|
_foreign_key_on_delete: ($) =>
|
|
seq($.keyword_on, $.keyword_delete, $.referencial_action),
|
|
_foreign_key_on_update: ($) =>
|
|
seq($.keyword_on, $.keyword_update, $.referencial_action),
|
|
|
|
referencial_action: ($) =>
|
|
choice(
|
|
seq($.keyword_no, $.keyword_action),
|
|
$.keyword_restrict,
|
|
$.keyword_cascade,
|
|
seq(
|
|
$.keyword_set,
|
|
choice($.keyword_null, $.keyword_default),
|
|
optional(parens(commaSep1($.identifier)))
|
|
)
|
|
),
|
|
|
|
table_reference: ($) =>
|
|
seq(
|
|
optional(seq(field("schema", $.identifier), ".")),
|
|
field("name", $.identifier)
|
|
),
|
|
|
|
_expression: ($) =>
|
|
choice(
|
|
$.literal
|
|
// TODO: add more types
|
|
),
|
|
|
|
literal: ($) =>
|
|
choice(
|
|
$.number,
|
|
$.literal_string,
|
|
$.keyword_true,
|
|
$.keyword_false,
|
|
$.keyword_null
|
|
),
|
|
|
|
// References: https://www.postgresql.org/docs/15/datatype.html
|
|
type: ($) =>
|
|
choice(
|
|
field("name", $.identifier),
|
|
$._type_numeric,
|
|
$._type_character,
|
|
$._type_datetime,
|
|
$._type_geometric,
|
|
$._type_net,
|
|
$._type_bit_string,
|
|
$._type_text_search,
|
|
$.keyword_money,
|
|
$.keyword_bytea,
|
|
$.keyword_boolean,
|
|
$.keyword_uuid,
|
|
$.keyword_xml
|
|
// TODO: add arrays References: https://www.postgresql.org/docs/15/arrays.html
|
|
// TODO: add rangetypes References: https://www.postgresql.org/docs/15/rangetypes.html
|
|
// TODO: add OID types References: https://www.postgresql.org/docs/15/datatype-oid.html
|
|
),
|
|
|
|
// References: https://www.postgresql.org/docs/15/datatype-numeric.html
|
|
_type_numeric: ($) =>
|
|
choice(
|
|
$.keyword_smallint,
|
|
$.keyword_integer,
|
|
$.keyword_bigint,
|
|
$.keyword_real,
|
|
$.keyword_smallserial,
|
|
$.keyword_serial,
|
|
$.keyword_bigserial,
|
|
$.double,
|
|
$.numeric,
|
|
$.decimal
|
|
),
|
|
double: ($) => seq(mkKeyword("double"), mkKeyword("precision")),
|
|
decimal: ($) =>
|
|
choice(
|
|
parametricType($, $.keyword_decimal, ["precision"]),
|
|
parametricType($, $.keyword_decimal, ["precision", "scale"])
|
|
),
|
|
numeric: ($) =>
|
|
choice(
|
|
parametricType($, $.keyword_numeric, ["precision"]),
|
|
parametricType($, $.keyword_numeric, ["precision", "scale"])
|
|
),
|
|
|
|
// References: https://www.postgresql.org/docs/15/datatype-character.html
|
|
_type_character: ($) => choice($.keyword_text, $.char, $.varchar),
|
|
char: ($) => parametricType($, $.keyword_char),
|
|
varchar: ($) => parametricType($, $.keyword_varchar),
|
|
|
|
// TODO: add interval type
|
|
// References: https://www.postgresql.org/docs/15/datatype-datetime.html
|
|
_type_datetime: ($) =>
|
|
choice(
|
|
$.keyword_date,
|
|
$.keyword_datetime,
|
|
$.keyword_time,
|
|
$.keyword_timestamp,
|
|
$.keyword_timestamptz
|
|
),
|
|
|
|
// TODO: add geometric types
|
|
// References: https://www.postgresql.org/docs/15/datatype-geometric.html
|
|
_type_geometric: ($) => choice(),
|
|
|
|
// TODO: add net types
|
|
// References: https://www.postgresql.org/docs/15/datatype-net-types.html
|
|
_type_net: ($) => choice(),
|
|
|
|
// TODO: add bit string types
|
|
// References: https://www.postgresql.org/docs/15/datatype-bit.html
|
|
_type_bit_string: ($) => choice(),
|
|
|
|
// TODO: add text search types
|
|
// References: https://www.postgresql.org/docs/15/datatype-textsearch.html
|
|
_type_text_search: ($) => choice(),
|
|
|
|
// References: https://www.postgresql.org/docs/15/datatype-json.html
|
|
_type_json: ($) => choice($.keyword_json, $.keyword_jsonb),
|
|
|
|
// keywords
|
|
_primary_key: ($) => seq($.keyword_primary, $.keyword_key),
|
|
_foreign_key: ($) => seq($.keyword_foreign, $.keyword_key),
|
|
_if_not_exists: ($) => seq($.keyword_if, $.keyword_not, $.keyword_exists),
|
|
_not_null: ($) => seq($.keyword_not, $.keyword_null),
|
|
_without_time_zone: ($) => seq(mkKeyword("without"), $._keyword_time_zone),
|
|
_with_time_zone: ($) => seq(mkKeyword("with"), $._keyword_time_zone),
|
|
_keyword_time_zone: (_) => seq(mkKeyword("time"), mkKeyword("zone")),
|
|
|
|
keyword_create: (_) => mkKeyword("create"),
|
|
keyword_table: (_) => mkKeyword("table"),
|
|
keyword_temporary: (_) => choice(mkKeyword("temporary"), mkKeyword("temp")),
|
|
keyword_unlogged: (_) => mkKeyword("unlogged"),
|
|
keyword_if: (_) => mkKeyword("if"),
|
|
keyword_not: (_) => mkKeyword("not"),
|
|
keyword_exists: (_) => mkKeyword("exists"),
|
|
keyword_null: (_) => mkKeyword("null"),
|
|
keyword_constraint: (_) => mkKeyword("constraint"),
|
|
keyword_default: (_) => mkKeyword("default"),
|
|
keyword_true: (_) => mkKeyword("true"),
|
|
keyword_false: (_) => mkKeyword("false"),
|
|
keyword_nulls: (_) => mkKeyword("nulls"),
|
|
keyword_distinct: (_) => mkKeyword("distinct"),
|
|
keyword_unique: (_) => mkKeyword("unique"),
|
|
keyword_primary: (_) => mkKeyword("primary"),
|
|
keyword_foreign: (_) => mkKeyword("foreign"),
|
|
keyword_key: (_) => mkKeyword("key"),
|
|
keyword_references: (_) => mkKeyword("references"),
|
|
keyword_on: (_) => mkKeyword("on"),
|
|
keyword_no: (_) => mkKeyword("no"),
|
|
keyword_delete: (_) => mkKeyword("delete"),
|
|
keyword_update: (_) => mkKeyword("update"),
|
|
keyword_match: (_) => mkKeyword("match"),
|
|
keyword_full: (_) => mkKeyword("full"),
|
|
keyword_partial: (_) => mkKeyword("partial"),
|
|
keyword_simple: (_) => mkKeyword("simple"),
|
|
keyword_action: (_) => mkKeyword("action"),
|
|
keyword_set: (_) => mkKeyword("set"),
|
|
keyword_restrict: (_) => mkKeyword("restrict"),
|
|
keyword_cascade: (_) => mkKeyword("cascade"),
|
|
// References: https://www.postgresql.org/docs/15/datatype-xml.html
|
|
keyword_xml: (_) => mkKeyword("xml"),
|
|
// References: https://www.postgresql.org/docs/15/datatype-uuid.html
|
|
keyword_uuid: (_) => mkKeyword("uuid"),
|
|
keyword_json: (_) => mkKeyword("json"),
|
|
keyword_jsonb: (_) => mkKeyword("jsonb"),
|
|
keyword_boolean: (_) => mkKeyword("boolean"),
|
|
keyword_smallint: (_) => mkKeyword("smallint"),
|
|
keyword_integer: (_) => mkKeyword("integer"),
|
|
keyword_bigint: (_) => mkKeyword("bigint"),
|
|
keyword_decimal: (_) => mkKeyword("decimal"),
|
|
keyword_numeric: (_) => mkKeyword("numeric"),
|
|
keyword_real: (_) => mkKeyword("real"),
|
|
keyword_smallserial: (_) => mkKeyword("smallserial"),
|
|
keyword_serial: (_) => mkKeyword("serial"),
|
|
keyword_bigserial: (_) => mkKeyword("bigserial"),
|
|
// References: https://www.postgresql.org/docs/15/datatype-money.html
|
|
keyword_money: (_) => mkKeyword("money"),
|
|
keyword_text: (_) => mkKeyword("text"),
|
|
keyword_char: (_) => choice(mkKeyword("character"), mkKeyword("char")),
|
|
keyword_varchar: (_) =>
|
|
choice(
|
|
mkKeyword("varchar"),
|
|
seq(mkKeyword("character", mkKeyword("varying")))
|
|
),
|
|
keyword_bytea: (_) => mkKeyword("bytea"),
|
|
keyword_date: (_) => mkKeyword("date"),
|
|
keyword_datetime: (_) => mkKeyword("datetime"),
|
|
keyword_time: ($) =>
|
|
seq(
|
|
mkKeyword("time"),
|
|
choice(optional($._without_time_zone), $._with_time_zone)
|
|
),
|
|
keyword_timestamp: ($) =>
|
|
seq(mkKeyword("timestamp"), optional($._without_time_zone)),
|
|
keyword_timestamptz: ($) =>
|
|
choice(
|
|
mkKeyword("timestamptz"),
|
|
seq(mkKeyword("timestamp"), $._with_time_zone)
|
|
),
|
|
|
|
// -------
|
|
|
|
line_comment: (_) => seq("--", /.*\n/),
|
|
// https://stackoverflow.com/questions/13014947/regex-to-match-a-c-style-multiline-comment
|
|
block_comment: (_) => seq("/*", /[^*]*\*+(?:[^/*][^*]*\*+)*/, "/"),
|
|
|
|
literal_string: ($) => choice(seq("'", /[^']*/, "'")),
|
|
number: (_) => /\d+/,
|
|
|
|
identifier: ($) => choice($._identifier, seq('"', /[^"]+/, '"')),
|
|
|
|
_identifier: (_) => /([a-zA-Z_][0-9a-zA-Z_]*)/,
|
|
},
|
|
});
|
|
|
|
function mkKeyword(word) {
|
|
return new RegExp(word + "|" + word.toUpperCase());
|
|
}
|
|
|
|
function commaSep1(rule) {
|
|
return sep1(",", rule);
|
|
}
|
|
|
|
function sep1(separator, rule) {
|
|
return seq(rule, repeat(seq(separator, rule)));
|
|
}
|
|
|
|
function parens(rule) {
|
|
return seq("(", rule, ")");
|
|
}
|
|
|
|
function parametricType($, type, params = ["size"]) {
|
|
return prec.right(
|
|
choice(
|
|
type,
|
|
seq(
|
|
type,
|
|
"(",
|
|
// first parameter is guaranteed, shift it out of the array
|
|
field(params.shift(), $.number),
|
|
// then, fill in the ", next" until done
|
|
...params.map((p) => seq(",", field(p, $.number))),
|
|
")"
|
|
)
|
|
)
|
|
);
|
|
}
|