lotus/lib/sturdy/clusterdb/userfuncs.go

150 lines
4.4 KiB
Go
Raw Normal View History

2023-07-11 22:20:09 +00:00
package clusterdb
import (
"context"
"github.com/georgysavva/scany/v2/pgxscan"
"github.com/jackc/pgx/v5"
)
type Intf interface {
ITestDeleteAll()
Exec(ctx context.Context, sql rawStringOnly, arguments ...any) error
Query(ctx context.Context, sql rawStringOnly, arguments ...any) (*Query, error)
QueryRow(ctx context.Context, sql rawStringOnly, arguments ...any) Row
Select(ctx context.Context, sliceOfStructPtr any, sql rawStringOnly, arguments ...any) error
BeginTransaction(ctx context.Context, f func(t *Transaction) (commit bool)) (retErr error)
}
// rawStringOnly is _intentionally_private_ to force only basic strings in SQL queries.
// In any package, raw strings will satisfy compilation. Ex:
//
2023-07-12 02:50:03 +00:00
// clusterDB.Exec("INSERT INTO version (number) VALUES (1)")
2023-07-11 22:20:09 +00:00
//
// This prevents SQL injection attacks where the input contains query fragments.
type rawStringOnly string
// Exec executes changes (INSERT, DELETE, or UPDATE).
// Note, for CREATE & DROP please keep these permanent and express
// them in the ./sql/ files (next number).
func (db *DB) Exec(ctx context.Context, sql rawStringOnly, arguments ...any) error {
_, err := db.pgx.Exec(ctx, string(sql), arguments...)
return err
}
type Qry interface {
Next() bool
Err() error
Close()
Scan(...any) error
Values() ([]any, error)
}
// Query offers Next/Err/Close/Scan/Values/StructScan
type Query struct {
Qry
}
// Query allows iterating returned values to save memory consumption
// with the downside of needing to `defer q.Close()`. For a simpler interface,
// try Select()
// Next() must be called to advance the row cursor, including the first time:
// Ex:
// q, err := db.Query(ctx, "SELECT id, name FROM users")
// handleError(err)
// defer q.Close()
//
// for q.Next() {
// var id int
// var name string
// handleError(q.Scan(&id, &name))
// fmt.Println(id, name)
// }
func (db *DB) Query(ctx context.Context, sql rawStringOnly, arguments ...any) (*Query, error) {
q, err := db.pgx.Query(ctx, string(sql), arguments...)
return &Query{q}, err
}
func (q *Query) StructScan(s any) error {
return pgxscan.ScanRow(s, q.Qry.(pgx.Rows))
}
type Row interface {
Scan(...any) error
}
// QueryRow gets 1 row using column order matching.
// This is a timesaver for the special case of wanting the first row returned only.
// EX:
//
// var name, pet string
// var ID = 123
// err := db.QueryRow(ctx, "SELECT name, pet FROM users WHERE ID=?", ID).Scan(&name, &pet)
func (db *DB) QueryRow(ctx context.Context, sql rawStringOnly, arguments ...any) Row {
return db.pgx.QueryRow(ctx, string(sql), arguments...)
}
/*
Select multiple rows into a slice using name matching
Ex:
type user struct {
Name string
ID int
Number string `db:"tel_no"`
}
var users []user
pet := "cat"
err := db.Select(ctx, &users, "SELECT name, id, tel_no FROM customers WHERE pet=?", pet)
*/
func (db *DB) Select(ctx context.Context, sliceOfStructPtr any, sql rawStringOnly, arguments ...any) error {
return pgxscan.Select(ctx, db.pgx, sliceOfStructPtr, string(sql), arguments...)
}
type Transaction struct {
pgx.Tx
}
// BeginTransaction is how you can access transactions using this library.
// The entire transaction happens in the function passed in.
// The return must be true or a rollback will occur.
func (db *DB) BeginTransaction(ctx context.Context, f func(t *Transaction) (commit bool)) (retErr error) {
tx, err := db.pgx.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
return err
}
var commit bool
defer func() { // Panic clean-up.
if !commit {
retErr = tx.Rollback(ctx)
}
}()
commit = f(&Transaction{tx})
if commit {
return tx.Commit(ctx)
}
return nil
}
// Exec in a transaction.
func (t *Transaction) Exec(ctx context.Context, sql rawStringOnly, arguments ...any) error {
_, err := t.Tx.Exec(ctx, string(sql), arguments...)
return err
}
// Query in a transaction.
func (t *Transaction) Query(ctx context.Context, sql rawStringOnly, arguments ...any) (*Query, error) {
q, err := t.Tx.Query(ctx, string(sql), arguments...)
return &Query{q}, err
}
// QueryRow in a transaction.
func (t *Transaction) QueryRow(ctx context.Context, sql rawStringOnly, arguments ...any) Row {
return t.Tx.QueryRow(ctx, string(sql), arguments...)
}
// Select in a transaction.
func (t *Transaction) Select(ctx context.Context, sliceOfStructPtr any, sql rawStringOnly, arguments ...any) error {
return pgxscan.Select(ctx, t.Tx, sliceOfStructPtr, string(sql), arguments...)
}