pg 1.4.6 → 1.5.2
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.appveyor.yml +1 -1
- data/.gitignore +3 -0
- data/History.md +61 -0
- data/README.ja.md +29 -19
- data/README.md +29 -15
- data/Rakefile.cross +1 -1
- data/ext/pg.c +10 -28
- data/ext/pg.h +10 -5
- data/ext/pg_binary_decoder.c +79 -0
- data/ext/pg_binary_encoder.c +224 -0
- data/ext/pg_coder.c +16 -7
- data/ext/pg_connection.c +50 -34
- data/ext/pg_copy_coder.c +306 -17
- data/ext/pg_record_coder.c +5 -4
- data/ext/pg_result.c +88 -17
- data/ext/pg_text_decoder.c +28 -10
- data/ext/pg_text_encoder.c +22 -9
- data/ext/pg_tuple.c +34 -31
- data/ext/pg_type_map.c +3 -2
- data/ext/pg_type_map_all_strings.c +2 -2
- data/ext/pg_type_map_by_class.c +5 -3
- data/ext/pg_type_map_by_column.c +9 -3
- data/ext/pg_type_map_by_oid.c +7 -4
- data/ext/pg_type_map_in_ruby.c +5 -2
- data/lib/pg/basic_type_map_based_on_result.rb +21 -1
- data/lib/pg/basic_type_map_for_queries.rb +13 -8
- data/lib/pg/basic_type_map_for_results.rb +26 -3
- data/lib/pg/basic_type_registry.rb +30 -32
- data/lib/pg/binary_decoder/date.rb +9 -0
- data/lib/pg/binary_decoder/timestamp.rb +26 -0
- data/lib/pg/binary_encoder/timestamp.rb +20 -0
- data/lib/pg/coder.rb +15 -13
- data/lib/pg/connection.rb +84 -12
- data/lib/pg/text_decoder/date.rb +18 -0
- data/lib/pg/text_decoder/inet.rb +9 -0
- data/lib/pg/text_decoder/json.rb +14 -0
- data/lib/pg/text_decoder/numeric.rb +9 -0
- data/lib/pg/text_decoder/timestamp.rb +30 -0
- data/lib/pg/text_encoder/date.rb +12 -0
- data/lib/pg/text_encoder/inet.rb +28 -0
- data/lib/pg/text_encoder/json.rb +14 -0
- data/lib/pg/text_encoder/numeric.rb +9 -0
- data/lib/pg/text_encoder/timestamp.rb +24 -0
- data/lib/pg/version.rb +1 -1
- data/lib/pg.rb +44 -9
- data/pg.gemspec +1 -1
- data/translation/po/all.pot +170 -135
- data/translation/po/ja.po +365 -186
- data/translation/po4a.cfg +4 -1
- data.tar.gz.sig +0 -0
- metadata +28 -10
- metadata.gz.sig +0 -0
- data/lib/pg/binary_decoder.rb +0 -23
- data/lib/pg/constants.rb +0 -12
- data/lib/pg/text_decoder.rb +0 -46
- data/lib/pg/text_encoder.rb +0 -59
|
@@ -39,7 +39,8 @@ class PG::BasicTypeRegistry
|
|
|
39
39
|
oid
|
|
40
40
|
bool
|
|
41
41
|
date timestamp timestamptz
|
|
42
|
-
].inject({}){|h,e| h[e] = true; h }
|
|
42
|
+
].inject({}){|h,e| h[e] = true; h }.freeze
|
|
43
|
+
private_constant :DONT_QUOTE_TYPES
|
|
43
44
|
|
|
44
45
|
def initialize(result, coders_by_name, format, arraycoder)
|
|
45
46
|
coder_map = {}
|
|
@@ -52,7 +53,7 @@ class PG::BasicTypeRegistry
|
|
|
52
53
|
coder.oid = row['oid'].to_i
|
|
53
54
|
coder.name = row['typname']
|
|
54
55
|
coder.format = format
|
|
55
|
-
coder_map[coder.oid] = coder
|
|
56
|
+
coder_map[coder.oid] = coder.freeze
|
|
56
57
|
end
|
|
57
58
|
|
|
58
59
|
if arraycoder
|
|
@@ -67,13 +68,14 @@ class PG::BasicTypeRegistry
|
|
|
67
68
|
coder.format = format
|
|
68
69
|
coder.elements_type = elements_coder
|
|
69
70
|
coder.needs_quotation = !DONT_QUOTE_TYPES[elements_coder.name]
|
|
70
|
-
coder_map[coder.oid] = coder
|
|
71
|
+
coder_map[coder.oid] = coder.freeze
|
|
71
72
|
end
|
|
72
73
|
end
|
|
73
74
|
|
|
74
|
-
@coders = coder_map.values
|
|
75
|
-
@coders_by_name = @coders.inject({}){|h, t| h[t.name] = t; h }
|
|
76
|
-
@coders_by_oid = @coders.inject({}){|h, t| h[t.oid] = t; h }
|
|
75
|
+
@coders = coder_map.values.freeze
|
|
76
|
+
@coders_by_name = @coders.inject({}){|h, t| h[t.name] = t; h }.freeze
|
|
77
|
+
@coders_by_oid = @coders.inject({}){|h, t| h[t.oid] = t; h }.freeze
|
|
78
|
+
freeze
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
attr_reader :coders
|
|
@@ -117,6 +119,11 @@ class PG::BasicTypeRegistry
|
|
|
117
119
|
JOIN pg_proc as ti ON ti.oid = t.typinput
|
|
118
120
|
SQL
|
|
119
121
|
|
|
122
|
+
init_maps(registry, result.freeze)
|
|
123
|
+
freeze
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private def init_maps(registry, result)
|
|
120
127
|
@maps = [
|
|
121
128
|
[0, :encoder, PG::TextEncoder::Array],
|
|
122
129
|
[0, :decoder, PG::TextDecoder::Array],
|
|
@@ -127,9 +134,9 @@ class PG::BasicTypeRegistry
|
|
|
127
134
|
h[format] ||= {}
|
|
128
135
|
h[format][direction] = CoderMap.new(result, coders, format, arraycoder)
|
|
129
136
|
h
|
|
130
|
-
end
|
|
137
|
+
end.each{|h| h.freeze }.freeze
|
|
131
138
|
|
|
132
|
-
@typenames_by_oid = result.inject({}){|h, t| h[t['oid'].to_i] = t['typname']; h }
|
|
139
|
+
@typenames_by_oid = result.inject({}){|h, t| h[t['oid'].to_i] = t['typname']; h }.freeze
|
|
133
140
|
end
|
|
134
141
|
|
|
135
142
|
def each_format(direction)
|
|
@@ -142,8 +149,9 @@ class PG::BasicTypeRegistry
|
|
|
142
149
|
end
|
|
143
150
|
|
|
144
151
|
module Checker
|
|
145
|
-
ValidFormats = { 0 => true, 1 => true }
|
|
146
|
-
ValidDirections = { :encoder => true, :decoder => true }
|
|
152
|
+
ValidFormats = { 0 => true, 1 => true }.freeze
|
|
153
|
+
ValidDirections = { :encoder => true, :decoder => true }.freeze
|
|
154
|
+
private_constant :ValidFormats, :ValidDirections
|
|
147
155
|
|
|
148
156
|
protected def check_format_and_direction(format, direction)
|
|
149
157
|
raise(ArgumentError, "Invalid format value %p" % format) unless ValidFormats[format]
|
|
@@ -155,7 +163,7 @@ class PG::BasicTypeRegistry
|
|
|
155
163
|
raise ArgumentError, "registry argument must be given to CoderMapsBundle" if registry
|
|
156
164
|
conn_or_maps
|
|
157
165
|
else
|
|
158
|
-
PG::BasicTypeRegistry::CoderMapsBundle.new(conn_or_maps, registry: registry)
|
|
166
|
+
PG::BasicTypeRegistry::CoderMapsBundle.new(conn_or_maps, registry: registry).freeze
|
|
159
167
|
end
|
|
160
168
|
end
|
|
161
169
|
end
|
|
@@ -192,8 +200,8 @@ class PG::BasicTypeRegistry
|
|
|
192
200
|
# +name+ must correspond to the +typname+ column in the +pg_type+ table.
|
|
193
201
|
# +format+ can be 0 for text format and 1 for binary.
|
|
194
202
|
def register_type(format, name, encoder_class, decoder_class)
|
|
195
|
-
register_coder(encoder_class.new(name: name, format: format)) if encoder_class
|
|
196
|
-
register_coder(decoder_class.new(name: name, format: format)) if decoder_class
|
|
203
|
+
register_coder(encoder_class.new(name: name, format: format).freeze) if encoder_class
|
|
204
|
+
register_coder(decoder_class.new(name: name, format: format).freeze) if decoder_class
|
|
197
205
|
self
|
|
198
206
|
end
|
|
199
207
|
|
|
@@ -232,9 +240,7 @@ class PG::BasicTypeRegistry
|
|
|
232
240
|
# alias_type 'uuid', 'text'
|
|
233
241
|
#
|
|
234
242
|
# register_type 'money', OID::Money.new
|
|
235
|
-
|
|
236
|
-
# in binary format, either with PG::BinaryEncoder::Bytea or in Hash param format.
|
|
237
|
-
register_type 0, 'bytea', nil, PG::TextDecoder::Bytea
|
|
243
|
+
register_type 0, 'bytea', PG::TextEncoder::Bytea, PG::TextDecoder::Bytea
|
|
238
244
|
register_type 0, 'bool', PG::TextEncoder::Boolean, PG::TextDecoder::Boolean
|
|
239
245
|
# register_type 'bit', OID::Bit.new
|
|
240
246
|
# register_type 'varbit', OID::Bit.new
|
|
@@ -242,6 +248,7 @@ class PG::BasicTypeRegistry
|
|
|
242
248
|
register_type 0, 'float4', PG::TextEncoder::Float, PG::TextDecoder::Float
|
|
243
249
|
alias_type 0, 'float8', 'float4'
|
|
244
250
|
|
|
251
|
+
# For compatibility reason the timestamp in text format is encoded as local time (TimestampWithoutTimeZone) instead of UTC
|
|
245
252
|
register_type 0, 'timestamp', PG::TextEncoder::TimestampWithoutTimeZone, PG::TextDecoder::TimestampWithoutTimeZone
|
|
246
253
|
register_type 0, 'timestamptz', PG::TextEncoder::TimestampWithTimeZone, PG::TextDecoder::TimestampWithTimeZone
|
|
247
254
|
register_type 0, 'date', PG::TextEncoder::Date, PG::TextDecoder::Date
|
|
@@ -276,26 +283,17 @@ class PG::BasicTypeRegistry
|
|
|
276
283
|
|
|
277
284
|
register_type 1, 'bytea', PG::BinaryEncoder::Bytea, PG::BinaryDecoder::Bytea
|
|
278
285
|
register_type 1, 'bool', PG::BinaryEncoder::Boolean, PG::BinaryDecoder::Boolean
|
|
279
|
-
register_type 1, 'float4',
|
|
280
|
-
register_type 1, 'float8',
|
|
281
|
-
register_type 1, 'timestamp',
|
|
282
|
-
register_type 1, 'timestamptz',
|
|
286
|
+
register_type 1, 'float4', PG::BinaryEncoder::Float4, PG::BinaryDecoder::Float
|
|
287
|
+
register_type 1, 'float8', PG::BinaryEncoder::Float8, PG::BinaryDecoder::Float
|
|
288
|
+
register_type 1, 'timestamp', PG::BinaryEncoder::TimestampUtc, PG::BinaryDecoder::TimestampUtc
|
|
289
|
+
register_type 1, 'timestamptz', PG::BinaryEncoder::TimestampUtc, PG::BinaryDecoder::TimestampUtcToLocal
|
|
290
|
+
register_type 1, 'date', PG::BinaryEncoder::Date, PG::BinaryDecoder::Date
|
|
283
291
|
|
|
284
292
|
self
|
|
285
293
|
end
|
|
286
294
|
|
|
287
295
|
alias define_default_types register_default_types
|
|
288
296
|
|
|
289
|
-
|
|
290
|
-
DEFAULT_TYPE_REGISTRY
|
|
291
|
-
|
|
292
|
-
# Delegate class method calls to DEFAULT_TYPE_REGISTRY
|
|
293
|
-
class << self
|
|
294
|
-
%i[ register_coder register_type alias_type ].each do |meth|
|
|
295
|
-
define_method(meth) do |*args|
|
|
296
|
-
warn "PG::BasicTypeRegistry.#{meth} is deprecated. Please use your own instance by PG::BasicTypeRegistry.new instead!"
|
|
297
|
-
DEFAULT_TYPE_REGISTRY.send(meth, *args)
|
|
298
|
-
end
|
|
299
|
-
end
|
|
300
|
-
end
|
|
297
|
+
DEFAULT_TYPE_REGISTRY = PG.make_shareable(PG::BasicTypeRegistry.new.register_default_types)
|
|
298
|
+
private_constant :DEFAULT_TYPE_REGISTRY
|
|
301
299
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module PG
|
|
5
|
+
module BinaryDecoder
|
|
6
|
+
# Convenience classes for timezone options
|
|
7
|
+
class TimestampUtc < Timestamp
|
|
8
|
+
def initialize(hash={}, **kwargs)
|
|
9
|
+
warn "PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}" unless hash.empty?
|
|
10
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
class TimestampUtcToLocal < Timestamp
|
|
14
|
+
def initialize(hash={}, **kwargs)
|
|
15
|
+
warn "PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}" unless hash.empty?
|
|
16
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
class TimestampLocal < Timestamp
|
|
20
|
+
def initialize(hash={}, **kwargs)
|
|
21
|
+
warn "PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}" unless hash.empty?
|
|
22
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end # module PG
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module PG
|
|
5
|
+
module BinaryEncoder
|
|
6
|
+
# Convenience classes for timezone options
|
|
7
|
+
class TimestampUtc < Timestamp
|
|
8
|
+
def initialize(hash={}, **kwargs)
|
|
9
|
+
warn "PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}" unless hash.empty?
|
|
10
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_UTC)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
class TimestampLocal < Timestamp
|
|
14
|
+
def initialize(hash={}, **kwargs)
|
|
15
|
+
warn "PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}" unless hash.empty?
|
|
16
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_LOCAL)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end # module PG
|
data/lib/pg/coder.rb
CHANGED
|
@@ -6,22 +6,24 @@ module PG
|
|
|
6
6
|
class Coder
|
|
7
7
|
|
|
8
8
|
module BinaryFormatting
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
super(
|
|
9
|
+
def initialize(hash={}, **kwargs)
|
|
10
|
+
warn "PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}" unless hash.empty?
|
|
11
|
+
super(format: 1, **hash, **kwargs)
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
# Create a new coder object based on the attribute Hash.
|
|
17
|
-
def initialize(
|
|
18
|
-
|
|
17
|
+
def initialize(hash=nil, **kwargs)
|
|
18
|
+
warn "PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}" if hash
|
|
19
|
+
|
|
20
|
+
(hash || kwargs).each do |key, val|
|
|
19
21
|
send("#{key}=", val)
|
|
20
22
|
end
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def dup
|
|
24
|
-
self.class.new(to_h)
|
|
26
|
+
self.class.new(**to_h)
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
# Returns coder attributes as Hash.
|
|
@@ -43,7 +45,7 @@ module PG
|
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
def marshal_load(str)
|
|
46
|
-
initialize
|
|
48
|
+
initialize(**Marshal.load(str))
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
def inspect
|
|
@@ -70,11 +72,11 @@ module PG
|
|
|
70
72
|
|
|
71
73
|
class CompositeCoder < Coder
|
|
72
74
|
def to_h
|
|
73
|
-
super
|
|
75
|
+
{ **super,
|
|
74
76
|
elements_type: elements_type,
|
|
75
77
|
needs_quotation: needs_quotation?,
|
|
76
78
|
delimiter: delimiter,
|
|
77
|
-
}
|
|
79
|
+
}
|
|
78
80
|
end
|
|
79
81
|
|
|
80
82
|
def inspect
|
|
@@ -86,19 +88,19 @@ module PG
|
|
|
86
88
|
|
|
87
89
|
class CopyCoder < Coder
|
|
88
90
|
def to_h
|
|
89
|
-
super
|
|
91
|
+
{ **super,
|
|
90
92
|
type_map: type_map,
|
|
91
93
|
delimiter: delimiter,
|
|
92
94
|
null_string: null_string,
|
|
93
|
-
}
|
|
95
|
+
}
|
|
94
96
|
end
|
|
95
97
|
end
|
|
96
98
|
|
|
97
99
|
class RecordCoder < Coder
|
|
98
100
|
def to_h
|
|
99
|
-
super
|
|
101
|
+
{ **super,
|
|
100
102
|
type_map: type_map,
|
|
101
|
-
}
|
|
103
|
+
}
|
|
102
104
|
end
|
|
103
105
|
end
|
|
104
106
|
end # module PG
|
data/lib/pg/connection.rb
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require 'pg' unless defined?( PG )
|
|
5
|
-
require '
|
|
6
|
-
require 'io/wait'
|
|
5
|
+
require 'io/wait' unless ::IO.public_instance_methods(false).include?(:wait_readable)
|
|
7
6
|
require 'socket'
|
|
8
7
|
|
|
9
8
|
# The PostgreSQL connection class. The interface for this class is based on
|
|
@@ -31,8 +30,8 @@ require 'socket'
|
|
|
31
30
|
class PG::Connection
|
|
32
31
|
|
|
33
32
|
# The order the options are passed to the ::connect method.
|
|
34
|
-
CONNECT_ARGUMENT_ORDER = %w[host port options tty dbname user password]
|
|
35
|
-
|
|
33
|
+
CONNECT_ARGUMENT_ORDER = %w[host port options tty dbname user password].freeze
|
|
34
|
+
private_constant :CONNECT_ARGUMENT_ORDER
|
|
36
35
|
|
|
37
36
|
### Quote a single +value+ for use in a connection-parameter string.
|
|
38
37
|
def self.quote_connstr( value )
|
|
@@ -46,6 +45,10 @@ class PG::Connection
|
|
|
46
45
|
hash.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
|
|
47
46
|
end
|
|
48
47
|
|
|
48
|
+
# Shareable program name for Ractor
|
|
49
|
+
PROGRAM_NAME = $PROGRAM_NAME.dup.freeze
|
|
50
|
+
private_constant :PROGRAM_NAME
|
|
51
|
+
|
|
49
52
|
# Parse the connection +args+ into a connection-parameter string.
|
|
50
53
|
# See PG::Connection.new for valid arguments.
|
|
51
54
|
#
|
|
@@ -63,8 +66,8 @@ class PG::Connection
|
|
|
63
66
|
iopts = {}
|
|
64
67
|
|
|
65
68
|
if args.length == 1
|
|
66
|
-
case args.first
|
|
67
|
-
when
|
|
69
|
+
case args.first.to_s
|
|
70
|
+
when /=/, /:\/\//
|
|
68
71
|
# Option or URL string style
|
|
69
72
|
conn_string = args.first.to_s
|
|
70
73
|
iopts = PG::Connection.conninfo_parse(conn_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
|
|
@@ -87,7 +90,7 @@ class PG::Connection
|
|
|
87
90
|
iopts.merge!( hash_arg )
|
|
88
91
|
|
|
89
92
|
if !iopts[:fallback_application_name]
|
|
90
|
-
iopts[:fallback_application_name] =
|
|
93
|
+
iopts[:fallback_application_name] = PROGRAM_NAME.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
|
|
91
94
|
end
|
|
92
95
|
|
|
93
96
|
return connect_hash_to_string(iopts)
|
|
@@ -114,6 +117,9 @@ class PG::Connection
|
|
|
114
117
|
return str
|
|
115
118
|
end
|
|
116
119
|
|
|
120
|
+
BinarySignature = "PGCOPY\n\377\r\n\0".b
|
|
121
|
+
private_constant :BinarySignature
|
|
122
|
+
|
|
117
123
|
# call-seq:
|
|
118
124
|
# conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
|
|
119
125
|
#
|
|
@@ -160,6 +166,14 @@ class PG::Connection
|
|
|
160
166
|
# conn.put_copy_data ['more', 'data', 'to', 'copy']
|
|
161
167
|
# end
|
|
162
168
|
#
|
|
169
|
+
# Also PG::BinaryEncoder::CopyRow can be used to send data in binary format to the server.
|
|
170
|
+
# In this case copy_data generates the header and trailer data automatically:
|
|
171
|
+
# enco = PG::BinaryEncoder::CopyRow.new
|
|
172
|
+
# conn.copy_data "COPY my_table FROM STDIN (FORMAT binary)", enco do
|
|
173
|
+
# conn.put_copy_data ['some', 'data', 'to', 'copy']
|
|
174
|
+
# conn.put_copy_data ['more', 'data', 'to', 'copy']
|
|
175
|
+
# end
|
|
176
|
+
#
|
|
163
177
|
# Example with CSV output format:
|
|
164
178
|
# conn.copy_data "COPY my_table TO STDOUT CSV" do
|
|
165
179
|
# while row=conn.get_copy_data
|
|
@@ -181,6 +195,18 @@ class PG::Connection
|
|
|
181
195
|
# This receives all rows of +my_table+ as ruby array:
|
|
182
196
|
# ["some", "data", "to", "copy"]
|
|
183
197
|
# ["more", "data", "to", "copy"]
|
|
198
|
+
#
|
|
199
|
+
# Also PG::BinaryDecoder::CopyRow can be used to retrieve data in binary format from the server.
|
|
200
|
+
# In this case the header and trailer data is processed by the decoder and the remaining +nil+ from get_copy_data is processed by copy_data, so that binary data can be processed equally to text data:
|
|
201
|
+
# deco = PG::BinaryDecoder::CopyRow.new
|
|
202
|
+
# conn.copy_data "COPY my_table TO STDOUT (FORMAT binary)", deco do
|
|
203
|
+
# while row=conn.get_copy_data
|
|
204
|
+
# p row
|
|
205
|
+
# end
|
|
206
|
+
# end
|
|
207
|
+
# This receives all rows of +my_table+ as ruby array:
|
|
208
|
+
# ["some", "data", "to", "copy"]
|
|
209
|
+
# ["more", "data", "to", "copy"]
|
|
184
210
|
|
|
185
211
|
def copy_data( sql, coder=nil )
|
|
186
212
|
raise PG::NotInBlockingMode.new("copy_data can not be used in nonblocking mode", connection: self) if nonblocking?
|
|
@@ -189,10 +215,16 @@ class PG::Connection
|
|
|
189
215
|
case res.result_status
|
|
190
216
|
when PGRES_COPY_IN
|
|
191
217
|
begin
|
|
218
|
+
if coder && res.binary_tuples == 1
|
|
219
|
+
# Binary file header (11 byte signature, 32 bit flags and 32 bit extension length)
|
|
220
|
+
put_copy_data(BinarySignature + ("\x00" * 8))
|
|
221
|
+
end
|
|
222
|
+
|
|
192
223
|
if coder
|
|
193
224
|
old_coder = self.encoder_for_put_copy_data
|
|
194
225
|
self.encoder_for_put_copy_data = coder
|
|
195
226
|
end
|
|
227
|
+
|
|
196
228
|
yield res
|
|
197
229
|
rescue Exception => err
|
|
198
230
|
errmsg = "%s while copy data: %s" % [ err.class.name, err.message ]
|
|
@@ -205,6 +237,12 @@ class PG::Connection
|
|
|
205
237
|
raise err
|
|
206
238
|
else
|
|
207
239
|
begin
|
|
240
|
+
self.encoder_for_put_copy_data = old_coder if coder
|
|
241
|
+
|
|
242
|
+
if coder && res.binary_tuples == 1
|
|
243
|
+
put_copy_data("\xFF\xFF") # Binary file trailer 16 bit "-1"
|
|
244
|
+
end
|
|
245
|
+
|
|
208
246
|
put_copy_end
|
|
209
247
|
rescue PG::Error => err
|
|
210
248
|
raise PG::LostCopyState.new("#{err} (probably by executing another SQL query while running a COPY command)", connection: self)
|
|
@@ -226,6 +264,14 @@ class PG::Connection
|
|
|
226
264
|
discard_results
|
|
227
265
|
raise
|
|
228
266
|
else
|
|
267
|
+
if coder && res.binary_tuples == 1
|
|
268
|
+
# There are two end markers in binary mode: file trailer and the final nil.
|
|
269
|
+
# The file trailer is expected to be processed by BinaryDecoder::CopyRow and already returns nil, so that the remaining NULL from PQgetCopyData is retrieved here:
|
|
270
|
+
if get_copy_data
|
|
271
|
+
discard_results
|
|
272
|
+
raise PG::NotAllCopyDataRetrieved.new("Not all binary COPY data retrieved", connection: self)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
229
275
|
res = get_last_result
|
|
230
276
|
if !res
|
|
231
277
|
discard_results
|
|
@@ -320,6 +366,23 @@ class PG::Connection
|
|
|
320
366
|
end
|
|
321
367
|
end
|
|
322
368
|
|
|
369
|
+
# Read all pending socket input to internal memory and raise an exception in case of errors.
|
|
370
|
+
#
|
|
371
|
+
# This verifies that the connection socket is in a usable state and not aborted in any way.
|
|
372
|
+
# No communication is done with the server.
|
|
373
|
+
# Only pending data is read from the socket - the method doesn't wait for any outstanding server answers.
|
|
374
|
+
#
|
|
375
|
+
# Raises a kind of PG::Error if there was an error reading the data or if the socket is in a failure state.
|
|
376
|
+
#
|
|
377
|
+
# The method doesn't verify that the server is still responding.
|
|
378
|
+
# To verify that the communication to the server works, it is recommended to use something like <tt>conn.exec('')</tt> instead.
|
|
379
|
+
def check_socket
|
|
380
|
+
while socket_io.wait_readable(0)
|
|
381
|
+
consume_input
|
|
382
|
+
end
|
|
383
|
+
nil
|
|
384
|
+
end
|
|
385
|
+
|
|
323
386
|
# call-seq:
|
|
324
387
|
# conn.get_result() -> PG::Result
|
|
325
388
|
# conn.get_result() {|pg_result| block }
|
|
@@ -774,7 +837,10 @@ class PG::Connection
|
|
|
774
837
|
# PG::Connection.ping(connection_string) -> Integer
|
|
775
838
|
# PG::Connection.ping(host, port, options, tty, dbname, login, password) -> Integer
|
|
776
839
|
#
|
|
777
|
-
#
|
|
840
|
+
# PQpingParams reports the status of the server.
|
|
841
|
+
#
|
|
842
|
+
# It accepts connection parameters identical to those of PQ::Connection.new .
|
|
843
|
+
# It is not necessary to supply correct user name, password, or database name values to obtain the server status; however, if incorrect values are provided, the server will log a failed connection attempt.
|
|
778
844
|
#
|
|
779
845
|
# See PG::Connection.new for a description of the parameters.
|
|
780
846
|
#
|
|
@@ -787,6 +853,8 @@ class PG::Connection
|
|
|
787
853
|
# could not establish connection
|
|
788
854
|
# [+PQPING_NO_ATTEMPT+]
|
|
789
855
|
# connection not attempted (bad params)
|
|
856
|
+
#
|
|
857
|
+
# See also check_socket for a way to check the connection without doing any server communication.
|
|
790
858
|
def ping(*args)
|
|
791
859
|
if Fiber.respond_to?(:scheduler) && Fiber.scheduler
|
|
792
860
|
# Run PQping in a second thread to avoid blocking of the scheduler.
|
|
@@ -798,23 +866,25 @@ class PG::Connection
|
|
|
798
866
|
end
|
|
799
867
|
alias async_ping ping
|
|
800
868
|
|
|
801
|
-
REDIRECT_CLASS_METHODS = {
|
|
869
|
+
REDIRECT_CLASS_METHODS = PG.make_shareable({
|
|
802
870
|
:new => [:async_connect, :sync_connect],
|
|
803
871
|
:connect => [:async_connect, :sync_connect],
|
|
804
872
|
:open => [:async_connect, :sync_connect],
|
|
805
873
|
:setdb => [:async_connect, :sync_connect],
|
|
806
874
|
:setdblogin => [:async_connect, :sync_connect],
|
|
807
875
|
:ping => [:async_ping, :sync_ping],
|
|
808
|
-
}
|
|
876
|
+
})
|
|
877
|
+
private_constant :REDIRECT_CLASS_METHODS
|
|
809
878
|
|
|
810
879
|
# These methods are affected by PQsetnonblocking
|
|
811
|
-
REDIRECT_SEND_METHODS = {
|
|
880
|
+
REDIRECT_SEND_METHODS = PG.make_shareable({
|
|
812
881
|
:isnonblocking => [:async_isnonblocking, :sync_isnonblocking],
|
|
813
882
|
:nonblocking? => [:async_isnonblocking, :sync_isnonblocking],
|
|
814
883
|
:put_copy_data => [:async_put_copy_data, :sync_put_copy_data],
|
|
815
884
|
:put_copy_end => [:async_put_copy_end, :sync_put_copy_end],
|
|
816
885
|
:flush => [:async_flush, :sync_flush],
|
|
817
|
-
}
|
|
886
|
+
})
|
|
887
|
+
private_constant :REDIRECT_SEND_METHODS
|
|
818
888
|
REDIRECT_METHODS = {
|
|
819
889
|
:exec => [:async_exec, :sync_exec],
|
|
820
890
|
:query => [:async_exec, :sync_exec],
|
|
@@ -832,12 +902,14 @@ class PG::Connection
|
|
|
832
902
|
:client_encoding= => [:async_set_client_encoding, :sync_set_client_encoding],
|
|
833
903
|
:cancel => [:async_cancel, :sync_cancel],
|
|
834
904
|
}
|
|
905
|
+
private_constant :REDIRECT_METHODS
|
|
835
906
|
|
|
836
907
|
if PG::Connection.instance_methods.include? :async_encrypt_password
|
|
837
908
|
REDIRECT_METHODS.merge!({
|
|
838
909
|
:encrypt_password => [:async_encrypt_password, :sync_encrypt_password],
|
|
839
910
|
})
|
|
840
911
|
end
|
|
912
|
+
PG.make_shareable(REDIRECT_METHODS)
|
|
841
913
|
|
|
842
914
|
def async_send_api=(enable)
|
|
843
915
|
REDIRECT_SEND_METHODS.each do |ali, (async, sync)|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'date'
|
|
5
|
+
|
|
6
|
+
module PG
|
|
7
|
+
module TextDecoder
|
|
8
|
+
class Date < SimpleDecoder
|
|
9
|
+
def decode(string, tuple=nil, field=nil)
|
|
10
|
+
if string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/
|
|
11
|
+
::Date.new $1.to_i, $2.to_i, $3.to_i
|
|
12
|
+
else
|
|
13
|
+
string
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end # module PG
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module PG
|
|
7
|
+
module TextDecoder
|
|
8
|
+
class JSON < SimpleDecoder
|
|
9
|
+
def decode(string, tuple=nil, field=nil)
|
|
10
|
+
::JSON.parse(string, quirks_mode: true)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end # module PG
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module PG
|
|
5
|
+
module TextDecoder
|
|
6
|
+
# Convenience classes for timezone options
|
|
7
|
+
class TimestampUtc < Timestamp
|
|
8
|
+
def initialize(hash={}, **kwargs)
|
|
9
|
+
warn "PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}" unless hash.empty?
|
|
10
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
class TimestampUtcToLocal < Timestamp
|
|
14
|
+
def initialize(hash={}, **kwargs)
|
|
15
|
+
warn "PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}" unless hash.empty?
|
|
16
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
class TimestampLocal < Timestamp
|
|
20
|
+
def initialize(hash={}, **kwargs)
|
|
21
|
+
warn "PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}" unless hash.empty?
|
|
22
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# For backward compatibility:
|
|
27
|
+
TimestampWithoutTimeZone = TimestampLocal
|
|
28
|
+
TimestampWithTimeZone = Timestamp
|
|
29
|
+
end
|
|
30
|
+
end # module PG
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'ipaddr'
|
|
5
|
+
|
|
6
|
+
module PG
|
|
7
|
+
module TextEncoder
|
|
8
|
+
class Inet < SimpleEncoder
|
|
9
|
+
def encode(value)
|
|
10
|
+
case value
|
|
11
|
+
when IPAddr
|
|
12
|
+
default_prefix = (value.family == Socket::AF_INET ? 32 : 128)
|
|
13
|
+
s = value.to_s
|
|
14
|
+
if value.respond_to?(:prefix)
|
|
15
|
+
prefix = value.prefix
|
|
16
|
+
else
|
|
17
|
+
range = value.to_range
|
|
18
|
+
prefix = default_prefix - Math.log(((range.end.to_i - range.begin.to_i) + 1), 2).to_i
|
|
19
|
+
end
|
|
20
|
+
s << "/" << prefix.to_s if prefix != default_prefix
|
|
21
|
+
s
|
|
22
|
+
else
|
|
23
|
+
value
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end # module PG
|