add a has_columns method
[dbsrgits/SQL-Translator-2.0-ish.git] / lib / SQL / Translator / Grammar / SQLite.pm
CommitLineData
c3734b46 1use MooseX::Declare;
2role SQL::Translator::Grammar::SQLite {
5ea69034 3
c3734b46 4method _build_grammar {
5return 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#
16startrule : statement(s) eofile {
17 $return = {
18 tables => \%tables,
19 views => \@views,
20 triggers => \@triggers,
21 }
5ea69034 22}
23
c3734b46 24eofile : /^\Z/
5ea69034 25
c3734b46 26statement : begin_transaction
27 | commit
28 | drop
29 | comment
30 | create
31 | <error>
5ea69034 32
c3734b46 33begin_transaction : /begin/i TRANSACTION(?) SEMICOLON
5ea69034 34
c3734b46 35commit : /commit/i SEMICOLON
5ea69034 36
c3734b46 37drop : /drop/i (tbl_drop | view_drop | trg_drop) SEMICOLON
5ea69034 38
c3734b46 39tbl_drop: TABLE <commit> table_name
5ea69034 40
c3734b46 41view_drop: VIEW if_exists(?) view_name
5ea69034 42
c3734b46 43trg_drop: TRIGGER if_exists(?) trigger_name
5ea69034 44
c3734b46 45comment : /^\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 53comment : /\/\*/ /[^\*]+/ /\*\//
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 63create : 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#
89create : 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 108definition : constraint_def | column_def
5ea69034 109
c3734b46 110column_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 151type : WORD parens_value_list(?)
152 {
153 $return = {
154 type => $item[1],
155 size => $item[2][0],
156 }
157 }
5ea69034 158
c3734b46 159column_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 216constraint_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 246ref_def : /(\w+)\s*\((\w+)\)/
247 { $return = { reference_table => $1, reference_fields => $2 } }
248
c3734b46 249table_name : qualified_name
250
251qualified_name : NAME
252 { $return = { name => $item[1] } }
5ea69034 253
c3734b46 254qualified_name : /(\w+)\.(\w+)/
255 { $return = { db_name => $1, name => $2 } }
5ea69034 256
c3734b46 257field_name : NAME
5ea69034 258
c3734b46 259conflict_clause : /on conflict/i conflict_algorigthm
5ea69034 260
c3734b46 261conflict_algorigthm : /(rollback|abort|fail|ignore|replace)/i
5ea69034 262
c3734b46 263parens_field_list : '(' column_list ')'
264 { $item[2] }
5ea69034 265
c3734b46 266column_list : field_name(s /,/)
5ea69034 267
c3734b46 268parens_value_list : '(' VALUE(s /,/) ')'
269 { $item[2] }
5ea69034 270
c3734b46 271expr : /[^)]+/
5ea69034 272
c3734b46 273sort_order : /(ASC|DESC)/i
5ea69034 274
c3734b46 275#
276# Create Trigger
5ea69034 277
c3734b46 278create : 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 292create : 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 306database_event : /(delete|insert|update)/i
5ea69034 307
c3734b46 308database_event : /update of/i column_list
5ea69034 309
c3734b46 310trigger_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 319for_each : /FOR EACH ROW/i
5ea69034 320
c3734b46 321when : WHEN expr { $item[2] }
5ea69034 322
c3734b46 323string :
324 /'(\\.|''|[^\\\'])*'/
5ea69034 325
c3734b46 326nonstring : /[^;\'"]+/
5ea69034 327
c3734b46 328statement_body : string | nonstring
5ea69034 329
c3734b46 330trigger_step : /(select|delete|insert|update)/i statement_body(s?) SEMICOLON
331 {
332 $return = join( ' ', $item[1], join ' ', @{ $item[2] || [] } )
333 }
5ea69034 334
c3734b46 335before_or_after : /(before|after)/i { $return = lc $1 }
5ea69034 336
c3734b46 337instead_of : /instead of/i
5ea69034 338
c3734b46 339if_exists : /if exists/i
5ea69034 340
c3734b46 341view_name : qualified_name
5ea69034 342
c3734b46 343trigger_name : qualified_name
5ea69034 344
c3734b46 345#
346# Create View
347#
348create : 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 357select_statement : SELECT /[^;]+/ SEMICOLON
358 {
359 $return = join( ' ', $item[1], $item[2] );
360 }
5ea69034 361
c3734b46 362#
363# Tokens
364#
365BEGIN_C : /begin/i
5ea69034 366
c3734b46 367END_C : /end/i
5ea69034 368
c3734b46 369TRANSACTION: /transaction/i
5ea69034 370
c3734b46 371CREATE : /create/i
5ea69034 372
c3734b46 373TEMPORARY : /temp(orary)?/i { 1 }
5ea69034 374
c3734b46 375TABLE : /table/i
5ea69034 376
c3734b46 377INDEX : /index/i
5ea69034 378
c3734b46 379NOT_NULL : /not null/i
5ea69034 380
c3734b46 381PRIMARY_KEY : /primary key/i
5ea69034 382
c3734b46 383CHECK_C : /check/i
5ea69034 384
c3734b46 385DEFAULT : /default/i
5ea69034 386
c3734b46 387TRIGGER : /trigger/i
5ea69034 388
c3734b46 389VIEW : /view/i
5ea69034 390
c3734b46 391SELECT : /select/i
5ea69034 392
c3734b46 393ON : /on/i
5ea69034 394
c3734b46 395AS : /as/i
5ea69034 396
c3734b46 397WORD : /\w+/
5ea69034 398
c3734b46 399WHEN : /when/i
5ea69034 400
feb8eab5 401REFERENCES : /references/i
402
403AUTOINCREMENT : /autoincrement/i
404
c3734b46 405UNIQUE : /unique/i { 1 }
5ea69034 406
c3734b46 407SEMICOLON : ';'
5ea69034 408
feb8eab5 409NAME : /["']?(\w+)["']?/ { $return = $1 }
5ea69034 410
c3734b46 411VALUE : /[-+]?\.?\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}