summaryrefslogtreecommitdiff
path: root/vendor/github.com/ncruces/go-sqlite3/conn.go
diff options
context:
space:
mode:
authorLibravatar kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>2024-05-27 15:46:15 +0000
committerLibravatar GitHub <noreply@github.com>2024-05-27 17:46:15 +0200
commit1e7b32490dfdccddd04f46d4b0416b48d749d51b (patch)
tree62a11365933a5a11e0800af64cbdf9172e5e6e7a /vendor/github.com/ncruces/go-sqlite3/conn.go
parent[chore] Small styling + link issues (#2933) (diff)
downloadgotosocial-1e7b32490dfdccddd04f46d4b0416b48d749d51b.tar.xz
[experiment] add alternative wasm sqlite3 implementation available via build-tag (#2863)
This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
Diffstat (limited to 'vendor/github.com/ncruces/go-sqlite3/conn.go')
-rw-r--r--vendor/github.com/ncruces/go-sqlite3/conn.go426
1 files changed, 426 insertions, 0 deletions
diff --git a/vendor/github.com/ncruces/go-sqlite3/conn.go b/vendor/github.com/ncruces/go-sqlite3/conn.go
new file mode 100644
index 000000000..f170ccf57
--- /dev/null
+++ b/vendor/github.com/ncruces/go-sqlite3/conn.go
@@ -0,0 +1,426 @@
+package sqlite3
+
+import (
+ "context"
+ "fmt"
+ "math"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/ncruces/go-sqlite3/internal/util"
+ "github.com/ncruces/go-sqlite3/vfs"
+ "github.com/tetratelabs/wazero/api"
+)
+
+// Conn is a database connection handle.
+// A Conn is not safe for concurrent use by multiple goroutines.
+//
+// https://sqlite.org/c3ref/sqlite3.html
+type Conn struct {
+ *sqlite
+
+ interrupt context.Context
+ pending *Stmt
+ busy func(int) bool
+ log func(xErrorCode, string)
+ collation func(*Conn, string)
+ authorizer func(AuthorizerActionCode, string, string, string, string) AuthorizerReturnCode
+ update func(AuthorizerActionCode, string, string, int64)
+ commit func() bool
+ rollback func()
+ wal func(*Conn, string, int) error
+ arena arena
+
+ handle uint32
+}
+
+// Open calls [OpenFlags] with [OPEN_READWRITE], [OPEN_CREATE], [OPEN_URI] and [OPEN_NOFOLLOW].
+func Open(filename string) (*Conn, error) {
+ return newConn(filename, OPEN_READWRITE|OPEN_CREATE|OPEN_URI|OPEN_NOFOLLOW)
+}
+
+// OpenFlags opens an SQLite database file as specified by the filename argument.
+//
+// If none of the required flags is used, a combination of [OPEN_READWRITE] and [OPEN_CREATE] is used.
+// If a URI filename is used, PRAGMA statements to execute can be specified using "_pragma":
+//
+// sqlite3.Open("file:demo.db?_pragma=busy_timeout(10000)")
+//
+// https://sqlite.org/c3ref/open.html
+func OpenFlags(filename string, flags OpenFlag) (*Conn, error) {
+ if flags&(OPEN_READONLY|OPEN_READWRITE|OPEN_CREATE) == 0 {
+ flags |= OPEN_READWRITE | OPEN_CREATE
+ }
+ return newConn(filename, flags)
+}
+
+type connKey struct{}
+
+func newConn(filename string, flags OpenFlag) (conn *Conn, err error) {
+ sqlite, err := instantiateSQLite()
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if conn == nil {
+ sqlite.close()
+ }
+ }()
+
+ c := &Conn{sqlite: sqlite}
+ c.arena = c.newArena(1024)
+ c.ctx = context.WithValue(c.ctx, connKey{}, c)
+ c.handle, err = c.openDB(filename, flags)
+ if err != nil {
+ return nil, err
+ }
+ return c, nil
+}
+
+func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) {
+ defer c.arena.mark()()
+ connPtr := c.arena.new(ptrlen)
+ namePtr := c.arena.string(filename)
+
+ flags |= OPEN_EXRESCODE
+ r := c.call("sqlite3_open_v2", uint64(namePtr), uint64(connPtr), uint64(flags), 0)
+
+ handle := util.ReadUint32(c.mod, connPtr)
+ if err := c.sqlite.error(r, handle); err != nil {
+ c.closeDB(handle)
+ return 0, err
+ }
+
+ if flags|OPEN_URI != 0 && strings.HasPrefix(filename, "file:") {
+ var pragmas strings.Builder
+ if _, after, ok := strings.Cut(filename, "?"); ok {
+ query, _ := url.ParseQuery(after)
+ for _, p := range query["_pragma"] {
+ pragmas.WriteString(`PRAGMA `)
+ pragmas.WriteString(p)
+ pragmas.WriteString(`;`)
+ }
+ }
+ if pragmas.Len() != 0 {
+ pragmaPtr := c.arena.string(pragmas.String())
+ r := c.call("sqlite3_exec", uint64(handle), uint64(pragmaPtr), 0, 0, 0)
+ if err := c.sqlite.error(r, handle, pragmas.String()); err != nil {
+ err = fmt.Errorf("sqlite3: invalid _pragma: %w", err)
+ c.closeDB(handle)
+ return 0, err
+ }
+ }
+ }
+ c.call("sqlite3_progress_handler_go", uint64(handle), 100)
+ return handle, nil
+}
+
+func (c *Conn) closeDB(handle uint32) {
+ r := c.call("sqlite3_close_v2", uint64(handle))
+ if err := c.sqlite.error(r, handle); err != nil {
+ panic(err)
+ }
+}
+
+// Close closes the database connection.
+//
+// If the database connection is associated with unfinalized prepared statements,
+// open blob handles, and/or unfinished backup objects,
+// Close will leave the database connection open and return [BUSY].
+//
+// It is safe to close a nil, zero or closed Conn.
+//
+// https://sqlite.org/c3ref/close.html
+func (c *Conn) Close() error {
+ if c == nil || c.handle == 0 {
+ return nil
+ }
+
+ c.pending.Close()
+ c.pending = nil
+
+ r := c.call("sqlite3_close", uint64(c.handle))
+ if err := c.error(r); err != nil {
+ return err
+ }
+
+ c.handle = 0
+ return c.close()
+}
+
+// Exec is a convenience function that allows an application to run
+// multiple statements of SQL without having to use a lot of code.
+//
+// https://sqlite.org/c3ref/exec.html
+func (c *Conn) Exec(sql string) error {
+ c.checkInterrupt()
+ defer c.arena.mark()()
+ sqlPtr := c.arena.string(sql)
+
+ r := c.call("sqlite3_exec", uint64(c.handle), uint64(sqlPtr), 0, 0, 0)
+ return c.error(r, sql)
+}
+
+// Prepare calls [Conn.PrepareFlags] with no flags.
+func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) {
+ return c.PrepareFlags(sql, 0)
+}
+
+// PrepareFlags compiles the first SQL statement in sql;
+// tail is left pointing to what remains uncompiled.
+// If the input text contains no SQL (if the input is an empty string or a comment),
+// both stmt and err will be nil.
+//
+// https://sqlite.org/c3ref/prepare.html
+func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) {
+ if len(sql) > _MAX_SQL_LENGTH {
+ return nil, "", TOOBIG
+ }
+
+ defer c.arena.mark()()
+ stmtPtr := c.arena.new(ptrlen)
+ tailPtr := c.arena.new(ptrlen)
+ sqlPtr := c.arena.string(sql)
+
+ r := c.call("sqlite3_prepare_v3", uint64(c.handle),
+ uint64(sqlPtr), uint64(len(sql)+1), uint64(flags),
+ uint64(stmtPtr), uint64(tailPtr))
+
+ stmt = &Stmt{c: c}
+ stmt.handle = util.ReadUint32(c.mod, stmtPtr)
+ if sql := sql[util.ReadUint32(c.mod, tailPtr)-sqlPtr:]; sql != "" {
+ tail = sql
+ }
+
+ if err := c.error(r, sql); err != nil {
+ return nil, "", err
+ }
+ if stmt.handle == 0 {
+ return nil, "", nil
+ }
+ return stmt, tail, nil
+}
+
+// DBName returns the schema name for n-th database on the database connection.
+//
+// https://sqlite.org/c3ref/db_name.html
+func (c *Conn) DBName(n int) string {
+ r := c.call("sqlite3_db_name", uint64(c.handle), uint64(n))
+
+ ptr := uint32(r)
+ if ptr == 0 {
+ return ""
+ }
+ return util.ReadString(c.mod, ptr, _MAX_NAME)
+}
+
+// Filename returns the filename for a database.
+//
+// https://sqlite.org/c3ref/db_filename.html
+func (c *Conn) Filename(schema string) *vfs.Filename {
+ var ptr uint32
+ if schema != "" {
+ defer c.arena.mark()()
+ ptr = c.arena.string(schema)
+ }
+
+ r := c.call("sqlite3_db_filename", uint64(c.handle), uint64(ptr))
+ return vfs.OpenFilename(c.ctx, c.mod, uint32(r), vfs.OPEN_MAIN_DB)
+}
+
+// ReadOnly determines if a database is read-only.
+//
+// https://sqlite.org/c3ref/db_readonly.html
+func (c *Conn) ReadOnly(schema string) (ro bool, ok bool) {
+ var ptr uint32
+ if schema != "" {
+ defer c.arena.mark()()
+ ptr = c.arena.string(schema)
+ }
+ r := c.call("sqlite3_db_readonly", uint64(c.handle), uint64(ptr))
+ return int32(r) > 0, int32(r) < 0
+}
+
+// GetAutocommit tests the connection for auto-commit mode.
+//
+// https://sqlite.org/c3ref/get_autocommit.html
+func (c *Conn) GetAutocommit() bool {
+ r := c.call("sqlite3_get_autocommit", uint64(c.handle))
+ return r != 0
+}
+
+// LastInsertRowID returns the rowid of the most recent successful INSERT
+// on the database connection.
+//
+// https://sqlite.org/c3ref/last_insert_rowid.html
+func (c *Conn) LastInsertRowID() int64 {
+ r := c.call("sqlite3_last_insert_rowid", uint64(c.handle))
+ return int64(r)
+}
+
+// SetLastInsertRowID allows the application to set the value returned by
+// [Conn.LastInsertRowID].
+//
+// https://sqlite.org/c3ref/set_last_insert_rowid.html
+func (c *Conn) SetLastInsertRowID(id int64) {
+ c.call("sqlite3_set_last_insert_rowid", uint64(c.handle), uint64(id))
+}
+
+// Changes returns the number of rows modified, inserted or deleted
+// by the most recently completed INSERT, UPDATE or DELETE statement
+// on the database connection.
+//
+// https://sqlite.org/c3ref/changes.html
+func (c *Conn) Changes() int64 {
+ r := c.call("sqlite3_changes64", uint64(c.handle))
+ return int64(r)
+}
+
+// TotalChanges returns the number of rows modified, inserted or deleted
+// by all INSERT, UPDATE or DELETE statements completed
+// since the database connection was opened.
+//
+// https://sqlite.org/c3ref/total_changes.html
+func (c *Conn) TotalChanges() int64 {
+ r := c.call("sqlite3_total_changes64", uint64(c.handle))
+ return int64(r)
+}
+
+// ReleaseMemory frees memory used by a database connection.
+//
+// https://sqlite.org/c3ref/db_release_memory.html
+func (c *Conn) ReleaseMemory() error {
+ r := c.call("sqlite3_db_release_memory", uint64(c.handle))
+ return c.error(r)
+}
+
+// GetInterrupt gets the context set with [Conn.SetInterrupt],
+// or nil if none was set.
+func (c *Conn) GetInterrupt() context.Context {
+ return c.interrupt
+}
+
+// SetInterrupt interrupts a long-running query when a context is done.
+//
+// Subsequent uses of the connection will return [INTERRUPT]
+// until the context is reset by another call to SetInterrupt.
+//
+// To associate a timeout with a connection:
+//
+// ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond)
+// conn.SetInterrupt(ctx)
+// defer cancel()
+//
+// SetInterrupt returns the old context assigned to the connection.
+//
+// https://sqlite.org/c3ref/interrupt.html
+func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
+ // Is it the same context?
+ if ctx == c.interrupt {
+ return ctx
+ }
+
+ // A busy SQL statement prevents SQLite from ignoring an interrupt
+ // that comes before any other statements are started.
+ if c.pending == nil {
+ c.pending, _, _ = c.Prepare(`WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x FROM c) SELECT x FROM c`)
+ }
+
+ old = c.interrupt
+ c.interrupt = ctx
+
+ if old != nil && old.Done() != nil && (ctx == nil || ctx.Err() == nil) {
+ c.pending.Reset()
+ }
+ if ctx != nil && ctx.Done() != nil {
+ c.pending.Step()
+ }
+ return old
+}
+
+func (c *Conn) checkInterrupt() {
+ if c.interrupt != nil && c.interrupt.Err() != nil {
+ c.call("sqlite3_interrupt", uint64(c.handle))
+ }
+}
+
+func progressCallback(ctx context.Context, mod api.Module, pDB uint32) (interrupt uint32) {
+ if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB &&
+ c.interrupt != nil && c.interrupt.Err() != nil {
+ interrupt = 1
+ }
+ return interrupt
+}
+
+// BusyTimeout sets a busy timeout.
+//
+// https://sqlite.org/c3ref/busy_timeout.html
+func (c *Conn) BusyTimeout(timeout time.Duration) error {
+ ms := min((timeout+time.Millisecond-1)/time.Millisecond, math.MaxInt32)
+ r := c.call("sqlite3_busy_timeout", uint64(c.handle), uint64(ms))
+ return c.error(r)
+}
+
+func timeoutCallback(ctx context.Context, mod api.Module, pDB uint32, count, tmout int32) (retry uint32) {
+ if c, ok := ctx.Value(connKey{}).(*Conn); ok &&
+ (c.interrupt == nil || c.interrupt.Err() == nil) {
+ const delays = "\x01\x02\x05\x0a\x0f\x14\x19\x19\x19\x32\x32\x64"
+ const totals = "\x00\x01\x03\x08\x12\x21\x35\x4e\x67\x80\xb2\xe4"
+ const ndelay = int32(len(delays) - 1)
+
+ var delay, prior int32
+ if count <= ndelay {
+ delay = int32(delays[count])
+ prior = int32(totals[count])
+ } else {
+ delay = int32(delays[ndelay])
+ prior = int32(totals[ndelay]) + delay*(count-ndelay)
+ }
+
+ if delay = min(delay, tmout-prior); delay > 0 {
+ time.Sleep(time.Duration(delay) * time.Millisecond)
+ retry = 1
+ }
+ }
+ return retry
+}
+
+// BusyHandler registers a callback to handle [BUSY] errors.
+//
+// https://sqlite.org/c3ref/busy_handler.html
+func (c *Conn) BusyHandler(cb func(count int) (retry bool)) error {
+ var enable uint64
+ if cb != nil {
+ enable = 1
+ }
+ r := c.call("sqlite3_busy_handler_go", uint64(c.handle), enable)
+ if err := c.error(r); err != nil {
+ return err
+ }
+ c.busy = cb
+ return nil
+}
+
+func busyCallback(ctx context.Context, mod api.Module, pDB uint32, count int32) (retry uint32) {
+ if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil &&
+ (c.interrupt == nil || c.interrupt.Err() == nil) {
+ if c.busy(int(count)) {
+ retry = 1
+ }
+ }
+ return retry
+}
+
+func (c *Conn) error(rc uint64, sql ...string) error {
+ return c.sqlite.error(rc, c.handle, sql...)
+}
+
+// DriverConn is implemented by the SQLite [database/sql] driver connection.
+//
+// It can be used to access SQLite features like [online backup].
+//
+// [online backup]: https://sqlite.org/backup.html
+type DriverConn interface {
+ Raw() *Conn
+}