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_.]*'`},