collect params of subqueries
[sqlgg.git] / lib / sql_parser.mly
blob41a14bfef2f9aa25c81841d395a324f99b101225
1 /*
2   Simple SQL parser
3 */
6 %{
7   open Sql
8   open Sql.Type
9   open Sql.Constraint
10   open ExtLib
12   (* preserve order *)
13   let make_limit l =
14     let param = function
15       | _, `Const _ -> None
16       | x, `Param { label=None; pos } -> Some (new_param { label = Some (match x with `Limit -> "limit" | `Offset -> "offset"); pos } Int)
17       | _, `Param id -> Some (new_param id Int)
18     in
19     List.filter_map param l, List.mem (`Limit,`Const 1) l
21   let maybe f = function None -> [] | Some x -> [f x]
22   let option_to_list = maybe Prelude.identity
24   let poly ret args = Fun (F (Typ ret, List.map (fun _ -> Var 0) args), args)
27 %token <int> INTEGER
28 %token <string> IDENT TEXT BLOB
29 %token <float> FLOAT
30 %token <Sql.param_id> PARAM
31 %token <int> LCURLY RCURLY
32 %token LPAREN RPAREN COMMA EOF DOT NULL
33 %token CONFLICT_ALGO
34 %token SELECT INSERT OR INTO CREATE UPDATE VIEW TABLE VALUES WHERE ASTERISK DISTINCT ALL ANY SOME
35        LIMIT ORDER BY DESC ASC EQUAL DELETE FROM DEFAULT OFFSET SET JOIN LIKE_OP LIKE
36        EXCL TILDE NOT BETWEEN AND XOR ESCAPE USING UNION EXCEPT INTERSECT AS TO
37        CONCAT_OP JOIN_TYPE1 JOIN_TYPE2 NATURAL CROSS REPLACE IN GROUP HAVING
38        UNIQUE PRIMARY KEY FOREIGN AUTOINCREMENT ON CONFLICT TEMPORARY IF EXISTS
39        PRECISION UNSIGNED ZEROFILL VARYING CHARSET NATIONAL ASCII UNICODE COLLATE BINARY CHARACTER
40        DATETIME_FUNC DATE TIME TIMESTAMP ALTER RENAME ADD COLUMN CASCADE RESTRICT DROP
41        GLOBAL LOCAL REFERENCES CHECK CONSTRAINT IGNORED AFTER INDEX FULLTEXT SPATIAL FIRST
42        CASE WHEN THEN ELSE END CHANGE MODIFY DELAYED ENUM FOR SHARE MODE LOCK
43        OF WITH NOWAIT ACTION NO IS INTERVAL SUBSTRING DIV MOD CONVERT LAG LEAD OVER
44        FIRST_VALUE LAST_VALUE NTH_VALUE PARTITION ROWS RANGE UNBOUNDED PRECEDING FOLLOWING CURRENT ROW
45        CAST GENERATED ALWAYS VIRTUAL STORED
46 %token FUNCTION PROCEDURE LANGUAGE RETURNS OUT INOUT BEGIN COMMENT
47 %token MICROSECOND SECOND MINUTE HOUR DAY WEEK MONTH QUARTER YEAR
48        SECOND_MICROSECOND MINUTE_MICROSECOND MINUTE_SECOND
49        HOUR_MICROSECOND HOUR_SECOND HOUR_MINUTE
50        DAY_MICROSECOND DAY_SECOND DAY_MINUTE DAY_HOUR
51        YEAR_MONTH FALSE TRUE DUPLICATE
52 %token NUM_DIV_OP NUM_EQ_OP NUM_CMP_OP PLUS MINUS NOT_DISTINCT_OP NUM_BIT_SHIFT NUM_BIT_OR NUM_BIT_AND
53 %token T_INTEGER T_BLOB T_TEXT T_FLOAT T_BOOLEAN T_DATETIME T_UUID T_DECIMAL
56 %left COMMA_JOIN
57 %left JOIN_JOIN
59 (* FIXME precedence of COMMA and JOIN *)
61 (* https://dev.mysql.com/doc/refman/8.0/en/operator-precedence.html *)
63 %left OR CONCAT_OP
64 %left XOR
65 %left AND
66 %nonassoc NOT
67 %nonassoc BETWEEN CASE (* WHEN THEN ELSE *) (* never useful *)
68 %nonassoc EQUAL NUM_EQ_OP NOT_DISTINCT_OP IS LIKE LIKE_OP IN
69 %nonassoc NUM_CMP_OP
70 %left NUM_BIT_OR
71 %left NUM_BIT_AND
72 %left NUM_BIT_SHIFT
73 %left PLUS MINUS
74 %left ASTERISK NUM_DIV_OP MOD DIV
75 (* ^ *)
76 %nonassoc UNARY_MINUS TILDE
77 %nonassoc EXCL
78 (* Warning: the precedence level assigned to BINARY is never useful. *)
79 (* %nonassoc BINARY COLLATE *)
80 %nonassoc INTERVAL
82 %type <Sql.expr> expr
84 %start <Sql.stmt> input
88 input: statement EOF { $1 }
90 if_not_exists: IF NOT EXISTS { }
91 if_exists: IF EXISTS {}
92 temporary: either(GLOBAL,LOCAL)? TEMPORARY { }
94 statement: CREATE ioption(temporary) TABLE ioption(if_not_exists) name=table_name schema=table_definition
95               {
96                 Create (name,`Schema schema)
97               }
98          | CREATE either(TABLE,VIEW) name=table_name AS select=maybe_parenth(select_stmt)
99               {
100                 Create (name,`Select select)
101               }
102          | ALTER TABLE name=table_name actions=commas(alter_action)
103               {
104                 Alter (name,actions)
105               }
106          | RENAME TABLE l=separated_nonempty_list(COMMA, separated_pair(table_name,TO,table_name)) { Rename l }
107          | DROP either(TABLE,VIEW) if_exists? name=table_name
108               {
109                 Drop name
110               }
111          | CREATE UNIQUE? INDEX if_not_exists? name=IDENT ON table=table_name cols=sequence(index_column)
112               {
113                 CreateIndex (name, table, cols)
114               }
115          | select_stmt { Select $1 }
116          | insert_cmd target=table_name names=sequence(IDENT)? VALUES values=commas(sequence(insert_expr))? ss=on_duplicate?
117               {
118                 Insert { target; action=`Values (names, values); on_duplicate=ss; }
119               }
120          | insert_cmd target=table_name names=sequence(IDENT)? VALUES p=PARAM ss=on_duplicate?
121               {
122                 Insert { target; action=`Param (names, p); on_duplicate=ss; }
123               }
124          | insert_cmd target=table_name names=sequence(IDENT)? select=maybe_parenth(select_stmt) ss=on_duplicate?
125               {
126                 Insert { target; action=`Select (names, select); on_duplicate=ss; }
127               }
128          | insert_cmd target=table_name SET set=commas(set_column)? ss=on_duplicate?
129               {
130                 Insert { target; action=`Set set; on_duplicate=ss; }
131               }
132          | update_cmd table=table_name SET ss=commas(set_column) w=where? o=loption(order) lim=loption(limit)
133               {
134                 Update (table,ss,w,o,lim)
135               }
136          /* http://dev.mysql.com/doc/refman/5.1/en/update.html multi-table syntax */
137          | update_cmd tables=commas(source) SET ss=commas(set_column) w=where?
138               {
139                 UpdateMulti (tables,ss,w)
140               }
141          | DELETE FROM table=table_name w=where?
142               {
143                 Delete (table,w)
144               }
145          /* https://dev.mysql.com/doc/refman/5.7/en/delete.html multi-table syntax */
146          | DELETE targets=commas(table_name) FROM tables=table_list w=where?
147               {
148                 DeleteMulti (targets, tables, w)
149               }
150          | SET name=IDENT EQUAL e=expr
151               {
152                 Set (name, e)
153               }
154          | CREATE or_replace? FUNCTION name=IDENT params=sequence(func_parameter)
155            RETURNS ret=sql_type
156            routine_extra?
157            AS? routine_body
158            routine_extra?
159               {
160                 Function.add (List.length params) (Ret ret) name;
161                 CreateRoutine (name, Some ret, params)
162               }
163          | CREATE or_replace? PROCEDURE name=IDENT params=sequence(proc_parameter)
164            routine_extra?
165            AS? routine_body
166            routine_extra?
167               {
168                 Function.add (List.length params) (Ret Any) name; (* FIXME void *)
169                 CreateRoutine (name, None, params)
170               }
172 parameter_default_: DEFAULT | EQUAL { }
173 parameter_default: parameter_default_ e=expr { e }
174 func_parameter: n=IDENT AS? t=sql_type e=parameter_default? { (n,t,e) }
175 parameter_mode: IN | OUT | INOUT { }
176 proc_parameter: parameter_mode? p=func_parameter { p }
178 or_replace: OR REPLACE { }
180 routine_body: TEXT | compound_stmt { }
181 compound_stmt: BEGIN statement+ END { } (* mysql *)
183 routine_extra: LANGUAGE IDENT { }
184              | COMMENT TEXT { }
186 %inline table_name: name=IDENT { Sql.make_table_name name }
187                   | db=IDENT DOT name=IDENT { Sql.make_table_name ~db name }
188 index_prefix: LPAREN n=INTEGER RPAREN { n }
189 index_column: name=IDENT index_prefix? collate? order_type? { name }
191 table_definition: t=sequence_(column_def1) table_def_done { List.filter_map (function `Attr a -> Some a | `Constraint _ | `Index _ -> None) t }
192                 | LIKE name=maybe_parenth(table_name) { Tables.get name |> snd } (* mysql *)
194 (* ugly, can you fixme? *)
195 (* ignoring everything after RPAREN (NB one look-ahead token) *)
196 table_def_done: parser_state_ignore RPAREN IGNORED* parser_state_normal { }
198 parser_state_ignore: { Parser_state.mode_ignore () }
199 parser_state_normal: { Parser_state.mode_normal () }
200 parser_state_ident: { Parser_state.mode_ident () }
202 select_stmt: select_core other=list(preceded(compound_op,select_core)) o=loption(order) lim=limit_t? select_row_locking?
203               {
204                 { select = ($1, other); order=o; limit=lim; }
205               }
207 select_core: SELECT select_type? r=commas(column1) f=from?  w=where?  g=loption(group) h=having?
208               {
209                 { columns=r; from=f; where=w; group=g; having=h; }
210               }
212 table_list: src=source joins=join_source* { (src,joins) }
214 join_source: NATURAL maybe_join_type JOIN src=source { src,`Natural }
215            | CROSS JOIN src=source { src,`Cross }
216            | qualified_join src=source cond=join_cond { src,cond }
218 qualified_join: COMMA | maybe_join_type JOIN { }
220 join_cond: ON e=expr { `Search e }
221          | USING l=sequence(IDENT) { `Using l }
222          | (* *) { `Default }
224 source1: table_name { `Table $1 }
225        | LPAREN s=select_stmt RPAREN { `Select s }
226        | LPAREN s=table_list RPAREN { `Nested s }
228 source: src=source1 alias=maybe_as { src, Option.map Sql.make_table_name alias }
230 insert_cmd: INSERT DELAYED? OR? conflict_algo INTO | INSERT INTO | REPLACE INTO { }
231 update_cmd: UPDATE | UPDATE OR conflict_algo { }
232 conflict_algo: CONFLICT_ALGO | REPLACE { }
233 on_duplicate: ON DUPLICATE KEY UPDATE ss=commas(set_column) { ss }
235 select_type: DISTINCT | ALL { }
237 select_row_locking:
238     for_update_or_share+
239       { }
240   | LOCK IN SHARE MODE
241       { }
243 for_update_or_share:
244   FOR either(UPDATE, SHARE) update_or_share_of? NOWAIT? with_lock? { }
246 update_or_share_of: OF commas(IDENT) { }
248 with_lock: WITH LOCK { }
250 int_or_param: i=INTEGER { `Const i }
251             | p=PARAM { `Param p }
253 limit_t: LIMIT lim=int_or_param { make_limit [`Limit,lim] }
254        | LIMIT ofs=int_or_param COMMA lim=int_or_param { make_limit [`Offset,ofs; `Limit,lim] }
255        | LIMIT lim=int_or_param OFFSET ofs=int_or_param { make_limit [`Limit,lim; `Offset,ofs] }
257 limit: limit_t { fst $1 }
259 order: ORDER BY l=commas(pair(expr,order_type?)) { l }
260 order_type:
261           | DESC | ASC { `Fixed }
262           | PARAM { `Param $1 }
264 from: FROM t=table_list { t }
265 where: WHERE e=expr { e }
266 group: GROUP BY l=expr_list { l }
267 having: HAVING e=expr { e }
269 column1:
270        | table_name DOT ASTERISK { Sql.AllOf $1 }
271        | ASTERISK { Sql.All }
272        | e=expr m=maybe_as { Sql.Expr (e,m) }
274 maybe_as: AS? name=IDENT { Some name }
275         | { None }
277 maybe_parenth(X): x=X | LPAREN x=X RPAREN { x }
279 alter_action: ADD COLUMN? col=maybe_parenth(column_def) pos=alter_pos { `Add (col,pos) }
280             | ADD index_type IDENT? sequence(IDENT) { `None }
281             | ADD pair(CONSTRAINT,IDENT?)? table_constraint_1 index_options { `None }
282             | RENAME either(TO,AS)? new_name=table_name { `RenameTable new_name }
283             | RENAME COLUMN old_name=IDENT TO new_name=IDENT { `RenameColumn (old_name, new_name) }
284             | RENAME index_or_key old_name=IDENT TO new_name=IDENT { `RenameIndex (old_name, new_name) }
285             | DROP INDEX IDENT { `None }
286             | DROP PRIMARY KEY { `None }
287             | DROP COLUMN? col=IDENT drop_behavior? { `Drop col } (* FIXME behavior? *)
288             | DROP FOREIGN KEY IDENT { `None }
289             | CHANGE COLUMN? old_name=IDENT column=column_def pos=alter_pos { `Change (old_name,column,pos) }
290             | MODIFY COLUMN? column=column_def pos=alter_pos { `Change (column.name,column,pos) }
291             | SET IDENT IDENT { `None }
292             | either(DEFAULT,pair(CONVERT,TO))? charset collate? { `None }
293 index_or_key: INDEX | KEY { }
294 index_type: index_or_key | UNIQUE index_or_key? | either(FULLTEXT,SPATIAL) index_or_key? | PRIMARY KEY { }
295 alter_pos: AFTER col=IDENT { `After col }
296          | FIRST { `First }
297          | { `Default }
298 drop_behavior: CASCADE | RESTRICT { }
300 column_def: name=IDENT t=sql_type? extra=column_def_extra* { make_attribute name (Option.default Int t) (Constraints.of_list @@ List.filter_map identity extra) }
302 column_def1: c=column_def { `Attr c }
303            | pair(CONSTRAINT,IDENT?)? l=table_constraint_1 index_options { `Constraint l }
304            | index_or_key l=table_index { `Index l }
305            | either(FULLTEXT,SPATIAL) index_or_key? l=table_index { `Index l }
307 key_part: n=IDENT delimited(LPAREN,INTEGER,RPAREN)? either(ASC,DESC)? { n }
308 index_options: list(IDENT)? { }
310 table_index: IDENT? l=sequence(key_part) index_options { l }
312 (* FIXME check columns *)
313 table_constraint_1:
314       | PRIMARY KEY l=sequence(key_part) { l }
315       | UNIQUE index_or_key? IDENT? l=sequence(key_part) { l }
316       | FOREIGN KEY IDENT? sequence(IDENT) REFERENCES IDENT sequence(IDENT)?
317         reference_action_clause*
318           { [] }
319       | CHECK LPAREN expr RPAREN { [] }
321 reference_action_clause:
322   ON either(DELETE, UPDATE) reference_action { }
324 reference_action:
325   RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT { }
327 on_conflict: ON CONFLICT algo=conflict_algo { algo }
328 column_def_extra: PRIMARY? KEY { Some PrimaryKey }
329                 | NOT NULL { Some NotNull }
330                 | NULL { Some Null }
331                 | UNIQUE KEY? { Some Unique }
332                 | AUTOINCREMENT { Some Autoincrement }
333                 | on_conflict { None }
334                 | CHECK LPAREN expr RPAREN { None }
335                 | DEFAULT e=default_value { if e = Value Any then Some Null else None } (* FIXME check type with column *)
336                 | COLLATE IDENT { None }
337                 | pair(GENERATED,ALWAYS)? AS LPAREN expr RPAREN either(VIRTUAL,STORED)? { None } (* FIXME params and typing ignored *)
339 default_value: e=single_literal_value | e=datetime_value { e } (* sub expr ? *)
341 set_column: name=attr_name EQUAL e=expr { name,e }
343 anyall: ANY | ALL | SOME { }
345 mnot(X): NOT x = X | x = X { x }
347 attr_name: cname=IDENT { { cname; tname=None} }
348          | table=table_name DOT cname=IDENT { {cname; tname=Some table} } (* FIXME database identifier *)
350 distinct_from: DISTINCT FROM { }
352 like_expr: e1=expr mnot(like) e2=expr %prec LIKE { Fun ((fixed Bool [Text; Text]), [e1;e2]) }
354 insert_expr: e=expr { `Expr e }
355            | DEFAULT { `Default }
357 expr:
358       e1=expr numeric_bin_op e2=expr %prec PLUS { Fun ((Ret Any),[e1;e2]) } (* TODO default Int *)
359     | MOD LPAREN e1=expr COMMA e2=expr RPAREN { Fun ((Ret Any),[e1;e2]) } (* mysql special *)
360     | e1=expr NUM_DIV_OP e2=expr %prec PLUS { Fun ((Ret Float),[e1;e2]) }
361     | e1=expr DIV e2=expr %prec PLUS { Fun ((Ret Int),[e1;e2]) }
362     | e1=expr boolean_bin_op e2=expr %prec AND { Fun ((fixed Bool [Bool;Bool]),[e1;e2]) }
363     | e1=expr comparison_op anyall? e2=expr %prec EQUAL { poly Bool [e1;e2] }
364     | e1=expr CONCAT_OP e2=expr { Fun ((fixed Text [Text;Text]),[e1;e2]) }
365     | e=like_expr esc=escape?
366       {
367         match esc with
368         | None -> e
369         | Some esc -> Fun ((fixed Bool [Bool; Text]), [e;esc])
370       }
371     | unary_op e=expr { e }
372     | MINUS e=expr %prec UNARY_MINUS { e }
373     | INTERVAL e=expr interval_unit { Fun (fixed Datetime [Int], [e]) }
374     | LPAREN e=expr RPAREN { e }
375     | a=attr_name collate? { Column a }
376     | VALUES LPAREN n=IDENT RPAREN { Inserted n }
377     | v=literal_value | v=datetime_value { v }
378     | v=interval_unit { v }
379     | e1=expr mnot(IN) l=sequence(expr) { poly Bool (e1::l) }
380     | e1=expr mnot(IN) LPAREN select=select_stmt RPAREN { poly Bool [e1; SelectExpr (select, `AsValue)] }
381     | e1=expr IN table=table_name { Tables.check table; e1 }
382     | e1=expr k=in_or_not_in p=PARAM
383       {
384         let e = poly Bool [ e1; Inparam (new_param p Any) ] in
385         InChoice ({ label = p.label; pos = ($startofs, $endofs) }, k, e )
386       }
387     | LPAREN select=select_stmt RPAREN { SelectExpr (select, `AsValue) }
388     | p=PARAM { Param (new_param p Any) }
389     | p=PARAM parser_state_ident LCURLY l=choices c2=RCURLY { let { label; pos=(p1,_p2) } = p in Choices ({ label; pos = (p1,c2+1)},l) }
390     | SUBSTRING LPAREN s=expr FROM p=expr FOR n=expr RPAREN
391     | SUBSTRING LPAREN s=expr COMMA p=expr COMMA n=expr RPAREN { Fun (Function.lookup "substring" 3, [s;p;n]) }
392     | SUBSTRING LPAREN s=expr either(FROM,COMMA) p=expr RPAREN { Fun (Function.lookup "substring" 2, [s;p]) }
393     | DATE LPAREN e=expr RPAREN { Fun (Function.lookup "date" 1, [e]) }
394     | TIME LPAREN e=expr RPAREN { Fun (Function.lookup "time" 1, [e]) }
395     | DEFAULT LPAREN a=attr_name RPAREN { Fun (Type.identity, [Column a]) }
396     | CONVERT LPAREN e=expr USING IDENT RPAREN { e }
397     | CONVERT LPAREN e=expr COMMA t=sql_type RPAREN
398     | CAST LPAREN e=expr AS t=sql_type RPAREN { Fun (Ret t, [e]) }
399     | f=IDENT LPAREN p=func_params RPAREN { Fun (Function.lookup f (List.length p), p) }
400     | e=expr IS NOT? NULL { Fun (Ret Bool, [e]) }
401     | e1=expr IS NOT? distinct_from? e2=expr { poly Bool [e1;e2] }
402     | e=expr mnot(BETWEEN) a=expr AND b=expr { poly Bool [e;a;b] }
403     | mnot(EXISTS) LPAREN select=select_stmt RPAREN { Fun (F (Typ  Bool, [Typ Any]),[SelectExpr (select,`Exists)]) }
404     | CASE e1=expr? branches=nonempty_list(case_branch) e2=preceded(ELSE,expr)? END (* FIXME typing *)
405       {
406         let t_args =
407           match e1 with
408           | None -> (List.flatten @@ List.map (fun _ -> [Typ Bool; Var 1]) branches)
409           | Some _ -> [Var 0] @ (List.flatten @@ List.map (fun _ -> [Var 0; Var 1]) branches)
410         in
411         let t_args = t_args @ maybe (fun _ -> Var 1) e2 in
412         let v_args = option_to_list e1 @ List.flatten branches @ option_to_list e2 in
413         Fun (F (Var 1, t_args), v_args)
414       }
415     | IF LPAREN e1=expr COMMA e2=expr COMMA e3=expr RPAREN { Fun (F (Var 0, [Typ Bool;Var 0;Var 0]), [e1;e2;e3]) }
416     | e=window_function OVER window_spec { e }
418 (* https://dev.mysql.com/doc/refman/8.0/en/window-functions-usage.html *)
419 window_function:
420   | either(FIRST_VALUE,LAST_VALUE) LPAREN e=expr RPAREN { e }
421   | NTH_VALUE LPAREN e=expr COMMA INTEGER RPAREN { e }
422   | either(LAG,LEAD) LPAREN e=expr pair(COMMA, pair(MINUS?,INTEGER))? RPAREN { e }
424 window_spec: LPAREN e=partition? order? frame? RPAREN (* TODO order parameters? *) { e }
425 partition: PARTITION BY expr { } (* TODO check no params *)
427 frame: either(ROWS,RANGE) either(frame_border, frame_between) { }
429 frame_between: BETWEEN frame_border AND frame_border { }
431 frame_border:
432   | CURRENT ROW
433   | UNBOUNDED PRECEDING
434   | UNBOUNDED FOLLOWING
435   | expr PRECEDING
436   | expr FOLLOWING { }
438 in_or_not_in: IN { `In } | NOT IN { `NotIn }
439 case_branch: WHEN e1=expr THEN e2=expr { [e1;e2] }
440 like: LIKE | LIKE_OP { }
442 choice_body: c1=LCURLY e=expr c2=RCURLY { (c1,Some e,c2) }
443 choice: parser_state_normal label=IDENT? e=choice_body? { let (c1,e,c2) = Option.default (0,None,0) e in ({ label; pos = (c1+1,c2) },e) }
444 choices: separated_nonempty_list(pair(parser_state_ident,NUM_BIT_OR),choice) { $1 }
446 datetime_value: | DATETIME_FUNC | DATETIME_FUNC LPAREN INTEGER? RPAREN { Value Datetime }
448 literal_value:
449     | TEXT collate? { Value Text }
450     | BLOB collate? { Value Blob }
451     | INTEGER { Value Int }
452     | FLOAT { Value Float }
453     | TRUE
454     | FALSE { Value Bool }
455     | DATE TEXT
456     | TIME TEXT
457     | TIMESTAMP TEXT { Value Datetime }
458     | NULL { Value Any } (* he he *)
460 single_literal_value:
461     | literal_value { $1 }
462     | MINUS INTEGER { Value Int }
463     | MINUS FLOAT { Value Float }
465 expr_list: l=commas(expr) { l }
466 func_params: DISTINCT? l=expr_list { l }
467            | ASTERISK { [] }
468            | (* *) { [] }
469 escape: ESCAPE expr { $2 }
470 numeric_bin_op: PLUS | MINUS | ASTERISK | MOD | NUM_BIT_OR | NUM_BIT_AND | NUM_BIT_SHIFT { }
471 comparison_op: EQUAL | NUM_CMP_OP | NUM_EQ_OP | NOT_DISTINCT_OP { }
472 boolean_bin_op: AND | OR | XOR { }
474 unary_op: EXCL { }
475         | TILDE { }
476         | NOT { }
478 interval_unit: MICROSECOND | SECOND | MINUTE | HOUR | DAY | WEEK | MONTH | QUARTER | YEAR
479              | SECOND_MICROSECOND | MINUTE_MICROSECOND | MINUTE_SECOND
480              | HOUR_MICROSECOND | HOUR_SECOND | HOUR_MINUTE
481              | DAY_MICROSECOND | DAY_SECOND | DAY_MINUTE | DAY_HOUR
482              | YEAR_MONTH { Value (Unit `Interval) }
484 sql_type_flavor: T_INTEGER UNSIGNED? ZEROFILL? { Int }
485                | T_DECIMAL { Decimal }
486                | binary { Blob }
487                | NATIONAL? text VARYING? charset? collate? { Text }
488                | ENUM sequence(TEXT) charset? collate? { Text }
489                | T_FLOAT PRECISION? { Float }
490                | T_BOOLEAN { Bool }
491                | T_DATETIME | YEAR | DATE | TIME | TIMESTAMP { Datetime }
492                | T_UUID { Blob }
494 binary: T_BLOB | BINARY | BINARY VARYING { }
495 text: T_TEXT | T_TEXT LPAREN INTEGER RPAREN | CHARACTER { }
497 %inline either(X,Y): X | Y { }
498 %inline commas(X): l=separated_nonempty_list(COMMA,X) { l }
499 (* (x1,x2,...,xn) *)
500 %inline sequence_(X): LPAREN l=commas(X) { l }
501 %inline sequence(X): l=sequence_(X) RPAREN { l }
503 charset: CHARSET either(IDENT,BINARY) | CHARACTER SET either(IDENT,BINARY) | ASCII | UNICODE { }
504 collate: COLLATE IDENT { }
506 sql_type: t=sql_type_flavor
507         | t=sql_type_flavor LPAREN INTEGER RPAREN UNSIGNED?
508         | t=sql_type_flavor LPAREN INTEGER COMMA INTEGER RPAREN
509         { t }
511 compound_op: UNION ALL? | EXCEPT | INTERSECT { }
513 maybe_join_type: JOIN_TYPE1? JOIN_TYPE2? { }