postgres-pr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ $LOAD_PATH.unshift "../lib"
2
+ require 'postgres-pr/message'
3
+ require 'socket'
4
+
5
+ s = UNIXSocket.new(ARGV.shift || "/tmp/.s.PGSQL.5432")
6
+
7
+ msg = StartupMessage.new(196608, "user" => "mneumann", "database" => "mneumann")
8
+ s << msg.dump
9
+
10
+ Thread.start(s) { |s|
11
+ sleep 2
12
+ s << Query.new("drop table test").dump
13
+ s << Query.new("create table test (i int, v varchar(100))").dump
14
+ s << Parse.new("insert into test (i, v) values ($1, $2)", "blah").dump
15
+ s << Query.new("EXECUTE blah(1, 'hallo')").dump
16
+
17
+ while not (line = gets.chomp).empty?
18
+ s << Query.new(line).dump
19
+ end
20
+ exit
21
+ }
22
+
23
+ loop do
24
+ msg = Message.read(s)
25
+ p msg
26
+
27
+ case msg
28
+ when AuthentificationOk
29
+ p "OK"
30
+ when ErrorResponse
31
+ p "FAILED"
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH.unshift "../lib"
2
+ require 'postgres-pr/message'
3
+ require 'socket'
4
+
5
+ s = UNIXServer.open(ARGV.shift || raise).accept
6
+ startup = true
7
+ loop do
8
+ msg = Message.read(s, startup)
9
+ p msg
10
+ startup = false
11
+ end
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift '../lib'
2
+ require 'postgres-pr/connection'
3
+
4
+ conn = Connection.new('mneumann', 'mneumann')
5
+ p conn.query("DROP TABLE test; CREATE TABLE test (a VARCHAR(100))")
6
+ p conn.query("INSERT INTO test VALUES ('hallo')")
7
+ p conn.query("INSERT INTO test VALUES ('leute')")
8
+ conn.query("COMMIT")
9
+
10
+ conn.query("BEGIN")
11
+ 10000.times do |i|
12
+ p i
13
+ conn.query("INSERT INTO test VALUES ('#{i}')")
14
+ end
15
+ conn.query("COMMIT")
16
+
17
+ p conn.query("SELECT * FROM test")
@@ -0,0 +1,120 @@
1
+ require 'byteorder'
2
+
3
+ # This mixin solely depends on method read(n), which must be defined
4
+ # in the class/module where you mixin this module.
5
+ module BinaryReaderMixin
6
+
7
+ # == 8 bit
8
+
9
+ # no byteorder for 8 bit!
10
+
11
+ def read_word8
12
+ ru(1, 'C')
13
+ end
14
+
15
+ def read_int8
16
+ ru(1, 'c')
17
+ end
18
+
19
+ alias read_byte read_word8
20
+
21
+ # == 16 bit
22
+
23
+ # === Unsigned
24
+
25
+ def read_word16_native
26
+ ru(2, 'S')
27
+ end
28
+
29
+ def read_word16_little
30
+ ru(2, 'v')
31
+ end
32
+
33
+ def read_word16_big
34
+ ru(2, 'n')
35
+ end
36
+
37
+ # === Signed
38
+
39
+ def read_int16_native
40
+ ru(2, 's')
41
+ end
42
+
43
+ def read_int16_little
44
+ # swap bytes if native=big (but we want little)
45
+ ru_swap(2, 's', ByteOrder::Big)
46
+ end
47
+
48
+ def read_int16_big
49
+ # swap bytes if native=little (but we want big)
50
+ ru_swap(2, 's', ByteOrder::Little)
51
+ end
52
+
53
+ # == 32 bit
54
+
55
+ # === Unsigned
56
+
57
+ def read_word32_native
58
+ ru(4, 'L')
59
+ end
60
+
61
+ def read_word32_little
62
+ ru(4, 'V')
63
+ end
64
+
65
+ def read_word32_big
66
+ ru(4, 'N')
67
+ end
68
+
69
+ # === Signed
70
+
71
+ def read_int32_native
72
+ ru(4, 'l')
73
+ end
74
+
75
+ def read_int32_little
76
+ # swap bytes if native=big (but we want little)
77
+ ru_swap(4, 'l', ByteOrder::Big)
78
+ end
79
+
80
+ def read_int32_big
81
+ # swap bytes if native=little (but we want big)
82
+ ru_swap(4, 'l', ByteOrder::Little)
83
+ end
84
+
85
+ # == Aliases
86
+
87
+ alias read_uint8 read_word8
88
+
89
+ # add some short-cut functions
90
+ %w(word16 int16 word32 int32).each do |typ|
91
+ alias_method "read_#{typ}_network", "read_#{typ}_big"
92
+ end
93
+
94
+ {:word16 => :uint16, :word32 => :uint32}.each do |old, new|
95
+ ['_native', '_little', '_big', '_network'].each do |bo|
96
+ alias_method "read_#{new}#{bo}", "read_#{old}#{bo}"
97
+ end
98
+ end
99
+
100
+ # read exactly n characters, otherwise raise an exception.
101
+ def readn(n)
102
+ str = read(n)
103
+ raise "couldn't read #{n} characters" if str.nil? or str.size != n
104
+ str
105
+ end
106
+
107
+ private
108
+
109
+ # shortcut method for readn+unpack
110
+ def ru(size, template)
111
+ readn(size).unpack(template).first
112
+ end
113
+
114
+ # same as method +ru+, but swap bytes if native byteorder == _byteorder_
115
+ def ru_swap(size, template, byteorder)
116
+ str = readn(size)
117
+ str.reverse! if ByteOrder.byteorder == byteorder
118
+ str.unpack(template).first
119
+ end
120
+ end
@@ -0,0 +1,100 @@
1
+ require 'byteorder'
2
+
3
+ module BinaryWriterMixin
4
+
5
+ # == 8 bit
6
+
7
+ # no byteorder for 8 bit!
8
+
9
+ def write_word8(val)
10
+ pw(val, 'C')
11
+ end
12
+
13
+ def write_int8(val)
14
+ pw(val, 'c')
15
+ end
16
+
17
+ alias write_byte write_word8
18
+
19
+ # == 16 bit
20
+
21
+ # === Unsigned
22
+
23
+ def write_word16_native(val)
24
+ pw(val, 'S')
25
+ end
26
+
27
+ def write_word16_little(val)
28
+ str = [val].pack('S')
29
+ str.reverse! if ByteOrder.network? # swap bytes as native=network (and we want little)
30
+ write(str)
31
+ end
32
+
33
+ def write_word16_network(val)
34
+ str = [val].pack('S')
35
+ str.reverse! if ByteOrder.little? # swap bytes as native=little (and we want network)
36
+ write(str)
37
+ end
38
+
39
+ # === Signed
40
+
41
+ def write_int16_native(val)
42
+ pw(val, 's')
43
+ end
44
+
45
+ def write_int16_little(val)
46
+ pw(val, 'v')
47
+ end
48
+
49
+ def write_int16_network(val)
50
+ pw(val, 'n')
51
+ end
52
+
53
+ # == 32 bit
54
+
55
+ # === Unsigned
56
+
57
+ def write_word32_native(val)
58
+ pw(val, 'L')
59
+ end
60
+
61
+ def write_word32_little(val)
62
+ str = [val].pack('L')
63
+ str.reverse! if ByteOrder.network? # swap bytes as native=network (and we want little)
64
+ write(str)
65
+ end
66
+
67
+ def write_word32_network(val)
68
+ str = [val].pack('L')
69
+ str.reverse! if ByteOrder.little? # swap bytes as native=little (and we want network)
70
+ write(str)
71
+ end
72
+
73
+ # === Signed
74
+
75
+ def write_int32_native(val)
76
+ pw(val, 'l')
77
+ end
78
+
79
+ def write_int32_little(val)
80
+ pw(val, 'V')
81
+ end
82
+
83
+ def write_int32_network(val)
84
+ pw(val, 'N')
85
+ end
86
+
87
+ # add some short-cut functions
88
+ %w(word16 int16 word32 int32).each do |typ|
89
+ alias_method "write_#{typ}_big", "write_#{typ}_network"
90
+ end
91
+
92
+ # == Other methods
93
+
94
+ private
95
+
96
+ # shortcut for pack and write
97
+ def pw(val, template)
98
+ write([val].pack(template))
99
+ end
100
+ end
@@ -0,0 +1,87 @@
1
+ require 'binary_writer'
2
+ require 'binary_reader'
3
+
4
+ # Fixed size buffer.
5
+ class Buffer
6
+
7
+ class Error < RuntimeError; end
8
+ class EOF < Error; end
9
+
10
+ def initialize(size)
11
+ raise ArgumentError if size < 0
12
+
13
+ @size = size
14
+ @position = 0
15
+ @content = "#" * @size
16
+ end
17
+
18
+ def size
19
+ @size
20
+ end
21
+
22
+ def position
23
+ @position
24
+ end
25
+
26
+ def position=(new_pos)
27
+ raise ArgumentError if new_pos < 0 or new_pos > @size
28
+ @position = new_pos
29
+ end
30
+
31
+ def at_end?
32
+ @position == @size
33
+ end
34
+
35
+ def content
36
+ @content
37
+ end
38
+
39
+ def read(n)
40
+ raise EOF, 'cannot read beyond the end of buffer' if @position + n > @size
41
+ str = @content[@position, n]
42
+ @position += n
43
+ str
44
+ end
45
+
46
+ def write(str)
47
+ sz = str.size
48
+ raise EOF, 'cannot write beyond the end of buffer' if @position + sz > @size
49
+ @content[@position, sz] = str
50
+ @position += sz
51
+ self
52
+ end
53
+
54
+ def copy_from_stream(stream, n)
55
+ raise ArgumentError if n < 0
56
+ while n > 0
57
+ str = stream.read(n)
58
+ write(str)
59
+ n -= str.size
60
+ end
61
+ end
62
+
63
+ def write_cstring(cstr)
64
+ raise ArgumentError, "Invalid Ruby/cstring" if cstr.include?("\000")
65
+ write(cstr)
66
+ write("\000")
67
+ end
68
+
69
+ # returns a Ruby string without the trailing NUL character
70
+ def read_cstring
71
+ nul_pos = @content.index(0, @position)
72
+ raise Error, "no cstring found!" unless nul_pos
73
+
74
+ sz = nul_pos - @position
75
+ str = @content[@position, sz]
76
+ @position += sz + 1
77
+ return str
78
+ end
79
+
80
+ # read till the end of the buffer
81
+ def read_rest
82
+ read(self.size-@position)
83
+ end
84
+
85
+ include BinaryWriterMixin
86
+ include BinaryReaderMixin
87
+ end
@@ -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
@@ -0,0 +1,101 @@
1
+ #
2
+ # Author:: Michael Neumann
3
+ # Copyright:: (c) 2004 by Michael Neumann
4
+ #
5
+
6
+ require 'postgres-pr/message'
7
+ require 'uri'
8
+ require 'socket'
9
+ require 'thread'
10
+
11
+ PROTO_VERSION = 196608
12
+
13
+ class Connection
14
+
15
+ # sync
16
+
17
+ def initialize(database, user, auth=nil, uri = "unix:/tmp/.s.PGSQL.5432")
18
+ raise unless @mutex.nil?
19
+
20
+ @mutex = Mutex.new
21
+
22
+ @mutex.synchronize {
23
+ @params = {}
24
+ establish_connection(uri)
25
+
26
+ @conn << StartupMessage.new(PROTO_VERSION, 'user' => user, 'database' => database).dump
27
+
28
+ loop do
29
+ msg = Message.read(@conn)
30
+ case msg
31
+ when AuthentificationOk
32
+ when ErrorResponse
33
+ raise
34
+ when NoticeResponse
35
+ # TODO
36
+ when ParameterStatus
37
+ @params[msg.key] = msg.value
38
+ when BackendKeyData
39
+ # TODO
40
+ #p msg
41
+ when ReadyForQuery
42
+ # TODO: use transaction status
43
+ break
44
+ else
45
+ raise "unhandled message type"
46
+ end
47
+ end
48
+ }
49
+ end
50
+
51
+ def query(sql)
52
+ @mutex.synchronize {
53
+ @conn << Query.dump(sql)
54
+
55
+ rows = []
56
+
57
+ loop do
58
+ msg = Message.read(@conn)
59
+ case msg
60
+ when DataRow
61
+ rows << msg.columns
62
+ when CommandComplete
63
+ when ReadyForQuery
64
+ break
65
+ when RowDescription
66
+ # TODO
67
+ when CopyInResponse
68
+ when CopyOutResponse
69
+ when EmptyQueryResponse
70
+ when ErrorResponse
71
+ p msg
72
+ raise
73
+ when NoticeResponse
74
+ # TODO
75
+ else
76
+ raise
77
+ end
78
+ end
79
+ rows
80
+ }
81
+ end
82
+
83
+ DEFAULT_PORT = 5432
84
+ DEFAULT_HOST = 'localhost'
85
+
86
+ private
87
+
88
+ # tcp://localhost:5432
89
+ # unix:/tmp/.s.PGSQL.5432
90
+ def establish_connection(uri)
91
+ u = URI.parse(uri)
92
+ case u.scheme
93
+ when 'tcp'
94
+ @conn = TCPSocket.new(u.host || DEFAULT_HOST, u.port || DEFAULT_PORT)
95
+ when 'unix'
96
+ @conn = UNIXSocket.new(u.path)
97
+ else
98
+ raise 'unrecognized uri scheme format (must be tcp or unix)'
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,473 @@
1
+ #
2
+ # Author:: Michael Neumann
3
+ # Copyright:: (c) 2004 by Michael Neumann
4
+ #
5
+
6
+ require 'buffer'
7
+ require 'readbytes'
8
+
9
+ class ParseError < RuntimeError; end
10
+ class DumpError < RuntimeError; end
11
+
12
+
13
+ # Base class representing a PostgreSQL protocol message
14
+ class Message
15
+ # One character message-typecode to class map
16
+ MsgTypeMap = Hash.new { UnknownMessageType }
17
+
18
+ def self.register_message_type(type)
19
+ raise ArgumentError if type < 0 or type > 255
20
+ raise "duplicate message type registration" if MsgTypeMap.has_key? type
21
+
22
+ MsgTypeMap[type] = self
23
+
24
+ self.const_set(:MsgType, type)
25
+ class_eval "def message_type; MsgType end"
26
+ end
27
+
28
+ def self.read(stream, startup=false)
29
+ type = stream.readbytes(1).unpack('C').first unless startup
30
+ length = stream.readbytes(4).unpack('N').first # FIXME: length should be signed, not unsigned
31
+
32
+ raise ParseError unless length >= 4
33
+
34
+ # initialize buffer
35
+ buffer = Buffer.new(startup ? length : 1+length)
36
+ buffer.write_byte(type) unless startup
37
+ buffer.write_int32_network(length)
38
+ buffer.copy_from_stream(stream, length-4)
39
+
40
+ (startup ? StartupMessage : MsgTypeMap[type]).create(buffer)
41
+ end
42
+
43
+ def self.create(buffer)
44
+ obj = allocate
45
+ obj.parse(buffer)
46
+ obj
47
+ end
48
+
49
+ def self.dump(*args)
50
+ new(*args).dump
51
+ end
52
+
53
+ def dump(body_size=0)
54
+ buffer = Buffer.new(5 + body_size)
55
+ buffer.write_byte(self.message_type)
56
+ buffer.write_int32_network(4 + body_size)
57
+ yield buffer if block_given?
58
+ raise DumpError unless buffer.at_end?
59
+ return buffer.content
60
+ end
61
+
62
+ def parse(buffer)
63
+ buffer.position = 5
64
+ yield buffer if block_given?
65
+ raise ParseError, buffer.inspect unless buffer.at_end?
66
+ end
67
+
68
+ def self.fields(*attribs)
69
+ names = attribs.map {|name, type| name.to_s}
70
+ arg_list = names.join(", ")
71
+ ivar_list = names.map {|name| "@" + name }.join(", ")
72
+ sym_list = names.map {|name| ":" + name }.join(", ")
73
+ class_eval %[
74
+ attr_accessor #{ sym_list }
75
+ def initialize(#{ arg_list })
76
+ #{ ivar_list } = #{ arg_list }
77
+ end
78
+ ]
79
+ end
80
+ end
81
+
82
+ class UnknownMessageType < Message
83
+ def dump
84
+ raise
85
+ end
86
+ end
87
+
88
+ class Authentification < Message
89
+ register_message_type ?R
90
+
91
+ AuthTypeMap = Hash.new { UnknownAuthType }
92
+
93
+ def self.create(buffer)
94
+ buffer.position = 5
95
+ authtype = buffer.read_int32_network
96
+ klass = AuthTypeMap[authtype]
97
+ obj = klass.allocate
98
+ obj.parse(buffer)
99
+ obj
100
+ end
101
+
102
+ def self.register_auth_type(type)
103
+ raise "duplicate auth type registration" if AuthTypeMap.has_key?(type)
104
+ AuthTypeMap[type] = self
105
+ self.const_set(:AuthType, type)
106
+ class_eval "def auth_type() AuthType end"
107
+ end
108
+
109
+ # the dump method of class Message
110
+ alias message__dump dump
111
+
112
+ def dump
113
+ super(4) do |buffer|
114
+ buffer.write_int32_network(self.auth_type)
115
+ end
116
+ end
117
+
118
+ def parse(buffer)
119
+ super do
120
+ auth_t = buffer.read_int32_network
121
+ raise ParseError unless auth_t == self.auth_type
122
+ yield if block_given?
123
+ end
124
+ end
125
+ end
126
+
127
+ class UnknownAuthType < Authentification
128
+ end
129
+
130
+ class AuthentificationOk < Authentification
131
+ register_auth_type 0
132
+ end
133
+
134
+ class AuthentificationKerberosV4 < Authentification
135
+ register_auth_type 1
136
+ end
137
+
138
+ class AuthentificationKerberosV5 < Authentification
139
+ register_auth_type 2
140
+ end
141
+
142
+ class AuthentificationClearTextPassword < Authentification
143
+ register_auth_type 3
144
+ end
145
+
146
+ module SaltedAuthentificationMixin
147
+ attr_accessor :salt
148
+
149
+ def initialize(salt)
150
+ @salt = salt
151
+ end
152
+
153
+ def dump
154
+ raise DumpError unless @salt.size == self.salt_size
155
+
156
+ message__dump(4 + self.salt_size) do |buffer|
157
+ buffer.write_int32_network(self.auth_type)
158
+ buffer.write(@salt)
159
+ end
160
+ end
161
+
162
+ def parse(buffer)
163
+ super do
164
+ @salt = buffer.read(self.salt_size)
165
+ end
166
+ end
167
+ end
168
+
169
+ class AuthentificationCryptPassword < Authentification
170
+ register_auth_type 4
171
+ include SaltedAuthentificationMixin
172
+ def salt_size; 2 end
173
+ end
174
+
175
+
176
+ class AuthentificationMD5Password < Authentification
177
+ register_auth_type 5
178
+ include SaltedAuthentificationMixin
179
+ def salt_size; 4 end
180
+ end
181
+
182
+ class AuthentificationSCMCredential < Authentification
183
+ register_auth_type 6
184
+ end
185
+
186
+ class ParameterStatus < Message
187
+ register_message_type ?S
188
+ fields :key, :value
189
+
190
+ def dump
191
+ super(@key.size + 1 + @value.size + 1) do |buffer|
192
+ buffer.write_cstring(@key)
193
+ buffer.write_cstring(@value)
194
+ end
195
+ end
196
+
197
+ def parse(buffer)
198
+ super do
199
+ @key = buffer.read_cstring
200
+ @value = buffer.read_cstring
201
+ end
202
+ end
203
+ end
204
+
205
+ class BackendKeyData < Message
206
+ register_message_type ?K
207
+ fields :process_id, :secret_key
208
+
209
+ def dump
210
+ super(4 + 4) do |buffer|
211
+ buffer.write_int32_network(@process_id)
212
+ buffer.write_int32_network(@secret_key)
213
+ end
214
+ end
215
+
216
+ def parse(buffer)
217
+ super do
218
+ @process_id = buffer.read_int32_network
219
+ @secret_key = buffer.read_int32_network
220
+ end
221
+ end
222
+ end
223
+
224
+ class ReadyForQuery < Message
225
+ register_message_type ?Z
226
+ fields :backend_transaction_status_indicator
227
+
228
+ def dump
229
+ super(1) do |buffer|
230
+ buffer.write_byte(@backend_transaction_status_indicator)
231
+ end
232
+ end
233
+
234
+ def parse(buffer)
235
+ super do
236
+ @backend_transaction_status_indicator = buffer.read_byte
237
+ end
238
+ end
239
+ end
240
+
241
+ class DataRow < Message
242
+ register_message_type ?D
243
+ fields :columns
244
+
245
+ def dump
246
+ sz = @columns.inject(2) {|sum, col| sum + 4 + (col ? col.size : 0)}
247
+ super(sz) do |buffer|
248
+ buffer.write_int16_network(@columns.size)
249
+ @columns.each {|col|
250
+ buffer.write_int32_network(col ? col.size : -1)
251
+ buffer.write(col) if col
252
+ }
253
+ end
254
+ end
255
+
256
+ def parse(buffer)
257
+ super do
258
+ n_cols = buffer.read_int16_network
259
+ @columns = (1..n_cols).collect {
260
+ len = buffer.read_int32_network
261
+ if len == -1
262
+ nil
263
+ else
264
+ buffer.read(len)
265
+ end
266
+ }
267
+ end
268
+ end
269
+ end
270
+
271
+ class CommandComplete < Message
272
+ register_message_type ?C
273
+ fields :cmd_tag
274
+
275
+ def dump
276
+ super(@cmd_tag.size + 1) do |buffer|
277
+ buffer.write_cstring(@cmd_tag)
278
+ end
279
+ end
280
+
281
+ def parse(buffer)
282
+ super do
283
+ @cmd_tag = buffer.read_cstring
284
+ end
285
+ end
286
+ end
287
+
288
+ class EmptyQueryResponse < Message
289
+ register_message_type ?I
290
+ end
291
+
292
+ module NoticeErrorMixin
293
+ attr_accessor :field_type, :field_values
294
+
295
+ def initialize(field_type=0, field_values=[])
296
+ raise ArgumentError if field_type == 0 and not field_values.empty?
297
+ @field_type, @field_values = field_type, field_values
298
+ end
299
+
300
+ def dump
301
+ raise ArgumentError if @field_type == 0 and not @field_values.empty?
302
+
303
+ sz = 1
304
+ sz += @field_values.inject(1) {|sum, fld| sum + fld.size + 1} unless @field_type == 0
305
+
306
+ super(sz) do |buffer|
307
+ buffer.write_byte(@field_type)
308
+ break if @field_type == 0
309
+ @field_values.each {|fld| buffer.write_cstring(fld) }
310
+ buffer.write_byte(0)
311
+ end
312
+ end
313
+
314
+ def parse(buffer)
315
+ super do
316
+ @field_type = buffer.read_byte
317
+ break if @field_type == 0
318
+ @field_values = []
319
+ while buffer.position < buffer.size-1
320
+ @field_values << buffer.read_cstring
321
+ end
322
+ terminator = buffer.read_byte
323
+ raise ParseError unless terminator == 0
324
+ end
325
+ end
326
+ end
327
+
328
+ class NoticeResponse < Message
329
+ register_message_type ?N
330
+ include NoticeErrorMixin
331
+ end
332
+
333
+ class ErrorResponse < Message
334
+ register_message_type ?E
335
+ include NoticeErrorMixin
336
+ end
337
+
338
+ # TODO
339
+ class CopyInResponse < Message
340
+ register_message_type ?G
341
+ end
342
+
343
+ # TODO
344
+ class CopyOutResponse < Message
345
+ register_message_type ?H
346
+ end
347
+
348
+ class Parse < Message
349
+ register_message_type ?P
350
+ fields :query, :stmt_name, :parameter_oids
351
+
352
+ def initialize(query, stmt_name="", parameter_oids=[])
353
+ @query, @stmt_name, @parameter_oids = query, stmt_name, parameter_oids
354
+ end
355
+
356
+ def dump
357
+ sz = @stmt_name.size + 1 + @query.size + 1 + 2 + (4 * @parameter_oids.size)
358
+ super(sz) do |buffer|
359
+ buffer.write_cstring(@stmt_name)
360
+ buffer.write_cstring(@query)
361
+ buffer.write_int16_network(@parameter_oids.size)
362
+ @parameter_oids.each {|oid| buffer.write_int32_network(oid) }
363
+ end
364
+ end
365
+
366
+ def parse(buffer)
367
+ super do
368
+ @stmt_name = buffer.read_cstring
369
+ @query = buffer.read_cstring
370
+ n_oids = buffer.read_int16_network
371
+ @parameter_oids = (1..n_oids).collect {
372
+ # TODO: zero means unspecified. map to nil?
373
+ buffer.read_int32_network
374
+ }
375
+ end
376
+ end
377
+ end
378
+
379
+ class ParseComplete < Message
380
+ register_message_type ?1
381
+ end
382
+
383
+ class Query < Message
384
+ register_message_type ?Q
385
+ fields :query
386
+
387
+ def dump
388
+ super(@query.size + 1) do |buffer|
389
+ buffer.write_cstring(@query)
390
+ end
391
+ end
392
+
393
+ def parse(buffer)
394
+ super do
395
+ @query = buffer.read_cstring
396
+ end
397
+ end
398
+ end
399
+
400
+ class RowDescription < Message
401
+ register_message_type ?T
402
+
403
+ class FieldInfo < Struct.new(:name, :oid, :attr_nr, :type_oid, :typlen, :atttypmod, :formatcode); end
404
+
405
+ def dump
406
+ sz = @fields.inject(2) {|sum, fld| sum + 18 + fld.name.size + 1 }
407
+ super(sz) do |buffer|
408
+ buffer.write_int16_network(@fields.size)
409
+ @fields.each { |f|
410
+ buffer.write_cstring(f.name)
411
+ buffer.write_int32_network(f.oid)
412
+ buffer.write_int16_network(f.attr_nr)
413
+ buffer.write_int32_network(f.type_oid)
414
+ buffer.write_int16_network(f.typlen)
415
+ buffer.write_int32_network(f.atttypmod)
416
+ buffer.write_int16_network(f.formatcode)
417
+ }
418
+ end
419
+ end
420
+
421
+ def parse(buffer)
422
+ super do
423
+ n_fields = buffer.read_int16_network
424
+ @fields = (1..n_fields).collect {
425
+ f = FieldInfo.new
426
+ f.name = buffer.read_cstring
427
+ f.oid = buffer.read_int32_network
428
+ f.attr_nr = buffer.read_int16_network
429
+ f.type_oid = buffer.read_int32_network
430
+ f.typlen = buffer.read_int16_network
431
+ f.atttypmod = buffer.read_int32_network
432
+ f.formatcode = buffer.read_int16_network
433
+ }
434
+ end
435
+ end
436
+ end
437
+
438
+ class StartupMessage < Message
439
+ fields :proto_version, :params
440
+
441
+ def dump
442
+ sz = @params.inject(4 + 4) {|sum, kv| sum + kv[0].size + 1 + kv[1].size + 1} + 1
443
+
444
+ buffer = Buffer.new(sz)
445
+ buffer.write_int32_network(sz)
446
+ buffer.write_int32_network(@proto_version)
447
+ @params.each_pair {|key, value|
448
+ buffer.write_cstring(key)
449
+ buffer.write_cstring(value)
450
+ }
451
+ buffer.write_byte(0)
452
+
453
+ raise DumpError unless buffer.at_end?
454
+ return buffer.content
455
+ end
456
+
457
+ def parse(buffer)
458
+ buffer.position = 4
459
+
460
+ @proto_version = buffer.read_int32_network
461
+ @params = {}
462
+
463
+ while buffer.position < buffer.size-1
464
+ key = buffer.read_cstring
465
+ val = buffer.read_cstring
466
+ @params[key] = val
467
+ end
468
+
469
+ nul = buffer.read_byte
470
+ raise ParseError unless nul == 0
471
+ raise ParseError unless buffer.at_end?
472
+ end
473
+ end
@@ -0,0 +1,18 @@
1
+ require 'test/unit'
2
+ require 'conv'
3
+ require 'array'
4
+ require 'bytea'
5
+
6
+ class TC_Conversion < Test::Unit::TestCase
7
+ def test_decode_array
8
+ assert_equal ["abcdef ", "hallo", ["1", "2"]], decode_array("{ abcdef , hallo, { 1, 2} }")
9
+ assert_equal [""], decode_array("{ }") # TODO: Correct?
10
+ assert_equal [], decode_array("{}")
11
+ assert_equal ["hallo", ""], decode_array("{hallo,}")
12
+ end
13
+
14
+ def test_bytea
15
+ end
16
+
17
+ include Postgres::Conversion
18
+ end
@@ -0,0 +1,46 @@
1
+ require 'strscan'
2
+
3
+ module Postgres::Conversion
4
+
5
+ def decode_array(str, delim=',', &conv_proc)
6
+ delim = Regexp.escape(delim)
7
+ buf = StringScanner.new(str)
8
+ return parse_arr(buf, delim, &conv_proc)
9
+ ensure
10
+ raise ConversionError, "end of string expected (#{buf.rest})" unless buf.empty?
11
+ end
12
+
13
+ private
14
+
15
+ def parse_arr(buf, delim, &conv_proc)
16
+ # skip whitespace
17
+ buf.skip(/\s*/)
18
+
19
+ raise ConversionError, "'{' expected" unless buf.get_byte == '{'
20
+
21
+ elems = []
22
+ unless buf.scan(/\}/) # array is not empty
23
+ loop do
24
+ # skip whitespace
25
+ buf.skip(/\s+/)
26
+
27
+ elems <<
28
+ if buf.check(/\{/)
29
+ parse_arr(buf, delim, &conv_proc)
30
+ else
31
+ e = buf.scan(/("((\\.)|[^"])*"|\\.|[^\}#{ delim }])*/) || raise(ConversionError)
32
+ if conv_proc then conv_proc.call(e) else e end
33
+ end
34
+
35
+ break if buf.scan(/\}/)
36
+ break unless buf.scan(/#{ delim }/)
37
+ end
38
+ end
39
+
40
+ # skip whitespace
41
+ buf.skip(/\s*/)
42
+
43
+ elems
44
+ end
45
+
46
+ end
@@ -0,0 +1,26 @@
1
+ module Postgres::Conversion
2
+
3
+ #
4
+ # Encodes a string as bytea value.
5
+ #
6
+ # for encoding rules see:
7
+ # http://www.postgresql.org/docs/7.4/static/datatype-binary.html
8
+ #
9
+
10
+ def encode_bytea(str)
11
+ str.gsub(/[\000-\037\047\134\177-\377]/) {|b| "\\#{ b[0].to_s(8).rjust(3, '0') }" }
12
+ end
13
+
14
+ #
15
+ # Decodes a bytea encoded string.
16
+ #
17
+ # for decoding rules see:
18
+ # http://www.postgresql.org/docs/7.4/static/datatype-binary.html
19
+ #
20
+ def decode_bytea(str)
21
+ str.gsub(/\\(\\|'|[0-3][0-7][0-7])/) {|s|
22
+ if s.size == 2 then s[1,1] else s[1,3].oct.chr end
23
+ }
24
+ end
25
+
26
+ end
@@ -0,0 +1,5 @@
1
+ module Postgres
2
+ module Conversion
3
+ class ConversionError < Exception; end
4
+ end
5
+ end
@@ -0,0 +1,101 @@
1
+ require 'test/unit'
2
+ require 'stringio'
3
+
4
+ class Module
5
+ def attr_accessor(*attrs)
6
+ @@attrs = [] unless defined?(@@attrs)
7
+ @@attrs += attrs
8
+
9
+ x = @@attrs.map {|a| "self.#{a} == o.#{a}"}.join(" && ")
10
+ class_eval %{
11
+ def ==(o)
12
+ #{ x }
13
+ end
14
+ }
15
+
16
+ @@attrs.each do |a|
17
+ class_eval %{
18
+ def #{a}() @#{a} end
19
+ def #{a}=(v) @#{a}=v end
20
+ }
21
+ end
22
+ end
23
+ end
24
+
25
+ $LOAD_PATH.unshift '../lib'
26
+ require 'postgres-pr/message'
27
+ class Buffer
28
+ alias old_content content
29
+ def content
30
+ self
31
+ end
32
+ end
33
+
34
+ class Message
35
+ attr_accessor :buffer
36
+
37
+ class << self
38
+ alias old_create create
39
+ def create(buffer)
40
+ obj = old_create(buffer)
41
+ obj.buffer = buffer
42
+ obj
43
+ end
44
+ end
45
+
46
+ alias old_dump dump
47
+
48
+ def dump(body_size=0, &block)
49
+ buf = old_dump(body_size, &block)
50
+ self.buffer = buf
51
+ buf.old_content
52
+ end
53
+ end
54
+
55
+ class StartupMessage
56
+ alias old_dump dump
57
+ def dump
58
+ buf = old_dump
59
+ self.buffer = buf
60
+ buf.old_content
61
+ end
62
+ end
63
+
64
+ class StringIO
65
+ alias readbytes read
66
+ end
67
+
68
+ class TC_Message < Test::Unit::TestCase
69
+
70
+ CASES = [
71
+ #[AuthentificationOk],
72
+ #[ErrorResponse],
73
+ [ParameterStatus, "key", "value"],
74
+ [BackendKeyData, 234234234, 213434],
75
+ [ReadyForQuery, ?T],
76
+ # TODO: RowDescription
77
+ [DataRow, ["a", "bbbbbb", "ccc", nil, nil, "ddddd", "e" * 10_000]],
78
+ [DataRow, []],
79
+ [CommandComplete, "INSERT"],
80
+ [StartupMessage, 196608, {"user" => "mneumann", "database" => "mneumann"}],
81
+ [Parse, "INSERT INTO blah values (?, ?)", ""],
82
+ [Query, "SELECT * FROM test\nWHERE a='test'"]
83
+ ]
84
+
85
+ def test_pack_unpack_feature
86
+ assert_equal ['a', 'b'], "a\000b\000".unpack('Z*Z*')
87
+ end
88
+
89
+ def test_marshal_unmarshal
90
+ CASES.each do |klass, *params|
91
+ msg = klass.new(*params)
92
+ new_msg = Message.read(StringIO.new(msg.dump), klass == StartupMessage)
93
+ assert_equal(msg, new_msg)
94
+
95
+ msg1, msg2 = klass.new(*params), klass.new(*params)
96
+ msg1.dump
97
+ msg2.dump; msg2.parse(msg2.buffer)
98
+ assert_equal(msg1, msg2)
99
+ end
100
+ end
101
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.1
3
+ specification_version: 1
4
+ name: postgres-pr
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2004-11-18
8
+ summary: A pure Ruby interface to the PostgreSQL database
9
+ require_paths:
10
+ - lib
11
+ author: Michael Neumann
12
+ email: mneumann@ntecs.de
13
+ homepage: ruby-dbi.rubyforge.org
14
+ rubyforge_project: ruby-dbi
15
+ description:
16
+ autorequire:
17
+ default_executable:
18
+ bindir: bin
19
+ has_rdoc: false
20
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
21
+ requirements:
22
+ -
23
+ - ">"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.0.0
26
+ version:
27
+ platform: ruby
28
+ files:
29
+ - lib/postgres-pr
30
+ - lib/binary_writer.rb
31
+ - lib/byteorder.rb
32
+ - lib/binary_reader.rb
33
+ - lib/buffer.rb
34
+ - lib/postgres-pr/typeconv
35
+ - lib/postgres-pr/connection.rb
36
+ - lib/postgres-pr/message.rb
37
+ - lib/postgres-pr/typeconv/array.rb
38
+ - lib/postgres-pr/typeconv/bytea.rb
39
+ - lib/postgres-pr/typeconv/conv.rb
40
+ - lib/postgres-pr/typeconv/TC_conv.rb
41
+ - test/TC_message.rb
42
+ - examples/client.rb
43
+ - examples/server.rb
44
+ - examples/test_connection.rb
45
+ test_files: []
46
+ rdoc_options: []
47
+ extra_rdoc_files: []
48
+ executables: []
49
+ extensions: []
50
+ requirements: []
51
+ dependencies: []