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 }