-
-
Notifications
You must be signed in to change notification settings - Fork 54
/
transaction.cr
131 lines (107 loc) · 3.61 KB
/
transaction.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
module DB
# Transactions should be started from `DB#transaction`, `Connection#transaction`
# or `Connection#begin_transaction`.
#
# Use `Transaction#connection` to submit statements to the database.
#
# Use `Transaction#commit` or `Transaction#rollback` to close the ongoing transaction
# explicitly. Or refer to `BeginTransaction#transaction` for documentation on how to
# use `#transaction(&block)` methods in `DB` and `Connection`.
#
# Nested transactions are supported by using sql `SAVEPOINT`. To start a nested
# transaction use `Transaction#transaction` or `Transaction#begin_transaction`.
#
abstract class Transaction
include Disposable
include BeginTransaction
abstract def connection : Connection
# commits the current transaction
def commit
close!
end
# rollbacks the current transaction
def rollback
close!
end
private def close!
raise DB::Error.new("Transaction already closed") if closed?
close
end
abstract def release_from_nested_transaction
end
class TopLevelTransaction < Transaction
getter connection
# :nodoc:
property savepoint_name : String? = nil
def initialize(@connection : Connection)
@nested_transaction = false
@connection.perform_begin_transaction
end
def commit
@connection.perform_commit_transaction
super
end
def rollback
@connection.perform_rollback_transaction
super
end
protected def do_close
connection.release_from_transaction
end
def begin_transaction : Transaction
raise DB::Error.new("There is an existing nested transaction in this transaction") if @nested_transaction
@nested_transaction = true
create_save_point_transaction(self)
end
# :nodoc:
def create_save_point_transaction(parent : Transaction) : SavePointTransaction
# TODO should we wrap this in a mutex?
previous_savepoint = @savepoint_name
savepoint_name = if previous_savepoint
previous_savepoint.succ
else
# random prefix to avoid determinism
"cr_#{@connection.object_id}_#{Random.rand(10_000)}_00001"
end
@savepoint_name = savepoint_name
create_save_point_transaction(parent, savepoint_name)
end
protected def create_save_point_transaction(parent : Transaction, savepoint_name : String) : SavePointTransaction
SavePointTransaction.new(parent, savepoint_name)
end
# :nodoc:
def release_from_nested_transaction
@nested_transaction = false
end
end
class SavePointTransaction < Transaction
getter connection : Connection
def initialize(@parent : Transaction, @savepoint_name : String)
@nested_transaction = false
@connection = @parent.connection
@connection.perform_create_savepoint(@savepoint_name)
end
def commit
@connection.perform_release_savepoint(@savepoint_name)
super
end
def rollback
@connection.perform_rollback_savepoint(@savepoint_name)
super
end
protected def do_close
@parent.release_from_nested_transaction
end
def begin_transaction : Transaction
raise DB::Error.new("There is an existing nested transaction in this transaction") if @nested_transaction
@nested_transaction = true
create_save_point_transaction(self)
end
def create_save_point_transaction(parent : Transaction)
@parent.create_save_point_transaction(parent)
end
def release_from_nested_transaction
@nested_transaction = false
end
end
end