pg 1.0.0 → 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 (126) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/Gemfile +20 -0
  4. data/History.md +932 -0
  5. data/Manifest.txt +8 -3
  6. data/README-Windows.rdoc +4 -4
  7. data/README.ja.md +300 -0
  8. data/README.md +286 -0
  9. data/Rakefile +41 -138
  10. data/Rakefile.cross +71 -66
  11. data/certs/ged.pem +24 -0
  12. data/certs/kanis@comcard.de.pem +20 -0
  13. data/certs/larskanis-2022.pem +26 -0
  14. data/certs/larskanis-2023.pem +24 -0
  15. data/certs/larskanis-2024.pem +24 -0
  16. data/ext/errorcodes.def +84 -5
  17. data/ext/errorcodes.rb +1 -1
  18. data/ext/errorcodes.txt +23 -6
  19. data/ext/extconf.rb +109 -25
  20. data/ext/gvl_wrappers.c +4 -0
  21. data/ext/gvl_wrappers.h +23 -0
  22. data/ext/pg.c +213 -155
  23. data/ext/pg.h +89 -23
  24. data/ext/pg_binary_decoder.c +164 -16
  25. data/ext/pg_binary_encoder.c +238 -13
  26. data/ext/pg_coder.c +159 -35
  27. data/ext/pg_connection.c +1584 -967
  28. data/ext/pg_copy_coder.c +373 -43
  29. data/ext/pg_errors.c +1 -1
  30. data/ext/pg_record_coder.c +522 -0
  31. data/ext/pg_result.c +710 -217
  32. data/ext/pg_text_decoder.c +630 -43
  33. data/ext/pg_text_encoder.c +222 -72
  34. data/ext/pg_tuple.c +572 -0
  35. data/ext/pg_type_map.c +45 -11
  36. data/ext/pg_type_map_all_strings.c +21 -7
  37. data/ext/pg_type_map_by_class.c +59 -27
  38. data/ext/pg_type_map_by_column.c +80 -37
  39. data/ext/pg_type_map_by_mri_type.c +49 -20
  40. data/ext/pg_type_map_by_oid.c +62 -29
  41. data/ext/pg_type_map_in_ruby.c +56 -22
  42. data/ext/{util.c → pg_util.c} +12 -12
  43. data/ext/{util.h → pg_util.h} +2 -2
  44. data/lib/pg/basic_type_map_based_on_result.rb +67 -0
  45. data/lib/pg/basic_type_map_for_queries.rb +202 -0
  46. data/lib/pg/basic_type_map_for_results.rb +104 -0
  47. data/lib/pg/basic_type_registry.rb +311 -0
  48. data/lib/pg/binary_decoder/date.rb +9 -0
  49. data/lib/pg/binary_decoder/timestamp.rb +26 -0
  50. data/lib/pg/binary_encoder/timestamp.rb +20 -0
  51. data/lib/pg/coder.rb +36 -13
  52. data/lib/pg/connection.rb +769 -70
  53. data/lib/pg/exceptions.rb +22 -2
  54. data/lib/pg/result.rb +14 -2
  55. data/lib/pg/text_decoder/date.rb +21 -0
  56. data/lib/pg/text_decoder/inet.rb +9 -0
  57. data/lib/pg/text_decoder/json.rb +17 -0
  58. data/lib/pg/text_decoder/numeric.rb +9 -0
  59. data/lib/pg/text_decoder/timestamp.rb +30 -0
  60. data/lib/pg/text_encoder/date.rb +13 -0
  61. data/lib/pg/text_encoder/inet.rb +31 -0
  62. data/lib/pg/text_encoder/json.rb +17 -0
  63. data/lib/pg/text_encoder/numeric.rb +9 -0
  64. data/lib/pg/text_encoder/timestamp.rb +24 -0
  65. data/lib/pg/tuple.rb +30 -0
  66. data/lib/pg/type_map_by_column.rb +3 -2
  67. data/lib/pg/version.rb +4 -0
  68. data/lib/pg.rb +106 -39
  69. data/misc/openssl-pg-segfault.rb +31 -0
  70. data/misc/postgres/History.txt +9 -0
  71. data/misc/postgres/Manifest.txt +5 -0
  72. data/misc/postgres/README.txt +21 -0
  73. data/misc/postgres/Rakefile +21 -0
  74. data/misc/postgres/lib/postgres.rb +16 -0
  75. data/misc/ruby-pg/History.txt +9 -0
  76. data/misc/ruby-pg/Manifest.txt +5 -0
  77. data/misc/ruby-pg/README.txt +21 -0
  78. data/misc/ruby-pg/Rakefile +21 -0
  79. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  80. data/pg.gemspec +36 -0
  81. data/rakelib/task_extension.rb +46 -0
  82. data/sample/array_insert.rb +20 -0
  83. data/sample/async_api.rb +102 -0
  84. data/sample/async_copyto.rb +39 -0
  85. data/sample/async_mixed.rb +56 -0
  86. data/sample/check_conn.rb +21 -0
  87. data/sample/copydata.rb +71 -0
  88. data/sample/copyfrom.rb +81 -0
  89. data/sample/copyto.rb +19 -0
  90. data/sample/cursor.rb +21 -0
  91. data/sample/disk_usage_report.rb +177 -0
  92. data/sample/issue-119.rb +94 -0
  93. data/sample/losample.rb +69 -0
  94. data/sample/minimal-testcase.rb +17 -0
  95. data/sample/notify_wait.rb +72 -0
  96. data/sample/pg_statistics.rb +285 -0
  97. data/sample/replication_monitor.rb +222 -0
  98. data/sample/test_binary_values.rb +33 -0
  99. data/sample/wal_shipper.rb +434 -0
  100. data/sample/warehouse_partitions.rb +311 -0
  101. data.tar.gz.sig +0 -0
  102. metadata +138 -223
  103. metadata.gz.sig +0 -0
  104. data/.gemtest +0 -0
  105. data/ChangeLog +0 -6595
  106. data/History.rdoc +0 -422
  107. data/README.ja.rdoc +0 -14
  108. data/README.rdoc +0 -167
  109. data/lib/pg/basic_type_mapping.rb +0 -426
  110. data/lib/pg/constants.rb +0 -11
  111. data/lib/pg/text_decoder.rb +0 -51
  112. data/lib/pg/text_encoder.rb +0 -35
  113. data/spec/data/expected_trace.out +0 -26
  114. data/spec/data/random_binary_data +0 -0
  115. data/spec/helpers.rb +0 -348
  116. data/spec/pg/basic_type_mapping_spec.rb +0 -305
  117. data/spec/pg/connection_spec.rb +0 -1719
  118. data/spec/pg/result_spec.rb +0 -456
  119. data/spec/pg/type_map_by_class_spec.rb +0 -138
  120. data/spec/pg/type_map_by_column_spec.rb +0 -222
  121. data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
  122. data/spec/pg/type_map_by_oid_spec.rb +0 -149
  123. data/spec/pg/type_map_in_ruby_spec.rb +0 -164
  124. data/spec/pg/type_map_spec.rb +0 -22
  125. data/spec/pg/type_spec.rb +0 -777
  126. data/spec/pg_spec.rb +0 -50
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_text_encoder.c - PG::TextEncoder module
3
- * $Id: pg_text_encoder.c,v e61a06f1f5ed 2015/12/25 21:14:21 lars $
3
+ * $Id$
4
4
  *
5
5
  */
6
6
 
@@ -41,7 +41,7 @@
41
41
 
42
42
 
43
43
  #include "pg.h"
44
- #include "util.h"
44
+ #include "pg_util.h"
45
45
  #ifdef HAVE_INTTYPES_H
46
46
  #include <inttypes.h>
47
47
  #endif
@@ -50,6 +50,9 @@
50
50
  VALUE rb_mPG_TextEncoder;
51
51
  static ID s_id_encode;
52
52
  static ID s_id_to_i;
53
+ static ID s_id_to_s;
54
+ static ID s_cBigDecimal;
55
+ static VALUE s_str_F;
53
56
 
54
57
  static int pg_text_enc_integer(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, int enc_idx);
55
58
 
@@ -116,6 +119,10 @@ pg_text_enc_boolean(t_pg_coder *this, VALUE value, char *out, VALUE *intermediat
116
119
  int
117
120
  pg_coder_enc_to_s(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, int enc_idx)
118
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
+ */
119
126
  VALUE str = rb_obj_as_string(value);
120
127
  if( ENCODING_GET(str) == enc_idx ){
121
128
  *intermediate = str;
@@ -125,11 +132,29 @@ pg_coder_enc_to_s(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate,
125
132
  return -1;
126
133
  }
127
134
 
135
+ static int
136
+ count_leading_zero_bits(unsigned long long x)
137
+ {
138
+ #if defined(__GNUC__) || defined(__clang__)
139
+ return __builtin_clzll(x);
140
+ #elif defined(_MSC_VER)
141
+ DWORD r = 0;
142
+ _BitScanForward64(&r, x);
143
+ return (int)r;
144
+ #else
145
+ unsigned int a;
146
+ for(a=0; a < sizeof(unsigned long long) * 8; a++){
147
+ if( x & (1 << (sizeof(unsigned long long) * 8 - 1))) return a;
148
+ x <<= 1;
149
+ }
150
+ return a;
151
+ #endif
152
+ }
128
153
 
129
154
  /*
130
155
  * Document-class: PG::TextEncoder::Integer < PG::SimpleEncoder
131
156
  *
132
- * This is the encoder class for the PostgreSQL int types.
157
+ * This is the encoder class for the PostgreSQL integer types.
133
158
  *
134
159
  * Non-Integer values are expected to have method +to_i+ defined.
135
160
  *
@@ -144,20 +169,23 @@ pg_text_enc_integer(t_pg_coder *this, VALUE value, char *out, VALUE *intermediat
144
169
  char *start = out;
145
170
  int len;
146
171
  int neg = 0;
147
- long long ll = NUM2LL(*intermediate);
172
+ long long sll = NUM2LL(*intermediate);
173
+ unsigned long long ll;
148
174
 
149
- if (ll < 0) {
150
- /* We don't expect problems with the most negative integer not being representable
151
- * as a positive integer, because Fixnum is only up to 63 bits.
175
+ if (sll < 0) {
176
+ /* Avoid problems with the most negative integer not being representable
177
+ * as a positive integer, by using unsigned long long for encoding.
152
178
  */
153
- ll = -ll;
179
+ ll = -sll;
154
180
  neg = 1;
181
+ } else {
182
+ ll = sll;
155
183
  }
156
184
 
157
185
  /* Compute the result string backwards. */
158
186
  do {
159
- long long remainder;
160
- long long oldval = ll;
187
+ unsigned long long remainder;
188
+ unsigned long long oldval = ll;
161
189
 
162
190
  ll /= 10;
163
191
  remainder = oldval - ll * 10;
@@ -167,7 +195,7 @@ pg_text_enc_integer(t_pg_coder *this, VALUE value, char *out, VALUE *intermediat
167
195
  if (neg)
168
196
  *out++ = '-';
169
197
 
170
- len = out - start;
198
+ len = (int)(out - start);
171
199
 
172
200
  /* Reverse string. */
173
201
  out--;
@@ -184,45 +212,17 @@ pg_text_enc_integer(t_pg_coder *this, VALUE value, char *out, VALUE *intermediat
184
212
  }else{
185
213
  *intermediate = pg_obj_to_i(value);
186
214
  if(TYPE(*intermediate) == T_FIXNUM){
187
- int len;
188
215
  long long sll = NUM2LL(*intermediate);
189
- long long ll = sll < 0 ? -sll : sll;
190
- if( ll < 100000000 ){
191
- if( ll < 10000 ){
192
- if( ll < 100 ){
193
- len = ll < 10 ? 1 : 2;
194
- }else{
195
- len = ll < 1000 ? 3 : 4;
196
- }
197
- }else{
198
- if( ll < 1000000 ){
199
- len = ll < 100000 ? 5 : 6;
200
- }else{
201
- len = ll < 10000000 ? 7 : 8;
202
- }
203
- }
204
- }else{
205
- if( ll < 1000000000000LL ){
206
- if( ll < 10000000000LL ){
207
- len = ll < 1000000000LL ? 9 : 10;
208
- }else{
209
- len = ll < 100000000000LL ? 11 : 12;
210
- }
211
- }else{
212
- if( ll < 100000000000000LL ){
213
- len = ll < 10000000000000LL ? 13 : 14;
214
- }else{
215
- return pg_coder_enc_to_s(this, *intermediate, NULL, intermediate, enc_idx);
216
- }
217
- }
218
- }
219
- return sll < 0 ? len+1 : len;
216
+ unsigned long long ll = sll < 0 ? -sll : sll;
217
+ int len = (sizeof(unsigned long long) * 8 - count_leading_zero_bits(ll)) / 3;
218
+ return sll < 0 ? len+2 : len+1;
220
219
  }else{
221
220
  return pg_coder_enc_to_s(this, *intermediate, NULL, intermediate, enc_idx);
222
221
  }
223
222
  }
224
223
  }
225
224
 
225
+ #define MAX_DOUBLE_DIGITS 16
226
226
 
227
227
  /*
228
228
  * Document-class: PG::TextEncoder::Float < PG::SimpleEncoder
@@ -235,6 +235,12 @@ pg_text_enc_float(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate,
235
235
  {
236
236
  if(out){
237
237
  double dvalue = NUM2DBL(value);
238
+ int len = 0;
239
+ int neg = 0;
240
+ int exp2i, exp10i, i;
241
+ unsigned long long ll, remainder, oldval;
242
+ VALUE intermediate;
243
+
238
244
  /* Cast to the same strings as value.to_s . */
239
245
  if( isinf(dvalue) ){
240
246
  if( dvalue < 0 ){
@@ -248,12 +254,145 @@ pg_text_enc_float(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate,
248
254
  memcpy( out, "NaN", 3);
249
255
  return 3;
250
256
  }
251
- return sprintf( out, "%.16E", dvalue);
257
+
258
+ /*
259
+ * The following computation is roughly a conversion kind of
260
+ * sprintf( out, "%.16E", dvalue);
261
+ */
262
+
263
+ /* write the algebraic sign */
264
+ if( dvalue < 0 ) {
265
+ dvalue = -dvalue;
266
+ *out++ = '-';
267
+ neg++;
268
+ }
269
+
270
+ /* retrieve the power of 2 exponent */
271
+ frexp(dvalue, &exp2i);
272
+ /* compute the power of 10 exponent */
273
+ exp10i = (int)floor(exp2i * 0.30102999566398114); /* Math.log(2)/Math.log(10) */
274
+ /* move the decimal point, so that we get an integer of MAX_DOUBLE_DIGITS decimal digits */
275
+ ll = (unsigned long long)(dvalue * pow(10, MAX_DOUBLE_DIGITS - 1 - exp10i) + 0.5);
276
+
277
+ /* avoid leading zeros due to inaccuracy of deriving exp10i from exp2i */
278
+ /* otherwise we would print "09.0" instead of "9.0" */
279
+ if( ll < 1000000000000000 ){ /* pow(10, MAX_DOUBLE_DIGITS-1) */
280
+ exp10i--;
281
+ ll *= 10;
282
+ }
283
+
284
+ if( exp10i <= -5 || exp10i >= 15 ) {
285
+ /* Write the float in exponent format (1.23e45) */
286
+
287
+ /* write fraction digits from right to left */
288
+ for( i = MAX_DOUBLE_DIGITS; i > 1; i--){
289
+ oldval = ll;
290
+ ll /= 10;
291
+ remainder = oldval - ll * 10;
292
+ /* omit trailing zeros */
293
+ if(remainder != 0 || len ) {
294
+ out[i] = '0' + remainder;
295
+ len++;
296
+ }
297
+ }
298
+
299
+ /* write decimal point */
300
+ if( len ){
301
+ out[1] = '.';
302
+ len++;
303
+ }
304
+
305
+ /* write remaining single digit left to the decimal point */
306
+ oldval = ll;
307
+ ll /= 10;
308
+ remainder = oldval - ll * 10;
309
+ out[0] = '0' + remainder;
310
+ len++;
311
+
312
+ /* write exponent */
313
+ out[len++] = 'e';
314
+ intermediate = INT2NUM(exp10i);
315
+
316
+ return neg + len + pg_text_enc_integer(conv, Qnil, out + len, &intermediate, enc_idx);
317
+ } else {
318
+ /* write the float in non exponent format (0.001234 or 123450.0) */
319
+
320
+ /* write digits from right to left */
321
+ int lz = exp10i < 0 ? 0 : exp10i;
322
+ for( i = MAX_DOUBLE_DIGITS - (exp10i < 0 ? exp10i : 0); i >= 0; i-- ){
323
+ oldval = ll;
324
+ ll /= 10;
325
+ remainder = oldval - ll * 10;
326
+ /* write decimal point */
327
+ if( i - 1 == lz ){
328
+ out[i--] = '.';
329
+ len++;
330
+ }
331
+ /* if possible then omit trailing zeros */
332
+ if(remainder != 0 || len || i - 2 == lz) {
333
+ out[i] = '0' + remainder;
334
+ len++;
335
+ }
336
+ }
337
+ return neg + len;
338
+ }
252
339
  }else{
253
- return 23;
340
+ return 1 /*sign*/ + MAX_DOUBLE_DIGITS + 1 /*dot*/ + 1 /*e*/ + 1 /*exp sign*/ + 3 /*exp digits*/;
341
+ }
342
+ }
343
+
344
+
345
+ /*
346
+ * Document-class: PG::TextEncoder::Numeric < PG::SimpleEncoder
347
+ *
348
+ * This is the encoder class for the PostgreSQL numeric types.
349
+ *
350
+ * It converts Integer, Float and BigDecimal objects.
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.
354
+ */
355
+ static int
356
+ pg_text_enc_numeric(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, int enc_idx)
357
+ {
358
+ switch(TYPE(value)){
359
+ case T_FIXNUM:
360
+ case T_BIGNUM:
361
+ return pg_text_enc_integer(this, value, out, intermediate, enc_idx);
362
+ case T_FLOAT:
363
+ return pg_text_enc_float(this, value, out, intermediate, enc_idx);
364
+ default:
365
+ if(out){ /* second pass */
366
+ rb_bug("unexpected value type: %d", TYPE(value));
367
+ } else { /* first pass */
368
+ if( rb_obj_is_kind_of(value, s_cBigDecimal) ){
369
+ /* value.to_s('F') */
370
+ *intermediate = rb_funcall(value, s_id_to_s, 1, s_str_F);
371
+ return -1; /* no second pass */
372
+ } else {
373
+ return pg_coder_enc_to_s(this, value, NULL, intermediate, enc_idx);
374
+ /* no second pass */
375
+ }
376
+ }
254
377
  }
255
378
  }
256
379
 
380
+ /* called per autoload when TextEncoder::Numeric is used */
381
+ static VALUE
382
+ init_pg_text_encoder_numeric(VALUE rb_mPG_TextDecoder)
383
+ {
384
+ s_str_F = rb_str_freeze(rb_str_new_cstr("F"));
385
+ rb_global_variable(&s_str_F);
386
+ rb_funcall(rb_mPG, rb_intern("require_bigdecimal_without_warning"), 0);
387
+ s_cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
388
+
389
+ /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Numeric", rb_cPG_SimpleEncoder ); */
390
+ pg_define_coder( "Numeric", pg_text_enc_numeric, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
391
+
392
+ return Qnil;
393
+ }
394
+
395
+
257
396
  static const char hextab[] = {
258
397
  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
259
398
  };
@@ -261,13 +400,16 @@ static const char hextab[] = {
261
400
  /*
262
401
  * Document-class: PG::TextEncoder::Bytea < PG::SimpleEncoder
263
402
  *
264
- * This is an encoder class for the PostgreSQL bytea type for server version 9.0
265
- * or newer.
403
+ * This is an encoder class for the PostgreSQL +bytea+ type.
266
404
  *
267
405
  * The binary String is converted to hexadecimal representation for transmission
268
406
  * in text format. For query bind parameters it is recommended to use
269
- * PG::BinaryEncoder::Bytea instead, in order to decrease network traffic and
270
- * CPU usage.
407
+ * PG::BinaryEncoder::Bytea or the hash form <tt>{value: binary_string, format: 1}</tt> instead,
408
+ * in order to decrease network traffic and CPU usage.
409
+ * See PG::Connection#exec_params for using the hash form.
410
+ *
411
+ * This encoder is particular useful when PG::TextEncoder::CopyRow is used with the COPY command.
412
+ * In this case there's no way to change the format of a single column to binary, so that the data have to be converted to bytea hex representation.
271
413
  *
272
414
  */
273
415
  static int
@@ -286,11 +428,11 @@ pg_text_enc_bytea(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate,
286
428
  *optr++ = hextab[c >> 4];
287
429
  *optr++ = hextab[c & 0xf];
288
430
  }
289
- return optr - out;
431
+ return (int)(optr - out);
290
432
  }else{
291
433
  *intermediate = rb_obj_as_string(value);
292
434
  /* The output starts with "\x" and each character is converted to hex. */
293
- return 2 + RSTRING_LEN(*intermediate) * 2;
435
+ return 2 + RSTRING_LENINT(*intermediate) * 2;
294
436
  }
295
437
  }
296
438
 
@@ -301,7 +443,7 @@ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
301
443
  t_pg_composite_coder *this = _this;
302
444
  char *ptr1;
303
445
  char *ptr2;
304
- int backslashs = 0;
446
+ int backslashes = 0;
305
447
  int needquote;
306
448
 
307
449
  /* count data plus backslashes; detect chars needing quotes */
@@ -318,7 +460,7 @@ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
318
460
 
319
461
  if (ch == '"' || ch == '\\'){
320
462
  needquote = 1;
321
- backslashs++;
463
+ backslashes++;
322
464
  } else if (ch == '{' || ch == '}' || ch == this->delimiter ||
323
465
  ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f'){
324
466
  needquote = 1;
@@ -327,12 +469,12 @@ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
327
469
 
328
470
  if( needquote ){
329
471
  ptr1 = p_in + strlen;
330
- ptr2 = p_out + strlen + backslashs + 2;
472
+ ptr2 = p_out + strlen + backslashes + 2;
331
473
  /* Write end quote */
332
474
  *--ptr2 = '"';
333
475
 
334
476
  /* Then store the escaped string on the final position, walking
335
- * right to left, until all backslashs are placed. */
477
+ * right to left, until all backslashes are placed. */
336
478
  while( ptr1 != p_in ) {
337
479
  *--ptr2 = *--ptr1;
338
480
  if(*ptr2 == '"' || *ptr2 == '\\'){
@@ -341,7 +483,7 @@ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
341
483
  }
342
484
  /* Write start quote */
343
485
  *p_out = '"';
344
- return strlen + backslashs + 2;
486
+ return strlen + backslashes + 2;
345
487
  } else {
346
488
  if( p_in != p_out )
347
489
  memcpy( p_out, p_in, strlen );
@@ -468,20 +610,19 @@ pg_text_enc_array(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate,
468
610
  static char *
469
611
  quote_identifier( VALUE value, VALUE out_string, char *current_out ){
470
612
  char *p_in = RSTRING_PTR(value);
471
- char *ptr1;
472
613
  size_t strlen = RSTRING_LEN(value);
614
+ char *p_inend = p_in + strlen;
473
615
  char *end_capa = current_out;
474
616
 
475
617
  PG_RB_STR_ENSURE_CAPA( out_string, strlen + 2, current_out, end_capa );
476
618
  *current_out++ = '"';
477
- for(ptr1 = p_in; ptr1 != p_in + strlen; ptr1++) {
478
- char c = *ptr1;
619
+ for(; p_in != p_inend; p_in++) {
620
+ char c = *p_in;
479
621
  if (c == '"'){
480
- strlen++;
481
- PG_RB_STR_ENSURE_CAPA( out_string, p_in - ptr1 + strlen + 1, current_out, end_capa );
622
+ PG_RB_STR_ENSURE_CAPA( out_string, p_inend - p_in + 2, current_out, end_capa );
482
623
  *current_out++ = '"';
483
624
  } else if (c == 0){
484
- break;
625
+ rb_raise(rb_eArgError, "string contains null byte");
485
626
  }
486
627
  *current_out++ = c;
487
628
  }
@@ -494,8 +635,8 @@ quote_identifier( VALUE value, VALUE out_string, char *current_out ){
494
635
  static char *
495
636
  pg_text_enc_array_identifier(VALUE value, VALUE string, char *out, int enc_idx)
496
637
  {
497
- int i;
498
- int nr_elems;
638
+ long i;
639
+ long nr_elems;
499
640
 
500
641
  Check_Type(value, T_ARRAY);
501
642
  nr_elems = RARRAY_LEN(value);
@@ -521,7 +662,8 @@ pg_text_enc_array_identifier(VALUE value, VALUE string, char *out, int enc_idx)
521
662
  *
522
663
  * This is the encoder class for PostgreSQL identifiers.
523
664
  *
524
- * An Array value can be used for "schema.table.column" type identifiers:
665
+ * An Array value can be used for identifiers of the kind "schema.table.column".
666
+ * This ensures that each element is properly quoted:
525
667
  * PG::TextEncoder::Identifier.new.encode(['schema', 'table', 'column'])
526
668
  * => '"schema"."table"."column"'
527
669
  *
@@ -556,22 +698,22 @@ static int
556
698
  quote_literal_buffer( void *_this, char *p_in, int strlen, char *p_out ){
557
699
  char *ptr1;
558
700
  char *ptr2;
559
- int backslashs = 0;
701
+ int backslashes = 0;
560
702
 
561
703
  /* count required backlashs */
562
704
  for(ptr1 = p_in; ptr1 != p_in + strlen; ptr1++) {
563
705
  if (*ptr1 == '\''){
564
- backslashs++;
706
+ backslashes++;
565
707
  }
566
708
  }
567
709
 
568
710
  ptr1 = p_in + strlen;
569
- ptr2 = p_out + strlen + backslashs + 2;
711
+ ptr2 = p_out + strlen + backslashes + 2;
570
712
  /* Write end quote */
571
713
  *--ptr2 = '\'';
572
714
 
573
715
  /* Then store the escaped string on the final position, walking
574
- * right to left, until all backslashs are placed. */
716
+ * right to left, until all backslashes are placed. */
575
717
  while( ptr1 != p_in ) {
576
718
  *--ptr2 = *--ptr1;
577
719
  if(*ptr2 == '\''){
@@ -580,7 +722,7 @@ quote_literal_buffer( void *_this, char *p_in, int strlen, char *p_out ){
580
722
  }
581
723
  /* Write start quote */
582
724
  *p_out = '\'';
583
- return strlen + backslashs + 2;
725
+ return strlen + backslashes + 2;
584
726
  }
585
727
 
586
728
 
@@ -589,7 +731,13 @@ quote_literal_buffer( void *_this, char *p_in, int strlen, char *p_out ){
589
731
  *
590
732
  * This is the encoder class for PostgreSQL literals.
591
733
  *
592
- * A literal is quoted and escaped by the +'+ character.
734
+ * A literal is quoted and escaped by the <tt>'</tt> character, so that it can be inserted into SQL queries.
735
+ * It works equal to PG::Connection#escape_literal, but integrates into the type cast system of ruby-pg.
736
+ *
737
+ * Both expressions have the same result:
738
+ * conn.escape_literal(PG::TextEncoder::Array.new.encode(["v1","v2"])) # => "'{v1,v2}'"
739
+ * PG::TextEncoder::QuotedLiteral.new(elements_type: PG::TextEncoder::Array.new).encode(["v1","v2"]) # => "'{v1,v2}'"
740
+ * While escape_literal requires a intermediate ruby string allocation, QuotedLiteral encodes the values directly to the result string.
593
741
  *
594
742
  */
595
743
  static int
@@ -652,13 +800,15 @@ pg_text_enc_to_base64(t_pg_coder *conv, VALUE value, char *out, VALUE *intermedi
652
800
 
653
801
 
654
802
  void
655
- init_pg_text_encoder()
803
+ init_pg_text_encoder(void)
656
804
  {
657
805
  s_id_encode = rb_intern("encode");
658
806
  s_id_to_i = rb_intern("to_i");
807
+ s_id_to_s = rb_intern("to_s");
659
808
 
660
809
  /* This module encapsulates all encoder classes with text output format */
661
810
  rb_mPG_TextEncoder = rb_define_module_under( rb_mPG, "TextEncoder" );
811
+ rb_define_private_method(rb_singleton_class(rb_mPG_TextEncoder), "init_numeric", init_pg_text_encoder_numeric, 0);
662
812
 
663
813
  /* Make RDoc aware of the encoder classes... */
664
814
  /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Boolean", rb_cPG_SimpleEncoder ); */