sequel_pg-1.12.2/0000755000175000017500000000000013476233314013131 5ustar josephjosephsequel_pg-1.12.2/Rakefile0000644000175000017500000000056213476233314014601 0ustar josephjosephrequire "rake" require "rake/clean" CLEAN.include %w'**.rbc rdoc' desc "Do a full cleaning" task :distclean do CLEAN.include %w'tmp pkg sequel_pg*.gem lib/*.so' Rake::Task[:clean].invoke end desc "Build the gem" task :gem do sh %{gem build sequel_pg.gemspec} end begin require 'rake/extensiontask' Rake::ExtensionTask.new('sequel_pg') rescue LoadError end sequel_pg-1.12.2/.gitignore0000644000175000017500000000017713476233314015126 0ustar josephjoseph/ext/sequel_pg/Makefile /ext/sequel_pg/mkmf.log /ext/sequel_pg/sequel_pg.*o /ext/sequel_pg/1.* /pkg /tmp /lib/*.so *.gem *.rbc sequel_pg-1.12.2/lib/0000755000175000017500000000000013476233314013677 5ustar josephjosephsequel_pg-1.12.2/lib/sequel/0000755000175000017500000000000013476233314015175 5ustar josephjosephsequel_pg-1.12.2/lib/sequel/extensions/0000755000175000017500000000000013476233314017374 5ustar josephjosephsequel_pg-1.12.2/lib/sequel/extensions/pg_streaming.rb0000644000175000017500000001040613476233314022401 0ustar josephjosephunless Sequel::Postgres.respond_to?(:supports_streaming?) raise LoadError, "either sequel_pg not loaded, or an old version of sequel_pg loaded" end unless Sequel::Postgres.supports_streaming? raise LoadError, "streaming is not supported by the version of libpq in use" end # Database methods necessary to support streaming. You should load this extension # into your database object: # # DB.extension(:pg_streaming) # # Then you can call #stream on your datasets to use the streaming support: # # DB[:table].stream.each{|row| ...} # # Or change a set so that all dataset calls use streaming: # # DB.stream_all_queries = true module Sequel::Postgres::Streaming attr_accessor :stream_all_queries # Also extend the database's datasets to support streaming. # This extension requires modifying connections, so disconnect # so that new connections will get the methods. def self.extended(db) db.extend_datasets(DatasetMethods) db.stream_all_queries = false db.disconnect end # Make sure all new connections have the appropriate methods added. def connect(server) conn = super conn.extend(AdapterMethods) conn end private # If streaming is requested, and a prepared statement is not # used, tell the connection to use single row mode for the query. def _execute(conn, sql, opts={}, &block) if opts[:stream] && !sql.is_a?(Symbol) conn.single_row_mode = true end super end # If streaming is requested, send the prepared statement instead # of executing it and blocking. def _execute_prepared_statement(conn, ps_name, args, opts) if opts[:stream] conn.send_prepared_statement(ps_name, args) else super end end module AdapterMethods # Whether the next query on this connection should use # single_row_mode. attr_accessor :single_row_mode # Send the prepared statement on this connection using # single row mode. def send_prepared_statement(ps_name, args) send_query_prepared(ps_name, args) set_single_row_mode block self end private if Sequel::Database.instance_methods.map(&:to_s).include?('log_connection_yield') # If using single row mode, send the query instead of executing it. def execute_query(sql, args) if @single_row_mode @single_row_mode = false @db.log_connection_yield(sql, self, args){args ? send_query(sql, args) : send_query(sql)} set_single_row_mode block self else super end end else def execute_query(sql, args) if @single_row_mode @single_row_mode = false @db.log_yield(sql, args){args ? send_query(sql, args) : send_query(sql)} set_single_row_mode block self else super end end end end # Dataset methods used to implement streaming. module DatasetMethods # If streaming has been requested and the current dataset # can be streamed, request the database use streaming when # executing this query, and use yield_each_row to process # the separate PGresult for each row in the connection. def fetch_rows(sql) if stream_results? execute(sql, :stream=>true) do |conn| yield_each_row(conn){|h| yield h} end else super end end # Use streaming to implement paging. def paged_each(opts=Sequel::OPTS, &block) unless block_given? return enum_for(:paged_each, opts) end stream.each(&block) end # Return a clone of the dataset that will use streaming to load # rows. def stream clone(:stream=>true) end private # Only stream results if streaming has been specifically requested # and the query is streamable. def stream_results? (@opts[:stream] || db.stream_all_queries) && streamable? end # Queries using cursors are not streamable, and queries that use # the map/select_map/to_hash/to_hash_groups optimizations are not # streamable, but other queries are streamable. def streamable? spgt = (o = @opts)[:_sequel_pg_type] (spgt.nil? || spgt == :model) && !o[:cursor] end end end Sequel::Database.register_extension(:pg_streaming, Sequel::Postgres::Streaming) sequel_pg-1.12.2/lib/sequel_pg/0000755000175000017500000000000013476233314015663 5ustar josephjosephsequel_pg-1.12.2/lib/sequel_pg/sequel_pg.rb0000644000175000017500000001021213476233314020170 0ustar josephjoseph# Add speedup for model class creation from dataset class Sequel::Postgres::Database def optimize_model_load=(v) Sequel::Deprecation.deprecate("Database#optimize_model_load= is deprecated. Optimized model loading is now enabled by default, and can only be disabled on a per-Dataset basis.") v end def optimize_model_load Sequel::Deprecation.deprecate("Database#optimize_model_load is deprecated. Optimized model loading is now enabled by default, and can only be disabled on a per-Dataset basis.") true end end # Add faster versions of Dataset#map, #as_hash, #to_hash_groups, #select_map, #select_order_map, and #select_hash class Sequel::Postgres::Dataset def optimize_model_load=(v) Sequel::Deprecation.deprecate("Dataset#optimize_model_load= mutation method is deprecated. Switch to using Dataset#with_optimize_model_load, which returns a modified dataset") opts[:optimize_model_load] = v end def optimize_model_load Sequel::Deprecation.deprecate("Dataset#optimize_model_load method is deprecated. Optimized model loading is enabled by default.") opts.has_key?(:optimize_model_load) ? opts[:optimize_model_load] : true end # In the case where an argument is given, use an optimized version. def map(sym=nil) if sym if block_given? super else rows = [] clone(:_sequel_pg_type=>:map, :_sequel_pg_value=>sym).fetch_rows(sql){|s| rows << s} rows end else super end end # Return a modified copy with the optimize_model_load setting changed. def with_optimize_model_load(v) clone(:optimize_model_load=>v) end # In the case where both arguments given, use an optimized version. def as_hash(key_column, value_column = nil, opts = Sequel::OPTS) if value_column && !opts[:hash] clone(:_sequel_pg_type=>:hash, :_sequel_pg_value=>[key_column, value_column]).fetch_rows(sql){|s| return s} {} elsif opts.empty? super(key_column, value_column) else super end end unless Sequel::Dataset.method_defined?(:as_hash) # Handle previous versions of Sequel that use to_hash instead of as_hash alias to_hash as_hash remove_method :as_hash end # In the case where both arguments given, use an optimized version. def to_hash_groups(key_column, value_column = nil, opts = Sequel::OPTS) if value_column && !opts[:hash] clone(:_sequel_pg_type=>:hash_groups, :_sequel_pg_value=>[key_column, value_column]).fetch_rows(sql){|s| return s} {} elsif opts.empty? super(key_column, value_column) else super end end if defined?(Sequel::Model::ClassMethods) # If model loads are being optimized and this is a model load, use the optimized # version. def each(&block) if optimize_model_load? clone(:_sequel_pg_type=>:model, :_sequel_pg_value=>row_proc).fetch_rows(sql, &block) else super end end end protected # Always use optimized version def _select_map_multiple(ret_cols) rows = [] clone(:_sequel_pg_type=>:array).fetch_rows(sql){|s| rows << s} rows end # Always use optimized version def _select_map_single rows = [] clone(:_sequel_pg_type=>:first).fetch_rows(sql){|s| rows << s} rows end private if defined?(Sequel::Model::ClassMethods) # The model load can only be optimized if it's for a model and it's not a graphed dataset # or using a cursor. def optimize_model_load? (rp = row_proc) && rp.is_a?(Class) && rp < Sequel::Model && rp.method(:call).owner == Sequel::Model::ClassMethods && opts[:optimize_model_load] != false && !opts[:use_cursor] && !opts[:graph] end end end if defined?(Sequel::Postgres::PGArray) # pg_array extension previously loaded class Sequel::Postgres::PGArray::Creator # Override Creator to use sequel_pg's C-based parser instead of the pure ruby parser. def call(string) Sequel::Postgres::PGArray.new(Sequel::Postgres.parse_pg_array(string, @converter), @type) end end # Remove the pure-ruby parser, no longer needed. Sequel::Postgres::PGArray.send(:remove_const, :Parser) end sequel_pg-1.12.2/sequel_pg.gemspec0000644000175000017500000000356013476233314016466 0ustar josephjosephversion_integer = File.readlines('ext/sequel_pg/sequel_pg.c').first.split.last.to_i raise "invalid version" unless version_integer >= 10617 SEQUEL_PG_GEMSPEC = Gem::Specification.new do |s| s.name = 'sequel_pg' s.version = "#{version_integer/10000}.#{(version_integer%10000)/100}.#{version_integer%100}" s.platform = Gem::Platform::RUBY s.extra_rdoc_files = ["README.rdoc", "CHANGELOG", "MIT-LICENSE"] s.rdoc_options += ["--quiet", "--line-numbers", "--inline-source", '--title', 'sequel_pg: Faster SELECTs when using Sequel with pg', '--main', 'README.rdoc'] s.summary = "Faster SELECTs when using Sequel with pg" s.author = "Jeremy Evans" s.email = "code@jeremyevans.net" s.homepage = "http://github.com/jeremyevans/sequel_pg" s.required_ruby_version = ">= 1.9.3" s.files = %w(MIT-LICENSE CHANGELOG README.rdoc Rakefile ext/sequel_pg/extconf.rb ext/sequel_pg/sequel_pg.c lib/sequel_pg/sequel_pg.rb lib/sequel/extensions/pg_streaming.rb) s.license = 'MIT' s.extensions << 'ext/sequel_pg/extconf.rb' s.add_dependency("pg", [">= 0.18.0"]) s.add_dependency("sequel", [">= 4.38.0"]) s.metadata = { 'bug_tracker_uri' => 'https://github.com/jeremyevans/sequel_pg/issues', 'changelog_uri' => 'https://github.com/jeremyevans/sequel_pg/blob/master/CHANGELOG', 'documentation_uri' => 'https://github.com/jeremyevans/sequel_pg/blob/master/README.rdoc', 'mailing_list_uri' => 'https://groups.google.com/forum/#!forum/sequel-talk', 'source_code_uri' => 'https://github.com/jeremyevans/sequel_pg', } s.description = < 202261 Album.count # => 7264 Without sequel_pg: puts Benchmark.measure{Track.each{}} # 3.400000 0.290000 3.690000 ( 4.005150) puts Benchmark.measure{10.times{Album.each{}}} # 2.180000 0.120000 2.300000 ( 2.479352) With sequel_pg: puts Benchmark.measure{Track.each{}} # 1.660000 0.260000 1.920000 ( 2.287216) puts Benchmark.measure{10.times{Album.each{}}} # 0.960000 0.110000 1.070000 ( 1.260913) sequel_pg also speeds up the following Dataset methods: * map * as_hash/to_hash * to_hash_groups * select_hash * select_hash_groups * select_map * select_order_map Additionally, in most cases sequel_pg also speeds up the loading of model datasets by optimizing model instance creation. == Streaming If you are using PostgreSQL 9.2+ on the client, then sequel_pg should enable streaming support. This allows you to stream returned rows one at a time, instead of collecting the entire result set in memory (which is how PostgreSQL works by default). You can check if streaming is supported by: Sequel::Postgres.supports_streaming? If streaming is supported, you can load the streaming support into the database: DB.extension(:pg_streaming) Then you can call the Dataset#stream method to have the dataset use the streaming support: DB[:table].stream.each{|row| ...} If you want to enable streaming for all of a database's datasets, you can do the following: DB.stream_all_queries = true == Installing the gem gem install sequel_pg Note that by default sequel_pg only supports result sets with up to 256 columns. If you will have a result set with more than 256 columns, you should modify the maximum supported number of columns via: gem install sequel_pg -- --with-cflags=\"-DSPG_MAX_FIELDS=512\" Make sure the pg_config binary is in your PATH so the installation can find the PostgreSQL shared library and header files. Alternatively, you can use the POSTGRES_LIB and POSTGRES_INCLUDE environment variables to specify the shared library and header directories. == Running the specs sequel_pg doesn't ship with it's own specs. It's designed to replace a part of Sequel, so it just uses Sequel's specs. Specifically, the spec_postgres rake task from Sequel. == Reporting issues/bugs sequel_pg uses GitHub Issues for tracking issues/bugs: http://github.com/jeremyevans/sequel_pg/issues == Contributing The source code is on GitHub: http://github.com/jeremyevans/sequel_pg To get a copy: git clone git://github.com/jeremyevans/sequel_pg.git There are only a few requirements, which you should probably have before considering use of the library: * Rake * Sequel * pg * libpq headers and library == Building To build the library from a git checkout, after installing the requirements: rake build == Platforms Supported sequel_pg has been tested on the following: * ruby 1.9.3 * ruby 2.0 * ruby 2.1 * ruby 2.2 * ruby 2.3 * ruby 2.4 * ruby 2.5 * ruby 2.6 == Known Issues * You must be using the ISO PostgreSQL date format (which is the default). Using the SQL, POSTGRESQL, or GERMAN date formats will result in incorrect date/timestamp handling. In addition to PostgreSQL defaulting to ISO, Sequel also manually sets the date format to ISO by default, so unless you are overriding that setting (via DB.use_iso_date_format = false), you should be OK. * Adding your own type conversion procs only has an effect if those types are not handled by default. * You do not need to require the library, the sequel postgres adapter will require it automatically. If you are using bundler, you should add it to your Gemfile like so: gem 'sequel_pg', :require=>'sequel' * sequel_pg currently calls functions defined in the pg gem, which does not work on Windows and does not work in some unix-like operating systems that disallow undefined functions in shared libraries. If RbConfig::CONFIG['LDFLAGS'] contains -Wl,--no-undefined, you'll probably have issues installing sequel_pg. You should probably fix RbConfig::CONFIG['LDFLAGS'] in that case. == Author Jeremy Evans sequel_pg-1.12.2/ext/0000755000175000017500000000000013476233314013731 5ustar josephjosephsequel_pg-1.12.2/ext/sequel_pg/0000755000175000017500000000000013476233314015715 5ustar josephjosephsequel_pg-1.12.2/ext/sequel_pg/sequel_pg.c0000644000175000017500000015534513476233314020062 0ustar josephjoseph#define SEQUEL_PG_VERSION_INTEGER 11202 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef SPG_MAX_FIELDS #define SPG_MAX_FIELDS 256 #endif #define SPG_MINUTES_PER_DAY 1440.0 #define SPG_SECONDS_PER_DAY 86400.0 #define SPG_DT_ADD_USEC if (usec != 0) { dt = rb_funcall(dt, spg_id_op_plus, 1, rb_Rational2(INT2NUM(usec), spg_usec_per_day)); } #ifndef RARRAY_AREF #define RARRAY_AREF(a, i) (RARRAY_PTR(a)[i]) #endif #define ntohll(c) ((uint64_t)( \ (((uint64_t)(*((unsigned char*)(c)+0)))<<56LL) | \ (((uint64_t)(*((unsigned char*)(c)+1)))<<48LL) | \ (((uint64_t)(*((unsigned char*)(c)+2)))<<40LL) | \ (((uint64_t)(*((unsigned char*)(c)+3)))<<32LL) | \ (((uint64_t)(*((unsigned char*)(c)+4)))<<24LL) | \ (((uint64_t)(*((unsigned char*)(c)+5)))<<16LL) | \ (((uint64_t)(*((unsigned char*)(c)+6)))<< 8LL) | \ (((uint64_t)(*((unsigned char*)(c)+7))) ) \ )) #define SPG_DB_LOCAL (1) #define SPG_DB_UTC (1<<1) #define SPG_DB_CUSTOM (1<<2) #define SPG_APP_LOCAL (1<<3) #define SPG_APP_UTC (1<<4) #define SPG_APP_CUSTOM (1<<5) #define SPG_TZ_INITIALIZED (1<<6) #define SPG_USE_TIME (1<<7) #define SPG_HAS_TIMEZONE (1<<8) #define SPG_YEAR_SHIFT 16 #define SPG_MONTH_SHIFT 8 #define SPG_MONTH_MASK 0x0000ffff #define SPG_DAY_MASK 0x0000001f #define SPG_TIME_UTC 32 #define SPG_YIELD_NORMAL 0 #define SPG_YIELD_COLUMN 1 #define SPG_YIELD_COLUMNS 2 #define SPG_YIELD_FIRST 3 #define SPG_YIELD_ARRAY 4 #define SPG_YIELD_KV_HASH 5 #define SPG_YIELD_MKV_HASH 6 #define SPG_YIELD_KMV_HASH 7 #define SPG_YIELD_MKMV_HASH 8 #define SPG_YIELD_MODEL 9 #define SPG_YIELD_KV_HASH_GROUPS 10 #define SPG_YIELD_MKV_HASH_GROUPS 11 #define SPG_YIELD_KMV_HASH_GROUPS 12 #define SPG_YIELD_MKMV_HASH_GROUPS 13 /* External functions defined by ruby-pg */ PGconn* pg_get_pgconn(VALUE); PGresult* pgresult_get(VALUE); static int spg_use_ipaddr_alloc; static VALUE spg_Sequel; static VALUE spg_PGArray; static VALUE spg_Blob; static VALUE spg_Blob_instance; static VALUE spg_Date; static VALUE spg_DateTime; static VALUE spg_SQLTime; static VALUE spg_PGError; static VALUE spg_IPAddr; static VALUE spg_vmasks4; static VALUE spg_vmasks6; static VALUE spg_sym_utc; static VALUE spg_sym_local; static VALUE spg_sym_map; static VALUE spg_sym_first; static VALUE spg_sym_array; static VALUE spg_sym_hash; static VALUE spg_sym_hash_groups; static VALUE spg_sym_model; static VALUE spg_sym__sequel_pg_type; static VALUE spg_sym__sequel_pg_value; static VALUE spg_sym_text; static VALUE spg_sym_character_varying; static VALUE spg_sym_integer; static VALUE spg_sym_timestamp; static VALUE spg_sym_timestamptz; static VALUE spg_sym_time; static VALUE spg_sym_timetz; static VALUE spg_sym_bigint; static VALUE spg_sym_numeric; static VALUE spg_sym_double_precision; static VALUE spg_sym_boolean; static VALUE spg_sym_bytea; static VALUE spg_sym_date; static VALUE spg_sym_smallint; static VALUE spg_sym_oid; static VALUE spg_sym_real; static VALUE spg_sym_xml; static VALUE spg_sym_money; static VALUE spg_sym_bit; static VALUE spg_sym_bit_varying; static VALUE spg_sym_uuid; static VALUE spg_sym_xid; static VALUE spg_sym_cid; static VALUE spg_sym_name; static VALUE spg_sym_tid; static VALUE spg_sym_int2vector; static VALUE spg_sym_inet; static VALUE spg_sym_cidr; static VALUE spg_nan; static VALUE spg_pos_inf; static VALUE spg_neg_inf; static VALUE spg_usec_per_day; static ID spg_id_BigDecimal; static ID spg_id_new; static ID spg_id_date; static ID spg_id_local; static ID spg_id_year; static ID spg_id_month; static ID spg_id_day; static ID spg_id_output_identifier; static ID spg_id_datetime_class; static ID spg_id_application_timezone; static ID spg_id_to_application_timestamp; static ID spg_id_timezone; static ID spg_id_op_plus; static ID spg_id_utc; static ID spg_id_utc_offset; static ID spg_id_localtime; static ID spg_id_new_offset; static ID spg_id_convert_infinite_timestamps; static ID spg_id_infinite_timestamp_value; static ID spg_id_call; static ID spg_id_get; static ID spg_id_opts; static ID spg_id_db; static ID spg_id_conversion_procs; static ID spg_id_columns_equal; static ID spg_id_columns; static ID spg_id_encoding; static ID spg_id_values; static ID spg_id_lshift; static ID spg_id_mask; static ID spg_id_family; static ID spg_id_addr; static ID spg_id_mask_addr; #if HAVE_PQSETSINGLEROWMODE static ID spg_id_get_result; static ID spg_id_clear; static ID spg_id_check; #endif struct spg_blob_initialization { char *blob_string; size_t length; }; static int enc_get_index(VALUE val) { int i = ENCODING_GET_INLINED(val); if (i == ENCODING_INLINE_MAX) { i = NUM2INT(rb_ivar_get(val, spg_id_encoding)); } return i; } #define PG_ENCODING_SET_NOCHECK(obj,i) \ do { \ if ((i) < ENCODING_INLINE_MAX) \ ENCODING_SET_INLINED((obj), (i)); \ else \ rb_enc_set_index((obj), (i)); \ } while(0) static VALUE pg_text_dec_integer(char *val, int len) { long i; int max_len; if( sizeof(i) >= 8 && FIXNUM_MAX >= 1000000000000000000LL ){ /* 64 bit system can safely handle all numbers up to 18 digits as Fixnum */ max_len = 18; } else if( sizeof(i) >= 4 && FIXNUM_MAX >= 1000000000LL ){ /* 32 bit system can safely handle all numbers up to 9 digits as Fixnum */ max_len = 9; } else { /* unknown -> don't use fast path for int conversion */ max_len = 0; } if( len <= max_len ){ /* rb_cstr2inum() seems to be slow, so we do the int conversion by hand. * This proved to be 40% faster by the following benchmark: * * conn.type_mapping_for_results = PG::BasicTypeMapForResults.new conn * Benchmark.measure do * conn.exec("select generate_series(1,1000000)").values } * end */ char *val_pos = val; char digit = *val_pos; int neg; int error = 0; if( digit=='-' ){ neg = 1; i = 0; }else if( digit>='0' && digit<='9' ){ neg = 0; i = digit - '0'; } else { error = 1; } while (!error && (digit=*++val_pos)) { if( digit>='0' && digit<='9' ){ i = i * 10 + (digit - '0'); } else { error = 1; } } if( !error ){ return LONG2FIX(neg ? -i : i); } } /* Fallback to ruby method if number too big or unrecognized. */ return rb_cstr2inum(val, 10); } static VALUE spg__array_col_value(char *v, size_t length, VALUE converter, int enc_index, int oid, VALUE db); static VALUE read_array(int *index, char *c_pg_array_string, int array_string_length, VALUE buf, VALUE converter, int enc_index, int oid, VALUE db) { int word_index = 0; char *word = RSTRING_PTR(buf); /* The current character in the input string. */ char c; /* 0: Currently outside a quoted string, current word never quoted * 1: Currently inside a quoted string * -1: Currently outside a quoted string, current word previously quoted */ int openQuote = 0; /* Inside quoted input means the next character should be treated literally, * instead of being treated as a metacharacter. * Outside of quoted input, means that the word shouldn't be pushed to the array, * used when the last entry was a subarray (which adds to the array itself). */ int escapeNext = 0; VALUE array = rb_ary_new(); RB_GC_GUARD(array); /* Special case the empty array, so it doesn't need to be handled manually inside * the loop. */ if(((*index) < array_string_length) && c_pg_array_string[(*index)] == '}') { return array; } for(;(*index) < array_string_length; ++(*index)) { c = c_pg_array_string[*index]; if(openQuote < 1) { if(c == ',' || c == '}') { if(!escapeNext) { if(openQuote == 0 && word_index == 4 && !strncmp(word, "NULL", word_index)) { rb_ary_push(array, Qnil); } else { word[word_index] = '\0'; rb_ary_push(array, spg__array_col_value(word, word_index, converter, enc_index, oid, db)); } } if(c == '}') { return array; } escapeNext = 0; openQuote = 0; word_index = 0; } else if(c == '"') { openQuote = 1; } else if(c == '{') { (*index)++; rb_ary_push(array, read_array(index, c_pg_array_string, array_string_length, buf, converter, enc_index, oid, db)); escapeNext = 1; } else { word[word_index] = c; word_index++; } } else if (escapeNext) { word[word_index] = c; word_index++; escapeNext = 0; } else if (c == '\\') { escapeNext = 1; } else if (c == '"') { openQuote = -1; } else { word[word_index] = c; word_index++; } } RB_GC_GUARD(buf); return array; } static VALUE check_pg_array(int* index, char *c_pg_array_string, int array_string_length) { if (array_string_length == 0) { rb_raise(rb_eArgError, "unexpected PostgreSQL array format, empty"); } else if (array_string_length == 2 && c_pg_array_string[0] == '{' && c_pg_array_string[0] == '}') { return rb_ary_new(); } switch (c_pg_array_string[0]) { case '[': /* Skip explicit subscripts, scanning until opening array */ for(;(*index) < array_string_length && c_pg_array_string[(*index)] != '{'; ++(*index)) /* nothing */; if ((*index) >= array_string_length) { rb_raise(rb_eArgError, "unexpected PostgreSQL array format, no {"); } else { ++(*index); } case '{': break; default: rb_raise(rb_eArgError, "unexpected PostgreSQL array format, doesn't start with { or ["); } return Qnil; } static VALUE parse_pg_array(VALUE self, VALUE pg_array_string, VALUE converter) { /* convert to c-string, create additional ruby string buffer of * the same length, as that will be the worst case. */ char *c_pg_array_string = StringValueCStr(pg_array_string); int array_string_length = RSTRING_LEN(pg_array_string); int index = 1; VALUE ary; if(RTEST(ary = check_pg_array(&index, c_pg_array_string, array_string_length))) { return ary; } ary = rb_str_buf_new(array_string_length); rb_str_set_len(ary, array_string_length); rb_obj_freeze(ary); return read_array(&index, c_pg_array_string, array_string_length, ary, converter, enc_get_index(pg_array_string), 0, Qnil); } static VALUE spg_timestamp_error(const char *s, VALUE self, const char *error_msg) { self = rb_funcall(self, spg_id_db, 0); if(RTEST(rb_funcall(self, spg_id_convert_infinite_timestamps, 0))) { if((strcmp(s, "infinity") == 0) || (strcmp(s, "-infinity") == 0)) { return rb_funcall(self, spg_id_infinite_timestamp_value, 1, rb_tainted_str_new2(s)); } } rb_raise(rb_eArgError, "%s", error_msg); } static inline int char_to_digit(char c) { return c - '0'; } static int str4_to_int(const char *str) { return char_to_digit(str[0]) * 1000 + char_to_digit(str[1]) * 100 + char_to_digit(str[2]) * 10 + char_to_digit(str[3]); } static int str2_to_int(const char *str) { return char_to_digit(str[0]) * 10 + char_to_digit(str[1]); } static VALUE spg_time(const char *p, size_t length, int current) { int hour, minute, second, i; int usec = 0; ID meth = spg_id_local; if (length < 8) { rb_raise(rb_eArgError, "unexpected time format, too short"); } if (p[2] == ':' && p[5] == ':') { hour = str2_to_int(p); minute = str2_to_int(p+3); second = str2_to_int(p+6); p += 8; if (length >= 10 && p[0] == '.') { static const int coef[6] = { 100000, 10000, 1000, 100, 10, 1 }; p++; for (i = 0; i < 6 && isdigit(*p); i++) { usec += coef[i] * char_to_digit(*p++); } } } else { rb_raise(rb_eArgError, "unexpected time format"); } if (current & SPG_TIME_UTC) { meth = spg_id_utc; } return rb_funcall(spg_SQLTime, meth, 7, INT2NUM(current >> SPG_YEAR_SHIFT), INT2NUM((current & SPG_MONTH_MASK) >> SPG_MONTH_SHIFT), INT2NUM(current & SPG_DAY_MASK), INT2NUM(hour), INT2NUM(minute), INT2NUM(second), INT2NUM(usec)); } /* Caller should check length is at least 4 */ static int parse_year(const char **str, size_t *length) { int year, i; size_t remaining = *length; const char * p = *str; year = str4_to_int(p); p += 4; remaining -= 4; for(i = 0; isdigit(*p) && i < 3; i++, p++, remaining--) { year = 10 * year + char_to_digit(*p); } *str = p; *length = remaining; return year; } static VALUE spg_date(const char *s, VALUE self, size_t length) { int year, month, day; const char *p = s; if (length < 10) { return spg_timestamp_error(s, self, "unexpected date format, too short"); } year = parse_year(&p, &length); if (length >= 5 && p[0] == '-' && p[3] == '-') { month = str2_to_int(p+1); day = str2_to_int(p+4); } else { return spg_timestamp_error(s, self, "unexpected date format"); } if(s[10] == ' ' && s[11] == 'B' && s[12] == 'C') { year = -year; year++; } return rb_funcall(spg_Date, spg_id_new, 3, INT2NUM(year), INT2NUM(month), INT2NUM(day)); } static VALUE spg_timestamp(const char *s, VALUE self, size_t length, int tz) { VALUE dt; int year, month, day, hour, min, sec, utc_offset; char offset_sign = 0; int offset_seconds = 0; int usec = 0; int i; const char *p = s; size_t remaining = length; if (tz & SPG_DB_CUSTOM || tz & SPG_APP_CUSTOM) { return rb_funcall(rb_funcall(self, spg_id_db, 0), spg_id_to_application_timestamp, 1, rb_str_new2(s)); } if (remaining < 19) { return spg_timestamp_error(s, self, "unexpected timetamp format, too short"); } year = parse_year(&p, &remaining); if (remaining >= 15 && p[0] == '-' && p[3] == '-' && p[6] == ' ' && p[9] == ':' && p[12] == ':') { month = str2_to_int(p+1); day = str2_to_int(p+4); hour = str2_to_int(p+7); min = str2_to_int(p+10); sec = str2_to_int(p+13); p += 15; remaining -= 15; if (remaining >= 2 && p[0] == '.') { /* microsecond part, up to 6 digits */ static const int coef[6] = { 100000, 10000, 1000, 100, 10, 1 }; p++; remaining--; for (i = 0; i < 6 && isdigit(*p); i++) { usec += coef[i] * char_to_digit(*p++); remaining--; } } if ((tz & SPG_HAS_TIMEZONE) && remaining >= 3 && (p[0] == '+' || p[0] == '-')) { offset_sign = p[0]; offset_seconds += str2_to_int(p+1)*3600; p += 3; remaining -= 3; if (p[0] == ':') { p++; remaining--; } if (remaining >= 2 && isdigit(p[0]) && isdigit(p[1])) { offset_seconds += str2_to_int(p)*60; p += 2; remaining -= 2; } if (p[0] == ':') { p++; remaining--; } if (remaining >= 2 && isdigit(p[0]) && isdigit(p[1])) { offset_seconds += str2_to_int(p); p += 2; remaining -= 2; } if (offset_sign == '-') { offset_seconds *= -1; } } if (remaining == 3 && p[0] == ' ' && p[1] == 'B' && p[2] == 'C') { year = -year; year++; } else if (remaining != 0) { return spg_timestamp_error(s, self, "unexpected timestamp format, remaining data left"); } } else { return spg_timestamp_error(s, self, "unexpected timestamp format"); } if (tz & SPG_USE_TIME) { #if (RUBY_API_VERSION_MAJOR > 2 || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 3)) && defined(HAVE_TIMEGM) /* Fast path for time conversion */ struct tm tm; struct timespec ts; time_t time; tm.tm_year = year - 1900; tm.tm_mon = month - 1; tm.tm_mday = day; tm.tm_hour = hour; tm.tm_min = min; tm.tm_sec = sec; tm.tm_isdst = -1; ts.tv_nsec = usec*1000; if (offset_sign) { time = timegm(&tm); if (time != -1) { ts.tv_sec = time - offset_seconds; dt = rb_time_timespec_new(&ts, offset_seconds); if (tz & SPG_APP_UTC) { dt = rb_funcall(dt, spg_id_utc, 0); } else if (tz & SPG_APP_LOCAL) { dt = rb_funcall(dt, spg_id_local, 0); } return dt; } } else { if (tz & SPG_DB_UTC) { time = timegm(&tm); } else { time = mktime(&tm); } if (time != -1) { ts.tv_sec = time; if (tz & SPG_APP_UTC) { offset_seconds = INT_MAX-1; } else { offset_seconds = INT_MAX; } return rb_time_timespec_new(&ts, offset_seconds); } } #endif if (offset_sign) { /* Offset given, convert to local time if not already in local time. * While PostgreSQL generally returns timestamps in local time, it's unwise to rely on this. */ dt = rb_funcall(rb_cTime, spg_id_local, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(usec)); utc_offset = NUM2INT(rb_funcall(dt, spg_id_utc_offset, 0)); if (utc_offset != offset_seconds) { dt = rb_funcall(dt, spg_id_op_plus, 1, INT2NUM(utc_offset - offset_seconds)); } if (tz & SPG_APP_UTC) { dt = rb_funcall(dt, spg_id_utc, 0); } return dt; } else if (!(tz & (SPG_APP_LOCAL|SPG_DB_LOCAL|SPG_APP_UTC|SPG_DB_UTC))) { return rb_funcall(rb_cTime, spg_id_local, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(usec)); } /* No offset given, and some timezone combination given */ if (tz & SPG_DB_UTC) { dt = rb_funcall(rb_cTime, spg_id_utc, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(usec)); if (tz & SPG_APP_LOCAL) { return rb_funcall(dt, spg_id_localtime, 0); } else { return dt; } } else { dt = rb_funcall(rb_cTime, spg_id_local, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(usec)); if (tz & SPG_APP_UTC) { return rb_funcall(dt, spg_id_utc, 0); } else { return dt; } } } else { /* datetime.class == DateTime */ double offset_fraction; if (offset_sign) { /* Offset given, handle correct local time. * While PostgreSQL generally returns timestamps in local time, it's unwise to rely on this. */ offset_fraction = offset_seconds/(double)SPG_SECONDS_PER_DAY; dt = rb_funcall(spg_DateTime, spg_id_new, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), rb_float_new(offset_fraction)); SPG_DT_ADD_USEC if (tz & SPG_APP_LOCAL) { utc_offset = NUM2INT(rb_funcall(rb_funcall(rb_cTime, spg_id_new, 0), spg_id_utc_offset, 0))/SPG_SECONDS_PER_DAY; dt = rb_funcall(dt, spg_id_new_offset, 1, rb_float_new(utc_offset)); } else if (tz & SPG_APP_UTC) { dt = rb_funcall(dt, spg_id_new_offset, 1, INT2NUM(0)); } return dt; } else if (!(tz & (SPG_APP_LOCAL|SPG_DB_LOCAL|SPG_APP_UTC|SPG_DB_UTC))) { dt = rb_funcall(spg_DateTime, spg_id_new, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec)); SPG_DT_ADD_USEC return dt; } /* No offset given, and some timezone combination given */ if (tz & SPG_DB_LOCAL) { offset_fraction = NUM2INT(rb_funcall(rb_funcall(rb_cTime, spg_id_local, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec)), spg_id_utc_offset, 0))/SPG_SECONDS_PER_DAY; dt = rb_funcall(spg_DateTime, spg_id_new, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), rb_float_new(offset_fraction)); SPG_DT_ADD_USEC if (tz & SPG_APP_UTC) { return rb_funcall(dt, spg_id_new_offset, 1, INT2NUM(0)); } else { return dt; } } else { dt = rb_funcall(spg_DateTime, spg_id_new, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec)); SPG_DT_ADD_USEC if (tz & SPG_APP_LOCAL) { offset_fraction = NUM2INT(rb_funcall(rb_funcall(rb_cTime, spg_id_local, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec)), spg_id_utc_offset, 0))/SPG_SECONDS_PER_DAY; dt = rb_funcall(dt, spg_id_new_offset, 1, rb_float_new(offset_fraction)); return dt; } else { return dt; } } } } static VALUE spg_inet(char *val, size_t len) { VALUE ip; VALUE ip_int; VALUE vmasks; unsigned int dst[4]; char buf[64]; int af = strchr(val, '.') ? AF_INET : AF_INET6; int mask = -1; if (len >= 64) { rb_raise(rb_eTypeError, "unable to parse IP address, too long"); } if (len >= 4) { if (val[len-2] == '/') { mask = val[len-1] - '0'; memcpy(buf, val, len-2); val = buf; val[len-2] = '\0'; } else if (val[len-3] == '/') { mask = (val[len-2]- '0')*10 + val[len-1] - '0'; memcpy(buf, val, len-3); val = buf; val[len-3] = '\0'; } else if (val[len-4] == '/') { mask = (val[len-3]- '0')*100 + (val[len-2]- '0')*10 + val[len-1] - '0'; memcpy(buf, val, len-4); val = buf; val[len-4] = '\0'; } } if (1 != inet_pton(af, val, (char *)dst)) { rb_raise(rb_eTypeError, "unable to parse IP address: %s", val); } if (af == AF_INET) { unsigned int ip_int_native; if (mask == -1) { mask = 32; } else if (mask < 0 || mask > 32) { rb_raise(rb_eTypeError, "invalid mask for IPv4: %d", mask); } vmasks = spg_vmasks4; ip_int_native = ntohl(*dst); /* Work around broken IPAddr behavior of convering portion of address after netmask to 0 */ switch (mask) { case 0: ip_int_native = 0; break; case 32: /* nothing to do */ break; default: ip_int_native &= ~((1UL<<(32-mask))-1); break; } ip_int = UINT2NUM(ip_int_native); } else { unsigned long long * dstllp = (unsigned long long *)dst; unsigned long long ip_int_native1; unsigned long long ip_int_native2; if (mask == -1) { mask = 128; } else if (mask < 0 || mask > 128) { rb_raise(rb_eTypeError, "invalid mask for IPv6: %d", mask); } vmasks = spg_vmasks6; ip_int_native1 = ntohll(dstllp); dstllp++; ip_int_native2 = ntohll(dstllp); if (mask == 128) { /* nothing to do */ } else if (mask == 64) { ip_int_native2 = 0; } else if (mask == 0) { ip_int_native1 = 0; ip_int_native2 = 0; } else if (mask < 64) { ip_int_native1 &= ~((1ULL<<(64-mask))-1); ip_int_native2 = 0; } else { ip_int_native2 &= ~((1ULL<<(128-mask))-1); } /* 4 Bignum allocations */ ip_int = ULL2NUM(ip_int_native1); ip_int = rb_funcall(ip_int, spg_id_lshift, 1, INT2NUM(64)); ip_int = rb_funcall(ip_int, spg_id_op_plus, 1, ULL2NUM(ip_int_native2)); } if (spg_use_ipaddr_alloc) { ip = rb_obj_alloc(spg_IPAddr); rb_ivar_set(ip, spg_id_family, INT2NUM(af)); rb_ivar_set(ip, spg_id_addr, ip_int); rb_ivar_set(ip, spg_id_mask_addr, RARRAY_AREF(vmasks, mask)); } else { VALUE ip_args[2]; ip_args[0] = ip_int; ip_args[1] = INT2NUM(af); ip = rb_class_new_instance(2, ip_args, spg_IPAddr); ip = rb_funcall(ip, spg_id_mask, 1, INT2NUM(mask)); } return ip; } static VALUE spg_create_Blob(VALUE v) { struct spg_blob_initialization *bi = (struct spg_blob_initialization *)v; if (bi->blob_string == NULL) { rb_raise(rb_eNoMemError, "PQunescapeBytea failure: probably not enough memory"); } return rb_obj_taint(rb_str_new_with_class(spg_Blob_instance, bi->blob_string, bi->length)); } static VALUE spg_fetch_rows_set_cols(VALUE self, VALUE ignore) { return Qnil; } static VALUE spg__array_col_value(char *v, size_t length, VALUE converter, int enc_index, int oid, VALUE db) { VALUE rv; struct spg_blob_initialization bi; switch(oid) { case 16: /* boolean */ rv = *v == 't' ? Qtrue : Qfalse; break; case 17: /* bytea */ bi.blob_string = (char *)PQunescapeBytea((unsigned char*)v, &bi.length); rv = rb_ensure(spg_create_Blob, (VALUE)&bi, (VALUE(*)())PQfreemem, (VALUE)bi.blob_string); break; case 20: /* integer */ case 21: case 23: case 26: rv = pg_text_dec_integer(v, length); break; case 700: /* float */ case 701: switch(*v) { case 'N': rv = spg_nan; break; case 'I': rv = spg_pos_inf; break; case '-': if (v[1] == 'I') { rv = spg_neg_inf; } else { rv = rb_float_new(rb_cstr_to_dbl(v, Qfalse)); } break; default: rv = rb_float_new(rb_cstr_to_dbl(v, Qfalse)); break; } break; case 1700: /* numeric */ rv = rb_funcall(rb_mKernel, spg_id_BigDecimal, 1, rb_str_new(v, length)); break; case 1082: /* date */ rv = spg_date(v, db, length); break; case 1083: /* time */ case 1266: rv = spg_time(v, length, (int)converter); break; case 1114: /* timestamp */ case 1184: rv = spg_timestamp(v, db, length, (int)converter); break; case 18: /* char */ case 25: /* text */ case 1043: /* varchar*/ rv = rb_tainted_str_new(v, length); PG_ENCODING_SET_NOCHECK(rv, enc_index); break; case 869: /* inet */ case 650: /* cidr */ rv = spg_inet(v, length); break; default: rv = rb_tainted_str_new(v, length); PG_ENCODING_SET_NOCHECK(rv, enc_index); if (RTEST(converter)) { rv = rb_funcall(converter, spg_id_call, 1, rv); } } return rv; } static VALUE spg_array_value(char *c_pg_array_string, int array_string_length, VALUE converter, int enc_index, int oid, VALUE self, VALUE array_type) { int index = 1; VALUE buf; VALUE args[2]; args[1] = array_type; if(RTEST(args[0] = check_pg_array(&index, c_pg_array_string, array_string_length))) { return rb_class_new_instance(2, args, spg_PGArray); } buf = rb_str_buf_new(array_string_length); rb_str_set_len(buf, array_string_length); rb_obj_freeze(buf); args[0] = read_array(&index, c_pg_array_string, array_string_length, buf, converter, enc_index, oid, self); return rb_class_new_instance(2, args, spg_PGArray); } static int spg_time_info_bitmask(void) { int info = 0; VALUE now = rb_funcall(spg_SQLTime, spg_id_date, 0); info = NUM2INT(rb_funcall(now, spg_id_year, 0)) << SPG_YEAR_SHIFT; info += NUM2INT(rb_funcall(now, spg_id_month, 0)) << SPG_MONTH_SHIFT; info += NUM2INT(rb_funcall(now, spg_id_day, 0)); if (rb_funcall(spg_Sequel, spg_id_application_timezone, 0) == spg_sym_utc) { info += SPG_TIME_UTC; } return info; } static int spg_timestamp_info_bitmask(VALUE self) { VALUE db, rtz; int tz = 0; db = rb_funcall(self, spg_id_db, 0); rtz = rb_funcall(db, spg_id_timezone, 0); if (rtz != Qnil) { if (rtz == spg_sym_local) { tz |= SPG_DB_LOCAL; } else if (rtz == spg_sym_utc) { tz |= SPG_DB_UTC; } else { tz |= SPG_DB_CUSTOM; } } rtz = rb_funcall(spg_Sequel, spg_id_application_timezone, 0); if (rtz != Qnil) { if (rtz == spg_sym_local) { tz |= SPG_APP_LOCAL; } else if (rtz == spg_sym_utc) { tz |= SPG_APP_UTC; } else { tz |= SPG_APP_CUSTOM; } } if (rb_cTime == rb_funcall(spg_Sequel, spg_id_datetime_class, 0)) { tz |= SPG_USE_TIME; } tz |= SPG_TZ_INITIALIZED; return tz; } static VALUE spg__col_value(VALUE self, PGresult *res, long i, long j, VALUE* colconvert, int enc_index) { char *v; VALUE rv; int ftype = PQftype(res, j); VALUE array_type; VALUE scalar_oid; struct spg_blob_initialization bi; if(PQgetisnull(res, i, j)) { rv = Qnil; } else { v = PQgetvalue(res, i, j); switch(ftype) { case 16: /* boolean */ rv = *v == 't' ? Qtrue : Qfalse; break; case 17: /* bytea */ bi.blob_string = (char *)PQunescapeBytea((unsigned char*)v, &bi.length); rv = rb_ensure(spg_create_Blob, (VALUE)&bi, (VALUE(*)())PQfreemem, (VALUE)bi.blob_string); break; case 20: /* integer */ case 21: case 23: case 26: rv = pg_text_dec_integer(v, PQgetlength(res, i, j)); break; case 700: /* float */ case 701: switch(*v) { case 'N': rv = spg_nan; break; case 'I': rv = spg_pos_inf; break; case '-': if (v[1] == 'I') { rv = spg_neg_inf; } else { rv = rb_float_new(rb_cstr_to_dbl(v, Qfalse)); } break; default: rv = rb_float_new(rb_cstr_to_dbl(v, Qfalse)); break; } break; case 1700: /* numeric */ rv = rb_funcall(rb_mKernel, spg_id_BigDecimal, 1, rb_str_new(v, PQgetlength(res, i, j))); break; case 1082: /* date */ rv = spg_date(v, self, PQgetlength(res, i, j)); break; case 1083: /* time */ case 1266: rv = spg_time(v, PQgetlength(res, i, j), (int)colconvert[j]); break; case 1114: /* timestamp */ case 1184: rv = spg_timestamp(v, self, PQgetlength(res, i, j), (int)colconvert[j]); break; case 18: /* char */ case 25: /* text */ case 1043: /* varchar*/ rv = rb_tainted_str_new(v, PQgetlength(res, i, j)); PG_ENCODING_SET_NOCHECK(rv, enc_index); break; case 869: /* inet */ case 650: /* cidr */ rv = spg_inet(v, PQgetlength(res, i, j)); break; /* array types */ case 1009: case 1014: case 1015: case 1007: case 1115: case 1185: case 1183: case 1270: case 1016: case 1231: case 1022: case 1000: case 1001: case 1182: case 1005: case 1028: case 1021: case 143: case 791: case 1561: case 1563: case 2951: case 1011: case 1012: case 1003: case 1010: case 1006: case 1041: case 651: switch(ftype) { case 1009: case 1014: array_type = spg_sym_text; scalar_oid = 25; break; case 1015: array_type = spg_sym_character_varying; scalar_oid = 25; break; case 1007: array_type = spg_sym_integer; scalar_oid = 23; break; case 1115: array_type = spg_sym_timestamp; scalar_oid = 1114; break; case 1185: array_type = spg_sym_timestamptz; scalar_oid = 1184; break; case 1183: array_type = spg_sym_time; scalar_oid = 1083; break; case 1270: array_type = spg_sym_timetz; scalar_oid = 1266; break; case 1016: array_type = spg_sym_bigint; scalar_oid = 20; break; case 1231: array_type = spg_sym_numeric; scalar_oid = 1700; break; case 1022: array_type = spg_sym_double_precision; scalar_oid = 701; break; case 1000: array_type = spg_sym_boolean; scalar_oid = 16; break; case 1001: array_type = spg_sym_bytea; scalar_oid = 17; break; case 1182: array_type = spg_sym_date; scalar_oid = 1082; break; case 1005: array_type = spg_sym_smallint; scalar_oid = 21; break; case 1028: array_type = spg_sym_oid; scalar_oid = 26; break; case 1021: array_type = spg_sym_real; scalar_oid = 700; break; case 143: array_type = spg_sym_xml; scalar_oid = 142; break; case 791: array_type = spg_sym_money; scalar_oid = 790; break; case 1561: array_type = spg_sym_bit; scalar_oid = 1560; break; case 1563: array_type = spg_sym_bit_varying; scalar_oid = 1562; break; case 2951: array_type = spg_sym_uuid; scalar_oid = 2950; break; case 1011: array_type = spg_sym_xid; scalar_oid = 28; break; case 1012: array_type = spg_sym_cid; scalar_oid = 29; break; case 1003: array_type = spg_sym_name; scalar_oid = 19; break; case 1010: array_type = spg_sym_tid; scalar_oid = 27; break; case 1006: array_type = spg_sym_int2vector; scalar_oid = 22; break; case 1041: array_type = spg_sym_inet; scalar_oid = 869; break; case 651: array_type = spg_sym_cidr; scalar_oid = 650; break; } rv = spg_array_value(v, PQgetlength(res, i, j), colconvert[j], enc_index, scalar_oid, self, array_type); break; default: rv = rb_tainted_str_new(v, PQgetlength(res, i, j)); PG_ENCODING_SET_NOCHECK(rv, enc_index); if (colconvert[j] != Qnil) { rv = rb_funcall(colconvert[j], spg_id_call, 1, rv); } } } return rv; } static VALUE spg__col_values(VALUE self, VALUE v, VALUE *colsyms, long nfields, PGresult *res, long i, VALUE *colconvert, int enc_index) { long j; VALUE cur; long len = RARRAY_LEN(v); VALUE a = rb_ary_new2(len); for (j=0; j SPG_MAX_FIELDS) { rb_raise(rb_eRangeError, "more than %d columns in query (%ld columns detected)", SPG_MAX_FIELDS, nfields); } spg_set_column_info(self, res, colsyms, colconvert, enc_index); opts = rb_funcall(self, spg_id_opts, 0); if (rb_type(opts) == T_HASH) { pg_type = rb_hash_aref(opts, spg_sym__sequel_pg_type); pg_value = rb_hash_aref(opts, spg_sym__sequel_pg_value); if (SYMBOL_P(pg_type)) { if (pg_type == spg_sym_map) { if (SYMBOL_P(pg_value)) { type = SPG_YIELD_COLUMN; } else if (rb_type(pg_value) == T_ARRAY) { type = SPG_YIELD_COLUMNS; } } else if (pg_type == spg_sym_first) { type = SPG_YIELD_FIRST; } else if (pg_type == spg_sym_array) { type = SPG_YIELD_ARRAY; } else if ((pg_type == spg_sym_hash || pg_type == spg_sym_hash_groups) && rb_type(pg_value) == T_ARRAY) { VALUE pg_value_key, pg_value_value; pg_value_key = rb_ary_entry(pg_value, 0); pg_value_value = rb_ary_entry(pg_value, 1); if (SYMBOL_P(pg_value_key)) { if (SYMBOL_P(pg_value_value)) { type = pg_type == spg_sym_hash_groups ? SPG_YIELD_KV_HASH_GROUPS : SPG_YIELD_KV_HASH; } else if (rb_type(pg_value_value) == T_ARRAY) { type = pg_type == spg_sym_hash_groups ? SPG_YIELD_KMV_HASH_GROUPS : SPG_YIELD_KMV_HASH; } } else if (rb_type(pg_value_key) == T_ARRAY) { if (SYMBOL_P(pg_value_value)) { type = pg_type == spg_sym_hash_groups ? SPG_YIELD_MKV_HASH_GROUPS : SPG_YIELD_MKV_HASH; } else if (rb_type(pg_value_value) == T_ARRAY) { type = pg_type == spg_sym_hash_groups ? SPG_YIELD_MKMV_HASH_GROUPS : SPG_YIELD_MKMV_HASH; } } } else if (pg_type == spg_sym_model && rb_type(pg_value) == T_CLASS) { type = SPG_YIELD_MODEL; } } } switch(type) { case SPG_YIELD_NORMAL: /* Normal, hash for entire row */ for(i=0; i SPG_MAX_FIELDS) { rb_funcall(rres, spg_id_clear, 0); rb_raise(rb_eRangeError, "more than %d columns in query", SPG_MAX_FIELDS); } spg_set_column_info(self, res, colsyms, colconvert, enc_index); while (PQntuples(res) != 0) { h = rb_hash_new(); for(j=0; j