todo -1
[sqlgg.git] / sql_parser.mly
blobe66661b7fa0a35b6a9c14d564b3950761dd4fdc7
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 def_param_name name (id,t) =
16     let name =
17     match id with
18     | Next | Numbered _ -> name
19     | Named x -> x
20     in
21     (Named name,t)
23   let params_of select = List.map (fun x -> `Param x) (snd select)
25   let select_value select =
26     let (s,p) = select in
27     if (List.length s <> 1) then
28       raise (RA.Scheme.Error (s,"only one column allowed for SELECT operator in this expression"));
29     params_of select
33 %token <int> INTEGER
34 %token <string> IDENT TEXT BLOB
35 %token <float> FLOAT
36 %token <Stmt.param_id> PARAM
37 %token <Sql.Type.t option> FUNCTION
38 %token LPAREN RPAREN COMMA EOF DOT NULL
39 %token CONFLICT_ALGO
40 %token SELECT INSERT OR INTO CREATE UPDATE VIEW TABLE VALUES WHERE ASTERISK DISTINCT ALL ANY SOME
41        LIMIT ORDER BY DESC ASC EQUAL DELETE FROM DEFAULT OFFSET SET JOIN LIKE_OP
42        EXCL TILDE NOT TEST_NULL BETWEEN AND ESCAPE USING UNION EXCEPT INTERSECT AS
43        CONCAT_OP JOIN_TYPE1 JOIN_TYPE2 NATURAL CROSS REPLACE IN GROUP HAVING
44        UNIQUE PRIMARY KEY FOREIGN AUTOINCREMENT ON CONFLICT TEMPORARY IF EXISTS
45        PRECISION UNSIGNED ZEROFILL VARYING CHARSET NATIONAL ASCII UNICODE COLLATE BINARY CHARACTER
46        DATETIME_FUNC DATE TIME TIMESTAMP ALTER ADD COLUMN CASCADE RESTRICT DROP
47        GLOBAL LOCAL VALUE REFERENCES CHECK CONSTRAINT IGNORED AFTER INDEX FULLTEXT FIRST
48 %token NUM_BINARY_OP PLUS MINUS COMPARISON_OP
49 %token T_INTEGER T_BLOB T_TEXT T_FLOAT T_BOOLEAN T_DATETIME
52 %left COMMA_JOIN
53 %left JOIN_JOIN
55 (* FIXME precedence of COMMA and JOIN *)
57 %left TEST_NULL
58 %left AND OR
59 %nonassoc EQUAL
60 %nonassoc NUM_BINARY_OP
61 %left PLUS MINUS
62 %left ASTERISK
64 %type <Syntax.expr> expr
66 %start <RA.Scheme.t * Stmt.params * Stmt.kind> input
70 input: statement EOF { $1 }
72 if_not_exists: IF NOT EXISTS { }
73 if_exists: IF EXISTS {}
74 temporary: either(GLOBAL,LOCAL)? TEMPORARY { }
76 statement: CREATE ioption(temporary) TABLE ioption(if_not_exists) name=IDENT
77            table_def=sequence_(column_def1) table_def_done
78               {
79                 let schema = List.filter_map (function `Attr a -> Some a | `Constraint _ -> None) table_def in
80                 let () = Tables.add (name,schema) in
81                 ([],[],Create name)
82               }
83          | ALTER TABLE name=IDENT action=alter_action
84               {
85                 begin match action with
86                 | `Add (col,pos) -> Tables.alter_add name col pos
87                 | `Drop col -> Tables.alter_drop name col
88                 | `None -> ()
89                 end;
90                 ([],[],Alter name)
91               }
92          | DROP TABLE if_exists? name=IDENT
93               {
94                 Tables.drop name;
95                 ([],[],Drop name)
96               }
97          | CREATE either(TABLE,VIEW) name=IDENT AS select=select_stmt
98               {
99                 let (s,p) = select in
100                 Tables.add (name,s);
101                 ([],p,Create name)
102               }
103          | select_stmt
104               { let (s,p) = $1 in s,p,Select }
105          | insert_cmd table=IDENT cols=sequence(IDENT)? VALUES (*sequence(expr)?*)
106               {
107                 let s = Tables.get_schema table in
108                 let s = match cols with
109                   | Some cols -> RA.Scheme.project cols s
110                   | None -> s
111                 in
112                 let p = Syntax.schema_as_params s in
113                 [],p,Insert table
114               }
115          | update_cmd table=IDENT SET assignments=separated_nonempty_list(COMMA,set_column) w=where?
116               {
117                 let t = Tables.get table in
118                 let p2 = get_params_opt [t] (snd t) w in
119                 let (cols,exprs) = List.split assignments in
120                 let _ = RA.Scheme.project cols (snd t) in (* validates columns *)
121                 let p1 = Syntax.get_params_l [t] (snd t) exprs in
122                 [], p1 @ p2, Update table
123               }
124          | DELETE FROM table=IDENT w=where?
125               {
126                 let t = Tables.get table in
127                 let p = get_params_opt [t] (snd t) w in
128                 [], p, Delete table
129               }
131 (* ignoring everything after RPAREN (NB one look-ahead token) *)
132 table_def_done: table_def_done1 RPAREN IGNORED* { Parser_state.mode_normal () }
133 table_def_done1: { Parser_state.mode_ignore () }
135 select_stmt: select_core list(preceded(compound_op,select_core)) o=loption(order) p4=loption(limit)
136               {
137                 let (s1,p1,tbls) = $1 in
138                 let (s2l,p2l) = List.split (List.map (fun (s,p,_) -> s,p) $2) in
139                 (* ignoring tables in compound statements - they cannot be used in ORDER BY *)
140                 let schema = List.fold_left RA.Scheme.compound s1 s2l in
141                 let p3 = Syntax.get_params_l tbls schema o in
142 (*                 RA.Scheme.check_unique schema; *)
143                 schema,(p1@(List.flatten p2l)@p3@p4)
144               }
146 select_core: SELECT select_type? r=separated_nonempty_list(COMMA,column1)
147              FROM t=table_list
148              w=where?
149              g=loption(group)
150              h=having?
151               {
152                 let (tbls,p2,joined_schema) = Syntax.join t in
153                 let p1 = Syntax.params_of_columns tbls joined_schema r in
154                 let p3 = Syntax.get_params_opt tbls joined_schema w in
155                 let p4 = Syntax.get_params_l tbls joined_schema g in
156                 let p5 = Syntax.get_params_opt tbls joined_schema h in
157                 (Syntax.infer_schema r tbls joined_schema, p1 @ p2 @ p3 @ p4 @ p5, tbls)
158               }
160 table_list: src=source joins=join_source* { (src,joins) }
162 join_source: NATURAL maybe_join_type JOIN src=source { src,`Natural }
163            | CROSS JOIN src=source { src,`Cross }
164            | qualified_join src=source cond=join_cond { src,cond }
166 qualified_join: COMMA | maybe_join_type JOIN { }
168 join_cond: ON e=expr { `Search e }
169          | USING l=sequence(IDENT) { `Using l }
170          | (* *) { `Default }
172 source1: IDENT { Tables.get $1,[] }
173        | LPAREN s=select_core RPAREN { let (s,p,_) = s in ("",s),p }
175 source: src=source1 alias=maybe_as
176     {
177       match alias with
178       | Some name -> let ((n,s),p) = src in ((name,s),p)
179       | None -> src
180     }
182 insert_cmd: INSERT OR CONFLICT_ALGO INTO | INSERT INTO | REPLACE INTO { }
184 update_cmd: UPDATE {}
185           | UPDATE OR CONFLICT_ALGO {} ;
187 select_type: DISTINCT | ALL { }
189 int_or_param: INTEGER { [] }
190             | PARAM { [($1,Some Int)] }
192 limit: LIMIT p=int_or_param { p }
193      | LIMIT p1=int_or_param COMMA p2=int_or_param { p1 @ p2 } (* Named? *)
194      | LIMIT p1=int_or_param OFFSET p2=int_or_param { p1 @ p2 }
196 order: ORDER BY l=separated_nonempty_list(COMMA,terminated(expr,order_type?)) { l }
197 order_type: DESC | ASC { }
199 where: WHERE e=expr { e }
200 group: GROUP BY l=separated_nonempty_list(COMMA,expr) { l }
201 having: HAVING e=expr { e }
203 column1:
204        | IDENT DOT ASTERISK { Syntax.AllOf $1 }
205        | ASTERISK { Syntax.All }
206        | e=expr m=maybe_as { Syntax.Expr (e,m) }
208 maybe_as: AS? name=IDENT { Some name }
209         | { None }
211 maybe_parenth(X): x=X | LPAREN x=X RPAREN { x }
213 alter_action: ADD COLUMN? col=maybe_parenth(column_def) pos=alter_pos { `Add (col,pos) }
214             | ADD index_type IDENT? sequence(IDENT) { `None }
215             | DROP COLUMN? col=IDENT drop_behavior? { `Drop col } (* FIXME behavior? *)
216 index_type: INDEX | FULLTEXT { }
217 alter_pos: AFTER col=IDENT { `After col }
218          | FIRST { `First }
219          | { `Last }
220 drop_behavior: CASCADE | RESTRICT { }
222 column_def: name=IDENT t=sql_type? column_def_extra*
223     { RA.attr name (match t with Some x -> x | None -> Int) }
225 column_def1: c=column_def { `Attr c }
226            | pair(CONSTRAINT,IDENT)? c=table_constraint_1 { `Constraint c }
228 on_conflict: ON CONFLICT algo=CONFLICT_ALGO { algo }
229 column_def_extra: PRIMARY KEY { Some PrimaryKey }
230                 | NOT NULL { Some NotNull }
231                 | NULL { None }
232                 | UNIQUE { Some Unique }
233                 | AUTOINCREMENT { Some Autoincrement }
234                 | on_conflict { None }
235                 | CHECK LPAREN expr RPAREN { None }
236                 | DEFAULT default_value { None } (* FIXME check type with column *)
237                 | COLLATE IDENT { None }
239 default_value: literal_value | datetime_value { }
241 (* FIXME check columns *)
242 table_constraint_1:
243       | primary_key IDENT? sequence(IDENT) { [] }
244       | unique_key IDENT? unique_arg { [] }
245       | FOREIGN KEY IDENT? sequence(IDENT) REFERENCES IDENT sequence(IDENT)? { [] }
246       | CHECK LPAREN expr RPAREN { [] }
248 (* mysql specific? (not in ANS) *)
249 unique_key: UNIQUE KEY? { }
251 primary_key: PRIMARY? KEY { }
252 unique_arg: LPAREN VALUE RPAREN | sequence(IDENT) { }
254 set_column: name=IDENT EQUAL e=expr { name,e }
256 (* expr: expr1 { $1 >> Syntax.expr_to_string >> prerr_endline; $1 } *)
258 anyall: ANY | ALL | SOME { }
260 mnot(X): NOT x = X | x = X { x }
262 expr:
263      expr numeric_bin_op expr %prec PLUS { `Func ((Some Int),[$1;$3]) }
264     | expr boolean_bin_op expr %prec AND { `Func ((Some Bool),[$1;$3]) }
265     | e1=expr comparison_op anyall? e2=expr %prec EQUAL { `Func ((Some Bool),[e1;e2]) }
266     | expr CONCAT_OP expr { `Func ((Some Text),[$1;$3]) }
267     | e1=expr mnot(LIKE_OP) e2=expr e3=escape?
268       { `Func (None,(List.filter_valid [Some e1; Some e2; e3])) }
269     | unary_op expr { $2 }
270     | LPAREN expr RPAREN { $2 }
271     | IDENT { `Column ($1,None) }
272     | t=IDENT DOT c=IDENT
273     | IDENT DOT t=IDENT DOT c=IDENT { `Column (c,Some t) }
274     | v=literal_value | v=datetime_value { v }
275     | e1=expr mnot(IN) l=sequence(expr) { `Func (None,e1::l) }
276     | e1=expr mnot(IN) LPAREN select=select_stmt RPAREN
277       {
278         `Func (None,e1::select_value select)
279       }
280     | e1=expr IN table=IDENT { Tables.check(table); e1 }
281     | LPAREN select=select_stmt RPAREN
282       {
283         `Func (None,select_value select)
284       }
285     | PARAM { `Param ($1,None) }
286     | f=FUNCTION LPAREN p=func_params RPAREN { `Func (f,p) }
287     | expr TEST_NULL { $1 }
288     | expr mnot(BETWEEN) expr AND expr { `Func ((Some Int),[$1;$3;$5]) }
289     | mnot(EXISTS) LPAREN select=select_stmt RPAREN { `Func ((Some Bool),params_of select) }
291 datetime_value: | DATETIME_FUNC | DATETIME_FUNC LPAREN INTEGER RPAREN { `Value Datetime }
293 literal_value:
294     | TEXT { `Value Text }
295     | BLOB { `Value Blob }
296     | INTEGER { `Value Int }
297     | FLOAT { `Value Float }
298     | DATE TEXT
299     | TIME TEXT
300     | TIMESTAMP TEXT { `Value Datetime }
302 expr_list: separated_nonempty_list(COMMA,expr) { $1 }
303 func_params: expr_list { $1 }
304            | ASTERISK { [] }
305            | (* *) { [] }
306 escape: ESCAPE expr { $2 }
307 numeric_bin_op: PLUS | MINUS | ASTERISK | NUM_BINARY_OP { }
308 comparison_op: EQUAL | COMPARISON_OP { }
309 boolean_bin_op: AND | OR { }
311 unary_op: EXCL { }
312         | PLUS { }
313         | MINUS { }
314         | TILDE { }
315         | NOT { }
317 sql_type_flavor: T_INTEGER UNSIGNED? ZEROFILL? { Int }
318                | binary { Blob }
319                | NATIONAL? text VARYING? charset? collate? { Text }
320                | T_FLOAT PRECISION? { Float }
321                | T_BOOLEAN { Bool }
322                | T_DATETIME | DATE | TIME | TIMESTAMP { Datetime }
324 binary: T_BLOB | BINARY | BINARY VARYING { }
325 text: T_TEXT | CHARACTER { }
327 %inline either(X,Y): X | Y { }
328 (* (x1,x2,...,xn) *)
329 %inline sequence_(X): LPAREN l=separated_nonempty_list(COMMA,X) { l }
330 %inline sequence(X): l=sequence_(X) RPAREN { l }
332 charset: CHARSET either(IDENT,BINARY) | CHARACTER SET either(IDENT,BINARY) | ASCII | UNICODE { }
333 collate: COLLATE IDENT { }
335 sql_type: t=sql_type_flavor
336         | t=sql_type_flavor LPAREN INTEGER RPAREN
337         | t=sql_type_flavor LPAREN INTEGER COMMA INTEGER RPAREN
338         { t }
340 compound_op: UNION ALL? | EXCEPT | INTERSECT { }
342 maybe_join_type: JOIN_TYPE1? JOIN_TYPE2? { }