rdo-mysql 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/.travis.yml +11 -0
- data/Gemfile +8 -0
- data/LICENSE +22 -0
- data/README.md +60 -0
- data/Rakefile +14 -0
- data/ext/rdo_mysql/driver.c +179 -0
- data/ext/rdo_mysql/driver.h +12 -0
- data/ext/rdo_mysql/extconf.rb +34 -0
- data/ext/rdo_mysql/macros.h +212 -0
- data/ext/rdo_mysql/rdo_mysql.c +15 -0
- data/ext/rdo_mysql/tuples.c +130 -0
- data/ext/rdo_mysql/tuples.h +15 -0
- data/lib/rdo-mysql.rb +1 -0
- data/lib/rdo/mysql.rb +15 -0
- data/lib/rdo/mysql/driver.rb +57 -0
- data/lib/rdo/mysql/version.rb +12 -0
- data/rdo-mysql.gemspec +23 -0
- data/spec/mysql/driver_spec.rb +155 -0
- data/spec/mysql/type_cast_spec.rb +212 -0
- data/spec/spec_helper.rb +10 -0
- metadata +119 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
language: ruby
|
2
|
+
services: mysql
|
3
|
+
before_script:
|
4
|
+
- mysql -u root -e "CREATE DATABASE rdo;"
|
5
|
+
- mysql -u root -e "GRANT ALL ON rdo.* TO rdo@localhost IDENTIFIED BY 'rdo';"
|
6
|
+
script: "bundle exec rake spec"
|
7
|
+
rvm:
|
8
|
+
- 1.9.2
|
9
|
+
- 1.9.3
|
10
|
+
notifications:
|
11
|
+
email: chris@w3style.co.uk
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 d11wtq
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# RDO MySQL Driver
|
2
|
+
|
3
|
+
This is the MySQL driver for [RDO—Ruby Data Objects](https://github.com/d11wtq/rdo).
|
4
|
+
|
5
|
+
[![Build Status](https://secure.travis-ci.org/d11wtq/rdo-mysql.png)](http://travis-ci.org/d11wtq/rdo-mysql)
|
6
|
+
|
7
|
+
Refer to the RDO project [README](https://github.com/d11wtq/rdo) for full usage information.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Via rubygems:
|
12
|
+
|
13
|
+
$ gem install rdo-mysql
|
14
|
+
|
15
|
+
Or add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem "rdo-mysql"
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
``` ruby
|
26
|
+
require "rdo-mysql"
|
27
|
+
|
28
|
+
conn = RDO.connect("mysql://user:pass@localhost/db_name?encoding=utf-8")
|
29
|
+
```
|
30
|
+
|
31
|
+
## Prepared statements
|
32
|
+
|
33
|
+
MySQL, in theory supports prepared statements. But really you would not want
|
34
|
+
to use them. There are still a number of known limitations with MySQL prepared
|
35
|
+
statements, as outlined here:
|
36
|
+
|
37
|
+
- http://dev.mysql.com/doc/refman/5.0/en/c-api-prepared-statement-problems.html
|
38
|
+
- http://dev.mysql.com/doc/refman/5.1/en/c-api-prepared-statement-problems.html
|
39
|
+
- http://dev.mysql.com/doc/refman/5.5/en/c-api-prepared-statement-problems.html
|
40
|
+
|
41
|
+
rdo-mysql uses RDO's emulated prepared statement support instead. If people
|
42
|
+
shout loud enough that they'd prefer to use MySQL's problematic prepared
|
43
|
+
statements anyway, I'll implement them.
|
44
|
+
|
45
|
+
## Contributing
|
46
|
+
|
47
|
+
If you find any bugs, please send a pull request if you think you can
|
48
|
+
fix it, or file in an issue in the issue tracker.
|
49
|
+
|
50
|
+
When sending pull requests, please use topic branches—don't send a pull
|
51
|
+
request from the master branch of your fork, as that may change
|
52
|
+
unintentionally.
|
53
|
+
|
54
|
+
Contributors will be credited in this README.
|
55
|
+
|
56
|
+
## Copyright & Licensing
|
57
|
+
|
58
|
+
Written by Chris Corbyn.
|
59
|
+
|
60
|
+
Licensed under the MIT license. See the LICENSE file for full details.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
require "rake/extensiontask"
|
4
|
+
|
5
|
+
Rake::ExtensionTask.new('rdo_mysql') do |ext|
|
6
|
+
ext.lib_dir = File.join('lib', 'rdo_mysql')
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "Run the full RSpec suite"
|
10
|
+
RSpec::Core::RakeTask.new('spec') do |t|
|
11
|
+
t.pattern = 'spec/'
|
12
|
+
end
|
13
|
+
|
14
|
+
Rake::Task['spec'].prerequisites << :compile
|
@@ -0,0 +1,179 @@
|
|
1
|
+
/*
|
2
|
+
* RDO MySQL Driver.
|
3
|
+
* Copyright © 2012 Chris Corbyn.
|
4
|
+
*
|
5
|
+
* See LICENSE file for details.
|
6
|
+
*/
|
7
|
+
|
8
|
+
#include "driver.h"
|
9
|
+
#include <stdio.h>
|
10
|
+
#include "tuples.h"
|
11
|
+
#include "macros.h"
|
12
|
+
#include <ruby/encoding.h>
|
13
|
+
|
14
|
+
/** Struct wrapped by RDO::MySQL::Driver */
|
15
|
+
typedef struct {
|
16
|
+
MYSQL * conn;
|
17
|
+
int is_open;
|
18
|
+
int encoding;
|
19
|
+
} RDOMySQLDriver;
|
20
|
+
|
21
|
+
/** Free memory associated with connection during GC */
|
22
|
+
static void rdo_mysql_driver_free(RDOMySQLDriver * driver) {
|
23
|
+
mysql_close(driver->conn);
|
24
|
+
free(driver);
|
25
|
+
}
|
26
|
+
|
27
|
+
/** Extract result information from the given result */
|
28
|
+
static VALUE rdo_mysql_result_info_new(MYSQL * conn, MYSQL_RES * res) {
|
29
|
+
unsigned long long count = (res == NULL) ? 0 : mysql_num_rows(res);
|
30
|
+
|
31
|
+
VALUE info = rb_hash_new();
|
32
|
+
rb_hash_aset(info, ID2SYM(rb_intern("count")), LL2NUM(count));
|
33
|
+
rb_hash_aset(info, ID2SYM(rb_intern("insert_id")),
|
34
|
+
LL2NUM(mysql_insert_id(conn)));
|
35
|
+
rb_hash_aset(info, ID2SYM(rb_intern("affected_rows")),
|
36
|
+
LL2NUM(mysql_affected_rows(conn)));
|
37
|
+
|
38
|
+
return info;
|
39
|
+
}
|
40
|
+
|
41
|
+
/** Allocate memory, wrapping RDOMySQLDriver */
|
42
|
+
static VALUE rdo_mysql_driver_allocate(VALUE klass) {
|
43
|
+
RDOMySQLDriver * driver = malloc(sizeof(RDOMySQLDriver));
|
44
|
+
driver->conn = NULL;
|
45
|
+
driver->is_open = 0;
|
46
|
+
driver->encoding = -1;
|
47
|
+
|
48
|
+
return Data_Wrap_Struct(klass, 0, rdo_mysql_driver_free, driver);
|
49
|
+
}
|
50
|
+
|
51
|
+
/** Open a connection to MySQL */
|
52
|
+
static VALUE rdo_mysql_driver_open(VALUE self) {
|
53
|
+
RDOMySQLDriver * driver;
|
54
|
+
Data_Get_Struct(self, RDOMySQLDriver, driver);
|
55
|
+
|
56
|
+
if (driver->is_open) {
|
57
|
+
return Qtrue;
|
58
|
+
}
|
59
|
+
|
60
|
+
if (!(driver->conn = mysql_init(NULL))) {
|
61
|
+
RDO_ERROR("Failed to connect to MySQL. Could not allocate memory.");
|
62
|
+
}
|
63
|
+
|
64
|
+
VALUE host = rb_funcall(self, rb_intern("host"), 0);
|
65
|
+
VALUE port = rb_funcall(self, rb_intern("port"), 0);
|
66
|
+
VALUE user = rb_funcall(self, rb_intern("user"), 0);
|
67
|
+
VALUE pass = rb_funcall(self, rb_intern("password"), 0);
|
68
|
+
VALUE db = rb_funcall(self, rb_intern("database"), 0);
|
69
|
+
|
70
|
+
Check_Type(host, T_STRING);
|
71
|
+
Check_Type(port, T_FIXNUM);
|
72
|
+
Check_Type(user, T_STRING);
|
73
|
+
Check_Type(pass, T_STRING);
|
74
|
+
Check_Type(db, T_STRING);
|
75
|
+
|
76
|
+
if (!mysql_real_connect(driver->conn,
|
77
|
+
RSTRING_PTR(host),
|
78
|
+
RSTRING_PTR(user),
|
79
|
+
RSTRING_PTR(pass),
|
80
|
+
RSTRING_PTR(db),
|
81
|
+
NUM2INT(port),
|
82
|
+
NULL, // UNIX socket
|
83
|
+
0)) {
|
84
|
+
RDO_ERROR("MySQL connection failed: %s", mysql_error(driver->conn));
|
85
|
+
} else {
|
86
|
+
driver->is_open = 1;
|
87
|
+
driver->encoding = rb_enc_find_index(
|
88
|
+
RSTRING_PTR(rb_funcall(self, rb_intern("encoding"), 0)));
|
89
|
+
rb_funcall(self, rb_intern("after_open"), 0);
|
90
|
+
}
|
91
|
+
|
92
|
+
return Qtrue;
|
93
|
+
}
|
94
|
+
|
95
|
+
/** Return true if the connection is open */
|
96
|
+
static VALUE rdo_mysql_driver_open_p(VALUE self) {
|
97
|
+
RDOMySQLDriver * driver;
|
98
|
+
Data_Get_Struct(self, RDOMySQLDriver, driver);
|
99
|
+
|
100
|
+
return driver->is_open ? Qtrue : Qfalse;
|
101
|
+
}
|
102
|
+
|
103
|
+
/** Close the connection to MySQL */
|
104
|
+
static VALUE rdo_mysql_driver_close(VALUE self) {
|
105
|
+
RDOMySQLDriver * driver;
|
106
|
+
Data_Get_Struct(self, RDOMySQLDriver, driver);
|
107
|
+
|
108
|
+
mysql_close(driver->conn);
|
109
|
+
driver->conn = NULL;
|
110
|
+
driver->is_open = 0;
|
111
|
+
driver->encoding = -1;
|
112
|
+
|
113
|
+
return Qtrue;
|
114
|
+
}
|
115
|
+
|
116
|
+
/** Quote values for safe insertion into a statement */
|
117
|
+
static VALUE rdo_mysql_driver_quote(VALUE self, VALUE obj) {
|
118
|
+
if (TYPE(obj) != T_STRING) {
|
119
|
+
obj = RDO_OBJ_TO_S(obj);
|
120
|
+
}
|
121
|
+
|
122
|
+
RDOMySQLDriver * driver;
|
123
|
+
Data_Get_Struct(self, RDOMySQLDriver, driver);
|
124
|
+
|
125
|
+
if (!(driver->is_open)) {
|
126
|
+
RDO_ERROR("Unable to quote value: connection is closed");
|
127
|
+
}
|
128
|
+
|
129
|
+
char quoted[RSTRING_LEN(obj) * 2 + 1];
|
130
|
+
|
131
|
+
mysql_real_escape_string(driver->conn,
|
132
|
+
quoted, RSTRING_PTR(obj), RSTRING_LEN(obj));
|
133
|
+
|
134
|
+
return rb_str_new2(quoted);
|
135
|
+
}
|
136
|
+
|
137
|
+
/** Execute a statement with possible bind parameters */
|
138
|
+
static VALUE rdo_mysql_driver_execute(int argc, VALUE * args, VALUE self) {
|
139
|
+
if (argc < 1) {
|
140
|
+
rb_raise(rb_eArgError, "wrong number of arguments (0 for 1)");
|
141
|
+
}
|
142
|
+
|
143
|
+
RDOMySQLDriver * driver;
|
144
|
+
Data_Get_Struct(self, RDOMySQLDriver, driver);
|
145
|
+
|
146
|
+
if (!(driver->is_open)) {
|
147
|
+
RDO_ERROR("Cannot execute query: connection is not open");
|
148
|
+
}
|
149
|
+
|
150
|
+
VALUE stmt = RDO_INTERPOLATE(self, args, argc);
|
151
|
+
|
152
|
+
if (mysql_real_query(driver->conn,
|
153
|
+
RSTRING_PTR(stmt),
|
154
|
+
RSTRING_LEN(stmt)) != 0) {
|
155
|
+
RDO_ERROR("Failed to execute query: %s", mysql_error(driver->conn));
|
156
|
+
}
|
157
|
+
|
158
|
+
MYSQL_RES * res = mysql_store_result(driver->conn);
|
159
|
+
|
160
|
+
return RDO_RESULT(rdo_mysql_tuple_list_new(res, driver->encoding),
|
161
|
+
rdo_mysql_result_info_new(driver->conn, res));
|
162
|
+
}
|
163
|
+
|
164
|
+
/** Initializer driver during extension initialization */
|
165
|
+
void Init_rdo_mysql_driver(void) {
|
166
|
+
rb_require("rdo/mysql/driver");
|
167
|
+
|
168
|
+
VALUE cMySQL = rb_path2class("RDO::MySQL::Driver");
|
169
|
+
|
170
|
+
rb_define_alloc_func(cMySQL, rdo_mysql_driver_allocate);
|
171
|
+
|
172
|
+
rb_define_method(cMySQL, "open", rdo_mysql_driver_open, 0);
|
173
|
+
rb_define_method(cMySQL, "open?", rdo_mysql_driver_open_p, 0);
|
174
|
+
rb_define_method(cMySQL, "close", rdo_mysql_driver_close, 0);
|
175
|
+
rb_define_method(cMySQL, "quote", rdo_mysql_driver_quote, 1);
|
176
|
+
rb_define_method(cMySQL, "execute", rdo_mysql_driver_execute, -1);
|
177
|
+
|
178
|
+
Init_rdo_mysql_tuples();
|
179
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "mkmf"
|
2
|
+
|
3
|
+
if ENV["CC"]
|
4
|
+
RbConfig::MAKEFILE_CONFIG["CC"] = ENV["CC"]
|
5
|
+
end
|
6
|
+
|
7
|
+
def config_value(type, flag)
|
8
|
+
IO.popen("mysql_config --#{type}").
|
9
|
+
readline.chomp.
|
10
|
+
split(/\s+/).select{|s| s =~ /^#{flag}/}.
|
11
|
+
map{|s| s.sub(/^#{flag}/, "")}.uniq
|
12
|
+
rescue
|
13
|
+
Array[]
|
14
|
+
end
|
15
|
+
|
16
|
+
def have_build_env
|
17
|
+
[
|
18
|
+
have_header("mysql.h"),
|
19
|
+
config_value("libs", "-l").all?{|lib| have_library(lib)}
|
20
|
+
].all?
|
21
|
+
end
|
22
|
+
|
23
|
+
dir_config(
|
24
|
+
"mysqlclient",
|
25
|
+
config_value("include", "-I"),
|
26
|
+
config_value("libs", "-L")
|
27
|
+
)
|
28
|
+
|
29
|
+
unless have_build_env
|
30
|
+
puts "Unable to find mysqlclient libraries and headers. Not building."
|
31
|
+
exit(1)
|
32
|
+
end
|
33
|
+
|
34
|
+
create_makefile("rdo_mysql/rdo_mysql")
|
@@ -0,0 +1,212 @@
|
|
1
|
+
/*
|
2
|
+
* RDO—Ruby Data Objects.
|
3
|
+
* Copyright © 2012 Chris Corbyn.
|
4
|
+
*
|
5
|
+
* See LICENSE file for details.
|
6
|
+
*/
|
7
|
+
|
8
|
+
/** -------------------------------------------------------------------------
|
9
|
+
* These macros are for use by RDO driver developers.
|
10
|
+
*
|
11
|
+
* They simplify the logic needed when converting types provided by the RDBMS
|
12
|
+
* into their equivalent Ruby types.
|
13
|
+
*
|
14
|
+
* All of these macros take a C string and return a Ruby VALUE.
|
15
|
+
*
|
16
|
+
* The actual logic for many of the conversions is handled in RDO::Util, which
|
17
|
+
* is written in Ruby.
|
18
|
+
* --------------------------------------------------------------------------
|
19
|
+
*/
|
20
|
+
|
21
|
+
#include <ruby.h>
|
22
|
+
#include <ruby/encoding.h>
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Convenience to call #to_s on any Ruby object.
|
26
|
+
*/
|
27
|
+
#define RDO_OBJ_TO_S(obj) (rb_funcall(obj, rb_intern("to_s"), 0))
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Raise an RDO::Exception with the given msg format and any number of parameters.
|
31
|
+
*
|
32
|
+
* @param (char *) msg
|
33
|
+
* a format string passed to rb_raise()
|
34
|
+
*
|
35
|
+
* @param (void *) ...
|
36
|
+
* args used to interpolate the error message
|
37
|
+
*/
|
38
|
+
#define RDO_ERROR(...) (rb_raise(rb_path2class("RDO::Exception"), __VA_ARGS__))
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Calls Driver#interpolate for args.
|
42
|
+
*
|
43
|
+
* @param VALUE (RDO::Driver)
|
44
|
+
* the Driver to use #quote from
|
45
|
+
*
|
46
|
+
* @param (VALUE *) args
|
47
|
+
* a C array where the first element is the SQL and the remainder are the bind params
|
48
|
+
*
|
49
|
+
* @param int argc
|
50
|
+
* the number of elements in args
|
51
|
+
*
|
52
|
+
* @return VALUE (String)
|
53
|
+
* a Ruby String with the SQL including the interpolated params
|
54
|
+
*/
|
55
|
+
#define RDO_INTERPOLATE(driver, args, argc) \
|
56
|
+
(rb_funcall(driver, rb_intern("interpolate"), 2, \
|
57
|
+
args[0], rb_ary_new4(argc - 1, &args[1])))
|
58
|
+
|
59
|
+
/**
|
60
|
+
* Factory to return a new RDO::Result for an Enumerable object of tuples.
|
61
|
+
*
|
62
|
+
* @param VALUE (Enumerable) tuples
|
63
|
+
* an object that knows how to iterate all tuples
|
64
|
+
*
|
65
|
+
* @param VALUE (Hash)
|
66
|
+
* an optional hash of query info.
|
67
|
+
*
|
68
|
+
* @return VALUE (RDO::Result)
|
69
|
+
* a new Result object
|
70
|
+
*/
|
71
|
+
#define RDO_RESULT(tuples, info) \
|
72
|
+
(rb_funcall(rb_path2class("RDO::Result"), rb_intern("new"), 2, tuples, info))
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Wrap the given StatementExecutor in a RDO::Statement.
|
76
|
+
*
|
77
|
+
* @param VALUE
|
78
|
+
* any object that responds to #command and #execute
|
79
|
+
*
|
80
|
+
* @return VALUE
|
81
|
+
* an RDO::Statement
|
82
|
+
*/
|
83
|
+
#define RDO_STATEMENT(executor) \
|
84
|
+
(rb_funcall(rb_path2class("RDO::Statement"), rb_intern("new"), 1, executor))
|
85
|
+
|
86
|
+
/**
|
87
|
+
* Convert a C string to a ruby String.
|
88
|
+
*
|
89
|
+
* @param (char *) s
|
90
|
+
* a C string that is valid in the default encoding
|
91
|
+
*
|
92
|
+
* @param (size_t) len
|
93
|
+
* the length of the string
|
94
|
+
*
|
95
|
+
* @return VALUE (String)
|
96
|
+
* a Ruby String
|
97
|
+
*/
|
98
|
+
#define RDO_STRING(s, len, enc) \
|
99
|
+
(rb_enc_associate_index(rb_str_new(s, len), enc > 0 ? enc : 0))
|
100
|
+
|
101
|
+
/**
|
102
|
+
* Convert a C string to a ruby String, assuming possible NULL bytes.
|
103
|
+
*
|
104
|
+
* @param (char *) s
|
105
|
+
* a C string, possibly containing nulls
|
106
|
+
*
|
107
|
+
* @param (size_t) len
|
108
|
+
* the length of the string
|
109
|
+
*
|
110
|
+
* @return VALUE (String)
|
111
|
+
* a Ruby String
|
112
|
+
*/
|
113
|
+
#define RDO_BINARY_STRING(s, len) (rb_str_new(s, len))
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Convert a C string to a Fixnum.
|
117
|
+
*/
|
118
|
+
#define RDO_FIXNUM(s) (rb_cstr2inum(s, 10))
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Convert a C string to a Float.
|
122
|
+
*
|
123
|
+
* This supports Infinity and NaN.
|
124
|
+
*
|
125
|
+
* @param (char *) s
|
126
|
+
* a C string representing a float (e.g. "1.234")
|
127
|
+
*
|
128
|
+
* @return VALUE (Float)
|
129
|
+
* a ruby Float
|
130
|
+
*/
|
131
|
+
#define RDO_FLOAT(s) \
|
132
|
+
(rb_funcall(rb_path2class("RDO::Util"), \
|
133
|
+
rb_intern("float"), 1, rb_str_new2(s)))
|
134
|
+
|
135
|
+
/**
|
136
|
+
* Convert a C string representing a precision decimal into a BigDecimal.
|
137
|
+
*
|
138
|
+
* @param (char *) s
|
139
|
+
* a C string representing a decimal ("1.245")
|
140
|
+
*
|
141
|
+
* @return VALUE (BigDecimal)
|
142
|
+
* a BigDecimal representation of this string
|
143
|
+
*
|
144
|
+
* @example
|
145
|
+
* RDO_DECIMAL("1.234")
|
146
|
+
* => #<BigDecimal:7feb42b2b6e8,'0.1234E1',18(18)>
|
147
|
+
*/
|
148
|
+
#define RDO_DECIMAL(s) \
|
149
|
+
(rb_funcall(rb_path2class("RDO::Util"), \
|
150
|
+
rb_intern("decimal"), 1, rb_str_new2(s)))
|
151
|
+
|
152
|
+
/**
|
153
|
+
* Convert a C string representing a date into a Date.
|
154
|
+
*
|
155
|
+
* @param (char *) s
|
156
|
+
* the C string with a parseable date
|
157
|
+
*
|
158
|
+
* @return VALUE (Date)
|
159
|
+
* a Date, exactly as was specified in the input
|
160
|
+
*
|
161
|
+
* @example
|
162
|
+
* RDO_DATE("431-09-22 BC")
|
163
|
+
* #<Date: -0430-09-22 ((1564265j,0s,0n),+0s,2299161j)>
|
164
|
+
*/
|
165
|
+
#define RDO_DATE(s) \
|
166
|
+
(rb_funcall(rb_path2class("RDO::Util"), \
|
167
|
+
rb_intern("date"), 1, rb_str_new2(s)))
|
168
|
+
|
169
|
+
/**
|
170
|
+
* Convert a C string representing a date & time with no time zone into a DateTime.
|
171
|
+
*
|
172
|
+
* @param (char *) s
|
173
|
+
* the C string with the date & time provided
|
174
|
+
*
|
175
|
+
* @return VALUE (DateTime)
|
176
|
+
* a DateTime, assuming the system time zone
|
177
|
+
*
|
178
|
+
* @example
|
179
|
+
* RDO_DATE_TIME_WITHOUT_ZONE("2012-09-22 04:36:12")
|
180
|
+
* #<DateTime: 2012-09-22T04:36:12+10:00 ((2456192j,66972s,0n),+36000s,2299161j)>
|
181
|
+
*/
|
182
|
+
#define RDO_DATE_TIME_WITHOUT_ZONE(s) \
|
183
|
+
(rb_funcall(rb_path2class("RDO::Util"), \
|
184
|
+
rb_intern("date_time_without_zone"), 1, rb_str_new2(s)))
|
185
|
+
|
186
|
+
/**
|
187
|
+
* Convert a C string representing a date & time that includes a time zone into a DateTime.
|
188
|
+
*
|
189
|
+
* @param (char *) s
|
190
|
+
* the C string with the date & time provided, including the time zone
|
191
|
+
*
|
192
|
+
* @return VALUE (DateTime)
|
193
|
+
* a DateTime, exactly as was specified in the input
|
194
|
+
*
|
195
|
+
* @example
|
196
|
+
* RDO_DATE_TIME_WITHOUT_ZONE("2012-09-22 04:36:12+10:00")
|
197
|
+
* #<DateTime: 2012-09-22T04:36:12+10:00 ((2456192j,66972s,0n),+36000s,2299161j)>
|
198
|
+
*/
|
199
|
+
#define RDO_DATE_TIME_WITH_ZONE(s) \
|
200
|
+
(rb_funcall(rb_path2class("RDO::Util"), \
|
201
|
+
rb_intern("date_time_with_zone"), 1, rb_str_new2(s)))
|
202
|
+
|
203
|
+
/**
|
204
|
+
* Convert a boolean string to TrueClass/FalseClass.
|
205
|
+
*
|
206
|
+
* @param (char *) s
|
207
|
+
* a C string that is either 't', 'true', 'f' or 'false'
|
208
|
+
*
|
209
|
+
* @return VALUE (TrueClass, FalseClass)
|
210
|
+
* the boolean representation
|
211
|
+
*/
|
212
|
+
#define RDO_BOOL(s) ((s[0] == 't') ? Qtrue : Qfalse)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* RDO MySQL Driver.
|
3
|
+
* Copyright © 2012 Chris Corbyn.
|
4
|
+
*
|
5
|
+
* See LICENSE file for details.
|
6
|
+
*/
|
7
|
+
|
8
|
+
#include <ruby.h>
|
9
|
+
#include "driver.h"
|
10
|
+
|
11
|
+
/** Extension initializer, called when .so is loaded */
|
12
|
+
void Init_rdo_mysql(void) {
|
13
|
+
rb_require("rdo");
|
14
|
+
Init_rdo_mysql_driver();
|
15
|
+
}
|
@@ -0,0 +1,130 @@
|
|
1
|
+
/*
|
2
|
+
* RDO MySQL Driver.
|
3
|
+
* Copyright © 2012 Chris Corbyn.
|
4
|
+
*
|
5
|
+
* See LICENSE file for details.
|
6
|
+
*/
|
7
|
+
|
8
|
+
#include "tuples.h"
|
9
|
+
#include <stdio.h>
|
10
|
+
#include "macros.h"
|
11
|
+
|
12
|
+
/* I hate magic numbers */
|
13
|
+
#define RDO_MYSQL_BINARY_ENC 63
|
14
|
+
|
15
|
+
/** RDO::MySQL::TupleList class */
|
16
|
+
static VALUE rdo_mysql_cTupleList;
|
17
|
+
|
18
|
+
/** Struct wrapped by TupleList class */
|
19
|
+
typedef struct {
|
20
|
+
MYSQL_RES * res;
|
21
|
+
int encoding;
|
22
|
+
} RDOMySQLTupleList;
|
23
|
+
|
24
|
+
/** Free memory allocated to list during GC */
|
25
|
+
static void rdo_mysql_tuple_list_free(RDOMySQLTupleList * list) {
|
26
|
+
mysql_free_result(list->res);
|
27
|
+
free(list);
|
28
|
+
}
|
29
|
+
|
30
|
+
/** Constructor to create a new TupleList for the given result */
|
31
|
+
VALUE rdo_mysql_tuple_list_new(MYSQL_RES * res, int encoding) {
|
32
|
+
RDOMySQLTupleList * list = malloc(sizeof(RDOMySQLTupleList));
|
33
|
+
list->res = res;
|
34
|
+
list->encoding = encoding;
|
35
|
+
|
36
|
+
VALUE obj = Data_Wrap_Struct(rdo_mysql_cTupleList,
|
37
|
+
0, rdo_mysql_tuple_list_free, list);
|
38
|
+
|
39
|
+
rb_obj_call_init(obj, 0, NULL);
|
40
|
+
|
41
|
+
return obj;
|
42
|
+
}
|
43
|
+
|
44
|
+
/** Cast the given value to a Ruby type */
|
45
|
+
static VALUE rdo_mysql_cast_value(char * v, unsigned long len, MYSQL_FIELD f, int enc) {
|
46
|
+
switch (f.type) {
|
47
|
+
case MYSQL_TYPE_NULL:
|
48
|
+
return Qnil;
|
49
|
+
|
50
|
+
case MYSQL_TYPE_TINY:
|
51
|
+
case MYSQL_TYPE_SHORT:
|
52
|
+
case MYSQL_TYPE_LONG:
|
53
|
+
case MYSQL_TYPE_INT24:
|
54
|
+
case MYSQL_TYPE_LONGLONG:
|
55
|
+
return RDO_FIXNUM(v);
|
56
|
+
|
57
|
+
case MYSQL_TYPE_STRING:
|
58
|
+
case MYSQL_TYPE_VAR_STRING:
|
59
|
+
case MYSQL_TYPE_BLOB:
|
60
|
+
if (f.charsetnr == RDO_MYSQL_BINARY_ENC)
|
61
|
+
return RDO_BINARY_STRING(v, len);
|
62
|
+
else
|
63
|
+
return RDO_STRING(v, len, enc);
|
64
|
+
|
65
|
+
case MYSQL_TYPE_DECIMAL:
|
66
|
+
case MYSQL_TYPE_NEWDECIMAL:
|
67
|
+
return RDO_DECIMAL(v);
|
68
|
+
|
69
|
+
case MYSQL_TYPE_FLOAT:
|
70
|
+
case MYSQL_TYPE_DOUBLE:
|
71
|
+
return RDO_FLOAT(v);
|
72
|
+
|
73
|
+
case MYSQL_TYPE_DATE:
|
74
|
+
return RDO_DATE(v);
|
75
|
+
|
76
|
+
case MYSQL_TYPE_TIMESTAMP:
|
77
|
+
case MYSQL_TYPE_DATETIME:
|
78
|
+
return RDO_DATE_TIME_WITHOUT_ZONE(v);
|
79
|
+
|
80
|
+
default:
|
81
|
+
return RDO_BINARY_STRING(v, len);
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
/** Iterate over all tuples in the list */
|
86
|
+
static VALUE rdo_mysql_tuple_list_each(VALUE self) {
|
87
|
+
RDOMySQLTupleList * list;
|
88
|
+
Data_Get_Struct(self, RDOMySQLTupleList, list);
|
89
|
+
|
90
|
+
if (!rb_block_given_p() || list->res == NULL) {
|
91
|
+
return self;
|
92
|
+
}
|
93
|
+
|
94
|
+
mysql_data_seek(list->res, 0);
|
95
|
+
|
96
|
+
unsigned int nfields = mysql_num_fields(list->res);
|
97
|
+
MYSQL_FIELD * fields = mysql_fetch_fields(list->res);
|
98
|
+
MYSQL_ROW row;
|
99
|
+
|
100
|
+
while ((row = mysql_fetch_row(list->res))) {
|
101
|
+
unsigned long * lengths = mysql_fetch_lengths(list->res);
|
102
|
+
VALUE hash = rb_hash_new();
|
103
|
+
unsigned int i = 0;
|
104
|
+
|
105
|
+
for (; i < nfields; ++i) {
|
106
|
+
rb_hash_aset(hash,
|
107
|
+
ID2SYM(rb_intern(fields[i].name)),
|
108
|
+
rdo_mysql_cast_value(row[i], lengths[i], fields[i], list->encoding));
|
109
|
+
}
|
110
|
+
|
111
|
+
rb_yield(hash);
|
112
|
+
}
|
113
|
+
|
114
|
+
return self;
|
115
|
+
}
|
116
|
+
|
117
|
+
/** Initializer for the TupleList class */
|
118
|
+
void Init_rdo_mysql_tuples(void) {
|
119
|
+
rb_require("rdo/mysql");
|
120
|
+
|
121
|
+
VALUE mMySQL = rb_path2class("RDO::MySQL");
|
122
|
+
|
123
|
+
rdo_mysql_cTupleList = rb_define_class_under(mMySQL,
|
124
|
+
"TupleList", rb_cObject);
|
125
|
+
|
126
|
+
rb_define_method(rdo_mysql_cTupleList,
|
127
|
+
"each", rdo_mysql_tuple_list_each, 0);
|
128
|
+
|
129
|
+
rb_include_module(rdo_mysql_cTupleList, rb_mEnumerable);
|
130
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* RDO MySQL Driver.
|
3
|
+
* Copyright © 2012 Chris Corbyn.
|
4
|
+
*
|
5
|
+
* See LICENSE file for details.
|
6
|
+
*/
|
7
|
+
|
8
|
+
#include <ruby.h>
|
9
|
+
#include <mysql.h>
|
10
|
+
|
11
|
+
/** Constructor to return a new TupleList wrapping res */
|
12
|
+
VALUE rdo_mysql_tuple_list_new(MYSQL_RES * res, int encoding);
|
13
|
+
|
14
|
+
/** Called during extension initialization to create the TupleList class */
|
15
|
+
void Init_rdo_mysql_tuples(void);
|
data/lib/rdo-mysql.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "rdo/mysql"
|
data/lib/rdo/mysql.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
##
|
2
|
+
# RDO MySQL driver.
|
3
|
+
# Copyright © 2012 Chris Corbyn.
|
4
|
+
#
|
5
|
+
# See LICENSE file for details.
|
6
|
+
##
|
7
|
+
|
8
|
+
require "rdo"
|
9
|
+
require "rdo/mysql/version"
|
10
|
+
require "rdo/mysql/driver"
|
11
|
+
|
12
|
+
# c extension
|
13
|
+
require "rdo_mysql/rdo_mysql"
|
14
|
+
|
15
|
+
RDO::Connection.register_driver(:mysql, RDO::MySQL::Driver)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
##
|
2
|
+
# RDO MySQL driver.
|
3
|
+
# Copyright © 2012 Chris Corbyn.
|
4
|
+
#
|
5
|
+
# See LICENSE file for details.
|
6
|
+
##
|
7
|
+
|
8
|
+
module RDO
|
9
|
+
module MySQL
|
10
|
+
# Principal RDO driver class for MySQL.
|
11
|
+
class Driver < RDO::Driver
|
12
|
+
# implementation defined by ext/rdo_mysql/driver.c
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
%w[host user password database].each do |key|
|
17
|
+
define_method(key) { options[key.intern].to_s }
|
18
|
+
end
|
19
|
+
|
20
|
+
def port
|
21
|
+
options[:port].to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
def after_open
|
25
|
+
set_time_zone
|
26
|
+
set_encoding
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_time_zone
|
30
|
+
execute(
|
31
|
+
"SET time_zone = ?",
|
32
|
+
options.fetch(:time_zone, RDO::Util.system_time_zone)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_encoding
|
37
|
+
execute("SET NAMES ?", charset_name(encoding))
|
38
|
+
end
|
39
|
+
|
40
|
+
def encoding
|
41
|
+
options.fetch(:encoding, 'utf8')
|
42
|
+
end
|
43
|
+
|
44
|
+
# mysql uses a restrictive syntax for setting the encoding
|
45
|
+
def charset_name(name)
|
46
|
+
case name.to_s.downcase
|
47
|
+
when "utf-8"
|
48
|
+
"utf8"
|
49
|
+
when /^iso-8859-[0-9]+$/
|
50
|
+
name.to_s.gsub(/^.*?-([0-9]+)$/, "latin\\1")
|
51
|
+
else
|
52
|
+
name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/rdo-mysql.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/rdo/mysql/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["d11wtq"]
|
6
|
+
gem.email = ["chris@w3style.co.uk"]
|
7
|
+
gem.description = "Provides access to MySQL using the RDO interface"
|
8
|
+
gem.summary = "MySQL Driver for RDO"
|
9
|
+
gem.homepage = "https://github.com/d11wtq/rdo-mysql"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "rdo-mysql"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = RDO::MySQL::VERSION
|
17
|
+
gem.extensions = ["ext/rdo_mysql/extconf.rb"]
|
18
|
+
|
19
|
+
gem.add_runtime_dependency "rdo", ">= 0.0.8"
|
20
|
+
|
21
|
+
gem.add_development_dependency "rspec"
|
22
|
+
gem.add_development_dependency "rake-compiler"
|
23
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "uri"
|
3
|
+
|
4
|
+
describe RDO::MySQL::Driver do
|
5
|
+
let(:options) { connection_uri }
|
6
|
+
let(:connection) { RDO::Connection.new(options) }
|
7
|
+
|
8
|
+
after(:each) { connection.close rescue nil }
|
9
|
+
|
10
|
+
describe "#initialize" do
|
11
|
+
context "with valid settings" do
|
12
|
+
it "opens a connection to the server" do
|
13
|
+
connection.should be_open
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "with invalid settings" do
|
18
|
+
let(:options) { URI.parse(connection_uri).tap{|u| u.user = "bad_user"}.to_s }
|
19
|
+
|
20
|
+
it "raises an RDO::Exception" do
|
21
|
+
expect { connection }.to raise_error(RDO::Exception)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "provides a meaningful error" do
|
25
|
+
begin
|
26
|
+
connection && fail("RDO::Exception should be raised")
|
27
|
+
rescue RDO::Exception => e
|
28
|
+
e.message.should =~ /mysql.*?bad_user/i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#close" do
|
35
|
+
it "closes the connection to the server" do
|
36
|
+
connection.close
|
37
|
+
connection.should_not be_open
|
38
|
+
end
|
39
|
+
|
40
|
+
it "returns true" do
|
41
|
+
connection.close.should == true
|
42
|
+
end
|
43
|
+
|
44
|
+
context "called multiple times" do
|
45
|
+
it "has no negative side-effects" do
|
46
|
+
5.times { connection.close }
|
47
|
+
connection.should_not be_open
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#open" do
|
53
|
+
it "opens a connection to the server" do
|
54
|
+
connection.close && connection.open
|
55
|
+
connection.should be_open
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns true" do
|
59
|
+
connection.close
|
60
|
+
connection.open.should == true
|
61
|
+
end
|
62
|
+
|
63
|
+
context "called multiple times" do
|
64
|
+
before(:each) { connection.close }
|
65
|
+
|
66
|
+
it "has no negative side-effects" do
|
67
|
+
5.times { connection.open }
|
68
|
+
connection.should be_open
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "#quote" do
|
74
|
+
it "escapes values for safe insertion into a query" do
|
75
|
+
connection.quote("that's life!").should == "that\\'s life!"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "#execute" do
|
80
|
+
before(:each) do
|
81
|
+
connection.execute("DROP TABLE IF EXISTS test")
|
82
|
+
connection.execute <<-SQL
|
83
|
+
CREATE TABLE test (
|
84
|
+
id INT PRIMARY KEY AUTO_INCREMENT,
|
85
|
+
name VARCHAR(255),
|
86
|
+
age INT
|
87
|
+
) ENGINE=InnoDB CHARSET=utf8
|
88
|
+
SQL
|
89
|
+
end
|
90
|
+
|
91
|
+
after(:each) do
|
92
|
+
connection.execute("DROP TABLE IF EXISTS test")
|
93
|
+
end
|
94
|
+
|
95
|
+
context "with an insert" do
|
96
|
+
let(:result) do
|
97
|
+
connection.execute("INSERT INTO test (name) VALUES (?)", "jimmy")
|
98
|
+
end
|
99
|
+
|
100
|
+
it "returns a RDO::Result" do
|
101
|
+
result.should be_a_kind_of(RDO::Result)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "provides the #insert_id" do
|
105
|
+
result.insert_id.should == 1
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "with a select" do
|
110
|
+
before(:each) do
|
111
|
+
connection.execute("INSERT INTO test (name, age) VALUES (?, ?)", "jimmy", 22)
|
112
|
+
connection.execute("INSERT INTO test (name, age) VALUES (?, ?)", "harry", 28)
|
113
|
+
connection.execute("INSERT INTO test (name, age) VALUES (?, ?)", "kat", 31)
|
114
|
+
end
|
115
|
+
|
116
|
+
let(:result) do
|
117
|
+
connection.execute("SELECT * FROM test WHERE age > ?", 25)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "returns a RDO::Result" do
|
121
|
+
result.should be_a_kind_of(RDO::Result)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "provides the #count" do
|
125
|
+
result.count.should == 2
|
126
|
+
end
|
127
|
+
|
128
|
+
it "allows enumeration of the rows" do
|
129
|
+
rows = []
|
130
|
+
result.each {|row| rows << row}
|
131
|
+
rows.should == [{id: 2, name: "harry", age: 28}, {id: 3, name: "kat", age: 31}]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "with an update" do
|
136
|
+
before(:each) do
|
137
|
+
connection.execute("INSERT INTO test (name, age) VALUES (?, ?)", "jimmy", 22)
|
138
|
+
connection.execute("INSERT INTO test (name, age) VALUES (?, ?)", "harry", 28)
|
139
|
+
connection.execute("INSERT INTO test (name, age) VALUES (?, ?)", "kat", 31)
|
140
|
+
end
|
141
|
+
|
142
|
+
let(:result) do
|
143
|
+
connection.execute("UPDATE test SET age = age + 3 WHERE age > ?", 25)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "returns a RDO::Result" do
|
147
|
+
result.should be_a_kind_of(RDO::Result)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "provides the #affected_rows" do
|
151
|
+
result.affected_rows.should == 2
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "bigdecimal"
|
3
|
+
require "date"
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
describe RDO::MySQL::Driver, "type casting" do
|
7
|
+
let(:options) { connection_uri }
|
8
|
+
let(:connection) { RDO.connect(options) }
|
9
|
+
let(:value) { connection.execute(sql).first_value }
|
10
|
+
|
11
|
+
describe "null cast" do
|
12
|
+
let(:sql) { "SELECT NULL" }
|
13
|
+
|
14
|
+
it "returns nil" do
|
15
|
+
value.should be_nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "varchar cast" do
|
20
|
+
before(:each) do
|
21
|
+
connection.execute("CREATE TEMPORARY TABLE test (v VARCHAR(16))")
|
22
|
+
connection.execute("INSERT INTO test (v) VALUES ('bob')")
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:sql) { "SELECT v FROM test" }
|
26
|
+
|
27
|
+
it "returns a String" do
|
28
|
+
value.should == "bob"
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with utf-8 encoding" do
|
32
|
+
let(:options) { URI.parse(connection_uri).tap{|u| u.query = "encoding=utf-8"}.to_s }
|
33
|
+
|
34
|
+
it "has the correct encoding" do
|
35
|
+
value.encoding.should == Encoding.find("utf-8")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "with iso-8859-1 encoding" do
|
40
|
+
let(:options) { URI.parse(connection_uri).tap{|u| u.query = "encoding=iso-8859-1"}.to_s }
|
41
|
+
|
42
|
+
it "has the correct encoding" do
|
43
|
+
value.encoding.should == Encoding.find("iso-8859-1")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "char cast" do
|
49
|
+
before(:each) do
|
50
|
+
connection.execute("CREATE TEMPORARY TABLE test (v CHAR(4))")
|
51
|
+
connection.execute("INSERT INTO test (v) VALUES ('dave')")
|
52
|
+
end
|
53
|
+
|
54
|
+
let(:sql) { "SELECT v FROM test" }
|
55
|
+
|
56
|
+
it "returns a String" do
|
57
|
+
value.should == "dave"
|
58
|
+
end
|
59
|
+
|
60
|
+
context "with utf-8 encoding" do
|
61
|
+
let(:options) { URI.parse(connection_uri).tap{|u| u.query = "encoding=utf-8"}.to_s }
|
62
|
+
|
63
|
+
it "has the correct encoding" do
|
64
|
+
value.encoding.should == Encoding.find("utf-8")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "with iso-8859-1 encoding" do
|
69
|
+
let(:options) { URI.parse(connection_uri).tap{|u| u.query = "encoding=iso-8859-1"}.to_s }
|
70
|
+
|
71
|
+
it "has the correct encoding" do
|
72
|
+
value.encoding.should == Encoding.find("iso-8859-1")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "text cast" do
|
78
|
+
before(:each) do
|
79
|
+
connection.execute("CREATE TEMPORARY TABLE test (v TEXT)")
|
80
|
+
connection.execute("INSERT INTO test (v) VALUES ('bobby')")
|
81
|
+
end
|
82
|
+
|
83
|
+
let(:sql) { "SELECT v FROM test" }
|
84
|
+
|
85
|
+
it "returns a String" do
|
86
|
+
value.should == "bobby"
|
87
|
+
end
|
88
|
+
|
89
|
+
context "with utf-8 encoding" do
|
90
|
+
let(:options) { URI.parse(connection_uri).tap{|u| u.query = "encoding=utf-8"}.to_s }
|
91
|
+
|
92
|
+
it "has the correct encoding" do
|
93
|
+
value.encoding.should == Encoding.find("utf-8")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "with iso-8859-1 encoding" do
|
98
|
+
let(:options) { URI.parse(connection_uri).tap{|u| u.query = "encoding=iso-8859-1"}.to_s }
|
99
|
+
|
100
|
+
it "has the correct encoding" do
|
101
|
+
value.encoding.should == Encoding.find("iso-8859-1")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "binary cast" do
|
107
|
+
before(:each) do
|
108
|
+
connection.execute("CREATE TEMPORARY TABLE test (v BINARY(3))")
|
109
|
+
connection.execute("INSERT INTO test (v) VALUES (?)", "\x00\x11\x22")
|
110
|
+
end
|
111
|
+
|
112
|
+
let(:sql) { "SELECT v FROM test" }
|
113
|
+
|
114
|
+
it "returns a String" do
|
115
|
+
value.should == "\x00\x11\x22"
|
116
|
+
end
|
117
|
+
|
118
|
+
it "has binary encoding" do
|
119
|
+
value.encoding.should == Encoding.find("binary")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "blob cast" do
|
124
|
+
before(:each) do
|
125
|
+
connection.execute("CREATE TEMPORARY TABLE test (v BLOB)")
|
126
|
+
connection.execute("INSERT INTO test (v) VALUES (?)", "\x00\x11\x22\x33\x44")
|
127
|
+
end
|
128
|
+
|
129
|
+
let(:sql) { "SELECT v FROM test" }
|
130
|
+
|
131
|
+
it "returns a String" do
|
132
|
+
value.should == "\x00\x11\x22\x33\x44"
|
133
|
+
end
|
134
|
+
|
135
|
+
it "has binary encoding" do
|
136
|
+
value.encoding.should == Encoding.find("binary")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "integer cast" do
|
141
|
+
let(:sql) { "SELECT 1234" }
|
142
|
+
|
143
|
+
it "returns a Fixnum" do
|
144
|
+
value.should == 1234
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "float cast" do
|
149
|
+
before(:each) do
|
150
|
+
connection.execute("CREATE TEMPORARY TABLE test (n FLOAT)")
|
151
|
+
connection.execute("INSERT INTO test (n) VALUES (12.34)")
|
152
|
+
end
|
153
|
+
|
154
|
+
let(:sql) { "SELECT n FROM test" }
|
155
|
+
|
156
|
+
it "returns a Float" do
|
157
|
+
value.should == 12.34
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe "decimal cast" do
|
162
|
+
before(:each) do
|
163
|
+
connection.execute("CREATE TEMPORARY TABLE test (n DECIMAL(6,2))")
|
164
|
+
connection.execute("INSERT INTO test (n) VALUES ('1234.56')")
|
165
|
+
end
|
166
|
+
|
167
|
+
let(:sql) { "SELECT n FROM test" }
|
168
|
+
|
169
|
+
it "returns a BigDecimal" do
|
170
|
+
value.should == BigDecimal("1234.56")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe "date cast" do
|
175
|
+
before(:each) do
|
176
|
+
connection.execute("CREATE TEMPORARY TABLE test (d DATE)")
|
177
|
+
connection.execute("INSERT INTO test (d) VALUES ('2012-09-30')")
|
178
|
+
end
|
179
|
+
|
180
|
+
let(:sql) { "SELECT d FROM test" }
|
181
|
+
|
182
|
+
it "returns a Date" do
|
183
|
+
value.should == Date.new(2012, 9, 30)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "datetime cast" do
|
188
|
+
before(:each) do
|
189
|
+
connection.execute("CREATE TEMPORARY TABLE test (d DATETIME)")
|
190
|
+
connection.execute("INSERT INTO test (d) VALUES ('2012-09-30 19:04:36')")
|
191
|
+
end
|
192
|
+
|
193
|
+
let(:sql) { "SELECT d FROM test" }
|
194
|
+
|
195
|
+
it "returns a DateTime in the system time zone" do
|
196
|
+
value.should == DateTime.new(2012, 9, 30, 19, 4, 36, DateTime.now.zone)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe "timestamp cast" do
|
201
|
+
before(:each) do
|
202
|
+
connection.execute("CREATE TEMPORARY TABLE test (d TIMESTAMP)")
|
203
|
+
connection.execute("INSERT INTO test (d) VALUES ('2012-09-30 19:04:36')")
|
204
|
+
end
|
205
|
+
|
206
|
+
let(:sql) { "SELECT d FROM test" }
|
207
|
+
|
208
|
+
it "returns a DateTime in the system time zone" do
|
209
|
+
value.should == DateTime.new(2012, 9, 30, 19, 4, 36, DateTime.now.zone)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rdo-mysql
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- d11wtq
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rdo
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.0.8
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.0.8
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake-compiler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Provides access to MySQL using the RDO interface
|
63
|
+
email:
|
64
|
+
- chris@w3style.co.uk
|
65
|
+
executables: []
|
66
|
+
extensions:
|
67
|
+
- ext/rdo_mysql/extconf.rb
|
68
|
+
extra_rdoc_files: []
|
69
|
+
files:
|
70
|
+
- .gitignore
|
71
|
+
- .rspec
|
72
|
+
- .travis.yml
|
73
|
+
- Gemfile
|
74
|
+
- LICENSE
|
75
|
+
- README.md
|
76
|
+
- Rakefile
|
77
|
+
- ext/rdo_mysql/driver.c
|
78
|
+
- ext/rdo_mysql/driver.h
|
79
|
+
- ext/rdo_mysql/extconf.rb
|
80
|
+
- ext/rdo_mysql/macros.h
|
81
|
+
- ext/rdo_mysql/rdo_mysql.c
|
82
|
+
- ext/rdo_mysql/tuples.c
|
83
|
+
- ext/rdo_mysql/tuples.h
|
84
|
+
- lib/rdo-mysql.rb
|
85
|
+
- lib/rdo/mysql.rb
|
86
|
+
- lib/rdo/mysql/driver.rb
|
87
|
+
- lib/rdo/mysql/version.rb
|
88
|
+
- rdo-mysql.gemspec
|
89
|
+
- spec/mysql/driver_spec.rb
|
90
|
+
- spec/mysql/type_cast_spec.rb
|
91
|
+
- spec/spec_helper.rb
|
92
|
+
homepage: https://github.com/d11wtq/rdo-mysql
|
93
|
+
licenses: []
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 1.8.24
|
113
|
+
signing_key:
|
114
|
+
specification_version: 3
|
115
|
+
summary: MySQL Driver for RDO
|
116
|
+
test_files:
|
117
|
+
- spec/mysql/driver_spec.rb
|
118
|
+
- spec/mysql/type_cast_spec.rb
|
119
|
+
- spec/spec_helper.rb
|