File Coverage

blib/lib/SQL/Translator/Producer/SQLite.pm
Criterion Covered Total %
statement 221 232 95.2
branch 70 94 74.4
condition 31 51 60.7
subroutine 26 27 96.3
pod 0 20 0.0
total 348 424 82.0


line stmt bran cond sub pod time code
1             package SQL::Translator::Producer::SQLite;
2              
3             =head1 NAME
4              
5             SQL::Translator::Producer::SQLite - SQLite producer for SQL::Translator
6              
7             =head1 SYNOPSIS
8              
9             use SQL::Translator;
10              
11             my $t = SQL::Translator->new( parser => '...', producer => 'SQLite' );
12             $t->translate;
13              
14             =head1 DESCRIPTION
15              
16             This module will produce text output of the schema suitable for SQLite.
17              
18             =cut
19              
20 10     10   6758 use strict;
  10         269  
  10         480  
21 10     10   62 use warnings;
  10         25  
  10         678  
22 10     10   1485 use Data::Dumper;
  10         22696  
  10         830  
23 10     10   179 use SQL::Translator::Schema::Constants;
  10         24  
  10         1039  
24 10     10   62 use SQL::Translator::Utils qw(debug header_comment parse_dbms_version batch_alter_table_statements);
  10         22  
  10         751  
25 10     10   5787 use SQL::Translator::Generator::DDL::SQLite;
  10         41  
  10         42389  
26              
27             our ($DEBUG, $WARN);
28             our $VERSION = '1.66';
29             $DEBUG = 0 unless defined $DEBUG;
30             $WARN = 0 unless defined $WARN;
31              
32             our $max_id_length = 30;
33             my %global_names;
34              
35             # HIDEOUS TEMPORARY DEFAULT WITHOUT QUOTING!
36             our $NO_QUOTES = 1;
37             {
38              
39             my ($quoting_generator, $nonquoting_generator);
40              
41             sub _generator {
42 389 100 66 389   4507 $NO_QUOTES
      66        
43             ? $nonquoting_generator ||= SQL::Translator::Generator::DDL::SQLite->new(quote_chars => [])
44             : $quoting_generator ||= SQL::Translator::Generator::DDL::SQLite->new;
45             }
46             }
47              
48             sub produce {
49 10     10 0 22 my $translator = shift;
50 10         42 local $DEBUG = $translator->debug;
51 10         265 local $WARN = $translator->show_warnings;
52 10         265 my $no_comments = $translator->no_comments;
53 10         215 my $add_drop_table = $translator->add_drop_table;
54 10         232 my $schema = $translator->schema;
55 10         252 my $producer_args = $translator->producer_args;
56 10         267 my $sqlite_version = parse_dbms_version($producer_args->{sqlite_version}, 'perl');
57 10         30 my $no_txn = $producer_args->{no_transaction};
58              
59 10         42 debug("PKG: Beginning production\n");
60              
61 10         34 %global_names = (); #reset
62              
63             # only quote if quotes were requested for real
64             # 0E0 indicates "the default of true" was assumed
65 10 100 66     242 local $NO_QUOTES = 0
66             if $translator->quote_identifiers
67             and $translator->quote_identifiers ne '0E0';
68              
69 10         87 my $head;
70 10 100       39 $head = (header_comment() . "\n") unless $no_comments;
71              
72 10         24 my @create = ();
73              
74 10 100       35 push @create, "BEGIN TRANSACTION" unless $no_txn;
75              
76 10         58 for my $table ($schema->get_tables) {
77 17         130 push @create,
78             create_table(
79             $table,
80             {
81             no_comments => $no_comments,
82             sqlite_version => $sqlite_version,
83             add_drop_table => $add_drop_table,
84             }
85             );
86             }
87              
88 10         75 for my $view ($schema->get_views) {
89 4         34 push @create,
90             create_view(
91             $view,
92             {
93             add_drop_view => $add_drop_table,
94             no_comments => $no_comments,
95             }
96             );
97             }
98              
99 10         52 for my $trigger ($schema->get_triggers) {
100 11         68 push @create,
101             create_trigger(
102             $trigger,
103             {
104             add_drop_trigger => $add_drop_table,
105             no_comments => $no_comments,
106             }
107             );
108             }
109              
110 10 100       47 push @create, "COMMIT" unless $no_txn;
111              
112 10 100       32 if (wantarray) {
113 3   33     45 return ($head || (), @create);
114             } else {
115 7   66     604 return join('', $head || (), join(";\n\n", @create), ";\n",);
116             }
117             }
118              
119             sub mk_name {
120 29     29 0 209 my ($name, $scope, $critical) = @_;
121              
122 29   50     168 $scope ||= \%global_names;
123 29 100       129 if (my $prev = $scope->{$name}) {
124 5         13 my $name_orig = $name;
125 5         31 $name .= sprintf("%02d", ++$prev);
126 5 50       22 substr($name, $max_id_length - 3) = "00"
127             if length($name) > $max_id_length;
128              
129 5 50       17 warn "The name '$name_orig' has been changed to ", "'$name' to make it unique.\n"
130             if $WARN;
131              
132 5         17 $scope->{$name_orig}++;
133             }
134              
135 29         91 $scope->{$name}++;
136 29         84 return _generator()->quote($name);
137             }
138              
139             sub create_view {
140 8     8 0 228 my ($view, $options) = @_;
141 8         24 my $add_drop_view = $options->{add_drop_view};
142              
143 8         35 my $view_name = _generator()->quote($view->name);
144 8         48 $global_names{ $view->name } = 1;
145              
146 8         52 debug("PKG: Looking at view '${view_name}'\n");
147              
148             # Header. Should this look like what mysqldump produces?
149 8         291 my $extra = $view->extra;
150 8         21 my @create;
151 8 100       35 push @create, "DROP VIEW IF EXISTS $view_name" if $add_drop_view;
152              
153 8         24 my $create_view = 'CREATE';
154             $create_view .= " TEMPORARY"
155 8 50 66     63 if exists($extra->{temporary}) && $extra->{temporary};
156 8         20 $create_view .= ' VIEW';
157             $create_view .= " IF NOT EXISTS"
158 8 50 66     36 if exists($extra->{if_not_exists}) && $extra->{if_not_exists};
159 8         24 $create_view .= " ${view_name}";
160              
161 8 50       65 if (my $sql = $view->sql) {
162 8         24 $create_view .= " AS\n ${sql}";
163             }
164 8         20 push @create, $create_view;
165              
166             # Tack the comment onto the first statement.
167 8 50       33 unless ($options->{no_comments}) {
168 0         0 $create[0] = "--\n-- View: ${view_name}\n--\n" . $create[0];
169             }
170              
171 8         51 return @create;
172             }
173              
174             sub create_table {
175 31     31 0 179 my ($table, $options) = @_;
176              
177 31         113 my $table_name = _generator()->quote($table->name);
178 31         802 $global_names{ $table->name } = 1;
179              
180 31         887 my $no_comments = $options->{no_comments};
181 31         98 my $add_drop_table = $options->{add_drop_table};
182 31   50     181 my $sqlite_version = $options->{sqlite_version} || 0;
183              
184 31         187 debug("PKG: Looking at table '$table_name'\n");
185              
186 31         85 my (@index_defs, @constraint_defs);
187 31 50       199 my @fields = $table->get_fields or die "No fields in $table_name";
188              
189 31 100       128 my $temp = $options->{temporary_table} ? 'TEMPORARY ' : '';
190             #
191             # Header.
192             #
193 31 50       125 my $exists = ($sqlite_version >= 3.003) ? ' IF EXISTS' : '';
194 31         64 my @create;
195 31         103 my ($comment, $create_table) = "";
196 31 100       92 $comment = "--\n-- Table: $table_name\n--\n" unless $no_comments;
197 31 100       85 if ($add_drop_table) {
198 12         80 push @create, $comment . qq[DROP TABLE$exists $table_name];
199             } else {
200 19         38 $create_table = $comment;
201             }
202              
203 31         119 $create_table .= "CREATE ${temp}TABLE $table_name (\n";
204              
205             #
206             # Comments
207             #
208 31 50 33     956 if ($table->comments and !$no_comments) {
209 0         0 $create_table .= "-- Comments: \n-- ";
210 0         0 $create_table .= join "\n-- ", $table->comments;
211 0         0 $create_table .= "\n--\n\n";
212             }
213              
214             #
215             # How many fields in PK?
216             #
217 31         137 my $pk = $table->primary_key;
218 31 100       800 my @pk_fields = $pk ? $pk->fields : ();
219              
220             #
221             # Fields
222             #
223 31         76 my (@field_defs, $pk_set);
224 31         99 for my $field (@fields) {
225 109         340 push @field_defs, create_field($field);
226             }
227              
228 31 100 66     368 if (scalar @pk_fields > 1
      66        
229             || (@pk_fields && !grep /INTEGER PRIMARY KEY/, @field_defs)) {
230 2         5 push @field_defs, 'PRIMARY KEY (' . join(', ', map _generator()->quote($_), @pk_fields) . ')';
231             }
232              
233             #
234             # Indices
235             #
236 31         191 for my $index ($table->get_indices) {
237 10         120 push @index_defs, create_index($index);
238             }
239              
240             #
241             # Constraints
242             #
243 31         140 for my $c ($table->get_constraints) {
244 43 100       1588 if ($c->type eq "FOREIGN KEY") {
    50          
245 10         249 push @field_defs, create_foreignkey($c);
246             } elsif ($c->type eq "CHECK") {
247 0         0 push @field_defs, create_check_constraint($c);
248             }
249 43 100       1837 next unless $c->type eq UNIQUE;
250 11         273 push @constraint_defs, create_constraint($c);
251             }
252              
253 31         546 $create_table .= join(",\n", map {" $_"} @field_defs) . "\n)";
  121         395  
254              
255 31         291 return (@create, $create_table, @index_defs, @constraint_defs);
256             }
257              
258             sub create_check_constraint {
259 1     1 0 8 my $c = shift;
260 1         3 my $check = '';
261 1 50       30 $check .= 'CONSTRAINT ' . _generator->quote($c->name) . ' ' if $c->name;
262 1         31 $check .= 'CHECK(' . $c->expression . ')';
263 1         6 return $check;
264             }
265              
266             sub create_foreignkey {
267 11     11 0 61 my $c = shift;
268              
269 11         53 my @fields = $c->fields;
270 11 50       348 my @rfields = map { $_ || () } $c->reference_fields;
  11         59  
271 11 50       139 unless (@rfields) {
272 0         0 my $rtable_name = $c->reference_table;
273 0 0       0 if (my $ref_table = $c->schema->get_table($rtable_name)) {
274 0         0 push @rfields, $ref_table->primary_key;
275              
276 0 0       0 die "FK constraint on " . $rtable_name . '.' . join('', @fields) . " has no reference fields\n"
277             unless @rfields;
278             } else {
279 0         0 die "Can't find reference table '$rtable_name' in schema\n";
280             }
281             }
282              
283             my $fk_sql = sprintf 'FOREIGN KEY (%s) REFERENCES %s(%s)',
284 11         39 join(', ', map { _generator()->quote($_) } @fields),
285             _generator()->quote($c->reference_table),
286 11         33 join(', ', map { _generator()->quote($_) } @rfields);
  11         38  
287              
288 11 100       66 $fk_sql .= " ON DELETE " . $c->{on_delete} if $c->{on_delete};
289 11 100       60 $fk_sql .= " ON UPDATE " . $c->{on_update} if $c->{on_update};
290              
291 11         63 return $fk_sql;
292             }
293              
294 112     112 0 315 sub create_field { return _generator()->field($_[0]) }
295              
296             sub create_index {
297 14     14 0 55 my ($index, $options) = @_;
298              
299 14         415 (my $index_table_name = $index->table->name) =~ s/^.+?\.//; # table name may not specify schema
300 14   66     710 my $name = mk_name($index->name || "${index_table_name}_idx");
301              
302 14 100       418 my $type = $index->type eq 'UNIQUE' ? "UNIQUE " : '';
303              
304             # strip any field size qualifiers as SQLite doesn't like these
305 14         776 my @fields = map { s/\(\d+\)$//; _generator()->quote($_) } $index->fields;
  14         307  
  14         56  
306 14         46 $index_table_name = _generator()->quote($index_table_name);
307 14 50       88 warn "removing schema name from '" . $index->table->name . "' to make '$index_table_name'\n"
308             if $WARN;
309 14         69 my $index_def = "CREATE ${type}INDEX $name ON " . $index_table_name . ' (' . join(', ', @fields) . ')';
310              
311 14         69 return $index_def;
312             }
313              
314             sub create_constraint {
315 15     15 0 75 my ($c, $options) = @_;
316              
317 15         443 (my $index_table_name = $c->table->name) =~ s/^.+?\.//; # table name may not specify schema
318 15   66     733 my $name = mk_name($c->name || "${index_table_name}_idx");
319 15         245 my @fields = map _generator()->quote($_), $c->fields;
320 15         76 $index_table_name = _generator()->quote($index_table_name);
321 15 50       61 warn "removing schema name from '" . $c->table->name . "' to make '$index_table_name'\n"
322             if $WARN;
323              
324 15         79 my $c_def = "CREATE UNIQUE INDEX $name ON " . $index_table_name . ' (' . join(', ', @fields) . ')';
325              
326 15         233 return $c_def;
327             }
328              
329             sub create_trigger {
330 13     13 0 48 my ($trigger, $options) = @_;
331 13         32 my $add_drop = $options->{add_drop_trigger};
332              
333 13         30 my @statements;
334              
335 13         55 my $trigger_name = $trigger->name;
336 13         53 $global_names{$trigger_name} = 1;
337              
338 13         414 my $events = $trigger->database_events;
339 13         129 for my $evt (@$events) {
340              
341 16         36 my $trig_name = $trigger_name;
342 16 100       57 if (@$events > 1) {
343 6         20 $trig_name .= "_$evt";
344              
345 6 50       21 warn
346             "Multiple database events supplied for trigger '$trigger_name', ",
347             "creating trigger '$trig_name' for the '$evt' event.\n"
348             if $WARN;
349             }
350              
351 16         53 $trig_name = _generator()->quote($trig_name);
352 16 100       75 push @statements, "DROP TRIGGER IF EXISTS $trig_name" if $add_drop;
353              
354 16         55 $DB::single = 1;
355 16         33 my $action = "";
356 16 100       80 if (not ref $trigger->action) {
357 12         46 $action = $trigger->action;
358 12 100       104 $action = "BEGIN " . $action . " END"
359             unless $action =~ /^ \s* BEGIN [\s\;] .*? [\s\;] END [\s\;]* $/six;
360             } else {
361             $action = $trigger->action->{for_each} . " "
362 4 50       27 if $trigger->action->{for_each};
363              
364             $action = $trigger->action->{when} . " "
365 4 50       21 if $trigger->action->{when};
366              
367 4   50     40 my $steps = $trigger->action->{steps} || [];
368              
369 4         8 $action .= "BEGIN ";
370 4         19 $action .= $_ . "; " for (@$steps);
371 4         9 $action .= "END";
372             }
373              
374 16         478 push @statements,
375             sprintf(
376             'CREATE TRIGGER %s %s %s on %s %s',
377             $trig_name, $trigger->perform_action_when,
378             $evt, _generator()->quote($trigger->on_table), $action
379             );
380             }
381              
382 13         108 return @statements;
383             }
384              
385 1     1 0 24 sub alter_table { () } # Noop
386              
387             sub add_field {
388 3     3 0 14 my ($field) = @_;
389              
390 3         6 return sprintf("ALTER TABLE %s ADD COLUMN %s", _generator()->quote($field->table->name), create_field($field));
391             }
392              
393             sub alter_create_index {
394 1     1 0 7 my ($index) = @_;
395              
396             # This might cause name collisions
397 1         4 return create_index($index);
398             }
399              
400             sub alter_create_constraint {
401 3     3 0 20 my ($constraint) = @_;
402              
403 3 100       39 return create_constraint($constraint) if $constraint->type eq 'UNIQUE';
404             }
405              
406 2     2 0 27 sub alter_drop_constraint { alter_drop_index(@_) }
407              
408             sub alter_drop_index {
409 3     3 0 11 my ($constraint) = @_;
410              
411 3         11 return sprintf("DROP INDEX %s", _generator()->quote($constraint->name));
412             }
413              
414             sub batch_alter_table {
415 6     6 0 215 my ($table, $diffs, $options) = @_;
416              
417             # If we have any of the following
418             #
419             # rename_field
420             # alter_field
421             # drop_field
422             #
423             # we need to do the following
424             #
425             # BEGIN TRANSACTION;
426             # CREATE TEMPORARY TABLE t1_backup(a,b);
427             # INSERT INTO t1_backup SELECT a,b FROM t1;
428             # DROP TABLE t1;
429             # CREATE TABLE t1(a,b);
430             # INSERT INTO t1 SELECT a,b FROM t1_backup;
431             # DROP TABLE t1_backup;
432             # COMMIT;
433             #
434             # Fun, eh?
435             #
436             # If we have rename_field we do similarly.
437             #
438             # We create the temporary table as a copy of the new table, copy all data
439             # to temp table, create new table and then copy as appropriate taking note
440             # of renamed fields.
441              
442 6         117 my $table_name = $table->name;
443              
444 6 100 66     108 if ( @{ $diffs->{rename_field} } == 0
  6   100     27  
445 4         18 && @{ $diffs->{alter_field} } == 0
446 4         14 && @{ $diffs->{drop_field} } == 0) {
447 2         7 return batch_alter_table_statements($diffs, $options);
448             }
449              
450 4         9 my @sql;
451              
452             # $table is the new table but we may need an old one
453             # TODO: this is NOT very well tested at the moment so add more tests
454              
455 4         9 my $old_table = $table;
456              
457 4 50 33     19 if ($diffs->{rename_table} && @{ $diffs->{rename_table} }) {
  4         17  
458 0         0 $old_table = $diffs->{rename_table}[0][0];
459             }
460              
461 4         11 my $temp_table_name = $table_name . '_temp_alter';
462              
463             # CREATE TEMPORARY TABLE t1_backup(a,b);
464              
465 4         9 my %temp_table_fields;
466 4         8 do {
467 4         13 local $table->{name} = $temp_table_name;
468              
469             # We only want the table - don't care about indexes on tmp table
470 4         39 my ($table_sql)
471             = create_table($table, { no_comments => 1, temporary_table => 1 });
472 4         16 push @sql, $table_sql;
473              
474 4         24 %temp_table_fields = map { $_ => 1 } $table->get_fields;
  16         35  
475             };
476              
477             # record renamed fields for later
478 4         84 my %rename_field = map { $_->[1]->name => $_->[0]->name } @{ $diffs->{rename_field} };
  2         43  
  4         32  
479              
480             # drop added fields from %temp_table_fields
481 4         46 delete @temp_table_fields{ @{ $diffs->{add_field} } };
  4         18  
482              
483             # INSERT INTO t1_backup SELECT a,b FROM t1;
484              
485             push @sql, sprintf(
486             'INSERT INTO %s( %s) SELECT %s FROM %s',
487              
488             _generator()->quote($temp_table_name),
489              
490 16         233 join(', ', map _generator()->quote($_), grep { $temp_table_fields{$_} } $table->get_fields),
491              
492             join(', ',
493             map _generator()->quote($_),
494 13 100       358 map { $rename_field{$_} ? $rename_field{$_} : $_ }
495 4         49 grep { $temp_table_fields{$_} } $table->get_fields),
  16         349  
496              
497             _generator()->quote($old_table->name)
498             );
499              
500             # DROP TABLE t1;
501              
502 4         28 push @sql, sprintf('DROP TABLE %s', _generator()->quote($old_table->name));
503              
504             # CREATE TABLE t1(a,b);
505              
506 4         25 push @sql, create_table($table, { no_comments => 1 });
507              
508             # INSERT INTO t1 SELECT a,b FROM t1_backup;
509              
510 4         21 push @sql,
511             sprintf(
512             'INSERT INTO %s SELECT %s FROM %s',
513             _generator()->quote($table_name),
514             join(', ', map _generator()->quote($_), $table->get_fields),
515             _generator()->quote($temp_table_name)
516             );
517              
518             # DROP TABLE t1_backup;
519              
520 4         26 push @sql, sprintf('DROP TABLE %s', _generator()->quote($temp_table_name));
521              
522 4 50       104 return wantarray ? @sql : join(";\n", @sql);
523             }
524              
525             sub drop_table {
526 2     2 0 13 my ($table) = @_;
527 2         4 $table = _generator()->quote($table);
528 2         9 return "DROP TABLE $table";
529             }
530              
531             sub rename_table {
532 2     2 0 14 my ($old_table, $new_table, $options) = @_;
533              
534 2         5 $old_table = _generator()->quote($old_table);
535 2         5 $new_table = _generator()->quote($new_table);
536              
537 2         9 return "ALTER TABLE $old_table RENAME TO $new_table";
538              
539             }
540              
541             # No-op. Just here to signify that we are a new style parser.
542       0 0   sub preproces_schema { }
543              
544             1;
545              
546             =pod
547              
548             =head1 SEE ALSO
549              
550             SQL::Translator, http://www.sqlite.org/.
551              
552             =head1 AUTHOR
553              
554             Ken Youens-Clark C<< >>.
555              
556             Diff code added by Ash Berlin C<< >>.
557              
558             =cut