diff --git a/constants/parameter_table.go b/constants/parameter_table.go new file mode 100644 index 0000000..bcbb12c --- /dev/null +++ b/constants/parameter_table.go @@ -0,0 +1,26 @@ +// Package constants define constant variable +package constants + +import "strings" + +var supportedParameterTableSuffixes = [...]string{ + "base_extend", + "rated", + "setup", + "model", + "stable", + "craft", + "integrity", + "behavior", +} + +// IsSupportedParameterTableName reports whether a dynamic parameter table has +// one of the supported attribute-group suffixes. +func IsSupportedParameterTableName(tableName string) bool { + for _, suffix := range supportedParameterTableSuffixes { + if strings.HasSuffix(tableName, "_"+suffix) { + return true + } + } + return false +} diff --git a/constants/parameter_table_test.go b/constants/parameter_table_test.go new file mode 100644 index 0000000..7277978 --- /dev/null +++ b/constants/parameter_table_test.go @@ -0,0 +1,25 @@ +package constants + +import "testing" + +func TestIsSupportedParameterTableName(t *testing.T) { + tests := []struct { + name string + tableName string + want bool + }{ + {name: "bay table is excluded", tableName: "ct_ct_demo_bay", want: false}, + {name: "model table is included", tableName: "cable_cable_demo_model", want: true}, + {name: "base extend table is included", tableName: "cable_cable_demo_base_extend", want: true}, + {name: "suffix must start at separator", tableName: "cable_cable_demomodel", want: false}, + {name: "empty table name", tableName: "", want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsSupportedParameterTableName(tt.tableName); got != tt.want { + t.Fatalf("IsSupportedParameterTableName(%q) = %v, want %v", tt.tableName, got, tt.want) + } + }) + } +} diff --git a/database/query_bay_columns.go b/database/query_bay_columns.go new file mode 100644 index 0000000..5a5e8dc --- /dev/null +++ b/database/query_bay_columns.go @@ -0,0 +1,28 @@ +package database + +import ( + "context" + "strings" + + "modelRT/orm" + + "gorm.io/gorm" +) + +// QueryBayDevColumnNames returns the bay table columns exposed as token7 +// candidates under token6=bay. +func QueryBayDevColumnNames(ctx context.Context, db *gorm.DB) ([]string, error) { + columnTypes, err := db.WithContext(ctx).Migrator().ColumnTypes((&orm.Bay{}).TableName()) + if err != nil { + return nil, err + } + + columnNames := make([]string, 0, len(columnTypes)) + for _, columnType := range columnTypes { + columnName := columnType.Name() + if strings.HasPrefix(columnName, "dev_") { + columnNames = append(columnNames, columnName) + } + } + return columnNames, nil +} diff --git a/database/query_component_measurement.go b/database/query_component_measurement.go index c4a874f..74a91ac 100644 --- a/database/query_component_measurement.go +++ b/database/query_component_measurement.go @@ -279,6 +279,31 @@ func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSe g, gctx := errgroup.WithContext(ctx) db = db.WithContext(gctx) + var bayLinkedCompTags []string + var bayDevColumnNames []string + + g.Go(func() error { + var linkedComponents []struct { + CompTag string `gorm:"column:comp_tag"` + } + if err := db.Raw(compactMeasurementSQL(sql.MeasurementBayLinkedComponentTags)).Scan(&linkedComponents).Error; err != nil { + return fmt.Errorf("query bay-linked components: %w", err) + } + bayLinkedCompTags = make([]string, 0, len(linkedComponents)) + for _, component := range linkedComponents { + bayLinkedCompTags = append(bayLinkedCompTags, component.CompTag) + } + return nil + }) + + g.Go(func() error { + var err error + bayDevColumnNames, err = QueryBayDevColumnNames(gctx, db) + if err != nil { + return fmt.Errorf("query bay dev columns: %w", err) + } + return nil + }) g.Go(func() error { var grids []orm.Grid @@ -383,6 +408,22 @@ func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSe return nil, err } + appendBayDevCandidates(mSet, bayLinkedCompTags, bayDevColumnNames) + mSet.AllConfigTags = append(mSet.AllConfigTags, "bay") return mSet, nil } + +func appendBayDevCandidates(mSet *orm.MeasurementSet, bayLinkedCompTags, bayDevColumnNames []string) { + if mSet == nil || len(bayLinkedCompTags) == 0 || len(bayDevColumnNames) == 0 { + return + } + + mSet.AllMeasTags = append(mSet.AllMeasTags, bayDevColumnNames...) + for _, compTag := range bayLinkedCompTags { + mSet.CompTagToMeasTags[compTag] = append( + mSet.CompTagToMeasTags[compTag], + bayDevColumnNames..., + ) + } +} diff --git a/database/query_component_measurement_bay_test.go b/database/query_component_measurement_bay_test.go new file mode 100644 index 0000000..db0d0a7 --- /dev/null +++ b/database/query_component_measurement_bay_test.go @@ -0,0 +1,52 @@ +package database + +import ( + "testing" + + "modelRT/orm" + + "github.com/stretchr/testify/require" +) + +func TestAppendBayDevCandidatesRequiresBayLinkedComponent(t *testing.T) { + measurementSet := &orm.MeasurementSet{ + AllMeasTags: []string{"current"}, + CompTagToMeasTags: map[string][]string{ + "linked-component": {"current"}, + "unlinked-component": {"voltage"}, + }, + } + + appendBayDevCandidates( + measurementSet, + []string{"linked-component"}, + []string{"dev_instruct", "dev_dyn_sense", "dev_fault_record"}, + ) + + require.Equal(t, + []string{"current", "dev_instruct", "dev_dyn_sense", "dev_fault_record"}, + measurementSet.AllMeasTags, + ) + require.Equal(t, + []string{"current", "dev_instruct", "dev_dyn_sense", "dev_fault_record"}, + measurementSet.CompTagToMeasTags["linked-component"], + ) + require.Equal(t, + []string{"voltage"}, + measurementSet.CompTagToMeasTags["unlinked-component"], + ) +} + +func TestAppendBayDevCandidatesWithoutBayLinkDoesNothing(t *testing.T) { + measurementSet := &orm.MeasurementSet{ + AllMeasTags: []string{"current"}, + CompTagToMeasTags: map[string][]string{ + "component": {"current"}, + }, + } + + appendBayDevCandidates(measurementSet, nil, []string{"dev_instruct"}) + + require.Equal(t, []string{"current"}, measurementSet.AllMeasTags) + require.Equal(t, []string{"current"}, measurementSet.CompTagToMeasTags["component"]) +} diff --git a/database/query_component_parameter.go b/database/query_component_parameter.go index ceff620..6a965f6 100644 --- a/database/query_component_parameter.go +++ b/database/query_component_parameter.go @@ -86,7 +86,7 @@ func QueryParameterByDataObjectToken(ctx context.Context, db *gorm.DB, token str } project := projects[0] - if !parameterTableNamePattern.MatchString(project.Name) { + if !validParameterTableName(project.Name) { return nil, fmt.Errorf("project mapping for parameter token %q contains invalid table name %q", token, project.Name) } attributeType, err := queryParameterAttributeType(ctx, db, project.Name, attributeName, token) @@ -155,7 +155,7 @@ func UpdateParameterDataObjectValue(ctx context.Context, db *gorm.DB, parameter if parameter.AttributeGroup == "component" { return fmt.Errorf("component data-object updates are not supported") } - if !parameterTableNamePattern.MatchString(parameter.TableName) { + if !validParameterTableName(parameter.TableName) { return fmt.Errorf("invalid parameter table name %q", parameter.TableName) } @@ -172,6 +172,10 @@ func UpdateParameterDataObjectValue(ctx context.Context, db *gorm.DB, parameter return nil } +func validParameterTableName(tableName string) bool { + return parameterTableNamePattern.MatchString(tableName) && constants.IsSupportedParameterTableName(tableName) +} + // QueryParameterAttributeDescription returns the display name registered for // token7 in basic.attribute. func QueryParameterAttributeDescription(ctx context.Context, db *gorm.DB, attributeName string) (string, error) { diff --git a/model/attribute_group_recommend_model.go b/model/attribute_group_recommend_model.go index 22253df..7a7632f 100644 --- a/model/attribute_group_recommend_model.go +++ b/model/attribute_group_recommend_model.go @@ -33,13 +33,19 @@ func TraverseAttributeGroupTables(ctx context.Context, db *gorm.DB, compTagToFul var tableNames []string excludedTables := []string{"component", ""} + var projectTableNames []string result := db.Model(&orm.ProjectManager{}). Where("name NOT IN ?", excludedTables). - Pluck("name", &tableNames) + Pluck("name", &projectTableNames) if result.Error != nil && result.Error != gorm.ErrRecordNotFound { logger.Error(ctx, "query name column data from postgres table failed", "err", result.Error) return result.Error } + for _, tableName := range projectTableNames { + if constants.IsSupportedParameterTableName(tableName) { + tableNames = append(tableNames, tableName) + } + } if len(tableNames) == 0 { logger.Info(ctx, "query from postgres successed, but no records found") diff --git a/sql/data_object_measurement.go b/sql/data_object_measurement.go index 5835412..bd734f6 100644 --- a/sql/data_object_measurement.go +++ b/sql/data_object_measurement.go @@ -86,4 +86,15 @@ const ( ON measurement.component_uuid = component.global_uuid LEFT JOIN bay ON measurement.bay_uuid = bay.bay_uuid` + + // MeasurementBayLinkedComponentTags returns components that have at least + // one measurement whose bay_uuid resolves to an existing bay record. + MeasurementBayLinkedComponentTags = ` + SELECT DISTINCT component.tag AS comp_tag + FROM component + INNER JOIN measurement + ON component.global_uuid = measurement.component_uuid + INNER JOIN bay + ON measurement.bay_uuid = bay.bay_uuid + WHERE component.tag <> ''` )