postgres-pr 0.0.1

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.
@@ -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: []