-
Notifications
You must be signed in to change notification settings - Fork 2
/
transactions.go
425 lines (386 loc) · 12 KB
/
transactions.go
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
package antidoteclient
import (
"bytes"
"fmt"
)
// Represents a bucket in the Antidote database.
// Offers a high-level interface to issue read and write operations on objects in the bucket.
type Bucket struct {
Bucket []byte
}
// A transaction object offers low-level mechanisms to send protocol-buffer messages to Antidote in the context of
// a highly-available transaction.
// Typical representatives are interactive transactions handled by Antidote and static transactions handled on the client side.
type Transaction interface {
Read(objects ...*ApbBoundObject) (resp *ApbReadObjectsResp, err error)
Update(updates ...*ApbUpdateOp) error
}
// Type alias for byte-slices.
// Used to represent keys of objects in buckets and maps
type Key []byte
// A CRDTReader allows to read the value of objects identified by keys in the context of a transaction.
type CRDTReader interface {
// Read the value of a add-wins set identified by the given key
ReadSet(tx Transaction, key Key) (val [][]byte, err error)
// Read the value of a last-writer-wins register identified by the given key
ReadReg(tx Transaction, key Key) (val []byte, err error)
// Read the value of a add-wins map identified by the given key
ReadMap(tx Transaction, key Key) (val *MapReadResult, err error)
// Read the value of a multi-value register identified by the given key
ReadMVReg(tx Transaction, key Key) (val [][]byte, err error)
// Read the value of a counter identified by the given key
ReadCounter(tx Transaction, key Key) (val int32, err error)
}
// A transaction handled by Antidote on the server side.
// Interactive Transactions need to be started on the server and are kept open for their duration.
// Update operations are only visible to reads issued in the context of the same transaction or after committing the transaction.
// Always commit or abort interactive transactions to clean up the server side!
type InteractiveTransaction struct {
txID []byte
con *connection
committed bool
}
func (tx *InteractiveTransaction) Update(updates ...*ApbUpdateOp) error {
apbUpdate := &ApbUpdateObjects{
Updates: updates,
TransactionDescriptor: tx.txID,
}
err := apbUpdate.encode(tx.con)
if err != nil {
return err
}
resp, err := decodeOperationResp(tx.con)
if err != nil {
return err
}
if !(*resp.Success) {
return fmt.Errorf("operation not successful; error code %d", *resp.Errorcode)
}
return nil
}
func (tx *InteractiveTransaction) Read(objects ...*ApbBoundObject) (resp *ApbReadObjectsResp, err error) {
apbUpdate := &ApbReadObjects{
TransactionDescriptor: tx.txID,
Boundobjects: objects,
}
err = apbUpdate.encode(tx.con)
if err != nil {
return
}
return decodeReadObjectsResp(tx.con)
}
// commits the transaction, makes the updates issued under this transaction visible to subsequent transaction
// and cleans up the server side.
func (tx *InteractiveTransaction) Commit() error {
if !tx.committed {
msg := &ApbCommitTransaction{TransactionDescriptor: tx.txID}
err := msg.encode(tx.con)
if err != nil {
return err
}
op, err := decodeCommitResp(tx.con)
if err != nil {
return err
}
err = tx.con.Close()
if err != nil {
return err
}
if !(*op.Success) {
return fmt.Errorf("operation not successful; error code %d", *op.Errorcode)
}
}
return nil
}
// aborts the transactions, discards updates issued under this transaction
// and cleans up the server side.
// WARNING: May not be supported by the current version of Antidote
func (tx *InteractiveTransaction) Abort() error {
if !tx.committed {
msg := &ApbAbortTransaction{TransactionDescriptor: tx.txID}
err := msg.encode(tx.con)
if err != nil {
return err
}
op, err := decodeOperationResp(tx.con)
if err != nil {
return err
}
err = tx.con.Close()
if err != nil {
return err
}
if !(*op.Success) {
return fmt.Errorf("operation not successful; error code %d", *op.Errorcode)
}
}
return nil
}
// Pseudo transaction to issue reads and updated without starting an interactive transaction.
// Can be interpreted as starting a transaction for each read or update and directly committing it.
type StaticTransaction struct {
client *Client
}
func (tx *StaticTransaction) Update(updates ...*ApbUpdateOp) error {
apbStaticUpdate := &ApbStaticUpdateObjects{
Transaction: &ApbStartTransaction{Properties: &ApbTxnProperties{}},
Updates: updates,
}
con, err := tx.client.getConnection()
if err != nil {
return err
}
err = apbStaticUpdate.encode(con)
if err != nil {
return err
}
resp, err := decodeCommitResp(con)
if err != nil {
return err
}
err = con.Close()
if err != nil {
return err
}
if !(*resp.Success) {
return fmt.Errorf("operation not successful; error code %d", *resp.Errorcode)
}
return nil
}
func (tx *StaticTransaction) Read(objects ...*ApbBoundObject) (resp *ApbReadObjectsResp, err error) {
apbRead := &ApbStaticReadObjects{
Transaction: &ApbStartTransaction{Properties: &ApbTxnProperties{}},
Objects: objects,
}
con, err := tx.client.getConnection()
if err != nil {
return
}
err = apbRead.encode(con)
if err != nil {
return
}
sresp, err := decodeStaticReadObjectsResp(con)
if err != nil {
return
}
err = con.Close()
if err != nil {
return
}
return sresp.Objects, nil
}
func (bucket *Bucket) ReadSet(tx Transaction, key Key) (val [][]byte, err error) {
crdtType := CRDTType_ORSET
resp, err := tx.Read(&ApbBoundObject{Bucket: bucket.Bucket, Key: key, Type: &crdtType})
if err != nil {
return
}
val = resp.Objects[0].Set.Value
return
}
func (bucket *Bucket) ReadReg(tx Transaction, key Key) (val []byte, err error) {
crdtType := CRDTType_LWWREG
resp, err := tx.Read(&ApbBoundObject{Bucket: bucket.Bucket, Key: key, Type: &crdtType})
if err != nil {
return
}
val = resp.Objects[0].Reg.Value
return
}
func (bucket *Bucket) ReadMap(tx Transaction, key Key) (val *MapReadResult, err error) {
crdtType := CRDTType_RRMAP
resp, err := tx.Read(&ApbBoundObject{Bucket: bucket.Bucket, Key: key, Type: &crdtType})
if err != nil {
return
}
val = &MapReadResult{mapResp: resp.Objects[0].Map}
return
}
func (bucket *Bucket) ReadMVReg(tx Transaction, key Key) (val [][]byte, err error) {
crdtType := CRDTType_MVREG
resp, err := tx.Read(&ApbBoundObject{Bucket: bucket.Bucket, Key: key, Type: &crdtType})
if err != nil {
return
}
val = resp.Objects[0].Mvreg.Values
return
}
func (bucket *Bucket) ReadCounter(tx Transaction, key Key) (val int32, err error) {
crdtType := CRDTType_COUNTER
resp, err := tx.Read(&ApbBoundObject{Bucket: bucket.Bucket, Key: key, Type: &crdtType})
if err != nil {
return
}
val = *resp.Objects[0].Counter.Value
return
}
// Represents the result of reading from a map object.
// Grants access to the keys of the map to access values of the nested CRDTs.
type MapReadResult struct {
mapResp *ApbGetMapResp
}
// Access the value of the nested add-wins set under the given key
func (mrr *MapReadResult) Set(key Key) (val [][]byte, err error) {
for _, me := range mrr.mapResp.Entries {
if *me.Key.Type == CRDTType_ORSET && bytes.Equal(me.Key.Key, key) {
return me.Value.Set.Value, nil
}
}
return nil, fmt.Errorf("set entry with key '%s' not found", key)
}
// Access the value of the nested last-writer-wins register under the given key
func (mrr *MapReadResult) Reg(key Key) (val []byte, err error) {
for _, me := range mrr.mapResp.Entries {
if *me.Key.Type == CRDTType_LWWREG && bytes.Equal(me.Key.Key, key) {
return me.Value.Reg.Value, nil
}
}
return nil, fmt.Errorf("register entry with key '%s' not found", key)
}
// Access the value of the nested add-wins map under the given key
func (mrr *MapReadResult) Map(key Key) (val *MapReadResult, err error) {
for _, me := range mrr.mapResp.Entries {
if *me.Key.Type == CRDTType_RRMAP && bytes.Equal(me.Key.Key, key) {
return &MapReadResult{mapResp: me.Value.Map}, nil
}
}
return nil, fmt.Errorf("map entry with key '%s' not found", key)
}
// Access the value of the nested multi-value register under the given key
func (mrr *MapReadResult) MVReg(key Key) (val [][]byte, err error) {
for _, me := range mrr.mapResp.Entries {
if *me.Key.Type == CRDTType_MVREG && bytes.Equal(me.Key.Key, key) {
return me.Value.Mvreg.Values, nil
}
}
return nil, fmt.Errorf("map entry with key '%s' not found", key)
}
// Access the value of the nested counter under the given key
func (mrr *MapReadResult) Counter(key Key) (val int32, err error) {
for _, me := range mrr.mapResp.Entries {
if *me.Key.Type == CRDTType_COUNTER && bytes.Equal(me.Key.Key, key) {
return *me.Value.Counter.Value, nil
}
}
return 0, fmt.Errorf("counter entry with key '%s' not found", key)
}
// MapEntryKey represents the key and type of a map entry (embedded CRDT).
type MapEntryKey struct {
Key []byte
CrdtType CRDTType
}
// ListMapKeys gives access to the keys and types of map entries (embedded CRDTs).
func (mrr *MapReadResult) ListMapKeys() []MapEntryKey {
keyList := make([]MapEntryKey, len(mrr.mapResp.Entries))
for i, me := range mrr.mapResp.Entries {
keyList[i] = MapEntryKey{
Key: me.Key.Key,
CrdtType: *me.Key.Type,
}
}
return keyList
}
// Represents updates that can be converted to top-level updates applicable to a bucket
// or nested updates applicable to a map
type UpdateConverter interface {
ConvertToToplevel(bucket []byte) *ApbUpdateOp
ConvertToNested() *ApbMapNestedUpdate
}
// Represents updates of a specific key of a specific type.
// Can be applied to either buckets or maps
type CRDTUpdate struct {
Update *ApbUpdateOperation
Key Key
Type CRDTType
}
// A CRDTUpdater allows to apply updates in the context of a transaction.
type CRDTUpdater interface {
Update(tx Transaction, updates ...*CRDTUpdate) error
}
func (bucket *Bucket) Update(tx Transaction, updates ...*CRDTUpdate) error {
updateOps := make([]*ApbUpdateOp, len(updates))
for i, v := range updates {
updateOps[i] = v.ConvertToToplevel(bucket.Bucket)
}
return tx.Update(updateOps...)
}
func (update *CRDTUpdate) ConvertToToplevel(bucket []byte) *ApbUpdateOp {
return &ApbUpdateOp{
Boundobject: &ApbBoundObject{Key: update.Key, Type: &update.Type, Bucket: bucket},
Operation: update.Update,
}
}
func (update *CRDTUpdate) ConvertToNested() *ApbMapNestedUpdate {
return &ApbMapNestedUpdate{
Key: &ApbMapKey{Key: update.Key, Type: &update.Type},
Update: update.Update,
}
}
// CRDT update operations
// Represents the update to add an element to an add-wins set
func SetAdd(key Key, elems ...[]byte) *CRDTUpdate {
optype := ApbSetUpdate_ADD
return &CRDTUpdate{
Key: key,
Type: CRDTType_ORSET,
Update: &ApbUpdateOperation{
Setop: &ApbSetUpdate{Adds: elems, Optype: &optype},
},
}
}
// Represents the update to remove an element from an add-wins set
func SetRemove(key Key, elems ...[]byte) *CRDTUpdate {
optype := ApbSetUpdate_REMOVE
return &CRDTUpdate{
Key: key,
Type: CRDTType_ORSET,
Update: &ApbUpdateOperation{
Setop: &ApbSetUpdate{Rems: elems, Optype: &optype},
},
}
}
// Represents the update to increment a counter
func CounterInc(key Key, inc int64) *CRDTUpdate {
return &CRDTUpdate{
Key: key,
Type: CRDTType_COUNTER,
Update: &ApbUpdateOperation{
Counterop: &ApbCounterUpdate{Inc: &inc},
},
}
}
// Represents the update to write a value into an last-writer-wins register
func RegPut(key Key, value []byte) *CRDTUpdate {
return &CRDTUpdate{
Key: key,
Type: CRDTType_LWWREG,
Update: &ApbUpdateOperation{
Regop: &ApbRegUpdate{Value: value},
},
}
}
// Represents the update to write a value into an multi-value register
func MVRegPut(key Key, value []byte) *CRDTUpdate {
return &CRDTUpdate{
Key: key,
Type: CRDTType_MVREG,
Update: &ApbUpdateOperation{
Regop: &ApbRegUpdate{Value: value},
},
}
}
// Represents the update to nested objects of an add-wins map
func MapUpdate(key Key, updates ...*CRDTUpdate) *CRDTUpdate {
nupdates := make([]*ApbMapNestedUpdate, len(updates))
for i, v := range updates {
nupdates[i] = v.ConvertToNested()
}
return &CRDTUpdate{
Key: key,
Type: CRDTType_RRMAP,
Update: &ApbUpdateOperation{
Mapop: &ApbMapUpdate{Updates: nupdates},
},
}
}