Files
wehub-resource-sync 5357c39144
Fuzzer / Run Fuzzer (push) Has been cancelled
Race tests / Go race tests (ubuntu-22.04) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:01:40 +08:00

310 lines
10 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 schema
import (
"errors"
"sort"
"strings"
)
// ErrColTagCollision is an error that is returned when two columns within a ColCollection have the same tag
// but a different name or type
var ErrColTagCollision = errors.New("two different columns with the same tag")
// ErrColNotFound is an error that is returned when attempting an operation on a column that does not exist
var ErrColNotFound = errors.New("column not found")
// ErrColNameCollision is an error that is returned when two columns within a ColCollection have the same name but a
// different type or tag
var ErrColNameCollision = errors.New("two different columns with the same name exist")
// ErrNoPrimaryKeyColumns is an error that is returned when no primary key columns are found
var ErrNoPrimaryKeyColumns = errors.New("no primary key columns")
var EmptyColColl = &ColCollection{
cols: []Column{},
Tags: []uint64{},
SortedTags: []uint64{},
TagToCol: map[uint64]Column{},
NameToCol: map[string]Column{},
LowerNameToCol: map[string]Column{},
TagToIdx: map[uint64]int{},
}
// ColCollection is a collection of columns. As a stand-alone collection, all columns in the collection must have unique
// tags. To be instantiated as a schema for writing to the database, names must also be unique.
// See schema.ValidateForInsert for details.
type ColCollection struct {
cols []Column
// virtualColumns stores the indexes of any virtual columns in the collection
virtualColumns []int
// storedIndexes stores the indexes of the stored columns in the collection
storedIndexes []int
// Tags is a list of all the tags in the ColCollection in their original order.
Tags []uint64
// SortedTags is a list of all the tags in the ColCollection in sorted order.
SortedTags []uint64
// TagToCol is a map of tag to column
TagToCol map[uint64]Column
// NameToCol is a map from name to column
NameToCol map[string]Column
// LowerNameToCol is a map from lower-cased name to column
LowerNameToCol map[string]Column
// TagToIdx is a map from a tag to the column index
TagToIdx map[uint64]int
// tagToStorageIndex is a map from a tag to the physical storage column index
tagToStorageIndex map[uint64]int
}
// NewColCollection creates a new collection from a list of columns. If any columns have the same tag, by-tag lookups in
// this collection will not function correctly. If any columns have the same name, by-name lookups from this collection
// will not function correctly. If any columns have the same case-insensitive name, case-insensitive lookups will be
// unable to return the correct column in all cases.
// For this collection to be used as a Dolt schema, it must pass schema.ValidateForInsert.
func NewColCollection(cols ...Column) *ColCollection {
var tags []uint64
var sortedTags []uint64
tagToCol := make(map[uint64]Column, len(cols))
nameToCol := make(map[string]Column, len(cols))
lowerNameToCol := make(map[string]Column, len(cols))
tagToIdx := make(map[uint64]int, len(cols))
tagToStorageIndex := make(map[uint64]int, len(cols))
var virtualColumns []int
var columns []Column
var storedIndexes []int
storageIdx := 0
for i, col := range cols {
// If multiple columns have the same tag, the last one is used for tag lookups.
// Columns must have unique tags to pass schema.ValidateForInsert.
columns = append(columns, col)
tagToCol[col.Tag] = col
tagToIdx[col.Tag] = i
tags = append(tags, col.Tag)
sortedTags = append(sortedTags, col.Tag)
nameToCol[col.Name] = cols[i]
// If multiple columns have the same lower case name, the first one is used for case-insensitive matching.
// Column names must all be case-insensitive different to pass schema.ValidateForInsert.
lowerCaseName := strings.ToLower(col.Name)
if _, ok := lowerNameToCol[lowerCaseName]; !ok {
lowerNameToCol[lowerCaseName] = cols[i]
}
if col.Virtual {
virtualColumns = append(virtualColumns, i)
} else {
storedIndexes = append(storedIndexes, i)
tagToStorageIndex[col.Tag] = storageIdx
storageIdx++
}
}
sort.Slice(sortedTags, func(i, j int) bool { return sortedTags[i] < sortedTags[j] })
return &ColCollection{
cols: columns,
virtualColumns: virtualColumns,
storedIndexes: storedIndexes,
tagToStorageIndex: tagToStorageIndex,
Tags: tags,
SortedTags: sortedTags,
TagToCol: tagToCol,
NameToCol: nameToCol,
LowerNameToCol: lowerNameToCol,
TagToIdx: tagToIdx,
}
}
// GetColumns returns the underlying list of columns. The list returned is a copy.
func (cc *ColCollection) GetColumns() []Column {
colsCopy := make([]Column, len(cc.cols))
copy(colsCopy, cc.cols)
return colsCopy
}
// GetColumnNames returns a list of names of the columns.
func (cc *ColCollection) GetColumnNames() []string {
names := make([]string, len(cc.cols))
for i, col := range cc.cols {
names[i] = col.Name
}
return names
}
// AppendColl returns a new collection with the additional ColCollection's columns appended
func (cc *ColCollection) AppendColl(colColl *ColCollection) *ColCollection {
return cc.Append(colColl.cols...)
}
// Append returns a new collection with the additional columns appended
func (cc *ColCollection) Append(cols ...Column) *ColCollection {
allCols := make([]Column, 0, len(cols)+len(cc.cols))
allCols = append(allCols, cc.cols...)
allCols = append(allCols, cols...)
return NewColCollection(allCols...)
}
// IndexOf returns the index of the column with the name given (case-insensitive) or -1 if it's not found
func (cc *ColCollection) IndexOf(colName string) int {
idx := -1
var i = 0
_ = cc.Iter(func(_ uint64, col Column) (stop bool, err error) {
defer func() {
i++
}()
if strings.EqualFold(col.Name, colName) {
idx = i
stop = true
}
return
})
return idx
}
// Iter iterates over all the columns in the supplied ordering
func (cc *ColCollection) Iter(cb func(tag uint64, col Column) (stop bool, err error)) error {
for _, col := range cc.cols {
if stop, err := cb(col.Tag, col); err != nil {
return err
} else if stop {
break
}
}
return nil
}
// IterInSortedOrder iterates over all the columns from lowest tag to highest tag.
func (cc *ColCollection) IterInSortedOrder(cb func(tag uint64, col Column) (stop bool)) {
for _, tag := range cc.SortedTags {
val := cc.TagToCol[tag]
if stop := cb(tag, val); stop {
break
}
}
}
// GetByName takes the name of a column and returns the column and true if found. Otherwise InvalidCol and false are
// returned.
func (cc *ColCollection) GetByName(name string) (Column, bool) {
val, ok := cc.NameToCol[name]
if ok {
return val, true
}
return InvalidCol, false
}
// GetByNameCaseInsensitive takes the name of a column and returns the column and true if there is a column with that
// name ignoring case. Otherwise InvalidCol and false are returned. If multiple columns have the same case-insensitive
// name, the first declared one is returned.
func (cc *ColCollection) GetByNameCaseInsensitive(name string) (Column, bool) {
val, ok := cc.LowerNameToCol[strings.ToLower(name)]
if ok {
return val, true
}
return InvalidCol, false
}
// GetByTag takes a tag and returns the corresponding column and true if found, otherwise InvalidCol and false are
// returned
func (cc *ColCollection) GetByTag(tag uint64) (Column, bool) {
val, ok := cc.TagToCol[tag]
if ok {
return val, true
}
return InvalidCol, false
}
// GetByIndex returns the Nth column in the collection
func (cc *ColCollection) GetByIndex(idx int) Column {
return cc.cols[idx]
}
// GetByStoredIndex returns the Nth stored column (omitting virtual columns from index calculation)
func (cc *ColCollection) GetByStoredIndex(idx int) Column {
return cc.cols[cc.storedIndexes[idx]]
}
// StoredIndexByTag returns the storage index of the column with the given tag, ignoring virtual columns
func (cc *ColCollection) StoredIndexByTag(tag uint64) (int, bool) {
idx, ok := cc.tagToStorageIndex[tag]
return idx, ok
}
// Size returns the number of columns in the collection.
func (cc *ColCollection) Size() int {
return len(cc.cols)
}
// StoredSize returns the number of non-virtual columns in the collection
func (cc *ColCollection) StoredSize() int {
return len(cc.storedIndexes)
}
// Contains returns whether this column collection contains a column with the name given, case insensitive
func (cc *ColCollection) Contains(name string) bool {
_, ok := cc.GetByNameCaseInsensitive(name)
return ok
}
// ColCollsAreEqual determines whether two ColCollections are equal.
func ColCollsAreEqual(cc1, cc2 *ColCollection) bool {
if cc1.Size() != cc2.Size() {
return false
}
// Pks Cols need to be in the same order and equivalent.
for i := 0; i < cc1.Size(); i++ {
// Test that the columns are identical, but don't worry about tags matching, since
// different tags could be generated depending on how the schemas were created.
if !cc1.cols[i].EqualsWithoutTag(cc2.cols[i]) {
return false
}
}
return true
}
// MapColCollection applies a function to each column in a ColCollection and creates a new ColCollection from the results.
func MapColCollection(cc *ColCollection, cb func(col Column) Column) *ColCollection {
mapped := make([]Column, cc.Size())
for i, c := range cc.cols {
mapped[i] = cb(c)
}
return NewColCollection(mapped...)
}
// FilterColCollection applies a boolean function to column in a ColCollection, it creates a new ColCollection from the
// set of columns for which the function returned true.
func FilterColCollection(cc *ColCollection, cb func(col Column) bool) *ColCollection {
filtered := make([]Column, 0, cc.Size())
for _, c := range cc.cols {
if cb(c) {
filtered = append(filtered, c)
}
}
return NewColCollection(filtered...)
}