File Coverage

blib/lib/Protocol/Database/PostgreSQL.pm
Criterion Covered Total %
statement 164 285 57.5
branch 14 78 17.9
condition 1 33 3.0
subroutine 47 74 63.5
pod 30 37 81.0
total 256 507 50.4


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            

PostgreSQL connection states

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__