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 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
@@ -0,0 +1,5 @@
1
+ require_relative '../postgres-pr/connection'
2
+
3
+ Sequel::Postgres::PGError = PostgresPR::PGError
4
+ Sequel::Postgres::PGconn = PostgresPR::Connection
5
+ Sequel::Postgres::PGresult = PostgresPR::Result
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: []