aboutsummaryrefslogtreecommitdiff
path: root/query.go
diff options
context:
space:
mode:
Diffstat (limited to 'query.go')
-rw-r--r--query.go120
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
+}