mirror of
https://gitea.invidious.io/iv-org/shard-crystal-db.git
synced 2024-08-15 00:53:32 +00:00
Transactions
* dsl, state checks * define transaction sql commands in connection
This commit is contained in:
parent
1049d95562
commit
751f8b26ac
9 changed files with 293 additions and 1 deletions
|
@ -42,11 +42,34 @@ class DummyDriver < DB::Driver
|
||||||
@connected = false
|
@connected = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_transaction
|
||||||
|
DummyTransaction.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
protected def do_close
|
protected def do_close
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class DummyTransaction < DB::TopLevelTransaction
|
||||||
|
getter committed = false
|
||||||
|
getter rolledback = false
|
||||||
|
|
||||||
|
def initialize(connection)
|
||||||
|
super(connection)
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit
|
||||||
|
super
|
||||||
|
@committed = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def rollback
|
||||||
|
super
|
||||||
|
@rolledback = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class DummyStatement < DB::Statement
|
class DummyStatement < DB::Statement
|
||||||
property params
|
property params
|
||||||
|
|
||||||
|
|
158
spec/transaction_spec.cr
Normal file
158
spec/transaction_spec.cr
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
require "./spec_helper"
|
||||||
|
|
||||||
|
private class FooException < Exception
|
||||||
|
end
|
||||||
|
|
||||||
|
describe DB::Transaction do
|
||||||
|
it "begin/commit transaction from connection" do
|
||||||
|
with_dummy_connection do |cnn|
|
||||||
|
tx = cnn.begin_transaction
|
||||||
|
tx.commit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "begin/rollback transaction from connection" do
|
||||||
|
with_dummy_connection do |cnn|
|
||||||
|
tx = cnn.begin_transaction
|
||||||
|
tx.rollback
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raise if begin over existing transaction" do
|
||||||
|
with_dummy_connection do |cnn|
|
||||||
|
cnn.begin_transaction
|
||||||
|
expect_raises(DB::Error, "There is an existing transaction in this connection") do
|
||||||
|
cnn.begin_transaction
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "allow sequential transactions" do
|
||||||
|
with_dummy_connection do |cnn|
|
||||||
|
tx = cnn.begin_transaction
|
||||||
|
tx.rollback
|
||||||
|
|
||||||
|
tx = cnn.begin_transaction
|
||||||
|
tx.commit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "transaction with block from connection should be committed" do
|
||||||
|
t = uninitialized DummyDriver::DummyTransaction
|
||||||
|
|
||||||
|
with_witness do |w|
|
||||||
|
with_dummy_connection do |cnn|
|
||||||
|
cnn.transaction do |tx|
|
||||||
|
if tx.is_a?(DummyDriver::DummyTransaction)
|
||||||
|
t = tx
|
||||||
|
w.check
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
t.committed.should be_true
|
||||||
|
t.rolledback.should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "transaction with block from connection should be rolledback if raise DB::Rollback" do
|
||||||
|
t = uninitialized DummyDriver::DummyTransaction
|
||||||
|
|
||||||
|
with_witness do |w|
|
||||||
|
with_dummy_connection do |cnn|
|
||||||
|
cnn.transaction do |tx|
|
||||||
|
if tx.is_a?(DummyDriver::DummyTransaction)
|
||||||
|
t = tx
|
||||||
|
w.check
|
||||||
|
end
|
||||||
|
raise DB::Rollback.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
t.rolledback.should be_true
|
||||||
|
t.committed.should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "transaction with block from connection should be rolledback if raise" do
|
||||||
|
t = uninitialized DummyDriver::DummyTransaction
|
||||||
|
|
||||||
|
with_witness do |w|
|
||||||
|
with_dummy_connection do |cnn|
|
||||||
|
expect_raises(FooException) do
|
||||||
|
cnn.transaction do |tx|
|
||||||
|
if tx.is_a?(DummyDriver::DummyTransaction)
|
||||||
|
t = tx
|
||||||
|
w.check
|
||||||
|
end
|
||||||
|
raise FooException.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
t.rolledback.should be_true
|
||||||
|
t.committed.should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "transaction can be committed within block" do
|
||||||
|
with_dummy_connection do |cnn|
|
||||||
|
cnn.transaction do |tx|
|
||||||
|
tx.commit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "transaction can be rolledback within block" do
|
||||||
|
with_dummy_connection do |cnn|
|
||||||
|
cnn.transaction do |tx|
|
||||||
|
tx.rollback
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "transaction can't be committed twice" do
|
||||||
|
with_dummy_connection do |cnn|
|
||||||
|
cnn.transaction do |tx|
|
||||||
|
tx.commit
|
||||||
|
expect_raises(DB::Error, "Transaction already closed") do
|
||||||
|
tx.commit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "transaction can't be rolledback twice" do
|
||||||
|
with_dummy_connection do |cnn|
|
||||||
|
cnn.transaction do |tx|
|
||||||
|
tx.rollback
|
||||||
|
expect_raises(DB::Error, "Transaction already closed") do
|
||||||
|
tx.rollback
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "return connection to pool after transaction block in db" do
|
||||||
|
DummyDriver::DummyConnection.clear_connections
|
||||||
|
|
||||||
|
with_dummy do |db|
|
||||||
|
db.transaction do |tx|
|
||||||
|
db.pool.is_available?(DummyDriver::DummyConnection.connections.first).should be_false
|
||||||
|
end
|
||||||
|
db.pool.is_available?(DummyDriver::DummyConnection.connections.first).should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "releasing result_set from within transaction should not return connection to pool" do
|
||||||
|
cnn = uninitialized DB::Connection
|
||||||
|
with_dummy do |db|
|
||||||
|
db.transaction do |tx|
|
||||||
|
cnn = tx.connection
|
||||||
|
cnn.scalar "1"
|
||||||
|
db.pool.is_available?(cnn).should be_false
|
||||||
|
end
|
||||||
|
db.pool.is_available?(cnn).should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -143,7 +143,10 @@ require "./db/session_methods"
|
||||||
require "./db/disposable"
|
require "./db/disposable"
|
||||||
require "./db/driver"
|
require "./db/driver"
|
||||||
require "./db/statement"
|
require "./db/statement"
|
||||||
|
require "./db/begin_transaction"
|
||||||
require "./db/connection"
|
require "./db/connection"
|
||||||
|
require "./db/transaction"
|
||||||
|
require "./db/statement"
|
||||||
require "./db/pool_statement"
|
require "./db/pool_statement"
|
||||||
require "./db/database"
|
require "./db/database"
|
||||||
require "./db/pool_prepared_statement"
|
require "./db/pool_prepared_statement"
|
||||||
|
|
17
src/db/begin_transaction.cr
Normal file
17
src/db/begin_transaction.cr
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
module DB
|
||||||
|
module BeginTransaction
|
||||||
|
abstract def begin_transaction : Transaction
|
||||||
|
|
||||||
|
def transaction
|
||||||
|
tx = begin_transaction
|
||||||
|
begin
|
||||||
|
yield tx
|
||||||
|
rescue e
|
||||||
|
tx.rollback
|
||||||
|
raise e unless e.is_a?(DB::Rollback)
|
||||||
|
else
|
||||||
|
tx.commit unless tx.closed?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -21,10 +21,12 @@ module DB
|
||||||
abstract class Connection
|
abstract class Connection
|
||||||
include Disposable
|
include Disposable
|
||||||
include SessionMethods(Connection, Statement)
|
include SessionMethods(Connection, Statement)
|
||||||
|
include BeginTransaction
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
getter database
|
getter database
|
||||||
@statements_cache = StringKeyCache(Statement).new
|
@statements_cache = StringKeyCache(Statement).new
|
||||||
|
@transaction = false
|
||||||
getter? prepared_statements : Bool
|
getter? prepared_statements : Bool
|
||||||
|
|
||||||
def initialize(@database : Database)
|
def initialize(@database : Database)
|
||||||
|
@ -42,10 +44,45 @@ module DB
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
abstract def build_unprepared_statement(query) : Statement
|
abstract def build_unprepared_statement(query) : Statement
|
||||||
|
|
||||||
|
def begin_transaction
|
||||||
|
raise DB::Error.new("There is an existing transaction in this connection") if @transaction
|
||||||
|
@transaction = true
|
||||||
|
create_transaction
|
||||||
|
end
|
||||||
|
|
||||||
|
protected def create_transaction : Transaction
|
||||||
|
TopLevelTransaction.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
protected def do_close
|
protected def do_close
|
||||||
@statements_cache.each_value &.close
|
@statements_cache.each_value &.close
|
||||||
@statements_cache.clear
|
@statements_cache.clear
|
||||||
@database.pool.delete self
|
@database.pool.delete self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
|
def release_from_statement
|
||||||
|
@database.return_to_pool(self) unless @transaction
|
||||||
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
|
def release_from_transaction
|
||||||
|
@transaction = false
|
||||||
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
|
def perform_begin_transaction
|
||||||
|
self.unprepared.exec "BEGIN"
|
||||||
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
|
def perform_commit_transaction
|
||||||
|
self.unprepared.exec "COMMIT"
|
||||||
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
|
def perform_rollback_transaction
|
||||||
|
self.unprepared.exec "ROLLBACK"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -105,6 +105,14 @@ module DB
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def transaction
|
||||||
|
using_connection do |cnn|
|
||||||
|
cnn.transaction do |tx|
|
||||||
|
yield tx
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
def retry
|
def retry
|
||||||
@pool.retry do
|
@pool.retry do
|
||||||
|
|
|
@ -17,4 +17,7 @@ module DB
|
||||||
def initialize(@connection)
|
def initialize(@connection)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class Rollback < Exception
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -59,7 +59,7 @@ module DB
|
||||||
end
|
end
|
||||||
|
|
||||||
def release_connection
|
def release_connection
|
||||||
@connection.database.return_to_pool(@connection)
|
@connection.release_from_statement
|
||||||
end
|
end
|
||||||
|
|
||||||
# See `QueryMethods#exec`
|
# See `QueryMethods#exec`
|
||||||
|
|
43
src/db/transaction.cr
Normal file
43
src/db/transaction.cr
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
module DB
|
||||||
|
abstract class Transaction
|
||||||
|
include Disposable
|
||||||
|
|
||||||
|
abstract def connection : Connection
|
||||||
|
|
||||||
|
def commit
|
||||||
|
close!
|
||||||
|
end
|
||||||
|
|
||||||
|
def rollback
|
||||||
|
close!
|
||||||
|
end
|
||||||
|
|
||||||
|
private def close!
|
||||||
|
raise DB::Error.new("Transaction already closed") if closed?
|
||||||
|
close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TopLevelTransaction < Transaction
|
||||||
|
# :nodoc:
|
||||||
|
getter connection
|
||||||
|
|
||||||
|
def initialize(@connection : Connection)
|
||||||
|
@connection.perform_begin_transaction
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit
|
||||||
|
@connection.perform_commit_transaction
|
||||||
|
close!
|
||||||
|
end
|
||||||
|
|
||||||
|
def rollback
|
||||||
|
@connection.perform_rollback_transaction
|
||||||
|
close!
|
||||||
|
end
|
||||||
|
|
||||||
|
protected def do_close
|
||||||
|
connection.release_from_transaction
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue