16 | x, `Param (None,pos) -> Some ((Some (match x with `Limit -> "limit" | `Offset -> "offset"),pos),Int)
17 | _, `Param p -> Some (p,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
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: 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?
186 { select = ($1, other); order=o; limit=lim; }
189 select_core: SELECT select_type? r=commas(column1) f=from? w=where? g=loption(group) h=having?
191 { columns=r; from=f; where=w; group=g; having=h; }
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 }
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 { }
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 }
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 }
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 }
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 }
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 *)
299 | some_key IDENT? key_arg { [] }
300 | FOREIGN KEY IDENT? sequence(IDENT) REFERENCES IDENT sequence(IDENT)?
301 reference_action_clause*
303 | CHECK LPAREN expr RPAREN { [] }
305 reference_action_clause:
306 ON either(DELETE, UPDATE) 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]) }
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?
338 | Some esc -> Fun ((fixed Bool [Bool; Text]), [e;esc])
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 *)
363 let maybe f = function None -> [] | Some x -> [f x] in
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)
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)
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 }
385 | TEXT collate? { Value Text }
386 | BLOB collate? { Value Blob }
387 | INTEGER { Value Int }
388 | FLOAT { Value Float }
390 | FALSE { Value Bool }
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 }
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 { }
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
420 sql_type_flavor: T_INTEGER UNSIGNED? ZEROFILL? { Int }
422 | NATIONAL? text VARYING? charset? collate? { Text }
423 | ENUM sequence(TEXT) charset? collate? { Text }
424 | T_FLOAT PRECISION? { Float }
426 | T_DATETIME | YEAR | DATE | TIME | TIMESTAMP { Datetime }
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 }
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
446 compound_op: UNION ALL? | EXCEPT | INTERSECT { }
448 maybe_join_type: JOIN_TYPE1? JOIN_TYPE2? { }