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 +25 -0
- data/Rakefile +2 -0
- data/examples/client.rb +34 -0
- data/examples/og/test.rb +50 -0
- data/examples/server.rb +12 -0
- data/examples/test_connection.rb +18 -0
- data/ext/postgres-pr/utils/extconf.rb +8 -0
- data/ext/postgres-pr/utils/string_unpack_single.c +73 -0
- data/lib/pg.rb +8 -0
- data/lib/postgres-pr/connection.rb +175 -0
- data/lib/postgres-pr/message.rb +589 -0
- data/lib/postgres-pr/pg-compat.rb +225 -0
- data/lib/postgres-pr/postgres-compat.rb +169 -0
- data/lib/postgres-pr/typeconv/TC_conv.rb +18 -0
- data/lib/postgres-pr/typeconv/array.rb +46 -0
- data/lib/postgres-pr/typeconv/bytea.rb +32 -0
- data/lib/postgres-pr/typeconv/conv.rb +5 -0
- data/lib/postgres-pr/utils/buffer.rb +252 -0
- data/lib/postgres-pr/utils/byteorder.rb +45 -0
- data/lib/postgres-pr/version.rb +3 -0
- data/lib/postgres.rb +8 -0
- metadata +102 -0
@@ -0,0 +1,225 @@
|
|
1
|
+
# This is a compatibility layer for using the pure Ruby postgres-pr instead of
|
2
|
+
# the C interface of postgres.
|
3
|
+
|
4
|
+
require 'rexml/syncenumerator'
|
5
|
+
require 'postgres-pr/connection'
|
6
|
+
require 'postgres-pr/typeconv/conv'
|
7
|
+
require 'postgres-pr/typeconv/bytea'
|
8
|
+
|
9
|
+
class PGconn
|
10
|
+
extend Postgres::Conversion
|
11
|
+
include Postgres::Conversion
|
12
|
+
PQTRANS_IDLE = 0 #(connection idle)
|
13
|
+
PQTRANS_INTRANS = 2 #(idle, within transaction block)
|
14
|
+
PQTRANS_INERROR = 3 #(idle, within failed transaction)
|
15
|
+
PQTRANS_UNKNOWN = 4 #(cannot determine status)
|
16
|
+
|
17
|
+
class << self
|
18
|
+
alias connect new
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(host, port, options, tty, database, user, auth)
|
22
|
+
uri =
|
23
|
+
if host.nil?
|
24
|
+
nil
|
25
|
+
elsif host[0] != ?/
|
26
|
+
"tcp://#{ host }:#{ port }"
|
27
|
+
else
|
28
|
+
"unix:#{ host }/.s.PGSQL.#{ port }"
|
29
|
+
end
|
30
|
+
@host = host
|
31
|
+
@db = database
|
32
|
+
@user = user
|
33
|
+
@conn = PostgresPR::Connection.new(database, user, auth, uri)
|
34
|
+
end
|
35
|
+
|
36
|
+
def close
|
37
|
+
@conn.close
|
38
|
+
end
|
39
|
+
|
40
|
+
alias finish close
|
41
|
+
|
42
|
+
attr_reader :host, :db, :user
|
43
|
+
|
44
|
+
def query(sql)
|
45
|
+
PGresult.new(@conn.query(sql))
|
46
|
+
end
|
47
|
+
|
48
|
+
alias exec query
|
49
|
+
alias async_exec exec
|
50
|
+
|
51
|
+
def transaction_status
|
52
|
+
@conn.transaction_status
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.escape(str)
|
56
|
+
str.gsub("'","''").gsub("\\", "\\\\\\\\")
|
57
|
+
end
|
58
|
+
|
59
|
+
def escape(str)
|
60
|
+
self.class.escape(str)
|
61
|
+
end
|
62
|
+
|
63
|
+
if RUBY_VERSION < '1.9'
|
64
|
+
def escape_string(str)
|
65
|
+
case @conn.params['client_encoding']
|
66
|
+
when /ASCII/, /ISO/, /KOI8/, /WIN/, /LATIN/
|
67
|
+
def self.escape_string(str)
|
68
|
+
str.gsub("'", "''").gsub("\\", "\\\\\\\\")
|
69
|
+
end
|
70
|
+
else
|
71
|
+
def self.escape_string(str)
|
72
|
+
str.gsub(/'/u, "''").gsub(/\\/u, "\\\\\\\\")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
escape_string(str)
|
76
|
+
end
|
77
|
+
else
|
78
|
+
def escape_string(str)
|
79
|
+
str.gsub("'", "''").gsub("\\", "\\\\\\\\")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def notice_processor
|
84
|
+
@conn.notice_processor
|
85
|
+
end
|
86
|
+
|
87
|
+
def notice_processor=(np)
|
88
|
+
@conn.notice_processor = np
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.quote_ident(name)
|
92
|
+
%("#{name}")
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
class PGresult
|
98
|
+
include Enumerable
|
99
|
+
|
100
|
+
EMPTY_QUERY = 0
|
101
|
+
COMMAND_OK = 1
|
102
|
+
TUPLES_OK = 2
|
103
|
+
COPY_OUT = 3
|
104
|
+
COPY_IN = 4
|
105
|
+
BAD_RESPONSE = 5
|
106
|
+
NONFATAL_ERROR = 6
|
107
|
+
FATAL_ERROR = 7
|
108
|
+
|
109
|
+
def each(&block)
|
110
|
+
@result.each(&block)
|
111
|
+
end
|
112
|
+
|
113
|
+
def [](index)
|
114
|
+
@result[index]
|
115
|
+
end
|
116
|
+
|
117
|
+
def initialize(res)
|
118
|
+
@res = res
|
119
|
+
@fields = @res.fields.map {|f| f.name}
|
120
|
+
@result = []
|
121
|
+
@res.rows.each do |row|
|
122
|
+
h = {}
|
123
|
+
@fields.zip(row){|field, value| h[field] = value}
|
124
|
+
@result << h
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# TODO: status, cmdstatus
|
129
|
+
|
130
|
+
def values
|
131
|
+
@res.rows
|
132
|
+
end
|
133
|
+
|
134
|
+
def column_values(i)
|
135
|
+
raise IndexError, "no column #{i} in result" unless i < @fields.size
|
136
|
+
@res.rows.map{|row| row[i]}
|
137
|
+
end
|
138
|
+
|
139
|
+
def field_values(field)
|
140
|
+
raise IndexError, "no such field '#{field}' in result" unless @fields.include?(field)
|
141
|
+
@result.map{|row| row[field]}
|
142
|
+
end
|
143
|
+
|
144
|
+
attr_reader :result, :fields
|
145
|
+
|
146
|
+
def num_tuples
|
147
|
+
@result.size
|
148
|
+
end
|
149
|
+
|
150
|
+
alias :ntuples :num_tuples
|
151
|
+
|
152
|
+
def num_fields
|
153
|
+
@fields.size
|
154
|
+
end
|
155
|
+
|
156
|
+
alias :nfields :num_fields
|
157
|
+
|
158
|
+
def fname(index)
|
159
|
+
@fields[index]
|
160
|
+
end
|
161
|
+
|
162
|
+
alias fieldname fname
|
163
|
+
|
164
|
+
def fnum(name)
|
165
|
+
@fields.index(name)
|
166
|
+
end
|
167
|
+
|
168
|
+
alias fieldnum fnum
|
169
|
+
|
170
|
+
def type(index)
|
171
|
+
# TODO: correct?
|
172
|
+
@res.fields[index].type_oid
|
173
|
+
end
|
174
|
+
|
175
|
+
alias :ftype :type
|
176
|
+
|
177
|
+
def size(index)
|
178
|
+
raise
|
179
|
+
# TODO: correct?
|
180
|
+
@res.fields[index].typlen
|
181
|
+
end
|
182
|
+
|
183
|
+
def getvalue(tup_num, field_num)
|
184
|
+
@res.rows[tup_num][field_num]
|
185
|
+
end
|
186
|
+
|
187
|
+
def getlength(tup_num, field_num)
|
188
|
+
@res.rows[typ_num][field_num].length
|
189
|
+
end
|
190
|
+
|
191
|
+
def status
|
192
|
+
if num_tuples > 0
|
193
|
+
TUPLES_OK
|
194
|
+
else
|
195
|
+
COMMAND_OK
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def cmdstatus
|
200
|
+
@res.cmd_tag || ''
|
201
|
+
end
|
202
|
+
|
203
|
+
# free the result set
|
204
|
+
def clear
|
205
|
+
@res = @fields = @result = nil
|
206
|
+
end
|
207
|
+
|
208
|
+
# Returns the number of rows affected by the SQL command
|
209
|
+
def cmdtuples
|
210
|
+
case @res.cmd_tag
|
211
|
+
when nil
|
212
|
+
return nil
|
213
|
+
when /^INSERT\s+(\d+)\s+(\d+)$/, /^(DELETE|UPDATE|MOVE|FETCH)\s+(\d+)$/
|
214
|
+
$2.to_i
|
215
|
+
else
|
216
|
+
nil
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
alias :cmd_tuples :cmdtuples
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
class PGError < Exception
|
225
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# This is a compatibility layer for using the pure Ruby postgres-pr instead of
|
2
|
+
# the C interface of postgres.
|
3
|
+
|
4
|
+
require 'postgres-pr/connection'
|
5
|
+
|
6
|
+
class PGconn
|
7
|
+
PQTRANS_IDLE = 0 #(connection idle)
|
8
|
+
PQTRANS_INTRANS = 2 #(idle, within transaction block)
|
9
|
+
PQTRANS_INERROR = 3 #(idle, within failed transaction)
|
10
|
+
PQTRANS_UNKNOWN = 4 #(cannot determine status)
|
11
|
+
|
12
|
+
class << self
|
13
|
+
alias connect new
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(host, port, options, tty, database, user, auth)
|
17
|
+
uri =
|
18
|
+
if host.nil?
|
19
|
+
nil
|
20
|
+
elsif host[0] != ?/
|
21
|
+
"tcp://#{ host }:#{ port }"
|
22
|
+
else
|
23
|
+
"unix:#{ host }/.s.PGSQL.#{ port }"
|
24
|
+
end
|
25
|
+
@host = host
|
26
|
+
@db = database
|
27
|
+
@user = user
|
28
|
+
@conn = PostgresPR::Connection.new(database, user, auth, uri)
|
29
|
+
end
|
30
|
+
|
31
|
+
def close
|
32
|
+
@conn.close
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :host, :db, :user
|
36
|
+
|
37
|
+
def query(sql)
|
38
|
+
PGresult.new(@conn.query(sql))
|
39
|
+
end
|
40
|
+
|
41
|
+
alias exec query
|
42
|
+
|
43
|
+
def transaction_status
|
44
|
+
@conn.transaction_status
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.escape(str)
|
48
|
+
str.gsub("'","''").gsub("\\", "\\\\\\\\")
|
49
|
+
end
|
50
|
+
|
51
|
+
def escape(str)
|
52
|
+
self.class.escape(str)
|
53
|
+
end
|
54
|
+
|
55
|
+
def notice_processor
|
56
|
+
@conn.notice_processor
|
57
|
+
end
|
58
|
+
|
59
|
+
def notice_processor=(np)
|
60
|
+
@conn.notice_processor = np
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.quote_ident(name)
|
64
|
+
%("#{name}")
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
class PGresult
|
70
|
+
include Enumerable
|
71
|
+
|
72
|
+
EMPTY_QUERY = 0
|
73
|
+
COMMAND_OK = 1
|
74
|
+
TUPLES_OK = 2
|
75
|
+
COPY_OUT = 3
|
76
|
+
COPY_IN = 4
|
77
|
+
BAD_RESPONSE = 5
|
78
|
+
NONFATAL_ERROR = 6
|
79
|
+
FATAL_ERROR = 7
|
80
|
+
|
81
|
+
def each(&block)
|
82
|
+
@result.each(&block)
|
83
|
+
end
|
84
|
+
|
85
|
+
def [](index)
|
86
|
+
@result[index]
|
87
|
+
end
|
88
|
+
|
89
|
+
def initialize(res)
|
90
|
+
@res = res
|
91
|
+
@fields = @res.fields.map {|f| f.name}
|
92
|
+
@result = @res.rows
|
93
|
+
end
|
94
|
+
|
95
|
+
# TODO: status, getlength, cmdstatus
|
96
|
+
|
97
|
+
attr_reader :result, :fields
|
98
|
+
|
99
|
+
def num_tuples
|
100
|
+
@result.size
|
101
|
+
end
|
102
|
+
|
103
|
+
alias :ntuples :num_tuples
|
104
|
+
|
105
|
+
def num_fields
|
106
|
+
@fields.size
|
107
|
+
end
|
108
|
+
|
109
|
+
alias :nfields :num_fields
|
110
|
+
|
111
|
+
def fieldname(index)
|
112
|
+
@fields[index]
|
113
|
+
end
|
114
|
+
|
115
|
+
def fieldnum(name)
|
116
|
+
@fields.index(name)
|
117
|
+
end
|
118
|
+
|
119
|
+
def type(index)
|
120
|
+
# TODO: correct?
|
121
|
+
@res.fields[index].type_oid
|
122
|
+
end
|
123
|
+
|
124
|
+
alias :ftype :type
|
125
|
+
|
126
|
+
def size(index)
|
127
|
+
raise PGError, 'size not implemented'
|
128
|
+
# TODO: correct?
|
129
|
+
@res.fields[index].typlen
|
130
|
+
end
|
131
|
+
|
132
|
+
def getvalue(tup_num, field_num)
|
133
|
+
@result[tup_num][field_num]
|
134
|
+
end
|
135
|
+
|
136
|
+
def status
|
137
|
+
if num_tuples > 0
|
138
|
+
TUPLES_OK
|
139
|
+
else
|
140
|
+
COMMAND_OK
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def cmdstatus
|
145
|
+
@res.cmd_tag || ''
|
146
|
+
end
|
147
|
+
|
148
|
+
# free the result set
|
149
|
+
def clear
|
150
|
+
@res = @fields = @result = nil
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns the number of rows affected by the SQL command
|
154
|
+
def cmdtuples
|
155
|
+
case @res.cmd_tag
|
156
|
+
when nil
|
157
|
+
return nil
|
158
|
+
when /^INSERT\s+(\d+)\s+(\d+)$/, /^(DELETE|UPDATE|MOVE|FETCH)\s+(\d+)$/
|
159
|
+
$2.to_i
|
160
|
+
else
|
161
|
+
nil
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
alias :cmd_tuples :cmdtuples
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
PGError = PostgresPR::PGError
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'conv'
|
3
|
+
require 'array'
|
4
|
+
require 'bytea'
|
5
|
+
|
6
|
+
class TC_Conversion < Test::Unit::TestCase
|
7
|
+
def test_decode_array
|
8
|
+
assert_equal ["abcdef ", "hallo", ["1", "2"]], decode_array("{ abcdef , hallo, { 1, 2} }")
|
9
|
+
assert_equal [""], decode_array("{ }") # TODO: Correct?
|
10
|
+
assert_equal [], decode_array("{}")
|
11
|
+
assert_equal ["hallo", ""], decode_array("{hallo,}")
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_bytea
|
15
|
+
end
|
16
|
+
|
17
|
+
include Postgres::Conversion
|
18
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module Postgres::Conversion
|
4
|
+
|
5
|
+
def decode_array(str, delim=',', &conv_proc)
|
6
|
+
delim = Regexp.escape(delim)
|
7
|
+
buf = StringScanner.new(str)
|
8
|
+
return parse_arr(buf, delim, &conv_proc)
|
9
|
+
ensure
|
10
|
+
raise ConversionError, "end of string expected (#{buf.rest})" unless buf.empty?
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def parse_arr(buf, delim, &conv_proc)
|
16
|
+
# skip whitespace
|
17
|
+
buf.skip(/\s*/)
|
18
|
+
|
19
|
+
raise ConversionError, "'{' expected" unless buf.get_byte == '{'
|
20
|
+
|
21
|
+
elems = []
|
22
|
+
unless buf.scan(/\}/) # array is not empty
|
23
|
+
loop do
|
24
|
+
# skip whitespace
|
25
|
+
buf.skip(/\s+/)
|
26
|
+
|
27
|
+
elems <<
|
28
|
+
if buf.check(/\{/)
|
29
|
+
parse_arr(buf, delim, &conv_proc)
|
30
|
+
else
|
31
|
+
e = buf.scan(/("((\\.)|[^"])*"|\\.|[^\}#{ delim }])*/) || raise(ConversionError)
|
32
|
+
if conv_proc then conv_proc.call(e) else e end
|
33
|
+
end
|
34
|
+
|
35
|
+
break if buf.scan(/\}/)
|
36
|
+
break unless buf.scan(/#{ delim }/)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# skip whitespace
|
41
|
+
buf.skip(/\s*/)
|
42
|
+
|
43
|
+
elems
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|