pg 0.18.4 → 1.2.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 (85) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/BSDL +2 -2
  5. data/ChangeLog +0 -5911
  6. data/History.rdoc +240 -0
  7. data/Manifest.txt +8 -20
  8. data/README-Windows.rdoc +4 -4
  9. data/README.ja.rdoc +1 -2
  10. data/README.rdoc +64 -15
  11. data/Rakefile +20 -21
  12. data/Rakefile.cross +67 -69
  13. data/ext/errorcodes.def +101 -0
  14. data/ext/errorcodes.rb +1 -1
  15. data/ext/errorcodes.txt +33 -2
  16. data/ext/extconf.rb +26 -36
  17. data/ext/gvl_wrappers.c +4 -0
  18. data/ext/gvl_wrappers.h +27 -39
  19. data/ext/pg.c +156 -145
  20. data/ext/pg.h +74 -98
  21. data/ext/pg_binary_decoder.c +82 -15
  22. data/ext/pg_binary_encoder.c +20 -19
  23. data/ext/pg_coder.c +103 -21
  24. data/ext/pg_connection.c +917 -523
  25. data/ext/pg_copy_coder.c +50 -12
  26. data/ext/pg_record_coder.c +491 -0
  27. data/ext/pg_result.c +590 -208
  28. data/ext/pg_text_decoder.c +606 -40
  29. data/ext/pg_text_encoder.c +245 -94
  30. data/ext/pg_tuple.c +549 -0
  31. data/ext/pg_type_map.c +14 -7
  32. data/ext/pg_type_map_all_strings.c +4 -4
  33. data/ext/pg_type_map_by_class.c +9 -4
  34. data/ext/pg_type_map_by_column.c +7 -6
  35. data/ext/pg_type_map_by_mri_type.c +1 -1
  36. data/ext/pg_type_map_by_oid.c +3 -2
  37. data/ext/pg_type_map_in_ruby.c +1 -1
  38. data/ext/{util.c → pg_util.c} +10 -10
  39. data/ext/{util.h → pg_util.h} +2 -2
  40. data/lib/pg.rb +23 -13
  41. data/lib/pg/basic_type_mapping.rb +155 -32
  42. data/lib/pg/binary_decoder.rb +23 -0
  43. data/lib/pg/coder.rb +23 -2
  44. data/lib/pg/connection.rb +73 -13
  45. data/lib/pg/constants.rb +2 -1
  46. data/lib/pg/exceptions.rb +2 -1
  47. data/lib/pg/result.rb +24 -7
  48. data/lib/pg/text_decoder.rb +24 -22
  49. data/lib/pg/text_encoder.rb +40 -8
  50. data/lib/pg/tuple.rb +30 -0
  51. data/lib/pg/type_map_by_column.rb +3 -2
  52. data/spec/helpers.rb +61 -36
  53. data/spec/pg/basic_type_mapping_spec.rb +415 -36
  54. data/spec/pg/connection_spec.rb +732 -327
  55. data/spec/pg/connection_sync_spec.rb +41 -0
  56. data/spec/pg/result_spec.rb +253 -21
  57. data/spec/pg/tuple_spec.rb +333 -0
  58. data/spec/pg/type_map_by_class_spec.rb +4 -4
  59. data/spec/pg/type_map_by_column_spec.rb +6 -2
  60. data/spec/pg/type_map_by_mri_type_spec.rb +2 -2
  61. data/spec/pg/type_map_by_oid_spec.rb +3 -3
  62. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  63. data/spec/pg/type_map_spec.rb +1 -1
  64. data/spec/pg/type_spec.rb +446 -20
  65. data/spec/pg_spec.rb +2 -2
  66. metadata +63 -72
  67. metadata.gz.sig +0 -0
  68. data/sample/array_insert.rb +0 -20
  69. data/sample/async_api.rb +0 -106
  70. data/sample/async_copyto.rb +0 -39
  71. data/sample/async_mixed.rb +0 -56
  72. data/sample/check_conn.rb +0 -21
  73. data/sample/copyfrom.rb +0 -81
  74. data/sample/copyto.rb +0 -19
  75. data/sample/cursor.rb +0 -21
  76. data/sample/disk_usage_report.rb +0 -186
  77. data/sample/issue-119.rb +0 -94
  78. data/sample/losample.rb +0 -69
  79. data/sample/minimal-testcase.rb +0 -17
  80. data/sample/notify_wait.rb +0 -72
  81. data/sample/pg_statistics.rb +0 -294
  82. data/sample/replication_monitor.rb +0 -231
  83. data/sample/test_binary_values.rb +0 -33
  84. data/sample/wal_shipper.rb +0 -434
  85. data/sample/warehouse_partitions.rb +0 -320
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_column_map.c - PG::ColumnMap class extension
3
- * $Id: pg_type_map.c,v fcf731d3dff7 2015/09/08 12:25:06 jfali $
3
+ * $Id$
4
4
  *
5
5
  */
6
6
 
@@ -11,46 +11,53 @@ VALUE rb_mDefaultTypeMappable;
11
11
  static ID s_id_fit_to_query;
12
12
  static ID s_id_fit_to_result;
13
13
 
14
+ NORETURN( VALUE
15
+ pg_typemap_fit_to_result( VALUE self, VALUE result ));
16
+ NORETURN( VALUE
17
+ pg_typemap_fit_to_query( VALUE self, VALUE params ));
18
+ NORETURN( int
19
+ pg_typemap_fit_to_copy_get( VALUE self ));
20
+ NORETURN( VALUE
21
+ pg_typemap_result_value( t_typemap *p_typemap, VALUE result, int tuple, int field ));
22
+ NORETURN( t_pg_coder *
23
+ pg_typemap_typecast_query_param( t_typemap *p_typemap, VALUE param_value, int field ));
24
+ NORETURN( VALUE
25
+ pg_typemap_typecast_copy_get( t_typemap *p_typemap, VALUE field_str, int fieldno, int format, int enc_idx ));
26
+
14
27
  VALUE
15
28
  pg_typemap_fit_to_result( VALUE self, VALUE result )
16
29
  {
17
30
  rb_raise( rb_eNotImpError, "type map %s is not suitable to map result values", rb_obj_classname(self) );
18
- return Qnil;
19
31
  }
20
32
 
21
33
  VALUE
22
34
  pg_typemap_fit_to_query( VALUE self, VALUE params )
23
35
  {
24
36
  rb_raise( rb_eNotImpError, "type map %s is not suitable to map query params", rb_obj_classname(self) );
25
- return Qnil;
26
37
  }
27
38
 
28
39
  int
29
40
  pg_typemap_fit_to_copy_get( VALUE self )
30
41
  {
31
42
  rb_raise( rb_eNotImpError, "type map %s is not suitable to map get_copy_data results", rb_obj_classname(self) );
32
- return Qnil;
33
43
  }
34
44
 
35
45
  VALUE
36
46
  pg_typemap_result_value( t_typemap *p_typemap, VALUE result, int tuple, int field )
37
47
  {
38
48
  rb_raise( rb_eNotImpError, "type map is not suitable to map result values" );
39
- return Qnil;
40
49
  }
41
50
 
42
51
  t_pg_coder *
43
52
  pg_typemap_typecast_query_param( t_typemap *p_typemap, VALUE param_value, int field )
44
53
  {
45
54
  rb_raise( rb_eNotImpError, "type map is not suitable to map query params" );
46
- return NULL;
47
55
  }
48
56
 
49
57
  VALUE
50
58
  pg_typemap_typecast_copy_get( t_typemap *p_typemap, VALUE field_str, int fieldno, int format, int enc_idx )
51
59
  {
52
60
  rb_raise( rb_eNotImpError, "type map is not suitable to map get_copy_data results" );
53
- return Qnil;
54
61
  }
55
62
 
56
63
  const struct pg_typemap_funcs pg_typemap_funcs = {
@@ -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'
@@ -8,11 +9,23 @@ rescue LoadError
8
9
  major_minor = RUBY_VERSION[ /^(\d+\.\d+)/ ] or
9
10
  raise "Oops, can't extract the major/minor version from #{RUBY_VERSION.dump}"
10
11
 
11
- # Set the PATH environment variable, so that libpq.dll can be found.
12
- old_path = ENV['PATH']
13
- ENV['PATH'] = "#{File.expand_path("../#{RUBY_PLATFORM}", __FILE__)};#{old_path}"
14
- require "#{major_minor}/pg_ext"
15
- ENV['PATH'] = old_path
12
+ add_dll_path = proc do |path, &block|
13
+ begin
14
+ require 'ruby_installer/runtime'
15
+ RubyInstaller::Runtime.add_dll_directory(path, &block)
16
+ rescue LoadError
17
+ old_path = ENV['PATH']
18
+ ENV['PATH'] = "#{path};#{old_path}"
19
+ block.call
20
+ ENV['PATH'] = old_path
21
+ end
22
+ end
23
+
24
+ # Temporary add this directory for DLL search, so that libpq.dll can be found.
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
27
+ require "#{major_minor}/pg_ext"
28
+ end
16
29
  else
17
30
  raise
18
31
  end
@@ -24,10 +37,10 @@ end
24
37
  module PG
25
38
 
26
39
  # Library version
27
- VERSION = '0.18.4'
40
+ VERSION = '1.2.3'
28
41
 
29
42
  # VCS revision
30
- REVISION = %q$Revision: da42b972b5ab $
43
+ REVISION = %q$Revision: 6f611e78845a $
31
44
 
32
45
  class NotAllCopyDataRetrieved < PG::Error
33
46
  end
@@ -49,16 +62,13 @@ module PG
49
62
  require 'pg/exceptions'
50
63
  require 'pg/constants'
51
64
  require 'pg/coder'
65
+ require 'pg/binary_decoder'
52
66
  require 'pg/text_encoder'
53
67
  require 'pg/text_decoder'
54
68
  require 'pg/basic_type_mapping'
55
69
  require 'pg/type_map_by_column'
56
70
  require 'pg/connection'
57
71
  require 'pg/result'
72
+ require 'pg/tuple'
58
73
 
59
74
  end # module PG
60
-
61
-
62
- # Backward-compatible aliase
63
- PGError = PG::Error
64
-
@@ -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'
@@ -188,12 +228,13 @@ module PG::BasicTypeRegistry
188
228
  # register_type 'polygon', OID::Text.new
189
229
  # register_type 'circle', OID::Text.new
190
230
  # register_type 'hstore', OID::Hstore.new
191
- # register_type 'json', OID::Json.new
231
+ register_type 0, 'json', PG::TextEncoder::JSON, PG::TextDecoder::JSON
232
+ alias_type 0, 'jsonb', 'json'
192
233
  # register_type 'citext', OID::Text.new
193
234
  # register_type 'ltree', OID::Text.new
194
235
  #
195
- # register_type 'cidr', OID::Cidr.new
196
- # alias_type 'inet', 'cidr'
236
+ register_type 0, 'inet', PG::TextEncoder::Inet, PG::TextDecoder::Inet
237
+ alias_type 0, 'cidr', 'inet'
197
238
 
198
239
 
199
240
 
@@ -212,12 +253,14 @@ module PG::BasicTypeRegistry
212
253
  register_type 1, 'bool', PG::BinaryEncoder::Boolean, PG::BinaryDecoder::Boolean
213
254
  register_type 1, 'float4', nil, PG::BinaryDecoder::Float
214
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
215
258
  end
216
259
 
217
260
  # Simple set of rules for type casting common PostgreSQL types to Ruby.
218
261
  #
219
262
  # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
220
- # PostgreSQL's pg_type table in PG::BasicTypeMapForResults.new .
263
+ # PostgreSQL's +pg_type+ table in PG::BasicTypeMapForResults.new .
221
264
  #
222
265
  # Result values are type casted based on the type OID of the given result column.
223
266
  #
@@ -226,18 +269,38 @@ end
226
269
  #
227
270
  # Example:
228
271
  # conn = PG::Connection.new
229
- # # Assign a default ruleset for type casts of input and output values.
230
- # conn.type_mapping = PG::BasicTypeMapping.new(conn)
272
+ # # Assign a default ruleset for type casts of output values.
273
+ # conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn)
231
274
  # # Execute a query.
232
275
  # res = conn.exec_params( "SELECT $1::INT", ['5'] )
233
276
  # # Retrieve and cast the result value. Value format is 0 (text) and OID is 20. Therefore typecasting
234
277
  # # is done by PG::TextDecoder::Integer internally for all value retrieval methods.
235
278
  # res.values # => [[5]]
236
279
  #
237
- # PG::TypeMapByOid#fit_to_result(result, false) can be used to generate
280
+ # PG::TypeMapByOid#build_column_map(result) can be used to generate
238
281
  # a result independent PG::TypeMapByColumn type map, which can subsequently be used
239
- # to cast #get_copy_data fields. See also PG::BasicTypeMapBasedOnResult .
282
+ # to cast #get_copy_data fields:
283
+ #
284
+ # For the following table:
285
+ # conn.exec( "CREATE TABLE copytable AS VALUES('a', 123, '{5,4,3}'::INT[])" )
286
+ #
287
+ # # Retrieve table OIDs per empty result set.
288
+ # res = conn.exec( "SELECT * FROM copytable LIMIT 0" )
289
+ # # Build a type map for common database to ruby type decoders.
290
+ # btm = PG::BasicTypeMapForResults.new(conn)
291
+ # # Build a PG::TypeMapByColumn with decoders suitable for copytable.
292
+ # tm = btm.build_column_map( res )
293
+ # row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
240
294
  #
295
+ # conn.copy_data( "COPY copytable TO STDOUT", row_decoder ) do |res|
296
+ # while row=conn.get_copy_data
297
+ # p row
298
+ # end
299
+ # end
300
+ # This prints the rows with type casted columns:
301
+ # ["a", 123, [5, 4, 3]]
302
+ #
303
+ # See also PG::BasicTypeMapBasedOnResult for the encoder direction and PG::BasicTypeRegistry for the definition of additional types.
241
304
  class PG::BasicTypeMapForResults < PG::TypeMapByOid
242
305
  include PG::BasicTypeRegistry
243
306
 
@@ -251,7 +314,7 @@ class PG::BasicTypeMapForResults < PG::TypeMapByOid
251
314
  format = result.fformat(field)
252
315
  oid = result.ftype(field)
253
316
  unless @already_warned[format][oid]
254
- 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."
255
318
  @already_warned[format][oid] = true
256
319
  end
257
320
  super
@@ -275,7 +338,7 @@ end
275
338
  # to PostgreSQL.
276
339
  #
277
340
  # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
278
- # PostgreSQL's pg_type table in PG::BasicTypeMapBasedOnResult.new .
341
+ # PostgreSQL's +pg_type+ table in PG::BasicTypeMapBasedOnResult.new .
279
342
  #
280
343
  # This class works equal to PG::BasicTypeMapForResults, but does not define decoders for
281
344
  # the given result OIDs, but encoders. So it can be used to type cast field values based on
@@ -290,12 +353,17 @@ end
290
353
  #
291
354
  # # Retrieve table OIDs per empty result set.
292
355
  # res = conn.exec( "SELECT * FROM copytable LIMIT 0" )
293
- # tm = basic_type_mapping.build_column_map( res )
356
+ # # Build a type map for common ruby to database type encoders.
357
+ # btm = PG::BasicTypeMapBasedOnResult.new(conn)
358
+ # # Build a PG::TypeMapByColumn with encoders suitable for copytable.
359
+ # tm = btm.build_column_map( res )
294
360
  # row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
295
361
  #
296
362
  # conn.copy_data( "COPY copytable FROM STDIN", row_encoder ) do |res|
297
363
  # conn.put_copy_data ['a', 123, [5,4,3]]
298
364
  # end
365
+ # This inserts a single row into copytable with type casts from ruby to
366
+ # database types.
299
367
  class PG::BasicTypeMapBasedOnResult < PG::TypeMapByOid
300
368
  include PG::BasicTypeRegistry
301
369
 
@@ -314,31 +382,65 @@ end
314
382
  # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
315
383
  # PostgreSQL's pg_type table in PG::BasicTypeMapForQueries.new .
316
384
  #
317
- # Query params are type casted based on the MRI internal type of the given value.
385
+ # Query params are type casted based on the class of the given value.
318
386
  #
319
387
  # Higher level libraries will most likely not make use of this class, but use their
320
- # own set of rules to choose suitable encoders and decoders.
388
+ # own derivation of PG::TypeMapByClass or another set of rules to choose suitable
389
+ # encoders and decoders for the values to be sent.
321
390
  #
322
391
  # Example:
323
392
  # conn = PG::Connection.new
324
393
  # # Assign a default ruleset for type casts of input and output values.
325
- # conn.type_mapping_for_queries = PG::BasicTypeMapForQueries.new(conn)
394
+ # conn.type_map_for_queries = PG::BasicTypeMapForQueries.new(conn)
326
395
  # # Execute a query. The Integer param value is typecasted internally by PG::BinaryEncoder::Int8.
327
- # # 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).
328
397
  # res = conn.exec_params( "SELECT $1", [5] )
329
398
  class PG::BasicTypeMapForQueries < PG::TypeMapByClass
330
399
  include PG::BasicTypeRegistry
331
400
 
332
401
  def initialize(connection)
333
402
  @coder_maps = build_coder_maps(connection)
334
-
335
- populate_encoder_list
336
403
  @array_encoders_by_klass = array_encoders_by_klass
337
- @anyarray_encoder = coder_by_name(0, :encoder, '_any')
404
+ @encode_array_as = :array
405
+ init_encoders
406
+ end
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
338
432
  end
339
433
 
434
+ attr_reader :encode_array_as
435
+
340
436
  private
341
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
+
342
444
  def coder_by_name(format, direction, name)
343
445
  check_format_and_direction(format, direction)
344
446
  @coder_maps[format][direction].coder_by_name(name)
@@ -356,7 +458,19 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
356
458
  end
357
459
  self[klass] = coder
358
460
  else
359
- 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
360
474
  end
361
475
  end
362
476
  end
@@ -375,7 +489,7 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
375
489
  end
376
490
  @array_encoders_by_klass[elem.class] ||
377
491
  elem.class.ancestors.lazy.map{|ancestor| @array_encoders_by_klass[ancestor] }.find{|a| a } ||
378
- @anyarray_encoder
492
+ @textarray_encoder
379
493
  end
380
494
 
381
495
  DEFAULT_TYPE_MAP = {
@@ -385,6 +499,12 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
385
499
  # to unnecessary type conversions on server side.
386
500
  Integer => [0, 'int8'],
387
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'],
388
508
  Array => :get_array_type,
389
509
  }
390
510
 
@@ -394,6 +514,9 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
394
514
  Integer => [0, '_int8'],
395
515
  String => [0, '_text'],
396
516
  Float => [0, '_float8'],
517
+ BigDecimal => [0, '_numeric'],
518
+ Time => [0, '_timestamptz'],
519
+ IPAddr => [0, '_inet'],
397
520
  }
398
521
 
399
522
  end