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 3     3   374608 use strict;
  3         6  
  3         117  
4 3     3   19 use warnings;
  3         4  
  3         311  
5              
6             our $VERSION = '2.001';
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 3     3   1738 no indirect;
  3         4411  
  3         14  
197              
198 3     3   178 use Digest::MD5 ();
  3         8  
  3         58  
199 3     3   1151 use Time::HiRes ();
  3         2750  
  3         112  
200 3     3   1727 use POSIX qw(strftime);
  3         28079  
  3         27  
201              
202 3     3   7680 use Log::Any qw($log);
  3         33769  
  3         17  
203 3     3   10100 use Ryu;
  3         272834  
  3         304  
204 3     3   33 use Future;
  3         7  
  3         95  
205 3     3   1744 use Sub::Identify;
  3         5246  
  3         246  
206 3     3   1466 use Unicode::UTF8;
  3         2180  
  3         206  
207 3     3   1741 use MIME::Base64;
  3         3001  
  3         298  
208              
209 3     3   1815 use Protocol::Database::PostgreSQL::Backend::AuthenticationRequest;
  3         8  
  3         179  
210 3     3   1547 use Protocol::Database::PostgreSQL::Backend::BackendKeyData;
  3         11  
  3         158  
211 3     3   1809 use Protocol::Database::PostgreSQL::Backend::BindComplete;
  3         10  
  3         150  
212 3     3   1622 use Protocol::Database::PostgreSQL::Backend::CloseComplete;
  3         10  
  3         150  
213 3     3   1664 use Protocol::Database::PostgreSQL::Backend::CommandComplete;
  3         11  
  3         181  
214 3     3   1657 use Protocol::Database::PostgreSQL::Backend::CopyData;
  3         13  
  3         199  
215 3     3   1730 use Protocol::Database::PostgreSQL::Backend::CopyDone;
  3         10  
  3         165  
216 3     3   1623 use Protocol::Database::PostgreSQL::Backend::CopyInResponse;
  3         11  
  3         183  
217 3     3   1672 use Protocol::Database::PostgreSQL::Backend::CopyOutResponse;
  3         11  
  3         163  
218 3     3   1646 use Protocol::Database::PostgreSQL::Backend::CopyBothResponse;
  3         9  
  3         192  
219 3     3   1628 use Protocol::Database::PostgreSQL::Backend::DataRow;
  3         11  
  3         228  
220 3     3   1688 use Protocol::Database::PostgreSQL::Backend::EmptyQueryResponse;
  3         12  
  3         182  
221 3     3   1619 use Protocol::Database::PostgreSQL::Backend::ErrorResponse;
  3         13  
  3         204  
222 3     3   1673 use Protocol::Database::PostgreSQL::Backend::FunctionCallResponse;
  3         11  
  3         195  
223 3     3   1806 use Protocol::Database::PostgreSQL::Backend::NoData;
  3         10  
  3         165  
224 3     3   1519 use Protocol::Database::PostgreSQL::Backend::NoticeResponse;
  3         10  
  3         193  
225 3     3   1556 use Protocol::Database::PostgreSQL::Backend::NotificationResponse;
  3         10  
  3         149  
226 3     3   1743 use Protocol::Database::PostgreSQL::Backend::ParameterDescription;
  3         12  
  3         187  
227 3     3   1979 use Protocol::Database::PostgreSQL::Backend::ParameterStatus;
  3         12  
  3         248  
228 3     3   1930 use Protocol::Database::PostgreSQL::Backend::ParseComplete;
  3         12  
  3         208  
229 3     3   1859 use Protocol::Database::PostgreSQL::Backend::PortalSuspended;
  3         12  
  3         177  
230 3     3   1899 use Protocol::Database::PostgreSQL::Backend::ReadyForQuery;
  3         10  
  3         189  
231 3     3   2161 use Protocol::Database::PostgreSQL::Backend::RowDescription;
  3         11  
  3         238  
232              
233             # Currently v3.0, which is used in PostgreSQL 7.4+
234 3     3   28 use constant PROTOCOL_VERSION => 0x00030000;
  3         7  
  3         34721  
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             '22031' => 'invalid_argument_for_sql_json_datetime_function',
421             '22032' => 'invalid_json_text',
422             '22033' => 'invalid_sql_json_subscript',
423             '22034' => 'more_than_one_sql_json_item',
424             '22035' => 'no_sql_json_item',
425             '22036' => 'non_numeric_sql_json_item',
426             '22037' => 'non_unique_keys_in_a_json_object',
427             '22038' => 'singleton_sql_json_item_required',
428             '22039' => 'sql_json_array_not_found',
429             '2203A' => 'sql_json_member_not_found',
430             '2203B' => 'sql_json_number_not_found',
431             '2203C' => 'sql_json_object_not_found',
432             '2203D' => 'too_many_json_array_elements',
433             '2203E' => 'too_many_json_object_members',
434             '2203F' => 'sql_json_scalar_required',
435             '2203G' => 'sql_json_item_cannot_be_cast_to_target_type',
436             '22P01' => 'floating_point_exception',
437             '22P02' => 'invalid_text_representation',
438             '22P03' => 'invalid_binary_representation',
439             '22P04' => 'bad_copy_file_format',
440             '22P05' => 'untranslatable_character',
441             '22P06' => 'nonstandard_use_of_escape_character',
442             '23000' => 'integrity_constraint_violation',
443             '23001' => 'restrict_violation',
444             '23502' => 'not_null_violation',
445             '23503' => 'foreign_key_violation',
446             '23505' => 'unique_violation',
447             '23514' => 'check_violation',
448             '23P01' => 'exclusion_violation',
449             '24000' => 'invalid_cursor_state',
450             '25000' => 'invalid_transaction_state',
451             '25001' => 'active_sql_transaction',
452             '25002' => 'branch_transaction_already_active',
453             '25003' => 'inappropriate_access_mode_for_branch_transaction',
454             '25004' => 'inappropriate_isolation_level_for_branch_transaction',
455             '25005' => 'no_active_sql_transaction_for_branch_transaction',
456             '25006' => 'read_only_sql_transaction',
457             '25007' => 'schema_and_data_statement_mixing_not_supported',
458             '25008' => 'held_cursor_requires_same_isolation_level',
459             '25P01' => 'no_active_sql_transaction',
460             '25P02' => 'in_failed_sql_transaction',
461             '25P03' => 'idle_in_transaction_session_timeout',
462             '26000' => 'invalid_sql_statement_name',
463             '27000' => 'triggered_data_change_violation',
464             '28000' => 'invalid_authorization_specification',
465             '28P01' => 'invalid_password',
466             '2B000' => 'dependent_privilege_descriptors_still_exist',
467             '2BP01' => 'dependent_objects_still_exist',
468             '2D000' => 'invalid_transaction_termination',
469             '2F000' => 'sql_routine_exception',
470             '2F002' => 'modifying_sql_data_not_permitted',
471             '2F003' => 'prohibited_sql_statement_attempted',
472             '2F004' => 'reading_sql_data_not_permitted',
473             '2F005' => 'function_executed_no_return_statement',
474             '34000' => 'invalid_cursor_name',
475             '38000' => 'external_routine_exception',
476             '38001' => 'containing_sql_not_permitted',
477             '38002' => 'modifying_sql_data_not_permitted',
478             '38003' => 'prohibited_sql_statement_attempted',
479             '38004' => 'reading_sql_data_not_permitted',
480             '39000' => 'external_routine_invocation_exception',
481             '39001' => 'invalid_sqlstate_returned',
482             '39004' => 'null_value_not_allowed',
483             '39P01' => 'trigger_protocol_violated',
484             '39P02' => 'srf_protocol_violated',
485             '39P03' => 'event_trigger_protocol_violated',
486             '3B000' => 'savepoint_exception',
487             '3B001' => 'invalid_savepoint_specification',
488             '3D000' => 'invalid_catalog_name',
489             '3F000' => 'invalid_schema_name',
490             '40000' => 'transaction_rollback',
491             '40001' => 'serialization_failure',
492             '40002' => 'transaction_integrity_constraint_violation',
493             '40003' => 'statement_completion_unknown',
494             '40P01' => 'deadlock_detected',
495             '42000' => 'syntax_error_or_access_rule_violation',
496             '42501' => 'insufficient_privilege',
497             '42601' => 'syntax_error',
498             '42602' => 'invalid_name',
499             '42611' => 'invalid_column_definition',
500             '42622' => 'name_too_long',
501             '42701' => 'duplicate_column',
502             '42702' => 'ambiguous_column',
503             '42703' => 'undefined_column',
504             '42704' => 'undefined_object',
505             '42710' => 'duplicate_object',
506             '42712' => 'duplicate_alias',
507             '42723' => 'duplicate_function',
508             '42725' => 'ambiguous_function',
509             '42803' => 'grouping_error',
510             '42804' => 'datatype_mismatch',
511             '42809' => 'wrong_object_type',
512             '42830' => 'invalid_foreign_key',
513             '42846' => 'cannot_coerce',
514             '42883' => 'undefined_function',
515             '428C9' => 'generated_always',
516             '42939' => 'reserved_name',
517             '42P01' => 'undefined_table',
518             '42P02' => 'undefined_parameter',
519             '42P03' => 'duplicate_cursor',
520             '42P04' => 'duplicate_database',
521             '42P05' => 'duplicate_prepared_statement',
522             '42P06' => 'duplicate_schema',
523             '42P07' => 'duplicate_table',
524             '42P08' => 'ambiguous_parameter',
525             '42P09' => 'ambiguous_alias',
526             '42P10' => 'invalid_column_reference',
527             '42P11' => 'invalid_cursor_definition',
528             '42P12' => 'invalid_database_definition',
529             '42P13' => 'invalid_function_definition',
530             '42P14' => 'invalid_prepared_statement_definition',
531             '42P15' => 'invalid_schema_definition',
532             '42P16' => 'invalid_table_definition',
533             '42P17' => 'invalid_object_definition',
534             '42P18' => 'indeterminate_datatype',
535             '42P19' => 'invalid_recursion',
536             '42P20' => 'windowing_error',
537             '42P21' => 'collation_mismatch',
538             '42P22' => 'indeterminate_collation',
539             '44000' => 'with_check_option_violation',
540             '53000' => 'insufficient_resources',
541             '53100' => 'disk_full',
542             '53200' => 'out_of_memory',
543             '53300' => 'too_many_connections',
544             '53400' => 'configuration_limit_exceeded',
545             '54000' => 'program_limit_exceeded',
546             '54001' => 'statement_too_complex',
547             '54011' => 'too_many_columns',
548             '54023' => 'too_many_arguments',
549             '55000' => 'object_not_in_prerequisite_state',
550             '55006' => 'object_in_use',
551             '55P02' => 'cant_change_runtime_param',
552             '55P03' => 'lock_not_available',
553             '55P04' => 'unsafe_new_enum_value_usage',
554             '57000' => 'operator_intervention',
555             '57014' => 'query_canceled',
556             '57P01' => 'admin_shutdown',
557             '57P02' => 'crash_shutdown',
558             '57P03' => 'cannot_connect_now',
559             '57P04' => 'database_dropped',
560             '57P05' => 'idle_session_timeout',
561             '58000' => 'system_error',
562             '58030' => 'io_error',
563             '58P01' => 'undefined_file',
564             '58P02' => 'duplicate_file',
565             '72000' => 'snapshot_too_old',
566             'F0000' => 'config_file_error',
567             'F0001' => 'lock_file_exists',
568             'HV000' => 'fdw_error',
569             'HV001' => 'fdw_out_of_memory',
570             'HV002' => 'fdw_dynamic_parameter_value_needed',
571             'HV004' => 'fdw_invalid_data_type',
572             'HV005' => 'fdw_column_name_not_found',
573             'HV006' => 'fdw_invalid_data_type_descriptors',
574             'HV007' => 'fdw_invalid_column_name',
575             'HV008' => 'fdw_invalid_column_number',
576             'HV009' => 'fdw_invalid_use_of_null_pointer',
577             'HV00A' => 'fdw_invalid_string_format',
578             'HV00B' => 'fdw_invalid_handle',
579             'HV00C' => 'fdw_invalid_option_index',
580             'HV00D' => 'fdw_invalid_option_name',
581             'HV00J' => 'fdw_option_name_not_found',
582             'HV00K' => 'fdw_reply_handle',
583             'HV00L' => 'fdw_unable_to_create_execution',
584             'HV00M' => 'fdw_unable_to_create_reply',
585             'HV00N' => 'fdw_unable_to_establish_connection',
586             'HV00P' => 'fdw_no_schemas',
587             'HV00Q' => 'fdw_schema_not_found',
588             'HV00R' => 'fdw_table_not_found',
589             'HV010' => 'fdw_function_sequence_error',
590             'HV014' => 'fdw_too_many_handles',
591             'HV021' => 'fdw_inconsistent_descriptor_information',
592             'HV024' => 'fdw_invalid_attribute_value',
593             'HV090' => 'fdw_invalid_string_length_or_buffer_length',
594             'HV091' => 'fdw_invalid_descriptor_field_identifier',
595             'P0000' => 'plpgsql_error',
596             'P0001' => 'raise_exception',
597             'P0002' => 'no_data_found',
598             'P0003' => 'too_many_rows',
599             'P0004' => 'assert_failure',
600             'XX000' => 'internal_error',
601             'XX001' => 'data_corrupted',
602             'XX002' => 'index_corrupted',
603             );
604              
605             =head1 METHODS
606              
607             =cut
608              
609             =head2 new
610              
611             Instantiate a new object. Blesses an empty hashref and calls L, subclasses can bypass this entirely
612             and just call L directly after instantiation.
613              
614             =cut
615              
616             sub new {
617 1     1 1 281293 my $self = bless {
618             }, shift;
619 1         8 $self->configure(@_);
620 1         4 return $self;
621             }
622              
623             =head2 configure
624              
625             Does the real preparation for the object.
626              
627             =cut
628              
629             sub configure {
630 1     1 1 4 my ($self, %args) = @_;
631              
632 1         13 $self->{$_} = 0 for grep !exists $self->{$_}, qw(authenticated message_count);
633 1 50       8 $self->{wait_for_startup} = 1 unless exists $self->{wait_for_startup};
634 1         6 $self->{$_} = delete $args{$_} for grep exists $args{$_}, qw(user pass database replication outgoing);
635              
636 1         3 return %args;
637             }
638              
639             =head2 frontend_bind
640              
641             Bind parameters to an existing prepared statement.
642              
643             =cut
644              
645             sub frontend_bind {
646 0     0 1 0 my ($self, %args) = @_;
647              
648 0   0     0 $args{param} ||= [];
649 0         0 my $param = '';
650 0         0 my $count = 0 + @{$args{param}};
  0         0  
651 0         0 for my $p (@{$args{param}}) {
  0         0  
652 0 0       0 if(!defined $p) {
653 0         0 $param .= pack 'N1', 0xFFFFFFFF;
654             } else {
655 0         0 $param .= pack 'N/a*', Unicode::UTF8::encode_utf8($p);
656             }
657             }
658             my $msg = pack('Z*Z*n1n1a*n1',
659             Unicode::UTF8::encode_utf8($args{portal} // ''),
660 0   0     0 Unicode::UTF8::encode_utf8($args{statement} // ''),
      0        
661             0, # Parameter types
662             $count, # Number of bound parameters
663             $param, # Actual parameter values
664             0 # Number of result column format definitions (0=use default text format)
665             );
666 0   0     0 push @{$self->{pending_bind}}, $args{sth} || ();
  0         0  
667             $log->tracef(sub {
668             join('',
669             "Bind",
670             defined($args{portal}) ? " for portal [" . $args{portal} . "]" : '',
671             defined($args{statement}) ? " for statement [" . $args{statement} . "]" : '',
672             " with $count parameter(s): ",
673 0 0   0   0 join(',', @{$args{param}})
  0 0       0  
674             )
675 0 0       0 }) if $log->is_debug;
676 0         0 return $self->build_message(
677             type => 'Bind',
678             data => $msg,
679             );
680             }
681              
682             =head2 frontend_copy_data
683              
684              
685              
686             =cut
687              
688             sub frontend_copy_data {
689 0     0 1 0 my $self = shift;
690 0         0 my %args = @_;
691             return $self->build_message(
692             type => 'CopyData',
693             data => pack('a*', $args{data})
694 0         0 );
695             }
696              
697             =head2 frontend_close
698              
699              
700              
701             =cut
702              
703             sub frontend_close {
704 0     0 1 0 my ($self, %args) = @_;
705              
706             my $msg = pack('a1Z*',
707             exists $args{portal} ? 'P' : 'S', # close a portal or a statement
708             defined($args{statement})
709             ? Unicode::UTF8::encode_utf8($args{statement})
710             : (defined($args{portal})
711             ? Unicode::UTF8::encode_utf8($args{portal})
712 0 0       0 : ''
    0          
    0          
713             )
714             );
715 0         0 return $self->build_message(
716             type => 'Close',
717             data => $msg,
718             );
719             }
720              
721             =head2 frontend_copy_done
722              
723              
724              
725             =cut
726              
727             sub frontend_copy_done {
728 0     0 1 0 my $self = shift;
729 0         0 return $self->build_message(
730             type => 'CopyDone',
731             data => '',
732             );
733             }
734              
735             =head2 frontend_describe
736              
737             Describe expected SQL results
738              
739             =cut
740              
741             sub frontend_describe {
742 0     0 1 0 my ($self, %args) = @_;
743              
744 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          
745 0         0 return $self->build_message(
746             type => 'Describe',
747             data => $msg,
748             );
749             }
750              
751             =head2 frontend_execute
752              
753             Execute either a named or anonymous portal (prepared statement with bind vars)
754              
755             =cut
756              
757             sub frontend_execute {
758 0     0 1 0 my ($self, %args) = @_;
759              
760 0   0     0 $args{portal} //= '';
761 0   0     0 my $msg = pack('Z*N1', Unicode::UTF8::encode_utf8($args{portal}), $args{limit} || 0);
762             $log->tracef(
763             "Executing portal '%s' %s",
764             $args{portal},
765 0 0       0 $args{limit} ? " with limit " . $args{limit} : " with no limit"
    0          
766             ) if $log->is_debug;
767 0         0 return $self->build_message(
768             type => 'Execute',
769             data => $msg,
770             );
771             }
772              
773             =head2 frontend_parse
774              
775             Parse SQL for a prepared statement
776              
777             =cut
778              
779             sub frontend_parse {
780 0     0 1 0 my ($self, %args) = @_;
781 0 0       0 die "No SQL provided" unless defined $args{sql};
782              
783 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);
784 0         0 return $self->build_message(
785             type => 'Parse',
786             data => $msg,
787             );
788             }
789              
790             =head2 frontend_password_message
791              
792             Password data, possibly encrypted depending on what the server specified.
793              
794             =cut
795              
796             sub frontend_password_message {
797 0     0 1 0 my ($self, %args) = @_;
798              
799 0   0     0 my $pass = $args{password} // die 'no password provided';
800 0 0       0 if($args{password_type} eq 'md5') {
801             # md5hex of password . username,
802             # then md5hex result with salt appended
803             # then stick 'md5' at the front.
804             $pass = 'md5' . Digest::MD5::md5_hex(
805             Digest::MD5::md5_hex(Unicode::UTF8::encode_utf8($pass) . Unicode::UTF8::encode_utf8($args{user}))
806             . $args{password_salt}
807 0         0 );
808             }
809              
810             # Yes, protocol requires zero-terminated string format even
811             # if we have a binary password value.
812 0         0 return $self->build_message(
813             type => 'PasswordMessage',
814             data => pack('Z*', $pass)
815             );
816             }
817              
818             =head2 frontend_sasl_initial
819              
820             Initial client response for SASL authentication
821              
822             =cut
823              
824             sub frontend_sasl_initial_response {
825 0     0 0 0 my ($self, %args) = @_;
826              
827 0   0     0 my $nonce = $args{nonce} // die 'no nonce provided';
828 0   0     0 my $mech = $args{mechanism} // die 'no SASL mechanism provided';
829              
830 0         0 my $data = 'n,,n=,r=' . $nonce;
831 0         0 return $self->build_message(
832             type => 'SASLInitialResponse',
833             data => pack('Z*N/a*', $mech, $data)
834             );
835             }
836              
837             sub frontend_sasl_response {
838 0     0 0 0 my ($self, %args) = @_;
839              
840 0   0     0 my $nonce = $args{nonce} // die 'no nonce provided';
841 0   0     0 my $proof = $args{proof} // die 'no proof provided';
842 0   0     0 my $header = $args{header} // die 'no header provided';
843              
844 0         0 $header = MIME::Base64::encode_base64($header, '');
845 0         0 $proof = MIME::Base64::encode_base64($proof, '');
846 0         0 my $data = "c=$header,r=$nonce,p=$proof";
847 0         0 return $self->build_message(
848             type => 'SASLResponse',
849             data => pack('A*', $data)
850             );
851             }
852              
853             =head2 frontend_query
854              
855             Simple query
856              
857             =cut
858              
859             sub frontend_query {
860 0     0 1 0 my ($self, %args) = @_;
861             return $self->build_message(
862             type => 'Query',
863 0         0 data => pack('Z*', Unicode::UTF8::encode_utf8($args{sql}))
864             );
865             }
866              
867             =head2 frontend_startup_message
868              
869             Initial mesage informing the server which database and user we want
870              
871             =cut
872              
873             sub frontend_startup_message {
874 1     1 1 6 my ($self, %args) = @_;
875 1 50       7 die "Not first message" unless $self->is_first_message;
876              
877 1 50       6 if($args{replication}) {
878 0         0 $args{replication} = 'database';
879 0         0 $args{database} = 'postgres';
880             } else {
881 1         4 delete $args{replication};
882             }
883 1         6 $log->tracef("Startup with %s", \%args);
884              
885 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         13  
  1         11  
  5         13  
886 1         4 $parameters .= "\0";
887              
888 1         9 return $self->build_message(
889             type => undef,
890             data => pack('N*', PROTOCOL_VERSION) . $parameters
891             );
892             }
893              
894             sub send_startup_request {
895 0     0 0 0 my ($self, %args) = @_;
896 0         0 $self->outgoing->emit($self->frontend_startup_message(%args));
897             }
898              
899             =head2 frontend_sync
900              
901             Synchonise after a prepared statement has finished execution.
902              
903             =cut
904              
905             sub frontend_sync {
906 0     0 1 0 my $self = shift;
907 0         0 return $self->build_message(
908             type => 'Sync',
909             data => '',
910             );
911             }
912              
913             =head2 frontend_terminate
914              
915              
916              
917             =cut
918              
919             sub frontend_terminate {
920 0     0 1 0 my $self = shift;
921 0         0 return $self->build_message(
922             type => 'Terminate',
923             data => '',
924             );
925             }
926              
927             =head2 is_authenticated
928              
929             Returns true if we are authenticated (and can start sending real data).
930              
931             =cut
932              
933 1 50   1 1 2368 sub is_authenticated { $_[0]->{authenticated} ? 1 : 0 }
934              
935             =head2 is_first_message
936              
937             Returns true if this is the first message, as per L:
938              
939             "For historical reasons, the very first message sent by the client (the startup message)
940             has no initial message-type byte."
941              
942             =cut
943              
944 3     3 1 21 sub is_first_message { $_[0]->{message_count} < 1 }
945              
946             =head2 send_message
947              
948             Send a message.
949              
950             =cut
951              
952             sub send_message {
953 0     0 1 0 my ($self, @args) = @_;
954              
955             # Clear the ready-to-send flag since we're about to throw a message over to the
956             # server and we don't want any others getting in the way.
957 0         0 $self->{is_ready} = 0;
958              
959 0         0 $log->tracef("Will send message with %s", \@args);
960 0 0       0 die "Empty message?" unless defined(my $msg = $self->message(@args));
961              
962             $log->tracef(
963             "send data: [%v02x] %s (%s)",
964             $msg,
965             (($self->is_first_message ? "startup packet" : $FRONTEND_MESSAGE_CODE{substr($msg, 0, 1)}) || 'unknown message'),
966 0 0 0     0 join('', '', map { (my $txt = defined($_) ? $_ : '') =~ tr/ []"'!#$%*&=:;A-Za-z0-9,()_ -/./c; $txt } split //, $msg)
  0 0       0  
  0         0  
967             ) if $log->is_debug;
968 0         0 $self->outgoing->emit($msg);
969 0         0 return $self;
970             }
971              
972 0   0 0 0 0 sub outgoing { shift->{outgoing} // die 'no outgoing source' }
973              
974             =head2 method_for_frontend_type
975              
976             Returns the method name for the given frontend type.
977              
978             =cut
979              
980             sub method_for_frontend_type {
981 2     2 1 6 my ($self, $type) = @_;
982 2         7 my $method = 'frontend' . $type;
983 2         5 for ($method) {
984 2         13 s{([A-Z0-9]+)([A-Z])}{"_" . lc($1) . "_" . lc($2)}ge;
  0         0  
985 2         13 s{([A-Z]+)}{"_" . lc($1)}ge;
  4         23  
986             }
987             # $method =~ s/([A-Z])/'_' . lc $1/ge;
988             $method
989 2         24 }
990              
991             =head2 is_known_frontend_message_type
992              
993             Returns true if the given frontend type is one that we know how to handle.
994              
995             =cut
996              
997             sub is_known_frontend_message_type {
998 1     1 1 5 my ($self, $type) = @_;
999 1 50       7 return 1 if exists $FRONTEND_MESSAGE_BUILDER{$type};
1000 1 50       9 return 1 if $self->can($self->method_for_frontend_type($type));
1001 0         0 return 0;
1002             }
1003              
1004             =head2 message
1005              
1006             Creates a new message of the given type.
1007              
1008             =cut
1009              
1010             sub message {
1011 1     1 1 6213 my ($self, $type, @args) = @_;
1012 1 50       7 die "Message $type unknown" unless $self->is_known_frontend_message_type($type);
1013              
1014 1   50     10 my $method = ($FRONTEND_MESSAGE_BUILDER{$type} || $self->can($self->method_for_frontend_type($type)) || die 'no method for ' . $type);
1015 1         10 $log->tracef("Method is %s", Sub::Identify::sub_name $method);
1016 1         27 my $msg = $self->$method(@args);
1017 1         3 ++$self->{message_count};
1018 1         9 return $msg;
1019             }
1020              
1021             =head2 handle_message
1022              
1023             Handle an incoming message from the server.
1024              
1025             =cut
1026              
1027             sub handle_message {
1028 1     1 1 29 my ($self, $msg) = @_;
1029              
1030             # Extract code and identify which message handler to use
1031 1         3 my $type = do {
1032 1         5 my $code = substr $msg, 0, 1;
1033 1 50       9 my $type = $BACKEND_MESSAGE_CODE{$code}
1034             or die 'unknown backend message code ' . $code;
1035 1         11 $log->tracef('Handle message of type %s (code %s)', $type, $code);
1036 1         6 $type
1037             };
1038              
1039             # Clear the ready-to-send flag until we've processed this
1040 1         4 $self->{is_ready} = 0;
1041 1         25 return ('Protocol::Database::PostgreSQL::Backend::' . $type)->new_from_message($msg);
1042             }
1043              
1044             sub ssl_request {
1045 0     0 1 0 my ($self) = @_;
1046             # Magic SSL code, see https://www.postgresql.org/docs/current/protocol-message-formats.html
1047 0         0 my $data = pack("n1n1", 1234, 5679);
1048 0         0 return pack 'Na*', 8, $data;
1049             }
1050              
1051             =head2 message_length
1052              
1053             Returns the length of the given message.
1054              
1055             =cut
1056              
1057             sub message_length {
1058 17     17 1 73295 my ($self, $msg) = @_;
1059 17 50       88 return undef unless length($msg) >= 5;
1060 17         85 (undef, my $len) = unpack('C1N1', substr($msg, 0, 5));
1061 17         94 return $len;
1062             }
1063              
1064             =head2 simple_query
1065              
1066             Send a simple query to the server - only supports plain queries (no bind parameters).
1067              
1068             =cut
1069              
1070             sub simple_query {
1071 0     0 1 0 my ($self, $sql) = @_;
1072              
1073 0         0 $log->tracef("Running query [%s]", $sql);
1074 0         0 $self->send_message('Query', sql => $sql);
1075 0         0 return $self;
1076             }
1077              
1078             =head2 copy_data
1079              
1080             Send copy data to the server.
1081              
1082             =cut
1083              
1084             sub copy_data {
1085 0     0 1 0 my $self = shift;
1086 0         0 my $data = shift;
1087 0 0       0 die "Invalid backend state" if $self->backend_state eq 'error';
1088              
1089 0         0 $self->send_message('CopyData', data => $data);
1090 0         0 return $self;
1091             }
1092              
1093             =head2 copy_done
1094              
1095             Indicate that the COPY data from the client is complete.
1096              
1097             =cut
1098              
1099             sub copy_done {
1100 0     0 1 0 my $self = shift;
1101 0         0 my $data = shift;
1102 0 0       0 die "Invalid backend state" if $self->backend_state eq 'error';
1103              
1104 0         0 $self->send_message('CopyDone');
1105 0         0 return $self;
1106             }
1107              
1108             =head2 backend_state
1109              
1110             Accessor for current backend state.
1111              
1112             =cut
1113              
1114             sub backend_state {
1115 0     0 1 0 my $self = shift;
1116 0 0       0 if(@_) {
1117 0         0 my $state = shift;
1118 0 0       0 die "bad state code" unless grep { $state eq $_ } qw(idle transaction error);
  0         0  
1119              
1120 0         0 $self->{backend_state} = $state;
1121 0         0 return $self;
1122             }
1123 0         0 return $self->{backend_state};
1124             }
1125              
1126             =head2 is_ready
1127              
1128             Returns true if we're ready to send more data to the server.
1129              
1130             =cut
1131              
1132             sub is_ready {
1133 0     0 1 0 my $self = shift;
1134 0 0       0 if(@_) {
1135 0         0 $self->{is_ready} = shift;
1136 0         0 return $self;
1137             }
1138 0 0       0 return 0 if $self->{wait_for_startup};
1139 0         0 return $self->{is_ready};
1140             }
1141              
1142             =head2 send_copy_data
1143              
1144             Send COPY data to the server. Takes an arrayref and replaces any reserved characters with quoted versions.
1145              
1146             =cut
1147              
1148             {
1149             my %_charmap = (
1150             "\\" => "\\\\",
1151             "\x08" => "\\b",
1152             "\x09" => "\\t",
1153             "\x0A" => "\\r",
1154             "\x0C" => "\\f",
1155             "\x0D" => "\\n",
1156             );
1157              
1158             sub send_copy_data {
1159 0     0 1 0 my ($self, $data) = @_;
1160             my $content = pack 'a*', (
1161             Unicode::UTF8::encode_utf8(
1162             join("\t", map {
1163 0 0       0 defined($_)
  0         0  
1164 0         0 ? s/([\\\x08\x09\x0A\x0C\x0D])/$_charmap{$1}/ger
1165             : '\N'
1166             } @$data) . "\n"
1167             )
1168             );
1169              
1170             $self->outgoing->emit(
1171 0         0 $MESSAGE_TYPE_FRONTEND{'CopyData'} . pack('N1', 4 + length $content) . $content
1172             );
1173 0         0 return $self;
1174             }
1175             }
1176              
1177             sub extract_message {
1178 0     0 0 0 my ($self, $buffref) = @_;
1179             # The smallest possible message is 5 bytes
1180 0 0       0 return undef unless length($$buffref) >= 5;
1181             # Don't start extracting until we know we have a full packet
1182 0         0 my ($code, $size) = unpack('C1N1', $$buffref);
1183 0 0       0 return undef unless length($$buffref) >= $size+1;
1184 0         0 return $self->handle_message(
1185             substr $$buffref, 0, $size+1, ''
1186             );
1187             }
1188              
1189             =head2 build_message
1190              
1191             Construct a new message.
1192              
1193             =cut
1194              
1195             sub build_message {
1196 3     3 1 1205 my $self = shift;
1197 3         12 my %args = @_;
1198              
1199             # Can be undef
1200 3 100       27 die "No type provided" unless exists $args{type};
1201 2 100       20 die "No data provided" unless exists $args{data};
1202              
1203             # Length includes the 4-byte length field, but not the type byte
1204 1         4 my $length = length($args{data}) + 4;
1205 1 50 0     13 return (defined($args{type}) ? ($MESSAGE_TYPE_FRONTEND{$args{type}} // die 'unknown message ' . $args{type}) : '') . pack('N1', $length) . $args{data};
1206             }
1207              
1208 0     0 0   sub state { $_[0]->{state} = $_[1] }
1209              
1210 0     0 0   sub current_state { shift->{state} }
1211              
1212             1;
1213              
1214             __END__