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 +40 -3
- data/ext/do_postgres_ext.c +156 -58
- data/lib/do_postgres/version.rb +1 -1
- data/spec/integration/do_postgres_spec.rb +20 -2
- data/spec/integration/logging_spec.rb +5 -1
- data/spec/integration/quoting_spec.rb +5 -1
- data/spec/integration/timezone_spec.rb +3 -0
- data/spec/spec_helper.rb +1 -1
- metadata +3 -3
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
|
44
|
-
t.spec_opts << '--
|
45
|
-
t.
|
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"]
|
data/ext/do_postgres_ext.c
CHANGED
@@ -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
|
-
|
214
|
-
|
215
|
-
|
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
|
-
|
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
|
-
|
287
|
-
|
288
|
-
|
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
|
-
|
294
|
+
char * value = NULL;
|
294
295
|
|
295
|
-
|
296
|
-
|
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
|
-
|
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
|
-
|
317
|
-
|
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
|
-
|
321
|
-
|
322
|
-
|
323
|
-
port = StringValuePtr(r_port);
|
324
|
-
}
|
304
|
+
if (Qnil != option_value) {
|
305
|
+
value = StringValuePtr(option_value);
|
306
|
+
}
|
325
307
|
|
326
|
-
|
327
|
-
|
328
|
-
port,
|
329
|
-
NULL,
|
330
|
-
NULL,
|
331
|
-
database,
|
332
|
-
user,
|
333
|
-
password
|
334
|
-
);
|
308
|
+
return value;
|
309
|
+
}
|
335
310
|
|
336
|
-
|
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 =
|
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 =
|
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");
|
data/lib/do_postgres/version.rb
CHANGED
@@ -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
|
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.
|
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-
|
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.
|
23
|
+
version: 0.9.6
|
24
24
|
version:
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: hoe
|