pg 0.15.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.tar.gz.sig +0 -0
- data/BSDL +2 -2
- data/ChangeLog +0 -3022
- data/History.rdoc +370 -4
- data/Manifest.txt +39 -19
- data/README-Windows.rdoc +17 -28
- data/README.ja.rdoc +1 -2
- data/README.rdoc +113 -14
- data/Rakefile +97 -36
- data/Rakefile.cross +109 -83
- data/ext/errorcodes.def +1032 -0
- data/ext/errorcodes.rb +45 -0
- data/ext/errorcodes.txt +494 -0
- data/ext/extconf.rb +55 -52
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +94 -38
- data/ext/pg.c +273 -121
- data/ext/pg.h +292 -50
- 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 +1811 -1051
- data/ext/pg_copy_coder.c +599 -0
- data/ext/pg_errors.c +95 -0
- data/ext/pg_record_coder.c +491 -0
- data/ext/pg_result.c +917 -203
- 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.rb +31 -9
- 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 +235 -30
- 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/spec/{lib/helpers.rb → helpers.rb} +154 -52
- data/spec/pg/basic_type_mapping_spec.rb +630 -0
- data/spec/pg/connection_spec.rb +1352 -426
- data/spec/pg/connection_sync_spec.rb +41 -0
- data/spec/pg/result_spec.rb +508 -105
- 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 +35 -16
- metadata +163 -84
- metadata.gz.sig +0 -0
- data/sample/array_insert.rb +0 -20
- data/sample/async_api.rb +0 -106
- data/sample/async_copyto.rb +0 -39
- data/sample/async_mixed.rb +0 -56
- data/sample/check_conn.rb +0 -21
- data/sample/copyfrom.rb +0 -81
- data/sample/copyto.rb +0 -19
- data/sample/cursor.rb +0 -21
- data/sample/disk_usage_report.rb +0 -186
- data/sample/issue-119.rb +0 -94
- data/sample/losample.rb +0 -69
- data/sample/minimal-testcase.rb +0 -17
- data/sample/notify_wait.rb +0 -72
- data/sample/pg_statistics.rb +0 -294
- data/sample/replication_monitor.rb +0 -231
- data/sample/test_binary_values.rb +0 -33
- data/sample/wal_shipper.rb +0 -434
- data/sample/warehouse_partitions.rb +0 -320
    
        data/spec/pg/connection_spec.rb
    CHANGED
    
    | @@ -1,49 +1,14 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # -*- rspec -*-
         | 
| 2 2 | 
             
            #encoding: utf-8
         | 
| 3 3 |  | 
| 4 | 
            -
             | 
| 5 | 
            -
            	require 'pathname'
         | 
| 4 | 
            +
            require_relative '../helpers'
         | 
| 6 5 |  | 
| 7 | 
            -
            	basedir = Pathname( __FILE__ ).dirname.parent.parent
         | 
| 8 | 
            -
            	libdir = basedir + 'lib'
         | 
| 9 | 
            -
             | 
| 10 | 
            -
            	$LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
         | 
| 11 | 
            -
            	$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
         | 
| 12 | 
            -
            }
         | 
| 13 | 
            -
             | 
| 14 | 
            -
            require 'rspec'
         | 
| 15 | 
            -
            require 'spec/lib/helpers'
         | 
| 16 6 | 
             
            require 'timeout'
         | 
| 17 7 | 
             
            require 'socket'
         | 
| 18 8 | 
             
            require 'pg'
         | 
| 19 9 |  | 
| 20 10 | 
             
            describe PG::Connection do
         | 
| 21 11 |  | 
| 22 | 
            -
            	before( :all ) do
         | 
| 23 | 
            -
            		@conn = setup_testing_db( described_class.name )
         | 
| 24 | 
            -
            	end
         | 
| 25 | 
            -
             | 
| 26 | 
            -
            	before( :each ) do
         | 
| 27 | 
            -
            		@conn.exec( 'BEGIN' ) unless example.metadata[:without_transaction]
         | 
| 28 | 
            -
            		if PG.respond_to?( :library_version )
         | 
| 29 | 
            -
            			@conn.exec_params %Q{SET application_name TO '%s'} %
         | 
| 30 | 
            -
            				[@conn.escape_string(example.description[0,60])]
         | 
| 31 | 
            -
            		end
         | 
| 32 | 
            -
            	end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
            	after( :each ) do
         | 
| 35 | 
            -
            		@conn.exec( 'ROLLBACK' ) unless example.metadata[:without_transaction]
         | 
| 36 | 
            -
            	end
         | 
| 37 | 
            -
             | 
| 38 | 
            -
            	after( :all ) do
         | 
| 39 | 
            -
            		teardown_testing_db( @conn )
         | 
| 40 | 
            -
            	end
         | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
            	#
         | 
| 44 | 
            -
            	# Examples
         | 
| 45 | 
            -
            	#
         | 
| 46 | 
            -
             | 
| 47 12 | 
             
            	it "can create a connection option string from a Hash of options" do
         | 
| 48 13 | 
             
            		optstring = described_class.parse_connect_args(
         | 
| 49 14 | 
             
            			:host => 'pgsql.example.com',
         | 
| @@ -51,54 +16,130 @@ describe PG::Connection do | |
| 51 16 | 
             
            			'sslmode' => 'require'
         | 
| 52 17 | 
             
            		  )
         | 
| 53 18 |  | 
| 54 | 
            -
            		optstring. | 
| 55 | 
            -
            		optstring. | 
| 56 | 
            -
            		optstring. | 
| 57 | 
            -
            		optstring. | 
| 19 | 
            +
            		expect( optstring ).to be_a( String )
         | 
| 20 | 
            +
            		expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
         | 
| 21 | 
            +
            		expect( optstring ).to match( /(^|\s)dbname='db01'/ )
         | 
| 22 | 
            +
            		expect( optstring ).to match( /(^|\s)sslmode='require'/ )
         | 
| 58 23 | 
             
            	end
         | 
| 59 24 |  | 
| 60 25 | 
             
            	it "can create a connection option string from positional parameters" do
         | 
| 61 26 | 
             
            		optstring = described_class.parse_connect_args( 'pgsql.example.com', nil, '-c geqo=off', nil,
         | 
| 62 27 | 
             
            		                                       'sales' )
         | 
| 63 28 |  | 
| 64 | 
            -
            		optstring. | 
| 65 | 
            -
            		optstring. | 
| 66 | 
            -
            		optstring. | 
| 67 | 
            -
            		optstring. | 
| 29 | 
            +
            		expect( optstring ).to be_a( String )
         | 
| 30 | 
            +
            		expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
         | 
| 31 | 
            +
            		expect( optstring ).to match( /(^|\s)dbname='sales'/ )
         | 
| 32 | 
            +
            		expect( optstring ).to match( /(^|\s)options='-c geqo=off'/ )
         | 
| 68 33 |  | 
| 69 | 
            -
            		optstring. | 
| 70 | 
            -
            		optstring. | 
| 34 | 
            +
            		expect( optstring ).to_not match( /port=/ )
         | 
| 35 | 
            +
            		expect( optstring ).to_not match( /tty=/ )
         | 
| 71 36 | 
             
            	end
         | 
| 72 37 |  | 
| 73 38 | 
             
            	it "can create a connection option string from a mix of positional and hash parameters" do
         | 
| 74 39 | 
             
            		optstring = described_class.parse_connect_args( 'pgsql.example.com',
         | 
| 75 40 | 
             
            		                                       :dbname => 'licensing', :user => 'jrandom' )
         | 
| 76 41 |  | 
| 77 | 
            -
            		optstring. | 
| 78 | 
            -
            		optstring. | 
| 79 | 
            -
            		optstring. | 
| 80 | 
            -
            		optstring. | 
| 42 | 
            +
            		expect( optstring ).to be_a( String )
         | 
| 43 | 
            +
            		expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
         | 
| 44 | 
            +
            		expect( optstring ).to match( /(^|\s)dbname='licensing'/ )
         | 
| 45 | 
            +
            		expect( optstring ).to match( /(^|\s)user='jrandom'/ )
         | 
| 46 | 
            +
            	end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            	it "can create a connection option string from an option string and a hash" do
         | 
| 49 | 
            +
            		optstring = described_class.parse_connect_args( 'dbname=original', :user => 'jrandom' )
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            		expect( optstring ).to be_a( String )
         | 
| 52 | 
            +
            		expect( optstring ).to match( /(^|\s)dbname=original/ )
         | 
| 53 | 
            +
            		expect( optstring ).to match( /(^|\s)user='jrandom'/ )
         | 
| 81 54 | 
             
            	end
         | 
| 82 55 |  | 
| 83 56 | 
             
            	it "escapes single quotes and backslashes in connection parameters" do
         | 
| 84 | 
            -
            		 | 
| 85 | 
            -
            			 | 
| 57 | 
            +
            		expect(
         | 
| 58 | 
            +
            			described_class.parse_connect_args( "DB 'browser' \\" )
         | 
| 59 | 
            +
            		).to match( /host='DB \\'browser\\' \\\\'/ )
         | 
| 86 60 |  | 
| 87 61 | 
             
            	end
         | 
| 88 62 |  | 
| 63 | 
            +
            	let(:uri) { 'postgresql://user:pass@pgsql.example.com:222/db01?sslmode=require' }
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            	it "can connect using a URI" do
         | 
| 66 | 
            +
            		string = described_class.parse_connect_args( uri )
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            		expect( string ).to be_a( String )
         | 
| 69 | 
            +
            		expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
         | 
| 70 | 
            +
            		expect( string ).to match( %r{\?.*sslmode=require} )
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            		string = described_class.parse_connect_args( URI.parse(uri) )
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            		expect( string ).to be_a( String )
         | 
| 75 | 
            +
            		expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
         | 
| 76 | 
            +
            		expect( string ).to match( %r{\?.*sslmode=require} )
         | 
| 77 | 
            +
            	end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            	it "can create a connection URI from a URI and a hash" do
         | 
| 80 | 
            +
            		string = described_class.parse_connect_args( uri, :connect_timeout => 2 )
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            		expect( string ).to be_a( String )
         | 
| 83 | 
            +
            		expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
         | 
| 84 | 
            +
            		expect( string ).to match( %r{\?.*sslmode=require} )
         | 
| 85 | 
            +
            		expect( string ).to match( %r{\?.*connect_timeout=2} )
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            		string = described_class.parse_connect_args( uri,
         | 
| 88 | 
            +
            			:user => 'a',
         | 
| 89 | 
            +
            			:password => 'b',
         | 
| 90 | 
            +
            			:host => 'localhost',
         | 
| 91 | 
            +
            			:port => 555,
         | 
| 92 | 
            +
            			:dbname => 'x' )
         | 
| 93 | 
            +
             | 
| 94 | 
            +
            		expect( string ).to be_a( String )
         | 
| 95 | 
            +
            		expect( string ).to match( %r{^postgresql://\?} )
         | 
| 96 | 
            +
            		expect( string ).to match( %r{\?.*user=a} )
         | 
| 97 | 
            +
            		expect( string ).to match( %r{\?.*password=b} )
         | 
| 98 | 
            +
            		expect( string ).to match( %r{\?.*host=localhost} )
         | 
| 99 | 
            +
            		expect( string ).to match( %r{\?.*port=555} )
         | 
| 100 | 
            +
            		expect( string ).to match( %r{\?.*dbname=x} )
         | 
| 101 | 
            +
            	end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            	it "can create a connection URI with a non-standard domain socket directory" do
         | 
| 104 | 
            +
            		string = described_class.parse_connect_args( 'postgresql://%2Fvar%2Flib%2Fpostgresql/dbname' )
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            		expect( string ).to be_a( String )
         | 
| 107 | 
            +
            		expect( string ).to match( %r{^postgresql://%2Fvar%2Flib%2Fpostgresql/dbname} )
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            		string = described_class.
         | 
| 110 | 
            +
            			parse_connect_args( 'postgresql:///dbname', :host => '/var/lib/postgresql' )
         | 
| 111 | 
            +
             | 
| 112 | 
            +
            		expect( string ).to be_a( String )
         | 
| 113 | 
            +
            		expect( string ).to match( %r{^postgresql:///dbname\?} )
         | 
| 114 | 
            +
            		expect( string ).to match( %r{\?.*host=%2Fvar%2Flib%2Fpostgresql} )
         | 
| 115 | 
            +
            	end
         | 
| 116 | 
            +
             | 
| 89 117 | 
             
            	it "connects with defaults if no connection parameters are given" do
         | 
| 90 | 
            -
            		described_class.parse_connect_args. | 
| 118 | 
            +
            		expect( described_class.parse_connect_args ).to eq( '' )
         | 
| 119 | 
            +
            	end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            	it "connects successfully with connection string" do
         | 
| 122 | 
            +
            		conninfo_with_colon_in_password = "host=localhost user=a port=555 dbname=test password=a:a"
         | 
| 123 | 
            +
             | 
| 124 | 
            +
            		string = described_class.parse_connect_args( conninfo_with_colon_in_password )
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            		expect( string ).to be_a( String )
         | 
| 127 | 
            +
            		expect( string ).to match( %r{(^|\s)user=a} )
         | 
| 128 | 
            +
            		expect( string ).to match( %r{(^|\s)password=a:a} )
         | 
| 129 | 
            +
            		expect( string ).to match( %r{(^|\s)host=localhost} )
         | 
| 130 | 
            +
            		expect( string ).to match( %r{(^|\s)port=555} )
         | 
| 131 | 
            +
            		expect( string ).to match( %r{(^|\s)dbname=test} )
         | 
| 91 132 | 
             
            	end
         | 
| 92 133 |  | 
| 93 134 | 
             
            	it "connects successfully with connection string" do
         | 
| 94 | 
            -
            		tmpconn = described_class.connect(@conninfo)
         | 
| 95 | 
            -
            		tmpconn.status. | 
| 135 | 
            +
            		tmpconn = described_class.connect( @conninfo )
         | 
| 136 | 
            +
            		expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
         | 
| 96 137 | 
             
            		tmpconn.finish
         | 
| 97 138 | 
             
            	end
         | 
| 98 139 |  | 
| 99 140 | 
             
            	it "connects using 7 arguments converted to strings" do
         | 
| 100 | 
            -
            		tmpconn = described_class.connect('localhost', @port, nil, nil, :test, nil, nil)
         | 
| 101 | 
            -
            		tmpconn.status. | 
| 141 | 
            +
            		tmpconn = described_class.connect( 'localhost', @port, nil, nil, :test, nil, nil )
         | 
| 142 | 
            +
            		expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
         | 
| 102 143 | 
             
            		tmpconn.finish
         | 
| 103 144 | 
             
            	end
         | 
| 104 145 |  | 
| @@ -107,87 +148,160 @@ describe PG::Connection do | |
| 107 148 | 
             
            			:host => 'localhost',
         | 
| 108 149 | 
             
            			:port => @port,
         | 
| 109 150 | 
             
            			:dbname => :test)
         | 
| 110 | 
            -
            		tmpconn.status. | 
| 151 | 
            +
            		expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
         | 
| 111 152 | 
             
            		tmpconn.finish
         | 
| 112 153 | 
             
            	end
         | 
| 113 154 |  | 
| 114 | 
            -
            	it "connects using a hash of optional connection parameters" | 
| 155 | 
            +
            	it "connects using a hash of optional connection parameters" do
         | 
| 115 156 | 
             
            		tmpconn = described_class.connect(
         | 
| 116 157 | 
             
            			:host => 'localhost',
         | 
| 117 158 | 
             
            			:port => @port,
         | 
| 118 159 | 
             
            			:dbname => :test,
         | 
| 119 160 | 
             
            			:keepalives => 1)
         | 
| 120 | 
            -
            		tmpconn.status. | 
| 161 | 
            +
            		expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
         | 
| 121 162 | 
             
            		tmpconn.finish
         | 
| 122 163 | 
             
            	end
         | 
| 123 164 |  | 
| 124 165 | 
             
            	it "raises an exception when connecting with an invalid number of arguments" do
         | 
| 125 166 | 
             
            		expect {
         | 
| 126 | 
            -
            			described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'extra' )
         | 
| 127 | 
            -
            		}.to raise_error | 
| 167 | 
            +
            			described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'the-extra-arg' )
         | 
| 168 | 
            +
            		}.to raise_error do |error|
         | 
| 169 | 
            +
            			expect( error ).to be_an( ArgumentError )
         | 
| 170 | 
            +
            			expect( error.message ).to match( /extra positional parameter/i )
         | 
| 171 | 
            +
            			expect( error.message ).to match( /8/ )
         | 
| 172 | 
            +
            			expect( error.message ).to match( /the-extra-arg/ )
         | 
| 173 | 
            +
            		end
         | 
| 128 174 | 
             
            	end
         | 
| 129 175 |  | 
| 130 | 
            -
            	it "can connect asynchronously" | 
| 176 | 
            +
            	it "can connect asynchronously" do
         | 
| 131 177 | 
             
            		tmpconn = described_class.connect_start( @conninfo )
         | 
| 132 | 
            -
            		tmpconn. | 
| 133 | 
            -
            		socket = tmpconn.socket_io
         | 
| 134 | 
            -
            		status = tmpconn.connect_poll
         | 
| 135 | 
            -
             | 
| 136 | 
            -
            		while status != PG::PGRES_POLLING_OK
         | 
| 137 | 
            -
            			if status == PG::PGRES_POLLING_READING
         | 
| 138 | 
            -
            				select( [socket], [], [], 5.0 ) or
         | 
| 139 | 
            -
            					raise "Asynchronous connection timed out!"
         | 
| 178 | 
            +
            		expect( tmpconn ).to be_a( described_class )
         | 
| 140 179 |  | 
| 141 | 
            -
             | 
| 142 | 
            -
             | 
| 143 | 
            -
            					raise "Asynchronous connection timed out!"
         | 
| 144 | 
            -
            			end
         | 
| 145 | 
            -
            			status = tmpconn.connect_poll
         | 
| 146 | 
            -
            		end
         | 
| 147 | 
            -
             | 
| 148 | 
            -
            		tmpconn.status.should == PG::CONNECTION_OK
         | 
| 180 | 
            +
            		wait_for_polling_ok(tmpconn)
         | 
| 181 | 
            +
            		expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
         | 
| 149 182 | 
             
            		tmpconn.finish
         | 
| 150 183 | 
             
            	end
         | 
| 151 184 |  | 
| 152 | 
            -
            	it "can connect asynchronously for the duration of a block" | 
| 185 | 
            +
            	it "can connect asynchronously for the duration of a block" do
         | 
| 153 186 | 
             
            		conn = nil
         | 
| 154 187 |  | 
| 155 188 | 
             
            		described_class.connect_start(@conninfo) do |tmpconn|
         | 
| 156 | 
            -
            			tmpconn. | 
| 189 | 
            +
            			expect( tmpconn ).to be_a( described_class )
         | 
| 157 190 | 
             
            			conn = tmpconn
         | 
| 158 | 
            -
            			socket = tmpconn.socket_io
         | 
| 159 | 
            -
            			status = tmpconn.connect_poll
         | 
| 160 191 |  | 
| 161 | 
            -
            			 | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
             | 
| 170 | 
            -
             | 
| 171 | 
            -
             | 
| 172 | 
            -
            			 | 
| 192 | 
            +
            			wait_for_polling_ok(tmpconn)
         | 
| 193 | 
            +
            			expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
         | 
| 194 | 
            +
            		end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
            		expect( conn ).to be_finished()
         | 
| 197 | 
            +
            	end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
            	context "with async established connection" do
         | 
| 200 | 
            +
            		before :each do
         | 
| 201 | 
            +
            			@conn2 = described_class.connect_start( @conninfo )
         | 
| 202 | 
            +
            			wait_for_polling_ok(@conn2)
         | 
| 203 | 
            +
            			expect( @conn2 ).to still_be_usable
         | 
| 204 | 
            +
            		end
         | 
| 205 | 
            +
             | 
| 206 | 
            +
            		after :each do
         | 
| 207 | 
            +
            			expect( @conn2 ).to still_be_usable
         | 
| 208 | 
            +
            			@conn2.close
         | 
| 209 | 
            +
            		end
         | 
| 173 210 |  | 
| 174 | 
            -
             | 
| 211 | 
            +
            		it "conn.send_query and IO.select work" do
         | 
| 212 | 
            +
            			@conn2.send_query("SELECT 1")
         | 
| 213 | 
            +
            			res = wait_for_query_result(@conn2)
         | 
| 214 | 
            +
            			expect( res.values ).to eq([["1"]])
         | 
| 175 215 | 
             
            		end
         | 
| 176 216 |  | 
| 177 | 
            -
            		conn. | 
| 217 | 
            +
            		it "conn.send_query and conn.block work" do
         | 
| 218 | 
            +
            			@conn2.send_query("SELECT 2")
         | 
| 219 | 
            +
            			@conn2.block
         | 
| 220 | 
            +
            			res = @conn2.get_last_result
         | 
| 221 | 
            +
            			expect( res.values ).to eq([["2"]])
         | 
| 222 | 
            +
            		end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
            		it "conn.async_query works" do
         | 
| 225 | 
            +
            			res = @conn2.async_query("SELECT 3")
         | 
| 226 | 
            +
            			expect( res.values ).to eq([["3"]])
         | 
| 227 | 
            +
            			expect( @conn2 ).to still_be_usable
         | 
| 228 | 
            +
             | 
| 229 | 
            +
            			res = @conn2.query("SELECT 4")
         | 
| 230 | 
            +
            		end
         | 
| 231 | 
            +
             | 
| 232 | 
            +
            		it "can use conn.reset_start to restart the connection" do
         | 
| 233 | 
            +
            			ios = IO.pipe
         | 
| 234 | 
            +
            			conn = described_class.connect_start( @conninfo )
         | 
| 235 | 
            +
            			wait_for_polling_ok(conn)
         | 
| 236 | 
            +
             | 
| 237 | 
            +
            			# Close the two pipe file descriptors, so that the file descriptor of
         | 
| 238 | 
            +
            			# newly established connection is probably distinct from the previous one.
         | 
| 239 | 
            +
            			ios.each(&:close)
         | 
| 240 | 
            +
            			conn.reset_start
         | 
| 241 | 
            +
            			wait_for_polling_ok(conn, :reset_poll)
         | 
| 242 | 
            +
             | 
| 243 | 
            +
            			# The new connection should work even when the file descriptor has changed.
         | 
| 244 | 
            +
            			conn.send_query("SELECT 1")
         | 
| 245 | 
            +
            			res = wait_for_query_result(conn)
         | 
| 246 | 
            +
            			expect( res.values ).to eq([["1"]])
         | 
| 247 | 
            +
             | 
| 248 | 
            +
            			conn.close
         | 
| 249 | 
            +
            		end
         | 
| 250 | 
            +
             | 
| 251 | 
            +
            		it "should properly close a socket IO when GC'ed" do
         | 
| 252 | 
            +
            			# This results in
         | 
| 253 | 
            +
            			#    Errno::ENOTSOCK: An operation was attempted on something that is not a socket.
         | 
| 254 | 
            +
            			# on Windows when rb_w32_unwrap_io_handle() isn't called in pgconn_gc_free().
         | 
| 255 | 
            +
            			5.times do
         | 
| 256 | 
            +
            				conn = described_class.connect( @conninfo )
         | 
| 257 | 
            +
            				conn.socket_io.close
         | 
| 258 | 
            +
            			end
         | 
| 259 | 
            +
            			GC.start
         | 
| 260 | 
            +
            			IO.pipe.each(&:close)
         | 
| 261 | 
            +
            		end
         | 
| 262 | 
            +
            	end
         | 
| 263 | 
            +
             | 
| 264 | 
            +
            	it "raises proper error when sending fails" do
         | 
| 265 | 
            +
            		conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
         | 
| 266 | 
            +
            		expect{ conn.exec 'SELECT 1' }.to raise_error(PG::UnableToSend, /no connection/)
         | 
| 178 267 | 
             
            	end
         | 
| 179 268 |  | 
| 180 269 | 
             
            	it "doesn't leave stale server connections after finish" do
         | 
| 181 270 | 
             
            		described_class.connect(@conninfo).finish
         | 
| 182 271 | 
             
            		sleep 0.5
         | 
| 183 272 | 
             
            		res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
         | 
| 184 | 
            -
            							WHERE usename IS NOT NULL])
         | 
| 273 | 
            +
            							WHERE usename IS NOT NULL AND application_name != ''])
         | 
| 185 274 | 
             
            		# there's still the global @conn, but should be no more
         | 
| 186 | 
            -
            		res[0]['n']. | 
| 275 | 
            +
            		expect( res[0]['n'] ).to eq( '1' )
         | 
| 187 276 | 
             
            	end
         | 
| 188 277 |  | 
| 278 | 
            +
            	it "can retrieve it's connection parameters for the established connection" do
         | 
| 279 | 
            +
            		expect( @conn.db ).to eq( "test" )
         | 
| 280 | 
            +
            		expect( @conn.user ).to be_a_kind_of( String )
         | 
| 281 | 
            +
            		expect( @conn.pass ).to eq( "" )
         | 
| 282 | 
            +
            		expect( @conn.port ).to eq( @port )
         | 
| 283 | 
            +
            		expect( @conn.tty ).to eq( "" )
         | 
| 284 | 
            +
            		expect( @conn.options ).to eq( "" )
         | 
| 285 | 
            +
            	end
         | 
| 286 | 
            +
            	it "can retrieve it's connection parameters for the established connection",
         | 
| 287 | 
            +
            	    skip: RUBY_PLATFORM=~/x64-mingw/ ? "host segfaults on Windows-x64" : false do
         | 
| 288 | 
            +
            		expect( @conn.host ).to eq( "localhost" )
         | 
| 289 | 
            +
            	end
         | 
| 290 | 
            +
             | 
| 291 | 
            +
            	it "can set error verbosity" do
         | 
| 292 | 
            +
            		old = @conn.set_error_verbosity( PG::PQERRORS_TERSE )
         | 
| 293 | 
            +
            		new = @conn.set_error_verbosity( old )
         | 
| 294 | 
            +
            		expect( new ).to eq( PG::PQERRORS_TERSE )
         | 
| 295 | 
            +
            	end
         | 
| 189 296 |  | 
| 190 | 
            -
            	 | 
| 297 | 
            +
            	it "can set error context visibility", :postgresql_96 do
         | 
| 298 | 
            +
            		old = @conn.set_error_context_visibility( PG::PQSHOW_CONTEXT_NEVER )
         | 
| 299 | 
            +
            		new = @conn.set_error_context_visibility( old )
         | 
| 300 | 
            +
            		expect( new ).to eq( PG::PQSHOW_CONTEXT_NEVER )
         | 
| 301 | 
            +
            	end
         | 
| 302 | 
            +
             | 
| 303 | 
            +
            	let(:expected_trace_output) do
         | 
| 304 | 
            +
            		%{
         | 
| 191 305 | 
             
            		To backend> Msg Q
         | 
| 192 306 | 
             
            		To backend> "SELECT 1 AS one"
         | 
| 193 307 | 
             
            		To backend> Msg complete, length 21
         | 
| @@ -215,6 +329,7 @@ describe PG::Connection do | |
| 215 329 | 
             
            		From backend (#4)> 5
         | 
| 216 330 | 
             
            		From backend> T
         | 
| 217 331 | 
             
            		}.gsub( /^\t{2}/, '' ).lstrip
         | 
| 332 | 
            +
            	end
         | 
| 218 333 |  | 
| 219 334 | 
             
            	it "trace and untrace client-server communication", :unix do
         | 
| 220 335 | 
             
            			# be careful to explicitly close files so that the
         | 
| @@ -225,25 +340,22 @@ describe PG::Connection do | |
| 225 340 | 
             
            			@conn.trace( trace_io )
         | 
| 226 341 | 
             
            			trace_io.close
         | 
| 227 342 |  | 
| 228 | 
            -
            			 | 
| 343 | 
            +
            			@conn.exec("SELECT 1 AS one")
         | 
| 229 344 | 
             
            			@conn.untrace
         | 
| 230 345 |  | 
| 231 | 
            -
            			 | 
| 346 | 
            +
            			@conn.exec("SELECT 2 AS two")
         | 
| 232 347 |  | 
| 233 348 | 
             
            			trace_data = trace_file.read
         | 
| 234 349 |  | 
| 235 | 
            -
            			 | 
| 236 | 
            -
            			#  | 
| 237 | 
            -
            			# | 
| 238 | 
            -
            			#  | 
| 239 | 
            -
            			# +From backend (#4)>  | 
| 240 | 
            -
            			# | 
| 241 | 
            -
            			 | 
| 242 | 
            -
            				expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
         | 
| 243 | 
            -
            				expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
         | 
| 244 | 
            -
            			end
         | 
| 350 | 
            +
            			# For async_exec the output will be different:
         | 
| 351 | 
            +
            			#  From backend> Z
         | 
| 352 | 
            +
            			#  From backend (#4)> 5
         | 
| 353 | 
            +
            			# +From backend> Z
         | 
| 354 | 
            +
            			# +From backend (#4)> 5
         | 
| 355 | 
            +
            			#  From backend> T
         | 
| 356 | 
            +
            			trace_data.sub!( /(From backend> Z\nFrom backend \(#4\)> 5\n){3}/m, '\\1\\1' )
         | 
| 245 357 |  | 
| 246 | 
            -
            			trace_data. | 
| 358 | 
            +
            			expect( trace_data ).to eq( expected_trace_output )
         | 
| 247 359 | 
             
            		end
         | 
| 248 360 |  | 
| 249 361 | 
             
            	it "allows a query to be cancelled" do
         | 
| @@ -254,9 +366,36 @@ describe PG::Connection do | |
| 254 366 | 
             
            		if(tmpres.result_status != PG::PGRES_TUPLES_OK)
         | 
| 255 367 | 
             
            			error = true
         | 
| 256 368 | 
             
            		end
         | 
| 257 | 
            -
            		error. | 
| 369 | 
            +
            		expect( error ).to eq( true )
         | 
| 370 | 
            +
            	end
         | 
| 371 | 
            +
             | 
| 372 | 
            +
            	it "can stop a thread that runs a blocking query with async_exec" do
         | 
| 373 | 
            +
            		start = Time.now
         | 
| 374 | 
            +
            		t = Thread.new do
         | 
| 375 | 
            +
            			@conn.async_exec( 'select pg_sleep(10)' )
         | 
| 376 | 
            +
            		end
         | 
| 377 | 
            +
            		sleep 0.1
         | 
| 378 | 
            +
             | 
| 379 | 
            +
            		t.kill
         | 
| 380 | 
            +
            		t.join
         | 
| 381 | 
            +
            		expect( (Time.now - start) ).to be < 10
         | 
| 258 382 | 
             
            	end
         | 
| 259 383 |  | 
| 384 | 
            +
            	it "should work together with signal handlers", :unix do
         | 
| 385 | 
            +
            		signal_received = false
         | 
| 386 | 
            +
            		trap 'USR2' do
         | 
| 387 | 
            +
            			signal_received = true
         | 
| 388 | 
            +
            		end
         | 
| 389 | 
            +
             | 
| 390 | 
            +
            		Thread.new do
         | 
| 391 | 
            +
            			sleep 0.1
         | 
| 392 | 
            +
            			Process.kill("USR2", Process.pid)
         | 
| 393 | 
            +
            		end
         | 
| 394 | 
            +
            		@conn.exec("select pg_sleep(0.3)")
         | 
| 395 | 
            +
            		expect( signal_received ).to be_truthy
         | 
| 396 | 
            +
            	end
         | 
| 397 | 
            +
             | 
| 398 | 
            +
             | 
| 260 399 | 
             
            	it "automatically rolls back a transaction started with Connection#transaction if an exception " +
         | 
| 261 400 | 
             
            	   "is raised" do
         | 
| 262 401 | 
             
            		# abort the per-example transaction so we can test our own
         | 
| @@ -265,15 +404,29 @@ describe PG::Connection do | |
| 265 404 | 
             
            		res = nil
         | 
| 266 405 | 
             
            		@conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
         | 
| 267 406 |  | 
| 268 | 
            -
            		 | 
| 269 | 
            -
            			 | 
| 270 | 
            -
            				@conn. | 
| 271 | 
            -
             | 
| 272 | 
            -
             | 
| 273 | 
            -
             | 
| 407 | 
            +
            		begin
         | 
| 408 | 
            +
            			expect {
         | 
| 409 | 
            +
            				res = @conn.transaction do
         | 
| 410 | 
            +
            					@conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
         | 
| 411 | 
            +
            					raise "Oh noes! All pie is gone!"
         | 
| 412 | 
            +
            				end
         | 
| 413 | 
            +
            			}.to raise_exception( RuntimeError, /all pie is gone/i )
         | 
| 274 414 |  | 
| 275 | 
            -
             | 
| 276 | 
            -
             | 
| 415 | 
            +
            			res = @conn.exec( "SELECT * FROM pie" )
         | 
| 416 | 
            +
            			expect( res.ntuples ).to eq( 0 )
         | 
| 417 | 
            +
            		ensure
         | 
| 418 | 
            +
            			@conn.exec( "DROP TABLE pie" )
         | 
| 419 | 
            +
            		end
         | 
| 420 | 
            +
            	end
         | 
| 421 | 
            +
             | 
| 422 | 
            +
            	it "returns the block result from Connection#transaction" do
         | 
| 423 | 
            +
            		# abort the per-example transaction so we can test our own
         | 
| 424 | 
            +
            		@conn.exec( 'ROLLBACK' )
         | 
| 425 | 
            +
             | 
| 426 | 
            +
            		res = @conn.transaction do
         | 
| 427 | 
            +
            			"transaction result"
         | 
| 428 | 
            +
            		end
         | 
| 429 | 
            +
            		expect( res ).to eq( "transaction result" )
         | 
| 277 430 | 
             
            	end
         | 
| 278 431 |  | 
| 279 432 | 
             
            	it "not read past the end of a large object" do
         | 
| @@ -281,33 +434,45 @@ describe PG::Connection do | |
| 281 434 | 
             
            			oid = @conn.lo_create( 0 )
         | 
| 282 435 | 
             
            			fd = @conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
         | 
| 283 436 | 
             
            			@conn.lo_write( fd, "foobar" )
         | 
| 284 | 
            -
            			@conn.lo_read( fd, 10 ). | 
| 437 | 
            +
            			expect( @conn.lo_read( fd, 10 ) ).to be_nil()
         | 
| 285 438 | 
             
            			@conn.lo_lseek( fd, 0, PG::SEEK_SET )
         | 
| 286 | 
            -
            			@conn.lo_read( fd, 10 ). | 
| 439 | 
            +
            			expect( @conn.lo_read( fd, 10 ) ).to eq( 'foobar' )
         | 
| 287 440 | 
             
            		end
         | 
| 288 441 | 
             
            	end
         | 
| 289 442 |  | 
| 290 | 
            -
             | 
| 291 | 
            -
            	it "supports parameters passed to #exec (backward compatibility)" do
         | 
| 292 | 
            -
            		@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
         | 
| 293 | 
            -
            		@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
         | 
| 294 | 
            -
            		@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
         | 
| 295 | 
            -
            		@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
         | 
| 296 | 
            -
             | 
| 297 | 
            -
            		res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
         | 
| 298 | 
            -
            		res.values.should == [ ['Wally'], ['Sally'] ]
         | 
| 299 | 
            -
            	end
         | 
| 300 | 
            -
             | 
| 301 443 | 
             
            	it "supports explicitly calling #exec_params" do
         | 
| 302 444 | 
             
            		@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
         | 
| 303 | 
            -
            		@conn. | 
| 304 | 
            -
            		@conn. | 
| 305 | 
            -
            		@conn. | 
| 445 | 
            +
            		@conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
         | 
| 446 | 
            +
            		@conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
         | 
| 447 | 
            +
            		@conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
         | 
| 306 448 |  | 
| 307 449 | 
             
            		res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
         | 
| 308 | 
            -
            		res.values. | 
| 450 | 
            +
            		expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
         | 
| 451 | 
            +
            	end
         | 
| 452 | 
            +
             | 
| 453 | 
            +
            	it "supports hash form parameters for #exec_params" do
         | 
| 454 | 
            +
            		hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
         | 
| 455 | 
            +
            		hash_param_nil = { value: nil, type: 17, format: 1 }
         | 
| 456 | 
            +
            		res = @conn.exec_params( "SELECT $1, $2",
         | 
| 457 | 
            +
            					[ hash_param_bin, hash_param_nil ] )
         | 
| 458 | 
            +
            		expect( res.values ).to eq( [["\\x00ff", nil]] )
         | 
| 459 | 
            +
            		expect( result_typenames(res) ).to eq( ['bytea', 'bytea'] )
         | 
| 309 460 | 
             
            	end
         | 
| 310 461 |  | 
| 462 | 
            +
            	it "should work with arbitrary number of params" do
         | 
| 463 | 
            +
            		begin
         | 
| 464 | 
            +
            			3.step( 12, 0.2 ) do |exp|
         | 
| 465 | 
            +
            				num_params = (2 ** exp).to_i
         | 
| 466 | 
            +
            				sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
         | 
| 467 | 
            +
            				params = num_params.times.to_a
         | 
| 468 | 
            +
            				res = @conn.exec_params( "SELECT #{sql}", params )
         | 
| 469 | 
            +
            				expect( res.nfields ).to eq( num_params )
         | 
| 470 | 
            +
            				expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
         | 
| 471 | 
            +
            			end
         | 
| 472 | 
            +
            		rescue PG::ProgramLimitExceeded
         | 
| 473 | 
            +
            			# Stop silently if the server complains about too many params
         | 
| 474 | 
            +
            		end
         | 
| 475 | 
            +
            	end
         | 
| 311 476 |  | 
| 312 477 | 
             
            	it "can wait for NOTIFY events" do
         | 
| 313 478 | 
             
            		@conn.exec( 'ROLLBACK' )
         | 
| @@ -323,7 +488,7 @@ describe PG::Connection do | |
| 323 488 | 
             
            			end
         | 
| 324 489 | 
             
            		end
         | 
| 325 490 |  | 
| 326 | 
            -
            		@conn.wait_for_notify( 10 ). | 
| 491 | 
            +
            		expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
         | 
| 327 492 | 
             
            		@conn.exec( 'UNLISTEN woo' )
         | 
| 328 493 |  | 
| 329 494 | 
             
            		t.join
         | 
| @@ -345,8 +510,8 @@ describe PG::Connection do | |
| 345 510 |  | 
| 346 511 | 
             
            		eventpid = event = nil
         | 
| 347 512 | 
             
            		@conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
         | 
| 348 | 
            -
            		event. | 
| 349 | 
            -
            		eventpid. | 
| 513 | 
            +
            		expect( event ).to eq( 'woo' )
         | 
| 514 | 
            +
            		expect( eventpid ).to be_an( Integer )
         | 
| 350 515 |  | 
| 351 516 | 
             
            		@conn.exec( 'UNLISTEN woo' )
         | 
| 352 517 |  | 
| @@ -373,8 +538,8 @@ describe PG::Connection do | |
| 373 538 | 
             
            			channels << @conn.wait_for_notify( 2 )
         | 
| 374 539 | 
             
            		end
         | 
| 375 540 |  | 
| 376 | 
            -
            		channels. | 
| 377 | 
            -
            		channels. | 
| 541 | 
            +
            		expect( channels.size ).to eq( 3 )
         | 
| 542 | 
            +
            		expect( channels ).to include( 'woo', 'war', 'woz' )
         | 
| 378 543 |  | 
| 379 544 | 
             
            		@conn.exec( 'UNLISTEN woz' )
         | 
| 380 545 | 
             
            		@conn.exec( 'UNLISTEN war' )
         | 
| @@ -396,25 +561,161 @@ describe PG::Connection do | |
| 396 561 | 
             
            		# Cause the notification to buffer, but not be read yet
         | 
| 397 562 | 
             
            		@conn.exec( 'SELECT 1' )
         | 
| 398 563 |  | 
| 399 | 
            -
            		@conn.wait_for_notify( 10 ). | 
| 564 | 
            +
            		expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
         | 
| 400 565 | 
             
            		@conn.exec( 'UNLISTEN woo' )
         | 
| 401 566 | 
             
            	end
         | 
| 402 567 |  | 
| 568 | 
            +
            	it "can receive notices while waiting for NOTIFY without exceeding the timeout" do
         | 
| 569 | 
            +
            		notices = []
         | 
| 570 | 
            +
            		@conn.set_notice_processor do |msg|
         | 
| 571 | 
            +
            			notices << [msg, Time.now]
         | 
| 572 | 
            +
            		end
         | 
| 573 | 
            +
            		st = Time.now
         | 
| 574 | 
            +
            		@conn.send_query "SELECT pg_sleep(0.5); do $$ BEGIN RAISE NOTICE 'woohoo'; END; $$ LANGUAGE plpgsql;"
         | 
| 575 | 
            +
            		expect( @conn.wait_for_notify( 1 ) ).to be_nil
         | 
| 576 | 
            +
            		expect( notices.first ).to_not be_nil
         | 
| 577 | 
            +
            		et = Time.now
         | 
| 578 | 
            +
            		expect( (et - notices.first[1]) ).to be >= 0.3
         | 
| 579 | 
            +
            		expect( (et - st) ).to be >= 0.9
         | 
| 580 | 
            +
            		expect( (et - st) ).to be < 1.4
         | 
| 581 | 
            +
            	end
         | 
| 582 | 
            +
             | 
| 403 583 | 
             
            	it "yields the result if block is given to exec" do
         | 
| 404 584 | 
             
            		rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
         | 
| 405 585 | 
             
            			values = []
         | 
| 406 | 
            -
            			result. | 
| 407 | 
            -
            			result.ntuples. | 
| 586 | 
            +
            			expect( result ).to be_kind_of( PG::Result )
         | 
| 587 | 
            +
            			expect( result.ntuples ).to eq( 2 )
         | 
| 408 588 | 
             
            			result.each do |tuple|
         | 
| 409 589 | 
             
            				values << tuple['a']
         | 
| 410 590 | 
             
            			end
         | 
| 411 591 | 
             
            			values
         | 
| 412 592 | 
             
            		end
         | 
| 413 593 |  | 
| 414 | 
            -
            		rval. | 
| 415 | 
            -
            		rval. | 
| 594 | 
            +
            		expect( rval.size ).to eq( 2 )
         | 
| 595 | 
            +
            		expect( rval ).to include( '5678', '1234' )
         | 
| 416 596 | 
             
            	end
         | 
| 417 597 |  | 
| 598 | 
            +
            	it "can process #copy_data output queries" do
         | 
| 599 | 
            +
            		rows = []
         | 
| 600 | 
            +
            		res2 = @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
         | 
| 601 | 
            +
            			expect( res.result_status ).to eq( PG::PGRES_COPY_OUT )
         | 
| 602 | 
            +
            			expect( res.nfields ).to eq( 1 )
         | 
| 603 | 
            +
            			while row=@conn.get_copy_data
         | 
| 604 | 
            +
            				rows << row
         | 
| 605 | 
            +
            			end
         | 
| 606 | 
            +
            		end
         | 
| 607 | 
            +
            		expect( rows ).to eq( ["1\n", "2\n"] )
         | 
| 608 | 
            +
            		expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
         | 
| 609 | 
            +
            		expect( @conn ).to still_be_usable
         | 
| 610 | 
            +
            	end
         | 
| 611 | 
            +
             | 
| 612 | 
            +
            	it "can handle incomplete #copy_data output queries" do
         | 
| 613 | 
            +
            		expect {
         | 
| 614 | 
            +
            			@conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
         | 
| 615 | 
            +
            				@conn.get_copy_data
         | 
| 616 | 
            +
            			end
         | 
| 617 | 
            +
            		}.to raise_error(PG::NotAllCopyDataRetrieved, /Not all/)
         | 
| 618 | 
            +
            		expect( @conn ).to still_be_usable
         | 
| 619 | 
            +
            	end
         | 
| 620 | 
            +
             | 
| 621 | 
            +
            	it "can handle client errors in #copy_data for output" do
         | 
| 622 | 
            +
            		expect {
         | 
| 623 | 
            +
            			@conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do
         | 
| 624 | 
            +
            				raise "boom"
         | 
| 625 | 
            +
            			end
         | 
| 626 | 
            +
            		}.to raise_error(RuntimeError, "boom")
         | 
| 627 | 
            +
            		expect( @conn ).to still_be_usable
         | 
| 628 | 
            +
            	end
         | 
| 629 | 
            +
             | 
| 630 | 
            +
            	it "can handle server errors in #copy_data for output" do
         | 
| 631 | 
            +
            		@conn.exec "ROLLBACK"
         | 
| 632 | 
            +
            		@conn.transaction do
         | 
| 633 | 
            +
            			@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
         | 
| 634 | 
            +
            			expect {
         | 
| 635 | 
            +
            				@conn.copy_data( "COPY (SELECT errfunc()) TO STDOUT" ) do |res|
         | 
| 636 | 
            +
            					while @conn.get_copy_data
         | 
| 637 | 
            +
            					end
         | 
| 638 | 
            +
            				end
         | 
| 639 | 
            +
            			}.to raise_error(PG::Error, /test-error/)
         | 
| 640 | 
            +
            		end
         | 
| 641 | 
            +
            		expect( @conn ).to still_be_usable
         | 
| 642 | 
            +
            	end
         | 
| 643 | 
            +
             | 
| 644 | 
            +
            	it "can process #copy_data input queries" do
         | 
| 645 | 
            +
            		@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
         | 
| 646 | 
            +
            		res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
         | 
| 647 | 
            +
            			expect( res.result_status ).to eq( PG::PGRES_COPY_IN )
         | 
| 648 | 
            +
            			expect( res.nfields ).to eq( 1 )
         | 
| 649 | 
            +
            			@conn.put_copy_data "1\n"
         | 
| 650 | 
            +
            			@conn.put_copy_data "2\n"
         | 
| 651 | 
            +
            		end
         | 
| 652 | 
            +
            		expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
         | 
| 653 | 
            +
             | 
| 654 | 
            +
            		expect( @conn ).to still_be_usable
         | 
| 655 | 
            +
             | 
| 656 | 
            +
            		res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
         | 
| 657 | 
            +
            		expect( res.values ).to eq( [["1"], ["2"]] )
         | 
| 658 | 
            +
            	end
         | 
| 659 | 
            +
             | 
| 660 | 
            +
            	it "can handle client errors in #copy_data for input" do
         | 
| 661 | 
            +
            		@conn.exec "ROLLBACK"
         | 
| 662 | 
            +
            		@conn.transaction do
         | 
| 663 | 
            +
            			@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
         | 
| 664 | 
            +
            			expect {
         | 
| 665 | 
            +
            				@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
         | 
| 666 | 
            +
            					raise "boom"
         | 
| 667 | 
            +
            				end
         | 
| 668 | 
            +
            			}.to raise_error(RuntimeError, "boom")
         | 
| 669 | 
            +
            		end
         | 
| 670 | 
            +
             | 
| 671 | 
            +
            		expect( @conn ).to still_be_usable
         | 
| 672 | 
            +
            	end
         | 
| 673 | 
            +
             | 
| 674 | 
            +
            	it "can handle server errors in #copy_data for input" do
         | 
| 675 | 
            +
            		@conn.exec "ROLLBACK"
         | 
| 676 | 
            +
            		@conn.transaction do
         | 
| 677 | 
            +
            			@conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
         | 
| 678 | 
            +
            			expect {
         | 
| 679 | 
            +
            				@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
         | 
| 680 | 
            +
            					@conn.put_copy_data "xyz\n"
         | 
| 681 | 
            +
            				end
         | 
| 682 | 
            +
            			}.to raise_error(PG::Error, /invalid input syntax for .*integer/)
         | 
| 683 | 
            +
            		end
         | 
| 684 | 
            +
            		expect( @conn ).to still_be_usable
         | 
| 685 | 
            +
            	end
         | 
| 686 | 
            +
             | 
| 687 | 
            +
            	it "gracefully handle SQL statements while in #copy_data for input" do
         | 
| 688 | 
            +
            		@conn.exec "ROLLBACK"
         | 
| 689 | 
            +
            		@conn.transaction do
         | 
| 690 | 
            +
            			@conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
         | 
| 691 | 
            +
            			expect {
         | 
| 692 | 
            +
            				@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
         | 
| 693 | 
            +
            					@conn.exec "SELECT 1"
         | 
| 694 | 
            +
            				end
         | 
| 695 | 
            +
            			}.to raise_error(PG::Error, /no COPY in progress/)
         | 
| 696 | 
            +
            		end
         | 
| 697 | 
            +
            		expect( @conn ).to still_be_usable
         | 
| 698 | 
            +
            	end
         | 
| 699 | 
            +
             | 
| 700 | 
            +
            	it "gracefully handle SQL statements while in #copy_data for output" do
         | 
| 701 | 
            +
            		@conn.exec "ROLLBACK"
         | 
| 702 | 
            +
            		@conn.transaction do
         | 
| 703 | 
            +
            			expect {
         | 
| 704 | 
            +
            				@conn.copy_data( "COPY (VALUES(1), (2)) TO STDOUT" ) do |res|
         | 
| 705 | 
            +
            					@conn.exec "SELECT 3"
         | 
| 706 | 
            +
            				end
         | 
| 707 | 
            +
            			}.to raise_error(PG::Error, /no COPY in progress/)
         | 
| 708 | 
            +
            		end
         | 
| 709 | 
            +
            		expect( @conn ).to still_be_usable
         | 
| 710 | 
            +
            	end
         | 
| 711 | 
            +
             | 
| 712 | 
            +
            	it "should raise an error for non copy statements in #copy_data" do
         | 
| 713 | 
            +
            		expect {
         | 
| 714 | 
            +
            			@conn.copy_data( "SELECT 1" ){}
         | 
| 715 | 
            +
            		}.to raise_error(ArgumentError, /no COPY/)
         | 
| 716 | 
            +
             | 
| 717 | 
            +
            		expect( @conn ).to still_be_usable
         | 
| 718 | 
            +
            	end
         | 
| 418 719 |  | 
| 419 720 | 
             
            	it "correctly finishes COPY queries passed to #async_exec" do
         | 
| 420 721 | 
             
            		@conn.async_exec( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" )
         | 
| @@ -429,8 +730,8 @@ describe PG::Connection do | |
| 429 730 | 
             
            			results << data if data
         | 
| 430 731 | 
             
            		end until data.nil?
         | 
| 431 732 |  | 
| 432 | 
            -
            		results. | 
| 433 | 
            -
            		results. | 
| 733 | 
            +
            		expect( results.size ).to eq( 2 )
         | 
| 734 | 
            +
            		expect( results ).to include( "1\n", "2\n" )
         | 
| 434 735 | 
             
            	end
         | 
| 435 736 |  | 
| 436 737 |  | 
| @@ -442,54 +743,138 @@ describe PG::Connection do | |
| 442 743 | 
             
            		end
         | 
| 443 744 |  | 
| 444 745 | 
             
            		sleep 0.5
         | 
| 445 | 
            -
            		t. | 
| 746 | 
            +
            		expect( t ).to be_alive()
         | 
| 446 747 | 
             
            		@conn.cancel
         | 
| 447 748 | 
             
            		t.join
         | 
| 448 | 
            -
            		(Time.now - start). | 
| 749 | 
            +
            		expect( (Time.now - start) ).to be < 3
         | 
| 449 750 | 
             
            	end
         | 
| 450 751 |  | 
| 451 752 | 
             
            	it "described_class#block should allow a timeout" do
         | 
| 452 | 
            -
            		@conn.send_query( "select pg_sleep( | 
| 753 | 
            +
            		@conn.send_query( "select pg_sleep(1)" )
         | 
| 453 754 |  | 
| 454 755 | 
             
            		start = Time.now
         | 
| 455 | 
            -
            		@conn.block( 0. | 
| 756 | 
            +
            		@conn.block( 0.3 )
         | 
| 456 757 | 
             
            		finish = Time.now
         | 
| 457 758 |  | 
| 458 | 
            -
            		(finish - start). | 
| 759 | 
            +
            		expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
         | 
| 459 760 | 
             
            	end
         | 
| 460 761 |  | 
| 762 | 
            +
            	it "can return the default connection options" do
         | 
| 763 | 
            +
            		expect( described_class.conndefaults ).to be_a( Array )
         | 
| 764 | 
            +
            		expect( described_class.conndefaults ).to all( be_a(Hash) )
         | 
| 765 | 
            +
            		expect( described_class.conndefaults[0] ).to include( :keyword, :label, :dispchar, :dispsize )
         | 
| 766 | 
            +
            		expect( @conn.conndefaults ).to eq( described_class.conndefaults )
         | 
| 767 | 
            +
            	end
         | 
| 461 768 |  | 
| 462 | 
            -
            	it "can  | 
| 463 | 
            -
            		described_class. | 
| 464 | 
            -
             | 
| 769 | 
            +
            	it "can return the default connection options as a Hash" do
         | 
| 770 | 
            +
            		expect( described_class.conndefaults_hash ).to be_a( Hash )
         | 
| 771 | 
            +
            		expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
         | 
| 772 | 
            +
            		expect( ['5432', '54321', @port.to_s] ).to include( described_class.conndefaults_hash[:port] )
         | 
| 773 | 
            +
            		expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
         | 
| 465 774 | 
             
            	end
         | 
| 466 775 |  | 
| 776 | 
            +
            	it "can return the connection's connection options", :postgresql_93 do
         | 
| 777 | 
            +
            		expect( @conn.conninfo ).to be_a( Array )
         | 
| 778 | 
            +
            		expect( @conn.conninfo ).to all( be_a(Hash) )
         | 
| 779 | 
            +
            		expect( @conn.conninfo[0] ).to include( :keyword, :label, :dispchar, :dispsize )
         | 
| 780 | 
            +
            	end
         | 
| 467 781 |  | 
| 468 | 
            -
             | 
| 469 | 
            -
             | 
| 470 | 
            -
            		expect  | 
| 471 | 
            -
             | 
| 472 | 
            -
            		 | 
| 473 | 
            -
             | 
| 474 | 
            -
             | 
| 475 | 
            -
             | 
| 476 | 
            -
             | 
| 477 | 
            -
             | 
| 478 | 
            -
             | 
| 782 | 
            +
             | 
| 783 | 
            +
            	it "can return the connection's connection options as a Hash", :postgresql_93 do
         | 
| 784 | 
            +
            		expect( @conn.conninfo_hash ).to be_a( Hash )
         | 
| 785 | 
            +
            		expect( @conn.conninfo_hash ).to include( :user, :password, :connect_timeout, :dbname, :host )
         | 
| 786 | 
            +
            		expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
         | 
| 787 | 
            +
            	end
         | 
| 788 | 
            +
             | 
| 789 | 
            +
            	describe "connection information related to SSL" do
         | 
| 790 | 
            +
             | 
| 791 | 
            +
            		it "can retrieve connection's ssl state", :postgresql_95 do
         | 
| 792 | 
            +
            			expect( @conn.ssl_in_use? ).to be false
         | 
| 793 | 
            +
            		end
         | 
| 794 | 
            +
             | 
| 795 | 
            +
            		it "can retrieve connection's ssl attribute_names", :postgresql_95 do
         | 
| 796 | 
            +
            			expect( @conn.ssl_attribute_names ).to be_a(Array)
         | 
| 797 | 
            +
            		end
         | 
| 798 | 
            +
             | 
| 799 | 
            +
            		it "can retrieve a single ssl connection attribute", :postgresql_95 do
         | 
| 800 | 
            +
            			expect( @conn.ssl_attribute('dbname') ).to eq( nil )
         | 
| 801 | 
            +
            		end
         | 
| 802 | 
            +
             | 
| 803 | 
            +
            		it "can retrieve all connection's ssl attributes", :postgresql_95 do
         | 
| 804 | 
            +
            			expect( @conn.ssl_attributes ).to be_a_kind_of( Hash )
         | 
| 805 | 
            +
            		end
         | 
| 806 | 
            +
            	end
         | 
| 807 | 
            +
             | 
| 808 | 
            +
             | 
| 809 | 
            +
            	it "honors the connect_timeout connection parameter", :postgresql_93 do
         | 
| 810 | 
            +
            		conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
         | 
| 811 | 
            +
            		begin
         | 
| 812 | 
            +
            			expect( conn.conninfo_hash[:connect_timeout] ).to eq( "11" )
         | 
| 813 | 
            +
            		ensure
         | 
| 814 | 
            +
            			conn.finish
         | 
| 815 | 
            +
            		end
         | 
| 816 | 
            +
            	end
         | 
| 817 | 
            +
             | 
| 818 | 
            +
            	describe "deprecated password encryption method" do
         | 
| 819 | 
            +
            		it "can encrypt password for a given user" do
         | 
| 820 | 
            +
            			expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
         | 
| 821 | 
            +
            		end
         | 
| 822 | 
            +
             | 
| 823 | 
            +
            		it "raises an appropriate error if either of the required arguments is not valid" do
         | 
| 824 | 
            +
            			expect {
         | 
| 825 | 
            +
            				described_class.encrypt_password( nil, nil )
         | 
| 826 | 
            +
            			}.to raise_error( TypeError )
         | 
| 827 | 
            +
            			expect {
         | 
| 828 | 
            +
            				described_class.encrypt_password( "postgres", nil )
         | 
| 829 | 
            +
            			}.to raise_error( TypeError )
         | 
| 830 | 
            +
            			expect {
         | 
| 831 | 
            +
            				described_class.encrypt_password( nil, "postgres" )
         | 
| 832 | 
            +
            			}.to raise_error( TypeError )
         | 
| 833 | 
            +
            		end
         | 
| 834 | 
            +
            	end
         | 
| 835 | 
            +
             | 
| 836 | 
            +
            	describe "password encryption method", :postgresql_10 do
         | 
| 837 | 
            +
            		it "can encrypt without algorithm" do
         | 
| 838 | 
            +
            			expect( @conn.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
         | 
| 839 | 
            +
            			expect( @conn.encrypt_password("postgres", "postgres", nil) ).to match( /\S+/ )
         | 
| 840 | 
            +
            		end
         | 
| 841 | 
            +
             | 
| 842 | 
            +
            		it "can encrypt with algorithm" do
         | 
| 843 | 
            +
            			expect( @conn.encrypt_password("postgres", "postgres", "md5") ).to match( /md5\S+/i )
         | 
| 844 | 
            +
            			expect( @conn.encrypt_password("postgres", "postgres", "scram-sha-256") ).to match( /SCRAM-SHA-256\S+/i )
         | 
| 845 | 
            +
            		end
         | 
| 846 | 
            +
             | 
| 847 | 
            +
            		it "raises an appropriate error if either of the required arguments is not valid" do
         | 
| 848 | 
            +
            			expect {
         | 
| 849 | 
            +
            				@conn.encrypt_password( nil, nil )
         | 
| 850 | 
            +
            			}.to raise_error( TypeError )
         | 
| 851 | 
            +
            			expect {
         | 
| 852 | 
            +
            				@conn.encrypt_password( "postgres", nil )
         | 
| 853 | 
            +
            			}.to raise_error( TypeError )
         | 
| 854 | 
            +
            			expect {
         | 
| 855 | 
            +
            				@conn.encrypt_password( nil, "postgres" )
         | 
| 856 | 
            +
            			}.to raise_error( TypeError )
         | 
| 857 | 
            +
            			expect {
         | 
| 858 | 
            +
            				@conn.encrypt_password( "postgres", "postgres", :invalid )
         | 
| 859 | 
            +
            			}.to raise_error( TypeError )
         | 
| 860 | 
            +
            			expect {
         | 
| 861 | 
            +
            				@conn.encrypt_password( "postgres", "postgres", "invalid" )
         | 
| 862 | 
            +
            			}.to raise_error( PG::Error, /unrecognized/ )
         | 
| 863 | 
            +
            		end
         | 
| 479 864 | 
             
            	end
         | 
| 480 865 |  | 
| 481 866 |  | 
| 482 867 | 
             
            	it "allows fetching a column of values from a result by column number" do
         | 
| 483 868 | 
             
            		res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
         | 
| 484 | 
            -
            		res.column_values( 0 ). | 
| 485 | 
            -
            		res.column_values( 1 ). | 
| 869 | 
            +
            		expect( res.column_values( 0 ) ).to eq( %w[1 2 3] )
         | 
| 870 | 
            +
            		expect( res.column_values( 1 ) ).to eq( %w[2 3 4] )
         | 
| 486 871 | 
             
            	end
         | 
| 487 872 |  | 
| 488 873 |  | 
| 489 874 | 
             
            	it "allows fetching a column of values from a result by field name" do
         | 
| 490 875 | 
             
            		res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
         | 
| 491 | 
            -
            		res.field_values( 'column1' ). | 
| 492 | 
            -
            		res.field_values( 'column2' ). | 
| 876 | 
            +
            		expect( res.field_values( 'column1' ) ).to eq( %w[1 2 3] )
         | 
| 877 | 
            +
            		expect( res.field_values( 'column2' ) ).to eq( %w[2 3 4] )
         | 
| 493 878 | 
             
            	end
         | 
| 494 879 |  | 
| 495 880 |  | 
| @@ -517,202 +902,245 @@ describe PG::Connection do | |
| 517 902 | 
             
            	end
         | 
| 518 903 |  | 
| 519 904 |  | 
| 520 | 
            -
            	it " | 
| 905 | 
            +
            	it "handles server close while asynchronous connect" do
         | 
| 521 906 | 
             
            		serv = TCPServer.new( '127.0.0.1', 54320 )
         | 
| 522 907 | 
             
            		conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
         | 
| 523 | 
            -
            		[PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK]. | 
| 908 | 
            +
            		expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
         | 
| 524 909 | 
             
            		select( nil, [conn.socket_io], nil, 0.2 )
         | 
| 525 910 | 
             
            		serv.close
         | 
| 526 911 | 
             
            		if conn.connect_poll == PG::PGRES_POLLING_READING
         | 
| 527 912 | 
             
            			select( [conn.socket_io], nil, nil, 0.2 )
         | 
| 528 913 | 
             
            		end
         | 
| 529 | 
            -
            		conn.connect_poll. | 
| 914 | 
            +
            		expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
         | 
| 915 | 
            +
            	end
         | 
| 916 | 
            +
             | 
| 917 | 
            +
            	it "discards previous results at #discard_results" do
         | 
| 918 | 
            +
            		@conn.send_query( "select 1" )
         | 
| 919 | 
            +
            		@conn.discard_results
         | 
| 920 | 
            +
            		@conn.send_query( "select 41 as one" )
         | 
| 921 | 
            +
            		res = @conn.get_last_result
         | 
| 922 | 
            +
            		expect( res.to_a ).to eq( [{ 'one' => '41' }] )
         | 
| 530 923 | 
             
            	end
         | 
| 531 924 |  | 
| 532 | 
            -
            	it "discards previous results (if any) before waiting on  | 
| 925 | 
            +
            	it "discards previous results (if any) before waiting on #exec" do
         | 
| 926 | 
            +
            		@conn.send_query( "select 1" )
         | 
| 927 | 
            +
            		res = @conn.exec( "select 42 as one" )
         | 
| 928 | 
            +
            		expect( res.to_a ).to eq( [{ 'one' => '42' }] )
         | 
| 929 | 
            +
            	end
         | 
| 533 930 |  | 
| 534 | 
            -
            	it " | 
| 931 | 
            +
            	it "discards previous errors before waiting on #exec", :without_transaction do
         | 
| 932 | 
            +
            		@conn.send_query( "ERROR" )
         | 
| 933 | 
            +
            		res = @conn.exec( "select 43 as one" )
         | 
| 934 | 
            +
            		expect( res.to_a ).to eq( [{ 'one' => '43' }] )
         | 
| 935 | 
            +
            	end
         | 
| 936 | 
            +
             | 
| 937 | 
            +
            	it "calls the block if one is provided to #exec" do
         | 
| 535 938 | 
             
            		result = nil
         | 
| 536 | 
            -
            		@conn. | 
| 939 | 
            +
            		@conn.exec( "select 47 as one" ) do |pg_res|
         | 
| 537 940 | 
             
            			result = pg_res[0]
         | 
| 538 941 | 
             
            		end
         | 
| 539 | 
            -
            		result. | 
| 942 | 
            +
            		expect( result ).to eq( { 'one' => '47' } )
         | 
| 540 943 | 
             
            	end
         | 
| 541 944 |  | 
| 542 945 | 
             
            	it "raises a rescue-able error if #finish is called twice", :without_transaction do
         | 
| 543 946 | 
             
            		conn = PG.connect( @conninfo )
         | 
| 544 947 |  | 
| 545 948 | 
             
            		conn.finish
         | 
| 546 | 
            -
            		expect { conn.finish }.to raise_error( PG:: | 
| 949 | 
            +
            		expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
         | 
| 950 | 
            +
            	end
         | 
| 951 | 
            +
             | 
| 952 | 
            +
            	it "can use conn.reset to restart the connection" do
         | 
| 953 | 
            +
            		ios = IO.pipe
         | 
| 954 | 
            +
            		conn = PG.connect( @conninfo )
         | 
| 955 | 
            +
             | 
| 956 | 
            +
            		# Close the two pipe file descriptors, so that the file descriptor of
         | 
| 957 | 
            +
            		# newly established connection is probably distinct from the previous one.
         | 
| 958 | 
            +
            		ios.each(&:close)
         | 
| 959 | 
            +
            		conn.reset
         | 
| 960 | 
            +
             | 
| 961 | 
            +
            		# The new connection should work even when the file descriptor has changed.
         | 
| 962 | 
            +
            		expect( conn.exec("SELECT 1").values ).to eq([["1"]])
         | 
| 963 | 
            +
            		conn.close
         | 
| 547 964 | 
             
            	end
         | 
| 548 965 |  | 
| 549 | 
            -
            	it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction | 
| 966 | 
            +
            	it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction do
         | 
| 550 967 | 
             
            		conn = PG.connect( @conninfo )
         | 
| 551 968 | 
             
            		io = conn.socket_io
         | 
| 552 969 | 
             
            		conn.finish
         | 
| 553 | 
            -
            		io. | 
| 554 | 
            -
            		expect { conn.socket_io }.to raise_error( PG:: | 
| 970 | 
            +
            		expect( io ).to be_closed()
         | 
| 971 | 
            +
            		expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
         | 
| 555 972 | 
             
            	end
         | 
| 556 973 |  | 
| 557 | 
            -
            	it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction | 
| 974 | 
            +
            	it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction do
         | 
| 558 975 | 
             
            		conn = PG.connect( @conninfo )
         | 
| 559 976 | 
             
            		io = conn.socket_io
         | 
| 560 977 | 
             
            		conn.reset
         | 
| 561 | 
            -
            		io. | 
| 562 | 
            -
            		conn.socket_io. | 
| 978 | 
            +
            		expect( io ).to be_closed()
         | 
| 979 | 
            +
            		expect( conn.socket_io ).to_not equal( io )
         | 
| 563 980 | 
             
            		conn.finish
         | 
| 564 981 | 
             
            	end
         | 
| 565 982 |  | 
| 566 | 
            -
             | 
| 567 | 
            -
             | 
| 568 | 
            -
             | 
| 569 | 
            -
            		 | 
| 570 | 
            -
            			 | 
| 983 | 
            +
            	it "block should raise ConnectionBad for a closed connection" do
         | 
| 984 | 
            +
            		serv = TCPServer.new( '127.0.0.1', 54320 )
         | 
| 985 | 
            +
            		conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
         | 
| 986 | 
            +
            		while [PG::CONNECTION_STARTED, PG::CONNECTION_MADE].include?(conn.connect_poll)
         | 
| 987 | 
            +
            			sleep 0.1
         | 
| 571 988 | 
             
            		end
         | 
| 989 | 
            +
            		serv.close
         | 
| 990 | 
            +
            		expect{ conn.block }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
         | 
| 991 | 
            +
            		expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
         | 
| 992 | 
            +
            	end
         | 
| 572 993 |  | 
| 573 | 
            -
             | 
| 574 | 
            -
             | 
| 575 | 
            -
            		end
         | 
| 994 | 
            +
            	it "sets the fallback_application_name on new connections" do
         | 
| 995 | 
            +
            		conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
         | 
| 576 996 |  | 
| 577 | 
            -
            		 | 
| 578 | 
            -
             | 
| 579 | 
            -
             | 
| 580 | 
            -
             | 
| 581 | 
            -
             | 
| 582 | 
            -
            			ensure
         | 
| 583 | 
            -
            				$0 = old_0
         | 
| 584 | 
            -
            			end
         | 
| 585 | 
            -
            		end
         | 
| 997 | 
            +
            		conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
         | 
| 998 | 
            +
            		expect( conn_name ).to include( $0[0..10] )
         | 
| 999 | 
            +
            		expect( conn_name ).to include( $0[-10..-1] )
         | 
| 1000 | 
            +
            		expect( conn_name.length ).to be <= 64
         | 
| 1001 | 
            +
            	end
         | 
| 586 1002 |  | 
| 587 | 
            -
             | 
| 588 | 
            -
             | 
| 1003 | 
            +
            	it "sets a shortened fallback_application_name on new connections" do
         | 
| 1004 | 
            +
            		old_0 = $0
         | 
| 1005 | 
            +
            		begin
         | 
| 1006 | 
            +
            			$0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
         | 
| 1007 | 
            +
            			conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
         | 
| 1008 | 
            +
            			conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
         | 
| 1009 | 
            +
            			expect( conn_name ).to include( $0[0..10] )
         | 
| 1010 | 
            +
            			expect( conn_name ).to include( $0[-10..-1] )
         | 
| 1011 | 
            +
            			expect( conn_name.length ).to be <= 64
         | 
| 1012 | 
            +
            		ensure
         | 
| 1013 | 
            +
            			$0 = old_0
         | 
| 1014 | 
            +
            		end
         | 
| 1015 | 
            +
            	end
         | 
| 589 1016 |  | 
| 590 | 
            -
             | 
| 591 | 
            -
            			 | 
| 1017 | 
            +
            	it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
         | 
| 1018 | 
            +
            			"any number of arguments" do
         | 
| 592 1019 |  | 
| 593 | 
            -
             | 
| 594 | 
            -
             | 
| 595 | 
            -
            			conn.finish
         | 
| 1020 | 
            +
            		@conn.exec( 'ROLLBACK' )
         | 
| 1021 | 
            +
            		@conn.exec( 'LISTEN knees' )
         | 
| 596 1022 |  | 
| 597 | 
            -
             | 
| 598 | 
            -
             | 
| 599 | 
            -
             | 
| 600 | 
            -
            			end
         | 
| 601 | 
            -
            			@conn.exec( 'UNLISTEN knees' )
         | 
| 1023 | 
            +
            		conn = described_class.connect( @conninfo )
         | 
| 1024 | 
            +
            		conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
         | 
| 1025 | 
            +
            		conn.finish
         | 
| 602 1026 |  | 
| 603 | 
            -
             | 
| 604 | 
            -
             | 
| 605 | 
            -
            			 | 
| 1027 | 
            +
            		event, pid, msg = nil
         | 
| 1028 | 
            +
            		@conn.wait_for_notify( 10 ) do |*args|
         | 
| 1029 | 
            +
            			event, pid, msg = *args
         | 
| 606 1030 | 
             
            		end
         | 
| 1031 | 
            +
            		@conn.exec( 'UNLISTEN knees' )
         | 
| 607 1032 |  | 
| 608 | 
            -
            		 | 
| 609 | 
            -
             | 
| 610 | 
            -
             | 
| 1033 | 
            +
            		expect( event ).to eq( 'knees' )
         | 
| 1034 | 
            +
            		expect( pid ).to be_a_kind_of( Integer )
         | 
| 1035 | 
            +
            		expect( msg ).to eq( 'skirt and boots' )
         | 
| 1036 | 
            +
            	end
         | 
| 611 1037 |  | 
| 612 | 
            -
             | 
| 613 | 
            -
             | 
| 614 | 
            -
             | 
| 1038 | 
            +
            	it "accepts nil as the timeout in #wait_for_notify " do
         | 
| 1039 | 
            +
            		@conn.exec( 'ROLLBACK' )
         | 
| 1040 | 
            +
            		@conn.exec( 'LISTEN knees' )
         | 
| 615 1041 |  | 
| 616 | 
            -
             | 
| 617 | 
            -
             | 
| 618 | 
            -
             | 
| 619 | 
            -
            			end
         | 
| 620 | 
            -
            			@conn.exec( 'UNLISTEN knees' )
         | 
| 1042 | 
            +
            		conn = described_class.connect( @conninfo )
         | 
| 1043 | 
            +
            		conn.exec( %Q{NOTIFY knees} )
         | 
| 1044 | 
            +
            		conn.finish
         | 
| 621 1045 |  | 
| 622 | 
            -
             | 
| 623 | 
            -
             | 
| 1046 | 
            +
            		event, pid = nil
         | 
| 1047 | 
            +
            		@conn.wait_for_notify( nil ) do |*args|
         | 
| 1048 | 
            +
            			event, pid = *args
         | 
| 624 1049 | 
             
            		end
         | 
| 1050 | 
            +
            		@conn.exec( 'UNLISTEN knees' )
         | 
| 625 1051 |  | 
| 626 | 
            -
            		 | 
| 627 | 
            -
             | 
| 628 | 
            -
             | 
| 1052 | 
            +
            		expect( event ).to eq( 'knees' )
         | 
| 1053 | 
            +
            		expect( pid ).to be_a_kind_of( Integer )
         | 
| 1054 | 
            +
            	end
         | 
| 629 1055 |  | 
| 630 | 
            -
             | 
| 631 | 
            -
             | 
| 632 | 
            -
             | 
| 1056 | 
            +
            	it "sends nil as the payload if the notification wasn't given one" do
         | 
| 1057 | 
            +
            		@conn.exec( 'ROLLBACK' )
         | 
| 1058 | 
            +
            		@conn.exec( 'LISTEN knees' )
         | 
| 633 1059 |  | 
| 634 | 
            -
             | 
| 635 | 
            -
             | 
| 636 | 
            -
             | 
| 637 | 
            -
            			end
         | 
| 638 | 
            -
            			@conn.exec( 'UNLISTEN knees' )
         | 
| 1060 | 
            +
            		conn = described_class.connect( @conninfo )
         | 
| 1061 | 
            +
            		conn.exec( %Q{NOTIFY knees} )
         | 
| 1062 | 
            +
            		conn.finish
         | 
| 639 1063 |  | 
| 640 | 
            -
             | 
| 1064 | 
            +
            		payload = :notnil
         | 
| 1065 | 
            +
            		@conn.wait_for_notify( nil ) do |*args|
         | 
| 1066 | 
            +
            			payload = args[ 2 ]
         | 
| 641 1067 | 
             
            		end
         | 
| 1068 | 
            +
            		@conn.exec( 'UNLISTEN knees' )
         | 
| 642 1069 |  | 
| 643 | 
            -
            		 | 
| 644 | 
            -
             | 
| 1070 | 
            +
            		expect( payload ).to be_nil()
         | 
| 1071 | 
            +
            	end
         | 
| 645 1072 |  | 
| 646 | 
            -
             | 
| 647 | 
            -
            			 | 
| 1073 | 
            +
            	it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
         | 
| 1074 | 
            +
            			"two arguments" do
         | 
| 648 1075 |  | 
| 649 | 
            -
             | 
| 650 | 
            -
             | 
| 651 | 
            -
            			conn.finish
         | 
| 1076 | 
            +
            		@conn.exec( 'ROLLBACK' )
         | 
| 1077 | 
            +
            		@conn.exec( 'LISTEN knees' )
         | 
| 652 1078 |  | 
| 653 | 
            -
             | 
| 654 | 
            -
             | 
| 655 | 
            -
             | 
| 656 | 
            -
            			end
         | 
| 657 | 
            -
            			@conn.exec( 'UNLISTEN knees' )
         | 
| 1079 | 
            +
            		conn = described_class.connect( @conninfo )
         | 
| 1080 | 
            +
            		conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
         | 
| 1081 | 
            +
            		conn.finish
         | 
| 658 1082 |  | 
| 659 | 
            -
             | 
| 660 | 
            -
             | 
| 661 | 
            -
            			msg | 
| 1083 | 
            +
            		event, pid, msg = nil
         | 
| 1084 | 
            +
            		@conn.wait_for_notify( 10 ) do |arg1, arg2|
         | 
| 1085 | 
            +
            			event, pid, msg = arg1, arg2
         | 
| 662 1086 | 
             
            		end
         | 
| 1087 | 
            +
            		@conn.exec( 'UNLISTEN knees' )
         | 
| 663 1088 |  | 
| 664 | 
            -
            		 | 
| 665 | 
            -
             | 
| 1089 | 
            +
            		expect( event ).to eq( 'knees' )
         | 
| 1090 | 
            +
            		expect( pid ).to be_a_kind_of( Integer )
         | 
| 1091 | 
            +
            		expect( msg ).to be_nil()
         | 
| 1092 | 
            +
            	end
         | 
| 666 1093 |  | 
| 667 | 
            -
             | 
| 668 | 
            -
            			 | 
| 1094 | 
            +
            	it "calls the block supplied to wait_for_notify with the notify payload if it " +
         | 
| 1095 | 
            +
            			"doesn't accept arguments" do
         | 
| 669 1096 |  | 
| 670 | 
            -
             | 
| 671 | 
            -
             | 
| 672 | 
            -
            			conn.finish
         | 
| 1097 | 
            +
            		@conn.exec( 'ROLLBACK' )
         | 
| 1098 | 
            +
            		@conn.exec( 'LISTEN knees' )
         | 
| 673 1099 |  | 
| 674 | 
            -
             | 
| 675 | 
            -
             | 
| 676 | 
            -
             | 
| 677 | 
            -
            			end
         | 
| 678 | 
            -
            			@conn.exec( 'UNLISTEN knees' )
         | 
| 1100 | 
            +
            		conn = described_class.connect( @conninfo )
         | 
| 1101 | 
            +
            		conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
         | 
| 1102 | 
            +
            		conn.finish
         | 
| 679 1103 |  | 
| 680 | 
            -
             | 
| 1104 | 
            +
            		notification_received = false
         | 
| 1105 | 
            +
            		@conn.wait_for_notify( 10 ) do
         | 
| 1106 | 
            +
            			notification_received = true
         | 
| 681 1107 | 
             
            		end
         | 
| 1108 | 
            +
            		@conn.exec( 'UNLISTEN knees' )
         | 
| 682 1109 |  | 
| 683 | 
            -
            		 | 
| 684 | 
            -
             | 
| 1110 | 
            +
            		expect( notification_received ).to be_truthy()
         | 
| 1111 | 
            +
            	end
         | 
| 685 1112 |  | 
| 686 | 
            -
             | 
| 687 | 
            -
            			 | 
| 1113 | 
            +
            	it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
         | 
| 1114 | 
            +
            			"three arguments" do
         | 
| 688 1115 |  | 
| 689 | 
            -
             | 
| 690 | 
            -
             | 
| 691 | 
            -
            			conn.finish
         | 
| 1116 | 
            +
            		@conn.exec( 'ROLLBACK' )
         | 
| 1117 | 
            +
            		@conn.exec( 'LISTEN knees' )
         | 
| 692 1118 |  | 
| 693 | 
            -
             | 
| 694 | 
            -
             | 
| 695 | 
            -
             | 
| 696 | 
            -
            			end
         | 
| 697 | 
            -
            			@conn.exec( 'UNLISTEN knees' )
         | 
| 1119 | 
            +
            		conn = described_class.connect( @conninfo )
         | 
| 1120 | 
            +
            		conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
         | 
| 1121 | 
            +
            		conn.finish
         | 
| 698 1122 |  | 
| 699 | 
            -
             | 
| 700 | 
            -
             | 
| 701 | 
            -
            			msg | 
| 1123 | 
            +
            		event, pid, msg = nil
         | 
| 1124 | 
            +
            		@conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
         | 
| 1125 | 
            +
            			event, pid, msg = arg1, arg2, arg3
         | 
| 702 1126 | 
             
            		end
         | 
| 1127 | 
            +
            		@conn.exec( 'UNLISTEN knees' )
         | 
| 703 1128 |  | 
| 1129 | 
            +
            		expect( event ).to eq( 'knees' )
         | 
| 1130 | 
            +
            		expect( pid ).to be_a_kind_of( Integer )
         | 
| 1131 | 
            +
            		expect( msg ).to eq( 'skirt and boots' )
         | 
| 704 1132 | 
             
            	end
         | 
| 705 1133 |  | 
| 706 | 
            -
            	context " | 
| 1134 | 
            +
            	context "server ping", :without_transaction do
         | 
| 707 1135 |  | 
| 708 1136 | 
             
            		it "pings successfully with connection string" do
         | 
| 709 1137 | 
             
            			ping = described_class.ping(@conninfo)
         | 
| 710 | 
            -
            			ping. | 
| 1138 | 
            +
            			expect( ping ).to eq( PG::PQPING_OK )
         | 
| 711 1139 | 
             
            		end
         | 
| 712 1140 |  | 
| 713 1141 | 
             
            		it "pings using 7 arguments converted to strings" do
         | 
| 714 1142 | 
             
            			ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
         | 
| 715 | 
            -
            			ping. | 
| 1143 | 
            +
            			expect( ping ).to eq( PG::PQPING_OK )
         | 
| 716 1144 | 
             
            		end
         | 
| 717 1145 |  | 
| 718 1146 | 
             
            		it "pings using a hash of connection parameters" do
         | 
| @@ -720,7 +1148,7 @@ describe PG::Connection do | |
| 720 1148 | 
             
            				:host => 'localhost',
         | 
| 721 1149 | 
             
            				:port => @port,
         | 
| 722 1150 | 
             
            				:dbname => :test)
         | 
| 723 | 
            -
            			ping. | 
| 1151 | 
            +
            			expect( ping ).to eq( PG::PQPING_OK )
         | 
| 724 1152 | 
             
            		end
         | 
| 725 1153 |  | 
| 726 1154 | 
             
            		it "returns correct response when ping connection cannot be established" do
         | 
| @@ -728,169 +1156,359 @@ describe PG::Connection do | |
| 728 1156 | 
             
            				:host => 'localhost',
         | 
| 729 1157 | 
             
            				:port => 9999,
         | 
| 730 1158 | 
             
            				:dbname => :test)
         | 
| 731 | 
            -
            			ping. | 
| 1159 | 
            +
            			expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
         | 
| 732 1160 | 
             
            		end
         | 
| 733 1161 |  | 
| 734 | 
            -
            		it "returns  | 
| 1162 | 
            +
            		it "returns error when ping connection arguments are wrong" do
         | 
| 735 1163 | 
             
            			ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
         | 
| 736 | 
            -
            			ping. | 
| 1164 | 
            +
            			expect( ping ).to_not eq( PG::PQPING_OK )
         | 
| 737 1165 | 
             
            		end
         | 
| 738 1166 |  | 
| 1167 | 
            +
            		it "returns correct response when ping connection arguments are wrong" do
         | 
| 1168 | 
            +
            			ping = described_class.ping(
         | 
| 1169 | 
            +
            				:host => 'localhost',
         | 
| 1170 | 
            +
            				:invalid_option => 9999,
         | 
| 1171 | 
            +
            				:dbname => :test)
         | 
| 1172 | 
            +
            			expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
         | 
| 1173 | 
            +
            		end
         | 
| 739 1174 |  | 
| 740 1175 | 
             
            	end
         | 
| 741 1176 |  | 
| 742 | 
            -
            	 | 
| 743 | 
            -
            		describe "set_single_row_mode" do
         | 
| 1177 | 
            +
            	describe "set_single_row_mode" do
         | 
| 744 1178 |  | 
| 745 | 
            -
             | 
| 746 | 
            -
             | 
| 747 | 
            -
             | 
| 748 | 
            -
             | 
| 1179 | 
            +
            		it "raises an error when called at the wrong time" do
         | 
| 1180 | 
            +
            			expect {
         | 
| 1181 | 
            +
            				@conn.set_single_row_mode
         | 
| 1182 | 
            +
            			}.to raise_error(PG::Error)
         | 
| 1183 | 
            +
            		end
         | 
| 1184 | 
            +
             | 
| 1185 | 
            +
            		it "should work in single row mode" do
         | 
| 1186 | 
            +
            			@conn.send_query( "SELECT generate_series(1,10)" )
         | 
| 1187 | 
            +
            			@conn.set_single_row_mode
         | 
| 1188 | 
            +
             | 
| 1189 | 
            +
            			results = []
         | 
| 1190 | 
            +
            			loop do
         | 
| 1191 | 
            +
            				@conn.block
         | 
| 1192 | 
            +
            				res = @conn.get_result or break
         | 
| 1193 | 
            +
            				results << res
         | 
| 749 1194 | 
             
            			end
         | 
| 1195 | 
            +
            			expect( results.length ).to eq( 11 )
         | 
| 1196 | 
            +
            			results[0..-2].each do |res|
         | 
| 1197 | 
            +
            				expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
         | 
| 1198 | 
            +
            				values = res.field_values('generate_series')
         | 
| 1199 | 
            +
            				expect( values.length ).to eq( 1 )
         | 
| 1200 | 
            +
            				expect( values.first.to_i ).to be > 0
         | 
| 1201 | 
            +
            			end
         | 
| 1202 | 
            +
            			expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
         | 
| 1203 | 
            +
            			expect( results.last.ntuples ).to eq( 0 )
         | 
| 1204 | 
            +
            		end
         | 
| 750 1205 |  | 
| 751 | 
            -
             | 
| 752 | 
            -
             | 
| 753 | 
            -
             | 
| 1206 | 
            +
            		it "should receive rows before entire query is finished" do
         | 
| 1207 | 
            +
            			@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
         | 
| 1208 | 
            +
            			@conn.set_single_row_mode
         | 
| 754 1209 |  | 
| 755 | 
            -
             | 
| 756 | 
            -
             | 
| 757 | 
            -
             | 
| 758 | 
            -
             | 
| 759 | 
            -
             | 
| 760 | 
            -
            				 | 
| 761 | 
            -
            				results.length.should == 11
         | 
| 762 | 
            -
            				results[0..-2].each do |res|
         | 
| 763 | 
            -
            					res.result_status.should == PG::PGRES_SINGLE_TUPLE
         | 
| 764 | 
            -
            					values = res.field_values('generate_series')
         | 
| 765 | 
            -
            					values.length.should == 1
         | 
| 766 | 
            -
            					values.first.to_i.should > 0
         | 
| 767 | 
            -
            				end
         | 
| 768 | 
            -
            				results.last.result_status.should == PG::PGRES_TUPLES_OK
         | 
| 769 | 
            -
            				results.last.ntuples.should == 0
         | 
| 1210 | 
            +
            			start_time = Time.now
         | 
| 1211 | 
            +
            			first_row_time = nil
         | 
| 1212 | 
            +
            			loop do
         | 
| 1213 | 
            +
            				res = @conn.get_result or break
         | 
| 1214 | 
            +
            				res.check
         | 
| 1215 | 
            +
            				first_row_time = Time.now unless first_row_time
         | 
| 770 1216 | 
             
            			end
         | 
| 1217 | 
            +
            			expect( (Time.now - start_time) ).to be >= 0.9
         | 
| 1218 | 
            +
            			expect( (first_row_time - start_time) ).to be < 0.9
         | 
| 1219 | 
            +
            		end
         | 
| 771 1220 |  | 
| 772 | 
            -
             | 
| 773 | 
            -
             | 
| 774 | 
            -
             | 
| 1221 | 
            +
            		it "should receive rows before entire query fails" do
         | 
| 1222 | 
            +
            			@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
         | 
| 1223 | 
            +
            			@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
         | 
| 1224 | 
            +
            			@conn.set_single_row_mode
         | 
| 775 1225 |  | 
| 776 | 
            -
             | 
| 777 | 
            -
             | 
| 1226 | 
            +
            			first_result = nil
         | 
| 1227 | 
            +
            			expect do
         | 
| 778 1228 | 
             
            				loop do
         | 
| 779 1229 | 
             
            					res = @conn.get_result or break
         | 
| 780 1230 | 
             
            					res.check
         | 
| 781 | 
            -
            					 | 
| 1231 | 
            +
            					first_result ||= res
         | 
| 782 1232 | 
             
            				end
         | 
| 783 | 
            -
             | 
| 784 | 
            -
             | 
| 785 | 
            -
            			 | 
| 786 | 
            -
             | 
| 787 | 
            -
            			it "should receive rows before entire query fails" do
         | 
| 788 | 
            -
            				@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
         | 
| 789 | 
            -
            				@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
         | 
| 790 | 
            -
            				@conn.set_single_row_mode
         | 
| 791 | 
            -
             | 
| 792 | 
            -
            				first_result = nil
         | 
| 793 | 
            -
            				expect do
         | 
| 794 | 
            -
            					loop do
         | 
| 795 | 
            -
            						res = @conn.get_result or break
         | 
| 796 | 
            -
            						res.check
         | 
| 797 | 
            -
            						first_result ||= res
         | 
| 798 | 
            -
            					end
         | 
| 799 | 
            -
            				end.to raise_error(PG::Error)
         | 
| 800 | 
            -
            				first_result.kind_of?(PG::Result).should be_true
         | 
| 801 | 
            -
            				first_result.result_status.should == PG::PGRES_SINGLE_TUPLE
         | 
| 802 | 
            -
            			end
         | 
| 1233 | 
            +
            			end.to raise_error(PG::Error)
         | 
| 1234 | 
            +
            			expect( first_result.kind_of?(PG::Result) ).to be_truthy
         | 
| 1235 | 
            +
            			expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
         | 
| 803 1236 | 
             
            		end
         | 
| 1237 | 
            +
             | 
| 804 1238 | 
             
            	end
         | 
| 805 1239 |  | 
| 806 | 
            -
            	context "multinationalization support" | 
| 1240 | 
            +
            	context "multinationalization support" do
         | 
| 807 1241 |  | 
| 808 1242 | 
             
            		describe "rubyforge #22925: m17n support" do
         | 
| 809 1243 | 
             
            			it "should return results in the same encoding as the client (iso-8859-1)" do
         | 
| 810 | 
            -
            				 | 
| 811 | 
            -
            				@conn. | 
| 812 | 
            -
             | 
| 813 | 
            -
             | 
| 814 | 
            -
             | 
| 815 | 
            -
            				end
         | 
| 816 | 
            -
            				out_string.should == 'fantasia'
         | 
| 817 | 
            -
            				out_string.encoding.should == Encoding::ISO8859_1
         | 
| 1244 | 
            +
            				@conn.internal_encoding = 'iso8859-1'
         | 
| 1245 | 
            +
            				res = @conn.exec_params("VALUES ('fantasia')", [], 0)
         | 
| 1246 | 
            +
            				out_string = res[0]['column1']
         | 
| 1247 | 
            +
            				expect( out_string ).to eq( 'fantasia' )
         | 
| 1248 | 
            +
            				expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
         | 
| 818 1249 | 
             
            			end
         | 
| 819 1250 |  | 
| 820 1251 | 
             
            			it "should return results in the same encoding as the client (utf-8)" do
         | 
| 821 | 
            -
            				 | 
| 822 | 
            -
            				@conn. | 
| 823 | 
            -
             | 
| 824 | 
            -
             | 
| 825 | 
            -
             | 
| 826 | 
            -
            				end
         | 
| 827 | 
            -
            				out_string.should == '世界線航跡蔵'
         | 
| 828 | 
            -
            				out_string.encoding.should == Encoding::UTF_8
         | 
| 1252 | 
            +
            				@conn.internal_encoding = 'utf-8'
         | 
| 1253 | 
            +
            				res = @conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
         | 
| 1254 | 
            +
            				out_string = res[0]['column1']
         | 
| 1255 | 
            +
            				expect( out_string ).to eq( '世界線航跡蔵' )
         | 
| 1256 | 
            +
            				expect( out_string.encoding ).to eq( Encoding::UTF_8 )
         | 
| 829 1257 | 
             
            			end
         | 
| 830 1258 |  | 
| 831 1259 | 
             
            			it "should return results in the same encoding as the client (EUC-JP)" do
         | 
| 832 | 
            -
            				 | 
| 833 | 
            -
            				 | 
| 834 | 
            -
             | 
| 835 | 
            -
             | 
| 836 | 
            -
             | 
| 837 | 
            -
             | 
| 838 | 
            -
            				end
         | 
| 839 | 
            -
            				out_string.should == '世界線航跡蔵'.encode('EUC-JP')
         | 
| 840 | 
            -
            				out_string.encoding.should == Encoding::EUC_JP
         | 
| 1260 | 
            +
            				@conn.internal_encoding = 'EUC-JP'
         | 
| 1261 | 
            +
            				stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
         | 
| 1262 | 
            +
            				res = @conn.exec_params(stmt, [], 0)
         | 
| 1263 | 
            +
            				out_string = res[0]['column1']
         | 
| 1264 | 
            +
            				expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
         | 
| 1265 | 
            +
            				expect( out_string.encoding ).to eq( Encoding::EUC_JP )
         | 
| 841 1266 | 
             
            			end
         | 
| 842 1267 |  | 
| 843 1268 | 
             
            			it "returns the results in the correct encoding even if the client_encoding has " +
         | 
| 844 1269 | 
             
            			   "changed since the results were fetched" do
         | 
| 845 | 
            -
            				 | 
| 846 | 
            -
            				 | 
| 847 | 
            -
             | 
| 848 | 
            -
             | 
| 849 | 
            -
             | 
| 850 | 
            -
             | 
| 851 | 
            -
             | 
| 852 | 
            -
            				end
         | 
| 853 | 
            -
            				out_string.should == '世界線航跡蔵'.encode('EUC-JP')
         | 
| 854 | 
            -
            				out_string.encoding.should == Encoding::EUC_JP
         | 
| 1270 | 
            +
            				@conn.internal_encoding = 'EUC-JP'
         | 
| 1271 | 
            +
            				stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
         | 
| 1272 | 
            +
            				res = @conn.exec_params(stmt, [], 0)
         | 
| 1273 | 
            +
            				@conn.internal_encoding = 'utf-8'
         | 
| 1274 | 
            +
            				out_string = res[0]['column1']
         | 
| 1275 | 
            +
            				expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
         | 
| 1276 | 
            +
            				expect( out_string.encoding ).to eq( Encoding::EUC_JP )
         | 
| 855 1277 | 
             
            			end
         | 
| 856 1278 |  | 
| 857 1279 | 
             
            			it "the connection should return ASCII-8BIT when it's set to SQL_ASCII" do
         | 
| 858 1280 | 
             
            				@conn.exec "SET client_encoding TO SQL_ASCII"
         | 
| 859 | 
            -
            				@conn.internal_encoding. | 
| 1281 | 
            +
            				expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
         | 
| 860 1282 | 
             
            			end
         | 
| 861 1283 |  | 
| 862 | 
            -
            			it " | 
| 863 | 
            -
            				 | 
| 864 | 
            -
             | 
| 865 | 
            -
             | 
| 866 | 
            -
             | 
| 867 | 
            -
             | 
| 868 | 
            -
             | 
| 869 | 
            -
             | 
| 870 | 
            -
             | 
| 871 | 
            -
             | 
| 872 | 
            -
             | 
| 873 | 
            -
             | 
| 1284 | 
            +
            			it "the connection should use JOHAB dummy encoding when it's set to JOHAB" do
         | 
| 1285 | 
            +
            				@conn.set_client_encoding "JOHAB"
         | 
| 1286 | 
            +
            				val = @conn.exec("SELECT chr(x'3391'::int)").values[0][0]
         | 
| 1287 | 
            +
            				expect( val.encoding.name ).to eq( "JOHAB" )
         | 
| 1288 | 
            +
            				expect( val.unpack("H*")[0] ).to eq( "dc65" )
         | 
| 1289 | 
            +
            			end
         | 
| 1290 | 
            +
             | 
| 1291 | 
            +
            			it "can retrieve server encoding as text" do
         | 
| 1292 | 
            +
            				enc = @conn.parameter_status "server_encoding"
         | 
| 1293 | 
            +
            				expect( enc ).to eq( "UTF8" )
         | 
| 1294 | 
            +
            			end
         | 
| 1295 | 
            +
             | 
| 1296 | 
            +
            			it "can retrieve server encoding as ruby encoding" do
         | 
| 1297 | 
            +
            				expect( @conn.external_encoding ).to eq( Encoding::UTF_8 )
         | 
| 874 1298 | 
             
            			end
         | 
| 875 1299 |  | 
| 876 1300 | 
             
            			it "uses the client encoding for escaped string" do
         | 
| 877 | 
            -
            				original = " | 
| 1301 | 
            +
            				original = "Möhre to 'scape".encode( "utf-16be" )
         | 
| 878 1302 | 
             
            				@conn.set_client_encoding( "euc_jp" )
         | 
| 879 1303 | 
             
            				escaped  = @conn.escape( original )
         | 
| 880 | 
            -
            				escaped.encoding. | 
| 1304 | 
            +
            				expect( escaped.encoding ).to eq( Encoding::EUC_JP )
         | 
| 1305 | 
            +
            				expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::EUC_JP) )
         | 
| 881 1306 | 
             
            			end
         | 
| 882 1307 |  | 
| 883 | 
            -
            			it " | 
| 884 | 
            -
            				original = " | 
| 1308 | 
            +
            			it "uses the client encoding for escaped literal" do
         | 
| 1309 | 
            +
            				original = "Möhre to 'scape".encode( "utf-16be" )
         | 
| 1310 | 
            +
            				@conn.set_client_encoding( "euc_jp" )
         | 
| 885 1311 | 
             
            				escaped  = @conn.escape_literal( original )
         | 
| 886 | 
            -
            				escaped. | 
| 1312 | 
            +
            				expect( escaped.encoding ).to eq( Encoding::EUC_JP )
         | 
| 1313 | 
            +
            				expect( escaped ).to eq( "'Möhre to ''scape'".encode(Encoding::EUC_JP) )
         | 
| 1314 | 
            +
            			end
         | 
| 1315 | 
            +
             | 
| 1316 | 
            +
            			it "uses the client encoding for escaped identifier" do
         | 
| 1317 | 
            +
            				original = "Möhre to 'scape".encode( "utf-16le" )
         | 
| 1318 | 
            +
            				@conn.set_client_encoding( "euc_jp" )
         | 
| 1319 | 
            +
            				escaped  = @conn.escape_identifier( original )
         | 
| 1320 | 
            +
            				expect( escaped.encoding ).to eq( Encoding::EUC_JP )
         | 
| 1321 | 
            +
            				expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
         | 
| 1322 | 
            +
            			end
         | 
| 1323 | 
            +
             | 
| 1324 | 
            +
            			it "uses the client encoding for quote_ident" do
         | 
| 1325 | 
            +
            				original = "Möhre to 'scape".encode( "utf-16le" )
         | 
| 1326 | 
            +
            				@conn.set_client_encoding( "euc_jp" )
         | 
| 1327 | 
            +
            				escaped  = @conn.quote_ident( original )
         | 
| 1328 | 
            +
            				expect( escaped.encoding ).to eq( Encoding::EUC_JP )
         | 
| 1329 | 
            +
            				expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
         | 
| 1330 | 
            +
            			end
         | 
| 1331 | 
            +
             | 
| 1332 | 
            +
            			it "uses the previous string encoding for escaped string" do
         | 
| 1333 | 
            +
            				original = "Möhre to 'scape".encode( "iso-8859-1" )
         | 
| 1334 | 
            +
            				@conn.set_client_encoding( "euc_jp" )
         | 
| 1335 | 
            +
            				escaped  = described_class.escape( original )
         | 
| 1336 | 
            +
            				expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
         | 
| 1337 | 
            +
            				expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::ISO8859_1) )
         | 
| 1338 | 
            +
            			end
         | 
| 1339 | 
            +
             | 
| 1340 | 
            +
            			it "uses the previous string encoding for quote_ident" do
         | 
| 1341 | 
            +
            				original = "Möhre to 'scape".encode( "iso-8859-1" )
         | 
| 1342 | 
            +
            				@conn.set_client_encoding( "euc_jp" )
         | 
| 1343 | 
            +
            				escaped  = described_class.quote_ident( original )
         | 
| 1344 | 
            +
            				expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
         | 
| 1345 | 
            +
            				expect( escaped.encode ).to eq( "\"Möhre to 'scape\"".encode(Encoding::ISO8859_1) )
         | 
| 1346 | 
            +
            			end
         | 
| 1347 | 
            +
             | 
| 1348 | 
            +
            			it "raises appropriate error if set_client_encoding is called with invalid arguments" do
         | 
| 1349 | 
            +
            				expect { @conn.set_client_encoding( "invalid" ) }.to raise_error(PG::Error, /invalid value/)
         | 
| 1350 | 
            +
            				expect { @conn.set_client_encoding( :invalid ) }.to raise_error(TypeError)
         | 
| 1351 | 
            +
            				expect { @conn.set_client_encoding( nil ) }.to raise_error(TypeError)
         | 
| 1352 | 
            +
            			end
         | 
| 1353 | 
            +
             | 
| 1354 | 
            +
            			it "can use an encoding with high index for client encoding" do
         | 
| 1355 | 
            +
            				# Allocate a lot of encoding indices, so that MRI's ENCODING_INLINE_MAX is exceeded
         | 
| 1356 | 
            +
            				unless Encoding.name_list.include?("pgtest-0")
         | 
| 1357 | 
            +
            					256.times do |eidx|
         | 
| 1358 | 
            +
            						Encoding::UTF_8.replicate("pgtest-#{eidx}")
         | 
| 1359 | 
            +
            					end
         | 
| 1360 | 
            +
            				end
         | 
| 1361 | 
            +
             | 
| 1362 | 
            +
            				# Now allocate the JOHAB encoding with an unusual high index
         | 
| 1363 | 
            +
            				@conn.set_client_encoding "JOHAB"
         | 
| 1364 | 
            +
            				val = @conn.exec("SELECT chr(x'3391'::int)").values[0][0]
         | 
| 1365 | 
            +
            				expect( val.encoding.name ).to eq( "JOHAB" )
         | 
| 1366 | 
            +
            			end
         | 
| 1367 | 
            +
             | 
| 1368 | 
            +
            		end
         | 
| 1369 | 
            +
             | 
| 1370 | 
            +
            		describe "respect and convert character encoding of input strings" do
         | 
| 1371 | 
            +
            			before :each do
         | 
| 1372 | 
            +
            				@conn.internal_encoding = __ENCODING__
         | 
| 1373 | 
            +
            			end
         | 
| 1374 | 
            +
             | 
| 1375 | 
            +
            			it "should convert query string and parameters to #exec_params" do
         | 
| 1376 | 
            +
            				r = @conn.exec_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
         | 
| 1377 | 
            +
            				                  ['grün'.encode('utf-16be'), 'grün'.encode('iso-8859-1')])
         | 
| 1378 | 
            +
            				expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
         | 
| 1379 | 
            +
            			end
         | 
| 1380 | 
            +
             | 
| 1381 | 
            +
            			it "should convert query string to #exec" do
         | 
| 1382 | 
            +
            				r = @conn.exec("SELECT 'grün'".encode("utf-16be"))
         | 
| 1383 | 
            +
            				expect( r.values ).to eq( [['grün']] )
         | 
| 1384 | 
            +
            			end
         | 
| 1385 | 
            +
             | 
| 1386 | 
            +
            			it "should convert strings and parameters to #prepare and #exec_prepared" do
         | 
| 1387 | 
            +
            				@conn.prepare("weiß1".encode("utf-16be"), "VALUES( $1, $2, $1=$2, 'grün')".encode("cp850"))
         | 
| 1388 | 
            +
            				r = @conn.exec_prepared("weiß1".encode("utf-32le"),
         | 
| 1389 | 
            +
            				                ['grün'.encode('cp936'), 'grün'.encode('utf-16le')])
         | 
| 1390 | 
            +
            				expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
         | 
| 1391 | 
            +
            			end
         | 
| 1392 | 
            +
             | 
| 1393 | 
            +
            			it "should convert strings to #describe_prepared" do
         | 
| 1394 | 
            +
            				@conn.prepare("weiß2", "VALUES(123)")
         | 
| 1395 | 
            +
            				r = @conn.describe_prepared("weiß2".encode("utf-16be"))
         | 
| 1396 | 
            +
            				expect( r.nfields ).to eq( 1 )
         | 
| 1397 | 
            +
            			end
         | 
| 1398 | 
            +
             | 
| 1399 | 
            +
            			it "should convert strings to #describe_portal" do
         | 
| 1400 | 
            +
            				@conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
         | 
| 1401 | 
            +
            				r = @conn.describe_portal("cörsör".encode("utf-16le"))
         | 
| 1402 | 
            +
            				expect( r.nfields ).to eq( 3 )
         | 
| 1403 | 
            +
            			end
         | 
| 1404 | 
            +
             | 
| 1405 | 
            +
            			it "should convert query string to #send_query" do
         | 
| 1406 | 
            +
            				@conn.send_query("VALUES('grün')".encode("utf-16be"))
         | 
| 1407 | 
            +
            				expect( @conn.get_last_result.values ).to eq( [['grün']] )
         | 
| 1408 | 
            +
            			end
         | 
| 1409 | 
            +
             | 
| 1410 | 
            +
            			it "should convert query string and parameters to #send_query_params" do
         | 
| 1411 | 
            +
            				@conn.send_query_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
         | 
| 1412 | 
            +
            				                  ['grün'.encode('utf-32be'), 'grün'.encode('iso-8859-1')])
         | 
| 1413 | 
            +
            				expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
         | 
| 887 1414 | 
             
            			end
         | 
| 1415 | 
            +
             | 
| 1416 | 
            +
            			it "should convert strings and parameters to #send_prepare and #send_query_prepared" do
         | 
| 1417 | 
            +
            				@conn.send_prepare("weiß3".encode("iso-8859-1"), "VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16be"))
         | 
| 1418 | 
            +
            				@conn.get_last_result
         | 
| 1419 | 
            +
            				@conn.send_query_prepared("weiß3".encode("utf-32le"),
         | 
| 1420 | 
            +
            				                ['grün'.encode('utf-16le'), 'grün'.encode('iso-8859-1')])
         | 
| 1421 | 
            +
            				expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
         | 
| 1422 | 
            +
            			end
         | 
| 1423 | 
            +
             | 
| 1424 | 
            +
            			it "should convert strings to #send_describe_prepared" do
         | 
| 1425 | 
            +
            				@conn.prepare("weiß4", "VALUES(123)")
         | 
| 1426 | 
            +
            				@conn.send_describe_prepared("weiß4".encode("utf-16be"))
         | 
| 1427 | 
            +
            				expect( @conn.get_last_result.nfields ).to eq( 1 )
         | 
| 1428 | 
            +
            			end
         | 
| 1429 | 
            +
             | 
| 1430 | 
            +
            			it "should convert strings to #send_describe_portal" do
         | 
| 1431 | 
            +
            				@conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
         | 
| 1432 | 
            +
            				@conn.send_describe_portal("cörsör".encode("utf-16le"))
         | 
| 1433 | 
            +
            				expect( @conn.get_last_result.nfields ).to eq( 3 )
         | 
| 1434 | 
            +
            			end
         | 
| 1435 | 
            +
             | 
| 1436 | 
            +
            			it "should convert error string to #put_copy_end" do
         | 
| 1437 | 
            +
            				@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
         | 
| 1438 | 
            +
            				@conn.exec( "COPY copytable FROM STDIN" )
         | 
| 1439 | 
            +
            				@conn.put_copy_end("grün".encode("utf-16be"))
         | 
| 1440 | 
            +
            				expect( @conn.get_result.error_message ).to match(/grün/)
         | 
| 1441 | 
            +
            				@conn.get_result
         | 
| 1442 | 
            +
            			end
         | 
| 1443 | 
            +
            		end
         | 
| 1444 | 
            +
             | 
| 1445 | 
            +
            		it "rejects command strings with zero bytes" do
         | 
| 1446 | 
            +
            			expect{ @conn.exec( "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1447 | 
            +
            			expect{ @conn.exec_params( "SELECT 1;\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1448 | 
            +
            			expect{ @conn.prepare( "abc\x00", "SELECT 1;" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1449 | 
            +
            			expect{ @conn.prepare( "abc", "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1450 | 
            +
            			expect{ @conn.exec_prepared( "abc\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1451 | 
            +
            			expect{ @conn.describe_prepared( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1452 | 
            +
            			expect{ @conn.describe_portal( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1453 | 
            +
            			expect{ @conn.send_query( "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1454 | 
            +
            			expect{ @conn.send_query_params( "SELECT 1;\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1455 | 
            +
            			expect{ @conn.send_prepare( "abc\x00", "SELECT 1;" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1456 | 
            +
            			expect{ @conn.send_prepare( "abc", "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1457 | 
            +
            			expect{ @conn.send_query_prepared( "abc\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1458 | 
            +
            			expect{ @conn.send_describe_prepared( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1459 | 
            +
            			expect{ @conn.send_describe_portal( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1460 | 
            +
            		end
         | 
| 1461 | 
            +
             | 
| 1462 | 
            +
            		it "rejects query params with zero bytes" do
         | 
| 1463 | 
            +
            			expect{ @conn.exec_params( "SELECT 1;\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1464 | 
            +
            			expect{ @conn.exec_prepared( "abc\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1465 | 
            +
            			expect{ @conn.send_query_params( "SELECT 1;\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1466 | 
            +
            			expect{ @conn.send_query_prepared( "abc\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1467 | 
            +
            		end
         | 
| 1468 | 
            +
             | 
| 1469 | 
            +
            		it "rejects string with zero bytes in escape" do
         | 
| 1470 | 
            +
            			expect{ @conn.escape( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1471 | 
            +
            		end
         | 
| 1472 | 
            +
             | 
| 1473 | 
            +
            		it "rejects string with zero bytes in escape_literal" do
         | 
| 1474 | 
            +
            			expect{ @conn.escape_literal( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1475 | 
            +
            		end
         | 
| 1476 | 
            +
             | 
| 1477 | 
            +
            		it "rejects string with zero bytes in escape_identifier" do
         | 
| 1478 | 
            +
            			expect{ @conn.escape_identifier( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1479 | 
            +
            		end
         | 
| 1480 | 
            +
             | 
| 1481 | 
            +
            		it "rejects string with zero bytes in quote_ident" do
         | 
| 1482 | 
            +
            			expect{ described_class.quote_ident( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 1483 | 
            +
            		end
         | 
| 1484 | 
            +
             | 
| 1485 | 
            +
            		it "rejects Array with string with zero bytes" do
         | 
| 1486 | 
            +
            			original = ["xyz", "2\x00"]
         | 
| 1487 | 
            +
            			expect{ described_class.quote_ident( original ) }.to raise_error(ArgumentError, /null byte/)
         | 
| 888 1488 | 
             
            		end
         | 
| 889 1489 |  | 
| 1490 | 
            +
            		it "can quote bigger strings with quote_ident" do
         | 
| 1491 | 
            +
            			original = "'01234567\"" * 100
         | 
| 1492 | 
            +
            			escaped = described_class.quote_ident( original )
         | 
| 1493 | 
            +
            			expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
         | 
| 1494 | 
            +
            		end
         | 
| 1495 | 
            +
             | 
| 1496 | 
            +
            		it "can quote Arrays with quote_ident" do
         | 
| 1497 | 
            +
            			original = "'01234567\""
         | 
| 1498 | 
            +
            			escaped = described_class.quote_ident( [original]*3 )
         | 
| 1499 | 
            +
            			expected = ["\"" + original.gsub("\"", "\"\"") + "\""] * 3
         | 
| 1500 | 
            +
            			expect( escaped ).to eq( expected.join(".") )
         | 
| 1501 | 
            +
            		end
         | 
| 1502 | 
            +
             | 
| 1503 | 
            +
            		it "will raise a TypeError for invalid arguments to quote_ident" do
         | 
| 1504 | 
            +
            			expect{ described_class.quote_ident( nil ) }.to raise_error(TypeError)
         | 
| 1505 | 
            +
            			expect{ described_class.quote_ident( [nil] ) }.to raise_error(TypeError)
         | 
| 1506 | 
            +
            			expect{ described_class.quote_ident( [['a']] ) }.to raise_error(TypeError)
         | 
| 1507 | 
            +
            		end
         | 
| 890 1508 |  | 
| 891 1509 | 
             
            		describe "Ruby 1.9.x default_internal encoding" do
         | 
| 892 1510 |  | 
| 893 | 
            -
            			it "honors the Encoding.default_internal if it's set and the synchronous interface is used" do
         | 
| 1511 | 
            +
            			it "honors the Encoding.default_internal if it's set and the synchronous interface is used", :without_transaction do
         | 
| 894 1512 | 
             
            				@conn.transaction do |txn_conn|
         | 
| 895 1513 | 
             
            					txn_conn.internal_encoding = Encoding::ISO8859_1
         | 
| 896 1514 | 
             
            					txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
         | 
| @@ -899,13 +1517,14 @@ describe PG::Connection do | |
| 899 1517 |  | 
| 900 1518 | 
             
            				begin
         | 
| 901 1519 | 
             
            					prev_encoding = Encoding.default_internal
         | 
| 902 | 
            -
            					Encoding.default_internal = Encoding:: | 
| 1520 | 
            +
            					Encoding.default_internal = Encoding::ISO8859_2
         | 
| 903 1521 |  | 
| 904 1522 | 
             
            					conn = PG.connect( @conninfo )
         | 
| 905 | 
            -
            					conn.internal_encoding. | 
| 1523 | 
            +
            					expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
         | 
| 906 1524 | 
             
            					res = conn.exec( "SELECT foo FROM defaultinternaltest" )
         | 
| 907 | 
            -
            					res[0]['foo'].encoding. | 
| 1525 | 
            +
            					expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
         | 
| 908 1526 | 
             
            				ensure
         | 
| 1527 | 
            +
            					conn.exec( "DROP TABLE defaultinternaltest" )
         | 
| 909 1528 | 
             
            					conn.finish if conn
         | 
| 910 1529 | 
             
            					Encoding.default_internal = prev_encoding
         | 
| 911 1530 | 
             
            				end
         | 
| @@ -918,7 +1537,7 @@ describe PG::Connection do | |
| 918 1537 |  | 
| 919 1538 | 
             
            					@conn.set_default_encoding
         | 
| 920 1539 |  | 
| 921 | 
            -
            					@conn.internal_encoding. | 
| 1540 | 
            +
            					expect( @conn.internal_encoding ).to eq( Encoding::KOI8_R )
         | 
| 922 1541 | 
             
            				ensure
         | 
| 923 1542 | 
             
            					Encoding.default_internal = prev_encoding
         | 
| 924 1543 | 
             
            				end
         | 
| @@ -939,7 +1558,7 @@ describe PG::Connection do | |
| 939 1558 | 
             
            					query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
         | 
| 940 1559 | 
             
            					conn.exec( query )
         | 
| 941 1560 | 
             
            				rescue => err
         | 
| 942 | 
            -
            					err.message.encoding. | 
| 1561 | 
            +
            					expect( err.message.encoding ).to eq( Encoding::ISO8859_15 )
         | 
| 943 1562 | 
             
            				else
         | 
| 944 1563 | 
             
            					fail "No exception raised?!"
         | 
| 945 1564 | 
             
            				end
         | 
| @@ -948,7 +1567,22 @@ describe PG::Connection do | |
| 948 1567 | 
             
            			conn.finish if conn
         | 
| 949 1568 | 
             
            		end
         | 
| 950 1569 |  | 
| 951 | 
            -
            		it " | 
| 1570 | 
            +
            		it "handles clearing result in or after set_notice_receiver" do
         | 
| 1571 | 
            +
            			r = nil
         | 
| 1572 | 
            +
            			@conn.set_notice_receiver do |result|
         | 
| 1573 | 
            +
            				r = result
         | 
| 1574 | 
            +
            				expect( r.cleared? ).to eq(false)
         | 
| 1575 | 
            +
            			end
         | 
| 1576 | 
            +
            			@conn.exec "do $$ BEGIN RAISE NOTICE 'foo'; END; $$ LANGUAGE plpgsql;"
         | 
| 1577 | 
            +
            			sleep 0.2
         | 
| 1578 | 
            +
            			expect( r ).to be_a( PG::Result )
         | 
| 1579 | 
            +
            			expect( r.cleared? ).to eq(true)
         | 
| 1580 | 
            +
            			expect( r.autoclear? ).to eq(true)
         | 
| 1581 | 
            +
            			r.clear
         | 
| 1582 | 
            +
            			@conn.set_notice_receiver
         | 
| 1583 | 
            +
            		end
         | 
| 1584 | 
            +
             | 
| 1585 | 
            +
            		it "receives properly encoded messages in the notice callbacks" do
         | 
| 952 1586 | 
             
            			[:receiver, :processor].each do |kind|
         | 
| 953 1587 | 
             
            				notices = []
         | 
| 954 1588 | 
             
            				@conn.internal_encoding = 'utf-8'
         | 
| @@ -966,19 +1600,18 @@ describe PG::Connection do | |
| 966 1600 | 
             
            					@conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
         | 
| 967 1601 | 
             
            				end
         | 
| 968 1602 |  | 
| 969 | 
            -
            				notices.length. | 
| 1603 | 
            +
            				expect( notices.length ).to eq( 3 )
         | 
| 970 1604 | 
             
            				notices.each do |notice|
         | 
| 971 | 
            -
            					notice. | 
| 972 | 
            -
            					notice.encoding. | 
| 1605 | 
            +
            					expect( notice ).to match( /^NOTICE:.*世界線航跡蔵/ )
         | 
| 1606 | 
            +
            					expect( notice.encoding ).to eq( Encoding::UTF_8 )
         | 
| 973 1607 | 
             
            				end
         | 
| 974 1608 | 
             
            				@conn.set_notice_receiver
         | 
| 975 1609 | 
             
            				@conn.set_notice_processor
         | 
| 976 1610 | 
             
            			end
         | 
| 977 1611 | 
             
            		end
         | 
| 978 1612 |  | 
| 979 | 
            -
            		it "receives properly encoded text from wait_for_notify", : | 
| 1613 | 
            +
            		it "receives properly encoded text from wait_for_notify", :without_transaction do
         | 
| 980 1614 | 
             
            			@conn.internal_encoding = 'utf-8'
         | 
| 981 | 
            -
            			@conn.exec( 'ROLLBACK' )
         | 
| 982 1615 | 
             
            			@conn.exec( 'LISTEN "Möhre"' )
         | 
| 983 1616 | 
             
            			@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
         | 
| 984 1617 | 
             
            			event, pid, msg = nil
         | 
| @@ -987,37 +1620,330 @@ describe PG::Connection do | |
| 987 1620 | 
             
            			end
         | 
| 988 1621 | 
             
            			@conn.exec( 'UNLISTEN "Möhre"' )
         | 
| 989 1622 |  | 
| 990 | 
            -
            			event. | 
| 991 | 
            -
            			event.encoding. | 
| 992 | 
            -
            			 | 
| 993 | 
            -
            			msg. | 
| 1623 | 
            +
            			expect( event ).to eq( "Möhre" )
         | 
| 1624 | 
            +
            			expect( event.encoding ).to eq( Encoding::UTF_8 )
         | 
| 1625 | 
            +
            			expect( pid ).to be_a_kind_of(Integer)
         | 
| 1626 | 
            +
            			expect( msg ).to eq( '世界線航跡蔵' )
         | 
| 1627 | 
            +
            			expect( msg.encoding ).to eq( Encoding::UTF_8 )
         | 
| 994 1628 | 
             
            		end
         | 
| 995 1629 |  | 
| 996 | 
            -
            		it "returns properly encoded text from notifies", : | 
| 1630 | 
            +
            		it "returns properly encoded text from notifies", :without_transaction do
         | 
| 997 1631 | 
             
            			@conn.internal_encoding = 'utf-8'
         | 
| 998 | 
            -
            			@conn.exec( 'ROLLBACK' )
         | 
| 999 1632 | 
             
            			@conn.exec( 'LISTEN "Möhre"' )
         | 
| 1000 1633 | 
             
            			@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
         | 
| 1001 1634 | 
             
            			@conn.exec( 'UNLISTEN "Möhre"' )
         | 
| 1002 1635 |  | 
| 1003 1636 | 
             
            			notification = @conn.notifies
         | 
| 1004 | 
            -
            			notification[:relname]. | 
| 1005 | 
            -
            			notification[:relname].encoding. | 
| 1006 | 
            -
            			notification[:extra]. | 
| 1007 | 
            -
            			notification[:extra].encoding. | 
| 1008 | 
            -
            			notification[:be_pid]. | 
| 1637 | 
            +
            			expect( notification[:relname] ).to eq( "Möhre" )
         | 
| 1638 | 
            +
            			expect( notification[:relname].encoding ).to eq( Encoding::UTF_8 )
         | 
| 1639 | 
            +
            			expect( notification[:extra] ).to eq( '世界線航跡蔵' )
         | 
| 1640 | 
            +
            			expect( notification[:extra].encoding ).to eq( Encoding::UTF_8 )
         | 
| 1641 | 
            +
            			expect( notification[:be_pid] ).to be > 0
         | 
| 1009 1642 | 
             
            		end
         | 
| 1010 1643 | 
             
            	end
         | 
| 1011 1644 |  | 
| 1012 | 
            -
            	context "OS thread support" | 
| 1013 | 
            -
            		it " | 
| 1645 | 
            +
            	context "OS thread support" do
         | 
| 1646 | 
            +
            		it "Connection#exec shouldn't block a second thread" do
         | 
| 1014 1647 | 
             
            			t = Thread.new do
         | 
| 1015 1648 | 
             
            				@conn.exec( "select pg_sleep(1)" )
         | 
| 1016 1649 | 
             
            			end
         | 
| 1017 1650 |  | 
| 1018 1651 | 
             
            			sleep 0.5
         | 
| 1019 | 
            -
            			t. | 
| 1652 | 
            +
            			expect( t ).to be_alive()
         | 
| 1020 1653 | 
             
            			t.join
         | 
| 1021 1654 | 
             
            		end
         | 
| 1655 | 
            +
             | 
| 1656 | 
            +
            		it "Connection.new shouldn't block a second thread" do
         | 
| 1657 | 
            +
            			serv = nil
         | 
| 1658 | 
            +
            			t = Thread.new do
         | 
| 1659 | 
            +
            				serv = TCPServer.new( '127.0.0.1', 54320 )
         | 
| 1660 | 
            +
            				expect {
         | 
| 1661 | 
            +
            					described_class.new( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
         | 
| 1662 | 
            +
            				}.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
         | 
| 1663 | 
            +
            			end
         | 
| 1664 | 
            +
             | 
| 1665 | 
            +
            			sleep 0.5
         | 
| 1666 | 
            +
            			expect( t ).to be_alive()
         | 
| 1667 | 
            +
            			serv.close
         | 
| 1668 | 
            +
            			t.join
         | 
| 1669 | 
            +
            		end
         | 
| 1670 | 
            +
            	end
         | 
| 1671 | 
            +
             | 
| 1672 | 
            +
            	describe "type casting" do
         | 
| 1673 | 
            +
            		it "should raise an error on invalid param mapping" do
         | 
| 1674 | 
            +
            			expect{
         | 
| 1675 | 
            +
            				@conn.exec_params( "SELECT 1", [], nil, :invalid )
         | 
| 1676 | 
            +
            			}.to raise_error(TypeError)
         | 
| 1677 | 
            +
            		end
         | 
| 1678 | 
            +
             | 
| 1679 | 
            +
            		it "should return nil if no type mapping is set" do
         | 
| 1680 | 
            +
            			expect( @conn.type_map_for_queries ).to be_kind_of(PG::TypeMapAllStrings)
         | 
| 1681 | 
            +
            			expect( @conn.type_map_for_results ).to be_kind_of(PG::TypeMapAllStrings)
         | 
| 1682 | 
            +
            		end
         | 
| 1683 | 
            +
             | 
| 1684 | 
            +
            		it "shouldn't type map params unless requested" do
         | 
| 1685 | 
            +
            			if @conn.server_version < 100000
         | 
| 1686 | 
            +
            				expect{
         | 
| 1687 | 
            +
            					@conn.exec_params( "SELECT $1", [5] )
         | 
| 1688 | 
            +
            				}.to raise_error(PG::IndeterminateDatatype)
         | 
| 1689 | 
            +
            			else
         | 
| 1690 | 
            +
            				# PostgreSQL-10 maps to TEXT type (OID 25)
         | 
| 1691 | 
            +
            				expect( @conn.exec_params( "SELECT $1", [5] ).ftype(0)).to eq(25)
         | 
| 1692 | 
            +
            			end
         | 
| 1693 | 
            +
            		end
         | 
| 1694 | 
            +
             | 
| 1695 | 
            +
            		it "should raise an error on invalid encoder to put_copy_data" do
         | 
| 1696 | 
            +
            			expect{
         | 
| 1697 | 
            +
            				@conn.put_copy_data [1], :invalid
         | 
| 1698 | 
            +
            			}.to raise_error(TypeError)
         | 
| 1699 | 
            +
            		end
         | 
| 1700 | 
            +
             | 
| 1701 | 
            +
            		it "can type cast parameters to put_copy_data with explicit encoder" do
         | 
| 1702 | 
            +
            			tm = PG::TypeMapByColumn.new [nil]
         | 
| 1703 | 
            +
            			row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
         | 
| 1704 | 
            +
             | 
| 1705 | 
            +
            			@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
         | 
| 1706 | 
            +
            			@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
         | 
| 1707 | 
            +
            				@conn.put_copy_data [1], row_encoder
         | 
| 1708 | 
            +
            				@conn.put_copy_data ["2"], row_encoder
         | 
| 1709 | 
            +
            			end
         | 
| 1710 | 
            +
             | 
| 1711 | 
            +
            			@conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
         | 
| 1712 | 
            +
            				@conn.put_copy_data [3]
         | 
| 1713 | 
            +
            				@conn.put_copy_data ["4"]
         | 
| 1714 | 
            +
            			end
         | 
| 1715 | 
            +
             | 
| 1716 | 
            +
            			res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
         | 
| 1717 | 
            +
            			expect( res.values ).to eq( [["1"], ["2"], ["3"], ["4"]] )
         | 
| 1718 | 
            +
            		end
         | 
| 1719 | 
            +
             | 
| 1720 | 
            +
            		context "with default query type map" do
         | 
| 1721 | 
            +
            			before :each do
         | 
| 1722 | 
            +
            				@conn2 = described_class.new(@conninfo)
         | 
| 1723 | 
            +
            				tm = PG::TypeMapByClass.new
         | 
| 1724 | 
            +
            				tm[Integer] = PG::TextEncoder::Integer.new oid: 20
         | 
| 1725 | 
            +
            				@conn2.type_map_for_queries = tm
         | 
| 1726 | 
            +
             | 
| 1727 | 
            +
            				row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
         | 
| 1728 | 
            +
            				@conn2.encoder_for_put_copy_data = row_encoder
         | 
| 1729 | 
            +
            			end
         | 
| 1730 | 
            +
            			after :each do
         | 
| 1731 | 
            +
            				@conn2.close
         | 
| 1732 | 
            +
            			end
         | 
| 1733 | 
            +
             | 
| 1734 | 
            +
            			it "should respect a type mapping for params and it's OID and format code" do
         | 
| 1735 | 
            +
            				res = @conn2.exec_params( "SELECT $1", [5] )
         | 
| 1736 | 
            +
            				expect( res.values ).to eq( [["5"]] )
         | 
| 1737 | 
            +
            				expect( res.ftype(0) ).to eq( 20 )
         | 
| 1738 | 
            +
            			end
         | 
| 1739 | 
            +
             | 
| 1740 | 
            +
            			it "should return the current type mapping" do
         | 
| 1741 | 
            +
            				expect( @conn2.type_map_for_queries ).to be_kind_of(PG::TypeMapByClass)
         | 
| 1742 | 
            +
            			end
         | 
| 1743 | 
            +
             | 
| 1744 | 
            +
            			it "should work with arbitrary number of params in conjunction with type casting" do
         | 
| 1745 | 
            +
            				begin
         | 
| 1746 | 
            +
            					3.step( 12, 0.2 ) do |exp|
         | 
| 1747 | 
            +
            						num_params = (2 ** exp).to_i
         | 
| 1748 | 
            +
            						sql = num_params.times.map{|n| "$#{n+1}" }.join(",")
         | 
| 1749 | 
            +
            						params = num_params.times.to_a
         | 
| 1750 | 
            +
            						res = @conn2.exec_params( "SELECT #{sql}", params )
         | 
| 1751 | 
            +
            						expect( res.nfields ).to eq( num_params )
         | 
| 1752 | 
            +
            						expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
         | 
| 1753 | 
            +
            					end
         | 
| 1754 | 
            +
            				rescue PG::ProgramLimitExceeded
         | 
| 1755 | 
            +
            					# Stop silently as soon the server complains about too many params
         | 
| 1756 | 
            +
            				end
         | 
| 1757 | 
            +
            			end
         | 
| 1758 | 
            +
             | 
| 1759 | 
            +
            			it "can process #copy_data input queries with row encoder and respects character encoding" do
         | 
| 1760 | 
            +
            				@conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
         | 
| 1761 | 
            +
            				@conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
         | 
| 1762 | 
            +
            					@conn2.put_copy_data [1]
         | 
| 1763 | 
            +
            					@conn2.put_copy_data ["Möhre".encode("utf-16le")]
         | 
| 1764 | 
            +
            				end
         | 
| 1765 | 
            +
             | 
| 1766 | 
            +
            				res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
         | 
| 1767 | 
            +
            				expect( res.values ).to eq( [["1"], ["Möhre"]] )
         | 
| 1768 | 
            +
            			end
         | 
| 1769 | 
            +
            		end
         | 
| 1770 | 
            +
             | 
| 1771 | 
            +
            		context "with default result type map" do
         | 
| 1772 | 
            +
            			before :each do
         | 
| 1773 | 
            +
            				@conn2 = described_class.new(@conninfo)
         | 
| 1774 | 
            +
            				tm = PG::TypeMapByOid.new
         | 
| 1775 | 
            +
            				tm.add_coder PG::TextDecoder::Integer.new oid: 23, format: 0
         | 
| 1776 | 
            +
            				@conn2.type_map_for_results = tm
         | 
| 1777 | 
            +
             | 
| 1778 | 
            +
            				row_decoder = PG::TextDecoder::CopyRow.new
         | 
| 1779 | 
            +
            				@conn2.decoder_for_get_copy_data = row_decoder
         | 
| 1780 | 
            +
            			end
         | 
| 1781 | 
            +
            			after :each do
         | 
| 1782 | 
            +
            				@conn2.close
         | 
| 1783 | 
            +
            			end
         | 
| 1784 | 
            +
             | 
| 1785 | 
            +
            			it "should respect a type mapping for result" do
         | 
| 1786 | 
            +
            				res = @conn2.exec_params( "SELECT $1::INT", ["5"] )
         | 
| 1787 | 
            +
            				expect( res.values ).to eq( [[5]] )
         | 
| 1788 | 
            +
            			end
         | 
| 1789 | 
            +
             | 
| 1790 | 
            +
            			it "should return the current type mapping" do
         | 
| 1791 | 
            +
            				expect( @conn2.type_map_for_results ).to be_kind_of(PG::TypeMapByOid)
         | 
| 1792 | 
            +
            			end
         | 
| 1793 | 
            +
             | 
| 1794 | 
            +
            			it "should work with arbitrary number of params in conjunction with type casting" do
         | 
| 1795 | 
            +
            				begin
         | 
| 1796 | 
            +
            					3.step( 12, 0.2 ) do |exp|
         | 
| 1797 | 
            +
            						num_params = (2 ** exp).to_i
         | 
| 1798 | 
            +
            						sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
         | 
| 1799 | 
            +
            						params = num_params.times.to_a
         | 
| 1800 | 
            +
            						res = @conn2.exec_params( "SELECT #{sql}", params )
         | 
| 1801 | 
            +
            						expect( res.nfields ).to eq( num_params )
         | 
| 1802 | 
            +
            						expect( res.values ).to eq( [num_params.times.to_a] )
         | 
| 1803 | 
            +
            					end
         | 
| 1804 | 
            +
            				rescue PG::ProgramLimitExceeded
         | 
| 1805 | 
            +
            					# Stop silently as soon the server complains about too many params
         | 
| 1806 | 
            +
            				end
         | 
| 1807 | 
            +
            			end
         | 
| 1808 | 
            +
             | 
| 1809 | 
            +
            			it "can process #copy_data output with row decoder and respects character encoding" do
         | 
| 1810 | 
            +
            				@conn2.internal_encoding = Encoding::ISO8859_1
         | 
| 1811 | 
            +
            				rows = []
         | 
| 1812 | 
            +
            				@conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
         | 
| 1813 | 
            +
            					while row=@conn2.get_copy_data
         | 
| 1814 | 
            +
            						rows << row
         | 
| 1815 | 
            +
            					end
         | 
| 1816 | 
            +
            				end
         | 
| 1817 | 
            +
            				expect( rows.last.last.encoding ).to eq( Encoding::ISO8859_1 )
         | 
| 1818 | 
            +
            				expect( rows ).to eq( [["1"], ["Möhre".encode("iso-8859-1")]] )
         | 
| 1819 | 
            +
            			end
         | 
| 1820 | 
            +
             | 
| 1821 | 
            +
            			it "can type cast #copy_data output with explicit decoder" do
         | 
| 1822 | 
            +
            				tm = PG::TypeMapByColumn.new [PG::TextDecoder::Integer.new]
         | 
| 1823 | 
            +
            				row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
         | 
| 1824 | 
            +
            				rows = []
         | 
| 1825 | 
            +
            				@conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT", row_decoder ) do |res|
         | 
| 1826 | 
            +
            					while row=@conn.get_copy_data
         | 
| 1827 | 
            +
            						rows << row
         | 
| 1828 | 
            +
            					end
         | 
| 1829 | 
            +
            				end
         | 
| 1830 | 
            +
            				@conn.copy_data( "COPY (SELECT 3 UNION ALL SELECT 4) TO STDOUT" ) do |res|
         | 
| 1831 | 
            +
            					while row=@conn.get_copy_data( false, row_decoder )
         | 
| 1832 | 
            +
            						rows << row
         | 
| 1833 | 
            +
            					end
         | 
| 1834 | 
            +
            				end
         | 
| 1835 | 
            +
            				expect( rows ).to eq( [[1], [2], [3], [4]] )
         | 
| 1836 | 
            +
            			end
         | 
| 1837 | 
            +
            		end
         | 
| 1838 | 
            +
            	end
         | 
| 1839 | 
            +
             | 
| 1840 | 
            +
            	describe :field_name_type do
         | 
| 1841 | 
            +
            		before :each do
         | 
| 1842 | 
            +
            			@conn2 = PG.connect(@conninfo)
         | 
| 1843 | 
            +
            		end
         | 
| 1844 | 
            +
            		after :each do
         | 
| 1845 | 
            +
            			@conn2.close
         | 
| 1846 | 
            +
            		end
         | 
| 1847 | 
            +
             | 
| 1848 | 
            +
            		it "uses string field names per default" do
         | 
| 1849 | 
            +
            			expect(@conn2.field_name_type).to eq(:string)
         | 
| 1850 | 
            +
            		end
         | 
| 1851 | 
            +
             | 
| 1852 | 
            +
            		it "can set string field names" do
         | 
| 1853 | 
            +
            			@conn2.field_name_type = :string
         | 
| 1854 | 
            +
            			expect(@conn2.field_name_type).to eq(:string)
         | 
| 1855 | 
            +
            			res = @conn2.exec("SELECT 1 as az")
         | 
| 1856 | 
            +
            			expect(res.field_name_type).to eq(:string)
         | 
| 1857 | 
            +
            			expect(res.fields).to eq(["az"])
         | 
| 1858 | 
            +
            		end
         | 
| 1859 | 
            +
             | 
| 1860 | 
            +
            		it "can set symbol field names" do
         | 
| 1861 | 
            +
            			@conn2.field_name_type = :symbol
         | 
| 1862 | 
            +
            			expect(@conn2.field_name_type).to eq(:symbol)
         | 
| 1863 | 
            +
            			res = @conn2.exec("SELECT 1 as az")
         | 
| 1864 | 
            +
            			expect(res.field_name_type).to eq(:symbol)
         | 
| 1865 | 
            +
            			expect(res.fields).to eq([:az])
         | 
| 1866 | 
            +
            		end
         | 
| 1867 | 
            +
             | 
| 1868 | 
            +
            		it "can't set invalid values" do
         | 
| 1869 | 
            +
            			expect{ @conn2.field_name_type = :sym }.to raise_error(ArgumentError, /invalid argument :sym/)
         | 
| 1870 | 
            +
            			expect{ @conn2.field_name_type = "symbol" }.to raise_error(ArgumentError, /invalid argument "symbol"/)
         | 
| 1871 | 
            +
            		end
         | 
| 1872 | 
            +
            	end
         | 
| 1873 | 
            +
             | 
| 1874 | 
            +
            	describe "deprecated forms of methods" do
         | 
| 1875 | 
            +
            		if PG::VERSION < "2"
         | 
| 1876 | 
            +
            			it "should forward exec to exec_params" do
         | 
| 1877 | 
            +
            				res = @conn.exec("VALUES($1::INT)", [7]).values
         | 
| 1878 | 
            +
            				expect(res).to eq( [["7"]] )
         | 
| 1879 | 
            +
            				res = @conn.exec("VALUES($1::INT)", [7], 1).values
         | 
| 1880 | 
            +
            				expect(res).to eq( [[[7].pack("N")]] )
         | 
| 1881 | 
            +
            				res = @conn.exec("VALUES(8)", [], 1).values
         | 
| 1882 | 
            +
            				expect(res).to eq( [[[8].pack("N")]] )
         | 
| 1883 | 
            +
            			end
         | 
| 1884 | 
            +
             | 
| 1885 | 
            +
            			it "should forward exec_params to exec" do
         | 
| 1886 | 
            +
            				res = @conn.exec_params("VALUES(3); VALUES(4)").values
         | 
| 1887 | 
            +
            				expect(res).to eq( [["4"]] )
         | 
| 1888 | 
            +
            				res = @conn.exec_params("VALUES(3); VALUES(4)", nil).values
         | 
| 1889 | 
            +
            				expect(res).to eq( [["4"]] )
         | 
| 1890 | 
            +
            				res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil).values
         | 
| 1891 | 
            +
            				expect(res).to eq( [["4"]] )
         | 
| 1892 | 
            +
            				res = @conn.exec_params("VALUES(3); VALUES(4)", nil, 1).values
         | 
| 1893 | 
            +
            				expect(res).to eq( [["4"]] )
         | 
| 1894 | 
            +
            				res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil).values
         | 
| 1895 | 
            +
            				expect(res).to eq( [["4"]] )
         | 
| 1896 | 
            +
            				expect{
         | 
| 1897 | 
            +
            					@conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil, nil).values
         | 
| 1898 | 
            +
            				}.to raise_error(ArgumentError)
         | 
| 1899 | 
            +
            			end
         | 
| 1900 | 
            +
             | 
| 1901 | 
            +
            			it "should forward send_query to send_query_params" do
         | 
| 1902 | 
            +
            				@conn.send_query("VALUES($1)", [5])
         | 
| 1903 | 
            +
            				expect(@conn.get_last_result.values).to eq( [["5"]] )
         | 
| 1904 | 
            +
            			end
         | 
| 1905 | 
            +
             | 
| 1906 | 
            +
            			it "should respond_to socket", :unix do
         | 
| 1907 | 
            +
            				expect( @conn.socket ).to eq( @conn.socket_io.fileno )
         | 
| 1908 | 
            +
            			end
         | 
| 1909 | 
            +
            		else
         | 
| 1910 | 
            +
            			# Method forwarding removed by PG::VERSION >= "2"
         | 
| 1911 | 
            +
            			it "shouldn't forward exec to exec_params" do
         | 
| 1912 | 
            +
            				expect do
         | 
| 1913 | 
            +
            					@conn.exec("VALUES($1::INT)", [7])
         | 
| 1914 | 
            +
            				end.to raise_error(ArgumentError)
         | 
| 1915 | 
            +
            			end
         | 
| 1916 | 
            +
             | 
| 1917 | 
            +
            			it "shouldn't forward exec_params to exec" do
         | 
| 1918 | 
            +
            				expect do
         | 
| 1919 | 
            +
            					@conn.exec_params("VALUES(3); VALUES(4)")
         | 
| 1920 | 
            +
            				end.to raise_error(ArgumentError)
         | 
| 1921 | 
            +
            			end
         | 
| 1922 | 
            +
             | 
| 1923 | 
            +
            			it "shouldn't forward send_query to send_query_params" do
         | 
| 1924 | 
            +
            				expect do
         | 
| 1925 | 
            +
            					@conn.send_query("VALUES($1)", [5])
         | 
| 1926 | 
            +
            				end.to raise_error(ArgumentError)
         | 
| 1927 | 
            +
            			end
         | 
| 1928 | 
            +
             | 
| 1929 | 
            +
            			it "shouldn't forward async_exec_params to async_exec" do
         | 
| 1930 | 
            +
            				expect do
         | 
| 1931 | 
            +
            					@conn.async_exec_params("VALUES(1)")
         | 
| 1932 | 
            +
            				end.to raise_error(ArgumentError)
         | 
| 1933 | 
            +
            			end
         | 
| 1934 | 
            +
             | 
| 1935 | 
            +
            			it "shouldn't respond_to socket" do
         | 
| 1936 | 
            +
            				expect do
         | 
| 1937 | 
            +
            					@conn.socket
         | 
| 1938 | 
            +
            				end.to raise_error(ArgumentError)
         | 
| 1939 | 
            +
            			end
         | 
| 1940 | 
            +
            		end
         | 
| 1941 | 
            +
             | 
| 1942 | 
            +
            		it "shouldn't forward send_query_params to send_query" do
         | 
| 1943 | 
            +
            			expect{ @conn.send_query_params("VALUES(4)").values }
         | 
| 1944 | 
            +
            				.to raise_error(ArgumentError)
         | 
| 1945 | 
            +
            			expect{ @conn.send_query_params("VALUES(4)", nil).values }
         | 
| 1946 | 
            +
            				.to raise_error(TypeError)
         | 
| 1947 | 
            +
            		end
         | 
| 1022 1948 | 
             
            	end
         | 
| 1023 1949 | 
             
            end
         |