pg 0.18.0.pre20140820094244 → 0.18.0.pre20141017155815

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +1573 -2
  5. data/History.rdoc +3 -11
  6. data/Manifest.txt +24 -0
  7. data/README.rdoc +51 -4
  8. data/Rakefile +20 -14
  9. data/Rakefile.cross +39 -32
  10. data/ext/extconf.rb +27 -26
  11. data/ext/pg.c +75 -21
  12. data/ext/pg.h +194 -6
  13. data/ext/pg_binary_decoder.c +160 -0
  14. data/ext/pg_binary_encoder.c +160 -0
  15. data/ext/pg_coder.c +454 -0
  16. data/ext/pg_connection.c +815 -518
  17. data/ext/pg_copy_coder.c +557 -0
  18. data/ext/pg_result.c +258 -103
  19. data/ext/pg_text_decoder.c +424 -0
  20. data/ext/pg_text_encoder.c +608 -0
  21. data/ext/pg_type_map.c +113 -0
  22. data/ext/pg_type_map_all_strings.c +113 -0
  23. data/ext/pg_type_map_by_column.c +254 -0
  24. data/ext/pg_type_map_by_mri_type.c +266 -0
  25. data/ext/pg_type_map_by_oid.c +341 -0
  26. data/ext/util.c +121 -0
  27. data/ext/util.h +63 -0
  28. data/lib/pg.rb +11 -1
  29. data/lib/pg/basic_type_mapping.rb +377 -0
  30. data/lib/pg/coder.rb +74 -0
  31. data/lib/pg/connection.rb +38 -5
  32. data/lib/pg/result.rb +13 -3
  33. data/lib/pg/text_decoder.rb +42 -0
  34. data/lib/pg/text_encoder.rb +27 -0
  35. data/lib/pg/type_map_by_column.rb +15 -0
  36. data/spec/helpers.rb +9 -1
  37. data/spec/pg/basic_type_mapping_spec.rb +251 -0
  38. data/spec/pg/connection_spec.rb +232 -13
  39. data/spec/pg/result_spec.rb +52 -0
  40. data/spec/pg/type_map_by_column_spec.rb +135 -0
  41. data/spec/pg/type_map_by_mri_type_spec.rb +122 -0
  42. data/spec/pg/type_map_by_oid_spec.rb +133 -0
  43. data/spec/pg/type_map_spec.rb +39 -0
  44. data/spec/pg/type_spec.rb +620 -0
  45. metadata +40 -4
  46. metadata.gz.sig +0 -0
data/ext/util.c ADDED
@@ -0,0 +1,121 @@
1
+ /*
2
+ * util.c - Utils for ruby-pg
3
+ * $Id: util.c,v c8d7c26dd595 2014/10/12 17:08:46 lars $
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
+ char *in_ptr = 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
+
data/ext/util.h ADDED
@@ -0,0 +1,63 @@
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
+ #endif /* end __utils_h */
data/lib/pg.rb CHANGED
@@ -7,7 +7,12 @@ rescue LoadError
7
7
  if RUBY_PLATFORM =~/(mswin|mingw)/i
8
8
  major_minor = RUBY_VERSION[ /^(\d+\.\d+)/ ] or
9
9
  raise "Oops, can't extract the major/minor version from #{RUBY_VERSION.dump}"
10
+
11
+ # Set the PATH environment variable, so that libpq.dll can be found.
12
+ old_path = ENV['PATH']
13
+ ENV['PATH'] = "#{old_path};#{File.expand_path("../#{RUBY_PLATFORM}", __FILE__)}"
10
14
  require "#{major_minor}/pg_ext"
15
+ ENV['PATH'] = old_path
11
16
  else
12
17
  raise
13
18
  end
@@ -22,7 +27,7 @@ module PG
22
27
  VERSION = '0.18.0'
23
28
 
24
29
  # VCS revision
25
- REVISION = %q$Revision: 8e95248e80f6 $
30
+ REVISION = %q$Revision: 57d770944b5d $
26
31
 
27
32
  class NotAllCopyDataRetrieved < PG::Error
28
33
  end
@@ -43,6 +48,11 @@ module PG
43
48
 
44
49
  require 'pg/exceptions'
45
50
  require 'pg/constants'
51
+ require 'pg/coder'
52
+ require 'pg/text_encoder'
53
+ require 'pg/text_decoder'
54
+ require 'pg/basic_type_mapping'
55
+ require 'pg/type_map_by_column'
46
56
  require 'pg/connection'
47
57
  require 'pg/result'
48
58
 
@@ -0,0 +1,377 @@
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
+ end
75
+
76
+ attr_reader :coders
77
+ attr_reader :coders_by_oid
78
+ attr_reader :coders_by_name
79
+
80
+ def coder_by_name(name)
81
+ @coders_by_name[name]
82
+ end
83
+
84
+ def coder_by_oid(oid)
85
+ @coders_by_oid[oid]
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def supports_ranges?(connection)
92
+ connection.server_version >= 90200
93
+ end
94
+
95
+ def build_coder_maps(connection)
96
+ if supports_ranges?(connection)
97
+ result = connection.exec <<-SQL
98
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype
99
+ FROM pg_type as t
100
+ LEFT JOIN pg_range as r ON oid = rngtypid
101
+ SQL
102
+ else
103
+ result = connection.exec <<-SQL
104
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput
105
+ FROM pg_type as t
106
+ SQL
107
+ end
108
+
109
+ [
110
+ [0, :encoder, PG::TextEncoder::Array],
111
+ [0, :decoder, PG::TextDecoder::Array],
112
+ [1, :encoder, nil],
113
+ [1, :decoder, nil],
114
+ ].inject([]) do |h, (format, direction, arraycoder)|
115
+ h[format] ||= {}
116
+ h[format][direction] = CoderMap.new result, CODERS_BY_NAME[format][direction], format, arraycoder
117
+ h
118
+ end
119
+ end
120
+
121
+ ValidFormats = { 0 => true, 1 => true }
122
+ ValidDirections = { :encoder => true, :decoder => true }
123
+
124
+ def check_format_and_direction(format, direction)
125
+ raise(ArgumentError, "Invalid format value %p" % format) unless ValidFormats[format]
126
+ raise(ArgumentError, "Invalid direction %p" % direction) unless ValidDirections[direction]
127
+ end
128
+
129
+
130
+ # The key of this hash maps to the `typname` column from the table.
131
+ # encoder_map is then dynamically built with oids as the key and Type
132
+ # objects as values.
133
+ CODERS_BY_NAME = []
134
+
135
+ # Register an OID type named +name+ with a typecasting encoder and decoder object in
136
+ # +type+. +name+ should correspond to the `typname` column in
137
+ # the `pg_type` table.
138
+ def self.register_type(format, name, encoder_class, decoder_class)
139
+ CODERS_BY_NAME[format] ||= { encoder: {}, decoder: {} }
140
+ CODERS_BY_NAME[format][:encoder][name] = encoder_class.new(name: name, format: format) if encoder_class
141
+ CODERS_BY_NAME[format][:decoder][name] = decoder_class.new(name: name, format: format) if decoder_class
142
+ end
143
+
144
+ # Alias the +old+ type to the +new+ type.
145
+ def self.alias_type(format, new, old)
146
+ CODERS_BY_NAME[format][:encoder][new] = CODERS_BY_NAME[format][:encoder][old]
147
+ CODERS_BY_NAME[format][:decoder][new] = CODERS_BY_NAME[format][:decoder][old]
148
+ end
149
+
150
+ register_type 0, 'int2', PG::TextEncoder::Integer, PG::TextDecoder::Integer
151
+ alias_type 0, 'int4', 'int2'
152
+ alias_type 0, 'int8', 'int2'
153
+ alias_type 0, 'oid', 'int2'
154
+
155
+ # register_type 0, 'numeric', OID::Decimal.new
156
+ register_type 0, 'text', PG::TextEncoder::String, PG::TextDecoder::String
157
+ alias_type 0, 'varchar', 'text'
158
+ alias_type 0, 'char', 'text'
159
+ alias_type 0, 'bpchar', 'text'
160
+ alias_type 0, 'xml', 'text'
161
+
162
+ # # FIXME: why are we keeping these types as strings?
163
+ # alias_type 'tsvector', 'text'
164
+ # alias_type 'interval', 'text'
165
+ # alias_type 'macaddr', 'text'
166
+ # alias_type 'uuid', 'text'
167
+ #
168
+ # register_type 'money', OID::Money.new
169
+ # There is no PG::TextEncoder::Bytea, because it's simple and more efficient to send bytea-data
170
+ # in binary format, either with PG::BinaryEncoder::Bytea or in Hash param format.
171
+ register_type 0, 'bytea', nil, PG::TextDecoder::Bytea
172
+ register_type 0, 'bool', PG::TextEncoder::Boolean, PG::TextDecoder::Boolean
173
+ # register_type 'bit', OID::Bit.new
174
+ # register_type 'varbit', OID::Bit.new
175
+ #
176
+ register_type 0, 'float4', PG::TextEncoder::Float, PG::TextDecoder::Float
177
+ alias_type 0, 'float8', 'float4'
178
+
179
+ register_type 0, 'timestamp', PG::TextEncoder::TimestampWithoutTimeZone, PG::TextDecoder::TimestampWithoutTimeZone
180
+ register_type 0, 'timestamptz', PG::TextEncoder::TimestampWithTimeZone, PG::TextDecoder::TimestampWithTimeZone
181
+ register_type 0, 'date', PG::TextEncoder::Date, PG::TextDecoder::Date
182
+ # register_type 'time', OID::Time.new
183
+ #
184
+ # register_type 'path', OID::Text.new
185
+ # register_type 'point', OID::Point.new
186
+ # register_type 'polygon', OID::Text.new
187
+ # register_type 'circle', OID::Text.new
188
+ # register_type 'hstore', OID::Hstore.new
189
+ # register_type 'json', OID::Json.new
190
+ # register_type 'citext', OID::Text.new
191
+ # register_type 'ltree', OID::Text.new
192
+ #
193
+ # register_type 'cidr', OID::Cidr.new
194
+ # alias_type 'inet', 'cidr'
195
+
196
+
197
+
198
+ register_type 1, 'int2', PG::BinaryEncoder::Int2, PG::BinaryDecoder::Integer
199
+ register_type 1, 'int4', PG::BinaryEncoder::Int4, PG::BinaryDecoder::Integer
200
+ register_type 1, 'int8', PG::BinaryEncoder::Int8, PG::BinaryDecoder::Integer
201
+ alias_type 1, 'oid', 'int2'
202
+
203
+ register_type 1, 'text', PG::BinaryEncoder::String, PG::BinaryDecoder::String
204
+ alias_type 1, 'varchar', 'text'
205
+ alias_type 1, 'char', 'text'
206
+ alias_type 1, 'bpchar', 'text'
207
+ alias_type 1, 'xml', 'text'
208
+
209
+ register_type 1, 'bytea', PG::BinaryEncoder::Bytea, PG::BinaryDecoder::Bytea
210
+ register_type 1, 'bool', PG::BinaryEncoder::Boolean, PG::BinaryDecoder::Boolean
211
+ register_type 1, 'float4', nil, PG::BinaryDecoder::Float
212
+ register_type 1, 'float8', nil, PG::BinaryDecoder::Float
213
+ end
214
+
215
+ # Simple set of rules for type casting common PostgreSQL types to Ruby.
216
+ #
217
+ # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
218
+ # PostgreSQL's pg_type table in PG::BasicTypeMapForResults.new .
219
+ #
220
+ # Result values are type casted based on the type OID of the given result column.
221
+ #
222
+ # Higher level libraries will most likely not make use of this class, but use their
223
+ # own set of rules to choose suitable encoders and decoders.
224
+ #
225
+ # Example:
226
+ # conn = PG::Connection.new
227
+ # # Assign a default ruleset for type casts of input and output values.
228
+ # conn.type_mapping = PG::BasicTypeMapping.new(conn)
229
+ # # Execute a query.
230
+ # res = conn.exec_params( "SELECT $1::INT", ['5'] )
231
+ # # Retrieve and cast the result value. Value format is 0 (text) and OID is 20. Therefore typecasting
232
+ # # is done by PG::TextDecoder::Integer internally for all value retrieval methods.
233
+ # res.values # => [[5]]
234
+ #
235
+ # PG::TypeMapByOid#fit_to_result(result, false) can be used to generate
236
+ # a result independent PG::TypeMapByColumn type map, which can subsequently be used
237
+ # to cast #get_copy_data fields. See also PG::BasicTypeMapBasedOnResult .
238
+ #
239
+ class PG::BasicTypeMapForResults < PG::TypeMapByOid
240
+ include PG::BasicTypeRegistry
241
+
242
+ def initialize(connection)
243
+ @coder_maps = build_coder_maps(connection)
244
+
245
+ # Populate TypeMapByOid hash with decoders
246
+ @coder_maps.map{|f| f[:decoder].coders }.flatten.each do |coder|
247
+ add_coder(coder)
248
+ end
249
+ end
250
+ end
251
+
252
+ # Simple set of rules for type casting common PostgreSQL types from Ruby
253
+ # to PostgreSQL.
254
+ #
255
+ # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
256
+ # PostgreSQL's pg_type table in PG::BasicTypeMapBasedOnResult.new .
257
+ #
258
+ # This class works equal to PG::BasicTypeMapForResults, but does not define decoders for
259
+ # the given result OIDs, but encoders. So it can be used to type cast field values based on
260
+ # the type OID retrieved by a separate SQL query.
261
+ #
262
+ # PG::TypeMapByOid#fit_to_result(result, false) can be used to generate a result independent
263
+ # PG::TypeMapByColumn type map, which can subsequently be used to cast query bind parameters
264
+ # or #put_copy_data fields.
265
+ #
266
+ # Example:
267
+ # conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
268
+ #
269
+ # # Retrieve table OIDs per empty result set.
270
+ # res = conn.exec( "SELECT * FROM copytable LIMIT 0" )
271
+ # tm = basic_type_mapping.fit_to_result( res, false )
272
+ # row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
273
+ #
274
+ # conn.copy_data( "COPY copytable FROM STDIN", row_encoder ) do |res|
275
+ # conn.put_copy_data ['a', 123, [5,4,3]]
276
+ # end
277
+ class PG::BasicTypeMapBasedOnResult < PG::TypeMapByOid
278
+ include PG::BasicTypeRegistry
279
+
280
+ def initialize(connection)
281
+ @coder_maps = build_coder_maps(connection)
282
+
283
+ # Populate TypeMapByOid hash with encoders
284
+ @coder_maps.map{|f| f[:encoder].coders }.flatten.each do |coder|
285
+ add_coder(coder)
286
+ end
287
+ end
288
+ end
289
+
290
+ # Simple set of rules for type casting common Ruby types to PostgreSQL.
291
+ #
292
+ # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
293
+ # PostgreSQL's pg_type table in PG::BasicTypeMapForQueries.new .
294
+ #
295
+ # Query params are type casted based on the MRI internal type of the given value.
296
+ #
297
+ # Higher level libraries will most likely not make use of this class, but use their
298
+ # own set of rules to choose suitable encoders and decoders.
299
+ #
300
+ # Example:
301
+ # conn = PG::Connection.new
302
+ # # Assign a default ruleset for type casts of input and output values.
303
+ # conn.type_mapping_for_queries = PG::BasicTypeMapForQueries.new(conn)
304
+ # # Execute a query. The Integer param value is typecasted internally by PG::BinaryEncoder::Int8.
305
+ # # The format of the parameter is set to 1 (binary) and the OID of this parameter is set to 20 (int8).
306
+ # res = conn.exec_params( "SELECT $1", [5] )
307
+ class PG::BasicTypeMapForQueries < PG::TypeMapByMriType
308
+ include PG::BasicTypeRegistry
309
+
310
+ def initialize(connection)
311
+ @coder_maps = build_coder_maps(connection)
312
+
313
+ populate_encoder_list
314
+ @array_encoders_by_klass = array_encoders_by_klass
315
+ @anyarray_encoder = coder_by_name(0, :encoder, '_any')
316
+ end
317
+
318
+ private
319
+
320
+ def coder_by_name(format, direction, name)
321
+ check_format_and_direction(format, direction)
322
+ @coder_maps[format][direction].coder_by_name(name)
323
+ end
324
+
325
+ def populate_encoder_list
326
+ DEFAULT_TYPE_MAP.each do |mri_type, selector|
327
+ if Array === selector
328
+ format, name, oid_name = selector
329
+ coder = coder_by_name(format, :encoder, name).dup
330
+ if oid_name
331
+ coder.oid = coder_by_name(format, :encoder, oid_name).oid
332
+ else
333
+ coder.oid = 0
334
+ end
335
+ self[mri_type] = coder
336
+ else
337
+ self[mri_type] = selector
338
+ end
339
+ end
340
+ end
341
+
342
+ def array_encoders_by_klass
343
+ DEFAULT_ARRAY_TYPE_MAP.inject({}) do |h, (klass, (format, name))|
344
+ h[klass] = coder_by_name(format, :encoder, name)
345
+ h
346
+ end
347
+ end
348
+
349
+ def get_array_type(value)
350
+ elem = value
351
+ while elem.kind_of?(Array)
352
+ elem = elem.first
353
+ end
354
+ @array_encoders_by_klass[elem.class] || @anyarray_encoder
355
+ end
356
+
357
+ DEFAULT_TYPE_MAP = {
358
+ 'T_TRUE'.freeze => [1, 'bool', 'bool'],
359
+ 'T_FALSE'.freeze => [1, 'bool', 'bool'],
360
+ # We use text format and no type OID for numbers, because setting the OID can lead
361
+ # to unnecessary type conversions on server side.
362
+ 'T_FIXNUM'.freeze => [0, 'int8'],
363
+ 'T_BIGNUM'.freeze => [0, 'int8'],
364
+ 'T_FLOAT'.freeze => [0, 'float8'],
365
+ 'T_ARRAY'.freeze => :get_array_type,
366
+ }
367
+
368
+ DEFAULT_ARRAY_TYPE_MAP = {
369
+ TrueClass => [0, '_bool'],
370
+ FalseClass => [0, '_bool'],
371
+ Fixnum => [0, '_int8'],
372
+ Bignum => [0, '_int8'],
373
+ String => [0, '_text'],
374
+ Float => [0, '_float8'],
375
+ }
376
+
377
+ end