status-go/vendor/zombiezen.com/go/sqlite/session.go

916 lines
30 KiB
Go
Raw Permalink Normal View History

2022-03-10 09:44:48 +00:00
// Copyright (c) 2018 David Crawshaw <david@zentus.com>
// Copyright (c) 2021 Ross Light <ross@zombiezen.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// SPDX-License-Identifier: ISC
package sqlite
import (
"fmt"
"io"
"io/ioutil"
"runtime"
"sync"
"unsafe"
"modernc.org/libc"
"modernc.org/libc/sys/types"
lib "modernc.org/sqlite/lib"
)
// A Session tracks database changes made by a Conn.
// It is used to build changesets.
//
// For more details: https://www.sqlite.org/sessionintro.html
type Session struct {
tls *libc.TLS
ptr uintptr
}
// CreateSession creates a new session object.
// If db is "", then a default of "main" is used.
// It is the caller's responsibility to call Delete when the session is
// no longer needed.
//
// https://www.sqlite.org/session/sqlite3session_create.html
func (c *Conn) CreateSession(db string) (*Session, error) {
if c == nil {
return nil, fmt.Errorf("sqlite: create session: nil connection")
}
var cdb uintptr
if db == "" || db == "main" {
cdb = mainCString
} else {
var err error
cdb, err = libc.CString(db)
if err != nil {
return nil, fmt.Errorf("sqlite: create session: %w", err)
}
defer libc.Xfree(c.tls, cdb)
}
doublePtr, err := malloc(c.tls, ptrSize)
if err != nil {
return nil, fmt.Errorf("sqlite: create session: %w", err)
}
defer libc.Xfree(c.tls, doublePtr)
res := ResultCode(lib.Xsqlite3session_create(c.tls, c.conn, cdb, doublePtr))
if err := reserr(res); err != nil {
return nil, fmt.Errorf("sqlite: create session: %w", err)
}
s := &Session{
tls: c.tls,
ptr: *(*uintptr)(unsafe.Pointer(doublePtr)),
}
runtime.SetFinalizer(s, func(s *Session) {
if s.ptr != 0 {
panic("open *sqlite.Session garbage collected, call Delete method")
}
})
return s, nil
}
// Delete releases any resources associated with the session.
// It must be called before closing the Conn the session is attached to.
func (s *Session) Delete() {
if s.ptr == 0 {
panic("Session.Delete called twice on same session")
}
lib.Xsqlite3session_delete(s.tls, s.ptr)
s.ptr = 0
s.tls = nil
}
// Enable enables recording of changes after a previous call to Disable.
// New sessions start enabled.
//
// https://www.sqlite.org/session/sqlite3session_enable.html
func (s *Session) Enable() {
if s.ptr == 0 {
panic("Session.Enable called on deleted session")
}
lib.Xsqlite3session_enable(s.tls, s.ptr, 1)
}
// Disable disables recording of changes.
//
// https://www.sqlite.org/session/sqlite3session_enable.html
func (s *Session) Disable() {
if s.ptr == 0 {
panic("Session.Disable called on deleted session")
}
lib.Xsqlite3session_enable(s.tls, s.ptr, 0)
}
// Attach attaches a table to the session object.
// Changes made to the table will be tracked by the session.
// An empty tableName attaches all the tables in the database.
func (s *Session) Attach(tableName string) error {
if s.ptr == 0 {
return fmt.Errorf("sqlite: attach table %q to session: session deleted", tableName)
}
var ctable uintptr
if tableName != "" {
var err error
ctable, err = libc.CString(tableName)
if err != nil {
return fmt.Errorf("sqlite: attach table %q to session: %v", tableName, err)
}
defer libc.Xfree(s.tls, ctable)
}
res := ResultCode(lib.Xsqlite3session_attach(s.tls, s.ptr, ctable))
if err := reserr(res); err != nil {
return fmt.Errorf("sqlite: attach table %q to session: %w", tableName, err)
}
return nil
}
// Diff appends the difference between two tables (srcDB and the session DB) to
// the session. The two tables must have the same name and schema.
//
// https://www.sqlite.org/session/sqlite3session_diff.html
func (s *Session) Diff(srcDB, tableName string) error {
if s.ptr == 0 {
return fmt.Errorf("sqlite: diff table %q: session deleted", tableName)
}
errMsgPtr, err := malloc(s.tls, ptrSize)
if err != nil {
return fmt.Errorf("sqlite: diff table %q: %v", tableName, err)
}
defer libc.Xfree(s.tls, errMsgPtr)
csrcDB, err := libc.CString(srcDB)
if err != nil {
return fmt.Errorf("sqlite: diff table %q: %v", tableName, err)
}
defer libc.Xfree(s.tls, csrcDB)
ctable, err := libc.CString(tableName)
if err != nil {
return fmt.Errorf("sqlite: diff table %q: %v", tableName, err)
}
defer libc.Xfree(s.tls, ctable)
res := ResultCode(lib.Xsqlite3session_diff(s.tls, s.ptr, csrcDB, ctable, errMsgPtr))
if err := reserr(res); err != nil {
cerrMsg := *(*uintptr)(unsafe.Pointer(errMsgPtr))
if cerrMsg == 0 {
return fmt.Errorf("sqlite: diff table %q: %w", tableName, err)
}
errMsg := libc.GoString(cerrMsg)
lib.Xsqlite3_free(s.tls, cerrMsg)
return fmt.Errorf("sqlite: diff table %q: %w (%s)", tableName, err, errMsg)
}
return nil
}
// WriteChangeset generates a changeset from a session.
//
// https://www.sqlite.org/session/sqlite3session_changeset.html
func (s *Session) WriteChangeset(w io.Writer) error {
if s.ptr == 0 {
return fmt.Errorf("sqlite: write session changeset: session deleted")
}
xOutput, pOut := registerStreamWriter(w)
defer unregisterStreamWriter(pOut)
res := ResultCode(lib.Xsqlite3session_changeset_strm(s.tls, s.ptr, xOutput, pOut))
if err := reserr(res); err != nil {
return fmt.Errorf("sqlite: write session changeset: %w", err)
}
return nil
}
// WritePatchset generates a patchset from a session.
//
// https://www.sqlite.org/session/sqlite3session_patchset.html
func (s *Session) WritePatchset(w io.Writer) error {
if s.ptr == 0 {
return fmt.Errorf("sqlite: write session patchset: session deleted")
}
xOutput, pOut := registerStreamWriter(w)
defer unregisterStreamWriter(pOut)
res := ResultCode(lib.Xsqlite3session_patchset_strm(s.tls, s.ptr, xOutput, pOut))
if err := reserr(res); err != nil {
return fmt.Errorf("sqlite: write session patchset: %w", err)
}
return nil
}
// ApplyChangeset applies a changeset to the database.
//
// If filterFn is not nil and the changeset includes changes for a table for
// which the function reports false, then the changes are ignored. If filterFn
// is nil, then all changes are applied.
//
// If a changeset will not apply cleanly, then conflictFn will be called to
// resolve the conflict. See https://www.sqlite.org/session/sqlite3changeset_apply.html
// for more details.
func (c *Conn) ApplyChangeset(r io.Reader, filterFn func(tableName string) bool, conflictFn ConflictHandler) error {
if c == nil {
return fmt.Errorf("sqlite: apply changeset: nil connection")
}
if conflictFn == nil {
return fmt.Errorf("sqlite: apply changeset: no conflict handler provided")
}
xInput, pIn := registerStreamReader(r)
defer unregisterStreamReader(pIn)
appliesIDMu.Lock()
pCtx := appliesIDs.next()
appliesIDMu.Unlock()
applies.Store(pCtx, applyFuncs{
tls: c.tls,
filterFn: filterFn,
conflictFn: conflictFn,
})
defer func() {
applies.Delete(pCtx)
appliesIDMu.Lock()
appliesIDs.reclaim(pCtx)
appliesIDMu.Unlock()
}()
// The following are conversions from function values to uintptrs. It assumes
// the memory representation described in https://golang.org/s/go11func.
//
// It does this by doing the following in order:
// 1) Create a Go struct containing a pointer to a pointer to the functions.
// It is assumed that the pointer to the function will be
// stored in the read-only data section and thus will not move.
// 2) Convert the pointer to the Go struct to a pointer to uintptr through
// unsafe.Pointer. This is permitted via Rule #1 of unsafe.Pointer.
// 3) Dereference the pointer to uintptr to obtain the function value as a
// uintptr. This is safe as long as function values are passed as pointers.
xFilter := uintptr(0)
if filterFn != nil {
xFilter = *(*uintptr)(unsafe.Pointer(&struct {
f func(*libc.TLS, uintptr, uintptr) int32
}{changesetApplyFilter}))
}
xConflict := *(*uintptr)(unsafe.Pointer(&struct {
f func(*libc.TLS, uintptr, int32, uintptr) int32
}{changesetApplyConflict}))
res := ResultCode(lib.Xsqlite3changeset_apply_strm(c.tls, c.conn, xInput, pIn, xFilter, xConflict, pCtx))
if err := reserr(res); err != nil {
return fmt.Errorf("sqlite: apply changeset: %w", err)
}
return nil
}
type applyFuncs struct {
tls *libc.TLS
filterFn func(tableName string) bool
conflictFn ConflictHandler
}
var (
applies sync.Map // map[uintptr]applyFuncs
appliesIDMu sync.Mutex
appliesIDs idGen
)
func changesetApplyFilter(tls *libc.TLS, pCtx uintptr, zTab uintptr) int32 {
appliesValue, _ := applies.Load(pCtx)
funcs := appliesValue.(applyFuncs)
tableName := libc.GoString(zTab)
if funcs.filterFn(tableName) {
return 1
} else {
return 0
}
}
func changesetApplyConflict(tls *libc.TLS, pCtx uintptr, eConflict int32, p uintptr) int32 {
appliesValue, _ := applies.Load(pCtx)
funcs := appliesValue.(applyFuncs)
return int32(funcs.conflictFn(ConflictType(eConflict), &ChangesetIterator{
tls: tls,
ptr: p,
}))
}
// ApplyInverseChangeset applies the inverse of a changeset to the database.
// See ApplyChangeset and InvertChangeset for more details.
func (c *Conn) ApplyInverseChangeset(r io.Reader, filterFn func(tableName string) bool, conflictFn ConflictHandler) error {
if c == nil {
return fmt.Errorf("sqlite: apply changeset: nil connection")
}
pr, pw := io.Pipe()
go func() {
err := InvertChangeset(pw, pr)
pw.CloseWithError(err)
}()
err := c.ApplyChangeset(pr, filterFn, conflictFn)
io.Copy(ioutil.Discard, pr) // wait for invert goroutine to finish
return err
}
// InvertChangeset generates an inverted changeset. Applying an inverted
// changeset to a database reverses the effects of applying the uninverted
// changeset.
//
// This function currently assumes that the input is a valid changeset.
// If it is not, the results are undefined.
//
// https://www.sqlite.org/session/sqlite3changeset_invert.html
func InvertChangeset(w io.Writer, r io.Reader) error {
tls := libc.NewTLS()
defer tls.Close()
xInput, pIn := registerStreamReader(r)
defer unregisterStreamReader(pIn)
xOutput, pOut := registerStreamWriter(w)
defer unregisterStreamWriter(pOut)
res := ResultCode(lib.Xsqlite3changeset_invert_strm(tls, xInput, pIn, xOutput, pOut))
if err := reserr(res); err != nil {
return fmt.Errorf("sqlite: invert changeset: %w", err)
}
return nil
}
// ChangesetIterator is an iterator over a changeset.
type ChangesetIterator struct {
tls *libc.TLS
ptr uintptr
ownTLS bool
pIn uintptr // if non-zero, then must be unregistered at Finalize
}
// NewChangesetIterator returns a new iterator over the contents of the
// changeset. The caller is responsible for calling Finalize on the returned
// iterator.
//
// https://www.sqlite.org/session/sqlite3changeset_start.html
func NewChangesetIterator(r io.Reader) (*ChangesetIterator, error) {
tls := libc.NewTLS()
xInput, pIn := registerStreamReader(r)
pp, err := malloc(tls, ptrSize)
if err != nil {
unregisterStreamReader(pIn)
tls.Close()
return nil, fmt.Errorf("sqlite: start changeset iterator: %v", err)
}
defer libc.Xfree(tls, pp)
res := ResultCode(lib.Xsqlite3changeset_start_strm(tls, pp, xInput, pIn))
if err := reserr(res); err != nil {
unregisterStreamReader(pIn)
tls.Close()
return nil, fmt.Errorf("sqlite: start changeset iterator: %w", err)
}
iter := &ChangesetIterator{
tls: tls,
ownTLS: true,
ptr: *(*uintptr)(unsafe.Pointer(pp)),
pIn: pIn,
}
runtime.SetFinalizer(iter, func(iter *ChangesetIterator) {
if iter.ptr != 0 {
panic("open *sqlite.ChangesetIterator garbage collected, call Finalize method")
}
})
return iter, nil
}
// Close releases any resources associated with the iterator created with
// NewChangesetIterator.
func (iter *ChangesetIterator) Close() error {
if iter.ptr == 0 {
return fmt.Errorf("sqlite: finalize changeset iterator: called twice on same iterator")
}
res := ResultCode(lib.Xsqlite3changeset_finalize(iter.tls, iter.ptr))
iter.ptr = 0
if iter.ownTLS {
iter.tls.Close()
}
iter.tls = nil
if iter.pIn != 0 {
unregisterStreamReader(iter.pIn)
}
iter.pIn = 0
if err := reserr(res); err != nil {
return fmt.Errorf("sqlite: finalize changeset iterator: %w", err)
}
return nil
}
// Next advances the iterator to the next change in the changeset.
// It is an error to call Next on an iterator passed to an ApplyChangeset
// conflict handler.
//
// https://www.sqlite.org/session/sqlite3changeset_next.html
func (iter *ChangesetIterator) Next() (rowReturned bool, err error) {
res := ResultCode(lib.Xsqlite3changeset_next(iter.tls, iter.ptr))
switch res {
case ResultRow:
return true, nil
case ResultDone:
return false, nil
default:
return false, fmt.Errorf("sqlite: iterate changeset: %w", reserr(res))
}
}
// ChangesetOperation holds information about a change in a changeset.
type ChangesetOperation struct {
// Type is one of OpInsert, OpDelete, or OpUpdate.
Type OpType
// TableName is the name of the table affected by the change.
TableName string
// NumColumns is the number of columns in the table affected by the change.
NumColumns int
// Indirect is true if the session object "indirect" flag was set when the
// change was made or the change was made by an SQL trigger or foreign key
// action instead of directly as a result of a users SQL statement.
Indirect bool
}
// Operation obtains the current operation from the iterator.
//
// https://www.sqlite.org/session/sqlite3changeset_op.html
func (iter *ChangesetIterator) Operation() (*ChangesetOperation, error) {
if iter.ptr == 0 {
return nil, fmt.Errorf("sqlite: changeset iterator operation: iterator finalized")
}
pzTab, err := malloc(iter.tls, ptrSize)
if err != nil {
return nil, fmt.Errorf("sqlite: changeset iterator operation: %v", err)
}
defer libc.Xfree(iter.tls, pzTab)
pnCol, err := malloc(iter.tls, types.Size_t(unsafe.Sizeof(int32(0))))
if err != nil {
return nil, fmt.Errorf("sqlite: changeset iterator operation: %v", err)
}
defer libc.Xfree(iter.tls, pnCol)
pOp, err := malloc(iter.tls, types.Size_t(unsafe.Sizeof(int32(0))))
if err != nil {
return nil, fmt.Errorf("sqlite: changeset iterator operation: %v", err)
}
defer libc.Xfree(iter.tls, pOp)
pbIndirect, err := malloc(iter.tls, types.Size_t(unsafe.Sizeof(int32(0))))
if err != nil {
return nil, fmt.Errorf("sqlite: changeset iterator operation: %v", err)
}
defer libc.Xfree(iter.tls, pbIndirect)
res := ResultCode(lib.Xsqlite3changeset_op(iter.tls, iter.ptr, pzTab, pnCol, pOp, pbIndirect))
if err := reserr(res); err != nil {
return nil, fmt.Errorf("sqlite: changeset iterator operation: %w", err)
}
return &ChangesetOperation{
Type: OpType(*(*int32)(unsafe.Pointer(pOp))),
TableName: libc.GoString(*(*uintptr)(unsafe.Pointer(pzTab))),
NumColumns: int(*(*int32)(unsafe.Pointer(pnCol))),
Indirect: *(*int32)(unsafe.Pointer(pbIndirect)) != 0,
}, nil
}
// Old obtains the old row value from an iterator. Column indices start at 0.
// The returned value is valid until the iterator is finalized.
//
// https://www.sqlite.org/session/sqlite3changeset_old.html
func (iter *ChangesetIterator) Old(col int) (Value, error) {
if iter.ptr == 0 {
return Value{}, fmt.Errorf("sqlite: get changeset iterator value: iterator finalized")
}
ppValue, err := malloc(iter.tls, ptrSize)
if err != nil {
return Value{}, fmt.Errorf("sqlite: get changeset iterator value: %v", err)
}
defer libc.Xfree(iter.tls, ppValue)
res := ResultCode(lib.Xsqlite3changeset_old(iter.tls, iter.ptr, int32(col), ppValue))
if err := reserr(res); err != nil {
return Value{}, fmt.Errorf("sqlite: get changeset iterator value: %w", err)
}
return Value{
tls: iter.tls,
ptrOrType: *(*uintptr)(unsafe.Pointer(ppValue)),
}, nil
}
// New obtains the new row value from an iterator. Column indices start at 0.
// The returned value is valid until the iterator is finalized.
//
// https://www.sqlite.org/session/sqlite3changeset_new.html
func (iter *ChangesetIterator) New(col int) (Value, error) {
if iter.ptr == 0 {
return Value{}, fmt.Errorf("sqlite: get changeset iterator value: iterator finalized")
}
ppValue, err := malloc(iter.tls, ptrSize)
if err != nil {
return Value{}, fmt.Errorf("sqlite: get changeset iterator value: %v", err)
}
defer libc.Xfree(iter.tls, ppValue)
res := ResultCode(lib.Xsqlite3changeset_new(iter.tls, iter.ptr, int32(col), ppValue))
if err := reserr(res); err != nil {
return Value{}, fmt.Errorf("sqlite: get changeset iterator value: %w", err)
}
return Value{
tls: iter.tls,
ptrOrType: *(*uintptr)(unsafe.Pointer(ppValue)),
}, nil
}
// ConflictValue obtains the conflicting row value from an iterator.
// Column indices start at 0. The returned value is valid until the iterator is
// finalized.
//
// https://www.sqlite.org/session/sqlite3changeset_conflict.html
func (iter *ChangesetIterator) ConflictValue(col int) (Value, error) {
if iter.ptr == 0 {
return Value{}, fmt.Errorf("sqlite: get changeset iterator value: iterator finalized")
}
ppValue, err := malloc(iter.tls, ptrSize)
if err != nil {
return Value{}, fmt.Errorf("sqlite: get changeset iterator value: %v", err)
}
defer libc.Xfree(iter.tls, ppValue)
res := ResultCode(lib.Xsqlite3changeset_conflict(iter.tls, iter.ptr, int32(col), ppValue))
if err := reserr(res); err != nil {
return Value{}, fmt.Errorf("sqlite: get changeset iterator value: %w", err)
}
return Value{
tls: iter.tls,
ptrOrType: *(*uintptr)(unsafe.Pointer(ppValue)),
}, nil
}
// ForeignKeyConflicts returns the number of foreign key constraint violations.
//
// https://www.sqlite.org/session/sqlite3changeset_fk_conflicts.html
func (iter *ChangesetIterator) ForeignKeyConflicts() (int, error) {
pnOut, err := malloc(iter.tls, types.Size_t(unsafe.Sizeof(int32(0))))
if err != nil {
return 0, fmt.Errorf("sqlite: get number of foreign key conflicts: %v", err)
}
defer libc.Xfree(iter.tls, pnOut)
res := ResultCode(lib.Xsqlite3changeset_fk_conflicts(iter.tls, iter.ptr, pnOut))
if err := reserr(res); err != nil {
return 0, fmt.Errorf("sqlite: get number of foreign key conflicts: %w", err)
}
return int(*(*int32)(unsafe.Pointer(pnOut))), nil
}
// PrimaryKey returns a map of columns that make up the primary key.
//
// https://www.sqlite.org/session/sqlite3changeset_pk.html
func (iter *ChangesetIterator) PrimaryKey() ([]bool, error) {
pabPK, err := malloc(iter.tls, ptrSize)
if err != nil {
return nil, fmt.Errorf("sqlite: get primary key columns: %v", err)
}
defer libc.Xfree(iter.tls, pabPK)
pnCol, err := malloc(iter.tls, types.Size_t(unsafe.Sizeof(int32(0))))
if err != nil {
return nil, fmt.Errorf("sqlite: get primary key columns: %v", err)
}
defer libc.Xfree(iter.tls, pnCol)
res := ResultCode(lib.Xsqlite3changeset_pk(iter.tls, iter.ptr, pabPK, pnCol))
if err := reserr(res); err != nil {
return nil, fmt.Errorf("sqlite: get primary key columns: %w", err)
}
c := libc.GoBytes(*(*uintptr)(unsafe.Pointer(pabPK)), int(*(*int32)(unsafe.Pointer(pnCol))))
cols := make([]bool, len(c))
for i := range cols {
cols[i] = c[i] != 0
}
return cols, nil
}
// ConcatChangesets concatenates two changesets into a single changeset.
//
// https://www.sqlite.org/session/sqlite3changeset_concat.html
func ConcatChangesets(w io.Writer, changeset1, changeset2 io.Reader) error {
tls := libc.NewTLS()
defer tls.Close()
xInput1, pIn1 := registerStreamReader(changeset1)
defer unregisterStreamReader(pIn1)
xInput2, pIn2 := registerStreamReader(changeset2)
defer unregisterStreamReader(pIn2)
xOutput, pOut := registerStreamWriter(w)
defer unregisterStreamWriter(pOut)
res := ResultCode(lib.Xsqlite3changeset_concat_strm(tls, xInput1, pIn1, xInput2, pIn2, xOutput, pOut))
if err := reserr(res); err != nil {
return fmt.Errorf("sqlite: concatenate changesets: %w", err)
}
return nil
}
// A Changegroup is an object used to combine two or more changesets or
// patchesets. The zero value is an empty changegroup.
//
// https://www.sqlite.org/session/changegroup.html
type Changegroup struct {
tls *libc.TLS
ptr uintptr
}
// NewChangegroup returns a new changegroup. The caller is responsible for
// calling Clear on the returned changegroup.
//
// https://www.sqlite.org/session/sqlite3changegroup_new.html
//
// Deprecated: Use new(sqlite.Changegroup) instead, which does not require
// calling Clear until Add is called.
func NewChangegroup() (*Changegroup, error) {
cg := new(Changegroup)
if err := cg.init(); err != nil {
return nil, fmt.Errorf("sqlite: %w", err)
}
return cg, nil
}
func (cg *Changegroup) init() error {
if cg.tls == nil {
cg.tls = libc.NewTLS()
}
if cg.ptr == 0 {
pp, err := malloc(cg.tls, ptrSize)
if err != nil {
cg.tls.Close()
cg.tls = nil
return fmt.Errorf("init changegroup: %v", err)
}
defer libc.Xfree(cg.tls, pp)
res := ResultCode(lib.Xsqlite3changegroup_new(cg.tls, pp))
if err := reserr(res); err != nil {
cg.tls.Close()
cg.tls = nil
return fmt.Errorf("init changegroup: %w", err)
}
cg.ptr = *(*uintptr)(unsafe.Pointer(pp))
}
return nil
}
// Clear empties the changegroup and releases any resources associated with
// the changegroup. This method may be called multiple times.
func (cg *Changegroup) Clear() {
if cg == nil {
return
}
if cg.ptr != 0 {
lib.Xsqlite3changegroup_delete(cg.tls, cg.ptr)
cg.ptr = 0
}
if cg.tls != nil {
cg.tls.Close()
cg.tls = nil
}
}
// Add adds all changes within the changeset (or patchset) read from r to
// the changegroup. Once Add has been called, it is the caller's responsibility
// to call Clear.
//
// https://www.sqlite.org/session/sqlite3changegroup_add.html
func (cg *Changegroup) Add(r io.Reader) error {
if err := cg.init(); err != nil {
return fmt.Errorf("sqlite: add to changegroup: %w", err)
}
xInput, pIn := registerStreamReader(r)
defer unregisterStreamReader(pIn)
res := ResultCode(lib.Xsqlite3changegroup_add_strm(cg.tls, cg.ptr, xInput, pIn))
if err := reserr(res); err != nil {
return fmt.Errorf("sqlite: add to changegroup: %w", err)
}
return nil
}
// WriteTo writes the current contents of the changegroup to w.
//
// https://www.sqlite.org/session/sqlite3changegroup_output.html
func (cg *Changegroup) WriteTo(w io.Writer) (n int64, err error) {
// We want to allow uninitialized changegroups to write output without
// forcing the caller to call Clear. In theses cases, we initialize a new
// changegroup that lasts for the length of the WriteTo call.
if cg == nil {
cg = new(Changegroup)
}
if cg.ptr == 0 {
defer cg.Clear()
}
if err := cg.init(); err != nil {
return 0, fmt.Errorf("sqlite: write changegroup: %w", err)
}
wc := &writeCounter{Writer: w}
xOutput, pOut := registerStreamWriter(wc)
defer unregisterStreamWriter(pOut)
res := ResultCode(lib.Xsqlite3changegroup_output_strm(cg.tls, cg.ptr, xOutput, pOut))
if err := reserr(res); err != nil {
return wc.n, fmt.Errorf("sqlite: write changegroup: %w", err)
}
return wc.n, nil
}
// A ConflictHandler function determines the action to take to resolve a
// conflict while applying a changeset.
//
// https://www.sqlite.org/session/sqlite3changeset_apply.html
type ConflictHandler func(ConflictType, *ChangesetIterator) ConflictAction
// ConflictType is an enumeration of changeset conflict types.
//
// https://www.sqlite.org/session/c_changeset_conflict.html
type ConflictType int32
// Conflict types.
const (
ChangesetData = ConflictType(lib.SQLITE_CHANGESET_DATA)
ChangesetNotFound = ConflictType(lib.SQLITE_CHANGESET_NOTFOUND)
ChangesetConflict = ConflictType(lib.SQLITE_CHANGESET_CONFLICT)
ChangesetConstraint = ConflictType(lib.SQLITE_CHANGESET_CONSTRAINT)
ChangesetForeignKey = ConflictType(lib.SQLITE_CHANGESET_FOREIGN_KEY)
)
// String returns the C constant name of the conflict type.
func (code ConflictType) String() string {
switch code {
case ChangesetData:
return "SQLITE_CHANGESET_DATA"
case ChangesetNotFound:
return "SQLITE_CHANGESET_NOTFOUND"
case ChangesetConflict:
return "SQLITE_CHANGESET_CONFLICT"
case ChangesetConstraint:
return "SQLITE_CHANGESET_CONSTRAINT"
case ChangesetForeignKey:
return "SQLITE_CHANGESET_FOREIGN_KEY"
default:
return fmt.Sprintf("ConflictType(%d)", int32(code))
}
}
// ConflictAction is an enumeration of actions that can be taken in response to
// a changeset conflict. The zero value is ChangesetOmit.
//
// https://www.sqlite.org/session/c_changeset_abort.html
type ConflictAction int32
// Conflict actions.
const (
// ChangesetOmit signals that no special action should be taken. The change
// that caused the conflict will not be applied. The session module continues
// to the next change in the changeset.
ChangesetOmit = ConflictAction(lib.SQLITE_CHANGESET_OMIT)
// ChangesetAbort signals that any changes applied so far should be rolled
// back and the call to ApplyChangeset returns an error whose code
// is ResultAbort.
ChangesetAbort = ConflictAction(lib.SQLITE_CHANGESET_ABORT)
// ChangesetReplace signals a different action depending on the conflict type.
//
// If the conflict type is ChangesetData, ChangesetReplace signals the
// conflicting row should be updated or deleted.
//
// If the conflict type is ChangesetConflict, then ChangesetReplace signals
// that the conflicting row should be removed from the database and a second
// attempt to apply the change should be made. If this second attempt fails,
// the original row is restored to the database before continuing.
//
// For all other conflict types, returning ChangesetReplace will cause
// ApplyChangeset to roll back any changes applied so far and return an error
// whose code is ResultMisuse.
ChangesetReplace = ConflictAction(lib.SQLITE_CHANGESET_REPLACE)
)
// String returns the C constant name of the conflict action.
func (code ConflictAction) String() string {
switch code {
case ChangesetOmit:
return "SQLITE_CHANGESET_OMIT"
case ChangesetAbort:
return "SQLITE_CHANGESET_ABORT"
case ChangesetReplace:
return "SQLITE_CHANGESET_REPLACE"
default:
return fmt.Sprintf("ConflictAction(%d)", int32(code))
}
}
var (
streamReaders sync.Map // map[uintptr]io.Reader
streamReadersIDMu sync.Mutex
streamReadersIDs idGen
)
func registerStreamReader(r io.Reader) (xInput, pIn uintptr) {
// The following is a conversion from function value to uintptr. It assumes
// the memory representation described in https://golang.org/s/go11func.
//
// It does this by doing the following in order:
// 1) Create a Go struct containing a pointer to a pointer to sessionStreamInput.
// It is assumed that the pointer to sessionStreamInput will be
// stored in the read-only data section and thus will not move.
// 2) Convert the pointer to the Go struct to a pointer to uintptr through
// unsafe.Pointer. This is permitted via Rule #1 of unsafe.Pointer.
// 3) Dereference the pointer to uintptr to obtain the function value as a
// uintptr. This is safe as long as function values are passed as pointers.
xInput = *(*uintptr)(unsafe.Pointer(&struct {
f func(*libc.TLS, uintptr, uintptr, uintptr) int32
}{sessionStreamInput}))
streamReadersIDMu.Lock()
pIn = streamReadersIDs.next()
streamReadersIDMu.Unlock()
streamReaders.Store(pIn, r)
return
}
func unregisterStreamReader(pIn uintptr) {
streamReaders.Delete(pIn)
streamReadersIDMu.Lock()
streamReadersIDs.reclaim(pIn)
streamReadersIDMu.Unlock()
}
// sessionStreamInput is the callback returned by registerSessionReader used
// for the session streaming APIs.
// https://www.sqlite.org/session/sqlite3changegroup_add_strm.html
func sessionStreamInput(tls *libc.TLS, pIn uintptr, pData uintptr, pnData uintptr) int32 {
rval, _ := streamReaders.Load(pIn)
r, _ := rval.(io.Reader)
if r == nil {
return lib.SQLITE_MISUSE
}
n := int(*(*int32)(unsafe.Pointer(pnData)))
n, err := r.Read(libc.GoBytes(pData, n))
*(*int32)(unsafe.Pointer(pnData)) = int32(n)
if n == 0 && err != io.EOF {
// Readers should not return n == 0 && err == nil. However, as per io.Reader
// docs, we can't treat it as an EOF condition.
return lib.SQLITE_IOERR_READ
}
return lib.SQLITE_OK
}
var (
streamWriters sync.Map // map[uintptr]io.Writer
streamWritersIDMu sync.Mutex
streamWritersIDs idGen
)
func registerStreamWriter(w io.Writer) (xOutput, pOut uintptr) {
// The following is a conversion from function value to uintptr. It assumes
// the memory representation described in https://golang.org/s/go11func.
//
// It does this by doing the following in order:
// 1) Create a Go struct containing a pointer to a pointer to sessionStreamOutput.
// It is assumed that the pointer to sessionStreamOutput will be
// stored in the read-only data section and thus will not move.
// 2) Convert the pointer to the Go struct to a pointer to uintptr through
// unsafe.Pointer. This is permitted via Rule #1 of unsafe.Pointer.
// 3) Dereference the pointer to uintptr to obtain the function value as a
// uintptr. This is safe as long as function values are passed as pointers.
xOutput = *(*uintptr)(unsafe.Pointer(&struct {
f func(*libc.TLS, uintptr, uintptr, int32) int32
}{sessionStreamOutput}))
streamWritersIDMu.Lock()
pOut = streamWritersIDs.next()
streamWritersIDMu.Unlock()
streamWriters.Store(pOut, w)
return
}
func unregisterStreamWriter(pOut uintptr) {
streamWriters.Delete(pOut)
streamWritersIDMu.Lock()
streamWritersIDs.reclaim(pOut)
streamWritersIDMu.Unlock()
}
// sessionStreamOutput is the callback returned by registerSessionWriter used
// for the session streaming APIs.
// https://www.sqlite.org/session/sqlite3changegroup_add_strm.html
func sessionStreamOutput(tls *libc.TLS, pOut uintptr, pData uintptr, nData int32) int32 {
wval, _ := streamWriters.Load(pOut)
w, _ := wval.(io.Writer)
if w == nil {
return lib.SQLITE_MISUSE
}
_, err := w.Write(libc.GoBytes(pData, int(nData)))
if err != nil {
return lib.SQLITE_IOERR_WRITE
}
return lib.SQLITE_OK
}
type writeCounter struct {
io.Writer
n int64
}
func (wc *writeCounter) Write(p []byte) (int, error) {
n, err := wc.Writer.Write(p)
wc.n += int64(n)
return n, err
}