mysql2-0.4.3/0000755000004100000410000000000012663414022012775 5ustar www-datawww-datamysql2-0.4.3/examples/0000755000004100000410000000000012663414022014613 5ustar www-datawww-datamysql2-0.4.3/examples/eventmachine.rb0000644000004100000410000000071712663414022017613 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 end mysql2-0.4.3/examples/threaded.rb0000644000004100000410000000110012663414022016710 0ustar www-datawww-data# encoding: utf-8 $LOAD_PATH.unshift 'lib' require 'mysql2' require 'timeout' # Should never exceed worst case 3.5 secs across all 20 threads Timeout.timeout(3.5) do 20.times.map do 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.each(&:join) end mysql2-0.4.3/spec/0000755000004100000410000000000012663414022013727 5ustar www-datawww-datamysql2-0.4.3/spec/rcov.opts0000644000004100000410000000010112663414022015577 0ustar www-datawww-data--exclude spec,gem --text-summary --sort coverage --sort-reverse mysql2-0.4.3/spec/em/0000755000004100000410000000000012663414022014330 5ustar www-datawww-datamysql2-0.4.3/spec/em/em_spec.rb0000644000004100000410000001015412663414022016271 0ustar www-datawww-data# encoding: UTF-8 require 'spec_helper' begin require 'eventmachine' require 'mysql2/em' RSpec.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 expect(results[0].keys).to include("second_query") expect(results[1].keys).to 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 expect(results[0].keys).to include("first_query") expect(results[1].keys).to include("second_query") end it "should not swallow exceptions raised in callbacks" do expect { EM.run do client = Mysql2::EM::Client.new DatabaseCredentials['root'] defer = client.query "SELECT sleep(0.1) as first_query" defer.callback do client.close fail 'some error' end defer.errback do # This _shouldn't_ be run, but it needed to prevent the specs from # freezing if this test fails. EM.stop_event_loop end end }.to raise_error('some 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 { allow(client).to receive(: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 # 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 expect(errors).to eq([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 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 callbacks_run << :errback EM.stop_event_loop end end end expect(callbacks_run).to eq([:errback]) end end it "should not raise error when closing client with no query running" do callbacks_run = [] EM.run do client = Mysql2::EM::Client.new DatabaseCredentials['root'] defer = client.query("select sleep(0.025)") defer.callback do callbacks_run << :callback end defer.errback do callbacks_run << :errback end EM.add_timer(0.1) do expect(callbacks_run).to eq([:callback]) expect { client.close }.not_to raise_error EM.stop_event_loop end end end end rescue LoadError puts "EventMachine not installed, skipping the specs that use it" end mysql2-0.4.3/spec/ssl/0000755000004100000410000000000012663414022014530 5ustar www-datawww-datamysql2-0.4.3/spec/ssl/client-key.pem0000644000004100000410000000321312663414022017276 0ustar www-datawww-data-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA1ZuBf1FVJqil7/LvnXqPd43ujo0xqbFy7QrqmM5U/UM3ggMC f2Gr2/WoZJGPTk1NFaiUyM5mhSlgi0/SGPEp9JMUuH+Uiv9UwmOFl9Em3FXQQ8SG 7fV7651uAUskNgfEqoy+f+uvi1P155rHNDx7Yw6i+wwfpLGTU0boMnLL6cO/KcIb Zlx4/2Lqr5sYbpIqhz46bbG+fIhvepruH9h7WVWqAibTqymYrA3T03O/HWTOqfq0 3gM7Oe3tJvJbqX2LecQvi2SbQoX8c2MrQ6X7xDe2Ajh7Yx9DQ1gqClTglbPFHNiW PcGACg+W2ptCY/Q2SdP5h1dtj5Sw5VwL3dvCjQIDAQABAoIBAEWhGjZZWctvQCAW bbtEv012a6P2LJEnMdJJM6253IRuC8MKnh7NxMq/qjOWK0OX+R+tQ0qt1Udk9H6U 92SAAHAkHaYCmHYywvtWm66gU+2Q34Gnp2AcHFfyinBLgTNHlvkNRe/G8QMWzFrB 3luNt57Tn5b8Hbh+1gpYW8pOF2BMgIsLRK+8b26TKUWrSCc/ZxOSY4wmrNybxkgr HGt27lwIN0cvJZbmQvHevNzzCn+bYoo2K1MQj34xHbZ2NLqKqFVlSJtr9+BHffAc fkcf+V+D+FkitUVkha9qXa02wtLzYSF+Q5Ef3kQQs6hs/HOdN16g17l9QC6Mk1vm a9yV5CECgYEA/9FglQmFimwBCOWEvjkZzoXFusuvRWRgAPU/1c9DAYRS2GfOkjlH RPAltczdXh4EQ0NkCqHH7JWgrdXGonKg4fcITumdwcYKV5QfmKBO4onAboEM0Wq7 wjifuga7npQhPnGvkXFDamVz5McQPObvV42VAUwk1N00gOYw/46ryLkCgYEA1cJv jHAq0DKlUGXKyZ+ixsogRpwTQvND/qUquSLgD/KgfeT+70AnsEF6DbVLKoaJ35CF ju83VYLfeBljq+E/lgmAyaChplORRXcu+xPQE4rbp0MbsoBOYGNWLFAw3twGsQf9 iuAtCVxij/hhj4FWRebYHMnV6Min2VPbZdASNnUCgYBIiX8gY3XJPTzB4ArWwWwu 4kGh6NWHEKIkQ2ZZYw615GZ1VGH/llw+EPYwaamvYUWGKRq55QvCat8Hy6EqOOSj jh99+MIxyszt7mNTLMmRdMvqyY7v5prcxJ+N6RDUM16FzUiiLgKWrbPCACv7iOP+ 6HeCyat77ElR73OfUz4kiQKBgH+2r9cEnU/PMp4ac1KLokGLOkV1srxpg9J89E2w 3JYqrGELlJV1i0DvnfDaxJIf1/hO7L09h537l3C2Gqry5X7LJrtQ0cQCYeVTFCrG 56cFa78/hSjdJ/bG4xGOx+QfKZBT6dQzpDTXkbva9s86w0T4a16n6LowSLi8NXVb H8aRAoGBAKzlt6deB+ASIrGH6mM0eLxF1OcNTB+rE4AJxoUyO1oAmCv9UeK3IzwP ohhmo/kEOSCVG6WE+6+r9mojcoHu3ZrobVKl59R7KMdzunMXqxZcXeTqjvqdTtV7 rWuEz/TKIe7o0Tx19XVGuNftyx2pLuspSAAbZ+YAQJtzmLzsGkss -----END RSA PRIVATE KEY----- mysql2-0.4.3/spec/ssl/ca-cert.pem0000644000004100000410000000173112663414022016553 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIICqjCCAZICCQDbDS+Z2mpWkDANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxj YV9teXNxbDJnZW0wHhcNMTUwOTA5MDQ1NzIxWhcNMjUwNzE4MDQ1NzIxWjAXMRUw EwYDVQQDDAxjYV9teXNxbDJnZW0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQDFnpc22lPFtdPELsIffsDt8cD2Hkt47nGMcKQ9n4U98yAg+fodipyP1Bn0 2OeaONqpttJIET7HxlGrtugPtV/O8XZHlhfAHrRUDMFZJhgnnqK+c/7fRGeB0Eqw ljBlRD/dDL3bFq5hVBC9QsGi5k03r+xLPKm5ccAr4WtofcoKXqEbSO6koTSrsGG5 7inlldM2AVzrY2kXbe0jAyNvYmDL2ycN8G2wObogPWDfITQRhOxfkzKIQiEhQF2Y /DlhT7IbIarBIm6abf6JxZ6/Sm5XyVNEWOnryXM6rKyVeGktCxLHNmxx5eKYs440 8hNgURa8pB+aZaiokkwhM1+jmE83AgMBAAEwDQYJKoZIhvcNAQELBQADggEBACrQ umqygXkkbff5Jqf6AYi30U3c+byX+IButRKXN9Ete2LPcT76o/snS9Lexf3KQsIy a2Tcc9adak7pBf7FgHdiZkWiQp3MDgx2gJu6Uu6TNzfT8jy2JrHyBWw4ydEvhyA8 cgelTHSaudafKeQgU4KYc8bqafYFILkWxPzgtwitENIDfx/SHt65BWaQZjYJlFou zPZXeoT3lAwKGYqIvwPvBTC23cXg/Swt/mcKe3/Xxjx85Dw/9vi6a9+VQwlOojgd w2o07xkIcJcI0Oxyp3mD0U5wAmBQGI76Yi9ZDROHF65KEXfQ3tYKl2vR7CXpcJ4+ 7+fVsE8+dADJdZIiuaA= -----END CERTIFICATE----- mysql2-0.4.3/spec/ssl/server-key.pem0000644000004100000410000000321712663414022017332 0ustar www-datawww-data-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEArcsVmA9NczEG8kcM6s6xsqX/8f64TVE+9BwoRPU6aIz5gnvX t/Hkr89ftJBlm+FRige9OZj7GPMq8iDv09krWSNdQ3Ye6hm4lyRRXlN7OJa3IrN6 mU2Tb7ovIT1ZhkY5+k0XuhZF9Af5RSKdE1d19mkgCFO6GaZ+qpp+FmRRSoMvxFbg 83mn/0b0634ZzoWwcIV/vd1l7edHyu92BQRFCeITjtFaAd+Dv+eeTh2OPxUY01k7 rC9i4MXE3BPxkRpPQTwTeHH4ZpSRwYU0ol5Bjsixr/JG62H36HJA4ZIjyWzEnViE VfmaUF92F39st5Rjhm1qbYDIt7bc4ekWipqGSQIDAQABAoIBABYQMi2bw7lcAAXB /2HP279SU6vlKoCmT9etu28FsuSTARoPntM7YkzvhrblngCsGi3rzZoCUd5ZAJq8 k7XRXC90zeJCnKobmdOWSL4w2vPULmu5XVcZAh6tBE6z+ymQwMuF/f0h1r+kQ4ED Y+2QrOkbb3WfK4EWIf6Gnns5QRi0WTiHllh02JPAjIbvrc0fgR2XV1BB6P7GR8Jq XYUENB7V7POxKOdcGlcBy9AYF0/mpL+6W4XurBoDYKIsefS7vVm2s8VyMkWAzJGX 4NHwmrkTBtOVr2nrqJ5P85QvPBINzMkCFvPgaHuzNsiPnoEWHjNkwgZ69ge5/H0x v8UBCgECgYEA3voYGFm6IenowmqwAVDnfdA//2i7W8rtBwUOmDYjz1L1ZhDHw1bH McOgV07UVqR2EsbD1fiKVDO90rf3LkZqz9Qzk0+LCSX/ZbzYN+fjnHhh0XtIVVFP ZlagTGy8+TTj8MVpauHkoCvnACqt4OZaeOyBcV9EfoPnb5O2IUsuPq8CgYEAx4hA S42WoWNdND33y8oCML8G08qCvVDI0jp9bBqjn/Whe/c0CCEYRJjzf43vY6pwHH/P or0tXS2KFG6KLJu9ted+PVMOkvQuN0SJ9Yi2URjuwXqtyjq3aeDRZ4d0LIKB+I6d YKeMOkMgQtrmAXUFwPRqVEdvmOOQSClUh0FeCIcCgYEA0C4oOPPtOKSvUHVThqcG uwyn88rQws8fpiIukiZaIMOPoo+gMtzedbP0DA/paEt8geNaDHhUjnEQlaB1A5tz Nj8lODBX217CqTY7mVbDx98NVQwJaL7VpvLSDwinFvUurClkVwo47uKOdjViykvH xIbsA/GyfXyhpCcBMOyEbLkCgYEAlA+H3kiPiOyJwfVlxiP9YhyuPUSdpUun6VAb t7dAybbkgG+cfZfJQUXaEkHJbMSRcqRo+BK28diUCLtJ7vYWXUANTgYu318wWDSU xs0OtLjymkGlalYNuhRobxF1ZH3nYgC5yXoqtSUQcGusl1I11cvIhweHSQU8EEAT CFa6J0sCgYEAzj2JJanc0b/ruF7pFlrLyuxi4/YbsHR9DSOYKLhjG+Tk4H1aOuvr q6uKmHB0TeL3HcWz/SqCTwbyoXcfgwN+iNhEbP//doCM+pXDbKpnQMxwF9cs6eCM hRrI82iom50K37e4W/Sn4PVcqQW/xt9ce5pvoGZuET9423ATw59MVP8= -----END RSA PRIVATE KEY----- mysql2-0.4.3/spec/ssl/client-cert.pem0000644000004100000410000000173112663414022017446 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIICqzCCAZMCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMY2FfbXlzcWwy Z2VtMB4XDTE1MDkwOTA0NTcyMVoXDTI1MDcxODA0NTcyMVowIDEeMBwGA1UEAwwV bXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEA1ZuBf1FVJqil7/LvnXqPd43ujo0xqbFy7QrqmM5U/UM3ggMCf2Gr2/Wo ZJGPTk1NFaiUyM5mhSlgi0/SGPEp9JMUuH+Uiv9UwmOFl9Em3FXQQ8SG7fV7651u AUskNgfEqoy+f+uvi1P155rHNDx7Yw6i+wwfpLGTU0boMnLL6cO/KcIbZlx4/2Lq r5sYbpIqhz46bbG+fIhvepruH9h7WVWqAibTqymYrA3T03O/HWTOqfq03gM7Oe3t JvJbqX2LecQvi2SbQoX8c2MrQ6X7xDe2Ajh7Yx9DQ1gqClTglbPFHNiWPcGACg+W 2ptCY/Q2SdP5h1dtj5Sw5VwL3dvCjQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA2 qTbfgDm0IG8x1qP61ztT9F2WRwG7cp6qHT5oB5wDcOUFes9QJjeB8RoIkB+hRlqG J6/Tbxs49d7oKhOQ0UaTnfIKC5m0UFYFGc3lUcwxQyggOWx9XV5ZmGb48+RLFnDV Gfcs/hvfem6Xfpgzr8bGs2ZM9x1j9YnXNJVePmKwktjCPnXPOeHyxNZPA+CWHed/ dNg1IWuQnnp20LgNRARCTgR/ONAJNUfh2GqRLq2JOf0cyhNlsKQ3epkeUyc72knI oWVxPluQYvFHN+xif0FMGVLM8lz0b+6uPJDA2Km70B/iorMRVb0vbMeFrMmQ5UgM 4tplX52P2vb6JNnektfR -----END CERTIFICATE----- mysql2-0.4.3/spec/ssl/cert.cnf0000644000004100000410000000112112663414022016150 0ustar www-datawww-data [ ca ] # January 1, 2015 default_startdate = 2015010360000Z [ req ] distinguished_name = req_distinguished_name [ req_distinguished_name ] # If this isn't set, the error is error, no objects specified in config file commonName = Common Name (hostname, IP, or your name) countryName_default = US stateOrProvinceName_default = CA localityName_default = San Francisco 0.organizationName_default = mysql2_gem organizationalUnitName_default = Mysql2Gem emailAddress_default = mysql2gem@example.com commonName_default = mysql2gem.example.com mysql2-0.4.3/spec/ssl/pkcs8-server-key.pem0000644000004100000410000000325412663414022020361 0ustar www-datawww-data-----BEGIN PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCtyxWYD01zMQby RwzqzrGypf/x/rhNUT70HChE9TpojPmCe9e38eSvz1+0kGWb4VGKB705mPsY8yry IO/T2StZI11Ddh7qGbiXJFFeU3s4lrcis3qZTZNvui8hPVmGRjn6TRe6FkX0B/lF Ip0TV3X2aSAIU7oZpn6qmn4WZFFKgy/EVuDzeaf/RvTrfhnOhbBwhX+93WXt50fK 73YFBEUJ4hOO0VoB34O/555OHY4/FRjTWTusL2LgxcTcE/GRGk9BPBN4cfhmlJHB hTSiXkGOyLGv8kbrYffockDhkiPJbMSdWIRV+ZpQX3YXf2y3lGOGbWptgMi3ttzh 6RaKmoZJAgMBAAECggEAFhAyLZvDuVwABcH/Yc/bv1JTq+UqgKZP1627bwWy5JMB Gg+e0ztiTO+GtuWeAKwaLevNmgJR3lkAmryTtdFcL3TN4kKcqhuZ05ZIvjDa89Qu a7ldVxkCHq0ETrP7KZDAy4X9/SHWv6RDgQNj7ZCs6RtvdZ8rgRYh/oaeezlBGLRZ OIeWWHTYk8CMhu+tzR+BHZdXUEHo/sZHwmpdhQQ0HtXs87Eo51waVwHL0BgXT+ak v7pbhe6sGgNgoix59Lu9WbazxXIyRYDMkZfg0fCauRMG05Wvaeuonk/zlC88Eg3M yQIW8+Boe7M2yI+egRYeM2TCBnr2B7n8fTG/xQEKAQKBgQDe+hgYWboh6ejCarAB UOd90D//aLtbyu0HBQ6YNiPPUvVmEMfDVscxw6BXTtRWpHYSxsPV+IpUM73St/cu RmrP1DOTT4sJJf9lvNg35+OceGHRe0hVUU9mVqBMbLz5NOPwxWlq4eSgK+cAKq3g 5lp47IFxX0R+g+dvk7YhSy4+rwKBgQDHiEBLjZahY100PffLygIwvwbTyoK9UMjS On1sGqOf9aF79zQIIRhEmPN/je9jqnAcf8+ivS1dLYoUboosm7215349Uw6S9C43 RIn1iLZRGO7Beq3KOrdp4NFnh3QsgoH4jp1gp4w6QyBC2uYBdQXA9GpUR2+Y45BI KVSHQV4IhwKBgQDQLig48+04pK9QdVOGpwa7DKfzytDCzx+mIi6SJlogw4+ij6Ay 3N51s/QMD+loS3yB41oMeFSOcRCVoHUDm3M2PyU4MFfbXsKpNjuZVsPH3w1VDAlo vtWm8tIPCKcW9S6sKWRXCjju4o52NWLKS8fEhuwD8bJ9fKGkJwEw7IRsuQKBgQCU D4feSI+I7InB9WXGI/1iHK49RJ2lS6fpUBu3t0DJtuSAb5x9l8lBRdoSQclsxJFy pGj4Erbx2JQIu0nu9hZdQA1OBi7fXzBYNJTGzQ60uPKaQaVqVg26FGhvEXVkfedi ALnJeiq1JRBwa6yXUjXVy8iHB4dJBTwQQBMIVronSwKBgQDOPYklqdzRv+u4XukW WsvK7GLj9huwdH0NI5gouGMb5OTgfVo66+urq4qYcHRN4vcdxbP9KoJPBvKhdx+D A36I2ERs//92gIz6lcNsqmdAzHAX1yzp4IyFGsjzaKibnQrft7hb9Kfg9VypBb/G 31x7mm+gZm4RP3jbcBPDn0xU/w== -----END PRIVATE KEY----- mysql2-0.4.3/spec/ssl/ca-key.pem0000644000004100000410000000321712663414022016407 0ustar www-datawww-data-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAxZ6XNtpTxbXTxC7CH37A7fHA9h5LeO5xjHCkPZ+FPfMgIPn6 HYqcj9QZ9NjnmjjaqbbSSBE+x8ZRq7boD7VfzvF2R5YXwB60VAzBWSYYJ56ivnP+ 30RngdBKsJYwZUQ/3Qy92xauYVQQvULBouZNN6/sSzypuXHAK+FraH3KCl6hG0ju pKE0q7Bhue4p5ZXTNgFc62NpF23tIwMjb2Jgy9snDfBtsDm6ID1g3yE0EYTsX5My iEIhIUBdmPw5YU+yGyGqwSJumm3+icWev0puV8lTRFjp68lzOqyslXhpLQsSxzZs ceXimLOONPITYFEWvKQfmmWoqJJMITNfo5hPNwIDAQABAoIBAQClkmtFRQVdKCum Ojrg4nVIpv2x983qI3U1YobpLocXUWVA29BIAgOMqfuZXkYlu67Q9OEYCoLcJHf2 88dYqfD81OfxsHpzuAYESa+RPs6MG2hlQ5BuhcRnShnZ++vOXLFZRjynnEg8OY/Q 0makUmqt1pKWstvNCNYmrbYtFP87UXQCF06zkhM0cZJvVt0gPZGUituWI0uAoE51 U8+WSwD/T761BHM6BuMn56mfZkP5jeVIFl0iFha9rGR0Z6K8mVQAYQAUtUx9tN/3 a8fEOcYulq/9R5tMRWtsF8LD8DGQBNkY3e/WKDuZtLw2Dl3L09gxVH9DXCLiYU5d OG3JmqDpAoGBAP08yq143H4n6yGT9DC8YjaLgN0VoenK21CEqhwtGWipc/kbGooe /jaHl6bo9v1GOGlJieqSUqsXNltS7FOLhGFAQFwMYZ3V/h15Vx23Z+xkCCHIB6HH YJZqkQY7Jt86wXcaLU5j9fxM+BY+8Ets4bVhZN9Ai6AnlTz0+d8UJG+bAoGBAMfG efYrdjTKI5eK9aiVJyoh57BEPOsTsave2U+R8Q+fErQ0QD0UmbWgwYGgkPuDrFYT owg09EEz88KONv18VZ+mB1qfyQUoOL6rWIGxXC08upy2i9100PaBFiYlkLNoK7yJ bze0rFSiFclJJXZGzEaVvcEdKnXxfhttaJwQGK6VAoGBAOQEUvJzuwWU5/CqCdvA JCa84eEv00RxtZwAeDM6oIBO4+/O6cyoL3nmCTTu20YebjjPUHF4IxuOoREFz2lC XIY8ljbLpzG5N0BOu5Q0SkzdnTzdoZGXtm55se+MX2nsu7qERXsqIpl0rIVLUo53 kZwCABPNSGuCeKwUYNDukAg1AoGBALiHHSqEVKhIOn4FDgqM0uM49CA9t6NPyqI9 sq6r2GWcgpNPXDLPL3e0KGlK3gBkTLApbULsXt1HVpZT9HlJ+nD/0/UieHS6BUgh Txxkrgbe/GQ6vZBuEYJQFBxiQHlm9Fcu/zsOOMvn94W4edD5bkCYmfChtxHAYcKF 2cWlnJbNAoGAWMV4GIY2DYlztXdyMVuPwsjPcSPMmL8Nc2ATWYRfcoG0Zl0yvwPh 2VOu7Q/7bNF2LOe6lPe1hoeB6rT44IYZaWMo3ikY8xW9RztOLSv8E9uE1K9yq8OA P8QzXmr1Lga+hoEmMHc2biEJNeF6iAcAFfrHj9Sr7w5PC8g4A3PlCvU= -----END RSA PRIVATE KEY----- mysql2-0.4.3/spec/ssl/client-req.pem0000644000004100000410000000161312663414022017277 0ustar www-datawww-data-----BEGIN CERTIFICATE REQUEST----- MIICZTCCAU0CAQAwIDEeMBwGA1UEAwwVbXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1ZuBf1FVJqil7/LvnXqPd43u jo0xqbFy7QrqmM5U/UM3ggMCf2Gr2/WoZJGPTk1NFaiUyM5mhSlgi0/SGPEp9JMU uH+Uiv9UwmOFl9Em3FXQQ8SG7fV7651uAUskNgfEqoy+f+uvi1P155rHNDx7Yw6i +wwfpLGTU0boMnLL6cO/KcIbZlx4/2Lqr5sYbpIqhz46bbG+fIhvepruH9h7WVWq AibTqymYrA3T03O/HWTOqfq03gM7Oe3tJvJbqX2LecQvi2SbQoX8c2MrQ6X7xDe2 Ajh7Yx9DQ1gqClTglbPFHNiWPcGACg+W2ptCY/Q2SdP5h1dtj5Sw5VwL3dvCjQID AQABoAAwDQYJKoZIhvcNAQELBQADggEBAB05YaBSyAKCgHfBWhpZ1+OOVp1anr2v TkStnqmNrNM2qBJXjfrythPTX4EJAt7+eNdH/6IVA93qKC/EUQVuMjgfMmMUaM+m 5pqfAo95w7vUY147U9nbC+EIo2u1KOVTNTgl45H372/1vCwTHZYu2atCk4tN3ueO 0O2XW89Kq94/7PDAExN2PhZdeATVX9dPNT+7ZUDNe8cuq9v0YCHy+2JN2WkplxcG kMyCE3YYLnd96YtWiS9DOUib3+b7FwyGe0dXeLVw1br3NZGCZrybyfmnAQfiouAF 9nMxKIpWFSx00ubGrUefOQqp6nuk27n+scgr4+d6dBXz9efEEvTbLKA= -----END CERTIFICATE REQUEST----- mysql2-0.4.3/spec/ssl/server-req.pem0000644000004100000410000000161312663414022017327 0ustar www-datawww-data-----BEGIN CERTIFICATE REQUEST----- MIICZTCCAU0CAQAwIDEeMBwGA1UEAwwVbXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArcsVmA9NczEG8kcM6s6xsqX/ 8f64TVE+9BwoRPU6aIz5gnvXt/Hkr89ftJBlm+FRige9OZj7GPMq8iDv09krWSNd Q3Ye6hm4lyRRXlN7OJa3IrN6mU2Tb7ovIT1ZhkY5+k0XuhZF9Af5RSKdE1d19mkg CFO6GaZ+qpp+FmRRSoMvxFbg83mn/0b0634ZzoWwcIV/vd1l7edHyu92BQRFCeIT jtFaAd+Dv+eeTh2OPxUY01k7rC9i4MXE3BPxkRpPQTwTeHH4ZpSRwYU0ol5Bjsix r/JG62H36HJA4ZIjyWzEnViEVfmaUF92F39st5Rjhm1qbYDIt7bc4ekWipqGSQID AQABoAAwDQYJKoZIhvcNAQELBQADggEBAInWIFsq14b8PhDroMMvi1ma30xyQGjg KObIxakwXkliSxmCdVqV4+MV6w8hK3z0q7H+NzRFByjo1PnU8BCIa058m5uvbjmM wGQvpcxmpm1p8VKKoeTqvE8OelbrqHrmyNIq7E/S3UZelVt+HOIPJOOs/aqEzaEg VL1u+4kCMbHM2rG8dii060oZ5i/gUtMn2TQWcNjSQBvaVztW5FOL74bYkoq0zIwd MFl2BoYyAnERJEcBmh1f+D7MuxPaqTUKjUmfDbHCMAAyTS1FHr9AnIN0/C2Mxywl H/zL9/DkfR53KZjITkf2gTH5D/N5oDUwmgCg6UZ0MeTOP27jvgcvb/k= -----END CERTIFICATE REQUEST----- mysql2-0.4.3/spec/ssl/ca.cnf0000644000004100000410000000111012663414022015574 0ustar www-datawww-data [ ca ] # January 1, 2015 default_startdate = 2015010360000Z [ req ] distinguished_name = req_distinguished_name [ req_distinguished_name ] # If this isn't set, the error is error, no objects specified in config file commonName = Common Name (hostname, IP, or your name) countryName_default = US stateOrProvinceName_default = CA localityName_default = San Francisco 0.organizationName_default = mysql2_gem organizationalUnitName_default = Mysql2Gem emailAddress_default = mysql2gem@example.com commonName_default = ca_mysql2gem mysql2-0.4.3/spec/ssl/pkcs8-client-key.pem0000644000004100000410000000325012663414022020325 0ustar www-datawww-data-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDVm4F/UVUmqKXv 8u+deo93je6OjTGpsXLtCuqYzlT9QzeCAwJ/Yavb9ahkkY9OTU0VqJTIzmaFKWCL T9IY8Sn0kxS4f5SK/1TCY4WX0SbcVdBDxIbt9XvrnW4BSyQ2B8SqjL5/66+LU/Xn msc0PHtjDqL7DB+ksZNTRugycsvpw78pwhtmXHj/YuqvmxhukiqHPjptsb58iG96 mu4f2HtZVaoCJtOrKZisDdPTc78dZM6p+rTeAzs57e0m8lupfYt5xC+LZJtChfxz YytDpfvEN7YCOHtjH0NDWCoKVOCVs8Uc2JY9wYAKD5bam0Jj9DZJ0/mHV22PlLDl XAvd28KNAgMBAAECggEARaEaNllZy29AIBZtu0S/TXZro/YskScx0kkzrbnchG4L wwqeHs3Eyr+qM5YrQ5f5H61DSq3VR2T0fpT3ZIAAcCQdpgKYdjLC+1abrqBT7ZDf gaenYBwcV/KKcEuBM0eW+Q1F78bxAxbMWsHeW423ntOflvwduH7WClhbyk4XYEyA iwtEr7xvbpMpRatIJz9nE5JjjCas3JvGSCsca3buXAg3Ry8lluZC8d683PMKf5ti ijYrUxCPfjEdtnY0uoqoVWVIm2v34Ed98Bx+Rx/5X4P4WSK1RWSFr2pdrTbC0vNh IX5DkR/eRBCzqGz8c503XqDXuX1ALoyTW+Zr3JXkIQKBgQD/0WCVCYWKbAEI5YS+ ORnOhcW6y69FZGAA9T/Vz0MBhFLYZ86SOUdE8CW1zN1eHgRDQ2QKocfslaCt1cai cqDh9whO6Z3BxgpXlB+YoE7iicBugQzRarvCOJ+6BruelCE+ca+RcUNqZXPkxxA8 5u9XjZUBTCTU3TSA5jD/jqvIuQKBgQDVwm+McCrQMqVQZcrJn6LGyiBGnBNC80P+ pSq5IuAP8qB95P7vQCewQXoNtUsqhonfkIWO7zdVgt94GWOr4T+WCYDJoKGmU5FF dy77E9ATitunQxuygE5gY1YsUDDe3AaxB/2K4C0JXGKP+GGPgVZF5tgcydXoyKfZ U9tl0BI2dQKBgEiJfyBjdck9PMHgCtbBbC7iQaHo1YcQoiRDZlljDrXkZnVUYf+W XD4Q9jBpqa9hRYYpGrnlC8Jq3wfLoSo45KOOH334wjHKzO3uY1MsyZF0y+rJju/m mtzEn43pENQzXoXNSKIuApats8IAK/uI4/7od4LJq3vsSVHvc59TPiSJAoGAf7av 1wSdT88ynhpzUouiQYs6RXWyvGmD0nz0TbDcliqsYQuUlXWLQO+d8NrEkh/X+E7s vT2HnfuXcLYaqvLlfssmu1DRxAJh5VMUKsbnpwVrvz+FKN0n9sbjEY7H5B8pkFPp 1DOkNNeRu9r2zzrDRPhrXqfoujBIuLw1dVsfxpECgYEArOW3p14H4BIisYfqYzR4 vEXU5w1MH6sTgAnGhTI7WgCYK/1R4rcjPA+iGGaj+QQ5IJUbpYT7r6v2aiNyge7d muhtUqXn1Hsox3O6cxerFlxd5OqO+p1O1Xuta4TP9Moh7ujRPHX1dUa41+3LHaku 6ylIABtn5gBAm3OYvOwaSyw= -----END PRIVATE KEY----- mysql2-0.4.3/spec/ssl/gen_certs.sh0000644000004100000410000000324412663414022017040 0ustar www-datawww-data#!/usr/bin/env bash set -eu echo " [ ca ] # January 1, 2015 default_startdate = 2015010360000Z [ req ] distinguished_name = req_distinguished_name [ req_distinguished_name ] # If this isn't set, the error is "error, no objects specified in config file" commonName = Common Name (hostname, IP, or your name) countryName_default = US stateOrProvinceName_default = CA localityName_default = San Francisco 0.organizationName_default = mysql2_gem organizationalUnitName_default = Mysql2Gem emailAddress_default = mysql2gem@example.com " | tee ca.cnf cert.cnf # The client and server certs must have a diferent common name than the CA # to avoid "SSL connection error: error:00000001:lib(0):func(0):reason(1)" echo " commonName_default = ca_mysql2gem " >> ca.cnf echo " commonName_default = mysql2gem.example.com " >> cert.cnf # Generate a set of certificates openssl genrsa -out ca-key.pem 2048 openssl req -new -x509 -nodes -days 3600 -key ca-key.pem -out ca-cert.pem -batch -config ca.cnf openssl req -newkey rsa:2048 -days 3600 -nodes -keyout pkcs8-server-key.pem -out server-req.pem -batch -config cert.cnf openssl x509 -req -in server-req.pem -days 3600 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem openssl req -newkey rsa:2048 -days 3600 -nodes -keyout pkcs8-client-key.pem -out client-req.pem -batch -config cert.cnf openssl x509 -req -in client-req.pem -days 3600 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out client-cert.pem # Convert format from PKCS#8 to PKCS#1 openssl rsa -in pkcs8-server-key.pem -out server-key.pem openssl rsa -in pkcs8-client-key.pem -out client-key.pem echo "done" mysql2-0.4.3/spec/ssl/server-cert.pem0000644000004100000410000000173112663414022017476 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIICqzCCAZMCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMY2FfbXlzcWwy Z2VtMB4XDTE1MDkwOTA0NTcyMVoXDTI1MDcxODA0NTcyMVowIDEeMBwGA1UEAwwV bXlzcWwyZ2VtLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEArcsVmA9NczEG8kcM6s6xsqX/8f64TVE+9BwoRPU6aIz5gnvXt/Hkr89f tJBlm+FRige9OZj7GPMq8iDv09krWSNdQ3Ye6hm4lyRRXlN7OJa3IrN6mU2Tb7ov IT1ZhkY5+k0XuhZF9Af5RSKdE1d19mkgCFO6GaZ+qpp+FmRRSoMvxFbg83mn/0b0 634ZzoWwcIV/vd1l7edHyu92BQRFCeITjtFaAd+Dv+eeTh2OPxUY01k7rC9i4MXE 3BPxkRpPQTwTeHH4ZpSRwYU0ol5Bjsixr/JG62H36HJA4ZIjyWzEnViEVfmaUF92 F39st5Rjhm1qbYDIt7bc4ekWipqGSQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBB PtRuVmOHiRPH3PmsZPtkgVagznojO+r0GDTxys5bxof8+HcokL6gbb4bRqzUQTdC 98RTsuFPnd/I8FbbaL+UyeXeKjjOEBPFyllOdykmpd67mHCA89574y7Ib7lkvtr1 nQFMbeKmcz4uLm1vAi/aOdAIA2de4yJU2XnOkVLDgYnQxpWR451WKqt/FtiuCzpQ E3peEemM2XVxvCMmfBAaroAyLYFrWOhNA7UoWVsubp7Ypf7SYuOh+sU6JLsYSadQ XVqgvIKG4deUpdl7oUBRz79spAi1OpHWiNmW3b+8nKJoHTfYkKzCebeLdI++xSjX jXNryv5xK88LFIPKL/7e -----END CERTIFICATE----- mysql2-0.4.3/spec/spec_helper.rb0000644000004100000410000000552112663414022016550 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.disable_monkey_patching! def with_internal_encoding(encoding) old_enc = Encoding.default_internal old_verbose = $VERBOSE $VERBOSE = nil Encoding.default_internal = encoding $VERBOSE = old_verbose yield ensure $VERBOSE = nil Encoding.default_internal = old_enc $VERBOSE = old_verbose end 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.4.3/spec/test_data0000644000004100000410000000001712663414022015620 0ustar www-datawww-data\N Hello World mysql2-0.4.3/spec/my.cnf.example0000644000004100000410000000015112663414022016473 0ustar www-datawww-data[root] host=localhost user=LOCALUSERNAME password= [client] host=localhost user=LOCALUSERNAME password= mysql2-0.4.3/spec/configuration.yml.example0000644000004100000410000000036512663414022020757 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.4.3/spec/mysql2/0000755000004100000410000000000012663414022015156 5ustar www-datawww-datamysql2-0.4.3/spec/mysql2/statement_spec.rb0000644000004100000410000006331412663414022020530 0ustar www-datawww-data# encoding: UTF-8 require './spec/spec_helper.rb' RSpec.describe Mysql2::Statement do before :each do @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) end it "should create a statement" do statement = nil expect { statement = @client.prepare 'SELECT 1' }.to change { @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i }.by(1) expect(statement).to be_an_instance_of(Mysql2::Statement) end it "should raise an exception when server disconnects" do @client.close expect { @client.prepare 'SELECT 1' }.to raise_error(Mysql2::Error) end it "should tell us the param count" do statement = @client.prepare 'SELECT ?, ?' expect(statement.param_count).to eq(2) statement2 = @client.prepare 'SELECT 1' expect(statement2.param_count).to eq(0) end it "should tell us the field count" do statement = @client.prepare 'SELECT ?, ?' expect(statement.field_count).to eq(2) statement2 = @client.prepare 'SELECT 1' expect(statement2.field_count).to eq(1) end it "should let us execute our statement" do statement = @client.prepare 'SELECT 1' expect(statement.execute).not_to eq(nil) end it "should raise an exception without a block" do statement = @client.prepare 'SELECT 1' expect { statement.execute.each }.to raise_error(LocalJumpError) end it "should tell us the result count" do statement = @client.prepare 'SELECT 1' result = statement.execute expect(result.count).to eq(1) end it "should let us iterate over results" do statement = @client.prepare 'SELECT 1' result = statement.execute rows = [] result.each { |r| rows << r } expect(rows).to eq([{ "1" => 1 }]) end it "should keep its result after other query" do @client.query 'USE test' @client.query 'CREATE TABLE IF NOT EXISTS mysql2_stmt_q(a int)' @client.query 'INSERT INTO mysql2_stmt_q (a) VALUES (1), (2)' stmt = @client.prepare('SELECT a FROM mysql2_stmt_q WHERE a = ?') result1 = stmt.execute(1) result2 = stmt.execute(2) expect(result2.first).to eq("a" => 2) expect(result1.first).to eq("a" => 1) @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q' end it "should be reusable 1000 times" do statement = @client.prepare 'SELECT 1' 1000.times do result = statement.execute expect(result.to_a.length).to eq(1) end end it "should be reusable 10000 times" do statement = @client.prepare 'SELECT 1' 10000.times do result = statement.execute expect(result.to_a.length).to eq(1) end end it "should handle comparisons and likes" do @client.query 'USE test' @client.query 'CREATE TABLE IF NOT EXISTS mysql2_stmt_q(a int, b varchar(10))' @client.query 'INSERT INTO mysql2_stmt_q (a, b) VALUES (1, "Hello"), (2, "World")' statement = @client.prepare 'SELECT * FROM mysql2_stmt_q WHERE a < ?' results = statement.execute(2) expect(results.first).to eq("a" => 1, "b" => "Hello") statement = @client.prepare 'SELECT * FROM mysql2_stmt_q WHERE b LIKE ?' results = statement.execute('%orld') expect(results.first).to eq("a" => 2, "b" => "World") @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q' end it "should select dates" do statement = @client.prepare 'SELECT NOW()' result = statement.execute expect(result.first.first[1]).to be_an_instance_of(Time) end it "should tell us about the fields" do statement = @client.prepare 'SELECT 1 as foo, 2' statement.execute list = statement.fields expect(list.length).to eq(2) expect(list.first).to eq('foo') expect(list[1]).to eq('2') end it "should update a DECIMAL value passing a BigDecimal" do @client.query 'USE test' @client.query 'DROP TABLE IF EXISTS mysql2_stmt_decimal_test' @client.query 'CREATE TABLE mysql2_stmt_decimal_test (decimal_test DECIMAL(10,3))' @client.prepare("INSERT INTO mysql2_stmt_decimal_test VALUES (?)").execute(BigDecimal.new("123.45")) test_result = @client.query("SELECT * FROM mysql2_stmt_decimal_test").first expect(test_result['decimal_test']).to eql(123.45) end context "utf8_db" do before(:each) do @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8") @client.query("CREATE DATABASE test_mysql2_stmt_utf8") @client.query("USE test_mysql2_stmt_utf8") @client.query("CREATE TABLE テーブル (整数 int, 文字列 varchar(32)) charset=utf8") @client.query("INSERT INTO テーブル (整数, 文字列) VALUES (1, 'イチ'), (2, '弐'), (3, 'さん')") end after(:each) do @client.query("DROP DATABASE test_mysql2_stmt_utf8") end it "should be able to retrieve utf8 field names correctly" do stmt = @client.prepare 'SELECT * FROM `テーブル`' expect(stmt.fields).to eq(%w(整数 文字列)) result = stmt.execute expect(result.to_a).to eq([{ "整数" => 1, "文字列" => "イチ" }, { "整数" => 2, "文字列" => "弐" }, { "整数" => 3, "文字列" => "さん" }]) end it "should be able to retrieve utf8 param query correctly" do stmt = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?' expect(stmt.param_count).to eq(1) result = stmt.execute 'イチ' expect(result.to_a).to eq([{ "整数" => 1 }]) end it "should be able to retrieve query with param in different encoding correctly" do stmt = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?' expect(stmt.param_count).to eq(1) param = 'イチ'.encode("EUC-JP") result = stmt.execute param expect(result.to_a).to eq([{ "整数" => 1 }]) end end if defined? Encoding context "streaming result" do it "should be able to stream query result" do n = 1 stmt = @client.prepare("SELECT 1 UNION SELECT 2") @client.query_options.merge!(:stream => true, :cache_rows => false, :as => :array) stmt.execute.each do |r| case n when 1 expect(r).to eq([1]) when 2 expect(r).to eq([2]) else violated "returned more than two rows" end n += 1 end end end context "#each" do # note: The current impl. of prepared statement requires results to be cached on #execute except for streaming queries # The drawback of this is that args of Result#each is ignored... it "should yield rows as hash's" do @result = @client.prepare("SELECT 1").execute @result.each do |row| expect(row).to be_an_instance_of(Hash) end end it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do @client.query_options[:symbolize_keys] = true @result = @client.prepare("SELECT 1").execute @result.each do |row| expect(row.keys.first).to be_an_instance_of(Symbol) end @client.query_options[:symbolize_keys] = false end it "should be able to return results as an array" do @client.query_options[:as] = :array @result = @client.prepare("SELECT 1").execute @result.each do |row| expect(row).to be_an_instance_of(Array) end @client.query_options[:as] = :hash end it "should cache previously yielded results by default" do @result = @client.prepare("SELECT 1").execute expect(@result.first.object_id).to eql(@result.first.object_id) end it "should yield different value for #first if streaming" do @client.query_options[:stream] = true @client.query_options[:cache_rows] = false result = @client.prepare("SELECT 1 UNION SELECT 2").execute expect(result.first).not_to eql(result.first) @client.query_options[:stream] = false @client.query_options[:cache_rows] = true end it "should yield the same value for #first if streaming is disabled" do @client.query_options[:stream] = false result = @client.prepare("SELECT 1 UNION SELECT 2").execute expect(result.first).to eql(result.first) end it "should throw an exception if we try to iterate twice when streaming is enabled" do @client.query_options[:stream] = true @client.query_options[:cache_rows] = false result = @client.prepare("SELECT 1 UNION SELECT 2").execute expect { result.each {} result.each {} }.to raise_exception(Mysql2::Error) @client.query_options[:stream] = false @client.query_options[:cache_rows] = true end end context "#fields" do before(:each) do @client.query "USE test" @test_result = @client.prepare("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").execute end it "method should exist" do expect(@test_result).to respond_to(:fields) end it "should return an array of field names in proper order" do result = @client.prepare("SELECT 'a', 'b', 'c'").execute expect(result.fields).to eql(%w(a b c)) end end context "row data type mapping" do before(:each) do @client.query "USE test" @test_result = @client.prepare("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").execute.first end it "should return nil for a NULL value" do expect(@test_result['null_test']).to be_an_instance_of(NilClass) expect(@test_result['null_test']).to eql(nil) end it "should return String for a BIT(64) value" do expect(@test_result['bit_test']).to be_an_instance_of(String) expect(@test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") end it "should return String for a BIT(1) value" do expect(@test_result['single_bit_test']).to be_an_instance_of(String) expect(@test_result['single_bit_test']).to eql("\001") end it "should return Fixnum for a TINYINT value" do expect([Fixnum, Bignum]).to include(@test_result['tiny_int_test'].class) expect(@test_result['tiny_int_test']).to 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 expect(result1.first['bool_cast_test']).to be true expect(result2.first['bool_cast_test']).to be false expect(result3.first['bool_cast_test']).to 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 expect(result1.first['single_bit_test']).to be true expect(result2.first['single_bit_test']).to be false @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" end it "should return Fixnum for a SMALLINT value" do expect([Fixnum, Bignum]).to include(@test_result['small_int_test'].class) expect(@test_result['small_int_test']).to eql(10) end it "should return Fixnum for a MEDIUMINT value" do expect([Fixnum, Bignum]).to include(@test_result['medium_int_test'].class) expect(@test_result['medium_int_test']).to eql(10) end it "should return Fixnum for an INT value" do expect([Fixnum, Bignum]).to include(@test_result['int_test'].class) expect(@test_result['int_test']).to eql(10) end it "should return Fixnum for a BIGINT value" do expect([Fixnum, Bignum]).to include(@test_result['big_int_test'].class) expect(@test_result['big_int_test']).to eql(10) end it "should return Fixnum for a YEAR value" do expect([Fixnum, Bignum]).to include(@test_result['year_test'].class) expect(@test_result['year_test']).to eql(2009) end it "should return BigDecimal for a DECIMAL value" do expect(@test_result['decimal_test']).to be_an_instance_of(BigDecimal) expect(@test_result['decimal_test']).to eql(10.3) end it "should return Float for a FLOAT value" do expect(@test_result['float_test']).to be_an_instance_of(Float) expect(@test_result['float_test']).to be_within(1e-5).of(10.3) end it "should return Float for a DOUBLE value" do expect(@test_result['double_test']).to be_an_instance_of(Float) expect(@test_result['double_test']).to eql(10.3) end it "should return Time for a DATETIME value when within the supported range" do expect(@test_result['date_time_test']).to be_an_instance_of(Time) expect(@test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end if 1.size == 4 # 32bit klass = if RUBY_VERSION =~ /1.8/ DateTime else Time 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") expect(r.first['test']).to be_an_instance_of(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") expect(r.first['test']).to be_an_instance_of(klass) end elsif 1.size == 8 # 64bit if RUBY_VERSION =~ /1.8/ 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") expect(r.first['test']).to be_an_instance_of(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") expect(r.first['test']).to be_an_instance_of(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") expect(r.first['test']).to be_an_instance_of(Time) end else 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") expect(r.first['test']).to be_an_instance_of(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") expect(r.first['test']).to be_an_instance_of(Time) end end end it "should return Time for a TIMESTAMP value when within the supported range" do expect(@test_result['timestamp_test']).to be_an_instance_of(Time) expect(@test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time for a TIME value" do expect(@test_result['time_test']).to be_an_instance_of(Time) expect(@test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00') end it "should return Date for a DATE value" do expect(@test_result['date_test']).to be_an_instance_of(Date) expect(@test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04') end it "should return String for an ENUM value" do expect(@test_result['enum_test']).to be_an_instance_of(String) expect(@test_result['enum_test']).to eql('val1') end it "should raise an error given an invalid DATETIME" do expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \ raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") end context "string encoding for ENUM values" do before { pending('Encoding is undefined') unless defined?(Encoding) } it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::US_ASCII) client2.close end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end end end it "should return String for a SET value" do expect(@test_result['set_test']).to be_an_instance_of(String) expect(@test_result['set_test']).to eql('val1,val2') end context "string encoding for SET values" do before { pending('Encoding is undefined') unless defined?(Encoding) } it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::US_ASCII) client2.close end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding.default_internal) end end end it "should return String for a BINARY value" do expect(@test_result['binary_test']).to be_an_instance_of(String) expect(@test_result['binary_test']).to eql("test#{"\000" * 6}") end context "string encoding for BINARY values" do before { pending('Encoding is undefined') unless defined?(Encoding) } it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end it "should not use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::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 expect(@test_result[field]).to be_an_instance_of(String) expect(@test_result[field]).to eql("test") end context "string encoding for #{type} values" do before { pending('Encoding is undefined') unless defined?(Encoding) } if %w(VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB).include?(type) it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end it "should not use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end else it "should default to utf-8 if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::US_ASCII) client2.close end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding.default_internal) end end end end end end context 'last_id' do before(:each) do @client.query 'USE test' @client.query 'CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))' end after(:each) do @client.query 'DROP TABLE lastIdTest' end it 'should return last insert id' do stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' expect(stmt.last_id).to eq 0 stmt.execute 1 expect(stmt.last_id).to eq 1 end it 'should handle bigint ids' do stmt = @client.prepare 'INSERT INTO lastIdTest (id, blah) VALUES (?, ?)' stmt.execute 5000000000, 5000 expect(stmt.last_id).to eql(5000000000) stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' stmt.execute 5001 expect(stmt.last_id).to eql(5000000001) end end context 'affected_rows' do before :each do @client.query 'USE test' @client.query 'CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))' end after :each do @client.query 'DROP TABLE lastIdTest' end it 'should return number of rows affected by an insert' do stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' expect(stmt.affected_rows).to eq 0 stmt.execute 1 expect(stmt.affected_rows).to eq 1 end it 'should return number of rows affected by an update' do stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' stmt.execute 1 expect(stmt.affected_rows).to eq 1 stmt.execute 2 expect(stmt.affected_rows).to eq 1 stmt = @client.prepare 'UPDATE lastIdTest SET blah=? WHERE blah=?' stmt.execute 0, 1 expect(stmt.affected_rows).to eq 1 end it 'should return number of rows affected by a delete' do stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)' stmt.execute 1 expect(stmt.affected_rows).to eq 1 stmt.execute 2 expect(stmt.affected_rows).to eq 1 stmt = @client.prepare 'DELETE FROM lastIdTest WHERE blah=?' stmt.execute 1 expect(stmt.affected_rows).to eq 1 end end context 'close' do it 'should free server resources' do stmt = @client.prepare 'SELECT 1' expect { stmt.close }.to change { @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i }.by(-1) end it 'should raise an error on subsequent execution' do stmt = @client.prepare 'SELECT 1' stmt.close expect { stmt.execute }.to raise_error(Mysql2::Error, /Invalid statement handle/) end end end mysql2-0.4.3/spec/mysql2/error_spec.rb0000644000004100000410000000430012663414022017643 0ustar www-datawww-data# encoding: UTF-8 require 'spec_helper' RSpec.describe Mysql2::Error do let(:client) { Mysql2::Client.new(DatabaseCredentials['root']) } let(:error) do begin client.query("HAHAHA") rescue Mysql2::Error => e error = e end error end it "responds to error_number and sql_state, with aliases" do expect(error).to respond_to(:error_number) expect(error).to respond_to(:sql_state) # Mysql gem compatibility expect(error).to respond_to(:errno) expect(error).to respond_to(:error) end context 'encoding' do let(:valid_utf8) { '造字' } let(:error) do begin client.query(valid_utf8) rescue Mysql2::Error => e e end end let(:invalid_utf8) { "\xE5\xC6\x7D\x1F" } let(:bad_err) do begin client.query(invalid_utf8) rescue Mysql2::Error => e e end end before do pending('String#encoding is not defined') unless String.public_method_defined?(:encoding) # sanity check expect(valid_utf8.encoding).to eql(Encoding::UTF_8) expect(valid_utf8).to be_valid_encoding expect(invalid_utf8.encoding).to eql(Encoding::UTF_8) expect(invalid_utf8).to_not be_valid_encoding end it "returns error messages as UTF-8 by default" do with_internal_encoding nil do expect(error.message.encoding).to eql(Encoding::UTF_8) expect(error.message).to be_valid_encoding expect(bad_err.message.encoding).to eql(Encoding::UTF_8) expect(bad_err.message).to be_valid_encoding expect(bad_err.message).to include("??}\u001F") end end it "returns sql state as ASCII" do expect(error.sql_state.encoding).to eql(Encoding::US_ASCII) expect(error.sql_state).to be_valid_encoding end it "returns error messages and sql state in Encoding.default_internal if set" do with_internal_encoding Encoding::UTF_16LE do expect(error.message.encoding).to eql(Encoding.default_internal) expect(error.message).to be_valid_encoding expect(bad_err.message.encoding).to eql(Encoding.default_internal) expect(bad_err.message).to be_valid_encoding end end end end mysql2-0.4.3/spec/mysql2/result_spec.rb0000644000004100000410000005023712663414022020042 0ustar www-datawww-data# encoding: UTF-8 require 'spec_helper' RSpec.describe Mysql2::Result do before(:each) do @result = @client.query "SELECT 1" end it "should raise a TypeError exception when it doesn't wrap a result set" do r = Mysql2::Result.new expect { r.count }.to raise_error(TypeError) expect { r.fields }.to raise_error(TypeError) expect { r.size }.to raise_error(TypeError) expect { r.each }.to raise_error(TypeError) end it "should have included Enumerable" do expect(Mysql2::Result.ancestors.include?(Enumerable)).to be true end it "should respond to #each" do expect(@result).to respond_to(:each) end it "should respond to #free" do expect(@result).to respond_to(:free) end it "should raise a Mysql2::Error exception upon a bad query" do expect { @client.query "bad sql" }.to raise_error(Mysql2::Error) expect { @client.query "SELECT 1" }.not_to raise_error end it "should respond to #count, which is aliased as #size" do r = @client.query "SELECT 1" expect(r).to respond_to :count expect(r).to respond_to :size end it "should be able to return the number of rows in the result set" do r = @client.query "SELECT 1" expect(r.count).to eql(1) expect(r.size).to 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| expect(row).to be_an_instance_of(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| expect(row.keys.first).to be_an_instance_of(Symbol) end end it "should be able to return results as an array" do @result.each(:as => :array) do |row| expect(row).to be_an_instance_of(Array) end end it "should cache previously yielded results by default" do expect(@result.first.object_id).to 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 expect(result.first.object_id).not_to 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 expect(result.first).not_to 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 expect(result.first).to 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 @test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1") end it "method should exist" do expect(@test_result).to respond_to(:fields) end it "should return an array of field names in proper order" do result = @client.query "SELECT 'a', 'b', 'c'" expect(result.fields).to eql(%w(a b c)) end end context "streaming" do it "should maintain a count while streaming" do result = @client.query('SELECT 1', :stream => true, :cache_rows => false) expect(result.count).to eql(0) result.each.to_a expect(result.count).to eql(1) end it "should retain the count when mixing first and each" do result = @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false) expect(result.count).to eql(0) result.first expect(result.count).to eql(1) result.each.to_a expect(result.count).to eql(2) end it "should not yield nil at the end of streaming" do result = @client.query('SELECT * FROM mysql2_test', :stream => true, :cache_rows => false) result.each { |r| expect(r).not_to 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) expect(result.count).to eql(0) result.each.to_a expect(result.count).to eql(0) end it "should raise an exception if streaming ended due to a timeout" do # Create an extra client instance, since we're going to time it out client = Mysql2::Client.new DatabaseCredentials['root'] client.query "CREATE TEMPORARY TABLE streamingTest (val BINARY(255)) ENGINE=MEMORY" # Insert enough records to force the result set into multiple reads # (the BINARY type is used simply because it forces full width results) 10000.times do |i| client.query "INSERT INTO streamingTest (val) VALUES ('Foo #{i}')" end client.query "SET net_write_timeout = 1" res = client.query "SELECT * FROM streamingTest", :stream => true, :cache_rows => false expect { res.each_with_index do |_, i| # Exhaust the first result packet then trigger a timeout sleep 2 if i > 0 && i % 1000 == 0 end }.to raise_error(Mysql2::Error, /Lost connection/) end end context "row data type mapping" do before(:each) do @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 expect(result["null_test"]).to be_nil expect(result["tiny_int_test"]).to eql("1") expect(result["bool_cast_test"]).to eql("1") expect(result["int_test"]).to eql("10") expect(result["date_test"]).to eql("2010-04-04") expect(result["enum_test"]).to eql("val1") end it "should return nil for a NULL value" do expect(@test_result['null_test']).to be_an_instance_of(NilClass) expect(@test_result['null_test']).to eql(nil) end it "should return String for a BIT(64) value" do expect(@test_result['bit_test']).to be_an_instance_of(String) expect(@test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005") end it "should return String for a BIT(1) value" do expect(@test_result['single_bit_test']).to be_an_instance_of(String) expect(@test_result['single_bit_test']).to eql("\001") end it "should return Fixnum for a TINYINT value" do expect([Fixnum, Bignum]).to include(@test_result['tiny_int_test'].class) expect(@test_result['tiny_int_test']).to 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 expect(result1.first['bool_cast_test']).to be true expect(result2.first['bool_cast_test']).to be false expect(result3.first['bool_cast_test']).to 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 expect(result1.first['single_bit_test']).to be true expect(result2.first['single_bit_test']).to be false @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" end it "should return Fixnum for a SMALLINT value" do expect([Fixnum, Bignum]).to include(@test_result['small_int_test'].class) expect(@test_result['small_int_test']).to eql(10) end it "should return Fixnum for a MEDIUMINT value" do expect([Fixnum, Bignum]).to include(@test_result['medium_int_test'].class) expect(@test_result['medium_int_test']).to eql(10) end it "should return Fixnum for an INT value" do expect([Fixnum, Bignum]).to include(@test_result['int_test'].class) expect(@test_result['int_test']).to eql(10) end it "should return Fixnum for a BIGINT value" do expect([Fixnum, Bignum]).to include(@test_result['big_int_test'].class) expect(@test_result['big_int_test']).to eql(10) end it "should return Fixnum for a YEAR value" do expect([Fixnum, Bignum]).to include(@test_result['year_test'].class) expect(@test_result['year_test']).to eql(2009) end it "should return BigDecimal for a DECIMAL value" do expect(@test_result['decimal_test']).to be_an_instance_of(BigDecimal) expect(@test_result['decimal_test']).to eql(10.3) end it "should return Float for a FLOAT value" do expect(@test_result['float_test']).to be_an_instance_of(Float) expect(@test_result['float_test']).to eql(10.3) end it "should return Float for a DOUBLE value" do expect(@test_result['double_test']).to be_an_instance_of(Float) expect(@test_result['double_test']).to eql(10.3) end it "should return Time for a DATETIME value when within the supported range" do expect(@test_result['date_time_test']).to be_an_instance_of(Time) expect(@test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end if 1.size == 4 # 32bit klass = if RUBY_VERSION =~ /1.8/ DateTime else Time 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") expect(r.first['test']).to be_an_instance_of(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") expect(r.first['test']).to be_an_instance_of(klass) end elsif 1.size == 8 # 64bit if RUBY_VERSION =~ /1.8/ 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") expect(r.first['test']).to be_an_instance_of(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") expect(r.first['test']).to be_an_instance_of(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") expect(r.first['test']).to be_an_instance_of(Time) end else 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") expect(r.first['test']).to be_an_instance_of(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") expect(r.first['test']).to be_an_instance_of(Time) end end end it "should return Time for a TIMESTAMP value when within the supported range" do expect(@test_result['timestamp_test']).to be_an_instance_of(Time) expect(@test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00') end it "should return Time for a TIME value" do expect(@test_result['time_test']).to be_an_instance_of(Time) expect(@test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00') end it "should return Date for a DATE value" do expect(@test_result['date_test']).to be_an_instance_of(Date) expect(@test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04') end it "should return String for an ENUM value" do expect(@test_result['enum_test']).to be_an_instance_of(String) expect(@test_result['enum_test']).to eql('val1') end it "should raise an error given an invalid DATETIME" do expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \ raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") end context "string encoding for ENUM values" do before { pending('Encoding is undefined') unless defined?(Encoding) } it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding::ASCII) client2.close end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['enum_test'].encoding).to eql(Encoding.default_internal) end end end it "should return String for a SET value" do expect(@test_result['set_test']).to be_an_instance_of(String) expect(@test_result['set_test']).to eql('val1,val2') end context "string encoding for SET values" do before { pending('Encoding is undefined') unless defined?(Encoding) } it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding::ASCII) client2.close end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['set_test'].encoding).to eql(Encoding.default_internal) end end end it "should return String for a BINARY value" do expect(@test_result['binary_test']).to be_an_instance_of(String) expect(@test_result['binary_test']).to eql("test#{"\000" * 6}") end context "string encoding for BINARY values" do before { pending('Encoding is undefined') unless defined?(Encoding) } it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end it "should not use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::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 expect(@test_result[field]).to be_an_instance_of(String) expect(@test_result[field]).to eql("test") end context "string encoding for #{type} values" do before { pending('Encoding is undefined') unless defined?(Encoding) } if %w(VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB).include?(type) it "should default to binary if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end it "should not use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result['binary_test'].encoding).to eql(Encoding::BINARY) end end else it "should default to utf-8 if Encoding.default_internal is nil" do with_internal_encoding nil do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding::ASCII) client2.close end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first expect(result[field].encoding).to eql(Encoding.default_internal) end end end end end end end mysql2-0.4.3/spec/mysql2/client_spec.rb0000644000004100000410000010476312663414022020006 0ustar www-datawww-data# encoding: UTF-8 require 'spec_helper' require 'stringio' RSpec.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 expect { opts = DatabaseCredentials['root'].merge(:default_file => cnf_file, :default_group => "test") @client = Mysql2::Client.new(opts) }.not_to raise_error end it "should not raise an exception without default group" do expect { @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:default_file => cnf_file)) }.not_to raise_error end end it "should raise an exception upon connection failure" do expect { # 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) }.to raise_error(Mysql2::Error) end it "should raise an exception on create for invalid encodings" do expect { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "fake")) }.to raise_error(Mysql2::Error) end it "should not raise an exception on create for a valid encoding" do expect { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8")) }.not_to raise_error expect { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5")) }.not_to raise_error end Klient = Class.new(Mysql2::Client) do attr_reader :connect_args def connect(*args) @connect_args ||= [] @connect_args << args end end it "should accept connect flags and pass them to #connect" do client = Klient.new :flags => Mysql2::Client::FOUND_ROWS expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to be > 0 end it "should parse flags array" do client = Klient.new :flags => %w( FOUND_ROWS -PROTOCOL_41 ) expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS) expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0) end it "should parse flags string" do client = Klient.new :flags => "FOUND_ROWS -PROTOCOL_41" expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS) expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0) end it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do client = Klient.new client_flags = Mysql2::Client::REMEMBER_OPTIONS | Mysql2::Client::LONG_PASSWORD | Mysql2::Client::LONG_FLAG | Mysql2::Client::TRANSACTIONS | Mysql2::Client::PROTOCOL_41 | Mysql2::Client::SECURE_CONNECTION expect(client.connect_args.last[6]).to eql(client_flags) end it "should execute init command" do options = DatabaseCredentials['root'].dup options[:init_command] = "SET @something = 'setting_value';" client = Mysql2::Client.new(options) result = client.query("SELECT @something;") expect(result.first['@something']).to eq('setting_value') end it "should send init_command after reconnect" do options = DatabaseCredentials['root'].dup options[:init_command] = "SET @something = 'setting_value';" options[:reconnect] = true client = Mysql2::Client.new(options) result = client.query("SELECT @something;") expect(result.first['@something']).to eq('setting_value') # get the current connection id result = client.query("SELECT CONNECTION_ID()") first_conn_id = result.first['CONNECTION_ID()'] # break the current connection expect { client.query("KILL #{first_conn_id}") }.to raise_error(Mysql2::Error) client.ping # reconnect now # get the new connection id result = client.query("SELECT CONNECTION_ID()") second_conn_id = result.first['CONNECTION_ID()'] # confirm reconnect by checking the new connection id expect(first_conn_id).not_to eq(second_conn_id) # At last, check that the init command executed result = client.query("SELECT @something;") expect(result.first['@something']).to eq('setting_value') end it "should have a global default_query_options hash" do expect(Mysql2::Client).to respond_to(:default_query_options) end it "should be able to connect via SSL options" do ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'" ssl_uncompiled = ssl.any? { |x| x['Value'] == 'OFF' } pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled ssl_disabled = ssl.any? { |x| x['Value'] == 'DISABLED' } pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled # You may need to adjust the lines below to match your SSL certificate paths ssl_client = nil expect { # rubocop:disable Style/TrailingComma ssl_client = Mysql2::Client.new( DatabaseCredentials['root'].merge( 'host' => 'mysql2gem.example.com', # must match the certificates :sslkey => '/etc/mysql/client-key.pem', :sslcert => '/etc/mysql/client-cert.pem', :sslca => '/etc/mysql/ca-cert.pem', :sslcipher => 'DHE-RSA-AES256-SHA', :sslverify => true ) ) # rubocop:enable Style/TrailingComma }.not_to raise_error results = Hash[ssl_client.query('SHOW STATUS WHERE Variable_name LIKE "Ssl_%"').map { |x| x.values_at('Variable_name', 'Value') }] expect(results['Ssl_cipher']).not_to be_empty expect(results['Ssl_version']).not_to be_empty expect(ssl_client.ssl_cipher).not_to be_empty expect(results['Ssl_cipher']).to eql(ssl_client.ssl_cipher) ssl_client.close end def run_gc if defined?(Rubinius) GC.run(true) else GC.start end sleep(0.5) end it "should terminate connections when calling close" do expect { Mysql2::Client.new(DatabaseCredentials['root']).close }.to_not change { @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a + @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a } end it "should not leave dangling connections after garbage collection" do run_gc expect { expect { 10.times do Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1') end }.to change { @client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i }.by(10) run_gc }.to_not change { @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a + @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a } end context "#automatic_close" do it "is enabled by default" do client = Mysql2::Client.new(DatabaseCredentials['root']) expect(client.automatic_close?).to be(true) end if RUBY_PLATFORM =~ /mingw|mswin/ it "cannot be disabled" do stderr, $stderr = $stderr, StringIO.new begin Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false)) expect($stderr.string).to include('always closed by garbage collector') $stderr.reopen client = Mysql2::Client.new(DatabaseCredentials['root']) client.automatic_close = false expect($stderr.string).to include('always closed by garbage collector') $stderr.reopen expect { client.automatic_close = true }.to_not change { $stderr.string } ensure $stderr = stderr end end else it "can be configured" do client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false)) expect(client.automatic_close?).to be(false) end it "can be assigned" do client = Mysql2::Client.new(DatabaseCredentials['root']) client.automatic_close = false expect(client.automatic_close?).to be(false) client.automatic_close = true expect(client.automatic_close?).to be(true) client.automatic_close = nil expect(client.automatic_close?).to be(false) client.automatic_close = 9 expect(client.automatic_close?).to be(true) end it "should not close connections when running in a child process" do run_gc client = Mysql2::Client.new(DatabaseCredentials['root']) client.automatic_close = false # this empty `fork` call fixes this tests on RBX; without it, the next # `fork` call hangs forever. WTF? fork {} fork do client.query('SELECT 1') client = nil run_gc end Process.wait # this will throw an error if the underlying socket was shutdown by the # child's GC expect { client.query('SELECT 1') }.to_not raise_exception end end end it "should be able to connect to database with numeric-only name" do creds = DatabaseCredentials['numericuser'] @client.query "CREATE DATABASE IF NOT EXISTS `#{creds['database']}`" @client.query "GRANT ALL ON `#{creds['database']}`.* TO #{creds['username']}@`#{creds['host']}`" expect { Mysql2::Client.new(creds) }.not_to raise_error @client.query "DROP DATABASE IF EXISTS `#{creds['database']}`" end it "should respond to #close" do expect(@client).to respond_to(:close) end it "should be able to close properly" do expect(@client.close).to be_nil expect { @client.query "SELECT 1" }.to raise_error(Mysql2::Error) end it "should respond to #query" do expect(@client).to respond_to(:query) end it "should respond to #warning_count" do expect(@client).to respond_to(:warning_count) end context "#warning_count" do context "when no warnings" do it "should 0" do @client.query('select 1') expect(@client.warning_count).to eq(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") expect(@client.warning_count).to be > 0 end end end it "should respond to #query_info" do expect(@client).to respond_to(:query_info) end context "#query_info" do context "when no info present" do it "should 0" do @client.query('select 1') expect(@client.query_info).to be_empty expect(@client.query_info_string).to 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)") expect(@client.query_info).to eql(:records => 2, :duplicates => 0, :warnings => 0) expect(@client.query_info_string).to eq('Records: 2 Duplicates: 0 Warnings: 0') @client.query "DROP TABLE infoTest" end end end context ":local_infile" do before(:all) do @client_i = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => true) local = @client_i.query "SHOW VARIABLES LIKE 'local_infile'" local_enabled = local.any? { |x| x['Value'] == 'ON' } pending("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled @client_i.query %[ CREATE TABLE IF NOT EXISTS infileTest ( id MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY, foo VARCHAR(10), bar MEDIUMTEXT ) ] end after(:all) do @client_i.query "DROP TABLE infileTest" end it "should raise an error when local_infile is disabled" do client = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => false) expect { client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" }.to raise_error(Mysql2::Error, /command is not allowed/) end it "should raise an error when a non-existent file is loaded" do expect { @client_i.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest" }.to raise_error(Mysql2::Error, 'No such file or directory: this/file/is/not/here') end it "should LOAD DATA LOCAL INFILE" do @client_i.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest" info = @client_i.query_info expect(info).to eql(:records => 1, :deleted => 0, :skipped => 0, :warnings => 0) result = @client_i.query "SELECT * FROM infileTest" expect(result.first).to eql('id' => 1, 'foo' => 'Hello', 'bar' => 'World') end end it "should expect connect_timeout to be a positive integer" do expect { Mysql2::Client.new(:connect_timeout => -1) }.to raise_error(Mysql2::Error) end it "should expect read_timeout to be a positive integer" do expect { Mysql2::Client.new(:read_timeout => -1) }.to raise_error(Mysql2::Error) end it "should expect write_timeout to be a positive integer" do expect { Mysql2::Client.new(:write_timeout => -1) }.to raise_error(Mysql2::Error) end it "should allow nil read_timeout" do client = Mysql2::Client.new(:read_timeout => nil) expect(client.read_timeout).to be_nil 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_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 expect { @client.query ["SELECT 'not right'"] }.to 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 expect(@client.query_options[:something]).to be_nil expect(result.instance_variable_get('@query_options')).to eql(@client.query_options.merge(:something => :else)) expect(@client.instance_variable_get('@current_query_options')).to eql(@client.query_options.merge(:something => :else)) result = @client.query "SELECT 1" expect(result.instance_variable_get('@query_options')).to eql(@client.query_options) expect(@client.instance_variable_get('@current_query_options')).to 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" expect(@client.query_options[:something]).to eql(:else) expect(result.instance_variable_get('@query_options')[:something]).to eql(:else) # Clean up after this test @client.query_options.delete(:something) expect(@client.query_options[:something]).to be_nil end it "should return results as a hash by default" do expect(@client.query("SELECT 1").first).to be_an_instance_of(Hash) end it "should be able to return results as an array" do expect(@client.query("SELECT 1", :as => :array).first).to be_an_instance_of(Array) @client.query("SELECT 1").each(:as => :array) end it "should be able to return results with symbolized keys" do expect(@client.query("SELECT 1", :symbolize_keys => true).first.keys[0]).to be_an_instance_of(Symbol) end it "should require an open connection" do @client.close expect { @client.query "SELECT 1" }.to 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) expect { @client.query("SELECT 1") }.to 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 expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, Regexp.new(Regexp.escape(thr.inspect))) end it "should timeout if we wait longer than :read_timeout" do client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => 0)) expect { client.query('SELECT SLEEP(0.1)') }.to raise_error(Mysql2::Error) end # 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 kill_time = 0.1 query_time = 2 * kill_time mark = {} begin trap(:USR1) { mark.store(:USR1, Time.now) } pid = fork do sleep kill_time # wait for client query to start Process.kill(:USR1, Process.ppid) sleep # wait for explicit kill to prevent GC disconnect end mark.store(:QUERY_START, Time.now) @client.query("SELECT SLEEP(#{query_time})") mark.store(:QUERY_END, Time.now) ensure Process.kill(:TERM, pid) Process.waitpid2(pid) trap(:USR1, 'DEFAULT') end # the query ran uninterrupted expect(mark.fetch(:QUERY_END) - mark.fetch(:QUERY_START)).to be_within(0.02).of(query_time) # signals fired while the query was running expect(mark.fetch(:USR1)).to be_between(mark.fetch(:QUERY_START), mark.fetch(:QUERY_END)) end it "#socket should return a Fixnum (file descriptor from C)" do expect(@client.socket).to be_an_instance_of(Fixnum) expect(@client.socket).not_to eql(0) end it "#socket should require an open connection" do @client.close expect { @client.socket }.to raise_error(Mysql2::Error) end it 'should be impervious to connection-corrupting timeouts in #query' do pending('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt) # attempt to break the connection expect { Timeout.timeout(0.1) { @client.query('SELECT SLEEP(0.2)') } }.to raise_error(Timeout::Error) # expect the connection to not be broken expect { @client.query('SELECT 1') }.to_not raise_error end it 'should be impervious to connection-corrupting timeouts in #execute' do # the statement handle gets corrupted and will segfault the tests if interrupted, # so we can't even use pending on this test, really have to skip it on older Rubies. skip('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt) # attempt to break the connection stmt = @client.prepare('SELECT SLEEP(?)') expect { Timeout.timeout(0.1) { stmt.execute(0.2) } }.to raise_error(Timeout::Error) stmt.close # expect the connection to not be broken expect { @client.query('SELECT 1') }.to_not raise_error end context 'when a non-standard exception class is raised' do it "should close the connection when an exception is raised" do expect { Timeout.timeout(0.1, ArgumentError) { @client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, 'closed MySQL connection') end it "should handle Timeouts without leaving the connection hanging if reconnect is true" do if RUBY_PLATFORM.include?('darwin') && Mysql2::Client.info.fetch(:version).start_with?('5.5') pending('libmysqlclient 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') end client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true)) expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) expect { client.query('SELECT 1') }.to_not raise_error end it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction" do if RUBY_PLATFORM.include?('darwin') && Mysql2::Client.info.fetch(:version).start_with?('5.5') pending('libmysqlclient 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.') end client = Mysql2::Client.new(DatabaseCredentials['root']) expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) expect { client.query('SELECT 1') }.to raise_error(Mysql2::Error) client.reconnect = true expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError) expect { client.query('SELECT 1') }.to_not raise_error end end it "threaded queries should be supported" do sleep_time = 0.5 # Note that each thread opens its own database connection threads = 5.times.map do Thread.new do client = Mysql2::Client.new(DatabaseCredentials.fetch('root')) client.query("SELECT SLEEP(#{sleep_time})") Thread.current.object_id end end # This timeout demonstrates that the threads are sleeping concurrently: # In the serial case, the timeout would fire and the test would fail values = Timeout.timeout(sleep_time * 1.1) { threads.map(&:value) } expect(values).to match_array(threads.map(&:object_id)) end it "evented async queries should be supported" do # should immediately return nil expect(@client.query("SELECT sleep(0.1)", :async => true)).to 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 expect(loops >= 1).to be true result = @client.async_result expect(result).to be_an_instance_of(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 "should raise an exception when one of multiple statements fails" do result = @multi_client.query("SELECT 1 AS 'set_1'; SELECT * FROM invalid_table_name; SELECT 2 AS 'set_2';") expect(result.first['set_1']).to be(1) expect { @multi_client.next_result }.to raise_error(Mysql2::Error) expect(@multi_client.next_result).to be false end it "returns multiple result sets" do expect(@multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'").first).to eql('set_1' => 1) expect(@multi_client.next_result).to be true expect(@multi_client.store_result.first).to eql('set_2' => 2) expect(@multi_client.next_result).to be false end it "does not interfere with other statements" do @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'") @multi_client.store_result while @multi_client.next_result expect(@multi_client.query("SELECT 3 AS 'next'").first).to eq('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") expect { @multi_client.query("SELECT 4") }.to raise_error(Mysql2::Error) end it "#abandon_results! should work" do @multi_client.query("SELECT 1; SELECT 2; SELECT 3") @multi_client.abandon_results! expect { @multi_client.query("SELECT 4") }.not_to raise_error end it "#more_results? should work" do @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'") expect(@multi_client.more_results?).to be true @multi_client.next_result @multi_client.store_result expect(@multi_client.more_results?).to be false end it "#more_results? should work with stored procedures" do @multi_client.query("DROP PROCEDURE IF EXISTS test_proc") @multi_client.query("CREATE PROCEDURE test_proc() BEGIN SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'; END") expect(@multi_client.query("CALL test_proc()").first).to eql('set_1' => 1) expect(@multi_client.more_results?).to be true @multi_client.next_result expect(@multi_client.store_result.first).to eql('set_2' => 2) @multi_client.next_result expect(@multi_client.store_result).to be_nil # this is the result from CALL itself expect(@multi_client.more_results?).to be false end end end it "should respond to #socket" do expect(@client).to respond_to(:socket) end if RUBY_PLATFORM =~ /mingw|mswin/ it "#socket should raise as it's not supported" do expect { @client.socket }.to raise_error(Mysql2::Error) end end it "should respond to escape" do expect(Mysql2::Client).to respond_to(:escape) end context "escape" do it "should return a new SQL-escape version of the passed string" do expect(Mysql2::Client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno") end it "should return the passed string if nothing was escaped" do str = "plain" expect(Mysql2::Client.escape(str).object_id).to eql(str.object_id) end it "should not overflow the thread stack" do expect { Thread.new { Mysql2::Client.escape("'" * 256 * 1024) }.join }.not_to raise_error end it "should not overflow the process stack" do expect { Thread.new { Mysql2::Client.escape("'" * 1024 * 1024 * 4) }.join }.not_to raise_error 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) expect(escaped.encoding).to eql(str.encoding) str.encode!('us-ascii') escaped = Mysql2::Client.escape(str) expect(escaped.encoding).to eql(str.encoding) end end end it "should respond to #escape" do expect(@client).to respond_to(:escape) end context "#escape" do it "should return a new SQL-escape version of the passed string" do expect(@client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno") end it "should return the passed string if nothing was escaped" do str = "plain" expect(@client.escape(str).object_id).to eql(str.object_id) end it "should not overflow the thread stack" do expect { Thread.new { @client.escape("'" * 256 * 1024) }.join }.not_to raise_error end it "should not overflow the process stack" do expect { Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join }.not_to raise_error end it "should require an open connection" do @client.close expect { @client.escape "" }.to raise_error(Mysql2::Error) end context 'when mysql encoding is not utf8' do before { pending('Encoding is undefined') unless defined?(Encoding) } let(:client) { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ujis")) } it 'should return a internal encoding string if Encoding.default_internal is set' do with_internal_encoding Encoding::UTF_8 do expect(client.escape("\u{30C6}\u{30B9}\u{30C8}")).to eq "\u{30C6}\u{30B9}\u{30C8}" expect(client.escape("\u{30C6}'\u{30B9}\"\u{30C8}")).to eq "\u{30C6}\\'\u{30B9}\\\"\u{30C8}" end end end end it "should respond to #info" do expect(@client).to respond_to(:info) end it "#info should return a hash containing the client version ID and String" do info = @client.info expect(info).to be_an_instance_of(Hash) expect(info).to have_key(:id) expect(info[:id]).to be_an_instance_of(Fixnum) expect(info).to have_key(:version) expect(info[:version]).to be_an_instance_of(String) end context "strings returned by #info" do before { pending('Encoding is undefined') unless defined?(Encoding) } it "should be tagged as ascii" do expect(@client.info[:version].encoding).to eql(Encoding::US_ASCII) expect(@client.info[:header_version].encoding).to eql(Encoding::US_ASCII) end end context "strings returned by .info" do before { pending('Encoding is undefined') unless defined?(Encoding) } it "should be tagged as ascii" do expect(Mysql2::Client.info[:version].encoding).to eql(Encoding::US_ASCII) expect(Mysql2::Client.info[:header_version].encoding).to eql(Encoding::US_ASCII) end end it "should respond to #server_info" do expect(@client).to 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 expect(server_info).to be_an_instance_of(Hash) expect(server_info).to have_key(:id) expect(server_info[:id]).to be_an_instance_of(Fixnum) expect(server_info).to have_key(:version) expect(server_info[:version]).to be_an_instance_of(String) end it "#server_info should require an open connection" do @client.close expect { @client.server_info }.to raise_error(Mysql2::Error) end context "strings returned by #server_info" do before { pending('Encoding is undefined') unless defined?(Encoding) } it "should default to the connection's encoding if Encoding.default_internal is nil" do with_internal_encoding nil do expect(@client.server_info[:version].encoding).to eql(Encoding::UTF_8) client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii')) expect(client2.server_info[:version].encoding).to eql(Encoding::ASCII) end end it "should use Encoding.default_internal" do with_internal_encoding Encoding::UTF_8 do expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal) end with_internal_encoding Encoding::ASCII do expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal) end end end it "should raise a Mysql2::Error exception upon connection failure" do expect { Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42' }.to raise_error(Mysql2::Error) expect { Mysql2::Client.new DatabaseCredentials['root'] }.not_to raise_error end context 'write operations api' do before(:each) do @client.query "USE test" @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT 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 expect(@client).to respond_to(:last_id) end it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do expect(@client.last_id).to eql(0) @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)" expect(@client.last_id).to eql(1) end it "should respond to #last_id" do expect(@client).to 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)" expect(@client.affected_rows).to eql(1) @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1" expect(@client.affected_rows).to eql(1) end it "#last_id should handle BIGINT auto-increment ids above 32 bits" do # The id column type must be BIGINT. Surprise: INT(x) is limited to 32-bits for all values of x. # Insert a row with a given ID, this should raise the auto-increment state @client.query "INSERT INTO lastIdTest (id, blah) VALUES (5000000000, 5000)" expect(@client.last_id).to eql(5000000000) @client.query "INSERT INTO lastIdTest (blah) VALUES (5001)" expect(@client.last_id).to eql(5000000001) end end it "should respond to #thread_id" do expect(@client).to respond_to(:thread_id) end it "#thread_id should be a Fixnum" do expect(@client.thread_id).to be_an_instance_of(Fixnum) end it "should respond to #ping" do expect(@client).to 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 expect(@client).to respond_to(:select_db) end it "should switch databases" do @client.select_db("test_selectdb_0") expect(@client.query("SHOW TABLES").first.values.first).to eql("test0") @client.select_db("test_selectdb_1") expect(@client.query("SHOW TABLES").first.values.first).to eql("test1") @client.select_db("test_selectdb_0") expect(@client.query("SHOW TABLES").first.values.first).to eql("test0") end it "should raise a Mysql2::Error when the database doesn't exist" do expect { @client.select_db("nopenothere") }.to raise_error(Mysql2::Error) end it "should return the database switched to" do expect(@client.select_db("test_selectdb_1")).to eq("test_selectdb_1") end end it "#thread_id should return a boolean" do expect(@client.ping).to eql(true) @client.close expect(@client.ping).to eql(false) end unless RUBY_VERSION =~ /1.8/ it "should respond to #encoding" do expect(@client).to respond_to(:encoding) end end end mysql2-0.4.3/lib/0000755000004100000410000000000012663414022013543 5ustar www-datawww-datamysql2-0.4.3/lib/mysql2.rb0000644000004100000410000000544712663414022015331 0ustar www-datawww-data# encoding: UTF-8 require 'date' require 'bigdecimal' require 'rational' unless RUBY_VERSION >= '1.9.2' # Load libmysql.dll before requiring mysql2/mysql2.so # This gives a chance to be flexible about the load path # Or to bomb out with a clear error message instead of a linker crash if RUBY_PLATFORM =~ /mswin|mingw/ dll_path = if ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] # If this environment variable is set, it overrides any other paths # The user is advised to use backslashes not forward slashes ENV['RUBY_MYSQL2_LIBMYSQL_DLL'] elsif File.exist?(File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__))) # Use vendor/libmysql.dll if it exists, convert slashes for Win32 LoadLibrary File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)).tr('/', '\\') else # This will use default / system library paths 'libmysql.dll' end require 'Win32API' LoadLibrary = Win32API.new('Kernel32', 'LoadLibrary', ['P'], 'I') if 0 == LoadLibrary.call(dll_path) abort "Failed to load libmysql.dll from #{dll_path}" end end require 'mysql2/version' unless defined? Mysql2::VERSION require 'mysql2/error' require 'mysql2/mysql2' require 'mysql2/result' require 'mysql2/client' require 'mysql2/field' require 'mysql2/statement' # = 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 module 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 # # In Mysql2::Client#query and Mysql2::Statement#execute, # Thread#handle_interrupt is used to prevent Timeout#timeout # from interrupting query execution. # # Timeout::ExitException was removed in Ruby 2.3.0, 2.2.3, and 2.1.8, # but is present in earlier 2.1.x and 2.2.x, so we provide a shim. # if Thread.respond_to?(:handle_interrupt) require 'timeout' # rubocop:disable Style/ConstantName TimeoutError = if defined?(::Timeout::ExitException) ::Timeout::ExitException else ::Timeout::Error end end end end mysql2-0.4.3/lib/mysql2/0000755000004100000410000000000012663414022014772 5ustar www-datawww-datamysql2-0.4.3/lib/mysql2/result.rb0000644000004100000410000000007612663414022016640 0ustar www-datawww-datamodule Mysql2 class Result include Enumerable end end mysql2-0.4.3/lib/mysql2/em.rb0000644000004100000410000000215412663414022015722 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 @is_watching = true end def notify_readable detach begin result = @client.async_result rescue => e @deferable.fail(e) else @deferable.succeed(result) end end def watching? @is_watching end def unbind @is_watching = false end end def close(*args) @watch.detach if @watch && @watch.watching? super(*args) end def query(sql, opts = {}) if ::EM.reactor_running? super(sql, opts.merge(:async => true)) deferable = ::EM::DefaultDeferrable.new @watch = ::EM.watch(socket, Watcher, self, deferable) @watch.notify_readable = true deferable else super(sql, opts) end end end end end mysql2-0.4.3/lib/mysql2/console.rb0000644000004100000410000000017112663414022016760 0ustar www-datawww-data# Loaded by script/console. Land helpers here. Pry.config.prompt = lambda do |context, *| "[mysql2] #{context}> " end mysql2-0.4.3/lib/mysql2/statement.rb0000644000004100000410000000051712663414022017326 0ustar www-datawww-datamodule Mysql2 class Statement include Enumerable if Thread.respond_to?(:handle_interrupt) def execute(*args) Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do _execute(*args) end end else def execute(*args) _execute(*args) end end end end mysql2-0.4.3/lib/mysql2/client.rb0000644000004100000410000001233612663414022016602 0ustar www-datawww-datamodule Mysql2 class Client attr_reader :query_options, :read_timeout def self.default_query_options @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 its 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, } end def initialize(opts = {}) opts = Mysql2::Util.key_hash_as_symbols(opts) @read_timeout = nil @query_options = self.class.default_query_options.dup @query_options.merge! opts initialize_ext # Set default connect_timeout to avoid unlimited retries from signal interruption opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout) # TODO: stricter validation rather than silent massaging [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command, :automatic_close].each do |key| next unless opts.key?(key) case key when :reconnect, :local_infile, :secure_auth, :automatic_close send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation when :connect_timeout, :read_timeout, :write_timeout send(:"#{key}=", opts[key]) unless opts[key].nil? 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? case opts[:flags] when Array flags = parse_flags_array(opts[:flags], @query_options[:connect_flags]) when String flags = parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags]) when Integer flags = @query_options[:connect_flags] | opts[:flags] else flags = @query_options[:connect_flags] end # SSL verify is a connection flag rather than a mysql_ssl_set option flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] && ssl_options.any? if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.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] # 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 parse_flags_array(flags, initial = 0) flags.reduce(initial) do |memo, f| fneg = f.start_with?('-') ? f[1..-1] : nil if fneg && fneg =~ /^\w+$/ && Mysql2::Client.const_defined?(fneg) memo & ~ Mysql2::Client.const_get(fneg) elsif f && f =~ /^\w+$/ && Mysql2::Client.const_defined?(f) memo | Mysql2::Client.const_get(f) else warn "Unknown MySQL connection flag: '#{f}'" memo end end end if Thread.respond_to?(:handle_interrupt) def query(sql, options = {}) Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do _query(sql, @query_options.merge(options)) end end else def query(sql, options = {}) _query(sql, @query_options.merge(options)) end 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 def info self.class.info end class << self private def local_offset ::Time.local(2010).utc_offset.to_r / 86400 end end end end mysql2-0.4.3/lib/mysql2/version.rb0000644000004100000410000000004612663414022017004 0ustar www-datawww-datamodule Mysql2 VERSION = "0.4.3" end mysql2-0.4.3/lib/mysql2/field.rb0000644000004100000410000000006512663414022016403 0ustar www-datawww-datamodule Mysql2 Field = Struct.new(:name, :type) end mysql2-0.4.3/lib/mysql2/error.rb0000644000004100000410000000445212663414022016455 0ustar www-datawww-data# encoding: UTF-8 module Mysql2 class Error < StandardError ENCODE_OPTS = { :undef => :replace, :invalid => :replace, :replace => '?'.freeze, }.freeze attr_reader :error_number, :sql_state # Mysql gem compatibility alias_method :errno, :error_number alias_method :error, :message def initialize(msg) @server_version ||= nil super(clean_message(msg)) end def self.new_with_args(msg, server_version, error_number, sql_state) err = allocate err.instance_variable_set('@server_version', server_version) err.instance_variable_set('@error_number', error_number) err.instance_variable_set('@sql_state', sql_state.respond_to?(:encode) ? sql_state.encode(ENCODE_OPTS) : sql_state) err.send(:initialize, msg) err end private # In MySQL 5.5+ error messages are always constructed server-side as UTF-8 # then returned in the encoding set by the `character_set_results` system # variable. # # See http://dev.mysql.com/doc/refman/5.5/en/charset-errors.html for # more context. # # Before MySQL 5.5 error message template strings are in whatever encoding # is associated with the error message language. # See http://dev.mysql.com/doc/refman/5.1/en/error-message-language.html # for more information. # # The issue is that the user-data inserted in the message could potentially # be in any encoding MySQL supports and is insert into the latin1, euckr or # koi8r string raw. Meaning there's a high probability the string will be # corrupt encoding-wise. # # See http://dev.mysql.com/doc/refman/5.1/en/charset-errors.html for # more information. # # So in an attempt to make sure the error message string is always in a valid # encoding, we'll assume UTF-8 and clean the string of anything that's not a # valid UTF-8 character. # # Except for if we're on 1.8, where we'll do nothing ;) # # Returns a valid UTF-8 string in Ruby 1.9+, the original string on Ruby 1.8 def clean_message(message) return message unless message.respond_to?(:encode) if @server_version && @server_version > 50500 message.encode(ENCODE_OPTS) else message.encode(Encoding::UTF_8, ENCODE_OPTS) end end end end mysql2-0.4.3/metadata.yml0000644000004100000410000000557112663414022015310 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: mysql2 version: !ruby/object:Gem::Version version: 0.4.3 platform: ruby authors: - Brian Lopez - Aaron Stone autorequire: bindir: bin cert_chain: [] date: 2016-02-24 00:00:00.000000000 Z dependencies: [] description: email: - seniorlopez@gmail.com - aaron@serendipity.cx executables: [] extensions: - ext/mysql2/extconf.rb extra_rdoc_files: [] files: - CHANGELOG.md - LICENSE - README.md - ext/mysql2/client.c - ext/mysql2/client.h - ext/mysql2/extconf.rb - ext/mysql2/infile.c - ext/mysql2/infile.h - 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/statement.c - ext/mysql2/statement.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/field.rb - lib/mysql2/result.rb - lib/mysql2/statement.rb - lib/mysql2/version.rb - support/libmysql.def - 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/mysql2/statement_spec.rb - spec/rcov.opts - spec/spec_helper.rb - spec/ssl/ca-cert.pem - spec/ssl/ca-key.pem - spec/ssl/ca.cnf - spec/ssl/cert.cnf - spec/ssl/client-cert.pem - spec/ssl/client-key.pem - spec/ssl/client-req.pem - spec/ssl/gen_certs.sh - spec/ssl/pkcs8-client-key.pem - spec/ssl/pkcs8-server-key.pem - spec/ssl/server-cert.pem - spec/ssl/server-key.pem - spec/ssl/server-req.pem - spec/test_data 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.14 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/mysql2/statement_spec.rb - spec/rcov.opts - spec/spec_helper.rb - spec/ssl/ca-cert.pem - spec/ssl/ca-key.pem - spec/ssl/ca.cnf - spec/ssl/cert.cnf - spec/ssl/client-cert.pem - spec/ssl/client-key.pem - spec/ssl/client-req.pem - spec/ssl/gen_certs.sh - spec/ssl/pkcs8-client-key.pem - spec/ssl/pkcs8-server-key.pem - spec/ssl/server-cert.pem - spec/ssl/server-key.pem - spec/ssl/server-req.pem - spec/test_data mysql2-0.4.3/LICENSE0000644000004100000410000000206612663414022014006 0ustar www-datawww-dataThe MIT License (MIT) Copyright (c) 2014 Brian Lopez 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.4.3/CHANGELOG.md0000644000004100000410000000012712663414022014606 0ustar www-datawww-dataChanges are maintained under [Releases](https://github.com/brianmario/mysql2/releases) mysql2-0.4.3/support/0000755000004100000410000000000012663414022014511 5ustar www-datawww-datamysql2-0.4.3/support/ruby_enc_to_mysql.rb0000644000004100000410000000267512663414022020605 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.4.3/support/libmysql.def0000644000004100000410000001072212663414022017027 0ustar www-datawww-data; MySQL's Connector/C ships with a libmysql.dll main library and libmysql.lib ; interface library. However, the interface library is not linkable by MinGW. ; ; At compile time, we generate a libmysql.a interface library with dlltool.exe. ; ; This def file can be re-generated using the reimp.exe or gendef.exe tools. ; LIBRARY libmysql.dll EXPORTS mysql_affected_rows mysql_affected_rows@4 mysql_change_user mysql_change_user@16 mysql_character_set_name mysql_character_set_name@4 mysql_close mysql_close@4 mysql_data_seek mysql_data_seek@12 mysql_debug mysql_debug@4 mysql_dump_debug_info mysql_dump_debug_info@4 mysql_eof mysql_eof@4 mysql_errno mysql_errno@4 mysql_error mysql_error@4 mysql_escape_string mysql_escape_string@12 mysql_fetch_field mysql_fetch_field@4 mysql_fetch_field_direct mysql_fetch_field_direct@8 mysql_fetch_fields mysql_fetch_fields@4 mysql_fetch_lengths mysql_fetch_lengths@4 mysql_fetch_row mysql_fetch_row@4 mysql_field_count mysql_field_count@4 mysql_field_seek mysql_field_seek@8 mysql_field_tell mysql_field_tell@4 mysql_free_result mysql_free_result@4 mysql_get_client_info mysql_get_client_info@0 mysql_get_client_version mysql_get_client_version@0 mysql_get_host_info mysql_get_host_info@4 mysql_get_option mysql_get_option@12 mysql_get_proto_info mysql_get_proto_info@4 mysql_get_server_info mysql_get_server_info@4 mysql_get_server_version mysql_get_server_version@4 mysql_get_ssl_cipher mysql_get_ssl_cipher@4 mysql_hex_string mysql_hex_string@12 mysql_info mysql_info@4 mysql_init mysql_init@4 mysql_insert_id mysql_insert_id@4 mysql_kill mysql_kill@8 mysql_library_end mysql_library_end@0 mysql_library_init mysql_library_init@12 mysql_list_dbs mysql_list_dbs@8 mysql_list_fields mysql_list_fields@12 mysql_list_processes mysql_list_processes@4 mysql_list_tables mysql_list_tables@8 mysql_more_results mysql_more_results@4 mysql_next_result mysql_next_result@4 mysql_num_fields mysql_num_fields@4 mysql_num_rows mysql_num_rows@4 mysql_options mysql_options@12 mysql_options4 mysql_options4@16 mysql_ping mysql_ping@4 mysql_query mysql_query@8 mysql_read_query_result mysql_read_query_result@4 mysql_real_connect mysql_real_connect@32 mysql_real_escape_string mysql_real_escape_string@16 mysql_real_query mysql_real_query@12 mysql_refresh mysql_refresh@8 mysql_reset_connection mysql_reset_connection@4 mysql_rollback mysql_rollback@4 mysql_row_seek mysql_row_seek@8 mysql_row_tell mysql_row_tell@4 mysql_select_db mysql_select_db@8 mysql_send_query mysql_send_query@12 mysql_server_end mysql_server_end@0 mysql_server_init mysql_server_init@12 mysql_session_track_get_first mysql_session_track_get_first@16 mysql_session_track_get_next mysql_session_track_get_next@16 mysql_set_character_set mysql_set_character_set@8 mysql_set_local_infile_default mysql_set_local_infile_default@4 mysql_set_local_infile_handler mysql_set_local_infile_handler@24 mysql_set_server_option mysql_set_server_option@8 mysql_shutdown mysql_shutdown@8 mysql_sqlstate mysql_sqlstate@4 mysql_ssl_set mysql_ssl_set@24 mysql_stat mysql_stat@4 mysql_stmt_affected_rows mysql_stmt_affected_rows@4 mysql_stmt_attr_get mysql_stmt_attr_get@12 mysql_stmt_attr_set mysql_stmt_attr_set@12 mysql_stmt_bind_param mysql_stmt_bind_param@8 mysql_stmt_bind_result mysql_stmt_bind_result@8 mysql_stmt_close mysql_stmt_close@4 mysql_stmt_data_seek mysql_stmt_data_seek@12 mysql_stmt_errno mysql_stmt_errno@4 mysql_stmt_error mysql_stmt_error@4 mysql_stmt_execute mysql_stmt_execute@4 mysql_stmt_fetch mysql_stmt_fetch@4 mysql_stmt_fetch_column mysql_stmt_fetch_column@16 mysql_stmt_field_count mysql_stmt_field_count@4 mysql_stmt_free_result mysql_stmt_free_result@4 mysql_stmt_init mysql_stmt_init@4 mysql_stmt_insert_id mysql_stmt_insert_id@4 mysql_stmt_next_result mysql_stmt_next_result@4 mysql_stmt_num_rows mysql_stmt_num_rows@4 mysql_stmt_param_count mysql_stmt_param_count@4 mysql_stmt_param_metadata mysql_stmt_param_metadata@4 mysql_stmt_prepare mysql_stmt_prepare@12 mysql_stmt_reset mysql_stmt_reset@4 mysql_stmt_result_metadata mysql_stmt_result_metadata@4 mysql_stmt_row_seek mysql_stmt_row_seek@8 mysql_stmt_row_tell mysql_stmt_row_tell@4 mysql_stmt_send_long_data mysql_stmt_send_long_data@16 mysql_stmt_sqlstate mysql_stmt_sqlstate@4 mysql_stmt_store_result mysql_stmt_store_result@4 mysql_store_result mysql_store_result@4 mysql_thread_end mysql_thread_end@0 mysql_thread_id mysql_thread_id@4 mysql_thread_init mysql_thread_init@0 mysql_thread_safe mysql_thread_safe@0 mysql_use_result mysql_use_result@4 mysql_warning_count mysql_warning_count@4 mysql2-0.4.3/support/mysql_enc_to_ruby.rb0000644000004100000410000000412612663414022020576 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 = if encoding.nil? || encoding[1] == 'NULL' 'NULL' else "\"#{encoding[1]}\"" end " #{name}" end # start printing output puts "static const char *mysql2_mysql_enc_to_rb[] = {" puts encodings_with_nil.join(",\n") puts "};" mysql2-0.4.3/ext/0000755000004100000410000000000012663414022013575 5ustar www-datawww-datamysql2-0.4.3/ext/mysql2/0000755000004100000410000000000012663414022015024 5ustar www-datawww-datamysql2-0.4.3/ext/mysql2/statement.h0000644000004100000410000000065712663414022017211 0ustar www-datawww-data#ifndef MYSQL2_STATEMENT_H #define MYSQL2_STATEMENT_H extern VALUE cMysql2Statement; typedef struct { VALUE client; MYSQL_STMT *stmt; int refcount; int closed; } mysql_stmt_wrapper; void init_mysql2_statement(void); void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper); VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql); void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) RB_MYSQL_NORETURN; #endif mysql2-0.4.3/ext/mysql2/mysql_enc_name_to_ruby.h0000644000004100000410000001331312663414022021733 0ustar www-datawww-data/* C code produced by gperf version 3.0.4 */ /* 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 #if defined __GNUC_STDC_INLINE__ || defined __GNUC_GNU_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.4.3/ext/mysql2/result.c0000644000004100000410000011113712663414022016512 0ustar www-datawww-data#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 #define GET_RESULT(self) \ mysql2_result_wrapper *wrapper; \ Data_Get_Struct(self, mysql2_result_wrapper, wrapper); typedef struct { int symbolizeKeys; int asArray; int castBool; int cacheRows; int cast; int streaming; ID db_timezone; ID app_timezone; VALUE block_given; } result_each_args; VALUE cBigDecimal, cDateTime, cDate; static VALUE cMysql2Result; 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; /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */ 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); rb_gc_mark(w->statement); } } /* this may be called manually or during GC */ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) { if (!wrapper) return; if (wrapper->resultFreed != 1) { if (wrapper->stmt_wrapper) { if (!wrapper->stmt_wrapper->closed) { mysql_stmt_free_result(wrapper->stmt_wrapper->stmt); /* MySQL BUG? If the statement handle was previously used, and so * mysql_stmt_bind_result was called, and if that result set and bind buffers were freed, * MySQL still thinks the result set buffer is available and will prefetch the * first result in mysql_stmt_execute. This will corrupt or crash the program. * By setting bind_result_done back to 0, we make MySQL think that a result set * has never been bound to this statement handle before to prevent the prefetch. */ wrapper->stmt_wrapper->stmt->bind_result_done = 0; } if (wrapper->statement != Qnil) { decr_mysql2_stmt(wrapper->stmt_wrapper); } if (wrapper->result_buffers) { unsigned int i; for (i = 0; i < wrapper->numberOfFields; i++) { if (wrapper->result_buffers[i].buffer) { xfree(wrapper->result_buffers[i].buffer); } } xfree(wrapper->result_buffers); xfree(wrapper->is_null); xfree(wrapper->error); xfree(wrapper->length); } /* Clue that the next statement execute will need to allocate a new result buffer. */ wrapper->result_buffers = NULL; } /* FIXME: this may call flush_use_result, which can hit the socket */ /* For prepared statements, wrapper->result is the result metadata */ 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) { decr_mysql2_client(wrapper->client_wrapper); } xfree(wrapper); } static VALUE rb_mysql_result_free_(VALUE self) { GET_RESULT(self); rb_mysql_result_free_result(wrapper); return Qnil; } /* * 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 void *nogvl_stmt_fetch(void *ptr) { MYSQL_STMT *stmt = ptr; uintptr_t r = mysql_stmt_fetch(stmt); return (void *)r; } static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbolize_keys) { VALUE rb_field; GET_RESULT(self); 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) { #ifdef HAVE_RB_INTERN3 rb_field = rb_intern3(field->name, field->name_length, rb_utf8_encoding()); rb_field = ID2SYM(rb_field); #else VALUE colStr; colStr = rb_str_new(field->name, field->name_length); 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 its 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 /* Interpret microseconds digits left-aligned in fixed-width field. * e.g. 10.123 seconds means 10 seconds and 123000 microseconds, * because the microseconds are to the right of the decimal point. */ static unsigned int msec_char_to_uint(char *msec_char, size_t len) { size_t i; for (i = 0; i < (len - 1); i++) { if (msec_char[i] == '\0') { msec_char[i] = '0'; } } return (unsigned int)strtoul(msec_char, NULL, 10); } static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields) { unsigned int i; GET_RESULT(self); if (wrapper->result_buffers != NULL) return; wrapper->result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND)); wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(my_bool)); wrapper->length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long)); for (i = 0; i < wrapper->numberOfFields; i++) { wrapper->result_buffers[i].buffer_type = fields[i].type; // mysql type | C type switch(fields[i].type) { case MYSQL_TYPE_NULL: // NULL break; case MYSQL_TYPE_TINY: // signed char wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(signed char)); wrapper->result_buffers[i].buffer_length = sizeof(signed char); break; case MYSQL_TYPE_SHORT: // short int wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(short int)); wrapper->result_buffers[i].buffer_length = sizeof(short int); break; case MYSQL_TYPE_INT24: // int case MYSQL_TYPE_LONG: // int case MYSQL_TYPE_YEAR: // int wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(int)); wrapper->result_buffers[i].buffer_length = sizeof(int); break; case MYSQL_TYPE_LONGLONG: // long long int wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(long long int)); wrapper->result_buffers[i].buffer_length = sizeof(long long int); break; case MYSQL_TYPE_FLOAT: // float case MYSQL_TYPE_DOUBLE: // double wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(double)); wrapper->result_buffers[i].buffer_length = sizeof(double); break; case MYSQL_TYPE_TIME: // MYSQL_TIME case MYSQL_TYPE_DATE: // MYSQL_TIME case MYSQL_TYPE_NEWDATE: // MYSQL_TIME case MYSQL_TYPE_DATETIME: // MYSQL_TIME case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(MYSQL_TIME)); wrapper->result_buffers[i].buffer_length = sizeof(MYSQL_TIME); break; case MYSQL_TYPE_DECIMAL: // char[] case MYSQL_TYPE_NEWDECIMAL: // char[] case MYSQL_TYPE_STRING: // char[] case MYSQL_TYPE_VAR_STRING: // char[] case MYSQL_TYPE_VARCHAR: // char[] case MYSQL_TYPE_TINY_BLOB: // char[] case MYSQL_TYPE_BLOB: // char[] case MYSQL_TYPE_MEDIUM_BLOB: // char[] case MYSQL_TYPE_LONG_BLOB: // char[] case MYSQL_TYPE_BIT: // char[] case MYSQL_TYPE_SET: // char[] case MYSQL_TYPE_ENUM: // char[] case MYSQL_TYPE_GEOMETRY: // char[] default: wrapper->result_buffers[i].buffer = xmalloc(fields[i].max_length); wrapper->result_buffers[i].buffer_length = fields[i].max_length; break; } wrapper->result_buffers[i].is_null = &wrapper->is_null[i]; wrapper->result_buffers[i].length = &wrapper->length[i]; wrapper->result_buffers[i].error = &wrapper->error[i]; wrapper->result_buffers[i].is_unsigned = ((fields[i].flags & UNSIGNED_FLAG) != 0); } } static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, const result_each_args *args) { VALUE rowVal; unsigned int i = 0; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc; rb_encoding *conn_enc; #endif GET_RESULT(self); #ifdef HAVE_RUBY_ENCODING_H default_internal_enc = rb_default_internal_encoding(); conn_enc = rb_to_encoding(wrapper->encoding); #endif if (args->asArray) { rowVal = rb_ary_new2(wrapper->numberOfFields); } else { rowVal = rb_hash_new(); } if (wrapper->fields == Qnil) { wrapper->numberOfFields = mysql_num_fields(wrapper->result); wrapper->fields = rb_ary_new2(wrapper->numberOfFields); } if (wrapper->result_buffers == NULL) { rb_mysql_result_alloc_result_buffers(self, fields); } if (mysql_stmt_bind_result(wrapper->stmt_wrapper->stmt, wrapper->result_buffers)) { rb_raise_mysql2_stmt_error(wrapper->stmt_wrapper); } { switch((uintptr_t)rb_thread_call_without_gvl(nogvl_stmt_fetch, wrapper->stmt_wrapper->stmt, RUBY_UBF_IO, 0)) { case 0: /* success */ break; case 1: /* error */ rb_raise_mysql2_stmt_error(wrapper->stmt_wrapper); case MYSQL_NO_DATA: /* no more row */ return Qnil; case MYSQL_DATA_TRUNCATED: rb_raise(cMysql2Error, "IMPLBUG: caught MYSQL_DATA_TRUNCATED. should not come here as buffer_length is set to fields[i].max_length."); } } for (i = 0; i < wrapper->numberOfFields; i++) { VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys); VALUE val = Qnil; MYSQL_TIME *ts; if (wrapper->is_null[i]) { val = Qnil; } else { const MYSQL_BIND* const result_buffer = &wrapper->result_buffers[i]; switch(result_buffer->buffer_type) { case MYSQL_TYPE_TINY: // signed char if (args->castBool && fields[i].length == 1) { val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse; break; } if (result_buffer->is_unsigned) { val = UINT2NUM(*((unsigned char*)result_buffer->buffer)); } else { val = INT2NUM(*((signed char*)result_buffer->buffer)); } break; case MYSQL_TYPE_SHORT: // short int if (result_buffer->is_unsigned) { val = UINT2NUM(*((unsigned short int*)result_buffer->buffer)); } else { val = INT2NUM(*((short int*)result_buffer->buffer)); } break; case MYSQL_TYPE_INT24: // int case MYSQL_TYPE_LONG: // int case MYSQL_TYPE_YEAR: // int if (result_buffer->is_unsigned) { val = UINT2NUM(*((unsigned int*)result_buffer->buffer)); } else { val = INT2NUM(*((int*)result_buffer->buffer)); } break; case MYSQL_TYPE_LONGLONG: // long long int if (result_buffer->is_unsigned) { val = ULL2NUM(*((unsigned long long int*)result_buffer->buffer)); } else { val = LL2NUM(*((long long int*)result_buffer->buffer)); } break; case MYSQL_TYPE_FLOAT: // float val = rb_float_new((double)(*((float*)result_buffer->buffer))); break; case MYSQL_TYPE_DOUBLE: // double val = rb_float_new((double)(*((double*)result_buffer->buffer))); break; case MYSQL_TYPE_DATE: // MYSQL_TIME case MYSQL_TYPE_NEWDATE: // MYSQL_TIME ts = (MYSQL_TIME*)result_buffer->buffer; val = rb_funcall(cDate, intern_new, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day)); break; case MYSQL_TYPE_TIME: // MYSQL_TIME ts = (MYSQL_TIME*)result_buffer->buffer; val = rb_funcall(rb_cTime, args->db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), ULONG2NUM(ts->second_part)); if (!NIL_P(args->app_timezone)) { if (args->app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); } else { // utc val = rb_funcall(val, intern_utc, 0); } } break; case MYSQL_TYPE_DATETIME: // MYSQL_TIME case MYSQL_TYPE_TIMESTAMP: { // MYSQL_TIME uint64_t seconds; ts = (MYSQL_TIME*)result_buffer->buffer; seconds = (ts->year*31557600ULL) + (ts->month*2592000ULL) + (ts->day*86400ULL) + (ts->hour*3600ULL) + (ts->minute*60ULL) + ts->second; if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead VALUE offset = INT2NUM(0); if (args->db_timezone == intern_local) { offset = rb_funcall(cMysql2Client, intern_local_offset, 0); } val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), offset); if (!NIL_P(args->app_timezone)) { if (args->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 { val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), ULONG2NUM(ts->second_part)); if (!NIL_P(args->app_timezone)) { if (args->app_timezone == intern_local) { val = rb_funcall(val, intern_localtime, 0); } else { // utc val = rb_funcall(val, intern_utc, 0); } } } break; } case MYSQL_TYPE_DECIMAL: // char[] case MYSQL_TYPE_NEWDECIMAL: // char[] val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(result_buffer->buffer, *(result_buffer->length))); break; case MYSQL_TYPE_STRING: // char[] case MYSQL_TYPE_VAR_STRING: // char[] case MYSQL_TYPE_VARCHAR: // char[] case MYSQL_TYPE_TINY_BLOB: // char[] case MYSQL_TYPE_BLOB: // char[] case MYSQL_TYPE_MEDIUM_BLOB: // char[] case MYSQL_TYPE_LONG_BLOB: // char[] case MYSQL_TYPE_BIT: // char[] case MYSQL_TYPE_SET: // char[] case MYSQL_TYPE_ENUM: // char[] case MYSQL_TYPE_GEOMETRY: // char[] default: val = rb_str_new(result_buffer->buffer, *(result_buffer->length)); #ifdef HAVE_RUBY_ENCODING_H val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); #endif break; } } if (args->asArray) { rb_ary_push(rowVal, val); } else { rb_hash_aset(rowVal, field, val); } } return rowVal; } static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const result_each_args *args) { VALUE rowVal; 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 GET_RESULT(self); #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 (args->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, args->symbolizeKeys); if (row[i]) { VALUE val = Qnil; enum enum_field_types type = fields[i].type; if (!args->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 (args->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 (args->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, msec=0; char msec_char[7] = {'0','0','0','0','0','0','\0'}; tokens = sscanf(row[i], "%2u:%2u:%2u.%6s", &hour, &min, &sec, msec_char); if (tokens < 3) { val = Qnil; break; } msec = msec_char_to_uint(msec_char, sizeof(msec_char)); val = rb_funcall(rb_cTime, args->db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); if (!NIL_P(args->app_timezone)) { if (args->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; char msec_char[7] = {'0','0','0','0','0','0','\0'}; uint64_t seconds; tokens = sscanf(row[i], "%4u-%2u-%2u %2u:%2u:%2u.%6s", &year, &month, &day, &hour, &min, &sec, msec_char); 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 in field '%.*s': %s", fields[i].name_length, fields[i].name, 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 (args->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(args->app_timezone)) { if (args->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 { msec = msec_char_to_uint(msec_char, sizeof(msec_char)); val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec)); if (!NIL_P(args->app_timezone)) { if (args->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 in field '%.*s': %s", fields[i].name_length, fields[i].name, 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 (args->asArray) { rb_ary_push(rowVal, val); } else { rb_hash_aset(rowVal, field, val); } } else { if (args->asArray) { rb_ary_push(rowVal, Qnil); } else { rb_hash_aset(rowVal, field, Qnil); } } } return rowVal; } static VALUE rb_mysql_result_fetch_fields(VALUE self) { unsigned int i = 0; short int symbolizeKeys = 0; VALUE defaults; GET_RESULT(self); 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 ((my_ulonglong)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_(VALUE self, VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args), const result_each_args *args) { unsigned long i; const char *errstr; MYSQL_FIELD *fields = NULL; GET_RESULT(self); if (wrapper->is_streaming) { /* When streaming, we will only yield rows, not return them. */ if (wrapper->rows == Qnil) { wrapper->rows = rb_ary_new(); } if (!wrapper->streamingComplete) { VALUE row; fields = mysql_fetch_fields(wrapper->result); do { row = fetch_row_func(self, fields, args); if (row != Qnil) { wrapper->numberOfRows++; if (args->block_given != Qnil) { rb_yield(row); } } } while(row != Qnil); rb_mysql_result_free_result(wrapper); wrapper->streamingComplete = 1; // Check for errors, the connection might have gone out from under us // mysql_error returns an empty string if there is no error errstr = mysql_error(wrapper->client_wrapper->client); if (errstr[0]) { rb_raise(cMysql2Error, "%s", errstr); } } 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 (args->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 (args->cacheRows && i < rowsProcessed) { row = rb_ary_entry(wrapper->rows, i); } else { row = fetch_row_func(self, fields, args); if (args->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 (args->block_given != 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); } } } // FIXME return Enumerator instead? // return rb_ary_each(wrapper->rows); return wrapper->rows; } static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { result_each_args args; VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args); ID db_timezone, app_timezone, dbTz, appTz; int symbolizeKeys, asArray, castBool, cacheRows, cast; GET_RESULT(self); if (wrapper->stmt_wrapper && wrapper->stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); } 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; } symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys)); asArray = rb_hash_aref(opts, sym_as) == sym_array; castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans)); cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows)); cast = RTEST(rb_hash_aref(opts, sym_cast)); if (wrapper->is_streaming && cacheRows) { rb_warn(":cache_rows is ignored if :stream is true"); } if (wrapper->stmt_wrapper && !cacheRows && !wrapper->is_streaming) { rb_warn(":cache_rows is forced for prepared statements (if not streaming)"); } if (wrapper->stmt_wrapper && !cast) { rb_warn(":cast is forced for prepared statements"); } 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 && !wrapper->is_streaming) { wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result); if (wrapper->numberOfRows == 0) { rb_mysql_result_free_result(wrapper); wrapper->rows = rb_ary_new(); return wrapper->rows; } wrapper->rows = rb_ary_new2(wrapper->numberOfRows); } // Backward compat args.symbolizeKeys = symbolizeKeys; args.asArray = asArray; args.castBool = castBool; args.cacheRows = cacheRows; args.cast = cast; args.db_timezone = db_timezone; args.app_timezone = app_timezone; args.block_given = block; if (wrapper->stmt_wrapper) { fetch_row_func = rb_mysql_result_fetch_row_stmt; } else { fetch_row_func = rb_mysql_result_fetch_row; } return rb_mysql_result_each_(self, fetch_row_func, &args); } static VALUE rb_mysql_result_count(VALUE self) { GET_RESULT(self); if (wrapper->is_streaming) { /* This is an unsigned long per result.h */ return ULONG2NUM(wrapper->numberOfRows); } if (wrapper->resultFreed) { /* Ruby arrays have platform signed long length */ return LONG2NUM(RARRAY_LEN(wrapper->rows)); } else { /* MySQL returns an unsigned 64-bit long here */ if (wrapper->stmt_wrapper) { return ULL2NUM(mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt)); } else { return ULL2NUM(mysql_num_rows(wrapper->result)); } } } /* Mysql2::Result */ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement) { 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++; wrapper->result_buffers = NULL; wrapper->is_null = NULL; wrapper->error = NULL; wrapper->length = NULL; /* Keep a handle to the Statement to ensure it doesn't get garbage collected first */ wrapper->statement = statement; if (statement != Qnil) { wrapper->stmt_wrapper = DATA_PTR(statement); wrapper->stmt_wrapper->refcount++; } else { wrapper->stmt_wrapper = NULL; } rb_obj_call_init(obj, 0, NULL); rb_iv_set(obj, "@query_options", options); /* Options that cannot be changed in results.each(...) { |row| } * should be processed here. */ wrapper->is_streaming = (rb_hash_aref(options, sym_stream) == Qtrue ? 1 : 0); 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, "free", rb_mysql_result_free_, 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.4.3/ext/mysql2/infile.h0000644000004100000410000000007412663414022016444 0ustar www-datawww-datavoid mysql2_set_local_infile(MYSQL *mysql, void *userdata); mysql2-0.4.3/ext/mysql2/extconf.rb0000644000004100000410000002174012663414022017023 0ustar www-datawww-data# encoding: UTF-8 require 'mkmf' require 'English' def asplode(lib) if RUBY_PLATFORM =~ /mingw|mswin/ abort "-----\n#{lib} is missing. Check your installation of MySQL or Connector/C, and try again.\n-----" elsif RUBY_PLATFORM =~ /darwin/ abort "-----\n#{lib} is missing. You may need to 'brew install mysql' or 'port install mysql', and try again.\n-----" else abort "-----\n#{lib} is missing. You may need to 'apt-get install libmysqlclient-dev' or 'yum install mysql-devel', and try again.\n-----" end 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.fetch('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* /usr/local/opt/mysql5* ).map { |dir| dir << '/bin' } GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5,mariadb_config}" # If the user has provided a --with-mysql-dir argument, we must respect it or fail. inc, lib = dir_config('mysql') if inc && lib # TODO: Remove when 2.0.0 is the minimum supported version # 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(s) #{inc}\n-----" unless inc && inc.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } 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.exist?(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 abort unless $CHILD_STATUS.success? libs = `#{mc} --libs_r`.chomp # MySQL 5.5 and above already have re-entrant code in libmysqlclient (no _r). libs = `#{mc} --libs`.chomp if ver >= 5.5 || libs.empty? abort unless $CHILD_STATUS.success? $INCFLAGS += ' ' + includes $libs = libs + " " + $libs rpath_dir = libs else _, usr_local_lib = dir_config('mysql', '/usr/local') asplode("mysql client") unless find_library('mysqlclient', 'mysql_query', usr_local_lib, "#{usr_local_lib}/mysql") rpath_dir = usr_local_lib 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 header end # This is our wishlist. We use whichever flags work on the host. # -Wall and -Wextra are included by default. wishlist = [ '-Weverything', '-Wno-bad-function-cast', # rb_thread_call_without_gvl returns void * that we cast to VALUE '-Wno-conditional-uninitialized', # false positive in client.c '-Wno-covered-switch-default', # result.c -- enum_field_types (when fully covered, e.g. mysql 5.5) '-Wno-declaration-after-statement', # GET_CLIENT followed by GET_STATEMENT in statement.c '-Wno-disabled-macro-expansion', # rubby :( '-Wno-documentation-unknown-command', # rubby :( '-Wno-missing-field-initializers', # gperf generates bad code '-Wno-missing-variable-declarations', # missing symbols due to ruby native ext initialization '-Wno-padded', # mysql :( '-Wno-reserved-id-macro', # rubby :( '-Wno-sign-conversion', # gperf generates bad code '-Wno-static-in-inline', # gperf generates bad code '-Wno-switch-enum', # result.c -- enum_field_types (when not fully covered, e.g. mysql 5.6+) '-Wno-undef', # rubinius :( '-Wno-unreachable-code', # rubby :( '-Wno-used-but-marked-unused', # rubby :( ] usable_flags = wishlist.select do |flag| try_link('int main() {return 0;}', "-Werror #{flag}") end $CFLAGS << ' ' << usable_flags.join(' ') enabled_sanitizers = disabled_sanitizers = [] # Specify a commna-separated list of sanitizers, or try them all by default sanitizers = with_config('sanitize') case sanitizers when true # Try them all, turn on whatever we can enabled_sanitizers = %w(address cfi integer memory thread undefined).select do |s| try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}") end abort "-----\nCould not enable any sanitizers!\n-----" if enabled_sanitizers.empty? when String # Figure out which sanitizers are supported enabled_sanitizers, disabled_sanitizers = sanitizers.split(',').partition do |s| try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}") end end unless disabled_sanitizers.empty? abort "-----\nCould not enable requested sanitizers: #{disabled_sanitizers.join(',')}\n-----" end unless enabled_sanitizers.empty? warn "-----\nEnabling sanitizers: #{enabled_sanitizers.join(',')}\n-----" enabled_sanitizers.each do |s| # address sanitizer requires runtime support if s == 'address' # rubocop:disable Style/IfUnlessModifier have_library('asan') || $LDFLAGS << ' -fsanitize=address' end $CFLAGS << " -fsanitize=#{s}" end # Options for line numbers in backtraces $CFLAGS << ' -g -fno-omit-frame-pointer' end if RUBY_PLATFORM =~ /mswin|mingw/ # Build libmysql.a interface link library require 'rake' # Build libmysql.a interface link library # Use rake to rebuild only if these files change deffile = File.expand_path('../../../support/libmysql.def', __FILE__) libfile = File.expand_path(File.join(rpath_dir, 'libmysql.lib')) file 'libmysql.a' => [deffile, libfile] do when_writing 'building libmysql.a' do # Ruby kindly shows us where dllwrap is, but that tool does more than we want. # Maybe in the future Ruby could provide RbConfig::CONFIG['DLLTOOL'] directly. dlltool = RbConfig::CONFIG['DLLWRAP'].gsub('dllwrap', 'dlltool') sh dlltool, '--kill-at', '--dllname', 'libmysql.dll', '--output-lib', 'libmysql.a', '--input-def', deffile, libfile end end Rake::Task['libmysql.a'].invoke $LOCAL_LIBS << ' ' << 'libmysql.a' # Make sure the generated interface library works (if cross-compiling, trust without verifying) unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ abort "-----\nCannot find libmysql.a\n-----" unless have_library('libmysql') abort "-----\nCannot link to libmysql.a (my_init)\n-----" unless have_func('my_init') end # Vendor libmysql.dll vendordir = File.expand_path('../../../vendor/', __FILE__) directory vendordir vendordll = File.join(vendordir, 'libmysql.dll') dllfile = File.expand_path(File.join(rpath_dir, 'libmysql.dll')) file vendordll => [dllfile, vendordir] do when_writing 'copying libmysql.dll' do cp dllfile, vendordll end end # Copy libmysql.dll to the local vendor directory by default if arg_config('--no-vendor-libmysql') # Fine, don't. puts "--no-vendor-libmysql" else # Default: arg_config('--vendor-libmysql') # Let's do it! Rake::Task[vendordll].invoke end else 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 end create_makefile('mysql2/mysql2') mysql2-0.4.3/ext/mysql2/statement.c0000644000004100000410000003653512663414022017210 0ustar www-datawww-data#include VALUE cMysql2Statement; extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate; static VALUE sym_stream, intern_new_with_args, intern_each; static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year, intern_to_s; #define GET_STATEMENT(self) \ mysql_stmt_wrapper *stmt_wrapper; \ Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); \ if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \ if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); } static void rb_mysql_stmt_mark(void * ptr) { mysql_stmt_wrapper *stmt_wrapper = ptr; if (!stmt_wrapper) return; rb_gc_mark(stmt_wrapper->client); } static void *nogvl_stmt_close(void * ptr) { mysql_stmt_wrapper *stmt_wrapper = ptr; if (stmt_wrapper->stmt) { mysql_stmt_close(stmt_wrapper->stmt); stmt_wrapper->stmt = NULL; } return NULL; } static void rb_mysql_stmt_free(void * ptr) { mysql_stmt_wrapper *stmt_wrapper = ptr; decr_mysql2_stmt(stmt_wrapper); } void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) { stmt_wrapper->refcount--; if (stmt_wrapper->refcount == 0) { nogvl_stmt_close(stmt_wrapper); xfree(stmt_wrapper); } } void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) { VALUE e; GET_CLIENT(stmt_wrapper->client); VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt)); VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt)); #ifdef HAVE_RUBY_ENCODING_H rb_encoding *conn_enc; conn_enc = rb_to_encoding(wrapper->encoding); rb_encoding *default_internal_enc = rb_default_internal_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_funcall(cMysql2Error, intern_new_with_args, 4, rb_error_msg, LONG2FIX(wrapper->server_version), UINT2NUM(mysql_stmt_errno(stmt_wrapper->stmt)), rb_sql_state); rb_exc_raise(e); } /* * used to pass all arguments to mysql_stmt_prepare while inside * nogvl_prepare_statement_args */ struct nogvl_prepare_statement_args { MYSQL_STMT *stmt; VALUE sql; const char *sql_ptr; unsigned long sql_len; }; static void *nogvl_prepare_statement(void *ptr) { struct nogvl_prepare_statement_args *args = ptr; if (mysql_stmt_prepare(args->stmt, args->sql_ptr, args->sql_len)) { return (void*)Qfalse; } else { return (void*)Qtrue; } } VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { mysql_stmt_wrapper *stmt_wrapper; VALUE rb_stmt; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *conn_enc; #endif Check_Type(sql, T_STRING); rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper); { stmt_wrapper->client = rb_client; stmt_wrapper->refcount = 1; stmt_wrapper->closed = 0; stmt_wrapper->stmt = NULL; } // instantiate stmt { GET_CLIENT(rb_client); stmt_wrapper->stmt = mysql_stmt_init(wrapper->client); #ifdef HAVE_RUBY_ENCODING_H conn_enc = rb_to_encoding(wrapper->encoding); #endif } if (stmt_wrapper->stmt == NULL) { rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory"); } // set STMT_ATTR_UPDATE_MAX_LENGTH attr { my_bool truth = 1; if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) { rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH"); } } // call mysql_stmt_prepare w/o gvl { struct nogvl_prepare_statement_args args; args.stmt = stmt_wrapper->stmt; args.sql = sql; #ifdef HAVE_RUBY_ENCODING_H // 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 = RSTRING_PTR(sql); args.sql_len = RSTRING_LEN(sql); if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) { rb_raise_mysql2_stmt_error(stmt_wrapper); } } return rb_stmt; } /* call-seq: stmt.param_count # => Numeric * * Returns the number of parameters the prepared statement expects. */ static VALUE param_count(VALUE self) { GET_STATEMENT(self); return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt)); } /* call-seq: stmt.field_count # => Numeric * * Returns the number of fields the prepared statement returns. */ static VALUE field_count(VALUE self) { GET_STATEMENT(self); return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt)); } static void *nogvl_execute(void *ptr) { MYSQL_STMT *stmt = ptr; if (mysql_stmt_execute(stmt)) { return (void*)Qfalse; } else { return (void*)Qtrue; } } static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) { unsigned long length; bind_buffer->buffer_type = MYSQL_TYPE_STRING; bind_buffer->buffer = RSTRING_PTR(string); length = RSTRING_LEN(string); bind_buffer->buffer_length = length; *length_buffer = length; bind_buffer->length = length_buffer; } /* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means * the buffer is a Ruby string pointer and not our memory to manage. */ #define FREE_BINDS \ for (i = 0; i < argc; i++) { \ if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \ xfree(bind_buffers[i].buffer); \ } \ } \ if (argc > 0) { \ xfree(bind_buffers); \ xfree(length_buffers); \ } /* call-seq: stmt.execute * * Executes the current prepared statement, returns +result+. */ static VALUE execute(int argc, VALUE *argv, VALUE self) { MYSQL_BIND *bind_buffers = NULL; unsigned long *length_buffers = NULL; unsigned long bind_count; long i; MYSQL_STMT *stmt; MYSQL_RES *metadata; VALUE current; VALUE resultObj; VALUE *params_enc; int is_streaming; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *conn_enc; #endif GET_STATEMENT(self); GET_CLIENT(stmt_wrapper->client); #ifdef HAVE_RUBY_ENCODING_H conn_enc = rb_to_encoding(wrapper->encoding); #endif /* Scratch space for string encoding exports, allocate on the stack. */ params_enc = alloca(sizeof(VALUE) * argc); stmt = stmt_wrapper->stmt; bind_count = mysql_stmt_param_count(stmt); if (argc != (long)bind_count) { rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, argc); } // setup any bind variables in the query if (bind_count > 0) { bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND)); length_buffers = xcalloc(bind_count, sizeof(unsigned long)); for (i = 0; i < argc; i++) { bind_buffers[i].buffer = NULL; params_enc[i] = Qnil; switch (TYPE(argv[i])) { case T_NIL: bind_buffers[i].buffer_type = MYSQL_TYPE_NULL; break; case T_FIXNUM: #if SIZEOF_INT < SIZEOF_LONG bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; bind_buffers[i].buffer = xmalloc(sizeof(long long int)); *(long*)(bind_buffers[i].buffer) = FIX2LONG(argv[i]); #else bind_buffers[i].buffer_type = MYSQL_TYPE_LONG; bind_buffers[i].buffer = xmalloc(sizeof(int)); *(long*)(bind_buffers[i].buffer) = FIX2INT(argv[i]); #endif break; case T_BIGNUM: bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG; bind_buffers[i].buffer = xmalloc(sizeof(long long int)); *(LONG_LONG*)(bind_buffers[i].buffer) = rb_big2ll(argv[i]); break; case T_FLOAT: bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE; bind_buffers[i].buffer = xmalloc(sizeof(double)); *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]); break; case T_STRING: { params_enc[i] = argv[i]; #ifdef HAVE_RUBY_ENCODING_H params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); #endif set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); } break; default: // TODO: what Ruby type should support MYSQL_TYPE_TIME if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) { MYSQL_TIME t; VALUE rb_time = argv[i]; bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME; bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); memset(&t, 0, sizeof(MYSQL_TIME)); t.neg = 0; t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0)); t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0)); t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0)); t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0)); t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0)); t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0)); t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0)); *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; } else if (CLASS_OF(argv[i]) == cDate) { MYSQL_TIME t; VALUE rb_time = argv[i]; bind_buffers[i].buffer_type = MYSQL_TYPE_DATE; bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME)); memset(&t, 0, sizeof(MYSQL_TIME)); t.second_part = 0; t.neg = 0; t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0)); t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0)); t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0)); *(MYSQL_TIME*)(bind_buffers[i].buffer) = t; } else if (CLASS_OF(argv[i]) == cBigDecimal) { bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL; // DECIMAL are represented with the "string representation of the // original server-side value", see // https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-conversions.html // This should be independent of the locale used both on the server // and the client side. VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0); params_enc[i] = rb_val_as_string; #ifdef HAVE_RUBY_ENCODING_H params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc); #endif set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]); } break; } } // copies bind_buffers into internal storage if (mysql_stmt_bind_param(stmt, bind_buffers)) { FREE_BINDS; rb_raise_mysql2_stmt_error(stmt_wrapper); } } if ((VALUE)rb_thread_call_without_gvl(nogvl_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) { FREE_BINDS; rb_raise_mysql2_stmt_error(stmt_wrapper); } FREE_BINDS; metadata = mysql_stmt_result_metadata(stmt); if (metadata == NULL) { if (mysql_stmt_errno(stmt) != 0) { // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal. wrapper->active_thread = Qnil; rb_raise_mysql2_stmt_error(stmt_wrapper); } // no data and no error, so query was not a SELECT return Qnil; } current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options")); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); is_streaming = (Qtrue == rb_hash_aref(current, sym_stream)); if (!is_streaming) { // recieve the whole result set from the server if (mysql_stmt_store_result(stmt)) { mysql_free_result(metadata); rb_raise_mysql2_stmt_error(stmt_wrapper); } wrapper->active_thread = Qnil; } resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self); if (!is_streaming) { // cache all result rb_funcall(resultObj, intern_each, 0); } return resultObj; } /* call-seq: stmt.fields # => array * * Returns a list of fields that will be returned by this statement. */ static VALUE fields(VALUE self) { MYSQL_FIELD *fields; MYSQL_RES *metadata; unsigned int field_count; unsigned int i; VALUE field_list; MYSQL_STMT* stmt; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc, *conn_enc; #endif GET_STATEMENT(self); stmt = stmt_wrapper->stmt; #ifdef HAVE_RUBY_ENCODING_H default_internal_enc = rb_default_internal_encoding(); { GET_CLIENT(stmt_wrapper->client); conn_enc = rb_to_encoding(wrapper->encoding); } #endif metadata = mysql_stmt_result_metadata(stmt); fields = mysql_fetch_fields(metadata); field_count = mysql_stmt_field_count(stmt); field_list = rb_ary_new2((long)field_count); for(i = 0; i < field_count; i++) { VALUE rb_field; rb_field = rb_str_new(fields[i].name, fields[i].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(field_list, (long)i, rb_field); } mysql_free_result(metadata); return field_list; } /* call-seq: * stmt.last_id * * Returns the AUTO_INCREMENT value from the executed INSERT or UPDATE. */ static VALUE rb_mysql_stmt_last_id(VALUE self) { GET_STATEMENT(self); return ULL2NUM(mysql_stmt_insert_id(stmt_wrapper->stmt)); } /* call-seq: * stmt.affected_rows * * Returns the number of rows changed, deleted, or inserted. */ static VALUE rb_mysql_stmt_affected_rows(VALUE self) { my_ulonglong affected; GET_STATEMENT(self); affected = mysql_stmt_affected_rows(stmt_wrapper->stmt); if (affected == (my_ulonglong)-1) { rb_raise_mysql2_stmt_error(stmt_wrapper); } return ULL2NUM(affected); } /* call-seq: * stmt.close * * Explicitly closing this will free up server resources immediately rather * than waiting for the garbage collector. Useful if you're managing your * own prepared statement cache. */ static VALUE rb_mysql_stmt_close(VALUE self) { GET_STATEMENT(self); stmt_wrapper->closed = 1; rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0); return Qnil; } void init_mysql2_statement() { cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); rb_define_method(cMysql2Statement, "param_count", param_count, 0); rb_define_method(cMysql2Statement, "field_count", field_count, 0); rb_define_method(cMysql2Statement, "_execute", execute, -1); rb_define_method(cMysql2Statement, "fields", fields, 0); rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0); rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0); rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0); sym_stream = ID2SYM(rb_intern("stream")); intern_new_with_args = rb_intern("new_with_args"); intern_each = rb_intern("each"); intern_usec = rb_intern("usec"); intern_sec = rb_intern("sec"); intern_min = rb_intern("min"); intern_hour = rb_intern("hour"); intern_day = rb_intern("day"); intern_month = rb_intern("month"); intern_year = rb_intern("year"); intern_to_s = rb_intern("to_s"); } mysql2-0.4.3/ext/mysql2/result.h0000644000004100000410000000130512663414022016512 0ustar www-datawww-data#ifndef MYSQL2_RESULT_H #define MYSQL2_RESULT_H void init_mysql2_result(void); VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement); typedef struct { VALUE fields; VALUE rows; VALUE client; VALUE encoding; VALUE statement; my_ulonglong numberOfFields; my_ulonglong numberOfRows; unsigned long lastRowProcessed; char is_streaming; char streamingComplete; char resultFreed; MYSQL_RES *result; mysql_stmt_wrapper *stmt_wrapper; mysql_client_wrapper *client_wrapper; /* statement result bind buffers */ MYSQL_BIND *result_buffers; my_bool *is_null; my_bool *error; unsigned long *length; } mysql2_result_wrapper; #endif mysql2-0.4.3/ext/mysql2/mysql2_ext.c0000644000004100000410000000044412663414022017301 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(); init_mysql2_statement(); } mysql2-0.4.3/ext/mysql2/mysql2_ext.h0000644000004100000410000000164512663414022017312 0ustar www-datawww-data#ifndef MYSQL2_EXT #define MYSQL2_EXT void Init_mysql2(void); /* 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 #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_NORETURN __attribute__ ((noreturn)) #define RB_MYSQL_UNUSED __attribute__ ((unused)) #else #define RB_MYSQL_NORETURN #define RB_MYSQL_UNUSED #endif #include #include #include #include #endif mysql2-0.4.3/ext/mysql2/wait_for_single_fd.h0000644000004100000410000000154512663414022021026 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.4.3/ext/mysql2/infile.c0000644000004100000410000000607012663414022016441 0ustar www-datawww-data#include #include #ifndef _MSC_VER #include #endif #include #define ERROR_LEN 1024 typedef struct { int fd; char *filename; char error[ERROR_LEN]; mysql_client_wrapper *wrapper; } mysql2_local_infile_data; /* MySQL calls this function when a user begins a LOAD DATA LOCAL INFILE query. * * Allocate a data struct and pass it back through the data pointer. * * Returns: * 0 on success * 1 on error */ static int mysql2_local_infile_init(void **ptr, const char *filename, void *userdata) { mysql2_local_infile_data *data = malloc(sizeof(mysql2_local_infile_data)); if (!data) return 1; *ptr = data; data->error[0] = 0; data->wrapper = userdata; data->filename = strdup(filename); if (!data->filename) { snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), filename); return 1; } data->fd = open(filename, O_RDONLY); if (data->fd < 0) { snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), filename); return 1; } return 0; } /* MySQL calls this function to read data from the local file. * * Returns: * > 0 number of bytes read * == 0 end of file * < 0 error */ static int mysql2_local_infile_read(void *ptr, char *buf, unsigned int buf_len) { int count; mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr; count = (int)read(data->fd, buf, buf_len); if (count < 0) { snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), data->filename); } return count; } /* MySQL calls this function when we're done with the LOCAL INFILE query. * * ptr will be null if the init function failed. */ static void mysql2_local_infile_end(void *ptr) { mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr; if (data) { if (data->fd >= 0) close(data->fd); if (data->filename) free(data->filename); free(data); } } /* MySQL calls this function if any of the functions above returned an error. * * This function is called even if init failed, with whatever ptr value * init has set, regardless of the return value of the init function. * * Returns: * Error message number (see http://dev.mysql.com/doc/refman/5.0/en/error-messages-client.html) */ static int mysql2_local_infile_error(void *ptr, char *error_msg, unsigned int error_msg_len) { mysql2_local_infile_data *data = (mysql2_local_infile_data *) ptr; if (data) { snprintf(error_msg, error_msg_len, "%s", data->error); return CR_UNKNOWN_ERROR; } snprintf(error_msg, error_msg_len, "Out of memory"); return CR_OUT_OF_MEMORY; } /* Tell MySQL Client to use our own local_infile functions. * This is both due to bugginess in the default handlers, * and to improve the Rubyness of the handlers here. */ void mysql2_set_local_infile(MYSQL *mysql, void *userdata) { mysql_set_local_infile_handler(mysql, mysql2_local_infile_init, mysql2_local_infile_read, mysql2_local_infile_end, mysql2_local_infile_error, userdata); } mysql2-0.4.3/ext/mysql2/client.c0000644000004100000410000012244612663414022016457 0ustar www-datawww-data#include #include #include #ifndef _WIN32 #include #include #endif #ifndef _MSC_VER #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_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream; static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args; #ifndef HAVE_RB_HASH_DUP VALUE rb_hash_dup(VALUE other) { return rb_funcall(rb_cHash, intern_brackets, 1, other); } #endif #define REQUIRE_INITIALIZED(wrapper) \ if (!wrapper->initialized) { \ rb_raise(cMysql2Error, "MySQL client is not initialized"); \ } #define REQUIRE_NOT_CONNECTED(wrapper) \ REQUIRE_INITIALIZED(wrapper) \ if (wrapper->connected) { \ rb_raise(cMysql2Error, "MySQL connection is already open"); \ } /* * 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_enc_associate(rb_error_msg, rb_utf8_encoding()); rb_enc_associate(rb_sql_state, rb_usascii_encoding()); #endif e = rb_funcall(cMysql2Error, intern_new_with_args, 4, rb_error_msg, LONG2FIX(wrapper->server_version), UINT2NUM(mysql_errno(wrapper->client)), rb_sql_state); rb_exc_raise(e); } static void *nogvl_init(void *ptr) { MYSQL *client; mysql_client_wrapper *wrapper = ptr; /* may initialize embedded server and read /etc/services off disk */ client = mysql_init(wrapper->client); if (client) mysql2_set_local_infile(client, wrapper); 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); } #ifndef _WIN32 /* * Redirect clientfd to /dev/null for mysql_close and SSL_close to write, * shutdown, and close. The hack is needed to prevent shutdown() from breaking * a socket that may be in use by the parent or other processes after fork. * * /dev/null is used to absorb writes; previously a dummy socket was used, but * it could not abosrb writes and caused openssl to go into an infinite loop. * * Returns Qtrue or Qfalse (success or failure) * * Note: if this function is needed on Windows, use "nul" instead of "/dev/null" */ static VALUE invalidate_fd(int clientfd) { #ifdef O_CLOEXEC /* Atomically set CLOEXEC on the new FD in case another thread forks */ int sockfd = open("/dev/null", O_RDWR | O_CLOEXEC); #else /* Well we don't have O_CLOEXEC, trigger the fallback code below */ int sockfd = -1; #endif if (sockfd < 0) { /* Either O_CLOEXEC wasn't defined at compile time, or it was defined at * compile time, but isn't available at run-time. So we'll just be quick * about setting FD_CLOEXEC now. */ int flags; sockfd = open("/dev/null", O_RDWR); flags = fcntl(sockfd, F_GETFD); /* Do the flags dance in case there are more defined flags in the future */ if (flags != -1) { flags |= FD_CLOEXEC; fcntl(sockfd, F_SETFD, flags); } } if (sockfd < 0) { /* Cannot raise here, because one or both of the following may be true: * a) we have no GVL (in C Ruby) * b) are running as a GC finalizer */ return Qfalse; } dup2(sockfd, clientfd); close(sockfd); return Qtrue; } #endif /* _WIN32 */ static void *nogvl_close(void *ptr) { mysql_client_wrapper *wrapper = ptr; if (wrapper->client) { mysql_close(wrapper->client); xfree(wrapper->client); wrapper->client = NULL; wrapper->connected = 0; wrapper->active_thread = Qnil; } return NULL; } /* this is called during GC */ static void rb_mysql_client_free(void *ptr) { mysql_client_wrapper *wrapper = ptr; decr_mysql2_client(wrapper); } void decr_mysql2_client(mysql_client_wrapper *wrapper) { wrapper->refcount--; if (wrapper->refcount == 0) { #ifndef _WIN32 if (wrapper->connected && !wrapper->automatic_close) { /* The client is being garbage collected while connected. Prevent * mysql_close() from sending a mysql-QUIT or from calling shutdown() on * the socket by invalidating it. invalidate_fd() will drop this * process's reference to the socket only, while a QUIT or shutdown() * would render the underlying connection unusable, interrupting other * processes which share this object across a fork(). */ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n"); close(wrapper->client->net.fd); } } #endif nogvl_close(wrapper); 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->automatic_close = 1; wrapper->server_version = 0; wrapper->reconnect_enabled = 0; wrapper->connect_timeout = 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, RSTRING_PTR(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_mysql_get_ssl_cipher(VALUE self) { const char *cipher; VALUE rb_str; GET_CLIENT(self); cipher = mysql_get_ssl_cipher(wrapper->client); if (cipher == NULL) { return Qnil; } rb_str = rb_str_new2(cipher); #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; time_t start_time, end_time, elapsed_time, connect_timeout; VALUE rv; GET_CLIENT(self); args.host = NIL_P(host) ? NULL : StringValueCStr(host); args.unix_socket = NIL_P(socket) ? NULL : StringValueCStr(socket); args.port = NIL_P(port) ? 0 : NUM2INT(port); args.user = NIL_P(user) ? NULL : StringValueCStr(user); args.passwd = NIL_P(pass) ? NULL : StringValueCStr(pass); args.db = NIL_P(database) ? NULL : StringValueCStr(database); args.mysql = wrapper->client; args.client_flag = NUM2ULONG(flags); if (wrapper->connect_timeout) time(&start_time); rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); if (rv == Qfalse) { while (rv == Qfalse && errno == EINTR) { if (wrapper->connect_timeout) { time(&end_time); /* avoid long connect timeout from system time changes */ if (end_time < start_time) start_time = end_time; elapsed_time = end_time - start_time; /* avoid an early timeout due to time truncating milliseconds off the start time */ if (elapsed_time > 0) elapsed_time--; if (elapsed_time >= (time_t)wrapper->connect_timeout) break; connect_timeout = wrapper->connect_timeout - elapsed_time; mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout); } errno = 0; rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0); } /* restore the connect timeout for reconnecting */ if (wrapper->connect_timeout) mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout); if (rv == Qfalse) rb_raise_mysql2_error(wrapper); } wrapper->server_version = mysql_get_server_version(wrapper->client); 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. * * @return [nil] */ 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 */ wrapper->active_thread = Qnil; 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 = ptr; MYSQL_RES *result; 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 */ wrapper->active_thread = Qnil; 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) { wrapper->active_thread = Qnil; 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")); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil); 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; /* Invalidate the MySQL socket to prevent further communication. * The GC will come along later and call mysql_close to free it. */ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n"); close(wrapper->client->net.fd); } rb_exc_raise(error); } static VALUE do_query(void *args) { struct async_query_args *async_args = args; struct timeval tv; struct timeval *tvp; long int sec; int retval; VALUE read_timeout; 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 = args; MYSQL_RES *result; 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 void rb_mysql_client_set_active_thread(VALUE self) { VALUE thread_current = rb_thread_current(); GET_CLIENT(self); // 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); } } /* 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_query(VALUE self, VALUE sql, VALUE current) { #ifndef _WIN32 struct async_query_args async_args; #endif struct nogvl_send_query_args args; GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); args.mysql = wrapper->client; (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); rb_iv_set(self, "@current_query_options", current); Check_Type(sql, T_STRING); #ifdef HAVE_RUBY_ENCODING_H /* ensure the string is in the encoding the connection is expecting */ args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding)); #else args.sql = sql; #endif args.sql_ptr = RSTRING_PTR(args.sql); args.sql_len = RSTRING_LEN(args.sql); args.wrapper = wrapper; rb_mysql_client_set_active_thread(self); #ifndef _WIN32 rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0); if (rb_hash_aref(current, sym_async) == Qtrue) { return Qnil; } else { 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 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, RSTRING_PTR(str), oldLen); if (newLen == oldLen) { /* no need to return a new ruby string if nothing changed */ #ifdef HAVE_RUBY_ENCODING_H if (default_internal_enc) { str = rb_str_export_to_enc(str, default_internal_enc); } #endif 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 = NUM2UINT(value); retval = &intval; break; case MYSQL_OPT_READ_TIMEOUT: intval = NUM2UINT(value); retval = &intval; break; case MYSQL_OPT_WRITE_TIMEOUT: intval = NUM2UINT(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 *)StringValueCStr(value); retval = charval; break; case MYSQL_READ_DEFAULT_GROUP: charval = (const char *)StringValueCStr(value); retval = charval; break; case MYSQL_INIT_COMMAND: charval = (const char *)StringValueCStr(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 options that are stored in the wrapper struct */ switch (opt) { case MYSQL_OPT_RECONNECT: wrapper->reconnect_enabled = boolval; break; case MYSQL_OPT_CONNECT_TIMEOUT: wrapper->connect_timeout = intval; break; } } return (result == 0) ? Qtrue : Qfalse; } /* call-seq: * client.info * * Returns a string that represents the client library version. */ static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) { VALUE version_info, version, header_version; version_info = rb_hash_new(); version = rb_str_new2(mysql_get_client_info()); header_version = rb_str_new2(MYSQL_LINK_VERSION); #ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(version, rb_usascii_encoding()); rb_enc_associate(header_version, rb_usascii_encoding()); #endif rb_hash_aset(version_info, sym_id, LONG2NUM(mysql_get_client_version())); rb_hash_aset(version_info, sym_version, version); rb_hash_aset(version_info, sym_header_version, header_version); return version_info; } /* 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. */ #ifndef _WIN32 static VALUE rb_mysql_client_socket(VALUE self) { GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); return INT2NUM(wrapper->client->net.fd); } #else static VALUE rb_mysql_client_socket(RB_MYSQL_UNUSED VALUE self) { 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 = StringValueCStr(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")); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil); 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.automatic_close? * * @return [Boolean] */ static VALUE get_automatic_close(VALUE self) { GET_CLIENT(self); return wrapper->automatic_close ? Qtrue : Qfalse; } /* call-seq: * client.automatic_close = false * * Set this to +false+ to leave the connection open after it is garbage * collected. To avoid "Aborted connection" errors on the server, explicitly * call +close+ when the connection is no longer needed. * * @see http://dev.mysql.com/doc/en/communication-errors.html */ static VALUE set_automatic_close(VALUE self, VALUE value) { GET_CLIENT(self); if (RTEST(value)) { wrapper->automatic_close = 1; } else { #ifndef _WIN32 wrapper->automatic_close = 0; #else rb_warn("Connections are always closed by garbage collector on Windows"); #endif } return value; } /* 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 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 mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value)); 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 : StringValueCStr(key), NIL_P(cert) ? NULL : StringValueCStr(cert), NIL_P(ca) ? NULL : StringValueCStr(ca), NIL_P(capath) ? NULL : StringValueCStr(capath), NIL_P(cipher) ? NULL : StringValueCStr(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 set_init_command(VALUE self, VALUE value) { return _mysql_client_options(self, MYSQL_INIT_COMMAND, value); } static VALUE initialize_ext(VALUE self) { GET_CLIENT(self); if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) { /* TODO: warning - not enough memory? */ rb_raise_mysql2_error(wrapper); } wrapper->initialized = 1; return self; } /* call-seq: client.prepare # => Mysql2::Statement * * Create a new prepared statement. */ static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) { GET_CLIENT(self); REQUIRE_CONNECTED(wrapper); return rb_mysql_stmt_new(self, sql); } void init_mysql2_client() { #ifdef _WIN32 /* 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); } } #endif /* Initializing mysql library, so different threads could call Client.new */ /* without race condition in the library */ if (mysql_library_init(0, NULL, NULL) != 0) { rb_raise(rb_eRuntimeError, "Could not initialize MySQL client library"); } #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_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0); rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0); 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, "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, "prepare", rb_mysql_client_prepare_statement, 1); 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, "automatic_close?", get_automatic_close, 0); rb_define_method(cMysql2Client, "automatic_close=", set_automatic_close, 1); 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); rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 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, "init_command=", set_init_command, 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); rb_define_private_method(cMysql2Client, "_query", rb_query, 2); sym_id = ID2SYM(rb_intern("id")); sym_version = ID2SYM(rb_intern("version")); sym_header_version = ID2SYM(rb_intern("header_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_brackets = rb_intern("[]"); intern_merge = rb_intern("merge"); intern_merge_bang = rb_intern("merge!"); intern_new_with_args = rb_intern("new_with_args"); #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)); #else /* HACK because MySQL5.7 no longer defines this constant, * but we're using it in our default connection flags. */ rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(0)); #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.4.3/ext/mysql2/client.h0000644000004100000410000000345712663414022016464 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 */ typedef struct { VALUE encoding; VALUE active_thread; /* rb_thread_current() or Qnil */ long server_version; int reconnect_enabled; unsigned int connect_timeout; int active; int automatic_close; int connected; int initialized; int refcount; int freed; MYSQL *client; } mysql_client_wrapper; #define REQUIRE_CONNECTED(wrapper) \ REQUIRE_INITIALIZED(wrapper) \ if (!wrapper->connected && !wrapper->reconnect_enabled) { \ rb_raise(cMysql2Error, "closed MySQL connection"); \ } void rb_mysql_client_set_active_thread(VALUE self); #define GET_CLIENT(self) \ mysql_client_wrapper *wrapper; \ Data_Get_Struct(self, mysql_client_wrapper, wrapper); void init_mysql2_client(void); void decr_mysql2_client(mysql_client_wrapper *wrapper); #endif #ifndef HAVE_RB_HASH_DUP VALUE rb_hash_dup(VALUE other); #endif mysql2-0.4.3/ext/mysql2/mysql_enc_to_ruby.h0000644000004100000410000000576712663414022020751 0ustar www-datawww-datastatic const 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", "", "Windows-1256", "Windows-1257", "Windows-1257", "UTF-32", "UTF-32", "", "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", "UTF-16", "UTF-16", "UTF-16", "UTF-16", 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", "UTF-16BE", "UTF-16BE", "UTF-16BE", "UTF-16BE", NULL, NULL, NULL, NULL, NULL, NULL, NULL, "UTF-16BE", "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", "UTF-32", "UTF-32", "UTF-32", "UTF-32", 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", "UTF-8", "UTF-8", "UTF-8", "UTF-8", 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", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8" }; mysql2-0.4.3/README.md0000644000004100000410000005422712663414022014266 0ustar www-datawww-data# Mysql2 - A modern, simple and very fast MySQL library for Ruby - binding to libmysql Travis CI [![Travis CI Status](https://travis-ci.org/brianmario/mysql2.png)](https://travis-ci.org/brianmario/mysql2) Appveyor CI [![Appveyor CI Status](https://ci.appveyor.com/api/projects/status/github/sodabrew/mysql2)](https://ci.appveyor.com/project/sodabrew/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 APIs 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 three classes: `Mysql2::Client` - your connection to the database. `Mysql2::Result` - returned from issuing a #query on the connection. It includes Enumerable. `Mysql2::Statement` - returned from issuing a #prepare on the connection. Execute the statement to get a Result. ## Installing ### General Instructions ``` sh gem install mysql2 ``` This gem links against MySQL's `libmysqlclient` library or `Connector/C` library, and compatible alternatives such as MariaDB. You may need to install a package such as `libmysqlclient-dev`, `mysql-devel`, or other appropriate package for your system. See below for system-specific instructions. 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 `mysql_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. * `--with-sanitize[=address,cfi,integer,memory,thread,undefined]` - Enable sanitizers for Clang / GCC. If no argument is given, try to enable all sanitizers or fail if none are available. If a command-separated list of specific sanitizers is given, configure will fail unless they all are available. Note that the some sanitizers may incur a performance penalty, and the Address Sanitizer may require a runtime library. To see line numbers in backtraces, declare these environment variables (adjust the llvm-symbolizer path as needed for your system): ``` sh export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-3.4 export ASAN_OPTIONS=symbolize=1 ``` ### Linux and other Unixes You may need to install a package such as `libmysqlclient-dev` or `mysql-devel`; refer to your distribution's package guide to find the particular package. The most common issue we see is a user who has the library file `libmysqlclient.so` but is missing the header file `mysql.h` -- double check that you have the _-dev_ packages installed. ### Mac OS X You may use MacPorts, Homebrew, or a native MySQL installer package. The most common paths will be automatically searched. If you want to select a specific MySQL directory, use the `--with-mysql-dir` or `--with-mysql-config` options above. ### Windows Make sure that you have Ruby and the DevKit compilers installed. We recommend the [Ruby Installer](http://rubyinstaller.org) distribution. By default, the mysql2 gem will download and use MySQL Connector/C from mysql.com. If you prefer to use a local installation of Connector/C, add the flag `--with-mysql-dir=c:/mysql-connector-c-x-y-z` (_this path may use forward slashes_). By default, the `libmysql.dll` library will be copied into the mysql2 gem directory. To prevent this, add the flag `--no-vendor-libmysql`. The mysql2 gem will search for `libmysql.dll` in the following paths, in order: * Environment variable `RUBY_MYSQL2_LIBMYSQL_DLL=C:\path\to\libmysql.dll` (_note the Windows-style backslashes_). * In the mysql2 gem's own directory `vendor/libmysql.dll` * In the system's default library search paths. ## 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 puts row["id"] # row["id"].class == Fixnum if row["dne"] # non-existant hash entry is nil puts row["dne"] end 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 client.query("SELECT * FROM users WHERE group='githubbers'", :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 ``` Prepared statements are supported, as well. In a prepared statement, use a `?` in place of each value and then execute the statement to retrieve a result set. Pass your arguments to the execute method in the same number and order as the question marks in the statement. ``` ruby statement = @client.prepare("SELECT * FROM users WHERE login_count = ?") result1 = statement.execute(1) result2 = statement.execute(2) statement = @client.prepare("SELECT * FROM users WHERE last_login >= ? AND location LIKE ?") result = statement.execute(1, "CA") ``` ## 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', :init_command => sql ) ``` ### SSL options Setting any of the following options will enable an SSL connection, but only if your MySQL client library and server have been compiled with SSL support. MySQL client library defaults will be used for any parameters that are left out or set to nil. Relative paths are allowed, and may be required by managed hosting providers such as Heroku. Set `:sslverify => true` to require that the server presents a valid certificate. ``` ruby Mysql2::Client.new( # ...options as above..., :sslkey => '/path/to/client-key.pem', :sslcert => '/path/to/client-cert.pem', :sslca => '/path/to/ca-cert.pem', :sslcapath => '/path/to/cacerts', :sslcipher => 'DHE-RSA-AES256-SHA', :sslverify => true, ) ``` ### Multiple result sets You can also retrieve multiple result sets. For this to work you need to connect with flags `Mysql2::Client::MULTI_STATEMENTS`. Multiple result sets can be used with stored procedures that return more than one result set, and for bundling several SQL statements into a single call to `client.query`. ``` 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 ``` Repeated calls to `client.next_result` will return true, false, or raise an exception if the respective query erred. When `client.next_result` returns true, call `client.store_result` to retrieve a result object. Exceptions are not raised until `client.next_result` is called to find the status of the respective query. Subsequent queries are not executed if an earlier query raised an exception. Subsequent calls to `client.next_result` will return false. ``` ruby result = client.query('SELECT 1; SELECT 2; SELECT A; SELECT 3') p result.first while client.next_result result = client.store_result p result.first end ``` Yields: ``` {"1"=>1} {"2"=>2} next_result: Unknown column 'A' in 'field list' (Mysql2::Error) ``` ### 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(). ### Flags option parsing The `:flags` parameter accepts an integer, a string, or an array. The integer form allows the client to assemble flags from constants defined under `Mysql2::Client` such as `Mysql2::Client::FOUND_ROWS`. Use a bitwise `|` (OR) to specify several flags. The string form will be split on whitespace and parsed as with the array form: Plain flags are added to the default flags, while flags prefixed with `-` (minus) are removed from the default flags. This allows easier use with ActiveRecord's database.yml, avoiding the need for magic flag numbers. For example, to disable protocol compression, and enable multiple statements and result sets: ``` yaml development: adapter: mysql2 encoding: utf8 database: my_db_name username: root password: my_password host: 127.0.0.1 port: 3306 flags: - -COMPRESS - FOUND_ROWS - MULTI_STATEMENTS 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` parameters. For example: ``` ruby Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client') ``` ### Initial command on connect and reconnect If you specify the `:init_command` option, the SQL string you provide will be executed after the connection is established. If `:reconnect` is set to `true`, init_command will also be executed after a successful reconnect. It is useful if you want to provide session options which survive reconnection. ``` ruby Mysql2::Client.new(:init_command => "SET @@SESSION.sql_mode = 'STRICT_ALL_TABLES'") ``` ## 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 ### 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. (Note that :cast => false overrides :cast_booleans => true.) ``` 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. ### 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 This gem is tested with the following Ruby versions on Linux and Mac OS X: * Ruby MRI 1.8.7, 1.9.3, 2.0.0, 2.1.x, 2.2.x * Ruby Enterprise Edition (based on MRI 1.8.7) * Rubinius 2.x This gem is tested with the following MySQL and MariaDB versions: * MySQL 5.5, 5.6, 5.7 * MySQL Connector/C 6.0 and 6.1 (primarily on Windows) * MariaDB 5.5, 10.0, 10.1 ### Rails / Active Record * mysql2 0.4.x works with Active Record 4.2.5 and higher. * mysql2 0.3.x works with Active Record 3.1 and higher (the AR adapter is now included in AR proper). * mysql2 0.2.x includes an Active Record adapter compatible with AR 2.3 and 3.0, and should not be used with AR 3.1 or higher. ### 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 Sequel includes a mysql2 adapter in all releases since 3.15 (2010-09-01). Use the prefix "mysql2://" in 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 ``` ## Benchmarks and Comparison The mysql2 gem converts MySQL field types to Ruby data types in C code, providing a serious speed benefit. The do_mysql gem also converts MySQL fields types, but has a considerably more complex API and is still ~2x slower than mysql2. The mysql gem returns only nil or string data types, leaving you to convert field values to Ruby types in Ruby-land, which is much slower than mysql2's C code. For a comparative benchmark, the script below performs 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: ``` 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) ``` These results are from the `query_with_mysql_casting.rb` script in the benchmarks folder. ## Development 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 * Kouhei Ueno (https://github.com/nyaxt) - for the original work on Prepared Statements way back in 2012 * John Cant (http://github.com/johncant) - polishing and updating Prepared Statements support * Justin Case (http://github.com/justincase) - polishing and updating Prepared Statements support and getting it merged * Tamir Duberstein (http://github.com/tamird) - for help with timeouts and all around updates and cleanups