swift-db-mysql 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_mysql_result();
@@ -0,0 +1,145 @@
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 cDMS;
12
+
13
+ VALUE db_mysql_result_allocate(VALUE);
14
+ VALUE db_mysql_result_from_statement(VALUE, VALUE);
15
+ Adapter* db_mysql_adapter_handle_safe(VALUE);
16
+
17
+ /* definition */
18
+
19
+ Statement* db_mysql_statement_handle(VALUE self) {
20
+ Statement *s;
21
+ Data_Get_Struct(self, Statement, s);
22
+ if (!s)
23
+ rb_raise(eSwiftRuntimeError, "Invalid mysql statement");
24
+ return s;
25
+ }
26
+
27
+ Statement* db_mysql_statement_handle_safe(VALUE self) {
28
+ Statement *s = db_mysql_statement_handle(self);
29
+ if (!s->statement)
30
+ rb_raise(eSwiftRuntimeError, "statement already closed or released");
31
+ return s;
32
+ }
33
+
34
+ void db_mysql_statement_mark(Statement *s) {
35
+ if (s && s->adapter)
36
+ rb_gc_mark_maybe(s->adapter);
37
+ }
38
+
39
+ VALUE db_mysql_statement_deallocate(Statement *s) {
40
+ if (s) {
41
+ if (s->statement)
42
+ mysql_stmt_close(s->statement);
43
+ free(s);
44
+ }
45
+ }
46
+
47
+ VALUE db_mysql_statement_allocate(VALUE klass) {
48
+ Statement *s = (Statement*)malloc(sizeof(Statement));
49
+ memset(s, 0, sizeof(Statement));
50
+ return Data_Wrap_Struct(klass, db_mysql_statement_mark, db_mysql_statement_deallocate, s);
51
+ }
52
+
53
+ VALUE db_mysql_statement_initialize(VALUE self, VALUE adapter, VALUE sql) {
54
+ MYSQL *connection;
55
+ Statement *s = db_mysql_statement_handle(self);
56
+
57
+ s->adapter = adapter;
58
+ rb_gc_mark(s->adapter);
59
+
60
+ connection = db_mysql_adapter_handle_safe(adapter)->connection;
61
+ s->statement = mysql_stmt_init(connection);
62
+ sql = TO_S(sql);
63
+
64
+ if (mysql_stmt_prepare(s->statement, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0)
65
+ rb_raise(eSwiftRuntimeError, "%s", mysql_stmt_error(s->statement));
66
+
67
+ return self;
68
+ }
69
+
70
+ VALUE nogvl_mysql_statement_execute(void *ptr) {
71
+ return (VALUE)mysql_stmt_execute((MYSQL_STMT *)ptr);
72
+ }
73
+
74
+ VALUE db_mysql_statement_execute(int argc, VALUE *argv, VALUE self) {
75
+ int n, error;
76
+ VALUE bind, data, result;
77
+ MYSQL_BIND *mysql_bind;
78
+ char MYSQL_BOOL_TRUE = 1, MYSQL_BOOL_FALSE = 0;
79
+
80
+ Statement *s = db_mysql_statement_handle_safe(self);
81
+
82
+ rb_scan_args(argc, argv, "00*", &bind);
83
+
84
+ mysql_stmt_free_result(s->statement);
85
+ if (RARRAY_LEN(bind) > 0) {
86
+ n = mysql_stmt_param_count(s->statement);
87
+ if (RARRAY_LEN(bind) != n)
88
+ rb_raise(eSwiftArgumentError, "expected %d bind arguments got %d instead", n, RARRAY_LEN(bind));
89
+ mysql_bind = (MYSQL_BIND *)malloc(sizeof(MYSQL_BIND) * RARRAY_LEN(bind));
90
+ memset(mysql_bind, 0, sizeof(MYSQL_BIND) * RARRAY_LEN(bind));
91
+
92
+ for (n = 0; n < RARRAY_LEN(bind); n++) {
93
+ data = rb_ary_entry(bind, n);
94
+ if (NIL_P(data)) {
95
+ mysql_bind[n].is_null = &MYSQL_BOOL_TRUE;
96
+ mysql_bind[n].buffer_type = MYSQL_TYPE_NULL;
97
+ }
98
+ else {
99
+ data = typecast_to_string(data);
100
+ mysql_bind[n].is_null = &MYSQL_BOOL_FALSE;
101
+ mysql_bind[n].buffer_type = MYSQL_TYPE_STRING;
102
+ mysql_bind[n].buffer = RSTRING_PTR(data);
103
+ mysql_bind[n].buffer_length = RSTRING_LEN(data);
104
+ }
105
+ }
106
+
107
+ if (mysql_stmt_bind_param(s->statement, mysql_bind) != 0) {
108
+ free(mysql_bind);
109
+ rb_raise(eSwiftRuntimeError, mysql_stmt_error(s->statement));
110
+ }
111
+
112
+ error = (int)rb_thread_blocking_region(nogvl_mysql_statement_execute, s->statement, RUBY_UBF_IO, 0);
113
+ free(mysql_bind);
114
+ }
115
+ else {
116
+ if ((n = mysql_stmt_param_count(s->statement)) > 0)
117
+ rb_raise(eSwiftArgumentError, "expected %d bind arguments got 0 instead", n);
118
+ error = (int)rb_thread_blocking_region(nogvl_mysql_statement_execute, s->statement, RUBY_UBF_IO, 0);
119
+ }
120
+
121
+ if (error)
122
+ rb_raise(eSwiftRuntimeError, mysql_stmt_error(s->statement));
123
+
124
+ result = db_mysql_result_allocate(cDMR);
125
+ return db_mysql_result_from_statement(result, self);
126
+ }
127
+
128
+ VALUE db_mysql_statement_release(VALUE self) {
129
+ Statement *s = db_mysql_statement_handle(self);
130
+ if (s->statement) {
131
+ mysql_stmt_free_result(s->statement);
132
+ mysql_stmt_close(s->statement);
133
+ s->statement = 0;
134
+ return Qtrue;
135
+ }
136
+ return Qfalse;
137
+ }
138
+
139
+ void init_swift_db_mysql_statement() {
140
+ cDMS = rb_define_class_under(cDMA, "Statement", rb_cObject);
141
+ rb_define_alloc_func(cDMS, db_mysql_statement_allocate);
142
+ rb_define_method(cDMS, "initialize", db_mysql_statement_initialize, 2);
143
+ rb_define_method(cDMS, "execute", db_mysql_statement_execute, -1);
144
+ rb_define_method(cDMS, "release", db_mysql_statement_release, 0);
145
+ }
@@ -0,0 +1,14 @@
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
+ typedef struct Statement {
10
+ MYSQL_STMT *statement;
11
+ VALUE adapter;
12
+ } Statement;
13
+
14
+ void init_swift_db_mysql_statement();
@@ -0,0 +1,105 @@
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
+ switch (type) {
22
+ case SWIFT_TYPE_INT:
23
+ return rb_cstr2inum(data, 10);
24
+ case SWIFT_TYPE_FLOAT:
25
+ return rb_float_new(atof(data));
26
+ case SWIFT_TYPE_NUMERIC:
27
+ return rb_funcall(cBigDecimal, fnew, 1, rb_str_new(data, size));
28
+ case SWIFT_TYPE_BOOLEAN:
29
+ return (data && (data[0] =='t' || data[0] == '1')) ? Qtrue : Qfalse;
30
+ case SWIFT_TYPE_BLOB:
31
+ return rb_funcall(cStringIO, fnew, 1, rb_str_new(data, size));
32
+ case SWIFT_TYPE_TIMESTAMP:
33
+ return datetime_parse(cSwiftDateTime, data, size);
34
+ case SWIFT_TYPE_DATE:
35
+ return date_parse(cSwiftDateTime, data, size);
36
+ default:
37
+ return rb_enc_str_new(data, size, rb_utf8_encoding());
38
+ }
39
+ }
40
+
41
+ #define TO_UTF8(value) rb_str_encode(value, rb_str_new2("UTF-8"), 0, Qnil)
42
+ #define UTF8_STRING(value) strcmp(rb_enc_get(value)->name, "UTF-8") ? TO_UTF8(value) : value
43
+
44
+ VALUE typecast_to_string(VALUE value) {
45
+ switch (TYPE(value)) {
46
+ case T_STRING:
47
+ return UTF8_STRING(value);
48
+ case T_TRUE:
49
+ return rb_str_new2("1");
50
+ case T_FALSE:
51
+ return rb_str_new2("0");
52
+ default:
53
+ if (rb_obj_is_kind_of(value, rb_cTime) || rb_obj_is_kind_of(value, cDateTime))
54
+ return rb_funcall(value, fstrftime, 1, dtformat);
55
+ else if (rb_obj_is_kind_of(value, rb_cIO) || rb_obj_is_kind_of(value, cStringIO))
56
+ return rb_funcall(value, rb_intern("read"), 0);
57
+ else
58
+ return UTF8_STRING(rb_funcall(value, rb_intern("to_s"), 0));
59
+ }
60
+ }
61
+
62
+ VALUE typecast_description(VALUE list) {
63
+ int n;
64
+ VALUE types = rb_ary_new();
65
+
66
+ for (n = 0; n < RARRAY_LEN(list); n++) {
67
+ switch (NUM2INT(rb_ary_entry(list, n))) {
68
+ case SWIFT_TYPE_INT:
69
+ rb_ary_push(types, rb_str_new2("integer")); break;
70
+ case SWIFT_TYPE_NUMERIC:
71
+ rb_ary_push(types, rb_str_new2("numeric")); break;
72
+ case SWIFT_TYPE_FLOAT:
73
+ rb_ary_push(types, rb_str_new2("float")); break;
74
+ case SWIFT_TYPE_BLOB:
75
+ rb_ary_push(types, rb_str_new2("blob")); break;
76
+ case SWIFT_TYPE_DATE:
77
+ rb_ary_push(types, rb_str_new2("date")); break;
78
+ case SWIFT_TYPE_TIME:
79
+ rb_ary_push(types, rb_str_new2("time")); break;
80
+ case SWIFT_TYPE_TIMESTAMP:
81
+ rb_ary_push(types, rb_str_new2("timestamp")); break;
82
+ case SWIFT_TYPE_BOOLEAN:
83
+ rb_ary_push(types, rb_str_new2("boolean")); break;
84
+ default:
85
+ rb_ary_push(types, rb_str_new2("text"));
86
+ }
87
+ }
88
+ return types;
89
+ }
90
+
91
+ void init_swift_db_mysql_typecast() {
92
+ rb_require("bigdecimal");
93
+ rb_require("stringio");
94
+ rb_require("date");
95
+
96
+ cStringIO = CONST_GET(rb_mKernel, "StringIO");
97
+ cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal");
98
+ cDateTime = CONST_GET(rb_mKernel, "DateTime");
99
+ fnew = rb_intern("new");
100
+ fto_date = rb_intern("to_date");
101
+ fstrftime = rb_intern("strftime");
102
+ dtformat = rb_str_new2("%F %T.%N %z");
103
+
104
+ rb_global_variable(&dtformat);
105
+ }
@@ -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_mysql_typecast();
@@ -0,0 +1 @@
1
+ require 'swift/db/mysql'
@@ -0,0 +1 @@
1
+ require 'swift/db/mysql/swift_db_mysql_ext'
data/test/helper.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'minitest/autorun'
2
+ require 'swift/db/mysql'
3
+
4
+ class MiniTest::Spec
5
+ def db
6
+ @db ||= Swift::DB::Mysql.new(db: 'swift_test')
7
+ end
8
+ end
@@ -0,0 +1,81 @@
1
+ require 'helper'
2
+
3
+ describe 'mysql 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 information_schema.tables limit 1")
10
+ end
11
+
12
+ it 'should expect the correct number of bind args' do
13
+ assert_raises(Swift::ArgumentError) { db.execute("select * from information_schema.tables where table_name = ?", 1, 2) }
14
+ end
15
+
16
+ it 'should return result on #execute' do
17
+ now = Time.now.utc
18
+ assert db.execute('drop table if exists users')
19
+ assert db.execute('create table users (id int auto_increment primary key, name text, age integer, created_at datetime)')
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_i, row[:created_at].to_time.to_i
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
+ end
45
+
46
+ it 'should close handle' do
47
+ assert !db.closed?
48
+ assert db.close
49
+ assert db.closed?
50
+
51
+ assert_raises(Swift::ConnectionError) { db.execute("select * from users") }
52
+ end
53
+
54
+ it 'should prepare & release statement' do
55
+ assert db.execute('drop table if exists users')
56
+ assert db.execute("create table users(id int auto_increment primary key, name text, created_at datetime)")
57
+ assert db.execute("insert into users (name, created_at) values (?, ?)", "test", Time.now)
58
+ assert s = db.prepare("select * from users where id > ? and created_at <= ?")
59
+
60
+ assert_equal 0, s.execute(1, Time.now).selected_rows
61
+ assert_equal 1, s.execute(0, Time.now).selected_rows
62
+
63
+ assert s.release
64
+ assert_raises(Swift::RuntimeError) { s.execute(1, Time.now) }
65
+ end
66
+
67
+ it 'should escape whatever' do
68
+ assert_equal "foo\\'bar", db.escape("foo'bar")
69
+ end
70
+
71
+ it 'should support #write' do
72
+ assert db.execute('drop table if exists users')
73
+ assert db.execute("create table users(id int auto_increment primary key, name text)")
74
+
75
+ assert_equal 3, db.write('users', %w(name), "foo\nbar\nbaz\n").affected_rows
76
+ assert_equal 3, db.execute('select count(*) as count from users').first[:count]
77
+
78
+ assert_equal 3, db.write('users', StringIO.new("7\tfoo\n8\tbar\n9\tbaz\n")).affected_rows
79
+ assert_equal 6, db.execute('select count(*) as count from users').first[:count]
80
+ end
81
+ end
@@ -0,0 +1,36 @@
1
+ require 'helper'
2
+
3
+ describe 'async operations' do
4
+ it 'can query async and call block with result when ready' do
5
+ rows = []
6
+ pool = 3.times.map {Swift::DB::Mysql.new(db: 'swift_test')}
7
+
8
+ 3.times do |n|
9
+ Thread.new do
10
+ pool[n].query("select sleep(#{(3 - n) / 10.0}), #{n + 1} as query_id") {|row| rows << row[:query_id]}
11
+ end
12
+ end
13
+
14
+ Thread.list.reject {|thread| Thread.current == thread}.each(&:join)
15
+ assert_equal [3, 2, 1], rows
16
+ end
17
+
18
+ it 'returns and allows IO poll on connection file descriptor' do
19
+
20
+ rows = []
21
+ pool = 3.times.map {Swift::DB::Mysql.new(db: 'swift_test')}
22
+
23
+ 3.times do |n|
24
+ Thread.new do
25
+ pool[n].query("select sleep(#{(3 - n) / 10.0}), #{n + 1} as query_id")
26
+ IO.select([IO.for_fd(pool[n].fileno)], [], [])
27
+ rows << pool[n].result.first[:query_id]
28
+ end
29
+ end
30
+
31
+ Thread.list.reject {|thread| Thread.current == thread}.each(&:join)
32
+ assert_equal [3, 2, 1], rows
33
+ end
34
+ end
35
+
36
+
@@ -0,0 +1,32 @@
1
+ require 'helper'
2
+
3
+ describe 'utf-8 encoding' do
4
+ before do
5
+ assert db.execute('drop table if exists users')
6
+ assert db.execute('create table users (name text)')
7
+ assert db.execute('alter table users default character set utf8')
8
+ assert db.execute('alter table users change name name text charset utf8')
9
+
10
+ @text = ["King of \u2665s", "\xA1\xB8".force_encoding('euc-jp')]
11
+ end
12
+
13
+ it 'should store and retrieve utf8 characters with Statement#execute' do
14
+ @text.each do |name|
15
+ db.prepare('insert into users (name) values(?)').execute(name)
16
+ value = db.prepare('select * from users limit 1').execute.first[:name]
17
+ assert_equal Encoding::UTF_8, value.encoding
18
+ assert_equal name.encode('utf-8'), value
19
+ db.execute('delete from users')
20
+ end
21
+ end
22
+
23
+ it 'should store and retrieve utf8 characters Adapter#execute' do
24
+ @text.each do |name|
25
+ db.execute('insert into users (name) values(?)', name)
26
+ value = db.execute('select * from users limit 1').first[:name]
27
+ assert_equal Encoding::UTF_8, value.encoding
28
+ assert_equal name.encode('utf-8'), value
29
+ db.execute('delete from users')
30
+ end
31
+ end
32
+ end