pg 0.18.4 → 1.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.appveyor.yml +36 -0
  4. data/.gems +6 -0
  5. data/.github/workflows/binary-gems.yml +86 -0
  6. data/.github/workflows/source-gem.yml +129 -0
  7. data/.gitignore +13 -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/BSDL +2 -2
  15. data/Gemfile +14 -0
  16. data/History.rdoc +436 -4
  17. data/Manifest.txt +8 -21
  18. data/README-Windows.rdoc +4 -4
  19. data/README.ja.rdoc +1 -2
  20. data/README.rdoc +67 -17
  21. data/Rakefile +32 -144
  22. data/Rakefile.cross +70 -69
  23. data/certs/ged.pem +24 -0
  24. data/certs/larskanis-2022.pem +26 -0
  25. data/ext/errorcodes.def +109 -0
  26. data/ext/errorcodes.rb +1 -1
  27. data/ext/errorcodes.txt +35 -2
  28. data/ext/extconf.rb +119 -54
  29. data/ext/gvl_wrappers.c +8 -0
  30. data/ext/gvl_wrappers.h +44 -33
  31. data/ext/pg.c +213 -171
  32. data/ext/pg.h +92 -98
  33. data/ext/pg_binary_decoder.c +82 -15
  34. data/ext/pg_binary_encoder.c +20 -19
  35. data/ext/pg_coder.c +175 -39
  36. data/ext/pg_connection.c +1730 -1135
  37. data/ext/pg_copy_coder.c +94 -27
  38. data/ext/pg_record_coder.c +521 -0
  39. data/ext/pg_result.c +640 -221
  40. data/ext/pg_text_decoder.c +606 -40
  41. data/ext/pg_text_encoder.c +250 -99
  42. data/ext/pg_tuple.c +569 -0
  43. data/ext/pg_type_map.c +55 -15
  44. data/ext/pg_type_map_all_strings.c +19 -5
  45. data/ext/pg_type_map_by_class.c +54 -24
  46. data/ext/pg_type_map_by_column.c +73 -34
  47. data/ext/pg_type_map_by_mri_type.c +48 -19
  48. data/ext/pg_type_map_by_oid.c +55 -25
  49. data/ext/pg_type_map_in_ruby.c +51 -20
  50. data/ext/{util.c → pg_util.c} +12 -12
  51. data/ext/{util.h → pg_util.h} +2 -2
  52. data/lib/pg/basic_type_map_based_on_result.rb +47 -0
  53. data/lib/pg/basic_type_map_for_queries.rb +193 -0
  54. data/lib/pg/basic_type_map_for_results.rb +81 -0
  55. data/lib/pg/basic_type_registry.rb +301 -0
  56. data/lib/pg/binary_decoder.rb +23 -0
  57. data/lib/pg/coder.rb +24 -3
  58. data/lib/pg/connection.rb +723 -65
  59. data/lib/pg/constants.rb +2 -1
  60. data/lib/pg/exceptions.rb +9 -2
  61. data/lib/pg/result.rb +24 -7
  62. data/lib/pg/text_decoder.rb +24 -22
  63. data/lib/pg/text_encoder.rb +40 -8
  64. data/lib/pg/tuple.rb +30 -0
  65. data/lib/pg/type_map_by_column.rb +3 -2
  66. data/lib/pg/version.rb +4 -0
  67. data/lib/pg.rb +61 -36
  68. data/misc/openssl-pg-segfault.rb +31 -0
  69. data/misc/postgres/History.txt +9 -0
  70. data/misc/postgres/Manifest.txt +5 -0
  71. data/misc/postgres/README.txt +21 -0
  72. data/misc/postgres/Rakefile +21 -0
  73. data/misc/postgres/lib/postgres.rb +16 -0
  74. data/misc/ruby-pg/History.txt +9 -0
  75. data/misc/ruby-pg/Manifest.txt +5 -0
  76. data/misc/ruby-pg/README.txt +21 -0
  77. data/misc/ruby-pg/Rakefile +21 -0
  78. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  79. data/pg.gemspec +32 -0
  80. data/rakelib/task_extension.rb +46 -0
  81. data/sample/array_insert.rb +1 -1
  82. data/sample/async_api.rb +4 -8
  83. data/sample/async_copyto.rb +1 -1
  84. data/sample/async_mixed.rb +1 -1
  85. data/sample/check_conn.rb +1 -1
  86. data/sample/copydata.rb +71 -0
  87. data/sample/copyfrom.rb +1 -1
  88. data/sample/copyto.rb +1 -1
  89. data/sample/cursor.rb +1 -1
  90. data/sample/disk_usage_report.rb +6 -15
  91. data/sample/issue-119.rb +2 -2
  92. data/sample/losample.rb +1 -1
  93. data/sample/minimal-testcase.rb +2 -2
  94. data/sample/notify_wait.rb +1 -1
  95. data/sample/pg_statistics.rb +6 -15
  96. data/sample/replication_monitor.rb +9 -18
  97. data/sample/test_binary_values.rb +1 -1
  98. data/sample/wal_shipper.rb +2 -2
  99. data/sample/warehouse_partitions.rb +8 -17
  100. data.tar.gz.sig +0 -0
  101. metadata +79 -225
  102. metadata.gz.sig +0 -0
  103. data/ChangeLog +0 -5911
  104. data/lib/pg/basic_type_mapping.rb +0 -399
  105. data/spec/data/expected_trace.out +0 -26
  106. data/spec/data/random_binary_data +0 -0
  107. data/spec/helpers.rb +0 -355
  108. data/spec/pg/basic_type_mapping_spec.rb +0 -251
  109. data/spec/pg/connection_spec.rb +0 -1544
  110. data/spec/pg/result_spec.rb +0 -449
  111. data/spec/pg/type_map_by_class_spec.rb +0 -138
  112. data/spec/pg/type_map_by_column_spec.rb +0 -222
  113. data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
  114. data/spec/pg/type_map_by_oid_spec.rb +0 -149
  115. data/spec/pg/type_map_in_ruby_spec.rb +0 -164
  116. data/spec/pg/type_map_spec.rb +0 -22
  117. data/spec/pg/type_spec.rb +0 -697
  118. data/spec/pg_spec.rb +0 -50
@@ -0,0 +1,521 @@
1
+ /*
2
+ * pg_record_coder.c - PG::Coder class extension
3
+ *
4
+ */
5
+
6
+ #include "pg.h"
7
+
8
+ VALUE rb_cPG_RecordCoder;
9
+ VALUE rb_cPG_RecordEncoder;
10
+ VALUE rb_cPG_RecordDecoder;
11
+
12
+ typedef struct {
13
+ t_pg_coder comp;
14
+ VALUE typemap;
15
+ } t_pg_recordcoder;
16
+
17
+
18
+ static void
19
+ pg_recordcoder_mark( void *_this )
20
+ {
21
+ t_pg_recordcoder *this = (t_pg_recordcoder *)_this;
22
+ rb_gc_mark_movable(this->typemap);
23
+ }
24
+
25
+ static size_t
26
+ pg_recordcoder_memsize( const void *_this )
27
+ {
28
+ const t_pg_recordcoder *this = (const t_pg_recordcoder *)_this;
29
+ return sizeof(*this);
30
+ }
31
+
32
+ static void
33
+ pg_recordcoder_compact( void *_this )
34
+ {
35
+ t_pg_recordcoder *this = (t_pg_recordcoder *)_this;
36
+ pg_coder_compact(&this->comp);
37
+ pg_gc_location(this->typemap);
38
+ }
39
+
40
+ static const rb_data_type_t pg_recordcoder_type = {
41
+ "PG::RecordCoder",
42
+ {
43
+ pg_recordcoder_mark,
44
+ RUBY_TYPED_DEFAULT_FREE,
45
+ pg_recordcoder_memsize,
46
+ pg_compact_callback(pg_recordcoder_compact),
47
+ },
48
+ &pg_coder_type,
49
+ 0,
50
+ RUBY_TYPED_FREE_IMMEDIATELY,
51
+ };
52
+
53
+ static VALUE
54
+ pg_recordcoder_encoder_allocate( VALUE klass )
55
+ {
56
+ t_pg_recordcoder *this;
57
+ VALUE self = TypedData_Make_Struct( klass, t_pg_recordcoder, &pg_recordcoder_type, this );
58
+ pg_coder_init_encoder( self );
59
+ this->typemap = pg_typemap_all_strings;
60
+ return self;
61
+ }
62
+
63
+ static VALUE
64
+ pg_recordcoder_decoder_allocate( VALUE klass )
65
+ {
66
+ t_pg_recordcoder *this;
67
+ VALUE self = TypedData_Make_Struct( klass, t_pg_recordcoder, &pg_recordcoder_type, this );
68
+ pg_coder_init_decoder( self );
69
+ this->typemap = pg_typemap_all_strings;
70
+ return self;
71
+ }
72
+
73
+ /*
74
+ * call-seq:
75
+ * coder.type_map = map
76
+ *
77
+ * Defines how single columns are encoded or decoded.
78
+ * +map+ must be a kind of PG::TypeMap .
79
+ *
80
+ * Defaults to a PG::TypeMapAllStrings , so that PG::TextEncoder::String respectively
81
+ * PG::TextDecoder::String is used for encoding/decoding of each column.
82
+ *
83
+ */
84
+ static VALUE
85
+ pg_recordcoder_type_map_set(VALUE self, VALUE type_map)
86
+ {
87
+ t_pg_recordcoder *this = RTYPEDDATA_DATA( self );
88
+
89
+ if ( !rb_obj_is_kind_of(type_map, rb_cTypeMap) ){
90
+ rb_raise( rb_eTypeError, "wrong elements type %s (expected some kind of PG::TypeMap)",
91
+ rb_obj_classname( type_map ) );
92
+ }
93
+ this->typemap = type_map;
94
+
95
+ return type_map;
96
+ }
97
+
98
+ /*
99
+ * call-seq:
100
+ * coder.type_map -> PG::TypeMap
101
+ *
102
+ * The PG::TypeMap that will be used for encoding and decoding of columns.
103
+ */
104
+ static VALUE
105
+ pg_recordcoder_type_map_get(VALUE self)
106
+ {
107
+ t_pg_recordcoder *this = RTYPEDDATA_DATA( self );
108
+
109
+ return this->typemap;
110
+ }
111
+
112
+
113
+ /*
114
+ * Document-class: PG::TextEncoder::Record < PG::RecordEncoder
115
+ *
116
+ * This class encodes one record of columns for transmission as query parameter in text format.
117
+ * See PostgreSQL {Composite Types}[https://www.postgresql.org/docs/current/rowtypes.html] for a description of the format and how it can be used.
118
+ *
119
+ * PostgreSQL allows composite types to be used in many of the same ways that simple types can be used.
120
+ * For example, a column of a table can be declared to be of a composite type.
121
+ *
122
+ * The encoder expects the record columns as array of values.
123
+ * The single values are encoded as defined in the assigned #type_map.
124
+ * If no type_map was assigned, all values are converted to strings by PG::TextEncoder::String.
125
+ *
126
+ * It is possible to manually assign a type encoder for each column per PG::TypeMapByColumn,
127
+ * or to make use of PG::BasicTypeMapBasedOnResult to assign them based on the table OIDs.
128
+ *
129
+ * Encode a record from an <code>Array<String></code> to a +String+ in PostgreSQL Composite Type format (uses default type map TypeMapAllStrings):
130
+ * PG::TextEncoder::Record.new.encode([1, 2]) # => "(\"1\",\"2\")"
131
+ *
132
+ * Encode a record from <code>Array<Float></code> to +String+ :
133
+ * # Build a type map for two Floats
134
+ * tm = PG::TypeMapByColumn.new([PG::TextEncoder::Float.new]*2)
135
+ * # Use this type map to encode the record:
136
+ * PG::TextEncoder::Record.new(type_map: tm).encode([1,2])
137
+ * # => "(\"1.0\",\"2.0\")"
138
+ *
139
+ * Records can also be encoded and decoded directly to and from the database.
140
+ * This avoids intermediate string allocations and is very fast.
141
+ * Take the following type and table definitions:
142
+ * conn.exec("CREATE TYPE complex AS (r float, i float) ")
143
+ * conn.exec("CREATE TABLE my_table (v1 complex, v2 complex) ")
144
+ *
145
+ * A record can be encoded by adding a type map to Connection#exec_params and siblings:
146
+ * # Build a type map for the two floats "r" and "i" as in our "complex" type
147
+ * tm = PG::TypeMapByColumn.new([PG::TextEncoder::Float.new]*2)
148
+ * # Build a record encoder to encode this type as a record:
149
+ * enco = PG::TextEncoder::Record.new(type_map: tm)
150
+ * # Insert table data and use the encoder to cast the complex value "v1" from ruby array:
151
+ * conn.exec_params("INSERT INTO my_table VALUES ($1) RETURNING v1", [[1,2]], 0, PG::TypeMapByColumn.new([enco])).to_a
152
+ * # => [{"v1"=>"(1,2)"}]
153
+ *
154
+ * Alternatively the typemap can be build based on database OIDs rather than manually assigning encoders.
155
+ * # Fetch a NULL record of our type to retrieve the OIDs of the two fields "r" and "i"
156
+ * oids = conn.exec( "SELECT (NULL::complex).*" )
157
+ * # Build a type map (PG::TypeMapByColumn) for encoding the "complex" type
158
+ * etm = PG::BasicTypeMapBasedOnResult.new(conn).build_column_map( oids )
159
+ *
160
+ * It's also possible to use the BasicTypeMapForQueries to send records to the database server.
161
+ * In contrast to ORM libraries, PG doesn't have information regarding the type of data the server is expecting.
162
+ * So BasicTypeMapForQueries works based on the class of the values to be sent and it has to be instructed that a ruby array shall be casted to a record.
163
+ * # Retrieve OIDs of all basic types from the database
164
+ * etm = PG::BasicTypeMapForQueries.new(conn)
165
+ * etm.encode_array_as = :record
166
+ * # Apply the basic type registry to all values sent to the server
167
+ * conn.type_map_for_queries = etm
168
+ * # Send a complex number as an array of two integers
169
+ * conn.exec_params("INSERT INTO my_table VALUES ($1) RETURNING v1", [[1,2]]).to_a
170
+ * # => [{"v1"=>"(1,2)"}]
171
+ *
172
+ * Records can also be nested or further wrapped into other encoders like PG::TextEncoder::CopyRow.
173
+ *
174
+ * See also PG::TextDecoder::Record for the decoding direction.
175
+ */
176
+ static int
177
+ pg_text_enc_record(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx)
178
+ {
179
+ t_pg_recordcoder *this = (t_pg_recordcoder *)conv;
180
+ t_pg_coder_enc_func enc_func;
181
+ static t_pg_coder *p_elem_coder;
182
+ int i;
183
+ t_typemap *p_typemap;
184
+ char *current_out;
185
+ char *end_capa_ptr;
186
+
187
+ p_typemap = RTYPEDDATA_DATA( this->typemap );
188
+ p_typemap->funcs.fit_to_query( this->typemap, value );
189
+
190
+ /* Allocate a new string with embedded capacity and realloc exponential when needed. */
191
+ PG_RB_STR_NEW( *intermediate, current_out, end_capa_ptr );
192
+ PG_ENCODING_SET_NOCHECK(*intermediate, enc_idx);
193
+ PG_RB_STR_ENSURE_CAPA( *intermediate, 1, current_out, end_capa_ptr );
194
+ *current_out++ = '(';
195
+
196
+ for( i=0; i<RARRAY_LEN(value); i++){
197
+ char *ptr1;
198
+ char *ptr2;
199
+ long strlen;
200
+ int backslashs;
201
+ VALUE subint;
202
+ VALUE entry;
203
+
204
+ entry = rb_ary_entry(value, i);
205
+
206
+ if( i > 0 ){
207
+ PG_RB_STR_ENSURE_CAPA( *intermediate, 1, current_out, end_capa_ptr );
208
+ *current_out++ = ',';
209
+ }
210
+
211
+ switch(TYPE(entry)){
212
+ case T_NIL:
213
+ /* emit nothing... */
214
+ break;
215
+ default:
216
+ p_elem_coder = p_typemap->funcs.typecast_query_param(p_typemap, entry, i);
217
+ enc_func = pg_coder_enc_func(p_elem_coder);
218
+
219
+ /* 1st pass for retiving the required memory space */
220
+ strlen = enc_func(p_elem_coder, entry, NULL, &subint, enc_idx);
221
+
222
+ if( strlen == -1 ){
223
+ /* we can directly use String value in subint */
224
+ strlen = RSTRING_LEN(subint);
225
+
226
+ /* size of string assuming the worst case, that every character must be escaped. */
227
+ PG_RB_STR_ENSURE_CAPA( *intermediate, strlen * 2 + 2, current_out, end_capa_ptr );
228
+
229
+ *current_out++ = '"';
230
+ /* Record string from subint with backslash escaping */
231
+ for(ptr1 = RSTRING_PTR(subint); ptr1 < RSTRING_PTR(subint) + strlen; ptr1++) {
232
+ if (*ptr1 == '"' || *ptr1 == '\\') {
233
+ *current_out++ = *ptr1;
234
+ }
235
+ *current_out++ = *ptr1;
236
+ }
237
+ *current_out++ = '"';
238
+ } else {
239
+ /* 2nd pass for writing the data to prepared buffer */
240
+ /* size of string assuming the worst case, that every character must be escaped. */
241
+ PG_RB_STR_ENSURE_CAPA( *intermediate, strlen * 2 + 2, current_out, end_capa_ptr );
242
+
243
+ *current_out++ = '"';
244
+ /* Place the unescaped string at current output position. */
245
+ strlen = enc_func(p_elem_coder, entry, current_out, &subint, enc_idx);
246
+
247
+ ptr1 = current_out;
248
+ ptr2 = current_out + strlen;
249
+
250
+ /* count required backlashs */
251
+ for(backslashs = 0; ptr1 != ptr2; ptr1++) {
252
+ /* Escape backslash itself, newline, carriage return, and the current delimiter character. */
253
+ if(*ptr1 == '"' || *ptr1 == '\\'){
254
+ backslashs++;
255
+ }
256
+ }
257
+
258
+ ptr1 = current_out + strlen;
259
+ ptr2 = current_out + strlen + backslashs;
260
+ current_out = ptr2;
261
+
262
+ /* Then store the escaped string on the final position, walking
263
+ * right to left, until all backslashs are placed. */
264
+ while( ptr1 != ptr2 ) {
265
+ *--ptr2 = *--ptr1;
266
+ if(*ptr1 == '"' || *ptr1 == '\\'){
267
+ *--ptr2 = *ptr1;
268
+ }
269
+ }
270
+ *current_out++ = '"';
271
+ }
272
+ }
273
+ }
274
+ PG_RB_STR_ENSURE_CAPA( *intermediate, 1, current_out, end_capa_ptr );
275
+ *current_out++ = ')';
276
+
277
+ rb_str_set_len( *intermediate, current_out - RSTRING_PTR(*intermediate) );
278
+
279
+ return -1;
280
+ }
281
+
282
+ /*
283
+ * record_isspace() --- a non-locale-dependent isspace()
284
+ *
285
+ * We used to use isspace() for parsing array values, but that has
286
+ * undesirable results: an array value might be silently interpreted
287
+ * differently depending on the locale setting. Now we just hard-wire
288
+ * the traditional ASCII definition of isspace().
289
+ */
290
+ static int
291
+ record_isspace(char ch)
292
+ {
293
+ if (ch == ' ' ||
294
+ ch == '\t' ||
295
+ ch == '\n' ||
296
+ ch == '\r' ||
297
+ ch == '\v' ||
298
+ ch == '\f')
299
+ return 1;
300
+ return 0;
301
+ }
302
+
303
+ /*
304
+ * Document-class: PG::TextDecoder::Record < PG::RecordDecoder
305
+ *
306
+ * This class decodes one record of values received from a composite type column in text format.
307
+ * See PostgreSQL {Composite Types}[https://www.postgresql.org/docs/current/rowtypes.html] for a description of the format and how it can be used.
308
+ *
309
+ * PostgreSQL allows composite types to be used in many of the same ways that simple types can be used.
310
+ * For example, a column of a table can be declared to be of a composite type.
311
+ *
312
+ * The columns are returned from the decoder as array of values.
313
+ * The single values are decoded as defined in the assigned #type_map.
314
+ * If no type_map was assigned, all values are converted to strings by PG::TextDecoder::String.
315
+ *
316
+ * Decode a record in Composite Type format from +String+ to <code>Array<String></code> (uses default type map TypeMapAllStrings):
317
+ * PG::TextDecoder::Record.new.decode("(1,2)") # => ["1", "2"]
318
+ *
319
+ * Decode a record from +String+ to <code>Array<Float></code> :
320
+ * # Build a type map for two Floats
321
+ * tm = PG::TypeMapByColumn.new([PG::TextDecoder::Float.new]*2)
322
+ * # Use this type map to decode the record:
323
+ * PG::TextDecoder::Record.new(type_map: tm).decode("(1,2)")
324
+ * # => [1.0, 2.0]
325
+ *
326
+ * Records can also be encoded and decoded directly to and from the database.
327
+ * This avoids intermediate String allocations and is very fast.
328
+ * Take the following type and table definitions:
329
+ * conn.exec("CREATE TYPE complex AS (r float, i float) ")
330
+ * conn.exec("CREATE TABLE my_table (v1 complex, v2 complex) ")
331
+ * conn.exec("INSERT INTO my_table VALUES((2,3), (4,5)), ((6,7), (8,9)) ")
332
+ *
333
+ * The record can be decoded by applying a type map to the PG::Result object:
334
+ * # Build a type map for two floats "r" and "i"
335
+ * tm = PG::TypeMapByColumn.new([PG::TextDecoder::Float.new]*2)
336
+ * # Build a record decoder to decode this two-value type:
337
+ * deco = PG::TextDecoder::Record.new(type_map: tm)
338
+ * # Fetch table data and use the decoder to cast the two complex values "v1" and "v2":
339
+ * conn.exec("SELECT * FROM my_table").map_types!(PG::TypeMapByColumn.new([deco]*2)).to_a
340
+ * # => [{"v1"=>[2.0, 3.0], "v2"=>[4.0, 5.0]}, {"v1"=>[6.0, 7.0], "v2"=>[8.0, 9.0]}]
341
+ *
342
+ * It's more very convenient to use the PG::BasicTypeRegistry, which is based on database OIDs.
343
+ * # Fetch a NULL record of our type to retrieve the OIDs of the two fields "r" and "i"
344
+ * oids = conn.exec( "SELECT (NULL::complex).*" )
345
+ * # Build a type map (PG::TypeMapByColumn) for decoding the "complex" type
346
+ * dtm = PG::BasicTypeMapForResults.new(conn).build_column_map( oids )
347
+ * # Build a type map and populate with basic types
348
+ * btr = PG::BasicTypeRegistry.new.register_default_types
349
+ * # Register a new record decoder for decoding our type "complex"
350
+ * btr.register_coder(PG::TextDecoder::Record.new(type_map: dtm, name: "complex"))
351
+ * # Apply our basic type registry to all results retrieved from the server
352
+ * conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn, registry: btr)
353
+ * # Now queries decode the "complex" type (and many basic types) automatically
354
+ * conn.exec("SELECT * FROM my_table").to_a
355
+ * # => [{"v1"=>[2.0, 3.0], "v2"=>[4.0, 5.0]}, {"v1"=>[6.0, 7.0], "v2"=>[8.0, 9.0]}]
356
+ *
357
+ * Records can also be nested or further wrapped into other decoders like PG::TextDecoder::CopyRow.
358
+ *
359
+ * See also PG::TextEncoder::Record for the encoding direction (data sent to the server).
360
+ */
361
+ /*
362
+ * Parse the current line into separate attributes (fields),
363
+ * performing de-escaping as needed.
364
+ *
365
+ * All fields are gathered into a ruby Array. The de-escaped field data is written
366
+ * into to a ruby String. This object is reused for non string columns.
367
+ * For String columns the field value is directly used as return value and no
368
+ * reuse of the memory is done.
369
+ *
370
+ * The parser is thankfully borrowed from the PostgreSQL sources:
371
+ * src/backend/utils/adt/rowtypes.c
372
+ */
373
+ static VALUE
374
+ pg_text_dec_record(t_pg_coder *conv, char *input_line, int len, int _tuple, int _field, int enc_idx)
375
+ {
376
+ t_pg_recordcoder *this = (t_pg_recordcoder *)conv;
377
+
378
+ /* Return value: array */
379
+ VALUE array;
380
+
381
+ /* Current field */
382
+ VALUE field_str;
383
+
384
+ int fieldno;
385
+ int expected_fields;
386
+ char *output_ptr;
387
+ char *cur_ptr;
388
+ char *end_capa_ptr;
389
+ t_typemap *p_typemap;
390
+
391
+ p_typemap = RTYPEDDATA_DATA( this->typemap );
392
+ expected_fields = p_typemap->funcs.fit_to_copy_get( this->typemap );
393
+
394
+ /* The received input string will probably have this->nfields fields. */
395
+ array = rb_ary_new2(expected_fields);
396
+
397
+ /* Allocate a new string with embedded capacity and realloc later with
398
+ * exponential growing size when needed. */
399
+ PG_RB_STR_NEW( field_str, output_ptr, end_capa_ptr );
400
+
401
+ /* set pointer variables for loop */
402
+ cur_ptr = input_line;
403
+
404
+ /*
405
+ * Scan the string. We use "buf" to accumulate the de-quoted data for
406
+ * each column, which is then fed to the appropriate input converter.
407
+ */
408
+ /* Allow leading whitespace */
409
+ while (*cur_ptr && record_isspace(*cur_ptr))
410
+ cur_ptr++;
411
+ if (*cur_ptr++ != '(')
412
+ rb_raise( rb_eArgError, "malformed record literal: \"%s\" - Missing left parenthesis.", input_line );
413
+
414
+ for (fieldno = 0; ; fieldno++)
415
+ {
416
+ /* Check for null: completely empty input means null */
417
+ if (*cur_ptr == ',' || *cur_ptr == ')')
418
+ {
419
+ rb_ary_push(array, Qnil);
420
+ }
421
+ else
422
+ {
423
+ /* Extract string for this column */
424
+ int inquote = 0;
425
+ VALUE field_value;
426
+
427
+ while (inquote || !(*cur_ptr == ',' || *cur_ptr == ')'))
428
+ {
429
+ char ch = *cur_ptr++;
430
+
431
+ if (ch == '\0')
432
+ rb_raise( rb_eArgError, "malformed record literal: \"%s\" - Unexpected end of input.", input_line );
433
+ if (ch == '\\')
434
+ {
435
+ if (*cur_ptr == '\0')
436
+ rb_raise( rb_eArgError, "malformed record literal: \"%s\" - Unexpected end of input.", input_line );
437
+ PG_RB_STR_ENSURE_CAPA( field_str, 1, output_ptr, end_capa_ptr );
438
+ *output_ptr++ = *cur_ptr++;
439
+ }
440
+ else if (ch == '"')
441
+ {
442
+ if (!inquote)
443
+ inquote = 1;
444
+ else if (*cur_ptr == '"')
445
+ {
446
+ /* doubled quote within quote sequence */
447
+ PG_RB_STR_ENSURE_CAPA( field_str, 1, output_ptr, end_capa_ptr );
448
+ *output_ptr++ = *cur_ptr++;
449
+ }
450
+ else
451
+ inquote = 0;
452
+ } else {
453
+ PG_RB_STR_ENSURE_CAPA( field_str, 1, output_ptr, end_capa_ptr );
454
+ /* Add ch to output string */
455
+ *output_ptr++ = ch;
456
+ }
457
+ }
458
+
459
+ /* Convert the column value */
460
+ rb_str_set_len( field_str, output_ptr - RSTRING_PTR(field_str) );
461
+ field_value = p_typemap->funcs.typecast_copy_get( p_typemap, field_str, fieldno, 0, enc_idx );
462
+
463
+ rb_ary_push(array, field_value);
464
+
465
+ if( field_value == field_str ){
466
+ /* Our output string will be send to the user, so we can not reuse
467
+ * it for the next field. */
468
+ PG_RB_STR_NEW( field_str, output_ptr, end_capa_ptr );
469
+ }
470
+ /* Reset the pointer to the start of the output/buffer string. */
471
+ output_ptr = RSTRING_PTR(field_str);
472
+ }
473
+
474
+ /* Skip comma that separates prior field from this one */
475
+ if (*cur_ptr == ',') {
476
+ cur_ptr++;
477
+ } else if (*cur_ptr == ')') {
478
+ cur_ptr++;
479
+ /* Done if we hit closing parenthesis */
480
+ break;
481
+ } else {
482
+ rb_raise( rb_eArgError, "malformed record literal: \"%s\" - Too few columns.", input_line );
483
+ }
484
+ }
485
+
486
+ /* Allow trailing whitespace */
487
+ while (*cur_ptr && record_isspace(*cur_ptr))
488
+ cur_ptr++;
489
+ if (*cur_ptr)
490
+ rb_raise( rb_eArgError, "malformed record literal: \"%s\" - Junk after right parenthesis.", input_line );
491
+
492
+ return array;
493
+ }
494
+
495
+
496
+ void
497
+ init_pg_recordcoder()
498
+ {
499
+ /* Document-class: PG::RecordCoder < PG::Coder
500
+ *
501
+ * This is the base class for all type cast classes for COPY data,
502
+ */
503
+ rb_cPG_RecordCoder = rb_define_class_under( rb_mPG, "RecordCoder", rb_cPG_Coder );
504
+ rb_define_method( rb_cPG_RecordCoder, "type_map=", pg_recordcoder_type_map_set, 1 );
505
+ rb_define_method( rb_cPG_RecordCoder, "type_map", pg_recordcoder_type_map_get, 0 );
506
+
507
+ /* Document-class: PG::RecordEncoder < PG::RecordCoder */
508
+ rb_cPG_RecordEncoder = rb_define_class_under( rb_mPG, "RecordEncoder", rb_cPG_RecordCoder );
509
+ rb_define_alloc_func( rb_cPG_RecordEncoder, pg_recordcoder_encoder_allocate );
510
+ /* Document-class: PG::RecordDecoder < PG::RecordCoder */
511
+ rb_cPG_RecordDecoder = rb_define_class_under( rb_mPG, "RecordDecoder", rb_cPG_RecordCoder );
512
+ rb_define_alloc_func( rb_cPG_RecordDecoder, pg_recordcoder_decoder_allocate );
513
+
514
+ /* Make RDoc aware of the encoder classes... */
515
+ /* rb_mPG_TextEncoder = rb_define_module_under( rb_mPG, "TextEncoder" ); */
516
+ /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Record", rb_cPG_RecordEncoder ); */
517
+ pg_define_coder( "Record", pg_text_enc_record, rb_cPG_RecordEncoder, rb_mPG_TextEncoder );
518
+ /* rb_mPG_TextDecoder = rb_define_module_under( rb_mPG, "TextDecoder" ); */
519
+ /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Record", rb_cPG_RecordDecoder ); */
520
+ pg_define_coder( "Record", pg_text_dec_record, rb_cPG_RecordDecoder, rb_mPG_TextDecoder );
521
+ }