summaryrefslogtreecommitdiffstats
path: root/lib/sqlite3
diff options
context:
space:
mode:
authorJamis Buck <jamis@37signals.com>2005-01-09 15:04:16 +0000
committerJamis Buck <jamis@37signals.com>2005-01-09 15:04:16 +0000
commitc51fa9fc3d7c0ec6f59bcaf3d352fae9325857e2 (patch)
treefa6c5fdee64da937145d594348100b03225a4207 /lib/sqlite3
downloadthird_party-sqlite3-ruby-c51fa9fc3d7c0ec6f59bcaf3d352fae9325857e2.tar.gz
third_party-sqlite3-ruby-c51fa9fc3d7c0ec6f59bcaf3d352fae9325857e2.tar.xz
third_party-sqlite3-ruby-c51fa9fc3d7c0ec6f59bcaf3d352fae9325857e2.zip
Changed layout to support tagging and branching
Diffstat (limited to 'lib/sqlite3')
-rw-r--r--lib/sqlite3/constants.rb81
-rw-r--r--lib/sqlite3/database.rb720
-rw-r--r--lib/sqlite3/driver/dl/api.rb184
-rw-r--r--lib/sqlite3/driver/dl/driver.rb334
-rw-r--r--lib/sqlite3/driver/native/driver.rb218
-rw-r--r--lib/sqlite3/errors.rb84
-rw-r--r--lib/sqlite3/pragmas.rb254
-rw-r--r--lib/sqlite3/resultset.rb172
-rw-r--r--lib/sqlite3/statement.rb218
-rw-r--r--lib/sqlite3/translator.rb135
-rw-r--r--lib/sqlite3/value.rb89
-rw-r--r--lib/sqlite3/version.rb45
12 files changed, 2534 insertions, 0 deletions
diff --git a/lib/sqlite3/constants.rb b/lib/sqlite3/constants.rb
new file mode 100644
index 0000000..5a20e46
--- /dev/null
+++ b/lib/sqlite3/constants.rb
@@ -0,0 +1,81 @@
+#--
+# =============================================================================
+# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * The names of its contributors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# =============================================================================
+#++
+
+module SQLite3 ; module Constants
+
+ module TextRep
+ UTF8 = 1
+ UTF16LE = 2
+ UTF16BE = 3
+ UTF16 = 4
+ ANY = 5
+ end
+
+ module ColumnType
+ INTEGER = 1
+ FLOAT = 2
+ TEXT = 3
+ BLOB = 4
+ NULL = 5
+ end
+
+ module ErrorCode
+ OK = 0 # Successful result
+ ERROR = 1 # SQL error or missing database
+ INTERNAL = 2 # An internal logic error in SQLite
+ PERM = 3 # Access permission denied
+ ABORT = 4 # Callback routine requested an abort
+ BUSY = 5 # The database file is locked
+ LOCKED = 6 # A table in the database is locked
+ NOMEM = 7 # A malloc() failed
+ READONLY = 8 # Attempt to write a readonly database
+ INTERRUPT = 9 # Operation terminated by sqlite_interrupt()
+ IOERR = 10 # Some kind of disk I/O error occurred
+ CORRUPT = 11 # The database disk image is malformed
+ NOTFOUND = 12 # (Internal Only) Table or record not found
+ FULL = 13 # Insertion failed because database is full
+ CANTOPEN = 14 # Unable to open the database file
+ PROTOCOL = 15 # Database lock protocol error
+ EMPTY = 16 # (Internal Only) Database table is empty
+ SCHEMA = 17 # The database schema changed
+ TOOBIG = 18 # Too much data for one row of a table
+ CONSTRAINT = 19 # Abort due to contraint violation
+ MISMATCH = 20 # Data type mismatch
+ MISUSE = 21 # Library used incorrectly
+ NOLFS = 22 # Uses OS features not supported on host
+ AUTH = 23 # Authorization denied
+
+ ROW = 100 # sqlite_step() has another row ready
+ DONE = 101 # sqlite_step() has finished executing
+ end
+
+end ; end
diff --git a/lib/sqlite3/database.rb b/lib/sqlite3/database.rb
new file mode 100644
index 0000000..65a8410
--- /dev/null
+++ b/lib/sqlite3/database.rb
@@ -0,0 +1,720 @@
+#--
+# =============================================================================
+# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * The names of its contributors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# =============================================================================
+#++
+
+require 'base64'
+require 'sqlite3/constants'
+require 'sqlite3/errors'
+require 'sqlite3/pragmas'
+require 'sqlite3/statement'
+require 'sqlite3/translator'
+require 'sqlite3/value'
+
+module SQLite3
+
+ # The Database class encapsulates a single connection to a SQLite3 database.
+ # Its usage is very straightforward:
+ #
+ # require 'sqlite3'
+ #
+ # db = SQLite3::Database.new( "data.db" )
+ #
+ # db.execute( "select * from table" ) do |row|
+ # p row
+ # end
+ #
+ # db.close
+ #
+ # It wraps the lower-level methods provides by the selected driver, and
+ # includes the Pragmas module for access to various pragma convenience
+ # methods.
+ #
+ # The Database class provides type translation services as well, by which
+ # the SQLite3 data types (which are all represented as strings) may be
+ # converted into their corresponding types (as defined in the schemas
+ # for their tables). This translation only occurs when querying data from
+ # the database--insertions and updates are all still typeless.
+ #
+ # Furthermore, the Database class has been designed to work well with the
+ # ArrayFields module from Ara Howard. If you require the ArrayFields
+ # module before performing a query, and if you have not enabled results as
+ # hashes, then the results will all be indexible by field name.
+ class Database
+ include Pragmas
+
+ class <<self
+
+ alias :open :new
+
+ # Quotes the given string, making it safe to use in an SQL statement.
+ # It replaces all instances of the single-quote character with two
+ # single-quote characters. The modified string is returned.
+ def quote( string )
+ string.gsub( /'/, "''" )
+ end
+
+ end
+
+ # The low-level opaque database handle that this object wraps.
+ attr_reader :handle
+
+ # A reference to the underlying SQLite3 driver used by this database.
+ attr_reader :driver
+
+ # A boolean that indicates whether rows in result sets should be returned
+ # as hashes or not. By default, rows are returned as arrays.
+ attr_accessor :results_as_hash
+
+ # A boolean indicating whether or not type translation is enabled for this
+ # database.
+ attr_accessor :type_translation
+
+ # Create a new Database object that opens the given file. If utf16
+ # is +true+, the filename is interpreted as a UTF-16 encoded string.
+ #
+ # By default, the new database will return result rows as arrays
+ # (#results_as_hash) and has type translation disabled (#type_translation=).
+ def initialize( file_name, options={} )
+ utf16 = options.fetch(:utf16, false)
+ load_driver( options[:driver] )
+
+ @statement_factory = options[:statement_factory] || Statement
+
+ result, @handle = @driver.open( file_name, utf16 )
+ Error.check( result, nil, "could not open database" )
+
+ @closed = false
+ @results_as_hash = options.fetch(:results_as_hash,false)
+ @type_translation = options.fetch(:type_translation,false)
+ @translator = nil
+ end
+
+ # Return +true+ if the string is a valid (ie, parsable) SQL statement, and
+ # +false+ otherwise. If +utf16+ is +true+, then the string is a UTF-16
+ # character string.
+ def complete?( string, utf16=false )
+ @driver.complete?( string, utf16 )
+ end
+
+ # Return a string describing the last error to have occurred with this
+ # database.
+ def errmsg( utf16=false )
+ @driver.errmsg( @handle, utf16 )
+ end
+
+ # Return an integer representing the last error to have occurred with this
+ # database.
+ def errcode
+ @driver.errcode( @handle )
+ end
+
+ # Return the type translator employed by this database instance. Each
+ # database instance has its own type translator; this allows for different
+ # type handlers to be installed in each instance without affecting other
+ # instances. Furthermore, the translators are instantiated lazily, so that
+ # if a database does not use type translation, it will not be burdened by
+ # the overhead of a useless type translator. (See the Translator class.)
+ def translator
+ @translator ||= Translator.new
+ end
+
+ # Closes this database.
+ def close
+ unless @closed
+ result = @driver.close( @handle )
+ Error.check( result, self )
+ end
+ @closed = true
+ end
+
+ # Returns +true+ if this database instance has been closed (see #close).
+ def closed?
+ @closed
+ end
+
+ # Installs (or removes) a block that will be invoked for every SQL
+ # statement executed. The block receives a two parameters: the +data+
+ # argument, and the SQL statement executed. If the block is +nil+,
+ # any existing tracer will be uninstalled.
+ def trace( data=nil, &block )
+ @driver.trace( @handle, data, &block )
+ end
+
+ # Installs (or removes) a block that will be invoked for every access
+ # to the database. If the block returns 0 (or +nil+), the statement
+ # is allowed to proceed. Returning 1 causes an authorization error to
+ # occur, and returning 2 causes the access to be silently denied.
+ def authorizer( data=nil, &block )
+ result = @driver.set_authorizer( @handle, data, &block )
+ Error.check( result, self )
+ end
+
+ # Returns a Statement object representing the given SQL. This does not
+ # execute the statement; it merely prepares the statement for execution.
+ def prepare( sql )
+ stmt = @statement_factory.new( self, sql )
+ if block_given?
+ begin
+ yield stmt
+ ensure
+ stmt.close
+ end
+ else
+ return stmt
+ end
+ end
+
+ # Executes the given SQL statement. If additional parameters are given,
+ # they are treated as bind variables, and are bound to the placeholders in
+ # the query.
+ #
+ # Note that if any of the values passed to this are hashes, then the
+ # key/value pairs are each bound separately, with the key being used as
+ # the name of the placeholder to bind the value to.
+ #
+ # The block is optional. If given, it will be invoked for each row returned
+ # by the query. Otherwise, any results are accumulated into an array and
+ # returned wholesale.
+ #
+ # See also #execute2, and #execute_batch for additional ways of
+ # executing statements.
+ def execute( sql, *bind_vars )
+ prepare( sql ) do |stmt|
+ result = stmt.execute( *bind_vars )
+ if block_given?
+ result.each { |row| yield row }
+ else
+ return result.inject( [] ) { |arr,row| arr << row; arr }
+ end
+ end
+ end
+
+ # Executes the given SQL statement, exactly as with #execute. However, the
+ # first row returned (either via the block, or in the returned array) is
+ # always the names of the columns. Subsequent rows correspond to the data
+ # from the result set.
+ #
+ # Thus, even if the query itself returns no rows, this method will always
+ # return at least one row--the names of the columns.
+ #
+ # See also #execute, and #execute_batch for additional ways of
+ # executing statements.
+ def execute2( sql, *bind_vars )
+ prepare( sql ) do |stmt|
+ result = stmt.execute( *bind_vars )
+ if block_given?
+ yield result.columns
+ result.each { |row| yield row }
+ else
+ return result.inject( [ result.columns ] ) { |arr,row|
+ arr << row; arr }
+ end
+ end
+ end
+
+ # Executes all SQL statements in the given string. By contrast, the other
+ # means of executing queries will only execute the first statement in the
+ # string, ignoring all subsequent statements. This will execute each one
+ # in turn. The same bind parameters, if given, will be applied to each
+ # statement.
+ #
+ # This always returns +nil+, making it unsuitable for queries that return
+ # rows.
+ def execute_batch( sql, *bind_vars )
+ sql = sql.strip
+ until sql.empty? do
+ prepare( sql ) do |stmt|
+ stmt.execute( *bind_vars )
+ sql = stmt.remainder.strip
+ end
+ end
+ nil
+ end
+
+ # A convenience method for obtaining the first row of a result set, and
+ # discarding all others. It is otherwise identical to #execute.
+ #
+ # See also #get_first_value.
+ def get_first_row( sql, *bind_vars )
+ execute( sql, *bind_vars ) { |row| return row }
+ nil
+ end
+
+ # A convenience method for obtaining the first value of the first row of a
+ # result set, and discarding all other values and rows. It is otherwise
+ # identical to #execute.
+ #
+ # See also #get_first_row.
+ def get_first_value( sql, *bind_vars )
+ execute( sql, *bind_vars ) { |row| return row[0] }
+ nil
+ end
+
+ # Obtains the unique row ID of the last row to be inserted by this Database
+ # instance.
+ def last_insert_row_id
+ @driver.last_insert_rowid( @handle )
+ end
+
+ # Returns the number of changes made to this database instance by the last
+ # operation performed. Note that a "delete from table" without a where
+ # clause will not affect this value.
+ def changes
+ @driver.changes( @handle )
+ end
+
+ # Returns the total number of changes made to this database instance
+ # since it was opened.
+ def total_changes
+ @driver.total_changes( @handle )
+ end
+
+ # Interrupts the currently executing operation, causing it to abort.
+ def interrupt
+ @driver.interrupt( @handle )
+ end
+
+ # Register a busy handler with this database instance. When a requested
+ # resource is busy, this handler will be invoked. If the handler returns
+ # +false+, the operation will be aborted; otherwise, the resource will
+ # be requested again.
+ #
+ # The handler will be invoked with the name of the resource that was
+ # busy, and the number of times it has been retried.
+ #
+ # See also #busy_timeout.
+ def busy_handler( data=nil, &block ) # :yields: data, retries
+ result = @driver.busy_handler( @handle, data, &block )
+ Error.check( result, self )
+ end
+
+ # Indicates that if a request for a resource terminates because that
+ # resource is busy, SQLite should wait for the indicated number of
+ # milliseconds before trying again. By default, SQLite does not retry
+ # busy resources. To restore the default behavior, send 0 as the
+ # +ms+ parameter.
+ #
+ # See also #busy_handler.
+ def busy_timeout( ms )
+ result = @driver.busy_timeout( @handle, ms )
+ Error.check( result, self )
+ end
+
+ # Creates a new function for use in SQL statements. It will be added as
+ # +name+, with the given +arity+. (For variable arity functions, use
+ # -1 for the arity.)
+ #
+ # The block should accept at least one parameter--the FunctionProxy
+ # instance that wraps this function invocation--and any other
+ # arguments it needs (up to its arity).
+ #
+ # The block does not return a value directly. Instead, it will invoke
+ # the FunctionProxy#set_result method on the +func+ parameter and
+ # indicate the return value that way.
+ #
+ # Example:
+ #
+ # db.create_function( "maim", 1 ) do |func, value|
+ # if value.nil?
+ # func.result = nil
+ # else
+ # func.result = value.split(//).sort.join
+ # end
+ # end
+ #
+ # puts db.get_first_value( "select maim(name) from table" )
+ def create_function( name, arity, text_rep=Constants::TextRep::ANY,
+ &block ) # :yields: func, *args
+ # begin
+ callback = proc do |func,*args|
+ begin
+ block.call( FunctionProxy.new( @driver, func ),
+ *args.map{|v| Value.new(self,v)} )
+ rescue StandardError, Exception => e
+ @driver.result_error( func,
+ "#{e.message} (#{e.class})", -1 )
+ end
+ end
+
+ result = @driver.create_function( @handle, name, arity, text_rep, nil,
+ callback, nil, nil )
+ Error.check( result, self )
+
+ self
+ end
+
+ # Creates a new aggregate function for use in SQL statements. Aggregate
+ # functions are functions that apply over every row in the result set,
+ # instead of over just a single row. (A very common aggregate function
+ # is the "count" function, for determining the number of rows that match
+ # a query.)
+ #
+ # The new function will be added as +name+, with the given +arity+. (For
+ # variable arity functions, use -1 for the arity.)
+ #
+ # The +step+ parameter must be a proc object that accepts as its first
+ # parameter a FunctionProxy instance (representing the function
+ # invocation), with any subsequent parameters (up to the function's arity).
+ # The +step+ callback will be invoked once for each row of the result set.
+ #
+ # The +finalize+ parameter must be a +proc+ object that accepts only a
+ # single parameter, the FunctionProxy instance representing the current
+ # function invocation. It should invoke FunctionProxy#set_result to
+ # store the result of the function.
+ #
+ # Example:
+ #
+ # db.create_aggregate( "lengths", 1 ) do
+ # step do |func, value|
+ # func[ :total ] ||= 0
+ # func[ :total ] += ( value ? value.length : 0 )
+ # end
+ #
+ # finalize do |func|
+ # func.set_result( func[ :total ] || 0 )
+ # end
+ # end
+ #
+ # puts db.get_first_value( "select lengths(name) from table" )
+ #
+ # See also #create_aggregate_handler for a more object-oriented approach to
+ # aggregate functions.
+ def create_aggregate( name, arity, step=nil, finalize=nil,
+ text_rep=Constants::TextRep::ANY, &block )
+ # begin
+ if block
+ proxy = AggregateDefinitionProxy.new
+ proxy.instance_eval &block
+ step ||= proxy.step_callback
+ finalize ||= proxy.finalize_callback
+ end
+
+ step_callback = proc do |func,*args|
+ ctx = @driver.aggregate_context( func )
+ unless ctx[:__error]
+ begin
+ step.call( FunctionProxy.new( @driver, func, ctx ),
+ *args.map{|v| Value.new(self,v)} )
+ rescue Exception => e
+ ctx[:__error] = e
+ end
+ end
+ end
+
+ finalize_callback = proc do |func|
+ ctx = @driver.aggregate_context( func )
+ unless ctx[:__error]
+ begin
+ finalize.call( FunctionProxy.new( @driver, func, ctx ) )
+ rescue Exception => e
+ @driver.result_error( func,
+ "#{e.message} (#{e.class})", -1 )
+ end
+ else
+ e = ctx[:__error]
+ @driver.result_error( func,
+ "#{e.message} (#{e.class})", -1 )
+ end
+ end
+
+ result = @driver.create_function( @handle, name, arity, text_rep, nil,
+ nil, step_callback, finalize_callback )
+ Error.check( result, self )
+
+ self
+ end
+
+ # This is another approach to creating an aggregate function (see
+ # #create_aggregate). Instead of explicitly specifying the name,
+ # callbacks, arity, and type, you specify a factory object
+ # (the "handler") that knows how to obtain all of that information. The
+ # handler should respond to the following messages:
+ #
+ # +arity+:: corresponds to the +arity+ parameter of #create_aggregate. This
+ # message is optional, and if the handler does not respond to it,
+ # the function will have an arity of -1.
+ # +name+:: this is the name of the function. The handler _must_ implement
+ # this message.
+ # +new+:: this must be implemented by the handler. It should return a new
+ # instance of the object that will handle a specific invocation of
+ # the function.
+ #
+ # The handler instance (the object returned by the +new+ message, described
+ # above), must respond to the following messages:
+ #
+ # +step+:: this is the method that will be called for each step of the
+ # aggregate function's evaluation. It should implement the same
+ # signature as the +step+ callback for #create_aggregate.
+ # +finalize+:: this is the method that will be called to finalize the
+ # aggregate function's evaluation. It should implement the
+ # same signature as the +finalize+ callback for
+ # #create_aggregate.
+ #
+ # Example:
+ #
+ # class LengthsAggregateHandler
+ # def self.arity; 1; end
+ #
+ # def initialize
+ # @total = 0
+ # end
+ #
+ # def step( ctx, name )
+ # @total += ( name ? name.length : 0 )
+ # end
+ #
+ # def finalize( ctx )
+ # ctx.set_result( @total )
+ # end
+ # end
+ #
+ # db.create_aggregate_handler( LengthsAggregateHandler )
+ # puts db.get_first_value( "select lengths(name) from A" )
+ def create_aggregate_handler( handler )
+ arity = -1
+ text_rep = Constants::TextRep::ANY
+
+ arity = handler.arity if handler.respond_to?(:arity)
+ text_rep = handler.text_rep if handler.respond_to?(:text_rep)
+ name = handler.name
+
+ step = proc do |func,*args|
+ ctx = @driver.aggregate_context( func )
+ unless ctx[ :__error ]
+ ctx[ :handler ] ||= handler.new
+ begin
+ ctx[ :handler ].step( FunctionProxy.new( @driver, func, ctx ),
+ *args.map{|v| Value.new(self,v)} )
+ rescue Exception, StandardError => e
+ ctx[ :__error ] = e
+ end
+ end
+ end
+
+ finalize = proc do |func|
+ ctx = @driver.aggregate_context( func )
+ unless ctx[ :__error ]
+ ctx[ :handler ] ||= handler.new
+ begin
+ ctx[ :handler ].finalize( FunctionProxy.new( @driver, func, ctx ) )
+ rescue Exception => e
+ ctx[ :__error ] = e
+ end
+ end
+
+ if ctx[ :__error ]
+ e = ctx[ :__error ]
+ @driver.sqlite3_result_error( func, "#{e.message} (#{e.class})", -1 )
+ end
+ end
+
+ result = @driver.create_function( @handle, name, arity, text_rep, nil,
+ nil, step, finalize )
+ Error.check( result, self )
+
+ self
+ end
+
+ # Begins a new transaction. Note that nested transactions are not allowed
+ # by SQLite, so attempting to nest a transaction will result in a runtime
+ # exception.
+ #
+ # The +mode+ parameter may be either <tt>:deferred</tt> (the default),
+ # <tt>:immediate</tt>, or <tt>:exclusive</tt>.
+ #
+ # If a block is given, the database instance is yielded to it, and the
+ # transaction is committed when the block terminates. If the block
+ # raises an exception, a rollback will be performed instead. Note that if
+ # a block is given, #commit and #rollback should never be called
+ # explicitly or you'll get an error when the block terminates.
+ #
+ # If a block is not given, it is the caller's responsibility to end the
+ # transaction explicitly, either by calling #commit, or by calling
+ # #rollback.
+ def transaction( mode = :deferred )
+ execute "begin #{mode.to_s} transaction"
+ @transaction_active = true
+
+ if block_given?
+ abort = false
+ begin
+ yield self
+ rescue Exception
+ abort = true
+ raise
+ ensure
+ abort and rollback or commit
+ end
+ end
+
+ true
+ end
+
+ # Commits the current transaction. If there is no current transaction,
+ # this will cause an error to be raised. This returns +true+, in order
+ # to allow it to be used in idioms like
+ # <tt>abort? and rollback or commit</tt>.
+ def commit
+ execute "commit transaction"
+ @transaction_active = false
+ true
+ end
+
+ # Rolls the current transaction back. If there is no current transaction,
+ # this will cause an error to be raised. This returns +true+, in order
+ # to allow it to be used in idioms like
+ # <tt>abort? and rollback or commit</tt>.
+ def rollback
+ execute "rollback transaction"
+ @transaction_active = false
+ true
+ end
+
+ # Returns +true+ if there is a transaction active, and +false+ otherwise.
+ def transaction_active?
+ @transaction_active
+ end
+
+ # Loads the corresponding driver, or if it is nil, attempts to locate a
+ # suitable driver.
+ def load_driver( driver )
+ case driver
+ when Class
+ # do nothing--use what was given
+ when Symbol, String
+ require "sqlite3/driver/#{driver.to_s.downcase}/driver"
+ driver = SQLite3::Driver.const_get( driver )::Driver
+ else
+ [ "Native", "DL" ].each do |d|
+ begin
+ require "sqlite3/driver/#{d.downcase}/driver"
+ driver = SQLite3::Driver.const_get( d )::Driver
+ break
+ rescue SyntaxError
+ raise
+ rescue ScriptError, Exception
+ end
+ end
+ raise "no driver for sqlite3 found" unless driver
+ end
+
+ @driver = driver.new
+ end
+ private :load_driver
+
+ # A helper class for dealing with custom functions (see #create_function,
+ # #create_aggregate, and #create_aggregate_handler). It encapsulates the
+ # opaque function object that represents the current invocation. It also
+ # provides more convenient access to the API functions that operate on
+ # the function object.
+ #
+ # This class will almost _always_ be instantiated indirectly, by working
+ # with the create methods mentioned above.
+ class FunctionProxy
+
+ # Create a new FunctionProxy that encapsulates the given +func+ object.
+ # If context is non-nil, the functions context will be set to that. If
+ # it is non-nil, it must quack like a Hash. If it is nil, then none of
+ # the context functions will be available.
+ def initialize( driver, func, context=nil )
+ @driver = driver
+ @func = func
+ @context = context
+ end
+
+ # Calls #set_result to set the result of this function.
+ def result=( result )
+ set_result( result )
+ end
+
+ # Set the result of the function to the given value. The function will
+ # then return this value.
+ def set_result( result, utf16=false )
+ @driver.result_text( @func, result, utf16 )
+ end
+
+ # Set the result of the function to the given error message.
+ # The function will then return that error.
+ def set_error( error )
+ @driver.result_error( @func, error.to_s, -1 )
+ end
+
+ # (Only available to aggregate functions.) Returns the number of rows
+ # that the aggregate has processed so far. This will include the current
+ # row, and so will always return at least 1.
+ def count
+ ensure_aggregate!
+ @driver.aggregate_count( @func )
+ end
+
+ # Returns the value with the given key from the context. This is only
+ # available to aggregate functions.
+ def []( key )
+ ensure_aggregate!
+ @context[ key ]
+ end
+
+ # Sets the value with the given key in the context. This is only
+ # available to aggregate functions.
+ def []=( key, value )
+ ensure_aggregate!
+ @context[ key ] = value
+ end
+
+ # A function for performing a sanity check, to ensure that the function
+ # being invoked is an aggregate function. This is implied by the
+ # existence of the context variable.
+ def ensure_aggregate!
+ unless @context
+ raise MisuseException, "function is not an aggregate"
+ end
+ end
+ private :ensure_aggregate!
+
+ end
+
+ # A proxy used for defining the callbacks to an aggregate function.
+ class AggregateDefinitionProxy # :nodoc:
+ attr_reader :step_callback, :finalize_callback
+
+ def step( &block )
+ @step_callback = block
+ end
+
+ def finalize( &block )
+ @finalize_callback = block
+ end
+ end
+
+ end
+
+end
+
diff --git a/lib/sqlite3/driver/dl/api.rb b/lib/sqlite3/driver/dl/api.rb
new file mode 100644
index 0000000..f892013
--- /dev/null
+++ b/lib/sqlite3/driver/dl/api.rb
@@ -0,0 +1,184 @@
+#--
+# =============================================================================
+# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * The names of its contributors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# =============================================================================
+#++
+
+require 'dl/import'
+
+module SQLite3 ; module Driver; module DL;
+
+ module API
+ extend ::DL::Importable
+
+ library_name = case RUBY_PLATFORM.downcase
+ when /darwin/
+ "libsqlite3.dylib"
+ when /linux/
+ "libsqlite3.so"
+ when /win32/
+ "sqlite3.dll"
+ else
+ abort <<-EOF
+== * UNSUPPORTED PLATFORM ======================================================
+The platform '#{RUBY_PLATFORM}' is unsupported. Please help the author by
+editing the following file to allow your sqlite3 library to be found, and
+submitting a patch to jamis_buck@byu.edu. Thanks!
+
+#{__FILE__}
+=========================================================================== * ==
+ EOF
+ end
+
+ if defined? SQLITE3_LIB_PATH
+ library_name = File.join( SQLITE3_LIB_PATH, library_name )
+ end
+
+ dlload library_name
+
+ typealias "db", "void*"
+ typealias "stmt", "void*"
+ typealias "value", "void*"
+ typealias "context", "void*"
+
+ # until Ruby/DL supports 64-bit ints, we'll just treat them as 32-bit ints
+ typealias "int64", "unsigned long"
+
+ extern "const char *sqlite3_libversion()"
+
+ extern "int sqlite3_open(const char*,db*)"
+ extern "int sqlite3_open16(const void*,db*)"
+ extern "int sqlite3_close(db)"
+ extern "const char* sqlite3_errmsg(db)"
+ extern "void* sqlite3_errmsg16(db)"
+ extern "int sqlite3_errcode(db)"
+
+ extern "int sqlite3_prepare(db,const char*,int,stmt*,const char**)"
+ extern "int sqlite3_prepare16(db,const void*,int,stmt*,const void**)"
+ extern "int sqlite3_finalize(stmt)"
+ extern "int sqlite3_reset(stmt)"
+ extern "int sqlite3_step(stmt)"
+
+ extern "int64 sqlite3_last_insert_rowid(db)"
+ extern "int sqlite3_changes(db)"
+ extern "int sqlite3_total_changes(db)"
+ extern "void sqlite3_interrupt(db)"
+ extern "ibool sqlite3_complete(const char*)"
+ extern "ibool sqlite3_complete16(const void*)"
+
+ extern "int sqlite3_busy_handler(db,void*,void*)"
+ extern "int sqlite3_busy_timeout(db,int)"
+
+ extern "int sqlite3_set_authorizer(db,void*,void*)"
+ extern "void* sqlite3_trace(db,void*,void*)"
+
+ extern "int sqlite3_bind_blob(stmt,int,const void*,int,void*)"
+ extern "int sqlite3_bind_double(stmt,int,double)"
+ extern "int sqlite3_bind_int(stmt,int,int)"
+ extern "int sqlite3_bind_int64(stmt,int,int64)"
+ extern "int sqlite3_bind_null(stmt,int)"
+ extern "int sqlite3_bind_text(stmt,int,const char*,int,void*)"
+ extern "int sqlite3_bind_text16(stmt,int,const void*,int,void*)"
+ #extern "int sqlite3_bind_value(stmt,int,value)"
+
+ extern "int sqlite3_bind_parameter_count(stmt)"
+ extern "const char* sqlite3_bind_parameter_name(stmt,int)"
+ extern "int sqlite3_bind_parameter_index(stmt,const char*)"
+
+ extern "int sqlite3_column_count(stmt)"
+ extern "int sqlite3_data_count(stmt)"
+
+ extern "const void *sqlite3_column_blob(stmt,int)"
+ extern "int sqlite3_column_bytes(stmt,int)"
+ extern "int sqlite3_column_bytes16(stmt,int)"
+ extern "const char *sqlite3_column_decltype(stmt,int)"
+ extern "void *sqlite3_column_decltype16(stmt,int)"
+ extern "double sqlite3_column_double(stmt,int)"
+ extern "int sqlite3_column_int(stmt,int)"
+ extern "int64 sqlite3_column_int64(stmt,int)"
+ extern "const char *sqlite3_column_name(stmt,int)"
+ extern "const void *sqlite3_column_name16(stmt,int)"
+ extern "const char *sqlite3_column_text(stmt,int)"
+ extern "const void *sqlite3_column_text16(stmt,int)"
+ extern "int sqlite3_column_type(stmt,int)"
+
+ extern "int sqlite3_create_function(db,const char*,int,int,void*,void*,void*,void*)"
+ extern "int sqlite3_create_function16(db,const void*,int,int,void*,void*,void*,void*)"
+ extern "int sqlite3_aggregate_count(context)"
+
+ extern "const void *sqlite3_value_blob(value)"
+ extern "int sqlite3_value_bytes(value)"
+ extern "int sqlite3_value_bytes16(value)"
+ extern "double sqlite3_value_double(value)"
+ extern "int sqlite3_value_int(value)"
+ extern "int64 sqlite3_value_int64(value)"
+ extern "const char* sqlite3_value_text(value)"
+ extern "const void* sqlite3_value_text16(value)"
+ extern "const void* sqlite3_value_text16le(value)"
+ extern "const void* sqlite3_value_text16be(value)"
+ extern "int sqlite3_value_type(value)"
+
+ extern "void *sqlite3_aggregate_context(context,int)"
+ extern "void *sqlite3_user_data(context)"
+ extern "void *sqlite3_get_auxdata(context,int)"
+ extern "void sqlite3_set_auxdata(context,int,void*,void*)"
+
+ extern "void sqlite3_result_blob(context,const void*,int,void*)"
+ extern "void sqlite3_result_double(context,double)"
+ extern "void sqlite3_result_error(context,const char*,int)"
+ extern "void sqlite3_result_error16(context,const void*,int)"
+ extern "void sqlite3_result_int(context,int)"
+ extern "void sqlite3_result_int64(context,int64)"
+ extern "void sqlite3_result_null(context)"
+ extern "void sqlite3_result_text(context,const char*,int,void*)"
+ extern "void sqlite3_result_text16(context,const void*,int,void*)"
+ extern "void sqlite3_result_text16le(context,const void*,int,void*)"
+ extern "void sqlite3_result_text16be(context,const void*,int,void*)"
+ extern "void sqlite3_result_value(context,value)"
+
+ extern "int sqlite3_create_collation(db,const char*,int,void*,void*)"
+ extern "int sqlite3_create_collation16(db,const char*,int,void*,void*)"
+ extern "int sqlite3_collation_needed(db,void*,void*)"
+ extern "int sqlite3_collation_needed16(db,void*,void*)"
+
+ # ==== CRYPTO (NOT IN PUBLIC RELEASE) ====
+ if defined?( CRYPTO_API ) && CRYPTO_API
+ extern "int sqlite3_key(db,void*,int)"
+ extern "int sqlite3_rekey(db,void*,int)"
+ end
+
+ # ==== EXPERIMENTAL ====
+ if defined?( EXPERIMENTAL_API ) && EXPERIMENTAL_API
+ extern "int sqlite3_progress_handler(db,int,void*,void*)"
+ extern "int sqlite3_commit_hook(db,void*,void*)"
+ end
+
+ end
+
+end ; end ; end
diff --git a/lib/sqlite3/driver/dl/driver.rb b/lib/sqlite3/driver/dl/driver.rb
new file mode 100644
index 0000000..ab24a13
--- /dev/null
+++ b/lib/sqlite3/driver/dl/driver.rb
@@ -0,0 +1,334 @@
+#--
+# =============================================================================
+# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * The names of its contributors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# =============================================================================
+#++
+
+require 'sqlite3/driver/dl/api'
+
+module Kernel
+ # Allows arbitrary objects to be passed as a pointer to functions.
+ # (Probably not very GC safe, but by encapsulating it like this we
+ # can change the implementation later.)
+ def to_ptr
+ ptr = DL.malloc(DL.sizeof("L"))
+ ptr.set_object self
+ ptr
+ end
+end
+
+class DL::PtrData
+ # The inverse of the Kernel#to_ptr operation.
+ def to_object
+ n = to_s(4).unpack("L").first
+ return nil if n < 1
+ ObjectSpace._id2ref(n) rescue self.to_s
+ end
+
+ def set_object(obj)
+ self[0] = [obj.object_id].pack("L")
+ end
+end
+
+module SQLite3 ; module Driver ; module DL
+
+ class Driver
+ STATIC = ::DL::PtrData.new(0)
+ TRANSIENT = ::DL::PtrData.new(-1)
+
+ def open( filename, utf16=false )
+ handle = ::DL::PtrData.new(0)
+ result = API.send( ( utf16 ? :sqlite3_open16 : :sqlite3_open ),
+ filename+"\0", handle.ref )
+ [ result, handle ]
+ end
+
+ def errmsg( db, utf16=false )
+ if utf16
+ msg = API.sqlite3_errmsg16( db )
+ msg.free = nil
+ msg.to_s(utf16_length(msg))
+ else
+ API.sqlite3_errmsg( db )
+ end
+ end
+
+ def prepare( db, sql, utf16=false )
+ handle = ::DL::PtrData.new(0)
+ remainder = ::DL::PtrData.new(0)
+
+ result = API.send( ( utf16 ? :sqlite3_prepare16 : :sqlite3_prepare ),
+ db, sql+"\0", sql.length, handle.ref, remainder.ref )
+
+ args = utf16 ? [ utf16_length(remainder) ] : []
+ remainder = remainder.to_s( *args )
+
+ [ result, handle, remainder ]
+ end
+
+ def complete?( sql, utf16=false )
+ API.send( utf16 ? :sqlite3_complete16 : :sqlite3_complete, sql+"\0" )
+ end
+
+ def value_blob( value )
+ blob = API.sqlite3_value_blob( value )
+ blob.free = nil
+ blob.to_s( API.sqlite3_value_bytes( value ) )
+ end
+
+ def value_text( value, utf16=false )
+ method = case utf16
+ when nil, false then :sqlite3_value_text
+ when :le then :sqlite3_value_text16le
+ when :be then :sqlite3_value_text16be
+ else :sqlite3_value_text16
+ end
+
+ result = API.send( method, value )
+ if utf16
+ result.free = nil
+ size = API.sqlite3_value_bytes( value )
+ result = result.to_s( size )
+ end
+
+ result
+ end
+
+ def column_blob( stmt, column )
+ blob = API.sqlite3_column_blob( stmt, column )
+ blob.free = nil
+ blob.to_s( API.sqlite3_column_bytes( stmt, column ) )
+ end
+
+ def result_text( func, text, utf16=false )
+ method = case utf16
+ when false, nil then :sqlite3_result_text
+ when :le then :sqlite3_result_text16le
+ when :be then :sqlite3_result_text16be
+ else :sqlite3_result_text16
+ end
+
+ s = text.to_s
+ API.send( method, func, s, s.length, TRANSIENT )
+ end
+
+ def busy_handler( db, data=nil, &block )
+ @busy_handler = block
+
+ unless @busy_handler_callback
+ @busy_handler_callback = ::DL.callback( "IPI" ) do |cookie, timeout|
+ @busy_handler.call( cookie, timeout ) || 0
+ end
+ end
+
+ API.sqlite3_busy_handler( db, block&&@busy_handler_callback, data )
+ end
+
+ def set_authorizer( db, data=nil, &block )
+ @authorizer_handler = block
+
+ unless @authorizer_handler_callback
+ @authorizer_handler_callback = ::DL.callback( "IPIPPPP"
+ ) do |cookie,mode,a,b,c,d|
+ @authorizer_handler.call( cookie, mode,
+ a&&a.to_s, b&&b.to_s, c&&c.to_s, d&&d.to_s ) || 0
+ end
+ end
+
+ API.sqlite3_set_authorizer( db, block&&@authorizer_handler_callback,
+ data )
+ end
+
+ def trace( db, data=nil, &block )
+ @trace_handler = block
+
+ unless @trace_handler_callback
+ @trace_handler_callback = ::DL.callback( "IPS" ) do |cookie,sql|
+ @trace_handler.call( cookie ? cookie.to_object : nil, sql ) || 0
+ end
+ end
+
+ API.sqlite3_trace( db, block&&@trace_handler_callback, data )
+ end
+
+ def create_function( db, name, args, text, cookie,
+ func, step, final )
+ # begin
+ if @func_handler_callback.nil? && func
+ @func_handler_callback = ::DL.callback( "0PIP" ) do |context,nargs,args|
+ args = args.to_s(nargs*4).unpack("L*").map {|i| ::DL::PtrData.new(i)}
+ data = API.sqlite3_user_data( context ).to_object
+ data[:func].call( context, *args )
+ end
+ end
+
+ if @step_handler_callback.nil? && step
+ @step_handler_callback = ::DL.callback( "0PIP" ) do |context,nargs,args|
+ args = args.to_s(nargs*4).unpack("L*").map {|i| ::DL::PtrData.new(i)}
+ data = API.sqlite3_user_data( context ).to_object
+ data[:step].call( context, *args )
+ end
+ end
+
+ if @final_handler_callback.nil? && final
+ @final_handler_callback = ::DL.callback( "0P" ) do |context|
+ data = API.sqlite3_user_data( context ).to_object
+ data[:final].call( context )
+ end
+ end
+
+ data = { :cookie => cookie,
+ :name => name,
+ :func => func,
+ :step => step,
+ :final => final }
+
+ API.sqlite3_create_function( db, name, args, text, data,
+ ( func ? @func_handler_callback : nil ),
+ ( step ? @step_handler_callback : nil ),
+ ( final ? @final_handler_callback : nil ) )
+ end
+
+ def aggregate_context( context )
+ ptr = API.sqlite3_aggregate_context( context, 4 )
+ ptr.free = nil
+ obj = ( ptr ? ptr.to_object : nil )
+ if obj.nil?
+ obj = Hash.new
+ ptr.set_object obj
+ end
+ obj
+ end
+
+ def bind_blob( stmt, index, value )
+ s = value.to_s
+ API.sqlite3_bind_blob( stmt, index, s, s.length, TRANSIENT )
+ end
+
+ def bind_text( stmt, index, value, utf16=false )
+ s = value.to_s
+ method = ( utf16 ? :sqlite3_bind_text16 : :sqlite3_bind_text )
+ API.send( method, stmt, index, s, s.length, TRANSIENT )
+ end
+
+ def column_text( stmt, column )
+ result = API.sqlite3_column_text( stmt, column )
+ result ? result.to_s : nil
+ end
+
+ def column_name( stmt, column )
+ result = API.sqlite3_column_name( stmt, column )
+ result ? result.to_s : nil
+ end
+
+ def column_decltype( stmt, column )
+ result = API.sqlite3_column_decltype( stmt, column )
+ result ? result.to_s : nil
+ end
+
+ def self.api_delegate( name )
+ define_method( name ) { |*args| API.send( "sqlite3_#{name}", *args ) }
+ end
+
+ api_delegate :aggregate_count
+ api_delegate :bind_double
+ api_delegate :bind_int
+ api_delegate :bind_null
+ api_delegate :bind_parameter_index
+ api_delegate :bind_parameter_name
+ api_delegate :busy_timeout
+ api_delegate :changes
+ api_delegate :close
+ api_delegate :column_bytes
+ api_delegate :column_bytes16
+ api_delegate :column_count
+ api_delegate :column_double
+ api_delegate :column_int
+ api_delegate :column_int64
+ api_delegate :column_type
+ api_delegate :data_count
+ api_delegate :errcode
+ api_delegate :finalize
+ api_delegate :interrupt
+ api_delegate :last_insert_rowid
+ api_delegate :libversion
+ api_delegate :reset
+ api_delegate :result_error
+ api_delegate :step
+ api_delegate :total_changes
+ api_delegate :value_bytes
+ api_delegate :value_bytes16
+ api_delegate :value_double
+ api_delegate :value_int
+ api_delegate :value_int64
+ api_delegate :value_type
+
+ # ==== EXPERIMENTAL ====
+ if defined?( EXPERIMENTAL_API ) && EXPERIMENTAL_API
+ def progress_handler( db, n, data=nil, &block )
+ @progress_handler = block
+
+ unless @progress_handler_callback
+ @progress_handler_callback = ::DL.callback( "IP" ) do |cookie|
+ @progress_handler.call( cookie )
+ end
+ end
+
+ API.sqlite3_progress_handler( db, n, block&&@progress_handler_callback,
+ data )
+ end
+
+ def commit_hook( db, data=nil, &block )
+ @commit_hook_handler = block
+
+ unless @commit_hook_handler_callback
+ @commit_hook_handler_callback = ::DL.callback( "IP" ) do |cookie|
+ @commit_hook_handler.call( cookie )
+ end
+ end
+
+ API.sqlite3_commit_hook( db, block&&@commit_hook_handler_callback,
+ data )
+ end
+ end
+
+ private
+
+ def utf16_length(ptr)
+ len = 0
+ loop do
+ break if ptr[len,1] == "\0"
+ len += 2
+ end
+ len
+ end
+
+ end
+
+end ; end ; end
diff --git a/lib/sqlite3/driver/native/driver.rb b/lib/sqlite3/driver/native/driver.rb
new file mode 100644
index 0000000..ee5e4a7
--- /dev/null
+++ b/lib/sqlite3/driver/native/driver.rb
@@ -0,0 +1,218 @@
+#--
+# =============================================================================
+# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * The names of its contributors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# =============================================================================
+#++
+
+require 'sqlite3_api'
+
+module SQLite3 ; module Driver ; module Native
+
+ class Driver
+
+ def initialize
+ @callback_data = Hash.new
+ end
+
+ def complete?( sql, utf16=false )
+ API.send( utf16 ? :sqlite3_complete16 : :sqlite3_complete, sql ) != 0
+ end
+
+ def busy_handler( db, data=nil, &block )
+ if block
+ cb = API::CallbackData.new
+ cb.proc = block
+ cb.data = data
+ end
+
+ API.sqlite3_busy_handler( db,
+ block ? API::Sqlite3_ruby_busy_handler : nil, cb )
+ end
+
+ def set_authorizer( db, data=nil, &block )
+ if block
+ cb = API::CallbackData.new
+ cb.proc = block
+ cb.data = data
+ end
+
+ API.sqlite3_set_authorizer( db,
+ block ? API::Sqlite3_ruby_authorizer : nil, cb )
+ end
+
+ def trace( db, data=nil, &block )
+ if block
+ cb = API::CallbackData.new
+ cb.proc = block
+ cb.data = data
+ end
+
+ API.sqlite3_trace( db,
+ block ? API::Sqlite3_ruby_trace : nil, cb )
+ end
+
+ def open( filename, utf16=false )
+ API.send( utf16 ? :sqlite3_open16 : :sqlite3_open, filename )
+ end
+
+ def errmsg( db, utf16=false )
+ API.send( utf16 ? :sqlite3_errmsg16 : :sqlite3_errmsg, db )
+ end
+
+ def prepare( db, sql, utf16=false )
+ API.send( ( utf16 ? :sqlite3_prepare16 : :sqlite3_prepare ),
+ db, sql )
+ end
+
+ def bind_text( stmt, index, value, utf16=false )
+ API.send( ( utf16 ? :sqlite3_bind_text16 : :sqlite3_bind_text ),
+ stmt, index, value )
+ end
+
+ def column_name( stmt, index, utf16=false )
+ API.send( ( utf16 ? :sqlite3_column_name16 : :sqlite3_column_name ),
+ stmt, index )
+ end
+
+ def column_decltype( stmt, index, utf16=false )
+ API.send(
+ ( utf16 ? :sqlite3_column_decltype16 : :sqlite3_column_decltype ),
+ stmt, index )
+ end
+
+ def column_text( stmt, index, utf16=false )
+ API.send( ( utf16 ? :sqlite3_column_text16 : :sqlite3_column_text ),
+ stmt, index )
+ end
+
+ def create_function( db, name, args, text, cookie, func, step, final )
+ if func || ( step && final )
+ cb = API::CallbackData.new
+ cb.proc = cb.proc2 = nil
+ cb.data = cookie
+ @callback_data[ name ] = cb
+ else
+ @callback_data.delete( name )
+ end
+
+ if func
+ cb.proc = func
+
+ func = API::Sqlite3_ruby_function_step
+ step = final = nil
+ elsif step && final
+ cb.proc = step
+ cb.proc2 = final
+
+ func = nil
+ step = API::Sqlite3_ruby_function_step
+ final = API::Sqlite3_ruby_function_final
+ end
+
+ API.sqlite3_create_function( db, name, args, text, cb, func, step, final )
+ end
+
+ def value_text( value, utf16=false )
+ method = case utf16
+ when nil, false then :sqlite3_value_text
+ when :le then :sqlite3_value_text16le
+ when :be then :sqlite3_value_text16be
+ else :sqlite3_value_text16
+ end
+
+ API.send( method, value )
+ end
+
+ def result_text( context, result, utf16=false )
+ method = case utf16
+ when nil, false then :sqlite3_result_text
+ when :le then :sqlite3_result_text16le
+ when :be then :sqlite3_result_text16be
+ else :sqlite3_result_text16
+ end
+
+ API.send( method, context, result.to_s )
+ end
+
+ def result_error( context, value, utf16=false )
+ API.send( ( utf16 ? :sqlite3_result_error16 : :sqlite3_result_error ),
+ context, value )
+ end
+
+ def self.api_delegate( name )
+ define_method( name ) { |*args| API.send( "sqlite3_#{name}", *args ) }
+ end
+
+ api_delegate :libversion
+ api_delegate :close
+ api_delegate :last_insert_rowid
+ api_delegate :changes
+ api_delegate :total_changes
+ api_delegate :interrupt
+ api_delegate :busy_timeout
+ api_delegate :errcode
+ api_delegate :bind_blob
+ api_delegate :bind_double
+ api_delegate :bind_int
+ api_delegate :bind_int64
+ api_delegate :bind_null
+ api_delegate :bind_parameter_count
+ api_delegate :bind_parameter_name
+ api_delegate :bind_parameter_index
+ api_delegate :column_count
+ api_delegate :step
+ api_delegate :data_count
+ api_delegate :column_blob
+ api_delegate :column_bytes
+ api_delegate :column_bytes16
+ api_delegate :column_double
+ api_delegate :column_int
+ api_delegate :column_int64
+ api_delegate :column_type
+ api_delegate :finalize
+ api_delegate :reset
+ api_delegate :aggregate_count
+ api_delegate :value_blob
+ api_delegate :value_bytes
+ api_delegate :value_bytes16
+ api_delegate :value_double
+ api_delegate :value_int
+ api_delegate :value_int64
+ api_delegate :value_type
+ api_delegate :result_blob
+ api_delegate :result_double
+ api_delegate :result_int
+ api_delegate :result_int64
+ api_delegate :result_null
+ api_delegate :result_value
+ api_delegate :aggregate_context
+
+ end
+
+end ; end ; end
diff --git a/lib/sqlite3/errors.rb b/lib/sqlite3/errors.rb
new file mode 100644
index 0000000..308a4c9
--- /dev/null
+++ b/lib/sqlite3/errors.rb
@@ -0,0 +1,84 @@
+#--
+# =============================================================================
+# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * The names of its contributors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# =============================================================================
+#++
+
+require 'sqlite3/constants'
+
+module SQLite3
+
+ class Exception < ::Exception; end
+
+ class SQLException < Exception; end
+ class InternalException < Exception; end
+ class PermissionException < Exception; end
+ class AbortException < Exception; end
+ class BusyException < Exception; end
+ class LockedException < Exception; end
+ class MemoryException < Exception; end
+ class ReadOnlyException < Exception; end
+ class InterruptException < Exception; end
+ class IOException < Exception; end
+ class CorruptException < Exception; end
+ class NotFoundException < Exception; end
+ class FullException < Exception; end
+ class CantOpenException < Exception; end
+ class ProtocolException < Exception; end
+ class EmptyException < Exception; end
+ class SchemaChangedException < Exception; end
+ class TooBigException < Exception; end
+ class ConstraintException < Exception; end
+ class MismatchException < Exception; end
+ class MisuseException < Exception; end
+ class UnsupportedException < Exception; end
+ class AuthorizationException < Exception; end
+
+ EXCEPTIONS = [
+ nil,
+ SQLException, InternalException, PermissionException,
+ AbortException, BusyException, LockedException, MemoryException,
+ ReadOnlyException, InterruptException, IOException, CorruptException,
+ NotFoundException, FullException, CantOpenException, ProtocolException,
+ EmptyException, SchemaChangedException, TooBigException,
+ ConstraintException, MismatchException, MisuseException,
+ UnsupportedException, AuthorizationException
+ ]
+
+ module Error
+ def check( result, db=nil, msg=nil )
+ unless result == Constants::ErrorCode::OK
+ msg = ( msg ? msg + ": " : "" ) + db.errmsg if db
+ raise EXCEPTIONS[result], msg
+ end
+ end
+ module_function :check
+ end
+
+end
diff --git a/lib/sqlite3/pragmas.rb b/lib/sqlite3/pragmas.rb
new file mode 100644
index 0000000..7247387
--- /dev/null
+++ b/lib/sqlite3/pragmas.rb
@@ -0,0 +1,254 @@
+#--
+# =============================================================================
+# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * The names of its contributors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# =============================================================================
+#++
+
+require 'sqlite3/errors'
+
+module SQLite3
+
+ # This module is intended for inclusion solely by the Database class. It
+ # defines convenience methods for the various pragmas supported by SQLite3.
+ #
+ # For a detailed description of these pragmas, see the SQLite3 documentation
+ # at http://sqlite.org/pragma.html.
+ module Pragmas
+
+ # Returns +true+ or +false+ depending on the value of the named pragma.
+ def get_boolean_pragma( name )
+ get_first_value( "PRAGMA #{name}" ) != "0"
+ end
+ private :get_boolean_pragma
+
+ # Sets the given pragma to the given boolean value. The value itself
+ # may be +true+ or +false+, or any other commonly used string or
+ # integer that represents truth.
+ def set_boolean_pragma( name, mode )
+ case mode
+ when String
+ case mode.downcase
+ when "on", "yes", "true", "y", "t": mode = "'ON'"
+ when "off", "no", "false", "n", "f": mode = "'OFF'"
+ else
+ raise Exception,
+ "unrecognized pragma parameter #{mode.inspect}"
+ end
+ when true, 1
+ mode = "ON"
+ when false, 0, nil
+ mode = "OFF"
+ else
+ raise Exception,
+ "unrecognized pragma parameter #{mode.inspect}"
+ end
+
+ execute( "PRAGMA #{name}=#{mode}" )
+ end
+ private :set_boolean_pragma
+
+ # Requests the given pragma (and parameters), and if the block is given,
+ # each row of the result set will be yielded to it. Otherwise, the results
+ # are returned as an array.
+ def get_query_pragma( name, *parms, &block ) # :yields: row
+ if parms.empty?
+ execute( "PRAGMA #{name}", &block )
+ else
+ args = "'" + parms.join("','") + "'"
+ execute( "PRAGMA #{name}( #{args} )", &block )
+ end
+ end
+ private :get_query_pragma
+
+ # Return the value of the given pragma.
+ def get_enum_pragma( name )
+ get_first_value( "PRAGMA #{name}" )
+ end
+ private :get_enum_pragma
+
+ # Set the value of the given pragma to +mode+. The +mode+ parameter must
+ # conform to one of the values in the given +enum+ array. Each entry in
+ # the array is another array comprised of elements in the enumeration that
+ # have duplicate values. See #synchronous, #default_synchronous,
+ # #temp_store, and #default_temp_store for usage examples.
+ def set_enum_pragma( name, mode, enums )
+ match = enums.find { |p| p.find { |i| i.to_s.downcase == mode.to_s.downcase } }
+ raise Exception,
+ "unrecognized #{name} #{mode.inspect}" unless match
+ execute( "PRAGMA #{name}='#{match.first.upcase}'" )
+ end
+ private :set_enum_pragma
+
+ # Returns the value of the given pragma as an integer.
+ def get_int_pragma( name )
+ get_first_value( "PRAGMA #{name}" ).to_i
+ end
+ private :get_int_pragma
+
+ # Set the value of the given pragma to the integer value of the +value+
+ # parameter.
+ def set_int_pragma( name, value )
+ execute( "PRAGMA #{name}=#{value.to_i}" )
+ end
+ private :set_int_pragma
+
+ # The enumeration of valid synchronous modes.
+ SYNCHRONOUS_MODES = [ [ 'full', 2 ], [ 'normal', 1 ], [ 'off', 0 ] ]
+
+ # The enumeration of valid temp store modes.
+ TEMP_STORE_MODES = [ [ 'default', 0 ], [ 'file', 1 ], [ 'memory', 2 ] ]
+
+ # Does an integrity check on the database. If the check fails, a
+ # SQLite3::Exception will be raised. Otherwise it
+ # returns silently.
+ def integrity_check
+ execute( "PRAGMA integrity_check" ) do |row|
+ raise Exception, row[0] if row[0] != "ok"
+ end
+ end
+
+ def auto_vacuum
+ get_boolean_pragma "auto_vacuum"
+ end
+
+ def auto_vacuum=( mode )
+ set_boolean_pragma "auto_vacuum", mode
+ end
+
+ def schema_cookie
+ get_int_pragma "schema_cookie"
+ end
+
+ def schema_cookie=( cookie )
+ set_int_pragma "schema_cookie", cookie
+ end
+
+ def user_cookie
+ get_int_pragma "user_cookie"
+ end
+
+ def user_cookie=( cookie )
+ set_int_pragma "user_cookie", cookie
+ end
+
+ def cache_size
+ get_int_pragma "cache_size"
+ end
+
+ def cache_size=( size )
+ set_int_pragma "cache_size", size
+ end
+
+ def default_cache_size
+ get_int_pragma "default_cache_size"
+ end
+
+ def default_cache_size=( size )
+ set_int_pragma "default_cache_size", size
+ end
+
+ def default_synchronous
+ get_enum_pragma "default_synchronous"
+ end
+
+ def default_synchronous=( mode )
+ set_enum_pragma "default_synchronous", mode, SYNCHRONOUS_MODES
+ end
+
+ def synchronous
+ get_enum_pragma "synchronous"
+ end
+
+ def synchronous=( mode )
+ set_enum_pragma "synchronous", mode, SYNCHRONOUS_MODES
+ end
+
+ def default_temp_store
+ get_enum_pragma "default_temp_store"
+ end
+
+ def default_temp_store=( mode )
+ set_enum_pragma "default_temp_store", mode, TEMP_STORE_MODES
+ end
+
+ def temp_store
+ get_enum_pragma "temp_store"
+ end
+
+ def temp_store=( mode )
+ set_enum_pragma "temp_store", mode, TEMP_STORE_MODES
+ end
+
+ def full_column_names
+ get_boolean_pragma "full_column_names"
+ end
+
+ def full_column_names=( mode )
+ set_boolean_pragma "full_column_names", mode
+ end
+
+ def parser_trace
+ get_boolean_pragma "parser_trace"
+ end
+
+ def parser_trace=( mode )
+ set_boolean_pragma "parser_trace", mode
+ end
+
+ def vdbe_trace
+ get_boolean_pragma "vdbe_trace"
+ end
+
+ def vdbe_trace=( mode )
+ set_boolean_pragma "vdbe_trace", mode
+ end
+
+ def database_list( &block ) # :yields: row
+ get_query_pragma "database_list", &block
+ end
+
+ def foreign_key_list( table, &block ) # :yields: row
+ get_query_pragma "foreign_key_list", table, &block
+ end
+
+ def index_info( index, &block ) # :yields: row
+ get_query_pragma "index_info", index, &block
+ end
+
+ def index_list( table, &block ) # :yields: row
+ get_query_pragma "index_list", table, &block
+ end
+
+ def table_info( table, &block ) # :yields: row
+ get_query_pragma "table_info", table, &block
+ end
+
+ end
+
+end
diff --git a/lib/sqlite3/resultset.rb b/lib/sqlite3/resultset.rb
new file mode 100644
index 0000000..bef2a0d
--- /dev/null
+++ b/lib/sqlite3/resultset.rb
@@ -0,0 +1,172 @@
+#--
+# =============================================================================
+# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * The names of its contributors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# =============================================================================
+#++
+
+require 'sqlite3/constants'
+require 'sqlite3/errors'
+
+module SQLite3
+
+ # The ResultSet object encapsulates the enumerability of a query's output.
+ # It is a simple cursor over the data that the query returns. It will
+ # very rarely (if ever) be instantiated directly. Instead, client's should
+ # obtain a ResultSet instance via Statement#execute.
+ class ResultSet
+ include Enumerable
+
+ # A trivial module for adding a +types+ accessor to an object.
+ module TypesContainer
+ attr_accessor :types
+ end
+
+ # A trivial module for adding a +fields+ accessor to an object.
+ module FieldsContainer
+ attr_accessor :fields
+ end
+
+ # Create a new ResultSet attached to the given database, using the
+ # given sql text.
+ def initialize( db, stmt )
+ @db = db
+ @driver = @db.driver
+ @stmt = stmt
+ commence
+ end
+
+ # A convenience method for compiling the virtual machine and stepping
+ # to the first row of the result set.
+ def commence
+ result = @driver.step( @stmt.handle )
+ check result
+ @first_row = true
+ end
+ private :commence
+
+ def check( result )
+ @eof = ( result == Constants::ErrorCode::DONE )
+ found = ( result == Constants::ErrorCode::ROW )
+ Error.check( result, @db ) unless @eof || found
+ end
+ private :check
+
+ # Reset the cursor, so that a result set which has reached end-of-file
+ # can be rewound and reiterated.
+ def reset( *bind_params )
+ @driver.reset( @stmt.handle )
+ @stmt.bind_params( *bind_params )
+ @eof = false
+ commence
+ end
+
+ # Query whether the cursor has reached the end of the result set or not.
+ def eof?
+ @eof
+ end
+
+ # Obtain the next row from the cursor. If there are no more rows to be
+ # had, this will return +nil+. If type translation is active on the
+ # corresponding database, the values in the row will be translated
+ # according to their types.
+ #
+ # The returned value will be an array, unless Database#results_as_hash has
+ # been set to +true+, in which case the returned value will be a hash.
+ #
+ # For arrays, the column names are accessible via the +fields+ property,
+ # and the column types are accessible via the +types+ property.
+ #
+ # For hashes, the column names are the keys of the hash, and the column
+ # types are accessible via the +types+ property.
+ def next
+ return nil if @eof
+
+ unless @first_row
+ result = @driver.step( @stmt.handle )
+ check result
+ end
+
+ @first_row = false
+
+ unless @eof
+ row = []
+ @driver.data_count( @stmt.handle ).times do |column|
+ case @driver.column_type( @stmt.handle, column )
+ when Constants::ColumnType::NULL then
+ row << nil
+ when Constants::ColumnType::BLOB then
+ row << @driver.column_blob( @stmt.handle, column )
+ else
+ row << @driver.column_text( @stmt.handle, column )
+ end
+ end
+
+ if @db.type_translation
+ row = @stmt.types.zip( row ).map do |type, value|
+ @db.translator.translate( type, value )
+ end
+ end
+
+ if @db.results_as_hash
+ new_row = Hash[ *( @stmt.columns.zip( row ).flatten ) ]
+ row.each_with_index { |value,idx| new_row[idx] = value }
+ row = new_row
+ else
+ row.extend FieldsContainer unless row.respond_to?(:fields)
+ row.fields = @stmt.columns
+ end
+
+ row.extend TypesContainer
+ row.types = @stmt.types
+
+ return row
+ end
+
+ nil
+ end
+
+ # Required by the Enumerable mixin. Provides an internal iterator over the
+ # rows of the result set.
+ def each
+ while row=self.next
+ yield row
+ end
+ end
+
+ def types
+ @stmt.types
+ end
+
+ def columns
+ @stmt.columns
+ end
+
+ end
+
+end
diff --git a/lib/sqlite3/statement.rb b/lib/sqlite3/statement.rb
new file mode 100644
index 0000000..8d2e3d5
--- /dev/null
+++ b/lib/sqlite3/statement.rb
@@ -0,0 +1,218 @@
+#--
+# =============================================================================
+# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * The names of its contributors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# =============================================================================
+#++
+
+require 'sqlite3/errors'
+require 'sqlite3/resultset'
+
+class String
+ def to_blob
+ SQLite3::Blob.new( self )
+ end
+end
+
+module SQLite3
+
+ # A class for differentiating between strings and blobs, when binding them
+ # into statements.
+ class Blob < String; end
+
+ # A statement represents a prepared-but-unexecuted SQL query. It will rarely
+ # (if ever) be instantiated directly by a client, and is most often obtained
+ # via the Database#prepare method.
+ class Statement
+
+ # This is any text that followed the first valid SQL statement in the text
+ # with which the statement was initialized. If there was no trailing text,
+ # this will be the empty string.
+ attr_reader :remainder
+
+ # The underlying opaque handle used to access the SQLite @driver.
+ attr_reader :handle
+
+ # Create a new statement attached to the given Database instance, and which
+ # encapsulates the given SQL text. If the text contains more than one
+ # statement (i.e., separated by semicolons), then the #remainder property
+ # will be set to the trailing text.
+ def initialize( db, sql, utf16=false )
+ @db = db
+ @driver = @db.driver
+ result, @handle, @remainder = @driver.prepare( @db.handle, sql )
+ Error.check( result, @db )
+ end
+
+ def close
+ @driver.finalize( @handle )
+ end
+
+ # Binds the given variables to the corresponding placeholders in the SQL
+ # text.
+ #
+ # See Database#execute for a description of the valid placeholder
+ # syntaxes.
+ #
+ # Example:
+ #
+ # stmt = db.prepare( "select * from table where a=? and b=?" )
+ # stmt.bind_params( 15, "hello" )
+ #
+ # See also #execute, #bind_param, Statement#bind_param, and
+ # Statement#bind_params.
+ def bind_params( *bind_vars )
+ index = 1
+ bind_vars.each do |var|
+ if Hash === var
+ var.each { |key, val| bind_param key, val }
+ else
+ bind_param index, var
+ index += 1
+ end
+ end
+ end
+
+ # Binds value to the named (or positional) placeholder. If +param+ is a
+ # Fixnum, it is treated as an index for a positional placeholder.
+ # Otherwise it is used as the name of the placeholder to bind to.
+ #
+ # See also #bind_params.
+ def bind_param( param, value )
+ if Fixnum === param
+ case value
+ when Integer then
+ @driver.bind_int( @handle, param, value )
+ when Numeric then
+ @driver.bind_double( @handle, param, value.to_f )
+ when Blob then
+ @driver.bind_blob( @handle, param, value )
+ when nil then
+ @driver.bind_null( @handle, param )
+ else
+ @driver.bind_text( @handle, param, value )
+ end
+ else
+ index = @driver.bind_parameter_index(
+ @handle, param.to_s )
+ raise Exception, "no such bind parameter '#{param}'" if index == 0
+ bind_param index, value
+ end
+ end
+
+ # Execute the statement. This creates a new ResultSet object for the
+ # statement's virtual machine. If a block was given, the new ResultSet will
+ # be yielded to it; otherwise, the ResultSet will be returned.
+ #
+ # Any parameters will be bound to the statement using #bind_params.
+ #
+ # Example:
+ #
+ # stmt = db.prepare( "select * from table" )
+ # stmt.execute do |result|
+ # ...
+ # end
+ #
+ # See also #bind_params, #execute!.
+ def execute( *bind_vars )
+ @driver.reset( @handle ) if @results
+
+ bind_params *bind_vars unless bind_vars.empty?
+ @results = ResultSet.new( @db, self )
+
+ if block_given?
+ yield @results
+ else
+ return @results
+ end
+ end
+
+ # Execute the statement. If no block was given, this returns an array of
+ # rows returned by executing the statement. Otherwise, each row will be
+ # yielded to the block.
+ #
+ # Any parameters will be bound to the statement using #bind_params.
+ #
+ # Example:
+ #
+ # stmt = db.prepare( "select * from table" )
+ # stmt.execute! do |row|
+ # ...
+ # end
+ #
+ # See also #bind_params, #execute.
+ def execute!( *bind_vars )
+ result = execute( *bind_vars )
+ rows = [] unless block_given?
+ while row = result.next
+ if block_given?
+ yield row
+ else
+ rows << row
+ end
+ end
+ rows
+ end
+
+ # Return an array of the column names for this statement. Note that this
+ # may execute the statement in order to obtain the metadata; this makes it
+ # a (potentially) expensive operation.
+ def columns
+ get_metadata unless @columns
+ return @columns
+ end
+
+ # Return an array of the data types for each column in this statement. Note
+ # that this may execute the statement in order to obtain the metadata; this
+ # makes it a (potentially) expensive operation.
+ def types
+ get_metadata unless @types
+ return @types
+ end
+
+ # A convenience method for obtaining the metadata about the query. Note
+ # that this will actually execute the SQL, which means it can be a
+ # (potentially) expensive operation.
+ def get_metadata
+ @columns = []
+ @types = []
+
+ column_count = @driver.column_count( @handle )
+ column_count.times do |column|
+ @columns << @driver.column_name( @handle, column )
+ @types << @driver.column_decltype( @handle, column )
+ end
+
+ @columns.freeze
+ @types.freeze
+ end
+ private :get_metadata
+
+ end
+
+end
diff --git a/lib/sqlite3/translator.rb b/lib/sqlite3/translator.rb
new file mode 100644
index 0000000..1ee315e
--- /dev/null
+++ b/lib/sqlite3/translator.rb
@@ -0,0 +1,135 @@
+#--
+# =============================================================================
+# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * The names of its contributors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# =============================================================================
+#++
+
+require 'time'
+
+module SQLite3
+
+ # The Translator class encapsulates the logic and callbacks necessary for
+ # converting string data to a value of some specified type. Every Database
+ # instance may have a Translator instance, in order to assist in type
+ # translation (Database#type_translation).
+ #
+ # Further, applications may define their own custom type translation logic
+ # by registering translator blocks with the corresponding database's
+ # translator instance (Database#translator).
+ class Translator
+
+ # Create a new Translator instance. It will be preinitialized with default
+ # translators for most SQL data types.
+ def initialize
+ @translators = Hash.new( proc { |type,value| value } )
+ register_default_translators
+ end
+
+ # Add a new translator block, which will be invoked to process type
+ # translations to the given type. The type should be an SQL datatype, and
+ # may include parentheses (i.e., "VARCHAR(30)"). However, any parenthetical
+ # information is stripped off and discarded, so type translation decisions
+ # are made solely on the "base" type name.
+ #
+ # The translator block itself should accept two parameters, "type" and
+ # "value". In this case, the "type" is the full type name (including
+ # parentheses), so the block itself may include logic for changing how a
+ # type is translated based on the additional data. The "value" parameter
+ # is the (string) data to convert.
+ #
+ # The block should return the translated value.
+ def add_translator( type, &block ) # :yields: type, value
+ @translators[ type_name( type ) ] = block
+ end
+
+ # Translate the given string value to a value of the given type. In the
+ # absense of an installed translator block for the given type, the value
+ # itself is always returned. Further, +nil+ values are never translated,
+ # and are always passed straight through regardless of the type parameter.
+ def translate( type, value )
+ unless value.nil?
+ @translators[ type_name( type ) ].call( type, value )
+ end
+ end
+
+ # A convenience method for working with type names. This returns the "base"
+ # type name, without any parenthetical data.
+ def type_name( type )
+ type = $1 if type =~ /^(.*?)\(/
+ type.upcase
+ end
+ private :type_name
+
+ # Register the default translators for the current Translator instance.
+ # This includes translators for most major SQL data types.
+ def register_default_translators
+ [ "date",
+ "datetime",
+ "time" ].each { |type| add_translator( type ) { |t,v| Time.parse( v ) } }
+
+ [ "decimal",
+ "float",
+ "numeric",
+ "double",
+ "real",
+ "dec",
+ "fixed" ].each { |type| add_translator( type ) { |t,v| v.to_f } }
+
+ [ "integer",
+ "smallint",
+ "mediumint",
+ "int",
+ "bigint" ].each { |type| add_translator( type ) { |t,v| v.to_i } }
+
+ [ "bit",
+ "bool",
+ "boolean" ].each do |type|
+ add_translator( type ) do |t,v|
+ !( v.strip.gsub(/00+/,"0") == "0" ||
+ v.downcase == "false" ||
+ v.downcase == "f" ||
+ v.downcase == "no" ||
+ v.downcase == "n" )
+ end
+ end
+
+ add_translator( "timestamp" ) { |type, value| Time.at( value.to_i ) }
+ add_translator( "tinyint" ) do |type, value|
+ if type =~ /\(\s*1\s*\)/
+ value.to_i == 1
+ else
+ value.to_i
+ end
+ end
+ end
+ private :register_default_translators
+
+ end
+
+end
diff --git a/lib/sqlite3/value.rb b/lib/sqlite3/value.rb
new file mode 100644
index 0000000..fb76376
--- /dev/null
+++ b/lib/sqlite3/value.rb
@@ -0,0 +1,89 @@
+#--
+# =============================================================================
+# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * The names of its contributors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# =============================================================================
+#++
+
+require 'sqlite3/constants'
+
+module SQLite3
+
+ class Value
+ attr_reader :handle
+
+ def initialize( db, handle )
+ @driver = db.driver
+ @handle = handle
+ end
+
+ def null?
+ type == :null
+ end
+
+ def to_blob
+ @driver.value_blob( @handle )
+ end
+
+ def length( utf16=false )
+ if utf16
+ @driver.value_bytes16( @handle )
+ else
+ @driver.value_bytes( @handle )
+ end
+ end
+
+ def to_f
+ @driver.value_double( @handle )
+ end
+
+ def to_i
+ @driver.value_int( @handle )
+ end
+
+ def to_int64
+ @driver.value_int64( @handle )
+ end
+
+ def to_s( utf16=false )
+ @driver.value_text( @handle, utf16 )
+ end
+
+ def type
+ case @driver.value_type( @handle )
+ when Constants::ColumnType::INTEGER then :int
+ when Constants::ColumnType::FLOAT then :float
+ when Constants::ColumnType::TEXT then :text
+ when Constants::ColumnType::BLOB then :blob
+ when Constants::ColumnType::NULL then :null
+ end
+ end
+
+ end
+
+end
diff --git a/lib/sqlite3/version.rb b/lib/sqlite3/version.rb
new file mode 100644
index 0000000..542cd75
--- /dev/null
+++ b/lib/sqlite3/version.rb
@@ -0,0 +1,45 @@
+#--
+# =============================================================================
+# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * The names of its contributors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# =============================================================================
+#++
+
+module SQLite3
+
+ module Version
+
+ MAJOR = 0
+ MINOR = 9
+ TINY = 0
+
+ STRING = [ MAJOR, MINOR, TINY ].join( "." )
+
+ end
+
+end