Files
dolt/go/libraries/doltcore/row/row.go
2022-10-26 13:10:24 -07:00

342 lines
9.9 KiB
Go

// Copyright 2019 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package row
import (
"context"
"errors"
"fmt"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/utils/valutil"
"github.com/dolthub/dolt/go/store/types"
)
var ErrRowNotValid = errors.New("invalid row for current schema")
type Row interface {
// Iterates over all the columns in the row. Columns that have no value set will not be visited.
IterCols(cb func(tag uint64, val types.Value) (stop bool, err error)) (bool, error)
// Iterates over all columns in the schema, using the value for the row. Columns that have no value set in this row
// will still be visited, and receive a nil value.
IterSchema(sch schema.Schema, cb func(tag uint64, val types.Value) (stop bool, err error)) (bool, error)
// Returns the value for the column with the tag given, and a success bool. The value will be null if the row
// doesn't contain a value for that tag.
GetColVal(tag uint64) (types.Value, bool)
// Format returns the types.NomsBinFormat for this row.
Format() *types.NomsBinFormat
// TODO(andy): NomsMapKey, NomsMapValue, & SetColVal
// don't make sense in the context of keyless tables.
// Make these methods package private.
// SetColVal sets a value for the column with the tag given, returning a new row with the update.
SetColVal(tag uint64, val types.Value, sch schema.Schema) (Row, error)
// NomsMapKey returns the noms map key for this row, using the schema provided.
NomsMapKey(sch schema.Schema) types.LesserValuable
// NomsMapValue returns the noms map value for this row, using the schema provided.
NomsMapValue(sch schema.Schema) types.Valuable
// NomsMapKeyTuple returns the noms map key tuple for this row, using the schema provided
NomsMapKeyTuple(sch schema.Schema, tf *types.TupleFactory) (types.Tuple, error)
// NomsMapValueTuple returns the noms map value tuple for this row, using the schema provided.
NomsMapValueTuple(sch schema.Schema, tf *types.TupleFactory) (types.Tuple, error)
// TaggedValues returns the row as TaggedValues.
TaggedValues() (TaggedValues, error)
// ReduceToIndexKeys returns full and partial index keys
ReduceToIndexKeys(idx schema.Index, tf *types.TupleFactory) (full types.Tuple, partial types.Tuple, value types.Tuple, err error)
}
func New(nbf *types.NomsBinFormat, sch schema.Schema, colVals TaggedValues) (Row, error) {
if schema.IsKeyless(sch) {
return keylessRowFromTaggedValued(nbf, sch, colVals)
}
return pkRowFromTaggedValues(nbf, sch, colVals)
}
func FromNoms(sch schema.Schema, nomsKey, nomsVal types.Tuple) (Row, error) {
if schema.IsKeyless(sch) {
row, _, err := KeylessRowsFromTuples(nomsKey, nomsVal)
return row, err
}
return pkRowFromNoms(sch, nomsKey, nomsVal)
}
// ToNoms returns the storage-layer tuples corresponding to |r|.
func ToNoms(ctx context.Context, sch schema.Schema, r Row) (key, val types.Tuple, err error) {
k, err := r.NomsMapKey(sch).Value(ctx)
if err != nil {
return key, val, err
}
v, err := r.NomsMapValue(sch).Value(ctx)
if err != nil {
return key, val, err
}
return k.(types.Tuple), v.(types.Tuple), nil
}
func GetFieldByName(colName string, r Row, sch schema.Schema) (types.Value, bool) {
col, ok := sch.GetAllCols().GetByName(colName)
if !ok {
panic("Requesting column that isn't in the schema. This is a bug. columns should be verified in the schema before attempted retrieval.")
} else {
return r.GetColVal(col.Tag)
}
}
func GetFieldByNameWithDefault(colName string, defVal types.Value, r Row, sch schema.Schema) types.Value {
col, ok := sch.GetAllCols().GetByName(colName)
if !ok {
panic("Requesting column that isn't in the schema. This is a bug. columns should be verified in the schema before attempted retrieval.")
} else {
val, ok := r.GetColVal(col.Tag)
if !ok {
return defVal
}
return val
}
}
// ReduceToIndexKeysFromTagMap creates a full key and a partial key from the given map of tags (first tuple being the
// full key). Please refer to the note in the index editor for more information regarding partial keys.
func ReduceToIndexKeysFromTagMap(nbf *types.NomsBinFormat, idx schema.Index, tagToVal map[uint64]types.Value, tf *types.TupleFactory) (types.Tuple, types.Tuple, error) {
vals := make([]types.Value, 0, len(idx.AllTags())*2)
for _, tag := range idx.AllTags() {
val, ok := tagToVal[tag]
if !ok {
val = types.NullValue
}
vals = append(vals, types.Uint(tag), val)
}
if tf == nil {
fullKey, err := types.NewTuple(nbf, vals...)
if err != nil {
return types.Tuple{}, types.Tuple{}, err
}
partialKey, err := types.NewTuple(nbf, vals[:idx.Count()*2]...)
if err != nil {
return types.Tuple{}, types.Tuple{}, err
}
return fullKey, partialKey, nil
} else {
fullKey, err := tf.Create(vals...)
if err != nil {
return types.Tuple{}, types.Tuple{}, err
}
partialKey, err := tf.Create(vals[:idx.Count()*2]...)
if err != nil {
return types.Tuple{}, types.Tuple{}, err
}
return fullKey, partialKey, nil
}
}
// ReduceToIndexPartialKey creates an index record from a primary storage record.
func ReduceToIndexPartialKey(tags []uint64, idx schema.Index, r Row) (types.Tuple, error) {
var vals []types.Value
if idx.Name() != "" {
tags = idx.IndexedColumnTags()
}
for _, tag := range tags {
val, ok := r.GetColVal(tag)
if !ok {
val = types.NullValue
}
vals = append(vals, types.Uint(tag), val)
}
return types.NewTuple(r.Format(), vals...)
}
func IsEmpty(r Row) (b bool) {
b = true
_, _ = r.IterCols(func(_ uint64, _ types.Value) (stop bool, err error) {
b = false
return true, nil
})
return b
}
// IsValid returns whether the row given matches the types and satisfies all the constraints of the schema given.
func IsValid(r Row, sch schema.Schema) (bool, error) {
column, constraint, err := findInvalidCol(r, sch)
if err != nil {
return false, err
}
return column == nil && constraint == nil, nil
}
// GetInvalidCol returns the first column in the schema that fails a constraint, or nil if none do.
func GetInvalidCol(r Row, sch schema.Schema) (*schema.Column, error) {
badCol, _, err := findInvalidCol(r, sch)
return badCol, err
}
// GetInvalidConstraint returns the failed constraint for the row given (previously identified by IsValid) along with
// the column with that constraint. Note that if there is a problem with the row besides the constraint, the constraint
// return value will be nil.
func GetInvalidConstraint(r Row, sch schema.Schema) (*schema.Column, schema.ColConstraint, error) {
return findInvalidCol(r, sch)
}
// Returns the first encountered invalid column and its constraint, or nil if the row is valid. Column will always be
// set if the row is invalid. Constraint will be set if the first encountered problem is a constraint failure.
func findInvalidCol(r Row, sch schema.Schema) (*schema.Column, schema.ColConstraint, error) {
allCols := sch.GetAllCols()
var badCol *schema.Column
var badCnst schema.ColConstraint
err := allCols.Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
val, colSet := r.GetColVal(tag)
if colSet && !types.IsNull(val) && val.Kind() != col.Kind {
badCol = &col
return true, nil
}
if !col.TypeInfo.IsValid(val) {
badCol = &col
return true, fmt.Errorf(`"%v" is not valid for column "%s" (type "%s")`, val, col.Name, col.TypeInfo.ToSqlType().String())
}
if len(col.Constraints) > 0 {
for _, cnst := range col.Constraints {
if !cnst.SatisfiesConstraint(val) {
badCol = &col
badCnst = cnst
return true, nil
}
}
}
return false, nil
})
return badCol, badCnst, err
}
func AreEqual(row1, row2 Row, sch schema.Schema) bool {
if row1 == nil && row2 == nil {
return true
} else if row1 == nil || row2 == nil {
return false
}
for _, tag := range sch.GetAllCols().Tags {
val1, _ := row1.GetColVal(tag)
val2, _ := row2.GetColVal(tag)
if !valutil.NilSafeEqCheck(val1, val2) {
return false
}
}
return true
}
func TaggedValsEqualForSch(tv, other TaggedValues, sch schema.Schema) bool {
if tv == nil && other == nil {
return true
} else if tv == nil || other == nil {
return false
}
for _, tag := range sch.GetAllCols().Tags {
val1, _ := tv[tag]
val2, _ := other[tag]
if !valutil.NilSafeEqCheck(val1, val2) {
return false
}
}
return true
}
func KeyAndTaggedValuesForRow(r Row, sch schema.Schema) (types.Tuple, TaggedValues, error) {
switch typed := r.(type) {
case nomsRow:
pkCols := sch.GetPKCols()
keyVals := make([]types.Value, 0, pkCols.Size()*2)
tv := make(TaggedValues)
err := pkCols.Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
val, ok := typed.key[tag]
if !ok || types.IsNull(val) {
return false, errors.New("invalid key contains null values")
}
tv[tag] = val
keyVals = append(keyVals, types.Uint(tag))
keyVals = append(keyVals, val)
return false, nil
})
if err != nil {
return types.Tuple{}, nil, err
}
nonPkCols := sch.GetNonPKCols()
_, err = typed.value.Iter(func(tag uint64, val types.Value) (stop bool, err error) {
if _, ok := nonPkCols.TagToIdx[tag]; ok {
tv[tag] = val
}
return false, nil
})
if err != nil {
return types.Tuple{}, nil, err
}
t, err := types.NewTuple(r.Format(), keyVals...)
if err != nil {
return types.Tuple{}, nil, err
}
return t, tv, nil
case keylessRow:
tv, err := typed.TaggedValues()
if err != nil {
return types.Tuple{}, nil, err
}
return typed.key, tv, nil
default:
panic("unknown row type")
}
}