This is an automated email from the ASF dual-hosted git repository.

kezhenxu94 pushed a commit to branch ql
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git

commit c9129efce96a7e2d759af2a4a0ad67c63072774b
Author: kezhenxu94 <[email protected]>
AuthorDate: Sun Sep 21 11:19:16 2025 +0800

    sync
---
 bydbctl/internal/cmd/bydbql.go      |   2 +-
 bydbctl/internal/cmd/bydbql_test.go |   8 +-
 pkg/bydbql/bydbql_test.go           | 410 +++++++++++++++++++++++++++++++++++-
 pkg/bydbql/parser.go                |   2 +-
 pkg/bydbql/translator.go            | 231 ++++++++++++--------
 5 files changed, 555 insertions(+), 98 deletions(-)

diff --git a/bydbctl/internal/cmd/bydbql.go b/bydbctl/internal/cmd/bydbql.go
index 387898d7..ba1faaae 100644
--- a/bydbctl/internal/cmd/bydbql.go
+++ b/bydbctl/internal/cmd/bydbql.go
@@ -293,7 +293,7 @@ func executeRESTQuery(endpoint string, jsonData 
map[string]any) error {
                        return []reqBody{reqBodyData}, nil
                },
                func(req request) (*resty.Response, error) {
-                       fmt.Printf("Executing query at endpoint %s\n", 
string(req.data))
+                       // fmt.Printf("Executing query at endpoint %s\n", 
string(req.data))
                        url := viper.GetString("addr") + endpoint
                        resp, err := req.req.
                                SetHeader("Content-Type", "application/json").
diff --git a/bydbctl/internal/cmd/bydbql_test.go 
b/bydbctl/internal/cmd/bydbql_test.go
index df34fadf..130de133 100644
--- a/bydbctl/internal/cmd/bydbql_test.go
+++ b/bydbctl/internal/cmd/bydbql_test.go
@@ -90,7 +90,7 @@ SELECT trace_id FROM STREAM sw in (default) TIME BETWEEN '%s' 
AND '%s'`, nowStr,
                }, flags.EventuallyTimeout).Should(Equal(5))
        })
 
-       It("executes stream query with WHERE condition", func() {
+       It("executes stream query with WHERE condition and limit", func() {
                conn, err := grpclib.NewClient(
                        grpcAddr,
                        
grpclib.WithTransportCredentials(insecure.NewCredentials()),
@@ -104,7 +104,7 @@ SELECT trace_id FROM STREAM sw in (default) TIME BETWEEN 
'%s' AND '%s'`, nowStr,
                rootCmd.SetArgs([]string{"bydbql", "query", "-a", addr, "-f", 
"-"})
                issue := func() string {
                        rootCmd.SetIn(strings.NewReader(fmt.Sprintf(`
-SELECT trace_id FROM STREAM sw in (default) TIME BETWEEN '%s' AND '%s' WHERE 
trace_id = 'trace-1' LIMIT 10`, nowStr, endStr)))
+                       SELECT trace_id, webapp_service FROM STREAM sw in 
(default) TIME BETWEEN '%s' AND '%s' WHERE service_id = 'webapp_id' LIMIT 3`, 
nowStr, endStr)))
                        return capturer.CaptureStdout(func() {
                                err := rootCmd.Execute()
                                if err != nil {
@@ -119,7 +119,7 @@ SELECT trace_id FROM STREAM sw in (default) TIME BETWEEN 
'%s' AND '%s' WHERE tra
                        helpers.UnmarshalYAML([]byte(out), resp)
                        GinkgoWriter.Println(resp)
                        return len(resp.Elements)
-               }, time.Microsecond).Should(Equal(1))
+               }, flags.EventuallyTimeout).Should(Equal(3)) // We have > 3 
elements match the condition, but limit 3
        })
 
        It("executes stream query with relative time range", func() {
@@ -136,7 +136,7 @@ SELECT trace_id FROM STREAM sw in (default) TIME BETWEEN 
'%s' AND '%s' WHERE tra
                rootCmd.SetArgs([]string{"bydbql", "query", "-a", addr, "-f", 
"-"})
                issue := func() string {
                        rootCmd.SetIn(strings.NewReader(`
-SELECT trace_id FROM STREAM sw TIME > '-30m' LIMIT 10`))
+SELECT trace_id FROM STREAM sw in (default) TIME > '-30m' LIMIT 10`))
                        return capturer.CaptureStdout(func() {
                                err := rootCmd.Execute()
                                if err != nil {
diff --git a/pkg/bydbql/bydbql_test.go b/pkg/bydbql/bydbql_test.go
index 8534dd73..22a14f49 100644
--- a/pkg/bydbql/bydbql_test.go
+++ b/pkg/bydbql/bydbql_test.go
@@ -249,12 +249,12 @@ var _ = Describe("Translator", func() {
                Entry("WHERE clause with criteria",
                        "SELECT * FROM STREAM sw WHERE service_id = 'webapp'",
                        func(data map[string]any) bool {
-                               criteria, ok := data["criteria"].([]any)
-                               if !ok || len(criteria) == 0 {
+                               criteria, ok := 
data["criteria"].(map[string]any)
+                               if !ok {
                                        return false
                                }
-                               first, ok := criteria[0].(map[string]any)
-                               return ok && first["tagName"] == "service_id" 
&& first["op"] == "BINARY_OP_EQ"
+                               condition, ok := 
criteria["condition"].(map[string]any)
+                               return ok && condition["name"] == "service_id" 
&& condition["op"] == "BINARY_OP_EQ"
                        }),
                Entry("TOP N query",
                        "SHOW TOP 10 FROM MEASURE service_latency ORDER BY 
value DESC",
@@ -266,8 +266,8 @@ var _ = Describe("Translator", func() {
                Entry("property query with IDs",
                        "SELECT * FROM PROPERTY metadata WHERE ID = 'id1' OR ID 
= 'id2'",
                        func(data map[string]any) bool {
-                               criteria, ok := data["criteria"].([]any)
-                               return ok && len(criteria) == 2
+                               criteria, ok := 
data["criteria"].(map[string]any)
+                               return ok && criteria != nil
                        }),
                Entry("query trace enabled",
                        "SELECT * FROM STREAM sw WITH QUERY_TRACE",
@@ -515,3 +515,401 @@ func BenchmarkEndToEnd(b *testing.B) {
                _, _, _ = TranslateQuery(query, context)
        }
 }
+
+var _ = Describe("Criteria BinaryOp Tests", func() {
+       var context *QueryContext
+
+       BeforeEach(func() {
+               context = &QueryContext{
+                       DefaultGroup:        "default",
+                       DefaultResourceName: "test_resource",
+                       DefaultResourceType: ResourceTypeStream,
+                       CurrentTime:         time.Date(2023, 1, 1, 12, 0, 0, 0, 
time.UTC),
+               }
+       })
+
+       DescribeTable("translates all BinaryOp operators correctly",
+               func(whereClause string, expectedOp string, validateValue 
func(map[string]any) bool) {
+                       query := fmt.Sprintf("SELECT * FROM STREAM sw WHERE 
%s", whereClause)
+                       parsed, errors := ParseQuery(query)
+                       Expect(errors).To(BeEmpty())
+                       Expect(parsed).NotTo(BeNil())
+
+                       translator := NewTranslator(context)
+                       data, err := translator.TranslateToMap(parsed)
+                       Expect(err).NotTo(HaveOccurred())
+
+                       // Extract condition from criteria
+                       criteria, ok := data["criteria"].(map[string]any)
+                       Expect(ok).To(BeTrue(), "criteria should be an object")
+                       Expect(criteria).NotTo(BeEmpty())
+
+                       condition, ok := criteria["condition"].(map[string]any)
+                       Expect(ok).To(BeTrue(), "should have condition field")
+
+                       Expect(condition["op"]).To(Equal(expectedOp))
+                       if validateValue != nil {
+                               Expect(validateValue(condition)).To(BeTrue(), 
"value validation failed for condition: %+v", condition)
+                       }
+               },
+               // BINARY_OP_EQ
+               Entry("BINARY_OP_EQ with string",
+                       "service_id = 'mysql'",
+                       "BINARY_OP_EQ",
+                       func(condition map[string]any) bool {
+                               value, ok := condition["value"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               str, ok := value["str"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               return str["value"] == "mysql" && 
condition["name"] == "service_id"
+                       }),
+               Entry("BINARY_OP_EQ with integer",
+                       "status = 200",
+                       "BINARY_OP_EQ",
+                       func(condition map[string]any) bool {
+                               value, ok := condition["value"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               intVal, ok := value["int"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               return intVal["value"] == "200" && 
condition["name"] == "status"
+                       }),
+               // BINARY_OP_NE
+               Entry("BINARY_OP_NE with string",
+                       "service_name != 'test-service'",
+                       "BINARY_OP_NE",
+                       func(condition map[string]any) bool {
+                               value, ok := condition["value"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               str, ok := value["str"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               return str["value"] == "test-service" && 
condition["name"] == "service_name"
+                       }),
+               // BINARY_OP_GT
+               Entry("BINARY_OP_GT with integer",
+                       "latency > 1000",
+                       "BINARY_OP_GT",
+                       func(condition map[string]any) bool {
+                               value, ok := condition["value"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               intVal, ok := value["int"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               return intVal["value"] == "1000" && 
condition["name"] == "latency"
+                       }),
+               // BINARY_OP_LT
+               Entry("BINARY_OP_LT with integer",
+                       "response_time < 500",
+                       "BINARY_OP_LT",
+                       func(condition map[string]any) bool {
+                               value, ok := condition["value"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               intVal, ok := value["int"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               return intVal["value"] == "500" && 
condition["name"] == "response_time"
+                       }),
+               // BINARY_OP_GE
+               Entry("BINARY_OP_GE with integer",
+                       "status >= 10",
+                       "BINARY_OP_GE",
+                       func(condition map[string]any) bool {
+                               value, ok := condition["value"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               intVal, ok := value["int"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               return intVal["value"] == "10" && 
condition["name"] == "status"
+                       }),
+               // BINARY_OP_LE
+               Entry("BINARY_OP_LE with integer",
+                       "duration <= 3000",
+                       "BINARY_OP_LE",
+                       func(condition map[string]any) bool {
+                               value, ok := condition["value"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               intVal, ok := value["int"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               return intVal["value"] == "3000" && 
condition["name"] == "duration"
+                       }),
+               // BINARY_OP_MATCH
+               Entry("BINARY_OP_MATCH with string",
+                       "service_id MATCH 'mysql'",
+                       "BINARY_OP_MATCH",
+                       func(condition map[string]any) bool {
+                               value, ok := condition["value"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               str, ok := value["str"].(map[string]any)
+                               if !ok {
+                                       return false
+                               }
+                               return str["value"] == "mysql" && 
condition["name"] == "service_id"
+                       }),
+       )
+})
+
+var _ = Describe("Criteria LogicalExpression Tests", func() {
+       var context *QueryContext
+
+       BeforeEach(func() {
+               context = &QueryContext{
+                       DefaultGroup:        "default",
+                       DefaultResourceName: "test_resource",
+                       DefaultResourceType: ResourceTypeStream,
+                       CurrentTime:         time.Date(2023, 1, 1, 12, 0, 0, 0, 
time.UTC),
+               }
+       })
+
+       It("translates AND logical expression correctly", func() {
+               query := "SELECT * FROM STREAM sw WHERE service_id = 'webapp' 
AND status = 200"
+               parsed, errors := ParseQuery(query)
+               Expect(errors).To(BeEmpty())
+               Expect(parsed).NotTo(BeNil())
+
+               translator := NewTranslator(context)
+               data, err := translator.TranslateToMap(parsed)
+               Expect(err).NotTo(HaveOccurred())
+
+               // Extract criteria
+               criteria, ok := data["criteria"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(criteria).NotTo(BeEmpty())
+
+               // Should have a logical expression
+               le, ok := criteria["le"].(map[string]any)
+               Expect(ok).To(BeTrue(), "should have logical expression")
+               Expect(le["op"]).To(Equal("LOGICAL_OP_AND"))
+
+               // Check left condition
+               left, ok := le["left"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               leftCond, ok := left["condition"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(leftCond["name"]).To(Equal("service_id"))
+               Expect(leftCond["op"]).To(Equal("BINARY_OP_EQ"))
+
+               // Check right condition
+               right, ok := le["right"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               rightCond, ok := right["condition"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(rightCond["name"]).To(Equal("status"))
+               Expect(rightCond["op"]).To(Equal("BINARY_OP_EQ"))
+       })
+
+       It("translates multiple AND conditions correctly", func() {
+               query := "SELECT * FROM STREAM sw WHERE service_id = 'webapp' 
AND status = 200 AND region = 'us-west'"
+               parsed, errors := ParseQuery(query)
+               Expect(errors).To(BeEmpty())
+               Expect(parsed).NotTo(BeNil())
+
+               translator := NewTranslator(context)
+               data, err := translator.TranslateToMap(parsed)
+               Expect(err).NotTo(HaveOccurred())
+
+               // Extract criteria
+               criteria, ok := data["criteria"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(criteria).NotTo(BeEmpty())
+
+               // Should have nested logical expressions
+               le, ok := criteria["le"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(le["op"]).To(Equal("LOGICAL_OP_AND"))
+
+               // The structure should be: ((service_id = 'webapp' AND status 
= 200) AND region = 'us-west')
+               // Check that left is another logical expression
+               left, ok := le["left"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               innerLe, ok := left["le"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(innerLe["op"]).To(Equal("LOGICAL_OP_AND"))
+
+               // Check the rightmost condition
+               right, ok := le["right"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               rightCond, ok := right["condition"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(rightCond["name"]).To(Equal("region"))
+               Expect(rightCond["op"]).To(Equal("BINARY_OP_EQ"))
+       })
+
+       It("translates complex nested criteria with different operators", 
func() {
+               query := "SELECT * FROM STREAM sw WHERE service_id = 'webapp' 
AND latency > 1000 AND status != 500"
+               parsed, errors := ParseQuery(query)
+               Expect(errors).To(BeEmpty())
+               Expect(parsed).NotTo(BeNil())
+
+               translator := NewTranslator(context)
+               data, err := translator.TranslateToMap(parsed)
+               Expect(err).NotTo(HaveOccurred())
+
+               // Verify structure exists
+               criteria, ok := data["criteria"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(criteria).NotTo(BeEmpty())
+
+               // Verify it has nested logical expressions
+               _, hasLe := criteria["le"]
+               Expect(hasLe).To(BeTrue())
+       })
+
+       It("translates MATCH operator correctly", func() {
+               query := "SELECT * FROM STREAM sw WHERE description MATCH 
'error occurred'"
+               parsed, errors := ParseQuery(query)
+               Expect(errors).To(BeEmpty())
+               Expect(parsed).NotTo(BeNil())
+
+               translator := NewTranslator(context)
+               data, err := translator.TranslateToMap(parsed)
+               Expect(err).NotTo(HaveOccurred())
+
+               criteria, ok := data["criteria"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(criteria).NotTo(BeEmpty())
+
+               condition, ok := criteria["condition"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(condition["name"]).To(Equal("description"))
+               Expect(condition["op"]).To(Equal("BINARY_OP_MATCH"))
+
+               value, ok := condition["value"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               str, ok := value["str"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(str["value"]).To(Equal("error occurred"))
+       })
+
+       It("translates mixed string and integer conditions", func() {
+               query := "SELECT * FROM STREAM sw WHERE service_id = 'webapp' 
AND status >= 400 AND latency < 5000"
+               parsed, errors := ParseQuery(query)
+               Expect(errors).To(BeEmpty())
+               Expect(parsed).NotTo(BeNil())
+
+               translator := NewTranslator(context)
+               yamlData, err := translator.TranslateToYAML(parsed)
+               Expect(err).NotTo(HaveOccurred())
+               Expect(yamlData).NotTo(BeEmpty())
+
+               // Verify YAML structure by unmarshaling
+               var result map[string]any
+               err = yaml.Unmarshal(yamlData, &result)
+               Expect(err).NotTo(HaveOccurred())
+               Expect(result["criteria"]).NotTo(BeNil())
+       })
+})
+
+var _ = Describe("Criteria Proto Format Verification", func() {
+       var context *QueryContext
+
+       BeforeEach(func() {
+               context = &QueryContext{
+                       DefaultGroup:        "default",
+                       DefaultResourceName: "test_resource",
+                       DefaultResourceType: ResourceTypeStream,
+                       CurrentTime:         time.Date(2023, 1, 1, 12, 0, 0, 0, 
time.UTC),
+               }
+       })
+
+       It("generates criteria in correct proto format for MATCH operator", 
func() {
+               query := "SELECT * FROM STREAM sw WHERE database_instance MATCH 
'mysql'"
+               parsed, errors := ParseQuery(query)
+               Expect(errors).To(BeEmpty())
+               Expect(parsed).NotTo(BeNil())
+
+               translator := NewTranslator(context)
+               yamlData, err := translator.TranslateToYAML(parsed)
+               Expect(err).NotTo(HaveOccurred())
+
+               // Print the YAML output to verify the format
+               fmt.Printf("YAML Output for MATCH query:\n%s\n", 
string(yamlData))
+
+               // Verify the structure matches the expected proto format
+               data, err := translator.TranslateToMap(parsed)
+               Expect(err).NotTo(HaveOccurred())
+
+               criteria, ok := data["criteria"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(criteria).NotTo(BeEmpty())
+
+               condition, ok := criteria["condition"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(condition["name"]).To(Equal("database_instance"))
+               Expect(condition["op"]).To(Equal("BINARY_OP_MATCH"))
+
+               // Verify value structure
+               value, ok := condition["value"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               str, ok := value["str"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(str["value"]).To(Equal("mysql"))
+       })
+
+       It("generates criteria with LogicalExpression for AND conditions", 
func() {
+               query := "SELECT * FROM STREAM sw WHERE service_id = 'webapp' 
AND status = 200"
+               parsed, errors := ParseQuery(query)
+               Expect(errors).To(BeEmpty())
+               Expect(parsed).NotTo(BeNil())
+
+               translator := NewTranslator(context)
+               yamlData, err := translator.TranslateToYAML(parsed)
+               Expect(err).NotTo(HaveOccurred())
+
+               // Print the YAML output to verify the format
+               fmt.Printf("YAML Output for AND conditions:\n%s\n", 
string(yamlData))
+
+               // Verify the structure has proper LogicalExpression
+               data, err := translator.TranslateToMap(parsed)
+               Expect(err).NotTo(HaveOccurred())
+
+               criteria, ok := data["criteria"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(criteria).NotTo(BeEmpty())
+
+               // Should have a logical expression
+               le, ok := criteria["le"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(le["op"]).To(Equal("LOGICAL_OP_AND"))
+
+               // Verify left and right conditions
+               left, ok := le["left"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               leftCond, ok := left["condition"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(leftCond["name"]).To(Equal("service_id"))
+               Expect(leftCond["op"]).To(Equal("BINARY_OP_EQ"))
+
+               right, ok := le["right"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               rightCond, ok := right["condition"].(map[string]any)
+               Expect(ok).To(BeTrue())
+               Expect(rightCond["name"]).To(Equal("status"))
+               Expect(rightCond["op"]).To(Equal("BINARY_OP_EQ"))
+       })
+})
diff --git a/pkg/bydbql/parser.go b/pkg/bydbql/parser.go
index 07b3610a..0009873a 100644
--- a/pkg/bydbql/parser.go
+++ b/pkg/bydbql/parser.go
@@ -665,7 +665,7 @@ func (p *Parser) parseCondition() *Condition {
        p.nextToken()
 
        // Parse right side
-       if condition.Operator == OpIn || condition.Operator == OpNotIn {
+       if condition.Operator == OpIn || condition.Operator == OpNotIn || 
condition.Operator == OpHaving || condition.Operator == OpNotHaving {
                // Parse value list
                if !p.expectToken(TokenLeftParen) {
                        return nil
diff --git a/pkg/bydbql/translator.go b/pkg/bydbql/translator.go
index df25f9eb..5f94ddd1 100644
--- a/pkg/bydbql/translator.go
+++ b/pkg/bydbql/translator.go
@@ -33,7 +33,7 @@ type QueryYAML struct {
        Name      string                   `yaml:"name,omitempty" 
json:"name,omitempty"`
        Groups    []string                 `yaml:"groups,omitempty" 
json:"groups,omitempty"`
        TimeRange *TimeRangeYAML           `yaml:"timeRange,omitempty" 
json:"timeRange,omitempty"`
-       Criteria  []map[string]interface{} `yaml:"criteria,omitempty" 
json:"criteria,omitempty"`
+       Criteria  map[string]interface{}   `yaml:"criteria,omitempty" 
json:"criteria,omitempty"`
        Limit     *int                     `yaml:"limit,omitempty" 
json:"limit,omitempty"`
        Offset    *int                     `yaml:"offset,omitempty" 
json:"offset,omitempty"`
        Trace     bool                     `yaml:"trace,omitempty" 
json:"trace,omitempty"`
@@ -55,7 +55,7 @@ type QueryYAML struct {
        // Top-N specific (for separate endpoint)
        TopN           int                      `yaml:"top_n,omitempty" 
json:"top_n,omitempty"`
        FieldValueSort string                   
`yaml:"field_value_sort,omitempty" json:"field_value_sort,omitempty"`
-       Conditions     []map[string]interface{} `yaml:"conditions,omitempty" 
json:"conditions,omitempty"`
+       Conditions     map[string]interface{}   `yaml:"conditions,omitempty" 
json:"conditions,omitempty"`
 }
 
 // TimeRangeYAML represents time range in YAML
@@ -471,104 +471,163 @@ func (t *Translator) parseTimestamp(timestamp string) 
(string, error) {
 }
 
 // translateWhereClause translates WHERE conditions to criteria format
-func (t *Translator) translateWhereClause(where *WhereClause) 
([]map[string]interface{}, error) {
-       var criteria []map[string]interface{}
+func (t *Translator) translateWhereClause(where *WhereClause) 
(map[string]interface{}, error) {
+       if len(where.Conditions) == 0 {
+               return nil, nil
+       }
 
-       for _, condition := range where.Conditions {
-               criterion := make(map[string]interface{})
+       if len(where.Conditions) == 1 {
+               // Single condition - wrap in Criteria with condition field
+               condition, err := t.translateCondition(where.Conditions[0])
+               if err != nil {
+                       return nil, err
+               }
+               return map[string]interface{}{
+                       "condition": condition,
+               }, nil
+       }
 
-               // Set tag name
-               criterion["tagName"] = condition.Left
+       // Multiple conditions - create LogicalExpression with AND operator
+       var criteria map[string]interface{}
+       leftCondition, err := t.translateCondition(where.Conditions[0])
+       if err != nil {
+               return nil, err
+       }
 
-               // Set operator and values
-               switch condition.Operator {
-               case OpEqual:
-                       criterion["op"] = "BINARY_OP_EQ"
-                       if condition.Right != nil {
-                               criterion["value"] = 
t.translateValue(condition.Right)
-                       }
-               case OpNotEqual:
-                       criterion["op"] = "BINARY_OP_NE"
-                       if condition.Right != nil {
-                               criterion["value"] = 
t.translateValue(condition.Right)
-                       }
-               case OpGreater:
-                       criterion["op"] = "BINARY_OP_GT"
-                       if condition.Right != nil {
-                               criterion["value"] = 
t.translateValue(condition.Right)
-                       }
-               case OpLess:
-                       criterion["op"] = "BINARY_OP_LT"
-                       if condition.Right != nil {
-                               criterion["value"] = 
t.translateValue(condition.Right)
-                       }
-               case OpGreaterEqual:
-                       criterion["op"] = "BINARY_OP_GE"
-                       if condition.Right != nil {
-                               criterion["value"] = 
t.translateValue(condition.Right)
-                       }
-               case OpLessEqual:
-                       criterion["op"] = "BINARY_OP_LE"
-                       if condition.Right != nil {
-                               criterion["value"] = 
t.translateValue(condition.Right)
-                       }
-               case OpIn:
-                       criterion["op"] = "BINARY_OP_IN"
-                       var values []interface{}
-                       for _, val := range condition.Values {
-                               values = append(values, t.translateValue(val))
-                       }
-                       criterion["value"] = map[string]interface{}{
-                               "strArray": map[string]interface{}{
-                                       "value": values,
-                               },
-                       }
-               case OpNotIn:
-                       criterion["op"] = "BINARY_OP_NOT_IN"
-                       var values []interface{}
-                       for _, val := range condition.Values {
-                               values = append(values, t.translateValue(val))
-                       }
-                       criterion["value"] = map[string]interface{}{
-                               "strArray": map[string]interface{}{
-                                       "value": values,
+       if len(where.Conditions) == 2 {
+               rightCondition, err := t.translateCondition(where.Conditions[1])
+               if err != nil {
+                       return nil, err
+               }
+               criteria = map[string]interface{}{
+                       "le": map[string]interface{}{
+                               "op": "LOGICAL_OP_AND",
+                               "left": map[string]interface{}{
+                                       "condition": leftCondition,
                                },
-                       }
-               case OpHaving:
-                       criterion["op"] = "BINARY_OP_HAVING"
-                       var values []interface{}
-                       for _, val := range condition.Values {
-                               values = append(values, t.translateValue(val))
-                       }
-                       criterion["value"] = map[string]interface{}{
-                               "strArray": map[string]interface{}{
-                                       "value": values,
+                               "right": map[string]interface{}{
+                                       "condition": rightCondition,
                                },
+                       },
+               }
+       } else {
+               // More than 2 conditions - chain them with AND operators
+               criteria = map[string]interface{}{
+                       "condition": leftCondition,
+               }
+               for i := 1; i < len(where.Conditions); i++ {
+                       condition, err := 
t.translateCondition(where.Conditions[i])
+                       if err != nil {
+                               return nil, err
                        }
-               case OpNotHaving:
-                       criterion["op"] = "BINARY_OP_NOT_HAVING"
-                       var values []interface{}
-                       for _, val := range condition.Values {
-                               values = append(values, t.translateValue(val))
-                       }
-                       criterion["value"] = map[string]interface{}{
-                               "strArray": map[string]interface{}{
-                                       "value": values,
+                       criteria = map[string]interface{}{
+                               "le": map[string]interface{}{
+                                       "op": "LOGICAL_OP_AND",
+                                       "left": criteria,
+                                       "right": map[string]interface{}{
+                                               "condition": condition,
+                                       },
                                },
                        }
-               case OpMatch:
-                       criterion["op"] = "BINARY_OP_MATCH"
-                       if condition.Right != nil {
-                               criterion["value"] = 
t.translateValue(condition.Right)
-                       }
                }
-
-               criteria = append(criteria, criterion)
        }
 
        return criteria, nil
 }
 
+// translateCondition translates a single condition to the proper format
+func (t *Translator) translateCondition(condition *Condition) 
(map[string]interface{}, error) {
+       criterion := make(map[string]interface{})
+
+       // Set field name
+       criterion["name"] = condition.Left
+
+       // Set operator and values
+       switch condition.Operator {
+       case OpEqual:
+               criterion["op"] = "BINARY_OP_EQ"
+               if condition.Right != nil {
+                       criterion["value"] = t.translateValue(condition.Right)
+               }
+       case OpNotEqual:
+               criterion["op"] = "BINARY_OP_NE"
+               if condition.Right != nil {
+                       criterion["value"] = t.translateValue(condition.Right)
+               }
+       case OpGreater:
+               criterion["op"] = "BINARY_OP_GT"
+               if condition.Right != nil {
+                       criterion["value"] = t.translateValue(condition.Right)
+               }
+       case OpLess:
+               criterion["op"] = "BINARY_OP_LT"
+               if condition.Right != nil {
+                       criterion["value"] = t.translateValue(condition.Right)
+               }
+       case OpGreaterEqual:
+               criterion["op"] = "BINARY_OP_GE"
+               if condition.Right != nil {
+                       criterion["value"] = t.translateValue(condition.Right)
+               }
+       case OpLessEqual:
+               criterion["op"] = "BINARY_OP_LE"
+               if condition.Right != nil {
+                       criterion["value"] = t.translateValue(condition.Right)
+               }
+       case OpIn:
+               criterion["op"] = "BINARY_OP_IN"
+               var values []interface{}
+               for _, val := range condition.Values {
+                       values = append(values, t.translateValue(val))
+               }
+               criterion["value"] = map[string]interface{}{
+                       "strArray": map[string]interface{}{
+                               "value": values,
+                       },
+               }
+       case OpNotIn:
+               criterion["op"] = "BINARY_OP_NOT_IN"
+               var values []interface{}
+               for _, val := range condition.Values {
+                       values = append(values, t.translateValue(val))
+               }
+               criterion["value"] = map[string]interface{}{
+                       "strArray": map[string]interface{}{
+                               "value": values,
+                       },
+               }
+       case OpHaving:
+               criterion["op"] = "BINARY_OP_HAVING"
+               var values []interface{}
+               for _, val := range condition.Values {
+                       values = append(values, t.translateValue(val))
+               }
+               criterion["value"] = map[string]interface{}{
+                       "strArray": map[string]interface{}{
+                               "value": values,
+                       },
+               }
+       case OpNotHaving:
+               criterion["op"] = "BINARY_OP_NOT_HAVING"
+               var values []interface{}
+               for _, val := range condition.Values {
+                       values = append(values, t.translateValue(val))
+               }
+               criterion["value"] = map[string]interface{}{
+                       "strArray": map[string]interface{}{
+                               "value": values,
+                       },
+               }
+       case OpMatch:
+               criterion["op"] = "BINARY_OP_MATCH"
+               if condition.Right != nil {
+                       criterion["value"] = t.translateValue(condition.Right)
+               }
+       }
+
+       return criterion, nil
+}
+
 // translateValue translates a value to the appropriate YAML format
 func (t *Translator) translateValue(value *Value) interface{} {
        if value.IsNull {

Reply via email to