pg 0.17.1-x64-mingw32 → 0.18.0.pre20141017160319-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +1885 -169
  5. data/History.rdoc +6 -0
  6. data/Manifest.txt +25 -1
  7. data/README.rdoc +47 -0
  8. data/Rakefile +21 -12
  9. data/Rakefile.cross +39 -33
  10. data/ext/extconf.rb +27 -26
  11. data/ext/pg.c +73 -19
  12. data/ext/pg.h +194 -6
  13. data/ext/pg_binary_decoder.c +160 -0
  14. data/ext/pg_binary_encoder.c +160 -0
  15. data/ext/pg_coder.c +473 -0
  16. data/ext/pg_connection.c +872 -534
  17. data/ext/pg_copy_coder.c +557 -0
  18. data/ext/pg_result.c +266 -111
  19. data/ext/pg_text_decoder.c +424 -0
  20. data/ext/pg_text_encoder.c +631 -0
  21. data/ext/pg_type_map.c +113 -0
  22. data/ext/pg_type_map_all_strings.c +113 -0
  23. data/ext/pg_type_map_by_column.c +254 -0
  24. data/ext/pg_type_map_by_mri_type.c +266 -0
  25. data/ext/pg_type_map_by_oid.c +341 -0
  26. data/ext/util.c +149 -0
  27. data/ext/util.h +65 -0
  28. data/lib/2.0/pg_ext.so +0 -0
  29. data/lib/2.1/pg_ext.so +0 -0
  30. data/lib/pg.rb +11 -1
  31. data/lib/pg/basic_type_mapping.rb +377 -0
  32. data/lib/pg/coder.rb +74 -0
  33. data/lib/pg/connection.rb +43 -1
  34. data/lib/pg/result.rb +13 -3
  35. data/lib/pg/text_decoder.rb +42 -0
  36. data/lib/pg/text_encoder.rb +27 -0
  37. data/lib/pg/type_map_by_column.rb +15 -0
  38. data/lib/x64-mingw32/libpq.dll +0 -0
  39. data/spec/{lib/helpers.rb → helpers.rb} +95 -35
  40. data/spec/pg/basic_type_mapping_spec.rb +251 -0
  41. data/spec/pg/connection_spec.rb +416 -214
  42. data/spec/pg/result_spec.rb +146 -116
  43. data/spec/pg/type_map_by_column_spec.rb +135 -0
  44. data/spec/pg/type_map_by_mri_type_spec.rb +122 -0
  45. data/spec/pg/type_map_by_oid_spec.rb +133 -0
  46. data/spec/pg/type_map_spec.rb +39 -0
  47. data/spec/pg/type_spec.rb +649 -0
  48. data/spec/pg_spec.rb +10 -18
  49. metadata +129 -50
  50. metadata.gz.sig +0 -0
@@ -0,0 +1,557 @@
1
+ /*
2
+ * pg_copycoder.c - PG::Coder class extension
3
+ *
4
+ */
5
+
6
+ #include "pg.h"
7
+
8
+ #define ISOCTAL(c) (((c) >= '0') && ((c) <= '7'))
9
+ #define OCTVALUE(c) ((c) - '0')
10
+
11
+ VALUE rb_cPG_CopyCoder;
12
+ VALUE rb_cPG_CopyEncoder;
13
+ VALUE rb_cPG_CopyDecoder;
14
+
15
+ typedef struct {
16
+ t_pg_coder comp;
17
+ VALUE typemap;
18
+ VALUE null_string;
19
+ char delimiter;
20
+ } t_pg_copycoder;
21
+
22
+
23
+ static void
24
+ pg_copycoder_mark( t_pg_copycoder *this )
25
+ {
26
+ rb_gc_mark(this->typemap);
27
+ rb_gc_mark(this->null_string);
28
+ }
29
+
30
+ static VALUE
31
+ pg_copycoder_encoder_allocate( VALUE klass )
32
+ {
33
+ t_pg_copycoder *this;
34
+ VALUE self = Data_Make_Struct( klass, t_pg_copycoder, pg_copycoder_mark, -1, this );
35
+ pg_coder_init_encoder( self );
36
+ this->typemap = Qnil;
37
+ this->delimiter = '\t';
38
+ this->null_string = rb_str_new_cstr("\\N");
39
+ return self;
40
+ }
41
+
42
+ static VALUE
43
+ pg_copycoder_decoder_allocate( VALUE klass )
44
+ {
45
+ t_pg_copycoder *this;
46
+ VALUE self = Data_Make_Struct( klass, t_pg_copycoder, pg_copycoder_mark, -1, this );
47
+ pg_coder_init_decoder( self );
48
+ this->typemap = Qnil;
49
+ this->delimiter = '\t';
50
+ this->null_string = rb_str_new_cstr("\\N");
51
+ return self;
52
+ }
53
+
54
+ /*
55
+ * call-seq:
56
+ * coder.delimiter = String
57
+ *
58
+ * Specifies the character that separates columns within each row (line) of the file.
59
+ * The default is a tab character in text format, a comma in CSV format.
60
+ * This must be a single one-byte character. This option is ignored when using binary format.
61
+ */
62
+ static VALUE
63
+ pg_copycoder_delimiter_set(VALUE self, VALUE delimiter)
64
+ {
65
+ t_pg_copycoder *this = DATA_PTR(self);
66
+ StringValue(delimiter);
67
+ if(RSTRING_LEN(delimiter) != 1)
68
+ rb_raise( rb_eArgError, "delimiter size must be one byte");
69
+ this->delimiter = *RSTRING_PTR(delimiter);
70
+ return delimiter;
71
+ }
72
+
73
+ /*
74
+ * call-seq:
75
+ * coder.delimiter -> String
76
+ *
77
+ * The character that separates columns within each row (line) of the file.
78
+ */
79
+ static VALUE
80
+ pg_copycoder_delimiter_get(VALUE self)
81
+ {
82
+ t_pg_copycoder *this = DATA_PTR(self);
83
+ return rb_str_new(&this->delimiter, 1);
84
+ }
85
+
86
+ /*
87
+ * Specifies the string that represents a null value. The default is \\N (backslash-N)
88
+ * in text format, and an unquoted empty string in CSV format. You might prefer an
89
+ * empty string even in text format for cases where you don't want to distinguish nulls
90
+ * from empty strings. This option is ignored when using binary format.
91
+ */
92
+ static VALUE
93
+ pg_copycoder_null_string_set(VALUE self, VALUE null_string)
94
+ {
95
+ t_pg_copycoder *this = DATA_PTR(self);
96
+ StringValue(null_string);
97
+ this->null_string = null_string;
98
+ return null_string;
99
+ }
100
+
101
+ /*
102
+ * The string that represents a null value.
103
+ */
104
+ static VALUE
105
+ pg_copycoder_null_string_get(VALUE self)
106
+ {
107
+ t_pg_copycoder *this = DATA_PTR(self);
108
+ return this->null_string;
109
+ }
110
+
111
+ /*
112
+ * call-seq:
113
+ * coder.type_map = map
114
+ *
115
+ * +map+ can be:
116
+ * * a kind of PG::TypeMap
117
+ * * +nil+ - use PG::TextEncoder::String respectively PG::TextDecoder::String
118
+ * for encoding/decoding of all columns.
119
+ *
120
+ */
121
+ static VALUE
122
+ pg_copycoder_type_map_set(VALUE self, VALUE type_map)
123
+ {
124
+ t_pg_copycoder *this = DATA_PTR( self );
125
+
126
+ if ( !NIL_P(type_map) && !rb_obj_is_kind_of(type_map, rb_cTypeMap) ){
127
+ rb_raise( rb_eTypeError, "wrong elements type %s (expected some kind of PG::TypeMap)",
128
+ rb_obj_classname( type_map ) );
129
+ }
130
+ this->typemap = type_map;
131
+
132
+ return type_map;
133
+ }
134
+
135
+ /*
136
+ * call-seq:
137
+ * coder.type_map -> PG::TypeMap
138
+ *
139
+ * Returns either:
140
+ * * a kind of PG::TypeMap
141
+ * * +nil+ - use String coder only.
142
+ *
143
+ */
144
+ static VALUE
145
+ pg_copycoder_type_map_get(VALUE self)
146
+ {
147
+ t_pg_copycoder *this = DATA_PTR( self );
148
+
149
+ return this->typemap;
150
+ }
151
+
152
+
153
+ /*
154
+ * Document-class: PG::TextEncoder::CopyRow < PG::CopyEncoder
155
+ *
156
+ * This class encodes one row of arbitrary columns for transmission as COPY data in text format.
157
+ * See the {COPY command}[http://www.postgresql.org/docs/current/static/sql-copy.html]
158
+ * for description of the format.
159
+ *
160
+ * It is intended to be used in conjunction with PG::Connection#copy_data .
161
+ *
162
+ * The columns are expected as Array of values. The single values are encoded as defined
163
+ * in the assigned #type_map. If no type_map was assigned, all values are converted to
164
+ * Strings by PG::TextEncoder::String.
165
+ *
166
+ */
167
+ static int
168
+ pg_text_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate)
169
+ {
170
+ t_pg_copycoder *this = (t_pg_copycoder *)conv;
171
+ t_pg_coder_enc_func enc_func;
172
+ VALUE typemap;
173
+ static t_pg_coder *p_elem_coder;
174
+ int i;
175
+ t_typemap *p_typemap;
176
+ char *current_out;
177
+ char *end_capa_ptr;
178
+
179
+ if( NIL_P(this->typemap) ){
180
+ Data_Get_Struct( pg_default_typemap, t_typemap, p_typemap);
181
+ /* We don't need to call fit_to_query for pg_default_typemap. It does nothing. */
182
+ typemap = pg_default_typemap;
183
+ } else {
184
+ p_typemap = DATA_PTR( this->typemap );
185
+ typemap = p_typemap->fit_to_query( this->typemap, value );
186
+ p_typemap = DATA_PTR( typemap );
187
+ }
188
+
189
+ /* Allocate a new string with embedded capacity and realloc exponential when needed. */
190
+ PG_RB_STR_NEW( *intermediate, current_out, end_capa_ptr );
191
+
192
+ for( i=0; i<RARRAY_LEN(value); i++){
193
+ char *ptr1;
194
+ char *ptr2;
195
+ int strlen;
196
+ int backslashs;
197
+ VALUE subint;
198
+ VALUE entry;
199
+
200
+ entry = rb_ary_entry(value, i);
201
+
202
+ if( i > 0 ){
203
+ PG_RB_STR_ENSURE_CAPA( *intermediate, 1, current_out, end_capa_ptr );
204
+ *current_out++ = this->delimiter;
205
+ }
206
+
207
+ switch(TYPE(entry)){
208
+ case T_NIL:
209
+ PG_RB_STR_ENSURE_CAPA( *intermediate, RSTRING_LEN(this->null_string), current_out, end_capa_ptr );
210
+ memcpy( current_out, RSTRING_PTR(this->null_string), RSTRING_LEN(this->null_string) );
211
+ current_out += RSTRING_LEN(this->null_string);
212
+ break;
213
+ default:
214
+ p_elem_coder = p_typemap->typecast_query_param(typemap, entry, i);
215
+ enc_func = pg_coder_enc_func(p_elem_coder);
216
+
217
+ /* 1st pass for retiving the required memory space */
218
+ strlen = enc_func(p_elem_coder, entry, NULL, &subint);
219
+
220
+ if( strlen == -1 ){
221
+ /* we can directly use String value in subint */
222
+ strlen = RSTRING_LEN(subint);
223
+
224
+ /* size of string assuming the worst case, that every character must be escaped. */
225
+ PG_RB_STR_ENSURE_CAPA( *intermediate, strlen * 2, current_out, end_capa_ptr );
226
+
227
+ /* Copy string from subint with backslash escaping */
228
+ for(ptr1 = RSTRING_PTR(subint); ptr1 < RSTRING_PTR(subint) + strlen; ptr1++) {
229
+ /* Escape backslash itself, newline, carriage return, and the current delimiter character. */
230
+ if(*ptr1 == '\\' || *ptr1 == '\n' || *ptr1 == '\r' || *ptr1 == this->delimiter){
231
+ *current_out++ = '\\';
232
+ }
233
+ *current_out++ = *ptr1;
234
+ }
235
+ } else {
236
+ /* 2nd pass for writing the data to prepared buffer */
237
+ /* size of string assuming the worst case, that every character must be escaped. */
238
+ PG_RB_STR_ENSURE_CAPA( *intermediate, strlen * 2, current_out, end_capa_ptr );
239
+
240
+ /* Place the unescaped string at current output position. */
241
+ strlen = enc_func(p_elem_coder, entry, current_out, &subint);
242
+
243
+ ptr1 = current_out;
244
+ ptr2 = current_out + strlen;
245
+
246
+ /* count required backlashs */
247
+ for(backslashs = 0; ptr1 != ptr2; ptr1++) {
248
+ /* Escape backslash itself, newline, carriage return, and the current delimiter character. */
249
+ if(*ptr1 == '\\' || *ptr1 == '\n' || *ptr1 == '\r' || *ptr1 == this->delimiter){
250
+ backslashs++;
251
+ }
252
+ }
253
+
254
+ ptr1 = current_out + strlen;
255
+ ptr2 = current_out + strlen + backslashs;
256
+ current_out = ptr2;
257
+
258
+ /* Then store the escaped string on the final position, walking
259
+ * right to left, until all backslashs are placed. */
260
+ while( ptr1 != ptr2 ) {
261
+ *--ptr2 = *--ptr1;
262
+ if(*ptr1 == '\\' || *ptr1 == '\n' || *ptr1 == '\r' || *ptr1 == this->delimiter){
263
+ *--ptr2 = '\\';
264
+ }
265
+ }
266
+ }
267
+ }
268
+ }
269
+ PG_RB_STR_ENSURE_CAPA( *intermediate, 1, current_out, end_capa_ptr );
270
+ *current_out++ = '\n';
271
+
272
+ rb_str_set_len( *intermediate, current_out - RSTRING_PTR(*intermediate) );
273
+
274
+ return -1;
275
+ }
276
+
277
+
278
+ /*
279
+ * Return decimal value for a hexadecimal digit
280
+ */
281
+ static int
282
+ GetDecimalFromHex(char hex)
283
+ {
284
+ if (hex >= '0' && hex <= '9')
285
+ return hex - '0';
286
+ else if (hex >= 'a' && hex <= 'f')
287
+ return hex - 'a' + 10;
288
+ else if (hex >= 'A' && hex <= 'F')
289
+ return hex - 'A' + 10;
290
+ else
291
+ return -1;
292
+ }
293
+
294
+ /*
295
+ * Document-class: PG::TextDecoder::CopyRow < PG::CopyDecoder
296
+ *
297
+ * This class decodes one row of arbitrary columns received as COPY data in text format.
298
+ * See the {COPY command}[http://www.postgresql.org/docs/current/static/sql-copy.html]
299
+ * for description of the format.
300
+ *
301
+ * It is intended to be used in conjunction with PG::Connection#copy_data .
302
+ *
303
+ * The columns are retrieved as Array of values. The single values are decoded as defined
304
+ * in the assigned #type_map. If no type_map was assigned, all values are converted to
305
+ * Strings by PG::TextDecoder::String.
306
+ *
307
+ */
308
+ /*
309
+ * Parse the current line into separate attributes (fields),
310
+ * performing de-escaping as needed.
311
+ *
312
+ * All fields are gathered into a ruby Array. The de-escaped field data is written
313
+ * into to a ruby String. This object is reused for non string columns.
314
+ * For String columns the field value is directly used as return value and no
315
+ * reuse of the memory is done.
316
+ *
317
+ * The parser is thankfully borrowed from the PostgreSQL sources:
318
+ * src/backend/commands/copy.c
319
+ */
320
+ static VALUE
321
+ pg_text_dec_copy_row(t_pg_coder *conv, char *input_line, int len, int _tuple, int _field, int enc_idx)
322
+ {
323
+ t_pg_copycoder *this = (t_pg_copycoder *)conv;
324
+
325
+ /* Return value: array */
326
+ VALUE array;
327
+
328
+ /* Current field */
329
+ VALUE field_str;
330
+
331
+ char delimc = this->delimiter;
332
+ int fieldno;
333
+ int expected_fields;
334
+ char *output_ptr;
335
+ char *cur_ptr;
336
+ char *line_end_ptr;
337
+ char *end_capa_ptr;
338
+ t_typemap *p_typemap;
339
+
340
+ if( NIL_P(this->typemap) ){
341
+ Data_Get_Struct( pg_default_typemap, t_typemap, p_typemap);
342
+ } else {
343
+ p_typemap = DATA_PTR( this->typemap );
344
+ }
345
+ expected_fields = p_typemap->fit_to_copy_get( this->typemap );
346
+
347
+ /* The received input string will probably have this->nfields fields. */
348
+ array = rb_ary_new2(expected_fields);
349
+
350
+ /* Allocate a new string with embedded capacity and realloc later with
351
+ * exponential growing size when needed. */
352
+ PG_RB_TAINTED_STR_NEW( field_str, output_ptr, end_capa_ptr );
353
+
354
+ /* set pointer variables for loop */
355
+ cur_ptr = input_line;
356
+ line_end_ptr = input_line + len;
357
+
358
+ /* Outer loop iterates over fields */
359
+ fieldno = 0;
360
+ for (;;)
361
+ {
362
+ int found_delim = 0;
363
+ char *start_ptr;
364
+ char *end_ptr;
365
+ int input_len;
366
+
367
+ /* Remember start of field on input side */
368
+ start_ptr = cur_ptr;
369
+
370
+ /*
371
+ * Scan data for field.
372
+ *
373
+ * Note that in this loop, we are scanning to locate the end of field
374
+ * and also speculatively performing de-escaping. Once we find the
375
+ * end-of-field, we can match the raw field contents against the null
376
+ * marker string. Only after that comparison fails do we know that
377
+ * de-escaping is actually the right thing to do; therefore we *must
378
+ * not* throw any syntax errors before we've done the null-marker
379
+ * check.
380
+ */
381
+ for (;;)
382
+ {
383
+ /* The current character in the input string. */
384
+ char c;
385
+
386
+ end_ptr = cur_ptr;
387
+ if (cur_ptr >= line_end_ptr)
388
+ break;
389
+ c = *cur_ptr++;
390
+ if (c == delimc){
391
+ found_delim = 1;
392
+ break;
393
+ }
394
+ if (c == '\n'){
395
+ break;
396
+ }
397
+ if (c == '\\'){
398
+ if (cur_ptr >= line_end_ptr)
399
+ break;
400
+
401
+ c = *cur_ptr++;
402
+ switch (c){
403
+ case '0':
404
+ case '1':
405
+ case '2':
406
+ case '3':
407
+ case '4':
408
+ case '5':
409
+ case '6':
410
+ case '7':
411
+ {
412
+ /* handle \013 */
413
+ int val;
414
+
415
+ val = OCTVALUE(c);
416
+ if (cur_ptr < line_end_ptr)
417
+ {
418
+ c = *cur_ptr;
419
+ if (ISOCTAL(c))
420
+ {
421
+ cur_ptr++;
422
+ val = (val << 3) + OCTVALUE(c);
423
+ if (cur_ptr < line_end_ptr)
424
+ {
425
+ c = *cur_ptr;
426
+ if (ISOCTAL(c))
427
+ {
428
+ cur_ptr++;
429
+ val = (val << 3) + OCTVALUE(c);
430
+ }
431
+ }
432
+ }
433
+ }
434
+ c = val & 0377;
435
+ }
436
+ break;
437
+ case 'x':
438
+ /* Handle \x3F */
439
+ if (cur_ptr < line_end_ptr)
440
+ {
441
+ char hexchar = *cur_ptr;
442
+ int val = GetDecimalFromHex(hexchar);;
443
+
444
+ if (val >= 0)
445
+ {
446
+ cur_ptr++;
447
+ if (cur_ptr < line_end_ptr)
448
+ {
449
+ int val2;
450
+ hexchar = *cur_ptr;
451
+ val2 = GetDecimalFromHex(hexchar);
452
+
453
+ if (val2 >= 0)
454
+ {
455
+ cur_ptr++;
456
+ val = (val << 4) + val2;
457
+ }
458
+ }
459
+ c = val & 0xff;
460
+ }
461
+ }
462
+ break;
463
+ case 'b':
464
+ c = '\b';
465
+ break;
466
+ case 'f':
467
+ c = '\f';
468
+ break;
469
+ case 'n':
470
+ c = '\n';
471
+ break;
472
+ case 'r':
473
+ c = '\r';
474
+ break;
475
+ case 't':
476
+ c = '\t';
477
+ break;
478
+ case 'v':
479
+ c = '\v';
480
+ break;
481
+
482
+ /*
483
+ * in all other cases, take the char after '\'
484
+ * literally
485
+ */
486
+ }
487
+ }
488
+
489
+ PG_RB_STR_ENSURE_CAPA( field_str, 1, output_ptr, end_capa_ptr );
490
+ /* Add c to output string */
491
+ *output_ptr++ = c;
492
+ }
493
+
494
+ if (!found_delim && cur_ptr < line_end_ptr)
495
+ rb_raise( rb_eArgError, "trailing data after linefeed at position: %ld", (long)(cur_ptr - input_line) + 1 );
496
+
497
+
498
+ /* Check whether raw input matched null marker */
499
+ input_len = end_ptr - start_ptr;
500
+ if (input_len == RSTRING_LEN(this->null_string) &&
501
+ strncmp(start_ptr, RSTRING_PTR(this->null_string), input_len) == 0) {
502
+ rb_ary_push(array, Qnil);
503
+ } else {
504
+ VALUE field_value;
505
+
506
+ rb_str_set_len( field_str, output_ptr - RSTRING_PTR(field_str) );
507
+ field_value = p_typemap->typecast_copy_get( p_typemap, field_str, fieldno, 0, enc_idx );
508
+
509
+ rb_ary_push(array, field_value);
510
+
511
+ if( field_value == field_str ){
512
+ /* Our output string will be send to the user, so we can not reuse
513
+ * it for the next field. */
514
+ PG_RB_TAINTED_STR_NEW( field_str, output_ptr, end_capa_ptr );
515
+ }
516
+ }
517
+ /* Reset the pointer to the start of the output/buffer string. */
518
+ output_ptr = RSTRING_PTR(field_str);
519
+
520
+ fieldno++;
521
+ /* Done if we hit EOL instead of a delim */
522
+ if (!found_delim)
523
+ break;
524
+ }
525
+
526
+ return array;
527
+ }
528
+
529
+
530
+ void
531
+ init_pg_copycoder()
532
+ {
533
+ /* Document-class: PG::CopyCoder < PG::Coder
534
+ *
535
+ * This is the base class for all type cast classes for COPY data,
536
+ */
537
+ rb_cPG_CopyCoder = rb_define_class_under( rb_mPG, "CopyCoder", rb_cPG_Coder );
538
+ rb_define_method( rb_cPG_CopyCoder, "type_map=", pg_copycoder_type_map_set, 1 );
539
+ rb_define_method( rb_cPG_CopyCoder, "type_map", pg_copycoder_type_map_get, 0 );
540
+ rb_define_method( rb_cPG_CopyCoder, "delimiter=", pg_copycoder_delimiter_set, 1 );
541
+ rb_define_method( rb_cPG_CopyCoder, "delimiter", pg_copycoder_delimiter_get, 0 );
542
+ rb_define_method( rb_cPG_CopyCoder, "null_string=", pg_copycoder_null_string_set, 1 );
543
+ rb_define_method( rb_cPG_CopyCoder, "null_string", pg_copycoder_null_string_get, 0 );
544
+
545
+ /* Document-class: PG::CopyEncoder < PG::CopyCoder */
546
+ rb_cPG_CopyEncoder = rb_define_class_under( rb_mPG, "CopyEncoder", rb_cPG_CopyCoder );
547
+ rb_define_alloc_func( rb_cPG_CopyEncoder, pg_copycoder_encoder_allocate );
548
+ /* Document-class: PG::CopyDecoder < PG::CopyCoder */
549
+ rb_cPG_CopyDecoder = rb_define_class_under( rb_mPG, "CopyDecoder", rb_cPG_CopyCoder );
550
+ rb_define_alloc_func( rb_cPG_CopyDecoder, pg_copycoder_decoder_allocate );
551
+
552
+ /* Make RDoc aware of the encoder classes... */
553
+ /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "CopyRow", rb_cPG_CopyEncoder ); */
554
+ pg_define_coder( "CopyRow", pg_text_enc_copy_row, rb_cPG_CopyEncoder, rb_mPG_TextEncoder );
555
+ /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "CopyRow", rb_cPG_CopyDecoder ); */
556
+ pg_define_coder( "CopyRow", pg_text_dec_copy_row, rb_cPG_CopyDecoder, rb_mPG_TextDecoder );
557
+ }