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 +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
|