feat(pgsql): add support for concurrent index creation
* Implemented `Concurrent` field in index model * Updated index creation template to support `CREATE INDEX CONCURRENTLY` * Added tests for concurrent index creation in migration writer
This commit is contained in:
@@ -169,6 +169,7 @@ When `include_audit` is enabled, adds:
|
|||||||
- Constraint actions (CASCADE, RESTRICT, SET NULL)
|
- Constraint actions (CASCADE, RESTRICT, SET NULL)
|
||||||
- Partial indexes
|
- Partial indexes
|
||||||
- Function-based indexes
|
- Function-based indexes
|
||||||
|
- Concurrent index creation (`CREATE INDEX CONCURRENTLY`) via `Index.Concurrent`
|
||||||
- Check constraints with expressions
|
- Check constraints with expressions
|
||||||
|
|
||||||
## Data Types
|
## Data Types
|
||||||
|
|||||||
@@ -652,6 +652,7 @@ func (w *MigrationWriter) generateIndexScripts(model *models.Schema, current *mo
|
|||||||
IndexType: indexType,
|
IndexType: indexType,
|
||||||
Columns: strings.Join(columnExprs, ", "),
|
Columns: strings.Join(columnExprs, ", "),
|
||||||
Unique: modelIndex.Unique,
|
Unique: modelIndex.Unique,
|
||||||
|
Concurrent: modelIndex.Concurrent,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -334,6 +334,46 @@ func TestWriteMigration_DoesNotAlterEquivalentNormalizedColumnType(t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWriteMigration_ConcurrentIndex(t *testing.T) {
|
||||||
|
current := models.InitDatabase("testdb")
|
||||||
|
currentSchema := models.InitSchema("public")
|
||||||
|
current.Schemas = append(current.Schemas, currentSchema)
|
||||||
|
|
||||||
|
model := models.InitDatabase("testdb")
|
||||||
|
modelSchema := models.InitSchema("public")
|
||||||
|
|
||||||
|
table := models.InitTable("articles", "public")
|
||||||
|
titleCol := models.InitColumn("title", "articles", "public")
|
||||||
|
titleCol.Type = "text"
|
||||||
|
table.Columns["title"] = titleCol
|
||||||
|
|
||||||
|
index := &models.Index{
|
||||||
|
Name: "idx_articles_title",
|
||||||
|
Columns: []string{"title"},
|
||||||
|
Concurrent: true,
|
||||||
|
}
|
||||||
|
table.Indexes[index.Name] = index
|
||||||
|
|
||||||
|
modelSchema.Tables = append(modelSchema.Tables, table)
|
||||||
|
model.Schemas = append(model.Schemas, modelSchema)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer, err := NewMigrationWriter(&writers.WriterOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create writer: %v", err)
|
||||||
|
}
|
||||||
|
writer.writer = &buf
|
||||||
|
|
||||||
|
if err := writer.WriteMigration(model, current); err != nil {
|
||||||
|
t.Fatalf("WriteMigration failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := buf.String()
|
||||||
|
if !strings.Contains(output, "CREATE INDEX CONCURRENTLY IF NOT EXISTS") {
|
||||||
|
t.Fatalf("expected CONCURRENTLY create index statement, got:\n%s", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWriteMigration_GinIndexOnTextUsesTrigramOperatorClass(t *testing.T) {
|
func TestWriteMigration_GinIndexOnTextUsesTrigramOperatorClass(t *testing.T) {
|
||||||
current := models.InitDatabase("testdb")
|
current := models.InitDatabase("testdb")
|
||||||
currentSchema := models.InitSchema("public")
|
currentSchema := models.InitSchema("public")
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ type CreateIndexData struct {
|
|||||||
IndexType string
|
IndexType string
|
||||||
Columns string
|
Columns string
|
||||||
Unique bool
|
Unique bool
|
||||||
|
Concurrent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateForeignKeyData contains data for create foreign key template
|
// CreateForeignKeyData contains data for create foreign key template
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
CREATE {{if .Unique}}UNIQUE {{end}}INDEX IF NOT EXISTS {{quote_ident .IndexName}}
|
CREATE {{if .Unique}}UNIQUE {{end}}INDEX {{if .Concurrent}}CONCURRENTLY {{end}}IF NOT EXISTS {{quote_ident .IndexName}}
|
||||||
ON {{qual_table .SchemaName .TableName}} USING {{.IndexType}} ({{.Columns}});
|
ON {{qual_table .SchemaName .TableName}} USING {{.IndexType}} ({{.Columns}});
|
||||||
@@ -1097,8 +1097,13 @@ func (w *Writer) writeIndexes(schema *models.Schema) error {
|
|||||||
whereClause = fmt.Sprintf(" WHERE %s", index.Where)
|
whereClause = fmt.Sprintf(" WHERE %s", index.Where)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(w.writer, "CREATE %sINDEX IF NOT EXISTS %s\n",
|
concurrently := ""
|
||||||
unique, indexName)
|
if index.Concurrent {
|
||||||
|
concurrently = "CONCURRENTLY "
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w.writer, "CREATE %sINDEX %sIF NOT EXISTS %s\n",
|
||||||
|
unique, concurrently, indexName)
|
||||||
fmt.Fprintf(w.writer, " ON %s USING %s (%s)%s;\n\n",
|
fmt.Fprintf(w.writer, " ON %s USING %s (%s)%s;\n\n",
|
||||||
w.qualTable(schema.SQLName(), table.SQLName()), indexType, strings.Join(columnExprs, ", "), whereClause)
|
w.qualTable(schema.SQLName(), table.SQLName()), indexType, strings.Join(columnExprs, ", "), whereClause)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,41 @@ func TestWriteDatabase(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWriteDatabase_ConcurrentIndex(t *testing.T) {
|
||||||
|
db := models.InitDatabase("testdb")
|
||||||
|
schema := models.InitSchema("public")
|
||||||
|
|
||||||
|
table := models.InitTable("users", "public")
|
||||||
|
|
||||||
|
emailCol := models.InitColumn("email", "users", "public")
|
||||||
|
emailCol.Type = "text"
|
||||||
|
table.Columns["email"] = emailCol
|
||||||
|
|
||||||
|
concurrentIndex := &models.Index{
|
||||||
|
Name: "idx_users_email",
|
||||||
|
Columns: []string{"email"},
|
||||||
|
Concurrent: true,
|
||||||
|
}
|
||||||
|
table.Indexes["idx_users_email"] = concurrentIndex
|
||||||
|
|
||||||
|
schema.Tables = append(schema.Tables, table)
|
||||||
|
db.Schemas = append(db.Schemas, schema)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&writers.WriterOptions{})
|
||||||
|
writer.writer = &buf
|
||||||
|
|
||||||
|
if err := writer.WriteDatabase(db); err != nil {
|
||||||
|
t.Fatalf("WriteDatabase failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := buf.String()
|
||||||
|
|
||||||
|
if !strings.Contains(output, "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_email") {
|
||||||
|
t.Errorf("Output missing CONCURRENTLY index creation:\n%s", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWriteDatabase_GinIndexOnTextArrayDoesNotUseTrigramOperatorClass(t *testing.T) {
|
func TestWriteDatabase_GinIndexOnTextArrayDoesNotUseTrigramOperatorClass(t *testing.T) {
|
||||||
db := models.InitDatabase("testdb")
|
db := models.InitDatabase("testdb")
|
||||||
schema := models.InitSchema("public")
|
schema := models.InitSchema("public")
|
||||||
|
|||||||
Reference in New Issue
Block a user