github.com/dolthub/go-mysql-server@v0.18.0/sql/rowexec/show_iters.go (about) 1 // Copyright 2023 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 rowexec 16 17 import ( 18 "fmt" 19 "io" 20 "strings" 21 "time" 22 23 "github.com/dolthub/go-mysql-server/sql" 24 "github.com/dolthub/go-mysql-server/sql/plan" 25 "github.com/dolthub/go-mysql-server/sql/types" 26 ) 27 28 type describeIter struct { 29 schema sql.Schema 30 i int 31 } 32 33 func (i *describeIter) Next(ctx *sql.Context) (sql.Row, error) { 34 if i.i >= len(i.schema) { 35 return nil, io.EOF 36 } 37 38 f := i.schema[i.i] 39 i.i++ 40 return sql.NewRow(f.Name, f.Type.String()), nil 41 } 42 43 func (i *describeIter) Close(*sql.Context) error { 44 return nil 45 } 46 47 type process struct { 48 id int64 49 user string 50 host string 51 db string 52 command string 53 time int64 54 state string 55 info string 56 } 57 58 func (p process) toRow() sql.Row { 59 var db interface{} 60 if p.db != "" { 61 db = p.db 62 } 63 return sql.NewRow( 64 p.id, 65 p.user, 66 p.host, 67 db, 68 p.command, 69 p.time, 70 p.state, 71 p.info, 72 ) 73 } 74 75 // cc here: https://dev.mysql.com/doc/refman/8.0/en/show-table-status.html 76 func tableToStatusRow(table string, numRows uint64, dataLength uint64, collation sql.CollationID) sql.Row { 77 var avgLength float64 = 0 78 if numRows > 0 { 79 avgLength = float64(dataLength) / float64(numRows) 80 } 81 return sql.NewRow( 82 table, // Name 83 "InnoDB", // Engine 84 // This column is unused. With the removal of .frm files in MySQL 8.0, this 85 // column now reports a hardcoded value of 10, which is the last .frm file 86 // version used in MySQL 5.7. 87 "10", // Version 88 "Fixed", // Row_format 89 numRows, // Rows 90 uint64(avgLength), // Avg_row_length 91 dataLength, // Data_length 92 uint64(0), // Max_data_length (Unused for InnoDB) 93 int64(0), // Index_length 94 int64(0), // Data_free 95 nil, // Auto_increment (always null) 96 nil, // Create_time 97 nil, // Update_time 98 nil, // Check_time 99 collation.String(), // Collation 100 nil, // Checksum 101 nil, // Create_options 102 nil, // Comments 103 ) 104 } 105 106 // generatePrivStrings creates a formatted GRANT <privilege_list> on <global/database/table> to <user@host> string 107 func generatePrivStrings(db, tbl, user string, privs []sql.PrivilegeType) string { 108 sb := strings.Builder{} 109 withGrantOption := "" 110 for i, priv := range privs { 111 privStr := priv.String() 112 if privStr == sql.PrivilegeType_GrantOption.String() { 113 if len(privs) > 1 { 114 withGrantOption = " WITH GRANT OPTION" 115 } 116 } else { 117 if i > 0 { 118 sb.WriteString(", ") 119 } 120 sb.WriteString(privStr) 121 } 122 } 123 // handle special case for empty global and database privileges 124 privStr := sb.String() 125 if len(privStr) == 0 { 126 if db == "*" { 127 privStr = "USAGE" 128 } else { 129 return "" 130 } 131 } 132 return fmt.Sprintf("GRANT %s ON %s.%s TO %s%s", privStr, db, tbl, user, withGrantOption) 133 } 134 135 // generateRoutinePrivStrings creates a formatted GRANT <PRILEDGE_LIST> on <ROUTINE_TYPE> <ROUTINE> to <user@host> string 136 func generateRoutinePrivStrings(db, routine, routine_type, user string, privs []sql.PrivilegeType) string { 137 privStrs := make([]string, 0, len(privs)) 138 grantOption := "" 139 for _, priv := range privs { 140 if priv == sql.PrivilegeType_GrantOption { 141 grantOption = " WITH GRANT OPTION" 142 continue 143 } 144 145 privStr := priv.String() 146 privStrs = append(privStrs, privStr) 147 } 148 149 // This is kind of an odd word to insert in the output, but it's what MySQL does when you have no privileges other 150 // than Grant Options. 151 finalPrivStr := "USAGE" 152 if len(privStrs) > 0 { 153 finalPrivStr = strings.Join(privStrs, ", ") 154 } 155 156 return fmt.Sprintf("GRANT %s ON %s %s.%s TO %s%s", finalPrivStr, routine_type, db, routine, user, grantOption) 157 } 158 159 func newIndexesToShow(indexes []sql.Index) *indexesToShow { 160 return &indexesToShow{ 161 indexes: indexes, 162 } 163 } 164 165 type indexesToShow struct { 166 indexes []sql.Index 167 pos int 168 epos int 169 } 170 171 type idxToShow struct { 172 index sql.Index 173 expression string 174 exPosition int 175 } 176 177 func (i *indexesToShow) next() (*idxToShow, error) { 178 if i.pos >= len(i.indexes) { 179 return nil, io.EOF 180 } 181 182 index := i.indexes[i.pos] 183 expressions := index.Expressions() 184 if i.epos >= len(expressions) { 185 i.pos++ 186 if i.pos >= len(i.indexes) { 187 return nil, io.EOF 188 } 189 190 index = i.indexes[i.pos] 191 i.epos = 0 192 expressions = index.Expressions() 193 } 194 195 show := &idxToShow{ 196 index: index, 197 expression: expressions[i.epos], 198 exPosition: i.epos, 199 } 200 201 i.epos++ 202 return show, nil 203 } 204 205 type showIndexesIter struct { 206 table *plan.ResolvedTable 207 idxs *indexesToShow 208 } 209 210 func (i *showIndexesIter) Next(ctx *sql.Context) (sql.Row, error) { 211 show, err := i.idxs.next() 212 if err != nil { 213 return nil, err 214 } 215 216 var expression, columnName interface{} 217 columnName, expression = nil, show.expression 218 tbl := i.table 219 220 if err != nil { 221 return nil, err 222 } 223 224 nullable := "" 225 if col := plan.GetColumnFromIndexExpr(show.expression, tbl); col != nil { 226 columnName, expression = col.Name, nil 227 if col.Nullable { 228 nullable = "YES" 229 } 230 } 231 232 visible := "YES" 233 if x, ok := show.index.(sql.DriverIndex); ok && len(x.Driver()) > 0 { 234 if !ctx.GetIndexRegistry().CanUseIndex(x) { 235 visible = "NO" 236 } 237 } 238 239 nonUnique := 0 240 if !show.index.IsUnique() { 241 nonUnique = 1 242 } 243 244 return sql.NewRow( 245 show.index.Table(), // "Table" string 246 nonUnique, // "Non_unique" int32, Values [0, 1] 247 show.index.ID(), // "Key_name" string 248 show.exPosition+1, // "Seq_in_index" int32 249 columnName, // "Column_name" string 250 nil, // "Collation" string, Values [A, D, NULL] 251 int64(0), // "Cardinality" int64 (not calculated) 252 nil, // "Sub_part" int64 253 nil, // "Packed" string 254 nullable, // "Null" string, Values [YES, ''] 255 show.index.IndexType(), // "Index_type" string 256 show.index.Comment(), // "Comment" string 257 "", // "Index_comment" string 258 visible, // "Visible" string, Values [YES, NO] 259 expression, // "Expression" string 260 ), nil 261 } 262 263 func isFirstColInUniqueKey(s *plan.ShowColumns, col *sql.Column, table sql.Table) bool { 264 for _, idx := range s.Indexes { 265 if !idx.IsUnique() { 266 continue 267 } 268 269 firstIndexCol := plan.GetColumnFromIndexExpr(idx.Expressions()[0], table) 270 if firstIndexCol != nil && firstIndexCol.Name == col.Name { 271 return true 272 } 273 } 274 275 return false 276 } 277 278 func isFirstColInNonUniqueKey(s *plan.ShowColumns, col *sql.Column, table sql.Table) bool { 279 for _, idx := range s.Indexes { 280 if idx.IsUnique() { 281 continue 282 } 283 284 firstIndexCol := plan.GetColumnFromIndexExpr(idx.Expressions()[0], table) 285 if firstIndexCol != nil && firstIndexCol.Name == col.Name { 286 return true 287 } 288 } 289 290 return false 291 } 292 293 func (i *showIndexesIter) Close(*sql.Context) error { 294 return nil 295 } 296 297 type showCreateTablesIter struct { 298 table sql.Node 299 schema sql.Schema 300 didIteration bool 301 isView bool 302 indexes []sql.Index 303 checks sql.CheckConstraints 304 pkSchema sql.PrimaryKeySchema 305 } 306 307 func (i *showCreateTablesIter) Next(ctx *sql.Context) (sql.Row, error) { 308 if i.didIteration { 309 return nil, io.EOF 310 } 311 312 i.didIteration = true 313 314 var row sql.Row 315 switch table := i.table.(type) { 316 case *plan.ResolvedTable: 317 // MySQL behavior is to allow show create table for views, but not show create view for tables. 318 if i.isView { 319 return nil, plan.ErrNotView.New(table.Name()) 320 } 321 322 composedCreateTableStatement, err := i.produceCreateTableStatement(ctx, table.Table, i.schema, i.pkSchema) 323 if err != nil { 324 return nil, err 325 } 326 row = sql.NewRow( 327 table.Name(), // "Table" string 328 composedCreateTableStatement, // "Create Table" string 329 ) 330 case *plan.SubqueryAlias: 331 characterSetClient, err := ctx.GetSessionVariable(ctx, "character_set_client") 332 if err != nil { 333 return nil, err 334 } 335 collationConnection, err := ctx.GetSessionVariable(ctx, "collation_connection") 336 if err != nil { 337 return nil, err 338 } 339 row = sql.NewRow( 340 table.Name(), // "View" string 341 produceCreateViewStatement(table), // "Create View" string 342 characterSetClient, 343 collationConnection, 344 ) 345 default: 346 panic(fmt.Sprintf("unexpected type %T", i.table)) 347 } 348 349 return row, nil 350 } 351 352 type NameAndSchema interface { 353 sql.Nameable 354 Schema() sql.Schema 355 } 356 357 func (i *showCreateTablesIter) produceCreateTableStatement(ctx *sql.Context, table sql.Table, schema sql.Schema, pkSchema sql.PrimaryKeySchema) (string, error) { 358 colStmts := make([]string, len(schema)) 359 var primaryKeyCols []string 360 361 var pkOrdinals []int 362 if len(pkSchema.Schema) > 0 { 363 pkOrdinals = pkSchema.PkOrdinals 364 } 365 366 // Statement creation parts for each column 367 tableCollation := table.Collation() 368 for i, col := range schema { 369 var colDefaultStr string 370 // TODO: The columns that are rendered in defaults should be backticked 371 if col.Default != nil && col.Generated == nil { 372 // TODO : string literals should have character set introducer 373 colDefaultStr = col.Default.String() 374 if colDefaultStr != "NULL" && col.Default.IsLiteral() && !types.IsTime(col.Default.Type()) && !types.IsText(col.Default.Type()) { 375 v, err := col.Default.Eval(ctx, nil) 376 if err != nil { 377 return "", err 378 } 379 colDefaultStr = fmt.Sprintf("'%v'", v) 380 } 381 } 382 var onUpdateStr string 383 if col.OnUpdate != nil { 384 onUpdateStr = col.OnUpdate.String() 385 if onUpdateStr != "NULL" && col.OnUpdate.IsLiteral() && !types.IsTime(col.OnUpdate.Type()) && !types.IsText(col.OnUpdate.Type()) { 386 v, err := col.OnUpdate.Eval(ctx, nil) 387 if err != nil { 388 return "", err 389 } 390 onUpdateStr = fmt.Sprintf("'%v'", v) 391 } 392 } 393 394 if col.PrimaryKey && len(pkSchema.Schema) == 0 { 395 pkOrdinals = append(pkOrdinals, i) 396 } 397 398 colStmts[i] = sql.GenerateCreateTableColumnDefinition(col, colDefaultStr, onUpdateStr, tableCollation) 399 } 400 401 for _, i := range pkOrdinals { 402 primaryKeyCols = append(primaryKeyCols, schema[i].Name) 403 } 404 405 if len(primaryKeyCols) > 0 { 406 colStmts = append(colStmts, sql.GenerateCreateTablePrimaryKeyDefinition(primaryKeyCols)) 407 } 408 409 for _, index := range i.indexes { 410 // The primary key may or may not be declared as an index by the table; don't print it twice. 411 if index.ID() == "PRIMARY" { 412 continue 413 } 414 415 prefixLengths := index.PrefixLengths() 416 var indexCols []string 417 for i, expr := range index.Expressions() { 418 col := plan.GetColumnFromIndexExpr(expr, table) 419 if col != nil { 420 indexDef := sql.QuoteIdentifier(col.Name) 421 if len(prefixLengths) > i && prefixLengths[i] != 0 { 422 indexDef += fmt.Sprintf("(%v)", prefixLengths[i]) 423 } 424 indexCols = append(indexCols, indexDef) 425 } 426 } 427 428 colStmts = append(colStmts, sql.GenerateCreateTableIndexDefinition(index.IsUnique(), index.IsSpatial(), 429 index.IsFullText(), index.ID(), indexCols, index.Comment())) 430 } 431 432 fkt, err := getForeignKeyTable(table) 433 if err == nil && fkt != nil { 434 fks, err := fkt.GetDeclaredForeignKeys(ctx) 435 if err != nil { 436 return "", err 437 } 438 for _, fk := range fks { 439 onDelete := "" 440 if len(fk.OnDelete) > 0 && fk.OnDelete != sql.ForeignKeyReferentialAction_DefaultAction { 441 onDelete = string(fk.OnDelete) 442 } 443 onUpdate := "" 444 if len(fk.OnUpdate) > 0 && fk.OnUpdate != sql.ForeignKeyReferentialAction_DefaultAction { 445 onUpdate = string(fk.OnUpdate) 446 } 447 colStmts = append(colStmts, sql.GenerateCreateTableForiegnKeyDefinition(fk.Name, fk.Columns, fk.ParentTable, fk.ParentColumns, onDelete, onUpdate)) 448 } 449 } 450 451 if i.checks != nil { 452 for _, check := range i.checks { 453 colStmts = append(colStmts, sql.GenerateCreateTableCheckConstraintClause(check.Name, check.Expr.String(), check.Enforced)) 454 } 455 } 456 457 comment := "" 458 if commentedTable, ok := table.(sql.CommentedTable); ok { 459 comment = commentedTable.Comment() 460 } 461 462 return sql.GenerateCreateTableStatement(table.Name(), colStmts, table.Collation().CharacterSet().Name(), table.Collation().Name(), comment), nil 463 } 464 465 func produceCreateViewStatement(view *plan.SubqueryAlias) string { 466 return fmt.Sprintf( 467 "CREATE VIEW `%s` AS %s", 468 view.Name(), 469 view.TextDefinition, 470 ) 471 } 472 473 func (i *showCreateTablesIter) Close(*sql.Context) error { 474 return nil 475 } 476 477 func getForeignKeyTable(t sql.Table) (sql.ForeignKeyTable, error) { 478 switch t := t.(type) { 479 case sql.ForeignKeyTable: 480 return t, nil 481 case sql.TableWrapper: 482 return getForeignKeyTable(t.Underlying()) 483 case *plan.ResolvedTable: 484 return getForeignKeyTable(t.Table) 485 default: 486 return nil, sql.ErrNoForeignKeySupport.New(t.Name()) 487 } 488 } 489 490 func formatReplicaStatusTimestamp(t *time.Time) string { 491 if t == nil { 492 return "" 493 } 494 495 return t.Format(time.UnixDate) 496 }