diff --git a/tools/dbutils/index.go b/tools/dbutils/index.go index 9f82fc7d..fd03e1f5 100644 --- a/tools/dbutils/index.go +++ b/tools/dbutils/index.go @@ -35,7 +35,95 @@ func (idx Index) IsValid() bool { return idx.IndexName != "" && idx.TableName != "" && len(idx.Columns) > 0 } -// ParseIndex parses the provided `CREATE INDEX` SQL string into Index struct. +// Build returns a "CREATE INDEX" SQL string from the current index parts. +// +// Returns empty string if idx.IsValid() is false. +func (idx Index) Build() string { + if !idx.IsValid() { + return "" + } + + var str strings.Builder + + str.WriteString("CREATE ") + + if idx.Unique { + str.WriteString("UNIQUE ") + } + + str.WriteString("INDEX ") + + if idx.Optional { + str.WriteString("IF NOT EXISTS ") + } + + if idx.SchemaName != "" { + str.WriteString("`") + str.WriteString(idx.SchemaName) + str.WriteString("`.") + } + + str.WriteString("`") + str.WriteString(idx.IndexName) + str.WriteString("` ") + + str.WriteString("ON `") + str.WriteString(idx.TableName) + str.WriteString("` (") + + if len(idx.Columns) > 1 { + str.WriteString("\n ") + } + + var hasCol bool + for _, col := range idx.Columns { + trimmedColName := strings.TrimSpace(col.Name) + if trimmedColName == "" { + continue + } + + if hasCol { + str.WriteString(",\n ") + } + + if strings.Contains(col.Name, "(") || strings.Contains(col.Name, " ") { + // most likely an expression + str.WriteString(trimmedColName) + } else { + // regular identifier + str.WriteString("`") + str.WriteString(trimmedColName) + str.WriteString("`") + } + + if col.Collate != "" { + str.WriteString(" COLLATE ") + str.WriteString(col.Collate) + } + + if col.Sort != "" { + str.WriteString(" ") + str.WriteString(strings.ToUpper(col.Sort)) + } + + hasCol = true + } + + if hasCol && len(idx.Columns) > 1 { + str.WriteString("\n") + } + + str.WriteString(")") + + if idx.Where != "" { + str.WriteString(" WHERE ") + str.WriteString(idx.Where) + } + + return str.String() +} + +// ParseIndex parses the provided "CREATE INDEX" SQL string into Index struct. func ParseIndex(createIndexExpr string) Index { result := Index{} diff --git a/tools/dbutils/index_test.go b/tools/dbutils/index_test.go index 22320baa..e9bae78d 100644 --- a/tools/dbutils/index_test.go +++ b/tools/dbutils/index_test.go @@ -142,3 +142,75 @@ func TestIndexIsValid(t *testing.T) { } } } + +func TestIndexBuild(t *testing.T) { + scenarios := []struct { + name string + index dbutils.Index + expected string + }{ + { + "empty", + dbutils.Index{}, + "", + }, + { + "no index name", + dbutils.Index{ + TableName: "table", + Columns: []dbutils.IndexColumn{{Name: "col"}}, + }, + "", + }, + { + "no table name", + dbutils.Index{ + IndexName: "index", + Columns: []dbutils.IndexColumn{{Name: "col"}}, + }, + "", + }, + { + "no columns", + dbutils.Index{ + IndexName: "index", + TableName: "table", + }, + "", + }, + { + "min valid", + dbutils.Index{ + IndexName: "index", + TableName: "table", + Columns: []dbutils.IndexColumn{{Name: "col"}}, + }, + "CREATE INDEX `index` ON `table` (`col`)", + }, + { + "all fields", + dbutils.Index{ + Optional: true, + Unique: true, + SchemaName: "schema", + IndexName: "index", + TableName: "table", + Columns: []dbutils.IndexColumn{ + {Name: "col1", Collate: "NOCASE", Sort: "asc"}, + {Name: "col2", Sort: "desc"}, + {Name: `json_extract("col3", "$.a")`, Collate: "NOCASE"}, + }, + Where: "test = 1 OR test = 2", + }, + "CREATE UNIQUE INDEX IF NOT EXISTS `schema`.`index` ON `table` (\n `col1` COLLATE NOCASE ASC,\n `col2` DESC,\n " + `json_extract("col3", "$.a")` + " COLLATE NOCASE\n) WHERE test = 1 OR test = 2", + }, + } + + for _, s := range scenarios { + result := s.index.Build() + + if result != s.expected { + t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, result) + } + } +}