pg 0.18.1 → 0.18.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_text_encoder.c - PG::TextEncoder module
3
- * $Id: pg_text_encoder.c,v 1a13e7eafeb7 2014/12/12 20:57:39 lars $
3
+ * $Id: pg_text_encoder.c,v b859963462b2 2015/03/13 17:39:35 lars $
4
4
  *
5
5
  */
6
6
 
@@ -299,7 +299,7 @@ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
299
299
  /* count data plus backslashes; detect chars needing quotes */
300
300
  if (strlen == 0)
301
301
  needquote = 1; /* force quotes for empty string */
302
- else if (strlen == 4 && pg_strncasecmp(p_in, "NULL", strlen) == 0)
302
+ else if (strlen == 4 && rbpg_strncasecmp(p_in, "NULL", strlen) == 0)
303
303
  needquote = 1; /* force quotes for literal NULL */
304
304
  else
305
305
  needquote = 0;
@@ -455,39 +455,34 @@ pg_text_enc_array(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate)
455
455
  }
456
456
  }
457
457
 
458
- static int
459
- quote_identifier_buffer( void *_this, char *p_in, int strlen, char *p_out ){
458
+ static char *
459
+ quote_identifier( VALUE value, VALUE out_string, char *current_out ){
460
+ char *p_in = RSTRING_PTR(value);
460
461
  char *ptr1;
461
- char *ptr2;
462
- int backslashs = 0;
462
+ size_t strlen = RSTRING_LEN(value);
463
+ char *end_capa = current_out;
463
464
 
464
- /* count required backlashs */
465
+ PG_RB_STR_ENSURE_CAPA( out_string, strlen + 2, current_out, end_capa );
466
+ *current_out++ = '"';
465
467
  for(ptr1 = p_in; ptr1 != p_in + strlen; ptr1++) {
466
- if (*ptr1 == '"'){
467
- backslashs++;
468
+ char c = *ptr1;
469
+ if (c == '"'){
470
+ strlen++;
471
+ PG_RB_STR_ENSURE_CAPA( out_string, p_in - ptr1 + strlen + 1, current_out, end_capa );
472
+ *current_out++ = '"';
473
+ } else if (c == 0){
474
+ break;
468
475
  }
476
+ *current_out++ = c;
469
477
  }
478
+ PG_RB_STR_ENSURE_CAPA( out_string, 1, current_out, end_capa );
479
+ *current_out++ = '"';
470
480
 
471
- ptr1 = p_in + strlen;
472
- ptr2 = p_out + strlen + backslashs + 2;
473
- /* Write end quote */
474
- *--ptr2 = '"';
475
-
476
- /* Then store the escaped string on the final position, walking
477
- * right to left, until all backslashs are placed. */
478
- while( ptr1 != p_in ) {
479
- *--ptr2 = *--ptr1;
480
- if(*ptr2 == '"'){
481
- *--ptr2 = '"';
482
- }
483
- }
484
- /* Write start quote */
485
- *p_out = '"';
486
- return strlen + backslashs + 2;
481
+ return current_out;
487
482
  }
488
483
 
489
484
  static char *
490
- pg_text_enc_array_identifier(t_pg_composite_coder *this, VALUE value, VALUE string, char *out)
485
+ pg_text_enc_array_identifier(VALUE value, VALUE string, char *out)
491
486
  {
492
487
  int i;
493
488
  int nr_elems;
@@ -498,7 +493,7 @@ pg_text_enc_array_identifier(t_pg_composite_coder *this, VALUE value, VALUE stri
498
493
  for( i=0; i<nr_elems; i++){
499
494
  VALUE entry = rb_ary_entry(value, i);
500
495
 
501
- out = quote_string(this->elem, entry, string, out, this->needs_quotation, quote_identifier_buffer, this);
496
+ out = quote_identifier(entry, string, out);
502
497
  if( i < nr_elems-1 ){
503
498
  out = pg_rb_str_ensure_capa( string, 1, out, NULL );
504
499
  *out++ = '.';
@@ -508,27 +503,29 @@ pg_text_enc_array_identifier(t_pg_composite_coder *this, VALUE value, VALUE stri
508
503
  }
509
504
 
510
505
  /*
511
- * Document-class: PG::TextEncoder::Identifier < PG::CompositeEncoder
506
+ * Document-class: PG::TextEncoder::Identifier < PG::SimpleEncoder
512
507
  *
513
508
  * This is the encoder class for PostgreSQL identifiers.
514
509
  *
515
510
  * An Array value can be used for "schema.table.column" type identifiers:
516
511
  * PG::TextEncoder::Identifier.new.encode(['schema', 'table', 'column'])
517
- * => "schema"."table"."column"
512
+ * => '"schema"."table"."column"'
518
513
  *
514
+ * This encoder can also be used per PG::Connection#quote_ident .
519
515
  */
520
- static int
521
- pg_text_enc_identifier(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate)
516
+ int
517
+ pg_text_enc_identifier(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate)
522
518
  {
523
- t_pg_composite_coder *this = (t_pg_composite_coder *)conv;
524
-
525
- *intermediate = rb_str_new(NULL, 0);
526
- out = RSTRING_PTR(*intermediate);
527
-
519
+ UNUSED( this );
528
520
  if( TYPE(value) == T_ARRAY){
529
- out = pg_text_enc_array_identifier(this, value, *intermediate, out);
521
+ *intermediate = rb_str_new(NULL, 0);
522
+ out = RSTRING_PTR(*intermediate);
523
+ out = pg_text_enc_array_identifier(value, *intermediate, out);
530
524
  } else {
531
- out = quote_string(this->elem, value, *intermediate, out, this->needs_quotation, quote_identifier_buffer, this);
525
+ StringValue(value);
526
+ *intermediate = rb_str_new(NULL, RSTRING_LEN(value) + 2);
527
+ out = RSTRING_PTR(*intermediate);
528
+ out = quote_identifier(value, *intermediate, out);
532
529
  }
533
530
  rb_str_set_len( *intermediate, out - RSTRING_PTR(*intermediate) );
534
531
  return -1;
@@ -651,11 +648,11 @@ init_pg_text_encoder()
651
648
  pg_define_coder( "String", pg_coder_enc_to_s, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
652
649
  /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Bytea", rb_cPG_SimpleEncoder ); */
653
650
  pg_define_coder( "Bytea", pg_text_enc_bytea, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
651
+ /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Identifier", rb_cPG_SimpleEncoder ); */
652
+ pg_define_coder( "Identifier", pg_text_enc_identifier, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
654
653
 
655
654
  /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Array", rb_cPG_CompositeEncoder ); */
656
655
  pg_define_coder( "Array", pg_text_enc_array, rb_cPG_CompositeEncoder, rb_mPG_TextEncoder );
657
- /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Identifier", rb_cPG_CompositeEncoder ); */
658
- pg_define_coder( "Identifier", pg_text_enc_identifier, rb_cPG_CompositeEncoder, rb_mPG_TextEncoder );
659
656
  /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "QuotedLiteral", rb_cPG_CompositeEncoder ); */
660
657
  pg_define_coder( "QuotedLiteral", pg_text_enc_quoted_literal, rb_cPG_CompositeEncoder, rb_mPG_TextEncoder );
661
658
  /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "ToBase64", rb_cPG_CompositeEncoder ); */
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_type_map_by_mri_type.c - PG::TypeMapByMriType class extension
3
- * $Id: pg_type_map_by_mri_type.c,v 27987dbd0b32 2014/11/07 20:55:52 lars $
3
+ * $Id: pg_type_map_by_mri_type.c,v 1269b8ad77b8 2015/02/06 16:38:23 lars $
4
4
  *
5
5
  * This type map can be used to select value encoders based on the MRI-internal
6
6
  * value type code.
@@ -39,7 +39,7 @@ static VALUE rb_cTypeMapByMriType;
39
39
  typedef struct {
40
40
  t_typemap typemap;
41
41
  struct pg_tmbmt_converter {
42
- FOR_EACH_MRI_TYPE( DECLARE_CODER );
42
+ FOR_EACH_MRI_TYPE( DECLARE_CODER )
43
43
  } coders;
44
44
  } t_tmbmt;
45
45
 
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_type_map_in_ruby.c - PG::TypeMapInRuby class extension
3
- * $Id: pg_type_map_in_ruby.c,v a38cf53a96f1 2014/12/13 21:59:57 lars $
3
+ * $Id: pg_type_map_in_ruby.c,v 3d89d3aae4fd 2015/01/05 16:19:41 kanis $
4
4
  *
5
5
  */
6
6
 
@@ -212,12 +212,9 @@ pg_tmir_copy_get( t_typemap *p_typemap, VALUE field_str, int fieldno, int format
212
212
  rb_encoding *p_encoding = rb_enc_from_index(enc_idx);
213
213
  VALUE enc = rb_enc_from_encoding(p_encoding);
214
214
  /* field_str is reused in-place by pg_text_dec_copy_row(), so we need to make
215
- * a copy of the string buffer before used in ruby space.
216
- * This requires rb_str_new() instead of rb_str_dup() for Rubinius.
217
- */
218
- VALUE field_str_copy = rb_str_new(RSTRING_PTR(field_str), RSTRING_LEN(field_str));
219
- PG_ENCODING_SET_NOCHECK(field_str_copy, ENCODING_GET(field_str));
220
- OBJ_INFECT(field_str_copy, field_str);
215
+ * a copy of the string buffer for use in ruby space. */
216
+ VALUE field_str_copy = rb_str_dup(field_str);
217
+ rb_str_modify(field_str_copy);
221
218
 
222
219
  return rb_funcall( this->self, s_id_typecast_copy_get, 4, field_str_copy, INT2NUM(fieldno), INT2NUM(format), enc );
223
220
  }
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: util.c,v b859963462b2 2015/03/13 17:39:35 lars $
4
4
  *
5
5
  */
6
6
 
@@ -124,7 +124,7 @@ base64_decode( char *out, char *in, unsigned int len)
124
124
  * At most n bytes will be examined from each string.
125
125
  */
126
126
  int
127
- pg_strncasecmp(const char *s1, const char *s2, size_t n)
127
+ rbpg_strncasecmp(const char *s1, const char *s2, size_t n)
128
128
  {
129
129
  while (n-- > 0)
130
130
  {
data/ext/util.h CHANGED
@@ -60,6 +60,6 @@
60
60
  void base64_encode( char *out, char *in, int len);
61
61
  int base64_decode( char *out, char *in, unsigned int len);
62
62
 
63
- int pg_strncasecmp(const char *s1, const char *s2, size_t n);
63
+ int rbpg_strncasecmp(const char *s1, const char *s2, size_t n);
64
64
 
65
65
  #endif /* end __utils_h */
data/lib/pg.rb CHANGED
@@ -24,10 +24,10 @@ end
24
24
  module PG
25
25
 
26
26
  # Library version
27
- VERSION = '0.18.1'
27
+ VERSION = '0.18.2'
28
28
 
29
29
  # VCS revision
30
- REVISION = %q$Revision: ba5aff64b5cb $
30
+ REVISION = %q$Revision: 7d31b04e7913 $
31
31
 
32
32
  class NotAllCopyDataRetrieved < PG::Error
33
33
  end
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'pg' unless defined?( PG )
4
+ require 'uri'
4
5
 
5
6
  # The PostgreSQL connection class. The interface for this class is based on
6
7
  # {libpq}[http://www.postgresql.org/docs/9.2/interactive/libpq.html], the C
@@ -34,46 +35,55 @@ class PG::Connection
34
35
  def self::parse_connect_args( *args )
35
36
  return '' if args.empty?
36
37
 
37
- # This will be swapped soon for code that makes options like those required for
38
- # PQconnectdbParams()/PQconnectStartParams(). For now, stick to an options string for
39
- # PQconnectdb()/PQconnectStart().
38
+ hash_arg = args.last.is_a?( Hash ) ? args.pop : {}
39
+ option_string = ''
40
+ options = {}
40
41
 
41
42
  # Parameter 'fallback_application_name' was introduced in PostgreSQL 9.0
42
43
  # together with PQescapeLiteral().
43
- if PG::Connection.instance_methods.find{|m| m.to_sym == :escape_literal }
44
- appname = $0.sub(/^(.{30}).{4,}(.{30})$/){ $1+"..."+$2 }
45
- appname = PG::Connection.quote_connstr( appname )
46
- connopts = ["fallback_application_name=#{appname}"]
47
- else
48
- connopts = []
44
+ if PG::Connection.instance_methods.find {|m| m.to_sym == :escape_literal }
45
+ options[:fallback_application_name] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
49
46
  end
50
47
 
51
- # Handle an options hash first
52
- if args.last.is_a?( Hash )
53
- opthash = args.pop
54
- opthash.each do |key, val|
55
- connopts.push( "%s=%s" % [key, PG::Connection.quote_connstr(val)] )
48
+ if args.length == 1
49
+ case args.first
50
+ when URI, URI.regexp
51
+ uri = URI(args.first)
52
+ options.merge!( Hash[URI.decode_www_form( uri.query )] ) if uri.query
53
+ when /=/
54
+ # Option string style
55
+ option_string = args.first.to_s
56
+ else
57
+ # Positional parameters
58
+ options[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
56
59
  end
57
- end
58
-
59
- # Option string style
60
- if args.length == 1 && args.first.to_s.index( '=' )
61
- connopts.unshift( args.first )
62
-
63
- # Append positional parameters
64
60
  else
65
- args.each_with_index do |val, i|
66
- next unless val # Skip nil placeholders
61
+ max = CONNECT_ARGUMENT_ORDER.length
62
+ raise ArgumentError,
63
+ "Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
67
64
 
68
- key = CONNECT_ARGUMENT_ORDER[ i ] or
69
- raise ArgumentError, "Extra positional parameter %d: %p" % [ i+1, val ]
70
- connopts.push( "%s=%s" % [key, PG::Connection.quote_connstr(val.to_s)] )
65
+ CONNECT_ARGUMENT_ORDER.zip( args ) do |(k,v)|
66
+ options[ k.to_sym ] = v if v
71
67
  end
72
68
  end
73
69
 
74
- return connopts.join(' ')
70
+ options.merge!( hash_arg )
71
+
72
+ if uri
73
+ uri.host = nil if options[:host]
74
+ uri.port = nil if options[:port]
75
+ uri.user = nil if options[:user]
76
+ uri.password = nil if options[:password]
77
+ uri.path = '' if options[:dbname]
78
+ uri.query = URI.encode_www_form( options )
79
+ return uri.to_s.sub( /^#{uri.scheme}:(?!\/\/)/, "#{uri.scheme}://" )
80
+ else
81
+ option_string += ' ' unless option_string.empty? && options.empty?
82
+ return option_string + options.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
83
+ end
75
84
  end
76
85
 
86
+
77
87
  # call-seq:
78
88
  # conn.copy_data( sql ) {|sql_result| ... } -> PG::Result
79
89
  #
@@ -27,11 +27,11 @@ module PG
27
27
  end
28
28
 
29
29
  class TimestampWithTimeZone < SimpleDecoder
30
- ISO_DATETIME_WITH_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?([-\+]\d\d)\z/
30
+ ISO_DATETIME_WITH_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?([-\+]\d\d):?(\d\d)?:?(\d\d)?\z/
31
31
 
32
32
  def decode(string, tuple=nil, field=nil)
33
33
  if string =~ ISO_DATETIME_WITH_TIMEZONE
34
- Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r, "#{$8}:00"
34
+ Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r, "#{$8}:#{$9 || '00'}:#{$10 || '00'}"
35
35
  else
36
36
  string
37
37
  end
@@ -45,6 +45,14 @@ describe PG::Connection do
45
45
  expect( optstring ).to match( /(^|\s)user='jrandom'/ )
46
46
  end
47
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'/ )
54
+ end
55
+
48
56
  it "escapes single quotes and backslashes in connection parameters" do
49
57
  expect(
50
58
  described_class.parse_connect_args( "DB 'browser' \\" )
@@ -52,18 +60,72 @@ describe PG::Connection do
52
60
 
53
61
  end
54
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
+
55
117
  it "connects with defaults if no connection parameters are given" do
56
118
  expect( described_class.parse_connect_args ).to eq( '' )
57
119
  end
58
120
 
59
121
  it "connects successfully with connection string" do
60
- tmpconn = described_class.connect(@conninfo)
122
+ tmpconn = described_class.connect( @conninfo )
61
123
  expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
62
124
  tmpconn.finish
63
125
  end
64
126
 
65
127
  it "connects using 7 arguments converted to strings" do
66
- tmpconn = described_class.connect('localhost', @port, nil, nil, :test, nil, nil)
128
+ tmpconn = described_class.connect( 'localhost', @port, nil, nil, :test, nil, nil )
67
129
  expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
68
130
  tmpconn.finish
69
131
  end
@@ -89,8 +151,13 @@ describe PG::Connection do
89
151
 
90
152
  it "raises an exception when connecting with an invalid number of arguments" do
91
153
  expect {
92
- described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'extra' )
93
- }.to raise_error( ArgumentError, /extra positional parameter/i )
154
+ described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'the-extra-arg' )
155
+ }.to raise_error do |error|
156
+ expect( error ).to be_an( ArgumentError )
157
+ expect( error.message ).to match( /extra positional parameter/i )
158
+ expect( error.message ).to match( /8/ )
159
+ expect( error.message ).to match( /the-extra-arg/ )
160
+ end
94
161
  end
95
162
 
96
163
  it "can connect asynchronously", :socket_io do
@@ -162,8 +229,6 @@ describe PG::Connection do
162
229
  expect( @conn.user ).to be_a_kind_of( String )
163
230
  expect( @conn.pass ).to eq( "" )
164
231
  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
232
  expect( @conn.port ).to eq( 54321 )
168
233
  expect( @conn.tty ).to eq( "" )
169
234
  expect( @conn.options ).to eq( "" )
@@ -1128,9 +1193,20 @@ describe PG::Connection do
1128
1193
  expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1129
1194
  expect( escaped ).to eq( "\"string to\"" )
1130
1195
  end
1196
+ end
1131
1197
 
1198
+ it "can quote bigger strings with quote_ident" do
1199
+ original = "'01234567\"" * 100
1200
+ escaped = described_class.quote_ident( original + "\0afterzero" )
1201
+ expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
1132
1202
  end
1133
1203
 
1204
+ it "can quote Arrays with quote_ident" do
1205
+ original = "'01234567\""
1206
+ escaped = described_class.quote_ident( [original]*3 )
1207
+ expected = ["\"" + original.gsub("\"", "\"\"") + "\""] * 3
1208
+ expect( escaped ).to eq( expected.join(".") )
1209
+ end
1134
1210
 
1135
1211
  describe "Ruby 1.9.x default_internal encoding" do
1136
1212