pg 0.15.1 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,89 @@
1
+ /*
2
+ * pg_errors.c - Definition and lookup of error classes.
3
+ *
4
+ */
5
+
6
+ #include "pg.h"
7
+
8
+ VALUE rb_hErrors;
9
+ VALUE rb_ePGerror;
10
+ VALUE rb_eServerError;
11
+ VALUE rb_eUnableToSend;
12
+ VALUE rb_eConnectionBad;
13
+
14
+ static VALUE
15
+ define_error_class(const char *name, const char *baseclass_code)
16
+ {
17
+ VALUE baseclass = rb_eServerError;
18
+ if(baseclass_code)
19
+ {
20
+ baseclass = rb_hash_aref( rb_hErrors, rb_str_new2(baseclass_code) );
21
+ }
22
+ return rb_define_class_under( rb_mPG, name, baseclass );
23
+ }
24
+
25
+ static void
26
+ register_error_class(const char *code, VALUE klass)
27
+ {
28
+ rb_hash_aset( rb_hErrors, rb_str_new2(code), klass );
29
+ }
30
+
31
+ /* Find a proper error class for the given SQLSTATE string
32
+ */
33
+ VALUE
34
+ lookup_error_class(const char *sqlstate)
35
+ {
36
+ VALUE klass;
37
+
38
+ if(sqlstate)
39
+ {
40
+ /* Find the proper error class by the 5-characters SQLSTATE. */
41
+ klass = rb_hash_aref( rb_hErrors, rb_str_new2(sqlstate) );
42
+ if(NIL_P(klass))
43
+ {
44
+ /* The given SQLSTATE couldn't be found. This might happen, if
45
+ * the server side uses a newer version than the client.
46
+ * Try to find a error class by using the 2-characters SQLSTATE.
47
+ */
48
+ klass = rb_hash_aref( rb_hErrors, rb_str_new(sqlstate, 2) );
49
+ if(NIL_P(klass))
50
+ {
51
+ /* Also the 2-characters SQLSTATE is unknown.
52
+ * Use the generic server error instead.
53
+ */
54
+ klass = rb_eServerError;
55
+ }
56
+ }
57
+ }
58
+ else
59
+ {
60
+ /* Unable to retrieve the PG_DIAG_SQLSTATE.
61
+ * Use the generic error instead.
62
+ */
63
+ klass = rb_eUnableToSend;
64
+ }
65
+
66
+ return klass;
67
+ }
68
+
69
+ void
70
+ init_pg_errors()
71
+ {
72
+ rb_hErrors = rb_hash_new();
73
+ rb_define_const( rb_mPG, "ERROR_CLASSES", rb_hErrors );
74
+
75
+ rb_ePGerror = rb_define_class_under( rb_mPG, "Error", rb_eStandardError );
76
+
77
+ /*************************
78
+ * PG::Error
79
+ *************************/
80
+ rb_define_alias( rb_ePGerror, "error", "message" );
81
+ rb_define_attr( rb_ePGerror, "connection", 1, 0 );
82
+ rb_define_attr( rb_ePGerror, "result", 1, 0 );
83
+
84
+ rb_eServerError = rb_define_class_under( rb_mPG, "ServerError", rb_ePGerror );
85
+ rb_eUnableToSend = rb_define_class_under( rb_mPG, "UnableToSend", rb_ePGerror );
86
+ rb_eConnectionBad = rb_define_class_under( rb_mPG, "ConnectionBad", rb_ePGerror );
87
+
88
+ #include "errorcodes.def"
89
+ }
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_result.c - PG::Result class extension
3
- * $Id: pg_result.c,v 76ebae01c937 2013/03/26 17:50:02 ged $
3
+ * $Id: pg_result.c,v de6bee6a208c 2013/07/18 13:47:31 kanis $
4
4
  *
5
5
  */
6
6
 
@@ -44,13 +44,14 @@ pg_new_result(PGresult *result, VALUE rb_pgconn)
44
44
  VALUE
45
45
  pg_result_check( VALUE self )
46
46
  {
47
- VALUE error, exception;
47
+ VALUE error, exception, klass;
48
48
  VALUE rb_pgconn = rb_iv_get( self, "@connection" );
49
49
  PGconn *conn = pg_get_pgconn(rb_pgconn);
50
50
  PGresult *result;
51
51
  #ifdef M17N_SUPPORTED
52
52
  rb_encoding *enc = pg_conn_enc_get( conn );
53
53
  #endif
54
+ char * sqlstate;
54
55
 
55
56
  Data_Get_Struct(self, PGresult, result);
56
57
 
@@ -87,9 +88,12 @@ pg_result_check( VALUE self )
87
88
  #ifdef M17N_SUPPORTED
88
89
  rb_enc_set_index( error, rb_enc_to_index(enc) );
89
90
  #endif
90
- exception = rb_exc_new3( rb_ePGerror, error );
91
+
92
+ sqlstate = PQresultErrorField( result, PG_DIAG_SQLSTATE );
93
+ klass = lookup_error_class( sqlstate );
94
+ exception = rb_exc_new3( klass, error );
91
95
  rb_iv_set( exception, "@connection", rb_pgconn );
92
- rb_iv_set( exception, "@result", self );
96
+ rb_iv_set( exception, "@result", result ? self : Qnil );
93
97
  rb_exc_raise( exception );
94
98
 
95
99
  /* Not reached */
@@ -245,18 +249,18 @@ pgresult_error_message(VALUE self)
245
249
  * conn.exec( "SELECT * FROM nonexistant_table" )
246
250
  * rescue PG::Error => err
247
251
  * p [
248
- * result.error_field( PG::Result::PG_DIAG_SEVERITY ),
249
- * result.error_field( PG::Result::PG_DIAG_SQLSTATE ),
250
- * result.error_field( PG::Result::PG_DIAG_MESSAGE_PRIMARY ),
251
- * result.error_field( PG::Result::PG_DIAG_MESSAGE_DETAIL ),
252
- * result.error_field( PG::Result::PG_DIAG_MESSAGE_HINT ),
253
- * result.error_field( PG::Result::PG_DIAG_STATEMENT_POSITION ),
254
- * result.error_field( PG::Result::PG_DIAG_INTERNAL_POSITION ),
255
- * result.error_field( PG::Result::PG_DIAG_INTERNAL_QUERY ),
256
- * result.error_field( PG::Result::PG_DIAG_CONTEXT ),
257
- * result.error_field( PG::Result::PG_DIAG_SOURCE_FILE ),
258
- * result.error_field( PG::Result::PG_DIAG_SOURCE_LINE ),
259
- * result.error_field( PG::Result::PG_DIAG_SOURCE_FUNCTION ),
252
+ * err.result.error_field( PG::Result::PG_DIAG_SEVERITY ),
253
+ * err.result.error_field( PG::Result::PG_DIAG_SQLSTATE ),
254
+ * err.result.error_field( PG::Result::PG_DIAG_MESSAGE_PRIMARY ),
255
+ * err.result.error_field( PG::Result::PG_DIAG_MESSAGE_DETAIL ),
256
+ * err.result.error_field( PG::Result::PG_DIAG_MESSAGE_HINT ),
257
+ * err.result.error_field( PG::Result::PG_DIAG_STATEMENT_POSITION ),
258
+ * err.result.error_field( PG::Result::PG_DIAG_INTERNAL_POSITION ),
259
+ * err.result.error_field( PG::Result::PG_DIAG_INTERNAL_QUERY ),
260
+ * err.result.error_field( PG::Result::PG_DIAG_CONTEXT ),
261
+ * err.result.error_field( PG::Result::PG_DIAG_SOURCE_FILE ),
262
+ * err.result.error_field( PG::Result::PG_DIAG_SOURCE_LINE ),
263
+ * err.result.error_field( PG::Result::PG_DIAG_SOURCE_FUNCTION ),
260
264
  * ]
261
265
  * end
262
266
  *
@@ -510,6 +514,31 @@ pgresult_fsize(VALUE self, VALUE index)
510
514
  return INT2NUM(PQfsize(result, i));
511
515
  }
512
516
 
517
+
518
+ static VALUE
519
+ pgresult_value(VALUE self, PGresult *result, int tuple_num, int field_num)
520
+ {
521
+ VALUE val;
522
+ if ( PQgetisnull(result, tuple_num, field_num) ) {
523
+ return Qnil;
524
+ }
525
+ else {
526
+ val = rb_tainted_str_new( PQgetvalue(result, tuple_num, field_num ),
527
+ PQgetlength(result, tuple_num, field_num) );
528
+
529
+ #ifdef M17N_SUPPORTED
530
+ /* associate client encoding for text format only */
531
+ if ( 0 == PQfformat(result, field_num) ) {
532
+ ASSOCIATE_INDEX( val, self );
533
+ } else {
534
+ rb_enc_associate( val, rb_ascii8bit_encoding() );
535
+ }
536
+ #endif
537
+
538
+ return val;
539
+ }
540
+ }
541
+
513
542
  /*
514
543
  * call-seq:
515
544
  * res.getvalue( tup_num, field_num )
@@ -520,7 +549,6 @@ pgresult_fsize(VALUE self, VALUE index)
520
549
  static VALUE
521
550
  pgresult_getvalue(VALUE self, VALUE tup_num, VALUE field_num)
522
551
  {
523
- VALUE val;
524
552
  PGresult *result;
525
553
  int i = NUM2INT(tup_num);
526
554
  int j = NUM2INT(field_num);
@@ -532,21 +560,7 @@ pgresult_getvalue(VALUE self, VALUE tup_num, VALUE field_num)
532
560
  if(j < 0 || j >= PQnfields(result)) {
533
561
  rb_raise(rb_eArgError,"invalid field number %d", j);
534
562
  }
535
- if(PQgetisnull(result, i, j))
536
- return Qnil;
537
- val = rb_tainted_str_new(PQgetvalue(result, i, j),
538
- PQgetlength(result, i, j));
539
-
540
- #ifdef M17N_SUPPORTED
541
- /* associate client encoding for text format only */
542
- if ( 0 == PQfformat(result, j) ) {
543
- ASSOCIATE_INDEX( val, self );
544
- } else {
545
- rb_enc_associate( val, rb_ascii8bit_encoding() );
546
- }
547
- #endif
548
-
549
- return val;
563
+ return pgresult_value(self, result, i, j);
550
564
  }
551
565
 
552
566
  /*
@@ -696,7 +710,7 @@ pgresult_aref(VALUE self, VALUE index)
696
710
  PGresult *result = pgresult_get(self);
697
711
  int tuple_num = NUM2INT(index);
698
712
  int field_num;
699
- VALUE fname,val;
713
+ VALUE fname;
700
714
  VALUE tuple;
701
715
 
702
716
  if ( tuple_num < 0 || tuple_num >= PQntuples(result) )
@@ -706,24 +720,7 @@ pgresult_aref(VALUE self, VALUE index)
706
720
  for ( field_num = 0; field_num < PQnfields(result); field_num++ ) {
707
721
  fname = rb_tainted_str_new2( PQfname(result,field_num) );
708
722
  ASSOCIATE_INDEX(fname, self);
709
- if ( PQgetisnull(result, tuple_num, field_num) ) {
710
- rb_hash_aset( tuple, fname, Qnil );
711
- }
712
- else {
713
- val = rb_tainted_str_new( PQgetvalue(result, tuple_num, field_num ),
714
- PQgetlength(result, tuple_num, field_num) );
715
-
716
- #ifdef M17N_SUPPORTED
717
- /* associate client encoding for text format only */
718
- if ( 0 == PQfformat(result, field_num) ) {
719
- ASSOCIATE_INDEX( val, self );
720
- } else {
721
- rb_enc_associate( val, rb_ascii8bit_encoding() );
722
- }
723
- #endif
724
-
725
- rb_hash_aset( tuple, fname, val );
726
- }
723
+ rb_hash_aset( tuple, fname, pgresult_value(self, result, tuple_num, field_num) );
727
724
  }
728
725
  return tuple;
729
726
  }
@@ -748,23 +745,7 @@ pgresult_each_row(VALUE self)
748
745
 
749
746
  /* populate the row */
750
747
  for ( field = 0; field < num_fields; field++ ) {
751
- if ( PQgetisnull(result, row, field) ) {
752
- rb_ary_store( new_row, field, Qnil );
753
- }
754
- else {
755
- VALUE val = rb_tainted_str_new( PQgetvalue(result, row, field),
756
- PQgetlength(result, row, field) );
757
-
758
- #ifdef M17N_SUPPORTED
759
- /* associate client encoding for text format only */
760
- if ( 0 == PQfformat(result, field) ) {
761
- ASSOCIATE_INDEX( val, self );
762
- } else {
763
- rb_enc_associate( val, rb_ascii8bit_encoding() );
764
- }
765
- #endif
766
- rb_ary_store( new_row, field, val );
767
- }
748
+ rb_ary_store( new_row, field, pgresult_value(self, result, row, field) );
768
749
  }
769
750
  rb_yield( new_row );
770
751
  }
data/lib/pg.rb CHANGED
@@ -19,10 +19,10 @@ end
19
19
  module PG
20
20
 
21
21
  # Library version
22
- VERSION = '0.15.1'
22
+ VERSION = '0.16.0'
23
23
 
24
24
  # VCS revision
25
- REVISION = %q$Revision: 0bfb6ff650be $
25
+ REVISION = %q$Revision: 4e0606f5f5aa $
26
26
 
27
27
 
28
28
  ### Get the PG library version. If +include_buildnum+ is +true+, include the build ID.
@@ -247,6 +247,13 @@ module PG::TestingHelpers
247
247
  end
248
248
  end
249
249
  end
250
+
251
+ def connection_string_should_contain_application_name(conn_args, app_name)
252
+ conn_name = conn_args.match(/application_name='(.*)'/)[1]
253
+ conn_name.should include(app_name[0..10])
254
+ conn_name.should include(app_name[-10..-1])
255
+ conn_name.length.should <= 64
256
+ end
250
257
  end
251
258
 
252
259
 
@@ -270,9 +277,11 @@ RSpec.configure do |config|
270
277
  PG::Connection.instance_methods.map( &:to_sym ).include?( :escape_literal )
271
278
 
272
279
  if !PG.respond_to?( :library_version )
273
- config.filter_run_excluding( :postgresql_91, :postgresql_92 )
280
+ config.filter_run_excluding( :postgresql_91, :postgresql_92, :postgresql_93 )
274
281
  elsif PG.library_version < 90200
275
- config.filter_run_excluding( :postgresql_92 )
282
+ config.filter_run_excluding( :postgresql_92, :postgresql_93 )
283
+ elsif PG.library_version < 90300
284
+ config.filter_run_excluding( :postgresql_93 )
276
285
  end
277
286
  end
278
287
 
@@ -177,6 +177,11 @@ describe PG::Connection do
177
177
  conn.should be_finished()
178
178
  end
179
179
 
180
+ it "raises proper error when sending fails" do
181
+ conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
182
+ expect{ conn.exec 'SELECT 1' }.to raise_error(PG::UnableToSend, /no connection/)
183
+ end
184
+
180
185
  it "doesn't leave stale server connections after finish" do
181
186
  described_class.connect(@conninfo).finish
182
187
  sleep 0.5
@@ -257,6 +262,18 @@ describe PG::Connection do
257
262
  error.should == true
258
263
  end
259
264
 
265
+ it "can stop a thread that runs a blocking query" do
266
+ start = Time.now
267
+ t = Thread.new do
268
+ @conn.async_exec( 'select pg_sleep(10)' )
269
+ end
270
+ sleep 0.1
271
+
272
+ t.kill
273
+ t.join
274
+ (Time.now - start).should < 10
275
+ end
276
+
260
277
  it "automatically rolls back a transaction started with Connection#transaction if an exception " +
261
278
  "is raised" do
262
279
  # abort the per-example transaction so we can test our own
@@ -276,6 +293,16 @@ describe PG::Connection do
276
293
  res.ntuples.should == 0
277
294
  end
278
295
 
296
+ it "returns the block result from Connection#transaction" do
297
+ # abort the per-example transaction so we can test our own
298
+ @conn.exec( 'ROLLBACK' )
299
+
300
+ res = @conn.transaction do
301
+ "transaction result"
302
+ end
303
+ res.should == "transaction result"
304
+ end
305
+
279
306
  it "not read past the end of a large object" do
280
307
  @conn.transaction do
281
308
  oid = @conn.lo_create( 0 )
@@ -400,6 +427,21 @@ describe PG::Connection do
400
427
  @conn.exec( 'UNLISTEN woo' )
401
428
  end
402
429
 
430
+ it "can receive notices while waiting for NOTIFY without exceeding the timeout", :postgresql_90 do
431
+ notices = []
432
+ @conn.set_notice_processor do |msg|
433
+ notices << [msg, Time.now]
434
+ end
435
+ st = Time.now
436
+ @conn.send_query "SELECT pg_sleep(0.5); do $$ BEGIN RAISE NOTICE 'woohoo'; END; $$ LANGUAGE plpgsql;"
437
+ @conn.wait_for_notify( 1 ).should be_nil
438
+ notices.first.should_not be_nil
439
+ et = Time.now
440
+ (et - notices.first[1]).should >= 0.4
441
+ (et - st).should >= 0.9
442
+ (et - st).should < 1.4
443
+ end
444
+
403
445
  it "yields the result if block is given to exec" do
404
446
  rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
405
447
  values = []
@@ -543,7 +585,7 @@ describe PG::Connection do
543
585
  conn = PG.connect( @conninfo )
544
586
 
545
587
  conn.finish
546
- expect { conn.finish }.to raise_error( PG::Error, /connection is closed/i )
588
+ expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
547
589
  end
548
590
 
549
591
  it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction, :socket_io do
@@ -551,7 +593,7 @@ describe PG::Connection do
551
593
  io = conn.socket_io
552
594
  conn.finish
553
595
  io.should be_closed()
554
- expect { conn.socket_io }.to raise_error( PG::Error, /connection is closed/i )
596
+ expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
555
597
  end
556
598
 
557
599
  it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction, :socket_io do
@@ -563,6 +605,16 @@ describe PG::Connection do
563
605
  conn.finish
564
606
  end
565
607
 
608
+ it "block should raise ConnectionBad for a closed connection" do
609
+ serv = TCPServer.new( '127.0.0.1', 54320 )
610
+ conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
611
+ while [PG::CONNECTION_STARTED, PG::CONNECTION_MADE].include?(conn.connect_poll)
612
+ sleep 0.1
613
+ end
614
+ serv.close
615
+ expect{ conn.block }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
616
+ expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
617
+ end
566
618
 
567
619
  context "under PostgreSQL 9", :postgresql_90 do
568
620
 
@@ -571,14 +623,16 @@ describe PG::Connection do
571
623
  end
572
624
 
573
625
  it "sets the fallback_application_name on new connections" do
574
- PG::Connection.parse_connect_args( 'dbname=test' ).should include( $0 )
626
+ conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
627
+ connection_string_should_contain_application_name(conn_string, $0)
575
628
  end
576
629
 
577
630
  it "sets a shortened fallback_application_name on new connections" do
578
631
  old_0 = $0
579
632
  begin
580
633
  $0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
581
- PG::Connection.parse_connect_args( 'dbname=test' ).should match(/\/this\/is\/a.*\.\.\..*\/beloved\/ruby/)
634
+ conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
635
+ connection_string_should_contain_application_name(conn_string, $0)
582
636
  ensure
583
637
  $0 = old_0
584
638
  end
@@ -874,17 +928,53 @@ describe PG::Connection do
874
928
  end
875
929
 
876
930
  it "uses the client encoding for escaped string" do
877
- original = "string to escape".force_encoding( "euc-jp" )
931
+ original = "string to\0 escape".force_encoding( "iso8859-1" )
878
932
  @conn.set_client_encoding( "euc_jp" )
879
933
  escaped = @conn.escape( original )
880
934
  escaped.encoding.should == Encoding::EUC_JP
935
+ escaped.should == "string to"
881
936
  end
882
937
 
883
- it "escapes string as literal", :postgresql_90 do
884
- original = "string to\0 escape"
938
+ it "uses the client encoding for escaped literal", :postgresql_90 do
939
+ original = "string to\0 escape".force_encoding( "iso8859-1" )
940
+ @conn.set_client_encoding( "euc_jp" )
885
941
  escaped = @conn.escape_literal( original )
942
+ escaped.encoding.should == Encoding::EUC_JP
886
943
  escaped.should == "'string to'"
887
944
  end
945
+
946
+ it "uses the client encoding for escaped identifier", :postgresql_90 do
947
+ original = "string to\0 escape".force_encoding( "iso8859-1" )
948
+ @conn.set_client_encoding( "euc_jp" )
949
+ escaped = @conn.escape_identifier( original )
950
+ escaped.encoding.should == Encoding::EUC_JP
951
+ escaped.should == "\"string to\""
952
+ end
953
+
954
+ it "uses the client encoding for quote_ident" do
955
+ original = "string to\0 escape".force_encoding( "iso8859-1" )
956
+ @conn.set_client_encoding( "euc_jp" )
957
+ escaped = @conn.quote_ident( original )
958
+ escaped.encoding.should == Encoding::EUC_JP
959
+ escaped.should == "\"string to\""
960
+ end
961
+
962
+ it "uses the previous string encoding for escaped string" do
963
+ original = "string to\0 escape".force_encoding( "iso8859-1" )
964
+ @conn.set_client_encoding( "euc_jp" )
965
+ escaped = described_class.escape( original )
966
+ escaped.encoding.should == Encoding::ISO8859_1
967
+ escaped.should == "string to"
968
+ end
969
+
970
+ it "uses the previous string encoding for quote_ident" do
971
+ original = "string to\0 escape".force_encoding( "iso8859-1" )
972
+ @conn.set_client_encoding( "euc_jp" )
973
+ escaped = described_class.quote_ident( original )
974
+ escaped.encoding.should == Encoding::ISO8859_1
975
+ escaped.should == "\"string to\""
976
+ end
977
+
888
978
  end
889
979
 
890
980
 
@@ -1010,7 +1100,7 @@ describe PG::Connection do
1010
1100
  end
1011
1101
 
1012
1102
  context "OS thread support", :ruby_19 do
1013
- it "described_class#exec shouldn't block a second thread" do
1103
+ it "Connection#exec shouldn't block a second thread" do
1014
1104
  t = Thread.new do
1015
1105
  @conn.exec( "select pg_sleep(1)" )
1016
1106
  end
@@ -1019,5 +1109,20 @@ describe PG::Connection do
1019
1109
  t.should be_alive()
1020
1110
  t.join
1021
1111
  end
1112
+
1113
+ it "Connection.new shouldn't block a second thread" do
1114
+ serv = nil
1115
+ t = Thread.new do
1116
+ serv = TCPServer.new( '127.0.0.1', 54320 )
1117
+ expect {
1118
+ described_class.new( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
1119
+ }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
1120
+ end
1121
+
1122
+ sleep 0.5
1123
+ t.should be_alive()
1124
+ serv.close
1125
+ t.join
1126
+ end
1022
1127
  end
1023
1128
  end