mysql2 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|