| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package DBD::PgLite::MirrorPgToSQLite; |
|
2
|
1
|
|
|
1
|
|
27155
|
use strict; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
58
|
|
|
3
|
1
|
|
|
1
|
|
2897
|
use DBI; |
|
|
1
|
|
|
|
|
27181
|
|
|
|
1
|
|
|
|
|
101
|
|
|
4
|
1
|
|
|
1
|
|
3157
|
use Storable; |
|
|
1
|
|
|
|
|
5359
|
|
|
|
1
|
|
|
|
|
102
|
|
|
5
|
1
|
|
|
1
|
|
1115
|
use File::Copy; |
|
|
1
|
|
|
|
|
6292
|
|
|
|
1
|
|
|
|
|
4055
|
|
|
6
|
|
|
|
|
|
|
require Exporter; |
|
7
|
|
|
|
|
|
|
our @ISA = qw(Exporter); |
|
8
|
|
|
|
|
|
|
our @EXPORT_OK = qw(pg_to_sqlite); |
|
9
|
|
|
|
|
|
|
our $VERSION = '0.05'; |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
#### MAIN SUBROUTINE #### |
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
sub pg_to_sqlite { |
|
14
|
0
|
|
|
0
|
0
|
|
my %opt = @_; |
|
15
|
0
|
|
|
|
|
|
my %defaults = defaults(); |
|
16
|
0
|
|
|
|
|
|
for (keys %defaults) { |
|
17
|
0
|
0
|
|
|
|
|
next if exists $opt{$_}; |
|
18
|
0
|
|
|
|
|
|
$opt{$_} = $defaults{$_}; |
|
19
|
|
|
|
|
|
|
} |
|
20
|
0
|
|
|
|
|
|
$opt{tables} = _commasplit($opt{tables}); |
|
21
|
0
|
|
|
|
|
|
$opt{views} = _commasplit($opt{views}); |
|
22
|
0
|
0
|
0
|
|
|
|
if ($opt{where} |
|
|
|
|
0
|
|
|
|
|
|
23
|
|
|
|
|
|
|
&& $opt{where} !~ /^\s*where\s/i |
|
24
|
|
|
|
|
|
|
&& $opt{where} !~ /^\s*(?:natural\s+)?join\s/i) { |
|
25
|
0
|
|
|
|
|
|
$opt{where} = 'where '.$opt{where}; |
|
26
|
|
|
|
|
|
|
} |
|
27
|
0
|
0
|
0
|
|
|
|
die "Incompatible options: 'append' and 'snapshot'" if $opt{append} && $opt{snapshot}; |
|
28
|
0
|
0
|
0
|
|
|
|
die "Need either database handle (pg_dbh) or DSN (pg_dsn)" unless $opt{pg_dbh} || $opt{pg_dsn}; |
|
29
|
0
|
|
|
|
|
|
my $disconnect = 0; |
|
30
|
0
|
0
|
|
|
|
|
unless ($opt{pg_dbh}) { |
|
31
|
0
|
|
|
|
|
|
$disconnect++; |
|
32
|
0
|
0
|
|
|
|
|
$opt{pg_dbh} = DBI->connect(@opt{ qw(pg_dsn pg_user pg_pass) },{RaiseError=>1}) |
|
33
|
|
|
|
|
|
|
or die "Could not connect to PostgreSQL: $DBI::errstr"; |
|
34
|
|
|
|
|
|
|
} |
|
35
|
0
|
|
|
|
|
|
my $fn = $opt{sqlite_file}; |
|
36
|
0
|
0
|
0
|
|
|
|
die "Need both list of source tables and a SQLite file" unless @{$opt{tables}} && $fn; |
|
|
0
|
|
|
|
|
|
|
|
37
|
0
|
0
|
|
|
|
|
$|++ if $opt{verbose}; |
|
38
|
0
|
|
|
|
|
|
my $lockfile = "$fn.lock"; |
|
39
|
0
|
|
|
|
|
|
lockfile('create',$lockfile); |
|
40
|
0
|
0
|
|
|
|
|
if (-f "$fn.tmp") { |
|
41
|
0
|
|
|
|
|
|
warn "WARNING: Removing temp file $fn.tmp - apparently left over from a previous run\n"; |
|
42
|
0
|
0
|
|
|
|
|
unlink "$fn.tmp" or die "ERROR: Could not remove file.tmp: $!\n"; |
|
43
|
|
|
|
|
|
|
} |
|
44
|
0
|
0
|
|
|
|
|
if ($opt{append}) { |
|
45
|
0
|
0
|
|
|
|
|
unless (copy $fn, "$fn.tmp") { |
|
46
|
0
|
|
|
|
|
|
warn "WARNING: Could not copy $fn to $fn.tmp for appending: $! - turning --append off\n"; |
|
47
|
0
|
|
|
|
|
|
$opt{append} = 0; |
|
48
|
|
|
|
|
|
|
} |
|
49
|
|
|
|
|
|
|
} |
|
50
|
0
|
|
|
|
|
|
$opt{sl_dbh} = DBI->connect("dbi:SQLite:dbname=$fn.tmp",undef,undef,{RaiseError=>1}); |
|
51
|
|
|
|
|
|
|
|
|
52
|
0
|
|
|
|
|
|
my @tables = tablelist($opt{pg_dbh}, $opt{schema}, @{ $opt{tables} }); # handle regexp |
|
|
0
|
|
|
|
|
|
|
|
53
|
0
|
0
|
|
|
|
|
print "MIRRORING ".scalar(@tables)." table(s):\n" if $opt{verbose}; |
|
54
|
0
|
|
|
|
|
|
my @views = viewlist($opt{pg_dbh}, $opt{schema}, @{ $opt{views} }) |
|
|
0
|
|
|
|
|
|
|
|
55
|
0
|
0
|
|
|
|
|
if grep { /^\/.+\/$/ } @{ $opt{views} }; |
|
|
0
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
|
|
57
|
0
|
|
|
|
|
|
eval { |
|
58
|
0
|
0
|
|
|
|
|
if ($opt{snapshot}) { |
|
59
|
0
|
|
|
|
|
|
$opt{pg_dbh}->do("set session characteristics as transaction isolation level serializable"); |
|
60
|
0
|
|
|
|
|
|
$opt{pg_dbh}->begin_work; |
|
61
|
0
|
|
|
|
|
|
mirror_table($_,%opt) for @tables; |
|
62
|
0
|
0
|
|
|
|
|
mirror_functions(%opt) if $opt{functions}; |
|
63
|
0
|
|
|
|
|
|
$opt{pg_dbh}->commit; |
|
64
|
|
|
|
|
|
|
} else { |
|
65
|
0
|
|
|
|
|
|
mirror_table($_,%opt) for @tables; |
|
66
|
0
|
0
|
|
|
|
|
mirror_functions(%opt) if $opt{functions}; |
|
67
|
|
|
|
|
|
|
} |
|
68
|
0
|
0
|
|
|
|
|
if (@views) { |
|
69
|
0
|
0
|
|
|
|
|
print "CREATING ".scalar(@views)." view(s):\n" if $opt{verbose}; |
|
70
|
0
|
|
|
|
|
|
create_view($_,%opt) for @views; |
|
71
|
|
|
|
|
|
|
} |
|
72
|
0
|
0
|
|
|
|
|
print "done!\n" if $opt{verbose}; |
|
73
|
|
|
|
|
|
|
}; |
|
74
|
|
|
|
|
|
|
|
|
75
|
0
|
0
|
|
|
|
|
if ($@) { |
|
76
|
0
|
|
|
|
|
|
lockfile('clear',$lockfile); |
|
77
|
0
|
|
|
|
|
|
die $@; |
|
78
|
|
|
|
|
|
|
} |
|
79
|
|
|
|
|
|
|
|
|
80
|
0
|
0
|
|
|
|
|
$opt{pg_dbh}->disconnect if $disconnect; |
|
81
|
0
|
|
|
|
|
|
$opt{sl_dbh}->disconnect; |
|
82
|
|
|
|
|
|
|
|
|
83
|
0
|
0
|
|
|
|
|
if (-f $fn) { |
|
84
|
0
|
0
|
|
|
|
|
copy $fn, "$fn.bak" or warn "WARNING: Could not make backup copy of $fn: $!\n"; |
|
85
|
|
|
|
|
|
|
} |
|
86
|
0
|
0
|
|
|
|
|
move "$fn.tmp", $fn or die "ERROR: Could not move temporary SQLite file $fn.tmp to $fn"; |
|
87
|
0
|
|
|
|
|
|
lockfile('clear',$lockfile); |
|
88
|
|
|
|
|
|
|
} |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
########## OTHER SUBROUTINES ########### |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
sub _commasplit { |
|
95
|
0
|
|
|
0
|
|
|
my $list = shift; |
|
96
|
0
|
0
|
|
|
|
|
return [] unless $list; |
|
97
|
0
|
0
|
|
|
|
|
$list = [$list] unless ref $list; |
|
98
|
0
|
|
|
|
|
|
my @new = split(/\s*,\s*/,join(',',@$list)); |
|
99
|
0
|
|
|
|
|
|
return \@new; |
|
100
|
|
|
|
|
|
|
} |
|
101
|
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
sub defaults { |
|
103
|
0
|
0
|
0
|
0
|
0
|
|
my %defaults = ( |
|
104
|
|
|
|
|
|
|
verbose => 0, |
|
105
|
|
|
|
|
|
|
pg_dsn => ($ENV{PGDATABASE} ? "dbi:Pg:dbname=$ENV{PGDATABASE}" : undef), |
|
106
|
|
|
|
|
|
|
pg_user => ($ENV{PGUSER} || $ENV{USER}), |
|
107
|
|
|
|
|
|
|
pg_pass => $ENV{PGPASSWORD}, |
|
108
|
|
|
|
|
|
|
schema => 'public', |
|
109
|
|
|
|
|
|
|
tables => [], |
|
110
|
|
|
|
|
|
|
sqlite_file => '', |
|
111
|
|
|
|
|
|
|
where => '', |
|
112
|
|
|
|
|
|
|
cachedir => '/tmp/sqlite_mirror_cache', |
|
113
|
|
|
|
|
|
|
append => 0, |
|
114
|
|
|
|
|
|
|
snapshot => 0, |
|
115
|
|
|
|
|
|
|
indexes => 0, |
|
116
|
|
|
|
|
|
|
views => [], |
|
117
|
|
|
|
|
|
|
functions => 0, |
|
118
|
|
|
|
|
|
|
page_limit => 5000, # each page is 8K |
|
119
|
|
|
|
|
|
|
pg_dbh => undef, |
|
120
|
|
|
|
|
|
|
); |
|
121
|
0
|
0
|
0
|
|
|
|
$defaults{pg_dsn} ||= "dbi:Pg:dbname=$defaults{pg_user}" if $defaults{pg_user}; |
|
122
|
0
|
|
|
|
|
|
return %defaults; |
|
123
|
|
|
|
|
|
|
} |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
sub mirror_table { |
|
126
|
0
|
|
|
0
|
0
|
|
my ($tn,%opt) = @_; |
|
127
|
0
|
|
|
|
|
|
my $sn = $opt{schema}; |
|
128
|
0
|
0
|
|
|
|
|
print " - $sn.$tn\n" if $opt{verbose}; |
|
129
|
0
|
|
|
|
|
|
my ($create,$colcnt) = get_schema($sn,$tn,%opt); |
|
130
|
0
|
|
|
|
|
|
my $drop = ''; |
|
131
|
0
|
0
|
|
|
|
|
if ($opt{append}) { |
|
132
|
0
|
|
|
|
|
|
$drop = $opt{sl_dbh}->selectrow_array("select name from sqlite_master where type = 'table' and name = ?",{},$tn); |
|
133
|
0
|
0
|
|
|
|
|
$opt{sl_dbh}->do("drop table $tn") if $drop; |
|
134
|
|
|
|
|
|
|
} |
|
135
|
0
|
|
|
|
|
|
$opt{sl_dbh}->do($create); |
|
136
|
0
|
|
|
|
|
|
my $pages = $opt{pg_dbh}->selectrow_array("select relpages from pg_class where relnamespace = (select oid from pg_namespace where nspname = ?) and relname = ?",{},$sn,$tn); |
|
137
|
0
|
|
|
|
|
|
my $ins = $opt{sl_dbh}->prepare("insert into $tn values (". join(',', ("?") x $colcnt) . ")"); |
|
138
|
0
|
0
|
|
|
|
|
if ($pages > $opt{page_limit}) { |
|
139
|
0
|
0
|
|
|
|
|
warn " pagelimit ($opt{page_limit}) kicks in for $sn.$tn ($pages)\n" if $opt{verbose}; |
|
140
|
0
|
|
|
|
|
|
my @pkey = $opt{pg_dbh}->primary_key(undef,$sn,$tn); |
|
141
|
0
|
0
|
|
|
|
|
warn " (pkey is )".join(":",@pkey)."\n" if $opt{verbose}; |
|
142
|
0
|
0
|
|
|
|
|
if (@pkey) { |
|
143
|
0
|
|
|
|
|
|
my $pkey_vals = $opt{pg_dbh}->selectall_arrayref("select ".join(', ', @pkey)." from $sn.$tn"); |
|
144
|
0
|
|
|
|
|
|
my $sql = "select * from $sn.$tn where ".join(" and ", map {"$_ = ?"} @pkey); |
|
|
0
|
|
|
|
|
|
|
|
145
|
0
|
|
|
|
|
|
my $selh = $opt{pg_dbh}->prepare($sql); |
|
146
|
0
|
|
|
|
|
|
$opt{sl_dbh}->begin_work; |
|
147
|
0
|
|
|
|
|
|
foreach (@$pkey_vals) { |
|
148
|
0
|
|
|
|
|
|
$selh->execute(@$_); |
|
149
|
0
|
|
|
|
|
|
my $row = $selh->fetchrow_arrayref; |
|
150
|
0
|
|
|
|
|
|
$ins->execute(@$row); |
|
151
|
|
|
|
|
|
|
} |
|
152
|
0
|
|
|
|
|
|
$opt{sl_dbh}->commit; |
|
153
|
0
|
|
|
|
|
|
$selh->finish; |
|
154
|
|
|
|
|
|
|
} |
|
155
|
|
|
|
|
|
|
else { |
|
156
|
0
|
|
|
|
|
|
warn "*** CANNOT READ $sn.$tn ROW-BY-ROW - NO PRIMARY KEY\n*** SKIPPING TABLE!\n"; |
|
157
|
0
|
|
|
|
|
|
$ins->finish; |
|
158
|
0
|
|
|
|
|
|
return; |
|
159
|
|
|
|
|
|
|
} |
|
160
|
|
|
|
|
|
|
} |
|
161
|
|
|
|
|
|
|
else { |
|
162
|
0
|
|
|
|
|
|
my $res = $opt{pg_dbh}->selectall_arrayref("select * from $sn.$tn $opt{where}"); |
|
163
|
0
|
0
|
0
|
|
|
|
if (@$res && scalar(@{$res->[0]}) != $colcnt) { |
|
|
0
|
|
|
|
|
|
|
|
164
|
0
|
|
|
|
|
|
$ins->finish; |
|
165
|
0
|
|
|
|
|
|
die "ERROR: Bad schema for table $tn: number of columns does not match\n"; |
|
166
|
|
|
|
|
|
|
} |
|
167
|
0
|
|
|
|
|
|
$opt{sl_dbh}->begin_work; |
|
168
|
0
|
|
|
|
|
|
$ins->execute(@$_) for @$res; |
|
169
|
0
|
|
|
|
|
|
$opt{sl_dbh}->commit; |
|
170
|
|
|
|
|
|
|
} |
|
171
|
0
|
|
|
|
|
|
$ins->finish; |
|
172
|
0
|
0
|
|
|
|
|
create_indexes($tn,%opt) if $opt{indexes}; |
|
173
|
0
|
0
|
|
|
|
|
$opt{sl_dbh}->do("vacuum") if $drop; |
|
174
|
|
|
|
|
|
|
} |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
sub mirror_functions { |
|
177
|
0
|
|
|
0
|
0
|
|
my %opt = @_; |
|
178
|
0
|
0
|
|
|
|
|
unless ($opt{sl_dbh}->selectrow_array("select name from sqlite_master where name = 'pglite_functions' and type = 'table'")) { |
|
179
|
0
|
|
|
|
|
|
$opt{sl_dbh}->do("CREATE TABLE pglite_functions (name text, argnum int, type text, sql text, primary key (name,argnum))"); |
|
180
|
|
|
|
|
|
|
} |
|
181
|
0
|
|
|
|
|
|
my $langnum = $opt{pg_dbh}->selectrow_array("select oid from pg_language where lanname = 'sql'"); |
|
182
|
0
|
|
|
|
|
|
my $snum = $opt{pg_dbh}->selectrow_array("select oid from pg_namespace where nspname = ?",{},$opt{schema}); |
|
183
|
0
|
|
|
|
|
|
my $fun = $opt{pg_dbh}->selectall_arrayref("select proname, pronargs, prosrc from pg_proc where prolang = ? and pronamespace = ?",{},$langnum,$snum); |
|
184
|
0
|
0
|
|
|
|
|
print "FUNCTIONS:\n" if $opt{verbose}; |
|
185
|
0
|
0
|
0
|
|
|
|
return unless ref $fun eq 'ARRAY' && @$fun; |
|
186
|
0
|
|
|
|
|
|
for (@$fun) { |
|
187
|
0
|
|
|
|
|
|
my ($name,$argnum,$sql) = @$_; |
|
188
|
0
|
0
|
|
|
|
|
unless ($opt{sl_dbh}->selectrow_array("select name from pglite_functions where name = ? and argnum = ?",{},$name,$argnum)) { |
|
189
|
0
|
0
|
|
|
|
|
print " - $name ($argnum)\n" if $opt{verbose}; |
|
190
|
0
|
|
|
|
|
|
$opt{sl_dbh}->do("insert into pglite_functions (name,argnum,type,sql) values (?,?,'sql',?)",{},$name,$argnum,$sql); |
|
191
|
|
|
|
|
|
|
} |
|
192
|
|
|
|
|
|
|
} |
|
193
|
|
|
|
|
|
|
} |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
sub create_indexes { |
|
196
|
0
|
|
|
0
|
0
|
|
my ($tn,%opt) = @_; |
|
197
|
0
|
|
|
|
|
|
my $sn = $opt{schema}; |
|
198
|
0
|
|
|
|
|
|
my $ixn = $opt{pg_dbh}->selectcol_arrayref("select indexdef from pg_indexes where schemaname = ? and tablename = ? ", {}, $sn,$tn); |
|
199
|
0
|
|
|
|
|
|
for (@$ixn) { |
|
200
|
0
|
0
|
|
|
|
|
next if /\(oid\)/; |
|
201
|
0
|
0
|
|
|
|
|
next if /_pkey\b/; # No need to recreate primary keys |
|
202
|
0
|
|
|
|
|
|
s/USING btree //; |
|
203
|
0
|
|
|
|
|
|
s/ ON \w+\.\"?([^\"]+)\"?/ ON $1/; |
|
204
|
0
|
0
|
|
|
|
|
print " + $_\n" if $opt{verbose}; |
|
205
|
0
|
|
|
|
|
|
eval { $opt{sl_dbh}->do($_); }; |
|
|
0
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# Pg supports multiple null values in unique columns - SQLite doesn't |
|
207
|
0
|
0
|
0
|
|
|
|
if ($@ =~ /unique/i && s/ UNIQUE / /) { |
|
208
|
0
|
0
|
|
|
|
|
print " + retry: $_\n" if $opt{verbose}; |
|
209
|
0
|
|
|
|
|
|
eval { $opt{sl_dbh}->do($_); }; |
|
|
0
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
} |
|
211
|
|
|
|
|
|
|
} |
|
212
|
|
|
|
|
|
|
} |
|
213
|
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
sub create_view { |
|
215
|
0
|
|
|
0
|
0
|
|
my ($vn,%opt) = @_; |
|
216
|
0
|
|
|
|
|
|
my $sn = $opt{schema}; |
|
217
|
0
|
|
|
|
|
|
my $def = $opt{pg_dbh}->selectrow_array("select definition from pg_views where schemaname = ? and viewname = ?",{},$sn,$vn); |
|
218
|
0
|
0
|
|
|
|
|
print " - $sn.$vn\n" if $opt{verbose}; |
|
219
|
0
|
|
|
|
|
|
$def =~ s/::\w+//g; # casting is not supported in SQLite |
|
220
|
0
|
0
|
|
|
|
|
if ($opt{sl_dbh}->selectrow_array("select name from sqlite_master where name = ? and type = 'view'",{},$vn)) { |
|
221
|
0
|
|
|
|
|
|
eval { $opt{sl_dbh}->do("drop view $vn") }; |
|
|
0
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
} |
|
223
|
0
|
|
|
|
|
|
eval { $opt{sl_dbh}->do("create view $vn as $def"); }; |
|
|
0
|
|
|
|
|
|
|
|
224
|
0
|
0
|
|
|
|
|
warn " *** COULD NOT CREATE VIEW $sn.$vn *** \n" if $@; |
|
225
|
|
|
|
|
|
|
} |
|
226
|
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
sub tablelist { |
|
228
|
0
|
|
|
0
|
0
|
|
my ($pg,$sn,@pats) = @_; |
|
229
|
0
|
|
|
|
|
|
my %tables; # prevent duplicate table names |
|
230
|
0
|
|
|
|
|
|
for my $pat (@pats) { |
|
231
|
0
|
0
|
|
|
|
|
if ($pat =~ s/^\/(.+)\/$/$1/) { |
|
232
|
0
|
|
|
|
|
|
my $res = $pg->selectcol_arrayref("select tablename from pg_tables where lower(schemaname) = lower('$sn') and tablename ~* '$pat'"); |
|
233
|
0
|
|
|
|
|
|
$tables{$_}++ for @$res; |
|
234
|
|
|
|
|
|
|
} else { |
|
235
|
0
|
|
|
|
|
|
$tables{$pat}++; |
|
236
|
|
|
|
|
|
|
} |
|
237
|
|
|
|
|
|
|
} |
|
238
|
0
|
|
|
|
|
|
return keys %tables; |
|
239
|
|
|
|
|
|
|
} |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
sub viewlist { |
|
242
|
0
|
|
|
0
|
0
|
|
my ($pg,$sn,@pats) = @_; |
|
243
|
0
|
|
|
|
|
|
my %views; # prevent duplicate table names |
|
244
|
0
|
|
|
|
|
|
for my $pat (@pats) { |
|
245
|
0
|
0
|
|
|
|
|
if ($pat =~ s/^\/(.+)\/$/$1/) { |
|
246
|
0
|
|
|
|
|
|
my $res = $pg->selectcol_arrayref("select viewname from pg_views where lower(schemaname) = lower('$sn') and viewname ~* '$pat'"); |
|
247
|
0
|
|
|
|
|
|
$views{$_}++ for @$res; |
|
248
|
|
|
|
|
|
|
} else { |
|
249
|
0
|
|
|
|
|
|
$views{$pat}++; |
|
250
|
|
|
|
|
|
|
} |
|
251
|
|
|
|
|
|
|
} |
|
252
|
0
|
|
|
|
|
|
return keys %views; |
|
253
|
|
|
|
|
|
|
} |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
sub get_schema { |
|
257
|
0
|
|
|
0
|
0
|
|
my ($sn,$tn,%opt) = @_; |
|
258
|
|
|
|
|
|
|
# Constructing a schema definition can be rather slow, |
|
259
|
|
|
|
|
|
|
# so we cache the result for up to a week |
|
260
|
0
|
|
|
|
|
|
my @cached = cached_schema($sn,$tn,undef,undef,%opt); |
|
261
|
0
|
0
|
|
|
|
|
return @cached if @cached; |
|
262
|
0
|
|
|
|
|
|
my @cdef = col_def($sn,$tn,%opt); |
|
263
|
0
|
|
|
|
|
|
my $colcnt = scalar @cdef; |
|
264
|
0
|
|
|
|
|
|
my @pknames = $opt{pg_dbh}->primary_key(undef,$sn,$tn); |
|
265
|
0
|
0
|
0
|
|
|
|
push @cdef, "primary key (" . join(',',@pknames) . ")" if @pknames && $pknames[0] ne ''; |
|
266
|
0
|
|
|
|
|
|
my $create = "create table $tn (\n ".join(",\n ",@cdef)."\n)\n"; |
|
267
|
0
|
|
|
|
|
|
cached_schema($sn,$tn,$create,$colcnt,%opt); |
|
268
|
0
|
|
|
|
|
|
return ($create, $colcnt); |
|
269
|
|
|
|
|
|
|
} |
|
270
|
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
sub cached_schema { |
|
272
|
0
|
|
|
0
|
0
|
|
my ($sn,$tn,$creat,$cnt,%opt) = @_; |
|
273
|
0
|
0
|
|
|
|
|
my $database = $opt{pg_dbh}->{mbl_dbh} |
|
274
|
|
|
|
|
|
|
? $opt{pg_dbh}->{mbl_dbh}->[0]->{Name} |
|
275
|
|
|
|
|
|
|
: $opt{pg_dbh}->{Name}; |
|
276
|
0
|
0
|
|
|
|
|
unless (-d $opt{cachedir}) { |
|
277
|
0
|
|
|
|
|
|
mkdir $opt{cachedir}; |
|
278
|
0
|
|
|
|
|
|
chmod 0777, $opt{cachedir}; |
|
279
|
|
|
|
|
|
|
} |
|
280
|
0
|
|
0
|
|
|
|
my $uid = (getpwuid($>))[0] || $>; |
|
281
|
0
|
0
|
|
|
|
|
mkdir "$opt{cachedir}/$uid" unless -d "$opt{cachedir}/$uid"; |
|
282
|
0
|
|
|
|
|
|
my $fn = "$opt{cachedir}/$uid/$database.$sn.$tn"; |
|
283
|
0
|
0
|
0
|
|
|
|
if ($cnt) { |
|
|
|
0
|
|
|
|
|
|
|
284
|
0
|
|
|
|
|
|
Storable::store [$creat,$cnt], $fn; |
|
285
|
|
|
|
|
|
|
} elsif (-f $fn && time-(stat $fn)[9]<7*24*60*60) { |
|
286
|
0
|
|
0
|
|
|
|
my $ret = Storable::retrieve $fn || []; |
|
287
|
0
|
|
|
|
|
|
return @$ret; |
|
288
|
|
|
|
|
|
|
} else { |
|
289
|
0
|
|
|
|
|
|
return (); |
|
290
|
|
|
|
|
|
|
} |
|
291
|
|
|
|
|
|
|
} |
|
292
|
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
sub col_def { |
|
294
|
0
|
|
|
0
|
0
|
|
my ($sn,$tn,%opt) = @_; |
|
295
|
0
|
|
|
|
|
|
my $sth = $opt{pg_dbh}->column_info(undef,$sn,$tn,undef); |
|
296
|
0
|
|
|
|
|
|
$sth->execute; |
|
297
|
0
|
|
|
|
|
|
my $res = $sth->fetchall_arrayref; |
|
298
|
0
|
|
|
|
|
|
my @ret; |
|
299
|
0
|
|
|
|
|
|
foreach my $ci (@$res) { |
|
300
|
0
|
|
|
|
|
|
my ($colnam,$typnam,$nullable) = @{$ci}[qw(3 5 10)]; #)]; |
|
|
0
|
|
|
|
|
|
|
|
301
|
0
|
0
|
|
|
|
|
my $notnull = $nullable ? "" : " not null"; |
|
302
|
0
|
|
|
|
|
|
push @ret, "$colnam $typnam$notnull"; |
|
303
|
|
|
|
|
|
|
} |
|
304
|
0
|
|
|
|
|
|
$sth->finish; |
|
305
|
0
|
|
|
|
|
|
return @ret; |
|
306
|
|
|
|
|
|
|
} |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
sub lockfile { |
|
309
|
0
|
|
|
0
|
0
|
|
my ($action,$lockfile) = @_; |
|
310
|
0
|
0
|
|
|
|
|
if ($action eq 'create') { |
|
|
|
0
|
|
|
|
|
|
|
311
|
0
|
0
|
|
|
|
|
die "ERROR: Lockfile $lockfile exists - cannot continue" if -f $lockfile; |
|
312
|
0
|
0
|
|
|
|
|
open LOCK, ">", "$lockfile" or die "ERROR: Could not open lockfile $lockfile: $!"; |
|
313
|
0
|
|
|
|
|
|
print LOCK $$; |
|
314
|
0
|
|
|
|
|
|
close LOCK; |
|
315
|
|
|
|
|
|
|
} elsif ($action eq 'clear') { |
|
316
|
0
|
0
|
|
|
|
|
if (-f $lockfile) { |
|
317
|
0
|
0
|
|
|
|
|
unlink $lockfile or die "ERROR: Could not remove lockfile $lockfile: $!"; |
|
318
|
|
|
|
|
|
|
} else { |
|
319
|
0
|
0
|
|
|
|
|
warn "WARNING: Lockfile $lockfile does not exist - cannot clear" unless -f $lockfile; |
|
320
|
|
|
|
|
|
|
} |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
} |
|
323
|
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
1; |
|
325
|
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
__END__ |