fix bug in automatic renaming of unnamed LIMIT OFFSET params
[sqlgg.git] / src / sql_parser.mly
blobbe68240bb0de0b6912a09988693ef82fa2f89ed1
1 /*
2   Simple SQL parser
3 */
6 %{
7   open Printf
8   open Sql.Constraint
9   open Sql.Type
10   open ListMore
11   open Stmt
12   open Syntax
13   open Operators
15   let params_of select = List.map (fun x -> `Param x) (snd select)
17   let select_value select =
18     let (s,_) = select in
19     if (List.length s <> 1) then
20       raise (RA.Schema.Error (s,"only one column allowed for SELECT operator in this expression"));
21     params_of select
23   let values_or_all table names =
24     let schema = Tables.get_schema table in
25     match names with
26     | Some names -> RA.Schema.project names schema
27     | None -> schema
29   let update_tables tables ss w =
30     let (tables,params) = List.split tables in
31     let p1 = Syntax.params_of_assigns tables ss in
32     let p2 = get_params_opt tables (Syntax.all_tbl_columns tables) w in
33     (List.flatten params) @ p1 @ p2
35   (* preserve order *)
36   let limit l =
37     let param = function
38       | _, `Const _ -> None
39       | x, `Param (None,pos) -> Some ((Some (match x with `Limit -> "limit" | `Offset -> "offset"),pos),Int)
40       | _, `Param p -> Some (p,Int)
41     in
42     List.filter_map param l, List.mem (`Limit,`Const 1) l
46 %token <int> INTEGER
47 %token <string> IDENT TEXT BLOB
48 %token <float> FLOAT
49 %token <Stmt.param_id> PARAM
50 %token <Sql.Type.t * bool> FUNCTION
51 %token LPAREN RPAREN COMMA EOF DOT NULL
52 %token CONFLICT_ALGO
53 %token SELECT INSERT OR INTO CREATE UPDATE VIEW TABLE VALUES WHERE ASTERISK DISTINCT ALL ANY SOME
54        LIMIT ORDER BY DESC ASC EQUAL DELETE FROM DEFAULT OFFSET SET JOIN LIKE_OP LIKE
55        EXCL TILDE NOT TEST_NULL BETWEEN AND ESCAPE USING UNION EXCEPT INTERSECT AS
56        CONCAT_OP JOIN_TYPE1 JOIN_TYPE2 NATURAL CROSS REPLACE IN GROUP HAVING
57        UNIQUE PRIMARY KEY FOREIGN AUTOINCREMENT ON CONFLICT TEMPORARY IF EXISTS
58        PRECISION UNSIGNED ZEROFILL VARYING CHARSET NATIONAL ASCII UNICODE COLLATE BINARY CHARACTER
59        DATETIME_FUNC DATE TIME TIMESTAMP ALTER ADD COLUMN CASCADE RESTRICT DROP
60        GLOBAL LOCAL VALUE REFERENCES CHECK CONSTRAINT IGNORED AFTER INDEX FULLTEXT FIRST
61        CASE WHEN THEN ELSE END
62 %token NUM_DIV_OP NUM_BIT_OP NUM_EQ_OP NUM_CMP_OP PLUS MINUS
63 %token T_INTEGER T_BLOB T_TEXT T_FLOAT T_BOOLEAN T_DATETIME
66 %left COMMA_JOIN
67 %left JOIN_JOIN
69 (* FIXME precedence of COMMA and JOIN *)
71 %left TEST_NULL
72 %left OR
73 %left AND
74 %nonassoc EQUAL NUM_EQ_OP
75 %nonassoc NUM_CMP_OP
76 %nonassoc NUM_BIT_OP
77 %left PLUS MINUS
78 %left ASTERISK NUM_DIV_OP
79 %left CONCAT_OP
81 %type <Syntax.expr> expr
83 %start <RA.Schema.t * Stmt.params * Stmt.kind> input
87 input: statement EOF { $1 }
89 if_not_exists: IF NOT EXISTS { }
90 if_exists: IF EXISTS {}
91 temporary: either(GLOBAL,LOCAL)? TEMPORARY { }
93 statement: CREATE ioption(temporary) TABLE ioption(if_not_exists) name=IDENT schema=table_definition
94               {
95                 Tables.add (name,schema);
96                 ([],[],Create name)
97               }
98          | ALTER TABLE name=table_name actions=commas(alter_action)
99               {
100                 List.iter (function
101                 | `Add (col,pos) -> Tables.alter_add name col pos
102                 | `Drop col -> Tables.alter_drop name col
103                 | `None -> ()) actions;
104                 ([],[],Alter name)
105               }
106          | DROP TABLE if_exists? name=IDENT
107               {
108                 Tables.drop name;
109                 ([],[],Drop name)
110               }
111          | CREATE either(TABLE,VIEW) name=IDENT AS select=select_stmt
112               {
113                 let (s,p) = select in
114                 Tables.add (name,s);
115                 ([],p,Create name)
116               }
117          | CREATE UNIQUE? INDEX if_not_exists? name=table_name 
118                 ON table=table_name cols=sequence(index_column)
119               {
120                 RA.Schema.project cols (Tables.get_schema table) >> ignore; (* just check *)
121                 [],[],CreateIndex name
122               }
123          | select_stmt_t { $1 }
124          | insert_cmd table=IDENT names=sequence(IDENT)? VALUES values=sequence(expr)?
125               {
126                 let expect = values_or_all table names in
127                 let params, values = match values with
128                 | None -> [], Some expect
129                 | Some values ->
130                   let vl = List.length values in
131                   let cl = List.length expect in
132                   if vl <> cl then
133                     failwith (sprintf "Expected %u expressions in VALUES list, %u provided" cl vl);
134                   Syntax.params_of_assigns [Tables.get table] (List.combine (List.map (fun a -> a.RA.name, None) expect) values), None
135                 in
136                 [], params, Insert (values,table)
137               }
138          | insert_cmd table=IDENT names=sequence(IDENT)? select=maybe_parenth(select_stmt)
139               {
140                 let (schema,params) = select in
141                 let expect = values_or_all table names in
142                 ignore (RA.Schema.compound expect schema); (* test equal types *)
143                 [], params, Insert(None,table)
144               }
145          | insert_cmd table=IDENT SET ss=commas(set_column)
146               {
147                 let p1 = Syntax.params_of_assigns [Tables.get table] ss in
148                 [], p1, Insert (None,table)
149               }
150          | update_cmd table=IDENT SET ss=commas(set_column) w=where? o=loption(order) lim=loption(limit)
151               {
152                 let params = update_tables [Tables.get table,[]] ss w in
153                 let p3 = Syntax.params_of_order o [] [Tables.get table] in
154                 [], params @ p3 @ lim, Update (Some table)
155               }
156          /* http://dev.mysql.com/doc/refman/5.1/en/update.html multi-table syntax */
157          | update_cmd tables=commas(source) SET ss=commas(set_column) w=where?
158               {
159                 let params = update_tables tables ss w in
160                 [], params, Update None
161               }
162          | DELETE FROM table=IDENT w=where?
163               {
164                 let t = Tables.get table in
165                 let p = get_params_opt [t] (snd t) w in
166                 [], p, Delete table
167               }
169 table_name: name=IDENT | IDENT DOT name=IDENT { name } (* FIXME db name *)
170 index_column: name=IDENT collate? order_type? { name }
172 table_definition: t=sequence_(column_def1) table_def_done { List.filter_map (function `Attr a -> Some a | `Constraint _ -> None) t }
173                 | LIKE name=maybe_parenth(IDENT) { Tables.get name >> snd } (* mysql *)
175 (* ugly, can you fixme? *)
176 (* ignoring everything after RPAREN (NB one look-ahead token) *)
177 table_def_done: table_def_done1 RPAREN IGNORED* { Parser_state.mode_normal () }
178 table_def_done1: { Parser_state.mode_ignore () }
180 select_stmt_t: select_core other=list(preceded(compound_op,select_core)) 
181                o=loption(order) lim=limit_t?
182               {
183                 let (s1,p1,tbls,singlerow1) = $1 in
184                 let (s2l,p2l) = List.split (List.map (fun (s,p,_,_) -> s,p) other) in
185                 if Config.debug1 () then eprintf "singlerow=%b other=%u\n%!" singlerow1 (List.length other);
186                 let singlerow1 = singlerow1 && other = [] in
187                 (* ignoring tables in compound statements - they cannot be used in ORDER BY *)
188                 let final_schema = List.fold_left RA.Schema.compound s1 s2l in
189                 let p3 = Syntax.params_of_order o final_schema tbls in
190                 let (p4,singlerow) = match lim with | Some x -> x | None -> [],false in
191 (*                 RA.Schema.check_unique schema; *)
192                 final_schema,(p1@(List.flatten p2l)@p3@p4), Select (singlerow || singlerow1)
193               }
195 select_stmt: select_stmt_t { let (s,p,_) = $1 in s,p }
197 select_core: SELECT select_type? r=commas(column1)
198              FROM t=table_list
199              w=where?
200              g=loption(group)
201              h=having?
202               {
203                 let (tbls,p2,joined_schema) = Syntax.join t in
204                 let singlerow = g = [] && Syntax.test_singlerow r in
205                 let p1 = Syntax.params_of_columns tbls joined_schema r in
206                 let p3 = Syntax.get_params_opt tbls joined_schema w in
207                 let p4 = Syntax.get_params_l tbls joined_schema g in
208                 let p5 = Syntax.get_params_opt tbls joined_schema h in
209                 (Syntax.infer_schema r tbls joined_schema, p1 @ p2 @ p3 @ p4 @ p5, tbls, singlerow)
210               }
212 table_list: src=source joins=join_source* { (src,joins) }
214 join_source: NATURAL maybe_join_type JOIN src=source { src,`Natural }
215            | CROSS JOIN src=source { src,`Cross }
216            | qualified_join src=source cond=join_cond { src,cond }
218 qualified_join: COMMA | maybe_join_type JOIN { }
220 join_cond: ON e=expr { `Search e }
221          | USING l=sequence(IDENT) { `Using l }
222          | (* *) { `Default }
224 source1: IDENT { Tables.get $1,[] }
225        | LPAREN s=select_core RPAREN { let (s,p,_,_) = s in ("",s),p }
227 source: src=source1 alias=maybe_as
228     {
229       match alias with
230       | Some name -> let ((_,s),p) = src in ((name,s),p)
231       | None -> src
232     }
234 insert_cmd: INSERT OR conflict_algo INTO | INSERT INTO | REPLACE INTO { }
235 update_cmd: UPDATE | UPDATE OR conflict_algo { }
236 conflict_algo: CONFLICT_ALGO | REPLACE { }
238 select_type: DISTINCT | ALL { }
240 int_or_param: i=INTEGER { `Const i }
241             | p=PARAM { `Param p }
243 limit_t: LIMIT lim=int_or_param { limit [`Limit,lim] }
244        | LIMIT ofs=int_or_param COMMA lim=int_or_param { limit [`Offset,ofs; `Limit,lim] }
245        | LIMIT lim=int_or_param OFFSET ofs=int_or_param { limit [`Limit,lim; `Offset,ofs] }
247 limit: limit_t { fst $1 }
249 order: ORDER BY l=commas(terminated(expr,order_type?)) { l }
250 order_type: DESC | ASC { }
252 where: WHERE e=expr { e }
253 group: GROUP BY l=expr_list { l }
254 having: HAVING e=expr { e }
256 column1:
257        | IDENT DOT ASTERISK { Syntax.AllOf $1 }
258        | ASTERISK { Syntax.All }
259        | e=expr m=maybe_as { Syntax.Expr (e,m) }
261 maybe_as: AS? name=IDENT { Some name }
262         | { None }
264 maybe_parenth(X): x=X | LPAREN x=X RPAREN { x }
266 alter_action: ADD COLUMN? col=maybe_parenth(column_def) pos=alter_pos { `Add (col,pos) }
267             | ADD index_type IDENT? sequence(IDENT) { `None }
268             | DROP INDEX IDENT { `None }
269             | DROP COLUMN? col=IDENT drop_behavior? { `Drop col } (* FIXME behavior? *)
270 index_type: INDEX | FULLTEXT { }
271 alter_pos: AFTER col=IDENT { `After col }
272          | FIRST { `First }
273          | { `Last }
274 drop_behavior: CASCADE | RESTRICT { }
276 column_def: name=IDENT t=sql_type? column_def_extra*
277     { RA.attr name (match t with Some x -> x | None -> Int) }
279 column_def1: c=column_def { `Attr c }
280            | pair(CONSTRAINT,IDENT)? c=table_constraint_1 { `Constraint c }
282 on_conflict: ON CONFLICT algo=conflict_algo { algo }
283 column_def_extra: PRIMARY KEY { Some PrimaryKey }
284                 | NOT NULL { Some NotNull }
285                 | NULL { None }
286                 | UNIQUE { Some Unique }
287                 | AUTOINCREMENT { Some Autoincrement }
288                 | on_conflict { None }
289                 | CHECK LPAREN expr RPAREN { None }
290                 | DEFAULT default_value { None } (* FIXME check type with column *)
291                 | COLLATE IDENT { None }
293 default_value: literal_value | datetime_value { }
295 (* FIXME check columns *)
296 table_constraint_1:
297       | some_key IDENT? key_arg { [] }
298       | FOREIGN KEY IDENT? sequence(IDENT) REFERENCES IDENT sequence(IDENT)? { [] }
299       | CHECK LPAREN expr RPAREN { [] }
301 some_key: UNIQUE KEY? | PRIMARY? KEY | FULLTEXT KEY { }
302 key_arg: LPAREN VALUE RPAREN | sequence(IDENT) { }
304 set_column: name=attr_name EQUAL e=expr { name,e }
306 (* expr: expr1 { $1 >> Syntax.expr_to_string >> prerr_endline; $1 } *)
308 anyall: ANY | ALL | SOME { }
310 mnot(X): NOT x = X | x = X { x }
312 attr_name: name=IDENT { (name,None) }
313          | table=IDENT DOT name=IDENT
314          | IDENT DOT table=IDENT DOT name=IDENT { (name,Some table) } (* FIXME database identifier *)
316 expr:
317      expr numeric_bin_op expr %prec PLUS { `Func ((Int,false),[$1;$3]) }
318     | expr boolean_bin_op expr %prec AND { `Func ((Bool,false),[$1;$3]) }
319     | e1=expr comparison_op anyall? e2=expr %prec EQUAL { `Func ((Bool,false),[e1;e2]) }
320     | expr CONCAT_OP expr { `Func ((Text,false),[$1;$3]) }
321     | e1=expr mnot(like) e2=expr e3=escape?
322       { `Func ((Any,false),(List.filter_valid [Some e1; Some e2; e3])) }
323     | unary_op expr { $2 }
324     | LPAREN expr RPAREN { $2 }
325     | attr_name { `Column $1 }
326     | v=literal_value | v=datetime_value { v }
327     | e1=expr mnot(IN) l=sequence(expr) { `Func ((Any,false),e1::l) }
328     | e1=expr mnot(IN) LPAREN select=select_stmt RPAREN
329       {
330         `Func ((Any,false),e1::select_value select)
331       }
332     | e1=expr IN table=IDENT { Tables.check(table); e1 }
333     | LPAREN select=select_stmt RPAREN
334       {
335         `Func ((Any,false),select_value select)
336       }
337     | PARAM { `Param ($1,Any) }
338     | f=FUNCTION LPAREN p=func_params RPAREN { `Func (f,p) }
339     | expr TEST_NULL { $1 }
340     | expr mnot(BETWEEN) expr AND expr { `Func ((Int,false),[$1;$3;$5]) }
341     | mnot(EXISTS) LPAREN select=select_stmt RPAREN { `Func ((Bool,false),params_of select) }
342     | CASE e1=expr? branches=nonempty_list(case_branch) e2=preceded(ELSE,expr)? END 
343       {
344         let l = function None -> [] | Some x -> [x] in
345         `Func ((Any,false),l e1 @ List.flatten branches @ l e2)
346       }
348 case_branch: WHEN e1=expr THEN e2=expr { [e1;e2] }
349 like: LIKE | LIKE_OP { }
351 datetime_value: | DATETIME_FUNC | DATETIME_FUNC LPAREN INTEGER? RPAREN { `Value Datetime }
353 literal_value:
354     | TEXT { `Value Text }
355     | BLOB { `Value Blob }
356     | INTEGER { `Value Int }
357     | FLOAT { `Value Float }
358     | DATE TEXT
359     | TIME TEXT
360     | TIMESTAMP TEXT { `Value Datetime }
362 expr_list: l=commas(expr) { l }
363 func_params: expr_list { $1 }
364            | ASTERISK { [] }
365            | (* *) { [] }
366 escape: ESCAPE expr { $2 }
367 numeric_bin_op: PLUS | MINUS | ASTERISK | NUM_DIV_OP | NUM_BIT_OP { }
368 comparison_op: EQUAL | NUM_CMP_OP | NUM_EQ_OP { }
369 boolean_bin_op: AND | OR { }
371 unary_op: EXCL { }
372         | PLUS { }
373         | MINUS { }
374         | TILDE { }
375         | NOT { }
377 sql_type_flavor: T_INTEGER UNSIGNED? ZEROFILL? { Int }
378                | binary { Blob }
379                | NATIONAL? text VARYING? charset? collate? { Text }
380                | T_FLOAT PRECISION? { Float }
381                | T_BOOLEAN { Bool }
382                | T_DATETIME | DATE | TIME | TIMESTAMP { Datetime }
384 binary: T_BLOB | BINARY | BINARY VARYING { }
385 text: T_TEXT | CHARACTER { }
387 %inline either(X,Y): X | Y { }
388 %inline commas(X): l=separated_nonempty_list(COMMA,X) { l }
389 (* (x1,x2,...,xn) *)
390 %inline sequence_(X): LPAREN l=commas(X) { l }
391 %inline sequence(X): l=sequence_(X) RPAREN { l }
393 charset: CHARSET either(IDENT,BINARY) | CHARACTER SET either(IDENT,BINARY) | ASCII | UNICODE { }
394 collate: COLLATE IDENT { }
396 sql_type: t=sql_type_flavor
397         | t=sql_type_flavor LPAREN INTEGER RPAREN UNSIGNED?
398         | t=sql_type_flavor LPAREN INTEGER COMMA INTEGER RPAREN
399         { t }
401 compound_op: UNION ALL? | EXCEPT | INTERSECT { }
403 maybe_join_type: JOIN_TYPE1? JOIN_TYPE2? { }