diff options
author | 2025-03-10 00:11:39 +0100 | |
---|---|---|
committer | 2025-03-10 01:59:49 +0100 | |
commit | 5e62aaf724e7f3d79427aebd1d5cdbbb3547265e (patch) | |
tree | 266556645a0ed5ecf1e5f7a248ea1adc6cf9fd13 | |
parent | [chore] remove vendor (diff) | |
download | gotosocial-5e62aaf724e7f3d79427aebd1d5cdbbb3547265e.tar.xz |
Add support for using the system's libsqlite3 library by adding a driver
using "github.com/mattn/go-sqlite3".
-rw-r--r-- | go.mod | 1 | ||||
-rw-r--r-- | go.sum | 2 | ||||
-rw-r--r-- | internal/db/sqlite/driver.go | 2 | ||||
-rw-r--r-- | internal/db/sqlite/driver_libsqlite3.go | 183 | ||||
-rw-r--r-- | internal/db/sqlite/errors.go | 2 | ||||
-rw-r--r-- | internal/db/sqlite/errors_libsqlite3.go | 60 |
6 files changed, 248 insertions, 2 deletions
@@ -44,6 +44,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/jackc/pgx/v5 v5.7.2 github.com/k3a/html2text v1.2.1 + github.com/mattn/go-sqlite3 v1.14.24 github.com/microcosm-cc/bluemonday v1.0.27 github.com/miekg/dns v1.1.63 github.com/minio/minio-go/v7 v7.0.85 @@ -397,6 +397,8 @@ github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= diff --git a/internal/db/sqlite/driver.go b/internal/db/sqlite/driver.go index 203edaff3..e659d1ffa 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 <http://www.gnu.org/licenses/>. -//go:build !moderncsqlite3 && !nowasm +//go:build !moderncsqlite3 && !nowasm && !libsqlite3 package sqlite diff --git a/internal/db/sqlite/driver_libsqlite3.go b/internal/db/sqlite/driver_libsqlite3.go new file mode 100644 index 000000000..fe9df7aac --- /dev/null +++ b/internal/db/sqlite/driver_libsqlite3.go @@ -0,0 +1,183 @@ +// 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 <http://www.gnu.org/licenses/>. + +//go:build !moderncsqlite3 && !nowasm && libsqlite3 + +package sqlite + +import ( + "context" + "database/sql/driver" + + sqlite3 "github.com/mattn/go-sqlite3" + "github.com/superseriousbusiness/gotosocial/internal/db" +) + +// 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{ sqlite3.SQLiteDriver } + +func (d *Driver) Open(name string) (driver.Conn, error) { + conn, err := d.SQLiteDriver.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) Close() (err error) { + // see: https://www.sqlite.org/pragma.html#pragma_optimize + const onClose = "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 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 + driver.RowsColumnTypeNullable +} diff --git a/internal/db/sqlite/errors.go b/internal/db/sqlite/errors.go index 9429a1bcd..784f9ef2d 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 <http://www.gnu.org/licenses/>. -//go:build !moderncsqlite3 && !nowasm +//go:build !moderncsqlite3 && !nowasm && !libsqlite3 package sqlite diff --git a/internal/db/sqlite/errors_libsqlite3.go b/internal/db/sqlite/errors_libsqlite3.go new file mode 100644 index 000000000..483d9ed14 --- /dev/null +++ b/internal/db/sqlite/errors_libsqlite3.go @@ -0,0 +1,60 @@ +// 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 <http://www.gnu.org/licenses/>. + +//go:build !moderncsqlite3 && !nowasm && libsqlite3 + +package sqlite + +import ( + "database/sql/driver" + "fmt" + + sqlite3 "github.com/mattn/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.ErrConstraintUnique, + sqlite3.ErrConstraintPrimaryKey: + 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.ErrBusyRecovery, + sqlite3.ErrBusySnapshot: + 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, + ) +} |