File Coverage

lib/DBD/PgPPSjis.pm
Criterion Covered Total %
statement 171 821 20.8
branch 0 286 0.0
condition 0 83 0.0
subroutine 57 178 32.0
pod 3 4 75.0
total 231 1372 16.8


line stmt bran cond sub pod time code
1             package DBD::PgPPSjis;
2 1     1   15574 use strict;
  1         2  
  1         24  
3              
4 1     1   4 use DBI;
  1         1  
  1         29  
5 1     1   4 use Carp ();
  1         2  
  1         10  
6 1     1   393 use IO::Socket ();
  1         15183  
  1         24  
7              
8 1     1   5 use vars qw($VERSION);
  1         2  
  1         2545  
9             $VERSION = '0.0801004';
10             $VERSION = $VERSION;
11             my $BUFFER_LEN = 1500;
12             my $DEBUG;
13              
14             my $sjis_leading_byte = '[\\x81-\\x9F\\xE0-\\xFC]';
15             my $sjis_trailing_byte = '[\\x00-\\xFF]';
16             my $sjis_mbcs = $sjis_leading_byte . $sjis_trailing_byte;
17              
18             my %BYTEA_DEMANGLE = (
19             '\\' => '\\',
20             map { sprintf('%03o', $_) => chr $_ } 0 .. 255,
21             );
22              
23             {
24             my $drh;
25             sub driver {
26 0     0 0   my ($class, $attr) = @_;
27 0   0       return $drh ||= DBI::_new_drh("$class\::dr", {
28             Name => 'PgPPSjis',
29             Version => $VERSION,
30             Err => \(my $err = 0),
31             Errstr => \(my $errstr = ''),
32             State => \(my $state = undef),
33             Attribution => 'DBD::PgPPSjis by Hiroyuki OYAMA and ShiftJIS support by INABA Hitoshi',
34             }, {});
35             }
36             }
37              
38 0     0 1   sub pgppsjis_server_identification { $_[0]->FETCH('pgppsjis_connection')->{server_identification} }
39 0     0 1   sub pgppsjis_server_version_num { $_[0]->FETCH('pgppsjis_connection')->{server_version_num} }
40 0     0 1   sub pgppsjis_server_version { $_[0]->FETCH('pgppsjis_connection')->{server_version} }
41              
42             sub _parse_dsn {
43 0     0     my ($class, $dsn, $args) = @_;
44              
45 0 0         return if !defined $dsn;
46              
47 0           my ($hash, $var, $val);
48 0           while (length $dsn) {
49 0 0         if ($dsn =~ /([^:;]*)[:;](.*)/) {
50 0           $val = $1;
51 0           $dsn = $2;
52             }
53             else {
54 0           $val = $dsn;
55 0           $dsn = '';
56             }
57 0 0         if ($val =~ /([^=]*)=(.*)/) {
58 0           $var = $1;
59 0           $val = $2;
60 0 0 0       if ($var eq 'hostname' || $var eq 'host') {
    0 0        
61 0           $hash->{'host'} = $val;
62             }
63             elsif ($var eq 'db' || $var eq 'dbname') {
64 0           $hash->{'database'} = $val;
65             }
66             else {
67 0           $hash->{$var} = $val;
68             }
69             }
70             else {
71 0           for $var (@$args) {
72 0 0         if (!defined($hash->{$var})) {
73 0           $hash->{$var} = $val;
74 0           last;
75             }
76             }
77             }
78             }
79 0           return $hash;
80             }
81              
82             sub _parse_dsn_host {
83 0     0     my ($class, $dsn) = @_;
84 0           my $hash = $class->_parse_dsn($dsn, ['host', 'port']);
85 0           return @$hash{qw};
86             }
87              
88              
89             package DBD::PgPPSjis::dr;
90              
91             $DBD::PgPPSjis::dr::imp_data_size =
92             $DBD::PgPPSjis::dr::imp_data_size = 0;
93              
94             sub connect {
95 0     0     my ($drh, $dsn, $user, $password, $attrhash) = @_;
96              
97 0           my $data_source_info
98             = DBD::PgPPSjis->_parse_dsn($dsn, ['database', 'host', 'port']);
99 0   0       $user ||= '';
100 0   0       $password ||= '';
101              
102 0           my $dbh = DBI::_new_dbh($drh, { Name => $dsn, USER => $user }, {});
103 0           eval {
104             my $pgsql = DBD::PgPPSjis::Protocol->new(
105             hostname => $data_source_info->{host},
106             port => $data_source_info->{port},
107             database => $data_source_info->{database},
108             user => $user,
109             password => $password,
110             debug => $data_source_info->{debug},
111             path => $data_source_info->{path},
112 0           );
113 0           $dbh->STORE(pgppsjis_connection => $pgsql);
114              
115             # DBD::PgPPSjis (1 of 6)
116              
117 0           $dbh->do(q{SET NAMES 'SJIS'});
118             };
119 0 0         if ($@) {
120 0           $dbh->DBI::set_err(1, $@);
121 0           return undef;
122             }
123              
124 0           return $dbh;
125             }
126              
127 0     0     sub data_sources { 'dbi:PgPPSjis:' }
128              
129       0     sub disconnect_all {}
130              
131              
132             package DBD::PgPPSjis::db;
133              
134             $DBD::PgPPSjis::db::imp_data_size =
135             $DBD::PgPPSjis::db::imp_data_size = 0;
136              
137             # We need to implement ->quote, because otherwise we get the default DBI
138             # one, which ignores backslashes. The DBD::Pg implementation doubles all
139             # backslashes and apostrophes; this version backslash-protects all of them.
140             # XXX: What about byte sequences that don't form valid characters in the
141             # relevant encoding?
142             # XXX: What about type-specific quoting?
143             sub quote {
144 0     0     my ($dbh, $s) = @_;
145              
146 0 0         if (!defined $s) {
147 0           return 'NULL';
148             }
149             else {
150             # In PostgreSQL versions before 8.1, plain old string literals are
151             # assumed to use backslash escaping. But that's incompatible with
152             # the SQL standard, which admits no special meaning for \ in a
153             # string literal, and requires the single-quote character to be
154             # doubled for inclusion in a literal. So PostgreSQL 8.1 introduces
155             # a new extension: an "escaped string" syntax E'...' which is
156             # unambiguously defined to support backslash sequences. The plan is
157             # apparently that some future version of PostgreSQL will change
158             # plain old literals to use the SQL-standard interpretation. So the
159             # only way I can quote reliably on both current versions and that
160             # hypothetical future version is to (a) always put backslashes in
161             # front of both single-quote and backslash, and (b) use the E'...'
162             # syntax if we know we're speaking to a version recent enough to
163             # support it.
164             #
165             # Also, it's best to always quote the value, even if it looks like a
166             # simple integer. Otherwise you can't compare the result of quoting
167             # Perl numeric zero to a boolean column. (You can't _reliably_
168             # compare a Perl scalar to a boolean column anyway, because there
169             # are six Postgres syntaxes for TRUE, and six for FALSE, and
170             # everything else is an error -- but that's another story, and at
171             # least if you quote '0' it looks false to Postgres. Sigh. I have
172             # some plans for a pure-Perl DBD which understands the 7.4 protocol,
173             # and can therefore fix up bools in _both_ directions.)
174              
175             # DBD::PgPPSjis (2 of 6)
176              
177 0           my @char = ();
178 0           while ($s =~ /\G ( $sjis_mbcs | [\x00-\xFF] )/gsx) {
179             push @char,
180             {
181             # escape "C-style backslash escape sequences"
182             # to include a backslash character, write two backslashes (\\)
183             qq(\\) => q(\\\\),
184              
185             # a single quote can be included in an escape string by writing ('')
186             qq(') => q(''), # works even if backslash_quote is off
187              
188             # the character with the code zero cannot be in a string constant
189             qq(\0) => q(\\0),
190              
191 0   0       }->{$1} || $1;
192             }
193 0           $s = join '', @char;
194              
195             # works even if standard_conforming_strings is any of on, off, and undef
196 0           my $version = $dbh->FETCH('pgppsjis_connection')->{server_version_num};
197 0 0         return $version >= 80100 ? "E'$s'" : "'$s'";
198             }
199             }
200              
201             sub prepare {
202 0     0     my ($dbh, $statement, @attribs) = @_;
203              
204 0 0         die 'PostgreSQL does not accept queries containing \0 bytes'
205             if $statement =~ /\0/;
206              
207 0           my $pgsql = $dbh->FETCH('pgppsjis_connection');
208 0           my $parsed = $pgsql->parse_statement($statement);
209              
210 0           my $sth = DBI::_new_sth($dbh, { Statement => $statement });
211 0           $sth->STORE(pgppsjis_parsed_stmt => $parsed);
212 0           $sth->STORE(pgppsjis_handle => $pgsql);
213 0           $sth->STORE(pgppsjis_params => []);
214 0           $sth->STORE(NUM_OF_PARAMS => scalar grep { ref } @$parsed);
  0            
215 0           $sth;
216             }
217              
218             sub commit {
219 0     0     my ($dbh) = @_;
220              
221 0           my $pgsql = $dbh->FETCH('pgppsjis_connection');
222 0           eval {
223 0           my $pgsth = $pgsql->prepare('COMMIT');
224 0           $pgsth->execute;
225             };
226 0 0         if ($@) {
227 0           $dbh->DBI::set_err(1, $@); # $pgsql->get_error_message ???
228 0           return undef;
229             }
230 0           return 1;
231             }
232              
233             sub rollback {
234 0     0     my ($dbh) = @_;
235 0           my $pgsql = $dbh->FETCH('pgppsjis_connection');
236 0           eval {
237 0           my $pgsth = $pgsql->prepare('ROLLBACK');
238 0           $pgsth->execute;
239             };
240 0 0         if ($@) {
241 0           $dbh->DBI::set_err(1, $@); # $pgsql->get_error_message ???
242 0           return undef;
243             }
244 0           return 1;
245             }
246              
247             sub disconnect {
248 0     0     my ($dbh) = @_;
249              
250 0 0         if (my $conn = $dbh->FETCH('pgppsjis_connection')) {
251 0           $conn->close;
252 0           $dbh->STORE('pgppsjis_connection', undef);
253             }
254              
255 0           return 1;
256             }
257              
258             sub FETCH {
259 0     0     my ($dbh, $key) = @_;
260              
261 0 0         return $dbh->{$key} if $key =~ /^pgppsjis_/;
262 0 0         return $dbh->{AutoCommit} if $key eq 'AutoCommit';
263 0           return $dbh->SUPER::FETCH($key);
264             }
265              
266             sub STORE {
267 0     0     my ($dbh, $key, $new) = @_;
268              
269 0 0         if ($key eq 'AutoCommit') {
270 0           my $old = $dbh->{$key};
271 0           my $never_set = !$dbh->{pgppsjis_ever_set_autocommit};
272              
273             # This logic is stolen from DBD::Pg
274 0 0 0       if (!$old && $new && $never_set) {
    0 0        
    0 0        
      0        
      0        
      0        
      0        
275             # Do nothing; fall through
276             }
277             elsif (!$old && $new) {
278             # Turning it on: commit
279             # XXX: Avoid this if no uncommitted changes.
280             # XXX: Desirable? See dbi-dev archives.
281             # XXX: Handle errors.
282 0           my $st = $dbh->{pgppsjis_connection}->prepare('COMMIT');
283 0           $st->execute;
284             }
285             elsif ($old && !$new || !$old && !$new && $never_set) {
286             # Turning it off, or initializing it to off at
287             # connection time: begin a new transaction
288             # XXX: Handle errors.
289 0           my $st = $dbh->{pgppsjis_connection}->prepare('BEGIN');
290 0           $st->execute;
291             }
292              
293 0           $dbh->{pgppsjis_ever_set_autocommit} = 1;
294 0           $dbh->{$key} = $new;
295              
296 0           return 1;
297             }
298              
299 0 0         if ($key =~ /^pgppsjis_/) {
300 0           $dbh->{$key} = $new;
301 0           return 1;
302             }
303              
304 0           return $dbh->SUPER::STORE($key, $new);
305             }
306              
307             sub last_insert_id {
308 0     0     my ($db, undef, $schema, $table, undef, $attr) = @_;
309             # DBI uses (catalog,schema,table,column), but we don't make use of
310             # catalog or column, so don't bother storing them.
311              
312 0           my $pgsql = $db->FETCH('pgppsjis_connection');
313              
314 0 0 0       if (!defined $attr) {
    0          
    0          
315 0           $attr = {};
316             }
317             elsif (!ref $attr && $attr ne '') {
318             # If not a hash, assume it is a sequence name
319 0           $attr = { sequence => $attr };
320             }
321             elsif (ref $attr ne 'HASH') {
322 0           return $db->set_err(1, "last_insert_id attrs must be a hashref");
323             }
324              
325             # Catalog and col are not used
326 0 0         $schema = '' if !defined $schema;
327 0 0         $table = '' if !defined $table;
328              
329             # Cache all of our table lookups? Default is yes
330 0 0         my $use_cache = exists $attr->{pgppsjis_cache} ? $attr->{pgppsjis_cache} : 1;
331              
332             # Cache key. Note we must distinguish ("a.b", "c") from ("a", "b.c")
333             # (and XXX: we ought really to have tests for that)
334 0           my $cache_key = join '.', map { quotemeta } $schema, $table;
  0            
335              
336 0           my $sequence;
337 0 0 0       if (defined $attr->{sequence}) {
    0          
338             # Named sequence overrides any table or schema settings
339 0           $sequence = $attr->{sequence};
340             }
341             elsif ($use_cache && exists $db->{pgppsjis_liicache}{$cache_key}) {
342 0           $sequence = $db->{pgppsjis_liicache}{$cache_key};
343             }
344             else {
345             # At this point, we must have a valid table name
346 0 0         return $db->set_err(1, "last_insert_id needs a sequence or table name")
347             if $table eq '';
348              
349 0           my @args = $table;
350              
351             # Only 7.3 and up can use schemas
352 0           my $pg_catalog;
353 0 0         if ($pgsql->{server_version_num} < 70300) {
354 0           $schema = '';
355 0           $pg_catalog = '';
356             }
357             else {
358 0           $pg_catalog = 'pg_catalog.';
359             }
360              
361             # Make sure the table in question exists and grab its oid
362 0           my ($schemajoin, $schemawhere) = ('','');
363 0 0         if (length $schema) {
364 0           $schemajoin =
365             ' JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace';
366 0           $schemawhere = ' AND n.nspname = ?';
367 0           push @args, $schema;
368             }
369              
370 0           my $st = $db->prepare(qq[
371             SELECT c.oid FROM ${pg_catalog}pg_class c $schemajoin
372             WHERE relname = ? $schemawhere
373             ]);
374 0           my $count = $st->execute(@args);
375 0 0         if (!defined $count) {
376 0           $st->finish;
377 0           my $message = qq{Could not find the table "$table"};
378 0 0         $message .= qq{ in the schema "$schema"} if $schema ne '';
379 0           return $db->set_err(1, $message);
380             }
381 0           my $oid = $st->fetchall_arrayref->[0][0];
382             # This table has a primary key. Is there a sequence associated with
383             # it via a unique, indexed column?
384 0           $st = $db->prepare(qq[
385             SELECT a.attname, i.indisprimary, substring(d.adsrc for 128) AS def
386             FROM ${pg_catalog}pg_index i
387             JOIN ${pg_catalog}pg_attribute a ON a.attrelid = i.indrelid
388             AND a.attnum = i.indkey[0]
389             JOIN ${pg_catalog}pg_attrdef d ON d.adrelid = a.attrelid
390             AND d.adnum = a.attnum
391             WHERE i.indrelid = $oid
392             AND a.attrelid = $oid
393             AND i.indisunique IS TRUE
394             AND a.atthasdef IS TRUE
395             AND d.adsrc ~ '^nextval'
396             ]);
397 0           $count = $st->execute;
398 0 0         if (!defined $count) {
399 0           $st->finish;
400 0           return $db->set_err(1, qq{No suitable column found for last_insert_id of table "$table"});
401             }
402 0           my $info = $st->fetchall_arrayref;
403              
404             # We have at least one with a default value. See if we can determine
405             # sequences
406 0           my @def;
407 0           for (@$info) {
408 0 0         my ($seq) = $_->[2] =~ /^nextval\('([^']+)'::/ or next;
409 0           push @def, [@$_, $seq];
410             }
411              
412 0 0         return $db->set_err(1, qq{No suitable column found for last_insert_id of table "$table"\n})
413             if !@def;
414              
415             # Tiebreaker goes to the primary keys
416 0 0         if (@def > 1) {
417 0           my @pri = grep { $_->[1] } @def;
  0            
418 0 0         return $db->set_err(1, qq{No suitable column found for last_insert_id of table "$table"\n})
419             if @pri != 1;
420 0           @def = @pri;
421             }
422              
423 0           $sequence = $def[0][3];
424              
425             # Cache this information for subsequent calls
426 0           $db->{pgppsjis_liicache}{$cache_key} = $sequence;
427             }
428              
429 0           my $st = $db->prepare("SELECT currval(?)");
430 0           $st->execute($sequence);
431 0           return $st->fetchall_arrayref->[0][0];
432             }
433              
434             sub DESTROY {
435 0     0     my ($dbh) = @_;
436 0           $dbh->disconnect;
437             }
438              
439             package DBD::PgPPSjis::st;
440              
441             $DBD::PgPPSjis::st::imp_data_size =
442             $DBD::PgPPSjis::st::imp_data_size = 0;
443              
444             sub bind_param {
445 0     0     my ($sth, $index, $value, $attr) = @_;
446 0 0         my $type = ref($attr) ? $attr->{TYPE} : $attr;
447 0           my $dbh = $sth->{Database};
448 0           my $params = $sth->FETCH('pgppsjis_params');
449 0           $params->[$index - 1] = $value;
450             }
451              
452             sub execute {
453 0     0     my ($sth, @args) = @_;
454              
455 0           my $pgsql = $sth->FETCH('pgppsjis_handle');
456 0 0         die "execute on disconnected database" if $pgsql->{closed};
457              
458 0           my $num_params = $sth->FETCH('NUM_OF_PARAMS');
459              
460 0 0         if (@args) {
461 0 0         return $sth->set_err(1, "Wrong number of arguments")
462             if @args != $num_params;
463             }
464             else {
465 0           my $bind_params = $sth->FETCH('pgppsjis_params');
466 0 0         return $sth->set_err(1, "Wrong number of bound parameters")
467             if @$bind_params != $num_params;
468              
469             # They've already been quoted by ->bind_param
470 0           @args = @$bind_params;
471             }
472              
473 0           my $parsed_statement = $sth->FETCH('pgppsjis_parsed_stmt');
474 0           my $dbh = $sth->{Database};
475 0 0         my $statement = join '', map { ref() ? $dbh->quote($args[$$_]) : $_ } @$parsed_statement;
  0            
476              
477 0           my $result;
478 0           eval {
479 0           $sth->{pgppsjis_record_iterator} = undef;
480 0           my $pgsql_sth = $pgsql->prepare($statement);
481 0           $pgsql_sth->execute;
482 0           $sth->{pgppsjis_record_iterator} = $pgsql_sth;
483 0           my $dbh = $sth->{Database};
484              
485 0 0         if (defined $pgsql_sth->{affected_rows}) {
486 0           $sth->{pgppsjis_rows} = $pgsql_sth->{affected_rows};
487 0           $result = $pgsql_sth->{affected_rows};
488             }
489             else {
490 0           $sth->{pgppsjis_rows} = 0;
491 0           $result = $pgsql_sth->{affected_rows};
492             }
493 0 0         if (!$pgsql_sth->{row_description}) {
494 0           $sth->STORE(NUM_OF_FIELDS => 0);
495 0           $sth->STORE(NAME => []);
496             }
497             else {
498 0           $sth->STORE(NUM_OF_FIELDS => scalar @{$pgsql_sth->{row_description}});
  0            
499 0           $sth->STORE(NAME => [ map {$_->{name}} @{$pgsql_sth->{row_description}} ]);
  0            
  0            
500             }
501             };
502 0 0         if ($@) {
503 0           $sth->DBI::set_err(1, $@);
504 0           return undef;
505             }
506              
507 0 0         return $pgsql->has_error ? undef
    0          
508             : $result ? $result
509             : '0E0';
510             }
511              
512             sub fetch {
513 0     0     my ($sth) = @_;
514              
515 0           my $iterator = $sth->FETCH('pgppsjis_record_iterator');
516 0 0         return undef if $iterator->{finished};
517              
518 0 0         if (my $row = $iterator->fetch) {
519 0 0         if ($sth->FETCH('ChopBlanks')) {
520 0           s/\s+\z// for @$row;
521             }
522 0           return $sth->_set_fbav($row);
523             }
524              
525 0           $iterator->{finished} = 1;
526 0           return undef;
527             }
528             *fetchrow_arrayref =
529             *fetchrow_arrayref = \&fetch;
530              
531             sub rows {
532 0     0     my ($sth) = @_;
533 0 0         return defined $sth->{pgppsjis_rows} ? $sth->{pgppsjis_rows} : 0;
534             }
535              
536             sub FETCH {
537 0     0     my ($dbh, $key) = @_;
538              
539             # return $dbh->{AutoCommit} if $key eq 'AutoCommit';
540 0 0         return $dbh->{NAME} if $key eq 'NAME';
541 0 0         return $dbh->{$key} if $key =~ /^pgppsjis_/;
542 0           return $dbh->SUPER::FETCH($key);
543             }
544              
545             sub STORE {
546 0     0     my ($sth, $key, $value) = @_;
547              
548 0 0         if ($key eq 'NAME') {
    0          
    0          
549 0           $sth->{NAME} = $value;
550 0           return 1;
551             }
552             elsif ($key =~ /^pgppsjis_/) {
553 0           $sth->{$key} = $value;
554 0           return 1;
555             }
556             elsif ($key eq 'NUM_OF_FIELDS') {
557             # Don't set this twice; DBI doesn't seem to like it.
558             # XXX: why not? Perhaps this conceals a PgPPSjis bug.
559 0           my $curr = $sth->FETCH($key);
560 0 0 0       return 1 if $curr && $curr == $value;
561             }
562 0           return $sth->SUPER::STORE($key, $value);
563             }
564              
565 0     0     sub DESTROY { return }
566              
567              
568             package DBD::PgPPSjis::Protocol;
569              
570 1     1   7 use constant DEFAULT_UNIX_SOCKET => '/tmp';
  1         2  
  1         76  
571 1     1   6 use constant DEFAULT_PORT_NUMBER => 5432;
  1         2  
  1         48  
572 1     1   5 use constant DEFAULT_TIMEOUT => 60;
  1         3  
  1         43  
573              
574 1     1   10 use constant AUTH_OK => 0;
  1         3  
  1         39  
575 1     1   5 use constant AUTH_KERBEROS_V4 => 1;
  1         1  
  1         41  
576 1     1   6 use constant AUTH_KERBEROS_V5 => 2;
  1         1  
  1         35  
577 1     1   5 use constant AUTH_CLEARTEXT_PASSWORD => 3;
  1         1  
  1         50  
578 1     1   5 use constant AUTH_CRYPT_PASSWORD => 4;
  1         1  
  1         44  
579 1     1   5 use constant AUTH_MD5_PASSWORD => 5;
  1         2  
  1         42  
580 1     1   5 use constant AUTH_SCM_CREDENTIAL => 6;
  1         2  
  1         1913  
581              
582             sub new {
583 0     0     my ($class, %args) = @_;
584              
585             my $self = bless {
586             hostname => $args{hostname},
587             path => $args{path} || DEFAULT_UNIX_SOCKET,
588             port => $args{port} || DEFAULT_PORT_NUMBER,
589             database => $args{database} || $ENV{USER} || '',
590             user => $args{user} || $ENV{USER} || '',
591             password => $args{password} || '',
592             args => $args{args} || '',
593             tty => $args{tty} || '',
594 0   0       timeout => $args{timeout} || DEFAULT_TIMEOUT,
      0        
      0        
      0        
      0        
      0        
      0        
      0        
595             'socket' => undef,
596             backend_pid => '',
597             secret_key => '',
598             selected_record => undef,
599             error_message => '',
600             last_oid => undef,
601             server_identification => '',
602             server_version => '0.0.0',
603             server_version_num => 0,
604             }, $class;
605 0 0         $DEBUG = 1 if $args{debug};
606 0           $self->_initialize;
607 0           return $self;
608             }
609              
610             sub close {
611 0     0     my ($self) = @_;
612 0 0         my $socket = $self->{'socket'} or return;
613 0 0         return if !fileno $socket;
614              
615 0           my $terminate_packet = 'X' . pack 'N', 5;
616 0 0         print " ==> Terminate\n" if $DEBUG;
617 0           _dump_packet($terminate_packet);
618 0           $socket->send($terminate_packet, 0);
619 0           $socket->close;
620 0           $self->{closed} = 1;
621             }
622              
623             sub DESTROY {
624 0     0     my ($self) = @_;
625 0 0         $self->close if $self;
626             }
627              
628             sub _initialize {
629 0     0     my ($self) = @_;
630 0           $self->_connect;
631 0           $self->_do_startup;
632 0           $self->_find_server_version;
633             }
634              
635             sub _connect {
636 0     0     my ($self) = @_;
637              
638 0           my $sock;
639 0 0         if ($self->{hostname}) {
640             $sock = IO::Socket::INET->new(
641             PeerAddr => $self->{hostname},
642             PeerPort => $self->{port},
643             Proto => 'tcp',
644             Timeout => $self->{timeout},
645 0 0         ) or Carp::croak("Couldn't connect to $self->{hostname}:$self->{port}/tcp: $!");
646             }
647             else {
648 0           (my $path = $self->{path}) =~ s{/*\z}{/.s.PGSQL.$self->{port}};
649 0 0         $sock = IO::Socket::UNIX->new(
650             Type => IO::Socket::SOCK_STREAM,
651             Peer => $path,
652             ) or Carp::croak("Couldn't connect to $path: $!");
653             }
654 0           $sock->autoflush(1);
655 0           $self->{socket} = $sock;
656             }
657              
658 0     0     sub get_handle { $_[0]{socket} }
659              
660             sub _do_startup {
661 0     0     my ($self) = @_;
662              
663             # create message body
664             my $packet = pack 'n n a64 a32 a64 a64 a64', (
665             2, # Protocol major version - Int16bit
666             0, # Protocol minor version - Int16bit
667             $self->{database}, # Database name - LimString64
668             $self->{user}, # User name - LimString32
669             $self->{args}, # Command line args - LimString64
670             '', # Unused - LimString64
671             $self->{tty} # Debugging msg tty - LimString64
672 0           );
673              
674             # add packet length
675 0           $packet = pack('N', length($packet) + 4). $packet;
676              
677 0 0         print " ==> StartupPacket\n" if $DEBUG;
678 0           _dump_packet($packet);
679 0           $self->{socket}->send($packet, 0);
680 0           $self->_do_authentication;
681             }
682              
683             sub _find_server_version {
684 0     0     my ($self) = @_;
685 0           eval {
686             # If this function doesn't exist (as was the case in PostgreSQL 7.1
687             # and earlier), we'll end up leaving the version as 0.0.0. I can
688             # live with that.
689 0           my $st = $self->prepare(q[SELECT version()]);
690 0           $st->execute;
691 0           my $data = $st->fetch;
692 0           1 while $st->fetch;
693 0           my $id = $data->[0];
694 0           $self->{server_identification} = $id;
695 0 0         if (my ($ver) = $id =~ /\A PostgreSQL \s+ ([0-9._]+) (?:\s|\z)/x) {
696 0           $self->{server_version} = $ver;
697 0 0         if (my ($maj, $min, $sub)
698             = $ver =~ /\A ([0-9]+)\.([0-9]{1,2})\.([0-9]{1,2}) \z/x) {
699 0           $self->{server_version_num} = ($maj * 100 + $min) * 100 + $sub;
700             }
701             }
702             };
703             }
704              
705             sub _dump_packet {
706 0 0 0 0     return unless $DBD::PgPPSjis::Protocol::DEBUG or
707             $DBD::PgPPSjis::Protocol::DEBUG;
708              
709 0           my ($packet) = @_;
710              
711 0           printf "%s()\n", (caller 1)[3];
712              
713             # DBD::PgPPSjis (3 of 6)
714              
715 0           while ($packet =~ m/([\x00-\xFF]{1,16})/g) {
716 0           my $chunk = $1;
717 0           print join ' ', map { sprintf '%02X', ord $_ } split //, $chunk;
  0            
718 0           print ' ' x (16 - length $chunk);
719 0           print ' ';
720             print join '',
721 0 0         map { sprintf '%s', (/[\x20-\x7F]/) ? $_ : '.' } split //, $chunk;
  0            
722 0           print "\n";
723             }
724             }
725              
726             sub get_stream {
727 0     0     my ($self) = @_;
728             $self->{stream} = DBD::PgPPSjis::PacketStream->new($self->{'socket'})
729 0 0         if !defined $self->{stream};
730 0           return $self->{stream};
731             }
732              
733             sub _do_authentication {
734 0     0     my ($self) = @_;
735 0           my $stream = $self->get_stream;
736 0           while (1) {
737 0           my $packet = $stream->each;
738 0 0         last if $packet->is_end_of_response;
739 0 0         Carp::croak($packet->get_message) if $packet->is_error;
740 0           $packet->compute($self);
741             }
742             }
743              
744             sub prepare {
745 0     0     my ($self, $sql) = @_;
746              
747 0           $self->{error_message} = '';
748 0           return DBD::PgPPSjis::ProtocolStatement->new($self, $sql);
749             }
750              
751             sub has_error {
752 0     0     my ($self) = @_;
753 0 0         return 1 if $self->{error_message};
754             }
755              
756             sub get_error_message {
757 0     0     my ($self) = @_;
758 0           return $self->{error_message};
759             }
760              
761             # DBD::PgPPSjis (4 of 6)
762              
763             sub parse_statement {
764 0     0     my ($invocant, $statement) = @_;
765              
766 0           my $param_num = 0;
767 0           my $comment_depth = 0;
768 0           my @tokens = ('');
769              
770 0           Parse: for ($statement) {
771              
772             # Observe the default action at the end
773 0 0 0       if (m{\G \z}xmsgc) {
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
774 0           last Parse;
775             }
776              
777             # C-style block comments nest, as specified in the SQL standard but unlike C
778             elsif (m{\G( /\* (?:$sjis_mbcs|[\x00-\xFF])*? ) (?= /\* | \*/ ) }xmsgc) {
779 0           $comment_depth++;
780             }
781             elsif ($comment_depth && m{\G( (?:$sjis_mbcs|[\x00-\xFF])*? ) (?= /\* | \*/ )}xmsgc) {
782             }
783             elsif ($comment_depth && m{\G( \*/ )}xmsgc) {
784 0           $comment_depth--;
785             }
786              
787             # string constants with C-style escapes
788             elsif (m{\G( ' (?: \\\\ | '' | \\' | \\0 | \\b | \\f | \\n | \\r | \\t | $sjis_mbcs | [^'] )* ' )}xmsgc) {
789             }
790             elsif (m{\G( ' )}xmsgc) {
791             }
792             elsif (m{\G( \\ )}xmsgc) {
793             }
794              
795             # quoted identifiers can contain any character, except the character with code zero
796             # to include a double quote, write two double quotes
797             elsif (m{\G( " (?: "" | $sjis_leading_byte[^\x00] | [^\x00"] )* " )}xmsgc) {
798             }
799             elsif (m{\G( " )}xmsgc) {
800             }
801              
802             # standard SQL comment
803             elsif (m{\G( -- (?:$sjis_mbcs|[^\n])* )}xmsgc) {
804             }
805              
806             # placeholder
807             elsif (m{\G \?}xmsgc) {
808 0 0         pop @tokens if $tokens[-1] eq '';
809 0           push @tokens, \(my $tmp = $param_num++), '';
810 0           redo Parse;
811             }
812              
813             # key words, numeric constants, etc
814             ### elsif (m{\G( \s+ | \w+ | ::? | \$[0-9]+ | [-/*\$] | [^[:ascii:]]+ | [\0-\037\177]+ )}xmsgc) {
815             elsif (m{\G( [\t\n\f\r\x20]+ | [_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0-9]+ | ::? | \$[0-9]+ | [-/*\$] | (?:$sjis_mbcs|[\x80\xA0-\xDF\xFD-\xFF])+ | [\0-\037\177]+ )}xmsgc) {
816             }
817              
818             # operators are + - * / < > = ~ ! @ # % ^ & | ` ?
819             # special characters are $ ( ) [ ] , ; : * .
820             elsif (m{\G( [+<>=~!\@\#%^&|`,;.()\[\]{}]+ )}xmsgc) {
821             }
822              
823             # panic
824             else {
825 0           die qq{BUG: Unknown cause syntax error occurs at @{[pos]}\n$statement\n};
  0            
826             }
827              
828 0           $tokens[-1] .= $1;
829 0           redo Parse;
830             }
831              
832 0 0 0       pop @tokens if @tokens > 1 && $tokens[-1] eq '';
833              
834 0           return \@tokens;
835             }
836              
837              
838             package DBD::PgPPSjis::ProtocolStatement;
839              
840             sub new {
841 0     0     my ($class, $pgsql, $statement) = @_;
842 0           bless {
843             postgres => $pgsql,
844             statement => $statement,
845             rows => [],
846             }, $class;
847             }
848              
849             sub execute {
850 0     0     my ($self) = @_;
851              
852 0           my $pgsql = $self->{postgres};
853 0           my $handle = $pgsql->get_handle;
854              
855 0           my $query_packet = "Q$self->{statement}\0";
856 0 0         print " ==> Query\n" if $DEBUG;
857 0           DBD::PgPPSjis::Protocol::_dump_packet($query_packet);
858 0           $handle->send($query_packet, 0);
859 0           $self->{affected_rows} = 0;
860 0           $self->{last_oid} = undef;
861 0           $self->{rows} = [];
862              
863 0           my $stream = $pgsql->get_stream;
864 0           my $packet = $stream->each;
865 0 0         if ($packet->is_error) {
    0          
    0          
866 0           $self->_to_end_of_response($stream);
867 0           die $packet->get_message;
868             }
869             elsif ($packet->is_end_of_response) {
870 0           return;
871             }
872             elsif ($packet->is_empty) {
873 0           $self->_to_end_of_response($stream);
874 0           return;
875             }
876 0           while ($packet->is_notice_response) {
877             # XXX: discard it for now
878 0           $packet = $stream->each;
879             }
880 0 0         if ($packet->is_cursor_response) {
881 0           $packet->compute($pgsql);
882 0           my $row_info = $stream->each; # fetch RowDescription
883 0 0         if ($row_info->is_error) {
884 0           $self->_to_end_of_response($stream);
885 0           Carp::croak($row_info->get_message);
886             }
887 0           $row_info->compute($self);
888 0           while (1) {
889 0           my $row_packet = $stream->each;
890 0 0         if ($row_packet->is_error) {
891 0           $self->_to_end_of_response($stream);
892 0           Carp::croak($row_packet->get_message);
893             }
894 0           $row_packet->compute($self);
895 0           push @{ $self->{rows} }, $row_packet->get_result;
  0            
896 0 0         last if $row_packet->is_end_of_response;
897             }
898 0           return;
899             }
900             else { # CompletedResponse
901 0           $packet->compute($self);
902 0           while (1) {
903 0           my $end = $stream->each;
904 0 0         if ($end->is_error) {
905 0           $self->_to_end_of_response($stream);
906 0           Carp::croak($end->get_message);
907             }
908 0 0         last if $end->is_end_of_response;
909             }
910 0           return;
911             }
912             }
913              
914             sub _to_end_of_response {
915 0     0     my ($self, $stream) = @_;
916              
917 0           while (1) {
918 0           my $packet = $stream->each;
919 0           $packet->compute($self);
920 0 0         last if $packet->is_end_of_response;
921             }
922             }
923              
924             sub fetch {
925 0     0     my ($self) = @_;
926 0           return shift @{ $self->{rows} }; # shift returns undef if empty
  0            
927             }
928              
929              
930             package DBD::PgPPSjis::PacketStream;
931              
932             # Message Identifiers
933 1     1   7 use constant ASCII_ROW => 'D';
  1         17  
  1         52  
934 1     1   6 use constant AUTHENTICATION => 'R';
  1         1  
  1         45  
935 1     1   5 use constant BACKEND_KEY_DATA => 'K';
  1         2  
  1         41  
936 1     1   5 use constant BINARY_ROW => 'B';
  1         1  
  1         35  
937 1     1   4 use constant COMPLETED_RESPONSE => 'C';
  1         1  
  1         51  
938 1     1   5 use constant COPY_IN_RESPONSE => 'G';
  1         1  
  1         42  
939 1     1   5 use constant COPY_OUT_RESPONSE => 'H';
  1         1  
  1         35  
940 1     1   4 use constant CURSOR_RESPONSE => 'P';
  1         2  
  1         43  
941 1     1   6 use constant EMPTY_QUERY_RESPONSE => 'I';
  1         1  
  1         48  
942 1     1   6 use constant ERROR_RESPONSE => 'E';
  1         6  
  1         43  
943 1     1   5 use constant FUNCTION_RESPONSE => 'V';
  1         2  
  1         40  
944 1     1   5 use constant NOTICE_RESPONSE => 'N';
  1         1  
  1         33  
945 1     1   4 use constant NOTIFICATION_RESPONSE => 'A';
  1         1  
  1         49  
946 1     1   4 use constant READY_FOR_QUERY => 'Z';
  1         2  
  1         39  
947 1     1   5 use constant ROW_DESCRIPTION => 'T';
  1         2  
  1         34  
948              
949             # Authentication Message specifiers
950 1     1   4 use constant AUTHENTICATION_OK => 0;
  1         1  
  1         37  
951 1     1   5 use constant AUTHENTICATION_KERBEROS_V4 => 1;
  1         1  
  1         33  
952 1     1   5 use constant AUTHENTICATION_KERBEROS_V5 => 2;
  1         1  
  1         40  
953 1     1   4 use constant AUTHENTICATION_CLEARTEXT_PASSWORD => 3;
  1         2  
  1         38  
954 1     1   4 use constant AUTHENTICATION_CRYPT_PASSWORD => 4;
  1         2  
  1         33  
955 1     1   3 use constant AUTHENTICATION_MD5_PASSWORD => 5;
  1         2  
  1         48  
956 1     1   5 use constant AUTHENTICATION_SCM_CREDENTIAL => 6;
  1         2  
  1         1197  
957              
958             sub new {
959 0     0     my ($class, $handle) = @_;
960 0           bless {
961             handle => $handle,
962             buffer => '',
963             }, $class;
964             }
965              
966             sub set_buffer {
967 0     0     my ($self, $buffer) = @_;
968 0           $self->{buffer} = $buffer;
969             }
970              
971 0     0     sub get_buffer { $_[0]{buffer} }
972              
973             sub each {
974 0     0     my ($self) = @_;
975 0           my $type = $self->_get_byte;
976             # XXX: This would perhaps be better as a dispatch table
977 0 0         my $p = $type eq ASCII_ROW ? $self->_each_ascii_row
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
978             : $type eq AUTHENTICATION ? $self->_each_authentication
979             : $type eq BACKEND_KEY_DATA ? $self->_each_backend_key_data
980             : $type eq BINARY_ROW ? $self->_each_binary_row
981             : $type eq COMPLETED_RESPONSE ? $self->_each_completed_response
982             : $type eq COPY_IN_RESPONSE ? $self->_each_copy_in_response
983             : $type eq COPY_OUT_RESPONSE ? $self->_each_copy_out_response
984             : $type eq CURSOR_RESPONSE ? $self->_each_cursor_response
985             : $type eq EMPTY_QUERY_RESPONSE ? $self->_each_empty_query_response
986             : $type eq ERROR_RESPONSE ? $self->_each_error_response
987             : $type eq FUNCTION_RESPONSE ? $self->_each_function_response
988             : $type eq NOTICE_RESPONSE ? $self->_each_notice_response
989             : $type eq NOTIFICATION_RESPONSE ? $self->_each_notification_response
990             : $type eq READY_FOR_QUERY ? $self->_each_ready_for_query
991             : $type eq ROW_DESCRIPTION ? $self->_each_row_description
992             : Carp::croak("Unknown message type: '$type'");
993 0 0         if ($DEBUG) {
994 0           (my $type = ref $p) =~ s/.*:://;
995 0           print "<== $type\n";
996             }
997 0           return $p;
998             }
999              
1000             sub _each_authentication {
1001 0     0     my ($self) = @_;
1002              
1003 0           my $code = $self->_get_int32;
1004 0 0         if ($code == AUTHENTICATION_OK) {
    0          
    0          
    0          
    0          
    0          
    0          
1005 0           return DBD::PgPPSjis::AuthenticationOk->new;
1006             }
1007             elsif ($code == AUTHENTICATION_KERBEROS_V4) {
1008 0           return DBD::PgPPSjis::AuthenticationKerberosV4->new;
1009             }
1010             elsif ($code == AUTHENTICATION_KERBEROS_V5) {
1011 0           return DBD::PgPPSjis::AuthenticationKerberosV5->new;
1012             }
1013             elsif ($code == AUTHENTICATION_CLEARTEXT_PASSWORD) {
1014 0           return DBD::PgPPSjis::AuthenticationCleartextPassword->new;
1015             }
1016             elsif ($code == AUTHENTICATION_CRYPT_PASSWORD) {
1017 0           my $salt = $self->_get_byte(2);
1018 0           return DBD::PgPPSjis::AuthenticationCryptPassword->new($salt);
1019             }
1020             elsif ($code == AUTHENTICATION_MD5_PASSWORD) {
1021 0           my $salt = $self->_get_byte(4);
1022 0           return DBD::PgPPSjis::AuthenticationMD5Password->new($salt);
1023             }
1024             elsif ($code == AUTHENTICATION_SCM_CREDENTIAL) {
1025 0           return DBD::PgPPSjis::AuthenticationSCMCredential->new;
1026             }
1027             else {
1028 0           Carp::croak("Unknown authentication type: $code");
1029             }
1030             }
1031              
1032             sub _each_backend_key_data {
1033 0     0     my ($self) = @_;
1034 0           my $process_id = $self->_get_int32;
1035 0           my $secret_key = $self->_get_int32;
1036 0           return DBD::PgPPSjis::BackendKeyData->new($process_id, $secret_key);
1037             }
1038              
1039             sub _each_error_response {
1040 0     0     my ($self) = @_;
1041 0           my $error_message = $self->_get_c_string;
1042 0           return DBD::PgPPSjis::ErrorResponse->new($error_message);
1043             }
1044              
1045             sub _each_notice_response {
1046 0     0     my ($self) = @_;
1047 0           my $notice_message = $self->_get_c_string;
1048 0           return DBD::PgPPSjis::NoticeResponse->new($notice_message);
1049             }
1050              
1051             sub _each_notification_response {
1052 0     0     my ($self) = @_;
1053 0           my $process_id = $self->_get_int32;
1054 0           my $condition = $self->_get_c_string;
1055 0           return DBD::PgPPSjis::NotificationResponse->new($process_id, $condition);
1056             }
1057              
1058             sub _each_ready_for_query {
1059 0     0     my ($self) = @_;
1060 0           return DBD::PgPPSjis::ReadyForQuery->new;
1061             }
1062              
1063             sub _each_cursor_response {
1064 0     0     my ($self) = @_;
1065 0           my $name = $self->_get_c_string;
1066 0           return DBD::PgPPSjis::CursorResponse->new($name);
1067             }
1068              
1069             sub _each_row_description {
1070 0     0     my ($self) = @_;
1071 0           my $row_number = $self->_get_int16;
1072 0           my @description;
1073 0           for my $i (1 .. $row_number) {
1074 0           push @description, {
1075             name => $self->_get_c_string,
1076             type => $self->_get_int32,
1077             size => $self->_get_int16,
1078             modifier => $self->_get_int32,
1079             };
1080             }
1081 0           return DBD::PgPPSjis::RowDescription->new(\@description);
1082             }
1083              
1084             sub _each_ascii_row {
1085 0     0     my ($self) = @_;
1086 0           return DBD::PgPPSjis::AsciiRow->new($self);
1087             }
1088              
1089             sub _each_completed_response {
1090 0     0     my ($self) = @_;
1091 0           my $tag = $self->_get_c_string;
1092 0           return DBD::PgPPSjis::CompletedResponse->new($tag);
1093             }
1094              
1095             sub _each_empty_query_response {
1096 0     0     my ($self) = @_;
1097 0           my $unused = $self->_get_c_string;
1098 0           return DBD::PgPPSjis::EmptyQueryResponse->new($unused);
1099             }
1100              
1101             sub _get_byte {
1102 0     0     my ($self, $length) = @_;
1103 0 0         $length = 1 if !defined $length;
1104              
1105 0           $self->_if_short_then_add_buffer($length);
1106 0           return substr $self->{buffer}, 0, $length, '';
1107             }
1108              
1109             sub _get_int32 {
1110 0     0     my ($self) = @_;
1111 0           $self->_if_short_then_add_buffer(4);
1112 0           return unpack 'N', substr $self->{buffer}, 0, 4, '';
1113             }
1114              
1115             sub _get_int16 {
1116 0     0     my ($self) = @_;
1117 0           $self->_if_short_then_add_buffer(2);
1118 0           return unpack 'n', substr $self->{buffer}, 0, 2, '';
1119             }
1120              
1121             sub _get_c_string {
1122 0     0     my ($self) = @_;
1123              
1124 0           my $null_pos;
1125 0           while (1) {
1126 0           $null_pos = index $self->{buffer}, "\0";
1127 0 0         last if $null_pos >= 0;
1128 0           $self->_if_short_then_add_buffer(1 + length $self->{buffer});
1129             }
1130 0           my $result = substr $self->{buffer}, 0, $null_pos, '';
1131 0           substr $self->{buffer}, 0, 1, ''; # remove trailing \0
1132 0           return $result;
1133             }
1134              
1135             # This method means "I'm about to read *this* many bytes from the buffer, so
1136             # make sure there are enough bytes available". That is, on exit, you are
1137             # guaranteed that $length bytes are available.
1138             sub _if_short_then_add_buffer {
1139 0     0     my ($self, $length) = @_;
1140 0   0       $length ||= 0;
1141              
1142 0           my $handle = $self->{handle};
1143 0           while (length($self->{buffer}) < $length) {
1144 0           my $packet = '';
1145 0           $handle->recv($packet, $BUFFER_LEN, 0);
1146 0           DBD::PgPPSjis::Protocol::_dump_packet($packet);
1147 0           $self->{buffer} .= $packet;
1148             }
1149             }
1150              
1151              
1152             package DBD::PgPPSjis::Response;
1153              
1154             sub new {
1155 0     0     my ($class) = @_;
1156 0           bless {}, $class;
1157             }
1158              
1159 0     0     sub compute { return }
1160 0     0     sub is_empty { undef }
1161 0     0     sub is_error { undef }
1162 0     0     sub is_end_of_response { undef }
1163 0     0     sub get_result { undef }
1164 0     0     sub is_cursor_response { undef }
1165 0     0     sub is_notice_response { undef }
1166              
1167              
1168             package DBD::PgPPSjis::AuthenticationOk;
1169 1     1   6 use base qw;
  1         2  
  1         325  
1170              
1171              
1172             package DBD::PgPPSjis::AuthenticationKerberosV4;
1173 1     1   12 use base qw;
  1         2  
  1         238  
1174              
1175 0     0     sub compute { Carp::croak("authentication type 'Kerberos V4' not supported.\n") }
1176              
1177              
1178             package DBD::PgPPSjis::AuthenticationKerberosV5;
1179 1     1   6 use base qw;
  1         2  
  1         235  
1180              
1181 0     0     sub compute { Carp::croak("authentication type 'Kerberos V5' not supported.\n") }
1182              
1183              
1184             package DBD::PgPPSjis::AuthenticationCleartextPassword;
1185 1     1   5 use base qw;
  1         2  
  1         310  
1186              
1187             sub compute {
1188 0     0     my ($self, $pgsql) = @_;
1189 0           my $handle = $pgsql->get_handle;
1190 0           my $password = $pgsql->{password};
1191              
1192 0           my $packet = pack('N', length($password) + 4 + 1). $password. "\0";
1193 0 0         print " ==> PasswordPacket (cleartext)\n" if $DEBUG;
1194 0           DBD::PgPPSjis::Protocol::_dump_packet($packet);
1195 0           $handle->send($packet, 0);
1196             }
1197              
1198              
1199             package DBD::PgPPSjis::AuthenticationCryptPassword;
1200 1     1   6 use base qw;
  1         2  
  1         460  
1201              
1202             sub new {
1203 0     0     my ($class, $salt) = @_;
1204 0           my $self = $class->SUPER::new;
1205 0           $self->{salt} = $salt;
1206 0           $self;
1207             }
1208              
1209 0     0     sub get_salt { $_[0]{salt} }
1210              
1211             sub compute {
1212 0     0     my ($self, $pgsql) = @_;
1213 0           my $handle = $pgsql->get_handle;
1214 0   0       my $password = $pgsql->{password} || '';
1215              
1216 0           $password = _encode_crypt($password, $self->{salt});
1217 0           my $packet = pack('N', length($password) + 4 + 1). $password. "\0";
1218 0 0         print " ==> PasswordPacket (crypt)\n" if $DEBUG;
1219 0           DBD::PgPPSjis::Protocol::_dump_packet($packet);
1220 0           $handle->send($packet, 0);
1221             }
1222              
1223             sub _encode_crypt {
1224 0     0     my ($password, $salt) = @_;
1225              
1226 0           my $crypted = '';
1227 0           eval {
1228 0           $crypted = crypt($password, $salt);
1229 0 0         die "is MD5 crypt()" if _is_md5_crypt($crypted, $salt);
1230             };
1231 0 0         Carp::croak("authentication type 'crypt' not supported on your platform. please use 'trust' or 'md5' or 'ident' authentication")
1232             if $@;
1233 0           return $crypted;
1234             }
1235              
1236             sub _is_md5_crypt {
1237 0     0     my ($crypted, $salt) = @_;
1238 0           $crypted =~ /^\$1\$\Q$salt\E\$/;
1239             }
1240              
1241              
1242             package DBD::PgPPSjis::AuthenticationMD5Password;
1243 1     1   6 use base qw;
  1         1  
  1         462  
1244              
1245             sub new {
1246 0     0     my ($class, $salt) = @_;
1247 0           my $self = $class->SUPER::new;
1248 0           $self->{salt} = $salt;
1249 0           return $self;
1250             }
1251              
1252             sub compute {
1253 0     0     my ($self, $pgsql) = @_;
1254 0           my $handle = $pgsql->get_handle;
1255 0   0       my $password = $pgsql->{password} || '';
1256              
1257 0           my $md5ed_password = _encode_md5($pgsql->{user}, $password, $self->{salt});
1258 0           my $packet = pack('N', 1 + 4 + length $md5ed_password). "$md5ed_password\0";
1259 0 0         print " ==> PasswordPacket (md5)\n" if $DEBUG;
1260 0           DBD::PgPPSjis::Protocol::_dump_packet($packet);
1261 0           $handle->send($packet, 0);
1262             }
1263              
1264             sub _encode_md5 {
1265 0     0     my ($user, $password, $salt) = @_;
1266              
1267             # DBD::PgPPSjis (5 of 6)
1268              
1269 0           my $md5 = '';
1270 0           eval {
1271 0           require Digest::MD5;
1272 0           $md5 = Digest::MD5->new;
1273             };
1274 0 0         if ($@) {
1275 0           $md5 = DBD::PgPPSjis::EncodeMD5->new();
1276             }
1277              
1278 0           $md5->add($password);
1279 0           $md5->add($user);
1280              
1281 0           my $tmp_digest = $md5->hexdigest;
1282 0           $md5->add($tmp_digest);
1283 0           $md5->add($salt);
1284              
1285 0           return 'md5' . $md5->hexdigest;
1286             }
1287              
1288              
1289             package DBD::PgPPSjis::AuthenticationSCMCredential;
1290 1     1   6 use base qw;
  1         1  
  1         257  
1291              
1292 0     0     sub compute { Carp::croak("authentication type 'SCM Credential' not supported.\n") }
1293              
1294              
1295             package DBD::PgPPSjis::BackendKeyData;
1296 1     1   6 use base qw;
  1         2  
  1         331  
1297              
1298             sub new {
1299 0     0     my ($class, $process_id, $secret_key) = @_;
1300 0           my $self = $class->SUPER::new;
1301 0           $self->{process_id} = $process_id;
1302 0           $self->{secret_key} = $secret_key;
1303 0           return $self;
1304             }
1305              
1306 0     0     sub get_process_id { $_[0]{process_id} }
1307 0     0     sub get_secret_key { $_[0]{secret_key} }
1308              
1309             sub compute {
1310 0     0     my ($self, $postgres) = @_;;
1311              
1312 0           $postgres->{process_id} = $self->get_process_id;
1313 0           $postgres->{secret_key} = $self->get_secret_key;
1314             }
1315              
1316              
1317             package DBD::PgPPSjis::ErrorResponse;
1318 1     1   6 use base qw;
  1         1  
  1         257  
1319              
1320             sub new {
1321 0     0     my ($class, $message) = @_;
1322 0           my $self = $class->SUPER::new;
1323 0           $self->{message} = $message;
1324 0           return $self;
1325             }
1326              
1327 0     0     sub get_message { $_[0]{message} }
1328 0     0     sub is_error { 1 }
1329              
1330              
1331             package DBD::PgPPSjis::NoticeResponse;
1332 1     1   5 use base qw;
  1         2  
  1         295  
1333              
1334 0     0     sub is_error { undef }
1335 0     0     sub is_notice_response { 1 }
1336              
1337              
1338             package DBD::PgPPSjis::NotificationResponse;
1339 1     1   7 use base qw;
  1         1  
  1         307  
1340              
1341             sub new {
1342 0     0     my ($class, $process_id, $condition) = @_;
1343 0           my $self = $class->SUPER::new;
1344 0           $self->{process_id} = $process_id;
1345 0           $self->{condition} = $condition;
1346 0           return $self;
1347             }
1348              
1349 0     0     sub get_process_id { $_[0]{process_id} }
1350 0     0     sub get_condition { $_[0]{condition} }
1351              
1352              
1353             package DBD::PgPPSjis::ReadyForQuery;
1354 1     1   6 use base qw;
  1         2  
  1         214  
1355              
1356 0     0     sub is_end_of_response { 1 }
1357              
1358              
1359             package DBD::PgPPSjis::CursorResponse;
1360 1     1   5 use base qw;
  1         2  
  1         303  
1361              
1362             sub new {
1363 0     0     my ($class, $name) = @_;
1364 0           my $self = $class->SUPER::new;
1365 0           $self->{name} = $name;
1366 0           return $self;
1367             }
1368              
1369 0     0     sub get_name { $_[0]{name} }
1370 0     0     sub is_cursor_response { 1 }
1371              
1372             sub compute {
1373 0     0     my ($self, $pgsql) = @_;
1374 0           $pgsql->{cursor_name} = $self->get_name;
1375             }
1376              
1377              
1378             package DBD::PgPPSjis::RowDescription;
1379 1     1   6 use base qw;
  1         2  
  1         265  
1380              
1381             sub new {
1382 0     0     my ($class, $row_description) = @_;
1383 0           my $self = $class->SUPER::new;
1384 0           $self->{row_description} = $row_description;
1385 0           return $self;
1386             }
1387              
1388             sub compute {
1389 0     0     my ($self, $pgsql_sth) = @_;
1390 0           $pgsql_sth->{row_description} = $self->{row_description};
1391             }
1392              
1393              
1394             package DBD::PgPPSjis::AsciiRow;
1395 1     1   6 use base qw;
  1         1  
  1         471  
1396              
1397             sub new {
1398 0     0     my ($class, $stream) = @_;
1399 0           my $self = $class->SUPER::new;
1400 0           $self->{stream} = $stream;
1401 0           return $self;
1402             }
1403              
1404             sub compute {
1405 0     0     my ($self, $pgsql_sth) = @_;
1406              
1407 0           my $stream = $self->{stream};
1408 0           my $fields_length = @{ $pgsql_sth->{row_description} };
  0            
1409 0           my $bitmap_length = $self->_get_length_of_null_bitmap($fields_length);
1410 0           my $non_null = unpack 'B*', $stream->_get_byte($bitmap_length);
1411              
1412 0           my @result;
1413 0           for my $i (0 .. $fields_length - 1) {
1414 0           my $value;
1415 0 0         if (substr $non_null, $i, 1) {
1416 0           my $length = $stream->_get_int32;
1417 0           $value = $stream->_get_byte($length - 4);
1418 0           my $type_oid = $pgsql_sth->{row_description}[$i]{type};
1419 0 0         if ($type_oid == 16) { # bool
    0          
1420 0 0         $value = ($value eq 'f') ? 0 : 1;
1421             }
1422             elsif ($type_oid == 17) { # bytea
1423 0           $value =~ s{\\(\\|[0-7]{3})}{$BYTEA_DEMANGLE{$1}}g;
1424             }
1425             }
1426 0           push @result, $value;
1427             }
1428              
1429 0           $self->{result} = \@result;
1430             }
1431              
1432             sub _get_length_of_null_bitmap {
1433 0     0     my ($self, $number) = @_;
1434 1     1   479 use integer;
  1         11  
  1         5  
1435 0           my $length = $number / 8;
1436 0 0         ++$length if $number % 8;
1437 0           return $length;
1438             }
1439              
1440 0     0     sub get_result { $_[0]{result} }
1441 0     0     sub is_cursor_response { 1 }
1442              
1443              
1444             package DBD::PgPPSjis::CompletedResponse;
1445 1     1   77 use base qw;
  1         2  
  1         350  
1446              
1447             sub new {
1448 0     0     my ($class, $tag) = @_;
1449 0           my $self = $class->SUPER::new;
1450 0           $self->{tag} = $tag;
1451 0           return $self;
1452             }
1453              
1454 0     0     sub get_tag { $_[0]{tag} }
1455              
1456             sub compute {
1457 0     0     my ($self, $pgsql_sth) = @_;
1458 0           my $tag = $self->{tag};
1459              
1460             # DBD::PgPPSjis (6 of 6)
1461              
1462 0 0         if ($tag =~ /^INSERT ([0-9]+) ([0-9]+)/) {
    0          
    0          
1463 0           $pgsql_sth->{affected_oid} = $1;
1464 0           $pgsql_sth->{affected_rows} = $2;
1465             }
1466             elsif ($tag =~ /^DELETE ([0-9]+)/) {
1467 0           $pgsql_sth->{affected_rows} = $1;
1468             }
1469             elsif ($tag =~ /^UPDATE ([0-9]+)/) {
1470 0           $pgsql_sth->{affected_rows} = $1;
1471             }
1472             }
1473              
1474              
1475             package DBD::PgPPSjis::EmptyQueryResponse;
1476 1     1   6 use base qw;
  1         2  
  1         216  
1477              
1478 0     0     sub is_empty { 1 }
1479              
1480              
1481             package DBD::PgPPSjis::EncodeMD5;
1482              
1483             =pod
1484              
1485             =begin wish
1486              
1487             Please do not question closely about this source code ;-)
1488              
1489             =end wish
1490              
1491             =cut
1492              
1493 1     1   6 use strict;
  1         2  
  1         20  
1494 1     1   3 use vars qw($a $b $c $d);
  1         2  
  1         595  
1495             my ($x, $n, $m, $l, $r, $z);
1496              
1497             sub new {
1498 0     0     my $class = shift;
1499 0           bless {
1500             source => '',
1501             }, $class;
1502             }
1503              
1504             sub add {
1505 0     0     my $self = shift;
1506 0           $self->{source} .= join '', @_;
1507             }
1508              
1509             sub hexdigest {
1510 0     0     my $self = shift;
1511              
1512 0           my @A = unpack('N4C24', unpack 'u', 'H9T4C`>_-JXF8NMS^$#)4=@<,$18%"0X4!`L0%P8*#Q4``04``04#!P``');
1513 0           my @K = map { int abs 2 ** 32 * sin $_ } 1..64;
  0            
1514 0           my ($p);
1515              
1516 0           my $position = 0;
1517 0           do {
1518 0           $_ = substr $self->{source}, $position, 64;
1519 0           $position += 64;
1520 0           $l += $r = length $_;
1521 0 0 0       $r++, $_ .= "\x80" if $r < 64 && !$p++;
1522 0           my @W = unpack 'V16', $_. "\0" x 7;
1523 0 0         $W[14] = $l * 8 if $r < 57;
1524 0           ($a, $b, $c, $d) = @A;
1525              
1526 0           for (0..63) {
1527             #no warnings;
1528 0           local($^W) = 0;
1529             $a = _m($b +
1530             _l($A[4 + 4 * ($_ >> 4) + $_ % 4],
1531 0           _m(&{(
1532             sub {
1533 0     0     $b & $c | $d & ~ $b;
1534             },
1535             sub {
1536 0     0     $b & $d | $c & ~ $d;
1537             },
1538             sub {
1539 0     0     $b ^ $c ^ $d;
1540             },
1541             sub {
1542 0     0     $c ^ ($b | ~ $d);
1543             }
1544 0           )[$z = $_ / 16]}
1545             + $W[($A[20 + $z] + $A[24 + $z] * ($_ % 16)) % 16] + $K[$_] + $a)
1546             )
1547             );
1548 0           ($a, $b, $c, $d) = ($d, $a, $b, $c)
1549             }
1550              
1551 0           my $i = $A[0];
1552 0           $A[0] = _m($A[0] + $a);
1553 0           $A[1] = _m($A[1] + $b);
1554 0           $A[2] = _m($A[2] + $c);
1555 0           $A[3] = _m($A[3] + $d);
1556              
1557             } while ($r > 56);
1558              
1559 0           ($x, $n, $m, $l, $r, $z) = ();
1560 0           $self->{source} = '';
1561              
1562 0           return unpack 'H32', pack 'V4', @A;
1563             }
1564              
1565             sub _l {
1566 0     0     ($x = pop @_) << ($n=pop) | 2 ** $n - 1 & $x >> 32 - $n;
1567             }
1568              
1569             sub _m {
1570 0     0     ($x = pop @_) - ($m = 1 + ~ 0) * int($x / $m);
1571             }
1572              
1573             1;
1574              
1575             __END__