| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # ************************************************************************* | 
| 2 |  |  |  |  |  |  | # Copyright (c) 2014-2017, SUSE LLC | 
| 3 |  |  |  |  |  |  | # | 
| 4 |  |  |  |  |  |  | # All rights reserved. | 
| 5 |  |  |  |  |  |  | # | 
| 6 |  |  |  |  |  |  | # Redistribution and use in source and binary forms, with or without | 
| 7 |  |  |  |  |  |  | # modification, are permitted provided that the following conditions are met: | 
| 8 |  |  |  |  |  |  | # | 
| 9 |  |  |  |  |  |  | # 1. Redistributions of source code must retain the above copyright notice, | 
| 10 |  |  |  |  |  |  | # this list of conditions and the following disclaimer. | 
| 11 |  |  |  |  |  |  | # | 
| 12 |  |  |  |  |  |  | # 2. Redistributions in binary form must reproduce the above copyright | 
| 13 |  |  |  |  |  |  | # notice, this list of conditions and the following disclaimer in the | 
| 14 |  |  |  |  |  |  | # documentation and/or other materials provided with the distribution. | 
| 15 |  |  |  |  |  |  | # | 
| 16 |  |  |  |  |  |  | # 3. Neither the name of SUSE LLC nor the names of its contributors may be | 
| 17 |  |  |  |  |  |  | # used to endorse or promote products derived from this software without | 
| 18 |  |  |  |  |  |  | # specific prior written permission. | 
| 19 |  |  |  |  |  |  | # | 
| 20 |  |  |  |  |  |  | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
| 21 |  |  |  |  |  |  | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
| 22 |  |  |  |  |  |  | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
| 23 |  |  |  |  |  |  | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | 
| 24 |  |  |  |  |  |  | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | 
| 25 |  |  |  |  |  |  | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | 
| 26 |  |  |  |  |  |  | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | 
| 27 |  |  |  |  |  |  | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | 
| 28 |  |  |  |  |  |  | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 
| 29 |  |  |  |  |  |  | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 
| 30 |  |  |  |  |  |  | # POSSIBILITY OF SUCH DAMAGE. | 
| 31 |  |  |  |  |  |  | # ************************************************************************* | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | package App::Dochazka::REST::Model::Shared; | 
| 34 |  |  |  |  |  |  |  | 
| 35 | 41 |  |  | 41 |  | 2320 | use 5.012; | 
|  | 41 |  |  |  |  | 146 |  | 
| 36 | 41 |  |  | 41 |  | 183 | use strict; | 
|  | 41 |  |  |  |  | 71 |  | 
|  | 41 |  |  |  |  | 754 |  | 
| 37 | 41 |  |  | 41 |  | 173 | use warnings; | 
|  | 41 |  |  |  |  | 70 |  | 
|  | 41 |  |  |  |  | 1069 |  | 
| 38 |  |  |  |  |  |  |  | 
| 39 | 41 |  |  | 41 |  | 196 | use App::CELL qw( $CELL $log $meta $site ); | 
|  | 41 |  |  |  |  | 94 |  | 
|  | 41 |  |  |  |  | 3362 |  | 
| 40 | 41 |  |  | 41 |  | 257 | use Data::Dumper; | 
|  | 41 |  |  |  |  | 77 |  | 
|  | 41 |  |  |  |  | 1703 |  | 
| 41 | 41 |  |  | 41 |  | 8080 | use JSON; | 
|  | 41 |  |  |  |  | 147841 |  | 
|  | 41 |  |  |  |  | 236 |  | 
| 42 | 41 |  |  | 41 |  | 4427 | use Params::Validate qw( :all ); | 
|  | 41 |  |  |  |  | 378 |  | 
|  | 41 |  |  |  |  | 5309 |  | 
| 43 | 41 |  |  | 41 |  | 255 | use Try::Tiny; | 
|  | 41 |  |  |  |  | 70 |  | 
|  | 41 |  |  |  |  | 1955 |  | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | =head1 NAME | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | App::Dochazka::REST::Model::Shared - functions shared by several modules within | 
| 51 |  |  |  |  |  |  | the data model | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | use App::Dochazka::REST::Model::Shared; | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | ... | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | =head1 EXPORTS | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | =cut | 
| 68 |  |  |  |  |  |  |  | 
| 69 | 41 |  |  | 41 |  | 209 | use Exporter qw( import ); | 
|  | 41 |  |  |  |  | 81 |  | 
|  | 41 |  |  |  |  | 76156 |  | 
| 70 |  |  |  |  |  |  | our @EXPORT_OK = qw( | 
| 71 |  |  |  |  |  |  | canonicalize_date | 
| 72 |  |  |  |  |  |  | canonicalize_ts | 
| 73 |  |  |  |  |  |  | canonicalize_tsrange | 
| 74 |  |  |  |  |  |  | cud | 
| 75 |  |  |  |  |  |  | cud_generic | 
| 76 |  |  |  |  |  |  | decode_schedule_json | 
| 77 |  |  |  |  |  |  | get_history | 
| 78 |  |  |  |  |  |  | load | 
| 79 |  |  |  |  |  |  | load_multiple | 
| 80 |  |  |  |  |  |  | noof | 
| 81 |  |  |  |  |  |  | priv_by_eid | 
| 82 |  |  |  |  |  |  | schedule_by_eid | 
| 83 |  |  |  |  |  |  | select_single | 
| 84 |  |  |  |  |  |  | select_set_of_single_scalar_rows | 
| 85 |  |  |  |  |  |  | split_tsrange | 
| 86 |  |  |  |  |  |  | timestamp_delta_minus | 
| 87 |  |  |  |  |  |  | timestamp_delta_plus | 
| 88 |  |  |  |  |  |  | tsrange_intersection | 
| 89 |  |  |  |  |  |  | tsrange_equal | 
| 90 |  |  |  |  |  |  | ); | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | =head1 FUNCTIONS | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | =head2 canonicalize_date | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | Given a string that PostgreSQL might recognize as a date, pass it to | 
| 101 |  |  |  |  |  |  | the database via the SQL statement: | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | SELECT CAST( ? AS date ) | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | and return the resulting status object. | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | =cut | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | sub canonicalize_date { | 
| 110 | 0 |  |  | 0 | 1 | 0 | my ( $conn, $ts ) = @_; | 
| 111 |  |  |  |  |  |  |  | 
| 112 | 0 |  |  |  |  | 0 | my $status = select_single( | 
| 113 |  |  |  |  |  |  | conn => $conn, | 
| 114 |  |  |  |  |  |  | sql => 'SELECT CAST( ? AS date )', | 
| 115 |  |  |  |  |  |  | keys => [ $ts ], | 
| 116 |  |  |  |  |  |  | ); | 
| 117 | 0 | 0 |  |  |  | 0 | _replace_payload_array_with_string( $status ) if $status->ok; | 
| 118 | 0 |  |  |  |  | 0 | return $status; | 
| 119 |  |  |  |  |  |  | } | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | =head2 canonicalize_ts | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | Given a string that might be a timestamp, "canonicalize" it by running it | 
| 125 |  |  |  |  |  |  | through the database in the SQL statement: | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | SELECT CAST( ? AS timestamptz ) | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | =cut | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | sub canonicalize_ts { | 
| 132 | 0 |  |  | 0 | 1 | 0 | my ( $conn, $ts ) = @_; | 
| 133 |  |  |  |  |  |  |  | 
| 134 | 0 |  |  |  |  | 0 | my $status = select_single( | 
| 135 |  |  |  |  |  |  | conn => $conn, | 
| 136 |  |  |  |  |  |  | sql => 'SELECT CAST( ? AS timestamptz )', | 
| 137 |  |  |  |  |  |  | keys => [ $ts ], | 
| 138 |  |  |  |  |  |  | ); | 
| 139 | 0 | 0 |  |  |  | 0 | _replace_payload_array_with_string( $status ) if $status->ok; | 
| 140 | 0 |  |  |  |  | 0 | return $status; | 
| 141 |  |  |  |  |  |  | } | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | sub _replace_payload_array_with_string { | 
| 144 | 0 |  |  | 0 |  | 0 | my $status = shift; | 
| 145 | 0 |  |  |  |  | 0 | $status->payload( $status->payload->[0] ); | 
| 146 | 0 |  |  |  |  | 0 | return $status; | 
| 147 |  |  |  |  |  |  | } | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | =head2 canonicalize_tsrange | 
| 151 |  |  |  |  |  |  |  | 
| 152 |  |  |  |  |  |  | Given a string that might be a tsrange, "canonicalize" it by running it | 
| 153 |  |  |  |  |  |  | through the database in the SQL statement: | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | SELECT CAST( ? AS tstzrange ) | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | Returns an L<App::CELL::Status> object. If the status code is OK, then the | 
| 158 |  |  |  |  |  |  | tsrange is OK and its canonicalized form is in the payload. Otherwise, some | 
| 159 |  |  |  |  |  |  | kind of error occurred, as described in the status object. | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | =cut | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | sub canonicalize_tsrange { | 
| 164 | 0 |  |  | 0 | 1 | 0 | my ( $conn, $tsr ) = @_; | 
| 165 |  |  |  |  |  |  |  | 
| 166 | 0 |  |  |  |  | 0 | my $status = select_single( | 
| 167 |  |  |  |  |  |  | conn => $conn, | 
| 168 |  |  |  |  |  |  | sql => 'SELECT CAST( ? AS tstzrange)', | 
| 169 |  |  |  |  |  |  | keys => [ $tsr ], | 
| 170 |  |  |  |  |  |  | ); | 
| 171 | 0 | 0 |  |  |  | 0 | _replace_payload_array_with_string( $status ) if $status->ok; | 
| 172 | 0 | 0 | 0 |  |  | 0 | return $CELL->status_err( 'DOCHAZKA_TSRANGE_EMPTY' ) if $status->ok and $status->payload eq "empty"; | 
| 173 | 0 |  |  |  |  | 0 | return $status; | 
| 174 |  |  |  |  |  |  | } | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | =head2 cud | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | Attempts to Create, Update, or Delete a single database record. Takes the | 
| 180 |  |  |  |  |  |  | following PARAMHASH: | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | =over | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  | =item * conn | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | The L<DBIx::Connector> object with which to gain access to the database. | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | =item * eid | 
| 189 |  |  |  |  |  |  |  | 
| 190 |  |  |  |  |  |  | The EID of the employee originating the request (needed for the audit triggers). | 
| 191 |  |  |  |  |  |  |  | 
| 192 |  |  |  |  |  |  | =item * object | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | The Dochazka datamodel object to be worked on. | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | =item * sql | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | The SQL statement to execute (should be INSERT, UPDATE, or DELETE). | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | =item * attrs | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | An array reference containing the bind values to be plugged into the SQL | 
| 203 |  |  |  |  |  |  | statement. | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | =back | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | Returns a status object. | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | Important note: it is up to the programmer to not pass any SQL statement that | 
| 210 |  |  |  |  |  |  | might affect more than one record. | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | =cut | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | sub cud { | 
| 215 | 0 |  |  | 0 | 1 | 0 | my %ARGS = validate( @_, { | 
| 216 |  |  |  |  |  |  | conn => { isa => 'DBIx::Connector' }, | 
| 217 |  |  |  |  |  |  | eid => { type => SCALAR }, | 
| 218 |  |  |  |  |  |  | object => { can => [ qw( insert delete ) ] }, | 
| 219 |  |  |  |  |  |  | sql => { type => SCALAR }, | 
| 220 |  |  |  |  |  |  | attrs => { type => ARRAYREF }, # order of attrs must match SQL statement | 
| 221 |  |  |  |  |  |  | } ); | 
| 222 |  |  |  |  |  |  |  | 
| 223 | 0 |  |  |  |  | 0 | my ( $status, $rv, $count ); | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | try { | 
| 226 |  |  |  |  |  |  | local $SIG{__WARN__} = sub { | 
| 227 | 0 |  |  |  |  | 0 | die @_; | 
| 228 | 0 |  |  | 0 |  | 0 | }; | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | # start transaction | 
| 231 |  |  |  |  |  |  | $ARGS{'conn'}->txn( fixup => sub { | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | # get DBI db handle | 
| 234 | 0 |  |  |  |  | 0 | my $dbh = shift; | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | # set the dochazka.eid GUC session parameter | 
| 237 | 0 |  |  |  |  | 0 | $dbh->do( $site->SQL_SET_DOCHAZKA_EID_GUC, undef, ( $ARGS{'eid'}+0 ) ); | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | # prepare the SQL statement and bind parameters | 
| 240 | 0 |  |  |  |  | 0 | my $sth = $dbh->prepare( $ARGS{'sql'} ); | 
| 241 | 0 |  |  |  |  | 0 | my $counter = 0; | 
| 242 |  |  |  |  |  |  | map { | 
| 243 | 0 |  |  |  |  | 0 | $counter += 1; | 
| 244 | 0 |  |  |  |  | 0 | $sth->bind_param( $counter, $ARGS{'object'}->{$_} ); | 
| 245 | 0 |  |  |  |  | 0 | } @{ $ARGS{'attrs'} }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | # execute the SQL statement | 
| 248 | 0 |  |  |  |  | 0 | $rv = $sth->execute; | 
| 249 | 0 |  |  |  |  | 0 | $log->debug( "cud: DBI execute returned " . Dumper( $rv ) ); | 
| 250 | 0 | 0 |  |  |  | 0 | if ( $rv == 1 ) { | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  | # a record was returned; get the values | 
| 253 | 0 |  |  |  |  | 0 | my $rh = $sth->fetchrow_hashref; | 
| 254 | 0 |  |  |  |  | 0 | $log->info( "Statement " . $sth->{'Statement'} . " RETURNING values: " . Dumper( $rh ) ); | 
| 255 |  |  |  |  |  |  | # populate object with all RETURNING fields | 
| 256 | 0 |  |  |  |  | 0 | map { $ARGS{'object'}->{$_} = $rh->{$_}; } ( keys %$rh ); | 
|  | 0 |  |  |  |  | 0 |  | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | # count number of rows affected | 
| 259 | 0 |  |  |  |  | 0 | $count = $sth->rows; | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | } elsif ( $rv eq '0E0' ) { | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | # no error, but no record returned either | 
| 264 | 0 |  |  |  |  | 0 | $count = $sth->rows; | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | } elsif ( $rv > 1 ) { | 
| 267 |  |  |  |  |  |  | $status = $CELL->status_crit( | 
| 268 |  |  |  |  |  |  | 'DOCHAZKA_CUD_MORE_THAN_ONE_RECORD_AFFECTED', | 
| 269 | 0 |  |  |  |  | 0 | args => [ $sth->{'Statement'} ] | 
| 270 |  |  |  |  |  |  | ); | 
| 271 |  |  |  |  |  |  | } elsif ( $rv == -1 ) { | 
| 272 |  |  |  |  |  |  | $status = $CELL->status_err( | 
| 273 |  |  |  |  |  |  | 'DOCHAZKA_CUD_UNKNOWN_NUMBER_OF_RECORDS_AFFECTED', | 
| 274 | 0 |  |  |  |  | 0 | args => [ $sth->{'Statement'} ] | 
| 275 |  |  |  |  |  |  | ); | 
| 276 |  |  |  |  |  |  | } else { | 
| 277 | 0 |  |  |  |  | 0 | $status = $CELL->status_crit( 'DOCHAZKA_DBI_EXECUTE_WEIRDNESS' ); | 
| 278 |  |  |  |  |  |  | } | 
| 279 | 0 |  |  |  |  | 0 | } ); | 
| 280 |  |  |  |  |  |  | } catch { | 
| 281 | 0 |  |  | 0 |  | 0 | my $errmsg = $_; | 
| 282 | 0 | 0 |  |  |  | 0 | if ( not defined( $errmsg ) ) { | 
| 283 | 0 |  |  |  |  | 0 | $log->err( '$_ undefined in catch' ); | 
| 284 | 0 |  |  |  |  | 0 | $errmsg = '<NONE>'; | 
| 285 |  |  |  |  |  |  | } | 
| 286 | 0 | 0 |  |  |  | 0 | if ( ! $site->DOCHAZKA_SQL_TRACE ) { | 
| 287 | 0 |  |  |  |  | 0 | $errmsg =~ s/^DBD::Pg::st execute failed: //; | 
| 288 | 0 |  |  |  |  | 0 | $errmsg =~ s#at /usr/lib/perl5/.* line .*\.$##; | 
| 289 |  |  |  |  |  |  | } | 
| 290 | 0 | 0 |  |  |  | 0 | if ( ! defined( $status ) ) { | 
| 291 | 0 |  |  |  |  | 0 | $status = $CELL->status_err( 'DOCHAZKA_DBI_ERR', | 
| 292 |  |  |  |  |  |  | args => [ $errmsg ], | 
| 293 |  |  |  |  |  |  | DBI_return_value => $rv, | 
| 294 |  |  |  |  |  |  | ); | 
| 295 |  |  |  |  |  |  | } | 
| 296 | 0 |  |  |  |  | 0 | }; | 
| 297 |  |  |  |  |  |  |  | 
| 298 | 0 | 0 |  |  |  | 0 | if ( not defined( $status ) ) { | 
| 299 |  |  |  |  |  |  | $status = $CELL->status_ok( 'DOCHAZKA_CUD_OK', | 
| 300 |  |  |  |  |  |  | DBI_return_value => $rv, | 
| 301 | 0 |  |  |  |  | 0 | payload => $ARGS{'object'}, | 
| 302 |  |  |  |  |  |  | count => $count, | 
| 303 |  |  |  |  |  |  | ); | 
| 304 |  |  |  |  |  |  | } | 
| 305 |  |  |  |  |  |  |  | 
| 306 | 0 |  |  |  |  | 0 | return $status; | 
| 307 |  |  |  |  |  |  | } | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | =head2 cud_generic | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | Attempts to execute a generic Create, Update, or Delete database operation. | 
| 313 |  |  |  |  |  |  | Takes the following PARAMHASH: | 
| 314 |  |  |  |  |  |  |  | 
| 315 |  |  |  |  |  |  | =over | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | =item * conn | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | The L<DBIx::Connector> object with which to gain access to the database. | 
| 320 |  |  |  |  |  |  |  | 
| 321 |  |  |  |  |  |  | =item * eid | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | The EID of the employee originating the request (needed for the audit triggers). | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | =item * sql | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | The SQL statement to execute (should be INSERT, UPDATE, or DELETE). | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | =item * bind_params | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | An array reference containing the bind values to be plugged into the SQL | 
| 332 |  |  |  |  |  |  | statement. | 
| 333 |  |  |  |  |  |  |  | 
| 334 |  |  |  |  |  |  | =back | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | Returns a status object. | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | Important note: it is up to the programmer to not pass any SQL statement that | 
| 339 |  |  |  |  |  |  | might affect more than one record. | 
| 340 |  |  |  |  |  |  |  | 
| 341 |  |  |  |  |  |  | =cut | 
| 342 |  |  |  |  |  |  |  | 
| 343 |  |  |  |  |  |  | sub cud_generic { | 
| 344 | 0 |  |  | 0 | 1 | 0 | my %ARGS = validate( @_, { | 
| 345 |  |  |  |  |  |  | conn => { isa => 'DBIx::Connector' }, | 
| 346 |  |  |  |  |  |  | eid => { type => SCALAR }, | 
| 347 |  |  |  |  |  |  | sql => { type => SCALAR }, | 
| 348 |  |  |  |  |  |  | bind_params => { type => ARRAYREF, optional => 1 }, # order must match SQL statement | 
| 349 |  |  |  |  |  |  | } ); | 
| 350 | 0 |  |  |  |  | 0 | $log->info( "Entering " . __PACKAGE__ . "::cud_generic with" ); | 
| 351 | 0 |  |  |  |  | 0 | $log->info( "sql: $ARGS{sql}" ); | 
| 352 | 0 |  |  |  |  | 0 | $log->info( "bind_param: " . Dumper( $ARGS{bind_params} ) ); | 
| 353 |  |  |  |  |  |  |  | 
| 354 | 0 |  |  |  |  | 0 | my ( $status, $rv, $count ); | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | try { | 
| 357 |  |  |  |  |  |  | local $SIG{__WARN__} = sub { | 
| 358 | 0 |  |  |  |  | 0 | die @_; | 
| 359 | 0 |  |  | 0 |  | 0 | }; | 
| 360 |  |  |  |  |  |  |  | 
| 361 |  |  |  |  |  |  | # start transaction | 
| 362 |  |  |  |  |  |  | $ARGS{'conn'}->txn( fixup => sub { | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | # get DBI db handle | 
| 365 | 0 |  |  |  |  | 0 | my $dbh = shift; | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  | # set the dochazka.eid GUC session parameter | 
| 368 | 0 |  |  |  |  | 0 | $dbh->do( $site->SQL_SET_DOCHAZKA_EID_GUC, undef, ( $ARGS{'eid'}+0 ) ); | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | # prepare the SQL statement and bind parameters | 
| 371 | 0 |  |  |  |  | 0 | my $sth = $dbh->prepare( $ARGS{'sql'} ); | 
| 372 | 0 |  |  |  |  | 0 | my $counter = 0; | 
| 373 |  |  |  |  |  |  | map { | 
| 374 | 0 |  |  |  |  | 0 | $counter += 1; | 
| 375 | 0 |  | 0 |  |  | 0 | $sth->bind_param( $counter, $_ || undef ); | 
| 376 | 0 |  |  |  |  | 0 | } @{ $ARGS{'bind_params'} }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | # execute the SQL statement | 
| 379 | 0 |  |  |  |  | 0 | $rv = $sth->execute; | 
| 380 | 0 |  |  |  |  | 0 | $log->debug( "cud_generic: DBI execute returned " . Dumper( $rv ) ); | 
| 381 | 0 | 0 |  |  |  | 0 | if ( $rv >= 1 ) { | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 382 |  |  |  |  |  |  |  | 
| 383 |  |  |  |  |  |  | # count number of rows affected | 
| 384 | 0 |  |  |  |  | 0 | $count = $sth->rows; | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | } elsif ( $rv eq '0E0' ) { | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | # no error, but no record returned either | 
| 389 | 0 |  |  |  |  | 0 | $count = $sth->rows; | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  | } elsif ( $rv == -1 ) { | 
| 392 |  |  |  |  |  |  | $status = $CELL->status_err( | 
| 393 |  |  |  |  |  |  | 'DOCHAZKA_CUD_UNKNOWN_NUMBER_OF_RECORDS_AFFECTED', | 
| 394 | 0 |  |  |  |  | 0 | args => [ $sth->{'Statement'} ] | 
| 395 |  |  |  |  |  |  | ); | 
| 396 |  |  |  |  |  |  | } else { | 
| 397 | 0 |  |  |  |  | 0 | $status = $CELL->status_crit( 'DOCHAZKA_DBI_EXECUTE_WEIRDNESS' ); | 
| 398 |  |  |  |  |  |  | } | 
| 399 | 0 |  |  |  |  | 0 | } ); | 
| 400 |  |  |  |  |  |  | } catch { | 
| 401 | 0 |  |  | 0 |  | 0 | my $errmsg = $_; | 
| 402 | 0 | 0 |  |  |  | 0 | if ( not defined( $errmsg ) ) { | 
| 403 | 0 |  |  |  |  | 0 | $log->err( '$_ undefined in catch' ); | 
| 404 | 0 |  |  |  |  | 0 | $errmsg = '<NONE>'; | 
| 405 |  |  |  |  |  |  | } | 
| 406 | 0 | 0 |  |  |  | 0 | if ( not defined( $status ) ) { | 
| 407 | 0 |  |  |  |  | 0 | $status = $CELL->status_err( 'DOCHAZKA_DBI_ERR', | 
| 408 |  |  |  |  |  |  | args => [ $errmsg ], | 
| 409 |  |  |  |  |  |  | DBI_return_value => $rv, | 
| 410 |  |  |  |  |  |  | ); | 
| 411 |  |  |  |  |  |  | } | 
| 412 | 0 |  |  |  |  | 0 | }; | 
| 413 |  |  |  |  |  |  |  | 
| 414 | 0 | 0 |  |  |  | 0 | if ( not defined( $status ) ) { | 
| 415 | 0 |  |  |  |  | 0 | $status = $CELL->status_ok( 'DOCHAZKA_CUD_OK', | 
| 416 |  |  |  |  |  |  | DBI_return_value => $rv, | 
| 417 |  |  |  |  |  |  | count => $count, | 
| 418 |  |  |  |  |  |  | ); | 
| 419 |  |  |  |  |  |  | } | 
| 420 |  |  |  |  |  |  |  | 
| 421 | 0 |  |  |  |  | 0 | return $status; | 
| 422 |  |  |  |  |  |  | } | 
| 423 |  |  |  |  |  |  |  | 
| 424 |  |  |  |  |  |  |  | 
| 425 |  |  |  |  |  |  | =head2 decode_schedule_json | 
| 426 |  |  |  |  |  |  |  | 
| 427 |  |  |  |  |  |  | Given JSON string representation of the schedule, return corresponding HASHREF. | 
| 428 |  |  |  |  |  |  |  | 
| 429 |  |  |  |  |  |  | =cut | 
| 430 |  |  |  |  |  |  |  | 
| 431 |  |  |  |  |  |  | sub decode_schedule_json { | 
| 432 | 0 |  |  | 0 | 1 | 0 | my ( $json_str ) = @_; | 
| 433 |  |  |  |  |  |  |  | 
| 434 | 0 | 0 |  |  |  | 0 | return unless $json_str; | 
| 435 | 0 |  |  |  |  | 0 | return JSON->new->utf8->canonical(1)->decode( $json_str ); | 
| 436 |  |  |  |  |  |  | } | 
| 437 |  |  |  |  |  |  |  | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | =head2 get_history | 
| 440 |  |  |  |  |  |  |  | 
| 441 |  |  |  |  |  |  | This function takes a number of arguments. The first two are (1) a SCALAR | 
| 442 |  |  |  |  |  |  | argument, which can be either 'priv' or 'schedule', and (2) a L<DBIx::Connector> | 
| 443 |  |  |  |  |  |  | object. | 
| 444 |  |  |  |  |  |  |  | 
| 445 |  |  |  |  |  |  | Following these there is a PARAMHASH which can have one or more of the | 
| 446 |  |  |  |  |  |  | properties 'eid', 'nick', and 'tsrange'. At least one of { 'eid', 'nick' } must | 
| 447 |  |  |  |  |  |  | be specified. If both are specified, the employee is determined according to | 
| 448 |  |  |  |  |  |  | 'eid'. | 
| 449 |  |  |  |  |  |  |  | 
| 450 |  |  |  |  |  |  | The function returns the history of privilege level or schedule changes for | 
| 451 |  |  |  |  |  |  | that employee over the given tsrange, or the entire history if no tsrange is | 
| 452 |  |  |  |  |  |  | supplied. | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | The return value will always be an L<App::CELL::Status|status> object. | 
| 455 |  |  |  |  |  |  |  | 
| 456 |  |  |  |  |  |  | Upon success, the payload will be a reference to an array of history | 
| 457 |  |  |  |  |  |  | objects. If nothing is found, the array will be empty. If there is a DBI error, | 
| 458 |  |  |  |  |  |  | the payload will be undefined. | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  | =cut | 
| 461 |  |  |  |  |  |  |  | 
| 462 |  |  |  |  |  |  | sub get_history { | 
| 463 | 0 |  |  | 0 | 1 | 0 | my $t = shift; # 'priv' or 'sched' | 
| 464 | 0 |  |  |  |  | 0 | my $conn = shift; | 
| 465 | 0 |  |  |  |  | 0 | validate_pos( @_, 1, 1, 0, 0, 0, 0 ); | 
| 466 | 0 |  |  |  |  | 0 | my %ARGS = validate( @_, { | 
| 467 |  |  |  |  |  |  | eid => { type => SCALAR, optional => 1 }, | 
| 468 |  |  |  |  |  |  | nick => { type => SCALAR, optional => 1 }, | 
| 469 |  |  |  |  |  |  | tsrange => { type => SCALAR|UNDEF, optional => 1 }, | 
| 470 |  |  |  |  |  |  | } ); | 
| 471 |  |  |  |  |  |  |  | 
| 472 | 0 |  |  |  |  | 0 | $log->debug("Entering get_history for $t - arguments: " . Dumper( \%ARGS ) ); | 
| 473 |  |  |  |  |  |  |  | 
| 474 | 0 |  |  |  |  | 0 | my ( $sql, $sk, $status, $result, $tsr ); | 
| 475 | 0 | 0 |  |  |  | 0 | if ( exists $ARGS{'nick'} ) { | 
| 476 | 0 | 0 |  |  |  | 0 | $sql = ($t eq 'priv') | 
| 477 |  |  |  |  |  |  | ? $site->SQL_PRIVHISTORY_SELECT_RANGE_BY_NICK | 
| 478 |  |  |  |  |  |  | : $site->SQL_SCHEDHISTORY_SELECT_RANGE_BY_NICK; | 
| 479 | 0 |  |  |  |  | 0 | $result->{'nick'} = $ARGS{'nick'}; | 
| 480 | 0 | 0 |  |  |  | 0 | $result->{'eid'} = $ARGS{'eid'} if exists $ARGS{'eid'}; | 
| 481 | 0 |  |  |  |  | 0 | $sk = $ARGS{'nick'}; | 
| 482 |  |  |  |  |  |  | } | 
| 483 | 0 | 0 |  |  |  | 0 | if ( exists $ARGS{'eid'} ) { | 
| 484 | 0 | 0 |  |  |  | 0 | $sql = ($t eq 'priv') | 
| 485 |  |  |  |  |  |  | ? $site->SQL_PRIVHISTORY_SELECT_RANGE_BY_EID | 
| 486 |  |  |  |  |  |  | : $site->SQL_SCHEDHISTORY_SELECT_RANGE_BY_EID; | 
| 487 | 0 |  |  |  |  | 0 | $result->{'eid'} = $ARGS{'eid'}; | 
| 488 | 0 | 0 |  |  |  | 0 | $result->{'nick'} = $ARGS{'nick'} if exists $ARGS{'nick'}; | 
| 489 | 0 |  |  |  |  | 0 | $sk = $ARGS{'eid'}; | 
| 490 |  |  |  |  |  |  | } | 
| 491 | 0 |  |  |  |  | 0 | $log->debug("sql == $sql"); | 
| 492 |  |  |  |  |  |  | $tsr = ( $ARGS{'tsrange'} ) | 
| 493 | 0 | 0 |  |  |  | 0 | ? $ARGS{'tsrange'} | 
| 494 |  |  |  |  |  |  | : '[,)'; | 
| 495 | 0 |  |  |  |  | 0 | $result->{'tsrange'} = $tsr; | 
| 496 | 0 |  |  |  |  | 0 | $log->debug("tsrange == $tsr"); | 
| 497 |  |  |  |  |  |  |  | 
| 498 | 0 | 0 | 0 |  |  | 0 | die "AAAAAAAAAAAHHHHH! Engulfed by the abyss" unless $sk and $sql and $tsr; | 
|  |  |  | 0 |  |  |  |  | 
| 499 |  |  |  |  |  |  |  | 
| 500 | 0 |  |  |  |  | 0 | $result->{'history'} = []; | 
| 501 |  |  |  |  |  |  | try { | 
| 502 |  |  |  |  |  |  | $conn->run( fixup => sub { | 
| 503 | 0 |  |  |  |  | 0 | my $sth = $_->prepare( $sql ); | 
| 504 | 0 |  |  |  |  | 0 | $sth->execute( $sk, $tsr ); | 
| 505 | 0 |  |  |  |  | 0 | while( defined( my $tmpres = $sth->fetchrow_hashref() ) ) { | 
| 506 | 0 |  |  |  |  | 0 | push @{ $result->{'history'} }, $tmpres; | 
|  | 0 |  |  |  |  | 0 |  | 
| 507 |  |  |  |  |  |  | } | 
| 508 | 0 |  |  | 0 |  | 0 | } ); | 
| 509 |  |  |  |  |  |  | } catch { | 
| 510 | 0 |  |  | 0 |  | 0 | $status = $CELL->status_err( 'DOCHAZKA_DBI_ERR', args => [ $_ ] ); | 
| 511 | 0 |  |  |  |  | 0 | }; | 
| 512 | 0 | 0 |  |  |  | 0 | return $status if defined $status; | 
| 513 |  |  |  |  |  |  |  | 
| 514 | 0 |  |  |  |  | 0 | my $counter = scalar @{ $result->{'history'} }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 515 | 0 | 0 |  |  |  | 0 | return ( $counter ) | 
| 516 |  |  |  |  |  |  | ? $CELL->status_ok( 'DISPATCH_RECORDS_FOUND', | 
| 517 |  |  |  |  |  |  | args => [ $counter ], payload => $result, count => $counter ) | 
| 518 |  |  |  |  |  |  | : $CELL->status_notice( 'DISPATCH_NO_RECORDS_FOUND', | 
| 519 |  |  |  |  |  |  | payload => $result, count => $counter ); | 
| 520 |  |  |  |  |  |  | } | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  |  | 
| 523 |  |  |  |  |  |  | =head2 load | 
| 524 |  |  |  |  |  |  |  | 
| 525 |  |  |  |  |  |  | Load a database record into an object based on an SQL statement and a set of | 
| 526 |  |  |  |  |  |  | search keys. The search key must be an exact match: this function returns only | 
| 527 |  |  |  |  |  |  | 1 or 0 records.  Call, e.g., like this: | 
| 528 |  |  |  |  |  |  |  | 
| 529 |  |  |  |  |  |  | my $status = load( | 
| 530 |  |  |  |  |  |  | conn => $conn, | 
| 531 |  |  |  |  |  |  | class => __PACKAGE__, | 
| 532 |  |  |  |  |  |  | sql => $site->DOCHAZKA_SQL_SOME_STATEMENT, | 
| 533 |  |  |  |  |  |  | keys => [ 44 ] | 
| 534 |  |  |  |  |  |  | ); | 
| 535 |  |  |  |  |  |  |  | 
| 536 |  |  |  |  |  |  | The status object will be one of the following: | 
| 537 |  |  |  |  |  |  |  | 
| 538 |  |  |  |  |  |  | =over | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | =item * 1 record found | 
| 541 |  |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  | Level C<OK>, code C<DISPATCH_RECORDS_FOUND>, payload: object of type 'class' | 
| 543 |  |  |  |  |  |  |  | 
| 544 |  |  |  |  |  |  | =item * 0 records found | 
| 545 |  |  |  |  |  |  |  | 
| 546 |  |  |  |  |  |  | Level C<NOTICE>, code C<DISPATCH_NO_RECORDS_FOUND>, payload: none | 
| 547 |  |  |  |  |  |  |  | 
| 548 |  |  |  |  |  |  | =item * Database error | 
| 549 |  |  |  |  |  |  |  | 
| 550 |  |  |  |  |  |  | Level C<ERR>, code C<DOCHAZKA_DBI_ERR>, text: error message, payload: none | 
| 551 |  |  |  |  |  |  |  | 
| 552 |  |  |  |  |  |  | =back | 
| 553 |  |  |  |  |  |  |  | 
| 554 |  |  |  |  |  |  | =cut | 
| 555 |  |  |  |  |  |  |  | 
| 556 |  |  |  |  |  |  | sub load { | 
| 557 |  |  |  |  |  |  | # get and verify arguments | 
| 558 | 0 |  |  | 0 | 1 | 0 | my %ARGS = validate( @_, { | 
| 559 |  |  |  |  |  |  | conn => { isa => 'DBIx::Connector' }, | 
| 560 |  |  |  |  |  |  | class => { type => SCALAR }, | 
| 561 |  |  |  |  |  |  | sql => { type => SCALAR }, | 
| 562 |  |  |  |  |  |  | keys => { type => ARRAYREF }, | 
| 563 |  |  |  |  |  |  | } ); | 
| 564 |  |  |  |  |  |  |  | 
| 565 |  |  |  |  |  |  | # consult the database; N.B. - select may only return a single record | 
| 566 | 0 |  |  |  |  | 0 | my ( $hr, $status ); | 
| 567 |  |  |  |  |  |  | try { | 
| 568 |  |  |  |  |  |  | $ARGS{'conn'}->run( fixup => sub { | 
| 569 | 0 |  |  |  |  | 0 | $hr = $_->selectrow_hashref( $ARGS{'sql'}, undef, @{ $ARGS{'keys'} } ); | 
|  | 0 |  |  |  |  | 0 |  | 
| 570 | 0 |  |  | 0 |  | 0 | } ); | 
| 571 |  |  |  |  |  |  | } catch { | 
| 572 | 0 |  |  | 0 |  | 0 | $status = $CELL->status_err( 'DOCHAZKA_DBI_ERR', args => [ $_ ] ); | 
| 573 | 0 |  |  |  |  | 0 | }; | 
| 574 |  |  |  |  |  |  |  | 
| 575 |  |  |  |  |  |  | # report the result | 
| 576 | 0 | 0 |  |  |  | 0 | return $status if $status; | 
| 577 |  |  |  |  |  |  | return $CELL->status_ok( 'DISPATCH_RECORDS_FOUND', args => [ '1' ], | 
| 578 | 0 | 0 |  |  |  | 0 | payload => $ARGS{'class'}->spawn( %$hr ), count => 1 ) if defined $hr; | 
| 579 | 0 |  |  |  |  | 0 | return $CELL->status_notice( 'DISPATCH_NO_RECORDS_FOUND', count => 0 ); | 
| 580 |  |  |  |  |  |  | } | 
| 581 |  |  |  |  |  |  |  | 
| 582 |  |  |  |  |  |  |  | 
| 583 |  |  |  |  |  |  | =head2 load_multiple | 
| 584 |  |  |  |  |  |  |  | 
| 585 |  |  |  |  |  |  | Load multiple database records based on an SQL statement and a set of search | 
| 586 |  |  |  |  |  |  | keys. Example: | 
| 587 |  |  |  |  |  |  |  | 
| 588 |  |  |  |  |  |  | my $status = load_multiple( | 
| 589 |  |  |  |  |  |  | conn => $conn, | 
| 590 |  |  |  |  |  |  | class => __PACKAGE__, | 
| 591 |  |  |  |  |  |  | sql => $site->DOCHAZKA_SQL_SOME_STATEMENT, | 
| 592 |  |  |  |  |  |  | keys => [ 'rom%' ] | 
| 593 |  |  |  |  |  |  | ); | 
| 594 |  |  |  |  |  |  |  | 
| 595 |  |  |  |  |  |  | The return value will be a status object, the payload of which will be an | 
| 596 |  |  |  |  |  |  | arrayref containing a set of objects. The objects are constructed by calling | 
| 597 |  |  |  |  |  |  | $ARGS{'class'}->spawn | 
| 598 |  |  |  |  |  |  |  | 
| 599 |  |  |  |  |  |  | For convenience, a 'count' property will be included in the status object. | 
| 600 |  |  |  |  |  |  |  | 
| 601 |  |  |  |  |  |  | =cut | 
| 602 |  |  |  |  |  |  |  | 
| 603 |  |  |  |  |  |  | sub load_multiple { | 
| 604 |  |  |  |  |  |  | # get and verify arguments | 
| 605 | 0 |  |  | 0 | 1 | 0 | my %ARGS = validate( @_, { | 
| 606 |  |  |  |  |  |  | conn => { isa => 'DBIx::Connector' }, | 
| 607 |  |  |  |  |  |  | class => { type => SCALAR }, | 
| 608 |  |  |  |  |  |  | sql => { type => SCALAR }, | 
| 609 |  |  |  |  |  |  | keys => { type => ARRAYREF }, | 
| 610 |  |  |  |  |  |  | } ); | 
| 611 | 0 |  |  |  |  | 0 | $log->debug( "Entering " . __PACKAGE__ . "::load_multiple" ); | 
| 612 |  |  |  |  |  |  |  | 
| 613 | 0 |  |  |  |  | 0 | my $status; | 
| 614 | 0 |  |  |  |  | 0 | my $results = []; | 
| 615 |  |  |  |  |  |  | try { | 
| 616 |  |  |  |  |  |  | $ARGS{'conn'}->run( fixup => sub { | 
| 617 | 0 |  |  |  |  | 0 | my $sth = $_->prepare( $ARGS{'sql'} ); | 
| 618 | 0 |  |  |  |  | 0 | my $bc = 0; | 
| 619 |  |  |  |  |  |  | map { | 
| 620 | 0 |  |  |  |  | 0 | $bc += 1; | 
| 621 | 0 |  | 0 |  |  | 0 | $sth->bind_param( $bc, $_ || undef ); | 
| 622 | 0 |  |  |  |  | 0 | } @{ $ARGS{'keys'} }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 623 | 0 |  |  |  |  | 0 | $sth->execute(); | 
| 624 |  |  |  |  |  |  | # assuming they are objects, spawn them and push them onto @results | 
| 625 | 0 |  |  |  |  | 0 | while( defined( my $tmpres = $sth->fetchrow_hashref() ) ) { | 
| 626 | 0 |  |  |  |  | 0 | push @$results, $ARGS{'class'}->spawn( %$tmpres ); | 
| 627 |  |  |  |  |  |  | } | 
| 628 | 0 |  |  | 0 |  | 0 | } ); | 
| 629 |  |  |  |  |  |  | } catch { | 
| 630 | 0 |  |  | 0 |  | 0 | $status = $CELL->status_err( 'DOCHAZKA_DBI_ERR', args => [ $_ ] ); | 
| 631 | 0 |  |  |  |  | 0 | }; | 
| 632 | 0 | 0 |  |  |  | 0 | return $status if defined $status; | 
| 633 |  |  |  |  |  |  |  | 
| 634 | 0 |  |  |  |  | 0 | my $counter = scalar @$results; | 
| 635 |  |  |  |  |  |  | $status = ( $counter ) | 
| 636 |  |  |  |  |  |  | ? $CELL->status_ok( 'DISPATCH_RECORDS_FOUND', | 
| 637 | 0 | 0 |  |  |  | 0 | args => [ $counter ], payload => $results, count => $counter, keys => $ARGS{'keys'} ) | 
| 638 |  |  |  |  |  |  | : $CELL->status_notice( 'DISPATCH_NO_RECORDS_FOUND', | 
| 639 |  |  |  |  |  |  | payload => $results, count => $counter ); | 
| 640 |  |  |  |  |  |  | #$log->debug( Dumper $status ); | 
| 641 | 0 |  |  |  |  | 0 | return $status; | 
| 642 |  |  |  |  |  |  | } | 
| 643 |  |  |  |  |  |  |  | 
| 644 |  |  |  |  |  |  |  | 
| 645 |  |  |  |  |  |  | =head2 make_test_exists | 
| 646 |  |  |  |  |  |  |  | 
| 647 |  |  |  |  |  |  | Returns coderef for a function, 'test_exists', that performs a simple | 
| 648 |  |  |  |  |  |  | true/false check for existence of a record matching a scalar search key.  The | 
| 649 |  |  |  |  |  |  | record must be an exact match (no wildcards). | 
| 650 |  |  |  |  |  |  |  | 
| 651 |  |  |  |  |  |  | Takes one argument: a type string C<$t> which is concatenated with the string | 
| 652 |  |  |  |  |  |  | 'load_by_' to arrive at the name of the function to be called to execute the | 
| 653 |  |  |  |  |  |  | search. | 
| 654 |  |  |  |  |  |  |  | 
| 655 |  |  |  |  |  |  | The returned function takes a single argument: the search key (a scalar value). | 
| 656 |  |  |  |  |  |  | If a record matching the search key is found, the corresponding object | 
| 657 |  |  |  |  |  |  | (i.e. a true value) is returned. If such a record does not exist, 'undef' (a | 
| 658 |  |  |  |  |  |  | false value) is returned. If there is a DBI error, the error text is logged | 
| 659 |  |  |  |  |  |  | and undef is returned. | 
| 660 |  |  |  |  |  |  |  | 
| 661 |  |  |  |  |  |  | =cut | 
| 662 |  |  |  |  |  |  |  | 
| 663 |  |  |  |  |  |  | sub make_test_exists { | 
| 664 |  |  |  |  |  |  |  | 
| 665 | 410 |  |  | 410 | 1 | 4493 | my ( $t ) = validate_pos( @_, { type => SCALAR } ); | 
| 666 | 410 |  |  |  |  | 1845 | my $pkg = (caller)[0]; | 
| 667 |  |  |  |  |  |  |  | 
| 668 |  |  |  |  |  |  | return sub { | 
| 669 | 0 |  |  | 0 |  |  | my ( $conn, $s_key ) = @_; | 
| 670 | 0 |  |  |  |  |  | require Try::Tiny; | 
| 671 | 0 |  |  |  |  |  | my $routine = "load_by_$t"; | 
| 672 | 0 |  |  |  |  |  | my ( $status, $txt ); | 
| 673 | 0 |  |  |  |  |  | $log->debug( "Entered $t" . "_exists with search key $s_key" ); | 
| 674 |  |  |  |  |  |  | try { | 
| 675 | 41 |  |  | 41 |  | 383 | no strict 'refs'; | 
|  | 41 |  |  |  |  | 110 |  | 
|  | 41 |  |  |  |  | 52882 |  | 
| 676 | 0 |  |  | 0 |  |  | $status = $pkg->$routine( $conn, $s_key ); | 
| 677 |  |  |  |  |  |  | } catch { | 
| 678 | 0 |  |  | 0 |  |  | $txt = "Function " . $pkg . "::test_exists was generated with argument $t, " . | 
| 679 |  |  |  |  |  |  | "so it tried to call $routine, resulting in exception $_"; | 
| 680 | 0 |  |  |  |  |  | $status = $CELL->status_crit( $txt ); | 
| 681 | 0 |  |  |  |  |  | }; | 
| 682 | 0 | 0 | 0 |  |  |  | if ( ! defined( $status ) or $status->level eq 'CRIT' ) { | 
| 683 | 0 |  |  |  |  |  | die $txt; | 
| 684 |  |  |  |  |  |  | } | 
| 685 |  |  |  |  |  |  | #$log->debug( "Status is " . Dumper( $status ) ); | 
| 686 | 0 | 0 |  |  |  |  | return $status->payload if $status->ok; | 
| 687 | 0 |  |  |  |  |  | return; | 
| 688 |  |  |  |  |  |  | } | 
| 689 | 410 |  |  |  |  | 2329 | } | 
| 690 |  |  |  |  |  |  |  | 
| 691 |  |  |  |  |  |  |  | 
| 692 |  |  |  |  |  |  | =head2 noof | 
| 693 |  |  |  |  |  |  |  | 
| 694 |  |  |  |  |  |  | Given a L<DBIx::Connector> object and the name of a data model table, returns | 
| 695 |  |  |  |  |  |  | the total number of records in the table. | 
| 696 |  |  |  |  |  |  |  | 
| 697 |  |  |  |  |  |  | activities employees intervals locks privhistory schedhistory | 
| 698 |  |  |  |  |  |  | schedintvls schedules tempintvls | 
| 699 |  |  |  |  |  |  |  | 
| 700 |  |  |  |  |  |  | On failure, returns undef. | 
| 701 |  |  |  |  |  |  |  | 
| 702 |  |  |  |  |  |  | =cut | 
| 703 |  |  |  |  |  |  |  | 
| 704 |  |  |  |  |  |  | sub noof { | 
| 705 | 0 |  |  | 0 | 1 |  | my ( $conn, $table ) = validate_pos( @_, | 
| 706 |  |  |  |  |  |  | { isa => 'DBIx::Connector' }, | 
| 707 |  |  |  |  |  |  | { type => SCALAR } | 
| 708 |  |  |  |  |  |  | ); | 
| 709 |  |  |  |  |  |  |  | 
| 710 | 0 | 0 |  |  |  |  | return unless grep { $table eq $_; } qw( activities employees intervals locks | 
|  | 0 |  |  |  |  |  |  | 
| 711 |  |  |  |  |  |  | privhistory schedhistory schedintvls schedules tempintvls ); | 
| 712 |  |  |  |  |  |  |  | 
| 713 | 0 |  |  |  |  |  | my $count; | 
| 714 |  |  |  |  |  |  | try { | 
| 715 |  |  |  |  |  |  | $conn->run( fixup => sub { | 
| 716 | 0 |  |  |  |  |  | ( $count ) = $_->selectrow_array( "SELECT count(*) FROM $table" ); | 
| 717 | 0 |  |  | 0 |  |  | } ); | 
| 718 |  |  |  |  |  |  | } catch { | 
| 719 | 0 |  |  | 0 |  |  | $CELL->status_crit( 'DOCHAZKA_DBI_ERR', args => [ $_ ] ); | 
| 720 | 0 |  |  |  |  |  | }; | 
| 721 | 0 |  |  |  |  |  | return $count; | 
| 722 |  |  |  |  |  |  | } | 
| 723 |  |  |  |  |  |  |  | 
| 724 |  |  |  |  |  |  |  | 
| 725 |  |  |  |  |  |  | =head2 priv_by_eid | 
| 726 |  |  |  |  |  |  |  | 
| 727 |  |  |  |  |  |  | Given an EID, and, optionally, a timestamp, returns the employee's priv | 
| 728 |  |  |  |  |  |  | level as of that timestamp, or as of "now" if no timestamp was given. The | 
| 729 |  |  |  |  |  |  | priv level will default to 'passerby' if it can't be determined from the | 
| 730 |  |  |  |  |  |  | database. | 
| 731 |  |  |  |  |  |  |  | 
| 732 |  |  |  |  |  |  | =cut | 
| 733 |  |  |  |  |  |  |  | 
| 734 |  |  |  |  |  |  | sub priv_by_eid { | 
| 735 | 0 |  |  | 0 | 1 |  | my ( $conn, $eid, $ts ) = validate_pos( @_, | 
| 736 |  |  |  |  |  |  | { isa => 'DBIx::Connector' }, | 
| 737 |  |  |  |  |  |  | { type => SCALAR }, | 
| 738 |  |  |  |  |  |  | { type => SCALAR|UNDEF, optional => 1 } | 
| 739 |  |  |  |  |  |  | ); | 
| 740 |  |  |  |  |  |  | #$log->debug( "priv_by_eid: EID is " . (defined( $eid ) ? $eid : 'undef') . " - called from " . (caller)[1] . " line " . (caller)[2] ); | 
| 741 | 0 |  |  |  |  |  | return _st_by_eid( $conn, 'priv', $eid, $ts ); | 
| 742 |  |  |  |  |  |  | } | 
| 743 |  |  |  |  |  |  |  | 
| 744 |  |  |  |  |  |  |  | 
| 745 |  |  |  |  |  |  | =head2 schedule_by_eid | 
| 746 |  |  |  |  |  |  |  | 
| 747 |  |  |  |  |  |  | Given an EID, and, optionally, a timestamp, returns the SID of the employee's | 
| 748 |  |  |  |  |  |  | schedule as of that timestamp, or as of "now" if no timestamp was given. | 
| 749 |  |  |  |  |  |  |  | 
| 750 |  |  |  |  |  |  | =cut | 
| 751 |  |  |  |  |  |  |  | 
| 752 |  |  |  |  |  |  | sub schedule_by_eid { | 
| 753 | 0 |  |  | 0 | 1 |  | my ( $conn, $eid, $ts ) = validate_pos( @_, | 
| 754 |  |  |  |  |  |  | { isa => 'DBIx::Connector' }, | 
| 755 |  |  |  |  |  |  | { type => SCALAR }, | 
| 756 |  |  |  |  |  |  | { type => SCALAR|UNDEF, optional => 1 }, | 
| 757 |  |  |  |  |  |  | ); | 
| 758 | 0 |  |  |  |  |  | return _st_by_eid( $conn, 'schedule', $eid, $ts ); | 
| 759 |  |  |  |  |  |  | } | 
| 760 |  |  |  |  |  |  |  | 
| 761 |  |  |  |  |  |  |  | 
| 762 |  |  |  |  |  |  | =head3 _st_by_eid | 
| 763 |  |  |  |  |  |  |  | 
| 764 |  |  |  |  |  |  | Function that 'priv_by_eid' and 'schedule_by_eid' are wrappers of. | 
| 765 |  |  |  |  |  |  |  | 
| 766 |  |  |  |  |  |  | =cut | 
| 767 |  |  |  |  |  |  |  | 
| 768 |  |  |  |  |  |  | sub _st_by_eid { | 
| 769 | 0 |  |  | 0 |  |  | my ( $conn, $st, $eid, $ts ) = @_; | 
| 770 | 0 |  |  |  |  |  | my ( @args, $sql, $row ); | 
| 771 | 0 |  | 0 |  |  |  | $log->debug( "Entering _st_by_eid with \$st == $st, \$eid == $eid, \$ts == " . ( $ts || '<NONE>' ) ); | 
| 772 | 0 | 0 |  |  |  |  | if ( $ts ) { | 
| 773 |  |  |  |  |  |  | # timestamp given | 
| 774 | 0 | 0 |  |  |  |  | if ( $st eq 'priv' ) { | 
|  |  | 0 |  |  |  |  |  | 
| 775 | 0 |  |  |  |  |  | $sql = $site->SQL_EMPLOYEE_PRIV_AT_TIMESTAMP; | 
| 776 |  |  |  |  |  |  | } elsif ( $st eq 'schedule' ) { | 
| 777 | 0 |  |  |  |  |  | $sql = $site->SQL_EMPLOYEE_SCHEDULE_AT_TIMESTAMP; | 
| 778 |  |  |  |  |  |  | } | 
| 779 | 0 |  |  |  |  |  | @args = ( $sql, undef, $eid, $ts ); | 
| 780 |  |  |  |  |  |  | } else { | 
| 781 |  |  |  |  |  |  | # no timestamp given | 
| 782 | 0 | 0 |  |  |  |  | if ( $st eq 'priv' ) { | 
|  |  | 0 |  |  |  |  |  | 
| 783 | 0 |  |  |  |  |  | $sql = $site->SQL_EMPLOYEE_CURRENT_PRIV; | 
| 784 |  |  |  |  |  |  | } elsif ( $st eq 'schedule' ) { | 
| 785 | 0 |  |  |  |  |  | $sql = $site->SQL_EMPLOYEE_CURRENT_SCHEDULE; | 
| 786 |  |  |  |  |  |  | } | 
| 787 | 0 |  |  |  |  |  | @args = ( $sql, undef, $eid ); | 
| 788 |  |  |  |  |  |  | } | 
| 789 |  |  |  |  |  |  |  | 
| 790 | 0 |  |  |  |  |  | $log->debug("About to run SQL statement $sql with parameter $eid - " . | 
| 791 |  |  |  |  |  |  | " called from " . (caller)[1] . " line " . (caller)[2] ); | 
| 792 |  |  |  |  |  |  |  | 
| 793 | 0 |  |  |  |  |  | my $status; | 
| 794 |  |  |  |  |  |  | try { | 
| 795 |  |  |  |  |  |  | $conn->run( fixup => sub { | 
| 796 | 0 |  |  |  |  |  | ( $row ) = $_->selectrow_array( @args ); | 
| 797 | 0 |  |  | 0 |  |  | } ); | 
| 798 |  |  |  |  |  |  | } catch { | 
| 799 | 0 |  |  | 0 |  |  | $log->debug( 'Encountered DBI error' ); | 
| 800 | 0 |  |  |  |  |  | $status = $CELL->status_err( 'DOCHAZKA_DBI_ERR', args => [ $_ ] ); | 
| 801 | 0 |  |  |  |  |  | }; | 
| 802 | 0 | 0 |  |  |  |  | return $status if $status; | 
| 803 |  |  |  |  |  |  |  | 
| 804 | 0 |  |  |  |  |  | $log->debug( "_st_by_eid success; returning payload " . Dumper( $row ) ); | 
| 805 | 0 |  |  |  |  |  | return $row; | 
| 806 |  |  |  |  |  |  | } | 
| 807 |  |  |  |  |  |  |  | 
| 808 |  |  |  |  |  |  |  | 
| 809 |  |  |  |  |  |  | =head2 select_single | 
| 810 |  |  |  |  |  |  |  | 
| 811 |  |  |  |  |  |  | Given a L<DBIx::Connector> object in the 'conn' property, a SELECT statement in | 
| 812 |  |  |  |  |  |  | the 'sql' property and, in the 'keys' property, an arrayref containing a list | 
| 813 |  |  |  |  |  |  | of scalar values to plug into the SELECT statement, run a C<selectrow_array> | 
| 814 |  |  |  |  |  |  | and return the resulting list. | 
| 815 |  |  |  |  |  |  |  | 
| 816 |  |  |  |  |  |  | Returns a standard status object (see C<load> routine, above, for description). | 
| 817 |  |  |  |  |  |  |  | 
| 818 |  |  |  |  |  |  | =cut | 
| 819 |  |  |  |  |  |  |  | 
| 820 |  |  |  |  |  |  | sub select_single { | 
| 821 | 0 |  |  | 0 | 1 |  | my %ARGS = validate( @_, { | 
| 822 |  |  |  |  |  |  | conn => { isa => 'DBIx::Connector' }, | 
| 823 |  |  |  |  |  |  | sql => { type => SCALAR }, | 
| 824 |  |  |  |  |  |  | keys => { type => ARRAYREF }, | 
| 825 |  |  |  |  |  |  | } ); | 
| 826 | 0 |  |  |  |  |  | my ( $status, @results ); | 
| 827 | 0 |  |  |  |  |  | $log->info( "select_single keys: " . Dumper( $ARGS{keys} ) ); | 
| 828 |  |  |  |  |  |  | try { | 
| 829 |  |  |  |  |  |  | $ARGS{'conn'}->run( fixup => sub { | 
| 830 | 0 |  |  |  |  |  | @results = $_->selectrow_array( $ARGS{'sql'}, undef, @{ $ARGS{'keys'} } ); | 
|  | 0 |  |  |  |  |  |  | 
| 831 | 0 |  |  | 0 |  |  | } ); | 
| 832 | 0 | 0 |  |  |  |  | my $count = scalar( @results ) ? 1 : 0; | 
| 833 | 0 |  |  |  |  |  | $log->info( "count: $count" ); | 
| 834 | 0 | 0 |  |  |  |  | $status = ( $count ) | 
| 835 |  |  |  |  |  |  | ? $CELL->status_ok( 'DISPATCH_RECORDS_FOUND', | 
| 836 |  |  |  |  |  |  | args => [ $count ], count => $count, payload => \@results ) | 
| 837 |  |  |  |  |  |  | : $CELL->status_notice( 'DISPATCH_NO_RECORDS_FOUND' ); | 
| 838 |  |  |  |  |  |  | } catch { | 
| 839 | 0 |  |  | 0 |  |  | $status = $CELL->status_err( 'DOCHAZKA_DBI_ERR', args => [ $_ ] ); | 
| 840 | 0 |  |  |  |  |  | }; | 
| 841 | 0 | 0 |  |  |  |  | die "AAAAHAHAAHAAAAAAGGGH! " . __PACKAGE__ . "::select_single" unless $status; | 
| 842 | 0 |  |  |  |  |  | return $status; | 
| 843 |  |  |  |  |  |  | } | 
| 844 |  |  |  |  |  |  |  | 
| 845 |  |  |  |  |  |  |  | 
| 846 |  |  |  |  |  |  | =head2 select_set_of_single_scalar_rows | 
| 847 |  |  |  |  |  |  |  | 
| 848 |  |  |  |  |  |  | Given DBIx::Connector object, an SQL statement, and a set of keys to bind | 
| 849 |  |  |  |  |  |  | into the SQL statement, assume that the statement can return 0-n records | 
| 850 |  |  |  |  |  |  | and that each record consists of a single field that must fit into a single | 
| 851 |  |  |  |  |  |  | scalar value. | 
| 852 |  |  |  |  |  |  |  | 
| 853 |  |  |  |  |  |  | =cut | 
| 854 |  |  |  |  |  |  |  | 
| 855 |  |  |  |  |  |  | sub select_set_of_single_scalar_rows { | 
| 856 | 0 |  |  | 0 | 1 |  | my %ARGS = validate( @_, { | 
| 857 |  |  |  |  |  |  | conn => { isa => 'DBIx::Connector' }, | 
| 858 |  |  |  |  |  |  | sql => { type => SCALAR }, | 
| 859 |  |  |  |  |  |  | keys => { type => ARRAYREF }, | 
| 860 |  |  |  |  |  |  | } ); | 
| 861 | 0 |  |  |  |  |  | $log->debug( "Entering " . __PACKAGE__ . "::select_set_of_single_scalar_rows with | 
| 862 |  |  |  |  |  |  | paramhash " . Dumper( \%ARGS ) ); | 
| 863 |  |  |  |  |  |  |  | 
| 864 | 0 |  |  |  |  |  | my ( $status, $result_set ); | 
| 865 |  |  |  |  |  |  | try { | 
| 866 |  |  |  |  |  |  | $ARGS{'conn'}->run( fixup => sub { | 
| 867 | 0 |  |  |  |  |  | my $sth = $_->prepare( $ARGS{'sql'} ); | 
| 868 | 0 |  |  |  |  |  | my $bc = 0; | 
| 869 |  |  |  |  |  |  | map { | 
| 870 | 0 |  |  |  |  |  | $bc += 1; | 
| 871 | 0 |  | 0 |  |  |  | $sth->bind_param( $bc, $_ || undef ); | 
| 872 | 0 |  |  |  |  |  | } @{ $ARGS{'keys'} }; | 
|  | 0 |  |  |  |  |  |  | 
| 873 | 0 |  |  |  |  |  | $sth->execute(); | 
| 874 |  |  |  |  |  |  | # push results onto $nicks | 
| 875 | 0 |  |  |  |  |  | while( defined( my $tmpres = $sth->fetchrow_arrayref() ) ) { | 
| 876 | 0 |  |  |  |  |  | push @$result_set, @$tmpres; | 
| 877 |  |  |  |  |  |  | } | 
| 878 | 0 |  |  | 0 |  |  | } ); | 
| 879 |  |  |  |  |  |  | } catch { | 
| 880 | 0 |  |  | 0 |  |  | $log->debug( 'Encountered DBI error' ); | 
| 881 | 0 |  |  |  |  |  | $status = $CELL->status_err( 'DOCHAZKA_DBI_ERR', args => [ $_ ] ); | 
| 882 | 0 |  |  |  |  |  | }; | 
| 883 |  |  |  |  |  |  |  | 
| 884 | 0 | 0 |  |  |  |  | return $status if $status; | 
| 885 | 0 |  |  |  |  |  | return $CELL->status_ok( 'RESULT_SET', payload => $result_set ); | 
| 886 |  |  |  |  |  |  | } | 
| 887 |  |  |  |  |  |  |  | 
| 888 |  |  |  |  |  |  |  | 
| 889 |  |  |  |  |  |  | =head2 split_tsrange | 
| 890 |  |  |  |  |  |  |  | 
| 891 |  |  |  |  |  |  | Given a string that might be a tsrange, run it through the database | 
| 892 |  |  |  |  |  |  | using the SQL statement: | 
| 893 |  |  |  |  |  |  |  | 
| 894 |  |  |  |  |  |  | SELECT lower(CAST( ? AS tstzrange )), upper(CAST( ? AS tstzrange )) | 
| 895 |  |  |  |  |  |  |  | 
| 896 |  |  |  |  |  |  | If all goes well, the result will be an array ( from, to ) of two | 
| 897 |  |  |  |  |  |  | timestamps. | 
| 898 |  |  |  |  |  |  |  | 
| 899 |  |  |  |  |  |  | Returns a status object. | 
| 900 |  |  |  |  |  |  |  | 
| 901 |  |  |  |  |  |  | =cut | 
| 902 |  |  |  |  |  |  |  | 
| 903 |  |  |  |  |  |  | sub split_tsrange { | 
| 904 | 0 |  |  | 0 | 1 |  | my ( $conn, $tsr ) = @_; | 
| 905 |  |  |  |  |  |  |  | 
| 906 | 0 |  |  |  |  |  | my $status = select_single( | 
| 907 |  |  |  |  |  |  | conn => $conn, | 
| 908 |  |  |  |  |  |  | sql => 'SELECT lower(CAST( ? AS tstzrange )), upper(CAST( ? AS tstzrange ))', | 
| 909 |  |  |  |  |  |  | keys => [ $tsr, $tsr ], | 
| 910 |  |  |  |  |  |  | ); | 
| 911 | 0 | 0 |  |  |  |  | return $status unless $status->ok; | 
| 912 | 0 |  |  |  |  |  | my ( $lower, $upper ) = @{ $status->payload }; | 
|  | 0 |  |  |  |  |  |  | 
| 913 | 0 | 0 | 0 |  |  |  | return $CELL->status_err( 'DOCHAZKA_UNBOUNDED_TSRANGE' ) unless defined( $lower ) and | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 914 |  |  |  |  |  |  | defined( $upper ) and $lower ne 'infinity' and $upper ne 'infinity'; | 
| 915 | 0 |  |  |  |  |  | return $status; | 
| 916 |  |  |  |  |  |  | } | 
| 917 |  |  |  |  |  |  |  | 
| 918 |  |  |  |  |  |  |  | 
| 919 |  |  |  |  |  |  | =head2 timestamp_delta_minus | 
| 920 |  |  |  |  |  |  |  | 
| 921 |  |  |  |  |  |  | Given a timestamp string and an interval string (e.g. "1 week 3 days" ), | 
| 922 |  |  |  |  |  |  | subtract the interval from the timestamp. | 
| 923 |  |  |  |  |  |  |  | 
| 924 |  |  |  |  |  |  | Returns a status object. If the database operation is successful, the payload | 
| 925 |  |  |  |  |  |  | will contain the resulting timestamp. | 
| 926 |  |  |  |  |  |  |  | 
| 927 |  |  |  |  |  |  | =cut | 
| 928 |  |  |  |  |  |  |  | 
| 929 |  |  |  |  |  |  | sub timestamp_delta_minus { | 
| 930 | 0 |  |  | 0 | 1 |  | my ( $conn, $ts, $delta ) = validate_pos( @_, | 
| 931 |  |  |  |  |  |  | { isa => 'DBIx::Connector' }, | 
| 932 |  |  |  |  |  |  | { type => SCALAR }, | 
| 933 |  |  |  |  |  |  | { type => SCALAR }, | 
| 934 |  |  |  |  |  |  | ); | 
| 935 | 0 |  |  |  |  |  | $log->info( "timestamp_delta_minus: timestamp $ts, delta $delta" ); | 
| 936 | 0 |  |  |  |  |  | my $status = select_single( | 
| 937 |  |  |  |  |  |  | conn => $conn, | 
| 938 |  |  |  |  |  |  | sql => "SELECT CAST( ? AS timestamptz ) - CAST( ? AS interval )", | 
| 939 |  |  |  |  |  |  | keys => [ $ts, $delta ], | 
| 940 |  |  |  |  |  |  | ); | 
| 941 | 0 | 0 |  |  |  |  | if ( $status->ok ) { | 
| 942 | 0 |  |  |  |  |  | my ( $result ) = @{ $status->payload }; | 
|  | 0 |  |  |  |  |  |  | 
| 943 | 0 |  |  |  |  |  | return $CELL->status_ok( 'SUCCESS', payload => $result ); | 
| 944 |  |  |  |  |  |  | } | 
| 945 | 0 |  |  |  |  |  | return $status; | 
| 946 |  |  |  |  |  |  | } | 
| 947 |  |  |  |  |  |  |  | 
| 948 |  |  |  |  |  |  |  | 
| 949 |  |  |  |  |  |  | =head2 timestamp_delta_plus | 
| 950 |  |  |  |  |  |  |  | 
| 951 |  |  |  |  |  |  | Given a timestamp string and an interval string (e.g. "1 week 3 days" ), | 
| 952 |  |  |  |  |  |  | add the interval to the timestamp. | 
| 953 |  |  |  |  |  |  |  | 
| 954 |  |  |  |  |  |  | Returns a status object. If the database operation is successful, the payload | 
| 955 |  |  |  |  |  |  | will contain the resulting timestamp. | 
| 956 |  |  |  |  |  |  |  | 
| 957 |  |  |  |  |  |  | =cut | 
| 958 |  |  |  |  |  |  |  | 
| 959 |  |  |  |  |  |  | sub timestamp_delta_plus { | 
| 960 | 0 |  |  | 0 | 1 |  | my ( $conn, $ts, $delta ) = validate_pos( @_, | 
| 961 |  |  |  |  |  |  | { isa => 'DBIx::Connector' }, | 
| 962 |  |  |  |  |  |  | { type => SCALAR }, | 
| 963 |  |  |  |  |  |  | { type => SCALAR }, | 
| 964 |  |  |  |  |  |  | ); | 
| 965 | 0 |  |  |  |  |  | $log->info( "timestamp_delta_plus: timestamp $ts, delta $delta" ); | 
| 966 | 0 |  |  |  |  |  | my $status = select_single( | 
| 967 |  |  |  |  |  |  | conn => $conn, | 
| 968 |  |  |  |  |  |  | sql => "SELECT CAST( ? AS timestamptz ) + CAST( ? AS interval )", | 
| 969 |  |  |  |  |  |  | keys => [ $ts, $delta ], | 
| 970 |  |  |  |  |  |  | ); | 
| 971 | 0 | 0 |  |  |  |  | if ( $status->ok ) { | 
| 972 | 0 |  |  |  |  |  | my ( $result ) = @{ $status->payload }; | 
|  | 0 |  |  |  |  |  |  | 
| 973 | 0 |  |  |  |  |  | return $CELL->status_ok( 'SUCCESS', payload => $result ); | 
| 974 |  |  |  |  |  |  | } | 
| 975 | 0 |  |  |  |  |  | return $status; | 
| 976 |  |  |  |  |  |  | } | 
| 977 |  |  |  |  |  |  |  | 
| 978 |  |  |  |  |  |  |  | 
| 979 |  |  |  |  |  |  | =head2 tsrange_intersection | 
| 980 |  |  |  |  |  |  |  | 
| 981 |  |  |  |  |  |  | Given two strings that might be tsranges, consult the database and return | 
| 982 |  |  |  |  |  |  | the result of tsrange1 * tsrange2 (also a tsrange). | 
| 983 |  |  |  |  |  |  |  | 
| 984 |  |  |  |  |  |  | =cut | 
| 985 |  |  |  |  |  |  |  | 
| 986 |  |  |  |  |  |  | sub tsrange_intersection { | 
| 987 | 0 |  |  | 0 | 1 |  | my ( $conn, $tr1, $tr2 ) = @_; | 
| 988 |  |  |  |  |  |  |  | 
| 989 | 0 |  |  |  |  |  | my $status = select_single( | 
| 990 |  |  |  |  |  |  | conn => $conn, | 
| 991 |  |  |  |  |  |  | sql => 'SELECT CAST( ? AS tstzrange) * CAST( ? AS tstzrange )', | 
| 992 |  |  |  |  |  |  | keys => [ $tr1, $tr2 ], | 
| 993 |  |  |  |  |  |  | ); | 
| 994 | 0 | 0 |  |  |  |  | die $status->text unless $status->ok; | 
| 995 | 0 |  |  |  |  |  | return $status->payload->[0]; | 
| 996 |  |  |  |  |  |  | } | 
| 997 |  |  |  |  |  |  |  | 
| 998 |  |  |  |  |  |  |  | 
| 999 |  |  |  |  |  |  | =head2 tsrange_equal | 
| 1000 |  |  |  |  |  |  |  | 
| 1001 |  |  |  |  |  |  | Given two strings that might be equal tsranges, consult the database and return | 
| 1002 |  |  |  |  |  |  | the result (true or false). | 
| 1003 |  |  |  |  |  |  |  | 
| 1004 |  |  |  |  |  |  | =cut | 
| 1005 |  |  |  |  |  |  |  | 
| 1006 |  |  |  |  |  |  | sub tsrange_equal { | 
| 1007 | 0 |  |  | 0 | 1 |  | my ( $conn, $tr1, $tr2 ) = @_; | 
| 1008 |  |  |  |  |  |  |  | 
| 1009 | 0 |  |  |  |  |  | my $status = select_single( | 
| 1010 |  |  |  |  |  |  | conn => $conn, | 
| 1011 |  |  |  |  |  |  | sql => 'SELECT CAST( ? AS tstzrange) = CAST( ? AS tstzrange )', | 
| 1012 |  |  |  |  |  |  | keys => [ $tr1, $tr2 ], | 
| 1013 |  |  |  |  |  |  | ); | 
| 1014 | 0 | 0 |  |  |  |  | die $status->text unless $status->ok; | 
| 1015 | 0 |  |  |  |  |  | return $status->payload->[0]; | 
| 1016 |  |  |  |  |  |  | } | 
| 1017 |  |  |  |  |  |  |  | 
| 1018 |  |  |  |  |  |  |  | 
| 1019 |  |  |  |  |  |  |  | 
| 1020 |  |  |  |  |  |  | =head1 AUTHOR | 
| 1021 |  |  |  |  |  |  |  | 
| 1022 |  |  |  |  |  |  | Nathan Cutler, C<< <presnypreklad@gmail.com> >> | 
| 1023 |  |  |  |  |  |  |  | 
| 1024 |  |  |  |  |  |  | =cut | 
| 1025 |  |  |  |  |  |  |  | 
| 1026 |  |  |  |  |  |  | 1; | 
| 1027 |  |  |  |  |  |  |  |