sequel_pg 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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: