line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
##### |
2
|
|
|
|
|
|
|
# |
3
|
|
|
|
|
|
|
# $Id: RDB.pm,v 1.1 2003/04/18 00:33:17 trostler Exp $ |
4
|
|
|
|
|
|
|
# |
5
|
|
|
|
|
|
|
# COPYRIGHT AND LICENSE |
6
|
|
|
|
|
|
|
# Copyright (c) 2001, 2003, Juniper Networks, Inc. All rights reserved. |
7
|
|
|
|
|
|
|
# Redistribution and use in source and binary forms, with or without |
8
|
|
|
|
|
|
|
# modification, are permitted provided that the following conditions are |
9
|
|
|
|
|
|
|
# met: |
10
|
|
|
|
|
|
|
# 1. Redistributions of source code must retain the above |
11
|
|
|
|
|
|
|
# copyright notice, this list of conditions and the following |
12
|
|
|
|
|
|
|
# disclaimer. |
13
|
|
|
|
|
|
|
# 2. Redistributions in binary form must reproduce the above |
14
|
|
|
|
|
|
|
# copyright notice, this list of conditions and the following disclaimer |
15
|
|
|
|
|
|
|
# in the documentation and/or other materials provided with the |
16
|
|
|
|
|
|
|
# distribution. |
17
|
|
|
|
|
|
|
# 3. The name of the copyright owner may not be used to |
18
|
|
|
|
|
|
|
# endorse or promote products derived from this software without specific |
19
|
|
|
|
|
|
|
# prior written permission. |
20
|
|
|
|
|
|
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
21
|
|
|
|
|
|
|
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
22
|
|
|
|
|
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
23
|
|
|
|
|
|
|
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, |
24
|
|
|
|
|
|
|
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
25
|
|
|
|
|
|
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
26
|
|
|
|
|
|
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
27
|
|
|
|
|
|
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
28
|
|
|
|
|
|
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING |
29
|
|
|
|
|
|
|
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
30
|
|
|
|
|
|
|
# POSSIBILITY OF SUCH DAMAGE. |
31
|
|
|
|
|
|
|
# |
32
|
|
|
|
|
|
|
##### |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
package XML::RDB; |
35
|
|
|
|
|
|
|
|
36
|
1
|
|
|
1
|
|
52549
|
use XML::DOM; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
use XML::RDB::MakeTables; |
38
|
|
|
|
|
|
|
use XML::RDB::PopulateTables; |
39
|
|
|
|
|
|
|
use XML::RDB::UnpopulateTables; |
40
|
|
|
|
|
|
|
use XML::RDB::UnpopulateSchema; |
41
|
|
|
|
|
|
|
use strict; |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
##### |
44
|
|
|
|
|
|
|
# |
45
|
|
|
|
|
|
|
# Copyright (c) 2001-2003 Juniper Networks, Inc. |
46
|
|
|
|
|
|
|
# All rights reserved. |
47
|
|
|
|
|
|
|
# |
48
|
|
|
|
|
|
|
# Set of common routines & constants |
49
|
|
|
|
|
|
|
# |
50
|
|
|
|
|
|
|
##### |
51
|
|
|
|
|
|
|
BEGIN { |
52
|
|
|
|
|
|
|
use Exporter (); |
53
|
|
|
|
|
|
|
use vars qw($VERSION); # @ISA @EXPORT); |
54
|
|
|
|
|
|
|
$VERSION = "1.3"; |
55
|
|
|
|
|
|
|
# @ISA = qw(Exporter); |
56
|
|
|
|
|
|
|
# @EXPORT = qw( |
57
|
|
|
|
|
|
|
# ); |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
sub new { |
61
|
|
|
|
|
|
|
my ($class, %arg) = @_; |
62
|
|
|
|
|
|
|
my $config; |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
if ($arg{config_file}) { |
65
|
|
|
|
|
|
|
open(C, "$arg{config_file}") || die "$!"; |
66
|
|
|
|
|
|
|
while() { |
67
|
|
|
|
|
|
|
next if (/^(#|$)/); |
68
|
|
|
|
|
|
|
next unless (my ($key, $value) = $_ =~ /^([A-Z_]+)=(.+)/); |
69
|
|
|
|
|
|
|
chomp $value; |
70
|
|
|
|
|
|
|
$key =~ s/\s//g; |
71
|
|
|
|
|
|
|
$value =~ s/\s//g; |
72
|
|
|
|
|
|
|
$config->{$key} = $value if ($key); |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
close(C); |
75
|
|
|
|
|
|
|
} |
76
|
|
|
|
|
|
|
elsif ( scalar(keys(%arg)) > 0 ) { |
77
|
|
|
|
|
|
|
$config = \%arg; |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
else { |
80
|
|
|
|
|
|
|
warn "$class requires a DSN for DBI->connect\n"; |
81
|
|
|
|
|
|
|
return undef; |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# setup connection |
85
|
|
|
|
|
|
|
my $dbh = DBI->connect( |
86
|
|
|
|
|
|
|
$config->{DSN}, |
87
|
|
|
|
|
|
|
($config->{DB_USERNAME} || ''), |
88
|
|
|
|
|
|
|
($config->{DB_PASSWORD} || ''), |
89
|
|
|
|
|
|
|
{ RaiseError => $config->{DB_RAISEERROR} || 0, |
90
|
|
|
|
|
|
|
PrintError => $config->{DB_PRINTERROR} || 0, |
91
|
|
|
|
|
|
|
# NOTE : |
92
|
|
|
|
|
|
|
# autocommit needs to be on for this app in its current state. |
93
|
|
|
|
|
|
|
# 1. the method of inserts, and updates |
94
|
|
|
|
|
|
|
# 2. DBIx::Sequence |
95
|
|
|
|
|
|
|
AutoCommit => 1 } |
96
|
|
|
|
|
|
|
) or (( warn $!) and ( return undef)); |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
if ( $config->{DSN} =~ /dbi:sqlite/i) { |
99
|
|
|
|
|
|
|
warn("SQLite detected...setting SQLite PRAGMA: synchronus = OFF; locking_mode = EXCLUSIVE\n") |
100
|
|
|
|
|
|
|
if ($config->{DB_PRINTERR}); |
101
|
|
|
|
|
|
|
$dbh->do('PRAGMA synchronous = OFF') || warn $dbh->errstr; |
102
|
|
|
|
|
|
|
$dbh->do('PRAGMA locking_mode = EXCLUSIVE') || warn $dbh->errstr; |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
$DBIx::Recordset::Debug = $config->{DBIX_DEBUG} || 0; |
106
|
|
|
|
|
|
|
$DBIx::Recordset::FetchsizeWarn = $config->{DBIX_FETCHSIZEWARN} || 0; |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
my $self = bless { |
109
|
|
|
|
|
|
|
DSN => $config->{DSN}, |
110
|
|
|
|
|
|
|
DB_CATALOG => (defined $config->{DB_CATALOG}) |
111
|
|
|
|
|
|
|
? $config->{DB_CATALOG} |
112
|
|
|
|
|
|
|
: undef, |
113
|
|
|
|
|
|
|
DBH => $dbh, |
114
|
|
|
|
|
|
|
TEXT_COLUMN => ($config->{TEXT_WIDTH}) |
115
|
|
|
|
|
|
|
? 'varchar('. $config->{TEXT_WIDTH} .')' |
116
|
|
|
|
|
|
|
: 'varchar(50)', |
117
|
|
|
|
|
|
|
TEXT_WIDTH => $config->{TEXT_WIDTH} || 50, |
118
|
|
|
|
|
|
|
TAB => ($config->{TAB}) ? ' ' x $config->{TAB} : ' ', |
119
|
|
|
|
|
|
|
PK_NAME => $config->{PK_NAME} || 'id', |
120
|
|
|
|
|
|
|
FK_NAME => $config->{FK_NAME} || 'fk', |
121
|
|
|
|
|
|
|
TABLE_PREFIX => $config->{TABLE_PREFIX} || 'gen', |
122
|
|
|
|
|
|
|
DB_USERNAME => $config->{DB_USERNAME} || '', |
123
|
|
|
|
|
|
|
DB_PASSWORD => $config->{DB_PASSWORD} || '', |
124
|
|
|
|
|
|
|
_HEADERS => (defined $config->{SQL_HEADERS}) |
125
|
|
|
|
|
|
|
? $config->{SQL_HEADERS} : 1, |
126
|
|
|
|
|
|
|
_SELECTS => (defined $config->{SQL_SELECTS}) |
127
|
|
|
|
|
|
|
? $config->{SQL_SELECTS} : 1, |
128
|
|
|
|
|
|
|
_SQLITE => ($config->{DSN} =~ /dbi:sqlite/i) ? 1 : 0, |
129
|
|
|
|
|
|
|
}, $class; |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
$self->{REAL_ELEMENT_NAME_TABLE} = $self->mtn('element_names'); |
132
|
|
|
|
|
|
|
$self->{LINK_TABLE_NAMES_TABLE} = $self->mtn('link_tables'); |
133
|
|
|
|
|
|
|
$self->{ROOT_TABLE_N_PK_TABLE} = $self->mtn('root_n_pk'); |
134
|
|
|
|
|
|
|
return $self; |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
sub done { my $self = shift; $self->DESTROY; return $self}; |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
sub DESTROY { |
140
|
|
|
|
|
|
|
my $self = shift; |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
# NOTE : Recordset specific |
143
|
|
|
|
|
|
|
# Undef takes the name of a typglob and will destroy the array, the |
144
|
|
|
|
|
|
|
# hash, and the object. All unwritten data is written to the db. |
145
|
|
|
|
|
|
|
# All db connections are closed and all memory is freed. |
146
|
|
|
|
|
|
|
# DBIx::Recordset::Undef ($name) |
147
|
|
|
|
|
|
|
# DBIx::Recordset->Flush(); |
148
|
|
|
|
|
|
|
$self->{DBH}->disconnect() if ($self->{DBH}); |
149
|
|
|
|
|
|
|
$self->{_DOC}->dispose if ($self->{_DOC}); |
150
|
|
|
|
|
|
|
} |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
sub drop_tables { |
153
|
|
|
|
|
|
|
my $self = shift; |
154
|
|
|
|
|
|
|
return warn "Please add DB_CATALOG for the dsn supplied for dropping tables.\n" |
155
|
|
|
|
|
|
|
unless ( defined $self->{DB_CATALOG} ); |
156
|
|
|
|
|
|
|
my $dbh = $self->{DBH}; |
157
|
|
|
|
|
|
|
my ($driver, $dbname) = $self->{DSN} =~ /dbi:(\w+):\w+=(\w+)/i; |
158
|
|
|
|
|
|
|
my $sth; |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
if ( $self->{DB_CATALOG} == 0 ) { |
161
|
|
|
|
|
|
|
$sth = $dbh->table_info(undef, $dbname, undef, undef); |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
elsif ( $self->{DB_CATALOG} == 1 ) { |
164
|
|
|
|
|
|
|
$sth = $dbh->table_info($dbname, 'public', undef, undef); |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
else { |
167
|
|
|
|
|
|
|
return warn 'Please fix DB_CATALOG to boolean (1|0) in your dsn config file.' |
168
|
|
|
|
|
|
|
.'examples : Postgres 1, SQLite 0, Mysql 0' ."\n"; |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
my $d_tables; |
172
|
|
|
|
|
|
|
my $regex = '^'. $self->{TABLE_PREFIX} .'_\w+'; |
173
|
|
|
|
|
|
|
for my $rel (@{$sth->fetchall_arrayref({})}) { |
174
|
|
|
|
|
|
|
if ($rel->{TABLE_NAME} =~ /$regex/o) { |
175
|
|
|
|
|
|
|
push(@{ $d_tables }, $rel->{TABLE_NAME}); |
176
|
|
|
|
|
|
|
$dbh->do('DROP TABLE '. $rel->{TABLE_NAME}) |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
return $d_tables; |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
sub create_tables { |
183
|
|
|
|
|
|
|
my ($self, $file) = @_; |
184
|
|
|
|
|
|
|
my $statement; |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
open (F, $file) || die $!; |
187
|
|
|
|
|
|
|
while () { |
188
|
|
|
|
|
|
|
next if (/^(-|$)/); chomp; |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
if (s/;$//) { |
191
|
|
|
|
|
|
|
$self->{DBH}->do($statement . $_) || die $self->{DBH}->errstr; |
192
|
|
|
|
|
|
|
$statement = ''; |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
else { |
195
|
|
|
|
|
|
|
$statement .= $_; |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
close(F); |
199
|
|
|
|
|
|
|
return $self; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
sub _get_xml { |
203
|
|
|
|
|
|
|
my $self = shift; |
204
|
|
|
|
|
|
|
my $xmlfile = shift; |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
if (($self->{_XMLFILE}) and |
207
|
|
|
|
|
|
|
((!$xmlfile) or ($self->{_XMLFILE} eq $xmlfile))) { |
208
|
|
|
|
|
|
|
undef; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
elsif ($self->{DOC}) { |
211
|
|
|
|
|
|
|
$self->{_DOC}->dispose; |
212
|
|
|
|
|
|
|
$self->{_XMLFILE} = $xmlfile; |
213
|
|
|
|
|
|
|
$self->{_DOC} = $self->{_XMLPARSER}->parsefile($xmlfile); |
214
|
|
|
|
|
|
|
$self->{_HEAD} = $self->{_DOC}->getDocumentElement; |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
else { |
217
|
|
|
|
|
|
|
$self->{_XMLFILE} = $xmlfile; |
218
|
|
|
|
|
|
|
$self->{_XMLPARSER} = new XML::DOM::Parser; |
219
|
|
|
|
|
|
|
$self->{_DOC} = $self->{_XMLPARSER}->parsefile($xmlfile) || die "$!"; |
220
|
|
|
|
|
|
|
$self->{_HEAD} = $self->{_DOC}->getDocumentElement; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
return ($self->{_DOC},$self->{_HEAD}); |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
sub make_tables { |
226
|
|
|
|
|
|
|
my ($self, $xmlfile, $outfile) = @_; |
227
|
|
|
|
|
|
|
my ($doc, $head) = $self->_get_xml($xmlfile); |
228
|
|
|
|
|
|
|
my $mt = new XML::RDB::MakeTables($self, $doc, $head, $outfile); |
229
|
|
|
|
|
|
|
$mt->go; |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
sub populate_tables { |
233
|
|
|
|
|
|
|
my ($self, $xmlfile) = @_; |
234
|
|
|
|
|
|
|
my ($doc, $head) = $self->_get_xml($xmlfile); |
235
|
|
|
|
|
|
|
my $pt = new XML::RDB::PopulateTables($self, $doc, $head); |
236
|
|
|
|
|
|
|
$pt->go; |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
sub unpopulate_tables { |
240
|
|
|
|
|
|
|
my $self = shift; |
241
|
|
|
|
|
|
|
my $outfile = shift; |
242
|
|
|
|
|
|
|
my $root_n_pk = $self->get_root_n_pk_db(); |
243
|
|
|
|
|
|
|
my $ut = new XML::RDB::UnpopulateTables($self,$outfile); |
244
|
|
|
|
|
|
|
$ut->go($root_n_pk->{root}, $root_n_pk->{pk}); |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
sub unpopulate_schema { |
248
|
|
|
|
|
|
|
my $us = new XML::RDB::UnpopulateSchema(@_); |
249
|
|
|
|
|
|
|
$us->go; |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# |
253
|
|
|
|
|
|
|
# Blow thru all nodes & find 1:N relationships between them |
254
|
|
|
|
|
|
|
# Need to pass the root of yer XML::DOM tree |
255
|
|
|
|
|
|
|
# Quite pleasant really |
256
|
|
|
|
|
|
|
# Basically just count the number of duplicate elements under |
257
|
|
|
|
|
|
|
# this one - if > 1 then 1:N relationship |
258
|
|
|
|
|
|
|
# |
259
|
|
|
|
|
|
|
sub find_one_to_n_relationships { |
260
|
|
|
|
|
|
|
# NOTE : Replaced the recursive loop with goto's with a @stack |
261
|
|
|
|
|
|
|
my($self, $head) = @_; |
262
|
|
|
|
|
|
|
my (@stack,%saw,$one_to_n); |
263
|
|
|
|
|
|
|
TOP_REL: |
264
|
|
|
|
|
|
|
my $nodes = [ $head->getChildNodes ]; |
265
|
|
|
|
|
|
|
%saw = (); |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# Count duplicates |
268
|
|
|
|
|
|
|
grep($saw{$_->getNodeName}++, @{$nodes}); |
269
|
|
|
|
|
|
|
foreach (keys %saw) { |
270
|
|
|
|
|
|
|
next if /#text/; |
271
|
|
|
|
|
|
|
next if /#comment/; |
272
|
|
|
|
|
|
|
next if ($one_to_n->{$head->getNodeName}{$_}); |
273
|
|
|
|
|
|
|
if ($saw{$_} > 1) { |
274
|
|
|
|
|
|
|
$one_to_n->{$head->getNodeName}{$_} = 1; |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
TOPLESS_REL: |
279
|
|
|
|
|
|
|
# And recurse on down the road |
280
|
|
|
|
|
|
|
while (scalar(@{$nodes})) { |
281
|
|
|
|
|
|
|
my $sub_node = shift(@{$nodes}); |
282
|
|
|
|
|
|
|
next if ($sub_node->getNodeType == XML::DOM::TEXT_NODE); |
283
|
|
|
|
|
|
|
push(@stack, $nodes); |
284
|
|
|
|
|
|
|
$head = $sub_node; |
285
|
|
|
|
|
|
|
goto TOP_REL; |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
if (scalar(@stack) > 0) { |
289
|
|
|
|
|
|
|
$nodes = pop(@stack); |
290
|
|
|
|
|
|
|
goto TOPLESS_REL; |
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
# Done is Done |
294
|
|
|
|
|
|
|
return $one_to_n; |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
# |
298
|
|
|
|
|
|
|
# Helper function to dump the 'one_to_n' datastructure out |
299
|
|
|
|
|
|
|
# |
300
|
|
|
|
|
|
|
sub dump_otn { |
301
|
|
|
|
|
|
|
my $self = shift; |
302
|
|
|
|
|
|
|
my($otn, $char) = @_; |
303
|
|
|
|
|
|
|
my $ret; |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
foreach my $one (keys %$otn) { |
306
|
|
|
|
|
|
|
foreach my $many (keys %{$otn->{$one}}) { |
307
|
|
|
|
|
|
|
$ret .= "${char}\t$one -> $many\n"; |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
return $ret; |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# |
314
|
|
|
|
|
|
|
# 'mtn' = 'Make Table Name' - cleans up text so it's a valid |
315
|
|
|
|
|
|
|
# DB table name & adds TABLE_PREFIX |
316
|
|
|
|
|
|
|
# |
317
|
|
|
|
|
|
|
sub mtn { |
318
|
|
|
|
|
|
|
my($self, $base, $out) = @_; |
319
|
|
|
|
|
|
|
($out = $self->{TABLE_PREFIX} . "_$base") =~ y/#:\.-/_____/; |
320
|
|
|
|
|
|
|
lc $out; |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
# Normalize column/table names - No ':' or '-' & all lower case |
324
|
|
|
|
|
|
|
sub normalize { |
325
|
|
|
|
|
|
|
my($in,$out) = shift; |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
# Get rid of funny stuff |
328
|
|
|
|
|
|
|
# ($out = $in) =~ s/[#:\.-]/_/go; |
329
|
|
|
|
|
|
|
($out = $in) =~ y/#:\.-/_____/; |
330
|
|
|
|
|
|
|
lc $out; |
331
|
|
|
|
|
|
|
} |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
# |
334
|
|
|
|
|
|
|
# Pulls out root_n_pk data from the DB itself |
335
|
|
|
|
|
|
|
# |
336
|
|
|
|
|
|
|
sub get_root_n_pk_db { |
337
|
|
|
|
|
|
|
my $self = shift; |
338
|
|
|
|
|
|
|
my $rt_n_pk; |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
if ($self->{_SQLITE}) { |
341
|
|
|
|
|
|
|
my $sth = $self->{DBH}->prepare('select * from '. $self->{ROOT_TABLE_N_PK_TABLE}); |
342
|
|
|
|
|
|
|
$sth->execute(); |
343
|
|
|
|
|
|
|
my $row = $sth->fetch(); |
344
|
|
|
|
|
|
|
$rt_n_pk = { root => $row->[1], pk => $row->[0] } |
345
|
|
|
|
|
|
|
if ($row); |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
else { |
348
|
|
|
|
|
|
|
use vars qw(*root_n_pk); |
349
|
|
|
|
|
|
|
*root_n_pk = DBIx::Recordset->Search({'!DataSource' => $self->{DBH}, |
350
|
|
|
|
|
|
|
'!Table' => $self->{ROOT_TABLE_N_PK_TABLE}}); |
351
|
|
|
|
|
|
|
my $root; |
352
|
|
|
|
|
|
|
$root = $root_n_pk[0] if (@root_n_pk); |
353
|
|
|
|
|
|
|
$rt_n_pk = { root => $root->{root}, pk => $root->{pk} } if ($root); |
354
|
|
|
|
|
|
|
DBIx::Recordset::Undef ('*root_n_pk'); |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
return $rt_n_pk; |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# |
360
|
|
|
|
|
|
|
# Pulls out one_to_n data structures from the DB itself |
361
|
|
|
|
|
|
|
# |
362
|
|
|
|
|
|
|
sub get_one_to_n_db { |
363
|
|
|
|
|
|
|
my $self = shift; |
364
|
|
|
|
|
|
|
my %one_to_n; |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
if ($self->{_SQLITE}) { |
367
|
|
|
|
|
|
|
my $sth = $self->{DBH}->prepare('select * from '. $self->{LINK_TABLE_NAMES_TABLE}); |
368
|
|
|
|
|
|
|
$sth->execute(); |
369
|
|
|
|
|
|
|
while (my $row = $sth->fetch()) { |
370
|
|
|
|
|
|
|
$one_to_n{$row->[0]}{$row->[1]} = 1; |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
} |
373
|
|
|
|
|
|
|
else { |
374
|
|
|
|
|
|
|
use vars qw(*link_tables); |
375
|
|
|
|
|
|
|
*link_tables = DBIx::Recordset->Search({'!DataSource' => $self->{DBH}, |
376
|
|
|
|
|
|
|
'!Table' => $self->{LINK_TABLE_NAMES_TABLE}}); |
377
|
|
|
|
|
|
|
foreach my $links (@link_tables) { |
378
|
|
|
|
|
|
|
$one_to_n{$links->{one_table}}{$links->{many_table}} = 1; |
379
|
|
|
|
|
|
|
} |
380
|
|
|
|
|
|
|
# *link_tables->Flush(); |
381
|
|
|
|
|
|
|
DBIx::Recordset::Undef ('*link_tables'); |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
return \%one_to_n; |
384
|
|
|
|
|
|
|
} |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
sub get_real_element_names_db { |
387
|
|
|
|
|
|
|
my $self = shift; |
388
|
|
|
|
|
|
|
my $names; |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
if ($self->{_SQLITE}) { |
391
|
|
|
|
|
|
|
my $sth = $self->{DBH}->prepare('select * from '. $self->{REAL_ELEMENT_NAME_TABLE}); |
392
|
|
|
|
|
|
|
$sth->execute(); |
393
|
|
|
|
|
|
|
while (my $row = $sth->fetch()) { |
394
|
|
|
|
|
|
|
$names->{$row->[0]} = $row->[1]; |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
} |
397
|
|
|
|
|
|
|
else { |
398
|
|
|
|
|
|
|
use vars qw(*set); |
399
|
|
|
|
|
|
|
*set = DBIx::Recordset->Search({'!DataSource' => $self->{DBH}, |
400
|
|
|
|
|
|
|
'!Table' => $self->{REAL_ELEMENT_NAME_TABLE}}); |
401
|
|
|
|
|
|
|
foreach my $n (@set) { |
402
|
|
|
|
|
|
|
$names->{$n->{db_name}} = $n->{xml_name}; |
403
|
|
|
|
|
|
|
} |
404
|
|
|
|
|
|
|
# *link_tables->Flush(); |
405
|
|
|
|
|
|
|
DBIx::Recordset::Undef ('*set'); |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
return $names; |
408
|
|
|
|
|
|
|
} |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
# |
412
|
|
|
|
|
|
|
# Get the corresponding 'xml_name' from this 'db_name' |
413
|
|
|
|
|
|
|
# |
414
|
|
|
|
|
|
|
#sub get_xml_name { |
415
|
|
|
|
|
|
|
# my($self, $db_name) = @_; |
416
|
|
|
|
|
|
|
# my @set = DBIx::Recordset->Search({ |
417
|
|
|
|
|
|
|
# '!DataSource' => $self->{DBH}, |
418
|
|
|
|
|
|
|
# '!Table' => $self->{REAL_ELEMENT_NAME_TABLE}, |
419
|
|
|
|
|
|
|
# '$where' => 'db_name = ?', |
420
|
|
|
|
|
|
|
# '$values' => [ $db_name ], |
421
|
|
|
|
|
|
|
# }); |
422
|
|
|
|
|
|
|
# |
423
|
|
|
|
|
|
|
# $set[0]{xml_name}; |
424
|
|
|
|
|
|
|
#} |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
# |
427
|
|
|
|
|
|
|
# Recursive routine to unpopulate DB into in-memory data structure |
428
|
|
|
|
|
|
|
# |
429
|
|
|
|
|
|
|
# Takes a table name, PK of a row, & a hash ref of where in this |
430
|
|
|
|
|
|
|
# giant monstrous data in-memory data-structure we've created to |
431
|
|
|
|
|
|
|
# stick this row's values |
432
|
|
|
|
|
|
|
# |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
sub un_populate_table { |
435
|
|
|
|
|
|
|
my($self, $one_to_n, $table_name, $id, $put_it_here) = @_; |
436
|
|
|
|
|
|
|
my $match = "_" . $self->{PK_NAME}; |
437
|
|
|
|
|
|
|
my(@stack, $schema_names, $jj, $other_table, $fk_field, $links, $kk, $i ); |
438
|
|
|
|
|
|
|
use vars qw(*schema); # DBIx::Recordset likes GLOBs, yummy |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
TOP_TBL: |
441
|
|
|
|
|
|
|
( $jj, $other_table, $fk_field, $links, $kk, $i ) = (); |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
# This'll get the ball rolling... |
444
|
|
|
|
|
|
|
*schema = DBIx::Recordset->Search({'!DataSource' => $self->{DBH}, |
445
|
|
|
|
|
|
|
'!Table' => $table_name, |
446
|
|
|
|
|
|
|
$self->{PK_NAME} => $id |
447
|
|
|
|
|
|
|
}); |
448
|
|
|
|
|
|
|
# Any column that ends in '_id' we gotta assume is a 1:1 map |
449
|
|
|
|
|
|
|
# Any column that ends in '_value' is text in this element |
450
|
|
|
|
|
|
|
# Any column that ends in '_attribute' is an attribute |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
$schema_names = $schema->Names; |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
# foreach my $col (@{$schema->Names}) { |
455
|
|
|
|
|
|
|
for ($jj = 0; scalar(@{$schema_names}) > $jj; $jj++) { |
456
|
|
|
|
|
|
|
my $col = $schema_names->[$jj]; |
457
|
|
|
|
|
|
|
my $val = $schema[0]{$col}; |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
if ($col =~ /${match}$/o) { |
460
|
|
|
|
|
|
|
# 1:1 |
461
|
|
|
|
|
|
|
next if (!$val); |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
# Unpopulate sub table since this is a foreign key |
464
|
|
|
|
|
|
|
my $this_table; |
465
|
|
|
|
|
|
|
($this_table = $col) =~ s/${match}$//; |
466
|
|
|
|
|
|
|
# $self->un_populate_table($one_to_n, $other_table, $val, \%{$put_it_here->{$other_table}}); |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
push(@stack, [ $table_name, $id, $put_it_here, $schema_names, $jj, |
469
|
|
|
|
|
|
|
$other_table, $fk_field, $links, $kk, $i ]); |
470
|
|
|
|
|
|
|
($table_name, $id, $put_it_here ) = |
471
|
|
|
|
|
|
|
($this_table, $val, \%{$put_it_here->{$this_table}}); |
472
|
|
|
|
|
|
|
goto TOP_TBL; |
473
|
|
|
|
|
|
|
TOPLESS_TBL_1to1: |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
# Put the candle - back |
476
|
|
|
|
|
|
|
*schema = DBIx::Recordset->Search({'!DataSource' => $self->{DBH}, |
477
|
|
|
|
|
|
|
'!Table' => $table_name, |
478
|
|
|
|
|
|
|
$self->{PK_NAME} => $id}); |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
elsif ($col =~ /_value$/o) { |
482
|
|
|
|
|
|
|
# text between tag |
483
|
|
|
|
|
|
|
$put_it_here->{value} = $val if (defined $val); |
484
|
|
|
|
|
|
|
} |
485
|
|
|
|
|
|
|
elsif ($col =~ /_attribute$/o) { |
486
|
|
|
|
|
|
|
# attribute |
487
|
|
|
|
|
|
|
$put_it_here->{attribute}{$col} = $val if (defined $val); |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
elsif ($col eq $self->{PK_NAME}) { |
490
|
|
|
|
|
|
|
# PK - don't do anything |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
elsif ($col =~ /_$self->{FK_NAME}$/o ) { |
493
|
|
|
|
|
|
|
# FK - don't do anything |
494
|
|
|
|
|
|
|
} |
495
|
|
|
|
|
|
|
else { |
496
|
|
|
|
|
|
|
# Keep us honest |
497
|
|
|
|
|
|
|
die "I don't know what to do with column name $col = $val!\n"; |
498
|
|
|
|
|
|
|
} |
499
|
|
|
|
|
|
|
} |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
# Now get 1:N relationships |
502
|
|
|
|
|
|
|
if ($one_to_n->{$table_name}) { |
503
|
|
|
|
|
|
|
# Go thru each 'N' relationship table to this one |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
$links = [ keys %{$one_to_n->{$table_name}} ]; |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
for ($kk = 0; scalar(@{$links}) > $kk; $kk++) { |
508
|
|
|
|
|
|
|
my $link = $links->[$kk]; |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
# Look up other PK via our PK (which is an FK in the sub_table) |
511
|
|
|
|
|
|
|
$other_table = $self->mtn($link); |
512
|
|
|
|
|
|
|
$fk_field = $table_name."_" . $self->{FK_NAME}; |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
# Look up matching row in other (linked) table |
515
|
|
|
|
|
|
|
# Get rows from other table with a $fk_field matching |
516
|
|
|
|
|
|
|
# this one's |
517
|
|
|
|
|
|
|
*schema = DBIx::Recordset->Search({'!DataSource' => $self->{DBH}, |
518
|
|
|
|
|
|
|
'!Table' => $other_table, |
519
|
|
|
|
|
|
|
"$fk_field" => $id}); |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
$i = 0; |
522
|
|
|
|
|
|
|
while(my $other_pk = $schema[$i]{$self->{PK_NAME}}) { |
523
|
|
|
|
|
|
|
next if (!$other_pk); |
524
|
|
|
|
|
|
|
# use $i in name to keep 'em seperate |
525
|
|
|
|
|
|
|
# & recursively unpopulate that other table |
526
|
|
|
|
|
|
|
# $self->un_populate_table($one_to_n, $other_table, $other_pk, \%{$put_it_here->{$i}{"${other_table}"}}); |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
push(@stack, [ $table_name, $id, $put_it_here, $schema_names, $jj, |
529
|
|
|
|
|
|
|
$other_table, $fk_field, $links, $kk, $i ]); |
530
|
|
|
|
|
|
|
($table_name, $id, $put_it_here ) = |
531
|
|
|
|
|
|
|
($other_table, $other_pk, \%{$put_it_here->{$i}{"${other_table}"}}); |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
goto TOP_TBL; |
534
|
|
|
|
|
|
|
TOPLESS_TBL_FK: |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
# Put The Candle - Back |
537
|
|
|
|
|
|
|
*schema = DBIx::Recordset->Search({'!DataSource'=>$self->{DBH}, |
538
|
|
|
|
|
|
|
'!Table' => $other_table, |
539
|
|
|
|
|
|
|
"$fk_field" => $id}); |
540
|
|
|
|
|
|
|
$i++; |
541
|
|
|
|
|
|
|
} |
542
|
|
|
|
|
|
|
} |
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
if (scalar(@stack) > 0) { |
546
|
|
|
|
|
|
|
( $table_name, $id, $put_it_here, $schema_names, $jj, |
547
|
|
|
|
|
|
|
$other_table, $fk_field, $links, $kk, $i ) = @{pop(@stack)}; |
548
|
|
|
|
|
|
|
unless ($other_table) { goto TOPLESS_TBL_1to1; } |
549
|
|
|
|
|
|
|
else { goto TOPLESS_TBL_FK; } |
550
|
|
|
|
|
|
|
} |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
DBIx::Recordset::Undef ('*schema'); |
553
|
|
|
|
|
|
|
} |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
#sub un_populate_table { |
559
|
|
|
|
|
|
|
# my($self, $one_to_n, $table_name, $id, $put_it_here) = @_; |
560
|
|
|
|
|
|
|
# use vars qw(*schema); # DBIx::Recordset likes GLOBs, yummy |
561
|
|
|
|
|
|
|
# |
562
|
|
|
|
|
|
|
# # This'll get the ball rolling... |
563
|
|
|
|
|
|
|
# *schema = DBIx::Recordset->Search({'!DataSource' => $self->{DBH}, |
564
|
|
|
|
|
|
|
# '!Table' => $table_name, |
565
|
|
|
|
|
|
|
# $self->{PK_NAME} => $id |
566
|
|
|
|
|
|
|
# }); |
567
|
|
|
|
|
|
|
# # Any column that ends in '_id' we gotta assume is a 1:1 map |
568
|
|
|
|
|
|
|
# # Any column that ends in '_value' is text in this element |
569
|
|
|
|
|
|
|
# # Any column that ends in '_attribute' is an attribute |
570
|
|
|
|
|
|
|
# foreach my $col (@{$schema->Names}) { |
571
|
|
|
|
|
|
|
# my $val = $schema[0]{$col}; |
572
|
|
|
|
|
|
|
# my $match = "_" . $self->{PK_NAME}; |
573
|
|
|
|
|
|
|
# |
574
|
|
|
|
|
|
|
# if ($col =~ /${match}$/) { |
575
|
|
|
|
|
|
|
# # 1:1 |
576
|
|
|
|
|
|
|
# next if (!$val); |
577
|
|
|
|
|
|
|
# |
578
|
|
|
|
|
|
|
# # Unpopulate sub table since this is a foreign key |
579
|
|
|
|
|
|
|
# my $other_table; |
580
|
|
|
|
|
|
|
# ($other_table = $col) =~ s/${match}$//; |
581
|
|
|
|
|
|
|
# $self->un_populate_table($one_to_n, $other_table, $val, \%{$put_it_here->{$other_table}}); |
582
|
|
|
|
|
|
|
# # Put the candle - back |
583
|
|
|
|
|
|
|
# *schema = DBIx::Recordset->Search({'!DataSource' => $self->{DBH}, |
584
|
|
|
|
|
|
|
# '!Table' => $table_name, |
585
|
|
|
|
|
|
|
# $self->{PK_NAME} => $id}); |
586
|
|
|
|
|
|
|
# |
587
|
|
|
|
|
|
|
# } |
588
|
|
|
|
|
|
|
# elsif ($col =~ /_value$/) { |
589
|
|
|
|
|
|
|
# # text between tag |
590
|
|
|
|
|
|
|
# $put_it_here->{value} = $val if (defined $val); |
591
|
|
|
|
|
|
|
# } |
592
|
|
|
|
|
|
|
# elsif ($col =~ /_attribute$/) { |
593
|
|
|
|
|
|
|
# # attribute |
594
|
|
|
|
|
|
|
# $put_it_here->{attribute}{$col} = $val if (defined $val); |
595
|
|
|
|
|
|
|
# } |
596
|
|
|
|
|
|
|
# elsif ($col eq $self->{PK_NAME}) { |
597
|
|
|
|
|
|
|
# # PK - don't do anything |
598
|
|
|
|
|
|
|
# } |
599
|
|
|
|
|
|
|
# elsif ($col =~ /_$self->{FK_NAME}$/ ) { |
600
|
|
|
|
|
|
|
# # FK - don't do anything |
601
|
|
|
|
|
|
|
# } |
602
|
|
|
|
|
|
|
# else { |
603
|
|
|
|
|
|
|
# # Keep us honest |
604
|
|
|
|
|
|
|
# die "I don't know what to do with column name $col = $val!\n"; |
605
|
|
|
|
|
|
|
# } |
606
|
|
|
|
|
|
|
# } |
607
|
|
|
|
|
|
|
# |
608
|
|
|
|
|
|
|
# # Now get 1:N relationships |
609
|
|
|
|
|
|
|
# if ($one_to_n->{$table_name}) { |
610
|
|
|
|
|
|
|
# # Go thru each 'N' relationship table to this one |
611
|
|
|
|
|
|
|
# foreach my $link (keys %{$one_to_n->{$table_name}}) { |
612
|
|
|
|
|
|
|
# # Look up other PK via our PK (which is an FK in the sub_table) |
613
|
|
|
|
|
|
|
# my $other_table = $self->mtn($link); |
614
|
|
|
|
|
|
|
# my $fk_field = $table_name."_" . $self->{FK_NAME}; |
615
|
|
|
|
|
|
|
# |
616
|
|
|
|
|
|
|
# # Look up matching row in other (linked) table |
617
|
|
|
|
|
|
|
# # Get rows from other table with a $fk_field matching |
618
|
|
|
|
|
|
|
# # this one's |
619
|
|
|
|
|
|
|
# *schema = DBIx::Recordset->Search({'!DataSource' => $self->{DBH}, |
620
|
|
|
|
|
|
|
# '!Table' => $other_table, |
621
|
|
|
|
|
|
|
# "$fk_field" => $id}); |
622
|
|
|
|
|
|
|
# my $i = 0; |
623
|
|
|
|
|
|
|
# while(my $other_pk = $schema[$i]{$self->{PK_NAME}}) { |
624
|
|
|
|
|
|
|
# next if (!$other_pk); |
625
|
|
|
|
|
|
|
# # use $i in name to keep 'em seperate |
626
|
|
|
|
|
|
|
# # & recursively unpopulate that other table |
627
|
|
|
|
|
|
|
# $self->un_populate_table($one_to_n, $other_table, $other_pk, \%{$put_it_here->{$i}{"${other_table}"}}); |
628
|
|
|
|
|
|
|
# |
629
|
|
|
|
|
|
|
# # Put The Candle - Back |
630
|
|
|
|
|
|
|
# *schema = DBIx::Recordset->Search({'!DataSource'=>$self->{DBH}, |
631
|
|
|
|
|
|
|
# '!Table' => $other_table, |
632
|
|
|
|
|
|
|
# "$fk_field" => $id}); |
633
|
|
|
|
|
|
|
# $i++; |
634
|
|
|
|
|
|
|
# } |
635
|
|
|
|
|
|
|
# } |
636
|
|
|
|
|
|
|
# } |
637
|
|
|
|
|
|
|
## *schema->Flush(); |
638
|
|
|
|
|
|
|
# DBIx::Recordset::Undef ('*schema'); |
639
|
|
|
|
|
|
|
#} |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
1; |
642
|
|
|
|
|
|
|
__END__ |