do_postgres 0.9.5 → 0.9.6

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