pg 1.0.0 → 1.1.0.pre20180730144600
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +0 -6595
- data/History.rdoc +52 -0
- data/README.rdoc +11 -0
- data/Rakefile +1 -1
- data/Rakefile.cross +1 -1
- data/ext/errorcodes.rb +1 -1
- data/ext/extconf.rb +2 -0
- data/ext/pg.c +3 -2
- data/ext/pg.h +33 -5
- data/ext/pg_binary_decoder.c +69 -6
- data/ext/pg_binary_encoder.c +1 -1
- data/ext/pg_coder.c +52 -3
- data/ext/pg_connection.c +290 -103
- data/ext/pg_copy_coder.c +10 -5
- data/ext/pg_result.c +339 -113
- data/ext/pg_text_decoder.c +597 -37
- data/ext/pg_text_encoder.c +1 -1
- data/ext/pg_tuple.c +540 -0
- data/ext/pg_type_map.c +1 -1
- data/ext/pg_type_map_all_strings.c +1 -1
- data/ext/pg_type_map_by_class.c +1 -1
- data/ext/pg_type_map_by_column.c +1 -1
- data/ext/pg_type_map_by_mri_type.c +1 -1
- data/ext/pg_type_map_by_oid.c +1 -1
- data/ext/pg_type_map_in_ruby.c +1 -1
- data/ext/util.c +6 -6
- data/ext/util.h +2 -2
- data/lib/pg.rb +5 -3
- data/lib/pg/basic_type_mapping.rb +40 -7
- data/lib/pg/coder.rb +1 -1
- data/lib/pg/connection.rb +20 -1
- data/lib/pg/constants.rb +1 -1
- data/lib/pg/exceptions.rb +1 -1
- data/lib/pg/result.rb +1 -1
- data/lib/pg/text_decoder.rb +19 -23
- data/lib/pg/text_encoder.rb +35 -1
- data/lib/pg/type_map_by_column.rb +1 -1
- data/spec/helpers.rb +39 -7
- data/spec/pg/basic_type_mapping_spec.rb +230 -27
- data/spec/pg/connection_spec.rb +116 -77
- data/spec/pg/result_spec.rb +46 -11
- data/spec/pg/type_map_by_class_spec.rb +1 -1
- data/spec/pg/type_map_by_column_spec.rb +1 -1
- data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
- data/spec/pg/type_map_by_oid_spec.rb +1 -1
- data/spec/pg/type_map_in_ruby_spec.rb +1 -1
- data/spec/pg/type_map_spec.rb +1 -1
- data/spec/pg/type_spec.rb +177 -11
- data/spec/pg_spec.rb +1 -1
- metadata +24 -28
- metadata.gz.sig +0 -0
data/ext/pg_type_map.c
CHANGED
data/ext/pg_type_map_by_class.c
CHANGED
data/ext/pg_type_map_by_column.c
CHANGED
data/ext/pg_type_map_by_oid.c
CHANGED
data/ext/pg_type_map_in_ruby.c
CHANGED
data/ext/util.c
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
2
|
* util.c - Utils for ruby-pg
|
3
|
-
* $Id
|
3
|
+
* $Id$
|
4
4
|
*
|
5
5
|
*/
|
6
6
|
|
@@ -15,9 +15,9 @@ static const char base64_encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk
|
|
15
15
|
* in-place (with _out_ == _in_).
|
16
16
|
*/
|
17
17
|
void
|
18
|
-
base64_encode( char *out, char *in, int len)
|
18
|
+
base64_encode( char *out, const char *in, int len)
|
19
19
|
{
|
20
|
-
unsigned char *in_ptr = (unsigned char *)in + len;
|
20
|
+
const unsigned char *in_ptr = (const unsigned char *)in + len;
|
21
21
|
char *out_ptr = out + BASE64_ENCODED_SIZE(len);
|
22
22
|
int part_len = len % 3;
|
23
23
|
|
@@ -72,12 +72,12 @@ static const unsigned char base64_decode_table[] =
|
|
72
72
|
* It is possible to decode a string in-place (with _out_ == _in_).
|
73
73
|
*/
|
74
74
|
int
|
75
|
-
base64_decode( char *out, char *in, unsigned int len)
|
75
|
+
base64_decode( char *out, const char *in, unsigned int len)
|
76
76
|
{
|
77
77
|
unsigned char a, b, c, d;
|
78
|
-
unsigned char *in_ptr = (unsigned char *)in;
|
78
|
+
const unsigned char *in_ptr = (const unsigned char *)in;
|
79
79
|
unsigned char *out_ptr = (unsigned char *)out;
|
80
|
-
unsigned char *iend_ptr = (unsigned char *)in + len;
|
80
|
+
const unsigned char *iend_ptr = (unsigned char *)in + len;
|
81
81
|
|
82
82
|
for(;;){
|
83
83
|
if( in_ptr+3 < iend_ptr &&
|
data/ext/util.h
CHANGED
@@ -57,8 +57,8 @@
|
|
57
57
|
#define BASE64_ENCODED_SIZE(strlen) (((strlen) + 2) / 3 * 4)
|
58
58
|
#define BASE64_DECODED_SIZE(base64len) (((base64len) + 3) / 4 * 3)
|
59
59
|
|
60
|
-
void base64_encode( char *out, char *in, int len);
|
61
|
-
int base64_decode( char *out, char *in, unsigned int len);
|
60
|
+
void base64_encode( char *out, const char *in, int len);
|
61
|
+
int base64_decode( char *out, const char *in, unsigned int len);
|
62
62
|
|
63
63
|
int rbpg_strncasecmp(const char *s1, const char *s2, size_t n);
|
64
64
|
|
data/lib/pg.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
|
3
3
|
begin
|
4
4
|
require 'pg_ext'
|
@@ -35,10 +35,10 @@ end
|
|
35
35
|
module PG
|
36
36
|
|
37
37
|
# Library version
|
38
|
-
VERSION = '1.0.
|
38
|
+
VERSION = '1.1.0.pre20180730144600'
|
39
39
|
|
40
40
|
# VCS revision
|
41
|
-
REVISION = %q$Revision
|
41
|
+
REVISION = %q$Revision$
|
42
42
|
|
43
43
|
class NotAllCopyDataRetrieved < PG::Error
|
44
44
|
end
|
@@ -60,12 +60,14 @@ module PG
|
|
60
60
|
require 'pg/exceptions'
|
61
61
|
require 'pg/constants'
|
62
62
|
require 'pg/coder'
|
63
|
+
require 'pg/binary_decoder'
|
63
64
|
require 'pg/text_encoder'
|
64
65
|
require 'pg/text_decoder'
|
65
66
|
require 'pg/basic_type_mapping'
|
66
67
|
require 'pg/type_map_by_column'
|
67
68
|
require 'pg/connection'
|
68
69
|
require 'pg/result'
|
70
|
+
require 'pg/tuple'
|
69
71
|
|
70
72
|
end # module PG
|
71
73
|
|
@@ -1,7 +1,27 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
|
3
3
|
require 'pg' unless defined?( PG )
|
4
4
|
|
5
|
+
# This module defines the mapping between OID and encoder/decoder classes for PG::BasicTypeMapForResults, PG::BasicTypeMapForQueries and PG::BasicTypeMapBasedOnResult.
|
6
|
+
#
|
7
|
+
# Additional types can be added like so:
|
8
|
+
#
|
9
|
+
# require 'pg'
|
10
|
+
# require 'ipaddr'
|
11
|
+
#
|
12
|
+
# class InetDecoder < PG::SimpleDecoder
|
13
|
+
# def decode(string, tuple=nil, field=nil)
|
14
|
+
# IPAddr.new(string)
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
# class InetEncoder < PG::SimpleEncoder
|
18
|
+
# def encode(ip_addr)
|
19
|
+
# ip_addr.to_s
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # 0 if for text format, can also be 1 for binary
|
24
|
+
# PG::BasicTypeRegistry.register_type(0, 'inet', InetEncoder, InetDecoder)
|
5
25
|
module PG::BasicTypeRegistry
|
6
26
|
# An instance of this class stores the coders that should be used for a given wire format (text or binary)
|
7
27
|
# and type cast direction (encoder or decoder).
|
@@ -137,6 +157,7 @@ module PG::BasicTypeRegistry
|
|
137
157
|
# Register an OID type named +name+ with a typecasting encoder and decoder object in
|
138
158
|
# +type+. +name+ should correspond to the `typname` column in
|
139
159
|
# the `pg_type` table.
|
160
|
+
# +format+ can be 0 for text format and 1 for binary.
|
140
161
|
def self.register_type(format, name, encoder_class, decoder_class)
|
141
162
|
CODERS_BY_NAME[format] ||= { encoder: {}, decoder: {} }
|
142
163
|
CODERS_BY_NAME[format][:encoder][name] = encoder_class.new(name: name, format: format) if encoder_class
|
@@ -145,8 +166,14 @@ module PG::BasicTypeRegistry
|
|
145
166
|
|
146
167
|
# Alias the +old+ type to the +new+ type.
|
147
168
|
def self.alias_type(format, new, old)
|
148
|
-
|
149
|
-
|
169
|
+
[:encoder, :decoder].each do |ende|
|
170
|
+
enc = CODERS_BY_NAME[format][ende][old]
|
171
|
+
if enc
|
172
|
+
CODERS_BY_NAME[format][ende][new] = enc
|
173
|
+
else
|
174
|
+
CODERS_BY_NAME[format][ende].delete(new)
|
175
|
+
end
|
176
|
+
end
|
150
177
|
end
|
151
178
|
|
152
179
|
register_type 0, 'int2', PG::TextEncoder::Integer, PG::TextDecoder::Integer
|
@@ -154,7 +181,7 @@ module PG::BasicTypeRegistry
|
|
154
181
|
alias_type 0, 'int8', 'int2'
|
155
182
|
alias_type 0, 'oid', 'int2'
|
156
183
|
|
157
|
-
|
184
|
+
register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric
|
158
185
|
register_type 0, 'text', PG::TextEncoder::String, PG::TextDecoder::String
|
159
186
|
alias_type 0, 'varchar', 'text'
|
160
187
|
alias_type 0, 'char', 'text'
|
@@ -193,8 +220,8 @@ module PG::BasicTypeRegistry
|
|
193
220
|
# register_type 'citext', OID::Text.new
|
194
221
|
# register_type 'ltree', OID::Text.new
|
195
222
|
#
|
196
|
-
|
197
|
-
|
223
|
+
register_type 0, 'inet', PG::TextEncoder::Inet, PG::TextDecoder::Inet
|
224
|
+
alias_type 0, 'cidr', 'inet'
|
198
225
|
|
199
226
|
|
200
227
|
|
@@ -213,6 +240,8 @@ module PG::BasicTypeRegistry
|
|
213
240
|
register_type 1, 'bool', PG::BinaryEncoder::Boolean, PG::BinaryDecoder::Boolean
|
214
241
|
register_type 1, 'float4', nil, PG::BinaryDecoder::Float
|
215
242
|
register_type 1, 'float8', nil, PG::BinaryDecoder::Float
|
243
|
+
register_type 1, 'timestamp', nil, PG::BinaryDecoder::TimestampUtc
|
244
|
+
register_type 1, 'timestamptz', nil, PG::BinaryDecoder::TimestampUtcToLocal
|
216
245
|
end
|
217
246
|
|
218
247
|
# Simple set of rules for type casting common PostgreSQL types to Ruby.
|
@@ -258,7 +287,7 @@ end
|
|
258
287
|
# This prints the rows with type casted columns:
|
259
288
|
# ["a", 123, [5, 4, 3]]
|
260
289
|
#
|
261
|
-
# See also PG::BasicTypeMapBasedOnResult for the encoder direction.
|
290
|
+
# See also PG::BasicTypeMapBasedOnResult for the encoder direction and PG::BasicTypeRegistry for the definition of additional types.
|
262
291
|
class PG::BasicTypeMapForResults < PG::TypeMapByOid
|
263
292
|
include PG::BasicTypeRegistry
|
264
293
|
|
@@ -412,6 +441,10 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
|
|
412
441
|
# to unnecessary type conversions on server side.
|
413
442
|
Integer => [0, 'int8'],
|
414
443
|
Float => [0, 'float8'],
|
444
|
+
BigDecimal => [0, 'numeric'],
|
445
|
+
# We use text format and no type OID for IPAddr, because setting the OID can lead
|
446
|
+
# to unnecessary inet/cidr conversions on the server side.
|
447
|
+
IPAddr => [0, 'inet'],
|
415
448
|
Array => :get_array_type,
|
416
449
|
}
|
417
450
|
|
data/lib/pg/coder.rb
CHANGED
data/lib/pg/connection.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
|
3
3
|
require 'pg' unless defined?( PG )
|
4
4
|
require 'uri'
|
@@ -268,5 +268,24 @@ class PG::Connection
|
|
268
268
|
end
|
269
269
|
end
|
270
270
|
|
271
|
+
REDIRECT_METHODS = {
|
272
|
+
:exec => [:async_exec, :sync_exec],
|
273
|
+
:query => [:async_exec, :sync_exec],
|
274
|
+
:exec_params => [:async_exec_params, :sync_exec_params],
|
275
|
+
:prepare => [:async_prepare, :sync_prepare],
|
276
|
+
:exec_prepared => [:async_exec_prepared, :sync_exec_prepared],
|
277
|
+
:describe_portal => [:async_describe_portal, :sync_describe_portal],
|
278
|
+
:describe_prepared => [:async_describe_prepared, :sync_describe_prepared],
|
279
|
+
}
|
280
|
+
|
281
|
+
def self.async_api=(enable)
|
282
|
+
REDIRECT_METHODS.each do |ali, (async, sync)|
|
283
|
+
remove_method(ali) if method_defined?(ali)
|
284
|
+
alias_method( ali, enable ? async : sync )
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# pg-1.1.0+ defaults to libpq's async API for query related blocking methods
|
289
|
+
self.async_api = true
|
271
290
|
end # class PG::Connection
|
272
291
|
|
data/lib/pg/constants.rb
CHANGED
data/lib/pg/exceptions.rb
CHANGED
data/lib/pg/result.rb
CHANGED
data/lib/pg/text_decoder.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
|
3
3
|
require 'date'
|
4
4
|
require 'json'
|
@@ -17,35 +17,31 @@ module PG
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
class
|
21
|
-
ISO_DATETIME_WITHOUT_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
22
|
-
|
20
|
+
class JSON < SimpleDecoder
|
23
21
|
def decode(string, tuple=nil, field=nil)
|
24
|
-
|
25
|
-
Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r
|
26
|
-
else
|
27
|
-
string
|
28
|
-
end
|
22
|
+
::JSON.parse(string, quirks_mode: true)
|
29
23
|
end
|
30
24
|
end
|
31
25
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
if string =~ ISO_DATETIME_WITH_TIMEZONE
|
37
|
-
Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r, "#{$8}:#{$9 || '00'}:#{$10 || '00'}"
|
38
|
-
else
|
39
|
-
string
|
40
|
-
end
|
26
|
+
# Convenience classes for timezone options
|
27
|
+
class TimestampUtc < Timestamp
|
28
|
+
def initialize(params={})
|
29
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC))
|
41
30
|
end
|
42
31
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
::JSON.parse(string, quirks_mode: true)
|
32
|
+
class TimestampUtcToLocal < Timestamp
|
33
|
+
def initialize(params={})
|
34
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL))
|
47
35
|
end
|
48
36
|
end
|
37
|
+
class TimestampLocal < Timestamp
|
38
|
+
def initialize(params={})
|
39
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# For backward compatibility:
|
44
|
+
TimestampWithoutTimeZone = TimestampLocal
|
45
|
+
TimestampWithTimeZone = Timestamp
|
49
46
|
end
|
50
47
|
end # module PG
|
51
|
-
|
data/lib/pg/text_encoder.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
|
3
3
|
require 'json'
|
4
|
+
require 'ipaddr'
|
4
5
|
|
5
6
|
module PG
|
6
7
|
module TextEncoder
|
@@ -18,6 +19,13 @@ module PG
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
22
|
+
class TimestampUtc < SimpleEncoder
|
23
|
+
STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE_UTC = "%Y-%m-%d %H:%M:%S.%N".freeze
|
24
|
+
def encode(value)
|
25
|
+
value.respond_to?(:utc) ? value.utc.strftime(STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE_UTC) : value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
21
29
|
class TimestampWithTimeZone < SimpleEncoder
|
22
30
|
STRFTIME_ISO_DATETIME_WITH_TIMEZONE = "%Y-%m-%d %H:%M:%S.%N %:z".freeze
|
23
31
|
def encode(value)
|
@@ -25,11 +33,37 @@ module PG
|
|
25
33
|
end
|
26
34
|
end
|
27
35
|
|
36
|
+
class Numeric < SimpleEncoder
|
37
|
+
def encode(value)
|
38
|
+
value.is_a?(BigDecimal) ? value.to_s('F') : value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
28
42
|
class JSON < SimpleEncoder
|
29
43
|
def encode(value)
|
30
44
|
::JSON.generate(value, quirks_mode: true)
|
31
45
|
end
|
32
46
|
end
|
47
|
+
|
48
|
+
class Inet < SimpleEncoder
|
49
|
+
def encode(value)
|
50
|
+
case value
|
51
|
+
when IPAddr
|
52
|
+
default_prefix = (value.family == Socket::AF_INET ? 32 : 128)
|
53
|
+
s = value.to_s
|
54
|
+
if value.respond_to?(:prefix)
|
55
|
+
prefix = value.prefix
|
56
|
+
else
|
57
|
+
range = value.to_range
|
58
|
+
prefix = default_prefix - Math.log(((range.end.to_i - range.begin.to_i) + 1), 2).to_i
|
59
|
+
end
|
60
|
+
s << "/" << prefix.to_s if prefix != default_prefix
|
61
|
+
s
|
62
|
+
else
|
63
|
+
value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
33
67
|
end
|
34
68
|
end # module PG
|
35
69
|
|
data/spec/helpers.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'rspec'
|
@@ -23,11 +23,9 @@ module PG::TestingHelpers
|
|
23
23
|
mod.around( :each ) do |example|
|
24
24
|
begin
|
25
25
|
@conn.exec( 'BEGIN' ) unless example.metadata[:without_transaction]
|
26
|
-
|
27
|
-
|
28
|
-
@conn.
|
29
|
-
[@conn.escape_string(desc.slice(-60))]
|
30
|
-
end
|
26
|
+
desc = example.source_location.join(':')
|
27
|
+
@conn.exec %Q{SET application_name TO '%s'} %
|
28
|
+
[@conn.escape_string(desc.slice(-60))]
|
31
29
|
example.run
|
32
30
|
ensure
|
33
31
|
@conn.exec( 'ROLLBACK' ) unless example.metadata[:without_transaction]
|
@@ -266,7 +264,7 @@ module PG::TestingHelpers
|
|
266
264
|
|
267
265
|
# Retrieve the names of the column types of a given result set.
|
268
266
|
def result_typenames(res)
|
269
|
-
@conn.
|
267
|
+
@conn.exec_params( "SELECT " + res.nfields.times.map{|i| "format_type($#{i*2+1},$#{i*2+2})"}.join(","),
|
270
268
|
res.nfields.times.map{|i| [res.ftype(i), res.fmod(i)] }.flatten ).
|
271
269
|
values[0]
|
272
270
|
end
|
@@ -320,6 +318,40 @@ module PG::TestingHelpers
|
|
320
318
|
return ConnStillUsableMatcher.new
|
321
319
|
end
|
322
320
|
|
321
|
+
def wait_for_polling_ok(conn)
|
322
|
+
socket = conn.socket_io
|
323
|
+
status = conn.connect_poll
|
324
|
+
|
325
|
+
while status != PG::PGRES_POLLING_OK
|
326
|
+
if status == PG::PGRES_POLLING_READING
|
327
|
+
select( [socket], [], [], 5.0 ) or
|
328
|
+
raise "Asynchronous connection timed out!"
|
329
|
+
|
330
|
+
elsif status == PG::PGRES_POLLING_WRITING
|
331
|
+
select( [], [socket], [], 5.0 ) or
|
332
|
+
raise "Asynchronous connection timed out!"
|
333
|
+
end
|
334
|
+
status = conn.connect_poll
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def wait_for_query_result(conn)
|
339
|
+
result = nil
|
340
|
+
loop do
|
341
|
+
# Buffer any incoming data on the socket until a full result is ready.
|
342
|
+
conn.consume_input
|
343
|
+
while conn.is_busy
|
344
|
+
select( [conn.socket_io], nil, nil, 5.0 ) or
|
345
|
+
raise "Timeout waiting for query response."
|
346
|
+
conn.consume_input
|
347
|
+
end
|
348
|
+
|
349
|
+
# Fetch the next result. If there isn't one, the query is finished
|
350
|
+
result = conn.get_result || break
|
351
|
+
end
|
352
|
+
result
|
353
|
+
end
|
354
|
+
|
323
355
|
end
|
324
356
|
|
325
357
|
|