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
| @@ -0,0 +1,226 @@ | |
| 1 | 
            +
            # -*- rspec -*-
         | 
| 2 | 
            +
            # encoding: utf-8
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require_relative '../helpers'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require 'pg'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
             | 
| 9 | 
            +
            describe PG::TypeMapByColumn do
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            	let!(:textenc_int){ PG::TextEncoder::Integer.new name: 'INT4', oid: 23 }
         | 
| 12 | 
            +
            	let!(:textdec_int){ PG::TextDecoder::Integer.new name: 'INT4', oid: 23 }
         | 
| 13 | 
            +
            	let!(:textenc_float){ PG::TextEncoder::Float.new name: 'FLOAT4', oid: 700 }
         | 
| 14 | 
            +
            	let!(:textdec_float){ PG::TextDecoder::Float.new name: 'FLOAT4', oid: 700 }
         | 
| 15 | 
            +
            	let!(:textenc_string){ PG::TextEncoder::String.new name: 'TEXT', oid: 25 }
         | 
| 16 | 
            +
            	let!(:textdec_string){ PG::TextDecoder::String.new name: 'TEXT', oid: 25 }
         | 
| 17 | 
            +
            	let!(:textdec_bytea){ PG::TextDecoder::Bytea.new name: 'BYTEA', oid: 17 }
         | 
| 18 | 
            +
            	let!(:binaryenc_bytea){ PG::BinaryEncoder::Bytea.new name: 'BYTEA', oid: 17, format: 1 }
         | 
| 19 | 
            +
            	let!(:binarydec_bytea){ PG::BinaryDecoder::Bytea.new name: 'BYTEA', oid: 17, format: 1 }
         | 
| 20 | 
            +
            	let!(:pass_through_type) do
         | 
| 21 | 
            +
            		type = Class.new(PG::SimpleDecoder) do
         | 
| 22 | 
            +
            			def decode(*v)
         | 
| 23 | 
            +
            				v
         | 
| 24 | 
            +
            			end
         | 
| 25 | 
            +
            		end.new
         | 
| 26 | 
            +
            		type.oid = 123456
         | 
| 27 | 
            +
            		type.format = 1
         | 
| 28 | 
            +
            		type.name = 'pass_through'
         | 
| 29 | 
            +
            		type
         | 
| 30 | 
            +
            	end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            	it "should retrieve it's conversions" do
         | 
| 33 | 
            +
            		cm = PG::TypeMapByColumn.new( [textdec_int, textenc_string, textdec_float, pass_through_type, nil] )
         | 
| 34 | 
            +
            		expect( cm.coders ).to eq( [
         | 
| 35 | 
            +
            			textdec_int,
         | 
| 36 | 
            +
            			textenc_string,
         | 
| 37 | 
            +
            			textdec_float,
         | 
| 38 | 
            +
            			pass_through_type,
         | 
| 39 | 
            +
            			nil
         | 
| 40 | 
            +
            		] )
         | 
| 41 | 
            +
            	end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            	it "should respond to inspect" do
         | 
| 44 | 
            +
            		cm = PG::TypeMapByColumn.new( [textdec_int, textenc_string, textdec_float, pass_through_type, PG::TextEncoder::Float.new, nil] )
         | 
| 45 | 
            +
            		expect( cm.inspect ).to eq( "#<PG::TypeMapByColumn INT4:TD TEXT:TE FLOAT4:TD pass_through:BD PG::TextEncoder::Float:TE nil>" )
         | 
| 46 | 
            +
            	end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            	it "should retrieve it's oids" do
         | 
| 49 | 
            +
            		cm = PG::TypeMapByColumn.new( [textdec_int, textdec_string, textdec_float, pass_through_type, nil] )
         | 
| 50 | 
            +
            		expect( cm.oids ).to eq( [23, 25, 700, 123456, nil] )
         | 
| 51 | 
            +
            	end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            	it "should gracefully handle not initialized state" do
         | 
| 54 | 
            +
            		# PG::TypeMapByColumn is not initialized in allocate function, like other
         | 
| 55 | 
            +
            		# type maps, but in #initialize. So it might be not called by derived classes.
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            		not_init = Class.new(PG::TypeMapByColumn) do
         | 
| 58 | 
            +
            			def initialize
         | 
| 59 | 
            +
            				# no super call
         | 
| 60 | 
            +
            			end
         | 
| 61 | 
            +
            		end.new
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            		expect{ @conn.exec_params( "SELECT $1", [ 0 ], 0, not_init ) }.to raise_error(NotImplementedError)
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            		res = @conn.exec( "SELECT 1" )
         | 
| 66 | 
            +
            		expect{ res.type_map = not_init }.to raise_error(NotImplementedError)
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            		@conn.copy_data("COPY (SELECT 1) TO STDOUT") do
         | 
| 69 | 
            +
            			decoder = PG::TextDecoder::CopyRow.new(type_map: not_init)
         | 
| 70 | 
            +
            			expect{ @conn.get_copy_data(false, decoder) }.to raise_error(NotImplementedError)
         | 
| 71 | 
            +
            			@conn.get_copy_data
         | 
| 72 | 
            +
            		end
         | 
| 73 | 
            +
            	end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
             | 
| 76 | 
            +
            	#
         | 
| 77 | 
            +
            	# Encoding Examples
         | 
| 78 | 
            +
            	#
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            	it "should encode integer params" do
         | 
| 81 | 
            +
            		col_map = PG::TypeMapByColumn.new( [textenc_int]*3 )
         | 
| 82 | 
            +
            		res = @conn.exec_params( "SELECT $1, $2, $3", [ 0, nil, "-999" ], 0, col_map )
         | 
| 83 | 
            +
            		expect( res.values ).to eq( [
         | 
| 84 | 
            +
            				[ "0", nil, "-999" ],
         | 
| 85 | 
            +
            		] )
         | 
| 86 | 
            +
            	end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            	it "should encode bytea params" do
         | 
| 89 | 
            +
            		data = "'\u001F\\"
         | 
| 90 | 
            +
            		col_map = PG::TypeMapByColumn.new( [binaryenc_bytea]*2 )
         | 
| 91 | 
            +
            		res = @conn.exec_params( "SELECT $1, $2", [ data, nil ], 0, col_map )
         | 
| 92 | 
            +
            		res.type_map = PG::TypeMapByColumn.new( [textdec_bytea]*2 )
         | 
| 93 | 
            +
            		expect( res.values ).to eq( [
         | 
| 94 | 
            +
            				[ data, nil ],
         | 
| 95 | 
            +
            		] )
         | 
| 96 | 
            +
            	end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
             | 
| 99 | 
            +
            	it "should allow hash form parameters for default encoder" do
         | 
| 100 | 
            +
            		col_map = PG::TypeMapByColumn.new( [nil, nil] )
         | 
| 101 | 
            +
            		hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
         | 
| 102 | 
            +
            		hash_param_nil = { value: nil, type: 17, format: 1 }
         | 
| 103 | 
            +
            		res = @conn.exec_params( "SELECT $1, $2",
         | 
| 104 | 
            +
            					[ hash_param_bin, hash_param_nil ], 0, col_map )
         | 
| 105 | 
            +
            		expect( res.values ).to eq( [["\\x00ff", nil]] )
         | 
| 106 | 
            +
            		expect( result_typenames(res) ).to eq( ['bytea', 'bytea'] )
         | 
| 107 | 
            +
            	end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            	it "should convert hash form parameters to string when using string encoders" do
         | 
| 110 | 
            +
            		col_map = PG::TypeMapByColumn.new( [textenc_string, textenc_string] )
         | 
| 111 | 
            +
            		hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
         | 
| 112 | 
            +
            		hash_param_nil = { value: nil, type: 17, format: 1 }
         | 
| 113 | 
            +
            		res = @conn.exec_params( "SELECT $1::text, $2::text",
         | 
| 114 | 
            +
            					[ hash_param_bin, hash_param_nil ], 0, col_map )
         | 
| 115 | 
            +
            		expect( res.values ).to eq( [["{:value=>\"\\x00\\xFF\", :type=>17, :format=>1}", "{:value=>nil, :type=>17, :format=>1}"]] )
         | 
| 116 | 
            +
            	end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            	it "shouldn't allow param mappings with different number of fields" do
         | 
| 119 | 
            +
            		expect{
         | 
| 120 | 
            +
            			@conn.exec_params( "SELECT $1", [ 123 ], 0, PG::TypeMapByColumn.new([]) )
         | 
| 121 | 
            +
            		}.to raise_error(ArgumentError, /mapped columns/)
         | 
| 122 | 
            +
            	end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
            	it "should verify the default type map for query params as well" do
         | 
| 125 | 
            +
            		tm1 = PG::TypeMapByColumn.new([])
         | 
| 126 | 
            +
            		expect{
         | 
| 127 | 
            +
            			@conn.exec_params( "SELECT $1", [ 123 ], 0, PG::TypeMapByColumn.new([nil]).with_default_type_map(tm1) )
         | 
| 128 | 
            +
            		}.to raise_error(ArgumentError, /mapped columns/)
         | 
| 129 | 
            +
            	end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
            	it "forwards query param conversions to the #default_type_map" do
         | 
| 132 | 
            +
            		tm1 = PG::TypeMapByClass.new
         | 
| 133 | 
            +
            		tm1[Integer] = PG::TextEncoder::Integer.new name: 'INT2', oid: 21
         | 
| 134 | 
            +
             | 
| 135 | 
            +
            		tm2 = PG::TypeMapByColumn.new( [textenc_int, nil, nil] ).with_default_type_map( tm1 )
         | 
| 136 | 
            +
            		res = @conn.exec_params( "SELECT $1, $2, $3::TEXT", [1, 2, :abc], 0, tm2 )
         | 
| 137 | 
            +
             | 
| 138 | 
            +
            		expect( res.ftype(0) ).to eq( 23 ) # tm2
         | 
| 139 | 
            +
            		expect( res.ftype(1) ).to eq( 21 ) # tm1
         | 
| 140 | 
            +
            		expect( res.getvalue(0,2) ).to eq( "abc" ) # TypeMapAllStrings
         | 
| 141 | 
            +
            	end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
            	#
         | 
| 144 | 
            +
            	# Decoding Examples
         | 
| 145 | 
            +
            	#
         | 
| 146 | 
            +
             | 
| 147 | 
            +
            	class Exception_in_decode < PG::SimpleDecoder
         | 
| 148 | 
            +
            		def decode(res, tuple, field)
         | 
| 149 | 
            +
            			raise "no type decoder defined for tuple #{tuple} field #{field}"
         | 
| 150 | 
            +
            		end
         | 
| 151 | 
            +
            	end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
            	it "should raise an error from decode method of type converter" do
         | 
| 154 | 
            +
            		res = @conn.exec( "SELECT now()" )
         | 
| 155 | 
            +
            		types = Array.new( res.nfields, Exception_in_decode.new )
         | 
| 156 | 
            +
            		res.type_map = PG::TypeMapByColumn.new( types )
         | 
| 157 | 
            +
            		expect{ res.values }.to raise_error(/no type decoder defined/)
         | 
| 158 | 
            +
            	end
         | 
| 159 | 
            +
             | 
| 160 | 
            +
            	it "should raise an error for invalid params" do
         | 
| 161 | 
            +
            		expect{ PG::TypeMapByColumn.new( :WrongType ) }.to raise_error(TypeError, /wrong argument type/)
         | 
| 162 | 
            +
            		expect{ PG::TypeMapByColumn.new( [123] ) }.to raise_error(ArgumentError, /invalid/)
         | 
| 163 | 
            +
            	end
         | 
| 164 | 
            +
             | 
| 165 | 
            +
            	it "shouldn't allow result mappings with different number of fields" do
         | 
| 166 | 
            +
            		res = @conn.exec( "SELECT 1" )
         | 
| 167 | 
            +
            		expect{ res.type_map = PG::TypeMapByColumn.new([]) }.to raise_error(ArgumentError, /mapped columns/)
         | 
| 168 | 
            +
            	end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
            	it "should verify the default type map for result values as well" do
         | 
| 171 | 
            +
            		res = @conn.exec( "SELECT 1" )
         | 
| 172 | 
            +
            		tm1 = PG::TypeMapByColumn.new([])
         | 
| 173 | 
            +
            		expect{
         | 
| 174 | 
            +
            			res.type_map = PG::TypeMapByColumn.new([nil]).with_default_type_map(tm1)
         | 
| 175 | 
            +
            		}.to raise_error(ArgumentError, /mapped columns/)
         | 
| 176 | 
            +
            	end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
            	it "forwards result value conversions to a TypeMapByOid as #default_type_map" do
         | 
| 179 | 
            +
            		# One run with implicit built TypeMapByColumn and another with online lookup
         | 
| 180 | 
            +
            		[0, 10].each do |max_rows|
         | 
| 181 | 
            +
            			tm1 = PG::TypeMapByOid.new
         | 
| 182 | 
            +
            			tm1.add_coder PG::TextDecoder::Integer.new name: 'INT2', oid: 21
         | 
| 183 | 
            +
            			tm1.max_rows_for_online_lookup = max_rows
         | 
| 184 | 
            +
             | 
| 185 | 
            +
            			tm2 = PG::TypeMapByColumn.new( [textdec_int, nil, nil] ).with_default_type_map( tm1 )
         | 
| 186 | 
            +
            			res = @conn.exec( "SELECT '1'::INT4, '2'::INT2, '3'::INT8" ).map_types!( tm2 )
         | 
| 187 | 
            +
             | 
| 188 | 
            +
            			expect( res.getvalue(0,0) ).to eq( 1 ) # tm2
         | 
| 189 | 
            +
            			expect( res.getvalue(0,1) ).to eq( 2 ) # tm1
         | 
| 190 | 
            +
            			expect( res.getvalue(0,2) ).to eq( "3" ) # TypeMapAllStrings
         | 
| 191 | 
            +
            		end
         | 
| 192 | 
            +
            	end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
            	it "forwards get_copy_data conversions to another TypeMapByColumn as #default_type_map" do
         | 
| 195 | 
            +
            		tm1 = PG::TypeMapByColumn.new( [textdec_int, nil, nil] )
         | 
| 196 | 
            +
            		tm2 = PG::TypeMapByColumn.new( [nil, textdec_int, nil] ).with_default_type_map( tm1 )
         | 
| 197 | 
            +
            		decoder = PG::TextDecoder::CopyRow.new(type_map: tm2)
         | 
| 198 | 
            +
            		@conn.copy_data("COPY (SELECT 1, 2, 3) TO STDOUT", decoder) do
         | 
| 199 | 
            +
            			expect( @conn.get_copy_data ).to eq( [1, 2, '3'] )
         | 
| 200 | 
            +
            			@conn.get_copy_data
         | 
| 201 | 
            +
            		end
         | 
| 202 | 
            +
            	end
         | 
| 203 | 
            +
             | 
| 204 | 
            +
            	it "will deny copy queries with different column count" do
         | 
| 205 | 
            +
            		[[2, 2], [2, 3], [3, 2]].each do |cols1, cols2|
         | 
| 206 | 
            +
            			tm1 = PG::TypeMapByColumn.new( [textdec_int, nil, nil][0, cols1] )
         | 
| 207 | 
            +
            			tm2 = PG::TypeMapByColumn.new( [nil, textdec_int, nil][0, cols2] ).with_default_type_map( tm1 )
         | 
| 208 | 
            +
            			decoder = PG::TextDecoder::CopyRow.new(type_map: tm2)
         | 
| 209 | 
            +
            			@conn.copy_data("COPY (SELECT 1, 2, 3) TO STDOUT", decoder) do
         | 
| 210 | 
            +
            				expect{ @conn.get_copy_data }.to raise_error(ArgumentError, /number of copy fields/)
         | 
| 211 | 
            +
            				@conn.get_copy_data
         | 
| 212 | 
            +
            			end
         | 
| 213 | 
            +
            		end
         | 
| 214 | 
            +
            	end
         | 
| 215 | 
            +
             | 
| 216 | 
            +
            	#
         | 
| 217 | 
            +
            	# Decoding Examples text format
         | 
| 218 | 
            +
            	#
         | 
| 219 | 
            +
             | 
| 220 | 
            +
            	it "should allow mixed type conversions" do
         | 
| 221 | 
            +
            		res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE, 3" )
         | 
| 222 | 
            +
            		res.type_map = PG::TypeMapByColumn.new( [textdec_int, textdec_string, textdec_float, pass_through_type, nil] )
         | 
| 223 | 
            +
            		expect( res.values ).to eq( [[1, 'a', 2.0, ['2013-06-30', 0, 3], '3' ]] )
         | 
| 224 | 
            +
            	end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
            end
         | 
| @@ -0,0 +1,136 @@ | |
| 1 | 
            +
            # -*- rspec -*-
         | 
| 2 | 
            +
            # encoding: utf-8
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require_relative '../helpers'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require 'pg'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
             | 
| 9 | 
            +
            describe PG::TypeMapByMriType do
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            	let!(:textenc_int){ PG::TextEncoder::Integer.new name: 'INT4', oid: 23 }
         | 
| 12 | 
            +
            	let!(:textenc_float){ PG::TextEncoder::Float.new name: 'FLOAT8', oid: 701 }
         | 
| 13 | 
            +
            	let!(:textenc_string){ PG::TextEncoder::String.new name: 'TEXT', oid: 25 }
         | 
| 14 | 
            +
            	let!(:binaryenc_int){ PG::BinaryEncoder::Int8.new name: 'INT8', oid: 20, format: 1 }
         | 
| 15 | 
            +
            	let!(:pass_through_type) do
         | 
| 16 | 
            +
            		type = Class.new(PG::SimpleEncoder) do
         | 
| 17 | 
            +
            			def encode(*v)
         | 
| 18 | 
            +
            				v.inspect
         | 
| 19 | 
            +
            			end
         | 
| 20 | 
            +
            		end.new
         | 
| 21 | 
            +
            		type.oid = 25
         | 
| 22 | 
            +
            		type.format = 0
         | 
| 23 | 
            +
            		type.name = 'pass_through'
         | 
| 24 | 
            +
            		type
         | 
| 25 | 
            +
            	end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            	let!(:tm) do
         | 
| 28 | 
            +
            		tm = PG::TypeMapByMriType.new
         | 
| 29 | 
            +
            		tm['T_FIXNUM'] = binaryenc_int
         | 
| 30 | 
            +
            		tm['T_FLOAT'] = textenc_float
         | 
| 31 | 
            +
            		tm['T_SYMBOL'] = pass_through_type
         | 
| 32 | 
            +
            		tm
         | 
| 33 | 
            +
            	end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            	let!(:derived_tm) do
         | 
| 36 | 
            +
            		tm = Class.new(PG::TypeMapByMriType) do
         | 
| 37 | 
            +
            			def array_type_map_for(value)
         | 
| 38 | 
            +
            				PG::TextEncoder::Array.new name: '_INT4', oid: 1007, elements_type: PG::TextEncoder::Integer.new
         | 
| 39 | 
            +
            			end
         | 
| 40 | 
            +
            		end.new
         | 
| 41 | 
            +
            		tm['T_FIXNUM'] = proc{|value| textenc_int }
         | 
| 42 | 
            +
            		tm['T_REGEXP'] = proc{|value| :invalid }
         | 
| 43 | 
            +
            		tm['T_ARRAY'] = :array_type_map_for
         | 
| 44 | 
            +
            		tm
         | 
| 45 | 
            +
            	end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            	it "should retrieve all conversions" do
         | 
| 48 | 
            +
            		expect( tm.coders ).to eq( {
         | 
| 49 | 
            +
            			"T_FIXNUM" => binaryenc_int,
         | 
| 50 | 
            +
            			"T_FLOAT" => textenc_float,
         | 
| 51 | 
            +
            			"T_SYMBOL" => pass_through_type,
         | 
| 52 | 
            +
            			"T_HASH" => nil,
         | 
| 53 | 
            +
            			"T_ARRAY" => nil,
         | 
| 54 | 
            +
            			"T_BIGNUM" => nil,
         | 
| 55 | 
            +
            			"T_CLASS" => nil,
         | 
| 56 | 
            +
            			"T_COMPLEX" => nil,
         | 
| 57 | 
            +
            			"T_DATA" => nil,
         | 
| 58 | 
            +
            			"T_FALSE" => nil,
         | 
| 59 | 
            +
            			"T_FILE" => nil,
         | 
| 60 | 
            +
            			"T_MODULE" => nil,
         | 
| 61 | 
            +
            			"T_OBJECT" => nil,
         | 
| 62 | 
            +
            			"T_RATIONAL" => nil,
         | 
| 63 | 
            +
            			"T_REGEXP" => nil,
         | 
| 64 | 
            +
            			"T_STRING" => nil,
         | 
| 65 | 
            +
            			"T_STRUCT" => nil,
         | 
| 66 | 
            +
            			"T_TRUE" => nil,
         | 
| 67 | 
            +
            		} )
         | 
| 68 | 
            +
            	end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            	it "should retrieve particular conversions" do
         | 
| 71 | 
            +
            		expect( tm['T_FIXNUM'] ).to eq(binaryenc_int)
         | 
| 72 | 
            +
            		expect( tm['T_FLOAT'] ).to eq(textenc_float)
         | 
| 73 | 
            +
            		expect( tm['T_BIGNUM'] ).to be_nil
         | 
| 74 | 
            +
            		expect( derived_tm['T_REGEXP'] ).to be_kind_of(Proc)
         | 
| 75 | 
            +
            		expect( derived_tm['T_ARRAY'] ).to eq(:array_type_map_for)
         | 
| 76 | 
            +
            	end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            	it "should allow deletion of coders" do
         | 
| 79 | 
            +
            		tm['T_FIXNUM'] = nil
         | 
| 80 | 
            +
            		expect( tm['T_FIXNUM'] ).to be_nil
         | 
| 81 | 
            +
            	end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            	it "should check MRI type key" do
         | 
| 84 | 
            +
            		expect{ tm['NO_TYPE'] }.to raise_error(ArgumentError)
         | 
| 85 | 
            +
            		expect{ tm[123] }.to raise_error(TypeError)
         | 
| 86 | 
            +
            		expect{ tm['NO_TYPE'] = textenc_float }.to raise_error(ArgumentError)
         | 
| 87 | 
            +
            		expect{ tm[123] = textenc_float }.to raise_error(TypeError)
         | 
| 88 | 
            +
            	end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            	it "forwards query param conversions to the #default_type_map" do
         | 
| 91 | 
            +
            		tm1 = PG::TypeMapByColumn.new( [textenc_int, nil, nil] )
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            		tm2 = PG::TypeMapByMriType.new
         | 
| 94 | 
            +
            		tm2['T_FIXNUM'] = PG::TextEncoder::Integer.new name: 'INT2', oid: 21
         | 
| 95 | 
            +
            		tm2.default_type_map = tm1
         | 
| 96 | 
            +
             | 
| 97 | 
            +
            		res = @conn.exec_params( "SELECT $1, $2, $3::TEXT", ['1', 2, 3], 0, tm2 )
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            		expect( res.ftype(0) ).to eq( 23 ) # tm1
         | 
| 100 | 
            +
            		expect( res.ftype(1) ).to eq( 21 ) # tm2
         | 
| 101 | 
            +
            		expect( res.getvalue(0,2) ).to eq( "3" ) # TypeMapAllStrings
         | 
| 102 | 
            +
            	end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            	#
         | 
| 105 | 
            +
            	# Decoding Examples
         | 
| 106 | 
            +
            	#
         | 
| 107 | 
            +
             | 
| 108 | 
            +
            	it "should raise an error when used for results" do
         | 
| 109 | 
            +
            		res = @conn.exec_params( "SELECT 1", [], 1 )
         | 
| 110 | 
            +
            		expect{ res.type_map = tm }.to raise_error(NotImplementedError, /not suitable to map result values/)
         | 
| 111 | 
            +
            	end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
            	#
         | 
| 114 | 
            +
            	# Encoding Examples
         | 
| 115 | 
            +
            	#
         | 
| 116 | 
            +
             | 
| 117 | 
            +
            	it "should allow mixed type conversions" do
         | 
| 118 | 
            +
            		res = @conn.exec_params( "SELECT $1, $2, $3", [5, 1.23, :TestSymbol], 0, tm )
         | 
| 119 | 
            +
            		expect( res.values ).to eq([['5', '1.23', "[:TestSymbol, #{@conn.internal_encoding.inspect}]"]])
         | 
| 120 | 
            +
            		expect( res.ftype(0) ).to eq(20)
         | 
| 121 | 
            +
            	end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            	it "should allow mixed type conversions with derived type map" do
         | 
| 124 | 
            +
            		res = @conn.exec_params( "SELECT $1, $2", [6, [7]], 0, derived_tm )
         | 
| 125 | 
            +
            		expect( res.values ).to eq([['6', '{7}']])
         | 
| 126 | 
            +
            		expect( res.ftype(0) ).to eq(23)
         | 
| 127 | 
            +
            		expect( res.ftype(1) ).to eq(1007)
         | 
| 128 | 
            +
            	end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
            	it "should raise TypeError with derived type map" do
         | 
| 131 | 
            +
            		expect{
         | 
| 132 | 
            +
            			@conn.exec_params( "SELECT $1", [//], 0, derived_tm )
         | 
| 133 | 
            +
            		}.to raise_error(TypeError, /argument 1/)
         | 
| 134 | 
            +
            	end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
            end
         | 
| @@ -0,0 +1,149 @@ | |
| 1 | 
            +
            # -*- rspec -*-
         | 
| 2 | 
            +
            # encoding: utf-8
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require_relative '../helpers'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require 'pg'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
             | 
| 9 | 
            +
            describe PG::TypeMapByOid do
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            	let!(:textdec_int){ PG::TextDecoder::Integer.new name: 'INT4', oid: 23 }
         | 
| 12 | 
            +
            	let!(:textdec_float){ PG::TextDecoder::Float.new name: 'FLOAT8', oid: 701 }
         | 
| 13 | 
            +
            	let!(:textdec_string){ PG::TextDecoder::String.new name: 'TEXT', oid: 25 }
         | 
| 14 | 
            +
            	let!(:textdec_bytea){ PG::TextDecoder::Bytea.new name: 'BYTEA', oid: 17 }
         | 
| 15 | 
            +
            	let!(:binarydec_float){ PG::BinaryDecoder::Float.new name: 'FLOAT8', oid: 701, format: 1 }
         | 
| 16 | 
            +
            	let!(:pass_through_type) do
         | 
| 17 | 
            +
            		type = Class.new(PG::SimpleDecoder) do
         | 
| 18 | 
            +
            			def decode(*v)
         | 
| 19 | 
            +
            				v
         | 
| 20 | 
            +
            			end
         | 
| 21 | 
            +
            		end.new
         | 
| 22 | 
            +
            		type.oid = 1082
         | 
| 23 | 
            +
            		type.format = 0
         | 
| 24 | 
            +
            		type.name = 'pass_through'
         | 
| 25 | 
            +
            		type
         | 
| 26 | 
            +
            	end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            	let!(:tm) do
         | 
| 29 | 
            +
            		tm = PG::TypeMapByOid.new
         | 
| 30 | 
            +
            		tm.add_coder textdec_int
         | 
| 31 | 
            +
            		tm.add_coder textdec_float
         | 
| 32 | 
            +
            		tm.add_coder binarydec_float
         | 
| 33 | 
            +
            		tm.add_coder pass_through_type
         | 
| 34 | 
            +
            		tm
         | 
| 35 | 
            +
            	end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            	it "should retrieve it's conversions" do
         | 
| 38 | 
            +
            		expect( tm.coders ).to eq( [
         | 
| 39 | 
            +
            			textdec_int,
         | 
| 40 | 
            +
            			textdec_float,
         | 
| 41 | 
            +
            			pass_through_type,
         | 
| 42 | 
            +
            			binarydec_float,
         | 
| 43 | 
            +
            		] )
         | 
| 44 | 
            +
            	end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            	it "should allow deletion of coders" do
         | 
| 47 | 
            +
            		expect( tm.rm_coder 0, 701 ).to eq(textdec_float)
         | 
| 48 | 
            +
            		expect( tm.rm_coder 0, 701 ).to eq(nil)
         | 
| 49 | 
            +
            		expect( tm.rm_coder 1, 701 ).to eq(binarydec_float)
         | 
| 50 | 
            +
            		expect( tm.coders ).to eq( [
         | 
| 51 | 
            +
            			textdec_int,
         | 
| 52 | 
            +
            			pass_through_type,
         | 
| 53 | 
            +
            		] )
         | 
| 54 | 
            +
            	end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            	it "should check format when deleting coders" do
         | 
| 57 | 
            +
            		expect{ tm.rm_coder(2, 123) }.to raise_error(ArgumentError)
         | 
| 58 | 
            +
            		expect{ tm.rm_coder(-1, 123) }.to raise_error(ArgumentError)
         | 
| 59 | 
            +
            	end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            	it "should check format when adding coders" do
         | 
| 62 | 
            +
            		textdec_int.format = 2
         | 
| 63 | 
            +
            		expect{ tm.add_coder textdec_int }.to raise_error(ArgumentError)
         | 
| 64 | 
            +
            		textdec_int.format = -1
         | 
| 65 | 
            +
            		expect{ tm.add_coder textdec_int }.to raise_error(ArgumentError)
         | 
| 66 | 
            +
            	end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            	it "should check coder type when adding coders" do
         | 
| 69 | 
            +
            		expect{ tm.add_coder :dummy }.to raise_error(ArgumentError)
         | 
| 70 | 
            +
            	end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            	it "should allow reading and writing max_rows_for_online_lookup" do
         | 
| 73 | 
            +
            		expect( tm.max_rows_for_online_lookup ).to eq(10)
         | 
| 74 | 
            +
            		tm.max_rows_for_online_lookup = 5
         | 
| 75 | 
            +
            		expect( tm.max_rows_for_online_lookup ).to eq(5)
         | 
| 76 | 
            +
            	end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            	it "should allow building new TypeMapByColumn for a given result" do
         | 
| 79 | 
            +
            		res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE" )
         | 
| 80 | 
            +
            		tm2 = tm.build_column_map(res)
         | 
| 81 | 
            +
            		expect( tm2 ).to be_a_kind_of(PG::TypeMapByColumn)
         | 
| 82 | 
            +
            		expect( tm2.coders ).to eq( [textdec_int, nil, textdec_float, pass_through_type] )
         | 
| 83 | 
            +
            	end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            	it "forwards result value conversions to another TypeMapByOid as #default_type_map" do
         | 
| 86 | 
            +
            		# One run with implicit built TypeMapByColumn and another with online lookup
         | 
| 87 | 
            +
            		# for each type map.
         | 
| 88 | 
            +
            		[[0, 0], [0, 10], [10, 0], [10, 10]].each do |max_rows1, max_rows2|
         | 
| 89 | 
            +
            			tm1 = PG::TypeMapByOid.new
         | 
| 90 | 
            +
            			tm1.add_coder PG::TextDecoder::Integer.new name: 'INT2', oid: 21
         | 
| 91 | 
            +
            			tm1.max_rows_for_online_lookup = max_rows1
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            			tm2 = PG::TypeMapByOid.new
         | 
| 94 | 
            +
            			tm2.add_coder PG::TextDecoder::Integer.new name: 'INT4', oid: 23
         | 
| 95 | 
            +
            			tm2.max_rows_for_online_lookup = max_rows2
         | 
| 96 | 
            +
            			tm2.default_type_map = tm1
         | 
| 97 | 
            +
             | 
| 98 | 
            +
            			res = @conn.exec( "SELECT '1'::INT4, '2'::INT2, '3'::INT8" ).map_types!( tm2 )
         | 
| 99 | 
            +
             | 
| 100 | 
            +
            			expect( res.getvalue(0,0) ).to eq( 1 ) # tm2
         | 
| 101 | 
            +
            			expect( res.getvalue(0,1) ).to eq( 2 ) # tm1
         | 
| 102 | 
            +
            			expect( res.getvalue(0,2) ).to eq( "3" ) # TypeMapAllStrings
         | 
| 103 | 
            +
            		end
         | 
| 104 | 
            +
            	end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            	#
         | 
| 107 | 
            +
            	# Decoding Examples text format
         | 
| 108 | 
            +
            	#
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            	it "should allow mixed type conversions in text format" do
         | 
| 111 | 
            +
            		res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE" )
         | 
| 112 | 
            +
            		res.type_map = tm
         | 
| 113 | 
            +
            		expect( res.values ).to eq( [[1, 'a', 2.0, ['2013-06-30', 0, 3] ]] )
         | 
| 114 | 
            +
            	end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
            	it "should build a TypeMapByColumn when assigned and the number of rows is high enough" do
         | 
| 117 | 
            +
            		res = @conn.exec( "SELECT generate_series(1,20), 'a', 2.0::FLOAT, '2013-06-30'::DATE" )
         | 
| 118 | 
            +
            		res.type_map = tm
         | 
| 119 | 
            +
            		expect( res.type_map ).to be_kind_of( PG::TypeMapByColumn )
         | 
| 120 | 
            +
            		expect( res.type_map.coders ).to eq( [textdec_int, nil, textdec_float, pass_through_type] )
         | 
| 121 | 
            +
            	end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            	it "should use TypeMapByOid for online lookup and the number of rows is low enough" do
         | 
| 124 | 
            +
            		res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE" )
         | 
| 125 | 
            +
            		res.type_map = tm
         | 
| 126 | 
            +
            		expect( res.type_map ).to be_kind_of( PG::TypeMapByOid )
         | 
| 127 | 
            +
            	end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
            	#
         | 
| 130 | 
            +
            	# Decoding Examples binary format
         | 
| 131 | 
            +
            	#
         | 
| 132 | 
            +
             | 
| 133 | 
            +
            	it "should allow mixed type conversions in binary format" do
         | 
| 134 | 
            +
            		res = @conn.exec_params( "SELECT 1, 2.0::FLOAT", [], 1 )
         | 
| 135 | 
            +
            		res.type_map = tm
         | 
| 136 | 
            +
            		expect( res.values ).to eq( [["\x00\x00\x00\x01", 2.0 ]] )
         | 
| 137 | 
            +
            	end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
            	#
         | 
| 140 | 
            +
            	# Encoding Examples
         | 
| 141 | 
            +
            	#
         | 
| 142 | 
            +
             | 
| 143 | 
            +
            	it "should raise an error used for query params" do
         | 
| 144 | 
            +
            		expect{
         | 
| 145 | 
            +
            			@conn.exec_params( "SELECT $1", [5], 0, tm )
         | 
| 146 | 
            +
            		}.to raise_error(NotImplementedError, /not suitable to map query params/)
         | 
| 147 | 
            +
            	end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
            end
         |