sql: + DIV MOD (close #72)
[sqlgg.git] / lib / sql_parser.mly
blob22e6bfa200ac022f595b8f0283f43315a936bef1
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 (None,pos) -> Some ((Some (match x with `Limit -> "limit" | `Offset -> "offset"),pos),Int)
17       | _, `Param p -> Some (p,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
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: table_def_done1 RPAREN IGNORED* { Parser_state.mode_normal () }
182 table_def_done1: { Parser_state.mode_ignore () }
184 select_stmt: select_core other=list(preceded(compound_op,select_core)) o=loption(order) lim=limit_t? select_row_locking?
185               {
186                 { select = ($1, other); order=o; limit=lim; }
187               }
189 select_core: SELECT select_type? r=commas(column1) f=from?  w=where?  g=loption(group) h=having?
190               {
191                 { columns=r; from=f; where=w; group=g; having=h; }
192               }
194 table_list: src=source joins=join_source* { (src,joins) }
196 join_source: NATURAL maybe_join_type JOIN src=source { src,`Natural }
197            | CROSS JOIN src=source { src,`Cross }
198            | qualified_join src=source cond=join_cond { src,cond }
200 qualified_join: COMMA | maybe_join_type JOIN { }
202 join_cond: ON e=expr { `Search e }
203          | USING l=sequence(IDENT) { `Using l }
204          | (* *) { `Default }
206 source1: IDENT { `Table $1 }
207        | LPAREN s=select_stmt RPAREN { `Select s }
208        | LPAREN s=table_list RPAREN { `Nested s }
210 source: src=source1 alias=maybe_as { src, alias }
212 insert_cmd: INSERT DELAYED? OR? conflict_algo INTO | INSERT INTO | REPLACE INTO { }
213 update_cmd: UPDATE | UPDATE OR conflict_algo { }
214 conflict_algo: CONFLICT_ALGO | REPLACE { }
215 on_duplicate: ON DUPLICATE KEY UPDATE ss=commas(set_column) { ss }
217 select_type: DISTINCT | ALL { }
219 select_row_locking:
220     for_update_or_share+
221       { }
222   | LOCK IN SHARE MODE
223       { }
225 for_update_or_share:
226   FOR either(UPDATE, SHARE) update_or_share_of? NOWAIT? with_lock? { }
228 update_or_share_of: OF commas(IDENT) { }
230 with_lock: WITH LOCK { }
232 int_or_param: i=INTEGER { `Const i }
233             | p=PARAM { `Param p }
235 limit_t: LIMIT lim=int_or_param { make_limit [`Limit,lim] }
236        | LIMIT ofs=int_or_param COMMA lim=int_or_param { make_limit [`Offset,ofs; `Limit,lim] }
237        | LIMIT lim=int_or_param OFFSET ofs=int_or_param { make_limit [`Limit,lim; `Offset,ofs] }
239 limit: limit_t { fst $1 }
241 order: ORDER BY l=commas(terminated(expr,order_type?)) { l }
242 order_type: DESC | ASC { }
244 from: FROM t=table_list { t }
245 where: WHERE e=expr { e }
246 group: GROUP BY l=expr_list { l }
247 having: HAVING e=expr { e }
249 column1:
250        | IDENT DOT ASTERISK { Sql.AllOf $1 }
251        | ASTERISK { Sql.All }
252        | e=expr m=maybe_as { Sql.Expr (e,m) }
254 maybe_as: AS? name=IDENT { Some name }
255         | { None }
257 maybe_parenth(X): x=X | LPAREN x=X RPAREN { x }
259 alter_action: ADD COLUMN? col=maybe_parenth(column_def) pos=alter_pos { `Add (col,pos) }
260             | ADD index_type IDENT? sequence(IDENT) { `None }
261             | RENAME either(TO,AS)? new_name=IDENT { `RenameTable new_name }
262             | RENAME COLUMN old_name=IDENT TO new_name=IDENT { `RenameColumn (old_name, new_name) }
263             | RENAME either(INDEX,KEY) old_name=IDENT TO new_name=IDENT { `RenameIndex (old_name, new_name) }
264             | DROP INDEX IDENT { `None }
265             | DROP PRIMARY KEY { `None }
266             | DROP COLUMN? col=IDENT drop_behavior? { `Drop col } (* FIXME behavior? *)
267             | CHANGE COLUMN? old_name=IDENT column=column_def pos=alter_pos { `Change (old_name,column,pos) }
268             | MODIFY COLUMN? column=column_def pos=alter_pos { `Change (column.name,column,pos) }
269             | SET IDENT IDENT { `None }
270 index_or_key: INDEX | KEY { }
271 index_type: index_or_key | UNIQUE index_or_key? | FULLTEXT index_or_key? | PRIMARY KEY { }
272 alter_pos: AFTER col=IDENT { `After col }
273          | FIRST { `First }
274          | { `Default }
275 drop_behavior: CASCADE | RESTRICT { }
277 column_def: name=IDENT t=sql_type? column_def_extra*
278     { attr name (match t with Some x -> x | None -> Int) }
280 column_def1: c=column_def { `Attr c }
281            | pair(CONSTRAINT,IDENT)? c=table_constraint_1 { `Constraint c }
282            | INDEX cols=sequence(index_column) { `Index cols }
284 on_conflict: ON CONFLICT algo=conflict_algo { algo }
285 column_def_extra: PRIMARY KEY { Some PrimaryKey }
286                 | NOT NULL { Some NotNull }
287                 | NULL { None }
288                 | UNIQUE { Some Unique }
289                 | AUTOINCREMENT { Some Autoincrement }
290                 | on_conflict { None }
291                 | CHECK LPAREN expr RPAREN { None }
292                 | DEFAULT default_value { None } (* FIXME check type with column *)
293                 | COLLATE IDENT { None }
295 default_value: single_literal_value | datetime_value { } (* sub expr ? *)
297 (* FIXME check columns *)
298 table_constraint_1:
299       | some_key IDENT? key_arg { [] }
300       | FOREIGN KEY IDENT? sequence(IDENT) REFERENCES IDENT sequence(IDENT)?
301         reference_action_clause*
302           { [] }
303       | CHECK LPAREN expr RPAREN { [] }
305 reference_action_clause:
306   ON either(DELETE, UPDATE) reference_action { }
308 reference_action:
309   RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT { }
311 some_key: UNIQUE KEY? | PRIMARY? KEY | FULLTEXT KEY { }
312 key_arg: LPAREN VALUE RPAREN | sequence(IDENT) { }
314 set_column: name=attr_name EQUAL e=expr { name,e }
316 anyall: ANY | ALL | SOME { }
318 mnot(X): NOT x = X | x = X { x }
320 attr_name: cname=IDENT { { cname; tname=None} }
321          | table=IDENT DOT cname=IDENT
322          | IDENT DOT table=IDENT DOT cname=IDENT { {cname; tname=Some table} } (* FIXME database identifier *)
324 distinct_from: DISTINCT FROM { }
326 like_expr: e1=expr mnot(like) e2=expr %prec LIKE { Fun ((fixed Bool [Text; Text]), [e1;e2]) }
328 expr:
329       expr numeric_bin_op expr %prec PLUS { Fun ((Ret Any),[$1;$3]) } (* TODO default Int *)
330     | expr DIV expr %prec PLUS { Fun ((Ret Int),[$1;$3]) }
331     | expr boolean_bin_op expr %prec AND { Fun ((fixed Bool [Bool;Bool]),[$1;$3]) }
332     | e1=expr comparison_op anyall? e2=expr %prec EQUAL { poly Bool [e1;e2] }
333     | expr CONCAT_OP expr { Fun ((fixed Text [Text;Text]),[$1;$3]) }
334     | e=like_expr esc=escape?
335       {
336         match esc with
337         | None -> e
338         | Some esc -> Fun ((fixed Bool [Bool; Text]), [e;esc])
339       }
340     | unary_op expr { $2 }
341     | MINUS expr %prec UNARY_MINUS { $2 }
342     | INTERVAL expr interval_unit { Fun (fixed Datetime [Int], [$2]) }
343     | LPAREN expr RPAREN { $2 }
344     | attr_name collate? { Column $1 }
345     | VALUES LPAREN n=IDENT RPAREN { Inserted n }
346     | v=literal_value | v=datetime_value { v }
347     | e1=expr mnot(IN) l=sequence(expr) { poly Bool (e1::l) }
348     | e1=expr mnot(IN) LPAREN select=select_stmt RPAREN { poly Bool [e1; Select (select, `AsValue)] }
349     | e1=expr IN table=IDENT { Tables.check table; e1 }
350     | LPAREN select=select_stmt RPAREN { Select (select, `AsValue) }
351     | PARAM { Param ($1,Any) }
352     | p=PARAM LCURLY l=choices c2=RCURLY { let (name,(p1,_p2)) = p in Choices ((name,(p1,c2+1)),l) }
353     | SUBSTRING LPAREN s=expr FROM p=expr FOR n=expr RPAREN
354     | SUBSTRING LPAREN s=expr COMMA p=expr COMMA n=expr RPAREN { Fun (Function.lookup "substring" 3, [s;p;n]) }
355     | SUBSTRING LPAREN s=expr either(FROM,COMMA) p=expr RPAREN { Fun (Function.lookup "substring" 2, [s;p]) }
356     | f=IDENT LPAREN p=func_params RPAREN { Fun (Function.lookup f (List.length p), p) }
357     | expr IS NOT? NULL { Fun (Ret Bool, [$1]) }
358     | e1=expr IS NOT? distinct_from? e2=expr { poly Bool [e1;e2] }
359     | expr mnot(BETWEEN) expr AND expr { poly Bool [$1;$3;$5] }
360     | mnot(EXISTS) LPAREN select=select_stmt RPAREN { Fun (F (Typ  Bool, [Typ Any]),[Select (select,`Exists)]) }
361     | CASE e1=expr? branches=nonempty_list(case_branch) e2=preceded(ELSE,expr)? END (* FIXME typing *)
362       {
363         let maybe f = function None -> [] | Some x -> [f x] in
364         let t_args =
365           match e1 with
366           | None -> (List.flatten @@ List.map (fun _ -> [Typ Bool; Var 1]) branches)
367           | Some _ -> [Var 0] @ (List.flatten @@ List.map (fun _ -> [Var 0; Var 1]) branches)
368         in
369         let t_args = t_args @ maybe (fun _ -> Var 1) e2 in
370         let v_args = maybe Prelude.identity e1 @ List.flatten branches @ maybe Prelude.identity e2 in
371         Fun (F (Var 1, t_args), v_args)
372       }
373     | IF LPAREN e1=expr COMMA e2=expr COMMA e3=expr RPAREN { Fun (F (Var 0, [Typ Bool;Var 0;Var 0]), [e1;e2;e3]) }
375 case_branch: WHEN e1=expr THEN e2=expr { [e1;e2] }
376 like: LIKE | LIKE_OP { }
378 choice_body: c1=LCURLY e=expr c2=RCURLY { (c1,Some e,c2) }
379 choice: name=IDENT? e=choice_body? { let (c1,e,c2) = Option.default (0,None,0) e in ((name, (c1+1,c2)),e) }
380 choices: separated_nonempty_list(NUM_BIT_OR,choice) { $1 }
382 datetime_value: | DATETIME_FUNC | DATETIME_FUNC LPAREN INTEGER? RPAREN { Value Datetime }
384 literal_value:
385     | TEXT collate? { Value Text }
386     | BLOB collate? { Value Blob }
387     | INTEGER { Value Int }
388     | FLOAT { Value Float }
389     | TRUE
390     | FALSE { Value Bool }
391     | DATE TEXT
392     | TIME TEXT
393     | TIMESTAMP TEXT { Value Datetime }
394     | NULL { Value Any } (* he he *)
396 single_literal_value:
397     | literal_value { $1 }
398     | MINUS INTEGER { Value Int }
399     | MINUS FLOAT { Value Float }
401 expr_list: l=commas(expr) { l }
402 func_params: DISTINCT? l=expr_list { l }
403            | ASTERISK { [] }
404            | (* *) { [] }
405 escape: ESCAPE expr { $2 }
406 numeric_bin_op: PLUS | MINUS | ASTERISK | MOD | NUM_DIV_OP | NUM_BIT_OR | NUM_BIT_AND | NUM_BIT_SHIFT { }
407 comparison_op: EQUAL | NUM_CMP_OP | NUM_EQ_OP | NOT_DISTINCT_OP { }
408 boolean_bin_op: AND | OR | XOR { }
410 unary_op: EXCL { }
411         | TILDE { }
412         | NOT { }
414 interval_unit: MICROSECOND | SECOND | MINUTE | HOUR | DAY | WEEK | MONTH | QUARTER | YEAR
415              | SECOND_MICROSECOND | MINUTE_MICROSECOND | MINUTE_SECOND
416              | HOUR_MICROSECOND | HOUR_SECOND | HOUR_MINUTE
417              | DAY_MICROSECOND | DAY_SECOND | DAY_MINUTE | DAY_HOUR
418              | YEAR_MONTH { }
420 sql_type_flavor: T_INTEGER UNSIGNED? ZEROFILL? { Int }
421                | binary { Blob }
422                | NATIONAL? text VARYING? charset? collate? { Text }
423                | ENUM sequence(TEXT) charset? collate? { Text }
424                | T_FLOAT PRECISION? { Float }
425                | T_BOOLEAN { Bool }
426                | T_DATETIME | YEAR | DATE | TIME | TIMESTAMP { Datetime }
427                | T_UUID { Blob }
429 binary: T_BLOB | BINARY | BINARY VARYING { }
430 text: T_TEXT | T_TEXT LPAREN INTEGER RPAREN | CHARACTER { }
432 %inline either(X,Y): X | Y { }
433 %inline commas(X): l=separated_nonempty_list(COMMA,X) { l }
434 (* (x1,x2,...,xn) *)
435 %inline sequence_(X): LPAREN l=commas(X) { l }
436 %inline sequence(X): l=sequence_(X) RPAREN { l }
438 charset: CHARSET either(IDENT,BINARY) | CHARACTER SET either(IDENT,BINARY) | ASCII | UNICODE { }
439 collate: COLLATE IDENT { }
441 sql_type: t=sql_type_flavor
442         | t=sql_type_flavor LPAREN INTEGER RPAREN UNSIGNED?
443         | t=sql_type_flavor LPAREN INTEGER COMMA INTEGER RPAREN
444         { t }
446 compound_op: UNION ALL? | EXCEPT | INTERSECT { }
448 maybe_join_type: JOIN_TYPE1? JOIN_TYPE2? { }