mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-31 08:44:25 +00:00
Updated logging, added getRowNumber and a few more
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/bitechdev/ResolveSpec/pkg/common"
|
||||
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
||||
)
|
||||
|
||||
// CursorDirection defines pagination direction
|
||||
@@ -85,7 +86,7 @@ func (opts *ExtendedRequestOptions) GetCursorFilter(
|
||||
field, prefix, tableName, modelColumns,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Printf("WARN: Skipping invalid sort column %q: %v\n", col, err)
|
||||
logger.Warn("Skipping invalid sort column %q: %v", col, err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/bitechdev/ResolveSpec/pkg/common"
|
||||
"github.com/bitechdev/ResolveSpec/pkg/common/adapters/database"
|
||||
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
||||
"github.com/bitechdev/ResolveSpec/pkg/reflection"
|
||||
)
|
||||
|
||||
// Handler handles API requests using database and model abstractions
|
||||
@@ -343,10 +343,10 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
||||
logger.Debug("Applying cursor pagination")
|
||||
|
||||
// Get primary key name
|
||||
pkName := database.GetPrimaryKeyName(model)
|
||||
pkName := reflection.GetPrimaryKeyName(model)
|
||||
|
||||
// Extract model columns for validation using the generic database function
|
||||
modelColumns := database.GetModelColumns(model)
|
||||
modelColumns := reflection.GetModelColumns(model)
|
||||
|
||||
// Build expand joins map (if needed in future)
|
||||
var expandJoins map[string]string
|
||||
@@ -371,6 +371,19 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
||||
}
|
||||
}
|
||||
|
||||
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
||||
hookCtx.Query = query
|
||||
if err := h.hooks.Execute(BeforeScan, hookCtx); err != nil {
|
||||
logger.Error("BeforeScan hook failed: %v", err)
|
||||
h.sendError(w, http.StatusBadRequest, "hook_error", "Hook execution failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Use potentially modified query from hook context
|
||||
if modifiedQuery, ok := hookCtx.Query.(common.SelectQuery); ok {
|
||||
query = modifiedQuery
|
||||
}
|
||||
|
||||
// Execute query - modelPtr was already created earlier
|
||||
if err := query.Scan(ctx, modelPtr); err != nil {
|
||||
logger.Error("Error executing query: %v", err)
|
||||
@@ -387,6 +400,9 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
||||
offset = *options.Offset
|
||||
}
|
||||
|
||||
// Set row numbers on each record if the model has a RowNumber field
|
||||
h.setRowNumbersOnRecords(modelPtr, offset)
|
||||
|
||||
metadata := &common.Metadata{
|
||||
Total: int64(total),
|
||||
Count: int64(common.Len(modelPtr)),
|
||||
@@ -395,6 +411,23 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
||||
Offset: offset,
|
||||
}
|
||||
|
||||
// Fetch row number for a specific record if requested
|
||||
if options.RequestOptions.FetchRowNumber != nil && *options.RequestOptions.FetchRowNumber != "" {
|
||||
pkName := reflection.GetPrimaryKeyName(model)
|
||||
pkValue := *options.RequestOptions.FetchRowNumber
|
||||
|
||||
logger.Debug("Fetching row number for specific PK %s = %s", pkName, pkValue)
|
||||
|
||||
rowNum, err := h.FetchRowNumber(ctx, tableName, pkName, pkValue, options, model)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to fetch row number: %v", err)
|
||||
// Don't fail the entire request, just log the warning
|
||||
} else {
|
||||
metadata.RowNumber = &rowNum
|
||||
logger.Debug("Row number for PK %s: %d", pkValue, rowNum)
|
||||
}
|
||||
}
|
||||
|
||||
// Execute AfterRead hooks
|
||||
hookCtx.Result = modelPtr
|
||||
hookCtx.Error = nil
|
||||
@@ -466,6 +499,29 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
||||
}
|
||||
|
||||
query := tx.NewInsert().Model(modelValue).Table(tableName)
|
||||
|
||||
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
||||
batchHookCtx := &HookContext{
|
||||
Context: ctx,
|
||||
Handler: h,
|
||||
Schema: schema,
|
||||
Entity: entity,
|
||||
TableName: tableName,
|
||||
Model: model,
|
||||
Options: options,
|
||||
Data: modelValue,
|
||||
Writer: w,
|
||||
Query: query,
|
||||
}
|
||||
if err := h.hooks.Execute(BeforeScan, batchHookCtx); err != nil {
|
||||
return fmt.Errorf("BeforeScan hook failed: %w", err)
|
||||
}
|
||||
|
||||
// Use potentially modified query from hook context
|
||||
if modifiedQuery, ok := batchHookCtx.Query.(common.InsertQuery); ok {
|
||||
query = modifiedQuery
|
||||
}
|
||||
|
||||
if _, err := query.Exec(ctx); err != nil {
|
||||
return fmt.Errorf("failed to insert record: %w", err)
|
||||
}
|
||||
@@ -508,6 +564,21 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
||||
}
|
||||
|
||||
query := h.db.NewInsert().Model(modelValue).Table(tableName)
|
||||
|
||||
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
||||
hookCtx.Data = modelValue
|
||||
hookCtx.Query = query
|
||||
if err := h.hooks.Execute(BeforeScan, hookCtx); err != nil {
|
||||
logger.Error("BeforeScan hook failed: %v", err)
|
||||
h.sendError(w, http.StatusBadRequest, "hook_error", "Hook execution failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Use potentially modified query from hook context
|
||||
if modifiedQuery, ok := hookCtx.Query.(common.InsertQuery); ok {
|
||||
query = modifiedQuery
|
||||
}
|
||||
|
||||
if _, err := query.Exec(ctx); err != nil {
|
||||
logger.Error("Error creating record: %v", err)
|
||||
h.sendError(w, http.StatusInternalServerError, "create_error", "Error creating record", err)
|
||||
@@ -593,6 +664,19 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id
|
||||
return
|
||||
}
|
||||
|
||||
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
||||
hookCtx.Query = query
|
||||
if err := h.hooks.Execute(BeforeScan, hookCtx); err != nil {
|
||||
logger.Error("BeforeScan hook failed: %v", err)
|
||||
h.sendError(w, http.StatusBadRequest, "hook_error", "Hook execution failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Use potentially modified query from hook context
|
||||
if modifiedQuery, ok := hookCtx.Query.(common.UpdateQuery); ok {
|
||||
query = modifiedQuery
|
||||
}
|
||||
|
||||
result, err := query.Exec(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Error updating record: %v", err)
|
||||
@@ -658,6 +742,19 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
||||
|
||||
query = query.Where("id = ?", id)
|
||||
|
||||
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
||||
hookCtx.Query = query
|
||||
if err := h.hooks.Execute(BeforeScan, hookCtx); err != nil {
|
||||
logger.Error("BeforeScan hook failed: %v", err)
|
||||
h.sendError(w, http.StatusBadRequest, "hook_error", "Hook execution failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Use potentially modified query from hook context
|
||||
if modifiedQuery, ok := hookCtx.Query.(common.DeleteQuery); ok {
|
||||
query = modifiedQuery
|
||||
}
|
||||
|
||||
result, err := query.Exec(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Error deleting record: %v", err)
|
||||
@@ -999,6 +1096,191 @@ func (h *Handler) sendError(w common.ResponseWriter, statusCode int, code, messa
|
||||
w.WriteJSON(response)
|
||||
}
|
||||
|
||||
// FetchRowNumber calculates the row number of a specific record based on sorting and filtering
|
||||
// Returns the 1-based row number of the record with the given primary key value
|
||||
func (h *Handler) FetchRowNumber(ctx context.Context, tableName string, pkName string, pkValue string, options ExtendedRequestOptions, model any) (int64, error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("Panic during FetchRowNumber: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// Build the sort order SQL
|
||||
sortSQL := ""
|
||||
if len(options.Sort) > 0 {
|
||||
sortParts := make([]string, 0, len(options.Sort))
|
||||
for _, sort := range options.Sort {
|
||||
direction := "ASC"
|
||||
if strings.ToLower(sort.Direction) == "desc" {
|
||||
direction = "DESC"
|
||||
}
|
||||
sortParts = append(sortParts, fmt.Sprintf("%s.%s %s", tableName, sort.Column, direction))
|
||||
}
|
||||
sortSQL = strings.Join(sortParts, ", ")
|
||||
} else {
|
||||
// Default sort by primary key
|
||||
sortSQL = fmt.Sprintf("%s.%s ASC", tableName, pkName)
|
||||
}
|
||||
|
||||
// Build WHERE clauses from filters
|
||||
whereClauses := make([]string, 0)
|
||||
for i := range options.Filters {
|
||||
filter := &options.Filters[i]
|
||||
whereClause := h.buildFilterSQL(filter, tableName)
|
||||
if whereClause != "" {
|
||||
whereClauses = append(whereClauses, fmt.Sprintf("(%s)", whereClause))
|
||||
}
|
||||
}
|
||||
|
||||
// Combine WHERE clauses
|
||||
whereSQL := ""
|
||||
if len(whereClauses) > 0 {
|
||||
whereSQL = "WHERE " + strings.Join(whereClauses, " AND ")
|
||||
}
|
||||
|
||||
// Add custom SQL WHERE if provided
|
||||
if options.CustomSQLWhere != "" {
|
||||
if whereSQL == "" {
|
||||
whereSQL = "WHERE " + options.CustomSQLWhere
|
||||
} else {
|
||||
whereSQL += " AND (" + options.CustomSQLWhere + ")"
|
||||
}
|
||||
}
|
||||
|
||||
// Build JOIN clauses from Expand options
|
||||
joinSQL := ""
|
||||
if len(options.Expand) > 0 {
|
||||
joinParts := make([]string, 0, len(options.Expand))
|
||||
for _, expand := range options.Expand {
|
||||
// Note: This is a simplified join - in production you'd need proper FK mapping
|
||||
joinParts = append(joinParts, fmt.Sprintf("LEFT JOIN %s ON %s.%s_id = %s.id",
|
||||
expand.Relation, tableName, expand.Relation, expand.Relation))
|
||||
}
|
||||
joinSQL = strings.Join(joinParts, "\n")
|
||||
}
|
||||
|
||||
// Build the final query with parameterized PK value
|
||||
queryStr := fmt.Sprintf(`
|
||||
SELECT search.rn
|
||||
FROM (
|
||||
SELECT %[1]s.%[2]s,
|
||||
ROW_NUMBER() OVER(ORDER BY %[3]s) AS rn
|
||||
FROM %[1]s
|
||||
%[5]s
|
||||
%[4]s
|
||||
) search
|
||||
WHERE search.%[2]s = ?
|
||||
`,
|
||||
tableName, // [1] - table name
|
||||
pkName, // [2] - primary key column name
|
||||
sortSQL, // [3] - sort order SQL
|
||||
whereSQL, // [4] - WHERE clause
|
||||
joinSQL, // [5] - JOIN clauses
|
||||
)
|
||||
|
||||
logger.Debug("FetchRowNumber query: %s, pkValue: %s", queryStr, pkValue)
|
||||
|
||||
// Execute the raw query with parameterized PK value
|
||||
var result []struct {
|
||||
RN int64 `bun:"rn"`
|
||||
}
|
||||
err := h.db.Query(ctx, &result, queryStr, pkValue)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to fetch row number: %w", err)
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
return 0, fmt.Errorf("no row found for primary key %s", pkValue)
|
||||
}
|
||||
|
||||
return result[0].RN, nil
|
||||
}
|
||||
|
||||
// buildFilterSQL converts a filter to SQL WHERE clause string
|
||||
func (h *Handler) buildFilterSQL(filter *common.FilterOption, tableName string) string {
|
||||
qualifiedColumn := h.qualifyColumnName(filter.Column, tableName)
|
||||
|
||||
switch strings.ToLower(filter.Operator) {
|
||||
case "eq", "equals":
|
||||
return fmt.Sprintf("%s = '%v'", qualifiedColumn, filter.Value)
|
||||
case "neq", "not_equals", "ne":
|
||||
return fmt.Sprintf("%s != '%v'", qualifiedColumn, filter.Value)
|
||||
case "gt", "greater_than":
|
||||
return fmt.Sprintf("%s > '%v'", qualifiedColumn, filter.Value)
|
||||
case "gte", "greater_than_equals", "ge":
|
||||
return fmt.Sprintf("%s >= '%v'", qualifiedColumn, filter.Value)
|
||||
case "lt", "less_than":
|
||||
return fmt.Sprintf("%s < '%v'", qualifiedColumn, filter.Value)
|
||||
case "lte", "less_than_equals", "le":
|
||||
return fmt.Sprintf("%s <= '%v'", qualifiedColumn, filter.Value)
|
||||
case "like":
|
||||
return fmt.Sprintf("%s LIKE '%v'", qualifiedColumn, filter.Value)
|
||||
case "ilike":
|
||||
return fmt.Sprintf("%s ILIKE '%v'", qualifiedColumn, filter.Value)
|
||||
case "in":
|
||||
if values, ok := filter.Value.([]any); ok {
|
||||
valueStrs := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
valueStrs[i] = fmt.Sprintf("'%v'", v)
|
||||
}
|
||||
return fmt.Sprintf("%s IN (%s)", qualifiedColumn, strings.Join(valueStrs, ", "))
|
||||
}
|
||||
return ""
|
||||
case "is_null", "isnull":
|
||||
return fmt.Sprintf("(%s IS NULL OR %s = '')", qualifiedColumn, qualifiedColumn)
|
||||
case "is_not_null", "isnotnull":
|
||||
return fmt.Sprintf("(%s IS NOT NULL AND %s != '')", qualifiedColumn, qualifiedColumn)
|
||||
default:
|
||||
logger.Warn("Unknown filter operator in buildFilterSQL: %s", filter.Operator)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// setRowNumbersOnRecords sets the RowNumber field on each record if it exists
|
||||
// The row number is calculated as offset + index + 1 (1-based)
|
||||
func (h *Handler) setRowNumbersOnRecords(records any, offset int) {
|
||||
// Get the reflect value of the records
|
||||
recordsValue := reflect.ValueOf(records)
|
||||
if recordsValue.Kind() == reflect.Ptr {
|
||||
recordsValue = recordsValue.Elem()
|
||||
}
|
||||
|
||||
// Ensure it's a slice
|
||||
if recordsValue.Kind() != reflect.Slice {
|
||||
logger.Debug("setRowNumbersOnRecords: records is not a slice, skipping")
|
||||
return
|
||||
}
|
||||
|
||||
// Iterate through each record
|
||||
for i := 0; i < recordsValue.Len(); i++ {
|
||||
record := recordsValue.Index(i)
|
||||
|
||||
// Dereference if it's a pointer
|
||||
if record.Kind() == reflect.Ptr {
|
||||
if record.IsNil() {
|
||||
continue
|
||||
}
|
||||
record = record.Elem()
|
||||
}
|
||||
|
||||
// Ensure it's a struct
|
||||
if record.Kind() != reflect.Struct {
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to find and set the RowNumber field
|
||||
rowNumberField := record.FieldByName("RowNumber")
|
||||
if rowNumberField.IsValid() && rowNumberField.CanSet() {
|
||||
// Check if the field is of type int64
|
||||
if rowNumberField.Kind() == reflect.Int64 {
|
||||
rowNum := int64(offset + i + 1)
|
||||
rowNumberField.SetInt(rowNum)
|
||||
logger.Debug("Set RowNumber=%d on record %d", rowNum, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filterExtendedOptions filters all column references, removing invalid ones and logging warnings
|
||||
func filterExtendedOptions(validator *common.ColumnValidator, options ExtendedRequestOptions) ExtendedRequestOptions {
|
||||
filtered := options
|
||||
|
||||
@@ -27,6 +27,9 @@ const (
|
||||
// Delete operation hooks
|
||||
BeforeDelete HookType = "before_delete"
|
||||
AfterDelete HookType = "after_delete"
|
||||
|
||||
// Scan/Execute operation hooks
|
||||
BeforeScan HookType = "before_scan"
|
||||
)
|
||||
|
||||
// HookContext contains all the data available to a hook
|
||||
@@ -46,6 +49,10 @@ type HookContext struct {
|
||||
Error error // For after hooks
|
||||
QueryFilter string // For read operations
|
||||
|
||||
// Query chain - allows hooks to modify the query before execution
|
||||
// Can be SelectQuery, InsertQuery, UpdateQuery, or DeleteQuery
|
||||
Query interface{}
|
||||
|
||||
// Response writer - allows hooks to modify response
|
||||
Writer common.ResponseWriter
|
||||
}
|
||||
|
||||
203
pkg/restheadspec/rownumber_test.go
Normal file
203
pkg/restheadspec/rownumber_test.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package restheadspec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestModel represents a typical model with RowNumber field (like DBAdhocBuffer)
|
||||
type TestModel struct {
|
||||
ID int64 `json:"id" bun:"id,pk"`
|
||||
Name string `json:"name" bun:"name"`
|
||||
RowNumber int64 `json:"_rownumber,omitempty" gorm:"-" bun:"-"`
|
||||
}
|
||||
|
||||
func TestSetRowNumbersOnRecords(t *testing.T) {
|
||||
handler := &Handler{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
records any
|
||||
offset int
|
||||
expected []int64
|
||||
}{
|
||||
{
|
||||
name: "Set row numbers on slice of pointers",
|
||||
records: []*TestModel{
|
||||
{ID: 1, Name: "First"},
|
||||
{ID: 2, Name: "Second"},
|
||||
{ID: 3, Name: "Third"},
|
||||
},
|
||||
offset: 0,
|
||||
expected: []int64{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "Set row numbers with offset",
|
||||
records: []*TestModel{
|
||||
{ID: 11, Name: "Eleventh"},
|
||||
{ID: 12, Name: "Twelfth"},
|
||||
},
|
||||
offset: 10,
|
||||
expected: []int64{11, 12},
|
||||
},
|
||||
{
|
||||
name: "Set row numbers on slice of values",
|
||||
records: []TestModel{
|
||||
{ID: 1, Name: "First"},
|
||||
{ID: 2, Name: "Second"},
|
||||
},
|
||||
offset: 5,
|
||||
expected: []int64{6, 7},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
handler.setRowNumbersOnRecords(tt.records, tt.offset)
|
||||
|
||||
// Verify row numbers were set correctly
|
||||
switch records := tt.records.(type) {
|
||||
case []*TestModel:
|
||||
assert.Equal(t, len(tt.expected), len(records))
|
||||
for i, record := range records {
|
||||
assert.Equal(t, tt.expected[i], record.RowNumber,
|
||||
"Record %d should have RowNumber=%d", i, tt.expected[i])
|
||||
}
|
||||
case []TestModel:
|
||||
assert.Equal(t, len(tt.expected), len(records))
|
||||
for i, record := range records {
|
||||
assert.Equal(t, tt.expected[i], record.RowNumber,
|
||||
"Record %d should have RowNumber=%d", i, tt.expected[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetRowNumbersOnRecords_NoRowNumberField(t *testing.T) {
|
||||
handler := &Handler{}
|
||||
|
||||
// Model without RowNumber field
|
||||
type SimpleModel struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
records := []*SimpleModel{
|
||||
{ID: 1, Name: "First"},
|
||||
{ID: 2, Name: "Second"},
|
||||
}
|
||||
|
||||
// Should not panic when model doesn't have RowNumber field
|
||||
assert.NotPanics(t, func() {
|
||||
handler.setRowNumbersOnRecords(records, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetRowNumbersOnRecords_NilRecords(t *testing.T) {
|
||||
handler := &Handler{}
|
||||
|
||||
records := []*TestModel{
|
||||
{ID: 1, Name: "First"},
|
||||
nil, // Nil record
|
||||
{ID: 3, Name: "Third"},
|
||||
}
|
||||
|
||||
// Should not panic with nil records
|
||||
assert.NotPanics(t, func() {
|
||||
handler.setRowNumbersOnRecords(records, 0)
|
||||
})
|
||||
|
||||
// Verify non-nil records were set
|
||||
assert.Equal(t, int64(1), records[0].RowNumber)
|
||||
assert.Equal(t, int64(3), records[2].RowNumber)
|
||||
}
|
||||
|
||||
// DBAdhocBuffer simulates the actual DBAdhocBuffer from db package
|
||||
type DBAdhocBuffer struct {
|
||||
CQL1 string `json:"cql1,omitempty" gorm:"->" bun:"-"`
|
||||
RowNumber int64 `json:"_rownumber,omitempty" gorm:"-" bun:"-"`
|
||||
}
|
||||
|
||||
// ModelWithEmbeddedBuffer simulates a real model like ModelPublicConsultant
|
||||
type ModelWithEmbeddedBuffer struct {
|
||||
ID int64 `json:"id" bun:"id,pk"`
|
||||
Name string `json:"name" bun:"name"`
|
||||
|
||||
DBAdhocBuffer `json:",omitempty"` // Embedded struct containing RowNumber
|
||||
}
|
||||
|
||||
func TestSetRowNumbersOnRecords_EmbeddedBuffer(t *testing.T) {
|
||||
handler := &Handler{}
|
||||
|
||||
// Test with embedded DBAdhocBuffer (like real models)
|
||||
records := []*ModelWithEmbeddedBuffer{
|
||||
{ID: 1, Name: "First"},
|
||||
{ID: 2, Name: "Second"},
|
||||
{ID: 3, Name: "Third"},
|
||||
}
|
||||
|
||||
handler.setRowNumbersOnRecords(records, 10)
|
||||
|
||||
// Verify row numbers were set on embedded field
|
||||
assert.Equal(t, int64(11), records[0].RowNumber, "First record should have RowNumber=11")
|
||||
assert.Equal(t, int64(12), records[1].RowNumber, "Second record should have RowNumber=12")
|
||||
assert.Equal(t, int64(13), records[2].RowNumber, "Third record should have RowNumber=13")
|
||||
}
|
||||
|
||||
func TestSetRowNumbersOnRecords_EmbeddedBuffer_SliceOfValues(t *testing.T) {
|
||||
handler := &Handler{}
|
||||
|
||||
// Test with slice of values (not pointers)
|
||||
records := []ModelWithEmbeddedBuffer{
|
||||
{ID: 1, Name: "First"},
|
||||
{ID: 2, Name: "Second"},
|
||||
}
|
||||
|
||||
handler.setRowNumbersOnRecords(records, 0)
|
||||
|
||||
// Verify row numbers were set on embedded field
|
||||
assert.Equal(t, int64(1), records[0].RowNumber, "First record should have RowNumber=1")
|
||||
assert.Equal(t, int64(2), records[1].RowNumber, "Second record should have RowNumber=2")
|
||||
}
|
||||
|
||||
// Simulate the exact structure from user's code
|
||||
type MockDBAdhocBuffer struct {
|
||||
CQL1 string `json:"cql1,omitempty" gorm:"->" bun:"-"`
|
||||
CQL2 string `json:"cql2,omitempty" gorm:"->" bun:"-"`
|
||||
RowNumber int64 `json:"_rownumber,omitempty" gorm:"-" bun:"-"`
|
||||
Request string `json:"_request,omitempty" gorm:"-" bun:"-"`
|
||||
}
|
||||
|
||||
// Exact structure like ModelPublicConsultant
|
||||
type ModelPublicConsultant struct {
|
||||
Consultant string `json:"consultant" bun:"consultant,type:citext,pk"`
|
||||
Ridconsultant int32 `json:"rid_consultant" bun:"rid_consultant,type:integer,pk"`
|
||||
Updatecnt int64 `json:"updatecnt" bun:"updatecnt,type:integer,default:0"`
|
||||
|
||||
MockDBAdhocBuffer `json:",omitempty"` // Embedded - RowNumber is here!
|
||||
}
|
||||
|
||||
func TestSetRowNumbersOnRecords_RealModelStructure(t *testing.T) {
|
||||
handler := &Handler{}
|
||||
|
||||
// Test with exact structure from user's ModelPublicConsultant
|
||||
records := []*ModelPublicConsultant{
|
||||
{Consultant: "John Doe", Ridconsultant: 1, Updatecnt: 0},
|
||||
{Consultant: "Jane Smith", Ridconsultant: 2, Updatecnt: 0},
|
||||
{Consultant: "Bob Johnson", Ridconsultant: 3, Updatecnt: 0},
|
||||
}
|
||||
|
||||
handler.setRowNumbersOnRecords(records, 100)
|
||||
|
||||
// Verify row numbers were set correctly in the embedded DBAdhocBuffer
|
||||
assert.Equal(t, int64(101), records[0].RowNumber, "First consultant should have RowNumber=101")
|
||||
assert.Equal(t, int64(102), records[1].RowNumber, "Second consultant should have RowNumber=102")
|
||||
assert.Equal(t, int64(103), records[2].RowNumber, "Third consultant should have RowNumber=103")
|
||||
|
||||
t.Logf("✓ RowNumber correctly set in embedded MockDBAdhocBuffer")
|
||||
t.Logf(" Record 0: Consultant=%s, RowNumber=%d", records[0].Consultant, records[0].RowNumber)
|
||||
t.Logf(" Record 1: Consultant=%s, RowNumber=%d", records[1].Consultant, records[1].RowNumber)
|
||||
t.Logf(" Record 2: Consultant=%s, RowNumber=%d", records[2].Consultant, records[2].RowNumber)
|
||||
}
|
||||
Reference in New Issue
Block a user