github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/cmd/internal/ddl/table/table.go (about) 1 package table 2 3 import ( 4 "fmt" 5 "github.com/iancoleman/strcase" 6 "github.com/pkg/errors" 7 "github.com/unionj-cloud/go-doudou/cmd/internal/astutils" 8 "github.com/unionj-cloud/go-doudou/cmd/internal/ddl/columnenum" 9 "github.com/unionj-cloud/go-doudou/cmd/internal/ddl/extraenum" 10 "github.com/unionj-cloud/go-doudou/cmd/internal/ddl/keyenum" 11 "github.com/unionj-cloud/go-doudou/cmd/internal/ddl/nullenum" 12 "github.com/unionj-cloud/go-doudou/cmd/internal/ddl/sortenum" 13 "github.com/unionj-cloud/go-doudou/toolkit/stringutils" 14 "github.com/unionj-cloud/go-doudou/toolkit/templateutils" 15 "regexp" 16 "sort" 17 "strconv" 18 "strings" 19 ) 20 21 const ( 22 now = "CURRENT_TIMESTAMP" 23 ) 24 25 // IndexItems slice type alias for IndexItem 26 type IndexItems []IndexItem 27 28 // IndexItem define an index item 29 type IndexItem struct { 30 Unique bool 31 Name string 32 Column string 33 Order int 34 Sort sortenum.Sort 35 } 36 37 // Len return length of IndexItems 38 func (it IndexItems) Len() int { 39 return len(it) 40 } 41 42 // Less define asc or desc order 43 func (it IndexItems) Less(i, j int) bool { 44 return it[i].Order < it[j].Order 45 } 46 47 // Swap change position of elements at i and j 48 func (it IndexItems) Swap(i, j int) { 49 it[i], it[j] = it[j], it[i] 50 } 51 52 // Index define an index 53 type Index struct { 54 Table string 55 Unique bool 56 Name string 57 Items []IndexItem 58 } 59 60 const indexsqltmpl = `{{define "drop"}} 61 ALTER TABLE ` + "`" + `{{.Table}}` + "`" + ` DROP INDEX ` + "`" + `{{.Name}}` + "`" + `; 62 {{end}} 63 64 {{define "add"}} 65 ALTER TABLE ` + "`" + `{{.Table}}` + "`" + ` ADD {{if .Unique}}UNIQUE{{end}} INDEX ` + "`" + `{{.Name}}` + "`" + ` ({{range $j, $it := .Items}}{{if $j}},{{end}}` + "`" + `{{$it.Column}}` + "`" + ` {{$it.Sort}}{{end}}); 66 {{end}}` 67 68 func (idx *Index) DropIndexSql() (string, error) { 69 return templateutils.StringBlock("index.tmpl", indexsqltmpl, "drop", idx) 70 } 71 72 func (idx *Index) AddIndexSql() (string, error) { 73 return templateutils.StringBlock("index.tmpl", indexsqltmpl, "add", idx) 74 } 75 76 func NewIndexFromDbIndexes(dbIndexes []DbIndex) Index { 77 unique := !dbIndexes[0].NonUnique 78 idxName := dbIndexes[0].KeyName 79 items := make([]IndexItem, len(dbIndexes)) 80 for i, idx := range dbIndexes { 81 var sor sortenum.Sort 82 if idx.Collation == "B" { 83 sor = sortenum.Desc 84 } else { 85 sor = sortenum.Asc 86 } 87 items[i] = IndexItem{ 88 Column: idx.ColumnName, 89 Order: idx.SeqInIndex, 90 Sort: sor, 91 } 92 } 93 it := IndexItems(items) 94 sort.Stable(it) 95 return Index{ 96 Unique: unique, 97 Name: idxName, 98 Items: it, 99 } 100 } 101 102 func toColumnType(goType string) columnenum.ColumnType { 103 switch goType { 104 case "int", "int16", "int32": 105 return columnenum.IntType 106 case "int64": 107 return columnenum.BigintType 108 case "float32": 109 return columnenum.FloatType 110 case "float64": 111 return columnenum.DoubleType 112 case "string": 113 return columnenum.VarcharType 114 case "bool", "int8": 115 return columnenum.TinyintType 116 case "time.Time": 117 return columnenum.DatetimeType 118 case "decimal.Decimal": 119 return "decimal(6,2)" 120 case "types.JSONText": 121 return columnenum.JSONType 122 } 123 panic(fmt.Sprintf("no available type %s", goType)) 124 } 125 126 func toGoType(colType columnenum.ColumnType, nullable bool) string { 127 var goType string 128 if nullable { 129 goType += "*" 130 } 131 if stringutils.HasPrefixI(string(colType), strings.ToLower(string(columnenum.IntType))) { 132 goType += "int" 133 } else if stringutils.HasPrefixI(string(colType), strings.ToLower(string(columnenum.BigintType))) { 134 goType += "int64" 135 } else if stringutils.HasPrefixI(string(colType), strings.ToLower(string(columnenum.FloatType))) { 136 goType += "float32" 137 } else if stringutils.HasPrefixI(string(colType), strings.ToLower(string(columnenum.DoubleType))) { 138 goType += "float64" 139 } else if stringutils.HasPrefixI(string(colType), "varchar") { 140 goType += "string" 141 } else if stringutils.HasPrefixI(string(colType), strings.ToLower(string(columnenum.TextType))) { 142 goType += "string" 143 } else if stringutils.HasPrefixI(string(colType), strings.ToLower(string(columnenum.TinyintType))) { 144 goType += "int8" 145 } else if stringutils.HasPrefixI(string(colType), strings.ToLower(string(columnenum.DatetimeType))) { 146 goType += "time.Time" 147 } else if stringutils.HasPrefixI(string(colType), strings.ToLower(string(columnenum.MediumtextType))) { 148 goType += "string" 149 } else if stringutils.HasPrefixI(string(colType), strings.ToLower(string(columnenum.DecimalType))) { 150 goType += "decimal.Decimal" 151 } else if stringutils.HasPrefixI(string(colType), strings.ToLower(string(columnenum.LongtextType))) { 152 goType += "string" 153 } else if stringutils.HasPrefixI(string(colType), strings.ToLower(string(columnenum.JSONType))) { 154 goType += "types.JSONText" 155 } else { 156 panic(fmt.Sprintf("no available type %s", colType)) 157 } 158 return goType 159 } 160 161 // CheckPk check key is primary key or not 162 func CheckPk(key keyenum.Key) bool { 163 return key == keyenum.Pri 164 } 165 166 // CheckNull check a column is nullable or not 167 func CheckNull(null nullenum.Null) bool { 168 return null == nullenum.Yes 169 } 170 171 // CheckUnsigned check a column is unsigned or not 172 func CheckUnsigned(dbColType string) bool { 173 splits := strings.Split(dbColType, " ") 174 if len(splits) == 1 { 175 return false 176 } 177 return splits[1] == "unsigned" 178 } 179 180 // CheckAutoincrement check a column is auto increment or not 181 func CheckAutoincrement(extra string) bool { 182 return strings.Contains(extra, "auto_increment") 183 } 184 185 // CheckAutoSet check a column is auto generated by database or not 186 func CheckAutoSet(defaultVal string) bool { 187 return strings.ToLower(defaultVal) == strings.ToLower(now) 188 } 189 190 // Column define a column 191 type Column struct { 192 Table string 193 Name string 194 Type columnenum.ColumnType 195 Default string 196 Pk bool 197 Nullable bool 198 Unsigned bool 199 Autoincrement bool 200 Extra extraenum.Extra 201 Meta astutils.FieldMeta 202 AutoSet bool 203 Indexes []IndexItem 204 Fk ForeignKey 205 } 206 207 var altersqltmpl = `{{define "change"}} 208 ALTER TABLE ` + "`" + `{{.Table}}` + "`" + ` 209 CHANGE COLUMN ` + "`" + `{{.Name}}` + "`" + ` ` + "`" + `{{.Name}}` + "`" + ` {{.Type}} {{if .Nullable}}NULL{{else}}NOT NULL{{end}}{{if .Autoincrement}} AUTO_INCREMENT{{end}}{{if .Default}} DEFAULT {{.Default}}{{end}}{{if .Extra}} {{.Extra}}{{end}}; 210 {{end}} 211 212 {{define "add"}} 213 ALTER TABLE ` + "`" + `{{.Table}}` + "`" + ` 214 ADD COLUMN ` + "`" + `{{.Name}}` + "`" + ` {{.Type}} {{if .Nullable}}NULL{{else}}NOT NULL{{end}}{{if .Autoincrement}} AUTO_INCREMENT{{end}}{{if .Default}} DEFAULT {{.Default}}{{end}}{{if .Extra}} {{.Extra}}{{end}}; 215 {{end}} 216 ` 217 218 // ChangeColumnSql return change column sql 219 func (c *Column) ChangeColumnSql() (string, error) { 220 return templateutils.StringBlock("alter.tmpl", altersqltmpl, "change", c) 221 } 222 223 // AddColumnSql return add column sql 224 func (c *Column) AddColumnSql() (string, error) { 225 return templateutils.StringBlock("alter.tmpl", altersqltmpl, "add", c) 226 } 227 228 // DbColumn defines a column 229 type DbColumn struct { 230 Field string `db:"Field"` 231 Type string `db:"Type"` 232 Null nullenum.Null `db:"Null"` 233 Key keyenum.Key `db:"Key"` 234 Default *string `db:"Default"` 235 Extra string `db:"Extra"` 236 Comment string `db:"Comment"` 237 } 238 239 // DbIndex defines an index refer to https://www.mysqltutorial.org/mysql-index/mysql-show-indexes/ 240 type DbIndex struct { 241 Table string `db:"Table"` // The name of the table 242 NonUnique bool `db:"Non_unique"` // 1 if the index can contain duplicates, 0 if it cannot. 243 KeyName string `db:"Key_name"` // The name of the index. The primary key index always has the name of PRIMARY. 244 SeqInIndex int `db:"Seq_in_index"` // The column sequence number in the index. The first column sequence number starts from 1. 245 ColumnName string `db:"Column_name"` // The column name 246 Collation string `db:"Collation"` // Collation represents how the column is sorted in the index. A means ascending, B means descending, or NULL means not sorted. 247 } 248 249 // DbForeignKey from INFORMATION_SCHEMA.KEY_COLUMN_USAGE 250 type DbForeignKey struct { 251 TableName string `db:"TABLE_NAME"` 252 ColumnName string `db:"COLUMN_NAME"` 253 ConstraintName string `db:"CONSTRAINT_NAME"` 254 ReferencedTableName string `db:"REFERENCED_TABLE_NAME"` 255 ReferencedColumnName string `db:"REFERENCED_COLUMN_NAME"` 256 } 257 258 // DbAction from information_schema.REFERENTIAL_CONSTRAINTS 259 type DbAction struct { 260 TableName string `db:"TABLE_NAME"` 261 ConstraintName string `db:"CONSTRAINT_NAME"` 262 ReferencedTableName string `db:"REFERENCED_TABLE_NAME"` 263 UpdateRule string `db:"UPDATE_RULE"` 264 DeleteRule string `db:"DELETE_RULE"` 265 } 266 267 type ForeignKey struct { 268 // Table the child table 269 Table string 270 // Constraint name of foreign key constraint 271 Constraint string 272 // Fk foreign key 273 Fk string 274 // ReferencedTable the referenced table 275 ReferencedTable string 276 // ReferencedCol the referenced column of ReferencedTable 277 ReferencedCol string 278 UpdateRule string 279 DeleteRule string 280 FullRule string 281 } 282 283 const fksqltmpl = `{{define "drop"}} 284 ALTER TABLE ` + "`" + `{{.Table}}` + "`" + ` DROP FOREIGN KEY {{.Constraint}}; 285 {{end}} 286 287 {{define "add"}} 288 ALTER TABLE ` + "`" + `{{.Table}}` + "`" + ` ADD CONSTRAINT {{.Constraint}} FOREIGN KEY ({{.Fk}}) REFERENCES {{.ReferencedTable}}({{.ReferencedCol}}) {{.FullRule}}; 289 {{end}}` 290 291 func (fk *ForeignKey) DropFkSql() (string, error) { 292 return templateutils.StringBlock("fk.tmpl", fksqltmpl, "drop", fk) 293 } 294 295 func (fk *ForeignKey) AddFkSql() (string, error) { 296 return templateutils.StringBlock("fk.tmpl", fksqltmpl, "add", fk) 297 } 298 299 // Table defines a table 300 type Table struct { 301 Name string 302 Columns []Column 303 Pk string 304 Indexes []Index 305 Meta astutils.StructMeta 306 Fks []ForeignKey 307 } 308 309 // NewTableFromStruct creates a Table instance from structMeta 310 func NewTableFromStruct(structMeta astutils.StructMeta, prefix ...string) Table { 311 var ( 312 columns []Column 313 uniqueindexes []Index 314 indexes []Index 315 fks []ForeignKey 316 pkColumn Column 317 table string 318 ) 319 table = strcase.ToSnake(structMeta.Name) 320 if len(prefix) > 0 { 321 table = prefix[0] + table 322 } 323 for _, field := range structMeta.Fields { 324 var ( 325 columnName string 326 _uniqueindexes []Index 327 _indexes []Index 328 _fks []ForeignKey 329 column Column 330 ) 331 column.Table = table 332 column.Meta = field 333 columnName = strcase.ToSnake(field.Name) 334 column.Name = columnName 335 if stringutils.IsNotEmpty(field.Tag) { 336 tags := strings.Split(field.Tag, `" `) 337 var ddTag string 338 for _, tag := range tags { 339 if strings.HasPrefix(tag, "dd:") { 340 ddTag = strings.Trim(strings.TrimPrefix(tag, "dd:"), `"`) 341 break 342 } 343 } 344 if stringutils.IsNotEmpty(ddTag) { 345 _indexes, _uniqueindexes, _fks = parseDdTag(ddTag, field, &column) 346 } 347 } 348 349 if strings.HasPrefix(field.Type, "*") { 350 column.Nullable = true 351 } 352 353 if stringutils.IsEmpty(string(column.Type)) { 354 column.Type = toColumnType(strings.TrimPrefix(field.Type, "*")) 355 } 356 357 for _, idx := range _indexes { 358 if stringutils.IsNotEmpty(idx.Name) { 359 idx.Items[0].Column = columnName 360 indexes = append(indexes, idx) 361 } 362 } 363 364 for _, uidx := range _uniqueindexes { 365 if stringutils.IsNotEmpty(uidx.Name) { 366 uidx.Items[0].Column = columnName 367 uniqueindexes = append(uniqueindexes, uidx) 368 } 369 } 370 371 for _, fk := range _fks { 372 if stringutils.IsNotEmpty(fk.Fk) { 373 fks = append(fks, fk) 374 } 375 } 376 377 columns = append(columns, column) 378 } 379 380 for _, column := range columns { 381 if column.Pk { 382 pkColumn = column 383 break 384 } 385 } 386 387 indexesResult := mergeIndexes(indexes, uniqueindexes) 388 389 return Table{ 390 Name: table, 391 Columns: columns, 392 Pk: pkColumn.Name, 393 Indexes: indexesResult, 394 Meta: structMeta, 395 Fks: fks, 396 } 397 } 398 399 type sortableIndexes []Index 400 401 // Len return length of sortableIndexes 402 func (it sortableIndexes) Len() int { 403 return len(it) 404 } 405 406 // Less define asc or desc order 407 func (it sortableIndexes) Less(i, j int) bool { 408 return it[i].Name < it[j].Name 409 } 410 411 // Swap change position of elements at i and j 412 func (it sortableIndexes) Swap(i, j int) { 413 it[i], it[j] = it[j], it[i] 414 } 415 416 func mergeIndexes(indexes, uniqueindexes []Index) []Index { 417 uniqueMap := make(map[string][]IndexItem) 418 indexMap := make(map[string][]IndexItem) 419 420 for _, unique := range uniqueindexes { 421 if items, exists := uniqueMap[unique.Name]; exists { 422 items = append(items, unique.Items...) 423 uniqueMap[unique.Name] = items 424 } else { 425 uniqueMap[unique.Name] = unique.Items 426 } 427 } 428 429 for _, index := range indexes { 430 if items, exists := indexMap[index.Name]; exists { 431 items = append(items, index.Items...) 432 indexMap[index.Name] = items 433 } else { 434 indexMap[index.Name] = index.Items 435 } 436 } 437 438 var uniquesResult, indexesResult []Index 439 440 for k, v := range uniqueMap { 441 it := IndexItems(v) 442 sort.Stable(it) 443 uniquesResult = append(uniquesResult, Index{ 444 Unique: true, 445 Name: k, 446 Items: it, 447 }) 448 } 449 450 for k, v := range indexMap { 451 it := IndexItems(v) 452 sort.Stable(it) 453 indexesResult = append(indexesResult, Index{ 454 Name: k, 455 Items: it, 456 }) 457 } 458 459 sort.Stable(sortableIndexes(indexesResult)) 460 sort.Stable(sortableIndexes(uniquesResult)) 461 462 indexesResult = append(indexesResult, uniquesResult...) 463 return indexesResult 464 } 465 466 func parseDdTag(ddTag string, field astutils.FieldMeta, column *Column) (indexes []Index, uniqueIndexes []Index, fks []ForeignKey) { 467 kvs := strings.Split(ddTag, ";") 468 for _, kv := range kvs { 469 pair := strings.Split(kv, ":") 470 if len(pair) > 1 { 471 parsePair(pair, column, &indexes, &uniqueIndexes, &fks) 472 } else { 473 parseSingle(pair, column, field, &indexes, &uniqueIndexes) 474 } 475 } 476 return 477 } 478 479 func parseSingle(pair []string, column *Column, field astutils.FieldMeta, indexes *[]Index, uniqueIndexes *[]Index) { 480 key := pair[0] 481 switch key { 482 case "pk": 483 column.Pk = true 484 break 485 case "null": 486 column.Nullable = true 487 break 488 case "unsigned": 489 column.Unsigned = true 490 break 491 case "auto": 492 column.Autoincrement = true 493 break 494 case "index": 495 *indexes = append(*indexes, Index{ 496 Name: strcase.ToSnake(field.Name) + "_idx", 497 Items: []IndexItem{ 498 { 499 Order: 1, 500 Sort: sortenum.Asc, 501 }, 502 }, 503 }) 504 break 505 case "unique": 506 *uniqueIndexes = append(*uniqueIndexes, Index{ 507 Name: strcase.ToSnake(field.Name) + "_idx", 508 Items: []IndexItem{ 509 { 510 Order: 1, 511 Sort: sortenum.Asc, 512 }, 513 }, 514 }) 515 break 516 } 517 } 518 519 func parsePair(pair []string, column *Column, indexes *[]Index, uniqueIndexes *[]Index, fks *[]ForeignKey) { 520 key := pair[0] 521 value := pair[1] 522 if stringutils.IsEmpty(value) { 523 panic(fmt.Sprintf("%+v", errors.New("value should not be empty"))) 524 } 525 switch key { 526 case "type": 527 column.Type = columnenum.ColumnType(value) 528 break 529 case "default": 530 column.Default = value 531 column.AutoSet = CheckAutoSet(value) 532 break 533 case "extra": 534 column.Extra = extraenum.Extra(value) 535 break 536 case "index": 537 props := strings.Split(value, ",") 538 indexName := props[0] 539 order := props[1] 540 orderInt, err := strconv.Atoi(order) 541 if err != nil { 542 panic(err) 543 } 544 var sor sortenum.Sort 545 if len(props) < 3 || stringutils.IsEmpty(props[2]) { 546 sor = sortenum.Asc 547 } else { 548 sor = sortenum.Sort(props[2]) 549 } 550 *indexes = append(*indexes, Index{ 551 Name: indexName, 552 Items: []IndexItem{ 553 { 554 Order: orderInt, 555 Sort: sor, 556 }, 557 }, 558 }) 559 break 560 case "unique": 561 props := strings.Split(value, ",") 562 indexName := props[0] 563 order := props[1] 564 orderInt, err := strconv.Atoi(order) 565 if err != nil { 566 panic(err) 567 } 568 var sort sortenum.Sort 569 if len(props) < 3 || stringutils.IsEmpty(props[2]) { 570 sort = sortenum.Asc 571 } else { 572 sort = sortenum.Sort(props[2]) 573 } 574 *uniqueIndexes = append(*uniqueIndexes, Index{ 575 Name: indexName, 576 Items: []IndexItem{ 577 { 578 Order: orderInt, 579 Sort: sort, 580 }, 581 }, 582 }) 583 break 584 case "fk": 585 props := strings.Split(value, ",") 586 refTable := props[0] 587 var refCol string 588 if len(props) > 1 { 589 refCol = props[1] 590 } else { 591 refCol = "id" 592 } 593 var constraint string 594 if len(props) > 2 { 595 constraint = props[2] 596 } else { 597 constraint = fmt.Sprintf("fk_%s_%s_%s", column.Name, refTable, refCol) 598 } 599 var fullRule string 600 if len(props) > 3 { 601 fullRule = props[3] 602 } 603 *fks = append(*fks, ForeignKey{ 604 Table: column.Table, 605 Constraint: constraint, 606 Fk: column.Name, 607 ReferencedTable: refTable, 608 ReferencedCol: refCol, 609 FullRule: fullRule, 610 }) 611 break 612 } 613 } 614 615 // NewFieldFromColumn creates an astutils.FieldMeta instance from col 616 func NewFieldFromColumn(col Column) astutils.FieldMeta { 617 tag := "dd:" 618 var feats []string 619 if col.Pk { 620 feats = append(feats, "pk") 621 } 622 if col.Autoincrement { 623 feats = append(feats, "auto") 624 } 625 goType := toGoType(col.Type, col.Nullable) 626 if col.Nullable && !strings.HasPrefix(goType, "*") { 627 feats = append(feats, "null") 628 } 629 if stringutils.IsNotEmpty(string(col.Type)) { 630 feats = append(feats, fmt.Sprintf("type:%s", string(col.Type))) 631 } 632 if stringutils.IsNotEmpty(col.Default) { 633 val := col.Default 634 re := regexp.MustCompile(`^\(.+\)$`) 635 var defaultClause string 636 if strings.ToUpper(val) == "CURRENT_TIMESTAMP" || re.MatchString(val) { 637 defaultClause = fmt.Sprintf("default:%s", val) 638 } else { 639 defaultClause = fmt.Sprintf("default:'%s'", val) 640 } 641 feats = append(feats, defaultClause) 642 } 643 if stringutils.IsNotEmpty(string(col.Extra)) { 644 feats = append(feats, fmt.Sprintf("extra:%s", string(col.Extra))) 645 } 646 for _, idx := range col.Indexes { 647 var indexClause string 648 if idx.Name == "PRIMARY" { 649 continue 650 } 651 if idx.Unique { 652 indexClause = "unique:" 653 } else { 654 indexClause = "index:" 655 } 656 indexClause += fmt.Sprintf("%s,%d,%s", idx.Name, idx.Order, string(idx.Sort)) 657 feats = append(feats, indexClause) 658 } 659 if stringutils.IsNotEmpty(col.Fk.Constraint) { 660 feats = append(feats, fmt.Sprintf("fk:%s,%s,%s,%s", col.Fk.ReferencedTable, col.Fk.ReferencedCol, col.Fk.Constraint, col.Fk.FullRule)) 661 } 662 return astutils.FieldMeta{ 663 Name: strcase.ToCamel(col.Name), 664 Type: goType, 665 Tag: fmt.Sprintf(`%s"%s"`, tag, strings.Join(feats, ";")), 666 } 667 } 668 669 var createsqltmpl = `CREATE TABLE ` + "`" + `{{.Name}}` + "`" + ` ( 670 {{- range $co := .Columns }} 671 ` + "`" + `{{$co.Name}}` + "`" + ` {{$co.Type}} {{if $co.Nullable}}NULL{{else}}NOT NULL{{end}}{{if $co.Autoincrement}} AUTO_INCREMENT{{end}}{{if $co.Default}} DEFAULT {{$co.Default}}{{end}}{{if $co.Extra}} {{$co.Extra}}{{end}}, 672 {{- end }} 673 PRIMARY KEY (` + "`" + `{{.Pk}}` + "`" + `){{if .Indexes}},{{end}} 674 {{- range $i, $ind := .Indexes}} 675 {{- if $i}},{{end}} 676 {{if $ind.Unique}}UNIQUE {{end}}INDEX ` + "`" + `{{$ind.Name}}` + "`" + ` ({{ range $j, $it := $ind.Items }}{{if $j}},{{end}}` + "`" + `{{$it.Column}}` + "`" + ` {{$it.Sort}}{{ end }}) 677 {{- end }}{{if .Fks}},{{end}} 678 {{- range $i, $fk := .Fks}} 679 {{- if $i}},{{end}} 680 CONSTRAINT ` + "`" + `{{$fk.Constraint}}` + "`" + ` FOREIGN KEY (` + "`" + `{{$fk.Fk}}` + "`" + `) 681 REFERENCES ` + "`" + `{{$fk.ReferencedTable}}` + "`" + `(` + "`" + `{{$fk.ReferencedCol}}` + "`" + `) 682 {{- if $fk.FullRule}} 683 {{$fk.FullRule}} 684 {{- end }} 685 {{- end }})` 686 687 // CreateSql return create table sql 688 func (t *Table) CreateSql() (string, error) { 689 return templateutils.String("create.sql.tmpl", createsqltmpl, t) 690 }