do not require sql quoting in choice ctor names (close #80)
[sqlgg.git] / lib / sql_parser.mly
blobdb3fec44eabc111d2187c3d5c5a3355094f78c91
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 VALUE REFERENCES CHECK CONSTRAINT IGNORED AFTER INDEX FULLTEXT 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
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=IDENT schema=table_definition
90               {
91                 Create (name,`Schema schema)
92               }
93          | CREATE either(TABLE,VIEW) name=IDENT 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(IDENT,TO,IDENT)) { Rename l }
102          | DROP either(TABLE,VIEW) if_exists? name=IDENT
103               {
104                 Drop name
105               }
106          | CREATE UNIQUE? INDEX if_not_exists? name=table_name ON table=table_name cols=sequence(index_column)
107               {
108                 CreateIndex (name, table, cols)
109               }
110          | select_stmt { Select $1 }
111          | insert_cmd target=IDENT names=sequence(IDENT)? VALUES values=commas(sequence(expr))? ss=on_duplicate?
112               {
113                 Insert { target; action=`Values (names, values); on_duplicate=ss; }
114               }
115          | insert_cmd target=IDENT 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=IDENT SET set=commas(set_column)? ss=on_duplicate?
120               {
121                 Insert { target; action=`Set set; on_duplicate=ss; }
122               }
123          | update_cmd table=IDENT 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=IDENT w=where?
133               {
134                 Delete (table,w)
135               }
136          | SET name=IDENT EQUAL e=expr
137               {
138                 Set (name, e)
139               }
140          | CREATE or_replace? FUNCTION name=IDENT params=sequence(func_parameter)
141            RETURNS ret=sql_type
142            routine_extra?
143            AS? routine_body
144            routine_extra?
145               {
146                 Function.add (List.length params) (Ret ret) name;
147                 CreateRoutine (name, Some ret, params)
148               }
149          | CREATE or_replace? PROCEDURE name=IDENT params=sequence(proc_parameter)
150            routine_extra?
151            AS? routine_body
152            routine_extra?
153               {
154                 Function.add (List.length params) (Ret Any) name; (* FIXME void *)
155                 CreateRoutine (name, None, params)
156               }
158 parameter_default_: DEFAULT | EQUAL { }
159 parameter_default: parameter_default_ e=expr { e }
160 func_parameter: n=IDENT AS? t=sql_type e=parameter_default? { (n,t,e) }
161 parameter_mode: IN | OUT | INOUT { }
162 proc_parameter: parameter_mode? p=func_parameter { p }
164 or_replace: OR REPLACE { }
166 routine_body: TEXT | compound_stmt { }
167 compound_stmt: BEGIN statement+ END { } (* mysql *)
169 routine_extra: LANGUAGE IDENT { }
170              | COMMENT TEXT { }
172 table_name: name=IDENT | IDENT DOT name=IDENT { name } (* FIXME db name *)
173 index_prefix: LPAREN n=INTEGER RPAREN { n }
174 index_column: name=IDENT index_prefix? collate? order_type? { name }
176 table_definition: t=sequence_(column_def1) table_def_done { List.filter_map (function `Attr a -> Some a | `Constraint _ | `Index _ -> None) t }
177                 | LIKE name=maybe_parenth(IDENT) { Tables.get name |> snd } (* mysql *)
179 (* ugly, can you fixme? *)
180 (* ignoring everything after RPAREN (NB one look-ahead token) *)
181 table_def_done: parser_state_ignore RPAREN IGNORED* parser_state_normal { }
183 parser_state_ignore: { Parser_state.mode_ignore () }
184 parser_state_normal: { Parser_state.mode_normal () }
185 parser_state_ident: { Parser_state.mode_ident () }
187 select_stmt: select_core other=list(preceded(compound_op,select_core)) o=loption(order) lim=limit_t? select_row_locking?
188               {
189                 { select = ($1, other); order=o; limit=lim; }
190               }
192 select_core: SELECT select_type? r=commas(column1) f=from?  w=where?  g=loption(group) h=having?
193               {
194                 { columns=r; from=f; where=w; group=g; having=h; }
195               }
197 table_list: src=source joins=join_source* { (src,joins) }
199 join_source: NATURAL maybe_join_type JOIN src=source { src,`Natural }
200            | CROSS JOIN src=source { src,`Cross }
201            | qualified_join src=source cond=join_cond { src,cond }
203 qualified_join: COMMA | maybe_join_type JOIN { }
205 join_cond: ON e=expr { `Search e }
206          | USING l=sequence(IDENT) { `Using l }
207          | (* *) { `Default }
209 source1: IDENT { `Table $1 }
210        | LPAREN s=select_stmt RPAREN { `Select s }
211        | LPAREN s=table_list RPAREN { `Nested s }
213 source: src=source1 alias=maybe_as { src, alias }
215 insert_cmd: INSERT DELAYED? OR? conflict_algo INTO | INSERT INTO | REPLACE INTO { }
216 update_cmd: UPDATE | UPDATE OR conflict_algo { }
217 conflict_algo: CONFLICT_ALGO | REPLACE { }
218 on_duplicate: ON DUPLICATE KEY UPDATE ss=commas(set_column) { ss }
220 select_type: DISTINCT | ALL { }
222 select_row_locking:
223     for_update_or_share+
224       { }
225   | LOCK IN SHARE MODE
226       { }
228 for_update_or_share:
229   FOR either(UPDATE, SHARE) update_or_share_of? NOWAIT? with_lock? { }
231 update_or_share_of: OF commas(IDENT) { }
233 with_lock: WITH LOCK { }
235 int_or_param: i=INTEGER { `Const i }
236             | p=PARAM { `Param p }
238 limit_t: LIMIT lim=int_or_param { make_limit [`Limit,lim] }
239        | LIMIT ofs=int_or_param COMMA lim=int_or_param { make_limit [`Offset,ofs; `Limit,lim] }
240        | LIMIT lim=int_or_param OFFSET ofs=int_or_param { make_limit [`Limit,lim; `Offset,ofs] }
242 limit: limit_t { fst $1 }
244 order: ORDER BY l=commas(pair(expr,order_type?)) { l }
245 order_type:
246           | DESC | ASC { `Fixed }
247           | PARAM { `Param $1 }
249 from: FROM t=table_list { t }
250 where: WHERE e=expr { e }
251 group: GROUP BY l=expr_list { l }
252 having: HAVING e=expr { e }
254 column1:
255        | IDENT DOT ASTERISK { Sql.AllOf $1 }
256        | ASTERISK { Sql.All }
257        | e=expr m=maybe_as { Sql.Expr (e,m) }
259 maybe_as: AS? name=IDENT { Some name }
260         | { None }
262 maybe_parenth(X): x=X | LPAREN x=X RPAREN { x }
264 alter_action: ADD COLUMN? col=maybe_parenth(column_def) pos=alter_pos { `Add (col,pos) }
265             | ADD index_type IDENT? sequence(IDENT) { `None }
266             | RENAME either(TO,AS)? new_name=IDENT { `RenameTable new_name }
267             | RENAME COLUMN old_name=IDENT TO new_name=IDENT { `RenameColumn (old_name, new_name) }
268             | RENAME either(INDEX,KEY) old_name=IDENT TO new_name=IDENT { `RenameIndex (old_name, new_name) }
269             | DROP INDEX IDENT { `None }
270             | DROP PRIMARY KEY { `None }
271             | DROP COLUMN? col=IDENT drop_behavior? { `Drop col } (* FIXME behavior? *)
272             | CHANGE COLUMN? old_name=IDENT column=column_def pos=alter_pos { `Change (old_name,column,pos) }
273             | MODIFY COLUMN? column=column_def pos=alter_pos { `Change (column.name,column,pos) }
274             | SET IDENT IDENT { `None }
275 index_or_key: INDEX | KEY { }
276 index_type: index_or_key | UNIQUE index_or_key? | FULLTEXT index_or_key? | PRIMARY KEY { }
277 alter_pos: AFTER col=IDENT { `After col }
278          | FIRST { `First }
279          | { `Default }
280 drop_behavior: CASCADE | RESTRICT { }
282 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) }
284 column_def1: c=column_def { `Attr c }
285            | pair(CONSTRAINT,IDENT)? c=table_constraint_1 { `Constraint c }
286            | INDEX cols=sequence(index_column) { `Index cols }
288 on_conflict: ON CONFLICT algo=conflict_algo { algo }
289 column_def_extra: PRIMARY KEY { Some PrimaryKey }
290                 | NOT NULL { Some NotNull }
291                 | NULL { Some Null }
292                 | UNIQUE { Some Unique }
293                 | AUTOINCREMENT { Some Autoincrement }
294                 | on_conflict { None }
295                 | CHECK LPAREN expr RPAREN { None }
296                 | DEFAULT default_value { None } (* FIXME check type with column *)
297                 | COLLATE IDENT { None }
299 default_value: single_literal_value | datetime_value { } (* sub expr ? *)
301 (* FIXME check columns *)
302 table_constraint_1:
303       | some_key IDENT? key_arg { [] }
304       | FOREIGN KEY IDENT? sequence(IDENT) REFERENCES IDENT sequence(IDENT)?
305         reference_action_clause*
306           { [] }
307       | CHECK LPAREN expr RPAREN { [] }
309 reference_action_clause:
310   ON either(DELETE, UPDATE) reference_action { }
312 reference_action:
313   RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT { }
315 some_key: UNIQUE KEY? | PRIMARY? KEY | FULLTEXT KEY { }
316 key_arg: LPAREN VALUE RPAREN | sequence(IDENT) { }
318 set_column: name=attr_name EQUAL e=expr { name,e }
320 anyall: ANY | ALL | SOME { }
322 mnot(X): NOT x = X | x = X { x }
324 attr_name: cname=IDENT { { cname; tname=None} }
325          | table=IDENT DOT cname=IDENT
326          | IDENT DOT table=IDENT DOT cname=IDENT { {cname; tname=Some table} } (* FIXME database identifier *)
328 distinct_from: DISTINCT FROM { }
330 like_expr: e1=expr mnot(like) e2=expr %prec LIKE { Fun ((fixed Bool [Text; Text]), [e1;e2]) }
332 expr:
333       expr numeric_bin_op expr %prec PLUS { Fun ((Ret Any),[$1;$3]) } (* TODO default Int *)
334     | expr DIV expr %prec PLUS { Fun ((Ret Int),[$1;$3]) }
335     | expr boolean_bin_op expr %prec AND { Fun ((fixed Bool [Bool;Bool]),[$1;$3]) }
336     | e1=expr comparison_op anyall? e2=expr %prec EQUAL { poly Bool [e1;e2] }
337     | expr CONCAT_OP expr { Fun ((fixed Text [Text;Text]),[$1;$3]) }
338     | e=like_expr esc=escape?
339       {
340         match esc with
341         | None -> e
342         | Some esc -> Fun ((fixed Bool [Bool; Text]), [e;esc])
343       }
344     | unary_op expr { $2 }
345     | MINUS expr %prec UNARY_MINUS { $2 }
346     | INTERVAL expr interval_unit { Fun (fixed Datetime [Int], [$2]) }
347     | LPAREN expr RPAREN { $2 }
348     | attr_name collate? { Column $1 }
349     | VALUES LPAREN n=IDENT RPAREN { Inserted n }
350     | v=literal_value | v=datetime_value { v }
351     | e1=expr mnot(IN) l=sequence(expr) { poly Bool (e1::l) }
352     | e1=expr mnot(IN) LPAREN select=select_stmt RPAREN { poly Bool [e1; Select (select, `AsValue)] }
353     | e1=expr IN table=IDENT { Tables.check table; e1 }
354     | LPAREN select=select_stmt RPAREN { Select (select, `AsValue) }
355     | PARAM { Param (new_param $1 Any) }
356     | p=PARAM parser_state_ident LCURLY l=choices c2=RCURLY { let { label; pos=(p1,_p2) } = p in Choices ({ label; pos = (p1,c2+1)},l) }
357     | SUBSTRING LPAREN s=expr FROM p=expr FOR n=expr RPAREN
358     | SUBSTRING LPAREN s=expr COMMA p=expr COMMA n=expr RPAREN { Fun (Function.lookup "substring" 3, [s;p;n]) }
359     | SUBSTRING LPAREN s=expr either(FROM,COMMA) p=expr RPAREN { Fun (Function.lookup "substring" 2, [s;p]) }
360     | f=IDENT LPAREN p=func_params RPAREN { Fun (Function.lookup f (List.length p), p) }
361     | expr IS NOT? NULL { Fun (Ret Bool, [$1]) }
362     | e1=expr IS NOT? distinct_from? e2=expr { poly Bool [e1;e2] }
363     | expr mnot(BETWEEN) expr AND expr { poly Bool [$1;$3;$5] }
364     | mnot(EXISTS) LPAREN select=select_stmt RPAREN { Fun (F (Typ  Bool, [Typ Any]),[Select (select,`Exists)]) }
365     | CASE e1=expr? branches=nonempty_list(case_branch) e2=preceded(ELSE,expr)? END (* FIXME typing *)
366       {
367         let maybe f = function None -> [] | Some x -> [f x] in
368         let t_args =
369           match e1 with
370           | None -> (List.flatten @@ List.map (fun _ -> [Typ Bool; Var 1]) branches)
371           | Some _ -> [Var 0] @ (List.flatten @@ List.map (fun _ -> [Var 0; Var 1]) branches)
372         in
373         let t_args = t_args @ maybe (fun _ -> Var 1) e2 in
374         let v_args = maybe Prelude.identity e1 @ List.flatten branches @ maybe Prelude.identity e2 in
375         Fun (F (Var 1, t_args), v_args)
376       }
377     | IF LPAREN e1=expr COMMA e2=expr COMMA e3=expr RPAREN { Fun (F (Var 0, [Typ Bool;Var 0;Var 0]), [e1;e2;e3]) }
379 case_branch: WHEN e1=expr THEN e2=expr { [e1;e2] }
380 like: LIKE | LIKE_OP { }
382 choice_body: c1=LCURLY e=expr c2=RCURLY { (c1,Some e,c2) }
383 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) }
384 choices: separated_nonempty_list(pair(parser_state_ident,NUM_BIT_OR),choice) { $1 }
386 datetime_value: | DATETIME_FUNC | DATETIME_FUNC LPAREN INTEGER? RPAREN { Value Datetime }
388 literal_value:
389     | TEXT collate? { Value Text }
390     | BLOB collate? { Value Blob }
391     | INTEGER { Value Int }
392     | FLOAT { Value Float }
393     | TRUE
394     | FALSE { Value Bool }
395     | DATE TEXT
396     | TIME TEXT
397     | TIMESTAMP TEXT { Value Datetime }
398     | NULL { Value Any } (* he he *)
400 single_literal_value:
401     | literal_value { $1 }
402     | MINUS INTEGER { Value Int }
403     | MINUS FLOAT { Value Float }
405 expr_list: l=commas(expr) { l }
406 func_params: DISTINCT? l=expr_list { l }
407            | ASTERISK { [] }
408            | (* *) { [] }
409 escape: ESCAPE expr { $2 }
410 numeric_bin_op: PLUS | MINUS | ASTERISK | MOD | NUM_DIV_OP | NUM_BIT_OR | NUM_BIT_AND | NUM_BIT_SHIFT { }
411 comparison_op: EQUAL | NUM_CMP_OP | NUM_EQ_OP | NOT_DISTINCT_OP { }
412 boolean_bin_op: AND | OR | XOR { }
414 unary_op: EXCL { }
415         | TILDE { }
416         | NOT { }
418 interval_unit: MICROSECOND | SECOND | MINUTE | HOUR | DAY | WEEK | MONTH | QUARTER | YEAR
419              | SECOND_MICROSECOND | MINUTE_MICROSECOND | MINUTE_SECOND
420              | HOUR_MICROSECOND | HOUR_SECOND | HOUR_MINUTE
421              | DAY_MICROSECOND | DAY_SECOND | DAY_MINUTE | DAY_HOUR
422              | YEAR_MONTH { }
424 sql_type_flavor: T_INTEGER UNSIGNED? ZEROFILL? { Int }
425                | T_DECIMAL { Decimal }
426                | binary { Blob }
427                | NATIONAL? text VARYING? charset? collate? { Text }
428                | ENUM sequence(TEXT) charset? collate? { Text }
429                | T_FLOAT PRECISION? { Float }
430                | T_BOOLEAN { Bool }
431                | T_DATETIME | YEAR | DATE | TIME | TIMESTAMP { Datetime }
432                | T_UUID { Blob }
434 binary: T_BLOB | BINARY | BINARY VARYING { }
435 text: T_TEXT | T_TEXT LPAREN INTEGER RPAREN | CHARACTER { }
437 %inline either(X,Y): X | Y { }
438 %inline commas(X): l=separated_nonempty_list(COMMA,X) { l }
439 (* (x1,x2,...,xn) *)
440 %inline sequence_(X): LPAREN l=commas(X) { l }
441 %inline sequence(X): l=sequence_(X) RPAREN { l }
443 charset: CHARSET either(IDENT,BINARY) | CHARACTER SET either(IDENT,BINARY) | ASCII | UNICODE { }
444 collate: COLLATE IDENT { }
446 sql_type: t=sql_type_flavor
447         | t=sql_type_flavor LPAREN INTEGER RPAREN UNSIGNED?
448         | t=sql_type_flavor LPAREN INTEGER COMMA INTEGER RPAREN
449         { t }
451 compound_op: UNION ALL? | EXCEPT | INTERSECT { }
453 maybe_join_type: JOIN_TYPE1? JOIN_TYPE2? { }