mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-02-11 19:16:07 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd77385dd6 |
@@ -433,7 +433,18 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
||||
// Execute query to get row number
|
||||
var result RowNumResult
|
||||
if err := rowNumQuery.Scan(ctx, &result); err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
if err == sql.ErrNoRows {
|
||||
// Build filter description for error message
|
||||
filterInfo := fmt.Sprintf("filters: %d", len(options.Filters))
|
||||
if len(options.CustomOperators) > 0 {
|
||||
customOps := make([]string, 0, len(options.CustomOperators))
|
||||
for _, op := range options.CustomOperators {
|
||||
customOps = append(customOps, op.SQL)
|
||||
}
|
||||
filterInfo += fmt.Sprintf(", custom operators: [%s]", strings.Join(customOps, "; "))
|
||||
}
|
||||
logger.Warn("No row found for primary key %s=%s with %s", pkName, *options.FetchRowNumber, filterInfo)
|
||||
} else {
|
||||
logger.Warn("Error fetching row number: %v", err)
|
||||
}
|
||||
} else {
|
||||
@@ -499,7 +510,11 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
||||
// When FetchRowNumber is used, we only return 1 record
|
||||
if options.FetchRowNumber != nil && *options.FetchRowNumber != "" {
|
||||
count = 1
|
||||
// Don't use limit/offset when fetching specific record
|
||||
// Set the fetched row number on the record
|
||||
if rowNumber != nil {
|
||||
logger.Debug("FetchRowNumber: Setting row number %d on record", *rowNumber)
|
||||
h.setRowNumbersOnRecords(result, int(*rowNumber-1)) // -1 because setRowNumbersOnRecords adds 1
|
||||
}
|
||||
} else {
|
||||
if options.Limit != nil {
|
||||
limit = *options.Limit
|
||||
|
||||
@@ -549,8 +549,30 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
||||
}
|
||||
}
|
||||
|
||||
// If ID is provided, filter by ID
|
||||
if id != "" {
|
||||
// Handle FetchRowNumber before applying ID filter
|
||||
// This must happen before the query to get the row position, then filter by PK
|
||||
var fetchedRowNumber *int64
|
||||
var fetchRowNumberPKValue string
|
||||
if options.FetchRowNumber != nil && *options.FetchRowNumber != "" {
|
||||
pkName := reflection.GetPrimaryKeyName(model)
|
||||
fetchRowNumberPKValue = *options.FetchRowNumber
|
||||
|
||||
logger.Debug("FetchRowNumber: Fetching row number for PK %s = %s", pkName, fetchRowNumberPKValue)
|
||||
|
||||
rowNum, err := h.FetchRowNumber(ctx, tableName, pkName, fetchRowNumberPKValue, options, model)
|
||||
if err != nil {
|
||||
logger.Error("Failed to fetch row number: %v", err)
|
||||
h.sendError(w, http.StatusBadRequest, "fetch_rownumber_error", "Failed to fetch row number", err)
|
||||
return
|
||||
}
|
||||
|
||||
fetchedRowNumber = &rowNum
|
||||
logger.Debug("FetchRowNumber: Row number %d for PK %s = %s", rowNum, pkName, fetchRowNumberPKValue)
|
||||
|
||||
// Now filter the main query to this specific primary key
|
||||
query = query.Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), fetchRowNumberPKValue)
|
||||
} else if id != "" {
|
||||
// If ID is provided (and not FetchRowNumber), filter by ID
|
||||
pkName := reflection.GetPrimaryKeyName(model)
|
||||
logger.Debug("Filtering by ID=%s: %s", pkName, id)
|
||||
|
||||
@@ -730,7 +752,14 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
||||
}
|
||||
|
||||
// Set row numbers on each record if the model has a RowNumber field
|
||||
h.setRowNumbersOnRecords(modelPtr, offset)
|
||||
// If FetchRowNumber was used, set the fetched row number instead of offset-based
|
||||
if fetchedRowNumber != nil {
|
||||
// FetchRowNumber: set the actual row position on the record
|
||||
logger.Debug("FetchRowNumber: Setting row number %d on record", *fetchedRowNumber)
|
||||
h.setRowNumbersOnRecords(modelPtr, int(*fetchedRowNumber-1)) // -1 because setRowNumbersOnRecords adds 1
|
||||
} else {
|
||||
h.setRowNumbersOnRecords(modelPtr, offset)
|
||||
}
|
||||
|
||||
metadata := &common.Metadata{
|
||||
Total: int64(total),
|
||||
@@ -740,21 +769,10 @@ 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.FetchRowNumber != nil && *options.FetchRowNumber != "" {
|
||||
pkName := reflection.GetPrimaryKeyName(model)
|
||||
pkValue := *options.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)
|
||||
}
|
||||
// If FetchRowNumber was used, also set it in metadata
|
||||
if fetchedRowNumber != nil {
|
||||
metadata.RowNumber = fetchedRowNumber
|
||||
logger.Debug("FetchRowNumber: Row number %d set in metadata", *fetchedRowNumber)
|
||||
}
|
||||
|
||||
// Execute AfterRead hooks
|
||||
@@ -2651,13 +2669,19 @@ func (h *Handler) FetchRowNumber(ctx context.Context, tableName string, pkName s
|
||||
var result []struct {
|
||||
RN int64 `bun:"rn"`
|
||||
}
|
||||
logger.Debug("[FetchRowNumber] BEFORE Query call - about to execute raw query")
|
||||
err := h.db.Query(ctx, &result, queryStr, pkValue)
|
||||
logger.Debug("[FetchRowNumber] AFTER Query call - query completed with %d results, err: %v", len(result), err)
|
||||
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)
|
||||
whereInfo := "none"
|
||||
if whereSQL != "" {
|
||||
whereInfo = whereSQL
|
||||
}
|
||||
return 0, fmt.Errorf("no row found for primary key %s=%s with active filters: %s", pkName, pkValue, whereInfo)
|
||||
}
|
||||
|
||||
return result[0].RN, nil
|
||||
|
||||
@@ -210,10 +210,14 @@ func (h *Handler) handleRead(conn *Connection, msg *Message, hookCtx *HookContex
|
||||
var metadata map[string]interface{}
|
||||
var err error
|
||||
|
||||
if hookCtx.ID != "" {
|
||||
// Read single record by ID
|
||||
// Check if FetchRowNumber is specified (treat as single record read)
|
||||
isFetchRowNumber := hookCtx.Options != nil && hookCtx.Options.FetchRowNumber != nil && *hookCtx.Options.FetchRowNumber != ""
|
||||
|
||||
if hookCtx.ID != "" || isFetchRowNumber {
|
||||
// Read single record by ID or FetchRowNumber
|
||||
data, err = h.readByID(hookCtx)
|
||||
metadata = map[string]interface{}{"total": 1}
|
||||
// The row number is already set on the record itself via setRowNumbersOnRecords
|
||||
} else {
|
||||
// Read multiple records
|
||||
data, metadata, err = h.readMultiple(hookCtx)
|
||||
@@ -510,10 +514,29 @@ func (h *Handler) notifySubscribers(schema, entity string, operation OperationTy
|
||||
// CRUD operation implementations
|
||||
|
||||
func (h *Handler) readByID(hookCtx *HookContext) (interface{}, error) {
|
||||
// Handle FetchRowNumber before building query
|
||||
var fetchedRowNumber *int64
|
||||
pkName := reflection.GetPrimaryKeyName(hookCtx.Model)
|
||||
|
||||
if hookCtx.Options != nil && hookCtx.Options.FetchRowNumber != nil && *hookCtx.Options.FetchRowNumber != "" {
|
||||
fetchRowNumberPKValue := *hookCtx.Options.FetchRowNumber
|
||||
logger.Debug("[WebSocketSpec] FetchRowNumber: Fetching row number for PK %s = %s", pkName, fetchRowNumberPKValue)
|
||||
|
||||
rowNum, err := h.FetchRowNumber(hookCtx.Context, hookCtx.TableName, pkName, fetchRowNumberPKValue, hookCtx.Options, hookCtx.Model)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch row number: %w", err)
|
||||
}
|
||||
|
||||
fetchedRowNumber = &rowNum
|
||||
logger.Debug("[WebSocketSpec] FetchRowNumber: Row number %d for PK %s = %s", rowNum, pkName, fetchRowNumberPKValue)
|
||||
|
||||
// Override ID with FetchRowNumber value
|
||||
hookCtx.ID = fetchRowNumberPKValue
|
||||
}
|
||||
|
||||
query := h.db.NewSelect().Model(hookCtx.ModelPtr).Table(hookCtx.TableName)
|
||||
|
||||
// Add ID filter
|
||||
pkName := reflection.GetPrimaryKeyName(hookCtx.Model)
|
||||
query = query.Where(fmt.Sprintf("%s = ?", pkName), hookCtx.ID)
|
||||
|
||||
// Apply columns
|
||||
@@ -533,6 +556,12 @@ func (h *Handler) readByID(hookCtx *HookContext) (interface{}, error) {
|
||||
return nil, fmt.Errorf("failed to read record: %w", err)
|
||||
}
|
||||
|
||||
// Set the fetched row number on the record if FetchRowNumber was used
|
||||
if fetchedRowNumber != nil {
|
||||
logger.Debug("[WebSocketSpec] FetchRowNumber: Setting row number %d on record", *fetchedRowNumber)
|
||||
h.setRowNumbersOnRecords(hookCtx.ModelPtr, int(*fetchedRowNumber-1)) // -1 because setRowNumbersOnRecords adds 1
|
||||
}
|
||||
|
||||
return hookCtx.ModelPtr, nil
|
||||
}
|
||||
|
||||
@@ -841,6 +870,92 @@ func (h *Handler) getOperatorSQL(operator string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 *common.RequestOptions, model interface{}) (int64, error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("[WebSocketSpec] Panic during FetchRowNumber: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// Build the sort order SQL
|
||||
sortSQL := ""
|
||||
if options != nil && len(options.Sort) > 0 {
|
||||
sortParts := make([]string, 0, len(options.Sort))
|
||||
for _, sort := range options.Sort {
|
||||
if sort.Column == "" {
|
||||
continue
|
||||
}
|
||||
direction := "ASC"
|
||||
if strings.EqualFold(sort.Direction, "desc") {
|
||||
direction = "DESC"
|
||||
}
|
||||
sortParts = append(sortParts, fmt.Sprintf("%s %s", sort.Column, direction))
|
||||
}
|
||||
sortSQL = strings.Join(sortParts, ", ")
|
||||
} else {
|
||||
// Default sort by primary key
|
||||
sortSQL = fmt.Sprintf("%s ASC", pkName)
|
||||
}
|
||||
|
||||
// Build WHERE clause from filters
|
||||
whereSQL := ""
|
||||
var whereArgs []interface{}
|
||||
if options != nil && len(options.Filters) > 0 {
|
||||
var conditions []string
|
||||
for _, filter := range options.Filters {
|
||||
operatorSQL := h.getOperatorSQL(filter.Operator)
|
||||
conditions = append(conditions, fmt.Sprintf("%s.%s %s ?", tableName, filter.Column, operatorSQL))
|
||||
whereArgs = append(whereArgs, filter.Value)
|
||||
}
|
||||
if len(conditions) > 0 {
|
||||
whereSQL = "WHERE " + strings.Join(conditions, " AND ")
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
%[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
|
||||
)
|
||||
|
||||
logger.Debug("[WebSocketSpec] FetchRowNumber query: %s, pkValue: %s", queryStr, pkValue)
|
||||
|
||||
// Append PK value to whereArgs
|
||||
whereArgs = append(whereArgs, pkValue)
|
||||
|
||||
// Execute the raw query with parameterized PK value
|
||||
var result []struct {
|
||||
RN int64 `bun:"rn"`
|
||||
}
|
||||
err := h.db.Query(ctx, &result, queryStr, whereArgs...)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to fetch row number: %w", err)
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
whereInfo := "none"
|
||||
if whereSQL != "" {
|
||||
whereInfo = whereSQL
|
||||
}
|
||||
return 0, fmt.Errorf("no row found for primary key %s=%s with active filters: %s", pkName, pkValue, whereInfo)
|
||||
}
|
||||
|
||||
return result[0].RN, nil
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the handler
|
||||
func (h *Handler) Shutdown() {
|
||||
h.connManager.Shutdown()
|
||||
|
||||
Reference in New Issue
Block a user