Filename | /mnt/catalyst/koha/svc/members/upsert |
Statements | Executed 74 statements in 15.3ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 10.2ms | 642ms | BEGIN@73 | main::
1 | 1 | 1 | 5.97ms | 223ms | BEGIN@72 | main::
1 | 1 | 1 | 3.10ms | 346ms | BEGIN@75 | main::
3 | 3 | 2 | 2.72ms | 2.72ms | do (xsub) | DBI::db::
1 | 1 | 1 | 2.50ms | 40.4ms | BEGIN@70 | main::
7 | 7 | 4 | 1.84ms | 1.84ms | execute (xsub) | DBI::st::
435 | 8 | 4 | 431µs | 431µs | isa (xsub) | UNIVERSAL::
6 | 1 | 1 | 426µs | 713µs | selectrow_array (xsub) | DBI::db::
1 | 1 | 1 | 171µs | 2.47ms | get_borrower_fields | main::
1 | 1 | 1 | 150µs | 31.0ms | process_upsert | main::
227 | 3 | 1 | 147µs | 147µs | SvREADONLY (xsub) | Internals::
94 | 22 | 10 | 127µs | 127µs | can (xsub) | UNIVERSAL::
69 | 2 | 2 | 120µs | 1.13ms | fetchrow_hashref (xsub) | DBI::st::
75 | 6 | 4 | 113µs | 113µs | FETCH (xsub) | DBI::common::
10 | 10 | 9 | 87µs | 87µs | VERSION (xsub) | UNIVERSAL::
72 | 2 | 2 | 78µs | 78µs | fetch (xsub) | DBI::st::
13 | 8 | 5 | 77µs | 670µs | prepare (xsub) | DBI::db::
21 | 2 | 1 | 60µs | 60µs | fetchrow (xsub) | DBI::st::
24 | 7 | 3 | 58µs | 58µs | DESTROY (xsub) | DBI::common::
69 | 1 | 1 | 45µs | 45µs | mysql_async_ready (xsub) | DBI::st::
17 | 2 | 1 | 44µs | 44µs | func (xsub) | DBI::common::
11 | 8 | 2 | 38µs | 38µs | STORE (xsub) | DBI::common::
1 | 1 | 1 | 33µs | 112µs | find_borrower | main::
1 | 1 | 1 | 25µs | 25µs | ping (xsub) | DBI::db::
1 | 1 | 1 | 22µs | 23µs | O_NOINHERIT | Fcntl::
1 | 1 | 1 | 19µs | 133µs | prepare_cached (xsub) | DBI::db::
12 | 7 | 3 | 16µs | 16µs | DESTROY (xsub) | DBD::_mem::common::
4 | 4 | 2 | 14µs | 14µs | CORE:pack (opcode) | main::
1 | 1 | 1 | 13µs | 198µs | fetchall_arrayref (xsub) | DBI::st::
1 | 1 | 1 | 12µs | 12µs | disconnect_all (xsub) | DBI::dr::
1 | 1 | 1 | 12µs | 1.44ms | connect (xsub) | DBI::dr::
3 | 1 | 1 | 11µs | 52µs | bind_col (xsub) | DBI::st::
1 | 1 | 1 | 10µs | 36µs | BEGIN@77 | main::
1 | 1 | 1 | 10µs | 10µs | create_borrower | main::
1 | 1 | 1 | 9µs | 10µs | O_EXLOCK | Fcntl::
1 | 1 | 1 | 8µs | 9µs | O_TEMPORARY | Fcntl::
1 | 1 | 1 | 8µs | 366µs | get_extended_attribs | main::
1 | 1 | 1 | 8µs | 8µs | BEGIN@74 | main::
1 | 1 | 1 | 8µs | 8µs | fetchrow_array (xsub) | DBI::st::
2 | 2 | 2 | 6µs | 6µs | finish (xsub) | DBI::st::
1 | 1 | 1 | 5µs | 5µs | connected (xsub) | DBI::db::
1 | 1 | 1 | 4µs | 124µs | bind_columns (xsub) | DBI::st::
1 | 1 | 1 | 4µs | 4µs | CORE:match (opcode) | main::
1 | 1 | 1 | 3µs | 3µs | fetchrow_arrayref (xsub) | DBI::st::
0 | 0 | 0 | 0s | 0s | RUNTIME | main::
0 | 0 | 0 | 0s | 0s | find_borrower_from_ext | main::
0 | 0 | 0 | 0s | 0s | update_borrower | main::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | #!/usr/bin/perl | ||||
2 | |||||
3 | # Copyright 2015 Catalyst IT | ||||
4 | # | ||||
5 | # This file is part of Koha. | ||||
6 | # | ||||
7 | # Koha is free software; you can redistribute it and/or modify it under the | ||||
8 | # terms of the GNU General Public License as published by the Free Software | ||||
9 | # Foundation; either version 3 of the License, or (at your option) any later | ||||
10 | # version. | ||||
11 | # | ||||
12 | # Koha is distributed in the hope that it will be useful, but WITHOUT ANY | ||||
13 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | ||||
14 | # A PARTICULAR PURPOSE. See the GNU General Public License for more details. | ||||
15 | # | ||||
16 | # You should have received a copy of the GNU General Public License along | ||||
17 | # with Koha; if not, write to the Free Software Foundation, Inc., | ||||
18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
19 | |||||
20 | =head1 NAME | ||||
21 | |||||
22 | svc/members/upsert - web service for inserting and updating user details | ||||
23 | |||||
24 | =head1 SYNOPSIS | ||||
25 | |||||
26 | POST /svc/members/upsert | ||||
27 | |||||
28 | The request paramters go in the POST body. | ||||
29 | |||||
30 | =head1 DESCRIPTION | ||||
31 | |||||
32 | This allows user data to be added and updated on a Koha system from another | ||||
33 | service. User data is supplied, and if it matches an existing user, that user | ||||
34 | is updated. If not, then a new user is created. A field to match on must be | ||||
35 | provided. For example, this might be an ID of the user in an employee | ||||
36 | management system. It may be an extended attribute. | ||||
37 | |||||
38 | =head1 PARAMETERS | ||||
39 | |||||
40 | The request can contain any field from the borrowers table, and any extended | ||||
41 | attribute name. It must also contain 'matchField' which specifies the field | ||||
42 | to use to see if the user already exists. | ||||
43 | |||||
44 | To clear a field, provide an empty parameter for it. If the parameter doesn't | ||||
45 | exist, then the field will be left alone. | ||||
46 | |||||
47 | Dates must be in YYYY-MM-DD form. | ||||
48 | |||||
49 | Boolean values must be 1 (for true) or 0 (for false.) | ||||
50 | |||||
51 | A borrowernumber field may be provided and used for matching. It will be | ||||
52 | ignored when it comes to creating or updating however. | ||||
53 | |||||
54 | If the matchField parameters returns more than one value, an error will be | ||||
55 | raised. Care should be taken to ensure that it is unique. | ||||
56 | |||||
57 | =head2 Results | ||||
58 | |||||
59 | On success, this will return a result in XML form containing the | ||||
60 | borrowernumber of the record (whether it's newly created or just updated) and | ||||
61 | a 'createOrUpdate' element that contains either 'create' or 'update', | ||||
62 | depending on what operation ended up happening. | ||||
63 | |||||
64 | On error, an error response is returned. This may be because a matchField | ||||
65 | was provided that didn't match anything, or because the matchField | ||||
66 | produced multiple results. Or probably many other things. | ||||
67 | |||||
68 | =cut | ||||
69 | |||||
70 | 2 | 1.87ms | 2 | 40.5ms | # spent 40.4ms (2.50+37.9) within main::BEGIN@70 which was called:
# once (2.50ms+37.9ms) by main::NULL at line 70 # spent 40.4ms making 1 call to main::BEGIN@70
# spent 120µs making 1 call to Modern::Perl::import |
71 | |||||
72 | 2 | 3.52ms | 2 | 272ms | # spent 223ms (5.97+217) within main::BEGIN@72 which was called:
# once (5.97ms+217ms) by main::NULL at line 72 # spent 223ms making 1 call to main::BEGIN@72
# spent 49.5ms making 1 call to C4::Context::import |
73 | 2 | 2.71ms | 2 | 643ms | # spent 642ms (10.2+632) within main::BEGIN@73 which was called:
# once (10.2ms+632ms) by main::NULL at line 73 # spent 642ms making 1 call to main::BEGIN@73
# spent 326µs making 1 call to Exporter::import |
74 | 2 | 22µs | 1 | 8µs | # spent 8µs within main::BEGIN@74 which was called:
# once (8µs+0s) by main::NULL at line 74 # spent 8µs making 1 call to main::BEGIN@74 |
75 | 2 | 2.68ms | 1 | 346ms | # spent 346ms (3.10+343) within main::BEGIN@75 which was called:
# once (3.10ms+343ms) by main::NULL at line 75 # spent 346ms making 1 call to main::BEGIN@75 |
76 | |||||
77 | 2 | 2.81ms | 2 | 61µs | # spent 36µs (10+25) within main::BEGIN@77 which was called:
# once (10µs+25µs) by main::NULL at line 77 # spent 36µs making 1 call to main::BEGIN@77
# spent 25µs making 1 call to Exporter::import |
78 | |||||
79 | 1 | 2µs | 1 | 31.0ms | process_upsert(); # spent 31.0ms making 1 call to main::process_upsert |
80 | |||||
81 | # spent 31.0ms (150µs+30.8) within main::process_upsert which was called:
# once (150µs+30.8ms) by main::RUNTIME at line 79 | ||||
82 | 1 | 6µs | 1 | 24.5ms | my $service = C4::Service->new( { needed_flags => { borrowers => 1 } } ); # spent 24.5ms making 1 call to C4::Service::new |
83 | |||||
84 | 1 | 2µs | 1 | 4µs | my $query = $service->query(); # spent 4µs making 1 call to C4::Service::query |
85 | 1 | 3µs | 1 | 21µs | my @supplied_fields = $query->param(); # spent 21µs making 1 call to CGI::param |
86 | 1 | 6µs | 4 | 2.47ms | my @borrower_fields = get_borrower_fields(); # spent 2.47ms making 1 call to main::get_borrower_fields
# spent 2µs making 2 calls to DBI::common::DESTROY, avg 1µs/call
# spent 900ns making 1 call to DBD::_mem::common::DESTROY |
87 | 1 | 3µs | 1 | 366µs | my @attrib_types = get_extended_attribs(); # spent 366µs making 1 call to main::get_extended_attribs |
88 | 1 | 2µs | 1 | 14µs | my $match_field = $query->param('matchField'); # spent 14µs making 1 call to CGI::param |
89 | |||||
90 | # Make a mapping of these so we can do fast lookups | ||||
91 | 1 | 43µs | my %borrower_fields = map { $_ => 1 } @borrower_fields; | ||
92 | 1 | 2µs | my %attrib_types = map { $_ => 1 } @attrib_types; | ||
93 | |||||
94 | 1 | 300ns | $service->return_error( 'parameters', 'No matchField provided' ) | ||
95 | unless $match_field; | ||||
96 | 1 | 500ns | my $is_borrower_field; # for matchField, as opposed to an ext attribute | ||
97 | 1 | 600ns | $is_borrower_field = $borrower_fields{$match_field} // 0; | ||
98 | 1 | 300ns | if ( !$is_borrower_field ) { | ||
99 | $service->return_error( 'parameters', | ||||
100 | 'Provided matchField doesn\'t match a valid field' ) | ||||
101 | unless $attrib_types{$match_field}; | ||||
102 | } | ||||
103 | 1 | 2µs | 1 | 7µs | my $match_value = $query->param($match_field); # spent 7µs making 1 call to CGI::param |
104 | 1 | 200ns | $service->return_error( 'parameters', | ||
105 | 'The field specified by matchField wasn\'t provided.' ) | ||||
106 | unless $match_value; | ||||
107 | |||||
108 | # verify we are only given valid fields, at the same time make a mapping | ||||
109 | # of them. | ||||
110 | 1 | 300ns | my ( %supplied_data_borr, %supplied_data_ext ); | ||
111 | 1 | 1µs | foreach my $f (@supplied_fields) { | ||
112 | 5 | 1µs | next if $f eq 'matchField'; | ||
113 | 4 | 1µs | $service->return_error( 'parameters', "Invalid parameter provided: $f" ) | ||
114 | unless $borrower_fields{$f} || $attrib_types{$f}; | ||||
115 | 4 | 6µs | 3 | 14µs | if ( $borrower_fields{$f} ) { # spent 14µs making 3 calls to CGI::param, avg 5µs/call |
116 | $supplied_data_borr{$f} = $query->param($f); | ||||
117 | } | ||||
118 | else { | ||||
119 | 1 | 1µs | 1 | 5µs | $supplied_data_ext{$f} = $query->param($f); # spent 5µs making 1 call to CGI::param |
120 | } | ||||
121 | } | ||||
122 | |||||
123 | # Sanity checks and data extraction over, find our borrower. | ||||
124 | 1 | 200ns | my $borr_num; | ||
125 | 1 | 600ns | eval { | ||
126 | 1 | 2µs | 4 | 114µs | if ($is_borrower_field) { # spent 112µs making 1 call to main::find_borrower
# spent 2µs making 2 calls to DBI::common::DESTROY, avg 1µs/call
# spent 700ns making 1 call to DBD::_mem::common::DESTROY |
127 | $borr_num = find_borrower( $match_field, $match_value ); | ||||
128 | } | ||||
129 | else { | ||||
130 | $borr_num = find_borrower_from_ext( $match_field, $match_value ); | ||||
131 | } | ||||
132 | }; | ||||
133 | 1 | 200ns | if ($@) { | ||
134 | $service->return_error( 'borrower', $@ ); | ||||
135 | } | ||||
136 | |||||
137 | # Now we know if we're creating a new user, or updating an existing one. | ||||
138 | 1 | 100ns | my $change_type; | ||
139 | 1 | 400ns | eval { | ||
140 | 1 | 600ns | if ($borr_num) { | ||
141 | update_borrower( $borr_num, \%supplied_data_borr, | ||||
142 | \%supplied_data_ext ); | ||||
143 | $change_type = 'update'; | ||||
144 | } | ||||
145 | else { | ||||
146 | 1 | 2µs | 1 | 10µs | $borr_num = # spent 10µs making 1 call to main::create_borrower |
147 | create_borrower( \%supplied_data_borr, \%supplied_data_ext ); | ||||
148 | $change_type = 'create'; | ||||
149 | } | ||||
150 | }; | ||||
151 | 1 | 300ns | if ($@) { | ||
152 | 1 | 2µs | 1 | 3.28ms | $service->return_error( 'data', $@ ); # spent 3.28ms making 1 call to C4::Service::return_error |
153 | } | ||||
154 | |||||
155 | $service->output_stream->param( 'borrowernumber', $borr_num ); | ||||
156 | $service->output_stream->param( 'createOrUpdate', $change_type ); | ||||
157 | $service->return_success(); | ||||
158 | } | ||||
159 | |||||
160 | =head2 get_borrower_fields | ||||
161 | |||||
162 | Fetches the list of columns from the borrower table. Returns a list. | ||||
163 | |||||
164 | =cut | ||||
165 | |||||
166 | # spent 2.47ms (171µs+2.29) within main::get_borrower_fields which was called:
# once (171µs+2.29ms) by main::process_upsert at line 86 | ||||
167 | 1 | 2µs | 1 | 4µs | my $dbh = C4::Context->dbh(); # spent 4µs making 1 call to C4::Context::dbh |
168 | |||||
169 | 1 | 200ns | my @fields; | ||
170 | 1 | 400ns | my $q = 'SHOW COLUMNS FROM borrowers'; | ||
171 | 1 | 5µs | 2 | 63µs | my $sth = $dbh->prepare($q); # spent 34µs making 1 call to DBI::db::prepare
# spent 30µs making 1 call to DBD::mysql::db::prepare |
172 | 1 | 1.22ms | 1 | 1.21ms | $sth->execute(); # spent 1.21ms making 1 call to DBI::st::execute |
173 | 1 | 212µs | 136 | 1.97ms | while ( my $row = $sth->fetchrow_hashref() ) { # spent 1.04ms making 68 calls to DBI::st::fetchrow_hashref, avg 15µs/call
# spent 930µs making 68 calls to DBD::mysql::st::__ANON__[DBD/mysql.pm:799], avg 14µs/call |
174 | push @fields, $row->{Field}; | ||||
175 | } | ||||
176 | 1 | 22µs | return @fields; | ||
177 | } | ||||
178 | |||||
179 | =head2 get_extended_attribs | ||||
180 | |||||
181 | Fetch all the extended attributes from the system. Returns a list. | ||||
182 | |||||
183 | =cut | ||||
184 | |||||
185 | # spent 366µs (8+358) within main::get_extended_attribs which was called:
# once (8µs+358µs) by main::process_upsert at line 87 | ||||
186 | 1 | 9µs | 4 | 361µs | return map { $_->{code} } C4::Members::AttributeTypes::GetAttributeTypes(); # spent 358µs making 1 call to C4::Members::AttributeTypes::GetAttributeTypes
# spent 2µs making 2 calls to DBI::common::DESTROY, avg 1µs/call
# spent 1µs making 1 call to DBD::_mem::common::DESTROY |
187 | } | ||||
188 | |||||
189 | =head2 find_borrower | ||||
190 | |||||
191 | $borr_num = find_borrower($field, $value); | ||||
192 | |||||
193 | Given a field and value, this will look up a borrower by that name. If none | ||||
194 | is found, C<undef> is returned. If more than one is found, an exception is | ||||
195 | raised. | ||||
196 | |||||
197 | It performs some basic validation to ensure the field is safe for searching. | ||||
198 | |||||
199 | =cut | ||||
200 | |||||
201 | # spent 112µs (33+78) within main::find_borrower which was called:
# once (33µs+78µs) by main::process_upsert at line 126 | ||||
202 | 1 | 500ns | my ( $field, $value ) = @_; | ||
203 | |||||
204 | # We could use C4::Members::GetMember here, however it doesn't quite | ||||
205 | # allow us to do what we need. | ||||
206 | # TODO for upstreaming: move this into Koha::Borrowers | ||||
207 | |||||
208 | 1 | 8µs | 1 | 4µs | die "Field value may not be safe for SQL" if $field =~ /[^A-Za-z0-9]/; # spent 4µs making 1 call to main::CORE:match |
209 | 1 | 2µs | 1 | 4µs | my $dbh = C4::Context->dbh(); # spent 4µs making 1 call to C4::Context::dbh |
210 | 1 | 1µs | my $q = "SELECT borrowernumber FROM borrowers WHERE $field=?"; | ||
211 | 1 | 5µs | 2 | 54µs | my $sth = $dbh->prepare($q); # spent 29µs making 1 call to DBI::db::prepare
# spent 25µs making 1 call to DBD::mysql::db::prepare |
212 | 1 | 40µs | 1 | 35µs | $sth->execute($value); # spent 35µs making 1 call to DBI::st::execute |
213 | 1 | 9µs | 1 | 3µs | my $row = $sth->fetchrow_arrayref(); # spent 3µs making 1 call to DBI::st::fetchrow_arrayref |
214 | 1 | 15µs | return undef unless $row; | ||
215 | my $bib = $row->[0]; | ||||
216 | die "Multiple borrowers match provided values\n" | ||||
217 | if $sth->fetchrow_arrayref(); | ||||
218 | return $bib; | ||||
219 | } | ||||
220 | |||||
221 | =head2 find_borrower_from_ext | ||||
222 | |||||
223 | $borr_num = find_borrower_from_ext($field, $value); | ||||
224 | |||||
225 | Given an extended attribute code and a value, this finds the borrower with the | ||||
226 | matching attribute. If none is found, C<undef> is returned. If more than one | ||||
227 | is found, an exception is raised. | ||||
228 | |||||
229 | =cut | ||||
230 | |||||
231 | sub find_borrower_from_ext { | ||||
232 | my ( $code, $value ) = @_; | ||||
233 | |||||
234 | # TODO for upstreaming: move this somewhere under Koha::Borrowers | ||||
235 | |||||
236 | my $dbh = C4::Context->dbh(); | ||||
237 | my $q = | ||||
238 | 'SELECT DISTINCT borrowernumber FROM borrower_attributes WHERE code=? AND attribute=?'; | ||||
239 | my $sth = $dbh->prepare($q); | ||||
240 | $sth->execute( $code, $value ); | ||||
241 | my $row = $sth->fetchrow_arrayref(); | ||||
242 | return undef unless $row; | ||||
243 | my $bib = $row->[0]; | ||||
244 | die "Multiple borrowers match provided values\n" | ||||
245 | if $sth->fetchrow_arrayref(); | ||||
246 | return $bib; | ||||
247 | } | ||||
248 | |||||
249 | =head2 update_borrower | ||||
250 | |||||
251 | update_borrower($borr_num, \%borrower_fields, \%ext_attrib_fields); | ||||
252 | |||||
253 | This takes a borrower number, a hashref containing the fields and values for | ||||
254 | the borrower, and a hashref containing the fields and values for the extended | ||||
255 | attributes. It will update the borrower to set its fields to the values | ||||
256 | supplied. | ||||
257 | |||||
258 | A supplied borrowernumber will be ignored. | ||||
259 | |||||
260 | =cut | ||||
261 | |||||
262 | sub update_borrower { | ||||
263 | my ( $borr_num, $borr_fields, $ext_fields ) = @_; | ||||
264 | |||||
265 | # For the first phase, we build an update query for the borrower | ||||
266 | my ( @f_borr, @v_borr ); | ||||
267 | while ( my ( $f, $v ) = each %$borr_fields ) { | ||||
268 | next if $f =~ /^borrowernumber$/i; | ||||
269 | die "Invalid fieldname provided (update): $f\n" if $f =~ /[^A-Za-z0-9]/; | ||||
270 | push @f_borr, $f; | ||||
271 | push @v_borr, $v; | ||||
272 | } | ||||
273 | my $q_borr = | ||||
274 | 'UPDATE borrowers SET ' | ||||
275 | . ( join ',', map { $_ . '=?' } @f_borr ) | ||||
276 | . ' WHERE borrowernumber=?'; | ||||
277 | |||||
278 | # Now queries to sort out the extended fields | ||||
279 | my @f_ext = keys %$ext_fields; | ||||
280 | my @v_ext = values %$ext_fields; | ||||
281 | my $q_ext_del = | ||||
282 | 'DELETE FROM borrower_attributes WHERE borrowernumber=? AND code IN (' | ||||
283 | . ( join ',', map { '?' } @f_ext ) . ')'; | ||||
284 | my $q_ext_add = | ||||
285 | 'INSERT INTO borrower_attributes (borrowernumber, code, attribute) VALUES (?, ?, ?)'; | ||||
286 | |||||
287 | my $dbh = C4::Context->dbh(); | ||||
288 | |||||
289 | # Finally, run these all inside a transaction. | ||||
290 | eval { | ||||
291 | $dbh->begin_work; | ||||
292 | |||||
293 | my $sth; | ||||
294 | |||||
295 | if (@f_borr) { | ||||
296 | $sth = $dbh->prepare($q_borr); | ||||
297 | $sth->execute( @v_borr, $borr_num ); | ||||
298 | } | ||||
299 | |||||
300 | $sth = $dbh->prepare($q_ext_del); | ||||
301 | $sth->execute( $borr_num, @f_ext ); | ||||
302 | |||||
303 | $sth = $dbh->prepare($q_ext_add); | ||||
304 | while ( my ( $f, $v ) = each %$ext_fields ) { | ||||
305 | next if $v eq ''; | ||||
306 | $sth->execute( $borr_num, $f, $v ); | ||||
307 | } | ||||
308 | |||||
309 | $dbh->commit; | ||||
310 | }; | ||||
311 | if ($@) { | ||||
312 | $dbh->rollback; | ||||
313 | die "Failed to update borrower record: $@\n"; | ||||
314 | } | ||||
315 | return $borr_num; | ||||
316 | } | ||||
317 | |||||
318 | =head2 create_borrower | ||||
319 | |||||
320 | my $borr_num = create_borrower(\%borrower_fields, \%ext_attrib_fields); | ||||
321 | |||||
322 | This creates a new borrower using the supplied data. | ||||
323 | |||||
324 | A supplied borrowernumber will be ignored. | ||||
325 | |||||
326 | The borrowernumber of the new borrower will be returned. | ||||
327 | |||||
328 | =cut | ||||
329 | |||||
330 | # spent 10µs within main::create_borrower which was called:
# once (10µs+0s) by main::process_upsert at line 146 | ||||
331 | 1 | 600ns | my ( $borr_fields, $ext_fields ) = @_; | ||
332 | |||||
333 | 1 | 1µs | my @criticals = qw(surname branchcode categorycode); | ||
334 | |||||
335 | # Check we have the ones we need | ||||
336 | 1 | 500ns | foreach my $c (@criticals) { | ||
337 | 2 | 11µs | die "Critical field missing (create): $c\n" unless $borr_fields->{$c}; | ||
338 | } | ||||
339 | |||||
340 | # Borrower fields | ||||
341 | my ( @f_borr, @v_borr ); | ||||
342 | while ( my ( $f, $v ) = each %$borr_fields ) { | ||||
343 | die "Invalid fieldname provided: $f\n" if $f =~ /[^A-Za-z0-9]/; | ||||
344 | push @f_borr, $f; | ||||
345 | push @v_borr, $v; | ||||
346 | } | ||||
347 | my $q_borr = | ||||
348 | 'INSERT INTO borrowers (' | ||||
349 | . ( join ',', @f_borr ) | ||||
350 | . ') VALUES (' | ||||
351 | . join ',', map { '?' } @f_borr . ')'; | ||||
352 | |||||
353 | # Extended attributes | ||||
354 | my @f_ext = keys %$ext_fields; | ||||
355 | my @v_ext = values %$ext_fields; | ||||
356 | my $q_ext_add = | ||||
357 | 'INSERT INTO borrower_attributes (borrowernumber, code, attribute) VALUES (?, ?, ?)'; | ||||
358 | |||||
359 | my $dbh = C4::Context->dbh(); | ||||
360 | |||||
361 | # Finally, run these all inside a transaction. | ||||
362 | my $borr_num; | ||||
363 | eval { | ||||
364 | $dbh->begin_work; | ||||
365 | |||||
366 | my $sth = $dbh->prepare($q_borr); | ||||
367 | $sth->execute(@v_borr); | ||||
368 | my $borr_num = $dbh->last_insert_id( undef, undef, undef, undef ); | ||||
369 | |||||
370 | $sth = $dbh->prepare($q_ext_add); | ||||
371 | while ( my ( $f, $v ) = each %$ext_fields ) { | ||||
372 | $sth->execute( $borr_num, $f, $v ); | ||||
373 | } | ||||
374 | |||||
375 | $dbh->commit; | ||||
376 | }; | ||||
377 | if ($@) { | ||||
378 | $dbh->rollback; | ||||
379 | die "Failed to create borrower record: $@\n"; | ||||
380 | } | ||||
381 | return $borr_num; | ||||
382 | } | ||||
# spent 16µs within DBD::_mem::common::DESTROY which was called 12 times, avg 1µs/call:
# 6 times (9µs+0s) by C4::Context::preference at line 566 of C4/Context.pm, avg 2µs/call
# once (2µs+0s) by C4::Auth::get_user_subpermissions at line 1652 of C4/Auth.pm
# once (2µs+0s) by C4::Auth::getuserflags at line 1747 of C4/Auth.pm
# once (1µs+0s) by C4::Auth::haspermission at line 1232 of C4/Auth.pm
# once (1µs+0s) by C4::Members::AttributeTypes::GetAttributeTypes at line 186
# once (900ns+0s) by main::get_borrower_fields at line 86
# once (700ns+0s) by main::find_borrower at line 126 | |||||
# spent 58µs within DBI::common::DESTROY which was called 24 times, avg 2µs/call:
# 12 times (23µs+0s) by C4::Context::preference at line 566 of C4/Context.pm, avg 2µs/call
# 2 times (18µs+0s) by C4::Auth::haspermission at line 1232 of C4/Auth.pm, avg 9µs/call
# 2 times (6µs+0s) by C4::Auth::get_user_subpermissions at line 1652 of C4/Auth.pm, avg 3µs/call
# 2 times (4µs+0s) by C4::Auth::getuserflags at line 1747 of C4/Auth.pm, avg 2µs/call
# 2 times (2µs+0s) by main::get_borrower_fields at line 86, avg 1µs/call
# 2 times (2µs+0s) by C4::Members::AttributeTypes::GetAttributeTypes at line 186, avg 1µs/call
# 2 times (2µs+0s) by main::find_borrower at line 126, avg 1µs/call | |||||
# spent 113µs within DBI::common::FETCH which was called 75 times, avg 2µs/call:
# 69 times (76µs+0s) by DBD::_::st::fetchrow_hashref at line 798 of DBD/mysql.pm, avg 1µs/call
# 2 times (7µs+0s) by DBD::_::st::fetchall_arrayref at line 2064 of DBI.pm, avg 4µs/call
# once (17µs+0s) by DBD::_::st::bind_columns at line 1888 of DBI.pm
# once (7µs+0s) by C4::Context::_new_dbh at line 819 of C4/Context.pm
# once (3µs+0s) by CGI::Session::Driver::DBI::DESTROY at line 141 of CGI/Session/Driver/DBI.pm
# once (2µs+0s) by C4::Context::_new_dbh at line 820 of C4/Context.pm | |||||
# spent 38µs within DBI::common::STORE which was called 11 times, avg 3µs/call:
# 3 times (14µs+0s) by DBI::__ANON__[/usr/lib/x86_64-linux-gnu/perl5/5.20/DBI.pm:738] at line 723 of DBI.pm, avg 5µs/call
# 2 times (6µs+0s) by C4::Context::_new_dbh at line 818 of C4/Context.pm, avg 3µs/call
# once (7µs+0s) by DBI::connect at line 742 of DBI.pm
# once (4µs+0s) by DBI::__ANON__[/usr/lib/x86_64-linux-gnu/perl5/5.20/DBI.pm:738] at line 726 of DBI.pm
# once (2µs+0s) by C4::Context::_new_dbh at line 819 of C4/Context.pm
# once (2µs+0s) by C4::Context::_new_dbh at line 820 of C4/Context.pm
# once (2µs+0s) by C4::Context::_new_dbh at line 828 of C4/Context.pm
# once (1µs+0s) by C4::Context::_new_dbh at line 836 of C4/Context.pm | |||||
# spent 44µs within DBI::common::func which was called 17 times, avg 3µs/call:
# 13 times (38µs+0s) by DBD::mysql::db::prepare at line 232 of DBD/mysql.pm, avg 3µs/call
# 4 times (6µs+0s) by DBD::mysql::st::__ANON__[/usr/lib/x86_64-linux-gnu/perl5/5.20/DBD/mysql.pm:810] at line 808 of DBD/mysql.pm, avg 2µs/call | |||||
# spent 5µs within DBI::db::connected which was called:
# once (5µs+0s) by DBI::__ANON__[/usr/lib/x86_64-linux-gnu/perl5/5.20/DBI.pm:738] at line 733 of DBI.pm | |||||
# spent 2.72ms within DBI::db::do which was called 3 times, avg 908µs/call:
# once (2.56ms+0s) by CGI::Session::Driver::mysql::store at line 50 of CGI/Session/Driver/mysql.pm
# once (93µs+0s) by C4::Context::_new_dbh at line 821 of C4/Context.pm
# once (70µs+0s) by C4::Context::_new_dbh at line 837 of C4/Context.pm | |||||
# spent 25µs within DBI::db::ping which was called:
# once (25µs+0s) by CGI::Session::Driver::DBI::DESTROY at line 136 of CGI/Session/Driver/DBI.pm | |||||
# spent 670µs (77+593) within DBI::db::prepare which was called 13 times, avg 52µs/call:
# 6 times (26µs+261µs) by DBI::db::selectrow_array at line 563 of C4/Context.pm, avg 48µs/call
# once (9µs+67µs) by DBD::_::db::prepare_cached at line 1717 of DBI.pm
# once (10µs+57µs) by C4::Auth::haspermission at line 1744 of C4/Auth.pm
# once (9µs+56µs) by C4::Auth::getuserflags at line 1640 of C4/Auth.pm
# once (9µs+56µs) by C4::Auth::get_user_subpermissions at line 1690 of C4/Auth.pm
# once (6µs+41µs) by C4::Members::AttributeTypes::GetAttributeTypes at line 86 of C4/Members/AttributeTypes.pm
# once (4µs+30µs) by main::get_borrower_fields at line 171
# once (4µs+25µs) by main::find_borrower at line 211 | |||||
# spent 133µs (19+114) within DBI::db::prepare_cached which was called:
# once (19µs+114µs) by CGI::Session::Driver::DBI::retrieve at line 68 of CGI/Session/Driver/DBI.pm | |||||
# spent 713µs (426+286) within DBI::db::selectrow_array which was called 6 times, avg 119µs/call:
# 6 times (426µs+286µs) by C4::Context::preference at line 563 of C4/Context.pm, avg 119µs/call | |||||
# spent 1.44ms (12µs+1.43) within DBI::dr::connect which was called:
# once (12µs+1.43ms) by DBI::__ANON__[/usr/lib/x86_64-linux-gnu/perl5/5.20/DBI.pm:738] at line 671 of DBI.pm | |||||
# spent 12µs within DBI::dr::disconnect_all which was called:
# once (12µs+0s) by DBI::disconnect_all at line 750 of DBI.pm | |||||
# spent 52µs (11+40) within DBI::st::bind_col which was called 3 times, avg 17µs/call:
# 3 times (11µs+40µs) by DBD::_::st::bind_columns at line 1899 of DBI.pm, avg 17µs/call | |||||
# spent 124µs (4+120) within DBI::st::bind_columns which was called:
# once (4µs+120µs) by DBD::_::st::fetchall_arrayref at line 2064 of DBI.pm | |||||
# spent 1.84ms within DBI::st::execute which was called 7 times, avg 262µs/call:
# once (1.21ms+0s) by main::get_borrower_fields at line 172
# once (347µs+0s) by CGI::Session::Driver::DBI::retrieve at line 72 of CGI/Session/Driver/DBI.pm
# once (67µs+0s) by C4::Auth::haspermission at line 1745 of C4/Auth.pm
# once (64µs+0s) by C4::Auth::get_user_subpermissions at line 1696 of C4/Auth.pm
# once (60µs+0s) by C4::Auth::getuserflags at line 1641 of C4/Auth.pm
# once (52µs+0s) by C4::Members::AttributeTypes::GetAttributeTypes at line 87 of C4/Members/AttributeTypes.pm
# once (35µs+0s) by main::find_borrower at line 212 | |||||
# spent 78µs within DBI::st::fetch which was called 72 times, avg 1µs/call:
# 69 times (74µs+0s) by DBD::_::st::fetchrow_hashref at line 798 of DBD/mysql.pm, avg 1µs/call
# 3 times (5µs+0s) by DBD::_::st::fetchall_arrayref at line 2071 of DBI.pm, avg 2µs/call | |||||
# spent 198µs (13+185) within DBI::st::fetchall_arrayref which was called:
# once (13µs+185µs) by C4::Members::AttributeTypes::GetAttributeTypes at line 88 of C4/Members/AttributeTypes.pm | |||||
# spent 60µs within DBI::st::fetchrow which was called 21 times, avg 3µs/call:
# 20 times (47µs+0s) by C4::Auth::getuserflags at line 1643 of C4/Auth.pm, avg 2µs/call
# once (13µs+0s) by C4::Auth::haspermission at line 1746 of C4/Auth.pm | |||||
# spent 8µs within DBI::st::fetchrow_array which was called:
# once (8µs+0s) by CGI::Session::Driver::DBI::retrieve at line 74 of CGI/Session/Driver/DBI.pm | |||||
# spent 3µs within DBI::st::fetchrow_arrayref which was called:
# once (3µs+0s) by main::find_borrower at line 213 | |||||
# spent 1.13ms (120µs+1.01) within DBI::st::fetchrow_hashref which was called 69 times, avg 16µs/call:
# 68 times (112µs+930µs) by main::get_borrower_fields at line 173, avg 15µs/call
# once (9µs+81µs) by C4::Auth::get_user_subpermissions at line 1699 of C4/Auth.pm | |||||
# spent 6µs within DBI::st::finish which was called 2 times, avg 3µs/call:
# once (5µs+0s) by CGI::Session::Driver::DBI::retrieve at line 76 of CGI/Session/Driver/DBI.pm
# once (1µs+0s) by C4::Members::AttributeTypes::GetAttributeTypes at line 89 of C4/Members/AttributeTypes.pm | |||||
# spent 45µs within DBI::st::mysql_async_ready which was called 69 times, avg 652ns/call:
# 69 times (45µs+0s) by DBD::mysql::st::__ANON__[/usr/lib/x86_64-linux-gnu/perl5/5.20/DBD/mysql.pm:799] at line 795 of DBD/mysql.pm, avg 652ns/call | |||||
# spent 147µs within Internals::SvREADONLY which was called 227 times, avg 648ns/call:
# 225 times (145µs+0s) by constant::import at line 149 of constant.pm, avg 646ns/call
# once (1µs+0s) by constant::BEGIN@24 at line 32 of constant.pm
# once (500ns+0s) by constant::BEGIN@24 at line 33 of constant.pm | |||||
# spent 87µs within UNIVERSAL::VERSION which was called 10 times, avg 9µs/call:
# once (12µs+0s) by Module::Implementation::BEGIN@8 at line 8 of Module/Implementation.pm
# once (11µs+0s) by Try::Tiny::BEGIN@12 at line 12 of Try/Tiny.pm
# once (10µs+0s) by DateTime::TimeZone::BEGIN@15 at line 15 of DateTime/TimeZone.pm
# once (9µs+0s) by Data::OptList::BEGIN@11 at line 11 of Data/OptList.pm
# once (9µs+0s) by Class::Load::XS::BEGIN@11 at line 11 of Class/Load/XS.pm
# once (8µs+0s) by Class::Load::BEGIN@11 at line 11 of Class/Load.pm
# once (8µs+0s) by Sub::Name::BEGIN@58 at line 58 of Sub/Name.pm
# once (7µs+0s) by Class::Load::BEGIN@12 at line 17 of Class/Load.pm
# once (7µs+0s) by Package::Stash::BEGIN@13 at line 13 of Package/Stash.pm
# once (6µs+0s) by Module::Runtime::use_module at line 349 of Module/Runtime.pm | |||||
# spent 127µs within UNIVERSAL::can which was called 94 times, avg 1µs/call:
# 44 times (38µs+0s) by Math::BigInt::import at line 2842 of Math/BigInt.pm, avg 868ns/call
# 10 times (18µs+0s) by DateTime::Format::Builder::Parser::create_single_parser at line 181 of DateTime/Format/Builder/Parser.pm, avg 2µs/call
# 4 times (5µs+0s) by XML::SAX::Base::comment at line 2354 of XML/SAX/Base.pm, avg 1µs/call
# 4 times (5µs+0s) by XML::SAX::Base::start_prefix_mapping at line 1112 of XML/SAX/Base.pm, avg 1µs/call
# 4 times (4µs+0s) by XML::SAX::Base::end_prefix_mapping at line 54 of XML/SAX/Base.pm, avg 925ns/call
# 3 times (12µs+0s) by Math::BigInt::import at line 2903 of Math/BigInt.pm, avg 4µs/call
# 3 times (7µs+0s) by if::work at line 14 of if.pm, avg 2µs/call
# 2 times (7µs+0s) by XML::SAX::ParserFactory::parser at line 36 of XML/SAX/ParserFactory.pm, avg 3µs/call
# 2 times (2µs+0s) by XML::SAX::Base::start_document at line 1250 of XML/SAX/Base.pm, avg 1µs/call
# 2 times (2µs+0s) by XML::SAX::Base::parse at line 2608 of XML/SAX/Base.pm, avg 1µs/call
# 2 times (2µs+0s) by XML::SAX::Base::start_element at line 284 of XML/SAX/Base.pm, avg 1µs/call
# 2 times (2µs+0s) by XML::SAX::Base::characters at line 192 of XML/SAX/Base.pm, avg 1µs/call
# 2 times (2µs+0s) by XML::SAX::Base::end_document at line 1434 of XML/SAX/Base.pm, avg 950ns/call
# 2 times (2µs+0s) by XML::SAX::Base::end_element at line 2193 of XML/SAX/Base.pm, avg 900ns/call
# once (5µs+0s) by attributes::import at line 59 of attributes.pm
# once (3µs+0s) by CGI::Session::Serialize::yaml::freeze at line 18 of CGI/Session/Serialize/yaml.pm
# once (3µs+0s) by CGI::Session::Serialize::yaml::thaw at line 24 of CGI/Session/Serialize/yaml.pm
# once (2µs+0s) by Math::BigInt::import at line 2826 of Math/BigInt.pm
# once (2µs+0s) by autouse::vet_import at line 75 of autouse.pm
# once (2µs+0s) by DateTime::Locale::_load_class_from_id at line 279 of DateTime/Locale.pm
# once (1µs+0s) by Sub::Install::__ANON__[/usr/share/perl5/Sub/Install.pm:118] at line 106 of Sub/Install.pm
# once (1µs+0s) by DateTime::Locale::_load_class_from_id at line 292 of DateTime/Locale.pm | |||||
# spent 431µs within UNIVERSAL::isa which was called 435 times, avg 990ns/call:
# 192 times (141µs+0s) by XML::Simple::collapse at line 1114 of XML/Simple.pm, avg 734ns/call
# 168 times (162µs+0s) by XML::Simple::collapse at line 1076 of XML/Simple.pm, avg 963ns/call
# 38 times (82µs+0s) by base::import at line 89 of base.pm, avg 2µs/call
# 20 times (8µs+0s) by XML::Simple::array_to_hash at line 1281 of XML/Simple.pm, avg 420ns/call
# 6 times (15µs+0s) by DBI::setup_driver at line 845 of DBI.pm, avg 2µs/call
# 6 times (12µs+0s) by DBI::setup_driver at line 850 of DBI.pm, avg 2µs/call
# 4 times (9µs+0s) by XML::Simple::_get_object at line 162 of XML/Simple.pm, avg 2µs/call
# once (2µs+0s) by CGI::header at line 17 of (eval 81)[CGI.pm:932] | |||||
# spent 4µs within main::CORE:match which was called:
# once (4µs+0s) by main::find_borrower at line 208 | |||||
# spent 14µs within main::CORE:pack which was called 4 times, avg 4µs/call:
# once (11µs+0s) by MARC::Charset::BEGIN@11 at line 4 of Unicode/Normalize.pm
# once (2µs+0s) by charnames::BEGIN@6 at line 88 of _charnames.pm
# once (1µs+0s) by MARC::Charset::BEGIN@11 at line 48 of Unicode/Normalize.pm
# once (600ns+0s) by charnames::BEGIN@6 at line 89 of _charnames.pm |