diff options
| author | Stefan Majewsky <majewsky@gmx.net> | 2026-04-12 19:13:50 +0200 |
|---|---|---|
| committer | Stefan Majewsky <majewsky@gmx.net> | 2026-04-12 19:13:55 +0200 |
| commit | a23dd12a27237a5e0d6883cd30373408a2f28f6e (patch) | |
| tree | 633264b882173c21c93b0325a15f9c53398ba05b /query.go | |
| parent | 9b5b72a549643a9e611f55ae8154fa801c808e5b (diff) | |
| download | go-oblast-a23dd12a27237a5e0d6883cd30373408a2f28f6e.tar.gz | |
add initial sketches for Store.Insert, Store.Update
Currently extremely bad performance for some reason. Need to investigate.
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 +} |
