Files
stash/vendor/github.com/doug-martin/goqu/v9/database.go
WithoutPants 5495d72849 File storage rewrite (#2676)
* Restructure data layer part 2 (#2599)
* Refactor and separate image model
* Refactor image query builder
* Handle relationships in image query builder
* Remove relationship management methods
* Refactor gallery model/query builder
* Add scenes to gallery model
* Convert scene model
* Refactor scene models
* Remove unused methods
* Add unit tests for gallery
* Add image tests
* Add scene tests
* Convert unnecessary scene value pointers to values
* Convert unnecessary pointer values to values
* Refactor scene partial
* Add scene partial tests
* Refactor ImagePartial
* Add image partial tests
* Refactor gallery partial update
* Add partial gallery update tests
* Use zero/null package for null values
* Add files and scan system
* Add sqlite implementation for files/folders
* Add unit tests for files/folders
* Image refactors
* Update image data layer
* Refactor gallery model and creation
* Refactor scene model
* Refactor scenes
* Don't set title from filename
* Allow galleries to freely add/remove images
* Add multiple scene file support to graphql and UI
* Add multiple file support for images in graphql/UI
* Add multiple file for galleries in graphql/UI
* Remove use of some deprecated fields
* Remove scene path usage
* Remove gallery path usage
* Remove path from image
* Move funscript to video file
* Refactor caption detection
* Migrate existing data
* Add post commit/rollback hook system
* Lint. Comment out import/export tests
* Add WithDatabase read only wrapper
* Prepend tasks to list
* Add 32 pre-migration
* Add warnings in release and migration notes
2022-09-06 07:03:42 +00:00

649 lines
21 KiB
Go

package goqu
import (
"context"
"database/sql"
"sync"
"github.com/doug-martin/goqu/v9/exec"
)
type (
Logger interface {
Printf(format string, v ...interface{})
}
// Interface for sql.DB, an interface is used so you can use with other
// libraries such as sqlx instead of the native sql.DB
SQLDatabase interface {
Begin() (*sql.Tx, error)
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}
// This struct is the wrapper for a Db. The struct delegates most calls to either an Exec instance or to the Db
// passed into the constructor.
Database struct {
logger Logger
dialect string
// nolint: stylecheck // keep for backwards compatibility
Db SQLDatabase
qf exec.QueryFactory
qfOnce sync.Once
}
)
// This is the common entry point into goqu.
//
// dialect: This is the adapter dialect, you should see your database adapter for the string to use. Built in adapters
// can be found at https://github.com/doug-martin/goqu/tree/master/adapters
//
// db: A sql.Db to use for querying the database
// import (
// "database/sql"
// "fmt"
// "github.com/doug-martin/goqu/v9"
// _ "github.com/doug-martin/goqu/v9/dialect/postgres"
// _ "github.com/lib/pq"
// )
//
// func main() {
// sqlDb, err := sql.Open("postgres", "user=postgres dbname=goqupostgres sslmode=disable ")
// if err != nil {
// panic(err.Error())
// }
// db := goqu.New("postgres", sqlDb)
// }
// The most commonly used Database method is From, which creates a new Dataset that uses the correct adapter and
// supports queries.
// var ids []uint32
// if err := db.From("items").Where(goqu.I("id").Gt(10)).Pluck("id", &ids); err != nil {
// panic(err.Error())
// }
// fmt.Printf("%+v", ids)
func newDatabase(dialect string, db SQLDatabase) *Database {
return &Database{
logger: nil,
dialect: dialect,
Db: db,
qf: nil,
qfOnce: sync.Once{},
}
}
// returns this databases dialect
func (d *Database) Dialect() string {
return d.dialect
}
// Starts a new Transaction.
func (d *Database) Begin() (*TxDatabase, error) {
sqlTx, err := d.Db.Begin()
if err != nil {
return nil, err
}
tx := NewTx(d.dialect, sqlTx)
tx.Logger(d.logger)
return tx, nil
}
// Starts a new Transaction. See sql.DB#BeginTx for option description
func (d *Database) BeginTx(ctx context.Context, opts *sql.TxOptions) (*TxDatabase, error) {
sqlTx, err := d.Db.BeginTx(ctx, opts)
if err != nil {
return nil, err
}
tx := NewTx(d.dialect, sqlTx)
tx.Logger(d.logger)
return tx, nil
}
// WithTx starts a new transaction and executes it in Wrap method
func (d *Database) WithTx(fn func(*TxDatabase) error) error {
tx, err := d.Begin()
if err != nil {
return err
}
return tx.Wrap(func() error { return fn(tx) })
}
// Creates a new Dataset that uses the correct adapter and supports queries.
// var ids []uint32
// if err := db.From("items").Where(goqu.I("id").Gt(10)).Pluck("id", &ids); err != nil {
// panic(err.Error())
// }
// fmt.Printf("%+v", ids)
//
// from...: Sources for you dataset, could be table names (strings), a goqu.Literal or another goqu.Dataset
func (d *Database) From(from ...interface{}) *SelectDataset {
return newDataset(d.dialect, d.queryFactory()).From(from...)
}
func (d *Database) Select(cols ...interface{}) *SelectDataset {
return newDataset(d.dialect, d.queryFactory()).Select(cols...)
}
func (d *Database) Update(table interface{}) *UpdateDataset {
return newUpdateDataset(d.dialect, d.queryFactory()).Table(table)
}
func (d *Database) Insert(table interface{}) *InsertDataset {
return newInsertDataset(d.dialect, d.queryFactory()).Into(table)
}
func (d *Database) Delete(table interface{}) *DeleteDataset {
return newDeleteDataset(d.dialect, d.queryFactory()).From(table)
}
func (d *Database) Truncate(table ...interface{}) *TruncateDataset {
return newTruncateDataset(d.dialect, d.queryFactory()).Table(table...)
}
// Sets the logger for to use when logging queries
func (d *Database) Logger(logger Logger) {
d.logger = logger
}
// Logs a given operation with the specified sql and arguments
func (d *Database) Trace(op, sqlString string, args ...interface{}) {
if d.logger != nil {
if sqlString != "" {
if len(args) != 0 {
d.logger.Printf("[goqu] %s [query:=`%s` args:=%+v]", op, sqlString, args)
} else {
d.logger.Printf("[goqu] %s [query:=`%s`]", op, sqlString)
}
} else {
d.logger.Printf("[goqu] %s", op)
}
}
}
// Uses the db to Execute the query with arguments and return the sql.Result
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) Exec(query string, args ...interface{}) (sql.Result, error) {
return d.ExecContext(context.Background(), query, args...)
}
// Uses the db to Execute the query with arguments and return the sql.Result
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
d.Trace("EXEC", query, args...)
return d.Db.ExecContext(ctx, query, args...)
}
// Can be used to prepare a query.
//
// You can use this in tandem with a dataset by doing the following.
// sql, args, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSQL(true)
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// stmt, err := db.Prepare(sql)
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// defer stmt.Close()
// rows, err := stmt.Query(args)
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// defer rows.Close()
// for rows.Next(){
// //scan your rows
// }
// if rows.Err() != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
//
// query: The SQL statement to prepare.
func (d *Database) Prepare(query string) (*sql.Stmt, error) {
return d.PrepareContext(context.Background(), query)
}
// Can be used to prepare a query.
//
// You can use this in tandem with a dataset by doing the following.
// sql, args, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSQL(true)
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// stmt, err := db.Prepare(sql)
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// defer stmt.Close()
// rows, err := stmt.QueryContext(ctx, args)
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// defer rows.Close()
// for rows.Next(){
// //scan your rows
// }
// if rows.Err() != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
//
// query: The SQL statement to prepare.
func (d *Database) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
d.Trace("PREPARE", query)
return d.Db.PrepareContext(ctx, query)
}
// Used to query for multiple rows.
//
// You can use this in tandem with a dataset by doing the following.
// sql, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSQL()
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// rows, err := stmt.Query(args)
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// defer rows.Close()
// for rows.Next(){
// //scan your rows
// }
// if rows.Err() != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) Query(query string, args ...interface{}) (*sql.Rows, error) {
return d.QueryContext(context.Background(), query, args...)
}
// Used to query for multiple rows.
//
// You can use this in tandem with a dataset by doing the following.
// sql, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSQL()
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// rows, err := stmt.QueryContext(ctx, args)
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// defer rows.Close()
// for rows.Next(){
// //scan your rows
// }
// if rows.Err() != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
d.Trace("QUERY", query, args...)
return d.Db.QueryContext(ctx, query, args...)
}
// Used to query for a single row.
//
// You can use this in tandem with a dataset by doing the following.
// sql, err := db.From("items").Where(goqu.I("id").Gt(10)).Limit(1).ToSQL()
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// rows, err := stmt.QueryRow(args)
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// //scan your row
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) QueryRow(query string, args ...interface{}) *sql.Row {
return d.QueryRowContext(context.Background(), query, args...)
}
// Used to query for a single row.
//
// You can use this in tandem with a dataset by doing the following.
// sql, err := db.From("items").Where(goqu.I("id").Gt(10)).Limit(1).ToSQL()
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// rows, err := stmt.QueryRowContext(ctx, args)
// if err != nil{
// panic(err.Error()) //you could gracefully handle the error also
// }
// //scan your row
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
d.Trace("QUERY ROW", query, args...)
return d.Db.QueryRowContext(ctx, query, args...)
}
func (d *Database) queryFactory() exec.QueryFactory {
d.qfOnce.Do(func() {
d.qf = exec.NewQueryFactory(d)
})
return d.qf
}
// Queries the database using the supplied query, and args and uses CrudExec.ScanStructs to scan the results into a
// slice of structs
//
// i: A pointer to a slice of structs
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) ScanStructs(i interface{}, query string, args ...interface{}) error {
return d.ScanStructsContext(context.Background(), i, query, args...)
}
// Queries the database using the supplied context, query, and args and uses CrudExec.ScanStructsContext to scan the
// results into a slice of structs
//
// i: A pointer to a slice of structs
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) ScanStructsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error {
return d.queryFactory().FromSQL(query, args...).ScanStructsContext(ctx, i)
}
// Queries the database using the supplied query, and args and uses CrudExec.ScanStruct to scan the results into a
// struct
//
// i: A pointer to a struct
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) ScanStruct(i interface{}, query string, args ...interface{}) (bool, error) {
return d.ScanStructContext(context.Background(), i, query, args...)
}
// Queries the database using the supplied context, query, and args and uses CrudExec.ScanStructContext to scan the
// results into a struct
//
// i: A pointer to a struct
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) ScanStructContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) {
return d.queryFactory().FromSQL(query, args...).ScanStructContext(ctx, i)
}
// Queries the database using the supplied query, and args and uses CrudExec.ScanVals to scan the results into a slice
// of primitive values
//
// i: A pointer to a slice of primitive values
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) ScanVals(i interface{}, query string, args ...interface{}) error {
return d.ScanValsContext(context.Background(), i, query, args...)
}
// Queries the database using the supplied context, query, and args and uses CrudExec.ScanValsContext to scan the
// results into a slice of primitive values
//
// i: A pointer to a slice of primitive values
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) ScanValsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error {
return d.queryFactory().FromSQL(query, args...).ScanValsContext(ctx, i)
}
// Queries the database using the supplied query, and args and uses CrudExec.ScanVal to scan the results into a
// primitive value
//
// i: A pointer to a primitive value
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) ScanVal(i interface{}, query string, args ...interface{}) (bool, error) {
return d.ScanValContext(context.Background(), i, query, args...)
}
// Queries the database using the supplied context, query, and args and uses CrudExec.ScanValContext to scan the
// results into a primitive value
//
// i: A pointer to a primitive value
//
// query: The SQL to execute
//
// args...: for any placeholder parameters in the query
func (d *Database) ScanValContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) {
return d.queryFactory().FromSQL(query, args...).ScanValContext(ctx, i)
}
// A wrapper around a sql.Tx and works the same way as Database
type (
// Interface for sql.Tx, an interface is used so you can use with other
// libraries such as sqlx instead of the native sql.DB
SQLTx interface {
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
Commit() error
Rollback() error
}
TxDatabase struct {
logger Logger
dialect string
Tx SQLTx
qf exec.QueryFactory
qfOnce sync.Once
}
)
// Creates a new TxDatabase
func NewTx(dialect string, tx SQLTx) *TxDatabase {
return &TxDatabase{dialect: dialect, Tx: tx}
}
// returns this databases dialect
func (td *TxDatabase) Dialect() string {
return td.dialect
}
// Creates a new Dataset for querying a Database.
func (td *TxDatabase) From(cols ...interface{}) *SelectDataset {
return newDataset(td.dialect, td.queryFactory()).From(cols...)
}
func (td *TxDatabase) Select(cols ...interface{}) *SelectDataset {
return newDataset(td.dialect, td.queryFactory()).Select(cols...)
}
func (td *TxDatabase) Update(table interface{}) *UpdateDataset {
return newUpdateDataset(td.dialect, td.queryFactory()).Table(table)
}
func (td *TxDatabase) Insert(table interface{}) *InsertDataset {
return newInsertDataset(td.dialect, td.queryFactory()).Into(table)
}
func (td *TxDatabase) Delete(table interface{}) *DeleteDataset {
return newDeleteDataset(td.dialect, td.queryFactory()).From(table)
}
func (td *TxDatabase) Truncate(table ...interface{}) *TruncateDataset {
return newTruncateDataset(td.dialect, td.queryFactory()).Table(table...)
}
// Sets the logger
func (td *TxDatabase) Logger(logger Logger) {
td.logger = logger
}
func (td *TxDatabase) Trace(op, sqlString string, args ...interface{}) {
if td.logger != nil {
if sqlString != "" {
if len(args) != 0 {
td.logger.Printf("[goqu - transaction] %s [query:=`%s` args:=%+v] ", op, sqlString, args)
} else {
td.logger.Printf("[goqu - transaction] %s [query:=`%s`] ", op, sqlString)
}
} else {
td.logger.Printf("[goqu - transaction] %s", op)
}
}
}
// See Database#Exec
func (td *TxDatabase) Exec(query string, args ...interface{}) (sql.Result, error) {
return td.ExecContext(context.Background(), query, args...)
}
// See Database#ExecContext
func (td *TxDatabase) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
td.Trace("EXEC", query, args...)
return td.Tx.ExecContext(ctx, query, args...)
}
// See Database#Prepare
func (td *TxDatabase) Prepare(query string) (*sql.Stmt, error) {
return td.PrepareContext(context.Background(), query)
}
// See Database#PrepareContext
func (td *TxDatabase) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
td.Trace("PREPARE", query)
return td.Tx.PrepareContext(ctx, query)
}
// See Database#Query
func (td *TxDatabase) Query(query string, args ...interface{}) (*sql.Rows, error) {
return td.QueryContext(context.Background(), query, args...)
}
// See Database#QueryContext
func (td *TxDatabase) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
td.Trace("QUERY", query, args...)
return td.Tx.QueryContext(ctx, query, args...)
}
// See Database#QueryRow
func (td *TxDatabase) QueryRow(query string, args ...interface{}) *sql.Row {
return td.QueryRowContext(context.Background(), query, args...)
}
// See Database#QueryRowContext
func (td *TxDatabase) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
td.Trace("QUERY ROW", query, args...)
return td.Tx.QueryRowContext(ctx, query, args...)
}
func (td *TxDatabase) queryFactory() exec.QueryFactory {
td.qfOnce.Do(func() {
td.qf = exec.NewQueryFactory(td)
})
return td.qf
}
// See Database#ScanStructs
func (td *TxDatabase) ScanStructs(i interface{}, query string, args ...interface{}) error {
return td.ScanStructsContext(context.Background(), i, query, args...)
}
// See Database#ScanStructsContext
func (td *TxDatabase) ScanStructsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error {
return td.queryFactory().FromSQL(query, args...).ScanStructsContext(ctx, i)
}
// See Database#ScanStruct
func (td *TxDatabase) ScanStruct(i interface{}, query string, args ...interface{}) (bool, error) {
return td.ScanStructContext(context.Background(), i, query, args...)
}
// See Database#ScanStructContext
func (td *TxDatabase) ScanStructContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) {
return td.queryFactory().FromSQL(query, args...).ScanStructContext(ctx, i)
}
// See Database#ScanVals
func (td *TxDatabase) ScanVals(i interface{}, query string, args ...interface{}) error {
return td.ScanValsContext(context.Background(), i, query, args...)
}
// See Database#ScanValsContext
func (td *TxDatabase) ScanValsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error {
return td.queryFactory().FromSQL(query, args...).ScanValsContext(ctx, i)
}
// See Database#ScanVal
func (td *TxDatabase) ScanVal(i interface{}, query string, args ...interface{}) (bool, error) {
return td.ScanValContext(context.Background(), i, query, args...)
}
// See Database#ScanValContext
func (td *TxDatabase) ScanValContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) {
return td.queryFactory().FromSQL(query, args...).ScanValContext(ctx, i)
}
// COMMIT the transaction
func (td *TxDatabase) Commit() error {
td.Trace("COMMIT", "")
return td.Tx.Commit()
}
// ROLLBACK the transaction
func (td *TxDatabase) Rollback() error {
td.Trace("ROLLBACK", "")
return td.Tx.Rollback()
}
// A helper method that will automatically COMMIT or ROLLBACK once the supplied function is done executing
//
// tx, err := db.Begin()
// if err != nil{
// panic(err.Error()) // you could gracefully handle the error also
// }
// if err := tx.Wrap(func() error{
// if _, err := tx.From("test").Insert(Record{"a":1, "b": "b"}).Exec(){
// // this error will be the return error from the Wrap call
// return err
// }
// return nil
// }); err != nil{
// panic(err.Error()) // you could gracefully handle the error also
// }
func (td *TxDatabase) Wrap(fn func() error) (err error) {
defer func() {
if p := recover(); p != nil {
_ = td.Rollback()
panic(p)
}
if err != nil {
if rollbackErr := td.Rollback(); rollbackErr != nil {
err = rollbackErr
}
} else {
if commitErr := td.Commit(); commitErr != nil {
err = commitErr
}
}
}()
return fn()
}