pg 1.4.5-x64-mingw-ucrt → 1.5.0-x64-mingw-ucrt
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 +15 -9
- data/.github/workflows/binary-gems.yml +41 -10
- data/.github/workflows/source-gem.yml +18 -12
- data/.gitignore +11 -2
- data/.travis.yml +2 -2
- data/{History.rdoc → History.md} +223 -153
- data/README.ja.md +276 -0
- data/README.md +286 -0
- data/Rakefile +12 -3
- data/Rakefile.cross +7 -11
- data/certs/larskanis-2023.pem +24 -0
- 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 +156 -60
- 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/3.1/pg_ext.so +0 -0
- data/lib/3.2/pg_ext.so +0 -0
- 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 +82 -30
- data/lib/pg/exceptions.rb +7 -0
- 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 -15
- data/lib/x64-mingw-ucrt/libpq.dll +0 -0
- data/pg.gemspec +4 -2
- data/rakelib/task_extension.rb +1 -1
- data/translation/.po4a-version +7 -0
- data/translation/po/all.pot +910 -0
- data/translation/po/ja.po +1047 -0
- data/translation/po4a.cfg +12 -0
- data.tar.gz.sig +0 -0
- metadata +111 -35
- metadata.gz.sig +0 -0
- data/README.ja.rdoc +0 -13
- data/README.rdoc +0 -233
- 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
@@ -46,22 +46,45 @@ require 'pg' unless defined?( PG )
|
|
46
46
|
# This prints the rows with type casted columns:
|
47
47
|
# ["a", 123, [5, 4, 3]]
|
48
48
|
#
|
49
|
+
# Very similar with binary format:
|
50
|
+
#
|
51
|
+
# conn.exec( "CREATE TABLE copytable AS VALUES('a', 123, '2023-03-19 18:39:44'::TIMESTAMP)" )
|
52
|
+
#
|
53
|
+
# # Retrieve table OIDs per empty result set in binary format.
|
54
|
+
# res = conn.exec_params( "SELECT * FROM copytable LIMIT 0", [], 1 )
|
55
|
+
# # Build a type map for common database to ruby type decoders.
|
56
|
+
# btm = PG::BasicTypeMapForResults.new(conn)
|
57
|
+
# # Build a PG::TypeMapByColumn with decoders suitable for copytable.
|
58
|
+
# tm = btm.build_column_map( res )
|
59
|
+
# row_decoder = PG::BinaryDecoder::CopyRow.new type_map: tm
|
60
|
+
#
|
61
|
+
# conn.copy_data( "COPY copytable TO STDOUT WITH (FORMAT binary)", row_decoder ) do |res|
|
62
|
+
# while row=conn.get_copy_data
|
63
|
+
# p row
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
# This prints the rows with type casted columns:
|
67
|
+
# ["a", 123, 2023-03-19 18:39:44 UTC]
|
68
|
+
#
|
49
69
|
# See also PG::BasicTypeMapBasedOnResult for the encoder direction and PG::BasicTypeRegistry for the definition of additional types.
|
50
70
|
class PG::BasicTypeMapForResults < PG::TypeMapByOid
|
51
71
|
include PG::BasicTypeRegistry::Checker
|
52
72
|
|
53
73
|
class WarningTypeMap < PG::TypeMapInRuby
|
54
74
|
def initialize(typenames)
|
55
|
-
@already_warned =
|
75
|
+
@already_warned = {}
|
56
76
|
@typenames_by_oid = typenames
|
57
77
|
end
|
58
78
|
|
59
79
|
def typecast_result_value(result, _tuple, field)
|
60
80
|
format = result.fformat(field)
|
61
81
|
oid = result.ftype(field)
|
62
|
-
unless @already_warned
|
82
|
+
unless @already_warned.dig(format, oid)
|
63
83
|
warn "Warning: no type cast defined for type #{@typenames_by_oid[oid].inspect} format #{format} with oid #{oid}. Please cast this type explicitly to TEXT to be safe for future changes."
|
64
|
-
|
84
|
+
unless frozen?
|
85
|
+
@already_warned[format] ||= {}
|
86
|
+
@already_warned[format][oid] = true
|
87
|
+
end
|
65
88
|
end
|
66
89
|
super
|
67
90
|
end
|
@@ -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(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC, **hash, **kwargs)
|
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(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL, **hash, **kwargs)
|
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(flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL, **hash, **kwargs)
|
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(flags: PG::Coder::TIMESTAMP_DB_UTC, **hash, **kwargs)
|
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(flags: PG::Coder::TIMESTAMP_DB_LOCAL, **hash, **kwargs)
|
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
|
#
|
@@ -189,18 +195,38 @@ class PG::Connection
|
|
189
195
|
case res.result_status
|
190
196
|
when PGRES_COPY_IN
|
191
197
|
begin
|
198
|
+
if res.binary_tuples == 1
|
199
|
+
# Binary file header (11 byte signature, 32 bit flags and 32 bit extension length)
|
200
|
+
put_copy_data(BinarySignature + ("\x00" * 8))
|
201
|
+
end
|
202
|
+
|
192
203
|
if coder
|
193
204
|
old_coder = self.encoder_for_put_copy_data
|
194
205
|
self.encoder_for_put_copy_data = coder
|
195
206
|
end
|
207
|
+
|
196
208
|
yield res
|
197
209
|
rescue Exception => err
|
198
210
|
errmsg = "%s while copy data: %s" % [ err.class.name, err.message ]
|
199
|
-
|
200
|
-
|
201
|
-
|
211
|
+
begin
|
212
|
+
put_copy_end( errmsg )
|
213
|
+
rescue PG::Error
|
214
|
+
# Ignore error in cleanup to avoid losing original exception
|
215
|
+
end
|
216
|
+
discard_results
|
217
|
+
raise err
|
202
218
|
else
|
203
|
-
|
219
|
+
begin
|
220
|
+
self.encoder_for_put_copy_data = old_coder if coder
|
221
|
+
|
222
|
+
if res.binary_tuples == 1
|
223
|
+
put_copy_data("\xFF\xFF") # Binary file trailer 16 bit "-1"
|
224
|
+
end
|
225
|
+
|
226
|
+
put_copy_end
|
227
|
+
rescue PG::Error => err
|
228
|
+
raise PG::LostCopyState.new("#{err} (probably by executing another SQL query while running a COPY command)", connection: self)
|
229
|
+
end
|
204
230
|
get_last_result
|
205
231
|
ensure
|
206
232
|
self.encoder_for_put_copy_data = old_coder if coder
|
@@ -213,24 +239,24 @@ class PG::Connection
|
|
213
239
|
self.decoder_for_get_copy_data = coder
|
214
240
|
end
|
215
241
|
yield res
|
216
|
-
rescue Exception
|
242
|
+
rescue Exception
|
217
243
|
cancel
|
218
|
-
|
219
|
-
|
244
|
+
discard_results
|
245
|
+
raise
|
246
|
+
else
|
247
|
+
if res.binary_tuples == 1
|
248
|
+
# there are two end markers in binary mode: file trailer and the final nil
|
249
|
+
if get_copy_data
|
250
|
+
discard_results
|
251
|
+
raise PG::NotAllCopyDataRetrieved.new("Not all binary COPY data retrieved", connection: self)
|
220
252
|
end
|
221
|
-
rescue PG::Error
|
222
|
-
# Ignore error in cleanup to avoid losing original exception
|
223
253
|
end
|
224
|
-
while get_result
|
225
|
-
end
|
226
|
-
raise err
|
227
|
-
else
|
228
254
|
res = get_last_result
|
229
|
-
if !res
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
255
|
+
if !res
|
256
|
+
discard_results
|
257
|
+
raise PG::LostCopyState.new("Lost COPY state (probably by executing another SQL query while running a COPY command)", connection: self)
|
258
|
+
elsif res.result_status != PGRES_COMMAND_OK
|
259
|
+
discard_results
|
234
260
|
raise PG::NotAllCopyDataRetrieved.new("Not all COPY data retrieved", connection: self)
|
235
261
|
end
|
236
262
|
res
|
@@ -319,6 +345,23 @@ class PG::Connection
|
|
319
345
|
end
|
320
346
|
end
|
321
347
|
|
348
|
+
# Read all pending socket input to internal memory and raise an exception in case of errors.
|
349
|
+
#
|
350
|
+
# This verifies that the connection socket is in a usable state and not aborted in any way.
|
351
|
+
# No communication is done with the server.
|
352
|
+
# Only pending data is read from the socket - the method doesn't wait for any outstanding server answers.
|
353
|
+
#
|
354
|
+
# Raises a kind of PG::Error if there was an error reading the data or if the socket is in a failure state.
|
355
|
+
#
|
356
|
+
# The method doesn't verify that the server is still responding.
|
357
|
+
# To verify that the communication to the server works, it is recommended to use something like <tt>conn.exec('')</tt> instead.
|
358
|
+
def check_socket
|
359
|
+
while socket_io.wait_readable(0)
|
360
|
+
consume_input
|
361
|
+
end
|
362
|
+
nil
|
363
|
+
end
|
364
|
+
|
322
365
|
# call-seq:
|
323
366
|
# conn.get_result() -> PG::Result
|
324
367
|
# conn.get_result() {|pg_result| block }
|
@@ -773,7 +816,10 @@ class PG::Connection
|
|
773
816
|
# PG::Connection.ping(connection_string) -> Integer
|
774
817
|
# PG::Connection.ping(host, port, options, tty, dbname, login, password) -> Integer
|
775
818
|
#
|
776
|
-
#
|
819
|
+
# PQpingParams reports the status of the server.
|
820
|
+
#
|
821
|
+
# It accepts connection parameters identical to those of PQ::Connection.new .
|
822
|
+
# 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.
|
777
823
|
#
|
778
824
|
# See PG::Connection.new for a description of the parameters.
|
779
825
|
#
|
@@ -786,6 +832,8 @@ class PG::Connection
|
|
786
832
|
# could not establish connection
|
787
833
|
# [+PQPING_NO_ATTEMPT+]
|
788
834
|
# connection not attempted (bad params)
|
835
|
+
#
|
836
|
+
# See also check_socket for a way to check the connection without doing any server communication.
|
789
837
|
def ping(*args)
|
790
838
|
if Fiber.respond_to?(:scheduler) && Fiber.scheduler
|
791
839
|
# Run PQping in a second thread to avoid blocking of the scheduler.
|
@@ -797,23 +845,25 @@ class PG::Connection
|
|
797
845
|
end
|
798
846
|
alias async_ping ping
|
799
847
|
|
800
|
-
REDIRECT_CLASS_METHODS = {
|
848
|
+
REDIRECT_CLASS_METHODS = PG.make_shareable({
|
801
849
|
:new => [:async_connect, :sync_connect],
|
802
850
|
:connect => [:async_connect, :sync_connect],
|
803
851
|
:open => [:async_connect, :sync_connect],
|
804
852
|
:setdb => [:async_connect, :sync_connect],
|
805
853
|
:setdblogin => [:async_connect, :sync_connect],
|
806
854
|
:ping => [:async_ping, :sync_ping],
|
807
|
-
}
|
855
|
+
})
|
856
|
+
private_constant :REDIRECT_CLASS_METHODS
|
808
857
|
|
809
858
|
# These methods are affected by PQsetnonblocking
|
810
|
-
REDIRECT_SEND_METHODS = {
|
859
|
+
REDIRECT_SEND_METHODS = PG.make_shareable({
|
811
860
|
:isnonblocking => [:async_isnonblocking, :sync_isnonblocking],
|
812
861
|
:nonblocking? => [:async_isnonblocking, :sync_isnonblocking],
|
813
862
|
:put_copy_data => [:async_put_copy_data, :sync_put_copy_data],
|
814
863
|
:put_copy_end => [:async_put_copy_end, :sync_put_copy_end],
|
815
864
|
:flush => [:async_flush, :sync_flush],
|
816
|
-
}
|
865
|
+
})
|
866
|
+
private_constant :REDIRECT_SEND_METHODS
|
817
867
|
REDIRECT_METHODS = {
|
818
868
|
:exec => [:async_exec, :sync_exec],
|
819
869
|
:query => [:async_exec, :sync_exec],
|
@@ -831,12 +881,14 @@ class PG::Connection
|
|
831
881
|
:client_encoding= => [:async_set_client_encoding, :sync_set_client_encoding],
|
832
882
|
:cancel => [:async_cancel, :sync_cancel],
|
833
883
|
}
|
884
|
+
private_constant :REDIRECT_METHODS
|
834
885
|
|
835
886
|
if PG::Connection.instance_methods.include? :async_encrypt_password
|
836
887
|
REDIRECT_METHODS.merge!({
|
837
888
|
:encrypt_password => [:async_encrypt_password, :sync_encrypt_password],
|
838
889
|
})
|
839
890
|
end
|
891
|
+
PG.make_shareable(REDIRECT_METHODS)
|
840
892
|
|
841
893
|
def async_send_api=(enable)
|
842
894
|
REDIRECT_SEND_METHODS.each do |ali, (async, sync)|
|
data/lib/pg/exceptions.rb
CHANGED
@@ -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(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC, **hash, **kwargs)
|
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(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL, **hash, **kwargs)
|
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(flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL, **hash, **kwargs)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# For backward compatibility:
|
27
|
+
TimestampWithoutTimeZone = TimestampLocal
|
28
|
+
TimestampWithTimeZone = Timestamp
|
29
|
+
end
|
30
|
+
end # module PG
|