pg 0.18.1 → 0.18.2

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.
@@ -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