├── .gitignore ├── LICENSE ├── README.rdoc ├── Rakefile ├── VERSION ├── drizzle.gemspec ├── lib ├── drizzle.rb └── drizzle │ ├── connection.rb │ ├── drizzle.rb │ ├── exceptions.rb │ ├── ffidrizzle.rb │ └── result.rb └── test ├── helper.rb ├── test_basic.rb └── test_complex.rb /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Padraig O'Sullivan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = drizzle-ruby 2 | 3 | An interface to Drizzle using FFI and libdrizzle. 4 | 5 | == Copyright 6 | 7 | Copyright (c) 2010 Padraig O'Sullivan. See LICENSE for details. 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | require 'rake/testtask' 4 | require 'rake/rdoctask' 5 | 6 | begin 7 | require 'jeweler' 8 | Jeweler::Tasks.new do |gem| 9 | gem.name = "drizzle" 10 | gem.summary = "A ruby interface to Drizzle using libdrizzle" 11 | gem.description = "An interface to Drizzle for Ruby that uses libdrizzle" 12 | gem.email = "osullivan.padraig@gmail.com" 13 | gem.homepage = "http://github.com/posulliv/drizzle-ruby" 14 | gem.authors = ["Padraig O'Sullivan"] 15 | gem.rubyforge_project = "drizzle-ruby" 16 | gem.add_development_dependency "thoughtbot-shoulda", ">= 0" 17 | # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings 18 | end 19 | Jeweler::GemcutterTasks.new 20 | Jeweler::RubyforgeTasks.new do |rubyforge| 21 | rubyforge.doc_task = "rdoc" 22 | end 23 | rescue LoadError 24 | puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler" 25 | exit(1) 26 | end 27 | 28 | Rake::TestTask.new(:test) do |test| 29 | test.libs << 'lib' << 'test' 30 | test.pattern = 'test/**/test_*.rb' 31 | test.verbose = true 32 | end 33 | 34 | begin 35 | require 'rcov/rcovtask' 36 | Rcov::RcovTask.new do |test| 37 | test.libs << 'test' 38 | test.pattern = 'test/**/test_*.rb' 39 | test.verbose = true 40 | end 41 | rescue LoadError 42 | task :rcov do 43 | abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov" 44 | end 45 | end 46 | 47 | task :test => :check_dependencies 48 | 49 | task :default => :test 50 | 51 | Rake::RDocTask.new do |rdoc| 52 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 53 | 54 | rdoc.rdoc_dir = 'rdoc' 55 | rdoc.title = "drizzle #{version}" 56 | rdoc.rdoc_files.include('README*') 57 | rdoc.rdoc_files.include('lib/**/*.rb') 58 | end 59 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /drizzle.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{drizzle} 8 | s.version = "0.1.0" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Padraig O'Sullivan"] 12 | s.date = %q{2011-04-13} 13 | s.description = %q{An interface to Drizzle for Ruby that uses libdrizzle} 14 | s.email = %q{osullivan.padraig@gmail.com} 15 | s.extra_rdoc_files = [ 16 | "LICENSE", 17 | "README.rdoc" 18 | ] 19 | s.files = [ 20 | "LICENSE", 21 | "README.rdoc", 22 | "Rakefile", 23 | "VERSION", 24 | "lib/drizzle.rb", 25 | "lib/drizzle/connection.rb", 26 | "lib/drizzle/drizzle.rb", 27 | "lib/drizzle/exceptions.rb", 28 | "lib/drizzle/ffidrizzle.rb", 29 | "lib/drizzle/result.rb", 30 | "test/helper.rb", 31 | "test/test_basic.rb", 32 | "test/test_complex.rb" 33 | ] 34 | s.homepage = %q{http://github.com/posulliv/drizzle-ruby} 35 | s.require_paths = ["lib"] 36 | s.rubyforge_project = %q{drizzle-ruby} 37 | s.rubygems_version = %q{1.3.6} 38 | s.summary = %q{A ruby interface to Drizzle using libdrizzle} 39 | s.test_files = [ 40 | "test/helper.rb", 41 | "test/test_basic.rb", 42 | "test/test_complex.rb" 43 | ] 44 | 45 | if s.respond_to? :specification_version then 46 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 47 | s.specification_version = 3 48 | 49 | if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then 50 | s.add_development_dependency(%q, [">= 0"]) 51 | else 52 | s.add_dependency(%q, [">= 0"]) 53 | end 54 | else 55 | s.add_dependency(%q, [">= 0"]) 56 | end 57 | end 58 | 59 | -------------------------------------------------------------------------------- /lib/drizzle.rb: -------------------------------------------------------------------------------- 1 | # rubygems 2 | require 'rubygems' 3 | 4 | # 3rd party 5 | require 'ffi' 6 | 7 | # internal requires 8 | require 'drizzle/ffidrizzle' 9 | require 'drizzle/drizzle' 10 | require 'drizzle/result' 11 | require 'drizzle/connection' 12 | require 'drizzle/exceptions' 13 | 14 | module Drizzle 15 | end 16 | -------------------------------------------------------------------------------- /lib/drizzle/connection.rb: -------------------------------------------------------------------------------- 1 | require 'drizzle/ffidrizzle' 2 | require 'drizzle/exceptions' 3 | 4 | module Drizzle 5 | 6 | class ConnectionPtr < FFI::AutoPointer 7 | def self.release(ptr) 8 | LibDrizzle.drizzle_con_free(ptr) 9 | end 10 | end 11 | 12 | # 13 | # map of keywords that will be used with the SQL injection 14 | # prevention plugin in drizzle 15 | # 16 | Keywords = 17 | { 18 | "add" => true, 19 | "all" => true, 20 | "alter" => true, 21 | "analyze" => true, 22 | "and" => true, 23 | "any" => true, 24 | "as" => true, 25 | "asc" => true, 26 | "before" => true, 27 | "between" => true, 28 | "by" => true, 29 | "count" => true, 30 | "distinct" => true, 31 | "drop" => true, 32 | "from" => true, 33 | "having" => true, 34 | "or" => true, 35 | "select" => true, 36 | "union" => true, 37 | "where" => true 38 | } 39 | 40 | 41 | # 42 | # connection options 43 | # 44 | NONE = 0 45 | ALLOCATED = 1 46 | MYSQL = 2 47 | RAW_PACKET = 4 48 | RAW_SCRAMBLE = 8 49 | READY = 16 50 | NO_RESULT_READ = 32 51 | INJECTION_PREVENTION = 64 52 | 53 | # 54 | # A drizzle connection 55 | # 56 | class Connection 57 | 58 | attr_accessor :host, :port, :db 59 | 60 | # 61 | # Creates a connection instance 62 | # 63 | # == parameters 64 | # 65 | # * host the hostname for the connection 66 | # * port the port number for the connection 67 | # * db the database name for the connection 68 | # * opts connection options 69 | # * drizzle_ptr FFI pointer to a drizzle_st object 70 | # 71 | # Some examples : 72 | # 73 | # c = Drizzle::Connection.new 74 | # c = Drizzle::Connection.new("my_host", 4427) 75 | # c = Drizzle::Connection.new("my_host", 4427, "my_db") 76 | # c = Drizzle::Connection.new("my_host", 4427, "my_db", [Drizzle::NONE]) 77 | # 78 | def initialize(host = "localhost", port = 4427, db = nil, opts = [], drizzle_ptr = nil) 79 | @host = host 80 | @port = port 81 | @db = db 82 | @drizzle_handle = drizzle_ptr || DrizzlePtr.new(LibDrizzle.drizzle_create(nil)) 83 | @con_ptr = ConnectionPtr.new(LibDrizzle.drizzle_con_create(@drizzle_handle, nil)) 84 | @rand_key = "" 85 | 86 | opts.each do |opt| 87 | if opt == INJECTION_PREVENTION 88 | @randomize_queries = true 89 | next 90 | end 91 | LibDrizzle.drizzle_con_add_options(@con_ptr, LibDrizzle::ConnectionOptions[opt]) 92 | end 93 | LibDrizzle.drizzle_con_set_tcp(@con_ptr, @host, @port) 94 | LibDrizzle.drizzle_con_set_db(@con_ptr, @db) if @db 95 | @ret_ptr = FFI::MemoryPointer.new(:int) 96 | 97 | # 98 | # if SQL injection prevention is enabled, we need to retrieve 99 | # the key to use for randomization from the server 100 | # 101 | if @randomize_queries == true 102 | query = "show variables like '%stad_key%'" 103 | res = LibDrizzle.drizzle_query_str(@con_ptr, nil, query, @ret_ptr) 104 | check_return_code 105 | result = Result.new(res) 106 | result.buffer_result 107 | result.each do |row| 108 | @rand_key = row[1] 109 | end 110 | end 111 | end 112 | 113 | # 114 | # set the host and port for the connection 115 | # 116 | def set_tcp(host, port) 117 | @host = host 118 | @port = port 119 | LibDrizzle.drizzle_con_set_tcp(@con_ptr, @host, @port) 120 | end 121 | 122 | # 123 | # set the database name for the connection 124 | # 125 | def set_db(db_name) 126 | @db = db_name 127 | LibDrizzle.drizzle_con_set_db(@con_ptr, @db) 128 | end 129 | 130 | # 131 | # execute a query and construct a result object 132 | # 133 | def query(query) 134 | res = LibDrizzle.drizzle_query_str(@con_ptr, nil, query, @ret_ptr) 135 | check_return_code 136 | Result.new(res) 137 | end 138 | 139 | # 140 | # tokenize the input query and append the randomization key to 141 | # keywords 142 | # 143 | def randomize_query(query) 144 | if @rand_key.empty? 145 | return query 146 | end 147 | toks = query.split(" ") 148 | new_query = "" 149 | toks.each do |token| 150 | if Keywords[token.downcase] == true 151 | token << @rand_key 152 | end 153 | new_query << token << " " 154 | end 155 | new_query 156 | end 157 | 158 | def check_return_code() 159 | case LibDrizzle::ReturnCode[@ret_ptr.get_int(0)] 160 | when :DRIZZLE_RETURN_IO_WAIT 161 | raise IoWait.new(LibDrizzle.drizzle_error(@drizzle_handle)) 162 | when :DRIZZLE_RETURN_PAUSE 163 | raise Pause.new(LibDrizzle.drizzle_error(@drizzle_handle)) 164 | when :DRIZZLE_RETURN_ROW_BREAK 165 | raise RowBreak.new(LibDrizzle.drizzle_error(@drizzle_handle)) 166 | when :DRIZZLE_RETURN_MEMORY 167 | raise Memory.new(LibDrizzle.drizzle_error(@drizzle_handle)) 168 | when :DRIZZLE_RETURN_INTERNAL_ERROR 169 | raise InternalError.new(LibDrizzle.drizzle_error(@drizzle_handle)) 170 | when :DRIZZLE_RETURN_NOT_READY 171 | raise NotReady.new(LibDrizzle.drizzle_error(@drizzle_handle)) 172 | when :DRIZZLE_RETURN_BAD_PACKET_NUMBER 173 | raise BadPacketNumber.new(LibDrizzle.drizzle_error(@drizzle_handle)) 174 | when :DRIZZLE_RETURN_BAD_HANDSHAKE_PACKET 175 | raise BadHandshake.new(LibDrizzle.drizzle_error(@drizzle_handle)) 176 | when :DRIZZLE_RETURN_BAD_PACKET 177 | raise BadPacket.new(LibDrizzle.drizzle_error(@drizzle_handle)) 178 | when :DRIZZLE_RETURN_PROTOCOL_NOT_SUPPORTED 179 | raise ProtocolNotSupported.new(LibDrizzle.drizzle_error(@drizzle_handle)) 180 | when :DRIZZLE_RETURN_UNEXPECTED_DATA 181 | raise UnexpectedData.new(LibDrizzle.drizzle_error(@drizzle_handle)) 182 | when :DRIZZLE_RETURN_NO_SCRAMBLE 183 | raise NoScramble.new(LibDrizzle.drizzle_error(@drizzle_handle)) 184 | when :DRIZZLE_RETURN_AUTH_FAILED 185 | raise AuthFailed.new(LibDrizzle.drizzle_error(@drizzle_handle)) 186 | when :DRIZZLE_RETURN_NULL_SIZE 187 | raise NullSize.new(LibDrizzle.drizzle_error(@drizzle_handle)) 188 | when :DRIZZLE_RETURN_TOO_MANY_COLUMNS 189 | raise TooManyColumns.new(LibDrizzle.drizzle_error(@drizzle_handle)) 190 | when :DRIZZLE_RETURN_ROW_END 191 | raise RowEnd.new(LibDrizzle.drizzle_error(@drizzle_handle)) 192 | when :DRIZZLE_RETURN_LOST_CONNECTION 193 | raise LostConnection.new(LibDrizzle.drizzle_error(@drizzle_handle)) 194 | when :DRIZZLE_RETURN_COULD_NOT_CONNECT 195 | raise CouldNotConnect.new(LibDrizzle.drizzle_error(@drizzle_handle)) 196 | when :DRIZZLE_RETURN_NO_ACTIVE_CONNECTIONS 197 | raise NoActiveConnections.new(LibDrizzle.drizzle_error(@drizzle_handle)) 198 | when :DRIZZLE_RETURN_HANDSHAKE_FAILED 199 | raise HandshakeFailed.new(LibDrizzle.drizzle_error(@drizzle_handle)) 200 | when :DRIZZLE_RETURN_TIMEOUT 201 | raise ReturnTimeout.new(LibDrizzle.drizzle_error(@drizzle_handle)) 202 | end 203 | end 204 | 205 | end 206 | 207 | end 208 | -------------------------------------------------------------------------------- /lib/drizzle/drizzle.rb: -------------------------------------------------------------------------------- 1 | require 'drizzle/ffidrizzle' 2 | 3 | module Drizzle 4 | 5 | class DrizzlePtr < FFI::AutoPointer 6 | def self.release(ptr) 7 | LibDrizzle.drizzle_free(ptr) 8 | end 9 | end 10 | 11 | # 12 | # A Drizzle instance 13 | # 14 | class Drizzle 15 | 16 | # 17 | # creates a drizzle instance 18 | # 19 | def initialize() 20 | @handle = DrizzlePtr.new(LibDrizzle.drizzle_create(nil)) 21 | end 22 | 23 | # 24 | # create a client connection 25 | # 26 | def create_client_connection(host, port, db) 27 | Connection.new(host, port, db, @handle) 28 | end 29 | 30 | # 31 | # return the libdrizzle API version 32 | # 33 | def version() 34 | LibDrizzle.drizzle_version 35 | end 36 | 37 | # 38 | # return the bug report URL to file libdrizzle bugs at 39 | # 40 | def bug_report_url() 41 | LibDrizzle.drizzle_bugreport 42 | end 43 | 44 | # 45 | # add a query to be run concurrently 46 | # 47 | def add_query(conn, query, query_num, opts = []) 48 | end 49 | 50 | # 51 | # execute all queries concurrently 52 | # 53 | def run_all() 54 | end 55 | 56 | end 57 | 58 | end 59 | -------------------------------------------------------------------------------- /lib/drizzle/exceptions.rb: -------------------------------------------------------------------------------- 1 | require 'drizzle/ffidrizzle' 2 | 3 | module Drizzle 4 | 5 | class IoWait < RuntimeError; end 6 | class Pause < RuntimeError; end 7 | class RowBreak < RuntimeError; end 8 | class Memory < RuntimeError; end 9 | class InternalError < RuntimeError; end 10 | class NotReady < RuntimeError; end 11 | class BadPacketNumber < RuntimeError; end 12 | class BadHandshake < RuntimeError; end 13 | class BadPacket < RuntimeError; end 14 | class ProtocolNotSupported < RuntimeError; end 15 | class UnexpectedData < RuntimeError; end 16 | class NoScramble < RuntimeError; end 17 | class AuthFailed < RuntimeError; end 18 | class NullSize < RuntimeError; end 19 | class TooManyColumns < RuntimeError; end 20 | class RowEnd < RuntimeError; end 21 | class LostConnection < RuntimeError; end 22 | class CouldNotConnect < RuntimeError; end 23 | class NoActiveConnections < RuntimeError; end 24 | class HandshakeFailed < RuntimeError; end 25 | class Timeout < RuntimeError; end 26 | class GeneralError < RuntimeError; end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /lib/drizzle/ffidrizzle.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'ffi' 3 | 4 | module LibDrizzle 5 | extend FFI::Library 6 | ffi_lib 'drizzle' 7 | 8 | # constants 9 | MAX_ERROR_SIZE = 2048 10 | MAX_USER_SIZE = 64 11 | MAX_PASSWORD_SIZE = 32 12 | MAX_DB_SIZE = 64 13 | MAX_INFO_SIZE = 2048 14 | MAX_SQLSTATE_SIZE = 5 15 | MAX_CATALOG_SIZE = 128 16 | MAX_TABLE_SIZE = 128 17 | 18 | # return codes 19 | ReturnCode = enum( :DRIZZLE_RETURN_OK, 0, 20 | :DRIZZLE_RETURN_IO_WAIT, 21 | :DRIZZLE_RETURN_PAUSE, 22 | :DRIZZLE_RETURN_ROW_BREAK, 23 | :DRIZZLE_RETURN_MEMORY, 24 | :DRIZZLE_RETURN_ERRNO, 25 | :DRIZZLE_RETURN_INTERNAL_ERROR, 26 | :DRIZZLE_RETURN_GETADDRINFO, 27 | :DRIZZLE_RETURN_NOT_READY, 28 | :DRIZZLE_RETURN_BAD_PACKET_NUMBER, 29 | :DRIZZLE_RETURN_BAD_HANDSHAKE_PACKET, 30 | :DRIZZLE_RETURN_BAD_PACKET, 31 | :DRIZZLE_RETURN_PROTOCOL_NOT_SUPPORTED, 32 | :DRIZZLE_RETURN_UNEXPECTED_DATA, 33 | :DRIZZLE_RETURN_NO_SCRAMBLE, 34 | :DRIZZLE_RETURN_AUTH_FAILED, 35 | :DRIZZLE_RETURN_NULL_SIZE, 36 | :DRIZZLE_RETURN_ERROR_CODE, 37 | :DRIZZLE_RETURN_TOO_MANY_COLUMNS, 38 | :DRIZZLE_RETURN_ROW_END, 39 | :DRIZZLE_RETURN_LOST_CONNECTION, 40 | :DRIZZLE_RETURN_COULD_NOT_CONNECT, 41 | :DRIZZLE_RETURN_NO_ACTIVE_CONNECTIONS, 42 | :DRIZZLE_RETURN_HANDSHAKE_FAILED, 43 | :DRIZZLE_RETURN_TIMEOUT, 44 | :DRIZZLE_RETURN_MAX ) 45 | 46 | # verbosity levels 47 | VerbosityLevel = enum( :DRIZZLE_VERBOSE_NEVER, 0, 48 | :DRIZZLE_VERBOSE_FATAL, 49 | :DRIZZLE_VERBOSE_ERROR, 50 | :DRIZZLE_VERBOSE_INFO, 51 | :DRIZZLE_VERBOSE_DEBUG, 52 | :DRIZZLE_VERBOSE_CRAZY, 53 | :DRIZZLE_VERBOSE_MAX ) 54 | 55 | # options for the Drizzle protocol functions. 56 | CommandTypes = enum( :DRIZZLE_COMMAND_DRIZZLE_SLEEP, 0, 57 | :DRIZZLE_COMMAND_DRIZZLE_QUIT, 58 | :DRIZZLE_COMMAND_DRIZZLE_INIT_DB, 59 | :DRIZZLE_COMMAND_DRIZZLE_QUERY, 60 | :DRIZZLE_COMMAND_DRIZZLE_SHUTDOWN, 61 | :DRIZZLE_COMMAND_DRIZZLE_CONNECT, 62 | :DRIZZLE_COMMAND_DRIZZLE_PING, 63 | :DRIZZLE_COMMAND_DRIZZLE_END ) 64 | 65 | # Status flags for a drizzle connection 66 | ConnectionStatus = enum( :DRIZZLE_CON_STATUS_NONE, 0, 67 | :DRIZZLE_CON_STATUS_IN_TRANS, (1 << 0), 68 | :DRIZZLE_CON_STATUS_AUTOCOMMIT, (1 << 1), 69 | :DRIZZLE_CON_STATUS_MORE_RESULTS_EXIST, (1 << 3), 70 | :DRIZZLE_CON_STATUS_QUERY_NO_GOOD_INDEX_USED, (1 << 4), 71 | :DRIZZLE_CON_STATUS_QUERY_NO_INDEX_USED, (1 << 5), 72 | :DRIZZLE_CON_STATUS_CURSOR_EXISTS, (1 << 6), 73 | :DRIZZLE_CON_STATUS_LAST_ROW_SENT, (1 << 7), 74 | :DRIZZLE_CON_STATUS_DB_DROPPED, (1 << 8), 75 | :DRIZZLE_CON_STATUS_NO_BACKSLASH_ESCAPES, (1 << 9), 76 | :DRIZZLE_CON_STATUS_QUERY_WAS_SLOW, (1 << 10) ) 77 | 78 | # Options for connections 79 | ConnectionOptions = enum( :DRIZZLE_CON_NONE, 0, 80 | :DRIZZLE_CON_ALLOCATED, (1 << 0), 81 | :DRIZZLE_CON_MYSQL, (1 << 1), 82 | :DRIZZLE_CON_RAW_PACKET, (1 << 2), 83 | :DRIZZLE_CON_RAW_SCRAMBLE, (1 << 3), 84 | :DRIZZLE_CON_READY, (1 << 4), 85 | :DRIZZLE_CON_NO_RESULT_READ, (1 << 5) ) 86 | 87 | # query options 88 | QueryOptions = enum( :DRIZZLE_QUERY_ALLOCATED, (1 << 0) ) 89 | 90 | # options for main drizzle structure 91 | Options = enum( :DRIZZLE_NONE, 0, 92 | :DRIZZLE_ALLOCATED, (1 << 0), 93 | :DRIZZLE_NON_BLOCKING, (1 << 1), 94 | :DRIZZLE_FREE_OBJECTS, (1 << 2), 95 | :DRIZZLE_ASSERT_DANGLING, (1 << 3) ) 96 | 97 | attach_function :drizzle_create, [ :pointer ], :pointer 98 | attach_function :drizzle_free, [ :pointer ], :void 99 | attach_function :drizzle_set_verbose, [ :pointer, VerbosityLevel ], :void 100 | 101 | # connection related functions 102 | attach_function :drizzle_con_create, [ :pointer, :pointer ], :pointer 103 | attach_function :drizzle_con_free, [ :pointer ], :void 104 | attach_function :drizzle_con_set_tcp, [ :pointer, :string, :int ], :void 105 | attach_function :drizzle_con_set_db, [ :pointer, :string ], :void 106 | attach_function :drizzle_con_add_options, [ :pointer, ConnectionOptions ], :void 107 | attach_function :drizzle_con_fd, [ :pointer ], :int 108 | 109 | # query related functions 110 | attach_function :drizzle_query_str, [ :pointer, :pointer, :string, :pointer ], :pointer 111 | attach_function :drizzle_query_add, [ :pointer, :pointer, :pointer, :pointer, :string, :size_t, QueryOptions, :pointer ], :pointer 112 | attach_function :drizzle_result_read, [ :pointer, :pointer, :pointer ], :pointer 113 | attach_function :drizzle_row_buffer, [ :pointer, :pointer ], :pointer 114 | attach_function :drizzle_row_free, [ :pointer ], :void 115 | attach_function :drizzle_result_buffer, [ :pointer ], ReturnCode 116 | attach_function :drizzle_result_free, [ :pointer ], :void 117 | attach_function :drizzle_row_next, [ :pointer ], :pointer 118 | attach_function :drizzle_column_next, [ :pointer ], :pointer 119 | attach_function :drizzle_column_name, [ :pointer ], :string 120 | attach_function :drizzle_column_buffer, [ :pointer ], ReturnCode 121 | attach_function :drizzle_result_column_count, [ :pointer ], :uint16 122 | attach_function :drizzle_result_affected_rows, [ :pointer ], :uint64 123 | attach_function :drizzle_result_insert_id, [ :pointer ], :uint64 124 | 125 | # miscellaneous functions 126 | attach_function :drizzle_version, [], :string 127 | attach_function :drizzle_bugreport, [], :string 128 | attach_function :drizzle_error, [ :pointer ], :string 129 | 130 | end 131 | -------------------------------------------------------------------------------- /lib/drizzle/result.rb: -------------------------------------------------------------------------------- 1 | require 'drizzle/ffidrizzle' 2 | 3 | module Drizzle 4 | 5 | # 6 | # a result set from a drizzle query 7 | # 8 | class Result 9 | 10 | attr_reader :columns, :rows, :affected_rows, :insert_id 11 | 12 | # 13 | # creates a result set instance 14 | # This result set does not buffer any results until instructed 15 | # to do so. Results can be fully buffered or buffered at the 16 | # row level. 17 | # 18 | # == parameters 19 | # 20 | # * res_ptr FFI memory pointer to a drizzle_result_t object 21 | # 22 | def initialize(res_ptr) 23 | @columns, @rows = [], [] 24 | @res_ptr = res_ptr 25 | @affected_rows = LibDrizzle.drizzle_result_affected_rows(@res_ptr) 26 | @insert_id = LibDrizzle.drizzle_result_insert_id(@res_ptr) 27 | end 28 | 29 | # 30 | # buffer all rows and columns and copy them into ruby arrays 31 | # Free the FFI pointer afterwards 32 | # 33 | def buffer_result() 34 | ret = LibDrizzle.drizzle_result_buffer(@res_ptr) 35 | if LibDrizzle::ReturnCode[ret] != LibDrizzle::ReturnCode[:DRIZZLE_RETURN_OK] 36 | LibDrizzle.drizzle_result_free(@res_ptr) 37 | end 38 | 39 | loop do 40 | col_ptr = LibDrizzle.drizzle_column_next(@res_ptr) 41 | break if col_ptr.null? 42 | @columns << LibDrizzle.drizzle_column_name(col_ptr).to_sym 43 | end 44 | 45 | loop do 46 | row_ptr = LibDrizzle.drizzle_row_next(@res_ptr) 47 | break if row_ptr.null? 48 | @rows << row_ptr.get_array_of_string(0, @columns.size) 49 | end 50 | 51 | LibDrizzle.drizzle_result_free(@res_ptr) 52 | end 53 | 54 | # 55 | # buffer an individual row. 56 | # 57 | def buffer_row() 58 | # if the columns have not been read for this result 59 | # set yet, then we need to do that here. If this is not 60 | # performed here, we will receive a bad packet error 61 | read_columns if @columns.empty? 62 | ret_ptr = FFI::MemoryPointer.new(:int) 63 | row_ptr = LibDrizzle.drizzle_row_buffer(@res_ptr, ret_ptr) 64 | if LibDrizzle::ReturnCode[ret_ptr.get_int(0)] != :DRIZZLE_RETURN_OK 65 | LibDrizzle.drizzle_result_free(@res_ptr) 66 | end 67 | if row_ptr.null? 68 | LibDrizzle.drizzle_result_free(@res_ptr) 69 | return nil 70 | end 71 | num_of_cols = LibDrizzle.drizzle_result_column_count(@res_ptr) 72 | row = row_ptr.get_array_of_string(0, @columns.size) 73 | end 74 | 75 | # 76 | # buffer all columns for this result set 77 | # 78 | def read_columns 79 | ret = LibDrizzle.drizzle_column_buffer(@res_ptr) 80 | loop do 81 | col_ptr = LibDrizzle.drizzle_column_next(@res_ptr) 82 | break if col_ptr.null? 83 | @columns << LibDrizzle.drizzle_column_name(col_ptr).to_sym 84 | end 85 | end 86 | 87 | def each 88 | @rows.each do |row| 89 | yield row if block_given? 90 | end 91 | end 92 | 93 | end 94 | 95 | end 96 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | require File.join(File.dirname(__FILE__), *%w[.. lib drizzle]) 4 | 5 | require 'test/unit' 6 | require 'shoulda' 7 | require 'rr' 8 | 9 | include Drizzle 10 | 11 | PORT = 4427 12 | 13 | class Test::Unit::TestCase 14 | include RR::Adapters::TestUnit 15 | 16 | def dest_dir(*subdirs) 17 | File.join(File.dirname(__FILE__), 'dest', *subdirs) 18 | end 19 | 20 | def source_dir(*subdirs) 21 | File.join(File.dirname(__FILE__), 'source', *subdirs) 22 | end 23 | 24 | def clear_dest 25 | FileUtils.rm_rf(dest_dir) 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /test/test_basic.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/helper' 2 | 3 | class TestBasic < Test::Unit::TestCase 4 | 5 | should "retrieve libdrizzle API version" do 6 | drizzle = Drizzle::Drizzle.new 7 | assert_equal "0.7", drizzle.version 8 | end 9 | 10 | should "retrieve libdrizzle bug report url" do 11 | drizzle = Drizzle::Drizzle.new 12 | assert_equal "https://launchpad.net/libdrizzle", drizzle.bug_report_url 13 | end 14 | 15 | should "connect to drizzle successfully" do 16 | conn = Drizzle::Connection.new("localhost", PORT) 17 | assert_equal conn.class, Drizzle::Connection 18 | end 19 | 20 | should "perform a simple query and buffer all results" do 21 | conn = Drizzle::Connection.new("localhost", PORT, "data_dictionary") 22 | res = conn.query("SELECT module_name, module_author FROM MODULES where module_name = 'SchemaEngine'") 23 | assert_equal res.class, Drizzle::Result 24 | res.buffer_result 25 | assert_equal 2, res.columns.size 26 | res.each do |row| 27 | assert_equal "Brian Aker", row[1] 28 | end 29 | end 30 | 31 | should "perform another simple query and buffer all results" do 32 | conn = Drizzle::Connection.new("localhost", PORT, "information_schema", [Drizzle::NONE]) 33 | res = conn.query("SELECT table_schema, table_name FROM TABLES WHERE table_schema = 'DATA_DICTIONARY' AND table_name = 'GLOBAL_STATUS'") 34 | assert_equal res.class, Drizzle::Result 35 | res.buffer_result 36 | assert_equal 2, res.columns.size 37 | res.each do |row| 38 | assert_equal "DATA_DICTIONARY", row[0] 39 | assert_equal "GLOBAL_STATUS", row[1] 40 | end 41 | end 42 | 43 | should "perform a simple query and buffer rows" do 44 | conn = Drizzle::Connection.new("localhost", PORT, "information_schema") 45 | res = conn.query("SELECT COUNT(*) FROM COLUMNS WHERE table_name = 'GLOBAL_STATUS'") 46 | assert_equal res.class, Drizzle::Result 47 | until (row = res.buffer_row).nil? 48 | assert_equal "2", row[0] 49 | end 50 | end 51 | 52 | should "create and drop a database" do 53 | conn = Drizzle::Connection.new("localhost", PORT) 54 | res = conn.query("CREATE DATABASE padraig") 55 | res = conn.query("select table_schema from information_schema.tables where table_schema = 'padraig'") 56 | assert_equal res.class, Drizzle::Result 57 | res.buffer_result 58 | assert_equal 1, res.columns.size 59 | res.each do |row| 60 | assert_equal "padraig", row[0] 61 | end 62 | res = conn.query("DROP DATABASE padraig") 63 | res = conn.query("select table_schema from information_schema.tables where table_schema = 'padraig'") 64 | assert_equal res.class, Drizzle::Result 65 | res.buffer_result 66 | assert_equal 1, res.columns.size 67 | assert_equal true, res.rows.empty? 68 | end 69 | 70 | should "use different connection options" do 71 | conn = Drizzle::Connection.new("localhost", PORT, "information_schema", [Drizzle::NONE]) 72 | res = conn.query("SELECT COUNT(*) FROM COLUMNS WHERE table_name = 'GLOBAL_STATUS'") 73 | assert_equal res.class, Drizzle::Result 74 | until (row = res.buffer_row).nil? 75 | assert_equal "2", row[0] 76 | end 77 | conn = Drizzle::Connection.new("localhost", PORT, "information_schema", [Drizzle::NONE, Drizzle::MYSQL]) 78 | res = conn.query("SELECT COUNT(*) FROM COLUMNS WHERE table_name = 'GLOBAL_STATUS'") 79 | assert_equal res.class, Drizzle::Result 80 | until (row = res.buffer_row).nil? 81 | assert_equal "2", row[0] 82 | end 83 | end 84 | 85 | should "perform a query with a code block" do 86 | conn = Drizzle::Connection.new("localhost", PORT, "information_schema", [Drizzle::NONE]) 87 | conn.query("SELECT COUNT(*) FROM COLUMNS WHERE table_name = 'GLOBAL_STATUS'") do |res| 88 | assert_equal res.class, Drizzle::Result 89 | until (row = res.buffer_row).nil? 90 | assert_equal "2", row[0] 91 | end 92 | end 93 | end 94 | 95 | should "perform a simple show variables command" do 96 | conn = Drizzle::Connection.new("localhost", PORT, "information_schema", [Drizzle::NONE]) 97 | assert_equal conn.class, Drizzle::Connection 98 | res = conn.query("show variables") 99 | assert_equal res.class, Drizzle::Result 100 | res.buffer_result 101 | end 102 | 103 | end 104 | -------------------------------------------------------------------------------- /test/test_complex.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/helper' 2 | 3 | class TestBasic < Test::Unit::TestCase 4 | 5 | def setup 6 | conn = Drizzle::Connection.new("localhost", PORT) 7 | res = conn.query("CREATE DATABASE drizzleruby") 8 | end 9 | 10 | def teardown 11 | conn = Drizzle::Connection.new("localhost", PORT) 12 | res = conn.query("DROP DATABASE drizzleruby") 13 | end 14 | 15 | should "create and drop a table" do 16 | conn = Drizzle::Connection.new("localhost", PORT, "drizzleruby") 17 | res = conn.query("select table_schema from information_schema.tables where table_schema = 'drizzleruby'") 18 | assert_equal res.class, Drizzle::Result 19 | res.buffer_result 20 | assert_equal 1, res.columns.size 21 | res.each do |row| 22 | assert_equal "drizzleruby", row[0] 23 | end 24 | res = conn.query("create table t1(a int, b varchar(255))") 25 | res = conn.query("select table_schema, table_name from information_schema.tables where table_schema = 'drizzleruby'") 26 | res.buffer_result 27 | assert_equal 2, res.columns.size 28 | res.each do |row| 29 | assert_equal "t1", row[1] 30 | end 31 | res = conn.query("DROP TABLE t1") 32 | res = conn.query("select table_name from information_schema.tables where table_schema = 'drizzleruby'") 33 | assert_equal res.class, Drizzle::Result 34 | res.buffer_result 35 | assert_equal 1, res.columns.size 36 | assert_equal true, res.rows.empty? 37 | end 38 | 39 | should "update affected rows appropriately" do 40 | conn = Drizzle::Connection.new("localhost", PORT, "drizzleruby") 41 | res = conn.query("select table_schema from information_schema.tables where table_schema = 'drizzleruby'") 42 | assert_equal res.class, Drizzle::Result 43 | res.buffer_result 44 | assert_equal 1, res.columns.size 45 | res.each do |row| 46 | assert_equal "drizzleruby", row[0] 47 | end 48 | res = conn.query("create table t1(a int, b varchar(255))") 49 | res = conn.query("select table_schema, table_name from information_schema.tables where table_schema = 'drizzleruby'") 50 | res.buffer_result 51 | assert_equal 2, res.columns.size 52 | res.each do |row| 53 | assert_equal "t1", row[1] 54 | end 55 | res = conn.query("insert into t1 values (1, 'padraig')") 56 | assert_equal 1, res.affected_rows 57 | assert_equal 0, res.insert_id 58 | res = conn.query("insert into t1 values (2, 'sarah')") 59 | assert_equal 1, res.affected_rows 60 | assert_equal 0, res.insert_id 61 | res = conn.query("select * from t1 where a = 2") 62 | res.buffer_result 63 | assert_equal 2, res.columns.size 64 | res.each do |row| 65 | assert_equal "2", row[0] 66 | assert_equal "sarah", row[1] 67 | end 68 | res = conn.query("DROP TABLE t1") 69 | res = conn.query("select table_name from information_schema.tables where table_schema = 'drizzleruby'") 70 | assert_equal res.class, Drizzle::Result 71 | res.buffer_result 72 | assert_equal 1, res.columns.size 73 | assert_equal true, res.rows.empty? 74 | end 75 | 76 | should "perform a multi-insert statement correctly" do 77 | conn = Drizzle::Connection.new("localhost", PORT, "drizzleruby") 78 | res = conn.query("select table_schema from information_schema.tables where table_schema = 'drizzleruby'") 79 | assert_equal res.class, Drizzle::Result 80 | res.buffer_result 81 | assert_equal 1, res.columns.size 82 | res.each do |row| 83 | assert_equal "drizzleruby", row[0] 84 | end 85 | res = conn.query("create table t1(a int, b varchar(255))") 86 | res = conn.query("select table_schema, table_name from information_schema.tables where table_schema = 'drizzleruby'") 87 | res.buffer_result 88 | assert_equal 2, res.columns.size 89 | res.each do |row| 90 | assert_equal "t1", row[1] 91 | end 92 | res = conn.query("insert into t1 values (1, 'padraig'), (2, 'sarah'), (3, 'tomas')") 93 | assert_equal 3, res.affected_rows 94 | res = conn.query("select * from t1 where a = 2") 95 | res.buffer_result 96 | assert_equal 2, res.columns.size 97 | res.each do |row| 98 | assert_equal "2", row[0] 99 | assert_equal "sarah", row[1] 100 | end 101 | res = conn.query("DROP TABLE t1") 102 | res = conn.query("select table_name from information_schema.tables where table_schema = 'drizzleruby'") 103 | assert_equal res.class, Drizzle::Result 104 | res.buffer_result 105 | assert_equal 1, res.columns.size 106 | assert_equal true, res.rows.empty? 107 | end 108 | 109 | should "insert and fetch a blob value correctly" do 110 | conn = Drizzle::Connection.new("localhost", PORT, "drizzleruby") 111 | res = conn.query("create table t1(a int, b blob)") 112 | res = conn.query("insert into t1 values (1, 'padraig'), (2, 'sarah'), (3, 'tomas')") 113 | assert_equal 3, res.affected_rows 114 | res = conn.query("insert into t1 values (4, null), (5, 'blahblahblah'), (6, 'southy')") 115 | assert_equal 3, res.affected_rows 116 | res = conn.query("select * from t1 where a = 2") 117 | res.buffer_result 118 | assert_equal 2, res.columns.size 119 | res.each do |row| 120 | assert_equal "2", row[0] 121 | assert_equal "sarah", row[1] 122 | end 123 | res = conn.query("select * from t1 where a = 4") 124 | res.buffer_result 125 | assert_equal 2, res.columns.size 126 | res.each do |row| 127 | assert_equal "4", row[0] 128 | assert_equal nil, row[1] 129 | end 130 | res = conn.query("DROP TABLE t1") 131 | end 132 | 133 | end 134 | --------------------------------------------------------------------------------