diff --git a/ext/extconf.rb b/ext/extconf.rb index 1556ad082..7e5727ba1 100644 --- a/ext/extconf.rb +++ b/ext/extconf.rb @@ -94,6 +94,10 @@ have_header 'unistd.h' have_header 'ruby/st.h' or have_header 'st.h' or abort "pg currently requires the ruby/st.h header" +checking_for "C99 variable length arrays" do + $defs.push( "-DHAVE_VARIABLE_LENGTH_ARRAYS" ) if try_compile('void test_vla(int l){ int vla[l]; }') +end + create_header() create_makefile( "pg_ext" ) diff --git a/ext/pg.c b/ext/pg.c index 362200745..91e262d49 100644 --- a/ext/pg.c +++ b/ext/pg.c @@ -252,7 +252,7 @@ pg_get_rb_encoding_as_pg_encoding( rb_encoding *enc ) * char *current_out, *end_capa; * PG_RB_STR_NEW( string, current_out, end_capa ); * while( data_is_going_to_be_processed ){ - * PG_RB_STR_ENSURE_CAPA( string, 2 current_out, end_capa ); + * PG_RB_STR_ENSURE_CAPA( string, 2, current_out, end_capa ); * *current_out++ = databyte1; * *current_out++ = databyte2; * } diff --git a/ext/pg.h b/ext/pg.h index 3da36c747..8ac72392f 100644 --- a/ext/pg.h +++ b/ext/pg.h @@ -133,6 +133,15 @@ typedef long suseconds_t; #endif +#if defined(HAVE_VARIABLE_LENGTH_ARRAYS) + #define PG_VARIABLE_LENGTH_ARRAY(type, name, len, maxlen) type name[(len)]; +#else + #define PG_VARIABLE_LENGTH_ARRAY(type, name, len, maxlen) \ + type name[(maxlen)] = {(len)>(maxlen) ? (rb_raise(rb_eArgError, "Number of " #name " (%d) exceeds allowed maximum of " #maxlen, (len) ), (type)1) : (type)0}; + + #define PG_MAX_COLUMNS 4000 +#endif + /* The data behind each PG::Connection object */ typedef struct { PGconn *pgconn; @@ -313,6 +322,7 @@ VALUE lookup_error_class _(( const char * )); VALUE pg_bin_dec_bytea _(( t_pg_coder*, char *, int, int, int, int )); VALUE pg_text_dec_string _(( t_pg_coder*, char *, int, int, int, int )); int pg_coder_enc_to_s _(( t_pg_coder*, VALUE, char *, VALUE *)); +int pg_text_enc_identifier _(( t_pg_coder*, VALUE, char *, VALUE *)); t_pg_coder_enc_func pg_coder_enc_func _(( t_pg_coder* )); t_pg_coder_dec_func pg_coder_dec_func _(( t_pg_coder*, int )); void pg_define_coder _(( const char *, void *, VALUE, VALUE )); diff --git a/ext/pg_connection.c b/ext/pg_connection.c index bc98cf04d..90847e050 100644 --- a/ext/pg_connection.c +++ b/ext/pg_connection.c @@ -2997,7 +2997,9 @@ pgconn_transaction(VALUE self) /* * call-seq: * PG::Connection.quote_ident( str ) -> String + * PG::Connection.quote_ident( array ) -> String * conn.quote_ident( str ) -> String + * conn.quote_ident( array ) -> String * * Returns a string that is safe for inclusion in a SQL query as an * identifier. Note: this is not a quote function for values, but for @@ -3014,31 +3016,20 @@ pgconn_transaction(VALUE self) * Similarly, this function also protects against special characters, * and other things that might allow SQL injection if the identifier * comes from an untrusted source. + * + * If the parameter is an Array, then all it's values are separately quoted + * and then joined by a "." character. This can be used for identifiers in + * the form "schema"."table"."column" . + * + * This method is functional identical to the encoder PG::TextEncoder::Identifier . + * */ static VALUE pgconn_s_quote_ident(VALUE self, VALUE in_str) { VALUE ret; - char *str = StringValuePtr(in_str); - /* result size at most NAMEDATALEN*2 plus surrounding - * double-quotes. */ - char buffer[NAMEDATALEN*2+2]; - unsigned int i=0,j=0; - unsigned int str_len = RSTRING_LENINT(in_str); - - if(str_len >= NAMEDATALEN) { - rb_raise(rb_eArgError, - "Input string is longer than NAMEDATALEN-1 (%d)", - NAMEDATALEN-1); - } - buffer[j++] = '"'; - for(i = 0; i < str_len && str[i]; i++) { - if(str[i] == '"') - buffer[j++] = '"'; - buffer[j++] = str[i]; - } - buffer[j++] = '"'; - ret = rb_str_new(buffer,j); + pg_text_enc_identifier(NULL, in_str, NULL, &ret); + OBJ_INFECT(ret, in_str); PG_ENCODING_SET_NOCHECK(ret, ENCODING_GET( rb_obj_class(self) == rb_cPGconn ? self : in_str )); diff --git a/ext/pg_result.c b/ext/pg_result.c index 038d13f53..fa5710474 100644 --- a/ext/pg_result.c +++ b/ext/pg_result.c @@ -863,7 +863,7 @@ pgresult_each_row(VALUE self) num_fields = PQnfields(this->pgresult); for ( row = 0; row < num_rows; row++ ) { - VALUE row_values[num_fields]; + PG_VARIABLE_LENGTH_ARRAY(VALUE, row_values, num_fields, PG_MAX_COLUMNS) /* populate the row */ for ( field = 0; field < num_fields; field++ ) { @@ -892,7 +892,7 @@ pgresult_values(VALUE self) VALUE results = rb_ary_new2( num_rows ); for ( row = 0; row < num_rows; row++ ) { - VALUE row_values[num_fields]; + PG_VARIABLE_LENGTH_ARRAY(VALUE, row_values, num_fields, PG_MAX_COLUMNS) /* populate the row */ for ( field = 0; field < num_fields; field++ ) { @@ -1176,7 +1176,7 @@ pgresult_stream_each_row(VALUE self) } for ( row = 0; row < ntuples; row++ ) { - VALUE row_values[nfields]; + PG_VARIABLE_LENGTH_ARRAY(VALUE, row_values, nfields, PG_MAX_COLUMNS) int field; /* populate the row */ diff --git a/ext/pg_text_decoder.c b/ext/pg_text_decoder.c index a69565905..66be3767e 100644 --- a/ext/pg_text_decoder.c +++ b/ext/pg_text_decoder.c @@ -293,7 +293,7 @@ pg_text_dec_array(t_pg_coder *conv, char *val, int len, int tuple, int field, in } /* - * Document-class: PG::TextDecoder::Identifier < PG::CompositeDecoder + * Document-class: PG::TextDecoder::Identifier < PG::SimpleDecoder * * This is the decoder class for PostgreSQL identifiers. * @@ -305,16 +305,13 @@ pg_text_dec_array(t_pg_coder *conv, char *val, int len, int tuple, int field, in static VALUE pg_text_dec_identifier(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx) { - t_pg_composite_coder *this = (t_pg_composite_coder *)conv; - t_pg_coder_dec_func dec_func = pg_coder_dec_func(this->elem, 0); - /* Return value: array */ VALUE array; VALUE elem; int word_index = 0; int index; /* Use a buffer of the same length, as that will be the worst case */ - char word[len + 1]; + PG_VARIABLE_LENGTH_ARRAY(char, word, len + 1, NAMEDATALEN) /* The current character in the input string. */ char c; @@ -331,7 +328,7 @@ pg_text_dec_identifier(t_pg_coder *conv, char *val, int len, int tuple, int fiel if(c == '.' && openQuote < 2 ) { word[word_index] = 0; - elem = dec_func(conv, word, word_index, tuple, field, enc_idx); + elem = pg_text_dec_string(conv, word, word_index, tuple, field, enc_idx); rb_ary_push(array, elem); openQuote = 0; @@ -353,7 +350,7 @@ pg_text_dec_identifier(t_pg_coder *conv, char *val, int len, int tuple, int fiel } word[word_index] = 0; - elem = dec_func(conv, word, word_index, tuple, field, enc_idx); + elem = pg_text_dec_string(conv, word, word_index, tuple, field, enc_idx); rb_ary_push(array, elem); return array; @@ -412,11 +409,11 @@ init_pg_text_decoder() pg_define_coder( "String", pg_text_dec_string, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder ); /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Bytea", rb_cPG_SimpleDecoder ); */ pg_define_coder( "Bytea", pg_text_dec_bytea, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder ); + /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Identifier", rb_cPG_SimpleDecoder ); */ + pg_define_coder( "Identifier", pg_text_dec_identifier, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder ); /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Array", rb_cPG_CompositeDecoder ); */ pg_define_coder( "Array", pg_text_dec_array, rb_cPG_CompositeDecoder, rb_mPG_TextDecoder ); - /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Identifier", rb_cPG_CompositeDecoder ); */ - pg_define_coder( "Identifier", pg_text_dec_identifier, rb_cPG_CompositeDecoder, rb_mPG_TextDecoder ); /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "FromBase64", rb_cPG_CompositeDecoder ); */ pg_define_coder( "FromBase64", pg_text_dec_from_base64, rb_cPG_CompositeDecoder, rb_mPG_TextDecoder ); } diff --git a/ext/pg_text_encoder.c b/ext/pg_text_encoder.c index 24f7cd72d..eb65b91d0 100644 --- a/ext/pg_text_encoder.c +++ b/ext/pg_text_encoder.c @@ -455,39 +455,34 @@ pg_text_enc_array(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate) } } -static int -quote_identifier_buffer( void *_this, char *p_in, int strlen, char *p_out ){ +static char * +quote_identifier( VALUE value, VALUE out_string, char *current_out ){ + char *p_in = RSTRING_PTR(value); char *ptr1; - char *ptr2; - int backslashs = 0; + size_t strlen = RSTRING_LEN(value); + char *end_capa = current_out; - /* count required backlashs */ + PG_RB_STR_ENSURE_CAPA( out_string, strlen + 2, current_out, end_capa ); + *current_out++ = '"'; for(ptr1 = p_in; ptr1 != p_in + strlen; ptr1++) { - if (*ptr1 == '"'){ - backslashs++; + char c = *ptr1; + if (c == '"'){ + strlen++; + PG_RB_STR_ENSURE_CAPA( out_string, p_in - ptr1 + strlen + 1, current_out, end_capa ); + *current_out++ = '"'; + } else if (c == 0){ + break; } + *current_out++ = c; } + PG_RB_STR_ENSURE_CAPA( out_string, 1, current_out, end_capa ); + *current_out++ = '"'; - ptr1 = p_in + strlen; - ptr2 = p_out + strlen + backslashs + 2; - /* Write end quote */ - *--ptr2 = '"'; - - /* Then store the escaped string on the final position, walking - * right to left, until all backslashs are placed. */ - while( ptr1 != p_in ) { - *--ptr2 = *--ptr1; - if(*ptr2 == '"'){ - *--ptr2 = '"'; - } - } - /* Write start quote */ - *p_out = '"'; - return strlen + backslashs + 2; + return current_out; } static char * -pg_text_enc_array_identifier(t_pg_composite_coder *this, VALUE value, VALUE string, char *out) +pg_text_enc_array_identifier(VALUE value, VALUE string, char *out) { int i; int nr_elems; @@ -498,7 +493,7 @@ pg_text_enc_array_identifier(t_pg_composite_coder *this, VALUE value, VALUE stri for( i=0; ielem, entry, string, out, this->needs_quotation, quote_identifier_buffer, this); + out = quote_identifier(entry, string, out); if( i < nr_elems-1 ){ out = pg_rb_str_ensure_capa( string, 1, out, NULL ); *out++ = '.'; @@ -508,27 +503,29 @@ pg_text_enc_array_identifier(t_pg_composite_coder *this, VALUE value, VALUE stri } /* - * Document-class: PG::TextEncoder::Identifier < PG::CompositeEncoder + * Document-class: PG::TextEncoder::Identifier < PG::SimpleEncoder * * This is the encoder class for PostgreSQL identifiers. * * An Array value can be used for "schema.table.column" type identifiers: * PG::TextEncoder::Identifier.new.encode(['schema', 'table', 'column']) - * => "schema"."table"."column" + * => '"schema"."table"."column"' * + * This encoder can also be used per PG::Connection#quote_ident . */ -static int -pg_text_enc_identifier(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate) +int +pg_text_enc_identifier(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate) { - t_pg_composite_coder *this = (t_pg_composite_coder *)conv; - - *intermediate = rb_str_new(NULL, 0); - out = RSTRING_PTR(*intermediate); - + UNUSED( this ); if( TYPE(value) == T_ARRAY){ - out = pg_text_enc_array_identifier(this, value, *intermediate, out); + *intermediate = rb_str_new(NULL, 0); + out = RSTRING_PTR(*intermediate); + out = pg_text_enc_array_identifier(value, *intermediate, out); } else { - out = quote_string(this->elem, value, *intermediate, out, this->needs_quotation, quote_identifier_buffer, this); + StringValue(value); + *intermediate = rb_str_new(NULL, RSTRING_LEN(value) + 2); + out = RSTRING_PTR(*intermediate); + out = quote_identifier(value, *intermediate, out); } rb_str_set_len( *intermediate, out - RSTRING_PTR(*intermediate) ); return -1; @@ -651,11 +648,11 @@ init_pg_text_encoder() pg_define_coder( "String", pg_coder_enc_to_s, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder ); /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Bytea", rb_cPG_SimpleEncoder ); */ pg_define_coder( "Bytea", pg_text_enc_bytea, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder ); + /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Identifier", rb_cPG_SimpleEncoder ); */ + pg_define_coder( "Identifier", pg_text_enc_identifier, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder ); /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Array", rb_cPG_CompositeEncoder ); */ pg_define_coder( "Array", pg_text_enc_array, rb_cPG_CompositeEncoder, rb_mPG_TextEncoder ); - /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Identifier", rb_cPG_CompositeEncoder ); */ - pg_define_coder( "Identifier", pg_text_enc_identifier, rb_cPG_CompositeEncoder, rb_mPG_TextEncoder ); /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "QuotedLiteral", rb_cPG_CompositeEncoder ); */ pg_define_coder( "QuotedLiteral", pg_text_enc_quoted_literal, rb_cPG_CompositeEncoder, rb_mPG_TextEncoder ); /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "ToBase64", rb_cPG_CompositeEncoder ); */ diff --git a/ext/pg_type_map_by_mri_type.c b/ext/pg_type_map_by_mri_type.c index 3bd0c89f7..69cde00e9 100644 --- a/ext/pg_type_map_by_mri_type.c +++ b/ext/pg_type_map_by_mri_type.c @@ -39,7 +39,7 @@ static VALUE rb_cTypeMapByMriType; typedef struct { t_typemap typemap; struct pg_tmbmt_converter { - FOR_EACH_MRI_TYPE( DECLARE_CODER ); + FOR_EACH_MRI_TYPE( DECLARE_CODER ) } coders; } t_tmbmt; diff --git a/spec/pg/connection_spec.rb b/spec/pg/connection_spec.rb index a92efc1b0..331197047 100755 --- a/spec/pg/connection_spec.rb +++ b/spec/pg/connection_spec.rb @@ -1193,9 +1193,20 @@ expect( escaped.encoding ).to eq( Encoding::ISO8859_1 ) expect( escaped ).to eq( "\"string to\"" ) end + end + it "can quote bigger strings with quote_ident" do + original = "'01234567\"" * 100 + escaped = described_class.quote_ident( original + "\0afterzero" ) + expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" ) end + it "can quote Arrays with quote_ident" do + original = "'01234567\"" + escaped = described_class.quote_ident( [original]*3 ) + expected = ["\"" + original.gsub("\"", "\"\"") + "\""] * 3 + expect( escaped ).to eq( expected.join(".") ) + end describe "Ruby 1.9.x default_internal encoding" do diff --git a/spec/pg/type_spec.rb b/spec/pg/type_spec.rb index 6c664123e..d36cab889 100644 --- a/spec/pg/type_spec.rb +++ b/spec/pg/type_spec.rb @@ -115,6 +115,19 @@ def encode(value) end end + context 'identifier quotation' do + it 'should build an array out of an quoted identifier string' do + quoted_type = PG::TextDecoder::Identifier.new + expect( quoted_type.decode(%["A.".".B"]) ).to eq( ["A.", ".B"] ) + expect( quoted_type.decode(%["'A"".""B'"]) ).to eq( ['\'A"."B\''] ) + end + + it 'should split unquoted identifier string' do + quoted_type = PG::TextDecoder::Identifier.new + expect( quoted_type.decode(%[a.b]) ).to eq( ['a','b'] ) + expect( quoted_type.decode(%[a]) ).to eq( ['a'] ) + end + end it "should raise when decode method is called with wrong args" do expect{ textdec_int.decode() }.to raise_error(ArgumentError) @@ -187,6 +200,15 @@ def encode(value) expect( textenc_bytea.encode("\x00\x01\x02\x03\xef".b) ).to eq( "\\x00010203ef" ) end + context 'identifier quotation' do + it 'should quote and escape identifier' do + quoted_type = PG::TextEncoder::Identifier.new + expect( quoted_type.encode(['schema','table','col']) ).to eq( %["schema"."table"."col"] ) + expect( quoted_type.encode(['A.','.B']) ).to eq( %["A.".".B"] ) + expect( quoted_type.encode(%['A"."B']) ).to eq( %["'A"".""B'"] ) + end + end + it "should encode with ruby encoder" do expect( intenc_incrementer.encode(3) ).to eq( "4 " ) end @@ -378,20 +400,6 @@ def encode(value) array_type = PG::TextDecoder::Array.new elements_type: nil expect( array_type.decode(%[{3,4}]) ).to eq( ['3','4'] ) end - - context 'identifier quotation' do - it 'should build an array out of an quoted identifier string' do - quoted_type = PG::TextDecoder::Identifier.new elements_type: textdec_string - expect( quoted_type.decode(%["A.".".B"]) ).to eq( ["A.", ".B"] ) - expect( quoted_type.decode(%["'A"".""B'"]) ).to eq( ['\'A"."B\''] ) - end - - it 'should split unquoted identifier string' do - quoted_type = PG::TextDecoder::Identifier.new elements_type: textdec_string - expect( quoted_type.decode(%[a.b]) ).to eq( ['a','b'] ) - expect( quoted_type.decode(%[a]) ).to eq( ['a'] ) - end - end end describe '#encode' do @@ -453,22 +461,6 @@ def encode(value) expect( textenc_float_array.encode(1234) ).to eq( "1234" ) end - context 'identifier quotation' do - it 'should quote and escape identifier' do - quoted_type = PG::TextEncoder::Identifier.new elements_type: textenc_string - expect( quoted_type.encode(['schema','table','col']) ).to eq( %["schema"."table"."col"] ) - expect( quoted_type.encode(['A.','.B']) ).to eq( %["A.".".B"] ) - expect( quoted_type.encode(%['A"."B']) ).to eq( %["'A"".""B'"] ) - end - - it 'shouldn\'t quote or escape identifier if requested to not do' do - quoted_type = PG::TextEncoder::Identifier.new elements_type: textenc_string, - needs_quotation: false - expect( quoted_type.encode(['a','b']) ).to eq( %[a.b] ) - expect( quoted_type.encode(%[a.b]) ).to eq( %[a.b] ) - end - end - context 'literal quotation' do it 'should quote and escape literals' do quoted_type = PG::TextEncoder::QuotedLiteral.new elements_type: textenc_string_array