pg 0.18.4-x64-mingw32 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) 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 +66 -78
  67. metadata.gz.sig +0 -0
  68. data/lib/2.0/pg_ext.so +0 -0
  69. data/lib/2.1/pg_ext.so +0 -0
  70. data/lib/2.2/pg_ext.so +0 -0
  71. data/lib/x64-mingw32/libpq.dll +0 -0
  72. data/sample/array_insert.rb +0 -20
  73. data/sample/async_api.rb +0 -106
  74. data/sample/async_copyto.rb +0 -39
  75. data/sample/async_mixed.rb +0 -56
  76. data/sample/check_conn.rb +0 -21
  77. data/sample/copyfrom.rb +0 -81
  78. data/sample/copyto.rb +0 -19
  79. data/sample/cursor.rb +0 -21
  80. data/sample/disk_usage_report.rb +0 -186
  81. data/sample/issue-119.rb +0 -94
  82. data/sample/losample.rb +0 -69
  83. data/sample/minimal-testcase.rb +0 -17
  84. data/sample/notify_wait.rb +0 -72
  85. data/sample/pg_statistics.rb +0 -294
  86. data/sample/replication_monitor.rb +0 -231
  87. data/sample/test_binary_values.rb +0 -33
  88. data/sample/wal_shipper.rb +0 -434
  89. 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