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 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
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=IDENT schema=table_definition
91 Create (name,`Schema schema)
93 | CREATE either(TABLE,VIEW) name=IDENT 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(IDENT,TO,IDENT)) { Rename l }
102 | DROP either(TABLE,VIEW) if_exists? name=IDENT
106 | CREATE UNIQUE? INDEX if_not_exists? name=table_name ON table=table_name cols=sequence(index_column)
108 CreateIndex (name, table, cols)
110 | select_stmt { Select $1 }
111 | insert_cmd target=IDENT names=sequence(IDENT)? VALUES values=commas(sequence(expr))? ss=on_duplicate?
113 Insert { target; action=`Values (names, values); on_duplicate=ss; }
115 | insert_cmd target=IDENT 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=IDENT SET set=commas(set_column)? ss=on_duplicate?
121 Insert { target; action=`Set set; on_duplicate=ss; }
123 | update_cmd table=IDENT 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=IDENT w=where?
136 | SET name=IDENT EQUAL e=expr
140 | CREATE or_replace? FUNCTION name=IDENT params=sequence(func_parameter)
146 Function.add (List.length params) (Ret ret) name;
147 CreateRoutine (name, Some ret, params)
149 | CREATE or_replace? PROCEDURE name=IDENT params=sequence(proc_parameter)
154 Function.add (List.length params) (Ret Any) name; (* FIXME void *)
155 CreateRoutine (name, None, params)
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 { }
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?
189 { select = ($1, other); order=o; limit=lim; }
192 select_core: SELECT select_type? r=commas(column1) f=from? w=where? g=loption(group) h=having?
194 { columns=r; from=f; where=w; group=g; having=h; }
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 }
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 { }
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 }
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 }
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 }
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 }
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 }
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 *)
303 | some_key IDENT? key_arg { [] }
304 | FOREIGN KEY IDENT? sequence(IDENT) REFERENCES IDENT sequence(IDENT)?
305 reference_action_clause*
307 | CHECK LPAREN expr RPAREN { [] }
309 reference_action_clause:
310 ON either(DELETE, UPDATE) 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]) }
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?
342 | Some esc -> Fun ((fixed Bool [Bool; Text]), [e;esc])
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 *)
367 let maybe f = function None -> [] | Some x -> [f x] in
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)
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)
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 }
389 | TEXT collate? { Value Text }
390 | BLOB collate? { Value Blob }
391 | INTEGER { Value Int }
392 | FLOAT { Value Float }
394 | FALSE { Value Bool }
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 }
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 { }
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
424 sql_type_flavor: T_INTEGER UNSIGNED? ZEROFILL? { Int }
425 | T_DECIMAL { Decimal }
427 | NATIONAL? text VARYING? charset? collate? { Text }
428 | ENUM sequence(TEXT) charset? collate? { Text }
429 | T_FLOAT PRECISION? { Float }
431 | T_DATETIME | YEAR | DATE | TIME | TIMESTAMP { Datetime }
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 }
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
451 compound_op: UNION ALL? | EXCEPT | INTERSECT { }
453 maybe_join_type: JOIN_TYPE1? JOIN_TYPE2? { }