-- Galaxy Object User-Defined Attributes/Tags for OPC UA Server -- Returns user-defined (dynamic) attributes for deployed automation objects. -- These are the attributes defined on templates and inherited by instances -- via the deployed package derivation chain (e.g., MachineID, MoveInFlag). -- -- Use full_tag_reference for read/write operations against the runtime. -- Join with hierarchy.sql results on gobject_id to place attributes in the OPC UA browse tree. -- -- For system/primitive attributes as well, see attributes_extended.sql. -- -- Only attributes that existed at deploy time are included. The CTE walks -- package.derived_from_package_id starting from each instance's deployed_package_id, -- then joins dynamic_attribute on package_id to filter out post-deploy additions. -- When the same attribute appears at multiple levels, only the shallowest -- (most-derived) version is kept. -- -- Historization detection: an attribute is historized when a primitive_instance -- with a matching name exists in the deployed package chain and its primitive_definition -- has primitive_name = 'HistoryExtension'. -- -- Array dimensions are extracted from the mx_value hex string on the template's -- dynamic_attribute row (bytes 5-6, little-endian uint16 at hex positions 13-16). -- -- Data types (mx_data_type): -- 1 = Boolean, 2 = Integer (Int32), 3 = Float (Single), 4 = Double, -- 5 = String, 6 = Time (DateTime), 7 = ElapsedTime (TimeSpan), -- 8 = (reference), 13 = (enumeration), 14 = (custom), 15 = InternationalizedString, 16 = (custom) ;WITH deployed_package_chain AS ( -- Start from each deployed instance's deployed package SELECT g.gobject_id, p.package_id, p.derived_from_package_id, 0 AS depth FROM gobject g INNER JOIN package p ON p.package_id = g.deployed_package_id WHERE g.is_template = 0 AND g.deployed_package_id <> 0 UNION ALL -- Walk up the package derivation chain SELECT dpc.gobject_id, p.package_id, p.derived_from_package_id, dpc.depth + 1 FROM deployed_package_chain dpc INNER JOIN package p ON p.package_id = dpc.derived_from_package_id WHERE dpc.derived_from_package_id <> 0 AND dpc.depth < 10 ) SELECT gobject_id, tag_name, attribute_name, full_tag_reference, mx_data_type, data_type_name, is_array, array_dimension, mx_attribute_category, security_classification, is_historized, is_alarm FROM ( SELECT dpc.gobject_id, g.tag_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, CASE WHEN EXISTS ( SELECT 1 FROM deployed_package_chain dpc2 INNER JOIN primitive_instance pi ON pi.package_id = dpc2.package_id AND pi.primitive_name = da.attribute_name INNER JOIN primitive_definition pd ON pd.primitive_definition_id = pi.primitive_definition_id AND pd.primitive_name = 'HistoryExtension' WHERE dpc2.gobject_id = dpc.gobject_id ) THEN 1 ELSE 0 END AS is_historized, CASE WHEN EXISTS ( SELECT 1 FROM deployed_package_chain dpc2 INNER JOIN primitive_instance pi ON pi.package_id = dpc2.package_id AND pi.primitive_name = da.attribute_name INNER JOIN primitive_definition pd ON pd.primitive_definition_id = pi.primitive_definition_id AND pd.primitive_name = 'AlarmExtension' WHERE dpc2.gobject_id = dpc.gobject_id ) THEN 1 ELSE 0 END AS is_alarm, ROW_NUMBER() OVER ( PARTITION BY dpc.gobject_id, da.attribute_name ORDER BY dpc.depth ) AS rn FROM deployed_package_chain dpc INNER JOIN dynamic_attribute da ON da.package_id = dpc.package_id INNER JOIN gobject g ON g.gobject_id = dpc.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 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) ) ranked WHERE rn = 1 ORDER BY tag_name, attribute_name;