main.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "os"
  6. "sort"
  7. "strings"
  8. "github.com/cockroachdb/errors"
  9. "github.com/glebarez/sqlite"
  10. "github.com/olekukonko/tablewriter"
  11. "github.com/olekukonko/tablewriter/renderer"
  12. "github.com/olekukonko/tablewriter/tw"
  13. "gopkg.in/DATA-DOG/go-sqlmock.v2"
  14. "gorm.io/driver/mysql"
  15. "gorm.io/driver/postgres"
  16. "gorm.io/gorm"
  17. "gorm.io/gorm/clause"
  18. "gorm.io/gorm/schema"
  19. "gogs.io/gogs/internal/database"
  20. )
  21. //go:generate go run main.go ../../../docs-old/dev/database_schema.md
  22. func main() {
  23. w, err := os.Create(os.Args[1])
  24. if err != nil {
  25. log.Fatalf("Failed to create file: %v", err)
  26. }
  27. defer func() { _ = w.Close() }()
  28. conn, _, err := sqlmock.New()
  29. if err != nil {
  30. log.Fatalf("Failed to get mock connection: %v", err)
  31. }
  32. defer func() { _ = conn.Close() }()
  33. dialectors := []gorm.Dialector{
  34. postgres.New(postgres.Config{
  35. Conn: conn,
  36. }),
  37. mysql.New(mysql.Config{
  38. Conn: conn,
  39. SkipInitializeWithVersion: true,
  40. }),
  41. sqlite.Open(""),
  42. }
  43. collected := make([][]*tableInfo, 0, len(dialectors))
  44. for i, dialector := range dialectors {
  45. tableInfos, err := generate(dialector)
  46. if err != nil {
  47. log.Fatalf("Failed to get table info of %d: %v", i, err)
  48. }
  49. collected = append(collected, tableInfos)
  50. }
  51. for i, ti := range collected[0] {
  52. _, _ = w.WriteString(`# Table "` + ti.Name + `"`)
  53. _, _ = w.WriteString("\n\n")
  54. _, _ = w.WriteString("```\n")
  55. table := tablewriter.NewTable(w,
  56. tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
  57. Borders: tw.BorderNone,
  58. Symbols: tw.NewSymbols(tw.StyleASCII),
  59. })),
  60. tablewriter.WithHeaderAutoFormat(tw.Off),
  61. )
  62. table.Header("Field", "Column", "PostgreSQL", "MySQL", "SQLite3")
  63. for j, f := range ti.Fields {
  64. sqlite3Type := strings.ToUpper(collected[2][i].Fields[j].Type)
  65. sqlite3Type = strings.ReplaceAll(sqlite3Type, "PRIMARY KEY ", "")
  66. _ = table.Append([]string{
  67. f.Name, f.Column,
  68. strings.ToUpper(f.Type), // PostgreSQL
  69. strings.ToUpper(collected[1][i].Fields[j].Type), // MySQL
  70. sqlite3Type,
  71. })
  72. }
  73. _ = table.Render()
  74. _, _ = w.WriteString("\n")
  75. _, _ = w.WriteString("Primary keys: ")
  76. _, _ = w.WriteString(strings.Join(ti.PrimaryKeys, ", "))
  77. _, _ = w.WriteString("\n")
  78. if len(ti.Indexes) > 0 {
  79. _, _ = w.WriteString("Indexes: \n")
  80. for _, index := range ti.Indexes {
  81. _, _ = fmt.Fprintf(w, "\t%q", index.Name)
  82. if index.Class != "" {
  83. _, _ = fmt.Fprintf(w, " %s", index.Class)
  84. }
  85. if index.Type != "" {
  86. _, _ = fmt.Fprintf(w, ", %s", index.Type)
  87. }
  88. if len(index.Fields) > 0 {
  89. fields := make([]string, len(index.Fields))
  90. for i := range index.Fields {
  91. fields[i] = index.Fields[i].DBName
  92. }
  93. _, _ = fmt.Fprintf(w, " (%s)", strings.Join(fields, ", "))
  94. }
  95. _, _ = w.WriteString("\n")
  96. }
  97. }
  98. _, _ = w.WriteString("```\n\n")
  99. }
  100. }
  101. type tableField struct {
  102. Name string
  103. Column string
  104. Type string
  105. }
  106. type tableInfo struct {
  107. Name string
  108. Fields []*tableField
  109. PrimaryKeys []string
  110. Indexes []schema.Index
  111. }
  112. // This function is derived from gorm.io/gorm/migrator/migrator.go:Migrator.CreateTable.
  113. func generate(dialector gorm.Dialector) ([]*tableInfo, error) {
  114. conn, err := gorm.Open(dialector,
  115. &gorm.Config{
  116. SkipDefaultTransaction: true,
  117. NamingStrategy: schema.NamingStrategy{
  118. SingularTable: true,
  119. },
  120. DryRun: true,
  121. DisableAutomaticPing: true,
  122. },
  123. )
  124. if err != nil {
  125. return nil, errors.Wrap(err, "open database")
  126. }
  127. m := conn.Migrator().(interface {
  128. RunWithValue(value any, fc func(*gorm.Statement) error) error
  129. FullDataTypeOf(*schema.Field) clause.Expr
  130. })
  131. tableInfos := make([]*tableInfo, 0, len(database.Tables))
  132. for _, table := range database.Tables {
  133. err = m.RunWithValue(table, func(stmt *gorm.Statement) error {
  134. fields := make([]*tableField, 0, len(stmt.Schema.DBNames))
  135. for _, field := range stmt.Schema.Fields {
  136. if field.DBName == "" {
  137. continue
  138. }
  139. tags := make([]string, 0)
  140. for tag := range field.TagSettings {
  141. if tag == "UNIQUE" {
  142. tags = append(tags, tag)
  143. }
  144. }
  145. typeSuffix := ""
  146. if len(tags) > 0 {
  147. typeSuffix = " " + strings.Join(tags, " ")
  148. }
  149. fields = append(fields, &tableField{
  150. Name: field.Name,
  151. Column: field.DBName,
  152. Type: m.FullDataTypeOf(field).SQL + typeSuffix,
  153. })
  154. }
  155. primaryKeys := make([]string, 0, len(stmt.Schema.PrimaryFields))
  156. if len(stmt.Schema.PrimaryFields) > 0 {
  157. for _, field := range stmt.Schema.PrimaryFields {
  158. primaryKeys = append(primaryKeys, field.DBName)
  159. }
  160. }
  161. var indexes []schema.Index
  162. for _, index := range stmt.Schema.ParseIndexes() {
  163. indexes = append(indexes, index)
  164. }
  165. sort.Slice(indexes, func(i, j int) bool {
  166. return indexes[i].Name < indexes[j].Name
  167. })
  168. tableInfos = append(tableInfos, &tableInfo{
  169. Name: stmt.Table,
  170. Fields: fields,
  171. PrimaryKeys: primaryKeys,
  172. Indexes: indexes,
  173. })
  174. return nil
  175. })
  176. if err != nil {
  177. return nil, errors.Wrap(err, "gather table information")
  178. }
  179. }
  180. return tableInfos, nil
  181. }