line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
=head1 NAME |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
StoredHash - Minimalistic, yet fairly complete DBI Persister with a definite NoSQL feel to it |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 SYNOPSIS |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
use StoredHash; |
8
|
|
|
|
|
|
|
use DBI; |
9
|
|
|
|
|
|
|
use Data::Dumper; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
my $dbh = DBI->connect("dbi:SQLite:dbname=/tmp/zoo.db"); |
12
|
|
|
|
|
|
|
# Lightweight demonstration of StoredHash in action (with SQLite) |
13
|
|
|
|
|
|
|
$dbh->do("CREATE TABLE animals (speciesid INTEGER NOT NULL PRIMARY KEY, name CHAR(16), limbcnt INTEGER, family CHAR(16))"); |
14
|
|
|
|
|
|
|
my $shp = StoredHash->new('table' => 'animals', 'pkey' => ['speciesid'], |
15
|
|
|
|
|
|
|
'autoid' => 1, 'dbh' => $dbh, 'debug' => 0); |
16
|
|
|
|
|
|
|
# Hash object to be stored |
17
|
|
|
|
|
|
|
my $monkey = {'name' => 'Common Monkey', 'limbcnt' => 5, 'family' => 'mammal',}; |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# Happens to return numeric id (because of auto-increment pkey / autoid) |
20
|
|
|
|
|
|
|
my $spid = $shp->insert($monkey); |
21
|
|
|
|
|
|
|
print("Created by: id=$spid\n"); |
22
|
|
|
|
|
|
|
# Load entry |
23
|
|
|
|
|
|
|
my $ent = $shp->load([$spid]); |
24
|
|
|
|
|
|
|
print("Fetched (by $spid): ".Dumper($ent)."\n"); |
25
|
|
|
|
|
|
|
# Get: {'name' => 'Common Monkey', 'speciesid' => 469, 'limbcnt' => 5,'family' => 'mammal',} |
26
|
|
|
|
|
|
|
# Fix error in entry (don't count tail to be limb) |
27
|
|
|
|
|
|
|
$ent->{'limbcnt'} = 4; |
28
|
|
|
|
|
|
|
# Update (with some redundant attributes that do not change) |
29
|
|
|
|
|
|
|
print("Update $ent->{'speciesid'}\n"); |
30
|
|
|
|
|
|
|
$shp->update($ent, [$ent->{'speciesid'}]); |
31
|
|
|
|
|
|
|
# Could reduce / optimize change to bare minimum: |
32
|
|
|
|
|
|
|
my %change = ('limbcnt' => 4); |
33
|
|
|
|
|
|
|
print("Reduce property value on $spid\n"); |
34
|
|
|
|
|
|
|
$shp->update(\%change, [$spid]); |
35
|
|
|
|
|
|
|
# Later ... (species dies extinct ?) |
36
|
|
|
|
|
|
|
#$shp->delete([$spid]); |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
# Test if we need to insert / update (based on presence in DB) |
39
|
|
|
|
|
|
|
my $id = 5987; |
40
|
|
|
|
|
|
|
my $invals = {'name' => 'Crow', 'limbcnt' => 4, 'family' => 'birds'}; |
41
|
|
|
|
|
|
|
print("Test Presence of Animal '$id'\n"); |
42
|
|
|
|
|
|
|
if ($shp->exists([$id])) {$shp->update($invals, [$id]);} |
43
|
|
|
|
|
|
|
else {$shp->insert($invals);} |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
##### Easy loading of sets / collections |
46
|
|
|
|
|
|
|
# Load all the animals |
47
|
|
|
|
|
|
|
my $animarr = $shp->loadset(); |
48
|
|
|
|
|
|
|
print("All Animals: ".Dumper($animarr)."\n"); |
49
|
|
|
|
|
|
|
# Load only mammals (by filter) |
50
|
|
|
|
|
|
|
my $mammarr = $shp->loadset({'family' => 'mammal'}); |
51
|
|
|
|
|
|
|
print("Mammals: ".Dumper($mammarr)."\n"); |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
=head1 DESCRIPTION |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
Allow DB Persistence operations (insert(), load(), update(), delete(), |
56
|
|
|
|
|
|
|
exists()) on a plain old hash (unblessed or blessed) without writing |
57
|
|
|
|
|
|
|
classes, persistence code or SQL. |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
Optionally StoredHash allows your classes to inherit peristence capability from StoredHash allowing your objects |
60
|
|
|
|
|
|
|
to call StoredHash persistence methoda via object directly. |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
=head1 GENERAL INFO ON StoredHash PERSISTENCE |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=over 4 |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=item * Connection is stored in persister. Thus there is no need to pass it as parameter to persister methods. |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=item * Composite keys are supported by StoredHash. Because of this id values are passed in array. Id values must |
70
|
|
|
|
|
|
|
be ordered the same as their attribute names suring construction (as passed in 'pkey' construction parameter). |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=item * Some persistence methods support 'attrs' parameter. This means "partial attributes" or "only these attributes" whatever the direction is |
73
|
|
|
|
|
|
|
persistence operation is. Examples: load(): load only these attributes, update(): update only these attributes, etc. |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=item * StoredHash is not validating the hash keys / attribute (or 'attrs' parameter above) against these attributes |
76
|
|
|
|
|
|
|
actually existing in DB schema. Caller of persistence methods is responsible validating the "fit" of hash to a schema. |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=back |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
Above principles are consistent across persistence methods. These details will be not repeated in method documentation. |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=cut |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# Maintain a good NoSQL feel with your SQL database. |
85
|
|
|
|
|
|
|
# ... a good nonrelation relationship |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# Author: olli.hollmen@gmail.com |
88
|
|
|
|
|
|
|
# License: Perl License |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
# StoredHash needs an OO instance of persister to function. |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# TODO: Because insert, update (the vals we want to pers.) are instance specific |
93
|
|
|
|
|
|
|
# Possibly return an object or bare hash from preparation of ins/upd/del |
94
|
|
|
|
|
|
|
# With |
95
|
|
|
|
|
|
|
# - query |
96
|
|
|
|
|
|
|
# - vals (to pass to exec) |
97
|
|
|
|
|
|
|
# - attr (needed ?) |
98
|
|
|
|
|
|
|
# - Assigned ID ? |
99
|
|
|
|
|
|
|
# Make this object w. meth execute() ???? getid() |
100
|
|
|
|
|
|
|
# http://www.nntp.perl.org/group/perl.dbi.dev/2010/03/msg5887.html |
101
|
|
|
|
|
|
|
# ANSI X3.135 and ISO/IEC 9075 |
102
|
|
|
|
|
|
|
# ftp://sqlstandards.org/SC32/SQL_Registry/ |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
# TODO: Change/Add pkey => idattr @pkv => @idv |
105
|
|
|
|
|
|
|
# Support Mappings (before storage as separate op ?) |
106
|
|
|
|
|
|
|
package StoredHash; |
107
|
5
|
|
|
5
|
|
93454
|
use Scalar::Util ('reftype'); # |
|
5
|
|
|
|
|
11
|
|
|
5
|
|
|
|
|
458
|
|
108
|
5
|
|
|
5
|
|
1116
|
use Data::Dumper; |
|
5
|
|
|
|
|
12196
|
|
|
5
|
|
|
|
|
315
|
|
109
|
|
|
|
|
|
|
our $hardval = 0; # 1= Call $dbh->do() 2=Return query |
110
|
5
|
|
|
5
|
|
41
|
use strict; |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
153
|
|
111
|
5
|
|
|
5
|
|
18
|
use warnings; |
|
5
|
|
|
|
|
8
|
|
|
5
|
|
|
|
|
25478
|
|
112
|
|
|
|
|
|
|
our $VERSION = '0.031'; |
113
|
|
|
|
|
|
|
# Module extraction config |
114
|
|
|
|
|
|
|
#our $mecfg = {}; |
115
|
|
|
|
|
|
|
# Instance attributes (create accessors ?) |
116
|
|
|
|
|
|
|
# Allow 'attr' to act as attr filter |
117
|
|
|
|
|
|
|
my @opta = ('dbh', 'table','pkey','autoid','autoprobe','simu','errstr', |
118
|
|
|
|
|
|
|
'seqname','debug',); # |
119
|
|
|
|
|
|
|
# TODO: Support sequence for Oracle / Postgres |
120
|
|
|
|
|
|
|
# seq_emp.NEXTVAL |
121
|
|
|
|
|
|
|
my $bkmeta = { |
122
|
|
|
|
|
|
|
#'mysql' => {'iq' => "SELECT LAST_INSERT_ID()",}, |
123
|
|
|
|
|
|
|
#'Sybase' => {'iq' => "SELECT \@\@identity",}, |
124
|
|
|
|
|
|
|
'Oracle' => { |
125
|
|
|
|
|
|
|
#'iq' => "SELECT \@\@identity", |
126
|
|
|
|
|
|
|
'sv' => '%s.NEXTVAL',}, # AS adid SET NOCOUNT OFF |
127
|
|
|
|
|
|
|
# Postgres ??? |
128
|
|
|
|
|
|
|
}; |
129
|
|
|
|
|
|
|
# Tentative class-level / static structures: |
130
|
|
|
|
|
|
|
# persister cache: $shpcache = {}; # Keyed by 'table' |
131
|
|
|
|
|
|
|
# query cached: $qcache = {}; # Allow prepared and params ? plain K-V or HoH ? |
132
|
|
|
|
|
|
|
# |
133
|
|
|
|
|
|
|
=head1 METHODS |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=head2 $shp = StoredHash->new(%opts); |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
Create new instance of StoredHash Persister. |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Keyword parameters in %opts: |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=over 4 |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
=item * 'pkey' - array (ref) to reflect the identifying attrtibute(s) of |
144
|
|
|
|
|
|
|
entry (e.g. single attr for numeric sequential ids, multiple for composite key) |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=item * 'dbh' - DBI connection to database (optional). Not passing 'dbh' makes |
147
|
|
|
|
|
|
|
methods insert/update/load/delete return the SQL query only (as a string) |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
=back |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=cut |
152
|
|
|
|
|
|
|
sub new { |
153
|
3
|
|
|
3
|
1
|
1851
|
my ($class, %opt) = @_; |
154
|
3
|
|
|
|
|
6
|
my $self = {}; |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
# Generate where by pkey OR use where |
157
|
|
|
|
|
|
|
#if ($opt{'where'}) {} |
158
|
|
|
|
|
|
|
# Moved for early bless |
159
|
3
|
|
|
|
|
6
|
bless($self, $class); |
160
|
|
|
|
|
|
|
# For Child loading / temp use |
161
|
3
|
50
|
|
|
|
426
|
if ($opt{'loose'}) {goto PASSPKEY;} |
|
0
|
|
|
|
|
0
|
|
162
|
3
|
50
|
|
|
|
8
|
if ($opt{'pkey'}) { |
|
0
|
|
|
|
|
0
|
|
163
|
3
|
|
|
|
|
13
|
$self->{'pkey'} = $opt{'pkey'}; |
164
|
|
|
|
|
|
|
# TODO: Do NOT cache WHERE id ... |
165
|
3
|
|
|
|
|
8
|
$self->{'where'} = whereid($self); # \%opt # join('AND', map({" $_ = ?";} pkeys(\%opt)); |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
else {die("Need pkey info");} |
168
|
3
|
|
|
|
|
20
|
PASSPKEY: |
169
|
|
|
|
|
|
|
# Validate seq. (Need additional params to note call for seq?) |
170
|
|
|
|
|
|
|
#if ($opt{'autoid'} eq 'seq') { |
171
|
|
|
|
|
|
|
# #$c{'seqcall'}; |
172
|
|
|
|
|
|
|
#} |
173
|
|
|
|
|
|
|
# Filter options to self |
174
|
|
|
|
|
|
|
@$self{@opta} = @opt{@opta}; |
175
|
|
|
|
|
|
|
|
176
|
3
|
|
|
|
|
10
|
return($self); |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
=head2 $shp->errstr($v) |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
Access error string that method may leave to object. |
182
|
|
|
|
|
|
|
Notice that many methods throw exception (by die()) with |
183
|
|
|
|
|
|
|
error message rather than leave it within object. |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=cut |
186
|
|
|
|
|
|
|
sub errstr { |
187
|
0
|
|
|
0
|
1
|
0
|
my ($p, $v) = @_; |
188
|
0
|
0
|
|
|
|
0
|
if ($v) {$p->{'errstr'} = $v;} |
|
0
|
|
|
|
|
0
|
|
189
|
0
|
|
|
|
|
0
|
$p->{'errstr'}; |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
# Internal method for executing query $q by filling placeholders with |
193
|
|
|
|
|
|
|
# values passed in @$vals. |
194
|
|
|
|
|
|
|
# Optional $rett (usually not passed) can force a special return type |
195
|
|
|
|
|
|
|
# Some supported return force tags: |
196
|
|
|
|
|
|
|
#=item * 'count' - number of entries counted with count(*) query |
197
|
|
|
|
|
|
|
#=item * 'sth' - return statement handle ($sth), which will be used outside. |
198
|
|
|
|
|
|
|
#=item * 'hash' - return a hash entry (first entry of resultset) |
199
|
|
|
|
|
|
|
#=item * 'aoh' - return array of hashes reflecting result set. |
200
|
|
|
|
|
|
|
# By default (no $rett) returns the ($ok)value from $sth->execute(). |
201
|
|
|
|
|
|
|
# Also by default statement statement handle gets properly closed |
202
|
|
|
|
|
|
|
# (If requested return type was $sth, the caller should take care of |
203
|
|
|
|
|
|
|
# calling $sth->finish() |
204
|
|
|
|
|
|
|
sub qexecute { |
205
|
0
|
|
|
0
|
0
|
0
|
my ($p, $q, $vals, $rett) = @_; |
206
|
0
|
|
|
|
|
0
|
my $dbh = $p->{'dbh'}; |
207
|
0
|
|
|
|
|
0
|
my $sth; # Keep here to have avail in callbacks below |
208
|
0
|
0
|
0
|
|
|
0
|
if (!$dbh || $p->{'simu'}) { # |
209
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Terse = 1; |
210
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Indent = 0; |
211
|
0
|
|
|
|
|
0
|
print(STDERR "SQL($p->{'table'}): $q\nPlaceholder Vals:".Dumper($vals)."\n"); |
212
|
0
|
|
|
|
|
0
|
return(0); |
213
|
|
|
|
|
|
|
} |
214
|
|
|
|
|
|
|
# Special Return value generators |
215
|
|
|
|
|
|
|
# These should also close the statement (if that is not returned) |
216
|
0
|
|
|
0
|
|
0
|
my $rets = { |
217
|
0
|
|
|
|
|
0
|
'count' => sub {my @a = $sth->fetchrow_array();$sth->finish();$a[0];}, |
|
0
|
|
|
|
|
0
|
|
218
|
0
|
|
|
0
|
|
0
|
'sth' => sub {return($sth);}, |
219
|
0
|
|
|
0
|
|
0
|
'hash' => sub {my $h = $sth->fetchrow_hashref();$sth->finish();$h;}, |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
220
|
0
|
|
|
0
|
|
0
|
'aoh' => sub {my $arr = $sth->fetchall_arrayref({});$sth->finish();$arr;}, |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
221
|
0
|
|
|
|
|
0
|
}; |
222
|
|
|
|
|
|
|
# $p->{'errstr'} =return(0); |
223
|
0
|
0
|
|
|
|
0
|
if (!$dbh) {die("SHP: No Connection !");} |
|
0
|
|
|
|
|
0
|
|
224
|
0
|
0
|
0
|
|
|
0
|
if ($p->{'debug'} && $vals) {print("Full Q: $q w. ".scalar(@$vals)." values\n");} |
|
0
|
|
|
|
|
0
|
|
225
|
|
|
|
|
|
|
# Prepare cached ? |
226
|
0
|
|
|
|
|
0
|
$sth = $dbh->prepare_cached($q); # print("CACHED\n"); |
227
|
0
|
0
|
|
|
|
0
|
if (!$sth) {die("Query ($q) Not prepared (".$dbh->errstr().")\n");} |
|
0
|
|
|
|
|
0
|
|
228
|
0
|
|
|
|
|
0
|
my $ok = $sth->execute(@$vals); |
229
|
0
|
0
|
|
|
|
0
|
if (!$ok) {die("Failed to execute\n - Query: $q\n - Vals: ".Dumper($vals)."\n - Message:\n".$sth->errstr()."");} |
|
0
|
|
|
|
|
0
|
|
230
|
|
|
|
|
|
|
# Special return processing. |
231
|
|
|
|
|
|
|
# TODO: Suppress Use of uninitialized value $rett in hash element |
232
|
0
|
0
|
|
|
|
0
|
if (!$rett) {$rett = '';} |
|
0
|
|
|
|
|
0
|
|
233
|
0
|
0
|
|
|
|
0
|
if (my $rcb = $rets->{$rett}) { |
234
|
|
|
|
|
|
|
#print("Special return by $rett ($rcb)\n"); |
235
|
0
|
|
|
|
|
0
|
return($rcb->()); |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
# Done with statement |
238
|
|
|
|
|
|
|
DWS: |
239
|
0
|
|
|
|
|
0
|
$sth->finish(); |
240
|
0
|
|
|
|
|
0
|
return($ok); |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
################################################### |
244
|
|
|
|
|
|
|
# Make this the "best-possible" fallback quote when quote() method from driver (via connection) |
245
|
|
|
|
|
|
|
# is not available. The surrounding quotes are included in the quoted string. |
246
|
|
|
|
|
|
|
# This aims to be SQL compliant as much as possible |
247
|
|
|
|
|
|
|
# Need looks like number (will fail sometimes) ? |
248
|
|
|
|
|
|
|
sub quote { |
249
|
0
|
|
|
0
|
0
|
0
|
my ($s) = @_; |
250
|
0
|
|
|
|
|
0
|
$s =~ s/\'/\'\'/g; |
251
|
0
|
|
|
|
|
0
|
$s =~ s/\n/\\n/g; |
252
|
0
|
|
|
|
|
0
|
"'".$s."'"; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
=head2 $shp->insert($e) |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
Store entry %$e (hash) inserting it as a new entry to a database. |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
Returns an array of ID values for the entry that got stored (array |
260
|
|
|
|
|
|
|
of one element for numeric primary key, multiple for composite key). |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=cut |
263
|
|
|
|
|
|
|
# (ref to) |
264
|
|
|
|
|
|
|
#Connection has been passed previously in construction of persister. |
265
|
|
|
|
|
|
|
#The table / schema to store to is either the one passed at |
266
|
|
|
|
|
|
|
#construction or derived from perl "blessing" of entry ($e). |
267
|
|
|
|
|
|
|
sub insert { |
268
|
2
|
|
|
2
|
1
|
6
|
my ($p, $e, %c) = @_; |
269
|
2
|
|
|
|
|
3
|
local $Data::Dumper::Terse = 1;local $Data::Dumper::Indent = 0; |
|
2
|
|
|
|
|
2
|
|
270
|
|
|
|
|
|
|
# No enforced internal validation |
271
|
2
|
|
|
|
|
2
|
eval {$p->validate();}; |
|
2
|
|
|
|
|
4
|
|
272
|
2
|
50
|
|
|
|
2
|
if ($@) {die("Persister validation error: $@");} # $p->{'errstr'} = $@;return(1); |
|
0
|
|
|
|
|
0
|
|
273
|
2
|
50
|
|
|
|
5
|
if (reftype($e) ne 'HASH') {die("Entry needs to be HASH");} # return(2); |
|
0
|
|
|
|
|
0
|
|
274
|
|
|
|
|
|
|
# Explicit attributes. Do not check for ref-valued attributes here. |
275
|
|
|
|
|
|
|
# (with an idea that caller must know what it is passing). |
276
|
2
|
50
|
|
|
|
5
|
if (my $ats = $c{'attrs'}) { |
277
|
0
|
0
|
|
|
|
0
|
if (ref($ats) ne 'ARRAY') {die("Passed 'attrs' must be an array");} |
|
0
|
|
|
|
|
0
|
|
278
|
0
|
|
|
|
|
0
|
%$e = map({($_, $e->{$_});} @$ats); # Reconfig $e content. |
|
0
|
|
|
|
|
0
|
|
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
# Possibly also test for references (ds branching ?) eliminating them too |
281
|
|
|
|
|
|
|
# In case some ARE found, make a copy, eliminate refs and mark copy as $e |
282
|
2
|
50
|
|
|
|
4
|
if (grep({ref($e->{$_});} keys(%$e))) { |
|
6
|
|
|
|
|
10
|
|
283
|
|
|
|
|
|
|
# Consider array serialization policy here or as a step before ? |
284
|
0
|
0
|
|
|
|
0
|
my %ec = map({ref($e->{$_}) ? () : ($_, $e->{$_});} keys(%$e)); |
|
0
|
|
|
|
|
0
|
|
285
|
0
|
|
|
|
|
0
|
$e = \%ec; |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
# Extract attrs and values (for non-ref attrs) |
288
|
2
|
|
|
|
|
6
|
my @ea = sort (keys(%$e)); |
289
|
2
|
|
|
|
|
5
|
my @ev = @$e{@ea}; # map() |
290
|
|
|
|
|
|
|
# To Support sequence we MUST precalc placeholders here. |
291
|
|
|
|
|
|
|
# In case of Sequence should place sequence call ... |
292
|
2
|
|
|
|
|
2
|
my @pha = map({'?';} @ea); |
|
6
|
|
|
|
|
16
|
|
293
|
|
|
|
|
|
|
# Sequence - Add sequenced ID allocation ??? |
294
|
|
|
|
|
|
|
# $p->{'seqname'} |
295
|
2
|
50
|
33
|
|
|
6
|
if ($p->{'autoid'} && ($p->{'autoid'} eq 'seq')) { |
296
|
0
|
|
|
|
|
0
|
my $bkt = 'Oracle'; |
297
|
0
|
|
|
|
|
0
|
my @pka = pkeys($p); |
298
|
0
|
0
|
|
|
|
0
|
if (@pka > 1) {die("Error: Multiple (composite) pkeys for sequenced ID");} |
|
0
|
|
|
|
|
0
|
|
299
|
|
|
|
|
|
|
# Add Sequence id attibute AND sequence call (unshift to front ?) |
300
|
0
|
|
|
|
|
0
|
push(@ea, @pka); # $p->{'pkey'}->[0] |
301
|
|
|
|
|
|
|
# Lookup Sequence Syntax (as printf format)for paticular DB Backend |
302
|
|
|
|
|
|
|
# Fixed INSERT Below to NOT have placeholder for sequence (placeholders calc'd above) |
303
|
|
|
|
|
|
|
# I case of sequence the counts of VALS vs @pha will be unbalanced (off by 1) |
304
|
0
|
|
|
|
|
0
|
push(@pha, sprintf("$bkmeta->{$bkt}->{'sv'}", $p->{'seqname'}) ); # |
305
|
|
|
|
|
|
|
#DEBUG:print("FMT: $bkmeta->{$bkt}->{'sv'} / $p->{'seqname'}\n"); |
306
|
|
|
|
|
|
|
} |
307
|
2
|
50
|
|
|
|
4
|
if ($StoredHash::hardval) { |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
#my $quote = $p->{'dbh'} ? ref($p->{'dbh'}).'::quote' : \"e; |
310
|
|
|
|
|
|
|
#DEBUG:print("QUOTE = $quote\n\n\n\n"); |
311
|
|
|
|
|
|
|
#OLD:my $quote = $p->{'dbh'}->can('quote') ? \&$p->{'dbh'}->quote : \&sqlvalesc; |
312
|
0
|
0
|
|
0
|
|
0
|
my $quoter = $p->{'dbh'}->can('quote') ? sub {$p->{'dbh'}->quote($_[0]);} : \"e; |
|
0
|
|
|
|
|
0
|
|
313
|
0
|
|
|
|
|
0
|
@pha = map({ |
314
|
|
|
|
|
|
|
#$p->{'dbh'}->quote($e->{$_}); |
315
|
0
|
|
|
|
|
0
|
$quoter->($e->{$_}); # OLD: Embed $dbh as $_[0] |
316
|
|
|
|
|
|
|
} @ea); |
317
|
|
|
|
|
|
|
} |
318
|
2
|
|
|
|
|
9
|
my $qp = "INSERT INTO $p->{'table'} (".join(',',@ea).") VALUES (".join(',', @pha).")"; |
319
|
|
|
|
|
|
|
# For now $StoredHash::hardval will always return query for the very efficient $dbh->do() |
320
|
|
|
|
|
|
|
# to execute the query. |
321
|
2
|
50
|
|
|
|
4
|
if (my $hv = $StoredHash::hardval) { |
322
|
0
|
0
|
|
|
|
0
|
if ($hv == 2) {return($qp);} # Return INSERT ... as-is |
|
0
|
0
|
|
|
|
0
|
|
323
|
|
|
|
|
|
|
elsif ($hv == 1) { |
324
|
0
|
|
|
|
|
0
|
my $ok = $p->{'dbh'}->do($qp); |
325
|
0
|
0
|
|
|
|
0
|
if (!$ok) {die("Failed do on hard query: $qp");} |
|
0
|
|
|
|
|
0
|
|
326
|
|
|
|
|
|
|
# Proceed to autoid |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
} |
329
|
|
|
|
|
|
|
#DEBUG:print(Dumper($p)); |
330
|
2
|
50
|
|
|
|
6
|
if ($p->{'debug'}) {print(STDERR "Ins.vals: ".Dumper(\@ev)."\n");} |
|
0
|
|
|
|
|
0
|
|
331
|
2
|
50
|
|
|
|
4
|
if (!$p->{'dbh'}) {return($qp);} # No conn. - return SQL |
|
2
|
|
|
|
|
6
|
|
332
|
|
|
|
|
|
|
|
333
|
0
|
|
|
|
|
0
|
my $okid; |
334
|
0
|
0
|
|
|
|
0
|
if (!$StoredHash::hardval) {$okid = $p->qexecute($qp, \@ev);} |
|
0
|
|
|
|
|
0
|
|
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
# Auto-id - either AUTO_INC style or Sequence (works for seq. too ?) |
337
|
0
|
0
|
|
|
|
0
|
if ($p->{'autoid'}) { |
338
|
0
|
|
|
|
|
0
|
my @pka = pkeys($p); |
339
|
0
|
0
|
|
|
|
0
|
if (@pka != 1) {die(scalar(@pka)." Keys for Autoid");} |
|
0
|
|
|
|
|
0
|
|
340
|
0
|
|
|
|
|
0
|
my $id = $p->fetchautoid(); |
341
|
|
|
|
|
|
|
#$e->{$pka[0]} = $id; |
342
|
0
|
|
|
|
|
0
|
return(($id)); |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
# Seq ? |
345
|
|
|
|
|
|
|
#elsif () {} |
346
|
|
|
|
|
|
|
# $p->pkeyvals($e); # wantarray ? |
347
|
|
|
|
|
|
|
else { |
348
|
0
|
|
|
|
|
0
|
my @pka = pkeys($p); |
349
|
0
|
|
|
|
|
0
|
return(@$e{@pka}); # wantarray ? @$e{@pka} : [@$e{@pka}]; |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
=head2 $shp->update($e, $ids, %opts); |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
Update an existing entry by ID(s) ($ids) in the database with values in hash %$e. |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
Return true for success, false for failure (direct $ok values from underlying |
358
|
|
|
|
|
|
|
$sth->execute() for 'autoid' => 1 ), |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=cut |
361
|
|
|
|
|
|
|
# Provide protection for AUTO-ID (to not be changed) ? |
362
|
|
|
|
|
|
|
#For flexibility the $idvals may be hash or array (reference) with |
363
|
|
|
|
|
|
|
#hash containing (all) id keys and id values or alternatively array |
364
|
|
|
|
|
|
|
#containing id values IN THE SAME ORDER as keys were passed during |
365
|
|
|
|
|
|
|
#construction (with idattr/pkey parameter). |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
sub update { |
368
|
2
|
|
|
2
|
1
|
688
|
my ($p, $e, $idvals, %c) = @_; |
369
|
2
|
|
|
|
|
3
|
local $Data::Dumper::Terse = 1;local $Data::Dumper::Indent = 0; |
|
2
|
|
|
|
|
2
|
|
370
|
2
|
|
|
|
|
2
|
my @pka; # To be visible to closure |
371
|
|
|
|
|
|
|
# Extract ID Values from hash OR array |
372
|
|
|
|
|
|
|
# TODO: Loosen requirement for hash to describe pk-attributes ? |
373
|
0
|
|
|
0
|
|
0
|
my $idvgens = { |
374
|
|
|
|
|
|
|
'HASH' => sub {@$idvals{@pka};}, |
375
|
2
|
|
|
2
|
|
3
|
'ARRAY' => sub {return(@$idvals);}, |
376
|
|
|
|
|
|
|
#'' => sub {[$idvals];} |
377
|
2
|
|
|
|
|
14
|
}; |
378
|
|
|
|
|
|
|
# No mandatory (internal) validation ? |
379
|
|
|
|
|
|
|
#eval {$p->validate();};if ($@) {$p->{'errstr'} = $@;return(1);} |
380
|
2
|
|
|
|
|
4
|
@pka = pkeys($p); # PKs from Persister |
381
|
2
|
50
|
|
|
|
8
|
if (reftype($e) ne 'HASH') {die("Entry not passed as hash");} # {$p->{'errstr'} = "Entry needs to be hash";return(2);} |
|
0
|
|
|
|
|
0
|
|
382
|
|
|
|
|
|
|
# Probe the type of $idvals |
383
|
2
|
|
|
|
|
4
|
my $idrt = reftype($idvals); |
384
|
2
|
50
|
|
|
|
4
|
if ($p->{'debug'}) {print("Got IDs:".Dumper($idvals)." as '$idrt'\n");} |
|
0
|
|
|
|
|
0
|
|
385
|
|
|
|
|
|
|
#my @idv; |
386
|
2
|
|
|
|
|
2
|
my @pkv; # PK Values |
387
|
|
|
|
|
|
|
# Handle kw params for bulk updates ? Example: 'w' => {...} if (!$idrt {$widstr = wherefilter();} |
388
|
2
|
50
|
|
|
|
5
|
if (my $idg = $idvgens->{$idrt}) {@pkv = $idg->();} |
|
2
|
|
|
|
|
3
|
|
|
0
|
|
|
|
|
0
|
|
389
|
|
|
|
|
|
|
#VERYOLD:if ($idrt ne 'HASH') {$p->{'errstr'} = "ID needs to be hash";return(3);} |
390
|
|
|
|
|
|
|
else {die("Need IDs as HASH or ARRAY (reference, got '$idrt')");} |
391
|
|
|
|
|
|
|
#my ($cnt_a, $cnt_v) = (scalar(@pka), scalar(@pkv)); |
392
|
2
|
50
|
|
|
|
5
|
if (@pkv != @pka) {die("Number of ID keys and ID values (".scalar(@pka).'/'.scalar(@pkv).") not matching for update ($p->{'table'})");} |
|
0
|
|
|
|
|
0
|
|
393
|
|
|
|
|
|
|
#OLDSIMPLE: my @ea = sort(keys(%$e)); |
394
|
2
|
|
|
|
|
2
|
my @ea; |
395
|
|
|
|
|
|
|
# Leave to caller to check: Verify that we DO NOT HAVE pkeys in set (?) |
396
|
|
|
|
|
|
|
#my $drive = 0; |
397
|
|
|
|
|
|
|
# Comply to explicit attributes passed as 'attrs' |
398
|
2
|
50
|
|
|
|
3
|
if (ref($c{'attrs'}) eq 'ARRAY') { |
|
2
|
|
|
|
|
8
|
|
399
|
|
|
|
|
|
|
#print("DRIVE ATTRIBUTES: "); |
400
|
0
|
|
|
|
|
0
|
@ea = @{$c{'attrs'}}; |
|
0
|
|
|
|
|
0
|
|
401
|
|
|
|
|
|
|
#$drive = 1; |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
# Use natural attributes from entry. |
404
|
|
|
|
|
|
|
else {@ea = sort(keys(%$e));} |
405
|
|
|
|
|
|
|
#print("DRIVE ATTRIBUTES = $drive: @ea\n");exit(1); |
406
|
|
|
|
|
|
|
#my @pkv = @$idh{@pka}; # $idvals, Does not work for hash |
407
|
|
|
|
|
|
|
# Check for undef/empty ID comps |
408
|
2
|
50
|
|
|
|
6
|
if (my @badid = $p->invalidids(@pkv)) { |
409
|
0
|
|
|
|
|
0
|
$p->{'errstr'} = "Bad ID Values found (@badid)";return(4); |
|
0
|
|
|
|
|
0
|
|
410
|
|
|
|
|
|
|
} |
411
|
2
|
50
|
|
|
|
4
|
my $widstr = whereid($p, $StoredHash::hardval ? (\@pkv) : () ); |
412
|
|
|
|
|
|
|
# Persistent object type |
413
|
2
|
|
|
|
|
3
|
my $pot = $p->{'table'}; |
414
|
2
|
50
|
|
|
|
5
|
if (!$pot) {die("No table for update");} |
|
0
|
|
|
|
|
0
|
|
415
|
|
|
|
|
|
|
# |
416
|
2
|
|
|
|
|
6
|
my $qp = "UPDATE $pot SET ".join(',', map({" $_ = ?";} @ea)). |
|
6
|
|
|
|
|
11
|
|
417
|
|
|
|
|
|
|
" WHERE $widstr"; |
418
|
2
|
|
|
|
|
2
|
my $dbh = $p->{'dbh'}; |
419
|
|
|
|
|
|
|
|
420
|
2
|
50
|
|
|
|
4
|
if (my $hv = $StoredHash::hardval) { |
421
|
|
|
|
|
|
|
#my $quote = $p->{'dbh'} ? ref($p->{'dbh'}).'::quote' : \"e; |
422
|
|
|
|
|
|
|
#TODO: |
423
|
0
|
0
|
|
0
|
|
0
|
my $quoter = $p->{'dbh'}->can('quote') ? sub {$p->{'dbh'}->quote($_[0]);} : \"e; |
|
0
|
|
|
|
|
0
|
|
424
|
0
|
|
|
|
|
0
|
my $set = join(',', map({ |
425
|
|
|
|
|
|
|
#" $_ = ".$dbh->quote($e->{$_}); |
426
|
0
|
|
|
|
|
0
|
" $_ = ".$quoter->($e->{$_}); |
427
|
|
|
|
|
|
|
} @ea) ); |
428
|
0
|
|
|
|
|
0
|
$qp = "UPDATE $pot SET $set WHERE $widstr"; # hard values embedded by whereid() |
429
|
0
|
0
|
|
|
|
0
|
if (!@pkv) {die("No ID:s for hardval=$StoredHash::hardval");} |
|
0
|
|
|
|
|
0
|
|
430
|
0
|
0
|
|
|
|
0
|
if ($hv == 2) {return($qp);} |
|
0
|
0
|
|
|
|
0
|
|
431
|
|
|
|
|
|
|
#elsif ($hv == 1) {return $dbh->do($qp);} |
432
|
|
|
|
|
|
|
elsif ($hv == 1) { |
433
|
0
|
|
|
|
|
0
|
my $ok = eval {$dbh->do($qp);}; |
|
0
|
|
|
|
|
0
|
|
434
|
0
|
0
|
|
|
|
0
|
if (!$ok) {die("Error DO(SQL: $qp): ".$@);} |
|
0
|
|
|
|
|
0
|
|
435
|
0
|
|
|
|
|
0
|
return($ok); |
436
|
|
|
|
|
|
|
} |
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
# Combine Entry attr values and primary key values |
439
|
2
|
|
|
|
|
5
|
my $allv = [@$e{@ea}, @pkv]; |
440
|
2
|
50
|
|
|
|
5
|
if ($p->{'debug'}) {print("Update allvals: ".Dumper($allv)."\n");} |
|
0
|
|
|
|
|
0
|
|
441
|
2
|
50
|
|
|
|
4
|
if (!$p->{'dbh'}) {return($qp);} |
|
2
|
|
|
|
|
12
|
|
442
|
0
|
|
|
|
|
0
|
my $ok; |
443
|
0
|
|
|
|
|
0
|
eval { |
444
|
0
|
|
|
|
|
0
|
$ok = $p->qexecute($qp, $allv); |
445
|
|
|
|
|
|
|
}; |
446
|
0
|
0
|
|
|
|
0
|
if ($@) {die("Error Executing: ".$p->{'dbh'}->errstr()."\n");} |
|
0
|
|
|
|
|
0
|
|
447
|
|
|
|
|
|
|
# Check all natural IDs (separate case for composite ?) |
448
|
|
|
|
|
|
|
#if (!$p->{'autoid'}) { |
449
|
|
|
|
|
|
|
# |
450
|
|
|
|
|
|
|
#} |
451
|
0
|
|
|
|
|
0
|
return($ok); |
452
|
|
|
|
|
|
|
} |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
=head2 $shp->delete($ids) OR $shp->delete($filter) |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
Delete an entry from database by passing one of the following: |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
=over 4 |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
=item * $ids - array with ID(s) for entry to be deleted (the usual use-case) |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=item * $filter - a hash with a where filter condition to delete by. |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
=back |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
Note that passing $filter haphazardly can cause massive destruction. Try to stick with passing $ids. |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=cut |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
# (filter) ... containing (all) primary key(s) and their values) |
471
|
|
|
|
|
|
|
#=item * array @$e - One or many primary key values for entry to be deleted |
472
|
|
|
|
|
|
|
#The recommended use is case "array" as it is most versatile and most |
473
|
|
|
|
|
|
|
#consistent with other API methods. |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
sub delete { |
476
|
2
|
|
|
2
|
1
|
643
|
my ($p, $e) = @_; |
477
|
|
|
|
|
|
|
#if (!ref($p->{'pkey'})) {die("PKA Not Known");} |
478
|
|
|
|
|
|
|
#eval {$p->validate();};if ($@) {$p->{'errstr'} = $@;return(1);} |
479
|
|
|
|
|
|
|
#my @pka = @{$p->{'pkey'}}; |
480
|
2
|
|
|
|
|
4
|
my @pka = pkeys($p); |
481
|
2
|
50
|
|
|
|
7
|
if (!$e) {die("Must have ID or filter for delete()\n");} |
|
0
|
|
|
|
|
0
|
|
482
|
|
|
|
|
|
|
|
483
|
2
|
|
|
|
|
4
|
my $rt = reftype($e); # Allows blessed |
484
|
2
|
|
|
|
|
7
|
my $pkc = $p->pkeycnt(); |
485
|
2
|
|
|
|
|
2
|
my @pkv;my $wstr; |
486
|
|
|
|
|
|
|
# $e Scalar, must have 1 pkey. Allow this forgiving behaviour for now. |
487
|
2
|
50
|
33
|
|
|
7
|
if (!$rt && ($pkc == 1)) {$e = [$e];$rt = 'ARRAY';} # OLD: {@pkv = $e;} |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
488
|
|
|
|
|
|
|
# Hash - OLD: extract primary keys @pkv = @$e{@pka}; |
489
|
|
|
|
|
|
|
# NEW: treat as filter |
490
|
2
|
50
|
0
|
|
|
3
|
if ($rt eq 'HASH') { |
|
|
0
|
|
|
|
|
|
491
|
2
|
50
|
|
|
|
12
|
if (!%$e) {die("Will not delete by empty filter (HASH) !");} |
|
0
|
|
|
|
|
0
|
|
492
|
|
|
|
|
|
|
# TODO: Share filter-case with load(), count() |
493
|
|
|
|
|
|
|
#my @ks = sort(keys(%$e)); |
494
|
2
|
|
|
|
|
6
|
my @ks = grep({!ref($e->{$_})} keys(%$e)); |
|
6
|
|
|
|
|
9
|
|
495
|
2
|
|
|
|
|
6
|
@pkv = @$e{@ks}; # In this context @vs => @pkv - Not really vals for primary keys, but filter |
496
|
|
|
|
|
|
|
#NOT:$wstr = wherefilter($e); |
497
|
2
|
|
|
|
|
3
|
$wstr = join(' AND ', map({"$_ = ?";} @ks)); |
|
6
|
|
|
|
|
11
|
|
498
|
|
|
|
|
|
|
} # |
499
|
|
|
|
|
|
|
# Array (of pk values) - check count matches |
500
|
0
|
|
|
|
|
0
|
elsif (($rt eq 'ARRAY') && ($pkc == scalar(@$e))) { |
501
|
0
|
|
|
|
|
0
|
@pkv = @$e; |
502
|
0
|
|
|
|
|
0
|
$wstr = whereid($p); |
503
|
|
|
|
|
|
|
} |
504
|
|
|
|
|
|
|
else {die("No way to delete (without ARRAY for IDs or HASH for filter)\n");} |
505
|
|
|
|
|
|
|
#NOTNEEDED:#my %pkh;@pkh{@pka} = @pkv; |
506
|
|
|
|
|
|
|
#my $wstr = join(' AND ', map({"$_ = ?";} @pka)); |
507
|
2
|
50
|
|
|
|
5
|
if (!$wstr) {die("Not proceding to delete with empty filter !");} |
|
0
|
|
|
|
|
0
|
|
508
|
2
|
|
|
|
|
5
|
my $qp = "DELETE FROM $p->{'table'} WHERE $wstr"; |
509
|
2
|
50
|
|
|
|
5
|
if (!$p->{'dbh'}) {return($qp);} |
|
2
|
|
|
|
|
6
|
|
510
|
0
|
|
|
|
|
0
|
$p->qexecute($qp, \@pkv); |
511
|
|
|
|
|
|
|
} |
512
|
|
|
|
|
|
|
#my $dbh = $p->{'dbh'}; |
513
|
|
|
|
|
|
|
#my $sth = $dbh->prepare($qp); |
514
|
|
|
|
|
|
|
#if (!$sth) {print("Not prepared\n");} |
515
|
|
|
|
|
|
|
#$sth->execute(@pkv); |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
=head2 $shp->exists($ids) |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
Test if an entry exists in the DB table with ID values passed in @$ids (array). |
520
|
|
|
|
|
|
|
Returns 1 (entry exists) or 0 (does not exist) under normal conditions. |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
=cut |
523
|
|
|
|
|
|
|
sub exists { |
524
|
2
|
|
|
2
|
1
|
649
|
my ($p, $ids) = @_; |
525
|
2
|
50
|
|
|
|
8
|
my $whereid = $p->{'where'} ? $p->{'where'} : whereid($p); |
526
|
2
|
|
|
|
|
5
|
my $qp = "SELECT COUNT(*) FROM $p->{'table'} WHERE $whereid"; |
527
|
2
|
50
|
|
|
|
6
|
if (!$p->{'dbh'}) {return($qp);} |
|
2
|
|
|
|
|
4
|
|
528
|
0
|
|
|
|
|
0
|
$p->qexecute($qp, $ids, 'count'); |
529
|
|
|
|
|
|
|
} |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
=head2 $shp->load($ids) |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
Load entry from DB table by its IDs passed in @$ids (array, |
534
|
|
|
|
|
|
|
single id typical sequece autoid pkey, multiple for composite primary key). |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
Entry will be loaded from single table passed at construction |
537
|
|
|
|
|
|
|
(never as result of join from multiple tables). |
538
|
|
|
|
|
|
|
Return entry as a hash (ref). |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
=cut |
541
|
|
|
|
|
|
|
sub load { |
542
|
2
|
|
|
2
|
1
|
685
|
my ($p, $ids) = @_; |
543
|
2
|
50
|
|
|
|
5
|
my $whereid = $p->{'where'} ? $p->{'where'} : whereid($p); |
544
|
|
|
|
|
|
|
# Allow loading unique entry generic filter |
545
|
2
|
50
|
|
|
|
8
|
if (reftype($ids) eq 'HASH') { |
546
|
|
|
|
|
|
|
#$whereid = wherefilter($ids); |
547
|
0
|
|
|
|
|
0
|
my @ks = grep({!ref($ids->{$_})} keys(%$ids)); |
|
0
|
|
|
|
|
0
|
|
548
|
0
|
|
|
|
|
0
|
my @vs = @$ids{@ks}; |
549
|
0
|
|
|
|
|
0
|
$whereid = join(' AND ', map({"$_ = ?";} @ks)); |
|
0
|
|
|
|
|
0
|
|
550
|
0
|
|
|
|
|
0
|
$ids = \@vs; |
551
|
|
|
|
|
|
|
# Need hard (unique values) for Certain DBs |
552
|
|
|
|
|
|
|
# TODO: Move to neater abstraction for $StoredHash::hardval |
553
|
0
|
0
|
0
|
|
|
0
|
if (my $hv = $StoredHash::hardval && $p->{'dbh'}) { |
554
|
0
|
|
|
|
|
0
|
my $i = -1; |
555
|
0
|
|
|
|
|
0
|
my $dbh = $p->{'dbh'}; |
556
|
0
|
|
|
|
|
0
|
$whereid = join(' AND ', map({$i++;"$_ = ".$dbh->quote($vs[$i]);} @ks)); |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
557
|
0
|
|
|
|
|
0
|
$ids = undef; |
558
|
|
|
|
|
|
|
} |
559
|
|
|
|
|
|
|
} |
560
|
2
|
|
|
|
|
6
|
my $qp = "SELECT * FROM $p->{'table'} WHERE $whereid"; |
561
|
2
|
50
|
|
|
|
4
|
if (!$p->{'dbh'}) {return($qp);} |
|
2
|
|
|
|
|
4
|
|
562
|
0
|
|
|
|
|
0
|
$p->qexecute($qp, $ids, 'hash'); |
563
|
|
|
|
|
|
|
#if (my $c = $p->{'class'}) {return(bless($h, $c));} |
564
|
|
|
|
|
|
|
} |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
=head2 $shp->loadset($filter, $sort, %opts); |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
Load a set of Entries from persistent storage. |
569
|
|
|
|
|
|
|
Optionally provide simple "where filter hash" ($filter), whose key-value criteria |
570
|
|
|
|
|
|
|
is ANDed together to form the filter. Allow attibutes (in $sort, arrayref) to define sorting for entry set. |
571
|
|
|
|
|
|
|
Allow %opts to contain 'attrs' (arrayref) to explicitly to define ettributes to load for each entry. |
572
|
|
|
|
|
|
|
Return set / collection of entries as array of hashes (AoH). |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
=cut |
575
|
|
|
|
|
|
|
sub loadset { |
576
|
0
|
|
|
0
|
1
|
0
|
my ($p, $h, $sort, %c) = @_; # filter, sortby |
577
|
0
|
|
|
|
|
0
|
my $w = ''; |
578
|
0
|
|
|
|
|
0
|
my $s = ''; |
579
|
|
|
|
|
|
|
# if (@_ = 2 && ref($_[1]) eq 'HASH') {} |
580
|
0
|
0
|
|
|
|
0
|
if ($h) { |
581
|
0
|
|
|
|
|
0
|
my $vals = []; # Parameteric values |
582
|
0
|
|
|
|
|
0
|
my $wf = wherefilter($h); # 'vals' => $vals |
583
|
0
|
0
|
|
|
|
0
|
if (!$wf) {die("Empty Filter !");} |
|
0
|
|
|
|
|
0
|
|
584
|
0
|
|
|
|
|
0
|
$w = " WHERE $wf"; |
585
|
|
|
|
|
|
|
} |
586
|
|
|
|
|
|
|
# TODO: How to trigger DESC sorting (Something in %c OR first or last elem of $sort) ? |
587
|
0
|
0
|
0
|
|
|
0
|
if (ref($sort) && @$sort) { |
588
|
0
|
|
|
|
|
0
|
my $stype = ''; # Default in SQL: |
589
|
|
|
|
|
|
|
#if ($sort->[0] eq '') {} |
590
|
0
|
|
|
|
|
0
|
$s = ' ORDER BY '.join(',', @$sort); |
591
|
|
|
|
|
|
|
} |
592
|
0
|
0
|
|
|
|
0
|
if ($p->{'debug'}) {print("Loading set by '$w'\n");} |
|
0
|
|
|
|
|
0
|
|
593
|
0
|
|
|
|
|
0
|
my $fldstr = '*'; |
594
|
0
|
0
|
|
|
|
0
|
if (ref($c{'attrs'}) eq 'ARRAY') { |
595
|
0
|
|
|
|
|
0
|
$fldstr = join(',', @{$c{'attrs'}}); |
|
0
|
|
|
|
|
0
|
|
596
|
|
|
|
|
|
|
} |
597
|
0
|
|
|
|
|
0
|
my $qp = "SELECT $fldstr FROM $p->{'table'} $w $s"; |
598
|
|
|
|
|
|
|
# Clean up query by (?): |
599
|
0
|
|
|
|
|
0
|
$qp =~ s/\s+$//; |
600
|
0
|
|
|
|
|
0
|
$p->qexecute($qp, undef, 'aoh'); |
601
|
|
|
|
|
|
|
} |
602
|
|
|
|
|
|
|
=head2 $shp->cols(%opts) |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
Sample Column names from (current) DB table. |
605
|
|
|
|
|
|
|
Return (ref to) array with field names in it. |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
%opts may contain KW parameter 'full' to get full DBI column_info() structure (See DBI for details). |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
=cut |
610
|
|
|
|
|
|
|
sub cols { |
611
|
0
|
|
|
0
|
0
|
0
|
my ($p, %c) = @_; |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
# Alternative for full table schema info |
614
|
|
|
|
|
|
|
# TODO: 'fullinfo' => 1 or 'meta' |
615
|
0
|
0
|
|
|
|
0
|
if ($c{'full'}) { |
616
|
0
|
|
|
|
|
0
|
my $dbh = $p->{'dbh'}; |
617
|
0
|
|
|
|
|
0
|
my $sth = $dbh->column_info(undef, undef, $p->{'table'}, '%'); |
618
|
0
|
|
|
|
|
0
|
my $fullinfo = $sth->fetchall_arrayref({}); |
619
|
0
|
|
|
|
|
0
|
$sth->finish(); |
620
|
0
|
|
|
|
|
0
|
return($fullinfo); |
621
|
|
|
|
|
|
|
} |
622
|
|
|
|
|
|
|
# Likely Most portable way of quering cols |
623
|
0
|
|
|
|
|
0
|
my $qp = "SELECT * FROM $p->{'table'} WHERE 1 = 0"; |
624
|
0
|
|
|
|
|
0
|
my $sth = $p->qexecute($qp, undef, 'sth'); |
625
|
0
|
|
|
|
|
0
|
my $cols = $sth->{'NAME'}; |
626
|
0
|
0
|
|
|
|
0
|
if (@_ == 1) {$sth->finish();return($cols);} |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
627
|
|
|
|
|
|
|
#elsif (@_ == 2) {$rett = $_[1];}; |
628
|
|
|
|
|
|
|
#if ($rett ne 'meta') {return(undef);} |
629
|
0
|
|
|
|
|
0
|
return(undef); |
630
|
|
|
|
|
|
|
} |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
# TODO: Load "tree" of entries rooted at an entry / entries (?) |
633
|
|
|
|
|
|
|
# Returns a set (array) of entries or single (root entry if |
634
|
|
|
|
|
|
|
# option $c{'fsingle'} - force single - is set. |
635
|
|
|
|
|
|
|
sub loadtree { |
636
|
0
|
|
|
0
|
0
|
0
|
my ($p, %c) = @_; |
637
|
0
|
|
|
|
|
0
|
my $chts = $c{'ctypes'}; |
638
|
0
|
|
|
|
|
0
|
my $w = $c{'w'}; |
639
|
0
|
|
|
|
|
0
|
my $fsingle = $c{'fsingle'}; # singleroot, uniroot |
640
|
0
|
|
|
|
|
0
|
my $arr = loadset($p, $w); |
641
|
0
|
|
|
|
|
0
|
for my $e (@$arr) {my $err = loadchildern($p, $e, %c);} |
|
0
|
|
|
|
|
0
|
|
642
|
|
|
|
|
|
|
# Choose return type |
643
|
0
|
0
|
|
|
|
0
|
if ($fsingle) {return($arr->[0]);} |
|
0
|
|
|
|
|
0
|
|
644
|
0
|
|
|
|
|
0
|
return($arr); |
645
|
|
|
|
|
|
|
} |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
# TODO: Load Instances of child object types for entry. |
648
|
|
|
|
|
|
|
# Child types are defined in 'ctypes' array(ref) in options. |
649
|
|
|
|
|
|
|
# Array 'ctypes' may be one of the following |
650
|
|
|
|
|
|
|
#=item * Plain child type names (array of scalars), the rest is guessed |
651
|
|
|
|
|
|
|
#=item * Array of child type definition hashes with hashes defining following: |
652
|
|
|
|
|
|
|
# =item * table - The table / objectspace of child type |
653
|
|
|
|
|
|
|
# =item * parkey - Parent id field in child ("foreign key" field in rel DBs) |
654
|
|
|
|
|
|
|
# =item * memname - Mamber name to place the child collection into in parent entry |
655
|
|
|
|
|
|
|
#=item * Array of arrays with inner arrays containing 'table','parkey','memname' in |
656
|
|
|
|
|
|
|
# that order(!), (see above for meanings) |
657
|
|
|
|
|
|
|
# Return 0 for no errors |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
# TODO: Maintain persister cache with rudimentary relational info. |
660
|
|
|
|
|
|
|
sub loadchildren { |
661
|
0
|
|
|
0
|
0
|
0
|
my ($p, $e, %c) = @_; |
662
|
0
|
|
|
|
|
0
|
my $chts = $c{'ctypes'}; |
663
|
0
|
0
|
|
|
|
0
|
if (!$chts) {die("No Child types indicated");} |
|
0
|
|
|
|
|
0
|
|
664
|
0
|
0
|
|
|
|
0
|
if (ref($chts) ne 'ARRAY') {die("Child types not ARRAY");} |
|
0
|
|
|
|
|
0
|
|
665
|
0
|
|
|
|
|
0
|
my @ids = pkeyvals($p, $e); |
666
|
0
|
0
|
|
|
|
0
|
if (@ids > 1) {die("Loading not supported for composite keys");} |
|
0
|
|
|
|
|
0
|
|
667
|
0
|
|
|
|
|
0
|
my $dbh = $p->{'dbh'}; |
668
|
0
|
|
|
|
|
0
|
my $debug = $p->{'debug'}; |
669
|
0
|
|
|
|
|
0
|
for (@$chts) { |
670
|
|
|
|
|
|
|
#my $ct = $_; |
671
|
0
|
|
|
|
|
0
|
my $cfilter; |
672
|
|
|
|
|
|
|
# Use or create a complete hash ? |
673
|
0
|
|
|
|
|
0
|
my $cinfo = makecinfo($p, $_); |
674
|
0
|
0
|
|
|
|
0
|
if ($debug) {print(Dumper($cinfo));} |
|
0
|
|
|
|
|
0
|
|
675
|
|
|
|
|
|
|
# Load type by created filter |
676
|
0
|
|
|
|
|
0
|
my ($ct, $park, $memn) = @$cinfo{'table','parkey','memname',}; |
677
|
0
|
0
|
|
|
|
0
|
if (!$park) {} |
678
|
|
|
|
|
|
|
# Create where by parkey info |
679
|
|
|
|
|
|
|
#$cfilter = {$park => $ids[0]}; # What is par key - assume same as parent |
680
|
0
|
0
|
|
|
|
0
|
if (@$park != @ids) {die("Par and child key counts mismatch");} |
|
0
|
|
|
|
|
0
|
|
681
|
0
|
|
|
|
|
0
|
@$cfilter{@$park} = @ids; |
682
|
|
|
|
|
|
|
#my $cfilter = |
683
|
|
|
|
|
|
|
# Take a shortcut by not providing pkey |
684
|
0
|
|
|
|
|
0
|
my $shc = StoredHash->new('table' => $ct, 'pkey' => [], |
685
|
|
|
|
|
|
|
'dbh' => $dbh, 'loose' => 1, 'debug' => $debug); |
686
|
0
|
|
|
|
|
0
|
my $carr = $shc->loadset($cfilter); |
687
|
0
|
0
|
0
|
|
|
0
|
if (!$carr || !@$carr) {next;} |
|
0
|
|
|
|
|
0
|
|
688
|
|
|
|
|
|
|
#if ($debug) {print("Got Children".Dumper($arr));} |
689
|
0
|
|
|
|
|
0
|
$e->{$memn} = $carr; |
690
|
|
|
|
|
|
|
# Blessing |
691
|
0
|
0
|
|
|
|
0
|
if (my $bto = $cinfo->{'blessto'}) {map({bless($_, $bto);} @$carr);} |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
692
|
|
|
|
|
|
|
# Circular Ref from child to parent ? |
693
|
|
|
|
|
|
|
#if (my $pla = $cinfo->{'parlinkattr'}) {map({$_->{$pla} = $e;} @$carr);} |
694
|
|
|
|
|
|
|
} |
695
|
|
|
|
|
|
|
# Autobless Children ? |
696
|
0
|
|
|
|
|
0
|
return(0); |
697
|
|
|
|
|
|
|
} |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
# Internal method for using or making up Child relationship information |
701
|
|
|
|
|
|
|
# for loading related entities. |
702
|
|
|
|
|
|
|
sub makecinfo { |
703
|
0
|
|
|
0
|
0
|
0
|
my ($p, $cv) = @_; |
704
|
|
|
|
|
|
|
# Support array with: 'table','parkey','memname' |
705
|
0
|
0
|
|
|
|
0
|
if (ref($cv) eq 'ARRAY') { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
706
|
0
|
|
|
|
|
0
|
my $cinfo; |
707
|
0
|
0
|
|
|
|
0
|
if (@$cv != 3) {die("Need table, parkey, memname in array");} |
|
0
|
|
|
|
|
0
|
|
708
|
0
|
|
|
|
|
0
|
@$cinfo{'table','parkey','memname'} = @$cv; |
709
|
0
|
|
|
|
|
0
|
return($cinfo); |
710
|
|
|
|
|
|
|
} |
711
|
|
|
|
|
|
|
# Assume all is there (could validate and provide missing) |
712
|
0
|
|
|
|
|
0
|
elsif (ref($cv) eq 'HASH') { |
713
|
0
|
|
|
|
|
0
|
my @a = ('table','parkey','memname'); |
714
|
|
|
|
|
|
|
# Try guess parkey ? |
715
|
0
|
0
|
|
|
|
0
|
if (!$cv->{'parkey'}) {$cv->{'parkey'} = [pkeys($p)];} |
|
0
|
|
|
|
|
0
|
|
716
|
0
|
0
|
|
|
|
0
|
for (@a) {if (!$cv->{$_}) {die("Missing '$_' in cinfo");}} |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
717
|
0
|
|
|
|
|
0
|
return($cv); |
718
|
|
|
|
|
|
|
} |
719
|
|
|
|
|
|
|
elsif (ref($cv) ne '') {die("child type Not scalar (or hash)");} |
720
|
|
|
|
|
|
|
################## Make up |
721
|
0
|
|
|
|
|
0
|
my $ctab = $cv; |
722
|
0
|
|
|
|
|
0
|
my $memname = $ctab; # Default memname to child type name (Plus 's') ? |
723
|
|
|
|
|
|
|
# Guess by parent |
724
|
0
|
|
|
|
|
0
|
my $parkey = [pkeys($p)]; |
725
|
0
|
|
|
|
|
0
|
my $cinfo = {'table' => $ctab, 'parkey' => $parkey, 'memname' => $ctab,}; |
726
|
0
|
|
|
|
|
0
|
return($cinfo); |
727
|
|
|
|
|
|
|
} |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
################################################################### |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
# Internal Persister validator for the absolutely mandatory properties of |
732
|
|
|
|
|
|
|
# persister object itself. |
733
|
|
|
|
|
|
|
# Doesn't not validate entry |
734
|
|
|
|
|
|
|
sub validate { |
735
|
2
|
|
|
2
|
0
|
2
|
my ($p) = @_; |
736
|
2
|
50
|
|
|
|
5
|
if (ref($p->{'pkey'}) ne 'ARRAY') {die("PK Attributes Not Known\n");} |
|
0
|
|
|
|
|
0
|
|
737
|
|
|
|
|
|
|
# Allow table to come from blessing (so NOT required) |
738
|
|
|
|
|
|
|
#if (!$p->{'table'}) {die("No Table\n");} |
739
|
2
|
50
|
|
|
|
6
|
if ($p->{'simu'}) {return;} |
|
0
|
|
|
|
|
0
|
|
740
|
|
|
|
|
|
|
# Do NOT Require conenction |
741
|
|
|
|
|
|
|
#if (!ref($p->{'dbh'})) {die("NO dbh to act on\n");} # ne 'DBI' |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
} |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
#=head2 @pka = $shp->pkeys() |
746
|
|
|
|
|
|
|
# |
747
|
|
|
|
|
|
|
# Internal method for returning array of id keys (Real array, not ref). |
748
|
|
|
|
|
|
|
# |
749
|
|
|
|
|
|
|
#=cut |
750
|
|
|
|
|
|
|
sub pkeys { |
751
|
11
|
|
|
11
|
0
|
12
|
my ($p) = @_; |
752
|
11
|
|
|
|
|
25
|
my $prt = reftype($p); |
753
|
11
|
50
|
|
|
|
22
|
if ($prt ne 'HASH') { |
754
|
0
|
|
|
|
|
0
|
$|=1; |
755
|
0
|
|
|
|
|
0
|
print(STDERR Dumper([caller(1)])); |
756
|
0
|
|
|
|
|
0
|
die("StoredHash Not a HASH (is '$p'/'$prt')"); |
757
|
|
|
|
|
|
|
} |
758
|
|
|
|
|
|
|
# Excessive validation ? |
759
|
11
|
50
|
|
|
|
23
|
if (ref($p->{'pkey'}) ne 'ARRAY') {die("Primary keys not in an array");} |
|
0
|
|
|
|
|
0
|
|
760
|
|
|
|
|
|
|
#return($p->{'pkey'}); |
761
|
11
|
|
|
|
|
9
|
return(@{$p->{'pkey'}}); |
|
11
|
|
|
|
|
24
|
|
762
|
|
|
|
|
|
|
} |
763
|
|
|
|
|
|
|
|
764
|
|
|
|
|
|
|
=head2 $shp->count($filter) |
765
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
Get Count of all or a filtered set of entries (by optional $filter) in table. |
767
|
|
|
|
|
|
|
Return (scalar) count of entries. |
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
=cut |
770
|
|
|
|
|
|
|
sub count { |
771
|
4
|
|
|
4
|
1
|
1985
|
my ($p, $fh) = @_; # $fh - Filter Hash |
772
|
4
|
|
|
|
|
9
|
my $qc = "SELECT COUNT(*) FROM $p->{'table'}"; |
773
|
|
|
|
|
|
|
# TODO: See filter case for load(), delete() |
774
|
|
|
|
|
|
|
# Use it and replace 2nd param of qexecute w. params |
775
|
4
|
100
|
66
|
|
|
19
|
if (ref($fh) eq 'HASH' && keys(%$fh)) { |
776
|
2
|
|
|
|
|
5
|
my $w = wherefilter($fh); # my ($w, $vals) = wherefilter_para($fh); |
777
|
2
|
|
|
|
|
4
|
$qc .= " WHERE $w"; |
778
|
|
|
|
|
|
|
} |
779
|
4
|
50
|
|
|
|
8
|
if ($p->{'debug'}) {print("Count Query:$qc\n");} |
|
0
|
|
|
|
|
0
|
|
780
|
4
|
50
|
|
|
|
7
|
if (!$p->{'dbh'}) {return($qc);} |
|
4
|
|
|
|
|
9
|
|
781
|
0
|
|
|
|
|
0
|
$p->qexecute($qc, undef, 'count'); # $vals |
782
|
|
|
|
|
|
|
} |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
=head1 INTERNAL METHODS |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
These methods you should not need working on the high level. However for the curious they are outlined here. |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
=head2 @pkv = $shp->pkeyvals($e) |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
Return Primary key values (as "real" array, not ref to one) from hash %$e. |
791
|
|
|
|
|
|
|
undef values are produced for non-existing keys. |
792
|
|
|
|
|
|
|
Mostly used for internal operations (and possibly debugging). |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
=cut |
795
|
|
|
|
|
|
|
sub pkeyvals { |
796
|
0
|
|
|
0
|
1
|
0
|
my ($p, $e) = @_; |
797
|
0
|
|
|
|
|
0
|
my @pkeys = pkeys($p); |
798
|
0
|
|
|
|
|
0
|
@$e{@pkeys}; |
799
|
|
|
|
|
|
|
} |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
# TODO: Implement pulling last id from sequence |
802
|
|
|
|
|
|
|
sub fetchautoid { |
803
|
0
|
|
|
0
|
0
|
0
|
my ($p) = @_; |
804
|
0
|
|
|
|
|
0
|
my $dbh; |
805
|
|
|
|
|
|
|
#$dbh->{'Driver'}; # Need to test ? |
806
|
|
|
|
|
|
|
#DEV:print("AUTOID FETCH TO BE IMPLEMENTED\n");return(69); |
807
|
0
|
|
|
|
|
0
|
my $pot = $p->{'table'}; |
808
|
0
|
0
|
|
|
|
0
|
if (!$pot) {die("No table for fetching auto-ID");} |
|
0
|
|
|
|
|
0
|
|
809
|
0
|
0
|
|
|
|
0
|
if (!($dbh = $p->{'dbh'})) {die("No Connection for fetching ID");} |
|
0
|
|
|
|
|
0
|
|
810
|
0
|
|
|
|
|
0
|
$dbh->last_insert_id(undef, undef, $pot, undef); |
811
|
|
|
|
|
|
|
} |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
sub pkeycnt { |
814
|
2
|
|
|
2
|
0
|
2
|
my ($p) = @_; |
815
|
|
|
|
|
|
|
#if (ref($p->{'pkey'}) ne 'ARRAY') {die("Primary keys not in an array");} |
816
|
|
|
|
|
|
|
#scalar(@{$p->{'pkey'}}); |
817
|
2
|
|
|
|
|
4
|
my @pkeys = pkeys($p); |
818
|
2
|
|
|
|
|
3
|
scalar(@pkeys); |
819
|
|
|
|
|
|
|
} |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
# Internal method for checking for empty or undefined ID values. |
822
|
|
|
|
|
|
|
# In all reasonable databases and apps these are not valid values. |
823
|
|
|
|
|
|
|
sub invalidids { |
824
|
2
|
|
|
2
|
0
|
3
|
my ($p, @idv) = @_; |
825
|
2
|
50
|
|
|
|
2
|
my @badid = grep({!defined($_) || $_ eq '';} @idv); |
|
3
|
|
|
|
|
15
|
|
826
|
2
|
|
|
|
|
6
|
return(@badid); |
827
|
|
|
|
|
|
|
} |
828
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
=head2 $shp->whereid($pkvals); |
830
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
Generate SQL WHERE Clause for update() (or delete() or load() or exists()) based on primary keys of current (table) type. |
832
|
|
|
|
|
|
|
Return WHERE clause with id-attribute(s) and placeholder(s) (idkey = ?, ...), without the WHERE keyword. |
833
|
|
|
|
|
|
|
Mostly called for internal operations. You should not need this. |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
=cut |
836
|
|
|
|
|
|
|
sub whereid { |
837
|
5
|
|
|
5
|
1
|
6
|
my ($p, $pkval) = @_; |
838
|
|
|
|
|
|
|
# # Allow IDs to be hash OR array ?? Not because hash would req. to store order |
839
|
5
|
|
|
|
|
9
|
my @pka = pkeys($p); |
840
|
5
|
50
|
|
|
|
426
|
if (@pka < 1) {die("No Pkeys to create where ID clause");} |
|
0
|
|
|
|
|
0
|
|
841
|
|
|
|
|
|
|
# my $wstr = |
842
|
5
|
0
|
33
|
|
|
10
|
if ($pkval && (ref($pkval) eq 'ARRAY') && (@$pkval == @pka) ) { |
|
|
|
33
|
|
|
|
|
843
|
|
|
|
|
|
|
# TODO: Mock DBI |
844
|
0
|
|
|
|
|
0
|
my $dbh = $p->{'dbh'}; |
845
|
|
|
|
|
|
|
# Can't use string ("DBI::db::quote") as a subroutine ref while "strict refs" in use |
846
|
0
|
|
|
0
|
|
0
|
my $quote = $p->{'dbh'} ? |
847
|
|
|
|
|
|
|
#ref($p->{'dbh'}).'::quote' |
848
|
|
|
|
|
|
|
sub {$p->{'dbh'}->quote($_[0]);} |
849
|
0
|
0
|
|
|
|
0
|
: \"e; |
850
|
0
|
|
|
|
|
0
|
my $i = -1; |
851
|
|
|
|
|
|
|
#no strict 'refs'; |
852
|
0
|
|
|
|
|
0
|
my $wif = join(' AND ', map({ |
853
|
0
|
|
|
|
|
0
|
$i++;"$_ = ".$quote->($pkval->[$i]); |
|
0
|
|
|
|
|
0
|
|
854
|
|
|
|
|
|
|
} @pka)); |
855
|
0
|
|
|
|
|
0
|
return $wif; # OLD (nostrict) $wid |
856
|
|
|
|
|
|
|
} |
857
|
5
|
|
|
|
|
8
|
return join(' AND ', map({"$_ = ?";} @pka)); |
|
7
|
|
|
|
|
24
|
|
858
|
|
|
|
|
|
|
} |
859
|
|
|
|
|
|
|
|
860
|
|
|
|
|
|
|
# Internal fallback method to escape (string) value. Prefer using $dbh->quote() if $dbh is |
861
|
|
|
|
|
|
|
# handle available and the associated DBD Driver supports it. |
862
|
|
|
|
|
|
|
# The first parameter to this is an unused dummy parameter to match call of $dbh->quote($str). |
863
|
|
|
|
|
|
|
# This does not place surrounding quotes on the value returned. |
864
|
|
|
|
|
|
|
# Return value properly escaped. |
865
|
|
|
|
|
|
|
# TODO: Cover all scenarios |
866
|
|
|
|
|
|
|
sub sqlvalesc { |
867
|
2
|
|
|
2
|
0
|
2
|
my ($foo, $v) = @_; |
868
|
|
|
|
|
|
|
#$v =~ s/'/\\'/g; # $str =~ s/'/''/g; |
869
|
2
|
|
|
|
|
3
|
$v =~ s/\'/\'\'/g; |
870
|
2
|
|
|
|
|
2
|
$v =~ s/\n/\\n/g; |
871
|
2
|
|
|
|
|
7
|
$v; |
872
|
|
|
|
|
|
|
} |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
# TODO: Create list for WHERE IN Clause based on some assumptions |
875
|
|
|
|
|
|
|
sub invalues { |
876
|
0
|
|
|
0
|
0
|
0
|
my ($vals) = @_; |
877
|
|
|
|
|
|
|
# Assume array ref validated outside |
878
|
0
|
0
|
|
|
|
0
|
if (ref($vals) ne 'ARRAY') {die("Not an array for invals");} |
|
0
|
|
|
|
|
0
|
|
879
|
|
|
|
|
|
|
# Escape within Quotes ? |
880
|
0
|
|
|
|
|
0
|
join(',', map({ |
881
|
0
|
0
|
|
|
|
0
|
if (/^\d+$/) {$_;} |
|
0
|
|
|
|
|
0
|
|
882
|
|
|
|
|
|
|
else { |
883
|
0
|
|
|
|
|
0
|
my $v = sqlvalesc(undef, $_); |
884
|
0
|
|
|
|
|
0
|
"'$v'"; |
885
|
|
|
|
|
|
|
} |
886
|
|
|
|
|
|
|
} @$vals)); |
887
|
|
|
|
|
|
|
} |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
sub rangefilter { |
890
|
0
|
|
|
0
|
0
|
0
|
my ($attr, $v) = @_; |
891
|
0
|
0
|
|
|
|
0
|
if (ref($v) ne 'ARRAY') {die("Need value range as ARRAY of 2 elems");} |
|
0
|
|
|
|
|
0
|
|
892
|
|
|
|
|
|
|
# Or just even and sort, grab 2 at the time ? |
893
|
0
|
0
|
|
|
|
0
|
if (@$v != 2) {die("Range cannot be formed - need exactly 2 elements");} |
|
0
|
|
|
|
|
0
|
|
894
|
0
|
0
|
0
|
|
|
0
|
if (!defined($v->[0]) || !defined($v->[1]) ) {die("Missing either of the values ($v->[0], $v->[0])");} |
|
0
|
|
|
|
|
0
|
|
895
|
|
|
|
|
|
|
# Auto-arrange ??? Test for both being numbers |
896
|
0
|
0
|
|
|
|
0
|
my @nums = map({Scalar::Util::looks_like_number($_) ? (1) : ();} @$v); |
|
0
|
|
|
|
|
0
|
|
897
|
0
|
0
|
|
|
|
0
|
if (@nums == 2) { |
898
|
0
|
0
|
|
|
|
0
|
if ($v->[1] < $v->[0]) {$v = [$v->[1],$v->[0]];} |
|
0
|
|
|
|
|
0
|
|
899
|
|
|
|
|
|
|
} |
900
|
|
|
|
|
|
|
# Detect need to escape (time vs. number) |
901
|
0
|
|
|
|
|
0
|
return "($attr >= $v->[0]) AND ($attr <= $v->[1])"; |
902
|
|
|
|
|
|
|
#return " $attr BETWEEN $v->[0] AND $v->[1]"; |
903
|
|
|
|
|
|
|
} |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
#=head2 StoredHash::wherefilter($e,%c); |
906
|
|
|
|
|
|
|
# |
907
|
|
|
|
|
|
|
# Generate simple WHERE filter by hash %$e. The keys are assumed to be attributes |
908
|
|
|
|
|
|
|
# of DB and values are embedded as values into SQL (as opposed to using placeholers). |
909
|
|
|
|
|
|
|
# To be perfect in escaping per attribute type info would be needed. |
910
|
|
|
|
|
|
|
# For now we do best effort heuristics (attr val \d+ is assumed |
911
|
|
|
|
|
|
|
# to be a numeric field in SQL, however 000002345 could actually |
912
|
|
|
|
|
|
|
# be content of a char/text/varchar field). |
913
|
|
|
|
|
|
|
# Return WHERE filter clause without WHERE keyword. |
914
|
|
|
|
|
|
|
sub wherefilter { |
915
|
2
|
|
|
2
|
0
|
3
|
my ($e, %c) = @_; |
916
|
2
|
|
|
|
|
3
|
my $w = ''; |
917
|
2
|
|
|
|
|
2
|
my $fop = ' AND '; |
918
|
|
|
|
|
|
|
#my $rnga = $c{'rnga'}; # Range attributes |
919
|
2
|
50
|
|
|
|
4
|
if (ref($e) ne 'HASH') {die("No hash for filter generation");} |
|
0
|
|
|
|
|
0
|
|
920
|
|
|
|
|
|
|
# Ensure deterministic order |
921
|
2
|
|
|
|
|
6
|
my @keys = sort keys(%$e); |
922
|
2
|
|
|
|
|
2
|
my @qc; # Query Components |
923
|
|
|
|
|
|
|
# Assume hard values, treat everything as string (?) |
924
|
|
|
|
|
|
|
# TODO: forcestr ? |
925
|
2
|
|
|
|
|
3
|
@qc = map({ |
926
|
2
|
|
|
|
|
3
|
my $v = $e->{$_}; |
927
|
|
|
|
|
|
|
#my $rv = ref($v); |
928
|
|
|
|
|
|
|
#if ($rnga->{$_} && ($rv eq 'ARRAY') && (@$v == 2)) {rangefilter($_, $v);} |
929
|
|
|
|
|
|
|
# For now, assume IN - clause |
930
|
2
|
50
|
|
|
|
11
|
if (ref($v) eq 'ARRAY') {" $_ IN (".invalues($v).") ";} |
|
0
|
50
|
|
|
|
0
|
|
|
0
|
50
|
|
|
|
0
|
|
931
|
|
|
|
|
|
|
# SQL Wildcard |
932
|
0
|
|
|
|
|
0
|
elsif ($v =~ /%/) {"$_ LIKE '$v'";} |
933
|
|
|
|
|
|
|
# Detect numeric (likely numeric, not perfect) |
934
|
|
|
|
|
|
|
# TODO: Explicit param to |
935
|
2
|
|
|
|
|
6
|
elsif ($v =~ /^\d+$/) {"$_ = $v";} |
936
|
|
|
|
|
|
|
# Assume string |
937
|
|
|
|
|
|
|
else {"$_ = '".sqlvalesc(undef, $v)."'";} |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
} @keys); |
940
|
|
|
|
|
|
|
# Create PARAMETRIC query |
941
|
2
|
50
|
|
|
|
6
|
if (ref $c{'vals'} eq 'ARRAY') { |
942
|
0
|
|
|
|
|
0
|
my @vals = (); |
943
|
0
|
|
|
|
|
0
|
map({ |
944
|
0
|
|
|
|
|
0
|
my $v = $e->{$_}; |
945
|
0
|
0
|
|
|
|
0
|
if (ref($v) eq 'ARRAY') {push(@vals, @$v);" $_ IN (".join(',', map({"?";} @$v)).") ";} |
|
0
|
0
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
946
|
0
|
|
|
|
|
0
|
elsif ($v =~ /%/) {push(@vals, $v);"$_ LIKE ?";} |
|
0
|
|
|
|
|
0
|
|
947
|
0
|
|
|
|
|
0
|
else {push(@vals, $v);"$_ = ?";} |
948
|
|
|
|
|
|
|
} @keys); |
949
|
0
|
|
|
|
|
0
|
push(@{$c{'vals'}}, @vals); |
|
0
|
|
|
|
|
0
|
|
950
|
|
|
|
|
|
|
} |
951
|
2
|
|
|
|
|
5
|
return(join($fop, @qc)); # join by AND |
952
|
|
|
|
|
|
|
} |
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
#=head2 my ($where, $para) = wherefilter_para($e); |
955
|
|
|
|
|
|
|
# Where filter for parametric query (for load(), delete() count()) |
956
|
|
|
|
|
|
|
# Return WHERE clause (without 'WHERE') and parametric values. |
957
|
|
|
|
|
|
|
# Throw exception on empty %$e or ... (empty filter) |
958
|
|
|
|
|
|
|
# Caller should not simply check the count of keys in hash as ref |
959
|
|
|
|
|
|
|
# valued key-pairs are skipped here. |
960
|
|
|
|
|
|
|
sub wherefilter_para { |
961
|
0
|
|
|
0
|
0
|
0
|
my ($e) = @_; |
962
|
0
|
0
|
0
|
|
|
0
|
if (!$e || !%$e) {die("Will not generate filter by no HASH / empty HASH !");} |
|
0
|
|
|
|
|
0
|
|
963
|
0
|
|
|
|
|
0
|
my @ks = grep({!ref($e->{$_})} keys(%$e)); |
|
0
|
|
|
|
|
0
|
|
964
|
0
|
|
|
|
|
0
|
my @vs = @$e{@ks}; # In this context @vs => @pkv - Not really vals for primary keys, but filter |
965
|
0
|
|
|
|
|
0
|
my $wstr = join(' AND ', map({"$_ = ?";} @ks)); |
|
0
|
|
|
|
|
0
|
|
966
|
|
|
|
|
|
|
#if (!$wstr || $wstr =~ /^\s*$/) {die("Will not generate empty filter clause");} |
967
|
0
|
|
|
|
|
0
|
return($wstr, \@vs); |
968
|
|
|
|
|
|
|
} |
969
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
# Internal: Serialize all values (singles,multi) from a hash to an array |
971
|
|
|
|
|
|
|
# based on sorted key order. Multi-valued keys (with value being array reference) |
972
|
|
|
|
|
|
|
# add multiple items. |
973
|
|
|
|
|
|
|
sub allentvals { |
974
|
2
|
|
|
2
|
0
|
11
|
my ($h) = @_; |
975
|
0
|
|
|
|
|
0
|
map({ |
976
|
2
|
50
|
|
|
|
11
|
if (ref($h->{$_}) eq 'HASH') {();} |
|
6
|
50
|
|
|
|
13
|
|
|
0
|
|
|
|
|
0
|
|
977
|
0
|
|
|
|
|
0
|
elsif (ref($h->{$_}) eq 'ARRAY') {@{$h->{$_}};} |
|
6
|
|
|
|
|
11
|
|
978
|
|
|
|
|
|
|
else {($h->{$_});} |
979
|
|
|
|
|
|
|
} sort(keys(%$h))); |
980
|
|
|
|
|
|
|
} |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
# TODO: Move to util ? |
984
|
|
|
|
|
|
|
#=head2 $p->dbtabinfo(%opts) OR StoredHash::dbtabinfo($dbh, %opts); |
985
|
|
|
|
|
|
|
# Covenience method for $dbh->table_info() |
986
|
|
|
|
|
|
|
# Options: |
987
|
|
|
|
|
|
|
#=item * tabonly - Filter out all DB Objects where TABLE_TYPE is not 'TABLE' |
988
|
|
|
|
|
|
|
# |
989
|
|
|
|
|
|
|
# Return AoH where each of inner hashes are info for single table. Property names are in |
990
|
|
|
|
|
|
|
# standard DBI table_info() format (see perldoc DBI). |
991
|
|
|
|
|
|
|
sub dbtabinfo { |
992
|
0
|
|
|
0
|
0
|
|
my (%c) = @_; |
993
|
0
|
|
|
|
|
|
my ($p, $pdbh); |
994
|
0
|
|
|
|
|
|
my $rt = reftype($_[0]); |
995
|
0
|
0
|
|
|
|
|
if ($rt eq 'StoredHash') {$p = shift();%c = @_;} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
# elsif ($rt eq '') |
997
|
0
|
|
|
|
|
|
else {$pdbh = shift();%c = @_;} |
998
|
0
|
|
0
|
|
|
|
my $dbh = $pdbh || $p->{'dbh'} || $c{'dbh'}; |
999
|
0
|
0
|
|
|
|
|
if (!$dbh) {die("No Connection for table info");} |
|
0
|
|
|
|
|
|
|
1000
|
0
|
|
|
|
|
|
my $sth = $dbh->table_info(); |
1001
|
0
|
|
|
|
|
|
my $tabinfo = $sth->fetchall_arrayref({}); # AoH |
1002
|
0
|
|
|
|
|
|
$sth->finish(); |
1003
|
|
|
|
|
|
|
# Replace with $c{'all'} - Get all database objects (like views, indices ...) |
1004
|
0
|
0
|
|
|
|
|
if ($c{'tabonly'}) { |
1005
|
0
|
|
|
|
|
|
@$tabinfo = grep({$_->{'TABLE_TYPE'} eq 'TABLE';} @$tabinfo); |
|
0
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
} |
1007
|
|
|
|
|
|
|
|
1008
|
0
|
|
|
|
|
|
return($tabinfo); |
1009
|
|
|
|
|
|
|
} |
1010
|
|
|
|
|
|
|
# Experimental wrapper to query attributes |
1011
|
|
|
|
|
|
|
# TODO: dbattrinfo($dbh, $tn); |
1012
|
|
|
|
|
|
|
sub dbattrinfo { |
1013
|
0
|
|
|
0
|
0
|
|
my ($dbh, $tn) = @_; |
1014
|
|
|
|
|
|
|
#my ($p, $pdbh); |
1015
|
|
|
|
|
|
|
#my $rt = reftype($_[0]); |
1016
|
|
|
|
|
|
|
#if ($rt eq 'StoredHash') {$p = shift();%c = @_;} |
1017
|
|
|
|
|
|
|
#elsif ($rt eq '') {$pdbh = shift();%c = @_;} |
1018
|
|
|
|
|
|
|
#my $dbh = $pdbh || $p->{'dbh'} || $c{'dbh'}; |
1019
|
0
|
0
|
|
|
|
|
if (!$dbh) {die("No Connection for attribute info");} |
|
0
|
|
|
|
|
|
|
1020
|
0
|
0
|
|
|
|
|
if (!$tn) {die("No table name for attribute info");} |
|
0
|
|
|
|
|
|
|
1021
|
0
|
|
|
|
|
|
my $sth = $dbh->column_info(undef, undef, $tn, '%'); |
1022
|
0
|
|
|
|
|
|
my $arr = $sth->fetchall_arrayref({}); # AoH |
1023
|
0
|
|
|
|
|
|
$sth->finish(); |
1024
|
0
|
|
|
|
|
|
return($arr); |
1025
|
|
|
|
|
|
|
} |
1026
|
|
|
|
|
|
|
1; |