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 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
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -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
@@ -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
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift "../lib"
2
+ require 'postgres-pr/message'
3
+ require 'socket'
4
+ include PostgresPR
5
+
6
+ s = UNIXServer.open(ARGV.shift || raise).accept
7
+ startup = true
8
+ loop do
9
+ msg = Message.read(s, startup)
10
+ p msg
11
+ startup = false
12
+ end
@@ -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,8 @@
1
+ if RUBY_ENGINE == 'ruby'
2
+ require 'mkmf'
3
+ create_makefile("unpack_single")
4
+ else
5
+ File.open(File.dirname(__FILE__) + "/Makefile", 'w') do |f|
6
+ f.write("install:\n\t#nothing to build")
7
+ end
8
+ end
@@ -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,8 @@
1
+ # This is a compatibility layer for using the pure Ruby postgres-pr instead of
2
+ # the C interface of postgres.
3
+
4
+ begin
5
+ require 'pg.so'
6
+ rescue LoadError
7
+ require 'postgres-pr/pg-compat'
8
+ end
@@ -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