rdo-mysql 0.0.1
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/.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
|
+
[](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
|