postgres-pr-opt 0.6.9

Sign up to get free protection for your applications and to get access to all the features.
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