pg 0.17.1 → 0.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/ChangeLog +2407 -2
  4. data/History.rdoc +68 -0
  5. data/Manifest.txt +29 -1
  6. data/README-Windows.rdoc +15 -26
  7. data/README.rdoc +52 -2
  8. data/Rakefile +56 -18
  9. data/Rakefile.cross +77 -49
  10. data/ext/extconf.rb +33 -26
  11. data/ext/pg.c +142 -21
  12. data/ext/pg.h +242 -6
  13. data/ext/pg_binary_decoder.c +162 -0
  14. data/ext/pg_binary_encoder.c +162 -0
  15. data/ext/pg_coder.c +479 -0
  16. data/ext/pg_connection.c +858 -553
  17. data/ext/pg_copy_coder.c +561 -0
  18. data/ext/pg_errors.c +6 -0
  19. data/ext/pg_result.c +479 -128
  20. data/ext/pg_text_decoder.c +421 -0
  21. data/ext/pg_text_encoder.c +663 -0
  22. data/ext/pg_type_map.c +159 -0
  23. data/ext/pg_type_map_all_strings.c +116 -0
  24. data/ext/pg_type_map_by_class.c +239 -0
  25. data/ext/pg_type_map_by_column.c +312 -0
  26. data/ext/pg_type_map_by_mri_type.c +284 -0
  27. data/ext/pg_type_map_by_oid.c +355 -0
  28. data/ext/pg_type_map_in_ruby.c +299 -0
  29. data/ext/util.c +149 -0
  30. data/ext/util.h +65 -0
  31. data/lib/pg/basic_type_mapping.rb +399 -0
  32. data/lib/pg/coder.rb +83 -0
  33. data/lib/pg/connection.rb +81 -29
  34. data/lib/pg/result.rb +13 -3
  35. data/lib/pg/text_decoder.rb +44 -0
  36. data/lib/pg/text_encoder.rb +27 -0
  37. data/lib/pg/type_map_by_column.rb +15 -0
  38. data/lib/pg.rb +12 -2
  39. data/spec/{lib/helpers.rb → helpers.rb} +101 -39
  40. data/spec/pg/basic_type_mapping_spec.rb +251 -0
  41. data/spec/pg/connection_spec.rb +516 -218
  42. data/spec/pg/result_spec.rb +216 -112
  43. data/spec/pg/type_map_by_class_spec.rb +138 -0
  44. data/spec/pg/type_map_by_column_spec.rb +222 -0
  45. data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
  46. data/spec/pg/type_map_by_oid_spec.rb +149 -0
  47. data/spec/pg/type_map_in_ruby_spec.rb +164 -0
  48. data/spec/pg/type_map_spec.rb +22 -0
  49. data/spec/pg/type_spec.rb +697 -0
  50. data/spec/pg_spec.rb +24 -18
  51. data.tar.gz.sig +0 -0
  52. metadata +111 -45
  53. metadata.gz.sig +0 -0
data/ext/util.c ADDED
@@ -0,0 +1,149 @@
1
+ /*
2
+ * util.c - Utils for ruby-pg
3
+ * $Id: util.c,v 5fb9170f6a7d 2015/06/29 11:15:12 kanis $
4
+ *
5
+ */
6
+
7
+ #include "pg.h"
8
+ #include "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, char *in, int len)
19
+ {
20
+ unsigned char *in_ptr = (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 = part_len > 2 ? *--in_ptr : 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 = part_len > 2 ? base64_encode_table[(triple >> 0 * 6) & 0x3F] : '=';
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, char *in, unsigned int len)
76
+ {
77
+ unsigned char a, b, c, d;
78
+ unsigned char *in_ptr = (unsigned char *)in;
79
+ unsigned char *out_ptr = (unsigned char *)out;
80
+ 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/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, char *in, int len);
61
+ int base64_decode( char *out, 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,399 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pg' unless defined?( PG )
4
+
5
+ module PG::BasicTypeRegistry
6
+ # An instance of this class stores the coders that should be used for a given wire format (text or binary)
7
+ # and type cast direction (encoder or decoder).
8
+ class CoderMap
9
+ # Hash of text types that don't require quotation, when used within composite types.
10
+ # type.name => true
11
+ DONT_QUOTE_TYPES = %w[
12
+ int2 int4 int8
13
+ float4 float8
14
+ oid
15
+ bool
16
+ date timestamp timestamptz
17
+ ].inject({}){|h,e| h[e] = true; h }
18
+
19
+ def initialize(result, coders_by_name, format, arraycoder)
20
+ coder_map = {}
21
+
22
+ _ranges, nodes = result.partition { |row| row['typinput'] == 'range_in' }
23
+ leaves, nodes = nodes.partition { |row| row['typelem'].to_i == 0 }
24
+ arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
25
+
26
+ # populate the enum types
27
+ _enums, leaves = leaves.partition { |row| row['typinput'] == 'enum_in' }
28
+ # enums.each do |row|
29
+ # coder_map[row['oid'].to_i] = OID::Enum.new
30
+ # end
31
+
32
+ # populate the base types
33
+ leaves.find_all { |row| coders_by_name.key?(row['typname']) }.each do |row|
34
+ coder = coders_by_name[row['typname']].dup
35
+ coder.oid = row['oid'].to_i
36
+ coder.name = row['typname']
37
+ coder.format = format
38
+ coder_map[coder.oid] = coder
39
+ end
40
+
41
+ _records_by_oid = result.group_by { |row| row['oid'] }
42
+
43
+ # populate composite types
44
+ # nodes.each do |row|
45
+ # add_oid row, records_by_oid, coder_map
46
+ # end
47
+
48
+ if arraycoder
49
+ # populate array types
50
+ arrays.each do |row|
51
+ elements_coder = coder_map[row['typelem'].to_i]
52
+ next unless elements_coder
53
+
54
+ coder = arraycoder.new
55
+ coder.oid = row['oid'].to_i
56
+ coder.name = row['typname']
57
+ coder.format = format
58
+ coder.elements_type = elements_coder
59
+ coder.needs_quotation = !DONT_QUOTE_TYPES[elements_coder.name]
60
+ coder_map[coder.oid] = coder
61
+ end
62
+ end
63
+
64
+ # populate range types
65
+ # ranges.find_all { |row| coder_map.key? row['rngsubtype'].to_i }.each do |row|
66
+ # subcoder = coder_map[row['rngsubtype'].to_i]
67
+ # range = OID::Range.new subcoder
68
+ # coder_map[row['oid'].to_i] = range
69
+ # end
70
+
71
+ @coders = coder_map.values
72
+ @coders_by_name = @coders.inject({}){|h, t| h[t.name] = t; h }
73
+ @coders_by_oid = @coders.inject({}){|h, t| h[t.oid] = t; h }
74
+ @typenames_by_oid = result.inject({}){|h, t| h[t['oid'].to_i] = t['typname']; h }
75
+ end
76
+
77
+ attr_reader :coders
78
+ attr_reader :coders_by_oid
79
+ attr_reader :coders_by_name
80
+ attr_reader :typenames_by_oid
81
+
82
+ def coder_by_name(name)
83
+ @coders_by_name[name]
84
+ end
85
+
86
+ def coder_by_oid(oid)
87
+ @coders_by_oid[oid]
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def supports_ranges?(connection)
94
+ connection.server_version >= 90200
95
+ end
96
+
97
+ def build_coder_maps(connection)
98
+ if supports_ranges?(connection)
99
+ result = connection.exec <<-SQL
100
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype
101
+ FROM pg_type as t
102
+ LEFT JOIN pg_range as r ON oid = rngtypid
103
+ SQL
104
+ else
105
+ result = connection.exec <<-SQL
106
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput
107
+ FROM pg_type as t
108
+ SQL
109
+ end
110
+
111
+ [
112
+ [0, :encoder, PG::TextEncoder::Array],
113
+ [0, :decoder, PG::TextDecoder::Array],
114
+ [1, :encoder, nil],
115
+ [1, :decoder, nil],
116
+ ].inject([]) do |h, (format, direction, arraycoder)|
117
+ h[format] ||= {}
118
+ h[format][direction] = CoderMap.new result, CODERS_BY_NAME[format][direction], format, arraycoder
119
+ h
120
+ end
121
+ end
122
+
123
+ ValidFormats = { 0 => true, 1 => true }
124
+ ValidDirections = { :encoder => true, :decoder => true }
125
+
126
+ def check_format_and_direction(format, direction)
127
+ raise(ArgumentError, "Invalid format value %p" % format) unless ValidFormats[format]
128
+ raise(ArgumentError, "Invalid direction %p" % direction) unless ValidDirections[direction]
129
+ end
130
+ protected :check_format_and_direction
131
+
132
+ # The key of this hash maps to the `typname` column from the table.
133
+ # encoder_map is then dynamically built with oids as the key and Type
134
+ # objects as values.
135
+ CODERS_BY_NAME = []
136
+
137
+ # Register an OID type named +name+ with a typecasting encoder and decoder object in
138
+ # +type+. +name+ should correspond to the `typname` column in
139
+ # the `pg_type` table.
140
+ def self.register_type(format, name, encoder_class, decoder_class)
141
+ CODERS_BY_NAME[format] ||= { encoder: {}, decoder: {} }
142
+ CODERS_BY_NAME[format][:encoder][name] = encoder_class.new(name: name, format: format) if encoder_class
143
+ CODERS_BY_NAME[format][:decoder][name] = decoder_class.new(name: name, format: format) if decoder_class
144
+ end
145
+
146
+ # Alias the +old+ type to the +new+ type.
147
+ def self.alias_type(format, new, old)
148
+ CODERS_BY_NAME[format][:encoder][new] = CODERS_BY_NAME[format][:encoder][old]
149
+ CODERS_BY_NAME[format][:decoder][new] = CODERS_BY_NAME[format][:decoder][old]
150
+ end
151
+
152
+ register_type 0, 'int2', PG::TextEncoder::Integer, PG::TextDecoder::Integer
153
+ alias_type 0, 'int4', 'int2'
154
+ alias_type 0, 'int8', 'int2'
155
+ alias_type 0, 'oid', 'int2'
156
+
157
+ # register_type 0, 'numeric', OID::Decimal.new
158
+ register_type 0, 'text', PG::TextEncoder::String, PG::TextDecoder::String
159
+ alias_type 0, 'varchar', 'text'
160
+ alias_type 0, 'char', 'text'
161
+ alias_type 0, 'bpchar', 'text'
162
+ alias_type 0, 'xml', 'text'
163
+
164
+ # FIXME: why are we keeping these types as strings?
165
+ # alias_type 'tsvector', 'text'
166
+ # alias_type 'interval', 'text'
167
+ # alias_type 'macaddr', 'text'
168
+ # alias_type 'uuid', 'text'
169
+ #
170
+ # register_type 'money', OID::Money.new
171
+ # There is no PG::TextEncoder::Bytea, because it's simple and more efficient to send bytea-data
172
+ # in binary format, either with PG::BinaryEncoder::Bytea or in Hash param format.
173
+ register_type 0, 'bytea', nil, PG::TextDecoder::Bytea
174
+ register_type 0, 'bool', PG::TextEncoder::Boolean, PG::TextDecoder::Boolean
175
+ # register_type 'bit', OID::Bit.new
176
+ # register_type 'varbit', OID::Bit.new
177
+
178
+ register_type 0, 'float4', PG::TextEncoder::Float, PG::TextDecoder::Float
179
+ alias_type 0, 'float8', 'float4'
180
+
181
+ register_type 0, 'timestamp', PG::TextEncoder::TimestampWithoutTimeZone, PG::TextDecoder::TimestampWithoutTimeZone
182
+ register_type 0, 'timestamptz', PG::TextEncoder::TimestampWithTimeZone, PG::TextDecoder::TimestampWithTimeZone
183
+ register_type 0, 'date', PG::TextEncoder::Date, PG::TextDecoder::Date
184
+ # register_type 'time', OID::Time.new
185
+ #
186
+ # register_type 'path', OID::Text.new
187
+ # register_type 'point', OID::Point.new
188
+ # register_type 'polygon', OID::Text.new
189
+ # register_type 'circle', OID::Text.new
190
+ # register_type 'hstore', OID::Hstore.new
191
+ # register_type 'json', OID::Json.new
192
+ # register_type 'citext', OID::Text.new
193
+ # register_type 'ltree', OID::Text.new
194
+ #
195
+ # register_type 'cidr', OID::Cidr.new
196
+ # alias_type 'inet', 'cidr'
197
+
198
+
199
+
200
+ register_type 1, 'int2', PG::BinaryEncoder::Int2, PG::BinaryDecoder::Integer
201
+ register_type 1, 'int4', PG::BinaryEncoder::Int4, PG::BinaryDecoder::Integer
202
+ register_type 1, 'int8', PG::BinaryEncoder::Int8, PG::BinaryDecoder::Integer
203
+ alias_type 1, 'oid', 'int2'
204
+
205
+ register_type 1, 'text', PG::BinaryEncoder::String, PG::BinaryDecoder::String
206
+ alias_type 1, 'varchar', 'text'
207
+ alias_type 1, 'char', 'text'
208
+ alias_type 1, 'bpchar', 'text'
209
+ alias_type 1, 'xml', 'text'
210
+
211
+ register_type 1, 'bytea', PG::BinaryEncoder::Bytea, PG::BinaryDecoder::Bytea
212
+ register_type 1, 'bool', PG::BinaryEncoder::Boolean, PG::BinaryDecoder::Boolean
213
+ register_type 1, 'float4', nil, PG::BinaryDecoder::Float
214
+ register_type 1, 'float8', nil, PG::BinaryDecoder::Float
215
+ end
216
+
217
+ # Simple set of rules for type casting common PostgreSQL types to Ruby.
218
+ #
219
+ # 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 .
221
+ #
222
+ # Result values are type casted based on the type OID of the given result column.
223
+ #
224
+ # Higher level libraries will most likely not make use of this class, but use their
225
+ # own set of rules to choose suitable encoders and decoders.
226
+ #
227
+ # Example:
228
+ # conn = PG::Connection.new
229
+ # # Assign a default ruleset for type casts of input and output values.
230
+ # conn.type_mapping = PG::BasicTypeMapping.new(conn)
231
+ # # Execute a query.
232
+ # res = conn.exec_params( "SELECT $1::INT", ['5'] )
233
+ # # Retrieve and cast the result value. Value format is 0 (text) and OID is 20. Therefore typecasting
234
+ # # is done by PG::TextDecoder::Integer internally for all value retrieval methods.
235
+ # res.values # => [[5]]
236
+ #
237
+ # PG::TypeMapByOid#fit_to_result(result, false) can be used to generate
238
+ # a result independent PG::TypeMapByColumn type map, which can subsequently be used
239
+ # to cast #get_copy_data fields. See also PG::BasicTypeMapBasedOnResult .
240
+ #
241
+ class PG::BasicTypeMapForResults < PG::TypeMapByOid
242
+ include PG::BasicTypeRegistry
243
+
244
+ class WarningTypeMap < PG::TypeMapInRuby
245
+ def initialize(typenames)
246
+ @already_warned = Hash.new{|h, k| h[k] = {} }
247
+ @typenames_by_oid = typenames
248
+ end
249
+
250
+ def typecast_result_value(result, _tuple, field)
251
+ format = result.fformat(field)
252
+ oid = result.ftype(field)
253
+ unless @already_warned[format][oid]
254
+ STDERR.puts "Warning: no type cast defined for type #{@typenames_by_oid[format][oid].inspect} with oid #{oid}. Please cast this type explicitly to TEXT to be safe for future changes."
255
+ @already_warned[format][oid] = true
256
+ end
257
+ super
258
+ end
259
+ end
260
+
261
+ def initialize(connection)
262
+ @coder_maps = build_coder_maps(connection)
263
+
264
+ # Populate TypeMapByOid hash with decoders
265
+ @coder_maps.map{|f| f[:decoder].coders }.flatten.each do |coder|
266
+ add_coder(coder)
267
+ end
268
+
269
+ typenames = @coder_maps.map{|f| f[:decoder].typenames_by_oid }
270
+ self.default_type_map = WarningTypeMap.new(typenames)
271
+ end
272
+ end
273
+
274
+ # Simple set of rules for type casting common PostgreSQL types from Ruby
275
+ # to PostgreSQL.
276
+ #
277
+ # 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 .
279
+ #
280
+ # This class works equal to PG::BasicTypeMapForResults, but does not define decoders for
281
+ # the given result OIDs, but encoders. So it can be used to type cast field values based on
282
+ # the type OID retrieved by a separate SQL query.
283
+ #
284
+ # PG::TypeMapByOid#build_column_map(result) can be used to generate a result independent
285
+ # PG::TypeMapByColumn type map, which can subsequently be used to cast query bind parameters
286
+ # or #put_copy_data fields.
287
+ #
288
+ # Example:
289
+ # conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
290
+ #
291
+ # # Retrieve table OIDs per empty result set.
292
+ # res = conn.exec( "SELECT * FROM copytable LIMIT 0" )
293
+ # tm = basic_type_mapping.build_column_map( res )
294
+ # row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
295
+ #
296
+ # conn.copy_data( "COPY copytable FROM STDIN", row_encoder ) do |res|
297
+ # conn.put_copy_data ['a', 123, [5,4,3]]
298
+ # end
299
+ class PG::BasicTypeMapBasedOnResult < PG::TypeMapByOid
300
+ include PG::BasicTypeRegistry
301
+
302
+ def initialize(connection)
303
+ @coder_maps = build_coder_maps(connection)
304
+
305
+ # Populate TypeMapByOid hash with encoders
306
+ @coder_maps.map{|f| f[:encoder].coders }.flatten.each do |coder|
307
+ add_coder(coder)
308
+ end
309
+ end
310
+ end
311
+
312
+ # Simple set of rules for type casting common Ruby types to PostgreSQL.
313
+ #
314
+ # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
315
+ # PostgreSQL's pg_type table in PG::BasicTypeMapForQueries.new .
316
+ #
317
+ # Query params are type casted based on the MRI internal type of the given value.
318
+ #
319
+ # Higher level libraries will most likely not make use of this class, but use their
320
+ # own set of rules to choose suitable encoders and decoders.
321
+ #
322
+ # Example:
323
+ # conn = PG::Connection.new
324
+ # # Assign a default ruleset for type casts of input and output values.
325
+ # conn.type_mapping_for_queries = PG::BasicTypeMapForQueries.new(conn)
326
+ # # Execute a query. The Integer param value is typecasted internally by PG::BinaryEncoder::Int8.
327
+ # # The format of the parameter is set to 1 (binary) and the OID of this parameter is set to 20 (int8).
328
+ # res = conn.exec_params( "SELECT $1", [5] )
329
+ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
330
+ include PG::BasicTypeRegistry
331
+
332
+ def initialize(connection)
333
+ @coder_maps = build_coder_maps(connection)
334
+
335
+ populate_encoder_list
336
+ @array_encoders_by_klass = array_encoders_by_klass
337
+ @anyarray_encoder = coder_by_name(0, :encoder, '_any')
338
+ end
339
+
340
+ private
341
+
342
+ def coder_by_name(format, direction, name)
343
+ check_format_and_direction(format, direction)
344
+ @coder_maps[format][direction].coder_by_name(name)
345
+ end
346
+
347
+ def populate_encoder_list
348
+ DEFAULT_TYPE_MAP.each do |klass, selector|
349
+ if Array === selector
350
+ format, name, oid_name = selector
351
+ coder = coder_by_name(format, :encoder, name).dup
352
+ if oid_name
353
+ coder.oid = coder_by_name(format, :encoder, oid_name).oid
354
+ else
355
+ coder.oid = 0
356
+ end
357
+ self[klass] = coder
358
+ else
359
+ self[klass] = selector
360
+ end
361
+ end
362
+ end
363
+
364
+ def array_encoders_by_klass
365
+ DEFAULT_ARRAY_TYPE_MAP.inject({}) do |h, (klass, (format, name))|
366
+ h[klass] = coder_by_name(format, :encoder, name)
367
+ h
368
+ end
369
+ end
370
+
371
+ def get_array_type(value)
372
+ elem = value
373
+ while elem.kind_of?(Array)
374
+ elem = elem.first
375
+ end
376
+ @array_encoders_by_klass[elem.class] ||
377
+ elem.class.ancestors.lazy.map{|ancestor| @array_encoders_by_klass[ancestor] }.find{|a| a } ||
378
+ @anyarray_encoder
379
+ end
380
+
381
+ DEFAULT_TYPE_MAP = {
382
+ TrueClass => [1, 'bool', 'bool'],
383
+ FalseClass => [1, 'bool', 'bool'],
384
+ # We use text format and no type OID for numbers, because setting the OID can lead
385
+ # to unnecessary type conversions on server side.
386
+ Integer => [0, 'int8'],
387
+ Float => [0, 'float8'],
388
+ Array => :get_array_type,
389
+ }
390
+
391
+ DEFAULT_ARRAY_TYPE_MAP = {
392
+ TrueClass => [0, '_bool'],
393
+ FalseClass => [0, '_bool'],
394
+ Integer => [0, '_int8'],
395
+ String => [0, '_text'],
396
+ Float => [0, '_float8'],
397
+ }
398
+
399
+ end
data/lib/pg/coder.rb ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module PG
4
+
5
+ class Coder
6
+
7
+ module BinaryFormatting
8
+ Params = { format: 1 }
9
+ def initialize( params={} )
10
+ super(params.merge(Params))
11
+ end
12
+ end
13
+
14
+
15
+ # Create a new coder object based on the attribute Hash.
16
+ def initialize(params={})
17
+ params.each do |key, val|
18
+ send("#{key}=", val)
19
+ end
20
+ end
21
+
22
+ def dup
23
+ self.class.new(to_h)
24
+ end
25
+
26
+ # Returns coder attributes as Hash.
27
+ def to_h
28
+ {
29
+ oid: oid,
30
+ format: format,
31
+ name: name,
32
+ }
33
+ end
34
+
35
+ def ==(v)
36
+ self.class == v.class && to_h == v.to_h
37
+ end
38
+
39
+ def marshal_dump
40
+ Marshal.dump(to_h)
41
+ end
42
+
43
+ def marshal_load(str)
44
+ initialize Marshal.load(str)
45
+ end
46
+
47
+ def inspect
48
+ str = self.to_s
49
+ oid_str = " oid=#{oid}" unless oid==0
50
+ format_str = " format=#{format}" unless format==0
51
+ name_str = " #{name.inspect}" if name
52
+ str[-1,0] = "#{name_str} #{oid_str}#{format_str}"
53
+ str
54
+ end
55
+ end
56
+
57
+ class CompositeCoder < Coder
58
+ def to_h
59
+ super.merge!({
60
+ elements_type: elements_type,
61
+ needs_quotation: needs_quotation?,
62
+ delimiter: delimiter,
63
+ })
64
+ end
65
+
66
+ def inspect
67
+ str = super
68
+ str[-1,0] = " elements_type=#{elements_type.inspect} #{needs_quotation? ? 'needs' : 'no'} quotation"
69
+ str
70
+ end
71
+ end
72
+
73
+ class CopyCoder < Coder
74
+ def to_h
75
+ super.merge!({
76
+ type_map: type_map,
77
+ delimiter: delimiter,
78
+ null_string: null_string,
79
+ })
80
+ end
81
+ end
82
+ end # module PG
83
+