sequel-postgres-pr 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README +17 -0
- data/lib/postgres-pr/buffer.rb +132 -0
- data/lib/postgres-pr/connection.rb +161 -0
- data/lib/postgres-pr/message.rb +307 -0
- data/lib/postgres-pr/result.rb +40 -0
- data/lib/sequel/postgres-pr.rb +5 -0
- metadata +61 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f4dd2fbfe50a8241149a3e1c152222cf432167b8a23ad8a2a64809df42592401
|
4
|
+
data.tar.gz: 41bdfefa0423a5d6effb320eb84efe033d3dac64e831d055805d00d8aacb79b3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 06ae1e4aec9e24c03cf157984ba11fc66ff38064687050ce04ba842390ec72a6a465ca20fc4ff64cd2fa3f2335654cdc72aa50220377d5a2b1d92d4d9e0ce36c
|
7
|
+
data.tar.gz: 4edf6e57f4fa8f65f3a39d53435189bf7645eae80f64dbd232e45720002de3152d827092c529754b9ee7e36177761f9543891dcc40499bf8f90e63fabe185514
|
data/README
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= sequel-postgres-pr
|
2
|
+
|
3
|
+
This is a library to access PostgreSQL from pure Ruby, without any C library,
|
4
|
+
with the goal of proper functioning with Sequel's postgres adapter.
|
5
|
+
|
6
|
+
The only supported PostgreSQL authentication methods are password and md5. SSL
|
7
|
+
is not supported. COPY and LISTEN are not supported.
|
8
|
+
|
9
|
+
This requires Sequel 5.59.0. For older versions of Sequel, please use the
|
10
|
+
jeremyevans-postgres-pr gem.
|
11
|
+
|
12
|
+
== Author and Copyright
|
13
|
+
|
14
|
+
Copyright (c) 2005, 2008 by Michael Neumann (mneumann@ntecs.de)
|
15
|
+
Copyright (c) 2009-2022 by Jeremy Evans (code@jeremyevans.net)
|
16
|
+
|
17
|
+
Released under the same terms of license as Ruby.
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# Fixed size buffer.
|
2
|
+
class PostgresPR::Buffer
|
3
|
+
|
4
|
+
class Error < RuntimeError; end
|
5
|
+
class EOF < Error; end
|
6
|
+
|
7
|
+
def self.of_size(size)
|
8
|
+
raise ArgumentError if size < 0
|
9
|
+
new('#' * size)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(content)
|
13
|
+
@size = content.size
|
14
|
+
@content = content
|
15
|
+
@position = 0
|
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
|
+
raise if n < 0
|
62
|
+
end
|
63
|
+
|
64
|
+
NUL = "\000"
|
65
|
+
|
66
|
+
def write_cstring(cstr)
|
67
|
+
raise ArgumentError, "Invalid Ruby/cstring" if cstr.include?(NUL)
|
68
|
+
write(cstr)
|
69
|
+
write(NUL)
|
70
|
+
end
|
71
|
+
|
72
|
+
# returns a Ruby string without the trailing NUL character
|
73
|
+
def read_cstring
|
74
|
+
nul_pos = @content.index(NUL, @position)
|
75
|
+
raise Error, "no cstring found!" unless nul_pos
|
76
|
+
|
77
|
+
sz = nul_pos - @position
|
78
|
+
str = @content[@position, sz]
|
79
|
+
@position += sz + 1
|
80
|
+
return str
|
81
|
+
end
|
82
|
+
|
83
|
+
IS_BIG_ENDIAN = [0x12345678].pack("L") == "\x12\x34\x56\x78"
|
84
|
+
private_constant :IS_BIG_ENDIAN
|
85
|
+
|
86
|
+
def read_byte
|
87
|
+
ru(1, 'C')
|
88
|
+
end
|
89
|
+
|
90
|
+
def read_int16
|
91
|
+
ru_swap(2, 's')
|
92
|
+
end
|
93
|
+
|
94
|
+
def read_int32
|
95
|
+
ru_swap(4, 'l')
|
96
|
+
end
|
97
|
+
|
98
|
+
def write_byte(val)
|
99
|
+
pw(val, 'C')
|
100
|
+
end
|
101
|
+
|
102
|
+
def write_int32(val)
|
103
|
+
pw(val, 'N')
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def pw(val, template)
|
109
|
+
write([val].pack(template))
|
110
|
+
end
|
111
|
+
|
112
|
+
# shortcut method for readn+unpack
|
113
|
+
def ru(size, template)
|
114
|
+
read(size).unpack(template).first
|
115
|
+
end
|
116
|
+
|
117
|
+
if [0x12345678].pack("L") == "\x12\x34\x56\x78"
|
118
|
+
# :nocov:
|
119
|
+
# Big Endian
|
120
|
+
def ru_swap(size, template)
|
121
|
+
read(size).unpack(template).first
|
122
|
+
end
|
123
|
+
# :nocov:
|
124
|
+
else
|
125
|
+
# Little Endian
|
126
|
+
def ru_swap(size, template)
|
127
|
+
str = read(size)
|
128
|
+
str.reverse!
|
129
|
+
str.unpack(template).first
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Michael Neumann
|
3
|
+
# Copyright:: (c) 2005 by Michael Neumann
|
4
|
+
# License:: Same as Ruby's or BSD
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'socket'
|
8
|
+
|
9
|
+
module PostgresPR
|
10
|
+
class Connection
|
11
|
+
CONNECTION_OK = -1
|
12
|
+
|
13
|
+
class << self
|
14
|
+
alias connect new
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns one of the following statuses:
|
18
|
+
#
|
19
|
+
# PQTRANS_IDLE = 0 (connection idle)
|
20
|
+
# PQTRANS_INTRANS = 2 (idle, within transaction block)
|
21
|
+
# PQTRANS_INERROR = 3 (idle, within failed transaction)
|
22
|
+
# PQTRANS_UNKNOWN = 4 (cannot determine status)
|
23
|
+
#
|
24
|
+
# Not yet implemented is:
|
25
|
+
#
|
26
|
+
# PQTRANS_ACTIVE = 1 (command in progress)
|
27
|
+
#
|
28
|
+
def transaction_status
|
29
|
+
case @transaction_status
|
30
|
+
when 73 # I
|
31
|
+
0
|
32
|
+
when 84 # T
|
33
|
+
2
|
34
|
+
when 69 # E
|
35
|
+
3
|
36
|
+
else
|
37
|
+
4
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(host, port, _, _, database, user, password)
|
42
|
+
@conn = _connection(host, port)
|
43
|
+
@transaction_status = nil
|
44
|
+
@params = {}
|
45
|
+
|
46
|
+
@conn << StartupMessage.new('user' => user, 'database' => database).dump
|
47
|
+
|
48
|
+
while true
|
49
|
+
msg = Message.read(@conn)
|
50
|
+
|
51
|
+
case msg
|
52
|
+
when AuthentificationClearTextPassword
|
53
|
+
check_password!(password)
|
54
|
+
@conn << PasswordMessage.new(password).dump
|
55
|
+
when AuthentificationMD5Password
|
56
|
+
check_password!(password)
|
57
|
+
require 'digest/md5'
|
58
|
+
@conn << PasswordMessage.new("md5#{Digest::MD5.hexdigest(Digest::MD5.hexdigest(password + user) << msg.salt)}").dump
|
59
|
+
when ErrorResponse
|
60
|
+
raise PGError, msg.field_values.join("\t")
|
61
|
+
when ReadyForQuery
|
62
|
+
@transaction_status = msg.backend_transaction_status_indicator
|
63
|
+
break
|
64
|
+
when AuthentificationOk, NoticeResponse, ParameterStatus, BackendKeyData
|
65
|
+
# ignore
|
66
|
+
when UnknownAuthType
|
67
|
+
raise PGError, "unhandled authentication type: #{msg.auth_type}"
|
68
|
+
else
|
69
|
+
raise PGError, "unhandled message type"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def finish
|
75
|
+
check_connection_open!
|
76
|
+
@conn.shutdown
|
77
|
+
@conn = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def async_exec(sql)
|
81
|
+
check_connection_open!
|
82
|
+
@conn << Query.dump(sql)
|
83
|
+
|
84
|
+
rows = []
|
85
|
+
errors = []
|
86
|
+
|
87
|
+
while true
|
88
|
+
msg = Message.read(@conn)
|
89
|
+
case msg
|
90
|
+
when DataRow
|
91
|
+
rows << msg.columns
|
92
|
+
when CommandComplete
|
93
|
+
cmd_tag = msg.cmd_tag
|
94
|
+
when ReadyForQuery
|
95
|
+
@transaction_status = msg.backend_transaction_status_indicator
|
96
|
+
break
|
97
|
+
when RowDescription
|
98
|
+
fields = msg.fields
|
99
|
+
when ErrorResponse
|
100
|
+
errors << msg
|
101
|
+
when NoticeResponse, EmptyQueryResponse
|
102
|
+
# ignore
|
103
|
+
else
|
104
|
+
raise PGError, "unhandled message type"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
raise(PGError, errors.map{|e| e.field_values.join("\t") }.join("\n")) unless errors.empty?
|
109
|
+
|
110
|
+
Result.new(fields||[], rows, cmd_tag)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Escape bytea values. Uses historical format instead of hex
|
114
|
+
# format for maximum compatibility.
|
115
|
+
def escape_bytea(str)
|
116
|
+
str.gsub(/[\000-\037\047\134\177-\377]/n){|b| "\\#{sprintf('%o', b.each_byte{|x| break x}).rjust(3, '0')}"}
|
117
|
+
end
|
118
|
+
|
119
|
+
# Escape strings by doubling apostrophes. This only works if standard
|
120
|
+
# conforming strings are used.
|
121
|
+
def escape_string(str)
|
122
|
+
str.gsub("'", "''")
|
123
|
+
end
|
124
|
+
|
125
|
+
def block(timeout=nil)
|
126
|
+
end
|
127
|
+
|
128
|
+
def status
|
129
|
+
CONNECTION_OK
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def check_password!(password)
|
135
|
+
raise PGError, "no password specified" unless password
|
136
|
+
end
|
137
|
+
|
138
|
+
# Doesn't check the connection actually works, only checks that
|
139
|
+
# it hasn't been closed.
|
140
|
+
def check_connection_open!
|
141
|
+
raise(PGError, "connection already closed") if @conn.nil?
|
142
|
+
end
|
143
|
+
|
144
|
+
def _connection(host, port)
|
145
|
+
if host.nil?
|
146
|
+
if RUBY_PLATFORM =~ /mingw|win/i
|
147
|
+
TCPSocket.new('localhost', port)
|
148
|
+
else
|
149
|
+
UNIXSocket.new("/tmp/.s.PGSQL.#{port}")
|
150
|
+
end
|
151
|
+
elsif host.start_with?('/')
|
152
|
+
UNIXSocket.new(host)
|
153
|
+
else
|
154
|
+
TCPSocket.new(host, port)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
require_relative 'message'
|
161
|
+
require_relative 'result'
|
@@ -0,0 +1,307 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Michael Neumann
|
3
|
+
# Copyright:: (c) 2005 by Michael Neumann
|
4
|
+
# License:: Same as Ruby's or BSD
|
5
|
+
#
|
6
|
+
|
7
|
+
module PostgresPR
|
8
|
+
|
9
|
+
class PGError < StandardError; end
|
10
|
+
class ParseError < PGError; end
|
11
|
+
class DumpError < PGError; end
|
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
|
+
define_method(:message_type){type}
|
20
|
+
MsgTypeMap[type] = self
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.read(stream)
|
24
|
+
type = read_exactly_n_bytes(stream, 1)
|
25
|
+
length = read_exactly_n_bytes(stream, 4).unpack('N').first # FIXME: length should be signed, not unsigned
|
26
|
+
|
27
|
+
raise ParseError unless length >= 4
|
28
|
+
|
29
|
+
# initialize buffer
|
30
|
+
buffer = Buffer.of_size(1+length)
|
31
|
+
buffer.write(type)
|
32
|
+
buffer.write_int32(length)
|
33
|
+
buffer.copy_from_stream(stream, length-4)
|
34
|
+
|
35
|
+
MsgTypeMap[type].create(buffer)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.read_exactly_n_bytes(io, n)
|
39
|
+
buf = io.read(n)
|
40
|
+
raise EOFError unless buf && buf.size == n
|
41
|
+
buf
|
42
|
+
end
|
43
|
+
private_class_method :read_exactly_n_bytes
|
44
|
+
|
45
|
+
def self.create(buffer)
|
46
|
+
obj = allocate
|
47
|
+
obj.parse(buffer)
|
48
|
+
obj
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.dump(*args)
|
52
|
+
new(*args).dump
|
53
|
+
end
|
54
|
+
|
55
|
+
def dump(body_size=0)
|
56
|
+
buffer = Buffer.of_size(5 + body_size)
|
57
|
+
buffer.write(self.message_type)
|
58
|
+
buffer.write_int32(4 + body_size)
|
59
|
+
yield buffer
|
60
|
+
raise DumpError unless buffer.at_end?
|
61
|
+
return buffer.content
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse(buffer)
|
65
|
+
buffer.position = 5
|
66
|
+
yield buffer
|
67
|
+
raise ParseError, buffer.inspect unless buffer.at_end?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class UnknownMessageType < Message
|
72
|
+
def parse(buffer)
|
73
|
+
raise PGError, "unable to parse message type: #{buffer.content.inspect}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Authentification < Message
|
78
|
+
register_message_type 'R'
|
79
|
+
attr_reader :auth_type
|
80
|
+
|
81
|
+
AuthTypeMap = {}
|
82
|
+
|
83
|
+
def self.create(buffer)
|
84
|
+
buffer.position = 5
|
85
|
+
authtype = buffer.read_int32
|
86
|
+
klass = AuthTypeMap.fetch(authtype, UnknownAuthType)
|
87
|
+
obj = klass.allocate
|
88
|
+
obj.parse(buffer)
|
89
|
+
obj
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.register_auth_type(type)
|
93
|
+
AuthTypeMap[type] = self
|
94
|
+
end
|
95
|
+
|
96
|
+
def parse(buffer)
|
97
|
+
super do
|
98
|
+
@auth_type = buffer.read_int32
|
99
|
+
yield if block_given?
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class UnknownAuthType < Authentification
|
105
|
+
end
|
106
|
+
|
107
|
+
class AuthentificationOk < Authentification
|
108
|
+
register_auth_type 0
|
109
|
+
end
|
110
|
+
|
111
|
+
class AuthentificationClearTextPassword < Authentification
|
112
|
+
register_auth_type 3
|
113
|
+
end
|
114
|
+
|
115
|
+
class AuthentificationMD5Password < Authentification
|
116
|
+
register_auth_type 5
|
117
|
+
attr_accessor :salt
|
118
|
+
|
119
|
+
def parse(buffer)
|
120
|
+
super do
|
121
|
+
@salt = buffer.read(4)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class PasswordMessage < Message
|
127
|
+
register_message_type 'p'
|
128
|
+
|
129
|
+
def initialize(password)
|
130
|
+
@password = password
|
131
|
+
end
|
132
|
+
|
133
|
+
def dump
|
134
|
+
super(@password.size + 1) do |buffer|
|
135
|
+
buffer.write_cstring(@password)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class ParameterStatus < Message
|
141
|
+
register_message_type 'S'
|
142
|
+
|
143
|
+
def parse(buffer)
|
144
|
+
super do
|
145
|
+
buffer.read_cstring
|
146
|
+
buffer.read_cstring
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class BackendKeyData < Message
|
152
|
+
register_message_type 'K'
|
153
|
+
|
154
|
+
def parse(buffer)
|
155
|
+
super do
|
156
|
+
buffer.read(8)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class ReadyForQuery < Message
|
162
|
+
register_message_type 'Z'
|
163
|
+
attr_reader :backend_transaction_status_indicator
|
164
|
+
|
165
|
+
def parse(buffer)
|
166
|
+
super do
|
167
|
+
@backend_transaction_status_indicator = buffer.read_byte
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class DataRow < Message
|
173
|
+
register_message_type 'D'
|
174
|
+
attr_reader :columns
|
175
|
+
|
176
|
+
def parse(buffer)
|
177
|
+
super do
|
178
|
+
n_cols = buffer.read_int16
|
179
|
+
@columns = (1..n_cols).collect {
|
180
|
+
len = buffer.read_int32
|
181
|
+
if len == -1
|
182
|
+
nil
|
183
|
+
else
|
184
|
+
buffer.read(len)
|
185
|
+
end
|
186
|
+
}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class CommandComplete < Message
|
192
|
+
register_message_type 'C'
|
193
|
+
attr_reader :cmd_tag
|
194
|
+
|
195
|
+
def parse(buffer)
|
196
|
+
super do
|
197
|
+
@cmd_tag = buffer.read_cstring
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
class EmptyQueryResponse < Message
|
203
|
+
register_message_type 'I'
|
204
|
+
end
|
205
|
+
|
206
|
+
module NoticeErrorMixin
|
207
|
+
attr_reader :field_values
|
208
|
+
|
209
|
+
def parse(buffer)
|
210
|
+
super do
|
211
|
+
break if buffer.read_byte == 0
|
212
|
+
@field_values = []
|
213
|
+
while buffer.position < buffer.size-1
|
214
|
+
@field_values << buffer.read_cstring
|
215
|
+
end
|
216
|
+
terminator = buffer.read_byte
|
217
|
+
raise ParseError unless terminator == 0
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
class NoticeResponse < Message
|
223
|
+
register_message_type 'N'
|
224
|
+
include NoticeErrorMixin
|
225
|
+
end
|
226
|
+
|
227
|
+
class ErrorResponse < Message
|
228
|
+
register_message_type 'E'
|
229
|
+
include NoticeErrorMixin
|
230
|
+
end
|
231
|
+
|
232
|
+
class Query < Message
|
233
|
+
register_message_type 'Q'
|
234
|
+
attr_accessor :query
|
235
|
+
|
236
|
+
# :nocov:
|
237
|
+
if RUBY_VERSION < '2'
|
238
|
+
def initialize(query)
|
239
|
+
@query = String.new(query).force_encoding('BINARY')
|
240
|
+
end
|
241
|
+
# :nocov:
|
242
|
+
else
|
243
|
+
def initialize(query)
|
244
|
+
@query = query.b
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def dump
|
249
|
+
super(@query.size + 1) do |buffer|
|
250
|
+
buffer.write_cstring(@query)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
class RowDescription < Message
|
256
|
+
register_message_type 'T'
|
257
|
+
attr_reader :fields
|
258
|
+
|
259
|
+
class FieldInfo < Struct.new(:name, :oid, :attr_nr, :type_oid, :typlen, :atttypmod, :formatcode); end
|
260
|
+
|
261
|
+
def parse(buffer)
|
262
|
+
super do
|
263
|
+
nfields = buffer.read_int16
|
264
|
+
@fields = nfields.times.map do
|
265
|
+
FieldInfo.new(
|
266
|
+
buffer.read_cstring,
|
267
|
+
buffer.read_int32,
|
268
|
+
buffer.read_int16,
|
269
|
+
buffer.read_int32,
|
270
|
+
buffer.read_int16,
|
271
|
+
buffer.read_int32,
|
272
|
+
buffer.read_int16
|
273
|
+
)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
class StartupMessage < Message
|
280
|
+
PROTO_VERSION = 3 << 16 # 196608
|
281
|
+
|
282
|
+
def initialize(params)
|
283
|
+
@params = params
|
284
|
+
end
|
285
|
+
|
286
|
+
def dump
|
287
|
+
params = @params.reject{|k,v| v.nil?}
|
288
|
+
sz = params.inject(4 + 4) {|sum, kv| sum + kv[0].size + 1 + kv[1].size + 1} + 1
|
289
|
+
|
290
|
+
buffer = Buffer.of_size(sz)
|
291
|
+
buffer.write_int32(sz)
|
292
|
+
buffer.write_int32(PROTO_VERSION)
|
293
|
+
params.each_pair do |key, value|
|
294
|
+
buffer.write_cstring(key)
|
295
|
+
buffer.write_cstring(value)
|
296
|
+
end
|
297
|
+
buffer.write_byte(0)
|
298
|
+
buffer.content
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
class Terminate < Message
|
303
|
+
register_message_type 'X'
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
require_relative 'buffer'
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class PostgresPR::Result
|
2
|
+
def initialize(fields, rows, cmd_tag)
|
3
|
+
@fields = fields
|
4
|
+
@rows = rows
|
5
|
+
@cmd_tag = cmd_tag
|
6
|
+
end
|
7
|
+
|
8
|
+
def ntuples
|
9
|
+
@rows.size
|
10
|
+
end
|
11
|
+
|
12
|
+
def nfields
|
13
|
+
@fields.size
|
14
|
+
end
|
15
|
+
|
16
|
+
def fname(index)
|
17
|
+
@fields[index].name
|
18
|
+
end
|
19
|
+
|
20
|
+
def ftype(index)
|
21
|
+
@fields[index].type_oid
|
22
|
+
end
|
23
|
+
|
24
|
+
def getvalue(tup_num, field_num)
|
25
|
+
@rows[tup_num][field_num]
|
26
|
+
end
|
27
|
+
|
28
|
+
# free the result set
|
29
|
+
def clear
|
30
|
+
@fields = @rows = @cmd_tag = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the number of rows affected by the SQL command
|
34
|
+
def cmd_tuples
|
35
|
+
case @cmd_tag
|
36
|
+
when /^INSERT\s+(\d+)\s+(\d+)$/, /^(DELETE|UPDATE|MOVE|FETCH)\s+(\d+)$/
|
37
|
+
$2.to_i
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sequel-postgres-pr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeremy Evans
|
8
|
+
- Michael Neumann
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2022-07-13 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email: code@jeremyevans.net
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files:
|
19
|
+
- README
|
20
|
+
files:
|
21
|
+
- README
|
22
|
+
- lib/postgres-pr/buffer.rb
|
23
|
+
- lib/postgres-pr/connection.rb
|
24
|
+
- lib/postgres-pr/message.rb
|
25
|
+
- lib/postgres-pr/result.rb
|
26
|
+
- lib/sequel/postgres-pr.rb
|
27
|
+
homepage: https://github.com/jeremyevans/sequel-postgres-pr
|
28
|
+
licenses:
|
29
|
+
- MIT
|
30
|
+
- Ruby
|
31
|
+
metadata:
|
32
|
+
bug_tracker_uri: https://github.com/jeremyevans/sequel-postgres-pr/issues
|
33
|
+
mailing_list_uri: https://github.com/jeremyevans/sequel-postgres-pr/discussions
|
34
|
+
source_code_uri: https://github.com/jeremyevans/sequel-postgres-pr
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options:
|
37
|
+
- "--quiet"
|
38
|
+
- "--line-numbers"
|
39
|
+
- "--inline-source"
|
40
|
+
- "--title"
|
41
|
+
- 'sequel-postgres-pr: Pure Ruby driver for PostgreSQL, designed for use with Sequel'
|
42
|
+
- "--main"
|
43
|
+
- README.rdoc
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 1.9.2
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
requirements: []
|
57
|
+
rubygems_version: 3.3.7
|
58
|
+
signing_key:
|
59
|
+
specification_version: 4
|
60
|
+
summary: Pure Ruby driver for PostgreSQL, designed for use with Sequel
|
61
|
+
test_files: []
|