feat(data-object): support bay device measurement candidates
- expose bay dev_* columns for components linked to bay measurements - restrict parameter queries and traversal to supported attribute-group tables - add regression tests for bay candidates and parameter table validation
This commit is contained in:
parent
ec7d97d4d2
commit
0adfe022b2
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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...,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 <> ''`
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue