first commit
This commit is contained in:
@@ -61,6 +61,8 @@ const (
|
||||
OpArrayContains FilterOperator = "_array_contains"
|
||||
OpArrayNotContains FilterOperator = "_array_ncontains"
|
||||
OpArrayLength FilterOperator = "_array_length"
|
||||
OpArrayOverlap FilterOperator = "_array_overlap"
|
||||
OpArrayContainedBy FilterOperator = "_array_contained_by"
|
||||
)
|
||||
|
||||
// DynamicFilter represents a single filter condition
|
||||
@@ -627,6 +629,19 @@ func (qb *QueryBuilder) buildCTEClause(ctes []CTE) (string, []interface{}, error
|
||||
|
||||
// buildFromClause builds the FROM clause with optional alias
|
||||
func (qb *QueryBuilder) buildFromClause(table, alias string) string {
|
||||
// Check if the table name contains a dot (schema.table)
|
||||
if strings.Contains(table, ".") {
|
||||
parts := strings.Split(table, ".")
|
||||
if len(parts) == 2 {
|
||||
// Quote schema and table separately
|
||||
fromClause := fmt.Sprintf("%s.%s", qb.escapeIdentifier(parts[0]), qb.escapeIdentifier(parts[1]))
|
||||
if alias != "" {
|
||||
fromClause += " " + qb.escapeIdentifier(alias)
|
||||
}
|
||||
return fromClause
|
||||
}
|
||||
}
|
||||
|
||||
fromClause := qb.escapeIdentifier(table)
|
||||
if alias != "" {
|
||||
fromClause += " " + qb.escapeIdentifier(alias)
|
||||
@@ -641,7 +656,18 @@ func (qb *QueryBuilder) buildSingleJoinClause(join Join) (string, string, string
|
||||
joinType = "INNER"
|
||||
}
|
||||
|
||||
table := qb.escapeIdentifier(join.Table)
|
||||
var table string
|
||||
if strings.Contains(join.Table, ".") {
|
||||
parts := strings.Split(join.Table, ".")
|
||||
if len(parts) == 2 {
|
||||
// Quote schema and table separately
|
||||
table = fmt.Sprintf("%s.%s", qb.escapeIdentifier(parts[0]), qb.escapeIdentifier(parts[1]))
|
||||
} else {
|
||||
table = qb.escapeIdentifier(join.Table)
|
||||
}
|
||||
} else {
|
||||
table = qb.escapeIdentifier(join.Table)
|
||||
}
|
||||
if join.Alias != "" {
|
||||
table += " " + qb.escapeIdentifier(join.Alias)
|
||||
}
|
||||
@@ -803,7 +829,7 @@ func (qb *QueryBuilder) buildFilterCondition(filter DynamicFilter) (string, []in
|
||||
switch filter.Operator {
|
||||
case OpJsonContains, OpJsonNotContains, OpJsonExists, OpJsonNotExists, OpJsonEqual, OpJsonNotEqual:
|
||||
return qb.buildJsonFilterCondition(filter)
|
||||
case OpArrayContains, OpArrayNotContains, OpArrayLength:
|
||||
case OpArrayContains, OpArrayNotContains, OpArrayLength, OpArrayOverlap, OpArrayContainedBy:
|
||||
return qb.buildArrayFilterCondition(filter)
|
||||
}
|
||||
|
||||
@@ -1051,6 +1077,45 @@ func (qb *QueryBuilder) buildArrayFilterCondition(filter DynamicFilter) (string,
|
||||
default:
|
||||
return "", nil, fmt.Errorf("Array operations not supported for database type: %s", qb.dbType)
|
||||
}
|
||||
case OpArrayOverlap:
|
||||
// TAMBAHKAN INI
|
||||
switch qb.dbType {
|
||||
case DBTypePostgreSQL:
|
||||
expr = fmt.Sprintf("%s && ?", column)
|
||||
args = append(args, filter.Value)
|
||||
case DBTypeMySQL:
|
||||
// MySQL doesn't have native array overlap, use JSON_OVERLAPS if available
|
||||
expr = fmt.Sprintf("JSON_OVERLAPS(%s, ?)", column)
|
||||
args = append(args, filter.Value)
|
||||
case DBTypeSQLServer:
|
||||
// SQL Server workaround using EXISTS and OPENJSON
|
||||
expr = fmt.Sprintf("EXISTS (SELECT 1 FROM OPENJSON(%s) o1 CROSS JOIN OPENJSON(?) o2 WHERE o1.value = o2.value)", column)
|
||||
args = append(args, filter.Value)
|
||||
case DBTypeSQLite:
|
||||
// SQLite workaround using json_each
|
||||
expr = fmt.Sprintf("EXISTS (SELECT 1 FROM json_each(%s) j1 CROSS JOIN json_each(?) j2 WHERE j1.value = j2.value)", column)
|
||||
args = append(args, filter.Value)
|
||||
default:
|
||||
return "", nil, fmt.Errorf("Array overlap operations not supported for database type: %s", qb.dbType)
|
||||
}
|
||||
case OpArrayContainedBy:
|
||||
// TAMBAHKAN INI
|
||||
switch qb.dbType {
|
||||
case DBTypePostgreSQL:
|
||||
expr = fmt.Sprintf("%s <@ ?", column)
|
||||
args = append(args, filter.Value)
|
||||
case DBTypeMySQL:
|
||||
expr = fmt.Sprintf("JSON_CONTAINS(?, %s)", column)
|
||||
args = append(args, filter.Value)
|
||||
case DBTypeSQLServer:
|
||||
expr = fmt.Sprintf("NOT EXISTS (SELECT 1 FROM OPENJSON(%s) WHERE value NOT IN (SELECT value FROM OPENJSON(?)))", column)
|
||||
args = append(args, filter.Value)
|
||||
case DBTypeSQLite:
|
||||
expr = fmt.Sprintf("NOT EXISTS (SELECT 1 FROM json_each(%s) j1 WHERE j1.value NOT IN (SELECT j2.value FROM json_each(?) j2))", column)
|
||||
args = append(args, filter.Value)
|
||||
default:
|
||||
return "", nil, fmt.Errorf("Array contained_by operations not supported for database type: %s", qb.dbType)
|
||||
}
|
||||
case OpArrayNotContains:
|
||||
switch qb.dbType {
|
||||
case DBTypePostgreSQL:
|
||||
@@ -1114,11 +1179,54 @@ func (qb *QueryBuilder) buildArrayFilterCondition(filter DynamicFilter) (string,
|
||||
// =============================================================================
|
||||
|
||||
func (qb *QueryBuilder) ExecuteQuery(ctx context.Context, db *sqlx.DB, query DynamicQuery, dest interface{}) error {
|
||||
// sql, args, err := qb.BuildQuery(query)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// start := time.Now()
|
||||
// err = db.SelectContext(ctx, dest, sql, args...)
|
||||
// fmt.Printf("[DEBUG] Query executed in %v\n", time.Since(start))
|
||||
// return err
|
||||
sql, args, err := qb.BuildQuery(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
// Check if dest is a pointer to a slice of maps
|
||||
destValue := reflect.ValueOf(dest)
|
||||
if destValue.Kind() != reflect.Ptr || destValue.IsNil() {
|
||||
return fmt.Errorf("dest must be a non-nil pointer")
|
||||
}
|
||||
|
||||
destElem := destValue.Elem()
|
||||
if destElem.Kind() == reflect.Slice {
|
||||
sliceType := destElem.Type().Elem()
|
||||
if sliceType.Kind() == reflect.Map &&
|
||||
sliceType.Key().Kind() == reflect.String &&
|
||||
sliceType.Elem().Kind() == reflect.Interface {
|
||||
|
||||
// Handle slice of map[string]interface{}
|
||||
rows, err := db.QueryxContext(ctx, sql, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
row := make(map[string]interface{})
|
||||
if err := rows.MapScan(row); err != nil {
|
||||
return err
|
||||
}
|
||||
destElem.Set(reflect.Append(destElem, reflect.ValueOf(row)))
|
||||
}
|
||||
|
||||
fmt.Printf("[DEBUG] Query executed in %v\n", time.Since(start))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Default case: use SelectContext
|
||||
err = db.SelectContext(ctx, dest, sql, args...)
|
||||
fmt.Printf("[DEBUG] Query executed in %v\n", time.Since(start))
|
||||
return err
|
||||
@@ -2345,3 +2453,42 @@ func (mqb *MongoQueryBuilder) ExecuteDelete(ctx context.Context, collection *mon
|
||||
fmt.Printf("[DEBUG] MongoDB Delete executed in %v\n", time.Since(start))
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (qb *QueryBuilder) getOperatorSQL(op FilterOperator) (string, error) {
|
||||
switch op {
|
||||
case OpEqual:
|
||||
return "=", nil
|
||||
case OpNotEqual:
|
||||
return "!=", nil
|
||||
case OpLike:
|
||||
return "LIKE", nil
|
||||
case OpILike:
|
||||
return "ILIKE", nil
|
||||
case OpIn:
|
||||
return "IN", nil
|
||||
case OpNotIn:
|
||||
return "NOT IN", nil
|
||||
case OpGreaterThan:
|
||||
return ">", nil
|
||||
case OpGreaterThanEqual:
|
||||
return ">=", nil
|
||||
case OpLessThan:
|
||||
return "<", nil
|
||||
case OpLessThanEqual:
|
||||
return "<=", nil
|
||||
case OpNull:
|
||||
return "IS NULL", nil
|
||||
case OpNotNull:
|
||||
return "IS NOT NULL", nil
|
||||
case OpArrayContains:
|
||||
return "@>", nil
|
||||
case OpArrayNotContains:
|
||||
return "NOT @>", nil
|
||||
case OpArrayOverlap: // TAMBAHKAN INI
|
||||
return "&&", nil
|
||||
case OpArrayContainedBy: // BONUS
|
||||
return "<@", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported operator: %s", op)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user