swift-db-postgres 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/README.md +131 -0
- data/ext/swift/db/postgres/adapter.c +580 -0
- data/ext/swift/db/postgres/adapter.h +14 -0
- data/ext/swift/db/postgres/common.c +59 -0
- data/ext/swift/db/postgres/common.h +35 -0
- data/ext/swift/db/postgres/datetime.c +99 -0
- data/ext/swift/db/postgres/datetime.h +8 -0
- data/ext/swift/db/postgres/extconf.rb +33 -0
- data/ext/swift/db/postgres/main.c +28 -0
- data/ext/swift/db/postgres/result.c +172 -0
- data/ext/swift/db/postgres/result.h +10 -0
- data/ext/swift/db/postgres/statement.c +169 -0
- data/ext/swift/db/postgres/statement.h +9 -0
- data/ext/swift/db/postgres/typecast.c +112 -0
- data/ext/swift/db/postgres/typecast.h +24 -0
- data/lib/swift-db-postgres.rb +1 -0
- data/lib/swift/db/postgres.rb +1 -0
- data/test/helper.rb +8 -0
- data/test/test_adapter.rb +106 -0
- data/test/test_async.rb +34 -0
- data/test/test_encoding.rb +30 -0
- data/test/test_ssl.rb +15 -0
- metadata +87 -0
@@ -0,0 +1,169 @@
|
|
1
|
+
// vim:ts=4:sts=4:sw=4:expandtab
|
2
|
+
|
3
|
+
// (c) Bharanee Rathna 2012
|
4
|
+
|
5
|
+
#include "statement.h"
|
6
|
+
#include "adapter.h"
|
7
|
+
#include "typecast.h"
|
8
|
+
|
9
|
+
/* declaration */
|
10
|
+
|
11
|
+
VALUE cDPS;
|
12
|
+
|
13
|
+
VALUE db_postgres_result_allocate(VALUE);
|
14
|
+
VALUE db_postgres_result_load(VALUE, PGresult*);
|
15
|
+
Adapter* db_postgres_adapter_handle_safe(VALUE);
|
16
|
+
|
17
|
+
typedef struct Statement {
|
18
|
+
char id[64];
|
19
|
+
VALUE adapter;
|
20
|
+
} Statement;
|
21
|
+
|
22
|
+
/* definition */
|
23
|
+
|
24
|
+
Statement* db_postgres_statement_handle(VALUE self) {
|
25
|
+
Statement *s;
|
26
|
+
Data_Get_Struct(self, Statement, s);
|
27
|
+
if (!s)
|
28
|
+
rb_raise(eSwiftRuntimeError, "Invalid postgres statement");
|
29
|
+
return s;
|
30
|
+
}
|
31
|
+
|
32
|
+
Statement* db_postgres_statement_handle_safe(VALUE self) {
|
33
|
+
Statement *s = db_postgres_statement_handle(self);
|
34
|
+
if (!s->adapter)
|
35
|
+
rb_raise(eSwiftRuntimeError, "Invalid postgres statement: no associated adapter");
|
36
|
+
return s;
|
37
|
+
}
|
38
|
+
|
39
|
+
void db_postgres_statement_mark(Statement *s) {
|
40
|
+
if (s && s->adapter)
|
41
|
+
rb_gc_mark_maybe(s->adapter);
|
42
|
+
}
|
43
|
+
|
44
|
+
VALUE db_postgres_statement_deallocate(Statement *s) {
|
45
|
+
if (s)
|
46
|
+
free(s);
|
47
|
+
}
|
48
|
+
|
49
|
+
VALUE db_postgres_statement_allocate(VALUE klass) {
|
50
|
+
Statement *s = (Statement*)malloc(sizeof(Statement));
|
51
|
+
memset(s, 0, sizeof(Statement));
|
52
|
+
return Data_Wrap_Struct(klass, db_postgres_statement_mark, db_postgres_statement_deallocate, s);
|
53
|
+
}
|
54
|
+
|
55
|
+
VALUE db_postgres_statement_initialize(VALUE self, VALUE adapter, VALUE sql) {
|
56
|
+
PGresult *result;
|
57
|
+
PGconn *connection;
|
58
|
+
Statement *s = db_postgres_statement_handle(self);
|
59
|
+
|
60
|
+
snprintf(s->id, 64, "s%s", CSTRING(rb_uuid_string()));
|
61
|
+
s->adapter = adapter;
|
62
|
+
rb_gc_mark(s->adapter);
|
63
|
+
|
64
|
+
connection = db_postgres_adapter_handle_safe(adapter)->connection;
|
65
|
+
result = PQprepare(connection, s->id, CSTRING(db_postgres_normalized_sql(sql)), 0, 0);
|
66
|
+
|
67
|
+
db_postgres_check_result(result);
|
68
|
+
PQclear(result);
|
69
|
+
return self;
|
70
|
+
}
|
71
|
+
|
72
|
+
VALUE db_postgres_statement_release(VALUE self) {
|
73
|
+
char command[128];
|
74
|
+
PGresult *result;
|
75
|
+
PGconn *connection;
|
76
|
+
|
77
|
+
Statement *s = db_postgres_statement_handle_safe(self);
|
78
|
+
connection = db_postgres_adapter_handle_safe(s->adapter)->connection;
|
79
|
+
|
80
|
+
if (connection && PQstatus(connection) == CONNECTION_OK) {
|
81
|
+
snprintf(command, 128, "deallocate %s", s->id);
|
82
|
+
result = PQexec(connection, command);
|
83
|
+
db_postgres_check_result(result);
|
84
|
+
PQclear(result);
|
85
|
+
return Qtrue;
|
86
|
+
}
|
87
|
+
|
88
|
+
return Qfalse;
|
89
|
+
}
|
90
|
+
|
91
|
+
VALUE nogvl_pq_exec_prepared(void *ptr) {
|
92
|
+
Query *q = (Query *)ptr;
|
93
|
+
return (VALUE)PQexecPrepared(q->connection, q->command, q->n_args, (const char * const *)q->data, q->size, q->format, 0);
|
94
|
+
}
|
95
|
+
|
96
|
+
VALUE db_postgres_statement_execute(int argc, VALUE *argv, VALUE self) {
|
97
|
+
PGresult *result;
|
98
|
+
PGconn *connection;
|
99
|
+
char **bind_args_data = 0;
|
100
|
+
int n, *bind_args_size = 0, *bind_args_fmt = 0;
|
101
|
+
VALUE bind, data;
|
102
|
+
|
103
|
+
Statement *s = db_postgres_statement_handle_safe(self);
|
104
|
+
connection = db_postgres_adapter_handle_safe(s->adapter)->connection;
|
105
|
+
|
106
|
+
rb_scan_args(argc, argv, "00*", &bind);
|
107
|
+
|
108
|
+
if (RARRAY_LEN(bind) > 0) {
|
109
|
+
bind_args_size = (int *) malloc(sizeof(int) * RARRAY_LEN(bind));
|
110
|
+
bind_args_fmt = (int *) malloc(sizeof(int) * RARRAY_LEN(bind));
|
111
|
+
bind_args_data = (char **) malloc(sizeof(char *) * RARRAY_LEN(bind));
|
112
|
+
|
113
|
+
for (n = 0; n < RARRAY_LEN(bind); n++) {
|
114
|
+
data = rb_ary_entry(bind, n);
|
115
|
+
if (NIL_P(data)) {
|
116
|
+
bind_args_size[n] = 0;
|
117
|
+
bind_args_data[n] = 0;
|
118
|
+
bind_args_fmt[n] = 0;
|
119
|
+
}
|
120
|
+
else {
|
121
|
+
if (rb_obj_is_kind_of(data, rb_cIO) || rb_obj_is_kind_of(data, cStringIO))
|
122
|
+
bind_args_fmt[n] = 1;
|
123
|
+
else
|
124
|
+
bind_args_fmt[n] = 0;
|
125
|
+
data = typecast_to_string(data);
|
126
|
+
bind_args_size[n] = RSTRING_LEN(data);
|
127
|
+
bind_args_data[n] = RSTRING_PTR(data);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
Query q = {
|
132
|
+
.connection = connection,
|
133
|
+
.command = s->id,
|
134
|
+
.n_args = RARRAY_LEN(bind),
|
135
|
+
.data = bind_args_data,
|
136
|
+
.size = bind_args_size,
|
137
|
+
.format = bind_args_fmt
|
138
|
+
};
|
139
|
+
|
140
|
+
result = (PGresult *)rb_thread_blocking_region(nogvl_pq_exec_prepared, &q, RUBY_UBF_IO, 0);
|
141
|
+
free(bind_args_fmt);
|
142
|
+
free(bind_args_size);
|
143
|
+
free(bind_args_data);
|
144
|
+
}
|
145
|
+
else {
|
146
|
+
Query q = {
|
147
|
+
.connection = connection,
|
148
|
+
.command = s->id,
|
149
|
+
.n_args = 0,
|
150
|
+
.data = 0,
|
151
|
+
.size = 0,
|
152
|
+
.format = 0
|
153
|
+
};
|
154
|
+
result = (PGresult *)rb_thread_blocking_region(nogvl_pq_exec_prepared, &q, RUBY_UBF_IO, 0);
|
155
|
+
}
|
156
|
+
|
157
|
+
db_postgres_check_result(result);
|
158
|
+
return db_postgres_result_load(db_postgres_result_allocate(cDPR), result);
|
159
|
+
}
|
160
|
+
|
161
|
+
void init_swift_db_postgres_statement() {
|
162
|
+
cDPS = rb_define_class_under(cDPA, "Statement", rb_cObject);
|
163
|
+
rb_define_alloc_func(cDPS, db_postgres_statement_allocate);
|
164
|
+
rb_define_method(cDPS, "initialize", db_postgres_statement_initialize, 2);
|
165
|
+
rb_define_method(cDPS, "execute", db_postgres_statement_execute, -1);
|
166
|
+
rb_define_method(cDPS, "release", db_postgres_statement_release, 0);
|
167
|
+
}
|
168
|
+
|
169
|
+
|
@@ -0,0 +1,112 @@
|
|
1
|
+
// vim:ts=4:sts=4:sw=4:expandtab
|
2
|
+
|
3
|
+
// (c) Bharanee Rathna 2012
|
4
|
+
|
5
|
+
#include "common.h"
|
6
|
+
#include "typecast.h"
|
7
|
+
#include "datetime.h"
|
8
|
+
|
9
|
+
#define date_parse(klass, data,len) rb_funcall(datetime_parse(klass, data, len), fto_date, 0)
|
10
|
+
|
11
|
+
ID fnew, fto_date, fstrftime;
|
12
|
+
VALUE cBigDecimal, cStringIO;
|
13
|
+
VALUE dtformat;
|
14
|
+
VALUE cDateTime;
|
15
|
+
|
16
|
+
VALUE typecast_string(const char *data, size_t n) {
|
17
|
+
return rb_enc_str_new(data, n, rb_utf8_encoding());
|
18
|
+
}
|
19
|
+
|
20
|
+
VALUE typecast_detect(const char *data, size_t size, int type) {
|
21
|
+
VALUE value;
|
22
|
+
char *bytea;
|
23
|
+
size_t bytea_len;
|
24
|
+
switch (type) {
|
25
|
+
case SWIFT_TYPE_INT:
|
26
|
+
return rb_cstr2inum(data, 10);
|
27
|
+
case SWIFT_TYPE_FLOAT:
|
28
|
+
return rb_float_new(atof(data));
|
29
|
+
case SWIFT_TYPE_NUMERIC:
|
30
|
+
return rb_funcall(cBigDecimal, fnew, 1, rb_str_new(data, size));
|
31
|
+
case SWIFT_TYPE_BOOLEAN:
|
32
|
+
return (data && (data[0] =='t' || data[0] == '1')) ? Qtrue : Qfalse;
|
33
|
+
case SWIFT_TYPE_BLOB:
|
34
|
+
bytea = PQunescapeBytea(data, &bytea_len);
|
35
|
+
value = rb_str_new(bytea, bytea_len);
|
36
|
+
PQfreemem(bytea);
|
37
|
+
return rb_funcall(cStringIO, fnew, 1, value);
|
38
|
+
case SWIFT_TYPE_TIMESTAMP:
|
39
|
+
return datetime_parse(cSwiftDateTime, data, size);
|
40
|
+
case SWIFT_TYPE_DATE:
|
41
|
+
return date_parse(cSwiftDateTime, data, size);
|
42
|
+
default:
|
43
|
+
return rb_enc_str_new(data, size, rb_utf8_encoding());
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
#define TO_UTF8(value) rb_str_encode(value, rb_str_new2("UTF-8"), 0, Qnil)
|
48
|
+
#define UTF8_STRING(value) strcmp(rb_enc_get(value)->name, "UTF-8") ? TO_UTF8(value) : value
|
49
|
+
|
50
|
+
VALUE typecast_to_string(VALUE value) {
|
51
|
+
switch (TYPE(value)) {
|
52
|
+
case T_STRING:
|
53
|
+
return UTF8_STRING(value);
|
54
|
+
case T_TRUE:
|
55
|
+
return rb_str_new2("1");
|
56
|
+
case T_FALSE:
|
57
|
+
return rb_str_new2("0");
|
58
|
+
default:
|
59
|
+
if (rb_obj_is_kind_of(value, rb_cTime) || rb_obj_is_kind_of(value, cDateTime))
|
60
|
+
return rb_funcall(value, fstrftime, 1, dtformat);
|
61
|
+
else if (rb_obj_is_kind_of(value, rb_cIO) || rb_obj_is_kind_of(value, cStringIO))
|
62
|
+
return rb_funcall(value, rb_intern("read"), 0);
|
63
|
+
else
|
64
|
+
return UTF8_STRING(rb_funcall(value, rb_intern("to_s"), 0));
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
VALUE typecast_description(VALUE list) {
|
69
|
+
int n;
|
70
|
+
VALUE types = rb_ary_new();
|
71
|
+
|
72
|
+
for (n = 0; n < RARRAY_LEN(list); n++) {
|
73
|
+
switch (NUM2INT(rb_ary_entry(list, n))) {
|
74
|
+
case SWIFT_TYPE_INT:
|
75
|
+
rb_ary_push(types, rb_str_new2("integer")); break;
|
76
|
+
case SWIFT_TYPE_NUMERIC:
|
77
|
+
rb_ary_push(types, rb_str_new2("numeric")); break;
|
78
|
+
case SWIFT_TYPE_FLOAT:
|
79
|
+
rb_ary_push(types, rb_str_new2("float")); break;
|
80
|
+
case SWIFT_TYPE_BLOB:
|
81
|
+
rb_ary_push(types, rb_str_new2("blob")); break;
|
82
|
+
case SWIFT_TYPE_DATE:
|
83
|
+
rb_ary_push(types, rb_str_new2("date")); break;
|
84
|
+
case SWIFT_TYPE_TIME:
|
85
|
+
rb_ary_push(types, rb_str_new2("time")); break;
|
86
|
+
case SWIFT_TYPE_TIMESTAMP:
|
87
|
+
rb_ary_push(types, rb_str_new2("timestamp")); break;
|
88
|
+
case SWIFT_TYPE_BOOLEAN:
|
89
|
+
rb_ary_push(types, rb_str_new2("boolean")); break;
|
90
|
+
default:
|
91
|
+
rb_ary_push(types, rb_str_new2("text"));
|
92
|
+
|
93
|
+
}
|
94
|
+
}
|
95
|
+
return types;
|
96
|
+
}
|
97
|
+
|
98
|
+
void init_swift_db_postgres_typecast() {
|
99
|
+
rb_require("bigdecimal");
|
100
|
+
rb_require("stringio");
|
101
|
+
rb_require("date");
|
102
|
+
|
103
|
+
cStringIO = CONST_GET(rb_mKernel, "StringIO");
|
104
|
+
cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal");
|
105
|
+
cDateTime = CONST_GET(rb_mKernel, "DateTime");
|
106
|
+
fnew = rb_intern("new");
|
107
|
+
fto_date = rb_intern("to_date");
|
108
|
+
fstrftime = rb_intern("strftime");
|
109
|
+
dtformat = rb_str_new2("%F %T.%N %z");
|
110
|
+
|
111
|
+
rb_global_variable(&dtformat);
|
112
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
// vim:ts=4:sts=4:sw=4:expandtab
|
2
|
+
|
3
|
+
// (c) Bharanee Rathna 2012
|
4
|
+
|
5
|
+
#pragma once
|
6
|
+
|
7
|
+
#include "common.h"
|
8
|
+
|
9
|
+
#define SWIFT_TYPE_INT 0
|
10
|
+
#define SWIFT_TYPE_FLOAT 1
|
11
|
+
#define SWIFT_TYPE_NUMERIC 2
|
12
|
+
#define SWIFT_TYPE_BOOLEAN 3
|
13
|
+
#define SWIFT_TYPE_DATE 4
|
14
|
+
#define SWIFT_TYPE_TIME 5
|
15
|
+
#define SWIFT_TYPE_TIMESTAMP 6
|
16
|
+
#define SWIFT_TYPE_TEXT 7
|
17
|
+
#define SWIFT_TYPE_BLOB 8
|
18
|
+
#define SWIFT_TYPE_UNKNOWN 9
|
19
|
+
|
20
|
+
DLL_PRIVATE VALUE typecast_to_string(VALUE);
|
21
|
+
DLL_PRIVATE VALUE typecast_string(const char *, size_t);
|
22
|
+
DLL_PRIVATE VALUE typecast_detect(const char *, size_t, int);
|
23
|
+
DLL_PRIVATE VALUE typecast_description(VALUE list);
|
24
|
+
DLL_PRIVATE void init_swift_db_postgres_typecast();
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'swift/db/postgres'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'swift/db/postgres/swift_db_postgres_ext'
|
data/test/helper.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe 'postgres adapter' do
|
4
|
+
it 'should initialize' do
|
5
|
+
assert db
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'should execute sql' do
|
9
|
+
assert db.execute("select * from pg_tables")
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should expect the correct number of bind args' do
|
13
|
+
assert_raises(Swift::ArgumentError) { db.execute("select * from pg_tables where tablename = ?", 1, 2) }
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should return result on #execute' do
|
17
|
+
now = Time.now
|
18
|
+
assert db.execute('drop table if exists users')
|
19
|
+
assert db.execute('create table users (id serial primary key, name text, age integer, created_at timestamp with time zone)')
|
20
|
+
assert db.execute('insert into users(name, age, created_at) values(?, ?, ?)', 'test', nil, now)
|
21
|
+
|
22
|
+
result = db.execute('select * from users')
|
23
|
+
|
24
|
+
assert_equal 1, result.selected_rows
|
25
|
+
assert_equal 0, result.affected_rows
|
26
|
+
assert_equal %w(id name age created_at).map(&:to_sym), result.fields
|
27
|
+
assert_equal %w(integer text integer timestamp), result.types
|
28
|
+
|
29
|
+
row = result.first
|
30
|
+
assert_equal 1, row[:id]
|
31
|
+
assert_equal 'test', row[:name]
|
32
|
+
assert_equal nil, row[:age]
|
33
|
+
assert_equal now.to_f.round(3), row[:created_at].to_time.to_f.round(3) # millisecs resolution on postgres
|
34
|
+
|
35
|
+
result = db.execute('delete from users where id = 0')
|
36
|
+
assert_equal 0, result.selected_rows
|
37
|
+
assert_equal 0, result.affected_rows
|
38
|
+
|
39
|
+
assert_equal 1, db.execute('select count(*) as count from users').first[:count]
|
40
|
+
|
41
|
+
result = db.execute('delete from users')
|
42
|
+
assert_equal 0, result.selected_rows
|
43
|
+
assert_equal 1, result.affected_rows
|
44
|
+
|
45
|
+
# empty result should have field & type information
|
46
|
+
result = db.execute('select * from users')
|
47
|
+
assert_equal %w(id name age created_at).map(&:to_sym), result.fields
|
48
|
+
assert_equal %w(integer text integer timestamp), result.types
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should close handle' do
|
52
|
+
assert !db.closed?
|
53
|
+
assert db.close
|
54
|
+
assert db.closed?
|
55
|
+
|
56
|
+
assert_raises(Swift::ConnectionError) { db.execute("select * from users") }
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should prepare & release statement' do
|
60
|
+
assert db.execute('drop table if exists users')
|
61
|
+
assert db.execute("create table users(id serial primary key, name text)")
|
62
|
+
assert db.execute("insert into users (name) values (?)", "test")
|
63
|
+
assert s = db.prepare("select * from users where id > ?")
|
64
|
+
|
65
|
+
assert_equal 1, s.execute(0).selected_rows
|
66
|
+
assert_equal 0, s.execute(1).selected_rows
|
67
|
+
|
68
|
+
assert s.release
|
69
|
+
assert_raises(Swift::RuntimeError) { s.execute(1) }
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should escape whatever' do
|
73
|
+
assert_equal "foo''bar", db.escape("foo'bar")
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should support #write and #read' do
|
77
|
+
assert db.execute('drop table if exists users')
|
78
|
+
assert db.execute("create table users(id serial primary key, name text)")
|
79
|
+
|
80
|
+
assert_equal 3, db.write('users', %w(name), "foo\nbar\nbaz\n").affected_rows
|
81
|
+
assert_equal 3, db.execute('select count(*) as count from users').first[:count]
|
82
|
+
|
83
|
+
assert db.execute('copy users(name) from stdin')
|
84
|
+
assert_equal 3, db.write("foo\nbar\nbaz\n").affected_rows
|
85
|
+
assert_equal 6, db.execute('select count(*) as count from users').first[:count]
|
86
|
+
|
87
|
+
assert_equal 3, db.write('users', StringIO.new("7\tfoo\n8\tbar\n9\tbaz\n")).affected_rows
|
88
|
+
assert_equal 9, db.execute('select count(*) as count from users').first[:count]
|
89
|
+
|
90
|
+
io = StringIO.new
|
91
|
+
db.execute('copy (select * from users order by id limit 1) to stdout with csv')
|
92
|
+
assert_equal 1, db.read(io).affected_rows
|
93
|
+
|
94
|
+
assert_match %r{1,foo\n}, io.tap(&:rewind).read
|
95
|
+
|
96
|
+
rows = []
|
97
|
+
db.read('users') {|row| rows << row}
|
98
|
+
|
99
|
+
expect = (%w(foo bar baz)*3).zip(1..9).map {|r| r.reverse.join("\t") + "\n"}
|
100
|
+
assert_equal expect, rows
|
101
|
+
|
102
|
+
assert_raises(Swift::RuntimeError) { db.write("foo") }
|
103
|
+
assert_raises(Swift::RuntimeError) { db.write("users", "bar") }
|
104
|
+
assert_raises(Swift::RuntimeError) { db.write("users", %w(name), "bar") }
|
105
|
+
end
|
106
|
+
end
|