Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to use symbols as field names instead of strings #306

Merged
merged 7 commits into from
Nov 21, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ $hoespec = Hoe.spec 'pg' do

self.spec_extras[:extensions] = [ 'ext/extconf.rb' ]

self.require_ruby_version( '>= 2.0.0' )
self.require_ruby_version( '>= 2.2' )

self.hg_sign_tags = true if self.respond_to?( :hg_sign_tags= )
self.check_history_on_release = true if self.respond_to?( :check_history_on_release= )
Expand Down
13 changes: 11 additions & 2 deletions ext/pg.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ typedef long suseconds_t;
#define RARRAY_AREF(a, i) (RARRAY_PTR(a)[i])
#endif

#define PG_ENC_IDX_BITS 30
#define PG_ENC_IDX_BITS 28

/* The data behind each PG::Connection object */
typedef struct {
Expand All @@ -102,6 +102,8 @@ typedef struct {
VALUE decoder_for_get_copy_data;
/* Ruby encoding index of the client/internal encoding */
int enc_idx : PG_ENC_IDX_BITS;
/* flags controlling Symbol/String field names */
unsigned int flags : 2;

#if defined(_WIN32)
/* File descriptor to be used for rb_w32_unwrap_io_handle() */
Expand Down Expand Up @@ -135,6 +137,9 @@ typedef struct {
*/
unsigned int autoclear : 1;

/* flags controlling Symbol/String field names */
unsigned int flags : 2;

/* Number of fields in fnames[] .
* Set to -1 if fnames[] is not yet initialized.
*/
Expand All @@ -149,7 +154,7 @@ typedef struct {
/* Hash with fnames[] to field number mapping. */
VALUE field_map;

/* List of field names as frozen String objects.
/* List of field names as frozen String or Symbol objects.
* Only valid if nfields != -1
*/
VALUE fnames[0];
Expand All @@ -165,6 +170,10 @@ typedef VALUE (* t_pg_typecast_result)(t_typemap *, VALUE, int, int);
typedef t_pg_coder *(* t_pg_typecast_query_param)(t_typemap *, VALUE, int);
typedef VALUE (* t_pg_typecast_copy_get)( t_typemap *, VALUE, int, int, int );

#define PG_RESULT_FIELD_NAMES_MASK 0x03
#define PG_RESULT_FIELD_NAMES_SYMBOL 0x01
#define PG_RESULT_FIELD_NAMES_STATIC_SYMBOL 0x02

#define PG_CODER_TIMESTAMP_DB_UTC 0x0
#define PG_CODER_TIMESTAMP_DB_LOCAL 0x1
#define PG_CODER_TIMESTAMP_APP_UTC 0x0
Expand Down
59 changes: 59 additions & 0 deletions ext/pg_connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
VALUE rb_cPGconn;
static ID s_id_encode;
static VALUE sym_type, sym_format, sym_value;
static VALUE sym_symbol, sym_string, sym_static_symbol;

static PQnoticeReceiver default_notice_receiver = NULL;
static PQnoticeProcessor default_notice_processor = NULL;
Expand Down Expand Up @@ -4060,6 +4061,58 @@ pgconn_decoder_for_get_copy_data_get(VALUE self)
return this->decoder_for_get_copy_data;
}

/*
* call-seq:
* conn.field_name_type = Symbol
*
* Set default type of field names of results retrieved by this connection.
* It can be set to one of:
* * +:string+ to use String based field names
* * +:symbol+ to use Symbol based field names
*
* The default is +:string+ .
*
* Settings the type of field names affects only future results.
*
* See further description at PG::Result#field_name_type=
*
*/
static VALUE
pgconn_field_name_type_set(VALUE self, VALUE sym)
{
t_pg_connection *this = pg_get_connection( self );

this->flags &= ~PG_RESULT_FIELD_NAMES_MASK;
if( sym == sym_symbol ) this->flags |= PG_RESULT_FIELD_NAMES_SYMBOL;
else if ( sym == sym_static_symbol ) this->flags |= PG_RESULT_FIELD_NAMES_STATIC_SYMBOL;
else if ( sym == sym_string );
else rb_raise(rb_eArgError, "invalid argument %+"PRIsVALUE, sym);

return sym;
}

/*
* call-seq:
* conn.field_name_type -> Symbol
*
* Get type of field names.
*
* See description at #field_name_type=
*/
static VALUE
pgconn_field_name_type_get(VALUE self)
{
t_pg_connection *this = pg_get_connection( self );

if( this->flags & PG_RESULT_FIELD_NAMES_SYMBOL ){
return sym_symbol;
} else if( this->flags & PG_RESULT_FIELD_NAMES_STATIC_SYMBOL ){
return sym_static_symbol;
} else {
return sym_string;
}
}


/*
* Document-class: PG::Connection
Expand All @@ -4071,6 +4124,9 @@ init_pg_connection()
sym_type = ID2SYM(rb_intern("type"));
sym_format = ID2SYM(rb_intern("format"));
sym_value = ID2SYM(rb_intern("value"));
sym_string = ID2SYM(rb_intern("string"));
sym_symbol = ID2SYM(rb_intern("symbol"));
sym_static_symbol = ID2SYM(rb_intern("static_symbol"));

rb_cPGconn = rb_define_class_under( rb_mPG, "Connection", rb_cObject );
rb_include_module(rb_cPGconn, rb_mPGconstants);
Expand Down Expand Up @@ -4255,4 +4311,7 @@ init_pg_connection()
rb_define_method(rb_cPGconn, "encoder_for_put_copy_data", pgconn_encoder_for_put_copy_data_get, 0);
rb_define_method(rb_cPGconn, "decoder_for_get_copy_data=", pgconn_decoder_for_get_copy_data_set, 1);
rb_define_method(rb_cPGconn, "decoder_for_get_copy_data", pgconn_decoder_for_get_copy_data_get, 0);

rb_define_method(rb_cPGconn, "field_name_type=", pgconn_field_name_type_set, 1 );
rb_define_method(rb_cPGconn, "field_name_type", pgconn_field_name_type_get, 0 );
}
118 changes: 104 additions & 14 deletions ext/pg_result.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

#include "pg.h"


VALUE rb_cPGresult;
static VALUE sym_symbol, sym_string, sym_static_symbol;

static VALUE pgresult_type_map_set( VALUE, VALUE );
static t_pg_result *pgresult_get_this( VALUE );
Expand Down Expand Up @@ -190,6 +190,7 @@ pg_new_result2(PGresult *result, VALUE rb_pgconn)
this->nfields = -1;
this->tuple_hash = Qnil;
this->field_map = Qnil;
this->flags = 0;
self = TypedData_Wrap_Struct(rb_cPGresult, &pgresult_type, this);

if( result ){
Expand All @@ -201,6 +202,7 @@ pg_new_result2(PGresult *result, VALUE rb_pgconn)
this->enc_idx = p_conn->enc_idx;
this->typemap = p_typemap->funcs.fit_to_result( typemap, self );
this->p_typemap = DATA_PTR( this->typemap );
this->flags = p_conn->flags;
} else {
this->enc_idx = rb_locale_encindex();
}
Expand Down Expand Up @@ -404,6 +406,29 @@ pgresult_get(VALUE self)
return this->pgresult;
}

static VALUE pg_cstr_to_sym(char *cstr, unsigned int flags, int enc_idx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature of this function could be more clear that it is specific to field names.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shrug! I don't care much about static function names.

{
VALUE fname;
if( flags & PG_RESULT_FIELD_NAMES_SYMBOL ){
#ifndef TRUFFLERUBY
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code here looks correct for :symbol and :string on truffle ruby, but it looks like :static_symbol will produce strings.

rb_encoding *enc = rb_enc_from_index(enc_idx);
fname = rb_check_symbol_cstr(cstr, strlen(cstr), enc);
if( fname == Qnil ){
fname = rb_tainted_str_new2(cstr);
PG_ENCODING_SET_NOCHECK(fname, enc_idx);
fname = rb_str_intern(fname);
}
} else if( flags & PG_RESULT_FIELD_NAMES_STATIC_SYMBOL ){
#endif
rb_encoding *enc = rb_enc_from_index(enc_idx);
fname = ID2SYM(rb_intern3(cstr, strlen(cstr), enc));
} else {
fname = rb_tainted_str_new2(cstr);
PG_ENCODING_SET_NOCHECK(fname, enc_idx);
fname = rb_obj_freeze(fname);
}
return fname;
}

static void pgresult_init_fnames(VALUE self)
{
Expand All @@ -414,12 +439,9 @@ static void pgresult_init_fnames(VALUE self)
int nfields = PQnfields(this->pgresult);

for( i=0; i<nfields; i++ ){
VALUE fname = rb_tainted_str_new2(PQfname(this->pgresult, i));
PG_ENCODING_SET_NOCHECK(fname, this->enc_idx);
this->fnames[i] = rb_obj_freeze(fname);
char *cfname = PQfname(this->pgresult, i);
this->fnames[i] = pg_cstr_to_sym(cfname, this->flags, this->enc_idx);
this->nfields = i + 1;

RB_GC_GUARD(fname);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the call to (or return from?) pg_cstr_to_sym makes this redundant?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A static function like pg_cstr_to_sym() could be inlined by the compiler, so it's not safe to protect the VALUE from being GC'ed. However a call to a non-static function is sufficient to protect it from being GC'ed, so the call to rb_obj_freeze() is (and was) already enough.

}
this->nfields = nfields;
}
Expand Down Expand Up @@ -597,24 +619,25 @@ pgresult_nfields(VALUE self)

/*
* call-seq:
* res.fname( index ) -> String
* res.fname( index ) -> String or Symbol
*
* Returns the name of the column corresponding to _index_.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems worth mentioning field_name_type in these cases where "string or symbol" may be returned.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'll change this.

* Depending on #field_name_type= it's a String or Symbol.
*
*/
static VALUE
pgresult_fname(VALUE self, VALUE index)
{
VALUE fname;
t_pg_result *this = pgresult_get_this_safe(self);
int i = NUM2INT(index);
char *cfname;

if (i < 0 || i >= PQnfields(this->pgresult)) {
rb_raise(rb_eArgError,"invalid field number %d", i);
}

fname = rb_tainted_str_new2(PQfname(this->pgresult, i));
PG_ENCODING_SET_NOCHECK(fname, this->enc_idx);
return rb_obj_freeze(fname);
cfname = PQfname(this->pgresult, i);
return pg_cstr_to_sym(cfname, this->flags, this->enc_idx);
}

/*
Expand Down Expand Up @@ -1115,8 +1138,12 @@ static VALUE
pgresult_field_values( VALUE self, VALUE field )
{
PGresult *result = pgresult_get( self );
const char *fieldname = StringValueCStr( field );
int fnum = PQfnumber( result, fieldname );
const char *fieldname;
int fnum;

if( RB_TYPE_P(field, T_SYMBOL) ) field = rb_sym_to_s( field );
fieldname = StringValueCStr( field );
fnum = PQfnumber( result, fieldname );

if ( fnum < 0 )
rb_raise( rb_eIndexError, "no such field '%s' in result", fieldname );
Expand Down Expand Up @@ -1230,7 +1257,7 @@ pgresult_each(VALUE self)
* call-seq:
* res.fields() -> Array
*
* Returns an array of Strings representing the names of the fields in the result.
* Depending on #field_name_type= returns an array of strings or symbols representing the names of the fields in the result.
*/
static VALUE
pgresult_fields(VALUE self)
Expand Down Expand Up @@ -1466,10 +1493,70 @@ pgresult_stream_each_tuple(VALUE self)
return pgresult_stream_any(self, yield_tuple);
}

/*
* call-seq:
* res.field_name_type = Symbol
*
* Set type of field names specific to this result.
* It can be set to one of:
* * +:string+ to use String based field names
* * +:symbol+ to use Symbol based field names
* * +:static_symbol+ to use pinned Symbol (can not be garbage collected) - Don't use this, it will probably removed in future.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why deprecate :static_symbol the moment it is introduced? Is there a use case for having it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:static_symbol is the classic way to create symbols from C and it is also used by mysql2 gem. So I wanted to be able compare and benchmark it a bit more. So far I think it is mostly obsolete.

*
* The default is retrieved from PG::Connection#field_name_type , which defaults to +:string+ .
*
* This setting affects several result methods:
* * keys of Hash returned by #[] , #each and #stream_each
* * #fields
* * #fname
* * field names used by #tuple and #stream_each_tuple
*
* The type of field names can only be changed before any of the affected methods have been called.
*
*/
static VALUE
pgresult_field_name_type_set(VALUE self, VALUE sym)
{
t_pg_result *this = pgresult_get_this(self);
if( this->nfields != -1 ) rb_raise(rb_eArgError, "field names are already materialized");

this->flags &= ~PG_RESULT_FIELD_NAMES_MASK;
if( sym == sym_symbol ) this->flags |= PG_RESULT_FIELD_NAMES_SYMBOL;
else if ( sym == sym_static_symbol ) this->flags |= PG_RESULT_FIELD_NAMES_STATIC_SYMBOL;
else if ( sym == sym_string );
else rb_raise(rb_eArgError, "invalid argument %+"PRIsVALUE, sym);

return sym;
}

/*
* call-seq:
* res.field_name_type -> Symbol
*
* Get type of field names.
*
* See description at #field_name_type=
*/
static VALUE
pgresult_field_name_type_get(VALUE self)
{
t_pg_result *this = pgresult_get_this(self);
if( this->flags & PG_RESULT_FIELD_NAMES_SYMBOL ){
return sym_symbol;
} else if( this->flags & PG_RESULT_FIELD_NAMES_STATIC_SYMBOL ){
return sym_static_symbol;
} else {
return sym_string;
}
}

void
init_pg_result()
{
sym_string = ID2SYM(rb_intern("string"));
sym_symbol = ID2SYM(rb_intern("symbol"));
sym_static_symbol = ID2SYM(rb_intern("static_symbol"));

rb_cPGresult = rb_define_class_under( rb_mPG, "Result", rb_cData );
rb_include_module(rb_cPGresult, rb_mEnumerable);
rb_include_module(rb_cPGresult, rb_mPGconstants);
Expand Down Expand Up @@ -1526,4 +1613,7 @@ init_pg_result()
rb_define_method(rb_cPGresult, "stream_each", pgresult_stream_each, 0);
rb_define_method(rb_cPGresult, "stream_each_row", pgresult_stream_each_row, 0);
rb_define_method(rb_cPGresult, "stream_each_tuple", pgresult_stream_each_tuple, 0);

rb_define_method(rb_cPGresult, "field_name_type=", pgresult_field_name_type_set, 1 );
rb_define_method(rb_cPGresult, "field_name_type", pgresult_field_name_type_get, 0 );
}
13 changes: 10 additions & 3 deletions ext/pg_tuple.c
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ pg_tuple_materialize(t_pg_tuple *this)
* An integer +key+ is interpreted as column index.
* Negative values of index count from the end of the array.
*
* A string +key+ is interpreted as column name.
* Depending on Result#field_name_type= a string or symbol +key+ is interpreted as column name.
*
* If the key can't be found, there are several options:
* With no other arguments, it will raise a IndexError exception;
Expand Down Expand Up @@ -265,9 +265,16 @@ pg_tuple_fetch(int argc, VALUE *argv, VALUE self)

/*
* call-seq:
* res[ name ] -> value
* tup[ key ] -> value
*
* Returns field _name_.
* Returns a field value by either column index or column name.
*
* An integer +key+ is interpreted as column index.
* Negative values of index count from the end of the array.
*
* Depending on Result#field_name_type= a string or symbol +key+ is interpreted as column name.
*
* If the key can't be found, it returns +nil+ .
*/
static VALUE
pg_tuple_aref(VALUE self, VALUE key)
Expand Down
13 changes: 12 additions & 1 deletion lib/pg/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,23 @@ class PG::Result
#
# +type_map+: a PG::TypeMap instance.
#
# See PG::BasicTypeMapForResults
# This method is equal to #type_map= , but returns self, so that calls can be chained.
#
# See also PG::BasicTypeMapForResults
def map_types!(type_map)
self.type_map = type_map
return self
end

# Set the data type for all field name returning methods.
#
# +type+: a Symbol defining the field name type.
#
# This method is equal to #field_name_type= , but returns self, so that calls can be chained.
def field_names_as(type)
self.field_name_type = type
return self
end

### Return a String representation of the object suitable for debugging.
def inspect
Expand Down
Loading