File Coverage

blib/lib/DBD/SQLcipher.pm
Criterion Covered Total %
statement 20 314 6.3
branch 0 208 0.0
condition 0 137 0.0
subroutine 7 27 25.9
pod n/a
total 27 686 3.9


line stmt bran cond sub pod time code
1             package DBD::SQLcipher;
2              
3 105     105   6340692 use 5.006;
  105         1181  
4 105     105   499 use strict;
  105         168  
  105         2439  
5 105     105   1416 use DBI 1.57 ();
  105         14730  
  105         1585  
6 105     105   477 use DynaLoader ();
  105         165  
  105         7455  
7              
8             our $VERSION = "0.03_0010";
9             our @ISA = 'DynaLoader';
10              
11             # sqlite_version cache (set in the XS bootstrap)
12             our ($sqlite_version, $sqlite_version_number);
13              
14             # not sure if we still need these...
15             our ($err, $errstr);
16              
17             __PACKAGE__->bootstrap($VERSION);
18              
19             # New or old API?
20 105     105   536 use constant NEWAPI => ($DBI::VERSION >= 1.608);
  105         184  
  105         13156  
21              
22             # global registry of collation functions, initialized with 2 builtins
23             our %COLLATION;
24             tie %COLLATION, 'DBD::SQLcipher::_WriteOnceHash';
25             $COLLATION{perl} = sub { $_[0] cmp $_[1] };
26 105     105   26860 $COLLATION{perllocale} = sub { use locale; $_[0] cmp $_[1] };
  105         48119  
  105         490  
27              
28             our $drh;
29             my $methods_are_installed = 0;
30              
31             sub driver {
32 0 0   0     return $drh if $drh;
33              
34 0 0 0       if (!$methods_are_installed && DBD::SQLcipher::NEWAPI ) {
35 0           DBI->setup_driver('DBD::SQLcipher');
36              
37 0           DBD::SQLcipher::db->install_method('sqlite_last_insert_rowid');
38 0           DBD::SQLcipher::db->install_method('sqlite_busy_timeout');
39 0           DBD::SQLcipher::db->install_method('sqlite_create_function');
40 0           DBD::SQLcipher::db->install_method('sqlite_create_aggregate');
41 0           DBD::SQLcipher::db->install_method('sqlite_create_collation');
42 0           DBD::SQLcipher::db->install_method('sqlite_collation_needed');
43 0           DBD::SQLcipher::db->install_method('sqlite_progress_handler');
44 0           DBD::SQLcipher::db->install_method('sqlite_commit_hook');
45 0           DBD::SQLcipher::db->install_method('sqlite_rollback_hook');
46 0           DBD::SQLcipher::db->install_method('sqlite_update_hook');
47 0           DBD::SQLcipher::db->install_method('sqlite_set_authorizer');
48 0           DBD::SQLcipher::db->install_method('sqlite_backup_from_file');
49 0           DBD::SQLcipher::db->install_method('sqlite_backup_to_file');
50 0           DBD::SQLcipher::db->install_method('sqlite_enable_load_extension');
51 0           DBD::SQLcipher::db->install_method('sqlite_load_extension');
52 0           DBD::SQLcipher::db->install_method('sqlite_register_fts3_perl_tokenizer');
53 0           DBD::SQLcipher::db->install_method('sqlite_trace', { O => 0x0004 });
54 0           DBD::SQLcipher::db->install_method('sqlite_profile', { O => 0x0004 });
55 0           DBD::SQLcipher::db->install_method('sqlite_table_column_metadata', { O => 0x0004 });
56 0           DBD::SQLcipher::db->install_method('sqlite_db_filename', { O => 0x0004 });
57 0           DBD::SQLcipher::db->install_method('sqlite_db_status', { O => 0x0004 });
58 0           DBD::SQLcipher::st->install_method('sqlite_st_status', { O => 0x0004 });
59 0           DBD::SQLcipher::db->install_method('sqlite_create_module');
60              
61 0           $methods_are_installed++;
62             }
63              
64 0           $drh = DBI::_new_drh( "$_[0]::dr", {
65             Name => 'SQLite',
66             Version => $VERSION,
67             Attribution => 'DBD::SQLcipher by Matt Sergeant et al',
68             } );
69              
70 0           return $drh;
71             }
72              
73             sub CLONE {
74 0     0     undef $drh;
75             }
76              
77              
78             package # hide from PAUSE
79             DBD::SQLcipher::dr;
80              
81             sub connect {
82 0     0     my ($drh, $dbname, $user, $auth, $attr) = @_;
83              
84             # Default PrintWarn to the value of $^W
85             # unless ( defined $attr->{PrintWarn} ) {
86             # $attr->{PrintWarn} = $^W ? 1 : 0;
87             # }
88              
89 0           my $dbh = DBI::_new_dbh( $drh, {
90             Name => $dbname,
91             } );
92              
93 0           my $real = $dbname;
94 0 0         if ( $dbname =~ /=/ ) {
95 0           foreach my $attrib ( split(/;/, $dbname) ) {
96 0           my ($key, $value) = split(/=/, $attrib, 2);
97 0 0         if ( $key =~ /^(?:db(?:name)?|database)$/ ) {
    0          
98 0           $real = $value;
99             } elsif ( $key eq 'uri' ) {
100 0           $real = $value;
101 0           $attr->{sqlite_open_flags} |= DBD::SQLcipher::OPEN_URI();
102             } else {
103 0           $attr->{$key} = $value;
104             }
105             }
106             }
107              
108 0 0         if (my $flags = $attr->{sqlite_open_flags}) {
109 0 0         unless ($flags & (DBD::SQLcipher::OPEN_READONLY() | DBD::SQLcipher::OPEN_READWRITE())) {
110 0           $attr->{sqlite_open_flags} |= DBD::SQLcipher::OPEN_READWRITE() | DBD::SQLcipher::OPEN_CREATE();
111             }
112             }
113              
114             # To avoid unicode and long file name problems on Windows,
115             # convert to the shortname if the file (or parent directory) exists.
116 0 0 0       if ( $^O =~ /MSWin32/ and $real ne ':memory:' and $real ne '' and $real !~ /^file:/ and !-f $real ) {
      0        
      0        
      0        
117 0           require File::Basename;
118 0           my ($file, $dir, $suffix) = File::Basename::fileparse($real);
119             # We are creating a new file.
120             # Does the directory it's in at least exist?
121 0 0         if ( -d $dir ) {
122 0           require Win32;
123 0           $real = join '', grep { defined } Win32::GetShortPathName($dir), $file, $suffix;
  0            
124             } else {
125             # SQLite can't do mkpath anyway.
126             # So let it go through as it and fail.
127             }
128             }
129              
130             # Hand off to the actual login function
131 0 0         DBD::SQLcipher::db::_login($dbh, $real, $user, $auth, $attr) or return undef;
132              
133             # Register the on-demand collation installer, REGEXP function and
134             # perl tokenizer
135 0           if ( DBD::SQLcipher::NEWAPI ) {
136 0           $dbh->sqlite_collation_needed( \&install_collation );
137 0           $dbh->sqlite_create_function( "REGEXP", 2, \®exp );
138 0           $dbh->sqlite_register_fts3_perl_tokenizer();
139             } else {
140             $dbh->func( \&install_collation, "collation_needed" );
141             $dbh->func( "REGEXP", 2, \®exp, "create_function" );
142             $dbh->func( "register_fts3_perl_tokenizer" );
143             }
144              
145             # HACK: Since PrintWarn = 0 doesn't seem to actually prevent warnings
146             # in DBD::SQLcipher we set Warn to false if PrintWarn is false.
147              
148             # NOTE: According to the explanation by timbunce,
149             # "Warn is meant to report on bad practices or problems with
150             # the DBI itself (hence always on by default), while PrintWarn
151             # is meant to report warnings coming from the database."
152             # That is, if you want to disable an ineffective rollback warning
153             # etc (due to bad practices), you should turn off Warn,
154             # and to silence other warnings, turn off PrintWarn.
155             # Warn and PrintWarn are independent, and turning off PrintWarn
156             # does not silence those warnings that should be controlled by
157             # Warn.
158              
159             # unless ( $attr->{PrintWarn} ) {
160             # $attr->{Warn} = 0;
161             # }
162              
163 0           return $dbh;
164             }
165              
166             sub install_collation {
167 0     0     my $dbh = shift;
168 0           my $name = shift;
169 0           my $collation = $DBD::SQLcipher::COLLATION{$name};
170 0 0         unless ($collation) {
171 0 0         warn "Can't install unknown collation: $name" if $dbh->{PrintWarn};
172 0           return;
173             }
174 0           if ( DBD::SQLcipher::NEWAPI ) {
175 0           $dbh->sqlite_create_collation( $name => $collation );
176             } else {
177             $dbh->func( $name => $collation, "create_collation" );
178             }
179             }
180              
181             # default implementation for sqlite 'REGEXP' infix operator.
182             # Note : args are reversed, i.e. "a REGEXP b" calls REGEXP(b, a)
183             # (see http://www.sqlite.org/vtab.html#xfindfunction)
184             sub regexp {
185 105     105   76769 use locale;
  105         211  
  105         362  
186 0 0 0 0     return if !defined $_[0] || !defined $_[1];
187 0           return scalar($_[1] =~ $_[0]);
188             }
189              
190             package # hide from PAUSE
191             DBD::SQLcipher::db;
192              
193             sub prepare {
194 0     0     my $dbh = shift;
195 0           my $sql = shift;
196 0 0         $sql = '' unless defined $sql;
197              
198 0           my $sth = DBI::_new_sth( $dbh, {
199             Statement => $sql,
200             } );
201              
202 0 0         DBD::SQLcipher::st::_prepare($sth, $sql, @_) or return undef;
203              
204 0           return $sth;
205             }
206              
207             sub do {
208 0     0     my ($dbh, $statement, $attr, @bind_values) = @_;
209              
210             # shortcut
211 0           my $allow_multiple_statements = $dbh->FETCH('sqlite_allow_multiple_statements');
212 0 0 0       if (defined $statement && !defined $attr && !@bind_values) {
      0        
213             # _do() (i.e. sqlite3_exec()) runs semicolon-separate SQL
214             # statements, which is handy but insecure sometimes.
215             # Use this only when it's safe or explicitly allowed.
216 0 0 0       if (index($statement, ';') == -1 or $allow_multiple_statements) {
217 0           return DBD::SQLcipher::db::_do($dbh, $statement);
218             }
219             }
220              
221 0           my @copy = @{[@bind_values]};
  0            
222 0           my $rows = 0;
223              
224 0           while ($statement) {
225 0 0         my $sth = $dbh->prepare($statement, $attr) or return undef;
226 0 0         $sth->execute(splice @copy, 0, $sth->{NUM_OF_PARAMS}) or return undef;
227 0           $rows += $sth->rows;
228             # XXX: not sure why but $dbh->{sqlite...} wouldn't work here
229 0 0         last unless $allow_multiple_statements;
230 0           $statement = $sth->{sqlite_unprepared_statements};
231             }
232              
233             # always return true if no error
234 0 0         return ($rows == 0) ? "0E0" : $rows;
235             }
236              
237             sub ping {
238 0     0     my $dbh = shift;
239              
240             # $file may be undef (ie. in-memory/temporary database)
241 0           my $file = DBD::SQLcipher::NEWAPI ? $dbh->sqlite_db_filename
242             : $dbh->func("db_filename");
243              
244 0 0 0       return 0 if $file && !-f $file;
245 0 0         return $dbh->FETCH('Active') ? 1 : 0;
246             }
247              
248             sub _get_version {
249 0     0     return ( DBD::SQLcipher::db::FETCH($_[0], 'sqlite_version') );
250             }
251              
252             my %info = (
253             17 => 'SQLite', # SQL_DBMS_NAME
254             18 => \&_get_version, # SQL_DBMS_VER
255             29 => '"', # SQL_IDENTIFIER_QUOTE_CHAR
256             );
257              
258             sub get_info {
259 0     0     my($dbh, $info_type) = @_;
260 0           my $v = $info{int($info_type)};
261 0 0         $v = $v->($dbh) if ref $v eq 'CODE';
262 0           return $v;
263             }
264              
265             sub _attached_database_list {
266 0     0     my $dbh = shift;
267 0           my @attached;
268              
269 0 0         my $sth_databases = $dbh->prepare( 'PRAGMA database_list' ) or return;
270 0 0         $sth_databases->execute or return;
271 0           while ( my $db_info = $sth_databases->fetchrow_hashref ) {
272 0 0         push @attached, $db_info->{name} if $db_info->{seq} >= 2;
273             }
274 0           return @attached;
275             }
276              
277             # SQL/CLI (ISO/IEC JTC 1/SC 32 N 0595), 6.63 Tables
278             # Based on DBD::Oracle's
279             # See also http://www.ch-werner.de/sqliteodbc/html/sqlite3odbc_8c.html#a213
280             sub table_info {
281 0     0     my ($dbh, $cat_val, $sch_val, $tbl_val, $typ_val, $attr) = @_;
282              
283 0           my @where = ();
284 0           my $sql;
285 0 0 0       if ( defined($cat_val) && $cat_val eq '%'
    0 0        
    0 0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
286             && defined($sch_val) && $sch_val eq ''
287             && defined($tbl_val) && $tbl_val eq '') { # Rule 19a
288 0           $sql = <<'END_SQL';
289             SELECT NULL TABLE_CAT
290             , NULL TABLE_SCHEM
291             , NULL TABLE_NAME
292             , NULL TABLE_TYPE
293             , NULL REMARKS
294             END_SQL
295             }
296             elsif ( defined($cat_val) && $cat_val eq ''
297             && defined($sch_val) && $sch_val eq '%'
298             && defined($tbl_val) && $tbl_val eq '') { # Rule 19b
299 0           $sql = <<'END_SQL';
300             SELECT NULL TABLE_CAT
301             , t.tn TABLE_SCHEM
302             , NULL TABLE_NAME
303             , NULL TABLE_TYPE
304             , NULL REMARKS
305             FROM (
306             SELECT 'main' tn
307             UNION SELECT 'temp' tn
308             END_SQL
309 0           for my $db_name (_attached_database_list($dbh)) {
310 0           $sql .= " UNION SELECT '$db_name' tn\n";
311             }
312 0           $sql .= ") t\n";
313             }
314             elsif ( defined($cat_val) && $cat_val eq ''
315             && defined($sch_val) && $sch_val eq ''
316             && defined($tbl_val) && $tbl_val eq ''
317             && defined($typ_val) && $typ_val eq '%') { # Rule 19c
318 0           $sql = <<'END_SQL';
319             SELECT NULL TABLE_CAT
320             , NULL TABLE_SCHEM
321             , NULL TABLE_NAME
322             , t.tt TABLE_TYPE
323             , NULL REMARKS
324             FROM (
325             SELECT 'TABLE' tt UNION
326             SELECT 'VIEW' tt UNION
327             SELECT 'LOCAL TEMPORARY' tt UNION
328             SELECT 'SYSTEM TABLE' tt
329             ) t
330             ORDER BY TABLE_TYPE
331             END_SQL
332             }
333             else {
334 0           $sql = <<'END_SQL';
335             SELECT *
336             FROM
337             (
338             SELECT NULL TABLE_CAT
339             , TABLE_SCHEM
340             , tbl_name TABLE_NAME
341             , TABLE_TYPE
342             , NULL REMARKS
343             , sql sqlite_sql
344             FROM (
345             SELECT 'main' TABLE_SCHEM, tbl_name, upper(type) TABLE_TYPE, sql
346             FROM sqlite_master
347             UNION ALL
348             SELECT 'temp' TABLE_SCHEM, tbl_name, 'LOCAL TEMPORARY' TABLE_TYPE, sql
349             FROM sqlite_temp_master
350             END_SQL
351              
352 0           for my $db_name (_attached_database_list($dbh)) {
353 0           $sql .= <<"END_SQL";
354             UNION ALL
355             SELECT '$db_name' TABLE_SCHEM, tbl_name, upper(type) TABLE_TYPE, sql
356             FROM "$db_name".sqlite_master
357             END_SQL
358             }
359              
360 0           $sql .= <<'END_SQL';
361             UNION ALL
362             SELECT 'main' TABLE_SCHEM, 'sqlite_master' tbl_name, 'SYSTEM TABLE' TABLE_TYPE, NULL sql
363             UNION ALL
364             SELECT 'temp' TABLE_SCHEM, 'sqlite_temp_master' tbl_name, 'SYSTEM TABLE' TABLE_TYPE, NULL sql
365             )
366             )
367             END_SQL
368 0 0         $attr = {} unless ref $attr eq 'HASH';
369 0 0         my $escape = defined $attr->{Escape} ? " ESCAPE '$attr->{Escape}'" : '';
370 0 0         if ( defined $sch_val ) {
371 0           push @where, "TABLE_SCHEM LIKE '$sch_val'$escape";
372             }
373 0 0         if ( defined $tbl_val ) {
374 0           push @where, "TABLE_NAME LIKE '$tbl_val'$escape";
375             }
376 0 0         if ( defined $typ_val ) {
377 0           my $table_type_list;
378 0           $typ_val =~ s/^\s+//;
379 0           $typ_val =~ s/\s+$//;
380 0           my @ttype_list = split (/\s*,\s*/, $typ_val);
381 0           foreach my $table_type (@ttype_list) {
382 0 0         if ($table_type !~ /^'.*'$/) {
383 0           $table_type = "'" . $table_type . "'";
384             }
385             }
386 0           $table_type_list = join(', ', @ttype_list);
387 0 0         push @where, "TABLE_TYPE IN (\U$table_type_list)" if $table_type_list;
388             }
389 0 0         $sql .= ' WHERE ' . join("\n AND ", @where ) . "\n" if @where;
390 0           $sql .= " ORDER BY TABLE_TYPE, TABLE_SCHEM, TABLE_NAME\n";
391             }
392 0 0         my $sth = $dbh->prepare($sql) or return undef;
393 0 0         $sth->execute or return undef;
394 0           $sth;
395             }
396              
397             sub primary_key_info {
398 0     0     my ($dbh, $catalog, $schema, $table, $attr) = @_;
399              
400 0           my $databases = $dbh->selectall_arrayref("PRAGMA database_list", {Slice => {}});
401              
402 0           my @pk_info;
403 0           for my $database (@$databases) {
404 0           my $dbname = $database->{name};
405 0 0 0       next if defined $schema && $schema ne '%' && $schema ne $dbname;
      0        
406              
407 0           my $quoted_dbname = $dbh->quote_identifier($dbname);
408              
409 0 0         my $master_table =
    0          
410             ($dbname eq 'main') ? 'sqlite_master' :
411             ($dbname eq 'temp') ? 'sqlite_temp_master' :
412             $quoted_dbname.'.sqlite_master';
413              
414 0 0         my $sth = $dbh->prepare("SELECT name, sql FROM $master_table WHERE type = ?") or return;
415 0 0         $sth->execute("table") or return;
416 0           while(my $row = $sth->fetchrow_hashref) {
417 0           my $tbname = $row->{name};
418 0 0 0       next if defined $table && $table ne '%' && $table ne $tbname;
      0        
419              
420 0           my $quoted_tbname = $dbh->quote_identifier($tbname);
421 0 0         my $t_sth = $dbh->prepare("PRAGMA $quoted_dbname.table_info($quoted_tbname)") or return;
422 0 0         $t_sth->execute or return;
423 0           my @pk;
424 0           while(my $col = $t_sth->fetchrow_hashref) {
425 0 0         push @pk, $col->{name} if $col->{pk};
426             }
427              
428             # If there're multiple primary key columns, we need to
429             # find their order from one of the auto-generated unique
430             # indices (note that single column integer primary key
431             # doesn't create an index).
432 0 0 0       if (@pk > 1 and $row->{sql} =~ /\bPRIMARY\s+KEY\s*\(\s*
433             (
434             (?:
435             (
436             [a-z_][a-z0-9_]*
437             | (["'`])(?:\3\3|(?!\3).)+?\3(?!\3)
438             | \[[^\]]+\]
439             )
440             \s*,\s*
441             )+
442             (
443             [a-z_][a-z0-9_]*
444             | (["'`])(?:\5\5|(?!\5).)+?\5(?!\5)
445             | \[[^\]]+\]
446             )
447             )
448             \s*\)/six) {
449 0           my $pk_sql = $1;
450 0           @pk = ();
451 0           while($pk_sql =~ /
452             (
453             [a-z_][a-z0-9_]*
454             | (["'`])(?:\2\2|(?!\2).)+?\2(?!\2)
455             | \[([^\]]+)\]
456             )
457             (?:\s*,\s*|$)
458             /sixg) {
459 0           my($col, $quote, $brack) = ($1, $2, $3);
460 0 0         if ( defined $quote ) {
    0          
461             # Dequote "'`
462 0           $col = substr $col, 1, -1;
463 0           $col =~ s/$quote$quote/$quote/g;
464             } elsif ( defined $brack ) {
465             # Dequote []
466 0           $col = $brack;
467             }
468 0           push @pk, $col;
469             }
470             }
471              
472 0 0         my $key_name = $row->{sql} =~ /\bCONSTRAINT\s+(\S+|"[^"]+")\s+PRIMARY\s+KEY\s*\(/i ? $1 : 'PRIMARY KEY';
473 0           my $key_seq = 0;
474 0           foreach my $pk_field (@pk) {
475 0           push @pk_info, {
476             TABLE_SCHEM => $dbname,
477             TABLE_NAME => $tbname,
478             COLUMN_NAME => $pk_field,
479             KEY_SEQ => ++$key_seq,
480             PK_NAME => $key_name,
481             };
482             }
483             }
484             }
485              
486 0 0         my $sponge = DBI->connect("DBI:Sponge:", '','')
487             or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr");
488 0           my @names = qw(TABLE_CAT TABLE_SCHEM TABLE_NAME COLUMN_NAME KEY_SEQ PK_NAME);
489             my $sth = $sponge->prepare( "primary_key_info", {
490 0 0         rows => [ map { [ @{$_}{@names} ] } @pk_info ],
  0            
  0            
491             NUM_OF_FIELDS => scalar @names,
492             NAME => \@names,
493             }) or return $dbh->DBI::set_err(
494             $sponge->err,
495             $sponge->errstr,
496             );
497 0           return $sth;
498             }
499              
500              
501             our %DBI_code_for_rule = ( # from DBI doc; curiously, they are not exported
502             # by the DBI module.
503             # codes for update/delete constraints
504             'CASCADE' => 0,
505             'RESTRICT' => 1,
506             'SET NULL' => 2,
507             'NO ACTION' => 3,
508             'SET DEFAULT' => 4,
509              
510             # codes for deferrability
511             'INITIALLY DEFERRED' => 5,
512             'INITIALLY IMMEDIATE' => 6,
513             'NOT DEFERRABLE' => 7,
514             );
515              
516              
517             my @FOREIGN_KEY_INFO_ODBC = (
518             'PKTABLE_CAT', # The primary (unique) key table catalog identifier.
519             'PKTABLE_SCHEM', # The primary (unique) key table schema identifier.
520             'PKTABLE_NAME', # The primary (unique) key table identifier.
521             'PKCOLUMN_NAME', # The primary (unique) key column identifier.
522             'FKTABLE_CAT', # The foreign key table catalog identifier.
523             'FKTABLE_SCHEM', # The foreign key table schema identifier.
524             'FKTABLE_NAME', # The foreign key table identifier.
525             'FKCOLUMN_NAME', # The foreign key column identifier.
526             'KEY_SEQ', # The column sequence number (starting with 1).
527             'UPDATE_RULE', # The referential action for the UPDATE rule.
528             'DELETE_RULE', # The referential action for the DELETE rule.
529             'FK_NAME', # The foreign key name.
530             'PK_NAME', # The primary (unique) key name.
531             'DEFERRABILITY', # The deferrability of the foreign key constraint.
532             'UNIQUE_OR_PRIMARY', # qualifies the key referenced by the foreign key
533             );
534              
535             # Column names below are not used, but listed just for completeness's sake.
536             # Maybe we could add an option so that the user can choose which field
537             # names will be returned; the DBI spec is not very clear about ODBC vs. CLI.
538             my @FOREIGN_KEY_INFO_SQL_CLI = qw(
539             UK_TABLE_CAT
540             UK_TABLE_SCHEM
541             UK_TABLE_NAME
542             UK_COLUMN_NAME
543             FK_TABLE_CAT
544             FK_TABLE_SCHEM
545             FK_TABLE_NAME
546             FK_COLUMN_NAME
547             ORDINAL_POSITION
548             UPDATE_RULE
549             DELETE_RULE
550             FK_NAME
551             UK_NAME
552             DEFERABILITY
553             UNIQUE_OR_PRIMARY
554             );
555              
556             sub foreign_key_info {
557 0     0     my ($dbh, $pk_catalog, $pk_schema, $pk_table, $fk_catalog, $fk_schema, $fk_table) = @_;
558              
559 0 0         my $databases = $dbh->selectall_arrayref("PRAGMA database_list", {Slice => {}}) or return;
560              
561 0           my @fk_info;
562             my %table_info;
563 0           for my $database (@$databases) {
564 0           my $dbname = $database->{name};
565 0 0 0       next if defined $fk_schema && $fk_schema ne '%' && $fk_schema ne $dbname;
      0        
566              
567 0           my $quoted_dbname = $dbh->quote_identifier($dbname);
568 0 0         my $master_table =
    0          
569             ($dbname eq 'main') ? 'sqlite_master' :
570             ($dbname eq 'temp') ? 'sqlite_temp_master' :
571             $quoted_dbname.'.sqlite_master';
572              
573 0 0         my $tables = $dbh->selectall_arrayref("SELECT name FROM $master_table WHERE type = ?", undef, "table") or return;
574 0           for my $table (@$tables) {
575 0           my $tbname = $table->[0];
576 0 0 0       next if defined $fk_table && $fk_table ne '%' && $fk_table ne $tbname;
      0        
577              
578 0           my $quoted_tbname = $dbh->quote_identifier($tbname);
579 0 0         my $sth = $dbh->prepare("PRAGMA $quoted_dbname.foreign_key_list($quoted_tbname)") or return;
580 0 0         $sth->execute or return;
581 0           while(my $row = $sth->fetchrow_hashref) {
582 0 0 0       next if defined $pk_table && $pk_table ne '%' && $pk_table ne $row->{table};
      0        
583              
584 0 0         unless ($table_info{$row->{table}}) {
585 0           my $quoted_tb = $dbh->quote_identifier($row->{table});
586 0           for my $db (@$databases) {
587 0           my $quoted_db = $dbh->quote_identifier($db->{name});
588 0 0         my $t_sth = $dbh->prepare("PRAGMA $quoted_db.table_info($quoted_tb)") or return;
589 0 0         $t_sth->execute or return;
590 0           my $cols = {};
591 0           while(my $r = $t_sth->fetchrow_hashref) {
592 0           $cols->{$r->{name}} = $r->{pk};
593             }
594 0 0         if (keys %$cols) {
595             $table_info{$row->{table}} = {
596             schema => $db->{name},
597 0           columns => $cols,
598             };
599 0           last;
600             }
601             }
602             }
603              
604 0 0 0       next if defined $pk_schema && $pk_schema ne '%' && $pk_schema ne $table_info{$row->{table}}{schema};
      0        
605              
606             push @fk_info, {
607             PKTABLE_CAT => undef,
608             PKTABLE_SCHEM => $table_info{$row->{table}}{schema},
609             PKTABLE_NAME => $row->{table},
610             PKCOLUMN_NAME => $row->{to},
611             FKTABLE_CAT => undef,
612             FKTABLE_SCHEM => $dbname,
613             FKTABLE_NAME => $tbname,
614             FKCOLUMN_NAME => $row->{from},
615             KEY_SEQ => $row->{seq} + 1,
616             UPDATE_RULE => $DBI_code_for_rule{$row->{on_update}},
617             DELETE_RULE => $DBI_code_for_rule{$row->{on_delete}},
618             FK_NAME => undef,
619             PK_NAME => undef,
620             DEFERRABILITY => undef,
621 0 0         UNIQUE_OR_PRIMARY => $table_info{$row->{table}}{columns}{$row->{to}} ? 'PRIMARY' : 'UNIQUE',
622             };
623             }
624             }
625             }
626              
627 0 0         my $sponge_dbh = DBI->connect("DBI:Sponge:", "", "")
628             or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr");
629             my $sponge_sth = $sponge_dbh->prepare("foreign_key_info", {
630             NAME => \@FOREIGN_KEY_INFO_ODBC,
631 0 0         rows => [ map { [@{$_}{@FOREIGN_KEY_INFO_ODBC} ] } @fk_info ],
  0            
  0            
632             NUM_OF_FIELDS => scalar(@FOREIGN_KEY_INFO_ODBC),
633             }) or return $dbh->DBI::set_err(
634             $sponge_dbh->err,
635             $sponge_dbh->errstr,
636             );
637 0           return $sponge_sth;
638             }
639              
640             my @STATISTICS_INFO_ODBC = (
641             'TABLE_CAT', # The catalog identifier.
642             'TABLE_SCHEM', # The schema identifier.
643             'TABLE_NAME', # The table identifier.
644             'NON_UNIQUE', # Unique index indicator.
645             'INDEX_QUALIFIER', # Index qualifier identifier.
646             'INDEX_NAME', # The index identifier.
647             'TYPE', # The type of information being returned.
648             'ORDINAL_POSITION', # Column sequence number (starting with 1).
649             'COLUMN_NAME', # The column identifier.
650             'ASC_OR_DESC', # Column sort sequence.
651             'CARDINALITY', # Cardinality of the table or index.
652             'PAGES', # Number of storage pages used by this table or index.
653             'FILTER_CONDITION', # The index filter condition as a string.
654             );
655              
656             sub statistics_info {
657 0     0     my ($dbh, $catalog, $schema, $table, $unique_only, $quick) = @_;
658              
659 0 0         my $databases = $dbh->selectall_arrayref("PRAGMA database_list", {Slice => {}}) or return;
660              
661 0           my @statistics_info;
662 0           for my $database (@$databases) {
663 0           my $dbname = $database->{name};
664 0 0 0       next if defined $schema && $schema ne '%' && $schema ne $dbname;
      0        
665              
666 0           my $quoted_dbname = $dbh->quote_identifier($dbname);
667 0 0         my $master_table =
    0          
668             ($dbname eq 'main') ? 'sqlite_master' :
669             ($dbname eq 'temp') ? 'sqlite_temp_master' :
670             $quoted_dbname.'.sqlite_master';
671              
672 0 0         my $tables = $dbh->selectall_arrayref("SELECT name FROM $master_table WHERE type = ?", undef, "table") or return;
673 0           for my $table_ref (@$tables) {
674 0           my $tbname = $table_ref->[0];
675 0 0 0       next if defined $table && $table ne '%' && uc($table) ne uc($tbname);
      0        
676              
677 0           my $quoted_tbname = $dbh->quote_identifier($tbname);
678 0 0         my $sth = $dbh->prepare("PRAGMA $quoted_dbname.index_list($quoted_tbname)") or return;
679 0 0         $sth->execute or return;
680 0           while(my $row = $sth->fetchrow_hashref) {
681              
682 0 0 0       next if $unique_only && !$row->{unique};
683 0           my $quoted_idx = $dbh->quote_identifier($row->{name});
684 0           for my $db (@$databases) {
685 0           my $quoted_db = $dbh->quote_identifier($db->{name});
686 0 0         my $i_sth = $dbh->prepare("PRAGMA $quoted_db.index_info($quoted_idx)") or return;
687 0 0         $i_sth->execute or return;
688 0           my $cols = {};
689 0           while(my $info = $i_sth->fetchrow_hashref) {
690             push @statistics_info, {
691             TABLE_CAT => undef,
692             TABLE_SCHEM => $db->{name},
693             TABLE_NAME => $tbname,
694             NON_UNIQUE => $row->{unique} ? 0 : 1,
695             INDEX_QUALIFIER => undef,
696             INDEX_NAME => $row->{name},
697             TYPE => 'btree', # see http://www.sqlite.org/version3.html esp. "Traditional B-trees are still used for indices"
698             ORDINAL_POSITION => $info->{seqno} + 1,
699             COLUMN_NAME => $info->{name},
700 0 0         ASC_OR_DESC => undef,
701             CARDINALITY => undef,
702             PAGES => undef,
703             FILTER_CONDITION => undef,
704             };
705             }
706             }
707             }
708             }
709             }
710              
711 0 0         my $sponge_dbh = DBI->connect("DBI:Sponge:", "", "")
712             or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr");
713             my $sponge_sth = $sponge_dbh->prepare("statistics_info", {
714             NAME => \@STATISTICS_INFO_ODBC,
715 0 0         rows => [ map { [@{$_}{@STATISTICS_INFO_ODBC} ] } @statistics_info ],
  0            
  0            
716             NUM_OF_FIELDS => scalar(@STATISTICS_INFO_ODBC),
717             }) or return $dbh->DBI::set_err(
718             $sponge_dbh->err,
719             $sponge_dbh->errstr,
720             );
721 0           return $sponge_sth;
722             }
723              
724             sub type_info_all {
725 0     0     return; # XXX code just copied from DBD::Oracle, not yet thought about
726             # return [
727             # {
728             # TYPE_NAME => 0,
729             # DATA_TYPE => 1,
730             # COLUMN_SIZE => 2,
731             # LITERAL_PREFIX => 3,
732             # LITERAL_SUFFIX => 4,
733             # CREATE_PARAMS => 5,
734             # NULLABLE => 6,
735             # CASE_SENSITIVE => 7,
736             # SEARCHABLE => 8,
737             # UNSIGNED_ATTRIBUTE => 9,
738             # FIXED_PREC_SCALE => 10,
739             # AUTO_UNIQUE_VALUE => 11,
740             # LOCAL_TYPE_NAME => 12,
741             # MINIMUM_SCALE => 13,
742             # MAXIMUM_SCALE => 14,
743             # SQL_DATA_TYPE => 15,
744             # SQL_DATETIME_SUB => 16,
745             # NUM_PREC_RADIX => 17,
746             # },
747             # [ 'CHAR', 1, 255, '\'', '\'', 'max length', 1, 1, 3,
748             # undef, '0', '0', undef, undef, undef, 1, undef, undef
749             # ],
750             # [ 'NUMBER', 3, 38, undef, undef, 'precision,scale', 1, '0', 3,
751             # '0', '0', '0', undef, '0', 38, 3, undef, 10
752             # ],
753             # [ 'DOUBLE', 8, 15, undef, undef, undef, 1, '0', 3,
754             # '0', '0', '0', undef, undef, undef, 8, undef, 10
755             # ],
756             # [ 'DATE', 9, 19, '\'', '\'', undef, 1, '0', 3,
757             # undef, '0', '0', undef, '0', '0', 11, undef, undef
758             # ],
759             # [ 'VARCHAR', 12, 1024*1024, '\'', '\'', 'max length', 1, 1, 3,
760             # undef, '0', '0', undef, undef, undef, 12, undef, undef
761             # ]
762             # ];
763             }
764              
765             my @COLUMN_INFO = qw(
766             TABLE_CAT
767             TABLE_SCHEM
768             TABLE_NAME
769             COLUMN_NAME
770             DATA_TYPE
771             TYPE_NAME
772             COLUMN_SIZE
773             BUFFER_LENGTH
774             DECIMAL_DIGITS
775             NUM_PREC_RADIX
776             NULLABLE
777             REMARKS
778             COLUMN_DEF
779             SQL_DATA_TYPE
780             SQL_DATETIME_SUB
781             CHAR_OCTET_LENGTH
782             ORDINAL_POSITION
783             IS_NULLABLE
784             );
785              
786             sub column_info {
787 0     0     my ($dbh, $cat_val, $sch_val, $tbl_val, $col_val) = @_;
788              
789 0 0 0       if ( defined $col_val and $col_val eq '%' ) {
790 0           $col_val = undef;
791             }
792              
793             # Get a list of all tables ordered by TABLE_SCHEM, TABLE_NAME
794 0           my $sql = <<'END_SQL';
795             SELECT TABLE_SCHEM, tbl_name TABLE_NAME
796             FROM (
797             SELECT 'main' TABLE_SCHEM, tbl_name
798             FROM sqlite_master
799             WHERE type IN ('table','view')
800             UNION ALL
801             SELECT 'temp' TABLE_SCHEM, tbl_name
802             FROM sqlite_temp_master
803             WHERE type IN ('table','view')
804             END_SQL
805              
806 0           for my $db_name (_attached_database_list($dbh)) {
807 0           $sql .= <<"END_SQL";
808             UNION ALL
809             SELECT '$db_name' TABLE_SCHEM, tbl_name
810             FROM "$db_name".sqlite_master
811             WHERE type IN ('table','view')
812             END_SQL
813             }
814              
815 0           $sql .= <<'END_SQL';
816             UNION ALL
817             SELECT 'main' TABLE_SCHEM, 'sqlite_master' tbl_name
818             UNION ALL
819             SELECT 'temp' TABLE_SCHEM, 'sqlite_temp_master' tbl_name
820             )
821             END_SQL
822              
823 0           my @where;
824 0 0         if ( defined $sch_val ) {
825 0           push @where, "TABLE_SCHEM LIKE '$sch_val'";
826             }
827 0 0         if ( defined $tbl_val ) {
828 0           push @where, "TABLE_NAME LIKE '$tbl_val'";
829             }
830 0 0         $sql .= ' WHERE ' . join("\n AND ", @where ) . "\n" if @where;
831 0           $sql .= " ORDER BY TABLE_SCHEM, TABLE_NAME\n";
832 0 0         my $sth_tables = $dbh->prepare($sql) or return undef;
833 0 0         $sth_tables->execute or return undef;
834              
835             # Taken from Fey::Loader::SQLite
836 0           my @cols;
837 0           while ( my ($schema, $table) = $sth_tables->fetchrow_array ) {
838 0 0         my $sth_columns = $dbh->prepare(qq{PRAGMA "$schema".table_info("$table")}) or return;
839 0 0         $sth_columns->execute or return;
840              
841 0           for ( my $position = 1; my $col_info = $sth_columns->fetchrow_hashref; $position++ ) {
842 0 0         if ( defined $col_val ) {
843             # This must do a LIKE comparison
844 0 0         my $sth = $dbh->prepare("SELECT '$col_info->{name}' LIKE '$col_val'") or return undef;
845 0 0         $sth->execute or return undef;
846             # Skip columns that don't match $col_val
847 0 0         next unless ($sth->fetchrow_array)[0];
848             }
849              
850             my %col = (
851             TABLE_SCHEM => $schema,
852             TABLE_NAME => $table,
853             COLUMN_NAME => $col_info->{name},
854 0           ORDINAL_POSITION => $position,
855             );
856              
857 0           my $type = $col_info->{type};
858 0 0         if ( $type =~ s/(\w+)\s*\(\s*(\d+)(?:\s*,\s*(\d+))?\s*\)/$1/ ) {
859 0           $col{COLUMN_SIZE} = $2;
860 0           $col{DECIMAL_DIGITS} = $3;
861             }
862              
863 0           $col{TYPE_NAME} = $type;
864              
865 0 0         if ( defined $col_info->{dflt_value} ) {
866             $col{COLUMN_DEF} = $col_info->{dflt_value}
867 0           }
868              
869 0 0         if ( $col_info->{notnull} ) {
870 0           $col{NULLABLE} = 0;
871 0           $col{IS_NULLABLE} = 'NO';
872             } else {
873 0           $col{NULLABLE} = 1;
874 0           $col{IS_NULLABLE} = 'YES';
875             }
876              
877 0           push @cols, \%col;
878             }
879 0           $sth_columns->finish;
880             }
881 0           $sth_tables->finish;
882              
883 0 0         my $sponge = DBI->connect("DBI:Sponge:", '','')
884             or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr");
885             $sponge->prepare( "column_info", {
886 0 0         rows => [ map { [ @{$_}{@COLUMN_INFO} ] } @cols ],
  0            
  0            
887             NUM_OF_FIELDS => scalar @COLUMN_INFO,
888             NAME => [ @COLUMN_INFO ],
889             } ) or return $dbh->DBI::set_err(
890             $sponge->err,
891             $sponge->errstr,
892             );
893             }
894              
895             #======================================================================
896             # An internal tied hash package used for %DBD::SQLcipher::COLLATION, to
897             # prevent people from unintentionally overriding globally registered collations.
898              
899             package # hide from PAUSE
900             DBD::SQLcipher::_WriteOnceHash;
901              
902             require Tie::Hash;
903              
904             our @ISA = qw(Tie::StdHash);
905              
906             sub TIEHASH {
907 0     0     bless {}, $_[0];
908             }
909              
910             sub STORE {
911 0 0   0     ! exists $_[0]->{$_[1]} or die "entry $_[1] already registered";
912 0           $_[0]->{$_[1]} = $_[2];
913             }
914              
915             sub DELETE {
916 0     0     die "deletion of entry $_[1] is forbidden";
917             }
918              
919             1;
920              
921             __END__