重构creatingUniqueIndex和createGinIndexes
This commit is contained in:
@@ -6,6 +6,7 @@ package database
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
@@ -265,144 +266,169 @@ func (ps *PostgresStorage) creatingIndex(ctx context.Context) error {
|
||||
func (ps *PostgresStorage) creatingUniqueIndex(ctx context.Context) error {
|
||||
storageCtx, logger := logs.Trace(ctx, ps.ctx, "creatingUniqueIndex")
|
||||
|
||||
// 为 raw_material_nutrients 表创建部分唯一索引,以兼容软删除
|
||||
logger.Debug("正在为 raw_material_nutrients 表创建部分唯一索引")
|
||||
partialIndexSQL := "CREATE UNIQUE INDEX IF NOT EXISTS idx_raw_material_nutrients_unique_when_not_deleted ON raw_material_nutrients (raw_material_id, nutrient_id) WHERE deleted_at IS NULL;"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 raw_material_nutrients 创建部分唯一索引失败", "error", err)
|
||||
return fmt.Errorf("为 raw_material_nutrients 创建部分唯一索引失败: %w", err)
|
||||
// uniqueIndexDefinition 结构体定义了唯一索引的详细信息
|
||||
type uniqueIndexDefinition struct {
|
||||
tableName string // 索引所属的表名
|
||||
columns []string // 构成唯一索引的列名
|
||||
indexName string // 唯一索引的名称
|
||||
whereClause string // 可选的 WHERE 子句,用于创建部分索引
|
||||
description string // 索引的描述,用于日志记录
|
||||
}
|
||||
logger.Debug("成功为 raw_material_nutrients 创建部分唯一索引 (或已存在)")
|
||||
|
||||
// 为 pig_breeds 表创建部分唯一索引,以兼容软删除 (name 唯一)
|
||||
logger.Debug("正在为 pig_breeds 表创建部分唯一索引")
|
||||
partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_pig_breeds_unique_name_when_not_deleted ON pig_breeds (name) WHERE deleted_at IS NULL;"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 pig_breeds 创建部分唯一索引失败", "error", err)
|
||||
return fmt.Errorf("为 pig_breeds 创建部分唯一索引失败: %w", err)
|
||||
// 定义所有需要创建的唯一索引
|
||||
uniqueIndexesToCreate := []uniqueIndexDefinition{
|
||||
{
|
||||
tableName: models.RawMaterialNutrient{}.TableName(),
|
||||
columns: []string{"raw_material_id", "nutrient_id"},
|
||||
indexName: "idx_raw_material_nutrients_unique_when_not_deleted",
|
||||
whereClause: "WHERE deleted_at IS NULL",
|
||||
description: "确保同一原料中的每种营养成分不重复",
|
||||
},
|
||||
{
|
||||
tableName: models.PigBreed{}.TableName(),
|
||||
columns: []string{"name"},
|
||||
indexName: "idx_pig_breeds_unique_name_when_not_deleted",
|
||||
whereClause: "WHERE deleted_at IS NULL",
|
||||
description: "pig_breeds 表的部分唯一索引 (name 唯一)",
|
||||
},
|
||||
{
|
||||
tableName: models.PigAgeStage{}.TableName(),
|
||||
columns: []string{"name"},
|
||||
indexName: "idx_pig_age_stages_unique_name_when_not_deleted",
|
||||
whereClause: "WHERE deleted_at IS NULL",
|
||||
description: "pig_age_stages 表的部分唯一索引 (name 唯一)",
|
||||
},
|
||||
{
|
||||
tableName: models.PigType{}.TableName(),
|
||||
columns: []string{"breed_id", "age_stage_id"},
|
||||
indexName: "idx_pig_types_unique_breed_age_stage_when_not_deleted",
|
||||
whereClause: "WHERE deleted_at IS NULL",
|
||||
description: "pig_types 表的部分唯一索引 (breed_id, age_stage_id 组合唯一)",
|
||||
},
|
||||
{
|
||||
tableName: models.PigNutrientRequirement{}.TableName(),
|
||||
columns: []string{"pig_type_id", "nutrient_id"},
|
||||
indexName: "idx_pig_nutrient_requirements_unique_type_nutrient_when_not_deleted",
|
||||
whereClause: "WHERE deleted_at IS NULL",
|
||||
description: "pig_nutrient_requirements 表的部分唯一索引 (pig_type_id, nutrient_id 组合唯一)",
|
||||
},
|
||||
{
|
||||
tableName: models.User{}.TableName(),
|
||||
columns: []string{"username"},
|
||||
indexName: "idx_users_unique_username_when_not_deleted",
|
||||
whereClause: "WHERE deleted_at IS NULL",
|
||||
description: "users 表的部分唯一索引 (username 唯一)",
|
||||
},
|
||||
{
|
||||
tableName: models.AreaController{}.TableName(),
|
||||
columns: []string{"name"},
|
||||
indexName: "idx_area_controllers_unique_name_when_not_deleted",
|
||||
whereClause: "WHERE deleted_at IS NULL",
|
||||
description: "area_controllers 表的部分唯一索引 (Name 唯一)",
|
||||
},
|
||||
{
|
||||
tableName: models.AreaController{}.TableName(),
|
||||
columns: []string{"network_id"},
|
||||
indexName: "idx_area_controllers_unique_network_id_when_not_deleted",
|
||||
whereClause: "WHERE deleted_at IS NULL",
|
||||
description: "area_controllers 表的部分唯一索引 (NetworkID 唯一)",
|
||||
},
|
||||
{
|
||||
tableName: models.DeviceTemplate{}.TableName(),
|
||||
columns: []string{"name"},
|
||||
indexName: "idx_device_templates_unique_name_when_not_deleted",
|
||||
whereClause: "WHERE deleted_at IS NULL",
|
||||
description: "device_templates 表的部分唯一索引 (name 唯一)",
|
||||
},
|
||||
{
|
||||
tableName: models.PigBatch{}.TableName(),
|
||||
columns: []string{"batch_number"},
|
||||
indexName: "idx_pig_batches_unique_batch_number_when_not_deleted",
|
||||
whereClause: "WHERE deleted_at IS NULL",
|
||||
description: "pig_batches 表的部分唯一索引 (batch_number 唯一)",
|
||||
},
|
||||
{
|
||||
tableName: models.PigHouse{}.TableName(),
|
||||
columns: []string{"name"},
|
||||
indexName: "idx_pig_houses_unique_name_when_not_deleted",
|
||||
whereClause: "WHERE deleted_at IS NULL",
|
||||
description: "pig_houses 表的部分唯一索引 (name 唯一)",
|
||||
},
|
||||
{
|
||||
tableName: models.RawMaterial{}.TableName(),
|
||||
columns: []string{"name"},
|
||||
indexName: "idx_raw_materials_unique_name_when_not_deleted",
|
||||
whereClause: "WHERE deleted_at IS NULL",
|
||||
description: "raw_materials 表的部分唯一索引 (name 唯一)",
|
||||
},
|
||||
{
|
||||
tableName: models.Nutrient{}.TableName(),
|
||||
columns: []string{"name"},
|
||||
indexName: "idx_nutrients_unique_name_when_not_deleted",
|
||||
whereClause: "WHERE deleted_at IS NULL",
|
||||
description: "nutrients 表的部分唯一索引 (name 唯一)",
|
||||
},
|
||||
}
|
||||
logger.Debug("成功为 pig_breeds 创建部分唯一索引 (或已存在)")
|
||||
|
||||
// 为 pig_age_stages 表创建部分唯一索引,以兼容软删除 (name 唯一)
|
||||
logger.Debug("正在为 pig_age_stages 表创建部分唯一索引")
|
||||
partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_pig_age_stages_unique_name_when_not_deleted ON pig_age_stages (name) WHERE deleted_at IS NULL;"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 pig_age_stages 创建部分唯一索引失败", "error", err)
|
||||
return fmt.Errorf("为 pig_age_stages 创建部分唯一索引失败: %w", err)
|
||||
}
|
||||
logger.Debug("成功为 pig_age_stages 创建部分唯一索引 (或已存在)")
|
||||
for _, indexDef := range uniqueIndexesToCreate {
|
||||
logger.Debugw("正在为表创建部分唯一索引", "表名", indexDef.tableName, "索引名", indexDef.indexName, "描述", indexDef.description)
|
||||
|
||||
// 为 pig_types 表创建部分唯一索引,以兼容软删除 (breed_id, age_stage_id 组合唯一)
|
||||
logger.Debug("正在为 pig_types 表创建部分唯一索引")
|
||||
partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_pig_types_unique_breed_age_stage_when_not_deleted ON pig_types (breed_id, age_stage_id) WHERE deleted_at IS NULL;"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 pig_types 创建部分唯一索引失败", "error", err)
|
||||
return fmt.Errorf("为 pig_types 创建部分唯一索引失败: %w", err)
|
||||
}
|
||||
logger.Debug("成功为 pig_types 创建部分唯一索引 (或已存在)")
|
||||
// 拼接列名字符串
|
||||
columnsStr := strings.Join(indexDef.columns, ", ")
|
||||
// 构建 SQL 语句
|
||||
sql := fmt.Sprintf("CREATE UNIQUE INDEX IF NOT EXISTS %s ON %s (%s) %s;",
|
||||
indexDef.indexName, indexDef.tableName, columnsStr, indexDef.whereClause)
|
||||
|
||||
// 为 pig_nutrient_requirements 表创建部分唯一索引,以兼容软删除 (pig_type_id, nutrient_id 组合唯一)
|
||||
logger.Debug("正在为 pig_nutrient_requirements 表创建部分唯一索引")
|
||||
partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_pig_nutrient_requirements_unique_type_nutrient_when_not_deleted ON pig_nutrient_requirements (pig_type_id, nutrient_id) WHERE deleted_at IS NULL;"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 pig_nutrient_requirements 创建部分唯一索引失败", "error", err)
|
||||
return fmt.Errorf("为 pig_nutrient_requirements 创建部分唯一索引失败: %w", err)
|
||||
if err := ps.db.WithContext(storageCtx).Exec(sql).Error; err != nil {
|
||||
logger.Errorw("创建部分唯一索引失败", "表名", indexDef.tableName, "索引名", indexDef.indexName, "错误", err)
|
||||
return fmt.Errorf("为 %s 表创建部分唯一索引 %s 失败: %w", indexDef.tableName, indexDef.indexName, err)
|
||||
}
|
||||
logger.Debugw("成功为表创建部分唯一索引 (或已存在)", "表名", indexDef.tableName, "索引名", indexDef.indexName)
|
||||
}
|
||||
logger.Debug("成功为 pig_nutrient_requirements 创建部分唯一索引 (或已存在)")
|
||||
|
||||
// 为 users 表创建部分唯一索引
|
||||
logger.Debug("正在为 users 表创建部分唯一索引")
|
||||
partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_users_unique_username_when_not_deleted ON users (username) WHERE deleted_at IS NULL;"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 users 创建部分唯一索引失败", "error", err)
|
||||
return fmt.Errorf("为 users 创建部分唯一索引失败: %w", err)
|
||||
}
|
||||
logger.Debug("成功为 users 创建部分唯一索引 (或已存在)")
|
||||
|
||||
// 为 area_controllers 表创建部分唯一索引 (Name)
|
||||
logger.Debug("正在为 area_controllers 表创建部分唯一索引 (Name)")
|
||||
partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_area_controllers_unique_name_when_not_deleted ON area_controllers (name) WHERE deleted_at IS NULL;"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 area_controllers 创建部分唯一索引 (Name) 失败", "error", err)
|
||||
return fmt.Errorf("为 area_controllers 创建部分唯一索引 (Name) 失败: %w", err)
|
||||
}
|
||||
logger.Debug("成功为 area_controllers 创建部分唯一索引 (Name) (或已存在)")
|
||||
|
||||
// 为 area_controllers 表创建部分唯一索引 (NetworkID)
|
||||
logger.Debug("正在为 area_controllers 表创建部分唯一索引 (NetworkID)")
|
||||
partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_area_controllers_unique_network_id_when_not_deleted ON area_controllers (network_id) WHERE deleted_at IS NULL;"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 area_controllers 创建部分唯一索引 (NetworkID) 失败", "error", err)
|
||||
return fmt.Errorf("为 area_controllers 创建部分唯一索引 (NetworkID) 失败: %w", err)
|
||||
}
|
||||
logger.Debug("成功为 area_controllers 创建部分唯一索引 (NetworkID) (或已存在)")
|
||||
|
||||
// 为 device_templates 表创建部分唯一索引
|
||||
logger.Debug("正在为 device_templates 表创建部分唯一索引")
|
||||
partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_device_templates_unique_name_when_not_deleted ON device_templates (name) WHERE deleted_at IS NULL;"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 device_templates 创建部分唯一索引失败", "error", err)
|
||||
return fmt.Errorf("为 device_templates 创建部分唯一索引失败: %w", err)
|
||||
}
|
||||
logger.Debug("成功为 device_templates 创建部分唯一索引 (或已存在)")
|
||||
|
||||
// 为 pig_batches 表创建部分唯一索引
|
||||
logger.Debug("正在为 pig_batches 表创建部分唯一索引")
|
||||
partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_pig_batches_unique_batch_number_when_not_deleted ON pig_batches (batch_number) WHERE deleted_at IS NULL;"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 pig_batches 创建部分唯一索引失败", "error", err)
|
||||
return fmt.Errorf("为 pig_batches 创建部分唯一索引失败: %w", err)
|
||||
}
|
||||
logger.Debug("成功为 pig_batches 创建部分唯一索引 (或已存在)")
|
||||
|
||||
// 为 pig_houses 表创建部分唯一索引
|
||||
logger.Debug("正在为 pig_houses 表创建部分唯一索引")
|
||||
partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_pig_houses_unique_name_when_not_deleted ON pig_houses (name) WHERE deleted_at IS NULL;"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 pig_houses 创建部分唯一索引失败", "error", err)
|
||||
return fmt.Errorf("为 pig_houses 创建部分唯一索引失败: %w", err)
|
||||
}
|
||||
logger.Debug("成功为 pig_houses 创建部分唯一索引 (或已存在)")
|
||||
|
||||
// 为 raw_materials 表创建部分唯一索引
|
||||
logger.Debug("正在为 raw_materials 表创建部分唯一索引")
|
||||
partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_raw_materials_unique_name_when_not_deleted ON raw_materials (name) WHERE deleted_at IS NULL;"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 raw_materials 创建部分唯一索引失败", "error", err)
|
||||
return fmt.Errorf("为 raw_materials 创建部分唯一索引失败: %w", err)
|
||||
}
|
||||
logger.Debug("成功为 raw_materials 创建部分唯一索引 (或已存在)")
|
||||
|
||||
// 为 nutrients 表创建部分唯一索引
|
||||
logger.Debug("正在为 nutrients 表创建部分唯一索引")
|
||||
partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_nutrients_unique_name_when_not_deleted ON nutrients (name) WHERE deleted_at IS NULL;"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 nutrients 创建部分唯一索引失败", "error", err)
|
||||
return fmt.Errorf("为 nutrients 创建部分唯一索引失败: %w", err)
|
||||
}
|
||||
logger.Debug("成功为 nutrients 创建部分唯一索引 (或已存在)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *PostgresStorage) createGinIndexes(ctx context.Context) error {
|
||||
storageCtx, logger := logs.Trace(ctx, ps.ctx, "createGinIndexes")
|
||||
|
||||
// 为 sensor_data 表的 data 字段创建 GIN 索引
|
||||
logger.Debug("正在为 sensor_data 表的 data 字段创建 GIN 索引")
|
||||
ginSensorDataIndexSQL := "CREATE INDEX IF NOT EXISTS idx_sensor_data_data_gin ON sensor_data USING GIN (data);"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(ginSensorDataIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 sensor_data 的 data 字段创建 GIN 索引失败", "error", err)
|
||||
return fmt.Errorf("为 sensor_data 的 data 字段创建 GIN 索引失败: %w", err)
|
||||
// ginIndexDefinition 结构体定义了 GIN 索引的详细信息
|
||||
type ginIndexDefinition struct {
|
||||
tableName string // 索引所属的表名
|
||||
columnName string // 需要创建 GIN 索引的列名
|
||||
indexName string // GIN 索引的名称
|
||||
description string // 索引的描述,用于日志记录
|
||||
}
|
||||
logger.Debug("成功为 sensor_data 的 data 字段创建 GIN 索引 (或已存在)")
|
||||
|
||||
// 为 tasks.parameters 创建 GIN 索引
|
||||
logger.Debug("正在为 tasks 表的 parameters 字段创建 GIN 索引")
|
||||
taskGinIndexSQL := "CREATE INDEX IF NOT EXISTS idx_tasks_parameters_gin ON tasks USING GIN (parameters);"
|
||||
if err := ps.db.WithContext(storageCtx).Exec(taskGinIndexSQL).Error; err != nil {
|
||||
logger.Errorw("为 tasks 的 parameters 字段创建 GIN 索引失败", "error", err)
|
||||
return fmt.Errorf("为 tasks 的 parameters 字段创建 GIN 索引失败: %w", err)
|
||||
// 定义所有需要创建的 GIN 索引
|
||||
ginIndexesToCreate := []ginIndexDefinition{
|
||||
{
|
||||
tableName: "sensor_data",
|
||||
columnName: "data",
|
||||
indexName: "idx_sensor_data_data_gin",
|
||||
description: "为 sensor_data 表的 data 字段创建 GIN 索引",
|
||||
},
|
||||
{
|
||||
tableName: "tasks",
|
||||
columnName: "parameters",
|
||||
indexName: "idx_tasks_parameters_gin",
|
||||
description: "为 tasks 表的 parameters 字段创建 GIN 索引",
|
||||
},
|
||||
}
|
||||
logger.Debug("成功为 tasks 的 parameters 字段创建 GIN 索引 (或已存在)")
|
||||
|
||||
for _, indexDef := range ginIndexesToCreate {
|
||||
logger.Debugw("正在创建 GIN 索引", "表名", indexDef.tableName, "列名", indexDef.columnName, "描述", indexDef.description)
|
||||
|
||||
// 构建 SQL 语句
|
||||
sql := fmt.Sprintf("CREATE INDEX IF NOT EXISTS %s ON %s USING GIN (%s);",
|
||||
indexDef.indexName, indexDef.tableName, indexDef.columnName)
|
||||
|
||||
if err := ps.db.WithContext(storageCtx).Exec(sql).Error; err != nil {
|
||||
logger.Errorw("创建 GIN 索引失败", "表名", indexDef.tableName, "索引名", indexDef.indexName, "错误", err)
|
||||
return fmt.Errorf("为 %s 表的 %s 字段创建 GIN 索引 %s 失败: %w", indexDef.tableName, indexDef.columnName, indexDef.indexName, err)
|
||||
}
|
||||
logger.Debugw("成功创建 GIN 索引 (或已存在)", "表名", indexDef.tableName, "索引名", indexDef.indexName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user