sequel_pg 1.3.0 → 1.4.0

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.
data/CHANGELOG CHANGED
@@ -1,5 +1,13 @@
1
+ === 1.4.0 (2012-06-01)
2
+
3
+ * Add support for streaming on PostgreSQL 9.2 using PQsetRowProcessor (jeremyevans)
4
+
5
+ * Respect DEBUG environment variable when building (jeremyevans)
6
+
1
7
  === 1.3.0 (2012-04-02)
2
8
 
9
+ * Build Windows version against PostgreSQL 9.1.1, ruby 1.8.7, and ruby 1.9.2 (previously 9.0.1, 1.8.6, and 1.9.1) (jeremyevans)
10
+
3
11
  * Add major speedup for new Sequel 3.34.0 methods Dataset#to_hash_groups and #select_hash_groups (jeremyevans)
4
12
 
5
13
  * Handle infinite timestamp values using Database#convert_infinite_timestamps in Sequel 3.34.0 (jeremyevans)
data/README.rdoc CHANGED
@@ -38,13 +38,13 @@ Here's an example that uses a modified version of swift's benchmarks
38
38
  sequel #select 0.090000 2.020000 2.110000 2.246688 46.54m
39
39
  sequel_pg #select 0.000000 0.250000 0.250000 0.361999 7.33m
40
40
 
41
- sequel_pg also has code to speed up the map, to_hash, select_hash,
42
- select_map, and select_order_map Dataset methods, which is on by
43
- default. It also has code to speed up the loading of model objects,
44
- which is off by default as it isn't fully compatible. It doesn't
45
- handle overriding Model.call, Model#set_values, or
46
- Model#after_initialize, which may cause problems with the
47
- following plugins that ship with Sequel:
41
+ sequel_pg also has code to speed up the map, to_hash, to_hash_groups,
42
+ select_hash, select_hash_groups, select_map, and select_order_map
43
+ Dataset methods, which is on by default. It also has code to speed
44
+ up the loading of model objects, which is off by default as it isn't
45
+ fully compatible. It doesn't handle overriding Model.call,
46
+ Model#set_values, or Model#after_initialize, which may cause problems
47
+ with the following plugins that ship with Sequel:
48
48
 
49
49
  * class_table_inheritance
50
50
  * force_encoding
@@ -63,6 +63,32 @@ enable the model optimization via:
63
63
  # Specific dataset
64
64
  Artist.dataset.optimize_model_load = true
65
65
 
66
+ == Streaming
67
+
68
+ If you are using PostgreSQL 9.2 or higher on the client, then sequel_pg
69
+ should enable streaming support. This allows you to stream returned
70
+ rows one at a time, instead of collecting the entire result set in
71
+ memory (which is how PostgreSQL works by default). You can check
72
+ if streaming is supported by:
73
+
74
+ Sequel::Postgres.supports_streaming?
75
+
76
+ If streaming is supported, you can load the streaming support into the
77
+ database:
78
+
79
+ require 'sequel_pg/streaming'
80
+ DB.extend Sequel::Postgres::Streaming
81
+
82
+ Then you can call the Dataset#stream method to have the dataset use
83
+ the streaming support:
84
+
85
+ DB[:table].stream.each{|row| ...}
86
+
87
+ If you want to enable streaming for all of a database's datasets, you
88
+ can do the following:
89
+
90
+ DB.extend_datasets Sequel::Postgres::Streaming::AllQueries
91
+
66
92
  == Installing the gem
67
93
 
68
94
  gem install sequel_pg
@@ -1,4 +1,5 @@
1
1
  require 'mkmf'
2
+ $CFLAGS << " -O0 -g -ggdb" if ENV['DEBUG']
2
3
  $CFLAGS << " -Wall " unless RUBY_PLATFORM =~ /solaris/
3
4
  dir_config('pg', ENV["POSTGRES_INCLUDE"] || (IO.popen("pg_config --includedir").readline.chomp rescue nil),
4
5
  ENV["POSTGRES_LIB"] || (IO.popen("pg_config --libdir").readline.chomp rescue nil))
@@ -13,6 +14,7 @@ if enable_config("static-build")
13
14
  end
14
15
 
15
16
  if (have_library('pq') || have_library('libpq') || have_library('ms/libpq')) && have_header('libpq-fe.h')
17
+ have_func 'PQsetRowProcessor'
16
18
  create_makefile("sequel_pg")
17
19
  else
18
20
  puts 'Could not find PostgreSQL build environment (libraries & headers): Makefile not created'
@@ -41,11 +41,23 @@
41
41
  #define SPG_YIELD_KMV_HASH_GROUPS 12
42
42
  #define SPG_YIELD_MKMV_HASH_GROUPS 13
43
43
 
44
+ struct spg_row_proc_info {
45
+ VALUE dataset;
46
+ VALUE block;
47
+ VALUE model;
48
+ VALUE colsyms[SPG_MAX_FIELDS];
49
+ VALUE colconvert[SPG_MAX_FIELDS];
50
+ #if SPG_ENCODING
51
+ int enc_index;
52
+ #endif
53
+ };
54
+
44
55
  static VALUE spg_Sequel;
45
56
  static VALUE spg_Blob;
46
57
  static VALUE spg_BigDecimal;
47
58
  static VALUE spg_Date;
48
59
  static VALUE spg_SQLTime;
60
+ static VALUE spg_PGError;
49
61
 
50
62
  static VALUE spg_sym_utc;
51
63
  static VALUE spg_sym_local;
@@ -399,32 +411,13 @@ static VALUE spg__field_ids(VALUE v, VALUE *colsyms, long nfields) {
399
411
  return pg_columns;
400
412
  }
401
413
 
402
- static VALUE spg_yield_hash_rows(VALUE self, VALUE rres, VALUE ignore) {
403
- PGresult *res;
404
- VALUE colsyms[SPG_MAX_FIELDS];
405
- VALUE colconvert[SPG_MAX_FIELDS];
406
- long ntuples;
407
- long nfields;
414
+ static void spg_set_column_info(VALUE self, PGresult *res, VALUE *colsyms, VALUE *colconvert) {
408
415
  long i;
409
416
  long j;
410
- VALUE h;
417
+ long nfields;
411
418
  VALUE conv_procs = 0;
412
- VALUE opts;
413
- VALUE pg_type;
414
- VALUE pg_value;
415
- char type = SPG_YIELD_NORMAL;
416
419
 
417
- #ifdef SPG_ENCODING
418
- int enc_index;
419
- enc_index = enc_get_index(rres);
420
- #endif
421
-
422
- Data_Get_Struct(rres, PGresult, res);
423
- ntuples = PQntuples(res);
424
420
  nfields = PQnfields(res);
425
- if (nfields > SPG_MAX_FIELDS) {
426
- rb_raise(rb_eRangeError, "more than %d columns in query", SPG_MAX_FIELDS);
427
- }
428
421
 
429
422
  for(j=0; j<nfields; j++) {
430
423
  colsyms[j] = rb_funcall(self, spg_id_output_identifier, 1, rb_str_new2(PQfname(res, j)));
@@ -459,6 +452,36 @@ static VALUE spg_yield_hash_rows(VALUE self, VALUE rres, VALUE ignore) {
459
452
  break;
460
453
  }
461
454
  }
455
+ }
456
+
457
+ static VALUE spg_yield_hash_rows(VALUE self, VALUE rres, VALUE ignore) {
458
+ PGresult *res;
459
+ VALUE colsyms[SPG_MAX_FIELDS];
460
+ VALUE colconvert[SPG_MAX_FIELDS];
461
+ long ntuples;
462
+ long nfields;
463
+ long i;
464
+ long j;
465
+ VALUE h;
466
+ VALUE opts;
467
+ VALUE pg_type;
468
+ VALUE pg_value;
469
+ char type = SPG_YIELD_NORMAL;
470
+
471
+ #ifdef SPG_ENCODING
472
+ int enc_index;
473
+ enc_index = enc_get_index(rres);
474
+ #endif
475
+
476
+ Data_Get_Struct(rres, PGresult, res);
477
+ ntuples = PQntuples(res);
478
+ nfields = PQnfields(res);
479
+ if (nfields > SPG_MAX_FIELDS) {
480
+ rb_raise(rb_eRangeError, "more than %d columns in query", SPG_MAX_FIELDS);
481
+ }
482
+
483
+ spg_set_column_info(self, res, colsyms, colconvert);
484
+
462
485
  rb_ivar_set(self, spg_id_columns, rb_ary_new4(nfields, colsyms));
463
486
 
464
487
  opts = rb_funcall(self, spg_id_opts, 0);
@@ -675,6 +698,199 @@ static VALUE spg_yield_hash_rows(VALUE self, VALUE rres, VALUE ignore) {
675
698
  return self;
676
699
  }
677
700
 
701
+ static VALUE spg_supports_streaming_p(VALUE self) {
702
+ return
703
+ #if HAVE_PQSETROWPROCESSOR
704
+ Qtrue;
705
+ #else
706
+ Qfalse;
707
+ #endif
708
+ }
709
+
710
+ #if HAVE_PQSETROWPROCESSOR
711
+ static VALUE spg__rp_value(VALUE self, PGresult* res, const PGdataValue* dvs, int j, VALUE* colconvert
712
+ #ifdef SPG_ENCODING
713
+ , int enc_index
714
+ #endif
715
+ ) {
716
+ const char *v;
717
+ PGdataValue dv = dvs[j];
718
+ VALUE rv;
719
+ size_t l;
720
+ int len = dv.len;
721
+
722
+ if(len < 0) {
723
+ rv = Qnil;
724
+ } else {
725
+ v = dv.value;
726
+
727
+ switch(PQftype(res, j)) {
728
+ case 16: /* boolean */
729
+ rv = *v == 't' ? Qtrue : Qfalse;
730
+ break;
731
+ case 17: /* bytea */
732
+ v = PQunescapeBytea((unsigned char*)v, &l);
733
+ rv = rb_funcall(spg_Blob, spg_id_new, 1, rb_str_new(v, l));
734
+ PQfreemem((char *)v);
735
+ break;
736
+ case 20: /* integer */
737
+ case 21:
738
+ case 22:
739
+ case 23:
740
+ case 26:
741
+ rv = rb_str2inum(rb_str_new(v, len), 10);
742
+ break;
743
+ case 700: /* float */
744
+ case 701:
745
+ if (strncmp("NaN", v, 3) == 0) {
746
+ rv = spg_nan;
747
+ } else if (strncmp("Infinity", v, 8) == 0) {
748
+ rv = spg_pos_inf;
749
+ } else if (strncmp("-Infinity", v, 9) == 0) {
750
+ rv = spg_neg_inf;
751
+ } else {
752
+ rv = rb_float_new(rb_str_to_dbl(rb_str_new(v, len), Qfalse));
753
+ }
754
+ break;
755
+ case 790: /* numeric */
756
+ case 1700:
757
+ rv = rb_funcall(spg_BigDecimal, spg_id_new, 1, rb_str_new(v, len));
758
+ break;
759
+ case 1082: /* date */
760
+ rv = rb_str_new(v, len);
761
+ rv = spg_date(StringValuePtr(rv));
762
+ break;
763
+ case 1083: /* time */
764
+ case 1266:
765
+ rv = rb_str_new(v, len);
766
+ rv = spg_time(StringValuePtr(rv));
767
+ break;
768
+ case 1114: /* timestamp */
769
+ case 1184:
770
+ rv = rb_str_new(v, len);
771
+ rv = spg_timestamp(StringValuePtr(rv), self);
772
+ break;
773
+ case 18: /* char */
774
+ case 25: /* text */
775
+ case 1043: /* varchar*/
776
+ rv = rb_tainted_str_new(v, len);
777
+ #ifdef SPG_ENCODING
778
+ rb_enc_associate_index(rv, enc_index);
779
+ #endif
780
+ break;
781
+ default:
782
+ rv = rb_tainted_str_new(v, len);
783
+ #ifdef SPG_ENCODING
784
+ rb_enc_associate_index(rv, enc_index);
785
+ #endif
786
+ if (colconvert[j] != Qnil) {
787
+ rv = rb_funcall(colconvert[j], spg_id_call, 1, rv);
788
+ }
789
+ }
790
+ }
791
+ return rv;
792
+ }
793
+
794
+ static int spg_row_processor(PGresult *res, const PGdataValue *columns, const char **errmsgp, void *param) {
795
+ long nfields;
796
+ struct spg_row_proc_info *info;
797
+ info = (struct spg_row_proc_info *)param;
798
+ VALUE *colsyms = info->colsyms;
799
+ VALUE *colconvert = info->colconvert;
800
+ VALUE self = info->dataset;
801
+
802
+ switch (PQresultStatus(res))
803
+ {
804
+ case PGRES_TUPLES_OK:
805
+ case PGRES_COPY_OUT:
806
+ case PGRES_COPY_IN:
807
+ #ifdef HAVE_CONST_PGRES_COPY_BOTH
808
+ case PGRES_COPY_BOTH:
809
+ #endif
810
+ case PGRES_EMPTY_QUERY:
811
+ case PGRES_COMMAND_OK:
812
+ break;
813
+ case PGRES_BAD_RESPONSE:
814
+ case PGRES_FATAL_ERROR:
815
+ case PGRES_NONFATAL_ERROR:
816
+ rb_raise(spg_PGError, "error while streaming results");
817
+ default:
818
+ rb_raise(spg_PGError, "unexpected result status while streaming results");
819
+ }
820
+
821
+ nfields = PQnfields(res);
822
+ if(columns == NULL) {
823
+ spg_set_column_info(self, res, colsyms, colconvert);
824
+ rb_ivar_set(self, spg_id_columns, rb_ary_new4(nfields, colsyms));
825
+ } else {
826
+ long j;
827
+ VALUE h, m;
828
+ h = rb_hash_new();
829
+
830
+ for(j=0; j<nfields; j++) {
831
+ rb_hash_aset(h, colsyms[j], spg__rp_value(self, res, columns, j, colconvert
832
+ #ifdef SPG_ENCODING
833
+ , info->enc_index
834
+ #endif
835
+ ));
836
+ }
837
+
838
+ /* optimize_model_load used, return model instance */
839
+ if ((m = info->model)) {
840
+ m = rb_obj_alloc(m);
841
+ rb_ivar_set(m, spg_id_values, h);
842
+ h = m;
843
+ }
844
+
845
+ rb_funcall(info->block, spg_id_call, 1, h);
846
+ }
847
+ return 1;
848
+ }
849
+
850
+ static VALUE spg_unset_row_processor(VALUE rconn) {
851
+ PGconn *conn;
852
+ Data_Get_Struct(rconn, PGconn, conn);
853
+ if ((PQskipResult(conn)) != NULL) {
854
+ /* Results remaining when row processor finished,
855
+ * either because an exception was raised or the iterator
856
+ * exited early, so skip all remaining rows. */
857
+ while(PQgetResult(conn) != NULL) {
858
+ /* Use a separate while loop as PQgetResult is faster than
859
+ * PQskipResult. */
860
+ }
861
+ }
862
+ PQsetRowProcessor(conn, NULL, NULL);
863
+ return Qnil;
864
+ }
865
+
866
+ static VALUE spg_with_row_processor(VALUE self, VALUE rconn, VALUE dataset, VALUE block) {
867
+ struct spg_row_proc_info info;
868
+ PGconn *conn;
869
+ Data_Get_Struct(rconn, PGconn, conn);
870
+ bzero(&info, sizeof(info));
871
+
872
+ info.dataset = dataset;
873
+ info.block = block;
874
+ info.model = 0;
875
+ #if SPG_ENCODING
876
+ info.enc_index = enc_get_index(rconn);
877
+ #endif
878
+
879
+ /* Abuse local variable, detect if optimize_model_load used */
880
+ block = rb_funcall(dataset, spg_id_opts, 0);
881
+ if (rb_type(block) == T_HASH && rb_hash_aref(block, spg_sym__sequel_pg_type) == spg_sym_model) {
882
+ block = rb_hash_aref(block, spg_sym__sequel_pg_value);
883
+ if (rb_type(block) == T_CLASS) {
884
+ info.model = block;
885
+ }
886
+ }
887
+
888
+ PQsetRowProcessor(conn, spg_row_processor, (void*)&info);
889
+ rb_ensure(rb_yield, Qnil, spg_unset_row_processor, rconn);
890
+ return Qnil;
891
+ }
892
+ #endif
893
+
678
894
  void Init_sequel_pg(void) {
679
895
  VALUE c, spg_Postgres;
680
896
  ID cg;
@@ -725,6 +941,7 @@ void Init_sequel_pg(void) {
725
941
  spg_BigDecimal = rb_funcall(rb_cObject, cg, 1, rb_str_new2("BigDecimal"));
726
942
  spg_Date = rb_funcall(rb_cObject, cg, 1, rb_str_new2("Date"));
727
943
  spg_Postgres = rb_funcall(spg_Sequel, cg, 1, rb_str_new2("Postgres"));
944
+ spg_PGError = rb_funcall(rb_cObject, cg, 1, rb_str_new2("PGError"));
728
945
 
729
946
  spg_nan = rb_eval_string("0.0/0.0");
730
947
  spg_pos_inf = rb_eval_string("1.0/0.0");
@@ -749,5 +966,12 @@ void Init_sequel_pg(void) {
749
966
  rb_define_private_method(c, "yield_hash_rows", spg_yield_hash_rows, 2);
750
967
  rb_define_private_method(c, "fetch_rows_set_cols", spg_fetch_rows_set_cols, 1);
751
968
 
969
+ rb_define_singleton_method(spg_Postgres, "supports_streaming?", spg_supports_streaming_p, 0);
970
+
971
+ #if HAVE_PQSETROWPROCESSOR
972
+ c = rb_funcall(spg_Postgres, cg, 1, rb_str_new2("Database"));
973
+ rb_define_private_method(c, "with_row_processor", spg_with_row_processor, 3);
974
+ #endif
975
+
752
976
  rb_require("sequel_pg/sequel_pg");
753
977
  }
@@ -0,0 +1,82 @@
1
+ unless Sequel::Postgres.respond_to?(:supports_streaming?)
2
+ raise LoadError, "either sequel_pg not loaded, or an old version of sequel_pg loaded"
3
+ end
4
+ unless Sequel::Postgres.supports_streaming?
5
+ raise LoadError, "streaming is not supported by the version of libpq in use"
6
+ end
7
+
8
+ # Database methods necessary to support streaming. You should extend your
9
+ # Database object with this:
10
+ #
11
+ # DB.extend Sequel::Postgres::Streaming
12
+ #
13
+ # Then you can call #stream on your datasets to use the streaming support:
14
+ #
15
+ # DB[:table].stream.each{|row| ...}
16
+ module Sequel::Postgres::Streaming
17
+ # Also extend the database's datasets to support streaming
18
+ def self.extended(db)
19
+ db.extend_datasets(DatasetMethods)
20
+ end
21
+
22
+ private
23
+
24
+ # If streaming is requested, set a row processor while executing
25
+ # the query.
26
+ def _execute(conn, sql, opts={})
27
+ if stream = opts[:stream]
28
+ with_row_processor(conn, *stream){super}
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ # Dataset methods used to implement streaming.
35
+ module DatasetMethods
36
+ # If streaming has been requested and the current dataset
37
+ # can be streamed, request the database use streaming when
38
+ # executing this query.
39
+ def fetch_rows(sql, &block)
40
+ if stream_results?
41
+ execute(sql, :stream=>[self, block])
42
+ else
43
+ super
44
+ end
45
+ end
46
+
47
+ # Return a clone of the dataset that will use streaming to load
48
+ # rows.
49
+ def stream
50
+ clone(:stream=>true)
51
+ end
52
+
53
+ private
54
+
55
+ # Only stream results if streaming has been specifically requested
56
+ # and the query is streamable.
57
+ def stream_results?
58
+ @opts[:stream] && streamable?
59
+ end
60
+
61
+ # Queries using cursors are not streamable, and queries that use
62
+ # the map/select_map/to_hash/to_hash_groups optimizations are not
63
+ # streamable, but other queries are streamable.
64
+ def streamable?
65
+ spgt = (o = @opts)[:_sequel_pg_type]
66
+ (spgt.nil? || spgt == :model) && !o[:cursor]
67
+ end
68
+ end
69
+
70
+ # Extend a database's datasets with this module to enable streaming
71
+ # on all streamable queries:
72
+ #
73
+ # DB.extend_datasets(Sequel::Postgres::Streaming::AllQueries)
74
+ module AllQueries
75
+ private
76
+
77
+ # Always stream results if the query is streamable.
78
+ def stream_results?
79
+ streamable?
80
+ end
81
+ end
82
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel_pg
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-20 00:00:00.000000000 Z
12
+ date: 2012-06-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pg
16
- requirement: &4390944760 !ruby/object:Gem::Requirement
16
+ requirement: &4392569800 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,18 +21,18 @@ dependencies:
21
21
  version: 0.8.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *4390944760
24
+ version_requirements: *4392569800
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: sequel
27
- requirement: &4414168760 !ruby/object:Gem::Requirement
27
+ requirement: &4392569320 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
31
31
  - !ruby/object:Gem::Version
32
- version: 3.29.0
32
+ version: 3.36.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *4414168760
35
+ version_requirements: *4392569320
36
36
  description: ! 'sequel_pg overwrites the inner loop of the Sequel postgres
37
37
 
38
38
  adapter row fetching code with a C version. The C version
@@ -58,6 +58,7 @@ files:
58
58
  - ext/sequel_pg/extconf.rb
59
59
  - ext/sequel_pg/sequel_pg.c
60
60
  - lib/sequel_pg/sequel_pg.rb
61
+ - lib/sequel_pg/streaming.rb
61
62
  homepage: http://github.com/jeremyevans/sequel_pg
62
63
  licenses: []
63
64
  post_install_message:
@@ -76,7 +77,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
77
  requirements:
77
78
  - - ! '>='
78
79
  - !ruby/object:Gem::Version
79
- version: 1.8.6
80
+ version: 1.8.7
80
81
  required_rubygems_version: !ruby/object:Gem::Requirement
81
82
  none: false
82
83
  requirements: