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..b4c0fa729 100644 --- a/src/component.jl +++ b/src/component.jl @@ -1,20 +1,29 @@ """ -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, supplemental attribute +associations, and subsystem membership sets. """ -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) + 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 @@ -47,8 +56,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 +94,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 +125,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 +141,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..de4039673 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 +""" +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) + return get_internal(obj).id +end + +""" +Set 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..84f64956d 100644 --- a/src/subsystems.jl +++ b/src/subsystems.jl @@ -6,8 +6,8 @@ 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}() - @debug "Added subystem $subsystem_name" _group = LOG_GROUP_SYSTEM + data.subsystems[subsystem_name] = Set{Int}() + @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 @@ -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,8 +60,8 @@ function add_component_to_subsystem!( ) end - push!(container, uuid) - @debug "Added $(summary(component)) to subystem $subsystem_name" _group = + push!(container, id) + @debug "Added $(summary(component)) to subsystem $subsystem_name" _group = LOG_GROUP_SYSTEM return end @@ -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,8 +97,8 @@ function remove_component_from_subsystem!( ) end - pop!(data.subsystems[subsystem_name], get_uuid(component)) - @debug "Removed $(summary(component)) from subystem $subsystem_name" _group = + pop!(data.subsystems[subsystem_name], get_id(component)) + @debug "Removed $(summary(component)) from subsystem $subsystem_name" _group = LOG_GROUP_SYSTEM return end @@ -107,10 +107,10 @@ function remove_component_from_subsystems!( data::SystemData, component::InfrastructureSystemsComponent, ) - uuid = get_uuid(component) - for (subsystem_name, uuids) in data.subsystems - pop!(uuids, uuid, nothing) - @debug "Removed $(summary(component)) from subystem $subsystem_name" _group = + id = get_id(component) + for (subsystem_name, ids) in data.subsystems + pop!(ids, id, nothing) + @debug "Removed $(summary(component)) from subsystem $subsystem_name" _group = LOG_GROUP_SYSTEM end return @@ -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..a97eed476 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, @@ -117,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 = ( - string(get_uuid(attribute)), + attribute_id, string(nameof(typeof(attribute))), - string(get_uuid(component)), + component_id, string(nameof(typeof(component))), ) placeholder = chop(repeat("?,", length(row))) @@ -184,7 +205,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 +217,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 +240,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 +254,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 +263,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 +273,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 +284,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 +302,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 +312,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 +320,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 +487,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 +517,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 +544,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 +577,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 +662,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 +685,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 +731,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 +743,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 +803,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..8f753ef74 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 @@ -79,21 +89,31 @@ 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{Base.UUID, T}() + mgr.data[T] = Dict{Int, T}() end - mgr.data[T][get_uuid(attribute)] = attribute + mgr.data[T][id] = 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 +190,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 +250,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 +304,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..7c8dabc2e 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} - "User-defined subystems. Components can be regular or masked." - subsystems::Dict{String, Set{Base.UUID}} + "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 subsystems. Components can be regular or masked." + 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,11 +407,16 @@ 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 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( @@ -425,12 +459,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 +503,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 +518,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 +745,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 +765,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 +961,8 @@ function to_dict(data::SystemData) ( :components, :masked_components, + :next_component_id, + :next_supplemental_attribute_id, :subsystems, :supplemental_attribute_manager, :internal, @@ -1031,7 +1067,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 = raw["next_component_id"] + next_supplemental_attribute_id = raw["next_supplemental_attribute_id"] supplemental_attribute_manager = deserialize( SupplementalAttributeManager, get( @@ -1051,30 +1089,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, 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 +1125,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 +1190,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 +1206,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 +1224,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 +1243,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 +1263,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 +1272,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 +1286,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 +1311,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 +1335,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 +1358,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 +1402,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 +1505,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 +1537,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..35b2de3be 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_uuid", + "owner_id", + "owner_category", "time_series_type", "name", "resolution", @@ -91,7 +95,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 +218,7 @@ function _migrate_from_v2_3(store::TimeSeriesMetadataStore) SELECT id ,time_series_type - ,owner_uuid + ,owner_id ,owner_type ,owner_category ,features @@ -261,7 +265,7 @@ function _migrate_from_v2_4(store::TimeSeriesMetadataStore) SELECT id ,time_series_type - ,owner_uuid + ,owner_id ,owner_type ,owner_category ,features @@ -290,7 +294,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 +317,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 +343,7 @@ function _add_migrated_rows!(store::TimeSeriesMetadataStore, rows) "window_count", "length", "name", - "owner_uuid", + "owner_id", "owner_type", "owner_category", "features", @@ -382,7 +386,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 +812,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 +1023,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 +1081,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)..., @@ -1223,9 +1230,9 @@ 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_uuid( +function list_metadata_with_owner_id( store::TimeSeriesMetadataStore, owner_type::Type{<:TimeSeriesOwners}; time_series_type::Union{Type{<:TimeSeriesData}, Nothing} = nothing, @@ -1243,20 +1250,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 +1284,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 +1406,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 +1470,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 +1503,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 +1592,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 +1706,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..dee4da2b8 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..33e7a72a9 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 @@ -179,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 @@ -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..f03673168 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,17 +557,57 @@ 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) + 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 + 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 diff --git a/test/test_time_series.jl b/test/test_time_series.jl index 677e96af6..8cf51c200 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",