Files
grafana/pkg/expr/sql/frame_db_conv.go
Kyle Brandt d64f41afdc SQL Expressions: Re-implement feature using go-mysql-server (#99521)
* Under feature flag `sqlExpressions` and is experimental
* Excluded from arm32
* Will not work with the Query Service yet
* Does not have limits in place yet
* Does not working with alerting yet
* Currently requires "prepare time series" Transform for time series viz
 
---------

Co-authored-by: Sam Jewell <sam.jewell@grafana.com>
2025-02-06 07:27:28 -05:00

475 lines
12 KiB
Go

//go:build !arm
package sql
import (
"errors"
"fmt"
"io"
"time"
mysql "github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/types"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/shopspring/decimal"
)
// TODO: Should this accept a row limit and converters, like sqlutil.FrameFromRows?
func convertToDataFrame(ctx *mysql.Context, iter mysql.RowIter, schema mysql.Schema) (*data.Frame, error) {
f := &data.Frame{}
// Create fields based on the schema
for _, col := range schema {
fT, err := MySQLColToFieldType(col)
if err != nil {
return nil, err
}
field := data.NewFieldFromFieldType(fT, 0)
field.Name = col.Name
f.Fields = append(f.Fields, field)
}
// Iterate through the rows and append data to fields
for {
row, err := iter.Next(ctx)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return nil, fmt.Errorf("error reading row: %v", err)
}
for i, val := range row {
v, err := fieldValFromRowVal(f.Fields[i].Type(), val)
if err != nil {
return nil, fmt.Errorf("unexpected type for column %s: %w", schema[i].Name, err)
}
f.Fields[i].Append(v)
}
}
return f, nil
}
// MySQLColToFieldType converts a MySQL column to a data.FieldType
func MySQLColToFieldType(col *mysql.Column) (data.FieldType, error) {
var fT data.FieldType
switch col.Type {
case types.Int8:
fT = data.FieldTypeInt8
case types.Uint8:
fT = data.FieldTypeUint8
case types.Int16:
fT = data.FieldTypeInt16
case types.Uint16:
fT = data.FieldTypeUint16
case types.Int32:
fT = data.FieldTypeInt32
case types.Uint32:
fT = data.FieldTypeUint32
case types.Int64:
fT = data.FieldTypeInt64
case types.Uint64:
fT = data.FieldTypeUint64
case types.Float64:
fT = data.FieldTypeFloat64
// StringType represents all string types, including VARCHAR and BLOB.
case types.Text, types.LongText:
fT = data.FieldTypeString
case types.Timestamp:
fT = data.FieldTypeTime
case types.Datetime:
fT = data.FieldTypeTime
case types.Boolean:
fT = data.FieldTypeBool
default:
if types.IsDecimal(col.Type) {
fT = data.FieldTypeFloat64
} else {
return fT, fmt.Errorf("unsupported type for column %s of type %v", col.Name, col.Type)
}
}
if col.Nullable {
fT = fT.NullableType()
}
return fT, nil
}
// Helper function to convert data.FieldType to types.Type
func convertDataType(fieldType data.FieldType) mysql.Type {
switch fieldType {
case data.FieldTypeInt8, data.FieldTypeNullableInt8:
return types.Int8
case data.FieldTypeUint8, data.FieldTypeNullableUint8:
return types.Uint8
case data.FieldTypeInt16, data.FieldTypeNullableInt16:
return types.Int16
case data.FieldTypeUint16, data.FieldTypeNullableUint16:
return types.Uint16
case data.FieldTypeInt32, data.FieldTypeNullableInt32:
return types.Int32
case data.FieldTypeUint32, data.FieldTypeNullableUint32:
return types.Uint32
case data.FieldTypeInt64, data.FieldTypeNullableInt64:
return types.Int64
case data.FieldTypeUint64, data.FieldTypeNullableUint64:
return types.Uint64
case data.FieldTypeFloat32, data.FieldTypeNullableFloat32:
return types.Float32
case data.FieldTypeFloat64, data.FieldTypeNullableFloat64:
return types.Float64
case data.FieldTypeString, data.FieldTypeNullableString:
return types.Text
case data.FieldTypeBool, data.FieldTypeNullableBool:
return types.Boolean
case data.FieldTypeTime, data.FieldTypeNullableTime:
return types.Timestamp
default:
fmt.Printf("------- Unsupported field type: %v", fieldType)
return types.JSON
}
}
// fieldValFromRowVal converts a go-mysql-server row value to a data.field value
//
//nolint:gocyclo
func fieldValFromRowVal(fieldType data.FieldType, val interface{}) (interface{}, error) {
// the input val may be nil, it also may not be a pointer even if the fieldtype is a nullable pointer type
if val == nil {
return nil, nil
}
switch fieldType {
// ----------------------------
// Int8 / Nullable Int8
// ----------------------------
case data.FieldTypeInt8:
v, ok := val.(int8)
if !ok {
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int8", val, val)
}
return v, nil
case data.FieldTypeNullableInt8:
vP, ok := val.(*int8)
if ok {
return vP, nil
}
v, ok := val.(int8)
if ok {
return &v, nil
}
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int8 or *int8", val, val)
// ----------------------------
// Uint8 / Nullable Uint8
// ----------------------------
case data.FieldTypeUint8:
v, ok := val.(uint8)
if !ok {
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint8", val, val)
}
return v, nil
case data.FieldTypeNullableUint8:
vP, ok := val.(*uint8)
if ok {
return vP, nil
}
v, ok := val.(uint8)
if ok {
return &v, nil
}
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint8 or *uint8", val, val)
// ----------------------------
// Int16 / Nullable Int16
// ----------------------------
case data.FieldTypeInt16:
v, ok := val.(int16)
if !ok {
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int16", val, val)
}
return v, nil
case data.FieldTypeNullableInt16:
vP, ok := val.(*int16)
if ok {
return vP, nil
}
v, ok := val.(int16)
if ok {
return &v, nil
}
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int16 or *int16", val, val)
// ----------------------------
// Uint16 / Nullable Uint16
// ----------------------------
case data.FieldTypeUint16:
v, ok := val.(uint16)
if !ok {
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint16", val, val)
}
return v, nil
case data.FieldTypeNullableUint16:
vP, ok := val.(*uint16)
if ok {
return vP, nil
}
v, ok := val.(uint16)
if ok {
return &v, nil
}
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint16 or *uint16", val, val)
// ----------------------------
// Int32 / Nullable Int32
// ----------------------------
case data.FieldTypeInt32:
v, ok := val.(int32)
if !ok {
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int32", val, val)
}
return v, nil
case data.FieldTypeNullableInt32:
vP, ok := val.(*int32)
if ok {
return vP, nil
}
v, ok := val.(int32)
if ok {
return &v, nil
}
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int32 or *int32", val, val)
// ----------------------------
// Uint32 / Nullable Uint32
// ----------------------------
case data.FieldTypeUint32:
v, ok := val.(uint32)
if !ok {
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint32", val, val)
}
return v, nil
case data.FieldTypeNullableUint32:
vP, ok := val.(*uint32)
if ok {
return vP, nil
}
v, ok := val.(uint32)
if ok {
return &v, nil
}
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint32 or *uint32", val, val)
// ----------------------------
// Int64 / Nullable Int64
// ----------------------------
case data.FieldTypeInt64:
v, ok := val.(int64)
if !ok {
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int64", val, val)
}
return v, nil
case data.FieldTypeNullableInt64:
vP, ok := val.(*int64)
if ok {
return vP, nil
}
v, ok := val.(int64)
if ok {
return &v, nil
}
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int64 or *int64", val, val)
// ----------------------------
// Uint64 / Nullable Uint64
// ----------------------------
case data.FieldTypeUint64:
v, ok := val.(uint64)
if !ok {
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint64", val, val)
}
return v, nil
case data.FieldTypeNullableUint64:
vP, ok := val.(*uint64)
if ok {
return vP, nil
}
v, ok := val.(uint64)
if ok {
return &v, nil
}
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint64 or *uint64", val, val)
// ----------------------------
// Float64 / Nullable Float64
// ----------------------------
case data.FieldTypeFloat64:
// Accept float64 or decimal.Decimal, convert decimal.Decimal -> float64
if v, ok := val.(float64); ok {
return v, nil
}
if d, ok := val.(decimal.Decimal); ok {
return d.InexactFloat64(), nil
}
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected float64 or decimal.Decimal", val, val)
case data.FieldTypeNullableFloat64:
// Possibly already *float64
if vP, ok := val.(*float64); ok {
return vP, nil
}
// Possibly float64
if v, ok := val.(float64); ok {
return &v, nil
}
// Possibly decimal.Decimal
if d, ok := val.(decimal.Decimal); ok {
f := d.InexactFloat64()
return &f, nil
}
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected float64, *float64, or decimal.Decimal", val, val)
// ----------------------------
// Time / Nullable Time
// ----------------------------
case data.FieldTypeTime:
v, ok := val.(time.Time)
if !ok {
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected time.Time", val, val)
}
return v, nil
case data.FieldTypeNullableTime:
vP, ok := val.(*time.Time)
if ok {
return vP, nil
}
v, ok := val.(time.Time)
if ok {
return &v, nil
}
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected time.Time or *time.Time", val, val)
// ----------------------------
// String / Nullable String
// ----------------------------
case data.FieldTypeString:
v, ok := val.(string)
if !ok {
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected string", val, val)
}
return v, nil
case data.FieldTypeNullableString:
vP, ok := val.(*string)
if ok {
return vP, nil
}
v, ok := val.(string)
if ok {
return &v, nil
}
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected string or *string", val, val)
// ----------------------------
// Bool / Nullable Bool
// ----------------------------
case data.FieldTypeBool:
v, ok := val.(bool)
if !ok {
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected bool", val, val)
}
return v, nil
case data.FieldTypeNullableBool:
vP, ok := val.(*bool)
if ok {
return vP, nil
}
v, ok := val.(bool)
if ok {
return &v, nil
}
return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected bool or *bool", val, val)
// ----------------------------
// Fallback / Unsupported
// ----------------------------
default:
return nil, fmt.Errorf("unsupported field type %s for val %v", fieldType, val)
}
}
// Is the field nilAt the index. Can panic if out of range.
// TODO: Maybe this should be a method on data.Field?
func nilAt(field data.Field, at int) bool {
if !field.Nullable() {
return false
}
switch field.Type() {
case data.FieldTypeNullableInt8:
v := field.At(at).(*int8)
return v == nil
case data.FieldTypeNullableUint8:
v := field.At(at).(*uint8)
return v == nil
case data.FieldTypeNullableInt16:
v := field.At(at).(*int16)
return v == nil
case data.FieldTypeNullableUint16:
v := field.At(at).(*uint16)
return v == nil
case data.FieldTypeNullableInt32:
v := field.At(at).(*int32)
return v == nil
case data.FieldTypeNullableUint32:
v := field.At(at).(*uint32)
return v == nil
case data.FieldTypeNullableInt64:
v := field.At(at).(*int64)
return v == nil
case data.FieldTypeNullableUint64:
v := field.At(at).(*uint64)
return v == nil
case data.FieldTypeNullableFloat64:
v := field.At(at).(*float64)
return v == nil
case data.FieldTypeNullableString:
v := field.At(at).(*string)
return v == nil
case data.FieldTypeNullableTime:
v := field.At(at).(*time.Time)
return v == nil
case data.FieldTypeNullableBool:
v := field.At(at).(*bool)
return v == nil
default:
// Either it's not a nullable type or it's unsupported
return false
}
}