From 6c54b000b9bd07f458d25c35fe49aae032842503 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Mon, 30 Sep 2024 19:06:42 +0530 Subject: [PATCH 1/7] feat: added datafusion sql support In this PR it will support for TRY_CAST function --- README.md | 109 +- ast/postgresql.ts | 58 +- pegjs/datafusionsql.pegjs | 5886 +++++++++++++++++++++++++++++++++++++ src/parser.all.js | 2 + test/datafusion.spec.js | 2166 ++++++++++++++ test/select.spec.js | 62 + 6 files changed, 8157 insertions(+), 126 deletions(-) create mode 100644 pegjs/datafusionsql.pegjs create mode 100644 test/datafusion.spec.js diff --git a/README.md b/README.md index cb13cf72..62b2b8c2 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,4 @@ -# Nodejs SQL Parser - -[![Build Status](https://travis-ci.org/taozhi8833998/node-sql-parser.svg?branch=master)](https://travis-ci.org/taozhi8833998/node-sql-parser) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/d923c9f2853f44f295c383d9943b56cc)](https://www.codacy.com/manual/taozhi8833998/node-sql-parser?utm_source=github.com&utm_medium=referral&utm_content=taozhi8833998/node-sql-parser&utm_campaign=Badge_Grade) -[![Coverage Status](https://img.shields.io/coveralls/github/taozhi8833998/node-sql-parser/master.svg)](https://coveralls.io/github/taozhi8833998/node-sql-parser?branch=master) -[![Dependencies](https://img.shields.io/david/taozhi8833998/node-sql-parser.svg)](https://img.shields.io/david/taozhi8833998/node-sql-parser) -[![Known Vulnerabilities](https://snyk.io/test/github/taozhi8833998/node-sql-parser/badge.svg?targetFile=package.json)](https://snyk.io/test/github/taozhi8833998/node-sql-parser?targetFile=package.json) -[![](https://img.shields.io/badge/Powered%20by-ganjiang-brightgreen.svg)](https://github.com/taozhi8833998/node-sql-parser) - -[![npm version](https://badge.fury.io/js/node-sql-parser.svg)](https://badge.fury.io/js/node-sql-parser) -[![NPM downloads](http://img.shields.io/npm/dm/node-sql-parser.svg?style=flat-square)](http://www.npmtrends.com/node-sql-parser) - -[![](https://img.shields.io/gitter/room/taozhi8833998/node-sql-parser.svg)](https://gitter.im/node-sql-parser/community) -[![issues](https://img.shields.io/github/issues/taozhi8833998/node-sql-parser.svg)](https://github.com/taozhi8833998/node-sql-parser/issues) - -[![TypeScript definitions on DefinitelyTyped](http://definitelytyped.org/badges/standard.svg)](http://definitelytyped.org) -[![license](https://img.shields.io/npm/l/node-sql-parser)](https://github.com/taozhi8833998/node-sql-parser/blob/master/LICENSE) - -**Parse simple SQL statements into an abstract syntax tree (AST) with the visited tableList, columnList and convert it back to SQL.** - -## :star: Features - -- support multiple sql statement seperate by semicolon -- support select, delete, update and insert type -- support drop, truncate and rename command -- output the table and column list that the sql visited with the corresponding authority -- support various databases engine - -## :tada: Install - -### From [npmjs](https://www.npmjs.org/) - -```bash -npm install node-sql-parser --save - -or - -yarn add node-sql-parser -``` - -### From [GitHub Package Registry](https://npm.pkg.github.com/) - -```bash -npm install @taozhi8833998/node-sql-parser --registry=https://npm.pkg.github.com/ -``` +## Nodejs SQL Parser ### From Browser @@ -88,20 +44,8 @@ Import the JS file in your page: ### Supported Database SQL Syntax -- Athena -- BigQuery -- DB2 -- Hive -- MariaDB -- MySQL - PostgresQL -- Redshift -- Sqlite -- TransactSQL -- [FlinkSQL](https://ci.apache.org/projects/flink/flink-docs-stable/dev/table/sql/) -- Snowflake(alpha) -- [Noql](https://noql.synatic.dev/) -- New issue could be made for other new database. +- DatafusionSQL ### Create AST for SQL statement @@ -344,51 +288,4 @@ const opt = { } // opt is optional parser.whiteListCheck(sql, whiteColumnList, opt) // if check failed, an error would be thrown with relevant error message, if passed it would return undefined -``` - -## :kissing_heart: Acknowledgement - -This project is inspired by the SQL parser [flora-sql-parser](https://github.com/godmodelabs/flora-sql-parser) module. - -## License - -[Apache-2.0](LICENSE) - -## Buy me a Coffee - -If you like my project, **Star** in the corresponding project right corner. Your support is my biggest encouragement! ^_^ - -You can also scan the qr code below or open paypal link to donate to Author. - -### Paypal - -Donate money by [paypal](https://www.paypal.me/taozhi8833998/5) to my account [taozhi8833998@163.com](taozhi8833998@163.com) - -### AliPay(支付宝) - -

- -

- - -### Wechat(微信) - -

- -

- -### Explain - -If you have made a donation, you can leave your name and email in the issue, your name will be written to the donation list. - - -## [Donation list](https://github.com/taozhi8833998/node-sql-parser/blob/master/DONATIONLIST.md) - -## Star History - - - - - Star History Chart - - +``` \ No newline at end of file diff --git a/ast/postgresql.ts b/ast/postgresql.ts index 9b2695ef..49276448 100644 --- a/ast/postgresql.ts +++ b/ast/postgresql.ts @@ -7,7 +7,7 @@ export type start = multiple_stmt | create_function_stmt; -export type cmd_stmt = drop_stmt | create_stmt | declare_stmt | truncate_stmt | rename_stmt | call_stmt | use_stmt | alter_stmt | set_stmt | lock_stmt | show_stmt | deallocate_stmt | grant_revoke_stmt | if_else_stmt | raise_stmt | execute_stmt | for_loop_stmt | transaction_stmt; +export type cmd_stmt = drop_stmt | create_stmt | declare_stmt | truncate_stmt | rename_stmt | call_stmt | use_stmt | alter_stmt | set_stmt | lock_stmt | show_stmt | deallocate_stmt | grant_revoke_stmt | if_else_stmt | raise_stmt | execute_stmt | for_loop_stmt | transaction_stmt | comment_on_stmt; export type create_stmt = create_table_stmt | create_constraint_trigger | create_extension_stmt | create_index_stmt | create_sequence | create_db_stmt | create_domain_stmt | create_type_stmt | create_view_stmt | create_aggregate_stmt; @@ -111,12 +111,12 @@ export type declare_stmt_t = { type: 'declare'; declare: declare_variable_item[] export type declare_stmt = AstStatement; -export type create_func_opt = literal_string | { type: 'as'; begin?: string; declare?: declare_stmt; expr: multiple_stmt; end?: string; symbol: string; } | literal_numeric | { type: "set"; parameter: ident_name; value?: { prefix: string; expr: expr }}; +export type create_func_opt = literal_string | { type: 'as'; begin?: string; declare?: declare_stmt; expr: multiple_stmt; end?: string; symbol: string; } | literal_numeric | { type: "set"; parameter: ident_name; value?: { prefix: string; expr: expr }} | return_stmt; export type create_function_stmt_t = { type: 'create'; replace?: string; - name: { schema?: string; name: string }; + name: proc_func_name; args?: alter_func_args; returns?: func_returns; keyword: 'function'; @@ -820,15 +820,37 @@ export interface for_loop_stmt_t { export type for_loop_stmt = AstStatement; +export type transaction_mode_isolation_level = { type: 'origin'; value: string; } | ignore; + +export type transaction_mode = { type: 'origin'; value: string; } | ignore; + +export type transaction_mode_list = transaction_mode[]; + export interface transaction_stmt_t { type: 'transaction'; expr: { - type: 'origin', - value: string + action: { + type: 'origin', + value: string + }; + keyword?: string; + modes?: transaction_mode[]; } } -export type transaction_stmt = AstStatement; +export type transaction_stmt = AstStatement | ignore; + +export type comment_on_option = { type: string; name: table_name; } | { type: string; name: column_ref; } | { type: string; name: ident; }; + +export type comment_on_is = { keyword: 'is'; expr: literal_string | literal_null; }; + +export interface comment_on_stmt_t { + type: 'comment'; + target: comment_on_option; + expr: comment_on_is; + } + +export type comment_on_stmt = AstStatement; export interface select_stmt_node extends select_stmt_nake { parentheses: true; @@ -842,7 +864,7 @@ export type cte_definition = { name: { type: 'default'; value: string; }; stmt: export type cte_column_definition = column_ref_list; -export type distinct_on = {type: string; columns: column_ref_list;} | { type: string | undefined; }; +export type distinct_on = {type: string; columns: column_list_items;} | { type: string | undefined; }; @@ -866,7 +888,9 @@ export type option_clause = query_option[]; export type query_option = 'SQL_CALC_FOUND_ROWS'| 'SQL_CACHE'| 'SQL_NO_CACHE'| 'SQL_BIG_RESULT'| 'SQL_SMALL_RESULT'| 'SQL_BUFFER_RESULT'; -export type column_clause = 'ALL' | '*' | column_list_item[] | column_list_item[]; +export type column_list_items = column_list_item[]; + +export type column_clause = 'ALL' | '*' | column_list_item[] | column_list_items; export type array_index = { brackets: boolean, number: number }; @@ -1448,6 +1472,8 @@ type KW_RECURSIVE = never; type KW_REPLACE = never; +type KW_RETURN = never; + type KW_RETURNING = never; type KW_RENAME = never; @@ -1480,6 +1506,8 @@ type KW_TABLESPACE = never; type KW_COLLATE = never; +type KW_COLLATION = never; + type KW_DEALLOCATE = never; type KW_ON = never; @@ -1628,6 +1656,8 @@ type KW_MEDIUMTEXT = never; type KW_LONGTEXT = never; +type KW_MEDIUMINT = never; + type KW_BIGINT = never; type KW_ENUM = never; @@ -1734,8 +1764,6 @@ type KW_VAR_PRE_DOLLAR_DOUBLE = never; type KW_VAR_PRE = never; -type KW_RETURN = never; - type KW_ASSIGN = never; type KW_DOUBLE_COLON = never; @@ -1899,8 +1927,6 @@ export type binary_type = data_type; export type character_varying = string; - - export type character_string_type = data_type; export type numeric_type_suffix = any[];; @@ -1921,16 +1947,10 @@ export type timezone = string[];; - - - - export type time_type = data_type; - - export type datetime_type = data_type | time_type; @@ -1951,8 +1971,6 @@ export type serial_interval_type = data_type; - - export type text_type = data_type; diff --git a/pegjs/datafusionsql.pegjs b/pegjs/datafusionsql.pegjs new file mode 100644 index 00000000..7faf9657 --- /dev/null +++ b/pegjs/datafusionsql.pegjs @@ -0,0 +1,5886 @@ +{ + const reservedMap = { + 'ALTER': true, + 'ALL': true, + 'ADD': true, + 'AND': true, + 'AS': true, + 'ASC': true, + + 'BETWEEN': true, + 'BY': true, + + 'CALL': true, + 'CASE': true, + 'CREATE': true, + 'CONTAINS': true, + 'CONSTRAINT': true, + 'CURRENT_DATE': true, + 'CURRENT_TIME': true, + 'CURRENT_TIMESTAMP': true, + 'CURRENT_USER': true, + + 'DELETE': true, + 'DESC': true, + 'DISTINCT': true, + 'DROP': true, + + 'ELSE': true, + 'END': true, + 'EXISTS': true, + 'EXPLAIN': true, + + 'FALSE': true, + 'FROM': true, + 'FULL': true, + + 'GROUP': true, + + 'HAVING': true, + + 'IN': true, + 'INNER': true, + 'INSERT': true, + 'INTERSECT': true, + 'INTO': true, + 'IS': true, + 'ILIKE': true, + + 'JOIN': true, + 'JSON': true, + + // 'KEY': true, + + 'LEFT': true, + 'LIKE': true, + 'LIMIT': true, + + 'NOT': true, + 'NULL': true, + 'NULLS': true, + + 'OFFSET': true, + 'ON': true, + 'OR': true, + 'ORDER': true, + 'OUTER': true, + + 'PARTITION': true, + + 'RECURSIVE': true, + 'RENAME': true, + // 'REPLACE': true, + 'RIGHT': true, + + 'SELECT': true, + 'SESSION_USER': true, + 'SET': true, + 'SHOW': true, + 'SYSTEM_USER': true, + + 'TABLE': true, + 'THEN': true, + 'TRUE': true, + 'TRUNCATE': true, + + 'UNION': true, + 'UPDATE': true, + 'USING': true, + + // 'VALUES': true, + + 'WITH': true, + 'WHEN': true, + 'WHERE': true, + 'WINDOW': true, + + 'GLOBAL': true, + 'SESSION': true, + 'LOCAL': true, + 'PERSIST': true, + 'PERSIST_ONLY': true, + }; + + function getLocationObject() { + return options.includeLocations ? {loc: location()} : {} + } + + function createUnaryExpr(op, e) { + return { + type: 'unary_expr', + operator: op, + expr: e + }; + } + + function createBinaryExpr(op, left, right) { + return { + type: 'binary_expr', + operator: op, + left: left, + right: right, + ...getLocationObject(), + }; + } + + function isBigInt(numberStr) { + const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER) + const num = BigInt(numberStr) + if (num < previousMaxSafe) return false + return true + } + + function createList(head, tail, po = 3) { + const result = Array.isArray(head) ? head : [head]; + for (let i = 0; i < tail.length; i++) { + delete tail[i][po].tableList + delete tail[i][po].columnList + result.push(tail[i][po]); + } + return result; + } + + function createBinaryExprChain(head, tail) { + let result = head; + for (let i = 0; i < tail.length; i++) { + result = createBinaryExpr(tail[i][1], result, tail[i][3]); + } + return result; + } + + function queryTableAlias(tableName) { + const alias = tableAlias[tableName] + if (alias) return alias + if (tableName) return tableName + return null + } + + function columnListTableAlias(columnList) { + const newColumnsList = new Set() + const symbolChar = '::' + for(let column of columnList.keys()) { + const columnInfo = column.split(symbolChar) + if (!columnInfo) { + newColumnsList.add(column) + break + } + if (columnInfo && columnInfo[1]) columnInfo[1] = queryTableAlias(columnInfo[1]) + newColumnsList.add(columnInfo.join(symbolChar)) + } + return Array.from(newColumnsList) + } + + function refreshColumnList(columnList) { + const columns = columnListTableAlias(columnList) + columnList.clear() + columns.forEach(col => columnList.add(col)) + } + + function commonStrToLiteral(strOrLiteral) { + return typeof strOrLiteral === 'string' ? { type: 'same', value: strOrLiteral } : strOrLiteral + } + + const cmpPrefixMap = { + '+': true, + '-': true, + '*': true, + '/': true, + '>': true, + '<': true, + '!': true, + '=': true, + + //between + 'B': true, + 'b': true, + //for is or in + 'I': true, + 'i': true, + //for like + 'L': true, + 'l': true, + //for not + 'N': true, + 'n': true + }; + + // used for dependency analysis + let varList = []; + const tableList = new Set(); + const columnList = new Set(); + const customTypes = new Set(); + const tableAlias = {}; +} + +start + = __ n:(create_function_stmt / multiple_stmt) { + // => multiple_stmt + return n + } + / create_function_stmt + / multiple_stmt + +cmd_stmt + = drop_stmt + / create_stmt + / declare_stmt + / truncate_stmt + / rename_stmt + / call_stmt + / use_stmt + / alter_stmt + / set_stmt + / lock_stmt + / show_stmt + / deallocate_stmt + / grant_revoke_stmt + / if_else_stmt + / raise_stmt + / execute_stmt + / for_loop_stmt + / transaction_stmt + / comment_on_stmt + +create_stmt + = create_table_stmt + / create_constraint_trigger + / create_extension_stmt + / create_index_stmt + / create_sequence + / create_db_stmt + / create_domain_stmt + / create_type_stmt + / create_view_stmt + / create_aggregate_stmt + +alter_stmt + = alter_table_stmt + / alter_schema_stmt + / alter_domain_type_stmt + / alter_function_stmt + / alter_aggregate_stmt + +crud_stmt + = union_stmt + / update_stmt + / replace_insert_stmt + / insert_no_columns_stmt + / delete_stmt + / cmd_stmt + / proc_stmts + +multiple_stmt + = head:crud_stmt tail:(__ SEMICOLON __ crud_stmt)* { + /* + // is in reality: { tableList: any[]; columnList: any[]; ast: T; } + export type AstStatement = T; + => AstStatement */ + const headAst = head && head.ast || head + const cur = tail && tail.length && tail[0].length >= 4 ? [headAst] : headAst + for (let i = 0; i < tail.length; i++) { + if(!tail[i][3] || tail[i][3].length === 0) continue; + cur.push(tail[i][3] && tail[i][3].ast || tail[i][3]); + } + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: cur + } + } + +set_op + = KW_UNION __ a:(KW_ALL / KW_DISTINCT)? { + // => 'union' | 'union all' | 'union distinct' + return a ? `union ${a.toLowerCase()}` : 'union' + } + / KW_INTERSECT { + // => 'intersect' + return 'intersect' + } + / KW_EXCEPT { + // => 'except' + return 'except' + } + +union_stmt + = head:select_stmt tail:(__ set_op __ select_stmt)* __ ob:order_by_clause? __ l:limit_clause? { + /* export interface union_stmt_node extends select_stmt_node { + _next: union_stmt_node; + set_op: 'union' | 'union all' | 'union distinct'; + } + => AstStatement + */ + let cur = head + for (let i = 0; i < tail.length; i++) { + cur._next = tail[i][3] + cur.set_op = tail[i][1] + cur = cur._next + } + if(ob) head._orderby = ob + if(l && l.value && l.value.length > 0) head._limit = l + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: head + } + } + +if_not_exists_stmt + = 'IF'i __ KW_NOT __ KW_EXISTS { + // => 'IF NOT EXISTS' + return 'IF NOT EXISTS' + } + +if_exists + = 'if'i __ 'exists'i { + // => 'IF EXISTS' + return 'IF EXISTS' + } + +create_extension_stmt + = a:KW_CREATE __ + e:'EXTENSION'i __ + ife: if_not_exists_stmt? __ + n:(ident_name / literal_string) __ + w:KW_WITH? __ + s:('SCHEMA'i __ ident_name / literal_string)? __ + v:('VERSION'i __ (ident_name / literal_string))? __ + f:(KW_FROM __ (ident_name / literal_string))? { + /* + export type nameOrLiteral = literal_string | { type: 'same', value: string; }; + => { + type: 'create'; + keyword: 'extension'; + if_not_exists?: 'if not exists'; + extension: nameOrLiteral; + with: 'with'; + schema: nameOrLiteral; + version: nameOrLiteral; + from: nameOrLiteral; + } + */ + return { + type: 'create', + keyword: e.toLowerCase(), + if_not_exists:ife, + extension: commonStrToLiteral(n), + with: w && w[0].toLowerCase(), + schema: commonStrToLiteral(s && s[2].toLowerCase()), // <== wont that be a bug ? + version: commonStrToLiteral(v && v[2]), + from: commonStrToLiteral(f && f[2]), + } + } + +create_db_definition + = head:create_option_character_set tail:(__ create_option_character_set)* { + // => create_option_character_set[] + return createList(head, tail, 1) + } + +create_db_stmt + = a:KW_CREATE __ + k:(KW_DATABASE / KW_SCHEMA) __ + ife:if_not_exists_stmt? __ + t:proc_func_name __ + c:create_db_definition? { + /* + export type create_db_stmt_t = { + type: 'create', + keyword: 'database' | 'schema', + if_not_exists?: 'if not exists', + database?: { db: ident_without_kw_type, schema: [ident_without_kw_type] }; + schema?: { db: ident_without_kw_type, schema: [ident_without_kw_type] }; + create_definitions?: create_db_definition + } + => AstStatement + */ + const keyword = k.toLowerCase() + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword, + if_not_exists:ife, + [keyword]: { db: t.schema, schema: t.name }, + create_definitions: c, + } + } + } +view_with + = KW_WITH __ c:("CASCADED"i / "LOCAL"i) __ "CHECK"i __ "OPTION" { + // => string + return `with ${c.toLowerCase()} check option` + } + / KW_WITH __ "CHECK"i __ "OPTION" { + // => string + return 'with check option' + } + +with_view_option + = 'check_option'i __ KW_ASSIGIN_EQUAL __ t:("CASCADED"i / "LOCAL"i) { + // => {type: string; value: string; symbol: string; } + return { type: 'check_option', value: t, symbol: '=' } + } + / k:('security_barrier'i / 'security_invoker'i) __ KW_ASSIGIN_EQUAL __ t:literal_bool { + // => {type: string; value: string; symbol: string; } + return { type: k.toLowerCase(), value: t.value ? 'true' : 'false', symbol: '=' } + } +with_view_options + = head:with_view_option tail:(__ COMMA __ with_view_option)* { + // => with_view_option[] + return createList(head, tail); + } +create_view_stmt + = a:KW_CREATE __ or:(KW_OR __ KW_REPLACE)? __ tp:(KW_TEMP / KW_TEMPORARY)? __ r:KW_RECURSIVE? __ + KW_VIEW __ v:table_name __ c:(LPAREN __ column_list __ RPAREN)? __ wo:(KW_WITH __ LPAREN __ with_view_options __ RPAREN)? __ + KW_AS __ s:select_stmt __ w:view_with? { + /* + export type create_view_stmt_t = { + type: 'create', + keyword: 'view', + replace?: 'or replace', + temporary?: 'temporary' | 'temp', + recursive?: 'recursive', + view: table_name, + columns?: column_list, + select: select_stmt, + with_options?: with_view_options, + with?: string, + } + => AstStatement + */ + v.view = v.table + delete v.table + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword: 'view', + replace: or && 'or replace', + temporary: tp && tp[0].toLowerCase(), + recursive: r && r.toLowerCase(), + columns: c && c[2], + select: s, + view: v, + with_options: wo && wo[4], + with: w, + } + } + } +create_aggregate_opt_required + = 'SFUNC'i __ KW_ASSIGIN_EQUAL __ n:table_name __ COMMA __ 'STYPE'i __ KW_ASSIGIN_EQUAL __ d:data_type { + // => { type: string; symbol: '='; value: expr; }[] + return [ + { + type: 'sfunc', + symbol: '=', + value: { schema: n.db, name: n.table }, + }, + { + type: 'stype', + symbol: '=', + value: d, + } + ] + } + +create_aggregate_opt_optional + = n:ident __ KW_ASSIGIN_EQUAL __ e:(ident / expr) { + // => { type: string; symbol: '='; value: ident | expr; } + return { + type: n, + symbol: '=', + value: typeof e === 'string' ? { type: 'default', value: e } : e + } + } + +create_aggregate_opts + = head:create_aggregate_opt_required tail:(__ COMMA __ create_aggregate_opt_optional)* { + // => create_aggregate_opt_optional[] + return createList(head, tail) + } + +create_aggregate_stmt + = a:KW_CREATE __ or:(KW_OR __ KW_REPLACE)? __ t:'AGGREGATE'i __ s:table_name __ LPAREN __ as:aggregate_signature __ RPAREN __ LPAREN __ opts:create_aggregate_opts __ RPAREN { + /* + export type create_aggregate_stmt_t = { + type: 'create', + keyword: 'aggregate', + replace?: 'or replace', + name: table_name, + args?: aggregate_signature, + options: create_aggregate_opt_optional[] + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'create', + keyword: 'aggregate', + name: { schema: s.db, name: s.table }, + args: { + parentheses: true, + expr: as, + orderby: as.orderby + }, + options: opts + } + }; + } +column_data_type + = c:column_ref __ d:data_type { + // => { column: column_ref; definition: data_type; } + return { + column: c, + definition: d, + } + } +column_data_type_list + = head:column_data_type tail:(__ COMMA __ column_data_type)* { + // => column_data_type[] + return createList(head, tail) + } +func_returns + = 'RETURNS'i __ k:'SETOF'i? __ t:(data_type / table_name) { + // => { type: "returns"; keyword?: "setof"; expr: data_type; } + return { + type: 'returns', + keyword: k, + expr: t + } + } + / 'RETURNS'i __ KW_TABLE __ LPAREN __ e:column_data_type_list __ RPAREN { + // => { type: "returns"; keyword?: "table"; expr: column_data_type_list; } + return { + type: 'returns', + keyword: 'table', + expr: e + } + } + +declare_variable_item + = n:ident_name &{ return n.toLowerCase() !== 'begin' } __ c:'CONSTANT'i? __ d:data_type __ collate:collate_expr? __ nu:(KW_NOT __ KW_NULL)? __ expr:((KW_DEFAULT / ':=')? __ (&'BEGIN'i / literal / expr))? __ s:SEMICOLON? { + // => { keyword: 'variable'; name: string, constant?: string; datatype: data_type; collate?: collate_expr; not_null?: string; default?: { type: 'default'; keyword: string; value: literal | expr; }; } + return { + keyword: 'variable', + name: n, + constant: c, + datatype: d, + collate, + not_null: nu && 'not null', + definition: expr && expr[0] && { + type: 'default', + keyword: expr[0], + value: expr[2] + }, + } + } +declare_variables + = head:declare_variable_item tail:(__ declare_variable_item)* { + // => declare_variable_item[] + return createList(head, tail, 1) +} +declare_stmt + = 'DECLARE'i __ vars:declare_variables { + /* + export type declare_stmt_t = { type: 'declare'; declare: declare_variable_item[]; } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'declare', + declare: vars, + symbol: ';', + } + } + } + +create_func_opt + = 'LANGUAGE' __ ln:ident_name __ { + // => literal_string + return { + prefix: 'LANGUAGE', + type: 'default', + value: ln + } + } + / 'TRANSORM'i __ ft:('FOR' __ 'TYPE' __ ident_name)? __ { + // => literal_string + if (!ft) return { type: 'origin', value: 'TRANSORM' } + return { + prefix: ['TRANSORM', ft[0].toUpperCase(), ft[2].toUpperCase()].join(' '), + type: 'default', + value: ft[4] + } + } + / i:('WINDOW'i / 'IMMUTABLE'i / 'STABLE'i / 'VOLATILE'i / 'STRICT'i) __ { + // => literal_string + return { + type: 'origin', + value: i + } + } + / n:('NOT'i)? __ 'LEAKPROOF'i __ { + // => literal_string + return { + type: 'origin', + value: [n, 'LEAKPROOF'].filter(v => v).join(' ') + } + } + / i:('CALLED'i / ('RETURNS'i __ 'NULL'i))? __ 'ON'i __ 'NULL'i __ 'INPUT'i __ { + // => literal_string + if (Array.isArray(i)) i = [i[0], i[2]].join(' ') + return { + type: 'origin', + value: `${i} ON NULL INPUT` + } + } + / e:('EXTERNAL'i)? __ 'SECURITY'i __ i:('INVOKER'i / 'DEFINER'i) __ { + // => literal_string + return { + type: 'origin', + value: [e, 'SECURITY', i].filter(v => v).join(' ') + } + } + / 'PARALLEL'i __ i:('UNSAFE'i / 'RESTRICTED'i / 'SAFE'i) __ { + // => literal_string + return { + type: 'origin', + value: ['PARALLEL', i].join(' ') + } + } + / KW_AS __ c:[^ \s\t\n\r]+ __ de:declare_stmt? __ b:('BEGIN'i)? __ s:multiple_stmt __ e:KW_END? &{ return (b && e) || (!b && !e) } __ SEMICOLON? __ l:[^ \s\t\n\r;]+ __ { + // => { type: 'as'; begin?: string; declare?: declare_stmt; expr: multiple_stmt; end?: string; symbol: string; } + const start = c.join('') + const end = l.join('') + if (start !== end) throw new Error(`start symbol '${start}'is not same with end symbol '${end}'`) + return { + type: 'as', + declare: de && de.ast, + begin: b, + expr: Array.isArray(s.ast) ? s.ast.flat() : [s.ast], + end: e && e[0], + symbol: start, + } + } + / p:('COST'i / 'ROWS'i) __ n:literal_numeric __ { + // => literal_numeric + n.prefix = p + return n + } + / 'SUPPORT'i __ n:proc_func_name __ { + // => literal_string + return { + prefix: 'support', + type: 'default', + value: [n.schema && n.schema.value, n.name.value].filter(v => v).join('.') + } + } + / KW_SET __ ca:ident_name __ e:((('TO'i / '=') __ ident_list) / (KW_FROM __ 'CURRENT'i))? __ { + // => { type: "set"; parameter: ident_name; value?: { prefix: string; expr: expr }} + let value + if (e) { + const val = Array.isArray(e[2]) ? e[2] : [e[2]] + value = { + prefix: e[0], + expr: val.map(v => ({ type: 'default', value: v })) + } + } + return { + type: 'set', + parameter: ca, + value, + } + } + +create_function_stmt + = a:KW_CREATE __ + or:(KW_OR __ KW_REPLACE)? __ + t:'FUNCTION'i __ + c:table_name __ LPAREN __ args:alter_func_args? __ RPAREN __ + r:func_returns? __ + fo:create_func_opt* __ SEMICOLON? __ { + /* + export type create_function_stmt_t = { + type: 'create'; + replace?: string; + name: { schema?: string; name: string }; + args?: alter_func_args; + returns?: func_returns; + keyword: 'function'; + options?: create_func_opt[]; + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + args: args || [], + type: 'create', + replace: or && 'or replace', + name: { schema: c.db, name: c.table }, + returns: r, + keyword: t && t.toLowerCase(), + options: fo || [], + } + } + } + +create_type_stmt_option + = KW_AS __ r:(KW_ENUM / 'RANGE'i) __ LPAREN __ e:expr_list? __ RPAREN { + // => { as: 'as'; resource: string; create_definitions: expr_list | create_column_definition_list; } + e.parentheses = true + return { + as: 'as', + resource: r.toLowerCase(), + create_definitions: e, + } + } + / KW_AS __ LPAREN __ e:create_column_definition_list? __ RPAREN { + // => ignore + return { + as: 'as', + create_definitions: e, + } + } + +create_type_stmt + = a:KW_CREATE __ k:'TYPE'i __ s:table_name __ e:create_type_stmt_option? { + /* + export type create_type_stmt_t = { + type: 'create', + keyword: 'type', + name: { schema: string; name: string }, + as?: string, + resource?: string, + create_definitions?: expr_list | create_column_definition_list; + } + => AstStatement + */ + + customTypes.add([s.db, s.table].filter(v => v).join('.')) + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword: k.toLowerCase(), + name: { schema: s.db, name: s.table }, + ...e, + } + } + } + +create_domain_stmt + = a:KW_CREATE __ k:'DOMAIN'i __ s:table_name __ as:KW_AS? __ d:data_type __ ce:collate_expr? __ de:default_expr? __ ccc: create_constraint_check? { + /* + export type create_domain_stmt_t = { + type: 'create', + keyword: 'domain', + domain: { schema: string; name: string }, + as?: string, + target: data_type, + create_definitions?: any[] + } + => AstStatement + */ + if (ccc) ccc.type = 'constraint' + const definitions = [ce, de, ccc].filter(v => v) + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword: k.toLowerCase(), + domain: { schema: s.db, name: s.table }, + as: as && as[0] && as[0].toLowerCase(), + target: d, + create_definitions: definitions, + } + } + } +create_table_stmt + = a:KW_CREATE __ + tp:KW_TEMPORARY? __ + KW_TABLE __ + ife:if_not_exists_stmt? __ + t:table_ref_list __ + po:create_table_partition_of { + // => AstStatement + if(t) t.forEach(tt => tableList.add(`create::${tt.db}::${tt.table}`)); + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword: 'table', + temporary: tp && tp[0].toLowerCase(), + if_not_exists: ife, + table: t, + partition_of: po + } + } + } + / a:KW_CREATE __ + tp:KW_TEMPORARY? __ + KW_TABLE __ + ife:if_not_exists_stmt? __ + t:table_ref_list __ + c:create_table_definition? __ + to:table_options? __ + ir: (KW_IGNORE / KW_REPLACE)? __ + as:KW_AS? __ + qe:union_stmt? { + /* + export type create_table_stmt_node = create_table_stmt_node_simple | create_table_stmt_node_like; + export interface create_table_stmt_node_base { + type: 'create'; + keyword: 'table'; + temporary?: 'temporary'; + if_not_exists?: 'if not exists'; + table: table_ref_list; + } + export interface create_table_stmt_node_simple extends create_table_stmt_node_base{ + ignore_replace?: 'ignore' | 'replace'; + as?: 'as'; + query_expr?: union_stmt_node; + create_definitions?: create_table_definition; + table_options?: table_options; + } + => AstStatement + */ + if(t) t.forEach(tt => tableList.add(`create::${tt.db}::${tt.table}`)); + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword: 'table', + temporary: tp && tp[0].toLowerCase(), + if_not_exists:ife, + table: t, + ignore_replace: ir && ir[0].toLowerCase(), + as: as && as[0].toLowerCase(), + query_expr: qe && qe.ast, + create_definitions: c, + table_options: to + } + } + } + / a:KW_CREATE __ + tp:KW_TEMPORARY? __ + KW_TABLE __ + ife:if_not_exists_stmt? __ + t:table_ref_list __ + lt:create_like_table { + /* + + export interface create_table_stmt_node_like extends create_table_stmt_node_base{ + like: create_like_table; + } + => AstStatement + */ + if(t) t.forEach(tt => tableList.add(`create::${tt.db}::${tt.table}`)); + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword: 'table', + temporary: tp && tp[0].toLowerCase(), + if_not_exists:ife, + table: t, + like: lt + } + } + } + +create_sequence + = a:KW_CREATE __ + tp:(KW_TEMPORARY / KW_TEMP)? __ + KW_SEQUENCE __ + ife:if_not_exists_stmt? __ + t:table_name __ as:(KW_AS __ alias_ident)?__ + c:create_sequence_definition_list? { + /* + export type create_sequence_stmt = { + type: 'create', + keyword: 'sequence', + temporary?: 'temporary' | 'temp', + if_not_exists?: 'if not exists', + table: table_ref_list, + create_definitions?: create_sequence_definition_list + } + => AstStatement + */ + t.as = as && as[2] + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword: 'sequence', + temporary: tp && tp[0].toLowerCase(), + if_not_exists:ife, + sequence: [t], + create_definitions: c, + } + } + } + +sequence_definition_increment + = k:'INCREMENT'i __ b:KW_BY? __ n:literal_numeric { + /* + export type sequence_definition = { "resource": "sequence", prefix?: string,value: literal | column_ref } + => sequence_definition + */ + return { + resource: 'sequence', + prefix: b ? `${k.toLowerCase()} by` : k.toLowerCase(), + value: n + } + } +sequence_definition_minval + = k:'MINVALUE'i __ n:literal_numeric { + // => sequence_definition + return { + resource: 'sequence', + prefix: k.toLowerCase(), + value: n + } + } + / 'NO'i __ 'MINVALUE'i { + // => sequence_definition + return { + resource: 'sequence', + value: { + type: 'origin', + value: 'no minvalue' + } + } + } + +sequence_definition_maxval + = k:'MAXVALUE'i __ n:literal_numeric { + // => sequence_definition + return { + resource: 'sequence', + prefix: k.toLowerCase(), + value: n + } + } + / 'NO'i __ 'MAXVALUE'i { + // => sequence_definition + return { + resource: 'sequence', + value: { + type: 'origin', + value: 'no maxvalue' + } + } + } + +sequence_definition_start + = k:'START'i __ w:KW_WITH? __ n:literal_numeric { + // => sequence_definition + return { + resource: 'sequence', + prefix: w ? `${k.toLowerCase()} with` : k.toLowerCase(), + value: n + } + } + +sequence_definition_cache + = k:'CACHE'i __ n:literal_numeric { + // => sequence_definition + return { + resource: 'sequence', + prefix: k.toLowerCase(), + value: n + } + } + +sequence_definition_cycle + = n:'NO'i? __ 'CYCLE'i { + // => sequence_definition + return { + resource: 'sequence', + value: { + type: 'origin', + value: n ? 'no cycle' : 'cycle' + } + } + } + +sequence_definition_owned + = 'OWNED'i __ KW_BY __ 'NONE'i { + // => sequence_definition + return { + resource: 'sequence', + prefix: 'owned by', + value: { + type: 'origin', + value: 'none' + } + } + } + / n:'OWNED'i __ KW_BY __ col:column_ref { + // => sequence_definition + return { + resource: 'sequence', + prefix: 'owned by', + value: col + } + } + +create_sequence_definition + = sequence_definition_increment + / sequence_definition_minval + / sequence_definition_maxval + / sequence_definition_start + / sequence_definition_cache + / sequence_definition_cycle + / sequence_definition_owned + +create_sequence_definition_list + = head: create_sequence_definition tail:(__ create_sequence_definition)* { + // => create_sequence_definition[] + return createList(head, tail, 1) +} + +create_index_stmt + = a:KW_CREATE __ + kw:KW_UNIQUE? __ + t:KW_INDEX __ + co:KW_CONCURRENTLY? __ + n:ident? __ + on:KW_ON __ + ta:table_name __ + um:index_type? __ + LPAREN __ cols:column_order_list __ RPAREN __ + wr:(KW_WITH __ LPAREN __ index_options_list __ RPAREN)? __ + ts:(KW_TABLESPACE __ ident_name)? __ + w:where_clause? __ { + /* + export interface create_index_stmt_node { + type: 'create'; + index_type?: 'unique'; + keyword: 'index'; + concurrently?: 'concurrently'; + index: string; + on_kw: string; + table: table_name; + index_using?: index_type; + index_columns: column_order[]; + with?: index_option[]; + with_before_where: true; + tablespace?: {type: 'origin'; value: string; } + where?: where_clause; + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + index_type: kw && kw.toLowerCase(), + keyword: t.toLowerCase(), + concurrently: co && co.toLowerCase(), + index: n, + on_kw: on[0].toLowerCase(), + table: ta, + index_using: um, + index_columns: cols, + with: wr && wr[4], + with_before_where: true, + tablespace: ts && { type: 'origin', value: ts[2] }, + where: w, + } + } + } + +column_order_list + = head:column_order tail:(__ COMMA __ column_order)* { + // => column_order[] + return createList(head, tail) + } + +column_order + = c:expr __ + ca:collate_expr? __ + op:ident? __ + o:(KW_ASC / KW_DESC)? __ + nf:('NULLS'i __ ('FIRST'i / 'LAST'i))? { + /* + => { + collate: collate_expr; + opclass: ident; + order: 'asc' | 'desc'; + nulls: 'nulls last' | 'nulls first'; + } + */ + return { + ...c, + collate: ca, + opclass: op, + order_by: o && o.toLowerCase(), + nulls: nf && `${nf[0].toLowerCase()} ${nf[2].toLowerCase()}`, + } + } + +create_like_table_simple + = KW_LIKE __ t: table_ref_list { + // => { type: 'like'; table: table_ref_list; } + return { + type: 'like', + table: t + } + } +create_like_table + = create_like_table_simple + / LPAREN __ e:create_like_table __ RPAREN { + // => create_like_table_simple & { parentheses?: boolean; } + e.parentheses = true; + return e; + } + +for_values_item + = KW_FROM __ LPAREN __ f:literal_string __ RPAREN __ KW_TO __ LPAREN __ t:literal_string __ RPAREN { + /* => { + type: 'for_values_item'; + keyword: 'from'; + from: literal_string; + to: literal_string; + } */ + return { + type: 'for_values_item', + keyword: 'from', + from: f, + to: t, + } + } + / KW_IN __ LPAREN __ e:expr_list __ RPAREN { + /* => { + type: 'for_values_item'; + keyword: 'in'; + in: expr_list; + } */ + return { + type: 'for_values_item', + keyword: 'in', + in: e, + } + } + / KW_WITH __ LPAREN __ 'MODULUS'i __ m:literal_numeric __ COMMA __ 'REMAINDER'i __ r:literal_numeric __ RPAREN { + /* => { + type: 'for_values_item'; + keyword: 'with'; + modulus: literal_numeric; + remainder: literal_numeric; + } */ + return { + type: 'for_values_item', + keyword: 'with', + modulus: m, + remainder: r, + } + } + +for_values + = 'FOR'i __ KW_VALUES __ fvi:for_values_item { + /* => { + type: 'for_values'; + keyword: 'for values'; + expr: for_values_item; + } */ + return { + type: 'for_values', + keyword: 'for values', + expr: fvi + } + } +create_table_partition_of + = KW_PARTITION __ 'OF'i __ t:table_name __ fv:for_values __ ts:(KW_TABLESPACE __ ident_without_kw_type)? { + /* => { + type: 'partition_of'; + keyword: 'partition of'; + table: table_name; + for_values: for_values; + tablespace: ident_without_kw_type | undefined; + } */ + return { + type: 'partition_of', + keyword: 'partition of', + table: t, + for_values: fv, + tablespace: ts && ts[2] + } + } + +create_table_definition + = LPAREN __ head:create_definition tail:(__ COMMA __ create_definition)* __ RPAREN { + // => create_definition[] + return createList(head, tail); + } + +create_definition + = create_column_definition + / create_index_definition + / create_fulltext_spatial_index_definition + / create_constraint_definition + +column_definition_opt + = column_constraint + / a:('AUTO_INCREMENT'i) { + // => { auto_increment: 'auto_increment'; } + return { auto_increment: a.toLowerCase() } + } + / 'UNIQUE'i __ k:('KEY'i)? { + // => { unique: 'unique' | 'unique key'; } + const sql = ['unique'] + if (k) sql.push(k) + return { unique: sql.join(' ').toLowerCase('') } + } + / p:('PRIMARY'i)? __ 'KEY'i { + // => { unique: 'key' | 'primary key'; } + const sql = [] + if (p) sql.push('primary') + sql.push('key') + return { primary_key: sql.join(' ').toLowerCase('') } + } + / co:keyword_comment { + // => { comment: keyword_comment; } + return { comment: co } + } + / ca:collate_expr { + // => { collate: collate_expr; } + return { collate: ca } + } + / cf:column_format { + // => { column_format: column_format; } + return { column_format: cf } + } + / s:storage { + // => { storage: storage } + return { storage: s } + } + / re:reference_definition { + // => { reference_definition: reference_definition; } + return { reference_definition: re } + } + / ck:check_constraint_definition { + // => { check: check_constraint_definition; } + return { check: ck } + } + / t:create_option_character_set_kw __ s:KW_ASSIGIN_EQUAL? __ v:ident_without_kw_type { + // => { character_set: { type: 'CHARACTER SET'; symbol: '=' | null; value: ident_without_kw_type; } } + return { character_set: { type: t, value: v, symbol: s }} + } + +column_definition_opt_list + = head:column_definition_opt __ tail:(__ column_definition_opt)* { + /* + => { + nullable?: column_constraint['nullable']; + default_val?: column_constraint['default_val']; + auto_increment?: 'auto_increment'; + unique?: 'unique' | 'unique key'; + primary?: 'key' | 'primary key'; + comment?: keyword_comment; + collate?: collate_expr; + column_format?: column_format; + storage?: storage; + reference_definition?: reference_definition; + } + */ + let opt = head + for (let i = 0; i < tail.length; i++) { + opt = { ...opt, ...tail[i][1] } + } + return opt + } + +create_column_definition_list + = head:create_column_definition tail:(__ COMMA __ create_column_definition)* { + // => create_column_definition[] + return createList(head, tail) + } + +create_column_definition + = c:column_ref __ + d:(data_type / double_quoted_ident) __ + cdo:column_definition_opt_list? { + /* + => { + column: column_ref; + definition: data_type; + nullable: column_constraint['nullable']; + default_val: column_constraint['default_val']; + auto_increment?: 'auto_increment'; + unique?: 'unique' | 'unique key'; + primary?: 'key' | 'primary key'; + comment?: keyword_comment; + collate?: collate_expr; + column_format?: column_format; + storage?: storage; + reference_definition?: reference_definition; + resource: 'column'; + } + */ + columnList.add(`create::${c.table}::${c.column.expr.value}`) + if (d.type === 'double_quote_string') d = { dataType: `"${d.value}"` } + return { + column: c, + definition: d, + resource: 'column', + ...(cdo || {}) + } + } + +column_constraint + = n:constraint_name { + // => { constraint: constraint_name; } + return { constraint: n } + } + / n:(literal_not_null / literal_null) __ df:default_expr? { + // => { nullable: literal_null | literal_not_null; default_val: default_expr; } + if (n && !n.value) n.value = 'null' + return { + default_val: df, + nullable: n + } + } + / df:default_expr __ n:(literal_not_null / literal_null)? { + // => { nullable: literal_null | literal_not_null; default_val: default_expr; } + if (n && !n.value) n.value = 'null' + return { + default_val: df, + nullable: n + } + } + +collate_expr + = KW_COLLATE __ ca:ident_type __ s:KW_ASSIGIN_EQUAL __ t:ident_type { + // => { type: 'collate'; keyword: 'collate'; collate: { symbol: '=' ; name: ident_type; value: ident_type; }} + return { + type: 'collate', + keyword: 'collate', + collate: { + name: ca, + symbol: s, + value: t + } + } + } + / KW_COLLATE __ s:KW_ASSIGIN_EQUAL? __ ca:ident_type { + // => { type: 'collate'; keyword: 'collate'; collate: { symbol: '=' | null ; name: ident_type; }} + return { + type: 'collate', + keyword: 'collate', + collate: { + name: ca, + symbol: s, + } + } + } +column_format + = k:'COLUMN_FORMAT'i __ f:('FIXED'i / 'DYNAMIC'i / 'DEFAULT'i) { + // => { type: 'column_format'; value: 'fixed' | 'dynamic' | 'default'; } + return { + type: 'column_format', + value: f.toLowerCase() + } + } +storage + = k:'STORAGE'i __ s:('DISK'i / 'MEMORY'i) { + // => { type: 'storage'; value: 'disk' | 'memory' } + return { + type: 'storage', + value: s.toLowerCase() + } + } +default_arg_expr + = kw:(KW_DEFAULT / KW_ASSIGIN_EQUAL)? __ ce:expr { + // => { type: 'default'; keyword: string, value: expr; } + return { + type: 'default', + keyword: kw && kw[0], + value: ce + } + } +default_expr + = KW_DEFAULT __ ce:expr { + // => { type: 'default'; value: expr; } + return { + type: 'default', + value: ce + } + } +drop_index_opt + = head:(ALTER_ALGORITHM / ALTER_LOCK) tail:(__ (ALTER_ALGORITHM / ALTER_LOCK))* { + // => (ALTER_ALGORITHM | ALTER_LOCK)[] + return createList(head, tail, 1) + } +drop_stmt + = a:KW_DROP __ + r:KW_TABLE __ + ife:if_exists? __ + t:table_ref_list { + /* + export interface drop_stmt_node { + type: 'drop'; + keyword: 'table'; + prefix?: string; + name: table_ref_list; + } + => AstStatement + */ + if(t) t.forEach(tt => tableList.add(`${a}::${tt.db}::${tt.table}`)); + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a.toLowerCase(), + keyword: r.toLowerCase(), + prefix: ife, + name: t + } + }; + } + / a:KW_DROP __ + r:KW_INDEX __ + cu:KW_CONCURRENTLY? __ + ife:if_exists? __ + i:column_ref __ + op:('CASCADE'i / 'RESTRICT'i)? { + /* + export interface drop_index_stmt_node { + type: 'drop'; + prefix?: string; + keyword: string; + name: column_ref; + options?: 'cascade' | 'restrict'; + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a.toLowerCase(), + keyword: r.toLowerCase(), + prefix: [cu, ife].filter(v => v).join(' '), + name: i, + options: op && [{ type: 'origin', value: op }] + } + }; + } + +truncate_table_name + = t:table_name __ s:STAR? { + // => table_name & { suffix?: string } + tableList.add(`truncate::${t.db}::${t.table}`) + if (s) t.suffix = s + return t + } +truncate_table_name_list + = head:truncate_table_name tail:(__ COMMA __ truncate_table_name)* { + // => truncate_table_name[] + return createList(head, tail) + } +truncate_stmt + = a:KW_TRUNCATE __ + kw:KW_TABLE? __ + on: 'ONLY'i? __ + t:truncate_table_name_list __ + id: (('RESTART'i / 'CONTINUE'i) __ 'IDENTITY'i)? __ + op:('CASCADE'i / 'RESTRICT'i)? { + /* + export interface truncate_stmt_node { + type: 'trucate'; + keyword: 'table'; + prefix?: string; + name: table_ref_list; + suffix: string[]; + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a.toLowerCase(), + keyword: kw && kw.toLowerCase() || 'table', + prefix: on, + name: t, + suffix: [id && [id[0], id[2]].join(' '), op].filter(v => v).map(v => ({ type: 'origin', value: v })) + } + } + } + +use_stmt + = KW_USE __ + d:ident { + /* + export interface use_stmt_node { + type: 'use'; + db: ident; + } + => AstStatement + */ + tableList.add(`use::${d}::null`); + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'use', + db: d + } + }; + } + +aggregate_signature + = STAR { + // => { name: "*" } + return [ + { + name: '*' + } + ] + } + / s:alter_func_args? __ KW_ORDER __ KW_BY __ o:alter_func_args { + // => alter_func_args + const ans = s || [] + ans.orderby = o + return ans + } + / alter_func_args + +alter_func_argmode + = t:(KW_IN / 'OUT'i / 'VARIADIC'i) { + // => "IN" | "OUT" | "VARIADIC" + return t.toUpperCase() + } + +alter_func_arg_item + = m:alter_func_argmode? __ ad:data_type __ de:default_arg_expr? { + // => { mode?: string; name?: string; type: data_type; default: default_arg_expr; } + return { + mode: m, + type: ad, + default: de, + } + } + / m:alter_func_argmode? __ an:ident_name __ ad:data_type __ de:default_arg_expr? { + // => { mode?: string; name?: string; type: data_type; default: default_arg_expr; } + return { + mode: m, + name: an, + type: ad, + default: de, + } + } +alter_func_args + = head:alter_func_arg_item tail:(__ COMMA __ alter_func_arg_item)* { + // => alter_func_arg_item[] + return createList(head, tail) + } +alter_aggregate_stmt + = KW_ALTER __ t:'AGGREGATE'i __ s:table_name __ LPAREN __ as:aggregate_signature __ RPAREN __ ac:(ALTER_RENAME / ALTER_OWNER_TO / ALTER_SET_SCHEMA) { + // => AstStatement + const keyword = t.toLowerCase() + ac.resource = keyword + ac[keyword] = ac.table + delete ac.table + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'alter', + keyword, + name: { schema: s.db, name: s.table }, + args: { + parentheses: true, + expr: as, + orderby: as.orderby + }, + expr: ac + } + }; + } +alter_function_stmt + = KW_ALTER __ t:'FUNCTION'i __ s:table_name __ ags:(LPAREN __ alter_func_args? __ RPAREN)? __ ac:(ALTER_RENAME / ALTER_OWNER_TO / ALTER_SET_SCHEMA) { + // => AstStatement + const keyword = t.toLowerCase() + ac.resource = keyword + ac[keyword] = ac.table + delete ac.table + const args = {} + if (ags && ags[0]) args.parentheses = true + args.expr = ags && ags[2] + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'alter', + keyword, + name: { schema: s.db, name: s.table }, + args, + expr: ac + } + }; + } +alter_domain_type_stmt + = KW_ALTER __ t:('DOMAIN'i / 'TYPE'i) __ s:table_name __ ac:(ALTER_RENAME / ALTER_OWNER_TO / ALTER_SET_SCHEMA) { + /* + export interface alter_resource_stmt_node { + type: 'alter'; + keyword: 'domain' | 'type', + name: string | { schema: string, name: string }; + args?: { parentheses: true; expr?: alter_func_args; orderby?: alter_func_args; }; + expr: alter_rename_owner; + } + => AstStatement + */ + const keyword = t.toLowerCase() + ac.resource = keyword + ac[keyword] = ac.table + delete ac.table + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'alter', + keyword, + name: { schema: s.db, name: s.table }, + expr: ac + } + }; + } + +alter_schema_stmt + = KW_ALTER __ t:KW_SCHEMA __ s:ident_name __ ac:(ALTER_RENAME / ALTER_OWNER_TO / ALTER_SET_SCHEMA) { + // => AstStatement + const keyword = t.toLowerCase() + ac.resource = keyword + ac[keyword] = ac.table + delete ac.table + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'alter', + keyword, + schema: s, + expr: ac + } + }; + } + +alter_table_stmt + = KW_ALTER __ + KW_TABLE __ + ife:if_exists? __ + o:'only'i? __ + t:table_ref_list __ + e:alter_action_list { + /* + export interface alter_table_stmt_node { + type: 'alter'; + table: table_ref_list; + keyword: 'table'; + if_exists: if_exists; + prefix?: literal_string; + expr: alter_action_list; + } + => AstStatement + */ + if (t && t.length > 0) t.forEach(table => tableList.add(`alter::${table.db}::${table.table}`)); + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'alter', + keyword: 'table', + if_exists: ife, + prefix: o && { type: 'origin', value: o }, + table: t, + expr: e + } + }; + } + +alter_action_list + = head:alter_action tail:(__ COMMA __ alter_action)* { + // => alter_action[] + return createList(head, tail); + } + +alter_action + = ALTER_ADD_COLUMN + / ALTER_ADD_CONSTRAINT + / ALTER_DROP_COLUMN + / ALTER_ADD_INDEX_OR_KEY + / ALTER_ADD_FULLETXT_SPARITAL_INDEX + / ALTER_RENAME + / ALTER_ALGORITHM + / ALTER_LOCK + / ALTER_COLUMN_DATA_TYPE + / ALTER_COLUMN_DEFAULT + / ALTER_COLUMN_NOT_NULL + +ALTER_ADD_COLUMN + = KW_ADD __ + kc:KW_COLUMN? __ + ife:if_not_exists_stmt? __ + cd:create_column_definition { + /* + => { + action: 'add'; + keyword: KW_COLUMN; + resource: 'column'; + if_not_exists: ife; + type: 'alter'; + } & create_column_definition; + */ + return { + action: 'add', + if_not_exists: ife, + ...cd, + keyword: kc, + resource: 'column', + type: 'alter', + } + } + +ALTER_DROP_COLUMN + = KW_DROP __ + kc:KW_COLUMN? __ + ife:if_exists? __ + c:column_ref { + /* => { + action: 'drop'; + collumn: column_ref; + keyword: KW_COLUMN; + if_exists: if_exists; + resource: 'column'; + type: 'alter'; + } */ + return { + action: 'drop', + column: c, + if_exists: ife, + keyword: kc, + resource: 'column', + type: 'alter', + } + } + +ALTER_ADD_CONSTRAINT + = KW_ADD __ c:create_constraint_definition { + /* => { + action: 'add'; + create_definitions: create_db_definition; + resource: 'constraint'; + type: 'alter'; + } */ + return { + action: 'add', + create_definitions: c, + resource: 'constraint', + type: 'alter', + } + } + +ALTER_ADD_INDEX_OR_KEY + = KW_ADD __ + id:create_index_definition + { + /* => { + action: 'add'; + type: 'alter'; + } & create_index_definition */ + return { + action: 'add', + type: 'alter', + ...id, + } + } + +ALTER_RENAME + = KW_RENAME __ kw:(KW_TO / KW_AS)? __ tn:ident { + /* + export interface alter_rename_owner { + action: string; + type: 'alter'; + resource: string; + keyword?: 'to' | 'as'; + [key: string]: ident | undefined; + } + => AstStatement + */ + return { + action: 'rename', + type: 'alter', + resource: 'table', + keyword: kw && kw[0].toLowerCase(), + table: tn + } + } + +ALTER_OWNER_TO + = 'OWNER'i __ KW_TO __ tn:(ident / 'CURRENT_ROLE'i / 'CURRENT_USER'i / 'SESSION_USER'i) { + // => AstStatement + return { + action: 'owner', + type: 'alter', + resource: 'table', + keyword: 'to', + table: tn + } + } + +ALTER_SET_SCHEMA + = KW_SET __ KW_SCHEMA __ s:ident { + // => AstStatement + return { + action: 'set', + type: 'alter', + resource: 'table', + keyword: 'schema', + table: s + } + } + +ALTER_ALGORITHM + = "ALGORITHM"i __ s:KW_ASSIGIN_EQUAL? __ val:("DEFAULT"i / "INSTANT"i / "INPLACE"i / "COPY"i) { + /* => { + type: 'alter'; + keyword: 'algorithm'; + resource: 'algorithm'; + symbol?: '='; + algorithm: 'DEFAULT' | 'INSTANT' | 'INPLACE' | 'COPY'; + }*/ + return { + type: 'alter', + keyword: 'algorithm', + resource: 'algorithm', + symbol: s, + algorithm: val + } + } + +ALTER_LOCK + = "LOCK"i __ s:KW_ASSIGIN_EQUAL? __ val:("DEFAULT"i / "NONE"i / "SHARED"i / "EXCLUSIVE"i) { + /* => { + type: 'alter'; + keyword: 'lock'; + resource: 'lock'; + symbol?: '='; + lock: 'DEFAULT' | 'NONE' | 'SHARED' | 'EXCLUSIVE'; + }*/ + return { + type: 'alter', + keyword: 'lock', + resource: 'lock', + symbol: s, + lock: val + } + } + +ALTER_COLUMN_DATA_TYPE + = KW_ALTER __ kc:KW_COLUMN? __ c:column_ref __ sd:(KW_SET __ 'data'i)? __ 'type'i __ t:data_type __ co:collate_expr? __ us:(KW_USING __ expr)? { + /* + => { + action: 'alter'; + keyword?: KW_COLUMN; + using?: expr; + type: 'alter'; + } & create_column_definition; + */ + c.suffix = sd ? 'set data type' : 'type' + return { + action: 'alter', + column: c, + keyword: kc, + resource: 'column', + definition: t, + collate: co, + using: us && us[2], + type: 'alter', + } + } + +ALTER_COLUMN_DEFAULT + = KW_ALTER __ kc:KW_COLUMN? __ c:column_ref __ KW_SET __ KW_DEFAULT __ e:expr { + /* => { + action: 'alter'; + keyword?: KW_COLUMN; + default_val?: { type: 'set default', value: expr }; + type: 'alter'; + } & create_column_definition; + */ + return { + action: 'alter', + column: c, + keyword: kc, + resource: 'column', + default_val: { + type: 'set default', + value: e, + }, + type: 'alter', + } + } + / KW_ALTER __ kc:KW_COLUMN? __ c:column_ref __ KW_DROP __ KW_DEFAULT { + /* => { + action: 'alter'; + keyword?: KW_COLUMN; + default_val?: { type: 'set default', value: expr }; + type: 'alter'; + } & create_column_definition; + */ + return { + action: 'alter', + column: c, + keyword: kc, + resource: 'column', + default_val: { + type: 'drop default', + }, + type: 'alter', + } + } + +ALTER_COLUMN_NOT_NULL + = KW_ALTER __ kc:KW_COLUMN? __ c:column_ref __ ac:(KW_SET / KW_DROP) __ n:literal_not_null { + /* => { + action: 'alter'; + keyword?: KW_COLUMN; + nullable: literal_not_null; + type: 'alter'; + } & create_column_definition; + */ + n.action = ac.toLowerCase(); + return { + action: 'alter', + column: c, + keyword: kc, + resource: 'column', + nullable: n, + type: 'alter', + } + } +create_index_definition + = kc:(KW_INDEX / KW_KEY) __ + c:column? __ + t:index_type? __ + de:cte_column_definition __ + id:index_options? __ + { + /* => { + index: column; + definition: cte_column_definition; + keyword: 'index' | 'key'; + index_type?: index_type; + resource: 'index'; + index_options?: index_options; + }*/ + return { + index: c, + definition: de, + keyword: kc.toLowerCase(), + index_type: t, + resource: 'index', + index_options: id, + } + } + +create_fulltext_spatial_index_definition + = p: (KW_FULLTEXT / KW_SPATIAL) __ + kc:(KW_INDEX / KW_KEY)? __ + c:column? __ + de: cte_column_definition __ + id: index_options? __ + { + /* => { + index: column; + definition: cte_column_definition; + keyword: 'fulltext' | 'spatial' | 'fulltext key' | 'spatial key' | 'fulltext index' | 'spatial index'; + index_options?: index_options; + resource: 'index'; + }*/ + return { + index: c, + definition: de, + keyword: kc && `${p.toLowerCase()} ${kc.toLowerCase()}` || p.toLowerCase(), + index_options: id, + resource: 'index', + } + } + +create_constraint_definition + = create_constraint_primary + / create_constraint_unique + / create_constraint_foreign + / create_constraint_check + +constraint_name + = kc:KW_CONSTRAINT __ c:ident? { + // => { keyword: 'constraint'; constraint: ident; } + return { + keyword: kc.toLowerCase(), + constraint: c + } + } +create_constraint_check + = kc:constraint_name? __ p:'CHECK'i __ LPAREN __ e:or_and_where_expr __ RPAREN { + /* => { + constraint?: constraint_name['constraint']; + definition: [or_and_where_expr]; + keyword?: constraint_name['keyword']; + constraint_type: 'check'; + resource: 'constraint'; + }*/ + return { + constraint: kc && kc.constraint, + definition: [e], + constraint_type: p.toLowerCase(), + keyword: kc && kc.keyword, + resource: 'constraint', + } + } +create_constraint_primary + = kc:constraint_name? __ + p:('PRIMARY KEY'i) __ + t:index_type? __ + de:cte_column_definition __ + id:index_options? { + /* => { + constraint?: constraint_name['constraint']; + definition: cte_column_definition; + constraint_type: 'primary key'; + keyword?: constraint_name['keyword']; + index_type?: index_type; + resource: 'constraint'; + index_options?: index_options; + }*/ + return { + constraint: kc && kc.constraint, + definition: de, + constraint_type: p.toLowerCase(), + keyword: kc && kc.keyword, + index_type: t, + resource: 'constraint', + index_options: id, + } + } + +create_constraint_unique + = kc:constraint_name? __ + u:KW_UNIQUE __ + p:(KW_INDEX / KW_KEY)? __ + i:column? __ + t:index_type? __ + de:cte_column_definition __ + id:index_options? { + /* => { + constraint?: constraint_name['constraint']; + definition: cte_column_definition; + constraint_type: 'unique key' | 'unique' | 'unique index'; + keyword?: constraint_name['keyword']; + index_type?: index_type; + resource: 'constraint'; + index_options?: index_options; + }*/ + return { + constraint: kc && kc.constraint, + definition: de, + constraint_type: p && `${u.toLowerCase()} ${p.toLowerCase()}` || u.toLowerCase(), + keyword: kc && kc.keyword, + index_type: t, + index: i, + resource: 'constraint', + index_options: id + } + } + +create_constraint_foreign + = kc:constraint_name? __ + p:('FOREIGN KEY'i) __ + i:column? __ + de:cte_column_definition __ + id:reference_definition? { + /* => { + constraint?: constraint_name['constraint']; + definition: cte_column_definition; + constraint_type: 'FOREIGN KEY'; + keyword: constraint_name['keyword']; + index?: column; + resource: 'constraint'; + reference_definition?: reference_definition; + }*/ + return { + constraint: kc && kc.constraint, + definition: de, + constraint_type: p, + keyword: kc && kc.keyword, + index: i, + resource: 'constraint', + reference_definition: id + } + } + +check_constraint_definition + = kc:constraint_name? __ u:'CHECK'i __ LPAREN __ c:or_and_expr __ RPAREN __ ne:(KW_NOT? __ 'ENFORCED'i)? { + /* => { + constraint_type: 'check'; + keyword: constraint_name['keyword']; + constraint?: constraint_name['constraint']; + definition: [or_and_expr]; + enforced?: 'enforced' | 'not enforced'; + resource: 'constraint'; + }*/ + const enforced = [] + if (ne) enforced.push(ne[0], ne[2]) + return { + constraint_type: u.toLowerCase(), + keyword: kc && kc.keyword, + constraint: kc && kc.constraint, + definition: [c], + enforced: enforced.filter(v => v).join(' ').toLowerCase(), + resource: 'constraint', + } + } + +reference_definition + = kc:KW_REFERENCES __ + t: table_name __ + de:cte_column_definition __ + m:('MATCH FULL'i / 'MATCH PARTIAL'i / 'MATCH SIMPLE'i)? __ + od: on_reference? __ + ou: on_reference? { + /* => { + definition: cte_column_definition; + table: table_ref_list; + keyword: 'references'; + match: 'match full' | 'match partial' | 'match simple'; + on_action: [on_reference?]; + }*/ + return { + definition: de, + table: [t], + keyword: kc.toLowerCase(), + match:m && m.toLowerCase(), + on_action: [od, ou].filter(v => v) + } + } + / oa:on_reference { + /* => { + on_action: [on_reference]; + } + */ + return { + on_action: [oa] + } + } + +on_reference + = KW_ON __ kw:(KW_DELETE / KW_UPDATE) __ ro:reference_option { + // => { type: 'on delete' | 'on update'; value: reference_option; } + return { + type: `on ${kw[0].toLowerCase()}`, + value: ro + } + } + +reference_option + = kw:KW_CURRENT_TIMESTAMP __ LPAREN __ l:expr_list? __ RPAREN { + // => { type: 'function'; name: string; args: expr_list; } + return { + type: 'function', + name: { name: [{ type: 'origin', value: kw }] }, + args: l + } + } + / kc:('RESTRICT'i / 'CASCADE'i / 'SET NULL'i / 'NO ACTION'i / 'SET DEFAULT'i / KW_CURRENT_TIMESTAMP) { + // => 'restrict' | 'cascade' | 'set null' | 'no action' | 'set default' | 'current_timestamp' + return { + type: 'origin', + value: kc.toLowerCase() + } + } + +create_constraint_trigger + = kw: KW_CREATE __ + or:(KW_OR __ KW_REPLACE)? __ + kc:KW_CONSTRAINT? __ + t:('TRIGGER'i) __ + c:ident_name __ + p:('BEFORE'i / 'AFTER'i / 'INSTEAD OF'i) __ + te:trigger_event_list __ + on:'ON'i __ + tn:table_name __ + fr:(KW_FROM __ table_name)? __ + de:trigger_deferrable? __ + fe:trigger_for_row? __ + tw:trigger_when? __ + fc:'EXECUTE'i __ e:('PROCEDURE'i / 'FUNCTION'i) __ + fct:proc_func_call { + /* + => { + type: 'create'; + replace?: string; + constraint?: string; + location: 'before' | 'after' | 'instead of'; + events: trigger_event_list; + table: table_name; + from?: table_name; + deferrable?: trigger_deferrable; + for_each?: trigger_for_row; + when?: trigger_when; + execute: { + keyword: string; + expr: proc_func_call; + }; + constraint_type: 'trigger'; + keyword: 'trigger'; + constraint_kw: 'constraint'; + resource: 'constraint'; + } + */ + return { + type: 'create', + replace: or && 'or replace', + constraint: c, + location: p && p.toLowerCase(), + events: te, + table: tn, + from: fr && fr[2], + deferrable: de, + for_each: fe, + when: tw, + execute: { + keyword: `execute ${e.toLowerCase()}`, + expr: fct + }, + constraint_type: t && t.toLowerCase(), + keyword: t && t.toLowerCase(), + constraint_kw: kc && kc.toLowerCase(), + resource: 'constraint', + } + } + +trigger_event + = kw:(KW_INSERT / KW_DELETE / KW_TRUNCATE) { + // => { keyword: 'insert' | 'delete' | 'truncate' } + const keyword = Array.isArray(kw) ? kw[0].toLowerCase() : kw.toLowerCase() + return { + keyword, + } + } + / kw:KW_UPDATE __ a:('OF'i __ column_ref_list)? { + // => { keyword: 'update'; args?: { keyword: 'of', columns: column_ref_list; }} + return { + keyword: kw && kw[0] && kw[0].toLowerCase(), + args: a && { keyword: a[0], columns: a[2] } || null + } + } + +trigger_event_list + = head:trigger_event tail:(__ KW_OR __ trigger_event)* { + // => trigger_event[]; + return createList(head, tail) + } + +trigger_deferrable + = kw:(('NOT'i)? __ 'DEFERRABLE'i) __ args:('INITIALLY IMMEDIATE'i / 'INITIALLY DEFERRED'i) { + // => { keyword: 'deferrable' | 'not deferrable'; args: 'initially immediate' | 'initially deferred' } + return { + keyword: kw && kw[0] ? `${kw[0].toLowerCase()} deferrable` : 'deferrable', + args: args && args.toLowerCase(), + } + } + +trigger_for_row + = kw:'FOR'i __ e:('EACH'i)? __ ob:('ROW'i / 'STATEMENT'i) { + // => { keyword: 'for' | 'for each'; args: 'row' | 'statement' } + return { + keyword: e ? `${kw.toLowerCase()} ${e.toLowerCase()}` : kw.toLowerCase(), + args: ob.toLowerCase() + } + } + +trigger_when + = KW_WHEN __ LPAREN __ condition:expr __ RPAREN { + // => { type: 'when'; cond: expr; parentheses: true; } + return { + type: 'when', + cond: condition, + parentheses: true, + } + } + +table_options + = head:table_option tail:(__ COMMA? __ table_option)* { + // => table_option[] + return createList(head, tail) + } + +create_option_character_set_kw + = 'CHARACTER'i __ 'SET'i { + // => string + return 'CHARACTER SET' + } + +create_option_character_set + = kw:KW_DEFAULT? __ t:(create_option_character_set_kw / 'CHARSET'i / 'COLLATE'i) __ s:(KW_ASSIGIN_EQUAL)? __ v:ident_without_kw_type { + /* => { + keyword: 'character set' | 'charset' | 'collate' | 'default character set' | 'default charset' | 'default collate'; + symbol: '='; + value: ident_without_kw_type; + } */ + return { + keyword: kw && `${kw[0].toLowerCase()} ${t.toLowerCase()}` || t.toLowerCase(), + symbol: s, + value: v + } + } + +table_option + = kw:('AUTO_INCREMENT'i / 'AVG_ROW_LENGTH'i / 'KEY_BLOCK_SIZE'i / 'MAX_ROWS'i / 'MIN_ROWS'i / 'STATS_SAMPLE_PAGES'i) __ s:(KW_ASSIGIN_EQUAL)? __ v:literal_numeric { + /* => { + keyword: 'auto_increment' | 'avg_row_length' | 'key_block_size' | 'max_rows' | 'min_rows' | 'stats_sample_pages'; + symbol: '='; + value: number; // <== literal_numeric['value'] + } */ + return { + keyword: kw.toLowerCase(), + symbol: s, + value: v.value + } + } + / create_option_character_set + / kw:(KW_COMMENT / 'CONNECTION'i) __ s:(KW_ASSIGIN_EQUAL)? __ c:literal_string { + // => { keyword: 'connection' | 'comment'; symbol: '='; value: string; } + return { + keyword: kw.toLowerCase(), + symbol: s, + value: `'${c.value}'` + } + } + / kw:'COMPRESSION'i __ s:(KW_ASSIGIN_EQUAL)? __ v:("'"('ZLIB'i / 'LZ4'i / 'NONE'i)"'") { + // => { keyword: 'compression'; symbol?: '='; value: "'ZLIB'" | "'LZ4'" | "'NONE'" } + return { + keyword: kw.toLowerCase(), + symbol: s, + value: v.join('').toUpperCase() + } + } + / kw:'ENGINE'i __ s:(KW_ASSIGIN_EQUAL)? __ c:ident_name { + // => { keyword: 'engine'; symbol?: '='; value: string; } + return { + keyword: kw.toLowerCase(), + symbol: s, + value: c.toUpperCase() + } + } + / KW_PARTITION __ KW_BY __ v:expr { + // => { keyword: 'partition by'; value: expr; } + return { + keyword: 'partition by', + value: v + } + } + + +ALTER_ADD_FULLETXT_SPARITAL_INDEX + = KW_ADD __ fsid:create_fulltext_spatial_index_definition { + // => create_fulltext_spatial_index_definition & { action: 'add'; type: 'alter' } + return { + action: 'add', + type: 'alter', + ...fsid, + } + } + +rename_stmt + = KW_RENAME __ + KW_TABLE __ + t:table_to_list { + /* + export interface rename_stmt_node { + type: 'rename'; + table: table_to_list; + } + => AstStatement + */ + t.forEach(tg => tg.forEach(dt => dt.table && tableList.add(`rename::${dt.db}::${dt.table}`))) + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'rename', + table: t + } + }; + } + +set_stmt + = KW_SET __ + kw: (KW_GLOBAL / KW_SESSION / KW_LOCAL / KW_PERSIST / KW_PERSIST_ONLY)? __ + a: assign_stmt_list { + /* + export interface set_stmt_node { + type: 'set'; + keyword?: 'GLOBAL' | 'SESSION' | 'LOCAL' | 'PERSIST' | 'PERSIST_ONLY' | undefined; + expr: assign_stmt_list; + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'set', + keyword: kw, + expr: a + } + } + } + +lock_mode + = "IN"i __ + m:("ACCESS SHARE"i / "ROW SHARE"i / "ROW EXCLUSIVE"i / "SHARE UPDATE EXCLUSIVE"i / "SHARE ROW EXCLUSIVE"i / "EXCLUSIVE"i / "ACCESS EXCLUSIVE"i / "SHARE"i) __ + "MODE"i { + // => { mode: string; } + return { + mode: `in ${m.toLowerCase()} mode` + } + } + +lock_stmt + = KW_LOCK __ + k:KW_TABLE? __ + t:table_ref_list __ + lm:lock_mode? __ + nw:("NOWAIT"i)? { + + /* + export interface lock_stmt_node { + type: 'lock'; + keyword: 'lock'; + tables: [[table_base], ...{table: table_ref}[]]; // see table_ref_list + lock_mode?: lock_mode; + nowait?: 'NOWAIT'; + } + => AstStatement + */ + + if (t) t.forEach(tt => tableList.add(`lock::${tt.db}::${tt.table}`)) + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'lock', + keyword: k && k.toLowerCase(), + tables: t.map((table) => ({ table })), + lock_mode: lm, + nowait: nw + } + } + } + +call_stmt + = KW_CALL __ + e: proc_func_call { + /* + export interface call_stmt_node { + type: 'call'; + expr: proc_func_call; + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'call', + expr: e + } + } + } + +show_stmt + = KW_SHOW __ 'TABLES'i { + return { + /* + export interface show_stmt_node { + type: 'show'; + keyword: 'tables' | 'var'; + var?: without_prefix_var_decl; + } + => AstStatement + */ + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'show', + keyword: 'tables' + } + } + } + / KW_SHOW __ c:without_prefix_var_decl { + return { + // => AstStatement + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'show', + keyword: 'var', + var: c, + } + } + } + +deallocate_stmt + = KW_DEALLOCATE __ p:('PREPARE'i)? __ i:(ident_name / KW_ALL) { + return { + /* + export interface deallocate_stmt_node { + type: 'deallocate'; + keyword: 'PREPARE' | undefined; + expr: { type: 'default', value: string } + } + => AstStatement + */ + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'deallocate', + keyword: p, + expr: { type: 'default', value: i } + }, + } + } +priv_type_table + = p:(KW_SELECT / KW_INSERT / KW_UPDATE / KW_DELETE / KW_TRUNCATE / KW_REFERENCES / 'TRIGGER'i) { + /* export interface origin_str_stmt { + type: 'origin'; + value: string; + } + => origin_str_stmt + */ + return { + type: 'origin', + value: Array.isArray(p) ? p[0] : p + } + } +priv_type_sequence + = p:('USAGE'i / KW_SELECT / KW_UPDATE) { + // => origin_str_stmt + return { + type: 'origin', + value: Array.isArray(p) ? p[0] : p + } + } +priv_type_database + = p:(KW_CREATE / 'CONNECT'i / KW_TEMPORARY / KW_TEMP) { + // => origin_str_stmt + return { + type: 'origin', + value: Array.isArray(p) ? p[0] : p + } + } +prive_type_all + = KW_ALL p:(__ 'PRIVILEGES'i)? { + // => origin_str_stmt + return { + type: 'origin', + value: p ? 'all privileges' : 'all' + } + } +prive_type_usage + = p:'USAGE'i { + // => origin_str_stmt + return { + type: 'origin', + value: p + } + } + / prive_type_all +prive_type_execute + = p:'EXECUTE'i { + // => origin_str_stmt + return { + type: 'origin', + value: p + } + } + / prive_type_all +priv_type + = priv_type_table / priv_type_sequence / priv_type_database / prive_type_usage / prive_type_execute +priv_item + = p:priv_type __ c:(LPAREN __ column_ref_list __ RPAREN)? { + // => { priv: priv_type; columns: column_ref_list; } + return { + priv: p, + columns: c && c[2], + } + } +priv_list + = head:priv_item tail:(__ COMMA __ priv_item)* { + // => priv_item[] + return createList(head, tail) + } +object_type + = o:(KW_TABLE / 'SEQUENCE'i / 'DATABASE'i / 'DOMAIN' / 'FUNCTION' / 'PROCEDURE'i / 'ROUTINE'i / 'LANGUAGE'i / 'LARGE'i / 'SCHEMA') { + // => origin_str_stmt + return { + type: 'origin', + value: o.toUpperCase() + } + } + / KW_ALL __ i:('TABLES'i / 'SEQUENCE'i / 'FUNCTIONS'i / 'PROCEDURES'i / 'ROUTINES'i) __ KW_IN __ KW_SCHEMA { + // => origin_str_stmt + return { + type: 'origin', + value: `all ${i} in schema` + } + } +priv_level + = prefix:(ident __ DOT)? __ name:(ident / STAR) { + // => { prefix: string; name: string; } + return { + prefix: prefix && prefix[0], + name, + } + } +priv_level_list + = head:priv_level tail:(__ COMMA __ priv_level)* { + // => priv_level[] + return createList(head, tail) + } +user_or_role + = g:KW_GROUP? __ i:ident { + // => origin_str_stmt + const name = g ? `${group} ${i}` : i + return { + name: { type: 'origin', value: name }, + } + } + / i:('PUBLIC'i / KW_CURRENT_ROLE / KW_CURRENT_USER / KW_SESSION_USER) { + // => origin_str_stmt + return { + name: { type: 'origin', value: i }, + } + } +user_or_role_list + = head:user_or_role tail:(__ COMMA __ user_or_role)* { + // => user_or_role[] + return createList(head, tail) + } +with_grant_option + = KW_WITH __ 'GRANT'i __ 'OPTION'i { + // => origin_str_stmt + return { + type: 'origin', + value: 'with grant option', + } + } +with_admin_option + = KW_WITH __ 'ADMIN'i __ 'OPTION'i { + // => origin_str_stmt + return { + type: 'origin', + value: 'with admin option', + } + } +grant_revoke_keyword + = 'GRANT'i { + // => { type: 'grant' } + return { + type: 'grant' + } + } + / 'REVOKE'i __ i:('GRANT'i __ 'OPTION'i __ 'FOR'i)? { + // => { type: 'revoke'; grant_option_for?: origin_str_stmt; } + return { + type: 'revoke', + grant_option_for: i && { type: 'origin', value: 'grant option for' } + } + } + + +grant_revoke_stmt + = g:grant_revoke_keyword __ pl:priv_list __ KW_ON __ ot:object_type? __ le:priv_level_list __ t:(KW_TO / KW_FROM) &{ + const obj = { revoke: 'from', grant: 'to' } + return obj[g.type].toLowerCase() === t[0].toLowerCase() + } __ to:user_or_role_list __ wo:with_grant_option? { + /* export interface grant_revoke_stmt_t { + type: string; + grant_option_for?: origin_str_stmt; + keyword: 'priv'; + objects: priv_list; + on: { + object_type?: object_type; + priv_level: priv_level_list; + }; + to_from: 'to' | 'from'; + user_or_roles?: user_or_role_list; + with?: with_grant_option; + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + ...g, + keyword: 'priv', + objects: pl, + on: { + object_type: ot, + priv_level: le + }, + to_from: t[0], + user_or_roles: to, + with: wo + } + } + } + / g:grant_revoke_keyword __ o:ident_list __ t:(KW_TO / KW_FROM) &{ + const obj = { revoke: 'from', grant: 'to' } + return obj[g.type].toLowerCase() === t[0].toLowerCase() + } __ to:user_or_role_list __ wo:with_admin_option? { + // => AstStatement + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + ...g, + keyword: 'role', + objects: o.map(name => ({ priv: { type: 'string', value: name }})), + to_from: t[0], + user_or_roles: to, + with: wo + } + } + } +elseif_stmt + = 'ELSEIF'i __ e:expr __ 'THEN'i __ ia:crud_stmt __ s:SEMICOLON? { + // => { type: 'elseif'; boolean_expr: expr; then: crud_stmt; semicolon?: string; } + return { + type: 'elseif', + boolean_expr: e, + then: ia, + semicolon: s + } + + } +elseif_stmt_list + = head:elseif_stmt tail:(__ elseif_stmt)* { + // => elseif_stmt[] + return createList(head, tail, 1) + } +if_else_stmt + = 'IF'i __ ie:expr __ 'THEN'i __ ia:crud_stmt __ s:SEMICOLON? __ ei:elseif_stmt_list? __ el:(KW_ELSE __ crud_stmt)? __ es:SEMICOLON? __ 'END'i __ 'IF'i { + /* export interface if_else_stmt_t { + type: 'if'; + keyword: 'if'; + boolean_expr: expr; + semicolons: string[]; + if_expr: crud_stmt; + elseif_expr: elseif_stmt[]; + else_expr: crud_stmt; + prefix: literal_string; + suffix: literal_string; + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'if', + keyword: 'if', + boolean_expr: ie, + semicolons: [s || '', es || ''], + prefix: { + type: 'origin', + value: 'then' + }, + if_expr: ia, + elseif_expr: ei, + else_expr: el && el[2], + suffix: { + type: 'origin', + value: 'end if', + } + } + } + } +raise_level + // => string + = 'DEBUG'i / 'LOG'i / 'INFO'i / 'NOTICE'i / 'WARNING'i / 'EXCEPTION'i +raise_opt + = KW_USING __ o:('MESSAGE'i / 'DETAIL'i / 'HINT'i / 'ERRCODE'i / 'COLUMN'i / 'CONSTRAINT'i / 'DATATYPE'i / 'TABLE'i / 'SCHEMA'i) __ KW_ASSIGIN_EQUAL __ e:expr es:(__ COMMA __ expr)* { + // => { type: 'using'; option: string; symbol: '='; expr: expr[]; } + const expr = [e] + if (es) es.forEach(ex => expr.push(ex[3])) + return { + type: 'using', + option: o, + symbol: '=', + expr + } + } +raise_item + = format:literal_string e:(__ COMMA __ proc_primary)* { + // => IGNORE + return { + type: 'format', + keyword: format, + expr: e && e.map(ex => ex[3]) + } + } + / 'SQLSTATE'i __ ss:literal_string { + // => IGNORE + return { + type: 'sqlstate', + keyword: { type: 'origin', value: 'SQLSTATE' }, + expr: [ss], + } + } + / n:ident { + // => IGNORE + return { + type: 'condition', + expr: [{ type: 'default', value: n }] + } + } +raise_stmt + = 'RAISE'i __ l:raise_level? __ r:raise_item? __ using:raise_opt? { + /* export interface raise_stmt_t { + type: 'raise'; + level?: string; + raise?: raise_item; + using?: raise_opt; + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'raise', + level: l, + using, + raise: r, + } + } + } +execute_stmt + = 'EXECUTE'i __ name:ident __ a:(LPAREN __ proc_primary_list __ RPAREN)? { + /* export interface execute_stmt_t { + type: 'execute'; + name: string; + args?: { type: expr_list; value: proc_primary_list; } + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'execute', + name, + args: a && { type: 'expr_list', value: a[2] } + } + } + } +for_label + = 'FOR'i { + // => { label?: string; keyword: 'for'; } + return { + label: null, + keyword: 'for', + } + } + / label:ident __ 'FOR'i { + // => IGNORE + return { + label, + keyword: 'for' + } + } +for_loop_stmt + = f:for_label __ target:ident __ KW_IN __ query:select_stmt __ 'LOOP'i __ stmts:multiple_stmt __ KW_END __ 'LOOP'i __ label:ident? &{ + if (f.label && label && f.label === label) return true + if (!f.label && !label) return true + return false + } { + /* export interface for_loop_stmt_t { + type: 'for'; + label?: string + target: string; + query: select_stmt; + stmts: multiple_stmt; + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'for', + label, + target, + query, + stmts: stmts.ast, + } + } + } +transaction_mode_isolation_level + = 'SERIALIZABLE'i { + // => { type: 'origin'; value: string; } + return { + type: 'origin', + value: 'serializable' + } + } + / 'REPEATABLE'i __ 'READ'i { + // => ignore + return { + type: 'origin', + value: 'repeatable read' + } + } + / 'READ'i __ e:('COMMITTED'i / 'UNCOMMITTED'i) { + // => ignore + return { + type: 'origin', + value: `read ${e.toLowerCase()}` + } + } + +transaction_mode + = 'ISOLATION'i __ 'LEVEL'i __ l:transaction_mode_isolation_level { + // => { type: 'origin'; value: string; } + return { + type: 'origin', + value: `isolation level ${l.value}` + } + } + / 'READ'i __ e:('WRITE'i / 'ONLY'i) { + // => ignore + return { + type: 'origin', + value: `read ${e.toLowerCase()}` + } + } + / n:KW_NOT? __ 'DEFERRABLE'i { + // => ignore + return { + type: 'origin', + value: n ? 'not deferrable' : 'deferrable' + } + } + +transaction_mode_list + = head: transaction_mode tail:(__ COMMA __ transaction_mode)* { + // => transaction_mode[] + return createList(head, tail) + } +transaction_stmt + = k:('commit'i / 'rollback'i) { + /* export interface transaction_stmt_t { + type: 'transaction'; + expr: { + action: { + type: 'origin', + value: string + }; + keyword?: string; + modes?: transaction_mode[]; + } + } + => AstStatement + */ + return { + type: 'transaction', + expr: { + action: { + type: 'origin', + value: k + }, + } + } + } + / 'begin'i __ k:('WORK'i / 'TRANSACTION'i)? __ m:transaction_mode_list? { + // => ignore + return { + type: 'transaction', + expr: { + action: { + type: 'origin', + value: 'begin' + }, + keyword: k, + modes: m + } + } + } + / 'start'i __ k:'transaction'i __ m:transaction_mode_list? { + // => ignore + return { + type: 'transaction', + expr: { + action: { + type: 'origin', + value: 'start' + }, + keyword: k, + modes: m + } + } + } +comment_on_option + = t:(KW_TABLE / KW_VIEW / KW_TABLESPACE) __ name:table_name { + // => { type: string; name: table_name; } + return { + type: t.toLowerCase(), + name, + } + } + / t:(KW_COLUMN) __ name:column_ref { + // => { type: string; name: column_ref; } + return { + type: t.toLowerCase(), + name, + } + } + / t:(KW_INDEX / KW_COLLATION / KW_TABLESPACE / KW_SCHEMA / 'DOMAIN'i / KW_DATABASE / 'ROLE'i / 'SEQUENCE'i / 'SERVER'i / 'SUBSCRIPTION'i ) __ name:ident_type { + // => { type: string; name: ident; } + return { + type: t.toLowerCase(), + name, + } + } + +comment_on_is + = 'IS'i __ e:(literal_string / literal_null) { + // => { keyword: 'is'; expr: literal_string | literal_null; } + return { + keyword: 'is', + expr: e, + } + } +comment_on_stmt + = 'COMMENT'i __ 'ON'i __ co:comment_on_option __ is:comment_on_is { + /* export interface comment_on_stmt_t { + type: 'comment'; + target: comment_on_option; + expr: comment_on_is; + } + => AstStatement + */ + return { + type: 'comment', + keyword: 'on', + target: co, + expr: is, + } + } +select_stmt + = KW_SELECT __ ';' { + // => { type: 'select'; } + return { + type: 'select', + } + } + / select_stmt_nake + / s:('(' __ select_stmt __ ')') { + /* + export interface select_stmt_node extends select_stmt_nake { + parentheses: true; + } + => select_stmt_node + */ + return { + ...s[2], + parentheses_symbol: true, + } + } + +with_clause + = KW_WITH __ head:cte_definition tail:(__ COMMA __ cte_definition)* { + // => cte_definition[] + return createList(head, tail); + } + / __ KW_WITH __ KW_RECURSIVE __ cte:cte_definition { + // => [cte_definition & { recursive: true; }] + cte.recursive = true; + return [cte] + } + +cte_definition + = name:(literal_string / ident_name) __ columns:cte_column_definition? __ KW_AS __ LPAREN __ stmt:crud_stmt __ RPAREN { + // => { name: { type: 'default'; value: string; }; stmt: crud_stmt; columns?: cte_column_definition; } + if (typeof name === 'string') name = { type: 'default', value: name } + return { name, stmt: stmt.ast, columns }; + } + +cte_column_definition + = LPAREN __ l:column_ref_list __ RPAREN { + // => column_ref_list + return l + } + +distinct_on + = d:KW_DISTINCT __ o:KW_ON __ LPAREN __ c:column_list_items __ RPAREN { + // => {type: string; columns: column_list_items;} + console.lo + return { + type: `${d} ON`, + columns: c + } + } + / d:KW_DISTINCT? { + // => { type: string | undefined; } + return { + type: d, + } + } + +select_stmt_nake + = __ cte:with_clause? __ KW_SELECT ___ + opts:option_clause? __ + d:distinct_on? __ + c:column_clause __ + ci:into_clause? __ + f:from_clause? __ + fi:into_clause? __ + w:where_clause? __ + g:group_by_clause? __ + h:having_clause? __ + o:order_by_clause? __ + l:limit_clause? __ + win:window_clause? __ + li:into_clause? { + /* => { + with?: with_clause; + type: 'select'; + options?: option_clause; + distinct?: {type: string; columns?: column_list; }; + columns: column_clause; + from?: from_clause; + into?: into_clause; + where?: where_clause; + groupby?: group_by_clause; + having?: having_clause; + orderby?: order_by_clause; + limit?: limit_clause; + window?: window_clause; + }*/ + if ((ci && fi) || (ci && li) || (fi && li) || (ci && fi && li)) { + throw new Error('A given SQL statement can contain at most one INTO clause') + } + if(f) f.forEach(info => info.table && tableList.add(`select::${info.db}::${info.table}`)); + return { + with: cte, + type: 'select', + options: opts, + distinct: d, + columns: c, + into: { + ...(ci || fi || li || {}), + position: ci && 'column' || fi && 'from' || li && 'end' + }, + from: f, + where: w, + groupby: g, + having: h, + orderby: o, + limit: l, + window: win, + }; + } + +// MySQL extensions to standard SQL +option_clause + = head:query_option tail:(__ query_option)* { + // => query_option[] + const opts = [head]; + for (let i = 0, l = tail.length; i < l; ++i) { + opts.push(tail[i][1]); + } + return opts; + } + +query_option + = option:( + OPT_SQL_CALC_FOUND_ROWS + / (OPT_SQL_CACHE / OPT_SQL_NO_CACHE) + / OPT_SQL_BIG_RESULT + / OPT_SQL_SMALL_RESULT + / OPT_SQL_BUFFER_RESULT + ) { + // => 'SQL_CALC_FOUND_ROWS'| 'SQL_CACHE'| 'SQL_NO_CACHE'| 'SQL_BIG_RESULT'| 'SQL_SMALL_RESULT'| 'SQL_BUFFER_RESULT' + return option; + } + +column_list_items + = head:column_list_item tail:(__ COMMA __ column_list_item)* { + // => column_list_item[] + return createList(head, tail); + } +column_clause + = head: (KW_ALL / (STAR !ident_start) / STAR) tail:(__ COMMA __ column_list_item)* { + // => 'ALL' | '*' | column_list_item[] + columnList.add('select::null::(.*)') + const item = { + expr: { + type: 'column_ref', + table: null, + column: '*' + }, + as: null + } + if (tail && tail.length > 0) return createList(item, tail) + return [item] + } + / column_list_items + +array_index + = LBRAKE __ n:(literal_numeric / literal_string) __ RBRAKE { + // => { brackets: boolean, number: number } + return { + brackets: true, + index: n + } + } + +array_index_list + = head:array_index tail:(__ array_index)* { + // => array_index[] + return createList(head, tail, 1) + } + +expr_item + = e:binary_column_expr __ a:array_index_list? { + // => binary_column_expr & { array_index: array_index } + if (a) e.array_index = a + return e + } + +cast_data_type + = p:'"'? t: data_type s:'"'? { + // => data_type & { quoted?: string } + if ((p && !s) || (!p && s)) throw new Error('double quoted not match') + if (p && s) t.quoted = '"' + return t + } + +column_list_item + = c:string_constants_escape { + // => { expr: expr; as: null; } + return { expr: c, as: null } + } + / e:(column_ref_quoted / expr_item) __ s:KW_DOUBLE_COLON __ t:cast_data_type __ tail:(__ (additive_operator / multiplicative_operator) __ expr_item)* __ alias:alias_clause? { + // => { type: 'cast'; expr: expr; symbol: '::'; target: cast_data_type; as?: null; } + return { + as: alias, + type: 'cast', + expr: e, + symbol: '::', + target: t, + tail: tail && tail[0] && { operator: tail[0][1], expr: tail[0][3] }, + } + } + / tbl:ident_type __ DOT pro:(ident_without_kw_type __ DOT)? __ STAR { + // => { expr: column_ref; as: null; } + const mid = pro && pro[0] + let schema + if (mid) { + schema = tbl + tbl = mid + } + columnList.add(`select::${tbl ? tbl.value : null}::(.*)`) + const column = '*' + return { + expr: { + type: 'column_ref', + table: tbl, + schema, + column, + }, + as: null + } + } + / tbl:(ident_type __ DOT)? __ STAR { + // => { expr: column_ref; as: null; } + const table = tbl && tbl[0] || null + columnList.add(`select::${table ? table.value : null}::(.*)`); + return { + expr: { + type: 'column_ref', + table: table, + column: '*' + }, + as: null + }; + } + / e:expr_item __ alias:alias_clause? { + // => { type: 'expr'; expr: expr; as?: alias_clause; } + return { type: 'expr', expr: e, as: alias }; + } + +value_alias_clause + = KW_AS? __ i:alias_ident { /*=>alias_ident*/ return i; } + +alias_clause + = KW_AS __ i:alias_ident { /*=>alias_ident*/ return i; } + / KW_AS? __ i:alias_ident { /*=>alias_ident*/ return i; } + +into_clause + = KW_INTO __ v:var_decl_list { + // => { keyword: 'var'; type: 'into'; expr: var_decl_list; } + return { + keyword: 'var', + type: 'into', + expr: v + } + } + / KW_INTO __ k:('OUTFILE'i / 'DUMPFILE'i)? __ f:(literal_string / ident) { + // => { keyword: 'var'; type: 'into'; expr: literal_string | ident; } + return { + keyword: k, + type: 'into', + expr: f + } + } + +from_clause + = KW_FROM __ l:table_ref_list { /*=>table_ref_list*/return l; } + +table_to_list + = head:table_to_item tail:(__ COMMA __ table_to_item)* { + // => table_to_item[] + return createList(head, tail); + } + +table_to_item + = head:table_name __ KW_TO __ tail: (table_name) { + // => table_name[] + return [head, tail] + } + +index_type + = KW_USING __ t:("BTREE"i / "HASH"i / "GIST"i / "GIN"i) { + // => { keyword: 'using'; type: 'btree' | 'hash' | 'gist' | 'gin' } + return { + keyword: 'using', + type: t.toLowerCase(), + } + } + +index_options_list + = head:index_option tail:(__ COMMA __ index_option)* { + // => index_option[] + return createList(head, tail) + } + +index_options + = head:index_option tail:(__ index_option)* { + // => index_option[] + const result = [head]; + for (let i = 0; i < tail.length; i++) { + result.push(tail[i][1]); + } + return result; + } + +index_option + = k:KW_KEY_BLOCK_SIZE __ e:(KW_ASSIGIN_EQUAL)? __ kbs:literal_numeric { + // => { type: 'key_block_size'; symbol: '='; expr: number; } + return { + type: k.toLowerCase(), + symbol: e, + expr: kbs + } + } + / k:ident_name __ e:KW_ASSIGIN_EQUAL __ kbs:(literal_numeric / ident) { + // => { type: ident_name; symbol: '='; expr: number | {type: 'origin'; value: ident; }; } + return { + type: k.toLowerCase(), + symbol: e, + expr: typeof kbs === 'string' && { type: 'origin', value: kbs } || kbs + }; + } + / index_type + / "WITH"i __ "PARSER"i __ pn:ident_name { + // => { type: 'with parser'; expr: ident_name } + return { + type: 'with parser', + expr: pn + } + } + / k:("VISIBLE"i / "INVISIBLE"i) { + // => { type: 'visible'; expr: 'visible' } | { type: 'invisible'; expr: 'invisible' } + return { + type: k.toLowerCase(), + expr: k.toLowerCase() + } + } + / keyword_comment + +table_ref_list + = head:table_base + tail:table_ref* { + // => [table_base, ...table_ref[]] + tail.unshift(head); + tail.forEach(tableInfo => { + const { table, as } = tableInfo + tableAlias[table] = table + if (as) tableAlias[as] = table + refreshColumnList(columnList) + }) + return tail; + } + +table_ref + = __ COMMA __ t:table_base { /* => table_base */ return t; } + / __ t:table_join { /* => table_join */ return t; } + +table_join + = op:join_op __ t:table_base __ KW_USING __ LPAREN __ head:ident_without_kw tail:(__ COMMA __ ident_name)* __ RPAREN { + // => table_base & {join: join_op; using: ident_name[]; } + t.join = op; + t.using = createList(head, tail); + return t; + } + / op:join_op __ t:table_base __ expr:on_clause? { + // => table_base & {join: join_op; on?: on_clause; } + t.join = op; + t.on = expr; + return t; + } + / op:(join_op / set_op) __ LPAREN __ stmt:(union_stmt / table_ref_list) __ RPAREN __ alias:alias_clause? __ expr:on_clause? { + /* => { + expr: (union_stmt | table_ref_list) & { parentheses: true; }; + as?: alias_clause; + join: join_op | set_op; + on?: on_clause; + }*/ + if (Array.isArray(stmt)) stmt = { type: 'tables', expr: stmt } + stmt.parentheses = true; + return { + expr: stmt, + as: alias, + join: op, + on: expr + }; + } + +//NOTE that, the table assigned to `var` shouldn't write in `table_join` +table_base + = KW_DUAL { + // => { type: 'dual' } + return { + type: 'dual' + }; + } + / stmt:value_clause __ alias:value_alias_clause? { + // => { expr: value_clause; as?: alias_clause; } + return { + expr: { type: 'values', values: stmt }, + as: alias + }; + } + / l:('LATERAL'i)? __ LPAREN __ stmt:(union_stmt / value_clause) __ RPAREN __ alias:value_alias_clause? { + // => { prefix?: string; expr: union_stmt | value_clause; as?: alias_clause; } + if (Array.isArray(stmt)) stmt = { type: 'values', values: stmt } + stmt.parentheses = true; + return { + prefix: l, + expr: stmt, + as: alias + }; + } + / l:('LATERAL'i)? __ LPAREN __ stmt:table_ref_list __ RPAREN __ alias:value_alias_clause? { + // => { prefix?: string; expr: table_ref_list; as?: alias_clause; } + stmt = { type: 'tables', expr: stmt, parentheses: true } + return { + prefix: l, + expr: stmt, + as: alias + }; + } + / l:('LATERAL'i)? __ e:func_call __ alias:alias_clause? { + // => { prefix?: string; type: 'expr'; expr: expr; as?: alias_clause; } + return { prefix: l, type: 'expr', expr: e, as: alias }; + } + / t:table_name __ 'TABLESAMPLE'i __ f:func_call __ re:('REPEATABLE'i __ LPAREN __ literal_numeric __ RPAREN)? __ alias:alias_clause? { + // => table_name & { expr: expr, repeatable: literal_numeric; as?: alias_clause;} + return { + ...t, + as: alias, + tablesample: { + expr: f, + repeatable: re && re[4], + } + } + } + / t:table_name __ alias:alias_clause? { + // => table_name & { as?: alias_clause; } + if (t.type === 'var') { + t.as = alias; + return t; + } else { + return { + ...t, + as: alias + }; + } + } + + +join_op + = KW_LEFT __ KW_OUTER? __ KW_JOIN { /* => 'LEFT JOIN' */ return 'LEFT JOIN'; } + / KW_RIGHT __ KW_OUTER? __ KW_JOIN { /* => 'RIGHT JOIN' */ return 'RIGHT JOIN'; } + / KW_FULL __ KW_OUTER? __ KW_JOIN { /* => 'FULL JOIN' */ return 'FULL JOIN'; } + / 'CROSS'i __ KW_JOIN { /* => 'CROSS JOIN' */ return 'CROSS JOIN'; } + / (KW_INNER __)? KW_JOIN { /* => 'INNER JOIN' */ return 'INNER JOIN'; } + +table_name + = dt:ident schema:(__ DOT __ (ident / STAR))? tail:(__ DOT __ (ident / STAR))? { + // => { db?: ident; schema?: ident, table: ident | '*'; } + const obj = { db: null, table: dt }; + if (tail !== null) { + obj.db = dt; + obj.schema = schema[3]; + obj.table = tail[3]; + return obj + } + if (schema !== null) { + obj.db = dt; + obj.table = schema[3]; + } + return obj; + } + / v:var_decl { + // => IGNORE + v.db = null; + v.table = v.name; + return v; + } + +or_and_expr + = head:expr tail:(__ (KW_AND / KW_OR) __ expr)* { + /* + export type BINARY_OPERATORS = + | LOGIC_OPERATOR + | "OR" + | "AND" + | multiplicative_operator + | additive_operator + | arithmetic_comparison_operator + | "IN" + | "NOT IN" + | "BETWEEN" + | "NOT BETWEEN" + | "IS" + | "IS NOT" + | "ILIKE" + | "LIKE" + | "@>" + | "<@" + | OPERATOR_CONCATENATION + | DOUBLE_WELL_ARROW + | WELL_ARROW + | "?" + | "?|" + | "?&" + | "#-"; + + export type binary_expr = { + type: "binary_expr"; + operator: BINARY_OPERATORS; + left: expr; + right: expr; + }; + => binary_expr + */ + const len = tail.length + let result = head + for (let i = 0; i < len; ++i) { + result = createBinaryExpr(tail[i][1], result, tail[i][3]) + } + return result + } + +on_clause + = KW_ON __ e:or_and_where_expr { /* => or_and_where_expr */ return e; } + +where_clause + = KW_WHERE __ e:or_and_where_expr { /* => or_and_where_expr */ return e; } + +group_by_clause + = KW_GROUP __ KW_BY __ e:expr_list { + // => { columns: expr_list['value']; modifiers: literal_string[]; } + return { + columns: e.value + } + } + +column_ref_list + = head:column_ref tail:(__ COMMA __ column_ref)* { + // => column_ref[] + return createList(head, tail); + } + +having_clause + = KW_HAVING __ e:or_and_where_expr { /* => expr */ return e; } + +window_clause + = KW_WINDOW __ l:named_window_expr_list { + // => { keyword: 'window'; type: 'window', expr: named_window_expr_list; } + return { + keyword: 'window', + type: 'window', + expr: l, + } + } + +named_window_expr_list + = head:named_window_expr tail:(__ COMMA __ named_window_expr)* { + // => named_window_expr[] + return createList(head, tail); + } + +named_window_expr + = nw:ident_name __ KW_AS __ anw:as_window_specification { + // => { name: ident_name; as_window_specification: as_window_specification; } + return { + name: nw, + as_window_specification: anw, + } + } + +as_window_specification + = ident_name + / LPAREN __ ws:window_specification? __ RPAREN { + // => { window_specification: window_specification; parentheses: boolean } + return { + window_specification: ws || {}, + parentheses: true + } + } + +window_specification + = bc:partition_by_clause? __ + l:order_by_clause? __ + w:window_frame_clause? { + // => { name: null; partitionby: partition_by_clause; orderby: order_by_clause; window_frame_clause: string | null; } + return { + name: null, + partitionby: bc, + orderby: l, + window_frame_clause: w + } + } + +window_specification_frameless + = bc:partition_by_clause? __ + l:order_by_clause? { + // => { name: null; partitionby: partition_by_clause; orderby: order_by_clause; window_frame_clause: null } + return { + name: null, + partitionby: bc, + orderby: l, + window_frame_clause: null + } + } + +window_frame_clause + = kw:KW_ROWS __ s:(window_frame_following / window_frame_preceding) { + // => string + return `rows ${s.value}` + } + / KW_ROWS __ KW_BETWEEN __ p:window_frame_preceding __ KW_AND __ f:window_frame_following { + // => string + return `rows between ${p.value} and ${f.value}` + } + +window_frame_following + = s:window_frame_value __ 'FOLLOWING'i { + // => string + s.value += ' FOLLOWING' + return s + } + / window_frame_current_row + +window_frame_preceding + = s:window_frame_value __ 'PRECEDING'i { + // => string + s.value += ' PRECEDING' + return s + } + / window_frame_current_row + +window_frame_current_row + = 'CURRENT'i __ 'ROW'i { + // => { type: 'single_quote_string'; value: string } + return { type: 'single_quote_string', value: 'current row' } + } + +window_frame_value + = s:'UNBOUNDED'i { + // => literal_string + return { type: 'single_quote_string', value: s.toUpperCase() } + } + / literal_numeric + +partition_by_clause + = KW_PARTITION __ KW_BY __ bc:column_ref_list { /* => { type: 'expr'; expr: column_ref_list }[] */ return bc.map(item => ({ type: 'expr', expr: item })); } + +order_by_clause + = KW_ORDER __ KW_BY __ l:order_by_list { /* => order_by_list */ return l; } + +order_by_list + = head:order_by_element tail:(__ COMMA __ order_by_element)* { + // => order_by_element[] + return createList(head, tail); + } + +order_by_element + = e:expr __ d:(KW_DESC / KW_ASC)? __ nl:('NULLS'i __ ('FIRST'i / 'LAST'i)?)? { + // => { expr: expr; type: 'ASC' | 'DESC' | undefined; nulls: 'NULLS FIRST' | 'NULLS LAST' | undefined } + const obj = { expr: e, type: d }; + obj.nulls = nl && [nl[0], nl[2]].filter(v => v).join(' ') + return obj; + } + +number_or_param + = literal_numeric + / var_decl + / param + +limit_clause + = l:(KW_LIMIT __ (number_or_param / KW_ALL))? __ tail:(KW_OFFSET __ number_or_param)? { + // => { separator: 'offset' | ''; value: [number_or_param | { type: 'origin', value: 'all' }, number_or_param?] } + const res = [] + if (l) res.push(typeof l[2] === 'string' ? { type: 'origin', value: 'all' } : l[2]) + if (tail) res.push(tail[2]); + return { + seperator: tail && tail[0] && tail[0].toLowerCase() || '', + value: res + }; + } + +update_stmt + = __ cte:with_clause? __ KW_UPDATE __ + t:table_ref_list __ + KW_SET __ + l:set_list __ + f:from_clause? __ + w:where_clause? __ + r:returning_stmt? { + /* export interface update_stmt_node { + with?: with_clause; + type: 'update'; + table: table_ref_list; + set: set_list; + from?: from_clause; + where?: where_clause; + returning?: returning_stmt; + } + => AstStatement + */ + const dbObj = {} + if (t) t.forEach(tableInfo => { + const { db, as, table, join } = tableInfo + const action = join ? 'select' : 'update' + if (db) dbObj[table] = db + if (table) tableList.add(`${action}::${db}::${table}`) + }); + if(l) { + l.forEach(col => { + if (col.table) { + const table = queryTableAlias(col.table) + tableList.add(`update::${dbObj[table] || null}::${table}`) + } + columnList.add(`update::${col.table}::${col.column.expr.value}`) + }); + } + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + with: cte, + type: 'update', + table: t, + set: l, + from: f, + where: w, + returning: r, + } + }; + } + +delete_stmt + = KW_DELETE __ + t:table_ref_list? __ + f:from_clause __ + w:where_clause? { + /* + export interface table_ref_addition extends table_name { + addition: true; + as?: alias_clause; + } + export interface delete_stmt_node { + type: 'delete'; + table?: table_ref_list | [table_ref_addition]; + where?: where_clause; + } + => AstStatement + */ + if(f) f.forEach(tableInfo => { + const { db, as, table, join } = tableInfo + const action = join ? 'select' : 'delete' + if (table) tableList.add(`${action}::${db}::${table}`) + if (!join) columnList.add(`delete::${table}::(.*)`); + }); + if (t === null && f.length === 1) { + const tableInfo = f[0] + t = [{ + db: tableInfo.db, + table: tableInfo.table, + as: tableInfo.as, + addition: true + }] + } + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'delete', + table: t, + from: f, + where: w + } + }; + } +set_list + = head:set_item tail:(__ COMMA __ set_item)* { + // => set_item[] + return createList(head, tail); + } + +/** + * here only use `additive_expr` to support 'col1 = col1+2' + * if you want to use lower operator, please use '()' like below + * 'col1 = (col2 > 3)' + */ +set_item + = c:column_ref_array_index __ '=' __ v:additive_expr { + // => { column: ident; value: additive_expr; table?: ident;} + return { ...c, value: v }; + } + / column_ref_array_index __ '=' __ KW_VALUES __ LPAREN __ v:column_ref __ RPAREN { + // => { column: ident; value: column_ref; table?: ident; keyword: 'values' } + return { ...c, value: v, keyword: 'values' }; + } + +returning_stmt + = k:KW_RETURNING __ c:(column_clause / select_stmt) { + // => { type: 'returning'; columns: column_clause | select_stmt; } + return { + type: k && k.toLowerCase() || 'returning', + columns: c === '*' && [{ type: 'expr', expr: { type: 'column_ref', table: null, column: '*' }, as: null }] || c + } + } + +insert_value_clause + = value_clause + / select_stmt_nake + +insert_partition + = KW_PARTITION __ LPAREN __ head:ident_name tail:(__ COMMA __ ident_name)* __ RPAREN { + // => ident_name[] + return createList(head, tail) + } + / KW_PARTITION __ v: value_item { + // => value_item + return v + } + +conflict_target + = LPAREN __ c:column_ref_list __ RPAREN { + // => { type: 'column'; expr: column_ref_list; parentheses: true; } + return { + type: 'column', + expr: c, + parentheses: true, + } + } + +conflict_action + = 'DO'i __ 'NOTHING'i { + // => { keyword: "do"; expr: {type: 'origin'; value: string; }; } + return { + keyword: 'do', + expr: { + type: 'origin', + value: 'nothing' + } + } + } + / 'DO'i __ KW_UPDATE __ KW_SET __ s:set_list __ w:where_clause? { + // => { keyword: "do"; expr: {type: 'update'; set: set_list; where: where_clause; }; } + return { + keyword: 'do', + expr: { + type: 'update', + set: s, + where: w, + } + } + } + +on_conflict + = KW_ON __ 'CONFLICT'i __ ct:conflict_target? __ ca:conflict_action { + // => { type: "conflict"; keyword: "on"; target: conflict_target; action: conflict_action; } + return { + type: 'conflict', + keyword: 'on', + target: ct, + action: ca, + } + } + +replace_insert_stmt + = ri:replace_insert __ + KW_INTO? __ + t:table_name __ + p:insert_partition? __ LPAREN __ c:column_list __ RPAREN __ + v:insert_value_clause __ + oc:on_conflict? __ + r:returning_stmt? { + /* + export interface replace_insert_stmt_node { + type: 'insert' | 'replace'; + table?: [table_name]; + columns: column_list; + conflict?: on_conflict; + values: insert_value_clause; + partition?: insert_partition; + returning?: returning_stmt; + } + => AstStatement + */ + if (t) { + tableList.add(`insert::${t.db}::${t.table}`) + t.as = null + } + if (c) { + let table = t && t.table || null + if(Array.isArray(v)) { + v.forEach((row, idx) => { + if(row.value.length != c.length) { + throw new Error(`Error: column count doesn't match value count at row ${idx+1}`) + } + }) + } + c.forEach(c => columnList.add(`insert::${table}::${c.value}`)); + } + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: ri, + table: [t], + columns: c, + values: v, + partition: p, + conflict: oc, + returning: r, + } + }; + } + +insert_no_columns_stmt + = ri:replace_insert __ + ig:KW_IGNORE? __ + it:KW_INTO? __ + t:table_name __ + p:insert_partition? __ + v:insert_value_clause __ + r:returning_stmt? { + // => AstStatement + if (t) { + tableList.add(`insert::${t.db}::${t.table}`) + columnList.add(`insert::${t.table}::(.*)`); + t.as = null + } + const prefix = [ig, it].filter(v => v).map(v => v[0] && v[0].toLowerCase()).join(' ') + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: ri, + table: [t], + columns: null, + values: v, + partition: p, + prefix, + returning: r, + } + }; + } + +replace_insert + = KW_INSERT { /* => 'insert' */ return 'insert'; } + / KW_REPLACE { /* => 'replace' */return 'replace'; } + +value_clause + = KW_VALUES __ l:value_list { /* => value_list */ return l; } + +value_list + = head:value_item tail:(__ COMMA __ value_item)* { + // => value_item[] + return createList(head, tail); + } + +value_item + = LPAREN __ l:expr_list __ RPAREN { + // => expr_list + return l; + } + +expr_list + = head:expr tail:(__ COMMA __ expr)* { + // => { type: 'expr_list'; value: expr[] } + const el = { type: 'expr_list' }; + el.value = createList(head, tail); + return el; + } + +interval_expr + = KW_INTERVAL __ + e:expr __ + u: interval_unit { + // => { type: 'interval', expr: expr; unit: interval_unit; } + return { + type: 'interval', + expr: e, + unit: u.toLowerCase(), + } + } + / KW_INTERVAL __ + e:literal_string { + // => { type: 'interval', expr: expr; unit: interval_unit; } + return { + type: 'interval', + expr: e, + unit: '', + } + } + +case_expr + = KW_CASE __ + condition_list:case_when_then_list __ + otherwise:case_else? __ + KW_END __ KW_CASE? { + /* => { + type: 'case'; + expr: null; + // nb: Only the last element is a case_else + args: (case_when_then | case_else)[]; + } */ + if (otherwise) condition_list.push(otherwise); + return { + type: 'case', + expr: null, + args: condition_list + }; + } + / KW_CASE __ + expr:expr __ + condition_list:case_when_then_list __ + otherwise:case_else? __ + KW_END __ KW_CASE? { + /* => { + type: 'case'; + expr: expr; + // nb: Only the last element is a case_else + args: (case_when_then | case_else)[]; + } */ + if (otherwise) condition_list.push(otherwise); + return { + type: 'case', + expr: expr, + args: condition_list + }; + } + +case_when_then_list + = head:case_when_then __ tail:(__ case_when_then)* { + // => case_when_then[] + return createList(head, tail, 1) + } + +case_when_then + = KW_WHEN __ condition:or_and_where_expr __ KW_THEN __ result:expr { + // => { type: 'when'; cond: or_and_where_expr; result: expr; } + return { + type: 'when', + cond: condition, + result: result + }; + } + +case_else = KW_ELSE __ result:expr { + // => { type: 'else'; condition?: never; result: expr; } + return { type: 'else', result: result }; + } + +/** + * Borrowed from PL/SQL ,the priority of below list IS ORDER BY DESC + * --------------------------------------------------------------------------------------------------- + * | +, - | identity, negation | + * | *, / | multiplication, division | + * | +, - | addition, subtraction, concatenation | + * | =, <, >, <=, >=, <>, !=, IS, LIKE, BETWEEN, IN | comparion | + * | !, NOT | logical negation | + * | AND | conjunction | + * | OR | inclusion | + * --------------------------------------------------------------------------------------------------- + */ + +_expr + = or_expr + / unary_expr + +expr + = _expr / union_stmt + +unary_expr + = op: additive_operator tail: (__ primary)+ { + /* + export type UNARY_OPERATORS = '+' | '-' | 'EXISTS' | 'NOT EXISTS' | 'NULL' + => { + type: 'unary_expr', + operator: UNARY_OPERATORS, + expr: expr; + parentheses?: boolean; + } */ + return createUnaryExpr(op, tail[0][1]); + } + +binary_column_expr + = head:expr tail:(__ (KW_AND / KW_OR / LOGIC_OPERATOR) __ expr)* { + const ast = head.ast + if (ast && ast.type === 'select') { + if (!(head.parentheses_symbol || head.parentheses || head.ast.parentheses || head.ast.parentheses_symbol) || ast.columns.length !== 1 || ast.columns[0].expr.column === '*') throw new Error('invalid column clause with select statement') + } + if (!tail || tail.length === 0) return head + // => binary_expr + const len = tail.length + let result = tail[len - 1][3] + for (let i = len - 1; i >= 0; i--) { + const left = i === 0 ? head : tail[i - 1][3] + result = createBinaryExpr(tail[i][1], left, result) + } + return result + } + +or_and_where_expr + = head:expr tail:(__ (KW_AND / KW_OR / COMMA) __ expr)* { + // => binary_expr | { type: 'expr_list'; value: expr[] } + const len = tail.length + let result = head; + let seperator = '' + for (let i = 0; i < len; ++i) { + if (tail[i][1] === ',') { + seperator = ',' + if (!Array.isArray(result)) result = [result] + result.push(tail[i][3]) + } else { + result = createBinaryExpr(tail[i][1], result, tail[i][3]); + } + } + if (seperator === ',') { + const el = { type: 'expr_list' } + el.value = result + return el + } + return result + } + +or_expr + = head:and_expr tail:(___ KW_OR __ and_expr)* { + // => binary_expr + return createBinaryExprChain(head, tail); + } + +and_expr + = head:not_expr tail:(___ KW_AND __ not_expr)* { + // => binary_expr + return createBinaryExprChain(head, tail); + } + +//here we should use `NOT` instead of `comparision_expr` to support chain-expr +not_expr + = comparison_expr + / exists_expr + / (KW_NOT / "!" !"=") __ expr:not_expr { + // => unary_expr + return createUnaryExpr('NOT', expr); + } + +comparison_expr + = left:additive_expr __ rh:comparison_op_right? { + // => binary_expr + if (rh === null) return left; + else if (rh.type === 'arithmetic') return createBinaryExprChain(left, rh.tail); + else return createBinaryExpr(rh.op, left, rh.right); + } + / literal_string + / column_ref + +exists_expr + = op:exists_op __ LPAREN __ stmt:union_stmt __ RPAREN { + // => unary_expr + stmt.parentheses = true; + return createUnaryExpr(op, stmt); + } + +exists_op + = nk:(KW_NOT __ KW_EXISTS) { /* => 'NOT EXISTS' */ return nk[0] + ' ' + nk[2]; } + / KW_EXISTS + +comparison_op_right + = arithmetic_op_right + / in_op_right + / between_op_right + / is_op_right + / like_op_right + / regex_op_right + +arithmetic_op_right + = l:(__ arithmetic_comparison_operator __ additive_expr)+ { + // => { type: 'arithmetic'; tail: any } + return { type: 'arithmetic', tail: l }; + } + +arithmetic_comparison_operator + = ">=" / ">" / "<=" / "<>" / "<" / "=" / "!=" + +is_op_right + = KW_IS __ right:additive_expr { + // => { op: 'IS'; right: additive_expr; } + return { op: 'IS', right: right }; + } + / KW_IS __ right:(KW_DISTINCT __ KW_FROM __ table_name) { + // => { type: 'origin'; value: string; } + const { db, table } = right.pop() + const tableName = table === '*' ? '*' : `"${table}"` + let tableStr = db ? `"${db}".${tableName}` : tableName + return { op: 'IS', right: { + type: 'default', + value: `DISTINCT FROM ${tableStr}` + }} + } + / (KW_IS __ KW_NOT) __ right:additive_expr { + // => { type: 'IS NOT'; right: additive_expr; } + return { op: 'IS NOT', right: right }; + } + +between_op_right + = op:between_or_not_between_op __ begin:additive_expr __ KW_AND __ end:additive_expr { + // => { op: 'BETWEEN' | 'NOT BETWEEN'; right: { type: 'expr_list'; value: [expr, expr] } } + return { + op: op, + right: { + type: 'expr_list', + value: [begin, end] + } + }; + } + +between_or_not_between_op + = nk:(KW_NOT __ KW_BETWEEN) { /* => 'NOT BETWEEN' */ return nk[0] + ' ' + nk[2]; } + / KW_BETWEEN + +like_op + = nk:(KW_NOT __ (KW_LIKE / KW_ILIKE)) { /* => 'LIKE' */ return nk[0] + ' ' + nk[2]; } + / KW_LIKE + / KW_ILIKE + / 'SIMILAR'i __ KW_TO { + // => 'SIMILAR TO' + return 'SIMILAR TO' + } + / KW_NOT __ 'SIMILAR'i __ KW_TO { + // => 'NOT SIMILAR TO' + return 'NOT SIMILAR TO' + } + +regex_op + = "!~*" / "~*" / "~" / "!~" + +regex_op_right += op:regex_op __ right:(literal / comparison_expr) { + // => { op: regex_op; right: literal | comparison_expr} + return { op: op, right: right }; + } + +escape_op + = kw:'ESCAPE'i __ c:literal_string { + // => { type: 'ESCAPE'; value: literal_string } + return { + type: 'ESCAPE', + value: c, + } + } + +in_op + = nk:(KW_NOT __ KW_IN) { /* => 'NOT IN' */ return nk[0] + ' ' + nk[2]; } + / KW_IN + +like_op_right + = op:like_op __ right:(literal / comparison_expr) __ es:escape_op? { + // => { op: like_op; right: (literal | comparison_expr) & { escape?: escape_op }; } + if (es) right.escape = es + return { op: op, right: right }; + } + +in_op_right + = op:in_op __ LPAREN __ l:expr_list __ RPAREN { + // => {op: in_op; right: expr_list | var_decl | literal_string; } + return { op: op, right: l }; + } + / op:in_op __ e:(var_decl / literal_string / func_call) { + // => IGNORE + return { op: op, right: e }; + } + +additive_expr + = head:multiplicative_expr + tail:(__ additive_operator __ multiplicative_expr)* { + // => binary_expr + if (tail && tail.length && head.type === 'column_ref' && head.column === '*') throw new Error(JSON.stringify({ + message: 'args could not be star column in additive expr', + ...getLocationObject(), + })) + return createBinaryExprChain(head, tail); + } + +additive_operator + = "+" / "-" + +multiplicative_expr + = head:unary_expr_or_primary + tail:(__ (multiplicative_operator / LOGIC_OPERATOR) __ unary_expr_or_primary)* { + // => binary_expr + return createBinaryExprChain(head, tail) + } + +multiplicative_operator + = "*" / "/" / "%" / "||" + +column_ref_array_index + = c:column_ref __ a:array_index_list? { + // => column_ref + if (a) c.array_index = a + return c + } + +primary + = cast_expr + / LPAREN __ list:or_and_where_expr __ RPAREN { + // => or_and_where_expr + list.parentheses = true; + return list; + } + / var_decl + / __ p:'$''<'n:literal_numeric'>' { + // => { type: 'origin'; value: string; } + return { + type: 'origin', + value: `$<${n.value}>`, + } + } + +unary_expr_or_primary + = jsonb_expr + / op:(unary_operator) tail:(__ unary_expr_or_primary) { + // => unary_expr + return createUnaryExpr(op, tail[1]) + } + +unary_operator + = '!' / '-' / '+' / '~' + +jsonb_expr + = head:primary __ tail: (__ ('?|' / '?&' / '?' / '#-' / '#>>' / '#>' / DOUBLE_ARROW / SINGLE_ARROW / '@>' / '<@') __ primary)* { + // => primary | binary_expr + if (!tail || tail.length === 0) return head + return createBinaryExprChain(head, tail) + } + +string_constants_escape + = 'E'i"'" __ n:single_char* __ "'" { + // => { type: 'origin'; value: string; } + return { + type: 'origin', + value: `E'${n.join('')}'` + } + } + +column_ref + = string_constants_escape + / tbl:(ident __ DOT)? __ STAR { + // => IGNORE + const table = tbl && tbl[0] || null + columnList.add(`select::${table}::(.*)`); + return { + type: 'column_ref', + table: table, + column: '*' + } + } + / schema:ident tbl:(__ DOT __ ident) col:(__ DOT __ column_without_kw_type) { + /* => { + type: 'column_ref'; + schema: string; + table: string; + column: column | '*'; + } */ + columnList.add(`select::${schema}.${tbl[3]}::${col[3].value}`); + return { + type: 'column_ref', + schema: schema, + table: tbl[3], + column: { expr: col[3] } + }; + } + / tbl:ident __ DOT __ col:column_without_kw_type { + /* => { + type: 'column_ref'; + table: ident; + column: column | '*'; + } */ + columnList.add(`select::${tbl}::${col.value}`); + return { + type: 'column_ref', + table: tbl, + column: { expr: col } + }; + } + / col:column_type { + // => IGNORE + columnList.add(`select::null::${col.value}`); + return { + type: 'column_ref', + table: null, + column: { expr: col } + }; + } + +column_ref_quoted + = col:literal_double_quoted_string { + // => unknown + columnList.add(`select::null::${col.value}`); + return { + type: 'column_ref', + table: null, + column: { expr: col } + }; + } + +column_list + = head:column_type tail:(__ COMMA __ column_type)* { + // => column[] + return createList(head, tail); + } + +ident_without_kw_type + = n:ident_name { + // => { type: 'default', value: string } + return { type: 'default', value: n } + } + / quoted_ident_type + +ident_type + = name:ident_name !{ return reservedMap[name.toUpperCase()] === true; } { + // => ident_name + return { type: 'default', value: name } + } + / quoted_ident_type + +ident + = name:ident_name !{ return reservedMap[name.toUpperCase()] === true; } { + // => ident_name + return name; + } + / quoted_ident +ident_list + = head:ident tail:(__ COMMA __ ident)* { + // => ident[] + return createList(head, tail) + } +alias_ident + = name:column_name !{ return reservedMap[name.toUpperCase()] === true } c:(__ LPAREN __ column_list __ RPAREN)? { + // => string + if (!c) return name; + return `${name}(${c[3].map(v => v.value).join(', ')})` + } + / name:double_quoted_ident { + // => IGNORE + return name.value; + } + +quoted_ident_type + = double_quoted_ident / single_quoted_ident / backticks_quoted_ident + +quoted_ident + = v:(double_quoted_ident / single_quoted_ident / backticks_quoted_ident) { + // => string + return v.value + } + +double_quoted_ident + = '"' chars:[^"]+ '"' { + // => { type: 'double_quote_string'; value: string; } + return { + type: 'double_quote_string', + value: chars.join('') + } + } + +single_quoted_ident + = "'" chars:[^']+ "'" { + // => { type: 'single_quote_string'; value: string; } + return { + type: 'single_quote_string', + value: chars.join('') + } + } + +backticks_quoted_ident + = "`" chars:[^`]+ "`" { + // => { type: 'backticks_quote_string'; value: string; } + return { + type: 'backticks_quote_string', + value: chars.join('') + } + } + +ident_without_kw + = ident_name / quoted_ident + +column_without_kw + = column_name / quoted_ident + +column_without_kw_type + = n:column_name { + // => { type: 'default', value: string } + return { type: 'default', value: n } + } + / quoted_ident_type +column_type + = name:column_name !{ return reservedMap[name.toUpperCase()] === true; } { + // => { type: 'default', value: string } + return { type: 'default', value: name } + } + / quoted_ident_type +column + = name:column_name !{ return reservedMap[name.toUpperCase()] === true; } { /* => string */ return name; } + / quoted_ident + +column_name + = start:ident_start parts:column_part* { /* => string */ return start + parts.join(''); } + +ident_name + = start:ident_start parts:ident_part* { + // => string + return start + parts.join(''); + } + +ident_start = [A-Za-z_\u4e00-\u9fa5] + +ident_part = [A-Za-z0-9_\-$\u4e00-\u9fa5\u00C0-\u017F] + +// to support column name like `cf1:name` in hbase +column_part = [A-Za-z0-9_\u4e00-\u9fa5\u00C0-\u017F] + +param + = l:(':' ident_name) { + // => { type: 'param'; value: ident_name } + return { type: 'param', value: l[1] }; + } + +on_update_current_timestamp + = KW_ON __ KW_UPDATE __ kw:KW_CURRENT_TIMESTAMP __ LPAREN __ l:expr_list? __ RPAREN{ + // => { type: 'on update'; keyword: string; parentheses: boolean; expr: expr } + return { + type: 'on update', + keyword: kw, + parentheses: true, + expr: l + } + } + / KW_ON __ KW_UPDATE __ kw:KW_CURRENT_TIMESTAMP { + // => { type: 'on update'; keyword: string; } + return { + type: 'on update', + keyword: kw, + } + } + +over_partition + = 'OVER'i __ aws:as_window_specification { + // => { type: 'windows'; as_window_specification: as_window_specification } + return { + type: 'window', + as_window_specification: aws, + } + } + / 'OVER'i __ LPAREN __ bc:partition_by_clause? __ l:order_by_clause? __ RPAREN { + // => { partitionby: partition_by_clause; orderby: order_by_clause } + return { + partitionby: bc, + orderby: l + } + } + / on_update_current_timestamp + +aggr_filter + = 'FILTER'i __ LPAREN __ wc:where_clause __ RPAREN { + // => { keyword: 'filter'; parentheses: true, where: where_clause } + return { + keyword: 'filter', + parentheses: true, + where: wc, + } + } + +aggr_func + = e:(aggr_fun_count / aggr_fun_smma / aggr_array_agg) __ f:aggr_filter? { + // => { type: 'aggr_func'; name: string; args: { expr: additive_expr } | count_arg; over: over_partition; filter?: aggr_filter; } + if (f) e.filter = f + return e + } + +window_func + = window_fun_rank + / window_fun_laglead + / window_fun_firstlast + +window_fun_rank + = name:KW_WIN_FNS_RANK __ LPAREN __ RPAREN __ over:over_partition { + // => { type: 'window_func'; name: string; over: over_partition } + return { + type: 'window_func', + name: name, + over: over + } + } + +window_fun_laglead + = name:KW_LAG_LEAD __ LPAREN __ l:expr_list __ RPAREN __ cn:consider_nulls_clause? __ over:over_partition { + // => { type: 'window_func'; name: string; args: expr_list; consider_nulls: null | string; over: over_partition } + return { + type: 'window_func', + name: name, + args: l, + over: over, + consider_nulls: cn + }; + } + +window_fun_firstlast + = name:KW_FIRST_LAST_VALUE __ LPAREN __ l:expr __ cn:consider_nulls_clause? __ RPAREN __ over:over_partition { + // => window_fun_laglead + return { + type: 'window_func', + name: name, + args: { + type: 'expr_list', value: [l] + }, + over: over, + consider_nulls: cn + }; + } + +KW_FIRST_LAST_VALUE + = 'FIRST_VALUE'i / 'LAST_VALUE'i + +KW_WIN_FNS_RANK + = 'ROW_NUMBER'i / 'DENSE_RANK'i / 'RANK'i + // / 'CUME_DIST'i / 'MEDIAN'i / 'PERCENT_RANK'i + // / 'PERCENTILE_CONT'i / 'PERCENTILE_DISC'i / 'RATIO_TO_REPORT'i + +KW_LAG_LEAD + = 'LAG'i / 'LEAD'i / 'NTH_VALUE'i + +consider_nulls_clause + = v:('IGNORE'i / 'RESPECT'i) __ 'NULLS'i { + // => string + return v.toUpperCase() + ' NULLS' + } + +aggr_fun_smma + = name:KW_SUM_MAX_MIN_AVG __ LPAREN __ e:additive_expr __ RPAREN __ bc:over_partition? { + // => { type: 'aggr_func'; name: 'SUM' | 'MAX' | 'MIN' | 'AVG'; args: { expr: additive_expr }; over: over_partition } + return { + type: 'aggr_func', + name: name, + args: { + expr: e + }, + over: bc, + ...getLocationObject(), + }; + } + +KW_SUM_MAX_MIN_AVG + = KW_SUM / KW_MAX / KW_MIN / KW_AVG + +aggr_fun_count + = name:(KW_COUNT / KW_GROUP_CONCAT) __ LPAREN __ arg:count_arg __ RPAREN __ bc:over_partition? { + // => { type: 'aggr_func'; name: 'COUNT' | 'GROUP_CONCAT'; args:count_arg; over: over_partition } + return { + type: 'aggr_func', + name: name, + args: arg, + over: bc + }; + } + / name:('percentile_cont'i / 'percentile_disc'i) __ LPAREN __ arg:(literal_numeric / literal_array) __ RPAREN __ 'within'i __ KW_GROUP __ LPAREN __ or:order_by_clause __ RPAREN __ bc:over_partition? { + // => { type: 'aggr_func'; name: 'PERCENTILE_CONT' | 'PERCENTILE_DISC'; args: literal_numeric | literal_array; within_group_orderby: order_by_clause; over?: over_partition } + return { + type: 'aggr_func', + name: name.toUpperCase(), + args: { + expr: arg + }, + within_group_orderby: or, + over: bc + }; + } + / name:('mode'i) __ LPAREN __ RPAREN __ 'within'i __ KW_GROUP __ LPAREN __ or:order_by_clause __ RPAREN __ bc:over_partition? { + // => { type: 'aggr_func'; name: 'MODE'; args: literal_numeric | literal_array; within_group_orderby: order_by_clause; over?: over_partition } + return { + type: 'aggr_func', + name: name.toUpperCase(), + args: { expr: {} }, + within_group_orderby: or, + over: bc + }; + } + +concat_separator + = kw:COMMA __ s:literal_string { + // => { symbol: ','; delimiter: literal_string; } + return { + symbol: kw, + delimiter: s + } + } + +distinct_args + = d:KW_DISTINCT? __ LPAREN __ c:expr __ RPAREN __ tail:(__ (KW_AND / KW_OR) __ expr)* __ s:concat_separator? __ or:order_by_clause? { + /* => { distinct: 'DISTINCT'; expr: expr; orderby?: order_by_clause; separator?: concat_separator; } */ + const len = tail.length + let result = c + result.parentheses = true + for (let i = 0; i < len; ++i) { + result = createBinaryExpr(tail[i][1], result, tail[i][3]) + } + return { + distinct: d, + expr: result, + orderby: or, + separator: s + }; + } + / d:KW_DISTINCT? __ c:or_and_expr __ s:concat_separator? __ or:order_by_clause? { + /* => { distinct: 'DISTINCT'; expr: expr; orderby?: order_by_clause; separator?: concat_separator; } */ + return { distinct: d, expr: c, orderby: or, separator: s }; + } + +count_arg + = e:star_expr { /* => { expr: star_expr } */ return { expr: e }; } + / distinct_args + +aggr_array_agg + = pre:(ident __ DOT)? __ name:(KW_ARRAY_AGG / KW_STRING_AGG) __ LPAREN __ arg:distinct_args __ RPAREN { + // => { type: 'aggr_func'; args:count_arg; name: 'ARRAY_AGG' | 'STRING_AGG'; } + return { + type: 'aggr_func', + name: pre ? `${pre[0]}.${name}` : name, + args: arg, + }; + } + +star_expr + = "*" { /* => { type: 'star'; value: '*' } */ return { type: 'star', value: '*' }; } + +trim_position + = 'BOTH'i / 'LEADING'i / 'TRAILING'i + +trim_rem + = p:trim_position? __ rm:literal_string? __ k:KW_FROM { + // => expr_list + let value = [] + if (p) value.push({type: 'origin', value: p }) + if (rm) value.push(rm) + value.push({type: 'origin', value: 'from' }) + return { + type: 'expr_list', + value, + } + } + +trim_func_clause + = 'trim'i __ LPAREN __ tr:trim_rem? __ s:expr __ RPAREN { + // => { type: 'function'; name: proc_func_name; args: expr_list; } + let args = tr || { type: 'expr_list', value: [] } + args.value.push(s) + return { + type: 'function', + name: { name: [{ type: 'origin', value: 'trim' }] }, + args, + ...getLocationObject(), + }; + } + +tablefunc_clause + = name:('crosstab'i / 'jsonb_to_recordset'i / 'jsonb_to_record'i / 'json_to_recordset'i / 'json_to_record'i) __ LPAREN __ s:expr_list __ RPAREN __ d:(KW_AS __ ident_name __ LPAREN __ column_data_type_list __ RPAREN)? { + // => { type: 'tablefunc'; name: proc_func_name; args: expr_list; as: func_call } + return { + type: 'tablefunc', + name: { name: [{ type: 'default', value: name }] }, + args: s, + as: d && { + type: 'function', + name: { name: [{ type: 'default', value: d[2] }]}, + args: { type: 'expr_list', value: d[6].map(v => ({ ...v, type: 'column_definition' })) }, + ...getLocationObject(), + }, + ...getLocationObject(), + } + } + +func_call + = trim_func_clause / tablefunc_clause + / name:'now'i __ LPAREN __ l:expr_list? __ RPAREN __ 'at'i __ KW_TIME __ 'zone'i __ z:literal_string { + // => { type: 'function'; name: proc_func_name; args: expr_list; suffix: literal_string; } + z.prefix = 'at time zone' + return { + type: 'function', + name: { name: [{ type: 'default', value: name }] }, + args: l ? l: { type: 'expr_list', value: [] }, + suffix: z, + ...getLocationObject(), + }; + } + / name:scalar_func __ LPAREN __ l:expr_list? __ RPAREN __ bc:over_partition? { + // => { type: 'function'; name: proc_func_name; args: expr_list; over?: over_partition; } + return { + type: 'function', + name: { name: [{ type: 'origin', value: name }] }, + args: l ? l: { type: 'expr_list', value: [] }, + over: bc, + ...getLocationObject(), + }; + } + / extract_func + / f:scalar_time_func __ up:on_update_current_timestamp? { + // => { type: 'function'; name: proc_func_name; over?: on_update_current_timestamp; } + return { + type: 'function', + name: { name: [{ type: 'origin', value: f }] }, + over: up, + ...getLocationObject(), + } + } + / name:proc_func_name __ LPAREN __ l:or_and_where_expr? __ RPAREN { + // => { type: 'function'; name: proc_func_name; args: expr_list; } + if (l && l.type !== 'expr_list') l = { type: 'expr_list', value: [l] } + return { + type: 'function', + name: name, + args: l ? l: { type: 'expr_list', value: [] }, + ...getLocationObject(), + }; + } + +extract_filed + = f:('CENTURY'i / 'DAY'i / 'DATE'i / 'DECADE'i / 'DOW'i / 'DOY'i / 'EPOCH'i / 'HOUR'i / 'ISODOW'i / 'ISOYEAR'i / 'MICROSECONDS'i / 'MILLENNIUM'i / 'MILLISECONDS'i / 'MINUTE'i / 'MONTH'i / 'QUARTER'i / 'SECOND'i / 'TIMEZONE'i / 'TIMEZONE_HOUR'i / 'TIMEZONE_MINUTE'i / 'WEEK'i / 'YEAR'i) { + // => 'string' + return f + } +extract_func + = kw:KW_EXTRACT __ LPAREN __ f:extract_filed __ KW_FROM __ t:(KW_TIMESTAMP / KW_INTERVAL / KW_TIME / KW_DATE)? __ s:expr __ RPAREN { + // => { type: 'extract'; args: { field: extract_filed; cast_type: 'TIMESTAMP' | 'INTERVAL' | 'TIME'; source: expr; }} + return { + type: kw.toLowerCase(), + args: { + field: f, + cast_type: t, + source: s, + }, + ...getLocationObject(), + } + } + / kw:KW_EXTRACT __ LPAREN __ f:extract_filed __ KW_FROM __ s:expr __ RPAREN { + // => { type: 'extract'; args: { field: extract_filed; source: expr; }} + return { + type: kw.toLowerCase(), + args: { + field: f, + source: s, + }, + ...getLocationObject(), + } + } + +scalar_time_func + = KW_CURRENT_DATE + / KW_CURRENT_TIME + / KW_CURRENT_TIMESTAMP + +scalar_func + = scalar_time_func + / KW_CURRENT_USER + / KW_USER + / KW_SESSION_USER + / KW_SYSTEM_USER + / "NTILE"i + +cast_double_colon + = s:KW_DOUBLE_COLON __ t:data_type __ alias:alias_clause? { + /* => { + as?: alias_clause, + symbol: '::' | 'as', + target: data_type; + } + */ + return { + as: alias, + symbol: '::', + target: t, + } + } +cast_expr + = c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ t:data_type __ RPAREN { + // => IGNORE + return { + type: 'cast', + keyword: c.toLowerCase(), + expr: e, + symbol: 'as', + target: t, + }; + } + / c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ KW_DECIMAL __ LPAREN __ precision:int __ RPAREN __ RPAREN { + // => IGNORE + return { + type: 'cast', + keyword: c.toLowerCase(), + expr: e, + symbol: 'as', + target: { + dataType: 'DECIMAL(' + precision + ')' + } + }; + } + / c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ KW_DECIMAL __ LPAREN __ precision:int __ COMMA __ scale:int __ RPAREN __ RPAREN { + // => IGNORE + return { + type: 'cast', + keyword: c.toLowerCase(), + expr: e, + symbol: 'as', + target: { + dataType: 'DECIMAL(' + precision + ', ' + scale + ')' + } + }; + } + / c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ s:signedness __ t:KW_INTEGER? __ RPAREN { /* MySQL cast to un-/signed integer */ + // => IGNORE + return { + type: 'cast', + keyword: c.toLowerCase(), + expr: e, + symbol: 'as', + target: { + dataType: s + (t ? ' ' + t: '') + } + }; + } + / LPAREN __ e:(or_expr / column_ref_array_index / param) __ RPAREN __ c:cast_double_colon? { + /* => { + type: 'cast'; + expr: or_expr | column_ref | param + | expr; + keyword: 'cast'; + } & cast_double_colon + */ + e.parentheses = true + if (!c) return e + return { + type: 'cast', + keyword: 'cast', + expr: e, + ...c, + } + } + / e:(column_ref_quoted / literal / aggr_func / window_func / func_call / case_expr / interval_expr / column_ref_array_index / param) __ c:cast_double_colon? { + /* => ({ + type: 'cast'; + expr: literal | jsonb_expr | aggr_func | func_call | case_expr | interval_expr | column_ref | param + | expr; + keyword: 'cast'; + } & cast_double_colon) + */ + if (!c) return e + return { + type: 'cast', + keyword: 'cast', + expr: e, + ...c, + } + } + + +signedness + = KW_SIGNED + / KW_UNSIGNED + +literal + = literal_string + / literal_numeric + / literal_bool + / literal_null + / literal_datetime + / literal_array + +literal_array + = s:KW_ARRAY __ LBRAKE __ c:expr_list? __ RBRAKE { + /* + => { + expr_list: expr_list | {type: 'origin', value: ident }, + type: string, + keyword: string, + brackets: boolean + } + */ + return { + expr_list: c || { type: 'origin', value: '' }, + type: 'array', + keyword: 'array', + brackets: true + } + } + +literal_list + = head:literal tail:(__ COMMA __ literal)* { + // => literal[] + return createList(head, tail); + } + +literal_null + = KW_NULL { + // => { type: 'null'; value: null } + return { type: 'null', value: null }; + } + +literal_not_null + = KW_NOT_NULL { + // => { type: 'not null'; value: 'not null' } + return { + type: 'not null', + value: 'not null', + } + } + +literal_bool + = KW_TRUE { + // => { type: 'bool', value: true } + return { type: 'bool', value: true }; + } + / KW_FALSE { + //=> { type: 'bool', value: false } + return { type: 'bool', value: false }; + } + +literal_string + = ca:("'" single_char* "'") [\n]+ __ fs:("'" single_char* "'") { + // => { type: 'single_quote_string'; value: string; } + return { + type: 'single_quote_string', + value: `${ca[1].join('')}${fs[1].join('')}` + }; + } + / ca:("'" single_char* "'") { + // => { type: 'single_quote_string'; value: string; } + return { + type: 'single_quote_string', + value: ca[1].join('') + }; + } + / literal_double_quoted_string + +literal_double_quoted_string + = ca:("\"" single_quote_char* "\"") !DOT { + // => { type: 'string'; value: string; } + return { + type: 'double_quote_string', + value: ca[1].join('') + }; + } + +literal_datetime + = type:(KW_TIME / KW_DATE / KW_TIMESTAMP / KW_DATETIME) __ ca:("'" single_char* "'") { + // => { type: 'TIME' | 'DATE' | 'TIMESTAMP' | 'DATETIME', value: string } + return { + type: type.toLowerCase(), + value: ca[1].join('') + }; + } + / type:(KW_TIME / KW_DATE / KW_TIMESTAMP / KW_DATETIME) __ ca:("\"" single_quote_char* "\"") { + // => { type: 'TIME' | 'DATE' | 'TIMESTAMP' | 'DATETIME', value: string } + return { + type: type.toLowerCase(), + value: ca[1].join('') + }; + } + +single_quote_char + = [^"\\\0-\x1F\x7f] + / escape_char + +single_char + = [^'\\] + / escape_char + +escape_char + = "\\'" { return "\\'"; } + / '\\"' { return '\\"'; } + / "\\\\" { return "\\\\"; } + / "\\/" { return "\\/"; } + / "\\b" { return "\b"; } + / "\\f" { return "\f"; } + / "\\n" { return "\n"; } + / "\\r" { return "\r"; } + / "\\t" { return "\t"; } + / "\\u" h1:hexDigit h2:hexDigit h3:hexDigit h4:hexDigit { + return String.fromCharCode(parseInt("0x" + h1 + h2 + h3 + h4)); + } + / "\\" { return "\\"; } + / "''" { return "''" } + +line_terminator + = [\n\r] + +literal_numeric + = n:number { + // => number | { type: 'bigint'; value: string; } + if (n && n.type === 'bigint') return n + return { type: 'number', value: n }; + } + +number + = int_:int? frac:frac exp:exp { + const numStr = (int_ || '') + frac + exp + return { + type: 'bigint', + value: numStr + } + } + / int_:int? frac:frac { + // => IGNORE + const numStr = (int_ || '') + frac + if (int_ && isBigInt(int_)) return { + type: 'bigint', + value: numStr + } + return parseFloat(numStr); + } + / int_:int exp:exp { + // => IGNORE + const numStr = int_ + exp + return { + type: 'bigint', + value: numStr + } + } + / int_:int { + // => IGNORE + if (isBigInt(int_)) return { + type: 'bigint', + value: int_ + } + return parseFloat(int_); + } + +int + = digits + / digit:digit + / op:("-" / "+" ) digits:digits { return op + digits; } + / op:("-" / "+" ) digit:digit { return op + digit; } + +frac + = "." digits:digits { return "." + digits; } + +exp + = e:e digits:digits { return e + digits; } + +digits + = digits:digit+ { return digits.join(""); } + +digit = [0-9] + +hexDigit + = [0-9a-fA-F] + +e + = e:[eE] sign:[+-]? { return e + (sign !== null ? sign: ''); } + + +KW_NULL = "NULL"i !ident_start +KW_DEFAULT = "DEFAULT"i !ident_start +KW_NOT_NULL = "NOT NULL"i !ident_start +KW_TRUE = "TRUE"i !ident_start +KW_TO = "TO"i !ident_start +KW_FALSE = "FALSE"i !ident_start + +KW_SHOW = "SHOW"i !ident_start +KW_DROP = "DROP"i !ident_start { return 'DROP'; } +KW_USE = "USE"i !ident_start +KW_ALTER = "ALTER"i !ident_start +KW_SELECT = "SELECT"i !ident_start +KW_UPDATE = "UPDATE"i !ident_start +KW_CREATE = "CREATE"i !ident_start +KW_TEMPORARY = "TEMPORARY"i !ident_start +KW_TEMP = "TEMP"i !ident_start +KW_DELETE = "DELETE"i !ident_start +KW_INSERT = "INSERT"i !ident_start +KW_RECURSIVE= "RECURSIVE" !ident_start { return 'RECURSIVE'; } +KW_REPLACE = "REPLACE"i !ident_start +KW_RETURNING = "RETURNING"i !ident_start { return 'RETURNING' } +KW_RENAME = "RENAME"i !ident_start +KW_IGNORE = "IGNORE"i !ident_start +KW_EXPLAIN = "EXPLAIN"i !ident_start +KW_PARTITION = "PARTITION"i !ident_start { return 'PARTITION' } + +KW_INTO = "INTO"i !ident_start +KW_FROM = "FROM"i !ident_start +KW_SET = "SET"i !ident_start { return 'SET' } +KW_LOCK = "LOCK"i !ident_start + +KW_AS = "AS"i !ident_start +KW_TABLE = "TABLE"i !ident_start { return 'TABLE'; } +KW_DATABASE = "DATABASE"i !ident_start { return 'DATABASE'; } +KW_SCHEMA = "SCHEMA"i !ident_start { return 'SCHEMA'; } +KW_SEQUENCE = "SEQUENCE"i !ident_start { return 'SEQUENCE'; } +KW_TABLESPACE = "TABLESPACE"i !ident_start { return 'TABLESPACE'; } +KW_COLLATE = "COLLATE"i !ident_start { return 'COLLATE'; } +KW_COLLATION = "COLLATION"i !ident_start { return 'COLLATION'; } +KW_DEALLOCATE = "DEALLOCATE"i !ident_start { return 'DEALLOCATE'; } + +KW_ON = "ON"i !ident_start +KW_LEFT = "LEFT"i !ident_start +KW_RIGHT = "RIGHT"i !ident_start +KW_FULL = "FULL"i !ident_start +KW_INNER = "INNER"i !ident_start +KW_JOIN = "JOIN"i !ident_start +KW_OUTER = "OUTER"i !ident_start +KW_UNION = "UNION"i !ident_start +KW_INTERSECT = "INTERSECT"i !ident_start +KW_EXCEPT = "EXCEPT"i !ident_start +KW_VALUES = "VALUES"i !ident_start +KW_USING = "USING"i !ident_start + +KW_WHERE = "WHERE"i !ident_start +KW_WITH = "WITH"i !ident_start + +KW_GROUP = "GROUP"i !ident_start +KW_BY = "BY"i !ident_start +KW_ORDER = "ORDER"i !ident_start +KW_HAVING = "HAVING"i !ident_start +KW_WINDOW = "WINDOW"i !ident_start + +KW_LIMIT = "LIMIT"i !ident_start +KW_OFFSET = "OFFSET"i !ident_start { return 'OFFSET' } + +KW_ASC = "ASC"i !ident_start { return 'ASC'; } +KW_DESC = "DESC"i !ident_start { return 'DESC'; } + +KW_ALL = "ALL"i !ident_start { return 'ALL'; } +KW_DISTINCT = "DISTINCT"i !ident_start { return 'DISTINCT';} + +KW_BETWEEN = "BETWEEN"i !ident_start { return 'BETWEEN'; } +KW_IN = "IN"i !ident_start { return 'IN'; } +KW_IS = "IS"i !ident_start { return 'IS'; } +KW_LIKE = "LIKE"i !ident_start { return 'LIKE'; } +KW_ILIKE = "ILIKE"i !ident_start { return 'ILIKE'; } +KW_EXISTS = "EXISTS"i !ident_start { /* => 'EXISTS' */ return 'EXISTS'; } + +KW_NOT = "NOT"i !ident_start { return 'NOT'; } +KW_AND = "AND"i !ident_start { return 'AND'; } +KW_OR = "OR"i !ident_start { return 'OR'; } + +KW_ARRAY = "ARRAY"i !ident_start { return 'ARRAY'; } +KW_ARRAY_AGG = "ARRAY_AGG"i !ident_start { return 'ARRAY_AGG'; } +KW_STRING_AGG = "STRING_AGG"i !ident_start { return 'STRING_AGG'; } +KW_COUNT = "COUNT"i !ident_start { return 'COUNT'; } +KW_GROUP_CONCAT = "GROUP_CONCAT"i !ident_start { return 'GROUP_CONCAT'; } +KW_MAX = "MAX"i !ident_start { return 'MAX'; } +KW_MIN = "MIN"i !ident_start { return 'MIN'; } +KW_SUM = "SUM"i !ident_start { return 'SUM'; } +KW_AVG = "AVG"i !ident_start { return 'AVG'; } + +KW_EXTRACT = "EXTRACT"i !ident_start { return 'EXTRACT'; } +KW_CALL = "CALL"i !ident_start { return 'CALL'; } + +KW_CASE = "CASE"i !ident_start +KW_WHEN = "WHEN"i !ident_start +KW_THEN = "THEN"i !ident_start +KW_ELSE = "ELSE"i !ident_start +KW_END = "END"i !ident_start + +KW_CAST = "CAST"i !ident_start { return 'CAST' } +KW_TRY_CAST = "TRY_CAST"i !ident_start { return 'TRY_CAST' } + +KW_BOOL = "BOOL"i !ident_start { return 'BOOL'; } +KW_BOOLEAN = "BOOLEAN"i !ident_start { return 'BOOLEAN'; } +KW_CHAR = "CHAR"i !ident_start { return 'CHAR'; } +KW_CHARACTER = "CHARACTER"i !ident_start { return 'CHARACTER'; } +KW_VARCHAR = "VARCHAR"i !ident_start { return 'VARCHAR';} +KW_NUMERIC = "NUMERIC"i !ident_start { return 'NUMERIC'; } +KW_DECIMAL = "DECIMAL"i !ident_start { return 'DECIMAL'; } +KW_SIGNED = "SIGNED"i !ident_start { return 'SIGNED'; } +KW_UNSIGNED = "UNSIGNED"i !ident_start { return 'UNSIGNED'; } +KW_INT = "INT"i !ident_start { return 'INT'; } +KW_ZEROFILL = "ZEROFILL"i !ident_start { return 'ZEROFILL'; } +KW_INTEGER = "INTEGER"i !ident_start { return 'INTEGER'; } +KW_JSON = "JSON"i !ident_start { return 'JSON'; } +KW_JSONB = "JSONB"i !ident_start { return 'JSONB'; } +KW_GEOMETRY = "GEOMETRY"i !ident_start { return 'GEOMETRY'; } +KW_SMALLINT = "SMALLINT"i !ident_start { return 'SMALLINT'; } +KW_SERIAL = "SERIAL"i !ident_start { return 'SERIAL'; } +KW_TINYINT = "TINYINT"i !ident_start { return 'TINYINT'; } +KW_TINYTEXT = "TINYTEXT"i !ident_start { return 'TINYTEXT'; } +KW_TEXT = "TEXT"i !ident_start { return 'TEXT'; } +KW_MEDIUMTEXT = "MEDIUMTEXT"i !ident_start { return 'MEDIUMTEXT'; } +KW_LONGTEXT = "LONGTEXT"i !ident_start { return 'LONGTEXT'; } +KW_MEDIUMINT = "MEDIUMINT"i !ident_start { return 'MEDIUMINT'; } +KW_BIGINT = "BIGINT"i !ident_start { return 'BIGINT'; } +KW_ENUM = "ENUM"i !ident_start { return 'ENUM'; } +KW_FLOAT = "FLOAT"i !ident_start { return 'FLOAT'; } +KW_DOUBLE = "DOUBLE"i !ident_start { return 'DOUBLE'; } +KW_BIGSERIAL = "BIGSERIAL"i !ident_start { return 'BIGSERIAL'; } +KW_REAL = "REAL"i !ident_start { return 'REAL'; } +KW_DATE = "DATE"i !ident_start { return 'DATE'; } +KW_DATETIME = "DATETIME"i !ident_start { return 'DATETIME'; } +KW_ROWS = "ROWS"i !ident_start { return 'ROWS'; } +KW_TIME = "TIME"i !ident_start { return 'TIME'; } +KW_TIMESTAMP = "TIMESTAMP"i!ident_start { return 'TIMESTAMP'; } +KW_TIMESTAMPTZ = "TIMESTAMPTZ"i!ident_start { return 'TIMESTAMPTZ'; } +KW_TRUNCATE = "TRUNCATE"i !ident_start { return 'TRUNCATE'; } +KW_USER = "USER"i !ident_start { return 'USER'; } +KW_UUID = "UUID"i !ident_start { return 'UUID'; } +KW_OID = "OID"i !ident_start { return 'OID'; } +KW_REGCLASS = "REGCLASS"i !ident_start { return 'REGCLASS'; } +KW_REGCOLLATION = "REGCOLLATION"i !ident_start { return 'REGCOLLATION'; } +KW_REGCONFIG = "REGCONFIG"i !ident_start { return 'REGCONFIG'; } +KW_REGDICTIONARY = "REGDICTIONARY"i !ident_start { return 'REGDICTIONARY'; } +KW_REGNAMESPACE = "REGNAMESPACE"i !ident_start { return 'REGNAMESPACE'; } +KW_REGOPER = "REGOPER"i !ident_start { return 'REGOPER'; } +KW_REGOPERATOR = "REGOPERATOR"i !ident_start { return 'REGOPERATOR'; } +KW_REGPROC = "REGPROC"i !ident_start { return 'REGPROC'; } +KW_REGPROCEDURE = "REGPROCEDURE"i !ident_start { return 'REGPROCEDURE'; } +KW_REGROLE = "REGROLE"i !ident_start { return 'REGROLE'; } +KW_REGTYPE = "REGTYPE"i !ident_start { return 'REGTYPE'; } + +KW_CURRENT_DATE = "CURRENT_DATE"i !ident_start { return 'CURRENT_DATE'; } +KW_ADD_DATE = "ADDDATE"i !ident_start { return 'ADDDATE'; } +KW_INTERVAL = "INTERVAL"i !ident_start { return 'INTERVAL'; } +KW_UNIT_YEAR = "YEAR"i !ident_start { return 'YEAR'; } +KW_UNIT_MONTH = "MONTH"i !ident_start { return 'MONTH'; } +KW_UNIT_DAY = "DAY"i !ident_start { return 'DAY'; } +KW_UNIT_HOUR = "HOUR"i !ident_start { return 'HOUR'; } +KW_UNIT_MINUTE = "MINUTE"i !ident_start { return 'MINUTE'; } +KW_UNIT_SECOND = "SECOND"i !ident_start { return 'SECOND'; } +KW_CURRENT_TIME = "CURRENT_TIME"i !ident_start { return 'CURRENT_TIME'; } +KW_CURRENT_TIMESTAMP= "CURRENT_TIMESTAMP"i !ident_start { return 'CURRENT_TIMESTAMP'; } +KW_CURRENT_USER = "CURRENT_USER"i !ident_start { return 'CURRENT_USER'; } +KW_CURRENT_ROLE = "CURRENT_ROLE"i !ident_start { return 'CURRENT_ROLE'; } +KW_SESSION_USER = "SESSION_USER"i !ident_start { return 'SESSION_USER'; } +KW_SYSTEM_USER = "SYSTEM_USER"i !ident_start { return 'SYSTEM_USER'; } + +KW_GLOBAL = "GLOBAL"i !ident_start { return 'GLOBAL'; } +KW_SESSION = "SESSION"i !ident_start { return 'SESSION'; } +KW_LOCAL = "LOCAL"i !ident_start { return 'LOCAL'; } +KW_PERSIST = "PERSIST"i !ident_start { return 'PERSIST'; } +KW_PERSIST_ONLY = "PERSIST_ONLY"i !ident_start { return 'PERSIST_ONLY'; } +KW_VIEW = "VIEW"i !ident_start { return 'VIEW'; } + +KW_VAR__PRE_AT = '@' +KW_VAR__PRE_AT_AT = '@@' +KW_VAR_PRE_DOLLAR = '$' +KW_VAR_PRE_DOLLAR_DOUBLE = '$$' +KW_VAR_PRE + = KW_VAR__PRE_AT_AT / KW_VAR__PRE_AT / KW_VAR_PRE_DOLLAR / KW_VAR_PRE_DOLLAR +KW_RETURN = 'return'i +KW_ASSIGN = ':=' +KW_DOUBLE_COLON = '::' +KW_ASSIGIN_EQUAL = '=' + +KW_DUAL = "DUAL"i + +// MySQL Alter +KW_ADD = "ADD"i !ident_start { return 'ADD'; } +KW_COLUMN = "COLUMN"i !ident_start { return 'COLUMN'; } +KW_INDEX = "INDEX"i !ident_start { return 'INDEX'; } +KW_KEY = "KEY"i !ident_start { return 'KEY'; } +KW_FULLTEXT = "FULLTEXT"i !ident_start { return 'FULLTEXT'; } +KW_SPATIAL = "SPATIAL"i !ident_start { return 'SPATIAL'; } +KW_UNIQUE = "UNIQUE"i !ident_start { return 'UNIQUE'; } +KW_KEY_BLOCK_SIZE = "KEY_BLOCK_SIZE"i !ident_start { return 'KEY_BLOCK_SIZE'; } +KW_COMMENT = "COMMENT"i !ident_start { return 'COMMENT'; } +KW_CONSTRAINT = "CONSTRAINT"i !ident_start { return 'CONSTRAINT'; } +KW_CONCURRENTLY = "CONCURRENTLY"i !ident_start { return 'CONCURRENTLY'; } +KW_REFERENCES = "REFERENCES"i !ident_start { return 'REFERENCES'; } + + + +// MySQL extensions to SQL +OPT_SQL_CALC_FOUND_ROWS = "SQL_CALC_FOUND_ROWS"i +OPT_SQL_CACHE = "SQL_CACHE"i +OPT_SQL_NO_CACHE = "SQL_NO_CACHE"i +OPT_SQL_SMALL_RESULT = "SQL_SMALL_RESULT"i +OPT_SQL_BIG_RESULT = "SQL_BIG_RESULT"i +OPT_SQL_BUFFER_RESULT = "SQL_BUFFER_RESULT"i + +//special character +DOT = '.' +COMMA = ',' +STAR = '*' +LPAREN = '(' +RPAREN = ')' + +LBRAKE = '[' +RBRAKE = ']' + +SEMICOLON = ';' +SINGLE_ARROW = '->' +DOUBLE_ARROW = '->>' +WELL_ARROW = '#>' +DOUBLE_WELL_ARROW = '#>>' + +OPERATOR_CONCATENATION = '||' +OPERATOR_AND = '&&' +LOGIC_OPERATOR = OPERATOR_CONCATENATION / OPERATOR_AND + +// separator +__ + = (whitespace / comment)* + +___ + = (whitespace / comment)+ + +comment + = block_comment + / line_comment + +block_comment + = "/*" (!"*/" !"/*" char / block_comment)* "*/" + +line_comment + = "--" (!EOL char)* + +pound_sign_comment + = "#" (!EOL char)* + +keyword_comment + = k:KW_COMMENT __ s:KW_ASSIGIN_EQUAL? __ c:literal_string { + // => { type: 'comment'; keyword: 'comment'; symbol: '='; value: literal_string; } + return { + type: k.toLowerCase(), + keyword: k.toLowerCase(), + symbol: s, + value: c, + } + } + +char = . + +interval_unit + = KW_UNIT_YEAR + / KW_UNIT_MONTH + / KW_UNIT_DAY + / KW_UNIT_HOUR + / KW_UNIT_MINUTE + / KW_UNIT_SECOND + +whitespace = + [ \t\n\r] + +EOL + = EOF + / [\n\r]+ + +EOF = !. + +//begin procedure extension +proc_stmts + = proc_stmt* + +proc_stmt + = &{ varList = []; return true; } __ s:(assign_stmt / return_stmt) { + /* export interface proc_stmt_t { type: 'proc'; stmt: assign_stmt | return_stmt; vars: any } + => AstStatement + */ + return { type: 'proc', stmt: s, vars: varList } + } + +assign_stmt_list + = head:assign_stmt tail:(__ COMMA __ assign_stmt)* { + // => assign_stmt[] + return createList(head, tail); + } + +assign_stmt + = va:(var_decl / without_prefix_var_decl) __ s:(KW_ASSIGN / KW_ASSIGIN_EQUAL / KW_TO) __ e:proc_expr { + // => { type: 'assign'; left: var_decl | without_prefix_var_decl; symbol: ':=' | '='; right: proc_expr; } + return { + type: 'assign', + left: va, + symbol: Array.isArray(s) ? s[0] : s, + right: e + }; + } + + +return_stmt + = KW_RETURN __ e:proc_expr { + // => { type: 'return'; expr: proc_expr; } + return { type: 'return', expr: e }; + } + +proc_expr + = select_stmt + / proc_join + / proc_additive_expr + / proc_array + +proc_additive_expr + = head:proc_multiplicative_expr + tail:(__ additive_operator __ proc_multiplicative_expr)* { + // => binary_expr + return createBinaryExprChain(head, tail); + } + +proc_multiplicative_expr + = head:proc_primary + tail:(__ multiplicative_operator __ proc_primary)* { + // => binary_expr + return createBinaryExprChain(head, tail); + } + +proc_join + = lt:var_decl __ op:join_op __ rt:var_decl __ expr:on_clause { + // => { type: 'join'; ltable: var_decl; rtable: var_decl; op: join_op; expr: on_clause; } + return { + type: 'join', + ltable: lt, + rtable: rt, + op: op, + on: expr + }; + } + +proc_primary + = literal + / var_decl + / proc_func_call + / param + / LPAREN __ e:proc_additive_expr __ RPAREN { + // => proc_additive_expr & { parentheses: true; } + e.parentheses = true; + return e; + } + / n:ident_name s:(DOT __ ident_name)? { + // => { type: 'var'; prefix: null; name: number; members: []; quoted: null } | column_ref + if (!s) return { + type: 'var', + name: n, + prefix: null + } + return { + type: 'column_ref', + table: n, + column: s[2] + } + } + +proc_func_name + = dt:ident_without_kw_type tail:(__ DOT __ ident_without_kw_type)? { + // => { schema?: ident_without_kw_type, name: ident_without_kw_type } + const result = { name: [dt] } + if (tail !== null) { + result.schema = dt + result.name = [tail[3]] + } + return result + } + +proc_func_call + = name:proc_func_name __ LPAREN __ l:proc_primary_list? __ RPAREN { + // => { type: 'function'; name: string; args: null | { type: expr_list; value: proc_primary_list; }} + //compatible with original func_call + return { + type: 'function', + name: name, + args: { + type: 'expr_list', + value: l + }, + ...getLocationObject(), + }; + } + +proc_primary_list + = head:proc_primary tail:(__ COMMA __ proc_primary)* { + // => proc_primary[] + return createList(head, tail); + } + +proc_array + = LBRAKE __ l:proc_primary_list __ RBRAKE { + // => { type: 'array'; value: proc_primary_list } + return { type: 'array', value: l }; + } + +var_decl_list + = head:var_decl tail:(__ COMMA __ var_decl)* { + // => var_decl[] + return createList(head, tail) + } + +var_decl + = p:KW_VAR_PRE_DOLLAR_DOUBLE d:[^$]* s:KW_VAR_PRE_DOLLAR_DOUBLE { + // => { type: 'var'; name: string; prefix: string; suffix: string; } + return { + type: 'var', + name: d.join(''), + prefix: '$$', + suffix: '$$' + }; + } + / KW_VAR_PRE_DOLLAR f:column KW_VAR_PRE_DOLLAR d:[^$]* KW_VAR_PRE_DOLLAR s:column !{ if (f !== s) return true } KW_VAR_PRE_DOLLAR { + // => { type: 'var'; name: string; prefix: string; suffix: string; } + return { + type: 'var', + name: d.join(''), + prefix: `$${f}$`, + suffix: `$${s}$` + }; + } + / p:KW_VAR_PRE d: without_prefix_var_decl { + // => without_prefix_var_decl & { type: 'var'; prefix: string; } + // push for analysis + return { + type: 'var', + ...d, + prefix: p + }; + } + +without_prefix_var_decl + = p:'"'? name:ident_name m:mem_chain s:'"'? { + // => { type: 'var'; prefix: string; name: ident_name; members: mem_chain; quoted: string | null } + //push for analysis + if ((p && !s) || (!p && s)) throw new Error('double quoted not match') + varList.push(name); + return { + type: 'var', + name: name, + members: m, + quoted: p && s ? '"' : null, + prefix: null, + }; + } + / n:literal_numeric { + // => { type: 'var'; prefix: null; name: number; members: []; quoted: null } + return { + type: 'var', + name: n.value, + members: [], + quoted: null, + prefix: null, + } + } + +mem_chain + = l:('.' ident_name)* { + // => ident_name[]; + const s = []; + for (let i = 0; i < l.length; i++) { + s.push(l[i][1]); + } + return s; + } + +data_type + = array_type + / character_string_type + / numeric_type + / datetime_type + / json_type + / geometry_type + / text_type + / uuid_type + / boolean_type + / enum_type + / serial_interval_type + / binary_type + / oid_type + / record_type + / custom_types + + +array_type + = t:(numeric_type / character_string_type) __ LBRAKE __ RBRAKE __ LBRAKE __ RBRAKE { + /* => data_type */ + return { ...t, array: { dimension: 2 } } + } + / t:(numeric_type / character_string_type) __ LBRAKE __ l:literal_numeric? __ RBRAKE { + /* => data_type */ + return { ...t, array: { dimension: 1, length: [l] } } + } + / t:(numeric_type / character_string_type) __ KW_ARRAY { + /* => data_type */ + return { ...t, array: { keyword: 'array' } } + } + + +boolean_type + = t:(KW_BOOL / KW_BOOLEAN) { /* => data_type */ return { dataType: t }} + +binary_type + = 'bytea'i { /* => data_type */ return { dataType: 'BYTEA' }; } + +character_varying + = KW_CHARACTER __ ('varying'i)? { + // => string + return 'CHARACTER VARYING' + } +character_string_type + = t:(KW_CHAR / KW_VARCHAR / character_varying) num:(__ LPAREN __ [0-9]+ __ RPAREN)? { + // => data_type + const result = { dataType: t } + if (num) { + result.length = parseInt(num[3].join(''), 10) + result.parentheses = true + } + return result + } + +numeric_type_suffix + = un: KW_UNSIGNED? __ ze: KW_ZEROFILL? { + // => any[]; + const result = [] + if (un) result.push(un) + if (ze) result.push(ze) + return result + } +numeric_type + = t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_TINYINT / KW_MEDIUMINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE __ 'PRECISION'i / KW_DOUBLE / KW_SERIAL / KW_BIGSERIAL / KW_REAL) __ LPAREN __ l:[0-9]+ __ r:(COMMA __ [0-9]+)? __ RPAREN __ s:numeric_type_suffix? { /* => data_type */ return { dataType: Array.isArray(t) ? `${t[0].toUpperCase()} ${t[2].toUpperCase()}` : t, length: parseInt(l.join(''), 10), scale: r && parseInt(r[2].join(''), 10), parentheses: true, suffix: s }; } + / t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_TINYINT / KW_MEDIUMINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE __ 'PRECISION'i / KW_DOUBLE / KW_SERIAL / KW_BIGSERIAL / KW_REAL)l:[0-9]+ __ s:numeric_type_suffix? { /* => data_type */ return { dataType: Array.isArray(t) ? `${t[0].toUpperCase()} ${t[2].toUpperCase()}` : t, length: parseInt(l.join(''), 10), suffix: s }; } + / t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_TINYINT / KW_MEDIUMINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE __ 'PRECISION'i / KW_DOUBLE / KW_SERIAL / KW_BIGSERIAL / KW_REAL) __ s:numeric_type_suffix? __{ /* => data_type */ return { dataType: Array.isArray(t) ? `${t[0].toUpperCase()} ${t[2].toUpperCase()}` : t, suffix: s }; } + +oid_type + = t:(KW_OID / KW_REGCLASS / KW_REGCOLLATION / KW_REGCONFIG / KW_REGDICTIONARY / KW_REGNAMESPACE / KW_REGOPER / KW_REGOPERATOR / KW_REGPROC / KW_REGPROCEDURE / KW_REGROLE / KW_REGTYPE) { /* => data_type */ return { dataType: t }} + +timezone + = w:('WITHOUT'i / 'WITH'i) __ KW_TIME __ 'ZONE'i { + // => string[]; + return [w.toUpperCase(), 'TIME', 'ZONE'] + } + +time_type + = t:(KW_TIME / KW_TIMESTAMP / KW_TIMESTAMPTZ) num:(__ LPAREN __ [0-9]+ __ RPAREN )? __ tz:timezone? { + /* => data_type */ + const result = { dataType: t } + if (num) { + result.length = parseInt(num[3].join(''), 10) + result.parentheses = true + } + if (tz) result.suffix = tz + return result + } + +datetime_type + = t:(KW_DATE / KW_DATETIME) num:(__ LPAREN __ [0-9]+ __ RPAREN)? { + /* => data_type */ + const result = { dataType: t } + if (num) { + result.length = parseInt(num[3].join(''), 10) + result.parentheses = true + } + return result + } + / time_type + +enum_type + = t:KW_ENUM __ e:value_item { + /* => data_type */ + e.parentheses = true + return { + dataType: t, + expr: e + } + } + +json_type + = t:(KW_JSON / KW_JSONB) { /* => data_type */ return { dataType: t }; } + +geometry_type + = t:KW_GEOMETRY {/* => data_type */ return { dataType: t }; } + +serial_interval_type + = t:(KW_SERIAL / KW_INTERVAL) { /* => data_type */ return { dataType: t }; } + +text_type + = t:(KW_TINYTEXT / KW_TEXT / KW_MEDIUMTEXT / KW_LONGTEXT) s:(LBRAKE __ RBRAKE)? { + /* => data_type */ + return { dataType: `${t}${s ? '[]' : ''}` } + } + +uuid_type + = t:KW_UUID {/* => data_type */ return { dataType: t }} + +record_type + = 'RECORD'i {/* => data_type */ return { dataType: 'RECORD' }} + +custom_types + = name:ident_name &{ return customTypes.has(name) } { + // => data_type + return { dataType: name } + } diff --git a/src/parser.all.js b/src/parser.all.js index 91315813..0c8548b7 100644 --- a/src/parser.all.js +++ b/src/parser.all.js @@ -7,6 +7,7 @@ import { parse as mysql } from '../pegjs/mysql.pegjs' import { parse as mariadb } from '../pegjs/mariadb.pegjs' import { parse as noql } from '../pegjs/noql.pegjs' import { parse as postgresql } from '../pegjs/postgresql.pegjs' +import { parse as datafusionsql } from '../pegjs/datafusionsql.pegjs' import { parse as redshift } from '../pegjs/redshift.pegjs' import { parse as sqlite } from '../pegjs/sqlite.pegjs' import { parse as transactsql } from '../pegjs/transactsql.pegjs' @@ -23,6 +24,7 @@ export default { mariadb, noql, postgresql, + datafusionsql, redshift, snowflake, sqlite, diff --git a/test/datafusion.spec.js b/test/datafusion.spec.js new file mode 100644 index 00000000..83617392 --- /dev/null +++ b/test/datafusion.spec.js @@ -0,0 +1,2166 @@ +const { expect } = require('chai') +const { conflictToSQL } = require('../src/insert') +const { procToSQL } = require('../src/proc') +const Parser = require('../src/parser').default + +describe('Datafusion', () => { + const parser = new Parser(); + const opt = { + database: 'datafusionsql' + } + + function getParsedSql(sql, opt) { + const ast = parser.astify(sql, opt); + return parser.sqlify(ast, opt); + } + + const SQL_LIST = [ + { + title: 'select with_query_name', + sql: [ + `WITH + subQ1 AS (SELECT * FROM Roster WHERE SchoolID = 52), + subQ2 AS (SELECT SchoolID FROM subQ1) + SELECT DISTINCT * FROM subQ2;`, + `WITH "subQ1" AS (SELECT * FROM "Roster" WHERE SchoolID = 52), "subQ2" AS (SELECT SchoolID FROM "subQ1") SELECT DISTINCT * FROM "subQ2"` + ] + }, + { + title: 'select subquery', + sql: [ + `SELECT AVG ( PointsScored ) + FROM + ( SELECT PointsScored + FROM Stats + WHERE SchoolID = 77 )`, + 'SELECT AVG(PointsScored) FROM (SELECT PointsScored FROM "Stats" WHERE SchoolID = 77)' + ] + }, + { + title: 'select subquery have alias', + sql: [ + `SELECT r.LastName + FROM + ( SELECT * FROM Roster) AS r`, + 'SELECT "r".LastName FROM (SELECT * FROM "Roster") AS "r"' + ] + }, + { + title: 'select implicit "comma cross join"', + sql: [ + 'SELECT * FROM Roster, TeamMascot', + 'SELECT * FROM "Roster", "TeamMascot"' + ] + }, + { + title: 'select inner join using', + sql: [ + `SELECT FirstName + FROM Roster INNER JOIN PlayerStats + USING (LastName);`, + 'SELECT FirstName FROM "Roster" INNER JOIN "PlayerStats" USING ("LastName")' + ] + }, + { + title: 'set op UNION', + sql: [ + `(SELECT s FROM t1) UNION (SELECT s FROM t2)`, + '(SELECT s FROM "t1") UNION (SELECT s FROM "t2")', + ], + }, + { + title: 'set op UNION ALL', + sql: [ + `(SELECT s FROM t1) UNION ALL (SELECT s FROM t2)`, + '(SELECT s FROM "t1") UNION ALL (SELECT s FROM "t2")', + ], + }, + { + title: 'set op UNION DISTINCT', + sql: [ + `(SELECT s FROM t1) UNION DISTINCT (SELECT s FROM t2)`, + '(SELECT s FROM "t1") UNION DISTINCT (SELECT s FROM "t2")', + ], + }, + { + title: 'Window Fns with qualified frame clause', + sql: [ + `SELECT + first_name, + SUM(user_age) OVER (PARTITION BY user_city ORDER BY created_at DESC) AS age_window + FROM roster`, + 'SELECT first_name, SUM(user_age) OVER (PARTITION BY user_city ORDER BY created_at DESC) AS "age_window" FROM "roster"' + ] + }, + { + title: 'Window Fns', + sql: [ + `SELECT + first_name, + SUM(user_age) OVER (PARTITION BY user_city ORDER BY created_at) AS age_window + FROM roster`, + 'SELECT first_name, SUM(user_age) OVER (PARTITION BY user_city ORDER BY created_at ASC) AS "age_window" FROM "roster"' + ] + }, + { + title: 'Window Fns + ROWS following', + sql: [ + `SELECT + first_name, + SUM(user_age) OVER ( + PARTITION BY user_city + ORDER BY created_at ASC + ROWS 1 FOLLOWING + ) AS age_window + FROM roster`, + 'SELECT first_name, SUM(user_age) OVER (PARTITION BY user_city ORDER BY created_at ASC ROWS 1 FOLLOWING) AS "age_window" FROM "roster"' + ] + }, + { + title: 'Window Fns + ROWS unbounded following', + sql: [ + `SELECT + first_name, + SUM(user_age) OVER ( + PARTITION BY user_city + ROWS UNbounded FOLLOWING + ) AS age_window + FROM roster`, + 'SELECT first_name, SUM(user_age) OVER (PARTITION BY user_city ROWS UNBOUNDED FOLLOWING) AS "age_window" FROM "roster"' + ] + }, + { + title: 'Window Fns + ROWS unbounded preceding', + sql: [ + `SELECT + first_name, + SUM(user_age) OVER ( + PARTITION BY user_city + ORDER BY created_at ASC + ROWS UNbounded preceding + ) AS age_window + FROM roster`, + 'SELECT first_name, SUM(user_age) OVER (PARTITION BY user_city ORDER BY created_at ASC ROWS UNBOUNDED PRECEDING) AS "age_window" FROM "roster"' + ] + }, + { + title: 'Window Fns + ROWS between', + sql: [ + `SELECT + "first_name", + SUM(user_age) OVER ( + PARTITION BY user_city + ORDER BY created_at DESC + ROWS BETWEEN 1 preceding AND 5 FOLLOWING + ) AS age_window, + SUM(user_age) OVER ( + PARTITION BY user_city + ORDER BY created_at DESC + ROWS BETWEEN unbounded preceding AND unbounded following + ) AS age_window2 + FROM roster`, + 'SELECT "first_name", SUM(user_age) OVER (PARTITION BY user_city ORDER BY created_at DESC ROWS BETWEEN 1 PRECEDING AND 5 FOLLOWING) AS "age_window", SUM(user_age) OVER (PARTITION BY user_city ORDER BY created_at DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS "age_window2" FROM "roster"' + ] + }, + { + title: 'Window Fns + ROWS unbounded preceding + current row', + sql: [ + `SELECT + first_name, + SUM(user_age) OVER ( + PARTITION BY user_city + ORDER BY created_at, user_id ASC + ROWS BETWEEN UNbounded preceding AND CURRENT ROW + ) AS age_window + FROM roster`, + 'SELECT first_name, SUM(user_age) OVER (PARTITION BY user_city ORDER BY created_at ASC, user_id ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS "age_window" FROM "roster"' + ] + }, + { + title: 'Window Fns + RANKING', + sql: [ + `SELECT + ROW_NUMBER() OVER ( + PARTITION BY user_city + ORDER BY created_at + ) AS age_window + FROM roster`, + 'SELECT ROW_NUMBER() OVER (PARTITION BY user_city ORDER BY created_at ASC) AS "age_window" FROM "roster"' + ] + }, + { + title: 'Window Fns + DENSE_RANK w/ empty OVER', + sql: [ + `SELECT + DENSE_RANK() OVER () AS age_window + FROM roster`, + 'SELECT DENSE_RANK() OVER () AS "age_window" FROM "roster"' + ] + }, + { + title: 'Window Fns + LAG', + sql: [ + `SELECT + LAG(user_name, 10) OVER ( + PARTITION BY user_city + ORDER BY created_at + ) AS age_window + FROM roster`, + 'SELECT LAG(user_name, 10) OVER (PARTITION BY user_city ORDER BY created_at ASC) AS "age_window" FROM "roster"' + ] + }, + { + title: 'Window Fns + LEAD', + sql: [ + `SELECT + LEAD("user_name", 10) OVER ( + PARTITION BY user_city + ORDER BY "created_at" + ) AS age_window + FROM roster`, + 'SELECT LEAD("user_name", 10) OVER (PARTITION BY user_city ORDER BY "created_at" ASC) AS "age_window" FROM "roster"' + ] + }, + { + title: 'Window Fns + NTH_VALUE', + sql: [ + `SELECT + NTH_VALUE(user_name, 10) OVER ( + PARTITION BY user_city + ORDER BY created_at + ) AS age_window + FROM roster`, + 'SELECT NTH_VALUE(user_name, 10) OVER (PARTITION BY user_city ORDER BY created_at ASC) AS "age_window" FROM "roster"' + ] + }, + { + title: 'Window Fns + LAG + explicit NULLS', + sql: [ + `SELECT + LAG(user_name) ignore NULLS OVER ( + PARTITION BY user_city + ORDER BY created_at + ) AS age_window + FROM roster`, + 'SELECT LAG(user_name) IGNORE NULLS OVER (PARTITION BY user_city ORDER BY created_at ASC) AS "age_window" FROM "roster"' + ] + }, + { + title: 'Window Fns + FIRST_VALUE', + sql: [ + `SELECT + FIRST_VALUE(user_name ignore NULLS) OVER ( + PARTITION BY user_city + ORDER BY created_at, ranking + ) AS age_window + FROM roster`, + 'SELECT FIRST_VALUE(user_name IGNORE NULLS) OVER (PARTITION BY user_city ORDER BY created_at ASC, ranking ASC) AS "age_window" FROM "roster"' + ] + }, + { + title: 'array column', + sql: [ + "SELECT ARRAY[col1, col2, 1, 'str_literal'] from tableb", + `SELECT ARRAY[col1,col2,1,'str_literal'] FROM "tableb"` + ] + }, + { + title: 'array column index', + sql: [ + "select (array['a', 'b', 'c'])[2]", + `SELECT (ARRAY['a','b','c'])[2]` + ] + }, + { + title: 'array cast column index', + sql: [ + "select ('{a, b, c}'::text[])[2]", + `SELECT ('{a, b, c}'::TEXT[])[2]` + ] + }, + { + title: 'column array index', + sql: [ + `with t as ( + select array['a', 'b', 'c'] as a + ) + select a[2] + from t`, + `WITH "t" AS (SELECT ARRAY['a','b','c'] AS "a") SELECT a[2] FROM "t"` + ] + }, + { + title: 'row function column', + sql: [ + "SELECT ROW(col1, col2, 'literal', 1) from tableb", + `SELECT ROW(col1, col2, 'literal', 1) FROM "tableb"` + ] + }, + { + title: 'json column', + sql: [ + `SELECT + d.metadata->>'communication_status' as communication_status + FROM + device d + WHERE d.metadata->>'communication_status' IS NOT NULL + LIMIT 10;`, + `SELECT "d".metadata ->> 'communication_status' AS "communication_status" FROM "device" AS "d" WHERE "d".metadata ->> 'communication_status' IS NOT NULL LIMIT 10` + ] + }, + { + title: 'case when in pg', + sql: [ + `SELECT SUM(CASE WHEN status = 'ACTIVE' THEN 1 ELSE 0 END) FROM tablename`, + `SELECT SUM(CASE WHEN status = 'ACTIVE' THEN 1 ELSE 0 END) FROM "tablename"` + ] + }, + { + title: 'case when multiple condition in pg', + sql: [ + `select case + when + ee.start_time <= current_timestamp + and ee.end_time > current_timestamp + then + true + else + false + end + is_live, + is_upcoming from abc`, + `SELECT CASE WHEN "ee".start_time <= CURRENT_TIMESTAMP AND "ee".end_time > CURRENT_TIMESTAMP THEN TRUE ELSE FALSE END AS "is_live", is_upcoming FROM "abc"` + ] + }, + { + title: 'key keyword in pg', + sql: [ + `SELECT * FROM partitions WHERE location IS NULL AND code like 'XX-%' AND key <> 1;`, + `SELECT * FROM "partitions" WHERE location IS NULL AND code LIKE 'XX-%' AND key <> 1` + ] + }, + { + title: 'a multi-line single-quoted string', + sql: [ + `SELECT 'Hello ' + 'world!' AS x;`, + `SELECT 'Hello world!' AS "x"` + ] + }, + { + title: 'left join', + sql: [ + `select + person.first_name, + department.dept_name + from + person + left join department on person.dept_id = department.dept_id`, + 'SELECT "person".first_name, "department".dept_name FROM "person" LEFT JOIN "department" ON "person".dept_id = "department".dept_id' + ] + }, + { + title: 'create table with serial', + sql: [ + `create table posts(id serial primary key, name varchar(128))`, + `CREATE TABLE "posts" (id SERIAL PRIMARY KEY, name VARCHAR(128))` + ] + }, + { + title: 'cast to interval', + sql: [ + `select '1 week'::interval`, + `SELECT '1 week'::INTERVAL` + ] + }, + { + title: 'with clause support double quote', + sql: [ + `with "cte name" as ( + select 1 + ) + select * from "cte name"`, + `WITH "cte name" AS (SELECT 1) SELECT * FROM "cte name"` + ] + }, + { + title: 'select from values as', + sql: [ + `select * + from (values (0, 0), (1, null), (null, 2), (3, 4)) as t(a,b) + where a is distinct from "b"`, + `SELECT * FROM (VALUES (0,0), (1,NULL), (NULL,2), (3,4)) AS "t(a, b)" WHERE a IS DISTINCT FROM "b"` + ] + }, + { + title: 'select from values as without parentheses', + sql: [ + `select last(col) FROM VALUES(10),(5),(20) AS tab(col)`, + `SELECT last(col) FROM VALUES (10), (5), (20) AS "tab(col)"` + ] + }, + { + title: 'aggr_fun percentile_cont', + sql: [ + `select percentile_cont(0.25) within group (order by a asc) as p25 + from (values (0),(0),(1),(2),(3),(4)) as t(a)`, + `SELECT PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY a ASC) AS "p25" FROM (VALUES (0), (0), (1), (2), (3), (4)) AS "t(a)"` + ] + }, + { + title: 'aggr_fun percentile_cont with array args', + sql: [ + `select percentile_cont(array[0.5, 1]) within group (order by a asc) as p25 + from (values (0),(0),(1),(2),(3),(4)) as t(a)`, + `SELECT PERCENTILE_CONT(ARRAY[0.5,1]) WITHIN GROUP (ORDER BY a ASC) AS "p25" FROM (VALUES (0), (0), (1), (2), (3), (4)) AS "t(a)"` + ] + }, + { + title: 'aggr_fun mode', + sql: [ + `select mode() within group (order by a asc) as p25 + from (values (0),(0),(1),(2),(3),(4)) as t(a)`, + `SELECT MODE() WITHIN GROUP (ORDER BY a ASC) AS "p25" FROM (VALUES (0), (0), (1), (2), (3), (4)) AS "t(a)"` + ] + }, + { + title: 'similar to keyword in pg', + sql: [ + `select name similar to 'John%' from (values ('John Doe'),('Jane Doe'),('Bob John')) as t(name)`, + `SELECT name SIMILAR TO 'John%' FROM (VALUES ('John Doe'), ('Jane Doe'), ('Bob John')) AS "t(name)"` + ] + }, + { + title: 'show tables', + sql: [ + `show tables`, + `SHOW TABLES` + ] + }, + { + title: 'String Constants', + sql: [ + `select ''''`, + `SELECT ''''` + ] + }, + { + title: 'String Constants', + sql: [ + `SELECT '''To be, or not'', it starts.' AS x;`, + `SELECT '''To be, or not'', it starts.' AS "x"` + ] + }, + { + title: 'String Constants', + sql: [ + `SELECT 'foo' + 'bar';`, + `SELECT 'foobar'` + ] + }, + { + title: 'String Constants with C-Style Escapes', + sql: [ + `SELECT E'\\''`, + `SELECT E'\\''` + ] + }, + { + title: 'schema prefix', + sql: [ + `SELECT "public"."Property"."id", + "public"."Property"."title", + "public"."Property"."description", + "public"."Property"."views", + "public"."Property"."saves", + "public"."Property"."postcode", + "public"."Property"."createdAt" + FROM "public"."Property" + WHERE 1 = 1 + ORDER BY "public"."Property"."createdAt"`, + `SELECT "public"."Property"."id", "public"."Property"."title", "public"."Property"."description", "public"."Property"."views", "public"."Property"."saves", "public"."Property"."postcode", "public"."Property"."createdAt" FROM "public"."Property" WHERE 1 = 1 ORDER BY "public"."Property"."createdAt" ASC` + ] + }, + { + title: 'cast to datatype array', + sql: [ + "select '{1,2,3}'::int[]", + "SELECT '{1,2,3}'::INT[]" + ] + }, + { + title: 'cast to datatype two dimension array', + sql: [ + "select '{{1,2},{2,3},{3,4}}'::int[][]", + "SELECT '{{1,2},{2,3},{3,4}}'::INT[][]" + ] + }, + { + title: 'a newline before cast symbol', + sql: [ + `select round(0.598736 + ::numeric, 2)`, + "SELECT round(0.598736::NUMERIC, 2)" + ] + }, + { + title: 'access array index in func parameter', + sql: [ + 'select round(arr[1])', + 'SELECT round(arr[1])' + ] + }, + { + title: 'access array index in where clause', + sql: [ + 'SELECT * FROM a INNER JOIN b ON c = d[1]', + 'SELECT * FROM "a" INNER JOIN "b" ON c = d[1]' + ] + }, + { + title: 'distinct on', + sql: [ + "SELECT DISTINCT ON (a->>'someJsonAttribute', b, c) a->>'someJsonAttribute', b, c FROM tbl", + `SELECT DISTINCT ON (a ->> 'someJsonAttribute', b, c) a ->> 'someJsonAttribute', b, c FROM "tbl"` + ] + }, + { + title: 'select current_date only', + sql: [ + 'select current_date', + 'SELECT CURRENT_DATE' + ] + }, + { + title: 'window function', + sql: [ + `SELECT sum(salary) OVER w, avg(salary) OVER w + FROM empsalary + WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);`, + 'SELECT SUM(salary) OVER w, AVG(salary) OVER w FROM "empsalary" WINDOW w AS (PARTITION BY depname ORDER BY salary DESC)' + ] + }, + { + title: '$ field id with parameters', + sql: [ + 'SELECT * FROM tablea WHERE comment_id = $<3>;', + 'SELECT * FROM "tablea" WHERE comment_id = $<3>' + ] + }, + { + title: 'cast with binary expr', + sql: [ + 'select (3-2)::float / (2 * 123) + 111', + 'SELECT (3 - 2)::FLOAT / (2 * 123) + 111' + ] + }, + { + title: 'cast with binary expr and cast', + sql: [ + 'select (2)::float/(3)::float', + 'SELECT (2)::FLOAT / (3)::FLOAT' + ] + }, + { + title: 'on expr with and', + sql: [ + `select * + from organization + JOIN payment ON organization.id = payment.organization_id and createdat = month`, + 'SELECT * FROM "organization" INNER JOIN "payment" ON "organization".id = "payment".organization_id AND createdat = month' + ] + }, + { + title: 'support tablesample', + sql: [ + 'select * from product.organization tablesample bernoulli(1)', + 'SELECT * FROM "product"."organization" TABLESAMPLE bernoulli(1)' + ] + }, + { + title: 'support on clause with function and expr', + sql: [ + `select * from pg_database a + join pg_database b + on a.oid = b.oid AND upper(a.datctype) = upper(b.datctype)`, + 'SELECT * FROM "pg_database" AS "a" INNER JOIN "pg_database" AS "b" ON "a".oid = "b".oid AND upper("a".datctype) = upper("b".datctype)' + ] + }, + { + title: 'support trim function', + sql: [ + `SELECT TRIM('.' from '....test.....') AS TrimmedString;`, + `SELECT TRIM('.' FROM '....test.....') AS "TrimmedString"` + ] + }, + { + title: 'from values without as', + sql: [ + `with statuses as ( + select a + from ( + values ('Closed'), ('Verified'), ('Done') + ) s(a) + ) select * from statuses`, + `WITH "statuses" AS (SELECT a FROM (VALUES ('Closed'), ('Verified'), ('Done')) AS "s(a)") SELECT * FROM "statuses"` + ] + }, + { + title: 'double dollar-quoted string', + sql: [ + 'SELECT $$foo bar$$;', + 'SELECT $$foo bar$$' + ] + }, + { + title: 'single dollar-quoted string', + sql: [ + "select $SomeTag$Dianne's horse$SomeTag$", + "SELECT $SomeTag$Dianne's horse$SomeTag$" + ] + }, + { + title: 'nested block comments', + sql: [ + "select /* /* */ */ col from tbl", + 'SELECT col FROM "tbl"' + ] + }, + { + title: 'select into', + sql: [ + "select c1, c2 into t1 from t2", + 'SELECT c1, c2 INTO "t1" FROM "t2"' + ] + }, + { + title: 'select;', + sql: [ + "select;", + 'SELECT' + ] + }, + { + title: 'with insert', + sql: [ + `CREATE TABLE stuff(id SERIAL PRIMARY KEY, name VARCHAR); + + WITH new_stuff AS ( + INSERT INTO stuff (name) VALUES ('foo'), ('bar') RETURNING id + ) + SELECT id + FROM new_stuff;`, + `CREATE TABLE "stuff" (id SERIAL PRIMARY KEY, name VARCHAR) ; WITH "new_stuff" AS (INSERT INTO "stuff" (name) VALUES ('foo'), ('bar') RETURNING id) SELECT id FROM "new_stuff"` + ] + }, + { + title: 'offset without limit', + sql: [ + 'select c1 from t1 offset 11', + 'SELECT c1 FROM "t1" OFFSET 11' + ] + }, + { + title: 'support empty space after ::', + sql: [ + 'SELECT (COALESCE(wp.weight, 0))::double(10) as net_weight , wp.gross_weight:: double(10) FROM wp ;', + 'SELECT (COALESCE("wp".weight, 0))::DOUBLE(10) AS "net_weight", "wp".gross_weight::DOUBLE(10) FROM "wp"' + ] + }, + { + title: 'support nested json traversal', + sql: [ + "SELECT meta.data->'foo'->'bar' as value FROM meta;", + `SELECT "meta".data -> 'foo' -> 'bar' AS "value" FROM "meta"` + ] + }, + { + title: 'support nulls first or last after order by', + sql: [ + 'SELECT has_geometry FROM rooms WHERE rooms.index = 200 ORDER BY has_geometry DESC NULLS LAST;', + 'SELECT has_geometry FROM "rooms" WHERE "rooms".index = 200 ORDER BY has_geometry DESC NULLS LAST' + ] + }, + { + title: 'support nulls after order by with default val', + sql: [ + 'SELECT has_geometry FROM rooms WHERE rooms.index = 200 ORDER BY has_geometry ASC NULLS;', + 'SELECT has_geometry FROM "rooms" WHERE "rooms".index = 200 ORDER BY has_geometry ASC NULLS' + ] + }, + { + title: 'support lateral with subquery', + sql: [ + 'SELECT * FROM foo, LATERAL (SELECT * FROM bar WHERE bar.id = foo.bar_id) ss;', + 'SELECT * FROM "foo", LATERAL (SELECT * FROM "bar" WHERE "bar".id = "foo".bar_id) AS "ss"' + ] + }, + { + title: 'support lateral with function', + sql: [ + `SELECT p1.id, p2.id, v1, v2 + FROM polygons p1, polygons p2, + LATERAL vertices(p1.poly) v1, + LATERAL vertices(p2.poly) v2 + WHERE (v1 - v2) < 10 AND p1.id != p2.id;`, + 'SELECT "p1".id, "p2".id, v1, v2 FROM "polygons" AS "p1", "polygons" AS "p2", LATERAL vertices("p1".poly) AS "v1", LATERAL vertices("p2".poly) AS "v2" WHERE (v1 - v2) < 10 AND "p1".id != "p2".id' + ] + }, + { + title: 'support lateral with join', + sql: [ + `SELECT m.name + FROM manufacturers m LEFT JOIN LATERAL get_product_names(m.id) pname ON true + WHERE pname IS NULL;`, + 'SELECT "m".name FROM "manufacturers" AS "m" LEFT JOIN LATERAL get_product_names("m".id) AS "pname" ON TRUE WHERE pname IS NULL' + ] + }, + { + title: 'support escape char patten matching', + sql: [ + "select c1 from t1 where c2 like 'abc' escape '!'", + `SELECT c1 FROM "t1" WHERE c2 LIKE 'abc' ESCAPE '!'` + ] + }, + { + title: 'with or without timezone', + sql: [ + 'select cast(c as time with time zone)', + 'SELECT CAST(c AS TIME WITH TIME ZONE)' + ] + }, + { + title: 'with or without timezone', + sql: [ + 'select cast(c as timestamp without time zone)', + 'SELECT CAST(c AS TIMESTAMP WITHOUT TIME ZONE)' + ] + }, + { + title: 'with or without timezone', + sql: [ + 'select try_cast(c as time with time zone)', + 'SELECT TRY_CAST(c AS TIME WITH TIME ZONE)' + ] + }, + { + title: 'with or without timezone', + sql: [ + 'select try_cast(c as timestamp without time zone)', + 'SELECT TRY_CAST(c AS TIMESTAMP WITHOUT TIME ZONE)' + ] + }, + { + title: 'bytea datatype', + sql: [ + 'SELECT \'abc \\153\\154\\155 \\052\\251\\124\'::bytea;', + "SELECT 'abc \\153\\154\\155 \\052\\251\\124'::BYTEA" + ] + }, + { + title: 'deallocate statement', + sql: [ + 'DEALLOCATE pdo_stmt_1', + 'DEALLOCATE pdo_stmt_1' + ] + }, + { + title: 'deallocate statement with prepare', + sql: [ + 'DEALLOCATE PREPARE ALL', + 'DEALLOCATE PREPARE ALL' + ] + }, + { + title: 'filter after aggregate expression', + sql: [ + "SELECT date_trunc('month', buy_window) AS month_window, marketplace, SUM(currency_amount_a) FILTER (WHERE currency_symbol_a IN ('REN', 'EUR')) + SUM(currency_amount_b) FILTER (WHERE currency_symbol_b IN ('REN', 'EUR')) as volume FROM currency.forex WHERE buy_window >= to_timestamp(1522540800) GROUP BY project, month", + `SELECT date_trunc('month', buy_window) AS "month_window", marketplace, SUM(currency_amount_a) FILTER (WHERE currency_symbol_a IN ('REN', 'EUR')) + SUM(currency_amount_b) FILTER (WHERE currency_symbol_b IN ('REN', 'EUR')) AS "volume" FROM "currency"."forex" WHERE buy_window >= to_timestamp(1522540800) GROUP BY project, month`, + ] + }, + { + title: 'decimal without prefix 0', + sql: [ + `SELECT date_trunc('month', time_window) , SUM(ren) * .999 as ren_normalized FROM currencies."forex" WHERE memory_address = '\x881d40237659c251811cec9c364ef91dc08d300c' GROUP BY 1`, + `SELECT date_trunc('month', time_window), SUM(ren) * 0.999 AS "ren_normalized" FROM "currencies"."forex" WHERE memory_address = 'ˆ1d40237659c251811cec9c364ef91dc08d300c' GROUP BY 1` + ] + }, + { + title: 'values as table name', + sql: [ + `with values as ( + select 1 as value + ) + select * from values`, + 'WITH "values" AS (SELECT 1 AS "value") SELECT * FROM "values"', + ] + }, + { + title: 'bigserial datatype', + sql: [ + 'create table if not exists "users" ( "id" bigserial, "name" varchar(128) not null, "second_name" varchar(128) default null )', + 'CREATE TABLE IF NOT EXISTS "users" ("id" BIGSERIAL, "name" VARCHAR(128) NOT NULL, "second_name" VARCHAR(128) DEFAULT NULL)', + ] + }, + { + title: 'delete statement', + sql: [ + 'DELETE FROM users WHERE id = 2;', + 'DELETE FROM "users" WHERE id = 2', + ] + }, + { + title: 'column quoted data type', + sql: [ + `select 'a'::"char" as b;`, + `SELECT 'a'::"CHAR" AS "b"` + ] + }, + { + title: 'set with quoted string', + sql: [ + `set "foo.bar" = 'a';`, + `SET "foo.bar" = 'a'` + ] + }, + { + title: 'show stmt', + sql: [ + 'show "foo.bar";', + 'SHOW "foo.bar"' + ] + }, + { + title: 'create now at time zone', + sql: [ + `CREATE TABLE IF NOT EXISTS "users" ( "id" BIGSERIAL PRIMARY KEY, "date_created" TIMESTAMP WITHOUT TIME ZONE DEFAULT (now() at time zone 'utc'), "first_name" VARCHAR(128) NOT NULL );`, + `CREATE TABLE IF NOT EXISTS "users" ("id" BIGSERIAL PRIMARY KEY, "date_created" TIMESTAMP WITHOUT TIME ZONE DEFAULT (now() AT TIME ZONE 'utc'), "first_name" VARCHAR(128) NOT NULL)` + ] + }, + { + title: 'from clause in update', + sql: [ + `UPDATE t1 SET c1 = 'x' FROM t2 WHERE c3 = t2.c2`, + `UPDATE "t1" SET c1 = 'x' FROM "t2" WHERE c3 = "t2".c2`, + ] + }, + { + title: 'from clause in update with select', + sql: [ + `UPDATE t1 SET c1 = 'x' FROM (select c2 from t2) WHERE c3 = c2`, + `UPDATE "t1" SET c1 = 'x' FROM (SELECT c2 FROM "t2") WHERE c3 = c2`, + ] + }, + { + title: 'drop index', + sql: [ + 'drop index concurrently title_index cascade', + 'DROP INDEX CONCURRENTLY title_index CASCADE' + ], + }, + { + title: 'with clause in update', + sql: [ + `WITH olds AS (SELECT test_field_1, test_field_2 FROM test_tbl WHERE test_field_1=5) + UPDATE test_tbl SET test_field_2 ='tested!' WHERE test_field_1=5 + RETURNING (SELECT test_field_1 FROM olds) AS test_field_1_old, + (SELECT test_field_2 FROM olds) AS test_field_2_old;`, + `WITH "olds" AS (SELECT test_field_1, test_field_2 FROM "test_tbl" WHERE test_field_1 = 5) UPDATE "test_tbl" SET test_field_2 = 'tested!' WHERE test_field_1 = 5 RETURNING (SELECT test_field_1 FROM "olds") AS "test_field_1_old", (SELECT test_field_2 FROM "olds") AS "test_field_2_old"` + ] + }, + { + title: 'string concatenator in where clause', + sql: [ + "SELECT * from tests where name = 'test' || 'abc';", + `SELECT * FROM "tests" WHERE name = 'test' || 'abc'` + ] + }, + { + title: 'alter table add constraint', + sql: [ + 'ALTER TABLE if exists only address ADD CONSTRAINT user_id_address_fk FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE ON UPDATE RESTRICT;', + 'ALTER TABLE IF EXISTS ONLY "address" ADD CONSTRAINT "user_id_address_fk" FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE ON UPDATE RESTRICT' + ] + }, + { + title: 'table constructor in join', + sql: [ + `select last_name, salary, title + from employees e left join ( + salaries s inner join titles t on s.emp_no = t.emp_no + ) on e.emp_no = s.emp_no`, + 'SELECT last_name, salary, title FROM "employees" AS "e" LEFT JOIN ("salaries" AS "s" INNER JOIN "titles" AS "t" ON "s".emp_no = "t".emp_no) ON "e".emp_no = "s".emp_no' + ] + }, + { + title: 'table constructor in from', + sql: [ + `select last_name, salary + from ( + employees inner join salaries on employees.emp_no = salaries.emp_no + );`, + 'SELECT last_name, salary FROM ("employees" INNER JOIN "salaries" ON "employees".emp_no = "salaries".emp_no)' + ] + }, + { + title: 'select from scheme.table.column', + sql: [ + 'select public.t1.* from public.t1;', + 'SELECT public.t1.* FROM "public"."t1"' + ] + }, + { + title: 'ntile function', + sql: [ + `SELECT name, + amount, + NTILE(2) OVER ( + ORDER BY amount + ) ntile, + unset(_id) + FROM function-test-data + WHERE testId='bugfix.ntile.case1'`, + `SELECT name, amount, NTILE(2) OVER (ORDER BY amount ASC) AS "ntile", unset(_id) FROM "function-test-data" WHERE testId = 'bugfix.ntile.case1'` + ] + }, + { + title: 'support character data type', + sql: [ + "SELECT 'x'::character varying;", + `SELECT 'x'::CHARACTER VARYING` + ] + }, + { + title: 'cast to jsonb and select key', + sql: [ + "SELECT TextColumn::JSONB->>'name' FROM table1", + `SELECT TextColumn::JSONB ->> 'name' FROM "table1"` + ] + }, + { + title: 'cast to jsonb and select key in function', + sql: [ + "SELECT CAST(properties AS JSONB)->>'name' FROM table1", + `SELECT CAST(properties AS JSONB) ->> 'name' FROM "table1"` + ] + }, + { + title: 'cast to jsonb and select key in function', + sql: [ + "SELECT TRY_CAST(properties AS JSONB)->>'name' FROM table1", + `SELECT TRY_CAST(properties AS JSONB) ->> 'name' FROM "table1"` + ] + }, + { + title: 'test !~ operator', + sql: [ + `SELECT * FROM partitions WHERE code !~ xyz;`, + `SELECT * FROM "partitions" WHERE code !~ xyz` + ] + }, + { + title: 'test ~ operator', + sql: [ + `SELECT * FROM partitions WHERE code ~ xyz;`, + `SELECT * FROM "partitions" WHERE code ~ xyz` + ] + }, + { + title: 'insert stmt', + sql: [ + `insert into table1 (id, firstname, lastname, email) + values ($id, $firstname, $lastname, $email) + RETURNING *`, + 'INSERT INTO "table1" (id, firstname, lastname, email) VALUES ($id,$firstname,$lastname,$email) RETURNING *' + ] + }, + { + title: 'insert with on conflict do update', + sql: [ + `insert into table1 (id, firstname, lastname, email) + values ($id, $firstname, $lastname, $email) + on conflict (id) + do + update set + firstname = $firstname, + lastname = $lastname, + email = $email, + updatedon = CURRENT_TIMESTAMP + RETURNING *`, + 'INSERT INTO "table1" (id, firstname, lastname, email) VALUES ($id,$firstname,$lastname,$email) ON CONFLICT (id) DO UPDATE SET firstname = $firstname, lastname = $lastname, email = $email, updatedon = CURRENT_TIMESTAMP RETURNING *' + ] + }, + { + title: 'insert with on conflict do nothing', + sql: [ + `insert into table1 (id, "firstname", "lastname", email) + values ($id, $firstname, $lastname, $email) + on conflict + do nothing + RETURNING *`, + 'INSERT INTO "table1" (id, "firstname", "lastname", email) VALUES ($id,$firstname,$lastname,$email) ON CONFLICT DO NOTHING RETURNING *' + ] + }, + { + title: 'alter schema', + sql: [ + 'ALTER SCHEMA public OWNER TO postgres;', + 'ALTER SCHEMA "public" OWNER TO "postgres"' + ] + }, + { + title: 'alter domain', + sql: [ + 'ALTER DOMAIN public."bıgınt" OWNER TO postgres;', + 'ALTER DOMAIN "public"."bıgınt" OWNER TO "postgres"' + ] + }, + { + title: 'alter type', + sql: [ + 'ALTER TYPE public.mpaa_rating OWNER TO postgres;', + 'ALTER TYPE "public"."mpaa_rating" OWNER TO "postgres"' + ] + }, + { + title: 'alter function', + sql: [ + 'ALTER FUNCTION public.film_in_stock(p_film_id integer, p_store_id integer, OUT p_film_count integer, p_effective_date timestamp with time zone, timestamp with time zone) OWNER TO postgres;', + 'ALTER FUNCTION "public"."film_in_stock"(p_film_id INTEGER, p_store_id INTEGER, OUT p_film_count INTEGER, p_effective_date TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH TIME ZONE) OWNER TO "postgres"' + ] + }, + { + title: 'alter function without args', + sql: [ + 'ALTER FUNCTION public.last_updated() OWNER TO postgres;', + 'ALTER FUNCTION "public"."last_updated"() OWNER TO "postgres"' + ] + }, + { + title: 'alter aggregate', + sql: [ + 'ALTER AGGREGATE public.group_concat(text) OWNER TO postgres;', + 'ALTER AGGREGATE "public"."group_concat"(TEXT) OWNER TO "postgres"' + ] + }, + { + title: 'alter aggregate with order by', + sql: [ + 'ALTER AGGREGATE mypercentile(float8 ORDER BY integer) SET SCHEMA mynewpercentile;', + 'ALTER AGGREGATE "mypercentile"(FLOAT8 ORDER BY INTEGER) SET SCHEMA "mynewpercentile"' + ] + }, + { + title: 'create domain', + sql: [ + 'CREATE DOMAIN public."bıgınt" AS bigint;', + 'CREATE DOMAIN "public"."bıgınt" AS BIGINT', + ] + }, + { + title: 'create domain with constraint', + sql: [ + 'CREATE DOMAIN public.year AS integer CONSTRAINT year_check CHECK (((VALUE >= 1901) AND (VALUE <= 2155)));', + 'CREATE DOMAIN "public"."year" AS INTEGER CONSTRAINT "year_check" CHECK (((VALUE >= 1901) AND (VALUE <= 2155)))', + ] + }, + { + title: 'create domain with full definition', + sql: [ + 'CREATE DOMAIN public.year AS integer collate utf8mb4_bin default 0 CONSTRAINT year_check CHECK (((VALUE >= 1901) AND (VALUE <= 2155)));', + 'CREATE DOMAIN "public"."year" AS INTEGER COLLATE utf8mb4_bin DEFAULT 0 CONSTRAINT "year_check" CHECK (((VALUE >= 1901) AND (VALUE <= 2155)))', + ] + }, + { + title: 'create type as enum', + sql: [ + `CREATE TYPE public.mpaa_rating AS ENUM ( + 'G', + 'PG', + 'PG-13', + 'R', + 'NC-17' + );`, + `CREATE TYPE "public"."mpaa_rating" AS ENUM ('G', 'PG', 'PG-13', 'R', 'NC-17')` + ] + }, + { + title: 'create type name', + sql: [ + 'create type public.test', + 'CREATE TYPE "public"."test"' + ] + }, + { + title: 'create view', + sql: [ + `CREATE OR REPLACE TEMPORARY RECURSIVE VIEW universal_comedies + with (check_option = local, security_barrier = false) + AS + SELECT * + FROM comedies + WHERE classification = 'U' + WITH LOCAL CHECK OPTION;`, + `CREATE OR REPLACE TEMPORARY RECURSIVE VIEW "universal_comedies" WITH (CHECK_OPTION = LOCAL, SECURITY_BARRIER = FALSE) AS SELECT * FROM "comedies" WHERE classification = 'U' WITH LOCAL CHECK OPTION` + ] + }, + { + title: 'create trigger', + sql: [ + "CREATE TRIGGER film_fulltext_trigger BEFORE INSERT OR UPDATE ON public.film FOR EACH ROW EXECUTE FUNCTION tsvector_update_trigger('fulltext', 'pg_catalog.english', 'title', 'description');", + `CREATE TRIGGER "film_fulltext_trigger" BEFORE INSERT OR UPDATE ON "public"."film" FOR EACH ROW EXECUTE FUNCTION tsvector_update_trigger('fulltext', 'pg_catalog.english', 'title', 'description')` + ] + }, + { + title: 'grant on schema', + sql: [ + 'GRANT ALL ON SCHEMA public TO PUBLIC;', + 'GRANT ALL ON SCHEMA "public" TO PUBLIC' + ] + }, + { + title: 'grant table', + sql: [ + 'GRANT INSERT ON TABLE films TO PUBLIC;', + 'GRANT INSERT ON TABLE "films" TO PUBLIC' + ] + }, + { + title: 'grant all tables', + sql: [ + 'GRANT SELECT ON ALL TABLES IN SCHEMA public, trusted TO PUBLIC;', + 'GRANT SELECT ON ALL TABLES IN SCHEMA "public", "trusted" TO PUBLIC' + ] + }, + { + title: 'revoke on schema', + sql: [ + 'REVOKE ALL ON SCHEMA public FROM PUBLIC;', + 'REVOKE ALL ON SCHEMA "public" FROM PUBLIC' + ] + }, + { + title: 'create function case when', + sql: [ + `CREATE FUNCTION public._group_concat(text, text) RETURNS text + LANGUAGE sql IMMUTABLE + AS $_$ + SELECT CASE + WHEN $2 IS NULL THEN $1 + WHEN $1 IS NULL THEN $2 + ELSE $1 || ', ' || $2 + END + $_$;`, + `CREATE FUNCTION "public"._group_concat(TEXT, TEXT) RETURNS TEXT LANGUAGE sql IMMUTABLE AS $_$ SELECT CASE WHEN $2 IS NULL THEN $1 WHEN $1 IS NULL THEN $2 ELSE $1 || ', ' || $2 END $_$` + ] + }, + { + title: 'create function select', + sql: [ + `CREATE FUNCTION public.film_not_in_stock(p_film_id integer default 1, p_store_id integer = 1, OUT p_film_count integer) RETURNS SETOF integer + LANGUAGE sql + AS $_$ + SELECT inventory_id + FROM inventory + WHERE film_id = $1 + AND store_id = $2 + AND NOT inventory_in_stock(inventory_id); + $_$;`, + `CREATE FUNCTION "public".film_not_in_stock(p_film_id INTEGER DEFAULT 1, p_store_id INTEGER = 1, OUT p_film_count INTEGER) RETURNS SETOF INTEGER LANGUAGE sql AS $_$ SELECT inventory_id FROM "inventory" WHERE film_id = $1 AND store_id = $2 AND NOT inventory_in_stock(inventory_id) $_$` + ] + }, + { + title: 'create function with declare', + sql: [ + `CREATE FUNCTION check_password(uname TEXT, pass TEXT) + RETURNS BOOLEAN AS $$ + DECLARE passed BOOLEAN; + BEGIN + SELECT (pwd = $2) INTO passed + FROM pwds + WHERE username = $1; + + RETURN passed; + END; + $$ LANGUAGE plpgsql + SECURITY DEFINER + -- Set a secure search_path: trusted schema(s), then 'pg_temp'. + SET search_path = admin, pg_temp; + `, + `CREATE FUNCTION check_password(uname TEXT, pass TEXT) RETURNS BOOLEAN AS $$ DECLARE passed BOOLEAN BEGIN SELECT (pwd = $2) INTO "passed" FROM "pwds" WHERE username = $1 ; RETURN passed END $$ LANGUAGE plpgsql SECURITY DEFINER SET search_path = admin, pg_temp` + ] + }, + { + title: 'create function returns table', + sql: [ + `CREATE FUNCTION dup(int) RETURNS TABLE(f1 int, f2 text) + AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$ + LANGUAGE SQL;`, + `CREATE FUNCTION dup(INT) RETURNS TABLE (f1 INT, f2 TEXT) AS $$ SELECT $1, CAST($1 AS TEXT) || ' is text' $$ LANGUAGE SQL` + ] + }, + { + title: 'create function returns table', + sql: [ + `CREATE FUNCTION dup(int) RETURNS TABLE(f1 int, f2 text) + AS $$ SELECT $1, TRY_CAST($1 AS text) || ' is text' $$ + LANGUAGE SQL;`, + `CREATE FUNCTION dup(INT) RETURNS TABLE (f1 INT, f2 TEXT) AS $$ SELECT $1, TRY_CAST($1 AS TEXT) || ' is text' $$ LANGUAGE SQL` + ] + }, + { + title: 'create function with if else stmt', + sql: [ + `CREATE FUNCTION public.inventory_in_stock(p_inventory_id integer) RETURNS boolean + LANGUAGE plpgsql + AS $$ + DECLARE + v_rentals INTEGER; + v_out INTEGER; + BEGIN + -- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE + -- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED + + SELECT count(*) INTO v_rentals + FROM rental + WHERE inventory_id = p_inventory_id; + + IF v_rentals = 0 THEN + RETURN TRUE; + END IF; + + SELECT COUNT(rental_id) INTO v_out + FROM inventory LEFT JOIN rental USING(inventory_id) + WHERE inventory.inventory_id = p_inventory_id + AND rental.return_date IS NULL; + + IF v_out > 0 THEN + RETURN FALSE; + ELSEIF v_out = 0 THEN + RETURN FALSE; + ELSE + RETURN TRUE; + END IF; + END + $$;`, + 'CREATE FUNCTION "public".inventory_in_stock(p_inventory_id INTEGER) RETURNS BOOLEAN LANGUAGE plpgsql AS $$ DECLARE v_rentals INTEGER; v_out INTEGER BEGIN SELECT COUNT(*) INTO "v_rentals" FROM "rental" WHERE inventory_id = p_inventory_id ; IF v_rentals = 0 THEN RETURN TRUE; END IF ; SELECT COUNT(rental_id) INTO "v_out" FROM "inventory" LEFT JOIN "rental" USING ("inventory_id") WHERE "inventory".inventory_id = p_inventory_id AND "rental".return_date IS NULL ; IF v_out > 0 THEN RETURN FALSE; ELSEIF v_out = 0 THEN RETURN FALSE ; ELSE RETURN TRUE; END IF END $$' + ] + }, + { + title: 'create function without args', + sql: [ + `CREATE FUNCTION public.last_updated() RETURNS trigger + LANGUAGE plpgsql + AS $$ + BEGIN + NEW.last_update = CURRENT_TIMESTAMP; + RETURN NEW; + END $$;`, + 'CREATE FUNCTION "public".last_updated() RETURNS "trigger" LANGUAGE plpgsql AS $$ BEGIN NEW.last_update = CURRENT_TIMESTAMP ; RETURN NEW END $$' + ] + }, + { + title: 'create aggregate', + sql: [ + `CREATE AGGREGATE public.group_concat(text order by integer, id integer) ( + SFUNC = public._group_concat, + STYPE = text, + SSPACE = 2, + FINALFUNC_MODIFY = READ_ONLY + );`, + 'CREATE AGGREGATE "public".group_concat(TEXT ORDER BY INTEGER, id INTEGER) (SFUNC = "public"._group_concat, STYPE = TEXT, SSPACE = 2, FINALFUNC_MODIFY = READ_ONLY)' + ] + }, + { + title: 'create aggregate without orderby', + sql: [ + `CREATE AGGREGATE public.group_concat(text, text) ( + SFUNC = public._group_concat, + STYPE = text, + MFINALFUNC_MODIFY = SHAREABLE + );`, + 'CREATE AGGREGATE "public".group_concat(TEXT, TEXT) (SFUNC = "public"._group_concat, STYPE = TEXT, MFINALFUNC_MODIFY = SHAREABLE)' + ] + }, + { + title: 'raise only', + sql: [ + 'raise', + 'RAISE' + ] + }, + { + title: 'raise notice', + sql: [ + "RAISE NOTICE 'Calling cs_create_job(%)', v_job_id;", + "RAISE NOTICE 'Calling cs_create_job(%)', v_job_id" + ] + }, + { + title: 'raise expection', + sql: [ + `RAISE EXCEPTION 'Nonexistent ID --> %', user_id + USING HINT = 'Please check your user ID';`, + "RAISE EXCEPTION 'Nonexistent ID --> %', user_id USING HINT = 'Please check your user ID'" + ] + }, + { + title: 'raise condition', + sql: [ + 'RAISE division_by_zero;', + 'RAISE division_by_zero' + ] + }, + { + title: 'raise sqlstate', + sql: [ + "RAISE SQLSTATE '22012';", + "RAISE SQLSTATE '22012'" + ] + }, + { + title: 'execute stmt', + sql: [ + 'EXECUTE tmpSQL;', + 'EXECUTE tmpSQL' + ] + }, + { + title: 'execute stmt with args', + sql: [ + 'EXECUTE test(a, b, c)', + 'EXECUTE test(a, b, c)' + ] + }, + { + title: 'for loop', + sql: [ + `CREATE FUNCTION refresh_mviews() RETURNS integer AS $$ + DECLARE + mviews RECORD; + BEGIN + RAISE NOTICE 'Refreshing all materialized views...'; + + FOR mviews IN + SELECT n.nspname AS mv_schema, + c.relname AS mv_name, + pg_catalog.pg_get_userbyid(c.relowner) AS owner + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) + WHERE c.relkind = 'm' + ORDER BY 1 + LOOP + + -- Now "mviews" has one record with information about the materialized view + + RAISE NOTICE 'Refreshing materialized view %.% (owner: %)...', + quote_ident(mviews.mv_schema), + quote_ident(mviews.mv_name), + quote_ident(mviews.owner); + EXECUTE format('REFRESH MATERIALIZED VIEW %I.%I', mviews.mv_schema, mviews.mv_name); + END LOOP; + + RAISE NOTICE 'Done refreshing materialized views.'; + RETURN 1; + END; + $$ LANGUAGE plpgsql;`, + `CREATE FUNCTION refresh_mviews() RETURNS INTEGER AS $$ DECLARE mviews RECORD BEGIN RAISE NOTICE 'Refreshing all materialized views...' ; FOR mviews IN SELECT "n".nspname AS "mv_schema", "c".relname AS "mv_name", pg_catalog.pg_get_userbyid("c".relowner) AS "owner" FROM "pg_catalog"."pg_class" AS "c" LEFT JOIN "pg_catalog"."pg_namespace" AS "n" ON ("n".oid = "c".relnamespace) WHERE "c".relkind = 'm' ORDER BY 1 ASC LOOP RAISE NOTICE 'Refreshing materialized view %.% (owner: %)...', quote_ident("mviews"."mv_schema"), quote_ident("mviews"."mv_name"), quote_ident("mviews"."owner") ; EXECUTE format('REFRESH MATERIALIZED VIEW %I.%I', "mviews"."mv_schema", "mviews"."mv_name") END LOOP ; RAISE NOTICE 'Done refreshing materialized views.' ; RETURN 1 END $$ LANGUAGE plpgsql` + ] + }, + { + title: 'support accentuated characters', + sql: [ + "SELECT 'Molière' AS théâtre", + `SELECT 'Molière' AS "théâtre"` + ] + }, + { + title: 'support character varying', + sql: [ + `CREATE TABLE "public"."authors_table" ( + "author_id" integer NOT NULL, + "first_name" character varying NOT NULL, + "last_name" character varying NOT NULL, + "birth_date" date + );`, + 'CREATE TABLE "public"."authors_table" ("author_id" INTEGER NOT NULL, "first_name" CHARACTER VARYING NOT NULL, "last_name" CHARACTER VARYING NOT NULL, "birth_date" DATE)' + ] + }, + { + title: 'as double quoted', + sql: [ + 'select 1 as "one"', + 'SELECT 1 AS "one"' + ] + }, + { + title: 'intersect op', + sql: [ + `SELECT item + FROM public.orders + WHERE ID = 1 + INTERSECT + SELECT "sku" + FROM public.inventory + WHERE ID = 1`, + 'SELECT item FROM "public"."orders" WHERE ID = 1 INTERSECT SELECT "sku" FROM "public"."inventory" WHERE ID = 1' + ] + }, + { + title: 'set to in transactions', + sql: [ + `BEGIN; + SET search_path TO ht_hyt; + COMMIT;`, + `BEGIN ; SET search_path TO ht_hyt ; COMMIT ;`, + ] + }, + { + title: 'transaction stmt', + sql: [ + 'begin;', + 'BEGIN ;' + ] + }, + { + title: 'double quoted column cast', + sql: [ + 'SELECT "created_date"::date FROM src_hosts', + 'SELECT "created_date"::DATE FROM "src_hosts"' + ] + }, + { + title: 'preserve column quoted symbol', + sql: [ + 'select "abc", def from tableName', + 'SELECT "abc", def FROM "tableName"' + ] + }, + { + title: 'quoted function name', + sql: [ + 'SELECT * FROM "func"("start_time", "end_time")', + 'SELECT * FROM "func"("start_time", "end_time")' + ] + }, + { + title: 'function name prefixed with quoted schema', + sql: [ + 'SELECT * FROM "schema"."func"("start_time", "end_time")', + 'SELECT * FROM "schema"."func"("start_time", "end_time")' + ] + }, + { + title: 'truncate table', + sql: [ + 'TRUNCATE TABLE employee RESTART IDENTITY cascade', + 'TRUNCATE TABLE "employee" RESTART IDENTITY CASCADE' + ] + }, + { + title: 'truncate all table', + sql: [ + 'TRUNCATE TABLE ONLY employee * CONTINUE IDENTITY RESTRICT', + 'TRUNCATE TABLE ONLY "employee" * CONTINUE IDENTITY RESTRICT' + ] + }, + { + title: 'jsonb in select column', + sql: [ + "SELECT data['author']['first_name'] as title FROM blogs", + `SELECT data['author']['first_name'] AS "title" FROM "blogs"` + ] + }, + { + title: 'jsonb in update set', + sql: [ + `UPDATE blogs SET data['author']['first_name'] = '"Sarah"'`, + `UPDATE "blogs" SET data['author']['first_name'] = '"Sarah"'` + ] + }, + { + title: 'table partitioning with', + sql: [ + 'CREATE TABLE access_keys_hash_p0 PARTITION OF access_keys FOR VALUES WITH (modulus 10, remainder 0)', + 'CREATE TABLE "access_keys_hash_p0" PARTITION OF "access_keys" FOR VALUES WITH (MODULUS 10, REMAINDER 0)' + ] + }, + { + title: 'table partitioning from', + sql: [ + `CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace;`, + `CREATE TABLE "measurement_y2007m12" PARTITION OF "measurement" FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') TABLESPACE fasttablespace` + ] + }, + { + title: 'table partitioning in', + sql: [ + "CREATE TABLE electronics PARTITION OF products FOR VALUES IN ('Electronics');", + `CREATE TABLE "electronics" PARTITION OF "products" FOR VALUES IN ('Electronics')` + ] + }, + { + title: 'distinct in args', + sql: [ + `SELECT + f.title, + STRING_AGG ( + a.first_name || ' ' || a.last_name, + ',' + ORDER BY + a.first_name, + a.last_name + ) actors + FROM + film f + INNER JOIN film_actor fa USING (film_id) + INNER JOIN actor a USING (actor_id) + GROUP BY + f.title;`, + `SELECT "f".title, STRING_AGG("a".first_name || ' ' || "a".last_name, ',' ORDER BY "a".first_name ASC, "a".last_name ASC) AS "actors" FROM "film" AS "f" INNER JOIN "film_actor" AS "fa" USING ("film_id") INNER JOIN "actor" AS "a" USING ("actor_id") GROUP BY "f".title` + ] + }, + { + title: 'drop if exists', + sql: [ + 'DROP TABLE IF EXISTS table_name;', + 'DROP TABLE IF EXISTS "table_name"' + ] + }, + { + title: 'ilike binary operator', + sql: [ + "SELECT a FROM b WHERE a::TEXT ILIKE '%x%'", + `SELECT a FROM "b" WHERE a::TEXT ILIKE '%x%'` + ] + }, + { + title: 'check constraint', + sql: [ + 'CREATE TABLE Books (price DECIMAL(10, 2) CHECK (Price > 0));', + 'CREATE TABLE "Books" (price DECIMAL(10, 2) CHECK (Price > 0))' + ] + }, + { + title: 'array data type', + sql: [ + `CREATE TABLE "table_0" ("hi" INTEGER ARRAY); CREATE TABLE "table_1" ("hi" INTEGER[3]);`, + `CREATE TABLE "table_0" ("hi" INTEGER ARRAY) ; CREATE TABLE "table_1" ("hi" INTEGER[3])` + ] + }, + { + title: 'binary expr as fun args', + sql: [ + `SELECT + somefunc( + engineering_networks.realizaciya, + engineering_networks.company = 'blah-blah' + AND + engineering_networks.obem_realizacii_tip = 'uslugi' + ) AS var0, + If(var0 > 0, '2', '1') AS fontColor + FROM engineering_networks AS engineering_networks + WHERE + engineering_networks.company = 'blah-blah' AND + engineering_networks.month IN ('April') AND + engineering_networks.year IN ('2024') + LIMIT 1;`, + `SELECT somefunc("engineering_networks".realizaciya, "engineering_networks".company = 'blah-blah' AND "engineering_networks".obem_realizacii_tip = 'uslugi') AS "var0", If(var0 > 0, '2', '1') AS "fontColor" FROM "engineering_networks" AS "engineering_networks" WHERE "engineering_networks".company = 'blah-blah' AND "engineering_networks".month IN ('April') AND "engineering_networks".year IN ('2024') LIMIT 1` + ], + }, + { + title: 'alter column data type', + sql: [ + `ALTER TABLE employees ALTER COLUMN first_name SET DATA TYPE TEXT COLLATE "C" using upper(first_name);`, + 'ALTER TABLE "employees" ALTER COLUMN first_name SET DATA TYPE TEXT COLLATE "C" USING upper(first_name)' + ] + }, + { + title: 'alter column set and drop default', + sql: [ + "ALTER TABLE transactions ADD COLUMN status varchar(30) DEFAULT 'old', ALTER COLUMN status SET default 'current', ALTER COLUMN name drop default;", + `ALTER TABLE "transactions" ADD COLUMN status VARCHAR(30) DEFAULT 'old', ALTER COLUMN status SET DEFAULT 'current', ALTER COLUMN name DROP DEFAULT`, + ] + }, + { + title: 'alter column set not null', + sql: [ + 'ALTER TABLE transactions ALTER COLUMN status SET NOT NULL', + 'ALTER TABLE "transactions" ALTER COLUMN status SET NOT NULL', + ] + }, + { + title: 'jsonb operator', + sql: [ + "SELECT id, collection::jsonb ?| array['val1', 'val2'] FROM instances", + `SELECT id, collection::JSONB ?| ARRAY['val1','val2'] FROM "instances"` + ] + }, + { + title: 'create type', + sql: [ + `CREATE TYPE address AS ( + street VARCHAR(255), + city VARCHAR(100), + state VARCHAR(100), + zip_code VARCHAR(10) + );`, + 'CREATE TYPE "address" AS (street VARCHAR(255), city VARCHAR(100), state VARCHAR(100), zip_code VARCHAR(10))' + ] + }, + { + title: 'create type as range', + sql: [ + 'CREATE TYPE float8_range AS RANGE (subtype = float8, subtype_diff = float8mi);', + 'CREATE TYPE "float8_range" AS RANGE (subtype = float8, subtype_diff = float8mi)' + ] + }, + { + title: 'create table with column_constraint', + sql: [ + `CREATE TABLE public.tnotok ("id" SERIAL CONSTRAINT users_PK PRIMARY KEY, description text DEFAULT ''::text NOT NULL);`, + `CREATE TABLE "public"."tnotok" ("id" SERIAL CONSTRAINT users_PK PRIMARY KEY, description TEXT NOT NULL DEFAULT ''::TEXT)`, + ] + }, + { + title: 'create table with quoted data type', + sql: [ + 'CREATE TABLE "User" ("gender" "Gender" NOT NULL);', + 'CREATE TABLE "User" ("gender" "Gender" NOT NULL)', + ] + }, + { + title: 'no space before line comment', + sql: [ + 'select * from model_a a-- comment', + 'SELECT * FROM "model_a" AS "a"' + ] + }, + { + title: 'mixed jsonb and cast', + sql: [ + "SELECT person.name FROM person WHERE person.email_addresses::json -> 'items'::text ILIKE '%sam%'", + `SELECT "person".name FROM "person" WHERE "person".email_addresses::JSON -> 'items'::TEXT ILIKE '%sam%'` + ] + }, + { + title: 'multiple jsonb operator expr', + sql: [ + `select '{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}', '{"a":1, "b":2}'::jsonb @> '{"b":2}'::jsonb, '{"a":1, "b":2}'::jsonb ? 'b', '{"a":1, "b":2, "c":3}'::jsonb ?| array['b', 'c'] +, '["a", "b"]'::jsonb ?& array['a', 'b'], '["a", "b"]'::jsonb || '["c", "d"]'::jsonb, '{"a": "b"}'::jsonb - 'a', '["a", "b"]'::jsonb - 1, '["a", {"b":1}]'::jsonb #- '{1,b}'`, + `SELECT '{"a": {"b":{"c": "foo"}}}'::JSON #> '{a,b}', '{"a":1, "b":2}'::JSONB @> '{"b":2}'::JSONB, '{"a":1, "b":2}'::JSONB ? 'b', '{"a":1, "b":2, "c":3}'::JSONB ?| ARRAY['b','c'], '["a", "b"]'::JSONB ?& ARRAY['a','b'], '["a", "b"]'::JSONB || '["c", "d"]'::JSONB, '{"a": "b"}'::JSONB - 'a', '["a", "b"]'::JSONB - 1, '["a", {"b":1}]'::JSONB #- '{1,b}'` + ] + }, + { + title: 'timestamptz data type', + sql: [ + 'CREATE TABLE "Users" (created_at timestamptz);', + 'CREATE TABLE "Users" (created_at TIMESTAMPTZ)' + ] + }, + { + title: 'start transaction', + sql: [ + 'start transaction isolation level repeatable read, read only, not deferrable', + 'START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY, NOT DEFERRABLE' + ] + }, + { + title: 'create table as', + sql: [ + 'create table test as select 1', + 'CREATE TABLE "test" AS SELECT 1' + ] + }, + ] + function neatlyNestTestedSQL(sqlList){ + sqlList.forEach(sqlInfo => { + const {title, sql} = sqlInfo + it(`should support ${title}`, () => { + expect(getParsedSql(sql[0], opt)).to.equal(sql[1]) + }) + }) + } + + neatlyNestTestedSQL(SQL_LIST) + + describe('tables to sql', () => { + it('should parse object tables', () => { + const ast = parser.astify(SQL_LIST[100].sql[0], opt) + ast[0].from[0].expr.parentheses = false + expect(parser.sqlify(ast, opt)).to.be.equal('SELECT last_name, salary FROM "employees" INNER JOIN "salaries" ON "employees".emp_no = "salaries".emp_no') + }) + }) + + describe('test pg parser speed', () => { + it('should parse nested function call in ms', () => { + const sql = `SELECT + f(f(f( + SELECT + f( + f( + f(c1,c2,c3,c4,c5,c6,c7,c8,c9) + ) + ) + FROM t2 + + ))) as cf + FROM t1` + const opt = { database: 'postgresql' } + let start = Date.now() + parser.astify(sql, opt) + let end = Date.now() + expect(end - start).to.be.lessThanOrEqual(1000) + start = Date.now() + parser.astify("SELECT coalesce(JSON_ARRAYAGG(JSON_OBJECT('id', id,'productId', productId,'colorId', colorId,'type', type)), JSON_ARRAY()) FROM abc") + end = Date.now() + expect(end - start).to.be.lessThan(100) + }) + }) + + describe('returning', () => { + it('should parse returning clause', () => { + let sql = "UPDATE buildings SET address = 'update test 2' WHERE id = 18 RETURNING id, address" + expect(getParsedSql(sql, opt)).to.equal(`UPDATE "buildings" SET address = 'update test 2' WHERE id = 18 RETURNING id, address`) + sql = "UPDATE buildings SET address = 'update test 2' WHERE id = 18 RETURNING *, address as newAddress" + expect(getParsedSql(sql, opt)).to.equal(`UPDATE "buildings" SET address = 'update test 2' WHERE id = 18 RETURNING *, address AS "newAddress"`) + sql = "UPDATE buildings SET address = 'update test 2' WHERE id = 18 RETURNING (SELECT address FROM buildings WHERE id = 18) as old_address;" + expect(getParsedSql(sql, opt)).to.equal(`UPDATE "buildings" SET address = 'update test 2' WHERE id = 18 RETURNING (SELECT address FROM "buildings" WHERE id = 18) AS "old_address"`) + }) + }) + + describe('create sequence', () => { + const SQL_LIST = [ + { + title: 'create sequence', + sql: [ + `CREATE SEQUENCE public.table_id_seq`, + 'CREATE SEQUENCE "public"."table_id_seq"' + ] + }, + { + title: 'create sequence increment by', + sql: [ + `CREATE TEMPORARY SEQUENCE if not exists public.table_id_seq increment by 10`, + 'CREATE TEMPORARY SEQUENCE IF NOT EXISTS "public"."table_id_seq" INCREMENT BY 10' + ] + }, + { + title: 'create sequence increment by minvalue and maxvalue', + sql: [ + `CREATE TEMPORARY SEQUENCE if not exists public.table_id_seq increment by 10 minvalue 20 maxvalue 30`, + 'CREATE TEMPORARY SEQUENCE IF NOT EXISTS "public"."table_id_seq" INCREMENT BY 10 MINVALUE 20 MAXVALUE 30' + ] + }, + { + title: 'create sequence increment by start with cache', + sql: [ + `CREATE TEMPORARY SEQUENCE if not exists public.table_id_seq increment by 10 no minvalue no maxvalue start with 1 cache 3`, + 'CREATE TEMPORARY SEQUENCE IF NOT EXISTS "public"."table_id_seq" INCREMENT BY 10 NO MINVALUE NO MAXVALUE START WITH 1 CACHE 3' + ] + }, + { + title: 'create sequence increment by start with cache, cycle and owned', + sql: [ + `CREATE TEMPORARY SEQUENCE if not exists public.table_id_seq increment by 10 no minvalue no maxvalue start with 1 cache 3 no cycle owned by tn.cn`, + 'CREATE TEMPORARY SEQUENCE IF NOT EXISTS "public"."table_id_seq" INCREMENT BY 10 NO MINVALUE NO MAXVALUE START WITH 1 CACHE 3 NO CYCLE OWNED BY "tn".cn' + ] + }, + { + title: 'create sequence increment by start with cache, cycle and owned', + sql: [ + `CREATE TEMPORARY SEQUENCE if not exists public.table_id_seq increment 10 no minvalue no maxvalue start with 1 cache 3 cycle owned by none`, + 'CREATE TEMPORARY SEQUENCE IF NOT EXISTS "public"."table_id_seq" INCREMENT 10 NO MINVALUE NO MAXVALUE START WITH 1 CACHE 3 CYCLE OWNED BY NONE' + ] + }, + { + title: 'cast oid type explicit', + sql: [ + `SELECT CAST(c AS OID) FROM pg_attribute`, + 'SELECT CAST(c AS OID) FROM "pg_attribute"' + ] + }, + { + title: 'cast oid type explicit', + sql: [ + `SELECT TRY_CAST(c AS OID) FROM pg_attribute`, + 'SELECT TRY_CAST(c AS OID) FROM "pg_attribute"' + ] + }, + { + title: 'cast oid type implicit', + sql: [ + `SELECT c::OID FROM pg_attribute`, + 'SELECT c::OID FROM "pg_attribute"' + ] + }, + { + title: 'cast regclass oid type', + sql: [ + `SELECT c::REGCLASS FROM pg_attribute`, + 'SELECT c::REGCLASS FROM "pg_attribute"' + ] + }, + { + title: 'cast regregcollation oid type', + sql: [ + `SELECT c::REGCOLLATION FROM pg_attribute`, + 'SELECT c::REGCOLLATION FROM "pg_attribute"' + ] + }, + { + title: 'cast regconfig oid type', + sql: [ + `SELECT c::REGCONFIG FROM pg_attribute`, + 'SELECT c::REGCONFIG FROM "pg_attribute"' + ] + }, + { + title: 'cast regdictionary oid type', + sql: [ + `SELECT c::REGDICTIONARY FROM pg_attribute`, + 'SELECT c::REGDICTIONARY FROM "pg_attribute"' + ] + }, + { + title: 'cast regnamespace oid type', + sql: [ + `SELECT c::REGNAMESPACE FROM pg_attribute`, + 'SELECT c::REGNAMESPACE FROM "pg_attribute"' + ] + }, + { + title: 'cast regoper oid type', + sql: [ + `SELECT c::REGOPER FROM pg_attribute`, + 'SELECT c::REGOPER FROM "pg_attribute"' + ] + }, + { + title: 'cast regoperator oid type', + sql: [ + `SELECT c::REGOPERATOR FROM pg_attribute`, + 'SELECT c::REGOPERATOR FROM "pg_attribute"' + ] + }, + { + title: 'cast regproc oid type', + sql: [ + `SELECT c::REGPROC FROM pg_attribute`, + 'SELECT c::REGPROC FROM "pg_attribute"' + ] + }, + { + title: 'cast regprocedure oid type', + sql: [ + `SELECT c::REGPROCEDURE FROM pg_attribute`, + 'SELECT c::REGPROCEDURE FROM "pg_attribute"' + ] + }, + { + title: 'cast regrole oid type', + sql: [ + `SELECT c::REGROLE FROM pg_attribute`, + 'SELECT c::REGROLE FROM "pg_attribute"' + ] + }, + { + title: 'cast regtype oid type', + sql: [ + `SELECT c::REGTYPE FROM pg_attribute`, + 'SELECT c::REGTYPE FROM "pg_attribute"' + ] + }, + { + title: 'cast varchar with length', + sql: [ + 'SELECT name::VARCHAR(200) FROM raw_hosts', + 'SELECT name::VARCHAR(200) FROM "raw_hosts"' + ] + }, + { + title: 'chinese oridinary identifier', + sql: [ + 'select 中文 from t1;', + 'SELECT 中文 FROM "t1"' + ], + }, + { + title: 'chinese delimited identifier', + sql: [ + 'select "中文" from t1;', + 'SELECT "中文" FROM "t1"' + ], + }, + { + title: 'double precision type', + sql: [ + `CREATE TABLE test ( + amount double precision + );`, + 'CREATE TABLE "test" (amount DOUBLE PRECISION)' + ] + }, + { + title: 'crosstab tablefunc', + sql: [ + `SELECT * FROM crosstab( 'select student, subject, evaluation_result from evaluations order by 1,2', $$VALUES ('t'::text), ('f'::text)$$) AS final_result(Student TEXT, Geography NUMERIC,History NUMERIC,Language NUMERIC,Maths NUMERIC,Music NUMERIC);`, + `SELECT * FROM crosstab('select student, subject, evaluation_result from evaluations order by 1,2', $$VALUES ('t'::text), ('f'::text)$$) AS final_result(Student TEXT, Geography NUMERIC, History NUMERIC, Language NUMERIC, Maths NUMERIC, Music NUMERIC)` + ] + }, + { + title: 'accentuated characters in column', + sql: [ + "SELECT * FROM test WHERE théâtre = 'Molière'", + `SELECT * FROM "test" WHERE théâtre = 'Molière'` + ] + }, + { + title: 'cast when expr is additive expr', + sql: [ + `SELECT + CASE + WHEN updated IS NOT NULL THEN (updated - created)::TIME + END AS some_time + FROM some_table`, + 'SELECT CASE WHEN updated IS NOT NULL THEN (updated - created)::TIME END AS "some_time" FROM "some_table"' + ] + }, + { + title: 'custom data type', + sql: [ + `CREATE TYPE access_key_permission_kind AS ENUM ('FULL_ACCESS', 'FUNCTION_CALL'); + + CREATE TABLE + access_keys ( + public_key text NOT NULL, + account_id text NOT NULL, + permission_kind access_key_permission_kind NOT NULL, + CONSTRAINT access_keys_pk PRIMARY KEY (public_key, account_id) + ) PARTITION BY HASH (public_key);`, + `CREATE TYPE "access_key_permission_kind" AS ENUM ('FULL_ACCESS', 'FUNCTION_CALL') ; CREATE TABLE "access_keys" (public_key TEXT NOT NULL, account_id TEXT NOT NULL, permission_kind access_key_permission_kind NOT NULL, CONSTRAINT "access_keys_pk" PRIMARY KEY (public_key, account_id)) PARTITION BY HASH(public_key)` + ] + }, + { + title: 'json to record or recordset table func', + sql: [ + `SELECT + * + from + jsonb_to_recordset( + '[{"amount":23, "currency": "INR"}]' :: jsonb + ) as l_amount (amount decimal, currency text);`, + `SELECT * FROM jsonb_to_recordset('[{"amount":23, "currency": "INR"}]'::JSONB) AS l_amount(amount DECIMAL, currency TEXT)` + ] + }, + { + title: 'create scheme', + sql: [ + 'CREATE SCHEMA public;', + 'CREATE SCHEMA public' + ] + }, + { + title: 'create constraint with quoted name', + sql: [ + `CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + CONSTRAINT "User_pkey" PRIMARY KEY ("id") + );`, + 'CREATE TABLE "User" ("id" SERIAL NOT NULL, CONSTRAINT "User_pkey" PRIMARY KEY ("id"))' + ] + }, + { + title: 'reserved keyword as column name', + sql: [ + 'select meeting.end from meeting', + 'SELECT "meeting".end FROM "meeting"' + ] + }, + { + title: 'comment on table', + sql: [ + "COMMENT ON TABLE users IS 'users table';", + `COMMENT ON TABLE "users" IS 'users table'` + ] + }, + { + title: 'comment on column', + sql: [ + `COMMENT ON COLUMN "users"."name" IS 'first name and last name';`, + `COMMENT ON COLUMN "users"."name" IS 'first name and last name'` + ] + }, + { + title: 'comment on database', + sql: [ + `COMMENT ON DATABASE my_database IS 'Development Database';`, + `COMMENT ON DATABASE my_database IS 'Development Database'`, + ], + }, + ] + neatlyNestTestedSQL(SQL_LIST) + }) + + describe('pg ast', () => { + it('should get correct columns and tables', () => { + let sql = 'SELECT "Id" FROM "Test";' + let ast = parser.parse(sql, opt) + expect(ast.tableList).to.be.eql(['select::null::Test']) + expect(ast.columnList).to.be.eql(['select::null::Id']) + expect(ast.ast[0].columns).to.be.eql([ + { + type: 'expr', + expr: { + type: 'column_ref', + table: null, + column: { + expr: { + type: 'double_quote_string', + value: 'Id' + } + } + }, + as: null + } + ]) + expect(parser.sqlify(ast.ast, opt)).to.be.equals(sql.slice(0, -1)) + sql = 'SELECT col1 + "col2" FROM "t1"' + ast = parser.parse(sql, opt) + expect(ast.tableList).to.be.eql(['select::null::t1']) + expect(ast.columnList).to.be.eql(['select::null::col1', 'select::null::col2']) + expect(ast.ast.columns[0].expr.right).to.be.eql({ + type: 'column_ref', + table: null, + column: { + expr: { + type: 'double_quote_string', + value: 'col2' + } + } + }) + expect(parser.sqlify(ast.ast, opt)).to.be.equals('SELECT col1 + "col2" FROM "t1"') + sql = 'SELECT "col1" + "col2" FROM "t1"' + ast = parser.parse(sql, opt) + expect(ast.ast.columns[0].expr.left).to.be.eql({ + type: 'column_ref', + table: null, + column: { + expr: { + type: 'double_quote_string', + value: 'col1' + } + } + }) + expect(parser.sqlify(ast.ast, opt)).to.be.equals('SELECT "col1" + "col2" FROM "t1"') + }) + + it('should support conflict be empty', () => { + expect(conflictToSQL(null)).to.be.equal('') + }) + it('should proc assign', () => { + expect(procToSQL({stmt: {type: 'assign', left: {type: 'default', value: 'abc'}, keyword: '', right: {type: 'number', value: 123}, symbol: '='}})).to.be.equal('abc = 123') + }) + it('should has loc property', () => { + const sql = 'SELECT * FROM "company" WHERE NOT(company._id IS NULL)' + const opt = { database: 'postgresql', parseOptions: { includeLocations: true } } + const ast = parser.astify(sql, opt) + expect(ast.where.loc).to.be.eql({ + "start": { + "offset": 30, + "line": 1, + "column": 31 + }, + "end": { + "offset": 54, + "line": 1, + "column": 55 + } + }) + expect(ast.where.args.value[0].loc).to.be.eql({ + "start": { + "offset": 34, + "line": 1, + "column": 35 + }, + "end": { + "offset": 53, + "line": 1, + "column": 54 + } + }) + }) + it('should throw error', () => { + const sql = "select 1 as 'one'" + const fun = parser.astify.bind(parser, sql, opt) + expect(fun).to.throw(`Expected "--", "/*", "\\"", [ \\t\\n\\r], or [A-Za-z_一-龥] but "'" found.`) + }) + }) + describe('pg parse speed', function () { + this.timeout(30) + const sql = `SELECT + "pr"."destination_currency" AS "currency", + "pr"."idempotency_key" AS "unqiueRequestId", + "pr"."status" AS "paymentRequestStatus", + "pr"."payment_date" AS "paymentDate", + "pr"."provider_system_reference_number" AS "providerSystemReferenceNumber", + "pr"."destination_amount" AS "destinationAmount", + "pr"."error" AS "error", + "pr"."source_id" AS "sourceId", + "pr"."source_type" AS "sourceType", + "pr"."client_legal_entity_id" AS "clientLegalEntityId", + "pr"."beneficiary_legal_entity_id" AS "beneficiaryLegalEntityId", + "pr"."provider" AS "provider", + "txn"."created_at" AS "transactionCreatedAt", + "txn"."updated_at" AS "transactionUpdatedAt", + "txn"."provider_metadata" AS "providerMetadata", + "txn"."currency" AS "currency", + "txn"."amount" AS "amount", + "txn"."status" AS "status", + "txn"."type" AS "txnType", + "txn"."purpose_of_payment" AS "purposeOfPayment", + "txn"."client_reference" AS "clientReference", + "txn"."description" AS "transactionDescription", + pr.meta -> 'invoiceIds' AS "invoiceIds" + FROM + "public"."payment_request" "pr" + LEFT JOIN "public"."transaction" "txn" ON "txn"."payment_request_id" = "pr"."id" + WHERE + "pr"."source_id" IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $50) + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + OR pr.meta::jsonb -> 'invoiceIds' @> '["c937cd8c-65bc-4006-9413-dde7b43c61b6"]' + ORDER BY + "pr"."created_at" DESC` + const ast = parser.astify(sql, opt) + expect(ast).to.be.an('object') + }) +}) diff --git a/test/select.spec.js b/test/select.spec.js index 9a560d50..771b26a9 100644 --- a/test/select.spec.js +++ b/test/select.spec.js @@ -1437,6 +1437,68 @@ describe('select', () => { }) }) + describe('datafusionsql', () => { + const opt = { + database: 'datafusionsql' + } + it('should properly escape column aliases that contain special characters', () => { + const sql = `select column_name as "Column Name" from table_name` + expect(getParsedSql(sql, opt)).to.equal('SELECT column_name AS "Column Name" FROM "table_name"') + }) + + it('should support union in in_op', () => { + const sql = `select 1 from pg_database a where a.oid in + ( + select 1 from pg_database b where b.oid = 1 + union + select 1 from pg_database c where c.oid=2 + )` + expect(getParsedSql(sql, opt)).to.be.equal('SELECT 1 FROM "pg_database" AS "a" WHERE "a".oid IN (SELECT 1 FROM "pg_database" AS "b" WHERE "b".oid = 1 UNION SELECT 1 FROM "pg_database" AS "c" WHERE "c".oid = 2)') + }) + + it('should support union distinct in in_op', () => { + const sql = `select 1 from pg_database a where a.oid in + ( + select 1 from pg_database b where b.oid = 1 + union distinct + select 1 from pg_database c where c.oid=2 + )` + expect(getParsedSql(sql, opt)).to.be.equal('SELECT 1 FROM "pg_database" AS "a" WHERE "a".oid IN (SELECT 1 FROM "pg_database" AS "b" WHERE "b".oid = 1 UNION DISTINCT SELECT 1 FROM "pg_database" AS "c" WHERE "c".oid = 2)') + }) + + it('should support array_agg', () => { + let sql = `SELECT shipmentId, ARRAY_AGG(distinct abc order by name) AS shipmentStopIDs, ARRAY_AGG (first_name || ' ' || last_name) actors FROM table_name GROUP BY shipmentId` + expect(getParsedSql(sql, opt)).to.equal('SELECT shipmentId, ARRAY_AGG(DISTINCT abc ORDER BY name ASC) AS "shipmentStopIDs", ARRAY_AGG(first_name || \' \' || last_name) AS "actors" FROM "table_name" GROUP BY shipmentId') + sql = 'select pg_catalog.array_agg(c1 order by c2) from t1' + expect(getParsedSql(sql, opt)).to.equal('SELECT pg_catalog.ARRAY_AGG(c1 ORDER BY c2 ASC) FROM "t1"') + }) + + it('should support array_agg in coalesce', () => { + const sql = `SELECT COALESCE(array_agg(DISTINCT(a.xx)), Array[]::text[]) AS "distinctName" FROM public."Users" a1` + expect(getParsedSql(sql, opt)).to.equal('SELECT COALESCE(ARRAY_AGG(DISTINCT ("a".xx)), ARRAY[]::TEXT[]) AS "distinctName" FROM "public"."Users" AS "a1"') + }) + + it('should support ilike', () => { + const sql = `select column_name as "Column Name" from table_name where a ilike 'f%' and 'b' not ilike 'B'` + expect(getParsedSql(sql, opt)).to.equal('SELECT column_name AS "Column Name" FROM "table_name" WHERE a ILIKE \'f%\' AND \'b\' NOT ILIKE \'B\'') + }) + + it('should support like and', () => { + const sql = `SELECT "contact"."_id" FROM "contact" WHERE LOWER("contact"."name.givenName") LIKE 'yan%' AND LOWER("contact"."name.familyName") LIKE 'ei%';` + expect(getParsedSql(sql, opt)).to.equal(`SELECT "contact"."_id" FROM "contact" WHERE LOWER("contact"."name.givenName") LIKE 'yan%' AND LOWER("contact"."name.familyName") LIKE 'ei%'`) + }) + + it('should support left', () => { + const sql = 'SELECT * FROM partitions WHERE "location" IS null AND "code" <> left("name", length("code"))' + expect(getParsedSql(sql, opt)).to.equal('SELECT * FROM "partitions" WHERE "location" IS NULL AND "code" <> left("name", length("code"))') + }) + + it('should support try_cast', () => { + const sql = 'SELECT try_cast("10" as INT) as num FROM partitions' + expect(getParsedSql(sql, opt)).to.equal('SELECT try_cast("10" as INT) as num FROM partitions') + }) + }) + describe('unknown type check', () => { it('should throw error', () => { From 9580814477169baed4900dcd0bf081439b4f0dd3 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Tue, 1 Oct 2024 15:06:49 +0530 Subject: [PATCH 2/7] Update package-lock.json --- README.md | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 62b2b8c2..e4c95673 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,48 @@ -## Nodejs SQL Parser +# Nodejs SQL Parser + +[![Build Status](https://travis-ci.org/taozhi8833998/node-sql-parser.svg?branch=master)](https://travis-ci.org/taozhi8833998/node-sql-parser) +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/d923c9f2853f44f295c383d9943b56cc)](https://www.codacy.com/manual/taozhi8833998/node-sql-parser?utm_source=github.com&utm_medium=referral&utm_content=taozhi8833998/node-sql-parser&utm_campaign=Badge_Grade) +[![Coverage Status](https://img.shields.io/coveralls/github/taozhi8833998/node-sql-parser/master.svg)](https://coveralls.io/github/taozhi8833998/node-sql-parser?branch=master) +[![Dependencies](https://img.shields.io/david/taozhi8833998/node-sql-parser.svg)](https://img.shields.io/david/taozhi8833998/node-sql-parser) +[![Known Vulnerabilities](https://snyk.io/test/github/taozhi8833998/node-sql-parser/badge.svg?targetFile=package.json)](https://snyk.io/test/github/taozhi8833998/node-sql-parser?targetFile=package.json) +[![](https://img.shields.io/badge/Powered%20by-ganjiang-brightgreen.svg)](https://github.com/taozhi8833998/node-sql-parser) + +[![npm version](https://badge.fury.io/js/node-sql-parser.svg)](https://badge.fury.io/js/node-sql-parser) +[![NPM downloads](http://img.shields.io/npm/dm/node-sql-parser.svg?style=flat-square)](http://www.npmtrends.com/node-sql-parser) + +[![](https://img.shields.io/gitter/room/taozhi8833998/node-sql-parser.svg)](https://gitter.im/node-sql-parser/community) +[![issues](https://img.shields.io/github/issues/taozhi8833998/node-sql-parser.svg)](https://github.com/taozhi8833998/node-sql-parser/issues) + +[![TypeScript definitions on DefinitelyTyped](http://definitelytyped.org/badges/standard.svg)](http://definitelytyped.org) +[![license](https://img.shields.io/npm/l/node-sql-parser)](https://github.com/taozhi8833998/node-sql-parser/blob/master/LICENSE) + +**Parse simple SQL statements into an abstract syntax tree (AST) with the visited tableList, columnList and convert it back to SQL.** + +## :star: Features + +- support multiple sql statement seperate by semicolon +- support select, delete, update and insert type +- support drop, truncate and rename command +- output the table and column list that the sql visited with the corresponding authority +- support various databases engine + +## :tada: Install + +### From [npmjs](https://www.npmjs.org/) + +```bash +npm install node-sql-parser --save + +or + +yarn add node-sql-parser +``` + +### From [GitHub Package Registry](https://npm.pkg.github.com/) + +```bash +npm install @taozhi8833998/node-sql-parser --registry=https://npm.pkg.github.com/ +``` ### From Browser @@ -44,8 +88,20 @@ Import the JS file in your page: ### Supported Database SQL Syntax +- Athena +- BigQuery +- DB2 +- Hive +- MariaDB +- MySQL - PostgresQL -- DatafusionSQL +- Redshift +- Sqlite +- TransactSQL +- [FlinkSQL](https://ci.apache.org/projects/flink/flink-docs-stable/dev/table/sql/) +- Snowflake(alpha) +- [Noql](https://noql.synatic.dev/) +- New issue could be made for other new database. ### Create AST for SQL statement @@ -288,4 +344,51 @@ const opt = { } // opt is optional parser.whiteListCheck(sql, whiteColumnList, opt) // if check failed, an error would be thrown with relevant error message, if passed it would return undefined -``` \ No newline at end of file +``` + +## :kissing_heart: Acknowledgement + +This project is inspired by the SQL parser [flora-sql-parser](https://github.com/godmodelabs/flora-sql-parser) module. + +## License + +[Apache-2.0](LICENSE) + +## Buy me a Coffee + +If you like my project, **Star** in the corresponding project right corner. Your support is my biggest encouragement! ^_^ + +You can also scan the qr code below or open paypal link to donate to Author. + +### Paypal + +Donate money by [paypal](https://www.paypal.me/taozhi8833998/5) to my account [taozhi8833998@163.com](taozhi8833998@163.com) + +### AliPay(支付宝) + +

+ +

+ + +### Wechat(微信) + +

+ +

+ +### Explain + +If you have made a donation, you can leave your name and email in the issue, your name will be written to the donation list. + + +## [Donation list](https://github.com/taozhi8833998/node-sql-parser/blob/master/DONATIONLIST.md) + +## Star History + + + + + Star History Chart + + \ No newline at end of file From 292bf2826e5dd8fe357ef98aa1da0aa763411ed5 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Tue, 1 Oct 2024 15:09:24 +0530 Subject: [PATCH 3/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e4c95673..cb13cf72 100644 --- a/README.md +++ b/README.md @@ -391,4 +391,4 @@ If you have made a donation, you can leave your name and email in the issue, you Star History Chart - \ No newline at end of file + From eb41355b5fb15e54ebbaaa22eda138498e9ba3ab Mon Sep 17 00:00:00 2001 From: Bhargav Date: Thu, 3 Oct 2024 16:02:21 +0530 Subject: [PATCH 4/7] mysql-datafusion --- pegjs/datafusionsql.pegjs | 5141 +++++++++++++------------------------ 1 file changed, 1813 insertions(+), 3328 deletions(-) diff --git a/pegjs/datafusionsql.pegjs b/pegjs/datafusionsql.pegjs index 7faf9657..c06f0170 100644 --- a/pegjs/datafusionsql.pegjs +++ b/pegjs/datafusionsql.pegjs @@ -6,101 +6,299 @@ 'AND': true, 'AS': true, 'ASC': true, + 'ANALYZE': true, + 'ACCESSIBLE': true, + 'BEFORE': true, 'BETWEEN': true, + 'BIGINT': true, + 'BLOB': true, + 'BOTH': true, 'BY': true, + 'BOOLEAN': true, 'CALL': true, + 'CASCADE': true, 'CASE': true, - 'CREATE': true, - 'CONTAINS': true, + 'CHAR': true, + 'CHECK': true, + 'COLLATE': true, + // 'COLUMN': true, + 'CONDITION': true, 'CONSTRAINT': true, + 'CONTINUE': true, + 'CONVERT': true, + 'CREATE': true, + 'CROSS': true, 'CURRENT_DATE': true, 'CURRENT_TIME': true, 'CURRENT_TIMESTAMP': true, 'CURRENT_USER': true, - + 'CURSOR': true, + + 'DATABASE': true, + 'DATABASES': true, + 'DAY_HOUR': true, + 'DAY_MICROSECOND': true, + 'DAY_MINUTE': true, + 'DAY_SECOND': true, + 'DEC': true, + 'DECIMAL': true, + 'DECLARE': true, + 'DEFAULT': true, + 'DELAYED': true, 'DELETE': true, 'DESC': true, + 'DESCRIBE': true, + 'DETERMINISTIC': true, 'DISTINCT': true, + 'DISTINCTROW': true, + 'DIV': true, 'DROP': true, + 'DOUBLE': true, + 'DUAL': true, 'ELSE': true, - 'END': true, + 'EACH': true, + 'ELSEIF': true, + 'ENCLOSED': true, + 'ESCAPED': true, + 'EXCEPT': true, 'EXISTS': true, + 'EXIT': true, 'EXPLAIN': true, 'FALSE': true, - 'FROM': true, 'FULL': true, - + 'FROM': true, + 'FETCH': true, + 'FLOAT': true, + 'FLOAT4': true, + 'FLOAT8': true, + 'FOR': true, + 'FORCE': true, + 'FOREIGN': true, + 'FULLTEXT': true, + 'FUNCTION': true, + + 'GENERATED': true, + 'GET': true, + 'GO': true, + 'GRANT': true, 'GROUP': true, + 'GROUPING': true, + 'GROUPS': true, 'HAVING': true, + 'HIGH_PRIORITY': true, + 'HOUR_MICROSECOND': true, + 'HOUR_MINUTE': true, + 'HOUR_SECOND': true, + // 'IF': true, + 'IGNORE': true, 'IN': true, 'INNER': true, + 'INFILE': true, + 'INOUT': true, + 'INSENSITIVE': true, 'INSERT': true, 'INTERSECT': true, + 'INT': true, + 'INT1': true, + 'INT2': true, + 'INT3': true, + 'INT4': true, + 'INT8': true, + 'INTEGER': true, + 'INTERVAL': true, 'INTO': true, + 'IO_AFTER_GTIDS': true, + 'IO_BEFORE_GTIDS': true, 'IS': true, - 'ILIKE': true, + 'ITERATE': true, 'JOIN': true, - 'JSON': true, - - // 'KEY': true, - + 'JSON_TABLE': true, + + 'KEY': true, + 'KEYS': true, + 'KILL': true, + + 'LAG': true, // added in 8.0.2 (reserved) + 'LAST_VALUE': true, + 'LATERAL': true, + 'LEAD': true, + 'LEADING': true, + 'LEAVE': true, 'LEFT': true, 'LIKE': true, 'LIMIT': true, - + 'LINEAR': true, + 'LINES': true, + 'LOAD': true, + 'LOCALTIME': true, + 'LOCALTIMESTAMP': true, + 'LOCK': true, + 'LONG': true, + 'LONGBLOB': true, + 'LONGTEXT': true, + 'LOOP': true, + 'LOW_PRIORITY': true, // for lock table + + 'MASTER_BIND': true, + 'MATCH': true, + 'MAXVALUE': true, + 'MEDIUMBLOB': true, + 'MEDIUMINT': true, + 'MEDIUMTEXT': true, + 'MIDDLEINT': true, + 'MINUTE_MICROSECOND': true, + 'MINUTE_SECOND': true, + 'MINUS': true, + 'MOD': true, + 'MODIFIES': true, + + + 'NATURAL': true, 'NOT': true, + 'NO_WRITE_TO_BINLOG': true, + 'NTH_VALUE': true, // added in 8.0.2 (reserved) + 'NTILE': true, // added in 8.0.2 (reserved) 'NULL': true, - 'NULLS': true, + 'NUMERIC': true, - 'OFFSET': true, + 'OF': true, // added in 8.0.1 (reserved) 'ON': true, + 'OPTIMIZE': true, + 'OPTIMIZER_COSTS': true, + 'OPTION': true, + 'OPTIONALLY': true, 'OR': true, 'ORDER': true, + 'OUT': true, 'OUTER': true, + 'OUTFILE': true, + 'OVER': true, // added in 8.0.2 (reserved) 'PARTITION': true, - + 'PERCENT_RANK': true, // added in 8.0.2 (reserved) + 'PRECISION': true, + 'PRIMARY': true, + 'PROCEDURE': true, + 'PURGE': true, + + 'RANGE': true, + 'RANK': true, // added in 8.0.2 (reserved) + 'READ': true, // for lock table + 'READS': true, // for lock table + 'READ_WRITE': true, // for lock table + 'REAL': true, // for lock table 'RECURSIVE': true, + 'REFERENCES': true, + 'REGEXP': true, + 'RELEASE': true, 'RENAME': true, - // 'REPLACE': true, + 'REPEAT': true, + 'REPLACE': true, + 'REQUIRE': true, + 'RESIGNAL': true, + 'RESTRICT': true, + 'RETURN': true, + 'REVOKE': true, 'RIGHT': true, + 'RLIKE': true, + 'ROW': true, // // added in 8.0.2 (reserved) + 'ROWS': true, // // added in 8.0.2 (reserved) + 'ROW_NUMBER': true, // // added in 8.0.2 (reserved) + 'SCHEMA': true, + 'SCHEMAS': true, 'SELECT': true, - 'SESSION_USER': true, + 'SENSITIVE': true, + 'SEPARATOR': true, 'SET': true, 'SHOW': true, - 'SYSTEM_USER': true, + 'SIGNAL': true, + 'SMALLINT': true, + 'SPATIAL': true, + 'SPECIFIC': true, + 'SQL': true, + 'SQLEXCEPTION': true, + 'SQLSTATE': true, + 'SQLWARNING': true, + 'SQL_BIG_RESULT': true, + // 'SQL_CALC_FOUND_ROWS': true, + // 'SQL_SMALL_RESULT': true, + 'SSL': true, + 'STARTING': true, + 'STORED': true, + 'STRAIGHT_JOIN': true, + 'SYSTEM': true, 'TABLE': true, + 'TERMINATED': true, 'THEN': true, + 'TINYBLOB': true, + 'TINYINT': true, + 'TINYTEXT': true, + 'TO': true, + 'TRAILING': true, + 'TRIGGER': true, 'TRUE': true, - 'TRUNCATE': true, 'UNION': true, + 'UNIQUE': true, + 'UNLOCK': true, + 'UNSIGNED': true, 'UPDATE': true, + 'USAGE': true, + 'USE': true, 'USING': true, + 'UTC_DATE': true, + 'UTC_TIME': true, + 'UTC_TIMESTAMP': true, - // 'VALUES': true, + 'VALUES': true, + 'VARBINARY': true, + 'VARCHAR': true, + 'VARCHARACTER': true, + 'VARYING': true, + 'VIRTUAL': true, - 'WITH': true, 'WHEN': true, 'WHERE': true, - 'WINDOW': true, + 'WHILE': true, + 'WINDOW': true, // added in 8.0.2 (reserved) + 'WITH': true, + 'WRITE': true, // for lock table - 'GLOBAL': true, - 'SESSION': true, - 'LOCAL': true, - 'PERSIST': true, - 'PERSIST_ONLY': true, + 'XOR': true, + + 'YEAR_MONTH': true, + + 'ZEROFILL': true, }; + const reservedFunctionName = { + avg: true, + sum: true, + count: true, + convert: true, + max: true, + min: true, + group_concat: true, + std: true, + variance: true, + current_date: true, + current_time: true, + current_timestamp: true, + current_user: true, + user: true, + session_user: true, + system_user: true, + } + function getLocationObject() { return options.includeLocations ? {loc: location()} : {} } @@ -131,7 +329,7 @@ } function createList(head, tail, po = 3) { - const result = Array.isArray(head) ? head : [head]; + const result = [head]; for (let i = 0; i < tail.length; i++) { delete tail[i][po].tableList delete tail[i][po].columnList @@ -176,10 +374,6 @@ columns.forEach(col => columnList.add(col)) } - function commonStrToLiteral(strOrLiteral) { - return typeof strOrLiteral === 'string' ? { type: 'same', value: strOrLiteral } : strOrLiteral - } - const cmpPrefixMap = { '+': true, '-': true, @@ -206,24 +400,40 @@ // used for dependency analysis let varList = []; + const tableList = new Set(); const columnList = new Set(); - const customTypes = new Set(); const tableAlias = {}; } start - = __ n:(create_function_stmt / multiple_stmt) { - // => multiple_stmt + = head:start_item __ tail:(__ KW_GO __ start_item)* { + if (!tail || tail.length === 0) return head + delete head.tableList + delete head.columnList + let cur = head + for (let i = 0; i < tail.length; i++) { + delete tail[i][3].tableList + delete tail[i][3].columnList + cur.go_next = tail[i][3] + cur.go = 'go' + cur = cur.go_next + } + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: head + } + } + +start_item + = __ n:multiple_stmt { return n } - / create_function_stmt - / multiple_stmt cmd_stmt = drop_stmt / create_stmt - / declare_stmt / truncate_stmt / rename_stmt / call_stmt @@ -231,52 +441,37 @@ cmd_stmt / alter_stmt / set_stmt / lock_stmt + / unlock_stmt / show_stmt - / deallocate_stmt - / grant_revoke_stmt - / if_else_stmt - / raise_stmt - / execute_stmt - / for_loop_stmt - / transaction_stmt - / comment_on_stmt + / desc_stmt + / grant_stmt + / explain_stmt create_stmt = create_table_stmt - / create_constraint_trigger - / create_extension_stmt + / create_trigger_stmt / create_index_stmt - / create_sequence / create_db_stmt - / create_domain_stmt - / create_type_stmt / create_view_stmt - / create_aggregate_stmt + / create_user_stmt alter_stmt = alter_table_stmt - / alter_schema_stmt - / alter_domain_type_stmt - / alter_function_stmt - / alter_aggregate_stmt crud_stmt - = union_stmt + = set_op_stmt / update_stmt / replace_insert_stmt / insert_no_columns_stmt + / insert_into_set / delete_stmt / cmd_stmt / proc_stmts multiple_stmt = head:crud_stmt tail:(__ SEMICOLON __ crud_stmt)* { - /* - // is in reality: { tableList: any[]; columnList: any[]; ast: T; } - export type AstStatement = T; - => AstStatement */ const headAst = head && head.ast || head - const cur = tail && tail.length && tail[0].length >= 4 ? [headAst] : headAst + const cur = tail && tail.length && tail[0].length >= 4 ? [headAst] : headAst; for (let i = 0; i < tail.length; i++) { if(!tail[i][3] || tail[i][3].length === 0) continue; cur.push(tail[i][3] && tail[i][3].ast || tail[i][3]); @@ -284,32 +479,19 @@ multiple_stmt return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), - ast: cur + ast: cur } } set_op - = KW_UNION __ a:(KW_ALL / KW_DISTINCT)? { - // => 'union' | 'union all' | 'union distinct' - return a ? `union ${a.toLowerCase()}` : 'union' - } - / KW_INTERSECT { - // => 'intersect' - return 'intersect' - } - / KW_EXCEPT { - // => 'except' - return 'except' + = KW_UNION __ s:(KW_ALL / KW_DISTINCT)? { + return s ? `union ${s.toLowerCase()}` : 'union' } + / KW_MINUS { return 'minus' } + / KW_INTERSECT { return 'intersect' } -union_stmt - = head:select_stmt tail:(__ set_op __ select_stmt)* __ ob:order_by_clause? __ l:limit_clause? { - /* export interface union_stmt_node extends select_stmt_node { - _next: union_stmt_node; - set_op: 'union' | 'union all' | 'union distinct'; - } - => AstStatement - */ +set_op_stmt + = head:select_stmt tail:(__ set_op __ select_stmt)* __ ob: order_by_clause? __ l:limit_clause? { let cur = head for (let i = 0; i < tail.length; i++) { cur._next = tail[i][3] @@ -317,7 +499,7 @@ union_stmt cur = cur._next } if(ob) head._orderby = ob - if(l && l.value && l.value.length > 0) head._limit = l + if(l) head._limit = l return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), @@ -325,75 +507,42 @@ union_stmt } } -if_not_exists_stmt - = 'IF'i __ KW_NOT __ KW_EXISTS { - // => 'IF NOT EXISTS' - return 'IF NOT EXISTS' +column_order_list + = head:column_order_item tail:(__ COMMA __ column_order_item)* { + return createList(head, tail) } -if_exists - = 'if'i __ 'exists'i { - // => 'IF EXISTS' - return 'IF EXISTS' +column_order_item + = c:expr o:(KW_ASC / KW_DESC)? { return { + ...c, + order_by: o && o.toLowerCase(), + } } + / column_order -create_extension_stmt - = a:KW_CREATE __ - e:'EXTENSION'i __ - ife: if_not_exists_stmt? __ - n:(ident_name / literal_string) __ - w:KW_WITH? __ - s:('SCHEMA'i __ ident_name / literal_string)? __ - v:('VERSION'i __ (ident_name / literal_string))? __ - f:(KW_FROM __ (ident_name / literal_string))? { - /* - export type nameOrLiteral = literal_string | { type: 'same', value: string; }; - => { - type: 'create'; - keyword: 'extension'; - if_not_exists?: 'if not exists'; - extension: nameOrLiteral; - with: 'with'; - schema: nameOrLiteral; - version: nameOrLiteral; - from: nameOrLiteral; - } - */ - return { - type: 'create', - keyword: e.toLowerCase(), - if_not_exists:ife, - extension: commonStrToLiteral(n), - with: w && w[0].toLowerCase(), - schema: commonStrToLiteral(s && s[2].toLowerCase()), // <== wont that be a bug ? - version: commonStrToLiteral(v && v[2]), - from: commonStrToLiteral(f && f[2]), - } +column_order + = c:column_ref __ o:(KW_ASC / KW_DESC)? { + return { + ...c, + order_by: o && o.toLowerCase(), } - + } create_db_definition = head:create_option_character_set tail:(__ create_option_character_set)* { - // => create_option_character_set[] return createList(head, tail, 1) } +if_not_exists_stmt + = 'IF'i __ KW_NOT __ KW_EXISTS { + return 'IF NOT EXISTS' + } + create_db_stmt = a:KW_CREATE __ k:(KW_DATABASE / KW_SCHEMA) __ ife:if_not_exists_stmt? __ t:proc_func_name __ c:create_db_definition? { - /* - export type create_db_stmt_t = { - type: 'create', - keyword: 'database' | 'schema', - if_not_exists?: 'if not exists', - database?: { db: ident_without_kw_type, schema: [ident_without_kw_type] }; - schema?: { db: ident_without_kw_type, schema: [ident_without_kw_type] }; - create_definitions?: create_db_definition - } - => AstStatement - */ const keyword = k.toLowerCase() return { tableList: Array.from(tableList), @@ -407,414 +556,250 @@ create_db_stmt } } } -view_with - = KW_WITH __ c:("CASCADED"i / "LOCAL"i) __ "CHECK"i __ "OPTION" { - // => string - return `with ${c.toLowerCase()} check option` - } - / KW_WITH __ "CHECK"i __ "OPTION" { - // => string - return 'with check option' - } -with_view_option - = 'check_option'i __ KW_ASSIGIN_EQUAL __ t:("CASCADED"i / "LOCAL"i) { - // => {type: string; value: string; symbol: string; } - return { type: 'check_option', value: t, symbol: '=' } - } - / k:('security_barrier'i / 'security_invoker'i) __ KW_ASSIGIN_EQUAL __ t:literal_bool { - // => {type: string; value: string; symbol: string; } - return { type: k.toLowerCase(), value: t.value ? 'true' : 'false', symbol: '=' } - } -with_view_options - = head:with_view_option tail:(__ COMMA __ with_view_option)* { - // => with_view_option[] - return createList(head, tail); +auth_option + = 'IDENTIFIED' __ ap:('WITH'i __ ident)? __ 'BY'i __ 'RANDOM'i __ 'PASSWORD'i { + const value = { + prefix: 'by', + type: 'origin', + value: 'random password', } -create_view_stmt - = a:KW_CREATE __ or:(KW_OR __ KW_REPLACE)? __ tp:(KW_TEMP / KW_TEMPORARY)? __ r:KW_RECURSIVE? __ - KW_VIEW __ v:table_name __ c:(LPAREN __ column_list __ RPAREN)? __ wo:(KW_WITH __ LPAREN __ with_view_options __ RPAREN)? __ - KW_AS __ s:select_stmt __ w:view_with? { - /* - export type create_view_stmt_t = { - type: 'create', - keyword: 'view', - replace?: 'or replace', - temporary?: 'temporary' | 'temp', - recursive?: 'recursive', - view: table_name, - columns?: column_list, - select: select_stmt, - with_options?: with_view_options, - with?: string, - } - => AstStatement - */ - v.view = v.table - delete v.table return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: a[0].toLowerCase(), - keyword: 'view', - replace: or && 'or replace', - temporary: tp && tp[0].toLowerCase(), - recursive: r && r.toLowerCase(), - columns: c && c[2], - select: s, - view: v, - with_options: wo && wo[4], - with: w, - } + keyword: ['identified', ap && ap[0].toLowerCase()].filter(v => v).join(' '), + auth_plugin: ap && ap[2], + value } } -create_aggregate_opt_required - = 'SFUNC'i __ KW_ASSIGIN_EQUAL __ n:table_name __ COMMA __ 'STYPE'i __ KW_ASSIGIN_EQUAL __ d:data_type { - // => { type: string; symbol: '='; value: expr; }[] - return [ - { - type: 'sfunc', - symbol: '=', - value: { schema: n.db, name: n.table }, - }, - { - type: 'stype', - symbol: '=', - value: d, - } - ] - } - -create_aggregate_opt_optional - = n:ident __ KW_ASSIGIN_EQUAL __ e:(ident / expr) { - // => { type: string; symbol: '='; value: ident | expr; } + / 'IDENTIFIED' __ ap:('WITH'i __ ident)? __ 'BY'i __ v:literal_string { + v.prefix = 'by' return { - type: n, - symbol: '=', - value: typeof e === 'string' ? { type: 'default', value: e } : e + keyword: ['identified', ap && ap[0].toLowerCase()].filter(v => v).join(' '), + auth_plugin: ap && ap[2], + value: v } } - -create_aggregate_opts - = head:create_aggregate_opt_required tail:(__ COMMA __ create_aggregate_opt_optional)* { - // => create_aggregate_opt_optional[] - return createList(head, tail) - } - -create_aggregate_stmt - = a:KW_CREATE __ or:(KW_OR __ KW_REPLACE)? __ t:'AGGREGATE'i __ s:table_name __ LPAREN __ as:aggregate_signature __ RPAREN __ LPAREN __ opts:create_aggregate_opts __ RPAREN { - /* - export type create_aggregate_stmt_t = { - type: 'create', - keyword: 'aggregate', - replace?: 'or replace', - name: table_name, - args?: aggregate_signature, - options: create_aggregate_opt_optional[] - } - => AstStatement - */ + / 'IDENTIFIED' __ 'WITH'i __ ap:ident __ 'AS'i __ v:literal_string { + v.prefix = 'as' return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'create', - keyword: 'aggregate', - name: { schema: s.db, name: s.table }, - args: { - parentheses: true, - expr: as, - orderby: as.orderby - }, - options: opts - } - }; + keyword: 'identified with', + auth_plugin: ap && ap[2], + value: v + } } -column_data_type - = c:column_ref __ d:data_type { - // => { column: column_ref; definition: data_type; } +user_auth_option + = u:user_or_role __ ap:(auth_option)? { return { - column: c, - definition: d, + user: u, + auth_option: ap } } -column_data_type_list - = head:column_data_type tail:(__ COMMA __ column_data_type)* { - // => column_data_type[] +user_auth_option_list + = head:user_auth_option tail:(__ COMMA __ user_auth_option)* { return createList(head, tail) } -func_returns - = 'RETURNS'i __ k:'SETOF'i? __ t:(data_type / table_name) { - // => { type: "returns"; keyword?: "setof"; expr: data_type; } +default_role + = KW_DEFAULT __ 'role'i __ r:user_or_role_list { return { - type: 'returns', - keyword: k, - expr: t + keyword: 'default role', + value: r } } - / 'RETURNS'i __ KW_TABLE __ LPAREN __ e:column_data_type_list __ RPAREN { - // => { type: "returns"; keyword?: "table"; expr: column_data_type_list; } - return { - type: 'returns', - keyword: 'table', - expr: e +tls_option + = v:('NONE'i / 'SSL'i / 'X509'i) { + return{ + type: 'origin', + value: v } } - -declare_variable_item - = n:ident_name &{ return n.toLowerCase() !== 'begin' } __ c:'CONSTANT'i? __ d:data_type __ collate:collate_expr? __ nu:(KW_NOT __ KW_NULL)? __ expr:((KW_DEFAULT / ':=')? __ (&'BEGIN'i / literal / expr))? __ s:SEMICOLON? { - // => { keyword: 'variable'; name: string, constant?: string; datatype: data_type; collate?: collate_expr; not_null?: string; default?: { type: 'default'; keyword: string; value: literal | expr; }; } - return { - keyword: 'variable', - name: n, - constant: c, - datatype: d, - collate, - not_null: nu && 'not null', - definition: expr && expr[0] && { - type: 'default', - keyword: expr[0], - value: expr[2] - }, - } + / k:('CIPHER'i / 'ISSUER'i / 'SUBJECT'i) __ v:literal_string { + v.prefix = k.toLowerCase() + return v } -declare_variables - = head:declare_variable_item tail:(__ declare_variable_item)* { - // => declare_variable_item[] - return createList(head, tail, 1) -} -declare_stmt - = 'DECLARE'i __ vars:declare_variables { - /* - export type declare_stmt_t = { type: 'declare'; declare: declare_variable_item[]; } - => AstStatement - */ +tls_option_list + = head:tls_option tail:(__ KW_AND __ tls_option)* { + return createBinaryExprChain(head, tail) + } +require_options + = 'REQUIRE'i __ t:tls_option_list { return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'declare', - declare: vars, - symbol: ';', - } + keyword: 'require', + value: t } } -create_func_opt - = 'LANGUAGE' __ ln:ident_name __ { - // => literal_string +resource_option + = k:('MAX_QUERIES_PER_HOUR'i / 'MAX_UPDATES_PER_HOUR'i / 'MAX_CONNECTIONS_PER_HOUR'i / 'MAX_USER_CONNECTIONS'i) __ v:literal_numeric { + v.prefix = k.toLowerCase() + return v + } +with_resource_option + = KW_WITH __ r:resource_option t:(__ resource_option)* { + const resourceOptions = [r] + if (t) { + for (const item of t) { + resourceOptions.push(item[1]) + } + } return { - prefix: 'LANGUAGE', - type: 'default', - value: ln + keyword: 'with', + value: resourceOptions } } - / 'TRANSORM'i __ ft:('FOR' __ 'TYPE' __ ident_name)? __ { - // => literal_string - if (!ft) return { type: 'origin', value: 'TRANSORM' } +password_option + = 'PASSWORD'i __ 'EXPIRE'i __ v:('DEFAULT'i / 'NEVER'i / interval_expr) { return { - prefix: ['TRANSORM', ft[0].toUpperCase(), ft[2].toUpperCase()].join(' '), - type: 'default', - value: ft[4] + keyword: 'password expire', + value: typeof v === 'string' ? { type: 'origin', value: v } : v } } - / i:('WINDOW'i / 'IMMUTABLE'i / 'STABLE'i / 'VOLATILE'i / 'STRICT'i) __ { - // => literal_string + / 'PASSWORD'i __ 'HISTORY'i __ v:('DEFAULT'i / literal_numeric) { return { - type: 'origin', - value: i + keyword: 'password history', + value: typeof v === 'string' ? { type: 'origin', value: v } : v } } - / n:('NOT'i)? __ 'LEAKPROOF'i __ { - // => literal_string + / 'PASSWORD'i __ 'REUSE' __ v:interval_expr { return { - type: 'origin', - value: [n, 'LEAKPROOF'].filter(v => v).join(' ') + keyword: 'password reuse', + value: v } } - / i:('CALLED'i / ('RETURNS'i __ 'NULL'i))? __ 'ON'i __ 'NULL'i __ 'INPUT'i __ { - // => literal_string - if (Array.isArray(i)) i = [i[0], i[2]].join(' ') + / 'PASSWORD'i __ 'REQUIRE'i __ 'CURRENT'i __ v:('DEFAULT'i / 'OPTIONAL'i) { return { - type: 'origin', - value: `${i} ON NULL INPUT` + keyword: 'password require current', + value: { type: 'origin', value: v } } } - / e:('EXTERNAL'i)? __ 'SECURITY'i __ i:('INVOKER'i / 'DEFINER'i) __ { - // => literal_string + / 'FAILED_LOGIN_ATTEMPTS'i __ v:literal_numeric { return { - type: 'origin', - value: [e, 'SECURITY', i].filter(v => v).join(' ') + keyword: 'failed_login_attempts', + value: v } } - / 'PARALLEL'i __ i:('UNSAFE'i / 'RESTRICTED'i / 'SAFE'i) __ { - // => literal_string + / 'PASSWORD_LOCK_TIME'i __ v:(literal_numeric / 'UNBOUNDED'i) { return { - type: 'origin', - value: ['PARALLEL', i].join(' ') + keyword: 'password_lock_time', + value: typeof v === 'string' ? { type: 'origin', value: v } : v } } - / KW_AS __ c:[^ \s\t\n\r]+ __ de:declare_stmt? __ b:('BEGIN'i)? __ s:multiple_stmt __ e:KW_END? &{ return (b && e) || (!b && !e) } __ SEMICOLON? __ l:[^ \s\t\n\r;]+ __ { - // => { type: 'as'; begin?: string; declare?: declare_stmt; expr: multiple_stmt; end?: string; symbol: string; } - const start = c.join('') - const end = l.join('') - if (start !== end) throw new Error(`start symbol '${start}'is not same with end symbol '${end}'`) - return { - type: 'as', - declare: de && de.ast, - begin: b, - expr: Array.isArray(s.ast) ? s.ast.flat() : [s.ast], - end: e && e[0], - symbol: start, + +password_option_list + = head:password_option tail:(__ password_option)* { + return createList(head, tail, 1) + } +user_lock_option + = 'ACCOUNT'i __ v:('LOCK'i / 'UNLOCK'i) { + const value = { + type: 'origin', + value: v.toLowerCase() } + value.prefix = 'account' + return value } - / p:('COST'i / 'ROWS'i) __ n:literal_numeric __ { - // => literal_numeric - n.prefix = p - return n +attribute + = 'ATTRIBUTE'i __ v:literal_string { + v.prefix = 'attribute' + return v } - / 'SUPPORT'i __ n:proc_func_name __ { - // => literal_string +create_user_stmt + = a:KW_CREATE __ k:KW_USER __ ife:if_not_exists_stmt? __ t:user_auth_option_list __ + d:default_role? __ r:require_options? __ wr:with_resource_option? __ p:password_option_list? __ + l:user_lock_option? __ c:keyword_comment? __ attr:attribute? { return { - prefix: 'support', - type: 'default', - value: [n.schema && n.schema.value, n.name.value].filter(v => v).join('.') - } - } - / KW_SET __ ca:ident_name __ e:((('TO'i / '=') __ ident_list) / (KW_FROM __ 'CURRENT'i))? __ { - // => { type: "set"; parameter: ident_name; value?: { prefix: string; expr: expr }} - let value - if (e) { - const val = Array.isArray(e[2]) ? e[2] : [e[2]] - value = { - prefix: e[0], - expr: val.map(v => ({ type: 'default', value: v })) + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword: 'user', + if_not_exists: ife, + user: t, + default_role: d, + require: r, + resource_options: wr, + password_options: p, + lock_option: l, + comment: c, + attribute: attr } } - return { - type: 'set', - parameter: ca, - value, - } } -create_function_stmt +view_with + = KW_WITH __ c:("CASCADED"i / "LOCAL"i) __ "CHECK"i __ "OPTION" { + return `with ${c.toLowerCase()} check option` + } + / KW_WITH __ "CHECK"i __ "OPTION" { + return 'with check option' + } + +create_view_stmt = a:KW_CREATE __ or:(KW_OR __ KW_REPLACE)? __ - t:'FUNCTION'i __ - c:table_name __ LPAREN __ args:alter_func_args? __ RPAREN __ - r:func_returns? __ - fo:create_func_opt* __ SEMICOLON? __ { - /* - export type create_function_stmt_t = { - type: 'create'; - replace?: string; - name: { schema?: string; name: string }; - args?: alter_func_args; - returns?: func_returns; - keyword: 'function'; - options?: create_func_opt[]; - } - => AstStatement - */ + al:("ALGORITHM"i __ KW_ASSIGIN_EQUAL __ ("UNDEFINED"i / "MERGE"i / "TEMPTABLE"i))? __ + df:trigger_definer? __ + ss:("SQL"i __ "SECURITY"i __ ("DEFINER"i / "INVOKER"i))? __ + KW_VIEW __ v:table_name __ c:(LPAREN __ column_list __ RPAREN)? __ + KW_AS __ s:select_stmt_nake __ + w:view_with? { + v.view = v.table + delete v.table return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - args: args || [], - type: 'create', - replace: or && 'or replace', - name: { schema: c.db, name: c.table }, - returns: r, - keyword: t && t.toLowerCase(), - options: fo || [], - } + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword: 'view', + replace: or && 'or replace', + algorithm: al && al[4], + definer: df, + sql_security: ss && ss[4], + columns: c && c[2], + select: s, + view: v, + with: w, } - } - -create_type_stmt_option - = KW_AS __ r:(KW_ENUM / 'RANGE'i) __ LPAREN __ e:expr_list? __ RPAREN { - // => { as: 'as'; resource: string; create_definitions: expr_list | create_column_definition_list; } - e.parentheses = true - return { - as: 'as', - resource: r.toLowerCase(), - create_definitions: e, - } - } - / KW_AS __ LPAREN __ e:create_column_definition_list? __ RPAREN { - // => ignore - return { - as: 'as', - create_definitions: e, } } -create_type_stmt - = a:KW_CREATE __ k:'TYPE'i __ s:table_name __ e:create_type_stmt_option? { - /* - export type create_type_stmt_t = { - type: 'create', - keyword: 'type', - name: { schema: string; name: string }, - as?: string, - resource?: string, - create_definitions?: expr_list | create_column_definition_list; - } - => AstStatement - */ - - customTypes.add([s.db, s.table].filter(v => v).join('.')) - return { +create_index_stmt + = a:KW_CREATE __ + kw:(KW_UNIQUE / KW_FULLTEXT / KW_SPATIAL)? __ + t:KW_INDEX __ + n:ident __ + um:index_type? __ + on:KW_ON __ + ta:table_name __ + LPAREN __ cols:column_order_list __ RPAREN __ + io:index_options? __ + al:ALTER_ALGORITHM? __ + lo:ALTER_LOCK? __ { + return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { type: a[0].toLowerCase(), - keyword: k.toLowerCase(), - name: { schema: s.db, name: s.table }, - ...e, + index_type: kw && kw.toLowerCase(), + keyword: t.toLowerCase(), + index: n, + on_kw: on[0].toLowerCase(), + table: ta, + index_columns: cols, + index_using: um, + index_options: io, + algorithm_option: al, + lock_option: lo, } - } } + } -create_domain_stmt - = a:KW_CREATE __ k:'DOMAIN'i __ s:table_name __ as:KW_AS? __ d:data_type __ ce:collate_expr? __ de:default_expr? __ ccc: create_constraint_check? { - /* - export type create_domain_stmt_t = { - type: 'create', - keyword: 'domain', - domain: { schema: string; name: string }, - as?: string, - target: data_type, - create_definitions?: any[] - } - => AstStatement - */ - if (ccc) ccc.type = 'constraint' - const definitions = [ce, de, ccc].filter(v => v) - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: a[0].toLowerCase(), - keyword: k.toLowerCase(), - domain: { schema: s.db, name: s.table }, - as: as && as[0] && as[0].toLowerCase(), - target: d, - create_definitions: definitions, - } - } - } create_table_stmt = a:KW_CREATE __ tp:KW_TEMPORARY? __ KW_TABLE __ ife:if_not_exists_stmt? __ - t:table_ref_list __ - po:create_table_partition_of { - // => AstStatement - if(t) t.forEach(tt => tableList.add(`create::${tt.db}::${tt.table}`)); + t:table_name __ + lt:create_like_table { + if(t) tableList.add(`create::${t.db}::${t.table}`) return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), @@ -823,40 +808,22 @@ create_table_stmt keyword: 'table', temporary: tp && tp[0].toLowerCase(), if_not_exists: ife, - table: t, - partition_of: po + table: [t], + like: lt } } } - / a:KW_CREATE __ + / a:KW_CREATE __ tp:KW_TEMPORARY? __ KW_TABLE __ ife:if_not_exists_stmt? __ - t:table_ref_list __ + t:table_name __ c:create_table_definition? __ to:table_options? __ - ir: (KW_IGNORE / KW_REPLACE)? __ + ir:(KW_IGNORE / KW_REPLACE)? __ as:KW_AS? __ - qe:union_stmt? { - /* - export type create_table_stmt_node = create_table_stmt_node_simple | create_table_stmt_node_like; - export interface create_table_stmt_node_base { - type: 'create'; - keyword: 'table'; - temporary?: 'temporary'; - if_not_exists?: 'if not exists'; - table: table_ref_list; - } - export interface create_table_stmt_node_simple extends create_table_stmt_node_base{ - ignore_replace?: 'ignore' | 'replace'; - as?: 'as'; - query_expr?: union_stmt_node; - create_definitions?: create_table_definition; - table_options?: table_options; - } - => AstStatement - */ - if(t) t.forEach(tt => tableList.add(`create::${tt.db}::${tt.table}`)); + qe:set_op_stmt? { + if(t) tableList.add(`create::${t.db}::${t.table}`) return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), @@ -864,8 +831,8 @@ create_table_stmt type: a[0].toLowerCase(), keyword: 'table', temporary: tp && tp[0].toLowerCase(), - if_not_exists:ife, - table: t, + if_not_exists: ife, + table: [t], ignore_replace: ir && ir[0].toLowerCase(), as: as && as[0].toLowerCase(), query_expr: qe && qe.ast, @@ -874,433 +841,83 @@ create_table_stmt } } } - / a:KW_CREATE __ - tp:KW_TEMPORARY? __ - KW_TABLE __ - ife:if_not_exists_stmt? __ - t:table_ref_list __ - lt:create_like_table { - /* - export interface create_table_stmt_node_like extends create_table_stmt_node_base{ - like: create_like_table; - } - => AstStatement - */ - if(t) t.forEach(tt => tableList.add(`create::${tt.db}::${tt.table}`)); - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: a[0].toLowerCase(), - keyword: 'table', - temporary: tp && tp[0].toLowerCase(), - if_not_exists:ife, - table: t, - like: lt - } - } + +create_like_table_simple + = KW_LIKE __ t: table_ref_list { + return { + type: 'like', + table: t } + } -create_sequence - = a:KW_CREATE __ - tp:(KW_TEMPORARY / KW_TEMP)? __ - KW_SEQUENCE __ - ife:if_not_exists_stmt? __ - t:table_name __ as:(KW_AS __ alias_ident)?__ - c:create_sequence_definition_list? { - /* - export type create_sequence_stmt = { - type: 'create', - keyword: 'sequence', - temporary?: 'temporary' | 'temp', - if_not_exists?: 'if not exists', - table: table_ref_list, - create_definitions?: create_sequence_definition_list - } - => AstStatement - */ - t.as = as && as[2] - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: a[0].toLowerCase(), - keyword: 'sequence', - temporary: tp && tp[0].toLowerCase(), - if_not_exists:ife, - sequence: [t], - create_definitions: c, - } - } - } - -sequence_definition_increment - = k:'INCREMENT'i __ b:KW_BY? __ n:literal_numeric { - /* - export type sequence_definition = { "resource": "sequence", prefix?: string,value: literal | column_ref } - => sequence_definition - */ - return { - resource: 'sequence', - prefix: b ? `${k.toLowerCase()} by` : k.toLowerCase(), - value: n - } - } -sequence_definition_minval - = k:'MINVALUE'i __ n:literal_numeric { - // => sequence_definition - return { - resource: 'sequence', - prefix: k.toLowerCase(), - value: n - } - } - / 'NO'i __ 'MINVALUE'i { - // => sequence_definition - return { - resource: 'sequence', - value: { - type: 'origin', - value: 'no minvalue' - } - } - } - -sequence_definition_maxval - = k:'MAXVALUE'i __ n:literal_numeric { - // => sequence_definition - return { - resource: 'sequence', - prefix: k.toLowerCase(), - value: n - } - } - / 'NO'i __ 'MAXVALUE'i { - // => sequence_definition - return { - resource: 'sequence', - value: { - type: 'origin', - value: 'no maxvalue' - } - } - } - -sequence_definition_start - = k:'START'i __ w:KW_WITH? __ n:literal_numeric { - // => sequence_definition - return { - resource: 'sequence', - prefix: w ? `${k.toLowerCase()} with` : k.toLowerCase(), - value: n - } - } - -sequence_definition_cache - = k:'CACHE'i __ n:literal_numeric { - // => sequence_definition - return { - resource: 'sequence', - prefix: k.toLowerCase(), - value: n - } - } - -sequence_definition_cycle - = n:'NO'i? __ 'CYCLE'i { - // => sequence_definition - return { - resource: 'sequence', - value: { - type: 'origin', - value: n ? 'no cycle' : 'cycle' - } - } - } - -sequence_definition_owned - = 'OWNED'i __ KW_BY __ 'NONE'i { - // => sequence_definition - return { - resource: 'sequence', - prefix: 'owned by', - value: { - type: 'origin', - value: 'none' - } - } - } - / n:'OWNED'i __ KW_BY __ col:column_ref { - // => sequence_definition - return { - resource: 'sequence', - prefix: 'owned by', - value: col - } - } - -create_sequence_definition - = sequence_definition_increment - / sequence_definition_minval - / sequence_definition_maxval - / sequence_definition_start - / sequence_definition_cache - / sequence_definition_cycle - / sequence_definition_owned - -create_sequence_definition_list - = head: create_sequence_definition tail:(__ create_sequence_definition)* { - // => create_sequence_definition[] - return createList(head, tail, 1) -} - -create_index_stmt - = a:KW_CREATE __ - kw:KW_UNIQUE? __ - t:KW_INDEX __ - co:KW_CONCURRENTLY? __ - n:ident? __ - on:KW_ON __ - ta:table_name __ - um:index_type? __ - LPAREN __ cols:column_order_list __ RPAREN __ - wr:(KW_WITH __ LPAREN __ index_options_list __ RPAREN)? __ - ts:(KW_TABLESPACE __ ident_name)? __ - w:where_clause? __ { - /* - export interface create_index_stmt_node { - type: 'create'; - index_type?: 'unique'; - keyword: 'index'; - concurrently?: 'concurrently'; - index: string; - on_kw: string; - table: table_name; - index_using?: index_type; - index_columns: column_order[]; - with?: index_option[]; - with_before_where: true; - tablespace?: {type: 'origin'; value: string; } - where?: where_clause; - } - => AstStatement - */ - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: a[0].toLowerCase(), - index_type: kw && kw.toLowerCase(), - keyword: t.toLowerCase(), - concurrently: co && co.toLowerCase(), - index: n, - on_kw: on[0].toLowerCase(), - table: ta, - index_using: um, - index_columns: cols, - with: wr && wr[4], - with_before_where: true, - tablespace: ts && { type: 'origin', value: ts[2] }, - where: w, - } - } - } - -column_order_list - = head:column_order tail:(__ COMMA __ column_order)* { - // => column_order[] - return createList(head, tail) - } - -column_order - = c:expr __ - ca:collate_expr? __ - op:ident? __ - o:(KW_ASC / KW_DESC)? __ - nf:('NULLS'i __ ('FIRST'i / 'LAST'i))? { - /* - => { - collate: collate_expr; - opclass: ident; - order: 'asc' | 'desc'; - nulls: 'nulls last' | 'nulls first'; - } - */ - return { - ...c, - collate: ca, - opclass: op, - order_by: o && o.toLowerCase(), - nulls: nf && `${nf[0].toLowerCase()} ${nf[2].toLowerCase()}`, - } - } - -create_like_table_simple - = KW_LIKE __ t: table_ref_list { - // => { type: 'like'; table: table_ref_list; } - return { - type: 'like', - table: t - } - } create_like_table = create_like_table_simple / LPAREN __ e:create_like_table __ RPAREN { - // => create_like_table_simple & { parentheses?: boolean; } e.parentheses = true; return e; } -for_values_item - = KW_FROM __ LPAREN __ f:literal_string __ RPAREN __ KW_TO __ LPAREN __ t:literal_string __ RPAREN { - /* => { - type: 'for_values_item'; - keyword: 'from'; - from: literal_string; - to: literal_string; - } */ - return { - type: 'for_values_item', - keyword: 'from', - from: f, - to: t, - } - } - / KW_IN __ LPAREN __ e:expr_list __ RPAREN { - /* => { - type: 'for_values_item'; - keyword: 'in'; - in: expr_list; - } */ - return { - type: 'for_values_item', - keyword: 'in', - in: e, - } - } - / KW_WITH __ LPAREN __ 'MODULUS'i __ m:literal_numeric __ COMMA __ 'REMAINDER'i __ r:literal_numeric __ RPAREN { - /* => { - type: 'for_values_item'; - keyword: 'with'; - modulus: literal_numeric; - remainder: literal_numeric; - } */ - return { - type: 'for_values_item', - keyword: 'with', - modulus: m, - remainder: r, - } - } - -for_values - = 'FOR'i __ KW_VALUES __ fvi:for_values_item { - /* => { - type: 'for_values'; - keyword: 'for values'; - expr: for_values_item; - } */ - return { - type: 'for_values', - keyword: 'for values', - expr: fvi - } - } -create_table_partition_of - = KW_PARTITION __ 'OF'i __ t:table_name __ fv:for_values __ ts:(KW_TABLESPACE __ ident_without_kw_type)? { - /* => { - type: 'partition_of'; - keyword: 'partition of'; - table: table_name; - for_values: for_values; - tablespace: ident_without_kw_type | undefined; - } */ - return { - type: 'partition_of', - keyword: 'partition of', - table: t, - for_values: fv, - tablespace: ts && ts[2] - } - } - create_table_definition = LPAREN __ head:create_definition tail:(__ COMMA __ create_definition)* __ RPAREN { - // => create_definition[] return createList(head, tail); } create_definition - = create_column_definition + = create_constraint_definition + / create_column_definition / create_index_definition / create_fulltext_spatial_index_definition - / create_constraint_definition column_definition_opt - = column_constraint + = n:(literal_not_null / literal_null) { + if (n && !n.value) n.value = 'null' + return { nullable: n } + } + / d:default_expr { + return { default_val: d } + } / a:('AUTO_INCREMENT'i) { - // => { auto_increment: 'auto_increment'; } return { auto_increment: a.toLowerCase() } } / 'UNIQUE'i __ k:('KEY'i)? { - // => { unique: 'unique' | 'unique key'; } const sql = ['unique'] if (k) sql.push(k) return { unique: sql.join(' ').toLowerCase('') } } / p:('PRIMARY'i)? __ 'KEY'i { - // => { unique: 'key' | 'primary key'; } const sql = [] if (p) sql.push('primary') sql.push('key') return { primary_key: sql.join(' ').toLowerCase('') } } / co:keyword_comment { - // => { comment: keyword_comment; } return { comment: co } } / ca:collate_expr { - // => { collate: collate_expr; } return { collate: ca } } / cf:column_format { - // => { column_format: column_format; } return { column_format: cf } } / s:storage { - // => { storage: storage } return { storage: s } } / re:reference_definition { - // => { reference_definition: reference_definition; } return { reference_definition: re } } / ck:check_constraint_definition { - // => { check: check_constraint_definition; } return { check: ck } } / t:create_option_character_set_kw __ s:KW_ASSIGIN_EQUAL? __ v:ident_without_kw_type { - // => { character_set: { type: 'CHARACTER SET'; symbol: '=' | null; value: ident_without_kw_type; } } return { character_set: { type: t, value: v, symbol: s }} } + / g:generated { + return { generated: g } + } column_definition_opt_list = head:column_definition_opt __ tail:(__ column_definition_opt)* { - /* - => { - nullable?: column_constraint['nullable']; - default_val?: column_constraint['default_val']; - auto_increment?: 'auto_increment'; - unique?: 'unique' | 'unique key'; - primary?: 'key' | 'primary key'; - comment?: keyword_comment; - collate?: collate_expr; - column_format?: column_format; - storage?: storage; - reference_definition?: reference_definition; - } - */ let opt = head for (let i = 0; i < tail.length; i++) { opt = { ...opt, ...tail[i][1] } @@ -1308,35 +925,11 @@ column_definition_opt_list return opt } -create_column_definition_list - = head:create_column_definition tail:(__ COMMA __ create_column_definition)* { - // => create_column_definition[] - return createList(head, tail) - } - create_column_definition = c:column_ref __ - d:(data_type / double_quoted_ident) __ + d:data_type __ cdo:column_definition_opt_list? { - /* - => { - column: column_ref; - definition: data_type; - nullable: column_constraint['nullable']; - default_val: column_constraint['default_val']; - auto_increment?: 'auto_increment'; - unique?: 'unique' | 'unique key'; - primary?: 'key' | 'primary key'; - comment?: keyword_comment; - collate?: collate_expr; - column_format?: column_format; - storage?: storage; - reference_definition?: reference_definition; - resource: 'column'; - } - */ - columnList.add(`create::${c.table}::${c.column.expr.value}`) - if (d.type === 'double_quote_string') d = { dataType: `"${d.value}"` } + columnList.add(`create::${c.table}::${c.column}`) return { column: c, definition: d, @@ -1345,31 +938,80 @@ create_column_definition } } -column_constraint - = n:constraint_name { - // => { constraint: constraint_name; } - return { constraint: n } +trigger_definer + = 'DEFINER'i __ KW_ASSIGIN_EQUAL __ u:literal_string __ '@' __ h:literal_string { + const userNameSymbol = u.type === 'single_quote_string' ? '\'' : '"' + const hostSymbol = h.type === 'single_quote_string' ? '\'' : '"' + return `DEFINER = ${userNameSymbol}${u.value}${userNameSymbol}@${hostSymbol}${h.value}${hostSymbol}` } - / n:(literal_not_null / literal_null) __ df:default_expr? { - // => { nullable: literal_null | literal_not_null; default_val: default_expr; } - if (n && !n.value) n.value = 'null' + / 'DEFINER'i __ KW_ASSIGIN_EQUAL __ KW_CURRENT_USER __ LPAREN __ RPAREN { + return `DEFINER = CURRENT_USER()` + } + / 'DEFINER'i __ KW_ASSIGIN_EQUAL __ KW_CURRENT_USER { + return `DEFINER = CURRENT_USER` + } +trigger_time + = 'BEFORE'i / 'AFTER'i +trigger_event + = kw:(KW_INSERT / KW_UPDATE / KW_DELETE) { return { - default_val: df, - nullable: n + keyword: kw[0].toLowerCase(), } } - / df:default_expr __ n:(literal_not_null / literal_null)? { - // => { nullable: literal_null | literal_not_null; default_val: default_expr; } - if (n && !n.value) n.value = 'null' +trigger_for_row + = kw:'FOR'i __ e:('EACH'i)? __ ob:('ROW'i / 'STATEMENT'i) { + return { + keyword: e ? `${kw.toLowerCase()} ${e.toLowerCase()}` : kw.toLowerCase(), + args: ob.toLowerCase() + } + } +trigger_order + = f:('FOLLOWS'i / 'PRECEDES'i) __ t:ident { + return { + keyword: f, + trigger: t + } + } +trigger_body + = KW_SET __ s:set_list { return { - default_val: df, - nullable: n + type: 'set', + expr: s, } } +create_trigger_stmt + = a:KW_CREATE __ + df:trigger_definer? __ + KW_TRIGGER __ + ife:if_not_exists_stmt? __ + t:table_name __ + tt:trigger_time __ + te:trigger_event __ + KW_ON __ tb:table_name __ fe:trigger_for_row __ + tr:trigger_order? __ + tbo:trigger_body { + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + definer: df, + keyword: 'trigger', + for_each: fe, + if_not_exists: ife, + trigger: t, + time: tt, + events: [te], + order: tr, + table: tb, + execute: tbo, + } + } + } + collate_expr - = KW_COLLATE __ ca:ident_type __ s:KW_ASSIGIN_EQUAL __ t:ident_type { - // => { type: 'collate'; keyword: 'collate'; collate: { symbol: '=' ; name: ident_type; value: ident_type; }} + = KW_COLLATE __ ca:ident_name __ s:KW_ASSIGIN_EQUAL __ t:ident { return { type: 'collate', keyword: 'collate', @@ -1380,8 +1022,7 @@ collate_expr } } } - / KW_COLLATE __ s:KW_ASSIGIN_EQUAL? __ ca:ident_type { - // => { type: 'collate'; keyword: 'collate'; collate: { symbol: '=' | null ; name: ident_type; }} + / KW_COLLATE __ s:KW_ASSIGIN_EQUAL? __ ca:ident { return { type: 'collate', keyword: 'collate', @@ -1391,9 +1032,9 @@ collate_expr } } } + column_format = k:'COLUMN_FORMAT'i __ f:('FIXED'i / 'DYNAMIC'i / 'DEFAULT'i) { - // => { type: 'column_format'; value: 'fixed' | 'dynamic' | 'default'; } return { type: 'column_format', value: f.toLowerCase() @@ -1401,48 +1042,48 @@ column_format } storage = k:'STORAGE'i __ s:('DISK'i / 'MEMORY'i) { - // => { type: 'storage'; value: 'disk' | 'memory' } return { type: 'storage', value: s.toLowerCase() } } -default_arg_expr - = kw:(KW_DEFAULT / KW_ASSIGIN_EQUAL)? __ ce:expr { - // => { type: 'default'; keyword: string, value: expr; } - return { - type: 'default', - keyword: kw && kw[0], - value: ce - } - } default_expr = KW_DEFAULT __ ce:expr { - // => { type: 'default'; value: expr; } return { type: 'default', value: ce } } + +generated_always + = ga:('GENERATED'i __ 'ALWAYS'i) { + return ga.join('').toLowerCase() + } + +generated + = gn:(generated_always? __ 'AS'i) __ LPAREN __ expr:(literal / expr) __ RPAREN __ st:('STORED'i / 'VIRTUAL'i)* { + return { + type: 'generated', + expr: expr, + value: gn.filter(s => typeof s === 'string').join(' ').toLowerCase(), + storage_type: st && st[0] && st[0].toLowerCase() + } + } + drop_index_opt = head:(ALTER_ALGORITHM / ALTER_LOCK) tail:(__ (ALTER_ALGORITHM / ALTER_LOCK))* { - // => (ALTER_ALGORITHM | ALTER_LOCK)[] return createList(head, tail, 1) } +if_exists + = 'if'i __ 'exists'i { + return 'if exists' + } + drop_stmt = a:KW_DROP __ r:KW_TABLE __ - ife:if_exists? __ + ife: if_exists? __ t:table_ref_list { - /* - export interface drop_stmt_node { - type: 'drop'; - keyword: 'table'; - prefix?: string; - name: table_ref_list; - } - => AstStatement - */ if(t) t.forEach(tt => tableList.add(`${a}::${tt.db}::${tt.table}`)); return { tableList: Array.from(tableList), @@ -1456,86 +1097,93 @@ drop_stmt }; } / a:KW_DROP __ - r:KW_INDEX __ - cu:KW_CONCURRENTLY? __ + r:KW_VIEW __ ife:if_exists? __ + t:table_ref_list __ + op:view_options? { + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a.toLowerCase(), + keyword: r.toLowerCase(), + prefix: ife, + name: t, + options: op && [{ type: 'origin', value: op }], + } + }; + } + / a:KW_DROP __ + r:KW_INDEX __ i:column_ref __ - op:('CASCADE'i / 'RESTRICT'i)? { - /* - export interface drop_index_stmt_node { - type: 'drop'; - prefix?: string; - keyword: string; - name: column_ref; - options?: 'cascade' | 'restrict'; - } - => AstStatement - */ + KW_ON __ + t:table_name __ + op:drop_index_opt? __ { return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { type: a.toLowerCase(), keyword: r.toLowerCase(), - prefix: [cu, ife].filter(v => v).join(' '), name: i, - options: op && [{ type: 'origin', value: op }] + table: t, + options: op } }; } - -truncate_table_name - = t:table_name __ s:STAR? { - // => table_name & { suffix?: string } - tableList.add(`truncate::${t.db}::${t.table}`) - if (s) t.suffix = s - return t - } -truncate_table_name_list - = head:truncate_table_name tail:(__ COMMA __ truncate_table_name)* { - // => truncate_table_name[] - return createList(head, tail) + / a:KW_DROP __ + r:(KW_DATABASE / KW_SCHEMA) __ + ife:if_exists? __ + t:ident_name { + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a.toLowerCase(), + keyword: r.toLowerCase(), + prefix: ife, + name: t + } + }; + } + / a:KW_DROP __ + r:KW_TRIGGER __ + ife:if_exists? __ + t:table_base { + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a.toLowerCase(), + keyword: r.toLowerCase(), + prefix: ife, + name: [{ + schema: t.db, + trigger: t.table + }] + } + }; } + truncate_stmt = a:KW_TRUNCATE __ kw:KW_TABLE? __ - on: 'ONLY'i? __ - t:truncate_table_name_list __ - id: (('RESTART'i / 'CONTINUE'i) __ 'IDENTITY'i)? __ - op:('CASCADE'i / 'RESTRICT'i)? { - /* - export interface truncate_stmt_node { - type: 'trucate'; - keyword: 'table'; - prefix?: string; - name: table_ref_list; - suffix: string[]; - } - => AstStatement - */ + t:table_ref_list { + if(t) t.forEach(tt => tableList.add(`${a}::${tt.db}::${tt.table}`)); return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { type: a.toLowerCase(), keyword: kw && kw.toLowerCase() || 'table', - prefix: on, - name: t, - suffix: [id && [id[0], id[2]].join(' '), op].filter(v => v).map(v => ({ type: 'origin', value: v })) + name: t } - } + }; } use_stmt = KW_USE __ d:ident { - /* - export interface use_stmt_node { - type: 'use'; - db: ident; - } - => AstStatement - */ tableList.add(`use::${d}::null`); return { tableList: Array.from(tableList), @@ -1547,215 +1195,93 @@ use_stmt }; } -aggregate_signature - = STAR { - // => { name: "*" } - return [ - { - name: '*' - } - ] - } - / s:alter_func_args? __ KW_ORDER __ KW_BY __ o:alter_func_args { - // => alter_func_args - const ans = s || [] - ans.orderby = o - return ans - } - / alter_func_args - -alter_func_argmode - = t:(KW_IN / 'OUT'i / 'VARIADIC'i) { - // => "IN" | "OUT" | "VARIADIC" - return t.toUpperCase() - } - -alter_func_arg_item - = m:alter_func_argmode? __ ad:data_type __ de:default_arg_expr? { - // => { mode?: string; name?: string; type: data_type; default: default_arg_expr; } - return { - mode: m, - type: ad, - default: de, - } - } - / m:alter_func_argmode? __ an:ident_name __ ad:data_type __ de:default_arg_expr? { - // => { mode?: string; name?: string; type: data_type; default: default_arg_expr; } - return { - mode: m, - name: an, - type: ad, - default: de, - } - } -alter_func_args - = head:alter_func_arg_item tail:(__ COMMA __ alter_func_arg_item)* { - // => alter_func_arg_item[] - return createList(head, tail) - } -alter_aggregate_stmt - = KW_ALTER __ t:'AGGREGATE'i __ s:table_name __ LPAREN __ as:aggregate_signature __ RPAREN __ ac:(ALTER_RENAME / ALTER_OWNER_TO / ALTER_SET_SCHEMA) { - // => AstStatement - const keyword = t.toLowerCase() - ac.resource = keyword - ac[keyword] = ac.table - delete ac.table - return { +alter_table_stmt + = KW_ALTER __ + KW_TABLE __ + t:table_name __ + e:alter_action_list { + tableList.add(`alter::${t.db}::${t.table}`) + return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { type: 'alter', - keyword, - name: { schema: s.db, name: s.table }, - args: { - parentheses: true, - expr: as, - orderby: as.orderby - }, - expr: ac - } - }; - } -alter_function_stmt - = KW_ALTER __ t:'FUNCTION'i __ s:table_name __ ags:(LPAREN __ alter_func_args? __ RPAREN)? __ ac:(ALTER_RENAME / ALTER_OWNER_TO / ALTER_SET_SCHEMA) { - // => AstStatement - const keyword = t.toLowerCase() - ac.resource = keyword - ac[keyword] = ac.table - delete ac.table - const args = {} - if (ags && ags[0]) args.parentheses = true - args.expr = ags && ags[2] - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'alter', - keyword, - name: { schema: s.db, name: s.table }, - args, - expr: ac - } - }; - } -alter_domain_type_stmt - = KW_ALTER __ t:('DOMAIN'i / 'TYPE'i) __ s:table_name __ ac:(ALTER_RENAME / ALTER_OWNER_TO / ALTER_SET_SCHEMA) { - /* - export interface alter_resource_stmt_node { - type: 'alter'; - keyword: 'domain' | 'type', - name: string | { schema: string, name: string }; - args?: { parentheses: true; expr?: alter_func_args; orderby?: alter_func_args; }; - expr: alter_rename_owner; - } - => AstStatement - */ - const keyword = t.toLowerCase() - ac.resource = keyword - ac[keyword] = ac.table - delete ac.table - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'alter', - keyword, - name: { schema: s.db, name: s.table }, - expr: ac - } - }; - } - -alter_schema_stmt - = KW_ALTER __ t:KW_SCHEMA __ s:ident_name __ ac:(ALTER_RENAME / ALTER_OWNER_TO / ALTER_SET_SCHEMA) { - // => AstStatement - const keyword = t.toLowerCase() - ac.resource = keyword - ac[keyword] = ac.table - delete ac.table - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'alter', - keyword, - schema: s, - expr: ac - } - }; - } - -alter_table_stmt - = KW_ALTER __ - KW_TABLE __ - ife:if_exists? __ - o:'only'i? __ - t:table_ref_list __ - e:alter_action_list { - /* - export interface alter_table_stmt_node { - type: 'alter'; - table: table_ref_list; - keyword: 'table'; - if_exists: if_exists; - prefix?: literal_string; - expr: alter_action_list; - } - => AstStatement - */ - if (t && t.length > 0) t.forEach(table => tableList.add(`alter::${table.db}::${table.table}`)); - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'alter', - keyword: 'table', - if_exists: ife, - prefix: o && { type: 'origin', value: o }, - table: t, + table: [t], expr: e } }; } - +alter_column_suffix + = k:('after'i / 'first'i) __ i:column_ref { + return { + keyword: k, + expr: i + } + } alter_action_list = head:alter_action tail:(__ COMMA __ alter_action)* { - // => alter_action[] return createList(head, tail); } alter_action - = ALTER_ADD_COLUMN - / ALTER_ADD_CONSTRAINT + = ALTER_ADD_CONSTRAINT + / ALTER_DROP_CONSTRAINT + / ALTER_DROP_KEY_INDEX + / ALTER_ENABLE_CONSTRAINT + / ALTER_DISABLE_CONSTRAINT + / ALTER_ADD_COLUMN / ALTER_DROP_COLUMN + / ALTER_MODIFY_COLUMN / ALTER_ADD_INDEX_OR_KEY / ALTER_ADD_FULLETXT_SPARITAL_INDEX - / ALTER_RENAME + / ALTER_RENAME_COLUMN + / ALTER_RENAME_TABLE / ALTER_ALGORITHM / ALTER_LOCK - / ALTER_COLUMN_DATA_TYPE - / ALTER_COLUMN_DEFAULT - / ALTER_COLUMN_NOT_NULL + / ALTER_CHANGE_COLUMN + / t:table_option { + t.resource = t.keyword + t[t.keyword] = t.value + delete t.value + return { + type: 'alter', + ...t, + } + } ALTER_ADD_COLUMN = KW_ADD __ - kc:KW_COLUMN? __ - ife:if_not_exists_stmt? __ - cd:create_column_definition { - /* - => { - action: 'add'; - keyword: KW_COLUMN; - resource: 'column'; - if_not_exists: ife; - type: 'alter'; - } & create_column_definition; - */ + kc:KW_COLUMN __ + cd:create_column_definition __ af:alter_column_suffix? { return { action: 'add', - if_not_exists: ife, ...cd, keyword: kc, + suffix: af, + resource: 'column', + type: 'alter', + } + } + / KW_ADD __ + cd:create_column_definition __ af:alter_column_suffix? { + return { + action: 'add', + ...cd, + suffix: af, + resource: 'column', + type: 'alter', + } + } + +ALTER_MODIFY_COLUMN + = KW_MODIFY __ + kc:KW_COLUMN? __ + cd:create_column_definition __ af:alter_column_suffix? { + return { + action: 'modify', + keyword: kc, + ...cd, + suffix: af, resource: 'column', type: 'alter', } @@ -1763,51 +1289,29 @@ ALTER_ADD_COLUMN ALTER_DROP_COLUMN = KW_DROP __ - kc:KW_COLUMN? __ - ife:if_exists? __ + kc:KW_COLUMN __ c:column_ref { - /* => { - action: 'drop'; - collumn: column_ref; - keyword: KW_COLUMN; - if_exists: if_exists; - resource: 'column'; - type: 'alter'; - } */ return { action: 'drop', column: c, - if_exists: ife, keyword: kc, resource: 'column', type: 'alter', } } - -ALTER_ADD_CONSTRAINT - = KW_ADD __ c:create_constraint_definition { - /* => { - action: 'add'; - create_definitions: create_db_definition; - resource: 'constraint'; - type: 'alter'; - } */ + / KW_DROP __ + c:column_ref { return { - action: 'add', - create_definitions: c, - resource: 'constraint', + action: 'drop', + column: c, + resource: 'column', type: 'alter', } } ALTER_ADD_INDEX_OR_KEY = KW_ADD __ - id:create_index_definition - { - /* => { - action: 'add'; - type: 'alter'; - } & create_index_definition */ + id:create_index_definition { return { action: 'add', type: 'alter', @@ -1815,18 +1319,10 @@ ALTER_ADD_INDEX_OR_KEY } } -ALTER_RENAME - = KW_RENAME __ kw:(KW_TO / KW_AS)? __ tn:ident { - /* - export interface alter_rename_owner { - action: string; - type: 'alter'; - resource: string; - keyword?: 'to' | 'as'; - [key: string]: ident | undefined; - } - => AstStatement - */ +ALTER_RENAME_TABLE + = KW_RENAME __ + kw:(KW_TO / KW_AS)? __ + tn:ident { return { action: 'rename', type: 'alter', @@ -1836,39 +1332,23 @@ ALTER_RENAME } } -ALTER_OWNER_TO - = 'OWNER'i __ KW_TO __ tn:(ident / 'CURRENT_ROLE'i / 'CURRENT_USER'i / 'SESSION_USER'i) { - // => AstStatement +ALTER_RENAME_COLUMN + = KW_RENAME __ KW_COLUMN __ c:column_ref __ + kw:(KW_TO / KW_AS)? __ + tn:column_ref { return { - action: 'owner', + action: 'rename', type: 'alter', - resource: 'table', - keyword: 'to', - table: tn - } - } - -ALTER_SET_SCHEMA - = KW_SET __ KW_SCHEMA __ s:ident { - // => AstStatement - return { - action: 'set', - type: 'alter', - resource: 'table', - keyword: 'schema', - table: s + resource: 'column', + keyword: 'column', + old_column: c, + prefix: kw && kw[0].toLowerCase(), + column: tn } } ALTER_ALGORITHM = "ALGORITHM"i __ s:KW_ASSIGIN_EQUAL? __ val:("DEFAULT"i / "INSTANT"i / "INPLACE"i / "COPY"i) { - /* => { - type: 'alter'; - keyword: 'algorithm'; - resource: 'algorithm'; - symbol?: '='; - algorithm: 'DEFAULT' | 'INSTANT' | 'INPLACE' | 'COPY'; - }*/ return { type: 'alter', keyword: 'algorithm', @@ -1880,13 +1360,6 @@ ALTER_ALGORITHM ALTER_LOCK = "LOCK"i __ s:KW_ASSIGIN_EQUAL? __ val:("DEFAULT"i / "NONE"i / "SHARED"i / "EXCLUSIVE"i) { - /* => { - type: 'alter'; - keyword: 'lock'; - resource: 'lock'; - symbol?: '='; - lock: 'DEFAULT' | 'NONE' | 'SHARED' | 'EXCLUSIVE'; - }*/ return { type: 'alter', keyword: 'lock', @@ -1896,104 +1369,89 @@ ALTER_LOCK } } -ALTER_COLUMN_DATA_TYPE - = KW_ALTER __ kc:KW_COLUMN? __ c:column_ref __ sd:(KW_SET __ 'data'i)? __ 'type'i __ t:data_type __ co:collate_expr? __ us:(KW_USING __ expr)? { - /* - => { - action: 'alter'; - keyword?: KW_COLUMN; - using?: expr; - type: 'alter'; - } & create_column_definition; - */ - c.suffix = sd ? 'set data type' : 'type' - return { - action: 'alter', - column: c, +ALTER_CHANGE_COLUMN + = 'CHANGE'i __ kc:KW_COLUMN? __ od:column_ref __ cd:create_column_definition __ af:alter_column_suffix? { + return { + action: 'change', + old_column: od, + ...cd, keyword: kc, resource: 'column', - definition: t, - collate: co, - using: us && us[2], type: 'alter', + suffix: af, } } -ALTER_COLUMN_DEFAULT - = KW_ALTER __ kc:KW_COLUMN? __ c:column_ref __ KW_SET __ KW_DEFAULT __ e:expr { - /* => { - action: 'alter'; - keyword?: KW_COLUMN; - default_val?: { type: 'set default', value: expr }; - type: 'alter'; - } & create_column_definition; - */ +ALTER_ADD_CONSTRAINT + = KW_ADD __ c:create_constraint_definition { return { - action: 'alter', - column: c, - keyword: kc, - resource: 'column', - default_val: { - type: 'set default', - value: e, - }, + action: 'add', + create_definitions: c, + resource: 'constraint', type: 'alter', } + } + +ALTER_DROP_KEY_INDEX + = KW_DROP __ 'PRIMARY'i __ KW_KEY { + return { + action: 'drop', + key: '', + keyword: 'primary key', + resource: 'key', + type: 'alter', + } + } + / KW_DROP __ k:(('FOREIGN'i? __ KW_KEY) / (KW_INDEX)) __ c:ident { + const resource = Array.isArray(k) ? 'key' : 'index' + return { + action: 'drop', + [resource]: c, + keyword: Array.isArray(k) ? `${[k[0], k[2]].filter(v => v).join(' ').toLowerCase()}` : k.toLowerCase(), + resource, + type: 'alter', + } } - / KW_ALTER __ kc:KW_COLUMN? __ c:column_ref __ KW_DROP __ KW_DEFAULT { - /* => { - action: 'alter'; - keyword?: KW_COLUMN; - default_val?: { type: 'set default', value: expr }; - type: 'alter'; - } & create_column_definition; - */ + +ALTER_DROP_CONSTRAINT + = KW_DROP __ kc:'CHECK'i __ c:ident_name { return { - action: 'alter', - column: c, - keyword: kc, - resource: 'column', - default_val: { - type: 'drop default', - }, + action: 'drop', + constraint: c, + keyword: kc.toLowerCase(), + resource: 'constraint', type: 'alter', } - } + } -ALTER_COLUMN_NOT_NULL - = KW_ALTER __ kc:KW_COLUMN? __ c:column_ref __ ac:(KW_SET / KW_DROP) __ n:literal_not_null { - /* => { - action: 'alter'; - keyword?: KW_COLUMN; - nullable: literal_not_null; - type: 'alter'; - } & create_column_definition; - */ - n.action = ac.toLowerCase(); +ALTER_ENABLE_CONSTRAINT + = KW_WITH __ 'CHECK'i __ 'CHECK'i __ KW_CONSTRAINT __ c:ident_name { return { - action: 'alter', - column: c, - keyword: kc, - resource: 'column', - nullable: n, + action: 'with', + constraint: c, + keyword: 'check check', + resource: 'constraint', type: 'alter', } - } + } + +ALTER_DISABLE_CONSTRAINT + = 'NOCHECK'i __ KW_CONSTRAINT __ c:ident_name { + return { + action: 'nocheck', + constraint: c, + resource: 'constraint', + type: 'alter', + } + } + + create_index_definition = kc:(KW_INDEX / KW_KEY) __ c:column? __ t:index_type? __ - de:cte_column_definition __ - id:index_options? __ - { - /* => { - index: column; - definition: cte_column_definition; - keyword: 'index' | 'key'; - index_type?: index_type; - resource: 'index'; - index_options?: index_options; - }*/ + de:cte_idx_column_definition __ + id:index_options? __ { return { index: c, definition: de, @@ -2009,15 +1467,7 @@ create_fulltext_spatial_index_definition kc:(KW_INDEX / KW_KEY)? __ c:column? __ de: cte_column_definition __ - id: index_options? __ - { - /* => { - index: column; - definition: cte_column_definition; - keyword: 'fulltext' | 'spatial' | 'fulltext key' | 'spatial key' | 'fulltext index' | 'spatial index'; - index_options?: index_options; - resource: 'index'; - }*/ + id: index_options? { return { index: c, definition: de, @@ -2035,48 +1485,22 @@ create_constraint_definition constraint_name = kc:KW_CONSTRAINT __ c:ident? { - // => { keyword: 'constraint'; constraint: ident; } return { keyword: kc.toLowerCase(), constraint: c } } -create_constraint_check - = kc:constraint_name? __ p:'CHECK'i __ LPAREN __ e:or_and_where_expr __ RPAREN { - /* => { - constraint?: constraint_name['constraint']; - definition: [or_and_where_expr]; - keyword?: constraint_name['keyword']; - constraint_type: 'check'; - resource: 'constraint'; - }*/ - return { - constraint: kc && kc.constraint, - definition: [e], - constraint_type: p.toLowerCase(), - keyword: kc && kc.keyword, - resource: 'constraint', - } - } + create_constraint_primary = kc:constraint_name? __ - p:('PRIMARY KEY'i) __ + p:('PRIMARY'i __ 'KEY'i) __ t:index_type? __ - de:cte_column_definition __ + de:cte_idx_column_definition __ id:index_options? { - /* => { - constraint?: constraint_name['constraint']; - definition: cte_column_definition; - constraint_type: 'primary key'; - keyword?: constraint_name['keyword']; - index_type?: index_type; - resource: 'constraint'; - index_options?: index_options; - }*/ return { constraint: kc && kc.constraint, definition: de, - constraint_type: p.toLowerCase(), + constraint_type: `${p[0].toLowerCase()} ${p[2].toLowerCase()}`, keyword: kc && kc.keyword, index_type: t, resource: 'constraint', @@ -2090,17 +1514,8 @@ create_constraint_unique p:(KW_INDEX / KW_KEY)? __ i:column? __ t:index_type? __ - de:cte_column_definition __ + de:cte_idx_column_definition __ id:index_options? { - /* => { - constraint?: constraint_name['constraint']; - definition: cte_column_definition; - constraint_type: 'unique key' | 'unique' | 'unique index'; - keyword?: constraint_name['keyword']; - index_type?: index_type; - resource: 'constraint'; - index_options?: index_options; - }*/ return { constraint: kc && kc.constraint, definition: de, @@ -2113,21 +1528,24 @@ create_constraint_unique } } +create_constraint_check + = kc:constraint_name? __ u:'CHECK'i __ nfr:('NOT'i __ 'FOR'i __ 'REPLICATION'i __)? LPAREN __ c:or_and_expr __ RPAREN { + return { + constraint_type: u.toLowerCase(), + keyword: kc && kc.keyword, + constraint: kc && kc.constraint, + index_type: nfr && { keyword: 'not for replication' }, + definition: [c], + resource: 'constraint', + } + } + create_constraint_foreign = kc:constraint_name? __ p:('FOREIGN KEY'i) __ i:column? __ de:cte_column_definition __ id:reference_definition? { - /* => { - constraint?: constraint_name['constraint']; - definition: cte_column_definition; - constraint_type: 'FOREIGN KEY'; - keyword: constraint_name['keyword']; - index?: column; - resource: 'constraint'; - reference_definition?: reference_definition; - }*/ return { constraint: kc && kc.constraint, definition: de, @@ -2141,14 +1559,6 @@ create_constraint_foreign check_constraint_definition = kc:constraint_name? __ u:'CHECK'i __ LPAREN __ c:or_and_expr __ RPAREN __ ne:(KW_NOT? __ 'ENFORCED'i)? { - /* => { - constraint_type: 'check'; - keyword: constraint_name['keyword']; - constraint?: constraint_name['constraint']; - definition: [or_and_expr]; - enforced?: 'enforced' | 'not enforced'; - resource: 'constraint'; - }*/ const enforced = [] if (ne) enforced.push(ne[0], ne[2]) return { @@ -2163,31 +1573,20 @@ check_constraint_definition reference_definition = kc:KW_REFERENCES __ - t: table_name __ + t:table_ref_list __ de:cte_column_definition __ m:('MATCH FULL'i / 'MATCH PARTIAL'i / 'MATCH SIMPLE'i)? __ - od: on_reference? __ - ou: on_reference? { - /* => { - definition: cte_column_definition; - table: table_ref_list; - keyword: 'references'; - match: 'match full' | 'match partial' | 'match simple'; - on_action: [on_reference?]; - }*/ + od:on_reference? __ + ou:on_reference? { return { definition: de, - table: [t], + table: t, keyword: kc.toLowerCase(), - match:m && m.toLowerCase(), + match: m && m.toLowerCase(), on_action: [od, ou].filter(v => v) } } / oa:on_reference { - /* => { - on_action: [on_reference]; - } - */ return { on_action: [oa] } @@ -2202,152 +1601,37 @@ on_reference } } +view_options + = kc:('RESTRICT'i / 'CASCADE'i) { + return kc.toLowerCase() + } + reference_option = kw:KW_CURRENT_TIMESTAMP __ LPAREN __ l:expr_list? __ RPAREN { - // => { type: 'function'; name: string; args: expr_list; } return { type: 'function', - name: { name: [{ type: 'origin', value: kw }] }, + name: { name: [{ type: 'origin', value: kw }]}, args: l } } - / kc:('RESTRICT'i / 'CASCADE'i / 'SET NULL'i / 'NO ACTION'i / 'SET DEFAULT'i / KW_CURRENT_TIMESTAMP) { - // => 'restrict' | 'cascade' | 'set null' | 'no action' | 'set default' | 'current_timestamp' + / kc:(view_options / 'SET NULL'i / 'NO ACTION'i / 'SET DEFAULT'i / KW_CURRENT_TIMESTAMP) { return { type: 'origin', value: kc.toLowerCase() } } -create_constraint_trigger - = kw: KW_CREATE __ - or:(KW_OR __ KW_REPLACE)? __ - kc:KW_CONSTRAINT? __ - t:('TRIGGER'i) __ - c:ident_name __ - p:('BEFORE'i / 'AFTER'i / 'INSTEAD OF'i) __ - te:trigger_event_list __ - on:'ON'i __ - tn:table_name __ - fr:(KW_FROM __ table_name)? __ - de:trigger_deferrable? __ - fe:trigger_for_row? __ - tw:trigger_when? __ - fc:'EXECUTE'i __ e:('PROCEDURE'i / 'FUNCTION'i) __ - fct:proc_func_call { - /* - => { - type: 'create'; - replace?: string; - constraint?: string; - location: 'before' | 'after' | 'instead of'; - events: trigger_event_list; - table: table_name; - from?: table_name; - deferrable?: trigger_deferrable; - for_each?: trigger_for_row; - when?: trigger_when; - execute: { - keyword: string; - expr: proc_func_call; - }; - constraint_type: 'trigger'; - keyword: 'trigger'; - constraint_kw: 'constraint'; - resource: 'constraint'; - } - */ - return { - type: 'create', - replace: or && 'or replace', - constraint: c, - location: p && p.toLowerCase(), - events: te, - table: tn, - from: fr && fr[2], - deferrable: de, - for_each: fe, - when: tw, - execute: { - keyword: `execute ${e.toLowerCase()}`, - expr: fct - }, - constraint_type: t && t.toLowerCase(), - keyword: t && t.toLowerCase(), - constraint_kw: kc && kc.toLowerCase(), - resource: 'constraint', - } - } - -trigger_event - = kw:(KW_INSERT / KW_DELETE / KW_TRUNCATE) { - // => { keyword: 'insert' | 'delete' | 'truncate' } - const keyword = Array.isArray(kw) ? kw[0].toLowerCase() : kw.toLowerCase() - return { - keyword, - } - } - / kw:KW_UPDATE __ a:('OF'i __ column_ref_list)? { - // => { keyword: 'update'; args?: { keyword: 'of', columns: column_ref_list; }} - return { - keyword: kw && kw[0] && kw[0].toLowerCase(), - args: a && { keyword: a[0], columns: a[2] } || null - } - } - -trigger_event_list - = head:trigger_event tail:(__ KW_OR __ trigger_event)* { - // => trigger_event[]; - return createList(head, tail) - } - -trigger_deferrable - = kw:(('NOT'i)? __ 'DEFERRABLE'i) __ args:('INITIALLY IMMEDIATE'i / 'INITIALLY DEFERRED'i) { - // => { keyword: 'deferrable' | 'not deferrable'; args: 'initially immediate' | 'initially deferred' } - return { - keyword: kw && kw[0] ? `${kw[0].toLowerCase()} deferrable` : 'deferrable', - args: args && args.toLowerCase(), - } - } - -trigger_for_row - = kw:'FOR'i __ e:('EACH'i)? __ ob:('ROW'i / 'STATEMENT'i) { - // => { keyword: 'for' | 'for each'; args: 'row' | 'statement' } - return { - keyword: e ? `${kw.toLowerCase()} ${e.toLowerCase()}` : kw.toLowerCase(), - args: ob.toLowerCase() - } - } - -trigger_when - = KW_WHEN __ LPAREN __ condition:expr __ RPAREN { - // => { type: 'when'; cond: expr; parentheses: true; } - return { - type: 'when', - cond: condition, - parentheses: true, - } - } - table_options = head:table_option tail:(__ COMMA? __ table_option)* { - // => table_option[] return createList(head, tail) } create_option_character_set_kw = 'CHARACTER'i __ 'SET'i { - // => string return 'CHARACTER SET' } - create_option_character_set = kw:KW_DEFAULT? __ t:(create_option_character_set_kw / 'CHARSET'i / 'COLLATE'i) __ s:(KW_ASSIGIN_EQUAL)? __ v:ident_without_kw_type { - /* => { - keyword: 'character set' | 'charset' | 'collate' | 'default character set' | 'default charset' | 'default collate'; - symbol: '='; - value: ident_without_kw_type; - } */ return { keyword: kw && `${kw[0].toLowerCase()} ${t.toLowerCase()}` || t.toLowerCase(), symbol: s, @@ -2357,28 +1641,35 @@ create_option_character_set table_option = kw:('AUTO_INCREMENT'i / 'AVG_ROW_LENGTH'i / 'KEY_BLOCK_SIZE'i / 'MAX_ROWS'i / 'MIN_ROWS'i / 'STATS_SAMPLE_PAGES'i) __ s:(KW_ASSIGIN_EQUAL)? __ v:literal_numeric { - /* => { - keyword: 'auto_increment' | 'avg_row_length' | 'key_block_size' | 'max_rows' | 'min_rows' | 'stats_sample_pages'; - symbol: '='; - value: number; // <== literal_numeric['value'] - } */ return { keyword: kw.toLowerCase(), symbol: s, value: v.value } } + / kw:('CHECKSUM' / 'DELAY_KEY_WRITE') __ s:(KW_ASSIGIN_EQUAL) __ v:[01] { + return { + keyword: kw.toLowerCase(), + symbol: s, + value: v + } + } / create_option_character_set - / kw:(KW_COMMENT / 'CONNECTION'i) __ s:(KW_ASSIGIN_EQUAL)? __ c:literal_string { - // => { keyword: 'connection' | 'comment'; symbol: '='; value: string; } + / kw:(KW_COMMENT / 'CONNECTION'i / 'ENGINE_ATTRIBUTE'i / 'SECONDARY_ENGINE_ATTRIBUTE'i ) __ s:(KW_ASSIGIN_EQUAL)? __ c:literal_string { return { keyword: kw.toLowerCase(), symbol: s, value: `'${c.value}'` } } + / type:('DATA'i / 'INDEX'i) __ 'DIRECTORY'i __ s:(KW_ASSIGIN_EQUAL)? __ c:literal_string { + return { + keyword: type.toLowerCase() + " directory", + symbol: s, + value: `'${c.value}'` + } + } / kw:'COMPRESSION'i __ s:(KW_ASSIGIN_EQUAL)? __ v:("'"('ZLIB'i / 'LZ4'i / 'NONE'i)"'") { - // => { keyword: 'compression'; symbol?: '='; value: "'ZLIB'" | "'LZ4'" | "'NONE'" } return { keyword: kw.toLowerCase(), symbol: s, @@ -2386,25 +1677,25 @@ table_option } } / kw:'ENGINE'i __ s:(KW_ASSIGIN_EQUAL)? __ c:ident_name { - // => { keyword: 'engine'; symbol?: '='; value: string; } return { keyword: kw.toLowerCase(), symbol: s, value: c.toUpperCase() } } - / KW_PARTITION __ KW_BY __ v:expr { - // => { keyword: 'partition by'; value: expr; } + / kw:'ROW_FORMAT'i __ s:(KW_ASSIGIN_EQUAL)? __ c:(KW_DEFAULT / 'DYNAMIC'i / 'FIXED'i / 'COMPRESSED'i / 'REDUNDANT'i / 'COMPACT'i) { return { - keyword: 'partition by', - value: v + keyword: kw.toLowerCase(), + symbol: s, + value: c.toUpperCase() } } ALTER_ADD_FULLETXT_SPARITAL_INDEX - = KW_ADD __ fsid:create_fulltext_spatial_index_definition { - // => create_fulltext_spatial_index_definition & { action: 'add'; type: 'alter' } + = KW_ADD __ + fsid:create_fulltext_spatial_index_definition + { return { action: 'add', type: 'alter', @@ -2416,13 +1707,6 @@ rename_stmt = KW_RENAME __ KW_TABLE __ t:table_to_list { - /* - export interface rename_stmt_node { - type: 'rename'; - table: table_to_list; - } - => AstStatement - */ t.forEach(tg => tg.forEach(dt => dt.table && tableList.add(`rename::${dt.db}::${dt.table}`))) return { tableList: Array.from(tableList), @@ -2438,14 +1722,7 @@ set_stmt = KW_SET __ kw: (KW_GLOBAL / KW_SESSION / KW_LOCAL / KW_PERSIST / KW_PERSIST_ONLY)? __ a: assign_stmt_list { - /* - export interface set_stmt_node { - type: 'set'; - keyword?: 'GLOBAL' | 'SESSION' | 'LOCAL' | 'PERSIST' | 'PERSIST_ONLY' | undefined; - expr: assign_stmt_list; - } - => AstStatement - */ + a.keyword = kw return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), @@ -2457,58 +1734,61 @@ set_stmt } } -lock_mode - = "IN"i __ - m:("ACCESS SHARE"i / "ROW SHARE"i / "ROW EXCLUSIVE"i / "SHARE UPDATE EXCLUSIVE"i / "SHARE ROW EXCLUSIVE"i / "EXCLUSIVE"i / "ACCESS EXCLUSIVE"i / "SHARE"i) __ - "MODE"i { - // => { mode: string; } +unlock_stmt + = KW_UNLOCK __ KW_TABLES { return { - mode: `in ${m.toLowerCase()} mode` + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'unlock', + keyword: 'tables' + } } } -lock_stmt - = KW_LOCK __ - k:KW_TABLE? __ - t:table_ref_list __ - lm:lock_mode? __ - nw:("NOWAIT"i)? { - - /* - export interface lock_stmt_node { - type: 'lock'; - keyword: 'lock'; - tables: [[table_base], ...{table: table_ref}[]]; // see table_ref_list - lock_mode?: lock_mode; - nowait?: 'NOWAIT'; - } - => AstStatement - */ +lock_type + = "READ"i __ s:("LOCAL"i)? { + return { + type: 'read', + suffix: s && 'local' + } + } + / p:("LOW_PRIORITY"i)? __ "WRITE"i { + return { + type: 'write', + prefix: p && 'low_priority' + } + } + +lock_table + = t:table_base __ lt:lock_type { + tableList.add(`lock::${t.db}::${t.table}`) + return { + table: t, + lock_type: lt + } + } + +lock_table_list + = head:lock_table tail:(__ COMMA __ lock_table)* { + return createList(head, tail); + } - if (t) t.forEach(tt => tableList.add(`lock::${tt.db}::${tt.table}`)) +lock_stmt + = KW_LOCK __ KW_TABLES __ ltl:lock_table_list { return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { type: 'lock', - keyword: k && k.toLowerCase(), - tables: t.map((table) => ({ table })), - lock_mode: lm, - nowait: nw + keyword: 'tables', + tables: ltl } } } call_stmt - = KW_CALL __ - e: proc_func_call { - /* - export interface call_stmt_node { - type: 'call'; - expr: proc_func_call; - } - => AstStatement - */ + = KW_CALL __ e:proc_func_call { return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), @@ -2520,601 +1800,275 @@ call_stmt } show_stmt - = KW_SHOW __ 'TABLES'i { + = KW_SHOW __ t:('BINARY'i / 'MASTER'i) __ 'LOGS'i { return { - /* - export interface show_stmt_node { - type: 'show'; - keyword: 'tables' | 'var'; - var?: without_prefix_var_decl; - } - => AstStatement - */ tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { type: 'show', - keyword: 'tables' + suffix: 'logs', + keyword: t.toLowerCase() } } } - / KW_SHOW __ c:without_prefix_var_decl { + / KW_SHOW __ KW_TABLES { return { - // => AstStatement tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { type: 'show', - keyword: 'var', - var: c, + keyword: 'tables' } } } - -deallocate_stmt - = KW_DEALLOCATE __ p:('PREPARE'i)? __ i:(ident_name / KW_ALL) { + / KW_SHOW __ keyword:('TRIGGERS'i / 'STATUS'i / 'PROCESSLIST'i) { return { - /* - export interface deallocate_stmt_node { - type: 'deallocate'; - keyword: 'PREPARE' | undefined; - expr: { type: 'default', value: string } - } - => AstStatement - */ tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { - type: 'deallocate', - keyword: p, - expr: { type: 'default', value: i } - }, - } - } -priv_type_table - = p:(KW_SELECT / KW_INSERT / KW_UPDATE / KW_DELETE / KW_TRUNCATE / KW_REFERENCES / 'TRIGGER'i) { - /* export interface origin_str_stmt { - type: 'origin'; - value: string; - } - => origin_str_stmt - */ - return { - type: 'origin', - value: Array.isArray(p) ? p[0] : p - } - } -priv_type_sequence - = p:('USAGE'i / KW_SELECT / KW_UPDATE) { - // => origin_str_stmt - return { - type: 'origin', - value: Array.isArray(p) ? p[0] : p - } - } -priv_type_database - = p:(KW_CREATE / 'CONNECT'i / KW_TEMPORARY / KW_TEMP) { - // => origin_str_stmt - return { - type: 'origin', - value: Array.isArray(p) ? p[0] : p - } - } -prive_type_all - = KW_ALL p:(__ 'PRIVILEGES'i)? { - // => origin_str_stmt - return { - type: 'origin', - value: p ? 'all privileges' : 'all' - } - } -prive_type_usage - = p:'USAGE'i { - // => origin_str_stmt - return { - type: 'origin', - value: p - } - } - / prive_type_all -prive_type_execute - = p:'EXECUTE'i { - // => origin_str_stmt - return { - type: 'origin', - value: p - } - } - / prive_type_all -priv_type - = priv_type_table / priv_type_sequence / priv_type_database / prive_type_usage / prive_type_execute -priv_item - = p:priv_type __ c:(LPAREN __ column_ref_list __ RPAREN)? { - // => { priv: priv_type; columns: column_ref_list; } - return { - priv: p, - columns: c && c[2], - } - } -priv_list - = head:priv_item tail:(__ COMMA __ priv_item)* { - // => priv_item[] - return createList(head, tail) - } -object_type - = o:(KW_TABLE / 'SEQUENCE'i / 'DATABASE'i / 'DOMAIN' / 'FUNCTION' / 'PROCEDURE'i / 'ROUTINE'i / 'LANGUAGE'i / 'LARGE'i / 'SCHEMA') { - // => origin_str_stmt - return { - type: 'origin', - value: o.toUpperCase() - } - } - / KW_ALL __ i:('TABLES'i / 'SEQUENCE'i / 'FUNCTIONS'i / 'PROCEDURES'i / 'ROUTINES'i) __ KW_IN __ KW_SCHEMA { - // => origin_str_stmt - return { - type: 'origin', - value: `all ${i} in schema` - } - } -priv_level - = prefix:(ident __ DOT)? __ name:(ident / STAR) { - // => { prefix: string; name: string; } - return { - prefix: prefix && prefix[0], - name, + type: 'show', + keyword: keyword.toLowerCase() } } -priv_level_list - = head:priv_level tail:(__ COMMA __ priv_level)* { - // => priv_level[] - return createList(head, tail) - } -user_or_role - = g:KW_GROUP? __ i:ident { - // => origin_str_stmt - const name = g ? `${group} ${i}` : i - return { - name: { type: 'origin', value: name }, - } - } - / i:('PUBLIC'i / KW_CURRENT_ROLE / KW_CURRENT_USER / KW_SESSION_USER) { - // => origin_str_stmt - return { - name: { type: 'origin', value: i }, - } - } -user_or_role_list - = head:user_or_role tail:(__ COMMA __ user_or_role)* { - // => user_or_role[] - return createList(head, tail) - } -with_grant_option - = KW_WITH __ 'GRANT'i __ 'OPTION'i { - // => origin_str_stmt - return { - type: 'origin', - value: 'with grant option', - } - } -with_admin_option - = KW_WITH __ 'ADMIN'i __ 'OPTION'i { - // => origin_str_stmt - return { - type: 'origin', - value: 'with admin option', - } } -grant_revoke_keyword - = 'GRANT'i { - // => { type: 'grant' } - return { - type: 'grant' - } - } - / 'REVOKE'i __ i:('GRANT'i __ 'OPTION'i __ 'FOR'i)? { - // => { type: 'revoke'; grant_option_for?: origin_str_stmt; } - return { - type: 'revoke', - grant_option_for: i && { type: 'origin', value: 'grant option for' } - } - } - - -grant_revoke_stmt - = g:grant_revoke_keyword __ pl:priv_list __ KW_ON __ ot:object_type? __ le:priv_level_list __ t:(KW_TO / KW_FROM) &{ - const obj = { revoke: 'from', grant: 'to' } - return obj[g.type].toLowerCase() === t[0].toLowerCase() - } __ to:user_or_role_list __ wo:with_grant_option? { - /* export interface grant_revoke_stmt_t { - type: string; - grant_option_for?: origin_str_stmt; - keyword: 'priv'; - objects: priv_list; - on: { - object_type?: object_type; - priv_level: priv_level_list; - }; - to_from: 'to' | 'from'; - user_or_roles?: user_or_role_list; - with?: with_grant_option; - } - => AstStatement - */ + / KW_SHOW __ keyword:('PROCEDURE'i / 'FUNCTION'i) __ 'STATUS'i { return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { - ...g, - keyword: 'priv', - objects: pl, - on: { - object_type: ot, - priv_level: le - }, - to_from: t[0], - user_or_roles: to, - with: wo + type: 'show', + keyword: keyword.toLowerCase(), + suffix: 'status', } } } - / g:grant_revoke_keyword __ o:ident_list __ t:(KW_TO / KW_FROM) &{ - const obj = { revoke: 'from', grant: 'to' } - return obj[g.type].toLowerCase() === t[0].toLowerCase() - } __ to:user_or_role_list __ wo:with_admin_option? { - // => AstStatement + / KW_SHOW __ 'BINLOG'i __ 'EVENTS'i __ ins:in_op_right? __ from: from_clause? __ limit: limit_clause? { return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { - ...g, - keyword: 'role', - objects: o.map(name => ({ priv: { type: 'string', value: name }})), - to_from: t[0], - user_or_roles: to, - with: wo + type: 'show', + suffix: 'events', + keyword: 'binlog', + in: ins, + from, + limit, } } } -elseif_stmt - = 'ELSEIF'i __ e:expr __ 'THEN'i __ ia:crud_stmt __ s:SEMICOLON? { - // => { type: 'elseif'; boolean_expr: expr; then: crud_stmt; semicolon?: string; } - return { - type: 'elseif', - boolean_expr: e, - then: ia, - semicolon: s - } - - } -elseif_stmt_list - = head:elseif_stmt tail:(__ elseif_stmt)* { - // => elseif_stmt[] - return createList(head, tail, 1) - } -if_else_stmt - = 'IF'i __ ie:expr __ 'THEN'i __ ia:crud_stmt __ s:SEMICOLON? __ ei:elseif_stmt_list? __ el:(KW_ELSE __ crud_stmt)? __ es:SEMICOLON? __ 'END'i __ 'IF'i { - /* export interface if_else_stmt_t { - type: 'if'; - keyword: 'if'; - boolean_expr: expr; - semicolons: string[]; - if_expr: crud_stmt; - elseif_expr: elseif_stmt[]; - else_expr: crud_stmt; - prefix: literal_string; - suffix: literal_string; - } - => AstStatement - */ + / KW_SHOW __ k:(('CHARACTER'i __ 'SET'i) / 'COLLATION'i / 'DATABASES'i) __ e:(like_op_right / where_clause)? { + let keyword = Array.isArray(k) && k || [k] return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { - type: 'if', - keyword: 'if', - boolean_expr: ie, - semicolons: [s || '', es || ''], - prefix: { - type: 'origin', - value: 'then' - }, - if_expr: ia, - elseif_expr: ei, - else_expr: el && el[2], - suffix: { - type: 'origin', - value: 'end if', - } + type: 'show', + suffix: keyword[2] && keyword[2].toLowerCase(), + keyword: keyword[0].toLowerCase(), + expr: e } } } -raise_level - // => string - = 'DEBUG'i / 'LOG'i / 'INFO'i / 'NOTICE'i / 'WARNING'i / 'EXCEPTION'i -raise_opt - = KW_USING __ o:('MESSAGE'i / 'DETAIL'i / 'HINT'i / 'ERRCODE'i / 'COLUMN'i / 'CONSTRAINT'i / 'DATATYPE'i / 'TABLE'i / 'SCHEMA'i) __ KW_ASSIGIN_EQUAL __ e:expr es:(__ COMMA __ expr)* { - // => { type: 'using'; option: string; symbol: '='; expr: expr[]; } - const expr = [e] - if (es) es.forEach(ex => expr.push(ex[3])) - return { - type: 'using', - option: o, - symbol: '=', - expr - } - } -raise_item - = format:literal_string e:(__ COMMA __ proc_primary)* { - // => IGNORE + / KW_SHOW __ keyword:('COLUMNS'i / 'INDEXES'i / "INDEX"i) __ from:from_clause { return { - type: 'format', - keyword: format, - expr: e && e.map(ex => ex[3]) - } - } - / 'SQLSTATE'i __ ss:literal_string { - // => IGNORE - return { - type: 'sqlstate', - keyword: { type: 'origin', value: 'SQLSTATE' }, - expr: [ss], - } + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'show', + keyword: keyword.toLowerCase(), + from + } + }; } - / n:ident { - // => IGNORE + / KW_SHOW __ KW_CREATE __ k:(KW_VIEW / KW_TABLE / 'EVENT'i / KW_TRIGGER / 'PROCEDURE'i) __ t:table_name { + const suffix = k.toLowerCase() return { - type: 'condition', - expr: [{ type: 'default', value: n }] - } + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'show', + keyword: 'create', + suffix, + [suffix]: t + } + }; } -raise_stmt - = 'RAISE'i __ l:raise_level? __ r:raise_item? __ using:raise_opt? { - /* export interface raise_stmt_t { - type: 'raise'; - level?: string; - raise?: raise_item; - using?: raise_opt; - } - => AstStatement - */ + / show_grant_stmt + +show_grant_stmt + = KW_SHOW __ 'GRANTS'i __ f:show_grant_for? { return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { - type: 'raise', - level: l, - using, - raise: r, + type: 'show', + keyword: 'grants', + for: f, } } } -execute_stmt - = 'EXECUTE'i __ name:ident __ a:(LPAREN __ proc_primary_list __ RPAREN)? { - /* export interface execute_stmt_t { - type: 'execute'; - name: string; - args?: { type: expr_list; value: proc_primary_list; } - } - => AstStatement - */ + +show_grant_for + = 'FOR'i __ n:ident __ h:(KW_VAR__PRE_AT __ ident)? __ u:show_grant_for_using? { return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'execute', - name, - args: a && { type: 'expr_list', value: a[2] } - } + user: n, + host: h && h[2], + role_list: u } } -for_label - = 'FOR'i { - // => { label?: string; keyword: 'for'; } - return { - label: null, - keyword: 'for', - } + +show_grant_for_using + = KW_USING __ l:show_grant_for_using_list { + return l } - / label:ident __ 'FOR'i { - // => IGNORE - return { - label, - keyword: 'for' - } + +show_grant_for_using_list + = head:ident tail:(__ COMMA __ ident)* { + return createList(head, tail); } -for_loop_stmt - = f:for_label __ target:ident __ KW_IN __ query:select_stmt __ 'LOOP'i __ stmts:multiple_stmt __ KW_END __ 'LOOP'i __ label:ident? &{ - if (f.label && label && f.label === label) return true - if (!f.label && !label) return true - return false - } { - /* export interface for_loop_stmt_t { - type: 'for'; - label?: string - target: string; - query: select_stmt; - stmts: multiple_stmt; - } - => AstStatement - */ + +desc_stmt + = (KW_DESC / KW_DESCRIBE) __ t:ident { return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { - type: 'for', - label, - target, - query, - stmts: stmts.ast, + type: 'desc', + table: t } - } + }; } -transaction_mode_isolation_level - = 'SERIALIZABLE'i { - // => { type: 'origin'; value: string; } + +explain_stmt + = KW_EXPLAIN __ t:select_stmt_nake { return { - type: 'origin', - value: 'serializable' + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'explain', + expr: t + } } } - / 'REPEATABLE'i __ 'READ'i { - // => ignore + +priv_type_table + = p:(KW_ALL / KW_ALTER / KW_CREATE __ 'VIEW'i / KW_CREATE / KW_DELETE / KW_DROP / 'GRANT'i __ 'OPTION'i / KW_INDEX / KW_INSERT / KW_REFERENCES / KW_SELECT / KW_SHOW __ KW_VIEW / KW_TRIGGER / KW_UPDATE) { return { type: 'origin', - value: 'repeatable read' + value: Array.isArray(p) ? p[0] : p } } - / 'READ'i __ e:('COMMITTED'i / 'UNCOMMITTED'i) { - // => ignore +priv_type_routine + = p:(KW_ALTER __ 'ROUTINE'i / 'EXECUTE'i / 'GRANT'i __ 'OPTION'i / KW_CREATE __ 'ROUTINE'i) { return { type: 'origin', - value: `read ${e.toLowerCase()}` + value: Array.isArray(p) ? p[0] : p } } - -transaction_mode - = 'ISOLATION'i __ 'LEVEL'i __ l:transaction_mode_isolation_level { - // => { type: 'origin'; value: string; } +priv_type + = priv_type_table / priv_type_routine +priv_item + = p:priv_type __ c:(LPAREN __ column_ref_list __ RPAREN)? { return { - type: 'origin', - value: `isolation level ${l.value}` + priv: p, + columns: c && c[2], } } - / 'READ'i __ e:('WRITE'i / 'ONLY'i) { - // => ignore - return { - type: 'origin', - value: `read ${e.toLowerCase()}` +priv_list + = head:priv_item tail:(__ COMMA __ priv_item)* { + return createList(head, tail) } - } - / n:KW_NOT? __ 'DEFERRABLE'i { - // => ignore +object_type + = o:(KW_TABLE / 'FUNCTION'i / 'PROCEDURE'i) { return { type: 'origin', - value: n ? 'not deferrable' : 'deferrable' + value: o.toUpperCase() } } - -transaction_mode_list - = head: transaction_mode tail:(__ COMMA __ transaction_mode)* { - // => transaction_mode[] - return createList(head, tail) - } -transaction_stmt - = k:('commit'i / 'rollback'i) { - /* export interface transaction_stmt_t { - type: 'transaction'; - expr: { - action: { - type: 'origin', - value: string - }; - keyword?: string; - modes?: transaction_mode[]; - } - } - => AstStatement - */ - return { - type: 'transaction', - expr: { - action: { - type: 'origin', - value: k - }, +priv_level + = prefix:((ident / STAR) __ DOT)? __ name:(ident / STAR) { + return { + prefix: prefix && prefix[0], + name, } } - } - / 'begin'i __ k:('WORK'i / 'TRANSACTION'i)? __ m:transaction_mode_list? { - // => ignore +user_or_role + = i:ident __ ho:('@' __ ident)? { return { - type: 'transaction', - expr: { - action: { - type: 'origin', - value: 'begin' - }, - keyword: k, - modes: m - } + name: { type: 'single_quote_string', value: i }, + host: ho ? { type: 'single_quote_string', value: ho[2] } : null } } - / 'start'i __ k:'transaction'i __ m:transaction_mode_list? { - // => ignore +user_or_role_list + = head:user_or_role tail:(__ COMMA __ user_or_role)* { + return createList(head, tail) + } +with_grant_option + = KW_WITH __ 'GRANT'i __ 'OPTION'i { return { - type: 'transaction', - expr: { - action: { - type: 'origin', - value: 'start' - }, - keyword: k, - modes: m - } + type: 'origin', + value: 'with grant option', } } -comment_on_option - = t:(KW_TABLE / KW_VIEW / KW_TABLESPACE) __ name:table_name { - // => { type: string; name: table_name; } +with_admin_option + = KW_WITH __ 'ADMIN'i __ 'OPTION'i { return { - type: t.toLowerCase(), - name, + type: 'origin', + value: 'with admin option', } } - / t:(KW_COLUMN) __ name:column_ref { - // => { type: string; name: column_ref; } +grant_stmt + = 'GRANT'i __ pl:priv_list __ KW_ON __ ot:object_type? __ le:priv_level __ t:KW_TO __ to:user_or_role_list __ wo:with_grant_option? { return { - type: t.toLowerCase(), - name, + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'grant', + keyword: 'priv', + objects: pl, + on: { + object_type: ot, + priv_level: [le] + }, + to_from: t[0], + user_or_roles: to, + with: wo + } } } - / t:(KW_INDEX / KW_COLLATION / KW_TABLESPACE / KW_SCHEMA / 'DOMAIN'i / KW_DATABASE / 'ROLE'i / 'SEQUENCE'i / 'SERVER'i / 'SUBSCRIPTION'i ) __ name:ident_type { - // => { type: string; name: ident; } + / 'GRANT' __ 'PROXY' __ KW_ON __ on:user_or_role __ t:KW_TO __ to:user_or_role_list __ wo:with_admin_option? { return { - type: t.toLowerCase(), - name, + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'grant', + keyword: 'proxy', + objects: [{ priv: { type: 'origin', value: 'proxy' }}], + on, + to_from: t[0], + user_or_roles: to, + with: wo + } } } - -comment_on_is - = 'IS'i __ e:(literal_string / literal_null) { - // => { keyword: 'is'; expr: literal_string | literal_null; } + / 'GRANT' __ o:ident_list __ t:KW_TO __ to:user_or_role_list __ wo:with_admin_option? { return { - keyword: 'is', - expr: e, - } - } -comment_on_stmt - = 'COMMENT'i __ 'ON'i __ co:comment_on_option __ is:comment_on_is { - /* export interface comment_on_stmt_t { - type: 'comment'; - target: comment_on_option; - expr: comment_on_is; + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'grant', + keyword: 'role', + objects: o.map(name => ({ priv: { type: 'string', value: name }})), + to_from: t[0], + user_or_roles: to, + with: wo } - => AstStatement - */ - return { - type: 'comment', - keyword: 'on', - target: co, - expr: is, } } + select_stmt - = KW_SELECT __ ';' { - // => { type: 'select'; } - return { - type: 'select', - } - } - / select_stmt_nake + = select_stmt_nake / s:('(' __ select_stmt __ ')') { - /* - export interface select_stmt_node extends select_stmt_nake { - parentheses: true; - } - => select_stmt_node - */ return { ...s[2], parentheses_symbol: true, @@ -3123,48 +2077,79 @@ select_stmt with_clause = KW_WITH __ head:cte_definition tail:(__ COMMA __ cte_definition)* { - // => cte_definition[] return createList(head, tail); } / __ KW_WITH __ KW_RECURSIVE __ cte:cte_definition { - // => [cte_definition & { recursive: true; }] cte.recursive = true; return [cte] } cte_definition - = name:(literal_string / ident_name) __ columns:cte_column_definition? __ KW_AS __ LPAREN __ stmt:crud_stmt __ RPAREN { - // => { name: { type: 'default'; value: string; }; stmt: crud_stmt; columns?: cte_column_definition; } + = name:(literal_string / ident_name / table_name) __ columns:cte_column_definition? __ KW_AS __ LPAREN __ stmt:set_op_stmt __ RPAREN { if (typeof name === 'string') name = { type: 'default', value: name } - return { name, stmt: stmt.ast, columns }; - } + if (name.table) name = { type: 'default', value: name.table } + return { name, stmt, columns }; + } cte_column_definition - = LPAREN __ l:column_ref_list __ RPAREN { - // => column_ref_list + = LPAREN __ l:column_ref_index __ RPAREN { return l } -distinct_on - = d:KW_DISTINCT __ o:KW_ON __ LPAREN __ c:column_list_items __ RPAREN { - // => {type: string; columns: column_list_items;} - console.lo - return { - type: `${d} ON`, - columns: c +column_idx_ref + = col:column_without_kw __ LPAREN __ l:[0-9]+ __ RPAREN __ ob:(KW_ASC / KW_DESC)? { + return { + type: 'column_ref', + column: col, + suffix: `(${parseInt(l.join(''), 10)})`, + order_by: ob, + ...getLocationObject(), + }; } - } - / d:KW_DISTINCT? { - // => { type: string | undefined; } - return { - type: d, + / col:column_without_kw __ ob:(KW_ASC / KW_DESC)? { + return { + type: 'column_ref', + column: col, + order_by: ob, + ...getLocationObject(), + }; + } + +column_ref_idx_list + = head:column_idx_ref tail:(__ COMMA __ column_idx_ref)* { + return createList(head, tail); + } + +cte_idx_column_definition + = LPAREN __ l:(column_ref_idx_list / expr_list) __ RPAREN { + if (l.type) return l.value + return l } + +for_update + = fu:('FOR'i __ KW_UPDATE) { + return `${fu[0]} ${fu[2][0]}` + } + +lock_in_share_mode + = m:('LOCK'i __ 'IN'i __ 'SHARE'i __ 'MODE'i) { + return `${m[0]} ${m[2]} ${m[4]} ${m[6]}` + } + +lock_option + = w:('WAIT'i __ literal_numeric) { return `${w[0]} ${w[2].value}` } + / nw:'NOWAIT'i + / sl:('SKIP'i __ 'LOCKED'i) { return `${sl[0]} ${sl[2]}` } + +locking_read + = t:(for_update / lock_in_share_mode) __ lo:lock_option? { + return t + (lo ? ` ${lo}` : '') } select_stmt_nake = __ cte:with_clause? __ KW_SELECT ___ opts:option_clause? __ - d:distinct_on? __ + d:KW_DISTINCT? __ c:column_clause __ ci:into_clause? __ f:from_clause? __ @@ -3173,24 +2158,11 @@ select_stmt_nake g:group_by_clause? __ h:having_clause? __ o:order_by_clause? __ + ce:collate_expr? __ l:limit_clause? __ + lr:locking_read? __ win:window_clause? __ li:into_clause? { - /* => { - with?: with_clause; - type: 'select'; - options?: option_clause; - distinct?: {type: string; columns?: column_list; }; - columns: column_clause; - from?: from_clause; - into?: into_clause; - where?: where_clause; - groupby?: group_by_clause; - having?: having_clause; - orderby?: order_by_clause; - limit?: limit_clause; - window?: window_clause; - }*/ if ((ci && fi) || (ci && li) || (fi && li) || (ci && fi && li)) { throw new Error('A given SQL statement can contain at most one INTO clause') } @@ -3211,14 +2183,16 @@ select_stmt_nake having: h, orderby: o, limit: l, + locking_read: lr && lr, window: win, + collate: ce, + ...getLocationObject(), }; } // MySQL extensions to standard SQL option_clause = head:query_option tail:(__ query_option)* { - // => query_option[] const opts = [head]; for (let i = 0, l = tail.length; i < l; ++i) { opts.push(tail[i][1]); @@ -3233,19 +2207,10 @@ query_option / OPT_SQL_BIG_RESULT / OPT_SQL_SMALL_RESULT / OPT_SQL_BUFFER_RESULT - ) { - // => 'SQL_CALC_FOUND_ROWS'| 'SQL_CACHE'| 'SQL_NO_CACHE'| 'SQL_BIG_RESULT'| 'SQL_SMALL_RESULT'| 'SQL_BUFFER_RESULT' - return option; - } + ) { return option; } -column_list_items - = head:column_list_item tail:(__ COMMA __ column_list_item)* { - // => column_list_item[] - return createList(head, tail); - } column_clause = head: (KW_ALL / (STAR !ident_start) / STAR) tail:(__ COMMA __ column_list_item)* { - // => 'ALL' | '*' | column_list_item[] columnList.add('select::null::(.*)') const item = { expr: { @@ -3253,107 +2218,88 @@ column_clause table: null, column: '*' }, - as: null + as: null, + ...getLocationObject(), } if (tail && tail.length > 0) return createList(item, tail) return [item] } - / column_list_items - -array_index - = LBRAKE __ n:(literal_numeric / literal_string) __ RBRAKE { - // => { brackets: boolean, number: number } - return { - brackets: true, - index: n + / head:column_list_item tail:(__ COMMA __ column_list_item)* { + return createList(head, tail); } - } -array_index_list - = head:array_index tail:(__ array_index)* { - // => array_index[] - return createList(head, tail, 1) +fulltext_search_mode + = KW_IN __ 'NATURAL'i __ 'LANGUAGE'i __ 'MODE'i __ 'WITH'i __ 'QUERY'i __ 'EXPANSION'i { + return { type: 'origin', value: 'IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION' } } - -expr_item - = e:binary_column_expr __ a:array_index_list? { - // => binary_column_expr & { array_index: array_index } - if (a) e.array_index = a - return e + / KW_IN __ 'NATURAL'i __ 'LANGUAGE'i __ 'MODE'i { + return { type: 'origin', value: 'IN NATURAL LANGUAGE MODE' } + } + / KW_IN __ 'BOOLEAN'i __ 'MODE'i { + return { type: 'origin', value: 'IN BOOLEAN MODE' } + } + / KW_WITH __ 'QUERY'i __ 'EXPANSION'i { + return { type: 'origin', value: 'WITH QUERY EXPANSION' } } -cast_data_type - = p:'"'? t: data_type s:'"'? { - // => data_type & { quoted?: string } - if ((p && !s) || (!p && s)) throw new Error('double quoted not match') - if (p && s) t.quoted = '"' - return t +fulltext_search + = 'MATCH'i __ LPAREN __ c:column_ref_list __ RPAREN __ 'AGAINST' __ LPAREN __ e:expr __ mo:fulltext_search_mode? __ RPAREN __ as:alias_clause? { + const expr = { + against: 'against', + columns: c, + expr: e, + match: 'match', + mode: mo, + type: 'fulltext_search', + as, + } + return expr } column_list_item - = c:string_constants_escape { - // => { expr: expr; as: null; } - return { expr: c, as: null } + = fs:fulltext_search { + const { as, ...expr } = fs + return { expr, as } } - / e:(column_ref_quoted / expr_item) __ s:KW_DOUBLE_COLON __ t:cast_data_type __ tail:(__ (additive_operator / multiplicative_operator) __ expr_item)* __ alias:alias_clause? { - // => { type: 'cast'; expr: expr; symbol: '::'; target: cast_data_type; as?: null; } - return { - as: alias, - type: 'cast', - expr: e, - symbol: '::', - target: t, - tail: tail && tail[0] && { operator: tail[0][1], expr: tail[0][3] }, - } - } - / tbl:ident_type __ DOT pro:(ident_without_kw_type __ DOT)? __ STAR { - // => { expr: column_ref; as: null; } - const mid = pro && pro[0] - let schema - if (mid) { - schema = tbl - tbl = mid - } - columnList.add(`select::${tbl ? tbl.value : null}::(.*)`) - const column = '*' + / db:ident __ DOT __ table:ident __ DOT __ STAR { + columnList.add(`select::${db}::${table}::(.*)`); return { expr: { type: 'column_ref', - table: tbl, - schema, - column, + db: db, + table: table, + column: '*' }, - as: null - } + as: null, + ...getLocationObject(), + }; } - / tbl:(ident_type __ DOT)? __ STAR { - // => { expr: column_ref; as: null; } + / tbl:(ident __ DOT)? __ STAR { const table = tbl && tbl[0] || null - columnList.add(`select::${table ? table.value : null}::(.*)`); + columnList.add(`select::${table}::(.*)`); return { expr: { type: 'column_ref', - table: table, + table, column: '*' }, - as: null + as: null, + ...getLocationObject(), }; } - / e:expr_item __ alias:alias_clause? { - // => { type: 'expr'; expr: expr; as?: alias_clause; } - return { type: 'expr', expr: e, as: alias }; - } - -value_alias_clause - = KW_AS? __ i:alias_ident { /*=>alias_ident*/ return i; } + / a:select_assign_stmt { + return { expr: a, as: null } + } + / e:binary_column_expr __ alias:alias_clause? { + return { expr: e, as: alias }; + } alias_clause - = KW_AS __ i:alias_ident { /*=>alias_ident*/ return i; } - / KW_AS? __ i:alias_ident { /*=>alias_ident*/ return i; } + = KW_AS __ i:alias_ident { return i; } + / KW_AS? __ i:ident { return i; } into_clause = KW_INTO __ v:var_decl_list { - // => { keyword: 'var'; type: 'into'; expr: var_decl_list; } return { keyword: 'var', type: 'into', @@ -3361,7 +2307,6 @@ into_clause } } / KW_INTO __ k:('OUTFILE'i / 'DUMPFILE'i)? __ f:(literal_string / ident) { - // => { keyword: 'var'; type: 'into'; expr: literal_string | ident; } return { keyword: k, type: 'into', @@ -3370,38 +2315,29 @@ into_clause } from_clause - = KW_FROM __ l:table_ref_list { /*=>table_ref_list*/return l; } + = KW_FROM __ l:table_ref_list { return l; } table_to_list = head:table_to_item tail:(__ COMMA __ table_to_item)* { - // => table_to_item[] return createList(head, tail); } table_to_item - = head:table_name __ KW_TO __ tail: (table_name) { - // => table_name[] + = head:table_name __ KW_TO __ tail:table_name { return [head, tail] } index_type - = KW_USING __ t:("BTREE"i / "HASH"i / "GIST"i / "GIN"i) { - // => { keyword: 'using'; type: 'btree' | 'hash' | 'gist' | 'gin' } + = KW_USING __ + t:("BTREE"i / "HASH"i) { return { keyword: 'using', type: t.toLowerCase(), } } -index_options_list - = head:index_option tail:(__ COMMA __ index_option)* { - // => index_option[] - return createList(head, tail) - } - index_options = head:index_option tail:(__ index_option)* { - // => index_option[] const result = [head]; for (let i = 0; i < tail.length; i++) { result.push(tail[i][1]); @@ -3411,31 +2347,20 @@ index_options index_option = k:KW_KEY_BLOCK_SIZE __ e:(KW_ASSIGIN_EQUAL)? __ kbs:literal_numeric { - // => { type: 'key_block_size'; symbol: '='; expr: number; } return { type: k.toLowerCase(), symbol: e, expr: kbs - } - } - / k:ident_name __ e:KW_ASSIGIN_EQUAL __ kbs:(literal_numeric / ident) { - // => { type: ident_name; symbol: '='; expr: number | {type: 'origin'; value: ident; }; } - return { - type: k.toLowerCase(), - symbol: e, - expr: typeof kbs === 'string' && { type: 'origin', value: kbs } || kbs }; } / index_type / "WITH"i __ "PARSER"i __ pn:ident_name { - // => { type: 'with parser'; expr: ident_name } return { type: 'with parser', expr: pn } } / k:("VISIBLE"i / "INVISIBLE"i) { - // => { type: 'visible'; expr: 'visible' } | { type: 'invisible'; expr: 'invisible' } return { type: k.toLowerCase(), expr: k.toLowerCase() @@ -3446,7 +2371,6 @@ index_option table_ref_list = head:table_base tail:table_ref* { - // => [table_base, ...table_ref[]] tail.unshift(head); tail.forEach(tableInfo => { const { table, as } = tableInfo @@ -3458,30 +2382,22 @@ table_ref_list } table_ref - = __ COMMA __ t:table_base { /* => table_base */ return t; } - / __ t:table_join { /* => table_join */ return t; } + = __ COMMA __ t:table_base { return t; } + / __ t:table_join { return t; } + table_join - = op:join_op __ t:table_base __ KW_USING __ LPAREN __ head:ident_without_kw tail:(__ COMMA __ ident_name)* __ RPAREN { - // => table_base & {join: join_op; using: ident_name[]; } + = op:join_op __ t:table_base __ KW_USING __ LPAREN __ head:ident_name tail:(__ COMMA __ ident_name)* __ RPAREN { t.join = op; t.using = createList(head, tail); return t; } / op:join_op __ t:table_base __ expr:on_clause? { - // => table_base & {join: join_op; on?: on_clause; } t.join = op; t.on = expr; return t; } - / op:(join_op / set_op) __ LPAREN __ stmt:(union_stmt / table_ref_list) __ RPAREN __ alias:alias_clause? __ expr:on_clause? { - /* => { - expr: (union_stmt | table_ref_list) & { parentheses: true; }; - as?: alias_clause; - join: join_op | set_op; - on?: on_clause; - }*/ - if (Array.isArray(stmt)) stmt = { type: 'tables', expr: stmt } + / op:(join_op / set_op) __ LPAREN __ stmt:set_op_stmt __ RPAREN __ alias:alias_clause? __ expr:on_clause? { stmt.parentheses = true; return { expr: stmt, @@ -3490,300 +2406,155 @@ table_join on: expr }; } - //NOTE that, the table assigned to `var` shouldn't write in `table_join` table_base = KW_DUAL { - // => { type: 'dual' } return { type: 'dual' }; } - / stmt:value_clause __ alias:value_alias_clause? { - // => { expr: value_clause; as?: alias_clause; } - return { - expr: { type: 'values', values: stmt }, - as: alias - }; - } - / l:('LATERAL'i)? __ LPAREN __ stmt:(union_stmt / value_clause) __ RPAREN __ alias:value_alias_clause? { - // => { prefix?: string; expr: union_stmt | value_clause; as?: alias_clause; } - if (Array.isArray(stmt)) stmt = { type: 'values', values: stmt } - stmt.parentheses = true; - return { - prefix: l, - expr: stmt, - as: alias - }; - } - / l:('LATERAL'i)? __ LPAREN __ stmt:table_ref_list __ RPAREN __ alias:value_alias_clause? { - // => { prefix?: string; expr: table_ref_list; as?: alias_clause; } - stmt = { type: 'tables', expr: stmt, parentheses: true } - return { - prefix: l, - expr: stmt, - as: alias - }; - } - / l:('LATERAL'i)? __ e:func_call __ alias:alias_clause? { - // => { prefix?: string; type: 'expr'; expr: expr; as?: alias_clause; } - return { prefix: l, type: 'expr', expr: e, as: alias }; - } - / t:table_name __ 'TABLESAMPLE'i __ f:func_call __ re:('REPEATABLE'i __ LPAREN __ literal_numeric __ RPAREN)? __ alias:alias_clause? { - // => table_name & { expr: expr, repeatable: literal_numeric; as?: alias_clause;} - return { - ...t, - as: alias, - tablesample: { - expr: f, - repeatable: re && re[4], + / t:table_name __ alias:alias_clause? { + if (t.type === 'var') { + t.as = alias; + return t; } + return { + db: t.db, + table: t.table, + as: alias, + ...getLocationObject(), + }; } - } - / t:table_name __ alias:alias_clause? { - // => table_name & { as?: alias_clause; } + / LPAREN __ t:table_name __ alias:alias_clause? __ r:RPAREN { + const parentheses = true if (t.type === 'var') { t.as = alias; + t.parentheses = parentheses return t; - } else { - return { - ...t, - as: alias - }; } + return { + db: t.db, + table: t.table, + as: alias, + parentheses, + }; + } + / stmt:value_clause __ alias:alias_clause? { + return { + expr: { type: 'values', values: stmt, prefix: 'row' }, + as: alias + }; + } + / LPAREN __ stmt:(set_op_stmt / value_clause) __ RPAREN __ alias:alias_clause? { + if (Array.isArray(stmt)) stmt = { type: 'values', values: stmt, prefix: 'row' } + stmt.parentheses = true; + return { + expr: stmt, + as: alias + }; } - join_op - = KW_LEFT __ KW_OUTER? __ KW_JOIN { /* => 'LEFT JOIN' */ return 'LEFT JOIN'; } - / KW_RIGHT __ KW_OUTER? __ KW_JOIN { /* => 'RIGHT JOIN' */ return 'RIGHT JOIN'; } - / KW_FULL __ KW_OUTER? __ KW_JOIN { /* => 'FULL JOIN' */ return 'FULL JOIN'; } - / 'CROSS'i __ KW_JOIN { /* => 'CROSS JOIN' */ return 'CROSS JOIN'; } - / (KW_INNER __)? KW_JOIN { /* => 'INNER JOIN' */ return 'INNER JOIN'; } + = KW_LEFT __ KW_OUTER? __ KW_JOIN { return 'LEFT JOIN'; } + / KW_RIGHT __ KW_OUTER? __ KW_JOIN { return 'RIGHT JOIN'; } + / KW_FULL __ KW_OUTER? __ KW_JOIN { return 'FULL JOIN'; } + / KW_CROSS __ KW_JOIN { return 'CROSS JOIN'; } + / (KW_INNER __)? KW_JOIN { return 'INNER JOIN'; } table_name - = dt:ident schema:(__ DOT __ (ident / STAR))? tail:(__ DOT __ (ident / STAR))? { - // => { db?: ident; schema?: ident, table: ident | '*'; } - const obj = { db: null, table: dt }; + = prefix:[_0-9]+ part:ident_without_kw tail:(__ DOT __ ident_without_kw)? { + const dt = `${prefix.join('')}${part}` + const obj = { db: null, table: dt } if (tail !== null) { - obj.db = dt; - obj.schema = schema[3]; - obj.table = tail[3]; - return obj + obj.db = dt + obj.table = tail[3] } - if (schema !== null) { - obj.db = dt; - obj.table = schema[3]; + return obj + } + / part:ident tail:(__ DOT __ ident)? { + const obj = { db: null, table: part } + if (tail !== null) { + obj.db = part + obj.table = tail[3] } - return obj; + return obj } / v:var_decl { - // => IGNORE v.db = null; v.table = v.name; return v; } -or_and_expr - = head:expr tail:(__ (KW_AND / KW_OR) __ expr)* { - /* - export type BINARY_OPERATORS = - | LOGIC_OPERATOR - | "OR" - | "AND" - | multiplicative_operator - | additive_operator - | arithmetic_comparison_operator - | "IN" - | "NOT IN" - | "BETWEEN" - | "NOT BETWEEN" - | "IS" - | "IS NOT" - | "ILIKE" - | "LIKE" - | "@>" - | "<@" - | OPERATOR_CONCATENATION - | DOUBLE_WELL_ARROW - | WELL_ARROW - | "?" - | "?|" - | "?&" - | "#-"; - - export type binary_expr = { - type: "binary_expr"; - operator: BINARY_OPERATORS; - left: expr; - right: expr; - }; - => binary_expr - */ - const len = tail.length - let result = head - for (let i = 0; i < len; ++i) { - result = createBinaryExpr(tail[i][1], result, tail[i][3]) - } - return result - } - on_clause - = KW_ON __ e:or_and_where_expr { /* => or_and_where_expr */ return e; } + = KW_ON __ e:or_and_expr { return e; } where_clause - = KW_WHERE __ e:or_and_where_expr { /* => or_and_where_expr */ return e; } - -group_by_clause - = KW_GROUP __ KW_BY __ e:expr_list { - // => { columns: expr_list['value']; modifiers: literal_string[]; } - return { - columns: e.value - } - } - -column_ref_list - = head:column_ref tail:(__ COMMA __ column_ref)* { - // => column_ref[] - return createList(head, tail); - } - -having_clause - = KW_HAVING __ e:or_and_where_expr { /* => expr */ return e; } - -window_clause - = KW_WINDOW __ l:named_window_expr_list { - // => { keyword: 'window'; type: 'window', expr: named_window_expr_list; } - return { - keyword: 'window', - type: 'window', - expr: l, - } + = KW_WHERE __ e:or_and_where_expr { + return e; } -named_window_expr_list - = head:named_window_expr tail:(__ COMMA __ named_window_expr)* { - // => named_window_expr[] - return createList(head, tail); - } - -named_window_expr - = nw:ident_name __ KW_AS __ anw:as_window_specification { - // => { name: ident_name; as_window_specification: as_window_specification; } +with_rollup + = KW_WITH __ 'ROLLUP'i { return { - name: nw, - as_window_specification: anw, + type: 'origin', + value: 'with rollup' } } -as_window_specification - = ident_name - / LPAREN __ ws:window_specification? __ RPAREN { - // => { window_specification: window_specification; parentheses: boolean } +group_by_clause + = KW_GROUP __ KW_BY __ e:expr_list __ wr:with_rollup? { return { - window_specification: ws || {}, - parentheses: true + columns: e.value, + modifiers: [wr], } } -window_specification - = bc:partition_by_clause? __ - l:order_by_clause? __ - w:window_frame_clause? { - // => { name: null; partitionby: partition_by_clause; orderby: order_by_clause; window_frame_clause: string | null; } - return { - name: null, - partitionby: bc, - orderby: l, - window_frame_clause: w - } - } +column_ref_index + = column_ref_list / literal_list -window_specification_frameless - = bc:partition_by_clause? __ - l:order_by_clause? { - // => { name: null; partitionby: partition_by_clause; orderby: order_by_clause; window_frame_clause: null } - return { - name: null, - partitionby: bc, - orderby: l, - window_frame_clause: null +column_ref_list + = head:column_ref tail:(__ COMMA __ column_ref)* { + return createList(head, tail); } - } - -window_frame_clause - = kw:KW_ROWS __ s:(window_frame_following / window_frame_preceding) { - // => string - return `rows ${s.value}` - } - / KW_ROWS __ KW_BETWEEN __ p:window_frame_preceding __ KW_AND __ f:window_frame_following { - // => string - return `rows between ${p.value} and ${f.value}` - } - -window_frame_following - = s:window_frame_value __ 'FOLLOWING'i { - // => string - s.value += ' FOLLOWING' - return s - } - / window_frame_current_row - -window_frame_preceding - = s:window_frame_value __ 'PRECEDING'i { - // => string - s.value += ' PRECEDING' - return s - } - / window_frame_current_row - -window_frame_current_row - = 'CURRENT'i __ 'ROW'i { - // => { type: 'single_quote_string'; value: string } - return { type: 'single_quote_string', value: 'current row' } - } -window_frame_value - = s:'UNBOUNDED'i { - // => literal_string - return { type: 'single_quote_string', value: s.toUpperCase() } - } - / literal_numeric +having_clause + = KW_HAVING __ e:or_and_where_expr { return e; } partition_by_clause - = KW_PARTITION __ KW_BY __ bc:column_ref_list { /* => { type: 'expr'; expr: column_ref_list }[] */ return bc.map(item => ({ type: 'expr', expr: item })); } + = KW_PARTITION __ KW_BY __ bc:column_clause { return bc; } order_by_clause - = KW_ORDER __ KW_BY __ l:order_by_list { /* => order_by_list */ return l; } + = KW_ORDER __ KW_BY __ l:order_by_list { return l; } order_by_list = head:order_by_element tail:(__ COMMA __ order_by_element)* { - // => order_by_element[] return createList(head, tail); } order_by_element - = e:expr __ d:(KW_DESC / KW_ASC)? __ nl:('NULLS'i __ ('FIRST'i / 'LAST'i)?)? { - // => { expr: expr; type: 'ASC' | 'DESC' | undefined; nulls: 'NULLS FIRST' | 'NULLS LAST' | undefined } + = e:expr __ d:(KW_DESC / KW_ASC)? { const obj = { expr: e, type: d }; - obj.nulls = nl && [nl[0], nl[2]].filter(v => v).join(' ') return obj; } number_or_param = literal_numeric - / var_decl / param + / '?' { + return { + type: 'origin', + value: '?' + } + } limit_clause - = l:(KW_LIMIT __ (number_or_param / KW_ALL))? __ tail:(KW_OFFSET __ number_or_param)? { - // => { separator: 'offset' | ''; value: [number_or_param | { type: 'origin', value: 'all' }, number_or_param?] } - const res = [] - if (l) res.push(typeof l[2] === 'string' ? { type: 'origin', value: 'all' } : l[2]) + = KW_LIMIT __ i1:(number_or_param) __ tail:((COMMA / KW_OFFSET) __ number_or_param)? { + const res = [i1]; if (tail) res.push(tail[2]); return { seperator: tail && tail[0] && tail[0].toLowerCase() || '', - value: res + value: res, + ...getLocationObject(), }; } @@ -3792,20 +2563,9 @@ update_stmt t:table_ref_list __ KW_SET __ l:set_list __ - f:from_clause? __ w:where_clause? __ - r:returning_stmt? { - /* export interface update_stmt_node { - with?: with_clause; - type: 'update'; - table: table_ref_list; - set: set_list; - from?: from_clause; - where?: where_clause; - returning?: returning_stmt; - } - => AstStatement - */ + or:order_by_clause? __ + lc:limit_clause? { const dbObj = {} if (t) t.forEach(tableInfo => { const { db, as, table, join } = tableInfo @@ -3819,7 +2579,7 @@ update_stmt const table = queryTableAlias(col.table) tableList.add(`update::${dbObj[table] || null}::${table}`) } - columnList.add(`update::${col.table}::${col.column.expr.value}`) + columnList.add(`update::${col.table}::${col.column}`) }); } return { @@ -3830,31 +2590,21 @@ update_stmt type: 'update', table: t, set: l, - from: f, where: w, - returning: r, + orderby: or, + limit: lc, } }; } delete_stmt - = KW_DELETE __ + = __ cte:with_clause? __ KW_DELETE __ t:table_ref_list? __ f:from_clause __ - w:where_clause? { - /* - export interface table_ref_addition extends table_name { - addition: true; - as?: alias_clause; - } - export interface delete_stmt_node { - type: 'delete'; - table?: table_ref_list | [table_ref_addition]; - where?: where_clause; - } - => AstStatement - */ - if(f) f.forEach(tableInfo => { + w:where_clause? __ + or:order_by_clause? __ + l:limit_clause? { + if(f) f.forEach(tableInfo => { const { db, as, table, join } = tableInfo const action = join ? 'select' : 'delete' if (table) tableList.add(`${action}::${db}::${table}`) @@ -3873,16 +2623,18 @@ delete_stmt tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { + with: cte, type: 'delete', table: t, from: f, - where: w + where: w, + orderby: or, + limit: l, } }; } set_list = head:set_item tail:(__ COMMA __ set_item)* { - // => set_item[] return createList(head, tail); } @@ -3892,22 +2644,11 @@ set_list * 'col1 = (col2 > 3)' */ set_item - = c:column_ref_array_index __ '=' __ v:additive_expr { - // => { column: ident; value: additive_expr; table?: ident;} - return { ...c, value: v }; - } - / column_ref_array_index __ '=' __ KW_VALUES __ LPAREN __ v:column_ref __ RPAREN { - // => { column: ident; value: column_ref; table?: ident; keyword: 'values' } - return { ...c, value: v, keyword: 'values' }; + = tbl:(ident __ DOT)? __ c:column_without_kw __ '=' __ v:additive_expr { + return { column: c, value: v, table: tbl && tbl[0] }; } - -returning_stmt - = k:KW_RETURNING __ c:(column_clause / select_stmt) { - // => { type: 'returning'; columns: column_clause | select_stmt; } - return { - type: k && k.toLowerCase() || 'returning', - columns: c === '*' && [{ type: 'expr', expr: { type: 'column_ref', table: null, column: '*' }, as: null }] || c - } + / tbl:(ident __ DOT)? __ c:column_without_kw __ '=' __ KW_VALUES __ LPAREN __ v:column_ref __ RPAREN { + return { column: c, value: v, table: tbl && tbl[0], keyword: 'values' }; } insert_value_clause @@ -3916,78 +2657,20 @@ insert_value_clause insert_partition = KW_PARTITION __ LPAREN __ head:ident_name tail:(__ COMMA __ ident_name)* __ RPAREN { - // => ident_name[] return createList(head, tail) } / KW_PARTITION __ v: value_item { - // => value_item return v } -conflict_target - = LPAREN __ c:column_ref_list __ RPAREN { - // => { type: 'column'; expr: column_ref_list; parentheses: true; } - return { - type: 'column', - expr: c, - parentheses: true, - } - } - -conflict_action - = 'DO'i __ 'NOTHING'i { - // => { keyword: "do"; expr: {type: 'origin'; value: string; }; } - return { - keyword: 'do', - expr: { - type: 'origin', - value: 'nothing' - } - } - } - / 'DO'i __ KW_UPDATE __ KW_SET __ s:set_list __ w:where_clause? { - // => { keyword: "do"; expr: {type: 'update'; set: set_list; where: where_clause; }; } - return { - keyword: 'do', - expr: { - type: 'update', - set: s, - where: w, - } - } - } - -on_conflict - = KW_ON __ 'CONFLICT'i __ ct:conflict_target? __ ca:conflict_action { - // => { type: "conflict"; keyword: "on"; target: conflict_target; action: conflict_action; } - return { - type: 'conflict', - keyword: 'on', - target: ct, - action: ca, - } - } - replace_insert_stmt = ri:replace_insert __ - KW_INTO? __ + ig:KW_IGNORE? __ + it:KW_INTO? __ t:table_name __ p:insert_partition? __ LPAREN __ c:column_list __ RPAREN __ v:insert_value_clause __ - oc:on_conflict? __ - r:returning_stmt? { - /* - export interface replace_insert_stmt_node { - type: 'insert' | 'replace'; - table?: [table_name]; - columns: column_list; - conflict?: on_conflict; - values: insert_value_clause; - partition?: insert_partition; - returning?: returning_stmt; - } - => AstStatement - */ + odp:on_duplicate_update_stmt? { if (t) { tableList.add(`insert::${t.db}::${t.table}`) t.as = null @@ -4001,8 +2684,9 @@ replace_insert_stmt } }) } - c.forEach(c => columnList.add(`insert::${table}::${c.value}`)); + c.forEach(c => columnList.add(`insert::${table}::${c}`)); } + const prefix = [ig, it].filter(v => v).map(v => v[0] && v[0].toLowerCase()).join(' ') return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), @@ -4012,21 +2696,20 @@ replace_insert_stmt columns: c, values: v, partition: p, - conflict: oc, - returning: r, + prefix, + on_duplicate_update: odp, } }; } insert_no_columns_stmt - = ri:replace_insert __ + = ri:replace_insert __ ig:KW_IGNORE? __ it:KW_INTO? __ t:table_name __ p:insert_partition? __ v:insert_value_clause __ - r:returning_stmt? { - // => AstStatement + odp: on_duplicate_update_stmt? { if (t) { tableList.add(`insert::${t.db}::${t.table}`) columnList.add(`insert::${t.table}::(.*)`); @@ -4043,70 +2726,89 @@ insert_no_columns_stmt values: v, partition: p, prefix, - returning: r, + on_duplicate_update: odp, + } + }; + } + +insert_into_set + = ri:replace_insert __ + ig:KW_IGNORE? __ + it:KW_INTO? __ + t:table_name __ + p:insert_partition? __ + KW_SET __ + l:set_list __ + odp:on_duplicate_update_stmt? { + if (t) { + tableList.add(`insert::${t.db}::${t.table}`) + columnList.add(`insert::${t.table}::(.*)`); + t.as = null + } + const prefix = [ig, it].filter(v => v).map(v => v[0] && v[0].toLowerCase()).join(' ') + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: ri, + table: [t], + columns: null, + partition: p, + prefix, + set: l, + on_duplicate_update: odp, } }; } +on_duplicate_update_stmt + = KW_ON __ 'DUPLICATE'i __ KW_KEY __ KW_UPDATE __ s:set_list { + return { + keyword: 'on duplicate key update', + set: s + } + } + replace_insert - = KW_INSERT { /* => 'insert' */ return 'insert'; } - / KW_REPLACE { /* => 'replace' */return 'replace'; } + = KW_INSERT { return 'insert'; } + / KW_REPLACE { return 'replace'; } value_clause - = KW_VALUES __ l:value_list { /* => value_list */ return l; } + = KW_VALUES __ l:value_list { return l; } value_list = head:value_item tail:(__ COMMA __ value_item)* { - // => value_item[] return createList(head, tail); } value_item - = LPAREN __ l:expr_list __ RPAREN { - // => expr_list + = 'ROW'i? __ LPAREN __ l:expr_list __ RPAREN { return l; } expr_list = head:expr tail:(__ COMMA __ expr)* { - // => { type: 'expr_list'; value: expr[] } const el = { type: 'expr_list' }; el.value = createList(head, tail); return el; } interval_expr - = KW_INTERVAL __ - e:expr __ + = KW_INTERVAL __ + e:expr __ u: interval_unit { - // => { type: 'interval', expr: expr; unit: interval_unit; } return { type: 'interval', expr: e, unit: u.toLowerCase(), } } - / KW_INTERVAL __ - e:literal_string { - // => { type: 'interval', expr: expr; unit: interval_unit; } - return { - type: 'interval', - expr: e, - unit: '', - } - } case_expr = KW_CASE __ condition_list:case_when_then_list __ otherwise:case_else? __ KW_END __ KW_CASE? { - /* => { - type: 'case'; - expr: null; - // nb: Only the last element is a case_else - args: (case_when_then | case_else)[]; - } */ if (otherwise) condition_list.push(otherwise); return { type: 'case', @@ -4114,17 +2816,11 @@ case_expr args: condition_list }; } - / KW_CASE __ + / KW_CASE __ expr:expr __ condition_list:case_when_then_list __ otherwise:case_else? __ KW_END __ KW_CASE? { - /* => { - type: 'case'; - expr: expr; - // nb: Only the last element is a case_else - args: (case_when_then | case_else)[]; - } */ if (otherwise) condition_list.push(otherwise); return { type: 'case', @@ -4135,13 +2831,11 @@ case_expr case_when_then_list = head:case_when_then __ tail:(__ case_when_then)* { - // => case_when_then[] return createList(head, tail, 1) } case_when_then = KW_WHEN __ condition:or_and_where_expr __ KW_THEN __ result:expr { - // => { type: 'when'; cond: or_and_where_expr; result: expr; } return { type: 'when', cond: condition, @@ -4150,42 +2844,33 @@ case_when_then } case_else = KW_ELSE __ result:expr { - // => { type: 'else'; condition?: never; result: expr; } return { type: 'else', result: result }; } /** - * Borrowed from PL/SQL ,the priority of below list IS ORDER BY DESC + * From MySQL Manual * --------------------------------------------------------------------------------------------------- - * | +, - | identity, negation | - * | *, / | multiplication, division | - * | +, - | addition, subtraction, concatenation | - * | =, <, >, <=, >=, <>, !=, IS, LIKE, BETWEEN, IN | comparion | - * | !, NOT | logical negation | - * | AND | conjunction | - * | OR | inclusion | + * * ! + * - (unary minus), ~ (unary bit inversion) + * ^ + * *, /, DIV, %, MOD + * -, + + * <<, >> + * & + * | + * = (comparison), <=>, >=, >, <=, <, <>, !=, IS, LIKE, REGEXP, IN, MEMBER OF + * BETWEEN, CASE, WHEN, THEN, ELSE + * NOT + * AND, && + * XOR + * OR, || + * = (assignment), := | * --------------------------------------------------------------------------------------------------- */ -_expr - = or_expr - / unary_expr expr - = _expr / union_stmt - -unary_expr - = op: additive_operator tail: (__ primary)+ { - /* - export type UNARY_OPERATORS = '+' | '-' | 'EXISTS' | 'NOT EXISTS' | 'NULL' - => { - type: 'unary_expr', - operator: UNARY_OPERATORS, - expr: expr; - parentheses?: boolean; - } */ - return createUnaryExpr(op, tail[0][1]); - } + = or_expr / set_op_stmt binary_column_expr = head:expr tail:(__ (KW_AND / KW_OR / LOGIC_OPERATOR) __ expr)* { @@ -4194,7 +2879,6 @@ binary_column_expr if (!(head.parentheses_symbol || head.parentheses || head.ast.parentheses || head.ast.parentheses_symbol) || ast.columns.length !== 1 || ast.columns[0].expr.column === '*') throw new Error('invalid column clause with select statement') } if (!tail || tail.length === 0) return head - // => binary_expr const len = tail.length let result = tail[len - 1][3] for (let i = len - 1; i >= 0; i--) { @@ -4204,9 +2888,18 @@ binary_column_expr return result } +or_and_expr + = head:expr tail:(__ (KW_AND / KW_OR) __ expr)* { + const len = tail.length + let result = head + for (let i = 0; i < len; ++i) { + result = createBinaryExpr(tail[i][1], result, tail[i][3]) + } + return result + } + or_and_where_expr = head:expr tail:(__ (KW_AND / KW_OR / COMMA) __ expr)* { - // => binary_expr | { type: 'expr_list'; value: expr[] } const len = tail.length let result = head; let seperator = '' @@ -4221,7 +2914,7 @@ or_and_where_expr } if (seperator === ',') { const el = { type: 'expr_list' } - el.value = result + el.value = Array.isArray(result) ? result : [result] return el } return result @@ -4229,28 +2922,23 @@ or_and_where_expr or_expr = head:and_expr tail:(___ KW_OR __ and_expr)* { - // => binary_expr return createBinaryExprChain(head, tail); } and_expr = head:not_expr tail:(___ KW_AND __ not_expr)* { - // => binary_expr return createBinaryExprChain(head, tail); - } - + } //here we should use `NOT` instead of `comparision_expr` to support chain-expr not_expr = comparison_expr / exists_expr - / (KW_NOT / "!" !"=") __ expr:not_expr { - // => unary_expr + / KW_NOT __ expr:not_expr { return createUnaryExpr('NOT', expr); } comparison_expr = left:additive_expr __ rh:comparison_op_right? { - // => binary_expr if (rh === null) return left; else if (rh.type === 'arithmetic') return createBinaryExprChain(left, rh.tail); else return createBinaryExpr(rh.op, left, rh.right); @@ -4259,14 +2947,13 @@ comparison_expr / column_ref exists_expr - = op:exists_op __ LPAREN __ stmt:union_stmt __ RPAREN { - // => unary_expr + = op:exists_op __ LPAREN __ stmt:set_op_stmt __ RPAREN { stmt.parentheses = true; return createUnaryExpr(op, stmt); } exists_op - = nk:(KW_NOT __ KW_EXISTS) { /* => 'NOT EXISTS' */ return nk[0] + ' ' + nk[2]; } + = nk:(KW_NOT __ KW_EXISTS) { return nk[0] + ' ' + nk[2]; } / KW_EXISTS comparison_op_right @@ -4275,11 +2962,10 @@ comparison_op_right / between_op_right / is_op_right / like_op_right - / regex_op_right + / regexp_op_right arithmetic_op_right = l:(__ arithmetic_comparison_operator __ additive_expr)+ { - // => { type: 'arithmetic'; tail: any } return { type: 'arithmetic', tail: l }; } @@ -4288,27 +2974,14 @@ arithmetic_comparison_operator is_op_right = KW_IS __ right:additive_expr { - // => { op: 'IS'; right: additive_expr; } return { op: 'IS', right: right }; } - / KW_IS __ right:(KW_DISTINCT __ KW_FROM __ table_name) { - // => { type: 'origin'; value: string; } - const { db, table } = right.pop() - const tableName = table === '*' ? '*' : `"${table}"` - let tableStr = db ? `"${db}".${tableName}` : tableName - return { op: 'IS', right: { - type: 'default', - value: `DISTINCT FROM ${tableStr}` - }} - } / (KW_IS __ KW_NOT) __ right:additive_expr { - // => { type: 'IS NOT'; right: additive_expr; } return { op: 'IS NOT', right: right }; } between_op_right = op:between_or_not_between_op __ begin:additive_expr __ KW_AND __ end:additive_expr { - // => { op: 'BETWEEN' | 'NOT BETWEEN'; right: { type: 'expr_list'; value: [expr, expr] } } return { op: op, right: { @@ -4319,31 +2992,17 @@ between_op_right } between_or_not_between_op - = nk:(KW_NOT __ KW_BETWEEN) { /* => 'NOT BETWEEN' */ return nk[0] + ' ' + nk[2]; } + = nk:(KW_NOT __ KW_BETWEEN) { return nk[0] + ' ' + nk[2]; } / KW_BETWEEN like_op - = nk:(KW_NOT __ (KW_LIKE / KW_ILIKE)) { /* => 'LIKE' */ return nk[0] + ' ' + nk[2]; } + = nk:(KW_NOT __ KW_LIKE) { return nk[0] + ' ' + nk[2]; } / KW_LIKE - / KW_ILIKE - / 'SIMILAR'i __ KW_TO { - // => 'SIMILAR TO' - return 'SIMILAR TO' - } - / KW_NOT __ 'SIMILAR'i __ KW_TO { - // => 'NOT SIMILAR TO' - return 'NOT SIMILAR TO' - } - -regex_op - = "!~*" / "~*" / "~" / "!~" - -regex_op_right -= op:regex_op __ right:(literal / comparison_expr) { - // => { op: regex_op; right: literal | comparison_expr} - return { op: op, right: right }; - } +regexp_op + = n: KW_NOT? __ k:(KW_REGEXP / KW_RLIKE) { + return n ? `${n} ${k}` : k + } escape_op = kw:'ESCAPE'i __ c:literal_string { // => { type: 'ESCAPE'; value: literal_string } @@ -4354,30 +3013,31 @@ escape_op } in_op - = nk:(KW_NOT __ KW_IN) { /* => 'NOT IN' */ return nk[0] + ' ' + nk[2]; } + = nk:(KW_NOT __ KW_IN) { return nk[0] + ' ' + nk[2]; } / KW_IN +regexp_op_right + = op:regexp_op __ b:'BINARY'i? __ e:(func_call / literal_string / column_ref) { + return { op: b ? `${op} ${b}` : op, right: e }; + } + like_op_right - = op:like_op __ right:(literal / comparison_expr) __ es:escape_op? { - // => { op: like_op; right: (literal | comparison_expr) & { escape?: escape_op }; } + = op:like_op __ right:(literal / param / comparison_expr ) __ es:escape_op? { if (es) right.escape = es return { op: op, right: right }; } in_op_right = op:in_op __ LPAREN __ l:expr_list __ RPAREN { - // => {op: in_op; right: expr_list | var_decl | literal_string; } return { op: op, right: l }; } - / op:in_op __ e:(var_decl / literal_string / func_call) { - // => IGNORE + / op:in_op __ e:(var_decl / column_ref / literal_string) { return { op: op, right: e }; } additive_expr - = head:multiplicative_expr + = head: multiplicative_expr tail:(__ additive_operator __ multiplicative_expr)* { - // => binary_expr if (tail && tail.length && head.type === 'column_ref' && head.column === '*') throw new Error(JSON.stringify({ message: 'args could not be star column in additive expr', ...getLocationObject(), @@ -4391,40 +3051,20 @@ additive_operator multiplicative_expr = head:unary_expr_or_primary tail:(__ (multiplicative_operator / LOGIC_OPERATOR) __ unary_expr_or_primary)* { - // => binary_expr return createBinaryExprChain(head, tail) } multiplicative_operator = "*" / "/" / "%" / "||" - -column_ref_array_index - = c:column_ref __ a:array_index_list? { - // => column_ref - if (a) c.array_index = a - return c - } - -primary - = cast_expr - / LPAREN __ list:or_and_where_expr __ RPAREN { - // => or_and_where_expr - list.parentheses = true; - return list; - } - / var_decl - / __ p:'$''<'n:literal_numeric'>' { - // => { type: 'origin'; value: string; } - return { - type: 'origin', - value: `$<${n.value}>`, - } + / "div"i { + return 'DIV' } + / '&' / '>>' / '<<' / '^' / '|' unary_expr_or_primary = jsonb_expr / op:(unary_operator) tail:(__ unary_expr_or_primary) { - // => unary_expr + // if (op === '!') op = 'NOT' return createUnaryExpr(op, tail[1]) } @@ -4438,116 +3078,107 @@ jsonb_expr return createBinaryExprChain(head, tail) } -string_constants_escape - = 'E'i"'" __ n:single_char* __ "'" { - // => { type: 'origin'; value: string; } +primary + = aggr_func + / fulltext_search + / func_call + / cast_expr + / case_expr + / interval_expr + / literal + / column_ref + / param + / LPAREN __ list:or_and_where_expr __ RPAREN { + list.parentheses = true + return list + } + / var_decl + / __ prepared_symbol:'?' { return { type: 'origin', - value: `E'${n.join('')}'` + value: prepared_symbol } } column_ref - = string_constants_escape - / tbl:(ident __ DOT)? __ STAR { - // => IGNORE - const table = tbl && tbl[0] || null - columnList.add(`select::${table}::(.*)`); - return { - type: 'column_ref', - table: table, - column: '*' - } - } - / schema:ident tbl:(__ DOT __ ident) col:(__ DOT __ column_without_kw_type) { - /* => { - type: 'column_ref'; - schema: string; - table: string; - column: column | '*'; - } */ - columnList.add(`select::${schema}.${tbl[3]}::${col[3].value}`); + = db:(ident_name / backticks_quoted_ident) __ DOT __ tbl:(ident_name / backticks_quoted_ident) __ DOT __ col:column_without_kw { + columnList.add(`select::${typeof db === 'object' ? db.value : db}::${typeof tbl === 'object' ? tbl.value : tbl}::${col}`); return { type: 'column_ref', - schema: schema, - table: tbl[3], - column: { expr: col[3] } + db: db, + table: tbl, + column: col, + ...getLocationObject(), }; } - / tbl:ident __ DOT __ col:column_without_kw_type { - /* => { - type: 'column_ref'; - table: ident; - column: column | '*'; - } */ - columnList.add(`select::${tbl}::${col.value}`); + / tbl:(ident_name / backticks_quoted_ident) __ DOT __ col:column_without_kw { + columnList.add(`select::${typeof tbl === 'object' ? tbl.value : tbl}::${col}`); return { type: 'column_ref', table: tbl, - column: { expr: col } + column: col, + ...getLocationObject(), }; } - / col:column_type { - // => IGNORE - columnList.add(`select::null::${col.value}`); + / tbl:(ident __ DOT)? __ STAR { + const table = tbl && tbl[0] || null + columnList.add(`select::${table}::(.*)`); return { - type: 'column_ref', - table: null, - column: { expr: col } + expr: { + type: 'column_ref', + table, + column: '*' + }, + as: null, + ...getLocationObject(), }; } - -column_ref_quoted - = col:literal_double_quoted_string { - // => unknown - columnList.add(`select::null::${col.value}`); + / col:column { + columnList.add(`select::null::${col}`); return { type: 'column_ref', table: null, - column: { expr: col } + column: col, + ...getLocationObject(), }; } column_list - = head:column_type tail:(__ COMMA __ column_type)* { - // => column[] + = head:column tail:(__ COMMA __ column)* { return createList(head, tail); } - -ident_without_kw_type +ident_name_type = n:ident_name { - // => { type: 'default', value: string } return { type: 'default', value: n } } - / quoted_ident_type +ident_without_kw_type + = ident_name_type / quoted_ident_type ident_type = name:ident_name !{ return reservedMap[name.toUpperCase()] === true; } { - // => ident_name return { type: 'default', value: name } } / quoted_ident_type - +ident_without_kw + = ident_name / quoted_ident ident = name:ident_name !{ return reservedMap[name.toUpperCase()] === true; } { - // => ident_name return name; } / quoted_ident ident_list = head:ident tail:(__ COMMA __ ident)* { - // => ident[] return createList(head, tail) } alias_ident - = name:column_name !{ return reservedMap[name.toUpperCase()] === true } c:(__ LPAREN __ column_list __ RPAREN)? { - // => string - if (!c) return name; - return `${name}(${c[3].map(v => v.value).join(', ')})` + = name:ident_name !{ + if (reservedMap[name.toUpperCase()] === true) throw new Error("Error: "+ JSON.stringify(name)+" is a reserved word, can not as alias clause"); + return false + } { + return name; } - / name:double_quoted_ident { - // => IGNORE - return name.value; + / name:quoted_ident { + return name; } quoted_ident_type @@ -4555,13 +3186,11 @@ quoted_ident_type quoted_ident = v:(double_quoted_ident / single_quoted_ident / backticks_quoted_ident) { - // => string return v.value } double_quoted_ident = '"' chars:[^"]+ '"' { - // => { type: 'double_quote_string'; value: string; } return { type: 'double_quote_string', value: chars.join('') @@ -4570,7 +3199,6 @@ double_quoted_ident single_quoted_ident = "'" chars:[^']+ "'" { - // => { type: 'single_quote_string'; value: string; } return { type: 'single_quote_string', value: chars.join('') @@ -4578,271 +3206,294 @@ single_quoted_ident } backticks_quoted_ident - = "`" chars:[^`]+ "`" { - // => { type: 'backticks_quote_string'; value: string; } + = "`" chars:([^`\\] / escape_char)+ "`" { return { type: 'backticks_quote_string', value: chars.join('') } } -ident_without_kw - = ident_name / quoted_ident - column_without_kw - = column_name / quoted_ident - -column_without_kw_type - = n:column_name { - // => { type: 'default', value: string } - return { type: 'default', value: n } - } - / quoted_ident_type -column_type - = name:column_name !{ return reservedMap[name.toUpperCase()] === true; } { - // => { type: 'default', value: string } - return { type: 'default', value: name } + = name:column_name { + return name; } - / quoted_ident_type -column - = name:column_name !{ return reservedMap[name.toUpperCase()] === true; } { /* => string */ return name; } / quoted_ident +column + = name:column_name !{ return reservedMap[name.toUpperCase()] === true; } { return name; } + / n:backticks_quoted_ident { + return n.value + } + column_name - = start:ident_start parts:column_part* { /* => string */ return start + parts.join(''); } + = start:ident_start parts:column_part* { return start + parts.join(''); } ident_name - = start:ident_start parts:ident_part* { - // => string - return start + parts.join(''); - } + = start:ident_start parts:ident_part* { return start + parts.join(''); } ident_start = [A-Za-z_\u4e00-\u9fa5] -ident_part = [A-Za-z0-9_\-$\u4e00-\u9fa5\u00C0-\u017F] +ident_part = [A-Za-z0-9_$\u0080-\uffff] // to support column name like `cf1:name` in hbase -column_part = [A-Za-z0-9_\u4e00-\u9fa5\u00C0-\u017F] +column_part = [A-Za-z0-9_:] param = l:(':' ident_name) { - // => { type: 'param'; value: ident_name } return { type: 'param', value: l[1] }; } +aggr_func + = aggr_fun_count + / aggr_fun_smma + +aggr_fun_smma + = name:KW_SUM_MAX_MIN_AVG __ LPAREN __ e:expr __ RPAREN __ bc:over_partition? { + return { + type: 'aggr_func', + name: name, + args: { + expr: e + }, + over: bc, + ...getLocationObject(), + }; + } + +KW_SUM_MAX_MIN_AVG + = KW_SUM / KW_MAX / KW_MIN / KW_AVG + on_update_current_timestamp - = KW_ON __ KW_UPDATE __ kw:KW_CURRENT_TIMESTAMP __ LPAREN __ l:expr_list? __ RPAREN{ - // => { type: 'on update'; keyword: string; parentheses: boolean; expr: expr } + = KW_ON __ KW_UPDATE __ kw:KW_CURRENT_TIMESTAMP __ l:(LPAREN __ expr_list? __ RPAREN)? { + const parentheses = l ? true : false + const expr = l ? l[2] : null return { type: 'on update', keyword: kw, - parentheses: true, - expr: l + parentheses, + expr, } } - / KW_ON __ KW_UPDATE __ kw:KW_CURRENT_TIMESTAMP { - // => { type: 'on update'; keyword: string; } + / KW_ON __ KW_UPDATE __ kw:'NOW'i __ LPAREN __ RPAREN { return { type: 'on update', keyword: kw, + parentheses: true, } } over_partition = 'OVER'i __ aws:as_window_specification { - // => { type: 'windows'; as_window_specification: as_window_specification } return { type: 'window', as_window_specification: aws, } } - / 'OVER'i __ LPAREN __ bc:partition_by_clause? __ l:order_by_clause? __ RPAREN { - // => { partitionby: partition_by_clause; orderby: order_by_clause } - return { - partitionby: bc, - orderby: l - } - } / on_update_current_timestamp -aggr_filter - = 'FILTER'i __ LPAREN __ wc:where_clause __ RPAREN { - // => { keyword: 'filter'; parentheses: true, where: where_clause } +window_clause + = 'WINDOW'i __ l:named_window_expr_list { + // => { keyword: 'window'; type: 'window', expr: named_window_expr_list; } return { - keyword: 'filter', - parentheses: true, - where: wc, + keyword: 'window', + type: 'window', + expr: l, } } -aggr_func - = e:(aggr_fun_count / aggr_fun_smma / aggr_array_agg) __ f:aggr_filter? { - // => { type: 'aggr_func'; name: string; args: { expr: additive_expr } | count_arg; over: over_partition; filter?: aggr_filter; } - if (f) e.filter = f - return e - } - -window_func - = window_fun_rank - / window_fun_laglead - / window_fun_firstlast +named_window_expr_list + = head:named_window_expr tail:(__ COMMA __ named_window_expr)* { + // => named_window_expr[] + return createList(head, tail); + } -window_fun_rank - = name:KW_WIN_FNS_RANK __ LPAREN __ RPAREN __ over:over_partition { - // => { type: 'window_func'; name: string; over: over_partition } +named_window_expr + = nw:ident_name __ KW_AS __ anw:as_window_specification { + // => { name: ident_name; as_window_specification: as_window_specification; } return { - type: 'window_func', - name: name, - over: over + name: nw, + as_window_specification: anw, } } -window_fun_laglead - = name:KW_LAG_LEAD __ LPAREN __ l:expr_list __ RPAREN __ cn:consider_nulls_clause? __ over:over_partition { - // => { type: 'window_func'; name: string; args: expr_list; consider_nulls: null | string; over: over_partition } +as_window_specification + = ident_name + / LPAREN __ ws:window_specification? __ RPAREN { return { - type: 'window_func', - name: name, - args: l, - over: over, - consider_nulls: cn - }; + window_specification: ws || {}, + parentheses: true + } } -window_fun_firstlast - = name:KW_FIRST_LAST_VALUE __ LPAREN __ l:expr __ cn:consider_nulls_clause? __ RPAREN __ over:over_partition { - // => window_fun_laglead +window_specification + = bc:partition_by_clause? __ l:order_by_clause? __ w:window_frame_clause? { return { - type: 'window_func', - name: name, - args: { - type: 'expr_list', value: [l] - }, - over: over, - consider_nulls: cn - }; + name: null, + partitionby: bc, + orderby: l, + window_frame_clause: w, + } } -KW_FIRST_LAST_VALUE - = 'FIRST_VALUE'i / 'LAST_VALUE'i +window_specification_frameless + = bc:partition_by_clause? __ l:order_by_clause? { + return { + name: null, + partitionby: bc, + orderby: l, + window_frame_clause: null + } + } -KW_WIN_FNS_RANK - = 'ROW_NUMBER'i / 'DENSE_RANK'i / 'RANK'i - // / 'CUME_DIST'i / 'MEDIAN'i / 'PERCENT_RANK'i - // / 'PERCENTILE_CONT'i / 'PERCENTILE_DISC'i / 'RATIO_TO_REPORT'i +window_frame_clause + = kw:KW_ROWS __ s:(window_frame_following / window_frame_preceding) { + // => string + return `rows ${s.value}` + } + / KW_ROWS __ KW_BETWEEN __ p:window_frame_preceding __ KW_AND __ f:window_frame_following { + // => string + return `rows between ${p.value} and ${f.value}` + } -KW_LAG_LEAD - = 'LAG'i / 'LEAD'i / 'NTH_VALUE'i +window_frame_following + = s:window_frame_value __ 'FOLLOWING'i { + // => string + s.value += ' FOLLOWING' + return s + } + / window_frame_current_row -consider_nulls_clause - = v:('IGNORE'i / 'RESPECT'i) __ 'NULLS'i { +window_frame_preceding + = s:window_frame_value __ 'PRECEDING'i { // => string - return v.toUpperCase() + ' NULLS' + s.value += ' PRECEDING' + return s + } + / window_frame_current_row + +window_frame_current_row + = 'CURRENT'i __ 'ROW'i { + return { type: 'single_quote_string', value: 'current row', ...getLocationObject() } } -aggr_fun_smma - = name:KW_SUM_MAX_MIN_AVG __ LPAREN __ e:additive_expr __ RPAREN __ bc:over_partition? { - // => { type: 'aggr_func'; name: 'SUM' | 'MAX' | 'MIN' | 'AVG'; args: { expr: additive_expr }; over: over_partition } - return { - type: 'aggr_func', - name: name, - args: { - expr: e - }, - over: bc, - ...getLocationObject(), - }; - } - -KW_SUM_MAX_MIN_AVG - = KW_SUM / KW_MAX / KW_MIN / KW_AVG +window_frame_value + = s:'UNBOUNDED'i { + return { type: 'single_quote_string', value: s.toUpperCase(), ...getLocationObject() } + } + / literal_numeric aggr_fun_count = name:(KW_COUNT / KW_GROUP_CONCAT) __ LPAREN __ arg:count_arg __ RPAREN __ bc:over_partition? { - // => { type: 'aggr_func'; name: 'COUNT' | 'GROUP_CONCAT'; args:count_arg; over: over_partition } return { type: 'aggr_func', name: name, args: arg, - over: bc + over: bc, + ...getLocationObject(), }; } - / name:('percentile_cont'i / 'percentile_disc'i) __ LPAREN __ arg:(literal_numeric / literal_array) __ RPAREN __ 'within'i __ KW_GROUP __ LPAREN __ or:order_by_clause __ RPAREN __ bc:over_partition? { - // => { type: 'aggr_func'; name: 'PERCENTILE_CONT' | 'PERCENTILE_DISC'; args: literal_numeric | literal_array; within_group_orderby: order_by_clause; over?: over_partition } - return { - type: 'aggr_func', - name: name.toUpperCase(), - args: { - expr: arg - }, - within_group_orderby: or, - over: bc - }; - } - / name:('mode'i) __ LPAREN __ RPAREN __ 'within'i __ KW_GROUP __ LPAREN __ or:order_by_clause __ RPAREN __ bc:over_partition? { - // => { type: 'aggr_func'; name: 'MODE'; args: literal_numeric | literal_array; within_group_orderby: order_by_clause; over?: over_partition } - return { - type: 'aggr_func', - name: name.toUpperCase(), - args: { expr: {} }, - within_group_orderby: or, - over: bc - }; - } concat_separator - = kw:COMMA __ s:literal_string { - // => { symbol: ','; delimiter: literal_string; } + = kw:'SEPARATOR'i? __ s:literal_string { return { - symbol: kw, - delimiter: s + keyword: kw, + value: s } } -distinct_args - = d:KW_DISTINCT? __ LPAREN __ c:expr __ RPAREN __ tail:(__ (KW_AND / KW_OR) __ expr)* __ s:concat_separator? __ or:order_by_clause? { - /* => { distinct: 'DISTINCT'; expr: expr; orderby?: order_by_clause; separator?: concat_separator; } */ - const len = tail.length - let result = c - result.parentheses = true - for (let i = 0; i < len; ++i) { - result = createBinaryExpr(tail[i][1], result, tail[i][3]) - } +count_arg + = e:star_expr { return { expr: e, ...getLocationObject() }; } + / d:KW_DISTINCT? __ c:or_and_where_expr __ or:order_by_clause? __ s:concat_separator? { return { distinct: d, - expr: result, + expr: c, orderby: or, - separator: s + separator: s, + ...getLocationObject() }; } - / d:KW_DISTINCT? __ c:or_and_expr __ s:concat_separator? __ or:order_by_clause? { - /* => { distinct: 'DISTINCT'; expr: expr; orderby?: order_by_clause; separator?: concat_separator; } */ - return { distinct: d, expr: c, orderby: or, separator: s }; - } -count_arg - = e:star_expr { /* => { expr: star_expr } */ return { expr: e }; } - / distinct_args +star_expr + = "*" { return { type: 'star', value: '*' }; } -aggr_array_agg - = pre:(ident __ DOT)? __ name:(KW_ARRAY_AGG / KW_STRING_AGG) __ LPAREN __ arg:distinct_args __ RPAREN { - // => { type: 'aggr_func'; args:count_arg; name: 'ARRAY_AGG' | 'STRING_AGG'; } - return { - type: 'aggr_func', - name: pre ? `${pre[0]}.${name}` : name, - args: arg, - }; +convert_args + = c:proc_additive_expr __ COMMA __ ch:(character_string_type / datetime_type) __ cs:create_option_character_set_kw __ v:ident_without_kw_type { + const { dataType, length } = ch + let dataTypeStr = dataType + if (length !== undefined) dataTypeStr = `${dataTypeStr}(${length})` + return { + type: 'expr_list', + value: [ + c, + { + type: 'origin', + value: dataTypeStr, + suffix: { + prefix: cs, + ...v, + } + }, + ] } - -star_expr - = "*" { /* => { type: 'star'; value: '*' } */ return { type: 'star', value: '*' }; } + } + / c:proc_additive_expr __ COMMA __ d:(signedness / data_type) { + const dataType = typeof d === 'string' ? { dataType: d } : d + return { + type: 'expr_list', + value: [c, { type: 'datatype', ...dataType, }] + } + } + / c:or_and_where_expr __ KW_USING __ d:ident_name { + c.suffix = `USING ${d.toUpperCase()}` + return { + type: 'expr_list', + value: [c] + } + } +extract_filed + = f:( + 'YEAR_MONTH'i / 'DAY_HOUR'i / 'DAY_MINUTE'i / 'DAY_SECOND'i / 'DAY_MICROSECOND'i / 'HOUR_MINUTE'i / 'HOUR_SECOND'i/ 'HOUR_MICROSECOND'i / 'MINUTE_SECOND'i / 'MINUTE_MICROSECOND'i / 'SECOND_MICROSECOND'i / 'TIMEZONE_HOUR'i / 'TIMEZONE_MINUTE'i + / 'CENTURY'i / 'DAY'i / 'DATE'i / 'DECADE'i / 'DOW'i / 'DOY'i / 'EPOCH'i / 'HOUR'i / 'ISODOW'i / 'ISOWEEK'i / 'ISOYEAR'i / 'MICROSECONDS'i / 'MILLENNIUM'i / 'MILLISECONDS'i / 'MINUTE'i / 'MONTH'i / 'QUARTER'i / 'SECOND'i / 'TIME'i / 'TIMEZONE'i / 'WEEK'i / 'YEAR'i + ) { + return f + } +extract_func + = kw:KW_EXTRACT __ LPAREN __ f:extract_filed __ KW_FROM __ t:(KW_TIMESTAMP / KW_INTERVAL / KW_TIME / KW_DATE) __ s:expr __ RPAREN { + return { + type: kw.toLowerCase(), + args: { + field: f, + cast_type: t, + source: s, + }, + ...getLocationObject(), + } + } + / kw:KW_EXTRACT __ LPAREN __ f:extract_filed __ KW_FROM __ s:expr __ RPAREN { + return { + type: kw.toLowerCase(), + args: { + field: f, + source: s, + }, + ...getLocationObject(), + } + } + / 'DATE_TRUNC'i __ LPAREN __ e:expr __ COMMA __ f:extract_filed __ RPAREN { + return { + type: 'function', + name: { name: [{ type: 'origin', value: 'date_trunc' }]}, + args: { type: 'expr_list', value: [e, { type: 'origin', value: f }] }, + over: null, + ...getLocationObject(), + }; + } trim_position = 'BOTH'i / 'LEADING'i / 'TRAILING'i trim_rem = p:trim_position? __ rm:literal_string? __ k:KW_FROM { - // => expr_list let value = [] if (p) value.push({type: 'origin', value: p }) if (rm) value.push(rm) @@ -4855,60 +3506,36 @@ trim_rem trim_func_clause = 'trim'i __ LPAREN __ tr:trim_rem? __ s:expr __ RPAREN { - // => { type: 'function'; name: proc_func_name; args: expr_list; } let args = tr || { type: 'expr_list', value: [] } args.value.push(s) return { type: 'function', - name: { name: [{ type: 'origin', value: 'trim' }] }, + name: { name: [{ type: 'origin', value: 'trim' }]}, args, ...getLocationObject(), }; } -tablefunc_clause - = name:('crosstab'i / 'jsonb_to_recordset'i / 'jsonb_to_record'i / 'json_to_recordset'i / 'json_to_record'i) __ LPAREN __ s:expr_list __ RPAREN __ d:(KW_AS __ ident_name __ LPAREN __ column_data_type_list __ RPAREN)? { - // => { type: 'tablefunc'; name: proc_func_name; args: expr_list; as: func_call } - return { - type: 'tablefunc', - name: { name: [{ type: 'default', value: name }] }, - args: s, - as: d && { - type: 'function', - name: { name: [{ type: 'default', value: d[2] }]}, - args: { type: 'expr_list', value: d[6].map(v => ({ ...v, type: 'column_definition' })) }, - ...getLocationObject(), - }, - ...getLocationObject(), - } - } - func_call - = trim_func_clause / tablefunc_clause - / name:'now'i __ LPAREN __ l:expr_list? __ RPAREN __ 'at'i __ KW_TIME __ 'zone'i __ z:literal_string { - // => { type: 'function'; name: proc_func_name; args: expr_list; suffix: literal_string; } - z.prefix = 'at time zone' - return { + = extract_func / trim_func_clause + / 'convert'i __ LPAREN __ l:convert_args __ RPAREN { + return { type: 'function', - name: { name: [{ type: 'default', value: name }] }, - args: l ? l: { type: 'expr_list', value: [] }, - suffix: z, + name: { name: [{ type: 'origin', value: 'convert' }] }, + args: l, ...getLocationObject(), - }; - } + }; + } / name:scalar_func __ LPAREN __ l:expr_list? __ RPAREN __ bc:over_partition? { - // => { type: 'function'; name: proc_func_name; args: expr_list; over?: over_partition; } return { type: 'function', - name: { name: [{ type: 'origin', value: name }] }, + name: { name: [{ type: 'default', value: name }] }, args: l ? l: { type: 'expr_list', value: [] }, over: bc, ...getLocationObject(), }; } - / extract_func / f:scalar_time_func __ up:on_update_current_timestamp? { - // => { type: 'function'; name: proc_func_name; over?: on_update_current_timestamp; } return { type: 'function', name: { name: [{ type: 'origin', value: f }] }, @@ -4916,87 +3543,54 @@ func_call ...getLocationObject(), } } - / name:proc_func_name __ LPAREN __ l:or_and_where_expr? __ RPAREN { - // => { type: 'function'; name: proc_func_name; args: expr_list; } - if (l && l.type !== 'expr_list') l = { type: 'expr_list', value: [l] } + / name:proc_func_name &{ return !reservedFunctionName[name.name[0] && name.name[0].value.toLowerCase()] } __ LPAREN __ l:or_and_where_expr? __ RPAREN __ bc:over_partition? { + if (l && l.type !== 'expr_list') l = { type: 'expr_list', value: [l] } + if (((name.name[0] && name.name[0].value.toUpperCase() === 'TIMESTAMPDIFF') || (name.name[0] && name.name[0].value.toUpperCase() === 'TIMESTAMPADD')) && l.value && l.value[0]) l.value[0] = { type: 'origin', value: l.value[0].column } return { type: 'function', name: name, args: l ? l: { type: 'expr_list', value: [] }, + over: bc, ...getLocationObject(), }; } - -extract_filed - = f:('CENTURY'i / 'DAY'i / 'DATE'i / 'DECADE'i / 'DOW'i / 'DOY'i / 'EPOCH'i / 'HOUR'i / 'ISODOW'i / 'ISOYEAR'i / 'MICROSECONDS'i / 'MILLENNIUM'i / 'MILLISECONDS'i / 'MINUTE'i / 'MONTH'i / 'QUARTER'i / 'SECOND'i / 'TIMEZONE'i / 'TIMEZONE_HOUR'i / 'TIMEZONE_MINUTE'i / 'WEEK'i / 'YEAR'i) { - // => 'string' - return f - } -extract_func - = kw:KW_EXTRACT __ LPAREN __ f:extract_filed __ KW_FROM __ t:(KW_TIMESTAMP / KW_INTERVAL / KW_TIME / KW_DATE)? __ s:expr __ RPAREN { - // => { type: 'extract'; args: { field: extract_filed; cast_type: 'TIMESTAMP' | 'INTERVAL' | 'TIME'; source: expr; }} - return { - type: kw.toLowerCase(), - args: { - field: f, - cast_type: t, - source: s, - }, - ...getLocationObject(), - } - } - / kw:KW_EXTRACT __ LPAREN __ f:extract_filed __ KW_FROM __ s:expr __ RPAREN { - // => { type: 'extract'; args: { field: extract_filed; source: expr; }} - return { - type: kw.toLowerCase(), - args: { - field: f, - source: s, - }, - ...getLocationObject(), - } - } - scalar_time_func = KW_CURRENT_DATE / KW_CURRENT_TIME / KW_CURRENT_TIMESTAMP - scalar_func = scalar_time_func / KW_CURRENT_USER / KW_USER / KW_SESSION_USER / KW_SYSTEM_USER - / "NTILE"i - -cast_double_colon - = s:KW_DOUBLE_COLON __ t:data_type __ alias:alias_clause? { - /* => { - as?: alias_clause, - symbol: '::' | 'as', - target: data_type; - } - */ + +cast_expr + = c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ ch:character_string_type __ cs:create_option_character_set_kw __ v:ident_without_kw_type __ RPAREN { + const { dataType, length } = ch + let dataTypeStr = dataType + if (length !== undefined) dataTypeStr = `${dataTypeStr}(${length})` return { - as: alias, - symbol: '::', - target: t, - } + type: 'cast', + keyword: c.toLowerCase(), + expr: e, + symbol: 'as', + target: { + dataType: dataTypeStr, + suffix: [{ type: 'origin', value: cs }, v], + }, + }; } -cast_expr - = c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ t:data_type __ RPAREN { - // => IGNORE + / c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ t:data_type __ RPAREN { return { type: 'cast', keyword: c.toLowerCase(), expr: e, symbol: 'as', - target: t, + target: t }; } / c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ KW_DECIMAL __ LPAREN __ precision:int __ RPAREN __ RPAREN { - // => IGNORE return { type: 'cast', keyword: c.toLowerCase(), @@ -5008,7 +3602,6 @@ cast_expr }; } / c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ KW_DECIMAL __ LPAREN __ precision:int __ COMMA __ scale:int __ RPAREN __ RPAREN { - // => IGNORE return { type: 'cast', keyword: c.toLowerCase(), @@ -5020,97 +3613,44 @@ cast_expr }; } / c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ s:signedness __ t:KW_INTEGER? __ RPAREN { /* MySQL cast to un-/signed integer */ - // => IGNORE return { type: 'cast', keyword: c.toLowerCase(), expr: e, symbol: 'as', target: { - dataType: s + (t ? ' ' + t: '') + dataType: [s, t].filter(Boolean).join(' ') } }; } - / LPAREN __ e:(or_expr / column_ref_array_index / param) __ RPAREN __ c:cast_double_colon? { - /* => { - type: 'cast'; - expr: or_expr | column_ref | param - | expr; - keyword: 'cast'; - } & cast_double_colon - */ - e.parentheses = true - if (!c) return e - return { - type: 'cast', - keyword: 'cast', - expr: e, - ...c, - } - } - / e:(column_ref_quoted / literal / aggr_func / window_func / func_call / case_expr / interval_expr / column_ref_array_index / param) __ c:cast_double_colon? { - /* => ({ - type: 'cast'; - expr: literal | jsonb_expr | aggr_func | func_call | case_expr | interval_expr | column_ref | param - | expr; - keyword: 'cast'; - } & cast_double_colon) - */ - if (!c) return e - return { - type: 'cast', - keyword: 'cast', - expr: e, - ...c, - } - } - signedness = KW_SIGNED / KW_UNSIGNED literal - = literal_string + = b:('binary'i / '_binary'i)? __ s:literal_string ca:(__ collate_expr)? { + if (b) s.prefix = b.toLowerCase() + if (ca) s.suffix = { collate: ca[1] } + return s + } / literal_numeric / literal_bool / literal_null / literal_datetime - / literal_array - -literal_array - = s:KW_ARRAY __ LBRAKE __ c:expr_list? __ RBRAKE { - /* - => { - expr_list: expr_list | {type: 'origin', value: ident }, - type: string, - keyword: string, - brackets: boolean - } - */ - return { - expr_list: c || { type: 'origin', value: '' }, - type: 'array', - keyword: 'array', - brackets: true - } - } literal_list = head:literal tail:(__ COMMA __ literal)* { - // => literal[] return createList(head, tail); } literal_null = KW_NULL { - // => { type: 'null'; value: null } return { type: 'null', value: null }; } literal_not_null = KW_NOT_NULL { - // => { type: 'not null'; value: 'not null' } return { type: 'not null', value: 'not null', @@ -5119,34 +3659,48 @@ literal_not_null literal_bool = KW_TRUE { - // => { type: 'bool', value: true } return { type: 'bool', value: true }; } / KW_FALSE { - //=> { type: 'bool', value: false } return { type: 'bool', value: false }; } + literal_string - = ca:("'" single_char* "'") [\n]+ __ fs:("'" single_char* "'") { - // => { type: 'single_quote_string'; value: string; } + = b:('_binary'i / '_latin1'i)? __ r:'X'i ca:("'" [0-9A-Fa-f]* "'") { return { - type: 'single_quote_string', - value: `${ca[1].join('')}${fs[1].join('')}` + type: 'hex_string', + prefix: b, + value: ca[1].join('') + }; + } + / b:('_binary'i / '_latin1'i)? __ r:'b'i ca:("'" [0-9A-Fa-f]* "'") { + return { + type: 'bit_string', + prefix: b, + value: ca[1].join('') }; } + / b:('_binary'i / '_latin1'i)? __ r:'0x'i ca:([0-9A-Fa-f]*) { + return { + type: 'full_hex_string', + prefix: b, + value: ca.join('') + }; + } + / r:'N'i ca:("'" single_char* "'") { + return { + type: 'natural_string', + value: ca[1].join('') + }; + } / ca:("'" single_char* "'") { - // => { type: 'single_quote_string'; value: string; } return { type: 'single_quote_string', value: ca[1].join('') }; } - / literal_double_quoted_string - -literal_double_quoted_string - = ca:("\"" single_quote_char* "\"") !DOT { - // => { type: 'string'; value: string; } + / ca:("\"" single_quote_char* "\"") { return { type: 'double_quote_string', value: ca[1].join('') @@ -5155,14 +3709,12 @@ literal_double_quoted_string literal_datetime = type:(KW_TIME / KW_DATE / KW_TIMESTAMP / KW_DATETIME) __ ca:("'" single_char* "'") { - // => { type: 'TIME' | 'DATE' | 'TIMESTAMP' | 'DATETIME', value: string } return { type: type.toLowerCase(), value: ca[1].join('') }; } / type:(KW_TIME / KW_DATE / KW_TIMESTAMP / KW_DATETIME) __ ca:("\"" single_quote_char* "\"") { - // => { type: 'TIME' | 'DATE' | 'TIMESTAMP' | 'DATETIME', value: string } return { type: type.toLowerCase(), value: ca[1].join('') @@ -5174,7 +3726,7 @@ single_quote_char / escape_char single_char - = [^'\\] + = [^'\\] // remove \0-\x1F\x7f pnCtrl char [^'\\\0-\x1F\x7f] / escape_char escape_char @@ -5192,36 +3744,35 @@ escape_char } / "\\" { return "\\"; } / "''" { return "''" } + / '""' { return '""' } + / '``' { return '``' } line_terminator = [\n\r] literal_numeric = n:number { - // => number | { type: 'bigint'; value: string; } if (n && n.type === 'bigint') return n return { type: 'number', value: n }; } number - = int_:int? frac:frac exp:exp { - const numStr = (int_ || '') + frac + exp + = int_:int frac:frac exp:exp { + const numStr = int_ + frac + exp return { type: 'bigint', value: numStr } } - / int_:int? frac:frac { - // => IGNORE - const numStr = (int_ || '') + frac - if (int_ && isBigInt(int_)) return { + / int_:int frac:frac { + const numStr = int_ + frac + if (isBigInt(int_)) return { type: 'bigint', value: numStr } return parseFloat(numStr); } / int_:int exp:exp { - // => IGNORE const numStr = int_ + exp return { type: 'bigint', @@ -5229,7 +3780,6 @@ number } } / int_:int { - // => IGNORE if (isBigInt(int_)) return { type: 'bigint', value: int_ @@ -5241,10 +3791,13 @@ int = digits / digit:digit / op:("-" / "+" ) digits:digits { return op + digits; } - / op:("-" / "+" ) digit:digit { return op + digit; } + / op:("-" / "+" ) digit:digit { return op + digit; } frac - = "." digits:digits { return "." + digits; } + = "." digits:digits? { + if (!digits) return '' + return "." + digits; + } exp = e:e digits:digits { return e + digits; } @@ -5252,7 +3805,7 @@ exp digits = digits:digit+ { return digits.join(""); } -digit = [0-9] +digit = [0-9] hexDigit = [0-9a-fA-F] @@ -5276,12 +3829,10 @@ KW_SELECT = "SELECT"i !ident_start KW_UPDATE = "UPDATE"i !ident_start KW_CREATE = "CREATE"i !ident_start KW_TEMPORARY = "TEMPORARY"i !ident_start -KW_TEMP = "TEMP"i !ident_start KW_DELETE = "DELETE"i !ident_start KW_INSERT = "INSERT"i !ident_start -KW_RECURSIVE= "RECURSIVE" !ident_start { return 'RECURSIVE'; } +KW_RECURSIVE= "RECURSIVE" !ident_start KW_REPLACE = "REPLACE"i !ident_start -KW_RETURNING = "RETURNING"i !ident_start { return 'RETURNING' } KW_RENAME = "RENAME"i !ident_start KW_IGNORE = "IGNORE"i !ident_start KW_EXPLAIN = "EXPLAIN"i !ident_start @@ -5290,45 +3841,47 @@ KW_PARTITION = "PARTITION"i !ident_start { return 'PARTITION' } KW_INTO = "INTO"i !ident_start KW_FROM = "FROM"i !ident_start KW_SET = "SET"i !ident_start { return 'SET' } +KW_UNLOCK = "UNLOCK"i !ident_start KW_LOCK = "LOCK"i !ident_start KW_AS = "AS"i !ident_start KW_TABLE = "TABLE"i !ident_start { return 'TABLE'; } +KW_TRIGGER = "TRIGGER"i !ident_start { return 'TRIGGER'; } +KW_TABLES = "TABLES"i !ident_start { return 'TABLES'; } KW_DATABASE = "DATABASE"i !ident_start { return 'DATABASE'; } KW_SCHEMA = "SCHEMA"i !ident_start { return 'SCHEMA'; } -KW_SEQUENCE = "SEQUENCE"i !ident_start { return 'SEQUENCE'; } -KW_TABLESPACE = "TABLESPACE"i !ident_start { return 'TABLESPACE'; } KW_COLLATE = "COLLATE"i !ident_start { return 'COLLATE'; } -KW_COLLATION = "COLLATION"i !ident_start { return 'COLLATION'; } -KW_DEALLOCATE = "DEALLOCATE"i !ident_start { return 'DEALLOCATE'; } KW_ON = "ON"i !ident_start KW_LEFT = "LEFT"i !ident_start KW_RIGHT = "RIGHT"i !ident_start KW_FULL = "FULL"i !ident_start KW_INNER = "INNER"i !ident_start +KW_CROSS = "CROSS"i !ident_start KW_JOIN = "JOIN"i !ident_start KW_OUTER = "OUTER"i !ident_start +KW_OVER = "OVER"i !ident_start KW_UNION = "UNION"i !ident_start -KW_INTERSECT = "INTERSECT"i !ident_start -KW_EXCEPT = "EXCEPT"i !ident_start +KW_MINUS = "MINUS"i !ident_start +KW_INTERSECT = "INTERSECT"i !ident_start KW_VALUES = "VALUES"i !ident_start KW_USING = "USING"i !ident_start KW_WHERE = "WHERE"i !ident_start KW_WITH = "WITH"i !ident_start +KW_GO = "GO"i !ident_start { return 'GO'; } KW_GROUP = "GROUP"i !ident_start KW_BY = "BY"i !ident_start KW_ORDER = "ORDER"i !ident_start KW_HAVING = "HAVING"i !ident_start -KW_WINDOW = "WINDOW"i !ident_start KW_LIMIT = "LIMIT"i !ident_start -KW_OFFSET = "OFFSET"i !ident_start { return 'OFFSET' } +KW_OFFSET = "OFFSET"i !ident_start { return 'OFFSET'; } KW_ASC = "ASC"i !ident_start { return 'ASC'; } KW_DESC = "DESC"i !ident_start { return 'DESC'; } +KW_DESCRIBE = "DESCRIBE"i !ident_start { return 'DESCRIBE'; } KW_ALL = "ALL"i !ident_start { return 'ALL'; } KW_DISTINCT = "DISTINCT"i !ident_start { return 'DISTINCT';} @@ -5337,16 +3890,14 @@ KW_BETWEEN = "BETWEEN"i !ident_start { return 'BETWEEN'; } KW_IN = "IN"i !ident_start { return 'IN'; } KW_IS = "IS"i !ident_start { return 'IS'; } KW_LIKE = "LIKE"i !ident_start { return 'LIKE'; } -KW_ILIKE = "ILIKE"i !ident_start { return 'ILIKE'; } -KW_EXISTS = "EXISTS"i !ident_start { /* => 'EXISTS' */ return 'EXISTS'; } +KW_RLIKE = "RLIKE"i !ident_start { return 'RLIKE'; } +KW_REGEXP = "REGEXP"i !ident_start { return 'REGEXP'; } +KW_EXISTS = "EXISTS"i !ident_start { return 'EXISTS'; } KW_NOT = "NOT"i !ident_start { return 'NOT'; } KW_AND = "AND"i !ident_start { return 'AND'; } KW_OR = "OR"i !ident_start { return 'OR'; } -KW_ARRAY = "ARRAY"i !ident_start { return 'ARRAY'; } -KW_ARRAY_AGG = "ARRAY_AGG"i !ident_start { return 'ARRAY_AGG'; } -KW_STRING_AGG = "STRING_AGG"i !ident_start { return 'STRING_AGG'; } KW_COUNT = "COUNT"i !ident_start { return 'COUNT'; } KW_GROUP_CONCAT = "GROUP_CONCAT"i !ident_start { return 'GROUP_CONCAT'; } KW_MAX = "MAX"i !ident_start { return 'MAX'; } @@ -5366,10 +3917,10 @@ KW_END = "END"i !ident_start KW_CAST = "CAST"i !ident_start { return 'CAST' } KW_TRY_CAST = "TRY_CAST"i !ident_start { return 'TRY_CAST' } -KW_BOOL = "BOOL"i !ident_start { return 'BOOL'; } -KW_BOOLEAN = "BOOLEAN"i !ident_start { return 'BOOLEAN'; } +KW_BINARY = "BINARY"i !ident_start { return 'BINARY'; } +KW_VARBINARY = "VARBINARY"i !ident_start { return 'VARBINARY'; } +KW_BIT = "BIT"i !ident_start { return 'BIT'; } KW_CHAR = "CHAR"i !ident_start { return 'CHAR'; } -KW_CHARACTER = "CHARACTER"i !ident_start { return 'CHARACTER'; } KW_VARCHAR = "VARCHAR"i !ident_start { return 'VARCHAR';} KW_NUMERIC = "NUMERIC"i !ident_start { return 'NUMERIC'; } KW_DECIMAL = "DECIMAL"i !ident_start { return 'DECIMAL'; } @@ -5379,57 +3930,55 @@ KW_INT = "INT"i !ident_start { return 'INT'; } KW_ZEROFILL = "ZEROFILL"i !ident_start { return 'ZEROFILL'; } KW_INTEGER = "INTEGER"i !ident_start { return 'INTEGER'; } KW_JSON = "JSON"i !ident_start { return 'JSON'; } -KW_JSONB = "JSONB"i !ident_start { return 'JSONB'; } -KW_GEOMETRY = "GEOMETRY"i !ident_start { return 'GEOMETRY'; } KW_SMALLINT = "SMALLINT"i !ident_start { return 'SMALLINT'; } -KW_SERIAL = "SERIAL"i !ident_start { return 'SERIAL'; } +KW_MEDIUMINT = "MEDIUMINT"i !ident_start { return 'MEDIUMINT'; } KW_TINYINT = "TINYINT"i !ident_start { return 'TINYINT'; } KW_TINYTEXT = "TINYTEXT"i !ident_start { return 'TINYTEXT'; } KW_TEXT = "TEXT"i !ident_start { return 'TEXT'; } KW_MEDIUMTEXT = "MEDIUMTEXT"i !ident_start { return 'MEDIUMTEXT'; } KW_LONGTEXT = "LONGTEXT"i !ident_start { return 'LONGTEXT'; } -KW_MEDIUMINT = "MEDIUMINT"i !ident_start { return 'MEDIUMINT'; } KW_BIGINT = "BIGINT"i !ident_start { return 'BIGINT'; } KW_ENUM = "ENUM"i !ident_start { return 'ENUM'; } KW_FLOAT = "FLOAT"i !ident_start { return 'FLOAT'; } KW_DOUBLE = "DOUBLE"i !ident_start { return 'DOUBLE'; } -KW_BIGSERIAL = "BIGSERIAL"i !ident_start { return 'BIGSERIAL'; } -KW_REAL = "REAL"i !ident_start { return 'REAL'; } KW_DATE = "DATE"i !ident_start { return 'DATE'; } KW_DATETIME = "DATETIME"i !ident_start { return 'DATETIME'; } KW_ROWS = "ROWS"i !ident_start { return 'ROWS'; } KW_TIME = "TIME"i !ident_start { return 'TIME'; } -KW_TIMESTAMP = "TIMESTAMP"i!ident_start { return 'TIMESTAMP'; } -KW_TIMESTAMPTZ = "TIMESTAMPTZ"i!ident_start { return 'TIMESTAMPTZ'; } +KW_TIMESTAMP = "TIMESTAMP"i !ident_start { return 'TIMESTAMP'; } +KW_YEAR = "YEAR"i !ident_start { return 'YEAR'; } KW_TRUNCATE = "TRUNCATE"i !ident_start { return 'TRUNCATE'; } KW_USER = "USER"i !ident_start { return 'USER'; } -KW_UUID = "UUID"i !ident_start { return 'UUID'; } -KW_OID = "OID"i !ident_start { return 'OID'; } -KW_REGCLASS = "REGCLASS"i !ident_start { return 'REGCLASS'; } -KW_REGCOLLATION = "REGCOLLATION"i !ident_start { return 'REGCOLLATION'; } -KW_REGCONFIG = "REGCONFIG"i !ident_start { return 'REGCONFIG'; } -KW_REGDICTIONARY = "REGDICTIONARY"i !ident_start { return 'REGDICTIONARY'; } -KW_REGNAMESPACE = "REGNAMESPACE"i !ident_start { return 'REGNAMESPACE'; } -KW_REGOPER = "REGOPER"i !ident_start { return 'REGOPER'; } -KW_REGOPERATOR = "REGOPERATOR"i !ident_start { return 'REGOPERATOR'; } -KW_REGPROC = "REGPROC"i !ident_start { return 'REGPROC'; } -KW_REGPROCEDURE = "REGPROCEDURE"i !ident_start { return 'REGPROCEDURE'; } -KW_REGROLE = "REGROLE"i !ident_start { return 'REGROLE'; } -KW_REGTYPE = "REGTYPE"i !ident_start { return 'REGTYPE'; } KW_CURRENT_DATE = "CURRENT_DATE"i !ident_start { return 'CURRENT_DATE'; } KW_ADD_DATE = "ADDDATE"i !ident_start { return 'ADDDATE'; } + KW_INTERVAL = "INTERVAL"i !ident_start { return 'INTERVAL'; } KW_UNIT_YEAR = "YEAR"i !ident_start { return 'YEAR'; } +KW_UNIT_QUARTER = "QUARTER"i !ident_start { return 'QUARTER'; } KW_UNIT_MONTH = "MONTH"i !ident_start { return 'MONTH'; } +KW_UNIT_WEEK = "WEEK"i !ident_start { return 'WEEK'; } KW_UNIT_DAY = "DAY"i !ident_start { return 'DAY'; } KW_UNIT_HOUR = "HOUR"i !ident_start { return 'HOUR'; } KW_UNIT_MINUTE = "MINUTE"i !ident_start { return 'MINUTE'; } KW_UNIT_SECOND = "SECOND"i !ident_start { return 'SECOND'; } +KW_UNIT_MICROSECOND = "MICROSECOND"i !ident_start { return 'MICROSECOND'; } + +KW_UNIT_SECOND_MICROSECOND = "SECOND_MICROSECOND"i !ident_start { return 'SECOND_MICROSECOND'; } +KW_UNIT_MINUTE_MICROSECOND = "MINUTE_MICROSECOND"i !ident_start { return 'MINUTE_MICROSECOND'; } +KW_UNIT_MINUTE_SECOND = "MINUTE_SECOND"i !ident_start { return 'MINUTE_SECOND'; } +KW_UNIT_HOUR_MICROSECOND = "HOUR_MICROSECOND"i !ident_start { return 'HOUR_MICROSECOND'; } +KW_UNIT_HOUR_SECOND = "HOUR_SECOND"i !ident_start { return 'HOUR_SECOND'; } +KW_UNIT_HOUR_MINUTE = "HOUR_MINUTE"i !ident_start { return 'HOUR_MINUTE'; } +KW_UNIT_DAY_MICROSECOND = "DAY_MICROSECOND"i !ident_start { return 'DAY_MICROSECOND'; } +KW_UNIT_DAY_SECOND = "DAY_SECOND"i !ident_start { return 'DAY_SECOND'; } +KW_UNIT_DAY_MINUTE = "DAY_MINUTE"i !ident_start { return 'DAY_MINUTE'; } +KW_UNIT_DAY_HOUR = "DAY_HOUR"i !ident_start { return 'DAY_HOUR'; } +KW_UNIT_YEAR_MONTH = "YEAR_MONTH"i !ident_start { return 'YEAR_MONTH'; } + KW_CURRENT_TIME = "CURRENT_TIME"i !ident_start { return 'CURRENT_TIME'; } KW_CURRENT_TIMESTAMP= "CURRENT_TIMESTAMP"i !ident_start { return 'CURRENT_TIMESTAMP'; } KW_CURRENT_USER = "CURRENT_USER"i !ident_start { return 'CURRENT_USER'; } -KW_CURRENT_ROLE = "CURRENT_ROLE"i !ident_start { return 'CURRENT_ROLE'; } KW_SESSION_USER = "SESSION_USER"i !ident_start { return 'SESSION_USER'; } KW_SYSTEM_USER = "SYSTEM_USER"i !ident_start { return 'SYSTEM_USER'; } @@ -5440,15 +3989,22 @@ KW_PERSIST = "PERSIST"i !ident_start { return 'PERSIST'; } KW_PERSIST_ONLY = "PERSIST_ONLY"i !ident_start { return 'PERSIST_ONLY'; } KW_VIEW = "VIEW"i !ident_start { return 'VIEW'; } +KW_GEOMETRY = "GEOMETRY"i !ident_start { return 'GEOMETRY'; } +KW_POINT = "POINT"i !ident_start { return 'POINT'; } +KW_LINESTRING = "LINESTRING"i !ident_start { return 'LINESTRING'; } +KW_POLYGON = "POLYGON"i !ident_start { return 'POLYGON'; } +KW_MULTIPOINT = "MULTIPOINT"i !ident_start { return 'MULTIPOINT'; } +KW_MULTILINESTRING = "MULTILINESTRING"i !ident_start { return 'MULTILINESTRING'; } +KW_MULTIPOLYGON = "MULTIPOLYGON"i !ident_start { return 'MULTIPOLYGON'; } +KW_GEOMETRYCOLLECTION = "GEOMETRYCOLLECTION"i !ident_start { return 'GEOMETRYCOLLECTION'; } + KW_VAR__PRE_AT = '@' KW_VAR__PRE_AT_AT = '@@' KW_VAR_PRE_DOLLAR = '$' -KW_VAR_PRE_DOLLAR_DOUBLE = '$$' KW_VAR_PRE - = KW_VAR__PRE_AT_AT / KW_VAR__PRE_AT / KW_VAR_PRE_DOLLAR / KW_VAR_PRE_DOLLAR + = KW_VAR__PRE_AT_AT / KW_VAR__PRE_AT / KW_VAR_PRE_DOLLAR KW_RETURN = 'return'i KW_ASSIGN = ':=' -KW_DOUBLE_COLON = '::' KW_ASSIGIN_EQUAL = '=' KW_DUAL = "DUAL"i @@ -5457,6 +4013,7 @@ KW_DUAL = "DUAL"i KW_ADD = "ADD"i !ident_start { return 'ADD'; } KW_COLUMN = "COLUMN"i !ident_start { return 'COLUMN'; } KW_INDEX = "INDEX"i !ident_start { return 'INDEX'; } +KW_MODIFY = "MODIFY"i !ident_start { return 'MODIFY'; } KW_KEY = "KEY"i !ident_start { return 'KEY'; } KW_FULLTEXT = "FULLTEXT"i !ident_start { return 'FULLTEXT'; } KW_SPATIAL = "SPATIAL"i !ident_start { return 'SPATIAL'; } @@ -5464,11 +4021,8 @@ KW_UNIQUE = "UNIQUE"i !ident_start { return 'UNIQUE'; } KW_KEY_BLOCK_SIZE = "KEY_BLOCK_SIZE"i !ident_start { return 'KEY_BLOCK_SIZE'; } KW_COMMENT = "COMMENT"i !ident_start { return 'COMMENT'; } KW_CONSTRAINT = "CONSTRAINT"i !ident_start { return 'CONSTRAINT'; } -KW_CONCURRENTLY = "CONCURRENTLY"i !ident_start { return 'CONCURRENTLY'; } KW_REFERENCES = "REFERENCES"i !ident_start { return 'REFERENCES'; } - - // MySQL extensions to SQL OPT_SQL_CALC_FOUND_ROWS = "SQL_CALC_FOUND_ROWS"i OPT_SQL_CACHE = "SQL_CACHE"i @@ -5490,12 +4044,11 @@ RBRAKE = ']' SEMICOLON = ';' SINGLE_ARROW = '->' DOUBLE_ARROW = '->>' -WELL_ARROW = '#>' -DOUBLE_WELL_ARROW = '#>>' OPERATOR_CONCATENATION = '||' OPERATOR_AND = '&&' -LOGIC_OPERATOR = OPERATOR_CONCATENATION / OPERATOR_AND +OPERATOR_XOR = 'XOR'i !ident_start { return 'XOR' } +LOGIC_OPERATOR = OPERATOR_CONCATENATION / OPERATOR_AND / OPERATOR_XOR // separator __ @@ -5507,9 +4060,10 @@ ___ comment = block_comment / line_comment + / pound_sign_comment block_comment - = "/*" (!"*/" !"/*" char / block_comment)* "*/" + = "/*" (!"*/" char)* "*/" line_comment = "--" (!EOL char)* @@ -5519,7 +4073,6 @@ pound_sign_comment keyword_comment = k:KW_COMMENT __ s:KW_ASSIGIN_EQUAL? __ c:literal_string { - // => { type: 'comment'; keyword: 'comment'; symbol: '='; value: literal_string; } return { type: k.toLowerCase(), keyword: k.toLowerCase(), @@ -5532,11 +4085,25 @@ char = . interval_unit = KW_UNIT_YEAR + / KW_UNIT_QUARTER / KW_UNIT_MONTH + / KW_UNIT_WEEK / KW_UNIT_DAY / KW_UNIT_HOUR / KW_UNIT_MINUTE / KW_UNIT_SECOND + / KW_UNIT_MICROSECOND + / KW_UNIT_SECOND_MICROSECOND + / KW_UNIT_MINUTE_MICROSECOND + / KW_UNIT_MINUTE_SECOND + / KW_UNIT_HOUR_MICROSECOND + / KW_UNIT_HOUR_SECOND + / KW_UNIT_HOUR_MINUTE + / KW_UNIT_DAY_MICROSECOND + / KW_UNIT_DAY_SECOND + / KW_UNIT_DAY_MINUTE + / KW_UNIT_DAY_HOUR + / KW_UNIT_YEAR_MONTH whitespace = [ \t\n\r] @@ -5553,25 +4120,30 @@ proc_stmts proc_stmt = &{ varList = []; return true; } __ s:(assign_stmt / return_stmt) { - /* export interface proc_stmt_t { type: 'proc'; stmt: assign_stmt | return_stmt; vars: any } - => AstStatement - */ - return { type: 'proc', stmt: s, vars: varList } + return { stmt: s, vars: varList }; } assign_stmt_list = head:assign_stmt tail:(__ COMMA __ assign_stmt)* { - // => assign_stmt[] return createList(head, tail); } assign_stmt - = va:(var_decl / without_prefix_var_decl) __ s:(KW_ASSIGN / KW_ASSIGIN_EQUAL / KW_TO) __ e:proc_expr { - // => { type: 'assign'; left: var_decl | without_prefix_var_decl; symbol: ':=' | '='; right: proc_expr; } + = va:(var_decl / without_prefix_var_decl) __ s: (KW_ASSIGN / KW_ASSIGIN_EQUAL) __ e:proc_expr { + return { + type: 'assign', + left: va, + symbol: s, + right: e + }; + } + +select_assign_stmt + = va:(var_decl / without_prefix_var_decl) __ s:KW_ASSIGN __ e:proc_expr { return { type: 'assign', left: va, - symbol: Array.isArray(s) ? s[0] : s, + symbol: s, right: e }; } @@ -5579,7 +4151,6 @@ assign_stmt return_stmt = KW_RETURN __ e:proc_expr { - // => { type: 'return'; expr: proc_expr; } return { type: 'return', expr: e }; } @@ -5592,20 +4163,17 @@ proc_expr proc_additive_expr = head:proc_multiplicative_expr tail:(__ additive_operator __ proc_multiplicative_expr)* { - // => binary_expr return createBinaryExprChain(head, tail); } proc_multiplicative_expr = head:proc_primary tail:(__ multiplicative_operator __ proc_primary)* { - // => binary_expr return createBinaryExprChain(head, tail); } proc_join = lt:var_decl __ op:join_op __ rt:var_decl __ expr:on_clause { - // => { type: 'join'; ltable: var_decl; rtable: var_decl; op: join_op; expr: on_clause; } return { type: 'join', ltable: lt, @@ -5616,32 +4184,19 @@ proc_join } proc_primary - = literal + = proc_func_call_args + / literal / var_decl - / proc_func_call + / column_ref + / proc_fun_call_without_args / param / LPAREN __ e:proc_additive_expr __ RPAREN { - // => proc_additive_expr & { parentheses: true; } e.parentheses = true; return e; } - / n:ident_name s:(DOT __ ident_name)? { - // => { type: 'var'; prefix: null; name: number; members: []; quoted: null } | column_ref - if (!s) return { - type: 'var', - name: n, - prefix: null - } - return { - type: 'column_ref', - table: n, - column: s[2] - } - } proc_func_name - = dt:ident_without_kw_type tail:(__ DOT __ ident_without_kw_type)? { - // => { schema?: ident_without_kw_type, name: ident_without_kw_type } + = dt:(ident_name_type / backticks_quoted_ident) tail:(__ DOT __ ((ident_name_type / backticks_quoted_ident)))? { const result = { name: [dt] } if (tail !== null) { result.schema = dt @@ -5650,9 +4205,8 @@ proc_func_name return result } -proc_func_call +proc_func_call_args = name:proc_func_name __ LPAREN __ l:proc_primary_list? __ RPAREN { - // => { type: 'function'; name: string; args: null | { type: expr_list; value: proc_primary_list; }} //compatible with original func_call return { type: 'function', @@ -5664,47 +4218,35 @@ proc_func_call ...getLocationObject(), }; } +proc_fun_call_without_args + = name:proc_func_name { + return { + type: 'function', + name: name, + args: null, + ...getLocationObject(), + }; + } +proc_func_call + = proc_func_call_args / proc_fun_call_without_args proc_primary_list = head:proc_primary tail:(__ COMMA __ proc_primary)* { - // => proc_primary[] return createList(head, tail); } -proc_array - = LBRAKE __ l:proc_primary_list __ RBRAKE { - // => { type: 'array'; value: proc_primary_list } +proc_array = + LBRAKE __ l:proc_primary_list __ RBRAKE { return { type: 'array', value: l }; } var_decl_list = head:var_decl tail:(__ COMMA __ var_decl)* { - // => var_decl[] return createList(head, tail) } var_decl - = p:KW_VAR_PRE_DOLLAR_DOUBLE d:[^$]* s:KW_VAR_PRE_DOLLAR_DOUBLE { - // => { type: 'var'; name: string; prefix: string; suffix: string; } - return { - type: 'var', - name: d.join(''), - prefix: '$$', - suffix: '$$' - }; - } - / KW_VAR_PRE_DOLLAR f:column KW_VAR_PRE_DOLLAR d:[^$]* KW_VAR_PRE_DOLLAR s:column !{ if (f !== s) return true } KW_VAR_PRE_DOLLAR { - // => { type: 'var'; name: string; prefix: string; suffix: string; } - return { - type: 'var', - name: d.join(''), - prefix: `$${f}$`, - suffix: `$${s}$` - }; - } - / p:KW_VAR_PRE d: without_prefix_var_decl { - // => without_prefix_var_decl & { type: 'var'; prefix: string; } - // push for analysis + = p: KW_VAR_PRE d: without_prefix_var_decl { return { type: 'var', ...d, @@ -5713,21 +4255,17 @@ var_decl } without_prefix_var_decl - = p:'"'? name:ident_name m:mem_chain s:'"'? { - // => { type: 'var'; prefix: string; name: ident_name; members: mem_chain; quoted: string | null } + = name:ident_name m:mem_chain { //push for analysis - if ((p && !s) || (!p && s)) throw new Error('double quoted not match') varList.push(name); return { type: 'var', name: name, members: m, - quoted: p && s ? '"' : null, prefix: null, }; } / n:literal_numeric { - // => { type: 'var'; prefix: null; name: number; members: []; quoted: null } return { type: 'var', name: n.value, @@ -5739,7 +4277,6 @@ without_prefix_var_decl mem_chain = l:('.' ident_name)* { - // => ident_name[]; const s = []; for (let i = 0; i < l.length; i++) { s.push(l[i][1]); @@ -5748,109 +4285,73 @@ mem_chain } data_type - = array_type - / character_string_type + = character_string_type / numeric_type / datetime_type / json_type - / geometry_type / text_type - / uuid_type - / boolean_type / enum_type - / serial_interval_type + / boolean_type / binary_type - / oid_type - / record_type - / custom_types - + / blob_type + / geometry_type -array_type - = t:(numeric_type / character_string_type) __ LBRAKE __ RBRAKE __ LBRAKE __ RBRAKE { - /* => data_type */ - return { ...t, array: { dimension: 2 } } - } - / t:(numeric_type / character_string_type) __ LBRAKE __ l:literal_numeric? __ RBRAKE { - /* => data_type */ - return { ...t, array: { dimension: 1, length: [l] } } - } - / t:(numeric_type / character_string_type) __ KW_ARRAY { - /* => data_type */ - return { ...t, array: { keyword: 'array' } } +data_type_size + = LPAREN __ l:[0-9]+ __ RPAREN __ s:numeric_type_suffix? { + return { + length: parseInt(l.join(''), 10), + parentheses: true, + suffix: s, + }; } - - boolean_type - = t:(KW_BOOL / KW_BOOLEAN) { /* => data_type */ return { dataType: t }} + = 'boolean'i { return { dataType: 'BOOLEAN' }; } -binary_type - = 'bytea'i { /* => data_type */ return { dataType: 'BYTEA' }; } +blob_type + = b:('blob'i / 'tinyblob'i / 'mediumblob'i / 'longblob'i) { return { dataType: b.toUpperCase() }; } -character_varying - = KW_CHARACTER __ ('varying'i)? { - // => string - return 'CHARACTER VARYING' +binary_type + = t:(KW_BINARY / KW_VARBINARY) __ num:data_type_size? { + return { dataType: t, ...(num || {}) } } + character_string_type - = t:(KW_CHAR / KW_VARCHAR / character_varying) num:(__ LPAREN __ [0-9]+ __ RPAREN)? { - // => data_type + = t:(KW_CHAR / KW_VARCHAR) num:(__ LPAREN __ [0-9]+ __ RPAREN __ ('ARRAY'i)?)? { const result = { dataType: t } if (num) { result.length = parseInt(num[3].join(''), 10) result.parentheses = true + result.suffix = num[7] && ['ARRAY'] } return result } numeric_type_suffix - = un: KW_UNSIGNED? __ ze: KW_ZEROFILL? { - // => any[]; + = un:signedness? __ ze: KW_ZEROFILL? { const result = [] if (un) result.push(un) if (ze) result.push(ze) return result } numeric_type - = t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_TINYINT / KW_MEDIUMINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE __ 'PRECISION'i / KW_DOUBLE / KW_SERIAL / KW_BIGSERIAL / KW_REAL) __ LPAREN __ l:[0-9]+ __ r:(COMMA __ [0-9]+)? __ RPAREN __ s:numeric_type_suffix? { /* => data_type */ return { dataType: Array.isArray(t) ? `${t[0].toUpperCase()} ${t[2].toUpperCase()}` : t, length: parseInt(l.join(''), 10), scale: r && parseInt(r[2].join(''), 10), parentheses: true, suffix: s }; } - / t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_TINYINT / KW_MEDIUMINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE __ 'PRECISION'i / KW_DOUBLE / KW_SERIAL / KW_BIGSERIAL / KW_REAL)l:[0-9]+ __ s:numeric_type_suffix? { /* => data_type */ return { dataType: Array.isArray(t) ? `${t[0].toUpperCase()} ${t[2].toUpperCase()}` : t, length: parseInt(l.join(''), 10), suffix: s }; } - / t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_TINYINT / KW_MEDIUMINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE __ 'PRECISION'i / KW_DOUBLE / KW_SERIAL / KW_BIGSERIAL / KW_REAL) __ s:numeric_type_suffix? __{ /* => data_type */ return { dataType: Array.isArray(t) ? `${t[0].toUpperCase()} ${t[2].toUpperCase()}` : t, suffix: s }; } - -oid_type - = t:(KW_OID / KW_REGCLASS / KW_REGCOLLATION / KW_REGCONFIG / KW_REGDICTIONARY / KW_REGNAMESPACE / KW_REGOPER / KW_REGOPERATOR / KW_REGPROC / KW_REGPROCEDURE / KW_REGROLE / KW_REGTYPE) { /* => data_type */ return { dataType: t }} - -timezone - = w:('WITHOUT'i / 'WITH'i) __ KW_TIME __ 'ZONE'i { - // => string[]; - return [w.toUpperCase(), 'TIME', 'ZONE'] - } + = t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_MEDIUMINT / KW_TINYINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE / KW_BIT) __ LPAREN __ l:[0-9]+ __ r:(COMMA __ [0-9]+)? __ RPAREN __ s:numeric_type_suffix? { return { dataType: t, length: parseInt(l.join(''), 10), scale: r && parseInt(r[2].join(''), 10), parentheses: true, suffix: s }; } + / t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_MEDIUMINT/ KW_TINYINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE)l:[0-9]+ __ s:numeric_type_suffix? { return { dataType: t, length: parseInt(l.join(''), 10), suffix: s }; } + / t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_MEDIUMINT / KW_TINYINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE) __ s:numeric_type_suffix? __{ return { dataType: t, suffix: s }; } -time_type - = t:(KW_TIME / KW_TIMESTAMP / KW_TIMESTAMPTZ) num:(__ LPAREN __ [0-9]+ __ RPAREN )? __ tz:timezone? { - /* => data_type */ - const result = { dataType: t } - if (num) { - result.length = parseInt(num[3].join(''), 10) - result.parentheses = true - } - if (tz) result.suffix = tz - return result - } datetime_type - = t:(KW_DATE / KW_DATETIME) num:(__ LPAREN __ [0-9]+ __ RPAREN)? { - /* => data_type */ + = t:(KW_DATE / KW_DATETIME / KW_TIME / KW_TIMESTAMP / KW_YEAR) num:(__ LPAREN __ [0-6] __ RPAREN __ numeric_type_suffix?)? { const result = { dataType: t } if (num) { - result.length = parseInt(num[3].join(''), 10) + result.length = parseInt(num[3], 10) result.parentheses = true + result.suffix = num[7] } return result } - / time_type enum_type - = t:KW_ENUM __ e:value_item { - /* => data_type */ + = t:(KW_ENUM / KW_SET) __ e:value_item { e.parentheses = true return { dataType: t, @@ -5859,28 +4360,12 @@ enum_type } json_type - = t:(KW_JSON / KW_JSONB) { /* => data_type */ return { dataType: t }; } - -geometry_type - = t:KW_GEOMETRY {/* => data_type */ return { dataType: t }; } - -serial_interval_type - = t:(KW_SERIAL / KW_INTERVAL) { /* => data_type */ return { dataType: t }; } + = t:KW_JSON { return { dataType: t }; } text_type - = t:(KW_TINYTEXT / KW_TEXT / KW_MEDIUMTEXT / KW_LONGTEXT) s:(LBRAKE __ RBRAKE)? { - /* => data_type */ - return { dataType: `${t}${s ? '[]' : ''}` } + = t:(KW_TINYTEXT / KW_TEXT / KW_MEDIUMTEXT / KW_LONGTEXT) num:data_type_size? { + return { dataType: t, ...(num || {}) } } -uuid_type - = t:KW_UUID {/* => data_type */ return { dataType: t }} - -record_type - = 'RECORD'i {/* => data_type */ return { dataType: 'RECORD' }} - -custom_types - = name:ident_name &{ return customTypes.has(name) } { - // => data_type - return { dataType: name } - } +geometry_type + = t:(KW_GEOMETRY / KW_POINT / KW_LINESTRING / KW_POLYGON / KW_MULTIPOINT / KW_MULTILINESTRING / KW_MULTIPOLYGON / KW_GEOMETRYCOLLECTION ) { return { dataType: t }} From aff9d0183e7804bbfca20dd1f999d65eb2d88367 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Mon, 7 Oct 2024 14:58:02 +0530 Subject: [PATCH 5/7] reverted to datafusion support on top of postgresql dialect --- pegjs/datafusionsql.pegjs | 5149 ++++++++++++++++++++++++------------- 1 file changed, 3332 insertions(+), 1817 deletions(-) diff --git a/pegjs/datafusionsql.pegjs b/pegjs/datafusionsql.pegjs index c06f0170..7faf9657 100644 --- a/pegjs/datafusionsql.pegjs +++ b/pegjs/datafusionsql.pegjs @@ -6,299 +6,101 @@ 'AND': true, 'AS': true, 'ASC': true, - 'ANALYZE': true, - 'ACCESSIBLE': true, - 'BEFORE': true, 'BETWEEN': true, - 'BIGINT': true, - 'BLOB': true, - 'BOTH': true, 'BY': true, - 'BOOLEAN': true, 'CALL': true, - 'CASCADE': true, 'CASE': true, - 'CHAR': true, - 'CHECK': true, - 'COLLATE': true, - // 'COLUMN': true, - 'CONDITION': true, - 'CONSTRAINT': true, - 'CONTINUE': true, - 'CONVERT': true, 'CREATE': true, - 'CROSS': true, + 'CONTAINS': true, + 'CONSTRAINT': true, 'CURRENT_DATE': true, 'CURRENT_TIME': true, 'CURRENT_TIMESTAMP': true, 'CURRENT_USER': true, - 'CURSOR': true, - - 'DATABASE': true, - 'DATABASES': true, - 'DAY_HOUR': true, - 'DAY_MICROSECOND': true, - 'DAY_MINUTE': true, - 'DAY_SECOND': true, - 'DEC': true, - 'DECIMAL': true, - 'DECLARE': true, - 'DEFAULT': true, - 'DELAYED': true, + 'DELETE': true, 'DESC': true, - 'DESCRIBE': true, - 'DETERMINISTIC': true, 'DISTINCT': true, - 'DISTINCTROW': true, - 'DIV': true, 'DROP': true, - 'DOUBLE': true, - 'DUAL': true, 'ELSE': true, - 'EACH': true, - 'ELSEIF': true, - 'ENCLOSED': true, - 'ESCAPED': true, - 'EXCEPT': true, + 'END': true, 'EXISTS': true, - 'EXIT': true, 'EXPLAIN': true, 'FALSE': true, - 'FULL': true, 'FROM': true, - 'FETCH': true, - 'FLOAT': true, - 'FLOAT4': true, - 'FLOAT8': true, - 'FOR': true, - 'FORCE': true, - 'FOREIGN': true, - 'FULLTEXT': true, - 'FUNCTION': true, - - 'GENERATED': true, - 'GET': true, - 'GO': true, - 'GRANT': true, + 'FULL': true, + 'GROUP': true, - 'GROUPING': true, - 'GROUPS': true, 'HAVING': true, - 'HIGH_PRIORITY': true, - 'HOUR_MICROSECOND': true, - 'HOUR_MINUTE': true, - 'HOUR_SECOND': true, - // 'IF': true, - 'IGNORE': true, 'IN': true, 'INNER': true, - 'INFILE': true, - 'INOUT': true, - 'INSENSITIVE': true, 'INSERT': true, 'INTERSECT': true, - 'INT': true, - 'INT1': true, - 'INT2': true, - 'INT3': true, - 'INT4': true, - 'INT8': true, - 'INTEGER': true, - 'INTERVAL': true, 'INTO': true, - 'IO_AFTER_GTIDS': true, - 'IO_BEFORE_GTIDS': true, 'IS': true, - 'ITERATE': true, + 'ILIKE': true, 'JOIN': true, - 'JSON_TABLE': true, - - 'KEY': true, - 'KEYS': true, - 'KILL': true, - - 'LAG': true, // added in 8.0.2 (reserved) - 'LAST_VALUE': true, - 'LATERAL': true, - 'LEAD': true, - 'LEADING': true, - 'LEAVE': true, + 'JSON': true, + + // 'KEY': true, + 'LEFT': true, 'LIKE': true, 'LIMIT': true, - 'LINEAR': true, - 'LINES': true, - 'LOAD': true, - 'LOCALTIME': true, - 'LOCALTIMESTAMP': true, - 'LOCK': true, - 'LONG': true, - 'LONGBLOB': true, - 'LONGTEXT': true, - 'LOOP': true, - 'LOW_PRIORITY': true, // for lock table - - 'MASTER_BIND': true, - 'MATCH': true, - 'MAXVALUE': true, - 'MEDIUMBLOB': true, - 'MEDIUMINT': true, - 'MEDIUMTEXT': true, - 'MIDDLEINT': true, - 'MINUTE_MICROSECOND': true, - 'MINUTE_SECOND': true, - 'MINUS': true, - 'MOD': true, - 'MODIFIES': true, - - - 'NATURAL': true, + 'NOT': true, - 'NO_WRITE_TO_BINLOG': true, - 'NTH_VALUE': true, // added in 8.0.2 (reserved) - 'NTILE': true, // added in 8.0.2 (reserved) 'NULL': true, - 'NUMERIC': true, + 'NULLS': true, - 'OF': true, // added in 8.0.1 (reserved) + 'OFFSET': true, 'ON': true, - 'OPTIMIZE': true, - 'OPTIMIZER_COSTS': true, - 'OPTION': true, - 'OPTIONALLY': true, 'OR': true, 'ORDER': true, - 'OUT': true, 'OUTER': true, - 'OUTFILE': true, - 'OVER': true, // added in 8.0.2 (reserved) 'PARTITION': true, - 'PERCENT_RANK': true, // added in 8.0.2 (reserved) - 'PRECISION': true, - 'PRIMARY': true, - 'PROCEDURE': true, - 'PURGE': true, - - 'RANGE': true, - 'RANK': true, // added in 8.0.2 (reserved) - 'READ': true, // for lock table - 'READS': true, // for lock table - 'READ_WRITE': true, // for lock table - 'REAL': true, // for lock table + 'RECURSIVE': true, - 'REFERENCES': true, - 'REGEXP': true, - 'RELEASE': true, 'RENAME': true, - 'REPEAT': true, - 'REPLACE': true, - 'REQUIRE': true, - 'RESIGNAL': true, - 'RESTRICT': true, - 'RETURN': true, - 'REVOKE': true, + // 'REPLACE': true, 'RIGHT': true, - 'RLIKE': true, - 'ROW': true, // // added in 8.0.2 (reserved) - 'ROWS': true, // // added in 8.0.2 (reserved) - 'ROW_NUMBER': true, // // added in 8.0.2 (reserved) - 'SCHEMA': true, - 'SCHEMAS': true, 'SELECT': true, - 'SENSITIVE': true, - 'SEPARATOR': true, + 'SESSION_USER': true, 'SET': true, 'SHOW': true, - 'SIGNAL': true, - 'SMALLINT': true, - 'SPATIAL': true, - 'SPECIFIC': true, - 'SQL': true, - 'SQLEXCEPTION': true, - 'SQLSTATE': true, - 'SQLWARNING': true, - 'SQL_BIG_RESULT': true, - // 'SQL_CALC_FOUND_ROWS': true, - // 'SQL_SMALL_RESULT': true, - 'SSL': true, - 'STARTING': true, - 'STORED': true, - 'STRAIGHT_JOIN': true, - 'SYSTEM': true, + 'SYSTEM_USER': true, 'TABLE': true, - 'TERMINATED': true, 'THEN': true, - 'TINYBLOB': true, - 'TINYINT': true, - 'TINYTEXT': true, - 'TO': true, - 'TRAILING': true, - 'TRIGGER': true, 'TRUE': true, + 'TRUNCATE': true, 'UNION': true, - 'UNIQUE': true, - 'UNLOCK': true, - 'UNSIGNED': true, 'UPDATE': true, - 'USAGE': true, - 'USE': true, 'USING': true, - 'UTC_DATE': true, - 'UTC_TIME': true, - 'UTC_TIMESTAMP': true, - 'VALUES': true, - 'VARBINARY': true, - 'VARCHAR': true, - 'VARCHARACTER': true, - 'VARYING': true, - 'VIRTUAL': true, + // 'VALUES': true, + 'WITH': true, 'WHEN': true, 'WHERE': true, - 'WHILE': true, - 'WINDOW': true, // added in 8.0.2 (reserved) - 'WITH': true, - 'WRITE': true, // for lock table + 'WINDOW': true, - 'XOR': true, - - 'YEAR_MONTH': true, - - 'ZEROFILL': true, + 'GLOBAL': true, + 'SESSION': true, + 'LOCAL': true, + 'PERSIST': true, + 'PERSIST_ONLY': true, }; - const reservedFunctionName = { - avg: true, - sum: true, - count: true, - convert: true, - max: true, - min: true, - group_concat: true, - std: true, - variance: true, - current_date: true, - current_time: true, - current_timestamp: true, - current_user: true, - user: true, - session_user: true, - system_user: true, - } - function getLocationObject() { return options.includeLocations ? {loc: location()} : {} } @@ -329,7 +131,7 @@ } function createList(head, tail, po = 3) { - const result = [head]; + const result = Array.isArray(head) ? head : [head]; for (let i = 0; i < tail.length; i++) { delete tail[i][po].tableList delete tail[i][po].columnList @@ -374,6 +176,10 @@ columns.forEach(col => columnList.add(col)) } + function commonStrToLiteral(strOrLiteral) { + return typeof strOrLiteral === 'string' ? { type: 'same', value: strOrLiteral } : strOrLiteral + } + const cmpPrefixMap = { '+': true, '-': true, @@ -400,40 +206,24 @@ // used for dependency analysis let varList = []; - const tableList = new Set(); const columnList = new Set(); + const customTypes = new Set(); const tableAlias = {}; } start - = head:start_item __ tail:(__ KW_GO __ start_item)* { - if (!tail || tail.length === 0) return head - delete head.tableList - delete head.columnList - let cur = head - for (let i = 0; i < tail.length; i++) { - delete tail[i][3].tableList - delete tail[i][3].columnList - cur.go_next = tail[i][3] - cur.go = 'go' - cur = cur.go_next - } - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: head - } - } - -start_item - = __ n:multiple_stmt { + = __ n:(create_function_stmt / multiple_stmt) { + // => multiple_stmt return n } + / create_function_stmt + / multiple_stmt cmd_stmt = drop_stmt / create_stmt + / declare_stmt / truncate_stmt / rename_stmt / call_stmt @@ -441,37 +231,52 @@ cmd_stmt / alter_stmt / set_stmt / lock_stmt - / unlock_stmt / show_stmt - / desc_stmt - / grant_stmt - / explain_stmt + / deallocate_stmt + / grant_revoke_stmt + / if_else_stmt + / raise_stmt + / execute_stmt + / for_loop_stmt + / transaction_stmt + / comment_on_stmt create_stmt = create_table_stmt - / create_trigger_stmt + / create_constraint_trigger + / create_extension_stmt / create_index_stmt + / create_sequence / create_db_stmt + / create_domain_stmt + / create_type_stmt / create_view_stmt - / create_user_stmt + / create_aggregate_stmt alter_stmt = alter_table_stmt + / alter_schema_stmt + / alter_domain_type_stmt + / alter_function_stmt + / alter_aggregate_stmt crud_stmt - = set_op_stmt + = union_stmt / update_stmt / replace_insert_stmt / insert_no_columns_stmt - / insert_into_set / delete_stmt / cmd_stmt / proc_stmts multiple_stmt = head:crud_stmt tail:(__ SEMICOLON __ crud_stmt)* { + /* + // is in reality: { tableList: any[]; columnList: any[]; ast: T; } + export type AstStatement = T; + => AstStatement */ const headAst = head && head.ast || head - const cur = tail && tail.length && tail[0].length >= 4 ? [headAst] : headAst; + const cur = tail && tail.length && tail[0].length >= 4 ? [headAst] : headAst for (let i = 0; i < tail.length; i++) { if(!tail[i][3] || tail[i][3].length === 0) continue; cur.push(tail[i][3] && tail[i][3].ast || tail[i][3]); @@ -479,19 +284,32 @@ multiple_stmt return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), - ast: cur + ast: cur } } set_op - = KW_UNION __ s:(KW_ALL / KW_DISTINCT)? { - return s ? `union ${s.toLowerCase()}` : 'union' + = KW_UNION __ a:(KW_ALL / KW_DISTINCT)? { + // => 'union' | 'union all' | 'union distinct' + return a ? `union ${a.toLowerCase()}` : 'union' + } + / KW_INTERSECT { + // => 'intersect' + return 'intersect' + } + / KW_EXCEPT { + // => 'except' + return 'except' } - / KW_MINUS { return 'minus' } - / KW_INTERSECT { return 'intersect' } -set_op_stmt - = head:select_stmt tail:(__ set_op __ select_stmt)* __ ob: order_by_clause? __ l:limit_clause? { +union_stmt + = head:select_stmt tail:(__ set_op __ select_stmt)* __ ob:order_by_clause? __ l:limit_clause? { + /* export interface union_stmt_node extends select_stmt_node { + _next: union_stmt_node; + set_op: 'union' | 'union all' | 'union distinct'; + } + => AstStatement + */ let cur = head for (let i = 0; i < tail.length; i++) { cur._next = tail[i][3] @@ -499,7 +317,7 @@ set_op_stmt cur = cur._next } if(ob) head._orderby = ob - if(l) head._limit = l + if(l && l.value && l.value.length > 0) head._limit = l return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), @@ -507,42 +325,75 @@ set_op_stmt } } -column_order_list - = head:column_order_item tail:(__ COMMA __ column_order_item)* { - return createList(head, tail) +if_not_exists_stmt + = 'IF'i __ KW_NOT __ KW_EXISTS { + // => 'IF NOT EXISTS' + return 'IF NOT EXISTS' } -column_order_item - = c:expr o:(KW_ASC / KW_DESC)? { return { - ...c, - order_by: o && o.toLowerCase(), - } +if_exists + = 'if'i __ 'exists'i { + // => 'IF EXISTS' + return 'IF EXISTS' } - / column_order -column_order - = c:column_ref __ o:(KW_ASC / KW_DESC)? { - return { - ...c, - order_by: o && o.toLowerCase(), +create_extension_stmt + = a:KW_CREATE __ + e:'EXTENSION'i __ + ife: if_not_exists_stmt? __ + n:(ident_name / literal_string) __ + w:KW_WITH? __ + s:('SCHEMA'i __ ident_name / literal_string)? __ + v:('VERSION'i __ (ident_name / literal_string))? __ + f:(KW_FROM __ (ident_name / literal_string))? { + /* + export type nameOrLiteral = literal_string | { type: 'same', value: string; }; + => { + type: 'create'; + keyword: 'extension'; + if_not_exists?: 'if not exists'; + extension: nameOrLiteral; + with: 'with'; + schema: nameOrLiteral; + version: nameOrLiteral; + from: nameOrLiteral; + } + */ + return { + type: 'create', + keyword: e.toLowerCase(), + if_not_exists:ife, + extension: commonStrToLiteral(n), + with: w && w[0].toLowerCase(), + schema: commonStrToLiteral(s && s[2].toLowerCase()), // <== wont that be a bug ? + version: commonStrToLiteral(v && v[2]), + from: commonStrToLiteral(f && f[2]), + } } - } + create_db_definition = head:create_option_character_set tail:(__ create_option_character_set)* { + // => create_option_character_set[] return createList(head, tail, 1) } -if_not_exists_stmt - = 'IF'i __ KW_NOT __ KW_EXISTS { - return 'IF NOT EXISTS' - } - create_db_stmt = a:KW_CREATE __ k:(KW_DATABASE / KW_SCHEMA) __ ife:if_not_exists_stmt? __ t:proc_func_name __ c:create_db_definition? { + /* + export type create_db_stmt_t = { + type: 'create', + keyword: 'database' | 'schema', + if_not_exists?: 'if not exists', + database?: { db: ident_without_kw_type, schema: [ident_without_kw_type] }; + schema?: { db: ident_without_kw_type, schema: [ident_without_kw_type] }; + create_definitions?: create_db_definition + } + => AstStatement + */ const keyword = k.toLowerCase() return { tableList: Array.from(tableList), @@ -556,250 +407,414 @@ create_db_stmt } } } +view_with + = KW_WITH __ c:("CASCADED"i / "LOCAL"i) __ "CHECK"i __ "OPTION" { + // => string + return `with ${c.toLowerCase()} check option` + } + / KW_WITH __ "CHECK"i __ "OPTION" { + // => string + return 'with check option' + } -auth_option - = 'IDENTIFIED' __ ap:('WITH'i __ ident)? __ 'BY'i __ 'RANDOM'i __ 'PASSWORD'i { - const value = { - prefix: 'by', - type: 'origin', - value: 'random password', +with_view_option + = 'check_option'i __ KW_ASSIGIN_EQUAL __ t:("CASCADED"i / "LOCAL"i) { + // => {type: string; value: string; symbol: string; } + return { type: 'check_option', value: t, symbol: '=' } + } + / k:('security_barrier'i / 'security_invoker'i) __ KW_ASSIGIN_EQUAL __ t:literal_bool { + // => {type: string; value: string; symbol: string; } + return { type: k.toLowerCase(), value: t.value ? 'true' : 'false', symbol: '=' } + } +with_view_options + = head:with_view_option tail:(__ COMMA __ with_view_option)* { + // => with_view_option[] + return createList(head, tail); } +create_view_stmt + = a:KW_CREATE __ or:(KW_OR __ KW_REPLACE)? __ tp:(KW_TEMP / KW_TEMPORARY)? __ r:KW_RECURSIVE? __ + KW_VIEW __ v:table_name __ c:(LPAREN __ column_list __ RPAREN)? __ wo:(KW_WITH __ LPAREN __ with_view_options __ RPAREN)? __ + KW_AS __ s:select_stmt __ w:view_with? { + /* + export type create_view_stmt_t = { + type: 'create', + keyword: 'view', + replace?: 'or replace', + temporary?: 'temporary' | 'temp', + recursive?: 'recursive', + view: table_name, + columns?: column_list, + select: select_stmt, + with_options?: with_view_options, + with?: string, + } + => AstStatement + */ + v.view = v.table + delete v.table return { - keyword: ['identified', ap && ap[0].toLowerCase()].filter(v => v).join(' '), - auth_plugin: ap && ap[2], - value + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword: 'view', + replace: or && 'or replace', + temporary: tp && tp[0].toLowerCase(), + recursive: r && r.toLowerCase(), + columns: c && c[2], + select: s, + view: v, + with_options: wo && wo[4], + with: w, + } } } - / 'IDENTIFIED' __ ap:('WITH'i __ ident)? __ 'BY'i __ v:literal_string { - v.prefix = 'by' +create_aggregate_opt_required + = 'SFUNC'i __ KW_ASSIGIN_EQUAL __ n:table_name __ COMMA __ 'STYPE'i __ KW_ASSIGIN_EQUAL __ d:data_type { + // => { type: string; symbol: '='; value: expr; }[] + return [ + { + type: 'sfunc', + symbol: '=', + value: { schema: n.db, name: n.table }, + }, + { + type: 'stype', + symbol: '=', + value: d, + } + ] + } + +create_aggregate_opt_optional + = n:ident __ KW_ASSIGIN_EQUAL __ e:(ident / expr) { + // => { type: string; symbol: '='; value: ident | expr; } return { - keyword: ['identified', ap && ap[0].toLowerCase()].filter(v => v).join(' '), - auth_plugin: ap && ap[2], - value: v + type: n, + symbol: '=', + value: typeof e === 'string' ? { type: 'default', value: e } : e } } - / 'IDENTIFIED' __ 'WITH'i __ ap:ident __ 'AS'i __ v:literal_string { - v.prefix = 'as' + +create_aggregate_opts + = head:create_aggregate_opt_required tail:(__ COMMA __ create_aggregate_opt_optional)* { + // => create_aggregate_opt_optional[] + return createList(head, tail) + } + +create_aggregate_stmt + = a:KW_CREATE __ or:(KW_OR __ KW_REPLACE)? __ t:'AGGREGATE'i __ s:table_name __ LPAREN __ as:aggregate_signature __ RPAREN __ LPAREN __ opts:create_aggregate_opts __ RPAREN { + /* + export type create_aggregate_stmt_t = { + type: 'create', + keyword: 'aggregate', + replace?: 'or replace', + name: table_name, + args?: aggregate_signature, + options: create_aggregate_opt_optional[] + } + => AstStatement + */ return { - keyword: 'identified with', - auth_plugin: ap && ap[2], - value: v - } + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'create', + keyword: 'aggregate', + name: { schema: s.db, name: s.table }, + args: { + parentheses: true, + expr: as, + orderby: as.orderby + }, + options: opts + } + }; } -user_auth_option - = u:user_or_role __ ap:(auth_option)? { +column_data_type + = c:column_ref __ d:data_type { + // => { column: column_ref; definition: data_type; } return { - user: u, - auth_option: ap + column: c, + definition: d, } } -user_auth_option_list - = head:user_auth_option tail:(__ COMMA __ user_auth_option)* { +column_data_type_list + = head:column_data_type tail:(__ COMMA __ column_data_type)* { + // => column_data_type[] return createList(head, tail) } -default_role - = KW_DEFAULT __ 'role'i __ r:user_or_role_list { +func_returns + = 'RETURNS'i __ k:'SETOF'i? __ t:(data_type / table_name) { + // => { type: "returns"; keyword?: "setof"; expr: data_type; } return { - keyword: 'default role', - value: r - } - } -tls_option - = v:('NONE'i / 'SSL'i / 'X509'i) { - return{ - type: 'origin', - value: v + type: 'returns', + keyword: k, + expr: t } } - / k:('CIPHER'i / 'ISSUER'i / 'SUBJECT'i) __ v:literal_string { - v.prefix = k.toLowerCase() - return v - } -tls_option_list - = head:tls_option tail:(__ KW_AND __ tls_option)* { - return createBinaryExprChain(head, tail) - } -require_options - = 'REQUIRE'i __ t:tls_option_list { + / 'RETURNS'i __ KW_TABLE __ LPAREN __ e:column_data_type_list __ RPAREN { + // => { type: "returns"; keyword?: "table"; expr: column_data_type_list; } return { - keyword: 'require', - value: t + type: 'returns', + keyword: 'table', + expr: e } } -resource_option - = k:('MAX_QUERIES_PER_HOUR'i / 'MAX_UPDATES_PER_HOUR'i / 'MAX_CONNECTIONS_PER_HOUR'i / 'MAX_USER_CONNECTIONS'i) __ v:literal_numeric { - v.prefix = k.toLowerCase() - return v +declare_variable_item + = n:ident_name &{ return n.toLowerCase() !== 'begin' } __ c:'CONSTANT'i? __ d:data_type __ collate:collate_expr? __ nu:(KW_NOT __ KW_NULL)? __ expr:((KW_DEFAULT / ':=')? __ (&'BEGIN'i / literal / expr))? __ s:SEMICOLON? { + // => { keyword: 'variable'; name: string, constant?: string; datatype: data_type; collate?: collate_expr; not_null?: string; default?: { type: 'default'; keyword: string; value: literal | expr; }; } + return { + keyword: 'variable', + name: n, + constant: c, + datatype: d, + collate, + not_null: nu && 'not null', + definition: expr && expr[0] && { + type: 'default', + keyword: expr[0], + value: expr[2] + }, + } } -with_resource_option - = KW_WITH __ r:resource_option t:(__ resource_option)* { - const resourceOptions = [r] - if (t) { - for (const item of t) { - resourceOptions.push(item[1]) +declare_variables + = head:declare_variable_item tail:(__ declare_variable_item)* { + // => declare_variable_item[] + return createList(head, tail, 1) +} +declare_stmt + = 'DECLARE'i __ vars:declare_variables { + /* + export type declare_stmt_t = { type: 'declare'; declare: declare_variable_item[]; } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'declare', + declare: vars, + symbol: ';', } } + } + +create_func_opt + = 'LANGUAGE' __ ln:ident_name __ { + // => literal_string return { - keyword: 'with', - value: resourceOptions + prefix: 'LANGUAGE', + type: 'default', + value: ln } } -password_option - = 'PASSWORD'i __ 'EXPIRE'i __ v:('DEFAULT'i / 'NEVER'i / interval_expr) { + / 'TRANSORM'i __ ft:('FOR' __ 'TYPE' __ ident_name)? __ { + // => literal_string + if (!ft) return { type: 'origin', value: 'TRANSORM' } return { - keyword: 'password expire', - value: typeof v === 'string' ? { type: 'origin', value: v } : v + prefix: ['TRANSORM', ft[0].toUpperCase(), ft[2].toUpperCase()].join(' '), + type: 'default', + value: ft[4] } } - / 'PASSWORD'i __ 'HISTORY'i __ v:('DEFAULT'i / literal_numeric) { + / i:('WINDOW'i / 'IMMUTABLE'i / 'STABLE'i / 'VOLATILE'i / 'STRICT'i) __ { + // => literal_string return { - keyword: 'password history', - value: typeof v === 'string' ? { type: 'origin', value: v } : v + type: 'origin', + value: i } } - / 'PASSWORD'i __ 'REUSE' __ v:interval_expr { + / n:('NOT'i)? __ 'LEAKPROOF'i __ { + // => literal_string return { - keyword: 'password reuse', - value: v + type: 'origin', + value: [n, 'LEAKPROOF'].filter(v => v).join(' ') } } - / 'PASSWORD'i __ 'REQUIRE'i __ 'CURRENT'i __ v:('DEFAULT'i / 'OPTIONAL'i) { + / i:('CALLED'i / ('RETURNS'i __ 'NULL'i))? __ 'ON'i __ 'NULL'i __ 'INPUT'i __ { + // => literal_string + if (Array.isArray(i)) i = [i[0], i[2]].join(' ') return { - keyword: 'password require current', - value: { type: 'origin', value: v } + type: 'origin', + value: `${i} ON NULL INPUT` } } - / 'FAILED_LOGIN_ATTEMPTS'i __ v:literal_numeric { + / e:('EXTERNAL'i)? __ 'SECURITY'i __ i:('INVOKER'i / 'DEFINER'i) __ { + // => literal_string return { - keyword: 'failed_login_attempts', - value: v + type: 'origin', + value: [e, 'SECURITY', i].filter(v => v).join(' ') } } - / 'PASSWORD_LOCK_TIME'i __ v:(literal_numeric / 'UNBOUNDED'i) { + / 'PARALLEL'i __ i:('UNSAFE'i / 'RESTRICTED'i / 'SAFE'i) __ { + // => literal_string return { - keyword: 'password_lock_time', - value: typeof v === 'string' ? { type: 'origin', value: v } : v + type: 'origin', + value: ['PARALLEL', i].join(' ') } } - -password_option_list - = head:password_option tail:(__ password_option)* { - return createList(head, tail, 1) - } -user_lock_option - = 'ACCOUNT'i __ v:('LOCK'i / 'UNLOCK'i) { - const value = { - type: 'origin', - value: v.toLowerCase() + / KW_AS __ c:[^ \s\t\n\r]+ __ de:declare_stmt? __ b:('BEGIN'i)? __ s:multiple_stmt __ e:KW_END? &{ return (b && e) || (!b && !e) } __ SEMICOLON? __ l:[^ \s\t\n\r;]+ __ { + // => { type: 'as'; begin?: string; declare?: declare_stmt; expr: multiple_stmt; end?: string; symbol: string; } + const start = c.join('') + const end = l.join('') + if (start !== end) throw new Error(`start symbol '${start}'is not same with end symbol '${end}'`) + return { + type: 'as', + declare: de && de.ast, + begin: b, + expr: Array.isArray(s.ast) ? s.ast.flat() : [s.ast], + end: e && e[0], + symbol: start, } - value.prefix = 'account' - return value } -attribute - = 'ATTRIBUTE'i __ v:literal_string { - v.prefix = 'attribute' - return v + / p:('COST'i / 'ROWS'i) __ n:literal_numeric __ { + // => literal_numeric + n.prefix = p + return n } -create_user_stmt - = a:KW_CREATE __ k:KW_USER __ ife:if_not_exists_stmt? __ t:user_auth_option_list __ - d:default_role? __ r:require_options? __ wr:with_resource_option? __ p:password_option_list? __ - l:user_lock_option? __ c:keyword_comment? __ attr:attribute? { + / 'SUPPORT'i __ n:proc_func_name __ { + // => literal_string return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: a[0].toLowerCase(), - keyword: 'user', - if_not_exists: ife, - user: t, - default_role: d, - require: r, - resource_options: wr, - password_options: p, - lock_option: l, - comment: c, - attribute: attr - } + prefix: 'support', + type: 'default', + value: [n.schema && n.schema.value, n.name.value].filter(v => v).join('.') } } - -view_with - = KW_WITH __ c:("CASCADED"i / "LOCAL"i) __ "CHECK"i __ "OPTION" { - return `with ${c.toLowerCase()} check option` - } - / KW_WITH __ "CHECK"i __ "OPTION" { - return 'with check option' + / KW_SET __ ca:ident_name __ e:((('TO'i / '=') __ ident_list) / (KW_FROM __ 'CURRENT'i))? __ { + // => { type: "set"; parameter: ident_name; value?: { prefix: string; expr: expr }} + let value + if (e) { + const val = Array.isArray(e[2]) ? e[2] : [e[2]] + value = { + prefix: e[0], + expr: val.map(v => ({ type: 'default', value: v })) + } + } + return { + type: 'set', + parameter: ca, + value, + } } -create_view_stmt +create_function_stmt = a:KW_CREATE __ or:(KW_OR __ KW_REPLACE)? __ - al:("ALGORITHM"i __ KW_ASSIGIN_EQUAL __ ("UNDEFINED"i / "MERGE"i / "TEMPTABLE"i))? __ - df:trigger_definer? __ - ss:("SQL"i __ "SECURITY"i __ ("DEFINER"i / "INVOKER"i))? __ - KW_VIEW __ v:table_name __ c:(LPAREN __ column_list __ RPAREN)? __ - KW_AS __ s:select_stmt_nake __ - w:view_with? { - v.view = v.table - delete v.table + t:'FUNCTION'i __ + c:table_name __ LPAREN __ args:alter_func_args? __ RPAREN __ + r:func_returns? __ + fo:create_func_opt* __ SEMICOLON? __ { + /* + export type create_function_stmt_t = { + type: 'create'; + replace?: string; + name: { schema?: string; name: string }; + args?: alter_func_args; + returns?: func_returns; + keyword: 'function'; + options?: create_func_opt[]; + } + => AstStatement + */ return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: a[0].toLowerCase(), - keyword: 'view', - replace: or && 'or replace', - algorithm: al && al[4], - definer: df, - sql_security: ss && ss[4], - columns: c && c[2], - select: s, - view: v, - with: w, + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + args: args || [], + type: 'create', + replace: or && 'or replace', + name: { schema: c.db, name: c.table }, + returns: r, + keyword: t && t.toLowerCase(), + options: fo || [], + } } - } } -create_index_stmt - = a:KW_CREATE __ - kw:(KW_UNIQUE / KW_FULLTEXT / KW_SPATIAL)? __ - t:KW_INDEX __ - n:ident __ - um:index_type? __ - on:KW_ON __ - ta:table_name __ - LPAREN __ cols:column_order_list __ RPAREN __ - io:index_options? __ - al:ALTER_ALGORITHM? __ - lo:ALTER_LOCK? __ { +create_type_stmt_option + = KW_AS __ r:(KW_ENUM / 'RANGE'i) __ LPAREN __ e:expr_list? __ RPAREN { + // => { as: 'as'; resource: string; create_definitions: expr_list | create_column_definition_list; } + e.parentheses = true + return { + as: 'as', + resource: r.toLowerCase(), + create_definitions: e, + } + } + / KW_AS __ LPAREN __ e:create_column_definition_list? __ RPAREN { + // => ignore return { + as: 'as', + create_definitions: e, + } + } + +create_type_stmt + = a:KW_CREATE __ k:'TYPE'i __ s:table_name __ e:create_type_stmt_option? { + /* + export type create_type_stmt_t = { + type: 'create', + keyword: 'type', + name: { schema: string; name: string }, + as?: string, + resource?: string, + create_definitions?: expr_list | create_column_definition_list; + } + => AstStatement + */ + + customTypes.add([s.db, s.table].filter(v => v).join('.')) + return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { type: a[0].toLowerCase(), - index_type: kw && kw.toLowerCase(), - keyword: t.toLowerCase(), - index: n, - on_kw: on[0].toLowerCase(), - table: ta, - index_columns: cols, - index_using: um, - index_options: io, - algorithm_option: al, - lock_option: lo, + keyword: k.toLowerCase(), + name: { schema: s.db, name: s.table }, + ...e, } + } } - } +create_domain_stmt + = a:KW_CREATE __ k:'DOMAIN'i __ s:table_name __ as:KW_AS? __ d:data_type __ ce:collate_expr? __ de:default_expr? __ ccc: create_constraint_check? { + /* + export type create_domain_stmt_t = { + type: 'create', + keyword: 'domain', + domain: { schema: string; name: string }, + as?: string, + target: data_type, + create_definitions?: any[] + } + => AstStatement + */ + if (ccc) ccc.type = 'constraint' + const definitions = [ce, de, ccc].filter(v => v) + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword: k.toLowerCase(), + domain: { schema: s.db, name: s.table }, + as: as && as[0] && as[0].toLowerCase(), + target: d, + create_definitions: definitions, + } + } + } create_table_stmt = a:KW_CREATE __ tp:KW_TEMPORARY? __ KW_TABLE __ ife:if_not_exists_stmt? __ - t:table_name __ - lt:create_like_table { - if(t) tableList.add(`create::${t.db}::${t.table}`) + t:table_ref_list __ + po:create_table_partition_of { + // => AstStatement + if(t) t.forEach(tt => tableList.add(`create::${tt.db}::${tt.table}`)); return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), @@ -808,22 +823,40 @@ create_table_stmt keyword: 'table', temporary: tp && tp[0].toLowerCase(), if_not_exists: ife, - table: [t], - like: lt + table: t, + partition_of: po } } } - / a:KW_CREATE __ + / a:KW_CREATE __ tp:KW_TEMPORARY? __ KW_TABLE __ ife:if_not_exists_stmt? __ - t:table_name __ + t:table_ref_list __ c:create_table_definition? __ to:table_options? __ - ir:(KW_IGNORE / KW_REPLACE)? __ + ir: (KW_IGNORE / KW_REPLACE)? __ as:KW_AS? __ - qe:set_op_stmt? { - if(t) tableList.add(`create::${t.db}::${t.table}`) + qe:union_stmt? { + /* + export type create_table_stmt_node = create_table_stmt_node_simple | create_table_stmt_node_like; + export interface create_table_stmt_node_base { + type: 'create'; + keyword: 'table'; + temporary?: 'temporary'; + if_not_exists?: 'if not exists'; + table: table_ref_list; + } + export interface create_table_stmt_node_simple extends create_table_stmt_node_base{ + ignore_replace?: 'ignore' | 'replace'; + as?: 'as'; + query_expr?: union_stmt_node; + create_definitions?: create_table_definition; + table_options?: table_options; + } + => AstStatement + */ + if(t) t.forEach(tt => tableList.add(`create::${tt.db}::${tt.table}`)); return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), @@ -831,8 +864,8 @@ create_table_stmt type: a[0].toLowerCase(), keyword: 'table', temporary: tp && tp[0].toLowerCase(), - if_not_exists: ife, - table: [t], + if_not_exists:ife, + table: t, ignore_replace: ir && ir[0].toLowerCase(), as: as && as[0].toLowerCase(), query_expr: qe && qe.ast, @@ -841,83 +874,433 @@ create_table_stmt } } } + / a:KW_CREATE __ + tp:KW_TEMPORARY? __ + KW_TABLE __ + ife:if_not_exists_stmt? __ + t:table_ref_list __ + lt:create_like_table { + /* - -create_like_table_simple - = KW_LIKE __ t: table_ref_list { - return { - type: 'like', - table: t + export interface create_table_stmt_node_like extends create_table_stmt_node_base{ + like: create_like_table; + } + => AstStatement + */ + if(t) t.forEach(tt => tableList.add(`create::${tt.db}::${tt.table}`)); + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword: 'table', + temporary: tp && tp[0].toLowerCase(), + if_not_exists:ife, + table: t, + like: lt + } + } } - } +create_sequence + = a:KW_CREATE __ + tp:(KW_TEMPORARY / KW_TEMP)? __ + KW_SEQUENCE __ + ife:if_not_exists_stmt? __ + t:table_name __ as:(KW_AS __ alias_ident)?__ + c:create_sequence_definition_list? { + /* + export type create_sequence_stmt = { + type: 'create', + keyword: 'sequence', + temporary?: 'temporary' | 'temp', + if_not_exists?: 'if not exists', + table: table_ref_list, + create_definitions?: create_sequence_definition_list + } + => AstStatement + */ + t.as = as && as[2] + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + keyword: 'sequence', + temporary: tp && tp[0].toLowerCase(), + if_not_exists:ife, + sequence: [t], + create_definitions: c, + } + } + } + +sequence_definition_increment + = k:'INCREMENT'i __ b:KW_BY? __ n:literal_numeric { + /* + export type sequence_definition = { "resource": "sequence", prefix?: string,value: literal | column_ref } + => sequence_definition + */ + return { + resource: 'sequence', + prefix: b ? `${k.toLowerCase()} by` : k.toLowerCase(), + value: n + } + } +sequence_definition_minval + = k:'MINVALUE'i __ n:literal_numeric { + // => sequence_definition + return { + resource: 'sequence', + prefix: k.toLowerCase(), + value: n + } + } + / 'NO'i __ 'MINVALUE'i { + // => sequence_definition + return { + resource: 'sequence', + value: { + type: 'origin', + value: 'no minvalue' + } + } + } + +sequence_definition_maxval + = k:'MAXVALUE'i __ n:literal_numeric { + // => sequence_definition + return { + resource: 'sequence', + prefix: k.toLowerCase(), + value: n + } + } + / 'NO'i __ 'MAXVALUE'i { + // => sequence_definition + return { + resource: 'sequence', + value: { + type: 'origin', + value: 'no maxvalue' + } + } + } + +sequence_definition_start + = k:'START'i __ w:KW_WITH? __ n:literal_numeric { + // => sequence_definition + return { + resource: 'sequence', + prefix: w ? `${k.toLowerCase()} with` : k.toLowerCase(), + value: n + } + } + +sequence_definition_cache + = k:'CACHE'i __ n:literal_numeric { + // => sequence_definition + return { + resource: 'sequence', + prefix: k.toLowerCase(), + value: n + } + } + +sequence_definition_cycle + = n:'NO'i? __ 'CYCLE'i { + // => sequence_definition + return { + resource: 'sequence', + value: { + type: 'origin', + value: n ? 'no cycle' : 'cycle' + } + } + } + +sequence_definition_owned + = 'OWNED'i __ KW_BY __ 'NONE'i { + // => sequence_definition + return { + resource: 'sequence', + prefix: 'owned by', + value: { + type: 'origin', + value: 'none' + } + } + } + / n:'OWNED'i __ KW_BY __ col:column_ref { + // => sequence_definition + return { + resource: 'sequence', + prefix: 'owned by', + value: col + } + } + +create_sequence_definition + = sequence_definition_increment + / sequence_definition_minval + / sequence_definition_maxval + / sequence_definition_start + / sequence_definition_cache + / sequence_definition_cycle + / sequence_definition_owned + +create_sequence_definition_list + = head: create_sequence_definition tail:(__ create_sequence_definition)* { + // => create_sequence_definition[] + return createList(head, tail, 1) +} + +create_index_stmt + = a:KW_CREATE __ + kw:KW_UNIQUE? __ + t:KW_INDEX __ + co:KW_CONCURRENTLY? __ + n:ident? __ + on:KW_ON __ + ta:table_name __ + um:index_type? __ + LPAREN __ cols:column_order_list __ RPAREN __ + wr:(KW_WITH __ LPAREN __ index_options_list __ RPAREN)? __ + ts:(KW_TABLESPACE __ ident_name)? __ + w:where_clause? __ { + /* + export interface create_index_stmt_node { + type: 'create'; + index_type?: 'unique'; + keyword: 'index'; + concurrently?: 'concurrently'; + index: string; + on_kw: string; + table: table_name; + index_using?: index_type; + index_columns: column_order[]; + with?: index_option[]; + with_before_where: true; + tablespace?: {type: 'origin'; value: string; } + where?: where_clause; + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: a[0].toLowerCase(), + index_type: kw && kw.toLowerCase(), + keyword: t.toLowerCase(), + concurrently: co && co.toLowerCase(), + index: n, + on_kw: on[0].toLowerCase(), + table: ta, + index_using: um, + index_columns: cols, + with: wr && wr[4], + with_before_where: true, + tablespace: ts && { type: 'origin', value: ts[2] }, + where: w, + } + } + } + +column_order_list + = head:column_order tail:(__ COMMA __ column_order)* { + // => column_order[] + return createList(head, tail) + } + +column_order + = c:expr __ + ca:collate_expr? __ + op:ident? __ + o:(KW_ASC / KW_DESC)? __ + nf:('NULLS'i __ ('FIRST'i / 'LAST'i))? { + /* + => { + collate: collate_expr; + opclass: ident; + order: 'asc' | 'desc'; + nulls: 'nulls last' | 'nulls first'; + } + */ + return { + ...c, + collate: ca, + opclass: op, + order_by: o && o.toLowerCase(), + nulls: nf && `${nf[0].toLowerCase()} ${nf[2].toLowerCase()}`, + } + } + +create_like_table_simple + = KW_LIKE __ t: table_ref_list { + // => { type: 'like'; table: table_ref_list; } + return { + type: 'like', + table: t + } + } create_like_table = create_like_table_simple / LPAREN __ e:create_like_table __ RPAREN { + // => create_like_table_simple & { parentheses?: boolean; } e.parentheses = true; return e; } +for_values_item + = KW_FROM __ LPAREN __ f:literal_string __ RPAREN __ KW_TO __ LPAREN __ t:literal_string __ RPAREN { + /* => { + type: 'for_values_item'; + keyword: 'from'; + from: literal_string; + to: literal_string; + } */ + return { + type: 'for_values_item', + keyword: 'from', + from: f, + to: t, + } + } + / KW_IN __ LPAREN __ e:expr_list __ RPAREN { + /* => { + type: 'for_values_item'; + keyword: 'in'; + in: expr_list; + } */ + return { + type: 'for_values_item', + keyword: 'in', + in: e, + } + } + / KW_WITH __ LPAREN __ 'MODULUS'i __ m:literal_numeric __ COMMA __ 'REMAINDER'i __ r:literal_numeric __ RPAREN { + /* => { + type: 'for_values_item'; + keyword: 'with'; + modulus: literal_numeric; + remainder: literal_numeric; + } */ + return { + type: 'for_values_item', + keyword: 'with', + modulus: m, + remainder: r, + } + } + +for_values + = 'FOR'i __ KW_VALUES __ fvi:for_values_item { + /* => { + type: 'for_values'; + keyword: 'for values'; + expr: for_values_item; + } */ + return { + type: 'for_values', + keyword: 'for values', + expr: fvi + } + } +create_table_partition_of + = KW_PARTITION __ 'OF'i __ t:table_name __ fv:for_values __ ts:(KW_TABLESPACE __ ident_without_kw_type)? { + /* => { + type: 'partition_of'; + keyword: 'partition of'; + table: table_name; + for_values: for_values; + tablespace: ident_without_kw_type | undefined; + } */ + return { + type: 'partition_of', + keyword: 'partition of', + table: t, + for_values: fv, + tablespace: ts && ts[2] + } + } + create_table_definition = LPAREN __ head:create_definition tail:(__ COMMA __ create_definition)* __ RPAREN { + // => create_definition[] return createList(head, tail); } create_definition - = create_constraint_definition - / create_column_definition + = create_column_definition / create_index_definition / create_fulltext_spatial_index_definition + / create_constraint_definition column_definition_opt - = n:(literal_not_null / literal_null) { - if (n && !n.value) n.value = 'null' - return { nullable: n } - } - / d:default_expr { - return { default_val: d } - } + = column_constraint / a:('AUTO_INCREMENT'i) { + // => { auto_increment: 'auto_increment'; } return { auto_increment: a.toLowerCase() } } / 'UNIQUE'i __ k:('KEY'i)? { + // => { unique: 'unique' | 'unique key'; } const sql = ['unique'] if (k) sql.push(k) return { unique: sql.join(' ').toLowerCase('') } } / p:('PRIMARY'i)? __ 'KEY'i { + // => { unique: 'key' | 'primary key'; } const sql = [] if (p) sql.push('primary') sql.push('key') return { primary_key: sql.join(' ').toLowerCase('') } } / co:keyword_comment { + // => { comment: keyword_comment; } return { comment: co } } / ca:collate_expr { + // => { collate: collate_expr; } return { collate: ca } } / cf:column_format { + // => { column_format: column_format; } return { column_format: cf } } / s:storage { + // => { storage: storage } return { storage: s } } / re:reference_definition { + // => { reference_definition: reference_definition; } return { reference_definition: re } } / ck:check_constraint_definition { + // => { check: check_constraint_definition; } return { check: ck } } / t:create_option_character_set_kw __ s:KW_ASSIGIN_EQUAL? __ v:ident_without_kw_type { + // => { character_set: { type: 'CHARACTER SET'; symbol: '=' | null; value: ident_without_kw_type; } } return { character_set: { type: t, value: v, symbol: s }} } - / g:generated { - return { generated: g } - } column_definition_opt_list = head:column_definition_opt __ tail:(__ column_definition_opt)* { + /* + => { + nullable?: column_constraint['nullable']; + default_val?: column_constraint['default_val']; + auto_increment?: 'auto_increment'; + unique?: 'unique' | 'unique key'; + primary?: 'key' | 'primary key'; + comment?: keyword_comment; + collate?: collate_expr; + column_format?: column_format; + storage?: storage; + reference_definition?: reference_definition; + } + */ let opt = head for (let i = 0; i < tail.length; i++) { opt = { ...opt, ...tail[i][1] } @@ -925,11 +1308,35 @@ column_definition_opt_list return opt } +create_column_definition_list + = head:create_column_definition tail:(__ COMMA __ create_column_definition)* { + // => create_column_definition[] + return createList(head, tail) + } + create_column_definition = c:column_ref __ - d:data_type __ + d:(data_type / double_quoted_ident) __ cdo:column_definition_opt_list? { - columnList.add(`create::${c.table}::${c.column}`) + /* + => { + column: column_ref; + definition: data_type; + nullable: column_constraint['nullable']; + default_val: column_constraint['default_val']; + auto_increment?: 'auto_increment'; + unique?: 'unique' | 'unique key'; + primary?: 'key' | 'primary key'; + comment?: keyword_comment; + collate?: collate_expr; + column_format?: column_format; + storage?: storage; + reference_definition?: reference_definition; + resource: 'column'; + } + */ + columnList.add(`create::${c.table}::${c.column.expr.value}`) + if (d.type === 'double_quote_string') d = { dataType: `"${d.value}"` } return { column: c, definition: d, @@ -938,80 +1345,31 @@ create_column_definition } } -trigger_definer - = 'DEFINER'i __ KW_ASSIGIN_EQUAL __ u:literal_string __ '@' __ h:literal_string { - const userNameSymbol = u.type === 'single_quote_string' ? '\'' : '"' - const hostSymbol = h.type === 'single_quote_string' ? '\'' : '"' - return `DEFINER = ${userNameSymbol}${u.value}${userNameSymbol}@${hostSymbol}${h.value}${hostSymbol}` - } - / 'DEFINER'i __ KW_ASSIGIN_EQUAL __ KW_CURRENT_USER __ LPAREN __ RPAREN { - return `DEFINER = CURRENT_USER()` - } - / 'DEFINER'i __ KW_ASSIGIN_EQUAL __ KW_CURRENT_USER { - return `DEFINER = CURRENT_USER` - } -trigger_time - = 'BEFORE'i / 'AFTER'i -trigger_event - = kw:(KW_INSERT / KW_UPDATE / KW_DELETE) { - return { - keyword: kw[0].toLowerCase(), - } - } -trigger_for_row - = kw:'FOR'i __ e:('EACH'i)? __ ob:('ROW'i / 'STATEMENT'i) { - return { - keyword: e ? `${kw.toLowerCase()} ${e.toLowerCase()}` : kw.toLowerCase(), - args: ob.toLowerCase() - } +column_constraint + = n:constraint_name { + // => { constraint: constraint_name; } + return { constraint: n } } -trigger_order - = f:('FOLLOWS'i / 'PRECEDES'i) __ t:ident { + / n:(literal_not_null / literal_null) __ df:default_expr? { + // => { nullable: literal_null | literal_not_null; default_val: default_expr; } + if (n && !n.value) n.value = 'null' return { - keyword: f, - trigger: t + default_val: df, + nullable: n } } -trigger_body - = KW_SET __ s:set_list { + / df:default_expr __ n:(literal_not_null / literal_null)? { + // => { nullable: literal_null | literal_not_null; default_val: default_expr; } + if (n && !n.value) n.value = 'null' return { - type: 'set', - expr: s, + default_val: df, + nullable: n } } -create_trigger_stmt - = a:KW_CREATE __ - df:trigger_definer? __ - KW_TRIGGER __ - ife:if_not_exists_stmt? __ - t:table_name __ - tt:trigger_time __ - te:trigger_event __ - KW_ON __ tb:table_name __ fe:trigger_for_row __ - tr:trigger_order? __ - tbo:trigger_body { - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: a[0].toLowerCase(), - definer: df, - keyword: 'trigger', - for_each: fe, - if_not_exists: ife, - trigger: t, - time: tt, - events: [te], - order: tr, - table: tb, - execute: tbo, - } - } - } - collate_expr - = KW_COLLATE __ ca:ident_name __ s:KW_ASSIGIN_EQUAL __ t:ident { + = KW_COLLATE __ ca:ident_type __ s:KW_ASSIGIN_EQUAL __ t:ident_type { + // => { type: 'collate'; keyword: 'collate'; collate: { symbol: '=' ; name: ident_type; value: ident_type; }} return { type: 'collate', keyword: 'collate', @@ -1022,7 +1380,8 @@ collate_expr } } } - / KW_COLLATE __ s:KW_ASSIGIN_EQUAL? __ ca:ident { + / KW_COLLATE __ s:KW_ASSIGIN_EQUAL? __ ca:ident_type { + // => { type: 'collate'; keyword: 'collate'; collate: { symbol: '=' | null ; name: ident_type; }} return { type: 'collate', keyword: 'collate', @@ -1032,9 +1391,9 @@ collate_expr } } } - column_format = k:'COLUMN_FORMAT'i __ f:('FIXED'i / 'DYNAMIC'i / 'DEFAULT'i) { + // => { type: 'column_format'; value: 'fixed' | 'dynamic' | 'default'; } return { type: 'column_format', value: f.toLowerCase() @@ -1042,48 +1401,48 @@ column_format } storage = k:'STORAGE'i __ s:('DISK'i / 'MEMORY'i) { + // => { type: 'storage'; value: 'disk' | 'memory' } return { type: 'storage', value: s.toLowerCase() } } +default_arg_expr + = kw:(KW_DEFAULT / KW_ASSIGIN_EQUAL)? __ ce:expr { + // => { type: 'default'; keyword: string, value: expr; } + return { + type: 'default', + keyword: kw && kw[0], + value: ce + } + } default_expr = KW_DEFAULT __ ce:expr { + // => { type: 'default'; value: expr; } return { type: 'default', value: ce } } - -generated_always - = ga:('GENERATED'i __ 'ALWAYS'i) { - return ga.join('').toLowerCase() - } - -generated - = gn:(generated_always? __ 'AS'i) __ LPAREN __ expr:(literal / expr) __ RPAREN __ st:('STORED'i / 'VIRTUAL'i)* { - return { - type: 'generated', - expr: expr, - value: gn.filter(s => typeof s === 'string').join(' ').toLowerCase(), - storage_type: st && st[0] && st[0].toLowerCase() - } - } - drop_index_opt = head:(ALTER_ALGORITHM / ALTER_LOCK) tail:(__ (ALTER_ALGORITHM / ALTER_LOCK))* { + // => (ALTER_ALGORITHM | ALTER_LOCK)[] return createList(head, tail, 1) } -if_exists - = 'if'i __ 'exists'i { - return 'if exists' - } - drop_stmt = a:KW_DROP __ r:KW_TABLE __ - ife: if_exists? __ + ife:if_exists? __ t:table_ref_list { + /* + export interface drop_stmt_node { + type: 'drop'; + keyword: 'table'; + prefix?: string; + name: table_ref_list; + } + => AstStatement + */ if(t) t.forEach(tt => tableList.add(`${a}::${tt.db}::${tt.table}`)); return { tableList: Array.from(tableList), @@ -1096,94 +1455,87 @@ drop_stmt } }; } - / a:KW_DROP __ - r:KW_VIEW __ - ife:if_exists? __ - t:table_ref_list __ - op:view_options? { - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: a.toLowerCase(), - keyword: r.toLowerCase(), - prefix: ife, - name: t, - options: op && [{ type: 'origin', value: op }], - } - }; - } / a:KW_DROP __ r:KW_INDEX __ + cu:KW_CONCURRENTLY? __ + ife:if_exists? __ i:column_ref __ - KW_ON __ - t:table_name __ - op:drop_index_opt? __ { + op:('CASCADE'i / 'RESTRICT'i)? { + /* + export interface drop_index_stmt_node { + type: 'drop'; + prefix?: string; + keyword: string; + name: column_ref; + options?: 'cascade' | 'restrict'; + } + => AstStatement + */ return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { type: a.toLowerCase(), keyword: r.toLowerCase(), + prefix: [cu, ife].filter(v => v).join(' '), name: i, - table: t, - options: op - } - }; - } - / a:KW_DROP __ - r:(KW_DATABASE / KW_SCHEMA) __ - ife:if_exists? __ - t:ident_name { - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: a.toLowerCase(), - keyword: r.toLowerCase(), - prefix: ife, - name: t - } - }; - } - / a:KW_DROP __ - r:KW_TRIGGER __ - ife:if_exists? __ - t:table_base { - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: a.toLowerCase(), - keyword: r.toLowerCase(), - prefix: ife, - name: [{ - schema: t.db, - trigger: t.table - }] + options: op && [{ type: 'origin', value: op }] } }; } +truncate_table_name + = t:table_name __ s:STAR? { + // => table_name & { suffix?: string } + tableList.add(`truncate::${t.db}::${t.table}`) + if (s) t.suffix = s + return t + } +truncate_table_name_list + = head:truncate_table_name tail:(__ COMMA __ truncate_table_name)* { + // => truncate_table_name[] + return createList(head, tail) + } truncate_stmt = a:KW_TRUNCATE __ kw:KW_TABLE? __ - t:table_ref_list { - if(t) t.forEach(tt => tableList.add(`${a}::${tt.db}::${tt.table}`)); + on: 'ONLY'i? __ + t:truncate_table_name_list __ + id: (('RESTART'i / 'CONTINUE'i) __ 'IDENTITY'i)? __ + op:('CASCADE'i / 'RESTRICT'i)? { + /* + export interface truncate_stmt_node { + type: 'trucate'; + keyword: 'table'; + prefix?: string; + name: table_ref_list; + suffix: string[]; + } + => AstStatement + */ return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { type: a.toLowerCase(), keyword: kw && kw.toLowerCase() || 'table', - name: t + prefix: on, + name: t, + suffix: [id && [id[0], id[2]].join(' '), op].filter(v => v).map(v => ({ type: 'origin', value: v })) } - }; + } } use_stmt = KW_USE __ d:ident { + /* + export interface use_stmt_node { + type: 'use'; + db: ident; + } + => AstStatement + */ tableList.add(`use::${d}::null`); return { tableList: Array.from(tableList), @@ -1195,93 +1547,215 @@ use_stmt }; } -alter_table_stmt - = KW_ALTER __ - KW_TABLE __ - t:table_name __ - e:alter_action_list { - tableList.add(`alter::${t.db}::${t.table}`) - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'alter', - table: [t], - expr: e - } - }; +aggregate_signature + = STAR { + // => { name: "*" } + return [ + { + name: '*' + } + ] + } + / s:alter_func_args? __ KW_ORDER __ KW_BY __ o:alter_func_args { + // => alter_func_args + const ans = s || [] + ans.orderby = o + return ans + } + / alter_func_args + +alter_func_argmode + = t:(KW_IN / 'OUT'i / 'VARIADIC'i) { + // => "IN" | "OUT" | "VARIADIC" + return t.toUpperCase() + } + +alter_func_arg_item + = m:alter_func_argmode? __ ad:data_type __ de:default_arg_expr? { + // => { mode?: string; name?: string; type: data_type; default: default_arg_expr; } + return { + mode: m, + type: ad, + default: de, } -alter_column_suffix - = k:('after'i / 'first'i) __ i:column_ref { + } + / m:alter_func_argmode? __ an:ident_name __ ad:data_type __ de:default_arg_expr? { + // => { mode?: string; name?: string; type: data_type; default: default_arg_expr; } return { - keyword: k, - expr: i + mode: m, + name: an, + type: ad, + default: de, } } +alter_func_args + = head:alter_func_arg_item tail:(__ COMMA __ alter_func_arg_item)* { + // => alter_func_arg_item[] + return createList(head, tail) + } +alter_aggregate_stmt + = KW_ALTER __ t:'AGGREGATE'i __ s:table_name __ LPAREN __ as:aggregate_signature __ RPAREN __ ac:(ALTER_RENAME / ALTER_OWNER_TO / ALTER_SET_SCHEMA) { + // => AstStatement + const keyword = t.toLowerCase() + ac.resource = keyword + ac[keyword] = ac.table + delete ac.table + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'alter', + keyword, + name: { schema: s.db, name: s.table }, + args: { + parentheses: true, + expr: as, + orderby: as.orderby + }, + expr: ac + } + }; + } +alter_function_stmt + = KW_ALTER __ t:'FUNCTION'i __ s:table_name __ ags:(LPAREN __ alter_func_args? __ RPAREN)? __ ac:(ALTER_RENAME / ALTER_OWNER_TO / ALTER_SET_SCHEMA) { + // => AstStatement + const keyword = t.toLowerCase() + ac.resource = keyword + ac[keyword] = ac.table + delete ac.table + const args = {} + if (ags && ags[0]) args.parentheses = true + args.expr = ags && ags[2] + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'alter', + keyword, + name: { schema: s.db, name: s.table }, + args, + expr: ac + } + }; + } +alter_domain_type_stmt + = KW_ALTER __ t:('DOMAIN'i / 'TYPE'i) __ s:table_name __ ac:(ALTER_RENAME / ALTER_OWNER_TO / ALTER_SET_SCHEMA) { + /* + export interface alter_resource_stmt_node { + type: 'alter'; + keyword: 'domain' | 'type', + name: string | { schema: string, name: string }; + args?: { parentheses: true; expr?: alter_func_args; orderby?: alter_func_args; }; + expr: alter_rename_owner; + } + => AstStatement + */ + const keyword = t.toLowerCase() + ac.resource = keyword + ac[keyword] = ac.table + delete ac.table + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'alter', + keyword, + name: { schema: s.db, name: s.table }, + expr: ac + } + }; + } + +alter_schema_stmt + = KW_ALTER __ t:KW_SCHEMA __ s:ident_name __ ac:(ALTER_RENAME / ALTER_OWNER_TO / ALTER_SET_SCHEMA) { + // => AstStatement + const keyword = t.toLowerCase() + ac.resource = keyword + ac[keyword] = ac.table + delete ac.table + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'alter', + keyword, + schema: s, + expr: ac + } + }; + } + +alter_table_stmt + = KW_ALTER __ + KW_TABLE __ + ife:if_exists? __ + o:'only'i? __ + t:table_ref_list __ + e:alter_action_list { + /* + export interface alter_table_stmt_node { + type: 'alter'; + table: table_ref_list; + keyword: 'table'; + if_exists: if_exists; + prefix?: literal_string; + expr: alter_action_list; + } + => AstStatement + */ + if (t && t.length > 0) t.forEach(table => tableList.add(`alter::${table.db}::${table.table}`)); + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'alter', + keyword: 'table', + if_exists: ife, + prefix: o && { type: 'origin', value: o }, + table: t, + expr: e + } + }; + } + alter_action_list = head:alter_action tail:(__ COMMA __ alter_action)* { + // => alter_action[] return createList(head, tail); } alter_action - = ALTER_ADD_CONSTRAINT - / ALTER_DROP_CONSTRAINT - / ALTER_DROP_KEY_INDEX - / ALTER_ENABLE_CONSTRAINT - / ALTER_DISABLE_CONSTRAINT - / ALTER_ADD_COLUMN + = ALTER_ADD_COLUMN + / ALTER_ADD_CONSTRAINT / ALTER_DROP_COLUMN - / ALTER_MODIFY_COLUMN / ALTER_ADD_INDEX_OR_KEY / ALTER_ADD_FULLETXT_SPARITAL_INDEX - / ALTER_RENAME_COLUMN - / ALTER_RENAME_TABLE + / ALTER_RENAME / ALTER_ALGORITHM / ALTER_LOCK - / ALTER_CHANGE_COLUMN - / t:table_option { - t.resource = t.keyword - t[t.keyword] = t.value - delete t.value - return { - type: 'alter', - ...t, - } - } + / ALTER_COLUMN_DATA_TYPE + / ALTER_COLUMN_DEFAULT + / ALTER_COLUMN_NOT_NULL ALTER_ADD_COLUMN = KW_ADD __ - kc:KW_COLUMN __ - cd:create_column_definition __ af:alter_column_suffix? { + kc:KW_COLUMN? __ + ife:if_not_exists_stmt? __ + cd:create_column_definition { + /* + => { + action: 'add'; + keyword: KW_COLUMN; + resource: 'column'; + if_not_exists: ife; + type: 'alter'; + } & create_column_definition; + */ return { action: 'add', + if_not_exists: ife, ...cd, keyword: kc, - suffix: af, - resource: 'column', - type: 'alter', - } - } - / KW_ADD __ - cd:create_column_definition __ af:alter_column_suffix? { - return { - action: 'add', - ...cd, - suffix: af, - resource: 'column', - type: 'alter', - } - } - -ALTER_MODIFY_COLUMN - = KW_MODIFY __ - kc:KW_COLUMN? __ - cd:create_column_definition __ af:alter_column_suffix? { - return { - action: 'modify', - keyword: kc, - ...cd, - suffix: af, resource: 'column', type: 'alter', } @@ -1289,29 +1763,51 @@ ALTER_MODIFY_COLUMN ALTER_DROP_COLUMN = KW_DROP __ - kc:KW_COLUMN __ + kc:KW_COLUMN? __ + ife:if_exists? __ c:column_ref { + /* => { + action: 'drop'; + collumn: column_ref; + keyword: KW_COLUMN; + if_exists: if_exists; + resource: 'column'; + type: 'alter'; + } */ return { action: 'drop', column: c, + if_exists: ife, keyword: kc, resource: 'column', type: 'alter', } } - / KW_DROP __ - c:column_ref { + +ALTER_ADD_CONSTRAINT + = KW_ADD __ c:create_constraint_definition { + /* => { + action: 'add'; + create_definitions: create_db_definition; + resource: 'constraint'; + type: 'alter'; + } */ return { - action: 'drop', - column: c, - resource: 'column', + action: 'add', + create_definitions: c, + resource: 'constraint', type: 'alter', } } ALTER_ADD_INDEX_OR_KEY = KW_ADD __ - id:create_index_definition { + id:create_index_definition + { + /* => { + action: 'add'; + type: 'alter'; + } & create_index_definition */ return { action: 'add', type: 'alter', @@ -1319,10 +1815,18 @@ ALTER_ADD_INDEX_OR_KEY } } -ALTER_RENAME_TABLE - = KW_RENAME __ - kw:(KW_TO / KW_AS)? __ - tn:ident { +ALTER_RENAME + = KW_RENAME __ kw:(KW_TO / KW_AS)? __ tn:ident { + /* + export interface alter_rename_owner { + action: string; + type: 'alter'; + resource: string; + keyword?: 'to' | 'as'; + [key: string]: ident | undefined; + } + => AstStatement + */ return { action: 'rename', type: 'alter', @@ -1332,23 +1836,39 @@ ALTER_RENAME_TABLE } } -ALTER_RENAME_COLUMN - = KW_RENAME __ KW_COLUMN __ c:column_ref __ - kw:(KW_TO / KW_AS)? __ - tn:column_ref { +ALTER_OWNER_TO + = 'OWNER'i __ KW_TO __ tn:(ident / 'CURRENT_ROLE'i / 'CURRENT_USER'i / 'SESSION_USER'i) { + // => AstStatement return { - action: 'rename', + action: 'owner', type: 'alter', - resource: 'column', - keyword: 'column', - old_column: c, - prefix: kw && kw[0].toLowerCase(), - column: tn + resource: 'table', + keyword: 'to', + table: tn + } + } + +ALTER_SET_SCHEMA + = KW_SET __ KW_SCHEMA __ s:ident { + // => AstStatement + return { + action: 'set', + type: 'alter', + resource: 'table', + keyword: 'schema', + table: s } } ALTER_ALGORITHM = "ALGORITHM"i __ s:KW_ASSIGIN_EQUAL? __ val:("DEFAULT"i / "INSTANT"i / "INPLACE"i / "COPY"i) { + /* => { + type: 'alter'; + keyword: 'algorithm'; + resource: 'algorithm'; + symbol?: '='; + algorithm: 'DEFAULT' | 'INSTANT' | 'INPLACE' | 'COPY'; + }*/ return { type: 'alter', keyword: 'algorithm', @@ -1360,6 +1880,13 @@ ALTER_ALGORITHM ALTER_LOCK = "LOCK"i __ s:KW_ASSIGIN_EQUAL? __ val:("DEFAULT"i / "NONE"i / "SHARED"i / "EXCLUSIVE"i) { + /* => { + type: 'alter'; + keyword: 'lock'; + resource: 'lock'; + symbol?: '='; + lock: 'DEFAULT' | 'NONE' | 'SHARED' | 'EXCLUSIVE'; + }*/ return { type: 'alter', keyword: 'lock', @@ -1369,89 +1896,104 @@ ALTER_LOCK } } -ALTER_CHANGE_COLUMN - = 'CHANGE'i __ kc:KW_COLUMN? __ od:column_ref __ cd:create_column_definition __ af:alter_column_suffix? { - return { - action: 'change', - old_column: od, - ...cd, +ALTER_COLUMN_DATA_TYPE + = KW_ALTER __ kc:KW_COLUMN? __ c:column_ref __ sd:(KW_SET __ 'data'i)? __ 'type'i __ t:data_type __ co:collate_expr? __ us:(KW_USING __ expr)? { + /* + => { + action: 'alter'; + keyword?: KW_COLUMN; + using?: expr; + type: 'alter'; + } & create_column_definition; + */ + c.suffix = sd ? 'set data type' : 'type' + return { + action: 'alter', + column: c, keyword: kc, resource: 'column', + definition: t, + collate: co, + using: us && us[2], type: 'alter', - suffix: af, } } -ALTER_ADD_CONSTRAINT - = KW_ADD __ c:create_constraint_definition { +ALTER_COLUMN_DEFAULT + = KW_ALTER __ kc:KW_COLUMN? __ c:column_ref __ KW_SET __ KW_DEFAULT __ e:expr { + /* => { + action: 'alter'; + keyword?: KW_COLUMN; + default_val?: { type: 'set default', value: expr }; + type: 'alter'; + } & create_column_definition; + */ return { - action: 'add', - create_definitions: c, - resource: 'constraint', + action: 'alter', + column: c, + keyword: kc, + resource: 'column', + default_val: { + type: 'set default', + value: e, + }, type: 'alter', } - } - -ALTER_DROP_KEY_INDEX - = KW_DROP __ 'PRIMARY'i __ KW_KEY { - return { - action: 'drop', - key: '', - keyword: 'primary key', - resource: 'key', - type: 'alter', - } - } - / KW_DROP __ k:(('FOREIGN'i? __ KW_KEY) / (KW_INDEX)) __ c:ident { - const resource = Array.isArray(k) ? 'key' : 'index' - return { - action: 'drop', - [resource]: c, - keyword: Array.isArray(k) ? `${[k[0], k[2]].filter(v => v).join(' ').toLowerCase()}` : k.toLowerCase(), - resource, - type: 'alter', - } } - -ALTER_DROP_CONSTRAINT - = KW_DROP __ kc:'CHECK'i __ c:ident_name { + / KW_ALTER __ kc:KW_COLUMN? __ c:column_ref __ KW_DROP __ KW_DEFAULT { + /* => { + action: 'alter'; + keyword?: KW_COLUMN; + default_val?: { type: 'set default', value: expr }; + type: 'alter'; + } & create_column_definition; + */ return { - action: 'drop', - constraint: c, - keyword: kc.toLowerCase(), - resource: 'constraint', - type: 'alter', - } - } - -ALTER_ENABLE_CONSTRAINT - = KW_WITH __ 'CHECK'i __ 'CHECK'i __ KW_CONSTRAINT __ c:ident_name { - return { - action: 'with', - constraint: c, - keyword: 'check check', - resource: 'constraint', + action: 'alter', + column: c, + keyword: kc, + resource: 'column', + default_val: { + type: 'drop default', + }, type: 'alter', } - } + } -ALTER_DISABLE_CONSTRAINT - = 'NOCHECK'i __ KW_CONSTRAINT __ c:ident_name { +ALTER_COLUMN_NOT_NULL + = KW_ALTER __ kc:KW_COLUMN? __ c:column_ref __ ac:(KW_SET / KW_DROP) __ n:literal_not_null { + /* => { + action: 'alter'; + keyword?: KW_COLUMN; + nullable: literal_not_null; + type: 'alter'; + } & create_column_definition; + */ + n.action = ac.toLowerCase(); return { - action: 'nocheck', - constraint: c, - resource: 'constraint', + action: 'alter', + column: c, + keyword: kc, + resource: 'column', + nullable: n, type: 'alter', } - } - - + } create_index_definition = kc:(KW_INDEX / KW_KEY) __ c:column? __ t:index_type? __ - de:cte_idx_column_definition __ - id:index_options? __ { + de:cte_column_definition __ + id:index_options? __ + { + /* => { + index: column; + definition: cte_column_definition; + keyword: 'index' | 'key'; + index_type?: index_type; + resource: 'index'; + index_options?: index_options; + }*/ return { index: c, definition: de, @@ -1467,7 +2009,15 @@ create_fulltext_spatial_index_definition kc:(KW_INDEX / KW_KEY)? __ c:column? __ de: cte_column_definition __ - id: index_options? { + id: index_options? __ + { + /* => { + index: column; + definition: cte_column_definition; + keyword: 'fulltext' | 'spatial' | 'fulltext key' | 'spatial key' | 'fulltext index' | 'spatial index'; + index_options?: index_options; + resource: 'index'; + }*/ return { index: c, definition: de, @@ -1485,22 +2035,48 @@ create_constraint_definition constraint_name = kc:KW_CONSTRAINT __ c:ident? { + // => { keyword: 'constraint'; constraint: ident; } return { keyword: kc.toLowerCase(), constraint: c } } - +create_constraint_check + = kc:constraint_name? __ p:'CHECK'i __ LPAREN __ e:or_and_where_expr __ RPAREN { + /* => { + constraint?: constraint_name['constraint']; + definition: [or_and_where_expr]; + keyword?: constraint_name['keyword']; + constraint_type: 'check'; + resource: 'constraint'; + }*/ + return { + constraint: kc && kc.constraint, + definition: [e], + constraint_type: p.toLowerCase(), + keyword: kc && kc.keyword, + resource: 'constraint', + } + } create_constraint_primary = kc:constraint_name? __ - p:('PRIMARY'i __ 'KEY'i) __ + p:('PRIMARY KEY'i) __ t:index_type? __ - de:cte_idx_column_definition __ + de:cte_column_definition __ id:index_options? { + /* => { + constraint?: constraint_name['constraint']; + definition: cte_column_definition; + constraint_type: 'primary key'; + keyword?: constraint_name['keyword']; + index_type?: index_type; + resource: 'constraint'; + index_options?: index_options; + }*/ return { constraint: kc && kc.constraint, definition: de, - constraint_type: `${p[0].toLowerCase()} ${p[2].toLowerCase()}`, + constraint_type: p.toLowerCase(), keyword: kc && kc.keyword, index_type: t, resource: 'constraint', @@ -1514,8 +2090,17 @@ create_constraint_unique p:(KW_INDEX / KW_KEY)? __ i:column? __ t:index_type? __ - de:cte_idx_column_definition __ + de:cte_column_definition __ id:index_options? { + /* => { + constraint?: constraint_name['constraint']; + definition: cte_column_definition; + constraint_type: 'unique key' | 'unique' | 'unique index'; + keyword?: constraint_name['keyword']; + index_type?: index_type; + resource: 'constraint'; + index_options?: index_options; + }*/ return { constraint: kc && kc.constraint, definition: de, @@ -1528,24 +2113,21 @@ create_constraint_unique } } -create_constraint_check - = kc:constraint_name? __ u:'CHECK'i __ nfr:('NOT'i __ 'FOR'i __ 'REPLICATION'i __)? LPAREN __ c:or_and_expr __ RPAREN { - return { - constraint_type: u.toLowerCase(), - keyword: kc && kc.keyword, - constraint: kc && kc.constraint, - index_type: nfr && { keyword: 'not for replication' }, - definition: [c], - resource: 'constraint', - } - } - create_constraint_foreign = kc:constraint_name? __ p:('FOREIGN KEY'i) __ i:column? __ de:cte_column_definition __ id:reference_definition? { + /* => { + constraint?: constraint_name['constraint']; + definition: cte_column_definition; + constraint_type: 'FOREIGN KEY'; + keyword: constraint_name['keyword']; + index?: column; + resource: 'constraint'; + reference_definition?: reference_definition; + }*/ return { constraint: kc && kc.constraint, definition: de, @@ -1559,6 +2141,14 @@ create_constraint_foreign check_constraint_definition = kc:constraint_name? __ u:'CHECK'i __ LPAREN __ c:or_and_expr __ RPAREN __ ne:(KW_NOT? __ 'ENFORCED'i)? { + /* => { + constraint_type: 'check'; + keyword: constraint_name['keyword']; + constraint?: constraint_name['constraint']; + definition: [or_and_expr]; + enforced?: 'enforced' | 'not enforced'; + resource: 'constraint'; + }*/ const enforced = [] if (ne) enforced.push(ne[0], ne[2]) return { @@ -1573,20 +2163,31 @@ check_constraint_definition reference_definition = kc:KW_REFERENCES __ - t:table_ref_list __ + t: table_name __ de:cte_column_definition __ m:('MATCH FULL'i / 'MATCH PARTIAL'i / 'MATCH SIMPLE'i)? __ - od:on_reference? __ - ou:on_reference? { + od: on_reference? __ + ou: on_reference? { + /* => { + definition: cte_column_definition; + table: table_ref_list; + keyword: 'references'; + match: 'match full' | 'match partial' | 'match simple'; + on_action: [on_reference?]; + }*/ return { definition: de, - table: t, + table: [t], keyword: kc.toLowerCase(), - match: m && m.toLowerCase(), + match:m && m.toLowerCase(), on_action: [od, ou].filter(v => v) } } / oa:on_reference { + /* => { + on_action: [on_reference]; + } + */ return { on_action: [oa] } @@ -1601,37 +2202,152 @@ on_reference } } -view_options - = kc:('RESTRICT'i / 'CASCADE'i) { - return kc.toLowerCase() - } - reference_option = kw:KW_CURRENT_TIMESTAMP __ LPAREN __ l:expr_list? __ RPAREN { + // => { type: 'function'; name: string; args: expr_list; } return { type: 'function', - name: { name: [{ type: 'origin', value: kw }]}, + name: { name: [{ type: 'origin', value: kw }] }, args: l } } - / kc:(view_options / 'SET NULL'i / 'NO ACTION'i / 'SET DEFAULT'i / KW_CURRENT_TIMESTAMP) { + / kc:('RESTRICT'i / 'CASCADE'i / 'SET NULL'i / 'NO ACTION'i / 'SET DEFAULT'i / KW_CURRENT_TIMESTAMP) { + // => 'restrict' | 'cascade' | 'set null' | 'no action' | 'set default' | 'current_timestamp' return { type: 'origin', value: kc.toLowerCase() } } +create_constraint_trigger + = kw: KW_CREATE __ + or:(KW_OR __ KW_REPLACE)? __ + kc:KW_CONSTRAINT? __ + t:('TRIGGER'i) __ + c:ident_name __ + p:('BEFORE'i / 'AFTER'i / 'INSTEAD OF'i) __ + te:trigger_event_list __ + on:'ON'i __ + tn:table_name __ + fr:(KW_FROM __ table_name)? __ + de:trigger_deferrable? __ + fe:trigger_for_row? __ + tw:trigger_when? __ + fc:'EXECUTE'i __ e:('PROCEDURE'i / 'FUNCTION'i) __ + fct:proc_func_call { + /* + => { + type: 'create'; + replace?: string; + constraint?: string; + location: 'before' | 'after' | 'instead of'; + events: trigger_event_list; + table: table_name; + from?: table_name; + deferrable?: trigger_deferrable; + for_each?: trigger_for_row; + when?: trigger_when; + execute: { + keyword: string; + expr: proc_func_call; + }; + constraint_type: 'trigger'; + keyword: 'trigger'; + constraint_kw: 'constraint'; + resource: 'constraint'; + } + */ + return { + type: 'create', + replace: or && 'or replace', + constraint: c, + location: p && p.toLowerCase(), + events: te, + table: tn, + from: fr && fr[2], + deferrable: de, + for_each: fe, + when: tw, + execute: { + keyword: `execute ${e.toLowerCase()}`, + expr: fct + }, + constraint_type: t && t.toLowerCase(), + keyword: t && t.toLowerCase(), + constraint_kw: kc && kc.toLowerCase(), + resource: 'constraint', + } + } + +trigger_event + = kw:(KW_INSERT / KW_DELETE / KW_TRUNCATE) { + // => { keyword: 'insert' | 'delete' | 'truncate' } + const keyword = Array.isArray(kw) ? kw[0].toLowerCase() : kw.toLowerCase() + return { + keyword, + } + } + / kw:KW_UPDATE __ a:('OF'i __ column_ref_list)? { + // => { keyword: 'update'; args?: { keyword: 'of', columns: column_ref_list; }} + return { + keyword: kw && kw[0] && kw[0].toLowerCase(), + args: a && { keyword: a[0], columns: a[2] } || null + } + } + +trigger_event_list + = head:trigger_event tail:(__ KW_OR __ trigger_event)* { + // => trigger_event[]; + return createList(head, tail) + } + +trigger_deferrable + = kw:(('NOT'i)? __ 'DEFERRABLE'i) __ args:('INITIALLY IMMEDIATE'i / 'INITIALLY DEFERRED'i) { + // => { keyword: 'deferrable' | 'not deferrable'; args: 'initially immediate' | 'initially deferred' } + return { + keyword: kw && kw[0] ? `${kw[0].toLowerCase()} deferrable` : 'deferrable', + args: args && args.toLowerCase(), + } + } + +trigger_for_row + = kw:'FOR'i __ e:('EACH'i)? __ ob:('ROW'i / 'STATEMENT'i) { + // => { keyword: 'for' | 'for each'; args: 'row' | 'statement' } + return { + keyword: e ? `${kw.toLowerCase()} ${e.toLowerCase()}` : kw.toLowerCase(), + args: ob.toLowerCase() + } + } + +trigger_when + = KW_WHEN __ LPAREN __ condition:expr __ RPAREN { + // => { type: 'when'; cond: expr; parentheses: true; } + return { + type: 'when', + cond: condition, + parentheses: true, + } + } + table_options = head:table_option tail:(__ COMMA? __ table_option)* { + // => table_option[] return createList(head, tail) } create_option_character_set_kw = 'CHARACTER'i __ 'SET'i { + // => string return 'CHARACTER SET' } + create_option_character_set = kw:KW_DEFAULT? __ t:(create_option_character_set_kw / 'CHARSET'i / 'COLLATE'i) __ s:(KW_ASSIGIN_EQUAL)? __ v:ident_without_kw_type { + /* => { + keyword: 'character set' | 'charset' | 'collate' | 'default character set' | 'default charset' | 'default collate'; + symbol: '='; + value: ident_without_kw_type; + } */ return { keyword: kw && `${kw[0].toLowerCase()} ${t.toLowerCase()}` || t.toLowerCase(), symbol: s, @@ -1641,35 +2357,28 @@ create_option_character_set table_option = kw:('AUTO_INCREMENT'i / 'AVG_ROW_LENGTH'i / 'KEY_BLOCK_SIZE'i / 'MAX_ROWS'i / 'MIN_ROWS'i / 'STATS_SAMPLE_PAGES'i) __ s:(KW_ASSIGIN_EQUAL)? __ v:literal_numeric { + /* => { + keyword: 'auto_increment' | 'avg_row_length' | 'key_block_size' | 'max_rows' | 'min_rows' | 'stats_sample_pages'; + symbol: '='; + value: number; // <== literal_numeric['value'] + } */ return { keyword: kw.toLowerCase(), symbol: s, value: v.value } } - / kw:('CHECKSUM' / 'DELAY_KEY_WRITE') __ s:(KW_ASSIGIN_EQUAL) __ v:[01] { - return { - keyword: kw.toLowerCase(), - symbol: s, - value: v - } - } / create_option_character_set - / kw:(KW_COMMENT / 'CONNECTION'i / 'ENGINE_ATTRIBUTE'i / 'SECONDARY_ENGINE_ATTRIBUTE'i ) __ s:(KW_ASSIGIN_EQUAL)? __ c:literal_string { + / kw:(KW_COMMENT / 'CONNECTION'i) __ s:(KW_ASSIGIN_EQUAL)? __ c:literal_string { + // => { keyword: 'connection' | 'comment'; symbol: '='; value: string; } return { keyword: kw.toLowerCase(), symbol: s, value: `'${c.value}'` } } - / type:('DATA'i / 'INDEX'i) __ 'DIRECTORY'i __ s:(KW_ASSIGIN_EQUAL)? __ c:literal_string { - return { - keyword: type.toLowerCase() + " directory", - symbol: s, - value: `'${c.value}'` - } - } / kw:'COMPRESSION'i __ s:(KW_ASSIGIN_EQUAL)? __ v:("'"('ZLIB'i / 'LZ4'i / 'NONE'i)"'") { + // => { keyword: 'compression'; symbol?: '='; value: "'ZLIB'" | "'LZ4'" | "'NONE'" } return { keyword: kw.toLowerCase(), symbol: s, @@ -1677,25 +2386,25 @@ table_option } } / kw:'ENGINE'i __ s:(KW_ASSIGIN_EQUAL)? __ c:ident_name { + // => { keyword: 'engine'; symbol?: '='; value: string; } return { keyword: kw.toLowerCase(), symbol: s, value: c.toUpperCase() } } - / kw:'ROW_FORMAT'i __ s:(KW_ASSIGIN_EQUAL)? __ c:(KW_DEFAULT / 'DYNAMIC'i / 'FIXED'i / 'COMPRESSED'i / 'REDUNDANT'i / 'COMPACT'i) { + / KW_PARTITION __ KW_BY __ v:expr { + // => { keyword: 'partition by'; value: expr; } return { - keyword: kw.toLowerCase(), - symbol: s, - value: c.toUpperCase() + keyword: 'partition by', + value: v } } ALTER_ADD_FULLETXT_SPARITAL_INDEX - = KW_ADD __ - fsid:create_fulltext_spatial_index_definition - { + = KW_ADD __ fsid:create_fulltext_spatial_index_definition { + // => create_fulltext_spatial_index_definition & { action: 'add'; type: 'alter' } return { action: 'add', type: 'alter', @@ -1707,6 +2416,13 @@ rename_stmt = KW_RENAME __ KW_TABLE __ t:table_to_list { + /* + export interface rename_stmt_node { + type: 'rename'; + table: table_to_list; + } + => AstStatement + */ t.forEach(tg => tg.forEach(dt => dt.table && tableList.add(`rename::${dt.db}::${dt.table}`))) return { tableList: Array.from(tableList), @@ -1722,7 +2438,14 @@ set_stmt = KW_SET __ kw: (KW_GLOBAL / KW_SESSION / KW_LOCAL / KW_PERSIST / KW_PERSIST_ONLY)? __ a: assign_stmt_list { - a.keyword = kw + /* + export interface set_stmt_node { + type: 'set'; + keyword?: 'GLOBAL' | 'SESSION' | 'LOCAL' | 'PERSIST' | 'PERSIST_ONLY' | undefined; + expr: assign_stmt_list; + } + => AstStatement + */ return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), @@ -1734,61 +2457,58 @@ set_stmt } } -unlock_stmt - = KW_UNLOCK __ KW_TABLES { +lock_mode + = "IN"i __ + m:("ACCESS SHARE"i / "ROW SHARE"i / "ROW EXCLUSIVE"i / "SHARE UPDATE EXCLUSIVE"i / "SHARE ROW EXCLUSIVE"i / "EXCLUSIVE"i / "ACCESS EXCLUSIVE"i / "SHARE"i) __ + "MODE"i { + // => { mode: string; } return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'unlock', - keyword: 'tables' - } - } - } - -lock_type - = "READ"i __ s:("LOCAL"i)? { - return { - type: 'read', - suffix: s && 'local' - } - } - / p:("LOW_PRIORITY"i)? __ "WRITE"i { - return { - type: 'write', - prefix: p && 'low_priority' - } - } - -lock_table - = t:table_base __ lt:lock_type { - tableList.add(`lock::${t.db}::${t.table}`) - return { - table: t, - lock_type: lt + mode: `in ${m.toLowerCase()} mode` } } -lock_table_list - = head:lock_table tail:(__ COMMA __ lock_table)* { - return createList(head, tail); - } - lock_stmt - = KW_LOCK __ KW_TABLES __ ltl:lock_table_list { + = KW_LOCK __ + k:KW_TABLE? __ + t:table_ref_list __ + lm:lock_mode? __ + nw:("NOWAIT"i)? { + + /* + export interface lock_stmt_node { + type: 'lock'; + keyword: 'lock'; + tables: [[table_base], ...{table: table_ref}[]]; // see table_ref_list + lock_mode?: lock_mode; + nowait?: 'NOWAIT'; + } + => AstStatement + */ + + if (t) t.forEach(tt => tableList.add(`lock::${tt.db}::${tt.table}`)) return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { type: 'lock', - keyword: 'tables', - tables: ltl + keyword: k && k.toLowerCase(), + tables: t.map((table) => ({ table })), + lock_mode: lm, + nowait: nw } } } call_stmt - = KW_CALL __ e:proc_func_call { + = KW_CALL __ + e: proc_func_call { + /* + export interface call_stmt_node { + type: 'call'; + expr: proc_func_call; + } + => AstStatement + */ return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), @@ -1800,275 +2520,601 @@ call_stmt } show_stmt - = KW_SHOW __ t:('BINARY'i / 'MASTER'i) __ 'LOGS'i { + = KW_SHOW __ 'TABLES'i { return { + /* + export interface show_stmt_node { + type: 'show'; + keyword: 'tables' | 'var'; + var?: without_prefix_var_decl; + } + => AstStatement + */ tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { type: 'show', - suffix: 'logs', - keyword: t.toLowerCase() + keyword: 'tables' } } } - / KW_SHOW __ KW_TABLES { + / KW_SHOW __ c:without_prefix_var_decl { return { + // => AstStatement tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { type: 'show', - keyword: 'tables' + keyword: 'var', + var: c, } } } - / KW_SHOW __ keyword:('TRIGGERS'i / 'STATUS'i / 'PROCESSLIST'i) { + +deallocate_stmt + = KW_DEALLOCATE __ p:('PREPARE'i)? __ i:(ident_name / KW_ALL) { return { + /* + export interface deallocate_stmt_node { + type: 'deallocate'; + keyword: 'PREPARE' | undefined; + expr: { type: 'default', value: string } + } + => AstStatement + */ tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { - type: 'show', - keyword: keyword.toLowerCase() + type: 'deallocate', + keyword: p, + expr: { type: 'default', value: i } + }, + } + } +priv_type_table + = p:(KW_SELECT / KW_INSERT / KW_UPDATE / KW_DELETE / KW_TRUNCATE / KW_REFERENCES / 'TRIGGER'i) { + /* export interface origin_str_stmt { + type: 'origin'; + value: string; } + => origin_str_stmt + */ + return { + type: 'origin', + value: Array.isArray(p) ? p[0] : p } } - / KW_SHOW __ keyword:('PROCEDURE'i / 'FUNCTION'i) __ 'STATUS'i { +priv_type_sequence + = p:('USAGE'i / KW_SELECT / KW_UPDATE) { + // => origin_str_stmt return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'show', - keyword: keyword.toLowerCase(), - suffix: 'status', + type: 'origin', + value: Array.isArray(p) ? p[0] : p + } + } +priv_type_database + = p:(KW_CREATE / 'CONNECT'i / KW_TEMPORARY / KW_TEMP) { + // => origin_str_stmt + return { + type: 'origin', + value: Array.isArray(p) ? p[0] : p + } + } +prive_type_all + = KW_ALL p:(__ 'PRIVILEGES'i)? { + // => origin_str_stmt + return { + type: 'origin', + value: p ? 'all privileges' : 'all' + } + } +prive_type_usage + = p:'USAGE'i { + // => origin_str_stmt + return { + type: 'origin', + value: p + } + } + / prive_type_all +prive_type_execute + = p:'EXECUTE'i { + // => origin_str_stmt + return { + type: 'origin', + value: p + } + } + / prive_type_all +priv_type + = priv_type_table / priv_type_sequence / priv_type_database / prive_type_usage / prive_type_execute +priv_item + = p:priv_type __ c:(LPAREN __ column_ref_list __ RPAREN)? { + // => { priv: priv_type; columns: column_ref_list; } + return { + priv: p, + columns: c && c[2], + } + } +priv_list + = head:priv_item tail:(__ COMMA __ priv_item)* { + // => priv_item[] + return createList(head, tail) + } +object_type + = o:(KW_TABLE / 'SEQUENCE'i / 'DATABASE'i / 'DOMAIN' / 'FUNCTION' / 'PROCEDURE'i / 'ROUTINE'i / 'LANGUAGE'i / 'LARGE'i / 'SCHEMA') { + // => origin_str_stmt + return { + type: 'origin', + value: o.toUpperCase() + } + } + / KW_ALL __ i:('TABLES'i / 'SEQUENCE'i / 'FUNCTIONS'i / 'PROCEDURES'i / 'ROUTINES'i) __ KW_IN __ KW_SCHEMA { + // => origin_str_stmt + return { + type: 'origin', + value: `all ${i} in schema` + } + } +priv_level + = prefix:(ident __ DOT)? __ name:(ident / STAR) { + // => { prefix: string; name: string; } + return { + prefix: prefix && prefix[0], + name, } } +priv_level_list + = head:priv_level tail:(__ COMMA __ priv_level)* { + // => priv_level[] + return createList(head, tail) + } +user_or_role + = g:KW_GROUP? __ i:ident { + // => origin_str_stmt + const name = g ? `${group} ${i}` : i + return { + name: { type: 'origin', value: name }, + } } - / KW_SHOW __ 'BINLOG'i __ 'EVENTS'i __ ins:in_op_right? __ from: from_clause? __ limit: limit_clause? { + / i:('PUBLIC'i / KW_CURRENT_ROLE / KW_CURRENT_USER / KW_SESSION_USER) { + // => origin_str_stmt + return { + name: { type: 'origin', value: i }, + } + } +user_or_role_list + = head:user_or_role tail:(__ COMMA __ user_or_role)* { + // => user_or_role[] + return createList(head, tail) + } +with_grant_option + = KW_WITH __ 'GRANT'i __ 'OPTION'i { + // => origin_str_stmt + return { + type: 'origin', + value: 'with grant option', + } + } +with_admin_option + = KW_WITH __ 'ADMIN'i __ 'OPTION'i { + // => origin_str_stmt + return { + type: 'origin', + value: 'with admin option', + } + } +grant_revoke_keyword + = 'GRANT'i { + // => { type: 'grant' } + return { + type: 'grant' + } + } + / 'REVOKE'i __ i:('GRANT'i __ 'OPTION'i __ 'FOR'i)? { + // => { type: 'revoke'; grant_option_for?: origin_str_stmt; } + return { + type: 'revoke', + grant_option_for: i && { type: 'origin', value: 'grant option for' } + } + } + + +grant_revoke_stmt + = g:grant_revoke_keyword __ pl:priv_list __ KW_ON __ ot:object_type? __ le:priv_level_list __ t:(KW_TO / KW_FROM) &{ + const obj = { revoke: 'from', grant: 'to' } + return obj[g.type].toLowerCase() === t[0].toLowerCase() + } __ to:user_or_role_list __ wo:with_grant_option? { + /* export interface grant_revoke_stmt_t { + type: string; + grant_option_for?: origin_str_stmt; + keyword: 'priv'; + objects: priv_list; + on: { + object_type?: object_type; + priv_level: priv_level_list; + }; + to_from: 'to' | 'from'; + user_or_roles?: user_or_role_list; + with?: with_grant_option; + } + => AstStatement + */ return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { - type: 'show', - suffix: 'events', - keyword: 'binlog', - in: ins, - from, - limit, + ...g, + keyword: 'priv', + objects: pl, + on: { + object_type: ot, + priv_level: le + }, + to_from: t[0], + user_or_roles: to, + with: wo } } } - / KW_SHOW __ k:(('CHARACTER'i __ 'SET'i) / 'COLLATION'i / 'DATABASES'i) __ e:(like_op_right / where_clause)? { - let keyword = Array.isArray(k) && k || [k] + / g:grant_revoke_keyword __ o:ident_list __ t:(KW_TO / KW_FROM) &{ + const obj = { revoke: 'from', grant: 'to' } + return obj[g.type].toLowerCase() === t[0].toLowerCase() + } __ to:user_or_role_list __ wo:with_admin_option? { + // => AstStatement return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { - type: 'show', - suffix: keyword[2] && keyword[2].toLowerCase(), - keyword: keyword[0].toLowerCase(), - expr: e + ...g, + keyword: 'role', + objects: o.map(name => ({ priv: { type: 'string', value: name }})), + to_from: t[0], + user_or_roles: to, + with: wo } } } - / KW_SHOW __ keyword:('COLUMNS'i / 'INDEXES'i / "INDEX"i) __ from:from_clause { +elseif_stmt + = 'ELSEIF'i __ e:expr __ 'THEN'i __ ia:crud_stmt __ s:SEMICOLON? { + // => { type: 'elseif'; boolean_expr: expr; then: crud_stmt; semicolon?: string; } return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'show', - keyword: keyword.toLowerCase(), - from - } - }; + type: 'elseif', + boolean_expr: e, + then: ia, + semicolon: s + } + } - / KW_SHOW __ KW_CREATE __ k:(KW_VIEW / KW_TABLE / 'EVENT'i / KW_TRIGGER / 'PROCEDURE'i) __ t:table_name { - const suffix = k.toLowerCase() - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'show', - keyword: 'create', - suffix, - [suffix]: t - } - }; +elseif_stmt_list + = head:elseif_stmt tail:(__ elseif_stmt)* { + // => elseif_stmt[] + return createList(head, tail, 1) } - / show_grant_stmt - -show_grant_stmt - = KW_SHOW __ 'GRANTS'i __ f:show_grant_for? { +if_else_stmt + = 'IF'i __ ie:expr __ 'THEN'i __ ia:crud_stmt __ s:SEMICOLON? __ ei:elseif_stmt_list? __ el:(KW_ELSE __ crud_stmt)? __ es:SEMICOLON? __ 'END'i __ 'IF'i { + /* export interface if_else_stmt_t { + type: 'if'; + keyword: 'if'; + boolean_expr: expr; + semicolons: string[]; + if_expr: crud_stmt; + elseif_expr: elseif_stmt[]; + else_expr: crud_stmt; + prefix: literal_string; + suffix: literal_string; + } + => AstStatement + */ return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { - type: 'show', - keyword: 'grants', - for: f, + type: 'if', + keyword: 'if', + boolean_expr: ie, + semicolons: [s || '', es || ''], + prefix: { + type: 'origin', + value: 'then' + }, + if_expr: ia, + elseif_expr: ei, + else_expr: el && el[2], + suffix: { + type: 'origin', + value: 'end if', + } } } } - -show_grant_for - = 'FOR'i __ n:ident __ h:(KW_VAR__PRE_AT __ ident)? __ u:show_grant_for_using? { +raise_level + // => string + = 'DEBUG'i / 'LOG'i / 'INFO'i / 'NOTICE'i / 'WARNING'i / 'EXCEPTION'i +raise_opt + = KW_USING __ o:('MESSAGE'i / 'DETAIL'i / 'HINT'i / 'ERRCODE'i / 'COLUMN'i / 'CONSTRAINT'i / 'DATATYPE'i / 'TABLE'i / 'SCHEMA'i) __ KW_ASSIGIN_EQUAL __ e:expr es:(__ COMMA __ expr)* { + // => { type: 'using'; option: string; symbol: '='; expr: expr[]; } + const expr = [e] + if (es) es.forEach(ex => expr.push(ex[3])) return { - user: n, - host: h && h[2], - role_list: u + type: 'using', + option: o, + symbol: '=', + expr } } - -show_grant_for_using - = KW_USING __ l:show_grant_for_using_list { - return l +raise_item + = format:literal_string e:(__ COMMA __ proc_primary)* { + // => IGNORE + return { + type: 'format', + keyword: format, + expr: e && e.map(ex => ex[3]) + } } - -show_grant_for_using_list - = head:ident tail:(__ COMMA __ ident)* { - return createList(head, tail); + / 'SQLSTATE'i __ ss:literal_string { + // => IGNORE + return { + type: 'sqlstate', + keyword: { type: 'origin', value: 'SQLSTATE' }, + expr: [ss], + } } - -desc_stmt - = (KW_DESC / KW_DESCRIBE) __ t:ident { + / n:ident { + // => IGNORE + return { + type: 'condition', + expr: [{ type: 'default', value: n }] + } + } +raise_stmt + = 'RAISE'i __ l:raise_level? __ r:raise_item? __ using:raise_opt? { + /* export interface raise_stmt_t { + type: 'raise'; + level?: string; + raise?: raise_item; + using?: raise_opt; + } + => AstStatement + */ return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { - type: 'desc', - table: t + type: 'raise', + level: l, + using, + raise: r, } - }; + } } - -explain_stmt - = KW_EXPLAIN __ t:select_stmt_nake { +execute_stmt + = 'EXECUTE'i __ name:ident __ a:(LPAREN __ proc_primary_list __ RPAREN)? { + /* export interface execute_stmt_t { + type: 'execute'; + name: string; + args?: { type: expr_list; value: proc_primary_list; } + } + => AstStatement + */ return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { - type: 'explain', - expr: t + type: 'execute', + name, + args: a && { type: 'expr_list', value: a[2] } } } } - -priv_type_table - = p:(KW_ALL / KW_ALTER / KW_CREATE __ 'VIEW'i / KW_CREATE / KW_DELETE / KW_DROP / 'GRANT'i __ 'OPTION'i / KW_INDEX / KW_INSERT / KW_REFERENCES / KW_SELECT / KW_SHOW __ KW_VIEW / KW_TRIGGER / KW_UPDATE) { +for_label + = 'FOR'i { + // => { label?: string; keyword: 'for'; } + return { + label: null, + keyword: 'for', + } + } + / label:ident __ 'FOR'i { + // => IGNORE + return { + label, + keyword: 'for' + } + } +for_loop_stmt + = f:for_label __ target:ident __ KW_IN __ query:select_stmt __ 'LOOP'i __ stmts:multiple_stmt __ KW_END __ 'LOOP'i __ label:ident? &{ + if (f.label && label && f.label === label) return true + if (!f.label && !label) return true + return false + } { + /* export interface for_loop_stmt_t { + type: 'for'; + label?: string + target: string; + query: select_stmt; + stmts: multiple_stmt; + } + => AstStatement + */ + return { + tableList: Array.from(tableList), + columnList: columnListTableAlias(columnList), + ast: { + type: 'for', + label, + target, + query, + stmts: stmts.ast, + } + } + } +transaction_mode_isolation_level + = 'SERIALIZABLE'i { + // => { type: 'origin'; value: string; } return { type: 'origin', - value: Array.isArray(p) ? p[0] : p + value: 'serializable' } } -priv_type_routine - = p:(KW_ALTER __ 'ROUTINE'i / 'EXECUTE'i / 'GRANT'i __ 'OPTION'i / KW_CREATE __ 'ROUTINE'i) { + / 'REPEATABLE'i __ 'READ'i { + // => ignore return { type: 'origin', - value: Array.isArray(p) ? p[0] : p + value: 'repeatable read' } } -priv_type - = priv_type_table / priv_type_routine -priv_item - = p:priv_type __ c:(LPAREN __ column_ref_list __ RPAREN)? { + / 'READ'i __ e:('COMMITTED'i / 'UNCOMMITTED'i) { + // => ignore return { - priv: p, - columns: c && c[2], + type: 'origin', + value: `read ${e.toLowerCase()}` } } -priv_list - = head:priv_item tail:(__ COMMA __ priv_item)* { - return createList(head, tail) + +transaction_mode + = 'ISOLATION'i __ 'LEVEL'i __ l:transaction_mode_isolation_level { + // => { type: 'origin'; value: string; } + return { + type: 'origin', + value: `isolation level ${l.value}` } -object_type - = o:(KW_TABLE / 'FUNCTION'i / 'PROCEDURE'i) { + } + / 'READ'i __ e:('WRITE'i / 'ONLY'i) { + // => ignore return { type: 'origin', - value: o.toUpperCase() + value: `read ${e.toLowerCase()}` } } -priv_level - = prefix:((ident / STAR) __ DOT)? __ name:(ident / STAR) { - return { - prefix: prefix && prefix[0], - name, - } + / n:KW_NOT? __ 'DEFERRABLE'i { + // => ignore + return { + type: 'origin', + value: n ? 'not deferrable' : 'deferrable' } -user_or_role - = i:ident __ ho:('@' __ ident)? { + } + +transaction_mode_list + = head: transaction_mode tail:(__ COMMA __ transaction_mode)* { + // => transaction_mode[] + return createList(head, tail) + } +transaction_stmt + = k:('commit'i / 'rollback'i) { + /* export interface transaction_stmt_t { + type: 'transaction'; + expr: { + action: { + type: 'origin', + value: string + }; + keyword?: string; + modes?: transaction_mode[]; + } + } + => AstStatement + */ return { - name: { type: 'single_quote_string', value: i }, - host: ho ? { type: 'single_quote_string', value: ho[2] } : null + type: 'transaction', + expr: { + action: { + type: 'origin', + value: k + }, + } } } -user_or_role_list - = head:user_or_role tail:(__ COMMA __ user_or_role)* { - return createList(head, tail) + / 'begin'i __ k:('WORK'i / 'TRANSACTION'i)? __ m:transaction_mode_list? { + // => ignore + return { + type: 'transaction', + expr: { + action: { + type: 'origin', + value: 'begin' + }, + keyword: k, + modes: m + } } -with_grant_option - = KW_WITH __ 'GRANT'i __ 'OPTION'i { + } + / 'start'i __ k:'transaction'i __ m:transaction_mode_list? { + // => ignore return { - type: 'origin', - value: 'with grant option', + type: 'transaction', + expr: { + action: { + type: 'origin', + value: 'start' + }, + keyword: k, + modes: m + } } } -with_admin_option - = KW_WITH __ 'ADMIN'i __ 'OPTION'i { +comment_on_option + = t:(KW_TABLE / KW_VIEW / KW_TABLESPACE) __ name:table_name { + // => { type: string; name: table_name; } return { - type: 'origin', - value: 'with admin option', + type: t.toLowerCase(), + name, } } -grant_stmt - = 'GRANT'i __ pl:priv_list __ KW_ON __ ot:object_type? __ le:priv_level __ t:KW_TO __ to:user_or_role_list __ wo:with_grant_option? { + / t:(KW_COLUMN) __ name:column_ref { + // => { type: string; name: column_ref; } return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'grant', - keyword: 'priv', - objects: pl, - on: { - object_type: ot, - priv_level: [le] - }, - to_from: t[0], - user_or_roles: to, - with: wo - } + type: t.toLowerCase(), + name, } } - / 'GRANT' __ 'PROXY' __ KW_ON __ on:user_or_role __ t:KW_TO __ to:user_or_role_list __ wo:with_admin_option? { + / t:(KW_INDEX / KW_COLLATION / KW_TABLESPACE / KW_SCHEMA / 'DOMAIN'i / KW_DATABASE / 'ROLE'i / 'SEQUENCE'i / 'SERVER'i / 'SUBSCRIPTION'i ) __ name:ident_type { + // => { type: string; name: ident; } return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'grant', - keyword: 'proxy', - objects: [{ priv: { type: 'origin', value: 'proxy' }}], - on, - to_from: t[0], - user_or_roles: to, - with: wo - } + type: t.toLowerCase(), + name, } } - / 'GRANT' __ o:ident_list __ t:KW_TO __ to:user_or_role_list __ wo:with_admin_option? { + +comment_on_is + = 'IS'i __ e:(literal_string / literal_null) { + // => { keyword: 'is'; expr: literal_string | literal_null; } return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: 'grant', - keyword: 'role', - objects: o.map(name => ({ priv: { type: 'string', value: name }})), - to_from: t[0], - user_or_roles: to, - with: wo + keyword: 'is', + expr: e, + } + } +comment_on_stmt + = 'COMMENT'i __ 'ON'i __ co:comment_on_option __ is:comment_on_is { + /* export interface comment_on_stmt_t { + type: 'comment'; + target: comment_on_option; + expr: comment_on_is; } + => AstStatement + */ + return { + type: 'comment', + keyword: 'on', + target: co, + expr: is, } } - select_stmt - = select_stmt_nake + = KW_SELECT __ ';' { + // => { type: 'select'; } + return { + type: 'select', + } + } + / select_stmt_nake / s:('(' __ select_stmt __ ')') { + /* + export interface select_stmt_node extends select_stmt_nake { + parentheses: true; + } + => select_stmt_node + */ return { ...s[2], parentheses_symbol: true, @@ -2077,79 +3123,48 @@ select_stmt with_clause = KW_WITH __ head:cte_definition tail:(__ COMMA __ cte_definition)* { + // => cte_definition[] return createList(head, tail); } / __ KW_WITH __ KW_RECURSIVE __ cte:cte_definition { + // => [cte_definition & { recursive: true; }] cte.recursive = true; return [cte] } cte_definition - = name:(literal_string / ident_name / table_name) __ columns:cte_column_definition? __ KW_AS __ LPAREN __ stmt:set_op_stmt __ RPAREN { + = name:(literal_string / ident_name) __ columns:cte_column_definition? __ KW_AS __ LPAREN __ stmt:crud_stmt __ RPAREN { + // => { name: { type: 'default'; value: string; }; stmt: crud_stmt; columns?: cte_column_definition; } if (typeof name === 'string') name = { type: 'default', value: name } - if (name.table) name = { type: 'default', value: name.table } - return { name, stmt, columns }; - } + return { name, stmt: stmt.ast, columns }; + } cte_column_definition - = LPAREN __ l:column_ref_index __ RPAREN { + = LPAREN __ l:column_ref_list __ RPAREN { + // => column_ref_list return l } -column_idx_ref - = col:column_without_kw __ LPAREN __ l:[0-9]+ __ RPAREN __ ob:(KW_ASC / KW_DESC)? { - return { - type: 'column_ref', - column: col, - suffix: `(${parseInt(l.join(''), 10)})`, - order_by: ob, - ...getLocationObject(), - }; - } - / col:column_without_kw __ ob:(KW_ASC / KW_DESC)? { - return { - type: 'column_ref', - column: col, - order_by: ob, - ...getLocationObject(), - }; - } - -column_ref_idx_list - = head:column_idx_ref tail:(__ COMMA __ column_idx_ref)* { - return createList(head, tail); - } - -cte_idx_column_definition - = LPAREN __ l:(column_ref_idx_list / expr_list) __ RPAREN { - if (l.type) return l.value - return l +distinct_on + = d:KW_DISTINCT __ o:KW_ON __ LPAREN __ c:column_list_items __ RPAREN { + // => {type: string; columns: column_list_items;} + console.lo + return { + type: `${d} ON`, + columns: c } - -for_update - = fu:('FOR'i __ KW_UPDATE) { - return `${fu[0]} ${fu[2][0]}` } - -lock_in_share_mode - = m:('LOCK'i __ 'IN'i __ 'SHARE'i __ 'MODE'i) { - return `${m[0]} ${m[2]} ${m[4]} ${m[6]}` - } - -lock_option - = w:('WAIT'i __ literal_numeric) { return `${w[0]} ${w[2].value}` } - / nw:'NOWAIT'i - / sl:('SKIP'i __ 'LOCKED'i) { return `${sl[0]} ${sl[2]}` } - -locking_read - = t:(for_update / lock_in_share_mode) __ lo:lock_option? { - return t + (lo ? ` ${lo}` : '') + / d:KW_DISTINCT? { + // => { type: string | undefined; } + return { + type: d, + } } select_stmt_nake = __ cte:with_clause? __ KW_SELECT ___ opts:option_clause? __ - d:KW_DISTINCT? __ + d:distinct_on? __ c:column_clause __ ci:into_clause? __ f:from_clause? __ @@ -2158,11 +3173,24 @@ select_stmt_nake g:group_by_clause? __ h:having_clause? __ o:order_by_clause? __ - ce:collate_expr? __ l:limit_clause? __ - lr:locking_read? __ win:window_clause? __ li:into_clause? { + /* => { + with?: with_clause; + type: 'select'; + options?: option_clause; + distinct?: {type: string; columns?: column_list; }; + columns: column_clause; + from?: from_clause; + into?: into_clause; + where?: where_clause; + groupby?: group_by_clause; + having?: having_clause; + orderby?: order_by_clause; + limit?: limit_clause; + window?: window_clause; + }*/ if ((ci && fi) || (ci && li) || (fi && li) || (ci && fi && li)) { throw new Error('A given SQL statement can contain at most one INTO clause') } @@ -2183,16 +3211,14 @@ select_stmt_nake having: h, orderby: o, limit: l, - locking_read: lr && lr, window: win, - collate: ce, - ...getLocationObject(), }; } // MySQL extensions to standard SQL option_clause = head:query_option tail:(__ query_option)* { + // => query_option[] const opts = [head]; for (let i = 0, l = tail.length; i < l; ++i) { opts.push(tail[i][1]); @@ -2207,10 +3233,19 @@ query_option / OPT_SQL_BIG_RESULT / OPT_SQL_SMALL_RESULT / OPT_SQL_BUFFER_RESULT - ) { return option; } + ) { + // => 'SQL_CALC_FOUND_ROWS'| 'SQL_CACHE'| 'SQL_NO_CACHE'| 'SQL_BIG_RESULT'| 'SQL_SMALL_RESULT'| 'SQL_BUFFER_RESULT' + return option; + } +column_list_items + = head:column_list_item tail:(__ COMMA __ column_list_item)* { + // => column_list_item[] + return createList(head, tail); + } column_clause = head: (KW_ALL / (STAR !ident_start) / STAR) tail:(__ COMMA __ column_list_item)* { + // => 'ALL' | '*' | column_list_item[] columnList.add('select::null::(.*)') const item = { expr: { @@ -2218,88 +3253,107 @@ column_clause table: null, column: '*' }, - as: null, - ...getLocationObject(), + as: null } if (tail && tail.length > 0) return createList(item, tail) return [item] } - / head:column_list_item tail:(__ COMMA __ column_list_item)* { - return createList(head, tail); - } + / column_list_items -fulltext_search_mode - = KW_IN __ 'NATURAL'i __ 'LANGUAGE'i __ 'MODE'i __ 'WITH'i __ 'QUERY'i __ 'EXPANSION'i { - return { type: 'origin', value: 'IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION' } - } - / KW_IN __ 'NATURAL'i __ 'LANGUAGE'i __ 'MODE'i { - return { type: 'origin', value: 'IN NATURAL LANGUAGE MODE' } +array_index + = LBRAKE __ n:(literal_numeric / literal_string) __ RBRAKE { + // => { brackets: boolean, number: number } + return { + brackets: true, + index: n + } } - / KW_IN __ 'BOOLEAN'i __ 'MODE'i { - return { type: 'origin', value: 'IN BOOLEAN MODE' } + +array_index_list + = head:array_index tail:(__ array_index)* { + // => array_index[] + return createList(head, tail, 1) } - / KW_WITH __ 'QUERY'i __ 'EXPANSION'i { - return { type: 'origin', value: 'WITH QUERY EXPANSION' } + +expr_item + = e:binary_column_expr __ a:array_index_list? { + // => binary_column_expr & { array_index: array_index } + if (a) e.array_index = a + return e } -fulltext_search - = 'MATCH'i __ LPAREN __ c:column_ref_list __ RPAREN __ 'AGAINST' __ LPAREN __ e:expr __ mo:fulltext_search_mode? __ RPAREN __ as:alias_clause? { - const expr = { - against: 'against', - columns: c, - expr: e, - match: 'match', - mode: mo, - type: 'fulltext_search', - as, - } - return expr +cast_data_type + = p:'"'? t: data_type s:'"'? { + // => data_type & { quoted?: string } + if ((p && !s) || (!p && s)) throw new Error('double quoted not match') + if (p && s) t.quoted = '"' + return t } column_list_item - = fs:fulltext_search { - const { as, ...expr } = fs - return { expr, as } + = c:string_constants_escape { + // => { expr: expr; as: null; } + return { expr: c, as: null } } - / db:ident __ DOT __ table:ident __ DOT __ STAR { - columnList.add(`select::${db}::${table}::(.*)`); + / e:(column_ref_quoted / expr_item) __ s:KW_DOUBLE_COLON __ t:cast_data_type __ tail:(__ (additive_operator / multiplicative_operator) __ expr_item)* __ alias:alias_clause? { + // => { type: 'cast'; expr: expr; symbol: '::'; target: cast_data_type; as?: null; } + return { + as: alias, + type: 'cast', + expr: e, + symbol: '::', + target: t, + tail: tail && tail[0] && { operator: tail[0][1], expr: tail[0][3] }, + } + } + / tbl:ident_type __ DOT pro:(ident_without_kw_type __ DOT)? __ STAR { + // => { expr: column_ref; as: null; } + const mid = pro && pro[0] + let schema + if (mid) { + schema = tbl + tbl = mid + } + columnList.add(`select::${tbl ? tbl.value : null}::(.*)`) + const column = '*' return { expr: { type: 'column_ref', - db: db, - table: table, - column: '*' + table: tbl, + schema, + column, }, - as: null, - ...getLocationObject(), - }; + as: null + } } - / tbl:(ident __ DOT)? __ STAR { + / tbl:(ident_type __ DOT)? __ STAR { + // => { expr: column_ref; as: null; } const table = tbl && tbl[0] || null - columnList.add(`select::${table}::(.*)`); + columnList.add(`select::${table ? table.value : null}::(.*)`); return { expr: { type: 'column_ref', - table, + table: table, column: '*' }, - as: null, - ...getLocationObject(), + as: null }; } - / a:select_assign_stmt { - return { expr: a, as: null } - } - / e:binary_column_expr __ alias:alias_clause? { - return { expr: e, as: alias }; - } + / e:expr_item __ alias:alias_clause? { + // => { type: 'expr'; expr: expr; as?: alias_clause; } + return { type: 'expr', expr: e, as: alias }; + } + +value_alias_clause + = KW_AS? __ i:alias_ident { /*=>alias_ident*/ return i; } alias_clause - = KW_AS __ i:alias_ident { return i; } - / KW_AS? __ i:ident { return i; } + = KW_AS __ i:alias_ident { /*=>alias_ident*/ return i; } + / KW_AS? __ i:alias_ident { /*=>alias_ident*/ return i; } into_clause = KW_INTO __ v:var_decl_list { + // => { keyword: 'var'; type: 'into'; expr: var_decl_list; } return { keyword: 'var', type: 'into', @@ -2307,6 +3361,7 @@ into_clause } } / KW_INTO __ k:('OUTFILE'i / 'DUMPFILE'i)? __ f:(literal_string / ident) { + // => { keyword: 'var'; type: 'into'; expr: literal_string | ident; } return { keyword: k, type: 'into', @@ -2315,29 +3370,38 @@ into_clause } from_clause - = KW_FROM __ l:table_ref_list { return l; } + = KW_FROM __ l:table_ref_list { /*=>table_ref_list*/return l; } table_to_list = head:table_to_item tail:(__ COMMA __ table_to_item)* { + // => table_to_item[] return createList(head, tail); } table_to_item - = head:table_name __ KW_TO __ tail:table_name { + = head:table_name __ KW_TO __ tail: (table_name) { + // => table_name[] return [head, tail] } index_type - = KW_USING __ - t:("BTREE"i / "HASH"i) { + = KW_USING __ t:("BTREE"i / "HASH"i / "GIST"i / "GIN"i) { + // => { keyword: 'using'; type: 'btree' | 'hash' | 'gist' | 'gin' } return { keyword: 'using', type: t.toLowerCase(), } } +index_options_list + = head:index_option tail:(__ COMMA __ index_option)* { + // => index_option[] + return createList(head, tail) + } + index_options = head:index_option tail:(__ index_option)* { + // => index_option[] const result = [head]; for (let i = 0; i < tail.length; i++) { result.push(tail[i][1]); @@ -2347,20 +3411,31 @@ index_options index_option = k:KW_KEY_BLOCK_SIZE __ e:(KW_ASSIGIN_EQUAL)? __ kbs:literal_numeric { + // => { type: 'key_block_size'; symbol: '='; expr: number; } return { type: k.toLowerCase(), symbol: e, expr: kbs + } + } + / k:ident_name __ e:KW_ASSIGIN_EQUAL __ kbs:(literal_numeric / ident) { + // => { type: ident_name; symbol: '='; expr: number | {type: 'origin'; value: ident; }; } + return { + type: k.toLowerCase(), + symbol: e, + expr: typeof kbs === 'string' && { type: 'origin', value: kbs } || kbs }; } / index_type / "WITH"i __ "PARSER"i __ pn:ident_name { + // => { type: 'with parser'; expr: ident_name } return { type: 'with parser', expr: pn } } / k:("VISIBLE"i / "INVISIBLE"i) { + // => { type: 'visible'; expr: 'visible' } | { type: 'invisible'; expr: 'invisible' } return { type: k.toLowerCase(), expr: k.toLowerCase() @@ -2371,6 +3446,7 @@ index_option table_ref_list = head:table_base tail:table_ref* { + // => [table_base, ...table_ref[]] tail.unshift(head); tail.forEach(tableInfo => { const { table, as } = tableInfo @@ -2382,22 +3458,30 @@ table_ref_list } table_ref - = __ COMMA __ t:table_base { return t; } - / __ t:table_join { return t; } - + = __ COMMA __ t:table_base { /* => table_base */ return t; } + / __ t:table_join { /* => table_join */ return t; } table_join - = op:join_op __ t:table_base __ KW_USING __ LPAREN __ head:ident_name tail:(__ COMMA __ ident_name)* __ RPAREN { + = op:join_op __ t:table_base __ KW_USING __ LPAREN __ head:ident_without_kw tail:(__ COMMA __ ident_name)* __ RPAREN { + // => table_base & {join: join_op; using: ident_name[]; } t.join = op; t.using = createList(head, tail); return t; } / op:join_op __ t:table_base __ expr:on_clause? { + // => table_base & {join: join_op; on?: on_clause; } t.join = op; t.on = expr; return t; } - / op:(join_op / set_op) __ LPAREN __ stmt:set_op_stmt __ RPAREN __ alias:alias_clause? __ expr:on_clause? { + / op:(join_op / set_op) __ LPAREN __ stmt:(union_stmt / table_ref_list) __ RPAREN __ alias:alias_clause? __ expr:on_clause? { + /* => { + expr: (union_stmt | table_ref_list) & { parentheses: true; }; + as?: alias_clause; + join: join_op | set_op; + on?: on_clause; + }*/ + if (Array.isArray(stmt)) stmt = { type: 'tables', expr: stmt } stmt.parentheses = true; return { expr: stmt, @@ -2406,155 +3490,300 @@ table_join on: expr }; } + //NOTE that, the table assigned to `var` shouldn't write in `table_join` table_base = KW_DUAL { + // => { type: 'dual' } return { type: 'dual' }; } - / t:table_name __ alias:alias_clause? { - if (t.type === 'var') { - t.as = alias; - return t; + / stmt:value_clause __ alias:value_alias_clause? { + // => { expr: value_clause; as?: alias_clause; } + return { + expr: { type: 'values', values: stmt }, + as: alias + }; + } + / l:('LATERAL'i)? __ LPAREN __ stmt:(union_stmt / value_clause) __ RPAREN __ alias:value_alias_clause? { + // => { prefix?: string; expr: union_stmt | value_clause; as?: alias_clause; } + if (Array.isArray(stmt)) stmt = { type: 'values', values: stmt } + stmt.parentheses = true; + return { + prefix: l, + expr: stmt, + as: alias + }; + } + / l:('LATERAL'i)? __ LPAREN __ stmt:table_ref_list __ RPAREN __ alias:value_alias_clause? { + // => { prefix?: string; expr: table_ref_list; as?: alias_clause; } + stmt = { type: 'tables', expr: stmt, parentheses: true } + return { + prefix: l, + expr: stmt, + as: alias + }; + } + / l:('LATERAL'i)? __ e:func_call __ alias:alias_clause? { + // => { prefix?: string; type: 'expr'; expr: expr; as?: alias_clause; } + return { prefix: l, type: 'expr', expr: e, as: alias }; + } + / t:table_name __ 'TABLESAMPLE'i __ f:func_call __ re:('REPEATABLE'i __ LPAREN __ literal_numeric __ RPAREN)? __ alias:alias_clause? { + // => table_name & { expr: expr, repeatable: literal_numeric; as?: alias_clause;} + return { + ...t, + as: alias, + tablesample: { + expr: f, + repeatable: re && re[4], } - return { - db: t.db, - table: t.table, - as: alias, - ...getLocationObject(), - }; } - / LPAREN __ t:table_name __ alias:alias_clause? __ r:RPAREN { - const parentheses = true + } + / t:table_name __ alias:alias_clause? { + // => table_name & { as?: alias_clause; } if (t.type === 'var') { t.as = alias; - t.parentheses = parentheses return t; + } else { + return { + ...t, + as: alias + }; } - return { - db: t.db, - table: t.table, - as: alias, - parentheses, - }; - } - / stmt:value_clause __ alias:alias_clause? { - return { - expr: { type: 'values', values: stmt, prefix: 'row' }, - as: alias - }; - } - / LPAREN __ stmt:(set_op_stmt / value_clause) __ RPAREN __ alias:alias_clause? { - if (Array.isArray(stmt)) stmt = { type: 'values', values: stmt, prefix: 'row' } - stmt.parentheses = true; - return { - expr: stmt, - as: alias - }; } + join_op - = KW_LEFT __ KW_OUTER? __ KW_JOIN { return 'LEFT JOIN'; } - / KW_RIGHT __ KW_OUTER? __ KW_JOIN { return 'RIGHT JOIN'; } - / KW_FULL __ KW_OUTER? __ KW_JOIN { return 'FULL JOIN'; } - / KW_CROSS __ KW_JOIN { return 'CROSS JOIN'; } - / (KW_INNER __)? KW_JOIN { return 'INNER JOIN'; } + = KW_LEFT __ KW_OUTER? __ KW_JOIN { /* => 'LEFT JOIN' */ return 'LEFT JOIN'; } + / KW_RIGHT __ KW_OUTER? __ KW_JOIN { /* => 'RIGHT JOIN' */ return 'RIGHT JOIN'; } + / KW_FULL __ KW_OUTER? __ KW_JOIN { /* => 'FULL JOIN' */ return 'FULL JOIN'; } + / 'CROSS'i __ KW_JOIN { /* => 'CROSS JOIN' */ return 'CROSS JOIN'; } + / (KW_INNER __)? KW_JOIN { /* => 'INNER JOIN' */ return 'INNER JOIN'; } table_name - = prefix:[_0-9]+ part:ident_without_kw tail:(__ DOT __ ident_without_kw)? { - const dt = `${prefix.join('')}${part}` - const obj = { db: null, table: dt } + = dt:ident schema:(__ DOT __ (ident / STAR))? tail:(__ DOT __ (ident / STAR))? { + // => { db?: ident; schema?: ident, table: ident | '*'; } + const obj = { db: null, table: dt }; if (tail !== null) { - obj.db = dt - obj.table = tail[3] + obj.db = dt; + obj.schema = schema[3]; + obj.table = tail[3]; + return obj } - return obj - } - / part:ident tail:(__ DOT __ ident)? { - const obj = { db: null, table: part } - if (tail !== null) { - obj.db = part - obj.table = tail[3] + if (schema !== null) { + obj.db = dt; + obj.table = schema[3]; } - return obj + return obj; } / v:var_decl { + // => IGNORE v.db = null; v.table = v.name; return v; } +or_and_expr + = head:expr tail:(__ (KW_AND / KW_OR) __ expr)* { + /* + export type BINARY_OPERATORS = + | LOGIC_OPERATOR + | "OR" + | "AND" + | multiplicative_operator + | additive_operator + | arithmetic_comparison_operator + | "IN" + | "NOT IN" + | "BETWEEN" + | "NOT BETWEEN" + | "IS" + | "IS NOT" + | "ILIKE" + | "LIKE" + | "@>" + | "<@" + | OPERATOR_CONCATENATION + | DOUBLE_WELL_ARROW + | WELL_ARROW + | "?" + | "?|" + | "?&" + | "#-"; + + export type binary_expr = { + type: "binary_expr"; + operator: BINARY_OPERATORS; + left: expr; + right: expr; + }; + => binary_expr + */ + const len = tail.length + let result = head + for (let i = 0; i < len; ++i) { + result = createBinaryExpr(tail[i][1], result, tail[i][3]) + } + return result + } + on_clause - = KW_ON __ e:or_and_expr { return e; } + = KW_ON __ e:or_and_where_expr { /* => or_and_where_expr */ return e; } where_clause - = KW_WHERE __ e:or_and_where_expr { - return e; + = KW_WHERE __ e:or_and_where_expr { /* => or_and_where_expr */ return e; } + +group_by_clause + = KW_GROUP __ KW_BY __ e:expr_list { + // => { columns: expr_list['value']; modifiers: literal_string[]; } + return { + columns: e.value + } } -with_rollup - = KW_WITH __ 'ROLLUP'i { +column_ref_list + = head:column_ref tail:(__ COMMA __ column_ref)* { + // => column_ref[] + return createList(head, tail); + } + +having_clause + = KW_HAVING __ e:or_and_where_expr { /* => expr */ return e; } + +window_clause + = KW_WINDOW __ l:named_window_expr_list { + // => { keyword: 'window'; type: 'window', expr: named_window_expr_list; } return { - type: 'origin', - value: 'with rollup' + keyword: 'window', + type: 'window', + expr: l, } } -group_by_clause - = KW_GROUP __ KW_BY __ e:expr_list __ wr:with_rollup? { +named_window_expr_list + = head:named_window_expr tail:(__ COMMA __ named_window_expr)* { + // => named_window_expr[] + return createList(head, tail); + } + +named_window_expr + = nw:ident_name __ KW_AS __ anw:as_window_specification { + // => { name: ident_name; as_window_specification: as_window_specification; } return { - columns: e.value, - modifiers: [wr], + name: nw, + as_window_specification: anw, } } -column_ref_index - = column_ref_list / literal_list +as_window_specification + = ident_name + / LPAREN __ ws:window_specification? __ RPAREN { + // => { window_specification: window_specification; parentheses: boolean } + return { + window_specification: ws || {}, + parentheses: true + } + } -column_ref_list - = head:column_ref tail:(__ COMMA __ column_ref)* { - return createList(head, tail); +window_specification + = bc:partition_by_clause? __ + l:order_by_clause? __ + w:window_frame_clause? { + // => { name: null; partitionby: partition_by_clause; orderby: order_by_clause; window_frame_clause: string | null; } + return { + name: null, + partitionby: bc, + orderby: l, + window_frame_clause: w } + } -having_clause - = KW_HAVING __ e:or_and_where_expr { return e; } +window_specification_frameless + = bc:partition_by_clause? __ + l:order_by_clause? { + // => { name: null; partitionby: partition_by_clause; orderby: order_by_clause; window_frame_clause: null } + return { + name: null, + partitionby: bc, + orderby: l, + window_frame_clause: null + } + } + +window_frame_clause + = kw:KW_ROWS __ s:(window_frame_following / window_frame_preceding) { + // => string + return `rows ${s.value}` + } + / KW_ROWS __ KW_BETWEEN __ p:window_frame_preceding __ KW_AND __ f:window_frame_following { + // => string + return `rows between ${p.value} and ${f.value}` + } + +window_frame_following + = s:window_frame_value __ 'FOLLOWING'i { + // => string + s.value += ' FOLLOWING' + return s + } + / window_frame_current_row + +window_frame_preceding + = s:window_frame_value __ 'PRECEDING'i { + // => string + s.value += ' PRECEDING' + return s + } + / window_frame_current_row + +window_frame_current_row + = 'CURRENT'i __ 'ROW'i { + // => { type: 'single_quote_string'; value: string } + return { type: 'single_quote_string', value: 'current row' } + } + +window_frame_value + = s:'UNBOUNDED'i { + // => literal_string + return { type: 'single_quote_string', value: s.toUpperCase() } + } + / literal_numeric partition_by_clause - = KW_PARTITION __ KW_BY __ bc:column_clause { return bc; } + = KW_PARTITION __ KW_BY __ bc:column_ref_list { /* => { type: 'expr'; expr: column_ref_list }[] */ return bc.map(item => ({ type: 'expr', expr: item })); } order_by_clause - = KW_ORDER __ KW_BY __ l:order_by_list { return l; } + = KW_ORDER __ KW_BY __ l:order_by_list { /* => order_by_list */ return l; } order_by_list = head:order_by_element tail:(__ COMMA __ order_by_element)* { + // => order_by_element[] return createList(head, tail); } order_by_element - = e:expr __ d:(KW_DESC / KW_ASC)? { + = e:expr __ d:(KW_DESC / KW_ASC)? __ nl:('NULLS'i __ ('FIRST'i / 'LAST'i)?)? { + // => { expr: expr; type: 'ASC' | 'DESC' | undefined; nulls: 'NULLS FIRST' | 'NULLS LAST' | undefined } const obj = { expr: e, type: d }; + obj.nulls = nl && [nl[0], nl[2]].filter(v => v).join(' ') return obj; } number_or_param = literal_numeric + / var_decl / param - / '?' { - return { - type: 'origin', - value: '?' - } - } limit_clause - = KW_LIMIT __ i1:(number_or_param) __ tail:((COMMA / KW_OFFSET) __ number_or_param)? { - const res = [i1]; + = l:(KW_LIMIT __ (number_or_param / KW_ALL))? __ tail:(KW_OFFSET __ number_or_param)? { + // => { separator: 'offset' | ''; value: [number_or_param | { type: 'origin', value: 'all' }, number_or_param?] } + const res = [] + if (l) res.push(typeof l[2] === 'string' ? { type: 'origin', value: 'all' } : l[2]) if (tail) res.push(tail[2]); return { seperator: tail && tail[0] && tail[0].toLowerCase() || '', - value: res, - ...getLocationObject(), + value: res }; } @@ -2563,9 +3792,20 @@ update_stmt t:table_ref_list __ KW_SET __ l:set_list __ + f:from_clause? __ w:where_clause? __ - or:order_by_clause? __ - lc:limit_clause? { + r:returning_stmt? { + /* export interface update_stmt_node { + with?: with_clause; + type: 'update'; + table: table_ref_list; + set: set_list; + from?: from_clause; + where?: where_clause; + returning?: returning_stmt; + } + => AstStatement + */ const dbObj = {} if (t) t.forEach(tableInfo => { const { db, as, table, join } = tableInfo @@ -2579,7 +3819,7 @@ update_stmt const table = queryTableAlias(col.table) tableList.add(`update::${dbObj[table] || null}::${table}`) } - columnList.add(`update::${col.table}::${col.column}`) + columnList.add(`update::${col.table}::${col.column.expr.value}`) }); } return { @@ -2590,21 +3830,31 @@ update_stmt type: 'update', table: t, set: l, + from: f, where: w, - orderby: or, - limit: lc, + returning: r, } }; } delete_stmt - = __ cte:with_clause? __ KW_DELETE __ + = KW_DELETE __ t:table_ref_list? __ f:from_clause __ - w:where_clause? __ - or:order_by_clause? __ - l:limit_clause? { - if(f) f.forEach(tableInfo => { + w:where_clause? { + /* + export interface table_ref_addition extends table_name { + addition: true; + as?: alias_clause; + } + export interface delete_stmt_node { + type: 'delete'; + table?: table_ref_list | [table_ref_addition]; + where?: where_clause; + } + => AstStatement + */ + if(f) f.forEach(tableInfo => { const { db, as, table, join } = tableInfo const action = join ? 'select' : 'delete' if (table) tableList.add(`${action}::${db}::${table}`) @@ -2623,18 +3873,16 @@ delete_stmt tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), ast: { - with: cte, type: 'delete', table: t, from: f, - where: w, - orderby: or, - limit: l, + where: w } }; } set_list = head:set_item tail:(__ COMMA __ set_item)* { + // => set_item[] return createList(head, tail); } @@ -2644,11 +3892,22 @@ set_list * 'col1 = (col2 > 3)' */ set_item - = tbl:(ident __ DOT)? __ c:column_without_kw __ '=' __ v:additive_expr { - return { column: c, value: v, table: tbl && tbl[0] }; + = c:column_ref_array_index __ '=' __ v:additive_expr { + // => { column: ident; value: additive_expr; table?: ident;} + return { ...c, value: v }; + } + / column_ref_array_index __ '=' __ KW_VALUES __ LPAREN __ v:column_ref __ RPAREN { + // => { column: ident; value: column_ref; table?: ident; keyword: 'values' } + return { ...c, value: v, keyword: 'values' }; } - / tbl:(ident __ DOT)? __ c:column_without_kw __ '=' __ KW_VALUES __ LPAREN __ v:column_ref __ RPAREN { - return { column: c, value: v, table: tbl && tbl[0], keyword: 'values' }; + +returning_stmt + = k:KW_RETURNING __ c:(column_clause / select_stmt) { + // => { type: 'returning'; columns: column_clause | select_stmt; } + return { + type: k && k.toLowerCase() || 'returning', + columns: c === '*' && [{ type: 'expr', expr: { type: 'column_ref', table: null, column: '*' }, as: null }] || c + } } insert_value_clause @@ -2657,20 +3916,78 @@ insert_value_clause insert_partition = KW_PARTITION __ LPAREN __ head:ident_name tail:(__ COMMA __ ident_name)* __ RPAREN { + // => ident_name[] return createList(head, tail) } / KW_PARTITION __ v: value_item { + // => value_item return v } +conflict_target + = LPAREN __ c:column_ref_list __ RPAREN { + // => { type: 'column'; expr: column_ref_list; parentheses: true; } + return { + type: 'column', + expr: c, + parentheses: true, + } + } + +conflict_action + = 'DO'i __ 'NOTHING'i { + // => { keyword: "do"; expr: {type: 'origin'; value: string; }; } + return { + keyword: 'do', + expr: { + type: 'origin', + value: 'nothing' + } + } + } + / 'DO'i __ KW_UPDATE __ KW_SET __ s:set_list __ w:where_clause? { + // => { keyword: "do"; expr: {type: 'update'; set: set_list; where: where_clause; }; } + return { + keyword: 'do', + expr: { + type: 'update', + set: s, + where: w, + } + } + } + +on_conflict + = KW_ON __ 'CONFLICT'i __ ct:conflict_target? __ ca:conflict_action { + // => { type: "conflict"; keyword: "on"; target: conflict_target; action: conflict_action; } + return { + type: 'conflict', + keyword: 'on', + target: ct, + action: ca, + } + } + replace_insert_stmt = ri:replace_insert __ - ig:KW_IGNORE? __ - it:KW_INTO? __ + KW_INTO? __ t:table_name __ p:insert_partition? __ LPAREN __ c:column_list __ RPAREN __ v:insert_value_clause __ - odp:on_duplicate_update_stmt? { + oc:on_conflict? __ + r:returning_stmt? { + /* + export interface replace_insert_stmt_node { + type: 'insert' | 'replace'; + table?: [table_name]; + columns: column_list; + conflict?: on_conflict; + values: insert_value_clause; + partition?: insert_partition; + returning?: returning_stmt; + } + => AstStatement + */ if (t) { tableList.add(`insert::${t.db}::${t.table}`) t.as = null @@ -2684,9 +4001,8 @@ replace_insert_stmt } }) } - c.forEach(c => columnList.add(`insert::${table}::${c}`)); + c.forEach(c => columnList.add(`insert::${table}::${c.value}`)); } - const prefix = [ig, it].filter(v => v).map(v => v[0] && v[0].toLowerCase()).join(' ') return { tableList: Array.from(tableList), columnList: columnListTableAlias(columnList), @@ -2696,20 +4012,21 @@ replace_insert_stmt columns: c, values: v, partition: p, - prefix, - on_duplicate_update: odp, + conflict: oc, + returning: r, } }; } insert_no_columns_stmt - = ri:replace_insert __ + = ri:replace_insert __ ig:KW_IGNORE? __ it:KW_INTO? __ t:table_name __ p:insert_partition? __ v:insert_value_clause __ - odp: on_duplicate_update_stmt? { + r:returning_stmt? { + // => AstStatement if (t) { tableList.add(`insert::${t.db}::${t.table}`) columnList.add(`insert::${t.table}::(.*)`); @@ -2726,89 +4043,70 @@ insert_no_columns_stmt values: v, partition: p, prefix, - on_duplicate_update: odp, - } - }; - } - -insert_into_set - = ri:replace_insert __ - ig:KW_IGNORE? __ - it:KW_INTO? __ - t:table_name __ - p:insert_partition? __ - KW_SET __ - l:set_list __ - odp:on_duplicate_update_stmt? { - if (t) { - tableList.add(`insert::${t.db}::${t.table}`) - columnList.add(`insert::${t.table}::(.*)`); - t.as = null - } - const prefix = [ig, it].filter(v => v).map(v => v[0] && v[0].toLowerCase()).join(' ') - return { - tableList: Array.from(tableList), - columnList: columnListTableAlias(columnList), - ast: { - type: ri, - table: [t], - columns: null, - partition: p, - prefix, - set: l, - on_duplicate_update: odp, + returning: r, } }; } -on_duplicate_update_stmt - = KW_ON __ 'DUPLICATE'i __ KW_KEY __ KW_UPDATE __ s:set_list { - return { - keyword: 'on duplicate key update', - set: s - } - } - replace_insert - = KW_INSERT { return 'insert'; } - / KW_REPLACE { return 'replace'; } + = KW_INSERT { /* => 'insert' */ return 'insert'; } + / KW_REPLACE { /* => 'replace' */return 'replace'; } value_clause - = KW_VALUES __ l:value_list { return l; } + = KW_VALUES __ l:value_list { /* => value_list */ return l; } value_list = head:value_item tail:(__ COMMA __ value_item)* { + // => value_item[] return createList(head, tail); } value_item - = 'ROW'i? __ LPAREN __ l:expr_list __ RPAREN { + = LPAREN __ l:expr_list __ RPAREN { + // => expr_list return l; } expr_list = head:expr tail:(__ COMMA __ expr)* { + // => { type: 'expr_list'; value: expr[] } const el = { type: 'expr_list' }; el.value = createList(head, tail); return el; } interval_expr - = KW_INTERVAL __ - e:expr __ + = KW_INTERVAL __ + e:expr __ u: interval_unit { + // => { type: 'interval', expr: expr; unit: interval_unit; } return { type: 'interval', expr: e, unit: u.toLowerCase(), } } + / KW_INTERVAL __ + e:literal_string { + // => { type: 'interval', expr: expr; unit: interval_unit; } + return { + type: 'interval', + expr: e, + unit: '', + } + } case_expr = KW_CASE __ condition_list:case_when_then_list __ otherwise:case_else? __ KW_END __ KW_CASE? { + /* => { + type: 'case'; + expr: null; + // nb: Only the last element is a case_else + args: (case_when_then | case_else)[]; + } */ if (otherwise) condition_list.push(otherwise); return { type: 'case', @@ -2816,11 +4114,17 @@ case_expr args: condition_list }; } - / KW_CASE __ + / KW_CASE __ expr:expr __ condition_list:case_when_then_list __ otherwise:case_else? __ KW_END __ KW_CASE? { + /* => { + type: 'case'; + expr: expr; + // nb: Only the last element is a case_else + args: (case_when_then | case_else)[]; + } */ if (otherwise) condition_list.push(otherwise); return { type: 'case', @@ -2831,11 +4135,13 @@ case_expr case_when_then_list = head:case_when_then __ tail:(__ case_when_then)* { + // => case_when_then[] return createList(head, tail, 1) } case_when_then = KW_WHEN __ condition:or_and_where_expr __ KW_THEN __ result:expr { + // => { type: 'when'; cond: or_and_where_expr; result: expr; } return { type: 'when', cond: condition, @@ -2844,33 +4150,42 @@ case_when_then } case_else = KW_ELSE __ result:expr { + // => { type: 'else'; condition?: never; result: expr; } return { type: 'else', result: result }; } /** - * From MySQL Manual + * Borrowed from PL/SQL ,the priority of below list IS ORDER BY DESC * --------------------------------------------------------------------------------------------------- - * * ! - * - (unary minus), ~ (unary bit inversion) - * ^ - * *, /, DIV, %, MOD - * -, + - * <<, >> - * & - * | - * = (comparison), <=>, >=, >, <=, <, <>, !=, IS, LIKE, REGEXP, IN, MEMBER OF - * BETWEEN, CASE, WHEN, THEN, ELSE - * NOT - * AND, && - * XOR - * OR, || - * = (assignment), := | + * | +, - | identity, negation | + * | *, / | multiplication, division | + * | +, - | addition, subtraction, concatenation | + * | =, <, >, <=, >=, <>, !=, IS, LIKE, BETWEEN, IN | comparion | + * | !, NOT | logical negation | + * | AND | conjunction | + * | OR | inclusion | * --------------------------------------------------------------------------------------------------- */ +_expr + = or_expr + / unary_expr expr - = or_expr / set_op_stmt + = _expr / union_stmt + +unary_expr + = op: additive_operator tail: (__ primary)+ { + /* + export type UNARY_OPERATORS = '+' | '-' | 'EXISTS' | 'NOT EXISTS' | 'NULL' + => { + type: 'unary_expr', + operator: UNARY_OPERATORS, + expr: expr; + parentheses?: boolean; + } */ + return createUnaryExpr(op, tail[0][1]); + } binary_column_expr = head:expr tail:(__ (KW_AND / KW_OR / LOGIC_OPERATOR) __ expr)* { @@ -2879,6 +4194,7 @@ binary_column_expr if (!(head.parentheses_symbol || head.parentheses || head.ast.parentheses || head.ast.parentheses_symbol) || ast.columns.length !== 1 || ast.columns[0].expr.column === '*') throw new Error('invalid column clause with select statement') } if (!tail || tail.length === 0) return head + // => binary_expr const len = tail.length let result = tail[len - 1][3] for (let i = len - 1; i >= 0; i--) { @@ -2888,18 +4204,9 @@ binary_column_expr return result } -or_and_expr - = head:expr tail:(__ (KW_AND / KW_OR) __ expr)* { - const len = tail.length - let result = head - for (let i = 0; i < len; ++i) { - result = createBinaryExpr(tail[i][1], result, tail[i][3]) - } - return result - } - or_and_where_expr = head:expr tail:(__ (KW_AND / KW_OR / COMMA) __ expr)* { + // => binary_expr | { type: 'expr_list'; value: expr[] } const len = tail.length let result = head; let seperator = '' @@ -2914,7 +4221,7 @@ or_and_where_expr } if (seperator === ',') { const el = { type: 'expr_list' } - el.value = Array.isArray(result) ? result : [result] + el.value = result return el } return result @@ -2922,23 +4229,28 @@ or_and_where_expr or_expr = head:and_expr tail:(___ KW_OR __ and_expr)* { + // => binary_expr return createBinaryExprChain(head, tail); } and_expr = head:not_expr tail:(___ KW_AND __ not_expr)* { + // => binary_expr return createBinaryExprChain(head, tail); - } + } + //here we should use `NOT` instead of `comparision_expr` to support chain-expr not_expr = comparison_expr / exists_expr - / KW_NOT __ expr:not_expr { + / (KW_NOT / "!" !"=") __ expr:not_expr { + // => unary_expr return createUnaryExpr('NOT', expr); } comparison_expr = left:additive_expr __ rh:comparison_op_right? { + // => binary_expr if (rh === null) return left; else if (rh.type === 'arithmetic') return createBinaryExprChain(left, rh.tail); else return createBinaryExpr(rh.op, left, rh.right); @@ -2947,13 +4259,14 @@ comparison_expr / column_ref exists_expr - = op:exists_op __ LPAREN __ stmt:set_op_stmt __ RPAREN { + = op:exists_op __ LPAREN __ stmt:union_stmt __ RPAREN { + // => unary_expr stmt.parentheses = true; return createUnaryExpr(op, stmt); } exists_op - = nk:(KW_NOT __ KW_EXISTS) { return nk[0] + ' ' + nk[2]; } + = nk:(KW_NOT __ KW_EXISTS) { /* => 'NOT EXISTS' */ return nk[0] + ' ' + nk[2]; } / KW_EXISTS comparison_op_right @@ -2962,10 +4275,11 @@ comparison_op_right / between_op_right / is_op_right / like_op_right - / regexp_op_right + / regex_op_right arithmetic_op_right = l:(__ arithmetic_comparison_operator __ additive_expr)+ { + // => { type: 'arithmetic'; tail: any } return { type: 'arithmetic', tail: l }; } @@ -2974,14 +4288,27 @@ arithmetic_comparison_operator is_op_right = KW_IS __ right:additive_expr { + // => { op: 'IS'; right: additive_expr; } return { op: 'IS', right: right }; } + / KW_IS __ right:(KW_DISTINCT __ KW_FROM __ table_name) { + // => { type: 'origin'; value: string; } + const { db, table } = right.pop() + const tableName = table === '*' ? '*' : `"${table}"` + let tableStr = db ? `"${db}".${tableName}` : tableName + return { op: 'IS', right: { + type: 'default', + value: `DISTINCT FROM ${tableStr}` + }} + } / (KW_IS __ KW_NOT) __ right:additive_expr { + // => { type: 'IS NOT'; right: additive_expr; } return { op: 'IS NOT', right: right }; } between_op_right = op:between_or_not_between_op __ begin:additive_expr __ KW_AND __ end:additive_expr { + // => { op: 'BETWEEN' | 'NOT BETWEEN'; right: { type: 'expr_list'; value: [expr, expr] } } return { op: op, right: { @@ -2992,17 +4319,31 @@ between_op_right } between_or_not_between_op - = nk:(KW_NOT __ KW_BETWEEN) { return nk[0] + ' ' + nk[2]; } + = nk:(KW_NOT __ KW_BETWEEN) { /* => 'NOT BETWEEN' */ return nk[0] + ' ' + nk[2]; } / KW_BETWEEN like_op - = nk:(KW_NOT __ KW_LIKE) { return nk[0] + ' ' + nk[2]; } + = nk:(KW_NOT __ (KW_LIKE / KW_ILIKE)) { /* => 'LIKE' */ return nk[0] + ' ' + nk[2]; } / KW_LIKE - -regexp_op - = n: KW_NOT? __ k:(KW_REGEXP / KW_RLIKE) { - return n ? `${n} ${k}` : k + / KW_ILIKE + / 'SIMILAR'i __ KW_TO { + // => 'SIMILAR TO' + return 'SIMILAR TO' + } + / KW_NOT __ 'SIMILAR'i __ KW_TO { + // => 'NOT SIMILAR TO' + return 'NOT SIMILAR TO' } + +regex_op + = "!~*" / "~*" / "~" / "!~" + +regex_op_right += op:regex_op __ right:(literal / comparison_expr) { + // => { op: regex_op; right: literal | comparison_expr} + return { op: op, right: right }; + } + escape_op = kw:'ESCAPE'i __ c:literal_string { // => { type: 'ESCAPE'; value: literal_string } @@ -3013,31 +4354,30 @@ escape_op } in_op - = nk:(KW_NOT __ KW_IN) { return nk[0] + ' ' + nk[2]; } + = nk:(KW_NOT __ KW_IN) { /* => 'NOT IN' */ return nk[0] + ' ' + nk[2]; } / KW_IN -regexp_op_right - = op:regexp_op __ b:'BINARY'i? __ e:(func_call / literal_string / column_ref) { - return { op: b ? `${op} ${b}` : op, right: e }; - } - like_op_right - = op:like_op __ right:(literal / param / comparison_expr ) __ es:escape_op? { + = op:like_op __ right:(literal / comparison_expr) __ es:escape_op? { + // => { op: like_op; right: (literal | comparison_expr) & { escape?: escape_op }; } if (es) right.escape = es return { op: op, right: right }; } in_op_right = op:in_op __ LPAREN __ l:expr_list __ RPAREN { + // => {op: in_op; right: expr_list | var_decl | literal_string; } return { op: op, right: l }; } - / op:in_op __ e:(var_decl / column_ref / literal_string) { + / op:in_op __ e:(var_decl / literal_string / func_call) { + // => IGNORE return { op: op, right: e }; } additive_expr - = head: multiplicative_expr + = head:multiplicative_expr tail:(__ additive_operator __ multiplicative_expr)* { + // => binary_expr if (tail && tail.length && head.type === 'column_ref' && head.column === '*') throw new Error(JSON.stringify({ message: 'args could not be star column in additive expr', ...getLocationObject(), @@ -3051,20 +4391,40 @@ additive_operator multiplicative_expr = head:unary_expr_or_primary tail:(__ (multiplicative_operator / LOGIC_OPERATOR) __ unary_expr_or_primary)* { + // => binary_expr return createBinaryExprChain(head, tail) } multiplicative_operator = "*" / "/" / "%" / "||" - / "div"i { - return 'DIV' + +column_ref_array_index + = c:column_ref __ a:array_index_list? { + // => column_ref + if (a) c.array_index = a + return c + } + +primary + = cast_expr + / LPAREN __ list:or_and_where_expr __ RPAREN { + // => or_and_where_expr + list.parentheses = true; + return list; + } + / var_decl + / __ p:'$''<'n:literal_numeric'>' { + // => { type: 'origin'; value: string; } + return { + type: 'origin', + value: `$<${n.value}>`, + } } - / '&' / '>>' / '<<' / '^' / '|' unary_expr_or_primary = jsonb_expr / op:(unary_operator) tail:(__ unary_expr_or_primary) { - // if (op === '!') op = 'NOT' + // => unary_expr return createUnaryExpr(op, tail[1]) } @@ -3078,107 +4438,116 @@ jsonb_expr return createBinaryExprChain(head, tail) } -primary - = aggr_func - / fulltext_search - / func_call - / cast_expr - / case_expr - / interval_expr - / literal - / column_ref - / param - / LPAREN __ list:or_and_where_expr __ RPAREN { - list.parentheses = true - return list - } - / var_decl - / __ prepared_symbol:'?' { +string_constants_escape + = 'E'i"'" __ n:single_char* __ "'" { + // => { type: 'origin'; value: string; } return { type: 'origin', - value: prepared_symbol + value: `E'${n.join('')}'` } } column_ref - = db:(ident_name / backticks_quoted_ident) __ DOT __ tbl:(ident_name / backticks_quoted_ident) __ DOT __ col:column_without_kw { - columnList.add(`select::${typeof db === 'object' ? db.value : db}::${typeof tbl === 'object' ? tbl.value : tbl}::${col}`); + = string_constants_escape + / tbl:(ident __ DOT)? __ STAR { + // => IGNORE + const table = tbl && tbl[0] || null + columnList.add(`select::${table}::(.*)`); + return { + type: 'column_ref', + table: table, + column: '*' + } + } + / schema:ident tbl:(__ DOT __ ident) col:(__ DOT __ column_without_kw_type) { + /* => { + type: 'column_ref'; + schema: string; + table: string; + column: column | '*'; + } */ + columnList.add(`select::${schema}.${tbl[3]}::${col[3].value}`); return { type: 'column_ref', - db: db, - table: tbl, - column: col, - ...getLocationObject(), + schema: schema, + table: tbl[3], + column: { expr: col[3] } }; } - / tbl:(ident_name / backticks_quoted_ident) __ DOT __ col:column_without_kw { - columnList.add(`select::${typeof tbl === 'object' ? tbl.value : tbl}::${col}`); + / tbl:ident __ DOT __ col:column_without_kw_type { + /* => { + type: 'column_ref'; + table: ident; + column: column | '*'; + } */ + columnList.add(`select::${tbl}::${col.value}`); return { type: 'column_ref', table: tbl, - column: col, - ...getLocationObject(), + column: { expr: col } }; } - / tbl:(ident __ DOT)? __ STAR { - const table = tbl && tbl[0] || null - columnList.add(`select::${table}::(.*)`); + / col:column_type { + // => IGNORE + columnList.add(`select::null::${col.value}`); return { - expr: { - type: 'column_ref', - table, - column: '*' - }, - as: null, - ...getLocationObject(), + type: 'column_ref', + table: null, + column: { expr: col } }; } - / col:column { - columnList.add(`select::null::${col}`); + +column_ref_quoted + = col:literal_double_quoted_string { + // => unknown + columnList.add(`select::null::${col.value}`); return { type: 'column_ref', table: null, - column: col, - ...getLocationObject(), + column: { expr: col } }; } column_list - = head:column tail:(__ COMMA __ column)* { + = head:column_type tail:(__ COMMA __ column_type)* { + // => column[] return createList(head, tail); } -ident_name_type + +ident_without_kw_type = n:ident_name { + // => { type: 'default', value: string } return { type: 'default', value: n } } -ident_without_kw_type - = ident_name_type / quoted_ident_type + / quoted_ident_type ident_type = name:ident_name !{ return reservedMap[name.toUpperCase()] === true; } { + // => ident_name return { type: 'default', value: name } } / quoted_ident_type -ident_without_kw - = ident_name / quoted_ident + ident = name:ident_name !{ return reservedMap[name.toUpperCase()] === true; } { + // => ident_name return name; } / quoted_ident ident_list = head:ident tail:(__ COMMA __ ident)* { + // => ident[] return createList(head, tail) } alias_ident - = name:ident_name !{ - if (reservedMap[name.toUpperCase()] === true) throw new Error("Error: "+ JSON.stringify(name)+" is a reserved word, can not as alias clause"); - return false - } { - return name; + = name:column_name !{ return reservedMap[name.toUpperCase()] === true } c:(__ LPAREN __ column_list __ RPAREN)? { + // => string + if (!c) return name; + return `${name}(${c[3].map(v => v.value).join(', ')})` } - / name:quoted_ident { - return name; + / name:double_quoted_ident { + // => IGNORE + return name.value; } quoted_ident_type @@ -3186,11 +4555,13 @@ quoted_ident_type quoted_ident = v:(double_quoted_ident / single_quoted_ident / backticks_quoted_ident) { + // => string return v.value } double_quoted_ident = '"' chars:[^"]+ '"' { + // => { type: 'double_quote_string'; value: string; } return { type: 'double_quote_string', value: chars.join('') @@ -3199,6 +4570,7 @@ double_quoted_ident single_quoted_ident = "'" chars:[^']+ "'" { + // => { type: 'single_quote_string'; value: string; } return { type: 'single_quote_string', value: chars.join('') @@ -3206,294 +4578,271 @@ single_quoted_ident } backticks_quoted_ident - = "`" chars:([^`\\] / escape_char)+ "`" { + = "`" chars:[^`]+ "`" { + // => { type: 'backticks_quote_string'; value: string; } return { type: 'backticks_quote_string', value: chars.join('') } } +ident_without_kw + = ident_name / quoted_ident + column_without_kw - = name:column_name { - return name; - } - / quoted_ident + = column_name / quoted_ident -column - = name:column_name !{ return reservedMap[name.toUpperCase()] === true; } { return name; } - / n:backticks_quoted_ident { - return n.value +column_without_kw_type + = n:column_name { + // => { type: 'default', value: string } + return { type: 'default', value: n } + } + / quoted_ident_type +column_type + = name:column_name !{ return reservedMap[name.toUpperCase()] === true; } { + // => { type: 'default', value: string } + return { type: 'default', value: name } } + / quoted_ident_type +column + = name:column_name !{ return reservedMap[name.toUpperCase()] === true; } { /* => string */ return name; } + / quoted_ident column_name - = start:ident_start parts:column_part* { return start + parts.join(''); } + = start:ident_start parts:column_part* { /* => string */ return start + parts.join(''); } ident_name - = start:ident_start parts:ident_part* { return start + parts.join(''); } + = start:ident_start parts:ident_part* { + // => string + return start + parts.join(''); + } ident_start = [A-Za-z_\u4e00-\u9fa5] -ident_part = [A-Za-z0-9_$\u0080-\uffff] +ident_part = [A-Za-z0-9_\-$\u4e00-\u9fa5\u00C0-\u017F] // to support column name like `cf1:name` in hbase -column_part = [A-Za-z0-9_:] +column_part = [A-Za-z0-9_\u4e00-\u9fa5\u00C0-\u017F] param = l:(':' ident_name) { + // => { type: 'param'; value: ident_name } return { type: 'param', value: l[1] }; } -aggr_func - = aggr_fun_count - / aggr_fun_smma - -aggr_fun_smma - = name:KW_SUM_MAX_MIN_AVG __ LPAREN __ e:expr __ RPAREN __ bc:over_partition? { - return { - type: 'aggr_func', - name: name, - args: { - expr: e - }, - over: bc, - ...getLocationObject(), - }; - } - -KW_SUM_MAX_MIN_AVG - = KW_SUM / KW_MAX / KW_MIN / KW_AVG - on_update_current_timestamp - = KW_ON __ KW_UPDATE __ kw:KW_CURRENT_TIMESTAMP __ l:(LPAREN __ expr_list? __ RPAREN)? { - const parentheses = l ? true : false - const expr = l ? l[2] : null + = KW_ON __ KW_UPDATE __ kw:KW_CURRENT_TIMESTAMP __ LPAREN __ l:expr_list? __ RPAREN{ + // => { type: 'on update'; keyword: string; parentheses: boolean; expr: expr } return { type: 'on update', keyword: kw, - parentheses, - expr, + parentheses: true, + expr: l } } - / KW_ON __ KW_UPDATE __ kw:'NOW'i __ LPAREN __ RPAREN { + / KW_ON __ KW_UPDATE __ kw:KW_CURRENT_TIMESTAMP { + // => { type: 'on update'; keyword: string; } return { type: 'on update', keyword: kw, - parentheses: true, } } over_partition = 'OVER'i __ aws:as_window_specification { + // => { type: 'windows'; as_window_specification: as_window_specification } return { type: 'window', as_window_specification: aws, } } - / on_update_current_timestamp - -window_clause - = 'WINDOW'i __ l:named_window_expr_list { - // => { keyword: 'window'; type: 'window', expr: named_window_expr_list; } + / 'OVER'i __ LPAREN __ bc:partition_by_clause? __ l:order_by_clause? __ RPAREN { + // => { partitionby: partition_by_clause; orderby: order_by_clause } return { - keyword: 'window', - type: 'window', - expr: l, + partitionby: bc, + orderby: l } } + / on_update_current_timestamp -named_window_expr_list - = head:named_window_expr tail:(__ COMMA __ named_window_expr)* { - // => named_window_expr[] - return createList(head, tail); - } - -named_window_expr - = nw:ident_name __ KW_AS __ anw:as_window_specification { - // => { name: ident_name; as_window_specification: as_window_specification; } +aggr_filter + = 'FILTER'i __ LPAREN __ wc:where_clause __ RPAREN { + // => { keyword: 'filter'; parentheses: true, where: where_clause } return { - name: nw, - as_window_specification: anw, + keyword: 'filter', + parentheses: true, + where: wc, } } -as_window_specification - = ident_name - / LPAREN __ ws:window_specification? __ RPAREN { - return { - window_specification: ws || {}, - parentheses: true - } +aggr_func + = e:(aggr_fun_count / aggr_fun_smma / aggr_array_agg) __ f:aggr_filter? { + // => { type: 'aggr_func'; name: string; args: { expr: additive_expr } | count_arg; over: over_partition; filter?: aggr_filter; } + if (f) e.filter = f + return e } -window_specification - = bc:partition_by_clause? __ l:order_by_clause? __ w:window_frame_clause? { +window_func + = window_fun_rank + / window_fun_laglead + / window_fun_firstlast + +window_fun_rank + = name:KW_WIN_FNS_RANK __ LPAREN __ RPAREN __ over:over_partition { + // => { type: 'window_func'; name: string; over: over_partition } return { - name: null, - partitionby: bc, - orderby: l, - window_frame_clause: w, + type: 'window_func', + name: name, + over: over } } -window_specification_frameless - = bc:partition_by_clause? __ l:order_by_clause? { +window_fun_laglead + = name:KW_LAG_LEAD __ LPAREN __ l:expr_list __ RPAREN __ cn:consider_nulls_clause? __ over:over_partition { + // => { type: 'window_func'; name: string; args: expr_list; consider_nulls: null | string; over: over_partition } return { - name: null, - partitionby: bc, - orderby: l, - window_frame_clause: null - } + type: 'window_func', + name: name, + args: l, + over: over, + consider_nulls: cn + }; } -window_frame_clause - = kw:KW_ROWS __ s:(window_frame_following / window_frame_preceding) { - // => string - return `rows ${s.value}` - } - / KW_ROWS __ KW_BETWEEN __ p:window_frame_preceding __ KW_AND __ f:window_frame_following { - // => string - return `rows between ${p.value} and ${f.value}` +window_fun_firstlast + = name:KW_FIRST_LAST_VALUE __ LPAREN __ l:expr __ cn:consider_nulls_clause? __ RPAREN __ over:over_partition { + // => window_fun_laglead + return { + type: 'window_func', + name: name, + args: { + type: 'expr_list', value: [l] + }, + over: over, + consider_nulls: cn + }; } -window_frame_following - = s:window_frame_value __ 'FOLLOWING'i { - // => string - s.value += ' FOLLOWING' - return s - } - / window_frame_current_row +KW_FIRST_LAST_VALUE + = 'FIRST_VALUE'i / 'LAST_VALUE'i -window_frame_preceding - = s:window_frame_value __ 'PRECEDING'i { - // => string - s.value += ' PRECEDING' - return s - } - / window_frame_current_row +KW_WIN_FNS_RANK + = 'ROW_NUMBER'i / 'DENSE_RANK'i / 'RANK'i + // / 'CUME_DIST'i / 'MEDIAN'i / 'PERCENT_RANK'i + // / 'PERCENTILE_CONT'i / 'PERCENTILE_DISC'i / 'RATIO_TO_REPORT'i -window_frame_current_row - = 'CURRENT'i __ 'ROW'i { - return { type: 'single_quote_string', value: 'current row', ...getLocationObject() } - } +KW_LAG_LEAD + = 'LAG'i / 'LEAD'i / 'NTH_VALUE'i -window_frame_value - = s:'UNBOUNDED'i { - return { type: 'single_quote_string', value: s.toUpperCase(), ...getLocationObject() } +consider_nulls_clause + = v:('IGNORE'i / 'RESPECT'i) __ 'NULLS'i { + // => string + return v.toUpperCase() + ' NULLS' } - / literal_numeric + +aggr_fun_smma + = name:KW_SUM_MAX_MIN_AVG __ LPAREN __ e:additive_expr __ RPAREN __ bc:over_partition? { + // => { type: 'aggr_func'; name: 'SUM' | 'MAX' | 'MIN' | 'AVG'; args: { expr: additive_expr }; over: over_partition } + return { + type: 'aggr_func', + name: name, + args: { + expr: e + }, + over: bc, + ...getLocationObject(), + }; + } + +KW_SUM_MAX_MIN_AVG + = KW_SUM / KW_MAX / KW_MIN / KW_AVG aggr_fun_count = name:(KW_COUNT / KW_GROUP_CONCAT) __ LPAREN __ arg:count_arg __ RPAREN __ bc:over_partition? { + // => { type: 'aggr_func'; name: 'COUNT' | 'GROUP_CONCAT'; args:count_arg; over: over_partition } return { type: 'aggr_func', name: name, args: arg, - over: bc, - ...getLocationObject(), + over: bc }; } + / name:('percentile_cont'i / 'percentile_disc'i) __ LPAREN __ arg:(literal_numeric / literal_array) __ RPAREN __ 'within'i __ KW_GROUP __ LPAREN __ or:order_by_clause __ RPAREN __ bc:over_partition? { + // => { type: 'aggr_func'; name: 'PERCENTILE_CONT' | 'PERCENTILE_DISC'; args: literal_numeric | literal_array; within_group_orderby: order_by_clause; over?: over_partition } + return { + type: 'aggr_func', + name: name.toUpperCase(), + args: { + expr: arg + }, + within_group_orderby: or, + over: bc + }; + } + / name:('mode'i) __ LPAREN __ RPAREN __ 'within'i __ KW_GROUP __ LPAREN __ or:order_by_clause __ RPAREN __ bc:over_partition? { + // => { type: 'aggr_func'; name: 'MODE'; args: literal_numeric | literal_array; within_group_orderby: order_by_clause; over?: over_partition } + return { + type: 'aggr_func', + name: name.toUpperCase(), + args: { expr: {} }, + within_group_orderby: or, + over: bc + }; + } concat_separator - = kw:'SEPARATOR'i? __ s:literal_string { + = kw:COMMA __ s:literal_string { + // => { symbol: ','; delimiter: literal_string; } return { - keyword: kw, - value: s + symbol: kw, + delimiter: s } } -count_arg - = e:star_expr { return { expr: e, ...getLocationObject() }; } - / d:KW_DISTINCT? __ c:or_and_where_expr __ or:order_by_clause? __ s:concat_separator? { +distinct_args + = d:KW_DISTINCT? __ LPAREN __ c:expr __ RPAREN __ tail:(__ (KW_AND / KW_OR) __ expr)* __ s:concat_separator? __ or:order_by_clause? { + /* => { distinct: 'DISTINCT'; expr: expr; orderby?: order_by_clause; separator?: concat_separator; } */ + const len = tail.length + let result = c + result.parentheses = true + for (let i = 0; i < len; ++i) { + result = createBinaryExpr(tail[i][1], result, tail[i][3]) + } return { distinct: d, - expr: c, + expr: result, orderby: or, - separator: s, - ...getLocationObject() + separator: s }; } + / d:KW_DISTINCT? __ c:or_and_expr __ s:concat_separator? __ or:order_by_clause? { + /* => { distinct: 'DISTINCT'; expr: expr; orderby?: order_by_clause; separator?: concat_separator; } */ + return { distinct: d, expr: c, orderby: or, separator: s }; + } -star_expr - = "*" { return { type: 'star', value: '*' }; } +count_arg + = e:star_expr { /* => { expr: star_expr } */ return { expr: e }; } + / distinct_args -convert_args - = c:proc_additive_expr __ COMMA __ ch:(character_string_type / datetime_type) __ cs:create_option_character_set_kw __ v:ident_without_kw_type { - const { dataType, length } = ch - let dataTypeStr = dataType - if (length !== undefined) dataTypeStr = `${dataTypeStr}(${length})` - return { - type: 'expr_list', - value: [ - c, - { - type: 'origin', - value: dataTypeStr, - suffix: { - prefix: cs, - ...v, - } - }, - ] - } - } - / c:proc_additive_expr __ COMMA __ d:(signedness / data_type) { - const dataType = typeof d === 'string' ? { dataType: d } : d - return { - type: 'expr_list', - value: [c, { type: 'datatype', ...dataType, }] - } - } - / c:or_and_where_expr __ KW_USING __ d:ident_name { - c.suffix = `USING ${d.toUpperCase()}` - return { - type: 'expr_list', - value: [c] - } - } -extract_filed - = f:( - 'YEAR_MONTH'i / 'DAY_HOUR'i / 'DAY_MINUTE'i / 'DAY_SECOND'i / 'DAY_MICROSECOND'i / 'HOUR_MINUTE'i / 'HOUR_SECOND'i/ 'HOUR_MICROSECOND'i / 'MINUTE_SECOND'i / 'MINUTE_MICROSECOND'i / 'SECOND_MICROSECOND'i / 'TIMEZONE_HOUR'i / 'TIMEZONE_MINUTE'i - / 'CENTURY'i / 'DAY'i / 'DATE'i / 'DECADE'i / 'DOW'i / 'DOY'i / 'EPOCH'i / 'HOUR'i / 'ISODOW'i / 'ISOWEEK'i / 'ISOYEAR'i / 'MICROSECONDS'i / 'MILLENNIUM'i / 'MILLISECONDS'i / 'MINUTE'i / 'MONTH'i / 'QUARTER'i / 'SECOND'i / 'TIME'i / 'TIMEZONE'i / 'WEEK'i / 'YEAR'i - ) { - return f - } -extract_func - = kw:KW_EXTRACT __ LPAREN __ f:extract_filed __ KW_FROM __ t:(KW_TIMESTAMP / KW_INTERVAL / KW_TIME / KW_DATE) __ s:expr __ RPAREN { - return { - type: kw.toLowerCase(), - args: { - field: f, - cast_type: t, - source: s, - }, - ...getLocationObject(), - } - } - / kw:KW_EXTRACT __ LPAREN __ f:extract_filed __ KW_FROM __ s:expr __ RPAREN { - return { - type: kw.toLowerCase(), - args: { - field: f, - source: s, - }, - ...getLocationObject(), - } - } - / 'DATE_TRUNC'i __ LPAREN __ e:expr __ COMMA __ f:extract_filed __ RPAREN { - return { - type: 'function', - name: { name: [{ type: 'origin', value: 'date_trunc' }]}, - args: { type: 'expr_list', value: [e, { type: 'origin', value: f }] }, - over: null, - ...getLocationObject(), +aggr_array_agg + = pre:(ident __ DOT)? __ name:(KW_ARRAY_AGG / KW_STRING_AGG) __ LPAREN __ arg:distinct_args __ RPAREN { + // => { type: 'aggr_func'; args:count_arg; name: 'ARRAY_AGG' | 'STRING_AGG'; } + return { + type: 'aggr_func', + name: pre ? `${pre[0]}.${name}` : name, + args: arg, }; - } + } + +star_expr + = "*" { /* => { type: 'star'; value: '*' } */ return { type: 'star', value: '*' }; } trim_position = 'BOTH'i / 'LEADING'i / 'TRAILING'i trim_rem = p:trim_position? __ rm:literal_string? __ k:KW_FROM { + // => expr_list let value = [] if (p) value.push({type: 'origin', value: p }) if (rm) value.push(rm) @@ -3506,36 +4855,60 @@ trim_rem trim_func_clause = 'trim'i __ LPAREN __ tr:trim_rem? __ s:expr __ RPAREN { + // => { type: 'function'; name: proc_func_name; args: expr_list; } let args = tr || { type: 'expr_list', value: [] } args.value.push(s) return { type: 'function', - name: { name: [{ type: 'origin', value: 'trim' }]}, + name: { name: [{ type: 'origin', value: 'trim' }] }, args, ...getLocationObject(), }; } -func_call - = extract_func / trim_func_clause - / 'convert'i __ LPAREN __ l:convert_args __ RPAREN { +tablefunc_clause + = name:('crosstab'i / 'jsonb_to_recordset'i / 'jsonb_to_record'i / 'json_to_recordset'i / 'json_to_record'i) __ LPAREN __ s:expr_list __ RPAREN __ d:(KW_AS __ ident_name __ LPAREN __ column_data_type_list __ RPAREN)? { + // => { type: 'tablefunc'; name: proc_func_name; args: expr_list; as: func_call } return { + type: 'tablefunc', + name: { name: [{ type: 'default', value: name }] }, + args: s, + as: d && { + type: 'function', + name: { name: [{ type: 'default', value: d[2] }]}, + args: { type: 'expr_list', value: d[6].map(v => ({ ...v, type: 'column_definition' })) }, + ...getLocationObject(), + }, + ...getLocationObject(), + } + } + +func_call + = trim_func_clause / tablefunc_clause + / name:'now'i __ LPAREN __ l:expr_list? __ RPAREN __ 'at'i __ KW_TIME __ 'zone'i __ z:literal_string { + // => { type: 'function'; name: proc_func_name; args: expr_list; suffix: literal_string; } + z.prefix = 'at time zone' + return { type: 'function', - name: { name: [{ type: 'origin', value: 'convert' }] }, - args: l, + name: { name: [{ type: 'default', value: name }] }, + args: l ? l: { type: 'expr_list', value: [] }, + suffix: z, ...getLocationObject(), - }; - } + }; + } / name:scalar_func __ LPAREN __ l:expr_list? __ RPAREN __ bc:over_partition? { + // => { type: 'function'; name: proc_func_name; args: expr_list; over?: over_partition; } return { type: 'function', - name: { name: [{ type: 'default', value: name }] }, + name: { name: [{ type: 'origin', value: name }] }, args: l ? l: { type: 'expr_list', value: [] }, over: bc, ...getLocationObject(), }; } + / extract_func / f:scalar_time_func __ up:on_update_current_timestamp? { + // => { type: 'function'; name: proc_func_name; over?: on_update_current_timestamp; } return { type: 'function', name: { name: [{ type: 'origin', value: f }] }, @@ -3543,54 +4916,87 @@ func_call ...getLocationObject(), } } - / name:proc_func_name &{ return !reservedFunctionName[name.name[0] && name.name[0].value.toLowerCase()] } __ LPAREN __ l:or_and_where_expr? __ RPAREN __ bc:over_partition? { - if (l && l.type !== 'expr_list') l = { type: 'expr_list', value: [l] } - if (((name.name[0] && name.name[0].value.toUpperCase() === 'TIMESTAMPDIFF') || (name.name[0] && name.name[0].value.toUpperCase() === 'TIMESTAMPADD')) && l.value && l.value[0]) l.value[0] = { type: 'origin', value: l.value[0].column } + / name:proc_func_name __ LPAREN __ l:or_and_where_expr? __ RPAREN { + // => { type: 'function'; name: proc_func_name; args: expr_list; } + if (l && l.type !== 'expr_list') l = { type: 'expr_list', value: [l] } return { type: 'function', name: name, args: l ? l: { type: 'expr_list', value: [] }, - over: bc, ...getLocationObject(), }; } + +extract_filed + = f:('CENTURY'i / 'DAY'i / 'DATE'i / 'DECADE'i / 'DOW'i / 'DOY'i / 'EPOCH'i / 'HOUR'i / 'ISODOW'i / 'ISOYEAR'i / 'MICROSECONDS'i / 'MILLENNIUM'i / 'MILLISECONDS'i / 'MINUTE'i / 'MONTH'i / 'QUARTER'i / 'SECOND'i / 'TIMEZONE'i / 'TIMEZONE_HOUR'i / 'TIMEZONE_MINUTE'i / 'WEEK'i / 'YEAR'i) { + // => 'string' + return f + } +extract_func + = kw:KW_EXTRACT __ LPAREN __ f:extract_filed __ KW_FROM __ t:(KW_TIMESTAMP / KW_INTERVAL / KW_TIME / KW_DATE)? __ s:expr __ RPAREN { + // => { type: 'extract'; args: { field: extract_filed; cast_type: 'TIMESTAMP' | 'INTERVAL' | 'TIME'; source: expr; }} + return { + type: kw.toLowerCase(), + args: { + field: f, + cast_type: t, + source: s, + }, + ...getLocationObject(), + } + } + / kw:KW_EXTRACT __ LPAREN __ f:extract_filed __ KW_FROM __ s:expr __ RPAREN { + // => { type: 'extract'; args: { field: extract_filed; source: expr; }} + return { + type: kw.toLowerCase(), + args: { + field: f, + source: s, + }, + ...getLocationObject(), + } + } + scalar_time_func = KW_CURRENT_DATE / KW_CURRENT_TIME / KW_CURRENT_TIMESTAMP + scalar_func = scalar_time_func / KW_CURRENT_USER / KW_USER / KW_SESSION_USER / KW_SYSTEM_USER - -cast_expr - = c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ ch:character_string_type __ cs:create_option_character_set_kw __ v:ident_without_kw_type __ RPAREN { - const { dataType, length } = ch - let dataTypeStr = dataType - if (length !== undefined) dataTypeStr = `${dataTypeStr}(${length})` + / "NTILE"i + +cast_double_colon + = s:KW_DOUBLE_COLON __ t:data_type __ alias:alias_clause? { + /* => { + as?: alias_clause, + symbol: '::' | 'as', + target: data_type; + } + */ return { - type: 'cast', - keyword: c.toLowerCase(), - expr: e, - symbol: 'as', - target: { - dataType: dataTypeStr, - suffix: [{ type: 'origin', value: cs }, v], - }, - }; + as: alias, + symbol: '::', + target: t, + } } - / c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ t:data_type __ RPAREN { +cast_expr + = c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ t:data_type __ RPAREN { + // => IGNORE return { type: 'cast', keyword: c.toLowerCase(), expr: e, symbol: 'as', - target: t + target: t, }; } / c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ KW_DECIMAL __ LPAREN __ precision:int __ RPAREN __ RPAREN { + // => IGNORE return { type: 'cast', keyword: c.toLowerCase(), @@ -3602,6 +5008,7 @@ cast_expr }; } / c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ KW_DECIMAL __ LPAREN __ precision:int __ COMMA __ scale:int __ RPAREN __ RPAREN { + // => IGNORE return { type: 'cast', keyword: c.toLowerCase(), @@ -3613,44 +5020,97 @@ cast_expr }; } / c:(KW_CAST / KW_TRY_CAST) __ LPAREN __ e:expr __ KW_AS __ s:signedness __ t:KW_INTEGER? __ RPAREN { /* MySQL cast to un-/signed integer */ + // => IGNORE return { type: 'cast', keyword: c.toLowerCase(), expr: e, symbol: 'as', target: { - dataType: [s, t].filter(Boolean).join(' ') + dataType: s + (t ? ' ' + t: '') } }; } + / LPAREN __ e:(or_expr / column_ref_array_index / param) __ RPAREN __ c:cast_double_colon? { + /* => { + type: 'cast'; + expr: or_expr | column_ref | param + | expr; + keyword: 'cast'; + } & cast_double_colon + */ + e.parentheses = true + if (!c) return e + return { + type: 'cast', + keyword: 'cast', + expr: e, + ...c, + } + } + / e:(column_ref_quoted / literal / aggr_func / window_func / func_call / case_expr / interval_expr / column_ref_array_index / param) __ c:cast_double_colon? { + /* => ({ + type: 'cast'; + expr: literal | jsonb_expr | aggr_func | func_call | case_expr | interval_expr | column_ref | param + | expr; + keyword: 'cast'; + } & cast_double_colon) + */ + if (!c) return e + return { + type: 'cast', + keyword: 'cast', + expr: e, + ...c, + } + } + signedness = KW_SIGNED / KW_UNSIGNED literal - = b:('binary'i / '_binary'i)? __ s:literal_string ca:(__ collate_expr)? { - if (b) s.prefix = b.toLowerCase() - if (ca) s.suffix = { collate: ca[1] } - return s - } + = literal_string / literal_numeric / literal_bool / literal_null / literal_datetime + / literal_array + +literal_array + = s:KW_ARRAY __ LBRAKE __ c:expr_list? __ RBRAKE { + /* + => { + expr_list: expr_list | {type: 'origin', value: ident }, + type: string, + keyword: string, + brackets: boolean + } + */ + return { + expr_list: c || { type: 'origin', value: '' }, + type: 'array', + keyword: 'array', + brackets: true + } + } literal_list = head:literal tail:(__ COMMA __ literal)* { + // => literal[] return createList(head, tail); } literal_null = KW_NULL { + // => { type: 'null'; value: null } return { type: 'null', value: null }; } literal_not_null = KW_NOT_NULL { + // => { type: 'not null'; value: 'not null' } return { type: 'not null', value: 'not null', @@ -3659,48 +5119,34 @@ literal_not_null literal_bool = KW_TRUE { + // => { type: 'bool', value: true } return { type: 'bool', value: true }; } / KW_FALSE { + //=> { type: 'bool', value: false } return { type: 'bool', value: false }; } - literal_string - = b:('_binary'i / '_latin1'i)? __ r:'X'i ca:("'" [0-9A-Fa-f]* "'") { + = ca:("'" single_char* "'") [\n]+ __ fs:("'" single_char* "'") { + // => { type: 'single_quote_string'; value: string; } return { - type: 'hex_string', - prefix: b, - value: ca[1].join('') - }; - } - / b:('_binary'i / '_latin1'i)? __ r:'b'i ca:("'" [0-9A-Fa-f]* "'") { - return { - type: 'bit_string', - prefix: b, - value: ca[1].join('') + type: 'single_quote_string', + value: `${ca[1].join('')}${fs[1].join('')}` }; } - / b:('_binary'i / '_latin1'i)? __ r:'0x'i ca:([0-9A-Fa-f]*) { - return { - type: 'full_hex_string', - prefix: b, - value: ca.join('') - }; - } - / r:'N'i ca:("'" single_char* "'") { - return { - type: 'natural_string', - value: ca[1].join('') - }; - } / ca:("'" single_char* "'") { + // => { type: 'single_quote_string'; value: string; } return { type: 'single_quote_string', value: ca[1].join('') }; } - / ca:("\"" single_quote_char* "\"") { + / literal_double_quoted_string + +literal_double_quoted_string + = ca:("\"" single_quote_char* "\"") !DOT { + // => { type: 'string'; value: string; } return { type: 'double_quote_string', value: ca[1].join('') @@ -3709,12 +5155,14 @@ literal_string literal_datetime = type:(KW_TIME / KW_DATE / KW_TIMESTAMP / KW_DATETIME) __ ca:("'" single_char* "'") { + // => { type: 'TIME' | 'DATE' | 'TIMESTAMP' | 'DATETIME', value: string } return { type: type.toLowerCase(), value: ca[1].join('') }; } / type:(KW_TIME / KW_DATE / KW_TIMESTAMP / KW_DATETIME) __ ca:("\"" single_quote_char* "\"") { + // => { type: 'TIME' | 'DATE' | 'TIMESTAMP' | 'DATETIME', value: string } return { type: type.toLowerCase(), value: ca[1].join('') @@ -3726,7 +5174,7 @@ single_quote_char / escape_char single_char - = [^'\\] // remove \0-\x1F\x7f pnCtrl char [^'\\\0-\x1F\x7f] + = [^'\\] / escape_char escape_char @@ -3744,35 +5192,36 @@ escape_char } / "\\" { return "\\"; } / "''" { return "''" } - / '""' { return '""' } - / '``' { return '``' } line_terminator = [\n\r] literal_numeric = n:number { + // => number | { type: 'bigint'; value: string; } if (n && n.type === 'bigint') return n return { type: 'number', value: n }; } number - = int_:int frac:frac exp:exp { - const numStr = int_ + frac + exp + = int_:int? frac:frac exp:exp { + const numStr = (int_ || '') + frac + exp return { type: 'bigint', value: numStr } } - / int_:int frac:frac { - const numStr = int_ + frac - if (isBigInt(int_)) return { + / int_:int? frac:frac { + // => IGNORE + const numStr = (int_ || '') + frac + if (int_ && isBigInt(int_)) return { type: 'bigint', value: numStr } return parseFloat(numStr); } / int_:int exp:exp { + // => IGNORE const numStr = int_ + exp return { type: 'bigint', @@ -3780,6 +5229,7 @@ number } } / int_:int { + // => IGNORE if (isBigInt(int_)) return { type: 'bigint', value: int_ @@ -3791,13 +5241,10 @@ int = digits / digit:digit / op:("-" / "+" ) digits:digits { return op + digits; } - / op:("-" / "+" ) digit:digit { return op + digit; } + / op:("-" / "+" ) digit:digit { return op + digit; } frac - = "." digits:digits? { - if (!digits) return '' - return "." + digits; - } + = "." digits:digits { return "." + digits; } exp = e:e digits:digits { return e + digits; } @@ -3805,7 +5252,7 @@ exp digits = digits:digit+ { return digits.join(""); } -digit = [0-9] +digit = [0-9] hexDigit = [0-9a-fA-F] @@ -3829,10 +5276,12 @@ KW_SELECT = "SELECT"i !ident_start KW_UPDATE = "UPDATE"i !ident_start KW_CREATE = "CREATE"i !ident_start KW_TEMPORARY = "TEMPORARY"i !ident_start +KW_TEMP = "TEMP"i !ident_start KW_DELETE = "DELETE"i !ident_start KW_INSERT = "INSERT"i !ident_start -KW_RECURSIVE= "RECURSIVE" !ident_start +KW_RECURSIVE= "RECURSIVE" !ident_start { return 'RECURSIVE'; } KW_REPLACE = "REPLACE"i !ident_start +KW_RETURNING = "RETURNING"i !ident_start { return 'RETURNING' } KW_RENAME = "RENAME"i !ident_start KW_IGNORE = "IGNORE"i !ident_start KW_EXPLAIN = "EXPLAIN"i !ident_start @@ -3841,47 +5290,45 @@ KW_PARTITION = "PARTITION"i !ident_start { return 'PARTITION' } KW_INTO = "INTO"i !ident_start KW_FROM = "FROM"i !ident_start KW_SET = "SET"i !ident_start { return 'SET' } -KW_UNLOCK = "UNLOCK"i !ident_start KW_LOCK = "LOCK"i !ident_start KW_AS = "AS"i !ident_start KW_TABLE = "TABLE"i !ident_start { return 'TABLE'; } -KW_TRIGGER = "TRIGGER"i !ident_start { return 'TRIGGER'; } -KW_TABLES = "TABLES"i !ident_start { return 'TABLES'; } KW_DATABASE = "DATABASE"i !ident_start { return 'DATABASE'; } KW_SCHEMA = "SCHEMA"i !ident_start { return 'SCHEMA'; } +KW_SEQUENCE = "SEQUENCE"i !ident_start { return 'SEQUENCE'; } +KW_TABLESPACE = "TABLESPACE"i !ident_start { return 'TABLESPACE'; } KW_COLLATE = "COLLATE"i !ident_start { return 'COLLATE'; } +KW_COLLATION = "COLLATION"i !ident_start { return 'COLLATION'; } +KW_DEALLOCATE = "DEALLOCATE"i !ident_start { return 'DEALLOCATE'; } KW_ON = "ON"i !ident_start KW_LEFT = "LEFT"i !ident_start KW_RIGHT = "RIGHT"i !ident_start KW_FULL = "FULL"i !ident_start KW_INNER = "INNER"i !ident_start -KW_CROSS = "CROSS"i !ident_start KW_JOIN = "JOIN"i !ident_start KW_OUTER = "OUTER"i !ident_start -KW_OVER = "OVER"i !ident_start KW_UNION = "UNION"i !ident_start -KW_MINUS = "MINUS"i !ident_start -KW_INTERSECT = "INTERSECT"i !ident_start +KW_INTERSECT = "INTERSECT"i !ident_start +KW_EXCEPT = "EXCEPT"i !ident_start KW_VALUES = "VALUES"i !ident_start KW_USING = "USING"i !ident_start KW_WHERE = "WHERE"i !ident_start KW_WITH = "WITH"i !ident_start -KW_GO = "GO"i !ident_start { return 'GO'; } KW_GROUP = "GROUP"i !ident_start KW_BY = "BY"i !ident_start KW_ORDER = "ORDER"i !ident_start KW_HAVING = "HAVING"i !ident_start +KW_WINDOW = "WINDOW"i !ident_start KW_LIMIT = "LIMIT"i !ident_start -KW_OFFSET = "OFFSET"i !ident_start { return 'OFFSET'; } +KW_OFFSET = "OFFSET"i !ident_start { return 'OFFSET' } KW_ASC = "ASC"i !ident_start { return 'ASC'; } KW_DESC = "DESC"i !ident_start { return 'DESC'; } -KW_DESCRIBE = "DESCRIBE"i !ident_start { return 'DESCRIBE'; } KW_ALL = "ALL"i !ident_start { return 'ALL'; } KW_DISTINCT = "DISTINCT"i !ident_start { return 'DISTINCT';} @@ -3890,14 +5337,16 @@ KW_BETWEEN = "BETWEEN"i !ident_start { return 'BETWEEN'; } KW_IN = "IN"i !ident_start { return 'IN'; } KW_IS = "IS"i !ident_start { return 'IS'; } KW_LIKE = "LIKE"i !ident_start { return 'LIKE'; } -KW_RLIKE = "RLIKE"i !ident_start { return 'RLIKE'; } -KW_REGEXP = "REGEXP"i !ident_start { return 'REGEXP'; } -KW_EXISTS = "EXISTS"i !ident_start { return 'EXISTS'; } +KW_ILIKE = "ILIKE"i !ident_start { return 'ILIKE'; } +KW_EXISTS = "EXISTS"i !ident_start { /* => 'EXISTS' */ return 'EXISTS'; } KW_NOT = "NOT"i !ident_start { return 'NOT'; } KW_AND = "AND"i !ident_start { return 'AND'; } KW_OR = "OR"i !ident_start { return 'OR'; } +KW_ARRAY = "ARRAY"i !ident_start { return 'ARRAY'; } +KW_ARRAY_AGG = "ARRAY_AGG"i !ident_start { return 'ARRAY_AGG'; } +KW_STRING_AGG = "STRING_AGG"i !ident_start { return 'STRING_AGG'; } KW_COUNT = "COUNT"i !ident_start { return 'COUNT'; } KW_GROUP_CONCAT = "GROUP_CONCAT"i !ident_start { return 'GROUP_CONCAT'; } KW_MAX = "MAX"i !ident_start { return 'MAX'; } @@ -3917,10 +5366,10 @@ KW_END = "END"i !ident_start KW_CAST = "CAST"i !ident_start { return 'CAST' } KW_TRY_CAST = "TRY_CAST"i !ident_start { return 'TRY_CAST' } -KW_BINARY = "BINARY"i !ident_start { return 'BINARY'; } -KW_VARBINARY = "VARBINARY"i !ident_start { return 'VARBINARY'; } -KW_BIT = "BIT"i !ident_start { return 'BIT'; } +KW_BOOL = "BOOL"i !ident_start { return 'BOOL'; } +KW_BOOLEAN = "BOOLEAN"i !ident_start { return 'BOOLEAN'; } KW_CHAR = "CHAR"i !ident_start { return 'CHAR'; } +KW_CHARACTER = "CHARACTER"i !ident_start { return 'CHARACTER'; } KW_VARCHAR = "VARCHAR"i !ident_start { return 'VARCHAR';} KW_NUMERIC = "NUMERIC"i !ident_start { return 'NUMERIC'; } KW_DECIMAL = "DECIMAL"i !ident_start { return 'DECIMAL'; } @@ -3930,55 +5379,57 @@ KW_INT = "INT"i !ident_start { return 'INT'; } KW_ZEROFILL = "ZEROFILL"i !ident_start { return 'ZEROFILL'; } KW_INTEGER = "INTEGER"i !ident_start { return 'INTEGER'; } KW_JSON = "JSON"i !ident_start { return 'JSON'; } +KW_JSONB = "JSONB"i !ident_start { return 'JSONB'; } +KW_GEOMETRY = "GEOMETRY"i !ident_start { return 'GEOMETRY'; } KW_SMALLINT = "SMALLINT"i !ident_start { return 'SMALLINT'; } -KW_MEDIUMINT = "MEDIUMINT"i !ident_start { return 'MEDIUMINT'; } +KW_SERIAL = "SERIAL"i !ident_start { return 'SERIAL'; } KW_TINYINT = "TINYINT"i !ident_start { return 'TINYINT'; } KW_TINYTEXT = "TINYTEXT"i !ident_start { return 'TINYTEXT'; } KW_TEXT = "TEXT"i !ident_start { return 'TEXT'; } KW_MEDIUMTEXT = "MEDIUMTEXT"i !ident_start { return 'MEDIUMTEXT'; } KW_LONGTEXT = "LONGTEXT"i !ident_start { return 'LONGTEXT'; } +KW_MEDIUMINT = "MEDIUMINT"i !ident_start { return 'MEDIUMINT'; } KW_BIGINT = "BIGINT"i !ident_start { return 'BIGINT'; } KW_ENUM = "ENUM"i !ident_start { return 'ENUM'; } KW_FLOAT = "FLOAT"i !ident_start { return 'FLOAT'; } KW_DOUBLE = "DOUBLE"i !ident_start { return 'DOUBLE'; } +KW_BIGSERIAL = "BIGSERIAL"i !ident_start { return 'BIGSERIAL'; } +KW_REAL = "REAL"i !ident_start { return 'REAL'; } KW_DATE = "DATE"i !ident_start { return 'DATE'; } KW_DATETIME = "DATETIME"i !ident_start { return 'DATETIME'; } KW_ROWS = "ROWS"i !ident_start { return 'ROWS'; } KW_TIME = "TIME"i !ident_start { return 'TIME'; } -KW_TIMESTAMP = "TIMESTAMP"i !ident_start { return 'TIMESTAMP'; } -KW_YEAR = "YEAR"i !ident_start { return 'YEAR'; } +KW_TIMESTAMP = "TIMESTAMP"i!ident_start { return 'TIMESTAMP'; } +KW_TIMESTAMPTZ = "TIMESTAMPTZ"i!ident_start { return 'TIMESTAMPTZ'; } KW_TRUNCATE = "TRUNCATE"i !ident_start { return 'TRUNCATE'; } KW_USER = "USER"i !ident_start { return 'USER'; } +KW_UUID = "UUID"i !ident_start { return 'UUID'; } +KW_OID = "OID"i !ident_start { return 'OID'; } +KW_REGCLASS = "REGCLASS"i !ident_start { return 'REGCLASS'; } +KW_REGCOLLATION = "REGCOLLATION"i !ident_start { return 'REGCOLLATION'; } +KW_REGCONFIG = "REGCONFIG"i !ident_start { return 'REGCONFIG'; } +KW_REGDICTIONARY = "REGDICTIONARY"i !ident_start { return 'REGDICTIONARY'; } +KW_REGNAMESPACE = "REGNAMESPACE"i !ident_start { return 'REGNAMESPACE'; } +KW_REGOPER = "REGOPER"i !ident_start { return 'REGOPER'; } +KW_REGOPERATOR = "REGOPERATOR"i !ident_start { return 'REGOPERATOR'; } +KW_REGPROC = "REGPROC"i !ident_start { return 'REGPROC'; } +KW_REGPROCEDURE = "REGPROCEDURE"i !ident_start { return 'REGPROCEDURE'; } +KW_REGROLE = "REGROLE"i !ident_start { return 'REGROLE'; } +KW_REGTYPE = "REGTYPE"i !ident_start { return 'REGTYPE'; } KW_CURRENT_DATE = "CURRENT_DATE"i !ident_start { return 'CURRENT_DATE'; } KW_ADD_DATE = "ADDDATE"i !ident_start { return 'ADDDATE'; } - KW_INTERVAL = "INTERVAL"i !ident_start { return 'INTERVAL'; } KW_UNIT_YEAR = "YEAR"i !ident_start { return 'YEAR'; } -KW_UNIT_QUARTER = "QUARTER"i !ident_start { return 'QUARTER'; } KW_UNIT_MONTH = "MONTH"i !ident_start { return 'MONTH'; } -KW_UNIT_WEEK = "WEEK"i !ident_start { return 'WEEK'; } KW_UNIT_DAY = "DAY"i !ident_start { return 'DAY'; } KW_UNIT_HOUR = "HOUR"i !ident_start { return 'HOUR'; } KW_UNIT_MINUTE = "MINUTE"i !ident_start { return 'MINUTE'; } KW_UNIT_SECOND = "SECOND"i !ident_start { return 'SECOND'; } -KW_UNIT_MICROSECOND = "MICROSECOND"i !ident_start { return 'MICROSECOND'; } - -KW_UNIT_SECOND_MICROSECOND = "SECOND_MICROSECOND"i !ident_start { return 'SECOND_MICROSECOND'; } -KW_UNIT_MINUTE_MICROSECOND = "MINUTE_MICROSECOND"i !ident_start { return 'MINUTE_MICROSECOND'; } -KW_UNIT_MINUTE_SECOND = "MINUTE_SECOND"i !ident_start { return 'MINUTE_SECOND'; } -KW_UNIT_HOUR_MICROSECOND = "HOUR_MICROSECOND"i !ident_start { return 'HOUR_MICROSECOND'; } -KW_UNIT_HOUR_SECOND = "HOUR_SECOND"i !ident_start { return 'HOUR_SECOND'; } -KW_UNIT_HOUR_MINUTE = "HOUR_MINUTE"i !ident_start { return 'HOUR_MINUTE'; } -KW_UNIT_DAY_MICROSECOND = "DAY_MICROSECOND"i !ident_start { return 'DAY_MICROSECOND'; } -KW_UNIT_DAY_SECOND = "DAY_SECOND"i !ident_start { return 'DAY_SECOND'; } -KW_UNIT_DAY_MINUTE = "DAY_MINUTE"i !ident_start { return 'DAY_MINUTE'; } -KW_UNIT_DAY_HOUR = "DAY_HOUR"i !ident_start { return 'DAY_HOUR'; } -KW_UNIT_YEAR_MONTH = "YEAR_MONTH"i !ident_start { return 'YEAR_MONTH'; } - KW_CURRENT_TIME = "CURRENT_TIME"i !ident_start { return 'CURRENT_TIME'; } KW_CURRENT_TIMESTAMP= "CURRENT_TIMESTAMP"i !ident_start { return 'CURRENT_TIMESTAMP'; } KW_CURRENT_USER = "CURRENT_USER"i !ident_start { return 'CURRENT_USER'; } +KW_CURRENT_ROLE = "CURRENT_ROLE"i !ident_start { return 'CURRENT_ROLE'; } KW_SESSION_USER = "SESSION_USER"i !ident_start { return 'SESSION_USER'; } KW_SYSTEM_USER = "SYSTEM_USER"i !ident_start { return 'SYSTEM_USER'; } @@ -3989,22 +5440,15 @@ KW_PERSIST = "PERSIST"i !ident_start { return 'PERSIST'; } KW_PERSIST_ONLY = "PERSIST_ONLY"i !ident_start { return 'PERSIST_ONLY'; } KW_VIEW = "VIEW"i !ident_start { return 'VIEW'; } -KW_GEOMETRY = "GEOMETRY"i !ident_start { return 'GEOMETRY'; } -KW_POINT = "POINT"i !ident_start { return 'POINT'; } -KW_LINESTRING = "LINESTRING"i !ident_start { return 'LINESTRING'; } -KW_POLYGON = "POLYGON"i !ident_start { return 'POLYGON'; } -KW_MULTIPOINT = "MULTIPOINT"i !ident_start { return 'MULTIPOINT'; } -KW_MULTILINESTRING = "MULTILINESTRING"i !ident_start { return 'MULTILINESTRING'; } -KW_MULTIPOLYGON = "MULTIPOLYGON"i !ident_start { return 'MULTIPOLYGON'; } -KW_GEOMETRYCOLLECTION = "GEOMETRYCOLLECTION"i !ident_start { return 'GEOMETRYCOLLECTION'; } - KW_VAR__PRE_AT = '@' KW_VAR__PRE_AT_AT = '@@' KW_VAR_PRE_DOLLAR = '$' +KW_VAR_PRE_DOLLAR_DOUBLE = '$$' KW_VAR_PRE - = KW_VAR__PRE_AT_AT / KW_VAR__PRE_AT / KW_VAR_PRE_DOLLAR + = KW_VAR__PRE_AT_AT / KW_VAR__PRE_AT / KW_VAR_PRE_DOLLAR / KW_VAR_PRE_DOLLAR KW_RETURN = 'return'i KW_ASSIGN = ':=' +KW_DOUBLE_COLON = '::' KW_ASSIGIN_EQUAL = '=' KW_DUAL = "DUAL"i @@ -4013,7 +5457,6 @@ KW_DUAL = "DUAL"i KW_ADD = "ADD"i !ident_start { return 'ADD'; } KW_COLUMN = "COLUMN"i !ident_start { return 'COLUMN'; } KW_INDEX = "INDEX"i !ident_start { return 'INDEX'; } -KW_MODIFY = "MODIFY"i !ident_start { return 'MODIFY'; } KW_KEY = "KEY"i !ident_start { return 'KEY'; } KW_FULLTEXT = "FULLTEXT"i !ident_start { return 'FULLTEXT'; } KW_SPATIAL = "SPATIAL"i !ident_start { return 'SPATIAL'; } @@ -4021,8 +5464,11 @@ KW_UNIQUE = "UNIQUE"i !ident_start { return 'UNIQUE'; } KW_KEY_BLOCK_SIZE = "KEY_BLOCK_SIZE"i !ident_start { return 'KEY_BLOCK_SIZE'; } KW_COMMENT = "COMMENT"i !ident_start { return 'COMMENT'; } KW_CONSTRAINT = "CONSTRAINT"i !ident_start { return 'CONSTRAINT'; } +KW_CONCURRENTLY = "CONCURRENTLY"i !ident_start { return 'CONCURRENTLY'; } KW_REFERENCES = "REFERENCES"i !ident_start { return 'REFERENCES'; } + + // MySQL extensions to SQL OPT_SQL_CALC_FOUND_ROWS = "SQL_CALC_FOUND_ROWS"i OPT_SQL_CACHE = "SQL_CACHE"i @@ -4044,11 +5490,12 @@ RBRAKE = ']' SEMICOLON = ';' SINGLE_ARROW = '->' DOUBLE_ARROW = '->>' +WELL_ARROW = '#>' +DOUBLE_WELL_ARROW = '#>>' OPERATOR_CONCATENATION = '||' OPERATOR_AND = '&&' -OPERATOR_XOR = 'XOR'i !ident_start { return 'XOR' } -LOGIC_OPERATOR = OPERATOR_CONCATENATION / OPERATOR_AND / OPERATOR_XOR +LOGIC_OPERATOR = OPERATOR_CONCATENATION / OPERATOR_AND // separator __ @@ -4060,10 +5507,9 @@ ___ comment = block_comment / line_comment - / pound_sign_comment block_comment - = "/*" (!"*/" char)* "*/" + = "/*" (!"*/" !"/*" char / block_comment)* "*/" line_comment = "--" (!EOL char)* @@ -4073,6 +5519,7 @@ pound_sign_comment keyword_comment = k:KW_COMMENT __ s:KW_ASSIGIN_EQUAL? __ c:literal_string { + // => { type: 'comment'; keyword: 'comment'; symbol: '='; value: literal_string; } return { type: k.toLowerCase(), keyword: k.toLowerCase(), @@ -4085,25 +5532,11 @@ char = . interval_unit = KW_UNIT_YEAR - / KW_UNIT_QUARTER / KW_UNIT_MONTH - / KW_UNIT_WEEK / KW_UNIT_DAY / KW_UNIT_HOUR / KW_UNIT_MINUTE / KW_UNIT_SECOND - / KW_UNIT_MICROSECOND - / KW_UNIT_SECOND_MICROSECOND - / KW_UNIT_MINUTE_MICROSECOND - / KW_UNIT_MINUTE_SECOND - / KW_UNIT_HOUR_MICROSECOND - / KW_UNIT_HOUR_SECOND - / KW_UNIT_HOUR_MINUTE - / KW_UNIT_DAY_MICROSECOND - / KW_UNIT_DAY_SECOND - / KW_UNIT_DAY_MINUTE - / KW_UNIT_DAY_HOUR - / KW_UNIT_YEAR_MONTH whitespace = [ \t\n\r] @@ -4120,30 +5553,25 @@ proc_stmts proc_stmt = &{ varList = []; return true; } __ s:(assign_stmt / return_stmt) { - return { stmt: s, vars: varList }; + /* export interface proc_stmt_t { type: 'proc'; stmt: assign_stmt | return_stmt; vars: any } + => AstStatement + */ + return { type: 'proc', stmt: s, vars: varList } } assign_stmt_list = head:assign_stmt tail:(__ COMMA __ assign_stmt)* { + // => assign_stmt[] return createList(head, tail); } assign_stmt - = va:(var_decl / without_prefix_var_decl) __ s: (KW_ASSIGN / KW_ASSIGIN_EQUAL) __ e:proc_expr { - return { - type: 'assign', - left: va, - symbol: s, - right: e - }; - } - -select_assign_stmt - = va:(var_decl / without_prefix_var_decl) __ s:KW_ASSIGN __ e:proc_expr { + = va:(var_decl / without_prefix_var_decl) __ s:(KW_ASSIGN / KW_ASSIGIN_EQUAL / KW_TO) __ e:proc_expr { + // => { type: 'assign'; left: var_decl | without_prefix_var_decl; symbol: ':=' | '='; right: proc_expr; } return { type: 'assign', left: va, - symbol: s, + symbol: Array.isArray(s) ? s[0] : s, right: e }; } @@ -4151,6 +5579,7 @@ select_assign_stmt return_stmt = KW_RETURN __ e:proc_expr { + // => { type: 'return'; expr: proc_expr; } return { type: 'return', expr: e }; } @@ -4163,17 +5592,20 @@ proc_expr proc_additive_expr = head:proc_multiplicative_expr tail:(__ additive_operator __ proc_multiplicative_expr)* { + // => binary_expr return createBinaryExprChain(head, tail); } proc_multiplicative_expr = head:proc_primary tail:(__ multiplicative_operator __ proc_primary)* { + // => binary_expr return createBinaryExprChain(head, tail); } proc_join = lt:var_decl __ op:join_op __ rt:var_decl __ expr:on_clause { + // => { type: 'join'; ltable: var_decl; rtable: var_decl; op: join_op; expr: on_clause; } return { type: 'join', ltable: lt, @@ -4184,19 +5616,32 @@ proc_join } proc_primary - = proc_func_call_args - / literal + = literal / var_decl - / column_ref - / proc_fun_call_without_args + / proc_func_call / param / LPAREN __ e:proc_additive_expr __ RPAREN { + // => proc_additive_expr & { parentheses: true; } e.parentheses = true; return e; } + / n:ident_name s:(DOT __ ident_name)? { + // => { type: 'var'; prefix: null; name: number; members: []; quoted: null } | column_ref + if (!s) return { + type: 'var', + name: n, + prefix: null + } + return { + type: 'column_ref', + table: n, + column: s[2] + } + } proc_func_name - = dt:(ident_name_type / backticks_quoted_ident) tail:(__ DOT __ ((ident_name_type / backticks_quoted_ident)))? { + = dt:ident_without_kw_type tail:(__ DOT __ ident_without_kw_type)? { + // => { schema?: ident_without_kw_type, name: ident_without_kw_type } const result = { name: [dt] } if (tail !== null) { result.schema = dt @@ -4205,8 +5650,9 @@ proc_func_name return result } -proc_func_call_args +proc_func_call = name:proc_func_name __ LPAREN __ l:proc_primary_list? __ RPAREN { + // => { type: 'function'; name: string; args: null | { type: expr_list; value: proc_primary_list; }} //compatible with original func_call return { type: 'function', @@ -4218,35 +5664,47 @@ proc_func_call_args ...getLocationObject(), }; } -proc_fun_call_without_args - = name:proc_func_name { - return { - type: 'function', - name: name, - args: null, - ...getLocationObject(), - }; - } -proc_func_call - = proc_func_call_args / proc_fun_call_without_args proc_primary_list = head:proc_primary tail:(__ COMMA __ proc_primary)* { + // => proc_primary[] return createList(head, tail); } -proc_array = - LBRAKE __ l:proc_primary_list __ RBRAKE { +proc_array + = LBRAKE __ l:proc_primary_list __ RBRAKE { + // => { type: 'array'; value: proc_primary_list } return { type: 'array', value: l }; } var_decl_list = head:var_decl tail:(__ COMMA __ var_decl)* { + // => var_decl[] return createList(head, tail) } var_decl - = p: KW_VAR_PRE d: without_prefix_var_decl { + = p:KW_VAR_PRE_DOLLAR_DOUBLE d:[^$]* s:KW_VAR_PRE_DOLLAR_DOUBLE { + // => { type: 'var'; name: string; prefix: string; suffix: string; } + return { + type: 'var', + name: d.join(''), + prefix: '$$', + suffix: '$$' + }; + } + / KW_VAR_PRE_DOLLAR f:column KW_VAR_PRE_DOLLAR d:[^$]* KW_VAR_PRE_DOLLAR s:column !{ if (f !== s) return true } KW_VAR_PRE_DOLLAR { + // => { type: 'var'; name: string; prefix: string; suffix: string; } + return { + type: 'var', + name: d.join(''), + prefix: `$${f}$`, + suffix: `$${s}$` + }; + } + / p:KW_VAR_PRE d: without_prefix_var_decl { + // => without_prefix_var_decl & { type: 'var'; prefix: string; } + // push for analysis return { type: 'var', ...d, @@ -4255,17 +5713,21 @@ var_decl } without_prefix_var_decl - = name:ident_name m:mem_chain { + = p:'"'? name:ident_name m:mem_chain s:'"'? { + // => { type: 'var'; prefix: string; name: ident_name; members: mem_chain; quoted: string | null } //push for analysis + if ((p && !s) || (!p && s)) throw new Error('double quoted not match') varList.push(name); return { type: 'var', name: name, members: m, + quoted: p && s ? '"' : null, prefix: null, }; } / n:literal_numeric { + // => { type: 'var'; prefix: null; name: number; members: []; quoted: null } return { type: 'var', name: n.value, @@ -4277,6 +5739,7 @@ without_prefix_var_decl mem_chain = l:('.' ident_name)* { + // => ident_name[]; const s = []; for (let i = 0; i < l.length; i++) { s.push(l[i][1]); @@ -4285,73 +5748,109 @@ mem_chain } data_type - = character_string_type + = array_type + / character_string_type / numeric_type / datetime_type / json_type + / geometry_type / text_type - / enum_type + / uuid_type / boolean_type + / enum_type + / serial_interval_type / binary_type - / blob_type - / geometry_type + / oid_type + / record_type + / custom_types -data_type_size - = LPAREN __ l:[0-9]+ __ RPAREN __ s:numeric_type_suffix? { - return { - length: parseInt(l.join(''), 10), - parentheses: true, - suffix: s, - }; + +array_type + = t:(numeric_type / character_string_type) __ LBRAKE __ RBRAKE __ LBRAKE __ RBRAKE { + /* => data_type */ + return { ...t, array: { dimension: 2 } } } -boolean_type - = 'boolean'i { return { dataType: 'BOOLEAN' }; } + / t:(numeric_type / character_string_type) __ LBRAKE __ l:literal_numeric? __ RBRAKE { + /* => data_type */ + return { ...t, array: { dimension: 1, length: [l] } } + } + / t:(numeric_type / character_string_type) __ KW_ARRAY { + /* => data_type */ + return { ...t, array: { keyword: 'array' } } + } + -blob_type - = b:('blob'i / 'tinyblob'i / 'mediumblob'i / 'longblob'i) { return { dataType: b.toUpperCase() }; } +boolean_type + = t:(KW_BOOL / KW_BOOLEAN) { /* => data_type */ return { dataType: t }} binary_type - = t:(KW_BINARY / KW_VARBINARY) __ num:data_type_size? { - return { dataType: t, ...(num || {}) } - } + = 'bytea'i { /* => data_type */ return { dataType: 'BYTEA' }; } +character_varying + = KW_CHARACTER __ ('varying'i)? { + // => string + return 'CHARACTER VARYING' + } character_string_type - = t:(KW_CHAR / KW_VARCHAR) num:(__ LPAREN __ [0-9]+ __ RPAREN __ ('ARRAY'i)?)? { + = t:(KW_CHAR / KW_VARCHAR / character_varying) num:(__ LPAREN __ [0-9]+ __ RPAREN)? { + // => data_type const result = { dataType: t } if (num) { result.length = parseInt(num[3].join(''), 10) result.parentheses = true - result.suffix = num[7] && ['ARRAY'] } return result } numeric_type_suffix - = un:signedness? __ ze: KW_ZEROFILL? { + = un: KW_UNSIGNED? __ ze: KW_ZEROFILL? { + // => any[]; const result = [] if (un) result.push(un) if (ze) result.push(ze) return result } numeric_type - = t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_MEDIUMINT / KW_TINYINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE / KW_BIT) __ LPAREN __ l:[0-9]+ __ r:(COMMA __ [0-9]+)? __ RPAREN __ s:numeric_type_suffix? { return { dataType: t, length: parseInt(l.join(''), 10), scale: r && parseInt(r[2].join(''), 10), parentheses: true, suffix: s }; } - / t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_MEDIUMINT/ KW_TINYINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE)l:[0-9]+ __ s:numeric_type_suffix? { return { dataType: t, length: parseInt(l.join(''), 10), suffix: s }; } - / t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_MEDIUMINT / KW_TINYINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE) __ s:numeric_type_suffix? __{ return { dataType: t, suffix: s }; } + = t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_TINYINT / KW_MEDIUMINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE __ 'PRECISION'i / KW_DOUBLE / KW_SERIAL / KW_BIGSERIAL / KW_REAL) __ LPAREN __ l:[0-9]+ __ r:(COMMA __ [0-9]+)? __ RPAREN __ s:numeric_type_suffix? { /* => data_type */ return { dataType: Array.isArray(t) ? `${t[0].toUpperCase()} ${t[2].toUpperCase()}` : t, length: parseInt(l.join(''), 10), scale: r && parseInt(r[2].join(''), 10), parentheses: true, suffix: s }; } + / t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_TINYINT / KW_MEDIUMINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE __ 'PRECISION'i / KW_DOUBLE / KW_SERIAL / KW_BIGSERIAL / KW_REAL)l:[0-9]+ __ s:numeric_type_suffix? { /* => data_type */ return { dataType: Array.isArray(t) ? `${t[0].toUpperCase()} ${t[2].toUpperCase()}` : t, length: parseInt(l.join(''), 10), suffix: s }; } + / t:(KW_NUMERIC / KW_DECIMAL / KW_INT / KW_INTEGER / KW_SMALLINT / KW_TINYINT / KW_MEDIUMINT / KW_BIGINT / KW_FLOAT / KW_DOUBLE __ 'PRECISION'i / KW_DOUBLE / KW_SERIAL / KW_BIGSERIAL / KW_REAL) __ s:numeric_type_suffix? __{ /* => data_type */ return { dataType: Array.isArray(t) ? `${t[0].toUpperCase()} ${t[2].toUpperCase()}` : t, suffix: s }; } + +oid_type + = t:(KW_OID / KW_REGCLASS / KW_REGCOLLATION / KW_REGCONFIG / KW_REGDICTIONARY / KW_REGNAMESPACE / KW_REGOPER / KW_REGOPERATOR / KW_REGPROC / KW_REGPROCEDURE / KW_REGROLE / KW_REGTYPE) { /* => data_type */ return { dataType: t }} + +timezone + = w:('WITHOUT'i / 'WITH'i) __ KW_TIME __ 'ZONE'i { + // => string[]; + return [w.toUpperCase(), 'TIME', 'ZONE'] + } +time_type + = t:(KW_TIME / KW_TIMESTAMP / KW_TIMESTAMPTZ) num:(__ LPAREN __ [0-9]+ __ RPAREN )? __ tz:timezone? { + /* => data_type */ + const result = { dataType: t } + if (num) { + result.length = parseInt(num[3].join(''), 10) + result.parentheses = true + } + if (tz) result.suffix = tz + return result + } datetime_type - = t:(KW_DATE / KW_DATETIME / KW_TIME / KW_TIMESTAMP / KW_YEAR) num:(__ LPAREN __ [0-6] __ RPAREN __ numeric_type_suffix?)? { + = t:(KW_DATE / KW_DATETIME) num:(__ LPAREN __ [0-9]+ __ RPAREN)? { + /* => data_type */ const result = { dataType: t } if (num) { - result.length = parseInt(num[3], 10) + result.length = parseInt(num[3].join(''), 10) result.parentheses = true - result.suffix = num[7] } return result } + / time_type enum_type - = t:(KW_ENUM / KW_SET) __ e:value_item { + = t:KW_ENUM __ e:value_item { + /* => data_type */ e.parentheses = true return { dataType: t, @@ -4360,12 +5859,28 @@ enum_type } json_type - = t:KW_JSON { return { dataType: t }; } + = t:(KW_JSON / KW_JSONB) { /* => data_type */ return { dataType: t }; } + +geometry_type + = t:KW_GEOMETRY {/* => data_type */ return { dataType: t }; } + +serial_interval_type + = t:(KW_SERIAL / KW_INTERVAL) { /* => data_type */ return { dataType: t }; } text_type - = t:(KW_TINYTEXT / KW_TEXT / KW_MEDIUMTEXT / KW_LONGTEXT) num:data_type_size? { - return { dataType: t, ...(num || {}) } + = t:(KW_TINYTEXT / KW_TEXT / KW_MEDIUMTEXT / KW_LONGTEXT) s:(LBRAKE __ RBRAKE)? { + /* => data_type */ + return { dataType: `${t}${s ? '[]' : ''}` } } -geometry_type - = t:(KW_GEOMETRY / KW_POINT / KW_LINESTRING / KW_POLYGON / KW_MULTIPOINT / KW_MULTILINESTRING / KW_MULTIPOLYGON / KW_GEOMETRYCOLLECTION ) { return { dataType: t }} +uuid_type + = t:KW_UUID {/* => data_type */ return { dataType: t }} + +record_type + = 'RECORD'i {/* => data_type */ return { dataType: 'RECORD' }} + +custom_types + = name:ident_name &{ return customTypes.has(name) } { + // => data_type + return { dataType: name } + } From 1bdb50fca08240a35b653272bdb5bf494348c093 Mon Sep 17 00:00:00 2001 From: omkark06 Date: Fri, 6 Jun 2025 13:17:58 +0530 Subject: [PATCH 6/7] fix: avoid formatting float to integer when decimals are .00 --- pegjs/datafusionsql.pegjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pegjs/datafusionsql.pegjs b/pegjs/datafusionsql.pegjs index 7faf9657..b6dd8f2b 100644 --- a/pegjs/datafusionsql.pegjs +++ b/pegjs/datafusionsql.pegjs @@ -5218,7 +5218,7 @@ number type: 'bigint', value: numStr } - return parseFloat(numStr); + return parseFloat(numStr).toFixed(frac.length - 1); } / int_:int exp:exp { // => IGNORE From 1c1721b4d3fcda2cff87c544494815c747843338 Mon Sep 17 00:00:00 2001 From: Bhargav Date: Fri, 6 Jun 2025 15:19:03 +0530 Subject: [PATCH 7/7] fix: package publish --- npm_publish.md | 13 +++++++++++++ package.json | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 npm_publish.md diff --git a/npm_publish.md b/npm_publish.md new file mode 100644 index 00000000..e253b0d1 --- /dev/null +++ b/npm_publish.md @@ -0,0 +1,13 @@ +Step1: +create .npmrc file with below content: + +@openobserve:registry=https://registry.npmjs.org/ +//registry.npmjs.org/:_authToken=npm_wZJw**************** + +Note: replace the active apiToken to publish the package. + +Step2: change version in package.json file. + +Step3: build the package using npm run build + +Step4: Go to output/prod directory and run "npm publish --userconfig=../../.npmrc" command to publish the package \ No newline at end of file diff --git a/package.json b/package.json index 214ce094..a060d271 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "node-sql-parser", - "version": "5.3.2", + "name": "@openobserve/node-sql-parser", + "version": "0.1.5", "description": "simple node sql parser", "main": "index.js", "types": "types.d.ts",