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  }