Add SQLite database storage backend
This commit is contained in:
131
base/database/storage/sqlite/models/bob_main.bob.go
Normal file
131
base/database/storage/sqlite/models/bob_main.bob.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// Code generated by BobGen sqlite v0.30.0. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"hash/maphash"
|
||||
"strings"
|
||||
|
||||
"github.com/stephenafamo/bob"
|
||||
"github.com/stephenafamo/bob/clause"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite/dialect"
|
||||
sqliteDriver "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
var TableNames = struct {
|
||||
Records string
|
||||
}{
|
||||
Records: "records",
|
||||
}
|
||||
|
||||
var ColumnNames = struct {
|
||||
Records recordColumnNames
|
||||
}{
|
||||
Records: recordColumnNames{
|
||||
Key: "key",
|
||||
Format: "format",
|
||||
Value: "value",
|
||||
Created: "created",
|
||||
Modified: "modified",
|
||||
Expires: "expires",
|
||||
Deleted: "deleted",
|
||||
Secret: "secret",
|
||||
Crownjewel: "crownjewel",
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
SelectWhere = Where[*dialect.SelectQuery]()
|
||||
InsertWhere = Where[*dialect.InsertQuery]()
|
||||
UpdateWhere = Where[*dialect.UpdateQuery]()
|
||||
DeleteWhere = Where[*dialect.DeleteQuery]()
|
||||
)
|
||||
|
||||
func Where[Q sqlite.Filterable]() struct {
|
||||
Records recordWhere[Q]
|
||||
} {
|
||||
return struct {
|
||||
Records recordWhere[Q]
|
||||
}{
|
||||
Records: buildRecordWhere[Q](RecordColumns),
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
SelectJoins = getJoins[*dialect.SelectQuery]
|
||||
UpdateJoins = getJoins[*dialect.UpdateQuery]
|
||||
)
|
||||
|
||||
type joinSet[Q interface{ aliasedAs(string) Q }] struct {
|
||||
InnerJoin Q
|
||||
LeftJoin Q
|
||||
RightJoin Q
|
||||
}
|
||||
|
||||
func (j joinSet[Q]) AliasedAs(alias string) joinSet[Q] {
|
||||
return joinSet[Q]{
|
||||
InnerJoin: j.InnerJoin.aliasedAs(alias),
|
||||
LeftJoin: j.LeftJoin.aliasedAs(alias),
|
||||
RightJoin: j.RightJoin.aliasedAs(alias),
|
||||
}
|
||||
}
|
||||
|
||||
type joins[Q dialect.Joinable] struct{}
|
||||
|
||||
func buildJoinSet[Q interface{ aliasedAs(string) Q }, C any, F func(C, string) Q](c C, f F) joinSet[Q] {
|
||||
return joinSet[Q]{
|
||||
InnerJoin: f(c, clause.InnerJoin),
|
||||
LeftJoin: f(c, clause.LeftJoin),
|
||||
RightJoin: f(c, clause.RightJoin),
|
||||
}
|
||||
}
|
||||
|
||||
func getJoins[Q dialect.Joinable]() joins[Q] {
|
||||
return joins[Q]{}
|
||||
}
|
||||
|
||||
type modAs[Q any, C interface{ AliasedAs(string) C }] struct {
|
||||
c C
|
||||
f func(C) bob.Mod[Q]
|
||||
}
|
||||
|
||||
func (m modAs[Q, C]) Apply(q Q) {
|
||||
m.f(m.c).Apply(q)
|
||||
}
|
||||
|
||||
func (m modAs[Q, C]) AliasedAs(alias string) bob.Mod[Q] {
|
||||
m.c = m.c.AliasedAs(alias)
|
||||
return m
|
||||
}
|
||||
|
||||
func randInt() int64 {
|
||||
out := int64(new(maphash.Hash).Sum64())
|
||||
|
||||
if out < 0 {
|
||||
return -out % 10000
|
||||
}
|
||||
|
||||
return out % 10000
|
||||
}
|
||||
|
||||
// ErrUniqueConstraint captures all unique constraint errors by explicitly leaving `s` empty.
|
||||
var ErrUniqueConstraint = &UniqueConstraintError{s: ""}
|
||||
|
||||
type UniqueConstraintError struct {
|
||||
// s is a string uniquely identifying the constraint in the raw error message returned from the database.
|
||||
s string
|
||||
}
|
||||
|
||||
func (e *UniqueConstraintError) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
func (e *UniqueConstraintError) Is(target error) bool {
|
||||
err, ok := target.(*sqliteDriver.Error)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return err.Code() == 2067 && strings.Contains(err.Error(), e.s)
|
||||
}
|
||||
9
base/database/storage/sqlite/models/bob_main_test.bob.go
Normal file
9
base/database/storage/sqlite/models/bob_main_test.bob.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Code generated by BobGen sqlite v0.30.0. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package models
|
||||
|
||||
import "github.com/stephenafamo/bob"
|
||||
|
||||
// Make sure the type Record runs hooks after queries
|
||||
var _ bob.HookableType = &Record{}
|
||||
553
base/database/storage/sqlite/models/records.bob.go
Normal file
553
base/database/storage/sqlite/models/records.bob.go
Normal file
@@ -0,0 +1,553 @@
|
||||
// Code generated by BobGen sqlite v0.30.0. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/stephenafamo/bob"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite/dialect"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite/dm"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite/sm"
|
||||
"github.com/stephenafamo/bob/dialect/sqlite/um"
|
||||
"github.com/stephenafamo/bob/expr"
|
||||
)
|
||||
|
||||
// Record is an object representing the database table.
|
||||
type Record struct {
|
||||
Key string `db:"key,pk" `
|
||||
Format int16 `db:"format" `
|
||||
Value []byte `db:"value" `
|
||||
Created int64 `db:"created" `
|
||||
Modified int64 `db:"modified" `
|
||||
Expires int64 `db:"expires" `
|
||||
Deleted int64 `db:"deleted" `
|
||||
Secret bool `db:"secret" `
|
||||
Crownjewel bool `db:"crownjewel" `
|
||||
}
|
||||
|
||||
// RecordSlice is an alias for a slice of pointers to Record.
|
||||
// This should almost always be used instead of []*Record.
|
||||
type RecordSlice []*Record
|
||||
|
||||
// Records contains methods to work with the records table
|
||||
var Records = sqlite.NewTablex[*Record, RecordSlice, *RecordSetter]("", "records")
|
||||
|
||||
// RecordsQuery is a query on the records table
|
||||
type RecordsQuery = *sqlite.ViewQuery[*Record, RecordSlice]
|
||||
|
||||
type recordColumnNames struct {
|
||||
Key string
|
||||
Format string
|
||||
Value string
|
||||
Created string
|
||||
Modified string
|
||||
Expires string
|
||||
Deleted string
|
||||
Secret string
|
||||
Crownjewel string
|
||||
}
|
||||
|
||||
var RecordColumns = buildRecordColumns("records")
|
||||
|
||||
type recordColumns struct {
|
||||
tableAlias string
|
||||
Key sqlite.Expression
|
||||
Format sqlite.Expression
|
||||
Value sqlite.Expression
|
||||
Created sqlite.Expression
|
||||
Modified sqlite.Expression
|
||||
Expires sqlite.Expression
|
||||
Deleted sqlite.Expression
|
||||
Secret sqlite.Expression
|
||||
Crownjewel sqlite.Expression
|
||||
}
|
||||
|
||||
func (c recordColumns) Alias() string {
|
||||
return c.tableAlias
|
||||
}
|
||||
|
||||
func (recordColumns) AliasedAs(alias string) recordColumns {
|
||||
return buildRecordColumns(alias)
|
||||
}
|
||||
|
||||
func buildRecordColumns(alias string) recordColumns {
|
||||
return recordColumns{
|
||||
tableAlias: alias,
|
||||
Key: sqlite.Quote(alias, "key"),
|
||||
Format: sqlite.Quote(alias, "format"),
|
||||
Value: sqlite.Quote(alias, "value"),
|
||||
Created: sqlite.Quote(alias, "created"),
|
||||
Modified: sqlite.Quote(alias, "modified"),
|
||||
Expires: sqlite.Quote(alias, "expires"),
|
||||
Deleted: sqlite.Quote(alias, "deleted"),
|
||||
Secret: sqlite.Quote(alias, "secret"),
|
||||
Crownjewel: sqlite.Quote(alias, "crownjewel"),
|
||||
}
|
||||
}
|
||||
|
||||
type recordWhere[Q sqlite.Filterable] struct {
|
||||
Key sqlite.WhereMod[Q, string]
|
||||
Format sqlite.WhereMod[Q, int16]
|
||||
Value sqlite.WhereMod[Q, []byte]
|
||||
Created sqlite.WhereMod[Q, int64]
|
||||
Modified sqlite.WhereMod[Q, int64]
|
||||
Expires sqlite.WhereMod[Q, int64]
|
||||
Deleted sqlite.WhereMod[Q, int64]
|
||||
Secret sqlite.WhereMod[Q, bool]
|
||||
Crownjewel sqlite.WhereMod[Q, bool]
|
||||
}
|
||||
|
||||
func (recordWhere[Q]) AliasedAs(alias string) recordWhere[Q] {
|
||||
return buildRecordWhere[Q](buildRecordColumns(alias))
|
||||
}
|
||||
|
||||
func buildRecordWhere[Q sqlite.Filterable](cols recordColumns) recordWhere[Q] {
|
||||
return recordWhere[Q]{
|
||||
Key: sqlite.Where[Q, string](cols.Key),
|
||||
Format: sqlite.Where[Q, int16](cols.Format),
|
||||
Value: sqlite.Where[Q, []byte](cols.Value),
|
||||
Created: sqlite.Where[Q, int64](cols.Created),
|
||||
Modified: sqlite.Where[Q, int64](cols.Modified),
|
||||
Expires: sqlite.Where[Q, int64](cols.Expires),
|
||||
Deleted: sqlite.Where[Q, int64](cols.Deleted),
|
||||
Secret: sqlite.Where[Q, bool](cols.Secret),
|
||||
Crownjewel: sqlite.Where[Q, bool](cols.Crownjewel),
|
||||
}
|
||||
}
|
||||
|
||||
// RecordSetter is used for insert/upsert/update operations
|
||||
// All values are optional, and do not have to be set
|
||||
// Generated columns are not included
|
||||
type RecordSetter struct {
|
||||
Key omit.Val[string] `db:"key,pk" `
|
||||
Format omit.Val[int16] `db:"format" `
|
||||
Value omit.Val[[]byte] `db:"value" `
|
||||
Created omit.Val[int64] `db:"created" `
|
||||
Modified omit.Val[int64] `db:"modified" `
|
||||
Expires omit.Val[int64] `db:"expires" `
|
||||
Deleted omit.Val[int64] `db:"deleted" `
|
||||
Secret omit.Val[bool] `db:"secret" `
|
||||
Crownjewel omit.Val[bool] `db:"crownjewel" `
|
||||
}
|
||||
|
||||
func (s RecordSetter) SetColumns() []string {
|
||||
vals := make([]string, 0, 9)
|
||||
if !s.Key.IsUnset() {
|
||||
vals = append(vals, "key")
|
||||
}
|
||||
|
||||
if !s.Format.IsUnset() {
|
||||
vals = append(vals, "format")
|
||||
}
|
||||
|
||||
if !s.Value.IsUnset() {
|
||||
vals = append(vals, "value")
|
||||
}
|
||||
|
||||
if !s.Created.IsUnset() {
|
||||
vals = append(vals, "created")
|
||||
}
|
||||
|
||||
if !s.Modified.IsUnset() {
|
||||
vals = append(vals, "modified")
|
||||
}
|
||||
|
||||
if !s.Expires.IsUnset() {
|
||||
vals = append(vals, "expires")
|
||||
}
|
||||
|
||||
if !s.Deleted.IsUnset() {
|
||||
vals = append(vals, "deleted")
|
||||
}
|
||||
|
||||
if !s.Secret.IsUnset() {
|
||||
vals = append(vals, "secret")
|
||||
}
|
||||
|
||||
if !s.Crownjewel.IsUnset() {
|
||||
vals = append(vals, "crownjewel")
|
||||
}
|
||||
|
||||
return vals
|
||||
}
|
||||
|
||||
func (s RecordSetter) Overwrite(t *Record) {
|
||||
if !s.Key.IsUnset() {
|
||||
t.Key, _ = s.Key.Get()
|
||||
}
|
||||
if !s.Format.IsUnset() {
|
||||
t.Format, _ = s.Format.Get()
|
||||
}
|
||||
if !s.Value.IsUnset() {
|
||||
t.Value, _ = s.Value.Get()
|
||||
}
|
||||
if !s.Created.IsUnset() {
|
||||
t.Created, _ = s.Created.Get()
|
||||
}
|
||||
if !s.Modified.IsUnset() {
|
||||
t.Modified, _ = s.Modified.Get()
|
||||
}
|
||||
if !s.Expires.IsUnset() {
|
||||
t.Expires, _ = s.Expires.Get()
|
||||
}
|
||||
if !s.Deleted.IsUnset() {
|
||||
t.Deleted, _ = s.Deleted.Get()
|
||||
}
|
||||
if !s.Secret.IsUnset() {
|
||||
t.Secret, _ = s.Secret.Get()
|
||||
}
|
||||
if !s.Crownjewel.IsUnset() {
|
||||
t.Crownjewel, _ = s.Crownjewel.Get()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RecordSetter) Apply(q *dialect.InsertQuery) {
|
||||
q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) {
|
||||
return Records.BeforeInsertHooks.RunHooks(ctx, exec, s)
|
||||
})
|
||||
|
||||
if len(q.Table.Columns) == 0 {
|
||||
q.Table.Columns = s.SetColumns()
|
||||
}
|
||||
|
||||
q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
|
||||
vals := make([]bob.Expression, 0, 9)
|
||||
if !s.Key.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Key))
|
||||
}
|
||||
|
||||
if !s.Format.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Format))
|
||||
}
|
||||
|
||||
if !s.Value.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Value))
|
||||
}
|
||||
|
||||
if !s.Created.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Created))
|
||||
}
|
||||
|
||||
if !s.Modified.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Modified))
|
||||
}
|
||||
|
||||
if !s.Expires.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Expires))
|
||||
}
|
||||
|
||||
if !s.Deleted.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Deleted))
|
||||
}
|
||||
|
||||
if !s.Secret.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Secret))
|
||||
}
|
||||
|
||||
if !s.Crownjewel.IsUnset() {
|
||||
vals = append(vals, sqlite.Arg(s.Crownjewel))
|
||||
}
|
||||
|
||||
return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "")
|
||||
}))
|
||||
}
|
||||
|
||||
func (s RecordSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] {
|
||||
return um.Set(s.Expressions()...)
|
||||
}
|
||||
|
||||
func (s RecordSetter) Expressions(prefix ...string) []bob.Expression {
|
||||
exprs := make([]bob.Expression, 0, 9)
|
||||
|
||||
if !s.Key.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "key")...),
|
||||
sqlite.Arg(s.Key),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Format.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "format")...),
|
||||
sqlite.Arg(s.Format),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Value.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "value")...),
|
||||
sqlite.Arg(s.Value),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Created.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "created")...),
|
||||
sqlite.Arg(s.Created),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Modified.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "modified")...),
|
||||
sqlite.Arg(s.Modified),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Expires.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "expires")...),
|
||||
sqlite.Arg(s.Expires),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Deleted.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "deleted")...),
|
||||
sqlite.Arg(s.Deleted),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Secret.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "secret")...),
|
||||
sqlite.Arg(s.Secret),
|
||||
}})
|
||||
}
|
||||
|
||||
if !s.Crownjewel.IsUnset() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
sqlite.Quote(append(prefix, "crownjewel")...),
|
||||
sqlite.Arg(s.Crownjewel),
|
||||
}})
|
||||
}
|
||||
|
||||
return exprs
|
||||
}
|
||||
|
||||
// FindRecord retrieves a single record by primary key
|
||||
// If cols is empty Find will return all columns.
|
||||
func FindRecord(ctx context.Context, exec bob.Executor, KeyPK string, cols ...string) (*Record, error) {
|
||||
if len(cols) == 0 {
|
||||
return Records.Query(
|
||||
SelectWhere.Records.Key.EQ(KeyPK),
|
||||
).One(ctx, exec)
|
||||
}
|
||||
|
||||
return Records.Query(
|
||||
SelectWhere.Records.Key.EQ(KeyPK),
|
||||
sm.Columns(Records.Columns().Only(cols...)),
|
||||
).One(ctx, exec)
|
||||
}
|
||||
|
||||
// RecordExists checks the presence of a single record by primary key
|
||||
func RecordExists(ctx context.Context, exec bob.Executor, KeyPK string) (bool, error) {
|
||||
return Records.Query(
|
||||
SelectWhere.Records.Key.EQ(KeyPK),
|
||||
).Exists(ctx, exec)
|
||||
}
|
||||
|
||||
// AfterQueryHook is called after Record is retrieved from the database
|
||||
func (o *Record) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error {
|
||||
var err error
|
||||
|
||||
switch queryType {
|
||||
case bob.QueryTypeSelect:
|
||||
ctx, err = Records.AfterSelectHooks.RunHooks(ctx, exec, RecordSlice{o})
|
||||
case bob.QueryTypeInsert:
|
||||
ctx, err = Records.AfterInsertHooks.RunHooks(ctx, exec, RecordSlice{o})
|
||||
case bob.QueryTypeUpdate:
|
||||
ctx, err = Records.AfterUpdateHooks.RunHooks(ctx, exec, RecordSlice{o})
|
||||
case bob.QueryTypeDelete:
|
||||
ctx, err = Records.AfterDeleteHooks.RunHooks(ctx, exec, RecordSlice{o})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// PrimaryKeyVals returns the primary key values of the Record
|
||||
func (o *Record) PrimaryKeyVals() bob.Expression {
|
||||
return sqlite.Arg(o.Key)
|
||||
}
|
||||
|
||||
func (o *Record) pkEQ() dialect.Expression {
|
||||
return sqlite.Quote("records", "key").EQ(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
|
||||
return o.PrimaryKeyVals().WriteSQL(ctx, w, d, start)
|
||||
}))
|
||||
}
|
||||
|
||||
// Update uses an executor to update the Record
|
||||
func (o *Record) Update(ctx context.Context, exec bob.Executor, s *RecordSetter) error {
|
||||
v, err := Records.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*o = *v
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a single Record record with an executor
|
||||
func (o *Record) Delete(ctx context.Context, exec bob.Executor) error {
|
||||
_, err := Records.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec)
|
||||
return err
|
||||
}
|
||||
|
||||
// Reload refreshes the Record using the executor
|
||||
func (o *Record) Reload(ctx context.Context, exec bob.Executor) error {
|
||||
o2, err := Records.Query(
|
||||
SelectWhere.Records.Key.EQ(o.Key),
|
||||
).One(ctx, exec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*o = *o2
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AfterQueryHook is called after RecordSlice is retrieved from the database
|
||||
func (o RecordSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error {
|
||||
var err error
|
||||
|
||||
switch queryType {
|
||||
case bob.QueryTypeSelect:
|
||||
ctx, err = Records.AfterSelectHooks.RunHooks(ctx, exec, o)
|
||||
case bob.QueryTypeInsert:
|
||||
ctx, err = Records.AfterInsertHooks.RunHooks(ctx, exec, o)
|
||||
case bob.QueryTypeUpdate:
|
||||
ctx, err = Records.AfterUpdateHooks.RunHooks(ctx, exec, o)
|
||||
case bob.QueryTypeDelete:
|
||||
ctx, err = Records.AfterDeleteHooks.RunHooks(ctx, exec, o)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (o RecordSlice) pkIN() dialect.Expression {
|
||||
if len(o) == 0 {
|
||||
return sqlite.Raw("NULL")
|
||||
}
|
||||
|
||||
return sqlite.Quote("records", "key").In(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
|
||||
pkPairs := make([]bob.Expression, len(o))
|
||||
for i, row := range o {
|
||||
pkPairs[i] = row.PrimaryKeyVals()
|
||||
}
|
||||
return bob.ExpressSlice(ctx, w, d, start, pkPairs, "", ", ", "")
|
||||
}))
|
||||
}
|
||||
|
||||
// copyMatchingRows finds models in the given slice that have the same primary key
|
||||
// then it first copies the existing relationships from the old model to the new model
|
||||
// and then replaces the old model in the slice with the new model
|
||||
func (o RecordSlice) copyMatchingRows(from ...*Record) {
|
||||
for i, old := range o {
|
||||
for _, new := range from {
|
||||
if new.Key != old.Key {
|
||||
continue
|
||||
}
|
||||
|
||||
o[i] = new
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateMod modifies an update query with "WHERE primary_key IN (o...)"
|
||||
func (o RecordSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] {
|
||||
return bob.ModFunc[*dialect.UpdateQuery](func(q *dialect.UpdateQuery) {
|
||||
q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) {
|
||||
return Records.BeforeUpdateHooks.RunHooks(ctx, exec, o)
|
||||
})
|
||||
|
||||
q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error {
|
||||
var err error
|
||||
switch retrieved := retrieved.(type) {
|
||||
case *Record:
|
||||
o.copyMatchingRows(retrieved)
|
||||
case []*Record:
|
||||
o.copyMatchingRows(retrieved...)
|
||||
case RecordSlice:
|
||||
o.copyMatchingRows(retrieved...)
|
||||
default:
|
||||
// If the retrieved value is not a Record or a slice of Record
|
||||
// then run the AfterUpdateHooks on the slice
|
||||
_, err = Records.AfterUpdateHooks.RunHooks(ctx, exec, o)
|
||||
}
|
||||
|
||||
return err
|
||||
}))
|
||||
|
||||
q.AppendWhere(o.pkIN())
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)"
|
||||
func (o RecordSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] {
|
||||
return bob.ModFunc[*dialect.DeleteQuery](func(q *dialect.DeleteQuery) {
|
||||
q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) {
|
||||
return Records.BeforeDeleteHooks.RunHooks(ctx, exec, o)
|
||||
})
|
||||
|
||||
q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error {
|
||||
var err error
|
||||
switch retrieved := retrieved.(type) {
|
||||
case *Record:
|
||||
o.copyMatchingRows(retrieved)
|
||||
case []*Record:
|
||||
o.copyMatchingRows(retrieved...)
|
||||
case RecordSlice:
|
||||
o.copyMatchingRows(retrieved...)
|
||||
default:
|
||||
// If the retrieved value is not a Record or a slice of Record
|
||||
// then run the AfterDeleteHooks on the slice
|
||||
_, err = Records.AfterDeleteHooks.RunHooks(ctx, exec, o)
|
||||
}
|
||||
|
||||
return err
|
||||
}))
|
||||
|
||||
q.AppendWhere(o.pkIN())
|
||||
})
|
||||
}
|
||||
|
||||
func (o RecordSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals RecordSetter) error {
|
||||
if len(o) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := Records.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec)
|
||||
return err
|
||||
}
|
||||
|
||||
func (o RecordSlice) DeleteAll(ctx context.Context, exec bob.Executor) error {
|
||||
if len(o) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := Records.Delete(o.DeleteMod()).Exec(ctx, exec)
|
||||
return err
|
||||
}
|
||||
|
||||
func (o RecordSlice) ReloadAll(ctx context.Context, exec bob.Executor) error {
|
||||
if len(o) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
o2, err := Records.Query(sm.Where(o.pkIN())).All(ctx, exec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.copyMatchingRows(o2...)
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user