v / vlib / orm / transaction.v
249 lines · 221 sloc · 6.26 KB · 83b622f651c1df985ef933e49692445526b99ce3
Raw
1module orm
2
3const tx_callback_closed_err = 'transaction callback must not close the transaction explicitly'
4
5enum TxKind {
6 root
7 savepoint
8}
9
10@[heap]
11struct TxRootState {
12mut:
13 next_savepoint_id int = 1
14 active bool = true
15}
16
17@[heap]
18struct TxInner {
19mut:
20 conn TransactionalConnection
21 kind TxKind
22 active bool = true
23 savepoint_name string
24 root_state &TxRootState = unsafe { nil }
25}
26
27// Tx is an ORM transaction handle that can be used anywhere an orm.Connection is accepted.
28pub struct Tx {
29mut:
30 inner &TxInner = unsafe { nil }
31}
32
33@[heap]
34struct SavepointInner {
35mut:
36 conn TransactionalConnection
37 owner &TxInner = unsafe { nil }
38 name string
39 active bool = true
40}
41
42// Savepoint is a manual savepoint handle created from an active transaction.
43pub struct Savepoint {
44mut:
45 inner &SavepointInner = unsafe { nil }
46}
47
48// begin starts a new ORM transaction on the provided connection.
49pub fn begin(mut conn TransactionalConnection) !Tx {
50 conn.orm_begin()!
51 return Tx{
52 inner: &TxInner{
53 conn: conn
54 kind: .root
55 root_state: &TxRootState{}
56 }
57 }
58}
59
60// transaction runs a callback inside a transaction and commits or rolls back automatically.
61pub fn transaction[T](mut conn TransactionalConnection, f fn (mut Tx) !T) !T {
62 mut tx := begin(mut conn)!
63 result := f(mut tx) or {
64 callback_err := err
65 if !tx.is_active() {
66 return error(tx_callback_closed_err)
67 }
68 tx.rollback() or {
69 return error('transaction callback failed: ${callback_err.msg()}; rollback failed: ${err.msg()}')
70 }
71 return callback_err
72 }
73 if !tx.is_active() {
74 return error(tx_callback_closed_err)
75 }
76 tx.commit()!
77 return result
78}
79
80// commit commits an active transaction.
81pub fn (mut tx Tx) commit() ! {
82 tx.ensure_active('commit')!
83 match tx.inner.kind {
84 .root {
85 tx.inner.conn.orm_commit()!
86 tx.inner.root_state.active = false
87 }
88 .savepoint {
89 tx.inner.conn.orm_release_savepoint(tx.inner.savepoint_name)!
90 }
91 }
92
93 tx.inner.active = false
94}
95
96// rollback rolls back an active transaction.
97pub fn (mut tx Tx) rollback() ! {
98 tx.ensure_active('rollback')!
99 match tx.inner.kind {
100 .root {
101 tx.inner.conn.orm_rollback()!
102 tx.inner.root_state.active = false
103 }
104 .savepoint {
105 tx.inner.conn.orm_rollback_to(tx.inner.savepoint_name)!
106 tx.inner.conn.orm_release_savepoint(tx.inner.savepoint_name)!
107 }
108 }
109
110 tx.inner.active = false
111}
112
113// savepoint creates a manual savepoint inside an active transaction.
114pub fn (mut tx Tx) savepoint() !Savepoint {
115 tx.ensure_active('create a savepoint')!
116 name := tx.next_savepoint_name()!
117 tx.inner.conn.orm_savepoint(name)!
118 return Savepoint{
119 inner: &SavepointInner{
120 conn: tx.inner.conn
121 owner: tx.inner
122 name: name
123 }
124 }
125}
126
127// transaction runs a nested transaction backed by a savepoint.
128pub fn (mut tx Tx) transaction[T](f fn (mut Tx) !T) !T {
129 tx.ensure_active('start a nested transaction')!
130 name := tx.next_savepoint_name()!
131 tx.inner.conn.orm_savepoint(name)!
132 mut nested := Tx{
133 inner: &TxInner{
134 conn: tx.inner.conn
135 kind: .savepoint
136 savepoint_name: name
137 root_state: tx.inner.root_state
138 }
139 }
140 result := f(mut nested) or {
141 callback_err := err
142 if !nested.is_active() {
143 return error(tx_callback_closed_err)
144 }
145 nested.rollback() or {
146 return error('transaction callback failed: ${callback_err.msg()}; rollback failed: ${err.msg()}')
147 }
148 return callback_err
149 }
150 if !nested.is_active() {
151 return error(tx_callback_closed_err)
152 }
153 nested.commit()!
154 return result
155}
156
157// rollback rolls back to the savepoint and releases it.
158pub fn (mut sp Savepoint) rollback() ! {
159 sp.ensure_active('rollback')!
160 sp.inner.conn.orm_rollback_to(sp.inner.name)!
161 sp.inner.conn.orm_release_savepoint(sp.inner.name)!
162 sp.inner.active = false
163}
164
165// release releases the savepoint without rolling back.
166pub fn (mut sp Savepoint) release() ! {
167 sp.ensure_active('release')!
168 sp.inner.conn.orm_release_savepoint(sp.inner.name)!
169 sp.inner.active = false
170}
171
172fn (tx Tx) is_active() bool {
173 return !isnil(tx.inner) && !isnil(tx.inner.root_state) && tx.inner.active
174 && tx.inner.root_state.active
175}
176
177fn (tx Tx) ensure_active(action string) ! {
178 if isnil(tx.inner) {
179 return error('transaction is not initialized')
180 }
181 if !tx.inner.active {
182 return error('transaction is inactive; cannot ${action}')
183 }
184}
185
186fn (mut tx Tx) next_savepoint_name() !string {
187 if isnil(tx.inner) || isnil(tx.inner.root_state) {
188 return error('transaction is not initialized')
189 }
190 name := 'v_orm_sp_${tx.inner.root_state.next_savepoint_id}'
191 tx.inner.root_state.next_savepoint_id++
192 return name
193}
194
195fn (sp Savepoint) ensure_active(action string) ! {
196 if isnil(sp.inner) {
197 return error('savepoint is not initialized')
198 }
199 owner_tx := Tx{
200 inner: sp.inner.owner
201 }
202 if !sp.inner.active || isnil(sp.inner.owner) || !owner_tx.is_active() {
203 return error('savepoint is inactive; cannot ${action}')
204 }
205}
206
207// select forwards ORM select queries through the active transaction.
208pub fn (mut tx Tx) select(config SelectConfig, data QueryData, where QueryData) ![][]Primitive {
209 tx.ensure_active('use the transaction')!
210 return tx.inner.conn.select(config, data, where)
211}
212
213// insert forwards ORM insert queries through the active transaction.
214pub fn (mut tx Tx) insert(table Table, data QueryData) ! {
215 tx.ensure_active('use the transaction')!
216 tx.inner.conn.insert(table, data)!
217}
218
219// update forwards ORM update queries through the active transaction.
220pub fn (mut tx Tx) update(table Table, data QueryData, where QueryData) ! {
221 tx.ensure_active('use the transaction')!
222 tx.inner.conn.update(table, data, where)!
223}
224
225// delete forwards ORM delete queries through the active transaction.
226pub fn (mut tx Tx) delete(table Table, where QueryData) ! {
227 tx.ensure_active('use the transaction')!
228 tx.inner.conn.delete(table, where)!
229}
230
231// create forwards ORM create queries through the active transaction.
232pub fn (mut tx Tx) create(table Table, fields []TableField) ! {
233 tx.ensure_active('use the transaction')!
234 tx.inner.conn.create(table, fields)!
235}
236
237// drop forwards ORM drop queries through the active transaction.
238pub fn (mut tx Tx) drop(table Table) ! {
239 tx.ensure_active('use the transaction')!
240 tx.inner.conn.drop(table)!
241}
242
243// last_id forwards the last inserted id through the wrapped connection.
244pub fn (mut tx Tx) last_id() int {
245 if isnil(tx.inner) {
246 return 0
247 }
248 return tx.inner.conn.last_id()
249}
250