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