github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/sqlfmt/schema_fmt.go (about)

     1  // Copyright 2020 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 sqlfmt
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  
    21  	"github.com/dolthub/go-mysql-server/sql"
    22  
    23  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    24  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    25  )
    26  
    27  // GenerateCreateTableColumnDefinition returns column definition for CREATE TABLE statement with no indentation
    28  func GenerateCreateTableColumnDefinition(col schema.Column, tableCollation sql.CollationID) string {
    29  	colStr := GenerateCreateTableIndentedColumnDefinition(col, tableCollation)
    30  	return strings.TrimPrefix(colStr, "  ")
    31  }
    32  
    33  // GenerateCreateTableIndentedColumnDefinition returns column definition for CREATE TABLE statement with no indentation
    34  func GenerateCreateTableIndentedColumnDefinition(col schema.Column, tableCollation sql.CollationID) string {
    35  	var defaultVal, genVal, onUpdateVal *sql.ColumnDefaultValue
    36  	if col.Default != "" {
    37  		defaultVal = sql.NewUnresolvedColumnDefaultValue(col.Default)
    38  	}
    39  	if col.Generated != "" {
    40  		genVal = sql.NewUnresolvedColumnDefaultValue(col.Generated)
    41  	}
    42  	if col.OnUpdate != "" {
    43  		onUpdateVal = sql.NewUnresolvedColumnDefaultValue(col.OnUpdate)
    44  	}
    45  
    46  	return sql.GenerateCreateTableColumnDefinition(
    47  		&sql.Column{
    48  			Name:          col.Name,
    49  			Type:          col.TypeInfo.ToSqlType(),
    50  			Default:       defaultVal,
    51  			AutoIncrement: col.AutoIncrement,
    52  			Nullable:      col.IsNullable(),
    53  			Comment:       col.Comment,
    54  			Generated:     genVal,
    55  			Virtual:       col.Virtual,
    56  			OnUpdate:      onUpdateVal,
    57  		}, col.Default, col.OnUpdate, tableCollation)
    58  }
    59  
    60  // GenerateCreateTableIndexDefinition returns index definition for CREATE TABLE statement with indentation of 2 spaces
    61  func GenerateCreateTableIndexDefinition(index schema.Index) string {
    62  	return sql.GenerateCreateTableIndexDefinition(index.IsUnique(), index.IsSpatial(), index.IsFullText(), index.Name(),
    63  		sql.QuoteIdentifiers(index.ColumnNames()), index.Comment())
    64  }
    65  
    66  // GenerateCreateTableForeignKeyDefinition returns foreign key definition for CREATE TABLE statement with indentation of 2 spaces
    67  func GenerateCreateTableForeignKeyDefinition(fk doltdb.ForeignKey, sch, parentSch schema.Schema) string {
    68  	var fkCols []string
    69  	if fk.IsResolved() {
    70  		for _, tag := range fk.TableColumns {
    71  			c, _ := sch.GetAllCols().GetByTag(tag)
    72  			fkCols = append(fkCols, c.Name)
    73  		}
    74  	} else {
    75  		fkCols = append(fkCols, fk.UnresolvedFKDetails.TableColumns...)
    76  	}
    77  
    78  	var parentCols []string
    79  	if parentSch != nil && fk.IsResolved() {
    80  		for _, tag := range fk.ReferencedTableColumns {
    81  			c, _ := parentSch.GetAllCols().GetByTag(tag)
    82  			parentCols = append(parentCols, c.Name)
    83  		}
    84  	} else {
    85  		// the referenced table is dropped, so the schema is nil or the foreign key is not resolved
    86  		parentCols = append(parentCols, fk.UnresolvedFKDetails.ReferencedTableColumns...)
    87  	}
    88  
    89  	onDelete := ""
    90  	if fk.OnDelete != doltdb.ForeignKeyReferentialAction_DefaultAction {
    91  		onDelete = fk.OnDelete.String()
    92  	}
    93  	onUpdate := ""
    94  	if fk.OnUpdate != doltdb.ForeignKeyReferentialAction_DefaultAction {
    95  		onUpdate = fk.OnUpdate.String()
    96  	}
    97  	return sql.GenerateCreateTableForiegnKeyDefinition(fk.Name, fkCols, fk.ReferencedTableName, parentCols, onDelete, onUpdate)
    98  }
    99  
   100  // GenerateCreateTableCheckConstraintClause returns check constraint clause definition for CREATE TABLE statement with indentation of 2 spaces
   101  func GenerateCreateTableCheckConstraintClause(check schema.Check) string {
   102  	return sql.GenerateCreateTableCheckConstraintClause(check.Name(), check.Expression(), check.Enforced())
   103  }
   104  
   105  func DropTableStmt(tableName string) string {
   106  	var b strings.Builder
   107  	b.WriteString("DROP TABLE ")
   108  	b.WriteString(QuoteIdentifier(tableName))
   109  	b.WriteString(";")
   110  	return b.String()
   111  }
   112  
   113  func DropTableIfExistsStmt(tableName string) string {
   114  	var b strings.Builder
   115  	b.WriteString("DROP TABLE IF EXISTS ")
   116  	b.WriteString(QuoteIdentifier(tableName))
   117  	b.WriteString(";")
   118  	return b.String()
   119  }
   120  
   121  func AlterTableAddColStmt(tableName string, newColDef string) string {
   122  	var b strings.Builder
   123  	b.WriteString("ALTER TABLE ")
   124  	b.WriteString(QuoteIdentifier(tableName))
   125  	b.WriteString(" ADD ")
   126  	b.WriteString(newColDef)
   127  	b.WriteRune(';')
   128  	return b.String()
   129  }
   130  
   131  func AlterTableModifyColStmt(tableName string, newColDef string) string {
   132  	var b strings.Builder
   133  	b.WriteString("ALTER TABLE ")
   134  	b.WriteString(QuoteIdentifier(tableName))
   135  	b.WriteString(" MODIFY COLUMN ")
   136  	b.WriteString(newColDef)
   137  	b.WriteRune(';')
   138  	return b.String()
   139  }
   140  
   141  func AlterTableDropColStmt(tableName string, oldColName string) string {
   142  	var b strings.Builder
   143  	b.WriteString("ALTER TABLE ")
   144  	b.WriteString(QuoteIdentifier(tableName))
   145  	b.WriteString(" DROP ")
   146  	b.WriteString(QuoteIdentifier(oldColName))
   147  	b.WriteRune(';')
   148  	return b.String()
   149  }
   150  
   151  func AlterTableRenameColStmt(tableName string, oldColName string, newColName string) string {
   152  	var b strings.Builder
   153  	b.WriteString("ALTER TABLE ")
   154  	b.WriteString(QuoteIdentifier(tableName))
   155  	b.WriteString(" RENAME COLUMN ")
   156  	b.WriteString(QuoteIdentifier(oldColName))
   157  	b.WriteString(" TO ")
   158  	b.WriteString(QuoteIdentifier(newColName))
   159  	b.WriteRune(';')
   160  	return b.String()
   161  }
   162  
   163  func AlterTableDropPks(tableName string) string {
   164  	var b strings.Builder
   165  	b.WriteString("ALTER TABLE ")
   166  	b.WriteString(QuoteIdentifier(tableName))
   167  	b.WriteString(" DROP PRIMARY KEY")
   168  	b.WriteRune(';')
   169  	return b.String()
   170  }
   171  
   172  func AlterTableAddPrimaryKeys(tableName string, pkColNames []string) string {
   173  	var b strings.Builder
   174  	b.WriteString("ALTER TABLE ")
   175  	b.WriteString(QuoteIdentifier(tableName))
   176  	b.WriteString(" ADD PRIMARY KEY (")
   177  
   178  	for i := 0; i < len(pkColNames); i++ {
   179  		if i == 0 {
   180  			b.WriteString(pkColNames[i])
   181  		} else {
   182  			b.WriteString("," + pkColNames[i])
   183  		}
   184  	}
   185  	b.WriteRune(')')
   186  	b.WriteRune(';')
   187  	return b.String()
   188  }
   189  
   190  func RenameTableStmt(fromName string, toName string) string {
   191  	var b strings.Builder
   192  	b.WriteString("RENAME TABLE ")
   193  	b.WriteString(QuoteIdentifier(fromName))
   194  	b.WriteString(" TO ")
   195  	b.WriteString(QuoteIdentifier(toName))
   196  	b.WriteString(";")
   197  
   198  	return b.String()
   199  }
   200  
   201  func AlterTableAddIndexStmt(tableName string, idx schema.Index) string {
   202  	var b strings.Builder
   203  	b.WriteString("ALTER TABLE ")
   204  	b.WriteString(QuoteIdentifier(tableName))
   205  	b.WriteString(" ADD INDEX ")
   206  	b.WriteString(QuoteIdentifier(idx.Name()))
   207  	var cols []string
   208  	for _, cn := range idx.ColumnNames() {
   209  		cols = append(cols, QuoteIdentifier(cn))
   210  	}
   211  	b.WriteString("(" + strings.Join(cols, ",") + ");")
   212  	return b.String()
   213  }
   214  
   215  func AlterTableDropIndexStmt(tableName string, idx schema.Index) string {
   216  	var b strings.Builder
   217  	b.WriteString("ALTER TABLE ")
   218  	b.WriteString(QuoteIdentifier(tableName))
   219  	b.WriteString(" DROP INDEX ")
   220  	b.WriteString(QuoteIdentifier(idx.Name()))
   221  	b.WriteRune(';')
   222  	return b.String()
   223  }
   224  
   225  func AlterTableCollateStmt(tableName string, fromCollation, toCollation schema.Collation) string {
   226  	var b strings.Builder
   227  	b.WriteString("ALTER TABLE ")
   228  	b.WriteString(QuoteIdentifier(tableName))
   229  	toCollationId := sql.CollationID(toCollation)
   230  	b.WriteString(" COLLATE=" + QuoteComment(toCollationId.Name()) + ";")
   231  	return b.String()
   232  }
   233  
   234  func AlterDatabaseCollateStmt(dbName string, fromCollation, toCollation schema.Collation) string {
   235  	var b strings.Builder
   236  	b.WriteString("ALTER DATABASE ")
   237  	b.WriteString(QuoteIdentifier(dbName))
   238  	toCollationId := sql.CollationID(toCollation)
   239  	b.WriteString(" COLLATE=" + QuoteComment(toCollationId.Name()) + ";")
   240  	return b.String()
   241  }
   242  
   243  func AlterTableAddForeignKeyStmt(fk doltdb.ForeignKey, sch, parentSch schema.Schema) string {
   244  	var b strings.Builder
   245  	b.WriteString("ALTER TABLE ")
   246  	b.WriteString(QuoteIdentifier(fk.TableName))
   247  	b.WriteString(" ADD CONSTRAINT ")
   248  	b.WriteString(QuoteIdentifier(fk.Name))
   249  	b.WriteString(" FOREIGN KEY ")
   250  	var childCols []string
   251  	for _, tag := range fk.TableColumns {
   252  		c, _ := sch.GetAllCols().GetByTag(tag)
   253  		childCols = append(childCols, QuoteIdentifier(c.Name))
   254  	}
   255  	b.WriteString("(" + strings.Join(childCols, ",") + ")")
   256  	b.WriteString(" REFERENCES ")
   257  	var parentCols []string
   258  	for _, tag := range fk.ReferencedTableColumns {
   259  		c, _ := parentSch.GetAllCols().GetByTag(tag)
   260  		parentCols = append(parentCols, QuoteIdentifier(c.Name))
   261  	}
   262  	b.WriteString(QuoteIdentifier(fk.ReferencedTableName))
   263  	b.WriteString(" (" + strings.Join(parentCols, ",") + ");")
   264  	return b.String()
   265  }
   266  
   267  func AlterTableDropForeignKeyStmt(tableName, fkName string) string {
   268  	var b strings.Builder
   269  	b.WriteString("ALTER TABLE ")
   270  	b.WriteString(QuoteIdentifier(tableName))
   271  	b.WriteString(" DROP FOREIGN KEY ")
   272  	b.WriteString(QuoteIdentifier(fkName))
   273  	b.WriteRune(';')
   274  	return b.String()
   275  }
   276  
   277  // GenerateCreateTableStatement returns a CREATE TABLE statement for given table. This is a reasonable approximation of
   278  // `SHOW CREATE TABLE` in the engine, but may have some differences. Callers are advised to use the engine when
   279  // possible.
   280  func GenerateCreateTableStatement(tblName string, sch schema.Schema, fks []doltdb.ForeignKey, fksParentSch map[string]schema.Schema) (string, error) {
   281  	colStmts := make([]string, sch.GetAllCols().Size())
   282  
   283  	// Statement creation parts for each column
   284  	for i, col := range sch.GetAllCols().GetColumns() {
   285  		colStmts[i] = GenerateCreateTableIndentedColumnDefinition(col, sql.CollationID(sch.GetCollation()))
   286  	}
   287  
   288  	primaryKeyCols := sch.GetPKCols().GetColumnNames()
   289  	if len(primaryKeyCols) > 0 {
   290  		primaryKey := sql.GenerateCreateTablePrimaryKeyDefinition(primaryKeyCols)
   291  		colStmts = append(colStmts, primaryKey)
   292  	}
   293  
   294  	indexes := sch.Indexes().AllIndexes()
   295  	for _, index := range indexes {
   296  		// The primary key may or may not be declared as an index by the table. Don't print it twice if it's here.
   297  		if isPrimaryKeyIndex(index, sch) {
   298  			continue
   299  		}
   300  		colStmts = append(colStmts, GenerateCreateTableIndexDefinition(index))
   301  	}
   302  
   303  	for _, fk := range fks {
   304  		colStmts = append(colStmts, GenerateCreateTableForeignKeyDefinition(fk, sch, fksParentSch[fk.ReferencedTableName]))
   305  	}
   306  
   307  	for _, check := range sch.Checks().AllChecks() {
   308  		colStmts = append(colStmts, GenerateCreateTableCheckConstraintClause(check))
   309  	}
   310  
   311  	coll := sql.CollationID(sch.GetCollation())
   312  	createTableStmt := sql.GenerateCreateTableStatement(tblName, colStmts, "", coll.CharacterSet().Name(), coll.Name(), sch.GetComment())
   313  	return fmt.Sprintf("%s;", createTableStmt), nil
   314  }
   315  
   316  // isPrimaryKeyIndex returns whether the index given matches the table's primary key columns. Order is not considered.
   317  func isPrimaryKeyIndex(index schema.Index, sch schema.Schema) bool {
   318  	var pks = sch.GetPKCols().GetColumns()
   319  	var pkMap = make(map[string]struct{})
   320  	for _, c := range pks {
   321  		pkMap[c.Name] = struct{}{}
   322  	}
   323  
   324  	indexCols := index.ColumnNames()
   325  	if len(indexCols) != len(pks) {
   326  		return false
   327  	}
   328  
   329  	for _, c := range index.ColumnNames() {
   330  		if _, ok := pkMap[c]; !ok {
   331  			return false
   332  		}
   333  	}
   334  
   335  	return true
   336  }