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 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)
28 %token <string> IDENT TEXT BLOB
30 %token <Sql.param_id> PARAM
31 %token <int> LCURLY RCURLY
32 %token LPAREN RPAREN COMMA EOF DOT NULL
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
59 (* FIXME precedence of COMMA and JOIN *)
61 (* https://dev.mysql.com/doc/refman/8.0/en/operator-precedence.html *)
67 %nonassoc BETWEEN CASE (* WHEN THEN ELSE *) (* never useful *)
68 %nonassoc EQUAL NUM_EQ_OP NOT_DISTINCT_OP IS LIKE LIKE_OP IN
74 %left ASTERISK NUM_DIV_OP MOD DIV
76 %nonassoc UNARY_MINUS TILDE
78 (* Warning: the precedence level assigned to BINARY is never useful. *)
79 (* %nonassoc BINARY COLLATE *)
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
97 Create (name,`Schema schema)
99 | CREATE either(TABLE,VIEW) name=table_name AS select=maybe_parenth(select_stmt)
101 Create (name,`Select select)
103 | ALTER TABLE name=table_name actions=commas(alter_action)
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
112 | CREATE UNIQUE? INDEX if_not_exists? name=IDENT ON table=table_name cols=sequence(index_column)
114 CreateIndex (name, table, cols)
116 | select_stmt { Select $1 }
117 | insert_cmd target=table_name names=sequence(IDENT)? VALUES values=commas(sequence(insert_expr))? ss=on_duplicate?
119 Insert { target; action=`Values (names, values); on_duplicate=ss; }
121 | insert_cmd target=table_name names=sequence(IDENT)? VALUES p=PARAM ss=on_duplicate?
123 Insert { target; action=`Param (names, p); on_duplicate=ss; }
125 | insert_cmd target=table_name names=sequence(IDENT)? select=maybe_parenth(select_stmt) ss=on_duplicate?
127 Insert { target; action=`Select (names, select); on_duplicate=ss; }
129 | insert_cmd target=table_name SET set=commas(set_column)? ss=on_duplicate?
131 Insert { target; action=`Set set; on_duplicate=ss; }
133 | update_cmd table=table_name SET ss=commas(set_column) w=where? o=loption(order) lim=loption(limit)
135 Update (table,ss,w,o,lim)
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?
140 UpdateMulti (tables,ss,w)
142 | DELETE FROM table=table_name w=where?
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?
149 DeleteMulti (targets, tables, w)
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)
162 Function.add (List.length params) (Ret ret) name;
163 CreateRoutine (name, Some ret, params)
165 | CREATE or_replace? PROCEDURE name=IDENT params=sequence(proc_parameter)
170 Function.add (List.length params) (Ret Any) name; (* FIXME void *)
171 CreateRoutine (name, None, params)
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 { }
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?
206 { select = ($1, other); order=o; limit=lim; }
209 select_core: SELECT select_type? r=commas(column1) f=from? w=where? g=loption(group) h=having?
211 { columns=r; from=f; where=w; group=g; having=h; }
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 }
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 { }
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 }
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 }
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 }
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 }
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 *)
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*
321 | CHECK LPAREN expr RPAREN { [] }
323 reference_action_clause:
324 ON either(DELETE, UPDATE) 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 }
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 }
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?
371 | Some esc -> Fun ((fixed Bool [Bool; Text]), [e;esc])
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
386 let e = poly Bool [ e1; Inparam (new_param p Any) ] in
387 InChoice ({ label = p.label; pos = ($startofs, $endofs) }, k, e )
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 *)
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)
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)
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 *)
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 { }
435 | UNBOUNDED PRECEDING
436 | UNBOUNDED 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 }
451 | TEXT collate? { Value Text }
452 | BLOB collate? { Value Blob }
453 | INTEGER { Value Int }
454 | FLOAT { Value Float }
456 | FALSE { Value Bool }
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 }
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 { }
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 }
489 | NATIONAL? text VARYING? charset? collate? { Text }
490 | ENUM sequence(TEXT) charset? collate? { Text }
491 | T_FLOAT PRECISION? { Float }
493 | T_DATETIME | YEAR | DATE | TIME | TIMESTAMP { Datetime }
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 }
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
513 compound_op: UNION ALL? | EXCEPT | INTERSECT { }
515 maybe_join_type: JOIN_TYPE1? JOIN_TYPE2? { }