line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package UR::DataSource::SQLite; |
2
|
127
|
|
|
127
|
|
3159
|
use strict; |
|
127
|
|
|
|
|
173
|
|
|
127
|
|
|
|
|
3498
|
|
3
|
127
|
|
|
127
|
|
442
|
use warnings; |
|
127
|
|
|
|
|
183
|
|
|
127
|
|
|
|
|
3338
|
|
4
|
|
|
|
|
|
|
|
5
|
127
|
|
|
127
|
|
52281
|
use IO::Dir; |
|
127
|
|
|
|
|
938724
|
|
|
127
|
|
|
|
|
5036
|
|
6
|
127
|
|
|
127
|
|
717
|
use File::Spec; |
|
127
|
|
|
|
|
184
|
|
|
127
|
|
|
|
|
470
|
|
7
|
127
|
|
|
127
|
|
2235
|
use File::Basename; |
|
127
|
|
|
|
|
165
|
|
|
127
|
|
|
|
|
6008
|
|
8
|
127
|
|
|
127
|
|
50293
|
use version; |
|
127
|
|
|
|
|
179969
|
|
|
127
|
|
|
|
|
635
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=pod |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 NAME |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
UR::DataSource::SQLite - base class for datasources using the SQLite3 RDBMS |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 SYNOPSIS |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
In the shell: |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
ur define datasource sqlite |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
Or write the singleton to represent the source directly: |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
class Acme::DataSource::MyDB1 { |
25
|
|
|
|
|
|
|
is => 'UR::DataSource::SQLite', |
26
|
|
|
|
|
|
|
has_constant => [ |
27
|
|
|
|
|
|
|
server => '/var/lib/acme-app/mydb1.sqlitedb' |
28
|
|
|
|
|
|
|
] |
29
|
|
|
|
|
|
|
}; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
You may also use a directory containing *.sqlite3 files. The primary database |
32
|
|
|
|
|
|
|
must be named main.sqlite3. All the other *.sqlite3 files are attached when |
33
|
|
|
|
|
|
|
the database is opened. |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
class Acme::DataSource::MyDB2 { |
36
|
|
|
|
|
|
|
is => 'UR::DataSource::SQLite', |
37
|
|
|
|
|
|
|
has_constant => [ |
38
|
|
|
|
|
|
|
server => '/path/to/directory/' |
39
|
|
|
|
|
|
|
] |
40
|
|
|
|
|
|
|
}; |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=cut |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
require UR; |
45
|
|
|
|
|
|
|
our $VERSION = "0.46"; # UR $VERSION; |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
UR::Object::Type->define( |
48
|
|
|
|
|
|
|
class_name => 'UR::DataSource::SQLite', |
49
|
|
|
|
|
|
|
is => ['UR::DataSource::RDBMS'], |
50
|
|
|
|
|
|
|
is_abstract => 1, |
51
|
|
|
|
|
|
|
); |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# RDBMS API |
54
|
|
|
|
|
|
|
|
55
|
178
|
|
|
178
|
0
|
485
|
sub driver { "SQLite" } |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
sub default_owner { |
58
|
349
|
|
|
349
|
0
|
2564
|
return 'main'; |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
|
61
|
348
|
|
|
348
|
0
|
776
|
sub owner { default_owner() } |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
sub login { |
64
|
|
|
|
|
|
|
undef |
65
|
206
|
|
|
206
|
0
|
491
|
} |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
sub auth { |
68
|
|
|
|
|
|
|
undef |
69
|
203
|
|
|
203
|
0
|
394
|
} |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
sub create_default_handle { |
72
|
175
|
|
|
175
|
0
|
3199
|
my $self = shift->_singleton_object(); |
73
|
|
|
|
|
|
|
|
74
|
175
|
|
|
|
|
1453
|
$self->_init_database; |
75
|
175
|
100
|
|
|
|
946
|
if ($self->_db_path_specifies_a_directory($self->server)) { |
76
|
2
|
|
|
|
|
14
|
return $self->_create_default_handle_from_directory(); |
77
|
|
|
|
|
|
|
} else { |
78
|
173
|
|
|
|
|
1576
|
return $self->SUPER::create_default_handle(@_); |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
sub _create_default_handle_from_directory { |
83
|
2
|
|
|
2
|
|
3
|
my $self = shift; |
84
|
|
|
|
|
|
|
|
85
|
2
|
|
|
|
|
5
|
my $server_directory = $self->server; |
86
|
2
|
|
|
|
|
6
|
my $ext = $self->_extension_for_db; |
87
|
2
|
|
|
|
|
44
|
my $main_schema_file = File::Spec->catfile($server_directory, "main${ext}"); |
88
|
2
|
50
|
33
|
|
|
33
|
-f $main_schema_file |
89
|
|
|
|
|
|
|
|| UR::Util::touch_file($main_schema_file) |
90
|
|
|
|
|
|
|
|| die "Could not create main schema file $main_schema_file: $!"; |
91
|
|
|
|
|
|
|
|
92
|
2
|
|
|
|
|
6
|
my $server_sub_name = join('::', ref($self), 'server'); |
93
|
|
|
|
|
|
|
|
94
|
2
|
|
|
|
|
3
|
my $dbh = do { |
95
|
127
|
|
|
127
|
|
32815
|
no strict 'refs'; |
|
127
|
|
|
|
|
196
|
|
|
127
|
|
|
|
|
3196
|
|
96
|
127
|
|
|
127
|
|
441
|
no warnings 'redefine'; |
|
127
|
|
|
|
|
228
|
|
|
127
|
|
|
|
|
396944
|
|
97
|
2
|
|
|
2
|
|
22
|
local *$server_sub_name = sub { $main_schema_file }; |
|
2
|
|
|
|
|
3
|
|
98
|
|
|
|
|
|
|
|
99
|
2
|
|
|
|
|
19
|
$self->SUPER::create_default_handle(); |
100
|
|
|
|
|
|
|
}; |
101
|
|
|
|
|
|
|
|
102
|
2
|
|
|
|
|
20
|
$self->_attach_all_schema_files_in_directory($dbh, $server_directory); |
103
|
2
|
|
|
|
|
1494
|
return $dbh; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
sub _attach_all_schema_files_in_directory { |
107
|
2
|
|
|
2
|
|
5
|
my($self, $dbh, $server_directory) = @_; |
108
|
2
|
|
|
|
|
11
|
my @schema_files = $self->_schema_files_in_directory($server_directory); |
109
|
|
|
|
|
|
|
|
110
|
2
|
|
|
|
|
147
|
local $dbh->{AutoCommit} = 1; |
111
|
|
|
|
|
|
|
|
112
|
2
|
|
|
|
|
37
|
my $main_db_file = join('', 'main', $self->_extension_for_db); |
113
|
2
|
|
|
|
|
5
|
foreach my $file ( @schema_files ) { |
114
|
4
|
100
|
|
|
|
8
|
next if $file eq $main_db_file; |
115
|
2
|
|
|
|
|
7
|
my $schema = $self->_schema_from_schema_filename($file); |
116
|
|
|
|
|
|
|
|
117
|
2
|
|
|
|
|
15
|
my $pathname = File::Spec->catfile($server_directory, $file); |
118
|
2
|
50
|
|
|
|
25
|
$dbh->do("ATTACH DATABASE '$pathname' as $schema") |
119
|
|
|
|
|
|
|
|| Carp::croak("Could not attach schema file $file: ".$dbh->errstr); |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
sub _schema_files_in_directory { |
124
|
2
|
|
|
2
|
|
3
|
my($self, $dir) = @_; |
125
|
|
|
|
|
|
|
|
126
|
2
|
|
|
|
|
25
|
my $dh = IO::Dir->new($dir); |
127
|
|
|
|
|
|
|
|
128
|
2
|
|
|
|
|
174
|
my @files; |
129
|
2
|
|
|
|
|
11
|
while (my $name = $dh->read) { |
130
|
8
|
|
|
|
|
167
|
my $pathname = File::Spec->catfile($dir, $name); |
131
|
8
|
100
|
|
|
|
89
|
next unless -f $pathname; |
132
|
4
|
50
|
|
|
|
19
|
push(@files, $name) if $self->_schema_from_schema_filename($name); |
133
|
|
|
|
|
|
|
} |
134
|
2
|
|
|
|
|
22
|
return @files; |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
sub _schema_from_schema_filename { |
138
|
6
|
|
|
6
|
|
8
|
my($self, $pathname) = @_; |
139
|
|
|
|
|
|
|
|
140
|
6
|
|
|
|
|
13
|
my($schema, $dir, $ext) = File::Basename::fileparse($pathname, $self->_extension_for_db); |
141
|
6
|
50
|
|
|
|
30
|
return $ext ? $schema : undef; |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
sub database_exists { |
145
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
146
|
0
|
0
|
|
|
|
0
|
return 1 if -e $self->server; |
147
|
0
|
0
|
|
|
|
0
|
return 1 if -e $self->_data_dump_path; # exists virtually, and will dynamicaly instantiate |
148
|
0
|
|
|
|
|
0
|
return; |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
sub create_database { |
152
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
153
|
0
|
0
|
|
|
|
0
|
die "Database exists!" if $self->database_exists; |
154
|
0
|
|
|
|
|
0
|
my $path = $self->server; |
155
|
0
|
0
|
|
|
|
0
|
return 1 if IO::File->new(">$path"); |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
|
158
|
217
|
|
|
217
|
0
|
626
|
sub can_savepoint { 0;} # Dosen't support savepoints |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
# SQLite API |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
sub _schema_path { |
163
|
290
|
|
|
290
|
|
79536
|
return shift->_database_file_path() . '-schema'; |
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
sub _data_dump_path { |
167
|
147
|
|
|
147
|
|
591
|
return shift->_database_file_path() . '-dump'; |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# FIXME is there a way to make this an object parameter instead of a method |
171
|
|
|
|
|
|
|
sub server { |
172
|
579
|
|
|
579
|
0
|
10930
|
my $self = shift->_singleton_object(); |
173
|
579
|
|
|
|
|
2213
|
my $path = $self->__meta__->module_path; |
174
|
579
|
|
|
|
|
2284
|
my $ext = $self->_extension_for_db; |
175
|
579
|
50
|
|
|
|
3287
|
$path =~ s/\.pm$/$ext/ or Carp::croak("Odd module path $path. Expected something endining in '.pm'"); |
176
|
|
|
|
|
|
|
|
177
|
579
|
|
|
|
|
22873
|
my $dir = File::Basename::dirname($path); |
178
|
579
|
|
|
|
|
2648
|
return $path; |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
*_database_file_path = \&server; |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
sub _extension_for_db { |
184
|
654
|
|
|
654
|
|
9769
|
'.sqlite3'; |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
sub _journal_file_path { |
188
|
115
|
|
|
115
|
|
2402
|
my $self = shift->_singleton_object(); |
189
|
115
|
|
|
|
|
466
|
return $self->server . "-journal"; |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
sub _init_database { |
193
|
176
|
|
|
176
|
|
2785
|
my $self = shift->_singleton_object(); |
194
|
|
|
|
|
|
|
|
195
|
176
|
|
|
|
|
910
|
my $db_file = $self->server; |
196
|
176
|
|
|
|
|
4377
|
my $dump_file = $self->_data_dump_path; |
197
|
176
|
|
|
|
|
1430
|
my $schema_file = $self->_schema_path; |
198
|
|
|
|
|
|
|
|
199
|
176
|
|
|
|
|
4514
|
my $db_time = (stat($db_file))[9]; |
200
|
176
|
|
|
|
|
1830
|
my $dump_time = (stat($dump_file))[9]; |
201
|
176
|
|
|
|
|
1939
|
my $schema_time = (stat($schema_file))[9]; |
202
|
|
|
|
|
|
|
|
203
|
176
|
50
|
33
|
|
|
1761
|
if ($schema_time && ((-e $db_file and $schema_time > $db_time) or (-e $dump_file and $schema_time > $dump_time))) { |
|
|
|
66
|
|
|
|
|
204
|
0
|
|
|
|
|
0
|
$self->warning_message("Schema file is newer than the db file or the dump file. Replacing db_file $db_file."); |
205
|
0
|
|
|
|
|
0
|
my $dbbak_file = $db_file . '-bak'; |
206
|
0
|
|
|
|
|
0
|
my $dumpbak_file = $dump_file . '-bak'; |
207
|
0
|
0
|
|
|
|
0
|
unlink $dbbak_file if -e $dbbak_file; |
208
|
0
|
0
|
|
|
|
0
|
unlink $dumpbak_file if -e $dumpbak_file; |
209
|
0
|
0
|
|
|
|
0
|
rename $db_file, $dbbak_file if -e $db_file; |
210
|
0
|
0
|
|
|
|
0
|
rename $dump_file, $dumpbak_file if -e $dump_file; |
211
|
0
|
0
|
|
|
|
0
|
if (-e $db_file) { |
212
|
0
|
|
|
|
|
0
|
Carp::croak "Failed to move out-of-date file $db_file out of the way for reconstruction! $!"; |
213
|
|
|
|
|
|
|
} |
214
|
0
|
0
|
|
|
|
0
|
if (-e $dump_file) { |
215
|
0
|
|
|
|
|
0
|
Carp::croak "Failed to move out-of-date file $dump_file out of the way for reconstruction! $!"; |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
} |
218
|
176
|
100
|
|
|
|
1584
|
if (-e $db_file) { |
219
|
137
|
50
|
66
|
|
|
725
|
if ($dump_time && ($db_time < $dump_time)) { |
220
|
0
|
|
|
|
|
0
|
my $bak_file = $db_file . '-bak'; |
221
|
0
|
|
|
|
|
0
|
$self->warning_message("Dump file is newer than the db file. Replacing db_file $db_file."); |
222
|
0
|
0
|
|
|
|
0
|
unlink $bak_file if -e $bak_file; |
223
|
0
|
|
|
|
|
0
|
rename $db_file, $bak_file; |
224
|
0
|
0
|
|
|
|
0
|
if (-e $db_file) { |
225
|
0
|
|
|
|
|
0
|
Carp::croak "Failed to move out-of-date file $db_file out of the way for reconstruction! $!"; |
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
# NOTE: don't make this an "else", since we might go into both branches because we delete the file above. |
231
|
176
|
100
|
|
|
|
1447
|
unless (-e $db_file) { |
232
|
|
|
|
|
|
|
# initialize a new database from the one in the base class |
233
|
|
|
|
|
|
|
# should this be moved to connect time? |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
# TODO: auto re-create things as needed based on timestamp |
236
|
|
|
|
|
|
|
|
237
|
39
|
50
|
0
|
|
|
425
|
if (-e $dump_file) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# create from dump |
239
|
39
|
|
|
|
|
613
|
$self->warning_message("Re-creating $db_file from $dump_file."); |
240
|
39
|
|
|
|
|
334
|
$self->_load_db_from_dump_internal($dump_file); |
241
|
39
|
50
|
|
|
|
937
|
unless (-e $db_file) { |
242
|
0
|
|
|
|
|
0
|
Carp::croak("Failed to import $dump_file into $db_file!"); |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
elsif ( (not -e $db_file) and (-e $schema_file) ) { |
246
|
|
|
|
|
|
|
# create from schema |
247
|
0
|
|
|
|
|
0
|
$self->warning_message("Re-creating $db_file from $schema_file."); |
248
|
0
|
|
|
|
|
0
|
$self->_load_db_from_dump_internal($schema_file); |
249
|
0
|
0
|
|
|
|
0
|
unless (-e $db_file) { |
250
|
0
|
|
|
|
|
0
|
Carp::croak("Failed to import $dump_file into $db_file!"); |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
elsif ($self->class ne __PACKAGE__) { |
254
|
|
|
|
|
|
|
# copy from the parent class (disabled) |
255
|
0
|
|
|
|
|
0
|
Carp::croak("No schema or dump file found for $db_file.\n Tried schema path $schema_file\n and dump path $dump_file\nIf you still have *sqlite3n* SQLite database files please rename them to *sqlite3*, without the 'n'"); |
256
|
|
|
|
|
|
|
|
257
|
0
|
|
|
|
|
0
|
my $template_database_file = $self->SUPER::server(); |
258
|
0
|
0
|
|
|
|
0
|
unless (-e $template_database_file) { |
259
|
0
|
|
|
|
|
0
|
Carp::croak("Missing template database file: $db_file! Cannot initialize database for " . $self->class); |
260
|
|
|
|
|
|
|
} |
261
|
0
|
0
|
|
|
|
0
|
unless(File::Copy::copy($template_database_file,$db_file)) { |
262
|
0
|
|
|
|
|
0
|
Carp::croak("Error copying $db_file to $template_database_file to initialize database!"); |
263
|
|
|
|
|
|
|
} |
264
|
0
|
0
|
|
|
|
0
|
unless(-e $db_file) { |
265
|
0
|
|
|
|
|
0
|
Carp::croak("File $db_file not found after copy from $template_database_file. Cannot initialize database!"); |
266
|
|
|
|
|
|
|
} |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
else { |
269
|
0
|
|
|
|
|
0
|
Carp::croak("No db file found, and no dump or schema file found from which to re-construct a db file!"); |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
} |
272
|
176
|
|
|
|
|
454
|
return 1; |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
*_init_created_dbh = \&init_created_handle; |
276
|
|
|
|
|
|
|
sub init_created_handle |
277
|
|
|
|
|
|
|
{ |
278
|
172
|
|
|
172
|
0
|
354
|
my ($self, $dbh) = @_; |
279
|
172
|
50
|
|
|
|
609
|
return unless defined $dbh; |
280
|
172
|
|
|
|
|
1247
|
$dbh->{LongTruncOk} = 0; |
281
|
|
|
|
|
|
|
# wait one minute busy timeout |
282
|
172
|
|
|
|
|
1400
|
$dbh->func(1800000,'busy_timeout'); |
283
|
172
|
|
|
|
|
357
|
return $dbh; |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
sub _ignore_table { |
287
|
2
|
|
|
2
|
|
4
|
my $self = shift; |
288
|
2
|
|
|
|
|
4
|
my $table_name = shift; |
289
|
2
|
50
|
|
|
|
13
|
return 1 if $table_name =~ /^(sqlite|\$|URMETA)/; |
290
|
|
|
|
|
|
|
} |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
sub _get_sequence_name_for_table_and_column { |
294
|
24
|
|
|
24
|
|
726
|
my $self = shift->_singleton_object; |
295
|
24
|
|
|
|
|
61
|
my ($table_name,$column_name) = @_; |
296
|
|
|
|
|
|
|
|
297
|
24
|
|
|
|
|
139
|
my $dbh = $self->get_default_handle(); |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
# See if the sequence generator "table" is already there |
300
|
24
|
|
|
|
|
171
|
my $seq_table = sprintf('URMETA_%s_%s_seq', $table_name, $column_name); |
301
|
24
|
100
|
66
|
|
|
587
|
unless ($self->{'_has_sequence_generator'}->{$seq_table} or |
302
|
42
|
|
|
|
|
73
|
grep {$_ eq $seq_table} $self->get_table_names() ) { |
303
|
22
|
50
|
|
|
|
311
|
unless ($dbh->do("CREATE TABLE IF NOT EXISTS $seq_table (next_value integer PRIMARY KEY AUTOINCREMENT)")) { |
304
|
0
|
|
|
|
|
0
|
die "Failed to create sequence generator $seq_table: ".$dbh->errstr(); |
305
|
|
|
|
|
|
|
} |
306
|
|
|
|
|
|
|
} |
307
|
24
|
|
|
|
|
6433
|
$self->{'_has_sequence_generator'}->{$seq_table} = 1; |
308
|
|
|
|
|
|
|
|
309
|
24
|
|
|
|
|
117
|
return $seq_table; |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
sub _get_next_value_from_sequence { |
313
|
94
|
|
|
94
|
|
333
|
my($self,$sequence_name) = @_; |
314
|
|
|
|
|
|
|
|
315
|
94
|
|
|
|
|
295
|
my $dbh = $self->get_default_handle(); |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
# FIXME can we use a statement handle with a wildcard as the table name here? |
318
|
94
|
100
|
|
|
|
593
|
unless ($dbh->do("INSERT into $sequence_name values(null)")) { |
319
|
3
|
|
|
|
|
47
|
die "Failed to INSERT into $sequence_name during id autogeneration: " . $dbh->errstr; |
320
|
|
|
|
|
|
|
} |
321
|
|
|
|
|
|
|
|
322
|
91
|
|
|
|
|
7481
|
my $new_id = $dbh->last_insert_id(undef,undef,$sequence_name,'next_value'); |
323
|
91
|
50
|
|
|
|
219
|
unless (defined $new_id) { |
324
|
0
|
|
|
|
|
0
|
die "last_insert_id() returned undef during id autogeneration after insert into $sequence_name: " . $dbh->errstr; |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
|
327
|
91
|
50
|
|
|
|
466
|
unless($dbh->do("DELETE from $sequence_name where next_value = $new_id")) { |
328
|
0
|
|
|
|
|
0
|
die "DELETE from $sequence_name for next_value $new_id failed during id autogeneration"; |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
91
|
|
|
|
|
3131
|
return $new_id; |
332
|
|
|
|
|
|
|
} |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
# Overriding this so we can force the schema to 'main' for older versions of SQLite |
336
|
|
|
|
|
|
|
# |
337
|
|
|
|
|
|
|
# NOTE: table_info (called by SUPER::get_table_details_from_data_dictionary) in older |
338
|
|
|
|
|
|
|
# versions of DBD::SQLite does not return data for tables in other attached databases. |
339
|
|
|
|
|
|
|
# |
340
|
|
|
|
|
|
|
# This probably isn't an issue... Due to the limited number of people using older DBD::SQLite |
341
|
|
|
|
|
|
|
# (of particular note is that OSX 10.5 and earlier use such an old version), interseted with |
342
|
|
|
|
|
|
|
# the limited number of people using attached databases, it's probably not a problem. |
343
|
|
|
|
|
|
|
# The commit_between_schemas test does do this. If it turns out it is a problem, we could |
344
|
|
|
|
|
|
|
# appropriate the code from recent DBD::SQLite::table_info |
345
|
|
|
|
|
|
|
sub get_table_details_from_data_dictionary { |
346
|
29
|
|
|
29
|
0
|
51
|
my $self = shift; |
347
|
|
|
|
|
|
|
|
348
|
29
|
|
|
|
|
214
|
my $sth = $self->SUPER::get_table_details_from_data_dictionary(@_); |
349
|
29
|
|
|
|
|
502
|
my $sqlite_version = version->parse($DBD::SQLite::VERSION); |
350
|
29
|
|
|
|
|
205
|
my $needed_version = version->parse("1.26_04"); |
351
|
29
|
50
|
33
|
|
|
295
|
if ($sqlite_version >= $needed_version || !$sth) { |
352
|
29
|
|
|
|
|
122
|
return $sth; |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
|
355
|
0
|
|
|
|
|
0
|
my($catalog,$schema,$table_name) = @_; |
356
|
|
|
|
|
|
|
|
357
|
0
|
|
|
|
|
0
|
my @tables; |
358
|
|
|
|
|
|
|
my @returned_names; |
359
|
0
|
|
|
|
|
0
|
while (my $info = $sth->fetchrow_hashref()) { |
360
|
|
|
|
|
|
|
#@returned_names ||= (keys %$info); |
361
|
0
|
0
|
|
|
|
0
|
unless (@returned_names) { |
362
|
0
|
|
|
|
|
0
|
@returned_names = keys(%$info); |
363
|
|
|
|
|
|
|
} |
364
|
0
|
|
0
|
|
|
0
|
$info->{'TABLE_SCHEM'} ||= 'main'; |
365
|
0
|
|
|
|
|
0
|
push @tables, $info; |
366
|
|
|
|
|
|
|
} |
367
|
|
|
|
|
|
|
|
368
|
0
|
|
|
|
|
0
|
my $dbh = $self->get_default_handle(); |
369
|
0
|
0
|
|
|
|
0
|
my $sponge = DBI->connect("DBI:Sponge:", '','') |
370
|
|
|
|
|
|
|
or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr"); |
371
|
|
|
|
|
|
|
|
372
|
0
|
0
|
|
|
|
0
|
unless (@returned_names) { |
373
|
0
|
|
|
|
|
0
|
@returned_names = qw( TABLE_CAT TABLE_SCHEM TABLE_NAME TABLE_TYPE REMARKS ); |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
my $returned_sth = $sponge->prepare("table_info $table_name", { |
376
|
0
|
0
|
|
|
|
0
|
rows => [ map { [ @{$_}{@returned_names} ] } @tables ], |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
377
|
|
|
|
|
|
|
NUM_OF_FIELDS => scalar @returned_names, |
378
|
|
|
|
|
|
|
NAME => \@returned_names, |
379
|
|
|
|
|
|
|
}) or return $dbh->DBI::set_err($sponge->err(), $sponge->errstr()); |
380
|
|
|
|
|
|
|
|
381
|
0
|
|
|
|
|
0
|
return $returned_sth; |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
# DBD::SQLite doesn't implement column_info. This is the UR::DataSource version of the same thing |
386
|
|
|
|
|
|
|
sub get_column_details_from_data_dictionary { |
387
|
29
|
|
|
29
|
0
|
75
|
my($self,$catalog,$schema,$table,$column) = @_; |
388
|
|
|
|
|
|
|
|
389
|
29
|
|
|
|
|
111
|
my $dbh = $self->get_default_handle(); |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
# Convert the SQL wildcards to regex wildcards |
392
|
29
|
50
|
|
|
|
99
|
$column = '' unless defined $column; |
393
|
29
|
|
|
|
|
112
|
$column =~ s/%/.*/; |
394
|
29
|
|
|
|
|
60
|
$column =~ s/_/./; |
395
|
29
|
|
|
|
|
532
|
my $column_regex = qr(^$column$); |
396
|
|
|
|
|
|
|
|
397
|
29
|
|
|
|
|
226
|
my $sth_tables = $dbh->table_info($catalog, $schema, $table, 'TABLE'); |
398
|
29
|
|
|
|
|
195
|
my @table_names = map { $_->{'TABLE_NAME'} } @{ $sth_tables->fetchall_arrayref({}) }; |
|
29
|
|
|
|
|
120
|
|
|
29
|
|
|
|
|
132
|
|
399
|
|
|
|
|
|
|
|
400
|
29
|
|
|
|
|
107
|
my $override_owner; |
401
|
29
|
50
|
|
|
|
164
|
if ($DBD::SQLite::VERSION < 1.26_04) { |
402
|
0
|
|
|
|
|
0
|
$override_owner = 'main'; |
403
|
|
|
|
|
|
|
} |
404
|
|
|
|
|
|
|
|
405
|
29
|
|
|
|
|
43
|
my @columns; |
406
|
29
|
|
|
|
|
65
|
foreach my $table_name ( @table_names ) { |
407
|
|
|
|
|
|
|
|
408
|
29
|
50
|
|
|
|
148
|
my $sth = $dbh->prepare("PRAGMA table_info($table_name)") |
409
|
|
|
|
|
|
|
or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr"); |
410
|
29
|
50
|
|
|
|
116
|
$sth->execute() or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr"); |
411
|
|
|
|
|
|
|
|
412
|
29
|
|
|
|
|
106
|
while (my $info = $sth->fetchrow_hashref()) { |
413
|
|
|
|
|
|
|
|
414
|
68
|
50
|
|
|
|
374
|
next unless $info->{'name'} =~ m/$column_regex/; |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
# SQLite doesn't parse our that type varchar(255) actually means type varchar size 255 |
417
|
68
|
|
|
|
|
100
|
my $data_type = $info->{'type'}; |
418
|
68
|
|
|
|
|
66
|
my $column_size; |
419
|
68
|
100
|
|
|
|
182
|
if ($data_type =~ m/(\S+)\s*\((\S+)\)/) { |
420
|
1
|
|
|
|
|
3
|
$data_type = $1; |
421
|
1
|
|
|
|
|
1
|
$column_size = $2; |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
|
424
|
68
|
|
|
|
|
104
|
my $node = {}; |
425
|
68
|
|
|
|
|
139
|
$node->{'TABLE_CAT'} = $catalog; |
426
|
68
|
|
66
|
|
|
243
|
$node->{'TABLE_SCHEM'} = $schema || $override_owner; |
427
|
68
|
|
|
|
|
105
|
$node->{'TABLE_NAME'} = $table_name; |
428
|
68
|
|
|
|
|
117
|
$node->{'COLUMN_NAME'} = $info->{'name'}; |
429
|
68
|
|
|
|
|
101
|
$node->{'DATA_TYPE'} = $data_type; |
430
|
68
|
|
|
|
|
93
|
$node->{'TYPE_NAME'} = $data_type; |
431
|
68
|
|
|
|
|
99
|
$node->{'COLUMN_SIZE'} = $column_size; |
432
|
68
|
|
|
|
|
171
|
$node->{'NULLABLE'} = ! $info->{'notnull'}; |
433
|
68
|
100
|
|
|
|
178
|
$node->{'IS_NULLABLE'} = ($node->{'NULLABLE'} ? 'YES' : 'NO'); |
434
|
68
|
|
|
|
|
90
|
$node->{'REMARKS'} = ""; |
435
|
68
|
|
|
|
|
99
|
$node->{'SQL_DATA_TYPE'} = ""; # FIXME shouldn't this be something related to DATA_TYPE |
436
|
68
|
|
|
|
|
108
|
$node->{'SQL_DATETIME_SUB'} = ""; |
437
|
68
|
|
|
|
|
88
|
$node->{'CHAR_OCTET_LENGTH'} = undef; # FIXME this should be the same as column_size, right? |
438
|
68
|
|
|
|
|
87
|
$node->{'ORDINAL_POSITION'} = $info->{'cid'}; |
439
|
68
|
|
|
|
|
87
|
$node->{'COLUMN_DEF'} = $info->{'dflt_value'}; |
440
|
|
|
|
|
|
|
# Remove starting and ending 's that appear erroneously with string default values |
441
|
68
|
100
|
|
|
|
152
|
$node->{'COLUMN_DEF'} =~ s/^'|'$//g if defined ( $node->{'COLUMN_DEF'}); |
442
|
|
|
|
|
|
|
|
443
|
68
|
|
|
|
|
267
|
push @columns, $node; |
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
} |
446
|
|
|
|
|
|
|
|
447
|
29
|
50
|
|
|
|
232
|
my $sponge = DBI->connect("DBI:Sponge:", '','') |
448
|
|
|
|
|
|
|
or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr"); |
449
|
|
|
|
|
|
|
|
450
|
29
|
|
|
|
|
7021
|
my @returned_names = qw( TABLE_CAT TABLE_SCHEM TABLE_NAME COLUMN_NAME DATA_TYPE TYPE_NAME COLUMN_SIZE |
451
|
|
|
|
|
|
|
BUFFER_LENGTH DECIMAL_DIGITS NUM_PREC_RADIX NULLABLE REMARKS COLUMN_DEF |
452
|
|
|
|
|
|
|
SQL_DATA_TYPE SQL_DATETIME_SUB CHAR_OCTET_LENGTH ORDINAL_POSITION IS_NULLABLE ); |
453
|
|
|
|
|
|
|
my $returned_sth = $sponge->prepare("column_info $table", { |
454
|
29
|
50
|
|
|
|
106
|
rows => [ map { [ @{$_}{@returned_names} ] } @columns ], |
|
68
|
|
|
|
|
80
|
|
|
68
|
|
|
|
|
602
|
|
455
|
|
|
|
|
|
|
NUM_OF_FIELDS => scalar @returned_names, |
456
|
|
|
|
|
|
|
NAME => \@returned_names, |
457
|
|
|
|
|
|
|
}) or return $dbh->DBI::set_err($sponge->err(), $sponge->errstr()); |
458
|
|
|
|
|
|
|
|
459
|
29
|
|
|
|
|
2605
|
return $returned_sth; |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
# SQLite doesn't store the name of a foreign key constraint in its metadata directly. |
464
|
|
|
|
|
|
|
# We can guess at it from the SQL used in the table creation. These regexes are probably |
465
|
|
|
|
|
|
|
# sloppy. We could replace them if there were a good SQL parser. |
466
|
|
|
|
|
|
|
sub _resolve_fk_name { |
467
|
43
|
|
|
43
|
|
82
|
my($self, $table_name, $column_list, $r_table_name, $r_column_list) = @_; |
468
|
|
|
|
|
|
|
|
469
|
43
|
50
|
|
|
|
120
|
if (@$column_list != @$r_column_list) { |
470
|
0
|
|
|
|
|
0
|
Carp::confess('There are '.scalar(@$column_list).' pk columns and '.scalar(@$r_column_list).' fk columns'); |
471
|
|
|
|
|
|
|
} |
472
|
|
|
|
|
|
|
|
473
|
43
|
|
|
|
|
110
|
my($table_info) = $self->_get_info_from_sqlite_master($table_name, 'table'); |
474
|
43
|
50
|
|
|
|
117
|
return unless $table_info; |
475
|
|
|
|
|
|
|
|
476
|
43
|
|
|
|
|
76
|
my $col_str = $table_info->{'sql'}; |
477
|
43
|
|
|
|
|
665
|
$col_str =~ s/^\s+|\s+$//g; # Remove leading and trailing whitespace |
478
|
43
|
|
|
|
|
179
|
$col_str =~ s/\s{2,}/ /g; # Remove multiple spaces |
479
|
43
|
50
|
|
|
|
243
|
if ($col_str =~ m/^CREATE TABLE (\w+)\s*?\((.*?)\)$/is) { |
480
|
43
|
50
|
|
|
|
128
|
unless ($1 eq $table_name) { |
481
|
0
|
|
|
|
|
0
|
Carp::croak("Table creation SQL for $table_name is inconsistent. Didn't find table name '$table_name' in string '$col_str'. Found $1 instead."); |
482
|
|
|
|
|
|
|
} |
483
|
43
|
|
|
|
|
102
|
$col_str = $2; |
484
|
|
|
|
|
|
|
} else { |
485
|
0
|
|
|
|
|
0
|
Carp::croak("Couldn't parse SQL for $table_name"); |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
|
489
|
43
|
|
|
|
|
53
|
my $fk_name; |
490
|
43
|
100
|
|
|
|
110
|
if (@$column_list > 1) { |
491
|
|
|
|
|
|
|
# Multiple column FKs must be specified as a table-wide constraint, and has a well-known format |
492
|
10
|
|
|
|
|
31
|
my $fk_list = '\s*' . join('\s*,\s*', @$column_list) . '\s*'; |
493
|
10
|
|
|
|
|
21
|
my $uk_list = '\s*' . join('\s*,\s*', @$r_column_list) . '\s*'; |
494
|
10
|
|
|
|
|
42
|
my $expected_to_find = sprintf('FOREIGN KEY\s*\(%s\) REFERENCES %s\s*\(%s\)', |
495
|
|
|
|
|
|
|
$fk_list, |
496
|
|
|
|
|
|
|
$r_table_name, |
497
|
|
|
|
|
|
|
$uk_list); |
498
|
10
|
|
|
|
|
111
|
my $regex = qr($expected_to_find)i; |
499
|
|
|
|
|
|
|
|
500
|
10
|
100
|
|
|
|
55
|
if ($col_str =~ m/$regex/) { |
501
|
8
|
|
|
|
|
50
|
($fk_name) = ($col_str =~ m/CONSTRAINT (\w+) FOREIGN KEY\s*\($fk_list\)/i); |
502
|
|
|
|
|
|
|
} else { |
503
|
|
|
|
|
|
|
# Didn't find anything... |
504
|
2
|
|
|
|
|
12
|
return; |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
} else { |
508
|
|
|
|
|
|
|
# single-column FK constraints can be specified a couple of ways... |
509
|
|
|
|
|
|
|
# First, try as a table-wide constraint |
510
|
33
|
|
|
|
|
55
|
my $col = $column_list->[0]; |
511
|
33
|
|
|
|
|
46
|
my $r_col = $r_column_list->[0]; |
512
|
33
|
100
|
|
|
|
536
|
if ($col_str =~ m/FOREIGN KEY\s*\($col\)\s*REFERENCES $r_table_name\s*\($r_col\)/i) { |
513
|
8
|
|
|
|
|
41
|
($fk_name) = ($col_str =~ m/CONSTRAINT\s+(\w+)\s+FOREIGN KEY\s*\($col\)/i); |
514
|
|
|
|
|
|
|
} else { |
515
|
25
|
|
|
|
|
75
|
while ($col_str) { |
516
|
|
|
|
|
|
|
# Try parsing each of the column definitions |
517
|
|
|
|
|
|
|
# commas can't appear in here except to separate each column, right? |
518
|
58
|
|
|
|
|
53
|
my $this_col; |
519
|
58
|
100
|
|
|
|
258
|
if ($col_str =~ m/^(.*?)\s*,\s*(.*)/) { |
520
|
43
|
|
|
|
|
85
|
$this_col = $1; |
521
|
43
|
|
|
|
|
61
|
$col_str = $2; |
522
|
|
|
|
|
|
|
} else { |
523
|
15
|
|
|
|
|
26
|
$this_col = $col_str; |
524
|
15
|
|
|
|
|
27
|
$col_str = ''; |
525
|
|
|
|
|
|
|
} |
526
|
|
|
|
|
|
|
|
527
|
58
|
|
|
|
|
173
|
my($col_name, $col_type) = ($this_col =~ m/^(\w+) (\w+)/); |
528
|
58
|
100
|
100
|
|
|
271
|
next unless ($col_name and |
529
|
|
|
|
|
|
|
$col_name eq $col); |
530
|
|
|
|
|
|
|
|
531
|
23
|
50
|
|
|
|
214
|
if ($this_col =~ m/REFERENCES $r_table_name\s*\($r_col\)/i) { |
532
|
|
|
|
|
|
|
# It's the right column, and there's a FK constraint on it |
533
|
|
|
|
|
|
|
# Did the FK get a name? |
534
|
23
|
|
|
|
|
55
|
($fk_name) = ($this_col =~ m/CONSTRAINT (\w+) REFERENCES/i); |
535
|
23
|
|
|
|
|
50
|
last; |
536
|
|
|
|
|
|
|
} else { |
537
|
|
|
|
|
|
|
# It's the right column, but there's no FK |
538
|
0
|
|
|
|
|
0
|
return; |
539
|
|
|
|
|
|
|
} |
540
|
|
|
|
|
|
|
} |
541
|
|
|
|
|
|
|
} |
542
|
|
|
|
|
|
|
} |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
# The constraint didn't have a name. Make up something that'll likely be unique |
545
|
41
|
|
66
|
|
|
195
|
$fk_name ||= join('_', $table_name, @$column_list, $r_table_name, @$r_column_list, 'fk'); |
546
|
41
|
|
|
|
|
121
|
return $fk_name; |
547
|
|
|
|
|
|
|
} |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
# We'll only support specifying $fk_table or $pk_table but not both |
551
|
|
|
|
|
|
|
# $fk_table refers to the table where the fk is attached |
552
|
|
|
|
|
|
|
# $pk_table refers to the table the pk points to - where the primary key exists |
553
|
|
|
|
|
|
|
sub get_foreign_key_details_from_data_dictionary { |
554
|
88
|
|
|
88
|
0
|
36607
|
my($self, $pk_catalog, $pk_schema, $pk_table, $fk_catalog, $fk_schema, $fk_table) = @_; |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
# first, build a data structure to collect columns of the same foreign key together |
557
|
88
|
|
|
|
|
114
|
my @returned_fk_info; |
558
|
88
|
100
|
|
|
|
232
|
if ($fk_table) { |
|
|
50
|
|
|
|
|
|
559
|
50
|
|
|
|
|
175
|
@returned_fk_info = $self->_get_foreign_key_details_for_fk_table_name($fk_schema, $fk_table); |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
} elsif ($pk_table) { |
562
|
|
|
|
|
|
|
# We'll have to loop through each table in the DB and find FKs that reference |
563
|
|
|
|
|
|
|
# the named table |
564
|
|
|
|
|
|
|
|
565
|
38
|
|
|
|
|
215
|
my @tables = $self->_get_info_from_sqlite_master(undef,'table'); |
566
|
|
|
|
|
|
|
TABLE: |
567
|
38
|
|
|
|
|
89
|
foreach my $table_data ( @tables ) { |
568
|
226
|
|
|
|
|
308
|
my $from_table = $table_data->{'table_name'}; |
569
|
226
|
|
|
220
|
|
1067
|
push @returned_fk_info, $self->_get_foreign_key_details_for_fk_table_name($fk_schema, $from_table, sub { $_[0]->{table} eq $pk_table }); |
|
220
|
|
|
|
|
910
|
|
570
|
|
|
|
|
|
|
} |
571
|
|
|
|
|
|
|
} else { |
572
|
0
|
|
|
|
|
0
|
Carp::croak("Can't get_foreign_key_details_from_data_dictionary(): either pk_table ($pk_table) or fk_table ($fk_table) are required"); |
573
|
|
|
|
|
|
|
} |
574
|
|
|
|
|
|
|
|
575
|
88
|
|
|
|
|
314
|
my $dbh = $self->get_default_handle; |
576
|
88
|
50
|
|
|
|
743
|
my $sponge = DBI->connect("DBI:Sponge:", '','') |
577
|
|
|
|
|
|
|
or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr"); |
578
|
|
|
|
|
|
|
|
579
|
88
|
|
|
|
|
13296
|
my @returned_names = qw( UK_TABLE_CAT UK_TABLE_SCHEM UK_TABLE_NAME UK_COLUMN_NAME |
580
|
|
|
|
|
|
|
FK_TABLE_CAT FK_TABLE_SCHEM FK_TABLE_NAME FK_COLUMN_NAME |
581
|
|
|
|
|
|
|
ORDINAL_POSITION UPDATE_RULE DELETE_RULE FK_NAME UK_NAME DEFERABILITY ); |
582
|
88
|
|
66
|
|
|
308
|
my $table = $pk_table || $fk_table; |
583
|
|
|
|
|
|
|
my $returned_sth = $sponge->prepare("foreign_key_info $table", { |
584
|
88
|
50
|
|
|
|
560
|
rows => [ map { [ @{$_}{@returned_names} ] } @returned_fk_info ], |
|
53
|
|
|
|
|
55
|
|
|
53
|
|
|
|
|
477
|
|
585
|
|
|
|
|
|
|
NUM_OF_FIELDS => scalar @returned_names, |
586
|
|
|
|
|
|
|
NAME => \@returned_names, |
587
|
|
|
|
|
|
|
}) or return $dbh->DBI::set_err($sponge->err(), $sponge->errstr()); |
588
|
|
|
|
|
|
|
|
589
|
88
|
|
|
|
|
6108
|
return $returned_sth; |
590
|
|
|
|
|
|
|
} |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
# used by _get_foreign_key_details_for_fk_table_name to convert the on_delete or on_update |
593
|
|
|
|
|
|
|
# string into the number code commonly returnd by DBI |
594
|
|
|
|
|
|
|
my %update_delete_action_to_numeric_code = ( |
595
|
|
|
|
|
|
|
CASCADE => 0, |
596
|
|
|
|
|
|
|
RESTRICT => 1, |
597
|
|
|
|
|
|
|
'SET NULL' => 2, |
598
|
|
|
|
|
|
|
'NO ACTION' => 3, |
599
|
|
|
|
|
|
|
'SET DEFAULT' => 4, |
600
|
|
|
|
|
|
|
); |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
sub _get_foreign_key_details_for_fk_table_name { |
603
|
276
|
|
|
276
|
|
416
|
my($self, $fk_schema_name, $fk_table_name, $accept_rows) = @_; |
604
|
276
|
|
100
|
31
|
|
714
|
$accept_rows ||= sub { 1 }; # default is accept all |
|
31
|
|
|
|
|
78
|
|
605
|
|
|
|
|
|
|
|
606
|
276
|
|
100
|
|
|
837
|
$fk_schema_name ||= 'main'; |
607
|
276
|
|
|
|
|
464
|
my $qualified_table_name = join('.', $fk_schema_name, $fk_table_name); |
608
|
|
|
|
|
|
|
|
609
|
276
|
|
|
|
|
670
|
my $dbh = $self->get_default_handle; |
610
|
276
|
50
|
|
|
|
938
|
my $fksth = $dbh->prepare("PRAGMA foreign_key_list($fk_table_name)") |
611
|
|
|
|
|
|
|
or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr"); |
612
|
276
|
50
|
|
|
|
689
|
unless ($fksth->execute()) { |
613
|
0
|
|
|
|
|
0
|
$self->error_message("foreign_key_list execute failed: $DBI::errstr"); |
614
|
0
|
|
|
|
|
0
|
return; |
615
|
|
|
|
|
|
|
} |
616
|
|
|
|
|
|
|
|
617
|
276
|
|
|
|
|
287
|
my @fk_rows_this_table; |
618
|
276
|
|
|
|
|
266
|
my(@column_list, @r_column_list); |
619
|
276
|
|
|
|
|
653
|
while (my $row = $fksth->fetchrow_hashref) { |
620
|
251
|
100
|
|
|
|
399
|
next unless ($accept_rows->($row)); |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
my %fk_info_row = ( FK_TABLE_NAME => $fk_table_name, |
623
|
|
|
|
|
|
|
FK_TABLE_SCHEM => $fk_schema_name, |
624
|
|
|
|
|
|
|
UK_TABLE_SCHEM => $fk_schema_name, # SQLite doesn't tell us what attached DB it's from, so we'll guess |
625
|
|
|
|
|
|
|
UPDATE_RULE => $update_delete_action_to_numeric_code{$row->{on_update}}, |
626
|
|
|
|
|
|
|
DELETE_RULE => $update_delete_action_to_numeric_code{$row->{on_delete}}, |
627
|
53
|
|
|
|
|
382
|
ORDINAL_POSITION => $row->{seq} + 1, |
628
|
|
|
|
|
|
|
); |
629
|
|
|
|
|
|
|
@fk_info_row{'FK_COLUMN_NAME','UK_TABLE_NAME','UK_COLUMN_NAME'} |
630
|
53
|
|
|
|
|
223
|
= @$row{'from','table','to'}; |
631
|
|
|
|
|
|
|
|
632
|
53
|
|
|
|
|
87
|
push @fk_rows_this_table, \%fk_info_row; |
633
|
|
|
|
|
|
|
|
634
|
53
|
|
|
|
|
75
|
push @column_list, $row->{from}; |
635
|
|
|
|
|
|
|
push @r_column_list, $row->{to} |
636
|
53
|
|
|
|
|
202
|
} |
637
|
|
|
|
|
|
|
|
638
|
276
|
100
|
|
|
|
475
|
if (@fk_rows_this_table) { |
639
|
|
|
|
|
|
|
my $fk_name = $self->_resolve_fk_name($fk_rows_this_table[0]->{FK_TABLE_NAME}, |
640
|
|
|
|
|
|
|
\@column_list, |
641
|
|
|
|
|
|
|
$fk_rows_this_table[0]->{UK_TABLE_NAME}, # They'll all have the same table, right? |
642
|
43
|
|
|
|
|
244
|
\@r_column_list); |
643
|
43
|
|
|
|
|
82
|
foreach my $fk_info_row ( @fk_rows_this_table ) { |
644
|
53
|
|
|
|
|
122
|
$fk_info_row->{FK_NAME} = $fk_name; |
645
|
|
|
|
|
|
|
} |
646
|
43
|
|
|
|
|
103
|
@fk_rows_this_table = sort { $a->{ORDINAL_POSITION} <=> $b->{ORDINAL_POSITION} } @fk_rows_this_table; |
|
10
|
|
|
|
|
29
|
|
647
|
|
|
|
|
|
|
} |
648
|
276
|
|
|
|
|
694
|
return @fk_rows_this_table; |
649
|
|
|
|
|
|
|
} |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
sub get_bitmap_index_details_from_data_dictionary { |
652
|
|
|
|
|
|
|
# SQLite dosen't support bitmap indicies, so there aren't any |
653
|
0
|
|
|
0
|
0
|
0
|
return []; |
654
|
|
|
|
|
|
|
} |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
sub get_unique_index_details_from_data_dictionary { |
658
|
30
|
|
|
30
|
0
|
77
|
my($self, $owner_name, $table_name) = @_; |
659
|
|
|
|
|
|
|
|
660
|
30
|
|
|
|
|
135
|
my $dbh = $self->get_default_handle(); |
661
|
30
|
50
|
|
|
|
97
|
return undef unless $dbh; |
662
|
|
|
|
|
|
|
|
663
|
30
|
|
|
|
|
80
|
my($index_list_fcn, $index_info_fcn) = ('index_list','index_info'); |
664
|
30
|
100
|
|
|
|
81
|
if ($owner_name) { |
665
|
11
|
|
|
|
|
51
|
$index_list_fcn = "${owner_name}.${index_list_fcn}"; |
666
|
11
|
|
|
|
|
22
|
$index_info_fcn = "${owner_name}.${index_info_fcn}"; |
667
|
|
|
|
|
|
|
} |
668
|
|
|
|
|
|
|
|
669
|
30
|
|
|
|
|
196
|
my $idx_sth = $dbh->prepare(qq(PRAGMA ${index_list_fcn}($table_name))); |
670
|
|
|
|
|
|
|
|
671
|
30
|
50
|
|
|
|
110
|
return undef unless $idx_sth; |
672
|
|
|
|
|
|
|
|
673
|
30
|
|
|
|
|
127
|
$idx_sth->execute(); |
674
|
|
|
|
|
|
|
|
675
|
30
|
|
|
|
|
56
|
my $ret = {}; |
676
|
30
|
|
|
|
|
128
|
while(my $data = $idx_sth->fetchrow_hashref()) { |
677
|
10
|
50
|
|
|
|
41
|
next unless ($data->{'unique'}); |
678
|
|
|
|
|
|
|
|
679
|
10
|
|
|
|
|
21
|
my $idx_name = $data->{'name'}; |
680
|
10
|
|
|
|
|
50
|
my $idx_item_sth = $dbh->prepare(qq(PRAGMA ${index_info_fcn}($idx_name))); |
681
|
10
|
|
|
|
|
38
|
$idx_item_sth->execute(); |
682
|
10
|
|
|
|
|
32
|
while(my $index_item = $idx_item_sth->fetchrow_hashref()) { |
683
|
13
|
|
100
|
|
|
206
|
$ret->{$idx_name} ||= []; |
684
|
13
|
|
|
|
|
15
|
push( @{$ret->{$idx_name}}, $index_item->{'name'}); |
|
13
|
|
|
|
|
57
|
|
685
|
|
|
|
|
|
|
} |
686
|
|
|
|
|
|
|
} |
687
|
|
|
|
|
|
|
|
688
|
30
|
|
|
|
|
102
|
return $ret; |
689
|
|
|
|
|
|
|
} |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
# By default, make a text dump of the database at commit time. |
693
|
|
|
|
|
|
|
# This should really be a datasource property |
694
|
|
|
|
|
|
|
sub dump_on_commit { |
695
|
38
|
|
|
38
|
0
|
766
|
0; |
696
|
|
|
|
|
|
|
} |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
# We're overriding commit from UR::DS::commit() to add the behavior that after |
699
|
|
|
|
|
|
|
# the actual commit happens, we also make a dump of the database in text format |
700
|
|
|
|
|
|
|
# so that can be version controlled |
701
|
|
|
|
|
|
|
sub commit { |
702
|
115
|
|
|
115
|
1
|
152
|
my $self = shift; |
703
|
|
|
|
|
|
|
|
704
|
115
|
|
|
|
|
405
|
my $has_no_pending_trans = (!-f $self->_journal_file_path()); |
705
|
|
|
|
|
|
|
|
706
|
115
|
|
|
|
|
1868
|
my $worked = $self->SUPER::commit(@_); |
707
|
115
|
50
|
|
|
|
228
|
return unless $worked; |
708
|
|
|
|
|
|
|
|
709
|
115
|
|
|
|
|
384
|
my $db_filename = $self->server(); |
710
|
115
|
|
|
|
|
555
|
my $dump_filename = $self->_data_dump_path(); |
711
|
|
|
|
|
|
|
|
712
|
115
|
100
|
|
|
|
541
|
return 1 if ($has_no_pending_trans); |
713
|
|
|
|
|
|
|
|
714
|
38
|
50
|
33
|
|
|
231
|
return 1 unless $self->dump_on_commit or -e $dump_filename; |
715
|
|
|
|
|
|
|
|
716
|
0
|
|
|
|
|
0
|
return $self->_dump_db_to_file_internal(); |
717
|
|
|
|
|
|
|
} |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
# Get info out of the sqlite_master table. Returns a hashref keyed by 'name' |
721
|
|
|
|
|
|
|
# columns are: |
722
|
|
|
|
|
|
|
# type - 'table' or 'index' |
723
|
|
|
|
|
|
|
# name - Name of the object |
724
|
|
|
|
|
|
|
# table_name - name of the table this object references. For tables, it's the same as name, |
725
|
|
|
|
|
|
|
# for indexes, it's the name of the table it's indexing |
726
|
|
|
|
|
|
|
# rootpage - Used internally by sqlite |
727
|
|
|
|
|
|
|
# sql - The sql used to create the thing |
728
|
|
|
|
|
|
|
sub _get_info_from_sqlite_master { |
729
|
83
|
|
|
83
|
|
145
|
my($self, $name,$type) = @_; |
730
|
|
|
|
|
|
|
|
731
|
83
|
|
|
|
|
95
|
my($schema, @where, @exec_values); |
732
|
83
|
100
|
|
|
|
186
|
if ($name) { |
733
|
45
|
|
|
|
|
158
|
($schema, $name) = $self->_resolve_owner_and_table_from_table_name($name); |
734
|
45
|
|
|
|
|
84
|
push @where, 'name = ?'; |
735
|
45
|
|
|
|
|
57
|
push @exec_values, $name; |
736
|
|
|
|
|
|
|
} |
737
|
83
|
100
|
|
|
|
185
|
if ($type) { |
738
|
81
|
|
|
|
|
109
|
push @where, 'type = ?'; |
739
|
81
|
|
|
|
|
98
|
push @exec_values, $type; |
740
|
|
|
|
|
|
|
} |
741
|
|
|
|
|
|
|
|
742
|
83
|
100
|
|
|
|
170
|
my $sqlite_master_table = $schema |
743
|
|
|
|
|
|
|
? "${schema}.sqlite_master" |
744
|
|
|
|
|
|
|
: 'sqlite_master'; |
745
|
83
|
|
|
|
|
160
|
my $sql = "select * from $sqlite_master_table"; |
746
|
83
|
50
|
|
|
|
172
|
if (@where) { |
747
|
83
|
|
|
|
|
232
|
$sql .= ' where '.join(' and ', @where); |
748
|
|
|
|
|
|
|
} |
749
|
|
|
|
|
|
|
|
750
|
83
|
|
|
|
|
281
|
my $dbh = $self->get_default_handle(); |
751
|
83
|
|
|
|
|
264
|
my $sth = $dbh->prepare($sql); |
752
|
83
|
50
|
|
|
|
221
|
unless ($sth) { |
753
|
127
|
|
|
127
|
|
785
|
no warnings; |
|
127
|
|
|
|
|
202
|
|
|
127
|
|
|
|
|
7156
|
|
754
|
0
|
|
|
|
|
0
|
$self->error_message("Can't get table details for name $name and type $type: ".$dbh->errstr); |
755
|
0
|
|
|
|
|
0
|
return; |
756
|
|
|
|
|
|
|
} |
757
|
|
|
|
|
|
|
|
758
|
83
|
50
|
|
|
|
274
|
unless ($sth->execute(@exec_values)) { |
759
|
127
|
|
|
127
|
|
544
|
no warnings; |
|
127
|
|
|
|
|
179
|
|
|
127
|
|
|
|
|
183737
|
|
760
|
0
|
|
|
|
|
0
|
$self->error_message("Can't get table details for name $name and type $type: ".$dbh->errstr); |
761
|
0
|
|
|
|
|
0
|
return; |
762
|
|
|
|
|
|
|
} |
763
|
|
|
|
|
|
|
|
764
|
83
|
|
|
|
|
124
|
my @rows; |
765
|
83
|
|
|
|
|
233
|
while (my $row = $sth->fetchrow_arrayref()) { |
766
|
271
|
|
|
|
|
232
|
my $item; |
767
|
271
|
|
|
|
|
909
|
@$item{'type','name','table_name','rootpage','sql'} = @$row; |
768
|
|
|
|
|
|
|
# Force all names to lower case so we can find them later |
769
|
271
|
|
|
|
|
565
|
push @rows, $item; |
770
|
|
|
|
|
|
|
} |
771
|
|
|
|
|
|
|
|
772
|
83
|
|
|
|
|
261
|
return @rows; |
773
|
|
|
|
|
|
|
} |
774
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
# This is used if, for whatever reason, we can't sue the sqlite3 command-line |
777
|
|
|
|
|
|
|
# program to load up the database. We'll make a good-faith effort to parse |
778
|
|
|
|
|
|
|
# the SQL text, but it won't be fancy. This is intended to be used to initialize |
779
|
|
|
|
|
|
|
# meta DB dumps, so we should have to worry about escaping quotes, multi-line |
780
|
|
|
|
|
|
|
# statements, etc. |
781
|
|
|
|
|
|
|
# |
782
|
|
|
|
|
|
|
# The real DB file should be moved out of the way before this is called. The existing |
783
|
|
|
|
|
|
|
# DB file will be removed. |
784
|
|
|
|
|
|
|
sub _load_db_from_dump_internal { |
785
|
39
|
|
|
39
|
|
72
|
my $self = shift; |
786
|
39
|
|
|
|
|
78
|
my $file_name = shift; |
787
|
|
|
|
|
|
|
|
788
|
39
|
|
|
|
|
402
|
my $fh = IO::File->new($file_name); |
789
|
39
|
50
|
|
|
|
4254
|
unless ($fh) { |
790
|
0
|
|
|
|
|
0
|
Carp::croak("Can't open DB dump file $file_name: $!"); |
791
|
|
|
|
|
|
|
} |
792
|
|
|
|
|
|
|
|
793
|
39
|
|
|
|
|
141
|
my $db_file = $self->server; |
794
|
39
|
50
|
|
|
|
444
|
if (-f $db_file) { |
795
|
0
|
0
|
|
|
|
0
|
unless(unlink($db_file)) { |
796
|
0
|
|
|
|
|
0
|
Carp::croak("Can't remove DB file $db_file: $!"); |
797
|
|
|
|
|
|
|
} |
798
|
|
|
|
|
|
|
} |
799
|
|
|
|
|
|
|
|
800
|
39
|
|
|
|
|
519
|
my $dbh = DBI->connect("dbi:SQLite:dbname=$db_file",'','',{ AutoCommit => 0, RaiseError => 0 }); |
801
|
39
|
50
|
|
|
|
24486
|
unless($dbh) { |
802
|
0
|
|
|
|
|
0
|
Carp::croak("Can't create DB handle for file $db_file: $DBI::errstr"); |
803
|
|
|
|
|
|
|
} |
804
|
|
|
|
|
|
|
|
805
|
39
|
|
|
|
|
63
|
my $dump_file_contents = do { local( $/ ) ; <$fh> }; |
|
39
|
|
|
|
|
145
|
|
|
39
|
|
|
|
|
973
|
|
806
|
39
|
|
|
|
|
565
|
my @sql = split(';',$dump_file_contents); |
807
|
|
|
|
|
|
|
|
808
|
39
|
|
|
|
|
191
|
for (my $i = 0; $i < @sql; $i++) { |
809
|
827
|
|
|
|
|
52202
|
my $sql = $sql[$i]; |
810
|
827
|
100
|
|
|
|
2418
|
next unless ($sql =~ m/\S/); # Skip blank lines |
811
|
788
|
100
|
|
|
|
5504
|
next if ($sql =~ m/BEGIN TRANSACTION|COMMIT/i); # We're probably already in a transaction |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
# Is it restoring the foreign_keys setting? |
814
|
710
|
100
|
|
|
|
1432
|
if ($sql =~ m/PRAGMA foreign_keys\s*=\s*(\w+)/) { |
815
|
30
|
|
|
|
|
74
|
my $value = $1; |
816
|
30
|
|
|
|
|
348
|
my $fk_setting = $self->_get_foreign_key_setting($dbh); |
817
|
30
|
50
|
|
|
|
122
|
if (! defined($fk_setting)) { |
818
|
|
|
|
|
|
|
# This version of SQLite cannot enforce foreign keys. |
819
|
|
|
|
|
|
|
# Print a warning message if they're trying to turn it on. |
820
|
|
|
|
|
|
|
# also, remember the setting so we can preserve its value |
821
|
|
|
|
|
|
|
# in _dump_db_to_file_internal() |
822
|
0
|
|
|
|
|
0
|
$self->_cache_foreign_key_setting_from_file($value); |
823
|
0
|
0
|
|
|
|
0
|
if ($value ne 'OFF') { |
824
|
0
|
|
|
|
|
0
|
$self->warning_message("Data source ".$self->id." does not support foreign key enforcement, but the dump file $db_file attempts to turn it on"); |
825
|
|
|
|
|
|
|
} |
826
|
0
|
|
|
|
|
0
|
next; |
827
|
|
|
|
|
|
|
} |
828
|
|
|
|
|
|
|
} |
829
|
|
|
|
|
|
|
|
830
|
710
|
50
|
|
|
|
2357
|
unless ($dbh->do($sql)) { |
831
|
0
|
|
|
|
|
0
|
Carp::croak("Error processing SQL statement $i from DB dump file:\n$sql\nDBI error was: $DBI::errstr\n"); |
832
|
|
|
|
|
|
|
} |
833
|
|
|
|
|
|
|
} |
834
|
|
|
|
|
|
|
|
835
|
39
|
|
|
|
|
1837025
|
$dbh->commit(); |
836
|
39
|
|
|
|
|
6361
|
$dbh->disconnect(); |
837
|
|
|
|
|
|
|
|
838
|
39
|
|
|
|
|
4090
|
return 1; |
839
|
|
|
|
|
|
|
} |
840
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
sub _cache_foreign_key_setting_from_file { |
843
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
844
|
|
|
|
|
|
|
|
845
|
0
|
|
|
|
|
0
|
our %foreign_key_setting_from_file; |
846
|
0
|
|
|
|
|
0
|
my $id = $self->id; |
847
|
|
|
|
|
|
|
|
848
|
0
|
0
|
|
|
|
0
|
if (@_) { |
849
|
0
|
|
|
|
|
0
|
$foreign_key_setting_from_file{$id} = shift; |
850
|
|
|
|
|
|
|
} |
851
|
0
|
|
|
|
|
0
|
return $foreign_key_setting_from_file{$id}; |
852
|
|
|
|
|
|
|
} |
853
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
# Is foreign key enforcement on or off? |
855
|
|
|
|
|
|
|
# returns undef if this version of SQLite cannot enforce foreign keys |
856
|
|
|
|
|
|
|
sub _get_foreign_key_setting { |
857
|
32
|
|
|
32
|
|
62
|
my $self = shift; |
858
|
32
|
|
|
|
|
67
|
my $dbh = shift; |
859
|
32
|
|
|
|
|
377
|
my $id = $self->id; |
860
|
|
|
|
|
|
|
|
861
|
32
|
|
|
|
|
69
|
our %foreign_key_setting; |
862
|
32
|
100
|
|
|
|
132
|
unless (exists $foreign_key_setting{$id}) { |
863
|
30
|
|
66
|
|
|
126
|
$dbh ||= $self->get_default_handle; |
864
|
30
|
|
|
|
|
383
|
my @row = $dbh->selectrow_array('PRAGMA foreign_keys'); |
865
|
30
|
|
|
|
|
15613
|
$foreign_key_setting{$id} = $row[0]; |
866
|
|
|
|
|
|
|
} |
867
|
32
|
|
|
|
|
97
|
return $foreign_key_setting{$id}; |
868
|
|
|
|
|
|
|
} |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
sub _resolve_order_by_clause_for_column { |
871
|
2161
|
|
|
2161
|
|
3201
|
my($self, $column_name, $query_plan, $property_meta) = @_; |
872
|
|
|
|
|
|
|
|
873
|
2161
|
|
|
|
|
6302
|
my $is_optional = $property_meta->is_optional; |
874
|
|
|
|
|
|
|
|
875
|
2161
|
|
|
|
|
2937
|
my $column_clause = $column_name; # default, usual case |
876
|
2161
|
100
|
|
|
|
7878
|
if ($is_optional) { |
|
|
100
|
|
|
|
|
|
877
|
7
|
100
|
|
|
|
28
|
if ($query_plan->order_by_column_is_descending($column_name)) { |
878
|
3
|
|
|
|
|
12
|
$column_clause = "CASE WHEN $column_name ISNULL THEN 0 ELSE 1 END, $column_name DESC"; |
879
|
|
|
|
|
|
|
} else { |
880
|
4
|
|
|
|
|
14
|
$column_clause = "CASE WHEN $column_name ISNULL THEN 1 ELSE 0 END, $column_name"; |
881
|
|
|
|
|
|
|
} |
882
|
|
|
|
|
|
|
} elsif ($query_plan->order_by_column_is_descending($column_name)) { |
883
|
3
|
|
|
|
|
8
|
$column_clause = $column_name . ' DESC'; |
884
|
|
|
|
|
|
|
} |
885
|
2161
|
|
|
|
|
6913
|
return $column_clause; |
886
|
|
|
|
|
|
|
} |
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
sub _resolve_limit_value_from_query_plan { |
889
|
1422
|
|
|
1422
|
|
1754
|
my($self, $query_plan) = @_; |
890
|
1422
|
|
|
|
|
4687
|
my $limit = $query_plan->limit; |
891
|
1422
|
100
|
100
|
|
|
6408
|
return (!defined($limit) and $query_plan->offset) |
892
|
|
|
|
|
|
|
? -1 |
893
|
|
|
|
|
|
|
: $limit; |
894
|
|
|
|
|
|
|
} |
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
sub _dump_db_to_file_internal { |
898
|
1
|
|
|
1
|
|
3
|
my $self = shift; |
899
|
|
|
|
|
|
|
|
900
|
1
|
|
|
|
|
5
|
my $fk_setting = $self->_get_foreign_key_setting(); |
901
|
|
|
|
|
|
|
|
902
|
1
|
|
|
|
|
5
|
my $file_name = $self->_data_dump_path(); |
903
|
1
|
50
|
|
|
|
21
|
unless (-w $file_name) { |
904
|
|
|
|
|
|
|
# dump file isn't writable... |
905
|
0
|
|
|
|
|
0
|
return 1; |
906
|
|
|
|
|
|
|
} |
907
|
|
|
|
|
|
|
|
908
|
1
|
|
|
|
|
5
|
my $fh = IO::File->new($file_name, '>'); |
909
|
1
|
50
|
|
|
|
87
|
unless ($fh) { |
910
|
0
|
|
|
|
|
0
|
Carp::croak("Can't open DB dump file $file_name for writing: $!"); |
911
|
|
|
|
|
|
|
} |
912
|
|
|
|
|
|
|
|
913
|
1
|
|
|
|
|
4
|
my $db_file = $self->server; |
914
|
1
|
|
|
|
|
8
|
my $dbh = $self->get_default_handle; |
915
|
1
|
50
|
|
|
|
4
|
unless ($dbh) { |
916
|
0
|
|
|
|
|
0
|
Carp::croak("Can't create DB handle for file $db_file: $DBI::errstr"); |
917
|
|
|
|
|
|
|
} |
918
|
|
|
|
|
|
|
|
919
|
1
|
50
|
|
|
|
5
|
if (defined $fk_setting) { |
920
|
|
|
|
|
|
|
# Save the value of the foreign_keys setting, if it's supported |
921
|
1
|
50
|
|
|
|
8
|
$fh->print('PRAGMA foreign_keys = ' . ( $fk_setting ? 'ON' : 'OFF' ) .";\n"); |
922
|
|
|
|
|
|
|
} else { |
923
|
|
|
|
|
|
|
# If not supported, but if _load_db_from_dump_internal came across the value, preserve it |
924
|
0
|
|
|
|
|
0
|
$fk_setting = $self->_cache_foreign_key_setting_from_file; |
925
|
0
|
0
|
|
|
|
0
|
if (defined $fk_setting) { |
926
|
0
|
|
|
|
|
0
|
$fh->print("PRAGMA foreign_keys = $fk_setting;\n"); |
927
|
|
|
|
|
|
|
} |
928
|
|
|
|
|
|
|
} |
929
|
|
|
|
|
|
|
|
930
|
1
|
|
|
|
|
11
|
$fh->print("BEGIN TRANSACTION;\n"); |
931
|
|
|
|
|
|
|
|
932
|
1
|
|
|
|
|
14
|
my @tables = $self->_get_table_names_from_data_dictionary(); |
933
|
1
|
|
|
|
|
4
|
foreach my $qualified_table ( @tables ) { |
934
|
2
|
|
|
|
|
14
|
my(undef, $table) = $self->_resolve_owner_and_table_from_table_name($qualified_table); |
935
|
2
|
|
|
|
|
10
|
my($item_info) = $self->_get_info_from_sqlite_master($table); |
936
|
2
|
|
|
|
|
5
|
my $creation_sql = $item_info->{'sql'}; |
937
|
2
|
50
|
|
|
|
7
|
$creation_sql .= ";" unless(substr($creation_sql, -1, 1) eq ";"); |
938
|
2
|
50
|
|
|
|
6
|
$creation_sql .= "\n" unless(substr($creation_sql, -1, 1) eq "\n"); |
939
|
|
|
|
|
|
|
|
940
|
2
|
|
|
|
|
9
|
$fh->print($creation_sql); |
941
|
|
|
|
|
|
|
|
942
|
2
|
50
|
|
|
|
18
|
if ($item_info->{'type'} eq 'table') { |
943
|
2
|
|
|
|
|
7
|
my $sth = $dbh->prepare("select * from $table"); |
944
|
2
|
50
|
|
|
|
7
|
unless ($sth) { |
945
|
0
|
|
|
|
|
0
|
Carp::croak("Can't retrieve data from table $table: $DBI::errstr"); |
946
|
|
|
|
|
|
|
} |
947
|
2
|
50
|
|
|
|
8
|
unless($sth->execute()) { |
948
|
0
|
|
|
|
|
0
|
Carp::croak("execute() failed while retrieving data for table $table: $DBI::errstr"); |
949
|
|
|
|
|
|
|
} |
950
|
|
|
|
|
|
|
|
951
|
2
|
|
|
|
|
7
|
while(my @row = $sth->fetchrow_array) { |
952
|
6
|
|
|
|
|
9
|
foreach my $col ( @row ) { |
953
|
12
|
100
|
66
|
|
|
63
|
if (! defined $col) { |
|
|
100
|
|
|
|
|
|
954
|
1
|
|
|
|
|
2
|
$col = 'null'; |
955
|
|
|
|
|
|
|
} elsif ($col =~ m/\D/ or length($col) == 0) { |
956
|
2
|
|
|
|
|
7
|
$col = "'" . $col . "'"; # Put quotes around non-numeric stuff |
957
|
|
|
|
|
|
|
} |
958
|
|
|
|
|
|
|
} |
959
|
6
|
|
|
|
|
32
|
$fh->printf("INSERT INTO %s VALUES(%s);\n", |
960
|
|
|
|
|
|
|
$table, |
961
|
|
|
|
|
|
|
join(',', @row)); |
962
|
|
|
|
|
|
|
} |
963
|
|
|
|
|
|
|
} |
964
|
|
|
|
|
|
|
} |
965
|
1
|
|
|
|
|
4
|
$fh->print("COMMIT;\n"); |
966
|
1
|
|
|
|
|
7
|
$fh->close(); |
967
|
|
|
|
|
|
|
|
968
|
1
|
|
|
|
|
77
|
$dbh->disconnect(); |
969
|
|
|
|
|
|
|
|
970
|
1
|
|
|
|
|
14
|
return 1; |
971
|
|
|
|
|
|
|
} |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
sub _create_dbh_for_alternate_db { |
975
|
70
|
|
|
70
|
|
121
|
my($self, $connect_string) = @_; |
976
|
|
|
|
|
|
|
|
977
|
70
|
|
|
|
|
244
|
my $match_dbname = qr{dbname=([^;]+)}i; |
978
|
70
|
|
|
|
|
392
|
my($db_file) = $connect_string =~ m/$match_dbname/; |
979
|
70
|
50
|
|
|
|
205
|
$db_file |
980
|
|
|
|
|
|
|
|| Carp::croak("Cannot determine dbname for alternate DB from dbi connect string $connect_string"); |
981
|
|
|
|
|
|
|
|
982
|
70
|
100
|
|
|
|
214
|
if ($self->_db_path_specifies_a_directory($db_file)) { |
983
|
36
|
|
|
|
|
244
|
mkdir $db_file; |
984
|
36
|
|
|
|
|
129
|
my $main_schema_file = join('', 'main', $self->_extension_for_db); |
985
|
36
|
|
|
|
|
511
|
$db_file = File::Spec->catfile($db_file, $main_schema_file); |
986
|
|
|
|
|
|
|
|
987
|
36
|
|
|
|
|
311
|
$connect_string =~ s/$match_dbname/dbname=$db_file/; |
988
|
|
|
|
|
|
|
} |
989
|
|
|
|
|
|
|
|
990
|
70
|
|
|
|
|
318
|
my $dbh = $self->SUPER::_create_dbh_for_alternate_db($connect_string); |
991
|
70
|
|
|
|
|
25366
|
return $dbh; |
992
|
|
|
|
|
|
|
} |
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
sub _db_path_specifies_a_directory { |
995
|
245
|
|
|
245
|
|
2249
|
my($self, $pathname) = @_; |
996
|
245
|
|
66
|
|
|
5072
|
return (-d $pathname) || ($pathname =~ m{/$}); |
997
|
|
|
|
|
|
|
} |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
sub _assure_schema_exists_for_table { |
1000
|
90
|
|
|
90
|
|
161
|
my($self, $table_name, $dbh) = @_; |
1001
|
90
|
|
33
|
|
|
186
|
$dbh ||= $self->get_default_handle; |
1002
|
|
|
|
|
|
|
|
1003
|
90
|
|
|
|
|
272
|
my($schema_name, undef) = $self->_extract_schema_and_table_name($table_name); |
1004
|
90
|
50
|
33
|
|
|
269
|
if ($schema_name |
1005
|
|
|
|
|
|
|
and |
1006
|
|
|
|
|
|
|
! $self->is_schema_attached($schema_name, $dbh) |
1007
|
|
|
|
|
|
|
) { |
1008
|
|
|
|
|
|
|
# pretend we have schemas |
1009
|
|
|
|
|
|
|
|
1010
|
0
|
|
|
|
|
|
my($main_filename) = $dbh->{Name} =~ m/(?:dbname=)*(.*)/; |
1011
|
0
|
|
|
|
|
|
my $directory = File::Basename::dirname($main_filename); |
1012
|
0
|
|
|
|
|
|
my $schema_filename = File::Spec->catfile($directory, "${schema_name}.sqlite3"); |
1013
|
0
|
0
|
|
|
|
|
unless (UR::Util::touch_file($schema_filename)) { |
1014
|
0
|
|
|
|
|
|
Carp::carp("touch_file $schema_filename failed: $!"); |
1015
|
0
|
|
|
|
|
|
return; |
1016
|
|
|
|
|
|
|
} |
1017
|
0
|
0
|
|
|
|
|
unless ($dbh->do(qq(ATTACH DATABASE '$schema_filename' as $schema_name))) { |
1018
|
0
|
|
|
|
|
|
Carp::carp("Cannot attach file $schema_filename as $schema_name: ".$dbh->errstr); |
1019
|
0
|
|
|
|
|
|
return; |
1020
|
|
|
|
|
|
|
} |
1021
|
|
|
|
|
|
|
} |
1022
|
|
|
|
|
|
|
} |
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
sub attached_schemas { |
1025
|
0
|
|
|
0
|
0
|
|
my($self, $dbh) = @_; |
1026
|
0
|
|
0
|
|
|
|
$dbh ||= $self->get_default_handle; |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
# Statement returns id, schema, filename |
1029
|
0
|
|
0
|
|
|
|
my $sth = $dbh->prepare('PRAGMA database_list') || Carp::croak("Cannot list attached databases: ".$dbh->errstr); |
1030
|
0
|
|
|
|
|
|
$sth->execute(); |
1031
|
0
|
|
|
|
|
|
my %schemas = map { $_->[1] => $_->[2] } |
1032
|
0
|
|
|
|
|
|
@{ $sth->fetchall_arrayref }; |
|
0
|
|
|
|
|
|
|
1033
|
0
|
|
|
|
|
|
return \%schemas; |
1034
|
|
|
|
|
|
|
} |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
sub is_schema_attached { |
1037
|
0
|
|
|
0
|
0
|
|
my($self, $schema, $dbh) = @_; |
1038
|
0
|
|
0
|
|
|
|
$dbh ||= $self->get_default_handle; |
1039
|
|
|
|
|
|
|
|
1040
|
0
|
|
|
|
|
|
my $schemas = $self->attached_schemas($dbh); |
1041
|
0
|
|
|
|
|
|
return exists $schemas->{$schema}; |
1042
|
|
|
|
|
|
|
} |
1043
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
1; |