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