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