File Coverage

lib/CallBackery/Database.pm
Criterion Covered Total %
statement 14 134 10.4
branch 0 14 0.0
condition 0 10 0.0
subroutine 5 19 26.3
pod 14 15 93.3
total 33 192 17.1


line stmt bran cond sub pod time code
1             package CallBackery::Database;
2              
3             # $Id: Database.pm 542 2013-12-12 16:36:34Z oetiker $
4              
5 1     1   8 use Mojo::Base -base,-signatures;
  1         2  
  1         9  
6              
7 1     1   464 use Data::Dumper;
  1         3  
  1         88  
8 1     1   7 use Carp qw(croak);
  1         2  
  1         71  
9 1     1   6 use CallBackery::Exception qw(mkerror);
  1         2  
  1         4725  
10              
11             =head1 NAME
12              
13             CallBackery::Database - database access helpers
14              
15             =head1 SYNOPSIS
16              
17             use CallBackery::Database;
18             my $db = CallBackery::Database->new(app=>$self->config);
19             my ($fields,$values) = $self->map2sql(table,data);
20             my $selWhere = $self->map2where(table,data);
21             my $rowHash = $self->fetchRow(table,{field=>val,field=>val},selExpression?);
22             my $value = $self->fetchValue(table,{field=>val,field=>val},column);
23             my $id = $self->matchData(table,{field=>val,field=>val});
24             my $id = $self->lookUp(table,field,value);
25             my $id = $self->updateOrInsertData(table,{dataField=>val,...},{matchField=>val,...}?);
26             my $id = $self->insertIfNew(table,{field=>val,field=>val});
27              
28             =head1 DESCRIPTION
29              
30             Database access helpers.
31              
32             =head2 config
33              
34             object needs access to the system config to get access to the database
35              
36             =cut
37              
38             has app => sub {
39             croak "app property is required";
40             }, weak => 1;
41              
42             has userName => sub {
43             return "* no user *";
44             };
45              
46             has config => sub {
47             shift->app->config;
48             }, weak => 1;
49              
50              
51             =head2 dhb
52              
53             a dbi database handle
54              
55             =cut
56              
57             my $lastFlush = time;
58              
59             has sql => sub {
60             my $self = shift;
61             require Mojo::SQLite;
62             my $sql = Mojo::SQLite->new($self->config->cfgHash->{BACKEND}{cfg_db});
63              
64             $sql->options({
65             RaiseError => 1,
66             PrintError => 0,
67             AutoCommit => 1,
68             ShowErrorStatement => 1,
69             sqlite_unicode => 1,
70             FetchHashKeyName=>'NAME_lc',
71             });
72              
73             $sql->on(connection => sub ($sql, $dbh) {
74             $dbh->do('PRAGMA foreign_keys = ON;');
75             });
76              
77             $sql->migrations
78             ->name('cbmig')
79             ->from_data(__PACKAGE__,'dbsetup.sql')
80             ->migrate;
81              
82             return $sql;
83             };
84              
85             # this must be fresh ... always!
86             sub mojoSqlDb {
87 2     2 0 43 my $self = shift;
88 2         5 return $self->sql->db;
89             };
90              
91             =over 4
92              
93             =item my($fields,$values) = $self->C;
94              
95             Provide two hash pointers and quote the field names for inclusion into an
96             SQL expression. Build field names according to the table_field rule.
97              
98             =cut
99              
100             sub map2sql {
101 0     0 1   my $self = shift;
102 0           my $table = shift;
103 0           my $data = shift;
104 0           my @values;
105             my @fields;
106 0           while (my($field, $value) = each %$data) {
107 0           push @fields,$self->mojoSqlDb->dbh->quote_identifier($table."_".$field);
108 0           push @values,$value;
109             }
110 0           return (\@fields,\@values);
111             }
112              
113             =item my $sqlWhere = $self->C;
114              
115             build a where statement Find a record matching the given data in a table the
116             data is a map. Quote field names and values. Build field names according to
117             the table_field rule.
118              
119             =cut
120              
121             sub map2where {
122 0     0 1   my $self = shift;
123 0           my $table = shift;
124 0           my $data = shift;
125 0           my $db = $self->mojoSqlDb;
126 0           my @expression;
127 0           while (my($field, $value) = each %$data) {
128 0           my $field = $db->dbh->quote_identifier($table."_".$field);
129 0           my $expr;
130 0 0         if (defined $value){
131 0           $expr = $field.' = '.$db->dbh->quote($value);
132             }
133             else {
134 0           $expr = $field.' is null';
135             }
136 0           push @expression, $expr;
137             }
138 0           return (join ' AND ',@expression);
139             }
140              
141             =item $hashRef = $self->C;
142              
143             Get an array of hashes with model and label tags:
144              
145             [{model: x, label: y},{id ...},...]
146              
147             =cut
148              
149             sub getMap {
150 0     0 1   my $self = shift;
151 0           my $table = shift;
152 0           my $column = shift;
153 0           my $db = $self->mojoSqlDb;
154 0           my $sqlId = $db->dbh->quote_identifier($table."_id");
155 0           my $sqlColumn = $db->dbh->quote_identifier($table."_".$column);
156 0           my $sqlTable = $db->dbh->quote_identifier($table);
157 0           my $SQL = <<"SQL";
158             SELECT $sqlId as model, $sqlColumn as label
159             FROM $sqlTable
160             ORDER by $sqlColumn
161             SQL
162 0           return $db->dbh->selectall_arrayref($SQL,{Slice=>{}});
163             }
164              
165             =item $hashRef = $self->Cvalue,....},$selectExpr?)>;
166              
167             Get a hash with record index as key. Optionally with a list of columns to return.
168              
169             {
170             2 => { a=>x, b=>y },
171             3 => { a=>k, b=>r }
172             }
173              
174             =cut
175              
176             sub getRowHash {
177 0     0 1   my $self = shift;
178 0           my $table = shift;
179 0           my $data = shift;
180 0   0       my $selectCols = shift // '*';
181 0           my $db = $self->mojoSqlDb;
182 0           my $sqlTable = $db->dbh->quote_identifier($table);
183 0           my $sqlWhere = $self->map2where($table,$data);
184 0           my $SQL = <<"SQL";
185             SELECT $selectCols
186             FROM $sqlTable
187             WHERE $sqlWhere
188             SQL
189 0           return $db->dbh->selectall_hashref($SQL,$table."_id",{Slice=>{}});
190             }
191              
192              
193             =item $id = $self->Cvalue,key=>value},$selectExp ?)>;
194              
195             Find a record matching the given data in a table and return a hash of the matching record.
196              
197             =cut
198              
199             sub fetchRow {
200 0     0 1   my $self = shift;
201 0           my $table = shift;
202 0           my $data = shift;
203 0   0       my $selectCols = shift // '*';
204 0           my $db = $self->mojoSqlDb;
205 0           my $sqlWhere = $self->map2where($table,$data);
206 0           my $sqlTable = $db->dbh->quote_identifier($table);
207 0           my $SQL = <<"SQL";
208             SELECT $selectCols
209             FROM $sqlTable
210             WHERE $sqlWhere
211             SQL
212 0           return $db->dbh->selectrow_hashref($SQL);
213             }
214              
215             =item $id = $self->Cvalue,key=>value},column)>;
216              
217             Find a record matching the given data in a table and returns the value in column.
218              
219             =cut
220              
221             sub fetchValue {
222 0     0 1   my $self = shift;
223 0           my $table = shift;
224 0           my $where = shift;
225 0           my $column = shift;
226 0           my $db = $self->mojoSqlDb;
227 0           my $row = $self->fetchRow($table,$where,$db->dbh->quote_identifier($table.'_'.$column));
228 0 0         if ($row){
229 0           return $row->{$table.'_'.$column};
230             }
231             else {
232 0           return undef;
233             }
234             }
235              
236              
237             =item $id = $self->C;
238              
239             Find a record matching the given data in a table
240             the data is a map.
241              
242             =cut
243              
244             sub matchData {
245 0     0 1   my $self = shift;
246 0           my $table = shift;
247 0           my $data = shift;
248 0           my $found = $self->fetchValue($table,$data,"id");
249 0           return $found;
250              
251             }
252              
253             =item $id = $self->C
254              
255             Lookup the value in table in table_column and return table_id.
256             Throw an exception if this fails. Use matchData if you are just looking.
257              
258             =cut
259              
260             sub lookUp {
261 0     0 1   my $self = shift;
262 0           my $table = shift;
263 0           my $column = shift;
264 0           my $value = shift;
265 0 0         my $id = $self->matchData($table,{$column => $value})
266             or die mkerror(1349,"Lookup for $column = $value in $table faild");
267 0           return $id;
268             }
269              
270             =item $id = $self->C
271              
272             Insert the given data into the table. If a match map is given, try an update first
273             with the given match only insert when update has 0 hits.
274              
275             =cut
276              
277             sub updateOrInsertData {
278 0     0 1   my $self = shift;
279 0           my $table = shift;
280 0           my $data = shift;
281 0           my $match = shift;
282 0           my $db = $self->mojoSqlDb;
283 0           my ($colNames,$colValues) = $self->map2sql($table,$data);
284 0           my $sqlTable = $db->dbh->quote_identifier($table);
285 0           my $sqlIdCol = $db->dbh->quote_identifier($table."_id");
286 0           my $sqlColumns = join ', ', @$colNames;
287 0           my $sqlSet = join ', ', map { "$_ = ?" } @$colNames;
  0            
288 0           my $sqlData = join ', ', map { '?' } @$colValues;
  0            
289 0 0         if ($match){ # try update first if we have an id
290 0           my $matchWhere = $self->map2where($table,$match);
291 0           my $SQL = <<"SQL";
292             UPDATE $sqlTable SET $sqlSet
293             WHERE $matchWhere
294             SQL
295 0           my $count = $db->dbh->do($SQL,{},@$colValues);
296 0 0         if ($count > 0){
297 0   0       return ( $data->{id} // $match->{id} );
298             }
299             }
300 0           my $SQL = <<"SQL";
301             INSERT INTO $sqlTable ( $sqlColumns )
302             VALUES ( $sqlData )
303             SQL
304 0           $db->dbh->do($SQL,{},@$colValues);
305              
306             # non serial primary key, id defined by user
307 0 0         if (exists $data->{'id'}){
308 0           return $data->{'id'};
309             }
310             # serial primary key
311             else{
312 0           return $db->dbh->last_insert_id(undef,undef,$table,$table."_id");
313             }
314             }
315              
316             =item $id = $self->C
317              
318             Lookup the given data. If it is new, insert a record. Returns the matching Id.
319              
320             =cut
321              
322             sub insertIfNew {
323 0     0 1   my $self = shift;
324 0           my $table = shift;
325 0           my $data = shift;
326 0   0       return ( $self->matchData($table,$data)
327             // $self->updateOrInsertData($table,$data));
328             }
329              
330             =item $id = $self->C
331              
332             Delete data from table. Given the record id.
333             Returns true if the record was deleted.
334              
335             =cut
336              
337             sub deleteData {
338 0     0 1   my $self = shift;
339 0           my $table = shift;
340 0           my $id = shift;
341 0           return $self->deleteDataWhere($table,{id=>$id});
342             }
343              
344             =item $id = $self->Cval,key=>val})>
345              
346             Delete data from table. Given the column title and the matching value.
347             Returns true if the record was deleted.
348              
349             =cut
350              
351             sub deleteDataWhere {
352 0     0 1   my $self = shift;
353 0           my $table = shift;
354 0           my $match = shift;
355 0           my $val = shift;
356 0           my $db = $self->mojoSqlDb;
357 0           my $sqlTable = $db->dbh->quote_identifier($table);
358 0           my $matchWhere = $self->map2where($table,$match);
359 0           my $SQL = 'DELETE FROM '.$sqlTable.' WHERE '.$matchWhere;
360             # say $SQL;
361 0           return $db->dbh->do($SQL);
362             }
363              
364             =item getConfigValue($key)
365              
366             return a raw data value from the config table
367              
368             =cut
369              
370             sub getConfigValue {
371 0     0 1   my $self = shift;
372 0           my $key = shift;
373 0           my $value = eval {
374 0           local $SIG{__DIE__};
375 0           $self->fetchValue('cbconfig',{id => $key},'value');
376             };
377 0 0         return ($@ ? undef : $value);
378             }
379              
380             =item setConfigValue($key,$value)
381              
382             write a config value
383              
384             =cut
385              
386             sub setConfigValue {
387 0     0 1   my $self = shift;
388 0           my $key = shift;
389 0           my $value = shift;
390             # warn "SET $key -> ".Dumper([$value]);
391 0           $self->updateOrInsertData('cbconfig',{
392             id=> $key,
393             value => $value
394             }, { id => $key });
395 0           return $value;
396             }
397              
398             1;
399              
400             =back
401              
402             =head1 COPYRIGHT
403              
404             Copyright (c) 2015 by OETIKER+PARTNER AG. All rights reserved.
405              
406             =head1 AUTHOR
407              
408             Stobi@oetiker.chE>
409              
410             =head1 HISTORY
411              
412             2010-06-12 to 1.0 initial
413             2013-11-19 to 1.1 converted to mojo
414              
415             =cut
416              
417             __DATA__