Skip to content

Commit f99ec94

Browse files
committed
Some alignment with main sqlite_adapter.rb
1 parent c4bbbe2 commit f99ec94

File tree

1 file changed

+96
-37
lines changed

1 file changed

+96
-37
lines changed

lib/arjdbc/sqlite3/adapter.rb

Lines changed: 96 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -90,20 +90,40 @@ def dealloc(stmt)
9090
end
9191
end
9292

93-
def initialize(config)
94-
@memory_database = config[:database] == ":memory:"
93+
def initialize(...)
9594
super
96-
configure_connection
97-
end
9895

99-
def self.database_exists?(config)
100-
config = config.symbolize_keys
101-
if config[:database] == ":memory:"
102-
true
96+
@memory_database = false
97+
case @config[:database].to_s
98+
when ""
99+
raise ArgumentError, "No database file specified. Missing argument: database"
100+
when ":memory:"
101+
@memory_database = true
102+
when /\Afile:/
103103
else
104-
database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
105-
File.exist?(database_file)
104+
# Otherwise we have a path relative to Rails.root
105+
@config[:database] = File.expand_path(@config[:database], Rails.root) if defined?(Rails.root)
106+
dirname = File.dirname(@config[:database])
107+
unless File.directory?(dirname)
108+
begin
109+
Dir.mkdir(dirname)
110+
rescue Errno::ENOENT => error
111+
if error.message.include?("No such file or directory")
112+
raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
113+
else
114+
raise
115+
end
116+
end
117+
end
106118
end
119+
120+
@config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
121+
@connection_parameters = @config.merge(database: @config[:database].to_s, results_as_hash: true)
122+
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
123+
end
124+
125+
def self.database_exists?(config)
126+
@config[:database] == ":memory:" || File.exist?(@config[:database].to_s)
107127
end
108128

109129
def supports_ddl_transactions?
@@ -178,19 +198,15 @@ def return_value_after_insert?(column) # :nodoc:
178198
column.auto_populated?
179199
end
180200

181-
def reconnect
182-
if active?
183-
@raw_connection.rollback rescue nil
184-
else
185-
connect
186-
end
187-
end
201+
# MISSING: alias :reset! :reconnect!
188202

189203
# Disconnects from the database if already connected. Otherwise, this
190204
# method does nothing.
191205
def disconnect!
192206
super
193-
@raw_connection.close rescue nil
207+
208+
@raw_connection&.close rescue nil
209+
@raw_connection = nil
194210
end
195211

196212
def supports_index_sort_order?
@@ -230,16 +246,7 @@ def disable_referential_integrity # :nodoc:
230246
end
231247
end
232248

233-
def all_foreign_keys_valid? # :nodoc:
234-
# Rails 7
235-
check_all_foreign_keys_valid!
236-
true
237-
rescue ActiveRecord::StatementInvalid
238-
false
239-
end
240-
241249
def check_all_foreign_keys_valid! # :nodoc:
242-
# Rails 7.1
243250
sql = "PRAGMA foreign_key_check"
244251
result = execute(sql)
245252

@@ -269,7 +276,8 @@ def remove_index(table_name, column_name = nil, **options) # :nodoc:
269276
#
270277
# Example:
271278
# rename_table('octopuses', 'octopi')
272-
def rename_table(table_name, new_name)
279+
def rename_table(table_name, new_name, **options)
280+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
273281
schema_cache.clear_data_source_cache!(table_name.to_s)
274282
schema_cache.clear_data_source_cache!(new_name.to_s)
275283
internal_exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
@@ -312,6 +320,8 @@ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
312320
end
313321

314322
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
323+
validate_change_column_null_argument!(null)
324+
315325
unless null || default.nil?
316326
internal_exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
317327
end
@@ -322,10 +332,7 @@ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
322332

323333
def change_column(table_name, column_name, type, **options) #:nodoc:
324334
alter_table(table_name) do |definition|
325-
definition[column_name].instance_eval do
326-
self.type = aliased_types(type.to_s, type)
327-
self.options.merge!(options)
328-
end
335+
definition.change_column(column_name, type, **options)
329336
end
330337
end
331338

@@ -413,6 +420,7 @@ def check_version
413420
end
414421
end
415422

423+
# DIFFERENCE: here to
416424
def new_column_from_field(table_name, field, definitions)
417425
default = field["dflt_value"]
418426

@@ -475,7 +483,7 @@ def extract_default_function(default_value, default)
475483
end
476484

477485
def has_default_function?(default_value, default)
478-
!default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
486+
!default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
479487
end
480488

481489
# See: https://www.sqlite.org/lang_altertable.html
@@ -688,18 +696,57 @@ def build_statement_pool
688696
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
689697
end
690698

699+
# DIFFERENCE: we delve into jdbc shared code and this does self.class.new_client.
691700
def connect
692701
@raw_connection = jdbc_connection_class(@config[:adapter_spec]).new(@config, self)
693702
@raw_connection.configure_connection
694703
end
695704

705+
def reconnect
706+
if active?
707+
@raw_connection.rollback rescue nil
708+
else
709+
connect
710+
end
711+
end
712+
696713
def configure_connection
697-
# FIXME: missing from adapter
698-
# @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
714+
if @config[:timeout] && @config[:retries]
715+
raise ArgumentError, "Cannot specify both timeout and retries arguments"
716+
elsif @config[:timeout]
717+
# FIXME:
718+
# @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
719+
elsif @config[:retries]
720+
retries = self.class.type_cast_config_to_integer(@config[:retries])
721+
raw_connection.busy_handler do |count|
722+
count <= retries
723+
end
724+
end
699725

700-
execute("PRAGMA foreign_keys = ON", "SCHEMA")
726+
# Enforce foreign key constraints
727+
# https://www.sqlite.org/pragma.html#pragma_foreign_keys
728+
# https://www.sqlite.org/foreignkeys.html
729+
raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
730+
unless @memory_database
731+
# Journal mode WAL allows for greater concurrency (many readers + one writer)
732+
# https://www.sqlite.org/pragma.html#pragma_journal_mode
733+
raw_execute("PRAGMA journal_mode = WAL", "SCHEMA")
734+
# Set more relaxed level of database durability
735+
# 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
736+
# https://www.sqlite.org/pragma.html#pragma_synchronous
737+
raw_execute("PRAGMA synchronous = NORMAL", "SCHEMA")
738+
# Set the global memory map so all processes can share some data
739+
# https://www.sqlite.org/pragma.html#pragma_mmap_size
740+
# https://www.sqlite.org/mmap.html
741+
raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA")
742+
end
743+
# Impose a limit on the WAL file to prevent unlimited growth
744+
# https://www.sqlite.org/pragma.html#pragma_journal_size_limit
745+
raw_execute("PRAGMA journal_size_limit = #{64.megabytes}", "SCHEMA")
746+
# Set the local connection cache to 2000 pages
747+
# https://www.sqlite.org/pragma.html#pragma_cache_size
748+
raw_execute("PRAGMA cache_size = 2000", "SCHEMA")
701749
end
702-
703750
end
704751
# DIFFERENCE: A registration here is moved down to concrete class so we are not registering part of an adapter.
705752
end
@@ -720,6 +767,18 @@ class SQLite3Adapter < AbstractAdapter
720767
include ArJdbc::Abstract::StatementCache
721768
include ArJdbc::Abstract::TransactionSupport
722769

770+
##
771+
# :singleton-method:
772+
# Configure the SQLite3Adapter to be used in a strict strings mode.
773+
# This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
774+
# For example, it is possible to create an index for a non existing column.
775+
# If you wish to enable this mode you can add the following line to your application.rb file:
776+
#
777+
# config.active_record.sqlite3_adapter_strict_strings_by_default = true
778+
class_attribute :strict_strings_by_default, default: false
779+
780+
781+
723782
def self.represent_boolean_as_integer=(value) # :nodoc:
724783
if value == false
725784
raise "`.represent_boolean_as_integer=` is now always true, so make sure your application can work with it and remove this settings."

0 commit comments

Comments
 (0)