pg 0.18.1 → 1.5.6

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