postgres-pr-encoding 0.7.0
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.
- checksums.yaml +7 -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/lib/binary_reader.rb +120 -0
- data/lib/binary_writer.rb +100 -0
- data/lib/buffer.rb +97 -0
- data/lib/byteorder.rb +32 -0
- data/lib/pg.rb +4 -0
- data/lib/postgres-pr/connection.rb +177 -0
- data/lib/postgres-pr/message.rb +542 -0
- data/lib/postgres-pr/pg-compat.rb +174 -0
- data/lib/postgres-pr/postgres-compat.rb +170 -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 +28 -0
- data/lib/postgres-pr/typeconv/conv.rb +5 -0
- data/lib/postgres-pr/version.rb +3 -0
- data/lib/postgres.rb +8 -0
- data/test/TC_message.rb +103 -0
- metadata +63 -0
data/lib/byteorder.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module ByteOrder
|
2
|
+
Native = :Native
|
3
|
+
BigEndian = Big = Network = :BigEndian
|
4
|
+
LittleEndian = Little = :LittleEndian
|
5
|
+
|
6
|
+
# examines the byte order of the underlying machine
|
7
|
+
def byte_order
|
8
|
+
if [0x12345678].pack("L") == "\x12\x34\x56\x78"
|
9
|
+
BigEndian
|
10
|
+
else
|
11
|
+
LittleEndian
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
alias byteorder byte_order
|
16
|
+
|
17
|
+
def little_endian?
|
18
|
+
byte_order == LittleEndian
|
19
|
+
end
|
20
|
+
|
21
|
+
def big_endian?
|
22
|
+
byte_order == BigEndian
|
23
|
+
end
|
24
|
+
|
25
|
+
alias little? little_endian?
|
26
|
+
alias big? big_endian?
|
27
|
+
alias network? big_endian?
|
28
|
+
|
29
|
+
module_function :byte_order, :byteorder
|
30
|
+
module_function :little_endian?, :little?
|
31
|
+
module_function :big_endian?, :big?, :network?
|
32
|
+
end
|
data/lib/pg.rb
ADDED
@@ -0,0 +1,177 @@
|
|
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
|
+
class Error < ::StandardError; end
|
13
|
+
class DatabaseError < Error; end
|
14
|
+
|
15
|
+
module PostgresPR
|
16
|
+
|
17
|
+
PROTO_VERSION = 3 << 16 #196608
|
18
|
+
|
19
|
+
class Connection
|
20
|
+
|
21
|
+
# A block which is called with the NoticeResponse object as parameter.
|
22
|
+
attr_accessor :notice_processor
|
23
|
+
|
24
|
+
#
|
25
|
+
# Returns one of the following statuses:
|
26
|
+
#
|
27
|
+
# PQTRANS_IDLE = 0 (connection idle)
|
28
|
+
# PQTRANS_INTRANS = 2 (idle, within transaction block)
|
29
|
+
# PQTRANS_INERROR = 3 (idle, within failed transaction)
|
30
|
+
# PQTRANS_UNKNOWN = 4 (cannot determine status)
|
31
|
+
#
|
32
|
+
# Not yet implemented is:
|
33
|
+
#
|
34
|
+
# PQTRANS_ACTIVE = 1 (command in progress)
|
35
|
+
#
|
36
|
+
def transaction_status
|
37
|
+
case @transaction_status
|
38
|
+
when ?I
|
39
|
+
0
|
40
|
+
when ?T
|
41
|
+
2
|
42
|
+
when ?E
|
43
|
+
3
|
44
|
+
else
|
45
|
+
4
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(database, user, password=nil, uri = nil)
|
50
|
+
uri ||= DEFAULT_URI
|
51
|
+
|
52
|
+
@transaction_status = nil
|
53
|
+
@params = {}
|
54
|
+
establish_connection(uri)
|
55
|
+
|
56
|
+
@conn << StartupMessage.new(PROTO_VERSION, 'user' => user, 'database' => database).dump
|
57
|
+
|
58
|
+
loop do
|
59
|
+
msg = Message.read(@conn)
|
60
|
+
|
61
|
+
case msg
|
62
|
+
when AuthentificationClearTextPassword
|
63
|
+
raise ArgumentError, "no password specified" if password.nil?
|
64
|
+
@conn << PasswordMessage.new(password).dump
|
65
|
+
|
66
|
+
when AuthentificationCryptPassword
|
67
|
+
raise ArgumentError, "no password specified" if password.nil?
|
68
|
+
@conn << PasswordMessage.new(password.crypt(msg.salt)).dump
|
69
|
+
|
70
|
+
when AuthentificationMD5Password
|
71
|
+
raise ArgumentError, "no password specified" if password.nil?
|
72
|
+
require 'digest/md5'
|
73
|
+
|
74
|
+
m = Digest::MD5.hexdigest(password + user)
|
75
|
+
m = Digest::MD5.hexdigest(m + msg.salt)
|
76
|
+
m = 'md5' + m
|
77
|
+
@conn << PasswordMessage.new(m).dump
|
78
|
+
|
79
|
+
when AuthentificationKerberosV4, AuthentificationKerberosV5, AuthentificationSCMCredential
|
80
|
+
raise "unsupported authentification"
|
81
|
+
|
82
|
+
when AuthentificationOk
|
83
|
+
when ErrorResponse
|
84
|
+
raise msg.field_values.join("\t")
|
85
|
+
when NoticeResponse
|
86
|
+
@notice_processor.call(msg) if @notice_processor
|
87
|
+
when ParameterStatus
|
88
|
+
@params[msg.key] = msg.value
|
89
|
+
when BackendKeyData
|
90
|
+
# TODO
|
91
|
+
#p msg
|
92
|
+
when ReadyForQuery
|
93
|
+
@transaction_status = msg.backend_transaction_status_indicator
|
94
|
+
break
|
95
|
+
else
|
96
|
+
raise "unhandled message type"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def close
|
102
|
+
raise "connection already closed" if @conn.nil?
|
103
|
+
@conn.shutdown
|
104
|
+
@conn = nil
|
105
|
+
end
|
106
|
+
|
107
|
+
class Result
|
108
|
+
attr_accessor :rows, :fields, :cmd_tag
|
109
|
+
def initialize(rows=[], fields=[])
|
110
|
+
@rows, @fields = rows, fields
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def query(sql)
|
115
|
+
@conn << Query.dump(sql)
|
116
|
+
|
117
|
+
result = Result.new
|
118
|
+
errors = []
|
119
|
+
|
120
|
+
loop do
|
121
|
+
msg = Message.read(@conn)
|
122
|
+
case msg
|
123
|
+
when DataRow
|
124
|
+
result.rows << msg.columns
|
125
|
+
when CommandComplete
|
126
|
+
result.cmd_tag = msg.cmd_tag
|
127
|
+
when ReadyForQuery
|
128
|
+
@transaction_status = msg.backend_transaction_status_indicator
|
129
|
+
break
|
130
|
+
when RowDescription
|
131
|
+
result.fields = msg.fields
|
132
|
+
when CopyInResponse
|
133
|
+
when CopyOutResponse
|
134
|
+
when EmptyQueryResponse
|
135
|
+
when ErrorResponse
|
136
|
+
# TODO
|
137
|
+
errors << msg
|
138
|
+
when NoticeResponse
|
139
|
+
@notice_processor.call(msg) if @notice_processor
|
140
|
+
else
|
141
|
+
# TODO
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
raise DatabaseError.new(errors.map{|e| e.field_values.join("\t") }.join("\n")) unless errors.empty?
|
146
|
+
|
147
|
+
result
|
148
|
+
end
|
149
|
+
|
150
|
+
DEFAULT_PORT = 5432
|
151
|
+
DEFAULT_HOST = 'localhost'
|
152
|
+
DEFAULT_PATH = '/tmp'
|
153
|
+
DEFAULT_URI =
|
154
|
+
if RUBY_PLATFORM.include?('win')
|
155
|
+
'tcp://' + DEFAULT_HOST + ':' + DEFAULT_PORT.to_s
|
156
|
+
else
|
157
|
+
'unix:' + File.join(DEFAULT_PATH, '.s.PGSQL.' + DEFAULT_PORT.to_s)
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
# tcp://localhost:5432
|
163
|
+
# unix:/tmp/.s.PGSQL.5432
|
164
|
+
def establish_connection(uri)
|
165
|
+
u = URI.parse(uri)
|
166
|
+
case u.scheme
|
167
|
+
when 'tcp'
|
168
|
+
@conn = TCPSocket.new(u.host || DEFAULT_HOST, u.port || DEFAULT_PORT)
|
169
|
+
when 'unix'
|
170
|
+
@conn = UNIXSocket.new(u.path)
|
171
|
+
else
|
172
|
+
raise 'unrecognized uri scheme format (must be tcp or unix)'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end # module PostgresPR
|
@@ -0,0 +1,542 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Michael Neumann
|
3
|
+
# Copyright:: (c) 2005 by Michael Neumann
|
4
|
+
# License:: Same as Ruby's or BSD
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'buffer'
|
8
|
+
class IO
|
9
|
+
def read_exactly_n_bytes(n)
|
10
|
+
buf = read(n)
|
11
|
+
raise EOFError if buf == nil
|
12
|
+
return buf if buf.size == n
|
13
|
+
|
14
|
+
n -= buf.size
|
15
|
+
|
16
|
+
while n > 0
|
17
|
+
str = read(n)
|
18
|
+
raise EOFError if str == nil
|
19
|
+
buf << str
|
20
|
+
n -= str.size
|
21
|
+
end
|
22
|
+
return buf
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module PostgresPR
|
27
|
+
|
28
|
+
class ParseError < RuntimeError; end
|
29
|
+
class DumpError < RuntimeError; end
|
30
|
+
|
31
|
+
|
32
|
+
# Base class representing a PostgreSQL protocol message
|
33
|
+
class Message
|
34
|
+
# One character message-typecode to class map
|
35
|
+
MsgTypeMap = Hash.new { UnknownMessageType }
|
36
|
+
|
37
|
+
def self.register_message_type(type)
|
38
|
+
raise "duplicate message type registration" if MsgTypeMap.has_key?(type)
|
39
|
+
|
40
|
+
MsgTypeMap[type] = self
|
41
|
+
|
42
|
+
self.const_set(:MsgType, type)
|
43
|
+
class_eval "def message_type; MsgType end"
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.read(stream, startup=false)
|
47
|
+
type = stream.read_exactly_n_bytes(1) unless startup
|
48
|
+
length = stream.read_exactly_n_bytes(4).unpack('N').first # FIXME: length should be signed, not unsigned
|
49
|
+
|
50
|
+
raise ParseError unless length >= 4
|
51
|
+
|
52
|
+
# initialize buffer
|
53
|
+
buffer = Buffer.of_size(startup ? length : 1+length)
|
54
|
+
buffer.write(type) unless startup
|
55
|
+
buffer.write_int32_network(length)
|
56
|
+
buffer.copy_from_stream(stream, length-4)
|
57
|
+
|
58
|
+
(startup ? StartupMessage : MsgTypeMap[type]).create(buffer)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.create(buffer)
|
62
|
+
obj = allocate
|
63
|
+
obj.parse(buffer)
|
64
|
+
obj
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.dump(*args)
|
68
|
+
new(*args).dump
|
69
|
+
end
|
70
|
+
|
71
|
+
def dump(body_size=0)
|
72
|
+
buffer = Buffer.of_size(5 + body_size)
|
73
|
+
buffer.write(self.message_type)
|
74
|
+
buffer.write_int32_network(4 + body_size)
|
75
|
+
yield buffer if block_given?
|
76
|
+
raise DumpError unless buffer.at_end?
|
77
|
+
return buffer.content
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse(buffer)
|
81
|
+
buffer.position = 5
|
82
|
+
yield buffer if block_given?
|
83
|
+
raise ParseError, buffer.inspect unless buffer.at_end?
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.fields(*attribs)
|
87
|
+
names = attribs.map {|name, type| name.to_s}
|
88
|
+
arg_list = names.join(", ")
|
89
|
+
ivar_list = names.map {|name| "@" + name }.join(", ")
|
90
|
+
sym_list = names.map {|name| ":" + name }.join(", ")
|
91
|
+
class_eval %[
|
92
|
+
attr_accessor #{ sym_list }
|
93
|
+
def initialize(#{ arg_list })
|
94
|
+
#{ ivar_list } = #{ arg_list }
|
95
|
+
end
|
96
|
+
]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class UnknownMessageType < Message
|
101
|
+
def dump
|
102
|
+
raise
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class Authentification < Message
|
107
|
+
register_message_type 'R'
|
108
|
+
|
109
|
+
AuthTypeMap = Hash.new { UnknownAuthType }
|
110
|
+
|
111
|
+
def self.create(buffer)
|
112
|
+
buffer.position = 5
|
113
|
+
authtype = buffer.read_int32_network
|
114
|
+
klass = AuthTypeMap[authtype]
|
115
|
+
obj = klass.allocate
|
116
|
+
obj.parse(buffer)
|
117
|
+
obj
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.register_auth_type(type)
|
121
|
+
raise "duplicate auth type registration" if AuthTypeMap.has_key?(type)
|
122
|
+
AuthTypeMap[type] = self
|
123
|
+
self.const_set(:AuthType, type)
|
124
|
+
class_eval "def auth_type() AuthType end"
|
125
|
+
end
|
126
|
+
|
127
|
+
# the dump method of class Message
|
128
|
+
alias message__dump dump
|
129
|
+
|
130
|
+
def dump
|
131
|
+
super(4) do |buffer|
|
132
|
+
buffer.write_int32_network(self.auth_type)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def parse(buffer)
|
137
|
+
super do
|
138
|
+
auth_t = buffer.read_int32_network
|
139
|
+
raise ParseError unless auth_t == self.auth_type
|
140
|
+
yield if block_given?
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class UnknownAuthType < Authentification
|
146
|
+
end
|
147
|
+
|
148
|
+
class AuthentificationOk < Authentification
|
149
|
+
register_auth_type 0
|
150
|
+
end
|
151
|
+
|
152
|
+
class AuthentificationKerberosV4 < Authentification
|
153
|
+
register_auth_type 1
|
154
|
+
end
|
155
|
+
|
156
|
+
class AuthentificationKerberosV5 < Authentification
|
157
|
+
register_auth_type 2
|
158
|
+
end
|
159
|
+
|
160
|
+
class AuthentificationClearTextPassword < Authentification
|
161
|
+
register_auth_type 3
|
162
|
+
end
|
163
|
+
|
164
|
+
module SaltedAuthentificationMixin
|
165
|
+
attr_accessor :salt
|
166
|
+
|
167
|
+
def initialize(salt)
|
168
|
+
@salt = salt
|
169
|
+
end
|
170
|
+
|
171
|
+
def dump
|
172
|
+
raise DumpError unless @salt.size == self.salt_size
|
173
|
+
|
174
|
+
message__dump(4 + self.salt_size) do |buffer|
|
175
|
+
buffer.write_int32_network(self.auth_type)
|
176
|
+
buffer.write(@salt)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def parse(buffer)
|
181
|
+
super do
|
182
|
+
@salt = buffer.read(self.salt_size)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class AuthentificationCryptPassword < Authentification
|
188
|
+
register_auth_type 4
|
189
|
+
include SaltedAuthentificationMixin
|
190
|
+
def salt_size; 2 end
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
class AuthentificationMD5Password < Authentification
|
195
|
+
register_auth_type 5
|
196
|
+
include SaltedAuthentificationMixin
|
197
|
+
def salt_size; 4 end
|
198
|
+
end
|
199
|
+
|
200
|
+
class AuthentificationSCMCredential < Authentification
|
201
|
+
register_auth_type 6
|
202
|
+
end
|
203
|
+
|
204
|
+
class PasswordMessage < Message
|
205
|
+
register_message_type 'p'
|
206
|
+
fields :password
|
207
|
+
|
208
|
+
def dump
|
209
|
+
super(@password.size + 1) do |buffer|
|
210
|
+
buffer.write_cstring(@password)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def parse(buffer)
|
215
|
+
super do
|
216
|
+
@password = buffer.read_cstring
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
class ParameterStatus < Message
|
222
|
+
register_message_type 'S'
|
223
|
+
fields :key, :value
|
224
|
+
|
225
|
+
def dump
|
226
|
+
super(@key.size + 1 + @value.size + 1) do |buffer|
|
227
|
+
buffer.write_cstring(@key)
|
228
|
+
buffer.write_cstring(@value)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def parse(buffer)
|
233
|
+
super do
|
234
|
+
@key = buffer.read_cstring
|
235
|
+
@value = buffer.read_cstring
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
class BackendKeyData < Message
|
241
|
+
register_message_type 'K'
|
242
|
+
fields :process_id, :secret_key
|
243
|
+
|
244
|
+
def dump
|
245
|
+
super(4 + 4) do |buffer|
|
246
|
+
buffer.write_int32_network(@process_id)
|
247
|
+
buffer.write_int32_network(@secret_key)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def parse(buffer)
|
252
|
+
super do
|
253
|
+
@process_id = buffer.read_int32_network
|
254
|
+
@secret_key = buffer.read_int32_network
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
class ReadyForQuery < Message
|
260
|
+
register_message_type 'Z'
|
261
|
+
fields :backend_transaction_status_indicator
|
262
|
+
|
263
|
+
def dump
|
264
|
+
super(1) do |buffer|
|
265
|
+
buffer.write_byte(@backend_transaction_status_indicator)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def parse(buffer)
|
270
|
+
super do
|
271
|
+
@backend_transaction_status_indicator = buffer.read_byte
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
class DataRow < Message
|
277
|
+
register_message_type 'D'
|
278
|
+
fields :columns
|
279
|
+
|
280
|
+
def dump
|
281
|
+
sz = @columns.inject(2) {|sum, col| sum + 4 + (col ? col.size : 0)}
|
282
|
+
super(sz) do |buffer|
|
283
|
+
buffer.write_int16_network(@columns.size)
|
284
|
+
@columns.each {|col|
|
285
|
+
buffer.write_int32_network(col ? col.size : -1)
|
286
|
+
buffer.write(col) if col
|
287
|
+
}
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def parse(buffer)
|
292
|
+
super do
|
293
|
+
n_cols = buffer.read_int16_network
|
294
|
+
@columns = (1..n_cols).collect {
|
295
|
+
len = buffer.read_int32_network
|
296
|
+
if len == -1
|
297
|
+
nil
|
298
|
+
else
|
299
|
+
buffer.read(len)
|
300
|
+
end
|
301
|
+
}
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
class CommandComplete < Message
|
307
|
+
register_message_type 'C'
|
308
|
+
fields :cmd_tag
|
309
|
+
|
310
|
+
def dump
|
311
|
+
super(@cmd_tag.size + 1) do |buffer|
|
312
|
+
buffer.write_cstring(@cmd_tag)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def parse(buffer)
|
317
|
+
super do
|
318
|
+
@cmd_tag = buffer.read_cstring
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
class EmptyQueryResponse < Message
|
324
|
+
register_message_type 'I'
|
325
|
+
end
|
326
|
+
|
327
|
+
module NoticeErrorMixin
|
328
|
+
attr_accessor :field_type, :field_values
|
329
|
+
|
330
|
+
def initialize(field_type=0, field_values=[])
|
331
|
+
raise ArgumentError if field_type == 0 and not field_values.empty?
|
332
|
+
@field_type, @field_values = field_type, field_values
|
333
|
+
end
|
334
|
+
|
335
|
+
def dump
|
336
|
+
raise ArgumentError if @field_type == 0 and not @field_values.empty?
|
337
|
+
|
338
|
+
sz = 1
|
339
|
+
sz += @field_values.inject(1) {|sum, fld| sum + fld.size + 1} unless @field_type == 0
|
340
|
+
|
341
|
+
super(sz) do |buffer|
|
342
|
+
buffer.write_byte(@field_type)
|
343
|
+
break if @field_type == 0
|
344
|
+
@field_values.each {|fld| buffer.write_cstring(fld) }
|
345
|
+
buffer.write_byte(0)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def parse(buffer)
|
350
|
+
super do
|
351
|
+
@field_type = buffer.read_byte
|
352
|
+
break if @field_type == 0
|
353
|
+
@field_values = []
|
354
|
+
while buffer.position < buffer.size-1
|
355
|
+
@field_values << buffer.read_cstring
|
356
|
+
end
|
357
|
+
terminator = buffer.read_byte
|
358
|
+
raise ParseError unless terminator == 0
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
class NoticeResponse < Message
|
364
|
+
register_message_type 'N'
|
365
|
+
include NoticeErrorMixin
|
366
|
+
end
|
367
|
+
|
368
|
+
class ErrorResponse < Message
|
369
|
+
register_message_type 'E'
|
370
|
+
include NoticeErrorMixin
|
371
|
+
end
|
372
|
+
|
373
|
+
# TODO
|
374
|
+
class CopyInResponse < Message
|
375
|
+
register_message_type 'G'
|
376
|
+
end
|
377
|
+
|
378
|
+
# TODO
|
379
|
+
class CopyOutResponse < Message
|
380
|
+
register_message_type 'H'
|
381
|
+
end
|
382
|
+
|
383
|
+
class Parse < Message
|
384
|
+
register_message_type 'P'
|
385
|
+
fields :query, :stmt_name, :parameter_oids
|
386
|
+
|
387
|
+
def initialize(query, stmt_name="", parameter_oids=[])
|
388
|
+
@query, @stmt_name, @parameter_oids = query, stmt_name, parameter_oids
|
389
|
+
end
|
390
|
+
|
391
|
+
def dump
|
392
|
+
sz = @stmt_name.size + 1 + @query.size + 1 + 2 + (4 * @parameter_oids.size)
|
393
|
+
super(sz) do |buffer|
|
394
|
+
buffer.write_cstring(@stmt_name)
|
395
|
+
buffer.write_cstring(@query)
|
396
|
+
buffer.write_int16_network(@parameter_oids.size)
|
397
|
+
@parameter_oids.each {|oid| buffer.write_int32_network(oid) }
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
def parse(buffer)
|
402
|
+
super do
|
403
|
+
@stmt_name = buffer.read_cstring
|
404
|
+
@query = buffer.read_cstring
|
405
|
+
n_oids = buffer.read_int16_network
|
406
|
+
@parameter_oids = (1..n_oids).collect {
|
407
|
+
# TODO: zero means unspecified. map to nil?
|
408
|
+
buffer.read_int32_network
|
409
|
+
}
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
class ParseComplete < Message
|
415
|
+
register_message_type '1'
|
416
|
+
end
|
417
|
+
|
418
|
+
class Query < Message
|
419
|
+
register_message_type 'Q'
|
420
|
+
fields :query
|
421
|
+
|
422
|
+
def dump
|
423
|
+
super(@query.size + 1) do |buffer|
|
424
|
+
buffer.write_cstring(@query)
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
def parse(buffer)
|
429
|
+
super do
|
430
|
+
@query = buffer.read_cstring
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
class RowDescription < Message
|
436
|
+
register_message_type 'T'
|
437
|
+
fields :fields
|
438
|
+
|
439
|
+
class FieldInfo < Struct.new(:name, :oid, :attr_nr, :type_oid, :typlen, :atttypmod, :formatcode); end
|
440
|
+
|
441
|
+
def dump
|
442
|
+
sz = @fields.inject(2) {|sum, fld| sum + 18 + fld.name.size + 1 }
|
443
|
+
super(sz) do |buffer|
|
444
|
+
buffer.write_int16_network(@fields.size)
|
445
|
+
@fields.each { |f|
|
446
|
+
buffer.write_cstring(f.name)
|
447
|
+
buffer.write_int32_network(f.oid)
|
448
|
+
buffer.write_int16_network(f.attr_nr)
|
449
|
+
buffer.write_int32_network(f.type_oid)
|
450
|
+
buffer.write_int16_network(f.typlen)
|
451
|
+
buffer.write_int32_network(f.atttypmod)
|
452
|
+
buffer.write_int16_network(f.formatcode)
|
453
|
+
}
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
def parse(buffer)
|
458
|
+
super do
|
459
|
+
n_fields = buffer.read_int16_network
|
460
|
+
@fields = (1..n_fields).collect {
|
461
|
+
f = FieldInfo.new
|
462
|
+
f.name = buffer.read_cstring
|
463
|
+
f.oid = buffer.read_int32_network
|
464
|
+
f.attr_nr = buffer.read_int16_network
|
465
|
+
f.type_oid = buffer.read_int32_network
|
466
|
+
f.typlen = buffer.read_int16_network
|
467
|
+
f.atttypmod = buffer.read_int32_network
|
468
|
+
f.formatcode = buffer.read_int16_network
|
469
|
+
f
|
470
|
+
}
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
class StartupMessage < Message
|
476
|
+
fields :proto_version, :params
|
477
|
+
|
478
|
+
def dump
|
479
|
+
sz = @params.inject(4 + 4) {|sum, kv| sum + kv[0].size + 1 + kv[1].size + 1} + 1
|
480
|
+
|
481
|
+
buffer = Buffer.of_size(sz)
|
482
|
+
buffer.write_int32_network(sz)
|
483
|
+
buffer.write_int32_network(@proto_version)
|
484
|
+
@params.each_pair {|key, value|
|
485
|
+
buffer.write_cstring(key)
|
486
|
+
buffer.write_cstring(value)
|
487
|
+
}
|
488
|
+
buffer.write_byte(0)
|
489
|
+
|
490
|
+
raise DumpError unless buffer.at_end?
|
491
|
+
return buffer.content
|
492
|
+
end
|
493
|
+
|
494
|
+
def parse(buffer)
|
495
|
+
buffer.position = 4
|
496
|
+
|
497
|
+
@proto_version = buffer.read_int32_network
|
498
|
+
@params = {}
|
499
|
+
|
500
|
+
while buffer.position < buffer.size-1
|
501
|
+
key = buffer.read_cstring
|
502
|
+
val = buffer.read_cstring
|
503
|
+
@params[key] = val
|
504
|
+
end
|
505
|
+
|
506
|
+
nul = buffer.read_byte
|
507
|
+
raise ParseError unless nul == 0
|
508
|
+
raise ParseError unless buffer.at_end?
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
class SSLRequest < Message
|
513
|
+
fields :ssl_request_code
|
514
|
+
|
515
|
+
def dump
|
516
|
+
sz = 4 + 4
|
517
|
+
buffer = Buffer.of_size(sz)
|
518
|
+
buffer.write_int32_network(sz)
|
519
|
+
buffer.write_int32_network(@ssl_request_code)
|
520
|
+
raise DumpError unless buffer.at_end?
|
521
|
+
return buffer.content
|
522
|
+
end
|
523
|
+
|
524
|
+
def parse(buffer)
|
525
|
+
buffer.position = 4
|
526
|
+
@ssl_request_code = buffer.read_int32_network
|
527
|
+
raise ParseError unless buffer.at_end?
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
=begin
|
532
|
+
# TODO: duplicate message-type, split into client/server messages
|
533
|
+
class Sync < Message
|
534
|
+
register_message_type 'S'
|
535
|
+
end
|
536
|
+
=end
|
537
|
+
|
538
|
+
class Terminate < Message
|
539
|
+
register_message_type 'X'
|
540
|
+
end
|
541
|
+
|
542
|
+
end # module PostgresPR
|