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 }