pg 0.19.0 → 1.1.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.
Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/ChangeLog +218 -1
  4. data/History.rdoc +106 -0
  5. data/Manifest.txt +5 -18
  6. data/README.rdoc +15 -5
  7. data/Rakefile +8 -9
  8. data/Rakefile.cross +19 -22
  9. data/ext/errorcodes.def +17 -0
  10. data/ext/errorcodes.rb +1 -1
  11. data/ext/errorcodes.txt +11 -1
  12. data/ext/extconf.rb +14 -32
  13. data/ext/gvl_wrappers.c +4 -0
  14. data/ext/gvl_wrappers.h +23 -39
  15. data/ext/pg.c +19 -48
  16. data/ext/pg.h +46 -81
  17. data/ext/pg_binary_decoder.c +69 -6
  18. data/ext/pg_coder.c +53 -4
  19. data/ext/pg_connection.c +401 -253
  20. data/ext/pg_copy_coder.c +10 -5
  21. data/ext/pg_result.c +359 -131
  22. data/ext/pg_text_decoder.c +597 -37
  23. data/ext/pg_text_encoder.c +6 -7
  24. data/ext/pg_tuple.c +541 -0
  25. data/ext/pg_type_map.c +14 -7
  26. data/ext/util.c +6 -6
  27. data/ext/util.h +2 -2
  28. data/lib/pg/basic_type_mapping.rb +40 -7
  29. data/lib/pg/binary_decoder.rb +22 -0
  30. data/lib/pg/coder.rb +1 -1
  31. data/lib/pg/connection.rb +27 -7
  32. data/lib/pg/constants.rb +1 -1
  33. data/lib/pg/exceptions.rb +1 -1
  34. data/lib/pg/result.rb +6 -5
  35. data/lib/pg/text_decoder.rb +19 -23
  36. data/lib/pg/text_encoder.rb +36 -2
  37. data/lib/pg/tuple.rb +30 -0
  38. data/lib/pg/type_map_by_column.rb +1 -1
  39. data/lib/pg.rb +21 -11
  40. data/spec/helpers.rb +47 -19
  41. data/spec/pg/basic_type_mapping_spec.rb +230 -27
  42. data/spec/pg/connection_spec.rb +402 -275
  43. data/spec/pg/connection_sync_spec.rb +41 -0
  44. data/spec/pg/result_spec.rb +59 -17
  45. data/spec/pg/tuple_spec.rb +280 -0
  46. data/spec/pg/type_map_by_class_spec.rb +2 -2
  47. data/spec/pg/type_map_by_column_spec.rb +1 -1
  48. data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
  49. data/spec/pg/type_map_by_oid_spec.rb +1 -1
  50. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  51. data/spec/pg/type_map_spec.rb +1 -1
  52. data/spec/pg/type_spec.rb +184 -12
  53. data/spec/pg_spec.rb +2 -2
  54. data.tar.gz.sig +0 -0
  55. metadata +47 -53
  56. metadata.gz.sig +0 -0
  57. data/sample/array_insert.rb +0 -20
  58. data/sample/async_api.rb +0 -106
  59. data/sample/async_copyto.rb +0 -39
  60. data/sample/async_mixed.rb +0 -56
  61. data/sample/check_conn.rb +0 -21
  62. data/sample/copyfrom.rb +0 -81
  63. data/sample/copyto.rb +0 -19
  64. data/sample/cursor.rb +0 -21
  65. data/sample/disk_usage_report.rb +0 -186
  66. data/sample/issue-119.rb +0 -94
  67. data/sample/losample.rb +0 -69
  68. data/sample/minimal-testcase.rb +0 -17
  69. data/sample/notify_wait.rb +0 -72
  70. data/sample/pg_statistics.rb +0 -294
  71. data/sample/replication_monitor.rb +0 -231
  72. data/sample/test_binary_values.rb +0 -33
  73. data/sample/wal_shipper.rb +0 -434
  74. data/sample/warehouse_partitions.rb +0 -320
data/ext/pg_result.c CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * pg_result.c - PG::Result class extension
3
- * $Id: pg_result.c,v 982082c0d77b 2016/01/31 19:28:55 ged $
3
+ * $Id: pg_result.c,v a8b70c42b3e8 2018/07/30 14:09:38 kanis $
4
4
  *
5
5
  */
6
6
 
@@ -16,6 +16,102 @@ static t_pg_result *pgresult_get_this( VALUE );
16
16
  static t_pg_result *pgresult_get_this_safe( VALUE );
17
17
 
18
18
 
19
+ #define PGRESULT_DATA_BLOCKSIZE 2048
20
+ typedef struct pgresAttValue
21
+ {
22
+ int len; /* length in bytes of the value */
23
+ char *value; /* actual value, plus terminating zero byte */
24
+ } PGresAttValue;
25
+
26
+
27
+ static int
28
+ count_leading_zero_bits(unsigned int x)
29
+ {
30
+ #if defined(__GNUC__) || defined(__clang__)
31
+ return __builtin_clz(x);
32
+ #elif defined(_MSC_VER)
33
+ DWORD r = 0;
34
+ _BitScanForward(&r, x);
35
+ return (int)r;
36
+ #else
37
+ unsigned int a;
38
+ for(a=0; a < sizeof(unsigned int) * 8; a++){
39
+ if( x & (1 << (sizeof(unsigned int) * 8 - 1))) return a;
40
+ x <<= 1;
41
+ }
42
+ return a;
43
+ #endif
44
+ }
45
+
46
+ static ssize_t
47
+ pgresult_approx_size(PGresult *result)
48
+ {
49
+ int num_fields = PQnfields(result);
50
+ ssize_t size = 0;
51
+
52
+ if( num_fields > 0 ){
53
+ int num_tuples = PQntuples(result);
54
+
55
+ if( num_tuples > 0 ){
56
+ int pos;
57
+
58
+ /* This is a simple heuristic to determine the number of sample fields and subsequently to approximate the memory size taken by all field values of the result set.
59
+ * Since scanning of all field values is would have a severe performance impact, only a small subset of fields is retrieved and the result is extrapolated to the whole result set.
60
+ * The given algorithm has no real scientific background, but is made for speed and typical table layouts.
61
+ */
62
+ int num_samples =
63
+ (num_fields < 9 ? num_fields : 39 - count_leading_zero_bits(num_fields-8)) *
64
+ (num_tuples < 8 ? 1 : 30 - count_leading_zero_bits(num_tuples));
65
+
66
+ /* start with scanning very last fields, since they are most probably in the cache */
67
+ for( pos = 0; pos < (num_samples+1)/2; pos++ ){
68
+ size += PQgetlength(result, num_tuples - 1 - (pos / num_fields), num_fields - 1 - (pos % num_fields));
69
+ }
70
+ /* scan the very first fields */
71
+ for( pos = 0; pos < num_samples/2; pos++ ){
72
+ size += PQgetlength(result, pos / num_fields, pos % num_fields);
73
+ }
74
+ /* extrapolate sample size to whole result set */
75
+ size = size * num_tuples * num_fields / num_samples;
76
+ }
77
+
78
+ /* count metadata */
79
+ size += num_fields * (
80
+ sizeof(PGresAttDesc) + /* column description */
81
+ num_tuples * (
82
+ sizeof(PGresAttValue) + 1 /* ptr, len and zero termination of each value */
83
+ )
84
+ );
85
+
86
+ /* Account free space due to libpq's default block size */
87
+ size = (size + PGRESULT_DATA_BLOCKSIZE - 1) / PGRESULT_DATA_BLOCKSIZE * PGRESULT_DATA_BLOCKSIZE;
88
+
89
+ /* count tuple pointers */
90
+ size += sizeof(void*) * ((num_tuples + 128 - 1) / 128 * 128);
91
+ }
92
+
93
+ size += 216; /* add PGresult size */
94
+
95
+ return size;
96
+ }
97
+
98
+ static void
99
+ pgresult_clear( t_pg_result *this )
100
+ {
101
+ if( this->pgresult && !this->autoclear ){
102
+ PQclear(this->pgresult);
103
+ #ifdef HAVE_RB_GC_ADJUST_MEMORY_USAGE
104
+ rb_gc_adjust_memory_usage(-this->result_size);
105
+ #endif
106
+ }
107
+ this->pgresult = NULL;
108
+ }
109
+
110
+ static size_t
111
+ pgresult_memsize( t_pg_result *this )
112
+ {
113
+ return this->result_size;
114
+ }
19
115
 
20
116
  /*
21
117
  * Global functions
@@ -24,23 +120,23 @@ static t_pg_result *pgresult_get_this_safe( VALUE );
24
120
  /*
25
121
  * Result constructor
26
122
  */
27
- VALUE
28
- pg_new_result(PGresult *result, VALUE rb_pgconn)
123
+ static VALUE
124
+ pg_new_result2(PGresult *result, VALUE rb_pgconn)
29
125
  {
30
126
  int nfields = result ? PQnfields(result) : 0;
31
127
  VALUE self = pgresult_s_allocate( rb_cPGresult );
32
128
  t_pg_result *this;
33
129
 
34
130
  this = (t_pg_result *)xmalloc(sizeof(*this) + sizeof(*this->fnames) * nfields);
35
- DATA_PTR(self) = this;
131
+ RTYPEDDATA_DATA(self) = this;
36
132
 
37
133
  this->pgresult = result;
38
134
  this->connection = rb_pgconn;
39
135
  this->typemap = pg_typemap_all_strings;
40
136
  this->p_typemap = DATA_PTR( this->typemap );
41
- this->autoclear = 0;
42
137
  this->nfields = -1;
43
138
  this->tuple_hash = Qnil;
139
+ this->field_map = Qnil;
44
140
 
45
141
  PG_ENCODING_SET_NOCHECK(self, ENCODING_GET(rb_pgconn));
46
142
 
@@ -58,11 +154,37 @@ pg_new_result(PGresult *result, VALUE rb_pgconn)
58
154
  return self;
59
155
  }
60
156
 
157
+ VALUE
158
+ pg_new_result(PGresult *result, VALUE rb_pgconn)
159
+ {
160
+ VALUE self = pg_new_result2(result, rb_pgconn);
161
+ t_pg_result *this = pgresult_get_this(self);
162
+ t_pg_connection *p_conn = pg_get_connection(rb_pgconn);
163
+
164
+ this->autoclear = 0;
165
+
166
+ if( p_conn->guess_result_memsize ){
167
+ /* Approximate size of underlying pgresult memory storage and account to ruby GC */
168
+ this->result_size = pgresult_approx_size(result);
169
+
170
+ #ifdef HAVE_RB_GC_ADJUST_MEMORY_USAGE
171
+ rb_gc_adjust_memory_usage(this->result_size);
172
+ #endif
173
+ }
174
+
175
+ return self;
176
+ }
177
+
61
178
  VALUE
62
179
  pg_new_result_autoclear(PGresult *result, VALUE rb_pgconn)
63
180
  {
64
- VALUE self = pg_new_result(result, rb_pgconn);
181
+ VALUE self = pg_new_result2(result, rb_pgconn);
65
182
  t_pg_result *this = pgresult_get_this(self);
183
+
184
+ /* Autocleared results are freed implicit instead of by PQclear().
185
+ * So it's not very useful to be accounted by ruby GC.
186
+ */
187
+ this->result_size = 0;
66
188
  this->autoclear = 1;
67
189
  return self;
68
190
  }
@@ -92,12 +214,8 @@ pg_result_check( VALUE self )
92
214
  case PGRES_TUPLES_OK:
93
215
  case PGRES_COPY_OUT:
94
216
  case PGRES_COPY_IN:
95
- #ifdef HAVE_CONST_PGRES_COPY_BOTH
96
217
  case PGRES_COPY_BOTH:
97
- #endif
98
- #ifdef HAVE_CONST_PGRES_SINGLE_TUPLE
99
218
  case PGRES_SINGLE_TUPLE:
100
- #endif
101
219
  case PGRES_EMPTY_QUERY:
102
220
  case PGRES_COMMAND_OK:
103
221
  return self;
@@ -135,7 +253,13 @@ pg_result_check( VALUE self )
135
253
  * call-seq:
136
254
  * res.clear() -> nil
137
255
  *
138
- * Clears the PG::Result object as the result of the query.
256
+ * Clears the PG::Result object as the result of a query.
257
+ * This frees all underlying memory consumed by the result object.
258
+ * Afterwards access to result methods raises PG::Error "result has been cleared".
259
+ *
260
+ * Explicit calling #clear can lead to better memory performance, but is not generally necessary.
261
+ * Special care must be taken when PG::Tuple objects are used.
262
+ * In this case #clear must not be called unless all PG::Tuple objects of this result are fully materialized.
139
263
  *
140
264
  * If PG::Result#autoclear? is true then the result is marked as cleared
141
265
  * and the underlying C struct will be cleared automatically by libpq.
@@ -145,9 +269,7 @@ VALUE
145
269
  pg_result_clear(VALUE self)
146
270
  {
147
271
  t_pg_result *this = pgresult_get_this(self);
148
- if( !this->autoclear )
149
- PQclear(pgresult_get(self));
150
- this->pgresult = NULL;
272
+ pgresult_clear( this );
151
273
  return Qnil;
152
274
  }
153
275
 
@@ -195,6 +317,7 @@ pgresult_gc_mark( t_pg_result *this )
195
317
  rb_gc_mark( this->connection );
196
318
  rb_gc_mark( this->typemap );
197
319
  rb_gc_mark( this->tuple_hash );
320
+ rb_gc_mark( this->field_map );
198
321
 
199
322
  for( i=0; i < this->nfields; i++ ){
200
323
  rb_gc_mark( this->fnames[i] );
@@ -208,8 +331,7 @@ static void
208
331
  pgresult_gc_free( t_pg_result *this )
209
332
  {
210
333
  if( !this ) return;
211
- if(this->pgresult != NULL && !this->autoclear)
212
- PQclear(this->pgresult);
334
+ pgresult_clear( this );
213
335
 
214
336
  xfree(this);
215
337
  }
@@ -243,6 +365,20 @@ pgresult_get(VALUE self)
243
365
  return this->pgresult;
244
366
  }
245
367
 
368
+
369
+ static const rb_data_type_t pgresult_type = {
370
+ "pg",
371
+ {
372
+ (void (*)(void*))pgresult_gc_mark,
373
+ (void (*)(void*))pgresult_gc_free,
374
+ (size_t (*)(const void *))pgresult_memsize,
375
+ },
376
+ 0, 0,
377
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
378
+ RUBY_TYPED_FREE_IMMEDIATELY,
379
+ #endif
380
+ };
381
+
246
382
  /*
247
383
  * Document-method: allocate
248
384
  *
@@ -252,7 +388,7 @@ pgresult_get(VALUE self)
252
388
  static VALUE
253
389
  pgresult_s_allocate( VALUE klass )
254
390
  {
255
- VALUE self = Data_Wrap_Struct( klass, pgresult_gc_mark, pgresult_gc_free, NULL );
391
+ VALUE self = TypedData_Wrap_Struct( klass, &pgresult_type, NULL );
256
392
 
257
393
  return self;
258
394
  }
@@ -283,8 +419,9 @@ static void pgresult_init_fnames(VALUE self)
283
419
  *
284
420
  * The class to represent the query result tuples (rows).
285
421
  * An instance of this class is created as the result of every query.
286
- * You may need to invoke the #clear method of the instance when finished with
287
- * the result for better memory performance.
422
+ *
423
+ * Since pg-1.1 the amount of memory in use by a PG::Result object is estimated and passed to ruby's garbage collector.
424
+ * You can invoke the #clear method to force deallocation of memory of the instance when finished with the result for better memory performance.
288
425
  *
289
426
  * Example:
290
427
  * require 'pg'
@@ -302,7 +439,7 @@ static void pgresult_init_fnames(VALUE self)
302
439
 
303
440
  /*
304
441
  * call-seq:
305
- * res.result_status() -> Fixnum
442
+ * res.result_status() -> Integer
306
443
  *
307
444
  * Returns the status of the query. The status value is one of:
308
445
  * * +PGRES_EMPTY_QUERY+
@@ -414,7 +551,7 @@ pgresult_error_field(VALUE self, VALUE field)
414
551
 
415
552
  /*
416
553
  * call-seq:
417
- * res.ntuples() -> Fixnum
554
+ * res.ntuples() -> Integer
418
555
  *
419
556
  * Returns the number of tuples in the query result.
420
557
  */
@@ -427,7 +564,7 @@ pgresult_ntuples(VALUE self)
427
564
  static VALUE
428
565
  pgresult_ntuples_for_enum(VALUE self, VALUE args, VALUE eobj)
429
566
  {
430
- return pgresult_ntuples(self);
567
+ return pgresult_ntuples(self);
431
568
  }
432
569
 
433
570
  /*
@@ -466,7 +603,7 @@ pgresult_fname(VALUE self, VALUE index)
466
603
 
467
604
  /*
468
605
  * call-seq:
469
- * res.fnumber( name ) -> Fixnum
606
+ * res.fnumber( name ) -> Integer
470
607
  *
471
608
  * Returns the index of the field specified by the string +name+.
472
609
  * The given +name+ is treated like an identifier in an SQL command, that is,
@@ -527,7 +664,7 @@ pgresult_ftable(VALUE self, VALUE column_number)
527
664
 
528
665
  /*
529
666
  * call-seq:
530
- * res.ftablecol( column_number ) -> Fixnum
667
+ * res.ftablecol( column_number ) -> Integer
531
668
  *
532
669
  * Returns the column number (within its table) of the table from
533
670
  * which the column _column_number_ is made up.
@@ -552,7 +689,7 @@ pgresult_ftablecol(VALUE self, VALUE column_number)
552
689
 
553
690
  /*
554
691
  * call-seq:
555
- * res.fformat( column_number ) -> Fixnum
692
+ * res.fformat( column_number ) -> Integer
556
693
  *
557
694
  * Returns the format (0 for text, 1 for binary) of column
558
695
  * _column_number_.
@@ -696,7 +833,7 @@ pgresult_getisnull(VALUE self, VALUE tup_num, VALUE field_num)
696
833
 
697
834
  /*
698
835
  * call-seq:
699
- * res.getlength( tup_num, field_num ) -> Fixnum
836
+ * res.getlength( tup_num, field_num ) -> Integer
700
837
  *
701
838
  * Returns the (String) length of the field in bytes.
702
839
  *
@@ -721,7 +858,7 @@ pgresult_getlength(VALUE self, VALUE tup_num, VALUE field_num)
721
858
 
722
859
  /*
723
860
  * call-seq:
724
- * res.nparams() -> Fixnum
861
+ * res.nparams() -> Integer
725
862
  *
726
863
  * Returns the number of parameters of a prepared statement.
727
864
  * Only useful for the result returned by conn.describePrepared
@@ -772,11 +909,17 @@ pgresult_cmd_status(VALUE self)
772
909
  * Returns the number of tuples (rows) affected by the SQL command.
773
910
  *
774
911
  * If the SQL command that generated the PG::Result was not one of:
775
- * * +INSERT+
776
- * * +UPDATE+
777
- * * +DELETE+
778
- * * +MOVE+
779
- * * +FETCH+
912
+ *
913
+ * * <tt>SELECT</tt>
914
+ * * <tt>CREATE TABLE AS</tt>
915
+ * * <tt>INSERT</tt>
916
+ * * <tt>UPDATE</tt>
917
+ * * <tt>DELETE</tt>
918
+ * * <tt>MOVE</tt>
919
+ * * <tt>FETCH</tt>
920
+ * * <tt>COPY</tt>
921
+ * * an +EXECUTE+ of a prepared query that contains an +INSERT+, +UPDATE+, or +DELETE+ statement
922
+ *
780
923
  * or if no tuples were affected, <tt>0</tt> is returned.
781
924
  */
782
925
  static VALUE
@@ -965,6 +1108,78 @@ pgresult_field_values( VALUE self, VALUE field )
965
1108
  }
966
1109
 
967
1110
 
1111
+ /*
1112
+ * call-seq:
1113
+ * res.tuple_values( n ) -> array
1114
+ *
1115
+ * Returns an Array of the field values from the nth row of the result.
1116
+ *
1117
+ */
1118
+ static VALUE
1119
+ pgresult_tuple_values(VALUE self, VALUE index)
1120
+ {
1121
+ int tuple_num = NUM2INT( index );
1122
+ t_pg_result *this;
1123
+ int field;
1124
+ int num_tuples;
1125
+ int num_fields;
1126
+
1127
+ this = pgresult_get_this_safe(self);
1128
+ num_tuples = PQntuples(this->pgresult);
1129
+ num_fields = PQnfields(this->pgresult);
1130
+
1131
+ if ( tuple_num < 0 || tuple_num >= num_tuples )
1132
+ rb_raise( rb_eIndexError, "Index %d is out of range", tuple_num );
1133
+
1134
+ {
1135
+ PG_VARIABLE_LENGTH_ARRAY(VALUE, row_values, num_fields, PG_MAX_COLUMNS)
1136
+
1137
+ /* populate the row */
1138
+ for ( field = 0; field < num_fields; field++ ) {
1139
+ row_values[field] = this->p_typemap->funcs.typecast_result_value(this->p_typemap, self, tuple_num, field);
1140
+ }
1141
+ return rb_ary_new4( num_fields, row_values );
1142
+ }
1143
+ }
1144
+
1145
+ /*
1146
+ * call-seq:
1147
+ * res.tuple( n ) -> PG::Tuple
1148
+ *
1149
+ * Returns a PG::Tuple from the nth row of the result.
1150
+ *
1151
+ */
1152
+ static VALUE
1153
+ pgresult_tuple(VALUE self, VALUE index)
1154
+ {
1155
+ int tuple_num = NUM2INT( index );
1156
+ t_pg_result *this;
1157
+ int num_tuples;
1158
+
1159
+ this = pgresult_get_this_safe(self);
1160
+ num_tuples = PQntuples(this->pgresult);
1161
+
1162
+ if ( tuple_num < 0 || tuple_num >= num_tuples )
1163
+ rb_raise( rb_eIndexError, "Index %d is out of range", tuple_num );
1164
+
1165
+ if( this->field_map == Qnil ){
1166
+ int i;
1167
+ VALUE field_map = rb_hash_new();
1168
+
1169
+ if( this->nfields == -1 )
1170
+ pgresult_init_fnames( self );
1171
+
1172
+ for( i = 0; i < this->nfields; i++ ){
1173
+ rb_hash_aset(field_map, this->fnames[i], INT2FIX(i));
1174
+ }
1175
+ rb_obj_freeze(field_map);
1176
+ this->field_map = field_map;
1177
+ }
1178
+
1179
+ return pg_tuple_new(self, tuple_num);
1180
+ }
1181
+
1182
+
968
1183
  /*
969
1184
  * call-seq:
970
1185
  * res.each{ |tuple| ... }
@@ -1050,40 +1265,57 @@ pgresult_type_map_get(VALUE self)
1050
1265
  return this->typemap;
1051
1266
  }
1052
1267
 
1053
- #ifdef HAVE_PQSETSINGLEROWMODE
1054
- /*
1055
- * call-seq:
1056
- * res.stream_each{ |tuple| ... }
1057
- *
1058
- * Invokes block for each tuple in the result set in single row mode.
1059
- *
1060
- * This is a convenience method for retrieving all result tuples
1061
- * as they are transferred. It is an alternative to repeated calls of
1062
- * PG::Connection#get_result , but given that it avoids the overhead of
1063
- * wrapping each row into a dedicated result object, it delivers data in nearly
1064
- * the same speed as with ordinary results.
1065
- *
1066
- * The result must be in status PGRES_SINGLE_TUPLE.
1067
- * It iterates over all tuples until the status changes to PGRES_TUPLES_OK.
1068
- * A PG::Error is raised for any errors from the server.
1069
- *
1070
- * Row description data does not change while the iteration. All value retrieval
1071
- * methods refer to only the current row. Result#ntuples returns +1+ while
1072
- * the iteration and +0+ after all tuples were yielded.
1073
- *
1074
- * Example:
1075
- * conn.send_query( "first SQL query; second SQL query" )
1076
- * conn.set_single_row_mode
1077
- * conn.get_result.stream_each do |row|
1078
- * # do something with the received row of the first query
1079
- * end
1080
- * conn.get_result.stream_each do |row|
1081
- * # do something with the received row of the second query
1082
- * end
1083
- * conn.get_result # => nil (no more results)
1084
- */
1268
+
1269
+ static void
1270
+ yield_hash(VALUE self, int ntuples, int nfields)
1271
+ {
1272
+ int tuple_num;
1273
+ t_pg_result *this = pgresult_get_this(self);
1274
+ UNUSED(nfields);
1275
+
1276
+ for(tuple_num = 0; tuple_num < ntuples; tuple_num++) {
1277
+ rb_yield(pgresult_aref(self, INT2NUM(tuple_num)));
1278
+ }
1279
+
1280
+ pgresult_clear( this );
1281
+ }
1282
+
1283
+ static void
1284
+ yield_array(VALUE self, int ntuples, int nfields)
1285
+ {
1286
+ int row;
1287
+ t_pg_result *this = pgresult_get_this(self);
1288
+
1289
+ for ( row = 0; row < ntuples; row++ ) {
1290
+ PG_VARIABLE_LENGTH_ARRAY(VALUE, row_values, nfields, PG_MAX_COLUMNS)
1291
+ int field;
1292
+
1293
+ /* populate the row */
1294
+ for ( field = 0; field < nfields; field++ ) {
1295
+ row_values[field] = this->p_typemap->funcs.typecast_result_value(this->p_typemap, self, row, field);
1296
+ }
1297
+ rb_yield( rb_ary_new4( nfields, row_values ));
1298
+ }
1299
+
1300
+ pgresult_clear( this );
1301
+ }
1302
+
1303
+ static void
1304
+ yield_tuple(VALUE self, int ntuples, int nfields)
1305
+ {
1306
+ int tuple_num;
1307
+ t_pg_result *this = pgresult_get_this(self);
1308
+ VALUE result = pg_new_result(this->pgresult, this->connection);
1309
+ UNUSED(nfields);
1310
+
1311
+ for(tuple_num = 0; tuple_num < ntuples; tuple_num++) {
1312
+ VALUE tuple = pgresult_tuple(result, INT2FIX(tuple_num));
1313
+ rb_yield( tuple );
1314
+ }
1315
+ }
1316
+
1085
1317
  static VALUE
1086
- pgresult_stream_each(VALUE self)
1318
+ pgresult_stream_any(VALUE self, void (*yielder)(VALUE, int, int))
1087
1319
  {
1088
1320
  t_pg_result *this;
1089
1321
  int nfields;
@@ -1098,7 +1330,6 @@ pgresult_stream_each(VALUE self)
1098
1330
  nfields = PQnfields(pgresult);
1099
1331
 
1100
1332
  for(;;){
1101
- int tuple_num;
1102
1333
  int ntuples = PQntuples(pgresult);
1103
1334
 
1104
1335
  switch( PQresultStatus(pgresult) ){
@@ -1112,14 +1343,7 @@ pgresult_stream_each(VALUE self)
1112
1343
  pg_result_check( self );
1113
1344
  }
1114
1345
 
1115
- for(tuple_num = 0; tuple_num < ntuples; tuple_num++) {
1116
- rb_yield(pgresult_aref(self, INT2NUM(tuple_num)));
1117
- }
1118
-
1119
- if( !this->autoclear ){
1120
- PQclear( pgresult );
1121
- this->pgresult = NULL;
1122
- }
1346
+ yielder( self, ntuples, nfields );
1123
1347
 
1124
1348
  pgresult = gvl_PQgetResult(pgconn);
1125
1349
  if( pgresult == NULL )
@@ -1135,6 +1359,46 @@ pgresult_stream_each(VALUE self)
1135
1359
  return self;
1136
1360
  }
1137
1361
 
1362
+
1363
+ /*
1364
+ * call-seq:
1365
+ * res.stream_each{ |tuple| ... }
1366
+ *
1367
+ * Invokes block for each tuple in the result set in single row mode.
1368
+ *
1369
+ * This is a convenience method for retrieving all result tuples
1370
+ * as they are transferred. It is an alternative to repeated calls of
1371
+ * PG::Connection#get_result , but given that it avoids the overhead of
1372
+ * wrapping each row into a dedicated result object, it delivers data in nearly
1373
+ * the same speed as with ordinary results.
1374
+ *
1375
+ * The base result must be in status PGRES_SINGLE_TUPLE.
1376
+ * It iterates over all tuples until the status changes to PGRES_TUPLES_OK.
1377
+ * A PG::Error is raised for any errors from the server.
1378
+ *
1379
+ * Row description data does not change while the iteration. All value retrieval
1380
+ * methods refer to only the current row. Result#ntuples returns +1+ while
1381
+ * the iteration and +0+ after all tuples were yielded.
1382
+ *
1383
+ * Example:
1384
+ * conn.send_query( "first SQL query; second SQL query" )
1385
+ * conn.set_single_row_mode
1386
+ * conn.get_result.stream_each do |row|
1387
+ * # do something with each received row of the first query
1388
+ * end
1389
+ * conn.get_result.stream_each do |row|
1390
+ * # do something with each received row of the second query
1391
+ * end
1392
+ * conn.get_result # => nil (no more results)
1393
+ *
1394
+ * Available since PostgreSQL-9.2
1395
+ */
1396
+ static VALUE
1397
+ pgresult_stream_each(VALUE self)
1398
+ {
1399
+ return pgresult_stream_any(self, yield_hash);
1400
+ }
1401
+
1138
1402
  /*
1139
1403
  * call-seq:
1140
1404
  * res.stream_each_row { |row| ... }
@@ -1144,67 +1408,30 @@ pgresult_stream_each(VALUE self)
1144
1408
  *
1145
1409
  * This method works equally to #stream_each , but yields an Array of
1146
1410
  * values.
1411
+ *
1412
+ * Available since PostgreSQL-9.2
1147
1413
  */
1148
1414
  static VALUE
1149
1415
  pgresult_stream_each_row(VALUE self)
1150
1416
  {
1151
- t_pg_result *this;
1152
- int row;
1153
- int nfields;
1154
- PGconn *pgconn;
1155
- PGresult *pgresult;
1156
-
1157
- RETURN_ENUMERATOR(self, 0, NULL);
1158
-
1159
- this = pgresult_get_this_safe(self);
1160
- pgconn = pg_get_pgconn(this->connection);
1161
- pgresult = this->pgresult;
1162
- nfields = PQnfields(pgresult);
1163
-
1164
- for(;;){
1165
- int ntuples = PQntuples(pgresult);
1166
-
1167
- switch( PQresultStatus(pgresult) ){
1168
- case PGRES_TUPLES_OK:
1169
- if( ntuples == 0 )
1170
- return self;
1171
- rb_raise( rb_eInvalidResultStatus, "PG::Result is not in single row mode");
1172
- case PGRES_SINGLE_TUPLE:
1173
- break;
1174
- default:
1175
- pg_result_check( self );
1176
- }
1177
-
1178
- for ( row = 0; row < ntuples; row++ ) {
1179
- PG_VARIABLE_LENGTH_ARRAY(VALUE, row_values, nfields, PG_MAX_COLUMNS)
1180
- int field;
1181
-
1182
- /* populate the row */
1183
- for ( field = 0; field < nfields; field++ ) {
1184
- row_values[field] = this->p_typemap->funcs.typecast_result_value(this->p_typemap, self, row, field);
1185
- }
1186
- rb_yield( rb_ary_new4( nfields, row_values ));
1187
- }
1188
-
1189
- if( !this->autoclear ){
1190
- PQclear( pgresult );
1191
- this->pgresult = NULL;
1192
- }
1193
-
1194
- pgresult = gvl_PQgetResult(pgconn);
1195
- if( pgresult == NULL )
1196
- rb_raise( rb_eNoResultError, "no result received - possibly an intersection with another result retrieval");
1197
-
1198
- if( nfields != PQnfields(pgresult) )
1199
- rb_raise( rb_eInvalidChangeOfResultFields, "number of fields must not change in single row mode");
1200
-
1201
- this->pgresult = pgresult;
1202
- }
1417
+ return pgresult_stream_any(self, yield_array);
1418
+ }
1203
1419
 
1204
- /* never reached */
1205
- return self;
1420
+ /*
1421
+ * call-seq:
1422
+ * res.stream_each_tuple { |tuple| ... }
1423
+ *
1424
+ * Yields each row of the result set in single row mode.
1425
+ *
1426
+ * This method works equally to #stream_each , but yields a PG::Tuple object.
1427
+ *
1428
+ * Available since PostgreSQL-9.2
1429
+ */
1430
+ static VALUE
1431
+ pgresult_stream_each_tuple(VALUE self)
1432
+ {
1433
+ return pgresult_stream_any(self, yield_tuple);
1206
1434
  }
1207
- #endif
1208
1435
 
1209
1436
 
1210
1437
  void
@@ -1255,17 +1482,18 @@ init_pg_result()
1255
1482
  rb_define_method(rb_cPGresult, "values", pgresult_values, 0);
1256
1483
  rb_define_method(rb_cPGresult, "column_values", pgresult_column_values, 1);
1257
1484
  rb_define_method(rb_cPGresult, "field_values", pgresult_field_values, 1);
1485
+ rb_define_method(rb_cPGresult, "tuple_values", pgresult_tuple_values, 1);
1486
+ rb_define_method(rb_cPGresult, "tuple", pgresult_tuple, 1);
1258
1487
  rb_define_method(rb_cPGresult, "cleared?", pgresult_cleared_p, 0);
1259
1488
  rb_define_method(rb_cPGresult, "autoclear?", pgresult_autoclear_p, 0);
1260
1489
 
1261
1490
  rb_define_method(rb_cPGresult, "type_map=", pgresult_type_map_set, 1);
1262
1491
  rb_define_method(rb_cPGresult, "type_map", pgresult_type_map_get, 0);
1263
1492
 
1264
- #ifdef HAVE_PQSETSINGLEROWMODE
1265
1493
  /****** PG::Result INSTANCE METHODS: streaming ******/
1266
1494
  rb_define_method(rb_cPGresult, "stream_each", pgresult_stream_each, 0);
1267
1495
  rb_define_method(rb_cPGresult, "stream_each_row", pgresult_stream_each_row, 0);
1268
- #endif
1496
+ rb_define_method(rb_cPGresult, "stream_each_tuple", pgresult_stream_each_tuple, 0);
1269
1497
  }
1270
1498
 
1271
1499