sql: CASE ... END, TODO more polymorphic `Func
[sqlgg.git] / src / sql_parser.mly
blobb3172957161e071f4ce6fd54e9116f8ebe1c4922
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,p) = 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
37 %token <int> INTEGER
38 %token <string> IDENT TEXT BLOB
39 %token <float> FLOAT
40 %token <Stmt.param_id> PARAM
41 %token <Sql.Type.t> FUNCTION
42 %token LPAREN RPAREN COMMA EOF DOT NULL
43 %token CONFLICT_ALGO
44 %token SELECT INSERT OR INTO CREATE UPDATE VIEW TABLE VALUES WHERE ASTERISK DISTINCT ALL ANY SOME
45        LIMIT ORDER BY DESC ASC EQUAL DELETE FROM DEFAULT OFFSET SET JOIN LIKE_OP LIKE
46        EXCL TILDE NOT TEST_NULL BETWEEN AND ESCAPE USING UNION EXCEPT INTERSECT AS
47        CONCAT_OP JOIN_TYPE1 JOIN_TYPE2 NATURAL CROSS REPLACE IN GROUP HAVING
48        UNIQUE PRIMARY KEY FOREIGN AUTOINCREMENT ON CONFLICT TEMPORARY IF EXISTS
49        PRECISION UNSIGNED ZEROFILL VARYING CHARSET NATIONAL ASCII UNICODE COLLATE BINARY CHARACTER
50        DATETIME_FUNC DATE TIME TIMESTAMP ALTER ADD COLUMN CASCADE RESTRICT DROP
51        GLOBAL LOCAL VALUE REFERENCES CHECK CONSTRAINT IGNORED AFTER INDEX FULLTEXT FIRST
52        CASE WHEN THEN ELSE END
53 %token NUM_BINARY_OP PLUS MINUS COMPARISON_OP
54 %token T_INTEGER T_BLOB T_TEXT T_FLOAT T_BOOLEAN T_DATETIME
57 %left COMMA_JOIN
58 %left JOIN_JOIN
60 (* FIXME precedence of COMMA and JOIN *)
62 %left TEST_NULL
63 %left AND OR
64 %nonassoc EQUAL
65 %nonassoc NUM_BINARY_OP
66 %left PLUS MINUS
67 %left ASTERISK
69 %type <Syntax.expr> expr
71 %start <RA.Schema.t * Stmt.params * Stmt.kind> input
75 input: statement EOF { $1 }
77 if_not_exists: IF NOT EXISTS { }
78 if_exists: IF EXISTS {}
79 temporary: either(GLOBAL,LOCAL)? TEMPORARY { }
81 statement: CREATE ioption(temporary) TABLE ioption(if_not_exists) name=IDENT schema=table_definition
82               {
83                 Tables.add (name,schema);
84                 ([],[],Create name)
85               }
86          | ALTER TABLE name=IDENT action=alter_action
87               {
88                 begin match action with
89                 | `Add (col,pos) -> Tables.alter_add name col pos
90                 | `Drop col -> Tables.alter_drop name col
91                 | `None -> ()
92                 end;
93                 ([],[],Alter name)
94               }
95          | DROP TABLE if_exists? name=IDENT
96               {
97                 Tables.drop name;
98                 ([],[],Drop name)
99               }
100          | CREATE either(TABLE,VIEW) name=IDENT AS select=select_stmt
101               {
102                 let (s,p) = select in
103                 Tables.add (name,s);
104                 ([],p,Create name)
105               }
106          | select_stmt
107               { let (s,p) = $1 in s,p,Select }
108          | insert_cmd table=IDENT names=sequence(IDENT)? VALUES values=sequence(expr)?
109               {
110                 let expect = values_or_all table names in
111                 let params, values = match values with
112                 | None -> [], Some expect
113                 | Some values ->
114                   let vl = List.length values in
115                   let cl = List.length expect in
116                   if vl <> cl then
117                     failwith (sprintf "Expected %u expressions in VALUES list, %u provided" cl vl);
118                   Syntax.params_of_assigns [Tables.get table] (List.combine (List.map (fun a -> a.RA.name, None) expect) values), None
119                 in
120                 [], params, Insert (values,table)
121               }
122          | insert_cmd table=IDENT names=sequence(IDENT)? select=maybe_parenth(select_stmt)
123               {
124                 let (schema,params) = select in
125                 let expect = values_or_all table names in
126                 ignore (RA.Schema.compound expect schema); (* test equal types *)
127                 [], params, Insert(None,table)
128               }
129          | insert_cmd table=IDENT SET ss=separated_nonempty_list(COMMA,set_column)
130               {
131                 let p1 = Syntax.params_of_assigns [Tables.get table] ss in
132                 [], p1, Insert (None,table)
133               }
134          | update_cmd table=IDENT SET ss=separated_nonempty_list(COMMA,set_column) w=where? o=loption(order) lim=loption(limit)
135               {
136                 let params = update_tables [Tables.get table,[]] ss w in
137                 let p3 = Syntax.params_of_order o [] [Tables.get table] in
138                 [], params @ p3 @ lim, Update (Some table)
139               }
140          /* http://dev.mysql.com/doc/refman/5.1/en/update.html multi-table syntax */
141          | update_cmd tables=separated_nonempty_list(COMMA,source) SET ss=separated_nonempty_list(COMMA,set_column) w=where?
142               {
143                 let params = update_tables tables ss w in
144                 [], params, Update None
145               }
146          | DELETE FROM table=IDENT w=where?
147               {
148                 let t = Tables.get table in
149                 let p = get_params_opt [t] (snd t) w in
150                 [], p, Delete table
151               }
153 table_definition: t=sequence_(column_def1) table_def_done { List.filter_map (function `Attr a -> Some a | `Constraint _ -> None) t }
154                 | LIKE name=maybe_parenth(IDENT) { Tables.get name >> snd } (* mysql *)
156 (* ugly, can you fixme? *)
157 (* ignoring everything after RPAREN (NB one look-ahead token) *)
158 table_def_done: table_def_done1 RPAREN IGNORED* { Parser_state.mode_normal () }
159 table_def_done1: { Parser_state.mode_ignore () }
161 select_stmt: select_core other=list(preceded(compound_op,select_core)) o=loption(order) p4=loption(limit)
162               {
163                 let (s1,p1,tbls) = $1 in
164                 let (s2l,p2l) = List.split (List.map (fun (s,p,_) -> s,p) other) in
165                 (* ignoring tables in compound statements - they cannot be used in ORDER BY *)
166                 let final_schema = List.fold_left RA.Schema.compound s1 s2l in
167                 let p3 = Syntax.params_of_order o final_schema tbls in
168 (*                 RA.Schema.check_unique schema; *)
169                 final_schema,(p1@(List.flatten p2l)@p3@p4)
170               }
172 select_core: SELECT select_type? r=separated_nonempty_list(COMMA,column1)
173              FROM t=table_list
174              w=where?
175              g=loption(group)
176              h=having?
177               {
178                 let (tbls,p2,joined_schema) = Syntax.join t in
179                 let p1 = Syntax.params_of_columns tbls joined_schema r in
180                 let p3 = Syntax.get_params_opt tbls joined_schema w in
181                 let p4 = Syntax.get_params_l tbls joined_schema g in
182                 let p5 = Syntax.get_params_opt tbls joined_schema h in
183                 (Syntax.infer_schema r tbls joined_schema, p1 @ p2 @ p3 @ p4 @ p5, tbls)
184               }
186 table_list: src=source joins=join_source* { (src,joins) }
188 join_source: NATURAL maybe_join_type JOIN src=source { src,`Natural }
189            | CROSS JOIN src=source { src,`Cross }
190            | qualified_join src=source cond=join_cond { src,cond }
192 qualified_join: COMMA | maybe_join_type JOIN { }
194 join_cond: ON e=expr { `Search e }
195          | USING l=sequence(IDENT) { `Using l }
196          | (* *) { `Default }
198 source1: IDENT { Tables.get $1,[] }
199        | LPAREN s=select_core RPAREN { let (s,p,_) = s in ("",s),p }
201 source: src=source1 alias=maybe_as
202     {
203       match alias with
204       | Some name -> let ((n,s),p) = src in ((name,s),p)
205       | None -> src
206     }
208 insert_cmd: INSERT OR conflict_algo INTO | INSERT INTO | REPLACE INTO { }
209 update_cmd: UPDATE | UPDATE OR conflict_algo { }
210 conflict_algo: CONFLICT_ALGO | REPLACE { }
212 select_type: DISTINCT | ALL { }
214 int_or_param: INTEGER { [] }
215             | PARAM { [($1,Int)] }
217 limit: LIMIT p=int_or_param { p }
218      | LIMIT p1=int_or_param COMMA p2=int_or_param { p1 @ p2 } (* Named? *)
219      | LIMIT p1=int_or_param OFFSET p2=int_or_param { p1 @ p2 }
221 order: ORDER BY l=separated_nonempty_list(COMMA,terminated(expr,order_type?)) { l }
222 order_type: DESC | ASC { }
224 where: WHERE e=expr { e }
225 group: GROUP BY l=separated_nonempty_list(COMMA,expr) { l }
226 having: HAVING e=expr { e }
228 column1:
229        | IDENT DOT ASTERISK { Syntax.AllOf $1 }
230        | ASTERISK { Syntax.All }
231        | e=expr m=maybe_as { Syntax.Expr (e,m) }
233 maybe_as: AS? name=IDENT { Some name }
234         | { None }
236 maybe_parenth(X): x=X | LPAREN x=X RPAREN { x }
238 alter_action: ADD COLUMN? col=maybe_parenth(column_def) pos=alter_pos { `Add (col,pos) }
239             | ADD index_type IDENT? sequence(IDENT) { `None }
240             | DROP COLUMN? col=IDENT drop_behavior? { `Drop col } (* FIXME behavior? *)
241 index_type: INDEX | FULLTEXT { }
242 alter_pos: AFTER col=IDENT { `After col }
243          | FIRST { `First }
244          | { `Last }
245 drop_behavior: CASCADE | RESTRICT { }
247 column_def: name=IDENT t=sql_type? column_def_extra*
248     { RA.attr name (match t with Some x -> x | None -> Int) }
250 column_def1: c=column_def { `Attr c }
251            | pair(CONSTRAINT,IDENT)? c=table_constraint_1 { `Constraint c }
253 on_conflict: ON CONFLICT algo=conflict_algo { algo }
254 column_def_extra: PRIMARY KEY { Some PrimaryKey }
255                 | NOT NULL { Some NotNull }
256                 | NULL { None }
257                 | UNIQUE { Some Unique }
258                 | AUTOINCREMENT { Some Autoincrement }
259                 | on_conflict { None }
260                 | CHECK LPAREN expr RPAREN { None }
261                 | DEFAULT default_value { None } (* FIXME check type with column *)
262                 | COLLATE IDENT { None }
264 default_value: literal_value | datetime_value { }
266 (* FIXME check columns *)
267 table_constraint_1:
268       | some_key IDENT? key_arg { [] }
269       | FOREIGN KEY IDENT? sequence(IDENT) REFERENCES IDENT sequence(IDENT)? { [] }
270       | CHECK LPAREN expr RPAREN { [] }
272 some_key: UNIQUE KEY? | PRIMARY? KEY | FULLTEXT KEY { }
273 key_arg: LPAREN VALUE RPAREN | sequence(IDENT) { }
275 set_column: name=attr_name EQUAL e=expr { name,e }
277 (* expr: expr1 { $1 >> Syntax.expr_to_string >> prerr_endline; $1 } *)
279 anyall: ANY | ALL | SOME { }
281 mnot(X): NOT x = X | x = X { x }
283 attr_name: name=IDENT { (name,None) }
284          | table=IDENT DOT name=IDENT
285          | IDENT DOT table=IDENT DOT name=IDENT { (name,Some table) } (* FIXME database identifier *)
287 expr:
288      expr numeric_bin_op expr %prec PLUS { `Func (Int,[$1;$3]) }
289     | expr boolean_bin_op expr %prec AND { `Func (Bool,[$1;$3]) }
290     | e1=expr comparison_op anyall? e2=expr %prec EQUAL { `Func (Bool,[e1;e2]) }
291     | expr CONCAT_OP expr { `Func (Text,[$1;$3]) }
292     | e1=expr mnot(like) e2=expr e3=escape?
293       { `Func (Any,(List.filter_valid [Some e1; Some e2; e3])) }
294     | unary_op expr { $2 }
295     | LPAREN expr RPAREN { $2 }
296     | attr_name { `Column $1 }
297     | v=literal_value | v=datetime_value { v }
298     | e1=expr mnot(IN) l=sequence(expr) { `Func (Any,e1::l) }
299     | e1=expr mnot(IN) LPAREN select=select_stmt RPAREN
300       {
301         `Func (Any,e1::select_value select)
302       }
303     | e1=expr IN table=IDENT { Tables.check(table); e1 }
304     | LPAREN select=select_stmt RPAREN
305       {
306         `Func (Any,select_value select)
307       }
308     | PARAM { `Param ($1,Any) }
309     | f=FUNCTION LPAREN p=func_params RPAREN { `Func (f,p) }
310     | expr TEST_NULL { $1 }
311     | expr mnot(BETWEEN) expr AND expr { `Func (Int,[$1;$3;$5]) }
312     | mnot(EXISTS) LPAREN select=select_stmt RPAREN { `Func (Bool,params_of select) }
313     | CASE e1=expr? branches=nonempty_list(case_branch) e2=preceded(ELSE,expr)? END 
314       {
315         let l = function None -> [] | Some x -> [x] in
316         `Func (Any,l e1 @ List.flatten branches @ l e2)
317       }
319 case_branch: WHEN e1=expr THEN e2=expr { [e1;e2] }
320 like: LIKE | LIKE_OP { }
322 datetime_value: | DATETIME_FUNC | DATETIME_FUNC LPAREN INTEGER? RPAREN { `Value Datetime }
324 literal_value:
325     | TEXT { `Value Text }
326     | BLOB { `Value Blob }
327     | INTEGER { `Value Int }
328     | FLOAT { `Value Float }
329     | DATE TEXT
330     | TIME TEXT
331     | TIMESTAMP TEXT { `Value Datetime }
333 expr_list: separated_nonempty_list(COMMA,expr) { $1 }
334 func_params: expr_list { $1 }
335            | ASTERISK { [] }
336            | (* *) { [] }
337 escape: ESCAPE expr { $2 }
338 numeric_bin_op: PLUS | MINUS | ASTERISK | NUM_BINARY_OP { }
339 comparison_op: EQUAL | COMPARISON_OP { }
340 boolean_bin_op: AND | OR { }
342 unary_op: EXCL { }
343         | PLUS { }
344         | MINUS { }
345         | TILDE { }
346         | NOT { }
348 sql_type_flavor: T_INTEGER UNSIGNED? ZEROFILL? { Int }
349                | binary { Blob }
350                | NATIONAL? text VARYING? charset? collate? { Text }
351                | T_FLOAT PRECISION? { Float }
352                | T_BOOLEAN { Bool }
353                | T_DATETIME | DATE | TIME | TIMESTAMP { Datetime }
355 binary: T_BLOB | BINARY | BINARY VARYING { }
356 text: T_TEXT | CHARACTER { }
358 %inline either(X,Y): X | Y { }
359 (* (x1,x2,...,xn) *)
360 %inline sequence_(X): LPAREN l=separated_nonempty_list(COMMA,X) { l }
361 %inline sequence(X): l=sequence_(X) RPAREN { l }
363 charset: CHARSET either(IDENT,BINARY) | CHARACTER SET either(IDENT,BINARY) | ASCII | UNICODE { }
364 collate: COLLATE IDENT { }
366 sql_type: t=sql_type_flavor
367         | t=sql_type_flavor LPAREN INTEGER RPAREN UNSIGNED?
368         | t=sql_type_flavor LPAREN INTEGER COMMA INTEGER RPAREN
369         { t }
371 compound_op: UNION ALL? | EXCEPT | INTERSECT { }
373 maybe_join_type: JOIN_TYPE1? JOIN_TYPE2? { }