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)
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)
25 %token <string> IDENT TEXT BLOB
27 %token <Sql.param_id> PARAM
28 %token <int> LCURLY RCURLY
29 %token LPAREN RPAREN COMMA EOF DOT NULL
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
54 (* FIXME precedence of COMMA and JOIN *)
56 (* https://dev.mysql.com/doc/refman/8.0/en/operator-precedence.html *)
62 %nonassoc BETWEEN CASE (* WHEN THEN ELSE *) (* never useful *)
63 %nonassoc EQUAL NUM_EQ_OP NOT_DISTINCT_OP IS LIKE LIKE_OP IN
69 %left ASTERISK NUM_DIV_OP MOD DIV
71 %nonassoc UNARY_MINUS TILDE
73 (* Warning: the precedence level assigned to BINARY is never useful. *)
74 (* %nonassoc BINARY COLLATE *)
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
91 Create (name,`Schema schema)
93 | CREATE either(TABLE,VIEW) name=table_name AS select=maybe_parenth(select_stmt)
95 Create (name,`Select select)
97 | ALTER TABLE name=table_name actions=commas(alter_action)
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
106 | CREATE UNIQUE? INDEX if_not_exists? name=IDENT ON table=table_name cols=sequence(index_column)
108 CreateIndex (name, table, cols)
110 | select_stmt { Select $1 }
111 | insert_cmd target=table_name names=sequence(IDENT)? VALUES values=commas(sequence(insert_expr))? ss=on_duplicate?
113 Insert { target; action=`Values (names, values); on_duplicate=ss; }
115 | insert_cmd target=table_name names=sequence(IDENT)? select=maybe_parenth(select_stmt) ss=on_duplicate?
117 Insert { target; action=`Select (names, select); on_duplicate=ss; }
119 | insert_cmd target=table_name SET set=commas(set_column)? ss=on_duplicate?
121 Insert { target; action=`Set set; on_duplicate=ss; }
123 | update_cmd table=table_name SET ss=commas(set_column) w=where? o=loption(order) lim=loption(limit)
125 Update (table,ss,w,o,lim)
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?
130 UpdateMulti (tables,ss,w)
132 | DELETE FROM table=table_name w=where?
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?
139 DeleteMulti (targets, tables, w)
141 | SET name=IDENT EQUAL e=expr
145 | CREATE or_replace? FUNCTION name=IDENT params=sequence(func_parameter)
151 Function.add (List.length params) (Ret ret) name;
152 CreateRoutine (name, Some ret, params)
154 | CREATE or_replace? PROCEDURE name=IDENT params=sequence(proc_parameter)
159 Function.add (List.length params) (Ret Any) name; (* FIXME void *)
160 CreateRoutine (name, None, params)
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 { }
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?
195 { select = ($1, other); order=o; limit=lim; }
198 select_core: SELECT select_type? r=commas(column1) f=from? w=where? g=loption(group) h=having?
200 { columns=r; from=f; where=w; group=g; having=h; }
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 }
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 { }
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 }
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 }
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 }
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 }
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 *)
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*
310 | CHECK LPAREN expr RPAREN { [] }
312 reference_action_clause:
313 ON either(DELETE, UPDATE) 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 }
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 }
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?
359 | Some esc -> Fun ((fixed Bool [Bool; Text]), [e;esc])
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
374 let e = poly Bool [ e1; Inparam (new_param p Any) ] in
375 InChoice ({ label = p.label; pos = ($startofs, $endofs) }, k, e )
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 *)
394 let maybe f = function None -> [] | Some x -> [f x] in
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)
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)
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? *)
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 }
420 | TEXT collate? { Value Text }
421 | BLOB collate? { Value Blob }
422 | INTEGER { Value Int }
423 | FLOAT { Value Float }
425 | FALSE { Value Bool }
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 }
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 { }
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 }
458 | NATIONAL? text VARYING? charset? collate? { Text }
459 | ENUM sequence(TEXT) charset? collate? { Text }
460 | T_FLOAT PRECISION? { Float }
462 | T_DATETIME | YEAR | DATE | TIME | TIMESTAMP { Datetime }
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 }
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
482 compound_op: UNION ALL? | EXCEPT | INTERSECT { }
484 maybe_join_type: JOIN_TYPE1? JOIN_TYPE2? { }