2026-07-16 16:04:39 +08:00
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 ]
2026-07-23 14:30:49 +08:00
if ! validParameterTableName ( project . Name ) {
2026-07-16 16:04:39 +08:00
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
}
2026-07-20 15:46:31 +08:00
// UpdateParameterDataObjectValue writes token7 to the dynamic parameter row
// resolved from a data-object token. Component-table attributes are not
// supported by the data-object update API.
func UpdateParameterDataObjectValue ( ctx context . Context , db * gorm . DB , parameter * ParameterDataObject , value any ) error {
if parameter == nil {
return fmt . Errorf ( "parameter data object is nil" )
}
if parameter . AttributeGroup == "component" {
return fmt . Errorf ( "component data-object updates are not supported" )
}
2026-07-23 14:30:49 +08:00
if ! validParameterTableName ( parameter . TableName ) {
2026-07-20 15:46:31 +08:00
return fmt . Errorf ( "invalid parameter table name %q" , parameter . TableName )
}
result := db . WithContext ( ctx ) .
Table ( parameter . TableName ) .
Where ( "global_uuid = ? AND attribute_group = ?" , parameter . Component . GlobalUUID , parameter . AttributeGroup ) .
Update ( parameter . AttributeName , value )
if result . Error != nil {
return fmt . Errorf ( "update parameter %q in table %q: %w" , parameter . AttributeName , parameter . TableName , result . Error )
}
if result . RowsAffected == 0 {
return fmt . Errorf ( "update parameter %q in table %q affected no rows" , parameter . AttributeName , parameter . TableName )
}
return nil
}
2026-07-23 14:30:49 +08:00
func validParameterTableName ( tableName string ) bool {
return parameterTableNamePattern . MatchString ( tableName ) && constants . IsSupportedParameterTableName ( tableName )
}
2026-07-16 16:04:39 +08:00
// 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 ) , " " )
}