From 58d8e223b96361ede36785c47f52637b48c3e831 Mon Sep 17 00:00:00 2001 From: Daniel Thom Date: Thu, 11 Jun 2026 15:46:36 -0600 Subject: [PATCH 1/4] Replace component & supplemental-attribute UUIDs with integer IDs Components and supplemental attributes are now identified by small integer IDs assigned by SystemData when attached, instead of random UUIDs. SystemData tracks two independent ID streams via next_component_id and next_supplemental_attribute_id, each starting at 1; a component and an attribute may therefore share a numeric ID. IDs are preserved across serialization/deserialization. - InfrastructureSystemsInternal gains an id::Int field (UNASSIGNED_ID until attached); identity goes through get_id/set_id!. Time series keep their UUIDs. - SystemData.component_uuids -> component_ids::Dict{Int}; subsystems -> Set{Int}; ComponentUUIDs -> ComponentIDs. - Supplemental attribute associations table uses component_id/attribute_id INTEGER columns. - Time series metadata store uses owner_id INTEGER. Because the two ID streams can collide, owner-specific queries (has_metadata, owner where-clause, replace_component_id!) also filter by owner_category. - Tests updated; new tests cover ID round-trip preservation, both counters surviving serialization, and independent ID streams. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/InfrastructureSystems.jl | 11 +- src/component.jl | 30 +-- src/component_ids.jl | 39 +++ src/component_uuids.jl | 33 --- src/components.jl | 8 +- src/internal.jl | 44 +++- src/iterators.jl | 10 +- src/subsystems.jl | 32 +-- src/supplemental_attribute_associations.jl | 275 +++++++++++---------- src/supplemental_attribute_manager.jl | 46 ++-- src/system_data.jl | 211 +++++++++++----- src/time_series_metadata_store.jl | 70 +++--- test/test_component_container.jl | 6 +- test/test_internal.jl | 10 +- test/test_serialization.jl | 35 +++ test/test_supplemental_attributes.jl | 61 ++--- test/test_system_data.jl | 70 ++++-- test/test_time_series.jl | 22 +- 18 files changed, 616 insertions(+), 397 deletions(-) create mode 100644 src/component_ids.jl delete mode 100644 src/component_uuids.jl diff --git a/src/InfrastructureSystems.jl b/src/InfrastructureSystems.jl index 38eb49737..93916330d 100644 --- a/src/InfrastructureSystems.jl +++ b/src/InfrastructureSystems.jl @@ -105,13 +105,10 @@ Required interface functions for subtypes: Optional interface functions: - - get_uuid() + - [`get_id`](@ref) + - [`supports_time_series`](@ref) -Subtypes may contain time series. Which requires - - - `supports_time_series(::SupplementalAttribute)` - -All subtypes must include an instance of ComponentUUIDs in order to track +All subtypes must include an instance of [`ComponentIDs`](@ref) in order to track components attached to each attribute. """ abstract type SupplementalAttribute <: InfrastructureSystemsType end @@ -182,7 +179,7 @@ include("static_time_series.jl") include("time_series_parameters.jl") include("containers.jl") include("component_container.jl") -include("component_uuids.jl") +include("component_ids.jl") include("geographic_supplemental_attribute.jl") include("generated/includes.jl") include("time_series_parser.jl") diff --git a/src/component.jl b/src/component.jl index 092b543be..0fdbeef02 100644 --- a/src/component.jl +++ b/src/component.jl @@ -1,20 +1,22 @@ """ -Assign a new UUID to the component. +Assign a new integer id to the component, drawn from the system counter, and update any +references to its old id in the time series metadata store and supplemental attribute +associations. """ -function assign_new_uuid_internal!(component::InfrastructureSystemsComponent) - old_uuid = get_uuid(component) - new_uuid = make_uuid() +function assign_new_id_internal!(data, component::InfrastructureSystemsComponent) + old_id = get_id(component) + new_id = get_next_component_id!(data) mgr = get_time_series_manager(component) if !isnothing(mgr) - replace_component_uuid!(mgr.metadata_store, old_uuid, new_uuid) + replace_component_id!(mgr.metadata_store, old_id, new_id) end associations = _get_supplemental_attribute_associations(component) if !isnothing(associations) - replace_component_uuid!(associations, old_uuid, new_uuid) + replace_component_id!(associations, old_id, new_id) end - set_uuid!(get_internal(component), new_uuid) + set_id!(get_internal(component), new_id) return end @@ -47,8 +49,8 @@ end function clear_supplemental_attributes!(component::InfrastructureSystemsComponent) mgr = _get_supplemental_attributes_manager(component) isnothing(mgr) && return - for uuid in list_associated_supplemental_attribute_uuids(mgr.associations, component) - attribute = get_supplemental_attribute(mgr, uuid) + for id in list_associated_supplemental_attribute_ids(mgr.associations, component) + attribute = get_supplemental_attribute(mgr, id) remove_supplemental_attribute!(mgr, component, attribute) end @debug "Cleared attributes in $(summary(component))." @@ -85,7 +87,7 @@ function _get_supplemental_attributes( isnothing(mgr) && return supplemental_attribute_type[] return supplemental_attribute_type[ get_supplemental_attribute(mgr, x) for - x in list_associated_supplemental_attribute_uuids( + x in list_associated_supplemental_attribute_ids( mgr.associations, component, supplemental_attribute_type, @@ -116,12 +118,12 @@ function _get_supplemental_attributes( mgr = _get_supplemental_attributes_manager(component) isnothing(mgr) && return [supplemental_attribute_type] attrs = Vector{supplemental_attribute_type}() - for uuid in list_associated_supplemental_attribute_uuids( + for id in list_associated_supplemental_attribute_ids( mgr.associations, component, supplemental_attribute_type, ) - attribute = get_supplemental_attribute(mgr, uuid) + attribute = get_supplemental_attribute(mgr, id) if filter_func(attribute) push!(attrs, attribute) end @@ -132,12 +134,12 @@ end function get_supplemental_attribute( component::InfrastructureSystemsComponent, - uuid::Base.UUID, + id::Int, ) mgr = _get_supplemental_attributes_manager(component) isnothing(mgr) && error("$(summary(component)) does not have supplemental attributes") - return get_supplemental_attribute(mgr, uuid) + return get_supplemental_attribute(mgr, id) end function _get_supplemental_attributes_manager(component::InfrastructureSystemsComponent) diff --git a/src/component_ids.jl b/src/component_ids.jl new file mode 100644 index 000000000..9c44de07d --- /dev/null +++ b/src/component_ids.jl @@ -0,0 +1,39 @@ +# This is an abstraction of a Set in order to enable de-serialization of supplemental +# attributes. + +""" +Set-like storage of component integer ids attached to a [`SupplementalAttribute`](@ref). + +Supplemental attribute subtypes include a `ComponentIDs` field so associations can be +tracked and serialized without storing full component references. +""" +struct ComponentIDs <: InfrastructureSystemsType + ids::Set{Int} + + function ComponentIDs(ids = Set{Int}()) + new(ids) + end +end + +Base.copy(x::ComponentIDs) = copy(x.ids) +Base.delete!(x::ComponentIDs, id) = delete!(x.ids, id) +Base.empty!(x::ComponentIDs) = empty!(x.ids) +Base.filter!(f, x::ComponentIDs) = filter!(f, x.ids) +Base.in(x, y::ComponentIDs) = in(x, y.ids) +Base.isempty(x::ComponentIDs) = isempty(x.ids) +Base.iterate(x::ComponentIDs, args...) = iterate(x.ids, args...) +Base.length(x::ComponentIDs) = length(x.ids) +Base.pop!(x::ComponentIDs) = pop!(x.ids) +Base.pop!(x::ComponentIDs, y) = pop!(x.ids, y) +Base.pop!(x::ComponentIDs, y, default) = pop!(x.ids, y, default) +Base.push!(x::ComponentIDs, y) = push!(x.ids, y) +Base.setdiff!(x::ComponentIDs, y::ComponentIDs) = setdiff!(x.ids, y.ids) +Base.sizehint!(x::ComponentIDs, newsz) = sizehint!(x.ids, newsz) + +function deserialize(::Type{ComponentIDs}, data::Dict) + ids = Set{Int}() + for id in data["ids"] + push!(ids, Int(id)) + end + return ComponentIDs(ids) +end diff --git a/src/component_uuids.jl b/src/component_uuids.jl deleted file mode 100644 index fbcfdb54c..000000000 --- a/src/component_uuids.jl +++ /dev/null @@ -1,33 +0,0 @@ -# This is an abstraction of a Set in order to enable de-serialization of supplemental -# attributes. - -struct ComponentUUIDs <: InfrastructureSystemsType - uuids::Set{Base.UUID} - - function ComponentUUIDs(uuids = Set{Base.UUID}()) - new(uuids) - end -end - -Base.copy(x::ComponentUUIDs) = copy(x.uuids) -Base.delete!(x::ComponentUUIDs, uuid) = delete!(x.uuids, uuid) -Base.empty!(x::ComponentUUIDs) = empty!(x.uuids) -Base.filter!(f, x::ComponentUUIDs) = filter!(f, x.uuids) -Base.in(x, y::ComponentUUIDs) = in(x, y.uuids) -Base.isempty(x::ComponentUUIDs) = isempty(x.uuids) -Base.iterate(x::ComponentUUIDs, args...) = iterate(x.uuids, args...) -Base.length(x::ComponentUUIDs) = length(x.uuids) -Base.pop!(x::ComponentUUIDs) = pop!(x.uuids) -Base.pop!(x::ComponentUUIDs, y) = pop!(x.uuids, y) -Base.pop!(x::ComponentUUIDs, y, default) = pop!(x.uuids, y, default) -Base.push!(x::ComponentUUIDs, y) = push!(x.uuids, y) -Base.setdiff!(x::ComponentUUIDs, y::ComponentUUIDs) = setdiff!(x.uuids, y.uuids) -Base.sizehint!(x::ComponentUUIDs, newsz) = sizehint!(x.uuids, newsz) - -function deserialize(::Type{ComponentUUIDs}, data::Dict) - uuids = Set{Base.UUID}() - for uuid in data["uuids"] - push!(uuids, deserialize(Base.UUID, uuid)) - end - return ComponentUUIDs(uuids) -end diff --git a/src/components.jl b/src/components.jl index 1645663ab..6664394c6 100644 --- a/src/components.jl +++ b/src/components.jl @@ -315,18 +315,18 @@ See also: [`iterate_components`](@ref) function get_components( ::Type{T}, components::Components; - component_uuids::Union{Nothing, Set{Base.UUID}} = nothing, + component_ids::Union{Nothing, Set{Int}} = nothing, ) where {T <: InfrastructureSystemsComponent} - return iterate_instances(T, components.data, component_uuids) + return iterate_instances(T, components.data, component_ids) end function get_components( filter_func::Function, ::Type{T}, components::Components; - component_uuids::Union{Nothing, Set{Base.UUID}} = nothing, + component_ids::Union{Nothing, Set{Int}} = nothing, ) where {T <: InfrastructureSystemsComponent} - return iterate_instances(filter_func, T, components.data, component_uuids) + return iterate_instances(filter_func, T, components.data, component_ids) end """ diff --git a/src/internal.jl b/src/internal.jl index 412fbe99f..26bd0586b 100644 --- a/src/internal.jl +++ b/src/internal.jl @@ -28,9 +28,23 @@ deserialize(T::Type{<:SystemUnitsSettings}, val::Dict) = deserialize_struct(T, v end """ -Internal storage common to InfrastructureSystems types. +Sentinel value for the integer `id` of a component or supplemental attribute that has not +yet been attached to a [`SystemData`](@ref). Assigned IDs start at 1. +""" +const UNASSIGNED_ID = 0 + +""" +Internal storage common to [`InfrastructureSystemsType`](@ref)s. + +Components and supplemental attributes are identified by an integer `id` assigned by the +owning [`SystemData`](@ref) when they are attached (see [`get_id`](@ref)); it is +[`UNASSIGNED_ID`](@ref) until then. The `uuid` is retained for time series, whose content +and metadata are still identified by UUID. Each instance also holds optional +[`SharedSystemReferences`](@ref) when attached to a system, optional unit metadata, and an +optional user extension dictionary accessed through [`get_ext`](@ref). """ mutable struct InfrastructureSystemsInternal <: InfrastructureSystemsType + id::Int uuid::Base.UUID shared_system_references::Union{Nothing, SharedSystemReferences} units_info::Union{Nothing, SystemUnitsSettings} @@ -38,21 +52,22 @@ mutable struct InfrastructureSystemsInternal <: InfrastructureSystemsType end """ -Creates InfrastructureSystemsInternal with a new UUID. +Creates InfrastructureSystemsInternal with a new UUID and an unassigned integer id. """ InfrastructureSystemsInternal(; + id = UNASSIGNED_ID, uuid = make_uuid(), shared_system_references = nothing, units_info = nothing, ext = nothing, ) = - InfrastructureSystemsInternal(uuid, shared_system_references, units_info, ext) + InfrastructureSystemsInternal(id, uuid, shared_system_references, units_info, ext) """ Creates InfrastructureSystemsInternal with an existing UUID. """ InfrastructureSystemsInternal(u::Base.UUID) = - InfrastructureSystemsInternal(u, nothing, nothing, nothing) + InfrastructureSystemsInternal(UNASSIGNED_ID, u, nothing, nothing, nothing) """ Return a user-modifiable dictionary to store extra information. @@ -76,6 +91,9 @@ end get_uuid(internal::InfrastructureSystemsInternal) = internal.uuid set_uuid!(internal::InfrastructureSystemsInternal, uuid) = internal.uuid = uuid +get_id(internal::InfrastructureSystemsInternal) = internal.id +set_id!(internal::InfrastructureSystemsInternal, id::Int) = internal.id = id + function set_shared_system_references!( internal::InfrastructureSystemsInternal, refs::Union{Nothing, SharedSystemReferences}, @@ -94,6 +112,22 @@ function get_uuid(obj::InfrastructureSystemsType) return get_internal(obj).uuid end +""" +Gets the integer id of a component or supplemental attribute. Returns [`UNASSIGNED_ID`](@ref) +if the object has not been attached to a [`SystemData`](@ref). +""" +function get_id(obj::InfrastructureSystemsType) + return get_internal(obj).id +end + +""" +Sets the integer id of a component or supplemental attribute. +""" +function set_id!(obj::InfrastructureSystemsType, id::Int) + set_id!(get_internal(obj), id) + return +end + """ Assign a new UUID. """ @@ -139,7 +173,7 @@ function compare_values( ) match = true for name in fieldnames(InfrastructureSystemsInternal) - if name in exclude || (name == :uuid && !compare_uuids) || + if name in exclude || (name in (:uuid, :id) && !compare_uuids) || name == :shared_system_references continue end diff --git a/src/iterators.jl b/src/iterators.jl index e7e23c770..46d9f14e5 100644 --- a/src/iterators.jl +++ b/src/iterators.jl @@ -4,19 +4,19 @@ function iterate_instances( filter_func::Function, ::Type{T}, data::_ContainerTypes, - uuids::Set{Base.UUID}, + ids::Set{Int}, ) where {T <: InfrastructureSystemsType} - func_uuids = x -> get_uuid(x) in uuids - _filter_func = x -> filter_func(x) && func_uuids(x) + func_ids = x -> get_id(x) in ids + _filter_func = x -> filter_func(x) && func_ids(x) return iterate_instances(_filter_func, T, data, nothing) end function iterate_instances( ::Type{T}, data::_ContainerTypes, - uuids::Set{Base.UUID}, + ids::Set{Int}, ) where {T <: InfrastructureSystemsType} - filter_func = x -> get_uuid(x) in uuids + filter_func = x -> get_id(x) in ids return iterate_instances(filter_func, T, data, nothing) end diff --git a/src/subsystems.jl b/src/subsystems.jl index 292e380d5..d0f53b51c 100644 --- a/src/subsystems.jl +++ b/src/subsystems.jl @@ -6,7 +6,7 @@ function add_subsystem!(data::SystemData, subsystem_name::AbstractString) throw(ArgumentError("There is already a subsystem with name = $subsystem_name.")) end - data.subsystems[subsystem_name] = Set{Base.UUID}() + data.subsystems[subsystem_name] = Set{Int}() @debug "Added subystem $subsystem_name" _group = LOG_GROUP_SYSTEM return end @@ -51,8 +51,8 @@ function add_component_to_subsystem!( _throw_if_not_stored(data, subsystem_name) container = data.subsystems[subsystem_name] - uuid = get_uuid(component) - if uuid in container + id = get_id(component) + if id in container throw( ArgumentError( "Subsystem $subsystem_name already contains $(summary(component))", @@ -60,7 +60,7 @@ function add_component_to_subsystem!( ) end - push!(container, uuid) + push!(container, id) @debug "Added $(summary(component)) to subystem $subsystem_name" _group = LOG_GROUP_SYSTEM return @@ -76,7 +76,7 @@ function get_subsystem_components(data::SystemData, subsystem_name::AbstractStri return (get_component(data, x) for x in data.subsystems[subsystem_name]) end -function get_component_uuids(data::SystemData, subsystem_name::AbstractString) +function get_component_ids(data::SystemData, subsystem_name::AbstractString) _throw_if_not_stored(data, subsystem_name) return data.subsystems[subsystem_name] end @@ -97,7 +97,7 @@ function remove_component_from_subsystem!( ) end - pop!(data.subsystems[subsystem_name], get_uuid(component)) + pop!(data.subsystems[subsystem_name], get_id(component)) @debug "Removed $(summary(component)) from subystem $subsystem_name" _group = LOG_GROUP_SYSTEM return @@ -107,9 +107,9 @@ function remove_component_from_subsystems!( data::SystemData, component::InfrastructureSystemsComponent, ) - uuid = get_uuid(component) - for (subsystem_name, uuids) in data.subsystems - pop!(uuids, uuid, nothing) + id = get_id(component) + for (subsystem_name, ids) in data.subsystems + pop!(ids, id, nothing) @debug "Removed $(summary(component)) from subystem $subsystem_name" _group = LOG_GROUP_SYSTEM end @@ -125,7 +125,7 @@ function has_component( component::InfrastructureSystemsComponent, ) _throw_if_not_stored(data, subsystem_name) - return get_uuid(component) in data.subsystems[subsystem_name] + return get_id(component) in data.subsystems[subsystem_name] end """ @@ -135,8 +135,8 @@ function get_assigned_subsystems( data::SystemData, component::InfrastructureSystemsComponent, ) - uuid = get_uuid(component) - return [k for (k, v) in data.subsystems if uuid in v] + id = get_id(component) + return [k for (k, v) in data.subsystems if id in v] end """ @@ -146,9 +146,9 @@ function is_assigned_to_subsystem( data::SystemData, component::InfrastructureSystemsComponent, ) - uuid = get_uuid(component) - for uuids in values(data.subsystems) - if uuid in uuids + id = get_id(component) + for ids in values(data.subsystems) + if id in ids return true end end @@ -165,7 +165,7 @@ function is_assigned_to_subsystem( subsystem_name::AbstractString, ) _throw_if_not_stored(data, subsystem_name) - return get_uuid(component) in data.subsystems[subsystem_name] + return get_id(component) in data.subsystems[subsystem_name] end function _throw_if_not_stored(data::SystemData, subsystem_name::AbstractString) diff --git a/src/supplemental_attribute_associations.jl b/src/supplemental_attribute_associations.jl index 3321c1f72..384e647cc 100644 --- a/src/supplemental_attribute_associations.jl +++ b/src/supplemental_attribute_associations.jl @@ -28,6 +28,17 @@ mutable struct SupplementalAttributeAssociations # If you add any fields, ensure they are managed in deepcopy_internal below. end +""" +SQLite-backed store linking [`SupplementalAttribute`](@ref)s to +[`InfrastructureSystemsComponent`](@ref)s. + +Owned by [`SupplementalAttributeManager`](@ref). Associations are serialized into the +system JSON file rather than a separate sidecar database. + +See also: [`add_supplemental_attribute!`](@ref), [`list_associated_component_ids`](@ref) +""" +SupplementalAttributeAssociations + """ Construct a new SupplementalAttributeAssociations with an in-memory database. """ @@ -47,9 +58,9 @@ function _create_attribute_associations_table!( associations::SupplementalAttributeAssociations, ) schema = [ - "attribute_uuid TEXT NOT NULL", + "attribute_id INTEGER NOT NULL", "attribute_type TEXT NOT NULL", - "component_uuid TEXT NOT NULL", + "component_id INTEGER NOT NULL", "component_type TEXT NOT NULL", ] schema_text = join(schema, ",") @@ -68,8 +79,8 @@ function _create_indexes!(associations::SupplementalAttributeAssociations) SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME, "by_attribute", [ - "attribute_uuid", - "component_uuid", + "attribute_id", + "component_id", "component_type", ]; unique = false, @@ -79,8 +90,8 @@ function _create_indexes!(associations::SupplementalAttributeAssociations) SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME, "by_component", [ - "component_uuid", - "attribute_uuid", + "component_id", + "attribute_id", "attribute_type", ]; unique = false, @@ -118,9 +129,9 @@ function add_association!( ) TimerOutputs.@timeit_debug SYSTEM_TIMERS "add supplemental attribute association" begin row = ( - string(get_uuid(attribute)), + get_id(attribute), string(nameof(typeof(attribute))), - string(get_uuid(component)), + get_id(component), string(nameof(typeof(component))), ) placeholder = chop(repeat("?,", length(row))) @@ -184,7 +195,7 @@ function get_attribute_summary_table(associations::SupplementalAttributeAssociat end const _QUERY_GET_NUM_ATTRIBUTES = """ - SELECT COUNT(DISTINCT attribute_uuid) AS count + SELECT COUNT(DISTINCT attribute_id) AS count FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME """ @@ -196,7 +207,7 @@ function get_num_attributes(associations::SupplementalAttributeAssociations) end const _QUERY_GET_NUM_COMPONENTS_WITH_ATTRIBUTES = """ - SELECT COUNT(DISTINCT component_uuid) AS count + SELECT COUNT(DISTINCT component_id) AS count FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME """ @@ -219,9 +230,9 @@ function optimize_database!(associations::SupplementalAttributeAssociations) end const _QUERY_HAS_ASSOCIATION_BY_ATTRIBUTE = """ - SELECT attribute_uuid + SELECT attribute_id FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME - WHERE attribute_uuid = ? + WHERE attribute_id = ? LIMIT 1 """ @@ -233,7 +244,7 @@ function has_association( attribute::SupplementalAttribute, ) # Note: Unlike the other has_association methods, this is not covered by an index. - params = (string(get_uuid(attribute)),) + params = (get_id(attribute),) return !isempty( Tables.rowtable( _execute_cached(associations, _QUERY_HAS_ASSOCIATION_BY_ATTRIBUTE, params), @@ -242,9 +253,9 @@ function has_association( end const _QUERY_HAS_ASSOCIATION_BY_COMPONENT_ATTRIBUTE = """ - SELECT attribute_uuid + SELECT attribute_id FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME - WHERE attribute_uuid = ? AND component_uuid = ? + WHERE attribute_id = ? AND component_id = ? LIMIT 1 """ function has_association( @@ -252,9 +263,7 @@ function has_association( component::InfrastructureSystemsComponent, attribute::SupplementalAttribute, ) - a_uuid = get_uuid(attribute) - c_uuid = get_uuid(component) - params = (string(a_uuid), string(c_uuid)) + params = (get_id(attribute), get_id(component)) return !isempty( _execute_cached( associations, @@ -265,16 +274,16 @@ function has_association( end const _QUERY_HAS_ASSOCIATION_BY_COMPONENT = """ - SELECT attribute_uuid + SELECT attribute_id FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME - WHERE component_uuid = ? + WHERE component_id = ? LIMIT 1 """ function has_association( associations::SupplementalAttributeAssociations, component::InfrastructureSystemsComponent, ) - params = (string(get_uuid(component)),) + params = (get_id(component),) return !isempty( Tables.rowtable( _execute_cached(associations, _QUERY_HAS_ASSOCIATION_BY_COMPONENT, params), @@ -283,9 +292,9 @@ function has_association( end const _QUERY_HAS_ASSOCIATION_BY_COMP_ATTR_TYPE = """ - SELECT attribute_uuid + SELECT attribute_id FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME - WHERE component_uuid = ? AND attribute_type = ? + WHERE component_id = ? AND attribute_type = ? LIMIT 1 """ function has_association( @@ -293,7 +302,7 @@ function has_association( component::InfrastructureSystemsComponent, attribute_type::Type{<:SupplementalAttribute}, ) - params = (string(get_uuid(component)), string(nameof(attribute_type))) + params = (get_id(component), string(nameof(attribute_type))) return !isempty( Tables.rowtable( _execute_cached(associations, _QUERY_HAS_ASSOCIATION_BY_COMP_ATTR_TYPE, params), @@ -301,165 +310,165 @@ function has_association( ) end -# component UUIDS from attribute -const _QUERY_LIST_ASSOCIATED_COMP_UUIDS = """ - SELECT DISTINCT component_uuid +# component ids from attribute +const _QUERY_LIST_ASSOCIATED_COMP_IDS = """ + SELECT DISTINCT component_id FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME - WHERE attribute_uuid = ? + WHERE attribute_id = ? """ """ -Return the distinct component UUIDs associated with the attribute. +Return the distinct component ids associated with the attribute. """ -function list_associated_component_uuids( +function list_associated_component_ids( associations::SupplementalAttributeAssociations, attribute::SupplementalAttribute, ::Nothing, ) - params = (string(get_uuid(attribute)),) + params = (get_id(attribute),) table = Tables.columntable( - _execute_cached(associations, _QUERY_LIST_ASSOCIATED_COMP_UUIDS, params), + _execute_cached(associations, _QUERY_LIST_ASSOCIATED_COMP_IDS, params), ) - return Base.UUID.(table.component_uuid) + return collect(Int, table.component_id) end -# attribute UUIDS from component -const _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_UUIDS = """ - SELECT DISTINCT attribute_uuid +# attribute ids from component +const _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_IDS = """ + SELECT DISTINCT attribute_id FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME - WHERE component_uuid = ? + WHERE component_id = ? """ """ -Return the distinct attribute UUIDs associated with the component. +Return the distinct attribute ids associated with the component. """ -function list_associated_supplemental_attribute_uuids( +function list_associated_supplemental_attribute_ids( associations::SupplementalAttributeAssociations, component::InfrastructureSystemsComponent, ::Nothing, ) - params = (string(get_uuid(component)),) + params = (get_id(component),) table = Tables.columntable( _execute_cached( associations, - _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_UUIDS, + _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_IDS, params, ), ) - return Base.UUID.(table.attribute_uuid) + return collect(Int, table.attribute_id) end -list_associated_supplemental_attribute_uuids( +list_associated_supplemental_attribute_ids( associations::SupplementalAttributeAssociations, component::InfrastructureSystemsComponent, -) = list_associated_supplemental_attribute_uuids(associations, component, nothing) +) = list_associated_supplemental_attribute_ids(associations, component, nothing) -# component UUIDS from attribute plus component type -const _QUERY_LIST_ASSOCIATED_COMP_UUIDS_BY_C_TYPE = StringTemplates.@template """ - SELECT DISTINCT component_uuid +# component ids from attribute plus component type +const _QUERY_LIST_ASSOCIATED_COMP_IDS_BY_C_TYPE = StringTemplates.@template """ + SELECT DISTINCT component_id FROM $table_name - WHERE attribute_uuid = ? AND $component_type_clause + WHERE attribute_id = ? AND $component_type_clause """ """ -Return the distinct component UUIDs associated with the attribute, filter by component type. +Return the distinct component ids associated with the attribute, filter by component type. """ -function list_associated_component_uuids( +function list_associated_component_ids( associations::SupplementalAttributeAssociations, attribute::SupplementalAttribute, component_type::Type{<:InfrastructureSystemsComponent}, ) - params = [string(get_uuid(attribute))] + params = Any[get_id(attribute)] component_type_clause = _get_type_clause!(params, component_type, "component_type") query = StringTemplates.render( - _QUERY_LIST_ASSOCIATED_COMP_UUIDS_BY_C_TYPE; + _QUERY_LIST_ASSOCIATED_COMP_IDS_BY_C_TYPE; table_name = SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME, component_type_clause = component_type_clause, ) table = Tables.columntable( _execute_cached(associations, query, params), ) - return Base.UUID.(table.component_uuid) + return collect(Int, table.component_id) end -# attribute UUIDS from component plus attribute type -const _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_UUIDS_BY_A_TYPE = +# attribute ids from component plus attribute type +const _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_IDS_BY_A_TYPE = StringTemplates.@template """ - SELECT DISTINCT attribute_uuid + SELECT DISTINCT attribute_id FROM $table_name - WHERE component_uuid = ? AND $attribute_type_clause + WHERE component_id = ? AND $attribute_type_clause """ """ -Return the distinct attribute UUIDs associated with the component, filter by attribute type. +Return the distinct attribute ids associated with the component, filter by attribute type. """ -function list_associated_supplemental_attribute_uuids( +function list_associated_supplemental_attribute_ids( associations::SupplementalAttributeAssociations, component::InfrastructureSystemsComponent, attribute_type::Type{<:SupplementalAttribute}, ) - params = [string(get_uuid(component))] + params = Any[get_id(component)] attribute_type_clause = _get_type_clause!(params, attribute_type, "attribute_type") query = StringTemplates.render( - _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_UUIDS_BY_A_TYPE; + _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_IDS_BY_A_TYPE; table_name = SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME, attribute_type_clause = attribute_type_clause, ) table = Tables.columntable( _execute_cached(associations, query, params), ) - return Base.UUID.(table.attribute_uuid) + return collect(Int, table.attribute_id) end -# component UUIDS from attribute type driver function +# component ids from attribute type driver function """ -Return the distinct component UUIDs associated with the attribute type. +Return the distinct component ids associated with the attribute type. """ -function list_associated_component_uuids( +function list_associated_component_ids( associations::SupplementalAttributeAssociations, attribute_type::Type{<:SupplementalAttribute}, ::Nothing, ) if isconcretetype(attribute_type) - return _list_associated_component_uuids(associations, (attribute_type,)) + return _list_associated_component_ids(associations, (attribute_type,)) end subtypes = get_all_concrete_subtypes(attribute_type) - return _list_associated_component_uuids(associations, subtypes) + return _list_associated_component_ids(associations, subtypes) end -# attribute UUIDS from component type driver function +# attribute ids from component type driver function """ -Return the distinct attribute UUIDs associated with the component type. +Return the distinct attribute ids associated with the component type. """ -function list_associated_supplemental_attribute_uuids( +function list_associated_supplemental_attribute_ids( associations::SupplementalAttributeAssociations, component_type::Type{<:InfrastructureSystemsComponent}, ::Nothing, ) if isconcretetype(component_type) - return _list_associated_supplemental_attribute_uuids( + return _list_associated_supplemental_attribute_ids( associations, (component_type,), ) end subtypes = get_all_concrete_subtypes(component_type) - return _list_associated_supplemental_attribute_uuids(associations, subtypes) + return _list_associated_supplemental_attribute_ids(associations, subtypes) end -# component UUIDS from attribute type plus component type -const _QUERY_LIST_ASSOCIATED_COMP_UUIDS_BY_TYPES = StringTemplates.@template """ - SELECT DISTINCT component_uuid +# component ids from attribute type plus component type +const _QUERY_LIST_ASSOCIATED_COMP_IDS_BY_TYPES = StringTemplates.@template """ + SELECT DISTINCT component_id FROM $table_name WHERE $attribute_type_clause AND $component_type_clause """ """ -Return the distinct component UUIDs associated with the attribute type, filter by +Return the distinct component ids associated with the attribute type, filter by component_type. """ -function list_associated_component_uuids( +function list_associated_component_ids( associations::SupplementalAttributeAssociations, attribute_type::Type{<:SupplementalAttribute}, component_type::Type{<:InfrastructureSystemsComponent}, @@ -468,28 +477,28 @@ function list_associated_component_uuids( attribute_type_clause = _get_type_clause!(params, attribute_type, "attribute_type") component_type_clause = _get_type_clause!(params, component_type, "component_type") query = StringTemplates.render( - _QUERY_LIST_ASSOCIATED_COMP_UUIDS_BY_TYPES; + _QUERY_LIST_ASSOCIATED_COMP_IDS_BY_TYPES; table_name = SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME, attribute_type_clause = attribute_type_clause, component_type_clause = component_type_clause, ) table = Tables.columntable(_execute_cached(associations, query, params)) - return Base.UUID.(table.component_uuid) + return collect(Int, table.component_id) end -# attribute UUIDS from attribute type plus component type -const _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_UUIDS_BY_TYPES = +# attribute ids from attribute type plus component type +const _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_IDS_BY_TYPES = StringTemplates.@template """ - SELECT DISTINCT attribute_uuid + SELECT DISTINCT attribute_id FROM $table_name WHERE $attribute_type_clause AND $component_type_clause """ """ -Return the distinct attribute UUIDs associated with the component type, filter by +Return the distinct attribute ids associated with the component type, filter by attribute_type. """ -function list_associated_supplemental_attribute_uuids( +function list_associated_supplemental_attribute_ids( associations::SupplementalAttributeAssociations, component_type::Type{<:InfrastructureSystemsComponent}, attribute_type::Type{<:SupplementalAttribute}, @@ -498,25 +507,25 @@ function list_associated_supplemental_attribute_uuids( attribute_type_clause = _get_type_clause!(params, attribute_type, "attribute_type") component_type_clause = _get_type_clause!(params, component_type, "component_type") query = StringTemplates.render( - _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_UUIDS_BY_TYPES; + _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_IDS_BY_TYPES; table_name = SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME, attribute_type_clause = attribute_type_clause, component_type_clause = component_type_clause, ) table = Tables.columntable(_execute_cached(associations, query, params)) - return Base.UUID.(table.attribute_uuid) + return collect(Int, table.attribute_id) end -const _QUERY_LIST_ASSOCIATED_PAIR_UUIDS = StringTemplates.@template """ - SELECT DISTINCT component_uuid, attribute_uuid +const _QUERY_LIST_ASSOCIATED_PAIR_IDS = StringTemplates.@template """ + SELECT DISTINCT component_id, attribute_id FROM $table_name WHERE $component_type_clause AND $attribute_type_clause """ """ -Return the component and attribute UUIDs that are associated with the given types. +Return the component and attribute ids that are associated with the given types. """ -function list_associated_pair_uuids( +function list_associated_pair_ids( associations::SupplementalAttributeAssociations, attribute_type::Type{<:SupplementalAttribute}, component_type::Type{<:InfrastructureSystemsComponent}, @@ -525,17 +534,17 @@ function list_associated_pair_uuids( component_type_clause = _get_type_clause!(params, component_type, "component_type") attribute_type_clause = _get_type_clause!(params, attribute_type, "attribute_type") query = StringTemplates.render( - _QUERY_LIST_ASSOCIATED_PAIR_UUIDS; + _QUERY_LIST_ASSOCIATED_PAIR_IDS; table_name = SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME, component_type_clause = component_type_clause, attribute_type_clause = attribute_type_clause, ) table = Tables.rowtable(_execute_cached(associations, query, params)) - return [(Base.UUID(row.component_uuid), Base.UUID(row.attribute_uuid)) for row in table] + return [(Int(row.component_id), Int(row.attribute_id)) for row in table] end function _get_type_clause!( - params::Vector{String}, + params::Vector, type::Type{<:InfrastructureSystemsType}, column::String, ) @@ -558,81 +567,81 @@ function _get_type_clause!( return type_clause end -# component UUIDs from attribute type implementation -const _QUERY_LIST_ASSOCIATED_COMP_UUIDS_BY_ONE_TYPE = """ - SELECT DISTINCT component_uuid +# component ids from attribute type implementation +const _QUERY_LIST_ASSOCIATED_COMP_IDS_BY_ONE_TYPE = """ + SELECT DISTINCT component_id FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME WHERE attribute_type = ? """ -const _QUERY_LIST_ASSOCIATED_COMP_UUIDS_BY_MULTIPLE_TYPES = StringTemplates.@template """ - SELECT DISTINCT component_uuid +const _QUERY_LIST_ASSOCIATED_COMP_IDS_BY_MULTIPLE_TYPES = StringTemplates.@template """ + SELECT DISTINCT component_id FROM $table_name WHERE attribute_type IN ($placeholder) """ -function _list_associated_component_uuids( +function _list_associated_component_ids( associations::SupplementalAttributeAssociations, attribute_types, ) len = length(attribute_types) if len == 0 # This would require an abstract type with no subtypes. Just here for completeness. - return Base.UUID[] + return Int[] elseif len == 1 - query = _QUERY_LIST_ASSOCIATED_COMP_UUIDS_BY_ONE_TYPE + query = _QUERY_LIST_ASSOCIATED_COMP_IDS_BY_ONE_TYPE params = (string(nameof(first(attribute_types))),) else placeholder = chop(repeat("?,", length(attribute_types))) params = Tuple(string(nameof(type)) for type in attribute_types) query = StringTemplates.render( - _QUERY_LIST_ASSOCIATED_COMP_UUIDS_BY_MULTIPLE_TYPES; + _QUERY_LIST_ASSOCIATED_COMP_IDS_BY_MULTIPLE_TYPES; table_name = SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME, placeholder = placeholder, ) end table = Tables.columntable(_execute_cached(associations, query, params)) - return Base.UUID.(table.component_uuid) + return collect(Int, table.component_id) end -# attribute UUIDs from component type implementation -const _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_UUIDS_BY_ONE_TYPE = """ - SELECT DISTINCT attribute_uuid +# attribute ids from component type implementation +const _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_IDS_BY_ONE_TYPE = """ + SELECT DISTINCT attribute_id FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME WHERE component_type = ? """ -const _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_UUIDS_BY_MULTIPLE_TYPES = +const _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_IDS_BY_MULTIPLE_TYPES = StringTemplates.@template """ - SELECT DISTINCT attribute_uuid + SELECT DISTINCT attribute_id FROM $table_name WHERE component_type IN ($placeholder) """ -function _list_associated_supplemental_attribute_uuids( +function _list_associated_supplemental_attribute_ids( associations::SupplementalAttributeAssociations, component_types, ) len = length(component_types) if len == 0 # This would require an abstract type with no subtypes. Just here for completeness. - return Base.UUID[] + return Int[] elseif len == 1 - query = _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_UUIDS_BY_ONE_TYPE + query = _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_IDS_BY_ONE_TYPE params = (string(nameof(first(component_types))),) else placeholder = chop(repeat("?,", length(component_types))) params = Tuple(string(nameof(type)) for type in component_types) query = StringTemplates.render( - _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_UUIDS_BY_MULTIPLE_TYPES; + _QUERY_LIST_ASSOCIATED_SUPPLEMENTAL_ATTRIBUTE_IDS_BY_MULTIPLE_TYPES; table_name = SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME, placeholder = placeholder, ) end table = Tables.columntable(_execute_cached(associations, query, params)) - return Base.UUID.(table.attribute_uuid) + return collect(Int, table.attribute_id) end """ @@ -643,8 +652,8 @@ function remove_association!( component::InfrastructureSystemsComponent, attribute::SupplementalAttribute, ) - where_clause = "WHERE attribute_uuid = ? AND component_uuid = ?" - params = (string(get_uuid(attribute)), string(get_uuid(component))) + where_clause = "WHERE attribute_id = ? AND component_id = ?" + params = (get_id(attribute), get_id(component)) num_deleted = _remove_associations!(associations, where_clause, params) if num_deleted != 1 error("Bug: unexpected number of deletions: $num_deleted. Should have been 1.") @@ -666,22 +675,22 @@ function remove_associations!( return end -const _QUERY_REPLACE_COMP_UUID_SA = """ +const _QUERY_REPLACE_COMP_ID_SA = """ UPDATE $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME - SET component_uuid = ? - WHERE component_uuid = ? + SET component_id = ? + WHERE component_id = ? """ """ -Replace the component UUID in the table. +Replace the component id in the table. """ -function replace_component_uuid!( +function replace_component_id!( associations::SupplementalAttributeAssociations, - old_uuid::Base.UUID, - new_uuid::Base.UUID, + old_id::Int, + new_id::Int, ) - params = (string(new_uuid), string(old_uuid)) - _execute_cached(associations, _QUERY_REPLACE_COMP_UUID_SA, params) + params = (new_id, old_id) + _execute_cached(associations, _QUERY_REPLACE_COMP_ID_SA, params) return end @@ -712,6 +721,11 @@ const _QUERY_INSERT_ROWS_INTO_C_ATTR_TABLE = StringTemplates.@template """ VALUES($placeholder) """ +# Records come either as string-keyed dictionaries (deserialized from JSON) or as +# NamedTuples (directly from `to_records`). +_record_value(record::AbstractDict, column::AbstractString) = record[column] +_record_value(record, column::AbstractString) = getproperty(record, Symbol(column)) + """ Add records to the database. Expects output from [`to_records`](@ref). """ @@ -719,15 +733,10 @@ function from_records(::Type{SupplementalAttributeAssociations}, records) associations = SupplementalAttributeAssociations(; create_indexes = false) isempty(records) && return associations - columns = ("attribute_uuid", "attribute_type", "component_uuid", "component_type") - num_rows = length(records) + columns = ("attribute_id", "attribute_type", "component_id", "component_type") num_columns = length(columns) - data = OrderedDict(x => Vector{String}(undef, num_rows) for x in columns) - for (i, record) in enumerate(records) - for column in columns - data[column][i] = record[column] - end - end + data = + OrderedDict(x => [_record_value(record, x) for record in records] for x in columns) placeholder = chop(repeat("?,", num_columns)) # Note: executemany automatically wraps operations in a transaction for performance @@ -784,7 +793,7 @@ function compare_values( query = """ SELECT * FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME - ORDER BY attribute_uuid, component_uuid + ORDER BY attribute_id, component_id """ table_x = Tables.rowtable(_execute(x, query)) table_y = Tables.rowtable(_execute(y, query)) diff --git a/src/supplemental_attribute_manager.jl b/src/supplemental_attribute_manager.jl index 9cbfb12d6..31ccaaab6 100644 --- a/src/supplemental_attribute_manager.jl +++ b/src/supplemental_attribute_manager.jl @@ -1,6 +1,16 @@ const SupplementalAttributesByType = - Dict{DataType, Dict{Base.UUID, <:SupplementalAttribute}} + Dict{DataType, Dict{Int, <:SupplementalAttribute}} +""" +Owns supplemental attributes and their associations to components in a [`SystemData`](@ref). + +Attributes are stored by type and integer id. Component links are tracked in +[`SupplementalAttributeAssociations`](@ref). User code typically calls +[`add_supplemental_attribute!`](@ref) on [`SystemData`](@ref) rather than on the manager +directly. + +See also: [`SupplementalAttribute`](@ref), [`iterate_supplemental_attributes`](@ref) +""" mutable struct SupplementalAttributeManager <: InfrastructureSystemsContainer data::SupplementalAttributesByType associations::SupplementalAttributeAssociations @@ -81,19 +91,19 @@ function _attach_attribute!( T = typeof(attribute) if !haskey(mgr.data, T) - mgr.data[T] = Dict{Base.UUID, T}() + mgr.data[T] = Dict{Int, T}() end - mgr.data[T][get_uuid(attribute)] = attribute + mgr.data[T][get_id(attribute)] = attribute end function is_attached(attribute::SupplementalAttribute, mgr::SupplementalAttributeManager) T = typeof(attribute) !haskey(mgr.data, T) && return false - _attribute = get(mgr.data[T], get_uuid(attribute), nothing) + _attribute = get(mgr.data[T], get_id(attribute), nothing) isnothing(_attribute) && return false if attribute !== _attribute - @warn "An attribute with the same UUUID as $(summary(attribute)) is stored in " * + @warn "An attribute with the same id as $(summary(attribute)) is stored in " * "the system but is not the same instance." return false end @@ -170,7 +180,7 @@ function remove_supplemental_attribute!( end T = typeof(supplemental_attribute) - pop!(mgr.data[T], get_uuid(supplemental_attribute)) + pop!(mgr.data[T], get_id(supplemental_attribute)) prepare_for_removal!(supplemental_attribute) if isempty(mgr.data[T]) pop!(mgr.data, T) @@ -230,31 +240,31 @@ function get_supplemental_attributes( return iterate_instances(T, mgr.data, nothing) end -function get_supplemental_attribute(mgr::SupplementalAttributeManager, uuid::Base.UUID) +function get_supplemental_attribute(mgr::SupplementalAttributeManager, id::Int) for attr_dict in values(mgr.data) - attribute = get(attr_dict, uuid, nothing) + attribute = get(attr_dict, id, nothing) if !isnothing(attribute) return attribute end end - throw(ArgumentError("No attribute with UUID = $uuid is stored")) + throw(ArgumentError("No attribute with id = $id is stored")) end -function list_associated_component_uuids( +function list_associated_component_ids( mgr::SupplementalAttributeManager, attribute_type::Type{<:SupplementalAttribute}, component_type::Union{Nothing, Type{<:InfrastructureSystemsComponent}}, ) - return list_associated_component_uuids(mgr.associations, attribute_type, component_type) + return list_associated_component_ids(mgr.associations, attribute_type, component_type) end -function list_associated_supplemental_attribute_uuids( +function list_associated_supplemental_attribute_ids( mgr::SupplementalAttributeManager, component_type::Type{<:InfrastructureSystemsComponent}, attribute_type::Union{Nothing, Type{<:SupplementalAttribute}}, ) - return list_associated_supplemental_attribute_uuids( + return list_associated_supplemental_attribute_ids( mgr.associations, component_type, attribute_type, @@ -284,14 +294,14 @@ function deserialize( for attr_dict in data["attributes"] type = get_type_from_serialization_metadata(get_serialization_metadata(attr_dict)) if !haskey(mgr.data, type) - mgr.data[type] = Dict{Base.UUID, SupplementalAttribute}() + mgr.data[type] = Dict{Int, SupplementalAttribute}() end attr = deserialize(type, attr_dict) - uuid = get_uuid(attr) - if haskey(mgr.data[type], uuid) - error("Bug: duplicate UUID in attributes container: type=$type uuid=$uuid") + id = get_id(attr) + if haskey(mgr.data[type], id) + error("Bug: duplicate id in attributes container: type=$type id=$id") end - mgr.data[type][uuid] = attr + mgr.data[type][id] = attr set_shared_system_references!(attr, refs) @debug "Deserialized $(summary(attr))" _group = LOG_GROUP_SERIALIZATION end diff --git a/src/system_data.jl b/src/system_data.jl index 554325348..4289e2e32 100644 --- a/src/system_data.jl +++ b/src/system_data.jl @@ -20,10 +20,14 @@ Container for system components and time series data mutable struct SystemData <: ComponentContainer components::Components masked_components::Components - "Contains all attached component UUIDs, regular and masked." - component_uuids::Dict{Base.UUID, <:InfrastructureSystemsComponent} + "Maps the integer id of every attached component, regular and masked, to the component." + component_ids::Dict{Int, <:InfrastructureSystemsComponent} + "Next integer id to assign to a component. Independent of the supplemental attribute id stream. Starts at 1." + next_component_id::Int + "Next integer id to assign to a supplemental attribute. Independent of the component id stream. Starts at 1." + next_supplemental_attribute_id::Int "User-defined subystems. Components can be regular or masked." - subsystems::Dict{String, Set{Base.UUID}} + subsystems::Dict{String, Set{Int}} supplemental_attribute_manager::SupplementalAttributeManager time_series_manager::TimeSeriesManager validation_descriptors::Vector @@ -67,8 +71,10 @@ function SystemData(; return SystemData( components, masked_components, - Dict{Base.UUID, InfrastructureSystemsComponent}(), - Dict{String, Set{Base.UUID}}(), + Dict{Int, InfrastructureSystemsComponent}(), + 1, + 1, + Dict{String, Set{Int}}(), supplemental_attribute_mgr, time_series_mgr, validation_descriptors, @@ -79,6 +85,8 @@ end function SystemData( validation_descriptors, time_series_manager, + next_component_id, + next_supplemental_attribute_id, subsystems, supplemental_attribute_manager, internal, @@ -88,7 +96,9 @@ function SystemData( return SystemData( components, masked_components, - Dict{Base.UUID, InfrastructureSystemsComponent}(), + Dict{Int, InfrastructureSystemsComponent}(), + next_component_id, + next_supplemental_attribute_id, subsystems, supplemental_attribute_manager, time_series_manager, @@ -97,6 +107,25 @@ function SystemData( ) end +""" +Return the next integer id to assign to a component and advance the component counter. +""" +function get_next_component_id!(data::SystemData) + id = data.next_component_id + data.next_component_id += 1 + return id +end + +""" +Return the next integer id to assign to a supplemental attribute and advance the +supplemental attribute counter. +""" +function get_next_supplemental_attribute_id!(data::SystemData) + id = data.next_supplemental_attribute_id + data.next_supplemental_attribute_id += 1 + return id +end + function open_time_series_store!( func::Function, data::SystemData, @@ -357,7 +386,7 @@ function _validate( end function _validate(data::SystemData, attribute::SupplementalAttribute) - _attribute = get_supplemental_attribute(data, get_uuid(attribute)) + _attribute = get_supplemental_attribute(data, get_id(attribute)) if attribute !== _attribute throw( ArgumentError( @@ -378,7 +407,7 @@ function compare_values( match = true for name in fieldnames(SystemData) name in exclude && continue - if name == :component_uuids + if name == :component_ids # These are not serialized. They get rebuilt when the parent package adds # the components. continue @@ -425,12 +454,12 @@ function remove_components!(::Type{T}, data::SystemData) where {T} end function _handle_component_removal!(data::SystemData, component) - uuid = get_uuid(component) - if !haskey(data.component_uuids, uuid) - error("Bug: component = $(summary(component)) did not have its uuid stored $uuid") + id = get_id(component) + if !haskey(data.component_ids, id) + error("Bug: component = $(summary(component)) did not have its id stored $id") end - pop!(data.component_uuids, uuid) + pop!(data.component_ids, id) remove_component_from_subsystems!(data, component) set_shared_system_references!(component, nothing) return @@ -469,7 +498,7 @@ function iterate_components_with_time_series( ) return ( get_component(data, x) for - x in list_owner_uuids_with_time_series( + x in list_owner_ids_with_time_series( data.time_series_manager.metadata_store, InfrastructureSystemsComponent; time_series_type = time_series_type, @@ -484,7 +513,7 @@ function iterate_supplemental_attributes_with_time_series( ) return ( get_supplemental_attribute(data, x) for - x in list_owner_uuids_with_time_series( + x in list_owner_ids_with_time_series( data.time_series_manager.metadata_store, SupplementalAttribute; time_series_type = time_series_type, @@ -711,7 +740,7 @@ function _check_transform_single_time_series( resolution::Union{Nothing, Dates.Period}; skip_existing::Bool = false, ) - items = list_metadata_with_owner_uuid( + items = list_metadata_with_owner_id( data.time_series_manager.metadata_store, InfrastructureSystemsComponent; time_series_type = SingleTimeSeries, @@ -731,7 +760,7 @@ function _check_transform_single_time_series( interval = params.interval, ) check_params_compatibility(system_params, params) - component = get_component(data, item.owner_uuid) + component = get_component(data, item.owner_id) # We do not allow a component to have both Deterministic and # DeterministicSingleTimeSeries with the same parameters. @@ -927,6 +956,8 @@ function to_dict(data::SystemData) ( :components, :masked_components, + :next_component_id, + :next_supplemental_attribute_id, :subsystems, :supplemental_attribute_manager, :internal, @@ -1031,7 +1062,9 @@ function deserialize( read_only = time_series_read_only, metadata_store = time_series_metadata_store, ) - subsystems = Dict(k => Set(Base.UUID.(v)) for (k, v) in raw["subsystems"]) + subsystems = Dict(k => Set(Int.(v)) for (k, v) in raw["subsystems"]) + next_component_id = Int(get(raw, "next_component_id", 1)) + next_supplemental_attribute_id = Int(get(raw, "next_supplemental_attribute_id", 1)) supplemental_attribute_manager = deserialize( SupplementalAttributeManager, get( @@ -1051,30 +1084,32 @@ function deserialize( sys = SystemData( validation_descriptors, time_series_manager, + next_component_id, + next_supplemental_attribute_id, subsystems, supplemental_attribute_manager, internal, ) - attributes_by_uuid = Dict{Base.UUID, SupplementalAttribute}() + attributes_by_id = Dict{Int, SupplementalAttribute}() for attr_dict in values(supplemental_attribute_manager.data) for attr in values(attr_dict) - uuid = get_uuid(attr) - if haskey(attributes_by_uuid, uuid) - error("Bug: Found duplicate supplemental attribute UUID: $uuid") + id = get_id(attr) + if haskey(attributes_by_id, id) + error("Bug: Found duplicate supplemental attribute id: $id") end - attributes_by_uuid[uuid] = attr + attributes_by_id[id] = attr end end - system_component_uuids = Set{Base.UUID}() + system_component_ids = Set{Int}() for component in Iterators.Flatten((raw["components"], raw["masked_components"])) - push!(system_component_uuids, UUIDs.UUID(component["internal"]["uuid"]["value"])) + push!(system_component_ids, Int(component["internal"]["id"])) end - for (name, subsystem_component_uuids) in sys.subsystems - if !issubset(subsystem_component_uuids, system_component_uuids) - diff = setdiff(subsystem_component_uuids, system_component_uuids) - error("Subsystem $name has component UUIDs that are not in the system: $diff") + for (name, subsystem_component_ids) in sys.subsystems + if !issubset(subsystem_component_ids, system_component_ids) + diff = setdiff(subsystem_component_ids, system_component_ids) + error("Subsystem $name has component ids that are not in the system: $diff") end end @@ -1085,10 +1120,55 @@ end # Redirect functions to Components +""" +Assign an integer id to a component being attached to `data`, drawn from the component id +stream. + +A freshly constructed component has [`UNASSIGNED_ID`](@ref) and receives the next component +id. A component that already carries an id (for example, one restored during +deserialization) keeps it; the counter is advanced past it so future ids do not collide. +Components and supplemental attributes have independent id streams, so a component and an +attribute may share a numeric id. +""" +function assign_id!(data::SystemData, component::InfrastructureSystemsComponent) + id = get_id(component) + if id == UNASSIGNED_ID + id = get_next_component_id!(data) + set_id!(component, id) + elseif id >= data.next_component_id + data.next_component_id = id + 1 + end + return id +end + +""" +Assign an integer id to a supplemental attribute being attached to `data`, drawn from the +supplemental attribute id stream (independent of the component id stream). +""" +function assign_id!(data::SystemData, attribute::SupplementalAttribute) + id = get_id(attribute) + if id == UNASSIGNED_ID + id = get_next_supplemental_attribute_id!(data) + set_id!(attribute, id) + elseif id >= data.next_supplemental_attribute_id + data.next_supplemental_attribute_id = id + 1 + end + return id +end + +""" +Add a component to a [`SystemData`](@ref) instance. + +Assigns the component's integer id, wires [`SharedSystemReferences`](@ref) for time series +and supplemental attributes, and delegates storage to the underlying [`Components`](@ref) +container. + +See also: [`add_component!`](@ref) on [`Components`](@ref) +""" function add_component!(data::SystemData, component; kwargs...) _check_add_component(data, component) add_component!(data.components, component; kwargs...) - data.component_uuids[get_uuid(component)] = component + data.component_ids[assign_id!(data, component)] = component refs = SharedSystemReferences(; time_series_manager = data.time_series_manager, supplemental_attribute_manager = data.supplemental_attribute_manager, @@ -1105,7 +1185,7 @@ function add_masked_component!(data::SystemData, component; kwargs...) allow_existing_time_series = true, kwargs..., ) - data.component_uuids[get_uuid(component)] = component + data.component_ids[assign_id!(data, component)] = component refs = SharedSystemReferences(; time_series_manager = data.time_series_manager, supplemental_attribute_manager = data.supplemental_attribute_manager, @@ -1121,16 +1201,16 @@ function remove_masked_component!(data::SystemData, component) end function _check_add_component(data::SystemData, component) - _check_duplicate_component_uuid(data, component) + _check_duplicate_component_id(data, component) if !isnothing(get_shared_system_references(component)) error("$(summary(component)) is already attached to a system") end end -function _check_duplicate_component_uuid(data::SystemData, component) - uuid = get_uuid(component) - if haskey(data.component_uuids, uuid) - throw(ArgumentError("Component $(summary(component)) uuid=$uuid is already stored")) +function _check_duplicate_component_id(data::SystemData, component) + id = get_id(component) + if id != UNASSIGNED_ID && haskey(data.component_ids, id) + throw(ArgumentError("Component $(summary(component)) id=$id is already stored")) end end @@ -1139,10 +1219,10 @@ iterate_components(data::SystemData) = iterate_components(data.components) get_component(::Type{T}, data::SystemData, args...) where {T} = get_component(T, data.components, args...) -function get_component(data::SystemData, uuid::Base.UUID) - component = get(data.component_uuids, uuid, nothing) +function get_component(data::SystemData, id::Int) + component = get(data.component_ids, id, nothing) if isnothing(component) - throw(ArgumentError("No component with uuid = $uuid is stored.")) + throw(ArgumentError("No component with id = $id is stored.")) end return component @@ -1158,17 +1238,17 @@ has_component( ) = has_component(data.components, T, name) function has_component(data::SystemData, component::InfrastructureSystemsComponent) - return get_uuid(component) in keys(data.component_uuids) + return get_id(component) in keys(data.component_ids) end -function assign_new_uuid!(data::SystemData, component::InfrastructureSystemsComponent) - orig_uuid = get_uuid(component) - if isnothing(pop!(data.component_uuids, orig_uuid, nothing)) - throw(ArgumentError("component with uuid = $orig_uuid is not stored.")) +function assign_new_id!(data::SystemData, component::InfrastructureSystemsComponent) + orig_id = get_id(component) + if isnothing(pop!(data.component_ids, orig_id, nothing)) + throw(ArgumentError("component with id = $orig_id is not stored.")) end - assign_new_uuid_internal!(component) - data.component_uuids[get_uuid(component)] = component + assign_new_id_internal!(data, component) + data.component_ids[get_id(component)] = component return end @@ -1178,8 +1258,8 @@ function get_components( data::SystemData; subsystem_name::Union{Nothing, AbstractString} = nothing, ) where {T} - uuids = isnothing(subsystem_name) ? nothing : get_component_uuids(data, subsystem_name) - return get_components(filter_func, T, data.components; component_uuids = uuids) + ids = isnothing(subsystem_name) ? nothing : get_component_ids(data, subsystem_name) + return get_components(filter_func, T, data.components; component_ids = ids) end function get_components( @@ -1187,8 +1267,8 @@ function get_components( data::SystemData; subsystem_name::Union{Nothing, AbstractString} = nothing, ) where {T} - uuids = isnothing(subsystem_name) ? nothing : get_component_uuids(data, subsystem_name) - return get_components(T, data.components; component_uuids = uuids) + ids = isnothing(subsystem_name) ? nothing : get_component_ids(data, subsystem_name) + return get_components(T, data.components; component_ids = ids) end get_components_by_name(::Type{T}, data::SystemData, args...) where {T} = @@ -1201,7 +1281,7 @@ function get_associated_components( ) return [ get_component(data, x) for x in - list_associated_component_uuids( + list_associated_component_ids( data.supplemental_attribute_manager, attribute_type, component_type, @@ -1226,7 +1306,7 @@ function get_associated_supplemental_attributes( ) return [ get_supplemental_attribute(data, x) for x in - list_associated_supplemental_attribute_uuids( + list_associated_supplemental_attribute_ids( data.supplemental_attribute_manager, component_type, attribute_type, @@ -1250,7 +1330,7 @@ function get_associated_components( ) return [ get_component(data, x) for x in - list_associated_component_uuids( + list_associated_component_ids( data.supplemental_attribute_manager.associations, attribute, component_type, @@ -1273,23 +1353,23 @@ function get_component_supplemental_attribute_pairs( attributes = nothing, ) where {T <: InfrastructureSystemsComponent, U <: SupplementalAttribute} ca_pairs = NamedTuple{(:component, :supplemental_attribute), Tuple{T, U}}[] - c_uuids = isnothing(components) ? Set{Base.UUID}() : Set(get_uuid.(components)) - a_uuids = isnothing(attributes) ? Set{Base.UUID}() : Set(get_uuid.(attributes)) - for (component_uuid, attribute_uuid) in - list_associated_pair_uuids( + c_ids = isnothing(components) ? Set{Int}() : Set(get_id.(components)) + a_ids = isnothing(attributes) ? Set{Int}() : Set(get_id.(attributes)) + for (component_id, attribute_id) in + list_associated_pair_ids( data.supplemental_attribute_manager.associations, U, T, ) - if !isnothing(components) && !(component_uuid in c_uuids) + if !isnothing(components) && !(component_id in c_ids) continue end - if !isnothing(attributes) && !(attribute_uuid in a_uuids) + if !isnothing(attributes) && !(attribute_id in a_ids) continue end - component = get_component(data, component_uuid) + component = get_component(data, component_id) - attribute = get_supplemental_attribute(data, attribute_uuid) + attribute = get_supplemental_attribute(data, attribute_id) push!(ca_pairs, (component = component, supplemental_attribute = attribute)) end @@ -1317,14 +1397,14 @@ get_masked_components_by_name(::Type{T}, data::SystemData, args...) where {T} = get_masked_component(::Type{T}, data::SystemData, name) where {T} = get_component(T, data.masked_components, name) -function get_masked_component(data::SystemData, uuid::Base.UUID) +function get_masked_component(data::SystemData, id::Int) for component in get_masked_components(InfrastructureSystemsComponent, data) - if get_uuid(component) == uuid + if get_id(component) == id return component end end - @error "no component with UUID $uuid is stored" + @error "no component with id $id is stored" return nothing end @@ -1420,6 +1500,7 @@ function add_supplemental_attribute!(data::SystemData, component, attribute; kwa # Note that we do not support adding attributes to masked components # and this check doesn't look at those. throw_if_not_attached(data.components, component) + assign_id!(data, attribute) add_supplemental_attribute!( data.supplemental_attribute_manager, component, @@ -1451,8 +1532,8 @@ function get_supplemental_attributes( return get_supplemental_attributes(T, data.supplemental_attribute_manager) end -function get_supplemental_attribute(data::SystemData, uuid::Base.UUID) - return get_supplemental_attribute(data.supplemental_attribute_manager, uuid) +function get_supplemental_attribute(data::SystemData, id::Int) + return get_supplemental_attribute(data.supplemental_attribute_manager, id) end function iterate_supplemental_attributes(data::SystemData) diff --git a/src/time_series_metadata_store.jl b/src/time_series_metadata_store.jl index 7ad76da49..5aa80c836 100644 --- a/src/time_series_metadata_store.jl +++ b/src/time_series_metadata_store.jl @@ -6,7 +6,7 @@ const DB_FILENAME = "time_series_metadata.db" const TS_METADATA_FORMAT_VERSION = "1.1.0" const TS_DB_INDEXES = Dict( "by_c_n_tst_features" => [ - "owner_uuid", + "owner_id", "time_series_type", "name", "resolution", @@ -91,7 +91,7 @@ function _load_metadata_into_memory!(store::TimeSeriesMetadataStore) store.db, "SELECT * FROM $ASSOCIATIONS_TABLE_NAME", ) - exclude_keys = Set((:metadata_uuid, :owner_uuid, :time_series_type)) + exclude_keys = Set((:metadata_uuid, :owner_id, :time_series_type)) for row in Tables.rowtable(SQLite.DBInterface.execute(stmt)) time_series_type = TIME_SERIES_STRING_TO_TYPE[row.time_series_type] metadata_type = time_series_data_to_metadata(time_series_type) @@ -214,7 +214,7 @@ function _migrate_from_v2_3(store::TimeSeriesMetadataStore) SELECT id ,time_series_type - ,owner_uuid + ,owner_id ,owner_type ,owner_category ,features @@ -261,7 +261,7 @@ function _migrate_from_v2_4(store::TimeSeriesMetadataStore) SELECT id ,time_series_type - ,owner_uuid + ,owner_id ,owner_type ,owner_category ,features @@ -290,7 +290,7 @@ function _create_migrated_row(metadata::SingleTimeSeriesMetadata, row) missing, get_length(metadata), get_name(metadata), - row.owner_uuid, + row.owner_id, row.owner_type, row.owner_category, row.features, @@ -313,7 +313,7 @@ function _create_migrated_row(metadata::ForecastMetadata, row) get_count(metadata), missing, get_name(metadata), - row.owner_uuid, + row.owner_id, row.owner_type, row.owner_category, row.features, @@ -339,7 +339,7 @@ function _add_migrated_rows!(store::TimeSeriesMetadataStore, rows) "window_count", "length", "name", - "owner_uuid", + "owner_id", "owner_type", "owner_category", "features", @@ -382,7 +382,7 @@ function _create_associations_table!(store::TimeSeriesMetadataStore) "window_count INTEGER", "length INTEGER", "name TEXT NOT NULL", - "owner_uuid TEXT NOT NULL", + "owner_id INTEGER NOT NULL", "owner_type TEXT NOT NULL", "owner_category TEXT NOT NULL", "features TEXT NOT NULL", @@ -808,12 +808,12 @@ Return an instance of TimeSeriesCounts. """ function get_time_series_counts(store::TimeSeriesMetadataStore) query_components = """ - SELECT COUNT(DISTINCT owner_uuid) AS count + SELECT COUNT(DISTINCT owner_id) AS count FROM $ASSOCIATIONS_TABLE_NAME WHERE owner_category = 'Component' """ query_attributes = """ - SELECT COUNT(DISTINCT owner_uuid) AS count + SELECT COUNT(DISTINCT owner_id) AS count FROM $ASSOCIATIONS_TABLE_NAME WHERE owner_category = 'SupplementalAttribute' """ @@ -1019,7 +1019,9 @@ function has_metadata(store::TimeSeriesMetadataStore, time_series_uuid::Base.UUI return _has_metadata(store, _QUERY_HAS_METADATA_BY_TS_UUID, params) end -const _QUERY_BASE_HAS_METADATA = "SELECT id FROM $ASSOCIATIONS_TABLE_NAME WHERE owner_uuid = ?" +# Component and supplemental attribute ids are independent streams, so an owner is +# identified by (owner_id, owner_category), never owner_id alone. +const _QUERY_BASE_HAS_METADATA = "SELECT id FROM $ASSOCIATIONS_TABLE_NAME WHERE owner_id = ? AND owner_category = ?" function _make_has_metadata_statement!( store::TimeSeriesMetadataStore, @@ -1075,7 +1077,8 @@ function _make_has_metadata_params( feature_value::Union{String, Nothing} = nothing, ) return ( - string(get_uuid(owner)), + get_id(owner), + _get_owner_category(owner), _get_name_params(name)..., _get_ts_type_params(time_series_type)..., _get_resolution_param(resolution)..., @@ -1225,7 +1228,7 @@ end """ Return a Vector of NamedTuple of owner UUID and time series metadata matching the inputs. """ -function list_metadata_with_owner_uuid( +function list_metadata_with_owner_id( store::TimeSeriesMetadataStore, owner_type::Type{<:TimeSeriesOwners}; time_series_type::Union{Type{<:TimeSeriesData}, Nothing} = nothing, @@ -1243,20 +1246,20 @@ function list_metadata_with_owner_uuid( features..., ) query = """ - SELECT owner_uuid, metadata_uuid + SELECT owner_id, metadata_uuid FROM $ASSOCIATIONS_TABLE_NAME $where_clause """ table = Tables.rowtable(_execute(store, query, params)) return [ ( - owner_uuid = Base.UUID(x.owner_uuid), + owner_id = Int(x.owner_id), metadata = store.metadata_uuids[Base.UUID(x.metadata_uuid)], ) for x in table ] end -function list_owner_uuids_with_time_series( +function list_owner_ids_with_time_series( store::TimeSeriesMetadataStore, owner_type::Type{<:TimeSeriesOwners}; time_series_type::Union{Nothing, Type{<:TimeSeriesData}} = nothing, @@ -1277,11 +1280,11 @@ function list_owner_uuids_with_time_series( where_clause = join(vals, " AND ") query = """ SELECT - DISTINCT owner_uuid + DISTINCT owner_id FROM $ASSOCIATIONS_TABLE_NAME WHERE $where_clause """ - return Base.UUID.(Tables.columntable(_execute(store, query, params)).owner_uuid) + return collect(Int, Tables.columntable(_execute(store, query, params)).owner_id) end """ @@ -1399,18 +1402,18 @@ function _handle_removed_metadata(store::TimeSeriesMetadataStore, metadata_uuid: end end -const _QUERY_REPLACE_COMP_UUID_TS = """ +const _QUERY_REPLACE_COMP_ID_TS = """ UPDATE $ASSOCIATIONS_TABLE_NAME - SET owner_uuid = ? - WHERE owner_uuid = ? + SET owner_id = ? + WHERE owner_id = ? AND owner_category = 'Component' """ -function replace_component_uuid!( +function replace_component_id!( store::TimeSeriesMetadataStore, - old_uuid::Base.UUID, - new_uuid::Base.UUID, + old_id::Int, + new_id::Int, ) - params = (string(new_uuid), string(old_uuid)) - _execute(store, _QUERY_REPLACE_COMP_UUID_TS, params) + params = (new_id, old_id) + _execute(store, _QUERY_REPLACE_COMP_ID_TS, params) return end @@ -1463,7 +1466,7 @@ function _create_row( get_count(metadata), missing, get_name(metadata), - string(get_uuid(owner)), + get_id(owner), string(nameof(typeof(owner))), owner_category, features, @@ -1496,7 +1499,7 @@ function _create_row( missing, get_length(metadata), get_name(metadata), - string(get_uuid(owner)), + get_id(owner), string(nameof(typeof(owner))), owner_category, features, @@ -1585,9 +1588,9 @@ function compare_values( exclude = Set{Symbol}(), ) # Note that we can't compare missing values. - owner_uuid = compare_uuids ? ", owner_uuid" : "" + owner_id = compare_uuids ? ", owner_id" : "" query = """ - SELECT id, metadata_uuid, time_series_uuid $owner_uuid + SELECT id, metadata_uuid, time_series_uuid $owner_id FROM $ASSOCIATIONS_TABLE_NAME ORDER BY id """ table_x = Tables.rowtable(_execute(x, query)) @@ -1699,9 +1702,12 @@ function _make_where_clause( if isnothing(params) params = [] end - push!(params, string(get_uuid(owner))) + # Owner ids are unique only within a category (component vs supplemental attribute), so + # match on both. + push!(params, get_id(owner)) + push!(params, _get_owner_category(owner)) return _make_where_clause(; - owner_clause = "owner_uuid = ?", + owner_clause = "owner_id = ? AND owner_category = ?", time_series_type = time_series_type, name = name, resolution = resolution, diff --git a/test/test_component_container.jl b/test/test_component_container.jl index 2b70a0632..6cc5e5449 100644 --- a/test/test_component_container.jl +++ b/test/test_component_container.jl @@ -4,7 +4,7 @@ cse(x, y) = (sort_name!(x) == sort_name!(y)) # collect, sort, equality @testset for test_sys in [create_simple_components(), create_simple_system_data()] component1 = IS.get_component(IS.TestComponent, test_sys, "Component1") - test_uuid = IS.get_uuid(component1) + test_id = IS.get_id(component1) test_type_sel = IS.TypeComponentSelector(IS.TestComponent, :all, nothing) test_name_sel = IS.NameComponentSelector(IS.TestComponent, "Component1", nothing) @@ -16,8 +16,8 @@ IS.get_components(test_type_sel, test_sys)) if test_sys isa IS.SystemData - @test IS.get_available_component(test_sys, test_uuid) == - IS.get_component(test_sys, test_uuid) + @test IS.get_available_component(test_sys, test_id) == + IS.get_component(test_sys, test_id) geo_supplemental_attribute = IS.GeographicInfo() IS.add_supplemental_attribute!(test_sys, component1, geo_supplemental_attribute) end diff --git a/test/test_internal.jl b/test/test_internal.jl index d62877024..4da47c053 100644 --- a/test/test_internal.jl +++ b/test/test_internal.jl @@ -1,8 +1,9 @@ -@testset "Test assign_new_uuid_internal" begin +@testset "Test integer id" begin component = IS.TestComponent("component", 5) - uuid1 = IS.get_uuid(component) - IS.assign_new_uuid_internal!(component) - @test uuid1 != IS.get_uuid(component) + # A freshly constructed component has no id until attached to a system. + @test IS.get_id(component) == IS.UNASSIGNED_ID + IS.set_id!(component, 5) + @test IS.get_id(component) == 5 end @testset "Test ext" begin @@ -14,6 +15,7 @@ end internal2 = IS.deserialize(IS.InfrastructureSystemsInternal, IS.serialize(internal)) @test internal.uuid == internal2.uuid + @test internal.id == internal2.id @test internal.ext == internal2.ext IS.clear_ext!(internal) diff --git a/test/test_serialization.jl b/test/test_serialization.jl index 7a42a7b45..466fda427 100644 --- a/test/test_serialization.jl +++ b/test/test_serialization.jl @@ -176,6 +176,41 @@ end end end +@testset "Test integer id preservation across serialization" begin + sys = IS.SystemData() + components = IS.TestComponent[] + for i in 1:3 + component = IS.TestComponent("component_$(i)", i) + IS.add_component!(sys, component) + push!(components, component) + end + attr = IS.GeographicInfo() + IS.add_supplemental_attribute!(sys, components[1], attr) + expected_ids = Dict(IS.get_name(c) => IS.get_id(c) for c in components) + attr_id = IS.get_id(attr) + next_component_id_before = sys.next_component_id + next_attribute_id_before = sys.next_supplemental_attribute_id + + sys2, result = validate_serialization(sys) + @test result + + # Component and attribute ids are preserved. + for c in IS.get_components(IS.TestComponent, sys2) + @test IS.get_id(c) == expected_ids[IS.get_name(c)] + end + attr2 = only(IS.get_supplemental_attributes(IS.GeographicInfo, sys2)) + @test IS.get_id(attr2) == attr_id + + # Both next-id counters survive the round trip; newly added objects do not collide + # within their own stream. + @test sys2.next_component_id == next_component_id_before + @test sys2.next_supplemental_attribute_id == next_attribute_id_before + new_component = IS.TestComponent("new_component", 9) + IS.add_component!(sys2, new_component) + @test IS.get_id(new_component) == next_component_id_before + @test IS.get_id(new_component) ∉ values(expected_ids) +end + @testset "Test version info" begin data = IS.serialize_julia_info() @test haskey(data, "julia_version") diff --git a/test/test_supplemental_attributes.jl b/test/test_supplemental_attributes.jl index 353e55770..b226a28ae 100644 --- a/test/test_supplemental_attributes.jl +++ b/test/test_supplemental_attributes.jl @@ -1,7 +1,12 @@ +# The SupplementalAttributeManager-level API is normally driven by SystemData, which assigns +# integer ids. These manager-direct tests have no system, so assign ids manually to mimic +# attachment. Returns the object so it can be used inline. +_id!(obj, id) = (IS.set_id!(obj, id); obj) + @testset "Test add_supplemental_attribute" begin mgr = IS.SupplementalAttributeManager() - geo_supplemental_attribute = IS.GeographicInfo() - component = IS.TestComponent("component1", 5) + geo_supplemental_attribute = _id!(IS.GeographicInfo(), 2) + component = _id!(IS.TestComponent("component1", 5), 1) IS.add_supplemental_attribute!(mgr, component, geo_supplemental_attribute) @test length(mgr.data) == 1 @test length(mgr.data[IS.GeographicInfo]) == 1 @@ -15,9 +20,9 @@ end @testset "Test bulk addition of supplemental attributes" begin mgr = IS.SupplementalAttributeManager() - attr1 = IS.GeographicInfo(; geo_json = Dict("x" => 1.0)) - attr2 = IS.GeographicInfo(; geo_json = Dict("x" => 2.0)) - component = IS.TestComponent("component1", 1) + attr1 = _id!(IS.GeographicInfo(; geo_json = Dict("x" => 1.0)), 2) + attr2 = _id!(IS.GeographicInfo(; geo_json = Dict("x" => 2.0)), 3) + component = _id!(IS.TestComponent("component1", 1), 1) IS.begin_supplemental_attributes_update(mgr) do IS.add_supplemental_attribute!(mgr, component, attr1) IS.add_supplemental_attribute!(mgr, component, attr2) @@ -29,9 +34,9 @@ end @testset "Test bulk addition of supplemental attributes with error" begin mgr = IS.SupplementalAttributeManager() - attr1 = IS.TestSupplemental(; value = 1.0) - attr2 = IS.GeographicInfo(; geo_json = Dict("x" => 2.0)) - component = IS.TestComponent("component1", 1) + attr1 = _id!(IS.TestSupplemental(; value = 1.0), 2) + attr2 = _id!(IS.GeographicInfo(; geo_json = Dict("x" => 2.0)), 3) + component = _id!(IS.TestComponent("component1", 1), 1) @test_throws( ArgumentError, IS.begin_supplemental_attributes_update(mgr) do @@ -46,16 +51,16 @@ end @testset "Test bulk addition of supplemental attributes with error, existing attrs" begin mgr = IS.SupplementalAttributeManager() - attr1 = IS.TestSupplemental(; value = 1.0) - attr2 = IS.GeographicInfo(; geo_json = Dict("x" => 2.0)) - component = IS.TestComponent("component1", 1) + attr1 = _id!(IS.TestSupplemental(; value = 1.0), 2) + attr2 = _id!(IS.GeographicInfo(; geo_json = Dict("x" => 2.0)), 3) + component = _id!(IS.TestComponent("component1", 1), 1) IS.begin_supplemental_attributes_update(mgr) do IS.add_supplemental_attribute!(mgr, component, attr1) IS.add_supplemental_attribute!(mgr, component, attr2) end - attr3 = IS.TestSupplemental(; value = 3.0) - attr4 = IS.GeographicInfo(; geo_json = Dict("x" => 3.0)) + attr3 = _id!(IS.TestSupplemental(; value = 3.0), 4) + attr4 = _id!(IS.GeographicInfo(; geo_json = Dict("x" => 3.0)), 5) @test_throws( ArgumentError, IS.begin_supplemental_attributes_update(mgr) do @@ -72,10 +77,10 @@ end @testset "Test bulk removal of supplemental attributes with error" begin mgr = IS.SupplementalAttributeManager() - attr1 = IS.TestSupplemental(; value = 1.0) - attr2 = IS.TestSupplemental(; value = 2.0) - attr3 = IS.GeographicInfo(; geo_json = Dict("x" => 3.0)) - component = IS.TestComponent("component1", 1) + attr1 = _id!(IS.TestSupplemental(; value = 1.0), 2) + attr2 = _id!(IS.TestSupplemental(; value = 2.0), 3) + attr3 = _id!(IS.GeographicInfo(; geo_json = Dict("x" => 3.0)), 4) + component = _id!(IS.TestComponent("component1", 1), 1) IS.begin_supplemental_attributes_update(mgr) do IS.add_supplemental_attribute!(mgr, component, attr1) IS.add_supplemental_attribute!(mgr, component, attr2) @@ -133,14 +138,14 @@ end @test length(components) == 1 @test IS.get_num_supplemental_attributes(data) == 1 @test length( - IS.list_associated_component_uuids(mgr.associations, attribute, nothing), + IS.list_associated_component_ids(mgr.associations, attribute, nothing), ) == 1 end @testset "Test remove_supplemental_attribute" begin mgr = IS.SupplementalAttributeManager() - geo_supplemental_attribute = IS.GeographicInfo() - component = IS.TestComponent("component1", 5) + geo_supplemental_attribute = _id!(IS.GeographicInfo(), 2) + component = _id!(IS.TestComponent("component1", 5), 1) IS.add_supplemental_attribute!(mgr, component, geo_supplemental_attribute) @test IS.get_num_attributes(mgr.associations) == 1 @test_throws ArgumentError IS.remove_supplemental_attribute!( @@ -156,7 +161,7 @@ end @test IS.get_num_attributes(mgr.associations) == 0 @test_throws ArgumentError IS.get_supplemental_attribute( mgr, - IS.get_uuid(geo_supplemental_attribute), + IS.get_id(geo_supplemental_attribute), ) end @@ -240,7 +245,7 @@ end @test IS.get_num_time_series(data) == 0 end -@testset "Test assign_new_uuid! for component with supplemental attributes" begin +@testset "Test assign_new_id! for component with supplemental attributes" begin data = IS.SystemData() geo = IS.GeographicInfo() other = IS.TestSupplemental(; value = 1.1) @@ -248,9 +253,9 @@ end IS.add_component!(data, component1) IS.add_supplemental_attribute!(data, component1, geo) IS.add_supplemental_attribute!(data, component1, other) - IS.assign_new_uuid!(data, component1) - @test IS.get_supplemental_attribute(component1, IS.get_uuid(geo)) isa IS.GeographicInfo - @test IS.get_supplemental_attribute(component1, IS.get_uuid(other)) isa + IS.assign_new_id!(data, component1) + @test IS.get_supplemental_attribute(component1, IS.get_id(geo)) isa IS.GeographicInfo + @test IS.get_supplemental_attribute(component1, IS.get_id(other)) isa IS.TestSupplemental geo_attrs = collect(IS.get_supplemental_attributes(IS.GeographicInfo, component1)) @test length(geo_attrs) == 1 @@ -313,12 +318,12 @@ end @testset "Test optimize_database!" begin mgr = IS.SupplementalAttributeManager() - component1 = IS.TestComponent("component1", 5) - component2 = IS.TestComponent("component2", 7) + component1 = _id!(IS.TestComponent("component1", 5), 1) + component2 = _id!(IS.TestComponent("component2", 7), 2) # Add several associations to create data for optimization for i in 1:10 - attr = IS.TestSupplemental(; value = Float64(i)) + attr = _id!(IS.TestSupplemental(; value = Float64(i)), i) IS.add_supplemental_attribute!(mgr, component1, attr) IS.add_supplemental_attribute!(mgr, component2, attr) end diff --git a/test/test_system_data.jl b/test/test_system_data.jl index 94a685f5e..980b39cf3 100644 --- a/test/test_system_data.jl +++ b/test/test_system_data.jl @@ -43,7 +43,7 @@ end IS.remove_component!(data, collect(components)[1]) components = IS.get_components(IS.TestComponent, data) @test length(components) == 0 - @test isempty(data.component_uuids) + @test isempty(data.component_ids) IS.add_component!(data, component) components = @@ -313,7 +313,7 @@ end IS.check_components(data, IS.TestComponent) component = IS.get_component(IS.TestComponent, data, "component_3") IS.check_component(data, component) - @test component === IS.get_component(data, IS.get_uuid(component)) + @test component === IS.get_component(data, IS.get_id(component)) end @testset "Test component and time series counts" begin @@ -387,7 +387,7 @@ end attributes = IS.get_supplemental_attributes(IS.GeographicInfo, data) @test length(attributes) == 4 - @test IS.get_uuid(attribute_removed) ∉ IS.get_uuid.(attributes) + @test IS.get_id(attribute_removed) ∉ IS.get_id.(attributes) IS.remove_supplemental_attributes!(data, IS.GeographicInfo) attributes = IS.get_supplemental_attributes(IS.GeographicInfo, data) @@ -528,17 +528,17 @@ end ), ) == 1 - uuid1 = IS.get_uuid(attr1) - uuid2 = IS.get_uuid(attr2) - uuid3 = IS.get_uuid(geo_supplemental_attribute) - @test IS.get_supplemental_attribute(data, uuid1) === - IS.get_supplemental_attribute(component1, uuid1) - @test IS.get_supplemental_attribute(data, uuid2) === - IS.get_supplemental_attribute(component2, uuid2) - @test IS.get_supplemental_attribute(data, uuid3) === - IS.get_supplemental_attribute(component1, uuid3) - @test IS.get_supplemental_attribute(data, uuid3) === - IS.get_supplemental_attribute(component2, uuid3) + id1 = IS.get_id(attr1) + id2 = IS.get_id(attr2) + id3 = IS.get_id(geo_supplemental_attribute) + @test IS.get_supplemental_attribute(data, id1) === + IS.get_supplemental_attribute(component1, id1) + @test IS.get_supplemental_attribute(data, id2) === + IS.get_supplemental_attribute(component2, id2) + @test IS.get_supplemental_attribute(data, id3) === + IS.get_supplemental_attribute(component1, id3) + @test IS.get_supplemental_attribute(data, id3) === + IS.get_supplemental_attribute(component2, id3) end @testset "Test retrieval of components with a supplemental attribute" begin @@ -557,19 +557,51 @@ end @test components[2] === component2 end -@testset "Test assign_new_uuid" begin +@testset "Test assign_new_id" begin data = IS.SystemData() name = "component1" component = IS.TestComponent(name, 5) IS.add_component!(data, component) - uuid1 = IS.get_uuid(component) - IS.assign_new_uuid!(data, component) - uuid2 = IS.get_uuid(component) - @test uuid1 != uuid2 + id1 = IS.get_id(component) + IS.assign_new_id!(data, component) + id2 = IS.get_id(component) + @test id1 != id2 + @test IS.get_component(data, id2) === component + @test_throws ArgumentError IS.get_component(data, id1) @test IS.get_component(IS.TestComponent, data, name).name == name end +@testset "Test independent integer id streams" begin + data = IS.SystemData() + component1 = IS.TestComponent("component1", 5) + component2 = IS.TestComponent("component2", 6) + # Unattached components have no id. + @test IS.get_id(component1) == IS.UNASSIGNED_ID + IS.add_component!(data, component1) + IS.add_component!(data, component2) + @test IS.get_id(component1) == 1 + @test IS.get_id(component2) == 2 + + # Components and supplemental attributes have independent id streams, each starting at 1, + # so a component and an attribute may share a numeric id. + attr1 = IS.GeographicInfo() + attr2 = IS.TestSupplemental(; value = 1.0) + IS.add_supplemental_attribute!(data, component1, attr1) + IS.add_supplemental_attribute!(data, component1, attr2) + @test IS.get_id(attr1) == 1 + @test IS.get_id(attr2) == 2 + + # Component ids are unique among components; attribute ids unique among attributes. + component_ids = IS.get_id.(IS.get_components(IS.TestComponent, data)) + @test length(component_ids) == length(unique(component_ids)) + attribute_ids = vcat( + IS.get_id.(IS.get_supplemental_attributes(IS.GeographicInfo, data)), + IS.get_id.(IS.get_supplemental_attributes(IS.TestSupplemental, data)), + ) + @test length(attribute_ids) == length(unique(attribute_ids)) +end + @testset "Test bulk add of time series" begin for in_memory in (false, true) sys = IS.SystemData(; time_series_in_memory = in_memory) diff --git a/test/test_time_series.jl b/test/test_time_series.jl index 677e96af6..184ea5ada 100644 --- a/test/test_time_series.jl +++ b/test/test_time_series.jl @@ -3080,7 +3080,7 @@ end end end -@testset "Test assign_new_uuid_internal! for component with time series" begin +@testset "Test assign_new_id! for component with time series" begin for in_memory in (true, false) sys = IS.SystemData(; time_series_in_memory = in_memory) name = "Component1" @@ -3101,12 +3101,12 @@ end @test IS.get_time_series(IS.SingleTimeSeries, component, name) isa IS.SingleTimeSeries - old_uuid = IS.get_uuid(component) - IS.assign_new_uuid_internal!(component) - new_uuid = IS.get_uuid(component) - @test old_uuid != new_uuid + old_id = IS.get_id(component) + IS.assign_new_id!(sys, component) + new_id = IS.get_id(component) + @test old_id != new_id - # The time series storage uses component UUIDs, so they must get updated. + # The time series storage uses component ids, so they must get updated. @test IS.get_time_series(IS.SingleTimeSeries, component, name) isa IS.SingleTimeSeries end @@ -3849,7 +3849,7 @@ end row.window_count, row.length, row.name, - row.owner_uuid, + row.owner_id, row.owner_type, row.owner_category, row.features, @@ -3871,7 +3871,7 @@ end "window_count INTEGER", "length INTEGER", "name TEXT NOT NULL", - "owner_uuid TEXT NOT NULL", + "owner_id INTEGER NOT NULL", "owner_type TEXT NOT NULL", "owner_category TEXT NOT NULL", "features TEXT NOT NULL", @@ -3897,7 +3897,7 @@ end "window_count", "length", "name", - "owner_uuid", + "owner_id", "owner_type", "owner_category", "features", @@ -3946,7 +3946,7 @@ function _setup_for_migration_tests_from_IS_v2_3() missing, IS.get_length(metadata), IS.get_name(metadata), - string(IS.get_uuid(component)), + IS.get_id(component), string(nameof(typeof(component))), owner_category, features, @@ -4036,7 +4036,7 @@ function _create_metadata_table_v2_3!(db::SQLite.DB) "window_count INTEGER", "length INTEGER", "name TEXT NOT NULL", - "owner_uuid TEXT NOT NULL", + "owner_id INTEGER NOT NULL", "owner_type TEXT NOT NULL", "owner_category TEXT NOT NULL", "features TEXT NOT NULL", From f896870f3b5b642a502daa2666c65620179dbfa4 Mon Sep 17 00:00:00 2001 From: Daniel Thom Date: Thu, 11 Jun 2026 16:52:22 -0600 Subject: [PATCH 2/4] Add owner_category to the by_c_n_tst_features unique index Component and supplemental attribute ids are independent streams and may collide numerically, so the unique time-series index must key on (owner_id, owner_category), not owner_id alone. Without this, a component and an attribute that share an id and have a time series with the same name/type/params violate the unique constraint. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/time_series_metadata_store.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/time_series_metadata_store.jl b/src/time_series_metadata_store.jl index 5aa80c836..d222ebc7f 100644 --- a/src/time_series_metadata_store.jl +++ b/src/time_series_metadata_store.jl @@ -5,8 +5,12 @@ const DB_FILENAME = "time_series_metadata.db" # This version is also used in the Python package infrasys. const TS_METADATA_FORMAT_VERSION = "1.1.0" const TS_DB_INDEXES = Dict( + # owner_category is part of the key because component and supplemental attribute ids are + # independent streams and may collide numerically; (owner_id, owner_category) identifies + # an owner uniquely. "by_c_n_tst_features" => [ "owner_id", + "owner_category", "time_series_type", "name", "resolution", From 2707dc4a73b89ad956b2291f99cdbb9aa28156f8 Mon Sep 17 00:00:00 2001 From: Daniel Thom Date: Tue, 16 Jun 2026 08:44:48 -0600 Subject: [PATCH 3/4] Fix ID check --- src/component.jl | 4 +- src/internal.jl | 6 +-- src/supplemental_attribute_associations.jl | 20 ++++----- src/supplemental_attribute_manager.jl | 4 +- src/system_data.jl | 47 ++++++++++++---------- test/test_internal.jl | 4 +- test/test_system_data.jl | 10 ++--- test/test_time_series.jl | 2 +- 8 files changed, 51 insertions(+), 46 deletions(-) diff --git a/src/component.jl b/src/component.jl index 0fdbeef02..11257b4a6 100644 --- a/src/component.jl +++ b/src/component.jl @@ -1,6 +1,6 @@ """ -Assign a new integer id to the component, drawn from the system counter, and update any -references to its old id in the time series metadata store and supplemental attribute +Assign a new integer ID to the component, drawn from the system counter, and update any +references to its old ID in the time series metadata store and supplemental attribute associations. """ function assign_new_id_internal!(data, component::InfrastructureSystemsComponent) diff --git a/src/internal.jl b/src/internal.jl index 26bd0586b..de4039673 100644 --- a/src/internal.jl +++ b/src/internal.jl @@ -52,7 +52,7 @@ mutable struct InfrastructureSystemsInternal <: InfrastructureSystemsType end """ -Creates InfrastructureSystemsInternal with a new UUID and an unassigned integer id. +Creates InfrastructureSystemsInternal with a new UUID and an unassigned integer ID. """ InfrastructureSystemsInternal(; id = UNASSIGNED_ID, @@ -113,7 +113,7 @@ function get_uuid(obj::InfrastructureSystemsType) end """ -Gets the integer id of a component or supplemental attribute. Returns [`UNASSIGNED_ID`](@ref) +Get the integer id of a component or supplemental attribute. Returns [`UNASSIGNED_ID`](@ref) if the object has not been attached to a [`SystemData`](@ref). """ function get_id(obj::InfrastructureSystemsType) @@ -121,7 +121,7 @@ function get_id(obj::InfrastructureSystemsType) end """ -Sets the integer id of a component or supplemental attribute. +Set the integer ID of a component or supplemental attribute. """ function set_id!(obj::InfrastructureSystemsType, id::Int) set_id!(get_internal(obj), id) diff --git a/src/supplemental_attribute_associations.jl b/src/supplemental_attribute_associations.jl index 384e647cc..1b63455ae 100644 --- a/src/supplemental_attribute_associations.jl +++ b/src/supplemental_attribute_associations.jl @@ -310,7 +310,7 @@ function has_association( ) end -# component ids from attribute +# component IDs from attribute const _QUERY_LIST_ASSOCIATED_COMP_IDS = """ SELECT DISTINCT component_id FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME @@ -318,7 +318,7 @@ const _QUERY_LIST_ASSOCIATED_COMP_IDS = """ """ """ -Return the distinct component ids associated with the attribute. +Return the distinct component IDs associated with the attribute. """ function list_associated_component_ids( associations::SupplementalAttributeAssociations, @@ -363,7 +363,7 @@ list_associated_supplemental_attribute_ids( component::InfrastructureSystemsComponent, ) = list_associated_supplemental_attribute_ids(associations, component, nothing) -# component ids from attribute plus component type +# component IDs from attribute plus component type const _QUERY_LIST_ASSOCIATED_COMP_IDS_BY_C_TYPE = StringTemplates.@template """ SELECT DISTINCT component_id FROM $table_name @@ -371,7 +371,7 @@ const _QUERY_LIST_ASSOCIATED_COMP_IDS_BY_C_TYPE = StringTemplates.@template """ """ """ -Return the distinct component ids associated with the attribute, filter by component type. +Return the distinct component IDs associated with the attribute, filter by component type. """ function list_associated_component_ids( associations::SupplementalAttributeAssociations, @@ -420,9 +420,9 @@ function list_associated_supplemental_attribute_ids( return collect(Int, table.attribute_id) end -# component ids from attribute type driver function +# component IDs from attribute type driver function """ -Return the distinct component ids associated with the attribute type. +Return the distinct component IDs associated with the attribute type. """ function list_associated_component_ids( associations::SupplementalAttributeAssociations, @@ -457,7 +457,7 @@ function list_associated_supplemental_attribute_ids( return _list_associated_supplemental_attribute_ids(associations, subtypes) end -# component ids from attribute type plus component type +# component IDs from attribute type plus component type const _QUERY_LIST_ASSOCIATED_COMP_IDS_BY_TYPES = StringTemplates.@template """ SELECT DISTINCT component_id FROM $table_name @@ -465,7 +465,7 @@ const _QUERY_LIST_ASSOCIATED_COMP_IDS_BY_TYPES = StringTemplates.@template """ """ """ -Return the distinct component ids associated with the attribute type, filter by +Return the distinct component IDs associated with the attribute type, filter by component_type. """ function list_associated_component_ids( @@ -567,7 +567,7 @@ function _get_type_clause!( return type_clause end -# component ids from attribute type implementation +# component IDs from attribute type implementation const _QUERY_LIST_ASSOCIATED_COMP_IDS_BY_ONE_TYPE = """ SELECT DISTINCT component_id FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME @@ -682,7 +682,7 @@ const _QUERY_REPLACE_COMP_ID_SA = """ """ """ -Replace the component id in the table. +Replace the component ID in the table. """ function replace_component_id!( associations::SupplementalAttributeAssociations, diff --git a/src/supplemental_attribute_manager.jl b/src/supplemental_attribute_manager.jl index 31ccaaab6..ebfea54c1 100644 --- a/src/supplemental_attribute_manager.jl +++ b/src/supplemental_attribute_manager.jl @@ -4,7 +4,7 @@ const SupplementalAttributesByType = """ Owns supplemental attributes and their associations to components in a [`SystemData`](@ref). -Attributes are stored by type and integer id. Component links are tracked in +Attributes are stored by type and integer ID. Component links are tracked in [`SupplementalAttributeAssociations`](@ref). User code typically calls [`add_supplemental_attribute!`](@ref) on [`SystemData`](@ref) rather than on the manager directly. @@ -103,7 +103,7 @@ function is_attached(attribute::SupplementalAttribute, mgr::SupplementalAttribut isnothing(_attribute) && return false if attribute !== _attribute - @warn "An attribute with the same id as $(summary(attribute)) is stored in " * + @warn "An attribute with the same ID as $(summary(attribute)) is stored in " * "the system but is not the same instance." return false end diff --git a/src/system_data.jl b/src/system_data.jl index 4289e2e32..769b5eebe 100644 --- a/src/system_data.jl +++ b/src/system_data.jl @@ -20,11 +20,11 @@ Container for system components and time series data mutable struct SystemData <: ComponentContainer components::Components masked_components::Components - "Maps the integer id of every attached component, regular and masked, to the component." + "Maps the integer ID of every attached component, regular and masked, to the component." component_ids::Dict{Int, <:InfrastructureSystemsComponent} - "Next integer id to assign to a component. Independent of the supplemental attribute id stream. Starts at 1." + "Next integer ID to assign to a component. Independent of the supplemental attribute ID stream. Starts at 1." next_component_id::Int - "Next integer id to assign to a supplemental attribute. Independent of the component id stream. Starts at 1." + "Next integer ID to assign to a supplemental attribute. Independent of the component ID stream. Starts at 1." next_supplemental_attribute_id::Int "User-defined subystems. Components can be regular or masked." subsystems::Dict{String, Set{Int}} @@ -108,7 +108,7 @@ function SystemData( end """ -Return the next integer id to assign to a component and advance the component counter. +Return the next integer ID to assign to a component and advance the component counter. """ function get_next_component_id!(data::SystemData) id = data.next_component_id @@ -117,7 +117,7 @@ function get_next_component_id!(data::SystemData) end """ -Return the next integer id to assign to a supplemental attribute and advance the +Return the next integer ID to assign to a supplemental attribute and advance the supplemental attribute counter. """ function get_next_supplemental_attribute_id!(data::SystemData) @@ -412,6 +412,11 @@ function compare_values( # the components. continue end + if !compare_uuids && name in (:next_component_id, :next_supplemental_attribute_id) + # These counters track the integer ID streams and only match when the ids + # themselves match, so skip them unless ids/UUIDs are being compared. + continue + end val_x = getproperty(x, name) val_y = getproperty(y, name) if !compare_values( @@ -456,7 +461,7 @@ end function _handle_component_removal!(data::SystemData, component) id = get_id(component) if !haskey(data.component_ids, id) - error("Bug: component = $(summary(component)) did not have its id stored $id") + error("Bug: component = $(summary(component)) did not have its ID stored $id") end pop!(data.component_ids, id) @@ -1062,9 +1067,9 @@ function deserialize( read_only = time_series_read_only, metadata_store = time_series_metadata_store, ) - subsystems = Dict(k => Set(Int.(v)) for (k, v) in raw["subsystems"]) - next_component_id = Int(get(raw, "next_component_id", 1)) - next_supplemental_attribute_id = Int(get(raw, "next_supplemental_attribute_id", 1)) + subsystems = Dict(k => Set{Int}(v) for (k, v) in raw["subsystems"]) + next_component_id = raw["next_component_id"] + next_supplemental_attribute_id = raw["next_supplemental_attribute_id"] supplemental_attribute_manager = deserialize( SupplementalAttributeManager, get( @@ -1103,13 +1108,13 @@ function deserialize( system_component_ids = Set{Int}() for component in Iterators.Flatten((raw["components"], raw["masked_components"])) - push!(system_component_ids, Int(component["internal"]["id"])) + push!(system_component_ids, component["internal"]["id"]) end for (name, subsystem_component_ids) in sys.subsystems if !issubset(subsystem_component_ids, system_component_ids) diff = setdiff(subsystem_component_ids, system_component_ids) - error("Subsystem $name has component ids that are not in the system: $diff") + error("Subsystem $name has component IDs that are not in the system: $diff") end end @@ -1121,14 +1126,14 @@ end # Redirect functions to Components """ -Assign an integer id to a component being attached to `data`, drawn from the component id +Assign an integer ID to a component being attached to `data`, drawn from the component ID stream. A freshly constructed component has [`UNASSIGNED_ID`](@ref) and receives the next component -id. A component that already carries an id (for example, one restored during +ID. A component that already carries an id (for example, one restored during deserialization) keeps it; the counter is advanced past it so future ids do not collide. -Components and supplemental attributes have independent id streams, so a component and an -attribute may share a numeric id. +Components and supplemental attributes have independent ID streams, so a component and an +attribute may share a numeric ID. """ function assign_id!(data::SystemData, component::InfrastructureSystemsComponent) id = get_id(component) @@ -1142,8 +1147,8 @@ function assign_id!(data::SystemData, component::InfrastructureSystemsComponent) end """ -Assign an integer id to a supplemental attribute being attached to `data`, drawn from the -supplemental attribute id stream (independent of the component id stream). +Assign an integer ID to a supplemental attribute being attached to `data`, drawn from the +supplemental attribute ID stream (independent of the component ID stream). """ function assign_id!(data::SystemData, attribute::SupplementalAttribute) id = get_id(attribute) @@ -1159,7 +1164,7 @@ end """ Add a component to a [`SystemData`](@ref) instance. -Assigns the component's integer id, wires [`SharedSystemReferences`](@ref) for time series +Assigns the component's integer ID, wires [`SharedSystemReferences`](@ref) for time series and supplemental attributes, and delegates storage to the underlying [`Components`](@ref) container. @@ -1222,7 +1227,7 @@ get_component(::Type{T}, data::SystemData, args...) where {T} = function get_component(data::SystemData, id::Int) component = get(data.component_ids, id, nothing) if isnothing(component) - throw(ArgumentError("No component with id = $id is stored.")) + throw(ArgumentError("No component with ID = $id is stored.")) end return component @@ -1244,7 +1249,7 @@ end function assign_new_id!(data::SystemData, component::InfrastructureSystemsComponent) orig_id = get_id(component) if isnothing(pop!(data.component_ids, orig_id, nothing)) - throw(ArgumentError("component with id = $orig_id is not stored.")) + throw(ArgumentError("component with ID = $orig_id is not stored.")) end assign_new_id_internal!(data, component) @@ -1404,7 +1409,7 @@ function get_masked_component(data::SystemData, id::Int) end end - @error "no component with id $id is stored" + @error "no component with ID $id is stored" return nothing end diff --git a/test/test_internal.jl b/test/test_internal.jl index 4da47c053..dee4da2b8 100644 --- a/test/test_internal.jl +++ b/test/test_internal.jl @@ -1,6 +1,6 @@ -@testset "Test integer id" begin +@testset "Test integer ID" begin component = IS.TestComponent("component", 5) - # A freshly constructed component has no id until attached to a system. + # A freshly constructed component has no ID until attached to a system. @test IS.get_id(component) == IS.UNASSIGNED_ID IS.set_id!(component, 5) @test IS.get_id(component) == 5 diff --git a/test/test_system_data.jl b/test/test_system_data.jl index 980b39cf3..c40e6673d 100644 --- a/test/test_system_data.jl +++ b/test/test_system_data.jl @@ -572,19 +572,19 @@ end @test IS.get_component(IS.TestComponent, data, name).name == name end -@testset "Test independent integer id streams" begin +@testset "Test independent integer ID streams" begin data = IS.SystemData() component1 = IS.TestComponent("component1", 5) component2 = IS.TestComponent("component2", 6) - # Unattached components have no id. + # Unattached components have no ID. @test IS.get_id(component1) == IS.UNASSIGNED_ID IS.add_component!(data, component1) IS.add_component!(data, component2) @test IS.get_id(component1) == 1 @test IS.get_id(component2) == 2 - # Components and supplemental attributes have independent id streams, each starting at 1, - # so a component and an attribute may share a numeric id. + # Components and supplemental attributes have independent ID streams, each starting at 1, + # so a component and an attribute may share a numeric ID. attr1 = IS.GeographicInfo() attr2 = IS.TestSupplemental(; value = 1.0) IS.add_supplemental_attribute!(data, component1, attr1) @@ -592,7 +592,7 @@ end @test IS.get_id(attr1) == 1 @test IS.get_id(attr2) == 2 - # Component ids are unique among components; attribute ids unique among attributes. + # Component IDs are unique among components; attribute ids unique among attributes. component_ids = IS.get_id.(IS.get_components(IS.TestComponent, data)) @test length(component_ids) == length(unique(component_ids)) attribute_ids = vcat( diff --git a/test/test_time_series.jl b/test/test_time_series.jl index 184ea5ada..8cf51c200 100644 --- a/test/test_time_series.jl +++ b/test/test_time_series.jl @@ -3106,7 +3106,7 @@ end new_id = IS.get_id(component) @test old_id != new_id - # The time series storage uses component ids, so they must get updated. + # The time series storage uses component IDs, so they must get updated. @test IS.get_time_series(IS.SingleTimeSeries, component, name) isa IS.SingleTimeSeries end From 60743fba68ffacc153c9b0899172544ac59bc4f2 Mon Sep 17 00:00:00 2001 From: Daniel Thom Date: Mon, 22 Jun 2026 09:36:03 -0600 Subject: [PATCH 4/4] Address Copilot review: subsystem ID remap, ID guards, docstring/typo fixes - assign_new_id_internal!: remap the component's ID in subsystem membership sets, alongside the existing time-series metadata and supplemental-attribute association updates (previously left stale, breaking subsystem lookups after reassignment) - Guard against UNASSIGNED_ID when attaching attributes and adding associations on the manager-direct path, throwing actionable errors instead of colliding at id 0 - Fix "owner UUID" -> "owner ID" docstring in list_metadata_with_owner_id - Fix "subystem(s)" typos in system_data.jl and subsystems.jl - Tests: assert subsystem membership tracks the new ID after assign_new_id!; assign IDs in manager-direct supplemental attribute tests Co-Authored-By: Claude Opus 4.8 (1M context) --- src/component.jl | 11 +++++++++-- src/subsystems.jl | 10 +++++----- src/supplemental_attribute_associations.jl | 14 ++++++++++++-- src/supplemental_attribute_manager.jl | 12 +++++++++++- src/system_data.jl | 2 +- src/time_series_metadata_store.jl | 2 +- test/test_supplemental_attributes.jl | 8 ++++---- test/test_system_data.jl | 8 ++++++++ 8 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/component.jl b/src/component.jl index 11257b4a6..b4c0fa729 100644 --- a/src/component.jl +++ b/src/component.jl @@ -1,7 +1,7 @@ """ Assign a new integer ID to the component, drawn from the system counter, and update any -references to its old ID in the time series metadata store and supplemental attribute -associations. +references to its old ID in the time series metadata store, supplemental attribute +associations, and subsystem membership sets. """ function assign_new_id_internal!(data, component::InfrastructureSystemsComponent) old_id = get_id(component) @@ -16,6 +16,13 @@ function assign_new_id_internal!(data, component::InfrastructureSystemsComponent replace_component_id!(associations, old_id, new_id) end + for ids in values(data.subsystems) + if old_id in ids + pop!(ids, old_id) + push!(ids, new_id) + end + end + set_id!(get_internal(component), new_id) return end diff --git a/src/subsystems.jl b/src/subsystems.jl index d0f53b51c..84f64956d 100644 --- a/src/subsystems.jl +++ b/src/subsystems.jl @@ -7,7 +7,7 @@ function add_subsystem!(data::SystemData, subsystem_name::AbstractString) end data.subsystems[subsystem_name] = Set{Int}() - @debug "Added subystem $subsystem_name" _group = LOG_GROUP_SYSTEM + @debug "Added subsystem $subsystem_name" _group = LOG_GROUP_SYSTEM return end @@ -33,7 +33,7 @@ Throws ArgumentError if the subsystem name is not stored. function remove_subsystem!(data::SystemData, subsystem_name::AbstractString) _throw_if_not_stored(data, subsystem_name) container = pop!(data.subsystems, subsystem_name) - @debug "Removed subystem $subsystem_name" length(container) _group = LOG_GROUP_SYSTEM + @debug "Removed subsystem $subsystem_name" length(container) _group = LOG_GROUP_SYSTEM return end @@ -61,7 +61,7 @@ function add_component_to_subsystem!( end push!(container, id) - @debug "Added $(summary(component)) to subystem $subsystem_name" _group = + @debug "Added $(summary(component)) to subsystem $subsystem_name" _group = LOG_GROUP_SYSTEM return end @@ -98,7 +98,7 @@ function remove_component_from_subsystem!( end pop!(data.subsystems[subsystem_name], get_id(component)) - @debug "Removed $(summary(component)) from subystem $subsystem_name" _group = + @debug "Removed $(summary(component)) from subsystem $subsystem_name" _group = LOG_GROUP_SYSTEM return end @@ -110,7 +110,7 @@ function remove_component_from_subsystems!( id = get_id(component) for (subsystem_name, ids) in data.subsystems pop!(ids, id, nothing) - @debug "Removed $(summary(component)) from subystem $subsystem_name" _group = + @debug "Removed $(summary(component)) from subsystem $subsystem_name" _group = LOG_GROUP_SYSTEM end return diff --git a/src/supplemental_attribute_associations.jl b/src/supplemental_attribute_associations.jl index 1b63455ae..a97eed476 100644 --- a/src/supplemental_attribute_associations.jl +++ b/src/supplemental_attribute_associations.jl @@ -128,10 +128,20 @@ function add_association!( attribute::SupplementalAttribute, ) TimerOutputs.@timeit_debug SYSTEM_TIMERS "add supplemental attribute association" begin + attribute_id = get_id(attribute) + component_id = get_id(component) + if attribute_id == UNASSIGNED_ID || component_id == UNASSIGNED_ID + throw( + ArgumentError( + "cannot associate $(summary(attribute)) with $(summary(component)): " * + "both must have assigned IDs (attach them to `SystemData` first).", + ), + ) + end row = ( - get_id(attribute), + attribute_id, string(nameof(typeof(attribute))), - get_id(component), + component_id, string(nameof(typeof(component))), ) placeholder = chop(repeat("?,", length(row))) diff --git a/src/supplemental_attribute_manager.jl b/src/supplemental_attribute_manager.jl index ebfea54c1..8f753ef74 100644 --- a/src/supplemental_attribute_manager.jl +++ b/src/supplemental_attribute_manager.jl @@ -89,11 +89,21 @@ function _attach_attribute!( ) end + id = get_id(attribute) + if id == UNASSIGNED_ID + throw( + ArgumentError( + "$(summary(attribute)) has an unassigned ID; attach it through " * + "`add_supplemental_attribute!` on `SystemData` so an ID is assigned first.", + ), + ) + end + T = typeof(attribute) if !haskey(mgr.data, T) mgr.data[T] = Dict{Int, T}() end - mgr.data[T][get_id(attribute)] = attribute + mgr.data[T][id] = attribute end function is_attached(attribute::SupplementalAttribute, mgr::SupplementalAttributeManager) diff --git a/src/system_data.jl b/src/system_data.jl index 769b5eebe..7c8dabc2e 100644 --- a/src/system_data.jl +++ b/src/system_data.jl @@ -26,7 +26,7 @@ mutable struct SystemData <: ComponentContainer next_component_id::Int "Next integer ID to assign to a supplemental attribute. Independent of the component ID stream. Starts at 1." next_supplemental_attribute_id::Int - "User-defined subystems. Components can be regular or masked." + "User-defined subsystems. Components can be regular or masked." subsystems::Dict{String, Set{Int}} supplemental_attribute_manager::SupplementalAttributeManager time_series_manager::TimeSeriesManager diff --git a/src/time_series_metadata_store.jl b/src/time_series_metadata_store.jl index d222ebc7f..35b2de3be 100644 --- a/src/time_series_metadata_store.jl +++ b/src/time_series_metadata_store.jl @@ -1230,7 +1230,7 @@ function list_metadata( end """ -Return a Vector of NamedTuple of owner UUID and time series metadata matching the inputs. +Return a Vector of NamedTuple of owner ID and time series metadata matching the inputs. """ function list_metadata_with_owner_id( store::TimeSeriesMetadataStore, diff --git a/test/test_supplemental_attributes.jl b/test/test_supplemental_attributes.jl index b226a28ae..33e7a72a9 100644 --- a/test/test_supplemental_attributes.jl +++ b/test/test_supplemental_attributes.jl @@ -184,16 +184,16 @@ end @testset "Test iterate_SupplementalAttributeManager" begin mgr = IS.SupplementalAttributeManager() - geo_supplemental_attribute = IS.GeographicInfo() - component = IS.TestComponent("component1", 5) + geo_supplemental_attribute = _id!(IS.GeographicInfo(), 2) + component = _id!(IS.TestComponent("component1", 5), 1) IS.add_supplemental_attribute!(mgr, component, geo_supplemental_attribute) @test length(collect(IS.iterate_supplemental_attributes(mgr))) == 1 end @testset "Summarize SupplementalAttributeManager" begin mgr = IS.SupplementalAttributeManager() - geo_supplemental_attribute = IS.GeographicInfo() - component = IS.TestComponent("component1", 5) + geo_supplemental_attribute = _id!(IS.GeographicInfo(), 2) + component = _id!(IS.TestComponent("component1", 5), 1) IS.add_supplemental_attribute!(mgr, component, geo_supplemental_attribute) summary(devnull, mgr) end diff --git a/test/test_system_data.jl b/test/test_system_data.jl index c40e6673d..f03673168 100644 --- a/test/test_system_data.jl +++ b/test/test_system_data.jl @@ -564,12 +564,20 @@ end component = IS.TestComponent(name, 5) IS.add_component!(data, component) id1 = IS.get_id(component) + subsystem_name = "subsystem1" + IS.add_subsystem!(data, subsystem_name) + IS.add_component_to_subsystem!(data, subsystem_name, component) IS.assign_new_id!(data, component) id2 = IS.get_id(component) @test id1 != id2 @test IS.get_component(data, id2) === component @test_throws ArgumentError IS.get_component(data, id1) @test IS.get_component(IS.TestComponent, data, name).name == name + # The subsystem membership set must track the new ID after reassignment. + @test IS.has_component(data, subsystem_name, component) + @test collect(IS.get_subsystem_components(data, subsystem_name)) == [component] + @test id2 in IS.get_component_ids(data, subsystem_name) + @test !(id1 in IS.get_component_ids(data, subsystem_name)) end @testset "Test independent integer ID streams" begin