diff --git a/CHANGELOG.md b/CHANGELOG.md
index 68f15d5e..56b12276 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@
- Vendored sqlite is update to [v3.44.2](https://sqlite.org/releaselog/3_44_2.html). @flavorjones
+### Added
+
+- `Database.new` now accepts a `:default_transaction_mode` option (defaulting to `:deferred`), and `Database#transaction` no longer requires a transaction mode to be specified. This should allow higher-level adapters to more easily choose a transaction mode for a database connection. [#426] @masamitsu-murase
+
## 1.6.8 / 2023-11-01
diff --git a/lib/sqlite3/database.rb b/lib/sqlite3/database.rb
index c50ca8cf..5d004a23 100644
--- a/lib/sqlite3/database.rb
+++ b/lib/sqlite3/database.rb
@@ -71,12 +71,23 @@ def quote( string )
# call-seq: SQLite3::Database.new(file, options = {})
#
- # Create a new Database object that opens the given file. If utf16
- # is +true+, the filename is interpreted as a UTF-16 encoded string.
+ # Create a new Database object that opens the given file.
+ #
+ # Supported permissions +options+:
+ # - the default mode is READWRITE | CREATE
+ # - +:readonly+: boolean (default false), true to set the mode to +READONLY+
+ # - +:readwrite+: boolean (default false), true to set the mode to +READWRITE+
+ # - +:flags+: set the mode to a combination of SQLite3::Constants::Open flags.
+ #
+ # Supported encoding +options+:
+ # - +:utf16+: boolean (default false), is the filename's encoding UTF-16 (only needed if the filename encoding is not UTF_16LE or BE)
+ #
+ # Other supported +options+:
+ # - +:strict+: boolean (default false), disallow the use of double-quoted string literals (see https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted)
+ # - +:results_as_hash+: boolean (default false), return rows as hashes instead of arrays
+ # - +:type_translation+: boolean (default false), enable type translation
+ # - +:default_transaction_mode+: one of +:deferred+ (default), +:immediate+, or +:exclusive+. If a mode is not specified in a call to #transaction, this will be the default transaction mode.
#
- # By default, the new database will return result rows as arrays
- # (#results_as_hash) and has type translation disabled (#type_translation=).
-
def initialize file, options = {}, zvfs = nil
mode = Constants::Open::READWRITE | Constants::Open::CREATE
@@ -119,6 +130,7 @@ def initialize file, options = {}, zvfs = nil
@type_translation = options[:type_translation]
@type_translator = make_type_translator @type_translation
@readonly = mode & Constants::Open::READONLY != 0
+ @default_transaction_mode = options[:default_transaction_mode] || :deferred
if block_given?
begin
@@ -622,8 +634,10 @@ def finalize
# by SQLite, so attempting to nest a transaction will result in a runtime
# exception.
#
- # The +mode+ parameter may be either :deferred (the default),
+ # The +mode+ parameter may be either :deferred,
# :immediate, or :exclusive.
+ # If `nil` is specified, the default transaction mode, which was
+ # passed to #initialize, is used.
#
# If a block is given, the database instance is yielded to it, and the
# transaction is committed when the block terminates. If the block
@@ -634,7 +648,8 @@ def finalize
# 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 )
+ def transaction( mode = nil )
+ mode = @default_transaction_mode if mode.nil?
execute "begin #{mode.to_s} transaction"
if block_given?
diff --git a/test/test_database.rb b/test/test_database.rb
index 81b10716..c6581b8c 100644
--- a/test/test_database.rb
+++ b/test/test_database.rb
@@ -624,5 +624,45 @@ def test_raw_float_infinity
db.execute("insert into foo values (?)", Float::INFINITY)
assert_equal Float::INFINITY, db.execute("select avg(temperature) from foo").first.first
end
+
+ def test_default_transaction_mode
+ tf = Tempfile.new 'database_default_transaction_mode'
+ SQLite3::Database.new(tf.path) do |db|
+ db.execute("create table foo (score int)")
+ db.execute("insert into foo values (?)", 1)
+ end
+
+ test_cases = [
+ {mode: nil, read: true, write: true},
+ {mode: :deferred, read: true, write: true},
+ {mode: :immediate, read: true, write: false},
+ {mode: :exclusive, read: false, write: false},
+ ]
+
+ test_cases.each do |item|
+ db = SQLite3::Database.new tf.path, default_transaction_mode: item[:mode]
+ db2 = SQLite3::Database.new tf.path
+ db.transaction do
+ sql_for_read_test = "select * from foo"
+ if item[:read]
+ assert_nothing_raised{ db2.execute(sql_for_read_test) }
+ else
+ assert_raises(SQLite3::BusyException){ db2.execute(sql_for_read_test) }
+ end
+
+ sql_for_write_test = "insert into foo values (2)"
+ if item[:write]
+ assert_nothing_raised{ db2.execute(sql_for_write_test) }
+ else
+ assert_raises(SQLite3::BusyException){ db2.execute(sql_for_write_test) }
+ end
+ end
+ ensure
+ db.close if db && !db.closed?
+ db2.close if db2 && !db2.closed?
+ end
+ ensure
+ tf.unlink if tf
+ end
end
end