feat(index): implement GIN index support for quoted text columns and enhance index column resolution
This commit is contained in:
@@ -604,7 +604,7 @@ func buildIndexColumnExpressions(table *models.Table, index *models.Index, index
|
|||||||
for _, colName := range index.Columns {
|
for _, colName := range index.Columns {
|
||||||
colExpr := colName
|
colExpr := colName
|
||||||
if table != nil {
|
if table != nil {
|
||||||
if col, ok := table.Columns[colName]; ok && col != nil {
|
if col, ok := resolveIndexColumn(table, colName); ok && col != nil {
|
||||||
colExpr = col.SQLName()
|
colExpr = col.SQLName()
|
||||||
if strings.EqualFold(indexType, "gin") && isTextType(col.Type) {
|
if strings.EqualFold(indexType, "gin") && isTextType(col.Type) {
|
||||||
opClass := extractOperatorClass(index.Comment)
|
opClass := extractOperatorClass(index.Comment)
|
||||||
|
|||||||
@@ -137,6 +137,46 @@ func TestWriteMigration_GinIndexOnTextUsesTrigramOperatorClass(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWriteMigration_GinIndexOnQuotedTextColumnUsesTrigramOperatorClass(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("agent_personas", "public")
|
||||||
|
nameCol := models.InitColumn("name", "agent_personas", "public")
|
||||||
|
nameCol.Type = "text"
|
||||||
|
table.Columns["name"] = nameCol
|
||||||
|
|
||||||
|
index := &models.Index{
|
||||||
|
Name: "idx_agent_personas_name_gin",
|
||||||
|
Type: "gin",
|
||||||
|
Columns: []string{`"name"`},
|
||||||
|
}
|
||||||
|
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, "USING gin (name gin_trgm_ops)") {
|
||||||
|
t.Fatalf("expected quoted text column GIN index to include gin_trgm_ops, got:\n%s", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWriteMigration_GinIndexOnTextArrayDoesNotUseTrigramOperatorClass(t *testing.T) {
|
func TestWriteMigration_GinIndexOnTextArrayDoesNotUseTrigramOperatorClass(t *testing.T) {
|
||||||
current := models.InitDatabase("testdb")
|
current := models.InitDatabase("testdb")
|
||||||
currentSchema := models.InitSchema("public")
|
currentSchema := models.InitSchema("public")
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ BEGIN
|
|||||||
SELECT tc.constraint_name,
|
SELECT tc.constraint_name,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
ARRAY(
|
ARRAY(
|
||||||
SELECT a.attname
|
SELECT a.attname::text
|
||||||
FROM pg_constraint c
|
FROM pg_constraint c
|
||||||
JOIN pg_class t ON t.oid = c.conrelid
|
JOIN pg_class t ON t.oid = c.conrelid
|
||||||
JOIN pg_namespace n ON n.oid = t.relnamespace
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ func (w *Writer) GenerateSchemaStatements(schema *models.Schema) ([]string, erro
|
|||||||
columnExprs := make([]string, 0, len(index.Columns))
|
columnExprs := make([]string, 0, len(index.Columns))
|
||||||
for _, colName := range index.Columns {
|
for _, colName := range index.Columns {
|
||||||
colExpr := colName
|
colExpr := colName
|
||||||
if col, ok := table.Columns[colName]; ok {
|
if col, ok := resolveIndexColumn(table, colName); ok {
|
||||||
// For GIN indexes on text columns, add operator class
|
// For GIN indexes on text columns, add operator class
|
||||||
if strings.EqualFold(indexType, "gin") && isTextType(col.Type) {
|
if strings.EqualFold(indexType, "gin") && isTextType(col.Type) {
|
||||||
opClass := extractOperatorClass(index.Comment)
|
opClass := extractOperatorClass(index.Comment)
|
||||||
@@ -855,7 +855,7 @@ func (w *Writer) writeIndexes(schema *models.Schema) error {
|
|||||||
// Build column list with operator class support for GIN indexes
|
// Build column list with operator class support for GIN indexes
|
||||||
columnExprs := make([]string, 0, len(index.Columns))
|
columnExprs := make([]string, 0, len(index.Columns))
|
||||||
for _, colName := range index.Columns {
|
for _, colName := range index.Columns {
|
||||||
if col, ok := table.Columns[colName]; ok {
|
if col, ok := resolveIndexColumn(table, colName); ok {
|
||||||
colExpr := col.SQLName()
|
colExpr := col.SQLName()
|
||||||
// For GIN indexes on text columns, add operator class
|
// For GIN indexes on text columns, add operator class
|
||||||
if strings.EqualFold(index.Type, "gin") && isTextType(col.Type) {
|
if strings.EqualFold(index.Type, "gin") && isTextType(col.Type) {
|
||||||
@@ -1269,6 +1269,33 @@ func isTextTypeWithoutLength(colType string) bool {
|
|||||||
return strings.EqualFold(colType, "text")
|
return strings.EqualFold(colType, "text")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveIndexColumn(table *models.Table, colName string) (*models.Column, bool) {
|
||||||
|
if table == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if col, ok := table.Columns[colName]; ok && col != nil {
|
||||||
|
return col, true
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized := strings.ToLower(strings.Trim(colName, `"`))
|
||||||
|
for key, col := range table.Columns {
|
||||||
|
if col == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.ToLower(strings.Trim(key, `"`)) == normalized {
|
||||||
|
return col, true
|
||||||
|
}
|
||||||
|
if strings.ToLower(strings.Trim(col.Name, `"`)) == normalized {
|
||||||
|
return col, true
|
||||||
|
}
|
||||||
|
if strings.ToLower(strings.Trim(col.SQLName(), `"`)) == normalized {
|
||||||
|
return col, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
// formatStringList formats a list of strings as a SQL-safe comma-separated quoted list
|
// formatStringList formats a list of strings as a SQL-safe comma-separated quoted list
|
||||||
func formatStringList(items []string) string {
|
func formatStringList(items []string) string {
|
||||||
quoted := make([]string, len(items))
|
quoted := make([]string, len(items))
|
||||||
|
|||||||
@@ -124,6 +124,40 @@ func TestWriteDatabase_GinIndexOnTextArrayDoesNotUseTrigramOperatorClass(t *test
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWriteDatabase_GinIndexOnQuotedTextColumnUsesTrigramOperatorClass(t *testing.T) {
|
||||||
|
db := models.InitDatabase("testdb")
|
||||||
|
schema := models.InitSchema("public")
|
||||||
|
|
||||||
|
table := models.InitTable("agent_personas", "public")
|
||||||
|
|
||||||
|
nameCol := models.InitColumn("name", "agent_personas", "public")
|
||||||
|
nameCol.Type = "text"
|
||||||
|
table.Columns["name"] = nameCol
|
||||||
|
|
||||||
|
index := &models.Index{
|
||||||
|
Name: "idx_agent_personas_name_gin",
|
||||||
|
Type: "gin",
|
||||||
|
Columns: []string{`"name"`},
|
||||||
|
}
|
||||||
|
table.Indexes[index.Name] = index
|
||||||
|
|
||||||
|
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, `USING gin (name gin_trgm_ops)`) {
|
||||||
|
t.Fatalf("expected quoted text GIN index to include gin_trgm_ops, got:\n%s", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWriteForeignKeys(t *testing.T) {
|
func TestWriteForeignKeys(t *testing.T) {
|
||||||
// Create a test database with two related tables
|
// Create a test database with two related tables
|
||||||
db := models.InitDatabase("testdb")
|
db := models.InitDatabase("testdb")
|
||||||
|
|||||||
Reference in New Issue
Block a user