package norm import ( "context" "database/sql" "fmt" ) type Database interface { Prepare(sql string) (*sql.Stmt, error) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) Exec(query string, args ...any) (sql.Result, error) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) } type Compiler interface { Compile() (string, []any, error) MustCompile() (string, []any) } type Builder interface { Build(db Database) (*sql.Stmt, []any, error) MustBuild(db Database) (*sql.Stmt, []any) } // anything that is a Compiler, can also be Builder: func Build(c Compiler, p Database) (*sql.Stmt, []any, error) { query, args, err := c.Compile() if err != nil { return nil, nil, err } stmt, err := p.Prepare(query) if err != nil { return nil, nil, fmt.Errorf("failed to prepare statement: `%s` : %w", query, err) } return stmt, args, nil } func MustBuild(c Compiler, p Database) (*sql.Stmt, []any) { stmt, args, err := Build(c, p) if err != nil { panic(err) } return stmt, args } type Execer interface { Exec(db Database) (sql.Result, error) ExecContext(ctx context.Context, db Database) (sql.Result, error) MustExec(db Database) sql.Result MustExecContext(ctx context.Context, db Database) sql.Result } // anything that is a Builder can also be an Execer func Exec(b Builder, p Database) (sql.Result, error) { return ExecContext(context.Background(), b, p) } func ExecContext(ctx context.Context, b Builder, p Database) (sql.Result, error) { stmt, args, err := b.Build(p) if err != nil { return nil, err } return stmt.ExecContext(ctx, args...) } func MustExec(b Builder, p Database) sql.Result { return unwrap(Exec(b, p)) } func MustExecContext(ctx context.Context, b Builder, p Database) sql.Result { return unwrap(ExecContext(ctx, b, p)) } type Querier interface { Query(db Database) (*sql.Rows, error) QueryContext(ctx context.Context, db Database) (*sql.Rows, error) QueryRow(db Database) (*sql.Row, error) QueryRowContext(ctx context.Context, db Database) (*sql.Row, error) MustQuery(db Database) *sql.Rows MustQueryContext(ctx context.Context, db Database) *sql.Rows MustQueryRow(db Database) *sql.Row MustQueryRowContext(ctx context.Context, db Database) *sql.Row } // anything that is a Builder can also be an Querier func Query(b Builder, db Database) (*sql.Rows, error) { return QueryContext(context.Background(), b, db) } func QueryContext(ctx context.Context, b Builder, db Database) (*sql.Rows, error) { stmt, args, err := b.Build(db) if err != nil { return nil, err } return stmt.QueryContext(ctx, args...) } func QueryRow(b Builder, db Database) (*sql.Row, error) { return QueryRowContext(context.Background(), b, db) } func QueryRowContext(ctx context.Context, b Builder, db Database) (*sql.Row, error) { stmt, args, err := b.Build(db) if err != nil { return nil, err } return stmt.QueryRowContext(ctx, args...), nil } func MustQuery(b Builder, db Database) *sql.Rows { return unwrap(Query(b, db)) } func MustQueryContext(ctx context.Context, b Builder, db Database) *sql.Rows { return unwrap(QueryContext(ctx, b, db)) } func MustQueryRow(b Builder, db Database) *sql.Row { return unwrap(QueryRow(b, db)) } func MustQueryRowContext(ctx context.Context, b Builder, db Database) *sql.Row { return unwrap(QueryRowContext(ctx, b, db)) } type Direction string const ( Ascending Direction = "asc" Descending Direction = "desc" ) func Placeholder[T any]() T { var zero T return zero } func unwrap[P any](p P, e error) P { if e != nil { panic(e) } return p }