// Copyright (c) 2018 David Crawshaw // Copyright (c) 2021 Ross Light // // 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 }