Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ require (
github.com/btcsuite/btcutil v1.0.2
github.com/prequel-dev/prequel-logmatch v0.0.13
github.com/rs/zerolog v1.34.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
)

Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
26 changes: 10 additions & 16 deletions pkg/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,17 @@ func (b *builderT) buildTree(parserNode *parser.NodeT, parentMachineAddress *Ast
)

// Build children (either matcher children or nested machines)
if isMatcherNode(parserNode) {
if parserNode.IsMatcherNode() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

if matchNode, err = b.buildMatcherChildren(parserNode, machineAddress, termIdx); err != nil {
return nil, err
}
children = append(children, matchNode)
} else if parserNode.IsPromNode() {
if matchNode, err = b.buildPromQLNode(parserNode, machineAddress, termIdx); err != nil {
return nil, err
}
children = append(children, matchNode)

} else {
if children, err = b.buildMachineChildren(parserNode, machineAddress); err != nil {
return nil, err
Expand Down Expand Up @@ -210,20 +216,6 @@ func newAstNode(parserNode *parser.NodeT, typ schema.NodeTypeT, scope string, pa
}
}

func isMatcherNode(node *parser.NodeT) bool {
var (
hasMatcher = true
)

for _, child := range node.Children {
if _, ok := child.(*parser.MatcherT); !ok {
hasMatcher = false
}
}

return hasMatcher
}

func (b *builderT) buildMatcherChildren(parserNode *parser.NodeT, machineAddress *AstNodeAddressT, termIdx *uint32) (*AstNodeT, error) {

var (
Expand Down Expand Up @@ -265,6 +257,8 @@ func (b *builderT) buildMatcherNodes(parserNode *parser.NodeT, machineAddress *A
switch parserNode.Metadata.Type {
case schema.NodeTypeLogSeq:
case schema.NodeTypeLogSet:
case schema.NodeTypePromQL:
return b.buildPromQLNode(parserNode, machineAddress, termIdx)
default:
return nil, parserNode.WrapError(ErrInvalidNodeType)
}
Expand Down Expand Up @@ -372,7 +366,7 @@ func (b *builderT) buildStateMachine(parserNode *parser.NodeT, parentMachineAddr
Msg("Window is required for sequences")
return nil, parserNode.WrapError(ErrInvalidWindow)
}
case schema.NodeTypeSet, schema.NodeTypeLogSet:
case schema.NodeTypeSet, schema.NodeTypeLogSet, schema.NodeTypePromQL:
default:
log.Error().
Any("address", machineAddress).
Expand Down
1 change: 1 addition & 0 deletions pkg/ast/ast_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ func newMatchTerm(field parser.FieldT) (AstFieldT, error) {
}

return t, nil

}

func newNegateTerm(field parser.FieldT, anchors uint32) (AstFieldT, error) {
Expand Down
22 changes: 14 additions & 8 deletions pkg/ast/ast_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,31 @@ type AstSetMatcherT struct {

func (b *builderT) buildMachineNode(parserNode *parser.NodeT, parentMachineAddress, machineAddress *AstNodeAddressT, children []*AstNodeT) (*AstNodeT, error) {
var (
seqMatcher *AstSeqMatcherT
setMatcher *AstSetMatcherT
matchNode = newAstNode(parserNode, parserNode.Metadata.Type, schema.ScopeCluster, parentMachineAddress, machineAddress)
err error
matchNode = newAstNode(parserNode, parserNode.Metadata.Type, schema.ScopeCluster, parentMachineAddress, machineAddress)
)

switch parserNode.Metadata.Type {
case schema.NodeTypeSeq, schema.NodeTypeLogSeq:
matchNode.Metadata.Type = schema.NodeTypeSeq
if seqMatcher, err = buildSeqMatcher(parserNode, children); err != nil {
if seqMatcher, err := buildSeqMatcher(parserNode, children); err != nil {
return nil, err
} else {
matchNode.Object = seqMatcher
}
matchNode.Object = seqMatcher
case schema.NodeTypeSet, schema.NodeTypeLogSet:
matchNode.Metadata.Type = schema.NodeTypeSet
if setMatcher, err = buildSetMatcher(parserNode, children); err != nil {
if setMatcher, err := buildSetMatcher(parserNode, children); err != nil {
return nil, err
} else {
matchNode.Object = setMatcher
}
case schema.NodeTypePromQL:
matchNode.Metadata.Type = schema.NodeTypePromQL
if promMatcher, err := b.buildPromQLNode(parserNode, machineAddress, nil); err != nil {
return nil, err
} else {
matchNode.Object = promMatcher
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent pattern: buildPromQLNode returns *AstNodeT while buildSeqMatcher and buildSetMatcher return *AstSeqMatcherT and *AstSetMatcherT respectively. Assigning the entire node (promMatcher) to matchNode.Object is incorrect - it should assign promMatcher.Object (which contains the *AstPromQL) to be consistent with the other cases. Alternatively, buildPromQLNode should be refactored to return *AstPromQL directly like the other builder functions.

Suggested change
matchNode.Object = promMatcher
matchNode.Object = promMatcher.Object

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I did this right. @tonymeehan ?

}
matchNode.Object = setMatcher
default:
log.Error().
Str("type", parserNode.Metadata.Type.String()).
Expand Down
69 changes: 69 additions & 0 deletions pkg/ast/ast_metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package ast

import (
"time"

"github.com/prequel-dev/prequel-compiler/pkg/parser"
"github.com/prequel-dev/prequel-compiler/pkg/schema"
"github.com/rs/zerolog/log"
)

type AstPromQL struct {
Expr string
For time.Duration
Interval time.Duration
Event *AstEventT
}

func (b *builderT) buildPromQLNode(parserNode *parser.NodeT, machineAddress *AstNodeAddressT, termIdx *uint32) (*AstNodeT, error) {

// Expects one child of type ParsePromQL

if len(parserNode.Children) != 1 {
log.Error().Int("child_count", len(parserNode.Children)).Msg("PromQL node must have exactly one child")
return nil, parserNode.WrapError(ErrInvalidNodeType)
}

promNode, ok := parserNode.Children[0].(*parser.PromQLT)

if !ok {
log.Error().Any("promql", parserNode.Children[0]).Msg("Failed to build PromQL node")
return nil, parserNode.WrapError(ErrMissingScalar)
}

if promNode.Expr == "" {
log.Error().Msg("PromQL Expr string is empty")
return nil, parserNode.WrapError(ErrMissingScalar)
}

pn := &AstPromQL{
Expr: promNode.Expr,
}

if parserNode.Metadata.Event != nil {
if parserNode.Metadata.Event.Origin {
b.HasOrigin = true
}
pn.Event = &AstEventT{
Source: parserNode.Metadata.Event.Source,
Origin: parserNode.Metadata.Event.Origin,
}
}

if promNode.Interval != nil {
pn.Interval = *promNode.Interval
}

if promNode.For != nil {
pn.For = *promNode.For
}

var (
address = b.newAstNodeAddress(parserNode.Metadata.RuleHash, parserNode.Metadata.Type.String(), termIdx)
node = newAstNode(parserNode, parserNode.Metadata.Type, schema.ScopeCluster, machineAddress, address)
)

node.Object = pn
return node, nil

}
6 changes: 5 additions & 1 deletion pkg/ast/ast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ func TestAstSuccess(t *testing.T) {
rule: testdata.TestSuccessSimpleExtraction,
expectedNodeTypes: []string{"machine_seq", "log_seq"},
},
"Success_PromQLMetric": {
rule: testdata.TestSuccessSimplePromQL,
expectedNodeTypes: []string{"machine_set", "promql", "log_set"},
},
}

for name, test := range tests {
Expand Down Expand Up @@ -172,7 +176,7 @@ func TestAstFail(t *testing.T) {
"Fail_TermsSemanticError4": {
rule: testdata.TestFailTermsSemanticError4,
err: ErrInvalidEventType,
line: 16,
line: 14,
col: 11,
},
"Fail_TermsSemanticError5": {
Expand Down
2 changes: 1 addition & 1 deletion pkg/datasrc/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"os"
"time"

"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)

// version: 0.0.1
Expand Down
28 changes: 19 additions & 9 deletions pkg/parser/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ type ParseTermT struct {
Set *ParseSetT `yaml:"set,omitempty"`
Sequence *ParseSequenceT `yaml:"sequence,omitempty"`
NegateOpts *ParseNegateOptsT `yaml:",inline,omitempty"`
PromQL *ParsePromQL `yaml:"promql,omitempty"`
Extract []ParseExtractT `yaml:"extract,omitempty"`
}

Expand All @@ -118,22 +119,30 @@ type ParseExtractT struct {
RegexValue string `yaml:"regex,omitempty"`
}

type ParsePromQL struct {
Expr string `yaml:"expr"`
Interval string `yaml:"interval,omitempty"`
For string `yaml:"for,omitempty"`
Event *ParseEventT `yaml:"event,omitempty"`
}

func (o *ParseTermT) UnmarshalYAML(unmarshal func(any) error) error {
var str string
if err := unmarshal(&str); err == nil {
o.StrValue = str
return nil
}
var temp struct {
Field string `yaml:"field,omitempty"`
StrValue string `yaml:"value,omitempty"`
JqValue string `yaml:"jq,omitempty"`
RegexValue string `yaml:"regex,omitempty"`
Count int `yaml:"count,omitempty"`
Set *ParseSetT `yaml:"set,omitempty"`
Sequence *ParseSequenceT `yaml:"sequence,omitempty"`
NegateOpts *ParseNegateOptsT `yaml:",inline,omitempty"`
Extract []ParseExtractT `yaml:"extract,omitempty"`
Field string `yaml:"field,omitempty"`
StrValue string `yaml:"value,omitempty"`
JqValue string `yaml:"jq,omitempty"`
RegexValue string `yaml:"regex,omitempty"`
Count int `yaml:"count,omitempty"`
Set *ParseSetT `yaml:"set,omitempty"`
Sequence *ParseSequenceT `yaml:"sequence,omitempty"`
NegateOpts *ParseNegateOptsT `yaml:",inline,omitempty"`
ParsePromQL *ParsePromQL `yaml:"promql,omitempty"`
Extract []ParseExtractT `yaml:"extract,omitempty"`
}
if err := unmarshal(&temp); err != nil {
return err
Expand All @@ -146,6 +155,7 @@ func (o *ParseTermT) UnmarshalYAML(unmarshal func(any) error) error {
o.Set = temp.Set
o.Sequence = temp.Sequence
o.NegateOpts = temp.NegateOpts
o.PromQL = temp.ParsePromQL
o.Extract = temp.Extract
return nil
}
Expand Down
15 changes: 10 additions & 5 deletions pkg/parser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,20 @@ func TestParseSuccess(t *testing.T) {
expectedNegIndexes: []int{-1, 2, 2, -1, -1, -1, -1},
},
"Success_MissingRuleId": {
rule: testdata.TestFailMissingRuleIdRule,
rule: testdata.TestFailMissingRuleIdRule,
expectedNodeTypes: []string{"log_set"},
expectedNegIndexes: []int{-1},
},
"Success_MissingRuleHash": {
rule: testdata.TestFailMissingRuleHashRule,
},
"Success_MissingRuleHash": {
rule: testdata.TestFailMissingRuleHashRule,
expectedNodeTypes: []string{"log_set"},
expectedNegIndexes: []int{-1},
},
},
"Success_PromQL": {
rule: testdata.TestSuccessSimplePromQL,
expectedNodeTypes: []string{"machine_set", "promql", "log_set"},
expectedNegIndexes: []int{-1, -1, -1},
},
}

for name, test := range tests {
Expand Down
Loading