| 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 |