rdo-mysql 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.so
19
+ *.bundle
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -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
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ # allow a local version of RDO to be used during dev
6
+ if ENV["RDO_PATH"]
7
+ gem 'rdo', path: ENV["RDO_PATH"]
8
+ end
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.
@@ -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.
@@ -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,12 @@
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
+ /** Initializer driver during extension initialization */
12
+ void Init_rdo_mysql_driver(void);
@@ -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);
@@ -0,0 +1 @@
1
+ require "rdo/mysql"
@@ -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
@@ -0,0 +1,12 @@
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
+ VERSION = "0.0.1"
11
+ end
12
+ end
@@ -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
@@ -0,0 +1,10 @@
1
+ require "rdo"
2
+ require "rdo/mysql"
3
+
4
+ ENV["CONNECTION"] ||= "mysql://rdo:rdo@localhost/rdo?encoding=utf-8"
5
+
6
+ RSpec.configure do |config|
7
+ def connection_uri
8
+ ENV["CONNECTION"]
9
+ end
10
+ end
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