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