pg 0.18.4 → 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/BSDL +2 -2
- data/ChangeLog +0 -5911
- data/History.rdoc +240 -0
- data/Manifest.txt +8 -20
- data/README-Windows.rdoc +4 -4
- data/README.ja.rdoc +1 -2
- data/README.rdoc +64 -15
- data/Rakefile +20 -21
- data/Rakefile.cross +67 -69
- data/ext/errorcodes.def +101 -0
- data/ext/errorcodes.rb +1 -1
- data/ext/errorcodes.txt +33 -2
- data/ext/extconf.rb +26 -36
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +27 -39
- data/ext/pg.c +156 -145
- data/ext/pg.h +74 -98
- data/ext/pg_binary_decoder.c +82 -15
- data/ext/pg_binary_encoder.c +20 -19
- data/ext/pg_coder.c +103 -21
- data/ext/pg_connection.c +917 -523
- data/ext/pg_copy_coder.c +50 -12
- data/ext/pg_record_coder.c +491 -0
- data/ext/pg_result.c +590 -208
- data/ext/pg_text_decoder.c +606 -40
- data/ext/pg_text_encoder.c +245 -94
- data/ext/pg_tuple.c +549 -0
- data/ext/pg_type_map.c +14 -7
- data/ext/pg_type_map_all_strings.c +4 -4
- data/ext/pg_type_map_by_class.c +9 -4
- data/ext/pg_type_map_by_column.c +7 -6
- data/ext/pg_type_map_by_mri_type.c +1 -1
- data/ext/pg_type_map_by_oid.c +3 -2
- data/ext/pg_type_map_in_ruby.c +1 -1
- data/ext/{util.c → pg_util.c} +10 -10
- data/ext/{util.h → pg_util.h} +2 -2
- data/lib/pg.rb +23 -13
- data/lib/pg/basic_type_mapping.rb +155 -32
- data/lib/pg/binary_decoder.rb +23 -0
- data/lib/pg/coder.rb +23 -2
- data/lib/pg/connection.rb +73 -13
- data/lib/pg/constants.rb +2 -1
- data/lib/pg/exceptions.rb +2 -1
- data/lib/pg/result.rb +24 -7
- data/lib/pg/text_decoder.rb +24 -22
- data/lib/pg/text_encoder.rb +40 -8
- data/lib/pg/tuple.rb +30 -0
- data/lib/pg/type_map_by_column.rb +3 -2
- data/spec/helpers.rb +61 -36
- data/spec/pg/basic_type_mapping_spec.rb +415 -36
- data/spec/pg/connection_spec.rb +732 -327
- data/spec/pg/connection_sync_spec.rb +41 -0
- data/spec/pg/result_spec.rb +253 -21
- data/spec/pg/tuple_spec.rb +333 -0
- data/spec/pg/type_map_by_class_spec.rb +4 -4
- data/spec/pg/type_map_by_column_spec.rb +6 -2
- data/spec/pg/type_map_by_mri_type_spec.rb +2 -2
- data/spec/pg/type_map_by_oid_spec.rb +3 -3
- data/spec/pg/type_map_in_ruby_spec.rb +1 -1
- data/spec/pg/type_map_spec.rb +1 -1
- data/spec/pg/type_spec.rb +446 -20
- data/spec/pg_spec.rb +2 -2
- metadata +63 -72
- metadata.gz.sig +0 -0
- data/sample/array_insert.rb +0 -20
- data/sample/async_api.rb +0 -106
- data/sample/async_copyto.rb +0 -39
- data/sample/async_mixed.rb +0 -56
- data/sample/check_conn.rb +0 -21
- data/sample/copyfrom.rb +0 -81
- data/sample/copyto.rb +0 -19
- data/sample/cursor.rb +0 -21
- data/sample/disk_usage_report.rb +0 -186
- data/sample/issue-119.rb +0 -94
- data/sample/losample.rb +0 -69
- data/sample/minimal-testcase.rb +0 -17
- data/sample/notify_wait.rb +0 -72
- data/sample/pg_statistics.rb +0 -294
- data/sample/replication_monitor.rb +0 -231
- data/sample/test_binary_values.rb +0 -33
- data/sample/wal_shipper.rb +0 -434
- data/sample/warehouse_partitions.rb +0 -320
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
|
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
|
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,
|
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,
|
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
|
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
|
data/ext/pg_type_map_by_class.c
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
2
|
* pg_type_map_by_class.c - PG::TypeMapByClass class extension
|
3
|
-
* $Id
|
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
|
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
|
-
|
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
|
}
|
data/ext/pg_type_map_by_column.c
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
2
|
* pg_column_map.c - PG::ColumnMap class extension
|
3
|
-
* $Id
|
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,
|
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,
|
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
|
296
|
-
* that is defined at
|
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::
|
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
|
}
|
data/ext/pg_type_map_by_oid.c
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
2
|
* pg_type_map_by_oid.c - PG::TypeMapByOid class extension
|
3
|
-
* $Id
|
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,
|
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
|
}
|
data/ext/pg_type_map_in_ruby.c
CHANGED
data/ext/{util.c → pg_util.c}
RENAMED
@@ -1,11 +1,11 @@
|
|
1
1
|
/*
|
2
|
-
*
|
3
|
-
* $Id
|
2
|
+
* pg_util.c - Utils for ruby-pg
|
3
|
+
* $Id$
|
4
4
|
*
|
5
5
|
*/
|
6
6
|
|
7
7
|
#include "pg.h"
|
8
|
-
#include "
|
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 =
|
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 =
|
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 &&
|
data/ext/{util.h → pg_util.h}
RENAMED
@@ -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
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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 = '
|
40
|
+
VERSION = '1.2.3'
|
28
41
|
|
29
42
|
# VCS revision
|
30
|
-
REVISION = %q$Revision:
|
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
|
-
|
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
|
-
|
138
|
-
|
139
|
-
#
|
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
|
-
|
142
|
-
|
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
|
-
|
149
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
196
|
-
|
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
|
230
|
-
# 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#
|
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
|
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
|
-
|
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
|
-
#
|
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
|
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
|
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.
|
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
|
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
|
-
@
|
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
|
-
|
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
|
-
@
|
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
|