pg 0.17.1 → 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/BSDL +2 -2
- data/ChangeLog +0 -3506
- data/History.rdoc +308 -0
- data/Manifest.txt +35 -19
- data/README-Windows.rdoc +17 -28
- data/README.ja.rdoc +1 -2
- data/README.rdoc +113 -14
- data/Rakefile +67 -30
- data/Rakefile.cross +109 -83
- data/ext/errorcodes.def +101 -0
- data/ext/errorcodes.rb +1 -1
- data/ext/errorcodes.txt +33 -2
- data/ext/extconf.rb +55 -58
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +27 -39
- data/ext/pg.c +262 -130
- data/ext/pg.h +266 -54
- data/ext/pg_binary_decoder.c +229 -0
- data/ext/pg_binary_encoder.c +163 -0
- data/ext/pg_coder.c +561 -0
- data/ext/pg_connection.c +1689 -990
- data/ext/pg_copy_coder.c +599 -0
- data/ext/pg_errors.c +6 -0
- data/ext/pg_record_coder.c +491 -0
- data/ext/pg_result.c +897 -164
- data/ext/pg_text_decoder.c +987 -0
- data/ext/pg_text_encoder.c +814 -0
- data/ext/pg_tuple.c +549 -0
- data/ext/pg_type_map.c +166 -0
- data/ext/pg_type_map_all_strings.c +116 -0
- data/ext/pg_type_map_by_class.c +244 -0
- data/ext/pg_type_map_by_column.c +313 -0
- data/ext/pg_type_map_by_mri_type.c +284 -0
- data/ext/pg_type_map_by_oid.c +356 -0
- data/ext/pg_type_map_in_ruby.c +299 -0
- data/ext/pg_util.c +149 -0
- data/ext/pg_util.h +65 -0
- data/lib/pg/basic_type_mapping.rb +522 -0
- data/lib/pg/binary_decoder.rb +23 -0
- data/lib/pg/coder.rb +104 -0
- data/lib/pg/connection.rb +153 -41
- data/lib/pg/constants.rb +2 -1
- data/lib/pg/exceptions.rb +2 -1
- data/lib/pg/result.rb +33 -6
- data/lib/pg/text_decoder.rb +46 -0
- data/lib/pg/text_encoder.rb +59 -0
- data/lib/pg/tuple.rb +30 -0
- data/lib/pg/type_map_by_column.rb +16 -0
- data/lib/pg.rb +29 -9
- data/spec/{lib/helpers.rb → helpers.rb} +151 -64
- data/spec/pg/basic_type_mapping_spec.rb +630 -0
- data/spec/pg/connection_spec.rb +1180 -477
- data/spec/pg/connection_sync_spec.rb +41 -0
- data/spec/pg/result_spec.rb +456 -120
- data/spec/pg/tuple_spec.rb +333 -0
- data/spec/pg/type_map_by_class_spec.rb +138 -0
- data/spec/pg/type_map_by_column_spec.rb +226 -0
- data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
- data/spec/pg/type_map_by_oid_spec.rb +149 -0
- data/spec/pg/type_map_in_ruby_spec.rb +164 -0
- data/spec/pg/type_map_spec.rb +22 -0
- data/spec/pg/type_spec.rb +1123 -0
- data/spec/pg_spec.rb +26 -20
- data.tar.gz.sig +0 -0
- metadata +148 -91
- 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_util.c
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
/*
|
2
|
+
* pg_util.c - Utils for ruby-pg
|
3
|
+
* $Id$
|
4
|
+
*
|
5
|
+
*/
|
6
|
+
|
7
|
+
#include "pg.h"
|
8
|
+
#include "pg_util.h"
|
9
|
+
|
10
|
+
static const char base64_encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
11
|
+
|
12
|
+
/* Encode _len_ bytes at _in_ as base64 and write output to _out_.
|
13
|
+
*
|
14
|
+
* This encoder runs backwards, so that it is possible to encode a string
|
15
|
+
* in-place (with _out_ == _in_).
|
16
|
+
*/
|
17
|
+
void
|
18
|
+
base64_encode( char *out, const char *in, int len)
|
19
|
+
{
|
20
|
+
const unsigned char *in_ptr = (const unsigned char *)in + len;
|
21
|
+
char *out_ptr = out + BASE64_ENCODED_SIZE(len);
|
22
|
+
int part_len = len % 3;
|
23
|
+
|
24
|
+
if( part_len > 0 ){
|
25
|
+
long byte2 = 0;
|
26
|
+
long byte1 = part_len > 1 ? *--in_ptr : 0;
|
27
|
+
long byte0 = *--in_ptr;
|
28
|
+
long triple = (byte0 << 16) + (byte1 << 8) + byte2;
|
29
|
+
|
30
|
+
*--out_ptr = '=';
|
31
|
+
*--out_ptr = part_len > 1 ? base64_encode_table[(triple >> 1 * 6) & 0x3F] : '=';
|
32
|
+
*--out_ptr = base64_encode_table[(triple >> 2 * 6) & 0x3F];
|
33
|
+
*--out_ptr = base64_encode_table[(triple >> 3 * 6) & 0x3F];
|
34
|
+
}
|
35
|
+
|
36
|
+
while( out_ptr > out ){
|
37
|
+
long byte2 = *--in_ptr;
|
38
|
+
long byte1 = *--in_ptr;
|
39
|
+
long byte0 = *--in_ptr;
|
40
|
+
long triple = (byte0 << 16) + (byte1 << 8) + byte2;
|
41
|
+
|
42
|
+
*--out_ptr = base64_encode_table[(triple >> 0 * 6) & 0x3F];
|
43
|
+
*--out_ptr = base64_encode_table[(triple >> 1 * 6) & 0x3F];
|
44
|
+
*--out_ptr = base64_encode_table[(triple >> 2 * 6) & 0x3F];
|
45
|
+
*--out_ptr = base64_encode_table[(triple >> 3 * 6) & 0x3F];
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
/*
|
50
|
+
* 0.upto(255).map{|a| "\\x#{ (base64_encode_table.index([a].pack("C")) || 0xff).to_s(16) }" }.join
|
51
|
+
*/
|
52
|
+
static const unsigned char base64_decode_table[] =
|
53
|
+
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
54
|
+
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
55
|
+
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3e\xff\xff\xff\x3f"
|
56
|
+
"\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\xff\xff\xff\xff\xff\xff"
|
57
|
+
"\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e"
|
58
|
+
"\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\xff\xff\xff\xff\xff"
|
59
|
+
"\xff\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28"
|
60
|
+
"\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\xff\xff\xff\xff\xff"
|
61
|
+
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
62
|
+
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
63
|
+
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
64
|
+
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
65
|
+
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
66
|
+
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
67
|
+
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
68
|
+
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
69
|
+
|
70
|
+
/* Decode _len_ bytes of base64 characters at _in_ and write output to _out_.
|
71
|
+
*
|
72
|
+
* It is possible to decode a string in-place (with _out_ == _in_).
|
73
|
+
*/
|
74
|
+
int
|
75
|
+
base64_decode( char *out, const char *in, unsigned int len)
|
76
|
+
{
|
77
|
+
unsigned char a, b, c, d;
|
78
|
+
const unsigned char *in_ptr = (const unsigned char *)in;
|
79
|
+
unsigned char *out_ptr = (unsigned char *)out;
|
80
|
+
const unsigned char *iend_ptr = (unsigned char *)in + len;
|
81
|
+
|
82
|
+
for(;;){
|
83
|
+
if( in_ptr+3 < iend_ptr &&
|
84
|
+
(a=base64_decode_table[in_ptr[0]]) != 0xff &&
|
85
|
+
(b=base64_decode_table[in_ptr[1]]) != 0xff &&
|
86
|
+
(c=base64_decode_table[in_ptr[2]]) != 0xff &&
|
87
|
+
(d=base64_decode_table[in_ptr[3]]) != 0xff )
|
88
|
+
{
|
89
|
+
in_ptr += 4;
|
90
|
+
*out_ptr++ = (a << 2) | (b >> 4);
|
91
|
+
*out_ptr++ = (b << 4) | (c >> 2);
|
92
|
+
*out_ptr++ = (c << 6) | d;
|
93
|
+
} else if (in_ptr < iend_ptr){
|
94
|
+
a = b = c = d = 0xff;
|
95
|
+
while ((a = base64_decode_table[*in_ptr++]) == 0xff && in_ptr < iend_ptr) {}
|
96
|
+
if (in_ptr < iend_ptr){
|
97
|
+
while ((b = base64_decode_table[*in_ptr++]) == 0xff && in_ptr < iend_ptr) {}
|
98
|
+
if (in_ptr < iend_ptr){
|
99
|
+
while ((c = base64_decode_table[*in_ptr++]) == 0xff && in_ptr < iend_ptr) {}
|
100
|
+
if (in_ptr < iend_ptr){
|
101
|
+
while ((d = base64_decode_table[*in_ptr++]) == 0xff && in_ptr < iend_ptr) {}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
if (a != 0xff && b != 0xff) {
|
106
|
+
*out_ptr++ = (a << 2) | (b >> 4);
|
107
|
+
if (c != 0xff) {
|
108
|
+
*out_ptr++ = (b << 4) | (c >> 2);
|
109
|
+
if (d != 0xff)
|
110
|
+
*out_ptr++ = (c << 6) | d;
|
111
|
+
}
|
112
|
+
}
|
113
|
+
} else {
|
114
|
+
break;
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
|
119
|
+
return (char*)out_ptr - out;
|
120
|
+
}
|
121
|
+
|
122
|
+
/*
|
123
|
+
* Case-independent comparison of two not-necessarily-null-terminated strings.
|
124
|
+
* At most n bytes will be examined from each string.
|
125
|
+
*/
|
126
|
+
int
|
127
|
+
rbpg_strncasecmp(const char *s1, const char *s2, size_t n)
|
128
|
+
{
|
129
|
+
while (n-- > 0)
|
130
|
+
{
|
131
|
+
unsigned char ch1 = (unsigned char) *s1++;
|
132
|
+
unsigned char ch2 = (unsigned char) *s2++;
|
133
|
+
|
134
|
+
if (ch1 != ch2){
|
135
|
+
if (ch1 >= 'A' && ch1 <= 'Z')
|
136
|
+
ch1 += 'a' - 'A';
|
137
|
+
|
138
|
+
if (ch2 >= 'A' && ch2 <= 'Z')
|
139
|
+
ch2 += 'a' - 'A';
|
140
|
+
|
141
|
+
if (ch1 != ch2)
|
142
|
+
return (int) ch1 - (int) ch2;
|
143
|
+
}
|
144
|
+
if (ch1 == 0)
|
145
|
+
break;
|
146
|
+
}
|
147
|
+
return 0;
|
148
|
+
}
|
149
|
+
|
data/ext/pg_util.h
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
/*
|
2
|
+
* utils.h
|
3
|
+
*
|
4
|
+
*/
|
5
|
+
|
6
|
+
#ifndef __utils_h
|
7
|
+
#define __utils_h
|
8
|
+
|
9
|
+
#define write_nbo16(l,c) ( \
|
10
|
+
*((unsigned char*)(c)+0)=(unsigned char)(((l)>>8)&0xff), \
|
11
|
+
*((unsigned char*)(c)+1)=(unsigned char)(((l) )&0xff)\
|
12
|
+
)
|
13
|
+
|
14
|
+
#define write_nbo32(l,c) ( \
|
15
|
+
*((unsigned char*)(c)+0)=(unsigned char)(((l)>>24L)&0xff), \
|
16
|
+
*((unsigned char*)(c)+1)=(unsigned char)(((l)>>16L)&0xff), \
|
17
|
+
*((unsigned char*)(c)+2)=(unsigned char)(((l)>> 8L)&0xff), \
|
18
|
+
*((unsigned char*)(c)+3)=(unsigned char)(((l) )&0xff)\
|
19
|
+
)
|
20
|
+
|
21
|
+
#define write_nbo64(l,c) ( \
|
22
|
+
*((unsigned char*)(c)+0)=(unsigned char)(((l)>>56LL)&0xff), \
|
23
|
+
*((unsigned char*)(c)+1)=(unsigned char)(((l)>>48LL)&0xff), \
|
24
|
+
*((unsigned char*)(c)+2)=(unsigned char)(((l)>>40LL)&0xff), \
|
25
|
+
*((unsigned char*)(c)+3)=(unsigned char)(((l)>>32LL)&0xff), \
|
26
|
+
*((unsigned char*)(c)+4)=(unsigned char)(((l)>>24LL)&0xff), \
|
27
|
+
*((unsigned char*)(c)+5)=(unsigned char)(((l)>>16LL)&0xff), \
|
28
|
+
*((unsigned char*)(c)+6)=(unsigned char)(((l)>> 8LL)&0xff), \
|
29
|
+
*((unsigned char*)(c)+7)=(unsigned char)(((l) )&0xff)\
|
30
|
+
)
|
31
|
+
|
32
|
+
#define read_nbo16(c) ((int16_t)( \
|
33
|
+
(((uint16_t)(*((unsigned char*)(c)+0)))<< 8L) | \
|
34
|
+
(((uint16_t)(*((unsigned char*)(c)+1))) ) \
|
35
|
+
))
|
36
|
+
|
37
|
+
#define read_nbo32(c) ((int32_t)( \
|
38
|
+
(((uint32_t)(*((unsigned char*)(c)+0)))<<24L) | \
|
39
|
+
(((uint32_t)(*((unsigned char*)(c)+1)))<<16L) | \
|
40
|
+
(((uint32_t)(*((unsigned char*)(c)+2)))<< 8L) | \
|
41
|
+
(((uint32_t)(*((unsigned char*)(c)+3))) ) \
|
42
|
+
))
|
43
|
+
|
44
|
+
#define read_nbo64(c) ((int64_t)( \
|
45
|
+
(((uint64_t)(*((unsigned char*)(c)+0)))<<56LL) | \
|
46
|
+
(((uint64_t)(*((unsigned char*)(c)+1)))<<48LL) | \
|
47
|
+
(((uint64_t)(*((unsigned char*)(c)+2)))<<40LL) | \
|
48
|
+
(((uint64_t)(*((unsigned char*)(c)+3)))<<32LL) | \
|
49
|
+
(((uint64_t)(*((unsigned char*)(c)+4)))<<24LL) | \
|
50
|
+
(((uint64_t)(*((unsigned char*)(c)+5)))<<16LL) | \
|
51
|
+
(((uint64_t)(*((unsigned char*)(c)+6)))<< 8LL) | \
|
52
|
+
(((uint64_t)(*((unsigned char*)(c)+7))) ) \
|
53
|
+
))
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
#define BASE64_ENCODED_SIZE(strlen) (((strlen) + 2) / 3 * 4)
|
58
|
+
#define BASE64_DECODED_SIZE(base64len) (((base64len) + 3) / 4 * 3)
|
59
|
+
|
60
|
+
void base64_encode( char *out, const char *in, int len);
|
61
|
+
int base64_decode( char *out, const char *in, unsigned int len);
|
62
|
+
|
63
|
+
int rbpg_strncasecmp(const char *s1, const char *s2, size_t n);
|
64
|
+
|
65
|
+
#endif /* end __utils_h */
|
@@ -0,0 +1,522 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'pg' unless defined?( PG )
|
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)
|
26
|
+
module PG::BasicTypeRegistry
|
27
|
+
# An instance of this class stores the coders that should be used for a given wire format (text or binary)
|
28
|
+
# and type cast direction (encoder or decoder).
|
29
|
+
class CoderMap
|
30
|
+
# Hash of text types that don't require quotation, when used within composite types.
|
31
|
+
# type.name => true
|
32
|
+
DONT_QUOTE_TYPES = %w[
|
33
|
+
int2 int4 int8
|
34
|
+
float4 float8
|
35
|
+
oid
|
36
|
+
bool
|
37
|
+
date timestamp timestamptz
|
38
|
+
].inject({}){|h,e| h[e] = true; h }
|
39
|
+
|
40
|
+
def initialize(result, coders_by_name, format, arraycoder)
|
41
|
+
coder_map = {}
|
42
|
+
|
43
|
+
_ranges, nodes = result.partition { |row| row['typinput'] == 'range_in' }
|
44
|
+
leaves, nodes = nodes.partition { |row| row['typelem'].to_i == 0 }
|
45
|
+
arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
|
46
|
+
|
47
|
+
# populate the enum types
|
48
|
+
_enums, leaves = leaves.partition { |row| row['typinput'] == 'enum_in' }
|
49
|
+
# enums.each do |row|
|
50
|
+
# coder_map[row['oid'].to_i] = OID::Enum.new
|
51
|
+
# end
|
52
|
+
|
53
|
+
# populate the base types
|
54
|
+
leaves.find_all { |row| coders_by_name.key?(row['typname']) }.each do |row|
|
55
|
+
coder = coders_by_name[row['typname']].dup
|
56
|
+
coder.oid = row['oid'].to_i
|
57
|
+
coder.name = row['typname']
|
58
|
+
coder.format = format
|
59
|
+
coder_map[coder.oid] = coder
|
60
|
+
end
|
61
|
+
|
62
|
+
_records_by_oid = result.group_by { |row| row['oid'] }
|
63
|
+
|
64
|
+
# populate composite types
|
65
|
+
# nodes.each do |row|
|
66
|
+
# add_oid row, records_by_oid, coder_map
|
67
|
+
# end
|
68
|
+
|
69
|
+
if arraycoder
|
70
|
+
# populate array types
|
71
|
+
arrays.each do |row|
|
72
|
+
elements_coder = coder_map[row['typelem'].to_i]
|
73
|
+
next unless elements_coder
|
74
|
+
|
75
|
+
coder = arraycoder.new
|
76
|
+
coder.oid = row['oid'].to_i
|
77
|
+
coder.name = row['typname']
|
78
|
+
coder.format = format
|
79
|
+
coder.elements_type = elements_coder
|
80
|
+
coder.needs_quotation = !DONT_QUOTE_TYPES[elements_coder.name]
|
81
|
+
coder_map[coder.oid] = coder
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# populate range types
|
86
|
+
# ranges.find_all { |row| coder_map.key? row['rngsubtype'].to_i }.each do |row|
|
87
|
+
# subcoder = coder_map[row['rngsubtype'].to_i]
|
88
|
+
# range = OID::Range.new subcoder
|
89
|
+
# coder_map[row['oid'].to_i] = range
|
90
|
+
# end
|
91
|
+
|
92
|
+
@coders = coder_map.values
|
93
|
+
@coders_by_name = @coders.inject({}){|h, t| h[t.name] = t; h }
|
94
|
+
@coders_by_oid = @coders.inject({}){|h, t| h[t.oid] = t; h }
|
95
|
+
@typenames_by_oid = result.inject({}){|h, t| h[t['oid'].to_i] = t['typname']; h }
|
96
|
+
end
|
97
|
+
|
98
|
+
attr_reader :coders
|
99
|
+
attr_reader :coders_by_oid
|
100
|
+
attr_reader :coders_by_name
|
101
|
+
attr_reader :typenames_by_oid
|
102
|
+
|
103
|
+
def coder_by_name(name)
|
104
|
+
@coders_by_name[name]
|
105
|
+
end
|
106
|
+
|
107
|
+
def coder_by_oid(oid)
|
108
|
+
@coders_by_oid[oid]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def supports_ranges?(connection)
|
115
|
+
connection.server_version >= 90200
|
116
|
+
end
|
117
|
+
|
118
|
+
def build_coder_maps(connection)
|
119
|
+
if supports_ranges?(connection)
|
120
|
+
result = connection.exec <<-SQL
|
121
|
+
SELECT t.oid, t.typname::text, t.typelem, t.typdelim, t.typinput::text, r.rngsubtype
|
122
|
+
FROM pg_type as t
|
123
|
+
LEFT JOIN pg_range as r ON oid = rngtypid
|
124
|
+
SQL
|
125
|
+
else
|
126
|
+
result = connection.exec <<-SQL
|
127
|
+
SELECT t.oid, t.typname::text, t.typelem, t.typdelim, t.typinput::text
|
128
|
+
FROM pg_type as t
|
129
|
+
SQL
|
130
|
+
end
|
131
|
+
|
132
|
+
[
|
133
|
+
[0, :encoder, PG::TextEncoder::Array],
|
134
|
+
[0, :decoder, PG::TextDecoder::Array],
|
135
|
+
[1, :encoder, nil],
|
136
|
+
[1, :decoder, nil],
|
137
|
+
].inject([]) do |h, (format, direction, arraycoder)|
|
138
|
+
h[format] ||= {}
|
139
|
+
h[format][direction] = CoderMap.new result, CODERS_BY_NAME[format][direction], format, arraycoder
|
140
|
+
h
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
ValidFormats = { 0 => true, 1 => true }
|
145
|
+
ValidDirections = { :encoder => true, :decoder => true }
|
146
|
+
|
147
|
+
def check_format_and_direction(format, direction)
|
148
|
+
raise(ArgumentError, "Invalid format value %p" % format) unless ValidFormats[format]
|
149
|
+
raise(ArgumentError, "Invalid direction %p" % direction) unless ValidDirections[direction]
|
150
|
+
end
|
151
|
+
protected :check_format_and_direction
|
152
|
+
|
153
|
+
# The key of this hash maps to the `typname` column from the table.
|
154
|
+
# encoder_map is then dynamically built with oids as the key and Type
|
155
|
+
# objects as values.
|
156
|
+
CODERS_BY_NAME = []
|
157
|
+
|
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.
|
175
|
+
def self.register_type(format, name, encoder_class, 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
|
178
|
+
end
|
179
|
+
|
180
|
+
# Alias the +old+ type to the +new+ type.
|
181
|
+
def self.alias_type(format, new, 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
|
190
|
+
end
|
191
|
+
|
192
|
+
register_type 0, 'int2', PG::TextEncoder::Integer, PG::TextDecoder::Integer
|
193
|
+
alias_type 0, 'int4', 'int2'
|
194
|
+
alias_type 0, 'int8', 'int2'
|
195
|
+
alias_type 0, 'oid', 'int2'
|
196
|
+
|
197
|
+
register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric
|
198
|
+
register_type 0, 'text', PG::TextEncoder::String, PG::TextDecoder::String
|
199
|
+
alias_type 0, 'varchar', 'text'
|
200
|
+
alias_type 0, 'char', 'text'
|
201
|
+
alias_type 0, 'bpchar', 'text'
|
202
|
+
alias_type 0, 'xml', 'text'
|
203
|
+
|
204
|
+
# FIXME: why are we keeping these types as strings?
|
205
|
+
# alias_type 'tsvector', 'text'
|
206
|
+
# alias_type 'interval', 'text'
|
207
|
+
# alias_type 'macaddr', 'text'
|
208
|
+
# alias_type 'uuid', 'text'
|
209
|
+
#
|
210
|
+
# register_type 'money', OID::Money.new
|
211
|
+
# There is no PG::TextEncoder::Bytea, because it's simple and more efficient to send bytea-data
|
212
|
+
# in binary format, either with PG::BinaryEncoder::Bytea or in Hash param format.
|
213
|
+
register_type 0, 'bytea', nil, PG::TextDecoder::Bytea
|
214
|
+
register_type 0, 'bool', PG::TextEncoder::Boolean, PG::TextDecoder::Boolean
|
215
|
+
# register_type 'bit', OID::Bit.new
|
216
|
+
# register_type 'varbit', OID::Bit.new
|
217
|
+
|
218
|
+
register_type 0, 'float4', PG::TextEncoder::Float, PG::TextDecoder::Float
|
219
|
+
alias_type 0, 'float8', 'float4'
|
220
|
+
|
221
|
+
register_type 0, 'timestamp', PG::TextEncoder::TimestampWithoutTimeZone, PG::TextDecoder::TimestampWithoutTimeZone
|
222
|
+
register_type 0, 'timestamptz', PG::TextEncoder::TimestampWithTimeZone, PG::TextDecoder::TimestampWithTimeZone
|
223
|
+
register_type 0, 'date', PG::TextEncoder::Date, PG::TextDecoder::Date
|
224
|
+
# register_type 'time', OID::Time.new
|
225
|
+
#
|
226
|
+
# register_type 'path', OID::Text.new
|
227
|
+
# register_type 'point', OID::Point.new
|
228
|
+
# register_type 'polygon', OID::Text.new
|
229
|
+
# register_type 'circle', OID::Text.new
|
230
|
+
# register_type 'hstore', OID::Hstore.new
|
231
|
+
register_type 0, 'json', PG::TextEncoder::JSON, PG::TextDecoder::JSON
|
232
|
+
alias_type 0, 'jsonb', 'json'
|
233
|
+
# register_type 'citext', OID::Text.new
|
234
|
+
# register_type 'ltree', OID::Text.new
|
235
|
+
#
|
236
|
+
register_type 0, 'inet', PG::TextEncoder::Inet, PG::TextDecoder::Inet
|
237
|
+
alias_type 0, 'cidr', 'inet'
|
238
|
+
|
239
|
+
|
240
|
+
|
241
|
+
register_type 1, 'int2', PG::BinaryEncoder::Int2, PG::BinaryDecoder::Integer
|
242
|
+
register_type 1, 'int4', PG::BinaryEncoder::Int4, PG::BinaryDecoder::Integer
|
243
|
+
register_type 1, 'int8', PG::BinaryEncoder::Int8, PG::BinaryDecoder::Integer
|
244
|
+
alias_type 1, 'oid', 'int2'
|
245
|
+
|
246
|
+
register_type 1, 'text', PG::BinaryEncoder::String, PG::BinaryDecoder::String
|
247
|
+
alias_type 1, 'varchar', 'text'
|
248
|
+
alias_type 1, 'char', 'text'
|
249
|
+
alias_type 1, 'bpchar', 'text'
|
250
|
+
alias_type 1, 'xml', 'text'
|
251
|
+
|
252
|
+
register_type 1, 'bytea', PG::BinaryEncoder::Bytea, PG::BinaryDecoder::Bytea
|
253
|
+
register_type 1, 'bool', PG::BinaryEncoder::Boolean, PG::BinaryDecoder::Boolean
|
254
|
+
register_type 1, 'float4', nil, PG::BinaryDecoder::Float
|
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
|
258
|
+
end
|
259
|
+
|
260
|
+
# Simple set of rules for type casting common PostgreSQL types to Ruby.
|
261
|
+
#
|
262
|
+
# OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
|
263
|
+
# PostgreSQL's +pg_type+ table in PG::BasicTypeMapForResults.new .
|
264
|
+
#
|
265
|
+
# Result values are type casted based on the type OID of the given result column.
|
266
|
+
#
|
267
|
+
# Higher level libraries will most likely not make use of this class, but use their
|
268
|
+
# own set of rules to choose suitable encoders and decoders.
|
269
|
+
#
|
270
|
+
# Example:
|
271
|
+
# conn = PG::Connection.new
|
272
|
+
# # Assign a default ruleset for type casts of output values.
|
273
|
+
# conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn)
|
274
|
+
# # Execute a query.
|
275
|
+
# res = conn.exec_params( "SELECT $1::INT", ['5'] )
|
276
|
+
# # Retrieve and cast the result value. Value format is 0 (text) and OID is 20. Therefore typecasting
|
277
|
+
# # is done by PG::TextDecoder::Integer internally for all value retrieval methods.
|
278
|
+
# res.values # => [[5]]
|
279
|
+
#
|
280
|
+
# PG::TypeMapByOid#build_column_map(result) can be used to generate
|
281
|
+
# a result independent PG::TypeMapByColumn type map, which can subsequently be used
|
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
|
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.
|
304
|
+
class PG::BasicTypeMapForResults < PG::TypeMapByOid
|
305
|
+
include PG::BasicTypeRegistry
|
306
|
+
|
307
|
+
class WarningTypeMap < PG::TypeMapInRuby
|
308
|
+
def initialize(typenames)
|
309
|
+
@already_warned = Hash.new{|h, k| h[k] = {} }
|
310
|
+
@typenames_by_oid = typenames
|
311
|
+
end
|
312
|
+
|
313
|
+
def typecast_result_value(result, _tuple, field)
|
314
|
+
format = result.fformat(field)
|
315
|
+
oid = result.ftype(field)
|
316
|
+
unless @already_warned[format][oid]
|
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."
|
318
|
+
@already_warned[format][oid] = true
|
319
|
+
end
|
320
|
+
super
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def initialize(connection)
|
325
|
+
@coder_maps = build_coder_maps(connection)
|
326
|
+
|
327
|
+
# Populate TypeMapByOid hash with decoders
|
328
|
+
@coder_maps.map{|f| f[:decoder].coders }.flatten.each do |coder|
|
329
|
+
add_coder(coder)
|
330
|
+
end
|
331
|
+
|
332
|
+
typenames = @coder_maps.map{|f| f[:decoder].typenames_by_oid }
|
333
|
+
self.default_type_map = WarningTypeMap.new(typenames)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# Simple set of rules for type casting common PostgreSQL types from Ruby
|
338
|
+
# to PostgreSQL.
|
339
|
+
#
|
340
|
+
# OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
|
341
|
+
# PostgreSQL's +pg_type+ table in PG::BasicTypeMapBasedOnResult.new .
|
342
|
+
#
|
343
|
+
# This class works equal to PG::BasicTypeMapForResults, but does not define decoders for
|
344
|
+
# the given result OIDs, but encoders. So it can be used to type cast field values based on
|
345
|
+
# the type OID retrieved by a separate SQL query.
|
346
|
+
#
|
347
|
+
# PG::TypeMapByOid#build_column_map(result) can be used to generate a result independent
|
348
|
+
# PG::TypeMapByColumn type map, which can subsequently be used to cast query bind parameters
|
349
|
+
# or #put_copy_data fields.
|
350
|
+
#
|
351
|
+
# Example:
|
352
|
+
# conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
|
353
|
+
#
|
354
|
+
# # Retrieve table OIDs per empty result set.
|
355
|
+
# res = conn.exec( "SELECT * FROM copytable LIMIT 0" )
|
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 )
|
360
|
+
# row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
|
361
|
+
#
|
362
|
+
# conn.copy_data( "COPY copytable FROM STDIN", row_encoder ) do |res|
|
363
|
+
# conn.put_copy_data ['a', 123, [5,4,3]]
|
364
|
+
# end
|
365
|
+
# This inserts a single row into copytable with type casts from ruby to
|
366
|
+
# database types.
|
367
|
+
class PG::BasicTypeMapBasedOnResult < PG::TypeMapByOid
|
368
|
+
include PG::BasicTypeRegistry
|
369
|
+
|
370
|
+
def initialize(connection)
|
371
|
+
@coder_maps = build_coder_maps(connection)
|
372
|
+
|
373
|
+
# Populate TypeMapByOid hash with encoders
|
374
|
+
@coder_maps.map{|f| f[:encoder].coders }.flatten.each do |coder|
|
375
|
+
add_coder(coder)
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
# Simple set of rules for type casting common Ruby types to PostgreSQL.
|
381
|
+
#
|
382
|
+
# OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
|
383
|
+
# PostgreSQL's pg_type table in PG::BasicTypeMapForQueries.new .
|
384
|
+
#
|
385
|
+
# Query params are type casted based on the class of the given value.
|
386
|
+
#
|
387
|
+
# Higher level libraries will most likely not make use of this class, but use their
|
388
|
+
# own derivation of PG::TypeMapByClass or another set of rules to choose suitable
|
389
|
+
# encoders and decoders for the values to be sent.
|
390
|
+
#
|
391
|
+
# Example:
|
392
|
+
# conn = PG::Connection.new
|
393
|
+
# # Assign a default ruleset for type casts of input and output values.
|
394
|
+
# conn.type_map_for_queries = PG::BasicTypeMapForQueries.new(conn)
|
395
|
+
# # Execute a query. The Integer param value is typecasted internally by PG::BinaryEncoder::Int8.
|
396
|
+
# # The format of the parameter is set to 0 (text) and the OID of this parameter is set to 20 (int8).
|
397
|
+
# res = conn.exec_params( "SELECT $1", [5] )
|
398
|
+
class PG::BasicTypeMapForQueries < PG::TypeMapByClass
|
399
|
+
include PG::BasicTypeRegistry
|
400
|
+
|
401
|
+
def initialize(connection)
|
402
|
+
@coder_maps = build_coder_maps(connection)
|
403
|
+
@array_encoders_by_klass = array_encoders_by_klass
|
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
|
432
|
+
end
|
433
|
+
|
434
|
+
attr_reader :encode_array_as
|
435
|
+
|
436
|
+
private
|
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
|
+
|
444
|
+
def coder_by_name(format, direction, name)
|
445
|
+
check_format_and_direction(format, direction)
|
446
|
+
@coder_maps[format][direction].coder_by_name(name)
|
447
|
+
end
|
448
|
+
|
449
|
+
def populate_encoder_list
|
450
|
+
DEFAULT_TYPE_MAP.each do |klass, selector|
|
451
|
+
if Array === selector
|
452
|
+
format, name, oid_name = selector
|
453
|
+
coder = coder_by_name(format, :encoder, name).dup
|
454
|
+
if oid_name
|
455
|
+
coder.oid = coder_by_name(format, :encoder, oid_name).oid
|
456
|
+
else
|
457
|
+
coder.oid = 0
|
458
|
+
end
|
459
|
+
self[klass] = coder
|
460
|
+
else
|
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
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
def array_encoders_by_klass
|
479
|
+
DEFAULT_ARRAY_TYPE_MAP.inject({}) do |h, (klass, (format, name))|
|
480
|
+
h[klass] = coder_by_name(format, :encoder, name)
|
481
|
+
h
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
def get_array_type(value)
|
486
|
+
elem = value
|
487
|
+
while elem.kind_of?(Array)
|
488
|
+
elem = elem.first
|
489
|
+
end
|
490
|
+
@array_encoders_by_klass[elem.class] ||
|
491
|
+
elem.class.ancestors.lazy.map{|ancestor| @array_encoders_by_klass[ancestor] }.find{|a| a } ||
|
492
|
+
@textarray_encoder
|
493
|
+
end
|
494
|
+
|
495
|
+
DEFAULT_TYPE_MAP = {
|
496
|
+
TrueClass => [1, 'bool', 'bool'],
|
497
|
+
FalseClass => [1, 'bool', 'bool'],
|
498
|
+
# We use text format and no type OID for numbers, because setting the OID can lead
|
499
|
+
# to unnecessary type conversions on server side.
|
500
|
+
Integer => [0, 'int8'],
|
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'],
|
508
|
+
Array => :get_array_type,
|
509
|
+
}
|
510
|
+
|
511
|
+
DEFAULT_ARRAY_TYPE_MAP = {
|
512
|
+
TrueClass => [0, '_bool'],
|
513
|
+
FalseClass => [0, '_bool'],
|
514
|
+
Integer => [0, '_int8'],
|
515
|
+
String => [0, '_text'],
|
516
|
+
Float => [0, '_float8'],
|
517
|
+
BigDecimal => [0, '_numeric'],
|
518
|
+
Time => [0, '_timestamptz'],
|
519
|
+
IPAddr => [0, '_inet'],
|
520
|
+
}
|
521
|
+
|
522
|
+
end
|