| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# Minimalistic, yet fairly complete DBI Persister |
|
2
|
|
|
|
|
|
|
# Allow DB Persistence operations (insert(), load(), update(), delete(), |
|
3
|
|
|
|
|
|
|
# exists()) on a plain old hash (unblessed or blessed) without writing |
|
4
|
|
|
|
|
|
|
# classes, persistence code or SQL. |
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
# Author: olli.hollmen@gmail.com |
|
7
|
|
|
|
|
|
|
# License: Perl License |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
# StoredHash needs an OO instance of persister to function. |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
# Because insert, update (the vals we want to pers.) are instance specific |
|
12
|
|
|
|
|
|
|
# Possibly return an object or bare hash from preparation of ins/upd/del |
|
13
|
|
|
|
|
|
|
# With |
|
14
|
|
|
|
|
|
|
# - query |
|
15
|
|
|
|
|
|
|
# - vals (to pass to exec) |
|
16
|
|
|
|
|
|
|
# - attr (needed ?) |
|
17
|
|
|
|
|
|
|
# - Assigned ID ? |
|
18
|
|
|
|
|
|
|
# Make this object w. meth execute() ???? getid() |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# TODO: Change/Add pkey => idattr @pkv => @idv |
|
21
|
|
|
|
|
|
|
# Support Mappings (before storage as separate op ?) |
|
22
|
|
|
|
|
|
|
package StoredHash; |
|
23
|
3
|
|
|
3
|
|
115434
|
use Scalar::Util ('reftype'); # |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
356
|
|
|
24
|
3
|
|
|
3
|
|
1239
|
use Data::Dumper; |
|
|
3
|
|
|
|
|
11939
|
|
|
|
3
|
|
|
|
|
13252
|
|
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
#use strict; |
|
27
|
|
|
|
|
|
|
#use warnings; |
|
28
|
|
|
|
|
|
|
our $VERSION = '0.029'; |
|
29
|
|
|
|
|
|
|
# Module extraction config |
|
30
|
|
|
|
|
|
|
our $mecfg = {}; |
|
31
|
|
|
|
|
|
|
# Instance attr (create access ...) |
|
32
|
|
|
|
|
|
|
# Allow 'attr' to act as attr filter |
|
33
|
|
|
|
|
|
|
my @opta = ('dbh', 'table','pkey','autoid','autoprobe','simu','errstr', |
|
34
|
|
|
|
|
|
|
'seqname','debug',); # |
|
35
|
|
|
|
|
|
|
# TODO: Support sequence for Oracle / Postgres |
|
36
|
|
|
|
|
|
|
# seq_emp.NEXTVAL |
|
37
|
|
|
|
|
|
|
my $bkmeta = { |
|
38
|
|
|
|
|
|
|
#'mysql' => {'iq' => "SELECT LAST_INSERT_ID()",}, |
|
39
|
|
|
|
|
|
|
#'Sybase' => {'iq' => "SELECT \@\@identity",}, |
|
40
|
|
|
|
|
|
|
'Oracle' => { |
|
41
|
|
|
|
|
|
|
#'iq' => "SELECT \@\@identity", |
|
42
|
|
|
|
|
|
|
'sv' => '%s.NEXTVAL',}, # AS adid SET NOCOUNT OFF |
|
43
|
|
|
|
|
|
|
# Postgres ??? |
|
44
|
|
|
|
|
|
|
}; |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# Create New instance of StoredHash Persister. |
|
47
|
|
|
|
|
|
|
# Options in %opt must have |
|
48
|
|
|
|
|
|
|
# - pkey/idattr - array (ref) to reflect the identifying attrtibute(s) of |
|
49
|
|
|
|
|
|
|
# entry (single attr for numeric ids, multiple for composite key) |
|
50
|
|
|
|
|
|
|
# Optional attributes |
|
51
|
|
|
|
|
|
|
# - dbh - DBI connection to database. Not passing 'dbh' makes |
|
52
|
|
|
|
|
|
|
# methods insert/update/load/delete return the SQL query only (as a string) |
|
53
|
|
|
|
|
|
|
sub new { |
|
54
|
3
|
|
|
3
|
1
|
1150
|
my ($class, %opt) = @_; |
|
55
|
3
|
|
|
|
|
8
|
my $self = {}; |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
# Generate where by pkey OR use where |
|
58
|
|
|
|
|
|
|
#if ($opt{'where'}) {} |
|
59
|
|
|
|
|
|
|
# Moved for early bless |
|
60
|
3
|
|
|
|
|
8
|
bless($self, $class); |
|
61
|
|
|
|
|
|
|
# For Child loading / temp use |
|
62
|
3
|
50
|
|
|
|
13
|
if ($opt{'loose'}) {goto PASSPKEY;} |
|
|
0
|
|
|
|
|
0
|
|
|
63
|
3
|
50
|
|
|
|
12
|
if ($opt{'pkey'}) { |
|
|
0
|
|
|
|
|
0
|
|
|
64
|
3
|
|
|
|
|
14
|
$self->{'pkey'} = $opt{'pkey'}; |
|
65
|
|
|
|
|
|
|
# TODO: Do NOT cache WHERE id ... |
|
66
|
3
|
|
|
|
|
13
|
$self->{'where'} = whereid($self); # \%opt # join('AND', map({" $_ = ?";} pkeys(\%opt)); |
|
67
|
|
|
|
|
|
|
} |
|
68
|
|
|
|
|
|
|
else {die("Need pkey info");} |
|
69
|
3
|
|
|
|
|
36
|
PASSPKEY: |
|
70
|
|
|
|
|
|
|
# Validate seq. (Need additional params to note call for seq?) |
|
71
|
|
|
|
|
|
|
#if ($opt{'autoid'} eq 'seq') { |
|
72
|
|
|
|
|
|
|
# #$c{'seqcall'}; |
|
73
|
|
|
|
|
|
|
#} |
|
74
|
|
|
|
|
|
|
# Filter options to self |
|
75
|
|
|
|
|
|
|
@$self{@opta} = @opt{@opta}; |
|
76
|
|
|
|
|
|
|
|
|
77
|
3
|
|
|
|
|
11
|
return($self); |
|
78
|
|
|
|
|
|
|
} |
|
79
|
|
|
|
|
|
|
# Access error string that method may leave to object. |
|
80
|
|
|
|
|
|
|
# Notice that many methods throw exception (by die()) with |
|
81
|
|
|
|
|
|
|
# error message rather than leave it within object. |
|
82
|
|
|
|
|
|
|
sub errstr { |
|
83
|
0
|
|
|
0
|
1
|
0
|
my ($p, $v) = @_; |
|
84
|
0
|
0
|
|
|
|
0
|
if ($v) {$p->{'errstr'} = $v;} |
|
|
0
|
|
|
|
|
0
|
|
|
85
|
0
|
|
|
|
|
0
|
$p->{'errstr'}; |
|
86
|
|
|
|
|
|
|
} |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# Internal method for executing query $q by filling placeholders with |
|
89
|
|
|
|
|
|
|
# values passed in @$vals. |
|
90
|
|
|
|
|
|
|
# Optional $rett (usually not passed) can force a special return type |
|
91
|
|
|
|
|
|
|
# Some supported return force tags: |
|
92
|
|
|
|
|
|
|
# - 'count' - number of entries counted with count(*) query |
|
93
|
|
|
|
|
|
|
# - 'sth' - return statement handle ($sth), which will be used outside. |
|
94
|
|
|
|
|
|
|
# - 'hash' - return a hash entry (first entry of resultset) |
|
95
|
|
|
|
|
|
|
# - 'aoh' - return array of hashes reflecting result set. |
|
96
|
|
|
|
|
|
|
# By default (no $rett) returns the ($ok)value from $sth->execute(). |
|
97
|
|
|
|
|
|
|
# Also by default statement statement handle gets properly closed |
|
98
|
|
|
|
|
|
|
# (If requested return type was $sth, the caller should take care of |
|
99
|
|
|
|
|
|
|
# calling $sth->finish() |
|
100
|
|
|
|
|
|
|
sub qexecute { |
|
101
|
0
|
|
|
0
|
0
|
0
|
my ($p, $q, $vals, $rett) = @_; |
|
102
|
0
|
|
|
|
|
0
|
my $dbh = $p->{'dbh'}; |
|
103
|
0
|
|
|
|
|
0
|
my $sth; # Keep here to have avail in callbacks below |
|
104
|
0
|
0
|
0
|
|
|
0
|
if (!$dbh || $p->{'simu'}) { # |
|
105
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Terse = 1; |
|
106
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Indent = 0; |
|
107
|
0
|
|
|
|
|
0
|
print("SQL($p->{'table'}): $q\nPlaceholder Vals:".Dumper($vals)."\n"); |
|
108
|
0
|
|
|
|
|
0
|
return(0); |
|
109
|
|
|
|
|
|
|
} |
|
110
|
|
|
|
|
|
|
# Special Return value generators |
|
111
|
|
|
|
|
|
|
# These should also close the statement (if that is not returned) |
|
112
|
0
|
|
|
0
|
|
0
|
my $rets = { |
|
113
|
0
|
|
|
|
|
0
|
'count' => sub {my @a = $sth->fetchrow_array();$sth->finish();$a[0];}, |
|
|
0
|
|
|
|
|
0
|
|
|
114
|
0
|
|
|
0
|
|
0
|
'sth' => sub {return($sth);}, |
|
115
|
0
|
|
|
0
|
|
0
|
'hash' => sub {my $h = $sth->fetchrow_hashref();$sth->finish();$h;}, |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
116
|
0
|
|
|
0
|
|
0
|
'aoh' => sub {my $arr = $sth->fetchall_arrayref({});$sth->finish();$arr;}, |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
117
|
0
|
|
|
|
|
0
|
}; |
|
118
|
0
|
0
|
|
|
|
0
|
if (!$dbh) {$p->{'errstr'} = "No Connection !";return(0);} |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
119
|
0
|
0
|
|
|
|
0
|
if ($p->{'debug'}) {print("Full Q: $q\n");} |
|
|
0
|
|
|
|
|
0
|
|
|
120
|
|
|
|
|
|
|
# Prepare cached ? |
|
121
|
0
|
|
|
|
|
0
|
$sth = $dbh->prepare($q); |
|
122
|
0
|
0
|
|
|
|
0
|
if (!$sth) {die("Query ($q) Not prepared (".$dbh->errstr().")\n");} |
|
|
0
|
|
|
|
|
0
|
|
|
123
|
0
|
|
|
|
|
0
|
my $ok = $sth->execute(@$vals); |
|
124
|
0
|
0
|
|
|
|
0
|
if (!$ok) {die("Failed to execute ".$sth->errstr()."");} |
|
|
0
|
|
|
|
|
0
|
|
|
125
|
|
|
|
|
|
|
# Special return processing |
|
126
|
0
|
0
|
|
|
|
0
|
if (my $rcb = $rets->{$rett}) { |
|
127
|
|
|
|
|
|
|
#print("Special return by $rett ($rcb)\n"); |
|
128
|
0
|
|
|
|
|
0
|
return($rcb->()); |
|
129
|
|
|
|
|
|
|
} |
|
130
|
|
|
|
|
|
|
# Done with statement |
|
131
|
|
|
|
|
|
|
DWS: |
|
132
|
0
|
|
|
|
|
0
|
$sth->finish(); |
|
133
|
0
|
|
|
|
|
0
|
return($ok); |
|
134
|
|
|
|
|
|
|
} |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
################################################### |
|
137
|
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
# Store entry %$e (hash) inserting it as a new entry to a database. |
|
139
|
|
|
|
|
|
|
# Connection has been passed previously in construction of persister. |
|
140
|
|
|
|
|
|
|
# The table / schema to store to is either the one passed at |
|
141
|
|
|
|
|
|
|
# construction or derived from perl "blessing" of entry ($e). |
|
142
|
|
|
|
|
|
|
# Returns (ref to) an array of ID values for the entry that got stored (array |
|
143
|
|
|
|
|
|
|
# of one element for numeric primary key, multiple for composite key). |
|
144
|
|
|
|
|
|
|
sub insert { |
|
145
|
2
|
|
|
2
|
1
|
347
|
my ($p, $e) = @_; |
|
146
|
|
|
|
|
|
|
# No enforced internal validation |
|
147
|
2
|
50
|
|
|
|
4
|
eval {$p->validate();};if ($@) {$p->{'errstr'} = $@;return(1);} |
|
|
2
|
|
|
|
|
9
|
|
|
|
2
|
|
|
|
|
8
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
148
|
2
|
50
|
|
|
|
11
|
if (reftype($e) ne 'HASH') {$p->errstr("Entry need to be HASH");return(2);} |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
149
|
|
|
|
|
|
|
# Possibly also test for references (ds branching ?) eliminating them too |
|
150
|
2
|
|
|
|
|
9
|
my @ea = sort (keys(%$e)); |
|
151
|
2
|
|
|
|
|
9
|
my @ev = @$e{@ea}; # map() |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# Sequence - Add sequenced ID allocation ??? |
|
154
|
|
|
|
|
|
|
# $p->{'seqname'} |
|
155
|
2
|
50
|
33
|
|
|
9
|
if ($p->{'autoid'} && ($p->{'autoid'} eq 'seq')) { |
|
156
|
0
|
|
|
|
|
0
|
my $bkt = 'Oracle'; |
|
157
|
0
|
|
|
|
|
0
|
my @pka = pkeys($p); |
|
158
|
0
|
0
|
|
|
|
0
|
if (@pka > 1) {die("Multiple pkeys for sequenced ID");} |
|
|
0
|
|
|
|
|
0
|
|
|
159
|
|
|
|
|
|
|
# Add Sequence id attibute AND sequence call (unshift to front ?) |
|
160
|
|
|
|
|
|
|
# |
|
161
|
0
|
|
|
|
|
0
|
push(@ea, @pka); # $p->{'pkey'}->[0] |
|
162
|
0
|
|
|
|
|
0
|
push(@ev, sprintf("$bkmeta->{$bkt}->{'sv'}", $p->{'seqname'}) ); # |
|
163
|
|
|
|
|
|
|
#DEBUG:print("FMT: $bkmeta->{$bkt}->{'sv'} / $p->{'seqname'}\n"); |
|
164
|
|
|
|
|
|
|
} |
|
165
|
6
|
|
|
|
|
24
|
my $qp = "INSERT INTO $p->{'table'} (".join(',',@ea).") ". |
|
166
|
2
|
|
|
|
|
12
|
"VALUES (".join(',', map({'?';} @ea)).")"; |
|
167
|
2
|
50
|
|
|
|
7
|
if (!$p->{'dbh'}) {return($qp);} |
|
|
2
|
|
|
|
|
17
|
|
|
168
|
0
|
|
|
|
|
0
|
my $okid = $p->qexecute($qp, \@ev); |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# Auto-id - either UTO_INC style or Sequence (works for seq. too ? |
|
171
|
0
|
0
|
|
|
|
0
|
if ($p->{'autoid'}) { |
|
172
|
0
|
|
|
|
|
0
|
my @pka = pkeys($p); |
|
173
|
0
|
0
|
|
|
|
0
|
if (@pka != 1) {die(scalar(@pka)." Keys for Autoid");} |
|
|
0
|
|
|
|
|
0
|
|
|
174
|
0
|
|
|
|
|
0
|
my $id = $p->fetchautoid(); |
|
175
|
|
|
|
|
|
|
#$e->{$pka[0]} = $id; |
|
176
|
0
|
|
|
|
|
0
|
return(($id)); |
|
177
|
|
|
|
|
|
|
} |
|
178
|
|
|
|
|
|
|
# Seq ? |
|
179
|
|
|
|
|
|
|
#elsif () {} |
|
180
|
|
|
|
|
|
|
else { |
|
181
|
0
|
|
|
|
|
0
|
my @pka = pkeys($p); |
|
182
|
0
|
|
|
|
|
0
|
return(@$e{@pka}); |
|
183
|
|
|
|
|
|
|
} |
|
184
|
|
|
|
|
|
|
} |
|
185
|
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
# Update an existing entry in the database with values in %$e (hash). |
|
187
|
|
|
|
|
|
|
# Provide protection for AUTO-ID (to not be changed) ? |
|
188
|
|
|
|
|
|
|
# For flexibility the $idvals may be hash or array (reference) with |
|
189
|
|
|
|
|
|
|
# hash containing (all) id keys and id values or alternatively array |
|
190
|
|
|
|
|
|
|
# containing id values IN THE SAME ORDER as keys were passed during |
|
191
|
|
|
|
|
|
|
# construction (with idattr/pkey parameter). |
|
192
|
|
|
|
|
|
|
sub update { |
|
193
|
2
|
|
|
2
|
1
|
11
|
my ($p, $e, $idvals) = @_; |
|
194
|
2
|
|
|
|
|
4
|
my @pka; # To be visible to closure |
|
195
|
|
|
|
|
|
|
# Extract ID Values from hash OR array |
|
196
|
0
|
|
|
0
|
|
0
|
my $idvgens = { |
|
197
|
|
|
|
|
|
|
'HASH' => sub {@$idvals{@pka};}, |
|
198
|
2
|
|
|
2
|
|
7
|
'ARRAY' => sub {return(@$idvals);}, |
|
199
|
|
|
|
|
|
|
#'' => sub {[$idvals];} |
|
200
|
2
|
|
|
|
|
19
|
}; |
|
201
|
|
|
|
|
|
|
# No mandatory (internal) validation ? |
|
202
|
|
|
|
|
|
|
#eval {$p->validate();};if ($@) {$p->{'errstr'} = $@;return(1);} |
|
203
|
2
|
|
|
|
|
5
|
@pka = pkeys($p); |
|
204
|
2
|
50
|
|
|
|
11
|
if (reftype($e) ne 'HASH') {$p->{'errstr'} = "Entry need to be hash";return(2);} |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
205
|
|
|
|
|
|
|
# Probe the type of $idvals |
|
206
|
2
|
|
|
|
|
6
|
my $idrt = reftype($idvals); |
|
207
|
2
|
50
|
|
|
|
7
|
if ($p->{'debug'}) {print("Got IDs:".Dumper($idvals)." as $idrt\n");} |
|
|
0
|
|
|
|
|
0
|
|
|
208
|
|
|
|
|
|
|
#my @idv; |
|
209
|
2
|
|
|
|
|
3
|
my @pkv; |
|
210
|
2
|
50
|
|
|
|
23
|
if (my $idg = $idvgens->{$idrt}) {@pkv = $idg->();} |
|
|
2
|
|
|
|
|
6
|
|
|
|
0
|
|
|
|
|
0
|
|
|
211
|
|
|
|
|
|
|
#if ($idrt ne 'HASH') {$p->{'errstr'} = "ID needs to be hash";return(3);} |
|
212
|
|
|
|
|
|
|
else {die("Need IDs as HASH or ARRAY (reference, got '$idrt')");} |
|
213
|
|
|
|
|
|
|
#my ($cnt_a, $cnt_v) = (scalar(@pka), scalar(@pkv)); |
|
214
|
2
|
50
|
|
|
|
8
|
if (@pkv != @pka) {die("Number of ID keys and values not matching for update");} |
|
|
0
|
|
|
|
|
0
|
|
|
215
|
2
|
|
|
|
|
9
|
my @ea = sort(keys(%$e)); |
|
216
|
|
|
|
|
|
|
#my @pkv = @$idh{@pka}; # $idvals, Does not work for hash |
|
217
|
|
|
|
|
|
|
|
|
218
|
2
|
50
|
|
|
|
9
|
if (my @badid = $p->invalidids(@pkv)) {$p->{'errstr'} = "Bad ID Values found (@badid)";return(4);} |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
219
|
2
|
|
|
|
|
16
|
my $widstr = whereid($p); |
|
220
|
|
|
|
|
|
|
# Persistent object type |
|
221
|
2
|
|
|
|
|
10
|
my $pot = $p->{'table'}; |
|
222
|
2
|
50
|
|
|
|
6
|
if (!$pot) {die("No table for update");} |
|
|
0
|
|
|
|
|
0
|
|
|
223
|
2
|
|
|
|
|
7
|
my $qp = "UPDATE $pot SET ".join(',', map({" $_ = ?";} @ea)). |
|
|
6
|
|
|
|
|
25
|
|
|
224
|
|
|
|
|
|
|
" WHERE $widstr"; |
|
225
|
2
|
50
|
|
|
|
7
|
if (!$p->{'dbh'}) {return($qp);} |
|
|
2
|
|
|
|
|
18
|
|
|
226
|
|
|
|
|
|
|
# Combine Entry attr values and primary key values |
|
227
|
0
|
|
|
|
|
0
|
my $allv = [@$e{@ea}, @pkv]; |
|
228
|
0
|
|
|
|
|
0
|
$p->qexecute($qp, $allv); |
|
229
|
|
|
|
|
|
|
} |
|
230
|
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
# Delete an entry from database by passing $e as one of the following |
|
232
|
|
|
|
|
|
|
# - hash %$e - a hash containing (all) primary key(s) and their values. |
|
233
|
|
|
|
|
|
|
# - scalar $e - Entry ID for entry to be deleted |
|
234
|
|
|
|
|
|
|
# - array @$e - One or many primary key values for entry to be deleted |
|
235
|
|
|
|
|
|
|
# The recommended use is caae "array" as it is most versatile and most |
|
236
|
|
|
|
|
|
|
# consistent with other API methods. |
|
237
|
|
|
|
|
|
|
sub delete { |
|
238
|
2
|
|
|
2
|
1
|
10
|
my ($p, $e) = @_; |
|
239
|
|
|
|
|
|
|
#if (!ref($p->{'pkey'})) {die("PKA Not Known");} |
|
240
|
|
|
|
|
|
|
#eval {$p->validate();};if ($@) {$p->{'errstr'} = $@;return(1);} |
|
241
|
|
|
|
|
|
|
#my @pka = @{$p->{'pkey'}}; |
|
242
|
2
|
|
|
|
|
4
|
my @pka = pkeys($p); |
|
243
|
2
|
50
|
|
|
|
7
|
if (!$e) {die("Must have Identifier for delete()\n");} |
|
|
0
|
|
|
|
|
0
|
|
|
244
|
|
|
|
|
|
|
|
|
245
|
2
|
|
|
|
|
6
|
my $rt = reftype($e); |
|
246
|
2
|
|
|
|
|
7
|
my $pkc = $p->pkeycnt(); |
|
247
|
2
|
|
|
|
|
3
|
my @pkv; |
|
248
|
|
|
|
|
|
|
# $e Scalar, must have 1 pkey |
|
249
|
2
|
50
|
33
|
|
|
5368
|
if (!$rt && ($pkc == 1)) {@pkv = $e;} |
|
|
0
|
50
|
0
|
|
|
0
|
|
|
|
2
|
0
|
|
|
|
9
|
|
|
250
|
|
|
|
|
|
|
# Hash - extract primary keys |
|
251
|
0
|
|
|
|
|
0
|
elsif ($rt eq 'HASH') {@pkv = @$e{@pka};} |
|
252
|
|
|
|
|
|
|
# Array (of pk values) - check count matches |
|
253
|
0
|
|
|
|
|
0
|
elsif (($rt eq 'ARRAY') && ($pkc == scalar(@$e))) {@pkv = @$e;} |
|
254
|
|
|
|
|
|
|
else {die("No way to delete (without HASH or ARRAY for IDs)\n");} |
|
255
|
|
|
|
|
|
|
#NOTNEEDED:#my %pkh;@pkh{@pka} = @pkv; |
|
256
|
|
|
|
|
|
|
#my $wstr = join(' AND ', map({"$_ = ?";} @pka)); |
|
257
|
2
|
|
|
|
|
6
|
my $wstr = whereid($p); |
|
258
|
2
|
|
|
|
|
8
|
my $qp = "DELETE FROM $p->{'table'} WHERE $wstr"; |
|
259
|
2
|
50
|
|
|
|
9
|
if (!$p->{'dbh'}) {return($qp);} |
|
|
2
|
|
|
|
|
11
|
|
|
260
|
0
|
|
|
|
|
0
|
$p->qexecute($qp, \@pkv); |
|
261
|
|
|
|
|
|
|
} |
|
262
|
|
|
|
|
|
|
#my $dbh = $p->{'dbh'}; |
|
263
|
|
|
|
|
|
|
#my $sth = $dbh->prepare($qp); |
|
264
|
|
|
|
|
|
|
#if (!$sth) {print("Not prepared\n");} |
|
265
|
|
|
|
|
|
|
#$sth->execute(@pkv); |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# Test if an entry exists in the DB table with ID values passed in @$ids (array). |
|
268
|
|
|
|
|
|
|
# Returns 1 (entry exists) or 0 (does not exist) under normal conditions. |
|
269
|
|
|
|
|
|
|
sub exists { |
|
270
|
2
|
|
|
2
|
1
|
12
|
my ($p, $ids) = @_; |
|
271
|
2
|
50
|
|
|
|
8
|
my $whereid = $p->{'where'} ? $p->{'where'} : whereid($p); |
|
272
|
2
|
|
|
|
|
6
|
my $qp = "SELECT COUNT(*) FROM $p->{'table'} WHERE $whereid"; |
|
273
|
2
|
50
|
|
|
|
9
|
if (!$p->{'dbh'}) {return($qp);} |
|
|
2
|
|
|
|
|
7
|
|
|
274
|
0
|
|
|
|
|
0
|
$p->qexecute($qp, $ids, 'count'); |
|
275
|
|
|
|
|
|
|
} |
|
276
|
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
# Load entry from DB table by its IDs passed in @$ids (array, |
|
278
|
|
|
|
|
|
|
# single id typical sequece autoid pkey, multiple for composite primary key). |
|
279
|
|
|
|
|
|
|
# Entry will be loaded from single table passed at construction |
|
280
|
|
|
|
|
|
|
# (never as result of join from multiple tables). |
|
281
|
|
|
|
|
|
|
# Return entry as a hash (ref). |
|
282
|
|
|
|
|
|
|
sub load { |
|
283
|
2
|
|
|
2
|
1
|
11
|
my ($p, $ids) = @_; |
|
284
|
2
|
50
|
|
|
|
6
|
my $whereid = $p->{'where'} ? $p->{'where'} : whereid($p); |
|
285
|
2
|
|
|
|
|
7
|
my $qp = "SELECT * FROM $p->{'table'} WHERE $whereid"; |
|
286
|
2
|
50
|
|
|
|
7
|
if (!$p->{'dbh'}) {return($qp);} |
|
|
2
|
|
|
|
|
6
|
|
|
287
|
0
|
|
|
|
|
0
|
$p->qexecute($qp, $ids, 'hash'); |
|
288
|
|
|
|
|
|
|
} |
|
289
|
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
# Load a set of Entries from persistent storage. |
|
291
|
|
|
|
|
|
|
# Optionally provide simple "where filter hash" ($h), whose key-value criteria |
|
292
|
|
|
|
|
|
|
# is ANDed together to form the filter. |
|
293
|
|
|
|
|
|
|
# Return set / collection of entries as array of hashes. |
|
294
|
|
|
|
|
|
|
sub loadset { |
|
295
|
0
|
|
|
0
|
1
|
0
|
my ($p, $h, $sort) = @_; # filter, sortby |
|
296
|
0
|
|
|
|
|
0
|
my $w = ''; |
|
297
|
|
|
|
|
|
|
# if (@_ = 2 && ref($_[1]) eq 'HASH') {} |
|
298
|
0
|
0
|
|
|
|
0
|
if ($h) { |
|
299
|
0
|
|
|
|
|
0
|
my $wf = wherefilter($h); |
|
300
|
0
|
0
|
|
|
|
0
|
if (!$wf) {die("Empty Filter !");} |
|
|
0
|
|
|
|
|
0
|
|
|
301
|
0
|
|
|
|
|
0
|
$w = " WHERE $wf"; |
|
302
|
|
|
|
|
|
|
} |
|
303
|
0
|
0
|
|
|
|
0
|
if ($p->{'debug'}) {print("Loading set by '$w'\n");} |
|
|
0
|
|
|
|
|
0
|
|
|
304
|
0
|
|
|
|
|
0
|
my $qp = "SELECT * FROM $p->{'table'} $w"; |
|
305
|
0
|
|
|
|
|
0
|
$p->qexecute($qp, undef, 'aoh'); |
|
306
|
|
|
|
|
|
|
} |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
# Sample Column names from (current) DB table. |
|
309
|
|
|
|
|
|
|
# Return (ref to) array with field names in it. |
|
310
|
|
|
|
|
|
|
sub cols { |
|
311
|
0
|
|
|
0
|
1
|
0
|
my ($p) = @_; |
|
312
|
0
|
|
|
|
|
0
|
my $qp = "SELECT * FROM $p->{'table'} WHERE 1 = 0"; |
|
313
|
0
|
|
|
|
|
0
|
my $sth = $p->qexecute($qp, undef, 'sth'); |
|
314
|
0
|
|
|
|
|
0
|
my $cols = $sth->{'NAME'}; |
|
315
|
0
|
0
|
|
|
|
0
|
if (@_ == 1) {$sth->finish();return($cols);} |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
316
|
|
|
|
|
|
|
#elsif (@_ == 2) {$rett = $_[1];}; |
|
317
|
|
|
|
|
|
|
#if ($rett ne 'meta') {return(undef);} |
|
318
|
0
|
|
|
|
|
0
|
return(undef); |
|
319
|
|
|
|
|
|
|
} |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
# TODO: Load "tree" of entries rooted at an entry / entries (?) |
|
322
|
|
|
|
|
|
|
# Returns a set (array) of entries or single (root entry if |
|
323
|
|
|
|
|
|
|
# option $c{'fsingle'} - force single - is set. |
|
324
|
|
|
|
|
|
|
sub loadtree { |
|
325
|
0
|
|
|
0
|
0
|
0
|
my ($p, %c) = @_; |
|
326
|
0
|
|
|
|
|
0
|
my $chts = $c{'ctypes'}; |
|
327
|
0
|
|
|
|
|
0
|
my $w = $c{'w'}; |
|
328
|
0
|
|
|
|
|
0
|
my $fsingle = $c{'fsingle'}; # singleroot, uniroot |
|
329
|
0
|
|
|
|
|
0
|
my $arr = loadset($p, $w); |
|
330
|
0
|
|
|
|
|
0
|
for my $e (@$arr) {my $err = loadchildern($p, $e, %c);} |
|
|
0
|
|
|
|
|
0
|
|
|
331
|
|
|
|
|
|
|
# Choose return type |
|
332
|
0
|
0
|
|
|
|
0
|
if ($fsingle) {return($arr->[0]);} |
|
|
0
|
|
|
|
|
0
|
|
|
333
|
0
|
|
|
|
|
0
|
return($arr); |
|
334
|
|
|
|
|
|
|
} |
|
335
|
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
# TODO: Load Instances of child object types for entry. |
|
337
|
|
|
|
|
|
|
# Child types are defined in 'ctypes' array(ref) in options. |
|
338
|
|
|
|
|
|
|
# Array 'ctypes' may be one of the following |
|
339
|
|
|
|
|
|
|
# - Plain child type names (array of scalars), the rest is guessed |
|
340
|
|
|
|
|
|
|
# - Array of child type definition hashes with hashes defining following: |
|
341
|
|
|
|
|
|
|
# - table - The table / objectspace of child type |
|
342
|
|
|
|
|
|
|
# - parkey - Parent id field in child ("foreign key" field in rel DBs) |
|
343
|
|
|
|
|
|
|
# - memname - Mamber name to place the child collection into in parent entry |
|
344
|
|
|
|
|
|
|
# - Array of arrays with inner arrays containing 'table','parkey','memname' in |
|
345
|
|
|
|
|
|
|
# that order(!), (see above for meanings) |
|
346
|
|
|
|
|
|
|
# Return 0 for no errors |
|
347
|
|
|
|
|
|
|
sub loadchildren { |
|
348
|
0
|
|
|
0
|
0
|
0
|
my ($p, $e, %c) = @_; |
|
349
|
0
|
|
|
|
|
0
|
my $chts = $c{'ctypes'}; |
|
350
|
0
|
0
|
|
|
|
0
|
if (!$chts) {die("No Child types indicated");} |
|
|
0
|
|
|
|
|
0
|
|
|
351
|
0
|
0
|
|
|
|
0
|
if (ref($chts) ne 'ARRAY') {die("Child types not ARRAY");} |
|
|
0
|
|
|
|
|
0
|
|
|
352
|
0
|
|
|
|
|
0
|
my @ids = pkeyvals($p, $e); |
|
353
|
0
|
0
|
|
|
|
0
|
if (@ids > 1) {die("Loading not supported for composite keys");} |
|
|
0
|
|
|
|
|
0
|
|
|
354
|
0
|
|
|
|
|
0
|
my $dbh = $p->{'dbh'}; |
|
355
|
0
|
|
|
|
|
0
|
my $debug = $p->{'debug'}; |
|
356
|
0
|
|
|
|
|
0
|
for (@$chts) { |
|
357
|
|
|
|
|
|
|
#my $ct = $_; |
|
358
|
0
|
|
|
|
|
0
|
my $cfilter; |
|
359
|
|
|
|
|
|
|
# Use or create a complete hash ? |
|
360
|
0
|
|
|
|
|
0
|
my $cinfo = makecinfo($p, $_); |
|
361
|
0
|
0
|
|
|
|
0
|
if ($debug) {print(Dumper($cinfo));} |
|
|
0
|
|
|
|
|
0
|
|
|
362
|
|
|
|
|
|
|
# Load type by created filter |
|
363
|
0
|
|
|
|
|
0
|
my ($ct, $park, $memn) = @$cinfo{'table','parkey','memname',}; |
|
364
|
0
|
0
|
|
|
|
0
|
if (!$park) {} |
|
365
|
|
|
|
|
|
|
# Create where by parkey info |
|
366
|
|
|
|
|
|
|
#$cfilter = {$park => $ids[0]}; # What is par key - assume same as parent |
|
367
|
0
|
0
|
|
|
|
0
|
if (@$park != @ids) {die("Par and child key counts mismatch");} |
|
|
0
|
|
|
|
|
0
|
|
|
368
|
0
|
|
|
|
|
0
|
@$cfilter{@$park} = @ids; |
|
369
|
|
|
|
|
|
|
#my $cfilter = |
|
370
|
|
|
|
|
|
|
# Take a shortcut by not providing pkey |
|
371
|
0
|
|
|
|
|
0
|
my $shc = StoredHash->new('table' => $ct, 'pkey' => [], |
|
372
|
|
|
|
|
|
|
'dbh' => $dbh, 'loose' => 1, 'debug' => $debug); |
|
373
|
0
|
|
|
|
|
0
|
my $carr = $shc->loadset($cfilter); |
|
374
|
0
|
0
|
0
|
|
|
0
|
if (!$carr || !@$carr) {next;} |
|
|
0
|
|
|
|
|
0
|
|
|
375
|
|
|
|
|
|
|
#if ($debug) {print("Got Children".Dumper($arr));} |
|
376
|
0
|
|
|
|
|
0
|
$e->{$memn} = $carr; |
|
377
|
|
|
|
|
|
|
# Blessing |
|
378
|
0
|
0
|
|
|
|
0
|
if (my $bto = $cinfo->{'blessto'}) {map({bless($_, $bto);} @$carr);} |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
379
|
|
|
|
|
|
|
# Circular Ref from child to parent ? |
|
380
|
|
|
|
|
|
|
#if (my $pla = $cinfo->{'parlinkattr'}) {map({$_->{$pla} = $e;} @$carr);} |
|
381
|
|
|
|
|
|
|
} |
|
382
|
|
|
|
|
|
|
# Autobless Children ? |
|
383
|
0
|
|
|
|
|
0
|
return(0); |
|
384
|
|
|
|
|
|
|
} |
|
385
|
|
|
|
|
|
|
# Internal method for using or making up Child relationship information |
|
386
|
|
|
|
|
|
|
# for loading related entities. |
|
387
|
|
|
|
|
|
|
sub makecinfo { |
|
388
|
0
|
|
|
0
|
0
|
0
|
my ($p, $cv) = @_; |
|
389
|
|
|
|
|
|
|
# Support array with: 'table','parkey','memname' |
|
390
|
0
|
0
|
|
|
|
0
|
if (ref($cv) eq 'ARRAY') { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
391
|
0
|
|
|
|
|
0
|
my $cinfo; |
|
392
|
0
|
0
|
|
|
|
0
|
if (@$cv != 3) {die("Need table, parkey, memname in array");} |
|
|
0
|
|
|
|
|
0
|
|
|
393
|
0
|
|
|
|
|
0
|
@$cinfo{'table','parkey','memname'} = @$cv; |
|
394
|
0
|
|
|
|
|
0
|
return($cinfo); |
|
395
|
|
|
|
|
|
|
} |
|
396
|
|
|
|
|
|
|
# Assume all is there (could validate and provide missing) |
|
397
|
0
|
|
|
|
|
0
|
elsif (ref($cv) eq 'HASH') { |
|
398
|
0
|
|
|
|
|
0
|
my @a = ('table','parkey','memname'); |
|
399
|
|
|
|
|
|
|
# Try guess parkey ? |
|
400
|
0
|
0
|
|
|
|
0
|
if (!$cv->{'parkey'}) {$cv->{'parkey'} = [pkeys($p)];} |
|
|
0
|
|
|
|
|
0
|
|
|
401
|
0
|
0
|
|
|
|
0
|
for (@a) {if (!$cv->{$_}) {die("Missing '$_' in cinfo");}} |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
402
|
0
|
|
|
|
|
0
|
return($cv); |
|
403
|
|
|
|
|
|
|
} |
|
404
|
|
|
|
|
|
|
elsif (ref($cv) ne '') {die("child type Not scalar (or hash)");} |
|
405
|
|
|
|
|
|
|
################## Make up |
|
406
|
0
|
|
|
|
|
0
|
my $ctab = $cv; |
|
407
|
0
|
|
|
|
|
0
|
my $memname = $ctab; # Default memname to child type name (Plus 's') ? |
|
408
|
|
|
|
|
|
|
# Guess by parent |
|
409
|
0
|
|
|
|
|
0
|
my $parkey = [pkeys($p)]; |
|
410
|
0
|
|
|
|
|
0
|
my $cinfo = {'table' => $ctab, 'parkey' => $parkey, 'memname' => $ctab,}; |
|
411
|
0
|
|
|
|
|
0
|
return($cinfo); |
|
412
|
|
|
|
|
|
|
} |
|
413
|
|
|
|
|
|
|
################################################################### |
|
414
|
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
# Internal Persister validator for the absolutely mandatory properties of |
|
416
|
|
|
|
|
|
|
# persister object itself. |
|
417
|
|
|
|
|
|
|
# Doesn't not validate entry |
|
418
|
|
|
|
|
|
|
sub validate { |
|
419
|
2
|
|
|
2
|
0
|
3
|
my ($p) = @_; |
|
420
|
2
|
50
|
|
|
|
11
|
if (ref($p->{'pkey'}) ne 'ARRAY') {die("PK Attributes Not Known\n");} |
|
|
0
|
|
|
|
|
0
|
|
|
421
|
|
|
|
|
|
|
# Allow table to come from blessing (so NOT required) |
|
422
|
|
|
|
|
|
|
#if (!$p->{'table'}) {die("No Table\n");} |
|
423
|
2
|
50
|
|
|
|
18
|
if ($p->{'simu'}) {return;} |
|
|
0
|
|
|
|
|
0
|
|
|
424
|
|
|
|
|
|
|
# Do NOT Require conenction |
|
425
|
|
|
|
|
|
|
#if (!ref($p->{'dbh'})) {die("NO dbh to act on\n");} # ne 'DBI' |
|
426
|
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
} |
|
428
|
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
# Internal method for returning array of id keys (Real array, not ref). |
|
430
|
|
|
|
|
|
|
sub pkeys { |
|
431
|
13
|
|
|
13
|
0
|
20
|
my ($p) = @_; |
|
432
|
13
|
|
|
|
|
39
|
my $prt = reftype($p); |
|
433
|
13
|
50
|
|
|
|
38
|
if ($prt ne 'HASH') { |
|
434
|
0
|
|
|
|
|
0
|
$|=1; |
|
435
|
0
|
|
|
|
|
0
|
print STDERR Dumper([caller(1)]); |
|
436
|
0
|
|
|
|
|
0
|
die("StoredHash Not a HASH (is '$p'/'$prt')"); |
|
437
|
|
|
|
|
|
|
} |
|
438
|
13
|
50
|
|
|
|
40
|
if (ref($p->{'pkey'}) ne 'ARRAY') {die("Primary keys not in an array");} |
|
|
0
|
|
|
|
|
0
|
|
|
439
|
|
|
|
|
|
|
#return($p->{'pkey'}); |
|
440
|
13
|
|
|
|
|
17
|
return(@{$p->{'pkey'}}); |
|
|
13
|
|
|
|
|
48
|
|
|
441
|
|
|
|
|
|
|
} |
|
442
|
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
# Return Primary key values (as real array) from hash %$e passed as parameter. |
|
444
|
|
|
|
|
|
|
# undef values are produced for non-existing keys. |
|
445
|
|
|
|
|
|
|
# Mostly used for internal operations (and maybe debugging). |
|
446
|
|
|
|
|
|
|
sub pkeyvals { |
|
447
|
0
|
|
|
0
|
1
|
0
|
my ($p, $e) = @_; |
|
448
|
0
|
|
|
|
|
0
|
my @pkeys = pkeys($p); |
|
449
|
0
|
|
|
|
|
0
|
@$e{@pkeys}; |
|
450
|
|
|
|
|
|
|
} |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
# TODO: Implement pulling last id from sequence |
|
453
|
|
|
|
|
|
|
sub fetchautoid { |
|
454
|
0
|
|
|
0
|
0
|
0
|
my ($p) = @_; |
|
455
|
0
|
|
|
|
|
0
|
my $dbh; |
|
456
|
|
|
|
|
|
|
#$dbh->{'Driver'}; # Need to test ? |
|
457
|
|
|
|
|
|
|
#DEV:print("AUTOID FETCH TO BE IMPLEMENTED\n");return(69); |
|
458
|
0
|
|
|
|
|
0
|
my $pot = $p->{'table'}; |
|
459
|
0
|
0
|
|
|
|
0
|
if (!$pot) {die("No table for fetching auto-ID");} |
|
|
0
|
|
|
|
|
0
|
|
|
460
|
0
|
0
|
|
|
|
0
|
if (!($dbh = $p->{'dbh'})) {die("No Connection for fetching ID");} |
|
|
0
|
|
|
|
|
0
|
|
|
461
|
0
|
|
|
|
|
0
|
$dbh->last_insert_id(undef, undef, $pot, undef); |
|
462
|
|
|
|
|
|
|
} |
|
463
|
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
sub pkeycnt { |
|
465
|
2
|
|
|
2
|
0
|
4
|
my ($p) = @_; |
|
466
|
|
|
|
|
|
|
#if (ref($p->{'pkey'}) ne 'ARRAY') {die("Primary keys not in an array");} |
|
467
|
|
|
|
|
|
|
#scalar(@{$p->{'pkey'}}); |
|
468
|
2
|
|
|
|
|
5
|
my @pkeys = pkeys($p); |
|
469
|
2
|
|
|
|
|
6
|
scalar(@pkeys); |
|
470
|
|
|
|
|
|
|
} |
|
471
|
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
# Internal method for checking for empty or undefined ID values. |
|
473
|
|
|
|
|
|
|
# In all reasonable databases and apps these are not valid values. |
|
474
|
|
|
|
|
|
|
sub invalidids { |
|
475
|
2
|
|
|
2
|
0
|
4
|
my ($p, @idv) = @_; |
|
476
|
2
|
50
|
|
|
|
5
|
my @badid = grep({!defined($_) || $_ eq '';} @idv); |
|
|
3
|
|
|
|
|
23
|
|
|
477
|
2
|
|
|
|
|
9
|
return(@badid); |
|
478
|
|
|
|
|
|
|
} |
|
479
|
|
|
|
|
|
|
# Generate SQL WHERE Clause for UPDATE based on primary keys of current type. |
|
480
|
|
|
|
|
|
|
# Return WHERE clause with id-attribute(s) and placeholder(s) (idkey = ?, ...), without the WHERE keyword. |
|
481
|
|
|
|
|
|
|
sub whereid { |
|
482
|
7
|
|
|
7
|
1
|
13
|
my ($p) = @_; |
|
483
|
|
|
|
|
|
|
# # Allow IDs to be hash OR array ?? Not because hash would req. to store order |
|
484
|
7
|
|
|
|
|
22
|
my @pka = pkeys($p); |
|
485
|
7
|
50
|
|
|
|
21
|
if (@pka < 1) {die("No Pkeys to create where ID clause");} |
|
|
0
|
|
|
|
|
0
|
|
|
486
|
|
|
|
|
|
|
# my $wstr = |
|
487
|
7
|
|
|
|
|
13
|
return join(' AND ', map({"$_ = ?";} @pka)); |
|
|
10
|
|
|
|
|
43
|
|
|
488
|
|
|
|
|
|
|
} |
|
489
|
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
sub sqlvalesc { |
|
491
|
0
|
|
|
0
|
0
|
0
|
my ($v) = @_; |
|
492
|
0
|
|
|
|
|
0
|
$v =~ s/'/\\'/g; |
|
493
|
0
|
|
|
|
|
0
|
$v; |
|
494
|
|
|
|
|
|
|
} |
|
495
|
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
# TODO: Create list for WHERE IN Clause based on some assumptions |
|
497
|
|
|
|
|
|
|
sub invalues { |
|
498
|
0
|
|
|
0
|
0
|
0
|
my ($vals) = @_; |
|
499
|
|
|
|
|
|
|
# Assume array ref validated outside |
|
500
|
0
|
0
|
|
|
|
0
|
if (ref($vals) eq 'ARRAY') {die("Not an array for invals");} |
|
|
0
|
|
|
|
|
0
|
|
|
501
|
|
|
|
|
|
|
# Escape within Quotes ? |
|
502
|
0
|
|
|
|
|
0
|
join(',', map({ |
|
503
|
0
|
0
|
|
|
|
0
|
if (/^\d+$/) {$_;} |
|
|
0
|
|
|
|
|
0
|
|
|
504
|
|
|
|
|
|
|
else { |
|
505
|
0
|
|
|
|
|
0
|
my $v = sqlvalesc($_); |
|
506
|
0
|
|
|
|
|
0
|
"'$v'"; |
|
507
|
|
|
|
|
|
|
} |
|
508
|
|
|
|
|
|
|
} @$vals)); |
|
509
|
|
|
|
|
|
|
} |
|
510
|
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
sub rangefilter { |
|
512
|
0
|
|
|
0
|
0
|
0
|
my ($attr, $v) = @_; |
|
513
|
|
|
|
|
|
|
# Or just even and sort, grab 2 at the time ? |
|
514
|
0
|
0
|
|
|
|
0
|
if (@$v != 2) {die("Range cannot be formed");} |
|
|
0
|
|
|
|
|
0
|
|
|
515
|
|
|
|
|
|
|
# Auto-arrange ??? |
|
516
|
|
|
|
|
|
|
#if ($v->[1] < $v->[0]) {$v = [$v->[1],$v->[0]];} |
|
517
|
|
|
|
|
|
|
# Detect need to escape (time vs. number) |
|
518
|
|
|
|
|
|
|
#"($attr >= $v->[0]) AND ($attr <= $v->[0])"; |
|
519
|
|
|
|
|
|
|
} |
|
520
|
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
# Generate simple WHERE filter by hash %$e. The keys are assumed to be attributes |
|
522
|
|
|
|
|
|
|
# of DB and values are embedded as values into SQL (as opposed to using placeholers). |
|
523
|
|
|
|
|
|
|
# To be perfect in escaping per attribute type info would be needed. |
|
524
|
|
|
|
|
|
|
# For now we do best effort heuristics (attr val \d+ is assumed |
|
525
|
|
|
|
|
|
|
# to be a numeric field in SQL, however 000002345 could actually |
|
526
|
|
|
|
|
|
|
# be content of a char/text/varchar field). |
|
527
|
|
|
|
|
|
|
# Return WHERE filter clause without WHERE keyword. |
|
528
|
|
|
|
|
|
|
sub wherefilter { |
|
529
|
0
|
|
|
0
|
1
|
0
|
my ($e, %c) = @_; |
|
530
|
0
|
|
|
|
|
0
|
my $w = ''; |
|
531
|
0
|
|
|
|
|
0
|
my $fop = ' AND '; |
|
532
|
|
|
|
|
|
|
#my $rnga = $c{'rnga'}; # Range attributes |
|
533
|
0
|
0
|
|
|
|
0
|
if (ref($e) ne 'HASH') {die("No hash for filter generation");} |
|
|
0
|
|
|
|
|
0
|
|
|
534
|
0
|
|
|
|
|
0
|
my @keys = sort keys(%$e); |
|
535
|
0
|
|
|
|
|
0
|
my @qc; |
|
536
|
|
|
|
|
|
|
# Assume hard values, treat everything as string (?) |
|
537
|
|
|
|
|
|
|
# TODO: forcestr ? |
|
538
|
0
|
|
|
|
|
0
|
@qc = map({ |
|
539
|
0
|
|
|
|
|
0
|
my $v = $e->{$_}; |
|
540
|
|
|
|
|
|
|
#my $rv = ref($v); |
|
541
|
|
|
|
|
|
|
#if ($rnga->{$_} && ($rv eq 'ARRAY') && (@$v == 2)) {rangefilter($_, $v);} |
|
542
|
0
|
0
|
|
|
|
0
|
if (ref($v) eq 'ARRAY') {" $_ IN (".invalues($v).") ";} |
|
|
0
|
0
|
|
|
|
0
|
|
|
|
0
|
0
|
|
|
|
0
|
|
|
543
|
|
|
|
|
|
|
# SQL Wildcard |
|
544
|
0
|
|
|
|
|
0
|
elsif ($v =~ /%/) {"$_ LIKE '$v'";} |
|
545
|
|
|
|
|
|
|
# Detect numeric (likely numeric, not perfect) |
|
546
|
0
|
|
|
|
|
0
|
elsif ($v =~ /^\d+$/) {"$_ = $v";} |
|
547
|
|
|
|
|
|
|
# Assume string |
|
548
|
|
|
|
|
|
|
else {"$_ = '".sqlvalesc($v)."'";} |
|
549
|
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
} @keys); |
|
551
|
0
|
|
|
|
|
0
|
return(join($fop, @qc)); |
|
552
|
|
|
|
|
|
|
} |
|
553
|
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
# Internal: Serialize all values (singles,multi) from a hash to an array |
|
555
|
|
|
|
|
|
|
# based on sorted key order. Multi-valued keys (with value being array reference) |
|
556
|
|
|
|
|
|
|
# add multiple items. |
|
557
|
|
|
|
|
|
|
sub allentvals { |
|
558
|
2
|
|
|
2
|
0
|
19
|
my ($h) = @_; |
|
559
|
0
|
|
|
|
|
0
|
map({ |
|
560
|
2
|
50
|
|
|
|
14
|
if (ref($h->{$_}) eq 'HASH') {();} |
|
|
6
|
50
|
|
|
|
24
|
|
|
|
0
|
|
|
|
|
0
|
|
|
561
|
0
|
|
|
|
|
0
|
elsif (ref($h->{$_}) eq 'ARRAY') {@{$h->{$_}};} |
|
|
6
|
|
|
|
|
19
|
|
|
562
|
|
|
|
|
|
|
else {($h->{$_});} |
|
563
|
|
|
|
|
|
|
} sort(keys(%$h))); |
|
564
|
|
|
|
|
|
|
} |
|
565
|
|
|
|
|
|
|
1; |