File Coverage

blib/lib/Protocol/Database/PostgreSQL.pm
Criterion Covered Total %
statement 159 266 59.7
branch 14 78 17.9
condition 1 21 4.7
subroutine 46 71 64.7
pod 30 35 85.7
total 250 471 53.0


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