Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ platforms :ruby, :windows do

# Active Record.
gem "sqlite3", "~> 1.6", ">= 1.6.6"
gem "extralite", "~> 2.2"

group :db do
gem "pg", "~> 1.3"
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ GEM
tzinfo
event_emitter (0.2.6)
execjs (2.8.1)
extralite (2.2)
faraday (1.10.2)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
Expand Down Expand Up @@ -587,6 +588,7 @@ DEPENDENCIES
debug (>= 1.1.0)
delayed_job
delayed_job_active_record
extralite (~> 2.2)
google-cloud-storage (~> 1.11)
image_processing (~> 1.2)
importmap-rails
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,20 @@ def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: fals
unless prepare
stmt = conn.prepare(sql)
begin
cols = stmt.columns
cols = stmt.columns.map(&:to_s)
unless without_prepared_statement?(binds)
stmt.bind_params(type_casted_binds)
stmt.bind(*type_casted_binds)
end
records = stmt.to_a
records = stmt.to_a_ary
ensure
stmt.close
end
else
stmt = @statements[sql] ||= conn.prepare(sql)
cols = stmt.columns
stmt.reset!
stmt.bind_params(type_casted_binds)
records = stmt.to_a
cols = stmt.columns.map(&:to_s)
stmt.reset
stmt.bind(*type_casted_binds)
records = stmt.to_a_ary
end

build_result(columns: cols, rows: records)
Expand Down Expand Up @@ -112,7 +112,7 @@ def high_precision_current_timestamp
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: false)
log(sql, name, async: async) do
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
conn.execute(sql)
conn.query(sql)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,45 @@

gem "sqlite3", "~> 1.4"
require "sqlite3"
require "extralite"

module Extralite
class Database
def transaction(mode = :deferred)
execute "begin #{mode} transaction"

if block_given?
abort = false
begin
yield self
rescue
abort = true
raise
ensure
abort and rollback or commit
end
end

true
end

def commit
execute "commit transaction"
true
end

def rollback
execute "rollback transaction"
true
end

def encoding
"UTF-8"
end

alias readonly? read_only?
end
end

module ActiveRecord
module ConnectionHandling # :nodoc:
Expand All @@ -39,7 +78,7 @@ class SQLite3Adapter < AbstractAdapter

class << self
def new_client(config)
::SQLite3::Database.new(config[:database].to_s, config)
::Extralite::Database.new(config[:database].to_s, read_only: config[:readonly])
rescue Errno::ENOENT => error
if error.message.include?("No such file or directory")
raise ActiveRecord::NoDatabaseError
Expand Down Expand Up @@ -717,7 +756,7 @@ def configure_connection
if @config[:timeout] && @config[:retries]
raise ArgumentError, "Cannot specify both timeout and retries arguments"
elsif @config[:timeout]
@raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
@raw_connection.busy_timeout = self.class.type_cast_config_to_integer(@config[:timeout])
elsif @config[:retries]
retries = self.class.type_cast_config_to_integer(@config[:retries])
raw_connection.busy_handler do |count|
Expand Down
130 changes: 35 additions & 95 deletions activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,20 +151,20 @@ def test_encoding

def test_default_pragmas
if in_memory_db?
assert_equal [{ "foreign_keys" => 1 }], @conn.execute("PRAGMA foreign_keys")
assert_equal [{ "journal_mode" => "memory" }], @conn.execute("PRAGMA journal_mode")
assert_equal [{ "synchronous" => 2 }], @conn.execute("PRAGMA synchronous")
assert_equal [{ "journal_size_limit" => 67108864 }], @conn.execute("PRAGMA journal_size_limit")
assert_equal [{ foreign_keys: 1 }], @conn.execute("PRAGMA foreign_keys")
assert_equal [{ journal_mode: "memory" }], @conn.execute("PRAGMA journal_mode")
assert_equal [{ synchronous: 2 }], @conn.execute("PRAGMA synchronous")
assert_equal [{ journal_size_limit: 67108864 }], @conn.execute("PRAGMA journal_size_limit")
assert_equal [], @conn.execute("PRAGMA mmap_size")
assert_equal [{ "cache_size" => 2000 }], @conn.execute("PRAGMA cache_size")
assert_equal [{ cache_size: 2000 }], @conn.execute("PRAGMA cache_size")
else
with_file_connection do |conn|
assert_equal [{ "foreign_keys" => 1 }], conn.execute("PRAGMA foreign_keys")
assert_equal [{ "journal_mode" => "wal" }], conn.execute("PRAGMA journal_mode")
assert_equal [{ "synchronous" => 1 }], conn.execute("PRAGMA synchronous")
assert_equal [{ "journal_size_limit" => 67108864 }], conn.execute("PRAGMA journal_size_limit")
assert_equal [{ "mmap_size" => 134217728 }], conn.execute("PRAGMA mmap_size")
assert_equal [{ "cache_size" => 2000 }], conn.execute("PRAGMA cache_size")
assert_equal [{ foreign_keys: 1 }], conn.execute("PRAGMA foreign_keys")
assert_equal [{ journal_mode: "wal" }], conn.execute("PRAGMA journal_mode")
assert_equal [{ synchronous: 1 }], conn.execute("PRAGMA synchronous")
assert_equal [{ journal_size_limit: 67108864 }], conn.execute("PRAGMA journal_size_limit")
assert_equal [{ mmap_size: 134217728 }], conn.execute("PRAGMA mmap_size")
assert_equal [{ cache_size: 2000 }], conn.execute("PRAGMA cache_size")
end
end
end
Expand Down Expand Up @@ -243,8 +243,8 @@ def test_execute
assert_equal 1, records.length

record = records.first
assert_equal 10, record["number"]
assert_equal 1, record["id"]
assert_equal 10, record[:number]
assert_equal 1, record[:id]
end
end

Expand Down Expand Up @@ -666,27 +666,27 @@ def test_respond_to_disable_extension
assert_respond_to @conn, :disable_extension
end

def test_statement_closed
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
db = ::SQLite3::Database.new(db_config.database)

@conn.connect!

statement = ::SQLite3::Statement.new(db,
"CREATE TABLE statement_test (number integer not null)")
statement.stub(:step, -> { raise ::SQLite3::BusyException.new("busy") }) do
assert_called(statement, :columns, returns: []) do
assert_called(statement, :close) do
::SQLite3::Statement.stub(:new, statement) do
error = assert_raises ActiveRecord::StatementInvalid do
@conn.exec_query "select * from statement_test"
end
assert_equal @conn.pool, error.connection_pool
end
end
end
end
end
# def test_statement_closed
# db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
# db = ::Extralite::Database.new(db_config.database)

# @conn.connect!

# statement = ::SQLite3::Statement.new(db,
# "CREATE TABLE statement_test (number integer not null)")
# statement.stub(:step, -> { raise ::SQLite3::BusyException.new("busy") }) do
# assert_called(statement, :columns, returns: []) do
# assert_called(statement, :close) do
# ::SQLite3::Statement.stub(:new, statement) do
# error = assert_raises ActiveRecord::StatementInvalid do
# @conn.exec_query "select * from statement_test"
# end
# assert_equal @conn.pool, error.connection_pool
# end
# end
# end
# end
# end

def test_db_is_not_readonly_when_readonly_option_is_false
conn = Base.sqlite3_connection database: ":memory:",
Expand Down Expand Up @@ -723,70 +723,10 @@ def test_writes_are_not_permitted_to_readonly_databases
exception = assert_raises(ActiveRecord::StatementInvalid) do
conn.execute("CREATE TABLE test(id integer)")
end
assert_match("SQLite3::ReadOnlyException", exception.message)
assert_match("Extralite::Error: attempt to write a readonly database", exception.message)
assert_equal conn.pool, exception.connection_pool
end

def test_strict_strings_by_default
conn = Base.sqlite3_connection(database: ":memory:", adapter: "sqlite3")
conn.create_table :testings

assert_nothing_raised do
conn.add_index :testings, :non_existent
end

with_strict_strings_by_default do
conn = Base.sqlite3_connection(database: ":memory:", adapter: "sqlite3")
conn.create_table :testings

error = assert_raises(StandardError) do
conn.add_index :testings, :non_existent2
end
assert_match(/no such column: non_existent2/, error.message)
assert_equal conn.pool, error.connection_pool
end
end

def test_strict_strings_by_default_and_true_in_database_yml
conn = Base.sqlite3_connection(database: ":memory:", adapter: "sqlite3", strict: true)
conn.create_table :testings

error = assert_raises(StandardError) do
conn.add_index :testings, :non_existent
end
assert_match(/no such column: non_existent/, error.message)
assert_equal conn.pool, error.connection_pool

with_strict_strings_by_default do
conn = Base.sqlite3_connection(database: ":memory:", adapter: "sqlite3", strict: true)
conn.create_table :testings

error = assert_raises(StandardError) do
conn.add_index :testings, :non_existent2
end
assert_match(/no such column: non_existent2/, error.message)
assert_equal conn.pool, error.connection_pool
end
end

def test_strict_strings_by_default_and_false_in_database_yml
conn = Base.sqlite3_connection(database: ":memory:", adapter: "sqlite3", strict: false)
conn.create_table :testings

assert_nothing_raised do
conn.add_index :testings, :non_existent
end

with_strict_strings_by_default do
conn = Base.sqlite3_connection(database: ":memory:", adapter: "sqlite3", strict: false)
conn.create_table :testings

assert_nothing_raised do
conn.add_index :testings, :non_existent
end
end
end

def test_rowid_column
with_example_table "id_uppercase INTEGER PRIMARY KEY" do
assert @conn.columns("ex").index_by(&:name)["id_uppercase"].rowid
Expand Down