| 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; |