mysql2-0.3.14/0000755000004100000410000000000012245130153013052 5ustar www-datawww-datamysql2-0.3.14/examples/0000755000004100000410000000000012245130153014670 5ustar www-datawww-datamysql2-0.3.14/examples/eventmachine.rb0000644000004100000410000000071612245130153017667 0ustar www-datawww-data# encoding: utf-8 $LOAD_PATH.unshift 'lib' require 'rubygems' require 'eventmachine' require 'mysql2/em' EM.run do client1 = Mysql2::EM::Client.new defer1 = client1.query "SELECT sleep(3) as first_query" defer1.callback do |result| puts "Result: #{result.to_a.inspect}" end client2 = Mysql2::EM::Client.new defer2 = client2.query "SELECT sleep(1) second_query" defer2.callback do |result| puts "Result: #{result.to_a.inspect}" end endmysql2-0.3.14/examples/threaded.rb0000644000004100000410000000114212245130153016773 0ustar www-datawww-data# encoding: utf-8 $LOAD_PATH.unshift 'lib' require 'mysql2' require 'timeout' threads = [] # Should never exceed worst case 3.5 secs across all 20 threads Timeout.timeout(3.5) do 20.times do threads << Thread.new do overhead = rand(3) puts ">> thread #{Thread.current.object_id} query, #{overhead} sec overhead" # 3 second overhead per query Mysql2::Client.new(:host => "localhost", :username => "root").query("SELECT sleep(#{overhead}) as result") puts "<< thread #{Thread.current.object_id} result, #{overhead} sec overhead" end end threads.each{|t| t.join } endmysql2-0.3.14/MIT-LICENSE0000644000004100000410000000210212245130153014501 0ustar www-datawww-dataCopyright (c) 2010-2011 Brian Lopez - http://github.com/brianmario Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.mysql2-0.3.14/spec/0000755000004100000410000000000012245130153014004 5ustar www-datawww-datamysql2-0.3.14/spec/rcov.opts0000644000004100000410000000010112245130153015654 0ustar www-datawww-data--exclude spec,gem --text-summary --sort coverage --sort-reverse mysql2-0.3.14/spec/em/0000755000004100000410000000000012245130153014405 5ustar www-datawww-datamysql2-0.3.14/spec/em/em_spec.rb0000644000004100000410000000700412245130153016346 0ustar www-datawww-data# encoding: UTF-8 require 'spec_helper' begin require 'eventmachine' require 'mysql2/em' describe Mysql2::EM::Client do it "should support async queries" do results = [] EM.run do client1 = Mysql2::EM::Client.new DatabaseCredentials['root'] defer1 = client1.query "SELECT sleep(0.1) as first_query" defer1.callback do |result| results << result.first client1.close EM.stop_event_loop end client2 = Mysql2::EM::Client.new DatabaseCredentials['root'] defer2 = client2.query "SELECT sleep(0.025) second_query" defer2.callback do |result| results << result.first client2.close end end results[0].keys.should include("second_query") results[1].keys.should include("first_query") end it "should support queries in callbacks" do results = [] EM.run do client = Mysql2::EM::Client.new DatabaseCredentials['root'] defer1 = client.query "SELECT sleep(0.025) as first_query" defer1.callback do |result| results << result.first defer2 = client.query "SELECT sleep(0.025) as second_query" defer2.callback do |r| results << r.first client.close EM.stop_event_loop end end end results[0].keys.should include("first_query") results[1].keys.should include("second_query") end it "should not swallow exceptions raised in callbacks" do lambda { EM.run do client = Mysql2::EM::Client.new DatabaseCredentials['root'] defer = client.query "SELECT sleep(0.1) as first_query" defer.callback do |result| client.close raise 'some error' end defer.errback do |err| # This _shouldn't_ be run, but it needed to prevent the specs from # freezing if this test fails. EM.stop_event_loop end end }.should raise_error end context 'when an exception is raised by the client' do let(:client) { Mysql2::EM::Client.new DatabaseCredentials['root'] } let(:error) { StandardError.new('some error') } before { client.stub(:async_result).and_raise(error) } it "should swallow exceptions raised in by the client" do errors = [] EM.run do defer = client.query "SELECT sleep(0.1) as first_query" defer.callback do |result| # This _shouldn't_ be run, but it is needed to prevent the specs from # freezing if this test fails. EM.stop_event_loop end defer.errback do |err| errors << err EM.stop_event_loop end end errors.should == [error] end it "should fail the deferrable" do callbacks_run = [] EM.run do defer = client.query "SELECT sleep(0.025) as first_query" EM.add_timer(0.1) do defer.callback do |result| callbacks_run << :callback # This _shouldn't_ be run, but it is needed to prevent the specs from # freezing if this test fails. EM.stop_event_loop end defer.errback do |err| callbacks_run << :errback EM.stop_event_loop end end end callbacks_run.should == [:errback] end end end rescue LoadError puts "EventMachine not installed, skipping the specs that use it" end mysql2-0.3.14/spec/spec_helper.rb0000644000004100000410000000477412245130153016636 0ustar www-datawww-data# encoding: UTF-8 require 'rspec' require 'mysql2' require 'timeout' require 'yaml' DatabaseCredentials = YAML.load_file('spec/configuration.yml') RSpec.configure do |config| config.before :each do @client = Mysql2::Client.new DatabaseCredentials['root'] end config.after :each do @client.close end config.before(:all) do client = Mysql2::Client.new DatabaseCredentials['root'] client.query %[ CREATE TABLE IF NOT EXISTS mysql2_test ( id MEDIUMINT NOT NULL AUTO_INCREMENT, null_test VARCHAR(10), bit_test BIT(64), single_bit_test BIT(1), tiny_int_test TINYINT, bool_cast_test TINYINT(1), small_int_test SMALLINT, medium_int_test MEDIUMINT, int_test INT, big_int_test BIGINT, float_test FLOAT(10,3), float_zero_test FLOAT(10,3), double_test DOUBLE(10,3), decimal_test DECIMAL(10,3), decimal_zero_test DECIMAL(10,3), date_test DATE, date_time_test DATETIME, timestamp_test TIMESTAMP, time_test TIME, year_test YEAR(4), char_test CHAR(10), varchar_test VARCHAR(10), binary_test BINARY(10), varbinary_test VARBINARY(10), tiny_blob_test TINYBLOB, tiny_text_test TINYTEXT, blob_test BLOB, text_test TEXT, medium_blob_test MEDIUMBLOB, medium_text_test MEDIUMTEXT, long_blob_test LONGBLOB, long_text_test LONGTEXT, enum_test ENUM('val1', 'val2'), set_test SET('val1', 'val2'), PRIMARY KEY (id) ) ] client.query "DELETE FROM mysql2_test;" client.query %[ INSERT INTO mysql2_test ( null_test, bit_test, single_bit_test, tiny_int_test, bool_cast_test, small_int_test, medium_int_test, int_test, big_int_test, float_test, float_zero_test, double_test, decimal_test, decimal_zero_test, date_test, date_time_test, timestamp_test, time_test, year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test, tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test, long_blob_test, long_text_test, enum_test, set_test ) VALUES ( NULL, b'101', b'1', 1, 1, 10, 10, 10, 10, 10.3, 0, 10.3, 10.3, 0, '2010-4-4', '2010-4-4 11:44:00', '2010-4-4 11:44:00', '11:44:00', 2009, "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", 'val1', 'val1,val2' ) ] end end mysql2-0.3.14/spec/my.cnf.example0000644000004100000410000000015112245130153016550 0ustar www-datawww-data[root] host=localhost user=LOCALUSERNAME password= [client] host=localhost user=LOCALUSERNAME password= mysql2-0.3.14/spec/configuration.yml.example0000644000004100000410000000036512245130153021034 0ustar www-datawww-dataroot: host: localhost username: root password: database: test user: host: localhost username: LOCALUSERNAME password: database: mysql2_test numericuser: host: localhost username: LOCALUSERNAME password: database: 12345 mysql2-0.3.14/spec/mysql2/0000755000004100000410000000000012245130153015233 5ustar www-datawww-datamysql2-0.3.14/spec/mysql2/error_spec.rb0000644000004100000410000000443012245130153017724 0ustar www-datawww-data# encoding: UTF-8 require 'spec_helper' describe Mysql2::Error do before(:each) do begin @err_client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) @err_client.query("HAHAHA") rescue Mysql2::Error => e @error = e ensure @err_client.close end begin @err_client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) @err_client2.query("HAHAHA") rescue Mysql2::Error => e @error2 = e ensure @err_client2.close end end it "should respond to #error_number" do @error.should respond_to(:error_number) end it "should respond to #sql_state" do @error.should respond_to(:sql_state) end # Mysql gem compatibility it "should alias #error_number to #errno" do @error.should respond_to(:errno) end it "should alias #message to #error" do @error.should respond_to(:error) end unless RUBY_VERSION =~ /1.8/ it "#message encoding should match the connection's encoding, or Encoding.default_internal if set" do if Encoding.default_internal.nil? @error.message.encoding.should eql(@err_client.encoding) @error2.message.encoding.should eql(@err_client2.encoding) else @error.message.encoding.should eql(Encoding.default_internal) @error2.message.encoding.should eql(Encoding.default_internal) end end it "#error encoding should match the connection's encoding, or Encoding.default_internal if set" do if Encoding.default_internal.nil? @error.error.encoding.should eql(@err_client.encoding) @error2.error.encoding.should eql(@err_client2.encoding) else @error.error.encoding.should eql(Encoding.default_internal) @error2.error.encoding.should eql(Encoding.default_internal) end end it "#sql_state encoding should match the connection's encoding, or Encoding.default_internal if set" do if Encoding.default_internal.nil? @error.sql_state.encoding.should eql(@err_client.encoding) @error2.sql_state.encoding.should eql(@err_client2.encoding) else @error.sql_state.encoding.should eql(Encoding.default_internal) @error2.sql_state.encoding.should eql(Encoding.default_internal) end end end end mysql2-0.3.14/spec/mysql2/result_spec.rb0000644000004100000410000004413012245130153020112 0ustar www-datawww-data# encoding: UTF-8 require 'spec_helper' describe Mysql2::Result do before(:each) do @result = @client.query "SELECT 1" end it "should maintain a count while streaming" do result = @client.query('SELECT 1') result.count.should eql(1) result.each.to_a result.count.should eql(1) end it "should set the actual count of rows after streaming" do @client.query "USE test" result = @client.query("SELECT * FROM mysql2_test", :stream => true, :cache_rows => false) result.count.should eql(0) result.each {|r| } result.count.should eql(1) end it "should not yield nil at the end of streaming" do result = @client.query('SELECT * FROM mysql2_test', :stream => true) result.each { |r| r.should_not be_nil} end it "#count should be zero for rows after streaming when there were no results " do @client.query "USE test" result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", :stream => true, :cache_rows => false) result.count.should eql(0) result.each.to_a result.count.should eql(0) end it "should have included Enumerable" do Mysql2::Result.ancestors.include?(Enumerable).should be_true end it "should respond to #each" do @result.should respond_to(:each) end it "should raise a Mysql2::Error exception upon a bad query" do lambda { @client.query "bad sql" }.should raise_error(Mysql2::Error) lambda { @client.query "SELECT 1" }.should_not raise_error(Mysql2::Error) end it "should respond to #count, which is aliased as #size" do r = @client.query "SELECT 1" r.should respond_to :count r.should respond_to :size end it "should be able to return the number of rows in the result set" do r = @client.query "SELECT 1" r.count.should eql(1) r.size.should eql(1) end context "metadata queries" do it "should show tables" do @result = @client.query "SHOW TABLES" end end context "#each" do it "should yield rows as hash's" do @result.each do |row| row.class.should eql(Hash) end end it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do @result.each(:symbolize_keys => true) do |row| row.keys.first.class.should eql(Symbol) end end it "should be able to return results as an array" do @result.each(:as => :array) do |row| row.class.should eql(Array) end end it "should cache previously yielded results by default" do @result.first.object_id.should eql(@result.first.object_id) end it "should not cache previously yielded results if cache_rows is disabled" do result = @client.query "SELECT 1", :cache_rows => false result.first.object_id.should_not eql(result.first.object_id) end it "should yield different value for #first if streaming" do result = @client.query "SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false result.first.should_not eql(result.first) end it "should yield the same value for #first if streaming is disabled" do result = @client.query "SELECT 1 UNION SELECT 2", :stream => false result.first.should eql(result.first) end it "should throw an exception if we try to iterate twice when streaming is enabled" do result = @client.query "SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false expect { result.each.to_a result.each.to_a }.to raise_exception(Mysql2::Error) end end context "#fields" do before(:each) do @client.query "USE test" @test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1") end it "method should exist" do @test_result.should respond_to(:fields) end it "should return an array of field names in proper order" do result = @client.query "SELECT 'a', 'b', 'c'" result.fields.should eql(['a', 'b', 'c']) end end context "row data type mapping" do before(:each) do @client.query "USE test" @test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first end it "should return nil values for NULL and strings for everything else when :cast is false" do result = @client.query('SELECT null_test, tiny_int_test, bool_cast_test, int_test, date_test, enum_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast => false).first result["null_test"].should be_nil result["tiny_int_test"].should eql("1") result["bool_cast_test"].should eql("1") result["int_test"].should eql("10") result["date_test"].should eql("2010-04-04") result["enum_test"].should eql("val1") end it "should return nil for a NULL value" do @test_result['null_test'].class.should eql(NilClass) @test_result['null_test'].should eql(nil) end it "should return String for a BIT(64) value" do @test_result['bit_test'].class.should eql(String) @test_result['bit_test'].should eql("\000\000\000\000\000\000\000\005") end it "should return String for a BIT(1) value" do @test_result['single_bit_test'].class.should eql(String) @test_result['single_bit_test'].should eql("\001") end it "should return Fixnum for a TINYINT value" do [Fixnum, Bignum].should include(@test_result['tiny_int_test'].class) @test_result['tiny_int_test'].should eql(1) end it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (1)' id1 = @client.last_id @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (0)' id2 = @client.last_id @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)' id3 = @client.last_id result1 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast_booleans => true result2 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 0 LIMIT 1', :cast_booleans => true result3 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = -1 LIMIT 1', :cast_booleans => true result1.first['bool_cast_test'].should be_true result2.first['bool_cast_test'].should be_false result3.first['bool_cast_test'].should be_true @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})" end it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)' id1 = @client.last_id @client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)' id2 = @client.last_id result1 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id1}", :cast_booleans => true result2 = @client.query "SELECT single_bit_test FROM mysql2_test WHERE id = #{id2}", :cast_booleans => true result1.first['single_bit_test'].should be_true result2.first['single_bit_test'].should be_false @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" end it "should return Fixnum for a SMALLINT value" do [Fixnum, Bignum].should include(@test_result['small_int_test'].class) @test_result['small_int_test'].should eql(10) end it "should return Fixnum for a MEDIUMINT value" do [Fixnum, Bignum].should include(@test_result['medium_int_test'].class) @test_result['medium_int_test'].should eql(10) end it "should return Fixnum for an INT value" do [Fixnum, Bignum].should include(@test_result['int_test'].class) @test_result['int_test'].should eql(10) end it "should return Fixnum for a BIGINT value" do [Fixnum, Bignum].should include(@test_result['big_int_test'].class) @test_result['big_int_test'].should eql(10) end it "should return Fixnum for a YEAR value" do [Fixnum, Bignum].should include(@test_result['year_test'].class) @test_result['year_test'].should eql(2009) end it "should return BigDecimal for a DECIMAL value" do @test_result['decimal_test'].class.should eql(BigDecimal) @test_result['decimal_test'].should eql(10.3) end it "should return Float for a FLOAT value" do @test_result['float_test'].class.should eql(Float) @test_result['float_test'].should eql(10.3) end it "should return Float for a DOUBLE value" do @test_result['double_test'].class.should eql(Float) @test_result['double_test'].should eql(10.3) end it "should return Time for a DATETIME value when within the supported range" do @test_result['date_time_test'].class.should eql(Time) @test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S").should eql('2010-04-04 11:44:00') end if 1.size == 4 # 32bit unless RUBY_VERSION =~ /1.8/ klass = Time else klass = DateTime end it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8 r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") r.first['test'].class.should eql(klass) end it "should return DateTime when timestamp is > 2038-01-19T03:14:07" do # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8 r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") r.first['test'].class.should eql(klass) end elsif 1.size == 8 # 64bit unless RUBY_VERSION =~ /1.8/ it "should return Time when timestamp is < 1901-12-13 20:45:52" do r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test") r.first['test'].class.should eql(Time) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") r.first['test'].class.should eql(Time) end else it "should return Time when timestamp is > 0138-12-31 11:59:59" do r = @client.query("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test") r.first['test'].class.should eql(Time) end it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do r = @client.query("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test") r.first['test'].class.should eql(DateTime) end it "should return Time when timestamp is > 2038-01-19T03:14:07" do r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test") r.first['test'].class.should eql(Time) end end end it "should return Time for a TIMESTAMP value when within the supported range" do @test_result['timestamp_test'].class.should eql(Time) @test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S").should eql('2010-04-04 11:44:00') end it "should return Time for a TIME value" do @test_result['time_test'].class.should eql(Time) @test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S").should eql('2000-01-01 11:44:00') end it "should return Date for a DATE value" do @test_result['date_test'].class.should eql(Date) @test_result['date_test'].strftime("%Y-%m-%d").should eql('2010-04-04') end it "should return String for an ENUM value" do @test_result['enum_test'].class.should eql(String) @test_result['enum_test'].should eql('val1') end if defined? Encoding context "string encoding for ENUM values" do it "should default to the connection's encoding if Encoding.default_internal is nil" do Encoding.default_internal = nil result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['enum_test'].encoding.should eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) client2.query "USE test" result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['enum_test'].encoding.should eql(Encoding.find('us-ascii')) client2.close end it "should use Encoding.default_internal" do Encoding.default_internal = Encoding.find('utf-8') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['enum_test'].encoding.should eql(Encoding.default_internal) Encoding.default_internal = Encoding.find('us-ascii') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['enum_test'].encoding.should eql(Encoding.default_internal) end end end it "should return String for a SET value" do @test_result['set_test'].class.should eql(String) @test_result['set_test'].should eql('val1,val2') end if defined? Encoding context "string encoding for SET values" do it "should default to the connection's encoding if Encoding.default_internal is nil" do Encoding.default_internal = nil result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['set_test'].encoding.should eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) client2.query "USE test" result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['set_test'].encoding.should eql(Encoding.find('us-ascii')) client2.close end it "should use Encoding.default_internal" do Encoding.default_internal = Encoding.find('utf-8') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['set_test'].encoding.should eql(Encoding.default_internal) Encoding.default_internal = Encoding.find('us-ascii') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['set_test'].encoding.should eql(Encoding.default_internal) end end end it "should return String for a BINARY value" do @test_result['binary_test'].class.should eql(String) @test_result['binary_test'].should eql("test#{"\000"*6}") end if defined? Encoding context "string encoding for BINARY values" do it "should default to binary if Encoding.default_internal is nil" do Encoding.default_internal = nil result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['binary_test'].encoding.should eql(Encoding.find('binary')) end it "should not use Encoding.default_internal" do Encoding.default_internal = Encoding.find('utf-8') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['binary_test'].encoding.should eql(Encoding.find('binary')) Encoding.default_internal = Encoding.find('us-ascii') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['binary_test'].encoding.should eql(Encoding.find('binary')) end end end { 'char_test' => 'CHAR', 'varchar_test' => 'VARCHAR', 'varbinary_test' => 'VARBINARY', 'tiny_blob_test' => 'TINYBLOB', 'tiny_text_test' => 'TINYTEXT', 'blob_test' => 'BLOB', 'text_test' => 'TEXT', 'medium_blob_test' => 'MEDIUMBLOB', 'medium_text_test' => 'MEDIUMTEXT', 'long_blob_test' => 'LONGBLOB', 'long_text_test' => 'LONGTEXT' }.each do |field, type| it "should return a String for #{type}" do @test_result[field].class.should eql(String) @test_result[field].should eql("test") end if defined? Encoding context "string encoding for #{type} values" do if ['VARBINARY', 'TINYBLOB', 'BLOB', 'MEDIUMBLOB', 'LONGBLOB'].include?(type) it "should default to binary if Encoding.default_internal is nil" do Encoding.default_internal = nil result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['binary_test'].encoding.should eql(Encoding.find('binary')) end it "should not use Encoding.default_internal" do Encoding.default_internal = Encoding.find('utf-8') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['binary_test'].encoding.should eql(Encoding.find('binary')) Encoding.default_internal = Encoding.find('us-ascii') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result['binary_test'].encoding.should eql(Encoding.find('binary')) end else it "should default to utf-8 if Encoding.default_internal is nil" do Encoding.default_internal = nil result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result[field].encoding.should eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) client2.query "USE test" result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result[field].encoding.should eql(Encoding.find('us-ascii')) client2.close end it "should use Encoding.default_internal" do Encoding.default_internal = Encoding.find('utf-8') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result[field].encoding.should eql(Encoding.default_internal) Encoding.default_internal = Encoding.find('us-ascii') result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first result[field].encoding.should eql(Encoding.default_internal) end end end end end end end mysql2-0.3.14/spec/mysql2/client_spec.rb0000644000004100000410000006021212245130153020051 0ustar www-datawww-data# encoding: UTF-8 require 'spec_helper' describe Mysql2::Client do context "using defaults file" do let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) } it "should not raise an exception for valid defaults group" do lambda { @client = Mysql2::Client.new(:default_file => cnf_file, :default_group => "test") }.should_not raise_error(Mysql2::Error) end it "should not raise an exception without default group" do lambda { @client = Mysql2::Client.new(:default_file => cnf_file) }.should_not raise_error(Mysql2::Error) end end it "should raise an exception upon connection failure" do lambda { # The odd local host IP address forces the mysql client library to # use a TCP socket rather than a domain socket. Mysql2::Client.new DatabaseCredentials['root'].merge('host' => '127.0.0.2', 'port' => 999999) }.should raise_error(Mysql2::Error) end if defined? Encoding it "should raise an exception on create for invalid encodings" do lambda { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "fake")) }.should raise_error(Mysql2::Error) end it "should not raise an exception on create for a valid encoding" do lambda { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) }.should_not raise_error(Mysql2::Error) lambda { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) }.should_not raise_error(Mysql2::Error) end end it "should accept connect flags and pass them to #connect" do klient = Class.new(Mysql2::Client) do attr_reader :connect_args def connect *args @connect_args ||= [] @connect_args << args end end client = klient.new :flags => Mysql2::Client::FOUND_ROWS (client.connect_args.last.last & Mysql2::Client::FOUND_ROWS).should be_true end it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do klient = Class.new(Mysql2::Client) do attr_reader :connect_args def connect *args @connect_args ||= [] @connect_args << args end end client = klient.new (client.connect_args.last.last & (Mysql2::Client::REMEMBER_OPTIONS | Mysql2::Client::LONG_PASSWORD | Mysql2::Client::LONG_FLAG | Mysql2::Client::TRANSACTIONS | Mysql2::Client::PROTOCOL_41 | Mysql2::Client::SECURE_CONNECTION)).should be_true end it "should have a global default_query_options hash" do Mysql2::Client.should respond_to(:default_query_options) end it "should be able to connect via SSL options" do 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.") ssl_client = nil lambda { ssl_client = Mysql2::Client.new( :sslkey => '/path/to/client-key.pem', :sslcert => '/path/to/client-cert.pem', :sslca => '/path/to/ca-cert.pem', :sslcapath => '/path/to/newcerts/', :sslcipher => 'DHE-RSA-AES256-SHA' ) }.should_not raise_error(Mysql2::Error) results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a results[0]['Variable_name'].should eql('Ssl_cipher') results[0]['Value'].should_not be_nil results[0]['Value'].should be_kind_of(String) results[0]['Value'].should_not be_empty results[1]['Variable_name'].should eql('Ssl_version') results[1]['Value'].should_not be_nil results[1]['Value'].should be_kind_of(String) results[1]['Value'].should_not be_empty ssl_client.close end it "should be able to connect to database with numeric-only name" do lambda { creds = DatabaseCredentials['numericuser'] @client.query "CREATE DATABASE IF NOT EXISTS `#{creds['database']}`" @client.query "GRANT ALL ON `#{creds['database']}`.* TO #{creds['username']}@`#{creds['host']}`" client = Mysql2::Client.new creds @client.query "DROP DATABASE IF EXISTS `#{creds['database']}`" }.should_not raise_error end it "should respond to #close" do @client.should respond_to(:close) end it "should be able to close properly" do @client.close.should be_nil lambda { @client.query "SELECT 1" }.should raise_error(Mysql2::Error) end it "should respond to #query" do @client.should respond_to(:query) end it "should respond to #warning_count" do @client.should respond_to(:warning_count) end context "#warning_count" do context "when no warnings" do it "should 0" do @client.query('select 1') @client.warning_count.should == 0 end end context "when has a warnings" do it "should > 0" do # "the statement produces extra information that can be viewed by issuing a SHOW WARNINGS" # http://dev.mysql.com/doc/refman/5.0/en/explain-extended.html @client.query("explain extended select 1") @client.warning_count.should > 0 end end end it "should respond to #query_info" do @client.should respond_to(:query_info) end context "#query_info" do context "when no info present" do it "should 0" do @client.query('select 1') @client.query_info.should be_empty @client.query_info_string.should be_nil end end context "when has some info" do it "should retrieve it" do @client.query "USE test" @client.query "CREATE TABLE IF NOT EXISTS infoTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))" # http://dev.mysql.com/doc/refman/5.0/en/mysql-info.html says # # Note that mysql_info() returns a non-NULL value for INSERT ... VALUES only for the multiple-row form of the statement (that is, only if multiple value lists are specified). @client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)") @client.query_info.should eql({:records => 2, :duplicates => 0, :warnings => 0}) @client.query_info_string.should eq('Records: 2 Duplicates: 0 Warnings: 0') @client.query "DROP TABLE infoTest" end end end it "should expect connect_timeout to be a positive integer" do lambda { Mysql2::Client.new(:connect_timeout => -1) }.should raise_error(Mysql2::Error) end it "should expect read_timeout to be a positive integer" do lambda { Mysql2::Client.new(:read_timeout => -1) }.should raise_error(Mysql2::Error) end it "should expect write_timeout to be a positive integer" do lambda { Mysql2::Client.new(:write_timeout => -1) }.should raise_error(Mysql2::Error) end context "#query" do it "should let you query again if iterating is finished when streaming" do @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).each.to_a expect { @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false) }.to_not raise_exception(Mysql2::Error) end it "should not let you query again if iterating is not finished when streaming" do @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).first expect { @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false) }.to raise_exception(Mysql2::Error) end it "should only accept strings as the query parameter" do lambda { @client.query ["SELECT 'not right'"] }.should raise_error(TypeError) end it "should not retain query options set on a query for subsequent queries, but should retain it in the result" do result = @client.query "SELECT 1", :something => :else @client.query_options[:something].should be_nil result.instance_variable_get('@query_options').should eql(@client.query_options.merge(:something => :else)) @client.instance_variable_get('@current_query_options').should eql(@client.query_options.merge(:something => :else)) result = @client.query "SELECT 1" result.instance_variable_get('@query_options').should eql(@client.query_options) @client.instance_variable_get('@current_query_options').should eql(@client.query_options) end it "should allow changing query options for subsequent queries" do @client.query_options.merge!(:something => :else) result = @client.query "SELECT 1" @client.query_options[:something].should eql(:else) result.instance_variable_get('@query_options')[:something].should eql(:else) # Clean up after this test @client.query_options.delete(:something) @client.query_options[:something].should be_nil end it "should return results as a hash by default" do @client.query("SELECT 1").first.class.should eql(Hash) end it "should be able to return results as an array" do @client.query("SELECT 1", :as => :array).first.class.should eql(Array) @client.query("SELECT 1").each(:as => :array) end it "should be able to return results with symbolized keys" do @client.query("SELECT 1", :symbolize_keys => true).first.keys[0].class.should eql(Symbol) end it "should require an open connection" do @client.close lambda { @client.query "SELECT 1" }.should raise_error(Mysql2::Error) end if RUBY_PLATFORM !~ /mingw|mswin/ it "should not allow another query to be sent without fetching a result first" do @client.query("SELECT 1", :async => true) lambda { @client.query("SELECT 1") }.should raise_error(Mysql2::Error) end it "should describe the thread holding the active query" do thr = Thread.new { @client.query("SELECT 1", :async => true) } thr.join begin @client.query("SELECT 1") rescue Mysql2::Error => e message = e.message end re = Regexp.escape(thr.inspect) message.should match(Regexp.new(re)) end it "should timeout if we wait longer than :read_timeout" do client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => 1)) lambda { client.query("SELECT sleep(2)") }.should raise_error(Mysql2::Error) end if !defined? Rubinius # XXX this test is not deterministic (because Unix signal handling is not) # and may fail on a loaded system it "should run signal handlers while waiting for a response" do mark = {} trap(:USR1) { mark[:USR1] = Time.now } begin mark[:START] = Time.now pid = fork do sleep 1 # wait for client "SELECT sleep(2)" query to start Process.kill(:USR1, Process.ppid) sleep # wait for explicit kill to prevent GC disconnect end @client.query("SELECT sleep(2)") mark[:END] = Time.now mark.include?(:USR1).should be_true (mark[:USR1] - mark[:START]).should >= 1 (mark[:USR1] - mark[:START]).should < 1.3 (mark[:END] - mark[:USR1]).should > 0.9 (mark[:END] - mark[:START]).should >= 2 (mark[:END] - mark[:START]).should < 2.3 Process.kill(:TERM, pid) Process.waitpid2(pid) ensure trap(:USR1, 'DEFAULT') end end end it "#socket should return a Fixnum (file descriptor from C)" do @client.socket.class.should eql(Fixnum) @client.socket.should_not eql(0) end it "#socket should require an open connection" do @client.close lambda { @client.socket }.should raise_error(Mysql2::Error) end it "should close the connection when an exception is raised" do begin Timeout.timeout(1) do @client.query("SELECT sleep(2)") end rescue Timeout::Error end lambda { @client.query("SELECT 1") }.should raise_error(Mysql2::Error, 'closed MySQL connection') end it "should handle Timeouts without leaving the connection hanging if reconnect is true" do client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true)) begin Timeout.timeout(1) do client.query("SELECT sleep(2)") end rescue Timeout::Error end lambda { client.query("SELECT 1") }.should_not raise_error(Mysql2::Error) end it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction true" do client = Mysql2::Client.new(DatabaseCredentials['root']) begin Timeout.timeout(1) do client.query("SELECT sleep(2)") end rescue Timeout::Error end lambda { client.query("SELECT 1") }.should raise_error(Mysql2::Error) client.reconnect = true begin Timeout.timeout(1) do client.query("SELECT sleep(2)") end rescue Timeout::Error end lambda { client.query("SELECT 1") }.should_not raise_error(Mysql2::Error) end it "threaded queries should be supported" do threads, results = [], {} lock = Mutex.new connect = lambda{ Mysql2::Client.new(DatabaseCredentials['root']) } Timeout.timeout(0.7) do 5.times { threads << Thread.new do result = connect.call.query("SELECT sleep(0.5) as result") lock.synchronize do results[Thread.current.object_id] = result end end } end threads.each{|t| t.join } results.keys.sort.should eql(threads.map{|t| t.object_id }.sort) end it "evented async queries should be supported" do # should immediately return nil @client.query("SELECT sleep(0.1)", :async => true).should eql(nil) io_wrapper = IO.for_fd(@client.socket) loops = 0 loop do if IO.select([io_wrapper], nil, nil, 0.05) break else loops += 1 end end # make sure we waited some period of time (loops >= 1).should be_true result = @client.async_result result.class.should eql(Mysql2::Result) end end context "Multiple results sets" do before(:each) do @multi_client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:flags => Mysql2::Client::MULTI_STATEMENTS)) end it "returns multiple result sets" do @multi_client.query( "select 1 as 'set_1'; select 2 as 'set_2'").first.should eql({ 'set_1' => 1 }) @multi_client.next_result.should be_true @multi_client.store_result.first.should eql({ 'set_2' => 2 }) @multi_client.next_result.should be_false end it "does not interfere with other statements" do @multi_client.query( "select 1 as 'set_1'; select 2 as 'set_2'") while( @multi_client.next_result ) @multi_client.store_result end @multi_client.query( "select 3 as 'next'").first.should == { 'next' => 3 } end it "will raise on query if there are outstanding results to read" do @multi_client.query("SELECT 1; SELECT 2; SELECT 3") lambda { @multi_client.query("SELECT 4") }.should raise_error(Mysql2::Error) end it "#abandon_results! should work" do @multi_client.query("SELECT 1; SELECT 2; SELECT 3") @multi_client.abandon_results! lambda { @multi_client.query("SELECT 4") }.should_not raise_error(Mysql2::Error) end it "#more_results? should work" do @multi_client.query( "select 1 as 'set_1'; select 2 as 'set_2'") @multi_client.more_results?.should be_true @multi_client.next_result @multi_client.store_result @multi_client.more_results?.should be_false end end end it "should respond to #socket" do @client.should respond_to(:socket) end if RUBY_PLATFORM =~ /mingw|mswin/ it "#socket should raise as it's not supported" do lambda { @client.socket }.should raise_error(Mysql2::Error) end end it "should respond to escape" do Mysql2::Client.should respond_to(:escape) end context "escape" do it "should return a new SQL-escape version of the passed string" do Mysql2::Client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno") end it "should return the passed string if nothing was escaped" do str = "plain" Mysql2::Client.escape(str).object_id.should eql(str.object_id) end it "should not overflow the thread stack" do lambda { Thread.new { Mysql2::Client.escape("'" * 256 * 1024) }.join }.should_not raise_error(SystemStackError) end it "should not overflow the process stack" do lambda { Thread.new { Mysql2::Client.escape("'" * 1024 * 1024 * 4) }.join }.should_not raise_error(SystemStackError) end unless RUBY_VERSION =~ /1.8/ it "should carry over the original string's encoding" do str = "abc'def\"ghi\0jkl%mno" escaped = Mysql2::Client.escape(str) escaped.encoding.should eql(str.encoding) str.encode!('us-ascii') escaped = Mysql2::Client.escape(str) escaped.encoding.should eql(str.encoding) end end end it "should respond to #escape" do @client.should respond_to(:escape) end context "#escape" do it "should return a new SQL-escape version of the passed string" do @client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno") end it "should return the passed string if nothing was escaped" do str = "plain" @client.escape(str).object_id.should eql(str.object_id) end it "should not overflow the thread stack" do lambda { Thread.new { @client.escape("'" * 256 * 1024) }.join }.should_not raise_error(SystemStackError) end it "should not overflow the process stack" do lambda { Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join }.should_not raise_error(SystemStackError) end it "should require an open connection" do @client.close lambda { @client.escape "" }.should raise_error(Mysql2::Error) end end it "should respond to #info" do @client.should respond_to(:info) end it "#info should return a hash containing the client version ID and String" do info = @client.info info.class.should eql(Hash) info.should have_key(:id) info[:id].class.should eql(Fixnum) info.should have_key(:version) info[:version].class.should eql(String) end if defined? Encoding context "strings returned by #info" do it "should default to the connection's encoding if Encoding.default_internal is nil" do Encoding.default_internal = nil @client.info[:version].encoding.should eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) client2.info[:version].encoding.should eql(Encoding.find('us-ascii')) end it "should use Encoding.default_internal" do Encoding.default_internal = Encoding.find('utf-8') @client.info[:version].encoding.should eql(Encoding.default_internal) Encoding.default_internal = Encoding.find('us-ascii') @client.info[:version].encoding.should eql(Encoding.default_internal) end end end it "should respond to #server_info" do @client.should respond_to(:server_info) end it "#server_info should return a hash containing the client version ID and String" do server_info = @client.server_info server_info.class.should eql(Hash) server_info.should have_key(:id) server_info[:id].class.should eql(Fixnum) server_info.should have_key(:version) server_info[:version].class.should eql(String) end it "#server_info should require an open connection" do @client.close lambda { @client.server_info }.should raise_error(Mysql2::Error) end if defined? Encoding context "strings returned by #server_info" do it "should default to the connection's encoding if Encoding.default_internal is nil" do Encoding.default_internal = nil @client.server_info[:version].encoding.should eql(Encoding.find('utf-8')) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) client2.server_info[:version].encoding.should eql(Encoding.find('us-ascii')) end it "should use Encoding.default_internal" do Encoding.default_internal = Encoding.find('utf-8') @client.server_info[:version].encoding.should eql(Encoding.default_internal) Encoding.default_internal = Encoding.find('us-ascii') @client.server_info[:version].encoding.should eql(Encoding.default_internal) end end end it "should raise a Mysql2::Error exception upon connection failure" do lambda { Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42' }.should raise_error(Mysql2::Error) lambda { Mysql2::Client.new DatabaseCredentials['root'] }.should_not raise_error(Mysql2::Error) end context 'write operations api' do before(:each) do @client.query "USE test" @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))" end after(:each) do @client.query "DROP TABLE lastIdTest" end it "should respond to #last_id" do @client.should respond_to(:last_id) end it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do @client.last_id.should eql(0) @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)" @client.last_id.should eql(1) end it "should respond to #last_id" do @client.should respond_to(:last_id) end it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)" @client.affected_rows.should eql(1) @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1" @client.affected_rows.should eql(1) end end it "should respond to #thread_id" do @client.should respond_to(:thread_id) end it "#thread_id should be a Fixnum" do @client.thread_id.class.should eql(Fixnum) end it "should respond to #ping" do @client.should respond_to(:ping) end context "select_db" do before(:each) do 2.times do |i| @client.query("CREATE DATABASE test_selectdb_#{i}") @client.query("USE test_selectdb_#{i}") @client.query("CREATE TABLE test#{i} (`id` int NOT NULL PRIMARY KEY)") end end after(:each) do 2.times do |i| @client.query("DROP DATABASE test_selectdb_#{i}") end end it "should respond to #select_db" do @client.should respond_to(:select_db) end it "should switch databases" do @client.select_db("test_selectdb_0") @client.query("SHOW TABLES").first.values.first.should eql("test0") @client.select_db("test_selectdb_1") @client.query("SHOW TABLES").first.values.first.should eql("test1") @client.select_db("test_selectdb_0") @client.query("SHOW TABLES").first.values.first.should eql("test0") end it "should raise a Mysql2::Error when the database doesn't exist" do lambda { @client.select_db("nopenothere") }.should raise_error(Mysql2::Error) end it "should return the database switched to" do @client.select_db("test_selectdb_1").should eq("test_selectdb_1") end end it "#thread_id should return a boolean" do @client.ping.should eql(true) @client.close @client.ping.should eql(false) end unless RUBY_VERSION =~ /1.8/ it "should respond to #encoding" do @client.should respond_to(:encoding) end end end mysql2-0.3.14/lib/0000755000004100000410000000000012245130153013620 5ustar www-datawww-datamysql2-0.3.14/lib/mysql2.rb0000644000004100000410000000230412245130153015373 0ustar www-datawww-data# encoding: UTF-8 require 'date' require 'bigdecimal' require 'rational' unless RUBY_VERSION >= '1.9.2' require 'mysql2/version' unless defined? Mysql2::VERSION require 'mysql2/error' require 'mysql2/mysql2' require 'mysql2/result' require 'mysql2/client' # = Mysql2 # # A modern, simple and very fast Mysql library for Ruby - binding to libmysql module Mysql2 end if defined?(ActiveRecord::VERSION::STRING) && ActiveRecord::VERSION::STRING < "3.1" begin require 'active_record/connection_adapters/mysql2_adapter' rescue LoadError warn "============= WARNING FROM mysql2 =============" warn "This version of mysql2 (#{Mysql2::VERSION}) doesn't ship with the ActiveRecord adapter." warn "In Rails version 3.1.0 and up, the mysql2 ActiveRecord adapter is included with rails." warn "If you want to use the mysql2 gem with Rails <= 3.0.x, please use the latest mysql2 in the 0.2.x series." warn "============= END WARNING FROM mysql2 =============" end end # For holding utility methods module Mysql2::Util # # Rekey a string-keyed hash with equivalent symbols. # def self.key_hash_as_symbols(hash) return nil unless hash Hash[hash.map { |k,v| [k.to_sym, v] }] end end mysql2-0.3.14/lib/mysql2/0000755000004100000410000000000012245130153015047 5ustar www-datawww-datamysql2-0.3.14/lib/mysql2/result.rb0000644000004100000410000000007612245130153016715 0ustar www-datawww-datamodule Mysql2 class Result include Enumerable end end mysql2-0.3.14/lib/mysql2/em.rb0000644000004100000410000000174312245130153016002 0ustar www-datawww-data# encoding: utf-8 require 'eventmachine' require 'mysql2' module Mysql2 module EM class Client < ::Mysql2::Client module Watcher def initialize(client, deferable) @client = client @deferable = deferable end def notify_readable detach begin result = @client.async_result rescue Exception => e @deferable.fail(e) else @deferable.succeed(result) end end end def close(*args) if @watch @watch.detach end super(*args) end def query(sql, opts={}) if ::EM.reactor_running? super(sql, opts.merge(:async => true)) deferable = ::EM::DefaultDeferrable.new @watch = ::EM.watch(self.socket, Watcher, self, deferable) @watch.notify_readable = true deferable else super(sql, opts) end end end end end mysql2-0.3.14/lib/mysql2/console.rb0000644000004100000410000000020412245130153017032 0ustar www-datawww-data# Loaded by script/console. Land helpers here. Pry.config.prompt = lambda do |context, nesting, pry| "[mysql2] #{context}> " end mysql2-0.3.14/lib/mysql2/client.rb0000644000004100000410000000714212245130153016656 0ustar www-datawww-datamodule Mysql2 class Client attr_reader :query_options, :read_timeout @@default_query_options = { :as => :hash, # the type of object you want each row back as; also supports :array (an array of values) :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result :cast_booleans => false, # cast tinyint(1) fields as true/false in ruby :symbolize_keys => false, # return field names as symbols instead of strings :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller :cache_rows => true, # tells Mysql2 to use it's internal row cache for results :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION, :cast => true, :default_file => nil, :default_group => nil } def initialize(opts = {}) opts = Mysql2::Util.key_hash_as_symbols( opts ) @read_timeout = nil @query_options = @@default_query_options.dup @query_options.merge! opts initialize_ext [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth].each do |key| next unless opts.key?(key) case key when :reconnect, :local_infile, :secure_auth send(:"#{key}=", !!opts[key]) when :connect_timeout, :read_timeout, :write_timeout send(:"#{key}=", opts[key].to_i) else send(:"#{key}=", opts[key]) end end # force the encoding to utf8 self.charset_name = opts[:encoding] || 'utf8' ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher) ssl_set(*ssl_options) if ssl_options.any? if [:user,:pass,:hostname,:dbname,:db,:sock].any?{|k| @query_options.has_key?(k) } warn "============= WARNING FROM mysql2 =============" warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be deprecated at some point in the future." warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options." warn "============= END WARNING FROM mysql2 =========" end user = opts[:username] || opts[:user] pass = opts[:password] || opts[:pass] host = opts[:host] || opts[:hostname] port = opts[:port] database = opts[:database] || opts[:dbname] || opts[:db] socket = opts[:socket] || opts[:sock] flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags] # Correct the data types before passing these values down to the C level user = user.to_s unless user.nil? pass = pass.to_s unless pass.nil? host = host.to_s unless host.nil? port = port.to_i unless port.nil? database = database.to_s unless database.nil? socket = socket.to_s unless socket.nil? connect user, pass, host, port, database, socket, flags end def self.default_query_options @@default_query_options end def query_info info = query_info_string return {} unless info info_hash = {} info.split.each_slice(2) { |s| info_hash[s[0].downcase.delete(':').to_sym] = s[1].to_i } info_hash end private def self.local_offset ::Time.local(2010).utc_offset.to_r / 86400 end end end mysql2-0.3.14/lib/mysql2/version.rb0000644000004100000410000000004712245130153017062 0ustar www-datawww-datamodule Mysql2 VERSION = "0.3.14" end mysql2-0.3.14/lib/mysql2/error.rb0000644000004100000410000000045212245130153016526 0ustar www-datawww-datamodule Mysql2 class Error < StandardError attr_accessor :error_number, :sql_state def initialize msg super @error_number = nil @sql_state = nil end # Mysql gem compatibility alias_method :errno, :error_number alias_method :error, :message end end mysql2-0.3.14/metadata.yml0000644000004100000410000000651512245130153015364 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: mysql2 version: !ruby/object:Gem::Version version: 0.3.14 platform: ruby authors: - Brian Lopez autorequire: bindir: bin cert_chain: [] date: 2013-11-07 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: eventmachine requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rake-compiler requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: 0.8.1 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: 0.8.1 - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: 0.9.3 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: 0.9.3 - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: 2.8.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: 2.8.0 description: email: seniorlopez@gmail.com executables: [] extensions: - ext/mysql2/extconf.rb extra_rdoc_files: [] files: - MIT-LICENSE - README.md - ext/mysql2/client.c - ext/mysql2/client.h - ext/mysql2/extconf.rb - ext/mysql2/mysql2_ext.c - ext/mysql2/mysql2_ext.h - ext/mysql2/mysql_enc_name_to_ruby.h - ext/mysql2/mysql_enc_to_ruby.h - ext/mysql2/result.c - ext/mysql2/result.h - ext/mysql2/wait_for_single_fd.h - lib/mysql2.rb - lib/mysql2/client.rb - lib/mysql2/console.rb - lib/mysql2/em.rb - lib/mysql2/error.rb - lib/mysql2/result.rb - lib/mysql2/version.rb - support/mysql_enc_to_ruby.rb - support/ruby_enc_to_mysql.rb - examples/eventmachine.rb - examples/threaded.rb - spec/configuration.yml.example - spec/em/em_spec.rb - spec/my.cnf.example - spec/mysql2/client_spec.rb - spec/mysql2/error_spec.rb - spec/mysql2/result_spec.rb - spec/rcov.opts - spec/spec_helper.rb homepage: http://github.com/brianmario/mysql2 licenses: - MIT metadata: {} post_install_message: rdoc_options: - --charset=UTF-8 require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.0.3 signing_key: specification_version: 4 summary: A simple, fast Mysql library for Ruby, binding to libmysql test_files: - examples/eventmachine.rb - examples/threaded.rb - spec/configuration.yml.example - spec/em/em_spec.rb - spec/my.cnf.example - spec/mysql2/client_spec.rb - spec/mysql2/error_spec.rb - spec/mysql2/result_spec.rb - spec/rcov.opts - spec/spec_helper.rb mysql2-0.3.14/support/0000755000004100000410000000000012245130153014566 5ustar www-datawww-datamysql2-0.3.14/support/ruby_enc_to_mysql.rb0000644000004100000410000000267412245130153020661 0ustar www-datawww-datamysql_to_rb = { "big5" => "Big5", "dec8" => nil, "cp850" => "CP850", "hp8" => nil, "koi8r" => "KOI8-R", "latin1" => "ISO-8859-1", "latin2" => "ISO-8859-2", "swe7" => nil, "ascii" => "US-ASCII", "ujis" => "eucJP-ms", "sjis" => "Shift_JIS", "hebrew" => "ISO-8859-8", "tis620" => "TIS-620", "euckr" => "EUC-KR", "koi8u" => "KOI8-R", "gb2312" => "GB2312", "greek" => "ISO-8859-7", "cp1250" => "Windows-1250", "gbk" => "GBK", "latin5" => "ISO-8859-9", "armscii8" => nil, "utf8" => "UTF-8", "ucs2" => "UTF-16BE", "cp866" => "IBM866", "keybcs2" => nil, "macce" => "macCentEuro", "macroman" => "macRoman", "cp852" => "CP852", "latin7" => "ISO-8859-13", "utf8mb4" => "UTF-8", "cp1251" => "Windows-1251", "utf16" => "UTF-16", "cp1256" => "Windows-1256", "cp1257" => "Windows-1257", "utf32" => "UTF-32", "binary" => "ASCII-8BIT", "geostd8" => nil, "cp932" => "Windows-31J", "eucjpms" => "eucJP-ms" } puts <<-header %readonly-tables %enum %define lookup-function-name mysql2_mysql_enc_name_to_rb %define hash-function-name mysql2_mysql_enc_name_to_rb_hash %struct-type struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; } %% header mysql_to_rb.each do |mysql, ruby| if ruby.nil? name = "NULL" else name = "\"#{ruby}\"" end puts "#{mysql}, #{name}" end mysql2-0.3.14/support/mysql_enc_to_ruby.rb0000644000004100000410000000412112245130153020646 0ustar www-datawww-data$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'mysql2' user, pass, host, port = ENV.values_at('user', 'pass', 'host', 'port') mysql_to_rb = { "big5" => "Big5", "dec8" => "NULL", "cp850" => "CP850", "hp8" => "NULL", "koi8r" => "KOI8-R", "latin1" => "ISO-8859-1", "latin2" => "ISO-8859-2", "swe7" => "NULL", "ascii" => "US-ASCII", "ujis" => "eucJP-ms", "sjis" => "Shift_JIS", "hebrew" => "ISO-8859-8", "tis620" => "TIS-620", "euckr" => "EUC-KR", "koi8u" => "KOI8-R", "gb2312" => "GB2312", "greek" => "ISO-8859-7", "cp1250" => "Windows-1250", "gbk" => "GBK", "latin5" => "ISO-8859-9", "armscii8" => "NULL", "utf8" => "UTF-8", "ucs2" => "UTF-16BE", "cp866" => "IBM866", "keybcs2" => "NULL", "macce" => "macCentEuro", "macroman" => "macRoman", "cp852" => "CP852", "latin7" => "ISO-8859-13", "utf8mb4" => "UTF-8", "cp1251" => "Windows-1251", "utf16" => "UTF-16", "cp1256" => "Windows-1256", "cp1257" => "Windows-1257", "utf32" => "UTF-32", "binary" => "ASCII-8BIT", "geostd8" => "NULL", "cp932" => "Windows-31J", "eucjpms" => "eucJP-ms" } client = Mysql2::Client.new(:username => user, :password => pass, :host => host, :port => port.to_i) collations = client.query "SHOW COLLATION", :as => :array encodings = Array.new(collations.to_a.last[2].to_i) encodings_with_nil = Array.new(encodings.size) collations.each do |collation| mysql_col_idx = collation[2].to_i rb_enc = mysql_to_rb[collation[1]] encodings[mysql_col_idx-1] = [mysql_col_idx, rb_enc] end encodings.each_with_index do |encoding, idx| encodings_with_nil[idx] = (encoding || [idx, "NULL"]) end encodings_with_nil.sort! do |a, b| a[0] <=> b[0] end encodings_with_nil = encodings_with_nil.map do |encoding| name = "NULL" if !encoding.nil? && encoding[1] != "NULL" name = "\"#{encoding[1]}\"" end " #{name}" end # start printing output puts "const char *mysql2_mysql_enc_to_rb[] = {" puts encodings_with_nil.join(",\n") puts "};" puts mysql2-0.3.14/checksums.yaml.gz0000444000004100000410000000041712245130153016342 0ustar www-datawww-data‹1{Re;v@ ó=Å^`y’F#œ9#çú‘ðqzÆ!TÐ]­z½^ßßñíñ|þê/ÿðo?ÿ¼=vʨ#«ˆh@Ëàœ0¦s\¡¥î›ûÊ|øï¯\ÐÉØ•™Ã¢¤-ÐP LÑOÓÆ³’k蓺‘þãŽo¦eMŸlé,ŽZ|”GQÐÒto­ïFu»çæJZ‹3Ž—›nq×Ò 1س“w™¦™ïôtœX¤ºÒ‰Z°þýGvYô©mv-˜^6¶¡Ãu¢pˆÊ×í¼›k g!X©ó˜*€‰„Æ:ÂWLh8˪î&GºVÆ2b( ö†kz…5<þÙ컕¢mysql2-0.3.14/ext/0000755000004100000410000000000012245130153013652 5ustar www-datawww-datamysql2-0.3.14/ext/mysql2/0000755000004100000410000000000012245130153015101 5ustar www-datawww-datamysql2-0.3.14/ext/mysql2/mysql_enc_name_to_ruby.h0000644000004100000410000001324712245130153022016 0ustar www-datawww-data/* C code produced by gperf version 3.0.3 */ /* Command-line: gperf */ /* Computed positions: -k'1,3,$' */ #if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) /* The character set is not based on ISO-646. */ error "gperf generated tables don't work with this execution character set. Please report a bug to ." #endif struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; }; /* maximum key range = 66, duplicates = 0 */ #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static unsigned int mysql2_mysql_enc_name_to_rb_hash (str, len) register const char *str; register unsigned int len; { static const unsigned char asso_values[] = { 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 40, 5, 0, 69, 0, 40, 25, 20, 10, 55, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 35, 5, 0, 10, 0, 20, 0, 5, 5, 69, 0, 10, 15, 0, 0, 69, 69, 25, 5, 5, 0, 69, 30, 69, 0, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69 }; return len + asso_values[(unsigned char)str[2]] + asso_values[(unsigned char)str[0]] + asso_values[(unsigned char)str[len - 1]]; } #ifdef __GNUC__ __inline #ifdef __GNUC_STDC_INLINE__ __attribute__ ((__gnu_inline__)) #endif #endif const struct mysql2_mysql_enc_name_to_rb_map * mysql2_mysql_enc_name_to_rb (str, len) register const char *str; register unsigned int len; { enum { TOTAL_KEYWORDS = 39, MIN_WORD_LENGTH = 3, MAX_WORD_LENGTH = 8, MIN_HASH_VALUE = 3, MAX_HASH_VALUE = 68 }; static const struct mysql2_mysql_enc_name_to_rb_map wordlist[] = { {""}, {""}, {""}, {"gbk", "GBK"}, {""}, {"greek", "ISO-8859-7"}, {"gb2312", "GB2312"}, {"keybcs2", NULL}, {""}, {"ucs2", "UTF-16BE"}, {"koi8u", "KOI8-R"}, {"binary", "ASCII-8BIT"}, {"eucjpms", "eucJP-ms"}, {""}, {"ujis", "eucJP-ms"}, {"cp852", "CP852"}, {"cp1251", "Windows-1251"}, {"geostd8", NULL}, {""}, {"sjis", "Shift_JIS"}, {"macce", "macCentEuro"}, {"latin2", "ISO-8859-2"}, {""}, {"macroman", "macRoman"}, {"dec8", NULL}, {"utf32", "UTF-32"}, {"latin1", "ISO-8859-1"}, {"utf8mb4", "UTF-8"}, {"hp8", NULL}, {"swe7", NULL}, {"euckr", "EUC-KR"}, {"cp1257", "Windows-1257"}, {""}, {""}, {"utf8", "UTF-8"}, {"koi8r", "KOI8-R"}, {"cp1256", "Windows-1256"}, {""}, {""}, {""}, {"cp866", "IBM866"}, {"latin7", "ISO-8859-13"}, {""}, {""}, {""}, {"ascii", "US-ASCII"}, {"hebrew", "ISO-8859-8"}, {""}, {""}, {"big5", "Big5"}, {"utf16", "UTF-16"}, {"cp1250", "Windows-1250"}, {""}, {""}, {""}, {"cp850", "CP850"}, {"tis620", "TIS-620"}, {""}, {""}, {""}, {"cp932", "Windows-31J"}, {"latin5", "ISO-8859-9"}, {""}, {""}, {""}, {""}, {""}, {""}, {"armscii8", NULL} }; if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) { register int key = mysql2_mysql_enc_name_to_rb_hash (str, len); if (key <= MAX_HASH_VALUE && key >= 0) { register const char *s = wordlist[key].name; if (*str == *s && !strcmp (str + 1, s + 1)) return &wordlist[key]; } } return 0; } mysql2-0.3.14/ext/mysql2/result.c0000644000004100000410000005311012245130153016563 0ustar www-datawww-data#include #include #include "mysql_enc_to_ruby.h" #ifdef HAVE_RUBY_ENCODING_H static rb_encoding *binaryEncoding; #endif #if (SIZEOF_INT < SIZEOF_LONG) || defined(HAVE_RUBY_ENCODING_H) /* on 64bit platforms we can handle dates way outside 2038-01-19T03:14:07 * * (9999*31557600) + (12*2592000) + (31*86400) + (11*3600) + (59*60) + 59 */ #define MYSQL2_MAX_TIME 315578267999ULL #else /** * On 32bit platforms the maximum date the Time class can handle is 2038-01-19T03:14:07 * 2038 years + 1 month + 19 days + 3 hours + 14 minutes + 7 seconds = 64318634047 seconds * * (2038*31557600) + (1*2592000) + (19*86400) + (3*3600) + (14*60) + 7 */ #define MYSQL2_MAX_TIME 64318634047ULL #endif #if defined(HAVE_RUBY_ENCODING_H) /* 0000-1-1 00:00:00 UTC * * (0*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0 */ #define MYSQL2_MIN_TIME 2678400ULL #elif SIZEOF_INT < SIZEOF_LONG /* 64bit Ruby 1.8 */ /* 0139-1-1 00:00:00 UTC * * (139*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0 */ #define MYSQL2_MIN_TIME 4389184800ULL #elif defined(NEGATIVE_TIME_T) /* 1901-12-13 20:45:52 UTC : The oldest time in 32-bit signed time_t. * * (1901*31557600) + (12*2592000) + (13*86400) + (20*3600) + (45*60) + 52 */ #define MYSQL2_MIN_TIME 60023299552ULL #else /* 1970-01-01 00:00:01 UTC : The Unix epoch - the oldest time in portable time_t. * * (1970*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 1 */ #define MYSQL2_MIN_TIME 62171150401ULL #endif static VALUE cMysql2Result; static VALUE cBigDecimal, cDate, cDateTime; static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset; extern VALUE mMysql2, cMysql2Client, cMysql2Error; static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset; static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone, sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name; static ID intern_merge; static void rb_mysql_result_mark(void * wrapper) { mysql2_result_wrapper * w = wrapper; if (w) { rb_gc_mark(w->fields); rb_gc_mark(w->rows); rb_gc_mark(w->encoding); rb_gc_mark(w->client); } } /* this may be called manually or during GC */ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { if (wrapper && wrapper->resultFreed != 1) { /* FIXME: this may call flush_use_result, which can hit the socket */ mysql_free_result(wrapper->result); wrapper->resultFreed = 1; } } /* this is called during GC */ static void rb_mysql_result_free(void *ptr) { mysql2_result_wrapper * wrapper = ptr; rb_mysql_result_free_result(wrapper); // If the GC gets to client first it will be nil if (wrapper->client != Qnil) { wrapper->client_wrapper->refcount--; if (wrapper->client_wrapper->refcount == 0) { xfree(wrapper->client_wrapper->client); xfree(wrapper->client_wrapper); } } xfree(wrapper); } /* * for small results, this won't hit the network, but there's no * reliable way for us to tell this so we'll always release the GVL * to be safe */ static void *nogvl_fetch_row(void *ptr) { MYSQL_RES *result = ptr; return mysql_fetch_row(result); } static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) { mysql2_result_wrapper * wrapper; VALUE rb_field; GetMysql2Result(self, wrapper); if (wrapper->fields == Qnil) { wrapper->numberOfFields = mysql_num_fields(wrapper->result); wrapper->fields = rb_ary_new2(wrapper->numberOfFields); } rb_field = rb_ary_entry(wrapper->fields, idx); if (rb_field == Qnil) { MYSQL_FIELD *field = NULL; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc = rb_default_internal_encoding(); rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); #endif field = mysql_fetch_field_direct(wrapper->result, idx); if (symbolize_keys) { char buf[field->name_length+1]; memcpy(buf, field->name, field->name_length); buf[field->name_length] = 0; #ifdef HAVE_RB_INTERN3 rb_field = rb_intern3(buf, field->name_length, rb_utf8_encoding()); rb_field = ID2SYM(rb_field); #else VALUE colStr; colStr = rb_str_new2(buf); rb_field = ID2SYM(rb_to_id(colStr)); #endif } else { rb_field = rb_str_new(field->name, field->name_length); #ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(rb_field, conn_enc); if (default_internal_enc) { rb_field = rb_str_export_to_enc(rb_field, default_internal_enc); } #endif } rb_ary_store(wrapper->fields, idx, rb_field); } return rb_field; } #ifdef HAVE_RUBY_ENCODING_H static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) { /* if binary flag is set, respect it's wishes */ if (field.flags & BINARY_FLAG && field.charsetnr == 63) { rb_enc_associate(val, binaryEncoding); } else if (!field.charsetnr) { /* MySQL 4.x may not provide an encoding, binary will get the bytes through */ rb_enc_associate(val, binaryEncoding); } else { /* lookup the encoding configured on this field */ const char *enc_name; int enc_index; enc_name = mysql2_mysql_enc_to_rb[field.charsetnr-1]; if (enc_name != NULL) { /* use the field encoding we were able to match */ enc_index = rb_enc_find_index(enc_name); rb_enc_set_index(val, enc_index); } else { /* otherwise fall-back to the connection's encoding */ rb_enc_associate(val, conn_enc); } if (default_internal_enc) { val = rb_str_export_to_enc(val, default_internal_enc); } } return val; } #endif static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD * fields) { VALUE rowVal; mysql2_result_wrapper * wrapper; MYSQL_ROW row; unsigned int i = 0; unsigned long * fieldLengths; void * ptr; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc; rb_encoding *conn_enc; #endif GetMysql2Result(self, wrapper); #ifdef HAVE_RUBY_ENCODING_H default_internal_enc = rb_default_internal_encoding(); conn_enc = rb_to_encoding(wrapper->encoding); #endif ptr = wrapper->result; row = (MYSQL_ROW)rb_thread_call_without_gvl(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0); if (row == NULL) { return Qnil; } if (asArray) { rowVal = rb_ary_new2(wrapper->numberOfFields); } else { rowVal = rb_hash_new(); } fieldLengths = mysql_fetch_lengths(wrapper->result); if (wrapper->fields == Qnil) { wrapper->numberOfFields = mysql_num_fields(wrapper->result); wrapper->fields = rb_ary_new2(wrapper->numberOfFields); } for (i = 0; i < wrapper->numberOfFields; i++) { VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys); if (row[i]) { VALUE val = Qnil; enum enum_field_types type = fields[i].type; if(!cast) { if (type == MYSQL_TYPE_NULL) { val = Qnil; } else { val = rb_str_new(row[i], fieldLengths[i]); #ifdef HAVE_RUBY_ENCODING_H val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); #endif } } else { switch(type) { case MYSQL_TYPE_NULL: /* NULL-type field */ val = Qnil; break; case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */ if (castBool && fields[i].length == 1) { val = *row[i] == 1 ? Qtrue : Qfalse; }else{ val = rb_str_new(row[i], fieldLengths[i]); } break; case MYSQL_TYPE_TINY: /* TINYINT field */ if (castBool && fields[i].length == 1) { val = *row[i] != '0' ? Qtrue : Qfalse; break; } case MYSQL_TYPE_SHORT: /* SMALLINT field */ case MYSQL_TYPE_LONG: /* INTEGER field */ case MYSQL_TYPE_INT24: /* MEDIUMINT field */ case MYSQL_TYPE_LONGLONG: /* BIGINT field */ case MYSQL_TYPE_YEAR: /* YEAR field */ val = rb_cstr2inum(row[i], 10); break; case MYSQL_TYPE_DECIMAL: /* DECIMAL or NUMERIC field */ case MYSQL_TYPE_NEWDECIMAL: /* Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up) */ if (fields[i].decimals == 0) { val = rb_cstr2inum(row[i], 10); } else if (strtod(row[i], NULL) == 0.000000){ val = rb_funcall(cBigDecimal, intern_new, 1, opt_decimal_zero); }else{ val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(row[i], fieldLengths[i])); } break; case MYSQL_TYPE_FLOAT: /* FLOAT field */ case MYSQL_TYPE_DOUBLE: { /* DOUBLE or REAL field */ double column_to_double; column_to_double = strtod(row[i], NULL); if (column_to_double == 0.000000){ val = opt_float_zero; }else{ val = rb_float_new(column_to_double); } break; } case MYSQL_TYPE_TIME: { /* TIME field */ int tokens; unsigned int hour=0, min=0, sec=0; tokens = sscanf(row[i], "%2u:%2u:%2u", &hour, &min, &sec); if (tokens < 3) { val = Qnil; break; } val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec)); if (!NIL_P(app_timezone)) { if (app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); } else { /* utc */ val = rb_funcall(val, intern_utc, 0); } } break; } case MYSQL_TYPE_TIMESTAMP: /* TIMESTAMP field */ case MYSQL_TYPE_DATETIME: { /* DATETIME field */ int tokens; unsigned int year=0, month=0, day=0, hour=0, min=0, sec=0, msec=0; uint64_t seconds; tokens = sscanf(row[i], "%4u-%2u-%2u %2u:%2u:%2u.%6u", &year, &month, &day, &hour, &min, &sec, &msec); if (tokens < 6) { /* msec might be empty */ val = Qnil; break; } seconds = (year*31557600ULL) + (month*2592000ULL) + (day*86400ULL) + (hour*3600ULL) + (min*60ULL) + sec; if (seconds == 0) { val = Qnil; } else { if (month < 1 || day < 1) { rb_raise(cMysql2Error, "Invalid date: %s", row[i]); val = Qnil; } else { if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */ VALUE offset = INT2NUM(0); if (db_timezone == intern_local) { offset = rb_funcall(cMysql2Client, intern_local_offset, 0); } val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), offset); if (!NIL_P(app_timezone)) { if (app_timezone == intern_local) { offset = rb_funcall(cMysql2Client, intern_local_offset, 0); val = rb_funcall(val, intern_new_offset, 1, offset); } else { /* utc */ val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset); } } } else { /* use Time, supports microseconds */ val = rb_funcall(rb_cTime, db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); if (!NIL_P(app_timezone)) { if (app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); } else { /* utc */ val = rb_funcall(val, intern_utc, 0); } } } } } break; } case MYSQL_TYPE_DATE: /* DATE field */ case MYSQL_TYPE_NEWDATE: { /* Newer const used > 5.0 */ int tokens; unsigned int year=0, month=0, day=0; tokens = sscanf(row[i], "%4u-%2u-%2u", &year, &month, &day); if (tokens < 3) { val = Qnil; break; } if (year+month+day == 0) { val = Qnil; } else { if (month < 1 || day < 1) { rb_raise(cMysql2Error, "Invalid date: %s", row[i]); val = Qnil; } else { val = rb_funcall(cDate, intern_new, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day)); } } break; } case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_BLOB: case MYSQL_TYPE_VAR_STRING: case MYSQL_TYPE_VARCHAR: case MYSQL_TYPE_STRING: /* CHAR or BINARY field */ case MYSQL_TYPE_SET: /* SET field */ case MYSQL_TYPE_ENUM: /* ENUM field */ case MYSQL_TYPE_GEOMETRY: /* Spatial fielda */ default: val = rb_str_new(row[i], fieldLengths[i]); #ifdef HAVE_RUBY_ENCODING_H val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); #endif break; } } if (asArray) { rb_ary_push(rowVal, val); } else { rb_hash_aset(rowVal, field, val); } } else { if (asArray) { rb_ary_push(rowVal, Qnil); } else { rb_hash_aset(rowVal, field, Qnil); } } } return rowVal; } static VALUE rb_mysql_result_fetch_fields(VALUE self) { mysql2_result_wrapper * wrapper; unsigned int i = 0; short int symbolizeKeys = 0; VALUE defaults; GetMysql2Result(self, wrapper); defaults = rb_iv_get(self, "@query_options"); Check_Type(defaults, T_HASH); if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) { symbolizeKeys = 1; } if (wrapper->fields == Qnil) { wrapper->numberOfFields = mysql_num_fields(wrapper->result); wrapper->fields = rb_ary_new2(wrapper->numberOfFields); } if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) { for (i=0; inumberOfFields; i++) { rb_mysql_result_fetch_field(self, i, symbolizeKeys); } } return wrapper->fields; } static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { VALUE defaults, opts, block; ID db_timezone, app_timezone, dbTz, appTz; mysql2_result_wrapper * wrapper; unsigned long i; int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1, streaming = 0; MYSQL_FIELD * fields = NULL; GetMysql2Result(self, wrapper); defaults = rb_iv_get(self, "@query_options"); Check_Type(defaults, T_HASH); if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) { opts = rb_funcall(defaults, intern_merge, 1, opts); } else { opts = defaults; } if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) { symbolizeKeys = 1; } if (rb_hash_aref(opts, sym_as) == sym_array) { asArray = 1; } if (rb_hash_aref(opts, sym_cast_booleans) == Qtrue) { castBool = 1; } if (rb_hash_aref(opts, sym_cache_rows) == Qfalse) { cacheRows = 0; } if (rb_hash_aref(opts, sym_cast) == Qfalse) { cast = 0; } if(rb_hash_aref(opts, sym_stream) == Qtrue) { streaming = 1; } if(streaming && cacheRows) { rb_warn("cacheRows is ignored if streaming is true"); } dbTz = rb_hash_aref(opts, sym_database_timezone); if (dbTz == sym_local) { db_timezone = intern_local; } else if (dbTz == sym_utc) { db_timezone = intern_utc; } else { if (!NIL_P(dbTz)) { rb_warn(":database_timezone option must be :utc or :local - defaulting to :local"); } db_timezone = intern_local; } appTz = rb_hash_aref(opts, sym_application_timezone); if (appTz == sym_local) { app_timezone = intern_local; } else if (appTz == sym_utc) { app_timezone = intern_utc; } else { app_timezone = Qnil; } if (wrapper->lastRowProcessed == 0) { if(streaming) { /* We can't get number of rows if we're streaming, */ /* until we've finished fetching all rows */ wrapper->numberOfRows = 0; wrapper->rows = rb_ary_new(); } else { wrapper->numberOfRows = mysql_num_rows(wrapper->result); if (wrapper->numberOfRows == 0) { wrapper->rows = rb_ary_new(); return wrapper->rows; } wrapper->rows = rb_ary_new2(wrapper->numberOfRows); } } if (streaming) { if(!wrapper->streamingComplete) { VALUE row; fields = mysql_fetch_fields(wrapper->result); do { row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields); if (block != Qnil && row != Qnil) { rb_yield(row); wrapper->lastRowProcessed++; } } while(row != Qnil); rb_mysql_result_free_result(wrapper); wrapper->numberOfRows = wrapper->lastRowProcessed; wrapper->streamingComplete = 1; } else { rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery)."); } } else { if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) { /* we've already read the entire dataset from the C result into our */ /* internal array. Lets hand that over to the user since it's ready to go */ for (i = 0; i < wrapper->numberOfRows; i++) { rb_yield(rb_ary_entry(wrapper->rows, i)); } } else { unsigned long rowsProcessed = 0; rowsProcessed = RARRAY_LEN(wrapper->rows); fields = mysql_fetch_fields(wrapper->result); for (i = 0; i < wrapper->numberOfRows; i++) { VALUE row; if (cacheRows && i < rowsProcessed) { row = rb_ary_entry(wrapper->rows, i); } else { row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields); if (cacheRows) { rb_ary_store(wrapper->rows, i, row); } wrapper->lastRowProcessed++; } if (row == Qnil) { /* we don't need the mysql C dataset around anymore, peace it */ rb_mysql_result_free_result(wrapper); return Qnil; } if (block != Qnil) { rb_yield(row); } } if (wrapper->lastRowProcessed == wrapper->numberOfRows) { /* we don't need the mysql C dataset around anymore, peace it */ rb_mysql_result_free_result(wrapper); } } } return wrapper->rows; } static VALUE rb_mysql_result_count(VALUE self) { mysql2_result_wrapper *wrapper; GetMysql2Result(self, wrapper); if(wrapper->resultFreed) { if (wrapper->streamingComplete){ return LONG2NUM(wrapper->numberOfRows); } else { return LONG2NUM(RARRAY_LEN(wrapper->rows)); } } else { return INT2FIX(mysql_num_rows(wrapper->result)); } } /* Mysql2::Result */ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r) { VALUE obj; mysql2_result_wrapper * wrapper; obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper); wrapper->numberOfFields = 0; wrapper->numberOfRows = 0; wrapper->lastRowProcessed = 0; wrapper->resultFreed = 0; wrapper->result = r; wrapper->fields = Qnil; wrapper->rows = Qnil; wrapper->encoding = encoding; wrapper->streamingComplete = 0; wrapper->client = client; wrapper->client_wrapper = DATA_PTR(client); wrapper->client_wrapper->refcount++; rb_obj_call_init(obj, 0, NULL); rb_iv_set(obj, "@query_options", options); return obj; } void init_mysql2_result() { cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal")); cDate = rb_const_get(rb_cObject, rb_intern("Date")); cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime")); cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject); rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1); rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0); rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0); rb_define_alias(cMysql2Result, "size", "count"); intern_new = rb_intern("new"); intern_utc = rb_intern("utc"); intern_local = rb_intern("local"); intern_merge = rb_intern("merge"); intern_localtime = rb_intern("localtime"); intern_local_offset = rb_intern("local_offset"); intern_civil = rb_intern("civil"); intern_new_offset = rb_intern("new_offset"); sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); sym_as = ID2SYM(rb_intern("as")); sym_array = ID2SYM(rb_intern("array")); sym_local = ID2SYM(rb_intern("local")); sym_utc = ID2SYM(rb_intern("utc")); sym_cast_booleans = ID2SYM(rb_intern("cast_booleans")); sym_database_timezone = ID2SYM(rb_intern("database_timezone")); sym_application_timezone = ID2SYM(rb_intern("application_timezone")); sym_cache_rows = ID2SYM(rb_intern("cache_rows")); sym_cast = ID2SYM(rb_intern("cast")); sym_stream = ID2SYM(rb_intern("stream")); sym_name = ID2SYM(rb_intern("name")); opt_decimal_zero = rb_str_new2("0.0"); rb_global_variable(&opt_decimal_zero); /*never GC */ opt_float_zero = rb_float_new((double)0); rb_global_variable(&opt_float_zero); opt_time_year = INT2NUM(2000); opt_time_month = INT2NUM(1); opt_utc_offset = INT2NUM(0); #ifdef HAVE_RUBY_ENCODING_H binaryEncoding = rb_enc_find("binary"); #endif } mysql2-0.3.14/ext/mysql2/extconf.rb0000644000004100000410000001060312245130153017074 0ustar www-datawww-data# encoding: UTF-8 require 'mkmf' def asplode lib abort "-----\n#{lib} is missing. please check your installation of mysql and try again.\n-----" end # 2.0-only have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h') # 1.9-only have_func('rb_thread_blocking_region') have_func('rb_wait_for_single_fd') have_func('rb_hash_dup') have_func('rb_intern3') # borrowed from mysqlplus # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb dirs = ENV['PATH'].split(File::PATH_SEPARATOR) + %w[ /opt /opt/local /opt/local/mysql /opt/local/lib/mysql5 /usr /usr/mysql /usr/local /usr/local/mysql /usr/local/mysql-* /usr/local/lib/mysql5 ].map{|dir| "#{dir}/bin" } GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5}" # If the user has provided a --with-mysql-dir argument, we must respect it or fail. inc, lib = dir_config('mysql') if inc && lib # Ruby versions not incorporating the mkmf fix at # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717 # do not properly search for lib directories, and must be corrected unless lib && lib[-3, 3] == 'lib' @libdir_basename = 'lib' inc, lib = dir_config('mysql') end abort "-----\nCannot find include dir at #{inc}\n-----" unless inc && File.directory?(inc) abort "-----\nCannot find library dir at #{lib}\n-----" unless lib && File.directory?(lib) warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----" rpath_dir = lib elsif mc = (with_config('mysql-config') || Dir[GLOB].first) # If the user has provided a --with-mysql-config argument, we must respect it or fail. # If the user gave --with-mysql-config with no argument means we should try to find it. mc = Dir[GLOB].first if mc == true abort "-----\nCannot find mysql_config at #{mc}\n-----" unless mc && File.exists?(mc) abort "-----\nCannot execute mysql_config at #{mc}\n-----" unless File.executable?(mc) warn "-----\nUsing mysql_config at #{mc}\n-----" ver = `#{mc} --version`.chomp.to_f includes = `#{mc} --include`.chomp exit 1 if $? != 0 libs = `#{mc} --libs_r`.chomp # MySQL 5.5 and above already have re-entrant code in libmysqlclient (no _r). if ver >= 5.5 || libs.empty? libs = `#{mc} --libs`.chomp end exit 1 if $? != 0 $INCFLAGS += ' ' + includes $libs = libs + " " + $libs rpath_dir = libs else inc, lib = dir_config('mysql', '/usr/local') libs = ['m', 'z', 'socket', 'nsl', 'mygcc'] while not find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql") do exit 1 if libs.empty? have_library(libs.shift) end rpath_dir = lib end if RUBY_PLATFORM =~ /mswin|mingw/ exit 1 unless have_library('libmysql') end if have_header('mysql.h') prefix = nil elsif have_header('mysql/mysql.h') prefix = 'mysql' else asplode 'mysql.h' end %w{ errmsg.h mysqld_error.h }.each do |h| header = [prefix, h].compact.join '/' asplode h unless have_header h end # These gcc style flags are also supported by clang and xcode compilers, # so we'll use a does-it-work test instead of an is-it-gcc test. gcc_flags = ' -Wall -funroll-loops' if try_link('int main() {return 0;}', gcc_flags) $CFLAGS << gcc_flags end case explicit_rpath = with_config('mysql-rpath') when true abort "-----\nOption --with-mysql-rpath must have an argument\n-----" when false warn "-----\nOption --with-mysql-rpath has been disabled at your request\n-----" when String # The user gave us a value so use it rpath_flags = " -Wl,-rpath,#{explicit_rpath}" warn "-----\nSetting mysql rpath to #{explicit_rpath}\n-----" $LDFLAGS << rpath_flags else if libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2] rpath_flags = " -Wl,-rpath,#{libdir}" if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', rpath_flags) # Usually Ruby sets RPATHFLAG the right way for each system, but not on OS X. warn "-----\nSetting rpath to #{libdir}\n-----" $LDFLAGS << rpath_flags else if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? # If we got here because try_link failed, warn the user warn "-----\nDon't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load\n-----" end # Make sure that LIBPATH gets set if we didn't explicitly set the rpath. warn "-----\nSetting libpath to #{libdir}\n-----" $LIBPATH << libdir unless $LIBPATH.include?(libdir) end end end create_makefile('mysql2/mysql2') mysql2-0.3.14/ext/mysql2/result.h0000644000004100000410000000106612245130153016573 0ustar www-datawww-data#ifndef MYSQL2_RESULT_H #define MYSQL2_RESULT_H void init_mysql2_result(); VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r); typedef struct { VALUE fields; VALUE rows; VALUE client; VALUE encoding; unsigned int numberOfFields; unsigned long numberOfRows; unsigned long lastRowProcessed; char streamingComplete; char resultFreed; MYSQL_RES *result; mysql_client_wrapper *client_wrapper; } mysql2_result_wrapper; #define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj)); #endif mysql2-0.3.14/ext/mysql2/mysql2_ext.c0000644000004100000410000000041112245130153017350 0ustar www-datawww-data#include VALUE mMysql2, cMysql2Error; /* Ruby Extension initializer */ void Init_mysql2() { mMysql2 = rb_define_module("Mysql2"); cMysql2Error = rb_const_get(mMysql2, rb_intern("Error")); init_mysql2_client(); init_mysql2_result(); } mysql2-0.3.14/ext/mysql2/mysql2_ext.h0000644000004100000410000000162112245130153017361 0ustar www-datawww-data#ifndef MYSQL2_EXT #define MYSQL2_EXT /* tell rbx not to use it's caching compat layer by doing this we're making a promise to RBX that we'll never modify the pointers we get back from RSTRING_PTR */ #define RSTRING_NOT_MODIFIED #include #include #ifndef HAVE_UINT #define HAVE_UINT typedef unsigned short ushort; typedef unsigned int uint; #endif #ifdef HAVE_MYSQL_H #include #include #include #include #else #include #include #include #include #endif #ifdef HAVE_RUBY_ENCODING_H #include #endif #ifdef HAVE_RUBY_THREAD_H #include #endif #if defined(__GNUC__) && (__GNUC__ >= 3) #define RB_MYSQL_UNUSED __attribute__ ((unused)) #else #define RB_MYSQL_UNUSED #endif #include #include #endif mysql2-0.3.14/ext/mysql2/wait_for_single_fd.h0000644000004100000410000000154512245130153021103 0ustar www-datawww-data/* * backwards compatibility for pre-1.9.3 C API * * Ruby 1.9.3 provides this API which allows the use of ppoll() on Linux * to minimize select() and malloc() overhead on high-numbered FDs. */ #ifdef HAVE_RB_WAIT_FOR_SINGLE_FD # include #else # define RB_WAITFD_IN 0x001 # define RB_WAITFD_PRI 0x002 # define RB_WAITFD_OUT 0x004 static int my_wait_for_single_fd(int fd, int events, struct timeval *tvp) { fd_set fdset; fd_set *rfds = NULL; fd_set *wfds = NULL; fd_set *efds = NULL; FD_ZERO(&fdset); FD_SET(fd, &fdset); if (events & RB_WAITFD_IN) rfds = &fdset; if (events & RB_WAITFD_OUT) wfds = &fdset; if (events & RB_WAITFD_PRI) efds = &fdset; return rb_thread_select(fd + 1, rfds, wfds, efds, tvp); } #define rb_wait_for_single_fd(fd,events,tvp) \ my_wait_for_single_fd((fd),(events),(tvp)) #endif mysql2-0.3.14/ext/mysql2/client.c0000644000004100000410000011065512245130153016533 0ustar www-datawww-data#include #include #include #ifndef _WIN32 #include #endif #include #include "wait_for_single_fd.h" #include "mysql_enc_name_to_ruby.h" VALUE cMysql2Client; extern VALUE mMysql2, cMysql2Error; static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql; #ifndef HAVE_RB_HASH_DUP static VALUE rb_hash_dup(VALUE other) { return rb_funcall(rb_cHash, rb_intern("[]"), 1, other); } #endif #define REQUIRE_INITIALIZED(wrapper) \ if (!wrapper->initialized) { \ rb_raise(cMysql2Error, "MySQL client is not initialized"); \ } #define REQUIRE_CONNECTED(wrapper) \ REQUIRE_INITIALIZED(wrapper) \ if (!wrapper->connected && !wrapper->reconnect_enabled) { \ rb_raise(cMysql2Error, "closed MySQL connection"); \ } #define REQUIRE_NOT_CONNECTED(wrapper) \ REQUIRE_INITIALIZED(wrapper) \ if (wrapper->connected) { \ rb_raise(cMysql2Error, "MySQL connection is already open"); \ } #define MARK_CONN_INACTIVE(conn) \ wrapper->active_thread = Qnil; #define GET_CLIENT(self) \ mysql_client_wrapper *wrapper; \ Data_Get_Struct(self, mysql_client_wrapper, wrapper) /* * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when * linking against the server itself */ #ifdef LIBMYSQL_VERSION #define MYSQL_LINK_VERSION LIBMYSQL_VERSION #else #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION #endif /* * used to pass all arguments to mysql_real_connect while inside * rb_thread_call_without_gvl */ struct nogvl_connect_args { MYSQL *mysql; const char *host; const char *user; const char *passwd; const char *db; unsigned int port; const char *unix_socket; unsigned long client_flag; }; /* * used to pass all arguments to mysql_send_query while inside * rb_thread_call_without_gvl */ struct nogvl_send_query_args { MYSQL *mysql; VALUE sql; const char *sql_ptr; long sql_len; mysql_client_wrapper *wrapper; }; /* * used to pass all arguments to mysql_select_db while inside * rb_thread_call_without_gvl */ struct nogvl_select_db_args { MYSQL *mysql; char *db; }; /* * non-blocking mysql_*() functions that we won't be wrapping since * they do not appear to hit the network nor issue any interruptible * or blocking system calls. * * - mysql_affected_rows() * - mysql_error() * - mysql_fetch_fields() * - mysql_fetch_lengths() - calls cli_fetch_lengths or emb_fetch_lengths * - mysql_field_count() * - mysql_get_client_info() * - mysql_get_client_version() * - mysql_get_server_info() * - mysql_get_server_version() * - mysql_insert_id() * - mysql_num_fields() * - mysql_num_rows() * - mysql_options() * - mysql_real_escape_string() * - mysql_ssl_set() */ static void rb_mysql_client_mark(void * wrapper) { mysql_client_wrapper * w = wrapper; if (w) { rb_gc_mark(w->encoding); rb_gc_mark(w->active_thread); } } static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) { VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client)); VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client)); VALUE e; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc = rb_default_internal_encoding(); rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); rb_enc_associate(rb_error_msg, conn_enc); rb_enc_associate(rb_sql_state, conn_enc); if (default_internal_enc) { rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc); rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc); } #endif e = rb_exc_new3(cMysql2Error, rb_error_msg); rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client))); rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state); rb_exc_raise(e); return Qnil; } static void *nogvl_init(void *ptr) { MYSQL *client; /* may initialize embedded server and read /etc/services off disk */ client = mysql_init((MYSQL *)ptr); return (void*)(client ? Qtrue : Qfalse); } static void *nogvl_connect(void *ptr) { struct nogvl_connect_args *args = ptr; MYSQL *client; client = mysql_real_connect(args->mysql, args->host, args->user, args->passwd, args->db, args->port, args->unix_socket, args->client_flag); return (void *)(client ? Qtrue : Qfalse); } static void *nogvl_close(void *ptr) { mysql_client_wrapper *wrapper; #ifndef _WIN32 int flags; #endif wrapper = ptr; if (wrapper->connected) { wrapper->active_thread = Qnil; wrapper->connected = 0; /* * we'll send a QUIT message to the server, but that message is more of a * formality than a hard requirement since the socket is getting shutdown * anyways, so ensure the socket write does not block our interpreter * * * if the socket is dead we have no chance of blocking, * so ignore any potential fcntl errors since they don't matter */ #ifndef _WIN32 flags = fcntl(wrapper->client->net.fd, F_GETFL); if (flags > 0 && !(flags & O_NONBLOCK)) fcntl(wrapper->client->net.fd, F_SETFL, flags | O_NONBLOCK); #endif mysql_close(wrapper->client); } return NULL; } static void rb_mysql_client_free(void *ptr) { mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr; wrapper->refcount--; if (wrapper->refcount == 0) { nogvl_close(wrapper); xfree(wrapper->client); xfree(wrapper); } } static VALUE allocate(VALUE klass) { VALUE obj; mysql_client_wrapper * wrapper; obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper); wrapper->encoding = Qnil; wrapper->active_thread = Qnil; wrapper->reconnect_enabled = 0; wrapper->connected = 0; /* means that a database connection is open */ wrapper->initialized = 0; /* means that that the wrapper is initialized */ wrapper->refcount = 1; wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL)); return obj; } /* call-seq: * Mysql2::Client.escape(string) * * Escape +string+ so that it may be used in a SQL statement. * Note that this escape method is not connection encoding aware. * If you need encoding support use Mysql2::Client#escape instead. */ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) { unsigned char *newStr; VALUE rb_str; unsigned long newLen, oldLen; Check_Type(str, T_STRING); oldLen = RSTRING_LEN(str); newStr = xmalloc(oldLen*2+1); newLen = mysql_escape_string((char *)newStr, StringValuePtr(str), oldLen); if (newLen == oldLen) { /* no need to return a new ruby string if nothing changed */ xfree(newStr); return str; } else { rb_str = rb_str_new((const char*)newStr, newLen); #ifdef HAVE_RUBY_ENCODING_H rb_enc_copy(rb_str, str); #endif xfree(newStr); return rb_str; } } static VALUE rb_mysql_client_warning_count(VALUE self) { unsigned int warning_count; GET_CLIENT(self); warning_count = mysql_warning_count(wrapper->client); return UINT2NUM(warning_count); } static VALUE rb_mysql_info(VALUE self) { const char *info; VALUE rb_str; GET_CLIENT(self); info = mysql_info(wrapper->client); if (info == NULL) { return Qnil; } rb_str = rb_str_new2(info); #ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(rb_str, rb_utf8_encoding()); #endif return rb_str; } static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) { struct nogvl_connect_args args; VALUE rv; GET_CLIENT(self); args.host = NIL_P(host) ? NULL : StringValuePtr(host); args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket); args.port = NIL_P(port) ? 0 : NUM2INT(port); args.user = NIL_P(user) ? NULL : StringValuePtr(user); args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass); args.db = NIL_P(database) ? NULL : StringValuePtr(database); args.mysql = wrapper->client; args.client_flag = NUM2ULONG(flags); rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); if (rv == Qfalse) { while (rv == Qfalse && errno == EINTR && !mysql_errno(wrapper->client)) { errno = 0; rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); } if (rv == Qfalse) return rb_raise_mysql2_error(wrapper); } wrapper->connected = 1; return self; } /* * Immediately disconnect from the server, normally the garbage collector * will disconnect automatically when a connection is no longer needed. * Explicitly closing this will free up server resources sooner than waiting * for the garbage collector. */ static VALUE rb_mysql_client_close(VALUE self) { GET_CLIENT(self); if (wrapper->connected) { rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0); } return Qnil; } /* * mysql_send_query is unlikely to block since most queries are small * enough to fit in a socket buffer, but sometimes large UPDATE and * INSERTs will cause the process to block */ static void *nogvl_send_query(void *ptr) { struct nogvl_send_query_args *args = ptr; int rv; rv = mysql_send_query(args->mysql, args->sql_ptr, args->sql_len); return (void*)(rv == 0 ? Qtrue : Qfalse); } static VALUE do_send_query(void *args) { struct nogvl_send_query_args *query_args = args; mysql_client_wrapper *wrapper = query_args->wrapper; if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, we're not active anymore */ MARK_CONN_INACTIVE(self); return rb_raise_mysql2_error(wrapper); } return Qnil; } /* * even though we did rb_thread_select before calling this, a large * response can overflow the socket buffers and cause us to eventually * block while calling mysql_read_query_result */ static void *nogvl_read_query_result(void *ptr) { MYSQL * client = ptr; my_bool res = mysql_read_query_result(client); return (void *)(res == 0 ? Qtrue : Qfalse); } static void *nogvl_do_result(void *ptr, char use_result) { mysql_client_wrapper *wrapper; MYSQL_RES *result; wrapper = (mysql_client_wrapper *)ptr; if(use_result) { result = mysql_use_result(wrapper->client); } else { result = mysql_store_result(wrapper->client); } /* once our result is stored off, this connection is ready for another command to be issued */ wrapper->active_thread = Qnil; return result; } /* mysql_store_result may (unlikely) read rows off the socket */ static void *nogvl_store_result(void *ptr) { return nogvl_do_result(ptr, 0); } static void *nogvl_use_result(void *ptr) { return nogvl_do_result(ptr, 1); } /* call-seq: * client.async_result * * Returns the result for the last async issued query. */ static VALUE rb_mysql_client_async_result(VALUE self) { MYSQL_RES * result; VALUE resultObj; VALUE current, is_streaming; GET_CLIENT(self); /* if we're not waiting on a result, do nothing */ if (NIL_P(wrapper->active_thread)) return Qnil; REQUIRE_CONNECTED(wrapper); if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, mark this connection inactive */ MARK_CONN_INACTIVE(self); return rb_raise_mysql2_error(wrapper); } is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream); if(is_streaming == Qtrue) { result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_use_result, wrapper, RUBY_UBF_IO, 0); } else { result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); } if (result == NULL) { if (mysql_errno(wrapper->client) != 0) { MARK_CONN_INACTIVE(self); rb_raise_mysql2_error(wrapper); } /* no data and no error, so query was not a SELECT */ return Qnil; } current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); RB_GC_GUARD(current); Check_Type(current, T_HASH); resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result); return resultObj; } #ifndef _WIN32 struct async_query_args { int fd; VALUE self; }; static VALUE disconnect_and_raise(VALUE self, VALUE error) { GET_CLIENT(self); wrapper->active_thread = Qnil; wrapper->connected = 0; /* manually close the socket for read/write this feels dirty, but is there another way? */ close(wrapper->client->net.fd); wrapper->client->net.fd = -1; rb_exc_raise(error); return Qnil; } static VALUE do_query(void *args) { struct async_query_args *async_args; struct timeval tv; struct timeval* tvp; long int sec; int retval; VALUE read_timeout; async_args = (struct async_query_args *)args; read_timeout = rb_iv_get(async_args->self, "@read_timeout"); tvp = NULL; if (!NIL_P(read_timeout)) { Check_Type(read_timeout, T_FIXNUM); tvp = &tv; sec = FIX2INT(read_timeout); /* TODO: support partial seconds? also, this check is here for sanity, we also check up in Ruby */ if (sec >= 0) { tvp->tv_sec = sec; } else { rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec); } tvp->tv_usec = 0; } for(;;) { retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp); if (retval == 0) { rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout)); } if (retval < 0) { rb_sys_fail(0); } if (retval > 0) { break; } } return Qnil; } #else static VALUE finish_and_mark_inactive(void *args) { VALUE self; MYSQL_RES *result; self = (VALUE)args; GET_CLIENT(self); if (!NIL_P(wrapper->active_thread)) { /* if we got here, the result hasn't been read off the wire yet so lets do that and then throw it away because we have no way of getting it back up to the caller from here */ result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); mysql_free_result(result); wrapper->active_thread = Qnil; } return Qnil; } #endif /* call-seq: * client.abandon_results! * * When using MULTI_STATEMENTS support, calling this will throw * away any unprocessed results as fast as it can in order to * put the connection back into a state where queries can be issued * again. */ static VALUE rb_mysql_client_abandon_results(VALUE self) { MYSQL_RES *result; int ret; GET_CLIENT(self); while (mysql_more_results(wrapper->client) == 1) { ret = mysql_next_result(wrapper->client); if (ret > 0) { rb_raise_mysql2_error(wrapper); } result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); if (result != NULL) { mysql_free_result(result); } } return Qnil; } /* call-seq: * client.query(sql, options = {}) * * Query the database with +sql+, with optional +options+. For the possible * options, see @@default_query_options on the Mysql2::Client class. */ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { #ifndef _WIN32 struct async_query_args async_args; #endif struct nogvl_send_query_args args; int async = 0; VALUE opts, current; VALUE thread_current = rb_thread_current(); #ifdef HAVE_RUBY_ENCODING_H rb_encoding *conn_enc; #endif GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); args.mysql = wrapper->client; current = rb_hash_dup(rb_iv_get(self, "@query_options")); RB_GC_GUARD(current); Check_Type(current, T_HASH); rb_iv_set(self, "@current_query_options", current); if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) { rb_funcall(current, intern_merge_bang, 1, opts); if (rb_hash_aref(current, sym_async) == Qtrue) { async = 1; } } Check_Type(args.sql, T_STRING); #ifdef HAVE_RUBY_ENCODING_H conn_enc = rb_to_encoding(wrapper->encoding); /* ensure the string is in the encoding the connection is expecting */ args.sql = rb_str_export_to_enc(args.sql, conn_enc); #endif args.sql_ptr = StringValuePtr(args.sql); args.sql_len = RSTRING_LEN(args.sql); /* see if this connection is still waiting on a result from a previous query */ if (NIL_P(wrapper->active_thread)) { /* mark this connection active */ wrapper->active_thread = thread_current; } else if (wrapper->active_thread == thread_current) { rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result"); } else { VALUE inspect = rb_inspect(wrapper->active_thread); const char *thr = StringValueCStr(inspect); rb_raise(cMysql2Error, "This connection is in use by: %s", thr); RB_GC_GUARD(inspect); } args.wrapper = wrapper; #ifndef _WIN32 rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0); if (!async) { async_args.fd = wrapper->client->net.fd; async_args.self = self; rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0); return rb_mysql_client_async_result(self); } else { return Qnil; } #else do_send_query(&args); /* this will just block until the result is ready */ return rb_ensure(rb_mysql_client_async_result, self, finish_and_mark_inactive, self); #endif } /* call-seq: * client.escape(string) * * Escape +string+ so that it may be used in a SQL statement. */ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) { unsigned char *newStr; VALUE rb_str; unsigned long newLen, oldLen; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc; rb_encoding *conn_enc; #endif GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); Check_Type(str, T_STRING); #ifdef HAVE_RUBY_ENCODING_H default_internal_enc = rb_default_internal_encoding(); conn_enc = rb_to_encoding(wrapper->encoding); /* ensure the string is in the encoding the connection is expecting */ str = rb_str_export_to_enc(str, conn_enc); #endif oldLen = RSTRING_LEN(str); newStr = xmalloc(oldLen*2+1); newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, StringValuePtr(str), oldLen); if (newLen == oldLen) { /* no need to return a new ruby string if nothing changed */ xfree(newStr); return str; } else { rb_str = rb_str_new((const char*)newStr, newLen); #ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(rb_str, conn_enc); if (default_internal_enc) { rb_str = rb_str_export_to_enc(rb_str, default_internal_enc); } #endif xfree(newStr); return rb_str; } } static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) { int result; const void *retval = NULL; unsigned int intval = 0; const char * charval = NULL; my_bool boolval; GET_CLIENT(self); REQUIRE_NOT_CONNECTED(wrapper); if (NIL_P(value)) return Qfalse; switch(opt) { case MYSQL_OPT_CONNECT_TIMEOUT: intval = NUM2INT(value); retval = &intval; break; case MYSQL_OPT_READ_TIMEOUT: intval = NUM2INT(value); retval = &intval; break; case MYSQL_OPT_WRITE_TIMEOUT: intval = NUM2INT(value); retval = &intval; break; case MYSQL_OPT_LOCAL_INFILE: intval = (value == Qfalse ? 0 : 1); retval = &intval; break; case MYSQL_OPT_RECONNECT: boolval = (value == Qfalse ? 0 : 1); retval = &boolval; break; case MYSQL_SECURE_AUTH: boolval = (value == Qfalse ? 0 : 1); retval = &boolval; break; case MYSQL_READ_DEFAULT_FILE: charval = (const char *)StringValuePtr(value); retval = charval; break; case MYSQL_READ_DEFAULT_GROUP: charval = (const char *)StringValuePtr(value); retval = charval; break; default: return Qfalse; } result = mysql_options(wrapper->client, opt, retval); /* Zero means success */ if (result != 0) { rb_warn("%s\n", mysql_error(wrapper->client)); } else { /* Special case for reconnect, this option is also stored in the wrapper struct */ if (opt == MYSQL_OPT_RECONNECT) wrapper->reconnect_enabled = boolval; } return (result == 0) ? Qtrue : Qfalse; } /* call-seq: * client.info * * Returns a string that represents the client library version. */ static VALUE rb_mysql_client_info(VALUE self) { VALUE version, client_info; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc; rb_encoding *conn_enc; GET_CLIENT(self); #endif version = rb_hash_new(); #ifdef HAVE_RUBY_ENCODING_H default_internal_enc = rb_default_internal_encoding(); conn_enc = rb_to_encoding(wrapper->encoding); #endif rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version())); client_info = rb_str_new2(mysql_get_client_info()); #ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(client_info, conn_enc); if (default_internal_enc) { client_info = rb_str_export_to_enc(client_info, default_internal_enc); } #endif rb_hash_aset(version, sym_version, client_info); return version; } /* call-seq: * client.server_info * * Returns a string that represents the server version number */ static VALUE rb_mysql_client_server_info(VALUE self) { VALUE version, server_info; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc; rb_encoding *conn_enc; #endif GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); #ifdef HAVE_RUBY_ENCODING_H default_internal_enc = rb_default_internal_encoding(); conn_enc = rb_to_encoding(wrapper->encoding); #endif version = rb_hash_new(); rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client))); server_info = rb_str_new2(mysql_get_server_info(wrapper->client)); #ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(server_info, conn_enc); if (default_internal_enc) { server_info = rb_str_export_to_enc(server_info, default_internal_enc); } #endif rb_hash_aset(version, sym_version, server_info); return version; } /* call-seq: * client.socket * * Return the file descriptor number for this client. */ static VALUE rb_mysql_client_socket(VALUE self) { GET_CLIENT(self); #ifndef _WIN32 { int fd_set_fd; REQUIRE_CONNECTED(wrapper); fd_set_fd = wrapper->client->net.fd; return INT2NUM(fd_set_fd); } #else rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows"); #endif } /* call-seq: * client.last_id * * Returns the value generated for an AUTO_INCREMENT column by the previous INSERT or UPDATE * statement. */ static VALUE rb_mysql_client_last_id(VALUE self) { GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); return ULL2NUM(mysql_insert_id(wrapper->client)); } /* call-seq: * client.affected_rows * * returns the number of rows changed, deleted, or inserted by the last statement * if it was an UPDATE, DELETE, or INSERT. */ static VALUE rb_mysql_client_affected_rows(VALUE self) { my_ulonglong retVal; GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); retVal = mysql_affected_rows(wrapper->client); if (retVal == (my_ulonglong)-1) { rb_raise_mysql2_error(wrapper); } return ULL2NUM(retVal); } /* call-seq: * client.thread_id * * Returns the thread ID of the current connection. */ static VALUE rb_mysql_client_thread_id(VALUE self) { unsigned long retVal; GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); retVal = mysql_thread_id(wrapper->client); return ULL2NUM(retVal); } static void *nogvl_select_db(void *ptr) { struct nogvl_select_db_args *args = ptr; if (mysql_select_db(args->mysql, args->db) == 0) return (void *)Qtrue; else return (void *)Qfalse; } /* call-seq: * client.select_db(name) * * Causes the database specified by +name+ to become the default (current) * database on the connection specified by mysql. */ static VALUE rb_mysql_client_select_db(VALUE self, VALUE db) { struct nogvl_select_db_args args; GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); args.mysql = wrapper->client; args.db = StringValuePtr(db); if (rb_thread_call_without_gvl(nogvl_select_db, &args, RUBY_UBF_IO, 0) == Qfalse) rb_raise_mysql2_error(wrapper); return db; } static void *nogvl_ping(void *ptr) { MYSQL *client = ptr; return (void *)(mysql_ping(client) == 0 ? Qtrue : Qfalse); } /* call-seq: * client.ping * * Checks whether the connection to the server is working. If the connection * has gone down and auto-reconnect is enabled an attempt to reconnect is made. * If the connection is down and auto-reconnect is disabled, ping returns an * error. */ static VALUE rb_mysql_client_ping(VALUE self) { GET_CLIENT(self); if (!wrapper->connected) { return Qfalse; } else { return (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0); } } /* call-seq: * client.more_results? * * Returns true or false if there are more results to process. */ static VALUE rb_mysql_client_more_results(VALUE self) { GET_CLIENT(self); if (mysql_more_results(wrapper->client) == 0) return Qfalse; else return Qtrue; } /* call-seq: * client.next_result * * Fetch the next result set from the server. * Returns nothing. */ static VALUE rb_mysql_client_next_result(VALUE self) { int ret; GET_CLIENT(self); ret = mysql_next_result(wrapper->client); if (ret > 0) { rb_raise_mysql2_error(wrapper); return Qfalse; } else if (ret == 0) { return Qtrue; } else { return Qfalse; } } /* call-seq: * client.store_result * * Return the next result object from a query which * yielded multiple result sets. */ static VALUE rb_mysql_client_store_result(VALUE self) { MYSQL_RES * result; VALUE resultObj; VALUE current; GET_CLIENT(self); result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); if (result == NULL) { if (mysql_errno(wrapper->client) != 0) { rb_raise_mysql2_error(wrapper); } /* no data and no error, so query was not a SELECT */ return Qnil; } current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); RB_GC_GUARD(current); Check_Type(current, T_HASH); resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result); return resultObj; } #ifdef HAVE_RUBY_ENCODING_H /* call-seq: * client.encoding * * Returns the encoding set on the client. */ static VALUE rb_mysql_client_encoding(VALUE self) { GET_CLIENT(self); return wrapper->encoding; } #endif /* call-seq: * client.reconnect = true * * Enable or disable the automatic reconnect behavior of libmysql. * Read http://dev.mysql.com/doc/refman/5.5/en/auto-reconnect.html * for more information. */ static VALUE set_reconnect(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_OPT_RECONNECT, value); } static VALUE set_local_infile(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_OPT_LOCAL_INFILE, value); } static VALUE set_connect_timeout(VALUE self, VALUE value) { long int sec; Check_Type(value, T_FIXNUM); sec = FIX2INT(value); if (sec < 0) { rb_raise(cMysql2Error, "connect_timeout must be a positive integer, you passed %ld", sec); } return _mysql_client_options(self, MYSQL_OPT_CONNECT_TIMEOUT, value); } static VALUE set_read_timeout(VALUE self, VALUE value) { long int sec; Check_Type(value, T_FIXNUM); sec = FIX2INT(value); if (sec < 0) { rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec); } /* Set the instance variable here even though _mysql_client_options might not succeed, because the timeout is used in other ways elsewhere */ rb_iv_set(self, "@read_timeout", value); return _mysql_client_options(self, MYSQL_OPT_READ_TIMEOUT, value); } static VALUE set_write_timeout(VALUE self, VALUE value) { long int sec; Check_Type(value, T_FIXNUM); sec = FIX2INT(value); if (sec < 0) { rb_raise(cMysql2Error, "write_timeout must be a positive integer, you passed %ld", sec); } return _mysql_client_options(self, MYSQL_OPT_WRITE_TIMEOUT, value); } static VALUE set_charset_name(VALUE self, VALUE value) { char *charset_name; #ifdef HAVE_RUBY_ENCODING_H size_t charset_name_len; const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb; rb_encoding *enc; VALUE rb_enc; #endif GET_CLIENT(self); charset_name = RSTRING_PTR(value); #ifdef HAVE_RUBY_ENCODING_H charset_name_len = RSTRING_LEN(value); mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, charset_name_len); if (mysql2rb == NULL || mysql2rb->rb_name == NULL) { VALUE inspect = rb_inspect(value); rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect)); } else { enc = rb_enc_find(mysql2rb->rb_name); rb_enc = rb_enc_from_encoding(enc); wrapper->encoding = rb_enc; } #endif if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) { /* TODO: warning - unable to set charset */ rb_warn("%s\n", mysql_error(wrapper->client)); } return value; } static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) { GET_CLIENT(self); mysql_ssl_set(wrapper->client, NIL_P(key) ? NULL : StringValuePtr(key), NIL_P(cert) ? NULL : StringValuePtr(cert), NIL_P(ca) ? NULL : StringValuePtr(ca), NIL_P(capath) ? NULL : StringValuePtr(capath), NIL_P(cipher) ? NULL : StringValuePtr(cipher)); return self; } static VALUE set_secure_auth(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_SECURE_AUTH, value); } static VALUE set_read_default_file(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_READ_DEFAULT_FILE, value); } static VALUE set_read_default_group(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_READ_DEFAULT_GROUP, value); } static VALUE initialize_ext(VALUE self) { GET_CLIENT(self); if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { /* TODO: warning - not enough memory? */ return rb_raise_mysql2_error(wrapper); } wrapper->initialized = 1; return self; } void init_mysql2_client() { /* verify the libmysql we're about to use was the version we were built against https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99 */ int i; int dots = 0; const char *lib = mysql_get_client_info(); for (i = 0; lib[i] != 0 && MYSQL_LINK_VERSION[i] != 0; i++) { if (lib[i] == '.') { dots++; /* we only compare MAJOR and MINOR */ if (dots == 2) break; } if (lib[i] != MYSQL_LINK_VERSION[i]) { rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_LINK_VERSION, lib); return; } } #if 0 mMysql2 = rb_define_module("Mysql2"); Teach RDoc about Mysql2 constant. #endif cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject); rb_define_alloc_func(cMysql2Client, allocate); rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1); rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0); rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1); rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0); rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1); rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0); rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0); rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0); rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0); rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0); rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0); rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0); rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0); rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1); rb_define_method(cMysql2Client, "more_results?", rb_mysql_client_more_results, 0); rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0); rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0); rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1); rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0); rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0); #ifdef HAVE_RUBY_ENCODING_H rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0); #endif rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1); rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1); rb_define_private_method(cMysql2Client, "write_timeout=", set_write_timeout, 1); rb_define_private_method(cMysql2Client, "local_infile=", set_local_infile, 1); rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1); rb_define_private_method(cMysql2Client, "secure_auth=", set_secure_auth, 1); rb_define_private_method(cMysql2Client, "default_file=", set_read_default_file, 1); rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1); rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0); rb_define_private_method(cMysql2Client, "connect", rb_connect, 7); sym_id = ID2SYM(rb_intern("id")); sym_version = ID2SYM(rb_intern("version")); sym_async = ID2SYM(rb_intern("async")); sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); sym_as = ID2SYM(rb_intern("as")); sym_array = ID2SYM(rb_intern("array")); sym_stream = ID2SYM(rb_intern("stream")); intern_merge = rb_intern("merge"); intern_merge_bang = rb_intern("merge!"); intern_error_number_eql = rb_intern("error_number="); intern_sql_state_eql = rb_intern("sql_state="); #ifdef CLIENT_LONG_PASSWORD rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), LONG2NUM(CLIENT_LONG_PASSWORD)); #endif #ifdef CLIENT_FOUND_ROWS rb_const_set(cMysql2Client, rb_intern("FOUND_ROWS"), LONG2NUM(CLIENT_FOUND_ROWS)); #endif #ifdef CLIENT_LONG_FLAG rb_const_set(cMysql2Client, rb_intern("LONG_FLAG"), LONG2NUM(CLIENT_LONG_FLAG)); #endif #ifdef CLIENT_CONNECT_WITH_DB rb_const_set(cMysql2Client, rb_intern("CONNECT_WITH_DB"), LONG2NUM(CLIENT_CONNECT_WITH_DB)); #endif #ifdef CLIENT_NO_SCHEMA rb_const_set(cMysql2Client, rb_intern("NO_SCHEMA"), LONG2NUM(CLIENT_NO_SCHEMA)); #endif #ifdef CLIENT_COMPRESS rb_const_set(cMysql2Client, rb_intern("COMPRESS"), LONG2NUM(CLIENT_COMPRESS)); #endif #ifdef CLIENT_ODBC rb_const_set(cMysql2Client, rb_intern("ODBC"), LONG2NUM(CLIENT_ODBC)); #endif #ifdef CLIENT_LOCAL_FILES rb_const_set(cMysql2Client, rb_intern("LOCAL_FILES"), LONG2NUM(CLIENT_LOCAL_FILES)); #endif #ifdef CLIENT_IGNORE_SPACE rb_const_set(cMysql2Client, rb_intern("IGNORE_SPACE"), LONG2NUM(CLIENT_IGNORE_SPACE)); #endif #ifdef CLIENT_PROTOCOL_41 rb_const_set(cMysql2Client, rb_intern("PROTOCOL_41"), LONG2NUM(CLIENT_PROTOCOL_41)); #endif #ifdef CLIENT_INTERACTIVE rb_const_set(cMysql2Client, rb_intern("INTERACTIVE"), LONG2NUM(CLIENT_INTERACTIVE)); #endif #ifdef CLIENT_SSL rb_const_set(cMysql2Client, rb_intern("SSL"), LONG2NUM(CLIENT_SSL)); #endif #ifdef CLIENT_IGNORE_SIGPIPE rb_const_set(cMysql2Client, rb_intern("IGNORE_SIGPIPE"), LONG2NUM(CLIENT_IGNORE_SIGPIPE)); #endif #ifdef CLIENT_TRANSACTIONS rb_const_set(cMysql2Client, rb_intern("TRANSACTIONS"), LONG2NUM(CLIENT_TRANSACTIONS)); #endif #ifdef CLIENT_RESERVED rb_const_set(cMysql2Client, rb_intern("RESERVED"), LONG2NUM(CLIENT_RESERVED)); #endif #ifdef CLIENT_SECURE_CONNECTION rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(CLIENT_SECURE_CONNECTION)); #endif #ifdef CLIENT_MULTI_STATEMENTS rb_const_set(cMysql2Client, rb_intern("MULTI_STATEMENTS"), LONG2NUM(CLIENT_MULTI_STATEMENTS)); #endif #ifdef CLIENT_PS_MULTI_RESULTS rb_const_set(cMysql2Client, rb_intern("PS_MULTI_RESULTS"), LONG2NUM(CLIENT_PS_MULTI_RESULTS)); #endif #ifdef CLIENT_SSL_VERIFY_SERVER_CERT rb_const_set(cMysql2Client, rb_intern("SSL_VERIFY_SERVER_CERT"), LONG2NUM(CLIENT_SSL_VERIFY_SERVER_CERT)); #endif #ifdef CLIENT_REMEMBER_OPTIONS rb_const_set(cMysql2Client, rb_intern("REMEMBER_OPTIONS"), LONG2NUM(CLIENT_REMEMBER_OPTIONS)); #endif #ifdef CLIENT_ALL_FLAGS rb_const_set(cMysql2Client, rb_intern("ALL_FLAGS"), LONG2NUM(CLIENT_ALL_FLAGS)); #endif #ifdef CLIENT_BASIC_FLAGS rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"), LONG2NUM(CLIENT_BASIC_FLAGS)); #endif } mysql2-0.3.14/ext/mysql2/client.h0000644000004100000410000000236612245130153016537 0ustar www-datawww-data#ifndef MYSQL2_CLIENT_H #define MYSQL2_CLIENT_H #ifndef HAVE_RB_THREAD_CALL_WITHOUT_GVL #ifdef HAVE_RB_THREAD_BLOCKING_REGION /* emulate rb_thread_call_without_gvl with rb_thread_blocking_region */ #define rb_thread_call_without_gvl(func, data1, ubf, data2) \ rb_thread_blocking_region((rb_blocking_function_t *)func, data1, ubf, data2) #else /* ! HAVE_RB_THREAD_BLOCKING_REGION */ /* * partial emulation of the 2.0 rb_thread_call_without_gvl under 1.8, * this is enough for dealing with blocking I/O functions in the * presence of threads. */ #include #define RUBY_UBF_IO ((rb_unblock_function_t *)-1) typedef void rb_unblock_function_t(void *); static void * rb_thread_call_without_gvl( void *(*func)(void *), void *data1, RB_MYSQL_UNUSED rb_unblock_function_t *ubf, RB_MYSQL_UNUSED void *data2) { void *rv; TRAP_BEG; rv = func(data1); TRAP_END; return rv; } #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */ #endif /* ! HAVE_RB_THREAD_CALL_WITHOUT_GVL */ void init_mysql2_client(); typedef struct { VALUE encoding; VALUE active_thread; /* rb_thread_current() or Qnil */ int reconnect_enabled; int active; int connected; int initialized; int refcount; int freed; MYSQL *client; } mysql_client_wrapper; #endif mysql2-0.3.14/ext/mysql2/mysql_enc_to_ruby.h0000644000004100000410000000557412245130153021022 0ustar www-datawww-dataconst char *mysql2_mysql_enc_to_rb[] = { "Big5", "ISO-8859-2", NULL, "CP850", "ISO-8859-1", NULL, "KOI8-R", "ISO-8859-1", "ISO-8859-2", NULL, "US-ASCII", "eucJP-ms", "Shift_JIS", "Windows-1251", "ISO-8859-1", "ISO-8859-8", NULL, "TIS-620", "EUC-KR", "ISO-8859-13", "ISO-8859-2", "KOI8-R", "Windows-1251", "GB2312", "ISO-8859-7", "Windows-1250", "ISO-8859-2", "GBK", "Windows-1257", "ISO-8859-9", "ISO-8859-1", NULL, "UTF-8", "Windows-1250", "UTF-16BE", "IBM866", NULL, "macCentEuro", "macRoman", "CP852", "ISO-8859-13", "ISO-8859-13", "macCentEuro", "Windows-1250", "UTF-8", "UTF-8", "ISO-8859-1", "ISO-8859-1", "ISO-8859-1", "Windows-1251", "Windows-1251", "Windows-1251", "macRoman", "UTF-16", "UTF-16", NULL, "Windows-1256", "Windows-1257", "Windows-1257", "UTF-32", "UTF-32", NULL, "ASCII-8BIT", NULL, "US-ASCII", "Windows-1250", "Windows-1256", "IBM866", NULL, "ISO-8859-7", "ISO-8859-8", NULL, NULL, "KOI8-R", "KOI8-R", NULL, "ISO-8859-2", "ISO-8859-9", "ISO-8859-13", "CP850", "CP852", NULL, "UTF-8", "Big5", "EUC-KR", "GB2312", "GBK", "Shift_JIS", "TIS-620", "UTF-16BE", "eucJP-ms", NULL, NULL, "ISO-8859-1", "Windows-31J", "Windows-31J", "eucJP-ms", "eucJP-ms", "Windows-1250", NULL, "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", "UTF-16", NULL, NULL, NULL, NULL, NULL, NULL, NULL, "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", "UTF-32", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8" }; mysql2-0.3.14/README.md0000644000004100000410000004330012245130153014331 0ustar www-datawww-data# Mysql2 - A modern, simple and very fast Mysql library for Ruby - binding to libmysql [![Build Status](https://travis-ci.org/brianmario/mysql2.png)](https://travis-ci.org/brianmario/mysql2) The Mysql2 gem is meant to serve the extremely common use-case of connecting, querying and iterating on results. Some database libraries out there serve as direct 1:1 mappings of the already complex C API's available. This one is not. It also forces the use of UTF-8 [or binary] for the connection [and all strings in 1.9, unless Encoding.default_internal is set then it'll convert from UTF-8 to that encoding] and uses encoding-aware MySQL API calls where it can. The API consists of two classes: Mysql2::Client - your connection to the database Mysql2::Result - returned from issuing a #query on the connection. It includes Enumerable. ## Installing ### OSX / Linux ``` sh gem install mysql2 ``` This gem links against MySQL's `libmysqlclient` C shared library. You may need to install a package such as `libmysqlclient-dev`, `mysql-devel`, or other appropriate package for your system. By default, the mysql2 gem will try to find a copy of MySQL in this order: * Option `--with-mysql-dir`, if provided (see below). * Option `--with-mysql-config`, if provided (see below). * Several typical paths for `msyql_config` (default for the majority of users). * The directory `/usr/local`. ### Configuration options Use these options by `gem install mysql2 -- [--optionA] [--optionB=argument]`. * `--with-mysql-dir[=/path/to/mysqldir]` - Specify the directory where MySQL is installed. The mysql2 gem will not use `mysql_config`, but will instead look at `mysqldir/lib` and `mysqldir/include` for the library and header files. This option is mutually exclusive with `--with-mysql-config`. * `--with-mysql-config[=/path/to/mysql_config]` - Specify a path to the `mysql_config` binary provided by your copy of MySQL. The mysql2 gem will ask this `mysql_config` binary about the compiler and linker arguments needed. This option is mutually exclusive with `--with-mysql-dir`. * `--with-mysql-rpath=/path/to/mysql/lib` / `--without-mysql-rpath` - Override the runtime path used to find the MySQL libraries. This may be needed if you deploy to a system where these libraries are located somewhere different than on your build system. This overrides any rpath calculated by default or by the options above. ### Windows First, make sure you have the DevKit installed (http://rubyinstaller.org/downloads/) and its variables are loaded by running devkit\devktvars.bat . Next, you need a MySQL library to link against. If you have MySQL loaded on your development machine, you can use that. If not, you will need to either copy the MySQL directory from your server, or else obtain a copy of the MySQL C connector: http://dev.mysql.com/downloads/connector/c/ If you're using the connector, I recommend just getting the .zip file and unzipping it someplace convenient. Now you can install mysql2. You must use the `--with-mysql-dir` option to tell gem where your MySQL library files are. For example, if you unzipped the connector to c:\mysql-connector-c-6.1.1-win32 you would install the gem like this: gem install mysql2 -- --with-mysql-dir=c:\mysql-connector-c-6.1.1-win32 Finally, you must copy libmysql.dll from the lib subdirectory of your MySQL or MySQL connector directory into your ruby\bin directory. In the above example, libmysql.dll would be located at c:\mysql-connector-c-6.1.1-win32\lib . ## Usage Connect to a database: ``` ruby # this takes a hash of options, almost all of which map directly # to the familiar database.yml in rails # See http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html client = Mysql2::Client.new(:host => "localhost", :username => "root") ``` Then query it: ``` ruby results = client.query("SELECT * FROM users WHERE group='githubbers'") ``` Need to escape something first? ``` ruby escaped = client.escape("gi'thu\"bbe\0r's") results = client.query("SELECT * FROM users WHERE group='#{escaped}'") ``` You can get a count of your results with `results.count`. Finally, iterate over the results: ``` ruby results.each do |row| # conveniently, row is a hash # the keys are the fields, as you'd expect # the values are pre-built ruby primitives mapped from their corresponding field types in MySQL # Here's an otter: http://farm1.static.flickr.com/130/398077070_b8795d0ef3_b.jpg end ``` Or, you might just keep it simple: ``` ruby client.query("SELECT * FROM users WHERE group='githubbers'").each do |row| # do something with row, it's ready to rock end ``` How about with symbolized keys? ``` ruby # NOTE: the :symbolize_keys and future options will likely move to the #query method soon client.query("SELECT * FROM users WHERE group='githubbers'").each(:symbolize_keys => true) do |row| # do something with row, it's ready to rock end ``` You can get the headers and the columns in the order that they were returned by the query like this: ``` ruby headers = results.fields # <= that's an array of field names, in order results.each(:as => :array) do |row| # Each row is an array, ordered the same as the query results # An otter's den is called a "holt" or "couch" end ``` ## Connection options You may set the following connection options in Mysql2::Client.new(...): ``` ruby Mysql2::Client.new( :host, :username, :password, :port, :database, :socket = '/path/to/mysql.sock', :flags = REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION | MULTI_STATEMENTS, :encoding = 'utf8', :read_timeout = seconds, :write_timeout = seconds, :connect_timeout = seconds, :reconnect = true/false, :local_infile = true/false, :secure_auth = true/false, :default_file = '/path/to/my.cfg', :default_group = 'my.cfg section' ) ``` ### Multiple result sets You can also retrieve multiple result sets. For this to work you need to connect with flags `Mysql2::Client::MULTI_STATEMENTS`. Using multiple result sets is normally used when calling stored procedures that return more than one result set ``` ruby client = Mysql2::Client.new(:host => "localhost", :username => "root", :flags => Mysql2::Client::MULTI_STATEMENTS ) result = client.query( 'CALL sp_customer_list( 25, 10 )') # result now contains the first result set while ( client.next_result) result = client.store_result # result now contains the next result set end ``` See https://gist.github.com/1367987 for using MULTI_STATEMENTS with Active Record. ### Secure auth Starting wih MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this). When secure_auth is enabled, the server will refuse a connection if the account password is stored in old pre-MySQL 4.1 format. The MySQL 5.6.5 client library may also refuse to attempt a connection if provided an older format password. To bypass this restriction in the client, pass the option :secure_auth => false to Mysql2::Client.new(). If using ActiveRecord, your database.yml might look something like this: ``` development: adapter: mysql2 encoding: utf8 database: my_db_name username: root password: my_password host: 127.0.0.1 port: 3306 secure_auth: false ``` ### Reading a MySQL config file You may read configuration options from a MySQL configuration file by passing the `:default_file` and `:default_group` paramters. For example: ``` client = Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client') ``` ## Cascading config The default config hash is at: ``` ruby Mysql2::Client.default_query_options ``` which defaults to: ``` ruby {:async => false, :as => :hash, :symbolize_keys => false} ``` that can be used as so: ``` ruby # these are the defaults all Mysql2::Client instances inherit Mysql2::Client.default_query_options.merge!(:as => :array) ``` or ``` ruby # this will change the defaults for all future results returned by the #query method _for this connection only_ c = Mysql2::Client.new c.query_options.merge!(:symbolize_keys => true) ``` or ``` ruby # this will set the options for the Mysql2::Result instance returned from the #query method c = Mysql2::Client.new c.query(sql, :symbolize_keys => true) ``` ## Result types ### Array of Arrays Pass the `:as => :array` option to any of the above methods of configuration ### Array of Hashes The default result type is set to :hash, but you can override a previous setting to something else with :as => :hash ### Others... I may add support for `:as => :csv` or even `:as => :json` to allow for *much* more efficient generation of those data types from result sets. If you'd like to see either of these (or others), open an issue and start bugging me about it ;) ### Timezones Mysql2 now supports two timezone options: ``` ruby :database_timezone # this is the timezone Mysql2 will assume fields are already stored as, and will use this when creating the initial Time objects in ruby :application_timezone # this is the timezone Mysql2 will convert to before finally handing back to the caller ``` In other words, if `:database_timezone` is set to `:utc` - Mysql2 will create the Time objects using `Time.utc(...)` from the raw value libmysql hands over initially. Then, if `:application_timezone` is set to say - `:local` - Mysql2 will then convert the just-created UTC Time object to local time. Both options only allow two values - `:local` or `:utc` - with the exception that `:application_timezone` can be [and defaults to] nil ### Casting "boolean" columns You can now tell Mysql2 to cast `tinyint(1)` fields to boolean values in Ruby with the `:cast_booleans` option. ``` ruby client = Mysql2::Client.new result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans => true) ``` ### Skipping casting Mysql2 casting is fast, but not as fast as not casting data. In rare cases where typecasting is not needed, it will be faster to disable it by providing :cast => false. ``` ruby client = Mysql2::Client.new result = client.query("SELECT * FROM table", :cast => false) ``` Here are the results from the `query_without_mysql_casting.rb` script in the benchmarks folder: ``` sh user system total real Mysql2 (cast: true) 0.340000 0.000000 0.340000 ( 0.405018) Mysql2 (cast: false) 0.160000 0.010000 0.170000 ( 0.209937) Mysql 0.080000 0.000000 0.080000 ( 0.129355) do_mysql 0.520000 0.010000 0.530000 ( 0.574619) ``` Although Mysql2 performs reasonably well at retrieving uncasted data, it (currently) is not as fast as the Mysql gem. In spite of this small disadvantage, Mysql2 still sports a friendlier interface and doesn't block the entire ruby process when querying. ### Async NOTE: Not supported on Windows. `Mysql2::Client` takes advantage of the MySQL C API's (undocumented) non-blocking function mysql_send_query for *all* queries. But, in order to take full advantage of it in your Ruby code, you can do: ``` ruby client.query("SELECT sleep(5)", :async => true) ``` Which will return nil immediately. At this point you'll probably want to use some socket monitoring mechanism like EventMachine or even IO.select. Once the socket becomes readable, you can do: ``` ruby # result will be a Mysql2::Result instance result = client.async_result ``` NOTE: Because of the way MySQL's query API works, this method will block until the result is ready. So if you really need things to stay async, it's best to just monitor the socket with something like EventMachine. If you need multiple query concurrency take a look at using a connection pool. ### Row Caching By default, Mysql2 will cache rows that have been created in Ruby (since this happens lazily). This is especially helpful since it saves the cost of creating the row in Ruby if you were to iterate over the collection again. If you only plan on using each row once, then it's much more efficient to disable this behavior by setting the `:cache_rows` option to false. This would be helpful if you wanted to iterate over the results in a streaming manner. Meaning the GC would cleanup rows you don't need anymore as you're iterating over the result set. ### Streaming `Mysql2::Client` can optionally only fetch rows from the server on demand by setting `:stream => true`. This is handy when handling very large result sets which might not fit in memory on the client. ``` ruby result = client.query("SELECT * FROM really_big_Table", :stream => true) ``` There are a few things that need to be kept in mind while using streaming: * `:cache_rows` is ignored currently. (if you want to use `:cache_rows` you probably don't want to be using `:stream`) * You must fetch all rows in the result set of your query before you can make new queries. (i.e. with `Mysql2::Result#each`) Read more about the consequences of using `mysql_use_result` (what streaming is implemented with) here: http://dev.mysql.com/doc/refman/5.0/en/mysql-use-result.html. ## Active Record To use the Active Record driver (with or without rails), all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2". That was easy right? :) NOTE: as of 0.3.0, and Active Record 3.1 - the Active Record adapter has been pulled out of this gem and into Active Record itself. If you need to use mysql2 with Rails versions < 3.1 make sure and specify `gem "mysql2", "~> 0.2.7"` in your Gemfile ## Asynchronous Active Record Please see the [em-synchrony](https://github.com/igrigorik/em-synchrony) project for details about using EventMachine with mysql2 and Rails. ## Sequel The Sequel adapter was pulled out into Sequel core (will be part of the next release) and can be used by specifying the "mysql2://" prefix to your connection specification. ## EventMachine The mysql2 EventMachine deferrable api allows you to make async queries using EventMachine, while specifying callbacks for success for failure. Here's a simple example: ``` ruby require 'mysql2/em' EM.run do client1 = Mysql2::EM::Client.new defer1 = client1.query "SELECT sleep(3) as first_query" defer1.callback do |result| puts "Result: #{result.to_a.inspect}" end client2 = Mysql2::EM::Client.new defer2 = client2.query "SELECT sleep(1) second_query" defer2.callback do |result| puts "Result: #{result.to_a.inspect}" end end ``` ## Lazy Everything Well... almost ;) Field name strings/symbols are shared across all the rows so only one object is ever created to represent the field name for an entire dataset. Rows themselves are lazily created in ruby-land when an attempt to yield it is made via #each. For example, if you were to yield 4 rows from a 100 row dataset, only 4 hashes will be created. The rest will sit and wait in C-land until you want them (or when the GC goes to cleanup your `Mysql2::Result` instance). Now say you were to iterate over that same collection again, this time yielding 15 rows - the 4 previous rows that had already been turned into ruby hashes would be pulled from an internal cache, then 11 more would be created and stored in that cache. Once the entire dataset has been converted into ruby objects, Mysql2::Result will free the Mysql C result object as it's no longer needed. This caching behavior can be disabled by setting the :cache_rows option to false. As for field values themselves, I'm workin on it - but expect that soon. ## Compatibility The specs pass on my system (SL 10.6.3, x86_64) in these rubies: * 1.8.7-p249 * ree-1.8.7-2010.01 * 1.9.1-p378 * ruby-trunk * rbx-head - broken at the moment, working with the rbx team for a solution The Active Record driver should work on 2.3.5 and 3.0 ## Yeah... but why? Someone: Dude, the Mysql gem works fiiiiiine. Me: It sure does, but it only hands you nil and strings for field values. Leaving you to convert them into proper Ruby types in Ruby-land - which is slow as balls. Someone: OK fine, but do_mysql can already give me back values with Ruby objects mapped to MySQL types. Me: Yep, but it's API is considerably more complex *and* can be ~2x slower. ## Benchmarks Performing a basic "SELECT * FROM" query on a table with 30k rows and fields of nearly every Ruby-representable data type, then iterating over every row using an #each like method yielding a block: These results are from the `query_with_mysql_casting.rb` script in the benchmarks folder ``` sh user system total real Mysql2 0.750000 0.180000 0.930000 ( 1.821655) do_mysql 1.650000 0.200000 1.850000 ( 2.811357) Mysql 7.500000 0.210000 7.710000 ( 8.065871) ``` ## Development To run the tests, you can use RVM and Bundler to create a pristine environment for mysql2 development/hacking. Use 'bundle install' to install the necessary development and testing gems: ``` sh bundle install rake ``` The tests require the "test" database to exist, and expect to connect both as root and the running user, both with a blank password: ``` sql CREATE DATABASE test; CREATE USER ''@'localhost' IDENTIFIED BY ''; GRANT ALL PRIVILEGES ON test.* TO ''@'localhost'; ``` You can change these defaults in the spec/configuration.yml which is generated automatically when you run rake (or explicitly `rake spec/configuration.yml`). For a normal installation on a Mac, you most likely do not need to do anything, though. ## Special Thanks * Eric Wong - for the contribution (and the informative explanations) of some thread-safety, non-blocking I/O and cleanup patches. You rock dude * Yury Korolev (http://github.com/yury) - for TONS of help testing the Active Record adapter * Aaron Patterson (http://github.com/tenderlove) - tons of contributions, suggestions and general badassness * Mike Perham (http://github.com/mperham) - Async Active Record adapter (uses Fibers and EventMachine) * Aaron Stone (http://github.com/sodabrew) - additional client settings, local files, microsecond time, maintenance support.