mirror of https://github.com/grafana/loki
chore(engine): Improve type system of the new query engine (#17456)
**What this PR does / why we need it**: The original type system for the new engine was introduced before adding Apache Arrow and with that an additional type system. One aspect of the new type system of this PR is that the logical types of the engine are aligned/interchangeable with the arrow logical types. ```go type Type uint8 const ( NULL = Type(arrow.NULL) BOOL = Type(arrow.BOOL) STRING = Type(arrow.STRING) INT64 = Type(arrow.INT64) FLOAT64 = Type(arrow.FLOAT64) ) ``` The actual data types of Loki (`Boolean`, `String`, `Integer`, `Float`, `Timestamp`, `Duration`, and `Bytes`) are implemented as wrapper around a logical type. Different data types can use the same logical type as representation, such as `Integer`, `Timestamp` and `Duration` both use `INT64`. Since Arrow array types are bound to logical types, and one logical type can have multiple Loki data types, it is required to "annotate" Fields of an arrow.Record using its Metadata, similar to how it is done for distinguishing builtin columns from label/metadata/parsed columns. Additionally, the PR contains a mapping between Loki types and Arrow types. The `Literal` has been updated to use the new types. The old system also used UINT64 as representation of the `Timestamp` type, which is incorrect, since both LogQL and the DataObjects allow timestamps before unix epoch. --- Signed-off-by: Christian Haudum <christian.haudum@gmail.com>pull/17578/head
parent
4841b2abe2
commit
94b1d2d5ce
@ -0,0 +1,67 @@ |
||||
package datatype |
||||
|
||||
import "github.com/apache/arrow-go/v18/arrow" |
||||
|
||||
var ( |
||||
LokiType = struct { |
||||
Null DataType |
||||
Bool DataType |
||||
String DataType |
||||
Integer DataType |
||||
Float DataType |
||||
Timestamp DataType |
||||
Duration DataType |
||||
Bytes DataType |
||||
}{ |
||||
Null: Null, |
||||
Bool: Bool, |
||||
String: String, |
||||
Integer: Integer, |
||||
Float: Float, |
||||
Timestamp: Timestamp, |
||||
Duration: Duration, |
||||
Bytes: Bytes, |
||||
} |
||||
|
||||
ArrowType = struct { |
||||
Null arrow.DataType |
||||
Bool arrow.DataType |
||||
String arrow.DataType |
||||
Integer arrow.DataType |
||||
Float arrow.DataType |
||||
Timestamp arrow.DataType |
||||
Duration arrow.DataType |
||||
Bytes arrow.DataType |
||||
}{ |
||||
Null: arrow.Null, |
||||
Bool: arrow.FixedWidthTypes.Boolean, |
||||
String: arrow.BinaryTypes.String, |
||||
Integer: arrow.PrimitiveTypes.Int64, |
||||
Float: arrow.PrimitiveTypes.Float64, |
||||
Timestamp: arrow.PrimitiveTypes.Int64, |
||||
Duration: arrow.PrimitiveTypes.Int64, |
||||
Bytes: arrow.PrimitiveTypes.Int64, |
||||
} |
||||
|
||||
ToArrow = map[DataType]arrow.DataType{ |
||||
Null: ArrowType.Null, |
||||
Bool: ArrowType.Bool, |
||||
String: ArrowType.String, |
||||
Integer: ArrowType.Integer, |
||||
Float: ArrowType.Float, |
||||
Timestamp: ArrowType.Timestamp, |
||||
Duration: ArrowType.Duration, |
||||
Bytes: ArrowType.Bytes, |
||||
} |
||||
|
||||
ToLoki = map[arrow.DataType]DataType{ |
||||
ArrowType.Null: Null, |
||||
ArrowType.Bool: Bool, |
||||
ArrowType.String: String, |
||||
ArrowType.Integer: Integer, |
||||
ArrowType.Float: Float, |
||||
ArrowType.Timestamp: Timestamp, |
||||
ArrowType.Duration: Duration, |
||||
ArrowType.Bytes: Bytes, |
||||
} |
||||
) |
||||
@ -0,0 +1,240 @@ |
||||
package datatype |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strconv" |
||||
"time" |
||||
) |
||||
|
||||
type NullLiteral struct { |
||||
} |
||||
|
||||
// String implements Literal.
|
||||
func (n *NullLiteral) String() string { |
||||
return "null" |
||||
} |
||||
|
||||
// Type implements Literal.
|
||||
func (n *NullLiteral) Type() DataType { |
||||
return Null |
||||
} |
||||
|
||||
// Any implements Literal.
|
||||
func (n *NullLiteral) Any() any { |
||||
return nil |
||||
} |
||||
|
||||
func (n *NullLiteral) Value() any { |
||||
return nil |
||||
} |
||||
|
||||
type BoolLiteral struct { |
||||
v bool |
||||
} |
||||
|
||||
// String implements Literal.
|
||||
func (b *BoolLiteral) String() string { |
||||
return strconv.FormatBool(b.v) |
||||
} |
||||
|
||||
// Type implements Literal.
|
||||
func (b *BoolLiteral) Type() DataType { |
||||
return Bool |
||||
} |
||||
|
||||
// Any implements Literal.
|
||||
func (b *BoolLiteral) Any() any { |
||||
return b.v |
||||
} |
||||
|
||||
func (b *BoolLiteral) Value() bool { |
||||
return b.v |
||||
} |
||||
|
||||
type StringLiteral struct { |
||||
v string |
||||
} |
||||
|
||||
// String implements Literal.
|
||||
func (s *StringLiteral) String() string { |
||||
return fmt.Sprintf(`"%s"`, s.v) |
||||
} |
||||
|
||||
// Type implements Literal.
|
||||
func (s *StringLiteral) Type() DataType { |
||||
return String |
||||
} |
||||
|
||||
// Any implements Literal.
|
||||
func (s *StringLiteral) Any() any { |
||||
return s.v |
||||
} |
||||
|
||||
func (s *StringLiteral) Value() string { |
||||
return s.v |
||||
} |
||||
|
||||
type IntegerLiteral struct { |
||||
v int64 |
||||
} |
||||
|
||||
// String implements Literal.
|
||||
func (i *IntegerLiteral) String() string { |
||||
return strconv.FormatInt(i.v, 10) |
||||
} |
||||
|
||||
// Type implements Literal.
|
||||
func (i *IntegerLiteral) Type() DataType { |
||||
return Integer |
||||
} |
||||
|
||||
// Any implements Literal.
|
||||
func (i *IntegerLiteral) Any() any { |
||||
return i.v |
||||
} |
||||
|
||||
func (i *IntegerLiteral) Value() int64 { |
||||
return i.v |
||||
} |
||||
|
||||
type FloatLiteral struct { |
||||
v float64 |
||||
} |
||||
|
||||
// String implements Literal.
|
||||
func (f *FloatLiteral) String() string { |
||||
return strconv.FormatFloat(f.v, 'f', -1, 64) |
||||
} |
||||
|
||||
// Type implements Literal.
|
||||
func (f *FloatLiteral) Type() DataType { |
||||
return Float |
||||
} |
||||
|
||||
// Any implements Literal.
|
||||
func (f *FloatLiteral) Any() any { |
||||
return f.v |
||||
} |
||||
|
||||
func (f *FloatLiteral) Value() float64 { |
||||
return f.v |
||||
} |
||||
|
||||
type TimestampLiteral struct { |
||||
v time.Time |
||||
} |
||||
|
||||
// String implements Literal.
|
||||
func (t *TimestampLiteral) String() string { |
||||
return t.v.UTC().Format(time.RFC3339Nano) |
||||
} |
||||
|
||||
// Type implements Literal.
|
||||
func (t *TimestampLiteral) Type() DataType { |
||||
return Timestamp |
||||
} |
||||
|
||||
// Any implements Literal.
|
||||
func (t *TimestampLiteral) Any() any { |
||||
return t.v.UTC() |
||||
} |
||||
|
||||
func (t *TimestampLiteral) Value() time.Time { |
||||
return t.v |
||||
} |
||||
|
||||
type DurationLiteral struct { |
||||
v time.Duration |
||||
} |
||||
|
||||
// String implements Literal.
|
||||
func (d *DurationLiteral) String() string { |
||||
return d.v.String() |
||||
} |
||||
|
||||
// Type implements Literal.
|
||||
func (d *DurationLiteral) Type() DataType { |
||||
return Duration |
||||
} |
||||
|
||||
// Any implements Literal.
|
||||
func (d *DurationLiteral) Any() any { |
||||
return d.v |
||||
} |
||||
|
||||
func (d *DurationLiteral) Value() time.Duration { |
||||
return d.v |
||||
} |
||||
|
||||
type BytesLiteral struct { |
||||
v int64 |
||||
} |
||||
|
||||
// String implements Literal.
|
||||
func (b *BytesLiteral) String() string { |
||||
return fmt.Sprintf("%dB", b.v) |
||||
} |
||||
|
||||
// Type implements Literal.
|
||||
func (b *BytesLiteral) Type() DataType { |
||||
return Bytes |
||||
} |
||||
|
||||
// Any implements Literal.
|
||||
func (b *BytesLiteral) Any() any { |
||||
return b.v |
||||
} |
||||
|
||||
func (b *BytesLiteral) Value() int64 { |
||||
return b.v |
||||
} |
||||
|
||||
// Literal is holds a value of [any] typed as [DataType].
|
||||
type Literal interface { |
||||
fmt.Stringer |
||||
Any() any |
||||
Type() DataType |
||||
} |
||||
|
||||
var ( |
||||
_ Literal = (*NullLiteral)(nil) |
||||
_ Literal = (*BoolLiteral)(nil) |
||||
_ Literal = (*StringLiteral)(nil) |
||||
_ Literal = (*IntegerLiteral)(nil) |
||||
_ Literal = (*FloatLiteral)(nil) |
||||
_ Literal = (*TimestampLiteral)(nil) |
||||
_ Literal = (*DurationLiteral)(nil) |
||||
_ Literal = (*BytesLiteral)(nil) |
||||
) |
||||
|
||||
func NewNullLiteral() *NullLiteral { |
||||
return &NullLiteral{} |
||||
} |
||||
|
||||
func NewBoolLiteral(v bool) *BoolLiteral { |
||||
return &BoolLiteral{v: v} |
||||
} |
||||
|
||||
func NewStringLiteral(v string) *StringLiteral { |
||||
return &StringLiteral{v: v} |
||||
} |
||||
|
||||
func NewIntegerLiteral(v int64) *IntegerLiteral { |
||||
return &IntegerLiteral{v: v} |
||||
} |
||||
|
||||
func NewFloatLiteral(v float64) *FloatLiteral { |
||||
return &FloatLiteral{v: v} |
||||
} |
||||
|
||||
func NewTimestampLiteral(v time.Time) *TimestampLiteral { |
||||
return &TimestampLiteral{v: v} |
||||
} |
||||
|
||||
func NewDurationLiteral(v time.Duration) *DurationLiteral { |
||||
return &DurationLiteral{v: v} |
||||
} |
||||
|
||||
func NewBytesLiteral(v int64) *BytesLiteral { |
||||
return &BytesLiteral{v: v} |
||||
} |
||||
@ -0,0 +1,120 @@ |
||||
package datatype |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/apache/arrow-go/v18/arrow" |
||||
) |
||||
|
||||
type Type uint8 |
||||
|
||||
const ( |
||||
NULL = Type(arrow.NULL) |
||||
BOOL = Type(arrow.BOOL) |
||||
STRING = Type(arrow.STRING) |
||||
INT64 = Type(arrow.INT64) |
||||
FLOAT64 = Type(arrow.FLOAT64) |
||||
) |
||||
|
||||
func (t Type) String() string { |
||||
switch t { |
||||
case NULL: |
||||
return "NULL" |
||||
case BOOL: |
||||
return "BOOL" |
||||
case STRING: |
||||
return "STRING" |
||||
case INT64: |
||||
return "INT64" |
||||
case FLOAT64: |
||||
return "FLOAT64" |
||||
default: |
||||
return "INVALID" |
||||
} |
||||
} |
||||
|
||||
type DataType interface { |
||||
fmt.Stringer |
||||
ID() Type |
||||
ArrowType() arrow.DataType |
||||
} |
||||
|
||||
var ( |
||||
Null DataType = tNull{} |
||||
Bool DataType = tBool{} |
||||
String DataType = tString{} |
||||
Integer DataType = tInteger{} |
||||
Float DataType = tFloat{} |
||||
Timestamp DataType = tTimestamp{} |
||||
Duration DataType = tDuration{} |
||||
Bytes DataType = tBytes{} |
||||
) |
||||
|
||||
type tNull struct{} |
||||
|
||||
func (tNull) ID() Type { return NULL } |
||||
func (tNull) String() string { return "null" } |
||||
func (tNull) ArrowType() arrow.DataType { return ArrowType.Null } |
||||
|
||||
type tBool struct{} |
||||
|
||||
func (tBool) ID() Type { return BOOL } |
||||
func (tBool) String() string { return "bool" } |
||||
func (tBool) ArrowType() arrow.DataType { return ArrowType.Bool } |
||||
|
||||
type tString struct{} |
||||
|
||||
func (tString) ID() Type { return STRING } |
||||
func (tString) String() string { return "string" } |
||||
func (tString) ArrowType() arrow.DataType { return ArrowType.String } |
||||
|
||||
type tInteger struct{} |
||||
|
||||
func (tInteger) ID() Type { return INT64 } |
||||
func (tInteger) String() string { return "integer" } |
||||
func (tInteger) ArrowType() arrow.DataType { return ArrowType.Integer } |
||||
|
||||
type tFloat struct{} |
||||
|
||||
func (tFloat) ID() Type { return FLOAT64 } |
||||
func (tFloat) String() string { return "float" } |
||||
func (tFloat) ArrowType() arrow.DataType { return ArrowType.Float } |
||||
|
||||
type tTimestamp struct{} |
||||
|
||||
func (tTimestamp) ID() Type { return INT64 } |
||||
func (tTimestamp) String() string { return "timestamp" } |
||||
func (tTimestamp) ArrowType() arrow.DataType { return ArrowType.Integer } |
||||
|
||||
type tDuration struct{} |
||||
|
||||
func (tDuration) ID() Type { return INT64 } |
||||
func (tDuration) String() string { return "duration" } |
||||
func (tDuration) ArrowType() arrow.DataType { return ArrowType.Integer } |
||||
|
||||
type tBytes struct{} |
||||
|
||||
func (tBytes) ID() Type { return INT64 } |
||||
func (tBytes) String() string { return "bytes" } |
||||
func (tBytes) ArrowType() arrow.DataType { return ArrowType.Integer } |
||||
|
||||
var ( |
||||
names = map[string]DataType{ |
||||
Null.String(): Null, |
||||
Bool.String(): Bool, |
||||
String.String(): String, |
||||
Integer.String(): Integer, |
||||
Float.String(): Float, |
||||
Timestamp.String(): Timestamp, |
||||
Duration.String(): Duration, |
||||
Bytes.String(): Bytes, |
||||
} |
||||
) |
||||
|
||||
func FromString(dt string) DataType { |
||||
ty, ok := names[dt] |
||||
if !ok { |
||||
panic("invalid data type name") |
||||
} |
||||
return ty |
||||
} |
||||
@ -0,0 +1,19 @@ |
||||
package datatype |
||||
|
||||
import ( |
||||
"github.com/apache/arrow-go/v18/arrow" |
||||
|
||||
"github.com/grafana/loki/v3/pkg/engine/internal/types" |
||||
) |
||||
|
||||
var ( |
||||
ColumnMetadataBuiltinLine = ColumnMetadata(types.ColumnTypeBuiltin, String) |
||||
ColumnMetadataBuiltinTimestamp = ColumnMetadata(types.ColumnTypeBuiltin, Timestamp) |
||||
) |
||||
|
||||
func ColumnMetadata(ct types.ColumnType, dt DataType) arrow.Metadata { |
||||
return arrow.NewMetadata( |
||||
[]string{types.MetadataKeyColumnType, types.MetadataKeyColumnDataType}, |
||||
[]string{ct.String(), dt.String()}, |
||||
) |
||||
} |
||||
@ -1,135 +0,0 @@ |
||||
package types |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strconv" |
||||
) |
||||
|
||||
// Literal is holds a value of [ValueType].
|
||||
type Literal struct { |
||||
Value any |
||||
} |
||||
|
||||
// String returns the string representation of the literal value.
|
||||
func (l *Literal) String() string { |
||||
switch v := l.Value.(type) { |
||||
case nil: |
||||
return "NULL" |
||||
case bool: |
||||
return strconv.FormatBool(v) |
||||
case string: |
||||
return fmt.Sprintf(`"%s"`, v) |
||||
case float64: |
||||
return strconv.FormatFloat(v, 'f', -1, 64) |
||||
case int64: |
||||
return strconv.FormatInt(v, 10) |
||||
case uint64: |
||||
return strconv.FormatUint(v, 10) |
||||
case []byte: |
||||
return fmt.Sprintf("%v", v) |
||||
default: |
||||
return "invalid" |
||||
} |
||||
} |
||||
|
||||
// ValueType returns the kind of value represented by the literal.
|
||||
func (l *Literal) ValueType() ValueType { |
||||
switch l.Value.(type) { |
||||
case nil: |
||||
return ValueTypeNull |
||||
case bool: |
||||
return ValueTypeBool |
||||
case string: |
||||
return ValueTypeStr |
||||
case float64: |
||||
return ValueTypeFloat |
||||
case int64: |
||||
return ValueTypeInt |
||||
case uint64: |
||||
return ValueTypeTimestamp |
||||
case []byte: |
||||
return ValueTypeByteArray |
||||
default: |
||||
return ValueTypeInvalid |
||||
} |
||||
} |
||||
|
||||
// IsNull returns true if lit is a [ValueTypeNull] value.
|
||||
func (l Literal) IsNull() bool { |
||||
return l.ValueType() == ValueTypeNull |
||||
} |
||||
|
||||
// Str returns the value as a string. It panics if lit is not a [ValueTypeString].
|
||||
func (l Literal) Str() string { |
||||
if expect, actual := ValueTypeStr, l.ValueType(); expect != actual { |
||||
panic(fmt.Sprintf("literal type is %s, not %s", actual, expect)) |
||||
} |
||||
return l.Value.(string) |
||||
} |
||||
|
||||
// Int64 returns the value as an int64. It panics if lit is not a [ValueTypeFloat].
|
||||
func (l Literal) Float() float64 { |
||||
if expect, actual := ValueTypeFloat, l.ValueType(); expect != actual { |
||||
panic(fmt.Sprintf("literal type is %s, not %s", actual, expect)) |
||||
} |
||||
return l.Value.(float64) |
||||
} |
||||
|
||||
// Int returns the value as an int64. It panics if lit is not a [ValueTypeInt].
|
||||
func (l Literal) Int() int64 { |
||||
if expect, actual := ValueTypeInt, l.ValueType(); expect != actual { |
||||
panic(fmt.Sprintf("literal type is %s, not %s", actual, expect)) |
||||
} |
||||
return l.Value.(int64) |
||||
} |
||||
|
||||
// Timestamp returns the value as a uint64. It panics if lit is not a [ValueTypeTimestamp].
|
||||
func (l Literal) Timestamp() uint64 { |
||||
if expect, actual := ValueTypeTimestamp, l.ValueType(); expect != actual { |
||||
panic(fmt.Sprintf("literal type is %s, not %s", actual, expect)) |
||||
} |
||||
return l.Value.(uint64) |
||||
} |
||||
|
||||
// ByteArray returns the value as a byte slice. It panics if lit is not a [ValueTypeByteArray].
|
||||
func (l Literal) ByteArray() []byte { |
||||
if expect, actual := ValueTypeByteArray, l.ValueType(); expect != actual { |
||||
panic(fmt.Sprintf("literal type is %s, not %s", actual, expect)) |
||||
} |
||||
return l.Value.([]byte) |
||||
} |
||||
|
||||
// Convenience function for creating a NULL literal.
|
||||
func NullLiteral() Literal { |
||||
return Literal{Value: nil} |
||||
} |
||||
|
||||
// Convenience function for creating a bool literal.
|
||||
func BoolLiteral(v bool) Literal { |
||||
return Literal{Value: v} |
||||
} |
||||
|
||||
// Convenience function for creating a string literal.
|
||||
func StringLiteral(v string) Literal { |
||||
return Literal{Value: v} |
||||
} |
||||
|
||||
// Convenience function for creating a float literal.
|
||||
func FloatLiteral(v float64) Literal { |
||||
return Literal{Value: v} |
||||
} |
||||
|
||||
// Convenience function for creating a timestamp literal.
|
||||
func TimestampLiteral(v uint64) Literal { |
||||
return Literal{Value: v} |
||||
} |
||||
|
||||
// Convenience function for creating an integer literal.
|
||||
func IntLiteral(v int64) Literal { |
||||
return Literal{Value: v} |
||||
} |
||||
|
||||
// Convenience function for creating a byte array literal.
|
||||
func ByteArrayLiteral(v []byte) Literal { |
||||
return Literal{Value: v} |
||||
} |
||||
@ -1,83 +0,0 @@ |
||||
package types |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestLiteralRepresentation(t *testing.T) { |
||||
testCases := []struct { |
||||
name string |
||||
literal Literal |
||||
expected string |
||||
}{ |
||||
{ |
||||
name: "null literal", |
||||
literal: NullLiteral(), |
||||
expected: "NULL", |
||||
}, |
||||
{ |
||||
name: "bool literal - true", |
||||
literal: BoolLiteral(true), |
||||
expected: "true", |
||||
}, |
||||
{ |
||||
name: "bool literal - false", |
||||
literal: BoolLiteral(false), |
||||
expected: "false", |
||||
}, |
||||
{ |
||||
name: "string literal", |
||||
literal: StringLiteral("test"), |
||||
expected: `"test"`, |
||||
}, |
||||
{ |
||||
name: "string literal with quotes", |
||||
literal: StringLiteral(`test "quoted" string`), |
||||
expected: `"test "quoted" string"`, |
||||
}, |
||||
{ |
||||
name: "float literal - integer value", |
||||
literal: FloatLiteral(42.0), |
||||
expected: "42", |
||||
}, |
||||
{ |
||||
name: "float literal - decimal value", |
||||
literal: FloatLiteral(3.14159), |
||||
expected: "3.14159", |
||||
}, |
||||
{ |
||||
name: "int literal - positive", |
||||
literal: IntLiteral(123), |
||||
expected: "123", |
||||
}, |
||||
{ |
||||
name: "int literal - negative", |
||||
literal: IntLiteral(-456), |
||||
expected: "-456", |
||||
}, |
||||
{ |
||||
name: "timestamp literal", |
||||
literal: TimestampLiteral(1625097600000), |
||||
expected: "1625097600000", |
||||
}, |
||||
{ |
||||
name: "byte array literal", |
||||
literal: ByteArrayLiteral([]byte("hello")), |
||||
expected: "[104 101 108 108 111]", |
||||
}, |
||||
{ |
||||
name: "invalid literal", |
||||
literal: Literal{Value: make(chan int)}, // channels are not supported types
|
||||
expected: "invalid", |
||||
}, |
||||
} |
||||
|
||||
for _, tc := range testCases { |
||||
t.Run(tc.name, func(t *testing.T) { |
||||
result := tc.literal.String() |
||||
require.Equal(t, tc.expected, result) |
||||
}) |
||||
} |
||||
} |
||||
@ -1,47 +0,0 @@ |
||||
package types |
||||
|
||||
const ( |
||||
typeInvalid = "invalid" |
||||
) |
||||
|
||||
// ValueType represents the type of a value, which can either be a literal value, or a column value.
|
||||
type ValueType uint32 |
||||
|
||||
const ( |
||||
ValueTypeInvalid ValueType = iota // zero-value is an invalid type
|
||||
|
||||
ValueTypeNull // NULL value.
|
||||
ValueTypeBool // Boolean value
|
||||
ValueTypeFloat // 64bit floating point value
|
||||
ValueTypeInt // Signed 64bit integer value
|
||||
ValueTypeTimestamp // Unsigned 64bit integer value (nanosecond timestamp)
|
||||
ValueTypeStr // String value
|
||||
ValueTypeByteArray // Byte-slice value
|
||||
// ValueTypeBytes
|
||||
// ValueTypeDate
|
||||
// ValueTypeDuration
|
||||
) |
||||
|
||||
// String returns the string representation of the LiteralKind.
|
||||
func (t ValueType) String() string { |
||||
switch t { |
||||
case ValueTypeInvalid: |
||||
return typeInvalid |
||||
case ValueTypeNull: |
||||
return "null" |
||||
case ValueTypeBool: |
||||
return "bool" |
||||
case ValueTypeFloat: |
||||
return "float" |
||||
case ValueTypeInt: |
||||
return "int" |
||||
case ValueTypeTimestamp: |
||||
return "timestamp" |
||||
case ValueTypeStr: |
||||
return "string" |
||||
case ValueTypeByteArray: |
||||
return "[]byte" |
||||
default: |
||||
return typeInvalid |
||||
} |
||||
} |
||||
Loading…
Reference in new issue