do_postgres 0.9.11 → 0.9.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/LICENSE +1 -1
  2. data/Manifest.txt +15 -5
  3. data/Rakefile +7 -121
  4. data/ext/do_postgres_ext/do_postgres_ext.c +245 -108
  5. data/ext/do_postgres_ext/extconf.rb +3 -1
  6. data/lib/do_postgres.rb +5 -2
  7. data/lib/do_postgres/version.rb +1 -1
  8. data/spec/command_spec.rb +9 -0
  9. data/spec/connection_spec.rb +19 -0
  10. data/spec/encoding_spec.rb +8 -0
  11. data/spec/lib/rspec_immediate_feedback_formatter.rb +3 -0
  12. data/spec/reader_spec.rb +8 -0
  13. data/spec/result_spec.rb +86 -0
  14. data/spec/spec_helper.rb +90 -57
  15. data/spec/typecast/array_spec.rb +8 -0
  16. data/spec/typecast/bigdecimal_spec.rb +9 -0
  17. data/spec/typecast/boolean_spec.rb +9 -0
  18. data/spec/typecast/byte_array_spec.rb +8 -0
  19. data/spec/typecast/class_spec.rb +8 -0
  20. data/spec/typecast/date_spec.rb +9 -0
  21. data/spec/typecast/datetime_spec.rb +9 -0
  22. data/spec/typecast/float_spec.rb +9 -0
  23. data/spec/typecast/integer_spec.rb +8 -0
  24. data/spec/typecast/nil_spec.rb +10 -0
  25. data/spec/typecast/range_spec.rb +8 -0
  26. data/spec/typecast/string_spec.rb +8 -0
  27. data/spec/typecast/time_spec.rb +8 -0
  28. data/tasks/gem.rake +61 -0
  29. data/tasks/install.rake +15 -0
  30. data/tasks/native.rake +35 -0
  31. data/tasks/release.rake +75 -0
  32. data/tasks/retrieve.rake +79 -0
  33. data/tasks/spec.rake +18 -0
  34. metadata +72 -44
  35. data/.gitignore +0 -0
  36. data/autobuild.rb +0 -90
  37. data/buildfile +0 -27
  38. data/ext-java/src/main/java/DoPostgresExtService.java +0 -23
  39. data/ext-java/src/main/java/do_postgres/PostgresDriverDefinition.java +0 -12
  40. data/script/timezone_spec_runner.rb +0 -28
  41. data/script/timezones.txt +0 -562
  42. data/spec/integration/do_postgres_spec.rb +0 -312
  43. data/spec/integration/logging_spec.rb +0 -53
  44. data/spec/integration/quoting_spec.rb +0 -25
  45. data/spec/integration/timezone_spec.rb +0 -66
  46. data/spec/spec.opts +0 -2
  47. data/spec/unit/transaction_spec.rb +0 -28
data/LICENSE CHANGED
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
17
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
18
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
19
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt CHANGED
@@ -15,10 +15,20 @@ lib/do_postgres/transaction.rb
15
15
  lib/do_postgres/version.rb
16
16
  script/timezone_spec_runner.rb
17
17
  script/timezones.txt
18
- spec/integration/do_postgres_spec.rb
19
- spec/integration/logging_spec.rb
20
- spec/integration/quoting_spec.rb
21
- spec/integration/timezone_spec.rb
18
+ spec/command_spec.rb
19
+ spec/connection_spec.rb
20
+ spec/reader_spec.rb
21
+ spec/result_spec.rb
22
22
  spec/spec.opts
23
23
  spec/spec_helper.rb
24
- spec/unit/transaction_spec.rb
24
+ spec/typecast/bigdecimal_spec.rb
25
+ spec/typecast/boolean_spec.rb
26
+ spec/typecast/byte_array_spec.rb
27
+ spec/typecast/class_spec.rb
28
+ spec/typecast/date_spec.rb
29
+ spec/typecast/datetime_spec.rb
30
+ spec/typecast/float_spec.rb
31
+ spec/typecast/integer_spec.rb
32
+ spec/typecast/nil_spec.rb
33
+ spec/typecast/string_spec.rb
34
+ spec/typecast/time_spec.rb
data/Rakefile CHANGED
@@ -1,130 +1,16 @@
1
- require 'pathname'
2
1
  require 'rubygems'
3
- require 'spec/rake/spectask'
4
- require 'lib/do_postgres/version'
2
+ require 'rake'
3
+ require 'rake/clean'
5
4
 
5
+ require 'pathname'
6
+ require 'lib/do_postgres/version'
6
7
 
7
8
  ROOT = Pathname(__FILE__).dirname.expand_path
8
9
  JRUBY = RUBY_PLATFORM =~ /java/
9
10
  WINDOWS = Gem.win_platform?
10
11
  SUDO = (WINDOWS || JRUBY) ? '' : ('sudo' unless ENV['SUDOLESS'])
12
+ BINARY_VERSION = '5.0.77'
11
13
 
12
- AUTHOR = "Dirkjan Bussink"
13
- EMAIL = "d.bussink@gmail.com"
14
- GEM_NAME = "do_postgres"
15
- GEM_VERSION = DataObjects::Postgres::VERSION
16
- GEM_DEPENDENCIES = if JRUBY
17
- [["data_objects", GEM_VERSION], ["do_jdbc", GEM_VERSION], ["jdbc-postgres", ">=8.2"]]
18
- else
19
- [["data_objects", GEM_VERSION]]
20
- end
21
- GEM_CLEAN = ['**/*.{o,so,bundle,jar,log,a,gem,dSYM,obj,pdb,lib,def,exp,DS_Store}',
22
- 'ext/Makefile', 'ext-java/target']
23
- GEM_EXTRAS = if JRUBY
24
- {
25
- :has_rdoc => false,
26
- :platform => 'java'
27
- }
28
- else
29
- {
30
- :has_rdoc => false,
31
- :extensions => 'ext/do_postgres_ext/extconf.rb'
32
- }
33
- end
34
-
35
- PROJECT_NAME = "dorb"
36
- PROJECT_URL = "http://rubyforge.org/projects/dorb"
37
- PROJECT_DESCRIPTION = PROJECT_SUMMARY = "A DataObject.rb driver for PostgreSQL"
38
-
39
-
40
- # RCov is run by default, except on the JRuby platform, or if NO_RCOV env is true
41
- RUN_RCOV = JRUBY ? false : (ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true)
42
-
43
- if (tasks_dir = ROOT.parent + 'tasks').directory?
44
- require tasks_dir + 'hoe'
45
- require tasks_dir + 'ext_helper_java'
46
-
47
- setup_java_extension "#{GEM_NAME}_ext", HOE.spec
48
- end
49
-
50
- if JRUBY
51
- HOE.spec.files += ['lib/do_postgres_ext.jar']
52
- HOE.spec.require_paths = Dir['lib']
53
- end
54
-
55
- # compile the extension
56
- if JRUBY
57
- Rake::Task['compile:jruby'].invoke
58
- else
59
- begin
60
- gem('rake-compiler')
61
- require 'rake/extensiontask'
62
- Rake::ExtensionTask.new('do_postgres_ext', HOE.spec)
63
- rescue LoadError
64
- warn "To cross-compile, install rake-compiler (gem install rake-compiler)"
65
- if tasks_dir.directory?
66
- require tasks_dir + 'ext_helper'
67
- setup_c_extension('do_postgres_ext', HOE.spec)
68
- end
69
- end
70
- end
71
-
72
-
73
- def sudo_gem(cmd)
74
- sh "#{SUDO} #{RUBY} -S gem #{cmd}", :verbose => false
75
- end
76
-
77
- # Installation
78
-
79
- desc "Install #{GEM_NAME} #{GEM_VERSION}"
80
- task :install => [ :package ] do
81
- sudo_gem "install pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources"
82
- end
83
-
84
- desc "Uninstall #{GEM_NAME} #{GEM_VERSION}"
85
- task :uninstall => [ :clobber ] do
86
- sudo_gem "uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x"
87
- end
88
-
89
- desc 'Run specifications'
90
- Spec::Rake::SpecTask.new(:spec) do |t|
91
- t.spec_opts << '--format' << 'specdoc' << '--colour'
92
- t.spec_opts << '--loadby' << 'random'
93
- t.spec_files = Pathname.glob(ENV['FILES'] || 'spec/**/*_spec.rb').map { |f| f.to_s }
94
-
95
- begin
96
- t.rcov = RUN_RCOV
97
- t.rcov_opts << '--exclude' << 'spec'
98
- t.rcov_opts << '--text-summary'
99
- t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
100
- rescue Exception
101
- # rcov not installed
102
- end
103
- end
104
-
105
- namespace :ci do
106
-
107
- task :prepare do
108
- rm_rf ROOT + "ci"
109
- mkdir_p ROOT + "ci"
110
- mkdir_p ROOT + "ci/doc"
111
- mkdir_p ROOT + "ci/cyclomatic"
112
- mkdir_p ROOT + "ci/token"
113
- end
114
-
115
- task :publish do
116
- out = ENV['CC_BUILD_ARTIFACTS'] || "out"
117
- mkdir_p out unless File.directory? out
118
-
119
- mv "ci/rspec_report.html", "#{out}/rspec_report.html"
120
- mv "ci/coverage", "#{out}/coverage"
121
- end
122
-
123
- task :spec => :prepare do
124
- Rake::Task[:spec].invoke
125
- mv ROOT + "coverage", ROOT + "ci/coverage"
126
- end
127
-
128
- end
14
+ Dir['tasks/*.rake'].each { |f| import f }
129
15
 
130
- task :ci => ["ci:spec"]
16
+ CLEAN.include(%w[ {tmp,pkg}/ **/*.{o,so,bundle,jar,log,a,gem,dSYM,obj,pdb,exp,DS_Store,rbc,db} ext/do_postgres_ext/Makefile ext-java/target ])
@@ -9,6 +9,22 @@
9
9
  #undef PACKAGE_STRING
10
10
  #undef PACKAGE_TARNAME
11
11
  #undef PACKAGE_VERSION
12
+
13
+ #ifdef _WIN32
14
+ /* On Windows this stuff is also defined by Postgres, but we don't
15
+ want to use Postgres' version actually */
16
+ #undef fsync
17
+ #undef vsnprintf
18
+ #undef snprintf
19
+ #undef sprintf
20
+ #undef printf
21
+ #define cCommand_execute cCommand_execute_sync
22
+ #define do_int64 signed __int64
23
+ #else
24
+ #define cCommand_execute cCommand_execute_async
25
+ #define do_int64 signed long long int
26
+ #endif
27
+
12
28
  #include <ruby.h>
13
29
  #include <string.h>
14
30
  #include <math.h>
@@ -39,11 +55,6 @@
39
55
  #define RARRAY_LEN(a) RARRAY(a)->len
40
56
  #endif
41
57
 
42
- #ifdef _WIN32
43
- #define do_int64 signed __int64
44
- #else
45
- #define do_int64 signed long long int
46
- #endif
47
58
 
48
59
  // To store rb_intern values
49
60
  static ID ID_NEW_DATE;
@@ -53,6 +64,7 @@ static ID ID_LEVEL;
53
64
  static ID ID_TO_S;
54
65
  static ID ID_RATIONAL;
55
66
 
67
+ static VALUE mExtlib;
56
68
  static VALUE mDO;
57
69
  static VALUE cDO_Quoting;
58
70
  static VALUE cDO_Connection;
@@ -63,6 +75,7 @@ static VALUE cDO_Reader;
63
75
  static VALUE rb_cDate;
64
76
  static VALUE rb_cDateTime;
65
77
  static VALUE rb_cBigDecimal;
78
+ static VALUE rb_cByteArray;
66
79
 
67
80
  static VALUE mPostgres;
68
81
  static VALUE cConnection;
@@ -70,6 +83,7 @@ static VALUE cCommand;
70
83
  static VALUE cResult;
71
84
  static VALUE cReader;
72
85
 
86
+ static VALUE eArgumentError;
73
87
  static VALUE ePostgresError;
74
88
 
75
89
  static void data_objects_debug(VALUE string, struct timeval* start) {
@@ -88,9 +102,6 @@ static void data_objects_debug(VALUE string, struct timeval* start) {
88
102
  gettimeofday(&stop, NULL);
89
103
 
90
104
  duration = (stop.tv_sec - start->tv_sec) * 1000000 + stop.tv_usec - start->tv_usec;
91
- if(stop.tv_usec < start->tv_usec) {
92
- duration += 1000000;
93
- }
94
105
 
95
106
  snprintf(total_time, 32, "%.6f", duration / 1000000.0);
96
107
  message = (char *)calloc(length + strlen(total_time) + 4, sizeof(char));
@@ -201,9 +212,14 @@ static VALUE parse_date_time(const char *date) {
201
212
  } else if ((max_tokens - 1) == tokens_read) {
202
213
  // We read the Date and Time, but no Minute Offset
203
214
  minute_offset = 0;
204
- } else if (tokens_read == 3) {
205
- return parse_date(date);
206
- } else if (tokens_read >= (max_tokens - 3)) {
215
+ } else if (tokens_read == 3 || tokens_read >= (max_tokens - 3)) {
216
+ if (tokens_read == 3) {
217
+ hour = 0;
218
+ min = 0;
219
+ hour_offset = 0;
220
+ minute_offset = 0;
221
+ sec = 0;
222
+ }
207
223
  // We read the Date and Time, default to the current locale's offset
208
224
 
209
225
  // Get localtime
@@ -258,18 +274,23 @@ static VALUE parse_date_time(const char *date) {
258
274
  return rb_funcall(rb_cDateTime, ID_NEW_DATE, 3, ajd, offset, INT2NUM(2299161));
259
275
  }
260
276
 
261
- static VALUE parse_time(char *date) {
277
+ static VALUE parse_time(const char *date) {
262
278
 
263
- int year, month, day, hour, min, sec, usec;
279
+ int year, month, day, hour, min, sec, usec, tokens;
264
280
  char subsec[7];
265
281
 
266
282
  if (0 != strchr(date, '.')) {
267
283
  // right padding usec with 0. e.g. '012' will become 12000 microsecond, since Time#local use microsecond
268
284
  sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%s", &year, &month, &day, &hour, &min, &sec, subsec);
269
- usec = atoi(subsec);
270
- usec *= pow(10, (6 - strlen(subsec)));
285
+ usec = atoi(subsec);
286
+ usec *= pow(10, (6 - strlen(subsec)));
271
287
  } else {
272
- sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
288
+ tokens = sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
289
+ if (tokens == 3) {
290
+ hour = 0;
291
+ min = 0;
292
+ sec = 0;
293
+ }
273
294
  usec = 0;
274
295
  }
275
296
 
@@ -279,61 +300,63 @@ static VALUE parse_time(char *date) {
279
300
  /* ===== Typecasting Functions ===== */
280
301
 
281
302
  static VALUE infer_ruby_type(Oid type) {
282
- char *ruby_type = "String";
283
303
  switch(type) {
284
304
  case BITOID:
285
305
  case VARBITOID:
286
306
  case INT2OID:
287
307
  case INT4OID:
288
- case INT8OID: {
289
- ruby_type = "Integer";
290
- break;
291
- }
308
+ case INT8OID:
309
+ return rb_cInteger;
292
310
  case FLOAT4OID:
293
- case FLOAT8OID: {
294
- ruby_type = "Float";
295
- break;
296
- }
311
+ case FLOAT8OID:
312
+ return rb_cFloat;
297
313
  case NUMERICOID:
298
- case CASHOID: {
299
- ruby_type = "BigDecimal";
300
- break;
301
- }
302
- case BOOLOID: {
303
- ruby_type = "TrueClass";
304
- break;
305
- }
314
+ case CASHOID:
315
+ return rb_cBigDecimal;
316
+ case BOOLOID:
317
+ return rb_cTrueClass;
306
318
  case TIMESTAMPTZOID:
307
- case TIMESTAMPOID: {
308
- ruby_type = "DateTime";
309
- break;
310
- }
311
- case DATEOID: {
312
- ruby_type = "Date";
313
- break;
314
- }
319
+ case TIMESTAMPOID:
320
+ return rb_cDateTime;
321
+ case DATEOID:
322
+ return rb_cDate;
323
+ case BYTEAOID:
324
+ return rb_cByteArray;
325
+ default:
326
+ return rb_cString;
315
327
  }
316
- return rb_str_new2(ruby_type);
317
328
  }
318
329
 
319
- static VALUE typecast(char *value, long length, const char *type) {
330
+ static VALUE typecast(const char *value, long length, const VALUE type) {
320
331
 
321
- if ( strcmp(type, "Class") == 0) {
322
- return rb_funcall(rb_cObject, rb_intern("full_const_get"), 1, TAINTED_STRING(value, length));
323
- } else if ( strcmp(type, "Integer") == 0 || strcmp(type, "Fixnum") == 0 || strcmp(type, "Bignum") == 0 ) {
332
+ if (type == rb_cInteger) {
324
333
  return rb_cstr2inum(value, 10);
325
- } else if ( strcmp(type, "Float") == 0 ) {
334
+ } else if (type == rb_cString) {
335
+ return TAINTED_STRING(value, length);
336
+ } else if (type == rb_cFloat) {
326
337
  return rb_float_new(rb_cstr_to_dbl(value, Qfalse));
327
- } else if (0 == strcmp("BigDecimal", type) ) {
338
+ } else if (type == rb_cBigDecimal) {
328
339
  return rb_funcall(rb_cBigDecimal, ID_NEW, 1, TAINTED_STRING(value, length));
329
- } else if ( strcmp(type, "TrueClass") == 0 ) {
330
- return *value == 't' ? Qtrue : Qfalse;
331
- } else if ( strcmp(type, "Date") == 0 ) {
340
+ } else if (type == rb_cDate) {
332
341
  return parse_date(value);
333
- } else if ( strcmp(type, "DateTime") == 0 ) {
342
+ } else if (type == rb_cDateTime) {
334
343
  return parse_date_time(value);
335
- } else if ( strcmp(type, "Time") == 0 ) {
344
+ } else if (type == rb_cTime) {
336
345
  return parse_time(value);
346
+ } else if (type == rb_cTrueClass) {
347
+ return *value == 't' ? Qtrue : Qfalse;
348
+ } else if (type == rb_cByteArray) {
349
+ size_t new_length = 0;
350
+ char* unescaped = (char *)PQunescapeBytea((unsigned char*)value, &new_length);
351
+ VALUE byte_array = rb_funcall(rb_cByteArray, ID_NEW, 1, TAINTED_STRING(unescaped, new_length));
352
+ PQfreemem(unescaped);
353
+ return byte_array;
354
+ } else if (type == rb_cClass) {
355
+ return rb_funcall(rb_cObject, rb_intern("full_const_get"), 1, TAINTED_STRING(value, length));
356
+ } else if (type == rb_cObject) {
357
+ return rb_marshal_load(rb_str_new2(value));
358
+ } else if (type == rb_cNilClass) {
359
+ return Qnil;
337
360
  } else {
338
361
  return TAINTED_STRING(value, length);
339
362
  }
@@ -342,31 +365,72 @@ static VALUE typecast(char *value, long length, const char *type) {
342
365
 
343
366
  /* ====== Public API ======= */
344
367
  static VALUE cConnection_dispose(VALUE self) {
345
- PGconn *db = DATA_PTR(rb_iv_get(self, "@connection"));
368
+ VALUE connection_container = rb_iv_get(self, "@connection");
369
+
370
+ PGconn *db;
371
+
372
+ if (Qnil == connection_container)
373
+ return Qfalse;
374
+
375
+ db = DATA_PTR(connection_container);
376
+
377
+ if (NULL == db)
378
+ return Qfalse;
379
+
346
380
  PQfinish(db);
381
+ rb_iv_set(self, "@connection", Qnil);
382
+
347
383
  return Qtrue;
348
384
  }
349
385
 
350
- static VALUE cCommand_set_types(VALUE self, VALUE array) {
351
- rb_iv_set(self, "@field_types", array);
386
+ static VALUE cCommand_set_types(int argc, VALUE *argv, VALUE self) {
387
+ VALUE type_strings = rb_ary_new();
388
+ VALUE array = rb_ary_new();
389
+
390
+ int i, j;
391
+
392
+ for ( i = 0; i < argc; i++) {
393
+ rb_ary_push(array, argv[i]);
394
+ }
395
+
396
+ for (i = 0; i < RARRAY_LEN(array); i++) {
397
+ VALUE entry = rb_ary_entry(array, i);
398
+ if(TYPE(entry) == T_CLASS) {
399
+ rb_ary_push(type_strings, entry);
400
+ } else if (TYPE(entry) == T_ARRAY) {
401
+ for (j = 0; j < RARRAY_LEN(entry); j++) {
402
+ VALUE sub_entry = rb_ary_entry(entry, j);
403
+ if(TYPE(sub_entry) == T_CLASS) {
404
+ rb_ary_push(type_strings, sub_entry);
405
+ } else {
406
+ rb_raise(eArgumentError, "Invalid type given");
407
+ }
408
+ }
409
+ } else {
410
+ rb_raise(eArgumentError, "Invalid type given");
411
+ }
412
+ }
413
+
414
+ rb_iv_set(self, "@field_types", type_strings);
415
+
352
416
  return array;
353
417
  }
354
418
 
355
419
  static VALUE build_query_from_args(VALUE klass, int count, VALUE *args[]) {
356
420
  VALUE query = rb_iv_get(klass, "@text");
357
- if ( count > 0 ) {
358
- int i;
359
- VALUE array = rb_ary_new();
360
- for ( i = 0; i < count; i++) {
361
- rb_ary_push(array, (VALUE)args[i]);
362
- }
363
- query = rb_funcall(klass, ID_ESCAPE, 1, array);
421
+
422
+ int i;
423
+ VALUE array = rb_ary_new();
424
+ for ( i = 0; i < count; i++) {
425
+ rb_ary_push(array, (VALUE)args[i]);
364
426
  }
427
+ query = rb_funcall(klass, ID_ESCAPE, 1, array);
428
+
365
429
  return query;
366
430
  }
367
431
 
368
- static VALUE cCommand_quote_string(VALUE self, VALUE string) {
369
- PGconn *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
432
+ static VALUE cConnection_quote_string(VALUE self, VALUE string) {
433
+ PGconn *db = DATA_PTR(rb_iv_get(self, "@connection"));
370
434
 
371
435
  const char *source = RSTRING_PTR(string);
372
436
  int source_len = RSTRING_LEN(string);
@@ -390,6 +454,63 @@ static VALUE cCommand_quote_string(VALUE self, VALUE string) {
390
454
  return result;
391
455
  }
392
456
 
457
+ static VALUE cConnection_quote_byte_array(VALUE self, VALUE string) {
458
+ PGconn *db = DATA_PTR(rb_iv_get(self, "@connection"));
459
+
460
+ const unsigned char *source = (unsigned char*) RSTRING_PTR(string);
461
+ size_t source_len = RSTRING_LEN(string);
462
+
463
+ unsigned char *escaped;
464
+ unsigned char *escaped_quotes;
465
+ size_t quoted_length = 0;
466
+ VALUE result;
467
+
468
+ // Allocate space for the escaped version of 'string'
469
+ // http://www.postgresql.org/docs/8.3/static/libpq-exec.html#LIBPQ-EXEC-ESCAPE-STRING
470
+ escaped = PQescapeByteaConn(db, source, source_len, &quoted_length);
471
+ escaped_quotes = (unsigned char *)calloc(quoted_length + 1, sizeof(unsigned char));
472
+ memcpy(escaped_quotes + 1, escaped, quoted_length);
473
+
474
+ // Wrap the escaped string in single-quotes, this is DO's convention (replace trailing \0)
475
+ escaped_quotes[quoted_length] = escaped_quotes[0] = '\'';
476
+
477
+ result = TAINTED_STRING((char*)escaped_quotes, quoted_length + 1);
478
+ PQfreemem(escaped);
479
+ free(escaped_quotes);
480
+ return result;
481
+ }
482
+
483
+ #ifdef _WIN32
484
+ static PGresult* cCommand_execute_sync(PGconn *db, VALUE query) {
485
+ PGresult *response;
486
+ struct timeval start;
487
+ char* str = StringValuePtr(query);
488
+
489
+ while ((response = PQgetResult(db)) != NULL) {
490
+ PQclear(response);
491
+ }
492
+
493
+ gettimeofday(&start, NULL);
494
+
495
+ response = PQexec(db, str);
496
+
497
+ if (response == NULL) {
498
+ if(PQstatus(db) != CONNECTION_OK) {
499
+ PQreset(db);
500
+ if (PQstatus(db) == CONNECTION_OK) {
501
+ response = PQexec(db, str);
502
+ }
503
+ }
504
+
505
+ if(response == NULL) {
506
+ rb_raise(ePostgresError, PQerrorMessage(db));
507
+ }
508
+ }
509
+
510
+ data_objects_debug(query, &start);
511
+ return response;
512
+ }
513
+ #else
393
514
  static PGresult* cCommand_execute_async(PGconn *db, VALUE query) {
394
515
  int socket_fd;
395
516
  int retval;
@@ -444,6 +565,7 @@ static PGresult* cCommand_execute_async(PGconn *db, VALUE query) {
444
565
  data_objects_debug(query, &start);
445
566
  return PQgetResult(db);
446
567
  }
568
+ #endif
447
569
 
448
570
  static VALUE cConnection_initialize(VALUE self, VALUE uri) {
449
571
  PGresult *result = NULL;
@@ -455,6 +577,7 @@ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
455
577
  char *search_path_query = NULL;
456
578
  char *backslash_off = "SET backslash_quote = off";
457
579
  char *standard_strings_on = "SET standard_conforming_strings = on";
580
+ char *warning_messages = "SET client_min_messages = warning";
458
581
 
459
582
  PGconn *db;
460
583
 
@@ -512,7 +635,7 @@ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
512
635
  search_path_query = (char *)calloc(256, sizeof(char));
513
636
  snprintf(search_path_query, 256, "set search_path to %s;", search_path);
514
637
  r_query = rb_str_new2(search_path_query);
515
- result = cCommand_execute_async(db, r_query);
638
+ result = cCommand_execute(db, r_query);
516
639
 
517
640
  if (PQresultStatus(result) != PGRES_COMMAND_OK) {
518
641
  free(search_path_query);
@@ -523,14 +646,21 @@ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
523
646
  }
524
647
 
525
648
  r_options = rb_str_new2(backslash_off);
526
- result = cCommand_execute_async(db, r_options);
649
+ result = cCommand_execute(db, r_options);
527
650
 
528
651
  if (PQresultStatus(result) != PGRES_COMMAND_OK) {
529
652
  rb_warn(PQresultErrorMessage(result));
530
653
  }
531
654
 
532
655
  r_options = rb_str_new2(standard_strings_on);
533
- result = cCommand_execute_async(db, r_options);
656
+ result = cCommand_execute(db, r_options);
657
+
658
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
659
+ rb_warn(PQresultErrorMessage(result));
660
+ }
661
+
662
+ r_options = rb_str_new2(warning_messages);
663
+ result = cCommand_execute(db, r_options);
534
664
 
535
665
  if (PQresultStatus(result) != PGRES_COMMAND_OK) {
536
666
  rb_warn(PQresultErrorMessage(result));
@@ -569,26 +699,32 @@ static VALUE cConnection_character_set(VALUE self) {
569
699
  }
570
700
 
571
701
  static VALUE cCommand_execute_non_query(int argc, VALUE *argv[], VALUE self) {
572
- PGconn *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
702
+ VALUE connection = rb_iv_get(self, "@connection");
703
+ VALUE postgres_connection = rb_iv_get(connection, "@connection");
704
+ if (Qnil == postgres_connection) {
705
+ rb_raise(ePostgresError, "This connection has already been closed.");
706
+ }
707
+
708
+ PGconn *db = DATA_PTR(postgres_connection);
573
709
  PGresult *response;
574
710
  int status;
575
711
 
576
- int affected_rows;
577
- int insert_id;
712
+ VALUE affected_rows = Qnil;
713
+ VALUE insert_id = Qnil;
578
714
 
579
715
  VALUE query = build_query_from_args(self, argc, argv);
580
716
 
581
- response = cCommand_execute_async(db, query);
717
+ response = cCommand_execute(db, query);
582
718
 
583
719
  status = PQresultStatus(response);
584
720
 
585
721
  if ( status == PGRES_TUPLES_OK ) {
586
- insert_id = atoi(PQgetvalue(response, 0, 0));
587
- affected_rows = 1;
722
+ insert_id = INT2NUM(atoi(PQgetvalue(response, 0, 0)));
723
+ affected_rows = INT2NUM(atoi(PQcmdTuples(response)));
588
724
  }
589
725
  else if ( status == PGRES_COMMAND_OK ) {
590
- insert_id = 0;
591
- affected_rows = atoi(PQcmdTuples(response));
726
+ insert_id = Qnil;
727
+ affected_rows = INT2NUM(atoi(PQcmdTuples(response)));
592
728
  }
593
729
  else {
594
730
  char *message = PQresultErrorMessage(response);
@@ -599,7 +735,7 @@ static VALUE cCommand_execute_non_query(int argc, VALUE *argv[], VALUE self) {
599
735
 
600
736
  PQclear(response);
601
737
 
602
- return rb_funcall(cResult, ID_NEW, 3, self, INT2NUM(affected_rows), INT2NUM(insert_id));
738
+ return rb_funcall(cResult, ID_NEW, 3, self, affected_rows, insert_id);
603
739
  }
604
740
 
605
741
  static VALUE cCommand_execute_reader(int argc, VALUE *argv[], VALUE self) {
@@ -610,12 +746,18 @@ static VALUE cCommand_execute_reader(int argc, VALUE *argv[], VALUE self) {
610
746
  int field_count;
611
747
  int infer_types = 0;
612
748
 
613
- PGconn *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
749
+ VALUE connection = rb_iv_get(self, "@connection");
750
+ VALUE postgres_connection = rb_iv_get(connection, "@connection");
751
+ if (Qnil == postgres_connection) {
752
+ rb_raise(ePostgresError, "This connection has already been closed.");
753
+ }
754
+
755
+ PGconn *db = DATA_PTR(postgres_connection);
614
756
  PGresult *response;
615
757
 
616
758
  query = build_query_from_args(self, argc, argv);
617
759
 
618
- response = cCommand_execute_async(db, query);
760
+ response = cCommand_execute(db, query);
619
761
 
620
762
  if ( PQresultStatus(response) != PGRES_TUPLES_OK ) {
621
763
  char *message = PQresultErrorMessage(response);
@@ -633,9 +775,14 @@ static VALUE cCommand_execute_reader(int argc, VALUE *argv[], VALUE self) {
633
775
  field_names = rb_ary_new();
634
776
  field_types = rb_iv_get(self, "@field_types");
635
777
 
636
- if ( field_types == Qnil || RARRAY_LEN(field_types) == 0 ) {
778
+ if ( field_types == Qnil || 0 == RARRAY_LEN(field_types) ) {
637
779
  field_types = rb_ary_new();
638
780
  infer_types = 1;
781
+ } else if (RARRAY_LEN(field_types) != field_count) {
782
+ // Whoops... wrong number of types passed to set_types. Close the reader and raise
783
+ // and error
784
+ rb_funcall(reader, rb_intern("close"), 0);
785
+ rb_raise(eArgumentError, "Field-count mismatch. Expected %ld fields, but the query yielded %d", RARRAY_LEN(field_types), field_count);
639
786
  }
640
787
 
641
788
  for ( i = 0; i < field_count; i++ ) {
@@ -678,10 +825,8 @@ static VALUE cReader_next(VALUE self) {
678
825
  int i;
679
826
  int position;
680
827
 
681
- const char *type;
682
-
683
828
  VALUE array = rb_ary_new();
684
- VALUE field_types, ruby_type;
829
+ VALUE field_types, field_type;
685
830
  VALUE value;
686
831
 
687
832
  row_count = NUM2INT(rb_iv_get(self, "@row_count"));
@@ -690,22 +835,16 @@ static VALUE cReader_next(VALUE self) {
690
835
  position = NUM2INT(rb_iv_get(self, "@position"));
691
836
 
692
837
  if ( position > (row_count-1) ) {
693
- return Qnil;
838
+ rb_iv_set(self, "@values", Qnil);
839
+ return Qfalse;
694
840
  }
695
841
 
696
842
  for ( i = 0; i < field_count; i++ ) {
697
- ruby_type = RARRAY_PTR(field_types)[i];
698
-
699
- if ( TYPE(ruby_type) == T_STRING ) {
700
- type = StringValuePtr(ruby_type);
701
- }
702
- else {
703
- type = rb_class2name(ruby_type);
704
- }
843
+ field_type = rb_ary_entry(field_types, i);
705
844
 
706
845
  // Always return nil if the value returned from Postgres is null
707
846
  if (!PQgetisnull(reader, position, i)) {
708
- value = typecast(PQgetvalue(reader, position, i), PQgetlength(reader, position, i), type);
847
+ value = typecast(PQgetvalue(reader, position, i), PQgetlength(reader, position, i), field_type);
709
848
  } else {
710
849
  value = Qnil;
711
850
  }
@@ -721,14 +860,12 @@ static VALUE cReader_next(VALUE self) {
721
860
 
722
861
  static VALUE cReader_values(VALUE self) {
723
862
 
724
- int position = rb_iv_get(self, "@position");
725
- int row_count = NUM2INT(rb_iv_get(self, "@row_count"));
726
-
727
- if ( position == Qnil || NUM2INT(position) > row_count ) {
863
+ VALUE values = rb_iv_get(self, "@values");
864
+ if(values == Qnil) {
728
865
  rb_raise(ePostgresError, "Reader not initialized");
729
- }
730
- else {
731
- return rb_iv_get(self, "@values");
866
+ return Qnil;
867
+ } else {
868
+ return values;
732
869
  }
733
870
  }
734
871
 
@@ -739,9 +876,6 @@ static VALUE cReader_fields(VALUE self) {
739
876
  static VALUE cReader_field_count(VALUE self) {
740
877
  return rb_iv_get(self, "@field_count");
741
878
  }
742
- static VALUE cReader_row_count(VALUE self) {
743
- return rb_iv_get(self, "@row_count");
744
- }
745
879
 
746
880
  void Init_do_postgres_ext() {
747
881
  rb_require("date");
@@ -750,7 +884,6 @@ void Init_do_postgres_ext() {
750
884
  // Get references classes needed for Date/Time parsing
751
885
  rb_cDate = CONST_GET(rb_mKernel, "Date");
752
886
  rb_cDateTime = CONST_GET(rb_mKernel, "DateTime");
753
- rb_cTime = CONST_GET(rb_mKernel, "Time");
754
887
  rb_cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal");
755
888
 
756
889
  rb_funcall(rb_mKernel, rb_intern("require"), 1, rb_str_new2("data_objects"));
@@ -766,6 +899,10 @@ void Init_do_postgres_ext() {
766
899
  ID_TO_S = rb_intern("to_s");
767
900
  ID_RATIONAL = rb_intern("Rational");
768
901
 
902
+ // Get references to the Extlib module
903
+ mExtlib = CONST_GET(rb_mKernel, "Extlib");
904
+ rb_cByteArray = CONST_GET(mExtlib, "ByteArray");
905
+
769
906
  // Get references to the DataObjects module and its classes
770
907
  mDO = CONST_GET(rb_mKernel, "DataObjects");
771
908
  cDO_Quoting = CONST_GET(mDO, "Quoting");
@@ -774,6 +911,7 @@ void Init_do_postgres_ext() {
774
911
  cDO_Result = CONST_GET(mDO, "Result");
775
912
  cDO_Reader = CONST_GET(mDO, "Reader");
776
913
 
914
+ eArgumentError = CONST_GET(rb_mKernel, "ArgumentError");
777
915
  mPostgres = rb_define_module_under(mDO, "Postgres");
778
916
  ePostgresError = rb_define_class("PostgresError", rb_eStandardError);
779
917
 
@@ -781,13 +919,13 @@ void Init_do_postgres_ext() {
781
919
  rb_define_method(cConnection, "initialize", cConnection_initialize, 1);
782
920
  rb_define_method(cConnection, "dispose", cConnection_dispose, 0);
783
921
  rb_define_method(cConnection, "character_set", cConnection_character_set , 0);
922
+ rb_define_method(cConnection, "quote_string", cConnection_quote_string, 1);
923
+ rb_define_method(cConnection, "quote_byte_array", cConnection_quote_byte_array, 1);
784
924
 
785
925
  cCommand = POSTGRES_CLASS("Command", cDO_Command);
786
- rb_include_module(cCommand, cDO_Quoting);
787
- rb_define_method(cCommand, "set_types", cCommand_set_types, 1);
926
+ rb_define_method(cCommand, "set_types", cCommand_set_types, -1);
788
927
  rb_define_method(cCommand, "execute_non_query", cCommand_execute_non_query, -1);
789
928
  rb_define_method(cCommand, "execute_reader", cCommand_execute_reader, -1);
790
- rb_define_method(cCommand, "quote_string", cCommand_quote_string, 1);
791
929
 
792
930
  cResult = POSTGRES_CLASS("Result", cDO_Result);
793
931
 
@@ -796,7 +934,6 @@ void Init_do_postgres_ext() {
796
934
  rb_define_method(cReader, "next!", cReader_next, 0);
797
935
  rb_define_method(cReader, "values", cReader_values, 0);
798
936
  rb_define_method(cReader, "fields", cReader_fields, 0);
799
- rb_define_method(cReader, "row_count", cReader_row_count, 0);
800
937
  rb_define_method(cReader, "field_count", cReader_field_count, 0);
801
938
 
802
939
  }