pg 1.5.3 → 1.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/Gemfile +6 -0
  4. data/History.md +60 -4
  5. data/README.ja.md +54 -30
  6. data/Rakefile +4 -1
  7. data/Rakefile.cross +13 -8
  8. data/certs/kanis@comcard.de.pem +20 -0
  9. data/certs/larskanis-2024.pem +24 -0
  10. data/ext/errorcodes.def +4 -5
  11. data/ext/errorcodes.txt +2 -5
  12. data/ext/extconf.rb +7 -0
  13. data/ext/pg.c +2 -2
  14. data/ext/pg.h +0 -1
  15. data/ext/pg_binary_decoder.c +2 -0
  16. data/ext/pg_binary_encoder.c +1 -1
  17. data/ext/pg_connection.c +60 -21
  18. data/ext/pg_copy_coder.c +17 -13
  19. data/ext/pg_record_coder.c +6 -6
  20. data/ext/pg_result.c +4 -4
  21. data/ext/pg_text_decoder.c +4 -1
  22. data/ext/pg_text_encoder.c +17 -11
  23. data/lib/pg/basic_type_map_for_queries.rb +8 -4
  24. data/lib/pg/basic_type_registry.rb +14 -2
  25. data/lib/pg/connection.rb +58 -38
  26. data/lib/pg/exceptions.rb +6 -0
  27. data/lib/pg/text_decoder/date.rb +3 -0
  28. data/lib/pg/text_decoder/json.rb +3 -0
  29. data/lib/pg/text_encoder/date.rb +1 -0
  30. data/lib/pg/text_encoder/inet.rb +3 -0
  31. data/lib/pg/text_encoder/json.rb +3 -0
  32. data/lib/pg/version.rb +1 -1
  33. data/lib/pg.rb +10 -0
  34. data/pg.gemspec +3 -1
  35. data.tar.gz.sig +0 -0
  36. metadata +17 -34
  37. metadata.gz.sig +0 -0
  38. data/.appveyor.yml +0 -42
  39. data/.gems +0 -6
  40. data/.gemtest +0 -0
  41. data/.github/workflows/binary-gems.yml +0 -117
  42. data/.github/workflows/source-gem.yml +0 -137
  43. data/.gitignore +0 -22
  44. data/.hgsigs +0 -34
  45. data/.hgtags +0 -41
  46. data/.irbrc +0 -23
  47. data/.pryrc +0 -23
  48. data/.tm_properties +0 -21
  49. data/.travis.yml +0 -49
  50. data/translation/.po4a-version +0 -7
  51. data/translation/po/all.pot +0 -910
  52. data/translation/po/ja.po +0 -1047
  53. data/translation/po4a.cfg +0 -12
data/ext/pg_connection.c CHANGED
@@ -33,8 +33,8 @@ static VALUE pgconn_async_flush(VALUE self);
33
33
  #ifdef __GNUC__
34
34
  __attribute__((format(printf, 3, 4)))
35
35
  #endif
36
- static void
37
- pg_raise_conn_error( VALUE klass, VALUE self, const char *format, ...)
36
+ NORETURN( static void
37
+ pg_raise_conn_error( VALUE klass, VALUE self, const char *format, ...))
38
38
  {
39
39
  VALUE msg, error;
40
40
  va_list ap;
@@ -264,6 +264,7 @@ pgconn_s_allocate( VALUE klass )
264
264
  RB_OBJ_WRITE(self, &this->decoder_for_get_copy_data, Qnil);
265
265
  RB_OBJ_WRITE(self, &this->trace_stream, Qnil);
266
266
  rb_ivar_set(self, rb_intern("@calls_to_put_copy_data"), INT2FIX(0));
267
+ rb_ivar_set(self, rb_intern("@iopts_for_reset"), Qnil);
267
268
 
268
269
  return self;
269
270
  }
@@ -515,9 +516,9 @@ static VALUE
515
516
  pgconn_connect_poll(VALUE self)
516
517
  {
517
518
  PostgresPollingStatusType status;
518
- status = gvl_PQconnectPoll(pg_get_pgconn(self));
519
519
 
520
520
  pgconn_close_socket_io(self);
521
+ status = gvl_PQconnectPoll(pg_get_pgconn(self));
521
522
 
522
523
  return INT2FIX((int)status);
523
524
  }
@@ -563,6 +564,27 @@ pgconn_sync_reset( VALUE self )
563
564
  return self;
564
565
  }
565
566
 
567
+ static VALUE
568
+ pgconn_reset_start2( VALUE self, VALUE conninfo )
569
+ {
570
+ t_pg_connection *this = pg_get_connection( self );
571
+
572
+ /* Close old connection */
573
+ pgconn_close_socket_io( self );
574
+ PQfinish( this->pgconn );
575
+
576
+ /* Start new connection */
577
+ this->pgconn = gvl_PQconnectStart( StringValueCStr(conninfo) );
578
+
579
+ if( this->pgconn == NULL )
580
+ rb_raise(rb_ePGerror, "PQconnectStart() unable to allocate PGconn structure");
581
+
582
+ if ( PQstatus(this->pgconn) == CONNECTION_BAD )
583
+ pg_raise_conn_error( rb_eConnectionBad, self, "%s", PQerrorMessage(this->pgconn));
584
+
585
+ return Qnil;
586
+ }
587
+
566
588
  /*
567
589
  * call-seq:
568
590
  * conn.reset_start() -> nil
@@ -594,9 +616,9 @@ static VALUE
594
616
  pgconn_reset_poll(VALUE self)
595
617
  {
596
618
  PostgresPollingStatusType status;
597
- status = gvl_PQresetPoll(pg_get_pgconn(self));
598
619
 
599
620
  pgconn_close_socket_io(self);
621
+ status = gvl_PQresetPoll(pg_get_pgconn(self));
600
622
 
601
623
  return INT2FIX((int)status);
602
624
  }
@@ -2245,6 +2267,17 @@ pgconn_notifies(VALUE self)
2245
2267
  return hash;
2246
2268
  }
2247
2269
 
2270
+ #ifndef HAVE_RB_IO_DESCRIPTOR
2271
+ static int
2272
+ rb_io_descriptor(VALUE io)
2273
+ {
2274
+ Check_Type(io, T_FILE);
2275
+ rb_io_t *fptr = RFILE(io)->fptr;
2276
+ rb_io_check_closed(fptr);
2277
+ return fptr->fd;
2278
+ }
2279
+ #endif
2280
+
2248
2281
  #if defined(_WIN32)
2249
2282
 
2250
2283
  /* We use a specialized implementation of rb_io_wait() on Windows.
@@ -2265,7 +2298,6 @@ int rb_w32_wait_events( HANDLE *events, int num, DWORD timeout );
2265
2298
 
2266
2299
  static VALUE
2267
2300
  pg_rb_thread_io_wait(VALUE io, VALUE events, VALUE timeout) {
2268
- rb_io_t *fptr;
2269
2301
  struct timeval ptimeout;
2270
2302
 
2271
2303
  struct timeval aborttime={0,0}, currtime, waittime;
@@ -2276,7 +2308,6 @@ pg_rb_thread_io_wait(VALUE io, VALUE events, VALUE timeout) {
2276
2308
  long w32_events = 0;
2277
2309
  DWORD wait_ret;
2278
2310
 
2279
- GetOpenFile((io), fptr);
2280
2311
  if( !NIL_P(timeout) ){
2281
2312
  ptimeout.tv_sec = (time_t)(NUM2DBL(timeout));
2282
2313
  ptimeout.tv_usec = (time_t)((NUM2DBL(timeout) - (double)ptimeout.tv_sec) * 1e6);
@@ -2290,7 +2321,7 @@ pg_rb_thread_io_wait(VALUE io, VALUE events, VALUE timeout) {
2290
2321
  if(rb_events & PG_RUBY_IO_PRIORITY) w32_events |= FD_OOB;
2291
2322
 
2292
2323
  for(;;) {
2293
- if ( WSAEventSelect(_get_osfhandle(fptr->fd), hEvent, w32_events) == SOCKET_ERROR ) {
2324
+ if ( WSAEventSelect(_get_osfhandle(rb_io_descriptor(io)), hEvent, w32_events) == SOCKET_ERROR ) {
2294
2325
  WSACloseEvent( hEvent );
2295
2326
  rb_raise( rb_eConnectionBad, "WSAEventSelect socket error: %d", WSAGetLastError() );
2296
2327
  }
@@ -2333,7 +2364,7 @@ static VALUE
2333
2364
  pg_rb_io_wait(VALUE io, VALUE events, VALUE timeout) {
2334
2365
  #if defined(HAVE_RUBY_FIBER_SCHEDULER_H)
2335
2366
  /* We don't support Fiber.scheduler on Windows ruby-3.0 because there is no fast way to check whether a scheduler is active.
2336
- * Fortunatelly ruby-3.1 offers a C-API for it.
2367
+ * Fortunately ruby-3.1 offers a C-API for it.
2337
2368
  */
2338
2369
  VALUE scheduler = rb_fiber_scheduler_current();
2339
2370
 
@@ -2363,16 +2394,14 @@ typedef enum {
2363
2394
 
2364
2395
  static VALUE
2365
2396
  pg_rb_io_wait(VALUE io, VALUE events, VALUE timeout) {
2366
- rb_io_t *fptr;
2367
2397
  struct timeval waittime;
2368
2398
  int res;
2369
2399
 
2370
- GetOpenFile((io), fptr);
2371
2400
  if( !NIL_P(timeout) ){
2372
2401
  waittime.tv_sec = (time_t)(NUM2DBL(timeout));
2373
2402
  waittime.tv_usec = (time_t)((NUM2DBL(timeout) - (double)waittime.tv_sec) * 1e6);
2374
2403
  }
2375
- res = rb_wait_for_single_fd(fptr->fd, NUM2UINT(events), NIL_P(timeout) ? NULL : &waittime);
2404
+ res = rb_wait_for_single_fd(rb_io_descriptor(io), NUM2UINT(events), NIL_P(timeout) ? NULL : &waittime);
2376
2405
 
2377
2406
  return UINT2NUM(res);
2378
2407
  }
@@ -3107,7 +3136,9 @@ pgconn_async_get_last_result(VALUE self)
3107
3136
  for(;;) {
3108
3137
  int status;
3109
3138
 
3110
- /* wait for input (without blocking) before reading each result */
3139
+ /* Wait for input before reading each result.
3140
+ * That way we support the ruby-3.x IO scheduler and don't block other ruby threads.
3141
+ */
3111
3142
  wait_socket_readable(self, NULL, get_result_readable);
3112
3143
 
3113
3144
  cur = gvl_PQgetResult(conn);
@@ -3141,7 +3172,7 @@ pgconn_async_get_last_result(VALUE self)
3141
3172
  * Returns:
3142
3173
  * * +nil+ when the connection is already idle
3143
3174
  * * +true+ when some results have been discarded
3144
- * * +false+ when a failure occured and the connection was closed
3175
+ * * +false+ when a failure occurred and the connection was closed
3145
3176
  *
3146
3177
  */
3147
3178
  static VALUE
@@ -4171,16 +4202,23 @@ static VALUE
4171
4202
  pgconn_set_default_encoding( VALUE self )
4172
4203
  {
4173
4204
  PGconn *conn = pg_get_pgconn( self );
4174
- rb_encoding *enc;
4175
- const char *encname;
4205
+ rb_encoding *rb_enc;
4176
4206
 
4177
4207
  rb_check_frozen(self);
4178
- if (( enc = rb_default_internal_encoding() )) {
4179
- encname = pg_get_rb_encoding_as_pg_encoding( enc );
4180
- if ( pgconn_set_client_encoding_async(self, rb_str_new_cstr(encname)) != 0 )
4181
- rb_warning( "Failed to set the default_internal encoding to %s: '%s'",
4182
- encname, PQerrorMessage(conn) );
4183
- return rb_enc_from_encoding( enc );
4208
+ if (( rb_enc = rb_default_internal_encoding() )) {
4209
+ rb_encoding * conn_encoding = pg_conn_enc_get( conn );
4210
+
4211
+ /* Don't set the server encoding, if it's unnecessary.
4212
+ * This is important for connection proxies, who disallow configuration settings.
4213
+ */
4214
+ if ( conn_encoding != rb_enc ) {
4215
+ const char *encname = pg_get_rb_encoding_as_pg_encoding( rb_enc );
4216
+ if ( pgconn_set_client_encoding_async(self, rb_str_new_cstr(encname)) != 0 )
4217
+ rb_warning( "Failed to set the default_internal encoding to %s: '%s'",
4218
+ encname, PQerrorMessage(conn) );
4219
+ }
4220
+ pgconn_set_internal_encoding_index( self );
4221
+ return rb_enc_from_encoding( rb_enc );
4184
4222
  } else {
4185
4223
  pgconn_set_internal_encoding_index( self );
4186
4224
  return Qnil;
@@ -4461,6 +4499,7 @@ init_pg_connection(void)
4461
4499
  rb_define_method(rb_cPGconn, "finished?", pgconn_finished_p, 0);
4462
4500
  rb_define_method(rb_cPGconn, "sync_reset", pgconn_sync_reset, 0);
4463
4501
  rb_define_method(rb_cPGconn, "reset_start", pgconn_reset_start, 0);
4502
+ rb_define_private_method(rb_cPGconn, "reset_start2", pgconn_reset_start2, 1);
4464
4503
  rb_define_method(rb_cPGconn, "reset_poll", pgconn_reset_poll, 0);
4465
4504
  rb_define_alias(rb_cPGconn, "close", "finish");
4466
4505
 
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 backslashs;
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(backslashs = 0; ptr1 != ptr2; ptr1++) {
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
- backslashs++;
293
+ backslashes++;
293
294
  }
294
295
  }
295
296
 
296
297
  ptr1 = current_out + strlen;
297
- ptr2 = current_out + strlen + backslashs;
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 backslashs are placed. */
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 indicationg a NULL value */
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 + 11 <= line_end_ptr && memcmp(cur_ptr, BinarySignature, 11) == 0){
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 + 4 > line_end_ptr) goto length_error;
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 + 4 > line_end_ptr) goto length_error;
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 + ext_bytes > line_end_ptr) goto length_error;
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 + 2 > line_end_ptr) goto length_error;
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 + 4 > line_end_ptr) goto length_error;
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 + input_len > line_end_ptr) goto length_error;
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 );
@@ -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 backslashs;
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(backslashs = 0; ptr1 != ptr2; ptr1++) {
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
- backslashs++;
255
+ backslashes++;
256
256
  }
257
257
  }
258
258
 
259
259
  ptr1 = current_out + strlen;
260
- ptr2 = current_out + strlen + backslashs;
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 backslashs are placed. */
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 very convenient to use the PG::BasicTypeRegistry, which is based on database OIDs.
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
@@ -392,8 +392,8 @@ pg_result_clear(VALUE self)
392
392
  * It also denies setting a type_map or field_name_type.
393
393
  *
394
394
  */
395
- VALUE
396
- static pg_result_freeze(VALUE self)
395
+ static VALUE
396
+ pg_result_freeze(VALUE self)
397
397
  {
398
398
  t_pg_result *this = pgresult_get_this(self);
399
399
 
@@ -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 nonexistant_table" )
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 \"nonexistant_table\" does not exist", nil, nil,
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
@@ -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
- rb_require("bigdecimal");
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)
@@ -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
- rb_require("bigdecimal");
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 backslashs = 0;
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
- backslashs++;
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 + backslashs + 2;
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 backslashs are placed. */
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 + backslashs + 2;
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 backslashs = 0;
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
- backslashs++;
706
+ backslashes++;
701
707
  }
702
708
  }
703
709
 
704
710
  ptr1 = p_in + strlen;
705
- ptr2 = p_out + strlen + backslashs + 2;
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 backslashs are placed. */
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 + backslashs + 2;
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
- # The key of these hashs maps to the `typname` column from the table pg_type.
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
- register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric
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