Add ExtendedAttributes config toggle for system+user attributes
When GalaxyRepository.ExtendedAttributes is true, uses the extended attributes query that includes both primitive (system) and dynamic (user-defined) attributes. Default is false (dynamic only, preserving existing behavior). Extended mode returns ~564 attributes vs ~48. Adds PrimitiveName and AttributeSource fields to GalaxyAttributeInfo. Includes 5 new unit tests and 6 new integration tests covering both standard and extended attribute modes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -92,6 +92,112 @@ WHERE td.category_id IN (1, 3, 4, 10, 11, 13, 17, 24, 26)
|
||||
AND da.mx_attribute_category IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24)
|
||||
ORDER BY g.tag_name, da.attribute_name";
|
||||
|
||||
private const string ExtendedAttributesSql = @"
|
||||
;WITH template_chain AS (
|
||||
SELECT g.gobject_id, g.derived_from_gobject_id, 0 AS depth
|
||||
FROM gobject g
|
||||
WHERE g.is_template = 0
|
||||
UNION ALL
|
||||
SELECT tc.gobject_id, t.derived_from_gobject_id, tc.depth + 1
|
||||
FROM template_chain tc
|
||||
INNER JOIN gobject t ON t.gobject_id = tc.derived_from_gobject_id
|
||||
WHERE tc.derived_from_gobject_id <> 0 AND tc.depth < 10
|
||||
)
|
||||
SELECT DISTINCT
|
||||
gobject_id,
|
||||
tag_name,
|
||||
primitive_name,
|
||||
attribute_name,
|
||||
full_tag_reference,
|
||||
mx_data_type,
|
||||
data_type_name,
|
||||
is_array,
|
||||
array_dimension,
|
||||
mx_attribute_category,
|
||||
security_classification,
|
||||
attribute_source
|
||||
FROM (
|
||||
SELECT
|
||||
g.gobject_id,
|
||||
g.tag_name,
|
||||
pi.primitive_name,
|
||||
ad.attribute_name,
|
||||
CASE WHEN pi.primitive_name = ''
|
||||
THEN g.tag_name + '.' + ad.attribute_name
|
||||
ELSE g.tag_name + '.' + pi.primitive_name + '.' + ad.attribute_name
|
||||
END + CASE WHEN ad.is_array = 1 THEN '[]' ELSE '' END
|
||||
AS full_tag_reference,
|
||||
ad.mx_data_type,
|
||||
dt.description AS data_type_name,
|
||||
ad.is_array,
|
||||
CASE WHEN ad.is_array = 1
|
||||
THEN CONVERT(int, CONVERT(varbinary(2),
|
||||
SUBSTRING(ad.mx_value, 15, 2) + SUBSTRING(ad.mx_value, 13, 2), 2))
|
||||
ELSE NULL
|
||||
END AS array_dimension,
|
||||
ad.mx_attribute_category,
|
||||
ad.security_classification,
|
||||
'primitive' AS attribute_source
|
||||
FROM gobject g
|
||||
INNER JOIN instance i
|
||||
ON i.gobject_id = g.gobject_id
|
||||
INNER JOIN template_definition td
|
||||
ON td.template_definition_id = g.template_definition_id
|
||||
AND td.runtime_clsid <> '{00000000-0000-0000-0000-000000000000}'
|
||||
INNER JOIN package p
|
||||
ON p.package_id = g.checked_in_package_id
|
||||
INNER JOIN primitive_instance pi
|
||||
ON pi.package_id = p.package_id
|
||||
AND pi.property_bitmask & 0x10 <> 0x10
|
||||
INNER JOIN attribute_definition ad
|
||||
ON ad.primitive_definition_id = pi.primitive_definition_id
|
||||
AND ad.attribute_name NOT LIKE '[_]%'
|
||||
AND ad.mx_attribute_category IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24)
|
||||
LEFT JOIN data_type dt
|
||||
ON dt.mx_data_type = ad.mx_data_type
|
||||
WHERE td.category_id IN (1, 3, 4, 10, 11, 13, 17, 24, 26)
|
||||
AND g.is_template = 0
|
||||
AND g.deployed_package_id <> 0
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
g.gobject_id,
|
||||
g.tag_name,
|
||||
'' AS primitive_name,
|
||||
da.attribute_name,
|
||||
g.tag_name + '.' + da.attribute_name
|
||||
+ CASE WHEN da.is_array = 1 THEN '[]' ELSE '' END
|
||||
AS full_tag_reference,
|
||||
da.mx_data_type,
|
||||
dt.description AS data_type_name,
|
||||
da.is_array,
|
||||
CASE WHEN da.is_array = 1
|
||||
THEN CONVERT(int, CONVERT(varbinary(2),
|
||||
SUBSTRING(da.mx_value, 15, 2) + SUBSTRING(da.mx_value, 13, 2), 2))
|
||||
ELSE NULL
|
||||
END AS array_dimension,
|
||||
da.mx_attribute_category,
|
||||
da.security_classification,
|
||||
'dynamic' AS attribute_source
|
||||
FROM template_chain tc
|
||||
INNER JOIN dynamic_attribute da
|
||||
ON da.gobject_id = tc.derived_from_gobject_id
|
||||
INNER JOIN gobject g
|
||||
ON g.gobject_id = tc.gobject_id
|
||||
INNER JOIN template_definition td
|
||||
ON td.template_definition_id = g.template_definition_id
|
||||
LEFT JOIN data_type dt
|
||||
ON dt.mx_data_type = da.mx_data_type
|
||||
WHERE td.category_id IN (1, 3, 4, 10, 11, 13, 17, 24, 26)
|
||||
AND g.is_template = 0
|
||||
AND g.deployed_package_id <> 0
|
||||
AND da.attribute_name NOT LIKE '[_]%'
|
||||
AND da.attribute_name NOT LIKE '%.Description'
|
||||
AND da.mx_attribute_category IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24)
|
||||
) all_attributes
|
||||
ORDER BY tag_name, primitive_name, attribute_name";
|
||||
|
||||
private const string ChangeDetectionSql = "SELECT time_of_last_deploy FROM galaxy";
|
||||
|
||||
private const string TestConnectionSql = "SELECT 1";
|
||||
@@ -137,32 +243,67 @@ ORDER BY g.tag_name, da.attribute_name";
|
||||
public async Task<List<GalaxyAttributeInfo>> GetAttributesAsync(CancellationToken ct = default)
|
||||
{
|
||||
var results = new List<GalaxyAttributeInfo>();
|
||||
var extended = _config.ExtendedAttributes;
|
||||
var sql = extended ? ExtendedAttributesSql : AttributesSql;
|
||||
|
||||
using var conn = new SqlConnection(_config.ConnectionString);
|
||||
await conn.OpenAsync(ct);
|
||||
|
||||
using var cmd = new SqlCommand(AttributesSql, conn) { CommandTimeout = _config.CommandTimeoutSeconds };
|
||||
using var cmd = new SqlCommand(sql, conn) { CommandTimeout = _config.CommandTimeoutSeconds };
|
||||
using var reader = await cmd.ExecuteReaderAsync(ct);
|
||||
|
||||
while (await reader.ReadAsync(ct))
|
||||
{
|
||||
results.Add(new GalaxyAttributeInfo
|
||||
{
|
||||
GobjectId = reader.GetInt32(0),
|
||||
TagName = reader.GetString(1),
|
||||
AttributeName = reader.GetString(2),
|
||||
FullTagReference = reader.GetString(3),
|
||||
MxDataType = Convert.ToInt32(reader.GetValue(4)),
|
||||
DataTypeName = reader.IsDBNull(5) ? "" : reader.GetString(5),
|
||||
IsArray = Convert.ToBoolean(reader.GetValue(6)),
|
||||
ArrayDimension = reader.IsDBNull(7) ? null : (int?)Convert.ToInt32(reader.GetValue(7))
|
||||
});
|
||||
results.Add(extended ? ReadExtendedAttribute(reader) : ReadStandardAttribute(reader));
|
||||
}
|
||||
|
||||
Log.Information("GetAttributesAsync returned {Count} attributes", results.Count);
|
||||
Log.Information("GetAttributesAsync returned {Count} attributes (extended={Extended})", results.Count, extended);
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a row from the standard attributes query (10 columns).
|
||||
/// Columns: gobject_id, tag_name, attribute_name, full_tag_reference, mx_data_type,
|
||||
/// data_type_name, is_array, array_dimension, mx_attribute_category, security_classification
|
||||
/// </summary>
|
||||
private static GalaxyAttributeInfo ReadStandardAttribute(SqlDataReader reader)
|
||||
{
|
||||
return new GalaxyAttributeInfo
|
||||
{
|
||||
GobjectId = Convert.ToInt32(reader.GetValue(0)),
|
||||
TagName = reader.GetString(1),
|
||||
AttributeName = reader.GetString(2),
|
||||
FullTagReference = reader.GetString(3),
|
||||
MxDataType = Convert.ToInt32(reader.GetValue(4)),
|
||||
DataTypeName = reader.IsDBNull(5) ? "" : reader.GetString(5),
|
||||
IsArray = Convert.ToBoolean(reader.GetValue(6)),
|
||||
ArrayDimension = reader.IsDBNull(7) ? null : (int?)Convert.ToInt32(reader.GetValue(7))
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a row from the extended attributes query (12 columns).
|
||||
/// Columns: gobject_id, tag_name, primitive_name, attribute_name, full_tag_reference,
|
||||
/// mx_data_type, data_type_name, is_array, array_dimension,
|
||||
/// mx_attribute_category, security_classification, attribute_source
|
||||
/// </summary>
|
||||
private static GalaxyAttributeInfo ReadExtendedAttribute(SqlDataReader reader)
|
||||
{
|
||||
return new GalaxyAttributeInfo
|
||||
{
|
||||
GobjectId = Convert.ToInt32(reader.GetValue(0)),
|
||||
TagName = reader.GetString(1),
|
||||
PrimitiveName = reader.IsDBNull(2) ? "" : reader.GetString(2),
|
||||
AttributeName = reader.GetString(3),
|
||||
FullTagReference = reader.GetString(4),
|
||||
MxDataType = Convert.ToInt32(reader.GetValue(5)),
|
||||
DataTypeName = reader.IsDBNull(6) ? "" : reader.GetString(6),
|
||||
IsArray = Convert.ToBoolean(reader.GetValue(7)),
|
||||
ArrayDimension = reader.IsDBNull(8) ? null : (int?)Convert.ToInt32(reader.GetValue(8)),
|
||||
AttributeSource = reader.IsDBNull(11) ? "" : reader.GetString(11)
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<DateTime?> GetLastDeployTimeAsync(CancellationToken ct = default)
|
||||
{
|
||||
using var conn = new SqlConnection(_config.ConnectionString);
|
||||
|
||||
Reference in New Issue
Block a user