diff options
Diffstat (limited to 'query.go')
| -rw-r--r-- | query.go | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/query.go b/query.go new file mode 100644 index 0000000..8b0d2cd --- /dev/null +++ b/query.go @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net> +// SPDX-License-Identifier: Apache-2.0 + +package oblast + +import ( + "errors" + "fmt" + "reflect" +) + +// Insert executes an SQL INSERT statement for each of the provided records. +// +// Fields that are declared with the "auto" tag will not be written into the DB, +// and instead their value (as auto-generated by the DB on insert) will be placed in the record. +// +// Returns an error if [NewStore] was called without the [TableNameIs] option, which is required to generate a query for this method. +func (s Store[R]) Insert(db Handle, records ...*R) (returnedError error) { + // NOTE: This function body should be as short as possible to reduce the binary size after monomorphization. + // Any expression that does not depend on type R should be factored out into a reusable function. + // TODO: minimize + + if s.plan.Insert.Query == "" { + return errors.New("cannot execute Insert() because query could not be autogenerated") + } + + var ( + argumentIndexes = s.plan.Insert.ArgumentIndexes + argumentSlots = make([]any, len(argumentIndexes)) + scanIndexes = s.plan.Insert.ScanIndexes + scanSlots []any + ) + if len(scanIndexes) > 0 { + scanSlots = make([]any, len(scanIndexes)) + } + + stmt, err := db.Prepare(s.plan.Insert.Query) + if err != nil { + return fmt.Errorf("during Prepare(): %w", err) + } + defer func() { + returnedError = mergeCloseError("Stmt", returnedError, stmt.Close()) + }() + + for idx, r := range records { + v := reflect.ValueOf(r).Elem() + for idx, index := range argumentIndexes { + argumentSlots[idx] = v.FieldByIndex(index).Addr().Interface() + } + + if s.dialect.UsesLastInsertID() { + result, err := stmt.Exec(argumentSlots...) + if err != nil { + return fmt.Errorf("during Exec() for record with idx = %d: %w", idx, err) + } + id, err := result.LastInsertId() + if err != nil { + return fmt.Errorf("during LastInsertId() for record with idx = %d: %w", idx, err) + } + if s.plan.FillIDWithSetInt { + v.FieldByIndex(scanIndexes[0]).SetInt(id) + } else if s.plan.FillIDWithSetUint { + if id < 0 { + return fmt.Errorf("LastInsertId() = %d for record with idx = %d cannot be converted to uint", id, idx) + } + v.FieldByIndex(scanIndexes[0]).SetUint(uint64(id)) + } + } else { + for idx, index := range scanIndexes { + scanSlots[idx] = v.FieldByIndex(index).Addr().Interface() + } + err := stmt.QueryRow(argumentSlots...).Scan(scanSlots...) + if err != nil { + return fmt.Errorf("during QueryRow() for record with idx = %d: %w", idx, err) + } + } + } + + return nil +} + +// TODO: update + +// Delete executes an SQL DELETE statement for each of the provided records, using their primary keys to locate the respective table rows. +// +// Returns an error if [NewStore] was called without the [TableNameIs] or [PrimaryKeyIs] options, which are both required to generate a query for this method. +func (s Store[R]) Delete(db Handle, records ...R) (returnedError error) { + // NOTE: This function body should be as short as possible to reduce the binary size after monomorphization. + // Any expression that does not depend on type R should be factored out into a reusable function. + // TODO: minimize + + if s.plan.Delete.Query == "" { + return errors.New("cannot execute Delete() because query could not be autogenerated") + } + + var ( + argumentIndexes = s.plan.Delete.ArgumentIndexes + argumentSlots = make([]any, len(argumentIndexes)) + ) + + stmt, err := db.Prepare(s.plan.Delete.Query) + if err != nil { + return fmt.Errorf("during Prepare(): %w", err) + } + defer func() { + returnedError = mergeCloseError("Stmt", returnedError, stmt.Close()) + }() + + for idx, r := range records { + v := reflect.ValueOf(&r).Elem() + for idx, index := range argumentIndexes { + argumentSlots[idx] = v.FieldByIndex(index).Addr().Interface() + } + _, err := stmt.Exec(argumentSlots...) + if err != nil { + return fmt.Errorf("during Exec() for record with idx = %d: %w", idx, err) + } + } + return nil +} |
