From 137ef5a9ff8f06f9167a1aca9bafa0e8ab2e21e6 Mon Sep 17 00:00:00 2001 From: Daenney Date: Sat, 29 Jun 2024 09:35:57 +0200 Subject: [feature] Default to WASM-based SQLite driver (#3053) * [feature] Default to WASM-based SQLite driver With 0.16 out this switches our default SQLite driver to the WASM-based solution instead. So far the driver seems to perform just as well. Switching our default should result in it getting a bit more testing during the 0.17 development cycle. * add the ol' john hancock --------- Co-authored-by: tobi --- internal/db/sqlite/driver.go | 70 +++++---- internal/db/sqlite/driver_moderncsqlite3.go | 197 ++++++++++++++++++++++++++ internal/db/sqlite/driver_wasmsqlite3.go | 211 ---------------------------- internal/db/sqlite/errors.go | 30 ++-- internal/db/sqlite/errors_moderncsqlite3.go | 62 ++++++++ internal/db/sqlite/errors_wasmsqlite3.go | 60 -------- 6 files changed, 315 insertions(+), 315 deletions(-) create mode 100644 internal/db/sqlite/driver_moderncsqlite3.go delete mode 100644 internal/db/sqlite/driver_wasmsqlite3.go create mode 100644 internal/db/sqlite/errors_moderncsqlite3.go delete mode 100644 internal/db/sqlite/errors_wasmsqlite3.go (limited to 'internal/db/sqlite') diff --git a/internal/db/sqlite/driver.go b/internal/db/sqlite/driver.go index 11cb6b27d..cea976d94 100644 --- a/internal/db/sqlite/driver.go +++ b/internal/db/sqlite/driver.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//go:build !wasmsqlite3 +//go:build !moderncsqlite3 package sqlite @@ -23,19 +23,22 @@ import ( "context" "database/sql/driver" - "modernc.org/sqlite" - "github.com/superseriousbusiness/gotosocial/internal/db" + + "github.com/ncruces/go-sqlite3" + sqlite3driver "github.com/ncruces/go-sqlite3/driver" + _ "github.com/ncruces/go-sqlite3/embed" // embed wasm binary + _ "github.com/ncruces/go-sqlite3/vfs/memdb" // include memdb vfs ) // Driver is our own wrapper around the -// sqlite.Driver{} type in order to wrap +// driver.SQLite{} type in order to wrap // further SQL types with our own // functionality, e.g. err processing. -type Driver struct{ sqlite.Driver } +type Driver struct{ sqlite3driver.SQLite } func (d *Driver) Open(name string) (driver.Conn, error) { - conn, err := d.Driver.Open(name) + conn, err := d.SQLite.Open(name) if err != nil { err = processSQLiteError(err) return nil, err @@ -43,6 +46,30 @@ func (d *Driver) Open(name string) (driver.Conn, error) { return &sqliteConn{conn.(connIface)}, nil } +func (d *Driver) OpenConnector(name string) (driver.Connector, error) { + cc, err := d.SQLite.OpenConnector(name) + if err != nil { + return nil, err + } + return &sqliteConnector{driver: d, Connector: cc}, nil +} + +type sqliteConnector struct { + driver *Driver + driver.Connector +} + +func (c *sqliteConnector) Driver() driver.Driver { return c.driver } + +func (c *sqliteConnector) Connect(ctx context.Context) (driver.Conn, error) { + conn, err := c.Connector.Connect(ctx) + err = processSQLiteError(err) + if err != nil { + return nil, err + } + return &sqliteConn{conn.(connIface)}, nil +} + type sqliteConn struct{ connIface } func (c *sqliteConn) Begin() (driver.Tx, error) { @@ -81,26 +108,16 @@ func (c *sqliteConn) ExecContext(ctx context.Context, query string, args []drive return } -func (c *sqliteConn) Query(query string, args []driver.Value) (driver.Rows, error) { - return c.QueryContext(context.Background(), query, db.ToNamedValues(args)) -} - -func (c *sqliteConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) { - rows, err = c.connIface.QueryContext(ctx, query, args) - err = processSQLiteError(err) - if err != nil { - return nil, err - } - return &sqliteRows{rows.(rowsIface)}, nil -} - func (c *sqliteConn) Close() (err error) { + // Get acces the underlying raw sqlite3 conn. + raw := c.connIface.(sqlite3.DriverConn).Raw() + // see: https://www.sqlite.org/pragma.html#pragma_optimize const onClose = "PRAGMA analysis_limit=1000; PRAGMA optimize;" - _, _ = c.connIface.ExecContext(context.Background(), onClose, nil) + _ = raw.Exec(onClose) - // Finally, close the conn. - err = c.connIface.Close() + // Finally, close. + err = raw.Close() return } @@ -164,7 +181,7 @@ func (r *sqliteRows) Close() (err error) { } // connIface is the driver.Conn interface -// types (and the like) that modernc.org/sqlite.conn +// types (and the like) that go-sqlite3/driver.conn // conforms to. Useful so you don't need // to repeatedly perform checks yourself. type connIface interface { @@ -172,11 +189,10 @@ type connIface interface { driver.ConnBeginTx driver.ConnPrepareContext driver.ExecerContext - driver.QueryerContext } // StmtIface is the driver.Stmt interface -// types (and the like) that modernc.org/sqlite.stmt +// types (and the like) that go-sqlite3/driver.stmt // conforms to. Useful so you don't need // to repeatedly perform checks yourself. type stmtIface interface { @@ -186,12 +202,10 @@ type stmtIface interface { } // RowsIface is the driver.Rows interface -// types (and the like) that modernc.org/sqlite.rows +// types (and the like) that go-sqlite3/driver.rows // conforms to. Useful so you don't need // to repeatedly perform checks yourself. type rowsIface interface { driver.Rows driver.RowsColumnTypeDatabaseTypeName - driver.RowsColumnTypeLength - driver.RowsColumnTypeScanType } diff --git a/internal/db/sqlite/driver_moderncsqlite3.go b/internal/db/sqlite/driver_moderncsqlite3.go new file mode 100644 index 000000000..7cb31efea --- /dev/null +++ b/internal/db/sqlite/driver_moderncsqlite3.go @@ -0,0 +1,197 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//go:build moderncsqlite3 + +package sqlite + +import ( + "context" + "database/sql/driver" + + "modernc.org/sqlite" + + "github.com/superseriousbusiness/gotosocial/internal/db" +) + +// Driver is our own wrapper around the +// sqlite.Driver{} type in order to wrap +// further SQL types with our own +// functionality, e.g. err processing. +type Driver struct{ sqlite.Driver } + +func (d *Driver) Open(name string) (driver.Conn, error) { + conn, err := d.Driver.Open(name) + if err != nil { + err = processSQLiteError(err) + return nil, err + } + return &sqliteConn{conn.(connIface)}, nil +} + +type sqliteConn struct{ connIface } + +func (c *sqliteConn) Begin() (driver.Tx, error) { + return c.BeginTx(context.Background(), driver.TxOptions{}) +} + +func (c *sqliteConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx driver.Tx, err error) { + tx, err = c.connIface.BeginTx(ctx, opts) + err = processSQLiteError(err) + if err != nil { + return nil, err + } + return &sqliteTx{tx}, nil +} + +func (c *sqliteConn) Prepare(query string) (driver.Stmt, error) { + return c.PrepareContext(context.Background(), query) +} + +func (c *sqliteConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { + stmt, err = c.connIface.PrepareContext(ctx, query) + err = processSQLiteError(err) + if err != nil { + return nil, err + } + return &sqliteStmt{stmtIface: stmt.(stmtIface)}, nil +} + +func (c *sqliteConn) Exec(query string, args []driver.Value) (driver.Result, error) { + return c.ExecContext(context.Background(), query, db.ToNamedValues(args)) +} + +func (c *sqliteConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (res driver.Result, err error) { + res, err = c.connIface.ExecContext(ctx, query, args) + err = processSQLiteError(err) + return +} + +func (c *sqliteConn) Query(query string, args []driver.Value) (driver.Rows, error) { + return c.QueryContext(context.Background(), query, db.ToNamedValues(args)) +} + +func (c *sqliteConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) { + rows, err = c.connIface.QueryContext(ctx, query, args) + err = processSQLiteError(err) + if err != nil { + return nil, err + } + return &sqliteRows{rows.(rowsIface)}, nil +} + +func (c *sqliteConn) Close() (err error) { + // see: https://www.sqlite.org/pragma.html#pragma_optimize + const onClose = "PRAGMA analysis_limit=1000; PRAGMA optimize;" + _, _ = c.connIface.ExecContext(context.Background(), onClose, nil) + + // Finally, close the conn. + err = c.connIface.Close() + return +} + +type sqliteTx struct{ driver.Tx } + +func (tx *sqliteTx) Commit() (err error) { + err = tx.Tx.Commit() + err = processSQLiteError(err) + return +} + +func (tx *sqliteTx) Rollback() (err error) { + err = tx.Tx.Rollback() + err = processSQLiteError(err) + return +} + +type sqliteStmt struct{ stmtIface } + +func (stmt *sqliteStmt) Exec(args []driver.Value) (driver.Result, error) { + return stmt.ExecContext(context.Background(), db.ToNamedValues(args)) +} + +func (stmt *sqliteStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) { + res, err = stmt.stmtIface.ExecContext(ctx, args) + err = processSQLiteError(err) + return +} + +func (stmt *sqliteStmt) Query(args []driver.Value) (driver.Rows, error) { + return stmt.QueryContext(context.Background(), db.ToNamedValues(args)) +} + +func (stmt *sqliteStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) { + rows, err = stmt.stmtIface.QueryContext(ctx, args) + err = processSQLiteError(err) + if err != nil { + return nil, err + } + return &sqliteRows{rows.(rowsIface)}, nil +} + +func (stmt *sqliteStmt) Close() (err error) { + err = stmt.stmtIface.Close() + err = processSQLiteError(err) + return +} + +type sqliteRows struct{ rowsIface } + +func (r *sqliteRows) Next(dest []driver.Value) (err error) { + err = r.rowsIface.Next(dest) + err = processSQLiteError(err) + return +} + +func (r *sqliteRows) Close() (err error) { + err = r.rowsIface.Close() + err = processSQLiteError(err) + return +} + +// connIface is the driver.Conn interface +// types (and the like) that modernc.org/sqlite.conn +// conforms to. Useful so you don't need +// to repeatedly perform checks yourself. +type connIface interface { + driver.Conn + driver.ConnBeginTx + driver.ConnPrepareContext + driver.ExecerContext + driver.QueryerContext +} + +// StmtIface is the driver.Stmt interface +// types (and the like) that modernc.org/sqlite.stmt +// conforms to. Useful so you don't need +// to repeatedly perform checks yourself. +type stmtIface interface { + driver.Stmt + driver.StmtExecContext + driver.StmtQueryContext +} + +// RowsIface is the driver.Rows interface +// types (and the like) that modernc.org/sqlite.rows +// conforms to. Useful so you don't need +// to repeatedly perform checks yourself. +type rowsIface interface { + driver.Rows + driver.RowsColumnTypeDatabaseTypeName + driver.RowsColumnTypeLength + driver.RowsColumnTypeScanType +} diff --git a/internal/db/sqlite/driver_wasmsqlite3.go b/internal/db/sqlite/driver_wasmsqlite3.go deleted file mode 100644 index afe499a98..000000000 --- a/internal/db/sqlite/driver_wasmsqlite3.go +++ /dev/null @@ -1,211 +0,0 @@ -// GoToSocial -// Copyright (C) GoToSocial Authors admin@gotosocial.org -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -//go:build wasmsqlite3 - -package sqlite - -import ( - "context" - "database/sql/driver" - - "github.com/superseriousbusiness/gotosocial/internal/db" - - "github.com/ncruces/go-sqlite3" - sqlite3driver "github.com/ncruces/go-sqlite3/driver" - _ "github.com/ncruces/go-sqlite3/embed" // embed wasm binary - _ "github.com/ncruces/go-sqlite3/vfs/memdb" // include memdb vfs -) - -// Driver is our own wrapper around the -// driver.SQLite{} type in order to wrap -// further SQL types with our own -// functionality, e.g. err processing. -type Driver struct{ sqlite3driver.SQLite } - -func (d *Driver) Open(name string) (driver.Conn, error) { - conn, err := d.SQLite.Open(name) - if err != nil { - err = processSQLiteError(err) - return nil, err - } - return &sqliteConn{conn.(connIface)}, nil -} - -func (d *Driver) OpenConnector(name string) (driver.Connector, error) { - cc, err := d.SQLite.OpenConnector(name) - if err != nil { - return nil, err - } - return &sqliteConnector{driver: d, Connector: cc}, nil -} - -type sqliteConnector struct { - driver *Driver - driver.Connector -} - -func (c *sqliteConnector) Driver() driver.Driver { return c.driver } - -func (c *sqliteConnector) Connect(ctx context.Context) (driver.Conn, error) { - conn, err := c.Connector.Connect(ctx) - err = processSQLiteError(err) - if err != nil { - return nil, err - } - return &sqliteConn{conn.(connIface)}, nil -} - -type sqliteConn struct{ connIface } - -func (c *sqliteConn) Begin() (driver.Tx, error) { - return c.BeginTx(context.Background(), driver.TxOptions{}) -} - -func (c *sqliteConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx driver.Tx, err error) { - tx, err = c.connIface.BeginTx(ctx, opts) - err = processSQLiteError(err) - if err != nil { - return nil, err - } - return &sqliteTx{tx}, nil -} - -func (c *sqliteConn) Prepare(query string) (driver.Stmt, error) { - return c.PrepareContext(context.Background(), query) -} - -func (c *sqliteConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { - stmt, err = c.connIface.PrepareContext(ctx, query) - err = processSQLiteError(err) - if err != nil { - return nil, err - } - return &sqliteStmt{stmtIface: stmt.(stmtIface)}, nil -} - -func (c *sqliteConn) Exec(query string, args []driver.Value) (driver.Result, error) { - return c.ExecContext(context.Background(), query, db.ToNamedValues(args)) -} - -func (c *sqliteConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (res driver.Result, err error) { - res, err = c.connIface.ExecContext(ctx, query, args) - err = processSQLiteError(err) - return -} - -func (c *sqliteConn) Close() (err error) { - // Get acces the underlying raw sqlite3 conn. - raw := c.connIface.(sqlite3.DriverConn).Raw() - - // see: https://www.sqlite.org/pragma.html#pragma_optimize - const onClose = "PRAGMA analysis_limit=1000; PRAGMA optimize;" - _ = raw.Exec(onClose) - - // Finally, close. - err = raw.Close() - return -} - -type sqliteTx struct{ driver.Tx } - -func (tx *sqliteTx) Commit() (err error) { - err = tx.Tx.Commit() - err = processSQLiteError(err) - return -} - -func (tx *sqliteTx) Rollback() (err error) { - err = tx.Tx.Rollback() - err = processSQLiteError(err) - return -} - -type sqliteStmt struct{ stmtIface } - -func (stmt *sqliteStmt) Exec(args []driver.Value) (driver.Result, error) { - return stmt.ExecContext(context.Background(), db.ToNamedValues(args)) -} - -func (stmt *sqliteStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) { - res, err = stmt.stmtIface.ExecContext(ctx, args) - err = processSQLiteError(err) - return -} - -func (stmt *sqliteStmt) Query(args []driver.Value) (driver.Rows, error) { - return stmt.QueryContext(context.Background(), db.ToNamedValues(args)) -} - -func (stmt *sqliteStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) { - rows, err = stmt.stmtIface.QueryContext(ctx, args) - err = processSQLiteError(err) - if err != nil { - return nil, err - } - return &sqliteRows{rows.(rowsIface)}, nil -} - -func (stmt *sqliteStmt) Close() (err error) { - err = stmt.stmtIface.Close() - err = processSQLiteError(err) - return -} - -type sqliteRows struct{ rowsIface } - -func (r *sqliteRows) Next(dest []driver.Value) (err error) { - err = r.rowsIface.Next(dest) - err = processSQLiteError(err) - return -} - -func (r *sqliteRows) Close() (err error) { - err = r.rowsIface.Close() - err = processSQLiteError(err) - return -} - -// connIface is the driver.Conn interface -// types (and the like) that go-sqlite3/driver.conn -// conforms to. Useful so you don't need -// to repeatedly perform checks yourself. -type connIface interface { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext -} - -// StmtIface is the driver.Stmt interface -// types (and the like) that go-sqlite3/driver.stmt -// conforms to. Useful so you don't need -// to repeatedly perform checks yourself. -type stmtIface interface { - driver.Stmt - driver.StmtExecContext - driver.StmtQueryContext -} - -// RowsIface is the driver.Rows interface -// types (and the like) that go-sqlite3/driver.rows -// conforms to. Useful so you don't need -// to repeatedly perform checks yourself. -type rowsIface interface { - driver.Rows - driver.RowsColumnTypeDatabaseTypeName -} diff --git a/internal/db/sqlite/errors.go b/internal/db/sqlite/errors.go index b07b026de..f814fa8a4 100644 --- a/internal/db/sqlite/errors.go +++ b/internal/db/sqlite/errors.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//go:build !wasmsqlite3 +//go:build !moderncsqlite3 package sqlite @@ -23,9 +23,7 @@ import ( "database/sql/driver" "fmt" - "modernc.org/sqlite" - sqlite3 "modernc.org/sqlite/lib" - + "github.com/ncruces/go-sqlite3" "github.com/superseriousbusiness/gotosocial/internal/db" ) @@ -33,30 +31,30 @@ import ( // handle conversion to any of our common db types. func processSQLiteError(err error) error { // Attempt to cast as sqlite error. - sqliteErr, ok := err.(*sqlite.Error) + sqliteErr, ok := err.(*sqlite3.Error) if !ok { return err } // Handle supplied error code: - switch sqliteErr.Code() { - case sqlite3.SQLITE_CONSTRAINT_UNIQUE, - sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY: + switch sqliteErr.ExtendedCode() { + case sqlite3.CONSTRAINT_UNIQUE, + sqlite3.CONSTRAINT_PRIMARYKEY: return db.ErrAlreadyExists - // Busy should be very rare, but - // on busy tell the database to close - // the connection, re-open and re-attempt - // which should give a necessary timeout. - case sqlite3.SQLITE_BUSY, - sqlite3.SQLITE_BUSY_RECOVERY, - sqlite3.SQLITE_BUSY_SNAPSHOT: + // Busy should be very rare, but on + // busy tell the database to close the + // connection, re-open and re-attempt + // which should give necessary timeout. + case sqlite3.BUSY_RECOVERY, + sqlite3.BUSY_SNAPSHOT: return driver.ErrBadConn } // Wrap the returned error with the code and // extended code for easier debugging later. - return fmt.Errorf("%w (code=%d)", err, + return fmt.Errorf("%w (code=%d extended=%d)", err, sqliteErr.Code(), + sqliteErr.ExtendedCode(), ) } diff --git a/internal/db/sqlite/errors_moderncsqlite3.go b/internal/db/sqlite/errors_moderncsqlite3.go new file mode 100644 index 000000000..b17cebefb --- /dev/null +++ b/internal/db/sqlite/errors_moderncsqlite3.go @@ -0,0 +1,62 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//go:build moderncsqlite3 + +package sqlite + +import ( + "database/sql/driver" + "fmt" + + "modernc.org/sqlite" + sqlite3 "modernc.org/sqlite/lib" + + "github.com/superseriousbusiness/gotosocial/internal/db" +) + +// processSQLiteError processes an sqlite3.Error to +// handle conversion to any of our common db types. +func processSQLiteError(err error) error { + // Attempt to cast as sqlite error. + sqliteErr, ok := err.(*sqlite.Error) + if !ok { + return err + } + + // Handle supplied error code: + switch sqliteErr.Code() { + case sqlite3.SQLITE_CONSTRAINT_UNIQUE, + sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY: + return db.ErrAlreadyExists + + // Busy should be very rare, but + // on busy tell the database to close + // the connection, re-open and re-attempt + // which should give a necessary timeout. + case sqlite3.SQLITE_BUSY, + sqlite3.SQLITE_BUSY_RECOVERY, + sqlite3.SQLITE_BUSY_SNAPSHOT: + return driver.ErrBadConn + } + + // Wrap the returned error with the code and + // extended code for easier debugging later. + return fmt.Errorf("%w (code=%d)", err, + sqliteErr.Code(), + ) +} diff --git a/internal/db/sqlite/errors_wasmsqlite3.go b/internal/db/sqlite/errors_wasmsqlite3.go deleted file mode 100644 index 26668a898..000000000 --- a/internal/db/sqlite/errors_wasmsqlite3.go +++ /dev/null @@ -1,60 +0,0 @@ -// GoToSocial -// Copyright (C) GoToSocial Authors admin@gotosocial.org -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -//go:build wasmsqlite3 - -package sqlite - -import ( - "database/sql/driver" - "fmt" - - "github.com/ncruces/go-sqlite3" - "github.com/superseriousbusiness/gotosocial/internal/db" -) - -// processSQLiteError processes an sqlite3.Error to -// handle conversion to any of our common db types. -func processSQLiteError(err error) error { - // Attempt to cast as sqlite error. - sqliteErr, ok := err.(*sqlite3.Error) - if !ok { - return err - } - - // Handle supplied error code: - switch sqliteErr.ExtendedCode() { - case sqlite3.CONSTRAINT_UNIQUE, - sqlite3.CONSTRAINT_PRIMARYKEY: - return db.ErrAlreadyExists - - // Busy should be very rare, but on - // busy tell the database to close the - // connection, re-open and re-attempt - // which should give necessary timeout. - case sqlite3.BUSY_RECOVERY, - sqlite3.BUSY_SNAPSHOT: - return driver.ErrBadConn - } - - // Wrap the returned error with the code and - // extended code for easier debugging later. - return fmt.Errorf("%w (code=%d extended=%d)", err, - sqliteErr.Code(), - sqliteErr.ExtendedCode(), - ) -} -- cgit v1.2.3