mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-03-07 05:58:55 +00:00
feat(sql): implement IN condition handling with parameterized queries
This commit is contained in:
@@ -2,6 +2,7 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -925,3 +926,36 @@ func extractLeftSideOfComparison(cond string) string {
|
|||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilterValueToSlice converts a filter value to []interface{} for use with IN operators.
|
||||||
|
// JSON-decoded arrays arrive as []interface{}, but typed slices (e.g. []string) also work.
|
||||||
|
// Returns a single-element slice if the value is not a slice type.
|
||||||
|
func FilterValueToSlice(v interface{}) []interface{} {
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if rv.Kind() == reflect.Slice {
|
||||||
|
result := make([]interface{}, rv.Len())
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
result[i] = rv.Index(i).Interface()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return []interface{}{v}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildInCondition builds a parameterized IN condition from a filter value.
|
||||||
|
// Returns the condition string (e.g. "col IN (?,?)") and the individual values as args.
|
||||||
|
// Returns ("", nil) if the value is empty or not a slice.
|
||||||
|
func BuildInCondition(column string, v interface{}) (query string, args []interface{}) {
|
||||||
|
values := FilterValueToSlice(v)
|
||||||
|
if len(values) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
placeholders := make([]string, len(values))
|
||||||
|
for i := range values {
|
||||||
|
placeholders[i] = "?"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s IN (%s)", column, strings.Join(placeholders, ",")), values
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ func TestBuildFilterCondition(t *testing.T) {
|
|||||||
Operator: "in",
|
Operator: "in",
|
||||||
Value: []string{"active", "pending"},
|
Value: []string{"active", "pending"},
|
||||||
},
|
},
|
||||||
expectedCondition: "status IN (?)",
|
expectedCondition: "status IN (?,?)",
|
||||||
expectedArgsCount: 1,
|
expectedArgsCount: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "LIKE operator",
|
name: "LIKE operator",
|
||||||
|
|||||||
@@ -1546,8 +1546,10 @@ func (h *Handler) buildFilterCondition(filter common.FilterOption) (conditionStr
|
|||||||
condition = fmt.Sprintf("%s ILIKE ?", filter.Column)
|
condition = fmt.Sprintf("%s ILIKE ?", filter.Column)
|
||||||
args = []interface{}{filter.Value}
|
args = []interface{}{filter.Value}
|
||||||
case "in":
|
case "in":
|
||||||
condition = fmt.Sprintf("%s IN (?)", filter.Column)
|
condition, args = common.BuildInCondition(filter.Column, filter.Value)
|
||||||
args = []interface{}{filter.Value}
|
if condition == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -1588,8 +1590,10 @@ func (h *Handler) applyFilter(query common.SelectQuery, filter common.FilterOpti
|
|||||||
condition = fmt.Sprintf("%s ILIKE ?", filter.Column)
|
condition = fmt.Sprintf("%s ILIKE ?", filter.Column)
|
||||||
args = []interface{}{filter.Value}
|
args = []interface{}{filter.Value}
|
||||||
case "in":
|
case "in":
|
||||||
condition = fmt.Sprintf("%s IN (?)", filter.Column)
|
condition, args = common.BuildInCondition(filter.Column, filter.Value)
|
||||||
args = []interface{}{filter.Value}
|
if condition == "" {
|
||||||
|
return query
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2146,7 +2146,11 @@ func (h *Handler) applyFilter(query common.SelectQuery, filter common.FilterOpti
|
|||||||
// Column is already cast to TEXT if needed
|
// Column is already cast to TEXT if needed
|
||||||
return applyWhere(fmt.Sprintf("%s ILIKE ?", qualifiedColumn), filter.Value)
|
return applyWhere(fmt.Sprintf("%s ILIKE ?", qualifiedColumn), filter.Value)
|
||||||
case "in":
|
case "in":
|
||||||
return applyWhere(fmt.Sprintf("%s IN (?)", qualifiedColumn), filter.Value)
|
cond, inArgs := common.BuildInCondition(qualifiedColumn, filter.Value)
|
||||||
|
if cond == "" {
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
return applyWhere(cond, inArgs...)
|
||||||
case "between":
|
case "between":
|
||||||
// Handle between operator - exclusive (> val1 AND < val2)
|
// Handle between operator - exclusive (> val1 AND < val2)
|
||||||
if values, ok := filter.Value.([]interface{}); ok && len(values) == 2 {
|
if values, ok := filter.Value.([]interface{}); ok && len(values) == 2 {
|
||||||
@@ -2239,7 +2243,8 @@ func (h *Handler) buildFilterCondition(qualifiedColumn string, filter *common.Fi
|
|||||||
case "ilike":
|
case "ilike":
|
||||||
return fmt.Sprintf("%s ILIKE ?", qualifiedColumn), []interface{}{filter.Value}
|
return fmt.Sprintf("%s ILIKE ?", qualifiedColumn), []interface{}{filter.Value}
|
||||||
case "in":
|
case "in":
|
||||||
return fmt.Sprintf("%s IN (?)", qualifiedColumn), []interface{}{filter.Value}
|
cond, inArgs := common.BuildInCondition(qualifiedColumn, filter.Value)
|
||||||
|
return cond, inArgs
|
||||||
case "between":
|
case "between":
|
||||||
// Handle between operator - exclusive (> val1 AND < val2)
|
// Handle between operator - exclusive (> val1 AND < val2)
|
||||||
if values, ok := filter.Value.([]interface{}); ok && len(values) == 2 {
|
if values, ok := filter.Value.([]interface{}); ok && len(values) == 2 {
|
||||||
|
|||||||
@@ -628,7 +628,10 @@ func (h *Handler) readMultiple(hookCtx *HookContext) (data interface{}, metadata
|
|||||||
countQuery := h.db.NewSelect().Model(hookCtx.ModelPtr).Table(hookCtx.TableName)
|
countQuery := h.db.NewSelect().Model(hookCtx.ModelPtr).Table(hookCtx.TableName)
|
||||||
if hookCtx.Options != nil {
|
if hookCtx.Options != nil {
|
||||||
for _, filter := range hookCtx.Options.Filters {
|
for _, filter := range hookCtx.Options.Filters {
|
||||||
countQuery = countQuery.Where(fmt.Sprintf("%s %s ?", filter.Column, h.getOperatorSQL(filter.Operator)), filter.Value)
|
cond, args := h.buildFilterCondition(filter)
|
||||||
|
if cond != "" {
|
||||||
|
countQuery = countQuery.Where(cond, args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count, _ := countQuery.Count(hookCtx.Context)
|
count, _ := countQuery.Count(hookCtx.Context)
|
||||||
@@ -800,14 +803,12 @@ func (h *Handler) applyFilterGroup(query common.SelectQuery, filters []common.Fi
|
|||||||
|
|
||||||
// buildFilterCondition builds a filter condition and returns it with args
|
// buildFilterCondition builds a filter condition and returns it with args
|
||||||
func (h *Handler) buildFilterCondition(filter common.FilterOption) (conditionString string, conditionArgs []interface{}) {
|
func (h *Handler) buildFilterCondition(filter common.FilterOption) (conditionString string, conditionArgs []interface{}) {
|
||||||
var condition string
|
if strings.EqualFold(filter.Operator, "in") {
|
||||||
var args []interface{}
|
cond, args := common.BuildInCondition(filter.Column, filter.Value)
|
||||||
|
return cond, args
|
||||||
|
}
|
||||||
operatorSQL := h.getOperatorSQL(filter.Operator)
|
operatorSQL := h.getOperatorSQL(filter.Operator)
|
||||||
condition = fmt.Sprintf("%s %s ?", filter.Column, operatorSQL)
|
return fmt.Sprintf("%s %s ?", filter.Column, operatorSQL), []interface{}{filter.Value}
|
||||||
args = []interface{}{filter.Value}
|
|
||||||
|
|
||||||
return condition, args
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setRowNumbersOnRecords sets the RowNumber field on each record if it exists
|
// setRowNumbersOnRecords sets the RowNumber field on each record if it exists
|
||||||
|
|||||||
Reference in New Issue
Block a user