218 lines
7.5 KiB
Go
218 lines
7.5 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"modelRT/common"
|
|
"modelRT/constants"
|
|
"modelRT/model"
|
|
"modelRT/orm"
|
|
modelsql "modelRT/sql"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var parameterTableNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
|
|
|
|
// ParameterDataObject contains the resolved metadata needed to query a
|
|
// parameter attribute after its token has passed hierarchy validation.
|
|
type ParameterDataObject struct {
|
|
Component orm.Component
|
|
Project orm.ProjectManager
|
|
TableName string
|
|
AttributeGroup string
|
|
AttributeName string
|
|
AttributeType string
|
|
}
|
|
|
|
// QueryParameterByDataObjectToken validates a four-part or seven-part
|
|
// parameter token. The component group resolves directly to the component
|
|
// table; other groups resolve through project_manager and a dynamic table.
|
|
func QueryParameterByDataObjectToken(ctx context.Context, db *gorm.DB, token string) (*ParameterDataObject, error) {
|
|
componentQuery, componentArgs, parts, err := buildParameterComponentQuery(token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var components []orm.Component
|
|
if err := db.WithContext(ctx).Raw(componentQuery, componentArgs...).Scan(&components).Error; err != nil {
|
|
return nil, fmt.Errorf("query component for parameter token %q: %w", token, err)
|
|
}
|
|
|
|
switch len(components) {
|
|
case 0:
|
|
return nil, fmt.Errorf("%w: component hierarchy for %q", common.ErrParameterTokenNotFound, token)
|
|
case 1:
|
|
// Continue by resolving the component model and attribute group.
|
|
default:
|
|
return nil, fmt.Errorf("%w: component hierarchy for %q matched more than one record", common.ErrAmbiguousParameterToken, token)
|
|
}
|
|
|
|
attributeGroup := parts[len(parts)-2]
|
|
attributeName := parts[len(parts)-1]
|
|
component := components[0]
|
|
if attributeGroup == "component" {
|
|
attributeType, err := queryParameterAttributeType(ctx, db, "component", attributeName, token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ParameterDataObject{
|
|
Component: component,
|
|
TableName: "component",
|
|
AttributeGroup: attributeGroup,
|
|
AttributeName: attributeName,
|
|
AttributeType: attributeType,
|
|
}, nil
|
|
}
|
|
|
|
var projects []orm.ProjectManager
|
|
if err := db.WithContext(ctx).
|
|
Where("tag = ? AND group_name = ?", component.ModelName, attributeGroup).
|
|
Limit(2).
|
|
Find(&projects).Error; err != nil {
|
|
return nil, fmt.Errorf("query project mapping for parameter token %q: %w", token, err)
|
|
}
|
|
|
|
switch len(projects) {
|
|
case 0:
|
|
return nil, fmt.Errorf("%w: model %q does not define attribute group %q", common.ErrParameterTokenNotFound, component.ModelName, attributeGroup)
|
|
case 1:
|
|
// Continue by validating the dynamic table and attribute.
|
|
default:
|
|
return nil, fmt.Errorf("%w: model %q and attribute group %q matched more than one project", common.ErrAmbiguousParameterToken, component.ModelName, attributeGroup)
|
|
}
|
|
|
|
project := projects[0]
|
|
if !parameterTableNamePattern.MatchString(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)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var recordCount int64
|
|
if err := db.WithContext(ctx).
|
|
Table(project.Name).
|
|
Where("global_uuid = ? AND attribute_group = ?", component.GlobalUUID, attributeGroup).
|
|
Count(&recordCount).Error; err != nil {
|
|
return nil, fmt.Errorf("query dynamic record for parameter token %q: %w", token, err)
|
|
}
|
|
|
|
switch {
|
|
case recordCount == 0:
|
|
return nil, fmt.Errorf("%w: component %q has no %q parameter record", common.ErrParameterTokenNotFound, component.Tag, attributeGroup)
|
|
case recordCount > 1:
|
|
return nil, fmt.Errorf("%w: component %q has %d %q parameter records", common.ErrAmbiguousParameterToken, component.Tag, recordCount, attributeGroup)
|
|
}
|
|
|
|
return &ParameterDataObject{
|
|
Component: component,
|
|
Project: project,
|
|
TableName: project.Name,
|
|
AttributeGroup: attributeGroup,
|
|
AttributeName: attributeName,
|
|
AttributeType: attributeType,
|
|
}, nil
|
|
}
|
|
|
|
// QueryParameterDataObjectValue returns token7 from the component row or from
|
|
// a dynamic parameter row identified during token validation.
|
|
func QueryParameterDataObjectValue(ctx context.Context, db *gorm.DB, parameter *ParameterDataObject) (any, error) {
|
|
if parameter == nil {
|
|
return nil, fmt.Errorf("parameter data object is nil")
|
|
}
|
|
|
|
var record map[string]any
|
|
query := db.WithContext(ctx).Table(parameter.TableName)
|
|
if parameter.AttributeGroup == "component" {
|
|
query = query.Where("tag = ?", parameter.Component.Tag)
|
|
} else {
|
|
query = query.Where("global_uuid = ? AND attribute_group = ?", parameter.Component.GlobalUUID, parameter.AttributeGroup)
|
|
}
|
|
result := query.Take(&record)
|
|
if result.Error != nil {
|
|
return nil, fmt.Errorf("query parameter value from table %q: %w", parameter.TableName, result.Error)
|
|
}
|
|
|
|
value, ok := record[parameter.AttributeName]
|
|
if !ok {
|
|
return nil, fmt.Errorf("parameter column %q is missing from table %q result", parameter.AttributeName, parameter.TableName)
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
// QueryParameterAttributeDescription returns the display name registered for
|
|
// token7 in basic.attribute.
|
|
func QueryParameterAttributeDescription(ctx context.Context, db *gorm.DB, attributeName string) (string, error) {
|
|
var rows []struct {
|
|
Description string `gorm:"column:attribute_name"`
|
|
}
|
|
if err := db.WithContext(ctx).
|
|
Raw(modelsql.ParameterAttributeDescription, attributeName).
|
|
Scan(&rows).Error; err != nil {
|
|
return "", fmt.Errorf("query parameter description for attribute %q: %w", attributeName, err)
|
|
}
|
|
|
|
switch len(rows) {
|
|
case 0:
|
|
return "", fmt.Errorf("parameter description not found for attribute %q", attributeName)
|
|
case 1:
|
|
return rows[0].Description, nil
|
|
default:
|
|
return "", fmt.Errorf("ambiguous parameter description for attribute %q", attributeName)
|
|
}
|
|
}
|
|
|
|
func queryParameterAttributeType(ctx context.Context, db *gorm.DB, tableName, attributeName, token string) (string, error) {
|
|
var attributeType string
|
|
result := db.WithContext(ctx).
|
|
Raw(modelsql.ParameterAttributeColumnType, tableName, attributeName).
|
|
Scan(&attributeType)
|
|
if result.Error != nil {
|
|
return "", fmt.Errorf("validate attribute column for parameter token %q: %w", token, result.Error)
|
|
}
|
|
if result.RowsAffected == 0 || attributeType == "" {
|
|
return "", fmt.Errorf("%w: column %q does not exist in parameter table %q", common.ErrParameterTokenNotFound, attributeName, tableName)
|
|
}
|
|
return strings.ToUpper(attributeType), nil
|
|
}
|
|
|
|
func buildParameterComponentQuery(token string) (string, []any, []string, error) {
|
|
dataObjectType, err := model.ClassifyDataObjectToken(token)
|
|
if err != nil {
|
|
return "", nil, nil, fmt.Errorf("%w %q: %v", common.ErrInvalidParameterToken, token, err)
|
|
}
|
|
if dataObjectType != constants.DataObjectTypeParameter {
|
|
return "", nil, nil, fmt.Errorf("%w %q: token does not identify a parameter", common.ErrInvalidParameterToken, token)
|
|
}
|
|
|
|
parts := strings.Split(token, ".")
|
|
var where string
|
|
var args []any
|
|
switch len(parts) {
|
|
case 7:
|
|
where = modelsql.ParameterSevenPartTokenWhere
|
|
args = []any{parts[0], parts[1], parts[2], parts[3], parts[4]}
|
|
case 4:
|
|
where = modelsql.ParameterFourPartTokenWhere
|
|
args = []any{parts[0], parts[1]}
|
|
default:
|
|
return "", nil, nil, fmt.Errorf("%w %q: expected 4 or 7 segments", common.ErrInvalidParameterToken, token)
|
|
}
|
|
|
|
query := compactParameterSQL(strings.Join([]string{
|
|
modelsql.ParameterComponentQueryBase,
|
|
where,
|
|
modelsql.ParameterLimitTwo,
|
|
}, "\n"))
|
|
return query, args, parts, nil
|
|
}
|
|
|
|
func compactParameterSQL(statement string) string {
|
|
return strings.Join(strings.Fields(statement), " ")
|
|
}
|