pg 1.1.3 → 1.3.3

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 (117) hide show
  1. checksums.yaml +4 -4
  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/Gemfile +14 -0
  15. data/History.rdoc +210 -6
  16. data/Manifest.txt +3 -3
  17. data/README-Windows.rdoc +4 -4
  18. data/README.ja.rdoc +1 -2
  19. data/README.rdoc +51 -15
  20. data/Rakefile +31 -140
  21. data/Rakefile.cross +60 -56
  22. data/certs/ged.pem +24 -0
  23. data/certs/larskanis-2022.pem +26 -0
  24. data/ext/errorcodes.def +76 -0
  25. data/ext/errorcodes.txt +21 -2
  26. data/ext/extconf.rb +101 -26
  27. data/ext/gvl_wrappers.c +4 -0
  28. data/ext/gvl_wrappers.h +23 -0
  29. data/ext/pg.c +190 -98
  30. data/ext/pg.h +42 -17
  31. data/ext/pg_binary_decoder.c +20 -16
  32. data/ext/pg_binary_encoder.c +13 -12
  33. data/ext/pg_coder.c +95 -29
  34. data/ext/pg_connection.c +1043 -769
  35. data/ext/pg_copy_coder.c +50 -18
  36. data/ext/pg_record_coder.c +519 -0
  37. data/ext/pg_result.c +326 -142
  38. data/ext/pg_text_decoder.c +15 -9
  39. data/ext/pg_text_encoder.c +185 -53
  40. data/ext/pg_tuple.c +61 -27
  41. data/ext/pg_type_map.c +42 -9
  42. data/ext/pg_type_map_all_strings.c +19 -5
  43. data/ext/pg_type_map_by_class.c +54 -24
  44. data/ext/pg_type_map_by_column.c +73 -34
  45. data/ext/pg_type_map_by_mri_type.c +48 -19
  46. data/ext/pg_type_map_by_oid.c +55 -25
  47. data/ext/pg_type_map_in_ruby.c +51 -20
  48. data/ext/{util.c → pg_util.c} +7 -7
  49. data/ext/{util.h → pg_util.h} +0 -0
  50. data/lib/pg/basic_type_map_based_on_result.rb +47 -0
  51. data/lib/pg/basic_type_map_for_queries.rb +193 -0
  52. data/lib/pg/basic_type_map_for_results.rb +81 -0
  53. data/lib/pg/basic_type_registry.rb +296 -0
  54. data/lib/pg/binary_decoder.rb +1 -0
  55. data/lib/pg/coder.rb +23 -2
  56. data/lib/pg/connection.rb +589 -59
  57. data/lib/pg/constants.rb +1 -0
  58. data/lib/pg/exceptions.rb +1 -0
  59. data/lib/pg/result.rb +13 -1
  60. data/lib/pg/text_decoder.rb +2 -3
  61. data/lib/pg/text_encoder.rb +8 -18
  62. data/lib/pg/type_map_by_column.rb +2 -1
  63. data/lib/pg/version.rb +4 -0
  64. data/lib/pg.rb +48 -33
  65. data/misc/openssl-pg-segfault.rb +31 -0
  66. data/misc/postgres/History.txt +9 -0
  67. data/misc/postgres/Manifest.txt +5 -0
  68. data/misc/postgres/README.txt +21 -0
  69. data/misc/postgres/Rakefile +21 -0
  70. data/misc/postgres/lib/postgres.rb +16 -0
  71. data/misc/ruby-pg/History.txt +9 -0
  72. data/misc/ruby-pg/Manifest.txt +5 -0
  73. data/misc/ruby-pg/README.txt +21 -0
  74. data/misc/ruby-pg/Rakefile +21 -0
  75. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  76. data/pg.gemspec +32 -0
  77. data/rakelib/task_extension.rb +46 -0
  78. data/sample/array_insert.rb +20 -0
  79. data/sample/async_api.rb +106 -0
  80. data/sample/async_copyto.rb +39 -0
  81. data/sample/async_mixed.rb +56 -0
  82. data/sample/check_conn.rb +21 -0
  83. data/sample/copydata.rb +71 -0
  84. data/sample/copyfrom.rb +81 -0
  85. data/sample/copyto.rb +19 -0
  86. data/sample/cursor.rb +21 -0
  87. data/sample/disk_usage_report.rb +177 -0
  88. data/sample/issue-119.rb +94 -0
  89. data/sample/losample.rb +69 -0
  90. data/sample/minimal-testcase.rb +17 -0
  91. data/sample/notify_wait.rb +72 -0
  92. data/sample/pg_statistics.rb +285 -0
  93. data/sample/replication_monitor.rb +222 -0
  94. data/sample/test_binary_values.rb +33 -0
  95. data/sample/wal_shipper.rb +434 -0
  96. data/sample/warehouse_partitions.rb +311 -0
  97. data.tar.gz.sig +0 -0
  98. metadata +94 -237
  99. metadata.gz.sig +0 -0
  100. data/ChangeLog +0 -6595
  101. data/lib/pg/basic_type_mapping.rb +0 -459
  102. data/spec/data/expected_trace.out +0 -26
  103. data/spec/data/random_binary_data +0 -0
  104. data/spec/helpers.rb +0 -381
  105. data/spec/pg/basic_type_mapping_spec.rb +0 -508
  106. data/spec/pg/connection_spec.rb +0 -1849
  107. data/spec/pg/connection_sync_spec.rb +0 -41
  108. data/spec/pg/result_spec.rb +0 -491
  109. data/spec/pg/tuple_spec.rb +0 -280
  110. data/spec/pg/type_map_by_class_spec.rb +0 -138
  111. data/spec/pg/type_map_by_column_spec.rb +0 -222
  112. data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
  113. data/spec/pg/type_map_by_oid_spec.rb +0 -149
  114. data/spec/pg/type_map_in_ruby_spec.rb +0 -164
  115. data/spec/pg/type_map_spec.rb +0 -22
  116. data/spec/pg/type_spec.rb +0 -949
  117. data/spec/pg_spec.rb +0 -50
data/ext/pg_copy_coder.c CHANGED
@@ -21,17 +21,47 @@ typedef struct {
21
21
 
22
22
 
23
23
  static void
24
- pg_copycoder_mark( t_pg_copycoder *this )
24
+ pg_copycoder_mark( void *_this )
25
25
  {
26
- rb_gc_mark(this->typemap);
27
- rb_gc_mark(this->null_string);
26
+ t_pg_copycoder *this = (t_pg_copycoder *)_this;
27
+ rb_gc_mark_movable(this->typemap);
28
+ rb_gc_mark_movable(this->null_string);
28
29
  }
29
30
 
31
+ static size_t
32
+ pg_copycoder_memsize( const void *_this )
33
+ {
34
+ const t_pg_copycoder *this = (const t_pg_copycoder *)_this;
35
+ return sizeof(*this);
36
+ }
37
+
38
+ static void
39
+ pg_copycoder_compact( void *_this )
40
+ {
41
+ t_pg_copycoder *this = (t_pg_copycoder *)_this;
42
+ pg_coder_compact(&this->comp);
43
+ pg_gc_location(this->typemap);
44
+ pg_gc_location(this->null_string);
45
+ }
46
+
47
+ static const rb_data_type_t pg_copycoder_type = {
48
+ "PG::CopyCoder",
49
+ {
50
+ pg_copycoder_mark,
51
+ RUBY_TYPED_DEFAULT_FREE,
52
+ pg_copycoder_memsize,
53
+ pg_compact_callback(pg_copycoder_compact),
54
+ },
55
+ &pg_coder_type,
56
+ 0,
57
+ RUBY_TYPED_FREE_IMMEDIATELY,
58
+ };
59
+
30
60
  static VALUE
31
61
  pg_copycoder_encoder_allocate( VALUE klass )
32
62
  {
33
63
  t_pg_copycoder *this;
34
- VALUE self = Data_Make_Struct( klass, t_pg_copycoder, pg_copycoder_mark, -1, this );
64
+ VALUE self = TypedData_Make_Struct( klass, t_pg_copycoder, &pg_copycoder_type, this );
35
65
  pg_coder_init_encoder( self );
36
66
  this->typemap = pg_typemap_all_strings;
37
67
  this->delimiter = '\t';
@@ -43,7 +73,7 @@ static VALUE
43
73
  pg_copycoder_decoder_allocate( VALUE klass )
44
74
  {
45
75
  t_pg_copycoder *this;
46
- VALUE self = Data_Make_Struct( klass, t_pg_copycoder, pg_copycoder_mark, -1, this );
76
+ VALUE self = TypedData_Make_Struct( klass, t_pg_copycoder, &pg_copycoder_type, this );
47
77
  pg_coder_init_decoder( self );
48
78
  this->typemap = pg_typemap_all_strings;
49
79
  this->delimiter = '\t';
@@ -62,7 +92,7 @@ pg_copycoder_decoder_allocate( VALUE klass )
62
92
  static VALUE
63
93
  pg_copycoder_delimiter_set(VALUE self, VALUE delimiter)
64
94
  {
65
- t_pg_copycoder *this = DATA_PTR(self);
95
+ t_pg_copycoder *this = RTYPEDDATA_DATA(self);
66
96
  StringValue(delimiter);
67
97
  if(RSTRING_LEN(delimiter) != 1)
68
98
  rb_raise( rb_eArgError, "delimiter size must be one byte");
@@ -79,7 +109,7 @@ pg_copycoder_delimiter_set(VALUE self, VALUE delimiter)
79
109
  static VALUE
80
110
  pg_copycoder_delimiter_get(VALUE self)
81
111
  {
82
- t_pg_copycoder *this = DATA_PTR(self);
112
+ t_pg_copycoder *this = RTYPEDDATA_DATA(self);
83
113
  return rb_str_new(&this->delimiter, 1);
84
114
  }
85
115
 
@@ -92,7 +122,7 @@ pg_copycoder_delimiter_get(VALUE self)
92
122
  static VALUE
93
123
  pg_copycoder_null_string_set(VALUE self, VALUE null_string)
94
124
  {
95
- t_pg_copycoder *this = DATA_PTR(self);
125
+ t_pg_copycoder *this = RTYPEDDATA_DATA(self);
96
126
  StringValue(null_string);
97
127
  this->null_string = null_string;
98
128
  return null_string;
@@ -104,7 +134,7 @@ pg_copycoder_null_string_set(VALUE self, VALUE null_string)
104
134
  static VALUE
105
135
  pg_copycoder_null_string_get(VALUE self)
106
136
  {
107
- t_pg_copycoder *this = DATA_PTR(self);
137
+ t_pg_copycoder *this = RTYPEDDATA_DATA(self);
108
138
  return this->null_string;
109
139
  }
110
140
 
@@ -112,16 +142,17 @@ pg_copycoder_null_string_get(VALUE self)
112
142
  * call-seq:
113
143
  * coder.type_map = map
114
144
  *
145
+ * Defines how single columns are encoded or decoded.
115
146
  * +map+ must be a kind of PG::TypeMap .
116
147
  *
117
148
  * Defaults to a PG::TypeMapAllStrings , so that PG::TextEncoder::String respectively
118
- * PG::TextDecoder::String is used for encoding/decoding of all columns.
149
+ * PG::TextDecoder::String is used for encoding/decoding of each column.
119
150
  *
120
151
  */
121
152
  static VALUE
122
153
  pg_copycoder_type_map_set(VALUE self, VALUE type_map)
123
154
  {
124
- t_pg_copycoder *this = DATA_PTR( self );
155
+ t_pg_copycoder *this = RTYPEDDATA_DATA( self );
125
156
 
126
157
  if ( !rb_obj_is_kind_of(type_map, rb_cTypeMap) ){
127
158
  rb_raise( rb_eTypeError, "wrong elements type %s (expected some kind of PG::TypeMap)",
@@ -136,11 +167,12 @@ pg_copycoder_type_map_set(VALUE self, VALUE type_map)
136
167
  * call-seq:
137
168
  * coder.type_map -> PG::TypeMap
138
169
  *
170
+ * The PG::TypeMap that will be used for encoding and decoding of columns.
139
171
  */
140
172
  static VALUE
141
173
  pg_copycoder_type_map_get(VALUE self)
142
174
  {
143
- t_pg_copycoder *this = DATA_PTR( self );
175
+ t_pg_copycoder *this = RTYPEDDATA_DATA( self );
144
176
 
145
177
  return this->typemap;
146
178
  }
@@ -185,7 +217,7 @@ pg_text_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermedia
185
217
  char *current_out;
186
218
  char *end_capa_ptr;
187
219
 
188
- p_typemap = DATA_PTR( this->typemap );
220
+ p_typemap = RTYPEDDATA_DATA( this->typemap );
189
221
  p_typemap->funcs.fit_to_query( this->typemap, value );
190
222
 
191
223
  /* Allocate a new string with embedded capacity and realloc exponential when needed. */
@@ -222,7 +254,7 @@ pg_text_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermedia
222
254
 
223
255
  if( strlen == -1 ){
224
256
  /* we can directly use String value in subint */
225
- strlen = RSTRING_LEN(subint);
257
+ strlen = RSTRING_LENINT(subint);
226
258
 
227
259
  /* size of string assuming the worst case, that every character must be escaped. */
228
260
  PG_RB_STR_ENSURE_CAPA( *intermediate, strlen * 2, current_out, end_capa_ptr );
@@ -373,7 +405,7 @@ pg_text_dec_copy_row(t_pg_coder *conv, const char *input_line, int len, int _tup
373
405
  char *end_capa_ptr;
374
406
  t_typemap *p_typemap;
375
407
 
376
- p_typemap = DATA_PTR( this->typemap );
408
+ p_typemap = RTYPEDDATA_DATA( this->typemap );
377
409
  expected_fields = p_typemap->funcs.fit_to_copy_get( this->typemap );
378
410
 
379
411
  /* The received input string will probably have this->nfields fields. */
@@ -381,7 +413,7 @@ pg_text_dec_copy_row(t_pg_coder *conv, const char *input_line, int len, int _tup
381
413
 
382
414
  /* Allocate a new string with embedded capacity and realloc later with
383
415
  * exponential growing size when needed. */
384
- PG_RB_TAINTED_STR_NEW( field_str, output_ptr, end_capa_ptr );
416
+ PG_RB_STR_NEW( field_str, output_ptr, end_capa_ptr );
385
417
 
386
418
  /* set pointer variables for loop */
387
419
  cur_ptr = input_line;
@@ -394,7 +426,7 @@ pg_text_dec_copy_row(t_pg_coder *conv, const char *input_line, int len, int _tup
394
426
  int found_delim = 0;
395
427
  const char *start_ptr;
396
428
  const char *end_ptr;
397
- int input_len;
429
+ long input_len;
398
430
 
399
431
  /* Remember start of field on input side */
400
432
  start_ptr = cur_ptr;
@@ -543,7 +575,7 @@ pg_text_dec_copy_row(t_pg_coder *conv, const char *input_line, int len, int _tup
543
575
  if( field_value == field_str ){
544
576
  /* Our output string will be send to the user, so we can not reuse
545
577
  * it for the next field. */
546
- PG_RB_TAINTED_STR_NEW( field_str, output_ptr, end_capa_ptr );
578
+ PG_RB_STR_NEW( field_str, output_ptr, end_capa_ptr );
547
579
  }
548
580
  }
549
581
  /* Reset the pointer to the start of the output/buffer string. */
@@ -0,0 +1,519 @@
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
+ * # Register a record decoder for decoding our type "complex"
348
+ * PG::BasicTypeRegistry.register_coder(PG::TextDecoder::Record.new(type_map: dtm, name: "complex"))
349
+ * # Apply the basic type registry to all results retrieved from the server
350
+ * conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn)
351
+ * # Now queries decode the "complex" type (and many basic types) automatically
352
+ * conn.exec("SELECT * FROM my_table").to_a
353
+ * # => [{"v1"=>[2.0, 3.0], "v2"=>[4.0, 5.0]}, {"v1"=>[6.0, 7.0], "v2"=>[8.0, 9.0]}]
354
+ *
355
+ * Records can also be nested or further wrapped into other decoders like PG::TextDecoder::CopyRow.
356
+ *
357
+ * See also PG::TextEncoder::Record for the encoding direction (data sent to the server).
358
+ */
359
+ /*
360
+ * Parse the current line into separate attributes (fields),
361
+ * performing de-escaping as needed.
362
+ *
363
+ * All fields are gathered into a ruby Array. The de-escaped field data is written
364
+ * into to a ruby String. This object is reused for non string columns.
365
+ * For String columns the field value is directly used as return value and no
366
+ * reuse of the memory is done.
367
+ *
368
+ * The parser is thankfully borrowed from the PostgreSQL sources:
369
+ * src/backend/utils/adt/rowtypes.c
370
+ */
371
+ static VALUE
372
+ pg_text_dec_record(t_pg_coder *conv, char *input_line, int len, int _tuple, int _field, int enc_idx)
373
+ {
374
+ t_pg_recordcoder *this = (t_pg_recordcoder *)conv;
375
+
376
+ /* Return value: array */
377
+ VALUE array;
378
+
379
+ /* Current field */
380
+ VALUE field_str;
381
+
382
+ int fieldno;
383
+ int expected_fields;
384
+ char *output_ptr;
385
+ char *cur_ptr;
386
+ char *end_capa_ptr;
387
+ t_typemap *p_typemap;
388
+
389
+ p_typemap = RTYPEDDATA_DATA( this->typemap );
390
+ expected_fields = p_typemap->funcs.fit_to_copy_get( this->typemap );
391
+
392
+ /* The received input string will probably have this->nfields fields. */
393
+ array = rb_ary_new2(expected_fields);
394
+
395
+ /* Allocate a new string with embedded capacity and realloc later with
396
+ * exponential growing size when needed. */
397
+ PG_RB_STR_NEW( field_str, output_ptr, end_capa_ptr );
398
+
399
+ /* set pointer variables for loop */
400
+ cur_ptr = input_line;
401
+
402
+ /*
403
+ * Scan the string. We use "buf" to accumulate the de-quoted data for
404
+ * each column, which is then fed to the appropriate input converter.
405
+ */
406
+ /* Allow leading whitespace */
407
+ while (*cur_ptr && record_isspace(*cur_ptr))
408
+ cur_ptr++;
409
+ if (*cur_ptr++ != '(')
410
+ rb_raise( rb_eArgError, "malformed record literal: \"%s\" - Missing left parenthesis.", input_line );
411
+
412
+ for (fieldno = 0; ; fieldno++)
413
+ {
414
+ /* Check for null: completely empty input means null */
415
+ if (*cur_ptr == ',' || *cur_ptr == ')')
416
+ {
417
+ rb_ary_push(array, Qnil);
418
+ }
419
+ else
420
+ {
421
+ /* Extract string for this column */
422
+ int inquote = 0;
423
+ VALUE field_value;
424
+
425
+ while (inquote || !(*cur_ptr == ',' || *cur_ptr == ')'))
426
+ {
427
+ char ch = *cur_ptr++;
428
+
429
+ if (ch == '\0')
430
+ rb_raise( rb_eArgError, "malformed record literal: \"%s\" - Unexpected end of input.", input_line );
431
+ if (ch == '\\')
432
+ {
433
+ if (*cur_ptr == '\0')
434
+ rb_raise( rb_eArgError, "malformed record literal: \"%s\" - Unexpected end of input.", input_line );
435
+ PG_RB_STR_ENSURE_CAPA( field_str, 1, output_ptr, end_capa_ptr );
436
+ *output_ptr++ = *cur_ptr++;
437
+ }
438
+ else if (ch == '"')
439
+ {
440
+ if (!inquote)
441
+ inquote = 1;
442
+ else if (*cur_ptr == '"')
443
+ {
444
+ /* doubled quote within quote sequence */
445
+ PG_RB_STR_ENSURE_CAPA( field_str, 1, output_ptr, end_capa_ptr );
446
+ *output_ptr++ = *cur_ptr++;
447
+ }
448
+ else
449
+ inquote = 0;
450
+ } else {
451
+ PG_RB_STR_ENSURE_CAPA( field_str, 1, output_ptr, end_capa_ptr );
452
+ /* Add ch to output string */
453
+ *output_ptr++ = ch;
454
+ }
455
+ }
456
+
457
+ /* Convert the column value */
458
+ rb_str_set_len( field_str, output_ptr - RSTRING_PTR(field_str) );
459
+ field_value = p_typemap->funcs.typecast_copy_get( p_typemap, field_str, fieldno, 0, enc_idx );
460
+
461
+ rb_ary_push(array, field_value);
462
+
463
+ if( field_value == field_str ){
464
+ /* Our output string will be send to the user, so we can not reuse
465
+ * it for the next field. */
466
+ PG_RB_STR_NEW( field_str, output_ptr, end_capa_ptr );
467
+ }
468
+ /* Reset the pointer to the start of the output/buffer string. */
469
+ output_ptr = RSTRING_PTR(field_str);
470
+ }
471
+
472
+ /* Skip comma that separates prior field from this one */
473
+ if (*cur_ptr == ',') {
474
+ cur_ptr++;
475
+ } else if (*cur_ptr == ')') {
476
+ cur_ptr++;
477
+ /* Done if we hit closing parenthesis */
478
+ break;
479
+ } else {
480
+ rb_raise( rb_eArgError, "malformed record literal: \"%s\" - Too few columns.", input_line );
481
+ }
482
+ }
483
+
484
+ /* Allow trailing whitespace */
485
+ while (*cur_ptr && record_isspace(*cur_ptr))
486
+ cur_ptr++;
487
+ if (*cur_ptr)
488
+ rb_raise( rb_eArgError, "malformed record literal: \"%s\" - Junk after right parenthesis.", input_line );
489
+
490
+ return array;
491
+ }
492
+
493
+
494
+ void
495
+ init_pg_recordcoder()
496
+ {
497
+ /* Document-class: PG::RecordCoder < PG::Coder
498
+ *
499
+ * This is the base class for all type cast classes for COPY data,
500
+ */
501
+ rb_cPG_RecordCoder = rb_define_class_under( rb_mPG, "RecordCoder", rb_cPG_Coder );
502
+ rb_define_method( rb_cPG_RecordCoder, "type_map=", pg_recordcoder_type_map_set, 1 );
503
+ rb_define_method( rb_cPG_RecordCoder, "type_map", pg_recordcoder_type_map_get, 0 );
504
+
505
+ /* Document-class: PG::RecordEncoder < PG::RecordCoder */
506
+ rb_cPG_RecordEncoder = rb_define_class_under( rb_mPG, "RecordEncoder", rb_cPG_RecordCoder );
507
+ rb_define_alloc_func( rb_cPG_RecordEncoder, pg_recordcoder_encoder_allocate );
508
+ /* Document-class: PG::RecordDecoder < PG::RecordCoder */
509
+ rb_cPG_RecordDecoder = rb_define_class_under( rb_mPG, "RecordDecoder", rb_cPG_RecordCoder );
510
+ rb_define_alloc_func( rb_cPG_RecordDecoder, pg_recordcoder_decoder_allocate );
511
+
512
+ /* Make RDoc aware of the encoder classes... */
513
+ /* rb_mPG_TextEncoder = rb_define_module_under( rb_mPG, "TextEncoder" ); */
514
+ /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Record", rb_cPG_RecordEncoder ); */
515
+ pg_define_coder( "Record", pg_text_enc_record, rb_cPG_RecordEncoder, rb_mPG_TextEncoder );
516
+ /* rb_mPG_TextDecoder = rb_define_module_under( rb_mPG, "TextDecoder" ); */
517
+ /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Record", rb_cPG_RecordDecoder ); */
518
+ pg_define_coder( "Record", pg_text_dec_record, rb_cPG_RecordDecoder, rb_mPG_TextDecoder );
519
+ }