swift-db-mysql 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.
- data/CHANGELOG +3 -0
- data/README.md +120 -0
- data/ext/swift/db/mysql/adapter.c +434 -0
- data/ext/swift/db/mysql/adapter.h +15 -0
- data/ext/swift/db/mysql/common.c +76 -0
- data/ext/swift/db/mysql/common.h +27 -0
- data/ext/swift/db/mysql/datetime.c +99 -0
- data/ext/swift/db/mysql/datetime.h +8 -0
- data/ext/swift/db/mysql/extconf.rb +35 -0
- data/ext/swift/db/mysql/main.c +34 -0
- data/ext/swift/db/mysql/result.c +415 -0
- data/ext/swift/db/mysql/result.h +10 -0
- data/ext/swift/db/mysql/statement.c +145 -0
- data/ext/swift/db/mysql/statement.h +14 -0
- data/ext/swift/db/mysql/typecast.c +105 -0
- data/ext/swift/db/mysql/typecast.h +24 -0
- data/lib/swift-db-mysql.rb +1 -0
- data/lib/swift/db/mysql.rb +1 -0
- data/test/helper.rb +8 -0
- data/test/test_adapter.rb +81 -0
- data/test/test_async.rb +36 -0
- data/test/test_encoding.rb +32 -0
- data/test/test_ssl.rb +10 -0
- metadata +87 -0
@@ -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,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,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
|
data/test/test_async.rb
ADDED
@@ -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
|