Skip to content

Commit 2704b2e

Browse files
chore(engine): add protobuf representation of physical plans
This adds two new packages, expressionpb and physicalpb, which are serializable representations of physical.Expression and physical.Plan, respectively. These packages include utility functions to convert between the protobuf representations and the planner types. A translation layer is used due to the complexity of integrating protobuf throughout the engine, as well as difficulties with finding a clean pattern to construct node types. #19638 took an initial attempt at fully integrating the protobuf types, but revealed that it is very challenging. While investiating the code, I observed that it's very clunky to work with the protobuf types, especailly with how often we rely on interface values. It's clear to me that we will want to eventually remove our translation layer, but doing it too soon means needing to update the entire engine code path twice. It is a much safer bet to start with a translation layer, find the right abstraction for constructing the protobuf, and then migrate once we have confidence in the pattern. Co-authored-by: Sophie Waldman <[email protected]>
1 parent af771c2 commit 2704b2e

20 files changed

+15251
-0
lines changed

pkg/engine/internal/proto/expressionpb/expressionpb.pb.go

Lines changed: 5546 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
syntax = "proto3";
2+
3+
package loki.expression;
4+
5+
option go_package = "github.com/grafana/loki/v3/pkg/engine/internal/proto/expressionpb";
6+
7+
// Expression represents an expression used to compute values in output arrays.
8+
message Expression {
9+
oneof kind {
10+
UnaryExpression unary = 1;
11+
BinaryExpression binary = 2;
12+
VariadicExpression variadic = 3;
13+
LiteralExpression literal = 4;
14+
ColumnExpression column = 5;
15+
}
16+
}
17+
18+
// UnaryExpression represents a unary operation applied to an expression.
19+
message UnaryExpression {
20+
UnaryOp op = 1;
21+
Expression value = 2;
22+
}
23+
24+
// UnaryOp denotes the kind of unary operation to perform.
25+
enum UnaryOp {
26+
UNARY_OP_INVALID = 0; // Invalid unary operation.
27+
UNARY_OP_NOT = 1; // Logical NOT operation (!).
28+
UNARY_OP_ABS = 2; // Mathematical absolute operation (abs).
29+
UNARY_OP_CAST_FLOAT = 3; // Cast string to float value operation (unwrap).
30+
UNARY_OP_CAST_BYTES = 4; // Cast string bytes to float value operation (unwrap).
31+
UNARY_OP_CAST_DURATION = 5; // Cast string duration to float value operation (unwrap).
32+
}
33+
34+
// BinaryExpression represents a binary operation applied to two expressions.
35+
message BinaryExpression {
36+
BinaryOp op = 1;
37+
Expression left = 2;
38+
Expression right = 3;
39+
}
40+
41+
// BinaryOp denotes the kind of binary operation to perform.
42+
enum BinaryOp {
43+
BINARY_OP_INVALID = 0; // Invalid binary operation.
44+
45+
BINARY_OP_EQ = 1; // Equality comparison (==).
46+
BINARY_OP_NEQ = 2; // Inequality comparison (!=).
47+
BINARY_OP_GT = 3; // Greater than comparison (>).
48+
BINARY_OP_GTE = 4; // Greater than or equal comparison (>=).
49+
BINARY_OP_LT = 5; // Less than comparison (<).
50+
BINARY_OP_LTE = 6; // Less than or equal comparison (<=).
51+
52+
BINARY_OP_AND = 7; // Logical AND operation (&&).
53+
BINARY_OP_OR = 8; // Logical OR operation (||).
54+
BINARY_OP_XOR = 9; // Logical XOR operation (^).
55+
BINARY_OP_NOT = 10; // Logical NOT operation (!).
56+
57+
BINARY_OP_ADD = 11; // Addition operation (+).
58+
BINARY_OP_SUB = 12; // Subtraction operation (-).
59+
BINARY_OP_MUL = 13; // Multiplication operation (*).
60+
BINARY_OP_DIV = 14; // Division operation (/).
61+
BINARY_OP_MOD = 15; // Modulo operation (%).
62+
BINARY_OP_POW = 16; // power/exponentiation operation (^).
63+
64+
BINARY_OP_MATCH_SUBSTR = 17; // Substring matching operation (|=). Used for string match filter.
65+
BINARY_OP_NOT_MATCH_SUBSTR = 18; // Substring non-matching operation (!=). Used for string match filter.
66+
BINARY_OP_MATCH_RE = 19; // Regular expression matching operation (|~). Used for regex match filter and label matcher.
67+
BINARY_OP_NOT_MATCH_RE = 20; // Regular expression non-matching operation (!~). Used for regex match filter and label matcher.
68+
BINARY_OP_MATCH_PATTERN = 21; // Pattern matching operation (|>). Used for pattern match filter.
69+
BINARY_OP_NOT_MATCH_PATTERN = 22; // Pattern non-matching operation (!>). Use for pattern match filter.
70+
}
71+
72+
// VariadicExpression is an expression that executes a function with a variable
73+
// number of arguments.
74+
message VariadicExpression {
75+
VariadicOp op = 1;
76+
repeated Expression args = 2;
77+
}
78+
79+
// VariadicOp denotes the kind of variadic operation to execute.
80+
enum VariadicOp {
81+
VARIADIC_OP_INVALID = 0; // Invalid operation.
82+
83+
VARIADIC_OP_PARSE_LOGFMT = 1; // Parse logfmt text into a set of columns.
84+
VARIADIC_OP_PARSE_JSON = 2; // Parse JSON text into a set of columns.
85+
}
86+
87+
// LiteralExpression represents a constant literal value in an expression tree.
88+
message LiteralExpression {
89+
oneof kind {
90+
NullLiteral null_literal = 1;
91+
BoolLiteral bool_literal = 2;
92+
StringLiteral string_literal = 3;
93+
IntegerLiteral integer_literal = 4;
94+
FloatLiteral float_literal = 5;
95+
TimestampLiteral timestamp_literal = 6;
96+
DurationLiteral duration_literal = 7;
97+
BytesLiteral bytes_literal = 8;
98+
StringListLiteral string_list_literal = 9;
99+
}
100+
}
101+
102+
// NullLiteral represents a null literal value.
103+
message NullLiteral {}
104+
105+
// BoolLiteral represents a boolean literal value.
106+
message BoolLiteral {
107+
bool value = 1;
108+
}
109+
110+
// StringLiteral represents a string literal value.
111+
message StringLiteral {
112+
string value = 1;
113+
}
114+
115+
// IntegerLiteral represents a signed integer literal value.
116+
message IntegerLiteral {
117+
int64 value = 1;
118+
}
119+
120+
// FloatLiteral represents a floating point literal value.
121+
message FloatLiteral {
122+
double value = 1;
123+
}
124+
125+
// TimestampLiteral represents a timestamp literal value in nanoseconds since
126+
// the Unix epoch.
127+
message TimestampLiteral {
128+
int64 value = 1;
129+
}
130+
131+
// DurationLiteral represents a duration literal value in nanoseconds.
132+
message DurationLiteral {
133+
int64 value = 1;
134+
}
135+
136+
// BytesLiteral represents a bytes count literal value.
137+
message BytesLiteral {
138+
int64 value = 1;
139+
}
140+
141+
// StringListLiteral represents a list of string literal values.
142+
message StringListLiteral {
143+
repeated string value = 1;
144+
}
145+
146+
// ColumnExpression is an expression used to reference a column.
147+
message ColumnExpression {
148+
string name = 1; // Name of the column being referenced.
149+
ColumnType type = 2; // Type of the column being referenced.
150+
}
151+
152+
// ColumnType holds valid types of columns that can be referenced.
153+
enum ColumnType {
154+
COLUMN_TYPE_INVALID = 0; // Invalid column type.
155+
156+
COLUMN_TYPE_BUILTIN = 1; // COLUMN_TYPE_BUILTIN represents a builtin column (such as timestamp).
157+
COLUMN_TYPE_LABEL = 2; // COLUMN_TYPE_LABEL represents a column from a stream label.
158+
COLUMN_TYPE_METADATA = 3; // COLUMN_TYPE_METADATA represents a column from a log metadata.
159+
COLUMN_TYPE_PARSED = 4; // COLUMN_TYPE_PARSED represents a parsed column from a parser stage.
160+
COLUMN_TYPE_AMBIGUOUS = 5; // COLUMN_TYPE_AMBIGUOUS represents a column that can either be a builtin, label, metadata, or parsed.
161+
COLUMN_TYPE_GENERATED = 6; // COLUMN_TYPE_GENERATED represents a column that is generated from an expression or computation.
162+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package expressionpb
2+
3+
import (
4+
fmt "fmt"
5+
6+
"github.com/grafana/loki/v3/pkg/engine/internal/planner/physical"
7+
"github.com/grafana/loki/v3/pkg/engine/internal/types"
8+
)
9+
10+
type marshaler interface {
11+
MarshalPhysical() (physical.Expression, error)
12+
}
13+
14+
// MarshalPhysical converts a protobuf expression into a physical plan
15+
// expression. Returns an error if the conversion fails or is unsupported.
16+
func (e *Expression) MarshalPhysical() (physical.Expression, error) {
17+
m, ok := e.Kind.(marshaler)
18+
if !ok {
19+
return nil, fmt.Errorf("unsupported physical expression type: %T", e.Kind)
20+
}
21+
return m.MarshalPhysical()
22+
}
23+
24+
// MarshalPhysical converts a protobuf expression into a physical plan
25+
// expression. Returns an error if the conversion fails or is unsupported.
26+
func (e *Expression_Unary) MarshalPhysical() (physical.Expression, error) {
27+
return e.Unary.MarshalPhysical()
28+
}
29+
30+
// MarshalPhysical converts a protobuf expression into a physical plan
31+
// expression. Returns an error if the conversion fails or is unsupported.
32+
func (e *Expression_Binary) MarshalPhysical() (physical.Expression, error) {
33+
return e.Binary.MarshalPhysical()
34+
}
35+
36+
// MarshalPhysical converts a protobuf expression into a physical plan
37+
// expression. Returns an error if the conversion fails or is unsupported.
38+
func (e *Expression_Variadic) MarshalPhysical() (physical.Expression, error) {
39+
return e.Variadic.MarshalPhysical()
40+
}
41+
42+
// MarshalPhysical converts a protobuf expression into a physical plan
43+
// expression. Returns an error if the conversion fails or is unsupported.
44+
func (e *Expression_Literal) MarshalPhysical() (physical.Expression, error) {
45+
return e.Literal.MarshalPhysical()
46+
}
47+
48+
// MarshalPhysical converts a protobuf expression into a physical plan
49+
// expression. Returns an error if the conversion fails or is unsupported.
50+
func (e *Expression_Column) MarshalPhysical() (physical.Expression, error) {
51+
return e.Column.MarshalPhysical()
52+
}
53+
54+
// MarshalPhysical converts a protobuf expression into a physical plan
55+
// expression. Returns an error if the conversion fails or is unsupported.
56+
func (e *UnaryExpression) MarshalPhysical() (physical.Expression, error) {
57+
value, err := e.Value.MarshalPhysical()
58+
if err != nil {
59+
return nil, err
60+
}
61+
62+
op, err := e.Op.MarshalType()
63+
if err != nil {
64+
return nil, err
65+
}
66+
67+
return &physical.UnaryExpr{
68+
Op: op,
69+
Left: value,
70+
}, nil
71+
}
72+
73+
// MarshalPhysical converts a protobuf expression into a physical plan
74+
// expression. Returns an error if the conversion fails or is unsupported.
75+
func (e *BinaryExpression) MarshalPhysical() (physical.Expression, error) {
76+
left, err := e.Left.MarshalPhysical()
77+
if err != nil {
78+
return nil, err
79+
}
80+
right, err := e.Right.MarshalPhysical()
81+
if err != nil {
82+
return nil, err
83+
}
84+
85+
op, err := e.Op.MarshalType()
86+
if err != nil {
87+
return nil, err
88+
}
89+
90+
return &physical.BinaryExpr{
91+
Op: op,
92+
Left: left,
93+
Right: right,
94+
}, nil
95+
}
96+
97+
// MarshalPhysical converts a protobuf expression into a physical plan
98+
// expression. Returns an error if the conversion fails or is unsupported.
99+
func (e *VariadicExpression) MarshalPhysical() (physical.Expression, error) {
100+
expressions := make([]physical.Expression, len(e.Args))
101+
for i, arg := range e.Args {
102+
expr, err := arg.MarshalPhysical()
103+
if err != nil {
104+
return nil, err
105+
}
106+
expressions[i] = expr
107+
}
108+
109+
op, err := e.Op.MarshalType()
110+
if err != nil {
111+
return nil, err
112+
}
113+
114+
return &physical.VariadicExpr{
115+
Op: op,
116+
Expressions: expressions,
117+
}, nil
118+
}
119+
120+
// MarshalPhysical converts a protobuf expression into a physical plan
121+
// expression. Returns an error if the conversion fails or is unsupported.
122+
func (e *LiteralExpression) MarshalPhysical() (physical.Expression, error) {
123+
m, ok := e.Kind.(literalMarshaler)
124+
if !ok {
125+
return nil, fmt.Errorf("unsupported literal expression type: %T", e.Kind)
126+
}
127+
literal, err := m.MarshalLiteral()
128+
if err != nil {
129+
return nil, err
130+
}
131+
132+
return &physical.LiteralExpr{Literal: literal}, nil
133+
}
134+
135+
// MarshalPhysical converts a protobuf expression into a physical plan
136+
// expression. Returns an error if the conversion fails or is unsupported.
137+
func (e *ColumnExpression) MarshalPhysical() (physical.Expression, error) {
138+
columnType, err := e.Type.MarshalType()
139+
if err != nil {
140+
return nil, err
141+
}
142+
143+
return &physical.ColumnExpr{
144+
Ref: types.ColumnRef{
145+
Column: e.Name,
146+
Type: columnType,
147+
},
148+
}, nil
149+
}

0 commit comments

Comments
 (0)