// SPDX-FileCopyrightText: 2026 Stefan Majewsky // 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. // On success, returns the original set of records, updated thusly. // // 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) (returnedRecords []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 nil, 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 nil, fmt.Errorf("during Prepare(): %w", err) } defer func() { returnedError = mergeCloseError("Stmt", returnedError, stmt.Close()) }() for idx := range records { v := reflect.ValueOf(&records[idx]).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 nil, fmt.Errorf("during Exec() for record with idx = %d: %w", idx, err) } id, err := result.LastInsertId() if err != nil { return nil, 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 nil, 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 nil, fmt.Errorf("during QueryRow() for record with idx = %d: %w", idx, err) } } } return records, 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 }