pg 1.5.4 → 1.5.8
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/.appveyor.yml +1 -1
- data/.github/workflows/binary-gems.yml +12 -12
- data/.github/workflows/source-gem.yml +28 -17
- data/Gemfile +6 -0
- data/History.md +41 -4
- data/Rakefile +4 -1
- data/Rakefile.cross +5 -5
- data/certs/kanis@comcard.de.pem +20 -0
- data/certs/larskanis-2024.pem +24 -0
- data/ext/errorcodes.txt +1 -1
- data/ext/extconf.rb +3 -0
- data/ext/pg.c +1 -1
- data/ext/pg_binary_decoder.c +2 -0
- data/ext/pg_binary_encoder.c +1 -1
- data/ext/pg_connection.c +45 -13
- data/ext/pg_copy_coder.c +17 -13
- data/ext/pg_record_coder.c +6 -6
- data/ext/pg_result.c +2 -2
- data/ext/pg_text_decoder.c +4 -1
- data/ext/pg_text_encoder.c +17 -11
- data/lib/pg/basic_type_map_for_queries.rb +8 -4
- data/lib/pg/basic_type_registry.rb +14 -2
- data/lib/pg/connection.rb +56 -34
- data/lib/pg/exceptions.rb +6 -0
- data/lib/pg/text_decoder/date.rb +3 -0
- data/lib/pg/text_decoder/json.rb +3 -0
- data/lib/pg/text_encoder/date.rb +1 -0
- data/lib/pg/text_encoder/inet.rb +3 -0
- data/lib/pg/text_encoder/json.rb +3 -0
- data/lib/pg/version.rb +1 -1
- data/lib/pg.rb +10 -0
- data/pg.gemspec +1 -1
- data.tar.gz.sig +0 -0
- metadata +30 -28
- metadata.gz.sig +0 -0
- data/translation/.po4a-version +0 -7
- data/translation/po/all.pot +0 -936
- data/translation/po/ja.po +0 -1036
- data/translation/po4a.cfg +0 -12
data/ext/pg_copy_coder.c
CHANGED
@@ -212,6 +212,7 @@ pg_copycoder_type_map_get(VALUE self)
|
|
212
212
|
*
|
213
213
|
* See also PG::TextDecoder::CopyRow for the decoding direction with
|
214
214
|
* PG::Connection#get_copy_data .
|
215
|
+
* And see PG::BinaryEncoder::CopyRow for an encoder of the COPY binary format.
|
215
216
|
*/
|
216
217
|
static int
|
217
218
|
pg_text_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx)
|
@@ -235,7 +236,7 @@ pg_text_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermedia
|
|
235
236
|
char *ptr1;
|
236
237
|
char *ptr2;
|
237
238
|
int strlen;
|
238
|
-
int
|
239
|
+
int backslashes;
|
239
240
|
VALUE subint;
|
240
241
|
VALUE entry;
|
241
242
|
|
@@ -286,19 +287,19 @@ pg_text_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermedia
|
|
286
287
|
ptr2 = current_out + strlen;
|
287
288
|
|
288
289
|
/* count required backlashs */
|
289
|
-
for(
|
290
|
+
for(backslashes = 0; ptr1 != ptr2; ptr1++) {
|
290
291
|
/* Escape backslash itself, newline, carriage return, and the current delimiter character. */
|
291
292
|
if(*ptr1 == '\\' || *ptr1 == '\n' || *ptr1 == '\r' || *ptr1 == this->delimiter){
|
292
|
-
|
293
|
+
backslashes++;
|
293
294
|
}
|
294
295
|
}
|
295
296
|
|
296
297
|
ptr1 = current_out + strlen;
|
297
|
-
ptr2 = current_out + strlen +
|
298
|
+
ptr2 = current_out + strlen + backslashes;
|
298
299
|
current_out = ptr2;
|
299
300
|
|
300
301
|
/* Then store the escaped string on the final position, walking
|
301
|
-
* right to left, until all
|
302
|
+
* right to left, until all backslashes are placed. */
|
302
303
|
while( ptr1 != ptr2 ) {
|
303
304
|
*--ptr2 = *--ptr1;
|
304
305
|
if(*ptr1 == '\\' || *ptr1 == '\n' || *ptr1 == '\r' || *ptr1 == this->delimiter){
|
@@ -358,6 +359,7 @@ pg_text_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermedia
|
|
358
359
|
*
|
359
360
|
* See also PG::BinaryDecoder::CopyRow for the decoding direction with
|
360
361
|
* PG::Connection#get_copy_data .
|
362
|
+
* And see PG::TextEncoder::CopyRow for an encoder of the COPY text format.
|
361
363
|
*/
|
362
364
|
static int
|
363
365
|
pg_bin_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx)
|
@@ -391,7 +393,7 @@ pg_bin_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediat
|
|
391
393
|
|
392
394
|
switch(TYPE(entry)){
|
393
395
|
case T_NIL:
|
394
|
-
/* 4 bytes for -1
|
396
|
+
/* 4 bytes for -1 indicating a NULL value */
|
395
397
|
PG_RB_STR_ENSURE_CAPA( *intermediate, 4, current_out, end_capa_ptr );
|
396
398
|
write_nbo32(-1, current_out);
|
397
399
|
current_out += 4;
|
@@ -496,6 +498,7 @@ GetDecimalFromHex(char hex)
|
|
496
498
|
*
|
497
499
|
* See also PG::TextEncoder::CopyRow for the encoding direction with
|
498
500
|
* PG::Connection#put_copy_data .
|
501
|
+
* And see PG::BinaryDecoder::CopyRow for a decoder of the COPY binary format.
|
499
502
|
*/
|
500
503
|
/*
|
501
504
|
* Parse the current line into separate attributes (fields),
|
@@ -763,6 +766,7 @@ static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
|
|
763
766
|
*
|
764
767
|
* See also PG::BinaryEncoder::CopyRow for the encoding direction with
|
765
768
|
* PG::Connection#put_copy_data .
|
769
|
+
* And see PG::TextDecoder::CopyRow for a decoder of the COPY text format.
|
766
770
|
*/
|
767
771
|
static VALUE
|
768
772
|
pg_bin_dec_copy_row(t_pg_coder *conv, const char *input_line, int len, int _tuple, int _field, int enc_idx)
|
@@ -795,26 +799,26 @@ pg_bin_dec_copy_row(t_pg_coder *conv, const char *input_line, int len, int _tupl
|
|
795
799
|
cur_ptr = input_line;
|
796
800
|
line_end_ptr = input_line + len;
|
797
801
|
|
798
|
-
if (cur_ptr
|
802
|
+
if (line_end_ptr - cur_ptr >= 11 && memcmp(cur_ptr, BinarySignature, 11) == 0){
|
799
803
|
/* binary COPY header signature detected -> just drop it */
|
800
804
|
int ext_bytes;
|
801
805
|
cur_ptr += 11;
|
802
806
|
|
803
807
|
/* read flags */
|
804
|
-
if (cur_ptr
|
808
|
+
if (line_end_ptr - cur_ptr < 4 ) goto length_error;
|
805
809
|
cur_ptr += 4;
|
806
810
|
|
807
811
|
/* read header extensions */
|
808
|
-
if (cur_ptr
|
812
|
+
if (line_end_ptr - cur_ptr < 4 ) goto length_error;
|
809
813
|
ext_bytes = read_nbo32(cur_ptr);
|
810
814
|
if (ext_bytes < 0) goto length_error;
|
811
815
|
cur_ptr += 4;
|
812
|
-
if (cur_ptr
|
816
|
+
if (line_end_ptr - cur_ptr < ext_bytes ) goto length_error;
|
813
817
|
cur_ptr += ext_bytes;
|
814
818
|
}
|
815
819
|
|
816
820
|
/* read row header */
|
817
|
-
if (cur_ptr
|
821
|
+
if (line_end_ptr - cur_ptr < 2 ) goto length_error;
|
818
822
|
nfields = read_nbo16(cur_ptr);
|
819
823
|
cur_ptr += 2;
|
820
824
|
|
@@ -830,7 +834,7 @@ pg_bin_dec_copy_row(t_pg_coder *conv, const char *input_line, int len, int _tupl
|
|
830
834
|
VALUE field_value;
|
831
835
|
|
832
836
|
/* read field size */
|
833
|
-
if (cur_ptr
|
837
|
+
if (line_end_ptr - cur_ptr < 4 ) goto length_error;
|
834
838
|
input_len = read_nbo32(cur_ptr);
|
835
839
|
cur_ptr += 4;
|
836
840
|
|
@@ -839,7 +843,7 @@ pg_bin_dec_copy_row(t_pg_coder *conv, const char *input_line, int len, int _tupl
|
|
839
843
|
/* NULL indicator */
|
840
844
|
rb_ary_push(array, Qnil);
|
841
845
|
} else {
|
842
|
-
if (cur_ptr
|
846
|
+
if (line_end_ptr - cur_ptr < input_len ) goto length_error;
|
843
847
|
|
844
848
|
/* copy input data to field_str */
|
845
849
|
PG_RB_STR_ENSURE_CAPA( field_str, input_len, output_ptr, end_capa_ptr );
|
data/ext/pg_record_coder.c
CHANGED
@@ -198,7 +198,7 @@ pg_text_enc_record(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate
|
|
198
198
|
char *ptr1;
|
199
199
|
char *ptr2;
|
200
200
|
long strlen;
|
201
|
-
int
|
201
|
+
int backslashes;
|
202
202
|
VALUE subint;
|
203
203
|
VALUE entry;
|
204
204
|
|
@@ -249,19 +249,19 @@ pg_text_enc_record(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate
|
|
249
249
|
ptr2 = current_out + strlen;
|
250
250
|
|
251
251
|
/* count required backlashs */
|
252
|
-
for(
|
252
|
+
for(backslashes = 0; ptr1 != ptr2; ptr1++) {
|
253
253
|
/* Escape backslash itself, newline, carriage return, and the current delimiter character. */
|
254
254
|
if(*ptr1 == '"' || *ptr1 == '\\'){
|
255
|
-
|
255
|
+
backslashes++;
|
256
256
|
}
|
257
257
|
}
|
258
258
|
|
259
259
|
ptr1 = current_out + strlen;
|
260
|
-
ptr2 = current_out + strlen +
|
260
|
+
ptr2 = current_out + strlen + backslashes;
|
261
261
|
current_out = ptr2;
|
262
262
|
|
263
263
|
/* Then store the escaped string on the final position, walking
|
264
|
-
* right to left, until all
|
264
|
+
* right to left, until all backslashes are placed. */
|
265
265
|
while( ptr1 != ptr2 ) {
|
266
266
|
*--ptr2 = *--ptr1;
|
267
267
|
if(*ptr1 == '"' || *ptr1 == '\\'){
|
@@ -340,7 +340,7 @@ record_isspace(char ch)
|
|
340
340
|
* conn.exec("SELECT * FROM my_table").map_types!(PG::TypeMapByColumn.new([deco]*2)).to_a
|
341
341
|
* # => [{"v1"=>[2.0, 3.0], "v2"=>[4.0, 5.0]}, {"v1"=>[6.0, 7.0], "v2"=>[8.0, 9.0]}]
|
342
342
|
*
|
343
|
-
* It's more
|
343
|
+
* It's more convenient to use the PG::BasicTypeRegistry, which is based on database OIDs.
|
344
344
|
* # Fetch a NULL record of our type to retrieve the OIDs of the two fields "r" and "i"
|
345
345
|
* oids = conn.exec( "SELECT (NULL::complex).*" )
|
346
346
|
* # Build a type map (PG::TypeMapByColumn) for decoding the "complex" type
|
data/ext/pg_result.c
CHANGED
@@ -664,7 +664,7 @@ pgresult_verbose_error_message(VALUE self, VALUE verbosity, VALUE show_context)
|
|
664
664
|
* An example:
|
665
665
|
*
|
666
666
|
* begin
|
667
|
-
* conn.exec( "SELECT * FROM
|
667
|
+
* conn.exec( "SELECT * FROM nonexistent_table" )
|
668
668
|
* rescue PG::Error => err
|
669
669
|
* p [
|
670
670
|
* err.result.error_field( PG::Result::PG_DIAG_SEVERITY ),
|
@@ -684,7 +684,7 @@ pgresult_verbose_error_message(VALUE self, VALUE verbosity, VALUE show_context)
|
|
684
684
|
*
|
685
685
|
* Outputs:
|
686
686
|
*
|
687
|
-
* ["ERROR", "42P01", "relation \"
|
687
|
+
* ["ERROR", "42P01", "relation \"nonexistent_table\" does not exist", nil, nil,
|
688
688
|
* "15", nil, nil, nil, "path/to/parse_relation.c", "857", "parserOpenTable"]
|
689
689
|
*/
|
690
690
|
static VALUE
|
data/ext/pg_text_decoder.c
CHANGED
@@ -163,6 +163,8 @@ pg_text_dec_integer(t_pg_coder *conv, const char *val, int len, int tuple, int f
|
|
163
163
|
* This is a decoder class for conversion of PostgreSQL numeric types
|
164
164
|
* to Ruby BigDecimal objects.
|
165
165
|
*
|
166
|
+
* As soon as this class is used, it requires the 'bigdecimal' gem.
|
167
|
+
*
|
166
168
|
*/
|
167
169
|
static VALUE
|
168
170
|
pg_text_dec_numeric(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx)
|
@@ -174,7 +176,7 @@ pg_text_dec_numeric(t_pg_coder *conv, const char *val, int len, int tuple, int f
|
|
174
176
|
static VALUE
|
175
177
|
init_pg_text_decoder_numeric(VALUE rb_mPG_TextDecoder)
|
176
178
|
{
|
177
|
-
|
179
|
+
rb_funcall(rb_mPG, rb_intern("require_bigdecimal_without_warning"), 0);
|
178
180
|
s_id_BigDecimal = rb_intern("BigDecimal");
|
179
181
|
|
180
182
|
/* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Numeric", rb_cPG_SimpleDecoder ); */
|
@@ -811,6 +813,7 @@ static VALUE pg_text_dec_timestamp(t_pg_coder *conv, const char *val, int len, i
|
|
811
813
|
* This is a decoder class for conversion of PostgreSQL inet type
|
812
814
|
* to Ruby IPAddr values.
|
813
815
|
*
|
816
|
+
* As soon as this class is used, it requires the ruby standard library 'ipaddr'.
|
814
817
|
*/
|
815
818
|
static VALUE
|
816
819
|
pg_text_dec_inet(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx)
|
data/ext/pg_text_encoder.c
CHANGED
@@ -119,6 +119,10 @@ pg_text_enc_boolean(t_pg_coder *this, VALUE value, char *out, VALUE *intermediat
|
|
119
119
|
int
|
120
120
|
pg_coder_enc_to_s(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, int enc_idx)
|
121
121
|
{
|
122
|
+
/* Attention:
|
123
|
+
* In contrast to all other encoders, the "this" pointer of this encoder can be NULL.
|
124
|
+
* This is because it is used as a fall-back if no encoder is defined.
|
125
|
+
*/
|
122
126
|
VALUE str = rb_obj_as_string(value);
|
123
127
|
if( ENCODING_GET(str) == enc_idx ){
|
124
128
|
*intermediate = str;
|
@@ -345,6 +349,8 @@ pg_text_enc_float(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate,
|
|
345
349
|
*
|
346
350
|
* It converts Integer, Float and BigDecimal objects.
|
347
351
|
* All other objects are expected to respond to +to_s+.
|
352
|
+
*
|
353
|
+
* As soon as this class is used, it requires the 'bigdecimal' gem.
|
348
354
|
*/
|
349
355
|
static int
|
350
356
|
pg_text_enc_numeric(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, int enc_idx)
|
@@ -377,7 +383,7 @@ init_pg_text_encoder_numeric(VALUE rb_mPG_TextDecoder)
|
|
377
383
|
{
|
378
384
|
s_str_F = rb_str_freeze(rb_str_new_cstr("F"));
|
379
385
|
rb_global_variable(&s_str_F);
|
380
|
-
|
386
|
+
rb_funcall(rb_mPG, rb_intern("require_bigdecimal_without_warning"), 0);
|
381
387
|
s_cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
|
382
388
|
|
383
389
|
/* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Numeric", rb_cPG_SimpleEncoder ); */
|
@@ -437,7 +443,7 @@ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
|
|
437
443
|
t_pg_composite_coder *this = _this;
|
438
444
|
char *ptr1;
|
439
445
|
char *ptr2;
|
440
|
-
int
|
446
|
+
int backslashes = 0;
|
441
447
|
int needquote;
|
442
448
|
|
443
449
|
/* count data plus backslashes; detect chars needing quotes */
|
@@ -454,7 +460,7 @@ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
|
|
454
460
|
|
455
461
|
if (ch == '"' || ch == '\\'){
|
456
462
|
needquote = 1;
|
457
|
-
|
463
|
+
backslashes++;
|
458
464
|
} else if (ch == '{' || ch == '}' || ch == this->delimiter ||
|
459
465
|
ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f'){
|
460
466
|
needquote = 1;
|
@@ -463,12 +469,12 @@ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
|
|
463
469
|
|
464
470
|
if( needquote ){
|
465
471
|
ptr1 = p_in + strlen;
|
466
|
-
ptr2 = p_out + strlen +
|
472
|
+
ptr2 = p_out + strlen + backslashes + 2;
|
467
473
|
/* Write end quote */
|
468
474
|
*--ptr2 = '"';
|
469
475
|
|
470
476
|
/* Then store the escaped string on the final position, walking
|
471
|
-
* right to left, until all
|
477
|
+
* right to left, until all backslashes are placed. */
|
472
478
|
while( ptr1 != p_in ) {
|
473
479
|
*--ptr2 = *--ptr1;
|
474
480
|
if(*ptr2 == '"' || *ptr2 == '\\'){
|
@@ -477,7 +483,7 @@ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
|
|
477
483
|
}
|
478
484
|
/* Write start quote */
|
479
485
|
*p_out = '"';
|
480
|
-
return strlen +
|
486
|
+
return strlen + backslashes + 2;
|
481
487
|
} else {
|
482
488
|
if( p_in != p_out )
|
483
489
|
memcpy( p_out, p_in, strlen );
|
@@ -692,22 +698,22 @@ static int
|
|
692
698
|
quote_literal_buffer( void *_this, char *p_in, int strlen, char *p_out ){
|
693
699
|
char *ptr1;
|
694
700
|
char *ptr2;
|
695
|
-
int
|
701
|
+
int backslashes = 0;
|
696
702
|
|
697
703
|
/* count required backlashs */
|
698
704
|
for(ptr1 = p_in; ptr1 != p_in + strlen; ptr1++) {
|
699
705
|
if (*ptr1 == '\''){
|
700
|
-
|
706
|
+
backslashes++;
|
701
707
|
}
|
702
708
|
}
|
703
709
|
|
704
710
|
ptr1 = p_in + strlen;
|
705
|
-
ptr2 = p_out + strlen +
|
711
|
+
ptr2 = p_out + strlen + backslashes + 2;
|
706
712
|
/* Write end quote */
|
707
713
|
*--ptr2 = '\'';
|
708
714
|
|
709
715
|
/* Then store the escaped string on the final position, walking
|
710
|
-
* right to left, until all
|
716
|
+
* right to left, until all backslashes are placed. */
|
711
717
|
while( ptr1 != p_in ) {
|
712
718
|
*--ptr2 = *--ptr1;
|
713
719
|
if(*ptr2 == '\''){
|
@@ -716,7 +722,7 @@ quote_literal_buffer( void *_this, char *p_in, int strlen, char *p_out ){
|
|
716
722
|
}
|
717
723
|
/* Write start quote */
|
718
724
|
*p_out = '\'';
|
719
|
-
return strlen +
|
725
|
+
return strlen + backslashes + 2;
|
720
726
|
}
|
721
727
|
|
722
728
|
|
@@ -166,6 +166,12 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
|
|
166
166
|
@textarray_encoder
|
167
167
|
end
|
168
168
|
|
169
|
+
begin
|
170
|
+
PG.require_bigdecimal_without_warning
|
171
|
+
has_bigdecimal = true
|
172
|
+
rescue LoadError
|
173
|
+
end
|
174
|
+
|
169
175
|
DEFAULT_TYPE_MAP = PG.make_shareable({
|
170
176
|
TrueClass => [1, 'bool', 'bool'],
|
171
177
|
FalseClass => [1, 'bool', 'bool'],
|
@@ -173,7 +179,6 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
|
|
173
179
|
# to unnecessary type conversions on server side.
|
174
180
|
Integer => [0, 'int8'],
|
175
181
|
Float => [0, 'float8'],
|
176
|
-
BigDecimal => [0, 'numeric'],
|
177
182
|
Time => [0, 'timestamptz'],
|
178
183
|
# We use text format and no type OID for IPAddr, because setting the OID can lead
|
179
184
|
# to unnecessary inet/cidr conversions on the server side.
|
@@ -181,7 +186,7 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
|
|
181
186
|
Hash => [0, 'json'],
|
182
187
|
Array => :get_array_type,
|
183
188
|
BinaryData => [1, 'bytea'],
|
184
|
-
})
|
189
|
+
}.merge(has_bigdecimal ? {BigDecimal => [0, 'numeric']} : {}))
|
185
190
|
private_constant :DEFAULT_TYPE_MAP
|
186
191
|
|
187
192
|
DEFAULT_ARRAY_TYPE_MAP = PG.make_shareable({
|
@@ -190,9 +195,8 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
|
|
190
195
|
Integer => [0, '_int8'],
|
191
196
|
String => [0, '_text'],
|
192
197
|
Float => [0, '_float8'],
|
193
|
-
BigDecimal => [0, '_numeric'],
|
194
198
|
Time => [0, '_timestamptz'],
|
195
199
|
IPAddr => [0, '_inet'],
|
196
|
-
})
|
200
|
+
}.merge(has_bigdecimal ? {BigDecimal => [0, '_numeric']} : {}))
|
197
201
|
private_constant :DEFAULT_ARRAY_TYPE_MAP
|
198
202
|
end
|
@@ -171,7 +171,14 @@ class PG::BasicTypeRegistry
|
|
171
171
|
include Checker
|
172
172
|
|
173
173
|
def initialize
|
174
|
-
#
|
174
|
+
# @coders_by_name has a content of
|
175
|
+
# Array< Hash< Symbol: Hash< String: Coder > > >
|
176
|
+
#
|
177
|
+
# The layers are:
|
178
|
+
# * index of Array is 0 (text) and 1 (binary)
|
179
|
+
# * Symbol key in the middle Hash is :encoder and :decoder
|
180
|
+
# * String key in the inner Hash corresponds to the `typname` column in the table pg_type
|
181
|
+
# * Coder value in the inner Hash is the associated coder object
|
175
182
|
@coders_by_name = []
|
176
183
|
end
|
177
184
|
|
@@ -225,7 +232,11 @@ class PG::BasicTypeRegistry
|
|
225
232
|
alias_type 0, 'int8', 'int2'
|
226
233
|
alias_type 0, 'oid', 'int2'
|
227
234
|
|
228
|
-
|
235
|
+
begin
|
236
|
+
PG.require_bigdecimal_without_warning
|
237
|
+
register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric
|
238
|
+
rescue LoadError
|
239
|
+
end
|
229
240
|
register_type 0, 'text', PG::TextEncoder::String, PG::TextDecoder::String
|
230
241
|
alias_type 0, 'varchar', 'text'
|
231
242
|
alias_type 0, 'char', 'text'
|
@@ -267,6 +278,7 @@ class PG::BasicTypeRegistry
|
|
267
278
|
register_type 0, 'inet', PG::TextEncoder::Inet, PG::TextDecoder::Inet
|
268
279
|
alias_type 0, 'cidr', 'inet'
|
269
280
|
|
281
|
+
register_type 0, 'record', PG::TextEncoder::Record, PG::TextDecoder::Record
|
270
282
|
|
271
283
|
|
272
284
|
register_type 1, 'int2', PG::BinaryEncoder::Int2, PG::BinaryDecoder::Integer
|
data/lib/pg/connection.rb
CHANGED
@@ -166,7 +166,10 @@ class PG::Connection
|
|
166
166
|
# conn.put_copy_data ['more', 'data', 'to', 'copy']
|
167
167
|
# end
|
168
168
|
#
|
169
|
-
|
169
|
+
# All 4 CopyRow classes can take a type map to specify how the columns are mapped to and from the database format.
|
170
|
+
# For details see the particular CopyRow class description.
|
171
|
+
#
|
172
|
+
# PG::BinaryEncoder::CopyRow can be used to send data in binary format to the server.
|
170
173
|
# In this case copy_data generates the header and trailer data automatically:
|
171
174
|
# enco = PG::BinaryEncoder::CopyRow.new
|
172
175
|
# conn.copy_data "COPY my_table FROM STDIN (FORMAT binary)", enco do
|
@@ -306,6 +309,11 @@ class PG::Connection
|
|
306
309
|
rollback = false
|
307
310
|
exec "BEGIN"
|
308
311
|
yield(self)
|
312
|
+
rescue PG::RollbackTransaction
|
313
|
+
rollback = true
|
314
|
+
cancel if transaction_status == PG::PQTRANS_ACTIVE
|
315
|
+
block
|
316
|
+
exec "ROLLBACK"
|
309
317
|
rescue Exception
|
310
318
|
rollback = true
|
311
319
|
cancel if transaction_status == PG::PQTRANS_ACTIVE
|
@@ -493,7 +501,7 @@ class PG::Connection
|
|
493
501
|
# See also #copy_data.
|
494
502
|
#
|
495
503
|
def put_copy_data(buffer, encoder=nil)
|
496
|
-
# sync_put_copy_data does a non-blocking
|
504
|
+
# sync_put_copy_data does a non-blocking attempt to flush data.
|
497
505
|
until res=sync_put_copy_data(buffer, encoder)
|
498
506
|
# It didn't flush immediately and allocation of more buffering memory failed.
|
499
507
|
# Wait for all data sent by doing a blocking flush.
|
@@ -565,7 +573,14 @@ class PG::Connection
|
|
565
573
|
# Resets the backend connection. This method closes the
|
566
574
|
# backend connection and tries to re-connect.
|
567
575
|
def reset
|
568
|
-
|
576
|
+
# Use connection options from PG::Connection.new to reconnect with the same options but with renewed DNS resolution.
|
577
|
+
# Use conninfo_hash as a fallback when connect_start was used to create the connection object.
|
578
|
+
iopts = @iopts_for_reset || conninfo_hash.compact
|
579
|
+
if iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
|
580
|
+
iopts = self.class.send(:resolve_hosts, iopts)
|
581
|
+
end
|
582
|
+
conninfo = self.class.parse_connect_args( iopts );
|
583
|
+
reset_start2(conninfo)
|
569
584
|
async_connect_or_reset(:reset_poll)
|
570
585
|
self
|
571
586
|
end
|
@@ -773,46 +788,51 @@ class PG::Connection
|
|
773
788
|
alias setdb new
|
774
789
|
alias setdblogin new
|
775
790
|
|
791
|
+
# Resolve DNS in Ruby to avoid blocking state while connecting.
|
792
|
+
# Multiple comma-separated values are generated, if the hostname resolves to both IPv4 and IPv6 addresses.
|
793
|
+
# This requires PostgreSQL-10+, so no DNS resolving is done on earlier versions.
|
794
|
+
private def resolve_hosts(iopts)
|
795
|
+
ihosts = iopts[:host].split(",", -1)
|
796
|
+
iports = iopts[:port].split(",", -1)
|
797
|
+
iports = [nil] if iports.size == 0
|
798
|
+
iports = iports * ihosts.size if iports.size == 1
|
799
|
+
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
|
800
|
+
|
801
|
+
dests = ihosts.each_with_index.flat_map do |mhost, idx|
|
802
|
+
unless host_is_named_pipe?(mhost)
|
803
|
+
if Fiber.respond_to?(:scheduler) &&
|
804
|
+
Fiber.scheduler &&
|
805
|
+
RUBY_VERSION < '3.1.'
|
806
|
+
|
807
|
+
# Use a second thread to avoid blocking of the scheduler.
|
808
|
+
# `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
|
809
|
+
hostaddrs = Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
|
810
|
+
else
|
811
|
+
hostaddrs = Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
|
812
|
+
end
|
813
|
+
else
|
814
|
+
# No hostname to resolve (UnixSocket)
|
815
|
+
hostaddrs = [nil]
|
816
|
+
end
|
817
|
+
hostaddrs.map { |hostaddr| [hostaddr, mhost, iports[idx]] }
|
818
|
+
end
|
819
|
+
iopts.merge(
|
820
|
+
hostaddr: dests.map{|d| d[0] }.join(","),
|
821
|
+
host: dests.map{|d| d[1] }.join(","),
|
822
|
+
port: dests.map{|d| d[2] }.join(","))
|
823
|
+
end
|
824
|
+
|
776
825
|
private def connect_to_hosts(*args)
|
777
826
|
option_string = parse_connect_args(*args)
|
778
827
|
iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
|
779
828
|
iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts)
|
780
829
|
|
830
|
+
iopts_for_reset = iopts
|
781
831
|
if iopts[:hostaddr]
|
782
832
|
# hostaddr is provided -> no need to resolve hostnames
|
783
833
|
|
784
834
|
elsif iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
|
785
|
-
|
786
|
-
# Multiple comma-separated values are generated, if the hostname resolves to both IPv4 and IPv6 addresses.
|
787
|
-
# This requires PostgreSQL-10+, so no DNS resolving is done on earlier versions.
|
788
|
-
ihosts = iopts[:host].split(",", -1)
|
789
|
-
iports = iopts[:port].split(",", -1)
|
790
|
-
iports = [nil] if iports.size == 0
|
791
|
-
iports = iports * ihosts.size if iports.size == 1
|
792
|
-
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
|
793
|
-
|
794
|
-
dests = ihosts.each_with_index.flat_map do |mhost, idx|
|
795
|
-
unless host_is_named_pipe?(mhost)
|
796
|
-
if Fiber.respond_to?(:scheduler) &&
|
797
|
-
Fiber.scheduler &&
|
798
|
-
RUBY_VERSION < '3.1.'
|
799
|
-
|
800
|
-
# Use a second thread to avoid blocking of the scheduler.
|
801
|
-
# `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
|
802
|
-
hostaddrs = Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
|
803
|
-
else
|
804
|
-
hostaddrs = Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
|
805
|
-
end
|
806
|
-
else
|
807
|
-
# No hostname to resolve (UnixSocket)
|
808
|
-
hostaddrs = [nil]
|
809
|
-
end
|
810
|
-
hostaddrs.map { |hostaddr| [hostaddr, mhost, iports[idx]] }
|
811
|
-
end
|
812
|
-
iopts.merge!(
|
813
|
-
hostaddr: dests.map{|d| d[0] }.join(","),
|
814
|
-
host: dests.map{|d| d[1] }.join(","),
|
815
|
-
port: dests.map{|d| d[2] }.join(","))
|
835
|
+
iopts = resolve_hosts(iopts)
|
816
836
|
else
|
817
837
|
# No host given
|
818
838
|
end
|
@@ -821,6 +841,8 @@ class PG::Connection
|
|
821
841
|
|
822
842
|
raise PG::ConnectionBad, conn.error_message if conn.status == PG::CONNECTION_BAD
|
823
843
|
|
844
|
+
# save the connection options for conn.reset
|
845
|
+
conn.instance_variable_set(:@iopts_for_reset, iopts_for_reset)
|
824
846
|
conn.send(:async_connect_or_reset, :connect_poll)
|
825
847
|
conn
|
826
848
|
end
|
data/lib/pg/exceptions.rb
CHANGED
@@ -21,5 +21,11 @@ module PG
|
|
21
21
|
class NotInBlockingMode < PG::Error
|
22
22
|
end
|
23
23
|
|
24
|
+
# PG::Connection#transaction uses this exception to distinguish a deliberate rollback from other exceptional situations.
|
25
|
+
# Normally, raising an exception will cause the .transaction method to rollback the database transaction and pass on the exception.
|
26
|
+
# But if you raise an PG::RollbackTransaction exception, then the database transaction will be rolled back, without passing on the exception.
|
27
|
+
class RollbackTransaction < StandardError
|
28
|
+
end
|
29
|
+
|
24
30
|
end # module PG
|
25
31
|
|
data/lib/pg/text_decoder/date.rb
CHANGED
@@ -5,6 +5,9 @@ require 'date'
|
|
5
5
|
|
6
6
|
module PG
|
7
7
|
module TextDecoder
|
8
|
+
# This is a decoder class for conversion of PostgreSQL date type to Ruby Date values.
|
9
|
+
#
|
10
|
+
# As soon as this class is used, it requires the ruby standard library 'date'.
|
8
11
|
class Date < SimpleDecoder
|
9
12
|
def decode(string, tuple=nil, field=nil)
|
10
13
|
if string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/
|
data/lib/pg/text_decoder/json.rb
CHANGED
@@ -5,6 +5,9 @@ require 'json'
|
|
5
5
|
|
6
6
|
module PG
|
7
7
|
module TextDecoder
|
8
|
+
# This is a decoder class for conversion of PostgreSQL JSON/JSONB type to Ruby Hash, Array, String, Numeric, nil values.
|
9
|
+
#
|
10
|
+
# As soon as this class is used, it requires the ruby standard library 'json'.
|
8
11
|
class JSON < SimpleDecoder
|
9
12
|
def decode(string, tuple=nil, field=nil)
|
10
13
|
::JSON.parse(string, quirks_mode: true)
|
data/lib/pg/text_encoder/date.rb
CHANGED
data/lib/pg/text_encoder/inet.rb
CHANGED
@@ -5,6 +5,9 @@ require 'ipaddr'
|
|
5
5
|
|
6
6
|
module PG
|
7
7
|
module TextEncoder
|
8
|
+
# This is a encoder class for conversion of Ruby IPAddr values to PostgreSQL inet type.
|
9
|
+
#
|
10
|
+
# As soon as this class is used, it requires the ruby standard library 'ipaddr'.
|
8
11
|
class Inet < SimpleEncoder
|
9
12
|
def encode(value)
|
10
13
|
case value
|
data/lib/pg/text_encoder/json.rb
CHANGED
@@ -5,6 +5,9 @@ require 'json'
|
|
5
5
|
|
6
6
|
module PG
|
7
7
|
module TextEncoder
|
8
|
+
# This is a encoder class for conversion of Ruby Hash, Array, String, Numeric, nil values to PostgreSQL JSON/JSONB type.
|
9
|
+
#
|
10
|
+
# As soon as this class is used, it requires the ruby standard library 'json'.
|
8
11
|
class JSON < SimpleEncoder
|
9
12
|
def encode(value)
|
10
13
|
::JSON.generate(value, quirks_mode: true)
|
data/lib/pg/version.rb
CHANGED
data/lib/pg.rb
CHANGED
@@ -126,4 +126,14 @@ module PG
|
|
126
126
|
Warning.extend(TruffleFixWarn)
|
127
127
|
end
|
128
128
|
|
129
|
+
# Ruby-3.4+ prints a warning, if bigdecimal is required but not in the Gemfile.
|
130
|
+
# But it's a false positive, since we enable bigdecimal depending features only if it's available.
|
131
|
+
# And most people don't need these features.
|
132
|
+
def self.require_bigdecimal_without_warning
|
133
|
+
oldverb, $VERBOSE = $VERBOSE, nil
|
134
|
+
require "bigdecimal"
|
135
|
+
ensure
|
136
|
+
$VERBOSE = oldverb
|
137
|
+
end
|
138
|
+
|
129
139
|
end # module PG
|
data/pg.gemspec
CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
# Specify which files should be added to the gem when it is released.
|
24
24
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
25
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
26
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
26
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features|translation)/}) }
|
27
27
|
end
|
28
28
|
spec.extensions = ["ext/extconf.rb"]
|
29
29
|
spec.require_paths = ["lib"]
|
data.tar.gz.sig
CHANGED
Binary file
|