do_postgres 0.9.5 → 0.9.6

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/Rakefile CHANGED
@@ -40,7 +40,44 @@ task :uninstall => [ :clobber ] do
40
40
  end
41
41
 
42
42
  desc 'Run specifications'
43
- Spec::Rake::SpecTask.new(:spec => [ :compile ]) do |t|
44
- t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
45
- t.spec_files = Pathname.glob(Pathname.new(__FILE__).dirname + 'spec/**/*_spec.rb')
43
+ Spec::Rake::SpecTask.new(:spec) do |t|
44
+ t.spec_opts << '--format' << 'specdoc' << '--colour'
45
+ t.spec_opts << '--loadby' << 'random'
46
+ t.spec_files = Pathname.glob(ENV['FILES'] || 'spec/**/*_spec.rb')
47
+
48
+ begin
49
+ t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
50
+ t.rcov_opts << '--exclude' << 'spec'
51
+ t.rcov_opts << '--text-summary'
52
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
53
+ rescue Exception
54
+ # rcov not installed
55
+ end
46
56
  end
57
+
58
+ namespace :ci do
59
+
60
+ task :prepare do
61
+ rm_rf ROOT + "ci"
62
+ mkdir_p ROOT + "ci"
63
+ mkdir_p ROOT + "ci/doc"
64
+ mkdir_p ROOT + "ci/cyclomatic"
65
+ mkdir_p ROOT + "ci/token"
66
+ end
67
+
68
+ task :publish do
69
+ out = ENV['CC_BUILD_ARTIFACTS'] || "out"
70
+ mkdir_p out unless File.directory? out
71
+
72
+ mv "ci/rspec_report.html", "#{out}/rspec_report.html"
73
+ mv "ci/coverage", "#{out}/coverage"
74
+ end
75
+
76
+ task :spec => :prepare do
77
+ Rake::Task[:spec].invoke
78
+ mv ROOT + "coverage", ROOT + "ci/coverage"
79
+ end
80
+
81
+ end
82
+
83
+ task :ci => ["ci:spec"]
@@ -17,6 +17,7 @@
17
17
  #define CONST_GET(scope, constant) (rb_funcall(scope, ID_CONST_GET, 1, rb_str_new2(constant)))
18
18
  #define POSTGRES_CLASS(klass, parent) (rb_define_class_under(mPostgres, klass, parent))
19
19
  #define DEBUG(value) data_objects_debug(value)
20
+ #define RUBY_CLASS(name) rb_const_get(rb_cObject, rb_intern(name))
20
21
 
21
22
  #ifdef _WIN32
22
23
  #define do_int64 signed __int64
@@ -29,6 +30,8 @@ static ID ID_NEW_DATE;
29
30
  static ID ID_LOGGER;
30
31
  static ID ID_DEBUG;
31
32
  static ID ID_LEVEL;
33
+ static ID ID_TO_S;
34
+ static ID ID_PARSE;
32
35
 
33
36
  static VALUE mDO;
34
37
  static VALUE cDO_Quoting;
@@ -41,6 +44,7 @@ static VALUE rb_cDate;
41
44
  static VALUE rb_cDateTime;
42
45
  static VALUE rb_cRational;
43
46
  static VALUE rb_cBigDecimal;
47
+ static VALUE rb_cCGI;
44
48
 
45
49
  static VALUE mPostgres;
46
50
  static VALUE cConnection;
@@ -210,9 +214,9 @@ static VALUE parse_time(char *date) {
210
214
 
211
215
  if (0 != strchr(date, '.')) {
212
216
  // right padding usec with 0. e.g. '012' will become 12000 microsecond, since Time#local use microsecond
213
- sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%s", &year, &month, &day, &hour, &min, &sec, subsec);
214
- usec = atoi(subsec);
215
- usec *= pow(10, (6 - strlen(subsec)));
217
+ sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%s", &year, &month, &day, &hour, &min, &sec, subsec);
218
+ usec = atoi(subsec);
219
+ usec *= pow(10, (6 - strlen(subsec)));
216
220
  } else {
217
221
  sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
218
222
  usec = 0;
@@ -262,7 +266,7 @@ static VALUE infer_ruby_type(Oid type) {
262
266
  static VALUE typecast(char *value, char *type) {
263
267
 
264
268
  if ( strcmp(type, "Class") == 0) {
265
- return rb_funcall(mDO, rb_intern("find_const"), 1, TAINTED_STRING(value));
269
+ return rb_funcall(mDO, rb_intern("find_const"), 1, TAINTED_STRING(value));
266
270
  } else if ( strcmp(type, "Integer") == 0 || strcmp(type, "Fixnum") == 0 || strcmp(type, "Bignum") == 0 ) {
267
271
  return rb_cstr2inum(value, 10);
268
272
  } else if ( strcmp(type, "Float") == 0 ) {
@@ -283,65 +287,30 @@ static VALUE typecast(char *value, char *type) {
283
287
 
284
288
  }
285
289
 
286
- /* ====== Public API ======= */
287
-
288
- static VALUE cConnection_initialize(VALUE self, VALUE uri) {
289
- VALUE r_host, r_user, r_password, r_path, r_port;
290
- char *host = NULL, *user = NULL, *password = NULL, *path;
291
- char *database = "", *port = "5432";
290
+ // Pull an option out of a querystring-formmated option list using CGI::parse
291
+ static char * get_uri_option(VALUE querystring, char * key) {
292
+ VALUE options_hash, option_value;
292
293
 
293
- PGconn *db;
294
+ char * value = NULL;
294
295
 
295
- r_host = rb_funcall(uri, rb_intern("host"), 0);
296
- if ( Qnil != r_host && "localhost" != StringValuePtr(r_host) ) {
297
- host = StringValuePtr(r_host);
298
- }
299
-
300
- r_user = rb_funcall(uri, rb_intern("user"), 0);
301
- if (Qnil != r_user) {
302
- user = StringValuePtr(r_user);
303
- }
304
-
305
- r_password = rb_funcall(uri, rb_intern("password"), 0);
306
- if (Qnil != r_password) {
307
- password = StringValuePtr(r_password);
308
- }
296
+ // Ensure that we're dealing with a string
297
+ querystring = rb_funcall(querystring, ID_TO_S, 0);
309
298
 
310
- r_path = rb_funcall(uri, rb_intern("path"), 0);
311
- path = StringValuePtr(r_path);
312
- if (Qnil != r_path) {
313
- database = strtok(path, "/");
314
- }
299
+ options_hash = rb_funcall(rb_cCGI, ID_PARSE, 1, querystring);
315
300
 
316
- if (NULL == database || 0 == strlen(database)) {
317
- rb_raise(ePostgresError, "Database must be specified");
318
- }
301
+ // TODO: rb_hash_aref always returns an array?
302
+ option_value = rb_ary_entry(rb_hash_aref(options_hash, RUBY_STRING(key)), 0);
319
303
 
320
- r_port = rb_funcall(uri, rb_intern("port"), 0);
321
- if (Qnil != r_port) {
322
- r_port = rb_funcall(r_port, rb_intern("to_s"), 0);
323
- port = StringValuePtr(r_port);
324
- }
304
+ if (Qnil != option_value) {
305
+ value = StringValuePtr(option_value);
306
+ }
325
307
 
326
- db = PQsetdbLogin(
327
- host,
328
- port,
329
- NULL,
330
- NULL,
331
- database,
332
- user,
333
- password
334
- );
308
+ return value;
309
+ }
335
310
 
336
- if ( PQstatus(db) == CONNECTION_BAD ) {
337
- rb_raise(ePostgresError, PQerrorMessage(db));
338
- }
311
+ /* ====== Public API ======= */
339
312
 
340
- rb_iv_set(self, "@uri", uri);
341
- rb_iv_set(self, "@connection", Data_Wrap_Struct(rb_cObject, 0, 0, db));
342
313
 
343
- return Qtrue;
344
- }
345
314
 
346
315
  static VALUE cConnection_dispose(VALUE self) {
347
316
  PGconn *db = DATA_PTR(rb_iv_get(self, "@connection"));
@@ -393,6 +362,132 @@ static VALUE cCommand_quote_string(VALUE self, VALUE string) {
393
362
  return result;
394
363
  }
395
364
 
365
+ static PGresult* cCommand_execute_async(PGconn *db, VALUE query) {
366
+ int socket_fd;
367
+ int retval;
368
+ fd_set rset;
369
+ PGresult *response;
370
+ char* str = StringValuePtr(query);
371
+ while ((response = PQgetResult(db)) != NULL) {
372
+ PQclear(response);
373
+ }
374
+
375
+ retval = PQsendQuery(db, str);
376
+ data_objects_debug(query);
377
+
378
+ if (!retval) {
379
+ rb_raise(ePostgresError, PQerrorMessage(db));
380
+ }
381
+
382
+ socket_fd = PQsocket(db);
383
+
384
+ for(;;) {
385
+ FD_ZERO(&rset);
386
+ FD_SET(socket_fd, &rset);
387
+ retval = rb_thread_select(socket_fd + 1, &rset, NULL, NULL, NULL);
388
+ if (retval < 0) {
389
+ rb_sys_fail(0);
390
+ }
391
+
392
+ if (retval == 0) {
393
+ continue;
394
+ }
395
+
396
+ if (PQconsumeInput(db) == 0) {
397
+ rb_raise(ePostgresError, PQerrorMessage(db));
398
+ }
399
+
400
+ if (PQisBusy(db) == 0) {
401
+ break;
402
+ }
403
+ }
404
+
405
+ return PQgetResult(db);
406
+ }
407
+
408
+ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
409
+ PGresult *result = NULL;
410
+ VALUE r_host, r_user, r_password, r_path, r_port, r_options, r_query;
411
+ char *host = NULL, *user = NULL, *password = NULL, *path;
412
+ char *database = "", *port = "5432";
413
+ char *search_path = NULL;
414
+ char *search_path_query = NULL;
415
+ PGconn *db;
416
+
417
+ r_host = rb_funcall(uri, rb_intern("host"), 0);
418
+ if ( Qnil != r_host && "localhost" != StringValuePtr(r_host) ) {
419
+ host = StringValuePtr(r_host);
420
+ }
421
+
422
+ r_user = rb_funcall(uri, rb_intern("user"), 0);
423
+ if (Qnil != r_user) {
424
+ user = StringValuePtr(r_user);
425
+ }
426
+
427
+ r_password = rb_funcall(uri, rb_intern("password"), 0);
428
+ if (Qnil != r_password) {
429
+ password = StringValuePtr(r_password);
430
+ }
431
+
432
+ r_path = rb_funcall(uri, rb_intern("path"), 0);
433
+ path = StringValuePtr(r_path);
434
+ if (Qnil != r_path) {
435
+ database = strtok(path, "/");
436
+ }
437
+
438
+ if (NULL == database || 0 == strlen(database)) {
439
+ rb_raise(ePostgresError, "Database must be specified");
440
+ }
441
+
442
+ r_port = rb_funcall(uri, rb_intern("port"), 0);
443
+ if (Qnil != r_port) {
444
+ r_port = rb_funcall(r_port, rb_intern("to_s"), 0);
445
+ port = StringValuePtr(r_port);
446
+ }
447
+
448
+ // Pull the querystring off the URI
449
+ r_options = rb_funcall(uri, rb_intern("query"), 0);
450
+
451
+ search_path = get_uri_option(r_options, "search_path");
452
+
453
+ db = PQsetdbLogin(
454
+ host,
455
+ port,
456
+ NULL,
457
+ NULL,
458
+ database,
459
+ user,
460
+ password
461
+ );
462
+
463
+ if ( PQstatus(db) == CONNECTION_BAD ) {
464
+ rb_raise(ePostgresError, PQerrorMessage(db));
465
+ }
466
+
467
+ if (search_path != NULL) {
468
+ search_path_query = (char *) malloc(256 * sizeof(char));
469
+ memset(search_path_query, 0, 256);
470
+ sprintf(search_path_query, "set search_path to %s;", search_path);
471
+ r_query = rb_str_new(search_path_query, strlen(search_path_query) + 1);
472
+ result = cCommand_execute_async(db, r_query);
473
+ // printf("status = %s\n", PQresStatus(PQresultStatus(result)));
474
+ // printf("result msg: %s\n", PQresultErrorMessage(result));
475
+
476
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
477
+ free(search_path_query);
478
+ rb_raise(ePostgresError, PQresultErrorMessage(result));
479
+ }
480
+
481
+ free(search_path_query);
482
+ }
483
+
484
+ rb_iv_set(self, "@uri", uri);
485
+ rb_iv_set(self, "@connection", Data_Wrap_Struct(rb_cObject, 0, 0, db));
486
+
487
+
488
+ return Qtrue;
489
+ }
490
+
396
491
  static VALUE cCommand_execute_non_query(int argc, VALUE *argv[], VALUE self) {
397
492
  PGconn *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
398
493
  PGresult *response;
@@ -402,9 +497,8 @@ static VALUE cCommand_execute_non_query(int argc, VALUE *argv[], VALUE self) {
402
497
  int insert_id;
403
498
 
404
499
  VALUE query = build_query_from_args(self, argc, argv);
405
- data_objects_debug(query);
406
500
 
407
- response = PQexec(db, StringValuePtr(query));
501
+ response = cCommand_execute_async(db, query);
408
502
 
409
503
  status = PQresultStatus(response);
410
504
 
@@ -439,9 +533,8 @@ static VALUE cCommand_execute_reader(int argc, VALUE *argv[], VALUE self) {
439
533
  PGresult *response;
440
534
 
441
535
  query = build_query_from_args(self, argc, argv);
442
- data_objects_debug(query);
443
536
 
444
- response = PQexec(db, StringValuePtr(query));
537
+ response = cCommand_execute_async(db, query);
445
538
 
446
539
  if ( PQresultStatus(response) != PGRES_TUPLES_OK ) {
447
540
  char *message = PQresultErrorMessage(response);
@@ -566,6 +659,7 @@ void Init_do_postgres_ext() {
566
659
  rb_require("rubygems");
567
660
  rb_require("date");
568
661
  rb_require("bigdecimal");
662
+ rb_require("cgi");
569
663
 
570
664
  // Get references classes needed for Date/Time parsing
571
665
  rb_cDate = CONST_GET(rb_mKernel, "Date");
@@ -573,6 +667,8 @@ void Init_do_postgres_ext() {
573
667
  rb_cTime = CONST_GET(rb_mKernel, "Time");
574
668
  rb_cRational = CONST_GET(rb_mKernel, "Rational");
575
669
  rb_cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal");
670
+
671
+ rb_cCGI = RUBY_CLASS("CGI");
576
672
 
577
673
  rb_funcall(rb_mKernel, rb_intern("require"), 1, rb_str_new2("data_objects"));
578
674
 
@@ -580,6 +676,8 @@ void Init_do_postgres_ext() {
580
676
  ID_LOGGER = rb_intern("logger");
581
677
  ID_DEBUG = rb_intern("debug");
582
678
  ID_LEVEL = rb_intern("level");
679
+ ID_TO_S = rb_intern("to_s");
680
+ ID_PARSE = rb_intern("parse");
583
681
 
584
682
  // Get references to the DataObjects module and its classes
585
683
  mDO = CONST_GET(rb_mKernel, "DataObjects");
@@ -1,5 +1,5 @@
1
1
  module DataObjects
2
2
  module Postgres
3
- VERSION = "0.9.5"
3
+ VERSION = "0.9.6"
4
4
  end
5
5
  end
@@ -4,7 +4,7 @@ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
4
4
  #
5
5
  #
6
6
  # Create a postgres db named do_test that accepts connections
7
- # from localhost from your current user (without password) to enable this spec.
7
+ # from localhost from the postgres user (without password) to enable this spec.
8
8
  #
9
9
  # You also need to allow passwordless access from localhost-
10
10
  # locate the following line in your pg_hba.conf file:
@@ -20,9 +20,27 @@ describe "DataObjects::Postgres::Connection" do
20
20
  include PostgresSpecHelpers
21
21
 
22
22
  it "should connect to the db" do
23
- connection = DataObjects::Connection.new("postgres://localhost/do_test")
23
+ connection = DataObjects::Connection.new("postgres://postgres@localhost/do_test")
24
24
  connection.close
25
25
  end
26
+
27
+ it "should be able to send querues asynchronuously in parallel" do
28
+ threads = []
29
+
30
+ start = Time.now
31
+ 4.times do |i|
32
+ threads << Thread.new do
33
+ connection = DataObjects::Connection.new("postgres://postgres@localhost/do_test")
34
+ command = connection.create_command("SELECT pg_sleep(1)")
35
+ result = command.execute_non_query
36
+ end
37
+ end
38
+
39
+ threads.each{|t| t.join }
40
+ finish = Time.now
41
+ (finish - start).should < 2
42
+ end
43
+
26
44
  end
27
45
 
28
46
  describe "DataObjects::Postgres::Command" do
@@ -4,7 +4,11 @@ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
4
4
  describe DataObjects::Postgres::Command do
5
5
 
6
6
  before(:each) do
7
- @connection = DataObjects::Connection.new("postgres://localhost/do_test")
7
+ @connection = DataObjects::Connection.new("postgres://postgres@localhost/do_test")
8
+ end
9
+
10
+ after(:each) do
11
+ @connection.close
8
12
  end
9
13
 
10
14
  describe "Executing a Reader" do
@@ -4,10 +4,14 @@ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
4
4
  describe DataObjects::Postgres::Command do
5
5
 
6
6
  before(:each) do
7
- @connection = DataObjects::Connection.new("postgres://localhost/do_test")
7
+ @connection = DataObjects::Connection.new("postgres://postgres@localhost/do_test")
8
8
  @command = @connection.create_command("INSERT INTO users (name) VALUES (?)")
9
9
  end
10
10
 
11
+ after(:each) do
12
+ @connection.close
13
+ end
14
+
11
15
  it "should properly quote a string" do
12
16
  @command.quote_string("O'Hare").should == "'O''Hare'"
13
17
  @command.quote_string("Willy O'Hare & Johnny O'Toole").should == "'Willy O''Hare & Johnny O''Toole'"
@@ -13,6 +13,9 @@ describe "DataObjects::Postgres::Reader" do
13
13
  @connection.create_command("INSERT INTO users (name) VALUES ('Test')").execute_non_query
14
14
  end
15
15
 
16
+ after :all do
17
+ @connection.close
18
+ end
16
19
 
17
20
  it "should return DateTimes using the current locale's Time Zone for TIMESTAMP WITHOUT TIME ZONE fields" do
18
21
  date = DateTime.now
data/spec/spec_helper.rb CHANGED
@@ -27,7 +27,7 @@ DataObjects::Postgres.logger = DataObjects::Logger.new(log_path, 0)
27
27
  module PostgresSpecHelpers
28
28
 
29
29
  def ensure_users_table_and_return_connection
30
- connection = DataObjects::Connection.new("postgres://localhost/do_test")
30
+ connection = DataObjects::Connection.new("postgres://postgres@localhost/do_test")
31
31
  connection.create_command("DROP TABLE users").execute_non_query rescue nil
32
32
  connection.create_command("DROP TABLE companies").execute_non_query rescue nil
33
33
  connection.create_command(<<-EOF).execute_non_query
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: do_postgres
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5
4
+ version: 0.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernerd Schaefer
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-26 00:00:00 -05:00
12
+ date: 2008-10-12 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - "="
22
22
  - !ruby/object:Gem::Version
23
- version: 0.9.5
23
+ version: 0.9.6
24
24
  version:
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: hoe