line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Protocol::Database::PostgreSQL; |
2
|
|
|
|
|
|
|
# ABSTRACT: PostgreSQL wire protocol implementation |
3
|
1
|
|
|
1
|
|
58691
|
use strict; |
|
1
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
25
|
|
4
|
1
|
|
|
1
|
|
4
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
50
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
our $VERSION = '2.000'; |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 NAME |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
Protocol::Database::PostgreSQL - support for the PostgreSQL wire protocol |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 SYNOPSIS |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
use strict; |
15
|
|
|
|
|
|
|
use warnings; |
16
|
|
|
|
|
|
|
use mro; |
17
|
|
|
|
|
|
|
package Example::PostgreSQL::Client; |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
sub new { bless { @_[1..$#_] }, $_[0] } |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
sub protocol { |
22
|
|
|
|
|
|
|
my ($self) = @_; |
23
|
|
|
|
|
|
|
$self->{protocol} //= Protocol::Database::PostgresQL->new( |
24
|
|
|
|
|
|
|
outgoing => $self->outgoing, |
25
|
|
|
|
|
|
|
) |
26
|
|
|
|
|
|
|
} |
27
|
|
|
|
|
|
|
# Any received packets will arrive here |
28
|
|
|
|
|
|
|
sub incoming { shift->{incoming} //= Ryu::Source->new } |
29
|
|
|
|
|
|
|
# Anything we want to send goes here |
30
|
|
|
|
|
|
|
sub outgoing { shift->{outgoing} //= Ryu::Source->new } |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
... |
33
|
|
|
|
|
|
|
# We raise events on our incoming source in this example - |
34
|
|
|
|
|
|
|
# if you prefer to handle each message as it's extracted you |
35
|
|
|
|
|
|
|
# could add that directly in the loop |
36
|
|
|
|
|
|
|
$self->incoming |
37
|
|
|
|
|
|
|
->switch_str( |
38
|
|
|
|
|
|
|
sub { $_->type }, |
39
|
|
|
|
|
|
|
authentication_request => sub { ... }, |
40
|
|
|
|
|
|
|
sub { warn 'unknown message - ' . $_->type } |
41
|
|
|
|
|
|
|
); |
42
|
|
|
|
|
|
|
# When there's something to write, we'll get an event here |
43
|
|
|
|
|
|
|
$self->outgoing |
44
|
|
|
|
|
|
|
->each(sub { $sock->write($_) }); |
45
|
|
|
|
|
|
|
while(1) { |
46
|
|
|
|
|
|
|
$sock->read(my $buf, 1_000_000); |
47
|
|
|
|
|
|
|
while(my $msg = $self->protocol->extract_message(\$buf)) { |
48
|
|
|
|
|
|
|
$self->incoming->emit($msg); |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=head1 DESCRIPTION |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
Provides protocol-level support for PostgreSQL 7.4+, as defined in L. |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
=head2 How do I use this? |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
The short answer: don't. |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
Use L instead, unless you're writing a driver for talking to PostgreSQL (or compatible) systems. |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
This distribution provides the abstract protocol handling, meaning that it understands the packets that make up the PostgreSQL |
63
|
|
|
|
|
|
|
communication protocol, but it does B attempt to send or receive those packets itself. You need to provide the transport layer |
64
|
|
|
|
|
|
|
(typically this would involve TCP or Unix sockets). |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
=head2 Connection states |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
Possible states: |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
=over 4 |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=item * B - we have a valid instantiated PostgreSQL object, but no connection yet. |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=item * B - transport layer has made a connection for us |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
=item * B - the server has challenged us to identify |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=item * B - we have successfully identified with the server |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=item * B - session is active and ready for commands |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=item * B - a statement has been passed to the server for parsing |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=item * B - the indicated statement is being described, called after the transport layer has sent the Describe request |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=item * B - parameters for a given query have been transmitted |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=item * B - we have sent a request to execute |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
=item * B - terminate request sent |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=item * B - the server is expecting data for a COPY command |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
=back |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=begin HTML |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=end HTML |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=head2 Message types |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
The L for incoming messages can currently include the following: |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=over 4 |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=item * C - Called each time there is a new message to be sent to the other side of the connection. |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=item * C - Called when authentication is complete |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=item * C - we have received data from an ongoing COPY request |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=item * C - the active COPY request has completed |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=back |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
For the client, the following additional callbacks are available: |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=over 4 |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=item * C - the server is ready for the next request |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=item * C - a Bind request has completed |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=item * C - the Close request has completed |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=item * C - the requested command has finished, this will typically be followed by an on_request_ready event |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
=item * C - indicates that the server is ready to receive COPY data |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=item * C - indicates that the server is ready to send COPY data |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=item * C - indicates that the server is ready to exchange COPY data (for replication) |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=item * C - data from the current query |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=item * C - special-case response when sent an empty query, can be used for 'ping'. Typically followed by on_request_ready |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
=item * C - server has raised an error |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=item * C - results from a function call |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=item * C - indicate that a query returned no data, typically followed by on_request_ready |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=item * C - server has sent us a notice |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=item * C - server has sent us a NOTIFY |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=item * C - parameters are being described |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=item * C - parameter status... |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=item * C - parsing is done |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=item * C - the portal has been suspended, probably hit the row limit |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=item * C - we're ready for queries |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
=item * C - descriptive information about the rows we're likely to be seeing shortly |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=back |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
And there are also these potential events back from the server: |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
=over 4 |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=item * C - the frontend is indicating that the copy has failed |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=item * C - request for something to be described |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=item * C - request execution of a given portal |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
=item * C - request flush |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=item * C - request execution of a given function |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=item * C - request to parse something |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=item * C - password information |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=item * C - simple query request |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=item * C - we have an SSL request |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
=item * C - we have an SSL request |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=item * C - sync request |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=item * C - termination request |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=back |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=cut |
195
|
|
|
|
|
|
|
|
196
|
1
|
|
|
1
|
|
368
|
no indirect; |
|
1
|
|
|
|
|
850
|
|
|
1
|
|
|
|
|
3
|
|
197
|
|
|
|
|
|
|
|
198
|
1
|
|
|
1
|
|
47
|
use Digest::MD5 (); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
14
|
|
199
|
1
|
|
|
1
|
|
423
|
use Time::HiRes (); |
|
1
|
|
|
|
|
1096
|
|
|
1
|
|
|
|
|
28
|
|
200
|
1
|
|
|
1
|
|
411
|
use POSIX qw(strftime); |
|
1
|
|
|
|
|
5266
|
|
|
1
|
|
|
|
|
4
|
|
201
|
|
|
|
|
|
|
|
202
|
1
|
|
|
1
|
|
1534
|
use Log::Any qw($log); |
|
1
|
|
|
|
|
8687
|
|
|
1
|
|
|
|
|
4
|
|
203
|
1
|
|
|
1
|
|
2158
|
use Ryu; |
|
1
|
|
|
|
|
53526
|
|
|
1
|
|
|
|
|
42
|
|
204
|
1
|
|
|
1
|
|
8
|
use Future; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
19
|
|
205
|
1
|
|
|
1
|
|
350
|
use Sub::Identify; |
|
1
|
|
|
|
|
884
|
|
|
1
|
|
|
|
|
47
|
|
206
|
1
|
|
|
1
|
|
325
|
use Unicode::UTF8; |
|
1
|
|
|
|
|
355
|
|
|
1
|
|
|
|
|
41
|
|
207
|
1
|
|
|
1
|
|
362
|
use MIME::Base64; |
|
1
|
|
|
|
|
502
|
|
|
1
|
|
|
|
|
54
|
|
208
|
|
|
|
|
|
|
|
209
|
1
|
|
|
1
|
|
404
|
use Protocol::Database::PostgreSQL::Backend::AuthenticationRequest; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
32
|
|
210
|
1
|
|
|
1
|
|
358
|
use Protocol::Database::PostgreSQL::Backend::BackendKeyData; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
31
|
|
211
|
1
|
|
|
1
|
|
338
|
use Protocol::Database::PostgreSQL::Backend::BindComplete; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
32
|
|
212
|
1
|
|
|
1
|
|
344
|
use Protocol::Database::PostgreSQL::Backend::CloseComplete; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
32
|
|
213
|
1
|
|
|
1
|
|
340
|
use Protocol::Database::PostgreSQL::Backend::CommandComplete; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
30
|
|
214
|
1
|
|
|
1
|
|
338
|
use Protocol::Database::PostgreSQL::Backend::CopyData; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
32
|
|
215
|
1
|
|
|
1
|
|
365
|
use Protocol::Database::PostgreSQL::Backend::CopyDone; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
31
|
|
216
|
1
|
|
|
1
|
|
364
|
use Protocol::Database::PostgreSQL::Backend::CopyInResponse; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
31
|
|
217
|
1
|
|
|
1
|
|
345
|
use Protocol::Database::PostgreSQL::Backend::CopyOutResponse; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
31
|
|
218
|
1
|
|
|
1
|
|
342
|
use Protocol::Database::PostgreSQL::Backend::CopyBothResponse; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
30
|
|
219
|
1
|
|
|
1
|
|
341
|
use Protocol::Database::PostgreSQL::Backend::DataRow; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
31
|
|
220
|
1
|
|
|
1
|
|
338
|
use Protocol::Database::PostgreSQL::Backend::EmptyQueryResponse; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
31
|
|
221
|
1
|
|
|
1
|
|
349
|
use Protocol::Database::PostgreSQL::Backend::ErrorResponse; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
34
|
|
222
|
1
|
|
|
1
|
|
341
|
use Protocol::Database::PostgreSQL::Backend::FunctionCallResponse; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
30
|
|
223
|
1
|
|
|
1
|
|
336
|
use Protocol::Database::PostgreSQL::Backend::NoData; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
30
|
|
224
|
1
|
|
|
1
|
|
339
|
use Protocol::Database::PostgreSQL::Backend::NoticeResponse; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
47
|
|
225
|
1
|
|
|
1
|
|
339
|
use Protocol::Database::PostgreSQL::Backend::NotificationResponse; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
30
|
|
226
|
1
|
|
|
1
|
|
339
|
use Protocol::Database::PostgreSQL::Backend::ParameterDescription; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
32
|
|
227
|
1
|
|
|
1
|
|
346
|
use Protocol::Database::PostgreSQL::Backend::ParameterStatus; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
30
|
|
228
|
1
|
|
|
1
|
|
335
|
use Protocol::Database::PostgreSQL::Backend::ParseComplete; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
29
|
|
229
|
1
|
|
|
1
|
|
336
|
use Protocol::Database::PostgreSQL::Backend::PortalSuspended; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
30
|
|
230
|
1
|
|
|
1
|
|
341
|
use Protocol::Database::PostgreSQL::Backend::ReadyForQuery; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
31
|
|
231
|
1
|
|
|
1
|
|
363
|
use Protocol::Database::PostgreSQL::Backend::RowDescription; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
32
|
|
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
# Currently v3.0, which is used in PostgreSQL 7.4+ |
234
|
1
|
|
|
1
|
|
6
|
use constant PROTOCOL_VERSION => 0x00030000; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
4990
|
|
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
# Types of authentication response |
237
|
|
|
|
|
|
|
our %AUTH_TYPE = ( |
238
|
|
|
|
|
|
|
0 => 'AuthenticationOk', |
239
|
|
|
|
|
|
|
2 => 'AuthenticationKerberosV5', |
240
|
|
|
|
|
|
|
3 => 'AuthenticationCleartextPassword', |
241
|
|
|
|
|
|
|
5 => 'AuthenticationMD5Password', |
242
|
|
|
|
|
|
|
6 => 'AuthenticationSCMCredential', |
243
|
|
|
|
|
|
|
7 => 'AuthenticationGSS', |
244
|
|
|
|
|
|
|
8 => 'AuthenticationGSSContinue', |
245
|
|
|
|
|
|
|
9 => 'AuthenticationSSPI', |
246
|
|
|
|
|
|
|
10 => 'AuthenticationSASL', |
247
|
|
|
|
|
|
|
11 => 'AuthenticationSASLContinue', |
248
|
|
|
|
|
|
|
12 => 'AuthenticationSASLFinal', |
249
|
|
|
|
|
|
|
); |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# The terms "backend" and "frontend" used in the documentation here reflect |
252
|
|
|
|
|
|
|
# the meanings assigned in the official PostgreSQL manual: |
253
|
|
|
|
|
|
|
# * frontend - the client connecting to the database server |
254
|
|
|
|
|
|
|
# * backend - the database server process |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
# Transaction states the backend can be in |
257
|
|
|
|
|
|
|
our %BACKEND_STATE = ( |
258
|
|
|
|
|
|
|
I => 'idle', |
259
|
|
|
|
|
|
|
T => 'transaction', |
260
|
|
|
|
|
|
|
E => 'error' |
261
|
|
|
|
|
|
|
); |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
# used for error and notice responses |
264
|
|
|
|
|
|
|
our %NOTICE_CODE = ( |
265
|
|
|
|
|
|
|
S => 'severity', |
266
|
|
|
|
|
|
|
V => 'severity_unlocalised', |
267
|
|
|
|
|
|
|
C => 'code', |
268
|
|
|
|
|
|
|
M => 'message', |
269
|
|
|
|
|
|
|
D => 'detail', |
270
|
|
|
|
|
|
|
H => 'hint', |
271
|
|
|
|
|
|
|
P => 'position', |
272
|
|
|
|
|
|
|
p => 'internal_position', |
273
|
|
|
|
|
|
|
q => 'internal_query', |
274
|
|
|
|
|
|
|
W => 'where', |
275
|
|
|
|
|
|
|
s => 'schema', |
276
|
|
|
|
|
|
|
t => 'table', |
277
|
|
|
|
|
|
|
c => 'column', |
278
|
|
|
|
|
|
|
d => 'data_type', |
279
|
|
|
|
|
|
|
n => 'constraint', |
280
|
|
|
|
|
|
|
F => 'file', |
281
|
|
|
|
|
|
|
L => 'line', |
282
|
|
|
|
|
|
|
R => 'routine' |
283
|
|
|
|
|
|
|
); |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
# Mapping from name to backend message code (single byte) |
286
|
|
|
|
|
|
|
our %MESSAGE_TYPE_BACKEND = ( |
287
|
|
|
|
|
|
|
AuthenticationRequest => 'R', |
288
|
|
|
|
|
|
|
BackendKeyData => 'K', |
289
|
|
|
|
|
|
|
BindComplete => '2', |
290
|
|
|
|
|
|
|
CloseComplete => '3', |
291
|
|
|
|
|
|
|
CommandComplete => 'C', |
292
|
|
|
|
|
|
|
CopyData => 'd', |
293
|
|
|
|
|
|
|
CopyDone => 'c', |
294
|
|
|
|
|
|
|
CopyInResponse => 'G', |
295
|
|
|
|
|
|
|
CopyOutResponse => 'H', |
296
|
|
|
|
|
|
|
CopyBothResponse => 'W', |
297
|
|
|
|
|
|
|
DataRow => 'D', |
298
|
|
|
|
|
|
|
EmptyQueryResponse => 'I', |
299
|
|
|
|
|
|
|
ErrorResponse => 'E', |
300
|
|
|
|
|
|
|
FunctionCallResponse => 'V', |
301
|
|
|
|
|
|
|
NoData => 'n', |
302
|
|
|
|
|
|
|
NoticeResponse => 'N', |
303
|
|
|
|
|
|
|
NotificationResponse => 'A', |
304
|
|
|
|
|
|
|
ParameterDescription => 't', |
305
|
|
|
|
|
|
|
ParameterStatus => 'S', |
306
|
|
|
|
|
|
|
ParseComplete => '1', |
307
|
|
|
|
|
|
|
PortalSuspended => 's', |
308
|
|
|
|
|
|
|
ReadyForQuery => 'Z', |
309
|
|
|
|
|
|
|
RowDescription => 'T', |
310
|
|
|
|
|
|
|
); |
311
|
|
|
|
|
|
|
our %BACKEND_MESSAGE_CODE = reverse %MESSAGE_TYPE_BACKEND; |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# Mapping from name to frontend message code (single byte) |
314
|
|
|
|
|
|
|
our %MESSAGE_TYPE_FRONTEND = ( |
315
|
|
|
|
|
|
|
Bind => 'B', |
316
|
|
|
|
|
|
|
Close => 'C', |
317
|
|
|
|
|
|
|
CopyData => 'd', |
318
|
|
|
|
|
|
|
CopyDone => 'c', |
319
|
|
|
|
|
|
|
CopyFail => 'f', |
320
|
|
|
|
|
|
|
Describe => 'D', |
321
|
|
|
|
|
|
|
Execute => 'E', |
322
|
|
|
|
|
|
|
Flush => 'H', |
323
|
|
|
|
|
|
|
FunctionCall => 'F', |
324
|
|
|
|
|
|
|
Parse => 'P', |
325
|
|
|
|
|
|
|
PasswordMessage => 'p', |
326
|
|
|
|
|
|
|
SASLInitialResponse => 'p', |
327
|
|
|
|
|
|
|
SASLResponse => 'p', |
328
|
|
|
|
|
|
|
Query => 'Q', |
329
|
|
|
|
|
|
|
# Both of these are handled separately, and for legacy reasons they don't |
330
|
|
|
|
|
|
|
# have a byte prefix for the message code |
331
|
|
|
|
|
|
|
# SSLRequest => '', |
332
|
|
|
|
|
|
|
# StartupMessage => '', |
333
|
|
|
|
|
|
|
Sync => 'S', |
334
|
|
|
|
|
|
|
Terminate => 'X', |
335
|
|
|
|
|
|
|
); |
336
|
|
|
|
|
|
|
our %FRONTEND_MESSAGE_CODE = reverse %MESSAGE_TYPE_FRONTEND; |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
# Defined message handlers for outgoing frontend messages |
339
|
|
|
|
|
|
|
our %FRONTEND_MESSAGE_BUILDER; |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
# from https://www.postgresql.org/docs/current/static/errcodes-appendix.html |
342
|
|
|
|
|
|
|
our %ERROR_CODE = ( |
343
|
|
|
|
|
|
|
'00000' => 'successful_completion', |
344
|
|
|
|
|
|
|
'01000' => 'warning', |
345
|
|
|
|
|
|
|
'01003' => 'null_value_eliminated_in_set_function', |
346
|
|
|
|
|
|
|
'01004' => 'string_data_right_truncation', |
347
|
|
|
|
|
|
|
'01006' => 'privilege_not_revoked', |
348
|
|
|
|
|
|
|
'01007' => 'privilege_not_granted', |
349
|
|
|
|
|
|
|
'01008' => 'implicit_zero_bit_padding', |
350
|
|
|
|
|
|
|
'0100C' => 'dynamic_result_sets_returned', |
351
|
|
|
|
|
|
|
'01P01' => 'deprecated_feature', |
352
|
|
|
|
|
|
|
'02000' => 'no_data', |
353
|
|
|
|
|
|
|
'02001' => 'no_additional_dynamic_result_sets_returned', |
354
|
|
|
|
|
|
|
'03000' => 'sql_statement_not_yet_complete', |
355
|
|
|
|
|
|
|
'08000' => 'connection_exception', |
356
|
|
|
|
|
|
|
'08001' => 'sqlclient_unable_to_establish_sqlconnection', |
357
|
|
|
|
|
|
|
'08003' => 'connection_does_not_exist', |
358
|
|
|
|
|
|
|
'08004' => 'sqlserver_rejected_establishment_of_sqlconnection', |
359
|
|
|
|
|
|
|
'08006' => 'connection_failure', |
360
|
|
|
|
|
|
|
'08007' => 'transaction_resolution_unknown', |
361
|
|
|
|
|
|
|
'08P01' => 'protocol_violation', |
362
|
|
|
|
|
|
|
'09000' => 'triggered_action_exception', |
363
|
|
|
|
|
|
|
'0A000' => 'feature_not_supported', |
364
|
|
|
|
|
|
|
'0B000' => 'invalid_transaction_initiation', |
365
|
|
|
|
|
|
|
'0F000' => 'locator_exception', |
366
|
|
|
|
|
|
|
'0F001' => 'invalid_locator_specification', |
367
|
|
|
|
|
|
|
'0L000' => 'invalid_grantor', |
368
|
|
|
|
|
|
|
'0LP01' => 'invalid_grant_operation', |
369
|
|
|
|
|
|
|
'0P000' => 'invalid_role_specification', |
370
|
|
|
|
|
|
|
'0Z000' => 'diagnostics_exception', |
371
|
|
|
|
|
|
|
'0Z002' => 'stacked_diagnostics_accessed_without_active_handler', |
372
|
|
|
|
|
|
|
'20000' => 'case_not_found', |
373
|
|
|
|
|
|
|
'21000' => 'cardinality_violation', |
374
|
|
|
|
|
|
|
'22000' => 'data_exception', |
375
|
|
|
|
|
|
|
'22001' => 'string_data_right_truncation', |
376
|
|
|
|
|
|
|
'22002' => 'null_value_no_indicator_parameter', |
377
|
|
|
|
|
|
|
'22003' => 'numeric_value_out_of_range', |
378
|
|
|
|
|
|
|
'22004' => 'null_value_not_allowed', |
379
|
|
|
|
|
|
|
'22005' => 'error_in_assignment', |
380
|
|
|
|
|
|
|
'22007' => 'invalid_datetime_format', |
381
|
|
|
|
|
|
|
'22008' => 'datetime_field_overflow', |
382
|
|
|
|
|
|
|
'22009' => 'invalid_time_zone_displacement_value', |
383
|
|
|
|
|
|
|
'2200B' => 'escape_character_conflict', |
384
|
|
|
|
|
|
|
'2200C' => 'invalid_use_of_escape_character', |
385
|
|
|
|
|
|
|
'2200D' => 'invalid_escape_octet', |
386
|
|
|
|
|
|
|
'2200F' => 'zero_length_character_string', |
387
|
|
|
|
|
|
|
'2200G' => 'most_specific_type_mismatch', |
388
|
|
|
|
|
|
|
'2200H' => 'sequence_generator_limit_exceeded', |
389
|
|
|
|
|
|
|
'2200L' => 'not_an_xml_document', |
390
|
|
|
|
|
|
|
'2200M' => 'invalid_xml_document', |
391
|
|
|
|
|
|
|
'2200N' => 'invalid_xml_content', |
392
|
|
|
|
|
|
|
'2200S' => 'invalid_xml_comment', |
393
|
|
|
|
|
|
|
'2200T' => 'invalid_xml_processing_instruction', |
394
|
|
|
|
|
|
|
'22010' => 'invalid_indicator_parameter_value', |
395
|
|
|
|
|
|
|
'22011' => 'substring_error', |
396
|
|
|
|
|
|
|
'22012' => 'division_by_zero', |
397
|
|
|
|
|
|
|
'22013' => 'invalid_preceding_or_following_size', |
398
|
|
|
|
|
|
|
'22014' => 'invalid_argument_for_ntile_function', |
399
|
|
|
|
|
|
|
'22015' => 'interval_field_overflow', |
400
|
|
|
|
|
|
|
'22016' => 'invalid_argument_for_nth_value_function', |
401
|
|
|
|
|
|
|
'22018' => 'invalid_character_value_for_cast', |
402
|
|
|
|
|
|
|
'22019' => 'invalid_escape_character', |
403
|
|
|
|
|
|
|
'2201B' => 'invalid_regular_expression', |
404
|
|
|
|
|
|
|
'2201E' => 'invalid_argument_for_logarithm', |
405
|
|
|
|
|
|
|
'2201F' => 'invalid_argument_for_power_function', |
406
|
|
|
|
|
|
|
'2201G' => 'invalid_argument_for_width_bucket_function', |
407
|
|
|
|
|
|
|
'2201W' => 'invalid_row_count_in_limit_clause', |
408
|
|
|
|
|
|
|
'2201X' => 'invalid_row_count_in_result_offset_clause', |
409
|
|
|
|
|
|
|
'22021' => 'character_not_in_repertoire', |
410
|
|
|
|
|
|
|
'22022' => 'indicator_overflow', |
411
|
|
|
|
|
|
|
'22023' => 'invalid_parameter_value', |
412
|
|
|
|
|
|
|
'22024' => 'unterminated_c_string', |
413
|
|
|
|
|
|
|
'22025' => 'invalid_escape_sequence', |
414
|
|
|
|
|
|
|
'22026' => 'string_data_length_mismatch', |
415
|
|
|
|
|
|
|
'22027' => 'trim_error', |
416
|
|
|
|
|
|
|
'2202E' => 'array_subscript_error', |
417
|
|
|
|
|
|
|
'2202G' => 'invalid_tablesample_repeat', |
418
|
|
|
|
|
|
|
'2202H' => 'invalid_tablesample_argument', |
419
|
|
|
|
|
|
|
'22030' => 'duplicate_json_object_key_value', |
420
|
|
|
|
|
|
|
'22032' => 'invalid_json_text', |
421
|
|
|
|
|
|
|
'22033' => 'invalid_json_subscript', |
422
|
|
|
|
|
|
|
'22034' => 'more_than_one_json_item', |
423
|
|
|
|
|
|
|
'22035' => 'no_json_item', |
424
|
|
|
|
|
|
|
'22036' => 'non_numeric_json_item', |
425
|
|
|
|
|
|
|
'22037' => 'non_unique_keys_in_json_object', |
426
|
|
|
|
|
|
|
'22038' => 'singleton_json_item_required', |
427
|
|
|
|
|
|
|
'22039' => 'json_array_not_found', |
428
|
|
|
|
|
|
|
'2203A' => 'json_member_not_found', |
429
|
|
|
|
|
|
|
'2203B' => 'json_number_not_found', |
430
|
|
|
|
|
|
|
'2203C' => 'object_not_found', |
431
|
|
|
|
|
|
|
'2203D' => 'too_many_json_array_elements', |
432
|
|
|
|
|
|
|
'2203E' => 'too_many_json_object_members', |
433
|
|
|
|
|
|
|
'2203F' => 'json_scalar_required', |
434
|
|
|
|
|
|
|
'22P01' => 'floating_point_exception', |
435
|
|
|
|
|
|
|
'22P02' => 'invalid_text_representation', |
436
|
|
|
|
|
|
|
'22P03' => 'invalid_binary_representation', |
437
|
|
|
|
|
|
|
'22P04' => 'bad_copy_file_format', |
438
|
|
|
|
|
|
|
'22P05' => 'untranslatable_character', |
439
|
|
|
|
|
|
|
'22P06' => 'nonstandard_use_of_escape_character', |
440
|
|
|
|
|
|
|
'23000' => 'integrity_constraint_violation', |
441
|
|
|
|
|
|
|
'23001' => 'restrict_violation', |
442
|
|
|
|
|
|
|
'23502' => 'not_null_violation', |
443
|
|
|
|
|
|
|
'23503' => 'foreign_key_violation', |
444
|
|
|
|
|
|
|
'23505' => 'unique_violation', |
445
|
|
|
|
|
|
|
'23514' => 'check_violation', |
446
|
|
|
|
|
|
|
'23P01' => 'exclusion_violation', |
447
|
|
|
|
|
|
|
'24000' => 'invalid_cursor_state', |
448
|
|
|
|
|
|
|
'25000' => 'invalid_transaction_state', |
449
|
|
|
|
|
|
|
'25001' => 'active_sql_transaction', |
450
|
|
|
|
|
|
|
'25002' => 'branch_transaction_already_active', |
451
|
|
|
|
|
|
|
'25003' => 'inappropriate_access_mode_for_branch_transaction', |
452
|
|
|
|
|
|
|
'25004' => 'inappropriate_isolation_level_for_branch_transaction', |
453
|
|
|
|
|
|
|
'25005' => 'no_active_sql_transaction_for_branch_transaction', |
454
|
|
|
|
|
|
|
'25006' => 'read_only_sql_transaction', |
455
|
|
|
|
|
|
|
'25007' => 'schema_and_data_statement_mixing_not_supported', |
456
|
|
|
|
|
|
|
'25008' => 'held_cursor_requires_same_isolation_level', |
457
|
|
|
|
|
|
|
'25P01' => 'no_active_sql_transaction', |
458
|
|
|
|
|
|
|
'25P02' => 'in_failed_sql_transaction', |
459
|
|
|
|
|
|
|
'25P03' => 'idle_in_transaction_session_timeout', |
460
|
|
|
|
|
|
|
'26000' => 'invalid_sql_statement_name', |
461
|
|
|
|
|
|
|
'27000' => 'triggered_data_change_violation', |
462
|
|
|
|
|
|
|
'28000' => 'invalid_authorization_specification', |
463
|
|
|
|
|
|
|
'28P01' => 'invalid_password', |
464
|
|
|
|
|
|
|
'2B000' => 'dependent_privilege_descriptors_still_exist', |
465
|
|
|
|
|
|
|
'2BP01' => 'dependent_objects_still_exist', |
466
|
|
|
|
|
|
|
'2D000' => 'invalid_transaction_termination', |
467
|
|
|
|
|
|
|
'2F000' => 'sql_routine_exception', |
468
|
|
|
|
|
|
|
'2F002' => 'modifying_sql_data_not_permitted', |
469
|
|
|
|
|
|
|
'2F003' => 'prohibited_sql_statement_attempted', |
470
|
|
|
|
|
|
|
'2F004' => 'reading_sql_data_not_permitted', |
471
|
|
|
|
|
|
|
'2F005' => 'function_executed_no_return_statement', |
472
|
|
|
|
|
|
|
'34000' => 'invalid_cursor_name', |
473
|
|
|
|
|
|
|
'38000' => 'external_routine_exception', |
474
|
|
|
|
|
|
|
'38001' => 'containing_sql_not_permitted', |
475
|
|
|
|
|
|
|
'38002' => 'modifying_sql_data_not_permitted', |
476
|
|
|
|
|
|
|
'38003' => 'prohibited_sql_statement_attempted', |
477
|
|
|
|
|
|
|
'38004' => 'reading_sql_data_not_permitted', |
478
|
|
|
|
|
|
|
'39000' => 'external_routine_invocation_exception', |
479
|
|
|
|
|
|
|
'39001' => 'invalid_sqlstate_returned', |
480
|
|
|
|
|
|
|
'39004' => 'null_value_not_allowed', |
481
|
|
|
|
|
|
|
'39P01' => 'trigger_protocol_violated', |
482
|
|
|
|
|
|
|
'39P02' => 'srf_protocol_violated', |
483
|
|
|
|
|
|
|
'39P03' => 'event_trigger_protocol_violated', |
484
|
|
|
|
|
|
|
'3B000' => 'savepoint_exception', |
485
|
|
|
|
|
|
|
'3B001' => 'invalid_savepoint_specification', |
486
|
|
|
|
|
|
|
'3D000' => 'invalid_catalog_name', |
487
|
|
|
|
|
|
|
'3F000' => 'invalid_schema_name', |
488
|
|
|
|
|
|
|
'40000' => 'transaction_rollback', |
489
|
|
|
|
|
|
|
'40001' => 'serialization_failure', |
490
|
|
|
|
|
|
|
'40002' => 'transaction_integrity_constraint_violation', |
491
|
|
|
|
|
|
|
'40003' => 'statement_completion_unknown', |
492
|
|
|
|
|
|
|
'40P01' => 'deadlock_detected', |
493
|
|
|
|
|
|
|
'42000' => 'syntax_error_or_access_rule_violation', |
494
|
|
|
|
|
|
|
'42501' => 'insufficient_privilege', |
495
|
|
|
|
|
|
|
'42601' => 'syntax_error', |
496
|
|
|
|
|
|
|
'42602' => 'invalid_name', |
497
|
|
|
|
|
|
|
'42611' => 'invalid_column_definition', |
498
|
|
|
|
|
|
|
'42622' => 'name_too_long', |
499
|
|
|
|
|
|
|
'42701' => 'duplicate_column', |
500
|
|
|
|
|
|
|
'42702' => 'ambiguous_column', |
501
|
|
|
|
|
|
|
'42703' => 'undefined_column', |
502
|
|
|
|
|
|
|
'42704' => 'undefined_object', |
503
|
|
|
|
|
|
|
'42710' => 'duplicate_object', |
504
|
|
|
|
|
|
|
'42712' => 'duplicate_alias', |
505
|
|
|
|
|
|
|
'42723' => 'duplicate_function', |
506
|
|
|
|
|
|
|
'42725' => 'ambiguous_function', |
507
|
|
|
|
|
|
|
'42803' => 'grouping_error', |
508
|
|
|
|
|
|
|
'42804' => 'datatype_mismatch', |
509
|
|
|
|
|
|
|
'42809' => 'wrong_object_type', |
510
|
|
|
|
|
|
|
'42830' => 'invalid_foreign_key', |
511
|
|
|
|
|
|
|
'42846' => 'cannot_coerce', |
512
|
|
|
|
|
|
|
'42883' => 'undefined_function', |
513
|
|
|
|
|
|
|
'428C9' => 'generated_always', |
514
|
|
|
|
|
|
|
'42939' => 'reserved_name', |
515
|
|
|
|
|
|
|
'42P01' => 'undefined_table', |
516
|
|
|
|
|
|
|
'42P02' => 'undefined_parameter', |
517
|
|
|
|
|
|
|
'42P03' => 'duplicate_cursor', |
518
|
|
|
|
|
|
|
'42P04' => 'duplicate_database', |
519
|
|
|
|
|
|
|
'42P05' => 'duplicate_prepared_statement', |
520
|
|
|
|
|
|
|
'42P06' => 'duplicate_schema', |
521
|
|
|
|
|
|
|
'42P07' => 'duplicate_table', |
522
|
|
|
|
|
|
|
'42P08' => 'ambiguous_parameter', |
523
|
|
|
|
|
|
|
'42P09' => 'ambiguous_alias', |
524
|
|
|
|
|
|
|
'42P10' => 'invalid_column_reference', |
525
|
|
|
|
|
|
|
'42P11' => 'invalid_cursor_definition', |
526
|
|
|
|
|
|
|
'42P12' => 'invalid_database_definition', |
527
|
|
|
|
|
|
|
'42P13' => 'invalid_function_definition', |
528
|
|
|
|
|
|
|
'42P14' => 'invalid_prepared_statement_definition', |
529
|
|
|
|
|
|
|
'42P15' => 'invalid_schema_definition', |
530
|
|
|
|
|
|
|
'42P16' => 'invalid_table_definition', |
531
|
|
|
|
|
|
|
'42P17' => 'invalid_object_definition', |
532
|
|
|
|
|
|
|
'42P18' => 'indeterminate_datatype', |
533
|
|
|
|
|
|
|
'42P19' => 'invalid_recursion', |
534
|
|
|
|
|
|
|
'42P20' => 'windowing_error', |
535
|
|
|
|
|
|
|
'42P21' => 'collation_mismatch', |
536
|
|
|
|
|
|
|
'42P22' => 'indeterminate_collation', |
537
|
|
|
|
|
|
|
'44000' => 'with_check_option_violation', |
538
|
|
|
|
|
|
|
'53000' => 'insufficient_resources', |
539
|
|
|
|
|
|
|
'53100' => 'disk_full', |
540
|
|
|
|
|
|
|
'53200' => 'out_of_memory', |
541
|
|
|
|
|
|
|
'53300' => 'too_many_connections', |
542
|
|
|
|
|
|
|
'53400' => 'configuration_limit_exceeded', |
543
|
|
|
|
|
|
|
'54000' => 'program_limit_exceeded', |
544
|
|
|
|
|
|
|
'54001' => 'statement_too_complex', |
545
|
|
|
|
|
|
|
'54011' => 'too_many_columns', |
546
|
|
|
|
|
|
|
'54023' => 'too_many_arguments', |
547
|
|
|
|
|
|
|
'55000' => 'object_not_in_prerequisite_state', |
548
|
|
|
|
|
|
|
'55006' => 'object_in_use', |
549
|
|
|
|
|
|
|
'55P02' => 'cant_change_runtime_param', |
550
|
|
|
|
|
|
|
'55P03' => 'lock_not_available', |
551
|
|
|
|
|
|
|
'55P04' => 'unsafe_new_enum_value_usage', |
552
|
|
|
|
|
|
|
'57000' => 'operator_intervention', |
553
|
|
|
|
|
|
|
'57014' => 'query_canceled', |
554
|
|
|
|
|
|
|
'57P01' => 'admin_shutdown', |
555
|
|
|
|
|
|
|
'57P02' => 'crash_shutdown', |
556
|
|
|
|
|
|
|
'57P03' => 'cannot_connect_now', |
557
|
|
|
|
|
|
|
'57P04' => 'database_dropped', |
558
|
|
|
|
|
|
|
'58000' => 'system_error', |
559
|
|
|
|
|
|
|
'58030' => 'io_error', |
560
|
|
|
|
|
|
|
'58P01' => 'undefined_file', |
561
|
|
|
|
|
|
|
'58P02' => 'duplicate_file', |
562
|
|
|
|
|
|
|
'72000' => 'snapshot_too_old', |
563
|
|
|
|
|
|
|
'F0000' => 'config_file_error', |
564
|
|
|
|
|
|
|
'F0001' => 'lock_file_exists', |
565
|
|
|
|
|
|
|
'HV000' => 'fdw_error', |
566
|
|
|
|
|
|
|
'HV001' => 'fdw_out_of_memory', |
567
|
|
|
|
|
|
|
'HV002' => 'fdw_dynamic_parameter_value_needed', |
568
|
|
|
|
|
|
|
'HV004' => 'fdw_invalid_data_type', |
569
|
|
|
|
|
|
|
'HV005' => 'fdw_column_name_not_found', |
570
|
|
|
|
|
|
|
'HV006' => 'fdw_invalid_data_type_descriptors', |
571
|
|
|
|
|
|
|
'HV007' => 'fdw_invalid_column_name', |
572
|
|
|
|
|
|
|
'HV008' => 'fdw_invalid_column_number', |
573
|
|
|
|
|
|
|
'HV009' => 'fdw_invalid_use_of_null_pointer', |
574
|
|
|
|
|
|
|
'HV00A' => 'fdw_invalid_string_format', |
575
|
|
|
|
|
|
|
'HV00B' => 'fdw_invalid_handle', |
576
|
|
|
|
|
|
|
'HV00C' => 'fdw_invalid_option_index', |
577
|
|
|
|
|
|
|
'HV00D' => 'fdw_invalid_option_name', |
578
|
|
|
|
|
|
|
'HV00J' => 'fdw_option_name_not_found', |
579
|
|
|
|
|
|
|
'HV00K' => 'fdw_reply_handle', |
580
|
|
|
|
|
|
|
'HV00L' => 'fdw_unable_to_create_execution', |
581
|
|
|
|
|
|
|
'HV00M' => 'fdw_unable_to_create_reply', |
582
|
|
|
|
|
|
|
'HV00N' => 'fdw_unable_to_establish_connection', |
583
|
|
|
|
|
|
|
'HV00P' => 'fdw_no_schemas', |
584
|
|
|
|
|
|
|
'HV00Q' => 'fdw_schema_not_found', |
585
|
|
|
|
|
|
|
'HV00R' => 'fdw_table_not_found', |
586
|
|
|
|
|
|
|
'HV010' => 'fdw_function_sequence_error', |
587
|
|
|
|
|
|
|
'HV014' => 'fdw_too_many_handles', |
588
|
|
|
|
|
|
|
'HV021' => 'fdw_inconsistent_descriptor_information', |
589
|
|
|
|
|
|
|
'HV024' => 'fdw_invalid_attribute_value', |
590
|
|
|
|
|
|
|
'HV090' => 'fdw_invalid_string_length_or_buffer_length', |
591
|
|
|
|
|
|
|
'HV091' => 'fdw_invalid_descriptor_field_identifier', |
592
|
|
|
|
|
|
|
'P0000' => 'plpgsql_error', |
593
|
|
|
|
|
|
|
'P0001' => 'raise_exception', |
594
|
|
|
|
|
|
|
'P0002' => 'no_data_found', |
595
|
|
|
|
|
|
|
'P0003' => 'too_many_rows', |
596
|
|
|
|
|
|
|
'P0004' => 'assert_failure', |
597
|
|
|
|
|
|
|
'XX000' => 'internal_error', |
598
|
|
|
|
|
|
|
'XX001' => 'data_corrupted', |
599
|
|
|
|
|
|
|
'XX002' => 'index_corrupted', |
600
|
|
|
|
|
|
|
); |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
=head1 METHODS |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
=cut |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
=head2 new |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
Instantiate a new object. Blesses an empty hashref and calls L, subclasses can bypass this entirely |
609
|
|
|
|
|
|
|
and just call L directly after instantiation. |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
=cut |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
sub new { |
614
|
1
|
|
|
1
|
1
|
117
|
my $self = bless { |
615
|
|
|
|
|
|
|
}, shift; |
616
|
1
|
|
|
|
|
3
|
$self->configure(@_); |
617
|
1
|
|
|
|
|
2
|
return $self; |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
=head2 configure |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
Does the real preparation for the object. |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
=cut |
625
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
sub configure { |
627
|
1
|
|
|
1
|
1
|
3
|
my ($self, %args) = @_; |
628
|
|
|
|
|
|
|
|
629
|
1
|
|
|
|
|
8
|
$self->{$_} = 0 for grep !exists $self->{$_}, qw(authenticated message_count); |
630
|
1
|
50
|
|
|
|
4
|
$self->{wait_for_startup} = 1 unless exists $self->{wait_for_startup}; |
631
|
1
|
|
|
|
|
4
|
$self->{$_} = delete $args{$_} for grep exists $args{$_}, qw(user pass database replication outgoing); |
632
|
|
|
|
|
|
|
|
633
|
1
|
|
|
|
|
2
|
return %args; |
634
|
|
|
|
|
|
|
} |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
=head2 frontend_bind |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
Bind parameters to an existing prepared statement. |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
=cut |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
sub frontend_bind { |
643
|
0
|
|
|
0
|
1
|
0
|
my ($self, %args) = @_; |
644
|
|
|
|
|
|
|
|
645
|
0
|
|
0
|
|
|
0
|
$args{param} ||= []; |
646
|
0
|
|
|
|
|
0
|
my $param = ''; |
647
|
0
|
|
|
|
|
0
|
my $count = 0 + @{$args{param}}; |
|
0
|
|
|
|
|
0
|
|
648
|
0
|
|
|
|
|
0
|
for my $p (@{$args{param}}) { |
|
0
|
|
|
|
|
0
|
|
649
|
0
|
0
|
|
|
|
0
|
if(!defined $p) { |
650
|
0
|
|
|
|
|
0
|
$param .= pack 'N1', 0xFFFFFFFF; |
651
|
|
|
|
|
|
|
} else { |
652
|
0
|
|
|
|
|
0
|
$param .= pack 'N/a*', Unicode::UTF8::encode_utf8($p); |
653
|
|
|
|
|
|
|
} |
654
|
|
|
|
|
|
|
} |
655
|
|
|
|
|
|
|
my $msg = pack('Z*Z*n1n1a*n1', |
656
|
|
|
|
|
|
|
Unicode::UTF8::encode_utf8($args{portal} // ''), |
657
|
0
|
|
0
|
|
|
0
|
Unicode::UTF8::encode_utf8($args{statement} // ''), |
|
|
|
0
|
|
|
|
|
658
|
|
|
|
|
|
|
0, # Parameter types |
659
|
|
|
|
|
|
|
$count, # Number of bound parameters |
660
|
|
|
|
|
|
|
$param, # Actual parameter values |
661
|
|
|
|
|
|
|
0 # Number of result column format definitions (0=use default text format) |
662
|
|
|
|
|
|
|
); |
663
|
0
|
|
0
|
|
|
0
|
push @{$self->{pending_bind}}, $args{sth} || (); |
|
0
|
|
|
|
|
0
|
|
664
|
|
|
|
|
|
|
$log->tracef(sub { |
665
|
|
|
|
|
|
|
join('', |
666
|
|
|
|
|
|
|
"Bind", |
667
|
|
|
|
|
|
|
defined($args{portal}) ? " for portal [" . $args{portal} . "]" : '', |
668
|
|
|
|
|
|
|
defined($args{statement}) ? " for statement [" . $args{statement} . "]" : '', |
669
|
|
|
|
|
|
|
" with $count parameter(s): ", |
670
|
0
|
0
|
|
0
|
|
0
|
join(',', @{$args{param}}) |
|
0
|
0
|
|
|
|
0
|
|
671
|
|
|
|
|
|
|
) |
672
|
0
|
0
|
|
|
|
0
|
}) if $log->is_debug; |
673
|
0
|
|
|
|
|
0
|
return $self->build_message( |
674
|
|
|
|
|
|
|
type => 'Bind', |
675
|
|
|
|
|
|
|
data => $msg, |
676
|
|
|
|
|
|
|
); |
677
|
|
|
|
|
|
|
} |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
=head2 frontend_copy_data |
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
=cut |
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
sub frontend_copy_data { |
686
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
687
|
0
|
|
|
|
|
0
|
my %args = @_; |
688
|
|
|
|
|
|
|
return $self->build_message( |
689
|
|
|
|
|
|
|
type => 'CopyData', |
690
|
|
|
|
|
|
|
data => pack('a*', $args{data}) |
691
|
0
|
|
|
|
|
0
|
); |
692
|
|
|
|
|
|
|
} |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
=head2 frontend_close |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
=cut |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
sub frontend_close { |
701
|
0
|
|
|
0
|
1
|
0
|
my ($self, %args) = @_; |
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
my $msg = pack('a1Z*', |
704
|
|
|
|
|
|
|
exists $args{portal} ? 'P' : 'S', # close a portal or a statement |
705
|
|
|
|
|
|
|
defined($args{statement}) |
706
|
|
|
|
|
|
|
? Unicode::UTF8::encode_utf8($args{statement}) |
707
|
|
|
|
|
|
|
: (defined($args{portal}) |
708
|
|
|
|
|
|
|
? Unicode::UTF8::encode_utf8($args{portal}) |
709
|
0
|
0
|
|
|
|
0
|
: '' |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
710
|
|
|
|
|
|
|
) |
711
|
|
|
|
|
|
|
); |
712
|
0
|
|
|
|
|
0
|
return $self->build_message( |
713
|
|
|
|
|
|
|
type => 'Close', |
714
|
|
|
|
|
|
|
data => $msg, |
715
|
|
|
|
|
|
|
); |
716
|
|
|
|
|
|
|
} |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
=head2 frontend_copy_done |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
=cut |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
sub frontend_copy_done { |
725
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
726
|
0
|
|
|
|
|
0
|
return $self->build_message( |
727
|
|
|
|
|
|
|
type => 'CopyDone', |
728
|
|
|
|
|
|
|
data => '', |
729
|
|
|
|
|
|
|
); |
730
|
|
|
|
|
|
|
} |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
=head2 frontend_describe |
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
Describe expected SQL results |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
=cut |
737
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
sub frontend_describe { |
739
|
0
|
|
|
0
|
1
|
0
|
my ($self, %args) = @_; |
740
|
|
|
|
|
|
|
|
741
|
0
|
0
|
|
|
|
0
|
my $msg = pack('a1Z*', exists $args{portal} ? 'P' : 'S', defined($args{statement}) ? Unicode::UTF8::encode_utf8($args{statement}) : (defined($args{portal}) ? Unicode::UTF8::encode_utf8($args{portal}) : '')); |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
742
|
0
|
|
|
|
|
0
|
return $self->build_message( |
743
|
|
|
|
|
|
|
type => 'Describe', |
744
|
|
|
|
|
|
|
data => $msg, |
745
|
|
|
|
|
|
|
); |
746
|
|
|
|
|
|
|
} |
747
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
=head2 frontend_execute |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
Execute either a named or anonymous portal (prepared statement with bind vars) |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
=cut |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
sub frontend_execute { |
755
|
0
|
|
|
0
|
1
|
0
|
my ($self, %args) = @_; |
756
|
|
|
|
|
|
|
|
757
|
0
|
|
0
|
|
|
0
|
$args{portal} //= ''; |
758
|
0
|
|
0
|
|
|
0
|
my $msg = pack('Z*N1', Unicode::UTF8::encode_utf8($args{portal}), $args{limit} || 0); |
759
|
|
|
|
|
|
|
$log->tracef( |
760
|
|
|
|
|
|
|
"Executing portal '%s' %s", |
761
|
|
|
|
|
|
|
$args{portal}, |
762
|
0
|
0
|
|
|
|
0
|
$args{limit} ? " with limit " . $args{limit} : " with no limit" |
|
|
0
|
|
|
|
|
|
763
|
|
|
|
|
|
|
) if $log->is_debug; |
764
|
0
|
|
|
|
|
0
|
return $self->build_message( |
765
|
|
|
|
|
|
|
type => 'Execute', |
766
|
|
|
|
|
|
|
data => $msg, |
767
|
|
|
|
|
|
|
); |
768
|
|
|
|
|
|
|
} |
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
=head2 frontend_parse |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
Parse SQL for a prepared statement |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
=cut |
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
sub frontend_parse { |
777
|
0
|
|
|
0
|
1
|
0
|
my ($self, %args) = @_; |
778
|
0
|
0
|
|
|
|
0
|
die "No SQL provided" unless defined $args{sql}; |
779
|
|
|
|
|
|
|
|
780
|
0
|
0
|
|
|
|
0
|
my $msg = pack('Z*Z*n1', (defined($args{statement}) ? Unicode::UTF8::encode_utf8($args{statement}) : ''), Unicode::UTF8::encode_utf8($args{sql}), 0); |
781
|
0
|
|
|
|
|
0
|
return $self->build_message( |
782
|
|
|
|
|
|
|
type => 'Parse', |
783
|
|
|
|
|
|
|
data => $msg, |
784
|
|
|
|
|
|
|
); |
785
|
|
|
|
|
|
|
} |
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
=head2 frontend_password_message |
788
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
Password data, possibly encrypted depending on what the server specified. |
790
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
=cut |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
sub frontend_password_message { |
794
|
0
|
|
|
0
|
1
|
0
|
my ($self, %args) = @_; |
795
|
|
|
|
|
|
|
|
796
|
0
|
|
0
|
|
|
0
|
my $pass = $args{password} // die 'no password provided'; |
797
|
0
|
0
|
|
|
|
0
|
if($args{password_type} eq 'md5') { |
798
|
|
|
|
|
|
|
# md5hex of password . username, |
799
|
|
|
|
|
|
|
# then md5hex result with salt appended |
800
|
|
|
|
|
|
|
# then stick 'md5' at the front. |
801
|
|
|
|
|
|
|
$pass = 'md5' . Digest::MD5::md5_hex( |
802
|
|
|
|
|
|
|
Digest::MD5::md5_hex(Unicode::UTF8::encode_utf8($pass) . Unicode::UTF8::encode_utf8($args{user})) |
803
|
|
|
|
|
|
|
. $args{password_salt} |
804
|
0
|
|
|
|
|
0
|
); |
805
|
|
|
|
|
|
|
} |
806
|
|
|
|
|
|
|
|
807
|
|
|
|
|
|
|
# Yes, protocol requires zero-terminated string format even |
808
|
|
|
|
|
|
|
# if we have a binary password value. |
809
|
0
|
|
|
|
|
0
|
return $self->build_message( |
810
|
|
|
|
|
|
|
type => 'PasswordMessage', |
811
|
|
|
|
|
|
|
data => pack('Z*', $pass) |
812
|
|
|
|
|
|
|
); |
813
|
|
|
|
|
|
|
} |
814
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
=head2 frontend_sasl_initial |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
Initial client response for SASL authentication |
818
|
|
|
|
|
|
|
|
819
|
|
|
|
|
|
|
=cut |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
sub frontend_sasl_initial_response { |
822
|
0
|
|
|
0
|
0
|
0
|
my ($self, %args) = @_; |
823
|
|
|
|
|
|
|
|
824
|
0
|
|
0
|
|
|
0
|
my $nonce = $args{nonce} // die 'no nonce provided'; |
825
|
0
|
|
0
|
|
|
0
|
my $mech = $args{mechanism} // die 'no SASL mechanism provided'; |
826
|
|
|
|
|
|
|
|
827
|
0
|
|
|
|
|
0
|
my $data = 'n,,n=,r=' . $nonce; |
828
|
0
|
|
|
|
|
0
|
return $self->build_message( |
829
|
|
|
|
|
|
|
type => 'SASLInitialResponse', |
830
|
|
|
|
|
|
|
data => pack('Z*N/a*', $mech, $data) |
831
|
|
|
|
|
|
|
); |
832
|
|
|
|
|
|
|
} |
833
|
|
|
|
|
|
|
|
834
|
|
|
|
|
|
|
sub frontend_sasl_response { |
835
|
0
|
|
|
0
|
0
|
0
|
my ($self, %args) = @_; |
836
|
|
|
|
|
|
|
|
837
|
0
|
|
0
|
|
|
0
|
my $nonce = $args{nonce} // die 'no nonce provided'; |
838
|
0
|
|
0
|
|
|
0
|
my $proof = $args{proof} // die 'no proof provided'; |
839
|
0
|
|
0
|
|
|
0
|
my $header = $args{header} // die 'no header provided'; |
840
|
|
|
|
|
|
|
|
841
|
0
|
|
|
|
|
0
|
$header = MIME::Base64::encode_base64($header, ''); |
842
|
0
|
|
|
|
|
0
|
$proof = MIME::Base64::encode_base64($proof, ''); |
843
|
0
|
|
|
|
|
0
|
my $data = "c=$header,r=$nonce,p=$proof"; |
844
|
0
|
|
|
|
|
0
|
return $self->build_message( |
845
|
|
|
|
|
|
|
type => 'SASLResponse', |
846
|
|
|
|
|
|
|
data => pack('A*', $data) |
847
|
|
|
|
|
|
|
); |
848
|
|
|
|
|
|
|
} |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
=head2 frontend_query |
851
|
|
|
|
|
|
|
|
852
|
|
|
|
|
|
|
Simple query |
853
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
=cut |
855
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
sub frontend_query { |
857
|
0
|
|
|
0
|
1
|
0
|
my ($self, %args) = @_; |
858
|
|
|
|
|
|
|
return $self->build_message( |
859
|
|
|
|
|
|
|
type => 'Query', |
860
|
0
|
|
|
|
|
0
|
data => pack('Z*', Unicode::UTF8::encode_utf8($args{sql})) |
861
|
|
|
|
|
|
|
); |
862
|
|
|
|
|
|
|
} |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
=head2 frontend_startup_message |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
Initial mesage informing the server which database and user we want |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
=cut |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
sub frontend_startup_message { |
871
|
1
|
|
|
1
|
1
|
3
|
my ($self, %args) = @_; |
872
|
1
|
50
|
|
|
|
3
|
die "Not first message" unless $self->is_first_message; |
873
|
|
|
|
|
|
|
|
874
|
1
|
50
|
|
|
|
3
|
if($args{replication}) { |
875
|
0
|
|
|
|
|
0
|
$args{replication} = 'database'; |
876
|
0
|
|
|
|
|
0
|
$args{database} = 'postgres'; |
877
|
|
|
|
|
|
|
} else { |
878
|
1
|
|
|
|
|
2
|
delete $args{replication}; |
879
|
|
|
|
|
|
|
} |
880
|
1
|
|
|
|
|
3
|
$log->tracef("Startup with %s", \%args); |
881
|
|
|
|
|
|
|
|
882
|
1
|
|
|
|
|
4
|
my $parameters = join('', map { pack('Z*', $_) } map { Unicode::UTF8::encode_utf8($_), Unicode::UTF8::encode_utf8($args{$_}) } grep { exists $args{$_} } qw(user database options application_name replication)); |
|
2
|
|
|
|
|
8
|
|
|
1
|
|
|
|
|
8
|
|
|
5
|
|
|
|
|
20
|
|
883
|
1
|
|
|
|
|
3
|
$parameters .= "\0"; |
884
|
|
|
|
|
|
|
|
885
|
1
|
|
|
|
|
4
|
return $self->build_message( |
886
|
|
|
|
|
|
|
type => undef, |
887
|
|
|
|
|
|
|
data => pack('N*', PROTOCOL_VERSION) . $parameters |
888
|
|
|
|
|
|
|
); |
889
|
|
|
|
|
|
|
} |
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
sub send_startup_request { |
892
|
0
|
|
|
0
|
0
|
0
|
my ($self, %args) = @_; |
893
|
0
|
|
|
|
|
0
|
$self->outgoing->emit($self->frontend_startup_message(%args)); |
894
|
|
|
|
|
|
|
} |
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
=head2 frontend_sync |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
Synchonise after a prepared statement has finished execution. |
899
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
=cut |
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
sub frontend_sync { |
903
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
904
|
0
|
|
|
|
|
0
|
return $self->build_message( |
905
|
|
|
|
|
|
|
type => 'Sync', |
906
|
|
|
|
|
|
|
data => '', |
907
|
|
|
|
|
|
|
); |
908
|
|
|
|
|
|
|
} |
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
=head2 frontend_terminate |
911
|
|
|
|
|
|
|
|
912
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
|
914
|
|
|
|
|
|
|
=cut |
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
sub frontend_terminate { |
917
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
918
|
0
|
|
|
|
|
0
|
return $self->build_message( |
919
|
|
|
|
|
|
|
type => 'Terminate', |
920
|
|
|
|
|
|
|
data => '', |
921
|
|
|
|
|
|
|
); |
922
|
|
|
|
|
|
|
} |
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
=head2 is_authenticated |
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
Returns true if we are authenticated (and can start sending real data). |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
=cut |
929
|
|
|
|
|
|
|
|
930
|
1
|
50
|
|
1
|
1
|
1235
|
sub is_authenticated { $_[0]->{authenticated} ? 1 : 0 } |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
=head2 is_first_message |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
Returns true if this is the first message, as per L: |
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
"For historical reasons, the very first message sent by the client (the startup message) |
937
|
|
|
|
|
|
|
has no initial message-type byte." |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
=cut |
940
|
|
|
|
|
|
|
|
941
|
3
|
|
|
3
|
1
|
11
|
sub is_first_message { $_[0]->{message_count} < 1 } |
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
=head2 send_message |
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
Send a message. |
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
=cut |
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
sub send_message { |
950
|
0
|
|
|
0
|
1
|
0
|
my ($self, @args) = @_; |
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
# Clear the ready-to-send flag since we're about to throw a message over to the |
953
|
|
|
|
|
|
|
# server and we don't want any others getting in the way. |
954
|
0
|
|
|
|
|
0
|
$self->{is_ready} = 0; |
955
|
|
|
|
|
|
|
|
956
|
0
|
|
|
|
|
0
|
$log->tracef("Will send message with %s", \@args); |
957
|
0
|
0
|
|
|
|
0
|
die "Empty message?" unless defined(my $msg = $self->message(@args)); |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
$log->tracef( |
960
|
|
|
|
|
|
|
"send data: [%v02x] %s (%s)", |
961
|
|
|
|
|
|
|
$msg, |
962
|
|
|
|
|
|
|
(($self->is_first_message ? "startup packet" : $FRONTEND_MESSAGE_CODE{substr($msg, 0, 1)}) || 'unknown message'), |
963
|
0
|
0
|
0
|
|
|
0
|
join('', '', map { (my $txt = defined($_) ? $_ : '') =~ tr/ []"'!#$%*&=:;A-Za-z0-9,()_ -/./c; $txt } split //, $msg) |
|
0
|
0
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
964
|
|
|
|
|
|
|
) if $log->is_debug; |
965
|
0
|
|
|
|
|
0
|
$self->outgoing->emit($msg); |
966
|
0
|
|
|
|
|
0
|
return $self; |
967
|
|
|
|
|
|
|
} |
968
|
|
|
|
|
|
|
|
969
|
0
|
|
0
|
0
|
0
|
0
|
sub outgoing { shift->{outgoing} // die 'no outgoing source' } |
970
|
|
|
|
|
|
|
|
971
|
|
|
|
|
|
|
=head2 method_for_frontend_type |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
Returns the method name for the given frontend type. |
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
=cut |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
sub method_for_frontend_type { |
978
|
2
|
|
|
2
|
1
|
6
|
my ($self, $type) = @_; |
979
|
2
|
|
|
|
|
5
|
my $method = 'frontend' . $type; |
980
|
2
|
|
|
|
|
3
|
for ($method) { |
981
|
2
|
|
|
|
|
7
|
s{([A-Z0-9]+)([A-Z])}{"_" . lc($1) . "_" . lc($2)}ge; |
|
0
|
|
|
|
|
0
|
|
982
|
2
|
|
|
|
|
8
|
s{([A-Z]+)}{"_" . lc($1)}ge; |
|
4
|
|
|
|
|
15
|
|
983
|
|
|
|
|
|
|
} |
984
|
|
|
|
|
|
|
# $method =~ s/([A-Z])/'_' . lc $1/ge; |
985
|
|
|
|
|
|
|
$method |
986
|
2
|
|
|
|
|
14
|
} |
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
=head2 is_known_frontend_message_type |
989
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
Returns true if the given frontend type is one that we know how to handle. |
991
|
|
|
|
|
|
|
|
992
|
|
|
|
|
|
|
=cut |
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
sub is_known_frontend_message_type { |
995
|
1
|
|
|
1
|
1
|
2
|
my ($self, $type) = @_; |
996
|
1
|
50
|
|
|
|
4
|
return 1 if exists $FRONTEND_MESSAGE_BUILDER{$type}; |
997
|
1
|
50
|
|
|
|
4
|
return 1 if $self->can($self->method_for_frontend_type($type)); |
998
|
0
|
|
|
|
|
0
|
return 0; |
999
|
|
|
|
|
|
|
} |
1000
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
=head2 message |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
Creates a new message of the given type. |
1004
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
=cut |
1006
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
sub message { |
1008
|
1
|
|
|
1
|
1
|
1978
|
my ($self, $type, @args) = @_; |
1009
|
1
|
50
|
|
|
|
4
|
die "Message $type unknown" unless $self->is_known_frontend_message_type($type); |
1010
|
|
|
|
|
|
|
|
1011
|
1
|
|
50
|
|
|
5
|
my $method = ($FRONTEND_MESSAGE_BUILDER{$type} || $self->can($self->method_for_frontend_type($type)) || die 'no method for ' . $type); |
1012
|
1
|
|
|
|
|
5
|
$log->tracef("Method is %s", Sub::Identify::sub_name $method); |
1013
|
1
|
|
|
|
|
13
|
my $msg = $self->$method(@args); |
1014
|
1
|
|
|
|
|
2
|
++$self->{message_count}; |
1015
|
1
|
|
|
|
|
4
|
return $msg; |
1016
|
|
|
|
|
|
|
} |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
=head2 handle_message |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
Handle an incoming message from the server. |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
=cut |
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
sub handle_message { |
1025
|
1
|
|
|
1
|
1
|
3
|
my ($self, $msg) = @_; |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
# Extract code and identify which message handler to use |
1028
|
1
|
|
|
|
|
2
|
my $type = do { |
1029
|
1
|
|
|
|
|
3
|
my $code = substr $msg, 0, 1; |
1030
|
1
|
50
|
|
|
|
5
|
my $type = $BACKEND_MESSAGE_CODE{$code} |
1031
|
|
|
|
|
|
|
or die 'unknown backend message code ' . $code; |
1032
|
1
|
|
|
|
|
4
|
$log->tracef('Handle message of type %s (code %s)', $type, $code); |
1033
|
1
|
|
|
|
|
4
|
$type |
1034
|
|
|
|
|
|
|
}; |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
# Clear the ready-to-send flag until we've processed this |
1037
|
1
|
|
|
|
|
3
|
$self->{is_ready} = 0; |
1038
|
1
|
|
|
|
|
9
|
return ('Protocol::Database::PostgreSQL::Backend::' . $type)->new_from_message($msg); |
1039
|
|
|
|
|
|
|
} |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
sub ssl_request { |
1042
|
0
|
|
|
0
|
1
|
0
|
my ($self) = @_; |
1043
|
|
|
|
|
|
|
# Magic SSL code, see https://www.postgresql.org/docs/current/protocol-message-formats.html |
1044
|
0
|
|
|
|
|
0
|
my $data = pack("n1n1", 1234, 5679); |
1045
|
0
|
|
|
|
|
0
|
return pack 'Na*', 8, $data; |
1046
|
|
|
|
|
|
|
} |
1047
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
=head2 message_length |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
Returns the length of the given message. |
1051
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
=cut |
1053
|
|
|
|
|
|
|
|
1054
|
|
|
|
|
|
|
sub message_length { |
1055
|
17
|
|
|
17
|
1
|
28158
|
my ($self, $msg) = @_; |
1056
|
17
|
50
|
|
|
|
43
|
return undef unless length($msg) >= 5; |
1057
|
17
|
|
|
|
|
50
|
(undef, my $len) = unpack('C1N1', substr($msg, 0, 5)); |
1058
|
17
|
|
|
|
|
53
|
return $len; |
1059
|
|
|
|
|
|
|
} |
1060
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
=head2 simple_query |
1062
|
|
|
|
|
|
|
|
1063
|
|
|
|
|
|
|
Send a simple query to the server - only supports plain queries (no bind parameters). |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
=cut |
1066
|
|
|
|
|
|
|
|
1067
|
|
|
|
|
|
|
sub simple_query { |
1068
|
0
|
|
|
0
|
1
|
0
|
my ($self, $sql) = @_; |
1069
|
|
|
|
|
|
|
|
1070
|
0
|
|
|
|
|
0
|
$log->tracef("Running query [%s]", $sql); |
1071
|
0
|
|
|
|
|
0
|
$self->send_message('Query', sql => $sql); |
1072
|
0
|
|
|
|
|
0
|
return $self; |
1073
|
|
|
|
|
|
|
} |
1074
|
|
|
|
|
|
|
|
1075
|
|
|
|
|
|
|
=head2 copy_data |
1076
|
|
|
|
|
|
|
|
1077
|
|
|
|
|
|
|
Send copy data to the server. |
1078
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
=cut |
1080
|
|
|
|
|
|
|
|
1081
|
|
|
|
|
|
|
sub copy_data { |
1082
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1083
|
0
|
|
|
|
|
0
|
my $data = shift; |
1084
|
0
|
0
|
|
|
|
0
|
die "Invalid backend state" if $self->backend_state eq 'error'; |
1085
|
|
|
|
|
|
|
|
1086
|
0
|
|
|
|
|
0
|
$self->send_message('CopyData', data => $data); |
1087
|
0
|
|
|
|
|
0
|
return $self; |
1088
|
|
|
|
|
|
|
} |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
=head2 copy_done |
1091
|
|
|
|
|
|
|
|
1092
|
|
|
|
|
|
|
Indicate that the COPY data from the client is complete. |
1093
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
=cut |
1095
|
|
|
|
|
|
|
|
1096
|
|
|
|
|
|
|
sub copy_done { |
1097
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1098
|
0
|
|
|
|
|
0
|
my $data = shift; |
1099
|
0
|
0
|
|
|
|
0
|
die "Invalid backend state" if $self->backend_state eq 'error'; |
1100
|
|
|
|
|
|
|
|
1101
|
0
|
|
|
|
|
0
|
$self->send_message('CopyDone'); |
1102
|
0
|
|
|
|
|
0
|
return $self; |
1103
|
|
|
|
|
|
|
} |
1104
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
=head2 backend_state |
1106
|
|
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
Accessor for current backend state. |
1108
|
|
|
|
|
|
|
|
1109
|
|
|
|
|
|
|
=cut |
1110
|
|
|
|
|
|
|
|
1111
|
|
|
|
|
|
|
sub backend_state { |
1112
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1113
|
0
|
0
|
|
|
|
0
|
if(@_) { |
1114
|
0
|
|
|
|
|
0
|
my $state = shift; |
1115
|
0
|
0
|
|
|
|
0
|
die "bad state code" unless grep { $state eq $_ } qw(idle transaction error); |
|
0
|
|
|
|
|
0
|
|
1116
|
|
|
|
|
|
|
|
1117
|
0
|
|
|
|
|
0
|
$self->{backend_state} = $state; |
1118
|
0
|
|
|
|
|
0
|
return $self; |
1119
|
|
|
|
|
|
|
} |
1120
|
0
|
|
|
|
|
0
|
return $self->{backend_state}; |
1121
|
|
|
|
|
|
|
} |
1122
|
|
|
|
|
|
|
|
1123
|
|
|
|
|
|
|
=head2 is_ready |
1124
|
|
|
|
|
|
|
|
1125
|
|
|
|
|
|
|
Returns true if we're ready to send more data to the server. |
1126
|
|
|
|
|
|
|
|
1127
|
|
|
|
|
|
|
=cut |
1128
|
|
|
|
|
|
|
|
1129
|
|
|
|
|
|
|
sub is_ready { |
1130
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1131
|
0
|
0
|
|
|
|
0
|
if(@_) { |
1132
|
0
|
|
|
|
|
0
|
$self->{is_ready} = shift; |
1133
|
0
|
|
|
|
|
0
|
return $self; |
1134
|
|
|
|
|
|
|
} |
1135
|
0
|
0
|
|
|
|
0
|
return 0 if $self->{wait_for_startup}; |
1136
|
0
|
|
|
|
|
0
|
return $self->{is_ready}; |
1137
|
|
|
|
|
|
|
} |
1138
|
|
|
|
|
|
|
|
1139
|
|
|
|
|
|
|
=head2 send_copy_data |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
Send COPY data to the server. Takes an arrayref and replaces any reserved characters with quoted versions. |
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
=cut |
1144
|
|
|
|
|
|
|
|
1145
|
|
|
|
|
|
|
{ |
1146
|
|
|
|
|
|
|
my %_charmap = ( |
1147
|
|
|
|
|
|
|
"\\" => "\\\\", |
1148
|
|
|
|
|
|
|
"\x08" => "\\b", |
1149
|
|
|
|
|
|
|
"\x09" => "\\t", |
1150
|
|
|
|
|
|
|
"\x0A" => "\\r", |
1151
|
|
|
|
|
|
|
"\x0C" => "\\f", |
1152
|
|
|
|
|
|
|
"\x0D" => "\\n", |
1153
|
|
|
|
|
|
|
); |
1154
|
|
|
|
|
|
|
|
1155
|
|
|
|
|
|
|
sub send_copy_data { |
1156
|
0
|
|
|
0
|
1
|
0
|
my ($self, $data) = @_; |
1157
|
|
|
|
|
|
|
my $content = pack 'a*', ( |
1158
|
|
|
|
|
|
|
Unicode::UTF8::encode_utf8( |
1159
|
|
|
|
|
|
|
join("\t", map { |
1160
|
0
|
0
|
|
|
|
0
|
defined($_) |
|
0
|
|
|
|
|
0
|
|
1161
|
0
|
|
|
|
|
0
|
? s/([\\\x08\x09\x0A\x0C\x0D])/$_charmap{$1}/ger |
1162
|
|
|
|
|
|
|
: '\N' |
1163
|
|
|
|
|
|
|
} @$data) . "\n" |
1164
|
|
|
|
|
|
|
) |
1165
|
|
|
|
|
|
|
); |
1166
|
|
|
|
|
|
|
|
1167
|
|
|
|
|
|
|
$self->outgoing->emit( |
1168
|
0
|
|
|
|
|
0
|
$MESSAGE_TYPE_FRONTEND{'CopyData'} . pack('N1', 4 + length $content) . $content |
1169
|
|
|
|
|
|
|
); |
1170
|
0
|
|
|
|
|
0
|
return $self; |
1171
|
|
|
|
|
|
|
} |
1172
|
|
|
|
|
|
|
} |
1173
|
|
|
|
|
|
|
|
1174
|
|
|
|
|
|
|
sub extract_message { |
1175
|
0
|
|
|
0
|
0
|
0
|
my ($self, $buffref) = @_; |
1176
|
|
|
|
|
|
|
# The smallest possible message is 5 bytes |
1177
|
0
|
0
|
|
|
|
0
|
return undef unless length($$buffref) >= 5; |
1178
|
|
|
|
|
|
|
# Don't start extracting until we know we have a full packet |
1179
|
0
|
|
|
|
|
0
|
my ($code, $size) = unpack('C1N1', $$buffref); |
1180
|
0
|
0
|
|
|
|
0
|
return undef unless length($$buffref) >= $size+1; |
1181
|
0
|
|
|
|
|
0
|
return $self->handle_message( |
1182
|
|
|
|
|
|
|
substr $$buffref, 0, $size+1, '' |
1183
|
|
|
|
|
|
|
); |
1184
|
|
|
|
|
|
|
} |
1185
|
|
|
|
|
|
|
|
1186
|
|
|
|
|
|
|
=head2 build_message |
1187
|
|
|
|
|
|
|
|
1188
|
|
|
|
|
|
|
Construct a new message. |
1189
|
|
|
|
|
|
|
|
1190
|
|
|
|
|
|
|
=cut |
1191
|
|
|
|
|
|
|
|
1192
|
|
|
|
|
|
|
sub build_message { |
1193
|
3
|
|
|
3
|
1
|
642
|
my $self = shift; |
1194
|
3
|
|
|
|
|
6
|
my %args = @_; |
1195
|
|
|
|
|
|
|
|
1196
|
|
|
|
|
|
|
# Can be undef |
1197
|
3
|
100
|
|
|
|
16
|
die "No type provided" unless exists $args{type}; |
1198
|
2
|
100
|
|
|
|
11
|
die "No data provided" unless exists $args{data}; |
1199
|
|
|
|
|
|
|
|
1200
|
|
|
|
|
|
|
# Length includes the 4-byte length field, but not the type byte |
1201
|
1
|
|
|
|
|
2
|
my $length = length($args{data}) + 4; |
1202
|
1
|
50
|
0
|
|
|
7
|
return (defined($args{type}) ? ($MESSAGE_TYPE_FRONTEND{$args{type}} // die 'unknown message ' . $args{type}) : '') . pack('N1', $length) . $args{data}; |
1203
|
|
|
|
|
|
|
} |
1204
|
|
|
|
|
|
|
|
1205
|
0
|
|
|
0
|
0
|
|
sub state { $_[0]->{state} = $_[1] } |
1206
|
|
|
|
|
|
|
|
1207
|
0
|
|
|
0
|
0
|
|
sub current_state { shift->{state} } |
1208
|
|
|
|
|
|
|
|
1209
|
|
|
|
|
|
|
1; |
1210
|
|
|
|
|
|
|
|
1211
|
|
|
|
|
|
|
__END__ |