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

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


The following commit(s) were added to refs/heads/main by this push:
     new 8d88629f5 Allow `*` in the BydbQL identifier (#1223)
8d88629f5 is described below

commit 8d88629f55a1776967d5033e02f73e8af7755663
Author: mrproliu <[email protected]>
AuthorDate: Wed Jul 22 08:59:27 2026 +0800

    Allow `*` in the BydbQL identifier (#1223)
---
 CHANGES.md                 |  1 +
 docs/interacting/bydbql.md | 28 +++++++++++++++++----
 pkg/bydbql/bydbql_test.go  | 63 ++++++++++++++++++++++++++++++++++++++++++++++
 pkg/bydbql/parser.go       |  7 +++++-
 4 files changed, 93 insertions(+), 6 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index f78e52aaf..d81e20539 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -139,6 +139,7 @@ Release Notes.
 - Deleting one TopN aggregation no longer tears down sibling aggregations on 
the same source measure.
 - Purge a deleted group's resource, index-rule and binding cache entries to 
avoid dangling references.
 - Clear a trace subject's index when its last index rule or binding is removed.
+- Allow `*` as a non-initial character in BydbQL identifiers, so resources 
named with `*` become queryable, not just writable.
 - Enable periodic health checks on the queue client 
(`--<prefix>-client-health-check-interval`, default `10s`), evicting dead data 
nodes proactively.
 
 ### Document
diff --git a/docs/interacting/bydbql.md b/docs/interacting/bydbql.md
index a2ab16166..02cc21436 100644
--- a/docs/interacting/bydbql.md
+++ b/docs/interacting/bydbql.md
@@ -98,6 +98,24 @@ FROM STREAM mystream in group1   -- refers to stream 
"mystream"
 
 **Best Practice**: Use uppercase for reserved words and consistent casing for 
identifiers to maintain readability.
 
+#### Identifier Character Set
+
+An identifier starts with a letter or underscore and may continue with 
letters, digits, underscores, hyphens (`-`), and asterisks (`*`):
+
+```
+identifier ::= [a-zA-Z_][a-zA-Z0-9_*-]*
+```
+
+Hyphens and asterisks are allowed only after the first character. This matters 
because the schema layer accepts such names at creation time — a TopN rule 
named `endpoint_avg-cluster-excludes-*`, for instance — and a resource that can 
be created must remain referenceable from a query:
+
+```sql
+SHOW TOP 10 FROM MEASURE endpoint_avg-cluster-excludes-* IN default;
+```
+
+Confining `*` to non-initial positions is what keeps the star projection 
unambiguous: a bare `*` cannot begin an identifier, so `SELECT *` is always a 
projection.
+
+**Caveat**: an identifier that begins with a reserved word followed by a 
non-word character is rejected by the lexer — `IN group-a` fails because 
`group` matches the keyword rule first. Avoid naming resources with a 
reserved-word prefix.
+
 ## 2.5. Timestamp Formats
 
 BydbQL supports flexible timestamp specifications in TIME clauses, 
accommodating both absolute and relative time formats:
@@ -392,7 +410,7 @@ timestamp       ::= string_literal | integer_literal
        /* timestamp supports both absolute and relative time formats:
           - Absolute: RFC3339 format like "2006-01-02T15:04:05Z07:00"
           - Relative: duration strings like "-30m", "2h", "1d" (relative to 
current time) */
-identifier      ::= [a-zA-Z_][a-zA-Z0-9_]*
+identifier      ::= [a-zA-Z_][a-zA-Z0-9_*-]*
 string_literal  ::= "'" [^']* "'" | "\"" [^\"]* "\""
 integer_literal ::= [0-9]+
 ```
@@ -530,7 +548,7 @@ timestamp         ::= string_literal | integer_literal
        /* timestamp supports both absolute and relative time formats:
           - Absolute: RFC3339 format like "2006-01-02T15:04:05Z07:00"
           - Relative: duration strings like "-30m", "2h", "1d" (relative to 
current time) */
-identifier        ::= [a-zA-Z_][a-zA-Z0-9_]*
+identifier        ::= [a-zA-Z_][a-zA-Z0-9_*-]*
 string_literal    ::= "'" [^']* "'" | "\"" [^\"]* "\""
 integer_literal   ::= [0-9]+
 ```
@@ -705,7 +723,7 @@ timestamp          ::= string_literal | integer_literal
        /* timestamp supports both absolute and relative time formats:
           - Absolute: RFC3339 format like "2006-01-02T15:04:05Z07:00"
           - Relative: duration strings like "-30m", "2h", "1d" (relative to 
current time) */
-identifier         ::= [a-zA-Z_][a-zA-Z0-9_]*
+identifier         ::= [a-zA-Z_][a-zA-Z0-9_*-]*
 string_literal     ::= "'" [^']* "'" | "\"" [^\"]* "\""
 integer_literal    ::= [0-9]+
 ```
@@ -825,7 +843,7 @@ condition           ::= identifier binary_op (value | 
value_list) | "ID" binary_
 binary_op           ::= "=" | "!=" | ">" | "<" | ">=" | "<=" | "IN" | "NOT IN"
 value               ::= string_literal | integer_literal | "NULL"
 value_list          ::= "(" value ("," value)* ")"
-identifier          ::= [a-zA-Z_][a-zA-Z0-9_]*
+identifier          ::= [a-zA-Z_][a-zA-Z0-9_*-]*
 string_literal      ::= "'" [^']* "'" | "\"" [^\"]* "\""
 integer_literal     ::= [0-9]+
 ```
@@ -912,7 +930,7 @@ timestamp             ::= string_literal | integer_literal
        /* timestamp supports both absolute and relative time formats:
           - Absolute: RFC3339 format like "2006-01-02T15:04:05Z07:00"
           - Relative: duration strings like "-30m", "2h", "1d" (relative to 
current time) */
-identifier            ::= [a-zA-Z_][a-zA-Z0-9_]*
+identifier            ::= [a-zA-Z_][a-zA-Z0-9_*-]*
 string_literal        ::= "'" [^']* "'" | "\"" [^\"]* "\""
 integer_literal       ::= [0-9]+
 ```
diff --git a/pkg/bydbql/bydbql_test.go b/pkg/bydbql/bydbql_test.go
index 9578e0cf5..e29b5559c 100644
--- a/pkg/bydbql/bydbql_test.go
+++ b/pkg/bydbql/bydbql_test.go
@@ -3794,6 +3794,69 @@ var _ = Describe("Parser", func() {
                        })
                })
        })
+
+       Describe("Identifier Character Set", func() {
+               // The schema layer accepts resource names containing `-` and 
`*` at creation time, so
+               // the query layer must be able to name them; otherwise a 
resource can be written but
+               // never read. `*` is confined to non-initial positions to keep 
`SELECT *` unambiguous.
+               It("parses a measure name containing a star", func() {
+                       grammar, err := ParseQuery("SHOW TOP 5 FROM MEASURE 
endpoint_avg-cluster-excludes-* IN default")
+                       Expect(err).To(BeNil())
+                       Expect(grammar).NotTo(BeNil())
+
+                       stmt := grammar.TopN
+                       
Expect(stmt.From.ResourceName).To(Equal("endpoint_avg-cluster-excludes-*"))
+               })
+
+               It("parses a stream name containing a star", func() {
+                       grammar, err := ParseQuery("SELECT * FROM STREAM 
sw-excludes-* IN default")
+                       Expect(err).To(BeNil())
+
+                       stmt := grammar.Select
+                       
Expect(stmt.From.ResourceName).To(Equal("sw-excludes-*"))
+               })
+
+               // A group name that begins with a reserved word (e.g. 
`group-a`) is rejected by the
+               // keyword rule regardless of the star, so this uses a 
non-reserved prefix.
+               It("parses a group name containing a star", func() {
+                       grammar, err := ParseQuery("SELECT * FROM MEASURE m IN 
mygroup-*")
+                       Expect(err).To(BeNil())
+
+                       stmt := grammar.Select
+                       
Expect(stmt.From.In.Groups).To(Equal([]string{"mygroup-*"}))
+               })
+
+               It("keeps the star projection working alongside a starred 
resource name", func() {
+                       grammar, err := ParseQuery("SELECT * FROM MEASURE 
endpoint_avg-cluster-excludes-* IN default")
+                       Expect(err).To(BeNil())
+
+                       stmt := grammar.Select
+                       Expect(stmt.Projection.All).To(BeTrue())
+                       
Expect(stmt.From.ResourceName).To(Equal("endpoint_avg-cluster-excludes-*"))
+               })
+
+               It("still parses the bare star projection as a projection, not 
an identifier", func() {
+                       grammar, err := ParseQuery("SELECT * FROM STREAM sw IN 
default")
+                       Expect(err).To(BeNil())
+
+                       stmt := grammar.Select
+                       Expect(stmt.Projection.All).To(BeTrue())
+                       Expect(stmt.Projection.Columns).To(BeEmpty())
+               })
+
+               It("rejects an identifier starting with a star", func() {
+                       _, err := ParseQuery("SELECT * FROM MEASURE *bad IN 
default")
+                       Expect(err).NotTo(BeNil())
+               })
+
+               It("parses a starred column reference in a condition", func() {
+                       grammar, err := ParseQuery("SELECT * FROM MEASURE m IN 
default WHERE col-* = 'v'")
+                       Expect(err).To(BeNil())
+
+                       stmt := grammar.Select
+                       Expect(stmt.Where).NotTo(BeNil())
+               })
+       })
 })
 
 func BenchmarkParser(b *testing.B) {
diff --git a/pkg/bydbql/parser.go b/pkg/bydbql/parser.go
index 35cbc8e15..66892e088 100644
--- a/pkg/bydbql/parser.go
+++ b/pkg/bydbql/parser.go
@@ -48,7 +48,12 @@ func init() {
                        Name:    "Keyword",
                        Pattern: fmt.Sprintf(`(?i)(%s)\b`, keywordStr),
                },
-               {Name: "Ident", Pattern: `[a-zA-Z_][a-zA-Z0-9_-]*`},
+               // `-` and `*` are accepted as continuation characters, never 
as the first one, so that
+               // resource names the schema layer already accepts at creation 
time — e.g. a TopN rule
+               // named `endpoint_avg-cluster-excludes-*` — stay referenceable 
from a query. Confining
+               // `*` to non-initial positions is what keeps the `SELECT *` 
projection unambiguous: a
+               // bare `*` cannot begin an identifier, so it still lexes as an 
operator.
+               {Name: "Ident", Pattern: `[a-zA-Z_][a-zA-Z0-9_*-]*`},
                {Name: "Int", Pattern: `[-+]?\d+`},
                {Name: "String", Pattern: 
`'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"`},
                {Name: "QuotedIdent", Pattern: 
`"[a-zA-Z_][a-zA-Z0-9_.]*"|'[a-zA-Z_][a-zA-Z0-9_.]*'`},

Reply via email to