pg 1.4.6 → 1.6.1

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 (92) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/{History.md → CHANGELOG.md} +185 -3
  4. data/Gemfile +12 -3
  5. data/README-Windows.rdoc +1 -1
  6. data/README.ja.md +75 -41
  7. data/README.md +86 -31
  8. data/Rakefile +95 -14
  9. data/certs/kanis@comcard.de.pem +20 -0
  10. data/certs/larskanis-2024.pem +24 -0
  11. data/ext/errorcodes.def +4 -5
  12. data/ext/errorcodes.txt +2 -5
  13. data/ext/extconf.rb +165 -14
  14. data/ext/gvl_wrappers.c +13 -2
  15. data/ext/gvl_wrappers.h +33 -0
  16. data/ext/pg.c +28 -35
  17. data/ext/pg.h +18 -14
  18. data/ext/pg_binary_decoder.c +231 -0
  19. data/ext/pg_binary_encoder.c +427 -0
  20. data/ext/pg_cancel_connection.c +360 -0
  21. data/ext/pg_coder.c +70 -12
  22. data/ext/pg_connection.c +473 -208
  23. data/ext/pg_copy_coder.c +316 -23
  24. data/ext/pg_record_coder.c +12 -11
  25. data/ext/pg_result.c +102 -30
  26. data/ext/pg_text_decoder.c +31 -10
  27. data/ext/pg_text_encoder.c +58 -26
  28. data/ext/pg_tuple.c +36 -33
  29. data/ext/pg_type_map.c +4 -3
  30. data/ext/pg_type_map_all_strings.c +3 -3
  31. data/ext/pg_type_map_by_class.c +6 -4
  32. data/ext/pg_type_map_by_column.c +9 -4
  33. data/ext/pg_type_map_by_mri_type.c +1 -1
  34. data/ext/pg_type_map_by_oid.c +10 -5
  35. data/ext/pg_type_map_in_ruby.c +6 -3
  36. data/lib/pg/basic_type_map_based_on_result.rb +21 -1
  37. data/lib/pg/basic_type_map_for_queries.rb +23 -10
  38. data/lib/pg/basic_type_map_for_results.rb +26 -3
  39. data/lib/pg/basic_type_registry.rb +46 -36
  40. data/lib/pg/binary_decoder/date.rb +9 -0
  41. data/lib/pg/binary_decoder/timestamp.rb +26 -0
  42. data/lib/pg/binary_encoder/timestamp.rb +20 -0
  43. data/lib/pg/cancel_connection.rb +53 -0
  44. data/lib/pg/coder.rb +18 -14
  45. data/lib/pg/connection.rb +387 -172
  46. data/lib/pg/exceptions.rb +6 -0
  47. data/lib/pg/text_decoder/date.rb +21 -0
  48. data/lib/pg/text_decoder/inet.rb +9 -0
  49. data/lib/pg/text_decoder/json.rb +17 -0
  50. data/lib/pg/text_decoder/numeric.rb +9 -0
  51. data/lib/pg/text_decoder/timestamp.rb +30 -0
  52. data/lib/pg/text_encoder/date.rb +13 -0
  53. data/lib/pg/text_encoder/inet.rb +31 -0
  54. data/lib/pg/text_encoder/json.rb +17 -0
  55. data/lib/pg/text_encoder/numeric.rb +9 -0
  56. data/lib/pg/text_encoder/timestamp.rb +24 -0
  57. data/lib/pg/version.rb +1 -1
  58. data/lib/pg.rb +78 -17
  59. data/misc/yugabyte/Dockerfile +9 -0
  60. data/misc/yugabyte/docker-compose.yml +28 -0
  61. data/misc/yugabyte/pg-test.rb +45 -0
  62. data/pg.gemspec +9 -5
  63. data/ports/patches/krb5/1.21.3/0001-Allow-static-linking-krb5-library.patch +30 -0
  64. data/ports/patches/openssl/3.5.1/0001-aarch64-mingw.patch +21 -0
  65. data/ports/patches/postgresql/17.5/0001-Use-workaround-of-__builtin_setjmp-only-on-MINGW-on-.patch +42 -0
  66. data/ports/patches/postgresql/17.5/0001-libpq-Process-buffered-SSL-read-bytes-to-support-rec.patch +52 -0
  67. data/rakelib/pg_gem_helper.rb +64 -0
  68. data.tar.gz.sig +0 -0
  69. metadata +61 -49
  70. metadata.gz.sig +0 -0
  71. data/.appveyor.yml +0 -42
  72. data/.gems +0 -6
  73. data/.gemtest +0 -0
  74. data/.github/workflows/binary-gems.yml +0 -117
  75. data/.github/workflows/source-gem.yml +0 -137
  76. data/.gitignore +0 -19
  77. data/.hgsigs +0 -34
  78. data/.hgtags +0 -41
  79. data/.irbrc +0 -23
  80. data/.pryrc +0 -23
  81. data/.tm_properties +0 -21
  82. data/.travis.yml +0 -49
  83. data/Manifest.txt +0 -72
  84. data/Rakefile.cross +0 -298
  85. data/lib/pg/binary_decoder.rb +0 -23
  86. data/lib/pg/constants.rb +0 -12
  87. data/lib/pg/text_decoder.rb +0 -46
  88. data/lib/pg/text_encoder.rb +0 -59
  89. data/translation/.po4a-version +0 -7
  90. data/translation/po/all.pot +0 -875
  91. data/translation/po/ja.po +0 -868
  92. data/translation/po4a.cfg +0 -9
@@ -12,6 +12,8 @@
12
12
  #endif
13
13
 
14
14
  VALUE rb_mPG_BinaryDecoder;
15
+ static VALUE s_Date;
16
+ static ID s_id_new;
15
17
 
16
18
 
17
19
  /*
@@ -131,6 +133,154 @@ pg_bin_dec_to_base64(t_pg_coder *conv, const char *val, int len, int tuple, int
131
133
  return out_value;
132
134
  }
133
135
 
136
+ /*
137
+ * Maximum number of array subscripts (arbitrary limit)
138
+ */
139
+ #define MAXDIM 6
140
+
141
+ /*
142
+ * Document-class: PG::BinaryDecoder::Array < PG::CompositeDecoder
143
+ *
144
+ * This is a decoder class for conversion of binary array types.
145
+ *
146
+ * It returns an Array with possibly an arbitrary number of sub-Arrays.
147
+ * All values are decoded according to the #elements_type accessor.
148
+ * Sub-arrays are decoded recursively.
149
+ *
150
+ * This decoder simply ignores any dimension decorations preceding the array values.
151
+ * It returns all array values as regular ruby Array with a zero based index, regardless of the index given in the dimension decoration.
152
+ *
153
+ * An array decoder which respects dimension decorations is waiting to be implemented.
154
+ *
155
+ */
156
+ static VALUE
157
+ pg_bin_dec_array(t_pg_coder *conv, const char *input_line, int len, int tuple, int field, int enc_idx)
158
+ {
159
+ t_pg_composite_coder *this = (t_pg_composite_coder *)conv;
160
+ t_pg_coder_dec_func dec_func = pg_coder_dec_func(this->elem, this->comp.format);
161
+
162
+ /* Current field */
163
+ VALUE field_str;
164
+
165
+ int32_t nitems32;
166
+ int i;
167
+ int ndim;
168
+ int nitems;
169
+ int flags;
170
+ int dim;
171
+ int dim_sizes[MAXDIM];
172
+ VALUE arrays[MAXDIM];
173
+ char *output_ptr;
174
+ const char *cur_ptr;
175
+ const char *line_end_ptr;
176
+ char *end_capa_ptr;
177
+
178
+ /* Allocate a new string with embedded capacity and realloc later with
179
+ * exponential growing size when needed. */
180
+ PG_RB_STR_NEW( field_str, output_ptr, end_capa_ptr );
181
+
182
+ /* set pointer variables for loop */
183
+ cur_ptr = input_line;
184
+ line_end_ptr = input_line + len;
185
+
186
+ /* read number of dimensions */
187
+ if (line_end_ptr - cur_ptr < 4 ) goto length_error;
188
+ ndim = read_nbo32(cur_ptr);
189
+ if (ndim < 0 || ndim > MAXDIM) {
190
+ rb_raise( rb_eArgError, "unsupported number of array dimensions: %d", ndim );
191
+ }
192
+ cur_ptr += 4;
193
+
194
+ /* read flags */
195
+ if (line_end_ptr - cur_ptr < 4 ) goto length_error;
196
+ flags = read_nbo32(cur_ptr);
197
+ if (flags != 0 && flags != 1) {
198
+ rb_raise( rb_eArgError, "unsupported binary array flags: %d", flags );
199
+ }
200
+ cur_ptr += 4;
201
+
202
+ /* ignore element OID */
203
+ if (line_end_ptr - cur_ptr < 4 ) goto length_error;
204
+ cur_ptr += 4;
205
+
206
+ nitems32 = ndim == 0 ? 0 : 1;
207
+ for (i = 0; i < ndim; i++) {
208
+ int64_t prod;
209
+
210
+ /* read size of dimensions and ignore lower bound */
211
+ if (line_end_ptr - cur_ptr < 8 ) goto length_error;
212
+ dim_sizes[i] = read_nbo32(cur_ptr);
213
+ prod = (int64_t) nitems32 * (int64_t) dim_sizes[i];
214
+ nitems32 = (int32_t) prod;
215
+ if (dim_sizes[i] < 0 || (int64_t) nitems32 != prod) {
216
+ rb_raise( rb_eArgError, "unsupported array size: %" PRId64, prod );
217
+ }
218
+ cur_ptr += 8;
219
+ }
220
+ nitems = (int)nitems32;
221
+
222
+ dim = 0;
223
+ arrays[dim] = rb_ary_new2(ndim == 0 ? 0 : dim_sizes[dim]);
224
+ for (i = 0; i < nitems; i++) {
225
+ int input_len;
226
+
227
+ /* traverse dimensions down */
228
+ while (dim < ndim - 1) {
229
+ dim++;
230
+ arrays[dim] = rb_ary_new2(dim_sizes[dim]);
231
+ rb_ary_push(arrays[dim - 1], arrays[dim]);
232
+ }
233
+
234
+ /* read element length */
235
+ if (line_end_ptr - cur_ptr < 4 ) goto length_error;
236
+ input_len = read_nbo32(cur_ptr);
237
+ cur_ptr += 4;
238
+
239
+ /* convert and put element into array */
240
+ if (input_len < 0) {
241
+ if (input_len != -1) goto length_error;
242
+ /* NULL indicator */
243
+ rb_ary_push(arrays[dim], Qnil);
244
+ } else {
245
+ VALUE field_value;
246
+ if (line_end_ptr - cur_ptr < input_len ) goto length_error;
247
+
248
+ /* copy input data to field_str */
249
+ PG_RB_STR_ENSURE_CAPA( field_str, input_len, output_ptr, end_capa_ptr );
250
+ memcpy(output_ptr, cur_ptr, input_len);
251
+ cur_ptr += input_len;
252
+ output_ptr += input_len;
253
+ /* convert field_str through the type map */
254
+ rb_str_set_len( field_str, output_ptr - RSTRING_PTR(field_str) );
255
+ field_value = dec_func(this->elem, RSTRING_PTR(field_str), input_len, tuple, field, enc_idx);
256
+
257
+ rb_ary_push(arrays[dim], field_value);
258
+
259
+ if( field_value == field_str ){
260
+ /* Our output string will be send to the user, so we can not reuse
261
+ * it for the next field. */
262
+ PG_RB_STR_NEW( field_str, output_ptr, end_capa_ptr );
263
+ }
264
+ }
265
+
266
+ /* Reset the pointer to the start of the output/buffer string. */
267
+ output_ptr = RSTRING_PTR(field_str);
268
+
269
+ /* traverse dimensions up */
270
+ while (RARRAY_LEN(arrays[dim]) >= dim_sizes[dim] && dim > 0) {
271
+ dim--;
272
+ }
273
+ }
274
+
275
+ if (cur_ptr < line_end_ptr)
276
+ rb_raise( rb_eArgError, "trailing data after binary array data at position: %ld", (long)(cur_ptr - input_line) + 1 );
277
+
278
+ return arrays[0];
279
+
280
+ length_error:
281
+ rb_raise( rb_eArgError, "premature end of binary array data at position: %ld", (long)(cur_ptr - input_line) + 1 );
282
+ }
283
+
134
284
  #define PG_INT64_MIN (-0x7FFFFFFFFFFFFFFFL - 1)
135
285
  #define PG_INT64_MAX 0x7FFFFFFFFFFFFFFFL
136
286
 
@@ -195,6 +345,84 @@ pg_bin_dec_timestamp(t_pg_coder *conv, const char *val, int len, int tuple, int
195
345
  }
196
346
  }
197
347
 
348
+ #define PG_INT32_MIN (-0x7FFFFFFF-1)
349
+ #define PG_INT32_MAX (0x7FFFFFFF)
350
+ #define POSTGRES_EPOCH_JDATE 2451545 /* == date2j(2000, 1, 1) */
351
+ #define MONTHS_PER_YEAR 12
352
+
353
+ /* taken from PostgreSQL sources at src/backend/utils/adt/datetime.c */
354
+ void
355
+ j2date(int jd, int *year, int *month, int *day)
356
+ {
357
+ unsigned int julian;
358
+ unsigned int quad;
359
+ unsigned int extra;
360
+ int y;
361
+
362
+ julian = jd;
363
+ julian += 32044;
364
+ quad = julian / 146097;
365
+ extra = (julian - quad * 146097) * 4 + 3;
366
+ julian += 60 + quad * 3 + extra / 146097;
367
+ quad = julian / 1461;
368
+ julian -= quad * 1461;
369
+ y = julian * 4 / 1461;
370
+ julian = ((y != 0) ? ((julian + 305) % 365) : ((julian + 306) % 366))
371
+ + 123;
372
+ y += quad * 4;
373
+ *year = y - 4800;
374
+ quad = julian * 2141 / 65536;
375
+ *day = julian - 7834 * quad / 256;
376
+ *month = (quad + 10) % MONTHS_PER_YEAR + 1;
377
+ } /* j2date() */
378
+
379
+ /*
380
+ * Document-class: PG::BinaryDecoder::Date < PG::SimpleDecoder
381
+ *
382
+ * This is a decoder class for conversion of PostgreSQL binary date
383
+ * to Ruby Date objects.
384
+ *
385
+ * As soon as this class is used, it requires the ruby standard library 'date'.
386
+ */
387
+ static VALUE
388
+ pg_bin_dec_date(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx)
389
+ {
390
+ int year, month, day;
391
+ int date;
392
+
393
+ if (len != 4) {
394
+ rb_raise(rb_eTypeError, "unexpected date format != 4 bytes");
395
+ }
396
+
397
+ date = read_nbo32(val);
398
+ switch(date){
399
+ case PG_INT32_MAX:
400
+ return rb_str_new2("infinity");
401
+ case PG_INT32_MIN:
402
+ return rb_str_new2("-infinity");
403
+ default:
404
+ j2date(date + POSTGRES_EPOCH_JDATE, &year, &month, &day);
405
+
406
+ return rb_funcall(s_Date, s_id_new, 3, INT2NUM(year), INT2NUM(month), INT2NUM(day));
407
+ }
408
+ }
409
+
410
+ /* called per autoload when BinaryDecoder::Date is used */
411
+ static VALUE
412
+ init_pg_bin_decoder_date(VALUE rb_mPG_BinaryDecoder)
413
+ {
414
+ rb_require("date");
415
+ s_Date = rb_const_get(rb_cObject, rb_intern("Date"));
416
+ rb_gc_register_mark_object(s_Date);
417
+ s_id_new = rb_intern("new");
418
+
419
+ /* dummy = rb_define_class_under( rb_mPG_BinaryDecoder, "Date", rb_cPG_SimpleDecoder ); */
420
+ pg_define_coder( "Date", pg_bin_dec_date, rb_cPG_SimpleDecoder, rb_mPG_BinaryDecoder );
421
+
422
+ return Qnil;
423
+ }
424
+
425
+
198
426
  /*
199
427
  * Document-class: PG::BinaryDecoder::String < PG::SimpleDecoder
200
428
  *
@@ -209,6 +437,7 @@ init_pg_binary_decoder(void)
209
437
  {
210
438
  /* This module encapsulates all decoder classes with binary input format */
211
439
  rb_mPG_BinaryDecoder = rb_define_module_under( rb_mPG, "BinaryDecoder" );
440
+ rb_define_private_method(rb_singleton_class(rb_mPG_BinaryDecoder), "init_date", init_pg_bin_decoder_date, 0);
212
441
 
213
442
  /* Make RDoc aware of the decoder classes... */
214
443
  /* dummy = rb_define_class_under( rb_mPG_BinaryDecoder, "Boolean", rb_cPG_SimpleDecoder ); */
@@ -224,6 +453,8 @@ init_pg_binary_decoder(void)
224
453
  /* dummy = rb_define_class_under( rb_mPG_BinaryDecoder, "Timestamp", rb_cPG_SimpleDecoder ); */
225
454
  pg_define_coder( "Timestamp", pg_bin_dec_timestamp, rb_cPG_SimpleDecoder, rb_mPG_BinaryDecoder );
226
455
 
456
+ /* dummy = rb_define_class_under( rb_mPG_BinaryDecoder, "Array", rb_cPG_CompositeDecoder ); */
457
+ pg_define_coder( "Array", pg_bin_dec_array, rb_cPG_CompositeDecoder, rb_mPG_BinaryDecoder );
227
458
  /* dummy = rb_define_class_under( rb_mPG_BinaryDecoder, "ToBase64", rb_cPG_CompositeDecoder ); */
228
459
  pg_define_coder( "ToBase64", pg_bin_dec_to_base64, rb_cPG_CompositeDecoder, rb_mPG_BinaryDecoder );
229
460
  }
@@ -11,6 +11,9 @@
11
11
  #endif
12
12
 
13
13
  VALUE rb_mPG_BinaryEncoder;
14
+ static ID s_id_year;
15
+ static ID s_id_month;
16
+ static ID s_id_day;
14
17
 
15
18
 
16
19
  /*
@@ -93,6 +96,416 @@ pg_bin_enc_int8(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, i
93
96
  return 8;
94
97
  }
95
98
 
99
+ /*
100
+ * Document-class: PG::BinaryEncoder::Float4 < PG::SimpleEncoder
101
+ *
102
+ * This is the binary encoder class for the PostgreSQL +float4+ type.
103
+ *
104
+ */
105
+ static int
106
+ pg_bin_enc_float4(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx)
107
+ {
108
+ union {
109
+ float f;
110
+ int32_t i;
111
+ } swap4;
112
+
113
+ if(out){
114
+ swap4.f = NUM2DBL(*intermediate);
115
+ write_nbo32(swap4.i, out);
116
+ }else{
117
+ *intermediate = value;
118
+ }
119
+ return 4;
120
+ }
121
+
122
+ /*
123
+ * Document-class: PG::BinaryEncoder::Float8 < PG::SimpleEncoder
124
+ *
125
+ * This is the binary encoder class for the PostgreSQL +float8+ type.
126
+ *
127
+ */
128
+ static int
129
+ pg_bin_enc_float8(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx)
130
+ {
131
+ union {
132
+ double f;
133
+ int64_t i;
134
+ } swap8;
135
+
136
+ if(out){
137
+ swap8.f = NUM2DBL(*intermediate);
138
+ write_nbo64(swap8.i, out);
139
+ }else{
140
+ *intermediate = value;
141
+ }
142
+ return 8;
143
+ }
144
+
145
+ #define PG_INT32_MIN (-0x7FFFFFFF-1)
146
+ #define PG_INT32_MAX (0x7FFFFFFF)
147
+ #define PG_INT64_MIN (-0x7FFFFFFFFFFFFFFFL - 1)
148
+ #define PG_INT64_MAX 0x7FFFFFFFFFFFFFFFL
149
+
150
+ /*
151
+ * Document-class: PG::BinaryEncoder::Timestamp < PG::SimpleEncoder
152
+ *
153
+ * This is a encoder class for conversion of Ruby Time objects to PostgreSQL binary timestamps.
154
+ *
155
+ * The following flags can be used to specify timezone interpretation:
156
+ * * +PG::Coder::TIMESTAMP_DB_UTC+ : Send timestamp as UTC time (default)
157
+ * * +PG::Coder::TIMESTAMP_DB_LOCAL+ : Send timestamp as local time (slower)
158
+ *
159
+ * Example:
160
+ * enco = PG::BinaryEncoder::Timestamp.new(flags: PG::Coder::TIMESTAMP_DB_UTC)
161
+ * enco.encode(Time.utc(2000, 1, 1)) # => "\x00\x00\x00\x00\x00\x00\x00\x00"
162
+ *
163
+ * String values are expected to contain a binary data with a length of 8 byte.
164
+ *
165
+ */
166
+ static int
167
+ pg_bin_enc_timestamp(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, int enc_idx)
168
+ {
169
+ if(out){
170
+ int64_t timestamp;
171
+ struct timespec ts;
172
+
173
+ /* second call -> write data to *out */
174
+ switch(TYPE(*intermediate)){
175
+ case T_STRING:
176
+ return pg_coder_enc_to_s(this, value, out, intermediate, enc_idx);
177
+ case T_TRUE:
178
+ write_nbo64(PG_INT64_MAX, out);
179
+ return 8;
180
+ case T_FALSE:
181
+ write_nbo64(PG_INT64_MIN, out);
182
+ return 8;
183
+ }
184
+
185
+ ts = rb_time_timespec(*intermediate);
186
+ /* PostgreSQL's timestamp is based on year 2000 and Ruby's time is based on 1970.
187
+ * Adjust the 30 years difference. */
188
+ timestamp = ((int64_t)ts.tv_sec - 10957L * 24L * 3600L) * 1000000 + ((int64_t)ts.tv_nsec / 1000);
189
+
190
+ if( this->flags & PG_CODER_TIMESTAMP_DB_LOCAL ) {
191
+ /* send as local time */
192
+ timestamp += NUM2LL(rb_funcall(*intermediate, rb_intern("utc_offset"), 0)) * 1000000;
193
+ }
194
+
195
+ write_nbo64(timestamp, out);
196
+ }else{
197
+ /* first call -> determine the required length */
198
+ if(TYPE(value) == T_STRING){
199
+ char *pstr = RSTRING_PTR(value);
200
+ if(RSTRING_LEN(value) >= 1){
201
+ switch(pstr[0]) {
202
+ case 'I':
203
+ case 'i':
204
+ *intermediate = Qtrue;
205
+ return 8;
206
+ case '-':
207
+ if (RSTRING_LEN(value) >= 2 && (pstr[1] == 'I' || pstr[1] == 'i')) {
208
+ *intermediate = Qfalse;
209
+ return 8;
210
+ }
211
+ }
212
+ }
213
+
214
+ return pg_coder_enc_to_s(this, value, out, intermediate, enc_idx);
215
+ }
216
+
217
+ if( this->flags & PG_CODER_TIMESTAMP_DB_LOCAL ) {
218
+ /* make a local time, so that utc_offset is set */
219
+ value = rb_funcall(value, rb_intern("getlocal"), 0);
220
+ }
221
+ *intermediate = value;
222
+ }
223
+ return 8;
224
+ }
225
+
226
+ #define POSTGRES_EPOCH_JDATE 2451545 /* == date2j(2000, 1, 1) */
227
+ int
228
+ date2j(int year, int month, int day)
229
+ {
230
+ int julian;
231
+ int century;
232
+
233
+ if (month > 2)
234
+ {
235
+ month += 1;
236
+ year += 4800;
237
+ }
238
+ else
239
+ {
240
+ month += 13;
241
+ year += 4799;
242
+ }
243
+
244
+ century = year / 100;
245
+ julian = year * 365 - 32167;
246
+ julian += year / 4 - century + century / 4;
247
+ julian += 7834 * month / 256 + day;
248
+
249
+ return julian;
250
+ } /* date2j() */
251
+
252
+ /*
253
+ * Document-class: PG::BinaryEncoder::Date < PG::SimpleEncoder
254
+ *
255
+ * This is a encoder class for conversion of Ruby Date objects to PostgreSQL binary date.
256
+ *
257
+ * String values are expected to contain a binary data with a length of 4 byte.
258
+ *
259
+ */
260
+ static int
261
+ pg_bin_enc_date(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, int enc_idx)
262
+ {
263
+ if(out){
264
+ /* second call -> write data to *out */
265
+ switch(TYPE(*intermediate)){
266
+ case T_STRING:
267
+ return pg_coder_enc_to_s(this, value, out, intermediate, enc_idx);
268
+ case T_TRUE:
269
+ write_nbo32(PG_INT32_MAX, out);
270
+ return 4;
271
+ case T_FALSE:
272
+ write_nbo32(PG_INT32_MIN, out);
273
+ return 4;
274
+ } {
275
+ VALUE year = rb_funcall(value, s_id_year, 0);
276
+ VALUE month = rb_funcall(value, s_id_month, 0);
277
+ VALUE day = rb_funcall(value, s_id_day, 0);
278
+ int jday = date2j(NUM2INT(year), NUM2INT(month), NUM2INT(day)) - POSTGRES_EPOCH_JDATE;
279
+ write_nbo32(jday, out);
280
+ }
281
+ }else{
282
+ /* first call -> determine the required length */
283
+ if(TYPE(value) == T_STRING){
284
+ char *pstr = RSTRING_PTR(value);
285
+ if(RSTRING_LEN(value) >= 1){
286
+ switch(pstr[0]) {
287
+ case 'I':
288
+ case 'i':
289
+ *intermediate = Qtrue;
290
+ return 4;
291
+ case '-':
292
+ if (RSTRING_LEN(value) >= 2 && (pstr[1] == 'I' || pstr[1] == 'i')) {
293
+ *intermediate = Qfalse;
294
+ return 4;
295
+ }
296
+ }
297
+ }
298
+
299
+ return pg_coder_enc_to_s(this, value, out, intermediate, enc_idx);
300
+ }
301
+
302
+ *intermediate = value;
303
+ }
304
+ return 4;
305
+ }
306
+
307
+ /*
308
+ * Maximum number of array subscripts (arbitrary limit)
309
+ */
310
+ #define MAXDIM 6
311
+
312
+ /*
313
+ * Document-class: PG::BinaryEncoder::Array < PG::CompositeEncoder
314
+ *
315
+ * This is the encoder class for PostgreSQL array types in binary format.
316
+ *
317
+ * All values are encoded according to the #elements_type
318
+ * accessor. Sub-arrays are encoded recursively.
319
+ *
320
+ * This encoder expects an Array of values or sub-arrays as input.
321
+ * Other values are passed through as byte string without interpretation.
322
+ *
323
+ * It is possible to enforce a number of dimensions to be encoded by #dimensions= .
324
+ * Deeper nested arrays are then passed to the elements encoder and less nested arrays raise an ArgumentError.
325
+ *
326
+ * The accessors needs_quotation and delimiter are ignored for binary encoding.
327
+ *
328
+ */
329
+ static int
330
+ pg_bin_enc_array(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx)
331
+ {
332
+ if (TYPE(value) == T_ARRAY) {
333
+ t_pg_composite_coder *this = (t_pg_composite_coder *)conv;
334
+ t_pg_coder_enc_func enc_func = pg_coder_enc_func(this->elem);
335
+ int dim_sizes[MAXDIM];
336
+ int ndim = 1;
337
+ int nitems = 1;
338
+ VALUE el1 = value;
339
+
340
+ if (RARRAY_LEN(value) == 0) {
341
+ nitems = 0;
342
+ ndim = 0;
343
+ dim_sizes[0] = 0;
344
+ } else {
345
+ /* Determine number of dimensions, sizes of dimensions and number of items */
346
+ while(1) {
347
+ VALUE el2;
348
+
349
+ dim_sizes[ndim-1] = RARRAY_LENINT(el1);
350
+ nitems *= dim_sizes[ndim-1];
351
+ el2 = rb_ary_entry(el1, 0);
352
+ if ( (this->dimensions < 0 || ndim < this->dimensions) &&
353
+ TYPE(el2) == T_ARRAY) {
354
+ ndim++;
355
+ if (ndim > MAXDIM)
356
+ rb_raise( rb_eArgError, "unsupported number of array dimensions: >%d", ndim );
357
+ } else {
358
+ break;
359
+ }
360
+ el1 = el2;
361
+ }
362
+ }
363
+ if( this->dimensions >= 0 && (ndim==0 ? 1 : ndim) != this->dimensions ){
364
+ rb_raise(rb_eArgError, "less array dimensions to encode (%d) than expected (%d)", ndim, this->dimensions);
365
+ }
366
+
367
+ if(out){
368
+ /* Second encoder pass -> write data to `out` */
369
+ int dimpos[MAXDIM];
370
+ VALUE arrays[MAXDIM];
371
+ int dim = 0;
372
+ int item_idx = 0;
373
+ int i;
374
+ char *orig_out = out;
375
+ Oid elem_oid = this->elem ? this->elem->oid : 0;
376
+
377
+ write_nbo32(ndim, out); out += 4;
378
+ write_nbo32(1 /* flags */, out); out += 4;
379
+ write_nbo32(elem_oid, out); out += 4;
380
+ for (i = 0; i < ndim; i++) {
381
+ dimpos[i] = 0;
382
+ write_nbo32(dim_sizes[i], out); out += 4;
383
+ write_nbo32(1 /* offset */, out); out += 4;
384
+ }
385
+ arrays[0] = value;
386
+
387
+ while(1) {
388
+ /* traverse tree down */
389
+ while (dim < ndim - 1) {
390
+ arrays[dim + 1] = rb_ary_entry(arrays[dim], dimpos[dim]);
391
+ dim++;
392
+ }
393
+
394
+ for (i = 0; i < dim_sizes[dim]; i++) {
395
+ VALUE item = rb_ary_entry(arrays[dim], i);
396
+
397
+ if (NIL_P(item)) {
398
+ write_nbo32(-1, out); out += 4;
399
+ } else {
400
+ /* Encoded string is returned in subint */
401
+ int strlen;
402
+ VALUE is_one_pass = rb_ary_entry(*intermediate, item_idx++);
403
+ VALUE subint = rb_ary_entry(*intermediate, item_idx++);
404
+
405
+ if (is_one_pass == Qtrue) {
406
+ strlen = RSTRING_LENINT(subint);
407
+ memcpy( out + 4, RSTRING_PTR(subint), strlen);
408
+ } else {
409
+ strlen = enc_func(this->elem, item, out + 4, &subint, enc_idx);
410
+ }
411
+ write_nbo32(strlen, out);
412
+ out += 4 /* length */ + strlen;
413
+ }
414
+ }
415
+
416
+ /* traverse tree up and go to next sibling array */
417
+ do {
418
+ if (dim > 0) {
419
+ dimpos[dim] = 0;
420
+ dim--;
421
+ dimpos[dim]++;
422
+ } else {
423
+ goto finished2;
424
+ }
425
+ } while (dimpos[dim] >= dim_sizes[dim]);
426
+ }
427
+ finished2:
428
+ return (int)(out - orig_out);
429
+
430
+ } else {
431
+ /* First encoder pass -> determine required buffer space for `out` */
432
+
433
+ int dimpos[MAXDIM];
434
+ VALUE arrays[MAXDIM];
435
+ int dim = 0;
436
+ int item_idx = 0;
437
+ int i;
438
+ int size_sum = 0;
439
+
440
+ *intermediate = rb_ary_new2(nitems);
441
+
442
+ for (i = 0; i < MAXDIM; i++) {
443
+ dimpos[i] = 0;
444
+ }
445
+ arrays[0] = value;
446
+
447
+ while(1) {
448
+
449
+ /* traverse tree down */
450
+ while (dim < ndim - 1) {
451
+ VALUE array = rb_ary_entry(arrays[dim], dimpos[dim]);
452
+ if (TYPE(array) != T_ARRAY) {
453
+ rb_raise( rb_eArgError, "expected Array instead of %+"PRIsVALUE" in dimension %d", array, dim + 1 );
454
+ }
455
+ if (dim_sizes[dim + 1] != RARRAY_LEN(array)) {
456
+ rb_raise( rb_eArgError, "varying number of array elements (%d and %d) in dimension %d", dim_sizes[dim + 1], RARRAY_LENINT(array), dim + 1 );
457
+ }
458
+ arrays[dim + 1] = array;
459
+ dim++;
460
+ }
461
+
462
+ for (i = 0; i < dim_sizes[dim]; i++) {
463
+ VALUE item = rb_ary_entry(arrays[dim], i);
464
+
465
+ if (NIL_P(item)) {
466
+ size_sum += 4 /* length bytes = -1 */;
467
+ } else {
468
+ VALUE subint;
469
+ int strlen = enc_func(this->elem, item, NULL, &subint, enc_idx);
470
+
471
+ /* Gather all intermediate values of elements into an array, which is returned as intermediate for the array encoder */
472
+ if( strlen == -1 ){
473
+ /* Encoded string is returned in subint */
474
+ rb_ary_store(*intermediate, item_idx++, Qtrue);
475
+ rb_ary_store(*intermediate, item_idx++, subint);
476
+
477
+ strlen = RSTRING_LENINT(subint);
478
+ } else {
479
+ /* Two passes necessary */
480
+ rb_ary_store(*intermediate, item_idx++, Qfalse);
481
+ rb_ary_store(*intermediate, item_idx++, subint);
482
+ }
483
+ size_sum += 4 /* length bytes */ + strlen;
484
+ }
485
+ }
486
+
487
+ /* traverse tree up and go to next sibling array */
488
+ do {
489
+ if (dim > 0) {
490
+ dimpos[dim] = 0;
491
+ dim--;
492
+ dimpos[dim]++;
493
+ } else {
494
+ goto finished1;
495
+ }
496
+ } while (dimpos[dim] >= dim_sizes[dim]);
497
+ }
498
+ finished1:;
499
+
500
+ return 4 /* ndim */ + 4 /* flags */ + 4 /* oid */ +
501
+ ndim * (4 /* dim size */ + 4 /* dim offset */) +
502
+ size_sum;
503
+ }
504
+ } else {
505
+ return pg_coder_enc_to_s( conv, value, out, intermediate, enc_idx );
506
+ }
507
+ }
508
+
96
509
  /*
97
510
  * Document-class: PG::BinaryEncoder::FromBase64 < PG::CompositeEncoder
98
511
  *
@@ -141,6 +554,10 @@ pg_bin_enc_from_base64(t_pg_coder *conv, VALUE value, char *out, VALUE *intermed
141
554
  void
142
555
  init_pg_binary_encoder(void)
143
556
  {
557
+ s_id_year = rb_intern("year");
558
+ s_id_month = rb_intern("month");
559
+ s_id_day = rb_intern("day");
560
+
144
561
  /* This module encapsulates all encoder classes with binary output format */
145
562
  rb_mPG_BinaryEncoder = rb_define_module_under( rb_mPG, "BinaryEncoder" );
146
563
 
@@ -153,11 +570,21 @@ init_pg_binary_encoder(void)
153
570
  pg_define_coder( "Int4", pg_bin_enc_int4, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder );
154
571
  /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Int8", rb_cPG_SimpleEncoder ); */
155
572
  pg_define_coder( "Int8", pg_bin_enc_int8, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder );
573
+ /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Float4", rb_cPG_SimpleEncoder ); */
574
+ pg_define_coder( "Float4", pg_bin_enc_float4, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder );
575
+ /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Float8", rb_cPG_SimpleEncoder ); */
576
+ pg_define_coder( "Float8", pg_bin_enc_float8, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder );
156
577
  /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "String", rb_cPG_SimpleEncoder ); */
157
578
  pg_define_coder( "String", pg_coder_enc_to_s, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder );
158
579
  /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Bytea", rb_cPG_SimpleEncoder ); */
159
580
  pg_define_coder( "Bytea", pg_coder_enc_to_s, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder );
581
+ /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Timestamp", rb_cPG_SimpleEncoder ); */
582
+ pg_define_coder( "Timestamp", pg_bin_enc_timestamp, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder );
583
+ /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Date", rb_cPG_SimpleEncoder ); */
584
+ pg_define_coder( "Date", pg_bin_enc_date, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder );
160
585
 
586
+ /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Array", rb_cPG_CompositeEncoder ); */
587
+ pg_define_coder( "Array", pg_bin_enc_array, rb_cPG_CompositeEncoder, rb_mPG_BinaryEncoder );
161
588
  /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "FromBase64", rb_cPG_CompositeEncoder ); */
162
589
  pg_define_coder( "FromBase64", pg_bin_enc_from_base64, rb_cPG_CompositeEncoder, rb_mPG_BinaryEncoder );
163
590
  }