| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# $Id: DBStag.pm,v 1.59 2008/02/06 00:50:55 cmungall Exp $ |
|
2
|
|
|
|
|
|
|
# ------------------------------------------------------- |
|
3
|
|
|
|
|
|
|
# |
|
4
|
|
|
|
|
|
|
# Copyright (C) 2002 Chris Mungall |
|
5
|
|
|
|
|
|
|
# |
|
6
|
|
|
|
|
|
|
# This module is free software. |
|
7
|
|
|
|
|
|
|
# You may distribute this module under the same terms as perl itself |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
#--- |
|
10
|
|
|
|
|
|
|
# POD docs at end of file |
|
11
|
|
|
|
|
|
|
#--- |
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
package DBIx::DBStag; |
|
14
|
|
|
|
|
|
|
|
|
15
|
15
|
|
|
15
|
|
146355
|
use strict; |
|
|
15
|
|
|
|
|
35
|
|
|
|
15
|
|
|
|
|
778
|
|
|
16
|
15
|
|
|
15
|
|
81
|
use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS $DEBUG $AUTOLOAD); |
|
|
15
|
|
|
|
|
30
|
|
|
|
15
|
|
|
|
|
1402
|
|
|
17
|
15
|
|
|
15
|
|
80
|
use Carp; |
|
|
15
|
|
|
|
|
32
|
|
|
|
15
|
|
|
|
|
968
|
|
|
18
|
15
|
|
|
15
|
|
25838
|
use DBI; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
use Data::Stag qw(:all); |
|
20
|
|
|
|
|
|
|
use DBIx::DBSchema; |
|
21
|
|
|
|
|
|
|
use Text::Balanced qw(extract_bracketed); |
|
22
|
|
|
|
|
|
|
#use SQL::Statement; |
|
23
|
|
|
|
|
|
|
use Parse::RecDescent; |
|
24
|
|
|
|
|
|
|
$VERSION='0.12'; |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
our $DEBUG; |
|
28
|
|
|
|
|
|
|
our $TRACE = $ENV{DBSTAG_TRACE}; |
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
sub DEBUG { |
|
31
|
|
|
|
|
|
|
$DEBUG = shift if @_; |
|
32
|
|
|
|
|
|
|
return $DEBUG; |
|
33
|
|
|
|
|
|
|
} |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
sub trace { |
|
36
|
|
|
|
|
|
|
my ($priority, @msg) = @_; |
|
37
|
|
|
|
|
|
|
return unless $ENV{DBSTAG_TRACE}; |
|
38
|
|
|
|
|
|
|
print STDERR "@msg\n"; |
|
39
|
|
|
|
|
|
|
} |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
sub dmp { |
|
42
|
|
|
|
|
|
|
use Data::Dumper; |
|
43
|
|
|
|
|
|
|
print Dumper shift; |
|
44
|
|
|
|
|
|
|
} |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
sub force { |
|
47
|
|
|
|
|
|
|
my $self = shift; |
|
48
|
|
|
|
|
|
|
$self->{_force} = shift if @_; |
|
49
|
|
|
|
|
|
|
return $self->{_force}; |
|
50
|
|
|
|
|
|
|
} |
|
51
|
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
sub new { |
|
54
|
|
|
|
|
|
|
my $proto = shift; |
|
55
|
|
|
|
|
|
|
my $class = ref($proto) || $proto; |
|
56
|
|
|
|
|
|
|
my ($dbh) = |
|
57
|
|
|
|
|
|
|
rearrange([qw(dbh)], @_); |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
my $self = {}; |
|
60
|
|
|
|
|
|
|
bless $self, $class; |
|
61
|
|
|
|
|
|
|
if ($dbh) { |
|
62
|
|
|
|
|
|
|
$self->dbh($dbh); |
|
63
|
|
|
|
|
|
|
} |
|
64
|
|
|
|
|
|
|
$self; |
|
65
|
|
|
|
|
|
|
} |
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
sub connect { |
|
69
|
|
|
|
|
|
|
my $class = shift; |
|
70
|
|
|
|
|
|
|
my $dbi = shift; |
|
71
|
|
|
|
|
|
|
my $self; |
|
72
|
|
|
|
|
|
|
if (ref($class)) { |
|
73
|
|
|
|
|
|
|
$self = $class; |
|
74
|
|
|
|
|
|
|
} |
|
75
|
|
|
|
|
|
|
else { |
|
76
|
|
|
|
|
|
|
$self = {}; |
|
77
|
|
|
|
|
|
|
bless $self, $class; |
|
78
|
|
|
|
|
|
|
} |
|
79
|
|
|
|
|
|
|
$dbi = $self->resolve_dbi($dbi); |
|
80
|
|
|
|
|
|
|
eval { |
|
81
|
|
|
|
|
|
|
$self->dbh(DBI->connect($dbi, @_)); |
|
82
|
|
|
|
|
|
|
}; |
|
83
|
|
|
|
|
|
|
if ($@ || !$self->dbh) { |
|
84
|
|
|
|
|
|
|
my $mapf = $ENV{DBSTAG_DBIMAP_FILE}; |
|
85
|
|
|
|
|
|
|
if ($dbi =~ /^dbi:(\w+)/) { |
|
86
|
|
|
|
|
|
|
print STDERR <
|
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
Could not connect to database: "$dbi" |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
EITHER The required DBD driver "$1" is not installed |
|
91
|
|
|
|
|
|
|
OR There is no such database as "$dbi" |
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
EOM |
|
94
|
|
|
|
|
|
|
; |
|
95
|
|
|
|
|
|
|
} |
|
96
|
|
|
|
|
|
|
else { |
|
97
|
|
|
|
|
|
|
print STDERR <
|
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
Could not connect to database: "$dbi" |
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
To connect to a database, you need to set the environment variable |
|
102
|
|
|
|
|
|
|
DBSTAG_DBIMAP_FILE to the location of your DBI Stag resources file, OR |
|
103
|
|
|
|
|
|
|
you need to specify the full dbi string of the database |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
A dbi string looks like this: |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
dbi:Pg:dbname=foo;host=mypgserver.foo.com |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
A resources file provides mappings from logical names like "foo" to |
|
110
|
|
|
|
|
|
|
full DBI locators suchas the one above |
|
111
|
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
Please type "man DBI" for more information on DBI strings |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
If you are specifying a valid DBI locator or valid logical name and |
|
115
|
|
|
|
|
|
|
still connect, check the database server is responding |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
EOM |
|
118
|
|
|
|
|
|
|
; |
|
119
|
|
|
|
|
|
|
} |
|
120
|
|
|
|
|
|
|
die; |
|
121
|
|
|
|
|
|
|
} |
|
122
|
|
|
|
|
|
|
# HACK |
|
123
|
|
|
|
|
|
|
$self->dbh->{RaiseError} = 1; |
|
124
|
|
|
|
|
|
|
$self->dbh->{ShowErrorStatement} = 1; |
|
125
|
|
|
|
|
|
|
if ($dbi =~ /dbi:([\w\d]+)/) { |
|
126
|
|
|
|
|
|
|
$self->{_driver} = $1; |
|
127
|
|
|
|
|
|
|
} |
|
128
|
|
|
|
|
|
|
$self->setup; |
|
129
|
|
|
|
|
|
|
return $self; |
|
130
|
|
|
|
|
|
|
} |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
sub resolve_dbi { |
|
133
|
|
|
|
|
|
|
my $self = shift; |
|
134
|
|
|
|
|
|
|
my $dbi = shift; |
|
135
|
|
|
|
|
|
|
if (!$dbi) { |
|
136
|
|
|
|
|
|
|
$self->throw("database name not provided!"); |
|
137
|
|
|
|
|
|
|
} |
|
138
|
|
|
|
|
|
|
if ($dbi !~ /^dbi:/) { |
|
139
|
|
|
|
|
|
|
my $rh = $self->resources_hash; |
|
140
|
|
|
|
|
|
|
my $res = |
|
141
|
|
|
|
|
|
|
$rh->{$dbi}; |
|
142
|
|
|
|
|
|
|
if (!$res) { |
|
143
|
|
|
|
|
|
|
if ($dbi =~ /:/) { |
|
144
|
|
|
|
|
|
|
$res = |
|
145
|
|
|
|
|
|
|
{loc=>"$dbi"} |
|
146
|
|
|
|
|
|
|
} |
|
147
|
|
|
|
|
|
|
else { |
|
148
|
|
|
|
|
|
|
$res = |
|
149
|
|
|
|
|
|
|
{loc=>"Pg:$dbi"}; |
|
150
|
|
|
|
|
|
|
} |
|
151
|
|
|
|
|
|
|
} |
|
152
|
|
|
|
|
|
|
if ($res) { |
|
153
|
|
|
|
|
|
|
my $loc = $res->{loc}; |
|
154
|
|
|
|
|
|
|
if ($loc =~ /(\S+?):(\S+)\@(\S+)/) { |
|
155
|
|
|
|
|
|
|
my $dbms = $1; |
|
156
|
|
|
|
|
|
|
my $dbn = $2; |
|
157
|
|
|
|
|
|
|
my $host = $3; |
|
158
|
|
|
|
|
|
|
my $extra = ''; |
|
159
|
|
|
|
|
|
|
if ($host =~ /(\S+?):(.*)/) { |
|
160
|
|
|
|
|
|
|
$host = $1; |
|
161
|
|
|
|
|
|
|
$extra = ":$2"; |
|
162
|
|
|
|
|
|
|
} |
|
163
|
|
|
|
|
|
|
if ($dbms =~ /pg/i) { |
|
164
|
|
|
|
|
|
|
$dbi = "dbi:Pg:dbname=$dbn;host=$host$extra"; |
|
165
|
|
|
|
|
|
|
} |
|
166
|
|
|
|
|
|
|
elsif ($dbms =~ /db2/i) { |
|
167
|
|
|
|
|
|
|
$dbi = "dbi:Pg:$dbn;host=$host$extra"; |
|
168
|
|
|
|
|
|
|
} |
|
169
|
|
|
|
|
|
|
else { |
|
170
|
|
|
|
|
|
|
# default - tested on MySQL |
|
171
|
|
|
|
|
|
|
$dbi = "dbi:$dbms:database=$dbn:host=$host$extra"; |
|
172
|
|
|
|
|
|
|
} |
|
173
|
|
|
|
|
|
|
} |
|
174
|
|
|
|
|
|
|
elsif ($loc =~ /(\S+):(\S+)$/) { |
|
175
|
|
|
|
|
|
|
my $dbms = $1; |
|
176
|
|
|
|
|
|
|
my $dbn = $2; |
|
177
|
|
|
|
|
|
|
$dbi = "dbi:$dbms:database=$dbn"; |
|
178
|
|
|
|
|
|
|
if ($dbms =~ /pg/i) { |
|
179
|
|
|
|
|
|
|
$dbi = "dbi:Pg:dbname=$dbn"; |
|
180
|
|
|
|
|
|
|
} |
|
181
|
|
|
|
|
|
|
} |
|
182
|
|
|
|
|
|
|
else { |
|
183
|
|
|
|
|
|
|
$self->throw("$dbi -> $loc does not conform to standard.\n". |
|
184
|
|
|
|
|
|
|
":\@"); |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
else { |
|
188
|
|
|
|
|
|
|
$self->throw("$dbi is not a valid DBI locator.\n"); |
|
189
|
|
|
|
|
|
|
} |
|
190
|
|
|
|
|
|
|
} |
|
191
|
|
|
|
|
|
|
return $dbi; |
|
192
|
|
|
|
|
|
|
} |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
sub resources_hash { |
|
195
|
|
|
|
|
|
|
my $self = shift; |
|
196
|
|
|
|
|
|
|
my $mapf = $ENV{DBSTAG_DBIMAP_FILE}; |
|
197
|
|
|
|
|
|
|
my $rh; |
|
198
|
|
|
|
|
|
|
if ($mapf) { |
|
199
|
|
|
|
|
|
|
if (-f $mapf) { |
|
200
|
|
|
|
|
|
|
$rh = {}; |
|
201
|
|
|
|
|
|
|
open(F, $mapf) || $self->throw("Cannot open $mapf"); |
|
202
|
|
|
|
|
|
|
while () { |
|
203
|
|
|
|
|
|
|
chomp; |
|
204
|
|
|
|
|
|
|
next if /^\#/; |
|
205
|
|
|
|
|
|
|
s/^\!//; |
|
206
|
|
|
|
|
|
|
my @parts =split(' ', $_); |
|
207
|
|
|
|
|
|
|
next unless (@parts >= 3); |
|
208
|
|
|
|
|
|
|
my ($name, $type, $loc, $tagstr) =@parts; |
|
209
|
|
|
|
|
|
|
my %tagh = (); |
|
210
|
|
|
|
|
|
|
if ($tagstr) { |
|
211
|
|
|
|
|
|
|
my @parts = split(/;\s*/, $tagstr); |
|
212
|
|
|
|
|
|
|
foreach (@parts) { |
|
213
|
|
|
|
|
|
|
my ($t, $v) = split(/\s*=\s*/, $_); |
|
214
|
|
|
|
|
|
|
$tagh{$t} = $v; |
|
215
|
|
|
|
|
|
|
} |
|
216
|
|
|
|
|
|
|
} |
|
217
|
|
|
|
|
|
|
$rh->{$name} = |
|
218
|
|
|
|
|
|
|
{ |
|
219
|
|
|
|
|
|
|
%tagh, |
|
220
|
|
|
|
|
|
|
name=>$name, |
|
221
|
|
|
|
|
|
|
type=>$type, |
|
222
|
|
|
|
|
|
|
loc=>$loc, |
|
223
|
|
|
|
|
|
|
tagstr=>$tagstr, |
|
224
|
|
|
|
|
|
|
}; |
|
225
|
|
|
|
|
|
|
} |
|
226
|
|
|
|
|
|
|
close(F) || $self->throw("Cannot close $mapf"); |
|
227
|
|
|
|
|
|
|
} else { |
|
228
|
|
|
|
|
|
|
$self->throw("$mapf does not exist"); |
|
229
|
|
|
|
|
|
|
} |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
return $rh; |
|
232
|
|
|
|
|
|
|
} |
|
233
|
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
sub resources_list { |
|
236
|
|
|
|
|
|
|
my $self = shift; |
|
237
|
|
|
|
|
|
|
my $rh = |
|
238
|
|
|
|
|
|
|
$self->resources_hash; |
|
239
|
|
|
|
|
|
|
my $rl; |
|
240
|
|
|
|
|
|
|
if ($rh) { |
|
241
|
|
|
|
|
|
|
$rl = |
|
242
|
|
|
|
|
|
|
[map {$_} values %$rh]; |
|
243
|
|
|
|
|
|
|
} |
|
244
|
|
|
|
|
|
|
return $rl; |
|
245
|
|
|
|
|
|
|
} |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
sub find_template { |
|
248
|
|
|
|
|
|
|
my $self = shift; |
|
249
|
|
|
|
|
|
|
my $tname = shift; |
|
250
|
|
|
|
|
|
|
my $path = $ENV{DBSTAG_TEMPLATE_DIRS} || ''; |
|
251
|
|
|
|
|
|
|
my $tl = $self->template_list; |
|
252
|
|
|
|
|
|
|
my ($template, @rest) = grep {$tname eq $_->name} @$tl; |
|
253
|
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
if (!$template) { |
|
255
|
|
|
|
|
|
|
print STDERR "\n\nI could not find the Stag SQL template called \"$tname\".\n"; |
|
256
|
|
|
|
|
|
|
if (!$path) { |
|
257
|
|
|
|
|
|
|
print STDERR <
|
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
In order to do use this or any other template, you need to set the environment |
|
260
|
|
|
|
|
|
|
variable DBSTAG_TEMPLATE_DIRS to the directory or a set of directories |
|
261
|
|
|
|
|
|
|
containing SQL templates. For example |
|
262
|
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
setenv DBSTAG_TEMPLATE_DIRS=".:\$HOME/my-sql-templates:/usr/share/system-sql-templates" |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
EOM1 |
|
266
|
|
|
|
|
|
|
; |
|
267
|
|
|
|
|
|
|
} |
|
268
|
|
|
|
|
|
|
else { |
|
269
|
|
|
|
|
|
|
print STDERR <
|
|
270
|
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
I am looking in the following directories: |
|
272
|
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
$path |
|
274
|
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
Check the contents of the directory to see if the stag sql template |
|
276
|
|
|
|
|
|
|
you require is there, and is readable by you. Stag SQL templates |
|
277
|
|
|
|
|
|
|
should end with the suffix ".stg" |
|
278
|
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
If you wish to search other directories, set the environment variable |
|
280
|
|
|
|
|
|
|
DBSTAG_TEMPLATE_DIRS, like this: |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
setenv DBSTAG_TEMPLATE_DIRS=".:\$HOME/my-sql-templates:$path" |
|
283
|
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
EOM2 |
|
285
|
|
|
|
|
|
|
; |
|
286
|
|
|
|
|
|
|
} |
|
287
|
|
|
|
|
|
|
$self->throw("Could not find template \"$tname\" in: $path"); |
|
288
|
|
|
|
|
|
|
} |
|
289
|
|
|
|
|
|
|
return $template; |
|
290
|
|
|
|
|
|
|
} |
|
291
|
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
sub find_templates_by_schema { |
|
293
|
|
|
|
|
|
|
my $self = shift; |
|
294
|
|
|
|
|
|
|
my $schema = shift; |
|
295
|
|
|
|
|
|
|
my $tl = $self->template_list; |
|
296
|
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
my @templates = grep {$_->stag_props->tmatch('schema', $schema)} @$tl; |
|
298
|
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
return \@templates; |
|
300
|
|
|
|
|
|
|
} |
|
301
|
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
sub find_templates_by_dbname { |
|
303
|
|
|
|
|
|
|
my $self = shift; |
|
304
|
|
|
|
|
|
|
my $dbname = shift; |
|
305
|
|
|
|
|
|
|
my $res = $self->resources_hash->{$dbname}; |
|
306
|
|
|
|
|
|
|
my $templates; |
|
307
|
|
|
|
|
|
|
if ($res) { |
|
308
|
|
|
|
|
|
|
my $schema = $res->{schema} || ''; |
|
309
|
|
|
|
|
|
|
if ($schema) { |
|
310
|
|
|
|
|
|
|
$templates = $self->find_templates_by_schema($schema); |
|
311
|
|
|
|
|
|
|
} |
|
312
|
|
|
|
|
|
|
else { |
|
313
|
|
|
|
|
|
|
# unknown schema - show all templates |
|
314
|
|
|
|
|
|
|
# $templates = $self->template_list; |
|
315
|
|
|
|
|
|
|
} |
|
316
|
|
|
|
|
|
|
} |
|
317
|
|
|
|
|
|
|
else { |
|
318
|
|
|
|
|
|
|
$self->throw("unknown db: $dbname"); |
|
319
|
|
|
|
|
|
|
} |
|
320
|
|
|
|
|
|
|
return $templates; |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
sub template_list { |
|
324
|
|
|
|
|
|
|
my $self = shift; |
|
325
|
|
|
|
|
|
|
my %already_got = (); |
|
326
|
|
|
|
|
|
|
if (!$self->{_template_list}) { |
|
327
|
|
|
|
|
|
|
my $path = $ENV{DBSTAG_TEMPLATE_DIRS} || '.'; |
|
328
|
|
|
|
|
|
|
my @dirs = split(/:/, $path); |
|
329
|
|
|
|
|
|
|
my @templates = (); |
|
330
|
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
foreach my $dir (@dirs) { |
|
332
|
|
|
|
|
|
|
foreach my $fn (glob("$dir/*.stg")) { |
|
333
|
|
|
|
|
|
|
if (-f $fn) { |
|
334
|
|
|
|
|
|
|
require "DBIx/DBStag/SQLTemplate.pm"; |
|
335
|
|
|
|
|
|
|
my $template = DBIx::DBStag::SQLTemplate->new; |
|
336
|
|
|
|
|
|
|
$template->parse($fn); |
|
337
|
|
|
|
|
|
|
push(@templates, $template) unless $already_got{$template->name}; |
|
338
|
|
|
|
|
|
|
$already_got{$template->name} = 1; |
|
339
|
|
|
|
|
|
|
} |
|
340
|
|
|
|
|
|
|
} |
|
341
|
|
|
|
|
|
|
} |
|
342
|
|
|
|
|
|
|
$self->{_template_list} = \@templates; |
|
343
|
|
|
|
|
|
|
} |
|
344
|
|
|
|
|
|
|
return $self->{_template_list}; |
|
345
|
|
|
|
|
|
|
} |
|
346
|
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
sub find_schema { |
|
348
|
|
|
|
|
|
|
my $self = shift; |
|
349
|
|
|
|
|
|
|
my $dbname = shift; |
|
350
|
|
|
|
|
|
|
my $rl = $self->resouces_list || []; |
|
351
|
|
|
|
|
|
|
my ($r) = grep {$_->{name} eq $_ || |
|
352
|
|
|
|
|
|
|
$_->{loc} eq $_} @$rl; |
|
353
|
|
|
|
|
|
|
if ($r) { |
|
354
|
|
|
|
|
|
|
return $r->{schema}; |
|
355
|
|
|
|
|
|
|
} |
|
356
|
|
|
|
|
|
|
return; |
|
357
|
|
|
|
|
|
|
} |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
sub setup { |
|
360
|
|
|
|
|
|
|
my $self = shift; |
|
361
|
|
|
|
|
|
|
return; |
|
362
|
|
|
|
|
|
|
} |
|
363
|
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
# counter |
|
365
|
|
|
|
|
|
|
sub next_id { |
|
366
|
|
|
|
|
|
|
my $self = shift; |
|
367
|
|
|
|
|
|
|
$self->{_next_id} = shift if @_; |
|
368
|
|
|
|
|
|
|
$self->{_next_id} = 0 unless $self->{_next_id}; |
|
369
|
|
|
|
|
|
|
return ++$self->{_next_id}; |
|
370
|
|
|
|
|
|
|
} |
|
371
|
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
sub dbh { |
|
374
|
|
|
|
|
|
|
my $self = shift; |
|
375
|
|
|
|
|
|
|
$self->{_dbh} = shift if @_; |
|
376
|
|
|
|
|
|
|
return $self->{_dbh}; |
|
377
|
|
|
|
|
|
|
} |
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
sub dbschema { |
|
380
|
|
|
|
|
|
|
my $self = shift; |
|
381
|
|
|
|
|
|
|
$self->{_dbschema} = shift if @_; |
|
382
|
|
|
|
|
|
|
if (!$self->{_dbschema}) { |
|
383
|
|
|
|
|
|
|
if (!$self->dbh) { |
|
384
|
|
|
|
|
|
|
confess("you must establish connection using connect() first"); |
|
385
|
|
|
|
|
|
|
} |
|
386
|
|
|
|
|
|
|
$self->dbschema(DBIx::DBSchema->new_native($self->dbh)); |
|
387
|
|
|
|
|
|
|
# my $sth = $self->dbh->table_info(undef, undef, undef, 'VIEW') or die $self->dbh->errstr; |
|
388
|
|
|
|
|
|
|
# use Data::Dumper; |
|
389
|
|
|
|
|
|
|
# print Dumper $sth->fetchall_arrayref([2,3]); |
|
390
|
|
|
|
|
|
|
} |
|
391
|
|
|
|
|
|
|
return $self->{_dbschema}; |
|
392
|
|
|
|
|
|
|
} |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
sub parser { |
|
395
|
|
|
|
|
|
|
my $self = shift; |
|
396
|
|
|
|
|
|
|
$self->{_parser} = shift if @_; |
|
397
|
|
|
|
|
|
|
if (!$self->{_parser}) { |
|
398
|
|
|
|
|
|
|
$self->{_parser} = Parse::RecDescent->new($self->selectgrammar()); |
|
399
|
|
|
|
|
|
|
} |
|
400
|
|
|
|
|
|
|
return $self->{_parser}; |
|
401
|
|
|
|
|
|
|
} |
|
402
|
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
sub warn { |
|
404
|
|
|
|
|
|
|
my $self = shift; |
|
405
|
|
|
|
|
|
|
my $fmt = shift; |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
print STDERR "\nWARNING:\n"; |
|
408
|
|
|
|
|
|
|
printf STDERR $fmt, @_; |
|
409
|
|
|
|
|
|
|
print STDERR "\n"; |
|
410
|
|
|
|
|
|
|
} |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
sub throw { |
|
413
|
|
|
|
|
|
|
my $self = shift; |
|
414
|
|
|
|
|
|
|
my $fmt = shift; |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
print STDERR "\nERROR:\n"; |
|
417
|
|
|
|
|
|
|
printf STDERR $fmt, @_; |
|
418
|
|
|
|
|
|
|
print STDERR "\n"; |
|
419
|
|
|
|
|
|
|
confess; |
|
420
|
|
|
|
|
|
|
} |
|
421
|
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
sub get_pk_col { |
|
423
|
|
|
|
|
|
|
my $self = shift; |
|
424
|
|
|
|
|
|
|
my $table = shift; |
|
425
|
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
my $tableobj = $self->dbschema->table(lc($table)); |
|
427
|
|
|
|
|
|
|
if (!$tableobj) { |
|
428
|
|
|
|
|
|
|
confess("Can't get table $table from db.\n". |
|
429
|
|
|
|
|
|
|
"Maybe DBIx::DBSchema does not work with your database?"); |
|
430
|
|
|
|
|
|
|
} |
|
431
|
|
|
|
|
|
|
return $tableobj->primary_key; |
|
432
|
|
|
|
|
|
|
} |
|
433
|
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
sub is_table { |
|
435
|
|
|
|
|
|
|
my $self = shift; |
|
436
|
|
|
|
|
|
|
my $tbl = shift; |
|
437
|
|
|
|
|
|
|
return 1 if $self->dbschema->table($tbl); |
|
438
|
|
|
|
|
|
|
} |
|
439
|
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
sub is_col { |
|
441
|
|
|
|
|
|
|
my $self = shift; |
|
442
|
|
|
|
|
|
|
my $col = shift; |
|
443
|
|
|
|
|
|
|
if ($self->{_is_col_h}) { |
|
444
|
|
|
|
|
|
|
return $self->{_is_col_h}->{$col} |
|
445
|
|
|
|
|
|
|
} |
|
446
|
|
|
|
|
|
|
my @tablenames = $self->dbschema->tables; |
|
447
|
|
|
|
|
|
|
my @allcols = |
|
448
|
|
|
|
|
|
|
map { |
|
449
|
|
|
|
|
|
|
$self->get_all_cols($_); |
|
450
|
|
|
|
|
|
|
} @tablenames; |
|
451
|
|
|
|
|
|
|
my %h = map {$_=>1} @allcols; |
|
452
|
|
|
|
|
|
|
$self->{_is_col_h} = \%h; |
|
453
|
|
|
|
|
|
|
return $self->{_is_col_h}->{$col}; |
|
454
|
|
|
|
|
|
|
} |
|
455
|
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
# ASSUMPTION: pk names same as fk names |
|
457
|
|
|
|
|
|
|
sub is_fk_col { |
|
458
|
|
|
|
|
|
|
my $self = shift; |
|
459
|
|
|
|
|
|
|
my $col = shift; |
|
460
|
|
|
|
|
|
|
# HACK!!! |
|
461
|
|
|
|
|
|
|
# currently dbschema does not know about FKs |
|
462
|
|
|
|
|
|
|
return 1 if $col =~ /_id$/; |
|
463
|
|
|
|
|
|
|
if ($self->{_is_fk_col_h}) { |
|
464
|
|
|
|
|
|
|
return $self->{_is_fk_col_h}->{$col} |
|
465
|
|
|
|
|
|
|
} |
|
466
|
|
|
|
|
|
|
my @tablenames = $self->dbschema->tables; |
|
467
|
|
|
|
|
|
|
my %h = (); |
|
468
|
|
|
|
|
|
|
foreach (@tablenames) { |
|
469
|
|
|
|
|
|
|
my $pk = $self->dbschema->table($_)->primary_key; |
|
470
|
|
|
|
|
|
|
$h{$pk} =1 if $pk; |
|
471
|
|
|
|
|
|
|
} |
|
472
|
|
|
|
|
|
|
$self->{_is_fk_col_h} = \%h; |
|
473
|
|
|
|
|
|
|
return $self->{_is_fk_col_h}->{$col}; |
|
474
|
|
|
|
|
|
|
} |
|
475
|
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
sub is_pk_col { |
|
477
|
|
|
|
|
|
|
my $self = shift; |
|
478
|
|
|
|
|
|
|
my $col = shift; |
|
479
|
|
|
|
|
|
|
if ($self->{_is_pk_col_h}) { |
|
480
|
|
|
|
|
|
|
return $self->{_is_pk_col_h}->{$col} |
|
481
|
|
|
|
|
|
|
} |
|
482
|
|
|
|
|
|
|
my @tablenames = $self->dbschema->tables; |
|
483
|
|
|
|
|
|
|
my %h = (); |
|
484
|
|
|
|
|
|
|
foreach (@tablenames) { |
|
485
|
|
|
|
|
|
|
my $pk = $self->dbschema->table($_)->primary_key; |
|
486
|
|
|
|
|
|
|
$h{$pk} =1 if $pk; |
|
487
|
|
|
|
|
|
|
} |
|
488
|
|
|
|
|
|
|
$self->{_is_pk_col_h} = \%h; |
|
489
|
|
|
|
|
|
|
return $self->{_is_pk_col_h}->{$col}; |
|
490
|
|
|
|
|
|
|
} |
|
491
|
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
sub get_all_cols { |
|
493
|
|
|
|
|
|
|
my $self = shift; |
|
494
|
|
|
|
|
|
|
my $table = shift; |
|
495
|
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
my $tableobj = $self->dbschema->table(lc($table)); |
|
497
|
|
|
|
|
|
|
if (!$tableobj) { |
|
498
|
|
|
|
|
|
|
confess("Can't get table $table from db.\n". |
|
499
|
|
|
|
|
|
|
"Maybe DBIx::DBSchema does not work with your database?"); |
|
500
|
|
|
|
|
|
|
} |
|
501
|
|
|
|
|
|
|
return $tableobj->columns; |
|
502
|
|
|
|
|
|
|
} |
|
503
|
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
sub get_unique_sets { |
|
505
|
|
|
|
|
|
|
my $self = shift; |
|
506
|
|
|
|
|
|
|
my $table = shift; |
|
507
|
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
my $tableobj = $self->dbschema->table(lc($table)); |
|
509
|
|
|
|
|
|
|
if (!$tableobj) { |
|
510
|
|
|
|
|
|
|
confess("Can't get table $table from db.\n". |
|
511
|
|
|
|
|
|
|
"Maybe DBIx::DBSchema does not work with your database?"); |
|
512
|
|
|
|
|
|
|
} |
|
513
|
|
|
|
|
|
|
if ($ENV{OLD_DBIX_DBSCHEMA}) { |
|
514
|
|
|
|
|
|
|
return @{$tableobj->unique->lol_ref || []}; |
|
515
|
|
|
|
|
|
|
} |
|
516
|
|
|
|
|
|
|
else { |
|
517
|
|
|
|
|
|
|
my %indices = $tableobj->indices; |
|
518
|
|
|
|
|
|
|
my @unique_indices = grep {$_->unique} values %indices; |
|
519
|
|
|
|
|
|
|
return map {$_->columns} @unique_indices; |
|
520
|
|
|
|
|
|
|
} |
|
521
|
|
|
|
|
|
|
} |
|
522
|
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
sub mapconf { |
|
524
|
|
|
|
|
|
|
my $self = shift; |
|
525
|
|
|
|
|
|
|
my $fn = shift; |
|
526
|
|
|
|
|
|
|
my $fh = FileHandle->new($fn) || confess("cannot open $fn"); |
|
527
|
|
|
|
|
|
|
my @mappings = <$fh>; |
|
528
|
|
|
|
|
|
|
$fh->close; |
|
529
|
|
|
|
|
|
|
$self->mapping(\@mappings); |
|
530
|
|
|
|
|
|
|
} |
|
531
|
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
sub mapping { |
|
533
|
|
|
|
|
|
|
my $self = shift; |
|
534
|
|
|
|
|
|
|
if (@_) { |
|
535
|
|
|
|
|
|
|
my $ml = shift; |
|
536
|
|
|
|
|
|
|
my @nu = |
|
537
|
|
|
|
|
|
|
map { |
|
538
|
|
|
|
|
|
|
if (ref($_)) { |
|
539
|
|
|
|
|
|
|
Data::Stag->nodify($_); |
|
540
|
|
|
|
|
|
|
} |
|
541
|
|
|
|
|
|
|
else { |
|
542
|
|
|
|
|
|
|
if (/^(\w+)\/(\w+)\.(\w+)=(\w+)\.(\w+)/) { |
|
543
|
|
|
|
|
|
|
Data::Stag->new(map=>[ |
|
544
|
|
|
|
|
|
|
[fktable_alias=>$1], |
|
545
|
|
|
|
|
|
|
[table=>$2], |
|
546
|
|
|
|
|
|
|
[col=>$3], |
|
547
|
|
|
|
|
|
|
[fktable=>$4], |
|
548
|
|
|
|
|
|
|
[fkcol=>$5] |
|
549
|
|
|
|
|
|
|
]); |
|
550
|
|
|
|
|
|
|
} |
|
551
|
|
|
|
|
|
|
elsif (/^(\w+)\.(\w+)=(\w+)\.(\w+)/) { |
|
552
|
|
|
|
|
|
|
Data::Stag->new(map=>[ |
|
553
|
|
|
|
|
|
|
[table=>$1], |
|
554
|
|
|
|
|
|
|
[col=>$2], |
|
555
|
|
|
|
|
|
|
[fktable=>$3], |
|
556
|
|
|
|
|
|
|
[fkcol=>$4] |
|
557
|
|
|
|
|
|
|
]); |
|
558
|
|
|
|
|
|
|
} |
|
559
|
|
|
|
|
|
|
elsif (/^parentfk:(\w+)\.(\w+)/) { |
|
560
|
|
|
|
|
|
|
Data::Stag->new(parentfk=>[ |
|
561
|
|
|
|
|
|
|
[table=>$1], |
|
562
|
|
|
|
|
|
|
[col=>$2], |
|
563
|
|
|
|
|
|
|
]); |
|
564
|
|
|
|
|
|
|
} |
|
565
|
|
|
|
|
|
|
else { |
|
566
|
|
|
|
|
|
|
confess("incorrectly specified mapping: $_". |
|
567
|
|
|
|
|
|
|
"(must be alias/tbl.col=ftbl.fcol)"); |
|
568
|
|
|
|
|
|
|
(); |
|
569
|
|
|
|
|
|
|
} |
|
570
|
|
|
|
|
|
|
} |
|
571
|
|
|
|
|
|
|
} @$ml; |
|
572
|
|
|
|
|
|
|
$self->{_mapping} = \@nu; |
|
573
|
|
|
|
|
|
|
} |
|
574
|
|
|
|
|
|
|
return $self->{_mapping}; |
|
575
|
|
|
|
|
|
|
} |
|
576
|
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
sub guess_mapping { |
|
578
|
|
|
|
|
|
|
my $self = shift; |
|
579
|
|
|
|
|
|
|
my $dbschema = $self->dbschema; |
|
580
|
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
$self->mapping([]); |
|
582
|
|
|
|
|
|
|
my %th = |
|
583
|
|
|
|
|
|
|
map { $_ => $dbschema->table($_) } $dbschema->tables; |
|
584
|
|
|
|
|
|
|
foreach my $tn (keys %th) { |
|
585
|
|
|
|
|
|
|
my @cns = $th{$tn}->columns; |
|
586
|
|
|
|
|
|
|
foreach my $cn (@cns) { |
|
587
|
|
|
|
|
|
|
my $ftn = $cn; |
|
588
|
|
|
|
|
|
|
$ftn =~ s/_id$//; |
|
589
|
|
|
|
|
|
|
if ($th{$ftn}) { |
|
590
|
|
|
|
|
|
|
push(@{$self->mapping}, |
|
591
|
|
|
|
|
|
|
Data::Stag->new(map=>[ |
|
592
|
|
|
|
|
|
|
[table=>$tn], |
|
593
|
|
|
|
|
|
|
[col=>$cn], |
|
594
|
|
|
|
|
|
|
[fktable=>$ftn], |
|
595
|
|
|
|
|
|
|
[fkcol=>$cn] |
|
596
|
|
|
|
|
|
|
])); |
|
597
|
|
|
|
|
|
|
} |
|
598
|
|
|
|
|
|
|
} |
|
599
|
|
|
|
|
|
|
} |
|
600
|
|
|
|
|
|
|
} |
|
601
|
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
sub linking_tables { |
|
603
|
|
|
|
|
|
|
my $self = shift; |
|
604
|
|
|
|
|
|
|
$self->{_linking_tables} = {@_} if @_; |
|
605
|
|
|
|
|
|
|
return %{$self->{_linking_tables} || {}}; |
|
606
|
|
|
|
|
|
|
} |
|
607
|
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
sub add_linking_tables { |
|
609
|
|
|
|
|
|
|
my $self = shift; |
|
610
|
|
|
|
|
|
|
my %linkh = $self->linking_tables; |
|
611
|
|
|
|
|
|
|
return unless %linkh; |
|
612
|
|
|
|
|
|
|
my $struct = shift; |
|
613
|
|
|
|
|
|
|
foreach my $ltname (keys %linkh) { |
|
614
|
|
|
|
|
|
|
my ($t1, $t2) = @{$linkh{$ltname}}; |
|
615
|
|
|
|
|
|
|
$struct->where($t1, |
|
616
|
|
|
|
|
|
|
sub { |
|
617
|
|
|
|
|
|
|
my $n=shift; |
|
618
|
|
|
|
|
|
|
my @v = $n->getnode($t2); |
|
619
|
|
|
|
|
|
|
return unless @v; |
|
620
|
|
|
|
|
|
|
$n->unset($t2); |
|
621
|
|
|
|
|
|
|
my @nv = |
|
622
|
|
|
|
|
|
|
map { |
|
623
|
|
|
|
|
|
|
$n->new($ltname=>[$_]); |
|
624
|
|
|
|
|
|
|
} @v; |
|
625
|
|
|
|
|
|
|
# $n->setnode($ltname, |
|
626
|
|
|
|
|
|
|
# $n->new($ltname=>[@v])); |
|
627
|
|
|
|
|
|
|
foreach (@nv) { |
|
628
|
|
|
|
|
|
|
$n->addkid($_); |
|
629
|
|
|
|
|
|
|
} |
|
630
|
|
|
|
|
|
|
0; |
|
631
|
|
|
|
|
|
|
}); |
|
632
|
|
|
|
|
|
|
} |
|
633
|
|
|
|
|
|
|
return; |
|
634
|
|
|
|
|
|
|
} |
|
635
|
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
# ---------------------------------------- |
|
637
|
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
sub elt_card { |
|
639
|
|
|
|
|
|
|
my $e = shift; |
|
640
|
|
|
|
|
|
|
my $c = ''; |
|
641
|
|
|
|
|
|
|
if ($e =~ /(.*)([\+\?\*])/) { |
|
642
|
|
|
|
|
|
|
($e, $c) = ($1, $2); |
|
643
|
|
|
|
|
|
|
} |
|
644
|
|
|
|
|
|
|
# make the element RDB-safe |
|
645
|
|
|
|
|
|
|
$e =~ s/\-//g; |
|
646
|
|
|
|
|
|
|
return ($e, $c); |
|
647
|
|
|
|
|
|
|
} |
|
648
|
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
sub source_transforms { |
|
650
|
|
|
|
|
|
|
my $self = shift; |
|
651
|
|
|
|
|
|
|
$self->{_source_transforms} = shift if @_; |
|
652
|
|
|
|
|
|
|
return $self->{_source_transforms}; |
|
653
|
|
|
|
|
|
|
} |
|
654
|
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
sub autotemplate { |
|
656
|
|
|
|
|
|
|
my $self = shift; |
|
657
|
|
|
|
|
|
|
my $schema = shift; |
|
658
|
|
|
|
|
|
|
return () unless grep {!stag_isterminal($_)} $schema->subnodes; |
|
659
|
|
|
|
|
|
|
my @J = (); |
|
660
|
|
|
|
|
|
|
my @W = (); |
|
661
|
|
|
|
|
|
|
my @EXAMPLE = (); |
|
662
|
|
|
|
|
|
|
my ($tname) = elt_card($schema->element); |
|
663
|
|
|
|
|
|
|
my %joinpaths = (); |
|
664
|
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
$schema->iterate(sub { |
|
666
|
|
|
|
|
|
|
my $n = shift; |
|
667
|
|
|
|
|
|
|
my $parent = shift; |
|
668
|
|
|
|
|
|
|
my ($tbl, $card) = elt_card($n->element); |
|
669
|
|
|
|
|
|
|
if (!$parent) { |
|
670
|
|
|
|
|
|
|
push(@J, $tbl); |
|
671
|
|
|
|
|
|
|
# $joinpaths{$tbl} = $tbl; |
|
672
|
|
|
|
|
|
|
return; |
|
673
|
|
|
|
|
|
|
} |
|
674
|
|
|
|
|
|
|
my ($ptbl) = elt_card($parent->element); |
|
675
|
|
|
|
|
|
|
if (stag_isterminal($n)) { |
|
676
|
|
|
|
|
|
|
my $v = $ptbl.'_'.$tbl; |
|
677
|
|
|
|
|
|
|
my $w = "$ptbl.$tbl => \&$v\&"; |
|
678
|
|
|
|
|
|
|
if ($ptbl eq $tname) { |
|
679
|
|
|
|
|
|
|
push(@W, |
|
680
|
|
|
|
|
|
|
"[ $w ]"); |
|
681
|
|
|
|
|
|
|
} |
|
682
|
|
|
|
|
|
|
else { |
|
683
|
|
|
|
|
|
|
my $pk = $tname.'_id'; |
|
684
|
|
|
|
|
|
|
my $subselect = |
|
685
|
|
|
|
|
|
|
"SELECT $pk FROM $joinpaths{$ptbl}". |
|
686
|
|
|
|
|
|
|
" WHERE $w"; |
|
687
|
|
|
|
|
|
|
push(@W, |
|
688
|
|
|
|
|
|
|
"[ $pk IN ($subselect) ]"); |
|
689
|
|
|
|
|
|
|
} |
|
690
|
|
|
|
|
|
|
# produce example formula for non-ints |
|
691
|
|
|
|
|
|
|
if ($n->data eq 's') { |
|
692
|
|
|
|
|
|
|
push(@EXAMPLE, |
|
693
|
|
|
|
|
|
|
"$v => SELECT DISTINCT $tbl FROM $ptbl"); |
|
694
|
|
|
|
|
|
|
} |
|
695
|
|
|
|
|
|
|
} |
|
696
|
|
|
|
|
|
|
else { |
|
697
|
|
|
|
|
|
|
my $jtype = 'INNER JOIN'; |
|
698
|
|
|
|
|
|
|
if ($card eq '*' || $card eq '?') { |
|
699
|
|
|
|
|
|
|
$jtype = 'LEFT OUTER JOIN'; |
|
700
|
|
|
|
|
|
|
} |
|
701
|
|
|
|
|
|
|
my $jcol = $ptbl.'_id'; |
|
702
|
|
|
|
|
|
|
push(@J, |
|
703
|
|
|
|
|
|
|
"$jtype $tbl USING ($jcol)"); |
|
704
|
|
|
|
|
|
|
if ($joinpaths{$ptbl}) { |
|
705
|
|
|
|
|
|
|
$joinpaths{$tbl} = |
|
706
|
|
|
|
|
|
|
"$joinpaths{$ptbl} INNER JOIN $tbl USING ($jcol)"; |
|
707
|
|
|
|
|
|
|
} |
|
708
|
|
|
|
|
|
|
else { |
|
709
|
|
|
|
|
|
|
$joinpaths{$tbl} = $tbl; |
|
710
|
|
|
|
|
|
|
} |
|
711
|
|
|
|
|
|
|
} |
|
712
|
|
|
|
|
|
|
return; |
|
713
|
|
|
|
|
|
|
}); |
|
714
|
|
|
|
|
|
|
my $from = join("\n ", @J); |
|
715
|
|
|
|
|
|
|
my $where = join("\n ", @W); |
|
716
|
|
|
|
|
|
|
my $nesting = $schema->duplicate; |
|
717
|
|
|
|
|
|
|
$nesting->iterate(sub { |
|
718
|
|
|
|
|
|
|
my $n = shift; |
|
719
|
|
|
|
|
|
|
if (stag_isterminal($n)) { |
|
720
|
|
|
|
|
|
|
return; |
|
721
|
|
|
|
|
|
|
} |
|
722
|
|
|
|
|
|
|
my ($tbl, $card) = elt_card($n->element); |
|
723
|
|
|
|
|
|
|
$n->element($tbl); |
|
724
|
|
|
|
|
|
|
my @sn = $n->kids; |
|
725
|
|
|
|
|
|
|
@sn = |
|
726
|
|
|
|
|
|
|
grep { |
|
727
|
|
|
|
|
|
|
my ($tbl, $card) = elt_card($_->element); |
|
728
|
|
|
|
|
|
|
$_->element($tbl); |
|
729
|
|
|
|
|
|
|
!stag_isterminal($_) |
|
730
|
|
|
|
|
|
|
} @sn; |
|
731
|
|
|
|
|
|
|
if (@sn) { |
|
732
|
|
|
|
|
|
|
$n->kids(@sn); |
|
733
|
|
|
|
|
|
|
} |
|
734
|
|
|
|
|
|
|
else { |
|
735
|
|
|
|
|
|
|
$n->data([]); |
|
736
|
|
|
|
|
|
|
} |
|
737
|
|
|
|
|
|
|
}); |
|
738
|
|
|
|
|
|
|
$nesting = Data::Stag->new(set=>[$nesting]); |
|
739
|
|
|
|
|
|
|
my $nstr = $nesting->sxpr; |
|
740
|
|
|
|
|
|
|
$nstr =~ s/^\'//; |
|
741
|
|
|
|
|
|
|
my $tt = |
|
742
|
|
|
|
|
|
|
join("\n", |
|
743
|
|
|
|
|
|
|
":SELECT *", |
|
744
|
|
|
|
|
|
|
":FROM $from", |
|
745
|
|
|
|
|
|
|
":WHERE $where", |
|
746
|
|
|
|
|
|
|
":USE NESTING", |
|
747
|
|
|
|
|
|
|
"$nstr", |
|
748
|
|
|
|
|
|
|
"", |
|
749
|
|
|
|
|
|
|
"// ---- METADATA ----", |
|
750
|
|
|
|
|
|
|
"schema:", |
|
751
|
|
|
|
|
|
|
"desc: Fetches $tname objects", |
|
752
|
|
|
|
|
|
|
" This is an AUTOGENERATED template", |
|
753
|
|
|
|
|
|
|
"", |
|
754
|
|
|
|
|
|
|
(map { |
|
755
|
|
|
|
|
|
|
"example_input: $_" |
|
756
|
|
|
|
|
|
|
} @EXAMPLE), |
|
757
|
|
|
|
|
|
|
); |
|
758
|
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
# my $template = DBIx::DBStag::SQLTemplate->new; |
|
760
|
|
|
|
|
|
|
my @sn = $schema->subnodes; |
|
761
|
|
|
|
|
|
|
my @tts = (); |
|
762
|
|
|
|
|
|
|
push(@tts, $self->autotemplate($_)) foreach @sn; |
|
763
|
|
|
|
|
|
|
return ([$tname=>$tt], @tts); |
|
764
|
|
|
|
|
|
|
} |
|
765
|
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
sub autoddl { |
|
767
|
|
|
|
|
|
|
my $self = shift; |
|
768
|
|
|
|
|
|
|
my $stag = shift; |
|
769
|
|
|
|
|
|
|
my $link = shift; |
|
770
|
|
|
|
|
|
|
$stag->makeattrsnodes; |
|
771
|
|
|
|
|
|
|
my $schema = $stag->autoschema; |
|
772
|
|
|
|
|
|
|
$self->source_transforms([]);; |
|
773
|
|
|
|
|
|
|
$self->_autoddl($schema, undef, $link); |
|
774
|
|
|
|
|
|
|
} |
|
775
|
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
sub _autoddl { |
|
777
|
|
|
|
|
|
|
my $self = shift; |
|
778
|
|
|
|
|
|
|
my $schema = shift; |
|
779
|
|
|
|
|
|
|
my $parent = shift; |
|
780
|
|
|
|
|
|
|
my $link = shift || []; # link tables |
|
781
|
|
|
|
|
|
|
my $tbls = shift || []; |
|
782
|
|
|
|
|
|
|
my @sn = $schema->subnodes; |
|
783
|
|
|
|
|
|
|
my ($tbl, $card) = elt_card($schema->element); |
|
784
|
|
|
|
|
|
|
my @cols = (sprintf("%s_id serial PRIMARY KEY NOT NULL", $tbl)); |
|
785
|
|
|
|
|
|
|
my $casc = " ON DELETE CASCADE"; |
|
786
|
|
|
|
|
|
|
foreach (grep {stag_isterminal($_)} @sn) { |
|
787
|
|
|
|
|
|
|
my ($col, $card) = elt_card($_->element); |
|
788
|
|
|
|
|
|
|
my $pk = ''; |
|
789
|
|
|
|
|
|
|
if ($col eq $tbl.'_id') { |
|
790
|
|
|
|
|
|
|
shift @cols; |
|
791
|
|
|
|
|
|
|
$pk = ' PRIMARY KEY'; |
|
792
|
|
|
|
|
|
|
} |
|
793
|
|
|
|
|
|
|
if ($card =~ /[\+\*]/) { |
|
794
|
|
|
|
|
|
|
my $new_name = sprintf("%s_%s", $tbl, $col); |
|
795
|
|
|
|
|
|
|
my $tf = ["$tbl/$col", "$new_name/$col"]; |
|
796
|
|
|
|
|
|
|
push(@{$self->source_transforms}, $tf); |
|
797
|
|
|
|
|
|
|
$_->name($new_name); |
|
798
|
|
|
|
|
|
|
$_->data([[$col => $_->data]]); |
|
799
|
|
|
|
|
|
|
# $self->throw("In the source data, '$col' is a multivalued\n". |
|
800
|
|
|
|
|
|
|
# "terminal (data) node. This is difficult to transform"); |
|
801
|
|
|
|
|
|
|
} |
|
802
|
|
|
|
|
|
|
else { |
|
803
|
|
|
|
|
|
|
# my $isnull = $card eq '?' ? '' : ' NOT NULL'; |
|
804
|
|
|
|
|
|
|
my $isnull = ''; |
|
805
|
|
|
|
|
|
|
push(@cols, |
|
806
|
|
|
|
|
|
|
sprintf("%s %s$isnull$pk", |
|
807
|
|
|
|
|
|
|
$col, $_->data)); |
|
808
|
|
|
|
|
|
|
} |
|
809
|
|
|
|
|
|
|
} |
|
810
|
|
|
|
|
|
|
if ($parent) { |
|
811
|
|
|
|
|
|
|
my ($pn) = elt_card($parent->element); |
|
812
|
|
|
|
|
|
|
push(@cols, |
|
813
|
|
|
|
|
|
|
sprintf("%s_id INT", $pn)); |
|
814
|
|
|
|
|
|
|
push(@cols, |
|
815
|
|
|
|
|
|
|
sprintf("FOREIGN KEY (%s_id) REFERENCES $pn(%s_id)$casc", $pn, $pn)); |
|
816
|
|
|
|
|
|
|
} |
|
817
|
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
my $mapping = $self->mapping || []; |
|
819
|
|
|
|
|
|
|
|
|
820
|
|
|
|
|
|
|
if (grep {$_ eq $tbl} @$tbls) { |
|
821
|
|
|
|
|
|
|
# $self->throw("$tbl has >1 parent - you need to\n". |
|
822
|
|
|
|
|
|
|
# "transform input data"); |
|
823
|
|
|
|
|
|
|
return ""; |
|
824
|
|
|
|
|
|
|
} |
|
825
|
|
|
|
|
|
|
push(@$tbls, $tbl); |
|
826
|
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
my $post_ddl = ''; |
|
828
|
|
|
|
|
|
|
my $pre_ddl = ''; |
|
829
|
|
|
|
|
|
|
foreach (grep {!stag_isterminal($_)} @sn) { |
|
830
|
|
|
|
|
|
|
# check for cases where we want to include FK to subnode |
|
831
|
|
|
|
|
|
|
my ($map) = |
|
832
|
|
|
|
|
|
|
grep { |
|
833
|
|
|
|
|
|
|
$_->name eq 'map' && |
|
834
|
|
|
|
|
|
|
($_->get_table eq $tbl && |
|
835
|
|
|
|
|
|
|
($_->get_fktable_alias eq $_->element || |
|
836
|
|
|
|
|
|
|
$_->get_fktable eq $_->element)) |
|
837
|
|
|
|
|
|
|
} @$mapping; |
|
838
|
|
|
|
|
|
|
# linking tables |
|
839
|
|
|
|
|
|
|
if ($map || |
|
840
|
|
|
|
|
|
|
grep {$_ eq $tbl} @$link) { |
|
841
|
|
|
|
|
|
|
my $ftbl = $_->element; |
|
842
|
|
|
|
|
|
|
push(@cols, |
|
843
|
|
|
|
|
|
|
sprintf("%s_id INT", $ftbl)); |
|
844
|
|
|
|
|
|
|
push(@cols, |
|
845
|
|
|
|
|
|
|
sprintf("FOREIGN KEY (%s_id) REFERENCES $ftbl(%s_id)$casc", $ftbl, $ftbl)); |
|
846
|
|
|
|
|
|
|
$pre_ddl .= $self->_autoddl($_, undef, $link, $tbls); |
|
847
|
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
} |
|
849
|
|
|
|
|
|
|
else { |
|
850
|
|
|
|
|
|
|
$post_ddl .= $self->_autoddl($_, $schema, $link, $tbls); |
|
851
|
|
|
|
|
|
|
} |
|
852
|
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
} |
|
854
|
|
|
|
|
|
|
my $ddl = |
|
855
|
|
|
|
|
|
|
sprintf("CREATE TABLE $tbl (\n%s\n);\n\n", |
|
856
|
|
|
|
|
|
|
join(",\n", map {" $_"} @cols)); |
|
857
|
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
return $pre_ddl . $ddl . $post_ddl;; |
|
859
|
|
|
|
|
|
|
} |
|
860
|
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
# ---------------------------------------- |
|
862
|
|
|
|
|
|
|
# CACHE METHODS |
|
863
|
|
|
|
|
|
|
# |
|
864
|
|
|
|
|
|
|
# we keep a cache of what is stored in |
|
865
|
|
|
|
|
|
|
# each table |
|
866
|
|
|
|
|
|
|
# |
|
867
|
|
|
|
|
|
|
# cache->{$element}->{$key}->{$val} |
|
868
|
|
|
|
|
|
|
# ---------------------------------------- |
|
869
|
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
# list of table names that should be cached |
|
871
|
|
|
|
|
|
|
sub cached_tables { |
|
872
|
|
|
|
|
|
|
my $self = shift; |
|
873
|
|
|
|
|
|
|
$self->{_cached_tables} = shift if @_; |
|
874
|
|
|
|
|
|
|
return $self->{_cached_tables}; |
|
875
|
|
|
|
|
|
|
} |
|
876
|
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
sub is_caching_on { |
|
878
|
|
|
|
|
|
|
my $self = shift; |
|
879
|
|
|
|
|
|
|
my $element = shift; |
|
880
|
|
|
|
|
|
|
$self->{_is_caching_on} = {} |
|
881
|
|
|
|
|
|
|
unless $self->{_is_caching_on}; |
|
882
|
|
|
|
|
|
|
if (@_) { |
|
883
|
|
|
|
|
|
|
$self->{_is_caching_on}->{$element} = shift; |
|
884
|
|
|
|
|
|
|
} |
|
885
|
|
|
|
|
|
|
return $self->{_is_caching_on}->{$element}; |
|
886
|
|
|
|
|
|
|
} |
|
887
|
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
sub query_cache { |
|
889
|
|
|
|
|
|
|
my $self = shift; |
|
890
|
|
|
|
|
|
|
my $element = shift; |
|
891
|
|
|
|
|
|
|
my $constr = shift; |
|
892
|
|
|
|
|
|
|
my $update_h = shift; |
|
893
|
|
|
|
|
|
|
my @keycols = sort keys %$constr; |
|
894
|
|
|
|
|
|
|
my $cache = $self->get_tuple_idx($element, \@keycols); |
|
895
|
|
|
|
|
|
|
my $valstr = join("\t", map {$constr->{$_}} @keycols); |
|
896
|
|
|
|
|
|
|
# use Data::Dumper; |
|
897
|
|
|
|
|
|
|
# print Dumper $cache; |
|
898
|
|
|
|
|
|
|
if ($update_h) { |
|
899
|
|
|
|
|
|
|
my $current_h = $cache->{$valstr} || {}; |
|
900
|
|
|
|
|
|
|
$current_h->{$_} = $update_h->{$_} foreach keys %$update_h; |
|
901
|
|
|
|
|
|
|
$cache->{$valstr} = $current_h; |
|
902
|
|
|
|
|
|
|
} |
|
903
|
|
|
|
|
|
|
return $cache->{$valstr}; |
|
904
|
|
|
|
|
|
|
} |
|
905
|
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
sub insert_into_cache { |
|
907
|
|
|
|
|
|
|
my $self = shift; |
|
908
|
|
|
|
|
|
|
my $element = shift; |
|
909
|
|
|
|
|
|
|
my $insert_h = shift; |
|
910
|
|
|
|
|
|
|
my $usets = shift; |
|
911
|
|
|
|
|
|
|
foreach my $uset (@$usets) { |
|
912
|
|
|
|
|
|
|
my @undef = grep {!defined $insert_h->{$_}} @$uset; |
|
913
|
|
|
|
|
|
|
if (@undef) { |
|
914
|
|
|
|
|
|
|
my @defined = grep {defined $insert_h->{$_}} @$uset; |
|
915
|
|
|
|
|
|
|
trace(1, |
|
916
|
|
|
|
|
|
|
"undefined column in unique key: @$uset IN $element/[@$uset] ". |
|
917
|
|
|
|
|
|
|
join('; ', |
|
918
|
|
|
|
|
|
|
map {"$_=$insert_h->{$_}"} @defined, |
|
919
|
|
|
|
|
|
|
) |
|
920
|
|
|
|
|
|
|
) if $TRACE; |
|
921
|
|
|
|
|
|
|
# cannot cache undefined values |
|
922
|
|
|
|
|
|
|
next; |
|
923
|
|
|
|
|
|
|
} |
|
924
|
|
|
|
|
|
|
my $cache = $self->get_tuple_idx($element, $uset); |
|
925
|
|
|
|
|
|
|
my $valstr = join("\t", map {$insert_h->{$_}} sort @$uset); |
|
926
|
|
|
|
|
|
|
$cache->{$valstr} = $insert_h; |
|
927
|
|
|
|
|
|
|
} |
|
928
|
|
|
|
|
|
|
return 1; |
|
929
|
|
|
|
|
|
|
} |
|
930
|
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
sub update_cache { |
|
932
|
|
|
|
|
|
|
my $self = shift; |
|
933
|
|
|
|
|
|
|
my $element = shift; |
|
934
|
|
|
|
|
|
|
my $store_hash = shift; |
|
935
|
|
|
|
|
|
|
my $unique_constr = shift; |
|
936
|
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
my $tuple = $self->query_cache($element, |
|
938
|
|
|
|
|
|
|
$unique_constr, |
|
939
|
|
|
|
|
|
|
$store_hash); |
|
940
|
|
|
|
|
|
|
return; |
|
941
|
|
|
|
|
|
|
} |
|
942
|
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
sub get_tuple_idx { |
|
944
|
|
|
|
|
|
|
my $self = shift; |
|
945
|
|
|
|
|
|
|
my $element = shift; |
|
946
|
|
|
|
|
|
|
my $ukey = shift; |
|
947
|
|
|
|
|
|
|
my @keycols = @$ukey; |
|
948
|
|
|
|
|
|
|
@keycols = sort @keycols; |
|
949
|
|
|
|
|
|
|
@keycols || die; |
|
950
|
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
my $cache = $self->cache; |
|
952
|
|
|
|
|
|
|
if (!$cache->{$element}) { |
|
953
|
|
|
|
|
|
|
$cache->{$element} = {}; |
|
954
|
|
|
|
|
|
|
} |
|
955
|
|
|
|
|
|
|
my $eltcache = $cache->{$element}; |
|
956
|
|
|
|
|
|
|
# we just use a flat perl hash - flatten the list of unique cols |
|
957
|
|
|
|
|
|
|
# to a string with spaces between |
|
958
|
|
|
|
|
|
|
my $k = "@keycols"; |
|
959
|
|
|
|
|
|
|
if (!$eltcache->{$k}) { |
|
960
|
|
|
|
|
|
|
$eltcache->{$k} = {}; |
|
961
|
|
|
|
|
|
|
} |
|
962
|
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
return $eltcache->{$k}; |
|
964
|
|
|
|
|
|
|
} |
|
965
|
|
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
sub cache_summary { |
|
967
|
|
|
|
|
|
|
my $self = shift; |
|
968
|
|
|
|
|
|
|
my $s = Data::Stag->new(cache_summary=>[]); |
|
969
|
|
|
|
|
|
|
my $cache = $self->cache || {}; |
|
970
|
|
|
|
|
|
|
my @elts = keys %$cache; |
|
971
|
|
|
|
|
|
|
foreach my $elt (@elts) { |
|
972
|
|
|
|
|
|
|
my $cnode = $cache->{$elt} || {}; |
|
973
|
|
|
|
|
|
|
my @keys = keys %$cnode; |
|
974
|
|
|
|
|
|
|
$s->add($elt=>[map {[$_=>scalar(keys %{$cnode->{$_}})]} @keys]); |
|
975
|
|
|
|
|
|
|
} |
|
976
|
|
|
|
|
|
|
return $s; |
|
977
|
|
|
|
|
|
|
} |
|
978
|
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
sub cache { |
|
980
|
|
|
|
|
|
|
my $self = shift; |
|
981
|
|
|
|
|
|
|
$self->{_cache} = shift if @_; |
|
982
|
|
|
|
|
|
|
$self->{_cache} = {} unless $self->{_cache}; |
|
983
|
|
|
|
|
|
|
return $self->{_cache}; |
|
984
|
|
|
|
|
|
|
} |
|
985
|
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
sub clear_cache { |
|
987
|
|
|
|
|
|
|
my $self = shift; |
|
988
|
|
|
|
|
|
|
$self->cache({}); |
|
989
|
|
|
|
|
|
|
} |
|
990
|
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
# ---- END OF CACHE METHODS ---- |
|
992
|
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
# set this if we are loading a fresh/blank slate DB |
|
994
|
|
|
|
|
|
|
# (will assume database is empty and not check for |
|
995
|
|
|
|
|
|
|
# existing tuples) |
|
996
|
|
|
|
|
|
|
sub policy_freshbulkload { |
|
997
|
|
|
|
|
|
|
my $self = shift; |
|
998
|
|
|
|
|
|
|
$self->{_policy_freshbulkload} = shift if @_; |
|
999
|
|
|
|
|
|
|
return $self->{_policy_freshbulkload}; |
|
1000
|
|
|
|
|
|
|
} |
|
1001
|
|
|
|
|
|
|
sub noupdate_h { |
|
1002
|
|
|
|
|
|
|
my $self = shift; |
|
1003
|
|
|
|
|
|
|
$self->{_noupdate_h} = shift if @_; |
|
1004
|
|
|
|
|
|
|
return $self->{_noupdate_h} || {}; |
|
1005
|
|
|
|
|
|
|
} |
|
1006
|
|
|
|
|
|
|
sub tracenode { |
|
1007
|
|
|
|
|
|
|
my $self = shift; |
|
1008
|
|
|
|
|
|
|
$self->{_tracenode} = shift if @_; |
|
1009
|
|
|
|
|
|
|
return $self->{_tracenode}; |
|
1010
|
|
|
|
|
|
|
} |
|
1011
|
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
sub mapgroups { |
|
1013
|
|
|
|
|
|
|
my $self = shift; |
|
1014
|
|
|
|
|
|
|
if (@_) { |
|
1015
|
|
|
|
|
|
|
$self->{_mapgroups} = [@_]; |
|
1016
|
|
|
|
|
|
|
$self->{_colvalmap} = {} |
|
1017
|
|
|
|
|
|
|
unless $self->{_colvalmap}; |
|
1018
|
|
|
|
|
|
|
foreach my $cols (@_) { |
|
1019
|
|
|
|
|
|
|
my $h = {}; |
|
1020
|
|
|
|
|
|
|
foreach (@$cols) { |
|
1021
|
|
|
|
|
|
|
$self->{_colvalmap}->{$_} = $h; |
|
1022
|
|
|
|
|
|
|
} |
|
1023
|
|
|
|
|
|
|
} |
|
1024
|
|
|
|
|
|
|
} |
|
1025
|
|
|
|
|
|
|
return @{$self->{_mapgroups} || []}; |
|
1026
|
|
|
|
|
|
|
} |
|
1027
|
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
# DEPRECATED |
|
1029
|
|
|
|
|
|
|
sub get_mapping_for_col { |
|
1030
|
|
|
|
|
|
|
my $self = shift; |
|
1031
|
|
|
|
|
|
|
my $col = shift; |
|
1032
|
|
|
|
|
|
|
$self->{_colvalmap}->{$col} = {} |
|
1033
|
|
|
|
|
|
|
unless $self->{_colvalmap}->{$col}; |
|
1034
|
|
|
|
|
|
|
return $self->{_colvalmap}->{$col}; |
|
1035
|
|
|
|
|
|
|
} |
|
1036
|
|
|
|
|
|
|
|
|
1037
|
|
|
|
|
|
|
# mapping of Old ID => New ID |
|
1038
|
|
|
|
|
|
|
# IDs are assumed to be global across ALL tables |
|
1039
|
|
|
|
|
|
|
sub id_remap_idx { |
|
1040
|
|
|
|
|
|
|
my $self = shift; |
|
1041
|
|
|
|
|
|
|
if (@_) { |
|
1042
|
|
|
|
|
|
|
$self->{_id_remap_idx} = shift; |
|
1043
|
|
|
|
|
|
|
} |
|
1044
|
|
|
|
|
|
|
else { |
|
1045
|
|
|
|
|
|
|
$self->{_id_remap_idx} = {} |
|
1046
|
|
|
|
|
|
|
unless $self->{_id_remap_idx}; |
|
1047
|
|
|
|
|
|
|
} |
|
1048
|
|
|
|
|
|
|
return $self->{_id_remap_idx}; |
|
1049
|
|
|
|
|
|
|
} |
|
1050
|
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
# do the PK values in the XML represent the actual |
|
1052
|
|
|
|
|
|
|
# internal db ids, or are they local to the document? |
|
1053
|
|
|
|
|
|
|
# if the latter then we will create a id_remap_idx |
|
1054
|
|
|
|
|
|
|
sub trust_primary_key_values { |
|
1055
|
|
|
|
|
|
|
my $self = shift; |
|
1056
|
|
|
|
|
|
|
$self->{_trust_primary_key_values} = shift if @_; |
|
1057
|
|
|
|
|
|
|
return $self->{_trust_primary_key_values}; |
|
1058
|
|
|
|
|
|
|
} |
|
1059
|
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
sub make_stag_node_dbsafe { |
|
1062
|
|
|
|
|
|
|
my $self = shift; |
|
1063
|
|
|
|
|
|
|
my $node = shift; |
|
1064
|
|
|
|
|
|
|
my $parent = shift; |
|
1065
|
|
|
|
|
|
|
my $name = $node->name; |
|
1066
|
|
|
|
|
|
|
# CJM 2007-03-05 |
|
1067
|
|
|
|
|
|
|
#return if $name eq '@'; # leave attrs alone |
|
1068
|
|
|
|
|
|
|
if ($name eq '@') { |
|
1069
|
|
|
|
|
|
|
# descend into attrs |
|
1070
|
|
|
|
|
|
|
$parent->data([grep {$_->name ne '@'} @{$parent->data},@{$node->data}]); |
|
1071
|
|
|
|
|
|
|
return; |
|
1072
|
|
|
|
|
|
|
} |
|
1073
|
|
|
|
|
|
|
my $safename = $self->dbsafe($name,$parent); |
|
1074
|
|
|
|
|
|
|
if ($name ne $safename) { |
|
1075
|
|
|
|
|
|
|
$node->name($safename); |
|
1076
|
|
|
|
|
|
|
} |
|
1077
|
|
|
|
|
|
|
my @kids = $node->kids; |
|
1078
|
|
|
|
|
|
|
foreach (@kids) { |
|
1079
|
|
|
|
|
|
|
$self->make_stag_node_dbsafe($_,$node) if ref $_; |
|
1080
|
|
|
|
|
|
|
} |
|
1081
|
|
|
|
|
|
|
return; |
|
1082
|
|
|
|
|
|
|
} |
|
1083
|
|
|
|
|
|
|
sub dbsafe { |
|
1084
|
|
|
|
|
|
|
my $self = shift; |
|
1085
|
|
|
|
|
|
|
my $name = shift; |
|
1086
|
|
|
|
|
|
|
my $parent = shift; |
|
1087
|
|
|
|
|
|
|
$name = lc($name); |
|
1088
|
|
|
|
|
|
|
# dbstag is designed for stag-like xml; no mixed attributes |
|
1089
|
|
|
|
|
|
|
# however, we do have basic checks for mixed attributes |
|
1090
|
|
|
|
|
|
|
if ($name eq '.') { |
|
1091
|
|
|
|
|
|
|
$name = $parent->name.'_data'; # TODO - allow custom column |
|
1092
|
|
|
|
|
|
|
} |
|
1093
|
|
|
|
|
|
|
$name =~ tr/a-z0-9_//cd; |
|
1094
|
|
|
|
|
|
|
return $name; |
|
1095
|
|
|
|
|
|
|
} |
|
1096
|
|
|
|
|
|
|
|
|
1097
|
|
|
|
|
|
|
# cache the attribute nodes as they are parsed |
|
1098
|
|
|
|
|
|
|
#sub current_attribute_node { |
|
1099
|
|
|
|
|
|
|
# my $self = shift; |
|
1100
|
|
|
|
|
|
|
# $self->{_current_attribute_node} = shift if @_; |
|
1101
|
|
|
|
|
|
|
# return $self->{_current_attribute_node}; |
|
1102
|
|
|
|
|
|
|
#} |
|
1103
|
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
# lookup table; macro ID => internal database ID |
|
1105
|
|
|
|
|
|
|
sub macro_id_h { |
|
1106
|
|
|
|
|
|
|
my $self = shift; |
|
1107
|
|
|
|
|
|
|
$self->{_macro_id_h} = shift if @_; |
|
1108
|
|
|
|
|
|
|
$self->{_macro_id_h} = {} |
|
1109
|
|
|
|
|
|
|
unless $self->{_macro_id_h}; |
|
1110
|
|
|
|
|
|
|
return $self->{_macro_id_h}; |
|
1111
|
|
|
|
|
|
|
} |
|
1112
|
|
|
|
|
|
|
|
|
1113
|
|
|
|
|
|
|
# xort-style XML; set if an attribute is encountered |
|
1114
|
|
|
|
|
|
|
sub xort_mode { |
|
1115
|
|
|
|
|
|
|
my $self = shift; |
|
1116
|
|
|
|
|
|
|
$self->{_xort_mode} = shift if @_; |
|
1117
|
|
|
|
|
|
|
return $self->{_xort_mode}; |
|
1118
|
|
|
|
|
|
|
} |
|
1119
|
|
|
|
|
|
|
|
|
1120
|
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
#'(t1 |
|
1122
|
|
|
|
|
|
|
# (foo x) |
|
1123
|
|
|
|
|
|
|
# (t2 |
|
1124
|
|
|
|
|
|
|
# (bar y))) |
|
1125
|
|
|
|
|
|
|
# |
|
1126
|
|
|
|
|
|
|
# '(fk |
|
1127
|
|
|
|
|
|
|
# (table t2) |
|
1128
|
|
|
|
|
|
|
# (ftable t1)) |
|
1129
|
|
|
|
|
|
|
# |
|
1130
|
|
|
|
|
|
|
# alg: store t1, then t2 |
|
1131
|
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
# '(t1 |
|
1133
|
|
|
|
|
|
|
# (foo x) |
|
1134
|
|
|
|
|
|
|
# (t1_t2 |
|
1135
|
|
|
|
|
|
|
# (t2 |
|
1136
|
|
|
|
|
|
|
# (bar y)))) |
|
1137
|
|
|
|
|
|
|
# |
|
1138
|
|
|
|
|
|
|
# '(fk |
|
1139
|
|
|
|
|
|
|
# (table t1_t2) |
|
1140
|
|
|
|
|
|
|
# (ftable t1)) |
|
1141
|
|
|
|
|
|
|
# '(fk |
|
1142
|
|
|
|
|
|
|
# (table t1_t2) |
|
1143
|
|
|
|
|
|
|
# (ftable t2)) |
|
1144
|
|
|
|
|
|
|
# |
|
1145
|
|
|
|
|
|
|
# |
|
1146
|
|
|
|
|
|
|
# alg: store t1, hold on t1_t2, store t2 |
|
1147
|
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
# '(t1 |
|
1149
|
|
|
|
|
|
|
# (foo x) |
|
1150
|
|
|
|
|
|
|
# (blah |
|
1151
|
|
|
|
|
|
|
# (t2 |
|
1152
|
|
|
|
|
|
|
# (bar y)))) |
|
1153
|
|
|
|
|
|
|
# |
|
1154
|
|
|
|
|
|
|
# '(fk |
|
1155
|
|
|
|
|
|
|
# (table t1) |
|
1156
|
|
|
|
|
|
|
# (fktable t2) |
|
1157
|
|
|
|
|
|
|
# (fktable_alias "blah") |
|
1158
|
|
|
|
|
|
|
# (fk "blah_id")) |
|
1159
|
|
|
|
|
|
|
|
|
1160
|
|
|
|
|
|
|
# alg: store t2, store t1 |
|
1161
|
|
|
|
|
|
|
|
|
1162
|
|
|
|
|
|
|
# if set, will ensure that tbl/col names are transformed to be safe |
|
1163
|
|
|
|
|
|
|
sub force_safe_node_names { |
|
1164
|
|
|
|
|
|
|
my $self = shift; |
|
1165
|
|
|
|
|
|
|
$self->{_force_safe_node_names} = shift if @_; |
|
1166
|
|
|
|
|
|
|
return $self->{_force_safe_node_names}; |
|
1167
|
|
|
|
|
|
|
} |
|
1168
|
|
|
|
|
|
|
|
|
1169
|
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
# recursively stores a Data::Stag tree node in the database |
|
1171
|
|
|
|
|
|
|
sub storenode { |
|
1172
|
|
|
|
|
|
|
my $self = shift; |
|
1173
|
|
|
|
|
|
|
my $node = shift; |
|
1174
|
|
|
|
|
|
|
my @args = @_; |
|
1175
|
|
|
|
|
|
|
my $dupnode = $node->duplicate; |
|
1176
|
|
|
|
|
|
|
|
|
1177
|
|
|
|
|
|
|
$self->make_stag_node_dbsafe($dupnode,'') |
|
1178
|
|
|
|
|
|
|
if $self->force_safe_node_names; |
|
1179
|
|
|
|
|
|
|
$self->add_linking_tables($dupnode); |
|
1180
|
|
|
|
|
|
|
$self->_storenode($dupnode,@args); |
|
1181
|
|
|
|
|
|
|
} |
|
1182
|
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
sub _storenode { |
|
1184
|
|
|
|
|
|
|
my $self = shift; |
|
1185
|
|
|
|
|
|
|
my $node = shift; |
|
1186
|
|
|
|
|
|
|
my $opt = shift; |
|
1187
|
|
|
|
|
|
|
if (!$node) { |
|
1188
|
|
|
|
|
|
|
confess("you need to pass in a node!"); |
|
1189
|
|
|
|
|
|
|
} |
|
1190
|
|
|
|
|
|
|
my $element = $node->element; |
|
1191
|
|
|
|
|
|
|
return unless $node->kids; |
|
1192
|
|
|
|
|
|
|
if ($element eq 'dbstag_metadata') { |
|
1193
|
|
|
|
|
|
|
my @maps = $node->get_map; |
|
1194
|
|
|
|
|
|
|
$self->mapping(\@maps); |
|
1195
|
|
|
|
|
|
|
my @links = $node->get_link; |
|
1196
|
|
|
|
|
|
|
if (@links) { |
|
1197
|
|
|
|
|
|
|
my %h = |
|
1198
|
|
|
|
|
|
|
map { |
|
1199
|
|
|
|
|
|
|
($_->sget_table => [$_->sget_from, $_->sget_to]) |
|
1200
|
|
|
|
|
|
|
} @links; |
|
1201
|
|
|
|
|
|
|
$self->linking_tables(%h); |
|
1202
|
|
|
|
|
|
|
} |
|
1203
|
|
|
|
|
|
|
return; |
|
1204
|
|
|
|
|
|
|
} |
|
1205
|
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
# sql can be embedded as <_sql> tags |
|
1207
|
|
|
|
|
|
|
if ($element eq '_sql') { |
|
1208
|
|
|
|
|
|
|
$self->_execute_sqlnode($node); |
|
1209
|
|
|
|
|
|
|
return; |
|
1210
|
|
|
|
|
|
|
} |
|
1211
|
|
|
|
|
|
|
|
|
1212
|
|
|
|
|
|
|
# check for XORT-style attributes |
|
1213
|
|
|
|
|
|
|
# if ($element eq '@') { |
|
1214
|
|
|
|
|
|
|
# # is this check required??? |
|
1215
|
|
|
|
|
|
|
# $self->current_attribute_node($node); |
|
1216
|
|
|
|
|
|
|
# $self->xort_mode(1); |
|
1217
|
|
|
|
|
|
|
# return; |
|
1218
|
|
|
|
|
|
|
# } |
|
1219
|
|
|
|
|
|
|
my $current_attribute_node; |
|
1220
|
|
|
|
|
|
|
unless ($node->isterminal) { |
|
1221
|
|
|
|
|
|
|
my @kids = $node->kids; |
|
1222
|
|
|
|
|
|
|
my $changed = 0; |
|
1223
|
|
|
|
|
|
|
@kids = |
|
1224
|
|
|
|
|
|
|
map { |
|
1225
|
|
|
|
|
|
|
if ($_->element eq '@') { |
|
1226
|
|
|
|
|
|
|
$self->xort_mode(1); |
|
1227
|
|
|
|
|
|
|
$current_attribute_node = $_; |
|
1228
|
|
|
|
|
|
|
$changed = 1; |
|
1229
|
|
|
|
|
|
|
trace(0, "GOT ATTR NODE"); |
|
1230
|
|
|
|
|
|
|
(); # omit |
|
1231
|
|
|
|
|
|
|
} |
|
1232
|
|
|
|
|
|
|
else { |
|
1233
|
|
|
|
|
|
|
$_; # unchanged |
|
1234
|
|
|
|
|
|
|
} |
|
1235
|
|
|
|
|
|
|
} @kids; |
|
1236
|
|
|
|
|
|
|
$node->kids(@kids) if $changed; |
|
1237
|
|
|
|
|
|
|
} |
|
1238
|
|
|
|
|
|
|
|
|
1239
|
|
|
|
|
|
|
my $operation; # directive: force/update/lookup |
|
1240
|
|
|
|
|
|
|
if ($current_attribute_node){ |
|
1241
|
|
|
|
|
|
|
$operation = |
|
1242
|
|
|
|
|
|
|
$current_attribute_node->sget_op; |
|
1243
|
|
|
|
|
|
|
} |
|
1244
|
|
|
|
|
|
|
|
|
1245
|
|
|
|
|
|
|
trace(0, "STORING $element\n", $node->xml) if $TRACE; |
|
1246
|
|
|
|
|
|
|
my $tracenode = $self->tracenode || ''; |
|
1247
|
|
|
|
|
|
|
my $tracekeyval; |
|
1248
|
|
|
|
|
|
|
if ($tracenode && $tracenode =~ /^(\w+)\/(.*)/) { |
|
1249
|
|
|
|
|
|
|
my $nn = $1; |
|
1250
|
|
|
|
|
|
|
my $tag = $2; |
|
1251
|
|
|
|
|
|
|
if ($nn eq $element) { |
|
1252
|
|
|
|
|
|
|
$tracekeyval = $node->get($tag); |
|
1253
|
|
|
|
|
|
|
} |
|
1254
|
|
|
|
|
|
|
} |
|
1255
|
|
|
|
|
|
|
|
|
1256
|
|
|
|
|
|
|
my $dbh = $self->dbh; |
|
1257
|
|
|
|
|
|
|
my $dbschema = $self->dbschema; |
|
1258
|
|
|
|
|
|
|
|
|
1259
|
|
|
|
|
|
|
my $is_caching_on = $self->is_caching_on($element) || 0; |
|
1260
|
|
|
|
|
|
|
|
|
1261
|
|
|
|
|
|
|
my $mapping = $self->mapping || []; |
|
1262
|
|
|
|
|
|
|
|
|
1263
|
|
|
|
|
|
|
# each relation has zero or one primary keys; |
|
1264
|
|
|
|
|
|
|
# primary keys are assumed to be single-column |
|
1265
|
|
|
|
|
|
|
my $pkcol = $self->get_pk_col($element); |
|
1266
|
|
|
|
|
|
|
trace(0, "PKCOL: $pkcol") if $TRACE; |
|
1267
|
|
|
|
|
|
|
|
|
1268
|
|
|
|
|
|
|
# DBIx::DBSchema metadata |
|
1269
|
|
|
|
|
|
|
my $tableobj = $dbschema->table($element); |
|
1270
|
|
|
|
|
|
|
|
|
1271
|
|
|
|
|
|
|
# -- PRE-STORE CHILD NON-TERMINALS -- |
|
1272
|
|
|
|
|
|
|
# before storing this node, we need to |
|
1273
|
|
|
|
|
|
|
# see if we first need to store any child |
|
1274
|
|
|
|
|
|
|
# non-terminal nodes (in order to get their |
|
1275
|
|
|
|
|
|
|
# primary keys, to use as foreign keys in |
|
1276
|
|
|
|
|
|
|
# the current relation) |
|
1277
|
|
|
|
|
|
|
|
|
1278
|
|
|
|
|
|
|
# store non-terminal subnodes first |
|
1279
|
|
|
|
|
|
|
my @ntnodes = $node->ntnodes; |
|
1280
|
|
|
|
|
|
|
|
|
1281
|
|
|
|
|
|
|
# keep track of nodes that have been assigned xort-style |
|
1282
|
|
|
|
|
|
|
my %assigned_node_h; |
|
1283
|
|
|
|
|
|
|
|
|
1284
|
|
|
|
|
|
|
# GET INFORMATION FROM SUPER-NODE |
|
1285
|
|
|
|
|
|
|
# some nodes may have been assigned by the calling process |
|
1286
|
|
|
|
|
|
|
# (eg if the supernode is refered to by a fk from the current table) |
|
1287
|
|
|
|
|
|
|
# this hash maps element names to a boolean; |
|
1288
|
|
|
|
|
|
|
# this is ONLY used in conjunction with xort-style xml |
|
1289
|
|
|
|
|
|
|
# we set this when we want to make sure that an element value is |
|
1290
|
|
|
|
|
|
|
# NOT macro-expanded by the expansion code |
|
1291
|
|
|
|
|
|
|
%assigned_node_h = %{$opt->{assigned_node_h} || {}}; |
|
1292
|
|
|
|
|
|
|
|
|
1293
|
|
|
|
|
|
|
# the primary key value of the supernode |
|
1294
|
|
|
|
|
|
|
my $parent_pk_id = $opt->{parent_pk_id}; |
|
1295
|
|
|
|
|
|
|
# the element type of the supernode |
|
1296
|
|
|
|
|
|
|
my $parent_element = $opt->{parent_element}; |
|
1297
|
|
|
|
|
|
|
# -- end of info from super-node |
|
1298
|
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
# PRE-STORE |
|
1300
|
|
|
|
|
|
|
# look through the current node's children; |
|
1301
|
|
|
|
|
|
|
# + some of these will be nodes that must be pre-stored BEFORE |
|
1302
|
|
|
|
|
|
|
# the current node (because the current node has a fk to them) |
|
1303
|
|
|
|
|
|
|
# + some of these will be nodes that must be post-stored AFTER |
|
1304
|
|
|
|
|
|
|
# the current node (because they have an fk to the current node) |
|
1305
|
|
|
|
|
|
|
# |
|
1306
|
|
|
|
|
|
|
# one or other of these situations must be true - otherwise |
|
1307
|
|
|
|
|
|
|
# nodes should not be nested! |
|
1308
|
|
|
|
|
|
|
my @delayed_store = (); # keep track of non-pre-stored nodes |
|
1309
|
|
|
|
|
|
|
foreach my $nt (@ntnodes) { |
|
1310
|
|
|
|
|
|
|
# First check for XORT-STYLE |
|
1311
|
|
|
|
|
|
|
# xort-style XML; nodes can be nested inside a non-terminal |
|
1312
|
|
|
|
|
|
|
# node corresponding to a FK column |
|
1313
|
|
|
|
|
|
|
# eg |
|
1314
|
|
|
|
|
|
|
# |
|
1315
|
|
|
|
|
|
|
# foo |
|
1316
|
|
|
|
|
|
|
# |
|
1317
|
|
|
|
|
|
|
# |
|
1318
|
|
|
|
|
|
|
# here, what looks like a non-terminal node should actually |
|
1319
|
|
|
|
|
|
|
|
|
1320
|
|
|
|
|
|
|
# check all sub-nodes; if any of them are nonterminal and correspond |
|
1321
|
|
|
|
|
|
|
# to a column (not a table) then add the sub-node and use the pk id |
|
1322
|
|
|
|
|
|
|
# as the returned value |
|
1323
|
|
|
|
|
|
|
# note: we have to explicitly check the col is not also a table |
|
1324
|
|
|
|
|
|
|
# since some dbs (eg go db) have col names the same as tbl names |
|
1325
|
|
|
|
|
|
|
if ($self->is_col($nt->name) && |
|
1326
|
|
|
|
|
|
|
!$nt->isterminal && |
|
1327
|
|
|
|
|
|
|
!$self->is_table($nt->name)) { |
|
1328
|
|
|
|
|
|
|
my @kids = $nt->kids; |
|
1329
|
|
|
|
|
|
|
if (@kids != 1) { |
|
1330
|
|
|
|
|
|
|
$self->throw("non-terminal pk node should have one subnode only; ". |
|
1331
|
|
|
|
|
|
|
$nt->name." has ".scalar(@kids)); |
|
1332
|
|
|
|
|
|
|
} |
|
1333
|
|
|
|
|
|
|
my $sn_val = $self->_storenode(shift @kids); |
|
1334
|
|
|
|
|
|
|
if (!defined($sn_val)) { |
|
1335
|
|
|
|
|
|
|
$self->throw("no returned value for ".$nt->name); |
|
1336
|
|
|
|
|
|
|
} |
|
1337
|
|
|
|
|
|
|
# TRANSFORM NODE: non-terminal to terminal |
|
1338
|
|
|
|
|
|
|
# replace node with return pk ID value |
|
1339
|
|
|
|
|
|
|
$nt->data($sn_val); |
|
1340
|
|
|
|
|
|
|
|
|
1341
|
|
|
|
|
|
|
# do NOT try and expand the value assigned to this |
|
1342
|
|
|
|
|
|
|
# node with a xort-macro expansion later on |
|
1343
|
|
|
|
|
|
|
$assigned_node_h{$nt->name} = 1; |
|
1344
|
|
|
|
|
|
|
trace(0, "ASSIGNED NON-MACRO ID for ".$nt->name." TO $sn_val") if $TRACE; |
|
1345
|
|
|
|
|
|
|
|
|
1346
|
|
|
|
|
|
|
# skip this ntnode - it is now a tnode |
|
1347
|
|
|
|
|
|
|
next; |
|
1348
|
|
|
|
|
|
|
} |
|
1349
|
|
|
|
|
|
|
# -- END OF xort-style check |
|
1350
|
|
|
|
|
|
|
|
|
1351
|
|
|
|
|
|
|
# we want to PRE-STORE any ntnodes that |
|
1352
|
|
|
|
|
|
|
# are required for foreign key relationships |
|
1353
|
|
|
|
|
|
|
# within this node; |
|
1354
|
|
|
|
|
|
|
# ie this node N1 has a foreign key "fk_id" that |
|
1355
|
|
|
|
|
|
|
# points to ntnode N2. |
|
1356
|
|
|
|
|
|
|
# if there is an intermediate alias element in |
|
1357
|
|
|
|
|
|
|
# between then we need to store the ntnode too |
|
1358
|
|
|
|
|
|
|
# |
|
1359
|
|
|
|
|
|
|
# check for either of these conditions |
|
1360
|
|
|
|
|
|
|
my ($map) = |
|
1361
|
|
|
|
|
|
|
grep { |
|
1362
|
|
|
|
|
|
|
$_->get_table && |
|
1363
|
|
|
|
|
|
|
$_->get_table eq $element && |
|
1364
|
|
|
|
|
|
|
($_->get_fktable_alias && |
|
1365
|
|
|
|
|
|
|
$_->get_fktable_alias eq $nt->element || |
|
1366
|
|
|
|
|
|
|
($_->get_fktable && |
|
1367
|
|
|
|
|
|
|
$_->get_fktable eq $nt->element && !$_->get_fktable_alias)) |
|
1368
|
|
|
|
|
|
|
} @$mapping; |
|
1369
|
|
|
|
|
|
|
# check to see if sub-element has FK to this element |
|
1370
|
|
|
|
|
|
|
if (!$map) { |
|
1371
|
|
|
|
|
|
|
# my $subtable = $dbschema->table($nt->element); |
|
1372
|
|
|
|
|
|
|
my $table = $dbschema->table($element); |
|
1373
|
|
|
|
|
|
|
my $ntelement = $nt->element; |
|
1374
|
|
|
|
|
|
|
my $subpkcol = $self->get_pk_col($ntelement); |
|
1375
|
|
|
|
|
|
|
|
|
1376
|
|
|
|
|
|
|
my @cns = $table->columns; |
|
1377
|
|
|
|
|
|
|
|
|
1378
|
|
|
|
|
|
|
my $cn; # col name (FK in current element) |
|
1379
|
|
|
|
|
|
|
my $fcn; # foreign col name (PK in sub element) |
|
1380
|
|
|
|
|
|
|
|
|
1381
|
|
|
|
|
|
|
# HACK - ASSUME NATURAL JOIN |
|
1382
|
|
|
|
|
|
|
# for example, a FK: person.dept_id => dept.dept_id |
|
1383
|
|
|
|
|
|
|
if ($subpkcol ne 'id') { |
|
1384
|
|
|
|
|
|
|
foreach (@cns) { |
|
1385
|
|
|
|
|
|
|
if ($_ eq $subpkcol) { |
|
1386
|
|
|
|
|
|
|
$cn = $_; |
|
1387
|
|
|
|
|
|
|
$fcn = $_; |
|
1388
|
|
|
|
|
|
|
} |
|
1389
|
|
|
|
|
|
|
} |
|
1390
|
|
|
|
|
|
|
} |
|
1391
|
|
|
|
|
|
|
|
|
1392
|
|
|
|
|
|
|
# second chance; allow base "id" style |
|
1393
|
|
|
|
|
|
|
# for example, a FK: person.dept_id => dept.id |
|
1394
|
|
|
|
|
|
|
# via ... |
|
1395
|
|
|
|
|
|
|
if (!$cn) { |
|
1396
|
|
|
|
|
|
|
if ($subpkcol eq 'id') { |
|
1397
|
|
|
|
|
|
|
foreach (@cns) { |
|
1398
|
|
|
|
|
|
|
if ($_ eq $ntelement."_id") { |
|
1399
|
|
|
|
|
|
|
$cn = $_; |
|
1400
|
|
|
|
|
|
|
$fcn = 'id'; |
|
1401
|
|
|
|
|
|
|
} |
|
1402
|
|
|
|
|
|
|
} |
|
1403
|
|
|
|
|
|
|
} |
|
1404
|
|
|
|
|
|
|
} |
|
1405
|
|
|
|
|
|
|
if ($cn) { |
|
1406
|
|
|
|
|
|
|
$map = |
|
1407
|
|
|
|
|
|
|
Data::Stag->new(map=>[ |
|
1408
|
|
|
|
|
|
|
[table=>$element], |
|
1409
|
|
|
|
|
|
|
[col=>$cn], |
|
1410
|
|
|
|
|
|
|
[fktable=>$nt->element], |
|
1411
|
|
|
|
|
|
|
[fkcol=>$fcn] |
|
1412
|
|
|
|
|
|
|
]); |
|
1413
|
|
|
|
|
|
|
} |
|
1414
|
|
|
|
|
|
|
} |
|
1415
|
|
|
|
|
|
|
|
|
1416
|
|
|
|
|
|
|
# if $map is set, then we have to pre-store this subnode |
|
1417
|
|
|
|
|
|
|
if ($map) { |
|
1418
|
|
|
|
|
|
|
# 1:many between this and child |
|
1419
|
|
|
|
|
|
|
# (eg this has fk to child) |
|
1420
|
|
|
|
|
|
|
# store child before this; |
|
1421
|
|
|
|
|
|
|
# use fk in this |
|
1422
|
|
|
|
|
|
|
my $fktable = $map->get_fktable; |
|
1423
|
|
|
|
|
|
|
|
|
1424
|
|
|
|
|
|
|
my $col = $map->get_col || $self->get_pk_col($fktable); |
|
1425
|
|
|
|
|
|
|
|
|
1426
|
|
|
|
|
|
|
# aliases map an extra table |
|
1427
|
|
|
|
|
|
|
# eg table X col X.A => Y.B |
|
1428
|
|
|
|
|
|
|
# fktable_alias = A |
|
1429
|
|
|
|
|
|
|
my $fktable_alias = $map->get_fktable_alias; |
|
1430
|
|
|
|
|
|
|
my $orig_nt = $nt; |
|
1431
|
|
|
|
|
|
|
|
|
1432
|
|
|
|
|
|
|
# if we have an alias, it means the actual node |
|
1433
|
|
|
|
|
|
|
# we want to store is one beneath the alias; |
|
1434
|
|
|
|
|
|
|
# eg .. |
|
1435
|
|
|
|
|
|
|
# we want to actually store the node foo2 |
|
1436
|
|
|
|
|
|
|
if ($fktable_alias) { |
|
1437
|
|
|
|
|
|
|
my @nts = $nt->sgetnode($map->sget_fktable); |
|
1438
|
|
|
|
|
|
|
if (!@nts) { |
|
1439
|
|
|
|
|
|
|
print STDERR $nt->sxpr; |
|
1440
|
|
|
|
|
|
|
confess("could not get node for: ".$map->sget_fktable); |
|
1441
|
|
|
|
|
|
|
} |
|
1442
|
|
|
|
|
|
|
if (@nts > 1) { |
|
1443
|
|
|
|
|
|
|
print STDERR $nt->sxpr; |
|
1444
|
|
|
|
|
|
|
confess("multiple nodes for: ".$map->sget_fktable); |
|
1445
|
|
|
|
|
|
|
} |
|
1446
|
|
|
|
|
|
|
$nt = shift @nts; |
|
1447
|
|
|
|
|
|
|
if (!$nt) { |
|
1448
|
|
|
|
|
|
|
print STDERR $map->sxpr; |
|
1449
|
|
|
|
|
|
|
print STDERR $orig_nt->sxpr; |
|
1450
|
|
|
|
|
|
|
confess("bad nodes for: ".$map->sget_fktable); |
|
1451
|
|
|
|
|
|
|
} |
|
1452
|
|
|
|
|
|
|
} |
|
1453
|
|
|
|
|
|
|
my $fk_id = $self->_storenode($nt); |
|
1454
|
|
|
|
|
|
|
if (!defined($fk_id)) { |
|
1455
|
|
|
|
|
|
|
confess("ASSERTION ERROR: could not get foreign key val\n". |
|
1456
|
|
|
|
|
|
|
"trying to store: $element\n". |
|
1457
|
|
|
|
|
|
|
"no fk returned when storing: $fktable"); |
|
1458
|
|
|
|
|
|
|
} |
|
1459
|
|
|
|
|
|
|
trace(0, "SETTING $element.$col=$fk_id [via ".$orig_nt->element."]") if $TRACE; |
|
1460
|
|
|
|
|
|
|
$node->set($col, $fk_id); |
|
1461
|
|
|
|
|
|
|
$node->unset($orig_nt->element); |
|
1462
|
|
|
|
|
|
|
|
|
1463
|
|
|
|
|
|
|
# do NOT try and expand the value assigned to this |
|
1464
|
|
|
|
|
|
|
# node with a xort-macro expansion later on |
|
1465
|
|
|
|
|
|
|
$assigned_node_h{$col} = 1; |
|
1466
|
|
|
|
|
|
|
trace(0, "ASSIGNED NON-MACRO ID for ".$col) if $TRACE; |
|
1467
|
|
|
|
|
|
|
} |
|
1468
|
|
|
|
|
|
|
else { |
|
1469
|
|
|
|
|
|
|
# 1:many between child and this |
|
1470
|
|
|
|
|
|
|
# (eg child has fk to this) |
|
1471
|
|
|
|
|
|
|
# store child after |
|
1472
|
|
|
|
|
|
|
trace(0, "WILL STORE LATER:\n", $nt->xml) if $TRACE; |
|
1473
|
|
|
|
|
|
|
$node->unset($nt->element); |
|
1474
|
|
|
|
|
|
|
push(@delayed_store, $nt); |
|
1475
|
|
|
|
|
|
|
} |
|
1476
|
|
|
|
|
|
|
# $node->unset($nt->element); # clear it |
|
1477
|
|
|
|
|
|
|
} |
|
1478
|
|
|
|
|
|
|
# --- done storing kids |
|
1479
|
|
|
|
|
|
|
|
|
1480
|
|
|
|
|
|
|
# --- replace *IDs --- |
|
1481
|
|
|
|
|
|
|
# dbstag XML allows placeholder values in primary key cols |
|
1482
|
|
|
|
|
|
|
# (for now, PKs are always assumed to be autoincrement/serial ints) |
|
1483
|
|
|
|
|
|
|
# placeholder PKs get remapped to a new autogenerated ID |
|
1484
|
|
|
|
|
|
|
# all FKs referring to this get remapped too |
|
1485
|
|
|
|
|
|
|
my @tnodes = $node->tnodes; # terminal nodes mapped to columns in db |
|
1486
|
|
|
|
|
|
|
my %remap = (); # indexed by column name; new PK value |
|
1487
|
|
|
|
|
|
|
if (!$self->trust_primary_key_values) { |
|
1488
|
|
|
|
|
|
|
foreach my $tnode (@tnodes) { |
|
1489
|
|
|
|
|
|
|
# foreign keys in XORT mode - replace macro ID with |
|
1490
|
|
|
|
|
|
|
# actual database foreign key value |
|
1491
|
|
|
|
|
|
|
if ($self->is_fk_col($tnode->name) && $self->xort_mode) { |
|
1492
|
|
|
|
|
|
|
my $v = $tnode->data; |
|
1493
|
|
|
|
|
|
|
|
|
1494
|
|
|
|
|
|
|
# -- CHECK FOR MACRO EXPANSION (XORT-STYLE) -- |
|
1495
|
|
|
|
|
|
|
# IF this tnode was originally an ntnode that |
|
1496
|
|
|
|
|
|
|
# was collapsed to a pk val, xort style, do not |
|
1497
|
|
|
|
|
|
|
# try and map it to a previously assigned macro |
|
1498
|
|
|
|
|
|
|
# EXAMPLE: |
|
1499
|
|
|
|
|
|
|
# we start with A>>> |
|
1500
|
|
|
|
|
|
|
# we collapse too $v> |
|
1501
|
|
|
|
|
|
|
if ($assigned_node_h{$tnode->name}) { |
|
1502
|
|
|
|
|
|
|
trace(0, "ALREADY CALCULATED; not a Macro ID:$v;; in $element/".$tnode->name) if $TRACE; |
|
1503
|
|
|
|
|
|
|
# DO NOTHING |
|
1504
|
|
|
|
|
|
|
} |
|
1505
|
|
|
|
|
|
|
else { # NOT ASSIGNED |
|
1506
|
|
|
|
|
|
|
my $actual_id = |
|
1507
|
|
|
|
|
|
|
$self->macro_id_h->{$v}; |
|
1508
|
|
|
|
|
|
|
if (!defined($actual_id)) { |
|
1509
|
|
|
|
|
|
|
$self->throw("XORT-style Macro ID:$v is undefined;; in $element/".$tnode->name); |
|
1510
|
|
|
|
|
|
|
} |
|
1511
|
|
|
|
|
|
|
$tnode->data($actual_id); |
|
1512
|
|
|
|
|
|
|
} |
|
1513
|
|
|
|
|
|
|
# -- END OF MACRO EXPANSION -- |
|
1514
|
|
|
|
|
|
|
} |
|
1515
|
|
|
|
|
|
|
elsif ($tnode->name eq $pkcol) { |
|
1516
|
|
|
|
|
|
|
my $v = $tnode->data; |
|
1517
|
|
|
|
|
|
|
trace(0, "REMAP $pkcol: $v => ? [do not know new value yet]") if $TRACE; |
|
1518
|
|
|
|
|
|
|
$remap{$tnode->name} = $v; # map after insert/update |
|
1519
|
|
|
|
|
|
|
$node->unset($tnode->name); # discard placeholder |
|
1520
|
|
|
|
|
|
|
} else { |
|
1521
|
|
|
|
|
|
|
if ($self->is_fk_col($tnode->name)) { |
|
1522
|
|
|
|
|
|
|
# hack!! need proper FK refs...; DBSchema wont do this |
|
1523
|
|
|
|
|
|
|
my $colvalmap = $self->id_remap_idx; |
|
1524
|
|
|
|
|
|
|
#my $colvalmap = $self->get_mapping_for_col($nt->elememt); |
|
1525
|
|
|
|
|
|
|
if ($colvalmap) { |
|
1526
|
|
|
|
|
|
|
my $v = $tnode->data; |
|
1527
|
|
|
|
|
|
|
my $nv = $colvalmap->{$v}; |
|
1528
|
|
|
|
|
|
|
if ($nv) { |
|
1529
|
|
|
|
|
|
|
trace(0, "remapping $v => $nv") if $TRACE; |
|
1530
|
|
|
|
|
|
|
$tnode->data($nv); |
|
1531
|
|
|
|
|
|
|
} |
|
1532
|
|
|
|
|
|
|
} |
|
1533
|
|
|
|
|
|
|
} |
|
1534
|
|
|
|
|
|
|
} |
|
1535
|
|
|
|
|
|
|
} |
|
1536
|
|
|
|
|
|
|
} # -- end of ID remapping |
|
1537
|
|
|
|
|
|
|
|
|
1538
|
|
|
|
|
|
|
# --- Get columns that need updating/inserting --- |
|
1539
|
|
|
|
|
|
|
# turn all remaining tag-val pairs into a hash |
|
1540
|
|
|
|
|
|
|
my %store_hash = $node->pairs; |
|
1541
|
|
|
|
|
|
|
|
|
1542
|
|
|
|
|
|
|
# All columns to be stored should be terminal nodes |
|
1543
|
|
|
|
|
|
|
# in the Stag tree; if not there is a problem |
|
1544
|
|
|
|
|
|
|
my @refcols = grep { ref($store_hash{$_}) } keys %store_hash; |
|
1545
|
|
|
|
|
|
|
if (@refcols) { |
|
1546
|
|
|
|
|
|
|
foreach (@$mapping) { |
|
1547
|
|
|
|
|
|
|
trace(0, $_->sxpr) if $TRACE; |
|
1548
|
|
|
|
|
|
|
} |
|
1549
|
|
|
|
|
|
|
confess("I can't store the current node; ". |
|
1550
|
|
|
|
|
|
|
"These elements need to be mapped via FKs: ". |
|
1551
|
|
|
|
|
|
|
join(', ', map {"\"@refcols\""} @refcols). |
|
1552
|
|
|
|
|
|
|
"\n\nPerhaps you need to specify more schema metadata?"); |
|
1553
|
|
|
|
|
|
|
} # -- end of sanity check |
|
1554
|
|
|
|
|
|
|
|
|
1555
|
|
|
|
|
|
|
# each relation has zero or more unique keys; |
|
1556
|
|
|
|
|
|
|
# unique keys may be compound (ie >1 column) |
|
1557
|
|
|
|
|
|
|
my @usets = $self->get_unique_sets($element); |
|
1558
|
|
|
|
|
|
|
trace(0, "USETS: ", map {"unique[ @$_ ]"} @usets) if $TRACE; |
|
1559
|
|
|
|
|
|
|
|
|
1560
|
|
|
|
|
|
|
# get all the columns/fields/attributes of this relation |
|
1561
|
|
|
|
|
|
|
my @cols = $self->get_all_cols($element); |
|
1562
|
|
|
|
|
|
|
trace(0, "COLS: @cols") if $TRACE; |
|
1563
|
|
|
|
|
|
|
|
|
1564
|
|
|
|
|
|
|
# store_node() will either perform an update or |
|
1565
|
|
|
|
|
|
|
# an insert. if we are performing an update, we |
|
1566
|
|
|
|
|
|
|
# need a query constraint to determine which row |
|
1567
|
|
|
|
|
|
|
# to update. |
|
1568
|
|
|
|
|
|
|
# |
|
1569
|
|
|
|
|
|
|
# this hash is used to determine the key/val pairs |
|
1570
|
|
|
|
|
|
|
my %unique_constr; |
|
1571
|
|
|
|
|
|
|
|
|
1572
|
|
|
|
|
|
|
# this is the value of the primary key of |
|
1573
|
|
|
|
|
|
|
# the inserted/update row |
|
1574
|
|
|
|
|
|
|
my $id; |
|
1575
|
|
|
|
|
|
|
|
|
1576
|
|
|
|
|
|
|
# if this relation has a primary key AND the stag node |
|
1577
|
|
|
|
|
|
|
# being stored has the value of this column set, THEN |
|
1578
|
|
|
|
|
|
|
# use this as the update constraint |
|
1579
|
|
|
|
|
|
|
if (0 && $pkcol) { |
|
1580
|
|
|
|
|
|
|
my $pk_id; |
|
1581
|
|
|
|
|
|
|
$pk_id = $node->get($pkcol); |
|
1582
|
|
|
|
|
|
|
if ($pk_id) { |
|
1583
|
|
|
|
|
|
|
# unset the value of the pk in the node; there |
|
1584
|
|
|
|
|
|
|
# is no point setting this in the UPDATE as it |
|
1585
|
|
|
|
|
|
|
# is already part of the update constraint |
|
1586
|
|
|
|
|
|
|
$node->unset($pkcol); |
|
1587
|
|
|
|
|
|
|
|
|
1588
|
|
|
|
|
|
|
# set the update constraint based on the PK value |
|
1589
|
|
|
|
|
|
|
%unique_constr = ($pkcol => $pk_id); |
|
1590
|
|
|
|
|
|
|
|
|
1591
|
|
|
|
|
|
|
# return this value at the end |
|
1592
|
|
|
|
|
|
|
$id = $pk_id; |
|
1593
|
|
|
|
|
|
|
trace(0, "SETTING UPDATE CONSTR BASED ON PK $pkcol = $pk_id") if $TRACE; |
|
1594
|
|
|
|
|
|
|
} |
|
1595
|
|
|
|
|
|
|
} # -- end of xxxx |
|
1596
|
|
|
|
|
|
|
|
|
1597
|
|
|
|
|
|
|
|
|
1598
|
|
|
|
|
|
|
# foreach my $sn ($node->kids) { |
|
1599
|
|
|
|
|
|
|
# my $name = $sn->element; |
|
1600
|
|
|
|
|
|
|
# my $nu_id = $self->id_mapping($name, $sn->data); |
|
1601
|
|
|
|
|
|
|
# # do the old 2 nu mapping |
|
1602
|
|
|
|
|
|
|
# # (the ids in the xml are just temporary |
|
1603
|
|
|
|
|
|
|
# # for internal consistency) |
|
1604
|
|
|
|
|
|
|
# $sn->data($nu_id) if $nu_id; |
|
1605
|
|
|
|
|
|
|
# } |
|
1606
|
|
|
|
|
|
|
|
|
1607
|
|
|
|
|
|
|
if (0) { |
|
1608
|
|
|
|
|
|
|
# ---- EXPERIMENTAL ---- |
|
1609
|
|
|
|
|
|
|
# if no unique keys are provided, assume that all |
|
1610
|
|
|
|
|
|
|
# non-PK columns together provide a compound unique key |
|
1611
|
|
|
|
|
|
|
# <> expedient for now! |
|
1612
|
|
|
|
|
|
|
if (!@usets) { |
|
1613
|
|
|
|
|
|
|
@usets = ( [grep {$_ ne $pkcol} @cols] ); |
|
1614
|
|
|
|
|
|
|
} |
|
1615
|
|
|
|
|
|
|
} |
|
1616
|
|
|
|
|
|
|
if ($pkcol) { |
|
1617
|
|
|
|
|
|
|
# make single PK the first unique key set; |
|
1618
|
|
|
|
|
|
|
# add to beginning as this is the most efficient |
|
1619
|
|
|
|
|
|
|
unshift(@usets, [$pkcol]); |
|
1620
|
|
|
|
|
|
|
} |
|
1621
|
|
|
|
|
|
|
|
|
1622
|
|
|
|
|
|
|
# get the column to select to get the pk for this element |
|
1623
|
|
|
|
|
|
|
my $select_col = $pkcol; |
|
1624
|
|
|
|
|
|
|
|
|
1625
|
|
|
|
|
|
|
# -------- find update constraint by unique keys ---- |
|
1626
|
|
|
|
|
|
|
# if the unique_constr hash is set, we know we |
|
1627
|
|
|
|
|
|
|
# are doing an UPDATE, and we know the query |
|
1628
|
|
|
|
|
|
|
# constraint that will be used; |
|
1629
|
|
|
|
|
|
|
# |
|
1630
|
|
|
|
|
|
|
# otherwise loop through all unique keys; if |
|
1631
|
|
|
|
|
|
|
# all the columns in the key are set, then we |
|
1632
|
|
|
|
|
|
|
# can safely use this unique key as the update |
|
1633
|
|
|
|
|
|
|
# constraint. |
|
1634
|
|
|
|
|
|
|
# if no update constraint can be found, this node |
|
1635
|
|
|
|
|
|
|
# is presumed not to exist in the DB and an INSERT |
|
1636
|
|
|
|
|
|
|
# is performed |
|
1637
|
|
|
|
|
|
|
foreach my $uset (@usets) { |
|
1638
|
|
|
|
|
|
|
# we already know & have the primary key |
|
1639
|
|
|
|
|
|
|
last if %unique_constr; |
|
1640
|
|
|
|
|
|
|
|
|
1641
|
|
|
|
|
|
|
# if we are loading up a fresh/blank slate |
|
1642
|
|
|
|
|
|
|
# database then we don't need to check for |
|
1643
|
|
|
|
|
|
|
# existing tuples, as everything should |
|
1644
|
|
|
|
|
|
|
# have been inserted/updated this session |
|
1645
|
|
|
|
|
|
|
if ($self->policy_freshbulkload) { |
|
1646
|
|
|
|
|
|
|
next; |
|
1647
|
|
|
|
|
|
|
} |
|
1648
|
|
|
|
|
|
|
|
|
1649
|
|
|
|
|
|
|
# if an xort-style attribute has op=insert |
|
1650
|
|
|
|
|
|
|
# this is the same as a bulkload |
|
1651
|
|
|
|
|
|
|
if ($operation && $operation eq 'insert') { |
|
1652
|
|
|
|
|
|
|
next; |
|
1653
|
|
|
|
|
|
|
} |
|
1654
|
|
|
|
|
|
|
|
|
1655
|
|
|
|
|
|
|
# already tried PK |
|
1656
|
|
|
|
|
|
|
# if (scalar(@$uset) == 1 && |
|
1657
|
|
|
|
|
|
|
# $uset->[0] eq $pkcol) { |
|
1658
|
|
|
|
|
|
|
# next; |
|
1659
|
|
|
|
|
|
|
# } |
|
1660
|
|
|
|
|
|
|
trace(0, "TRYING USET: ;@$uset; [pk=$pkcol]") if $TRACE; |
|
1661
|
|
|
|
|
|
|
|
|
1662
|
|
|
|
|
|
|
# get the values of the unique key columns; |
|
1663
|
|
|
|
|
|
|
# %constr is a candidate unique key=>val mapping |
|
1664
|
|
|
|
|
|
|
my %constr = |
|
1665
|
|
|
|
|
|
|
map { |
|
1666
|
|
|
|
|
|
|
my $v = $node->sget($_); |
|
1667
|
|
|
|
|
|
|
$_ => $v |
|
1668
|
|
|
|
|
|
|
} @$uset; |
|
1669
|
|
|
|
|
|
|
|
|
1670
|
|
|
|
|
|
|
# each column in the unique key must be |
|
1671
|
|
|
|
|
|
|
# non-NULL; try the next unique key if |
|
1672
|
|
|
|
|
|
|
# this one is unsuitable |
|
1673
|
|
|
|
|
|
|
|
|
1674
|
|
|
|
|
|
|
# -- COMMENTED OUT cjm 20041012 |
|
1675
|
|
|
|
|
|
|
# mysql auto-creates defaults for non-null fields; |
|
1676
|
|
|
|
|
|
|
# we cannot use this code: |
|
1677
|
|
|
|
|
|
|
# UNCOMMENTED 20050304 |
|
1678
|
|
|
|
|
|
|
|
|
1679
|
|
|
|
|
|
|
# -- make null value part of the key |
|
1680
|
|
|
|
|
|
|
# -- ADDED 20041012 - make null 0/'' |
|
1681
|
|
|
|
|
|
|
|
|
1682
|
|
|
|
|
|
|
foreach (keys %constr) { |
|
1683
|
|
|
|
|
|
|
# in pg, pk cols are sequences with defaults nextval |
|
1684
|
|
|
|
|
|
|
# skip these |
|
1685
|
|
|
|
|
|
|
next if $self->is_pk_col($_); |
|
1686
|
|
|
|
|
|
|
if (!defined($constr{$_})) { |
|
1687
|
|
|
|
|
|
|
if ($self->is_fk_col($_)) { |
|
1688
|
|
|
|
|
|
|
# if xort-style, the container may be an |
|
1689
|
|
|
|
|
|
|
# implicit foreign key |
|
1690
|
|
|
|
|
|
|
|
|
1691
|
|
|
|
|
|
|
# TODO: check element |
|
1692
|
|
|
|
|
|
|
if ($parent_pk_id) { |
|
1693
|
|
|
|
|
|
|
trace(0, "USING PARENT ELEMENT: $_ => $parent_pk_id"); |
|
1694
|
|
|
|
|
|
|
$constr{$_} = $parent_pk_id; |
|
1695
|
|
|
|
|
|
|
} |
|
1696
|
|
|
|
|
|
|
} |
|
1697
|
|
|
|
|
|
|
else { |
|
1698
|
|
|
|
|
|
|
my $colobj = $tableobj->column($_); |
|
1699
|
|
|
|
|
|
|
my $default_val = $colobj->default; |
|
1700
|
|
|
|
|
|
|
my $col_type = $colobj->type; |
|
1701
|
|
|
|
|
|
|
if (defined $default_val) { |
|
1702
|
|
|
|
|
|
|
# problem with DBIx::DBSchema |
|
1703
|
|
|
|
|
|
|
if ($default_val =~ /^\'(.*)\'::/) { |
|
1704
|
|
|
|
|
|
|
trace(0, "FIXING DEFAULT: $default_val => $1") if $TRACE; |
|
1705
|
|
|
|
|
|
|
$default_val = $1; |
|
1706
|
|
|
|
|
|
|
} |
|
1707
|
|
|
|
|
|
|
if (($col_type =~ /^int/ || $col_type =~ /float/) && $default_val eq '') { |
|
1708
|
|
|
|
|
|
|
# this SHOULDN'T be necessary, but appears to be required for |
|
1709
|
|
|
|
|
|
|
# some configuartions. DBSchema problem? |
|
1710
|
|
|
|
|
|
|
$default_val=0; |
|
1711
|
|
|
|
|
|
|
} |
|
1712
|
|
|
|
|
|
|
if (ref($default_val)) { |
|
1713
|
|
|
|
|
|
|
# In new versions of DBIx::DBSchema (0.38, possibly older versions), |
|
1714
|
|
|
|
|
|
|
# this appears to be a reference |
|
1715
|
|
|
|
|
|
|
$default_val = $$default_val; |
|
1716
|
|
|
|
|
|
|
if ($default_val eq "''") { |
|
1717
|
|
|
|
|
|
|
$default_val = ''; |
|
1718
|
|
|
|
|
|
|
} |
|
1719
|
|
|
|
|
|
|
} |
|
1720
|
|
|
|
|
|
|
$constr{$_} = $default_val; |
|
1721
|
|
|
|
|
|
|
trace(0, "USING DEFAULT[type=$col_type] $_ => \"$constr{$_}\"") if $TRACE; |
|
1722
|
|
|
|
|
|
|
} |
|
1723
|
|
|
|
|
|
|
} |
|
1724
|
|
|
|
|
|
|
} |
|
1725
|
|
|
|
|
|
|
} |
|
1726
|
|
|
|
|
|
|
|
|
1727
|
|
|
|
|
|
|
# TODO: check cases eg dbxref in chado; null default values...? |
|
1728
|
|
|
|
|
|
|
next if grep { !defined($_) } values %constr; |
|
1729
|
|
|
|
|
|
|
|
|
1730
|
|
|
|
|
|
|
%unique_constr = %constr; |
|
1731
|
|
|
|
|
|
|
if (!$select_col && @$uset == 1) { |
|
1732
|
|
|
|
|
|
|
$select_col = $uset->[0]; |
|
1733
|
|
|
|
|
|
|
} |
|
1734
|
|
|
|
|
|
|
trace(0, "GOT unique_constr, select_col=$select_col") if $TRACE; |
|
1735
|
|
|
|
|
|
|
last; |
|
1736
|
|
|
|
|
|
|
} |
|
1737
|
|
|
|
|
|
|
# -- END OF @usets -- |
|
1738
|
|
|
|
|
|
|
|
|
1739
|
|
|
|
|
|
|
# %unique_constr is set; a mapping for a unique key colset |
|
1740
|
|
|
|
|
|
|
# if this is not set, then we must insert |
|
1741
|
|
|
|
|
|
|
|
|
1742
|
|
|
|
|
|
|
if (%unique_constr) { |
|
1743
|
|
|
|
|
|
|
|
|
1744
|
|
|
|
|
|
|
# -- IN-MEMORY CACHING -- |
|
1745
|
|
|
|
|
|
|
# check if we have already updated/inserted |
|
1746
|
|
|
|
|
|
|
# this tuple this session; and if so, what |
|
1747
|
|
|
|
|
|
|
# the update constraint used was |
|
1748
|
|
|
|
|
|
|
if ($is_caching_on == 1 || $is_caching_on == 3) { |
|
1749
|
|
|
|
|
|
|
|
|
1750
|
|
|
|
|
|
|
#$self->throw("no select col for $element") unless $select_col; |
|
1751
|
|
|
|
|
|
|
# fetch values of unique_constr from cache |
|
1752
|
|
|
|
|
|
|
my %cached_colvals = |
|
1753
|
|
|
|
|
|
|
%{$self->query_cache($element, |
|
1754
|
|
|
|
|
|
|
\%unique_constr) |
|
1755
|
|
|
|
|
|
|
|| {}}; |
|
1756
|
|
|
|
|
|
|
# have we stored anything with uniq key %unique_constr before? |
|
1757
|
|
|
|
|
|
|
if (%cached_colvals) { |
|
1758
|
|
|
|
|
|
|
if ($pkcol) { |
|
1759
|
|
|
|
|
|
|
$id = $cached_colvals{$pkcol}; |
|
1760
|
|
|
|
|
|
|
if ($id) { |
|
1761
|
|
|
|
|
|
|
# use the cached pk id for efficiency |
|
1762
|
|
|
|
|
|
|
#%unique_constr = {$pkcol => $id}; |
|
1763
|
|
|
|
|
|
|
trace(0, "CACHED $pkcol = $id") if $TRACE; |
|
1764
|
|
|
|
|
|
|
} |
|
1765
|
|
|
|
|
|
|
else { |
|
1766
|
|
|
|
|
|
|
trace(0, "NO CACHED COLVAL FOR $pkcol :: ". |
|
1767
|
|
|
|
|
|
|
join("; ",map {"$_ = $cached_colvals{$_}"} keys %cached_colvals)) if $TRACE; |
|
1768
|
|
|
|
|
|
|
} |
|
1769
|
|
|
|
|
|
|
} |
|
1770
|
|
|
|
|
|
|
|
|
1771
|
|
|
|
|
|
|
# yes - has it changed? |
|
1772
|
|
|
|
|
|
|
foreach my $col (keys %cached_colvals) { |
|
1773
|
|
|
|
|
|
|
if ($cached_colvals{$col} && $store_hash{$col} && |
|
1774
|
|
|
|
|
|
|
$cached_colvals{$col} && $store_hash{$col}) { |
|
1775
|
|
|
|
|
|
|
# don't bother re-storing anything |
|
1776
|
|
|
|
|
|
|
delete $store_hash{$col}; |
|
1777
|
|
|
|
|
|
|
} |
|
1778
|
|
|
|
|
|
|
} |
|
1779
|
|
|
|
|
|
|
if (%store_hash) { |
|
1780
|
|
|
|
|
|
|
my @x = keys %store_hash; |
|
1781
|
|
|
|
|
|
|
trace(0, "WILL STORE: @x") if $TRACE; |
|
1782
|
|
|
|
|
|
|
} |
|
1783
|
|
|
|
|
|
|
else { |
|
1784
|
|
|
|
|
|
|
trace(0, "UNCHANGED - WILL NOT STORE; store_hash empty") if $TRACE; |
|
1785
|
|
|
|
|
|
|
} |
|
1786
|
|
|
|
|
|
|
} |
|
1787
|
|
|
|
|
|
|
else { |
|
1788
|
|
|
|
|
|
|
} |
|
1789
|
|
|
|
|
|
|
} |
|
1790
|
|
|
|
|
|
|
# -- END OF CACHING CHECK -- |
|
1791
|
|
|
|
|
|
|
|
|
1792
|
|
|
|
|
|
|
# -- GET PK VAL $id BASED ON unique_constr -- |
|
1793
|
|
|
|
|
|
|
# (we may already have this based on memory-cache) |
|
1794
|
|
|
|
|
|
|
if (!$id) { |
|
1795
|
|
|
|
|
|
|
|
|
1796
|
|
|
|
|
|
|
# the input node contains all the keys in %update_constr |
|
1797
|
|
|
|
|
|
|
# - check to see if this relation exists in the DB |
|
1798
|
|
|
|
|
|
|
|
|
1799
|
|
|
|
|
|
|
my $vals; |
|
1800
|
|
|
|
|
|
|
if ($is_caching_on >= 2) { |
|
1801
|
|
|
|
|
|
|
$vals = []; |
|
1802
|
|
|
|
|
|
|
} |
|
1803
|
|
|
|
|
|
|
else { |
|
1804
|
|
|
|
|
|
|
my $sql = |
|
1805
|
|
|
|
|
|
|
$self->makesql($element, |
|
1806
|
|
|
|
|
|
|
\%unique_constr, |
|
1807
|
|
|
|
|
|
|
$select_col); |
|
1808
|
|
|
|
|
|
|
trace(0, "SQL: $sql") if $TRACE; |
|
1809
|
|
|
|
|
|
|
$vals = |
|
1810
|
|
|
|
|
|
|
$dbh->selectcol_arrayref($sql); |
|
1811
|
|
|
|
|
|
|
} |
|
1812
|
|
|
|
|
|
|
|
|
1813
|
|
|
|
|
|
|
if (@$vals) { |
|
1814
|
|
|
|
|
|
|
# yes it does exist in DB; check if there is a |
|
1815
|
|
|
|
|
|
|
# pkcol - if there is, it means we can do an |
|
1816
|
|
|
|
|
|
|
# update and |
|
1817
|
|
|
|
|
|
|
if ($pkcol && $select_col && $select_col eq $pkcol) { |
|
1818
|
|
|
|
|
|
|
# this is the value we return at the |
|
1819
|
|
|
|
|
|
|
# end |
|
1820
|
|
|
|
|
|
|
$id = $vals->[0]; |
|
1821
|
|
|
|
|
|
|
if ($remap{$pkcol}) { |
|
1822
|
|
|
|
|
|
|
#my $colvalmap = $self->get_mapping_for_col($pkcol); |
|
1823
|
|
|
|
|
|
|
my $colvalmap = $self->id_remap_idx; |
|
1824
|
|
|
|
|
|
|
#my $colvalmap = $self->get_mapping_for_col($element); |
|
1825
|
|
|
|
|
|
|
$colvalmap->{$remap{$pkcol}} = $id; |
|
1826
|
|
|
|
|
|
|
trace(0, "COLVALMAP $pkcol $remap{$pkcol} = $id") if $TRACE; |
|
1827
|
|
|
|
|
|
|
} |
|
1828
|
|
|
|
|
|
|
} |
|
1829
|
|
|
|
|
|
|
else { |
|
1830
|
|
|
|
|
|
|
# $id not set, but we will later perform an update anyway |
|
1831
|
|
|
|
|
|
|
} |
|
1832
|
|
|
|
|
|
|
} |
|
1833
|
|
|
|
|
|
|
else { |
|
1834
|
|
|
|
|
|
|
# this node is not in the DB; force insert |
|
1835
|
|
|
|
|
|
|
%unique_constr = (); |
|
1836
|
|
|
|
|
|
|
} |
|
1837
|
|
|
|
|
|
|
} |
|
1838
|
|
|
|
|
|
|
} # end of get pk val |
|
1839
|
|
|
|
|
|
|
|
|
1840
|
|
|
|
|
|
|
# ---- UPDATE OR INSERT ----- |
|
1841
|
|
|
|
|
|
|
# at this stage we know if we are updating |
|
1842
|
|
|
|
|
|
|
# or inserting, depending on whether a suitable |
|
1843
|
|
|
|
|
|
|
# update constraint has been found |
|
1844
|
|
|
|
|
|
|
|
|
1845
|
|
|
|
|
|
|
my $this_op; |
|
1846
|
|
|
|
|
|
|
if (%unique_constr) { |
|
1847
|
|
|
|
|
|
|
$this_op = 'update'; |
|
1848
|
|
|
|
|
|
|
} |
|
1849
|
|
|
|
|
|
|
else { |
|
1850
|
|
|
|
|
|
|
$this_op = 'insert'; |
|
1851
|
|
|
|
|
|
|
} |
|
1852
|
|
|
|
|
|
|
if (defined $operation) { |
|
1853
|
|
|
|
|
|
|
if ($operation eq 'force') { |
|
1854
|
|
|
|
|
|
|
$operation = $this_op; |
|
1855
|
|
|
|
|
|
|
} |
|
1856
|
|
|
|
|
|
|
else { |
|
1857
|
|
|
|
|
|
|
# update/lookup/insert |
|
1858
|
|
|
|
|
|
|
# insert: already dealt with |
|
1859
|
|
|
|
|
|
|
} |
|
1860
|
|
|
|
|
|
|
} |
|
1861
|
|
|
|
|
|
|
else { |
|
1862
|
|
|
|
|
|
|
$operation = $this_op; |
|
1863
|
|
|
|
|
|
|
} |
|
1864
|
|
|
|
|
|
|
|
|
1865
|
|
|
|
|
|
|
if ($operation eq 'replace') { |
|
1866
|
|
|
|
|
|
|
# replace = delete followed by insert |
|
1867
|
|
|
|
|
|
|
if (%unique_constr) { |
|
1868
|
|
|
|
|
|
|
$self->deleterow($element,\%unique_constr); |
|
1869
|
|
|
|
|
|
|
} |
|
1870
|
|
|
|
|
|
|
else { |
|
1871
|
|
|
|
|
|
|
$self->throw("Cannot find row to delete it:\n".$node->xml); |
|
1872
|
|
|
|
|
|
|
} |
|
1873
|
|
|
|
|
|
|
$operation = 'insert'; |
|
1874
|
|
|
|
|
|
|
} |
|
1875
|
|
|
|
|
|
|
|
|
1876
|
|
|
|
|
|
|
if ($operation eq 'update') { |
|
1877
|
|
|
|
|
|
|
# ** UPDATE ** |
|
1878
|
|
|
|
|
|
|
if ($self->noupdate_h->{$element}) { |
|
1879
|
|
|
|
|
|
|
if ($tracekeyval) { |
|
1880
|
|
|
|
|
|
|
printf STDERR "NOUPDATE: $tracenode = $tracekeyval\n" |
|
1881
|
|
|
|
|
|
|
} |
|
1882
|
|
|
|
|
|
|
trace(0, sprintf("NOUPDATE on %s OR child nodes (We have %s)", |
|
1883
|
|
|
|
|
|
|
$element, |
|
1884
|
|
|
|
|
|
|
join('; ',values %unique_constr) |
|
1885
|
|
|
|
|
|
|
)) if $TRACE; |
|
1886
|
|
|
|
|
|
|
# don't return yet; there are still the delayed nodes |
|
1887
|
|
|
|
|
|
|
##return $id; |
|
1888
|
|
|
|
|
|
|
} |
|
1889
|
|
|
|
|
|
|
else { |
|
1890
|
|
|
|
|
|
|
# if there are no fields modified, |
|
1891
|
|
|
|
|
|
|
# no change |
|
1892
|
|
|
|
|
|
|
foreach (keys %unique_constr) { |
|
1893
|
|
|
|
|
|
|
# no point setting any column |
|
1894
|
|
|
|
|
|
|
# that is part of the update constraint |
|
1895
|
|
|
|
|
|
|
delete $store_hash{$_}; |
|
1896
|
|
|
|
|
|
|
} |
|
1897
|
|
|
|
|
|
|
|
|
1898
|
|
|
|
|
|
|
# only update if there are cols set that are |
|
1899
|
|
|
|
|
|
|
# not part of unique constraint |
|
1900
|
|
|
|
|
|
|
if (%store_hash) { |
|
1901
|
|
|
|
|
|
|
if ($tracekeyval) { |
|
1902
|
|
|
|
|
|
|
printf STDERR "UPDATE: $tracenode = $tracekeyval\n" |
|
1903
|
|
|
|
|
|
|
} |
|
1904
|
|
|
|
|
|
|
|
|
1905
|
|
|
|
|
|
|
$self->updaterow($element, |
|
1906
|
|
|
|
|
|
|
\%store_hash, |
|
1907
|
|
|
|
|
|
|
\%unique_constr); |
|
1908
|
|
|
|
|
|
|
# -- CACHE RESULTS -- |
|
1909
|
|
|
|
|
|
|
if ($is_caching_on == 1 || $is_caching_on == 3) { |
|
1910
|
|
|
|
|
|
|
$self->update_cache($element, |
|
1911
|
|
|
|
|
|
|
\%store_hash, |
|
1912
|
|
|
|
|
|
|
\%unique_constr); |
|
1913
|
|
|
|
|
|
|
} |
|
1914
|
|
|
|
|
|
|
} |
|
1915
|
|
|
|
|
|
|
else { |
|
1916
|
|
|
|
|
|
|
trace(0, sprintf("NOCHANGE on %s (We have %s) id=$id", |
|
1917
|
|
|
|
|
|
|
$element, |
|
1918
|
|
|
|
|
|
|
join('; ',values %unique_constr) |
|
1919
|
|
|
|
|
|
|
)) if $TRACE; |
|
1920
|
|
|
|
|
|
|
if ($tracekeyval) { |
|
1921
|
|
|
|
|
|
|
print STDERR "NOCHANGE: $tracenode = $tracekeyval\n" |
|
1922
|
|
|
|
|
|
|
} |
|
1923
|
|
|
|
|
|
|
} |
|
1924
|
|
|
|
|
|
|
} |
|
1925
|
|
|
|
|
|
|
} elsif ($operation eq 'insert') { |
|
1926
|
|
|
|
|
|
|
# ** INSERT ** |
|
1927
|
|
|
|
|
|
|
if (%store_hash) { |
|
1928
|
|
|
|
|
|
|
$id = |
|
1929
|
|
|
|
|
|
|
$self->insertrow($element, |
|
1930
|
|
|
|
|
|
|
\%store_hash, |
|
1931
|
|
|
|
|
|
|
$pkcol); |
|
1932
|
|
|
|
|
|
|
if (!$id) { |
|
1933
|
|
|
|
|
|
|
# this only happens if $self->force(1) is set |
|
1934
|
|
|
|
|
|
|
if (@delayed_store) { |
|
1935
|
|
|
|
|
|
|
print STDERR "Insert on \"$element\" did not return a primary key ID.\n Possible causes: sequence not define [Pg]?\n"; |
|
1936
|
|
|
|
|
|
|
if ($self->force) { |
|
1937
|
|
|
|
|
|
|
return; |
|
1938
|
|
|
|
|
|
|
} |
|
1939
|
|
|
|
|
|
|
else { |
|
1940
|
|
|
|
|
|
|
confess("non-recoverable error"); |
|
1941
|
|
|
|
|
|
|
} |
|
1942
|
|
|
|
|
|
|
} |
|
1943
|
|
|
|
|
|
|
return; |
|
1944
|
|
|
|
|
|
|
} |
|
1945
|
|
|
|
|
|
|
if ($tracekeyval) { |
|
1946
|
|
|
|
|
|
|
printf STDERR "INSERT: $tracenode $tracekeyval [val = $id]\n" |
|
1947
|
|
|
|
|
|
|
} |
|
1948
|
|
|
|
|
|
|
if ($pkcol) { |
|
1949
|
|
|
|
|
|
|
if ($remap{$pkcol}) { |
|
1950
|
|
|
|
|
|
|
my $colvalmap = $self->id_remap_idx; |
|
1951
|
|
|
|
|
|
|
#my $colvalmap = $self->get_mapping_for_col($element); |
|
1952
|
|
|
|
|
|
|
$colvalmap->{$remap{$pkcol}} = $id; |
|
1953
|
|
|
|
|
|
|
trace(0, "colvalmap $remap{$pkcol} = $id") if $TRACE; |
|
1954
|
|
|
|
|
|
|
} |
|
1955
|
|
|
|
|
|
|
} |
|
1956
|
|
|
|
|
|
|
|
|
1957
|
|
|
|
|
|
|
# -- CACHE RESULTS -- |
|
1958
|
|
|
|
|
|
|
if ($is_caching_on) { |
|
1959
|
|
|
|
|
|
|
my %cache_hash = %store_hash; |
|
1960
|
|
|
|
|
|
|
if ($pkcol) { |
|
1961
|
|
|
|
|
|
|
$cache_hash{$pkcol} = $id; |
|
1962
|
|
|
|
|
|
|
} |
|
1963
|
|
|
|
|
|
|
$self->insert_into_cache($element, |
|
1964
|
|
|
|
|
|
|
\%cache_hash, |
|
1965
|
|
|
|
|
|
|
\@usets); |
|
1966
|
|
|
|
|
|
|
trace(0, "CACHING: $element") if $TRACE; |
|
1967
|
|
|
|
|
|
|
} |
|
1968
|
|
|
|
|
|
|
} |
|
1969
|
|
|
|
|
|
|
} |
|
1970
|
|
|
|
|
|
|
elsif ($operation eq 'delete') { |
|
1971
|
|
|
|
|
|
|
if (%unique_constr) { |
|
1972
|
|
|
|
|
|
|
$self->deleterow($element,\%unique_constr); |
|
1973
|
|
|
|
|
|
|
} |
|
1974
|
|
|
|
|
|
|
else { |
|
1975
|
|
|
|
|
|
|
$self->throw("Cannot find row to delete it (perhaps unique constraint not satisfied?):\n".$node->xml); |
|
1976
|
|
|
|
|
|
|
} |
|
1977
|
|
|
|
|
|
|
} |
|
1978
|
|
|
|
|
|
|
elsif ($operation eq 'lookup') { |
|
1979
|
|
|
|
|
|
|
# lookup: do nothing, already have ID |
|
1980
|
|
|
|
|
|
|
if (!$id) { |
|
1981
|
|
|
|
|
|
|
$self->throw("lookup: no ID; could not find this node in db (perhaps unique constraint not satisfied?) %s:\n",$node->xml); |
|
1982
|
|
|
|
|
|
|
} |
|
1983
|
|
|
|
|
|
|
} |
|
1984
|
|
|
|
|
|
|
else { |
|
1985
|
|
|
|
|
|
|
$self->throw("cannot do op: $operation"); |
|
1986
|
|
|
|
|
|
|
} # -- end of UPDATE/INSERT/LOOKUP |
|
1987
|
|
|
|
|
|
|
|
|
1988
|
|
|
|
|
|
|
|
|
1989
|
|
|
|
|
|
|
# -- DELAYED STORE -- |
|
1990
|
|
|
|
|
|
|
# Any non-terminal child nodes of the current one have |
|
1991
|
|
|
|
|
|
|
# some kind of foreign key relationship to the current |
|
1992
|
|
|
|
|
|
|
# relation. Either it is 1:many or many:1 |
|
1993
|
|
|
|
|
|
|
# |
|
1994
|
|
|
|
|
|
|
# if the relation for the child node has a foreign key |
|
1995
|
|
|
|
|
|
|
# into the current relation, we need to store the current |
|
1996
|
|
|
|
|
|
|
# relation first to get the current relation's primary key. |
|
1997
|
|
|
|
|
|
|
# |
|
1998
|
|
|
|
|
|
|
# we have already done this, so now is the time to store |
|
1999
|
|
|
|
|
|
|
# any of these child nodes |
|
2000
|
|
|
|
|
|
|
if (@delayed_store) { |
|
2001
|
|
|
|
|
|
|
foreach my $sn (@delayed_store) { |
|
2002
|
|
|
|
|
|
|
|
|
2003
|
|
|
|
|
|
|
my $fk; # foreign key column in subtable |
|
2004
|
|
|
|
|
|
|
my $snname = $sn->name; # subtable name |
|
2005
|
|
|
|
|
|
|
|
|
2006
|
|
|
|
|
|
|
# if a mapping is used (eg in metadata), then |
|
2007
|
|
|
|
|
|
|
# this takes priority |
|
2008
|
|
|
|
|
|
|
foreach (@$mapping) { |
|
2009
|
|
|
|
|
|
|
if ($_->name eq 'parentfk' && |
|
2010
|
|
|
|
|
|
|
$_->get_table eq $snname) { |
|
2011
|
|
|
|
|
|
|
$fk = $_->get_col; |
|
2012
|
|
|
|
|
|
|
} |
|
2013
|
|
|
|
|
|
|
} |
|
2014
|
|
|
|
|
|
|
|
|
2015
|
|
|
|
|
|
|
# no mapping, by default use the current nodes primary |
|
2016
|
|
|
|
|
|
|
# key (this assumes eg person.address_id is a fk to |
|
2017
|
|
|
|
|
|
|
# a table with pk address_id; we will check and possibly |
|
2018
|
|
|
|
|
|
|
# override this later) |
|
2019
|
|
|
|
|
|
|
if (!$fk) { |
|
2020
|
|
|
|
|
|
|
$fk = $pkcol; |
|
2021
|
|
|
|
|
|
|
} |
|
2022
|
|
|
|
|
|
|
|
|
2023
|
|
|
|
|
|
|
# HACK!! |
|
2024
|
|
|
|
|
|
|
# Some databases (eg GO Database) use 'id' for pk col |
|
2025
|
|
|
|
|
|
|
# names; fks to this table will be of form _id
|
2026
|
|
|
|
|
|
|
if ($fk eq 'id') { |
|
2027
|
|
|
|
|
|
|
$fk = $element . '_id'; |
|
2028
|
|
|
|
|
|
|
} |
|
2029
|
|
|
|
|
|
|
|
|
2030
|
|
|
|
|
|
|
# --SET SUBNODE FK-- |
|
2031
|
|
|
|
|
|
|
# it is necessarily true that each delayed-store subnode |
|
2032
|
|
|
|
|
|
|
# must have some fk relationship back to the existing one |
|
2033
|
|
|
|
|
|
|
# the subnode has a fk relation up to this one; |
|
2034
|
|
|
|
|
|
|
# by default we assume that the subnode fk column is named |
|
2035
|
|
|
|
|
|
|
# the same as the current pk. However, we check that this |
|
2036
|
|
|
|
|
|
|
# is the case. If not, we deduce what the correct fk col is |
|
2037
|
|
|
|
|
|
|
my $subtable = |
|
2038
|
|
|
|
|
|
|
$dbschema->table($snname); |
|
2039
|
|
|
|
|
|
|
if ($subtable->column($fk)) { |
|
2040
|
|
|
|
|
|
|
# a fk col with the name as the current node pk col exists; |
|
2041
|
|
|
|
|
|
|
# use it |
|
2042
|
|
|
|
|
|
|
|
|
2043
|
|
|
|
|
|
|
# do nothing - current value of $fk is fine |
|
2044
|
|
|
|
|
|
|
} |
|
2045
|
|
|
|
|
|
|
else { |
|
2046
|
|
|
|
|
|
|
# deduce actual fk column |
|
2047
|
|
|
|
|
|
|
# there should only be ONE subnode fk column UNSET; |
|
2048
|
|
|
|
|
|
|
# this implicitly refers to the current node |
|
2049
|
|
|
|
|
|
|
my @subcolumns = $subtable->columns; |
|
2050
|
|
|
|
|
|
|
my @potential_fks = (); |
|
2051
|
|
|
|
|
|
|
foreach my $subcolumn (@subcolumns) { |
|
2052
|
|
|
|
|
|
|
if ($self->is_fk_col($subcolumn) && |
|
2053
|
|
|
|
|
|
|
!$self->is_pk_col($subcolumn)) { |
|
2054
|
|
|
|
|
|
|
|
|
2055
|
|
|
|
|
|
|
# Definite foreign key |
|
2056
|
|
|
|
|
|
|
if (defined $sn->sget($subcolumn)) { |
|
2057
|
|
|
|
|
|
|
# already set |
|
2058
|
|
|
|
|
|
|
} |
|
2059
|
|
|
|
|
|
|
else { |
|
2060
|
|
|
|
|
|
|
push(@potential_fks, $subcolumn); |
|
2061
|
|
|
|
|
|
|
} |
|
2062
|
|
|
|
|
|
|
} |
|
2063
|
|
|
|
|
|
|
} |
|
2064
|
|
|
|
|
|
|
trace(0, "POTENTIAL FKS: @potential_fks"); |
|
2065
|
|
|
|
|
|
|
if (!@potential_fks) { |
|
2066
|
|
|
|
|
|
|
$self->throw("I do not know what to do with the current ". |
|
2067
|
|
|
|
|
|
|
"pl val ($id). There does not appear to be ". |
|
2068
|
|
|
|
|
|
|
"a $fk column in $snname, and all fks in ". |
|
2069
|
|
|
|
|
|
|
"the subtable $snname are currently set"); |
|
2070
|
|
|
|
|
|
|
} |
|
2071
|
|
|
|
|
|
|
if (@potential_fks > 1) { |
|
2072
|
|
|
|
|
|
|
$self->throw("There appear to be multiple potential fks ". |
|
2073
|
|
|
|
|
|
|
"[ @potential_fks ]. I do not know which ". |
|
2074
|
|
|
|
|
|
|
"to choose to assign the current pk val $id". |
|
2075
|
|
|
|
|
|
|
" to"); |
|
2076
|
|
|
|
|
|
|
} |
|
2077
|
|
|
|
|
|
|
$fk = shift @potential_fks; |
|
2078
|
|
|
|
|
|
|
} |
|
2079
|
|
|
|
|
|
|
# -- $fk value is set |
|
2080
|
|
|
|
|
|
|
$sn->set($fk, $id); |
|
2081
|
|
|
|
|
|
|
# -- $fk table assigned |
|
2082
|
|
|
|
|
|
|
|
|
2083
|
|
|
|
|
|
|
trace(0, "NOW TIME TO STORE [curr pk val = $id] [fkcol = $fk] ", $sn->xml) if $TRACE; |
|
2084
|
|
|
|
|
|
|
# store subnode, passing in info on current node |
|
2085
|
|
|
|
|
|
|
$self->_storenode($sn,{parent_pk_id=>$id, |
|
2086
|
|
|
|
|
|
|
parent_element=>$element, |
|
2087
|
|
|
|
|
|
|
assigned_node_h=>{$fk=>1}}); |
|
2088
|
|
|
|
|
|
|
} |
|
2089
|
|
|
|
|
|
|
} # -- end of @delayed_store |
|
2090
|
|
|
|
|
|
|
|
|
2091
|
|
|
|
|
|
|
if ($current_attribute_node) { |
|
2092
|
|
|
|
|
|
|
if ($id) { |
|
2093
|
|
|
|
|
|
|
my $macro_id = $current_attribute_node->sget_id; |
|
2094
|
|
|
|
|
|
|
if ($macro_id) { |
|
2095
|
|
|
|
|
|
|
$self->macro_id_h->{$macro_id} = $id; |
|
2096
|
|
|
|
|
|
|
trace(0, "SETTING MACRO ID MAP: $macro_id => $id") if $TRACE; |
|
2097
|
|
|
|
|
|
|
} |
|
2098
|
|
|
|
|
|
|
else { |
|
2099
|
|
|
|
|
|
|
} |
|
2100
|
|
|
|
|
|
|
} |
|
2101
|
|
|
|
|
|
|
} |
|
2102
|
|
|
|
|
|
|
|
|
2103
|
|
|
|
|
|
|
return $id; |
|
2104
|
|
|
|
|
|
|
} |
|
2105
|
|
|
|
|
|
|
|
|
2106
|
|
|
|
|
|
|
# --SQL directives embedded in XML-- |
|
2107
|
|
|
|
|
|
|
|
|
2108
|
|
|
|
|
|
|
sub _execute_sqlnode { |
|
2109
|
|
|
|
|
|
|
my $self = shift; |
|
2110
|
|
|
|
|
|
|
my $sqlnode = shift; |
|
2111
|
|
|
|
|
|
|
if ($sqlnode->element eq '_sql') { |
|
2112
|
|
|
|
|
|
|
my $dbh = $self->dbh; |
|
2113
|
|
|
|
|
|
|
my $op = $sqlnode->get('@/op'); |
|
2114
|
|
|
|
|
|
|
my $col = $sqlnode->get('@/col'); |
|
2115
|
|
|
|
|
|
|
my $table = $sqlnode->get('@/from'); |
|
2116
|
|
|
|
|
|
|
my $match = $sqlnode->get('@/match'); |
|
2117
|
|
|
|
|
|
|
my @subnodes = grep {$_->element ne '@'} $sqlnode->kids; |
|
2118
|
|
|
|
|
|
|
if ($op eq 'delete') { |
|
2119
|
|
|
|
|
|
|
my $pkey = $sqlnode->get('@/pkey'); |
|
2120
|
|
|
|
|
|
|
trace(0,"deleting from $table"); |
|
2121
|
|
|
|
|
|
|
my @vals = map {$self->_execute_sqlnode($_)} @subnodes; |
|
2122
|
|
|
|
|
|
|
# do iteratively rather than in 1 SQL stmt |
|
2123
|
|
|
|
|
|
|
if (@vals) { |
|
2124
|
|
|
|
|
|
|
my $sql = |
|
2125
|
|
|
|
|
|
|
sprintf("SELECT $pkey FROM $table WHERE $match IN (%s)", |
|
2126
|
|
|
|
|
|
|
join(", ",@vals)); |
|
2127
|
|
|
|
|
|
|
trace(0, "SQL: $sql"); |
|
2128
|
|
|
|
|
|
|
my $ids_to_delete = |
|
2129
|
|
|
|
|
|
|
$dbh->selectcol_arrayref($sql); # quote |
|
2130
|
|
|
|
|
|
|
foreach my $id (@$ids_to_delete) { |
|
2131
|
|
|
|
|
|
|
my $delete_sql = |
|
2132
|
|
|
|
|
|
|
"DELETE FROM $table WHERE $pkey=$id"; |
|
2133
|
|
|
|
|
|
|
trace(0,"SQL: $delete_sql"); |
|
2134
|
|
|
|
|
|
|
$dbh->do($delete_sql); |
|
2135
|
|
|
|
|
|
|
} |
|
2136
|
|
|
|
|
|
|
} |
|
2137
|
|
|
|
|
|
|
} |
|
2138
|
|
|
|
|
|
|
elsif ($op eq "select") { |
|
2139
|
|
|
|
|
|
|
my @vals = $sqlnode->get('.'); |
|
2140
|
|
|
|
|
|
|
my $sql = |
|
2141
|
|
|
|
|
|
|
sprintf("SELECT $col FROM $table WHERE $match IN (%s)", |
|
2142
|
|
|
|
|
|
|
join(", ",map {$dbh->quote($_)} @vals)); |
|
2143
|
|
|
|
|
|
|
trace(0, "SQL: $sql"); |
|
2144
|
|
|
|
|
|
|
my $ids = |
|
2145
|
|
|
|
|
|
|
$dbh->selectcol_arrayref($sql); |
|
2146
|
|
|
|
|
|
|
trace(0,"id list in select: @$ids"); |
|
2147
|
|
|
|
|
|
|
return(@$ids); |
|
2148
|
|
|
|
|
|
|
} |
|
2149
|
|
|
|
|
|
|
else { |
|
2150
|
|
|
|
|
|
|
$self->throw("Do not understand SQL directive: $op") |
|
2151
|
|
|
|
|
|
|
} |
|
2152
|
|
|
|
|
|
|
} |
|
2153
|
|
|
|
|
|
|
else { |
|
2154
|
|
|
|
|
|
|
return $sqlnode->data; |
|
2155
|
|
|
|
|
|
|
} |
|
2156
|
|
|
|
|
|
|
return; |
|
2157
|
|
|
|
|
|
|
} |
|
2158
|
|
|
|
|
|
|
|
|
2159
|
|
|
|
|
|
|
sub _process_sql { |
|
2160
|
|
|
|
|
|
|
my $self = shift; |
|
2161
|
|
|
|
|
|
|
my $node = shift; |
|
2162
|
|
|
|
|
|
|
my $element = $node->element; |
|
2163
|
|
|
|
|
|
|
if ($element eq 'in') { |
|
2164
|
|
|
|
|
|
|
|
|
2165
|
|
|
|
|
|
|
} |
|
2166
|
|
|
|
|
|
|
else { |
|
2167
|
|
|
|
|
|
|
$self->throw("Do not understand SQL directive: $element") |
|
2168
|
|
|
|
|
|
|
} |
|
2169
|
|
|
|
|
|
|
} |
|
2170
|
|
|
|
|
|
|
|
|
2171
|
|
|
|
|
|
|
# -- QUERYING -- |
|
2172
|
|
|
|
|
|
|
|
|
2173
|
|
|
|
|
|
|
sub rmake_nesting { |
|
2174
|
|
|
|
|
|
|
my $node = shift; |
|
2175
|
|
|
|
|
|
|
|
|
2176
|
|
|
|
|
|
|
if ($node->element eq 'composite') { |
|
2177
|
|
|
|
|
|
|
my $first = $node->getnode_first; |
|
2178
|
|
|
|
|
|
|
my $second = $node->getnode_second; |
|
2179
|
|
|
|
|
|
|
my $head = rmake_nesting($first->data->[0]); |
|
2180
|
|
|
|
|
|
|
my $tail = rmake_nesting($second->data->[0]); |
|
2181
|
|
|
|
|
|
|
if ($head->isterminal) { |
|
2182
|
|
|
|
|
|
|
return |
|
2183
|
|
|
|
|
|
|
Data::Stag->new($head->element => [$tail]); |
|
2184
|
|
|
|
|
|
|
} |
|
2185
|
|
|
|
|
|
|
$head->addkid($tail); |
|
2186
|
|
|
|
|
|
|
return $head; |
|
2187
|
|
|
|
|
|
|
} |
|
2188
|
|
|
|
|
|
|
elsif ($node->element eq 'leaf') { |
|
2189
|
|
|
|
|
|
|
my $alias = $node->get_alias; |
|
2190
|
|
|
|
|
|
|
my $tn = $alias || $node->get_name; |
|
2191
|
|
|
|
|
|
|
return Data::Stag->new($tn=>1); |
|
2192
|
|
|
|
|
|
|
} |
|
2193
|
|
|
|
|
|
|
else { |
|
2194
|
|
|
|
|
|
|
die; |
|
2195
|
|
|
|
|
|
|
} |
|
2196
|
|
|
|
|
|
|
} |
|
2197
|
|
|
|
|
|
|
|
|
2198
|
|
|
|
|
|
|
# if true, a metadata tag will be added to stag nodes selected from db |
|
2199
|
|
|
|
|
|
|
sub include_metadata { |
|
2200
|
|
|
|
|
|
|
my $self = shift; |
|
2201
|
|
|
|
|
|
|
$self->{_include_metadata} = shift if @_; |
|
2202
|
|
|
|
|
|
|
return $self->{_include_metadata}; |
|
2203
|
|
|
|
|
|
|
} |
|
2204
|
|
|
|
|
|
|
|
|
2205
|
|
|
|
|
|
|
|
|
2206
|
|
|
|
|
|
|
# last SQL SELECT statement executed |
|
2207
|
|
|
|
|
|
|
sub last_stmt { |
|
2208
|
|
|
|
|
|
|
my $self = shift; |
|
2209
|
|
|
|
|
|
|
$self->{_last_stmt} = shift if @_; |
|
2210
|
|
|
|
|
|
|
return $self->{_last_stmt}; |
|
2211
|
|
|
|
|
|
|
} |
|
2212
|
|
|
|
|
|
|
|
|
2213
|
|
|
|
|
|
|
sub last_sql_and_args { |
|
2214
|
|
|
|
|
|
|
my $self = shift; |
|
2215
|
|
|
|
|
|
|
$self->{_last_sql_and_args} = shift if @_; |
|
2216
|
|
|
|
|
|
|
return $self->{_last_sql_and_args}; |
|
2217
|
|
|
|
|
|
|
} |
|
2218
|
|
|
|
|
|
|
|
|
2219
|
|
|
|
|
|
|
|
|
2220
|
|
|
|
|
|
|
sub sax_handler { |
|
2221
|
|
|
|
|
|
|
my $self = shift; |
|
2222
|
|
|
|
|
|
|
$self->{_sax_handler} = shift if @_; |
|
2223
|
|
|
|
|
|
|
return $self->{_sax_handler}; |
|
2224
|
|
|
|
|
|
|
} |
|
2225
|
|
|
|
|
|
|
|
|
2226
|
|
|
|
|
|
|
|
|
2227
|
|
|
|
|
|
|
# delegates to selectall_stag and turns tree to XML |
|
2228
|
|
|
|
|
|
|
sub selectall_xml { |
|
2229
|
|
|
|
|
|
|
my $self = shift; |
|
2230
|
|
|
|
|
|
|
my $stag = $self->selectall_stag(@_); |
|
2231
|
|
|
|
|
|
|
return $stag->xml; |
|
2232
|
|
|
|
|
|
|
} |
|
2233
|
|
|
|
|
|
|
|
|
2234
|
|
|
|
|
|
|
# delegates to selectall_stag and turns tree to SAX |
|
2235
|
|
|
|
|
|
|
# (candidate for optimisation - TODO - use event firing model) |
|
2236
|
|
|
|
|
|
|
sub selectall_sax { |
|
2237
|
|
|
|
|
|
|
my $self = shift; |
|
2238
|
|
|
|
|
|
|
my ($sql, $nesting, $h) = |
|
2239
|
|
|
|
|
|
|
rearrange([qw(sql nesting handler)], @_); |
|
2240
|
|
|
|
|
|
|
my $stag = $self->selectall_stag(@_); |
|
2241
|
|
|
|
|
|
|
$h = $h || $self->sax_handler; |
|
2242
|
|
|
|
|
|
|
if (!$h) { |
|
2243
|
|
|
|
|
|
|
$self->throw("You must specify the sax handler;\n". |
|
2244
|
|
|
|
|
|
|
"Either use \$dbh->sax_handler(\$h), or \n". |
|
2245
|
|
|
|
|
|
|
"\$dbh->selectall_sax(-sql=>\$sql, handler->\$h)"); |
|
2246
|
|
|
|
|
|
|
} |
|
2247
|
|
|
|
|
|
|
return $stag->sax($h); |
|
2248
|
|
|
|
|
|
|
} |
|
2249
|
|
|
|
|
|
|
|
|
2250
|
|
|
|
|
|
|
# delegates to selectall_stag and turns tree to S-Expression |
|
2251
|
|
|
|
|
|
|
sub selectall_sxpr { |
|
2252
|
|
|
|
|
|
|
my $self = shift; |
|
2253
|
|
|
|
|
|
|
my $stag = $self->selectall_stag(@_); |
|
2254
|
|
|
|
|
|
|
return $stag->sxpr; |
|
2255
|
|
|
|
|
|
|
} |
|
2256
|
|
|
|
|
|
|
|
|
2257
|
|
|
|
|
|
|
# does not bother decomposing and nesting the results; just |
|
2258
|
|
|
|
|
|
|
# returns the denormalised table from the SQL query. |
|
2259
|
|
|
|
|
|
|
# arrayref of arrayrefs - rows x cols |
|
2260
|
|
|
|
|
|
|
# first row of rows is column headings |
|
2261
|
|
|
|
|
|
|
sub selectall_rows { |
|
2262
|
|
|
|
|
|
|
my $self = shift; |
|
2263
|
|
|
|
|
|
|
my ($sql, $nesting, $bind, $template) = |
|
2264
|
|
|
|
|
|
|
rearrange([qw(sql nesting bind template)], @_); |
|
2265
|
|
|
|
|
|
|
my $rows = |
|
2266
|
|
|
|
|
|
|
$self->selectall_stag(-sql=>$sql, |
|
2267
|
|
|
|
|
|
|
-nesting=>$nesting, |
|
2268
|
|
|
|
|
|
|
-bind=>$bind, |
|
2269
|
|
|
|
|
|
|
-template=>$template, |
|
2270
|
|
|
|
|
|
|
-return_arrayref=>1, |
|
2271
|
|
|
|
|
|
|
); |
|
2272
|
|
|
|
|
|
|
return $rows; |
|
2273
|
|
|
|
|
|
|
} |
|
2274
|
|
|
|
|
|
|
|
|
2275
|
|
|
|
|
|
|
# --------------------------------------- |
|
2276
|
|
|
|
|
|
|
# selectall_stag(sql, nesting) |
|
2277
|
|
|
|
|
|
|
# |
|
2278
|
|
|
|
|
|
|
# Takes an sql string containing a SELECT statement, |
|
2279
|
|
|
|
|
|
|
# parses it to get the tree structure; this can be |
|
2280
|
|
|
|
|
|
|
# overridden with the nesting optional argument. |
|
2281
|
|
|
|
|
|
|
# |
|
2282
|
|
|
|
|
|
|
# The SELECT statement is executed, and the relations are |
|
2283
|
|
|
|
|
|
|
# transformed into a stag tree |
|
2284
|
|
|
|
|
|
|
# |
|
2285
|
|
|
|
|
|
|
# --------------------------------------- |
|
2286
|
|
|
|
|
|
|
sub selectall_stag { |
|
2287
|
|
|
|
|
|
|
my $self = shift; |
|
2288
|
|
|
|
|
|
|
my ($sql, $nesting, $bind, $template, $return_arrayref, $include_metadata, $aliaspolicy) = |
|
2289
|
|
|
|
|
|
|
rearrange([qw(sql nesting bind template return_arrayref include_metadata aliaspolicy)], @_); |
|
2290
|
|
|
|
|
|
|
my $prep_h = $self->prepare_stag(@_); |
|
2291
|
|
|
|
|
|
|
my $cols = $prep_h->{cols}; |
|
2292
|
|
|
|
|
|
|
my $sth = $prep_h->{sth}; |
|
2293
|
|
|
|
|
|
|
my $exec_args = $prep_h->{exec_args}; |
|
2294
|
|
|
|
|
|
|
|
|
2295
|
|
|
|
|
|
|
if (!defined($include_metadata)) { |
|
2296
|
|
|
|
|
|
|
$include_metadata = $self->include_metadata; |
|
2297
|
|
|
|
|
|
|
} |
|
2298
|
|
|
|
|
|
|
|
|
2299
|
|
|
|
|
|
|
# TODO - make this event based so we don't have to |
|
2300
|
|
|
|
|
|
|
# load all into memory |
|
2301
|
|
|
|
|
|
|
my $rows = |
|
2302
|
|
|
|
|
|
|
$self->dbh->selectall_arrayref($sth, undef, @$exec_args); |
|
2303
|
|
|
|
|
|
|
if ($return_arrayref) { |
|
2304
|
|
|
|
|
|
|
my @hdrs = (); |
|
2305
|
|
|
|
|
|
|
for (my $i=0; $i<@$cols; $i++) { |
|
2306
|
|
|
|
|
|
|
my $h = $prep_h->{col_aliases_ordered}->[$i] || $cols->[$i]; |
|
2307
|
|
|
|
|
|
|
push(@hdrs, $h); |
|
2308
|
|
|
|
|
|
|
} |
|
2309
|
|
|
|
|
|
|
return [\@hdrs, @$rows]; |
|
2310
|
|
|
|
|
|
|
} |
|
2311
|
|
|
|
|
|
|
|
|
2312
|
|
|
|
|
|
|
trace(0, sprintf("Got %d rows\n", scalar(@$rows))) if $TRACE; |
|
2313
|
|
|
|
|
|
|
# --- reconstruct tree from relations |
|
2314
|
|
|
|
|
|
|
my $stag = |
|
2315
|
|
|
|
|
|
|
$self->reconstruct( |
|
2316
|
|
|
|
|
|
|
-rows=>$rows, |
|
2317
|
|
|
|
|
|
|
-cols=>$cols, |
|
2318
|
|
|
|
|
|
|
-alias=>$prep_h->{alias}, |
|
2319
|
|
|
|
|
|
|
-nesting=>$prep_h->{nesting}, |
|
2320
|
|
|
|
|
|
|
-aliaspolicy=>$aliaspolicy, |
|
2321
|
|
|
|
|
|
|
); |
|
2322
|
|
|
|
|
|
|
if ($include_metadata) { |
|
2323
|
|
|
|
|
|
|
my ($last_sql, @sql_args) = @{$self->last_sql_and_args || []}; |
|
2324
|
|
|
|
|
|
|
my @kids = $stag->kids; |
|
2325
|
|
|
|
|
|
|
my @bind_nodes; |
|
2326
|
|
|
|
|
|
|
if ($bind && ref($bind) eq 'HASH') { |
|
2327
|
|
|
|
|
|
|
@bind_nodes = (stag_unflatten(argset=>[%$bind])); |
|
2328
|
|
|
|
|
|
|
} |
|
2329
|
|
|
|
|
|
|
unshift(@kids, |
|
2330
|
|
|
|
|
|
|
[dbstag_metadata=>[ |
|
2331
|
|
|
|
|
|
|
[sql=>$last_sql], |
|
2332
|
|
|
|
|
|
|
[nesting=>$nesting], |
|
2333
|
|
|
|
|
|
|
[template=>$template], |
|
2334
|
|
|
|
|
|
|
@bind_nodes, |
|
2335
|
|
|
|
|
|
|
(map {[exec_arg=>$_]} @sql_args) |
|
2336
|
|
|
|
|
|
|
]]); |
|
2337
|
|
|
|
|
|
|
$stag->kids(@kids); |
|
2338
|
|
|
|
|
|
|
} |
|
2339
|
|
|
|
|
|
|
return $stag; |
|
2340
|
|
|
|
|
|
|
} |
|
2341
|
|
|
|
|
|
|
|
|
2342
|
|
|
|
|
|
|
sub prepare_stag { |
|
2343
|
|
|
|
|
|
|
my $self = shift; |
|
2344
|
|
|
|
|
|
|
my ($sql, $nesting, $bind, $template, $return_arrayref, $aliaspolicy) = |
|
2345
|
|
|
|
|
|
|
rearrange([qw(sql nesting bind template return_arrayref aliaspolicy)], @_); |
|
2346
|
|
|
|
|
|
|
|
|
2347
|
|
|
|
|
|
|
my $parser = $self->parser; |
|
2348
|
|
|
|
|
|
|
|
|
2349
|
|
|
|
|
|
|
my $sth; |
|
2350
|
|
|
|
|
|
|
my @exec_args = (); |
|
2351
|
|
|
|
|
|
|
if (ref($sql)) { |
|
2352
|
|
|
|
|
|
|
$template = $sql; |
|
2353
|
|
|
|
|
|
|
} |
|
2354
|
|
|
|
|
|
|
if ($template) { |
|
2355
|
|
|
|
|
|
|
if (!ref($template)) { |
|
2356
|
|
|
|
|
|
|
$template = $self->find_template($template); |
|
2357
|
|
|
|
|
|
|
} |
|
2358
|
|
|
|
|
|
|
($sql, @exec_args) = $template->get_sql_and_args($bind); |
|
2359
|
|
|
|
|
|
|
} |
|
2360
|
|
|
|
|
|
|
trace 0, "parsing_sql: $sql\n"; |
|
2361
|
|
|
|
|
|
|
|
|
2362
|
|
|
|
|
|
|
# PRE-parse SQL statement for stag-specific extensions |
|
2363
|
|
|
|
|
|
|
if ($sql =~ /(.*)\s+use\s+nesting\s*(.*)/si) { |
|
2364
|
|
|
|
|
|
|
my ($pre, $post) = ($1, $2); |
|
2365
|
|
|
|
|
|
|
my ($extracted, $remainder) = |
|
2366
|
|
|
|
|
|
|
extract_bracketed($post, '()'); |
|
2367
|
|
|
|
|
|
|
if ($nesting) { |
|
2368
|
|
|
|
|
|
|
$self->throw("nestings clash: $nesting vs $extracted"); |
|
2369
|
|
|
|
|
|
|
} |
|
2370
|
|
|
|
|
|
|
$nesting = Data::Stag->parsestr($extracted); |
|
2371
|
|
|
|
|
|
|
$sql = "$pre $remainder"; |
|
2372
|
|
|
|
|
|
|
} |
|
2373
|
|
|
|
|
|
|
|
|
2374
|
|
|
|
|
|
|
|
|
2375
|
|
|
|
|
|
|
# get the parsed SQL SELECT statement as a stag node |
|
2376
|
|
|
|
|
|
|
my $stmt = $parser->selectstmt($sql); |
|
2377
|
|
|
|
|
|
|
if (!$stmt) { |
|
2378
|
|
|
|
|
|
|
# there was some error parsing the SQL; |
|
2379
|
|
|
|
|
|
|
# DBI can probably give a better explanation. |
|
2380
|
|
|
|
|
|
|
eval { |
|
2381
|
|
|
|
|
|
|
my $sth = $self->dbh->prepare($sql); |
|
2382
|
|
|
|
|
|
|
|
|
2383
|
|
|
|
|
|
|
}; |
|
2384
|
|
|
|
|
|
|
if ($@) { |
|
2385
|
|
|
|
|
|
|
$self->throw("SQL ERROR:\n$@"); |
|
2386
|
|
|
|
|
|
|
} |
|
2387
|
|
|
|
|
|
|
# DBI accepted it - must be a bug in the DBStag grammar |
|
2388
|
|
|
|
|
|
|
$self->throw("I'm sorry but the SQL statement you gave does\n". |
|
2389
|
|
|
|
|
|
|
"not conform to the more limited subset of SQL\n". |
|
2390
|
|
|
|
|
|
|
"that DBStag supports. Please see the DBStag docs\n". |
|
2391
|
|
|
|
|
|
|
"for details.\n". |
|
2392
|
|
|
|
|
|
|
"\n". |
|
2393
|
|
|
|
|
|
|
"Remember to check you explicitly declare all aliases\n". |
|
2394
|
|
|
|
|
|
|
"using AS\n\n\nSQL:$sql"); |
|
2395
|
|
|
|
|
|
|
} |
|
2396
|
|
|
|
|
|
|
|
|
2397
|
|
|
|
|
|
|
|
|
2398
|
|
|
|
|
|
|
trace 0, "parsed_sql: $sql\n"; |
|
2399
|
|
|
|
|
|
|
# trace 0, $stmt->xml; |
|
2400
|
|
|
|
|
|
|
my $dbschema = $self->dbschema; |
|
2401
|
|
|
|
|
|
|
|
|
2402
|
|
|
|
|
|
|
$self->last_stmt($stmt); |
|
2403
|
|
|
|
|
|
|
|
|
2404
|
|
|
|
|
|
|
# stag node of FROM part of SQL |
|
2405
|
|
|
|
|
|
|
my $fromstruct = $stmt->get_from; |
|
2406
|
|
|
|
|
|
|
|
|
2407
|
|
|
|
|
|
|
# --- aliases --- |
|
2408
|
|
|
|
|
|
|
|
|
2409
|
|
|
|
|
|
|
# keep a hash of table aliases |
|
2410
|
|
|
|
|
|
|
# KEY: table alias |
|
2411
|
|
|
|
|
|
|
# VAL: base table |
|
2412
|
|
|
|
|
|
|
# for example, 'SELECT * FROM person AS p' |
|
2413
|
|
|
|
|
|
|
# will result in $alias_h = { p => person } |
|
2414
|
|
|
|
|
|
|
my $alias_h = {}; |
|
2415
|
|
|
|
|
|
|
|
|
2416
|
|
|
|
|
|
|
# build alias hash using FROM node |
|
2417
|
|
|
|
|
|
|
foreach my $sn ($fromstruct->subnodes) { |
|
2418
|
|
|
|
|
|
|
get_table_alias_map($sn, $alias_h); |
|
2419
|
|
|
|
|
|
|
} |
|
2420
|
|
|
|
|
|
|
|
|
2421
|
|
|
|
|
|
|
# as well as an alias hash map, |
|
2422
|
|
|
|
|
|
|
# keep an array of stag nodes representing all the aliases |
|
2423
|
|
|
|
|
|
|
my @aliases = (); |
|
2424
|
|
|
|
|
|
|
foreach my $alias (keys %$alias_h) { |
|
2425
|
|
|
|
|
|
|
push(@aliases, |
|
2426
|
|
|
|
|
|
|
Data::Stag->new(alias=>[ |
|
2427
|
|
|
|
|
|
|
[name=>$alias], |
|
2428
|
|
|
|
|
|
|
[table=>$alias_h->{$alias}->[0]] |
|
2429
|
|
|
|
|
|
|
])); |
|
2430
|
|
|
|
|
|
|
} |
|
2431
|
|
|
|
|
|
|
my $aliasstruct = Data::Stag->new(alias=>[@aliases]); |
|
2432
|
|
|
|
|
|
|
|
|
2433
|
|
|
|
|
|
|
# --- nestings --- |
|
2434
|
|
|
|
|
|
|
# |
|
2435
|
|
|
|
|
|
|
# the cartesian product that results from a SELECT can |
|
2436
|
|
|
|
|
|
|
# be turned into a tree - there is more than one tree to |
|
2437
|
|
|
|
|
|
|
# choose from; eg with "x NJ y NJ z" we can have trees: |
|
2438
|
|
|
|
|
|
|
# [x [y [z]]] |
|
2439
|
|
|
|
|
|
|
# [x [y z]] |
|
2440
|
|
|
|
|
|
|
# [z [x y]] |
|
2441
|
|
|
|
|
|
|
# etc |
|
2442
|
|
|
|
|
|
|
# |
|
2443
|
|
|
|
|
|
|
# the actual allowed nestings through the graph is constrained |
|
2444
|
|
|
|
|
|
|
# by the FK relationships; we do not utilise this yet (TODO!) |
|
2445
|
|
|
|
|
|
|
# later the user need only specify the root. for now they |
|
2446
|
|
|
|
|
|
|
# must specify the full nesting OR allow the bracket structure |
|
2447
|
|
|
|
|
|
|
# of the joins... |
|
2448
|
|
|
|
|
|
|
|
|
2449
|
|
|
|
|
|
|
# if the user did not explicitly supply a nesting, |
|
2450
|
|
|
|
|
|
|
# guess one from the bracket structure of the FROM |
|
2451
|
|
|
|
|
|
|
# clause (see rmake_nesting) |
|
2452
|
|
|
|
|
|
|
# [TODO: be more clever in guessing the nesting using FKs] |
|
2453
|
|
|
|
|
|
|
if (!$nesting) { |
|
2454
|
|
|
|
|
|
|
$nesting = Data::Stag->new(top=>1); |
|
2455
|
|
|
|
|
|
|
# my $cons = rmake_cons($fromstruct->data->[0], $nesting); |
|
2456
|
|
|
|
|
|
|
$nesting = rmake_nesting($fromstruct->data->[0]); |
|
2457
|
|
|
|
|
|
|
$nesting = Data::Stag->new(top=>[$nesting]); |
|
2458
|
|
|
|
|
|
|
trace(0, "\n\nNesting:\n%s\n\n", $nesting->xml) if $TRACE; |
|
2459
|
|
|
|
|
|
|
} |
|
2460
|
|
|
|
|
|
|
if ($nesting && !ref($nesting)) { |
|
2461
|
|
|
|
|
|
|
$nesting = Data::Stag->parsestr($nesting); |
|
2462
|
|
|
|
|
|
|
} |
|
2463
|
|
|
|
|
|
|
|
|
2464
|
|
|
|
|
|
|
# keep an array of named relations used in the query - |
|
2465
|
|
|
|
|
|
|
# the named relation is the alias if present; |
|
2466
|
|
|
|
|
|
|
# eg |
|
2467
|
|
|
|
|
|
|
# SELECT * FROM person AS p NATURAL JOIN department |
|
2468
|
|
|
|
|
|
|
# the named relations here are 'p' and 'department' |
|
2469
|
|
|
|
|
|
|
my @namedrelations = (); |
|
2470
|
|
|
|
|
|
|
$fromstruct->iterate(sub { |
|
2471
|
|
|
|
|
|
|
my $n = shift; |
|
2472
|
|
|
|
|
|
|
if ($n->element eq 'leaf') { |
|
2473
|
|
|
|
|
|
|
my $v = $n->sget_alias || $n->sget_name; |
|
2474
|
|
|
|
|
|
|
push(@namedrelations, $v) |
|
2475
|
|
|
|
|
|
|
} |
|
2476
|
|
|
|
|
|
|
}); |
|
2477
|
|
|
|
|
|
|
|
|
2478
|
|
|
|
|
|
|
# --- fetch columns --- |
|
2479
|
|
|
|
|
|
|
# |
|
2480
|
|
|
|
|
|
|
# loop through all the columns in the SELECT clause |
|
2481
|
|
|
|
|
|
|
# making them all of a standard form; eg dealing |
|
2482
|
|
|
|
|
|
|
# with functions and '*' wildcards appropriately |
|
2483
|
|
|
|
|
|
|
|
|
2484
|
|
|
|
|
|
|
my @col_aliases_ordered = (); |
|
2485
|
|
|
|
|
|
|
my @cols = |
|
2486
|
|
|
|
|
|
|
map { |
|
2487
|
|
|
|
|
|
|
# $_ iterator variable is over the columns |
|
2488
|
|
|
|
|
|
|
# specified in the SELECT part of the query; |
|
2489
|
|
|
|
|
|
|
# each column is represented as a stag node |
|
2490
|
|
|
|
|
|
|
|
|
2491
|
|
|
|
|
|
|
# column name |
|
2492
|
|
|
|
|
|
|
my $name = $_->get_name; |
|
2493
|
|
|
|
|
|
|
|
|
2494
|
|
|
|
|
|
|
# column alias, if exists |
|
2495
|
|
|
|
|
|
|
# eg in 'SELECT name AS n' the alias is 'n' |
|
2496
|
|
|
|
|
|
|
my $col_alias = $_->get_alias; |
|
2497
|
|
|
|
|
|
|
push(@col_aliases_ordered, $col_alias); |
|
2498
|
|
|
|
|
|
|
|
|
2499
|
|
|
|
|
|
|
# make the name the alias; prepend named relation if supplied. |
|
2500
|
|
|
|
|
|
|
# eg in 'SELECT person.name AS n' the name will become |
|
2501
|
|
|
|
|
|
|
# 'person.n' |
|
2502
|
|
|
|
|
|
|
if ($col_alias) { |
|
2503
|
|
|
|
|
|
|
$name = $col_alias; |
|
2504
|
|
|
|
|
|
|
if ($_->get_table) { |
|
2505
|
|
|
|
|
|
|
$name = $_->get_table . '.'. $name; |
|
2506
|
|
|
|
|
|
|
} |
|
2507
|
|
|
|
|
|
|
} |
|
2508
|
|
|
|
|
|
|
|
|
2509
|
|
|
|
|
|
|
my $func = $_->getnode('func'); |
|
2510
|
|
|
|
|
|
|
|
|
2511
|
|
|
|
|
|
|
# from here on determines returned value of the |
|
2512
|
|
|
|
|
|
|
# map iteration: |
|
2513
|
|
|
|
|
|
|
|
|
2514
|
|
|
|
|
|
|
if ($func) { |
|
2515
|
|
|
|
|
|
|
# a typical column node for a function looks like |
|
2516
|
|
|
|
|
|
|
# this: |
|
2517
|
|
|
|
|
|
|
# |
|
2518
|
|
|
|
|
|
|
# (col |
|
2519
|
|
|
|
|
|
|
# (func |
|
2520
|
|
|
|
|
|
|
# (name "somefunc") |
|
2521
|
|
|
|
|
|
|
# (args |
|
2522
|
|
|
|
|
|
|
# (col |
|
2523
|
|
|
|
|
|
|
# (name "x.foo") |
|
2524
|
|
|
|
|
|
|
# (table "x"))))) |
|
2525
|
|
|
|
|
|
|
# (alias "myname")) |
|
2526
|
|
|
|
|
|
|
# |
|
2527
|
|
|
|
|
|
|
# if a function is included, and the function |
|
2528
|
|
|
|
|
|
|
# return value is aliased, use that alias; |
|
2529
|
|
|
|
|
|
|
# otherwise ... |
|
2530
|
|
|
|
|
|
|
|
|
2531
|
|
|
|
|
|
|
my $funcname = $func->get_name; |
|
2532
|
|
|
|
|
|
|
# query the function stag node for the element |
|
2533
|
|
|
|
|
|
|
# 'col' |
|
2534
|
|
|
|
|
|
|
my ($col) = |
|
2535
|
|
|
|
|
|
|
$func->where('col', |
|
2536
|
|
|
|
|
|
|
sub {shift->get_table}); |
|
2537
|
|
|
|
|
|
|
my $table = $col_alias || $funcname; |
|
2538
|
|
|
|
|
|
|
if (!$col_alias) { |
|
2539
|
|
|
|
|
|
|
$col_alias = $funcname; |
|
2540
|
|
|
|
|
|
|
} |
|
2541
|
|
|
|
|
|
|
if ($col) { |
|
2542
|
|
|
|
|
|
|
$table = $col->get_table; |
|
2543
|
|
|
|
|
|
|
} |
|
2544
|
|
|
|
|
|
|
# if ($col_alias =~ /(\w+)__(\w+)/) { |
|
2545
|
|
|
|
|
|
|
# $table = $1; |
|
2546
|
|
|
|
|
|
|
# $col_alias = $2; |
|
2547
|
|
|
|
|
|
|
# } |
|
2548
|
|
|
|
|
|
|
$name = $table . '.' . $col_alias; |
|
2549
|
|
|
|
|
|
|
# return: |
|
2550
|
|
|
|
|
|
|
$name; |
|
2551
|
|
|
|
|
|
|
} |
|
2552
|
|
|
|
|
|
|
elsif ($name =~ /^(\w+)\.\*$/) { |
|
2553
|
|
|
|
|
|
|
# if the column name is of the form |
|
2554
|
|
|
|
|
|
|
# RELATION.*, then replace the * with |
|
2555
|
|
|
|
|
|
|
# all the actual columns from the base relation |
|
2556
|
|
|
|
|
|
|
# RELATION |
|
2557
|
|
|
|
|
|
|
# |
|
2558
|
|
|
|
|
|
|
# the final result will be TABLE.col1, TABLE.col2,... |
|
2559
|
|
|
|
|
|
|
|
|
2560
|
|
|
|
|
|
|
my $tn = $1; |
|
2561
|
|
|
|
|
|
|
my $tn_alias = $tn; |
|
2562
|
|
|
|
|
|
|
|
|
2563
|
|
|
|
|
|
|
# use base relation name to introspect schema |
|
2564
|
|
|
|
|
|
|
if ($alias_h->{$tn}) { |
|
2565
|
|
|
|
|
|
|
$tn = $alias_h->{$tn}->[0]; |
|
2566
|
|
|
|
|
|
|
} |
|
2567
|
|
|
|
|
|
|
my $tbl = $dbschema->table(lc($tn)); |
|
2568
|
|
|
|
|
|
|
if (!$tbl) { |
|
2569
|
|
|
|
|
|
|
confess("No such table as $tn"); |
|
2570
|
|
|
|
|
|
|
} |
|
2571
|
|
|
|
|
|
|
# introspect schema to get columns for this table |
|
2572
|
|
|
|
|
|
|
my @cns = $tbl->columns; |
|
2573
|
|
|
|
|
|
|
|
|
2574
|
|
|
|
|
|
|
# trace(0, Dumper $tbl) if $TRACE; |
|
2575
|
|
|
|
|
|
|
trace(0, "TN:$tn ALIAS:$tn_alias COLS:@cns") if $TRACE; |
|
2576
|
|
|
|
|
|
|
|
|
2577
|
|
|
|
|
|
|
# return: |
|
2578
|
|
|
|
|
|
|
map { "$tn_alias.$_" } @cns; |
|
2579
|
|
|
|
|
|
|
} |
|
2580
|
|
|
|
|
|
|
elsif ($name =~ /^\*$/) { |
|
2581
|
|
|
|
|
|
|
# if the column name is '*' (ie select all) |
|
2582
|
|
|
|
|
|
|
# then replace the * with |
|
2583
|
|
|
|
|
|
|
# all the actual columns from the base relations in |
|
2584
|
|
|
|
|
|
|
# the query (use FROM clause) |
|
2585
|
|
|
|
|
|
|
# |
|
2586
|
|
|
|
|
|
|
|
|
2587
|
|
|
|
|
|
|
my %got = (); |
|
2588
|
|
|
|
|
|
|
my @allcols = |
|
2589
|
|
|
|
|
|
|
map { |
|
2590
|
|
|
|
|
|
|
my $tn = $_; |
|
2591
|
|
|
|
|
|
|
my $baserelname = |
|
2592
|
|
|
|
|
|
|
$alias_h->{$tn} ? |
|
2593
|
|
|
|
|
|
|
$alias_h->{$tn}->[0] : $tn; |
|
2594
|
|
|
|
|
|
|
my $tbl = $dbschema->table(lc($baserelname)); |
|
2595
|
|
|
|
|
|
|
if (!$tbl) { |
|
2596
|
|
|
|
|
|
|
confess("Don't know anything about table:$tn\n". |
|
2597
|
|
|
|
|
|
|
"Maybe DBIx::DBSchema does not work for your DBMS?\n". |
|
2598
|
|
|
|
|
|
|
"If $tn is a view, you may need to modify DBIxLLDBSchema"); |
|
2599
|
|
|
|
|
|
|
} |
|
2600
|
|
|
|
|
|
|
my @cns = $tbl->columns; |
|
2601
|
|
|
|
|
|
|
# @cns = grep { !$got{$_}++ } @cns; |
|
2602
|
|
|
|
|
|
|
map { "$tn.$_"} @cns; |
|
2603
|
|
|
|
|
|
|
} @namedrelations; |
|
2604
|
|
|
|
|
|
|
|
|
2605
|
|
|
|
|
|
|
# This is a bit hacky; if the user specifies |
|
2606
|
|
|
|
|
|
|
# SELECT * FROM... then there is no way |
|
2607
|
|
|
|
|
|
|
# to introspect the actual column returned |
|
2608
|
|
|
|
|
|
|
# using DBI->selectall_arrayref |
|
2609
|
|
|
|
|
|
|
# |
|
2610
|
|
|
|
|
|
|
# maybe we should selectall_hashref |
|
2611
|
|
|
|
|
|
|
# instead? this is generally slower; also |
|
2612
|
|
|
|
|
|
|
# even if we get it with a hashref, the |
|
2613
|
|
|
|
|
|
|
# result can be ambiguous since DBI only |
|
2614
|
|
|
|
|
|
|
# gives us the colun names back |
|
2615
|
|
|
|
|
|
|
# |
|
2616
|
|
|
|
|
|
|
# to get round this we just replace the * |
|
2617
|
|
|
|
|
|
|
# in the user's query (ie in the actual SQL) |
|
2618
|
|
|
|
|
|
|
# with the full column list |
|
2619
|
|
|
|
|
|
|
my $replace = join(', ', @allcols); |
|
2620
|
|
|
|
|
|
|
# rewrite SQL statement; assum only one instance of |
|
2621
|
|
|
|
|
|
|
# string '*' in these cases |
|
2622
|
|
|
|
|
|
|
$sql =~ s/\*/$replace/; |
|
2623
|
|
|
|
|
|
|
# return: |
|
2624
|
|
|
|
|
|
|
@allcols; |
|
2625
|
|
|
|
|
|
|
} |
|
2626
|
|
|
|
|
|
|
else { |
|
2627
|
|
|
|
|
|
|
# no * wildcard in column, and not a function; |
|
2628
|
|
|
|
|
|
|
# just give back the node |
|
2629
|
|
|
|
|
|
|
|
|
2630
|
|
|
|
|
|
|
# return: |
|
2631
|
|
|
|
|
|
|
$name |
|
2632
|
|
|
|
|
|
|
} |
|
2633
|
|
|
|
|
|
|
} $stmt->sgetnode_cols->getnode_col; |
|
2634
|
|
|
|
|
|
|
|
|
2635
|
|
|
|
|
|
|
@cols = |
|
2636
|
|
|
|
|
|
|
map { |
|
2637
|
|
|
|
|
|
|
if (/(\w+)__(\w+)/) { |
|
2638
|
|
|
|
|
|
|
"$1.$2"; |
|
2639
|
|
|
|
|
|
|
} |
|
2640
|
|
|
|
|
|
|
else { |
|
2641
|
|
|
|
|
|
|
$_ |
|
2642
|
|
|
|
|
|
|
} |
|
2643
|
|
|
|
|
|
|
} @cols; |
|
2644
|
|
|
|
|
|
|
|
|
2645
|
|
|
|
|
|
|
# ---- end of column fetching --- |
|
2646
|
|
|
|
|
|
|
|
|
2647
|
|
|
|
|
|
|
trace(0, "COLS:@cols") if $TRACE; |
|
2648
|
|
|
|
|
|
|
|
|
2649
|
|
|
|
|
|
|
|
|
2650
|
|
|
|
|
|
|
|
|
2651
|
|
|
|
|
|
|
# --- execute SQL SELECT statement --- |
|
2652
|
|
|
|
|
|
|
if ($template) { |
|
2653
|
|
|
|
|
|
|
$sth = $template->cached_sth->{$sql}; |
|
2654
|
|
|
|
|
|
|
if (!$sth) { |
|
2655
|
|
|
|
|
|
|
$sth = $self->dbh->prepare($sql); |
|
2656
|
|
|
|
|
|
|
$template->cached_sth->{$sql} = $sth; |
|
2657
|
|
|
|
|
|
|
} |
|
2658
|
|
|
|
|
|
|
# ($sql, $sth, @exec_args) = |
|
2659
|
|
|
|
|
|
|
# $template->prepare($self->dbh, $bind); |
|
2660
|
|
|
|
|
|
|
} |
|
2661
|
|
|
|
|
|
|
else { |
|
2662
|
|
|
|
|
|
|
$sth = $self->dbh->prepare($sql); |
|
2663
|
|
|
|
|
|
|
} |
|
2664
|
|
|
|
|
|
|
my $sql_or_sth = $sql; |
|
2665
|
|
|
|
|
|
|
if ($sth) { |
|
2666
|
|
|
|
|
|
|
$sql_or_sth = $sth; |
|
2667
|
|
|
|
|
|
|
} |
|
2668
|
|
|
|
|
|
|
trace(0, "SQL:$sql") if $TRACE; |
|
2669
|
|
|
|
|
|
|
trace(0, "Exec_args: @exec_args") if $TRACE && @exec_args; |
|
2670
|
|
|
|
|
|
|
$self->last_sql_and_args([$sql, @exec_args]); |
|
2671
|
|
|
|
|
|
|
return |
|
2672
|
|
|
|
|
|
|
{ |
|
2673
|
|
|
|
|
|
|
sth=>$sth, |
|
2674
|
|
|
|
|
|
|
exec_args=>\@exec_args, |
|
2675
|
|
|
|
|
|
|
cols=>\@cols, |
|
2676
|
|
|
|
|
|
|
col_aliases_ordered=>\@col_aliases_ordered, |
|
2677
|
|
|
|
|
|
|
alias=>$aliasstruct, |
|
2678
|
|
|
|
|
|
|
nesting=>$nesting |
|
2679
|
|
|
|
|
|
|
}; |
|
2680
|
|
|
|
|
|
|
} |
|
2681
|
|
|
|
|
|
|
|
|
2682
|
|
|
|
|
|
|
|
|
2683
|
|
|
|
|
|
|
# ============================ |
|
2684
|
|
|
|
|
|
|
# get_table_alias_map(tablenode, alias hash) |
|
2685
|
|
|
|
|
|
|
# |
|
2686
|
|
|
|
|
|
|
# checks a tablenode (eg the stag representing |
|
2687
|
|
|
|
|
|
|
# a table construct in the FROM clause) and adds |
|
2688
|
|
|
|
|
|
|
# it to the alias hash if it specifies an alias |
|
2689
|
|
|
|
|
|
|
# ============================ |
|
2690
|
|
|
|
|
|
|
sub get_table_alias_map { |
|
2691
|
|
|
|
|
|
|
my $s = shift; |
|
2692
|
|
|
|
|
|
|
my $h = shift; |
|
2693
|
|
|
|
|
|
|
|
|
2694
|
|
|
|
|
|
|
# the FROM clause is natively stored as a binary tree |
|
2695
|
|
|
|
|
|
|
# (in order to group the joins by brackets) - recursively |
|
2696
|
|
|
|
|
|
|
# descend building the hash map |
|
2697
|
|
|
|
|
|
|
|
|
2698
|
|
|
|
|
|
|
if ($s->name eq 'leaf') { |
|
2699
|
|
|
|
|
|
|
my $alias = $s->get_alias; |
|
2700
|
|
|
|
|
|
|
if ($alias) { |
|
2701
|
|
|
|
|
|
|
$h->{$alias} = [$s->get_name]; |
|
2702
|
|
|
|
|
|
|
} |
|
2703
|
|
|
|
|
|
|
return ($s->get_name); |
|
2704
|
|
|
|
|
|
|
} |
|
2705
|
|
|
|
|
|
|
elsif ($s->name eq 'composite') { |
|
2706
|
|
|
|
|
|
|
my ($first, $second) = |
|
2707
|
|
|
|
|
|
|
($s->getnode_first, |
|
2708
|
|
|
|
|
|
|
$s->getnode_second); |
|
2709
|
|
|
|
|
|
|
my $alias = $s->get_alias; |
|
2710
|
|
|
|
|
|
|
my @sn = ($first->subnodes, $second->subnodes); |
|
2711
|
|
|
|
|
|
|
my @subtbls = map { |
|
2712
|
|
|
|
|
|
|
get_table_alias_map($_, $h), |
|
2713
|
|
|
|
|
|
|
} @sn; |
|
2714
|
|
|
|
|
|
|
if ($alias) { |
|
2715
|
|
|
|
|
|
|
$h->{$alias} = [@subtbls]; |
|
2716
|
|
|
|
|
|
|
} |
|
2717
|
|
|
|
|
|
|
return @subtbls; |
|
2718
|
|
|
|
|
|
|
} |
|
2719
|
|
|
|
|
|
|
else { |
|
2720
|
|
|
|
|
|
|
confess $s->name; |
|
2721
|
|
|
|
|
|
|
} |
|
2722
|
|
|
|
|
|
|
} |
|
2723
|
|
|
|
|
|
|
|
|
2724
|
|
|
|
|
|
|
# ============================ |
|
2725
|
|
|
|
|
|
|
# reconstruct(schema, rows, top, cols, constraints, nesting, aliasstruct) |
|
2726
|
|
|
|
|
|
|
# |
|
2727
|
|
|
|
|
|
|
# mainly called by: selectall_stag(...) |
|
2728
|
|
|
|
|
|
|
# |
|
2729
|
|
|
|
|
|
|
# takes an array of rows (ie the result of an SQL query, probably |
|
2730
|
|
|
|
|
|
|
# involving JOINs, which is a denormalised relation) and |
|
2731
|
|
|
|
|
|
|
# decomposes this relation into a tree structure |
|
2732
|
|
|
|
|
|
|
# |
|
2733
|
|
|
|
|
|
|
# in order to do this, it requires schema information, and a nesting |
|
2734
|
|
|
|
|
|
|
# through the implicit result graph to build a tree |
|
2735
|
|
|
|
|
|
|
# ============================ |
|
2736
|
|
|
|
|
|
|
sub reconstruct { |
|
2737
|
|
|
|
|
|
|
my $self = shift; |
|
2738
|
|
|
|
|
|
|
my $tree = Data::Stag->new(); |
|
2739
|
|
|
|
|
|
|
my ($schema, # OPTIONAL - meta data on relation |
|
2740
|
|
|
|
|
|
|
$rows, # REQUIRED - relation R - array-of-array |
|
2741
|
|
|
|
|
|
|
$top, # OPTIONAL - root node name |
|
2742
|
|
|
|
|
|
|
$cols, # REQUIRED - array of stag nodes per column of R |
|
2743
|
|
|
|
|
|
|
$constraints, # NOT USED!!! |
|
2744
|
|
|
|
|
|
|
$nesting, # REQUIRED - tree representing decomposed schema |
|
2745
|
|
|
|
|
|
|
$aliasstruct, # OPTIONAL - renaming of columns in R |
|
2746
|
|
|
|
|
|
|
$aliaspolicy) = |
|
2747
|
|
|
|
|
|
|
rearrange([qw(schema |
|
2748
|
|
|
|
|
|
|
rows |
|
2749
|
|
|
|
|
|
|
top |
|
2750
|
|
|
|
|
|
|
cols |
|
2751
|
|
|
|
|
|
|
constraints |
|
2752
|
|
|
|
|
|
|
nesting |
|
2753
|
|
|
|
|
|
|
alias |
|
2754
|
|
|
|
|
|
|
aliaspolicy)], @_); |
|
2755
|
|
|
|
|
|
|
|
|
2756
|
|
|
|
|
|
|
$aliaspolicy = 'nest' unless $aliaspolicy; |
|
2757
|
|
|
|
|
|
|
|
|
2758
|
|
|
|
|
|
|
# --- get the schema --- |
|
2759
|
|
|
|
|
|
|
# |
|
2760
|
|
|
|
|
|
|
# $schema is a stag representing the schema |
|
2761
|
|
|
|
|
|
|
# of the input releation R (not the schema of |
|
2762
|
|
|
|
|
|
|
# the db that produced it.... hmm, this could |
|
2763
|
|
|
|
|
|
|
# be misleading) |
|
2764
|
|
|
|
|
|
|
# |
|
2765
|
|
|
|
|
|
|
# it conforms to the following stag-struct: |
|
2766
|
|
|
|
|
|
|
# |
|
2767
|
|
|
|
|
|
|
#'(schema |
|
2768
|
|
|
|
|
|
|
# (top? "RECORDSET-ELEMENT-NAME") |
|
2769
|
|
|
|
|
|
|
# (cols? |
|
2770
|
|
|
|
|
|
|
# (col+ |
|
2771
|
|
|
|
|
|
|
# (relation "RELATION-NAME") |
|
2772
|
|
|
|
|
|
|
# (name "COLUMN-NAME") |
|
2773
|
|
|
|
|
|
|
# )) |
|
2774
|
|
|
|
|
|
|
# (nesting? |
|
2775
|
|
|
|
|
|
|
# (* "NESTING-TREE"))) |
|
2776
|
|
|
|
|
|
|
# |
|
2777
|
|
|
|
|
|
|
# each column represents the |
|
2778
|
|
|
|
|
|
|
|
|
2779
|
|
|
|
|
|
|
if (!$schema) { |
|
2780
|
|
|
|
|
|
|
$schema = $tree->new(schema=>[]); |
|
2781
|
|
|
|
|
|
|
} |
|
2782
|
|
|
|
|
|
|
if (!ref($schema)) { |
|
2783
|
|
|
|
|
|
|
# it is a string - parse it |
|
2784
|
|
|
|
|
|
|
# (assume sxpr) |
|
2785
|
|
|
|
|
|
|
$schema = $tree->from('sxprstr', $schema); |
|
2786
|
|
|
|
|
|
|
} |
|
2787
|
|
|
|
|
|
|
|
|
2788
|
|
|
|
|
|
|
# TOP - this is the element name |
|
2789
|
|
|
|
|
|
|
# to group the structs under. |
|
2790
|
|
|
|
|
|
|
# [override if specified explicitly] |
|
2791
|
|
|
|
|
|
|
if ($top) { |
|
2792
|
|
|
|
|
|
|
stag_set($schema, 'top', $top); |
|
2793
|
|
|
|
|
|
|
} |
|
2794
|
|
|
|
|
|
|
# $top = $schema->get_top || "set"; |
|
2795
|
|
|
|
|
|
|
if (!$top) { |
|
2796
|
|
|
|
|
|
|
if ($nesting) { |
|
2797
|
|
|
|
|
|
|
# use first element in nesting |
|
2798
|
|
|
|
|
|
|
$top = $nesting->element; |
|
2799
|
|
|
|
|
|
|
} |
|
2800
|
|
|
|
|
|
|
else { |
|
2801
|
|
|
|
|
|
|
$top = 'set'; |
|
2802
|
|
|
|
|
|
|
} |
|
2803
|
|
|
|
|
|
|
} |
|
2804
|
|
|
|
|
|
|
my $topstruct = $tree->new($top, []); |
|
2805
|
|
|
|
|
|
|
|
|
2806
|
|
|
|
|
|
|
# COLS - this is the columns (attribute names) |
|
2807
|
|
|
|
|
|
|
# in the order they appear |
|
2808
|
|
|
|
|
|
|
# [override if specified explicitly] |
|
2809
|
|
|
|
|
|
|
if ($cols) { |
|
2810
|
|
|
|
|
|
|
my @ncols = |
|
2811
|
|
|
|
|
|
|
map { |
|
2812
|
|
|
|
|
|
|
if (ref($_)) { |
|
2813
|
|
|
|
|
|
|
$_ |
|
2814
|
|
|
|
|
|
|
} |
|
2815
|
|
|
|
|
|
|
else { |
|
2816
|
|
|
|
|
|
|
# presume it's a string |
|
2817
|
|
|
|
|
|
|
# format = RELATION.ATTRIBUTENAME |
|
2818
|
|
|
|
|
|
|
if (/(\w+)\.(\w+)/) { |
|
2819
|
|
|
|
|
|
|
$tree->new(col=>[ |
|
2820
|
|
|
|
|
|
|
[relation=>$1], |
|
2821
|
|
|
|
|
|
|
[name=>$2]]); |
|
2822
|
|
|
|
|
|
|
} |
|
2823
|
|
|
|
|
|
|
elsif (/(\w+)/) { |
|
2824
|
|
|
|
|
|
|
confess("Not implemented yet - must specify tbl for $_"); |
|
2825
|
|
|
|
|
|
|
$tree->new(col=>[ |
|
2826
|
|
|
|
|
|
|
[relation=>'unknown'], |
|
2827
|
|
|
|
|
|
|
[name=>$2]]); |
|
2828
|
|
|
|
|
|
|
} |
|
2829
|
|
|
|
|
|
|
else { |
|
2830
|
|
|
|
|
|
|
confess "I am confused by this column: $_"; |
|
2831
|
|
|
|
|
|
|
} |
|
2832
|
|
|
|
|
|
|
} |
|
2833
|
|
|
|
|
|
|
} @$cols; |
|
2834
|
|
|
|
|
|
|
$schema->set_cols([@ncols]); |
|
2835
|
|
|
|
|
|
|
} |
|
2836
|
|
|
|
|
|
|
|
|
2837
|
|
|
|
|
|
|
|
|
2838
|
|
|
|
|
|
|
# NESTING - this is the tree structure in |
|
2839
|
|
|
|
|
|
|
# which the relations are structured |
|
2840
|
|
|
|
|
|
|
# [override if specified explicitly] |
|
2841
|
|
|
|
|
|
|
if ($nesting) { |
|
2842
|
|
|
|
|
|
|
if (ref($nesting)) { |
|
2843
|
|
|
|
|
|
|
} |
|
2844
|
|
|
|
|
|
|
else { |
|
2845
|
|
|
|
|
|
|
$nesting = $tree->from('sxprstr', $nesting); |
|
2846
|
|
|
|
|
|
|
} |
|
2847
|
|
|
|
|
|
|
$schema->set_nesting([$nesting]); |
|
2848
|
|
|
|
|
|
|
} |
|
2849
|
|
|
|
|
|
|
else { |
|
2850
|
|
|
|
|
|
|
$nesting = $schema->sgetnode_nesting; |
|
2851
|
|
|
|
|
|
|
} |
|
2852
|
|
|
|
|
|
|
if (!$nesting) { |
|
2853
|
|
|
|
|
|
|
confess("no nesting!"); |
|
2854
|
|
|
|
|
|
|
} |
|
2855
|
|
|
|
|
|
|
|
|
2856
|
|
|
|
|
|
|
# --- alias structure --- |
|
2857
|
|
|
|
|
|
|
# |
|
2858
|
|
|
|
|
|
|
# use this to get a hash map of alias => baserelation |
|
2859
|
|
|
|
|
|
|
|
|
2860
|
|
|
|
|
|
|
($aliasstruct) = $schema->getnode_aliases unless $aliasstruct; |
|
2861
|
|
|
|
|
|
|
if ($aliasstruct && !ref($aliasstruct)) { |
|
2862
|
|
|
|
|
|
|
$aliasstruct = $tree->from('sxprstr', $aliasstruct); |
|
2863
|
|
|
|
|
|
|
} |
|
2864
|
|
|
|
|
|
|
my @aliases = (); |
|
2865
|
|
|
|
|
|
|
if ($aliasstruct && $aliaspolicy !~ /^a/i) { |
|
2866
|
|
|
|
|
|
|
@aliases = $aliasstruct->getnode_alias; |
|
2867
|
|
|
|
|
|
|
} |
|
2868
|
|
|
|
|
|
|
my %alias2baserelation = |
|
2869
|
|
|
|
|
|
|
map { |
|
2870
|
|
|
|
|
|
|
$_->sget_name => $_->sget_table |
|
2871
|
|
|
|
|
|
|
} @aliases; |
|
2872
|
|
|
|
|
|
|
|
|
2873
|
|
|
|
|
|
|
# column headings; (ie all columns in R) |
|
2874
|
|
|
|
|
|
|
my @cols = $schema->sgetnode_cols->getnode_col(); |
|
2875
|
|
|
|
|
|
|
|
|
2876
|
|
|
|
|
|
|
# --- primary key info --- |
|
2877
|
|
|
|
|
|
|
|
|
2878
|
|
|
|
|
|
|
# set the primary key for each relation (one per relation); |
|
2879
|
|
|
|
|
|
|
# the default is *all* the columns in that relation |
|
2880
|
|
|
|
|
|
|
my %pkey_by_relationname = (); # eg {person => [person_id] |
|
2881
|
|
|
|
|
|
|
my %cols_by_relationname = (); # eg {person => [person_id, fname, lname] |
|
2882
|
|
|
|
|
|
|
|
|
2883
|
|
|
|
|
|
|
# loop through all columns in R, setting above hash maps |
|
2884
|
|
|
|
|
|
|
foreach my $col (@cols) { |
|
2885
|
|
|
|
|
|
|
|
|
2886
|
|
|
|
|
|
|
# the stag struct for each $col looks like this: |
|
2887
|
|
|
|
|
|
|
# |
|
2888
|
|
|
|
|
|
|
# (col+ |
|
2889
|
|
|
|
|
|
|
# (relation "RELATION-NAME") |
|
2890
|
|
|
|
|
|
|
# (name "COLUMN-NAME") |
|
2891
|
|
|
|
|
|
|
# )) |
|
2892
|
|
|
|
|
|
|
|
|
2893
|
|
|
|
|
|
|
my $relationname = $col->get_relation; |
|
2894
|
|
|
|
|
|
|
my $colname = $col->get_name; |
|
2895
|
|
|
|
|
|
|
|
|
2896
|
|
|
|
|
|
|
# pkey defaults to all columns in a relation |
|
2897
|
|
|
|
|
|
|
# (we may override this later) |
|
2898
|
|
|
|
|
|
|
$pkey_by_relationname{$relationname} = [] |
|
2899
|
|
|
|
|
|
|
unless $pkey_by_relationname{$relationname}; |
|
2900
|
|
|
|
|
|
|
push(@{$pkey_by_relationname{$relationname}}, |
|
2901
|
|
|
|
|
|
|
$colname); |
|
2902
|
|
|
|
|
|
|
|
|
2903
|
|
|
|
|
|
|
# all columns in a relation |
|
2904
|
|
|
|
|
|
|
# (note: same as default PK) |
|
2905
|
|
|
|
|
|
|
$cols_by_relationname{$relationname} = [] |
|
2906
|
|
|
|
|
|
|
unless $cols_by_relationname{$relationname}; |
|
2907
|
|
|
|
|
|
|
push(@{$cols_by_relationname{$relationname}}, |
|
2908
|
|
|
|
|
|
|
$colname); |
|
2909
|
|
|
|
|
|
|
} |
|
2910
|
|
|
|
|
|
|
my @relationnames = keys %pkey_by_relationname; |
|
2911
|
|
|
|
|
|
|
|
|
2912
|
|
|
|
|
|
|
# override PK if explicitly set as a constraint |
|
2913
|
|
|
|
|
|
|
my @pks = $schema->findnode("primarykey"); |
|
2914
|
|
|
|
|
|
|
foreach my $pk (@pks) { |
|
2915
|
|
|
|
|
|
|
|
|
2916
|
|
|
|
|
|
|
# $pk looks like this: |
|
2917
|
|
|
|
|
|
|
# |
|
2918
|
|
|
|
|
|
|
# '(primarykey |
|
2919
|
|
|
|
|
|
|
# (relation "R-NAME") |
|
2920
|
|
|
|
|
|
|
# (col+ "COL-NAME")) |
|
2921
|
|
|
|
|
|
|
|
|
2922
|
|
|
|
|
|
|
my $relationname = $pk->get_relation; |
|
2923
|
|
|
|
|
|
|
my @cols = $pk->get_col; |
|
2924
|
|
|
|
|
|
|
|
|
2925
|
|
|
|
|
|
|
# the hash %pkey_by_relationname should |
|
2926
|
|
|
|
|
|
|
# be keyed by the named relations, not the |
|
2927
|
|
|
|
|
|
|
# base relations |
|
2928
|
|
|
|
|
|
|
my @aliasnames = |
|
2929
|
|
|
|
|
|
|
grep { |
|
2930
|
|
|
|
|
|
|
$alias2baserelation{$_} eq $relationname |
|
2931
|
|
|
|
|
|
|
} keys %alias2baserelation; |
|
2932
|
|
|
|
|
|
|
|
|
2933
|
|
|
|
|
|
|
# relation is not aliased |
|
2934
|
|
|
|
|
|
|
if (!@aliasnames) { |
|
2935
|
|
|
|
|
|
|
@aliasnames = ($relationname); |
|
2936
|
|
|
|
|
|
|
} |
|
2937
|
|
|
|
|
|
|
foreach (@aliasnames) { |
|
2938
|
|
|
|
|
|
|
$pkey_by_relationname{$_} = [@cols]; |
|
2939
|
|
|
|
|
|
|
} |
|
2940
|
|
|
|
|
|
|
} |
|
2941
|
|
|
|
|
|
|
|
|
2942
|
|
|
|
|
|
|
# ------------------ |
|
2943
|
|
|
|
|
|
|
# |
|
2944
|
|
|
|
|
|
|
# loop through denormalised rows, |
|
2945
|
|
|
|
|
|
|
# putting the columns into their |
|
2946
|
|
|
|
|
|
|
# respecive relations |
|
2947
|
|
|
|
|
|
|
# |
|
2948
|
|
|
|
|
|
|
# eg |
|
2949
|
|
|
|
|
|
|
# |
|
2950
|
|
|
|
|
|
|
# <----- a -----> <-- b --> |
|
2951
|
|
|
|
|
|
|
# a.1 a.2 a.3 b.1 b.2 |
|
2952
|
|
|
|
|
|
|
# |
|
2953
|
|
|
|
|
|
|
# algorithm: |
|
2954
|
|
|
|
|
|
|
# use nesting/tree to walk through |
|
2955
|
|
|
|
|
|
|
# |
|
2956
|
|
|
|
|
|
|
# ------------------ |
|
2957
|
|
|
|
|
|
|
|
|
2958
|
|
|
|
|
|
|
#~~~ keep a hash of all relations by their primary key vals |
|
2959
|
|
|
|
|
|
|
#~~~ outer key = relationname |
|
2960
|
|
|
|
|
|
|
#~~~ inner key = pkval |
|
2961
|
|
|
|
|
|
|
#~~~ hash val = relation structure |
|
2962
|
|
|
|
|
|
|
#~~~ my %all_relation_hh = (); |
|
2963
|
|
|
|
|
|
|
#~~~ foreach my $relationname (@relationnames) { |
|
2964
|
|
|
|
|
|
|
#~~~ $all_relation_hh{$relationname} = {}; |
|
2965
|
|
|
|
|
|
|
#~~~ } |
|
2966
|
|
|
|
|
|
|
|
|
2967
|
|
|
|
|
|
|
#~~~ keep an array of all relations |
|
2968
|
|
|
|
|
|
|
#~~~ outer key = relationname |
|
2969
|
|
|
|
|
|
|
#~~~ inner array = ordered list of relations |
|
2970
|
|
|
|
|
|
|
#~~~ my %all_relation_ah = (); |
|
2971
|
|
|
|
|
|
|
#~~~ foreach my $relationname (keys %pkey_by_relationname) { |
|
2972
|
|
|
|
|
|
|
#~~~ $all_relation_ah{$relationname} = []; |
|
2973
|
|
|
|
|
|
|
#~~~ } |
|
2974
|
|
|
|
|
|
|
|
|
2975
|
|
|
|
|
|
|
# start at top of nesting tree |
|
2976
|
|
|
|
|
|
|
# |
|
2977
|
|
|
|
|
|
|
# a typical nesting tree may look like this: |
|
2978
|
|
|
|
|
|
|
# |
|
2979
|
|
|
|
|
|
|
# '(tableA |
|
2980
|
|
|
|
|
|
|
# (tableB "1") |
|
2981
|
|
|
|
|
|
|
# (tableC |
|
2982
|
|
|
|
|
|
|
# (tableD "1"))) |
|
2983
|
|
|
|
|
|
|
# |
|
2984
|
|
|
|
|
|
|
# terminals ie "1" are ignored |
|
2985
|
|
|
|
|
|
|
|
|
2986
|
|
|
|
|
|
|
my ($first_in_nesting) = $nesting->subnodes; |
|
2987
|
|
|
|
|
|
|
if (!$first_in_nesting) { |
|
2988
|
|
|
|
|
|
|
$first_in_nesting = $nesting; |
|
2989
|
|
|
|
|
|
|
} |
|
2990
|
|
|
|
|
|
|
my $fipname = $first_in_nesting ? $first_in_nesting->name : ''; |
|
2991
|
|
|
|
|
|
|
|
|
2992
|
|
|
|
|
|
|
# recursive hash representing tree |
|
2993
|
|
|
|
|
|
|
# |
|
2994
|
|
|
|
|
|
|
# $record = |
|
2995
|
|
|
|
|
|
|
# {child_h => { |
|
2996
|
|
|
|
|
|
|
# $relation_name* => { |
|
2997
|
|
|
|
|
|
|
# $pk_val => $record |
|
2998
|
|
|
|
|
|
|
# } |
|
2999
|
|
|
|
|
|
|
# }, |
|
3000
|
|
|
|
|
|
|
# struct => $stag_obj |
|
3001
|
|
|
|
|
|
|
# } |
|
3002
|
|
|
|
|
|
|
# |
|
3003
|
|
|
|
|
|
|
# this is recursively constructed using the make_a_tree() method |
|
3004
|
|
|
|
|
|
|
# below. the nesting tree (see above) is traversed depth first, |
|
3005
|
|
|
|
|
|
|
# constructing both the child_h hash and the resulting Stag |
|
3006
|
|
|
|
|
|
|
# structure. |
|
3007
|
|
|
|
|
|
|
|
|
3008
|
|
|
|
|
|
|
my $top_record_h = |
|
3009
|
|
|
|
|
|
|
{ |
|
3010
|
|
|
|
|
|
|
child_h=>{ $fipname ? ($fipname=>{}) : () }, |
|
3011
|
|
|
|
|
|
|
struct=>$topstruct |
|
3012
|
|
|
|
|
|
|
}; |
|
3013
|
|
|
|
|
|
|
# loop through rows in R |
|
3014
|
|
|
|
|
|
|
foreach my $row (@$rows) { |
|
3015
|
|
|
|
|
|
|
my @colvals = @$row; |
|
3016
|
|
|
|
|
|
|
|
|
3017
|
|
|
|
|
|
|
# keep a record of all table names in |
|
3018
|
|
|
|
|
|
|
# this row from R |
|
3019
|
|
|
|
|
|
|
my %current_relation_h = (); |
|
3020
|
|
|
|
|
|
|
for (my $i=0; $i<@cols; $i++) { |
|
3021
|
|
|
|
|
|
|
my $colval = $colvals[$i]; |
|
3022
|
|
|
|
|
|
|
my $col = $cols[$i]; |
|
3023
|
|
|
|
|
|
|
my $relationname = $col->get_relation; |
|
3024
|
|
|
|
|
|
|
my $colname = $col->get_name; |
|
3025
|
|
|
|
|
|
|
my $relation = $current_relation_h{$relationname}; |
|
3026
|
|
|
|
|
|
|
if (!$relation) { |
|
3027
|
|
|
|
|
|
|
$relation = {}; |
|
3028
|
|
|
|
|
|
|
$current_relation_h{$relationname} = $relation; |
|
3029
|
|
|
|
|
|
|
} |
|
3030
|
|
|
|
|
|
|
$relation->{$colname} = $colval; |
|
3031
|
|
|
|
|
|
|
} |
|
3032
|
|
|
|
|
|
|
|
|
3033
|
|
|
|
|
|
|
# print "ROW=@$row\n"; |
|
3034
|
|
|
|
|
|
|
# dmp(\%pkey_by_relationname); |
|
3035
|
|
|
|
|
|
|
# dmp($top_record_h); |
|
3036
|
|
|
|
|
|
|
|
|
3037
|
|
|
|
|
|
|
# we now have a hash of hashes - |
|
3038
|
|
|
|
|
|
|
# outer keyed by relation id |
|
3039
|
|
|
|
|
|
|
# inner keyed by relation attribute name |
|
3040
|
|
|
|
|
|
|
|
|
3041
|
|
|
|
|
|
|
# traverse depth first down nesting; |
|
3042
|
|
|
|
|
|
|
# add new nodes as children of the parent |
|
3043
|
|
|
|
|
|
|
$self->make_a_tree($tree, |
|
3044
|
|
|
|
|
|
|
$top_record_h, |
|
3045
|
|
|
|
|
|
|
$first_in_nesting, |
|
3046
|
|
|
|
|
|
|
\%current_relation_h, |
|
3047
|
|
|
|
|
|
|
\%pkey_by_relationname, |
|
3048
|
|
|
|
|
|
|
\%cols_by_relationname, |
|
3049
|
|
|
|
|
|
|
\%alias2baserelation, |
|
3050
|
|
|
|
|
|
|
$aliaspolicy); |
|
3051
|
|
|
|
|
|
|
} |
|
3052
|
|
|
|
|
|
|
return $topstruct; |
|
3053
|
|
|
|
|
|
|
} |
|
3054
|
|
|
|
|
|
|
*norm = \&reconstruct; |
|
3055
|
|
|
|
|
|
|
*normalise = \&reconstruct; |
|
3056
|
|
|
|
|
|
|
*normalize = \&reconstruct; |
|
3057
|
|
|
|
|
|
|
|
|
3058
|
|
|
|
|
|
|
# ============================ |
|
3059
|
|
|
|
|
|
|
# make_a_tree(...) RECURSIVE |
|
3060
|
|
|
|
|
|
|
# |
|
3061
|
|
|
|
|
|
|
# called by: reconstruct(...) |
|
3062
|
|
|
|
|
|
|
# |
|
3063
|
|
|
|
|
|
|
# ============================ |
|
3064
|
|
|
|
|
|
|
sub make_a_tree { |
|
3065
|
|
|
|
|
|
|
my $self = shift; |
|
3066
|
|
|
|
|
|
|
my $tree = shift; |
|
3067
|
|
|
|
|
|
|
my $parent_rec_h = shift; |
|
3068
|
|
|
|
|
|
|
my $node = shift; |
|
3069
|
|
|
|
|
|
|
my %current_relation_h= %{shift ||{}}; |
|
3070
|
|
|
|
|
|
|
my %pkey_by_relationname = %{shift ||{}}; |
|
3071
|
|
|
|
|
|
|
my %cols_by_relationname = %{shift ||{}}; |
|
3072
|
|
|
|
|
|
|
my %alias2baserelation = %{shift ||{}}; |
|
3073
|
|
|
|
|
|
|
my $aliaspolicy = shift; |
|
3074
|
|
|
|
|
|
|
|
|
3075
|
|
|
|
|
|
|
my $relationname = $node->name; |
|
3076
|
|
|
|
|
|
|
my $relationrec = $current_relation_h{$relationname}; |
|
3077
|
|
|
|
|
|
|
my $pkcols = $pkey_by_relationname{$relationname}; |
|
3078
|
|
|
|
|
|
|
my $rec; # this is the next node down in the hash tree |
|
3079
|
|
|
|
|
|
|
|
|
3080
|
|
|
|
|
|
|
if (!$pkcols || !@$pkcols) { |
|
3081
|
|
|
|
|
|
|
# if we have no columns for a particular part of |
|
3082
|
|
|
|
|
|
|
# the nesting through the relation, it means it |
|
3083
|
|
|
|
|
|
|
# was ommitted from the select clause - just skip |
|
3084
|
|
|
|
|
|
|
# this part of the nesting. |
|
3085
|
|
|
|
|
|
|
# |
|
3086
|
|
|
|
|
|
|
# for example: SELECT a.*, b.* FROM a NJ a_to_b NJ b |
|
3087
|
|
|
|
|
|
|
# the default nesting will be: [a [a_to_b [b]]] |
|
3088
|
|
|
|
|
|
|
# the relation R will have columns: |
|
3089
|
|
|
|
|
|
|
# a.c1 a.c2 b.c1 b.c2 |
|
3090
|
|
|
|
|
|
|
# |
|
3091
|
|
|
|
|
|
|
# we want to build a resulting structure like this: |
|
3092
|
|
|
|
|
|
|
# (a |
|
3093
|
|
|
|
|
|
|
# (c1 "x") (c2 "y") |
|
3094
|
|
|
|
|
|
|
# (b |
|
3095
|
|
|
|
|
|
|
# (c1 "a") (c2 "b"))) |
|
3096
|
|
|
|
|
|
|
# |
|
3097
|
|
|
|
|
|
|
# so we just miss out a_to_b in the nesting, because it |
|
3098
|
|
|
|
|
|
|
# has no columns in the relation R. |
|
3099
|
|
|
|
|
|
|
$rec = $parent_rec_h; |
|
3100
|
|
|
|
|
|
|
} |
|
3101
|
|
|
|
|
|
|
else { |
|
3102
|
|
|
|
|
|
|
|
|
3103
|
|
|
|
|
|
|
my $pkval = |
|
3104
|
|
|
|
|
|
|
CORE::join("\t", |
|
3105
|
|
|
|
|
|
|
map { |
|
3106
|
|
|
|
|
|
|
esctab($relationrec->{$_} || '') |
|
3107
|
|
|
|
|
|
|
} @$pkcols); |
|
3108
|
|
|
|
|
|
|
|
|
3109
|
|
|
|
|
|
|
$rec = $parent_rec_h->{child_h}->{$relationname}->{$pkval}; |
|
3110
|
|
|
|
|
|
|
|
|
3111
|
|
|
|
|
|
|
if (!$rec) { |
|
3112
|
|
|
|
|
|
|
my $relationcols = $cols_by_relationname{$relationname}; |
|
3113
|
|
|
|
|
|
|
my $has_non_null_val = grep {defined($relationrec->{$_})} @$relationcols; |
|
3114
|
|
|
|
|
|
|
return unless $has_non_null_val; |
|
3115
|
|
|
|
|
|
|
my $relationstruct = |
|
3116
|
|
|
|
|
|
|
$tree->new($relationname=>[ |
|
3117
|
|
|
|
|
|
|
map { |
|
3118
|
|
|
|
|
|
|
defined($relationrec->{$_}) ? [$_ => $relationrec->{$_}] : () |
|
3119
|
|
|
|
|
|
|
} @$relationcols |
|
3120
|
|
|
|
|
|
|
]); |
|
3121
|
|
|
|
|
|
|
my $parent_relationstruct = $parent_rec_h->{struct}; |
|
3122
|
|
|
|
|
|
|
if (!$parent_relationstruct) { |
|
3123
|
|
|
|
|
|
|
confess("no parent for $relationname"); |
|
3124
|
|
|
|
|
|
|
} |
|
3125
|
|
|
|
|
|
|
|
|
3126
|
|
|
|
|
|
|
# if we have an aliased relation, add an extra |
|
3127
|
|
|
|
|
|
|
# level of nesting |
|
3128
|
|
|
|
|
|
|
my $baserelation = $alias2baserelation{$relationname}; |
|
3129
|
|
|
|
|
|
|
if ($baserelation) { |
|
3130
|
|
|
|
|
|
|
|
|
3131
|
|
|
|
|
|
|
# $aliaspolicy eq 'nest' or 't' |
|
3132
|
|
|
|
|
|
|
# nest base relations inside an alias node |
|
3133
|
|
|
|
|
|
|
# OR use table name in place of alias name |
|
3134
|
|
|
|
|
|
|
if ($aliaspolicy =~ /^t/i) { |
|
3135
|
|
|
|
|
|
|
stag_add($parent_relationstruct, |
|
3136
|
|
|
|
|
|
|
$baserelation, |
|
3137
|
|
|
|
|
|
|
$relationstruct->data); |
|
3138
|
|
|
|
|
|
|
} |
|
3139
|
|
|
|
|
|
|
else { |
|
3140
|
|
|
|
|
|
|
# nest |
|
3141
|
|
|
|
|
|
|
my $baserelationstruct = |
|
3142
|
|
|
|
|
|
|
Data::Stag->new($baserelation => |
|
3143
|
|
|
|
|
|
|
$relationstruct->data); |
|
3144
|
|
|
|
|
|
|
stag_add($parent_relationstruct, |
|
3145
|
|
|
|
|
|
|
$relationname, |
|
3146
|
|
|
|
|
|
|
[$baserelationstruct]); |
|
3147
|
|
|
|
|
|
|
} |
|
3148
|
|
|
|
|
|
|
} else { |
|
3149
|
|
|
|
|
|
|
# either no aliases, or $aliaspolicy eq 'a' |
|
3150
|
|
|
|
|
|
|
# (in which case columns already mapped to aliases) |
|
3151
|
|
|
|
|
|
|
stag_add($parent_relationstruct, |
|
3152
|
|
|
|
|
|
|
$relationstruct->name, |
|
3153
|
|
|
|
|
|
|
$relationstruct->data); |
|
3154
|
|
|
|
|
|
|
} |
|
3155
|
|
|
|
|
|
|
$rec = |
|
3156
|
|
|
|
|
|
|
{struct=>$relationstruct, |
|
3157
|
|
|
|
|
|
|
child_h=>{}}; |
|
3158
|
|
|
|
|
|
|
foreach ($node->subnodes) { |
|
3159
|
|
|
|
|
|
|
# keep index of children by PK |
|
3160
|
|
|
|
|
|
|
$rec->{child_h}->{$_->name} = {}; |
|
3161
|
|
|
|
|
|
|
} |
|
3162
|
|
|
|
|
|
|
$parent_rec_h->{child_h}->{$relationname}->{$pkval} = $rec; |
|
3163
|
|
|
|
|
|
|
} |
|
3164
|
|
|
|
|
|
|
} |
|
3165
|
|
|
|
|
|
|
foreach ($node->subnodes) { |
|
3166
|
|
|
|
|
|
|
$self->make_a_tree($tree, |
|
3167
|
|
|
|
|
|
|
$rec, |
|
3168
|
|
|
|
|
|
|
$_, |
|
3169
|
|
|
|
|
|
|
\%current_relation_h, |
|
3170
|
|
|
|
|
|
|
\%pkey_by_relationname, |
|
3171
|
|
|
|
|
|
|
\%cols_by_relationname, |
|
3172
|
|
|
|
|
|
|
\%alias2baserelation, |
|
3173
|
|
|
|
|
|
|
$aliaspolicy); |
|
3174
|
|
|
|
|
|
|
} |
|
3175
|
|
|
|
|
|
|
} |
|
3176
|
|
|
|
|
|
|
|
|
3177
|
|
|
|
|
|
|
|
|
3178
|
|
|
|
|
|
|
# -------- GENERAL SUBS ----------- |
|
3179
|
|
|
|
|
|
|
|
|
3180
|
|
|
|
|
|
|
sub esctab { |
|
3181
|
|
|
|
|
|
|
my $w=shift; |
|
3182
|
|
|
|
|
|
|
$w =~ s/\t/__MAGICTAB__/g; |
|
3183
|
|
|
|
|
|
|
$w; |
|
3184
|
|
|
|
|
|
|
} |
|
3185
|
|
|
|
|
|
|
|
|
3186
|
|
|
|
|
|
|
sub makesql { |
|
3187
|
|
|
|
|
|
|
my $self = shift; |
|
3188
|
|
|
|
|
|
|
my ($table, |
|
3189
|
|
|
|
|
|
|
$where, |
|
3190
|
|
|
|
|
|
|
$select, |
|
3191
|
|
|
|
|
|
|
$order, |
|
3192
|
|
|
|
|
|
|
$group, |
|
3193
|
|
|
|
|
|
|
$distinct) = |
|
3194
|
|
|
|
|
|
|
rearrange([qw(table |
|
3195
|
|
|
|
|
|
|
where |
|
3196
|
|
|
|
|
|
|
select |
|
3197
|
|
|
|
|
|
|
order |
|
3198
|
|
|
|
|
|
|
group |
|
3199
|
|
|
|
|
|
|
distinct)], @_); |
|
3200
|
|
|
|
|
|
|
|
|
3201
|
|
|
|
|
|
|
confess("must specify table") unless $table; |
|
3202
|
|
|
|
|
|
|
|
|
3203
|
|
|
|
|
|
|
# array of tables |
|
3204
|
|
|
|
|
|
|
if (ref($table)) { |
|
3205
|
|
|
|
|
|
|
if (ref($table) eq "HASH") { |
|
3206
|
|
|
|
|
|
|
$table = |
|
3207
|
|
|
|
|
|
|
[ |
|
3208
|
|
|
|
|
|
|
map { |
|
3209
|
|
|
|
|
|
|
"$table->{$_} AS $_" |
|
3210
|
|
|
|
|
|
|
} keys %$table |
|
3211
|
|
|
|
|
|
|
]; |
|
3212
|
|
|
|
|
|
|
} |
|
3213
|
|
|
|
|
|
|
} |
|
3214
|
|
|
|
|
|
|
else { |
|
3215
|
|
|
|
|
|
|
$table = [$table]; |
|
3216
|
|
|
|
|
|
|
} |
|
3217
|
|
|
|
|
|
|
|
|
3218
|
|
|
|
|
|
|
$where = [] unless $where; |
|
3219
|
|
|
|
|
|
|
# array of ANDed where clauses |
|
3220
|
|
|
|
|
|
|
if (ref($where)) { |
|
3221
|
|
|
|
|
|
|
if (ref($where) eq "HASH") { |
|
3222
|
|
|
|
|
|
|
$where = |
|
3223
|
|
|
|
|
|
|
[ |
|
3224
|
|
|
|
|
|
|
map { |
|
3225
|
|
|
|
|
|
|
"$_ = ".$self->quote($where->{$_}) |
|
3226
|
|
|
|
|
|
|
} keys %$where |
|
3227
|
|
|
|
|
|
|
]; |
|
3228
|
|
|
|
|
|
|
} |
|
3229
|
|
|
|
|
|
|
} |
|
3230
|
|
|
|
|
|
|
else { |
|
3231
|
|
|
|
|
|
|
$where = [$where]; |
|
3232
|
|
|
|
|
|
|
} |
|
3233
|
|
|
|
|
|
|
|
|
3234
|
|
|
|
|
|
|
$select = ['*'] unless $select; |
|
3235
|
|
|
|
|
|
|
# array of SELECT cols |
|
3236
|
|
|
|
|
|
|
if (ref($select)) { |
|
3237
|
|
|
|
|
|
|
if (ref($select) eq "HASH") { |
|
3238
|
|
|
|
|
|
|
$select = |
|
3239
|
|
|
|
|
|
|
[ |
|
3240
|
|
|
|
|
|
|
map { |
|
3241
|
|
|
|
|
|
|
"$select->{$_} AS $_" |
|
3242
|
|
|
|
|
|
|
} keys %$select |
|
3243
|
|
|
|
|
|
|
]; |
|
3244
|
|
|
|
|
|
|
} |
|
3245
|
|
|
|
|
|
|
} |
|
3246
|
|
|
|
|
|
|
else { |
|
3247
|
|
|
|
|
|
|
$select = [$select]; |
|
3248
|
|
|
|
|
|
|
} |
|
3249
|
|
|
|
|
|
|
|
|
3250
|
|
|
|
|
|
|
$order = [] unless $order; |
|
3251
|
|
|
|
|
|
|
# array of order tables |
|
3252
|
|
|
|
|
|
|
if (ref($order)) { |
|
3253
|
|
|
|
|
|
|
if (ref($order) eq "HASH") { |
|
3254
|
|
|
|
|
|
|
confess("order must be an array"); |
|
3255
|
|
|
|
|
|
|
} |
|
3256
|
|
|
|
|
|
|
} |
|
3257
|
|
|
|
|
|
|
else { |
|
3258
|
|
|
|
|
|
|
$order = [$order]; |
|
3259
|
|
|
|
|
|
|
} |
|
3260
|
|
|
|
|
|
|
|
|
3261
|
|
|
|
|
|
|
$group = [] unless $group; |
|
3262
|
|
|
|
|
|
|
# array of group tables |
|
3263
|
|
|
|
|
|
|
if (ref($group)) { |
|
3264
|
|
|
|
|
|
|
if (ref($group) eq "HASH") { |
|
3265
|
|
|
|
|
|
|
confess("group must be an array"); |
|
3266
|
|
|
|
|
|
|
} |
|
3267
|
|
|
|
|
|
|
} |
|
3268
|
|
|
|
|
|
|
else { |
|
3269
|
|
|
|
|
|
|
$group = [$group]; |
|
3270
|
|
|
|
|
|
|
} |
|
3271
|
|
|
|
|
|
|
|
|
3272
|
|
|
|
|
|
|
$distinct = $distinct ? '' : ' DISTINCT'; |
|
3273
|
|
|
|
|
|
|
my $sql = |
|
3274
|
|
|
|
|
|
|
sprintf("SELECT%s %s FROM %s%s%s", |
|
3275
|
|
|
|
|
|
|
$distinct, |
|
3276
|
|
|
|
|
|
|
join(', ', @$select), |
|
3277
|
|
|
|
|
|
|
join(', ', @$table), |
|
3278
|
|
|
|
|
|
|
(scalar(@$where) ? |
|
3279
|
|
|
|
|
|
|
' WHERE '.join(' AND ', @$where) : ''), |
|
3280
|
|
|
|
|
|
|
(scalar(@$group) ? |
|
3281
|
|
|
|
|
|
|
' GROUP BY '.join(', ', @$group) : ''), |
|
3282
|
|
|
|
|
|
|
(scalar(@$order) ? |
|
3283
|
|
|
|
|
|
|
' ORDER BY '.join(', ', @$order) : ''), |
|
3284
|
|
|
|
|
|
|
); |
|
3285
|
|
|
|
|
|
|
return $sql; |
|
3286
|
|
|
|
|
|
|
} |
|
3287
|
|
|
|
|
|
|
|
|
3288
|
|
|
|
|
|
|
|
|
3289
|
|
|
|
|
|
|
|
|
3290
|
|
|
|
|
|
|
sub selectval { |
|
3291
|
|
|
|
|
|
|
my $self = shift; |
|
3292
|
|
|
|
|
|
|
trace(0, "@_") if $TRACE; |
|
3293
|
|
|
|
|
|
|
return $self->dbh->selectcol_arrayref(@_)->[0]; |
|
3294
|
|
|
|
|
|
|
} |
|
3295
|
|
|
|
|
|
|
|
|
3296
|
|
|
|
|
|
|
sub insertrow { |
|
3297
|
|
|
|
|
|
|
my $self = shift; |
|
3298
|
|
|
|
|
|
|
my ($table, $colvalh, $pkcol) = @_; |
|
3299
|
|
|
|
|
|
|
|
|
3300
|
|
|
|
|
|
|
my $driver = $self->dbh->{Driver}->{Name}; |
|
3301
|
|
|
|
|
|
|
my @cols = keys %$colvalh; |
|
3302
|
|
|
|
|
|
|
my @vals = |
|
3303
|
|
|
|
|
|
|
map { |
|
3304
|
|
|
|
|
|
|
defined($_) ? $colvalh->{$_} : undef |
|
3305
|
|
|
|
|
|
|
} @cols; |
|
3306
|
|
|
|
|
|
|
my @placeholders = map { '?' } @vals; |
|
3307
|
|
|
|
|
|
|
my $sql = |
|
3308
|
|
|
|
|
|
|
sprintf("INSERT INTO %s (%s) VALUES (%s)", |
|
3309
|
|
|
|
|
|
|
$table, |
|
3310
|
|
|
|
|
|
|
join(", ", @cols), |
|
3311
|
|
|
|
|
|
|
#join(", ", @vals), |
|
3312
|
|
|
|
|
|
|
join(", ", @placeholders), |
|
3313
|
|
|
|
|
|
|
); |
|
3314
|
|
|
|
|
|
|
if (!@cols) { |
|
3315
|
|
|
|
|
|
|
$sql = "INSERT INTO $table DEFAULT VALUES"; |
|
3316
|
|
|
|
|
|
|
} |
|
3317
|
|
|
|
|
|
|
|
|
3318
|
|
|
|
|
|
|
trace(0, "SQL:$sql") if $TRACE; |
|
3319
|
|
|
|
|
|
|
my $succeeded = 0; |
|
3320
|
|
|
|
|
|
|
eval { |
|
3321
|
|
|
|
|
|
|
my $sth = $self->dbh->prepare($sql); |
|
3322
|
|
|
|
|
|
|
my $rval = $sth->execute(@vals); |
|
3323
|
|
|
|
|
|
|
$succeeded = 1 if defined $rval; |
|
3324
|
|
|
|
|
|
|
}; |
|
3325
|
|
|
|
|
|
|
if ($@) { |
|
3326
|
|
|
|
|
|
|
if ($self->force) { |
|
3327
|
|
|
|
|
|
|
# what about transactions?? |
|
3328
|
|
|
|
|
|
|
$self->warn("IN SQL: $sql\nWARNING: $@\n"); |
|
3329
|
|
|
|
|
|
|
return; |
|
3330
|
|
|
|
|
|
|
} |
|
3331
|
|
|
|
|
|
|
else { |
|
3332
|
|
|
|
|
|
|
confess $@; |
|
3333
|
|
|
|
|
|
|
} |
|
3334
|
|
|
|
|
|
|
} |
|
3335
|
|
|
|
|
|
|
return unless $succeeded; |
|
3336
|
|
|
|
|
|
|
my $pkval; |
|
3337
|
|
|
|
|
|
|
if ($pkcol) { |
|
3338
|
|
|
|
|
|
|
# primary key value may have been specified in the xml |
|
3339
|
|
|
|
|
|
|
# (this is necessary for non-surrogate pks in tables that |
|
3340
|
|
|
|
|
|
|
# are to be linked to via foreign keys) |
|
3341
|
|
|
|
|
|
|
$pkval = $colvalh->{$pkcol}; |
|
3342
|
|
|
|
|
|
|
|
|
3343
|
|
|
|
|
|
|
# pk was not supplied - perhaps this is a SERIAL/AUTO_INCREMENT |
|
3344
|
|
|
|
|
|
|
# column (ie surrogate integer primary key) |
|
3345
|
|
|
|
|
|
|
if (!$pkval) { |
|
3346
|
|
|
|
|
|
|
# assume pk is a SERIAL / AUTO_INCREMENT |
|
3347
|
|
|
|
|
|
|
if ($driver eq 'Pg') { |
|
3348
|
|
|
|
|
|
|
my $seqn = sprintf("%s_%s_seq", |
|
3349
|
|
|
|
|
|
|
$table, |
|
3350
|
|
|
|
|
|
|
$pkcol); |
|
3351
|
|
|
|
|
|
|
$pkval = $self->selectval("select currval('$seqn')"); |
|
3352
|
|
|
|
|
|
|
trace(0, "CURRVAL $seqn = $pkval [Pg]") if $TRACE; |
|
3353
|
|
|
|
|
|
|
} |
|
3354
|
|
|
|
|
|
|
# this doesn't work on older |
|
3355
|
|
|
|
|
|
|
# versions of DBI/DBD::mysql |
|
3356
|
|
|
|
|
|
|
# seems to have been fixed Oct 2004 release |
|
3357
|
|
|
|
|
|
|
elsif ($driver eq 'mysql') { |
|
3358
|
|
|
|
|
|
|
$pkval = $self->dbh->last_insert_id(undef,undef,$table,$pkcol); |
|
3359
|
|
|
|
|
|
|
trace(0, "CURRVAL mysql_insert_id $pkval [mysql]") if $TRACE; |
|
3360
|
|
|
|
|
|
|
} |
|
3361
|
|
|
|
|
|
|
else { |
|
3362
|
|
|
|
|
|
|
$pkval = $self->selectval("select max($pkcol) from $table"); |
|
3363
|
|
|
|
|
|
|
} |
|
3364
|
|
|
|
|
|
|
} |
|
3365
|
|
|
|
|
|
|
trace(0, "PKVAL = $pkval") if $TRACE; |
|
3366
|
|
|
|
|
|
|
} |
|
3367
|
|
|
|
|
|
|
return $pkval; |
|
3368
|
|
|
|
|
|
|
} |
|
3369
|
|
|
|
|
|
|
|
|
3370
|
|
|
|
|
|
|
sub updaterow { |
|
3371
|
|
|
|
|
|
|
my $self = shift; |
|
3372
|
|
|
|
|
|
|
my ($table, $set, $where) = @_; |
|
3373
|
|
|
|
|
|
|
|
|
3374
|
|
|
|
|
|
|
confess("must specify table") unless $table; |
|
3375
|
|
|
|
|
|
|
|
|
3376
|
|
|
|
|
|
|
my $dbh = $self->dbh; |
|
3377
|
|
|
|
|
|
|
|
|
3378
|
|
|
|
|
|
|
# array of WHERE cols |
|
3379
|
|
|
|
|
|
|
if (ref($where)) { |
|
3380
|
|
|
|
|
|
|
if (ref($where) eq "HASH") { |
|
3381
|
|
|
|
|
|
|
$where = |
|
3382
|
|
|
|
|
|
|
[ |
|
3383
|
|
|
|
|
|
|
map { |
|
3384
|
|
|
|
|
|
|
"$_ = ".$dbh->quote($where->{$_}) |
|
3385
|
|
|
|
|
|
|
} keys %$where |
|
3386
|
|
|
|
|
|
|
]; |
|
3387
|
|
|
|
|
|
|
} |
|
3388
|
|
|
|
|
|
|
} |
|
3389
|
|
|
|
|
|
|
else { |
|
3390
|
|
|
|
|
|
|
$where = [$where]; |
|
3391
|
|
|
|
|
|
|
} |
|
3392
|
|
|
|
|
|
|
confess("must specify constraints") unless @$where; |
|
3393
|
|
|
|
|
|
|
|
|
3394
|
|
|
|
|
|
|
confess("must set update vals") unless $set; |
|
3395
|
|
|
|
|
|
|
my @bind = (); |
|
3396
|
|
|
|
|
|
|
# array of SET colvals |
|
3397
|
|
|
|
|
|
|
if (ref($set)) { |
|
3398
|
|
|
|
|
|
|
if (ref($set) eq "HASH") { |
|
3399
|
|
|
|
|
|
|
$set = |
|
3400
|
|
|
|
|
|
|
[ |
|
3401
|
|
|
|
|
|
|
map { |
|
3402
|
|
|
|
|
|
|
push(@bind, defined $set->{$_} ? $set->{$_} : 'NULL'); |
|
3403
|
|
|
|
|
|
|
"$_ = ?" |
|
3404
|
|
|
|
|
|
|
} keys %$set |
|
3405
|
|
|
|
|
|
|
]; |
|
3406
|
|
|
|
|
|
|
} |
|
3407
|
|
|
|
|
|
|
} |
|
3408
|
|
|
|
|
|
|
else { |
|
3409
|
|
|
|
|
|
|
$set = [$set]; |
|
3410
|
|
|
|
|
|
|
} |
|
3411
|
|
|
|
|
|
|
|
|
3412
|
|
|
|
|
|
|
my $sql = |
|
3413
|
|
|
|
|
|
|
sprintf("UPDATE %s SET %s WHERE %s", |
|
3414
|
|
|
|
|
|
|
$table, |
|
3415
|
|
|
|
|
|
|
join(', ', @$set), |
|
3416
|
|
|
|
|
|
|
join(' AND ', @$where), |
|
3417
|
|
|
|
|
|
|
); |
|
3418
|
|
|
|
|
|
|
trace(0, "SQL:$sql [",join(', ',@bind)."]") if $TRACE; |
|
3419
|
|
|
|
|
|
|
|
|
3420
|
|
|
|
|
|
|
my $sth = $dbh->prepare($sql) || confess($sql."\n\t".$dbh->errstr); |
|
3421
|
|
|
|
|
|
|
return $sth->execute(@bind) || confess($sql."\n\t".$sth->errstr); |
|
3422
|
|
|
|
|
|
|
} |
|
3423
|
|
|
|
|
|
|
|
|
3424
|
|
|
|
|
|
|
sub deleterow { |
|
3425
|
|
|
|
|
|
|
my $self = shift; |
|
3426
|
|
|
|
|
|
|
my ($table, $where) = @_; |
|
3427
|
|
|
|
|
|
|
|
|
3428
|
|
|
|
|
|
|
confess("must specify table") unless $table; |
|
3429
|
|
|
|
|
|
|
|
|
3430
|
|
|
|
|
|
|
my $dbh = $self->dbh; |
|
3431
|
|
|
|
|
|
|
|
|
3432
|
|
|
|
|
|
|
# array of WHERE cols |
|
3433
|
|
|
|
|
|
|
if (ref($where)) { |
|
3434
|
|
|
|
|
|
|
if (ref($where) eq "HASH") { |
|
3435
|
|
|
|
|
|
|
$where = |
|
3436
|
|
|
|
|
|
|
[ |
|
3437
|
|
|
|
|
|
|
map { |
|
3438
|
|
|
|
|
|
|
"$_ = ".$dbh->quote($where->{$_}) |
|
3439
|
|
|
|
|
|
|
} keys %$where |
|
3440
|
|
|
|
|
|
|
]; |
|
3441
|
|
|
|
|
|
|
} |
|
3442
|
|
|
|
|
|
|
} |
|
3443
|
|
|
|
|
|
|
else { |
|
3444
|
|
|
|
|
|
|
$where = [$where]; |
|
3445
|
|
|
|
|
|
|
} |
|
3446
|
|
|
|
|
|
|
confess("must specify constraints") unless @$where; |
|
3447
|
|
|
|
|
|
|
|
|
3448
|
|
|
|
|
|
|
my $sql = |
|
3449
|
|
|
|
|
|
|
sprintf("DELETE FROM %s WHERE %s", |
|
3450
|
|
|
|
|
|
|
$table, |
|
3451
|
|
|
|
|
|
|
join(' AND ', @$where), |
|
3452
|
|
|
|
|
|
|
); |
|
3453
|
|
|
|
|
|
|
trace(0, "SQL:$sql") if $TRACE; |
|
3454
|
|
|
|
|
|
|
|
|
3455
|
|
|
|
|
|
|
my $sth = $dbh->prepare($sql) || confess($sql."\n\t".$dbh->errstr); |
|
3456
|
|
|
|
|
|
|
return $sth->execute() || confess($sql."\n\t".$sth->errstr); |
|
3457
|
|
|
|
|
|
|
} |
|
3458
|
|
|
|
|
|
|
|
|
3459
|
|
|
|
|
|
|
#$::RD_HINT = 1; |
|
3460
|
|
|
|
|
|
|
|
|
3461
|
|
|
|
|
|
|
$::RD_AUTOACTION = q { [@item] }; |
|
3462
|
|
|
|
|
|
|
sub selectgrammar { |
|
3463
|
|
|
|
|
|
|
return q[ |
|
3464
|
|
|
|
|
|
|
|
|
3465
|
|
|
|
|
|
|
{ |
|
3466
|
|
|
|
|
|
|
use Data::Dumper; |
|
3467
|
|
|
|
|
|
|
use Data::Stag; |
|
3468
|
|
|
|
|
|
|
sub N { |
|
3469
|
|
|
|
|
|
|
Data::Stag->new(@_); |
|
3470
|
|
|
|
|
|
|
} |
|
3471
|
|
|
|
|
|
|
} |
|
3472
|
|
|
|
|
|
|
] |
|
3473
|
|
|
|
|
|
|
. |
|
3474
|
|
|
|
|
|
|
q[ |
|
3475
|
|
|
|
|
|
|
|
|
3476
|
|
|
|
|
|
|
selectstmts: selectstmt ';' selectstmts |
|
3477
|
|
|
|
|
|
|
selectstmts: selectstmt |
|
3478
|
|
|
|
|
|
|
# selectstmt: /select/i selectcols /from/i fromtables |
|
3479
|
|
|
|
|
|
|
selectstmt: /select/i selectq(?) selectcols /from/i fromtables where(?) group(?) having(?) combiner(?) order(?) limit(?) offset(?) |
|
3480
|
|
|
|
|
|
|
{ |
|
3481
|
|
|
|
|
|
|
N(select => [ |
|
3482
|
|
|
|
|
|
|
[qual => $item{'selectq'}[0]], |
|
3483
|
|
|
|
|
|
|
[cols => $item[3]], |
|
3484
|
|
|
|
|
|
|
[from => $item[5]], |
|
3485
|
|
|
|
|
|
|
# [where => $item[6]], |
|
3486
|
|
|
|
|
|
|
# [group => $item{'group'}[0]], |
|
3487
|
|
|
|
|
|
|
# [having => $item{'having'}[0]], |
|
3488
|
|
|
|
|
|
|
]); |
|
3489
|
|
|
|
|
|
|
} |
|
3490
|
|
|
|
|
|
|
| |
|
3491
|
|
|
|
|
|
|
selectq: /all/i | /distinct/i |
|
3492
|
|
|
|
|
|
|
{ $item[1] } |
|
3493
|
|
|
|
|
|
|
| |
|
3494
|
|
|
|
|
|
|
# as: /\s+as\s+/i |
|
3495
|
|
|
|
|
|
|
as: /as/i |
|
3496
|
|
|
|
|
|
|
selectcols: selectexpr /\,/ selectcols |
|
3497
|
|
|
|
|
|
|
{ [$item[1], @{$item[3]}] } |
|
3498
|
|
|
|
|
|
|
| |
|
3499
|
|
|
|
|
|
|
selectcols: selectexpr |
|
3500
|
|
|
|
|
|
|
{ [$item[1]] } |
|
3501
|
|
|
|
|
|
|
| |
|
3502
|
|
|
|
|
|
|
selectexpr: bselectexpr as aliasname |
|
3503
|
|
|
|
|
|
|
{ |
|
3504
|
|
|
|
|
|
|
my $col = $item{bselectexpr}; |
|
3505
|
|
|
|
|
|
|
$col->set_alias($item{aliasname}->[1]); |
|
3506
|
|
|
|
|
|
|
$col; |
|
3507
|
|
|
|
|
|
|
} |
|
3508
|
|
|
|
|
|
|
| |
|
3509
|
|
|
|
|
|
|
selectexpr: bselectexpr |
|
3510
|
|
|
|
|
|
|
{ $item[1] } |
|
3511
|
|
|
|
|
|
|
| |
|
3512
|
|
|
|
|
|
|
bselectexpr: funccall |
|
3513
|
|
|
|
|
|
|
{ $item[1] } |
|
3514
|
|
|
|
|
|
|
| |
|
3515
|
|
|
|
|
|
|
bselectexpr: selectcol |
|
3516
|
|
|
|
|
|
|
{ $item[1] } |
|
3517
|
|
|
|
|
|
|
| |
|
3518
|
|
|
|
|
|
|
|
|
3519
|
|
|
|
|
|
|
selectcol: brackselectcol operator selectcol |
|
3520
|
|
|
|
|
|
|
{ |
|
3521
|
|
|
|
|
|
|
N(col=>[ |
|
3522
|
|
|
|
|
|
|
[func => [ |
|
3523
|
|
|
|
|
|
|
[name => $item[2]->[1]], |
|
3524
|
|
|
|
|
|
|
[args => [$item[1],$item[3]]] |
|
3525
|
|
|
|
|
|
|
] |
|
3526
|
|
|
|
|
|
|
] |
|
3527
|
|
|
|
|
|
|
]); |
|
3528
|
|
|
|
|
|
|
} |
|
3529
|
|
|
|
|
|
|
### { $item[1]} |
|
3530
|
|
|
|
|
|
|
| |
|
3531
|
|
|
|
|
|
|
selectcol: brackselectcol |
|
3532
|
|
|
|
|
|
|
{ $item[1]} |
|
3533
|
|
|
|
|
|
|
| |
|
3534
|
|
|
|
|
|
|
|
|
3535
|
|
|
|
|
|
|
brackselectcol: '(' selectcol ')' |
|
3536
|
|
|
|
|
|
|
{ $item[2]} |
|
3537
|
|
|
|
|
|
|
| |
|
3538
|
|
|
|
|
|
|
|
|
3539
|
|
|
|
|
|
|
brackselectcol: bselectcol |
|
3540
|
|
|
|
|
|
|
{ $item[1]} |
|
3541
|
|
|
|
|
|
|
| |
|
3542
|
|
|
|
|
|
|
|
|
3543
|
|
|
|
|
|
|
bselectcol: /(\w+)\.(\w+)/ |
|
3544
|
|
|
|
|
|
|
{ N(col=>[ |
|
3545
|
|
|
|
|
|
|
[name => $item[1]], |
|
3546
|
|
|
|
|
|
|
[table=>$1], |
|
3547
|
|
|
|
|
|
|
]) |
|
3548
|
|
|
|
|
|
|
} |
|
3549
|
|
|
|
|
|
|
| |
|
3550
|
|
|
|
|
|
|
bselectcol: /(\w+)\.\*/ |
|
3551
|
|
|
|
|
|
|
{ N(col=>[ |
|
3552
|
|
|
|
|
|
|
[name => $item[1]], |
|
3553
|
|
|
|
|
|
|
[table=>$1], |
|
3554
|
|
|
|
|
|
|
]) |
|
3555
|
|
|
|
|
|
|
} |
|
3556
|
|
|
|
|
|
|
| |
|
3557
|
|
|
|
|
|
|
bselectcol: /\*/ |
|
3558
|
|
|
|
|
|
|
{ N(col=>[ |
|
3559
|
|
|
|
|
|
|
[name => $item[1]] |
|
3560
|
|
|
|
|
|
|
]) |
|
3561
|
|
|
|
|
|
|
} |
|
3562
|
|
|
|
|
|
|
| |
|
3563
|
|
|
|
|
|
|
bselectcol: /\w+/ |
|
3564
|
|
|
|
|
|
|
{ N(col=>[ |
|
3565
|
|
|
|
|
|
|
[name => $item[1]] |
|
3566
|
|
|
|
|
|
|
]) |
|
3567
|
|
|
|
|
|
|
} |
|
3568
|
|
|
|
|
|
|
| |
|
3569
|
|
|
|
|
|
|
bselectcol: expr |
|
3570
|
|
|
|
|
|
|
{ N(col=>[ |
|
3571
|
|
|
|
|
|
|
[expr => $item[1]] |
|
3572
|
|
|
|
|
|
|
]) } |
|
3573
|
|
|
|
|
|
|
| |
|
3574
|
|
|
|
|
|
|
funccall: funcname '(' distinct(?) selectcols ')' |
|
3575
|
|
|
|
|
|
|
{ |
|
3576
|
|
|
|
|
|
|
my $col = N(col=>[ |
|
3577
|
|
|
|
|
|
|
[func => [ |
|
3578
|
|
|
|
|
|
|
[name => $item[1]->[1]], |
|
3579
|
|
|
|
|
|
|
[args => $item[4]] |
|
3580
|
|
|
|
|
|
|
] |
|
3581
|
|
|
|
|
|
|
] |
|
3582
|
|
|
|
|
|
|
]); |
|
3583
|
|
|
|
|
|
|
$col; |
|
3584
|
|
|
|
|
|
|
} |
|
3585
|
|
|
|
|
|
|
| |
|
3586
|
|
|
|
|
|
|
|
|
3587
|
|
|
|
|
|
|
distinct: /distinct/i |
|
3588
|
|
|
|
|
|
|
|
|
3589
|
|
|
|
|
|
|
operator: '+' | '-' | '*' | '/' | '||' |
|
3590
|
|
|
|
|
|
|
|
|
3591
|
|
|
|
|
|
|
|
|
3592
|
|
|
|
|
|
|
fromtables: jtable |
|
3593
|
|
|
|
|
|
|
{ [$item[1]] } |
|
3594
|
|
|
|
|
|
|
| |
|
3595
|
|
|
|
|
|
|
jtable: join_jtable |
|
3596
|
|
|
|
|
|
|
{ $item[1] } |
|
3597
|
|
|
|
|
|
|
| |
|
3598
|
|
|
|
|
|
|
join_jtable: qual_jtable jointype join_jtable |
|
3599
|
|
|
|
|
|
|
{ |
|
3600
|
|
|
|
|
|
|
shift @{$item[2]}; |
|
3601
|
|
|
|
|
|
|
my $j = |
|
3602
|
|
|
|
|
|
|
N(composite=>[ |
|
3603
|
|
|
|
|
|
|
[ctype=>"@{$item[2]}"], |
|
3604
|
|
|
|
|
|
|
[first=>[$item[1]]], |
|
3605
|
|
|
|
|
|
|
[second=>[$item[3]]] |
|
3606
|
|
|
|
|
|
|
]); |
|
3607
|
|
|
|
|
|
|
$j; |
|
3608
|
|
|
|
|
|
|
} |
|
3609
|
|
|
|
|
|
|
| |
|
3610
|
|
|
|
|
|
|
join_jtable: qual_jtable |
|
3611
|
|
|
|
|
|
|
{ $item[1] } |
|
3612
|
|
|
|
|
|
|
| |
|
3613
|
|
|
|
|
|
|
qual_jtable: alias_jtable joinqual |
|
3614
|
|
|
|
|
|
|
{ |
|
3615
|
|
|
|
|
|
|
my $j = $item[1]; |
|
3616
|
|
|
|
|
|
|
$j->setnode_qual($item[2]); |
|
3617
|
|
|
|
|
|
|
$j; |
|
3618
|
|
|
|
|
|
|
} |
|
3619
|
|
|
|
|
|
|
| |
|
3620
|
|
|
|
|
|
|
qual_jtable: alias_jtable |
|
3621
|
|
|
|
|
|
|
{ $item[1] } |
|
3622
|
|
|
|
|
|
|
| |
|
3623
|
|
|
|
|
|
|
alias_jtable: brack_jtable /as\s+/i aliasname |
|
3624
|
|
|
|
|
|
|
{ |
|
3625
|
|
|
|
|
|
|
my $j = $item[1]; |
|
3626
|
|
|
|
|
|
|
$j->set_alias($item[3][1]); |
|
3627
|
|
|
|
|
|
|
$j; |
|
3628
|
|
|
|
|
|
|
} |
|
3629
|
|
|
|
|
|
|
| |
|
3630
|
|
|
|
|
|
|
alias_jtable: brack_jtable |
|
3631
|
|
|
|
|
|
|
{ $item[1] } |
|
3632
|
|
|
|
|
|
|
| |
|
3633
|
|
|
|
|
|
|
brack_jtable: '(' jtable ')' |
|
3634
|
|
|
|
|
|
|
{ $item[2] } |
|
3635
|
|
|
|
|
|
|
| |
|
3636
|
|
|
|
|
|
|
brack_jtable: table |
|
3637
|
|
|
|
|
|
|
{ N(leaf=>[[name=>$item[1]->[1]]]) } |
|
3638
|
|
|
|
|
|
|
| |
|
3639
|
|
|
|
|
|
|
|
|
3640
|
|
|
|
|
|
|
joinqual: /on\s+/i bool_expr |
|
3641
|
|
|
|
|
|
|
{ N(qual => [ |
|
3642
|
|
|
|
|
|
|
[type=>'on'], |
|
3643
|
|
|
|
|
|
|
[expr=>"@{$item[2]}"] |
|
3644
|
|
|
|
|
|
|
]) |
|
3645
|
|
|
|
|
|
|
} |
|
3646
|
|
|
|
|
|
|
| |
|
3647
|
|
|
|
|
|
|
joinqual: /using\s+/i '(' cols ')' |
|
3648
|
|
|
|
|
|
|
{ N(qual =>[ |
|
3649
|
|
|
|
|
|
|
[type=>'using'], |
|
3650
|
|
|
|
|
|
|
[expr=>"@{$item[3]}"] |
|
3651
|
|
|
|
|
|
|
]) |
|
3652
|
|
|
|
|
|
|
} |
|
3653
|
|
|
|
|
|
|
| |
|
3654
|
|
|
|
|
|
|
|
|
3655
|
|
|
|
|
|
|
table: tablename |
|
3656
|
|
|
|
|
|
|
{ $item[1] } |
|
3657
|
|
|
|
|
|
|
| |
|
3658
|
|
|
|
|
|
|
|
|
3659
|
|
|
|
|
|
|
funcname: /\w+/ |
|
3660
|
|
|
|
|
|
|
tablename: /\w+/ |
|
3661
|
|
|
|
|
|
|
aliasname: /\w+/ |
|
3662
|
|
|
|
|
|
|
|
|
3663
|
|
|
|
|
|
|
|
|
3664
|
|
|
|
|
|
|
cols: col(s) |
|
3665
|
|
|
|
|
|
|
col: /\w+\.\w+/ |
|
3666
|
|
|
|
|
|
|
col: /\w+/ |
|
3667
|
|
|
|
|
|
|
|
|
3668
|
|
|
|
|
|
|
jointype: /\,/ |
|
3669
|
|
|
|
|
|
|
jointype: /natural/i bjointype /join/i |
|
3670
|
|
|
|
|
|
|
jointype: /natural/i /join/i |
|
3671
|
|
|
|
|
|
|
jointype: bjointype /join/i |
|
3672
|
|
|
|
|
|
|
jointype: /join/i |
|
3673
|
|
|
|
|
|
|
bjointype: /inner/i |
|
3674
|
|
|
|
|
|
|
bjointype: lrf(?) /outer/i |
|
3675
|
|
|
|
|
|
|
lrf: /left/i | /right/i | /full/i |
|
3676
|
|
|
|
|
|
|
bjointype: /cross/i |
|
3677
|
|
|
|
|
|
|
|
|
3678
|
|
|
|
|
|
|
number: float | int |
|
3679
|
|
|
|
|
|
|
float: /\d*\.?\d+/ 'e' sign int |
|
3680
|
|
|
|
|
|
|
float: /\d*\.\d+/ |
|
3681
|
|
|
|
|
|
|
int: /\d+/ |
|
3682
|
|
|
|
|
|
|
string: /\'.*?\'/ |
|
3683
|
|
|
|
|
|
|
sign: '+' | '-' |
|
3684
|
|
|
|
|
|
|
|
|
3685
|
|
|
|
|
|
|
exprs: '(' exprs ')' |
|
3686
|
|
|
|
|
|
|
exprs: expr ',' exprs |
|
3687
|
|
|
|
|
|
|
exprs: expr |
|
3688
|
|
|
|
|
|
|
|
|
3689
|
|
|
|
|
|
|
# bool_expr - eg in where clause |
|
3690
|
|
|
|
|
|
|
bool_expr: not_bool_expr boolop bool_expr | not_bool_expr |
|
3691
|
|
|
|
|
|
|
not_bool_expr: '!' brack_bool_expr | brack_bool_expr |
|
3692
|
|
|
|
|
|
|
brack_bool_expr: '(' bool_expr ')' | bool_exprprim |
|
3693
|
|
|
|
|
|
|
bool_exprprim: boolval | expr |
|
3694
|
|
|
|
|
|
|
boolval: /true/i | /false/i | /null/i |
|
3695
|
|
|
|
|
|
|
|
|
3696
|
|
|
|
|
|
|
expr: brack_expr op expr | brack_expr |
|
3697
|
|
|
|
|
|
|
brack_expr: '(' expr ')' | exprprim |
|
3698
|
|
|
|
|
|
|
exprprim: col | val |
|
3699
|
|
|
|
|
|
|
val: number | string |
|
3700
|
|
|
|
|
|
|
|
|
3701
|
|
|
|
|
|
|
op: /not\s+/i /like\s+/i |
|
3702
|
|
|
|
|
|
|
op: /like\s+/i |
|
3703
|
|
|
|
|
|
|
op: /is\s+/i /not\s+/i |
|
3704
|
|
|
|
|
|
|
op: /is\s+/i |
|
3705
|
|
|
|
|
|
|
op: '=' | '!=' | '<>' | '<=' | '>=' | '<' | '>' |
|
3706
|
|
|
|
|
|
|
boolop: /and\s+/i | /or\s+/i | /not\s+/i |
|
3707
|
|
|
|
|
|
|
|
|
3708
|
|
|
|
|
|
|
# where: /where/i /.*/ |
|
3709
|
|
|
|
|
|
|
where: /where/i bool_expr |
|
3710
|
|
|
|
|
|
|
group: /group/i /by/i exprs |
|
3711
|
|
|
|
|
|
|
having: /having/i /.*/ |
|
3712
|
|
|
|
|
|
|
combiner: combinekwd selectstmt |
|
3713
|
|
|
|
|
|
|
combinekwd: /union/i | /intersect/i | /update/i |
|
3714
|
|
|
|
|
|
|
order: /order/i /by/i orderexprs |
|
3715
|
|
|
|
|
|
|
orderexprs: orderexpr ',' orderexprs |
|
3716
|
|
|
|
|
|
|
orderexprs: orderexpr |
|
3717
|
|
|
|
|
|
|
orderexpr: expr /asc/i |
|
3718
|
|
|
|
|
|
|
orderexpr: expr /desc/i |
|
3719
|
|
|
|
|
|
|
orderexpr: expr /using/i op |
|
3720
|
|
|
|
|
|
|
orderexpr: expr |
|
3721
|
|
|
|
|
|
|
limit: /limit/i /\w+/ |
|
3722
|
|
|
|
|
|
|
offset: /offset/i /\d+/ |
|
3723
|
|
|
|
|
|
|
]; |
|
3724
|
|
|
|
|
|
|
} |
|
3725
|
|
|
|
|
|
|
|
|
3726
|
|
|
|
|
|
|
no strict 'refs'; |
|
3727
|
|
|
|
|
|
|
sub AUTOLOAD { |
|
3728
|
|
|
|
|
|
|
my $self = shift; |
|
3729
|
|
|
|
|
|
|
my @args = @_; |
|
3730
|
|
|
|
|
|
|
|
|
3731
|
|
|
|
|
|
|
my $name = $AUTOLOAD; |
|
3732
|
|
|
|
|
|
|
$name =~ s/.*://; # strip fully-qualified portion |
|
3733
|
|
|
|
|
|
|
|
|
3734
|
|
|
|
|
|
|
if ($name eq "DESTROY") { |
|
3735
|
|
|
|
|
|
|
# we dont want to propagate this!! |
|
3736
|
|
|
|
|
|
|
return; |
|
3737
|
|
|
|
|
|
|
} |
|
3738
|
|
|
|
|
|
|
|
|
3739
|
|
|
|
|
|
|
unless ($self->isa("DBIx::DBStag")) { |
|
3740
|
|
|
|
|
|
|
confess("no such subroutine $name"); |
|
3741
|
|
|
|
|
|
|
} |
|
3742
|
|
|
|
|
|
|
if ($self->dbh) { |
|
3743
|
|
|
|
|
|
|
if ($TRACE) { |
|
3744
|
|
|
|
|
|
|
# the following check may impair performance |
|
3745
|
|
|
|
|
|
|
if (grep { ref($_) } @args) { |
|
3746
|
|
|
|
|
|
|
$self->throw("cannot quote @args"); |
|
3747
|
|
|
|
|
|
|
} |
|
3748
|
|
|
|
|
|
|
} |
|
3749
|
|
|
|
|
|
|
if ($self->dbh->can($name)) { |
|
3750
|
|
|
|
|
|
|
return $self->dbh->$name(@args); |
|
3751
|
|
|
|
|
|
|
} |
|
3752
|
|
|
|
|
|
|
} |
|
3753
|
|
|
|
|
|
|
confess("no such method:$name)"); |
|
3754
|
|
|
|
|
|
|
} |
|
3755
|
|
|
|
|
|
|
|
|
3756
|
|
|
|
|
|
|
sub rearrange { |
|
3757
|
|
|
|
|
|
|
my($order,@param) = @_; |
|
3758
|
|
|
|
|
|
|
|
|
3759
|
|
|
|
|
|
|
# If there are no parameters, we simply wish to return |
|
3760
|
|
|
|
|
|
|
# an undef array which is the size of the @{$order} array. |
|
3761
|
|
|
|
|
|
|
return (undef) x $#{$order} unless @param; |
|
3762
|
|
|
|
|
|
|
|
|
3763
|
|
|
|
|
|
|
# If we've got parameters, we need to check to see whether |
|
3764
|
|
|
|
|
|
|
# they are named or simply listed. If they are listed, we |
|
3765
|
|
|
|
|
|
|
# can just return them. |
|
3766
|
|
|
|
|
|
|
return @param unless (defined($param[0]) && $param[0]=~/^-/); |
|
3767
|
|
|
|
|
|
|
|
|
3768
|
|
|
|
|
|
|
# Now we've got to do some work on the named parameters. |
|
3769
|
|
|
|
|
|
|
# The next few lines strip out the '-' characters which |
|
3770
|
|
|
|
|
|
|
# preceed the keys, and capitalizes them. |
|
3771
|
|
|
|
|
|
|
my $i; |
|
3772
|
|
|
|
|
|
|
for ($i=0;$i<@param;$i+=2) { |
|
3773
|
|
|
|
|
|
|
if (!defined($param[$i])) { |
|
3774
|
|
|
|
|
|
|
cluck("Hmmm in $i ".CORE::join(";", @param)." == ".CORE::join(";",@$order)."\n"); |
|
3775
|
|
|
|
|
|
|
} |
|
3776
|
|
|
|
|
|
|
else { |
|
3777
|
|
|
|
|
|
|
$param[$i]=~s/^\-//; |
|
3778
|
|
|
|
|
|
|
$param[$i]=~tr/a-z/A-Z/; |
|
3779
|
|
|
|
|
|
|
} |
|
3780
|
|
|
|
|
|
|
} |
|
3781
|
|
|
|
|
|
|
|
|
3782
|
|
|
|
|
|
|
# Now we'll convert the @params variable into an associative array. |
|
3783
|
|
|
|
|
|
|
my(%param) = @param; |
|
3784
|
|
|
|
|
|
|
|
|
3785
|
|
|
|
|
|
|
my(@return_array); |
|
3786
|
|
|
|
|
|
|
|
|
3787
|
|
|
|
|
|
|
# What we intend to do is loop through the @{$order} variable, |
|
3788
|
|
|
|
|
|
|
# and for each value, we use that as a key into our associative |
|
3789
|
|
|
|
|
|
|
# array, pushing the value at that key onto our return array. |
|
3790
|
|
|
|
|
|
|
my($key); |
|
3791
|
|
|
|
|
|
|
|
|
3792
|
|
|
|
|
|
|
foreach $key (@{$order}) { |
|
3793
|
|
|
|
|
|
|
$key=~tr/a-z/A-Z/; |
|
3794
|
|
|
|
|
|
|
my($value) = $param{$key}; |
|
3795
|
|
|
|
|
|
|
delete $param{$key}; |
|
3796
|
|
|
|
|
|
|
push(@return_array,$value); |
|
3797
|
|
|
|
|
|
|
} |
|
3798
|
|
|
|
|
|
|
|
|
3799
|
|
|
|
|
|
|
# catch user misspellings resulting in unrecognized names |
|
3800
|
|
|
|
|
|
|
my(@restkeys) = keys %param; |
|
3801
|
|
|
|
|
|
|
if (scalar(@restkeys) > 0) { |
|
3802
|
|
|
|
|
|
|
confess("@restkeys not processed in rearrange(), did you use a |
|
3803
|
|
|
|
|
|
|
non-recognized parameter name ? "); |
|
3804
|
|
|
|
|
|
|
} |
|
3805
|
|
|
|
|
|
|
return @return_array; |
|
3806
|
|
|
|
|
|
|
} |
|
3807
|
|
|
|
|
|
|
|
|
3808
|
|
|
|
|
|
|
#sub loadschema { |
|
3809
|
|
|
|
|
|
|
# my $self = shift; |
|
3810
|
|
|
|
|
|
|
# my ($ddl, $ddlf, $dialect) = |
|
3811
|
|
|
|
|
|
|
# rearrange([qw(ddl ddlf dialect)], @_); |
|
3812
|
|
|
|
|
|
|
# if ($ddlf) { |
|
3813
|
|
|
|
|
|
|
# my $fh = FileHandle->new($ddlf) || $self->throw("no file $ddlf"); |
|
3814
|
|
|
|
|
|
|
# $ddl = join('',<$fh>); |
|
3815
|
|
|
|
|
|
|
# $fh->close; |
|
3816
|
|
|
|
|
|
|
# } |
|
3817
|
|
|
|
|
|
|
# $self->throw("no DDL") unless $ddl; |
|
3818
|
|
|
|
|
|
|
# if ($dialect) { |
|
3819
|
|
|
|
|
|
|
# my $driver = $self->{_driver} || 'Pg'; |
|
3820
|
|
|
|
|
|
|
# if ($driver ne $dialect) { |
|
3821
|
|
|
|
|
|
|
|
|
3822
|
|
|
|
|
|
|
# } |
|
3823
|
|
|
|
|
|
|
# } |
|
3824
|
|
|
|
|
|
|
#} |
|
3825
|
|
|
|
|
|
|
|
|
3826
|
|
|
|
|
|
|
1; |
|
3827
|
|
|
|
|
|
|
|
|
3828
|
|
|
|
|
|
|
__END__ |
|