blob: 6a0b07c1b7ce69855f516b8766e331efa6adb821 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* SQLite grammar adapted from http://www.sqlite.org/docsrc/doc/trunk/art/syntax/all-bnf.html
* This should correspond directly to diagrams in the "SQL Syntax" part of SQLite documentation,
* e.g. https://sqlite.org/lang_select.html. See also all diagrams here: http://www.sqlite.org/syntaxdiagrams.html
*
* Unfortunately the grammar linked above skips the most basic definitions, like string-literal,
* table-name or digit, so we need to fill in these gaps ourselves.
*
* The grammar for expressions (`expr`) also needed to be reworked, see below.
*
* This file is used by Grammar-Kit to generate the lexer, parser, node types and PSI classes for Android SQL.
*/
{
parserClass="com.android.tools.idea.lang.androidSql.parser.AndroidSqlParser"
parserUtilClass="com.android.tools.idea.lang.androidSql.parser.AndroidSqlParserUtil"
extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
psiClassPrefix="AndroidSql"
psiImplClassSuffix="Impl"
psiPackage="com.android.tools.idea.lang.androidSql.psi"
psiImplPackage="com.android.tools.idea.lang.androidSql.psi.impl"
psiImplUtilClass="com.android.tools.idea.lang.androidSql.psi.PsiImplUtil"
elementTypeHolderClass="com.android.tools.idea.lang.androidSql.psi.AndroidSqlPsiTypes"
elementTypeClass="com.android.tools.idea.lang.androidSql.psi.AndroidSqlAstNodeType"
tokenTypeClass="com.android.tools.idea.lang.androidSql.psi.AndroidSqlTokenType"
classHeader='generatedFilesHeader.txt'
tokens=[
// In the flex file we manually insert %caseless, in the preview only upper case keywords work.
NUMERIC_LITERAL='regexp:(([0-9]+(\.[0-9]*)?|\.[0-9]+)(E(\+|-)?[0-9]+)?)|(0x[0-9a-f]+)'
// Manually tweaked in the flex file to handle literals without the closing character.
SINGLE_QUOTE_STRING_LITERAL="regexp:X?'(''|[^'])*'"
DOUBLE_QUOTE_STRING_LITERAL="regexp:X?\"(\"\"|[^\"])*\""
BRACKET_LITERAL="regexp:\[[^\]]*\]"
BACKTICK_LITERAL="regexp:`(``|[^`])*`"
// Some approximation of what's actually allowed. This doesn't seem to be defined anywhere.
IDENTIFIER='regexp:([:letter:]|_)([:letter:]|[:digit:]|_)*'
NAMED_PARAMETER='regexp::\w+' // Simple regexp for Live Preview, this is tweaked in the flex file.
COMMENT='regexp:/\*.*\*/' // Simple regexp for Live Preview, this is tweaked in the flex file.
LINE_COMMENT='regexp:--[^r\n]*'
AMP='&'
BAR='|'
COMMA=','
CONCAT='||'
DIV='/'
DOT='.'
EQ='='
EQEQ='=='
GT='>'
GTE='>='
LPAREN='('
LT='<'
LTE='<='
MINUS='-'
MOD='%'
NOT_EQ='!='
PLUS='+'
RPAREN=')'
SEMICOLON=';'
SHL='<<'
SHR='>>'
STAR='*'
TILDE='~'
UNEQ='<>'
// More keywords from https://sqlite.org/lang_keywords.html that don't appear in the grammar below, but we want to treat them as
// keywords (so that the IDE escapes them just in case).
FULL='FULL'
RIGHT='RIGHT'
// Grammar-Kit's live preview emulates PsiBuilder's behavior of ignoring whitespace, by
// looking for a token type that matches a space and is not used otherwise. Here's one:
WHITE_SPACE_FOR_LIVE_PREVIEW='regexp:\s+'
]
extends(".*_expression")=expression
implements(".*_name")="com.android.tools.idea.lang.androidSql.psi.AndroidSqlNameElement"
mixin(".*_name")="com.android.tools.idea.lang.androidSql.psi.AbstractAndroidSqlNameElement"
generateTokenAccessors(".*_name")=false
generate=[names='long' java='8']
}
root ::= statement ';'?
private statement ::=
explain_prefix ?
(
select_statement
| update_statement
| insert_statement
| delete_statement
| with_clause_statement
| alter_table_statement
| analyze_statement
| attach_statement
| begin_statement
| commit_statement
| create_index_statement
| create_table_statement
| create_trigger_statement
| create_view_statement
| create_virtual_table_statement
| detach_statement
| drop_index_statement
| drop_table_statement
| drop_trigger_statement
| drop_view_statement
| pragma_statement
| reindex_statement
| release_statement
| rollback_statement
| savepoint_statement
| vacuum_statement
) { name="statement" }
explain_prefix ::= EXPLAIN ( QUERY PLAN )?
with_clause_statement ::= with_clause (delete_statement | insert_statement | update_statement | select_statement) {
implements="com.android.tools.idea.lang.androidSql.psi.HasWithClause"
}
with_clause_select_statement ::= with_clause select_statement {
implements="com.android.tools.idea.lang.androidSql.psi.HasWithClause"
}
// Variant of with_clause_select_statement that is inside parens and consumes all input until the matching paren.
private subquery_greedy ::= select_statement | with_clause_select_statement {
recoverWhile=subquery_recover
}
private subquery_recover ::= !')'
private rename_table_statement ::= RENAME TO table_definition_name
private rename_column_statement ::= RENAME COLUMN? column_name TO name
private add_column_statement ::= ADD COLUMN? column_definition
alter_table_statement ::= ALTER TABLE single_table_statement_table ( rename_table_statement | rename_column_statement | add_column_statement )
// Name of a table that is already in scope for the current query.
selected_table_name ::= name {
methods=[getReference]
}
// Name of a table defined in the schema or using a WITH clause.
defined_table_name ::= name {
methods=[getReference]
}
// Name of a table being defined.
table_definition_name ::= name {
implements="com.intellij.psi.PsiNamedElement"
methods=[getName setName]
}
database_name ::= name | TEMP
analyze_statement ::= ANALYZE ( database_name | table_or_index_name | database_name '.' table_or_index_name )?
table_or_index_name ::= name
attach_statement ::= ATTACH ( DATABASE )? expression AS database_name
begin_statement ::= BEGIN ( DEFERRED | IMMEDIATE | EXCLUSIVE )? ( TRANSACTION )?
commit_statement ::= ( COMMIT | END ) ( TRANSACTION )?
rollback_statement ::= ROLLBACK ( TRANSACTION )? ( TO ( SAVEPOINT )? savepoint_name )?
savepoint_name ::= name
savepoint_statement ::= SAVEPOINT savepoint_name
release_statement ::= RELEASE ( SAVEPOINT )? savepoint_name
create_index_statement ::=
CREATE ( UNIQUE )? INDEX ( IF NOT EXISTS )?
( database_name '.' )? index_name ON defined_table_name '(' indexed_column ( ',' indexed_column )* ')'
where_clause?
private index_name ::= name
indexed_column ::= column_name ( COLLATE collation_name )? ( ASC | DESC )?
column_name ::= name {
methods=[getReference]
}
column_definition_name ::= name {
implements="com.intellij.psi.PsiNamedElement"
methods=[getName setName]
}
collation_name ::= name
create_table_statement ::=
CREATE ( TEMP | TEMPORARY )? TABLE ( IF NOT EXISTS )?
( database_name '.' )? table_definition_name
( '(' column_definition ( ',' column_definition )* ( ',' table_constraint )* ')' ( WITHOUT "ROWID" )? | AS (select_statement | with_clause_select_statement) )
column_definition ::= column_definition_name ( type_name )? ( column_constraint )*
type_name ::= name ( '(' signed_number ')' | '(' signed_number ',' signed_number ')' )?
column_constraint ::=
( CONSTRAINT name )?
( PRIMARY KEY ( ASC | DESC )? conflict_clause ( AUTOINCREMENT )?
| NOT NULL conflict_clause
| UNIQUE conflict_clause
| CHECK '(' expression ')'
| DEFAULT ( signed_number | literal_value | '(' expression ')' )
| COLLATE collation_name | foreign_key_clause )
signed_number ::= ( '+' | '-' )? NUMERIC_LITERAL
table_constraint ::=
( CONSTRAINT name )?
( ( PRIMARY KEY | UNIQUE ) '(' indexed_column ( ',' indexed_column )* ')' conflict_clause
| CHECK '(' expression ')'
| FOREIGN KEY '(' column_name ( ',' column_name )* ')' foreign_key_clause )
foreign_key_clause ::=
REFERENCES foreign_table ( '(' column_name ( ',' column_name )* ')' )?
( ( ON ( DELETE | UPDATE ) ( SET NULL | SET DEFAULT | CASCADE | RESTRICT | NO ACTION ) | MATCH name ) )*
( ( NOT )? DEFERRABLE ( INITIALLY DEFERRED | INITIALLY IMMEDIATE )? )?
foreign_table ::= name
conflict_clause ::= ( ON CONFLICT ( ROLLBACK | ABORT | FAIL | IGNORE | REPLACE ) )?
create_trigger_statement ::=
CREATE ( TEMP | TEMPORARY )? TRIGGER ( IF NOT EXISTS )?
( database_name '.' )? trigger_name ( BEFORE | AFTER | INSTEAD OF )?
( DELETE | INSERT | UPDATE ( OF column_name ( ',' column_name )* )? ) ON defined_table_name
( FOR EACH ROW )? ( WHEN expression )?
BEGIN with_clause? ( update_statement | insert_statement | delete_statement | select_statement ) ';' END
trigger_name ::= name
create_view_statement ::=
CREATE ( TEMP | TEMPORARY )? VIEW ( IF NOT EXISTS )?
( database_name '.' )? view_name AS (select_statement | with_clause_select_statement)
view_name ::= name
create_virtual_table_statement ::=
CREATE VIRTUAL TABLE ( IF NOT EXISTS )?
( database_name '.' )? table_definition_name
USING module_name ( '(' module_argument ( ',' module_argument )* ')' )?
module_name ::= name
module_argument ::= name
with_clause ::= &WITH with_clause_greedy
private with_clause_greedy ::= WITH ( RECURSIVE )? with_clause_table ( ',' with_clause_table )* {
recoverWhile=with_clause_recover
pin=1
}
private with_clause_recover ::= !(DELETE | INSERT | REPLACE | SELECT | UPDATE | VALUES | ')')
with_clause_table ::= with_clause_table_def AS with_clause_table_def_subquery {
methods=[getTableDefinition]
}
private with_clause_table_def_subquery ::= '(' &(SELECT|VALUES|WITH) subquery_greedy ')' { pin=2 }
with_clause_table_def ::= table_definition_name ( '(' column_definition_name ( ',' column_definition_name )* ')' )?
delete_statement ::=
DELETE FROM single_table_statement_table ( INDEXED BY index_name | NOT INDEXED )?
where_clause?
( order_clause? LIMIT expression ( ( OFFSET | ',' ) expression )? )? {
}
detach_statement ::= DETACH ( DATABASE )? database_name
drop_index_statement ::= DROP INDEX ( IF EXISTS )? ( database_name '.' )? index_name
drop_table_statement ::= DROP TABLE ( IF EXISTS )? ( database_name '.' )? defined_table_name
drop_trigger_statement ::= DROP TRIGGER ( IF EXISTS )? ( database_name '.' )? trigger_name
drop_view_statement ::= DROP VIEW ( IF EXISTS )? ( database_name '.' )? view_name
// Below is the original, left-recursive grammar for expressions, as pictured on the diagram: http://www.sqlite.org/syntaxdiagrams.html#expr
//
// It needed to be changed to a form that Grammar-Kit accepts (due to left recursion), see this link for details:
// https://github.com/JetBrains/Grammar-Kit/blob/master/HOWTO.md#24-compact-expression-parsing-with-priorities
//
// Operator precedence taken from http://www.sqlite.org/lang_expr.html
//
//expr ::=
// literal_value
// | bind_parameter
// | ( ( database_name '.' )? table_name '.' )? column_name
// | unary_operator expr
// | expr binary_operator expr
// | function_name '(' ( ( DISTINCT )? expr ( ',' expr )* | '*' )? ')'
// | '(' expression ( ',' expression )* ')'
// | CAST '(' expr AS type_name ')'
// | expr COLLATE collation_name
// | expr ( NOT )? ( LIKE | GLOB | REGEXP | MATCH ) expr ( ESCAPE expr )?
// | expr ( ISNULL | NOTNULL | NOT NULL )
// | expr IS ( NOT )? expr
// | expr ( NOT )? BETWEEN expr AND expr
// | expr ( NOT )? IN ( '(' ( select_statement | expr ( ',' expr )* )? ')' | ( database_name '.' )? table_name )
// | ( ( NOT )? EXISTS )? '(' select_statement ')'
// | CASE ( expr )? WHEN expr THEN expr ( ELSE expr )? END
// | raise_function
expression ::=
raise_function_expression
| or_expression
| and_expression
| case_expression
| exists_expression
| in_expression
| isnull_expression
| like_expression
| cast_expression
| function_call_expression
| equivalence_group
| comparison_expression
| bit_expression
| add_expression
| mul_expression
| concat_expression
| unary_expression
| collate_expression // "The COLLATE operator has a higher precedence (binds more tightly) than any binary operator and any unary prefix operator..."
| literal_expression
| column_ref_expression
| paren_expression
// "The precedence of the BETWEEN operator is the same as the precedence as operators == and != and LIKE and groups left to right."
private equivalence_group ::= equivalence_expression | between_expression
and_expression ::= expression AND expression
or_expression ::= expression OR expression
equivalence_expression ::= expression ( '==' | '=' | '!=' | '<>' | IS NOT?) expression
comparison_expression ::= expression ( '<' | '<=' | '>' | '>=' ) expression
bit_expression ::= expression ( '<<' | '>>' | '&' | '|' ) expression
add_expression ::= expression ( '+' | '-' ) expression
mul_expression ::= expression ( '*' | '/' | '%' ) expression
concat_expression ::= expression '||' expression
unary_expression ::= ('-' | '+' | '~' | NOT) expression
literal_expression ::= literal_value | bind_parameter
paren_expression ::= '(' expression ( ',' expression )* ')'
collate_expression ::= expression COLLATE collation_name
between_expression ::= expression NOT? BETWEEN expression AND expression
cast_expression ::= CAST '(' expression AS type_name ')'
case_expression ::= CASE expression? ( WHEN expression THEN expression )+ ( ELSE expression )? END
like_expression ::= expression NOT? ( LIKE | GLOB | REGEXP | MATCH ) expression ( ESCAPE expression )?
isnull_expression ::= expression ( ISNULL | NOTNULL | NOT NULL )
in_expression ::= expression ( NOT )? IN ( '(' ( expression_subquery | expression ( ',' expression )* )? ')' | ( database_name '.' )? defined_table_name )
exists_expression ::= ( ( NOT )? EXISTS )? '(' expression_subquery ')'
function_call_expression ::= function_name '(' ( ( DISTINCT )? expression ( ',' expression )* | '*' )? ')'
private expression_subquery ::= &(WITH|SELECT|VALUES) subquery_greedy {pin=1}
column_ref_expression ::=
database_name '.' selected_table_name '.' column_name
| selected_table_name '.' column_name
| column_name
// Some tokens are allowed as function names and remapped to identifiers in code
external function_name ::= parseFunctionName
bind_parameter ::= NUMBERED_PARAMETER | NAMED_PARAMETER {
methods=[
getParameterNameAsString
getReference
isColonNamedParameter
]
}
raise_function_expression ::= RAISE '(' ( IGNORE | ( ROLLBACK | ABORT | FAIL ) ',' error_message ) ')'
error_message ::= string_literal // TODO: check
boolean_literal ::= TRUE | FALSE
private literal_value ::=
NUMERIC_LITERAL
| string_literal // X marks a blob literal
| boolean_literal
| NULL
| CURRENT_TIME
| CURRENT_DATE
| CURRENT_TIMESTAMP
insert_statement ::=
( INSERT ( OR ( REPLACE | ROLLBACK | ABORT | FAIL | IGNORE ))? | REPLACE ) INTO
single_table_statement_table insert_columns?
( select_statement | with_clause_select_statement | DEFAULT VALUES )
insert_columns ::= '(' column_name ( ',' column_name )* ')'
pragma_statement ::= PRAGMA ( database_name '.' )? pragma_name ( '=' pragma_value | '(' pragma_value ')' )?
pragma_name ::= name // TODO: check
// SQLite has special parsing code for pragmas, we try to keep up by listing keywords that happen to be allowed values for certain pragmas.
// See https://sqlite.org/pragma.html
pragma_value ::= signed_number | name | string_literal | boolean_literal | ON | NO | FULL | DELETE | EXCLUSIVE | DEFAULT
reindex_statement ::= REINDEX ( collation_name | ( database_name '.' )? ( defined_table_name | index_name ) )?
select_statement ::= select_core (compound_operator select_core)* order_clause? limit_clause?
limit_clause ::= LIMIT expression ( ( OFFSET | ',' ) expression )?
order_clause ::= ORDER BY ordering_term ( ',' ordering_term )*
select_core ::= select_core_select | select_core_values
select_core_select ::= SELECT ( DISTINCT | ALL )? result_columns from_clause? where_clause? group_by_clause?
group_by_clause ::= GROUP BY expression ( ',' expression )* ( HAVING expression )?
where_clause ::= WHERE expression
from_clause ::= FROM table_or_subquery ( join_operator table_or_subquery join_constraint? )*
result_columns ::= result_column ( ',' result_column )* {
implements="com.android.tools.idea.lang.androidSql.psi.AndroidSqlTableElement"
methods=[getSqlTable]
}
select_core_values ::= VALUES '(' expression ( ',' expression )* ')' ( ',' '(' expression ( ',' expression )* ')' )*
table_or_subquery ::= from_table | select_subquery | '(' table_or_subquery ')'
from_table ::= ( database_name '.' )? defined_table_name ( ( AS )? table_alias_name )? ( INDEXED BY index_name | NOT INDEXED )? {
implements="com.android.tools.idea.lang.androidSql.psi.AndroidSqlTableElement"
methods=[getSqlTable]
}
select_subquery ::= '(' &(SELECT|VALUES|WITH) subquery_greedy ')' ( ( AS )? table_alias_name )? {
implements="com.android.tools.idea.lang.androidSql.psi.AndroidSqlTableElement"
methods=[getSqlTable]
pin=2
}
table_alias_name ::= name {
implements="com.intellij.psi.PsiNamedElement"
methods=[getName setName]
}
result_column ::=
'*'
| selected_table_name '.' '*'
| expression ( ( AS )? column_alias_name )?
column_alias_name ::= name {
implements="com.intellij.psi.PsiNamedElement"
methods=[getName setName]
}
join_operator ::= ',' | ( NATURAL )? ( LEFT ( OUTER )? | INNER | CROSS )? JOIN
join_constraint ::= ON expression | USING '(' column_name ( ',' column_name )* ')'
ordering_term ::= expression ( COLLATE collation_name )? ( ASC | DESC )?
compound_operator ::= UNION ALL? | INTERSECT | EXCEPT
update_statement ::=
UPDATE ( OR ROLLBACK | OR ABORT | OR REPLACE | OR FAIL | OR IGNORE )? single_table_statement_table ( INDEXED BY index_name | NOT INDEXED )?
SET column_name '=' expression ( ',' column_name '=' expression )*
where_clause? { pin=1 }
single_table_statement_table ::= ( database_name '.' )? defined_table_name {
implements="com.android.tools.idea.lang.androidSql.psi.AndroidSqlTableElement"
methods=[getSqlTable]
}
vacuum_statement ::= VACUUM
private name ::= IDENTIFIER | BRACKET_LITERAL | BACKTICK_LITERAL | string_literal
private string_literal ::= SINGLE_QUOTE_STRING_LITERAL | DOUBLE_QUOTE_STRING_LITERAL