line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package DBIx::Pg::CallFunction; |
2
|
|
|
|
|
|
|
our $VERSION = '0.019'; |
3
|
2
|
|
|
2
|
|
41906
|
use 5.008; |
|
2
|
|
|
|
|
8
|
|
|
2
|
|
|
|
|
148
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 NAME |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
DBIx::Pg::CallFunction - Simple interface for calling PostgreSQL functions from Perl |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 VERSION |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
version 0.019 |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 SYNOPSIS |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
use DBI; |
16
|
|
|
|
|
|
|
use DBIx::Pg::CallFunction; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
my $dbh = DBI->connect("dbi:Pg:dbname=joel", 'joel', ''); |
19
|
|
|
|
|
|
|
my $pg = DBIx::Pg::CallFunction->new($dbh); |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
Returning single-row single-column values: |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
my $userid = $pg->get_userid_by_username({'username' => 'joel'}); |
24
|
|
|
|
|
|
|
# returns scalar 123 |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
Returning multi-row single-column values: |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
my $hosts = $pg->get_user_hosts({userid => 123}); |
29
|
|
|
|
|
|
|
# returns array ref ['127.0.0.1', '192.168.0.1', ...] |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
Returning single-row multi-column values: |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
my $user_details = $pg->get_user_details({userid => 123}); |
34
|
|
|
|
|
|
|
# returns hash ref { firstname=>..., lastname=>... } |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
Returning multi-row multi-column values: |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
my $user_friends = $pg->get_user_friends({userid => 123}); |
39
|
|
|
|
|
|
|
# returns array ref of hash refs [{ userid=>..., firstname=>..., lastname=>...}, ...] |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=head1 DESCRIPTION |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
This module provides a simple efficient way to call PostgreSQL functions |
44
|
|
|
|
|
|
|
with from Perl code. It only support functions with named arguments, or |
45
|
|
|
|
|
|
|
functions with no arguments at all. This limitation reduces the mapping |
46
|
|
|
|
|
|
|
complexity, as multiple functions in PostgreSQL can share the same name, |
47
|
|
|
|
|
|
|
but with different input argument types. |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
Please see L for an example on how to use this module. |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=head1 CONSTRUCTOR METHODS |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
The following constructor methods are available: |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
=over 4 |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
=item my $pg = DBIx::Pg::CallFunction->new($dbh, [$hashref]) |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
This method constructs a new C object and returns it. |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
$dbh is a handle to your database connection. |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
$hashref is an optional reference to a hash containing configuration parameters. |
64
|
|
|
|
|
|
|
If it not present, the default values will be used. |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
=back |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=head2 CONFIGURATION PARAMETERS |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
The following configuration parameters are available: |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=over 4 |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=item EnableFunctionLookupCache |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
When enabled, the procedure returns set for each function will be cached. |
77
|
|
|
|
|
|
|
This is disabled by default. |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=item RaiseError |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
By default, this is enabled. It is used like L. |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=back |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=head1 REQUEST METHODS |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=over 4 |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=item my $output = $pg->$name_of_stored_procedure($hashref_of_input_arguments) |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=item my $output = $pg->$name_of_stored_procedure($hashref_of_input_arguments, $namespace) |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=back |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
=head1 SEE ALSO |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
This module is built on top of L, and |
98
|
|
|
|
|
|
|
you need to use that module (and the appropriate DBD::Pg driver) |
99
|
|
|
|
|
|
|
to establish a database connection. |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
There is another module providing about the same functionality, |
102
|
|
|
|
|
|
|
but without support for named arguments for PostgreSQL. |
103
|
|
|
|
|
|
|
Have a look at this one if you need to access functions |
104
|
|
|
|
|
|
|
without named arguments, or if you are using Oracle: |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
L |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=head1 LIMITATIONS |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
Requires PostgreSQL 9.0 or later. |
111
|
|
|
|
|
|
|
Only supports stored procedures / functions with |
112
|
|
|
|
|
|
|
named input arguments. |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=head1 AUTHOR |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
Joel Jacobson L |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=head1 COPYRIGHT |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
Copyright (c) Joel Jacobson, Sweden, 2012. All rights reserved. |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
This software is released under the MIT license cited below. |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=head2 The "MIT" License |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
127
|
|
|
|
|
|
|
of this software and associated documentation files (the "Software"), to deal |
128
|
|
|
|
|
|
|
in the Software without restriction, including without limitation the rights |
129
|
|
|
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
130
|
|
|
|
|
|
|
copies of the Software, and to permit persons to whom the Software is |
131
|
|
|
|
|
|
|
furnished to do so, subject to the following conditions: |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in |
134
|
|
|
|
|
|
|
all copies or substantial portions of the Software. |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
137
|
|
|
|
|
|
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
138
|
|
|
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
139
|
|
|
|
|
|
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
140
|
|
|
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
141
|
|
|
|
|
|
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
142
|
|
|
|
|
|
|
DEALINGS IN THE SOFTWARE. |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=cut |
145
|
|
|
|
|
|
|
|
146
|
2
|
|
|
2
|
|
12
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
66
|
|
147
|
2
|
|
|
2
|
|
28
|
use warnings; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
56
|
|
148
|
|
|
|
|
|
|
|
149
|
2
|
|
|
2
|
|
11
|
use Carp; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
184
|
|
150
|
2
|
|
|
2
|
|
18164
|
use DBI; |
|
2
|
|
|
|
|
59659
|
|
|
2
|
|
|
|
|
3029
|
|
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
our $AUTOLOAD; |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
sub new |
155
|
|
|
|
|
|
|
{ |
156
|
0
|
|
|
0
|
1
|
|
my $class = shift; |
157
|
0
|
|
|
|
|
|
my $self = |
158
|
|
|
|
|
|
|
{ |
159
|
|
|
|
|
|
|
dbh => shift, |
160
|
|
|
|
|
|
|
RaiseError => 1, |
161
|
|
|
|
|
|
|
EnableFunctionLookupCache => 0, |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
prosetret_cache => {} |
164
|
|
|
|
|
|
|
}; |
165
|
|
|
|
|
|
|
|
166
|
0
|
|
|
|
|
|
my $params = shift; |
167
|
0
|
0
|
|
|
|
|
if (defined $params) |
168
|
|
|
|
|
|
|
{ |
169
|
0
|
0
|
|
|
|
|
$self->{RaiseError} = delete $params->{RaiseError} if exists $params->{RaiseError}; |
170
|
0
|
0
|
|
|
|
|
$self->{EnableFunctionLookupCache} = delete $params->{EnableFunctionLookupCache} if exists $params->{EnableFunctionLookupCache}; |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
# If there were any unrecognized parameters left, report one of them |
173
|
0
|
0
|
|
|
|
|
if (scalar keys %{$params} > 0) |
|
0
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
{ |
175
|
0
|
|
|
|
|
|
my $param = shift @{keys %{$params}}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
176
|
0
|
|
|
|
|
|
croak "unrecognized parameter $param"; |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
|
180
|
0
|
|
|
|
|
|
bless $self, $class; |
181
|
0
|
|
|
|
|
|
return $self; |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
sub set_dbh |
185
|
|
|
|
|
|
|
{ |
186
|
0
|
|
|
0
|
0
|
|
my ($self, $dbh) = @_; |
187
|
0
|
|
|
|
|
|
$self->{dbh} = $dbh; |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
sub AUTOLOAD |
191
|
|
|
|
|
|
|
{ |
192
|
0
|
|
|
0
|
|
|
my $self = shift; |
193
|
0
|
|
|
|
|
|
my $args = shift; |
194
|
0
|
|
|
|
|
|
my $namespace = shift; |
195
|
0
|
|
|
|
|
|
my $name = $AUTOLOAD; |
196
|
0
|
0
|
|
|
|
|
return if ($name =~ /DESTROY$/); |
197
|
0
|
|
|
|
|
|
$name =~ s!^.*::([^:]+)$!$1!; |
198
|
0
|
|
|
|
|
|
return $self->_call($name, $args, $namespace); |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
# Calculates a cache key for a function, given its signature. |
202
|
|
|
|
|
|
|
# |
203
|
|
|
|
|
|
|
# The caller should sort $argnames before passing them to us. |
204
|
|
|
|
|
|
|
sub _calculate_proretset_cache_key |
205
|
|
|
|
|
|
|
{ |
206
|
0
|
|
|
0
|
|
|
my ($self, $name, $argnames, $namespace) = @_; |
207
|
|
|
|
|
|
|
|
208
|
0
|
|
|
|
|
|
return (defined $namespace ? $namespace : "").".". |
209
|
0
|
0
|
|
|
|
|
$name."(".join(",", @{$argnames}).")"; |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# Because there is no way for us to do "proper" cache invalidation, we have to |
214
|
|
|
|
|
|
|
# rely on detecting the SQLSTATEs of the cases where the cache entry might be |
215
|
|
|
|
|
|
|
# stale. Currently, these cases are: |
216
|
|
|
|
|
|
|
# |
217
|
|
|
|
|
|
|
# 1) A cached function gets dropped. (SQLSTATE undefined_function) |
218
|
|
|
|
|
|
|
# 2) A new function with the same signature is introduced (SQLSTATE |
219
|
|
|
|
|
|
|
# ambiguous_function) |
220
|
|
|
|
|
|
|
sub _invalidate_proretset_cache_entry |
221
|
|
|
|
|
|
|
{ |
222
|
0
|
|
|
0
|
|
|
my ($self, $name, $argnames, $namespace) = @_; |
223
|
|
|
|
|
|
|
|
224
|
0
|
|
|
|
|
|
my $cachekey = $self->_calculate_proretset_cache_key($name, $argnames, $namespace); |
225
|
0
|
|
|
|
|
|
delete $self->{proretset_cache}->{$cachekey}; |
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
sub _proretset |
229
|
|
|
|
|
|
|
{ |
230
|
|
|
|
|
|
|
# Returns the value of pg_catalog.pg_proc.proretset for the function. |
231
|
|
|
|
|
|
|
# "proretset" is short for procedure returns set. |
232
|
|
|
|
|
|
|
# If 1, the function returns multiple rows, or zero rows. |
233
|
|
|
|
|
|
|
# If 0, the function always returns exactly one row. |
234
|
0
|
|
|
0
|
|
|
my ($self, $name, $argnames, $namespace) = @_; |
235
|
|
|
|
|
|
|
|
236
|
0
|
|
|
|
|
|
my $cachekey = undef; |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# do a cache lookup if the caller asked for that |
239
|
0
|
0
|
|
|
|
|
if ($self->{EnableFunctionLookupCache}) |
240
|
|
|
|
|
|
|
{ |
241
|
0
|
|
|
|
|
|
$cachekey = $self->_calculate_proretset_cache_key($name, $argnames, $namespace); |
242
|
0
|
0
|
|
|
|
|
if (exists ($self->{proretset_cache}->{$cachekey})) |
243
|
|
|
|
|
|
|
{ |
244
|
0
|
|
|
|
|
|
my $cached = $self->{proretset_cache}->{$cachekey}; |
245
|
0
|
|
|
|
|
|
return $cached; |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
|
249
|
0
|
|
|
|
|
|
my $get_proretset; |
250
|
0
|
0
|
|
|
|
|
if (@$argnames == 0) |
251
|
|
|
|
|
|
|
{ |
252
|
|
|
|
|
|
|
# no arguments |
253
|
0
|
|
|
|
|
|
$get_proretset = $self->{dbh}->prepare_cached(" |
254
|
|
|
|
|
|
|
SELECT pg_catalog.pg_proc.proretset |
255
|
|
|
|
|
|
|
FROM pg_catalog.pg_proc |
256
|
|
|
|
|
|
|
INNER JOIN pg_catalog.pg_namespace ON (pg_catalog.pg_namespace.oid = pg_catalog.pg_proc.pronamespace) |
257
|
|
|
|
|
|
|
WHERE (?::text IS NULL OR pg_catalog.pg_namespace.nspname = ?::text) |
258
|
|
|
|
|
|
|
AND pg_catalog.pg_proc.proname = ?::text |
259
|
|
|
|
|
|
|
AND pg_catalog.pg_proc.pronargs = 0 |
260
|
|
|
|
|
|
|
"); |
261
|
0
|
|
|
|
|
|
$get_proretset->execute($namespace,$namespace,$name); |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
else |
264
|
|
|
|
|
|
|
{ |
265
|
0
|
0
|
|
|
|
|
$get_proretset = $self->{dbh}->prepare_cached(" |
266
|
|
|
|
|
|
|
WITH |
267
|
|
|
|
|
|
|
-- Unnest the proargname and proargmode |
268
|
|
|
|
|
|
|
-- arrays, so we get one argument per row, |
269
|
|
|
|
|
|
|
-- allowing us to select only the IN |
270
|
|
|
|
|
|
|
-- arguments and build new arrays. |
271
|
|
|
|
|
|
|
NamedInputArgumentFunctions AS ( |
272
|
|
|
|
|
|
|
-- For functions with INOUT/OUT arguments, |
273
|
|
|
|
|
|
|
-- proargmodes is an array where each |
274
|
|
|
|
|
|
|
-- position matches proargname and |
275
|
|
|
|
|
|
|
-- indicates if its an IN, OUT or INOUT |
276
|
|
|
|
|
|
|
-- argument. |
277
|
|
|
|
|
|
|
SELECT |
278
|
|
|
|
|
|
|
pg_catalog.pg_proc.oid, |
279
|
|
|
|
|
|
|
pg_catalog.pg_proc.proname, |
280
|
|
|
|
|
|
|
pg_catalog.pg_proc.proretset, |
281
|
|
|
|
|
|
|
pg_catalog.pg_proc.pronargdefaults, |
282
|
|
|
|
|
|
|
unnest(pg_catalog.pg_proc.proargnames) AS proargname, |
283
|
|
|
|
|
|
|
unnest(pg_catalog.pg_proc.proargmodes) AS proargmode |
284
|
|
|
|
|
|
|
FROM pg_catalog.pg_proc |
285
|
|
|
|
|
|
|
INNER JOIN pg_catalog.pg_namespace ON (pg_catalog.pg_namespace.oid = pg_catalog.pg_proc.pronamespace) |
286
|
|
|
|
|
|
|
WHERE (?::name IS NULL OR pg_catalog.pg_namespace.nspname = ?::name) |
287
|
|
|
|
|
|
|
AND pg_catalog.pg_proc.proname = ?::name |
288
|
|
|
|
|
|
|
AND pg_catalog.pg_proc.proargnames IS NOT NULL |
289
|
|
|
|
|
|
|
AND pg_catalog.pg_proc.proargmodes IS NOT NULL |
290
|
|
|
|
|
|
|
), |
291
|
|
|
|
|
|
|
OnlyINandINOUTArguments AS ( |
292
|
|
|
|
|
|
|
-- Select only the IN and INOUT |
293
|
|
|
|
|
|
|
-- arguments and build new arrays |
294
|
|
|
|
|
|
|
SELECT |
295
|
|
|
|
|
|
|
oid, |
296
|
|
|
|
|
|
|
proname, |
297
|
|
|
|
|
|
|
proretset, |
298
|
|
|
|
|
|
|
pronargdefaults, |
299
|
|
|
|
|
|
|
array_agg(proargname) AS proargnames |
300
|
|
|
|
|
|
|
FROM NamedInputArgumentFunctions |
301
|
|
|
|
|
|
|
WHERE proargmode IN ('i','b') |
302
|
|
|
|
|
|
|
GROUP BY |
303
|
|
|
|
|
|
|
oid, |
304
|
|
|
|
|
|
|
proname, |
305
|
|
|
|
|
|
|
proretset, |
306
|
|
|
|
|
|
|
pronargdefaults |
307
|
|
|
|
|
|
|
UNION ALL |
308
|
|
|
|
|
|
|
-- For functions with only IN arguments, |
309
|
|
|
|
|
|
|
-- proargmodes IS NULL |
310
|
|
|
|
|
|
|
SELECT |
311
|
|
|
|
|
|
|
pg_catalog.pg_proc.oid, |
312
|
|
|
|
|
|
|
pg_catalog.pg_proc.proname, |
313
|
|
|
|
|
|
|
pg_catalog.pg_proc.proretset, |
314
|
|
|
|
|
|
|
pg_catalog.pg_proc.pronargdefaults, |
315
|
|
|
|
|
|
|
pg_catalog.pg_proc.proargnames |
316
|
|
|
|
|
|
|
FROM pg_catalog.pg_proc |
317
|
|
|
|
|
|
|
INNER JOIN pg_catalog.pg_namespace ON (pg_catalog.pg_namespace.oid = pg_catalog.pg_proc.pronamespace) |
318
|
|
|
|
|
|
|
WHERE (?::name IS NULL OR pg_catalog.pg_namespace.nspname = ?::name) |
319
|
|
|
|
|
|
|
AND pg_catalog.pg_proc.proname = ?::name |
320
|
|
|
|
|
|
|
AND pg_catalog.pg_proc.proargnames IS NOT NULL |
321
|
|
|
|
|
|
|
AND pg_catalog.pg_proc.proargmodes IS NULL |
322
|
|
|
|
|
|
|
) |
323
|
|
|
|
|
|
|
-- Find any function matching the name |
324
|
|
|
|
|
|
|
-- and having identical argument names |
325
|
|
|
|
|
|
|
SELECT * FROM OnlyINandINOUTArguments |
326
|
|
|
|
|
|
|
WHERE ?::text[] <@ proargnames AND (( |
327
|
|
|
|
|
|
|
-- No default arguments |
328
|
|
|
|
|
|
|
pronargdefaults = 0 AND ?::text[] @> proargnames |
329
|
|
|
|
|
|
|
) OR ( |
330
|
|
|
|
|
|
|
-- Default arguments, only require first input arguments to match |
331
|
|
|
|
|
|
|
pronargdefaults > 0 AND ?::text[] @> proargnames[ |
332
|
|
|
|
|
|
|
1 |
333
|
|
|
|
|
|
|
: |
334
|
|
|
|
|
|
|
array_upper(proargnames,1) - pronargdefaults |
335
|
|
|
|
|
|
|
] |
336
|
|
|
|
|
|
|
)) |
337
|
|
|
|
|
|
|
-- The order of arguments doesn't matter, |
338
|
|
|
|
|
|
|
-- so compare the arrays by checking |
339
|
|
|
|
|
|
|
-- if A contains B and B contains A |
340
|
|
|
|
|
|
|
") or croak "failed to prepare get_proretset query"; |
341
|
0
|
0
|
|
|
|
|
$get_proretset->execute($namespace, $namespace, $name, $namespace, $namespace, $name, $argnames, $argnames, $argnames) |
342
|
|
|
|
|
|
|
or croak("failed to execute get_proretset query: " . $get_proretset->errstr); |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
|
346
|
0
|
|
|
|
|
|
my $proretset; |
347
|
0
|
|
|
|
|
|
my $i = 0; |
348
|
0
|
|
|
|
|
|
while (my $h = $get_proretset->fetchrow_hashref()) { |
349
|
0
|
|
|
|
|
|
$i++; |
350
|
0
|
|
|
|
|
|
$proretset = $h; |
351
|
|
|
|
|
|
|
} |
352
|
0
|
0
|
|
|
|
|
if ($i == 0) |
|
|
0
|
|
|
|
|
|
353
|
|
|
|
|
|
|
{ |
354
|
0
|
|
|
|
|
|
croak "no function matches the input arguments, function: $name"; |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
elsif ($i == 1) |
357
|
|
|
|
|
|
|
{ |
358
|
|
|
|
|
|
|
# The function exists and can be called. Add it to the cache if the |
359
|
|
|
|
|
|
|
# caller has asked for caching. |
360
|
0
|
0
|
|
|
|
|
$self->{proretset_cache}->{$cachekey} = $proretset->{proretset} if ($self->{EnableFunctionLookupCache}); |
361
|
|
|
|
|
|
|
|
362
|
0
|
|
|
|
|
|
return $proretset->{proretset}; |
363
|
|
|
|
|
|
|
} |
364
|
|
|
|
|
|
|
else |
365
|
|
|
|
|
|
|
{ |
366
|
0
|
|
|
|
|
|
croak "multiple functions matches the same input arguments, function: $name"; |
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
sub _call |
371
|
|
|
|
|
|
|
{ |
372
|
0
|
|
|
0
|
|
|
my ($self,$name,$args,$namespace) = @_; |
373
|
|
|
|
|
|
|
|
374
|
0
|
|
|
|
|
|
my $validate_name_regex = qr/^[a-zA-Z_][a-zA-Z0-9_]*$/; |
375
|
|
|
|
|
|
|
|
376
|
0
|
0
|
|
|
|
|
unless (defined $args) |
377
|
|
|
|
|
|
|
{ |
378
|
0
|
|
|
|
|
|
$args = {}; |
379
|
|
|
|
|
|
|
} |
380
|
|
|
|
|
|
|
|
381
|
0
|
0
|
0
|
|
|
|
croak "dbh and name must be defined" unless defined $self->{dbh} && defined $name; |
382
|
0
|
0
|
0
|
|
|
|
croak "invalid format of namespace" unless !defined $namespace || $namespace =~ $validate_name_regex; |
383
|
0
|
0
|
|
|
|
|
croak "invalid format of name" unless $name =~ $validate_name_regex; |
384
|
0
|
0
|
|
|
|
|
croak "args must be a hashref" unless ref $args eq 'HASH'; |
385
|
|
|
|
|
|
|
|
386
|
0
|
|
|
|
|
|
my @arg_names = sort keys %{$args}; |
|
0
|
|
|
|
|
|
|
387
|
0
|
|
|
|
|
|
my @arg_values = @{$args}{@arg_names}; |
|
0
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
|
389
|
0
|
|
|
|
|
|
foreach my $arg_name (@arg_names) |
390
|
|
|
|
|
|
|
{ |
391
|
0
|
0
|
|
|
|
|
if ($arg_name !~ $validate_name_regex) |
392
|
|
|
|
|
|
|
{ |
393
|
0
|
|
|
|
|
|
croak "invalid format of argument name: $arg_name"; |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
|
397
|
0
|
|
|
|
|
|
my $proretset = $self->_proretset($name, \@arg_names, $namespace); |
398
|
|
|
|
|
|
|
|
399
|
0
|
|
|
|
|
|
my $placeholders = join ",", map { "$_ := ?" } @arg_names; |
|
0
|
|
|
|
|
|
|
400
|
0
|
0
|
|
|
|
|
my $sql = 'SELECT * FROM ' . (defined $namespace ? "$namespace.$name" : $name) . '(' . $placeholders . ');'; |
401
|
|
|
|
|
|
|
|
402
|
0
|
|
|
|
|
|
local $self->{dbh}->{RaiseError} = 0; |
403
|
0
|
|
|
|
|
|
my $query = $self->{dbh}->prepare($sql); |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
# reset the error information |
407
|
0
|
|
|
|
|
|
$self->{SQLState} = '00000'; |
408
|
0
|
|
|
|
|
|
$self->{SQLErrorMessage} = undef; |
409
|
|
|
|
|
|
|
|
410
|
0
|
|
|
|
|
|
my $failed = !defined $query->execute(@arg_values); |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
# If something went wrong, we might have to invalidate the cache entry for |
413
|
|
|
|
|
|
|
# this function. |
414
|
0
|
0
|
0
|
|
|
|
if ($failed && $self->{EnableFunctionLookupCache}) |
415
|
|
|
|
|
|
|
{ |
416
|
|
|
|
|
|
|
# List of SQLSTATEs that warrant cache invalidation. See |
417
|
|
|
|
|
|
|
# _invalidate_proretset_cache_entry() for more information and |
418
|
|
|
|
|
|
|
# http://www.postgresql.org/docs/current/static/errcodes-appendix.html |
419
|
|
|
|
|
|
|
# for a list of error codes. |
420
|
|
|
|
|
|
|
# |
421
|
|
|
|
|
|
|
# Unfortunately there is no way to reliably tell whether our call or |
422
|
|
|
|
|
|
|
# something in the function we called caused the error. However, for |
423
|
|
|
|
|
|
|
# our use case it doesn't really matter since in the worst case that |
424
|
|
|
|
|
|
|
# would only mean unnecessary invalidations for functions that are |
425
|
|
|
|
|
|
|
# already slow to run because they're broken. |
426
|
0
|
|
|
|
|
|
my @sqlstates = ( |
427
|
|
|
|
|
|
|
"42883", # undefined function |
428
|
|
|
|
|
|
|
"42725" # ambiguous function |
429
|
|
|
|
|
|
|
); |
430
|
|
|
|
|
|
|
|
431
|
0
|
|
|
|
|
|
$self->_invalidate_proretset_cache_entry($name, \@arg_names, $namespace) |
432
|
0
|
0
|
|
|
|
|
if ((scalar grep { $_ eq $query->state } @sqlstates) > 0); |
433
|
|
|
|
|
|
|
} |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
|
436
|
0
|
0
|
0
|
|
|
|
if ($failed && $self->{RaiseError}) |
|
|
0
|
|
|
|
|
|
437
|
|
|
|
|
|
|
{ |
438
|
0
|
|
|
|
|
|
croak "Call to $name failed: $DBI::errstr"; |
439
|
|
|
|
|
|
|
} |
440
|
|
|
|
|
|
|
elsif ($failed) |
441
|
|
|
|
|
|
|
{ |
442
|
|
|
|
|
|
|
# if we failed but RaiseError wasn't set, let the caller deal with the problem |
443
|
0
|
|
|
|
|
|
$self->{SQLState} = $query->state; |
444
|
0
|
|
|
|
|
|
$self->{SQLErrorMessage} = $query->errstr; |
445
|
0
|
|
|
|
|
|
return undef; |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
|
448
|
0
|
|
|
|
|
|
my $output; |
449
|
|
|
|
|
|
|
my $num_cols; |
450
|
0
|
|
|
|
|
|
my @output_columns; |
451
|
0
|
|
|
|
|
|
for (my $row_number=0; my $h = $query->fetchrow_hashref(); $row_number++) |
452
|
|
|
|
|
|
|
{ |
453
|
0
|
0
|
|
|
|
|
if ($row_number == 0) |
454
|
|
|
|
|
|
|
{ |
455
|
0
|
|
|
|
|
|
@output_columns = sort keys %{$h}; |
|
0
|
|
|
|
|
|
|
456
|
0
|
|
|
|
|
|
$num_cols = scalar @output_columns; |
457
|
0
|
0
|
|
|
|
|
croak "no columns in return" unless $num_cols >= 1; |
458
|
|
|
|
|
|
|
} |
459
|
0
|
0
|
|
|
|
|
if ($proretset == 0) |
|
|
0
|
|
|
|
|
|
460
|
|
|
|
|
|
|
{ |
461
|
|
|
|
|
|
|
# single-row |
462
|
0
|
0
|
|
|
|
|
croak "function returned multiple rows" if defined $output; |
463
|
0
|
0
|
|
|
|
|
if ($num_cols == 1) |
|
|
0
|
|
|
|
|
|
464
|
|
|
|
|
|
|
{ |
465
|
|
|
|
|
|
|
# single-column |
466
|
0
|
|
|
|
|
|
$output = $h->{$output_columns[0]}; |
467
|
|
|
|
|
|
|
} |
468
|
|
|
|
|
|
|
elsif ($num_cols > 1) |
469
|
|
|
|
|
|
|
{ |
470
|
|
|
|
|
|
|
# multi-column |
471
|
0
|
|
|
|
|
|
$output = $h; |
472
|
|
|
|
|
|
|
} |
473
|
|
|
|
|
|
|
} |
474
|
|
|
|
|
|
|
elsif ($proretset == 1) |
475
|
|
|
|
|
|
|
{ |
476
|
|
|
|
|
|
|
# multi-row |
477
|
0
|
0
|
|
|
|
|
if ($num_cols == 1) |
|
|
0
|
|
|
|
|
|
478
|
|
|
|
|
|
|
{ |
479
|
|
|
|
|
|
|
# single-column |
480
|
0
|
|
|
|
|
|
push @$output, $h->{$output_columns[0]}; |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
elsif ($num_cols > 1) |
483
|
|
|
|
|
|
|
{ |
484
|
|
|
|
|
|
|
# multi-column |
485
|
0
|
|
|
|
|
|
push @$output, $h; |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
} |
488
|
|
|
|
|
|
|
} |
489
|
0
|
|
|
|
|
|
return $output; |
490
|
|
|
|
|
|
|
} |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
1; |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
=begin Pod::Coverage |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
new |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
=end Pod::Coverage |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
# vim: ts=8:sw=4:sts=4:et |