pg 1.2.3 → 1.5.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.appveyor.yml +42 -0
  4. data/.gems +6 -0
  5. data/.github/workflows/binary-gems.yml +117 -0
  6. data/.github/workflows/source-gem.yml +141 -0
  7. data/.gitignore +22 -0
  8. data/.hgsigs +34 -0
  9. data/.hgtags +41 -0
  10. data/.irbrc +23 -0
  11. data/.pryrc +23 -0
  12. data/.tm_properties +21 -0
  13. data/.travis.yml +49 -0
  14. data/Gemfile +17 -0
  15. data/History.md +901 -0
  16. data/Manifest.txt +0 -1
  17. data/README.ja.md +300 -0
  18. data/README.md +286 -0
  19. data/Rakefile +36 -135
  20. data/Rakefile.cross +13 -13
  21. data/certs/ged.pem +24 -0
  22. data/certs/kanis@comcard.de.pem +20 -0
  23. data/certs/larskanis-2022.pem +26 -0
  24. data/certs/larskanis-2023.pem +24 -0
  25. data/certs/larskanis-2024.pem +24 -0
  26. data/ext/errorcodes.def +12 -0
  27. data/ext/errorcodes.rb +0 -0
  28. data/ext/errorcodes.txt +4 -1
  29. data/ext/extconf.rb +104 -25
  30. data/ext/gvl_wrappers.c +4 -0
  31. data/ext/gvl_wrappers.h +23 -0
  32. data/ext/pg.c +73 -58
  33. data/ext/pg.h +28 -5
  34. data/ext/pg_binary_decoder.c +80 -1
  35. data/ext/pg_binary_encoder.c +225 -1
  36. data/ext/pg_coder.c +96 -33
  37. data/ext/pg_connection.c +1032 -704
  38. data/ext/pg_copy_coder.c +351 -33
  39. data/ext/pg_errors.c +1 -1
  40. data/ext/pg_record_coder.c +50 -19
  41. data/ext/pg_result.c +177 -64
  42. data/ext/pg_text_decoder.c +29 -11
  43. data/ext/pg_text_encoder.c +29 -16
  44. data/ext/pg_tuple.c +83 -60
  45. data/ext/pg_type_map.c +44 -10
  46. data/ext/pg_type_map_all_strings.c +17 -3
  47. data/ext/pg_type_map_by_class.c +54 -27
  48. data/ext/pg_type_map_by_column.c +73 -31
  49. data/ext/pg_type_map_by_mri_type.c +48 -19
  50. data/ext/pg_type_map_by_oid.c +59 -27
  51. data/ext/pg_type_map_in_ruby.c +55 -21
  52. data/ext/pg_util.c +2 -2
  53. data/lib/pg/basic_type_map_based_on_result.rb +67 -0
  54. data/lib/pg/basic_type_map_for_queries.rb +202 -0
  55. data/lib/pg/basic_type_map_for_results.rb +104 -0
  56. data/lib/pg/basic_type_registry.rb +303 -0
  57. data/lib/pg/binary_decoder/date.rb +9 -0
  58. data/lib/pg/binary_decoder/timestamp.rb +26 -0
  59. data/lib/pg/binary_encoder/timestamp.rb +20 -0
  60. data/lib/pg/coder.rb +15 -13
  61. data/lib/pg/connection.rb +752 -83
  62. data/lib/pg/exceptions.rb +14 -1
  63. data/lib/pg/text_decoder/date.rb +18 -0
  64. data/lib/pg/text_decoder/inet.rb +9 -0
  65. data/lib/pg/text_decoder/json.rb +14 -0
  66. data/lib/pg/text_decoder/numeric.rb +9 -0
  67. data/lib/pg/text_decoder/timestamp.rb +30 -0
  68. data/lib/pg/text_encoder/date.rb +12 -0
  69. data/lib/pg/text_encoder/inet.rb +28 -0
  70. data/lib/pg/text_encoder/json.rb +14 -0
  71. data/lib/pg/text_encoder/numeric.rb +9 -0
  72. data/lib/pg/text_encoder/timestamp.rb +24 -0
  73. data/lib/pg/version.rb +4 -0
  74. data/lib/pg.rb +94 -39
  75. data/misc/openssl-pg-segfault.rb +31 -0
  76. data/misc/postgres/History.txt +9 -0
  77. data/misc/postgres/Manifest.txt +5 -0
  78. data/misc/postgres/README.txt +21 -0
  79. data/misc/postgres/Rakefile +21 -0
  80. data/misc/postgres/lib/postgres.rb +16 -0
  81. data/misc/ruby-pg/History.txt +9 -0
  82. data/misc/ruby-pg/Manifest.txt +5 -0
  83. data/misc/ruby-pg/README.txt +21 -0
  84. data/misc/ruby-pg/Rakefile +21 -0
  85. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  86. data/pg.gemspec +34 -0
  87. data/rakelib/task_extension.rb +46 -0
  88. data/sample/array_insert.rb +20 -0
  89. data/sample/async_api.rb +102 -0
  90. data/sample/async_copyto.rb +39 -0
  91. data/sample/async_mixed.rb +56 -0
  92. data/sample/check_conn.rb +21 -0
  93. data/sample/copydata.rb +71 -0
  94. data/sample/copyfrom.rb +81 -0
  95. data/sample/copyto.rb +19 -0
  96. data/sample/cursor.rb +21 -0
  97. data/sample/disk_usage_report.rb +177 -0
  98. data/sample/issue-119.rb +94 -0
  99. data/sample/losample.rb +69 -0
  100. data/sample/minimal-testcase.rb +17 -0
  101. data/sample/notify_wait.rb +72 -0
  102. data/sample/pg_statistics.rb +285 -0
  103. data/sample/replication_monitor.rb +222 -0
  104. data/sample/test_binary_values.rb +33 -0
  105. data/sample/wal_shipper.rb +434 -0
  106. data/sample/warehouse_partitions.rb +311 -0
  107. data.tar.gz.sig +0 -0
  108. metadata +137 -210
  109. metadata.gz.sig +0 -0
  110. data/ChangeLog +0 -0
  111. data/History.rdoc +0 -578
  112. data/README.ja.rdoc +0 -13
  113. data/README.rdoc +0 -213
  114. data/lib/pg/basic_type_mapping.rb +0 -522
  115. data/lib/pg/binary_decoder.rb +0 -23
  116. data/lib/pg/constants.rb +0 -12
  117. data/lib/pg/text_decoder.rb +0 -46
  118. data/lib/pg/text_encoder.rb +0 -59
  119. data/spec/data/expected_trace.out +0 -26
  120. data/spec/data/random_binary_data +0 -0
  121. data/spec/helpers.rb +0 -380
  122. data/spec/pg/basic_type_mapping_spec.rb +0 -630
  123. data/spec/pg/connection_spec.rb +0 -1949
  124. data/spec/pg/connection_sync_spec.rb +0 -41
  125. data/spec/pg/result_spec.rb +0 -681
  126. data/spec/pg/tuple_spec.rb +0 -333
  127. data/spec/pg/type_map_by_class_spec.rb +0 -138
  128. data/spec/pg/type_map_by_column_spec.rb +0 -226
  129. data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
  130. data/spec/pg/type_map_by_oid_spec.rb +0 -149
  131. data/spec/pg/type_map_in_ruby_spec.rb +0 -164
  132. data/spec/pg/type_map_spec.rb +0 -22
  133. data/spec/pg/type_spec.rb +0 -1123
  134. data/spec/pg_spec.rb +0 -50
@@ -46,7 +46,7 @@ pg_tmbo_lookup_oid(t_tmbo *this, int format, Oid oid)
46
46
  } else {
47
47
  VALUE obj = rb_hash_lookup( this->format[format].oid_to_coder, UINT2NUM( oid ));
48
48
  /* obj must be nil or some kind of PG::Coder, this is checked at insertion */
49
- conv = NIL_P(obj) ? NULL : DATA_PTR(obj);
49
+ conv = NIL_P(obj) ? NULL : RTYPEDDATA_DATA(obj);
50
50
  /* Write the retrieved coder to the cache */
51
51
  p_ce->oid = oid;
52
52
  p_ce->p_coder = conv;
@@ -70,7 +70,7 @@ pg_tmbo_build_type_map_for_result2( t_tmbo *this, PGresult *pgresult )
70
70
  p_colmap->typemap.default_typemap = pg_typemap_all_strings;
71
71
 
72
72
  colmap = pg_tmbc_allocate();
73
- DATA_PTR(colmap) = p_colmap;
73
+ RTYPEDDATA_DATA(colmap) = p_colmap;
74
74
 
75
75
  for(i=0; i<nfields; i++)
76
76
  {
@@ -113,18 +113,18 @@ pg_tmbo_result_value(t_typemap *p_typemap, VALUE result, int tuple, int field)
113
113
  return dec_func( p_coder, val, len, tuple, field, p_result->enc_idx );
114
114
  }
115
115
 
116
- default_tm = DATA_PTR( this->typemap.default_typemap );
116
+ default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
117
117
  return default_tm->funcs.typecast_result_value( default_tm, result, tuple, field );
118
118
  }
119
119
 
120
120
  static VALUE
121
121
  pg_tmbo_fit_to_result( VALUE self, VALUE result )
122
122
  {
123
- t_tmbo *this = DATA_PTR( self );
123
+ t_tmbo *this = RTYPEDDATA_DATA( self );
124
124
  PGresult *pgresult = pgresult_get( result );
125
125
 
126
- /* Ensure that the default type map fits equaly. */
127
- t_typemap *default_tm = DATA_PTR( this->typemap.default_typemap );
126
+ /* Ensure that the default type map fits equally. */
127
+ t_typemap *default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
128
128
  VALUE sub_typemap = default_tm->funcs.fit_to_result( this->typemap.default_typemap, result );
129
129
 
130
130
  if( PQntuples( pgresult ) <= this->max_rows_for_online_lookup ){
@@ -137,7 +137,7 @@ pg_tmbo_fit_to_result( VALUE self, VALUE result )
137
137
  /* The default type map built a new object, so we need to propagate it
138
138
  * and build a copy of this type map. */
139
139
  VALUE new_typemap = pg_tmbo_s_allocate( rb_cTypeMapByOid );
140
- t_tmbo *p_new_typemap = DATA_PTR(new_typemap);
140
+ t_tmbo *p_new_typemap = RTYPEDDATA_DATA(new_typemap);
141
141
  *p_new_typemap = *this;
142
142
  p_new_typemap->typemap.default_typemap = sub_typemap;
143
143
  return new_typemap;
@@ -147,23 +147,56 @@ pg_tmbo_fit_to_result( VALUE self, VALUE result )
147
147
  * uses a fast array lookup.
148
148
  */
149
149
  VALUE new_typemap = pg_tmbo_build_type_map_for_result2( this, pgresult );
150
- t_tmbo *p_new_typemap = DATA_PTR(new_typemap);
150
+ t_tmbo *p_new_typemap = RTYPEDDATA_DATA(new_typemap);
151
151
  p_new_typemap->typemap.default_typemap = sub_typemap;
152
152
  return new_typemap;
153
153
  }
154
154
  }
155
155
 
156
156
  static void
157
- pg_tmbo_mark( t_tmbo *this )
157
+ pg_tmbo_mark( void *_this )
158
158
  {
159
+ t_tmbo *this = (t_tmbo *)_this;
159
160
  int i;
160
161
 
161
- rb_gc_mark(this->typemap.default_typemap);
162
+ pg_typemap_mark(&this->typemap);
162
163
  for( i=0; i<2; i++){
163
- rb_gc_mark(this->format[i].oid_to_coder);
164
+ rb_gc_mark_movable(this->format[i].oid_to_coder);
164
165
  }
165
166
  }
166
167
 
168
+ static size_t
169
+ pg_tmbo_memsize( const void *_this )
170
+ {
171
+ const t_tmbo *this = (const t_tmbo *)_this;
172
+ return sizeof(*this);
173
+ }
174
+
175
+ static void
176
+ pg_tmbo_compact( void *_this )
177
+ {
178
+ t_tmbo *this = (t_tmbo *)_this;
179
+ int i;
180
+
181
+ pg_typemap_compact(&this->typemap);
182
+ for( i=0; i<2; i++){
183
+ pg_gc_location(this->format[i].oid_to_coder);
184
+ }
185
+ }
186
+
187
+ static const rb_data_type_t pg_tmbo_type = {
188
+ "PG::TypeMapByOid",
189
+ {
190
+ pg_tmbo_mark,
191
+ RUBY_TYPED_DEFAULT_FREE,
192
+ pg_tmbo_memsize,
193
+ pg_compact_callback(pg_tmbo_compact),
194
+ },
195
+ &pg_typemap_type,
196
+ 0,
197
+ RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | PG_RUBY_TYPED_FROZEN_SHAREABLE,
198
+ };
199
+
167
200
  static VALUE
168
201
  pg_tmbo_s_allocate( VALUE klass )
169
202
  {
@@ -171,7 +204,7 @@ pg_tmbo_s_allocate( VALUE klass )
171
204
  VALUE self;
172
205
  int i;
173
206
 
174
- self = Data_Make_Struct( klass, t_tmbo, pg_tmbo_mark, -1, this );
207
+ self = TypedData_Make_Struct( klass, t_tmbo, &pg_tmbo_type, this );
175
208
 
176
209
  this->typemap.funcs.fit_to_result = pg_tmbo_fit_to_result;
177
210
  this->typemap.funcs.fit_to_query = pg_typemap_fit_to_query;
@@ -179,11 +212,11 @@ pg_tmbo_s_allocate( VALUE klass )
179
212
  this->typemap.funcs.typecast_result_value = pg_tmbo_result_value;
180
213
  this->typemap.funcs.typecast_query_param = pg_typemap_typecast_query_param;
181
214
  this->typemap.funcs.typecast_copy_get = pg_typemap_typecast_copy_get;
182
- this->typemap.default_typemap = pg_typemap_all_strings;
215
+ RB_OBJ_WRITE(self, &this->typemap.default_typemap, pg_typemap_all_strings);
183
216
  this->max_rows_for_online_lookup = 10;
184
217
 
185
218
  for( i=0; i<2; i++){
186
- this->format[i].oid_to_coder = rb_hash_new();
219
+ RB_OBJ_WRITE(self, &this->format[i].oid_to_coder, rb_hash_new());
187
220
  }
188
221
 
189
222
  return self;
@@ -205,15 +238,12 @@ static VALUE
205
238
  pg_tmbo_add_coder( VALUE self, VALUE coder )
206
239
  {
207
240
  VALUE hash;
208
- t_tmbo *this = DATA_PTR( self );
241
+ t_tmbo *this = RTYPEDDATA_DATA( self );
209
242
  t_pg_coder *p_coder;
210
243
  struct pg_tmbo_oid_cache_entry *p_ce;
211
244
 
212
- if( !rb_obj_is_kind_of(coder, rb_cPG_Coder) )
213
- rb_raise(rb_eArgError, "invalid type %s (should be some kind of PG::Coder)",
214
- rb_obj_classname( coder ));
215
-
216
- Data_Get_Struct(coder, t_pg_coder, p_coder);
245
+ rb_check_frozen(self);
246
+ TypedData_Get_Struct(coder, t_pg_coder, &pg_coder_type, p_coder);
217
247
 
218
248
  if( p_coder->format < 0 || p_coder->format > 1 )
219
249
  rb_raise(rb_eArgError, "invalid format code %d", p_coder->format);
@@ -243,10 +273,11 @@ pg_tmbo_rm_coder( VALUE self, VALUE format, VALUE oid )
243
273
  {
244
274
  VALUE hash;
245
275
  VALUE coder;
246
- t_tmbo *this = DATA_PTR( self );
276
+ t_tmbo *this = RTYPEDDATA_DATA( self );
247
277
  int i_format = NUM2INT(format);
248
278
  struct pg_tmbo_oid_cache_entry *p_ce;
249
279
 
280
+ rb_check_frozen(self);
250
281
  if( i_format < 0 || i_format > 1 )
251
282
  rb_raise(rb_eArgError, "invalid format code %d", i_format);
252
283
 
@@ -269,7 +300,7 @@ pg_tmbo_rm_coder( VALUE self, VALUE format, VALUE oid )
269
300
  static VALUE
270
301
  pg_tmbo_coders( VALUE self )
271
302
  {
272
- t_tmbo *this = DATA_PTR( self );
303
+ t_tmbo *this = RTYPEDDATA_DATA( self );
273
304
 
274
305
  return rb_ary_concat(
275
306
  rb_funcall(this->format[0].oid_to_coder, rb_intern("values"), 0),
@@ -288,7 +319,8 @@ pg_tmbo_coders( VALUE self )
288
319
  static VALUE
289
320
  pg_tmbo_max_rows_for_online_lookup_set( VALUE self, VALUE value )
290
321
  {
291
- t_tmbo *this = DATA_PTR( self );
322
+ t_tmbo *this = RTYPEDDATA_DATA( self );
323
+ rb_check_frozen(self);
292
324
  this->max_rows_for_online_lookup = NUM2INT(value);
293
325
  return value;
294
326
  }
@@ -300,7 +332,7 @@ pg_tmbo_max_rows_for_online_lookup_set( VALUE self, VALUE value )
300
332
  static VALUE
301
333
  pg_tmbo_max_rows_for_online_lookup_get( VALUE self )
302
334
  {
303
- t_tmbo *this = DATA_PTR( self );
335
+ t_tmbo *this = RTYPEDDATA_DATA( self );
304
336
  return INT2NUM(this->max_rows_for_online_lookup);
305
337
  }
306
338
 
@@ -309,13 +341,13 @@ pg_tmbo_max_rows_for_online_lookup_get( VALUE self )
309
341
  * typemap.build_column_map( result )
310
342
  *
311
343
  * This builds a PG::TypeMapByColumn that fits to the given PG::Result object
312
- * based on it's type OIDs.
344
+ * based on it's type OIDs and binary/text format.
313
345
  *
314
346
  */
315
347
  static VALUE
316
348
  pg_tmbo_build_column_map( VALUE self, VALUE result )
317
349
  {
318
- t_tmbo *this = DATA_PTR( self );
350
+ t_tmbo *this = RTYPEDDATA_DATA( self );
319
351
 
320
352
  if ( !rb_obj_is_kind_of(result, rb_cPGresult) ) {
321
353
  rb_raise( rb_eTypeError, "wrong argument type %s (expected kind of PG::Result)",
@@ -327,7 +359,7 @@ pg_tmbo_build_column_map( VALUE self, VALUE result )
327
359
 
328
360
 
329
361
  void
330
- init_pg_type_map_by_oid()
362
+ init_pg_type_map_by_oid(void)
331
363
  {
332
364
  s_id_decode = rb_intern("decode");
333
365
 
@@ -19,6 +19,33 @@ typedef struct {
19
19
  VALUE self;
20
20
  } t_tmir;
21
21
 
22
+ static size_t
23
+ pg_tmir_memsize( const void *_this )
24
+ {
25
+ const t_tmir *this = (const t_tmir *)_this;
26
+ return sizeof(*this);
27
+ }
28
+
29
+ static void
30
+ pg_tmir_compact( void *_this )
31
+ {
32
+ t_tmir *this = (t_tmir *)_this;
33
+ pg_typemap_compact(&this->typemap);
34
+ pg_gc_location(this->self);
35
+ }
36
+
37
+ static const rb_data_type_t pg_tmir_type = {
38
+ "PG::TypeMapInRuby",
39
+ {
40
+ pg_typemap_mark,
41
+ RUBY_TYPED_DEFAULT_FREE,
42
+ pg_tmir_memsize,
43
+ pg_compact_callback(pg_tmir_compact),
44
+ },
45
+ &pg_typemap_type,
46
+ 0,
47
+ RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | PG_RUBY_TYPED_FROZEN_SHAREABLE,
48
+ };
22
49
 
23
50
  /*
24
51
  * call-seq:
@@ -34,33 +61,37 @@ typedef struct {
34
61
  static VALUE
35
62
  pg_tmir_fit_to_result( VALUE self, VALUE result )
36
63
  {
37
- t_tmir *this = DATA_PTR( self );
64
+ t_tmir *this = RTYPEDDATA_DATA( self );
38
65
  t_typemap *default_tm;
39
66
  t_typemap *p_new_typemap;
40
67
  VALUE sub_typemap;
41
68
  VALUE new_typemap;
42
69
 
43
70
  if( rb_respond_to(self, s_id_fit_to_result) ){
71
+ t_typemap *tm;
72
+ UNUSED(tm);
44
73
  new_typemap = rb_funcall( self, s_id_fit_to_result, 1, result );
45
74
 
46
75
  if ( !rb_obj_is_kind_of(new_typemap, rb_cTypeMap) ) {
76
+ /* TypedData_Get_Struct() raises "wrong argument type", which is misleading,
77
+ * so we better raise our own message */
47
78
  rb_raise( rb_eTypeError, "wrong return type from fit_to_result: %s expected kind of PG::TypeMap",
48
79
  rb_obj_classname( new_typemap ) );
49
80
  }
50
- Check_Type( new_typemap, T_DATA );
81
+ TypedData_Get_Struct(new_typemap, t_typemap, &pg_typemap_type, tm);
51
82
  } else {
52
83
  new_typemap = self;
53
84
  }
54
85
 
55
- /* Ensure that the default type map fits equaly. */
56
- default_tm = DATA_PTR( this->typemap.default_typemap );
86
+ /* Ensure that the default type map fits equally. */
87
+ default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
57
88
  sub_typemap = default_tm->funcs.fit_to_result( this->typemap.default_typemap, result );
58
89
 
59
90
  if( sub_typemap != this->typemap.default_typemap ){
60
91
  new_typemap = rb_obj_dup( new_typemap );
61
92
  }
62
93
 
63
- p_new_typemap = DATA_PTR(new_typemap);
94
+ p_new_typemap = RTYPEDDATA_DATA(new_typemap);
64
95
  p_new_typemap->default_typemap = sub_typemap;
65
96
  return new_typemap;
66
97
  }
@@ -95,8 +126,8 @@ pg_tmir_result_value( t_typemap *p_typemap, VALUE result, int tuple, int field )
95
126
  static VALUE
96
127
  pg_tmir_typecast_result_value( VALUE self, VALUE result, VALUE tuple, VALUE field )
97
128
  {
98
- t_tmir *this = DATA_PTR( self );
99
- t_typemap *default_tm = DATA_PTR( this->typemap.default_typemap );
129
+ t_tmir *this = RTYPEDDATA_DATA( self );
130
+ t_typemap *default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
100
131
  return default_tm->funcs.typecast_result_value( default_tm, result, NUM2INT(tuple), NUM2INT(field) );
101
132
  }
102
133
 
@@ -113,15 +144,15 @@ pg_tmir_typecast_result_value( VALUE self, VALUE result, VALUE tuple, VALUE fiel
113
144
  static VALUE
114
145
  pg_tmir_fit_to_query( VALUE self, VALUE params )
115
146
  {
116
- t_tmir *this = DATA_PTR( self );
147
+ t_tmir *this = RTYPEDDATA_DATA( self );
117
148
  t_typemap *default_tm;
118
149
 
119
150
  if( rb_respond_to(self, s_id_fit_to_query) ){
120
151
  rb_funcall( self, s_id_fit_to_query, 1, params );
121
152
  }
122
153
 
123
- /* Ensure that the default type map fits equaly. */
124
- default_tm = DATA_PTR( this->typemap.default_typemap );
154
+ /* Ensure that the default type map fits equally. */
155
+ default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
125
156
  default_tm->funcs.fit_to_query( this->typemap.default_typemap, params );
126
157
 
127
158
  return self;
@@ -137,7 +168,7 @@ pg_tmir_query_param( t_typemap *p_typemap, VALUE param_value, int field )
137
168
  if ( NIL_P(coder) ){
138
169
  return NULL;
139
170
  } else if( rb_obj_is_kind_of(coder, rb_cPG_Coder) ) {
140
- return DATA_PTR(coder);
171
+ return RTYPEDDATA_DATA(coder);
141
172
  } else {
142
173
  rb_raise( rb_eTypeError, "wrong return type from typecast_query_param: %s expected nil or kind of PG::Coder",
143
174
  rb_obj_classname( coder ) );
@@ -161,8 +192,8 @@ pg_tmir_query_param( t_typemap *p_typemap, VALUE param_value, int field )
161
192
  static VALUE
162
193
  pg_tmir_typecast_query_param( VALUE self, VALUE param_value, VALUE field )
163
194
  {
164
- t_tmir *this = DATA_PTR( self );
165
- t_typemap *default_tm = DATA_PTR( this->typemap.default_typemap );
195
+ t_tmir *this = RTYPEDDATA_DATA( self );
196
+ t_typemap *default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
166
197
  t_pg_coder *p_coder = default_tm->funcs.typecast_query_param( default_tm, param_value, NUM2INT(field) );
167
198
 
168
199
  return p_coder ? p_coder->coder_obj : Qnil;
@@ -179,6 +210,9 @@ pg_tmir_typecast_query_param( VALUE self, VALUE param_value, VALUE field )
179
210
  * This method is called, when a type map is used for decoding copy data,
180
211
  * before the value is casted.
181
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
+ *
182
216
  */
183
217
  static VALUE pg_tmir_fit_to_copy_get_dummy( VALUE self ){}
184
218
  #endif
@@ -186,7 +220,7 @@ static VALUE pg_tmir_fit_to_copy_get_dummy( VALUE self ){}
186
220
  static int
187
221
  pg_tmir_fit_to_copy_get( VALUE self )
188
222
  {
189
- t_tmir *this = DATA_PTR( self );
223
+ t_tmir *this = RTYPEDDATA_DATA( self );
190
224
  t_typemap *default_tm;
191
225
  VALUE num_columns = INT2NUM(0);
192
226
 
@@ -198,8 +232,8 @@ pg_tmir_fit_to_copy_get( VALUE self )
198
232
  rb_raise( rb_eTypeError, "wrong return type from fit_to_copy_get: %s expected kind of Integer",
199
233
  rb_obj_classname( num_columns ) );
200
234
  }
201
- /* Ensure that the default type map fits equaly. */
202
- default_tm = DATA_PTR( this->typemap.default_typemap );
235
+ /* Ensure that the default type map fits equally. */
236
+ default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
203
237
  default_tm->funcs.fit_to_copy_get( this->typemap.default_typemap );
204
238
 
205
239
  return NUM2INT(num_columns);;
@@ -239,8 +273,8 @@ pg_tmir_copy_get( t_typemap *p_typemap, VALUE field_str, int fieldno, int format
239
273
  static VALUE
240
274
  pg_tmir_typecast_copy_get( VALUE self, VALUE field_str, VALUE fieldno, VALUE format, VALUE enc )
241
275
  {
242
- t_tmir *this = DATA_PTR( self );
243
- t_typemap *default_tm = DATA_PTR( this->typemap.default_typemap );
276
+ t_tmir *this = RTYPEDDATA_DATA( self );
277
+ t_typemap *default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
244
278
  int enc_idx = rb_to_encoding_index( enc );
245
279
 
246
280
  return default_tm->funcs.typecast_copy_get( default_tm, field_str, NUM2INT(fieldno), NUM2INT(format), enc_idx );
@@ -252,7 +286,7 @@ pg_tmir_s_allocate( VALUE klass )
252
286
  t_tmir *this;
253
287
  VALUE self;
254
288
 
255
- self = Data_Make_Struct( klass, t_tmir, NULL, -1, this );
289
+ self = TypedData_Make_Struct( klass, t_tmir, &pg_tmir_type, this );
256
290
 
257
291
  this->typemap.funcs.fit_to_result = pg_tmir_fit_to_result;
258
292
  this->typemap.funcs.fit_to_query = pg_tmir_fit_to_query;
@@ -260,7 +294,7 @@ pg_tmir_s_allocate( VALUE klass )
260
294
  this->typemap.funcs.typecast_result_value = pg_tmir_result_value;
261
295
  this->typemap.funcs.typecast_query_param = pg_tmir_query_param;
262
296
  this->typemap.funcs.typecast_copy_get = pg_tmir_copy_get;
263
- this->typemap.default_typemap = pg_typemap_all_strings;
297
+ RB_OBJ_WRITE(self, &this->typemap.default_typemap, pg_typemap_all_strings);
264
298
  this->self = self;
265
299
 
266
300
  return self;
@@ -268,7 +302,7 @@ pg_tmir_s_allocate( VALUE klass )
268
302
 
269
303
 
270
304
  void
271
- init_pg_type_map_in_ruby()
305
+ init_pg_type_map_in_ruby(void)
272
306
  {
273
307
  s_id_fit_to_result = rb_intern("fit_to_result");
274
308
  s_id_fit_to_query = rb_intern("fit_to_query");
data/ext/pg_util.c CHANGED
@@ -91,7 +91,7 @@ base64_decode( char *out, const char *in, unsigned int len)
91
91
  *out_ptr++ = (b << 4) | (c >> 2);
92
92
  *out_ptr++ = (c << 6) | d;
93
93
  } else if (in_ptr < iend_ptr){
94
- a = b = c = d = 0xff;
94
+ b = c = d = 0xff;
95
95
  while ((a = base64_decode_table[*in_ptr++]) == 0xff && in_ptr < iend_ptr) {}
96
96
  if (in_ptr < iend_ptr){
97
97
  while ((b = base64_decode_table[*in_ptr++]) == 0xff && in_ptr < iend_ptr) {}
@@ -116,7 +116,7 @@ base64_decode( char *out, const char *in, unsigned int len)
116
116
  }
117
117
 
118
118
 
119
- return (char*)out_ptr - out;
119
+ return (int)((char*)out_ptr - out);
120
120
  }
121
121
 
122
122
  /*
@@ -0,0 +1,67 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'pg' unless defined?( PG )
5
+
6
+ # Simple set of rules for type casting common PostgreSQL types from Ruby
7
+ # to PostgreSQL.
8
+ #
9
+ # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
10
+ # PostgreSQL's +pg_type+ table in PG::BasicTypeMapBasedOnResult.new .
11
+ #
12
+ # This class works equal to PG::BasicTypeMapForResults, but does not define decoders for
13
+ # the given result OIDs, but encoders. So it can be used to type cast field values based on
14
+ # the type OID retrieved by a separate SQL query.
15
+ #
16
+ # PG::TypeMapByOid#build_column_map(result) can be used to generate a result independent
17
+ # PG::TypeMapByColumn type map, which can subsequently be used to cast query bind parameters
18
+ # or #put_copy_data fields.
19
+ #
20
+ # Example:
21
+ # conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
22
+ #
23
+ # # Retrieve table OIDs per empty result set.
24
+ # res = conn.exec( "SELECT * FROM copytable LIMIT 0" )
25
+ # # Build a type map for common ruby to database type encoders.
26
+ # btm = PG::BasicTypeMapBasedOnResult.new(conn)
27
+ # # Build a PG::TypeMapByColumn with encoders suitable for copytable.
28
+ # tm = btm.build_column_map( res )
29
+ # row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
30
+ #
31
+ # conn.copy_data( "COPY copytable FROM STDIN", row_encoder ) do |res|
32
+ # conn.put_copy_data ['a', 123, [5,4,3]]
33
+ # end
34
+ # This inserts a single row into copytable with type casts from ruby to
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
+ #
56
+ class PG::BasicTypeMapBasedOnResult < PG::TypeMapByOid
57
+ include PG::BasicTypeRegistry::Checker
58
+
59
+ def initialize(connection_or_coder_maps, registry: nil)
60
+ @coder_maps = build_coder_maps(connection_or_coder_maps, registry: registry)
61
+
62
+ # Populate TypeMapByOid hash with encoders
63
+ @coder_maps.each_format(:encoder).flat_map{|f| f.coders }.each do |coder|
64
+ add_coder(coder)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,202 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'pg' unless defined?( PG )
5
+
6
+ # Simple set of rules for type casting common Ruby types to PostgreSQL.
7
+ #
8
+ # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
9
+ # PostgreSQL's pg_type table in PG::BasicTypeMapForQueries.new .
10
+ #
11
+ # Query params are type casted based on the class of the given value.
12
+ #
13
+ # Higher level libraries will most likely not make use of this class, but use their
14
+ # own derivation of PG::TypeMapByClass or another set of rules to choose suitable
15
+ # encoders and decoders for the values to be sent.
16
+ #
17
+ # Example:
18
+ # conn = PG::Connection.new
19
+ # # Assign a default ruleset for type casts of input and output values.
20
+ # conn.type_map_for_queries = PG::BasicTypeMapForQueries.new(conn)
21
+ # # Execute a query. The Integer param value is typecasted internally by PG::BinaryEncoder::Int8.
22
+ # # The format of the parameter is set to 0 (text) and the OID of this parameter is set to 20 (int8).
23
+ # res = conn.exec_params( "SELECT $1", [5] )
24
+ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
25
+ # Helper class for submission of binary strings into bytea columns.
26
+ #
27
+ # Since PG::BasicTypeMapForQueries chooses the encoder to be used by the class of the submitted value,
28
+ # it's necessary to send binary strings as BinaryData.
29
+ # That way they're distinct from text strings.
30
+ # Please note however that PG::BasicTypeMapForResults delivers bytea columns as plain String
31
+ # with binary encoding.
32
+ #
33
+ # conn.type_map_for_queries = PG::BasicTypeMapForQueries.new(conn)
34
+ # conn.exec("CREATE TEMP TABLE test (data bytea)")
35
+ # bd = PG::BasicTypeMapForQueries::BinaryData.new("ab\xff\0cd")
36
+ # conn.exec_params("INSERT INTO test (data) VALUES ($1)", [bd])
37
+ class BinaryData < String
38
+ end
39
+
40
+ class UndefinedEncoder < RuntimeError
41
+ end
42
+
43
+ include PG::BasicTypeRegistry::Checker
44
+
45
+ # Create a new type map for query submission
46
+ #
47
+ # Options:
48
+ # * +registry+: Custom type registry, nil for default global registry
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.
52
+ def initialize(connection_or_coder_maps, registry: nil, if_undefined: nil)
53
+ @coder_maps = build_coder_maps(connection_or_coder_maps, registry: registry)
54
+ @array_encoders_by_klass = array_encoders_by_klass
55
+ @encode_array_as = :array
56
+ @if_undefined = if_undefined || method(:raise_undefined_type).to_proc
57
+ init_encoders
58
+ end
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
+
64
+ # Change the mechanism that is used to encode ruby array values
65
+ #
66
+ # Possible values:
67
+ # * +:array+ : Encode the ruby array as a PostgreSQL array.
68
+ # The array element type is inferred from the class of the first array element. This is the default.
69
+ # * +:json+ : Encode the ruby array as a JSON document.
70
+ # * +:record+ : Encode the ruby array as a composite type row.
71
+ # * <code>"_type"</code> : Encode the ruby array as a particular PostgreSQL type.
72
+ # All PostgreSQL array types are supported.
73
+ # If there's an encoder registered for the elements +type+, it will be used.
74
+ # Otherwise a string conversion (by +value.to_s+) is done.
75
+ def encode_array_as=(pg_type)
76
+ case pg_type
77
+ when :array
78
+ when :json
79
+ when :record
80
+ when /\A_/
81
+ else
82
+ raise ArgumentError, "invalid pg_type #{pg_type.inspect}"
83
+ end
84
+
85
+ @encode_array_as = pg_type
86
+
87
+ init_encoders
88
+ end
89
+
90
+ attr_reader :encode_array_as
91
+
92
+ private
93
+
94
+ def init_encoders
95
+ coders.each { |kl, c| self[kl] = nil } # Clear type map
96
+ populate_encoder_list
97
+ @textarray_encoder = coder_by_name(0, :encoder, '_text')
98
+ end
99
+
100
+ def coder_by_name(format, direction, name)
101
+ check_format_and_direction(format, direction)
102
+ @coder_maps.map_for(format, direction).coder_by_name(name)
103
+ end
104
+
105
+ def undefined(name, format)
106
+ @if_undefined.call(name, format)
107
+ end
108
+
109
+ def populate_encoder_list
110
+ DEFAULT_TYPE_MAP.each do |klass, selector|
111
+ if Array === selector
112
+ format, name, oid_name = selector
113
+ coder = coder_by_name(format, :encoder, name).dup
114
+ if coder
115
+ if oid_name
116
+ oid_coder = coder_by_name(format, :encoder, oid_name)
117
+ if oid_coder
118
+ coder.oid = oid_coder.oid
119
+ else
120
+ undefined(oid_name, format)
121
+ end
122
+ else
123
+ coder.oid = 0
124
+ end
125
+ self[klass] = coder
126
+ else
127
+ undefined(name, format)
128
+ end
129
+ else
130
+
131
+ case @encode_array_as
132
+ when :array
133
+ self[klass] = selector
134
+ when :json
135
+ self[klass] = PG::TextEncoder::JSON.new
136
+ when :record
137
+ self[klass] = PG::TextEncoder::Record.new type_map: self
138
+ when /\A_/
139
+ coder = coder_by_name(0, :encoder, @encode_array_as)
140
+ if coder
141
+ self[klass] = coder
142
+ else
143
+ undefined(@encode_array_as, format)
144
+ end
145
+ else
146
+ raise ArgumentError, "invalid pg_type #{@encode_array_as.inspect}"
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ def array_encoders_by_klass
153
+ DEFAULT_ARRAY_TYPE_MAP.inject({}) do |h, (klass, (format, name))|
154
+ h[klass] = coder_by_name(format, :encoder, name)
155
+ h
156
+ end
157
+ end
158
+
159
+ def get_array_type(value)
160
+ elem = value
161
+ while elem.kind_of?(Array)
162
+ elem = elem.first
163
+ end
164
+ @array_encoders_by_klass[elem.class] ||
165
+ elem.class.ancestors.lazy.map{|ancestor| @array_encoders_by_klass[ancestor] }.find{|a| a } ||
166
+ @textarray_encoder
167
+ end
168
+
169
+ begin
170
+ require "bigdecimal"
171
+ has_bigdecimal = true
172
+ rescue LoadError
173
+ end
174
+
175
+ DEFAULT_TYPE_MAP = PG.make_shareable({
176
+ TrueClass => [1, 'bool', 'bool'],
177
+ FalseClass => [1, 'bool', 'bool'],
178
+ # We use text format and no type OID for numbers, because setting the OID can lead
179
+ # to unnecessary type conversions on server side.
180
+ Integer => [0, 'int8'],
181
+ Float => [0, 'float8'],
182
+ Time => [0, 'timestamptz'],
183
+ # We use text format and no type OID for IPAddr, because setting the OID can lead
184
+ # to unnecessary inet/cidr conversions on the server side.
185
+ IPAddr => [0, 'inet'],
186
+ Hash => [0, 'json'],
187
+ Array => :get_array_type,
188
+ BinaryData => [1, 'bytea'],
189
+ }.merge(has_bigdecimal ? {BigDecimal => [0, 'numeric']} : {}))
190
+ private_constant :DEFAULT_TYPE_MAP
191
+
192
+ DEFAULT_ARRAY_TYPE_MAP = PG.make_shareable({
193
+ TrueClass => [0, '_bool'],
194
+ FalseClass => [0, '_bool'],
195
+ Integer => [0, '_int8'],
196
+ String => [0, '_text'],
197
+ Float => [0, '_float8'],
198
+ Time => [0, '_timestamptz'],
199
+ IPAddr => [0, '_inet'],
200
+ }.merge(has_bigdecimal ? {BigDecimal => [0, '_numeric']} : {}))
201
+ private_constant :DEFAULT_ARRAY_TYPE_MAP
202
+ end