github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/cat/utils.go (about) 1 // Copyright 2019 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package cat 12 13 import ( 14 "bytes" 15 "context" 16 "fmt" 17 18 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 19 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 20 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 21 "github.com/cockroachdb/cockroach/pkg/util" 22 "github.com/cockroachdb/cockroach/pkg/util/treeprinter" 23 "github.com/cockroachdb/errors" 24 ) 25 26 // ExpandDataSourceGlob is a utility function that expands a tree.TablePattern 27 // into a list of object names. 28 func ExpandDataSourceGlob( 29 ctx context.Context, catalog Catalog, flags Flags, pattern tree.TablePattern, 30 ) ([]DataSourceName, error) { 31 32 switch p := pattern.(type) { 33 case *tree.TableName: 34 _, name, err := catalog.ResolveDataSource(ctx, flags, p) 35 if err != nil { 36 return nil, err 37 } 38 return []DataSourceName{name}, nil 39 40 case *tree.AllTablesSelector: 41 schema, _, err := catalog.ResolveSchema(ctx, flags, &p.ObjectNamePrefix) 42 if err != nil { 43 return nil, err 44 } 45 46 return schema.GetDataSourceNames(ctx) 47 48 default: 49 return nil, errors.Errorf("invalid TablePattern type %T", p) 50 } 51 } 52 53 // ResolveTableIndex resolves a TableIndexName. 54 func ResolveTableIndex( 55 ctx context.Context, catalog Catalog, flags Flags, name *tree.TableIndexName, 56 ) (Index, DataSourceName, error) { 57 if name.Table.ObjectName != "" { 58 ds, tn, err := catalog.ResolveDataSource(ctx, flags, &name.Table) 59 if err != nil { 60 return nil, DataSourceName{}, err 61 } 62 table, ok := ds.(Table) 63 if !ok { 64 return nil, DataSourceName{}, pgerror.Newf( 65 pgcode.WrongObjectType, "%q is not a table", name.Table.ObjectName, 66 ) 67 } 68 if name.Index == "" { 69 // Return primary index. 70 return table.Index(0), tn, nil 71 } 72 for i := 0; i < table.IndexCount(); i++ { 73 if idx := table.Index(i); idx.Name() == tree.Name(name.Index) { 74 return idx, tn, nil 75 } 76 } 77 return nil, DataSourceName{}, pgerror.Newf( 78 pgcode.UndefinedObject, "index %q does not exist", name.Index, 79 ) 80 } 81 82 // We have to search for a table that has an index with the given name. 83 schema, _, err := catalog.ResolveSchema(ctx, flags, &name.Table.ObjectNamePrefix) 84 if err != nil { 85 return nil, DataSourceName{}, err 86 } 87 dsNames, err := schema.GetDataSourceNames(ctx) 88 if err != nil { 89 return nil, DataSourceName{}, err 90 } 91 var found Index 92 var foundTabName DataSourceName 93 for i := range dsNames { 94 ds, tn, err := catalog.ResolveDataSource(ctx, flags, &dsNames[i]) 95 if err != nil { 96 return nil, DataSourceName{}, err 97 } 98 table, ok := ds.(Table) 99 if !ok { 100 // Not a table, ignore. 101 continue 102 } 103 for i := 0; i < table.IndexCount(); i++ { 104 if idx := table.Index(i); idx.Name() == tree.Name(name.Index) { 105 if found != nil { 106 return nil, DataSourceName{}, pgerror.Newf(pgcode.AmbiguousParameter, 107 "index name %q is ambiguous (found in %s and %s)", 108 name.Index, tn.String(), foundTabName.String()) 109 } 110 found = idx 111 foundTabName = tn 112 break 113 } 114 } 115 } 116 if found == nil { 117 return nil, DataSourceName{}, pgerror.Newf( 118 pgcode.UndefinedObject, "index %q does not exist", name.Index, 119 ) 120 } 121 return found, foundTabName, nil 122 } 123 124 // ConvertColumnIDsToOrdinals converts a list of ColumnIDs (such as from a 125 // tree.TableRef), to a list of ordinal positions of columns within the given 126 // table. See tree.Table for more information on column ordinals. 127 func ConvertColumnIDsToOrdinals(tab Table, columns []tree.ColumnID) (ordinals []int) { 128 ordinals = make([]int, len(columns)) 129 for i, c := range columns { 130 ord := 0 131 cnt := tab.ColumnCount() 132 for ord < cnt { 133 if tab.Column(ord).ColID() == StableID(c) { 134 break 135 } 136 ord++ 137 } 138 if ord >= cnt { 139 panic(pgerror.Newf(pgcode.UndefinedColumn, 140 "column [%d] does not exist", c)) 141 } 142 ordinals[i] = ord 143 } 144 return ordinals 145 } 146 147 // FindTableColumnByName returns the ordinal of the non-mutation column having 148 // the given name, if one exists in the given table. Otherwise, it returns -1. 149 func FindTableColumnByName(tab Table, name tree.Name) int { 150 for ord, n := 0, tab.ColumnCount(); ord < n; ord++ { 151 if tab.Column(ord).ColName() == name { 152 return ord 153 } 154 } 155 return -1 156 } 157 158 // FormatTable nicely formats a catalog table using a treeprinter for debugging 159 // and testing. 160 func FormatTable(cat Catalog, tab Table, tp treeprinter.Node) { 161 child := tp.Childf("TABLE %s", tab.Name()) 162 if tab.IsVirtualTable() { 163 child.Child("virtual table") 164 } 165 166 var buf bytes.Buffer 167 for i := 0; i < tab.DeletableColumnCount(); i++ { 168 buf.Reset() 169 formatColumn(tab.Column(i), IsMutationColumn(tab, i), &buf) 170 child.Child(buf.String()) 171 } 172 173 // If we only have one primary family (the default), don't print it. 174 if tab.FamilyCount() > 1 || tab.Family(0).Name() != "primary" { 175 for i := 0; i < tab.FamilyCount(); i++ { 176 buf.Reset() 177 formatFamily(tab.Family(i), &buf) 178 child.Child(buf.String()) 179 } 180 } 181 182 for i := 0; i < tab.CheckCount(); i++ { 183 child.Childf("CHECK (%s)", tab.Check(i).Constraint) 184 } 185 186 for i := 0; i < tab.DeletableIndexCount(); i++ { 187 formatCatalogIndex(tab, i, child) 188 } 189 190 for i := 0; i < tab.OutboundForeignKeyCount(); i++ { 191 formatCatalogFKRef(cat, false /* inbound */, tab.OutboundForeignKey(i), child) 192 } 193 194 for i := 0; i < tab.InboundForeignKeyCount(); i++ { 195 formatCatalogFKRef(cat, true /* inbound */, tab.InboundForeignKey(i), child) 196 } 197 198 // TODO(radu): show stats. 199 } 200 201 // formatCatalogIndex nicely formats a catalog index using a treeprinter for 202 // debugging and testing. 203 func formatCatalogIndex(tab Table, ord int, tp treeprinter.Node) { 204 idx := tab.Index(ord) 205 inverted := "" 206 if idx.IsInverted() { 207 inverted = "INVERTED " 208 } 209 mutation := "" 210 if IsMutationIndex(tab, ord) { 211 mutation = " (mutation)" 212 } 213 child := tp.Childf("%sINDEX %s%s", inverted, idx.Name(), mutation) 214 215 var buf bytes.Buffer 216 colCount := idx.ColumnCount() 217 if ord == PrimaryIndex { 218 // Omit the "stored" columns from the primary index. 219 colCount = idx.KeyColumnCount() 220 } 221 222 for i := 0; i < colCount; i++ { 223 buf.Reset() 224 225 idxCol := idx.Column(i) 226 formatColumn(idxCol.Column, false /* isMutationCol */, &buf) 227 if idxCol.Descending { 228 fmt.Fprintf(&buf, " desc") 229 } 230 231 if i >= idx.LaxKeyColumnCount() { 232 fmt.Fprintf(&buf, " (storing)") 233 } 234 235 child.Child(buf.String()) 236 } 237 238 FormatZone(idx.Zone(), child) 239 240 partPrefixes := idx.PartitionByListPrefixes() 241 if len(partPrefixes) != 0 { 242 c := child.Child("partition by list prefixes") 243 for i := range partPrefixes { 244 c.Child(partPrefixes[i].String()) 245 } 246 } 247 if n := idx.InterleaveAncestorCount(); n > 0 { 248 c := child.Child("interleave ancestors") 249 for i := 0; i < n; i++ { 250 table, index, numKeyCols := idx.InterleaveAncestor(i) 251 c.Childf( 252 "table=%d index=%d (%d key column%s)", 253 table, index, numKeyCols, util.Pluralize(int64(numKeyCols)), 254 ) 255 } 256 } 257 if n := idx.InterleavedByCount(); n > 0 { 258 c := child.Child("interleaved by") 259 for i := 0; i < n; i++ { 260 table, index := idx.InterleavedBy(i) 261 c.Childf("table=%d index=%d", table, index) 262 } 263 } 264 } 265 266 // formatColPrefix returns a string representation of a list of columns. The 267 // columns are provided through a function. 268 func formatCols(tab Table, numCols int, colOrdinal func(tab Table, i int) int) string { 269 var buf bytes.Buffer 270 buf.WriteByte('(') 271 for i := 0; i < numCols; i++ { 272 if i > 0 { 273 buf.WriteString(", ") 274 } 275 colName := tab.Column(colOrdinal(tab, i)).ColName() 276 buf.WriteString(colName.String()) 277 } 278 buf.WriteByte(')') 279 280 return buf.String() 281 } 282 283 // formatCatalogFKRef nicely formats a catalog foreign key reference using a 284 // treeprinter for debugging and testing. 285 func formatCatalogFKRef( 286 catalog Catalog, inbound bool, fkRef ForeignKeyConstraint, tp treeprinter.Node, 287 ) { 288 originDS, _, err := catalog.ResolveDataSourceByID(context.TODO(), Flags{}, fkRef.OriginTableID()) 289 if err != nil { 290 panic(err) 291 } 292 refDS, _, err := catalog.ResolveDataSourceByID(context.TODO(), Flags{}, fkRef.ReferencedTableID()) 293 if err != nil { 294 panic(err) 295 } 296 title := "CONSTRAINT" 297 if inbound { 298 title = "REFERENCED BY " + title 299 } 300 var extra bytes.Buffer 301 if fkRef.MatchMethod() != tree.MatchSimple { 302 fmt.Fprintf(&extra, " %s", fkRef.MatchMethod()) 303 } 304 305 if action := fkRef.DeleteReferenceAction(); action != tree.NoAction { 306 fmt.Fprintf(&extra, " ON DELETE %s", action.String()) 307 } 308 309 tp.Childf( 310 "%s %s FOREIGN KEY %v %s REFERENCES %v %s%s", 311 title, 312 fkRef.Name(), 313 originDS.Name(), 314 formatCols(originDS.(Table), fkRef.ColumnCount(), fkRef.OriginColumnOrdinal), 315 refDS.Name(), 316 formatCols(refDS.(Table), fkRef.ColumnCount(), fkRef.ReferencedColumnOrdinal), 317 extra.String(), 318 ) 319 } 320 321 func formatColumn(col Column, isMutationCol bool, buf *bytes.Buffer) { 322 fmt.Fprintf(buf, "%s %s", col.ColName(), col.DatumType()) 323 if !col.IsNullable() { 324 fmt.Fprintf(buf, " not null") 325 } 326 if col.IsComputed() { 327 fmt.Fprintf(buf, " as (%s) stored", col.ComputedExprStr()) 328 } 329 if col.HasDefault() { 330 fmt.Fprintf(buf, " default (%s)", col.DefaultExprStr()) 331 } 332 if col.IsHidden() { 333 fmt.Fprintf(buf, " [hidden]") 334 } 335 if isMutationCol { 336 fmt.Fprintf(buf, " [mutation]") 337 } 338 } 339 340 func formatFamily(family Family, buf *bytes.Buffer) { 341 fmt.Fprintf(buf, "FAMILY %s (", family.Name()) 342 for i, n := 0, family.ColumnCount(); i < n; i++ { 343 if i != 0 { 344 buf.WriteString(", ") 345 } 346 col := family.Column(i) 347 buf.WriteString(string(col.ColName())) 348 } 349 buf.WriteString(")") 350 }