swift-db-postgres 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
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
+ #include "typecast.h"
9
+
10
+ void init_swift_db_postgres_result();
@@ -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,9 @@
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
+ void init_swift_db_postgres_statement();
@@ -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,8 @@
1
+ require 'minitest/autorun'
2
+ require 'swift/db/postgres'
3
+
4
+ class MiniTest::Spec
5
+ def db
6
+ @db ||= Swift::DB::Postgres.new(db: 'swift_test')
7
+ end
8
+ end
@@ -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