pg 1.4.1 → 1.5.6

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 (72) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.appveyor.yml +15 -9
  4. data/.github/workflows/binary-gems.yml +45 -14
  5. data/.github/workflows/source-gem.yml +35 -23
  6. data/.gitignore +11 -2
  7. data/.travis.yml +2 -2
  8. data/Gemfile +3 -0
  9. data/{History.rdoc → History.md} +285 -140
  10. data/README.ja.md +300 -0
  11. data/README.md +286 -0
  12. data/Rakefile +18 -6
  13. data/Rakefile.cross +8 -11
  14. data/certs/kanis@comcard.de.pem +20 -0
  15. data/certs/larskanis-2023.pem +24 -0
  16. data/certs/larskanis-2024.pem +24 -0
  17. data/ext/errorcodes.def +4 -0
  18. data/ext/errorcodes.txt +2 -1
  19. data/ext/extconf.rb +4 -0
  20. data/ext/pg.c +15 -55
  21. data/ext/pg.h +11 -6
  22. data/ext/pg_binary_decoder.c +80 -1
  23. data/ext/pg_binary_encoder.c +225 -1
  24. data/ext/pg_coder.c +17 -8
  25. data/ext/pg_connection.c +201 -73
  26. data/ext/pg_copy_coder.c +307 -18
  27. data/ext/pg_errors.c +1 -1
  28. data/ext/pg_record_coder.c +6 -5
  29. data/ext/pg_result.c +102 -26
  30. data/ext/pg_text_decoder.c +28 -10
  31. data/ext/pg_text_encoder.c +23 -10
  32. data/ext/pg_tuple.c +35 -32
  33. data/ext/pg_type_map.c +4 -3
  34. data/ext/pg_type_map_all_strings.c +3 -3
  35. data/ext/pg_type_map_by_class.c +6 -4
  36. data/ext/pg_type_map_by_column.c +9 -5
  37. data/ext/pg_type_map_by_mri_type.c +1 -1
  38. data/ext/pg_type_map_by_oid.c +8 -5
  39. data/ext/pg_type_map_in_ruby.c +6 -3
  40. data/lib/pg/basic_type_map_based_on_result.rb +21 -1
  41. data/lib/pg/basic_type_map_for_queries.rb +19 -10
  42. data/lib/pg/basic_type_map_for_results.rb +26 -3
  43. data/lib/pg/basic_type_registry.rb +35 -33
  44. data/lib/pg/binary_decoder/date.rb +9 -0
  45. data/lib/pg/binary_decoder/timestamp.rb +26 -0
  46. data/lib/pg/binary_encoder/timestamp.rb +20 -0
  47. data/lib/pg/coder.rb +15 -13
  48. data/lib/pg/connection.rb +186 -104
  49. data/lib/pg/exceptions.rb +7 -0
  50. data/lib/pg/text_decoder/date.rb +18 -0
  51. data/lib/pg/text_decoder/inet.rb +9 -0
  52. data/lib/pg/text_decoder/json.rb +14 -0
  53. data/lib/pg/text_decoder/numeric.rb +9 -0
  54. data/lib/pg/text_decoder/timestamp.rb +30 -0
  55. data/lib/pg/text_encoder/date.rb +12 -0
  56. data/lib/pg/text_encoder/inet.rb +28 -0
  57. data/lib/pg/text_encoder/json.rb +14 -0
  58. data/lib/pg/text_encoder/numeric.rb +9 -0
  59. data/lib/pg/text_encoder/timestamp.rb +24 -0
  60. data/lib/pg/version.rb +1 -1
  61. data/lib/pg.rb +55 -15
  62. data/pg.gemspec +5 -3
  63. data/rakelib/task_extension.rb +1 -1
  64. data.tar.gz.sig +0 -0
  65. metadata +96 -32
  66. metadata.gz.sig +0 -0
  67. data/README.ja.rdoc +0 -13
  68. data/README.rdoc +0 -214
  69. data/lib/pg/binary_decoder.rb +0 -23
  70. data/lib/pg/constants.rb +0 -12
  71. data/lib/pg/text_decoder.rb +0 -46
  72. data/lib/pg/text_encoder.rb +0 -59
data/ext/pg_type_map.c CHANGED
@@ -37,7 +37,7 @@ const rb_data_type_t pg_typemap_type = {
37
37
  },
38
38
  0,
39
39
  0,
40
- RUBY_TYPED_FREE_IMMEDIATELY,
40
+ RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | PG_RUBY_TYPED_FROZEN_SHAREABLE,
41
41
  };
42
42
 
43
43
  VALUE rb_cTypeMap;
@@ -132,9 +132,10 @@ pg_typemap_default_type_map_set(VALUE self, VALUE typemap)
132
132
  t_typemap *tm;
133
133
  UNUSED(tm);
134
134
 
135
+ rb_check_frozen(self);
135
136
  /* Check type of method param */
136
137
  TypedData_Get_Struct(typemap, t_typemap, &pg_typemap_type, tm);
137
- this->default_typemap = typemap;
138
+ RB_OBJ_WRITE(self, &this->default_typemap, typemap);
138
139
 
139
140
  return typemap;
140
141
  }
@@ -176,7 +177,7 @@ pg_typemap_with_default_type_map(VALUE self, VALUE typemap)
176
177
  }
177
178
 
178
179
  void
179
- init_pg_type_map()
180
+ init_pg_type_map(void)
180
181
  {
181
182
  s_id_fit_to_query = rb_intern("fit_to_query");
182
183
  s_id_fit_to_result = rb_intern("fit_to_result");
@@ -18,7 +18,7 @@ static const rb_data_type_t pg_tmas_type = {
18
18
  },
19
19
  &pg_typemap_type,
20
20
  0,
21
- RUBY_TYPED_FREE_IMMEDIATELY,
21
+ RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | PG_RUBY_TYPED_FROZEN_SHAREABLE,
22
22
  };
23
23
 
24
24
  VALUE rb_cTypeMapAllStrings;
@@ -105,7 +105,7 @@ pg_tmas_s_allocate( VALUE klass )
105
105
 
106
106
 
107
107
  void
108
- init_pg_type_map_all_strings()
108
+ init_pg_type_map_all_strings(void)
109
109
  {
110
110
  /*
111
111
  * Document-class: PG::TypeMapAllStrings < PG::TypeMap
@@ -125,6 +125,6 @@ init_pg_type_map_all_strings()
125
125
  rb_cTypeMapAllStrings = rb_define_class_under( rb_mPG, "TypeMapAllStrings", rb_cTypeMap );
126
126
  rb_define_alloc_func( rb_cTypeMapAllStrings, pg_tmas_s_allocate );
127
127
 
128
- pg_typemap_all_strings = rb_funcall( rb_cTypeMapAllStrings, rb_intern("new"), 0 );
128
+ pg_typemap_all_strings = rb_obj_freeze( rb_funcall( rb_cTypeMapAllStrings, rb_intern("new"), 0 ));
129
129
  rb_gc_register_address( &pg_typemap_all_strings );
130
130
  }
@@ -157,7 +157,7 @@ static const rb_data_type_t pg_tmbk_type = {
157
157
  },
158
158
  &pg_typemap_type,
159
159
  0,
160
- RUBY_TYPED_FREE_IMMEDIATELY,
160
+ RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | PG_RUBY_TYPED_FROZEN_SHAREABLE,
161
161
  };
162
162
 
163
163
  static VALUE
@@ -173,12 +173,12 @@ pg_tmbk_s_allocate( VALUE klass )
173
173
  this->typemap.funcs.typecast_result_value = pg_typemap_result_value;
174
174
  this->typemap.funcs.typecast_query_param = pg_tmbk_typecast_query_param;
175
175
  this->typemap.funcs.typecast_copy_get = pg_typemap_typecast_copy_get;
176
- this->typemap.default_typemap = pg_typemap_all_strings;
176
+ RB_OBJ_WRITE(self, &this->typemap.default_typemap, pg_typemap_all_strings);
177
177
 
178
178
  /* We need to store self in the this-struct, because pg_tmbk_typecast_query_param(),
179
179
  * is called with the this-pointer only. */
180
180
  this->self = self;
181
- this->klass_to_coder = rb_hash_new();
181
+ RB_OBJ_WRITE(self, &this->klass_to_coder, rb_hash_new());
182
182
 
183
183
  /* The cache is properly initialized by TypedData_Make_Struct(). */
184
184
 
@@ -205,6 +205,8 @@ pg_tmbk_aset( VALUE self, VALUE klass, VALUE coder )
205
205
  {
206
206
  t_tmbk *this = RTYPEDDATA_DATA( self );
207
207
 
208
+ rb_check_frozen(self);
209
+
208
210
  if(NIL_P(coder)){
209
211
  rb_hash_delete( this->klass_to_coder, klass );
210
212
  }else{
@@ -247,7 +249,7 @@ pg_tmbk_coders( VALUE self )
247
249
  }
248
250
 
249
251
  void
250
- init_pg_type_map_by_class()
252
+ init_pg_type_map_by_class(void)
251
253
  {
252
254
  /*
253
255
  * Document-class: PG::TypeMapByClass < PG::TypeMap
@@ -232,7 +232,7 @@ static const rb_data_type_t pg_tmbc_type = {
232
232
  },
233
233
  &pg_typemap_type,
234
234
  0,
235
- RUBY_TYPED_FREE_IMMEDIATELY,
235
+ RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | PG_RUBY_TYPED_FROZEN_SHAREABLE,
236
236
  };
237
237
 
238
238
  static VALUE
@@ -243,7 +243,7 @@ pg_tmbc_s_allocate( VALUE klass )
243
243
  }
244
244
 
245
245
  VALUE
246
- pg_tmbc_allocate()
246
+ pg_tmbc_allocate(void)
247
247
  {
248
248
  return pg_tmbc_s_allocate(rb_cTypeMapByColumn);
249
249
  }
@@ -266,13 +266,14 @@ pg_tmbc_init(VALUE self, VALUE conv_ary)
266
266
  t_tmbc *this;
267
267
  int conv_ary_len;
268
268
 
269
+ rb_check_frozen(self);
269
270
  Check_Type(conv_ary, T_ARRAY);
270
271
  conv_ary_len = RARRAY_LENINT(conv_ary);
271
272
  this = xmalloc(sizeof(t_tmbc) + sizeof(struct pg_tmbc_converter) * conv_ary_len);
272
273
  /* Set nfields to 0 at first, so that GC mark function doesn't access uninitialized memory. */
273
274
  this->nfields = 0;
274
275
  this->typemap.funcs = pg_tmbc_funcs;
275
- this->typemap.default_typemap = pg_typemap_all_strings;
276
+ RB_OBJ_WRITE(self, &this->typemap.default_typemap, pg_typemap_all_strings);
276
277
  RTYPEDDATA_DATA(self) = this;
277
278
 
278
279
  for(i=0; i<conv_ary_len; i++)
@@ -283,8 +284,11 @@ pg_tmbc_init(VALUE self, VALUE conv_ary)
283
284
  /* no type cast */
284
285
  this->convs[i].cconv = NULL;
285
286
  } else {
287
+ t_pg_coder *p_coder;
286
288
  /* Check argument type and store the coder pointer */
287
- TypedData_Get_Struct(obj, t_pg_coder, &pg_coder_type, this->convs[i].cconv);
289
+ TypedData_Get_Struct(obj, t_pg_coder, &pg_coder_type, p_coder);
290
+ RB_OBJ_WRITTEN(self, Qnil, p_coder->coder_obj);
291
+ this->convs[i].cconv = p_coder;
288
292
  }
289
293
  }
290
294
 
@@ -320,7 +324,7 @@ pg_tmbc_coders(VALUE self)
320
324
  }
321
325
 
322
326
  void
323
- init_pg_type_map_by_column()
327
+ init_pg_type_map_by_column(void)
324
328
  {
325
329
  s_id_decode = rb_intern("decode");
326
330
  s_id_encode = rb_intern("encode");
@@ -286,7 +286,7 @@ pg_tmbmt_coders( VALUE self )
286
286
  }
287
287
 
288
288
  void
289
- init_pg_type_map_by_mri_type()
289
+ init_pg_type_map_by_mri_type(void)
290
290
  {
291
291
  /*
292
292
  * Document-class: PG::TypeMapByMriType < PG::TypeMap
@@ -194,7 +194,7 @@ static const rb_data_type_t pg_tmbo_type = {
194
194
  },
195
195
  &pg_typemap_type,
196
196
  0,
197
- RUBY_TYPED_FREE_IMMEDIATELY,
197
+ RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | PG_RUBY_TYPED_FROZEN_SHAREABLE,
198
198
  };
199
199
 
200
200
  static VALUE
@@ -212,11 +212,11 @@ pg_tmbo_s_allocate( VALUE klass )
212
212
  this->typemap.funcs.typecast_result_value = pg_tmbo_result_value;
213
213
  this->typemap.funcs.typecast_query_param = pg_typemap_typecast_query_param;
214
214
  this->typemap.funcs.typecast_copy_get = pg_typemap_typecast_copy_get;
215
- this->typemap.default_typemap = pg_typemap_all_strings;
215
+ RB_OBJ_WRITE(self, &this->typemap.default_typemap, pg_typemap_all_strings);
216
216
  this->max_rows_for_online_lookup = 10;
217
217
 
218
218
  for( i=0; i<2; i++){
219
- this->format[i].oid_to_coder = rb_hash_new();
219
+ RB_OBJ_WRITE(self, &this->format[i].oid_to_coder, rb_hash_new());
220
220
  }
221
221
 
222
222
  return self;
@@ -242,6 +242,7 @@ pg_tmbo_add_coder( VALUE self, VALUE coder )
242
242
  t_pg_coder *p_coder;
243
243
  struct pg_tmbo_oid_cache_entry *p_ce;
244
244
 
245
+ rb_check_frozen(self);
245
246
  TypedData_Get_Struct(coder, t_pg_coder, &pg_coder_type, p_coder);
246
247
 
247
248
  if( p_coder->format < 0 || p_coder->format > 1 )
@@ -276,6 +277,7 @@ pg_tmbo_rm_coder( VALUE self, VALUE format, VALUE oid )
276
277
  int i_format = NUM2INT(format);
277
278
  struct pg_tmbo_oid_cache_entry *p_ce;
278
279
 
280
+ rb_check_frozen(self);
279
281
  if( i_format < 0 || i_format > 1 )
280
282
  rb_raise(rb_eArgError, "invalid format code %d", i_format);
281
283
 
@@ -318,6 +320,7 @@ static VALUE
318
320
  pg_tmbo_max_rows_for_online_lookup_set( VALUE self, VALUE value )
319
321
  {
320
322
  t_tmbo *this = RTYPEDDATA_DATA( self );
323
+ rb_check_frozen(self);
321
324
  this->max_rows_for_online_lookup = NUM2INT(value);
322
325
  return value;
323
326
  }
@@ -338,7 +341,7 @@ pg_tmbo_max_rows_for_online_lookup_get( VALUE self )
338
341
  * typemap.build_column_map( result )
339
342
  *
340
343
  * This builds a PG::TypeMapByColumn that fits to the given PG::Result object
341
- * based on it's type OIDs.
344
+ * based on it's type OIDs and binary/text format.
342
345
  *
343
346
  */
344
347
  static VALUE
@@ -356,7 +359,7 @@ pg_tmbo_build_column_map( VALUE self, VALUE result )
356
359
 
357
360
 
358
361
  void
359
- init_pg_type_map_by_oid()
362
+ init_pg_type_map_by_oid(void)
360
363
  {
361
364
  s_id_decode = rb_intern("decode");
362
365
 
@@ -44,7 +44,7 @@ static const rb_data_type_t pg_tmir_type = {
44
44
  },
45
45
  &pg_typemap_type,
46
46
  0,
47
- RUBY_TYPED_FREE_IMMEDIATELY,
47
+ RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | PG_RUBY_TYPED_FROZEN_SHAREABLE,
48
48
  };
49
49
 
50
50
  /*
@@ -210,6 +210,9 @@ pg_tmir_typecast_query_param( VALUE self, VALUE param_value, VALUE field )
210
210
  * This method is called, when a type map is used for decoding copy data,
211
211
  * before the value is casted.
212
212
  *
213
+ * Should return the expected number of columns or 0 if the number of columns is unknown.
214
+ * This number is only used for memory pre-allocation.
215
+ *
213
216
  */
214
217
  static VALUE pg_tmir_fit_to_copy_get_dummy( VALUE self ){}
215
218
  #endif
@@ -291,7 +294,7 @@ pg_tmir_s_allocate( VALUE klass )
291
294
  this->typemap.funcs.typecast_result_value = pg_tmir_result_value;
292
295
  this->typemap.funcs.typecast_query_param = pg_tmir_query_param;
293
296
  this->typemap.funcs.typecast_copy_get = pg_tmir_copy_get;
294
- this->typemap.default_typemap = pg_typemap_all_strings;
297
+ RB_OBJ_WRITE(self, &this->typemap.default_typemap, pg_typemap_all_strings);
295
298
  this->self = self;
296
299
 
297
300
  return self;
@@ -299,7 +302,7 @@ pg_tmir_s_allocate( VALUE klass )
299
302
 
300
303
 
301
304
  void
302
- init_pg_type_map_in_ruby()
305
+ init_pg_type_map_in_ruby(void)
303
306
  {
304
307
  s_id_fit_to_result = rb_intern("fit_to_result");
305
308
  s_id_fit_to_query = rb_intern("fit_to_query");
@@ -32,7 +32,27 @@ require 'pg' unless defined?( PG )
32
32
  # conn.put_copy_data ['a', 123, [5,4,3]]
33
33
  # end
34
34
  # This inserts a single row into copytable with type casts from ruby to
35
- # database types.
35
+ # database types using text format.
36
+ #
37
+ # Very similar with binary format:
38
+ #
39
+ # conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, blob bytea, created_at timestamp)" )
40
+ # # Retrieve table OIDs per empty result set in binary format.
41
+ # res = conn.exec_params( "SELECT * FROM copytable LIMIT 0", [], 1 )
42
+ # # Build a type map for common ruby to database type encoders.
43
+ # btm = PG::BasicTypeMapBasedOnResult.new(conn)
44
+ # # Build a PG::TypeMapByColumn with encoders suitable for copytable.
45
+ # tm = btm.build_column_map( res )
46
+ # row_encoder = PG::BinaryEncoder::CopyRow.new type_map: tm
47
+ #
48
+ # conn.copy_data( "COPY copytable FROM STDIN WITH (FORMAT binary)", row_encoder ) do |res|
49
+ # conn.put_copy_data ['a', 123, "\xff\x00".b, Time.now]
50
+ # end
51
+ #
52
+ # This inserts a single row into copytable with type casts from ruby to
53
+ # database types using binary copy and value format.
54
+ # Binary COPY is faster than text format but less portable and less readable and pg offers fewer en-/decoders of database types.
55
+ #
36
56
  class PG::BasicTypeMapBasedOnResult < PG::TypeMapByOid
37
57
  include PG::BasicTypeRegistry::Checker
38
58
 
@@ -47,16 +47,20 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
47
47
  # Options:
48
48
  # * +registry+: Custom type registry, nil for default global registry
49
49
  # * +if_undefined+: Optional +Proc+ object which is called, if no type for an parameter class is not defined in the registry.
50
+ # The +Proc+ object is called with the name and format of the missing type.
51
+ # Its return value is not used.
50
52
  def initialize(connection_or_coder_maps, registry: nil, if_undefined: nil)
51
53
  @coder_maps = build_coder_maps(connection_or_coder_maps, registry: registry)
52
54
  @array_encoders_by_klass = array_encoders_by_klass
53
55
  @encode_array_as = :array
54
- @if_undefined = if_undefined || proc { |oid_name, format|
55
- raise UndefinedEncoder, "no encoder defined for type #{oid_name.inspect} format #{format}"
56
- }
56
+ @if_undefined = if_undefined || method(:raise_undefined_type).to_proc
57
57
  init_encoders
58
58
  end
59
59
 
60
+ private def raise_undefined_type(oid_name, format)
61
+ raise UndefinedEncoder, "no encoder defined for type #{oid_name.inspect} format #{format}"
62
+ end
63
+
60
64
  # Change the mechanism that is used to encode ruby array values
61
65
  #
62
66
  # Possible values:
@@ -162,14 +166,19 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
162
166
  @textarray_encoder
163
167
  end
164
168
 
165
- DEFAULT_TYPE_MAP = {
169
+ begin
170
+ require "bigdecimal"
171
+ has_bigdecimal = true
172
+ rescue LoadError
173
+ end
174
+
175
+ DEFAULT_TYPE_MAP = PG.make_shareable({
166
176
  TrueClass => [1, 'bool', 'bool'],
167
177
  FalseClass => [1, 'bool', 'bool'],
168
178
  # We use text format and no type OID for numbers, because setting the OID can lead
169
179
  # to unnecessary type conversions on server side.
170
180
  Integer => [0, 'int8'],
171
181
  Float => [0, 'float8'],
172
- BigDecimal => [0, 'numeric'],
173
182
  Time => [0, 'timestamptz'],
174
183
  # We use text format and no type OID for IPAddr, because setting the OID can lead
175
184
  # to unnecessary inet/cidr conversions on the server side.
@@ -177,17 +186,17 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
177
186
  Hash => [0, 'json'],
178
187
  Array => :get_array_type,
179
188
  BinaryData => [1, 'bytea'],
180
- }
189
+ }.merge(has_bigdecimal ? {BigDecimal => [0, 'numeric']} : {}))
190
+ private_constant :DEFAULT_TYPE_MAP
181
191
 
182
- DEFAULT_ARRAY_TYPE_MAP = {
192
+ DEFAULT_ARRAY_TYPE_MAP = PG.make_shareable({
183
193
  TrueClass => [0, '_bool'],
184
194
  FalseClass => [0, '_bool'],
185
195
  Integer => [0, '_int8'],
186
196
  String => [0, '_text'],
187
197
  Float => [0, '_float8'],
188
- BigDecimal => [0, '_numeric'],
189
198
  Time => [0, '_timestamptz'],
190
199
  IPAddr => [0, '_inet'],
191
- }
192
-
200
+ }.merge(has_bigdecimal ? {BigDecimal => [0, '_numeric']} : {}))
201
+ private_constant :DEFAULT_ARRAY_TYPE_MAP
193
202
  end
@@ -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 = Hash.new{|h, k| h[k] = {} }
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[format][oid]
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
- @already_warned[format][oid] = true
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
 
@@ -217,7 +225,11 @@ class PG::BasicTypeRegistry
217
225
  alias_type 0, 'int8', 'int2'
218
226
  alias_type 0, 'oid', 'int2'
219
227
 
220
- register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric
228
+ begin
229
+ require "bigdecimal"
230
+ register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric
231
+ rescue LoadError
232
+ end
221
233
  register_type 0, 'text', PG::TextEncoder::String, PG::TextDecoder::String
222
234
  alias_type 0, 'varchar', 'text'
223
235
  alias_type 0, 'char', 'text'
@@ -232,9 +244,7 @@ class PG::BasicTypeRegistry
232
244
  # alias_type 'uuid', 'text'
233
245
  #
234
246
  # register_type 'money', OID::Money.new
235
- # There is no PG::TextEncoder::Bytea, because it's simple and more efficient to send bytea-data
236
- # in binary format, either with PG::BinaryEncoder::Bytea or in Hash param format.
237
- register_type 0, 'bytea', nil, PG::TextDecoder::Bytea
247
+ register_type 0, 'bytea', PG::TextEncoder::Bytea, PG::TextDecoder::Bytea
238
248
  register_type 0, 'bool', PG::TextEncoder::Boolean, PG::TextDecoder::Boolean
239
249
  # register_type 'bit', OID::Bit.new
240
250
  # register_type 'varbit', OID::Bit.new
@@ -242,6 +252,7 @@ class PG::BasicTypeRegistry
242
252
  register_type 0, 'float4', PG::TextEncoder::Float, PG::TextDecoder::Float
243
253
  alias_type 0, 'float8', 'float4'
244
254
 
255
+ # For compatibility reason the timestamp in text format is encoded as local time (TimestampWithoutTimeZone) instead of UTC
245
256
  register_type 0, 'timestamp', PG::TextEncoder::TimestampWithoutTimeZone, PG::TextDecoder::TimestampWithoutTimeZone
246
257
  register_type 0, 'timestamptz', PG::TextEncoder::TimestampWithTimeZone, PG::TextDecoder::TimestampWithTimeZone
247
258
  register_type 0, 'date', PG::TextEncoder::Date, PG::TextDecoder::Date
@@ -276,26 +287,17 @@ class PG::BasicTypeRegistry
276
287
 
277
288
  register_type 1, 'bytea', PG::BinaryEncoder::Bytea, PG::BinaryDecoder::Bytea
278
289
  register_type 1, 'bool', PG::BinaryEncoder::Boolean, PG::BinaryDecoder::Boolean
279
- register_type 1, 'float4', nil, PG::BinaryDecoder::Float
280
- register_type 1, 'float8', nil, PG::BinaryDecoder::Float
281
- register_type 1, 'timestamp', nil, PG::BinaryDecoder::TimestampUtc
282
- register_type 1, 'timestamptz', nil, PG::BinaryDecoder::TimestampUtcToLocal
290
+ register_type 1, 'float4', PG::BinaryEncoder::Float4, PG::BinaryDecoder::Float
291
+ register_type 1, 'float8', PG::BinaryEncoder::Float8, PG::BinaryDecoder::Float
292
+ register_type 1, 'timestamp', PG::BinaryEncoder::TimestampUtc, PG::BinaryDecoder::TimestampUtc
293
+ register_type 1, 'timestamptz', PG::BinaryEncoder::TimestampUtc, PG::BinaryDecoder::TimestampUtcToLocal
294
+ register_type 1, 'date', PG::BinaryEncoder::Date, PG::BinaryDecoder::Date
283
295
 
284
296
  self
285
297
  end
286
298
 
287
299
  alias define_default_types register_default_types
288
300
 
289
- # @private
290
- DEFAULT_TYPE_REGISTRY = PG::BasicTypeRegistry.new.register_default_types
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
301
+ DEFAULT_TYPE_REGISTRY = PG.make_shareable(PG::BasicTypeRegistry.new.register_default_types)
302
+ private_constant :DEFAULT_TYPE_REGISTRY
301
303
  end
@@ -0,0 +1,9 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ module PG
5
+ module BinaryDecoder
6
+ # Init C part of the decoder
7
+ init_date
8
+ end
9
+ end # module PG
@@ -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}", category: :deprecated) 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}", category: :deprecated) 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}", category: :deprecated) 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}", category: :deprecated) 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}", category: :deprecated) unless hash.empty?
16
+ super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_LOCAL)
17
+ end
18
+ end
19
+ end
20
+ end # module PG