You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ke...@apache.org on 2025/10/01 12:53:20 UTC

(skywalking-banyandb) 01/04: ql

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 2a3954e5c2a11b2215edaf17c5e576e803719527
Author: kezhenxu94 <ke...@apache.org>
AuthorDate: Wed Sep 17 17:18:18 2025 +0800

    ql
---
 bydbctl/internal/cmd/bydbql.go      | 378 +++++++++++++++++
 bydbctl/internal/cmd/bydbql_test.go | 345 ++++++++++++++++
 bydbctl/internal/cmd/root.go        |   2 +-
 pkg/bydbql/ast.go                   | 327 +++++++++++++++
 pkg/bydbql/bydbql_suite_test.go     |  30 ++
 pkg/bydbql/bydbql_test.go           | 465 +++++++++++++++++++++
 pkg/bydbql/lexer.go                 | 608 +++++++++++++++++++++++++++
 pkg/bydbql/parser.go                | 796 ++++++++++++++++++++++++++++++++++++
 pkg/bydbql/translator.go            | 591 ++++++++++++++++++++++++++
 9 files changed, 3541 insertions(+), 1 deletion(-)

diff --git a/bydbctl/internal/cmd/bydbql.go b/bydbctl/internal/cmd/bydbql.go
new file mode 100644
index 00000000..67545eae
--- /dev/null
+++ b/bydbctl/internal/cmd/bydbql.go
@@ -0,0 +1,378 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package cmd
+
+import (
+	"bufio"
+	"encoding/json"
+	"fmt"
+	"io"
+	"os"
+	"strings"
+
+	"github.com/go-resty/resty/v2"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+	"sigs.k8s.io/yaml"
+
+	"github.com/apache/skywalking-banyandb/pkg/bydbql"
+	"github.com/apache/skywalking-banyandb/pkg/version"
+)
+
+// BydbQL endpoint paths
+const (
+	streamQueryPath   = "/api/v1/stream/query"
+	measureQueryPath  = "/api/v1/measure/query"
+	traceQueryPath    = "/api/v1/trace/query"
+	propertyQueryPath = "/api/v1/property/query"
+	topnQueryPath     = "/api/v1/measure/topn"
+)
+
+// newBydbQLCmd creates the BydbQL command
+func newBydbQLCmd() *cobra.Command {
+	bydbqlCmd := &cobra.Command{
+		Use:     "bydbql",
+		Version: version.Build(),
+		Short:   "BanyanDB Query Language (BydbQL) interface",
+		Long: `BanyanDB Query Language (BydbQL) provides a SQL-like interface for querying BanyanDB.
+It supports querying streams, measures, traces, properties, and top-N operations with familiar SQL syntax.
+
+Examples:
+  # Interactive mode
+  bydbctl bydbql
+
+  # Execute query from file
+  bydbctl bydbql query -f query.sql
+
+  # Execute query from stdin
+  echo "SELECT * FROM STREAM sw WHERE service_id = 'webapp'" | bydbctl bydbql query -f -`,
+	}
+
+	// Interactive command
+	interactiveCmd := &cobra.Command{
+		Use:     "interactive",
+		Aliases: []string{"i"},
+		Version: version.Build(),
+		Short:   "Start interactive BydbQL shell",
+		RunE: func(cmd *cobra.Command, _ []string) error {
+			return runInteractiveMode(cmd)
+		},
+	}
+
+	// Query command
+	queryCmd := &cobra.Command{
+		Use:     "query [-f file]",
+		Version: version.Build(),
+		Short:   "Execute BydbQL query from file or stdin",
+		Long: `Execute BydbQL query from a file or stdin. The query will be parsed and translated to YAML format,
+then sent to the appropriate BanyanDB endpoint.
+
+Supported query types:
+  - SELECT queries for streams, measures, traces, and properties
+  - SHOW TOP N queries for top-N operations
+
+Examples:
+  bydbctl bydbql query -f query.sql
+  echo "SELECT * FROM STREAM sw" | bydbctl bydbql query -f -`,
+		RunE: func(cmd *cobra.Command, _ []string) error {
+			return runQueryMode(cmd)
+		},
+	}
+
+	bindFileFlag(queryCmd)
+	bindTLSRelatedFlag(interactiveCmd, queryCmd)
+
+	bydbqlCmd.AddCommand(interactiveCmd, queryCmd)
+
+	// Default to interactive mode if no subcommand specified
+	bydbqlCmd.RunE = func(cmd *cobra.Command, _ []string) error {
+		return runInteractiveMode(cmd)
+	}
+
+	return bydbqlCmd
+}
+
+// runInteractiveMode starts the interactive BydbQL shell
+func runInteractiveMode(cmd *cobra.Command) error {
+	fmt.Println("BanyanDB Query Language (BydbQL) Interactive Shell")
+	fmt.Println("Type 'help' for help, 'exit' or 'quit' to quit.")
+	fmt.Println()
+
+	scanner := bufio.NewScanner(os.Stdin)
+
+	for {
+		fmt.Print("bydbql> ")
+
+		if !scanner.Scan() {
+			break
+		}
+
+		line := strings.TrimSpace(scanner.Text())
+
+		if line == "" {
+			continue
+		}
+
+		// Handle special commands
+		switch strings.ToLower(line) {
+		case "help":
+			printHelp()
+			continue
+		case "exit", "quit":
+			fmt.Println("Goodbye!")
+			return nil
+		case "\\q":
+			fmt.Println("Goodbye!")
+			return nil
+		}
+
+		// Check for multi-line queries (ending with semicolon)
+		query := line
+		for !strings.HasSuffix(strings.TrimSpace(query), ";") {
+			fmt.Print("    -> ")
+			if !scanner.Scan() {
+				break
+			}
+			query += " " + scanner.Text()
+		}
+
+		// Remove trailing semicolon
+		query = strings.TrimSuffix(strings.TrimSpace(query), ";")
+
+		// Execute the query
+		if err := executeBydbQLQuery(query); err != nil {
+			fmt.Printf("Error: %v\n", err)
+		}
+		fmt.Println()
+	}
+
+	if err := scanner.Err(); err != nil {
+		return fmt.Errorf("error reading input: %w", err)
+	}
+
+	return nil
+}
+
+// runQueryMode executes BydbQL query from file or stdin
+func runQueryMode(cmd *cobra.Command) error {
+	var reader io.Reader
+
+	if filePath == "-" || filePath == "" {
+		reader = cmd.InOrStdin()
+	} else {
+		file, err := os.Open(filePath)
+		if err != nil {
+			return fmt.Errorf("error opening file %s: %w", filePath, err)
+		}
+		defer file.Close()
+		reader = file
+	}
+
+	// Read the entire query
+	content, err := io.ReadAll(reader)
+	if err != nil {
+		return fmt.Errorf("error reading query: %w", err)
+	}
+
+	query := strings.TrimSpace(string(content))
+	query = strings.TrimSuffix(query, ";")
+
+	if query == "" {
+		return fmt.Errorf("empty query")
+	}
+
+	return executeBydbQLQuery(query)
+}
+
+// executeBydbQLQuery parses and executes a BydbQL query
+func executeBydbQLQuery(query string) error {
+	// Create query context
+	context := &bydbql.QueryContext{
+		DefaultGroup:        viper.GetString("group"),
+		DefaultResourceType: bydbql.ResourceTypeAuto,
+	}
+
+	// Parse the query
+	parsed, parseErrors := bydbql.ParseQuery(query)
+	if len(parseErrors) > 0 {
+		return fmt.Errorf("parsing errors:\n  %s", strings.Join(parseErrors, "\n  "))
+	}
+
+	if parsed == nil {
+		return fmt.Errorf("failed to parse query")
+	}
+
+	// Merge context
+	if context.DefaultGroup != "" && len(parsed.Groups) == 0 {
+		parsed.Groups = []string{context.DefaultGroup}
+	}
+
+	// Translate to YAML
+	translator := bydbql.NewTranslator(context)
+	yamlData, err := translator.TranslateToMap(parsed)
+	if err != nil {
+		return fmt.Errorf("translation error: %w", err)
+	}
+	fmt.Printf("Translated YAML:\n%s\n", yamlData)
+
+	// Convert to JSON for REST API
+	jsonData, err := json.Marshal(yamlData)
+	if err != nil {
+		return fmt.Errorf("JSON conversion error: %w", err)
+	}
+
+	// Determine endpoint based on query type
+	endpoint, err := determineEndpoint(parsed)
+	if err != nil {
+		return err
+	}
+
+	// Execute the query via REST API
+	return executeRESTQuery(endpoint, jsonData)
+}
+
+// determineEndpoint determines the REST endpoint based on query type
+func determineEndpoint(parsed *bydbql.ParsedQuery) (string, error) {
+	switch parsed.Statement.(type) {
+	case *bydbql.SelectStatement:
+		// Determine resource type
+		resourceType := parsed.ResourceType
+		if resourceType == bydbql.ResourceTypeAuto {
+			// Default to stream if not specified
+			resourceType = bydbql.ResourceTypeStream
+		}
+
+		switch resourceType {
+		case bydbql.ResourceTypeStream:
+			return streamQueryPath, nil
+		case bydbql.ResourceTypeMeasure:
+			return measureQueryPath, nil
+		case bydbql.ResourceTypeTrace:
+			return traceQueryPath, nil
+		case bydbql.ResourceTypeProperty:
+			return propertyQueryPath, nil
+		default:
+			return "", fmt.Errorf("unsupported resource type: %s", resourceType.String())
+		}
+
+	case *bydbql.TopNStatement:
+		return topnQueryPath, nil
+
+	default:
+		return "", fmt.Errorf("unsupported statement type")
+	}
+}
+
+// executeRESTQuery executes the query via REST API
+func executeRESTQuery(endpoint string, jsonData []byte) error {
+	// Create REST request using existing infrastructure
+	reqBodyData := reqBody{
+		data: jsonData,
+	}
+
+	// Use existing rest function with appropriate parameters
+	return rest(
+		func() ([]reqBody, error) {
+			return []reqBody{reqBodyData}, nil
+		},
+		func(req request) (*resty.Response, error) {
+			url := viper.GetString("addr") + endpoint
+			resp, err := req.req.
+				SetHeader("Content-Type", "application/json").
+				SetBody(req.data).
+				Post(url)
+			return resp, err
+		},
+		yamlPrinter,
+		enableTLS,
+		insecure,
+		cert,
+	)
+}
+
+// printHelp prints help information for the interactive mode
+func printHelp() {
+	fmt.Println("BanyanDB Query Language (BydbQL) Help")
+	fmt.Println("=====================================")
+	fmt.Println()
+	fmt.Println("BydbQL supports SQL-like syntax for querying BanyanDB resources:")
+	fmt.Println()
+	fmt.Println("STREAM QUERIES:")
+	fmt.Println("  SELECT * FROM STREAM sw WHERE service_id = 'webapp';")
+	fmt.Println("  SELECT trace_id, service_id FROM STREAM sw TIME > '-30m';")
+	fmt.Println("  SELECT * FROM STREAM sw TIME BETWEEN '2023-01-01T00:00:00Z' AND '2023-01-02T00:00:00Z';")
+	fmt.Println()
+	fmt.Println("MEASURE QUERIES:")
+	fmt.Println("  SELECT region, SUM(latency) FROM MEASURE service_cpm GROUP BY region;")
+	fmt.Println("  SELECT TOP 10 instance, cpu_usage FROM MEASURE instance_metrics ORDER BY cpu_usage DESC;")
+	fmt.Println("  SELECT AVG(response_time) FROM MEASURE http_metrics WHERE region = 'us-west';")
+	fmt.Println()
+	fmt.Println("TRACE QUERIES:")
+	fmt.Println("  SELECT trace_id, service_id FROM TRACE sw_trace WHERE status = 'error';")
+	fmt.Println("  SELECT () FROM TRACE sw_trace WHERE service_id = 'webapp' LIMIT 100;")
+	fmt.Println()
+	fmt.Println("PROPERTY QUERIES:")
+	fmt.Println("  SELECT ip, owner FROM PROPERTY server_metadata WHERE datacenter = 'dc-101';")
+	fmt.Println("  SELECT * FROM PROPERTY server_metadata WHERE ID = 'server-123';")
+	fmt.Println()
+	fmt.Println("TOP-N QUERIES:")
+	fmt.Println("  SHOW TOP 10 FROM MEASURE service_latency ORDER BY value DESC;")
+	fmt.Println("  SHOW TOP 5 FROM MEASURE service_errors WHERE status_code = '500';")
+	fmt.Println()
+	fmt.Println("TIME FORMATS:")
+	fmt.Println("  Absolute: '2023-01-01T00:00:00Z' (RFC3339)")
+	fmt.Println("  Relative: '-30m', '2h', '-1d', '-1w', 'now'")
+	fmt.Println()
+	fmt.Println("OPERATORS:")
+	fmt.Println("  =, !=, >, <, >=, <=, IN, NOT IN, HAVING, NOT HAVING, MATCH")
+	fmt.Println("  AND, OR for combining conditions")
+	fmt.Println()
+	fmt.Println("SPECIAL COMMANDS:")
+	fmt.Println("  help     - Show this help")
+	fmt.Println("  exit     - Exit the shell")
+	fmt.Println("  quit     - Exit the shell")
+	fmt.Println("  \\q       - Exit the shell")
+	fmt.Println()
+	fmt.Println("QUERY TRACING:")
+	fmt.Println("  Add 'WITH QUERY_TRACE' to any query for distributed tracing")
+	fmt.Println()
+	fmt.Println("NOTE: Queries can span multiple lines and should end with semicolon (;)")
+}
+
+// printQueryResult prints the query result in YAML format
+func printQueryResult(data []byte) error {
+	// The data is already in JSON format from the REST response
+	// Convert it to YAML for better readability
+	var jsonData interface{}
+	if err := json.Unmarshal(data, &jsonData); err != nil {
+		// If JSON parsing fails, print raw data
+		fmt.Println(string(data))
+		return nil
+	}
+
+	yamlData, err := yaml.Marshal(jsonData)
+	if err != nil {
+		// If YAML conversion fails, print JSON data
+		fmt.Println(string(data))
+		return nil
+	}
+
+	fmt.Print(string(yamlData))
+	return nil
+}
+
diff --git a/bydbctl/internal/cmd/bydbql_test.go b/bydbctl/internal/cmd/bydbql_test.go
new file mode 100644
index 00000000..1b13e792
--- /dev/null
+++ b/bydbctl/internal/cmd/bydbql_test.go
@@ -0,0 +1,345 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package cmd
+
+import (
+	"bytes"
+	"strings"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+	"github.com/zenizh/go-capturer"
+	"sigs.k8s.io/yaml"
+
+	"github.com/apache/skywalking-banyandb/pkg/bydbql"
+	"github.com/apache/skywalking-banyandb/pkg/test/flags"
+	"github.com/apache/skywalking-banyandb/pkg/test/setup"
+)
+
+const httpSchema = "http://"
+
+var _ = Describe("BydbQL Command", func() {
+	var addr string
+	var deferFunc func()
+	var rootCmd *cobra.Command
+
+	BeforeEach(func() {
+		_, addr, deferFunc = setup.EmptyStandalone()
+		addr = httpSchema + addr
+
+		// Reset viper for clean test state
+		viper.Reset()
+		viper.Set("addr", addr)
+		viper.Set("group", "default")
+
+		// Create root command with all subcommands
+		rootCmd = &cobra.Command{Use: "root"}
+		RootCmdFlags(rootCmd)
+	})
+
+	AfterEach(func() {
+		deferFunc()
+	})
+
+	It("creates command with correct structure", func() {
+		// Find bydbql command in root commands
+		var bydbqlCmd *cobra.Command
+		for _, cmd := range rootCmd.Commands() {
+			if cmd.Use == "bydbql" {
+				bydbqlCmd = cmd
+				break
+			}
+		}
+
+		Expect(bydbqlCmd).NotTo(BeNil())
+		Expect(bydbqlCmd.Use).To(Equal("bydbql"))
+
+		// Test subcommands
+		subCommands := bydbqlCmd.Commands()
+		Expect(subCommands).To(HaveLen(2))
+
+		// Check that the expected subcommands exist (order may vary)
+		subCommandNames := make([]string, len(subCommands))
+		for i, cmd := range subCommands {
+			subCommandNames[i] = cmd.Use
+		}
+		Expect(subCommandNames).To(ContainElements("interactive", "query"))
+	})
+})
+
+var _ = Describe("Endpoint Determination", func() {
+	DescribeTable("determines correct endpoint for different query types",
+		func(query, expectedEndpoint string) {
+			parsed, errors := bydbql.ParseQuery(query)
+			Expect(errors).To(BeEmpty())
+
+			endpoint, err := determineEndpoint(parsed)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(endpoint).To(Equal(expectedEndpoint))
+		},
+		Entry("stream query", "SELECT * FROM STREAM sw", streamQueryPath),
+		Entry("measure query", "SELECT * FROM MEASURE metrics", measureQueryPath),
+		Entry("trace query", "SELECT * FROM TRACE traces", traceQueryPath),
+		Entry("property query", "SELECT * FROM PROPERTY props", propertyQueryPath),
+		Entry("top-n query", "SHOW TOP 10 FROM MEASURE metrics", topnQueryPath),
+	)
+})
+
+var _ = Describe("Query Execution Parse Errors", func() {
+	DescribeTable("handles parsing errors correctly",
+		func(query string) {
+			err := executeBydbQLQuery(query)
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("parsing errors"))
+		},
+		Entry("incomplete SELECT", "SELECT"),
+		Entry("missing resource", "SELECT * FROM"),
+		Entry("missing N in TOP", "SHOW TOP FROM MEASURE metrics"),
+	)
+})
+
+var _ = Describe("Valid Query Parsing", func() {
+	DescribeTable("parses valid queries correctly",
+		func(query string) {
+			// Parse and validate query structure
+			parsed, errors := bydbql.ParseQuery(query)
+			Expect(errors).To(BeEmpty())
+			Expect(parsed).NotTo(BeNil())
+
+			// Validate endpoint determination
+			_, err := determineEndpoint(parsed)
+			Expect(err).NotTo(HaveOccurred())
+		},
+		Entry("stream query", "SELECT * FROM STREAM sw"),
+		Entry("measure query with aggregation", "SELECT region, SUM(latency) FROM MEASURE metrics GROUP BY region"),
+		Entry("top-n query", "SHOW TOP 10 FROM MEASURE service_latency ORDER BY value DESC"),
+		Entry("trace query with condition", "SELECT * FROM TRACE traces WHERE status = 'error'"),
+		Entry("property query with condition", "SELECT ip, owner FROM PROPERTY metadata WHERE datacenter = 'dc-1'"),
+	)
+})
+
+var _ = Describe("Help Command", func() {
+	var addr string
+	var deferFunc func()
+	var rootCmd *cobra.Command
+
+	BeforeEach(func() {
+		_, addr, deferFunc = setup.EmptyStandalone()
+		addr = httpSchema + addr
+
+		// Reset viper for clean test state
+		viper.Reset()
+		viper.Set("addr", addr)
+		viper.Set("group", "default")
+
+		// Create root command with all subcommands
+		rootCmd = &cobra.Command{Use: "root"}
+		RootCmdFlags(rootCmd)
+	})
+
+	AfterEach(func() {
+		deferFunc()
+	})
+
+	It("displays help information correctly", func() {
+		// Capture help output
+		var output bytes.Buffer
+		rootCmd.SetOut(&output)
+
+		// Execute help for bydbql command
+		rootCmd.SetArgs([]string{"bydbql", "--help"})
+		err := rootCmd.Execute()
+		Expect(err).NotTo(HaveOccurred())
+
+		helpOutput := output.String()
+
+		// Verify help content includes key information
+		expectedContent := []string{
+			"BanyanDB Query Language",
+			"BydbQL",
+			"interactive",
+			"query",
+			"SQL-like",
+		}
+
+		for _, expected := range expectedContent {
+			Expect(helpOutput).To(ContainSubstring(expected))
+		}
+	})
+})
+
+var _ = Describe("BydbQL Integration", func() {
+	DescribeTable("full pipeline processing",
+		func(query string, resourceType bydbql.ResourceType) {
+			// Step 1: Parse query
+			parsed, errors := bydbql.ParseQuery(query)
+			Expect(errors).To(BeEmpty())
+			Expect(parsed).NotTo(BeNil())
+
+			// Step 2: Validate resource type detection
+			if parsed.ResourceType != resourceType {
+				// For auto-detected resource types, this might be expected
+				GinkgoWriter.Printf("Resource type: expected %s, got %s (may be auto-detected)",
+					resourceType.String(), parsed.ResourceType.String())
+			}
+
+			// Step 3: Translate to YAML
+			context := &bydbql.QueryContext{
+				DefaultGroup: "default",
+			}
+			translator := bydbql.NewTranslator(context)
+			yamlData, err := translator.TranslateToYAML(parsed)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(yamlData).NotTo(BeEmpty())
+
+			// Step 4: Determine correct endpoint
+			endpoint, err := determineEndpoint(parsed)
+			Expect(err).NotTo(HaveOccurred())
+
+			// Step 5: Validate endpoint matches expected resource type
+			expectedEndpoints := map[bydbql.ResourceType]string{
+				bydbql.ResourceTypeStream:   streamQueryPath,
+				bydbql.ResourceTypeMeasure:  measureQueryPath,
+				bydbql.ResourceTypeTrace:    traceQueryPath,
+				bydbql.ResourceTypeProperty: propertyQueryPath,
+			}
+
+			// For Top-N queries, always use measure endpoint
+			if _, isTopN := parsed.Statement.(*bydbql.TopNStatement); isTopN {
+				Expect(endpoint).To(Equal(topnQueryPath))
+			} else if expectedEndpoint, ok := expectedEndpoints[resourceType]; ok {
+				Expect(endpoint).To(Equal(expectedEndpoint))
+			}
+
+			// Step 6: Validate YAML structure
+			var yamlMap map[string]interface{}
+			err = yaml.Unmarshal(yamlData, &yamlMap)
+			Expect(err).NotTo(HaveOccurred())
+
+			// Basic validation: should have name or resource identifier
+			Expect(yamlMap["Name"] != nil || yamlMap["topN"] != nil).To(BeTrue())
+
+			GinkgoWriter.Printf("Successfully processed query: %s", query)
+			GinkgoWriter.Printf("Generated YAML length: %d bytes", len(yamlData))
+			GinkgoWriter.Printf("Target endpoint: %s", endpoint)
+		},
+		Entry("stream query full pipeline",
+			"SELECT trace_id, service_id FROM STREAM sw WHERE service_id = 'webapp' TIME > '-30m' LIMIT 100",
+			bydbql.ResourceTypeStream),
+		Entry("measure query full pipeline",
+			"SELECT region, AVG(latency) FROM MEASURE service_metrics WHERE service = 'auth' GROUP BY region",
+			bydbql.ResourceTypeMeasure),
+		Entry("topn query full pipeline",
+			"SHOW TOP 5 FROM MEASURE error_count WHERE status_code = '500'",
+			bydbql.ResourceTypeMeasure),
+		Entry("trace query full pipeline",
+			"SELECT * FROM TRACE app_traces WHERE operation_name = 'GET /api/users' TIME BETWEEN '-1h' AND 'now'",
+			bydbql.ResourceTypeTrace),
+		Entry("property query full pipeline",
+			"SELECT ip, region FROM PROPERTY server_info WHERE ID = 'server-1'",
+			bydbql.ResourceTypeProperty),
+	)
+})
+
+var _ = Describe("Command Line Flags", func() {
+	var addr string
+	var deferFunc func()
+	var rootCmd *cobra.Command
+
+	BeforeEach(func() {
+		_, addr, deferFunc = setup.EmptyStandalone()
+		addr = httpSchema + addr
+
+		// Reset viper for clean test state
+		viper.Reset()
+		viper.Set("addr", addr)
+		viper.Set("group", "default")
+
+		// Create root command with all subcommands
+		rootCmd = &cobra.Command{Use: "root"}
+		RootCmdFlags(rootCmd)
+	})
+
+	AfterEach(func() {
+		deferFunc()
+	})
+
+	DescribeTable("handles command line flags correctly",
+		func(args []string, expectError bool) {
+			// Prepend "bydbql" to the args since we're testing the bydbql subcommand
+			fullArgs := append([]string{"bydbql"}, args...)
+			rootCmd.SetArgs(fullArgs)
+
+			// We're testing command structure, not execution
+			// So we expect parsing to succeed even if execution might fail
+			err := rootCmd.ParseFlags(fullArgs)
+
+			if expectError {
+				Expect(err).To(HaveOccurred())
+			} else {
+				Expect(err).NotTo(HaveOccurred())
+			}
+		},
+		Entry("valid query command with file", []string{"query", "-f", "test.sql"}, false),
+		Entry("valid interactive command", []string{"interactive"}, false),
+		Entry("alias i for interactive", []string{"i"}, false),
+	)
+})
+
+var _ = Describe("BydbQL Query Execution", func() {
+	var addr string
+	var deferFunc func()
+	var rootCmd *cobra.Command
+
+	BeforeEach(func() {
+		_, addr, deferFunc = setup.EmptyStandalone()
+		addr = httpSchema + addr
+
+		// Reset viper for clean test state
+		viper.Reset()
+		viper.Set("addr", addr)
+		viper.Set("group", "default")
+
+		// Create root command with all subcommands
+		rootCmd = &cobra.Command{Use: "root"}
+		RootCmdFlags(rootCmd)
+	})
+
+	AfterEach(func() {
+		deferFunc()
+	})
+
+	It("executes simple query successfully", func() {
+		// Test a simple query execution
+		rootCmd.SetArgs([]string{"bydbql", "query", "-f", "-"})
+		rootCmd.SetIn(strings.NewReader("SELECT * FROM STREAM sw LIMIT 1"))
+
+		executeQuery := func() string {
+			return capturer.CaptureStdout(func() {
+				err := rootCmd.Execute()
+				if err != nil {
+					GinkgoWriter.Printf("execution fails: %v", err)
+				}
+			})
+		}
+
+		Eventually(executeQuery, flags.EventuallyTimeout).ShouldNot(ContainSubstring("error"))
+	})
+})
diff --git a/bydbctl/internal/cmd/root.go b/bydbctl/internal/cmd/root.go
index c83f2752..095a995d 100644
--- a/bydbctl/internal/cmd/root.go
+++ b/bydbctl/internal/cmd/root.go
@@ -80,7 +80,7 @@ func RootCmdFlags(command *cobra.Command) {
 	_ = viper.BindPFlag("password", command.PersistentFlags().Lookup("password"))
 
 	command.AddCommand(newGroupCmd(), newUseCmd(), newStreamCmd(), newMeasureCmd(), newTopnCmd(),
-		newIndexRuleCmd(), newIndexRuleBindingCmd(), newPropertyCmd(), newTraceCmd(), newHealthCheckCmd(), newAnalyzeCmd())
+		newIndexRuleCmd(), newIndexRuleBindingCmd(), newPropertyCmd(), newTraceCmd(), newHealthCheckCmd(), newAnalyzeCmd(), newBydbQLCmd())
 }
 
 func init() {
diff --git a/pkg/bydbql/ast.go b/pkg/bydbql/ast.go
new file mode 100644
index 00000000..0348d1e1
--- /dev/null
+++ b/pkg/bydbql/ast.go
@@ -0,0 +1,327 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// Package bydbql provides BanyanDB Query Language (BydbQL) parsing and translation capabilities.
+package bydbql
+
+import "time"
+
+// Node represents a node in the Abstract Syntax Tree
+type Node interface {
+	String() string
+}
+
+// Statement represents a BydbQL statement
+type Statement interface {
+	Node
+	statementNode()
+}
+
+// Expression represents a BydbQL expression
+type Expression interface {
+	Node
+	expressionNode()
+}
+
+// SelectStatement represents a SELECT statement
+type SelectStatement struct {
+	Projection  *Projection
+	From        *FromClause
+	Time        *TimeCondition
+	Where       *WhereClause
+	GroupBy     *GroupByClause
+	OrderBy     *OrderByClause
+	Limit       *int
+	Offset      *int
+	QueryTrace  bool
+}
+
+func (s *SelectStatement) statementNode() {}
+func (s *SelectStatement) String() string { return "SELECT" }
+
+// TopNStatement represents a SHOW TOP N statement
+type TopNStatement struct {
+	TopN        int
+	From        *FromClause
+	Time        *TimeCondition
+	Where       *WhereClause
+	AggregateBy *AggregateFunction
+	OrderBy     *OrderByClause
+	QueryTrace  bool
+}
+
+func (s *TopNStatement) statementNode() {}
+func (s *TopNStatement) String() string { return "SHOW TOP" }
+
+// Projection represents the SELECT projection clause
+type Projection struct {
+	All     bool
+	Empty   bool // SELECT () for traces
+	Columns []*Column
+	TopN    *TopNProjection
+}
+
+func (p *Projection) expressionNode() {}
+func (p *Projection) String() string { return "projection" }
+
+// TopNProjection represents TOP N projection
+type TopNProjection struct {
+	N           int
+	Projection  *Projection
+}
+
+func (p *TopNProjection) expressionNode() {}
+func (p *TopNProjection) String() string { return "TOP N" }
+
+// Column represents a column in projection
+type Column struct {
+	Name      string
+	Type      ColumnType // ::tag or ::field disambiguator
+	Function  *AggregateFunction
+}
+
+func (c *Column) expressionNode() {}
+func (c *Column) String() string { return c.Name }
+
+// ColumnType represents the type disambiguator for columns
+type ColumnType int
+
+const (
+	ColumnTypeAuto ColumnType = iota
+	ColumnTypeTag
+	ColumnTypeField
+)
+
+// AggregateFunction represents aggregate functions (SUM, MEAN, COUNT, MAX, MIN)
+type AggregateFunction struct {
+	Function string // SUM, MEAN, COUNT, MAX, MIN
+	Column   string
+}
+
+func (f *AggregateFunction) expressionNode() {}
+func (f *AggregateFunction) String() string { return f.Function }
+
+// FromClause represents the FROM clause
+type FromClause struct {
+	ResourceType ResourceType
+	ResourceName string
+	Groups       []string
+}
+
+func (f *FromClause) expressionNode() {}
+func (f *FromClause) String() string { return "FROM" }
+
+// ResourceType represents the type of resource being queried
+type ResourceType int
+
+const (
+	ResourceTypeAuto ResourceType = iota
+	ResourceTypeStream
+	ResourceTypeMeasure
+	ResourceTypeTrace
+	ResourceTypeProperty
+)
+
+func (r ResourceType) String() string {
+	switch r {
+	case ResourceTypeStream:
+		return "STREAM"
+	case ResourceTypeMeasure:
+		return "MEASURE"
+	case ResourceTypeTrace:
+		return "TRACE"
+	case ResourceTypeProperty:
+		return "PROPERTY"
+	default:
+		return "AUTO"
+	}
+}
+
+// TimeCondition represents TIME conditions
+type TimeCondition struct {
+	Operator  TimeOperator
+	Timestamp string
+	Begin     string // for BETWEEN
+	End       string // for BETWEEN
+}
+
+func (t *TimeCondition) expressionNode() {}
+func (t *TimeCondition) String() string { return "TIME" }
+
+// TimeOperator represents time comparison operators
+type TimeOperator int
+
+const (
+	TimeOpEqual TimeOperator = iota
+	TimeOpGreater
+	TimeOpLess
+	TimeOpGreaterEqual
+	TimeOpLessEqual
+	TimeOpBetween
+)
+
+// WhereClause represents the WHERE clause
+type WhereClause struct {
+	Conditions []*Condition
+	Logic      LogicOperator
+}
+
+func (w *WhereClause) expressionNode() {}
+func (w *WhereClause) String() string { return "WHERE" }
+
+// Condition represents a single condition in WHERE clause
+type Condition struct {
+	Left     string
+	Operator BinaryOperator
+	Right    *Value
+	Values   []*Value // for IN, NOT IN
+	Logic    LogicOperator // for combining conditions
+}
+
+func (c *Condition) expressionNode() {}
+func (c *Condition) String() string { return c.Left }
+
+// BinaryOperator represents binary operators
+type BinaryOperator int
+
+const (
+	OpEqual BinaryOperator = iota
+	OpNotEqual
+	OpGreater
+	OpLess
+	OpGreaterEqual
+	OpLessEqual
+	OpIn
+	OpNotIn
+	OpHaving
+	OpNotHaving
+	OpMatch
+)
+
+func (op BinaryOperator) String() string {
+	switch op {
+	case OpEqual:
+		return "="
+	case OpNotEqual:
+		return "!="
+	case OpGreater:
+		return ">"
+	case OpLess:
+		return "<"
+	case OpGreaterEqual:
+		return ">="
+	case OpLessEqual:
+		return "<="
+	case OpIn:
+		return "IN"
+	case OpNotIn:
+		return "NOT IN"
+	case OpHaving:
+		return "HAVING"
+	case OpNotHaving:
+		return "NOT HAVING"
+	case OpMatch:
+		return "MATCH"
+	default:
+		return "UNKNOWN"
+	}
+}
+
+// LogicOperator represents logical operators (AND, OR)
+type LogicOperator int
+
+const (
+	LogicAnd LogicOperator = iota
+	LogicOr
+)
+
+func (op LogicOperator) String() string {
+	switch op {
+	case LogicAnd:
+		return "AND"
+	case LogicOr:
+		return "OR"
+	default:
+		return "UNKNOWN"
+	}
+}
+
+// Value represents a value in conditions
+type Value struct {
+	Type     ValueType
+	StringVal string
+	Integer   int64
+	IsNull    bool
+}
+
+func (v *Value) expressionNode() {}
+func (v *Value) String() string {
+	if v.IsNull {
+		return "NULL"
+	}
+	switch v.Type {
+	case ValueTypeString:
+		return v.StringVal
+	case ValueTypeInteger:
+		return string(rune(v.Integer))
+	default:
+		return ""
+	}
+}
+
+// ValueType represents the type of a value
+type ValueType int
+
+const (
+	ValueTypeString ValueType = iota
+	ValueTypeInteger
+	ValueTypeNull
+)
+
+// GroupByClause represents GROUP BY clause
+type GroupByClause struct {
+	Columns []string
+}
+
+func (g *GroupByClause) expressionNode() {}
+func (g *GroupByClause) String() string { return "GROUP BY" }
+
+// OrderByClause represents ORDER BY clause
+type OrderByClause struct {
+	Column string
+	Desc   bool
+}
+
+func (o *OrderByClause) expressionNode() {}
+func (o *OrderByClause) String() string { return "ORDER BY" }
+
+// ParsedQuery represents a parsed BydbQL query with metadata
+type ParsedQuery struct {
+	Statement    Statement
+	ResourceType ResourceType
+	ResourceName string
+	Groups       []string
+	Context      *QueryContext
+}
+
+// QueryContext provides execution context for queries
+type QueryContext struct {
+	DefaultGroup        string
+	DefaultResourceName string
+	DefaultResourceType ResourceType
+	CurrentTime         time.Time
+}
\ No newline at end of file
diff --git a/pkg/bydbql/bydbql_suite_test.go b/pkg/bydbql/bydbql_suite_test.go
new file mode 100644
index 00000000..623ecab8
--- /dev/null
+++ b/pkg/bydbql/bydbql_suite_test.go
@@ -0,0 +1,30 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package bydbql_test
+
+import (
+	"testing"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+)
+
+func TestBydbql(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Bydbql Suite")
+}
\ No newline at end of file
diff --git a/pkg/bydbql/bydbql_test.go b/pkg/bydbql/bydbql_test.go
new file mode 100644
index 00000000..57e8d8f8
--- /dev/null
+++ b/pkg/bydbql/bydbql_test.go
@@ -0,0 +1,465 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package bydbql_test
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"sigs.k8s.io/yaml"
+
+	. "github.com/apache/skywalking-banyandb/pkg/bydbql"
+)
+
+var _ = Describe("Lexer", func() {
+	DescribeTable("tokenizes queries correctly",
+		func(input string, expected []TokenType) {
+			lexer := NewLexer(input)
+			tokens := lexer.GetAllTokens()
+
+			Expect(tokens).To(HaveLen(len(expected)))
+
+			for i, expectedToken := range expected {
+				Expect(tokens[i].Type).To(Equal(expectedToken),
+					"token %d: expected %s, got %s", i, expectedToken.String(), tokens[i].Type.String())
+			}
+		},
+		Entry("basic SELECT query",
+			"SELECT * FROM STREAM sw",
+			[]TokenType{TokenSelect, TokenStar, TokenFrom, TokenStream, TokenIdentifier, TokenEOF}),
+		Entry("SELECT with WHERE clause",
+			"SELECT trace_id FROM STREAM sw WHERE service_id = 'webapp'",
+			[]TokenType{
+				TokenSelect, TokenIdentifier, TokenFrom, TokenStream, TokenIdentifier,
+				TokenWhere, TokenIdentifier, TokenEqual, TokenString, TokenEOF,
+			}),
+		Entry("TOP N query",
+			"SHOW TOP 10 FROM MEASURE service_latency",
+			[]TokenType{TokenShow, TokenTop, TokenInteger, TokenFrom, TokenMeasure, TokenIdentifier, TokenEOF}),
+		Entry("aggregate function",
+			"SELECT SUM(latency) FROM MEASURE metrics",
+			[]TokenType{
+				TokenSelect, TokenSum, TokenLeftParen, TokenIdentifier, TokenRightParen,
+				TokenFrom, TokenMeasure, TokenIdentifier, TokenEOF,
+			}),
+		Entry("time range",
+			"TIME BETWEEN '2023-01-01T00:00:00Z' AND '2023-01-02T00:00:00Z'",
+			[]TokenType{TokenTime, TokenBetween, TokenString, TokenAnd, TokenString, TokenEOF}),
+		Entry("column type disambiguator",
+			"SELECT status::tag, status::field FROM MEASURE metrics",
+			[]TokenType{
+				TokenSelect, TokenIdentifier, TokenDoubleColon, TokenIdentifier,
+				TokenComma, TokenIdentifier, TokenDoubleColon, TokenIdentifier,
+				TokenFrom, TokenMeasure, TokenIdentifier, TokenEOF,
+			}),
+	)
+})
+
+var _ = Describe("Parser", func() {
+	Describe("valid queries", func() {
+		It("parses simple SELECT *", func() {
+			parsed, errors := ParseQuery("SELECT *")
+			Expect(errors).To(BeEmpty())
+			Expect(parsed).NotTo(BeNil())
+
+			stmt, ok := parsed.Statement.(*SelectStatement)
+			Expect(ok).To(BeTrue())
+			Expect(stmt.Projection).NotTo(BeNil())
+			Expect(stmt.Projection.All).To(BeTrue())
+		})
+
+		It("parses SELECT with FROM clause", func() {
+			parsed, errors := ParseQuery("SELECT * FROM STREAM sw")
+			Expect(errors).To(BeEmpty())
+			Expect(parsed).NotTo(BeNil())
+
+			stmt, ok := parsed.Statement.(*SelectStatement)
+			Expect(ok).To(BeTrue())
+			Expect(stmt.From).NotTo(BeNil())
+			Expect(stmt.From.ResourceType).To(Equal(ResourceTypeStream))
+			Expect(stmt.From.ResourceName).To(Equal("sw"))
+		})
+
+		It("parses SELECT with WHERE clause", func() {
+			parsed, errors := ParseQuery("SELECT trace_id FROM STREAM sw WHERE service_id = 'webapp'")
+			Expect(errors).To(BeEmpty())
+			Expect(parsed).NotTo(BeNil())
+
+			stmt, ok := parsed.Statement.(*SelectStatement)
+			Expect(ok).To(BeTrue())
+			Expect(stmt.Where).NotTo(BeNil())
+			Expect(stmt.Where.Conditions).To(HaveLen(1))
+			Expect(stmt.Where.Conditions[0].Left).To(Equal("service_id"))
+		})
+
+		It("parses SELECT with TIME condition", func() {
+			parsed, errors := ParseQuery("SELECT * FROM STREAM sw TIME > '-30m'")
+			Expect(errors).To(BeEmpty())
+			Expect(parsed).NotTo(BeNil())
+
+			stmt, ok := parsed.Statement.(*SelectStatement)
+			Expect(ok).To(BeTrue())
+			Expect(stmt.Time).NotTo(BeNil())
+			Expect(stmt.Time.Operator).To(Equal(TimeOpGreater))
+		})
+
+		It("parses SELECT with GROUP BY", func() {
+			parsed, errors := ParseQuery("SELECT region, SUM(latency) FROM MEASURE metrics GROUP BY region")
+			Expect(errors).To(BeEmpty())
+			Expect(parsed).NotTo(BeNil())
+
+			stmt, ok := parsed.Statement.(*SelectStatement)
+			Expect(ok).To(BeTrue())
+			Expect(stmt.GroupBy).NotTo(BeNil())
+			Expect(stmt.GroupBy.Columns).To(HaveLen(1))
+			Expect(stmt.GroupBy.Columns[0]).To(Equal("region"))
+		})
+
+		It("parses TOP N statement", func() {
+			parsed, errors := ParseQuery("SHOW TOP 10 FROM MEASURE service_latency ORDER BY value DESC")
+			Expect(errors).To(BeEmpty())
+			Expect(parsed).NotTo(BeNil())
+
+			stmt, ok := parsed.Statement.(*TopNStatement)
+			Expect(ok).To(BeTrue())
+			Expect(stmt.TopN).To(Equal(10))
+			Expect(stmt.OrderBy).NotTo(BeNil())
+			Expect(stmt.OrderBy.Desc).To(BeTrue())
+		})
+
+		It("parses empty projection for traces", func() {
+			parsed, errors := ParseQuery("SELECT () FROM TRACE sw_trace")
+			Expect(errors).To(BeEmpty())
+			Expect(parsed).NotTo(BeNil())
+
+			stmt, ok := parsed.Statement.(*SelectStatement)
+			Expect(ok).To(BeTrue())
+			Expect(stmt.Projection).NotTo(BeNil())
+			Expect(stmt.Projection.Empty).To(BeTrue())
+		})
+
+		It("parses WITH QUERY_TRACE", func() {
+			parsed, errors := ParseQuery("SELECT * FROM STREAM sw WITH QUERY_TRACE")
+			Expect(errors).To(BeEmpty())
+			Expect(parsed).NotTo(BeNil())
+
+			stmt, ok := parsed.Statement.(*SelectStatement)
+			Expect(ok).To(BeTrue())
+			Expect(stmt.QueryTrace).To(BeTrue())
+		})
+	})
+
+	Describe("invalid queries", func() {
+		It("handles invalid syntax", func() {
+			parsed, errors := ParseQuery("SELECT FROM")
+			Expect(errors).NotTo(BeEmpty())
+			Expect(parsed).To(BeNil())
+		})
+	})
+})
+
+var _ = Describe("Translator", 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 queries correctly",
+		func(input string, validateFunc func(map[string]any) bool) {
+			parsed, errors := ParseQuery(input)
+			Expect(errors).To(BeEmpty())
+			Expect(parsed).NotTo(BeNil())
+
+			translator := NewTranslator(context)
+			data, err := translator.TranslateToMap(parsed)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(validateFunc(data)).To(BeTrue(), "validation failed for data: %+v", data)
+		},
+		Entry("simple stream query",
+			"SELECT * FROM STREAM sw",
+			func(data map[string]any) bool {
+				return data["Name"] == "sw"
+			}),
+		Entry("measure query with aggregation",
+			"SELECT region, SUM(latency) FROM MEASURE metrics GROUP BY region",
+			func(data map[string]any) bool {
+				agg, ok := data["Agg"].(map[string]any)
+				return ok && agg["Function"] == "SUM" && agg["FieldName"] == "latency"
+			}),
+		Entry("time range query",
+			"SELECT * FROM STREAM sw TIME BETWEEN '2023-01-01T00:00:00Z' AND '2023-01-02T00:00:00Z'",
+			func(data map[string]any) bool {
+				timeRange, ok := data["TimeRange"].(map[string]any)
+				return ok && timeRange["Begin"] == "2023-01-01T00:00:00Z" &&
+					timeRange["End"] == "2023-01-02T00:00:00Z"
+			}),
+		Entry("relative time query",
+			"SELECT * FROM STREAM sw TIME > '-30m'",
+			func(data map[string]any) bool {
+				timeRange, ok := data["TimeRange"].(map[string]any)
+				return ok && timeRange["Begin"] != nil && timeRange["End"] != nil
+			}),
+		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 {
+					return false
+				}
+				first, ok := criteria[0].(map[string]any)
+				return ok && first["tagName"] == "service_id" && first["op"] == "BINARY_OP_EQ"
+			}),
+		Entry("TOP N query",
+			"SHOW TOP 10 FROM MEASURE service_latency ORDER BY value DESC",
+			func(data map[string]any) bool {
+				topN := data["TopN"]
+				fieldValueSort := data["FieldValueSort"]
+				return topN == float64(10) && fieldValueSort == "DESC"
+			}),
+		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
+			}),
+		Entry("query trace enabled",
+			"SELECT * FROM STREAM sw WITH QUERY_TRACE",
+			func(data map[string]any) bool {
+				return data["Trace"] == true
+			}),
+	)
+})
+
+var _ = Describe("Complex Queries", func() {
+	var context *QueryContext
+
+	BeforeEach(func() {
+		context = &QueryContext{
+			DefaultGroup: "default",
+			CurrentTime:  time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC),
+		}
+	})
+
+	complexQueries := []string{
+		// Stream queries
+		`SELECT trace_id, service_id, start_time
+		 FROM STREAM sw IN (default, updated)
+		 WHERE service_id = 'webapp' AND state = 1
+		 ORDER BY start_time DESC
+		 LIMIT 100`,
+
+		// Measure queries with complex projections
+		`SELECT region, SUM(latency)
+		 FROM MEASURE service_cpm IN (us-west, us-east)
+		 TIME BETWEEN '-2h' AND 'now'
+		 WHERE service = 'auth-service'
+		 GROUP BY region`,
+
+		// Trace queries with empty projection
+		`SELECT ()
+		 FROM TRACE sw_trace
+		 TIME > '-1h'
+		 WHERE status = 'error'
+		 WITH QUERY_TRACE
+		 LIMIT 50`,
+
+		// Property queries
+		`SELECT ip, region, owner
+		 FROM PROPERTY server_metadata IN (datacenter-1, datacenter-2)
+		 WHERE datacenter = 'dc-101'
+		 LIMIT 50`,
+
+		// Top-N queries
+		`SHOW TOP 5
+		 FROM MEASURE service_errors IN (production, staging)
+		 TIME BETWEEN '-24h' AND 'now'
+		 WHERE status_code = '500'
+		 ORDER BY value DESC`,
+	}
+
+	for i, query := range complexQueries {
+		It(fmt.Sprintf("parses and translates complex query %d", i+1), func() {
+			// Parse the query
+			parsed, errors := ParseQuery(query)
+			Expect(errors).To(BeEmpty())
+			Expect(parsed).NotTo(BeNil())
+
+			// Translate to YAML
+			translator := NewTranslator(context)
+			yamlData, err := translator.TranslateToYAML(parsed)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(yamlData).NotTo(BeEmpty())
+
+			// Validate YAML structure by unmarshaling
+			var result map[string]any
+			err = yaml.Unmarshal(yamlData, &result)
+			Expect(err).NotTo(HaveOccurred())
+		})
+	}
+})
+
+var _ = Describe("Error Handling", func() {
+	invalidQueries := []string{
+		"SELECT",                           // incomplete query
+		"SELECT * FROM",                    // missing resource
+		"SELECT * FROM INVALID sw",         // invalid resource type
+		"SHOW TOP FROM MEASURE metrics",    // missing N
+		"SELECT * WHERE service_id",        // incomplete condition
+		"TIME > '2023-01-01'",              // missing SELECT
+		"SELECT * FROM STREAM sw GROUP BY", // incomplete GROUP BY
+	}
+
+	for i, query := range invalidQueries {
+		It(fmt.Sprintf("handles invalid query %d", i+1), func() {
+			parsed, errors := ParseQuery(query)
+
+			// Should have parsing errors
+			Expect(errors).NotTo(BeEmpty(), "expected parsing errors for query: %s", query)
+
+			// Parsed query should be nil for invalid queries
+			Expect(parsed).To(BeNil(), "expected nil parsed query for invalid query: %s", query)
+		})
+	}
+})
+
+var _ = Describe("Time Format Parsing", func() {
+	var context *QueryContext
+
+	BeforeEach(func() {
+		context = &QueryContext{
+			CurrentTime: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC),
+		}
+	})
+
+	DescribeTable("parses time formats correctly in queries",
+		func(timeCondition string, validateFunc func(map[string]any) bool) {
+			query := fmt.Sprintf("SELECT * FROM STREAM sw TIME %s", timeCondition)
+			parsed, errors := ParseQuery(query)
+			Expect(errors).To(BeEmpty())
+			Expect(parsed).NotTo(BeNil())
+
+			translator := NewTranslator(context)
+			data, err := translator.TranslateToMap(parsed)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(validateFunc(data)).To(BeTrue(), "time validation failed for data: %+v", data)
+		},
+		Entry("absolute time range",
+			"BETWEEN '2023-01-01T10:00:00Z' AND '2023-01-01T11:00:00Z'",
+			func(data map[string]any) bool {
+				timeRange, ok := data["TimeRange"].(map[string]any)
+				return ok && timeRange["Begin"] == "2023-01-01T10:00:00Z" &&
+					timeRange["End"] == "2023-01-01T11:00:00Z"
+			}),
+		Entry("relative time condition",
+			"> '-30m'",
+			func(data map[string]any) bool {
+				timeRange, ok := data["TimeRange"].(map[string]any)
+				return ok && timeRange["Begin"] != nil && timeRange["End"] != nil
+			}),
+	)
+})
+
+var _ = Describe("Case Insensitivity", func() {
+	// Test that keywords are case-insensitive
+	queries := []string{
+		"SELECT * FROM STREAM sw",
+		"select * from stream sw",
+		"Select * From Stream sw",
+		"sElEcT * fRoM sTrEaM sw",
+	}
+
+	for i, query := range queries {
+		It(fmt.Sprintf("parses case-insensitive query %d", i+1), func() {
+			parsed, errors := ParseQuery(query)
+			Expect(errors).To(BeEmpty(), "parsing errors for query '%s': %v", query, errors)
+			Expect(parsed).NotTo(BeNil(), "failed to parse case-insensitive query: %s", query)
+		})
+	}
+})
+
+// Benchmark tests
+func BenchmarkLexer(b *testing.B) {
+	query := `SELECT trace_id, service_id, start_time
+			  FROM STREAM sw IN (default, updated)
+			  WHERE service_id = 'webapp' AND state = 1
+			  ORDER BY start_time DESC
+			  LIMIT 100`
+
+	for i := 0; i < b.N; i++ {
+		lexer := NewLexer(query)
+		_ = lexer.GetAllTokens()
+	}
+}
+
+func BenchmarkParser(b *testing.B) {
+	query := `SELECT trace_id, service_id, start_time
+			  FROM STREAM sw IN (default, updated)
+			  WHERE service_id = 'webapp' AND state = 1
+			  ORDER BY start_time DESC
+			  LIMIT 100`
+
+	for i := 0; i < b.N; i++ {
+		_, _ = ParseQuery(query)
+	}
+}
+
+func BenchmarkTranslator(b *testing.B) {
+	query := `SELECT trace_id, service_id, start_time
+			  FROM STREAM sw IN (default, updated)
+			  WHERE service_id = 'webapp' AND state = 1
+			  ORDER BY start_time DESC
+			  LIMIT 100`
+
+	context := &QueryContext{
+		CurrentTime: time.Now(),
+	}
+
+	// Parse once
+	parsed, _ := ParseQuery(query)
+	translator := NewTranslator(context)
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _ = translator.TranslateToYAML(parsed)
+	}
+}
+
+func BenchmarkEndToEnd(b *testing.B) {
+	query := `SELECT trace_id, service_id, start_time
+			  FROM STREAM sw IN (default, updated)
+			  WHERE service_id = 'webapp' AND state = 1
+			  ORDER BY start_time DESC
+			  LIMIT 100`
+
+	context := &QueryContext{
+		CurrentTime: time.Now(),
+	}
+
+	for i := 0; i < b.N; i++ {
+		_, _, _ = TranslateQuery(query, context)
+	}
+}
diff --git a/pkg/bydbql/lexer.go b/pkg/bydbql/lexer.go
new file mode 100644
index 00000000..a3acee3f
--- /dev/null
+++ b/pkg/bydbql/lexer.go
@@ -0,0 +1,608 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package bydbql
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+	"unicode"
+	"unicode/utf8"
+)
+
+// TokenType represents the type of a token
+type TokenType int
+
+const (
+	// Special tokens
+	TokenEOF TokenType = iota
+	TokenIllegal
+
+	// Literals
+	TokenIdentifier
+	TokenString
+	TokenInteger
+
+	// Keywords
+	TokenSelect
+	TokenFrom
+	TokenWhere
+	TokenOrderBy
+	TokenGroupBy
+	TokenLimit
+	TokenOffset
+	TokenTime
+	TokenBetween
+	TokenAnd
+	TokenOr
+	TokenIn
+	TokenNotIn
+	TokenHaving
+	TokenNotHaving
+	TokenMatch
+	TokenShow
+	TokenTop
+	TokenStream
+	TokenMeasure
+	TokenTrace
+	TokenProperty
+	TokenAsc
+	TokenDesc
+	TokenWith
+	TokenQueryTrace
+	TokenNull
+	TokenAggregateBy
+
+	// Aggregate functions
+	TokenSum
+	TokenMean
+	TokenAvg
+	TokenCount
+	TokenMax
+	TokenMin
+
+	// Operators
+	TokenEqual
+	TokenNotEqual
+	TokenGreater
+	TokenLess
+	TokenGreaterEqual
+	TokenLessEqual
+
+	// Punctuation
+	TokenComma
+	TokenSemicolon
+	TokenLeftParen
+	TokenRightParen
+	TokenDot
+	TokenDoubleColon
+
+	// Special literals
+	TokenStar
+	TokenBy
+	TokenValue
+)
+
+// Token represents a lexical token
+type Token struct {
+	Type     TokenType
+	Literal  string
+	Line     int
+	Column   int
+	Position int
+}
+
+func (t Token) String() string {
+	return fmt.Sprintf("Token{Type: %s, Literal: %s, Line: %d, Column: %d}",
+		t.Type.String(), t.Literal, t.Line, t.Column)
+}
+
+func (tt TokenType) String() string {
+	switch tt {
+	case TokenEOF:
+		return "EOF"
+	case TokenIllegal:
+		return "ILLEGAL"
+	case TokenIdentifier:
+		return "IDENTIFIER"
+	case TokenString:
+		return "STRING"
+	case TokenInteger:
+		return "INTEGER"
+	case TokenSelect:
+		return "SELECT"
+	case TokenFrom:
+		return "FROM"
+	case TokenWhere:
+		return "WHERE"
+	case TokenOrderBy:
+		return "ORDER BY"
+	case TokenGroupBy:
+		return "GROUP BY"
+	case TokenLimit:
+		return "LIMIT"
+	case TokenOffset:
+		return "OFFSET"
+	case TokenTime:
+		return "TIME"
+	case TokenBetween:
+		return "BETWEEN"
+	case TokenAnd:
+		return "AND"
+	case TokenOr:
+		return "OR"
+	case TokenIn:
+		return "IN"
+	case TokenNotIn:
+		return "NOT IN"
+	case TokenHaving:
+		return "HAVING"
+	case TokenNotHaving:
+		return "NOT HAVING"
+	case TokenMatch:
+		return "MATCH"
+	case TokenShow:
+		return "SHOW"
+	case TokenTop:
+		return "TOP"
+	case TokenStream:
+		return "STREAM"
+	case TokenMeasure:
+		return "MEASURE"
+	case TokenTrace:
+		return "TRACE"
+	case TokenProperty:
+		return "PROPERTY"
+	case TokenAsc:
+		return "ASC"
+	case TokenDesc:
+		return "DESC"
+	case TokenWith:
+		return "WITH"
+	case TokenQueryTrace:
+		return "QUERY_TRACE"
+	case TokenNull:
+		return "NULL"
+	case TokenAggregateBy:
+		return "AGGREGATE BY"
+	case TokenSum:
+		return "SUM"
+	case TokenMean:
+		return "MEAN"
+	case TokenAvg:
+		return "AVG"
+	case TokenCount:
+		return "COUNT"
+	case TokenMax:
+		return "MAX"
+	case TokenMin:
+		return "MIN"
+	case TokenEqual:
+		return "="
+	case TokenNotEqual:
+		return "!="
+	case TokenGreater:
+		return ">"
+	case TokenLess:
+		return "<"
+	case TokenGreaterEqual:
+		return ">="
+	case TokenLessEqual:
+		return "<="
+	case TokenComma:
+		return ","
+	case TokenSemicolon:
+		return ";"
+	case TokenLeftParen:
+		return "("
+	case TokenRightParen:
+		return ")"
+	case TokenDot:
+		return "."
+	case TokenDoubleColon:
+		return "::"
+	case TokenStar:
+		return "*"
+	case TokenBy:
+		return "BY"
+	case TokenValue:
+		return "VALUE"
+	default:
+		return "UNKNOWN"
+	}
+}
+
+// keywords maps keyword strings to their token types
+var keywords = map[string]TokenType{
+	"select":      TokenSelect,
+	"from":        TokenFrom,
+	"where":       TokenWhere,
+	"order":       TokenOrderBy, // handled specially as "order by"
+	"group":       TokenGroupBy, // handled specially as "group by"
+	"limit":       TokenLimit,
+	"offset":      TokenOffset,
+	"time":        TokenTime,
+	"between":     TokenBetween,
+	"and":         TokenAnd,
+	"or":          TokenOr,
+	"in":          TokenIn,
+	"not":         TokenNotIn, // handled specially
+	"having":      TokenHaving,
+	"match":       TokenMatch,
+	"show":        TokenShow,
+	"top":         TokenTop,
+	"stream":      TokenStream,
+	"measure":     TokenMeasure,
+	"trace":       TokenTrace,
+	"property":    TokenProperty,
+	"asc":         TokenAsc,
+	"desc":        TokenDesc,
+	"with":        TokenWith,
+	"query_trace": TokenQueryTrace,
+	"null":        TokenNull,
+	"aggregate":   TokenAggregateBy, // handled specially as "aggregate by"
+	"sum":         TokenSum,
+	"mean":        TokenMean,
+	"avg":         TokenAvg,
+	"count":       TokenCount,
+	"max":         TokenMax,
+	"min":         TokenMin,
+	"by":          TokenBy,
+	"value":       TokenValue,
+}
+
+// Lexer represents a lexical analyzer
+type Lexer struct {
+	input        string
+	position     int
+	readPosition int
+	ch           byte
+	line         int
+	column       int
+}
+
+// NewLexer creates a new lexer instance
+func NewLexer(input string) *Lexer {
+	l := &Lexer{
+		input:  input,
+		line:   1,
+		column: 0,
+	}
+	l.readChar()
+	return l
+}
+
+// readChar reads the next character and advances position
+func (l *Lexer) readChar() {
+	if l.readPosition >= len(l.input) {
+		l.ch = 0 // EOF
+	} else {
+		l.ch = l.input[l.readPosition]
+	}
+	l.position = l.readPosition
+	l.readPosition++
+
+	if l.ch == '\n' {
+		l.line++
+		l.column = 0
+	} else {
+		l.column++
+	}
+}
+
+// peekChar returns the next character without advancing position
+func (l *Lexer) peekChar() byte {
+	if l.readPosition >= len(l.input) {
+		return 0
+	}
+	return l.input[l.readPosition]
+}
+
+// skipWhitespace skips whitespace characters
+func (l *Lexer) skipWhitespace() {
+	for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
+		l.readChar()
+	}
+}
+
+// readIdentifier reads an identifier or keyword
+func (l *Lexer) readIdentifier() string {
+	position := l.position
+	for isLetter(l.ch) || isDigit(l.ch) || l.ch == '_' || l.ch == '-' {
+		l.readChar()
+	}
+	return l.input[position:l.position]
+}
+
+// readNumber reads a numeric literal
+func (l *Lexer) readNumber() string {
+	position := l.position
+	for isDigit(l.ch) {
+		l.readChar()
+	}
+	return l.input[position:l.position]
+}
+
+// readString reads a string literal
+func (l *Lexer) readString(delimiter byte) (string, error) {
+	position := l.position + 1 // skip opening quote
+	for {
+		l.readChar()
+		if l.ch == delimiter || l.ch == 0 {
+			break
+		}
+		if l.ch == '\\' && l.peekChar() == delimiter {
+			l.readChar() // skip escaped quote
+		}
+	}
+
+	if l.ch == 0 {
+		return "", fmt.Errorf("unterminated string literal at line %d, column %d", l.line, l.column)
+	}
+
+	str := l.input[position:l.position]
+	l.readChar() // skip closing quote
+	return str, nil
+}
+
+// lookupIdent checks if an identifier is a keyword
+func (l *Lexer) lookupIdent(ident string) TokenType {
+	lower := strings.ToLower(ident)
+	if tok, ok := keywords[lower]; ok {
+		// Handle compound keywords
+		switch lower {
+		case "order":
+			if l.peekAhead("by") {
+				l.consumeKeyword("by")
+				return TokenOrderBy
+			}
+		case "group":
+			if l.peekAhead("by") {
+				l.consumeKeyword("by")
+				return TokenGroupBy
+			}
+		case "aggregate":
+			if l.peekAhead("by") {
+				l.consumeKeyword("by")
+				return TokenAggregateBy
+			}
+		case "not":
+			if l.peekAhead("in") {
+				l.consumeKeyword("in")
+				return TokenNotIn
+			} else if l.peekAhead("having") {
+				l.consumeKeyword("having")
+				return TokenNotHaving
+			}
+		}
+		return tok
+	}
+	return TokenIdentifier
+}
+
+// peekAhead checks if the next identifier matches the expected keyword
+func (l *Lexer) peekAhead(expected string) bool {
+	savedPos := l.position
+	savedReadPos := l.readPosition
+	savedCh := l.ch
+	savedLine := l.line
+	savedColumn := l.column
+
+	l.skipWhitespace()
+
+	if !isLetter(l.ch) {
+		// Restore lexer state
+		l.position = savedPos
+		l.readPosition = savedReadPos
+		l.ch = savedCh
+		l.line = savedLine
+		l.column = savedColumn
+		return false
+	}
+
+	ident := l.readIdentifier()
+	match := strings.EqualFold(ident, expected)
+
+	if !match {
+		// Restore lexer state
+		l.position = savedPos
+		l.readPosition = savedReadPos
+		l.ch = savedCh
+		l.line = savedLine
+		l.column = savedColumn
+	} else {
+		// When there's a match, we need to restore the lexer state
+		// because consumeKeyword will consume the keyword again
+		l.position = savedPos
+		l.readPosition = savedReadPos
+		l.ch = savedCh
+		l.line = savedLine
+		l.column = savedColumn
+	}
+
+	return match
+}
+
+// consumeKeyword consumes the expected keyword
+func (l *Lexer) consumeKeyword(expected string) {
+	l.skipWhitespace()
+	l.readIdentifier() // consume the keyword
+}
+
+// NextToken returns the next token
+func (l *Lexer) NextToken() Token {
+	var tok Token
+	tok.Line = l.line
+	tok.Column = l.column
+	tok.Position = l.position
+
+	l.skipWhitespace()
+
+	switch l.ch {
+	case '=':
+		tok.Type = TokenEqual
+		tok.Literal = "="
+	case '!':
+		if l.peekChar() == '=' {
+			ch := l.ch
+			l.readChar()
+			tok.Type = TokenNotEqual
+			tok.Literal = string(ch) + string(l.ch)
+		} else {
+			tok.Type = TokenIllegal
+			tok.Literal = string(l.ch)
+		}
+	case '>':
+		if l.peekChar() == '=' {
+			ch := l.ch
+			l.readChar()
+			tok.Type = TokenGreaterEqual
+			tok.Literal = string(ch) + string(l.ch)
+		} else {
+			tok.Type = TokenGreater
+			tok.Literal = ">"
+		}
+	case '<':
+		if l.peekChar() == '=' {
+			ch := l.ch
+			l.readChar()
+			tok.Type = TokenLessEqual
+			tok.Literal = string(ch) + string(l.ch)
+		} else {
+			tok.Type = TokenLess
+			tok.Literal = "<"
+		}
+	case ',':
+		tok.Type = TokenComma
+		tok.Literal = ","
+	case ';':
+		tok.Type = TokenSemicolon
+		tok.Literal = ";"
+	case '(':
+		tok.Type = TokenLeftParen
+		tok.Literal = "("
+	case ')':
+		tok.Type = TokenRightParen
+		tok.Literal = ")"
+	case '.':
+		tok.Type = TokenDot
+		tok.Literal = "."
+	case ':':
+		if l.peekChar() == ':' {
+			l.readChar()
+			tok.Type = TokenDoubleColon
+			tok.Literal = "::"
+		} else {
+			tok.Type = TokenIllegal
+			tok.Literal = ":"
+		}
+	case '*':
+		tok.Type = TokenStar
+		tok.Literal = "*"
+	case '\'', '"':
+		str, err := l.readString(l.ch)
+		if err != nil {
+			tok.Type = TokenIllegal
+			tok.Literal = err.Error()
+		} else {
+			tok.Type = TokenString
+			tok.Literal = str
+		}
+	case 0:
+		tok.Type = TokenEOF
+		tok.Literal = ""
+	default:
+		if isLetter(l.ch) {
+			tok.Literal = l.readIdentifier()
+			tok.Type = l.lookupIdent(tok.Literal)
+			return tok // return early to avoid readChar()
+		} else if isDigit(l.ch) {
+			tok.Type = TokenInteger
+			tok.Literal = l.readNumber()
+			return tok // return early to avoid readChar()
+		} else {
+			tok.Type = TokenIllegal
+			tok.Literal = string(l.ch)
+		}
+	}
+
+	l.readChar()
+	return tok
+}
+
+// GetAllTokens returns all tokens from the input
+func (l *Lexer) GetAllTokens() []Token {
+	var tokens []Token
+	for {
+		tok := l.NextToken()
+		tokens = append(tokens, tok)
+		if tok.Type == TokenEOF {
+			break
+		}
+	}
+	return tokens
+}
+
+// isLetter checks if the character is a letter
+func isLetter(ch byte) bool {
+	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
+}
+
+// isDigit checks if the character is a digit
+func isDigit(ch byte) bool {
+	return '0' <= ch && ch <= '9'
+}
+
+// isAlphaNumeric checks if the character is alphanumeric
+func isAlphaNumeric(ch byte) bool {
+	return isLetter(ch) || isDigit(ch)
+}
+
+// ParseInteger parses a token literal as integer
+func ParseInteger(literal string) (int64, error) {
+	return strconv.ParseInt(literal, 10, 64)
+}
+
+// IsUnicodeIdentifier checks if a rune can be part of a Unicode identifier
+func IsUnicodeIdentifier(r rune) bool {
+	return unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_'
+}
+
+// IsUnicodeIdentifierStart checks if a rune can start a Unicode identifier
+func IsUnicodeIdentifierStart(r rune) bool {
+	return unicode.IsLetter(r) || r == '_'
+}
+
+// ReadUnicodeIdentifier reads a Unicode identifier
+func (l *Lexer) ReadUnicodeIdentifier() string {
+	start := l.position
+	for l.position < len(l.input) {
+		r, size := utf8.DecodeRuneInString(l.input[l.position:])
+		if r == utf8.RuneError || !IsUnicodeIdentifier(r) {
+			break
+		}
+		l.position += size
+		l.readPosition = l.position + 1
+		if l.position < len(l.input) {
+			l.ch = l.input[l.position]
+		} else {
+			l.ch = 0
+		}
+	}
+	return l.input[start:l.position]
+}
\ No newline at end of file
diff --git a/pkg/bydbql/parser.go b/pkg/bydbql/parser.go
new file mode 100644
index 00000000..07b3610a
--- /dev/null
+++ b/pkg/bydbql/parser.go
@@ -0,0 +1,796 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package bydbql
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// Parser represents a BydbQL parser
+type Parser struct {
+	lexer  *Lexer
+	errors []string
+
+	currentToken Token
+	peekToken    Token
+}
+
+// NewParser creates a new parser instance
+func NewParser(lexer *Lexer) *Parser {
+	p := &Parser{
+		lexer:  lexer,
+		errors: []string{},
+	}
+
+	// Read two tokens, so currentToken and peekToken are both set
+	p.nextToken()
+	p.nextToken()
+
+	return p
+}
+
+// nextToken advances to the next token
+func (p *Parser) nextToken() {
+	p.currentToken = p.peekToken
+	p.peekToken = p.lexer.NextToken()
+}
+
+// addError adds a parsing error
+func (p *Parser) addError(msg string, args ...interface{}) {
+	p.errors = append(p.errors, fmt.Sprintf(msg, args...))
+}
+
+// Errors returns parsing errors
+func (p *Parser) Errors() []string {
+	return p.errors
+}
+
+// expectToken checks if the current token matches expected type and advances
+func (p *Parser) expectToken(tokenType TokenType) bool {
+	if p.currentTokenIs(tokenType) {
+		p.nextToken()
+		return true
+	}
+
+	p.addError("expected token %s, got %s at line %d, column %d",
+		tokenType.String(), p.currentToken.Type.String(),
+		p.currentToken.Line, p.currentToken.Column)
+	return false
+}
+
+// currentTokenIs checks if current token matches the given type
+func (p *Parser) currentTokenIs(tokenType TokenType) bool {
+	return p.currentToken.Type == tokenType
+}
+
+// peekTokenIs checks if peek token matches the given type
+func (p *Parser) peekTokenIs(tokenType TokenType) bool {
+	return p.peekToken.Type == tokenType
+}
+
+// Parse parses the BydbQL query and returns a parsed query
+func (p *Parser) Parse() *ParsedQuery {
+	var stmt Statement
+
+	switch p.currentToken.Type {
+	case TokenSelect:
+		stmt = p.parseSelectStatement()
+	case TokenShow:
+		stmt = p.parseTopNStatement()
+	default:
+		p.addError("unexpected token %s at line %d, column %d. Expected SELECT or SHOW",
+			p.currentToken.Type.String(), p.currentToken.Line, p.currentToken.Column)
+		return nil
+	}
+
+	if len(p.errors) > 0 {
+		return nil
+	}
+
+	// Extract resource information from the statement
+	query := &ParsedQuery{
+		Statement: stmt,
+		Context:   &QueryContext{CurrentTime: time.Now()},
+	}
+
+	switch s := stmt.(type) {
+	case *SelectStatement:
+		if s.From != nil {
+			query.ResourceType = s.From.ResourceType
+			query.ResourceName = s.From.ResourceName
+			query.Groups = s.From.Groups
+		}
+	case *TopNStatement:
+		if s.From != nil {
+			query.ResourceType = s.From.ResourceType
+			query.ResourceName = s.From.ResourceName
+			query.Groups = s.From.Groups
+		}
+	}
+
+	return query
+}
+
+// parseSelectStatement parses a SELECT statement
+func (p *Parser) parseSelectStatement() *SelectStatement {
+	stmt := &SelectStatement{}
+
+	if !p.expectToken(TokenSelect) {
+		return nil
+	}
+
+	// Parse projection
+	stmt.Projection = p.parseProjection()
+	if stmt.Projection == nil {
+		return nil
+	}
+
+	// Parse optional FROM clause
+	if p.currentTokenIs(TokenFrom) {
+		stmt.From = p.parseFromClause()
+	}
+
+	// Parse optional TIME clause
+	if p.currentTokenIs(TokenTime) {
+		stmt.Time = p.parseTimeCondition()
+	}
+
+	// Parse optional WHERE clause
+	if p.currentTokenIs(TokenWhere) {
+		stmt.Where = p.parseWhereClause()
+	}
+
+	// Parse optional GROUP BY clause
+	if p.currentTokenIs(TokenGroupBy) {
+		stmt.GroupBy = p.parseGroupByClause()
+	}
+
+	// Parse optional ORDER BY clause
+	if p.currentTokenIs(TokenOrderBy) {
+		stmt.OrderBy = p.parseOrderByClause()
+	}
+
+	// Parse optional LIMIT clause
+	if p.currentTokenIs(TokenLimit) {
+		p.nextToken()
+		if p.currentTokenIs(TokenInteger) {
+			if limit, err := strconv.Atoi(p.currentToken.Literal); err == nil {
+				stmt.Limit = &limit
+			} else {
+				p.addError("invalid LIMIT value: %s", p.currentToken.Literal)
+			}
+			p.nextToken()
+		} else {
+			p.addError("expected integer after LIMIT")
+		}
+	}
+
+	// Parse optional OFFSET clause
+	if p.currentTokenIs(TokenOffset) {
+		p.nextToken()
+		if p.currentTokenIs(TokenInteger) {
+			if offset, err := strconv.Atoi(p.currentToken.Literal); err == nil {
+				stmt.Offset = &offset
+			} else {
+				p.addError("invalid OFFSET value: %s", p.currentToken.Literal)
+			}
+			p.nextToken()
+		} else {
+			p.addError("expected integer after OFFSET")
+		}
+	}
+
+	// Parse optional WITH QUERY_TRACE clause
+	if p.currentTokenIs(TokenWith) {
+		p.nextToken()
+		if p.currentTokenIs(TokenQueryTrace) {
+			stmt.QueryTrace = true
+			p.nextToken()
+		} else {
+			p.addError("expected QUERY_TRACE after WITH")
+		}
+	}
+
+	return stmt
+}
+
+// parseTopNStatement parses a SHOW TOP N statement
+func (p *Parser) parseTopNStatement() *TopNStatement {
+	stmt := &TopNStatement{}
+
+	if !p.expectToken(TokenShow) {
+		return nil
+	}
+
+	if !p.expectToken(TokenTop) {
+		return nil
+	}
+
+	// Parse N
+	if p.currentTokenIs(TokenInteger) {
+		if n, err := strconv.Atoi(p.currentToken.Literal); err == nil {
+			stmt.TopN = n
+		} else {
+			p.addError("invalid TOP N value: %s", p.currentToken.Literal)
+			return nil
+		}
+		p.nextToken()
+	} else {
+		p.addError("expected integer after TOP")
+		return nil
+	}
+
+	// Parse FROM clause (mandatory for Top-N)
+	if p.currentTokenIs(TokenFrom) {
+		stmt.From = p.parseFromClause()
+		if stmt.From == nil {
+			return nil
+		}
+	} else {
+		p.addError("FROM clause is mandatory for TOP N queries")
+		return nil
+	}
+
+	// Parse optional TIME clause
+	if p.currentTokenIs(TokenTime) {
+		stmt.Time = p.parseTimeCondition()
+	}
+
+	// Parse optional WHERE clause
+	if p.currentTokenIs(TokenWhere) {
+		stmt.Where = p.parseWhereClause()
+	}
+
+	// Parse optional AGGREGATE BY clause
+	if p.currentTokenIs(TokenAggregateBy) {
+		p.nextToken()
+		stmt.AggregateBy = p.parseAggregateFunction()
+	}
+
+	// Parse optional ORDER BY clause
+	if p.currentTokenIs(TokenOrderBy) {
+		stmt.OrderBy = p.parseOrderByClause()
+	}
+
+	// Parse optional WITH QUERY_TRACE clause
+	if p.currentTokenIs(TokenWith) {
+		p.nextToken()
+		if p.currentTokenIs(TokenQueryTrace) {
+			stmt.QueryTrace = true
+			p.nextToken()
+		} else {
+			p.addError("expected QUERY_TRACE after WITH")
+		}
+	}
+
+	return stmt
+}
+
+// parseProjection parses the SELECT projection
+func (p *Parser) parseProjection() *Projection {
+	projection := &Projection{}
+
+	if p.currentTokenIs(TokenStar) {
+		projection.All = true
+		p.nextToken()
+		return projection
+	}
+
+	// Check for empty projection SELECT ()
+	if p.currentTokenIs(TokenLeftParen) {
+		p.nextToken()
+		if p.currentTokenIs(TokenRightParen) {
+			projection.Empty = true
+			p.nextToken()
+			return projection
+		} else {
+			p.addError("expected ) after (")
+			return nil
+		}
+	}
+
+	// Check for TOP N projection
+	if p.currentTokenIs(TokenTop) {
+		p.nextToken()
+		if p.currentTokenIs(TokenInteger) {
+			if n, err := strconv.Atoi(p.currentToken.Literal); err == nil {
+				projection.TopN = &TopNProjection{N: n}
+				p.nextToken()
+
+				// Parse the rest of the projection after TOP N
+				if p.currentTokenIs(TokenStar) {
+					projection.TopN.Projection = &Projection{All: true}
+					p.nextToken()
+				} else {
+					// Parse column list
+					projection.TopN.Projection = &Projection{}
+					projection.TopN.Projection.Columns = p.parseColumnList()
+				}
+				return projection
+			} else {
+				p.addError("invalid TOP N value: %s", p.currentToken.Literal)
+				return nil
+			}
+		} else {
+			p.addError("expected integer after TOP")
+			return nil
+		}
+	}
+
+	// Parse column list
+	projection.Columns = p.parseColumnList()
+	if projection.Columns == nil {
+		return nil
+	}
+
+	return projection
+}
+
+// parseColumnList parses a comma-separated list of columns
+func (p *Parser) parseColumnList() []*Column {
+	var columns []*Column
+
+	for {
+		col := p.parseColumn()
+		if col == nil {
+			return nil
+		}
+		columns = append(columns, col)
+
+		if !p.currentTokenIs(TokenComma) {
+			break
+		}
+		p.nextToken() // consume comma
+	}
+
+	return columns
+}
+
+// parseColumn parses a single column
+func (p *Parser) parseColumn() *Column {
+	col := &Column{}
+
+	// Check for aggregate functions
+	if p.isAggregateFunction(p.currentToken.Type) {
+		col.Function = p.parseAggregateFunction()
+		return col
+	}
+
+	// Parse column name
+	if p.currentTokenIs(TokenIdentifier) {
+		col.Name = p.currentToken.Literal
+		p.nextToken()
+
+		// Check for type disambiguator ::tag or ::field
+		if p.currentTokenIs(TokenDoubleColon) {
+			p.nextToken()
+			if p.currentTokenIs(TokenIdentifier) {
+				switch strings.ToLower(p.currentToken.Literal) {
+				case "tag":
+					col.Type = ColumnTypeTag
+				case "field":
+					col.Type = ColumnTypeField
+				default:
+					p.addError("invalid column type: %s. Expected 'tag' or 'field'", p.currentToken.Literal)
+					return nil
+				}
+				p.nextToken()
+			} else {
+				p.addError("expected 'tag' or 'field' after ::")
+				return nil
+			}
+		}
+	} else {
+		p.addError("expected column name")
+		return nil
+	}
+
+	return col
+}
+
+// parseAggregateFunction parses an aggregate function
+func (p *Parser) parseAggregateFunction() *AggregateFunction {
+	fn := &AggregateFunction{}
+
+	if p.isAggregateFunction(p.currentToken.Type) {
+		fn.Function = strings.ToUpper(p.currentToken.Literal)
+		p.nextToken()
+
+		if !p.expectToken(TokenLeftParen) {
+			return nil
+		}
+
+		if p.currentTokenIs(TokenIdentifier) {
+			fn.Column = p.currentToken.Literal
+			p.nextToken()
+		} else {
+			p.addError("expected column name in aggregate function")
+			return nil
+		}
+
+		if !p.expectToken(TokenRightParen) {
+			return nil
+		}
+	}
+
+	return fn
+}
+
+// isAggregateFunction checks if token type is an aggregate function
+func (p *Parser) isAggregateFunction(tokenType TokenType) bool {
+	return tokenType == TokenSum || tokenType == TokenMean || tokenType == TokenAvg || 
+		tokenType == TokenCount || tokenType == TokenMax || tokenType == TokenMin
+}
+
+// parseFromClause parses the FROM clause
+func (p *Parser) parseFromClause() *FromClause {
+	if !p.expectToken(TokenFrom) {
+		return nil
+	}
+
+	from := &FromClause{}
+
+	// Parse resource type
+	switch p.currentToken.Type {
+	case TokenStream:
+		from.ResourceType = ResourceTypeStream
+		p.nextToken()
+	case TokenMeasure:
+		from.ResourceType = ResourceTypeMeasure
+		p.nextToken()
+	case TokenTrace:
+		from.ResourceType = ResourceTypeTrace
+		p.nextToken()
+	case TokenProperty:
+		from.ResourceType = ResourceTypeProperty
+		p.nextToken()
+	case TokenIdentifier:
+		// Only allow specific resource types, reject others
+		switch strings.ToUpper(p.currentToken.Literal) {
+		case "STREAM":
+			from.ResourceType = ResourceTypeStream
+		case "MEASURE":
+			from.ResourceType = ResourceTypeMeasure
+		case "TRACE":
+			from.ResourceType = ResourceTypeTrace
+		case "PROPERTY":
+			from.ResourceType = ResourceTypeProperty
+		default:
+			p.addError("expected resource type (STREAM, MEASURE, TRACE, PROPERTY), got: %s", p.currentToken.Literal)
+			return nil
+		}
+		p.nextToken()
+	default:
+		p.addError("expected resource type (STREAM, MEASURE, TRACE, PROPERTY) or resource name")
+		return nil
+	}
+
+	// Parse resource name
+	if p.currentTokenIs(TokenIdentifier) {
+		from.ResourceName = p.currentToken.Literal
+		p.nextToken()
+	} else {
+		p.addError("expected resource name")
+		return nil
+	}
+
+	// Parse optional IN (groups) clause
+	if p.currentTokenIs(TokenIn) {
+		p.nextToken()
+		if !p.expectToken(TokenLeftParen) {
+			return nil
+		}
+
+		// Parse group list
+		for {
+			if p.currentTokenIs(TokenIdentifier) {
+				from.Groups = append(from.Groups, p.currentToken.Literal)
+				p.nextToken()
+			} else {
+				p.addError("expected group name")
+				return nil
+			}
+
+			if !p.currentTokenIs(TokenComma) {
+				break
+			}
+			p.nextToken() // consume comma
+		}
+
+		if !p.expectToken(TokenRightParen) {
+			return nil
+		}
+	}
+
+	return from
+}
+
+// parseTimeCondition parses TIME conditions
+func (p *Parser) parseTimeCondition() *TimeCondition {
+	if !p.expectToken(TokenTime) {
+		return nil
+	}
+
+	condition := &TimeCondition{}
+
+	// Parse operator
+	switch p.currentToken.Type {
+	case TokenEqual:
+		condition.Operator = TimeOpEqual
+		p.nextToken()
+		condition.Timestamp = p.parseTimestamp()
+	case TokenGreater:
+		condition.Operator = TimeOpGreater
+		p.nextToken()
+		condition.Timestamp = p.parseTimestamp()
+	case TokenLess:
+		condition.Operator = TimeOpLess
+		p.nextToken()
+		condition.Timestamp = p.parseTimestamp()
+	case TokenGreaterEqual:
+		condition.Operator = TimeOpGreaterEqual
+		p.nextToken()
+		condition.Timestamp = p.parseTimestamp()
+	case TokenLessEqual:
+		condition.Operator = TimeOpLessEqual
+		p.nextToken()
+		condition.Timestamp = p.parseTimestamp()
+	case TokenBetween:
+		condition.Operator = TimeOpBetween
+		p.nextToken()
+		condition.Begin = p.parseTimestamp()
+		if !p.expectToken(TokenAnd) {
+			return nil
+		}
+		condition.End = p.parseTimestamp()
+	default:
+		p.addError("expected time operator (=, >, <, >=, <=, BETWEEN)")
+		return nil
+	}
+
+	return condition
+}
+
+// parseTimestamp parses a timestamp (string or integer)
+func (p *Parser) parseTimestamp() string {
+	if p.currentTokenIs(TokenString) || p.currentTokenIs(TokenInteger) {
+		timestamp := p.currentToken.Literal
+		p.nextToken()
+		return timestamp
+	}
+
+	p.addError("expected timestamp")
+	return ""
+}
+
+// parseWhereClause parses WHERE clause
+func (p *Parser) parseWhereClause() *WhereClause {
+	if !p.expectToken(TokenWhere) {
+		return nil
+	}
+
+	where := &WhereClause{}
+	where.Conditions = p.parseConditions()
+
+	return where
+}
+
+// parseConditions parses conditions with AND/OR logic
+func (p *Parser) parseConditions() []*Condition {
+	var conditions []*Condition
+
+	condition := p.parseCondition()
+	if condition == nil {
+		return nil
+	}
+	conditions = append(conditions, condition)
+
+	for p.currentTokenIs(TokenAnd) || p.currentTokenIs(TokenOr) {
+		if p.currentTokenIs(TokenAnd) {
+			condition.Logic = LogicAnd
+		} else {
+			condition.Logic = LogicOr
+		}
+		p.nextToken()
+
+		condition = p.parseCondition()
+		if condition == nil {
+			return nil
+		}
+		conditions = append(conditions, condition)
+	}
+
+	return conditions
+}
+
+// parseCondition parses a single condition
+func (p *Parser) parseCondition() *Condition {
+	condition := &Condition{}
+
+	// Parse left side (column name)
+	if p.currentTokenIs(TokenIdentifier) {
+		condition.Left = p.currentToken.Literal
+		p.nextToken()
+	} else {
+		p.addError("expected column name in condition")
+		return nil
+	}
+
+	// Parse operator
+	switch p.currentToken.Type {
+	case TokenEqual:
+		condition.Operator = OpEqual
+	case TokenNotEqual:
+		condition.Operator = OpNotEqual
+	case TokenGreater:
+		condition.Operator = OpGreater
+	case TokenLess:
+		condition.Operator = OpLess
+	case TokenGreaterEqual:
+		condition.Operator = OpGreaterEqual
+	case TokenLessEqual:
+		condition.Operator = OpLessEqual
+	case TokenIn:
+		condition.Operator = OpIn
+	case TokenNotIn:
+		condition.Operator = OpNotIn
+	case TokenHaving:
+		condition.Operator = OpHaving
+	case TokenNotHaving:
+		condition.Operator = OpNotHaving
+	case TokenMatch:
+		condition.Operator = OpMatch
+	default:
+		p.addError("expected comparison operator")
+		return nil
+	}
+	p.nextToken()
+
+	// Parse right side
+	if condition.Operator == OpIn || condition.Operator == OpNotIn {
+		// Parse value list
+		if !p.expectToken(TokenLeftParen) {
+			return nil
+		}
+
+		for {
+			value := p.parseValue()
+			if value == nil {
+				return nil
+			}
+			condition.Values = append(condition.Values, value)
+
+			if !p.currentTokenIs(TokenComma) {
+				break
+			}
+			p.nextToken() // consume comma
+		}
+
+		if !p.expectToken(TokenRightParen) {
+			return nil
+		}
+	} else {
+		// Parse single value
+		condition.Right = p.parseValue()
+		if condition.Right == nil {
+			return nil
+		}
+	}
+
+	return condition
+}
+
+// parseValue parses a value (string, integer, or null)
+func (p *Parser) parseValue() *Value {
+	value := &Value{}
+
+	switch p.currentToken.Type {
+	case TokenString:
+		value.Type = ValueTypeString
+		value.StringVal = p.currentToken.Literal
+		p.nextToken()
+	case TokenInteger:
+		value.Type = ValueTypeInteger
+		if i, err := strconv.ParseInt(p.currentToken.Literal, 10, 64); err == nil {
+			value.Integer = i
+		} else {
+			p.addError("invalid integer: %s", p.currentToken.Literal)
+			return nil
+		}
+		p.nextToken()
+	case TokenNull:
+		value.Type = ValueTypeNull
+		value.IsNull = true
+		p.nextToken()
+	default:
+		p.addError("expected value (string, integer, or NULL)")
+		return nil
+	}
+
+	return value
+}
+
+// parseGroupByClause parses GROUP BY clause
+func (p *Parser) parseGroupByClause() *GroupByClause {
+	if !p.expectToken(TokenGroupBy) {
+		return nil
+	}
+
+	groupBy := &GroupByClause{}
+
+	// Parse column list
+	for {
+		if p.currentTokenIs(TokenIdentifier) {
+			groupBy.Columns = append(groupBy.Columns, p.currentToken.Literal)
+			p.nextToken()
+		} else {
+			p.addError("expected column name in GROUP BY")
+			return nil
+		}
+
+		if !p.currentTokenIs(TokenComma) {
+			break
+		}
+		p.nextToken() // consume comma
+	}
+
+	return groupBy
+}
+
+// parseOrderByClause parses ORDER BY clause
+func (p *Parser) parseOrderByClause() *OrderByClause {
+	if !p.expectToken(TokenOrderBy) {
+		return nil
+	}
+
+	orderBy := &OrderByClause{}
+
+	// Parse column name (or "value" for Top-N queries)
+	if p.currentTokenIs(TokenIdentifier) {
+		orderBy.Column = p.currentToken.Literal
+		p.nextToken()
+	} else if p.currentTokenIs(TokenValue) {
+		orderBy.Column = "value"
+		p.nextToken()
+	} else {
+		p.addError("expected column name in ORDER BY")
+		return nil
+	}
+
+	// Parse optional ASC/DESC
+	if p.currentTokenIs(TokenAsc) {
+		orderBy.Desc = false
+		p.nextToken()
+	} else if p.currentTokenIs(TokenDesc) {
+		orderBy.Desc = true
+		p.nextToken()
+	}
+
+	return orderBy
+}
+
+// ParseQuery is a convenience function to parse a BydbQL query string
+func ParseQuery(query string) (*ParsedQuery, []string) {
+	lexer := NewLexer(query)
+	parser := NewParser(lexer)
+	parsed := parser.Parse()
+	return parsed, parser.Errors()
+}
\ No newline at end of file
diff --git a/pkg/bydbql/translator.go b/pkg/bydbql/translator.go
new file mode 100644
index 00000000..ca41b8fd
--- /dev/null
+++ b/pkg/bydbql/translator.go
@@ -0,0 +1,591 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package bydbql
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+
+	str2duration "github.com/xhit/go-str2duration/v2"
+	"sigs.k8s.io/yaml"
+)
+
+// QueryYAML represents the YAML structure for BanyanDB queries
+type QueryYAML struct {
+	// Common fields
+	Name       string                 `yaml:"name,omitempty"`
+	Groups     []string               `yaml:"groups,omitempty"`
+	TimeRange  *TimeRangeYAML         `yaml:"timeRange,omitempty"`
+	Criteria   []map[string]interface{} `yaml:"criteria,omitempty"`
+	Limit      *int                   `yaml:"limit,omitempty"`
+	Offset     *int                   `yaml:"offset,omitempty"`
+	Trace      bool                   `yaml:"trace,omitempty"`
+
+	// Stream/Trace specific
+	Projection []string               `yaml:"projection,omitempty"`
+	OrderBy    *OrderByYAML           `yaml:"orderBy,omitempty"`
+
+	// Measure specific
+	TagProjection   []string              `yaml:"tagProjection,omitempty"`
+	FieldProjection []string              `yaml:"fieldProjection,omitempty"`
+	GroupBy         *GroupByYAML          `yaml:"groupBy,omitempty"`
+	Agg             *AggregationYAML      `yaml:"agg,omitempty"`
+	Top             *TopYAML              `yaml:"top,omitempty"`
+
+	// Property specific
+	IDs []string `yaml:"ids,omitempty"`
+
+	// Top-N specific (for separate endpoint)
+	TopN              int                `yaml:"topN,omitempty"`
+	FieldValueSort    string             `yaml:"fieldValueSort,omitempty"`
+	Conditions        []map[string]interface{} `yaml:"conditions,omitempty"`
+}
+
+// TimeRangeYAML represents time range in YAML
+type TimeRangeYAML struct {
+	Begin string `yaml:"begin"`
+	End   string `yaml:"end"`
+}
+
+// OrderByYAML represents ORDER BY clause in YAML
+type OrderByYAML struct {
+	IndexRuleName string `yaml:"indexRuleName"`
+	Sort          string `yaml:"sort"` // ASC or DESC
+}
+
+// GroupByYAML represents GROUP BY clause in YAML
+type GroupByYAML struct {
+	TagProjection []string `yaml:"tagProjection"`
+}
+
+// AggregationYAML represents aggregation function in YAML
+type AggregationYAML struct {
+	Function   string `yaml:"function"`
+	FieldName  string `yaml:"fieldName"`
+}
+
+// TopYAML represents TOP N clause in YAML
+type TopYAML struct {
+	Number        int      `yaml:"number"`
+	FieldName     string   `yaml:"fieldName"`
+	FieldValueSort string  `yaml:"fieldValueSort"`
+}
+
+// Translator converts parsed BydbQL to YAML format
+type Translator struct {
+	context *QueryContext
+}
+
+// NewTranslator creates a new translator
+func NewTranslator(context *QueryContext) *Translator {
+	if context == nil {
+		context = &QueryContext{CurrentTime: time.Now()}
+	}
+	return &Translator{context: context}
+}
+
+// TranslateToYAML translates a parsed query to YAML format
+func (t *Translator) TranslateToYAML(query *ParsedQuery) ([]byte, error) {
+	yamlQuery, err := t.translateQuery(query)
+	if err != nil {
+		return nil, err
+	}
+
+	return yaml.Marshal(yamlQuery)
+}
+
+// TranslateToMap translates a parsed query to a map suitable for JSON/YAML conversion
+func (t *Translator) TranslateToMap(query *ParsedQuery) (map[string]interface{}, error) {
+	yamlQuery, err := t.translateQuery(query)
+	if err != nil {
+		return nil, err
+	}
+
+	yamlBytes, err := yaml.Marshal(yamlQuery)
+	if err != nil {
+		return nil, err
+	}
+
+	var result map[string]interface{}
+	err = yaml.Unmarshal(yamlBytes, &result)
+	return result, err
+}
+
+// translateQuery translates the main query structure
+func (t *Translator) translateQuery(query *ParsedQuery) (*QueryYAML, error) {
+	yamlQuery := &QueryYAML{}
+
+	// Set common fields from context or FROM clause
+	if query.ResourceName != "" {
+		yamlQuery.Name = query.ResourceName
+	} else if t.context.DefaultResourceName != "" {
+		yamlQuery.Name = t.context.DefaultResourceName
+	}
+
+	if len(query.Groups) > 0 {
+		yamlQuery.Groups = query.Groups
+	} else if t.context.DefaultGroup != "" {
+		yamlQuery.Groups = []string{t.context.DefaultGroup}
+	}
+
+	switch stmt := query.Statement.(type) {
+	case *SelectStatement:
+		return t.translateSelectStatement(stmt, yamlQuery, query)
+	case *TopNStatement:
+		return t.translateTopNStatement(stmt, yamlQuery, query)
+	default:
+		return nil, fmt.Errorf("unsupported statement type")
+	}
+}
+
+// translateSelectStatement translates SELECT statements
+func (t *Translator) translateSelectStatement(stmt *SelectStatement, yamlQuery *QueryYAML, query *ParsedQuery) (*QueryYAML, error) {
+	// Set query trace flag
+	yamlQuery.Trace = stmt.QueryTrace
+
+	// Translate TIME clause
+	if stmt.Time != nil {
+		timeRange, err := t.translateTimeCondition(stmt.Time)
+		if err != nil {
+			return nil, err
+		}
+		yamlQuery.TimeRange = timeRange
+	}
+
+	// Translate WHERE clause
+	if stmt.Where != nil {
+		criteria, err := t.translateWhereClause(stmt.Where)
+		if err != nil {
+			return nil, err
+		}
+		yamlQuery.Criteria = criteria
+	}
+
+	// Set LIMIT and OFFSET
+	yamlQuery.Limit = stmt.Limit
+	yamlQuery.Offset = stmt.Offset
+
+	// Translate based on resource type
+	resourceType := query.ResourceType
+	if resourceType == ResourceTypeAuto {
+		resourceType = t.context.DefaultResourceType
+	}
+
+	switch resourceType {
+	case ResourceTypeStream, ResourceTypeTrace:
+		return t.translateStreamOrTraceQuery(stmt, yamlQuery)
+	case ResourceTypeMeasure:
+		return t.translateMeasureQuery(stmt, yamlQuery)
+	case ResourceTypeProperty:
+		return t.translatePropertyQuery(stmt, yamlQuery)
+	default:
+		// Auto-detect or use default
+		return t.translateStreamOrTraceQuery(stmt, yamlQuery)
+	}
+}
+
+// translateStreamOrTraceQuery translates stream/trace specific fields
+func (t *Translator) translateStreamOrTraceQuery(stmt *SelectStatement, yamlQuery *QueryYAML) (*QueryYAML, error) {
+	// Translate projection
+	if stmt.Projection != nil {
+		if stmt.Projection.All {
+			// SELECT * - no specific projection needed
+		} else if stmt.Projection.Empty {
+			// SELECT () - empty projection for traces
+			yamlQuery.Projection = []string{}
+		} else if len(stmt.Projection.Columns) > 0 {
+			for _, col := range stmt.Projection.Columns {
+				yamlQuery.Projection = append(yamlQuery.Projection, col.Name)
+			}
+		}
+	}
+
+	// Translate ORDER BY
+	if stmt.OrderBy != nil {
+		yamlQuery.OrderBy = &OrderByYAML{
+			IndexRuleName: stmt.OrderBy.Column,
+		}
+		if stmt.OrderBy.Desc {
+			yamlQuery.OrderBy.Sort = "DESC"
+		} else {
+			yamlQuery.OrderBy.Sort = "ASC"
+		}
+	}
+
+	return yamlQuery, nil
+}
+
+// translateMeasureQuery translates measure specific fields
+func (t *Translator) translateMeasureQuery(stmt *SelectStatement, yamlQuery *QueryYAML) (*QueryYAML, error) {
+	// Translate projection
+	if stmt.Projection != nil {
+		if stmt.Projection.All {
+			// SELECT * - no specific projection needed
+		} else if stmt.Projection.TopN != nil {
+			// Handle TOP N projection
+			yamlQuery.Top = &TopYAML{
+				Number: stmt.Projection.TopN.N,
+			}
+			if stmt.OrderBy != nil {
+				if stmt.OrderBy.Desc {
+					yamlQuery.Top.FieldValueSort = "DESC"
+				} else {
+					yamlQuery.Top.FieldValueSort = "ASC"
+				}
+			}
+		} else if len(stmt.Projection.Columns) > 0 {
+			// Separate tags and fields based on column type or function
+			for _, col := range stmt.Projection.Columns {
+				if col.Function != nil {
+					// Aggregate function
+					yamlQuery.Agg = &AggregationYAML{
+						Function:  col.Function.Function,
+						FieldName: col.Function.Column,
+					}
+				} else {
+					// Regular column - determine if tag or field
+					switch col.Type {
+					case ColumnTypeTag:
+						yamlQuery.TagProjection = append(yamlQuery.TagProjection, col.Name)
+					case ColumnTypeField:
+						yamlQuery.FieldProjection = append(yamlQuery.FieldProjection, col.Name)
+					default:
+						// Auto-detect: assume tag for now (schema lookup would be needed)
+						yamlQuery.TagProjection = append(yamlQuery.TagProjection, col.Name)
+					}
+				}
+			}
+		}
+	}
+
+	// Translate GROUP BY
+	if stmt.GroupBy != nil {
+		yamlQuery.GroupBy = &GroupByYAML{
+			TagProjection: stmt.GroupBy.Columns,
+		}
+	}
+
+	// Translate ORDER BY
+	if stmt.OrderBy != nil {
+		yamlQuery.OrderBy = &OrderByYAML{
+			IndexRuleName: stmt.OrderBy.Column,
+		}
+		if stmt.OrderBy.Desc {
+			yamlQuery.OrderBy.Sort = "DESC"
+		} else {
+			yamlQuery.OrderBy.Sort = "ASC"
+		}
+	}
+
+	return yamlQuery, nil
+}
+
+// translatePropertyQuery translates property specific fields
+func (t *Translator) translatePropertyQuery(stmt *SelectStatement, yamlQuery *QueryYAML) (*QueryYAML, error) {
+	// Translate projection
+	if stmt.Projection != nil && !stmt.Projection.All && len(stmt.Projection.Columns) > 0 {
+		for _, col := range stmt.Projection.Columns {
+			yamlQuery.Projection = append(yamlQuery.Projection, col.Name)
+		}
+	}
+
+	// Extract IDs from WHERE clause if present
+	if stmt.Where != nil {
+		for _, condition := range stmt.Where.Conditions {
+			if strings.ToUpper(condition.Left) == "ID" {
+				if condition.Operator == OpEqual && condition.Right != nil {
+					yamlQuery.IDs = []string{condition.Right.StringVal}
+				} else if condition.Operator == OpIn && len(condition.Values) > 0 {
+					for _, val := range condition.Values {
+						yamlQuery.IDs = append(yamlQuery.IDs, val.StringVal)
+					}
+				}
+			}
+		}
+	}
+
+	return yamlQuery, nil
+}
+
+// translateTopNStatement translates SHOW TOP N statements
+func (t *Translator) translateTopNStatement(stmt *TopNStatement, yamlQuery *QueryYAML, query *ParsedQuery) (*QueryYAML, error) {
+	// Set Top-N specific fields
+	yamlQuery.TopN = stmt.TopN
+	yamlQuery.Trace = stmt.QueryTrace
+
+	// Translate TIME clause
+	if stmt.Time != nil {
+		timeRange, err := t.translateTimeCondition(stmt.Time)
+		if err != nil {
+			return nil, err
+		}
+		yamlQuery.TimeRange = timeRange
+	}
+
+	// Translate WHERE clause to conditions (Top-N uses different field name)
+	if stmt.Where != nil {
+		conditions, err := t.translateWhereClause(stmt.Where)
+		if err != nil {
+			return nil, err
+		}
+		yamlQuery.Conditions = conditions
+	}
+
+	// Translate AGGREGATE BY
+	if stmt.AggregateBy != nil {
+		yamlQuery.Agg = &AggregationYAML{
+			Function:  stmt.AggregateBy.Function,
+			FieldName: stmt.AggregateBy.Column,
+		}
+	}
+
+	// Translate ORDER BY
+	if stmt.OrderBy != nil {
+		if stmt.OrderBy.Desc {
+			yamlQuery.FieldValueSort = "DESC"
+		} else {
+			yamlQuery.FieldValueSort = "ASC"
+		}
+	}
+
+	return yamlQuery, nil
+}
+
+// translateTimeCondition translates TIME conditions to time range
+func (t *Translator) translateTimeCondition(timeCondition *TimeCondition) (*TimeRangeYAML, error) {
+	timeRange := &TimeRangeYAML{}
+
+	switch timeCondition.Operator {
+	case TimeOpEqual:
+		timestamp, err := t.parseTimestamp(timeCondition.Timestamp)
+		if err != nil {
+			return nil, err
+		}
+		timeRange.Begin = timestamp
+		timeRange.End = timestamp
+	case TimeOpGreater:
+		timestamp, err := t.parseTimestamp(timeCondition.Timestamp)
+		if err != nil {
+			return nil, err
+		}
+		timeRange.Begin = timestamp
+		// End time not specified, use current time
+		timeRange.End = t.context.CurrentTime.Format(time.RFC3339)
+	case TimeOpLess:
+		timestamp, err := t.parseTimestamp(timeCondition.Timestamp)
+		if err != nil {
+			return nil, err
+		}
+		// Begin time not specified, use a time far in the past
+		timeRange.Begin = "1970-01-01T00:00:00Z"
+		timeRange.End = timestamp
+	case TimeOpGreaterEqual:
+		timestamp, err := t.parseTimestamp(timeCondition.Timestamp)
+		if err != nil {
+			return nil, err
+		}
+		timeRange.Begin = timestamp
+		timeRange.End = t.context.CurrentTime.Format(time.RFC3339)
+	case TimeOpLessEqual:
+		timestamp, err := t.parseTimestamp(timeCondition.Timestamp)
+		if err != nil {
+			return nil, err
+		}
+		timeRange.Begin = "1970-01-01T00:00:00Z"
+		timeRange.End = timestamp
+	case TimeOpBetween:
+		begin, err := t.parseTimestamp(timeCondition.Begin)
+		if err != nil {
+			return nil, err
+		}
+		end, err := t.parseTimestamp(timeCondition.End)
+		if err != nil {
+			return nil, err
+		}
+		timeRange.Begin = begin
+		timeRange.End = end
+	}
+
+	return timeRange, nil
+}
+
+// parseTimestamp parses timestamp string (absolute or relative)
+func (t *Translator) parseTimestamp(timestamp string) (string, error) {
+	// Try parsing as absolute time first (RFC3339)
+	if parsedTime, err := time.Parse(time.RFC3339, timestamp); err == nil {
+		return parsedTime.Format(time.RFC3339), nil
+	}
+
+	// Try parsing as relative time (duration string)
+	if strings.ToLower(timestamp) == "now" {
+		return t.context.CurrentTime.Format(time.RFC3339), nil
+	}
+
+	duration, err := str2duration.ParseDuration(timestamp)
+	if err != nil {
+		return "", fmt.Errorf("invalid timestamp format: %s", timestamp)
+	}
+
+	resultTime := t.context.CurrentTime.Add(duration)
+	return resultTime.Format(time.RFC3339), nil
+}
+
+// translateWhereClause translates WHERE conditions to criteria format
+func (t *Translator) translateWhereClause(where *WhereClause) ([]map[string]interface{}, error) {
+	var criteria []map[string]interface{}
+
+	for _, condition := range where.Conditions {
+		criterion := make(map[string]interface{})
+
+		// Set tag name
+		criterion["tagName"] = 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)
+			}
+		}
+
+		criteria = append(criteria, criterion)
+	}
+
+	return criteria, nil
+}
+
+// translateValue translates a value to the appropriate YAML format
+func (t *Translator) translateValue(value *Value) interface{} {
+	if value.IsNull {
+		return nil
+	}
+
+	switch value.Type {
+	case ValueTypeString:
+		return map[string]interface{}{
+			"str": map[string]interface{}{
+				"value": value.StringVal,
+			},
+		}
+	case ValueTypeInteger:
+		return map[string]interface{}{
+			"int": map[string]interface{}{
+				"value": strconv.FormatInt(value.Integer, 10),
+			},
+		}
+	default:
+		return nil
+	}
+}
+
+// TranslateQuery is a convenience function to translate a BydbQL query string to YAML
+func TranslateQuery(query string, context *QueryContext) ([]byte, []string, error) {
+	parsed, errors := ParseQuery(query)
+	if len(errors) > 0 {
+		return nil, errors, fmt.Errorf("parsing errors occurred")
+	}
+
+	if parsed == nil {
+		return nil, []string{"failed to parse query"}, fmt.Errorf("parsing failed")
+	}
+
+	translator := NewTranslator(context)
+	yamlBytes, err := translator.TranslateToYAML(parsed)
+	if err != nil {
+		return nil, []string{err.Error()}, err
+	}
+
+	return yamlBytes, nil, nil
+}
\ No newline at end of file