featurebase/sql3/planner/opsystemtable.go
pokeeffe-molecula f62313762c
implemented extract ddl; tightened up type related stuff (#2329)
* implemented extract ddl; tightened up type related stuff

* added some test coverage

* review feedback
2022-12-05 16:50:30 -06:00

526 lines
13 KiB
Go

// Copyright 2021 Molecula Corp. All rights reserved.
package planner
import (
"bytes"
"context"
"fmt"
pilosa "github.com/molecula/featurebase/v3"
"github.com/molecula/featurebase/v3/pql"
"github.com/molecula/featurebase/v3/sql3"
"github.com/molecula/featurebase/v3/sql3/parser"
"github.com/molecula/featurebase/v3/sql3/planner/types"
)
// exclude this file from SonarCloud dupe eval
const (
fbClusterInfo = "fb_cluster_info"
fbClusterNodes = "fb_cluster_nodes"
fbExecRequests = "fb_exec_requests"
fbTableDDL = "fb_table_ddl"
)
type systemTable struct {
name string
schema types.Schema
}
var systemTables = map[string]*systemTable{
fbClusterInfo: {
name: fbClusterInfo,
schema: types.Schema{
&types.PlannerColumn{
RelationName: fbClusterInfo,
ColumnName: "name",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbClusterInfo,
ColumnName: "name",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbClusterInfo,
ColumnName: "platform",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbClusterInfo,
ColumnName: "platform_version",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbClusterInfo,
ColumnName: "db_version",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbClusterInfo,
ColumnName: "state",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbClusterInfo,
ColumnName: "node_count",
Type: parser.NewDataTypeInt(),
},
&types.PlannerColumn{
RelationName: fbClusterInfo,
ColumnName: "shard_width",
Type: parser.NewDataTypeInt(),
},
&types.PlannerColumn{
RelationName: fbClusterInfo,
ColumnName: "replica_count",
Type: parser.NewDataTypeInt(),
},
},
},
fbClusterNodes: {
name: fbClusterNodes,
schema: types.Schema{
&types.PlannerColumn{
RelationName: fbClusterNodes,
ColumnName: "id",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbClusterNodes,
ColumnName: "state",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbClusterNodes,
ColumnName: "uri",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbClusterNodes,
ColumnName: "grpc_uri",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbClusterNodes,
ColumnName: "is_primary",
Type: parser.NewDataTypeBool(),
},
},
},
fbExecRequests: {
name: fbExecRequests,
schema: types.Schema{
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "request_id",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "user",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "start_time",
Type: parser.NewDataTypeTimestamp(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "end_time",
Type: parser.NewDataTypeTimestamp(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "status",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "wait_type",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "wait_time",
Type: parser.NewDataTypeInt(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "wait_resource",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "cpu_time",
Type: parser.NewDataTypeInt(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "elapsed_time",
Type: parser.NewDataTypeInt(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "reads",
Type: parser.NewDataTypeInt(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "writes",
Type: parser.NewDataTypeInt(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "logical_reads",
Type: parser.NewDataTypeInt(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "row_count",
Type: parser.NewDataTypeInt(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "sql",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbExecRequests,
ColumnName: "plan",
Type: parser.NewDataTypeString(),
},
},
},
fbTableDDL: {
name: fbTableDDL,
schema: types.Schema{
&types.PlannerColumn{
RelationName: fbTableDDL,
ColumnName: "id",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbTableDDL,
ColumnName: "name",
Type: parser.NewDataTypeString(),
},
&types.PlannerColumn{
RelationName: fbTableDDL,
ColumnName: "ddl",
Type: parser.NewDataTypeString(),
},
},
},
}
// PlanOpSystemTable handles system tables
type PlanOpSystemTable struct {
planner *ExecutionPlanner
table *systemTable
warnings []string
}
func NewPlanOpSystemTable(p *ExecutionPlanner, table *systemTable) *PlanOpSystemTable {
return &PlanOpSystemTable{
planner: p,
table: table,
warnings: make([]string, 0),
}
}
func (p *PlanOpSystemTable) Plan() map[string]interface{} {
result := make(map[string]interface{})
result["_op"] = fmt.Sprintf("%T", p)
ps := make([]string, 0)
for _, e := range p.Schema() {
ps = append(ps, fmt.Sprintf("'%s', '%s', '%s'", e.ColumnName, e.RelationName, e.Type.TypeDescription()))
}
result["_schema"] = ps
return result
}
func (p *PlanOpSystemTable) String() string {
return ""
}
func (p *PlanOpSystemTable) AddWarning(warning string) {
p.warnings = append(p.warnings, warning)
}
func (p *PlanOpSystemTable) Warnings() []string {
return p.warnings
}
func (p *PlanOpSystemTable) Schema() types.Schema {
return p.table.schema
}
func (p *PlanOpSystemTable) Children() []types.PlanOperator {
return []types.PlanOperator{}
}
func (p *PlanOpSystemTable) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
switch p.table.name {
case fbClusterInfo:
return &fbClusterInfoRowIter{
planner: p.planner,
}, nil
case fbClusterNodes:
return &fbClusterNodesRowIter{
planner: p.planner,
}, nil
case fbExecRequests:
return &fbExecRequestsRowIter{
planner: p.planner,
}, nil
case fbTableDDL:
return &fbTableDDLRowIter{
planner: p.planner,
}, nil
default:
return nil, sql3.NewErrInternalf("unable to find system table '%s'", p.table.name)
}
}
func (p *PlanOpSystemTable) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
if len(children) > 0 {
return nil, sql3.NewErrInternalf("unexpected number of children '%d'", len(children))
}
return NewPlanOpSystemTable(p.planner, p.table), nil
}
type fbClusterInfoRowIter struct {
planner *ExecutionPlanner
rowIndex int
}
var _ types.RowIterator = (*fbClusterInfoRowIter)(nil)
func (i *fbClusterInfoRowIter) Next(ctx context.Context) (types.Row, error) {
if i.rowIndex < 1 {
row := []interface{}{
i.planner.systemAPI.ClusterName(),
i.planner.systemAPI.ClusterName(),
i.planner.systemAPI.PlatformDescription(),
i.planner.systemAPI.PlatformVersion(),
i.planner.systemAPI.Version(),
i.planner.systemAPI.ClusterState(),
i.planner.systemAPI.ClusterNodeCount(),
i.planner.systemAPI.ShardWidth(),
i.planner.systemAPI.ClusterReplicaCount(),
}
i.rowIndex += 1
return row, nil
}
return nil, types.ErrNoMoreRows
}
type fbClusterNodesRowIter struct {
planner *ExecutionPlanner
result []pilosa.ClusterNode
}
var _ types.RowIterator = (*fbClusterNodesRowIter)(nil)
func (i *fbClusterNodesRowIter) Next(ctx context.Context) (types.Row, error) {
if i.result == nil {
i.result = i.planner.systemAPI.ClusterNodes()
}
if len(i.result) > 0 {
n := i.result[0]
row := []interface{}{
n.ID,
n.State,
n.URI,
n.GRPCURI,
n.IsPrimary,
}
// Move to next result element.
i.result = i.result[1:]
return row, nil
}
return nil, types.ErrNoMoreRows
}
type fbExecRequestsRowIter struct {
planner *ExecutionPlanner
result []pilosa.ExecutionRequest
}
var _ types.RowIterator = (*fbExecRequestsRowIter)(nil)
func (i *fbExecRequestsRowIter) Next(ctx context.Context) (types.Row, error) {
if i.result == nil {
var err error
i.result, err = i.planner.systemLayerAPI.ExecutionRequests().ListRequests()
if err != nil {
return nil, err
}
}
if len(i.result) > 0 {
n := i.result[0]
row := []interface{}{
n.RequestID,
n.UserID,
n.StartTime,
n.EndTime,
n.Status,
n.WaitType,
n.WaitTime.Microseconds(),
n.WaitResource,
n.CPUTime.Microseconds(),
n.ElapsedTime.Microseconds(),
n.Reads,
n.Writes,
n.LogicalReads,
n.RowCount,
n.SQL,
n.Plan,
}
// Move to next result element.
i.result = i.result[1:]
return row, nil
}
return nil, types.ErrNoMoreRows
}
type fbTableDDLRow struct {
id string
name string
ddl string
}
type fbTableDDLRowIter struct {
planner *ExecutionPlanner
result []*fbTableDDLRow
}
var _ types.RowIterator = (*fbTableDDLRowIter)(nil)
func (i *fbTableDDLRowIter) Next(ctx context.Context) (types.Row, error) {
if i.result == nil {
schema, err := i.planner.schemaAPI.Schema(ctx, false)
if err != nil {
return nil, err
}
i.result = make([]*fbTableDDLRow, len(schema))
for idx, table := range schema {
index, err := i.planner.schemaAPI.IndexInfo(context.Background(), table.Name)
if err != nil {
return nil, err
}
// build the ddl for this table
var buf bytes.Buffer
buf.WriteString("create table ")
fmt.Fprintf(&buf, "%s", index.Name)
buf.WriteString(" (")
for idx, col := range index.Fields {
if idx > 0 {
buf.WriteString(", ")
}
fmt.Fprintf(&buf, "%s", col.Name)
dataType := fieldSQLDataType(col)
fmt.Fprintf(&buf, " %s", dataType.TypeDescription())
switch dt := dataType.(type) {
case *parser.DataTypeID, *parser.DataTypeString:
if col.Options.CacheType != pilosa.DefaultCacheType && len(col.Options.CacheType) > 0 {
fmt.Fprintf(&buf, " cachetype %s", col.Options.CacheType)
}
if col.Options.CacheSize != pilosa.DefaultCacheSize && col.Options.CacheSize > 0 {
fmt.Fprintf(&buf, " cachesize %d", col.Options.CacheSize)
}
case *parser.DataTypeIDSet, *parser.DataTypeStringSet:
if col.Options.CacheType != pilosa.DefaultCacheType && len(col.Options.CacheType) > 0 {
fmt.Fprintf(&buf, " cachetype %s", col.Options.CacheType)
}
if col.Options.CacheSize != pilosa.DefaultCacheSize && col.Options.CacheSize > 0 {
fmt.Fprintf(&buf, " cachesize %d", col.Options.CacheSize)
}
case *parser.DataTypeIDSetQuantum, *parser.DataTypeStringSetQuantum:
if col.Options.CacheType != pilosa.DefaultCacheType && len(col.Options.CacheType) > 0 {
fmt.Fprintf(&buf, " cachetype %s", col.Options.CacheType)
}
if col.Options.CacheSize != pilosa.DefaultCacheSize && col.Options.CacheSize > 0 {
fmt.Fprintf(&buf, " cachesize %d", col.Options.CacheSize)
}
if !col.Options.TimeQuantum.IsEmpty() {
fmt.Fprintf(&buf, " timequantum '%s'", col.Options.TimeQuantum)
}
if col.Options.TTL > 0 {
fmt.Fprintf(&buf, " ttl '%s'", col.Options.TTL.String())
}
case *parser.DataTypeInt:
minValue, maxValue := pql.MinMax(0)
min := col.Options.Min
if !min.EqualTo(minValue) {
fmt.Fprintf(&buf, " min %d", min.ToInt64(0))
}
max := col.Options.Max
if !max.EqualTo(maxValue) {
fmt.Fprintf(&buf, " max %d", max.ToInt64(0))
}
case *parser.DataTypeDecimal:
minValue, maxValue := pql.MinMax(dt.Scale)
min := col.Options.Min
if !min.EqualTo(minValue) {
fmt.Fprintf(&buf, " min %v", min)
}
max := col.Options.Max
if !max.EqualTo(maxValue) {
fmt.Fprintf(&buf, " max %v", max)
}
case *parser.DataTypeTimestamp:
if len(col.Options.TimeUnit) > 0 {
fmt.Fprintf(&buf, " timeunit '%s'", col.Options.TimeUnit)
}
// TODO(pok) how do we get epoch out of col?
}
}
buf.WriteString(");")
ddl := buf.String()
i.result[idx] = &fbTableDDLRow{
id: table.Name,
name: table.Name,
ddl: ddl,
}
}
}
if len(i.result) > 0 {
n := i.result[0]
row := []interface{}{
n.id,
n.name,
n.ddl,
}
// Move to next result element.
i.result = i.result[1:]
return row, nil
}
return nil, types.ErrNoMoreRows
}