From d04dfa3bc58c86149064bbca672b42f660a4aebd Mon Sep 17 00:00:00 2001 From: Stefan Majewsky Date: Fri, 17 Apr 2026 16:49:10 +0200 Subject: reduce function body size for Update(), Delete() --- query.go | 103 +++++++++++++++++++++++++++++++++------------------------------ 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/query.go b/query.go index f8f8edd..bf1ea2d 100644 --- a/query.go +++ b/query.go @@ -5,7 +5,6 @@ package oblast import ( "database/sql" - "errors" "fmt" "reflect" ) @@ -30,7 +29,11 @@ type preparedStatement struct { } // prepare behaves like [Handle.Prepare]. -func prepare(db Handle, query string, inputSize int) (preparedStatement, error) { +func prepare(db Handle, query, operation string, inputSize int) (preparedStatement, error) { + if query == "" { + return preparedStatement{}, fmt.Errorf("cannot execute %s() because query could not be autogenerated", operation) + } + if inputSize < PrepareThreshold { return preparedStatement{db, query, nil}, nil } @@ -77,9 +80,13 @@ func (s Store[R]) Insert(db Handle, records ...R) (returnedRecords []R, returned // 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") + stmt, err := prepare(db, s.plan.Insert.Query, "Insert", len(records)) + if err != nil { + return nil, err } + defer func() { + returnedError = newIOError(returnedError, "Stmt.Close", stmt.Close()) + }() var ( argumentIndexes = s.plan.Insert.ArgumentIndexes @@ -91,14 +98,6 @@ func (s Store[R]) Insert(db Handle, records ...R) (returnedRecords []R, returned scanSlots = make([]any, len(scanIndexes)) } - stmt, err := prepare(db, s.plan.Insert.Query, len(records)) - if err != nil { - return nil, err - } - defer func() { - returnedError = newIOError(returnedError, "Stmt.Close", stmt.Close()) - }() - for idx := range records { v := reflect.ValueOf(&records[idx]).Elem() for idx, index := range argumentIndexes { @@ -141,8 +140,12 @@ func (s Store[R]) Insert(db Handle, records ...R) (returnedRecords []R, returned // // 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]) Update(db Handle, records ...R) (returnedError error) { - if s.plan.Update.Query == "" { - return errors.New("cannot execute Update() because query could not be autogenerated") + // 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. + + stmt, err := prepare(db, s.plan.Update.Query, "Update", len(records)) + if err != nil { + return err } var ( @@ -150,32 +153,32 @@ func (s Store[R]) Update(db Handle, records ...R) (returnedError error) { argumentSlots = make([]any, len(argumentIndexes)) ) - stmt, err := prepare(db, s.plan.Update.Query, len(records)) - if err != nil { - return err - } - defer func() { - returnedError = newIOError(returnedError, "Stmt.Close", stmt.Close()) - }() - for idx, r := range records { v := reflect.ValueOf(&r).Elem() - for idx, index := range argumentIndexes { - argumentSlots[idx] = v.FieldByIndex(index).Addr().Interface() + rowsAffected, err := updateRecord(v, idx, stmt, argumentIndexes, argumentSlots) + if err == nil && rowsAffected == 0 { + err = MissingRecordError[R]{r, s.plan} } - result, err := stmt.Exec(argumentSlots...) if err != nil { - return fmt.Errorf("during Exec() for record with idx = %d: %w", idx, err) - } - rowsAffected, err := result.RowsAffected() - if err != nil { - return fmt.Errorf("during RowsAffected() for record with idx = %d: %w", idx, err) - } - if rowsAffected == 0 { - return MissingRecordError[R]{r, s.plan} + return newIOError(err, "Stmt.Close", stmt.Close()) } } - return nil + return newIOError(nil, "Stmt.Close", stmt.Close()) +} + +func updateRecord(v reflect.Value, recordIndex int, stmt preparedStatement, argumentIndexes [][]int, argumentSlots []any) (int64, error) { + for idx, index := range argumentIndexes { + argumentSlots[idx] = v.FieldByIndex(index).Interface() + } + result, err := stmt.Exec(argumentSlots...) + if err != nil { + return 0, fmt.Errorf("during Exec() for record with idx = %d: %w", recordIndex, err) + } + rowsAffected, err := result.RowsAffected() + if err != nil { + return 0, fmt.Errorf("during RowsAffected() for record with idx = %d: %w", recordIndex, err) + } + return rowsAffected, nil } // Delete executes an SQL DELETE statement for each of the provided records, using their primary keys to locate the respective table rows. @@ -186,8 +189,9 @@ func (s Store[R]) Delete(db Handle, records ...R) (returnedError error) { // 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") + stmt, err := prepare(db, s.plan.Delete.Query, "Delete", len(records)) + if err != nil { + return err } var ( @@ -195,23 +199,24 @@ func (s Store[R]) Delete(db Handle, records ...R) (returnedError error) { argumentSlots = make([]any, len(argumentIndexes)) ) - stmt, err := prepare(db, s.plan.Delete.Query, len(records)) - if err != nil { - return err - } - defer func() { - returnedError = newIOError(returnedError, "Stmt.Close", 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...) + err := deleteRecord(v, idx, stmt, argumentIndexes, argumentSlots) if err != nil { - return fmt.Errorf("during Exec() for record with idx = %d: %w", idx, err) + return newIOError(err, "Stmt.Close", stmt.Close()) } } + + return newIOError(nil, "Stmt.Close", stmt.Close()) +} + +func deleteRecord(v reflect.Value, recordIndex int, stmt preparedStatement, argumentIndexes [][]int, argumentSlots []any) error { + for idx, index := range argumentIndexes { + argumentSlots[idx] = v.FieldByIndex(index).Interface() + } + _, err := stmt.Exec(argumentSlots...) + if err != nil { + return fmt.Errorf("during Exec() for record with idx = %d: %w", recordIndex, err) + } return nil } -- cgit v1.2.3