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.
- 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
|