mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-10-02 03:22:09 +00:00
pkg/security no longer contains SQL. Every provider calls a store interface
from lookup, implemented by a procedure backend (Postgres stored procedures,
the default there) and a direct backend (dialect-driven SQL for postgres,
sqlite, mysql and mssql with configurable table and column names).
- add sectypes, lookup, lookup/{dialect,procedure,direct,backends,ddl,conformance}
- split totp and providers sub packages out of the core package
- replace SQLNames/TableNames/QueryMode with lookup.Config (see breaking_changes.md)
- direct backend now covers column/row security and API-key login
- move txsettings SQL to lookup.ApplyTxSettings; remove password.go
- move schema scripts under lookup/, add reference DDL per dialect
- add a shared conformance suite; run it on sqlite, and on Postgres in a
podman/docker container (RESOLVESPEC_TEST_CONTAINERS=1)
- fix procedure schema bugs found on real Postgres: duplicate p_data
parameter, JSON null arrays, expires_at timezone casts, passkey list
GROUP BY, missing resolvespec_passkey_login; accept zone-less timestamps
93 lines
2.4 KiB
Go
93 lines
2.4 KiB
Go
// Package procedure is the stored-procedure backend of lookup: it calls the
|
|
// resolvespec_* functions (names from lookup.ProcNames) and keeps their
|
|
// p_success / p_error / p_data contract. Error texts match the ones the security
|
|
// package returned before the extraction.
|
|
package procedure
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// Runner runs a database operation, reconnecting once when the *sql.DB has been closed.
|
|
type Runner interface {
|
|
Run(run func(*sql.DB) error) error
|
|
}
|
|
|
|
// RunFunc adapts a function to Runner. The security package passes its own
|
|
// reconnecting helper this way.
|
|
type RunFunc func(run func(*sql.DB) error) error
|
|
|
|
// Run implements Runner.
|
|
func (f RunFunc) Run(run func(*sql.DB) error) error { return f(run) }
|
|
|
|
// DB is a standalone Runner over a *sql.DB with an optional reconnect factory.
|
|
type DB struct {
|
|
mu sync.RWMutex
|
|
db *sql.DB
|
|
factory func() (*sql.DB, error)
|
|
onReconnect func()
|
|
}
|
|
|
|
// NewDB wraps db. factory (optional) is called to obtain a fresh handle when the current
|
|
// one is closed; onReconnect (optional) runs after a successful reconnect, e.g. to reset
|
|
// cached procedure probes.
|
|
func NewDB(db *sql.DB, factory func() (*sql.DB, error), onReconnect func()) *DB {
|
|
return &DB{db: db, factory: factory, onReconnect: onReconnect}
|
|
}
|
|
|
|
// Get returns the current handle.
|
|
func (d *DB) Get() *sql.DB {
|
|
d.mu.RLock()
|
|
defer d.mu.RUnlock()
|
|
return d.db
|
|
}
|
|
|
|
func (d *DB) reconnect() error {
|
|
if d.factory == nil {
|
|
return fmt.Errorf("no db factory configured for reconnect")
|
|
}
|
|
newDB, err := d.factory()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.mu.Lock()
|
|
d.db = newDB
|
|
d.mu.Unlock()
|
|
if d.onReconnect != nil {
|
|
d.onReconnect()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Run implements Runner.
|
|
func (d *DB) Run(run func(*sql.DB) error) error {
|
|
db := d.Get()
|
|
if db == nil {
|
|
return fmt.Errorf("database connection is nil")
|
|
}
|
|
err := run(db)
|
|
if IsClosed(err) {
|
|
if reconnErr := d.reconnect(); reconnErr == nil {
|
|
err = run(d.Get())
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// IsClosed reports whether err indicates the *sql.DB has been closed.
|
|
func IsClosed(err error) bool {
|
|
return err != nil && strings.Contains(err.Error(), "sql: database is closed")
|
|
}
|
|
|
|
// failure builds the error for p_success = false: the procedure's own message when it
|
|
// returned one, otherwise def.
|
|
func failure(errMsg sql.NullString, def string) error {
|
|
if errMsg.Valid {
|
|
return fmt.Errorf("%s", errMsg.String)
|
|
}
|
|
return fmt.Errorf("%s", def)
|
|
}
|