diff --git a/pkg/api/aim/query/query.go b/pkg/api/aim/query/query.go index 39577e42f..608d8985e 100644 --- a/pkg/api/aim/query/query.go +++ b/pkg/api/aim/query/query.go @@ -248,46 +248,58 @@ func (pq *parsedQuery) parseAttribute(node *ast.Attribute) (any, error) { case "endswith": return callable(func(args []ast.Expr) (any, error) { if len(args) != 1 { - return nil, errors.New("`endwith` function support exactly one argument") + return nil, errors.New("`endswith` function support exactly one argument") } - c, ok := parsedNode.(clause.Column) - if !ok { - return nil, errors.New("unsupported node type. has to be clause.Column") - } - arg, ok := args[0].(*ast.Str) if !ok { return nil, errors.New("unsupported argument type. has to be `string` only") } - return clause.Like{ - Value: fmt.Sprintf("%%%s", arg.S), - Column: clause.Column{ - Table: c.Table, - Name: c.Name, - }, - }, nil + value := fmt.Sprintf("%%%s", arg.S) + switch c := parsedNode.(type) { + case clause.Column: + return clause.Like{ + Value: value, + Column: clause.Column{ + Table: c.Table, + Name: c.Name, + }, + }, nil + case Json: + return JsonLike{ + Value: value, + Json: c, + }, nil + default: + return nil, errors.New("unsupported node type. has to be clause.Column or Json") + } }), nil case "startswith": return callable(func(args []ast.Expr) (any, error) { if len(args) != 1 { - return nil, errors.New("`startwith` function support exactly one argument") + return nil, errors.New("`startswith` function support exactly one argument") } - c, ok := parsedNode.(clause.Column) - if !ok { - return nil, errors.New("unsupported node type. has to be clause.Column") - } - arg, ok := args[0].(*ast.Str) if !ok { return nil, errors.New("unsupported argument type. has to be `string` only") } - return clause.Like{ - Value: fmt.Sprintf("%s%%", arg.S), - Column: clause.Column{ - Table: c.Table, - Name: c.Name, - }, - }, nil + value := fmt.Sprintf("%s%%", arg.S) + switch c := parsedNode.(type) { + case clause.Column: + return clause.Like{ + Value: value, + Column: clause.Column{ + Table: c.Table, + Name: c.Name, + }, + }, nil + case Json: + return JsonLike{ + Value: value, + Json: c, + }, nil + default: + return nil, errors.New("unsupported node type. has to be clause.Column or Json") + } }), nil } diff --git a/pkg/api/aim/query/query_test.go b/pkg/api/aim/query/query_test.go index e5d7db3e5..3f43e196c 100644 --- a/pkg/api/aim/query/query_test.go +++ b/pkg/api/aim/query/query_test.go @@ -209,6 +209,24 @@ func (s *QueryTestSuite) TestPostgresDialector_Ok() { `AND "runs"."lifecycle_stage" <> $3`, expectedVars: []interface{}{"{key1}", "%val1%", models.LifecycleStageDeleted}, }, + { + name: "TestMetricContextStartsWith", + query: `metric.context.key1.startswith("va")`, + selectMetrics: true, + expectedSQL: `SELECT ID FROM "metrics" ` + + `WHERE "contexts"."json"#>>$1 LIKE $2 ` + + `AND "runs"."lifecycle_stage" <> $3`, + expectedVars: []interface{}{"{key1}", "va%", models.LifecycleStageDeleted}, + }, + { + name: "TestMetricContextEndsWith", + query: `metric.context.key1.endswith("va")`, + selectMetrics: true, + expectedSQL: `SELECT ID FROM "metrics" ` + + `WHERE "contexts"."json"#>>$1 LIKE $2 ` + + `AND "runs"."lifecycle_stage" <> $3`, + expectedVars: []interface{}{"{key1}", "%va", models.LifecycleStageDeleted}, + }, } for _, tt := range tests { @@ -430,6 +448,24 @@ func (s *QueryTestSuite) TestSqliteDialector_Ok() { `AND "runs"."lifecycle_stage" <> $3`, expectedVars: []interface{}{"$.key1", "%val1%", models.LifecycleStageDeleted}, }, + { + name: "TestMetricContextStartsWith", + query: `metric.context.key1.startswith("va")`, + selectMetrics: true, + expectedSQL: `SELECT ID FROM "metrics" ` + + `WHERE IFNULL("contexts"."json", JSON('{}'))->>$1 LIKE $2 ` + + `AND "runs"."lifecycle_stage" <> $3`, + expectedVars: []interface{}{"$.key1", "va%", models.LifecycleStageDeleted}, + }, + { + name: "TestMetricContextEndsWith", + query: `metric.context.key1.endswith("va")`, + selectMetrics: true, + expectedSQL: `SELECT ID FROM "metrics" ` + + `WHERE IFNULL("contexts"."json", JSON('{}'))->>$1 LIKE $2 ` + + `AND "runs"."lifecycle_stage" <> $3`, + expectedVars: []interface{}{"$.key1", "%va", models.LifecycleStageDeleted}, + }, } for _, tt := range tests {