Skip to content

Commit d3a7403

Browse files
committed
Implemented SELECT syntax without FROM
1 parent 111f5c9 commit d3a7403

File tree

2 files changed

+114
-6
lines changed

2 files changed

+114
-6
lines changed

lib/logsql/select.go

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ var (
4545
safeFormatFieldLiteral = regexp.MustCompile(`^[A-Za-z0-9_.-]+$`)
4646
)
4747

48+
const constantSelectBasePipeline = "* | limit 1 | delete *"
49+
4850
type selectTranslatorVisitor struct {
4951
result string
5052
err error
@@ -69,6 +71,7 @@ type selectTranslatorVisitor struct {
6971
constantFieldCount int
7072
aggTempDeletes map[string]string
7173
aggPreserve map[string]struct{}
74+
constantBase bool
7275
}
7376

7477
type tableBinding struct {
@@ -194,6 +197,7 @@ func (v *selectTranslatorVisitor) translateSimpleSelect(stmt *ast.SelectStatemen
194197
v.constantFieldCount = 0
195198
v.aggTempDeletes = nil
196199
v.aggPreserve = nil
200+
v.constantBase = false
197201

198202
joinPipes, err := v.processFrom(stmt.From)
199203
if err != nil {
@@ -433,11 +437,14 @@ func (v *selectTranslatorVisitor) buildDistinctPipe(fields []string, aggregated
433437

434438
func (v *selectTranslatorVisitor) processFrom(from ast.TableExpr) ([]string, error) {
435439
if from == nil {
436-
return nil, &TranslationError{
437-
Code: http.StatusBadRequest,
438-
Message: "translator: FROM clause is required",
439-
}
440+
v.baseAlias = ""
441+
v.baseUsesPipeline = true
442+
v.basePipeline = constantSelectBasePipeline
443+
v.baseFilter = ""
444+
v.constantBase = true
445+
return nil, nil
440446
}
447+
v.constantBase = false
441448

442449
switch t := from.(type) {
443450
case *ast.TableName:
@@ -1288,7 +1295,7 @@ func (v *selectTranslatorVisitor) prepareGroupByField(expr ast.Expr, index int)
12881295
return "", nil, err
12891296
}
12901297
return aliasName, []string{mathPipe}, nil
1291-
case *ast.BinaryExpr, *ast.UnaryExpr, *ast.NumericLiteral:
1298+
case *ast.BinaryExpr, *ast.UnaryExpr, *ast.NumericLiteral, *ast.StringLiteral:
12921299
alias := fmt.Sprintf("group_%d", index+1)
12931300
mathPipe, aliasName, err := v.translateMathProjection(expr, alias)
12941301
if err != nil {
@@ -2436,7 +2443,78 @@ func (v *selectTranslatorVisitor) buildProjectionPipes(columns []ast.SelectItem,
24362443
}
24372444
computedPipes = append(computedPipes, mathPipe)
24382445
fields = append(fields, formatFieldName(aliasName))
2439-
case *ast.BinaryExpr, *ast.UnaryExpr, *ast.NumericLiteral:
2446+
case *ast.NumericLiteral:
2447+
if aggregated {
2448+
groupField, ok, err := v.lookupGroupExpr(col.Expr)
2449+
if err != nil {
2450+
return nil, nil, err
2451+
}
2452+
if !ok {
2453+
return nil, nil, &TranslationError{
2454+
Code: http.StatusBadRequest,
2455+
Message: fmt.Sprintf("translator: unsupported expression %T in aggregated select", expr),
2456+
}
2457+
}
2458+
finalName := groupField
2459+
if alias := strings.TrimSpace(col.Alias); alias != "" {
2460+
formattedAlias := formatFieldName(alias)
2461+
if formattedAlias != groupField {
2462+
renamePairs = append(renamePairs, fmt.Sprintf("%s as %s", groupField, formattedAlias))
2463+
}
2464+
finalName = formattedAlias
2465+
}
2466+
fields = append(fields, finalName)
2467+
continue
2468+
}
2469+
aliasTrim := strings.TrimSpace(col.Alias)
2470+
if aliasTrim == "" && v.constantBase {
2471+
computedPipes = append(computedPipes, fmt.Sprintf("format %s", expr.Value))
2472+
continue
2473+
}
2474+
aliasName, err := makeProjectionAlias(aliasTrim, "literal", expr.Value)
2475+
if err != nil {
2476+
return nil, nil, err
2477+
}
2478+
pipe := fmt.Sprintf("format %s as %s", expr.Value, formatFieldName(aliasName))
2479+
computedPipes = append(computedPipes, pipe)
2480+
fields = append(fields, formatFieldName(aliasName))
2481+
case *ast.StringLiteral:
2482+
if aggregated {
2483+
groupField, ok, err := v.lookupGroupExpr(col.Expr)
2484+
if err != nil {
2485+
return nil, nil, err
2486+
}
2487+
if !ok {
2488+
return nil, nil, &TranslationError{
2489+
Code: http.StatusBadRequest,
2490+
Message: fmt.Sprintf("translator: unsupported expression %T in aggregated select", expr),
2491+
}
2492+
}
2493+
finalName := groupField
2494+
if alias := strings.TrimSpace(col.Alias); alias != "" {
2495+
formattedAlias := formatFieldName(alias)
2496+
if formattedAlias != groupField {
2497+
renamePairs = append(renamePairs, fmt.Sprintf("%s as %s", groupField, formattedAlias))
2498+
}
2499+
finalName = formattedAlias
2500+
}
2501+
fields = append(fields, finalName)
2502+
continue
2503+
}
2504+
aliasTrim := strings.TrimSpace(col.Alias)
2505+
value := quoteString(expr.Value)
2506+
if aliasTrim == "" && v.constantBase {
2507+
computedPipes = append(computedPipes, fmt.Sprintf("format %s", value))
2508+
continue
2509+
}
2510+
aliasName, err := makeProjectionAlias(aliasTrim, "literal", expr.Value)
2511+
if err != nil {
2512+
return nil, nil, err
2513+
}
2514+
pipe := fmt.Sprintf("format %s as %s", value, formatFieldName(aliasName))
2515+
computedPipes = append(computedPipes, pipe)
2516+
fields = append(fields, formatFieldName(aliasName))
2517+
case *ast.BinaryExpr, *ast.UnaryExpr:
24402518
if aggregated {
24412519
groupField, ok, err := v.lookupGroupExpr(col.Expr)
24422520
if err != nil {

lib/logsql/select_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,36 @@ func TestToLogsQLSuccess(t *testing.T) {
126126
sql: "SELECT * FROM logs OFFSET 3",
127127
expected: "* | offset 3",
128128
},
129+
{
130+
name: "select literal without from",
131+
sql: "SELECT 1",
132+
expected: "* | limit 1 | delete * | format 1",
133+
},
134+
{
135+
name: "select literal with alias without from",
136+
sql: "SELECT 1 AS one",
137+
expected: "* | limit 1 | delete * | format 1 as one | fields one",
138+
},
139+
{
140+
name: "select literal with from",
141+
sql: "SELECT 1 FROM logs",
142+
expected: "* | format 1 as literal_1 | fields literal_1",
143+
},
144+
{
145+
name: "select string literal without from",
146+
sql: "SELECT 'hello'",
147+
expected: "* | limit 1 | delete * | format \"hello\"",
148+
},
149+
{
150+
name: "select string literal with alias without from",
151+
sql: "SELECT 'hello' AS greeting",
152+
expected: "* | limit 1 | delete * | format \"hello\" as greeting | fields greeting",
153+
},
154+
{
155+
name: "select string literal with from",
156+
sql: "SELECT 'hello' FROM logs",
157+
expected: "* | format \"hello\" as literal_hello | fields literal_hello",
158+
},
129159
{
130160
name: "in list",
131161
sql: "SELECT * FROM logs WHERE service IN ('api', 'worker')",

0 commit comments

Comments
 (0)