postgres-pr-opt 0.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +25 -0
- data/Rakefile +2 -0
- data/examples/client.rb +34 -0
- data/examples/og/test.rb +50 -0
- data/examples/server.rb +12 -0
- data/examples/test_connection.rb +18 -0
- data/ext/postgres-pr/utils/extconf.rb +8 -0
- data/ext/postgres-pr/utils/string_unpack_single.c +73 -0
- data/lib/pg.rb +8 -0
- data/lib/postgres-pr/connection.rb +175 -0
- data/lib/postgres-pr/message.rb +589 -0
- data/lib/postgres-pr/pg-compat.rb +225 -0
- data/lib/postgres-pr/postgres-compat.rb +169 -0
- data/lib/postgres-pr/typeconv/TC_conv.rb +18 -0
- data/lib/postgres-pr/typeconv/array.rb +46 -0
- data/lib/postgres-pr/typeconv/bytea.rb +32 -0
- data/lib/postgres-pr/typeconv/conv.rb +5 -0
- data/lib/postgres-pr/utils/buffer.rb +252 -0
- data/lib/postgres-pr/utils/byteorder.rb +45 -0
- data/lib/postgres-pr/version.rb +3 -0
- data/lib/postgres.rb +8 -0
- metadata +102 -0
data/README
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
= Pure Ruby PostgreSQL interface
|
2
|
+
|
3
|
+
This is a library to access PostgreSQL (>= 7.4) from Ruby without the need of
|
4
|
+
any C library.
|
5
|
+
|
6
|
+
== Author and Copyright
|
7
|
+
|
8
|
+
Copyright (c) 2005, 2008 by Michael Neumann (mneumann@ntecs.de).
|
9
|
+
Released under the same terms of license as Ruby.
|
10
|
+
|
11
|
+
== Homepage
|
12
|
+
|
13
|
+
http://rubyforge.org/projects/ruby-dbi
|
14
|
+
|
15
|
+
== Quick Example
|
16
|
+
|
17
|
+
> gem install postgres-pr
|
18
|
+
> irb -r rubygems
|
19
|
+
|
20
|
+
Then in the interactive Ruby interpreter type (replace DBNAME and DBUSER
|
21
|
+
accordingly):
|
22
|
+
|
23
|
+
require 'postgres-pr/connection'
|
24
|
+
c = PostgresPR::Connection.new('DBNAME', 'DBUSER')
|
25
|
+
c.query('SELECT 1+2').rows # => [["3"]]
|
data/Rakefile
ADDED
data/examples/client.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
$LOAD_PATH.unshift "../lib"
|
2
|
+
require 'postgres-pr/message'
|
3
|
+
require 'socket'
|
4
|
+
include PostgresPR
|
5
|
+
|
6
|
+
s = UNIXSocket.new(ARGV.shift || "/tmp/.s.PGSQL.5432")
|
7
|
+
|
8
|
+
msg = StartupMessage.new(196608, "user" => "mneumann", "database" => "mneumann")
|
9
|
+
s << msg.dump
|
10
|
+
|
11
|
+
Thread.start(s) { |s|
|
12
|
+
sleep 2
|
13
|
+
s << Query.new("DROP TABLE IF EXISTS test").dump
|
14
|
+
s << Query.new("create table test (i int, v varchar(100))").dump
|
15
|
+
s << Parse.new("insert into test (i, v) values ($1, $2)", "blah").dump
|
16
|
+
s << Query.new("EXECUTE blah(1, 'hallo')").dump
|
17
|
+
|
18
|
+
while not (line = gets.chomp).empty?
|
19
|
+
s << Query.new(line).dump
|
20
|
+
end
|
21
|
+
exit
|
22
|
+
}
|
23
|
+
|
24
|
+
loop do
|
25
|
+
msg = Message.read(s)
|
26
|
+
p msg
|
27
|
+
|
28
|
+
case msg
|
29
|
+
when AuthenticationOk
|
30
|
+
p "OK"
|
31
|
+
when ErrorResponse
|
32
|
+
p "FAILED"
|
33
|
+
end
|
34
|
+
end
|
data/examples/og/test.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
$LOAD_PATH.unshift '../../lib'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'og'
|
4
|
+
require 'glue/logger'
|
5
|
+
|
6
|
+
$DBG = true
|
7
|
+
|
8
|
+
class User
|
9
|
+
end
|
10
|
+
|
11
|
+
class Comment
|
12
|
+
prop_accessor :body, String
|
13
|
+
belongs_to :user, User
|
14
|
+
end
|
15
|
+
|
16
|
+
class User
|
17
|
+
prop_accessor :name, String
|
18
|
+
has_many :comments, Comment
|
19
|
+
end
|
20
|
+
|
21
|
+
if __FILE__ == $0
|
22
|
+
config = {
|
23
|
+
:address => "localhost",
|
24
|
+
:database => "mneumann",
|
25
|
+
:backend => "psql",
|
26
|
+
:user => "mneumann",
|
27
|
+
:password => "",
|
28
|
+
:connection_count => 1
|
29
|
+
}
|
30
|
+
$log = Logger.new(STDERR)
|
31
|
+
$og = Og::Database.new(config)
|
32
|
+
|
33
|
+
$og.get_connection
|
34
|
+
|
35
|
+
u1 = User.new
|
36
|
+
u1.name = "Michael Neumann"
|
37
|
+
u1.save!
|
38
|
+
|
39
|
+
u2 = User.new
|
40
|
+
u2.name = "John User"
|
41
|
+
u2.save!
|
42
|
+
|
43
|
+
c1 = Comment.new
|
44
|
+
c1.body = "og test"
|
45
|
+
c1.user = u1
|
46
|
+
c1.save!
|
47
|
+
|
48
|
+
p User.all
|
49
|
+
p Comment.all
|
50
|
+
end
|
data/examples/server.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$LOAD_PATH.unshift '../lib'
|
2
|
+
require 'postgres-pr/connection'
|
3
|
+
|
4
|
+
conn = PostgresPR::Connection.new('mneumann', 'mneumann', nil, 'unix:/var/run/postgresql/.s.PGSQL.5432')
|
5
|
+
p conn.query("DROP TABLE IF EXISTS test") rescue nil
|
6
|
+
p conn.query("CREATE TABLE test (a VARCHAR(100))")
|
7
|
+
p conn.query("INSERT INTO test VALUES ('hallo')")
|
8
|
+
p conn.query("INSERT INTO test VALUES ('leute')")
|
9
|
+
conn.query("COMMIT")
|
10
|
+
|
11
|
+
conn.query("BEGIN")
|
12
|
+
10000.times do |i|
|
13
|
+
p i
|
14
|
+
conn.query("INSERT INTO test VALUES ('#{i}')")
|
15
|
+
end
|
16
|
+
conn.query("COMMIT")
|
17
|
+
|
18
|
+
p conn.query("SELECT * FROM test")
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
|
3
|
+
static ID id_readbyte;
|
4
|
+
static VALUE rb_str_get_int16_network(VALUE self, VALUE position)
|
5
|
+
{
|
6
|
+
long pos = NUM2LONG(position);
|
7
|
+
if (pos + 2 > RSTRING_LEN(self))
|
8
|
+
return Qnil;
|
9
|
+
else {
|
10
|
+
const unsigned char *buf = RSTRING_PTR(self);
|
11
|
+
long byte1 = buf[pos],
|
12
|
+
byte2 = buf[pos+1];
|
13
|
+
long res = (byte1 < 128 ? byte1 : byte1 - 256) * 256 + byte2;
|
14
|
+
return LONG2FIX(res);
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
static VALUE rb_str_get_int32_network(VALUE self, VALUE position)
|
19
|
+
{
|
20
|
+
long pos = NUM2LONG(position);
|
21
|
+
if (pos + 4 > RSTRING_LEN(self))
|
22
|
+
return Qnil;
|
23
|
+
else {
|
24
|
+
const unsigned char *buf = RSTRING_PTR(self);
|
25
|
+
long byte1 = buf[pos],
|
26
|
+
byte2 = buf[pos+1],
|
27
|
+
byte3 = buf[pos+2],
|
28
|
+
byte4 = buf[pos+3];
|
29
|
+
long res = (((byte1 < 128 ? byte1 : byte1 - 256) * 256 + byte2) * 256 +
|
30
|
+
byte3) * 256 + byte4;
|
31
|
+
return LONG2NUM(res);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
static VALUE rb_read_int16_network(VALUE self)
|
36
|
+
{
|
37
|
+
VALUE B1, B2;
|
38
|
+
long byte1, byte2, res;
|
39
|
+
B1 = rb_funcall(self, id_readbyte, 0);
|
40
|
+
B2 = rb_funcall(self, id_readbyte, 0);
|
41
|
+
byte1 = FIX2LONG(B1);
|
42
|
+
byte2 = FIX2LONG(B2);
|
43
|
+
res = (byte1 < 128 ? byte1 : byte1 - 256) * 256 + byte2;
|
44
|
+
return LONG2FIX(res);
|
45
|
+
}
|
46
|
+
|
47
|
+
static VALUE rb_read_int32_network(VALUE self)
|
48
|
+
{
|
49
|
+
VALUE B1, B2, B3, B4;
|
50
|
+
long byte1, byte2, byte3, byte4, res;
|
51
|
+
B1 = rb_funcall(self, id_readbyte, 0);
|
52
|
+
B2 = rb_funcall(self, id_readbyte, 0);
|
53
|
+
B3 = rb_funcall(self, id_readbyte, 0);
|
54
|
+
B4 = rb_funcall(self, id_readbyte, 0);
|
55
|
+
byte1 = FIX2LONG(B1);
|
56
|
+
byte2 = FIX2LONG(B2);
|
57
|
+
byte3 = FIX2LONG(B3);
|
58
|
+
byte4 = FIX2LONG(B4);
|
59
|
+
res = (((byte1 < 128 ? byte1 : byte1 - 256) * 256 + byte2) * 256 +
|
60
|
+
byte3) * 256 + byte4;
|
61
|
+
return LONG2NUM(res);
|
62
|
+
}
|
63
|
+
|
64
|
+
|
65
|
+
void Init_unpack_single() {
|
66
|
+
VALUE mod_unpack;
|
67
|
+
rb_define_method(rb_cString, "get_int16_network", rb_str_get_int16_network, 1);
|
68
|
+
rb_define_method(rb_cString, "get_int32_network", rb_str_get_int32_network, 1);
|
69
|
+
id_readbyte = rb_intern("readbyte");
|
70
|
+
mod_unpack = rb_define_module("ReadUnpack");
|
71
|
+
rb_define_method(mod_unpack, "read_int16_network", rb_read_int16_network, 0);
|
72
|
+
rb_define_method(mod_unpack, "read_int32_network", rb_read_int32_network, 0);
|
73
|
+
}
|
data/lib/pg.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Michael Neumann
|
3
|
+
# Copyright:: (c) 2005 by Michael Neumann
|
4
|
+
# License:: Same as Ruby's or BSD
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'postgres-pr/message'
|
8
|
+
require 'postgres-pr/version'
|
9
|
+
require 'uri'
|
10
|
+
require 'socket'
|
11
|
+
|
12
|
+
module PostgresPR
|
13
|
+
|
14
|
+
PROTO_VERSION = 3 << 16 #196608
|
15
|
+
|
16
|
+
class Connection
|
17
|
+
|
18
|
+
# A block which is called with the NoticeResponse object as parameter.
|
19
|
+
attr_accessor :notice_processor
|
20
|
+
attr_reader :params
|
21
|
+
|
22
|
+
#
|
23
|
+
# Returns one of the following statuses:
|
24
|
+
#
|
25
|
+
# PQTRANS_IDLE = 0 (connection idle)
|
26
|
+
# PQTRANS_INTRANS = 2 (idle, within transaction block)
|
27
|
+
# PQTRANS_INERROR = 3 (idle, within failed transaction)
|
28
|
+
# PQTRANS_UNKNOWN = 4 (cannot determine status)
|
29
|
+
#
|
30
|
+
# Not yet implemented is:
|
31
|
+
#
|
32
|
+
# PQTRANS_ACTIVE = 1 (command in progress)
|
33
|
+
#
|
34
|
+
def transaction_status
|
35
|
+
case @transaction_status
|
36
|
+
when ?I
|
37
|
+
0
|
38
|
+
when ?T
|
39
|
+
2
|
40
|
+
when ?E
|
41
|
+
3
|
42
|
+
else
|
43
|
+
4
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(database, user, password=nil, uri = nil)
|
48
|
+
uri ||= DEFAULT_URI
|
49
|
+
|
50
|
+
@transaction_status = nil
|
51
|
+
@params = {}
|
52
|
+
establish_connection(uri)
|
53
|
+
|
54
|
+
@conn << StartupMessage.new(PROTO_VERSION, 'user' => user, 'database' => database).dump
|
55
|
+
|
56
|
+
loop do
|
57
|
+
msg = Message.read(@conn)
|
58
|
+
|
59
|
+
case msg
|
60
|
+
when AuthenticationClearTextPassword
|
61
|
+
raise PGError, "no password specified" if password.nil?
|
62
|
+
@conn << PasswordMessage.new(password).dump
|
63
|
+
|
64
|
+
when AuthenticationCryptPassword
|
65
|
+
raise PGError, "no password specified" if password.nil?
|
66
|
+
@conn << PasswordMessage.new(password.crypt(msg.salt)).dump
|
67
|
+
|
68
|
+
when AuthenticationMD5Password
|
69
|
+
raise PGError, "no password specified" if password.nil?
|
70
|
+
require 'digest/md5'
|
71
|
+
|
72
|
+
m = Digest::MD5.hexdigest(password + user)
|
73
|
+
m = Digest::MD5.hexdigest(m + msg.salt)
|
74
|
+
m = 'md5' + m
|
75
|
+
@conn << PasswordMessage.new(m).dump
|
76
|
+
|
77
|
+
when AuthenticationKerberosV4, AuthenticationKerberosV5, AuthenticationSCMCredential
|
78
|
+
raise PGError, "unsupported Authentication"
|
79
|
+
|
80
|
+
when AuthenticationOk
|
81
|
+
when ErrorResponse
|
82
|
+
raise PGError, msg.field_values.join("\t")
|
83
|
+
when NoticeResponse
|
84
|
+
@notice_processor.call(msg) if @notice_processor
|
85
|
+
when ParameterStatus
|
86
|
+
@params[msg.key] = msg.value
|
87
|
+
when BackendKeyData
|
88
|
+
# TODO
|
89
|
+
#p msg
|
90
|
+
when ReadyForQuery
|
91
|
+
@transaction_status = msg.backend_transaction_status_indicator
|
92
|
+
break
|
93
|
+
else
|
94
|
+
raise PGError, "unhandled message type"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def close
|
100
|
+
raise(PGError, "connection already closed") if @conn.nil?
|
101
|
+
@conn.shutdown
|
102
|
+
@conn = nil
|
103
|
+
end
|
104
|
+
|
105
|
+
class Result
|
106
|
+
attr_accessor :rows, :fields, :cmd_tag
|
107
|
+
def initialize(rows=[], fields=[])
|
108
|
+
@rows, @fields = rows, fields
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def query(sql)
|
113
|
+
@conn << Query.dump(sql)
|
114
|
+
|
115
|
+
result = Result.new
|
116
|
+
errors = []
|
117
|
+
|
118
|
+
loop do
|
119
|
+
msg = Message.read(@conn)
|
120
|
+
case msg
|
121
|
+
when DataRow
|
122
|
+
result.rows << msg.columns
|
123
|
+
when CommandComplete
|
124
|
+
result.cmd_tag = msg.cmd_tag
|
125
|
+
when ReadyForQuery
|
126
|
+
@transaction_status = msg.backend_transaction_status_indicator
|
127
|
+
break
|
128
|
+
when RowDescription
|
129
|
+
result.fields = msg.fields
|
130
|
+
when CopyInResponse
|
131
|
+
when CopyOutResponse
|
132
|
+
when EmptyQueryResponse
|
133
|
+
when ErrorResponse
|
134
|
+
# TODO
|
135
|
+
errors << msg
|
136
|
+
when NoticeResponse
|
137
|
+
@notice_processor.call(msg) if @notice_processor
|
138
|
+
else
|
139
|
+
# TODO
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
raise(PGError, errors.map{|e| e.field_values.join("\t") }.join("\n")) unless errors.empty?
|
144
|
+
|
145
|
+
result
|
146
|
+
end
|
147
|
+
|
148
|
+
DEFAULT_PORT = 5432
|
149
|
+
DEFAULT_HOST = 'localhost'
|
150
|
+
DEFAULT_PATH = '/tmp'
|
151
|
+
DEFAULT_URI =
|
152
|
+
if RUBY_PLATFORM.include?('win')
|
153
|
+
'tcp://' + DEFAULT_HOST + ':' + DEFAULT_PORT.to_s
|
154
|
+
else
|
155
|
+
'unix:' + File.join(DEFAULT_PATH, '.s.PGSQL.' + DEFAULT_PORT.to_s)
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
# tcp://localhost:5432
|
161
|
+
# unix:/tmp/.s.PGSQL.5432
|
162
|
+
def establish_connection(uri)
|
163
|
+
u = URI.parse(uri)
|
164
|
+
case u.scheme
|
165
|
+
when 'tcp'
|
166
|
+
@conn = TCPSocket.new(u.host || DEFAULT_HOST, u.port || DEFAULT_PORT)
|
167
|
+
when 'unix'
|
168
|
+
@conn = UNIXSocket.new(u.path)
|
169
|
+
else
|
170
|
+
raise PGError, 'unrecognized uri scheme format (must be tcp or unix)'
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
end # module PostgresPR
|
@@ -0,0 +1,589 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Michael Neumann
|
3
|
+
# Copyright:: (c) 2005 by Michael Neumann
|
4
|
+
# License:: Same as Ruby's or BSD
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'postgres-pr/utils/buffer'
|
8
|
+
require 'postgres-pr/utils/byteorder'
|
9
|
+
class IO
|
10
|
+
def read_exactly_n_bytes(n)
|
11
|
+
buf = read(n)
|
12
|
+
raise EOFError if buf == nil
|
13
|
+
return buf if buf.size == n
|
14
|
+
|
15
|
+
n -= buf.size
|
16
|
+
|
17
|
+
while n > 0
|
18
|
+
str = read(n)
|
19
|
+
raise EOFError if str == nil
|
20
|
+
buf << str
|
21
|
+
n -= str.size
|
22
|
+
end
|
23
|
+
return buf
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
module PostgresPR
|
29
|
+
|
30
|
+
class PGError < StandardError; end
|
31
|
+
class ParseError < PGError; end
|
32
|
+
class DumpError < PGError; end
|
33
|
+
|
34
|
+
|
35
|
+
# Base class representing a PostgreSQL protocol message
|
36
|
+
class Message < Utils::ReadBuffer
|
37
|
+
# One character message-typecode to class map
|
38
|
+
MsgTypeMap = Hash.new { UnknownMessageType }
|
39
|
+
|
40
|
+
def self.register_message_type(type)
|
41
|
+
raise(PGError, "duplicate message type registration") if MsgTypeMap.has_key?(type)
|
42
|
+
|
43
|
+
MsgTypeMap[type] = self
|
44
|
+
|
45
|
+
self.const_set(:MsgType, type)
|
46
|
+
class_eval "def message_type; MsgType end"
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.read(stream, startup=false)
|
50
|
+
type = stream.read_exactly_n_bytes(1) unless startup
|
51
|
+
length_s = stream.read_exactly_n_bytes(4)
|
52
|
+
length = length_s.get_int32_network(0) # FIXME: length should be signed, not unsigned
|
53
|
+
|
54
|
+
raise ParseError unless length >= 4
|
55
|
+
|
56
|
+
# initialize buffer
|
57
|
+
body = stream.read_exactly_n_bytes(length - 4)
|
58
|
+
(startup ? StartupMessage : MsgTypeMap[type]).create("#{type}#{length_s}#{body}")
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.create(buffer)
|
62
|
+
obj = allocate
|
63
|
+
obj.init_buffer buffer
|
64
|
+
obj.parse(obj)
|
65
|
+
obj
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.dump(*args)
|
69
|
+
new(*args).dump
|
70
|
+
end
|
71
|
+
|
72
|
+
def buffer
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
def dump(body_size=0)
|
77
|
+
buffer = Utils::WriteBuffer.of_size(5 + body_size)
|
78
|
+
buffer.write(self.message_type)
|
79
|
+
buffer.write_int32_network(4 + body_size)
|
80
|
+
yield buffer if block_given?
|
81
|
+
raise DumpError unless buffer.at_end?
|
82
|
+
buffer.content
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse(buffer)
|
86
|
+
buffer.position = 5
|
87
|
+
yield buffer if block_given?
|
88
|
+
raise ParseError, buffer.inspect unless buffer.at_end?
|
89
|
+
buffer.close
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.fields(*attribs)
|
93
|
+
names = attribs.map {|name, type| name.to_s}
|
94
|
+
arg_list = names.join(", ")
|
95
|
+
ivar_list = names.map {|name| "@" + name }.join(", ")
|
96
|
+
sym_list = names.map {|name| ":" + name }.join(", ")
|
97
|
+
class_eval %[
|
98
|
+
attr_accessor #{ sym_list }
|
99
|
+
def initialize(#{ arg_list })
|
100
|
+
#{ ivar_list } = #{ arg_list }
|
101
|
+
end
|
102
|
+
]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class UnknownMessageType < Message
|
107
|
+
def dump
|
108
|
+
raise PGError, "dumping unknown message"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class Authentication < Message
|
113
|
+
register_message_type 'R'
|
114
|
+
|
115
|
+
AuthTypeMap = Hash.new { UnknownAuthType }
|
116
|
+
|
117
|
+
def self.create(buffer)
|
118
|
+
authtype = buffer.get_int32_network(5)
|
119
|
+
klass = AuthTypeMap[authtype]
|
120
|
+
obj = klass.allocate
|
121
|
+
obj.init_buffer buffer
|
122
|
+
obj.parse(obj)
|
123
|
+
obj
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.register_auth_type(type)
|
127
|
+
raise(PGError, "duplicate auth type registration") if AuthTypeMap.has_key?(type)
|
128
|
+
AuthTypeMap[type] = self
|
129
|
+
self.const_set(:AuthType, type)
|
130
|
+
class_eval "def auth_type() AuthType end"
|
131
|
+
end
|
132
|
+
|
133
|
+
# the dump method of class Message
|
134
|
+
alias message__dump dump
|
135
|
+
|
136
|
+
def dump
|
137
|
+
super(4) do |buffer|
|
138
|
+
buffer.write_int32_network(self.auth_type)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def parse(buffer)
|
143
|
+
super do
|
144
|
+
auth_t = buffer.read_int32_network
|
145
|
+
raise ParseError unless auth_t == self.auth_type
|
146
|
+
yield if block_given?
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class UnknownAuthType < Authentication
|
152
|
+
end
|
153
|
+
|
154
|
+
class AuthenticationOk < Authentication
|
155
|
+
register_auth_type 0
|
156
|
+
end
|
157
|
+
|
158
|
+
class AuthenticationKerberosV4 < Authentication
|
159
|
+
register_auth_type 1
|
160
|
+
end
|
161
|
+
|
162
|
+
class AuthenticationKerberosV5 < Authentication
|
163
|
+
register_auth_type 2
|
164
|
+
end
|
165
|
+
|
166
|
+
class AuthenticationClearTextPassword < Authentication
|
167
|
+
register_auth_type 3
|
168
|
+
end
|
169
|
+
|
170
|
+
module SaltedAuthenticationMixin
|
171
|
+
attr_accessor :salt
|
172
|
+
|
173
|
+
def initialize(salt)
|
174
|
+
@salt = salt
|
175
|
+
end
|
176
|
+
|
177
|
+
def dump
|
178
|
+
raise DumpError unless @salt.size == self.salt_size
|
179
|
+
|
180
|
+
message__dump(4 + self.salt_size) do |buffer|
|
181
|
+
buffer.write_int32_network(self.auth_type)
|
182
|
+
buffer.write(@salt)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def parse(buffer)
|
187
|
+
super do
|
188
|
+
@salt = buffer.read(self.salt_size)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
class AuthenticationCryptPassword < Authentication
|
194
|
+
register_auth_type 4
|
195
|
+
include SaltedAuthenticationMixin
|
196
|
+
def salt_size; 2 end
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
class AuthenticationMD5Password < Authentication
|
201
|
+
register_auth_type 5
|
202
|
+
include SaltedAuthenticationMixin
|
203
|
+
def salt_size; 4 end
|
204
|
+
end
|
205
|
+
|
206
|
+
class AuthenticationSCMCredential < Authentication
|
207
|
+
register_auth_type 6
|
208
|
+
end
|
209
|
+
|
210
|
+
class PasswordMessage < Message
|
211
|
+
register_message_type 'p'
|
212
|
+
fields :password
|
213
|
+
|
214
|
+
def dump
|
215
|
+
super(@password.size + 1) do |buffer|
|
216
|
+
buffer.write_cstring(@password)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def parse(buffer)
|
221
|
+
super do
|
222
|
+
@password = buffer.read_cstring
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class ParameterStatus < Message
|
228
|
+
register_message_type 'S'
|
229
|
+
fields :key, :value
|
230
|
+
|
231
|
+
def dump
|
232
|
+
super(@key.size + 1 + @value.size + 1) do |buffer|
|
233
|
+
buffer.write_cstring(@key)
|
234
|
+
buffer.write_cstring(@value)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def parse(buffer)
|
239
|
+
super do
|
240
|
+
@key = buffer.read_cstring
|
241
|
+
@value = buffer.read_cstring
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
class BackendKeyData < Message
|
247
|
+
register_message_type 'K'
|
248
|
+
fields :process_id, :secret_key
|
249
|
+
|
250
|
+
def dump
|
251
|
+
super(4 + 4) do |buffer|
|
252
|
+
buffer.write_int32_network(@process_id)
|
253
|
+
buffer.write_int32_network(@secret_key)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def parse(buffer)
|
258
|
+
super do
|
259
|
+
@process_id = buffer.read_int32_network
|
260
|
+
@secret_key = buffer.read_int32_network
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
class ReadyForQuery < Message
|
266
|
+
register_message_type 'Z'
|
267
|
+
fields :backend_transaction_status_indicator
|
268
|
+
|
269
|
+
def dump
|
270
|
+
super(1) do |buffer|
|
271
|
+
buffer.write_byte(@backend_transaction_status_indicator)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def parse(buffer)
|
276
|
+
super do
|
277
|
+
@backend_transaction_status_indicator = buffer.read_byte
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
class DataRow < Message
|
283
|
+
register_message_type 'D'
|
284
|
+
fields :columns
|
285
|
+
|
286
|
+
def dump
|
287
|
+
sz = @columns.inject(2) {|sum, col| sum + 4 + (col ? col.size : 0)}
|
288
|
+
super(sz) do |buffer|
|
289
|
+
buffer.write_int16_network(@columns.size)
|
290
|
+
@columns.each {|col|
|
291
|
+
buffer.write_int32_network(col ? col.size : -1)
|
292
|
+
buffer.write(col) if col
|
293
|
+
}
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def parse(buffer)
|
298
|
+
super do
|
299
|
+
columns = []
|
300
|
+
n_cols = buffer.read_int16_network
|
301
|
+
while n_cols > 0
|
302
|
+
len = buffer.read_int32_network
|
303
|
+
if len == -1
|
304
|
+
columns << nil
|
305
|
+
else
|
306
|
+
columns << buffer.read(len)
|
307
|
+
end
|
308
|
+
n_cols -= 1
|
309
|
+
end
|
310
|
+
@columns = columns
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
class CommandComplete < Message
|
316
|
+
register_message_type 'C'
|
317
|
+
fields :cmd_tag
|
318
|
+
|
319
|
+
def dump
|
320
|
+
super(@cmd_tag.size + 1) do |buffer|
|
321
|
+
buffer.write_cstring(@cmd_tag)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def parse(buffer)
|
326
|
+
super do
|
327
|
+
@cmd_tag = buffer.read_cstring
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
class EmptyQueryResponse < Message
|
333
|
+
register_message_type 'I'
|
334
|
+
end
|
335
|
+
|
336
|
+
module NoticeErrorMixin
|
337
|
+
attr_accessor :field_type, :field_values
|
338
|
+
|
339
|
+
def initialize(field_type=0, field_values=[])
|
340
|
+
raise PGError if field_type == 0 and not field_values.empty?
|
341
|
+
@field_type, @field_values = field_type, field_values
|
342
|
+
end
|
343
|
+
|
344
|
+
def dump
|
345
|
+
raise PGError if @field_type == 0 and not @field_values.empty?
|
346
|
+
|
347
|
+
sz = 1
|
348
|
+
sz += @field_values.inject(1) {|sum, fld| sum + fld.size + 1} unless @field_type == 0
|
349
|
+
|
350
|
+
super(sz) do |buffer|
|
351
|
+
buffer.write_byte(@field_type)
|
352
|
+
break if @field_type == 0
|
353
|
+
@field_values.each {|fld| buffer.write_cstring(fld) }
|
354
|
+
buffer.write_byte(0)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def parse(buffer)
|
359
|
+
super do
|
360
|
+
@field_type = buffer.read_byte
|
361
|
+
break if @field_type == 0
|
362
|
+
@field_values = []
|
363
|
+
while buffer.position < buffer.size-1
|
364
|
+
@field_values << buffer.read_cstring
|
365
|
+
end
|
366
|
+
terminator = buffer.read_byte
|
367
|
+
raise ParseError unless terminator == 0
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
class NoticeResponse < Message
|
373
|
+
register_message_type 'N'
|
374
|
+
include NoticeErrorMixin
|
375
|
+
end
|
376
|
+
|
377
|
+
class ErrorResponse < Message
|
378
|
+
register_message_type 'E'
|
379
|
+
include NoticeErrorMixin
|
380
|
+
end
|
381
|
+
|
382
|
+
# TODO
|
383
|
+
class CopyInResponse < Message
|
384
|
+
register_message_type 'G'
|
385
|
+
end
|
386
|
+
|
387
|
+
# TODO
|
388
|
+
class CopyOutResponse < Message
|
389
|
+
register_message_type 'H'
|
390
|
+
end
|
391
|
+
|
392
|
+
class Parse < Message
|
393
|
+
register_message_type 'P'
|
394
|
+
fields :query, :stmt_name, :parameter_oids
|
395
|
+
|
396
|
+
def initialize(query, stmt_name="", parameter_oids=[])
|
397
|
+
@query, @stmt_name, @parameter_oids = query, stmt_name, parameter_oids
|
398
|
+
end
|
399
|
+
|
400
|
+
def dump
|
401
|
+
sz = @stmt_name.size + 1 + @query.size + 1 + 2 + (4 * @parameter_oids.size)
|
402
|
+
super(sz) do |buffer|
|
403
|
+
buffer.write_cstring(@stmt_name)
|
404
|
+
buffer.write_cstring(@query)
|
405
|
+
buffer.write_int16_network(@parameter_oids.size)
|
406
|
+
@parameter_oids.each {|oid| buffer.write_int32_network(oid) }
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def parse(buffer)
|
411
|
+
super do
|
412
|
+
@stmt_name = buffer.read_cstring
|
413
|
+
@query = buffer.read_cstring
|
414
|
+
n_oids = buffer.read_int16_network
|
415
|
+
@parameter_oids = (1..n_oids).collect {
|
416
|
+
# TODO: zero means unspecified. map to nil?
|
417
|
+
buffer.read_int32_network
|
418
|
+
}
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
class ParseComplete < Message
|
424
|
+
register_message_type '1'
|
425
|
+
end
|
426
|
+
|
427
|
+
class Query < Message
|
428
|
+
register_message_type 'Q'
|
429
|
+
fields :query
|
430
|
+
|
431
|
+
def dump
|
432
|
+
super(@query.size + 1) do |buffer|
|
433
|
+
buffer.write_cstring(@query)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
def parse(buffer)
|
438
|
+
super do
|
439
|
+
@query = buffer.read_cstring
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
class RowDescription < Message
|
445
|
+
register_message_type 'T'
|
446
|
+
fields :fields
|
447
|
+
|
448
|
+
class FieldInfo < Struct.new(:name, :oid, :attr_nr, :type_oid, :typlen, :atttypmod, :formatcode); end
|
449
|
+
|
450
|
+
def dump
|
451
|
+
sz = @fields.inject(2) {|sum, fld| sum + 18 + fld.name.size + 1 }
|
452
|
+
super(sz) do |buffer|
|
453
|
+
buffer.write_int16_network(@fields.size)
|
454
|
+
@fields.each { |f|
|
455
|
+
buffer.write_cstring(f.name)
|
456
|
+
buffer.write_int32_network(f.oid)
|
457
|
+
buffer.write_int16_network(f.attr_nr)
|
458
|
+
buffer.write_int32_network(f.type_oid)
|
459
|
+
buffer.write_int16_network(f.typlen)
|
460
|
+
buffer.write_int32_network(f.atttypmod)
|
461
|
+
buffer.write_int16_network(f.formatcode)
|
462
|
+
}
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
if RUBY_ENGINE == 'rbx' || (RUBY_ENGINE == 'ruby' && RUBY_VERSION > '1.9' && Utils::STRING_NATIVE_UNPACK_SINGLE)
|
467
|
+
def parse(buffer)
|
468
|
+
super do
|
469
|
+
fields = []
|
470
|
+
n_fields = buffer.read_int16_network
|
471
|
+
while n_fields > 0
|
472
|
+
f = FieldInfo.new
|
473
|
+
f.name = buffer.read_cstring
|
474
|
+
f.oid = buffer.read_int32_network
|
475
|
+
f.attr_nr = buffer.read_int16_network
|
476
|
+
f.type_oid = buffer.read_int32_network
|
477
|
+
f.typlen = buffer.read_int16_network
|
478
|
+
f.atttypmod = buffer.read_int32_network
|
479
|
+
f.formatcode = buffer.read_int16_network
|
480
|
+
fields << f
|
481
|
+
n_fields -= 1
|
482
|
+
end
|
483
|
+
@fields = fields
|
484
|
+
end
|
485
|
+
end
|
486
|
+
elsif Utils::ByteOrder.little?
|
487
|
+
def parse(buffer)
|
488
|
+
super do
|
489
|
+
fields = []
|
490
|
+
n_fields = buffer.read_int16_network
|
491
|
+
while n_fields > 0
|
492
|
+
f = FieldInfo.new
|
493
|
+
f.name = buffer.read_cstring
|
494
|
+
s = buffer.read(18); s.reverse!
|
495
|
+
a = s.unpack('slslsl'); a.reverse!
|
496
|
+
f.oid, f.attr_nr, f.type_oid, f.typlen, f.atttypmod, f.formatcode = a
|
497
|
+
fields << f
|
498
|
+
n_fields -= 1
|
499
|
+
end
|
500
|
+
@fields = fields
|
501
|
+
end
|
502
|
+
end
|
503
|
+
else
|
504
|
+
def parse(buffer)
|
505
|
+
super do
|
506
|
+
fields = []
|
507
|
+
n_fields = buffer.read_int16_network
|
508
|
+
while n_fields > 0
|
509
|
+
f = FieldInfo.new
|
510
|
+
f.name = buffer.read_cstring
|
511
|
+
a = buffer.read(18).unpack('lslsls')
|
512
|
+
f.formatcode, f.atttypmod, f.typlen, f.type_oid, f.attr_nr, f.oid = a
|
513
|
+
fields << f
|
514
|
+
n_fields -= 1
|
515
|
+
end
|
516
|
+
@fields = fields
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
class StartupMessage < Message
|
523
|
+
fields :proto_version, :params
|
524
|
+
|
525
|
+
def dump
|
526
|
+
sz = @params.inject(4 + 4) {|sum, kv| sum + kv[0].size + 1 + kv[1].size + 1} + 1
|
527
|
+
|
528
|
+
buffer = Utils::WriteBuffer.of_size(sz)
|
529
|
+
buffer.write_int32_network(sz)
|
530
|
+
buffer.write_int32_network(@proto_version)
|
531
|
+
@params.each_pair {|key, value|
|
532
|
+
buffer.write_cstring(key)
|
533
|
+
buffer.write_cstring(value)
|
534
|
+
}
|
535
|
+
buffer.write_byte(0)
|
536
|
+
|
537
|
+
raise DumpError unless buffer.at_end?
|
538
|
+
buffer.content
|
539
|
+
end
|
540
|
+
|
541
|
+
def parse(buffer)
|
542
|
+
buffer.position = 4
|
543
|
+
|
544
|
+
@proto_version = buffer.read_int32_network
|
545
|
+
@params = {}
|
546
|
+
|
547
|
+
while buffer.position < buffer.size-1
|
548
|
+
key = buffer.read_cstring
|
549
|
+
val = buffer.read_cstring
|
550
|
+
@params[key] = val
|
551
|
+
end
|
552
|
+
|
553
|
+
nul = buffer.read_byte
|
554
|
+
raise ParseError unless nul == 0
|
555
|
+
raise ParseError unless buffer.at_end?
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
class SSLRequest < Message
|
560
|
+
fields :ssl_request_code
|
561
|
+
|
562
|
+
def dump
|
563
|
+
sz = 4 + 4
|
564
|
+
buffer = Buffer.of_size(sz)
|
565
|
+
buffer.write_int32_network(sz)
|
566
|
+
buffer.write_int32_network(@ssl_request_code)
|
567
|
+
raise DumpError unless buffer.at_end?
|
568
|
+
return buffer.content
|
569
|
+
end
|
570
|
+
|
571
|
+
def parse(buffer)
|
572
|
+
buffer.position = 4
|
573
|
+
@ssl_request_code = buffer.read_int32_network
|
574
|
+
raise ParseError unless buffer.at_end?
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
=begin
|
579
|
+
# TODO: duplicate message-type, split into client/server messages
|
580
|
+
class Sync < Message
|
581
|
+
register_message_type 'S'
|
582
|
+
end
|
583
|
+
=end
|
584
|
+
|
585
|
+
class Terminate < Message
|
586
|
+
register_message_type 'X'
|
587
|
+
end
|
588
|
+
|
589
|
+
end # module PostgresPR
|