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