pg 1.2.3 → 1.4.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  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 +137 -0
  7. data/.gitignore +19 -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.md +804 -0
  16. data/Manifest.txt +0 -1
  17. data/README.ja.md +266 -0
  18. data/README.md +272 -0
  19. data/Rakefile +33 -135
  20. data/Rakefile.cross +12 -13
  21. data/certs/ged.pem +24 -0
  22. data/certs/larskanis-2022.pem +26 -0
  23. data/certs/larskanis-2023.pem +24 -0
  24. data/ext/errorcodes.def +12 -0
  25. data/ext/errorcodes.rb +0 -0
  26. data/ext/errorcodes.txt +4 -1
  27. data/ext/extconf.rb +100 -25
  28. data/ext/gvl_wrappers.c +4 -0
  29. data/ext/gvl_wrappers.h +23 -0
  30. data/ext/pg.c +62 -29
  31. data/ext/pg.h +20 -1
  32. data/ext/pg_binary_decoder.c +1 -1
  33. data/ext/pg_binary_encoder.c +1 -1
  34. data/ext/pg_coder.c +83 -29
  35. data/ext/pg_connection.c +953 -670
  36. data/ext/pg_copy_coder.c +46 -17
  37. data/ext/pg_errors.c +1 -1
  38. data/ext/pg_record_coder.c +46 -16
  39. data/ext/pg_result.c +88 -49
  40. data/ext/pg_text_decoder.c +2 -2
  41. data/ext/pg_text_encoder.c +7 -7
  42. data/ext/pg_tuple.c +50 -30
  43. data/ext/pg_type_map.c +42 -9
  44. data/ext/pg_type_map_all_strings.c +16 -2
  45. data/ext/pg_type_map_by_class.c +50 -25
  46. data/ext/pg_type_map_by_column.c +68 -30
  47. data/ext/pg_type_map_by_mri_type.c +48 -19
  48. data/ext/pg_type_map_by_oid.c +53 -24
  49. data/ext/pg_type_map_in_ruby.c +51 -20
  50. data/ext/pg_util.c +2 -2
  51. data/lib/pg/basic_type_map_based_on_result.rb +47 -0
  52. data/lib/pg/basic_type_map_for_queries.rb +193 -0
  53. data/lib/pg/basic_type_map_for_results.rb +81 -0
  54. data/lib/pg/basic_type_registry.rb +301 -0
  55. data/lib/pg/coder.rb +1 -1
  56. data/lib/pg/connection.rb +669 -81
  57. data/lib/pg/exceptions.rb +14 -1
  58. data/lib/pg/version.rb +4 -0
  59. data/lib/pg.rb +45 -36
  60. data/misc/openssl-pg-segfault.rb +31 -0
  61. data/misc/postgres/History.txt +9 -0
  62. data/misc/postgres/Manifest.txt +5 -0
  63. data/misc/postgres/README.txt +21 -0
  64. data/misc/postgres/Rakefile +21 -0
  65. data/misc/postgres/lib/postgres.rb +16 -0
  66. data/misc/ruby-pg/History.txt +9 -0
  67. data/misc/ruby-pg/Manifest.txt +5 -0
  68. data/misc/ruby-pg/README.txt +21 -0
  69. data/misc/ruby-pg/Rakefile +21 -0
  70. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  71. data/pg.gemspec +34 -0
  72. data/rakelib/task_extension.rb +46 -0
  73. data/sample/array_insert.rb +20 -0
  74. data/sample/async_api.rb +102 -0
  75. data/sample/async_copyto.rb +39 -0
  76. data/sample/async_mixed.rb +56 -0
  77. data/sample/check_conn.rb +21 -0
  78. data/sample/copydata.rb +71 -0
  79. data/sample/copyfrom.rb +81 -0
  80. data/sample/copyto.rb +19 -0
  81. data/sample/cursor.rb +21 -0
  82. data/sample/disk_usage_report.rb +177 -0
  83. data/sample/issue-119.rb +94 -0
  84. data/sample/losample.rb +69 -0
  85. data/sample/minimal-testcase.rb +17 -0
  86. data/sample/notify_wait.rb +72 -0
  87. data/sample/pg_statistics.rb +285 -0
  88. data/sample/replication_monitor.rb +222 -0
  89. data/sample/test_binary_values.rb +33 -0
  90. data/sample/wal_shipper.rb +434 -0
  91. data/sample/warehouse_partitions.rb +311 -0
  92. data/translation/.po4a-version +7 -0
  93. data/translation/po/all.pot +875 -0
  94. data/translation/po/ja.po +868 -0
  95. data/translation/po4a.cfg +9 -0
  96. data.tar.gz.sig +0 -0
  97. metadata +120 -206
  98. metadata.gz.sig +0 -0
  99. data/ChangeLog +0 -0
  100. data/History.rdoc +0 -578
  101. data/README.ja.rdoc +0 -13
  102. data/README.rdoc +0 -213
  103. data/lib/pg/basic_type_mapping.rb +0 -522
  104. data/spec/data/expected_trace.out +0 -26
  105. data/spec/data/random_binary_data +0 -0
  106. data/spec/helpers.rb +0 -380
  107. data/spec/pg/basic_type_mapping_spec.rb +0 -630
  108. data/spec/pg/connection_spec.rb +0 -1949
  109. data/spec/pg/connection_sync_spec.rb +0 -41
  110. data/spec/pg/result_spec.rb +0 -681
  111. data/spec/pg/tuple_spec.rb +0 -333
  112. data/spec/pg/type_map_by_class_spec.rb +0 -138
  113. data/spec/pg/type_map_by_column_spec.rb +0 -226
  114. data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
  115. data/spec/pg/type_map_by_oid_spec.rb +0 -149
  116. data/spec/pg/type_map_in_ruby_spec.rb +0 -164
  117. data/spec/pg/type_map_spec.rb +0 -22
  118. data/spec/pg/type_spec.rb +0 -1123
  119. data/spec/pg_spec.rb +0 -50
@@ -19,6 +19,33 @@ typedef struct {
19
19
  VALUE self;
20
20
  } t_tmir;
21
21
 
22
+ static size_t
23
+ pg_tmir_memsize( const void *_this )
24
+ {
25
+ const t_tmir *this = (const t_tmir *)_this;
26
+ return sizeof(*this);
27
+ }
28
+
29
+ static void
30
+ pg_tmir_compact( void *_this )
31
+ {
32
+ t_tmir *this = (t_tmir *)_this;
33
+ pg_typemap_compact(&this->typemap);
34
+ pg_gc_location(this->self);
35
+ }
36
+
37
+ static const rb_data_type_t pg_tmir_type = {
38
+ "PG::TypeMapInRuby",
39
+ {
40
+ pg_typemap_mark,
41
+ RUBY_TYPED_DEFAULT_FREE,
42
+ pg_tmir_memsize,
43
+ pg_compact_callback(pg_tmir_compact),
44
+ },
45
+ &pg_typemap_type,
46
+ 0,
47
+ RUBY_TYPED_FREE_IMMEDIATELY,
48
+ };
22
49
 
23
50
  /*
24
51
  * call-seq:
@@ -34,33 +61,37 @@ typedef struct {
34
61
  static VALUE
35
62
  pg_tmir_fit_to_result( VALUE self, VALUE result )
36
63
  {
37
- t_tmir *this = DATA_PTR( self );
64
+ t_tmir *this = RTYPEDDATA_DATA( self );
38
65
  t_typemap *default_tm;
39
66
  t_typemap *p_new_typemap;
40
67
  VALUE sub_typemap;
41
68
  VALUE new_typemap;
42
69
 
43
70
  if( rb_respond_to(self, s_id_fit_to_result) ){
71
+ t_typemap *tm;
72
+ UNUSED(tm);
44
73
  new_typemap = rb_funcall( self, s_id_fit_to_result, 1, result );
45
74
 
46
75
  if ( !rb_obj_is_kind_of(new_typemap, rb_cTypeMap) ) {
76
+ /* TypedData_Get_Struct() raises "wrong argument type", which is misleading,
77
+ * so we better raise our own message */
47
78
  rb_raise( rb_eTypeError, "wrong return type from fit_to_result: %s expected kind of PG::TypeMap",
48
79
  rb_obj_classname( new_typemap ) );
49
80
  }
50
- Check_Type( new_typemap, T_DATA );
81
+ TypedData_Get_Struct(new_typemap, t_typemap, &pg_typemap_type, tm);
51
82
  } else {
52
83
  new_typemap = self;
53
84
  }
54
85
 
55
- /* Ensure that the default type map fits equaly. */
56
- default_tm = DATA_PTR( this->typemap.default_typemap );
86
+ /* Ensure that the default type map fits equally. */
87
+ default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
57
88
  sub_typemap = default_tm->funcs.fit_to_result( this->typemap.default_typemap, result );
58
89
 
59
90
  if( sub_typemap != this->typemap.default_typemap ){
60
91
  new_typemap = rb_obj_dup( new_typemap );
61
92
  }
62
93
 
63
- p_new_typemap = DATA_PTR(new_typemap);
94
+ p_new_typemap = RTYPEDDATA_DATA(new_typemap);
64
95
  p_new_typemap->default_typemap = sub_typemap;
65
96
  return new_typemap;
66
97
  }
@@ -95,8 +126,8 @@ pg_tmir_result_value( t_typemap *p_typemap, VALUE result, int tuple, int field )
95
126
  static VALUE
96
127
  pg_tmir_typecast_result_value( VALUE self, VALUE result, VALUE tuple, VALUE field )
97
128
  {
98
- t_tmir *this = DATA_PTR( self );
99
- t_typemap *default_tm = DATA_PTR( this->typemap.default_typemap );
129
+ t_tmir *this = RTYPEDDATA_DATA( self );
130
+ t_typemap *default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
100
131
  return default_tm->funcs.typecast_result_value( default_tm, result, NUM2INT(tuple), NUM2INT(field) );
101
132
  }
102
133
 
@@ -113,15 +144,15 @@ pg_tmir_typecast_result_value( VALUE self, VALUE result, VALUE tuple, VALUE fiel
113
144
  static VALUE
114
145
  pg_tmir_fit_to_query( VALUE self, VALUE params )
115
146
  {
116
- t_tmir *this = DATA_PTR( self );
147
+ t_tmir *this = RTYPEDDATA_DATA( self );
117
148
  t_typemap *default_tm;
118
149
 
119
150
  if( rb_respond_to(self, s_id_fit_to_query) ){
120
151
  rb_funcall( self, s_id_fit_to_query, 1, params );
121
152
  }
122
153
 
123
- /* Ensure that the default type map fits equaly. */
124
- default_tm = DATA_PTR( this->typemap.default_typemap );
154
+ /* Ensure that the default type map fits equally. */
155
+ default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
125
156
  default_tm->funcs.fit_to_query( this->typemap.default_typemap, params );
126
157
 
127
158
  return self;
@@ -137,7 +168,7 @@ pg_tmir_query_param( t_typemap *p_typemap, VALUE param_value, int field )
137
168
  if ( NIL_P(coder) ){
138
169
  return NULL;
139
170
  } else if( rb_obj_is_kind_of(coder, rb_cPG_Coder) ) {
140
- return DATA_PTR(coder);
171
+ return RTYPEDDATA_DATA(coder);
141
172
  } else {
142
173
  rb_raise( rb_eTypeError, "wrong return type from typecast_query_param: %s expected nil or kind of PG::Coder",
143
174
  rb_obj_classname( coder ) );
@@ -161,8 +192,8 @@ pg_tmir_query_param( t_typemap *p_typemap, VALUE param_value, int field )
161
192
  static VALUE
162
193
  pg_tmir_typecast_query_param( VALUE self, VALUE param_value, VALUE field )
163
194
  {
164
- t_tmir *this = DATA_PTR( self );
165
- t_typemap *default_tm = DATA_PTR( this->typemap.default_typemap );
195
+ t_tmir *this = RTYPEDDATA_DATA( self );
196
+ t_typemap *default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
166
197
  t_pg_coder *p_coder = default_tm->funcs.typecast_query_param( default_tm, param_value, NUM2INT(field) );
167
198
 
168
199
  return p_coder ? p_coder->coder_obj : Qnil;
@@ -186,7 +217,7 @@ static VALUE pg_tmir_fit_to_copy_get_dummy( VALUE self ){}
186
217
  static int
187
218
  pg_tmir_fit_to_copy_get( VALUE self )
188
219
  {
189
- t_tmir *this = DATA_PTR( self );
220
+ t_tmir *this = RTYPEDDATA_DATA( self );
190
221
  t_typemap *default_tm;
191
222
  VALUE num_columns = INT2NUM(0);
192
223
 
@@ -198,8 +229,8 @@ pg_tmir_fit_to_copy_get( VALUE self )
198
229
  rb_raise( rb_eTypeError, "wrong return type from fit_to_copy_get: %s expected kind of Integer",
199
230
  rb_obj_classname( num_columns ) );
200
231
  }
201
- /* Ensure that the default type map fits equaly. */
202
- default_tm = DATA_PTR( this->typemap.default_typemap );
232
+ /* Ensure that the default type map fits equally. */
233
+ default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
203
234
  default_tm->funcs.fit_to_copy_get( this->typemap.default_typemap );
204
235
 
205
236
  return NUM2INT(num_columns);;
@@ -239,8 +270,8 @@ pg_tmir_copy_get( t_typemap *p_typemap, VALUE field_str, int fieldno, int format
239
270
  static VALUE
240
271
  pg_tmir_typecast_copy_get( VALUE self, VALUE field_str, VALUE fieldno, VALUE format, VALUE enc )
241
272
  {
242
- t_tmir *this = DATA_PTR( self );
243
- t_typemap *default_tm = DATA_PTR( this->typemap.default_typemap );
273
+ t_tmir *this = RTYPEDDATA_DATA( self );
274
+ t_typemap *default_tm = RTYPEDDATA_DATA( this->typemap.default_typemap );
244
275
  int enc_idx = rb_to_encoding_index( enc );
245
276
 
246
277
  return default_tm->funcs.typecast_copy_get( default_tm, field_str, NUM2INT(fieldno), NUM2INT(format), enc_idx );
@@ -252,7 +283,7 @@ pg_tmir_s_allocate( VALUE klass )
252
283
  t_tmir *this;
253
284
  VALUE self;
254
285
 
255
- self = Data_Make_Struct( klass, t_tmir, NULL, -1, this );
286
+ self = TypedData_Make_Struct( klass, t_tmir, &pg_tmir_type, this );
256
287
 
257
288
  this->typemap.funcs.fit_to_result = pg_tmir_fit_to_result;
258
289
  this->typemap.funcs.fit_to_query = pg_tmir_fit_to_query;
@@ -268,7 +299,7 @@ pg_tmir_s_allocate( VALUE klass )
268
299
 
269
300
 
270
301
  void
271
- init_pg_type_map_in_ruby()
302
+ init_pg_type_map_in_ruby(void)
272
303
  {
273
304
  s_id_fit_to_result = rb_intern("fit_to_result");
274
305
  s_id_fit_to_query = rb_intern("fit_to_query");
data/ext/pg_util.c CHANGED
@@ -91,7 +91,7 @@ base64_decode( char *out, const char *in, unsigned int len)
91
91
  *out_ptr++ = (b << 4) | (c >> 2);
92
92
  *out_ptr++ = (c << 6) | d;
93
93
  } else if (in_ptr < iend_ptr){
94
- a = b = c = d = 0xff;
94
+ b = c = d = 0xff;
95
95
  while ((a = base64_decode_table[*in_ptr++]) == 0xff && in_ptr < iend_ptr) {}
96
96
  if (in_ptr < iend_ptr){
97
97
  while ((b = base64_decode_table[*in_ptr++]) == 0xff && in_ptr < iend_ptr) {}
@@ -116,7 +116,7 @@ base64_decode( char *out, const char *in, unsigned int len)
116
116
  }
117
117
 
118
118
 
119
- return (char*)out_ptr - out;
119
+ return (int)((char*)out_ptr - out);
120
120
  }
121
121
 
122
122
  /*
@@ -0,0 +1,47 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'pg' unless defined?( PG )
5
+
6
+ # Simple set of rules for type casting common PostgreSQL types from Ruby
7
+ # to PostgreSQL.
8
+ #
9
+ # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
10
+ # PostgreSQL's +pg_type+ table in PG::BasicTypeMapBasedOnResult.new .
11
+ #
12
+ # This class works equal to PG::BasicTypeMapForResults, but does not define decoders for
13
+ # the given result OIDs, but encoders. So it can be used to type cast field values based on
14
+ # the type OID retrieved by a separate SQL query.
15
+ #
16
+ # PG::TypeMapByOid#build_column_map(result) can be used to generate a result independent
17
+ # PG::TypeMapByColumn type map, which can subsequently be used to cast query bind parameters
18
+ # or #put_copy_data fields.
19
+ #
20
+ # Example:
21
+ # conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
22
+ #
23
+ # # Retrieve table OIDs per empty result set.
24
+ # res = conn.exec( "SELECT * FROM copytable LIMIT 0" )
25
+ # # Build a type map for common ruby to database type encoders.
26
+ # btm = PG::BasicTypeMapBasedOnResult.new(conn)
27
+ # # Build a PG::TypeMapByColumn with encoders suitable for copytable.
28
+ # tm = btm.build_column_map( res )
29
+ # row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
30
+ #
31
+ # conn.copy_data( "COPY copytable FROM STDIN", row_encoder ) do |res|
32
+ # conn.put_copy_data ['a', 123, [5,4,3]]
33
+ # end
34
+ # This inserts a single row into copytable with type casts from ruby to
35
+ # database types.
36
+ class PG::BasicTypeMapBasedOnResult < PG::TypeMapByOid
37
+ include PG::BasicTypeRegistry::Checker
38
+
39
+ def initialize(connection_or_coder_maps, registry: nil)
40
+ @coder_maps = build_coder_maps(connection_or_coder_maps, registry: registry)
41
+
42
+ # Populate TypeMapByOid hash with encoders
43
+ @coder_maps.each_format(:encoder).flat_map{|f| f.coders }.each do |coder|
44
+ add_coder(coder)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,193 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'pg' unless defined?( PG )
5
+
6
+ # Simple set of rules for type casting common Ruby types to PostgreSQL.
7
+ #
8
+ # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
9
+ # PostgreSQL's pg_type table in PG::BasicTypeMapForQueries.new .
10
+ #
11
+ # Query params are type casted based on the class of the given value.
12
+ #
13
+ # Higher level libraries will most likely not make use of this class, but use their
14
+ # own derivation of PG::TypeMapByClass or another set of rules to choose suitable
15
+ # encoders and decoders for the values to be sent.
16
+ #
17
+ # Example:
18
+ # conn = PG::Connection.new
19
+ # # Assign a default ruleset for type casts of input and output values.
20
+ # conn.type_map_for_queries = PG::BasicTypeMapForQueries.new(conn)
21
+ # # Execute a query. The Integer param value is typecasted internally by PG::BinaryEncoder::Int8.
22
+ # # The format of the parameter is set to 0 (text) and the OID of this parameter is set to 20 (int8).
23
+ # res = conn.exec_params( "SELECT $1", [5] )
24
+ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
25
+ # Helper class for submission of binary strings into bytea columns.
26
+ #
27
+ # Since PG::BasicTypeMapForQueries chooses the encoder to be used by the class of the submitted value,
28
+ # it's necessary to send binary strings as BinaryData.
29
+ # That way they're distinct from text strings.
30
+ # Please note however that PG::BasicTypeMapForResults delivers bytea columns as plain String
31
+ # with binary encoding.
32
+ #
33
+ # conn.type_map_for_queries = PG::BasicTypeMapForQueries.new(conn)
34
+ # conn.exec("CREATE TEMP TABLE test (data bytea)")
35
+ # bd = PG::BasicTypeMapForQueries::BinaryData.new("ab\xff\0cd")
36
+ # conn.exec_params("INSERT INTO test (data) VALUES ($1)", [bd])
37
+ class BinaryData < String
38
+ end
39
+
40
+ class UndefinedEncoder < RuntimeError
41
+ end
42
+
43
+ include PG::BasicTypeRegistry::Checker
44
+
45
+ # Create a new type map for query submission
46
+ #
47
+ # Options:
48
+ # * +registry+: Custom type registry, nil for default global registry
49
+ # * +if_undefined+: Optional +Proc+ object which is called, if no type for an parameter class is not defined in the registry.
50
+ def initialize(connection_or_coder_maps, registry: nil, if_undefined: nil)
51
+ @coder_maps = build_coder_maps(connection_or_coder_maps, registry: registry)
52
+ @array_encoders_by_klass = array_encoders_by_klass
53
+ @encode_array_as = :array
54
+ @if_undefined = if_undefined || proc { |oid_name, format|
55
+ raise UndefinedEncoder, "no encoder defined for type #{oid_name.inspect} format #{format}"
56
+ }
57
+ init_encoders
58
+ end
59
+
60
+ # Change the mechanism that is used to encode ruby array values
61
+ #
62
+ # Possible values:
63
+ # * +:array+ : Encode the ruby array as a PostgreSQL array.
64
+ # The array element type is inferred from the class of the first array element. This is the default.
65
+ # * +:json+ : Encode the ruby array as a JSON document.
66
+ # * +:record+ : Encode the ruby array as a composite type row.
67
+ # * <code>"_type"</code> : Encode the ruby array as a particular PostgreSQL type.
68
+ # All PostgreSQL array types are supported.
69
+ # If there's an encoder registered for the elements +type+, it will be used.
70
+ # Otherwise a string conversion (by +value.to_s+) is done.
71
+ def encode_array_as=(pg_type)
72
+ case pg_type
73
+ when :array
74
+ when :json
75
+ when :record
76
+ when /\A_/
77
+ else
78
+ raise ArgumentError, "invalid pg_type #{pg_type.inspect}"
79
+ end
80
+
81
+ @encode_array_as = pg_type
82
+
83
+ init_encoders
84
+ end
85
+
86
+ attr_reader :encode_array_as
87
+
88
+ private
89
+
90
+ def init_encoders
91
+ coders.each { |kl, c| self[kl] = nil } # Clear type map
92
+ populate_encoder_list
93
+ @textarray_encoder = coder_by_name(0, :encoder, '_text')
94
+ end
95
+
96
+ def coder_by_name(format, direction, name)
97
+ check_format_and_direction(format, direction)
98
+ @coder_maps.map_for(format, direction).coder_by_name(name)
99
+ end
100
+
101
+ def undefined(name, format)
102
+ @if_undefined.call(name, format)
103
+ end
104
+
105
+ def populate_encoder_list
106
+ DEFAULT_TYPE_MAP.each do |klass, selector|
107
+ if Array === selector
108
+ format, name, oid_name = selector
109
+ coder = coder_by_name(format, :encoder, name).dup
110
+ if coder
111
+ if oid_name
112
+ oid_coder = coder_by_name(format, :encoder, oid_name)
113
+ if oid_coder
114
+ coder.oid = oid_coder.oid
115
+ else
116
+ undefined(oid_name, format)
117
+ end
118
+ else
119
+ coder.oid = 0
120
+ end
121
+ self[klass] = coder
122
+ else
123
+ undefined(name, format)
124
+ end
125
+ else
126
+
127
+ case @encode_array_as
128
+ when :array
129
+ self[klass] = selector
130
+ when :json
131
+ self[klass] = PG::TextEncoder::JSON.new
132
+ when :record
133
+ self[klass] = PG::TextEncoder::Record.new type_map: self
134
+ when /\A_/
135
+ coder = coder_by_name(0, :encoder, @encode_array_as)
136
+ if coder
137
+ self[klass] = coder
138
+ else
139
+ undefined(@encode_array_as, format)
140
+ end
141
+ else
142
+ raise ArgumentError, "invalid pg_type #{@encode_array_as.inspect}"
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ def array_encoders_by_klass
149
+ DEFAULT_ARRAY_TYPE_MAP.inject({}) do |h, (klass, (format, name))|
150
+ h[klass] = coder_by_name(format, :encoder, name)
151
+ h
152
+ end
153
+ end
154
+
155
+ def get_array_type(value)
156
+ elem = value
157
+ while elem.kind_of?(Array)
158
+ elem = elem.first
159
+ end
160
+ @array_encoders_by_klass[elem.class] ||
161
+ elem.class.ancestors.lazy.map{|ancestor| @array_encoders_by_klass[ancestor] }.find{|a| a } ||
162
+ @textarray_encoder
163
+ end
164
+
165
+ DEFAULT_TYPE_MAP = {
166
+ TrueClass => [1, 'bool', 'bool'],
167
+ FalseClass => [1, 'bool', 'bool'],
168
+ # We use text format and no type OID for numbers, because setting the OID can lead
169
+ # to unnecessary type conversions on server side.
170
+ Integer => [0, 'int8'],
171
+ Float => [0, 'float8'],
172
+ BigDecimal => [0, 'numeric'],
173
+ Time => [0, 'timestamptz'],
174
+ # We use text format and no type OID for IPAddr, because setting the OID can lead
175
+ # to unnecessary inet/cidr conversions on the server side.
176
+ IPAddr => [0, 'inet'],
177
+ Hash => [0, 'json'],
178
+ Array => :get_array_type,
179
+ BinaryData => [1, 'bytea'],
180
+ }
181
+
182
+ DEFAULT_ARRAY_TYPE_MAP = {
183
+ TrueClass => [0, '_bool'],
184
+ FalseClass => [0, '_bool'],
185
+ Integer => [0, '_int8'],
186
+ String => [0, '_text'],
187
+ Float => [0, '_float8'],
188
+ BigDecimal => [0, '_numeric'],
189
+ Time => [0, '_timestamptz'],
190
+ IPAddr => [0, '_inet'],
191
+ }
192
+
193
+ end
@@ -0,0 +1,81 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'pg' unless defined?( PG )
5
+
6
+ # Simple set of rules for type casting common PostgreSQL types to Ruby.
7
+ #
8
+ # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
9
+ # PostgreSQL's +pg_type+ table in PG::BasicTypeMapForResults.new .
10
+ #
11
+ # Result values are type casted based on the type OID of the given result column.
12
+ #
13
+ # Higher level libraries will most likely not make use of this class, but use their
14
+ # own set of rules to choose suitable encoders and decoders.
15
+ #
16
+ # Example:
17
+ # conn = PG::Connection.new
18
+ # # Assign a default ruleset for type casts of output values.
19
+ # conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn)
20
+ # # Execute a query.
21
+ # res = conn.exec_params( "SELECT $1::INT", ['5'] )
22
+ # # Retrieve and cast the result value. Value format is 0 (text) and OID is 20. Therefore typecasting
23
+ # # is done by PG::TextDecoder::Integer internally for all value retrieval methods.
24
+ # res.values # => [[5]]
25
+ #
26
+ # PG::TypeMapByOid#build_column_map(result) can be used to generate
27
+ # a result independent PG::TypeMapByColumn type map, which can subsequently be used
28
+ # to cast #get_copy_data fields:
29
+ #
30
+ # For the following table:
31
+ # conn.exec( "CREATE TABLE copytable AS VALUES('a', 123, '{5,4,3}'::INT[])" )
32
+ #
33
+ # # Retrieve table OIDs per empty result set.
34
+ # res = conn.exec( "SELECT * FROM copytable LIMIT 0" )
35
+ # # Build a type map for common database to ruby type decoders.
36
+ # btm = PG::BasicTypeMapForResults.new(conn)
37
+ # # Build a PG::TypeMapByColumn with decoders suitable for copytable.
38
+ # tm = btm.build_column_map( res )
39
+ # row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
40
+ #
41
+ # conn.copy_data( "COPY copytable TO STDOUT", row_decoder ) do |res|
42
+ # while row=conn.get_copy_data
43
+ # p row
44
+ # end
45
+ # end
46
+ # This prints the rows with type casted columns:
47
+ # ["a", 123, [5, 4, 3]]
48
+ #
49
+ # See also PG::BasicTypeMapBasedOnResult for the encoder direction and PG::BasicTypeRegistry for the definition of additional types.
50
+ class PG::BasicTypeMapForResults < PG::TypeMapByOid
51
+ include PG::BasicTypeRegistry::Checker
52
+
53
+ class WarningTypeMap < PG::TypeMapInRuby
54
+ def initialize(typenames)
55
+ @already_warned = Hash.new{|h, k| h[k] = {} }
56
+ @typenames_by_oid = typenames
57
+ end
58
+
59
+ def typecast_result_value(result, _tuple, field)
60
+ format = result.fformat(field)
61
+ oid = result.ftype(field)
62
+ unless @already_warned[format][oid]
63
+ warn "Warning: no type cast defined for type #{@typenames_by_oid[oid].inspect} format #{format} with oid #{oid}. Please cast this type explicitly to TEXT to be safe for future changes."
64
+ @already_warned[format][oid] = true
65
+ end
66
+ super
67
+ end
68
+ end
69
+
70
+ def initialize(connection_or_coder_maps, registry: nil)
71
+ @coder_maps = build_coder_maps(connection_or_coder_maps, registry: registry)
72
+
73
+ # Populate TypeMapByOid hash with decoders
74
+ @coder_maps.each_format(:decoder).flat_map{|f| f.coders }.each do |coder|
75
+ add_coder(coder)
76
+ end
77
+
78
+ typenames = @coder_maps.typenames_by_oid
79
+ self.default_type_map = WarningTypeMap.new(typenames)
80
+ end
81
+ end