DBD-PgPP
view release on metacpan or search on metacpan
lib/DBD/PgPP.pm view on Meta::CPAN
| [^[:ascii:]]+ | [\0-\037\177]+)}xmsgc) { }
elsif (m{\G( [+<>=~!\@\#%^&|`,;.()\[\]{}]+ )}xmsgc) { }
elsif (m{\G( [\'\"\\] )}xmsgc) { } # unmatched: a bug in your query
else {
my $pos = pos;
die "BUG: can't parse statement at $pos\n$statement\n";
}
$tokens[-1] .= $1;
redo Parse;
}
pop @tokens if @tokens > 1 && $tokens[-1] eq '';
return \@tokens;
}
package DBD::PgPP::ProtocolStatement;
sub new {
my ($class, $pgsql, $statement) = @_;
bless {
postgres => $pgsql,
statement => $statement,
rows => [],
}, $class;
}
sub execute {
my ($self) = @_;
my $pgsql = $self->{postgres};
my $handle = $pgsql->get_handle;
my $query_packet = "Q$self->{statement}\0";
print " ==> Query\n" if $DEBUG;
DBD::PgPP::Protocol::_dump_packet($query_packet);
$handle->send($query_packet, 0);
$self->{affected_rows} = 0;
$self->{last_oid} = undef;
$self->{rows} = [];
my $stream = $pgsql->get_stream;
my $packet = $stream->each;
if ($packet->is_error) {
$self->_to_end_of_response($stream);
die $packet->get_message;
}
elsif ($packet->is_end_of_response) {
return;
}
elsif ($packet->is_empty) {
$self->_to_end_of_response($stream);
return;
}
while ($packet->is_notice_response) {
# XXX: discard it for now
$packet = $stream->each;
}
if ($packet->is_cursor_response) {
$packet->compute($pgsql);
my $row_info = $stream->each; # fetch RowDescription
if ($row_info->is_error) {
$self->_to_end_of_response($stream);
Carp::croak($row_info->get_message);
}
$row_info->compute($self);
while (1) {
my $row_packet = $stream->each;
if ($row_packet->is_error) {
$self->_to_end_of_response($stream);
Carp::croak($row_packet->get_message);
}
$row_packet->compute($self);
push @{ $self->{rows} }, $row_packet->get_result;
last if $row_packet->is_end_of_response;
}
return;
}
else { # CompletedResponse
$packet->compute($self);
while (1) {
my $end = $stream->each;
if ($end->is_error) {
$self->_to_end_of_response($stream);
Carp::croak($end->get_message);
}
last if $end->is_end_of_response;
}
return;
}
}
sub _to_end_of_response {
my ($self, $stream) = @_;
while (1) {
my $packet = $stream->each;
$packet->compute($self);
last if $packet->is_end_of_response;
}
}
sub fetch {
my ($self) = @_;
return shift @{ $self->{rows} }; # shift returns undef if empty
}
package DBD::PgPP::PacketStream;
# Message Identifiers
use constant ASCII_ROW => 'D';
use constant AUTHENTICATION => 'R';
use constant BACKEND_KEY_DATA => 'K';
use constant BINARY_ROW => 'B';
use constant COMPLETED_RESPONSE => 'C';
use constant COPY_IN_RESPONSE => 'G';
use constant COPY_OUT_RESPONSE => 'H';
use constant CURSOR_RESPONSE => 'P';
use constant EMPTY_QUERY_RESPONSE => 'I';
use constant ERROR_RESPONSE => 'E';
use constant FUNCTION_RESPONSE => 'V';
use constant NOTICE_RESPONSE => 'N';
use constant NOTIFICATION_RESPONSE => 'A';
use constant READY_FOR_QUERY => 'Z';
use constant ROW_DESCRIPTION => 'T';
# Authentication Message specifiers
use constant AUTHENTICATION_OK => 0;
use constant AUTHENTICATION_KERBEROS_V4 => 1;
use constant AUTHENTICATION_KERBEROS_V5 => 2;
use constant AUTHENTICATION_CLEARTEXT_PASSWORD => 3;
use constant AUTHENTICATION_CRYPT_PASSWORD => 4;
use constant AUTHENTICATION_MD5_PASSWORD => 5;
use constant AUTHENTICATION_SCM_CREDENTIAL => 6;
sub new {
my ($class, $handle) = @_;
bless {
handle => $handle,
buffer => '',
}, $class;
}
sub set_buffer {
my ($self, $buffer) = @_;
$self->{buffer} = $buffer;
}
sub get_buffer { $_[0]{buffer} }
sub each {
my ($self) = @_;
my $type = $self->_get_byte;
# XXX: This would perhaps be better as a dispatch table
my $p = $type eq ASCII_ROW ? $self->_each_ascii_row
: $type eq AUTHENTICATION ? $self->_each_authentication
: $type eq BACKEND_KEY_DATA ? $self->_each_backend_key_data
: $type eq BINARY_ROW ? $self->_each_binary_row
: $type eq COMPLETED_RESPONSE ? $self->_each_completed_response
: $type eq COPY_IN_RESPONSE ? $self->_each_copy_in_response
: $type eq COPY_OUT_RESPONSE ? $self->_each_copy_out_response
: $type eq CURSOR_RESPONSE ? $self->_each_cursor_response
: $type eq EMPTY_QUERY_RESPONSE ? $self->_each_empty_query_response
: $type eq ERROR_RESPONSE ? $self->_each_error_response
: $type eq FUNCTION_RESPONSE ? $self->_each_function_response
: $type eq NOTICE_RESPONSE ? $self->_each_notice_response
: $type eq NOTIFICATION_RESPONSE ? $self->_each_notification_response
: $type eq READY_FOR_QUERY ? $self->_each_ready_for_query
: $type eq ROW_DESCRIPTION ? $self->_each_row_description
: Carp::croak("Unknown message type: '$type'");
if ($DEBUG) {
(my $type = ref $p) =~ s/.*:://;
print "<== $type\n";
}
return $p;
}
sub _each_authentication {
my ($self) = @_;
my $code = $self->_get_int32;
if ($code == AUTHENTICATION_OK) {
return DBD::PgPP::AuthenticationOk->new;
}
elsif ($code == AUTHENTICATION_KERBEROS_V4) {
return DBD::PgPP::AuthenticationKerberosV4->new;
}
elsif ($code == AUTHENTICATION_KERBEROS_V5) {
return DBD::PgPP::AuthenticationKerberosV5->new;
}
elsif ($code == AUTHENTICATION_CLEARTEXT_PASSWORD) {
return DBD::PgPP::AuthenticationCleartextPassword->new;
}
elsif ($code == AUTHENTICATION_CRYPT_PASSWORD) {
my $salt = $self->_get_byte(2);
return DBD::PgPP::AuthenticationCryptPassword->new($salt);
}
elsif ($code == AUTHENTICATION_MD5_PASSWORD) {
my $salt = $self->_get_byte(4);
return DBD::PgPP::AuthenticationMD5Password->new($salt);
}
elsif ($code == AUTHENTICATION_SCM_CREDENTIAL) {
return DBD::PgPP::AuthenticationSCMCredential->new;
}
else {
Carp::croak("Unknown authentication type: $code");
}
}
sub _each_backend_key_data {
my ($self) = @_;
my $process_id = $self->_get_int32;
my $secret_key = $self->_get_int32;
return DBD::PgPP::BackendKeyData->new($process_id, $secret_key);
}
sub _each_error_response {
my ($self) = @_;
my $error_message = $self->_get_c_string;
return DBD::PgPP::ErrorResponse->new($error_message);
}
sub _each_notice_response {
my ($self) = @_;
my $notice_message = $self->_get_c_string;
return DBD::PgPP::NoticeResponse->new($notice_message);
}
sub _each_notification_response {
my ($self) = @_;
my $process_id = $self->_get_int32;
my $condition = $self->_get_c_string;
return DBD::PgPP::NotificationResponse->new($process_id, $condition);
}
sub _each_ready_for_query {
my ($self) = @_;
return DBD::PgPP::ReadyForQuery->new;
}
sub _each_cursor_response {
my ($self) = @_;
my $name = $self->_get_c_string;
return DBD::PgPP::CursorResponse->new($name);
}
sub _each_row_description {
my ($self) = @_;
my $row_number = $self->_get_int16;
my @description;
for my $i (1 .. $row_number) {
push @description, {
name => $self->_get_c_string,
type => $self->_get_int32,
size => $self->_get_int16,
modifier => $self->_get_int32,
};
}
return DBD::PgPP::RowDescription->new(\@description);
}
sub _each_ascii_row {
my ($self) = @_;
return DBD::PgPP::AsciiRow->new($self);
}
sub _each_completed_response {
my ($self) = @_;
my $tag = $self->_get_c_string;
return DBD::PgPP::CompletedResponse->new($tag);
}
sub _each_empty_query_response {
my ($self) = @_;
my $unused = $self->_get_c_string;
return DBD::PgPP::EmptyQueryResponse->new($unused);
}
sub _get_byte {
my ($self, $length) = @_;
$length = 1 if !defined $length;
$self->_if_short_then_add_buffer($length);
return substr $self->{buffer}, 0, $length, '';
}
sub _get_int32 {
my ($self) = @_;
$self->_if_short_then_add_buffer(4);
return unpack 'N', substr $self->{buffer}, 0, 4, '';
}
sub _get_int16 {
my ($self) = @_;
$self->_if_short_then_add_buffer(2);
return unpack 'n', substr $self->{buffer}, 0, 2, '';
}
sub _get_c_string {
my ($self) = @_;
my $null_pos;
while (1) {
$null_pos = index $self->{buffer}, "\0";
last if $null_pos >= 0;
$self->_if_short_then_add_buffer(1 + length $self->{buffer});
}
my $result = substr $self->{buffer}, 0, $null_pos, '';
substr $self->{buffer}, 0, 1, ''; # remove trailing \0
return $result;
}
# This method means "I'm about to read *this* many bytes from the buffer, so
# make sure there are enough bytes available". That is, on exit, you are
# guaranteed that $length bytes are available.
sub _if_short_then_add_buffer {
my ($self, $length) = @_;
$length ||= 0;
my $handle = $self->{handle};
while (length($self->{buffer}) < $length) {
my $packet = '';
$handle->recv($packet, $BUFFER_LEN, 0);
DBD::PgPP::Protocol::_dump_packet($packet);
$self->{buffer} .= $packet;
}
}
package DBD::PgPP::Response;
sub new {
my ($class) = @_;
bless {}, $class;
}
sub compute { return }
sub is_empty { undef }
sub is_error { undef }
sub is_end_of_response { undef }
sub get_result { undef }
sub is_cursor_response { undef }
sub is_notice_response { undef }
package DBD::PgPP::AuthenticationOk;
use base qw<DBD::PgPP::Response>;
package DBD::PgPP::AuthenticationKerberosV4;
use base qw<DBD::PgPP::Response>;
sub compute { Carp::croak("authentication type 'Kerberos V4' not supported.\n") }
package DBD::PgPP::AuthenticationKerberosV5;
use base qw<DBD::PgPP::Response>;
sub compute { Carp::croak("authentication type 'Kerberos V5' not supported.\n") }
package DBD::PgPP::AuthenticationCleartextPassword;
use base qw<DBD::PgPP::Response>;
sub compute {
my ($self, $pgsql) = @_;
my $handle = $pgsql->get_handle;
my $password = $pgsql->{password};
my $packet = pack('N', length($password) + 4 + 1). $password. "\0";
print " ==> PasswordPacket (cleartext)\n" if $DEBUG;
DBD::PgPP::Protocol::_dump_packet($packet);
$handle->send($packet, 0);
}
package DBD::PgPP::AuthenticationCryptPassword;
use base qw<DBD::PgPP::Response>;
sub new {
my ($class, $salt) = @_;
my $self = $class->SUPER::new;
$self->{salt} = $salt;
$self;
}
sub get_salt { $_[0]{salt} }
sub compute {
my ($self, $pgsql) = @_;
my $handle = $pgsql->get_handle;
my $password = $pgsql->{password} || '';
$password = _encode_crypt($password, $self->{salt});
my $packet = pack('N', length($password) + 4 + 1). $password. "\0";
print " ==> PasswordPacket (crypt)\n" if $DEBUG;
DBD::PgPP::Protocol::_dump_packet($packet);
$handle->send($packet, 0);
}
sub _encode_crypt {
my ($password, $salt) = @_;
lib/DBD/PgPP.pm view on Meta::CPAN
my ($self, $postgres) = @_;;
$postgres->{process_id} = $self->get_process_id;
$postgres->{secret_key} = $self->get_secret_key;
}
package DBD::PgPP::ErrorResponse;
use base qw<DBD::PgPP::Response>;
sub new {
my ($class, $message) = @_;
my $self = $class->SUPER::new;
$self->{message} = $message;
return $self;
}
sub get_message { $_[0]{message} }
sub is_error { 1 }
package DBD::PgPP::NoticeResponse;
use base qw<DBD::PgPP::ErrorResponse>;
sub is_error { undef }
sub is_notice_response { 1 }
package DBD::PgPP::NotificationResponse;
use base qw<DBD::PgPP::Response>;
sub new {
my ($class, $process_id, $condition) = @_;
my $self = $class->SUPER::new;
$self->{process_id} = $process_id;
$self->{condition} = $condition;
return $self;
}
sub get_process_id { $_[0]{process_id} }
sub get_condition { $_[0]{condition} }
package DBD::PgPP::ReadyForQuery;
use base qw<DBD::PgPP::Response>;
sub is_end_of_response { 1 }
package DBD::PgPP::CursorResponse;
use base qw<DBD::PgPP::Response>;
sub new {
my ($class, $name) = @_;
my $self = $class->SUPER::new;
$self->{name} = $name;
return $self;
}
sub get_name { $_[0]{name} }
sub is_cursor_response { 1 }
sub compute {
my ($self, $pgsql) = @_;
$pgsql->{cursor_name} = $self->get_name;
}
package DBD::PgPP::RowDescription;
use base qw<DBD::PgPP::Response>;
sub new {
my ($class, $row_description) = @_;
my $self = $class->SUPER::new;
$self->{row_description} = $row_description;
return $self;
}
sub compute {
my ($self, $pgsql_sth) = @_;
$pgsql_sth->{row_description} = $self->{row_description};
}
package DBD::PgPP::AsciiRow;
use base qw<DBD::PgPP::Response>;
sub new {
my ($class, $stream) = @_;
my $self = $class->SUPER::new;
$self->{stream} = $stream;
return $self;
}
sub compute {
my ($self, $pgsql_sth) = @_;
my $stream = $self->{stream};
my $fields_length = @{ $pgsql_sth->{row_description} };
my $bitmap_length = $self->_get_length_of_null_bitmap($fields_length);
my $non_null = unpack 'B*', $stream->_get_byte($bitmap_length);
my @result;
for my $i (0 .. $fields_length - 1) {
my $value;
if (substr $non_null, $i, 1) {
my $length = $stream->_get_int32;
$value = $stream->_get_byte($length - 4);
my $type_oid = $pgsql_sth->{row_description}[$i]{type};
if ($type_oid == 16) { # bool
$value = ($value eq 'f') ? 0 : 1;
}
elsif ($type_oid == 17) { # bytea
$value =~ s{\\(\\|[0-7]{3})}{$BYTEA_DEMANGLE{$1}}g;
}
}
push @result, $value;
}
$self->{result} = \@result;
}
sub _get_length_of_null_bitmap {
my ($self, $number) = @_;
use integer;
my $length = $number / 8;
++$length if $number % 8;
return $length;
}
sub get_result { $_[0]{result} }
sub is_cursor_response { 1 }
package DBD::PgPP::CompletedResponse;
use base qw<DBD::PgPP::Response>;
sub new {
my ($class, $tag) = @_;
my $self = $class->SUPER::new;
$self->{tag} = $tag;
return $self;
}
sub get_tag { $_[0]{tag} }
sub compute {
my ($self, $pgsql_sth) = @_;
my $tag = $self->{tag};
if ($tag =~ /^INSERT (\d+) (\d+)/) {
$pgsql_sth->{affected_oid} = $1;
$pgsql_sth->{affected_rows} = $2;
}
elsif ($tag =~ /^DELETE (\d+)/) {
$pgsql_sth->{affected_rows} = $1;
}
elsif ($tag =~ /^UPDATE (\d+)/) {
$pgsql_sth->{affected_rows} = $1;
}
}
package DBD::PgPP::EmptyQueryResponse;
use base qw<DBD::PgPP::Response>;
sub is_empty { 1 }
1;
__END__
=head1 DESCRIPTION
DBD::PgPP is a pure-Perl client interface for the PostgreSQL database. This
module implements the network protocol that allows a client to communicate
with a PostgreSQL server, so you don't need an external PostgreSQL client
library like B<libpq> for it to work. That means this module enables you to
connect to PostgreSQL server from platforms where there's no PostgreSQL
port, or where installing PostgreSQL is prohibitively hard.
=head1 MODULE DOCUMENTATION
This documentation describes driver specific behavior and restrictions; it
does not attempt to describe everything you might need to use DBD::PgPP. In
particular, users are advised to be familiar with the DBI documentation.
=head1 THE DBI CLASS
=head2 DBI Class Methods
=over 4
( run in 1.583 second using v1.01-cache-2.11-cpan-39bf76dae61 )