mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-02-12 03:26:08 +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
|
// Execute query to get row number
|
||||||
var result RowNumResult
|
var result RowNumResult
|
||||||
if err := rowNumQuery.Scan(ctx, &result); err != nil {
|
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)
|
logger.Warn("Error fetching row number: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// When FetchRowNumber is used, we only return 1 record
|
||||||
if options.FetchRowNumber != nil && *options.FetchRowNumber != "" {
|
if options.FetchRowNumber != nil && *options.FetchRowNumber != "" {
|
||||||
count = 1
|
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 {
|
} else {
|
||||||
if options.Limit != nil {
|
if options.Limit != nil {
|
||||||
limit = *options.Limit
|
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
|
// Handle FetchRowNumber before applying ID filter
|
||||||
if id != "" {
|
// 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)
|
pkName := reflection.GetPrimaryKeyName(model)
|
||||||
logger.Debug("Filtering by ID=%s: %s", pkName, id)
|
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
|
// 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{
|
metadata := &common.Metadata{
|
||||||
Total: int64(total),
|
Total: int64(total),
|
||||||
@@ -740,21 +769,10 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
|||||||
Offset: offset,
|
Offset: offset,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch row number for a specific record if requested
|
// If FetchRowNumber was used, also set it in metadata
|
||||||
if options.FetchRowNumber != nil && *options.FetchRowNumber != "" {
|
if fetchedRowNumber != nil {
|
||||||
pkName := reflection.GetPrimaryKeyName(model)
|
metadata.RowNumber = fetchedRowNumber
|
||||||
pkValue := *options.FetchRowNumber
|
logger.Debug("FetchRowNumber: Row number %d set in metadata", *fetchedRowNumber)
|
||||||
|
|
||||||
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
|
// Execute AfterRead hooks
|
||||||
@@ -2651,13 +2669,19 @@ func (h *Handler) FetchRowNumber(ctx context.Context, tableName string, pkName s
|
|||||||
var result []struct {
|
var result []struct {
|
||||||
RN int64 `bun:"rn"`
|
RN int64 `bun:"rn"`
|
||||||
}
|
}
|
||||||
|
logger.Debug("[FetchRowNumber] BEFORE Query call - about to execute raw query")
|
||||||
err := h.db.Query(ctx, &result, queryStr, pkValue)
|
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 {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to fetch row number: %w", err)
|
return 0, fmt.Errorf("failed to fetch row number: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result) == 0 {
|
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
|
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 metadata map[string]interface{}
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if hookCtx.ID != "" {
|
// Check if FetchRowNumber is specified (treat as single record read)
|
||||||
// Read single record by ID
|
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)
|
data, err = h.readByID(hookCtx)
|
||||||
metadata = map[string]interface{}{"total": 1}
|
metadata = map[string]interface{}{"total": 1}
|
||||||
|
// The row number is already set on the record itself via setRowNumbersOnRecords
|
||||||
} else {
|
} else {
|
||||||
// Read multiple records
|
// Read multiple records
|
||||||
data, metadata, err = h.readMultiple(hookCtx)
|
data, metadata, err = h.readMultiple(hookCtx)
|
||||||
@@ -510,10 +514,29 @@ func (h *Handler) notifySubscribers(schema, entity string, operation OperationTy
|
|||||||
// CRUD operation implementations
|
// CRUD operation implementations
|
||||||
|
|
||||||
func (h *Handler) readByID(hookCtx *HookContext) (interface{}, error) {
|
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)
|
query := h.db.NewSelect().Model(hookCtx.ModelPtr).Table(hookCtx.TableName)
|
||||||
|
|
||||||
// Add ID filter
|
// Add ID filter
|
||||||
pkName := reflection.GetPrimaryKeyName(hookCtx.Model)
|
|
||||||
query = query.Where(fmt.Sprintf("%s = ?", pkName), hookCtx.ID)
|
query = query.Where(fmt.Sprintf("%s = ?", pkName), hookCtx.ID)
|
||||||
|
|
||||||
// Apply columns
|
// Apply columns
|
||||||
@@ -533,6 +556,12 @@ func (h *Handler) readByID(hookCtx *HookContext) (interface{}, error) {
|
|||||||
return nil, fmt.Errorf("failed to read record: %w", err)
|
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
|
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
|
// Shutdown gracefully shuts down the handler
|
||||||
func (h *Handler) Shutdown() {
|
func (h *Handler) Shutdown() {
|
||||||
h.connManager.Shutdown()
|
h.connManager.Shutdown()
|
||||||
|
|||||||
Reference in New Issue
Block a user