featurebase/sql3/planner/executionplannersystemtables.go
Seebs 10b60f5d51 set default epoch for timestamps in system tables
If we don't set an epoch, we get a cryptic message on the console.
Note, this message isn't logged properly, it doesn't use the
logger, it uses the `log` package.

	2023/02/09 11:07:23 ERROR: converting timestamp options for
	end_time: checking overflow: custom epoch too far from
	Unix epoch: 0001-01-01 00:00:00 +0000 UTC

Because this uses the log package, it doesn't go to the same place
as other messages, making it a pain to debug.

The underlying problem is that a timestamp can't just have a zero
value for its epoch. So, we set a default epoch of 0 Unix Time.

We should possibly revisit the question of whether the conversion
in the top-level schema.go should handle an epoch which IsZero,
but I'm not sure what "base" should be in that case. In practice,
all existing usages except this one are specifying time.Unix(0, 0)
already.
2023-02-15 14:34:51 -06:00

172 lines
4.9 KiB
Go

// Copyright 2022 Molecula Corp. All rights reserved.
package planner
import (
"context"
"time"
pilosa "github.com/featurebasedb/featurebase/v3"
"github.com/featurebasedb/featurebase/v3/dax"
"github.com/featurebasedb/featurebase/v3/sql3"
"github.com/featurebasedb/featurebase/v3/sql3/parser"
"github.com/pkg/errors"
)
// Ensure type implements interface.
var _ pilosa.SchemaAPI = (*systemTableDefinitionsWrapper)(nil)
type systemTableDefinitionsWrapper struct {
schemaAPI pilosa.SchemaAPI
}
func newSystemTableDefinitionsWrapper(api pilosa.SchemaAPI) *systemTableDefinitionsWrapper {
return &systemTableDefinitionsWrapper{
schemaAPI: api,
}
}
func (s *systemTableDefinitionsWrapper) CreateDatabase(ctx context.Context, db *dax.Database) error {
return s.schemaAPI.CreateDatabase(ctx, db)
}
func (s *systemTableDefinitionsWrapper) DropDatabase(ctx context.Context, dbid dax.DatabaseID) error {
return s.schemaAPI.DropDatabase(ctx, dbid)
}
func (s *systemTableDefinitionsWrapper) DatabaseByName(ctx context.Context, dbname dax.DatabaseName) (*dax.Database, error) {
return s.schemaAPI.DatabaseByName(ctx, dbname)
}
func (s *systemTableDefinitionsWrapper) DatabaseByID(ctx context.Context, dbid dax.DatabaseID) (*dax.Database, error) {
return s.schemaAPI.DatabaseByID(ctx, dbid)
}
func (s *systemTableDefinitionsWrapper) SetDatabaseOption(ctx context.Context, dbid dax.DatabaseID, option string, value string) error {
return s.schemaAPI.SetDatabaseOption(ctx, dbid, option, value)
}
func (s *systemTableDefinitionsWrapper) Databases(ctx context.Context, dbids ...dax.DatabaseID) ([]*dax.Database, error) {
return s.schemaAPI.Databases(ctx, dbids...)
}
func (s *systemTableDefinitionsWrapper) TableByName(ctx context.Context, tname dax.TableName) (*dax.Table, error) {
tbl, err := s.schemaAPI.TableByName(ctx, tname)
if err != nil {
if isTableNotFoundError(err) {
st, ok := systemTables[string(tname)]
if !ok {
return nil, dax.NewErrTableNameDoesNotExist(tname)
}
return indexInfoFromSystemTableB(st)
}
return nil, err
}
return tbl, nil
}
func (s *systemTableDefinitionsWrapper) TableByID(ctx context.Context, tid dax.TableID) (*dax.Table, error) {
return s.schemaAPI.TableByID(ctx, tid)
}
func (s *systemTableDefinitionsWrapper) Tables(ctx context.Context) ([]*dax.Table, error) {
tbls, err := s.schemaAPI.Tables(ctx)
if err != nil {
return nil, errors.Wrap(err, "getting tables")
}
// Append the system tables.
for tblName, st := range systemTables {
ii, err := indexInfoFromSystemTable(st)
if err != nil {
return nil, errors.Wrapf(err, "converting system table to table: %s", tblName)
}
tbls = append(tbls, pilosa.IndexInfoToTable(ii))
}
return tbls, nil
}
func (s *systemTableDefinitionsWrapper) CreateTable(ctx context.Context, tbl *dax.Table) error {
return s.schemaAPI.CreateTable(ctx, tbl)
}
func (s *systemTableDefinitionsWrapper) CreateField(ctx context.Context, tname dax.TableName, fld *dax.Field) error {
return s.schemaAPI.CreateField(ctx, tname, fld)
}
func (s *systemTableDefinitionsWrapper) DeleteTable(ctx context.Context, tname dax.TableName) error {
return s.schemaAPI.DeleteTable(ctx, tname)
}
func (s *systemTableDefinitionsWrapper) DeleteField(ctx context.Context, tname dax.TableName, fname dax.FieldName) error {
return s.schemaAPI.DeleteField(ctx, tname, fname)
}
func indexInfoFromSystemTableB(st *systemTable) (*dax.Table, error) {
fields := make([]*dax.Field, 0)
for _, f := range st.schema {
var baseType dax.BaseType
var epoch time.Time
switch f.Type.(type) {
case *parser.DataTypeInt:
baseType = dax.BaseTypeInt
case *parser.DataTypeBool:
baseType = dax.BaseTypeBool
case *parser.DataTypeString:
baseType = dax.BaseTypeString
case *parser.DataTypeTimestamp:
baseType = dax.BaseTypeTimestamp
epoch = time.Unix(0, 0)
default:
return nil, sql3.NewErrInternalf("unexpected system table field type '%T'", f.Type)
}
_ = dax.FieldOptions{}
fld := &dax.Field{
Name: dax.FieldName(f.ColumnName),
Type: baseType,
Options: dax.FieldOptions{Epoch: epoch},
}
fields = append(fields, fld)
}
tbl := &dax.Table{
Name: dax.TableName(st.name),
Fields: fields,
}
return tbl, nil
}
func indexInfoFromSystemTable(st *systemTable) (*pilosa.IndexInfo, error) {
fields := make([]*pilosa.FieldInfo, 0)
for _, f := range st.schema {
var opts pilosa.FieldOptions
switch f.Type.(type) {
case *parser.DataTypeInt:
opts.Type = pilosa.FieldTypeInt
case *parser.DataTypeBool:
opts.Type = pilosa.FieldTypeBool
case *parser.DataTypeString:
opts.Type = pilosa.FieldTypeMutex
opts.Keys = true
case *parser.DataTypeTimestamp:
opts.Type = pilosa.FieldTypeTimestamp
default:
return nil, sql3.NewErrInternalf("unexpected system table field type '%T'", f.Type)
}
fld := &pilosa.FieldInfo{
Name: f.ColumnName,
Options: opts,
}
fields = append(fields, fld)
}
i := &pilosa.IndexInfo{
Name: st.name,
Fields: fields,
}
return i, nil
}