swift-db-postgres 0.1.0

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.
@@ -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