pg 0.18.0.pre20141017160319 → 0.18.0.pre20141117110243

Sign up to get free protection for your applications and to get access to all the features.
data/ext/util.c CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * util.c - Utils for ruby-pg
3
- * $Id: util.c,v 117fb5c5eed7 2014/10/15 18:36:39 lars $
3
+ * $Id$
4
4
  *
5
5
  */
6
6
 
data/lib/pg.rb CHANGED
@@ -10,7 +10,7 @@ rescue LoadError
10
10
 
11
11
  # Set the PATH environment variable, so that libpq.dll can be found.
12
12
  old_path = ENV['PATH']
13
- ENV['PATH'] = "#{old_path};#{File.expand_path("../#{RUBY_PLATFORM}", __FILE__)}"
13
+ ENV['PATH'] = "#{File.expand_path("../#{RUBY_PLATFORM}", __FILE__)};#{old_path}"
14
14
  require "#{major_minor}/pg_ext"
15
15
  ENV['PATH'] = old_path
16
16
  else
@@ -24,10 +24,10 @@ end
24
24
  module PG
25
25
 
26
26
  # Library version
27
- VERSION = '0.18.0'
27
+ VERSION = '0.18.0.pre20141117110243'
28
28
 
29
29
  # VCS revision
30
- REVISION = %q$Revision: 57d770944b5d $
30
+ REVISION = %q$Revision$
31
31
 
32
32
  class NotAllCopyDataRetrieved < PG::Error
33
33
  end
@@ -304,7 +304,7 @@ end
304
304
  # # Execute a query. The Integer param value is typecasted internally by PG::BinaryEncoder::Int8.
305
305
  # # The format of the parameter is set to 1 (binary) and the OID of this parameter is set to 20 (int8).
306
306
  # res = conn.exec_params( "SELECT $1", [5] )
307
- class PG::BasicTypeMapForQueries < PG::TypeMapByMriType
307
+ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
308
308
  include PG::BasicTypeRegistry
309
309
 
310
310
  def initialize(connection)
@@ -323,7 +323,7 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByMriType
323
323
  end
324
324
 
325
325
  def populate_encoder_list
326
- DEFAULT_TYPE_MAP.each do |mri_type, selector|
326
+ DEFAULT_TYPE_MAP.each do |klass, selector|
327
327
  if Array === selector
328
328
  format, name, oid_name = selector
329
329
  coder = coder_by_name(format, :encoder, name).dup
@@ -332,9 +332,9 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByMriType
332
332
  else
333
333
  coder.oid = 0
334
334
  end
335
- self[mri_type] = coder
335
+ self[klass] = coder
336
336
  else
337
- self[mri_type] = selector
337
+ self[klass] = selector
338
338
  end
339
339
  end
340
340
  end
@@ -351,25 +351,25 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByMriType
351
351
  while elem.kind_of?(Array)
352
352
  elem = elem.first
353
353
  end
354
- @array_encoders_by_klass[elem.class] || @anyarray_encoder
354
+ @array_encoders_by_klass[elem.class] ||
355
+ elem.class.ancestors.lazy.map{|ancestor| @array_encoders_by_klass[ancestor] }.find{|a| a } ||
356
+ @anyarray_encoder
355
357
  end
356
358
 
357
359
  DEFAULT_TYPE_MAP = {
358
- 'T_TRUE'.freeze => [1, 'bool', 'bool'],
359
- 'T_FALSE'.freeze => [1, 'bool', 'bool'],
360
+ TrueClass => [1, 'bool', 'bool'],
361
+ FalseClass => [1, 'bool', 'bool'],
360
362
  # We use text format and no type OID for numbers, because setting the OID can lead
361
363
  # to unnecessary type conversions on server side.
362
- 'T_FIXNUM'.freeze => [0, 'int8'],
363
- 'T_BIGNUM'.freeze => [0, 'int8'],
364
- 'T_FLOAT'.freeze => [0, 'float8'],
365
- 'T_ARRAY'.freeze => :get_array_type,
364
+ Integer => [0, 'int8'],
365
+ Float => [0, 'float8'],
366
+ Array => :get_array_type,
366
367
  }
367
368
 
368
369
  DEFAULT_ARRAY_TYPE_MAP = {
369
370
  TrueClass => [0, '_bool'],
370
371
  FalseClass => [0, '_bool'],
371
- Fixnum => [0, '_int8'],
372
- Bignum => [0, '_int8'],
372
+ Integer => [0, '_int8'],
373
373
  String => [0, '_text'],
374
374
  Float => [0, '_float8'],
375
375
  }
@@ -3,6 +3,15 @@
3
3
  module PG
4
4
 
5
5
  class Coder
6
+
7
+ module BinaryFormatting
8
+ Params = { format: 1 }
9
+ def initialize( params={} )
10
+ super(params.merge(Params))
11
+ end
12
+ end
13
+
14
+
6
15
  # Create a new coder object based on the attribute Hash.
7
16
  def initialize(params={})
8
17
  params.each do |key, val|
@@ -24,7 +24,7 @@ rescue LoadError # 1.8 support
24
24
  raise
25
25
  end
26
26
 
27
- SCRIPT_VERSION = %q$Id: disk_usage_report.rb,v 76ebae01c937 2013/03/26 17:50:02 ged $
27
+ SCRIPT_VERSION = %q$Id$
28
28
 
29
29
 
30
30
  ### Gather data and output it to $stdout.
@@ -32,7 +32,7 @@ end
32
32
  ### Optionally run in a continuous loop, displaying deltas.
33
33
  ###
34
34
  class Stats
35
- VERSION = %q$Id: pg_statistics.rb,v 36ca5b412583 2012/04/17 23:32:25 mahlon $
35
+ VERSION = %q$Id$
36
36
 
37
37
  def initialize( opts )
38
38
  @opts = opts
@@ -36,7 +36,7 @@ end
36
36
  ###
37
37
  class PGMonitor
38
38
 
39
- VERSION = %q$Id: replication_monitor.rb,v 36ca5b412583 2012/04/17 23:32:25 mahlon $
39
+ VERSION = %q$Id$
40
40
 
41
41
  # When to consider a slave as 'behind', measured in WAL segments.
42
42
  # The default WAL segment size is 16, so we'll alert after
@@ -343,11 +343,13 @@ RSpec.configure do |config|
343
343
  PG::Connection.instance_methods.map( &:to_sym ).include?( :escape_literal )
344
344
 
345
345
  if !PG.respond_to?( :library_version )
346
- config.filter_run_excluding( :postgresql_91, :postgresql_92, :postgresql_93 )
346
+ config.filter_run_excluding( :postgresql_91, :postgresql_92, :postgresql_93, :postgresql_94 )
347
347
  elsif PG.library_version < 90200
348
- config.filter_run_excluding( :postgresql_92, :postgresql_93 )
348
+ config.filter_run_excluding( :postgresql_92, :postgresql_93, :postgresql_94 )
349
349
  elsif PG.library_version < 90300
350
- config.filter_run_excluding( :postgresql_93 )
350
+ config.filter_run_excluding( :postgresql_93, :postgresql_94 )
351
+ elsif PG.library_version < 90400
352
+ config.filter_run_excluding( :postgresql_94 )
351
353
  end
352
354
  end
353
355
 
@@ -73,7 +73,7 @@ describe 'Basic type mapping' do
73
73
  end
74
74
 
75
75
  after :each do
76
- @conn.type_map_for_results = nil
76
+ @conn.type_map_for_results = PG::TypeMapAllStrings.new
77
77
  end
78
78
 
79
79
  it "should do boolean type conversions" do
@@ -157,6 +157,17 @@ describe PG::Connection do
157
157
  expect( res[0]['n'] ).to eq( '1' )
158
158
  end
159
159
 
160
+ it "can retrieve it's connection parameters for the established connection" do
161
+ expect( @conn.db ).to eq( "test" )
162
+ expect( @conn.user ).to be_a_kind_of( String )
163
+ expect( @conn.pass ).to eq( "" )
164
+ expect( @conn.host ).to eq( "localhost" )
165
+ # TODO: Not sure why libpq returns a NULL ptr instead of "127.0.0.1"
166
+ expect( @conn.hostaddr ).to eq( nil ) if @conn.server_version >= 9_04_00
167
+ expect( @conn.port ).to eq( 54321 )
168
+ expect( @conn.tty ).to eq( "" )
169
+ expect( @conn.options ).to eq( "" )
170
+ end
160
171
 
161
172
  EXPECTED_TRACE_OUTPUT = %{
162
173
  To backend> Msg Q
@@ -1293,8 +1304,8 @@ describe PG::Connection do
1293
1304
  end
1294
1305
 
1295
1306
  it "should return nil if no type mapping is set" do
1296
- expect( @conn.type_map_for_queries ).to be_nil
1297
- expect( @conn.type_map_for_results ).to be_nil
1307
+ expect( @conn.type_map_for_queries ).to be_kind_of(PG::TypeMapAllStrings)
1308
+ expect( @conn.type_map_for_results ).to be_kind_of(PG::TypeMapAllStrings)
1298
1309
  end
1299
1310
 
1300
1311
  it "shouldn't type map params unless requested" do
@@ -1331,8 +1342,8 @@ describe PG::Connection do
1331
1342
  context "with default query type map" do
1332
1343
  before :each do
1333
1344
  @conn2 = described_class.new(@conninfo)
1334
- tm = PG::TypeMapByMriType.new
1335
- tm['T_FIXNUM'] = PG::TextEncoder::Integer.new oid: 20
1345
+ tm = PG::TypeMapByClass.new
1346
+ tm[Integer] = PG::TextEncoder::Integer.new oid: 20
1336
1347
  @conn2.type_map_for_queries = tm
1337
1348
 
1338
1349
  row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
@@ -1349,7 +1360,7 @@ describe PG::Connection do
1349
1360
  end
1350
1361
 
1351
1362
  it "should return the current type mapping" do
1352
- expect( @conn2.type_map_for_queries ).to be_kind_of(PG::TypeMapByMriType)
1363
+ expect( @conn2.type_map_for_queries ).to be_kind_of(PG::TypeMapByClass)
1353
1364
  end
1354
1365
 
1355
1366
  it "should work with arbitrary number of params in conjunction with type casting" do
@@ -21,6 +21,80 @@ describe PG::Result do
21
21
  expect( list ).to eq [['1', '2']]
22
22
  end
23
23
 
24
+ it "yields a row as an Enumerator" do
25
+ res = @conn.exec("SELECT 1 AS a, 2 AS b")
26
+ e = res.each_row
27
+ expect( e ).to be_a_kind_of(Enumerator)
28
+ pending "Rubinius doesn't define RETURN_SIZED_ENUMERATOR()" if RUBY_ENGINE=='rbx'
29
+ expect( e.size ).to eq( 1 )
30
+ expect( e.to_a ).to eq [['1', '2']]
31
+ end
32
+
33
+ it "yields a row as an Enumerator of hashs" do
34
+ res = @conn.exec("SELECT 1 AS a, 2 AS b")
35
+ e = res.each
36
+ expect( e ).to be_a_kind_of(Enumerator)
37
+ pending "Rubinius doesn't define RETURN_SIZED_ENUMERATOR()" if RUBY_ENGINE=='rbx'
38
+ expect( e.size ).to eq( 1 )
39
+ expect( e.to_a ).to eq [{'a'=>'1', 'b'=>'2'}]
40
+ end
41
+
42
+ context "result streaming", :postgresql_92 do
43
+ it "can iterate over all tuples in single row mode" do
44
+ @conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
45
+ @conn.set_single_row_mode
46
+ expect(
47
+ @conn.get_result.stream_each.to_a
48
+ ).to eq(
49
+ [{'a'=>"2"}, {'a'=>"3"}, {'a'=>"4"}]
50
+ )
51
+ expect(
52
+ @conn.get_result.enum_for(:stream_each).to_a
53
+ ).to eq(
54
+ [{'b'=>"1", 'c'=>"5"}, {'b'=>"1", 'c'=>"6"}]
55
+ )
56
+ expect( @conn.get_result ).to be_nil
57
+ end
58
+
59
+ it "can iterate over all rows in single row mode" do
60
+ @conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
61
+ @conn.set_single_row_mode
62
+ expect(
63
+ @conn.get_result.enum_for(:stream_each_row).to_a
64
+ ).to eq(
65
+ [["2"], ["3"], ["4"]]
66
+ )
67
+ expect(
68
+ @conn.get_result.stream_each_row.to_a
69
+ ).to eq(
70
+ [["1", "5"], ["1", "6"]]
71
+ )
72
+ expect( @conn.get_result ).to be_nil
73
+ end
74
+
75
+ it "complains when not in single row mode" do
76
+ @conn.send_query( "SELECT generate_series(2,4)" )
77
+ expect{
78
+ @conn.get_result.stream_each_row.to_a
79
+ }.to raise_error(PG::InvalidResultStatus, /not in single row mode/)
80
+ end
81
+
82
+ it "complains when intersected with get_result" do
83
+ @conn.send_query( "SELECT 1" )
84
+ @conn.set_single_row_mode
85
+ expect{
86
+ @conn.get_result.stream_each_row.each{ @conn.get_result }
87
+ }.to raise_error(PG::NoResultError, /no result received/)
88
+ end
89
+
90
+ it "raises server errors" do
91
+ @conn.send_query( "SELECT 0/0" )
92
+ expect{
93
+ @conn.get_result.stream_each_row.to_a
94
+ }.to raise_error(PG::DivisionByZero)
95
+ end
96
+ end
97
+
24
98
  it "inserts nil AS NULL and return NULL as nil" do
25
99
  res = @conn.exec("SELECT $1::int AS n", [nil])
26
100
  expect( res[0]['n'] ).to be_nil()
@@ -335,14 +409,14 @@ describe PG::Result do
335
409
 
336
410
  it "should allow reading, assigning and diabling type conversions" do
337
411
  res = @conn.exec( "SELECT 123" )
338
- expect( res.type_map ).to be_nil
412
+ expect( res.type_map ).to be_kind_of(PG::TypeMapAllStrings)
339
413
  res.type_map = PG::TypeMapByColumn.new [textdec_int]
340
414
  expect( res.type_map ).to be_an_instance_of(PG::TypeMapByColumn)
341
415
  expect( res.type_map.coders ).to eq( [textdec_int] )
342
416
  res.type_map = PG::TypeMapByColumn.new [textdec_float]
343
417
  expect( res.type_map.coders ).to eq( [textdec_float] )
344
- res.type_map = nil
345
- expect( res.type_map ).to be_nil
418
+ res.type_map = PG::TypeMapAllStrings.new
419
+ expect( res.type_map ).to be_kind_of(PG::TypeMapAllStrings)
346
420
  end
347
421
 
348
422
  it "should be applied to all value retrieving methods" do
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env rspec
2
+ # encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ require 'pg'
7
+
8
+
9
+ describe PG::TypeMapByClass do
10
+
11
+ let!(:textenc_int){ PG::TextEncoder::Integer.new name: 'INT4', oid: 23 }
12
+ let!(:textenc_float){ PG::TextEncoder::Float.new name: 'FLOAT8', oid: 701 }
13
+ let!(:textenc_string){ PG::TextEncoder::String.new name: 'TEXT', oid: 25 }
14
+ let!(:binaryenc_int){ PG::BinaryEncoder::Int8.new name: 'INT8', oid: 20, format: 1 }
15
+ let!(:pass_through_type) do
16
+ type = Class.new(PG::SimpleEncoder) do
17
+ def encode(*v)
18
+ v.inspect
19
+ end
20
+ end.new
21
+ type.oid = 25
22
+ type.format = 0
23
+ type.name = 'pass_through'
24
+ type
25
+ end
26
+
27
+ let!(:tm) do
28
+ tm = PG::TypeMapByClass.new
29
+ tm[Integer] = binaryenc_int
30
+ tm[Float] = textenc_float
31
+ tm[Symbol] = pass_through_type
32
+ tm
33
+ end
34
+
35
+ let!(:raise_class) do
36
+ Class.new
37
+ end
38
+
39
+ let!(:derived_tm) do
40
+ tm = Class.new(PG::TypeMapByClass) do
41
+ def array_type_map_for(value)
42
+ PG::TextEncoder::Array.new name: '_INT4', oid: 1007, elements_type: PG::TextEncoder::Integer.new
43
+ end
44
+ end.new
45
+ tm[Integer] = proc{|value| textenc_int }
46
+ tm[raise_class] = proc{|value| /invalid/ }
47
+ tm[Array] = :array_type_map_for
48
+ tm
49
+ end
50
+
51
+ it "should retrieve all conversions" do
52
+ expect( tm.coders ).to eq( {
53
+ Integer => binaryenc_int,
54
+ Float => textenc_float,
55
+ Symbol => pass_through_type,
56
+ } )
57
+ end
58
+
59
+ it "should retrieve particular conversions" do
60
+ expect( tm[Integer] ).to eq(binaryenc_int)
61
+ expect( tm[Float] ).to eq(textenc_float)
62
+ expect( tm[Bignum] ).to be_nil
63
+ expect( derived_tm[raise_class] ).to be_kind_of(Proc)
64
+ expect( derived_tm[Array] ).to eq(:array_type_map_for)
65
+ end
66
+
67
+ it "should allow deletion of coders" do
68
+ tm[Integer] = nil
69
+ expect( tm[Integer] ).to be_nil
70
+ expect( tm.coders ).to eq( {
71
+ Float => textenc_float,
72
+ Symbol => pass_through_type,
73
+ } )
74
+ end
75
+
76
+ it "forwards query param conversions to the #default_type_map" do
77
+ tm1 = PG::TypeMapByColumn.new( [textenc_int, nil, nil] )
78
+
79
+ tm2 = PG::TypeMapByClass.new
80
+ tm2[Integer] = PG::TextEncoder::Integer.new name: 'INT2', oid: 21
81
+ tm2.default_type_map = tm1
82
+
83
+ res = @conn.exec_params( "SELECT $1, $2, $3::TEXT", ['1', 2, 3], 0, tm2 )
84
+
85
+ expect( res.ftype(0) ).to eq( 23 ) # tm1
86
+ expect( res.ftype(1) ).to eq( 21 ) # tm2
87
+ expect( res.getvalue(0,2) ).to eq( "3" ) # TypeMapAllStrings
88
+ end
89
+
90
+ #
91
+ # Decoding Examples
92
+ #
93
+
94
+ it "should raise an error when used for results" do
95
+ res = @conn.exec_params( "SELECT 1", [], 1 )
96
+ expect{ res.type_map = tm }.to raise_error(NotImplementedError, /not suitable to map result values/)
97
+ end
98
+
99
+ #
100
+ # Encoding Examples
101
+ #
102
+
103
+ it "should allow mixed type conversions" do
104
+ res = @conn.exec_params( "SELECT $1, $2, $3", [5, 1.23, :TestSymbol], 0, tm )
105
+ expect( res.values ).to eq([['5', '1.23', '[:TestSymbol]']])
106
+ expect( res.ftype(0) ).to eq(20)
107
+ end
108
+
109
+ it "should expire the cache after changes to the coders" do
110
+ res = @conn.exec_params( "SELECT $1", [5], 0, tm )
111
+ expect( res.ftype(0) ).to eq(20)
112
+
113
+ tm[Integer] = textenc_int
114
+
115
+ res = @conn.exec_params( "SELECT $1", [5], 0, tm )
116
+ expect( res.ftype(0) ).to eq(23)
117
+ end
118
+
119
+ it "should allow mixed type conversions with derived type map" do
120
+ res = @conn.exec_params( "SELECT $1, $2", [6, [7]], 0, derived_tm )
121
+ expect( res.values ).to eq([['6', '{7}']])
122
+ expect( res.ftype(0) ).to eq(23)
123
+ expect( res.ftype(1) ).to eq(1007)
124
+ end
125
+
126
+ it "should raise TypeError with derived type map" do
127
+ expect{
128
+ @conn.exec_params( "SELECT $1", [raise_class.new], 0, derived_tm )
129
+ }.to raise_error(TypeError, /invalid type Regexp/)
130
+ end
131
+
132
+ it "should raise error on invalid coder object" do
133
+ tm[TrueClass] = "dummy"
134
+ expect{
135
+ res = @conn.exec_params( "SELECT $1", [true], 0, tm )
136
+ }.to raise_error(NoMethodError, /undefined method.*call.*dummy/)
137
+ end
138
+ end