Skip to content

Commit

Permalink
separate between AST and execution
Browse files Browse the repository at this point in the history
  • Loading branch information
tuannh982 committed Aug 22, 2023
1 parent e1ae427 commit fb0748d
Show file tree
Hide file tree
Showing 15 changed files with 857 additions and 0 deletions.
7 changes: 7 additions & 0 deletions cascades/cost/cost.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package cost

type Cost interface{}

type CostModel interface {
IsBetter(currentCost Cost, newCost Cost) bool
}
74 changes: 74 additions & 0 deletions cascades/logicalplan/ast2plan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package logicalplan

import "github.com/thanos-io/promql-engine/parser"

func NewLogicalPlan(expr *parser.Expr) LogicalPlan {
switch node := (*expr).(type) {
case *parser.StepInvariantExpr:
return &StepInvariantExpr{Expr: NewLogicalPlan(&node.Expr)}
case *parser.VectorSelector:
return &VectorSelector{
Name: node.Name,
OriginalOffset: node.OriginalOffset,
Offset: node.Offset,
Timestamp: node.Timestamp,
StartOrEnd: node.StartOrEnd,
LabelMatchers: node.LabelMatchers,
}
case *parser.MatrixSelector:
return &MatrixSelector{
VectorSelector: NewLogicalPlan(&node.VectorSelector),
Range: node.Range,
}
case *parser.AggregateExpr:
return &AggregateExpr{
Op: node.Op,
Expr: NewLogicalPlan(&node.Expr),
Param: NewLogicalPlan(&node.Param),
Grouping: node.Grouping,
Without: node.Without,
}
case *parser.Call:
var args []LogicalPlan
for i := range node.Args {
args = append(args, NewLogicalPlan(&node.Args[i]))
}
return &Call{
Func: node.Func,
Args: args,
}
case *parser.BinaryExpr:
return &BinaryExpr{
Op: node.Op,
LHS: NewLogicalPlan(&node.LHS),
RHS: NewLogicalPlan(&node.RHS),
VectorMatching: node.VectorMatching,
ReturnBool: node.ReturnBool,
}
case *parser.UnaryExpr:
return &UnaryExpr{
Op: node.Op,
Expr: NewLogicalPlan(&node.Expr),
}
case *parser.ParenExpr:
return &ParenExpr{
Expr: NewLogicalPlan(&node.Expr),
}
case *parser.SubqueryExpr:
return &SubqueryExpr{
Expr: NewLogicalPlan(&node.Expr),
Range: node.Range,
OriginalOffset: node.OriginalOffset,
Offset: node.Offset,
Timestamp: node.Timestamp,
StartOrEnd: node.StartOrEnd,
Step: node.Step,
}
// literal types
case *parser.NumberLiteral:
return &NumberLiteral{Val: node.Val}
case *parser.StringLiteral:
return &StringLiteral{Val: node.Val}
}
return nil // should never reach here
}
81 changes: 81 additions & 0 deletions cascades/logicalplan/ast2plan_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package logicalplan

import (
"github.com/stretchr/testify/require"
"github.com/thanos-io/promql-engine/parser"
"math"
"testing"
)

var ast2planTestCases = []struct {
input parser.Expr // The AST input.
expected LogicalPlan // The expected logical plan.
}{
{
input: &parser.NumberLiteral{Val: 1},
expected: &NumberLiteral{Val: 1},
},
{
input: &parser.NumberLiteral{Val: math.Inf(1)},
expected: &NumberLiteral{Val: math.Inf(1)},
},
{
input: &parser.NumberLiteral{Val: math.Inf(-1)},
expected: &NumberLiteral{Val: math.Inf(-1)},
},
{
input: &parser.BinaryExpr{
Op: parser.ADD,
LHS: &parser.NumberLiteral{Val: 1},
RHS: &parser.NumberLiteral{Val: 1},
},
expected: &BinaryExpr{
Op: parser.ADD,
LHS: &NumberLiteral{Val: 1},
RHS: &NumberLiteral{Val: 1},
},
},
{
input: &parser.BinaryExpr{
Op: parser.ADD,
LHS: &parser.NumberLiteral{Val: 1},
RHS: &parser.BinaryExpr{
Op: parser.DIV,
LHS: &parser.NumberLiteral{Val: 2},
RHS: &parser.ParenExpr{
Expr: &parser.BinaryExpr{
Op: parser.MUL,
LHS: &parser.NumberLiteral{Val: 3},
RHS: &parser.NumberLiteral{Val: 1},
},
},
},
},
expected: &BinaryExpr{
Op: parser.ADD,
LHS: &NumberLiteral{Val: 1},
RHS: &BinaryExpr{
Op: parser.DIV,
LHS: &NumberLiteral{Val: 2},
RHS: &ParenExpr{
Expr: &BinaryExpr{
Op: parser.MUL,
LHS: &NumberLiteral{Val: 3},
RHS: &NumberLiteral{Val: 1},
},
},
},
},
},
// TODO add tests
}

func TestAST2Plan(t *testing.T) {
for _, test := range ast2planTestCases {
t.Run(test.input.String(), func(t *testing.T) {
plan := NewLogicalPlan(&test.input)
require.True(t, plan != nil, "could not convert AST to logical plan")
require.Equal(t, test.expected, plan, "error on input '%s'", test.input.String())
})
}
}
156 changes: 156 additions & 0 deletions cascades/logicalplan/plan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package logicalplan

import (
"github.com/prometheus/prometheus/model/labels"
"github.com/thanos-io/promql-engine/parser"
"time"
)

type LogicalPlan interface {
Children() LogicalPlans
}

type LogicalPlans []LogicalPlan

// StepInvariantExpr represents a query which evaluates to the same result
// irrespective of the evaluation time given the raw samples from TSDB remain unchanged.
// Currently this is only used for engine optimisations and the parser does not produce this.
type StepInvariantExpr struct {
Expr LogicalPlan
}

func (l *StepInvariantExpr) Children() LogicalPlans {
return []LogicalPlan{l.Expr}
}

// VectorSelector represents a Vector selection.
type VectorSelector struct {
Name string
// OriginalOffset is the actual offset that was set in the query.
// This never changes.
OriginalOffset time.Duration
// Offset is the offset used during the query execution
// which is calculated using the original offset, at modifier time,
// eval time, and subquery offsets in the AST tree.
Offset time.Duration
Timestamp *int64
StartOrEnd parser.ItemType // Set when @ is used with start() or end()
LabelMatchers []*labels.Matcher
}

func (l *VectorSelector) Children() LogicalPlans {
return []LogicalPlan{}
}

// MatrixSelector represents a Matrix selection.
type MatrixSelector struct {
// It is safe to assume that this is an VectorSelector
// if the parser hasn't returned an error.
VectorSelector LogicalPlan
Range time.Duration
}

func (l *MatrixSelector) Children() LogicalPlans {
return []LogicalPlan{l.VectorSelector}
}

// AggregateExpr represents an aggregation operation on a Vector.
type AggregateExpr struct {
Op parser.ItemType // The used aggregation operation.
Expr LogicalPlan // The Vector expression over which is aggregated.
Param LogicalPlan // Parameter used by some aggregators.
Grouping []string // The labels by which to group the Vector.
Without bool // Whether to drop the given labels rather than keep them.
}

func (l *AggregateExpr) Children() LogicalPlans {
return []LogicalPlan{l.Expr, l.Param}
}

// Call represents a function call.
type Call struct {
Func *parser.Function // The function that was called.
Args LogicalPlans // Arguments used in the call.
}

func (l *Call) Children() LogicalPlans {
return l.Args
}

// BinaryExpr represents a binary expression between two child expressions.
type BinaryExpr struct {
Op parser.ItemType // The operation of the expression.
LHS, RHS LogicalPlan // The operands on the respective sides of the operator.

// The matching behavior for the operation if both operands are Vectors.
// If they are not this field is nil.
VectorMatching *parser.VectorMatching

// If a comparison operator, return 0/1 rather than filtering.
ReturnBool bool
}

func (l *BinaryExpr) Children() LogicalPlans {
return []LogicalPlan{l.LHS, l.RHS}
}

// UnaryExpr represents a unary operation on another expression.
// Currently unary operations are only supported for Scalars.
type UnaryExpr struct {
Op parser.ItemType
Expr LogicalPlan
}

func (l *UnaryExpr) Children() LogicalPlans {
return []LogicalPlan{l.Expr}
}

// ParenExpr wraps an expression so it cannot be disassembled as a consequence
// of operator precedence.
type ParenExpr struct {
Expr LogicalPlan
}

func (l *ParenExpr) Children() LogicalPlans {
return []LogicalPlan{l.Expr}
}

// SubqueryExpr represents a subquery.
type SubqueryExpr struct {
Expr LogicalPlan
Range time.Duration
// OriginalOffset is the actual offset that was set in the query.
// This never changes.
OriginalOffset time.Duration
// Offset is the offset used during the query execution
// which is calculated using the original offset, at modifier time,
// eval time, and subquery offsets in the AST tree.
Offset time.Duration
Timestamp *int64
StartOrEnd parser.ItemType // Set when @ is used with start() or end()
Step time.Duration
}

func (l *SubqueryExpr) Children() LogicalPlans {
return []LogicalPlan{l.Expr}
}

// literal types

// NumberLiteral represents a number.
type NumberLiteral struct {
Val float64
}

func (l *NumberLiteral) Children() LogicalPlans {
return []LogicalPlan{}
}

// StringLiteral represents a string.
type StringLiteral struct {
Val string
}

func (l *StringLiteral) Children() LogicalPlans {
return []LogicalPlan{}
}
20 changes: 20 additions & 0 deletions cascades/memo/explore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package memo

const (
InitialExplorationRound ExplorationRound = 0
)

type ExplorationRound int // The exploration round.
type ExplorationMark int // The exploration mark, if i-th bit of the mark is set, then i-th round is explored.

func (e *ExplorationMark) IsExplored(round ExplorationRound) bool {
return (*e & (1 << round)) != 0
}

func (e *ExplorationMark) SetExplore(round ExplorationRound, explored bool) {
if explored {
*e |= 1 << round
} else {
*e &= ^(1 << round)
}
}
Loading

0 comments on commit fb0748d

Please sign in to comment.