transaction.go
1package ygo
2
3/*
4#include "libyrs.h"
5#include <stdlib.h>
6*/
7import "C"
8import (
9 "fmt"
10 "unsafe"
11)
12
13// Transaction represents a read or read-write transaction on a document.
14// All operations on shared types happen within a transaction scope.
15type Transaction struct {
16 ptr *C.YTransaction
17 doc *Doc
18}
19
20// WithReadTransaction executes a callback within a read-only transaction.
21// The transaction is automatically committed after the callback completes.
22// Returns any error from the callback.
23func (d *Doc) WithReadTransaction(fn func(*Transaction) error) error {
24 if d.ptr == nil {
25 return ErrNilDocument
26 }
27
28 txn := C.ydoc_read_transaction(d.ptr)
29 if txn == nil {
30 return fmt.Errorf("failed to create read transaction: another transaction may be active")
31 }
32 t := &Transaction{ptr: txn, doc: d}
33
34 // Execute callback
35 err := fn(t)
36
37 // Always commit read transactions (they don't modify state)
38 t.Commit()
39
40 return err
41}
42
43// BeginRead starts a read-only transaction.
44// The caller must call Commit() when done to release resources.
45// Returns an error if another transaction is already active.
46//
47// Example:
48//
49// txn, err := doc.BeginRead()
50// if err != nil {
51// return err
52// }
53// defer txn.Commit()
54//
55// length := arr.Len()
56// // ... read operations ...
57func (d *Doc) BeginRead() (*Transaction, error) {
58 if d.ptr == nil {
59 return nil, ErrNilDocument
60 }
61
62 txn := C.ydoc_read_transaction(d.ptr)
63 if txn == nil {
64 return nil, fmt.Errorf("failed to create read transaction: another transaction may be active")
65 }
66
67 return &Transaction{ptr: txn, doc: d}, nil
68}
69
70// WithWriteTransaction executes a callback within a read-write transaction.
71// If the callback returns nil, the transaction is committed.
72// If the callback returns an error, the transaction is rolled back.
73// Returns any error from the callback.
74func (d *Doc) WithWriteTransaction(fn func(*Transaction) error) error {
75 return d.WithWriteTransactionWithOrigin(nil, fn)
76}
77
78// WithWriteTransactionWithOrigin executes a callback within a read-write transaction with an origin marker.
79// The origin can be used by event handlers and undo managers to identify change sources.
80// If the callback returns nil, the transaction is committed.
81// If the callback returns an error, the transaction is rolled back.
82func (d *Doc) WithWriteTransactionWithOrigin(origin []byte, fn func(*Transaction) error) error {
83 if d.ptr == nil {
84 return ErrNilDocument
85 }
86
87 var preStateVector []byte
88 if d.sync != nil {
89 err := d.WithReadTransaction(func(txn *Transaction) error {
90 sv := txn.GetStateVector()
91 if sv != nil {
92 preStateVector = sv.Data()
93 }
94 return nil
95 })
96 if err != nil {
97 return err
98 }
99 }
100
101 var originPtr *C.char
102 var originLen C.uint32_t
103 if len(origin) > 0 {
104 originPtr = (*C.char)(unsafe.Pointer(&origin[0]))
105 originLen = C.uint32_t(len(origin))
106 }
107
108 txn := C.ydoc_write_transaction(d.ptr, originLen, originPtr)
109 if txn == nil {
110 return fmt.Errorf("failed to create write transaction: another transaction may be active")
111 }
112 t := &Transaction{ptr: txn, doc: d}
113
114 err := fn(t)
115
116 if err != nil {
117 t.Rollback()
118 return err
119 }
120
121 t.Commit()
122
123 if d.sync != nil && len(preStateVector) > 0 {
124 sv := NewStateVectorFromBytes(preStateVector)
125 err = d.WithReadTransaction(func(txn *Transaction) error {
126 diff := txn.GetStateDiff(sv)
127 if diff != nil {
128 return d.sync.SendUpdate(diff.Data())
129 }
130 return nil
131 })
132 if err != nil {
133 return err
134 }
135 }
136
137 return nil
138}
139
140// BeginWrite starts a read-write transaction.
141// The caller must call Commit() to apply changes or Rollback() to abort.
142// Returns an error if another transaction is already active.
143//
144// Example:
145//
146// txn, err := doc.BeginWrite()
147// if err != nil {
148// return err
149// }
150// defer func() {
151// if err != nil {
152// txn.Rollback()
153// } else {
154// txn.Commit()
155// }
156// }()
157//
158// txt.Insert(txn, 0, "Hello")
159// // ... more write operations ...
160func (d *Doc) BeginWrite() (*Transaction, error) {
161 return d.BeginWriteWithOrigin(nil)
162}
163
164// BeginWriteWithOrigin starts a read-write transaction with an origin marker.
165// The origin can be used by event handlers and undo managers to identify change sources.
166// The caller must call Commit() to apply changes or Rollback() to abort.
167// Returns an error if another transaction is already active.
168func (d *Doc) BeginWriteWithOrigin(origin []byte) (*Transaction, error) {
169 if d.ptr == nil {
170 return nil, ErrNilDocument
171 }
172
173 var originPtr *C.char
174 var originLen C.uint32_t
175 if len(origin) > 0 {
176 originPtr = (*C.char)(unsafe.Pointer(&origin[0]))
177 originLen = C.uint32_t(len(origin))
178 }
179
180 txn := C.ydoc_write_transaction(d.ptr, originLen, originPtr)
181 if txn == nil {
182 return nil, fmt.Errorf("failed to create write transaction: another transaction may be active")
183 }
184
185 return &Transaction{ptr: txn, doc: d}, nil
186}
187
188// IsWriteable returns true if this is a read-write transaction.
189func (t *Transaction) IsWriteable() bool {
190 if t.ptr == nil {
191 return false
192 }
193 return C.ytransaction_writeable(t.ptr) != 0
194}
195
196// Commit finishes the transaction, releasing resources and triggering events.
197// For write transactions, this also performs storage compression.
198func (t *Transaction) Commit() {
199 if t.ptr != nil {
200 C.ytransaction_commit(t.ptr)
201 t.ptr = nil
202 }
203}
204
205// Rollback aborts the transaction without applying changes.
206// This is used internally when a callback returns an error.
207func (t *Transaction) Rollback() {
208 if t.ptr != nil {
209 C.ytransaction_commit(t.ptr) // yffi uses commit to end, even for rollback
210 t.ptr = nil
211 }
212}
213
214// ForceGC performs garbage collection of deleted blocks, even if GC was disabled.
215func (t *Transaction) ForceGC() {
216 if t.ptr != nil && t.IsWriteable() {
217 C.ytransaction_force_gc(t.ptr)
218 }
219}