This is an automated email from the ASF dual-hosted git repository.

lynwee pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new 529a71bee perf(tapd): optimize migration script 20230411 by caching 
column metadata (#8677)
529a71bee is described below

commit 529a71bee8cf584bd7c35ef3fe23373500c92c3c
Author: NaRro <[email protected]>
AuthorDate: Thu Jan 8 04:49:45 2026 +0000

    perf(tapd): optimize migration script 20230411 by caching column metadata 
(#8677)
    
    Reduce PostgreSQL migration time from 22s to 4s by:
    
    1. Cache column metadata per table instead of querying information_schema
       for each column check (270 queries → 3 queries)
    2. Batch PostgreSQL cache clearing (135 SELECTs → 3 SELECTs)
    3. Handle PostgreSQL and MySQL syntax differences appropriately
    
    The key optimization is fetching all column names once via GetColumns()
    and using a map for O(1) lookups instead of calling HasColumn() twice
    per column rename operation.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    Co-authored-by: Claude <[email protected]>
---
 .../20230411_modify_custom_field_name.go           | 139 +++++++++++++++------
 1 file changed, 102 insertions(+), 37 deletions(-)

diff --git 
a/backend/plugins/tapd/models/migrationscripts/20230411_modify_custom_field_name.go
 
b/backend/plugins/tapd/models/migrationscripts/20230411_modify_custom_field_name.go
index fe3703710..2120138e3 100644
--- 
a/backend/plugins/tapd/models/migrationscripts/20230411_modify_custom_field_name.go
+++ 
b/backend/plugins/tapd/models/migrationscripts/20230411_modify_custom_field_name.go
@@ -19,47 +19,59 @@ package migrationscripts
 
 import (
        "fmt"
+       "strings"
+
        "github.com/apache/incubator-devlake/core/context"
        "github.com/apache/incubator-devlake/core/dal"
        "github.com/apache/incubator-devlake/core/errors"
+       "gorm.io/gorm/clause"
 )
 
 type modifyCustomFieldName struct{}
 
+type columnRename struct {
+       Old string
+       New string
+}
+
 func (*modifyCustomFieldName) Up(basicRes context.BasicRes) errors.Error {
        db := basicRes.GetDal()
-       issuesNameList := []string{"_tool_tapd_stories", "_tool_tapd_bugs", 
"_tool_tapd_tasks"}
-       for _, issuesName := range issuesNameList {
-               switch issuesName {
-               case "_tool_tapd_bugs":
-                       for i := 6; i < 9; i++ {
-                               oldColumnName := fmt.Sprintf("custom_field%d", 
i)
-                               newColumnName := fmt.Sprintf("custom_field_%d", 
i)
-                               if err := renameColumnSafely(db, issuesName, 
oldColumnName, newColumnName, dal.Text); err != nil {
-                                       return err
-                               }
-                       }
-               case "_tool_tapd_tasks", "_tool_tapd_stories":
-                       tableName := issuesName
-                       renameColumnMap := map[string]string{
-                               "custom_field6": "custom_field_six",
-                               "custom_field7": "custom_field_seven",
-                               "custom_field8": "custom_field_eight",
-                       }
-                       for oldColumn, newColumn := range renameColumnMap {
-                               if err := renameColumnSafely(db, tableName, 
oldColumn, newColumn, dal.Text); err != nil {
-                                       return err
-                               }
-                       }
+
+       // Define all column renames for each table
+       tableRenames := map[string][]columnRename{
+               "_tool_tapd_bugs": {
+                       {Old: "custom_field6", New: "custom_field_6"},
+                       {Old: "custom_field7", New: "custom_field_7"},
+                       {Old: "custom_field8", New: "custom_field_8"},
+               },
+               "_tool_tapd_stories": {
+                       {Old: "custom_field6", New: "custom_field_six"},
+                       {Old: "custom_field7", New: "custom_field_seven"},
+                       {Old: "custom_field8", New: "custom_field_eight"},
+               },
+               "_tool_tapd_tasks": {
+                       {Old: "custom_field6", New: "custom_field_six"},
+                       {Old: "custom_field7", New: "custom_field_seven"},
+                       {Old: "custom_field8", New: "custom_field_eight"},
+               },
+       }
+
+       // Add custom_field_9 to custom_field_50 for all tables
+       for i := 9; i <= 50; i++ {
+               oldCol := fmt.Sprintf("custom_field%d", i)
+               newCol := fmt.Sprintf("custom_field_%d", i)
+               for _, table := range []string{"_tool_tapd_bugs", 
"_tool_tapd_stories", "_tool_tapd_tasks"} {
+                       tableRenames[table] = append(tableRenames[table], 
columnRename{Old: oldCol, New: newCol})
                }
-               for i := 9; i <= 50; i++ {
-                       oldColumnName := fmt.Sprintf("custom_field%d", i)
-                       newColumnName := fmt.Sprintf("custom_field_%d", i)
-                       if err := renameColumnSafely(db, issuesName, 
oldColumnName, newColumnName, dal.Text); err != nil {
-                               return err
-                       }
+       }
+
+       // Execute batch rename for each table
+       for tableName, renames := range tableRenames {
+               if err := batchRenameColumns(db, tableName, renames); err != 
nil {
+                       return err
                }
        }
+
        return nil
 }
 
@@ -71,18 +83,71 @@ func (*modifyCustomFieldName) Name() string {
        return "modify tapd custom field name"
 }
 
-func renameColumnSafely(db dal.Dal, table, oldColumn string, newColumn string, 
newColumnType dal.ColumnType) errors.Error {
-       if table == "" || oldColumn == "" || newColumn == "" {
-               return errors.BadInput.New("empty params")
+// batchRenameColumns renames multiple columns in a single ALTER TABLE 
statement
+func batchRenameColumns(db dal.Dal, table string, renames []columnRename) 
errors.Error {
+       if len(renames) == 0 {
+               return nil
        }
-       if db.HasColumn(table, oldColumn) {
-               if !db.HasColumn(table, newColumn) {
-                       return db.RenameColumn(table, oldColumn, newColumn)
+
+       // Get all existing column names once to avoid repeated 
information_schema queries
+       existingColumns := getExistingColumns(db, table)
+
+       // Filter out renames where old column doesn't exist or new column 
already exists
+       validRenames := filterValidRenamesCached(renames, existingColumns)
+       if len(validRenames) == 0 {
+               return nil
+       }
+
+       var sql string
+       var dialect = db.Dialect()
+
+       if dialect == "postgres" {
+               // PostgreSQL requires separate ALTER TABLE statements for each 
RENAME COLUMN
+               for _, rename := range validRenames {
+                       sql = fmt.Sprintf(`ALTER TABLE "%s" RENAME COLUMN "%s" 
TO "%s"`, table, rename.Old, rename.New)
+                       if err := db.Exec(sql); err != nil {
+                               return err
+                       }
                }
+               // Clear PostgreSQL cached plan after all renames
+               _ = db.Exec("SELECT * FROM ? LIMIT 1", clause.Table{Name: 
table})
        } else {
-               if !db.HasColumn(table, newColumn) {
-                       return db.AddColumn(table, newColumn, newColumnType)
+               // MySQL: ALTER TABLE t CHANGE COLUMN a new_name TEXT, CHANGE 
COLUMN c new_name2 TEXT
+               clauses := make([]string, 0, len(validRenames))
+               for _, rename := range validRenames {
+                       clauses = append(clauses, fmt.Sprintf("CHANGE COLUMN 
`%s` `%s` %s", rename.Old, rename.New, dal.Text.String()))
+               }
+               sql = fmt.Sprintf("ALTER TABLE `%s` %s", table, 
strings.Join(clauses, ", "))
+               if err := db.Exec(sql); err != nil {
+                       return err
                }
        }
+
        return nil
 }
+
+// getExistingColumns fetches all column names for a table in a single query
+func getExistingColumns(db dal.Dal, table string) map[string]bool {
+       columns := make(map[string]bool)
+       columnMetas, err := db.GetColumns(&dal.DefaultTabler{Name: table}, nil)
+       if err != nil {
+               return columns
+       }
+       for _, col := range columnMetas {
+               columns[col.Name()] = true
+       }
+       return columns
+}
+
+// filterValidRenamesCached checks which renames are needed using pre-fetched 
column map
+func filterValidRenamesCached(renames []columnRename, existingColumns 
map[string]bool) []columnRename {
+       valid := make([]columnRename, 0, len(renames))
+       for _, rename := range renames {
+               oldExists := existingColumns[rename.Old]
+               newExists := existingColumns[rename.New]
+               if oldExists && !newExists {
+                       valid = append(valid, rename)
+               }
+       }
+       return valid
+}

Reply via email to