Skip to content

Commit e5f9915

Browse files
authored
Merge pull request #35 from rqlite/changeset-support
Add support for Sessions and Changesets
2 parents 6380ea0 + 422b6bf commit e5f9915

File tree

3 files changed

+500
-0
lines changed

3 files changed

+500
-0
lines changed

sqlite3.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ package sqlite3
2727
#cgo CFLAGS: -DSQLITE_ENABLE_DBSTAT_VTAB
2828
#cgo CFLAGS: -DSQLITE_MEMDB_DEFAULT_MAXSIZE=2147483648
2929
#cgo CFLAGS: -DSQLITE_ENABLE_GEOPOLY=1
30+
#cgo CFLAGS: -DSQLITE_ENABLE_SESSION
31+
#cgo CFLAGS: -DSQLITE_ENABLE_PREUPDATE_HOOK
3032
#cgo CFLAGS: -Wno-deprecated-declarations
3133
#cgo openbsd CFLAGS: -I/usr/local/include
3234
#cgo openbsd LDFLAGS: -L/usr/local/lib

sqlite3_session.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
//go:build !sqlite_omit_session
2+
// +build !sqlite_omit_session
3+
4+
package sqlite3
5+
6+
/*
7+
#cgo CFLAGS: -DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK
8+
#cgo LDFLAGS: -lm
9+
*/
10+
11+
/*
12+
#ifndef USE_LIBSQLITE3
13+
#include "sqlite3-binding.h"
14+
#else
15+
#include <sqlite3.h>
16+
#endif
17+
#include <stdlib.h>
18+
#include <stdint.h>
19+
*/
20+
import "C"
21+
import (
22+
"fmt"
23+
"unsafe"
24+
)
25+
26+
type Session struct {
27+
session *C.sqlite3_session
28+
}
29+
30+
// CreateSession creates a new session object.
31+
func (c *SQLiteConn) CreateSession(dbName string) (*Session, error) {
32+
cDbName := C.CString(dbName)
33+
defer C.free(unsafe.Pointer(cDbName))
34+
35+
var session *C.sqlite3_session
36+
rc := C.sqlite3session_create(c.db, cDbName, &session)
37+
if rc != C.SQLITE_OK {
38+
return nil, fmt.Errorf("sqlite3session_create: %s", C.GoString(C.sqlite3_errstr(rc)))
39+
}
40+
return &Session{session: session}, nil
41+
}
42+
43+
// AttachSession attaches a session object to a table or all tables.
44+
func (s *Session) AttachSession(tableName string) error {
45+
var cTableName *C.char
46+
if tableName != "" {
47+
cTableName = C.CString(tableName)
48+
defer C.free(unsafe.Pointer(cTableName))
49+
}
50+
51+
rc := C.sqlite3session_attach(s.session, cTableName)
52+
if rc != C.SQLITE_OK {
53+
return fmt.Errorf("sqlite3session_attach: %s", C.GoString(C.sqlite3_errstr(rc)))
54+
}
55+
return nil
56+
}
57+
58+
// Delete deletes a session object.
59+
func (s *Session) DeleteSession() error {
60+
if s.session != nil {
61+
C.sqlite3session_delete(s.session)
62+
s.session = nil
63+
}
64+
return nil
65+
}
66+
67+
// Changeset represents a changeset object.
68+
type Changeset struct {
69+
b []byte
70+
}
71+
72+
// NewChangeset returns a changeset from a session object.
73+
func NewChangeset(s *Session) (*Changeset, error) {
74+
var nChangeset C.int
75+
var pChangeset unsafe.Pointer
76+
77+
rc := C.sqlite3session_changeset(s.session, &nChangeset, &pChangeset)
78+
if rc != C.SQLITE_OK {
79+
return nil, fmt.Errorf("sqlite3session_changeset: %s", C.GoString(C.sqlite3_errstr(rc)))
80+
}
81+
defer C.sqlite3_free(pChangeset)
82+
83+
// Copy the changeset buffer to a Go byte slice, because cgo
84+
// does not support Go slices with C memory.
85+
changeset := C.GoBytes(pChangeset, nChangeset)
86+
return &Changeset{b: changeset}, nil
87+
}
88+
89+
// ChangesetIterator represents a changeset iterator object.
90+
type ChangesetIterator struct {
91+
iter *C.sqlite3_changeset_iter
92+
}
93+
94+
// NewChangesetIterator creates a new changeset iterator object.
95+
func NewChangesetIterator(cs *Changeset) (*ChangesetIterator, error) {
96+
var iter *C.sqlite3_changeset_iter
97+
ptr := unsafe.Pointer(nil)
98+
if len(cs.b) > 0 {
99+
ptr = unsafe.Pointer(&cs.b[0])
100+
}
101+
rc := C.sqlite3changeset_start(&iter, C.int(len(cs.b)), ptr)
102+
if rc != C.SQLITE_OK {
103+
return nil, fmt.Errorf("sqlite3changeset_start: %s", C.GoString(C.sqlite3_errstr(rc)))
104+
}
105+
return &ChangesetIterator{iter: iter}, nil
106+
}
107+
108+
// Next moves the changeset iterator to the next change.
109+
func (ci *ChangesetIterator) Next() (bool, error) {
110+
rc := C.sqlite3changeset_next(ci.iter)
111+
if rc == C.SQLITE_DONE {
112+
return false, nil // No more changes
113+
}
114+
if rc != C.SQLITE_ROW {
115+
return false, fmt.Errorf("sqlite3changeset_next: %s", C.GoString(C.sqlite3_errstr(rc)))
116+
}
117+
return true, nil
118+
}
119+
120+
// Op returns the current Operation from a Changeset Iterator
121+
func (ci *ChangesetIterator) Op() (tblName string, numCol int, oper int, indirect bool, err error) {
122+
var tableName *C.char
123+
var nCol C.int
124+
var op C.int
125+
var ind C.int
126+
127+
rc := C.sqlite3changeset_op(ci.iter, &tableName, &nCol, &op, &ind)
128+
if rc != C.SQLITE_OK {
129+
return "", 0, 0, false, fmt.Errorf("sqlite3changeset_op: %s", C.GoString(C.sqlite3_errstr(rc)))
130+
}
131+
return C.GoString(tableName), int(nCol), int(op), ind != 0, nil
132+
}
133+
134+
// Old retrieves the old value for the specified column in the change payload.
135+
func (ci *ChangesetIterator) Old(dest []any) error {
136+
return ci.row(dest, true)
137+
}
138+
139+
// New retrieves the new value for the specified column in the change payload.
140+
func (ci *ChangesetIterator) New(dest []any) error {
141+
return ci.row(dest, false)
142+
}
143+
144+
// Finalize deletes a changeset iterator.
145+
func (ci *ChangesetIterator) Finalize() error {
146+
if ci.iter != nil {
147+
rc := C.sqlite3changeset_finalize(ci.iter)
148+
ci.iter = nil
149+
if rc != C.SQLITE_OK {
150+
return fmt.Errorf("sqlite3changeset_finalize: %s", C.GoString(C.sqlite3_errstr(rc)))
151+
}
152+
}
153+
return nil
154+
}
155+
156+
// New retrieves the new value for the specified column in the change payload.
157+
func (ci *ChangesetIterator) row(dest []any, old bool) error {
158+
var val *C.sqlite3_value
159+
var rc C.int
160+
for i := 0; i < len(dest); i++ {
161+
fn := ""
162+
if old {
163+
fn = "old"
164+
rc = C.sqlite3changeset_old(ci.iter, C.int(i), &val)
165+
} else {
166+
fn = "new"
167+
rc = C.sqlite3changeset_new(ci.iter, C.int(i), &val)
168+
}
169+
if rc != C.SQLITE_OK {
170+
return fmt.Errorf("sqlite3changeset_%s: %s", fn, C.GoString(C.sqlite3_errstr(rc)))
171+
}
172+
173+
switch C.sqlite3_value_type(val) {
174+
case C.SQLITE_INTEGER:
175+
dest[i] = int64(C.sqlite3_value_int64(val))
176+
case C.SQLITE_FLOAT:
177+
dest[i] = float64(C.sqlite3_value_double(val))
178+
case C.SQLITE_BLOB:
179+
len := C.sqlite3_value_bytes(val)
180+
blobptr := C.sqlite3_value_blob(val)
181+
dest[i] = C.GoBytes(blobptr, len)
182+
case C.SQLITE_TEXT:
183+
cstrptr := unsafe.Pointer(C.sqlite3_value_text(val))
184+
dest[i] = C.GoString((*C.char)(cstrptr))
185+
case C.SQLITE_NULL:
186+
dest[i] = nil
187+
}
188+
}
189+
return nil
190+
}

0 commit comments

Comments
 (0)