mysql2 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +6 -0
- data/CHANGELOG.md +4 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +93 -0
- data/Rakefile +35 -0
- data/VERSION +1 -0
- data/benchmark/escape.rb +39 -0
- data/benchmark/query.rb +49 -0
- data/benchmark/setup_db.rb +110 -0
- data/ext/extconf.rb +19 -0
- data/ext/mysql2_ext.c +461 -0
- data/ext/mysql2_ext.h +41 -0
- data/lib/mysql2.rb +9 -0
- data/mysql2.gemspec +60 -0
- data/spec/mysql2/client_spec.rb +93 -0
- data/spec/mysql2/result_spec.rb +170 -0
- data/spec/rcov.opts +3 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +4 -0
- metadata +83 -0
data/CHANGELOG.md
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Brian Lopez - http://github.com/brianmario
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
= Mysql2 - A modern, simple and very fast Mysql library for Ruby, binding to libmysql
|
2
|
+
|
3
|
+
The Mysql2 gem is meant to serve the extremely common use-case of connecting, querying and iterating on results.
|
4
|
+
Some database libraries out there serve as direct 1:1 mappings of the already complex C API's available.
|
5
|
+
This one is not.
|
6
|
+
|
7
|
+
It also forces the use of UTF-8 [or binary] for the connection [and all strings in 1.9] and uses encoding-aware MySQL API calls where it can.
|
8
|
+
|
9
|
+
The API consists of two clases:
|
10
|
+
|
11
|
+
Mysql2::Client - your connection to the database
|
12
|
+
|
13
|
+
Mysql2::Result - returned from issuing a #query on the connection. It includes Enumerable.
|
14
|
+
|
15
|
+
== Installing
|
16
|
+
|
17
|
+
I plan to release the initial gem version within the next day or so. Just wanted to tweak extconf to help find the libmysql binary easier, and add the ssl options.
|
18
|
+
|
19
|
+
== Usage
|
20
|
+
|
21
|
+
Connect to a database:
|
22
|
+
|
23
|
+
# this takes a hash of options, almost all of which map directly
|
24
|
+
# to the familiar database.yml in rails
|
25
|
+
# See http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html
|
26
|
+
client = Mysql2::Client.new(:host => "localhost", :username => "root")
|
27
|
+
|
28
|
+
Then query it:
|
29
|
+
|
30
|
+
results = client.query("SELECT * FROM users WHERE group='githubbers'")
|
31
|
+
|
32
|
+
Need to escape something first?
|
33
|
+
|
34
|
+
escaped = client.escape("gi'thu\"bbe\0r's")
|
35
|
+
results = client.query("SELECT * FROM users WHERE group='#{escaped}'")
|
36
|
+
|
37
|
+
Finally, iterate over the results:
|
38
|
+
|
39
|
+
results.each do |row|
|
40
|
+
# conveniently, row is a hash
|
41
|
+
# the keys are the fields, as you'd expect
|
42
|
+
# the values are pre-built ruby primitives mapped from their corresponding field types in MySQL
|
43
|
+
# Here's an otter: http://farm1.static.flickr.com/130/398077070_b8795d0ef3_b.jpg
|
44
|
+
end
|
45
|
+
|
46
|
+
Or, you might just keep it simple:
|
47
|
+
|
48
|
+
client.query("SELECT * FROM users WHERE group='githubbers'").each do |row|
|
49
|
+
# do something with row, it's ready to rock
|
50
|
+
end
|
51
|
+
|
52
|
+
How about with symbolized keys?
|
53
|
+
|
54
|
+
# NOTE: the :symbolize_keys and future options will likely move to the #query method soon
|
55
|
+
client.query("SELECT * FROM users WHERE group='githubbers'").each(:symbolize_keys => true) do |row|
|
56
|
+
# do something with row, it's ready to rock
|
57
|
+
end
|
58
|
+
|
59
|
+
== Compatibility
|
60
|
+
|
61
|
+
The specs pass on my system (SL 10.6.3, x86_64) in these rubies:
|
62
|
+
|
63
|
+
* 1.8.7-p249
|
64
|
+
* ree-1.8.7-2010.01
|
65
|
+
* 1.9.1-p378
|
66
|
+
* ruby-trunk
|
67
|
+
* rbx-head
|
68
|
+
|
69
|
+
== Yeah... but why?
|
70
|
+
|
71
|
+
Someone: Dude, the Mysql gem works fiiiiiine.
|
72
|
+
|
73
|
+
Me: It sure does, but it only hands you nil and strings for field values. Leaving you to convert
|
74
|
+
them into proper Ruby types in Ruby-land - which is slow as balls.
|
75
|
+
|
76
|
+
|
77
|
+
Someone: OK fine, but do_mysql can already give me back values with Ruby objects mapped to MySQL types.
|
78
|
+
|
79
|
+
Me: Yep, but it's API is considerably more complex *and* is 2-3x slower.
|
80
|
+
|
81
|
+
== Benchmarks
|
82
|
+
|
83
|
+
Performing a basic "SELECT * FROM" query on a table with 30k rows and fields of nearly every Ruby-representable data type,
|
84
|
+
then iterating over every row using an #each like method yielding a block:
|
85
|
+
|
86
|
+
# And remember, the Mysql gem only gives back nil and strings.
|
87
|
+
user system total real
|
88
|
+
Mysql2
|
89
|
+
2.080000 0.790000 2.870000 ( 3.418861)
|
90
|
+
Mysql
|
91
|
+
1.210000 0.790000 2.000000 ( 4.527824)
|
92
|
+
do_mysql
|
93
|
+
5.450000 0.980000 6.430000 ( 7.001563)
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
begin
|
3
|
+
require 'jeweler'
|
4
|
+
Jeweler::Tasks.new do |gem|
|
5
|
+
gem.name = "mysql2"
|
6
|
+
gem.summary = "A simple, fast Mysql library for Ruby, binding to libmysql"
|
7
|
+
gem.email = "seniorlopez@gmail.com"
|
8
|
+
gem.homepage = "http://github.com/brianmario/mysql2"
|
9
|
+
gem.authors = ["Brian Lopez"]
|
10
|
+
gem.require_paths = ["lib", "ext"]
|
11
|
+
gem.extra_rdoc_files = `git ls-files *.rdoc`.split("\n")
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.extensions = ["ext/extconf.rb"]
|
14
|
+
gem.files.include %w(lib/jeweler/templates/.document lib/jeweler/templates/.gitignore)
|
15
|
+
# gem.rubyforge_project = "mysql2"
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler -s http://gems.github.com"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake'
|
22
|
+
require 'spec/rake/spectask'
|
23
|
+
|
24
|
+
desc "Run all examples with RCov"
|
25
|
+
Spec::Rake::SpecTask.new('spec:rcov') do |t|
|
26
|
+
t.spec_files = FileList['spec/']
|
27
|
+
t.rcov = true
|
28
|
+
t.rcov_opts = lambda do
|
29
|
+
IO.readlines("spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
|
30
|
+
end
|
31
|
+
end
|
32
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
33
|
+
t.spec_files = FileList['spec/']
|
34
|
+
t.spec_opts << '--options' << 'spec/spec.opts'
|
35
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/benchmark/escape.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'benchmark'
|
5
|
+
require 'mysql'
|
6
|
+
require 'mysql2_ext'
|
7
|
+
require 'do_mysql'
|
8
|
+
|
9
|
+
number_of = 1000
|
10
|
+
database = 'nbb_1_production'
|
11
|
+
str = "abc'def\"ghi\0jkl%mno"
|
12
|
+
|
13
|
+
Benchmark.bmbm do |x|
|
14
|
+
mysql = Mysql.new("localhost", "root")
|
15
|
+
mysql.query "USE #{database}"
|
16
|
+
x.report do
|
17
|
+
puts "Mysql"
|
18
|
+
number_of.times do
|
19
|
+
mysql.quote str
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
|
24
|
+
mysql2.query "USE #{database}"
|
25
|
+
x.report do
|
26
|
+
puts "Mysql2"
|
27
|
+
number_of.times do
|
28
|
+
mysql2.escape str
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
|
33
|
+
x.report do
|
34
|
+
puts "do_mysql"
|
35
|
+
number_of.times do
|
36
|
+
do_mysql.quote_string str
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/benchmark/query.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'benchmark'
|
5
|
+
require 'mysql'
|
6
|
+
require 'mysql2_ext'
|
7
|
+
require 'do_mysql'
|
8
|
+
|
9
|
+
number_of = 1
|
10
|
+
database = 'test'
|
11
|
+
sql = "SELECT * FROM mysql2_test"
|
12
|
+
|
13
|
+
Benchmark.bmbm do |x|
|
14
|
+
mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
|
15
|
+
mysql2.query "USE #{database}"
|
16
|
+
x.report do
|
17
|
+
puts "Mysql2"
|
18
|
+
number_of.times do
|
19
|
+
mysql2_result = mysql2.query sql
|
20
|
+
mysql2_result.each(:symbolize_keys => true) do |res|
|
21
|
+
# puts res.inspect
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
mysql = Mysql.new("localhost", "root")
|
27
|
+
mysql.query "USE #{database}"
|
28
|
+
x.report do
|
29
|
+
puts "Mysql"
|
30
|
+
number_of.times do
|
31
|
+
mysql_result = mysql.query sql
|
32
|
+
mysql_result.each_hash do |res|
|
33
|
+
# puts res.inspect
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
|
39
|
+
command = DataObjects::Mysql::Command.new do_mysql, sql
|
40
|
+
x.report do
|
41
|
+
puts "do_mysql"
|
42
|
+
number_of.times do
|
43
|
+
do_result = command.execute_reader
|
44
|
+
do_result.each do |res|
|
45
|
+
# puts res.inspect
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# This script is for generating psudo-random data into a single table consisting of nearly every
|
4
|
+
# data type MySQL 5.1 supports.
|
5
|
+
#
|
6
|
+
# It's meant to be used with the query.rb benchmark script (or others in the future)
|
7
|
+
|
8
|
+
require 'mysql2_ext'
|
9
|
+
require 'rubygems'
|
10
|
+
require 'faker'
|
11
|
+
|
12
|
+
num = ENV['NUM'] && ENV['NUM'].to_i || 10_000
|
13
|
+
|
14
|
+
create_table_sql = %[
|
15
|
+
CREATE TABLE IF NOT EXISTS mysql2_test (
|
16
|
+
null_test VARCHAR(10),
|
17
|
+
bit_test BIT,
|
18
|
+
tiny_int_test TINYINT,
|
19
|
+
small_int_test SMALLINT,
|
20
|
+
medium_int_test MEDIUMINT,
|
21
|
+
int_test INT,
|
22
|
+
big_int_test BIGINT,
|
23
|
+
float_test FLOAT(10,3),
|
24
|
+
double_test DOUBLE(10,3),
|
25
|
+
decimal_test DECIMAL(10,3),
|
26
|
+
date_test DATE,
|
27
|
+
date_time_test DATETIME,
|
28
|
+
timestamp_test TIMESTAMP,
|
29
|
+
time_test TIME,
|
30
|
+
year_test YEAR(4),
|
31
|
+
char_test CHAR(10),
|
32
|
+
varchar_test VARCHAR(10),
|
33
|
+
binary_test BINARY(10),
|
34
|
+
varbinary_test VARBINARY(10),
|
35
|
+
tiny_blob_test TINYBLOB,
|
36
|
+
tiny_text_test TINYTEXT,
|
37
|
+
blob_test BLOB,
|
38
|
+
text_test TEXT,
|
39
|
+
medium_blob_test MEDIUMBLOB,
|
40
|
+
medium_text_test MEDIUMTEXT,
|
41
|
+
long_blob_test LONGBLOB,
|
42
|
+
long_text_test LONGTEXT,
|
43
|
+
enum_test ENUM('val1', 'val2'),
|
44
|
+
set_test SET('val1', 'val2')
|
45
|
+
) DEFAULT CHARSET=utf8
|
46
|
+
]
|
47
|
+
|
48
|
+
# connect to localhost by default, pass options as needed
|
49
|
+
@client = Mysql2::Client.new :host => "localhost", :username => "root", :database => "test"
|
50
|
+
|
51
|
+
@client.query create_table_sql
|
52
|
+
|
53
|
+
def insert_record(args)
|
54
|
+
insert_sql = "
|
55
|
+
INSERT INTO mysql2_test (
|
56
|
+
null_test, bit_test, tiny_int_test, small_int_test, medium_int_test, int_test, big_int_test,
|
57
|
+
float_test, double_test, decimal_test, date_test, date_time_test, timestamp_test, time_test,
|
58
|
+
year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test,
|
59
|
+
tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test,
|
60
|
+
long_blob_test, long_text_test, enum_test, set_test
|
61
|
+
)
|
62
|
+
|
63
|
+
VALUES (
|
64
|
+
NULL, #{args[:bit_test]}, #{args[:tiny_int_test]}, #{args[:small_int_test]}, #{args[:medium_int_test]}, #{args[:int_test]}, #{args[:big_int_test]},
|
65
|
+
#{args[:float_test]}, #{args[:double_test]}, #{args[:decimal_test]}, '#{args[:date_test]}', '#{args[:date_time_test]}', '#{args[:timestamp_test]}', '#{args[:time_test]}',
|
66
|
+
#{args[:year_test]}, '#{args[:char_test]}', '#{args[:varchar_test]}', '#{args[:binary_test]}', '#{args[:varbinary_test]}', '#{args[:tiny_blob_test]}',
|
67
|
+
'#{args[:tiny_text_test]}', '#{args[:blob_test]}', '#{args[:text_test]}', '#{args[:medium_blob_test]}', '#{args[:medium_text_test]}',
|
68
|
+
'#{args[:long_blob_test]}', '#{args[:long_text_test]}', '#{args[:enum_test]}', '#{args[:set_test]}'
|
69
|
+
)
|
70
|
+
"
|
71
|
+
@client.query insert_sql
|
72
|
+
end
|
73
|
+
|
74
|
+
puts "Creating #{num} records"
|
75
|
+
num.times do |n|
|
76
|
+
insert_record(
|
77
|
+
:bit_test => 1,
|
78
|
+
:tiny_int_test => rand(128),
|
79
|
+
:small_int_test => rand(32767),
|
80
|
+
:medium_int_test => rand(8388607),
|
81
|
+
:int_test => rand(2147483647),
|
82
|
+
:big_int_test => rand(9223372036854775807),
|
83
|
+
:float_test => rand(32767)/1.87,
|
84
|
+
:double_test => rand(8388607)/1.87,
|
85
|
+
:decimal_test => rand(8388607)/1.87,
|
86
|
+
:date_test => '2010-4-4',
|
87
|
+
:date_time_test => '2010-4-4 11:44:00',
|
88
|
+
:timestamp_test => '2010-4-4 11:44:00',
|
89
|
+
:time_test => '11:44:00',
|
90
|
+
:year_test => Time.now.year,
|
91
|
+
:char_test => Faker::Lorem.words(rand(5)),
|
92
|
+
:varchar_test => Faker::Lorem.words(rand(5)),
|
93
|
+
:binary_test => Faker::Lorem.words(rand(5)),
|
94
|
+
:varbinary_test => Faker::Lorem.words(rand(5)),
|
95
|
+
:tiny_blob_test => Faker::Lorem.words(rand(5)),
|
96
|
+
:tiny_text_test => Faker::Lorem.paragraph(rand(5)),
|
97
|
+
:blob_test => Faker::Lorem.paragraphs(rand(25)),
|
98
|
+
:text_test => Faker::Lorem.paragraphs(rand(25)),
|
99
|
+
:medium_blob_test => Faker::Lorem.paragraphs(rand(25)),
|
100
|
+
:medium_text_test => Faker::Lorem.paragraphs(rand(25)),
|
101
|
+
:long_blob_test => Faker::Lorem.paragraphs(rand(25)),
|
102
|
+
:long_text_test => Faker::Lorem.paragraphs(rand(25)),
|
103
|
+
:enum_test => ['val1', 'val2'].rand,
|
104
|
+
:set_test => ['val1', 'val2', 'val1,val2'].rand
|
105
|
+
)
|
106
|
+
$stdout.putc '.'
|
107
|
+
$stdout.flush
|
108
|
+
end
|
109
|
+
puts
|
110
|
+
puts "Done"
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'mkmf'
|
4
|
+
dir_config('mysql')
|
5
|
+
|
6
|
+
have_header('mysql/mysql.h')
|
7
|
+
|
8
|
+
$CFLAGS << ' -Wall -Wextra -funroll-loops'
|
9
|
+
# $CFLAGS << ' -O0 -ggdb3'
|
10
|
+
|
11
|
+
if have_library('mysqlclient')
|
12
|
+
if RUBY_VERSION =~ /1.9/
|
13
|
+
$CFLAGS << ' -DRUBY_19_COMPATIBILITY'
|
14
|
+
end
|
15
|
+
|
16
|
+
create_makefile('mysql2_ext')
|
17
|
+
else
|
18
|
+
puts 'libmysql not found, maybe try manually specifying --with-mysql-lib to find it?'
|
19
|
+
end
|
data/ext/mysql2_ext.c
ADDED
@@ -0,0 +1,461 @@
|
|
1
|
+
#include "mysql2_ext.h"
|
2
|
+
|
3
|
+
/* Mysql2::Client */
|
4
|
+
static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass) {
|
5
|
+
MYSQL * client;
|
6
|
+
VALUE obj, opts;
|
7
|
+
VALUE rb_host, rb_socket, rb_port, rb_database,
|
8
|
+
rb_username, rb_password, rb_reconnect,
|
9
|
+
rb_connect_timeout;
|
10
|
+
VALUE rb_ssl_client_key, rb_ssl_client_cert, rb_ssl_ca_cert,
|
11
|
+
rb_ssl_ca_path, rb_ssl_cipher;
|
12
|
+
char *host = "localhost", *socket = NULL, *username = NULL,
|
13
|
+
*password = NULL, *database = NULL;
|
14
|
+
char *ssl_client_key = NULL, *ssl_client_cert = NULL, *ssl_ca_cert = NULL,
|
15
|
+
*ssl_ca_path = NULL, *ssl_cipher = NULL;
|
16
|
+
unsigned int port = 3306, connect_timeout = 0;
|
17
|
+
my_bool reconnect = 0;
|
18
|
+
|
19
|
+
obj = Data_Make_Struct(klass, MYSQL, NULL, rb_mysql_client_free, client);
|
20
|
+
|
21
|
+
if (rb_scan_args(argc, argv, "01", &opts) == 1) {
|
22
|
+
Check_Type(opts, T_HASH);
|
23
|
+
|
24
|
+
if ((rb_host = rb_hash_aref(opts, sym_host)) != Qnil) {
|
25
|
+
Check_Type(rb_host, T_STRING);
|
26
|
+
host = RSTRING_PTR(rb_host);
|
27
|
+
}
|
28
|
+
|
29
|
+
if ((rb_socket = rb_hash_aref(opts, sym_socket)) != Qnil) {
|
30
|
+
Check_Type(rb_socket, T_STRING);
|
31
|
+
socket = RSTRING_PTR(rb_socket);
|
32
|
+
}
|
33
|
+
|
34
|
+
if ((rb_port = rb_hash_aref(opts, sym_port)) != Qnil) {
|
35
|
+
Check_Type(rb_port, T_FIXNUM);
|
36
|
+
port = FIX2INT(rb_port);
|
37
|
+
}
|
38
|
+
|
39
|
+
if ((rb_username = rb_hash_aref(opts, sym_username)) != Qnil) {
|
40
|
+
Check_Type(rb_username, T_STRING);
|
41
|
+
username = RSTRING_PTR(rb_username);
|
42
|
+
}
|
43
|
+
|
44
|
+
if ((rb_password = rb_hash_aref(opts, sym_password)) != Qnil) {
|
45
|
+
Check_Type(rb_password, T_STRING);
|
46
|
+
password = RSTRING_PTR(rb_password);
|
47
|
+
}
|
48
|
+
|
49
|
+
if ((rb_database = rb_hash_aref(opts, sym_database)) != Qnil) {
|
50
|
+
Check_Type(rb_database, T_STRING);
|
51
|
+
database = RSTRING_PTR(rb_database);
|
52
|
+
}
|
53
|
+
|
54
|
+
if ((rb_reconnect = rb_hash_aref(opts, sym_reconnect)) != Qnil) {
|
55
|
+
reconnect = rb_reconnect == Qtrue ? 1 : 0;
|
56
|
+
}
|
57
|
+
|
58
|
+
if ((rb_connect_timeout = rb_hash_aref(opts, sym_connect_timeout)) != Qnil) {
|
59
|
+
Check_Type(rb_connect_timeout, T_FIXNUM);
|
60
|
+
connect_timeout = FIX2INT(rb_connect_timeout);
|
61
|
+
}
|
62
|
+
|
63
|
+
// SSL options
|
64
|
+
if ((rb_ssl_client_key = rb_hash_aref(opts, sym_sslkey)) != Qnil) {
|
65
|
+
Check_Type(rb_ssl_client_key, T_STRING);
|
66
|
+
ssl_client_key = RSTRING_PTR(rb_ssl_client_key);
|
67
|
+
}
|
68
|
+
|
69
|
+
if ((rb_ssl_client_cert = rb_hash_aref(opts, sym_sslcert)) != Qnil) {
|
70
|
+
Check_Type(rb_ssl_client_cert, T_STRING);
|
71
|
+
ssl_client_cert = RSTRING_PTR(rb_ssl_client_cert);
|
72
|
+
}
|
73
|
+
|
74
|
+
if ((rb_ssl_ca_cert = rb_hash_aref(opts, sym_sslca)) != Qnil) {
|
75
|
+
Check_Type(rb_ssl_ca_cert, T_STRING);
|
76
|
+
ssl_ca_cert = RSTRING_PTR(rb_ssl_ca_cert);
|
77
|
+
}
|
78
|
+
|
79
|
+
if ((rb_ssl_ca_path = rb_hash_aref(opts, sym_sslcapath)) != Qnil) {
|
80
|
+
Check_Type(rb_ssl_ca_path, T_STRING);
|
81
|
+
ssl_ca_path = RSTRING_PTR(rb_ssl_ca_path);
|
82
|
+
}
|
83
|
+
|
84
|
+
if ((rb_ssl_cipher = rb_hash_aref(opts, sym_sslcipher)) != Qnil) {
|
85
|
+
Check_Type(rb_ssl_cipher, T_STRING);
|
86
|
+
ssl_cipher = RSTRING_PTR(rb_ssl_cipher);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
if (!mysql_init(client)) {
|
91
|
+
// TODO: warning - not enough memory?
|
92
|
+
rb_raise(cMysql2Error, "%s", mysql_error(client));
|
93
|
+
return Qnil;
|
94
|
+
}
|
95
|
+
|
96
|
+
// set default reconnect behavior
|
97
|
+
if (mysql_options(client, MYSQL_OPT_RECONNECT, &reconnect) != 0) {
|
98
|
+
// TODO: warning - unable to set reconnect behavior
|
99
|
+
rb_warn("%s\n", mysql_error(client));
|
100
|
+
}
|
101
|
+
|
102
|
+
// set default connection timeout behavior
|
103
|
+
if (connect_timeout != 0 && mysql_options(client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout) != 0) {
|
104
|
+
// TODO: warning - unable to set connection timeout
|
105
|
+
rb_warn("%s\n", mysql_error(client));
|
106
|
+
}
|
107
|
+
|
108
|
+
// force the encoding to utf8
|
109
|
+
if (mysql_options(client, MYSQL_SET_CHARSET_NAME, "utf8") != 0) {
|
110
|
+
// TODO: warning - unable to set charset
|
111
|
+
rb_warn("%s\n", mysql_error(client));
|
112
|
+
}
|
113
|
+
|
114
|
+
if (ssl_ca_cert != NULL || ssl_client_key != NULL) {
|
115
|
+
mysql_ssl_set(client, ssl_client_key, ssl_client_cert, ssl_ca_cert, ssl_ca_path, ssl_cipher);
|
116
|
+
}
|
117
|
+
|
118
|
+
if (mysql_real_connect(client, host, username, password, database, port, socket, 0) == NULL) {
|
119
|
+
// unable to connect
|
120
|
+
rb_raise(cMysql2Error, "%s", mysql_error(client));
|
121
|
+
return Qnil;
|
122
|
+
}
|
123
|
+
|
124
|
+
rb_obj_call_init(obj, argc, argv);
|
125
|
+
return obj;
|
126
|
+
}
|
127
|
+
|
128
|
+
static VALUE rb_mysql_client_init(VALUE self, int argc, VALUE * argv) {
|
129
|
+
return self;
|
130
|
+
}
|
131
|
+
|
132
|
+
void rb_mysql_client_free(void * client) {
|
133
|
+
MYSQL * c = client;
|
134
|
+
if (c) {
|
135
|
+
mysql_close(client);
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
static VALUE rb_mysql_client_query(VALUE self, VALUE sql) {
|
140
|
+
MYSQL * client;
|
141
|
+
MYSQL_RES * result;
|
142
|
+
fd_set fdset;
|
143
|
+
int fd, retval;
|
144
|
+
Check_Type(sql, T_STRING);
|
145
|
+
|
146
|
+
GetMysql2Client(self, client);
|
147
|
+
if (mysql_send_query(client, RSTRING_PTR(sql), RSTRING_LEN(sql)) != 0) {
|
148
|
+
rb_raise(cMysql2Error, "%s", mysql_error(client));
|
149
|
+
return Qnil;
|
150
|
+
}
|
151
|
+
|
152
|
+
// the below code is largely from do_mysql
|
153
|
+
// http://github.com/datamapper/do
|
154
|
+
fd = client->net.fd;
|
155
|
+
for(;;) {
|
156
|
+
FD_ZERO(&fdset);
|
157
|
+
FD_SET(fd, &fdset);
|
158
|
+
|
159
|
+
retval = rb_thread_select(fd + 1, &fdset, NULL, NULL, NULL);
|
160
|
+
|
161
|
+
if (retval < 0) {
|
162
|
+
rb_sys_fail(0);
|
163
|
+
}
|
164
|
+
|
165
|
+
if (retval > 0) {
|
166
|
+
break;
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
if (mysql_read_query_result(client) != 0) {
|
171
|
+
rb_raise(cMysql2Error, "%s", mysql_error(client));
|
172
|
+
return Qnil;
|
173
|
+
}
|
174
|
+
|
175
|
+
result = mysql_store_result(client);
|
176
|
+
if (result == NULL) {
|
177
|
+
if (mysql_field_count(client) != 0) {
|
178
|
+
rb_raise(cMysql2Error, "%s", mysql_error(client));
|
179
|
+
}
|
180
|
+
return Qnil;
|
181
|
+
}
|
182
|
+
return rb_mysql_result_to_obj(result);
|
183
|
+
}
|
184
|
+
|
185
|
+
static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
|
186
|
+
MYSQL * client;
|
187
|
+
VALUE newStr;
|
188
|
+
unsigned long newLen, oldLen;
|
189
|
+
|
190
|
+
Check_Type(str, T_STRING);
|
191
|
+
oldLen = RSTRING_LEN(str);
|
192
|
+
char escaped[(oldLen*2)+1];
|
193
|
+
|
194
|
+
GetMysql2Client(self, client);
|
195
|
+
|
196
|
+
newLen = mysql_real_escape_string(client, escaped, RSTRING_PTR(str), RSTRING_LEN(str));
|
197
|
+
if (newLen == oldLen) {
|
198
|
+
// no need to return a new ruby string if nothing changed
|
199
|
+
return str;
|
200
|
+
} else {
|
201
|
+
newStr = rb_str_new(escaped, newLen);
|
202
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
203
|
+
rb_enc_associate_index(newStr, utf8Encoding);
|
204
|
+
#endif
|
205
|
+
return newStr;
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
static VALUE rb_mysql_client_info(VALUE self) {
|
210
|
+
VALUE version = rb_hash_new();
|
211
|
+
rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_client_version()));
|
212
|
+
rb_hash_aset(version, sym_version, rb_str_new2(mysql_get_client_info()));
|
213
|
+
return version;
|
214
|
+
}
|
215
|
+
|
216
|
+
static VALUE rb_mysql_client_server_info(VALUE self) {
|
217
|
+
MYSQL * client;
|
218
|
+
VALUE version;
|
219
|
+
|
220
|
+
GetMysql2Client(self, client);
|
221
|
+
version = rb_hash_new();
|
222
|
+
rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(client)));
|
223
|
+
rb_hash_aset(version, sym_version, rb_str_new2(mysql_get_server_info(client)));
|
224
|
+
return version;
|
225
|
+
}
|
226
|
+
|
227
|
+
static VALUE rb_mysql_client_socket(VALUE self) {
|
228
|
+
MYSQL * client = GetMysql2Client(self, client);;
|
229
|
+
return INT2NUM(client->net.fd);
|
230
|
+
}
|
231
|
+
|
232
|
+
/* Mysql2::Result */
|
233
|
+
static VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
|
234
|
+
VALUE obj;
|
235
|
+
obj = Data_Wrap_Struct(cMysql2Result, 0, rb_mysql_result_free, r);
|
236
|
+
rb_obj_call_init(obj, 0, NULL);
|
237
|
+
return obj;
|
238
|
+
}
|
239
|
+
|
240
|
+
void rb_mysql_result_free(void * result) {
|
241
|
+
MYSQL_RES * r = result;
|
242
|
+
if (r) {
|
243
|
+
mysql_free_result(r);
|
244
|
+
}
|
245
|
+
}
|
246
|
+
|
247
|
+
static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
|
248
|
+
VALUE rowHash, opts, block;
|
249
|
+
MYSQL_RES * result;
|
250
|
+
MYSQL_ROW row;
|
251
|
+
MYSQL_FIELD * fields;
|
252
|
+
struct tm parsedTime;
|
253
|
+
unsigned int i = 0, numFields = 0, symbolizeKeys = 0;
|
254
|
+
unsigned long * fieldLengths;
|
255
|
+
|
256
|
+
GetMysql2Result(self, result);
|
257
|
+
|
258
|
+
if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
|
259
|
+
Check_Type(opts, T_HASH);
|
260
|
+
if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) {
|
261
|
+
symbolizeKeys = 1;
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
row = mysql_fetch_row(result);
|
266
|
+
if (row == NULL) {
|
267
|
+
return Qnil;
|
268
|
+
}
|
269
|
+
|
270
|
+
numFields = mysql_num_fields(result);
|
271
|
+
fieldLengths = mysql_fetch_lengths(result);
|
272
|
+
fields = mysql_fetch_fields(result);
|
273
|
+
|
274
|
+
rowHash = rb_hash_new();
|
275
|
+
for (i = 0; i < numFields; i++) {
|
276
|
+
VALUE key;
|
277
|
+
if (symbolizeKeys) {
|
278
|
+
char buf[fields[i].name_length+1];
|
279
|
+
memcpy(buf, fields[i].name, fields[i].name_length);
|
280
|
+
buf[fields[i].name_length] = 0;
|
281
|
+
key = ID2SYM(rb_intern(buf));
|
282
|
+
} else {
|
283
|
+
key = rb_str_new(fields[i].name, fields[i].name_length);
|
284
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
285
|
+
rb_enc_associate_index(key, utf8Encoding);
|
286
|
+
#endif
|
287
|
+
}
|
288
|
+
if (row[i]) {
|
289
|
+
VALUE val;
|
290
|
+
switch(fields[i].type) {
|
291
|
+
case MYSQL_TYPE_NULL: // NULL-type field
|
292
|
+
val = Qnil;
|
293
|
+
break;
|
294
|
+
case MYSQL_TYPE_TINY: // TINYINT field
|
295
|
+
case MYSQL_TYPE_BIT: // BIT field (MySQL 5.0.3 and up)
|
296
|
+
case MYSQL_TYPE_SHORT: // SMALLINT field
|
297
|
+
case MYSQL_TYPE_LONG: // INTEGER field
|
298
|
+
case MYSQL_TYPE_INT24: // MEDIUMINT field
|
299
|
+
case MYSQL_TYPE_LONGLONG: // BIGINT field
|
300
|
+
case MYSQL_TYPE_YEAR: // YEAR field
|
301
|
+
val = rb_cstr2inum(row[i], 10);
|
302
|
+
break;
|
303
|
+
case MYSQL_TYPE_DECIMAL: // DECIMAL or NUMERIC field
|
304
|
+
case MYSQL_TYPE_NEWDECIMAL: // Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up)
|
305
|
+
val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(row[i], fieldLengths[i]));
|
306
|
+
break;
|
307
|
+
case MYSQL_TYPE_FLOAT: // FLOAT field
|
308
|
+
case MYSQL_TYPE_DOUBLE: // DOUBLE or REAL field
|
309
|
+
val = rb_float_new(strtod(row[i], NULL));
|
310
|
+
break;
|
311
|
+
case MYSQL_TYPE_TIME: // TIME field
|
312
|
+
if (memcmp("00:00:00", row[i], 10) == 0) {
|
313
|
+
val = rb_str_new(row[i], fieldLengths[i]);
|
314
|
+
} else {
|
315
|
+
strptime(row[i], "%T", &parsedTime);
|
316
|
+
val = rb_funcall(rb_cTime, intern_local, 6, INT2NUM(1900+parsedTime.tm_year), INT2NUM(parsedTime.tm_mon+1), INT2NUM(parsedTime.tm_mday), INT2NUM(parsedTime.tm_hour), INT2NUM(parsedTime.tm_min), INT2NUM(parsedTime.tm_sec));
|
317
|
+
}
|
318
|
+
break;
|
319
|
+
case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field
|
320
|
+
case MYSQL_TYPE_DATETIME: // DATETIME field
|
321
|
+
if (memcmp("0000-00-00 00:00:00", row[i], 19) == 0) {
|
322
|
+
val = Qnil;
|
323
|
+
} else {
|
324
|
+
strptime(row[i], "%F %T", &parsedTime);
|
325
|
+
val = rb_funcall(rb_cTime, intern_local, 6, INT2NUM(1900+parsedTime.tm_year), INT2NUM(parsedTime.tm_mon+1), INT2NUM(parsedTime.tm_mday), INT2NUM(parsedTime.tm_hour), INT2NUM(parsedTime.tm_min), INT2NUM(parsedTime.tm_sec));
|
326
|
+
}
|
327
|
+
break;
|
328
|
+
case MYSQL_TYPE_DATE: // DATE field
|
329
|
+
case MYSQL_TYPE_NEWDATE: // Newer const used > 5.0
|
330
|
+
if (memcmp("0000-00-00", row[i], 10) == 0) {
|
331
|
+
val = Qnil;
|
332
|
+
} else {
|
333
|
+
strptime(row[i], "%F", &parsedTime);
|
334
|
+
val = rb_funcall(rb_cTime, intern_local, 3, INT2NUM(1900+parsedTime.tm_year), INT2NUM(parsedTime.tm_mon+1), INT2NUM(parsedTime.tm_mday));
|
335
|
+
}
|
336
|
+
break;
|
337
|
+
case MYSQL_TYPE_TINY_BLOB:
|
338
|
+
case MYSQL_TYPE_MEDIUM_BLOB:
|
339
|
+
case MYSQL_TYPE_LONG_BLOB:
|
340
|
+
case MYSQL_TYPE_BLOB:
|
341
|
+
case MYSQL_TYPE_VAR_STRING:
|
342
|
+
case MYSQL_TYPE_VARCHAR:
|
343
|
+
case MYSQL_TYPE_STRING: // CHAR or BINARY field
|
344
|
+
case MYSQL_TYPE_SET: // SET field
|
345
|
+
case MYSQL_TYPE_ENUM: // ENUM field
|
346
|
+
case MYSQL_TYPE_GEOMETRY: // Spatial fielda
|
347
|
+
default:
|
348
|
+
val = rb_str_new(row[i], fieldLengths[i]);
|
349
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
350
|
+
// rudimentary check for binary content
|
351
|
+
if ((fields[i].flags & BINARY_FLAG) || fields[i].charsetnr == 63) {
|
352
|
+
rb_enc_associate_index(val, binaryEncoding);
|
353
|
+
} else {
|
354
|
+
rb_enc_associate_index(val, utf8Encoding);
|
355
|
+
}
|
356
|
+
#endif
|
357
|
+
break;
|
358
|
+
}
|
359
|
+
rb_hash_aset(rowHash, key, val);
|
360
|
+
} else {
|
361
|
+
rb_hash_aset(rowHash, key, Qnil);
|
362
|
+
}
|
363
|
+
}
|
364
|
+
return rowHash;
|
365
|
+
}
|
366
|
+
|
367
|
+
static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
|
368
|
+
VALUE dataset, opts, block;
|
369
|
+
MYSQL_RES * result;
|
370
|
+
unsigned long numRows, i;
|
371
|
+
|
372
|
+
GetMysql2Result(self, result);
|
373
|
+
|
374
|
+
rb_scan_args(argc, argv, "01&", &opts, &block);
|
375
|
+
|
376
|
+
// force-start at the beginning of the result set for proper
|
377
|
+
// behavior of #each
|
378
|
+
mysql_data_seek(result, 0);
|
379
|
+
|
380
|
+
numRows = mysql_num_rows(result);
|
381
|
+
if (numRows == 0) {
|
382
|
+
return Qnil;
|
383
|
+
}
|
384
|
+
|
385
|
+
// TODO: allow yielding datasets of configurable size
|
386
|
+
// like find_in_batches from AR...
|
387
|
+
if (block != Qnil) {
|
388
|
+
for (i = 0; i < numRows; i++) {
|
389
|
+
VALUE row = rb_mysql_result_fetch_row(argc, argv, self);
|
390
|
+
if (row == Qnil) {
|
391
|
+
return Qnil;
|
392
|
+
}
|
393
|
+
rb_yield(row);
|
394
|
+
}
|
395
|
+
} else {
|
396
|
+
dataset = rb_ary_new2(numRows);
|
397
|
+
for (i = 0; i < numRows; i++) {
|
398
|
+
VALUE row = rb_mysql_result_fetch_row(argc, argv, self);
|
399
|
+
if (row == Qnil) {
|
400
|
+
return Qnil;
|
401
|
+
}
|
402
|
+
rb_ary_store(dataset, i, row);
|
403
|
+
}
|
404
|
+
return dataset;
|
405
|
+
}
|
406
|
+
return Qnil;
|
407
|
+
}
|
408
|
+
|
409
|
+
/* Ruby Extension initializer */
|
410
|
+
void Init_mysql2_ext() {
|
411
|
+
rb_require("date");
|
412
|
+
rb_require("bigdecimal");
|
413
|
+
|
414
|
+
cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
|
415
|
+
cDate = rb_const_get(rb_cObject, rb_intern("Date"));
|
416
|
+
cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
|
417
|
+
|
418
|
+
VALUE mMysql2 = rb_define_module("Mysql2");
|
419
|
+
|
420
|
+
VALUE cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
|
421
|
+
rb_define_singleton_method(cMysql2Client, "new", rb_mysql_client_new, -1);
|
422
|
+
rb_define_method(cMysql2Client, "initialize", rb_mysql_client_init, -1);
|
423
|
+
rb_define_method(cMysql2Client, "query", rb_mysql_client_query, 1);
|
424
|
+
rb_define_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
|
425
|
+
rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
|
426
|
+
rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
|
427
|
+
rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
|
428
|
+
|
429
|
+
cMysql2Error = rb_define_class_under(mMysql2, "Error", rb_eStandardError);
|
430
|
+
|
431
|
+
cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
|
432
|
+
rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
|
433
|
+
|
434
|
+
VALUE mEnumerable = rb_const_get(rb_cObject, rb_intern("Enumerable"));
|
435
|
+
rb_include_module(cMysql2Result, mEnumerable);
|
436
|
+
|
437
|
+
intern_new = rb_intern("new");
|
438
|
+
intern_local = rb_intern("local");
|
439
|
+
|
440
|
+
sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
|
441
|
+
sym_reconnect = ID2SYM(rb_intern("reconnect"));
|
442
|
+
sym_database = ID2SYM(rb_intern("database"));
|
443
|
+
sym_username = ID2SYM(rb_intern("username"));
|
444
|
+
sym_password = ID2SYM(rb_intern("password"));
|
445
|
+
sym_host = ID2SYM(rb_intern("host"));
|
446
|
+
sym_port = ID2SYM(rb_intern("port"));
|
447
|
+
sym_socket = ID2SYM(rb_intern("socket"));
|
448
|
+
sym_connect_timeout = ID2SYM(rb_intern("connect_timeout"));
|
449
|
+
sym_id = ID2SYM(rb_intern("id"));
|
450
|
+
sym_version = ID2SYM(rb_intern("version"));
|
451
|
+
sym_sslkey = ID2SYM(rb_intern("sslkey"));
|
452
|
+
sym_sslcert = ID2SYM(rb_intern("sslcert"));
|
453
|
+
sym_sslca = ID2SYM(rb_intern("sslca"));
|
454
|
+
sym_sslcapath = ID2SYM(rb_intern("sslcapath"));
|
455
|
+
sym_sslcipher = ID2SYM(rb_intern("sslcipher"));
|
456
|
+
|
457
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
458
|
+
utf8Encoding = rb_enc_find_index("UTF-8");
|
459
|
+
binaryEncoding = rb_enc_find_index("binary");
|
460
|
+
#endif
|
461
|
+
}
|
data/ext/mysql2_ext.h
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#include <time.h>
|
2
|
+
#include <ruby.h>
|
3
|
+
|
4
|
+
#include <mysql/mysql.h>
|
5
|
+
#include <mysql/mysql_com.h>
|
6
|
+
#include <mysql/errmsg.h>
|
7
|
+
#include <mysql/mysqld_error.h>
|
8
|
+
|
9
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
10
|
+
#include <ruby/encoding.h>
|
11
|
+
int utf8Encoding, binaryEncoding;
|
12
|
+
#endif
|
13
|
+
|
14
|
+
static VALUE cBigDecimal, cDate, cDateTime;
|
15
|
+
ID intern_new, intern_local;
|
16
|
+
|
17
|
+
/* Mysql2::Error */
|
18
|
+
VALUE cMysql2Error;
|
19
|
+
|
20
|
+
/* Mysql2::Client */
|
21
|
+
#define GetMysql2Client(obj, sval) (sval = (MYSQL*)DATA_PTR(obj));
|
22
|
+
static ID sym_socket, sym_host, sym_port, sym_username, sym_password,
|
23
|
+
sym_database, sym_reconnect, sym_connect_timeout, sym_id, sym_version,
|
24
|
+
sym_sslkey, sym_sslcert, sym_sslca, sym_sslcapath, sym_sslcipher;
|
25
|
+
static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass);
|
26
|
+
static VALUE rb_mysql_client_init(VALUE self, int argc, VALUE * argv);
|
27
|
+
static VALUE rb_mysql_client_query(VALUE self, VALUE query);
|
28
|
+
static VALUE rb_mysql_client_escape(VALUE self, VALUE str);
|
29
|
+
static VALUE rb_mysql_client_info(VALUE self);
|
30
|
+
static VALUE rb_mysql_client_server_info(VALUE self);
|
31
|
+
static VALUE rb_mysql_client_socket(VALUE self);
|
32
|
+
void rb_mysql_client_free(void * client);
|
33
|
+
|
34
|
+
/* Mysql2::Result */
|
35
|
+
#define GetMysql2Result(obj, sval) (sval = (MYSQL_RES*)DATA_PTR(obj));
|
36
|
+
VALUE cMysql2Result;
|
37
|
+
static ID sym_symbolize_keys;
|
38
|
+
static VALUE rb_mysql_result_to_obj(MYSQL_RES * res);
|
39
|
+
static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self);
|
40
|
+
static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self);
|
41
|
+
void rb_mysql_result_free(void * result);
|
data/lib/mysql2.rb
ADDED
data/mysql2.gemspec
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{mysql2}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Brian Lopez"]
|
12
|
+
s.date = %q{2010-04-06}
|
13
|
+
s.email = %q{seniorlopez@gmail.com}
|
14
|
+
s.extensions = ["ext/extconf.rb"]
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"CHANGELOG.md",
|
21
|
+
"MIT-LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"benchmark/escape.rb",
|
26
|
+
"benchmark/query.rb",
|
27
|
+
"benchmark/setup_db.rb",
|
28
|
+
"ext/extconf.rb",
|
29
|
+
"ext/mysql2_ext.c",
|
30
|
+
"ext/mysql2_ext.h",
|
31
|
+
"lib/mysql2.rb",
|
32
|
+
"mysql2.gemspec",
|
33
|
+
"spec/mysql2/client_spec.rb",
|
34
|
+
"spec/mysql2/result_spec.rb",
|
35
|
+
"spec/rcov.opts",
|
36
|
+
"spec/spec.opts",
|
37
|
+
"spec/spec_helper.rb"
|
38
|
+
]
|
39
|
+
s.homepage = %q{http://github.com/brianmario/mysql2}
|
40
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
41
|
+
s.require_paths = ["lib", "ext"]
|
42
|
+
s.rubygems_version = %q{1.3.6}
|
43
|
+
s.summary = %q{A simple, fast Mysql library for Ruby, binding to libmysql}
|
44
|
+
s.test_files = [
|
45
|
+
"spec/mysql2/client_spec.rb",
|
46
|
+
"spec/mysql2/result_spec.rb",
|
47
|
+
"spec/spec_helper.rb"
|
48
|
+
]
|
49
|
+
|
50
|
+
if s.respond_to? :specification_version then
|
51
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
52
|
+
s.specification_version = 3
|
53
|
+
|
54
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
55
|
+
else
|
56
|
+
end
|
57
|
+
else
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
3
|
+
|
4
|
+
describe Mysql2::Client do
|
5
|
+
before(:each) do
|
6
|
+
@client = Mysql2::Client.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should be able to connect via SSL options" do
|
10
|
+
pending("DON'T WORRY, THIS TEST PASSES :) - but is machine-specific. You need to have MySQL running with SSL configured and enabled. Then update the paths in this test to your needs and remove the pending state.")
|
11
|
+
ssl_client = nil
|
12
|
+
lambda {
|
13
|
+
ssl_client = Mysql2::Client.new(
|
14
|
+
:sslkey => '/path/to/client-key.pem',
|
15
|
+
:sslcert => '/path/to/client-cert.pem',
|
16
|
+
:sslca => '/path/to/ca-cert.pem',
|
17
|
+
:sslcapath => '/path/to/newcerts/',
|
18
|
+
:sslcipher => 'DHE-RSA-AES256-SHA'
|
19
|
+
)
|
20
|
+
}.should_not raise_error(Mysql2::Error)
|
21
|
+
|
22
|
+
results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a
|
23
|
+
results[0]['Variable_name'].should eql('Ssl_cipher')
|
24
|
+
results[0]['Value'].should_not be_nil
|
25
|
+
results[0]['Value'].class.should eql(String)
|
26
|
+
|
27
|
+
results[1]['Variable_name'].should eql('Ssl_version')
|
28
|
+
results[1]['Value'].should_not be_nil
|
29
|
+
results[1]['Value'].class.should eql(String)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should respond to #query" do
|
33
|
+
@client.should respond_to :query
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should respond to #escape" do
|
37
|
+
@client.should respond_to :escape
|
38
|
+
end
|
39
|
+
|
40
|
+
it "#escape should return a new SQL-escape version of the passed string" do
|
41
|
+
@client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno")
|
42
|
+
end
|
43
|
+
|
44
|
+
it "#escape should return the passed string if nothing was escaped" do
|
45
|
+
str = "plain"
|
46
|
+
@client.escape(str).object_id.should eql(str.object_id)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should respond to #info" do
|
50
|
+
@client.should respond_to :info
|
51
|
+
end
|
52
|
+
|
53
|
+
it "#info should return a hash containing the client version ID and String" do
|
54
|
+
info = @client.info
|
55
|
+
info.class.should eql(Hash)
|
56
|
+
info.should have_key(:id)
|
57
|
+
info[:id].class.should eql(Fixnum)
|
58
|
+
info.should have_key(:version)
|
59
|
+
info[:version].class.should eql(String)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should respond to #server_info" do
|
63
|
+
@client.should respond_to :server_info
|
64
|
+
end
|
65
|
+
|
66
|
+
it "#server_info should return a hash containing the client version ID and String" do
|
67
|
+
server_info = @client.server_info
|
68
|
+
server_info.class.should eql(Hash)
|
69
|
+
server_info.should have_key(:id)
|
70
|
+
server_info[:id].class.should eql(Fixnum)
|
71
|
+
server_info.should have_key(:version)
|
72
|
+
server_info[:version].class.should eql(String)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should respond to #socket" do
|
76
|
+
@client.should respond_to :socket
|
77
|
+
end
|
78
|
+
|
79
|
+
it "#socket should return a Fixnum (file descriptor from C)" do
|
80
|
+
@client.socket.class.should eql(Fixnum)
|
81
|
+
@client.socket.should_not eql(0)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should raise a Mysql2::Error exception upon connection failure" do
|
85
|
+
lambda {
|
86
|
+
bad_client = Mysql2::Client.new :host => "dfjhdi9wrhw", :username => 'asdfasdf8d2h'
|
87
|
+
}.should raise_error(Mysql2::Error)
|
88
|
+
|
89
|
+
lambda {
|
90
|
+
good_client = Mysql2::Client.new
|
91
|
+
}.should_not raise_error(Mysql2::Error)
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
3
|
+
|
4
|
+
describe Mysql2::Result do
|
5
|
+
before(:all) do
|
6
|
+
@client = Mysql2::Client.new :host => "localhost", :username => "root"
|
7
|
+
end
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
@result = @client.query "SELECT 1"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have included Enumerable" do
|
14
|
+
Mysql2::Result.ancestors.include?(Enumerable).should be_true
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should respond to #each" do
|
18
|
+
@result.should respond_to :each
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should raise a Mysql2::Error exception upon a bad query" do
|
22
|
+
lambda {
|
23
|
+
@client.query "bad sql"
|
24
|
+
}.should raise_error(Mysql2::Error)
|
25
|
+
|
26
|
+
lambda {
|
27
|
+
@client.query "SELECT 1"
|
28
|
+
}.should_not raise_error(Mysql2::Error)
|
29
|
+
end
|
30
|
+
|
31
|
+
context "#each" do
|
32
|
+
it "should yield rows as hash's" do
|
33
|
+
@result.each do |row|
|
34
|
+
row.class.should eql(Hash)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do
|
39
|
+
@result.each(:symbolize_keys => true) do |row|
|
40
|
+
row.class.should eql(Hash)
|
41
|
+
row.keys.first.class.should eql(Symbol)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "row data type mapping" do
|
47
|
+
before(:all) do
|
48
|
+
@client.query "USE test"
|
49
|
+
@client.query %[
|
50
|
+
CREATE TABLE IF NOT EXISTS mysql2_test (
|
51
|
+
null_test VARCHAR(10),
|
52
|
+
bit_test BIT,
|
53
|
+
tiny_int_test TINYINT,
|
54
|
+
small_int_test SMALLINT,
|
55
|
+
medium_int_test MEDIUMINT,
|
56
|
+
int_test INT,
|
57
|
+
big_int_test BIGINT,
|
58
|
+
float_test FLOAT(10,3),
|
59
|
+
double_test DOUBLE(10,3),
|
60
|
+
decimal_test DECIMAL(10,3),
|
61
|
+
date_test DATE,
|
62
|
+
date_time_test DATETIME,
|
63
|
+
timestamp_test TIMESTAMP,
|
64
|
+
time_test TIME,
|
65
|
+
year_test YEAR(4),
|
66
|
+
char_test CHAR(10),
|
67
|
+
varchar_test VARCHAR(10),
|
68
|
+
binary_test BINARY(10),
|
69
|
+
varbinary_test VARBINARY(10),
|
70
|
+
tiny_blob_test TINYBLOB,
|
71
|
+
tiny_text_test TINYTEXT,
|
72
|
+
blob_test BLOB,
|
73
|
+
text_test TEXT,
|
74
|
+
medium_blob_test MEDIUMBLOB,
|
75
|
+
medium_text_test MEDIUMTEXT,
|
76
|
+
long_blob_test LONGBLOB,
|
77
|
+
long_text_test LONGTEXT,
|
78
|
+
enum_test ENUM('val1', 'val2'),
|
79
|
+
set_test SET('val1', 'val2')
|
80
|
+
)
|
81
|
+
]
|
82
|
+
@client.query %[
|
83
|
+
INSERT INTO mysql2_test (
|
84
|
+
null_test, bit_test, tiny_int_test, small_int_test, medium_int_test, int_test, big_int_test,
|
85
|
+
float_test, double_test, decimal_test, date_test, date_time_test, timestamp_test, time_test,
|
86
|
+
year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test,
|
87
|
+
tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test,
|
88
|
+
long_blob_test, long_text_test, enum_test, set_test
|
89
|
+
)
|
90
|
+
|
91
|
+
VALUES (
|
92
|
+
NULL, 1, 1, 10, 10, 10, 10,
|
93
|
+
10.3, 10.3, 10.3, '2010-4-4', '2010-4-4 11:44:00', '2010-4-4 11:44:00', '11:44:00',
|
94
|
+
2009, "test", "test", "test", "test", "test",
|
95
|
+
"test", "test", "test", "test", "test",
|
96
|
+
"test", "test", 'val1', 'val1,val2'
|
97
|
+
)
|
98
|
+
]
|
99
|
+
@test_result = @client.query("SELECT * FROM mysql2_test LIMIT 1").first
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should return nil for a NULL value" do
|
103
|
+
@test_result['null_test'].class.should eql(NilClass)
|
104
|
+
@test_result['null_test'].should eql(nil)
|
105
|
+
end
|
106
|
+
|
107
|
+
{
|
108
|
+
'bit_test' => 'BIT',
|
109
|
+
'tiny_int_test' => 'TINYINT',
|
110
|
+
'small_int_test' => 'SMALLINT',
|
111
|
+
'medium_int_test' => 'MEDIUMINT',
|
112
|
+
'int_test' => 'INT',
|
113
|
+
'big_int_test' => 'BIGINT',
|
114
|
+
'year_test' => 'YEAR'
|
115
|
+
}.each do |field, type|
|
116
|
+
it "should return a Fixnum for #{type}" do
|
117
|
+
[Fixnum, Bignum].should include(@test_result[field].class)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
{
|
122
|
+
'decimal_test' => 'DECIMAL'
|
123
|
+
}.each do |field, type|
|
124
|
+
it "should return a Fixnum for #{type}" do
|
125
|
+
@test_result[field].class.should eql(BigDecimal)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
{
|
130
|
+
'float_test' => 'FLOAT',
|
131
|
+
'double_test' => 'DOUBLE'
|
132
|
+
}.each do |field, type|
|
133
|
+
it "should return a Float for #{type}" do
|
134
|
+
@test_result[field].class.should eql(Float)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
{
|
139
|
+
'date_test' => 'DATE',
|
140
|
+
'date_time_test' => 'DATETIME',
|
141
|
+
'timestamp_test' => 'TIMESTAMP',
|
142
|
+
'time_test' => 'TIME'
|
143
|
+
}.each do |field, type|
|
144
|
+
it "should return a Time for #{type}" do
|
145
|
+
@test_result[field].class.should eql(Time)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
{
|
150
|
+
'char_test' => 'CHAR',
|
151
|
+
'varchar_test' => 'VARCHAR',
|
152
|
+
'binary_test' => 'BINARY',
|
153
|
+
'varbinary_test' => 'VARBINARY',
|
154
|
+
'tiny_blob_test' => 'TINYBLOB',
|
155
|
+
'tiny_text_test' => 'TINYTEXT',
|
156
|
+
'blob_test' => 'BLOB',
|
157
|
+
'text_test' => 'TEXT',
|
158
|
+
'medium_blob_test' => 'MEDIUMBLOB',
|
159
|
+
'medium_text_test' => 'MEDIUMTEXT',
|
160
|
+
'long_blob_test' => 'LONGBLOB',
|
161
|
+
'long_text_test' => 'LONGTEXT',
|
162
|
+
'enum_test' => 'ENUM',
|
163
|
+
'set_test' => 'SET'
|
164
|
+
}.each do |field, type|
|
165
|
+
it "should return a String for #{type}" do
|
166
|
+
@test_result[field].class.should eql(String)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
data/spec/rcov.opts
ADDED
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mysql2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Brian Lopez
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-04-06 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email: seniorlopez@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions:
|
26
|
+
- ext/extconf.rb
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.rdoc
|
29
|
+
files:
|
30
|
+
- .gitignore
|
31
|
+
- CHANGELOG.md
|
32
|
+
- MIT-LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
- Rakefile
|
35
|
+
- VERSION
|
36
|
+
- benchmark/escape.rb
|
37
|
+
- benchmark/query.rb
|
38
|
+
- benchmark/setup_db.rb
|
39
|
+
- ext/extconf.rb
|
40
|
+
- ext/mysql2_ext.c
|
41
|
+
- ext/mysql2_ext.h
|
42
|
+
- lib/mysql2.rb
|
43
|
+
- mysql2.gemspec
|
44
|
+
- spec/mysql2/client_spec.rb
|
45
|
+
- spec/mysql2/result_spec.rb
|
46
|
+
- spec/rcov.opts
|
47
|
+
- spec/spec.opts
|
48
|
+
- spec/spec_helper.rb
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: http://github.com/brianmario/mysql2
|
51
|
+
licenses: []
|
52
|
+
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options:
|
55
|
+
- --charset=UTF-8
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
- ext
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.3.6
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: A simple, fast Mysql library for Ruby, binding to libmysql
|
80
|
+
test_files:
|
81
|
+
- spec/mysql2/client_spec.rb
|
82
|
+
- spec/mysql2/result_spec.rb
|
83
|
+
- spec/spec_helper.rb
|