swift-db-mysql 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 +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
|