File Coverage

lib/Parse/Dia/SQL/Output/SQLite3.pm
Criterion Covered Total %
statement 18 18 100.0
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 24 24 100.0


line stmt bran cond sub pod time code
1             package Parse::Dia::SQL::Output::SQLite3;
2              
3             # $Id: SQLite3.pm,v 1.5 2009/05/14 09:42:47 aff Exp $
4              
5             =pod
6              
7             =head1 NAME
8              
9             Parse::Dia::SQL::Output::SQLite3 - Create SQL for SQLite version 3.
10              
11             =head1 SYNOPSIS
12              
13             use Parse::Dia::SQL;
14             my $dia = Parse::Dia::SQL->new(file => 'foo.dia', db => 'sqlite3');
15             print $dia->get_sql();
16              
17             =head1 DESCRIPTION
18              
19             This sub-class creates SQL for the SQLite database version 3.
20              
21             =cut
22              
23 3     3   3435 use warnings;
  3         4  
  3         75  
24 3     3   9 use strict;
  3         4  
  3         39  
25              
26 3     3   9 use Data::Dumper;
  3         2  
  3         107  
27 3     3   10 use File::Spec::Functions qw(catfile);
  3         21  
  3         134  
28              
29 3     3   9 use lib q{lib};
  3         28  
  3         14  
30 3     3   191 use base q{Parse::Dia::SQL::Output}; # extends
  3         3  
  3         249  
31              
32             require Parse::Dia::SQL::Logger;
33             require Parse::Dia::SQL::Const;
34              
35             =head2 new
36              
37             The constructor.
38              
39             Object names in SQLite have no inherent limit. 60 has been arbitrarily chosen.
40              
41             =cut
42              
43             sub new {
44             my ( $class, %param ) = @_;
45             my $self = {};
46              
47             # Set defaults for sqlite
48             $param{db} = q{sqlite3};
49             $param{object_name_max_length} = $param{object_name_max_length} || 60;
50              
51             $self = $class->SUPER::new( %param );
52             bless( $self, $class );
53              
54             return $self;
55             }
56              
57             =head2 _get_create_table_sql
58              
59             Generate create table statement for a single table using SQLite
60             syntax:
61              
62             Includes class comments before the table definition.
63              
64             Includes autoupdate triggers based on the class comment.
65              
66             =head3 autoupdate triggers
67              
68             If the class comment includes a line like:
69              
70             />
71              
72             Then an 'after update' trigger is generated for this table which
73             executes the statement I for the updated row.
74              
75             Examples of use include tracking record modification dates
76             (C<>) or deriving a value from
77             another field (C<>)
78              
79             =cut
80              
81             sub _get_create_table_sql {
82              
83             my ( $self, $table ) = @_;
84             my $sqlstr = '';
85             my $temp;
86             my $comment;
87             my $tablename;
88             my $trigger = '';
89             my $update;
90             my $primary_keys = '';
91              
92             # include the comments before the table creation
93             $comment = $table->{comment};
94             if ( !defined( $comment ) ) { $comment = ''; }
95             $tablename = $table->{name};
96             $sqlstr .= $self->{newline};
97             if ( $comment ne "" ) {
98             $temp = "-- $comment";
99             $temp =~ s/\n/\n-- /g;
100             $temp =~ s/^-- $//mgi;
101             if ( $temp ne "" ) {
102             if ( $temp !~ /\n$/m ) { $temp .= $self->{newline}; }
103             $sqlstr .= $temp;
104             }
105             }
106              
107             # Call the base class to generate the main create table statements
108             $sqlstr .= $self->SUPER::_get_create_table_sql( $table );
109              
110             # Generate update triggers if required
111             if ( $comment =~ //mi ) {
112             $update = $3; # what we will set it to
113             $trigger = $2; # the trigger suffix to use (optional)
114             $trigger = $tablename . "_autoupdate" . $trigger;
115              
116             # Check that the column exists
117             foreach $temp ( @{ $table->{attList} } ) {
118              
119             # build the two primary key elements
120             if ( $$temp[3] == 2 ) {
121             if ( $primary_keys ) { $primary_keys .= " and "; }
122             $primary_keys .= $$temp[0] . "=OLD." . $$temp[0];
123             }
124             }
125              
126             $sqlstr .=
127             "drop trigger if exists $trigger"
128             . $self->{end_of_statement}
129             . $self->{newline};
130              
131             $sqlstr .=
132             "create trigger $trigger after update on $tablename begin update $tablename set $update where $primary_keys;end"
133             . $self->{end_of_statement}
134             . $self->{newline};
135              
136             $sqlstr .= $self->{newline};
137             }
138              
139             return $sqlstr;
140             }
141              
142             =head2 get_schema_drop
143              
144             Generate drop table statments for all tables using SQLite syntax:
145              
146             drop table {foo} if exists
147              
148             =cut
149              
150             sub get_schema_drop {
151             my $self = shift;
152             my $sqlstr = '';
153              
154             return unless $self->_check_classes();
155              
156             CLASS:
157             foreach my $object ( @{ $self->{classes} } ) {
158             next CLASS if ( $object->{type} ne q{table} );
159              
160             # Sanity checks on internal state
161             if (!defined( $object )
162             || ref( $object ) ne q{HASH}
163             || !exists( $object->{name} ) )
164             {
165             $self->{log}
166             ->error( q{Error in table input - cannot create drop table sql!} );
167             next;
168             }
169              
170             $sqlstr .=
171             qq{drop table if exists }
172             . $object->{name}
173             . $self->{end_of_statement}
174             . $self->{newline};
175             }
176              
177             return $sqlstr;
178             }
179              
180             =head2 get_view_drop
181              
182             Generate drop view statments for all tables using SQLite syntax:
183              
184             drop view {foo} if exists
185              
186             =cut
187              
188             # Create drop view for all views
189             sub get_view_drop {
190             my $self = shift;
191             my $sqlstr = '';
192              
193             return unless $self->_check_classes();
194              
195             CLASS:
196             foreach my $object ( @{ $self->{classes} } ) {
197             next CLASS if ( $object->{type} ne q{view} );
198              
199             # Sanity checks on internal state
200             if (!defined( $object )
201             || ref( $object ) ne q{HASH}
202             || !exists( $object->{name} ) )
203             {
204             $self->{log}
205             ->error( q{Error in table input - cannot create drop table sql!} );
206             next;
207             }
208              
209             $sqlstr .=
210             qq{drop view if exists }
211             . $object->{name}
212             . $self->{end_of_statement}
213             . $self->{newline};
214             }
215              
216             return $sqlstr;
217              
218             }
219              
220             =head2 _get_fk_drop
221              
222             Drop foreign key enforcement triggers using SQLite syntax:
223              
224             drop trigger {foo} if exists
225            
226             The automatically generated foreign key enforcement triggers are:
227              
228             See L<"_get_create_association_sql"> for more details.
229              
230             =over
231              
232             =item I_bi_tr
233              
234             =item I_bu_tr
235              
236             =item I_buparent_tr
237              
238             =item I_bdparent_tr
239              
240             =back
241              
242             =cut
243              
244             # Drop all foreign keys
245             sub _get_fk_drop {
246             my $self = shift;
247             my $sqlstr = '';
248             my $temp;
249              
250             return unless $self->_check_associations();
251              
252             # drop fk
253             foreach my $association ( @{ $self->{associations} } ) {
254             my ( $table_name, $constraint_name, undef, undef, undef, undef ) =
255             @{$association};
256              
257             $temp = $constraint_name . "_bi_tr";
258             $sqlstr .=
259             qq{drop trigger if exists $temp}
260             . $self->{end_of_statement}
261             . $self->{newline};
262              
263             $temp = $constraint_name . "_bu_tr";
264             $sqlstr .=
265             qq{drop trigger if exists $temp}
266             . $self->{end_of_statement}
267             . $self->{newline};
268              
269             $temp = $constraint_name . "_buparent_tr";
270             $sqlstr .=
271             qq{drop trigger if exists $temp}
272             . $self->{end_of_statement}
273             . $self->{newline};
274              
275             $temp = $constraint_name . "_bdparent_tr";
276             $sqlstr .=
277             qq{drop trigger if exists $temp}
278             . $self->{end_of_statement}
279             . $self->{newline};
280              
281             $sqlstr .= $self->{newline};
282              
283             }
284             return $sqlstr;
285             }
286              
287             =head2 _get_drop_index_sql
288              
289             drop index statement using SQLite syntax:
290              
291             drop index {foo} if exists
292              
293             =cut
294              
295             sub _get_drop_index_sql {
296             my ( $self, $tablename, $indexname ) = @_;
297             return
298             qq{drop index if exists $indexname}
299             . $self->{end_of_statement}
300             . $self->{newline};
301             }
302              
303             =head2 get_permissions_create
304              
305             SQLite doesn't support permissions, so suppress this output.
306              
307             =cut
308              
309             sub get_permissions_create {
310             return '';
311             }
312              
313             =head2 get_permissions_drop
314              
315             SQLite doesn't support permissions, so suppress this output.
316              
317             =cut
318              
319             sub get_permissions_drop {
320             return '';
321             }
322              
323             =head2 _get_create_association_sql
324              
325             Create the foreign key enforcement triggers using SQLite syntax:
326              
327             create trigger {fkname}[_bi_tr|_bu_tr|_bdparent_tr|_buparent_tr]
328              
329             Because SQLite doesn't natively enforce foreign key constraints (see L),
330             we use triggers to emulate this behaviour.
331              
332             The trigger names are the default contraint name (something like I_fk_I) with suffixes described below.
333              
334             =over
335              
336             =item I<{constraint_name}> is the name of the association, either specified or generated.
337              
338             =item I<{child_table}> is the name of the dependent or child table.
339              
340             =item I<{child_fkcolumn}> is the field in the dependent table that hold the foreign key.
341              
342             =item I<{parent_table}> is the name of the parent table.
343              
344             =item I<{parent_key}> is the key field of the parent table.
345              
346             =back
347              
348             =head3 Before insert - Dependent Table
349              
350             I_bi_tr
351              
352             Before insert on the child table require that the parent key exists.
353              
354             create trigger {constraint_name}_bi_tr before insert on {child_table}
355             for each row
356             begin
357             select
358             raise(abort, 'insert on table {child_table} violates foreign key constraint {constraint_name}')
359             where new.{child_fkcolumn} is not null and (select {parent_key} from {parent_table} where {parent_key}=new.{child_fkcolumn}) is null;
360             end;
361              
362             =head3 Before update - Dependent Table
363              
364             I_bu_tr
365              
366             Before update on the child table require that the parent key exists.
367              
368             create trigger {constraint_name}_bu_tr before update on {table_name}
369             for each row
370             begin
371             select raise(abort, 'update on table {child_table} violates foreign key constraint {constraint_name}')
372             where new.{child_fkcolumn} is not null and (select {parent_key} from {parent_table} where {parent_key}=new.{child_fkcolumn}) is null;
373             end;
374              
375              
376             =head3 Before update - Parent Table
377              
378             I_buparent_tr
379              
380             Before update on the primary key of the parent table ensure that there are no dependent child records.
381             Note that cascading updates B.
382              
383             create trigger {constraint_name}_buparent_tr before update on {parent_table}
384             for each row when new.{parent_key} <> old.{parent_key}
385             begin
386             select raise(abort, 'update on table {parent_table} violates foreign key constraint {constraint_name} on {child_table}')
387             where (select {child_fkcolumn} from {child_table} where {child_fkcolumn}=old.{parent_key}) is not null;
388             end;
389              
390             =head3 Before delete - Parent Table
391              
392             I_bdparent_tr
393              
394             The default behaviour can be modified through the contraint (in the multiplicity field) of the association.
395              
396             =head4 Default (On Delete Restrict)
397              
398             Before delete on the parent table ensure that there are no dependent child records.
399              
400             create trigger {constraint_name}_bdparent_tr before delete on {parent_table}
401             for each row
402             begin
403             select raise(abort, 'delete on table {parent_table} violates foreign key constraint {constraint_name} on {child_table}')
404             where (select {child_fkcolumn} from {child_table} where {child_fkcolumn}=old.{parent_key}) is not null;
405             end;
406              
407             =head4 On Delete Cascade
408              
409             Before delete on the parent table delete all dependent child records.
410              
411             create trigger {constraint_name}_bdparent_tr before delete on {parent_table}
412             for each row
413             begin
414             delete from {child_table} where {child_table}.{child_fkcolumn}=old.{parent_key};
415             end;
416              
417             =head4 On Delete Set Null
418              
419             Before delete on the parent table set the foreign key field(s) in all dependent child records to NULL.
420              
421             create trigger {constraint_name}_bdparent_tr before delete on {parent_table}
422             for each row
423             begin
424             update {child_table} set {child_table}.{child_fkcolumn}=null where {child_table}.{child_fkcolumn}=old.{parent_key};
425             end;
426              
427             =cut
428              
429             # Create sql for given association.
430             sub _get_create_association_sql {
431             my ( $self, $association ) = @_;
432             my $sqlstr = '';
433             my $temp;
434              
435             # Sanity checks on input
436             if ( ref( $association ) ne 'ARRAY' ) {
437             $self->{log}
438             ->error( q{Error in association input - cannot create association sql!} );
439             return;
440             }
441              
442             # FK constraints are implemented as triggers in SQLite
443              
444             my (
445             $table_name, $constraint_name, $key_column,
446             $ref_table, $ref_column, $constraint_action
447             ) = @{$association};
448              
449             # Shorten constraint name, if necessary (DB2 only)
450             $constraint_name = $self->_create_constraint_name( $constraint_name );
451              
452             $temp = $constraint_name . "_bi_tr";
453             $sqlstr .=
454             qq{create trigger $temp before insert on $table_name for each row begin select raise(abort, 'insert on table $table_name violates foreign key constraint $constraint_name') where new.$key_column is not null and (select $ref_column from $ref_table where $ref_column=new.$key_column) is null;end}
455             . $self->{end_of_statement}
456             . $self->{newline};
457              
458             $temp = $constraint_name . "_bu_tr";
459             $sqlstr .=
460             qq{create trigger $temp before update on $table_name for each row begin select raise(abort, 'update on table $table_name violates foreign key constraint $constraint_name') where new.$key_column is not null and (select $ref_column from $ref_table where $ref_column=new.$key_column) is null;end}
461             . $self->{end_of_statement}
462             . $self->{newline};
463              
464             # note that the before delete triggers are on the parent ($ref_table)
465             $temp = $constraint_name . "_bdparent_tr";
466             if ( $constraint_action =~ /on delete cascade/i ) {
467             $sqlstr .=
468             qq{create trigger $temp before delete on $ref_table for each row begin delete from $table_name where $table_name.$key_column=old.$ref_column;end}
469             . $self->{end_of_statement}
470             . $self->{newline};
471             } elsif ( $constraint_action =~ /on delete set null/i ) {
472             $sqlstr .=
473             qq{create trigger $temp before delete on $ref_table for each row begin update $table_name set $key_column=null where $table_name.$key_column=old.$ref_column;end}
474             . $self->{end_of_statement}
475             . $self->{newline};
476             } else # default on delete restrict
477             {
478             $sqlstr .=
479             qq{create trigger $temp before delete on $ref_table for each row begin select raise(abort, 'delete on table $ref_table violates foreign key constraint $constraint_name on $table_name') where (select $key_column from $table_name where $key_column=old.$ref_column) is not null;end}
480             . $self->{end_of_statement}
481             . $self->{newline};
482             }
483              
484             # Cascade updates doesn't work, so we always restrict
485             $temp = $constraint_name . "_buparent_tr";
486             $sqlstr .=
487             qq{create trigger $temp before update on $ref_table for each row when new.$ref_column <> old.$ref_column begin select raise(abort, 'update on table $ref_table violates foreign key constraint $constraint_name on $table_name') where (select $key_column from $table_name where $key_column=old.$ref_column) is not null;end}
488             . $self->{end_of_statement}
489             . $self->{newline};
490              
491             $sqlstr .= $self->{newline};
492              
493             return $sqlstr;
494             }
495              
496             1;
497              
498             =head1 TODO
499              
500             Things that might get added in future versions:
501              
502             =head3 Mandatory constraints
503              
504             The current foreign key triggers allow NULL in the child table. This might use a keyword in the
505             multiplicity field (perhaps 'required') or could check the 'not null' state of the child fkcolumn.
506              
507             =head3 Views
508              
509             Views haven't been tested. They might already work, but who knows...
510              
511             =head3 Other stuff
512              
513             Bugs etc
514              
515             =cut
516              
517             __END__