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}