Skip to content

Commit

Permalink
feat: add is empty, is not empty, starts with, ends with filters oper…
Browse files Browse the repository at this point in the history
…ators
  • Loading branch information
ChibiBlasphem committed Jan 16, 2025
1 parent 9f2e035 commit d92d4b2
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 26 deletions.
10 changes: 10 additions & 0 deletions models/ast/ast_filter.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ast

import "slices"

type FilterOperator string

const (
Expand All @@ -11,9 +13,17 @@ const (
FILTER_LESSER_OR_EQUAL FilterOperator = "<="
FILTER_IS_IN_LIST FilterOperator = "IsInList"
FILTER_IS_NOT_IN_LIST FilterOperator = "IsNotInList"
FILTER_IS_EMPTY FilterOperator = "IsEmpty"
FILTER_IS_NOT_EMPTY FilterOperator = "IsNotEmpty"
FILTER_STARTS_WITH FilterOperator = "StringStartsWith"
FILTER_ENDS_WITH FilterOperator = "StringEndsWith"
FILTER_UNKNOWN_OPERATION FilterOperator = "FILTER_UNKNOWN_OPERATION"
)

func (op FilterOperator) IsUnary() bool {
return slices.Contains([]FilterOperator{FILTER_IS_EMPTY, FILTER_IS_NOT_EMPTY}, op)
}

type Filter struct {
TableName string
FieldName string
Expand Down
41 changes: 36 additions & 5 deletions repositories/ingested_data_read_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import (
"github.com/checkmarble/marble-backend/models/ast"
)

// Define where to put that
type FilterWithType struct {
Filter ast.Filter
FieldType models.DataType
}

type IngestedDataReadRepository interface {
GetDbField(ctx context.Context, exec Executor, readParams models.DbFieldReadParams) (any, error)
ListAllObjectIdsFromTable(
Expand All @@ -33,7 +39,7 @@ type IngestedDataReadRepository interface {
fieldName string,
fieldType models.DataType,
aggregator ast.Aggregator,
filters []ast.Filter,
filters []FilterWithType,
) (any, error)
}

Expand Down Expand Up @@ -314,7 +320,7 @@ func createQueryAggregated(
fieldName string,
fieldType models.DataType,
aggregator ast.Aggregator,
filters []ast.Filter,
filters []FilterWithType,
) (squirrel.SelectBuilder, error) {
var selectExpression string
if aggregator == ast.AGGREGATOR_COUNT_DISTINCT {
Expand All @@ -339,7 +345,8 @@ func createQueryAggregated(

var err error
for _, filter := range filters {
query, err = addConditionForOperator(query, qualifiedTableName, filter.FieldName, filter.Operator, filter.Value)
query, err = addConditionForOperator(query, qualifiedTableName,
filter.Filter.FieldName, filter.FieldType, filter.Filter.Operator, filter.Filter.Value)
if err != nil {
return squirrel.SelectBuilder{}, err
}
Expand All @@ -354,7 +361,7 @@ func (repo *IngestedDataReadRepositoryImpl) QueryAggregatedValue(
fieldName string,
fieldType models.DataType,
aggregator ast.Aggregator,
filters []ast.Filter,
filters []FilterWithType,
) (any, error) {
if err := validateClientDbExecutor(exec); err != nil {
return nil, err
Expand All @@ -377,7 +384,7 @@ func (repo *IngestedDataReadRepositoryImpl) QueryAggregatedValue(
return result, nil
}

func addConditionForOperator(query squirrel.SelectBuilder, tableName string, fieldName string,
func addConditionForOperator(query squirrel.SelectBuilder, tableName string, fieldName string, fieldType models.DataType,
operator ast.FilterOperator, value any,
) (squirrel.SelectBuilder, error) {
switch operator {
Expand All @@ -393,6 +400,30 @@ func addConditionForOperator(query squirrel.SelectBuilder, tableName string, fie
return query.Where(squirrel.Lt{fmt.Sprintf("%s.%s", tableName, fieldName): value}), nil
case ast.FILTER_LESSER_OR_EQUAL:
return query.Where(squirrel.LtOrEq{fmt.Sprintf("%s.%s", tableName, fieldName): value}), nil
case ast.FILTER_IS_EMPTY:
orCondition := squirrel.Or{
squirrel.Eq{fmt.Sprintf("%s.%s", tableName, fieldName): nil},
}
if fieldType == models.String {
orCondition = append(orCondition, squirrel.Eq{
fmt.Sprintf("%s.%s", tableName, fieldName): "",
})
}
return query.Where(orCondition), nil
case ast.FILTER_IS_NOT_EMPTY:
andCondition := squirrel.And{
squirrel.NotEq{fmt.Sprintf("%s.%s", tableName, fieldName): nil},
}
if fieldType == models.String {
andCondition = append(andCondition,
squirrel.NotEq{fmt.Sprintf("%s.%s", tableName, fieldName): ""},
)
}
return query.Where(andCondition), nil
case ast.FILTER_STARTS_WITH:
return query.Where(squirrel.Like{fmt.Sprintf("%s.%s", tableName, fieldName): fmt.Sprintf("%s%%", value)}), nil
case ast.FILTER_ENDS_WITH:
return query.Where(squirrel.Like{fmt.Sprintf("%s.%s", tableName, fieldName): fmt.Sprintf("%%%s", value)}), nil
default:
return query, fmt.Errorf("unknown operator %s: %w", operator, models.BadParameterError)
}
Expand Down
26 changes: 21 additions & 5 deletions usecases/ast_eval/evaluate/evaluate_aggregator.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func (a AggregatorEvaluator) Evaluate(ctx context.Context, arguments ast.Argumen
}

// Filters validation
var filtersWithType []repositories.FilterWithType
if len(filters) > 0 {
for _, filter := range filters {
if filter.TableName != tableName {
Expand All @@ -82,14 +83,28 @@ func (a AggregatorEvaluator) Evaluate(ctx context.Context, arguments ast.Argumen
ast.NewNamedArgumentError("filters"),
))
}
// At the first nil filter value found, stop and just return the default value for the aggregator
if filter.Value == nil {

// At the first nil filter value found if we're not on an unary operator, stop and just return the default value for the aggregator
if filter.Value == nil && !filter.Operator.IsUnary() {
return a.defaultValueForAggregator(aggregator)
}

filterFieldType, err := getFieldType(a.DataModel, filter.TableName, filter.FieldName)
if err != nil {
return MakeEvaluateError(errors.Join(
errors.Wrap(err, fmt.Sprintf("field type for %s.%s not found in data model in Evaluate aggregator", filter.TableName, filter.FieldName)),
ast.NewNamedArgumentError("fieldName"),
))
}

filtersWithType = append(filtersWithType, repositories.FilterWithType{
Filter: filter,
FieldType: filterFieldType,
})
}
}

result, err := a.runQueryInRepository(ctx, tableName, fieldName, fieldType, aggregator, filters)
result, err := a.runQueryInRepository(ctx, tableName, fieldName, fieldType, aggregator, filtersWithType)
if err != nil {
return MakeEvaluateError(errors.Wrap(err, "Error running aggregation query in repository"))
}
Expand All @@ -107,7 +122,7 @@ func (a AggregatorEvaluator) runQueryInRepository(
fieldName string,
fieldType models.DataType,
aggregator ast.Aggregator,
filters []ast.Filter,
filters []repositories.FilterWithType,
) (any, error) {
if a.ReturnFakeValue {
return DryRunQueryAggregatedValue(a.DataModel, tableName, fieldName, aggregator)
Expand All @@ -117,7 +132,8 @@ func (a AggregatorEvaluator) runQueryInRepository(
if err != nil {
return nil, err
}
return a.IngestedDataReadRepository.QueryAggregatedValue(ctx, db, tableName, fieldName, fieldType, aggregator, filters)
return a.IngestedDataReadRepository.QueryAggregatedValue(ctx, db, tableName,
fieldName, fieldType, aggregator, filters)
}

func (a AggregatorEvaluator) defaultValueForAggregator(aggregator ast.Aggregator) (any, []error) {
Expand Down
4 changes: 4 additions & 0 deletions usecases/ast_eval/evaluate/evaluate_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ var validTypeForFilterOperators = map[ast.FilterOperator][]models.DataType{
ast.FILTER_LESSER_OR_EQUAL: {models.Int, models.Float, models.String, models.Timestamp},
ast.FILTER_IS_IN_LIST: {models.String},
ast.FILTER_IS_NOT_IN_LIST: {models.String},
ast.FILTER_IS_EMPTY: {models.Int, models.Float, models.String, models.Timestamp},
ast.FILTER_IS_NOT_EMPTY: {models.Int, models.Float, models.String, models.Timestamp},
ast.FILTER_STARTS_WITH: {models.String},
ast.FILTER_ENDS_WITH: {models.String},
}

func (f FilterEvaluator) Evaluate(ctx context.Context, arguments ast.Arguments) (any, []error) {
Expand Down
Loading

0 comments on commit d92d4b2

Please sign in to comment.