pg 0.21.0 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +0 -6595
  5. data/History.rdoc +184 -0
  6. data/Manifest.txt +8 -3
  7. data/README-Windows.rdoc +4 -4
  8. data/README.ja.rdoc +1 -2
  9. data/README.rdoc +58 -13
  10. data/Rakefile +10 -9
  11. data/Rakefile.cross +68 -71
  12. data/ext/errorcodes.def +76 -0
  13. data/ext/errorcodes.rb +1 -1
  14. data/ext/errorcodes.txt +21 -2
  15. data/ext/extconf.rb +18 -36
  16. data/ext/gvl_wrappers.c +4 -0
  17. data/ext/gvl_wrappers.h +23 -39
  18. data/ext/pg.c +154 -144
  19. data/ext/pg.h +68 -95
  20. data/ext/pg_binary_decoder.c +82 -15
  21. data/ext/pg_binary_encoder.c +13 -12
  22. data/ext/pg_coder.c +73 -12
  23. data/ext/pg_connection.c +699 -459
  24. data/ext/pg_copy_coder.c +16 -8
  25. data/ext/pg_record_coder.c +491 -0
  26. data/ext/pg_result.c +571 -195
  27. data/ext/pg_text_decoder.c +606 -40
  28. data/ext/pg_text_encoder.c +185 -54
  29. data/ext/pg_tuple.c +549 -0
  30. data/ext/pg_type_map.c +1 -1
  31. data/ext/pg_type_map_all_strings.c +4 -4
  32. data/ext/pg_type_map_by_class.c +9 -4
  33. data/ext/pg_type_map_by_column.c +7 -6
  34. data/ext/pg_type_map_by_mri_type.c +1 -1
  35. data/ext/pg_type_map_by_oid.c +3 -2
  36. data/ext/pg_type_map_in_ruby.c +1 -1
  37. data/ext/{util.c → pg_util.c} +10 -10
  38. data/ext/{util.h → pg_util.h} +2 -2
  39. data/lib/pg.rb +8 -10
  40. data/lib/pg/basic_type_mapping.rb +121 -25
  41. data/lib/pg/binary_decoder.rb +23 -0
  42. data/lib/pg/coder.rb +23 -2
  43. data/lib/pg/connection.rb +28 -4
  44. data/lib/pg/constants.rb +2 -1
  45. data/lib/pg/exceptions.rb +2 -1
  46. data/lib/pg/result.rb +14 -2
  47. data/lib/pg/text_decoder.rb +21 -26
  48. data/lib/pg/text_encoder.rb +32 -8
  49. data/lib/pg/tuple.rb +30 -0
  50. data/lib/pg/type_map_by_column.rb +3 -2
  51. data/spec/helpers.rb +61 -33
  52. data/spec/pg/basic_type_mapping_spec.rb +362 -37
  53. data/spec/pg/connection_spec.rb +602 -329
  54. data/spec/pg/connection_sync_spec.rb +41 -0
  55. data/spec/pg/result_spec.rb +242 -17
  56. data/spec/pg/tuple_spec.rb +333 -0
  57. data/spec/pg/type_map_by_class_spec.rb +2 -2
  58. data/spec/pg/type_map_by_column_spec.rb +6 -2
  59. data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
  60. data/spec/pg/type_map_by_oid_spec.rb +3 -3
  61. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  62. data/spec/pg/type_map_spec.rb +1 -1
  63. data/spec/pg/type_spec.rb +364 -18
  64. data/spec/pg_spec.rb +2 -2
  65. metadata +48 -43
  66. metadata.gz.sig +0 -0
  67. data/lib/pg/deprecated_constants.rb +0 -21
data/ext/pg_type_map.c CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_column_map.c - PG::ColumnMap class extension
3
- * $Id: pg_type_map.c,v 2af122820861 2017/01/14 19:56:36 lars $
3
+ * $Id$
4
4
  *
5
5
  */
6
6
 
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_type_map_all_strings.c - PG::TypeMapAllStrings class extension
3
- * $Id: pg_type_map_all_strings.c,v c53f993a4254 2014/12/12 21:57:29 lars $
3
+ * $Id$
4
4
  *
5
5
  * This is the default typemap.
6
6
  *
@@ -33,9 +33,9 @@ pg_tmas_result_value( t_typemap *p_typemap, VALUE result, int tuple, int field )
33
33
  len = PQgetlength( p_result->pgresult, tuple, field );
34
34
 
35
35
  if ( 0 == PQfformat(p_result->pgresult, field) ) {
36
- ret = pg_text_dec_string(NULL, val, len, tuple, field, ENCODING_GET(result));
36
+ ret = pg_text_dec_string(NULL, val, len, tuple, field, p_result->enc_idx);
37
37
  } else {
38
- ret = pg_bin_dec_bytea(NULL, val, len, tuple, field, ENCODING_GET(result));
38
+ ret = pg_bin_dec_bytea(NULL, val, len, tuple, field, p_result->enc_idx);
39
39
  }
40
40
 
41
41
  return ret;
@@ -99,7 +99,7 @@ init_pg_type_map_all_strings()
99
99
  * This type map casts all values received from the database server to Strings
100
100
  * and sends all values to the server after conversion to String by +#to_s+ .
101
101
  * That means, it is hard coded to PG::TextEncoder::String for value encoding
102
- * and to PG::TextDecoder::String for text format respectivly PG::BinaryDecoder::Bytea
102
+ * and to PG::TextDecoder::String for text format respectively PG::BinaryDecoder::Bytea
103
103
  * for binary format received from the server.
104
104
  *
105
105
  * It is suitable for type casting query bind parameters, result values and
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_type_map_by_class.c - PG::TypeMapByClass class extension
3
- * $Id: pg_type_map_by_class.c,v eeb8a82c5328 2014/11/10 19:34:02 lars $
3
+ * $Id$
4
4
  *
5
5
  * This type map can be used to select value encoders based on the class
6
6
  * of the given value to be send.
@@ -28,7 +28,7 @@ typedef struct {
28
28
  * We use 8 Bits of the klass object id as index to a 256 entry cache.
29
29
  * This avoids full lookups in most cases.
30
30
  */
31
- #define CACHE_LOOKUP(this, klass) ( &this->cache_row[(klass >> 8) & 0xff] )
31
+ #define CACHE_LOOKUP(this, klass) ( &this->cache_row[(((unsigned long)klass) >> 8) & 0xff] )
32
32
 
33
33
 
34
34
  static t_pg_coder *
@@ -66,7 +66,7 @@ pg_tmbk_lookup_klass(t_tmbk *this, VALUE klass, VALUE param_value)
66
66
  Data_Get_Struct(obj, t_pg_coder, p_coder);
67
67
  }else{
68
68
  if( RB_TYPE_P(obj, T_SYMBOL) ){
69
- /* A Proc object (or something that responds to #call). */
69
+ /* A Symbol: Call the method with this name. */
70
70
  obj = rb_funcall(this->self, SYM2ID(obj), 1, param_value);
71
71
  }else{
72
72
  /* A Proc object (or something that responds to #call). */
@@ -126,7 +126,11 @@ pg_tmbk_mark( t_tmbk *this )
126
126
  {
127
127
  rb_gc_mark(this->typemap.default_typemap);
128
128
  rb_gc_mark(this->klass_to_coder);
129
- /* All coders are in the Hash, so no need to mark the cache. */
129
+ rb_gc_mark(this->self);
130
+ /* Clear the cache, to be safe from changes of klass VALUE by GC.compact.
131
+ * TODO: Move cache clearing to compactation callback provided by Ruby-2.7+.
132
+ */
133
+ memset(&this->cache_row, 0, sizeof(this->cache_row));
130
134
  }
131
135
 
132
136
  static VALUE
@@ -235,5 +239,6 @@ init_pg_type_map_by_class()
235
239
  rb_define_method( rb_cTypeMapByClass, "[]=", pg_tmbk_aset, 2 );
236
240
  rb_define_method( rb_cTypeMapByClass, "[]", pg_tmbk_aref, 1 );
237
241
  rb_define_method( rb_cTypeMapByClass, "coders", pg_tmbk_coders, 0 );
242
+ /* rb_mDefaultTypeMappable = rb_define_module_under( rb_cTypeMap, "DefaultTypeMappable"); */
238
243
  rb_include_module( rb_cTypeMapByClass, rb_mDefaultTypeMappable );
239
244
  }
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_column_map.c - PG::ColumnMap class extension
3
- * $Id: pg_type_map_by_column.c,v fcf731d3dff7 2015/09/08 12:25:06 jfali $
3
+ * $Id$
4
4
  *
5
5
  */
6
6
 
@@ -99,11 +99,11 @@ pg_tmbc_result_value( t_typemap *p_typemap, VALUE result, int tuple, int field )
99
99
  int len = PQgetlength( p_result->pgresult, tuple, field );
100
100
 
101
101
  if( p_coder->dec_func ){
102
- return p_coder->dec_func(p_coder, val, len, tuple, field, ENCODING_GET(result));
102
+ return p_coder->dec_func(p_coder, val, len, tuple, field, p_result->enc_idx);
103
103
  } else {
104
104
  t_pg_coder_dec_func dec_func;
105
105
  dec_func = pg_coder_dec_func( p_coder, PQfformat(p_result->pgresult, field) );
106
- return dec_func(p_coder, val, len, tuple, field, ENCODING_GET(result));
106
+ return dec_func(p_coder, val, len, tuple, field, p_result->enc_idx);
107
107
  }
108
108
  }
109
109
 
@@ -292,13 +292,13 @@ init_pg_type_map_by_column()
292
292
  *
293
293
  * This type map casts values by a coder assigned per field/column.
294
294
  *
295
- * Each PG:TypeMapByColumn has a fixed list of either encoders or decoders,
296
- * that is defined at #new . A type map with encoders is usable for type casting
295
+ * Each PG::TypeMapByColumn has a fixed list of either encoders or decoders,
296
+ * that is defined at TypeMapByColumn.new . A type map with encoders is usable for type casting
297
297
  * query bind parameters and COPY data for PG::Connection#put_copy_data .
298
298
  * A type map with decoders is usable for type casting of result values and
299
299
  * COPY data from PG::Connection#get_copy_data .
300
300
  *
301
- * PG::TypeMapByColumns are in particular useful in conjunction with prepared statements,
301
+ * PG::TypeMapByColumn objects are in particular useful in conjunction with prepared statements,
302
302
  * since they can be cached alongside with the statement handle.
303
303
  *
304
304
  * This type map strategy is also used internally by PG::TypeMapByOid, when the
@@ -308,5 +308,6 @@ init_pg_type_map_by_column()
308
308
  rb_define_alloc_func( rb_cTypeMapByColumn, pg_tmbc_s_allocate );
309
309
  rb_define_method( rb_cTypeMapByColumn, "initialize", pg_tmbc_init, 1 );
310
310
  rb_define_method( rb_cTypeMapByColumn, "coders", pg_tmbc_coders, 0 );
311
+ /* rb_mDefaultTypeMappable = rb_define_module_under( rb_cTypeMap, "DefaultTypeMappable"); */
311
312
  rb_include_module( rb_cTypeMapByColumn, rb_mDefaultTypeMappable );
312
313
  }
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_type_map_by_mri_type.c - PG::TypeMapByMriType class extension
3
- * $Id: pg_type_map_by_mri_type.c,v 1269b8ad77b8 2015/02/06 16:38:23 lars $
3
+ * $Id$
4
4
  *
5
5
  * This type map can be used to select value encoders based on the MRI-internal
6
6
  * value type code.
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_type_map_by_oid.c - PG::TypeMapByOid class extension
3
- * $Id: pg_type_map_by_oid.c,v c99d26015e3c 2014/12/12 20:58:25 lars $
3
+ * $Id$
4
4
  *
5
5
  */
6
6
 
@@ -110,7 +110,7 @@ pg_tmbo_result_value(t_typemap *p_typemap, VALUE result, int tuple, int field)
110
110
  char * val = PQgetvalue( p_result->pgresult, tuple, field );
111
111
  int len = PQgetlength( p_result->pgresult, tuple, field );
112
112
  t_pg_coder_dec_func dec_func = pg_coder_dec_func( p_coder, format );
113
- return dec_func( p_coder, val, len, tuple, field, ENCODING_GET(result) );
113
+ return dec_func( p_coder, val, len, tuple, field, p_result->enc_idx );
114
114
  }
115
115
 
116
116
  default_tm = DATA_PTR( this->typemap.default_typemap );
@@ -351,5 +351,6 @@ init_pg_type_map_by_oid()
351
351
  rb_define_method( rb_cTypeMapByOid, "max_rows_for_online_lookup=", pg_tmbo_max_rows_for_online_lookup_set, 1 );
352
352
  rb_define_method( rb_cTypeMapByOid, "max_rows_for_online_lookup", pg_tmbo_max_rows_for_online_lookup_get, 0 );
353
353
  rb_define_method( rb_cTypeMapByOid, "build_column_map", pg_tmbo_build_column_map, 1 );
354
+ /* rb_mDefaultTypeMappable = rb_define_module_under( rb_cTypeMap, "DefaultTypeMappable"); */
354
355
  rb_include_module( rb_cTypeMapByOid, rb_mDefaultTypeMappable );
355
356
  }
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_type_map_in_ruby.c - PG::TypeMapInRuby class extension
3
- * $Id: pg_type_map_in_ruby.c,v 3d89d3aae4fd 2015/01/05 16:19:41 kanis $
3
+ * $Id$
4
4
  *
5
5
  */
6
6
 
@@ -1,11 +1,11 @@
1
1
  /*
2
- * util.c - Utils for ruby-pg
3
- * $Id: util.c,v 5fb9170f6a7d 2015/06/29 11:15:12 kanis $
2
+ * pg_util.c - Utils for ruby-pg
3
+ * $Id$
4
4
  *
5
5
  */
6
6
 
7
7
  #include "pg.h"
8
- #include "util.h"
8
+ #include "pg_util.h"
9
9
 
10
10
  static const char base64_encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
11
11
 
@@ -15,19 +15,19 @@ static const char base64_encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk
15
15
  * in-place (with _out_ == _in_).
16
16
  */
17
17
  void
18
- base64_encode( char *out, char *in, int len)
18
+ base64_encode( char *out, const char *in, int len)
19
19
  {
20
- unsigned char *in_ptr = (unsigned char *)in + len;
20
+ const unsigned char *in_ptr = (const unsigned char *)in + len;
21
21
  char *out_ptr = out + BASE64_ENCODED_SIZE(len);
22
22
  int part_len = len % 3;
23
23
 
24
24
  if( part_len > 0 ){
25
- long byte2 = part_len > 2 ? *--in_ptr : 0;
25
+ long byte2 = 0;
26
26
  long byte1 = part_len > 1 ? *--in_ptr : 0;
27
27
  long byte0 = *--in_ptr;
28
28
  long triple = (byte0 << 16) + (byte1 << 8) + byte2;
29
29
 
30
- *--out_ptr = part_len > 2 ? base64_encode_table[(triple >> 0 * 6) & 0x3F] : '=';
30
+ *--out_ptr = '=';
31
31
  *--out_ptr = part_len > 1 ? base64_encode_table[(triple >> 1 * 6) & 0x3F] : '=';
32
32
  *--out_ptr = base64_encode_table[(triple >> 2 * 6) & 0x3F];
33
33
  *--out_ptr = base64_encode_table[(triple >> 3 * 6) & 0x3F];
@@ -72,12 +72,12 @@ static const unsigned char base64_decode_table[] =
72
72
  * It is possible to decode a string in-place (with _out_ == _in_).
73
73
  */
74
74
  int
75
- base64_decode( char *out, char *in, unsigned int len)
75
+ base64_decode( char *out, const char *in, unsigned int len)
76
76
  {
77
77
  unsigned char a, b, c, d;
78
- unsigned char *in_ptr = (unsigned char *)in;
78
+ const unsigned char *in_ptr = (const unsigned char *)in;
79
79
  unsigned char *out_ptr = (unsigned char *)out;
80
- unsigned char *iend_ptr = (unsigned char *)in + len;
80
+ const unsigned char *iend_ptr = (unsigned char *)in + len;
81
81
 
82
82
  for(;;){
83
83
  if( in_ptr+3 < iend_ptr &&
@@ -57,8 +57,8 @@
57
57
  #define BASE64_ENCODED_SIZE(strlen) (((strlen) + 2) / 3 * 4)
58
58
  #define BASE64_DECODED_SIZE(base64len) (((base64len) + 3) / 4 * 3)
59
59
 
60
- void base64_encode( char *out, char *in, int len);
61
- int base64_decode( char *out, char *in, unsigned int len);
60
+ void base64_encode( char *out, const char *in, int len);
61
+ int base64_decode( char *out, const char *in, unsigned int len);
62
62
 
63
63
  int rbpg_strncasecmp(const char *s1, const char *s2, size_t n);
64
64
 
data/lib/pg.rb CHANGED
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  begin
4
5
  require 'pg_ext'
@@ -21,7 +22,8 @@ rescue LoadError
21
22
  end
22
23
 
23
24
  # Temporary add this directory for DLL search, so that libpq.dll can be found.
24
- add_dll_path.call(__dir__) do
25
+ # mingw32-platform strings differ (RUBY_PLATFORM=i386-mingw32 vs. x86-mingw32 for rubygems)
26
+ add_dll_path.call(File.join(__dir__, RUBY_PLATFORM.gsub(/^i386-/, "x86-"))) do
25
27
  require "#{major_minor}/pg_ext"
26
28
  end
27
29
  else
@@ -35,10 +37,10 @@ end
35
37
  module PG
36
38
 
37
39
  # Library version
38
- VERSION = '0.21.0'
40
+ VERSION = '1.2.3'
39
41
 
40
42
  # VCS revision
41
- REVISION = %q$Revision: f6063a34ae2b $
43
+ REVISION = %q$Revision: 6f611e78845a $
42
44
 
43
45
  class NotAllCopyDataRetrieved < PG::Error
44
46
  end
@@ -60,17 +62,13 @@ module PG
60
62
  require 'pg/exceptions'
61
63
  require 'pg/constants'
62
64
  require 'pg/coder'
65
+ require 'pg/binary_decoder'
63
66
  require 'pg/text_encoder'
64
67
  require 'pg/text_decoder'
65
68
  require 'pg/basic_type_mapping'
66
69
  require 'pg/type_map_by_column'
67
70
  require 'pg/connection'
68
71
  require 'pg/result'
72
+ require 'pg/tuple'
69
73
 
70
74
  end # module PG
71
-
72
-
73
- autoload :PGError, 'pg/deprecated_constants'
74
- autoload :PGconn, 'pg/deprecated_constants'
75
- autoload :PGresult, 'pg/deprecated_constants'
76
-
@@ -1,7 +1,28 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
5
 
6
+ # This module defines the mapping between OID and encoder/decoder classes for PG::BasicTypeMapForResults, PG::BasicTypeMapForQueries and PG::BasicTypeMapBasedOnResult.
7
+ #
8
+ # Additional types can be added like so:
9
+ #
10
+ # require 'pg'
11
+ # require 'ipaddr'
12
+ #
13
+ # class InetDecoder < PG::SimpleDecoder
14
+ # def decode(string, tuple=nil, field=nil)
15
+ # IPAddr.new(string)
16
+ # end
17
+ # end
18
+ # class InetEncoder < PG::SimpleEncoder
19
+ # def encode(ip_addr)
20
+ # ip_addr.to_s
21
+ # end
22
+ # end
23
+ #
24
+ # # 0 if for text format, can also be 1 for binary
25
+ # PG::BasicTypeRegistry.register_type(0, 'inet', InetEncoder, InetDecoder)
5
26
  module PG::BasicTypeRegistry
6
27
  # An instance of this class stores the coders that should be used for a given wire format (text or binary)
7
28
  # and type cast direction (encoder or decoder).
@@ -97,13 +118,13 @@ module PG::BasicTypeRegistry
97
118
  def build_coder_maps(connection)
98
119
  if supports_ranges?(connection)
99
120
  result = connection.exec <<-SQL
100
- SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype
121
+ SELECT t.oid, t.typname::text, t.typelem, t.typdelim, t.typinput::text, r.rngsubtype
101
122
  FROM pg_type as t
102
123
  LEFT JOIN pg_range as r ON oid = rngtypid
103
124
  SQL
104
125
  else
105
126
  result = connection.exec <<-SQL
106
- SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput
127
+ SELECT t.oid, t.typname::text, t.typelem, t.typdelim, t.typinput::text
107
128
  FROM pg_type as t
108
129
  SQL
109
130
  end
@@ -134,19 +155,38 @@ module PG::BasicTypeRegistry
134
155
  # objects as values.
135
156
  CODERS_BY_NAME = []
136
157
 
137
- # Register an OID type named +name+ with a typecasting encoder and decoder object in
138
- # +type+. +name+ should correspond to the `typname` column in
139
- # the `pg_type` table.
158
+ public
159
+
160
+ # Register an encoder or decoder instance for casting a PostgreSQL type.
161
+ #
162
+ # Coder#name must correspond to the +typname+ column in the +pg_type+ table.
163
+ # Coder#format can be 0 for text format and 1 for binary.
164
+ def self.register_coder(coder)
165
+ h = CODERS_BY_NAME[coder.format] ||= { encoder: {}, decoder: {} }
166
+ name = coder.name || raise(ArgumentError, "name of #{coder.inspect} must be defined")
167
+ h[:encoder][name] = coder if coder.respond_to?(:encode)
168
+ h[:decoder][name] = coder if coder.respond_to?(:decode)
169
+ end
170
+
171
+ # Register the given +encoder_class+ and/or +decoder_class+ for casting a PostgreSQL type.
172
+ #
173
+ # +name+ must correspond to the +typname+ column in the +pg_type+ table.
174
+ # +format+ can be 0 for text format and 1 for binary.
140
175
  def self.register_type(format, name, encoder_class, decoder_class)
141
- CODERS_BY_NAME[format] ||= { encoder: {}, decoder: {} }
142
- CODERS_BY_NAME[format][:encoder][name] = encoder_class.new(name: name, format: format) if encoder_class
143
- CODERS_BY_NAME[format][:decoder][name] = decoder_class.new(name: name, format: format) if decoder_class
176
+ register_coder(encoder_class.new(name: name, format: format)) if encoder_class
177
+ register_coder(decoder_class.new(name: name, format: format)) if decoder_class
144
178
  end
145
179
 
146
180
  # Alias the +old+ type to the +new+ type.
147
181
  def self.alias_type(format, new, old)
148
- CODERS_BY_NAME[format][:encoder][new] = CODERS_BY_NAME[format][:encoder][old]
149
- CODERS_BY_NAME[format][:decoder][new] = CODERS_BY_NAME[format][:decoder][old]
182
+ [:encoder, :decoder].each do |ende|
183
+ enc = CODERS_BY_NAME[format][ende][old]
184
+ if enc
185
+ CODERS_BY_NAME[format][ende][new] = enc
186
+ else
187
+ CODERS_BY_NAME[format][ende].delete(new)
188
+ end
189
+ end
150
190
  end
151
191
 
152
192
  register_type 0, 'int2', PG::TextEncoder::Integer, PG::TextDecoder::Integer
@@ -154,7 +194,7 @@ module PG::BasicTypeRegistry
154
194
  alias_type 0, 'int8', 'int2'
155
195
  alias_type 0, 'oid', 'int2'
156
196
 
157
- # register_type 0, 'numeric', OID::Decimal.new
197
+ register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric
158
198
  register_type 0, 'text', PG::TextEncoder::String, PG::TextDecoder::String
159
199
  alias_type 0, 'varchar', 'text'
160
200
  alias_type 0, 'char', 'text'
@@ -193,8 +233,8 @@ module PG::BasicTypeRegistry
193
233
  # register_type 'citext', OID::Text.new
194
234
  # register_type 'ltree', OID::Text.new
195
235
  #
196
- # register_type 'cidr', OID::Cidr.new
197
- # alias_type 'inet', 'cidr'
236
+ register_type 0, 'inet', PG::TextEncoder::Inet, PG::TextDecoder::Inet
237
+ alias_type 0, 'cidr', 'inet'
198
238
 
199
239
 
200
240
 
@@ -213,12 +253,14 @@ module PG::BasicTypeRegistry
213
253
  register_type 1, 'bool', PG::BinaryEncoder::Boolean, PG::BinaryDecoder::Boolean
214
254
  register_type 1, 'float4', nil, PG::BinaryDecoder::Float
215
255
  register_type 1, 'float8', nil, PG::BinaryDecoder::Float
256
+ register_type 1, 'timestamp', nil, PG::BinaryDecoder::TimestampUtc
257
+ register_type 1, 'timestamptz', nil, PG::BinaryDecoder::TimestampUtcToLocal
216
258
  end
217
259
 
218
260
  # Simple set of rules for type casting common PostgreSQL types to Ruby.
219
261
  #
220
262
  # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
221
- # PostgreSQL's pg_type table in PG::BasicTypeMapForResults.new .
263
+ # PostgreSQL's +pg_type+ table in PG::BasicTypeMapForResults.new .
222
264
  #
223
265
  # Result values are type casted based on the type OID of the given result column.
224
266
  #
@@ -235,7 +277,7 @@ end
235
277
  # # is done by PG::TextDecoder::Integer internally for all value retrieval methods.
236
278
  # res.values # => [[5]]
237
279
  #
238
- # PG::TypeMapByOid#fit_to_result(result, false) can be used to generate
280
+ # PG::TypeMapByOid#build_column_map(result) can be used to generate
239
281
  # a result independent PG::TypeMapByColumn type map, which can subsequently be used
240
282
  # to cast #get_copy_data fields:
241
283
  #
@@ -258,7 +300,7 @@ end
258
300
  # This prints the rows with type casted columns:
259
301
  # ["a", 123, [5, 4, 3]]
260
302
  #
261
- # See also PG::BasicTypeMapBasedOnResult for the encoder direction.
303
+ # See also PG::BasicTypeMapBasedOnResult for the encoder direction and PG::BasicTypeRegistry for the definition of additional types.
262
304
  class PG::BasicTypeMapForResults < PG::TypeMapByOid
263
305
  include PG::BasicTypeRegistry
264
306
 
@@ -272,7 +314,7 @@ class PG::BasicTypeMapForResults < PG::TypeMapByOid
272
314
  format = result.fformat(field)
273
315
  oid = result.ftype(field)
274
316
  unless @already_warned[format][oid]
275
- STDERR.puts "Warning: no type cast defined for type #{@typenames_by_oid[format][oid].inspect} with oid #{oid}. Please cast this type explicitly to TEXT to be safe for future changes."
317
+ $stderr.puts "Warning: no type cast defined for type #{@typenames_by_oid[format][oid].inspect} with oid #{oid}. Please cast this type explicitly to TEXT to be safe for future changes."
276
318
  @already_warned[format][oid] = true
277
319
  end
278
320
  super
@@ -296,7 +338,7 @@ end
296
338
  # to PostgreSQL.
297
339
  #
298
340
  # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
299
- # PostgreSQL's pg_type table in PG::BasicTypeMapBasedOnResult.new .
341
+ # PostgreSQL's +pg_type+ table in PG::BasicTypeMapBasedOnResult.new .
300
342
  #
301
343
  # This class works equal to PG::BasicTypeMapForResults, but does not define decoders for
302
344
  # the given result OIDs, but encoders. So it can be used to type cast field values based on
@@ -351,21 +393,54 @@ end
351
393
  # # Assign a default ruleset for type casts of input and output values.
352
394
  # conn.type_map_for_queries = PG::BasicTypeMapForQueries.new(conn)
353
395
  # # Execute a query. The Integer param value is typecasted internally by PG::BinaryEncoder::Int8.
354
- # # The format of the parameter is set to 1 (binary) and the OID of this parameter is set to 20 (int8).
396
+ # # The format of the parameter is set to 0 (text) and the OID of this parameter is set to 20 (int8).
355
397
  # res = conn.exec_params( "SELECT $1", [5] )
356
398
  class PG::BasicTypeMapForQueries < PG::TypeMapByClass
357
399
  include PG::BasicTypeRegistry
358
400
 
359
401
  def initialize(connection)
360
402
  @coder_maps = build_coder_maps(connection)
361
-
362
- populate_encoder_list
363
403
  @array_encoders_by_klass = array_encoders_by_klass
364
- @anyarray_encoder = coder_by_name(0, :encoder, '_any')
404
+ @encode_array_as = :array
405
+ init_encoders
365
406
  end
366
407
 
408
+ # Change the mechanism that is used to encode ruby array values
409
+ #
410
+ # Possible values:
411
+ # * +:array+ : Encode the ruby array as a PostgreSQL array.
412
+ # The array element type is inferred from the class of the first array element. This is the default.
413
+ # * +:json+ : Encode the ruby array as a JSON document.
414
+ # * +:record+ : Encode the ruby array as a composite type row.
415
+ # * <code>"_type"</code> : Encode the ruby array as a particular PostgreSQL type.
416
+ # All PostgreSQL array types are supported.
417
+ # If there's an encoder registered for the elements +type+, it will be used.
418
+ # Otherwise a string conversion (by +value.to_s+) is done.
419
+ def encode_array_as=(pg_type)
420
+ case pg_type
421
+ when :array
422
+ when :json
423
+ when :record
424
+ when /\A_/
425
+ else
426
+ raise ArgumentError, "invalid pg_type #{pg_type.inspect}"
427
+ end
428
+
429
+ @encode_array_as = pg_type
430
+
431
+ init_encoders
432
+ end
433
+
434
+ attr_reader :encode_array_as
435
+
367
436
  private
368
437
 
438
+ def init_encoders
439
+ coders.each { |kl, c| self[kl] = nil } # Clear type map
440
+ populate_encoder_list
441
+ @textarray_encoder = coder_by_name(0, :encoder, '_text')
442
+ end
443
+
369
444
  def coder_by_name(format, direction, name)
370
445
  check_format_and_direction(format, direction)
371
446
  @coder_maps[format][direction].coder_by_name(name)
@@ -383,7 +458,19 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
383
458
  end
384
459
  self[klass] = coder
385
460
  else
386
- self[klass] = selector
461
+
462
+ case @encode_array_as
463
+ when :array
464
+ self[klass] = selector
465
+ when :json
466
+ self[klass] = PG::TextEncoder::JSON.new
467
+ when :record
468
+ self[klass] = PG::TextEncoder::Record.new type_map: self
469
+ when /\A_/
470
+ self[klass] = coder_by_name(0, :encoder, @encode_array_as) || raise(ArgumentError, "unknown array type #{@encode_array_as.inspect}")
471
+ else
472
+ raise ArgumentError, "invalid pg_type #{@encode_array_as.inspect}"
473
+ end
387
474
  end
388
475
  end
389
476
  end
@@ -402,7 +489,7 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
402
489
  end
403
490
  @array_encoders_by_klass[elem.class] ||
404
491
  elem.class.ancestors.lazy.map{|ancestor| @array_encoders_by_klass[ancestor] }.find{|a| a } ||
405
- @anyarray_encoder
492
+ @textarray_encoder
406
493
  end
407
494
 
408
495
  DEFAULT_TYPE_MAP = {
@@ -412,6 +499,12 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
412
499
  # to unnecessary type conversions on server side.
413
500
  Integer => [0, 'int8'],
414
501
  Float => [0, 'float8'],
502
+ BigDecimal => [0, 'numeric'],
503
+ Time => [0, 'timestamptz'],
504
+ # We use text format and no type OID for IPAddr, because setting the OID can lead
505
+ # to unnecessary inet/cidr conversions on the server side.
506
+ IPAddr => [0, 'inet'],
507
+ Hash => [0, 'json'],
415
508
  Array => :get_array_type,
416
509
  }
417
510
 
@@ -421,6 +514,9 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
421
514
  Integer => [0, '_int8'],
422
515
  String => [0, '_text'],
423
516
  Float => [0, '_float8'],
517
+ BigDecimal => [0, '_numeric'],
518
+ Time => [0, '_timestamptz'],
519
+ IPAddr => [0, '_inet'],
424
520
  }
425
521
 
426
522
  end