sequel-postgres-pr 0.9.0

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