Commit | Line | Data |
c3734b46 |
1 | use MooseX::Declare; |
2 | role SQL::Translator::Grammar::SQLite { |
5ea69034 |
3 | |
c3734b46 |
4 | method _build_grammar { |
5 | return q! |
6 | { |
5ea69034 |
7 | my ( %tables, $table_order, @table_comments, @views, @triggers ); |
5ea69034 |
8 | } |
9 | |
c3734b46 |
10 | # |
11 | # The "eofile" rule makes the parser fail if any "statement" rule |
12 | # fails. Otherwise, the first successful match by a "statement" |
13 | # won't cause the failure needed to know that the parse, as a whole, |
14 | # failed. -ky |
15 | # |
16 | startrule : statement(s) eofile { |
17 | $return = { |
18 | tables => \%tables, |
19 | views => \@views, |
20 | triggers => \@triggers, |
21 | } |
5ea69034 |
22 | } |
23 | |
c3734b46 |
24 | eofile : /^\Z/ |
5ea69034 |
25 | |
c3734b46 |
26 | statement : begin_transaction |
27 | | commit |
28 | | drop |
29 | | comment |
30 | | create |
31 | | <error> |
5ea69034 |
32 | |
c3734b46 |
33 | begin_transaction : /begin/i TRANSACTION(?) SEMICOLON |
5ea69034 |
34 | |
c3734b46 |
35 | commit : /commit/i SEMICOLON |
5ea69034 |
36 | |
c3734b46 |
37 | drop : /drop/i (tbl_drop | view_drop | trg_drop) SEMICOLON |
5ea69034 |
38 | |
c3734b46 |
39 | tbl_drop: TABLE <commit> table_name |
5ea69034 |
40 | |
c3734b46 |
41 | view_drop: VIEW if_exists(?) view_name |
5ea69034 |
42 | |
c3734b46 |
43 | trg_drop: TRIGGER if_exists(?) trigger_name |
5ea69034 |
44 | |
c3734b46 |
45 | comment : /^\s*(?:#|-{2}).*\n/ |
46 | { |
47 | my $comment = $item[1]; |
48 | $comment =~ s/^\s*(#|-{2})\s*//; |
49 | $comment =~ s/\s*$//; |
50 | $return = $comment; |
51 | } |
5ea69034 |
52 | |
c3734b46 |
53 | comment : /\/\*/ /[^\*]+/ /\*\// |
54 | { |
55 | my $comment = $item[2]; |
56 | $comment =~ s/^\s*|\s*$//g; |
57 | $return = $comment; |
58 | } |
5ea69034 |
59 | |
c3734b46 |
60 | # |
61 | # Create Index |
62 | # |
feb8eab5 |
63 | create : CREATE TEMPORARY(?) UNIQUE(?) INDEX NAME ON table_name parens_field_list conflict_clause(?) SEMICOLON |
c3734b46 |
64 | { |
65 | my $db_name = $item[7]->{'db_name'} || ''; |
66 | my $table_name = $item[7]->{'name'}; |
5ea69034 |
67 | |
c3734b46 |
68 | my $index = { |
69 | name => $item[5], |
feb8eab5 |
70 | fields => $item[8], |
c3734b46 |
71 | on_conflict => $item[9][0], |
72 | is_temporary => $item[2][0] ? 1 : 0, |
73 | }; |
5ea69034 |
74 | |
c3734b46 |
75 | my $is_unique = $item[3][0]; |
5ea69034 |
76 | |
c3734b46 |
77 | if ( $is_unique ) { |
78 | $index->{'type'} = 'unique'; |
79 | push @{ $tables{ $table_name }{'constraints'} }, $index; |
80 | } |
81 | else { |
82 | push @{ $tables{ $table_name }{'indices'} }, $index; |
83 | } |
84 | } |
5ea69034 |
85 | |
c3734b46 |
86 | # |
87 | # Create Table |
88 | # |
89 | create : CREATE TEMPORARY(?) TABLE table_name '(' definition(s /,/) ')' SEMICOLON |
90 | { |
91 | my $db_name = $item[4]->{'db_name'} || ''; |
92 | my $table_name = $item[4]->{'name'}; |
5ea69034 |
93 | |
c3734b46 |
94 | $tables{ $table_name }{'name'} = $table_name; |
95 | $tables{ $table_name }{'is_temporary'} = $item[2][0] ? 1 : 0; |
96 | $tables{ $table_name }{'order'} = ++$table_order; |
5ea69034 |
97 | |
c3734b46 |
98 | for my $def ( @{ $item[6] } ) { |
99 | if ( $def->{'supertype'} eq 'column' ) { |
feb8eab5 |
100 | push @{ $tables{ $table_name }{'fields'} }, $def; |
c3734b46 |
101 | } |
102 | elsif ( $def->{'supertype'} eq 'constraint' ) { |
103 | push @{ $tables{ $table_name }{'constraints'} }, $def; |
104 | } |
105 | } |
106 | } |
5ea69034 |
107 | |
c3734b46 |
108 | definition : constraint_def | column_def |
5ea69034 |
109 | |
c3734b46 |
110 | column_def: comment(s?) NAME type(?) column_constraint(s?) |
111 | { |
5ea69034 |
112 | my $column = { |
113 | supertype => 'column', |
114 | name => $item[2], |
115 | data_type => $item[3][0]->{'type'}, |
116 | size => $item[3][0]->{'size'}, |
117 | is_nullable => 1, |
118 | is_primary_key => 0, |
119 | is_unique => 0, |
120 | check => '', |
121 | default => undef, |
122 | constraints => $item[4], |
123 | comments => $item[1], |
124 | }; |
125 | |
126 | |
127 | for my $c ( @{ $item[4] } ) { |
128 | if ( $c->{'type'} eq 'not_null' ) { |
129 | $column->{'is_nullable'} = 0; |
130 | } |
131 | elsif ( $c->{'type'} eq 'primary_key' ) { |
132 | $column->{'is_primary_key'} = 1; |
133 | } |
134 | elsif ( $c->{'type'} eq 'unique' ) { |
135 | $column->{'is_unique'} = 1; |
136 | } |
137 | elsif ( $c->{'type'} eq 'check' ) { |
138 | $column->{'check'} = $c->{'expression'}; |
139 | } |
140 | elsif ( $c->{'type'} eq 'default' ) { |
141 | $column->{'default'} = $c->{'value'}; |
142 | } |
feb8eab5 |
143 | elsif ( $c->{'type'} eq 'autoincrement' ) { |
144 | $column->{'is_auto_inc'} = 1; |
145 | } |
5ea69034 |
146 | } |
147 | |
148 | $column; |
c3734b46 |
149 | } |
5ea69034 |
150 | |
c3734b46 |
151 | type : WORD parens_value_list(?) |
152 | { |
153 | $return = { |
154 | type => $item[1], |
155 | size => $item[2][0], |
156 | } |
157 | } |
5ea69034 |
158 | |
c3734b46 |
159 | column_constraint : NOT_NULL conflict_clause(?) |
160 | { |
161 | $return = { |
162 | type => 'not_null', |
163 | } |
164 | } |
165 | | |
166 | PRIMARY_KEY sort_order(?) conflict_clause(?) |
167 | { |
168 | $return = { |
169 | type => 'primary_key', |
170 | sort_order => $item[2][0], |
171 | on_conflict => $item[2][0], |
172 | } |
173 | } |
174 | | |
175 | UNIQUE conflict_clause(?) |
176 | { |
177 | $return = { |
178 | type => 'unique', |
179 | on_conflict => $item[2][0], |
180 | } |
181 | } |
182 | | |
183 | CHECK_C '(' expr ')' conflict_clause(?) |
184 | { |
185 | $return = { |
186 | type => 'check', |
187 | expression => $item[3], |
188 | on_conflict => $item[5][0], |
189 | } |
190 | } |
191 | | |
192 | DEFAULT VALUE |
193 | { |
194 | $return = { |
195 | type => 'default', |
196 | value => $item[2], |
197 | } |
198 | } |
feb8eab5 |
199 | | |
200 | REFERENCES ref_def |
201 | { |
202 | $return = { |
203 | type => 'foreign_key', |
204 | reference_table => $item[2]{'reference_table'}, |
205 | reference_fields => $item[2]{'reference_fields'}, |
206 | } |
207 | } |
208 | | |
209 | AUTOINCREMENT |
210 | { |
211 | $return = { |
212 | type => 'autoincrement', |
213 | } |
214 | } |
5ea69034 |
215 | |
c3734b46 |
216 | constraint_def : PRIMARY_KEY parens_field_list conflict_clause(?) |
217 | { |
5ea69034 |
218 | $return = { |
219 | supertype => 'constraint', |
220 | type => 'primary_key', |
feb8eab5 |
221 | fields => $item[2], |
5ea69034 |
222 | on_conflict => $item[3][0], |
223 | } |
c3734b46 |
224 | } |
225 | | |
226 | UNIQUE parens_field_list conflict_clause(?) |
227 | { |
5ea69034 |
228 | $return = { |
229 | supertype => 'constraint', |
230 | type => 'unique', |
feb8eab5 |
231 | fields => $item[2], |
5ea69034 |
232 | on_conflict => $item[3][0], |
233 | } |
c3734b46 |
234 | } |
235 | | |
236 | CHECK_C '(' expr ')' conflict_clause(?) |
237 | { |
5ea69034 |
238 | $return = { |
239 | supertype => 'constraint', |
240 | type => 'check', |
241 | expression => $item[3], |
242 | on_conflict => $item[5][0], |
243 | } |
c3734b46 |
244 | } |
5ea69034 |
245 | |
feb8eab5 |
246 | ref_def : /(\w+)\s*\((\w+)\)/ |
247 | { $return = { reference_table => $1, reference_fields => $2 } } |
248 | |
c3734b46 |
249 | table_name : qualified_name |
250 | |
251 | qualified_name : NAME |
252 | { $return = { name => $item[1] } } |
5ea69034 |
253 | |
c3734b46 |
254 | qualified_name : /(\w+)\.(\w+)/ |
255 | { $return = { db_name => $1, name => $2 } } |
5ea69034 |
256 | |
c3734b46 |
257 | field_name : NAME |
5ea69034 |
258 | |
c3734b46 |
259 | conflict_clause : /on conflict/i conflict_algorigthm |
5ea69034 |
260 | |
c3734b46 |
261 | conflict_algorigthm : /(rollback|abort|fail|ignore|replace)/i |
5ea69034 |
262 | |
c3734b46 |
263 | parens_field_list : '(' column_list ')' |
264 | { $item[2] } |
5ea69034 |
265 | |
c3734b46 |
266 | column_list : field_name(s /,/) |
5ea69034 |
267 | |
c3734b46 |
268 | parens_value_list : '(' VALUE(s /,/) ')' |
269 | { $item[2] } |
5ea69034 |
270 | |
c3734b46 |
271 | expr : /[^)]+/ |
5ea69034 |
272 | |
c3734b46 |
273 | sort_order : /(ASC|DESC)/i |
5ea69034 |
274 | |
c3734b46 |
275 | # |
276 | # Create Trigger |
5ea69034 |
277 | |
c3734b46 |
278 | create : CREATE TEMPORARY(?) TRIGGER NAME before_or_after(?) database_event ON table_name trigger_action SEMICOLON |
279 | { |
280 | my $table_name = $item[8]->{'name'}; |
281 | push @triggers, { |
282 | name => $item[4], |
283 | is_temporary => $item[2][0] ? 1 : 0, |
284 | when => $item[5][0], |
285 | instead_of => 0, |
286 | db_events => [ $item[6] ], |
287 | action => $item[9], |
288 | on_table => $table_name, |
289 | } |
290 | } |
5ea69034 |
291 | |
c3734b46 |
292 | create : CREATE TEMPORARY(?) TRIGGER NAME instead_of database_event ON view_name trigger_action |
293 | { |
294 | my $table_name = $item[8]->{'name'}; |
295 | push @triggers, { |
296 | name => $item[4], |
297 | is_temporary => $item[2][0] ? 1 : 0, |
298 | when => undef, |
299 | instead_of => 1, |
300 | db_events => [ $item[6] ], |
301 | action => $item[9], |
302 | on_table => $table_name, |
303 | } |
304 | } |
5ea69034 |
305 | |
c3734b46 |
306 | database_event : /(delete|insert|update)/i |
5ea69034 |
307 | |
c3734b46 |
308 | database_event : /update of/i column_list |
5ea69034 |
309 | |
c3734b46 |
310 | trigger_action : for_each(?) when(?) BEGIN_C trigger_step(s) END_C |
311 | { |
312 | $return = { |
313 | for_each => $item[1][0], |
314 | when => $item[2][0], |
315 | steps => $item[4], |
316 | } |
317 | } |
5ea69034 |
318 | |
c3734b46 |
319 | for_each : /FOR EACH ROW/i |
5ea69034 |
320 | |
c3734b46 |
321 | when : WHEN expr { $item[2] } |
5ea69034 |
322 | |
c3734b46 |
323 | string : |
324 | /'(\\.|''|[^\\\'])*'/ |
5ea69034 |
325 | |
c3734b46 |
326 | nonstring : /[^;\'"]+/ |
5ea69034 |
327 | |
c3734b46 |
328 | statement_body : string | nonstring |
5ea69034 |
329 | |
c3734b46 |
330 | trigger_step : /(select|delete|insert|update)/i statement_body(s?) SEMICOLON |
331 | { |
332 | $return = join( ' ', $item[1], join ' ', @{ $item[2] || [] } ) |
333 | } |
5ea69034 |
334 | |
c3734b46 |
335 | before_or_after : /(before|after)/i { $return = lc $1 } |
5ea69034 |
336 | |
c3734b46 |
337 | instead_of : /instead of/i |
5ea69034 |
338 | |
c3734b46 |
339 | if_exists : /if exists/i |
5ea69034 |
340 | |
c3734b46 |
341 | view_name : qualified_name |
5ea69034 |
342 | |
c3734b46 |
343 | trigger_name : qualified_name |
5ea69034 |
344 | |
c3734b46 |
345 | # |
346 | # Create View |
347 | # |
348 | create : CREATE TEMPORARY(?) VIEW view_name AS select_statement |
349 | { |
350 | push @views, { |
351 | name => $item[4]->{'name'}, |
352 | sql => $item[6], |
353 | is_temporary => $item[2][0] ? 1 : 0, |
354 | } |
355 | } |
5ea69034 |
356 | |
c3734b46 |
357 | select_statement : SELECT /[^;]+/ SEMICOLON |
358 | { |
359 | $return = join( ' ', $item[1], $item[2] ); |
360 | } |
5ea69034 |
361 | |
c3734b46 |
362 | # |
363 | # Tokens |
364 | # |
365 | BEGIN_C : /begin/i |
5ea69034 |
366 | |
c3734b46 |
367 | END_C : /end/i |
5ea69034 |
368 | |
c3734b46 |
369 | TRANSACTION: /transaction/i |
5ea69034 |
370 | |
c3734b46 |
371 | CREATE : /create/i |
5ea69034 |
372 | |
c3734b46 |
373 | TEMPORARY : /temp(orary)?/i { 1 } |
5ea69034 |
374 | |
c3734b46 |
375 | TABLE : /table/i |
5ea69034 |
376 | |
c3734b46 |
377 | INDEX : /index/i |
5ea69034 |
378 | |
c3734b46 |
379 | NOT_NULL : /not null/i |
5ea69034 |
380 | |
c3734b46 |
381 | PRIMARY_KEY : /primary key/i |
5ea69034 |
382 | |
c3734b46 |
383 | CHECK_C : /check/i |
5ea69034 |
384 | |
c3734b46 |
385 | DEFAULT : /default/i |
5ea69034 |
386 | |
c3734b46 |
387 | TRIGGER : /trigger/i |
5ea69034 |
388 | |
c3734b46 |
389 | VIEW : /view/i |
5ea69034 |
390 | |
c3734b46 |
391 | SELECT : /select/i |
5ea69034 |
392 | |
c3734b46 |
393 | ON : /on/i |
5ea69034 |
394 | |
c3734b46 |
395 | AS : /as/i |
5ea69034 |
396 | |
c3734b46 |
397 | WORD : /\w+/ |
5ea69034 |
398 | |
c3734b46 |
399 | WHEN : /when/i |
5ea69034 |
400 | |
feb8eab5 |
401 | REFERENCES : /references/i |
402 | |
403 | AUTOINCREMENT : /autoincrement/i |
404 | |
c3734b46 |
405 | UNIQUE : /unique/i { 1 } |
5ea69034 |
406 | |
c3734b46 |
407 | SEMICOLON : ';' |
5ea69034 |
408 | |
feb8eab5 |
409 | NAME : /["']?(\w+)["']?/ { $return = $1 } |
5ea69034 |
410 | |
c3734b46 |
411 | VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/ |
412 | { $item[1] } |
413 | | /'.*?'/ |
414 | { |
5ea69034 |
415 | # remove leading/trailing quotes |
416 | my $val = $item[1]; |
417 | $val =~ s/^['"]|['"]$//g; |
418 | $return = $val; |
c3734b46 |
419 | } |
420 | | /NULL/ |
421 | { 'NULL' } |
422 | | /CURRENT_TIMESTAMP/i |
423 | { 'CURRENT_TIMESTAMP' } |
424 | !; |
5ea69034 |
425 | } |
5ea69034 |
426 | } |