require 'sqlite3_c' require 'data_objects' module DataObject module Sqlite3 QUOTE_STRING = "\"" QUOTE_COLUMN = "'" class Connection < DataObject::Connection attr_reader :db def initialize(connection_string) @state = STATE_CLOSED @connection_string = connection_string @conn = Hash[*connection_string.split(" ").map {|x| x.split("=")}.flatten]["dbname"] end def open r, d = Sqlite3_c.sqlite3_open(@conn) unless r == Sqlite3_c::SQLITE_OK raise ConnectionFailed, "Unable to connect to database with provided connection string. \n#{Sqlite3_c.sqlite3_errmsg(d)}" else @db = d end @state = STATE_OPEN true end def close Sqlite3_c.sqlite3_close(@db) @state = STATE_CLOSED true end def create_command(text) Command.new(self, text) end def begin_transaction Transaction.new(self) end end class Transaction < DataObject::Transaction attr_reader :connection def initialize(conn) @connection = conn exec_sql("BEGIN") end # Commits the transaction def commit exec_sql("COMMIT") end # Rolls back the transaction def rollback(savepoint = nil) raise NotImplementedError, "SQLite3 does not support savepoints" if savepoint exec_sql("ROLLBACK") end # Creates a savepoint for rolling back later (not commonly supported) def save(name) raise NotImplementedError, "SQLite3 does not support savepoints" end def create_command(*args) @connection.create_command(*args) end protected def exec_sql(sql) @connection.logger.debug { sql } result, reader = Sqlite3_c.sqlite3_prepare_v2(@connection.db, sql, sql.size + 1) exec_result = Sqlite3_c.sqlite3_step(reader) Sqlite3_c.sqlite3_finalize(reader) exec_result end end class Reader < DataObject::Reader def initialize(db, reader) @reader = reader result = Sqlite3_c.sqlite3_step(reader) rows_affected, field_count = Sqlite3_c.sqlite3_changes(db), Sqlite3_c.sqlite3_column_count(reader) if field_count == 0 @records_affected = rows_affected close else @field_count = field_count @fields, @field_types = [], [] i = 0 while(i < @field_count) @field_types.push(Sqlite3_c.sqlite3_column_type(reader, i)) @fields.push(Sqlite3_c.sqlite3_column_name(reader, i)) i += 1 end case result when Sqlite3_c::SQLITE_BUSY, Sqlite3_c::SQLITE_ERROR, Sqlite3_c::SQLITE_MISUSE raise ReaderError, "An error occurred while trying to get the next row\n#{Sqlite3_c.sqlite3_errmsg(db)}" else @has_rows = result == Sqlite3_c::SQLITE_ROW @state = STATE_OPEN close unless @has_rows end end end def real_close Sqlite3_c.sqlite3_finalize(@reader) end def name(idx) super @fields[idx] end def get_index(name) super @fields.index(name) end def null?(idx) super item(idx).nil? end def item(idx) super case @field_types[idx] when 1 # SQLITE_INTEGER Sqlite3_c.sqlite3_column_int(@reader, idx).to_i when 2 # SQLITE_FLOAT Sqlite3_c.sqlite3_column_double(@reader, idx) else Sqlite3_c.sqlite3_column_text(@reader, idx) end end def each return unless has_rows? while(true) do yield break unless Sqlite3_c.sqlite3_step(@reader) == Sqlite3_c::SQLITE_ROW end end end class Command < DataObject::Command def execute_reader(*args) super sql = escape_sql(args) @connection.logger.debug { sql } result, ptr = Sqlite3_c.sqlite3_prepare_v2(@connection.db, sql, sql.size + 1) unless result == Sqlite3_c::SQLITE_OK raise QueryError, "Your query failed.\n#{Sqlite3_c.sqlite3_errmsg(@connection.db)}\nQUERY: \"#{sql}\"" else reader = Reader.new(@connection.db, ptr) if block_given? return_value = yield(reader) reader.close return_value else reader end end end def execute_non_query(*args) super sql = escape_sql(args) @connection.logger.debug { sql } result, reader = Sqlite3_c.sqlite3_prepare_v2(@connection.db, sql, -1) unless result == Sqlite3_c::SQLITE_OK Sqlite3_c.sqlite3_finalize(reader) raise QueryError, "Your query failed.\n#{Sqlite3_c.sqlite3_errmsg(@connection.db)}\nQUERY: \"#{sql}\"" else exec_result = Sqlite3_c.sqlite3_step(reader) Sqlite3_c.sqlite3_finalize(reader) if exec_result == Sqlite3_c::SQLITE_DONE ResultData.new(@connection, Sqlite3_c.sqlite3_changes(@connection.db), Sqlite3_c.sqlite3_last_insert_rowid(@connection.db)) else raise QueryError, "Your query failed or you tried to execute a SELECT query through execute_non_reader\n#{Sqlite3_c.sqlite3_errmsg(@connection.db)}\nQUERY: \"#{@text}\"" end end end def quote_symbol(value) value.to_s end def quote_boolean(value) value ? '1' : '0' end end end end