github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/schema/col_coll.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package schema
    16  
    17  import (
    18  	"errors"
    19  	"sort"
    20  	"strings"
    21  )
    22  
    23  // ErrColTagCollision is an error that is returned when two columns within a ColCollection have the same tag
    24  // but a different name or type
    25  var ErrColTagCollision = errors.New("two different columns with the same tag")
    26  
    27  // ErrColNotFound is an error that is returned when attempting an operation on a column that does not exist
    28  var ErrColNotFound = errors.New("column not found")
    29  
    30  // ErrColNameCollision is an error that is returned when two columns within a ColCollection have the same name but a
    31  // different type or tag
    32  var ErrColNameCollision = errors.New("two different columns with the same name exist")
    33  
    34  // ErrNoPrimaryKeyColumns is an error that is returned when no primary key columns are found
    35  var ErrNoPrimaryKeyColumns = errors.New("no primary key columns")
    36  
    37  var EmptyColColl = &ColCollection{
    38  	[]Column{},
    39  	[]uint64{},
    40  	[]uint64{},
    41  	map[uint64]Column{},
    42  	map[string]Column{},
    43  	map[string]Column{},
    44  	map[uint64]int{},
    45  }
    46  
    47  // ColCollection is a collection of columns. As a stand-alone collection, all columns in the collection must have unique
    48  // tags. To be instantiated as a schema for writing to the database, names must also be unique.
    49  // See schema.ValidateForInsert for details.
    50  type ColCollection struct {
    51  	cols []Column
    52  	// Tags is a list of all the tags in the ColCollection in their original order.
    53  	Tags []uint64
    54  	// SortedTags is a list of all the tags in the ColCollection in sorted order.
    55  	SortedTags []uint64
    56  	// TagToCol is a map of tag to column
    57  	TagToCol map[uint64]Column
    58  	// NameToCol is a map from name to column
    59  	NameToCol map[string]Column
    60  	// LowerNameToCol is a map from lower-cased name to column
    61  	LowerNameToCol map[string]Column
    62  	// TagToIdx is a map from a tag to the column index
    63  	TagToIdx map[uint64]int
    64  }
    65  
    66  // NewColCollection creates a new collection from a list of columns. If any columns have the same tag, by-tag lookups in
    67  // this collection will not function correctly. If any columns have the same name, by-name lookups from this collection
    68  // will not function correctly. If any columns have the same case-insensitive name, case-insensitive lookups will be
    69  // unable to return the correct column in all cases.
    70  // For this collection to be used as a Dolt schema, it must pass schema.ValidateForInsert.
    71  func NewColCollection(cols ...Column) *ColCollection {
    72  	var tags []uint64
    73  	var sortedTags []uint64
    74  
    75  	tagToCol := make(map[uint64]Column, len(cols))
    76  	nameToCol := make(map[string]Column, len(cols))
    77  	lowerNameToCol := make(map[string]Column, len(cols))
    78  	tagToIdx := make(map[uint64]int, len(cols))
    79  
    80  	var columns []Column
    81  	for i, col := range cols {
    82  		// If multiple columns have the same tag, the last one is used for tag lookups.
    83  		// Columns must have unique tags to pass schema.ValidateForInsert.
    84  		columns = append(columns, col)
    85  		tagToCol[col.Tag] = col
    86  		tagToIdx[col.Tag] = i
    87  		tags = append(tags, col.Tag)
    88  		sortedTags = append(sortedTags, col.Tag)
    89  		nameToCol[col.Name] = cols[i]
    90  
    91  		// If multiple columns have the same lower case name, the first one is used for case-insensitive matching.
    92  		// Column names must all be case-insensitive different to pass schema.ValidateForInsert.
    93  		lowerCaseName := strings.ToLower(col.Name)
    94  		if _, ok := lowerNameToCol[lowerCaseName]; !ok {
    95  			lowerNameToCol[lowerCaseName] = cols[i]
    96  		}
    97  	}
    98  
    99  	sort.Slice(sortedTags, func(i, j int) bool { return sortedTags[i] < sortedTags[j] })
   100  
   101  	return &ColCollection{
   102  		cols:           columns,
   103  		Tags:           tags,
   104  		SortedTags:     sortedTags,
   105  		TagToCol:       tagToCol,
   106  		NameToCol:      nameToCol,
   107  		LowerNameToCol: lowerNameToCol,
   108  		TagToIdx:       tagToIdx,
   109  	}
   110  }
   111  
   112  // GetColumns returns the underlying list of columns. The list returned is a copy.
   113  func (cc *ColCollection) GetColumns() []Column {
   114  	colsCopy := make([]Column, len(cc.cols))
   115  	copy(colsCopy, cc.cols)
   116  	return colsCopy
   117  }
   118  
   119  func (cc *ColCollection) GetAtIndex(i int) Column {
   120  	return cc.cols[i]
   121  }
   122  
   123  // GetColumnNames returns a list of names of the columns.
   124  func (cc *ColCollection) GetColumnNames() []string {
   125  	names := make([]string, len(cc.cols))
   126  	for i, col := range cc.cols {
   127  		names[i] = col.Name
   128  	}
   129  	return names
   130  }
   131  
   132  // AppendColl returns a new collection with the additional ColCollection's columns appended
   133  func (cc *ColCollection) AppendColl(colColl *ColCollection) *ColCollection {
   134  	return cc.Append(colColl.cols...)
   135  }
   136  
   137  // Append returns a new collection with the additional columns appended
   138  func (cc *ColCollection) Append(cols ...Column) *ColCollection {
   139  	allCols := make([]Column, 0, len(cols)+len(cc.cols))
   140  	allCols = append(allCols, cc.cols...)
   141  	allCols = append(allCols, cols...)
   142  
   143  	return NewColCollection(allCols...)
   144  }
   145  
   146  // Iter iterates over all the columns in the supplied ordering
   147  func (cc *ColCollection) Iter(cb func(tag uint64, col Column) (stop bool, err error)) error {
   148  	for _, col := range cc.cols {
   149  		if stop, err := cb(col.Tag, col); err != nil {
   150  			return err
   151  		} else if stop {
   152  			break
   153  		}
   154  	}
   155  
   156  	return nil
   157  }
   158  
   159  // IterInSortOrder iterates over all the columns from lowest tag to highest tag.
   160  func (cc *ColCollection) IterInSortedOrder(cb func(tag uint64, col Column) (stop bool)) {
   161  	for _, tag := range cc.SortedTags {
   162  		val := cc.TagToCol[tag]
   163  		if stop := cb(tag, val); stop {
   164  			break
   165  		}
   166  	}
   167  }
   168  
   169  // GetByName takes the name of a column and returns the column and true if found. Otherwise InvalidCol and false are
   170  // returned.
   171  func (cc *ColCollection) GetByName(name string) (Column, bool) {
   172  	val, ok := cc.NameToCol[name]
   173  
   174  	if ok {
   175  		return val, true
   176  	}
   177  
   178  	return InvalidCol, false
   179  }
   180  
   181  // GetByNameCaseInensitive takes the name of a column and returns the column and true if there is a column with that
   182  // name ignoring case. Otherwise InvalidCol and false are returned. If multiple columns have the same case-insensitive
   183  // name, the first declared one is returned.
   184  func (cc *ColCollection) GetByNameCaseInsensitive(name string) (Column, bool) {
   185  	val, ok := cc.LowerNameToCol[strings.ToLower(name)]
   186  
   187  	if ok {
   188  		return val, true
   189  	}
   190  
   191  	return InvalidCol, false
   192  }
   193  
   194  // GetByTag takes a tag and returns the corresponding column and true if found, otherwise InvalidCol and false are
   195  // returned
   196  func (cc *ColCollection) GetByTag(tag uint64) (Column, bool) {
   197  	val, ok := cc.TagToCol[tag]
   198  
   199  	if ok {
   200  		return val, true
   201  	}
   202  
   203  	return InvalidCol, false
   204  }
   205  
   206  // GetByIndex returns a column with a given index
   207  func (cc *ColCollection) GetByIndex(idx int) Column {
   208  	return cc.cols[idx]
   209  }
   210  
   211  // Size returns the number of columns in the collection.
   212  func (cc *ColCollection) Size() int {
   213  	return len(cc.cols)
   214  }
   215  
   216  // ColCollsAreEqual determines whether two ColCollections are equal.
   217  func ColCollsAreEqual(cc1, cc2 *ColCollection) bool {
   218  	if cc1.Size() != cc2.Size() {
   219  		return false
   220  	}
   221  
   222  	areEqual := true
   223  	_ = cc1.Iter(func(tag uint64, col1 Column) (stop bool, err error) {
   224  		col2, ok := cc2.GetByTag(tag)
   225  
   226  		if !ok || !col1.Equals(col2) {
   227  			areEqual = false
   228  			return true, nil
   229  		}
   230  
   231  		return false, nil
   232  	})
   233  
   234  	return areEqual
   235  }
   236  
   237  // ColCollsAreCompatible determines whether two ColCollections are compatible with each other. Compatible columns have
   238  // the same tags and storage types, but may have different names, constraints or SQL type parameters.
   239  func ColCollsAreCompatible(cc1, cc2 *ColCollection) bool {
   240  	if cc1.Size() != cc2.Size() {
   241  		return false
   242  	}
   243  
   244  	areCompatible := true
   245  	_ = cc1.Iter(func(tag uint64, col1 Column) (stop bool, err error) {
   246  		col2, ok := cc2.GetByTag(tag)
   247  
   248  		if !ok || !col1.Compatible(col2) {
   249  			areCompatible = false
   250  			return true, nil
   251  		}
   252  
   253  		return false, nil
   254  	})
   255  
   256  	return areCompatible
   257  }
   258  
   259  // MapColCollection applies a function to each column in a ColCollection and creates a new ColCollection from the results.
   260  func MapColCollection(cc *ColCollection, cb func(col Column) Column) *ColCollection {
   261  	mapped := make([]Column, cc.Size())
   262  	for i, c := range cc.cols {
   263  		mapped[i] = cb(c)
   264  	}
   265  	return NewColCollection(mapped...)
   266  }
   267  
   268  // FilterColCollection applies a boolean function to column in a ColCollection, it creates a new ColCollection from the
   269  // set of columns for which the function returned true.
   270  func FilterColCollection(cc *ColCollection, cb func(col Column) bool) *ColCollection {
   271  	filtered := make([]Column, 0, cc.Size())
   272  	for _, c := range cc.cols {
   273  		if cb(c) {
   274  			filtered = append(filtered, c)
   275  		}
   276  	}
   277  	return NewColCollection(filtered...)
   278  }
   279  
   280  func ColCollUnion(colColls ...*ColCollection) (*ColCollection, error) {
   281  	var allCols []Column
   282  	for _, sch := range colColls {
   283  		err := sch.Iter(func(tag uint64, col Column) (stop bool, err error) {
   284  			allCols = append(allCols, col)
   285  			return false, nil
   286  		})
   287  
   288  		if err != nil {
   289  			return nil, err
   290  		}
   291  	}
   292  
   293  	return NewColCollection(allCols...), nil
   294  }
   295  
   296  // ColCollectionSetDifference returns the set difference leftCC - rightCC.
   297  func ColCollectionSetDifference(leftCC, rightCC *ColCollection) (d *ColCollection) {
   298  	d = FilterColCollection(leftCC, func(col Column) bool {
   299  		_, ok := rightCC.GetByTag(col.Tag)
   300  		return !ok
   301  	})
   302  	return d
   303  }