From 2c1ba457ce802ee66e028d018527b805b78827a5 Mon Sep 17 00:00:00 2001 From: Manuel Marin Date: Wed, 1 Jul 2020 19:53:40 -0400 Subject: [PATCH] Get rid of `ObjectGroup` and use `Object` also for groups It's better to manage a single type, because of how things work down the line, especially when creating NamedTuples with Object values. In particular, an array of NamedTuples will only have the 'right' type if all the values for a given key have the same type. So, if for some of the elements, the key `:node` has a value of type `Object` and for some others `ObjectGroup`, julia won't be able to determine the 'right' element type for the array and some of our overrides in SpineOpt won't work. We also introduce `members` to replace `expand`, and `groups` to return an array of all `Object`s where an `Object` passed as argument happens to be a member. --- src/SpineInterface.jl | 2 ++ src/api.jl | 7 ++++++ src/base.jl | 12 ++++------ src/constructors.jl | 3 +-- src/types.jl | 55 ++++++++++++++++++++----------------------- src/using_spinedb.jl | 39 ++++++++++++++++++++---------- test/using_spinedb.jl | 16 +++++++++---- 7 files changed, 77 insertions(+), 57 deletions(-) diff --git a/src/SpineInterface.jl b/src/SpineInterface.jl index 05dcbb1..8f039c7 100644 --- a/src/SpineInterface.jl +++ b/src/SpineInterface.jl @@ -31,6 +31,8 @@ export using_spinedb export anything export indices export write_parameters +export members +export groups export blocks export duration export start diff --git a/src/api.jl b/src/api.jl index 8935e9f..5e1d69b 100644 --- a/src/api.jl +++ b/src/api.jl @@ -197,6 +197,13 @@ function (p::Parameter)(;_strict=true, prefix=(), i=nothing, t=nothing, kwargs.. nothing end +members(::Anything) = anything +members(x::Object...) = unique(member for obj in x for member in obj.members) +members(x::Array{Object,1}) = unique(member for obj in x for member in obj.members) + +groups(x::Object...) = unique(group for obj in x for group in obj.groups) +groups(x::Array{Object,1}) = unique(group for obj in x for group in obj.groups) + """ (::TimeSliceMap)(t::TimeSlice...) diff --git a/src/base.jl b/src/base.jl index 47e4775..87761db 100644 --- a/src/base.jl +++ b/src/base.jl @@ -22,15 +22,12 @@ Base.intersect(s::T, ::Anything) where T<:AbstractArray = s Base.in(item, ::Anything) = true -Base.iterate(o::Object) = iterate((o,)) -Base.iterate(o::Object, state::T) where T = iterate((o,), state) -Base.iterate(t::TimeSlice) = iterate((t,)) -Base.iterate(t::TimeSlice, state::T) where T = iterate((t,), state) +Base.iterate(o::Union{Object,TimeSlice}) = iterate((o,)) +Base.iterate(o::Union{Object,TimeSlice}, state::T) where T = iterate((o,), state) Base.iterate(v::ScalarParameterValue) = iterate((v,)) Base.iterate(v::ScalarParameterValue, state::T) where T = iterate((v,), state) -Base.length(t::TimeSlice) = 1 -Base.length(o::Object) = 1 +Base.length(t::Union{Object,TimeSlice}) = 1 Base.length(v::ScalarParameterValue) = 1 Base.isless(o1::Object, o2::Object) = o1.name < o2.name @@ -41,8 +38,7 @@ Base.:(==)(o1::Object, o2::Object) = o1.id == o2.id Base.:(==)(a::TimeSlice, b::TimeSlice) = a.id == b.id Base.hash(::Anything) = zero(UInt64) -Base.hash(o::Object) = o.id -Base.hash(t::TimeSlice) = t.id +Base.hash(o::Union{Object,TimeSlice}) = o.id Base.hash(r::RelationshipLike{K}) where {K} = hash(values(r)) Base.show(io::IO, ::Anything) = print(io, "anything") diff --git a/src/constructors.jl b/src/constructors.jl index d7a2d65..8829461 100644 --- a/src/constructors.jl +++ b/src/constructors.jl @@ -17,11 +17,10 @@ # along with this program. If not, see . ############################################################################# +Object(name::Symbol, id) = Object(name, id, [], []) Object(name::AbstractString, args...) = Object(Symbol(name), args...) Object(name::Symbol) = Base.invokelatest(Object, name) # NOTE: this allows us to override `Object` in `using_spinedb` -ObjectGroup(obj::Object) = ObjectGroup(obj.name, obj.id, []) - ObjectClass(name, objects, vals) = ObjectClass(name, objects, vals, Dict()) ObjectClass(name, objects) = ObjectClass(name, objects, Dict()) diff --git a/src/types.jl b/src/types.jl index 28125ad..fdb1e28 100644 --- a/src/types.jl +++ b/src/types.jl @@ -17,13 +17,6 @@ # along with this program. If not, see . ############################################################################# -""" - AbstractObject - -Supertype for [`Object`](@ref) and [`TimeSlice`](@ref). -""" -abstract type AbstractObject end - """ AbstractParameterValue @@ -47,12 +40,32 @@ struct Anything end A type for representing an object in a Spine db. """ -struct Object <: AbstractObject +struct Object name::Symbol id::UInt64 + members::Array{Object,1} + groups::Array{Object,1} +end + +""" + TimeSlice + +A type for representing a slice of time. +""" +struct TimeSlice + start::Ref{DateTime} + end_::Ref{DateTime} + duration::Float64 + blocks::NTuple{N,Object} where N + id::UInt64 + function TimeSlice(start, end_, duration, blocks) + start > end_ && error("out of order") + id = objectid((start, end_, duration, blocks)) + new(Ref(start), Ref(end_), duration, blocks, id) + end end -ObjectLike = Union{T,Int64} where T<:AbstractObject +ObjectLike = Union{Object,TimeSlice,Int64} RelationshipLike{K} = NamedTuple{K,V} where {K,V<:Tuple{Vararg{ObjectLike}}} @@ -64,7 +77,7 @@ end struct ObjectClass name::Symbol objects::Array{ObjectLike,1} - parameter_values::Dict{Object,Dict{Symbol,AbstractParameterValue}} + parameter_values::Dict{ObjectLike,Dict{Symbol,AbstractParameterValue}} parameter_defaults::Dict{Symbol,AbstractParameterValue} end @@ -72,7 +85,7 @@ struct RelationshipClass name::Symbol object_class_names::Array{Symbol,1} relationships::Array{RelationshipLike,1} - parameter_values::Dict{Tuple{Vararg{Object}},Dict{Symbol,AbstractParameterValue}} + parameter_values::Dict{Tuple{Vararg{ObjectLike}},Dict{Symbol,AbstractParameterValue}} parameter_defaults::Dict{Symbol,AbstractParameterValue} lookup_cache::Dict{Bool,Dict} RelationshipClass(name, obj_cls_names, rels, vals, defaults) = @@ -84,24 +97,6 @@ struct Parameter classes::Array{Union{ObjectClass,RelationshipClass},1} end -""" - TimeSlice - -A type for representing a slice of time. -""" -struct TimeSlice <: AbstractObject - start::Ref{DateTime} - end_::Ref{DateTime} - duration::Float64 - blocks::NTuple{N,Object} where N - id::UInt64 - function TimeSlice(start, end_, duration, blocks) - start > end_ && error("out of order") - id = objectid((start, end_, duration, blocks)) - new(Ref(start), Ref(end_), duration, blocks, id) - end -end - struct TimeSliceMap time_slices::Array{TimeSlice,1} index::Array{Int64,1} @@ -205,7 +200,7 @@ struct RepeatingTimeSeriesParameterValue{V} <: AbstractTimeSeriesParameterValue t_map::TimeSeriesMap end -struct MapParameterValue{K,V} <: AbstractParameterValue where V<:AbstractParameterValue +struct MapParameterValue{K,V} <: AbstractParameterValue where V <: AbstractParameterValue value::Map{K,V} end diff --git a/src/using_spinedb.jl b/src/using_spinedb.jl index 04c4031..fab9b76 100644 --- a/src/using_spinedb.jl +++ b/src/using_spinedb.jl @@ -29,20 +29,32 @@ function _members_per_group(groups) end """ -A Dict mapping `Int64` ids to the corresponding `Object` or `ObjectGroup`. +A Dict mapping member ids to an Array of entity group ids. """ -function _full_objects_per_id(objects, members_per_group) - objects_per_id = Dict(obj["id"] => Object(obj["name"], obj["id"]) for obj in objects) - group_ids = intersect(keys(objects_per_id), keys(members_per_group)) - # Promote `Object` to `ObjectGroup` - for group_id in group_ids - objects_per_id[group_id] = ObjectGroup(objects_per_id[group_id]) +function _groups_per_member(groups) + d = Dict() + for grp in groups + push!(get!(d, grp["member_id"], []), grp["entity_id"]) + end + d +end + +""" +A Dict mapping `Int64` ids to the corresponding `Object`. +""" +function _full_objects_per_id(objects, members_per_group, groups_per_member) + objects_per_id = Dict{Int64,Object}(obj["id"] => Object(obj["name"], obj["id"]) for obj in objects) + # Specify `members` for each group + for (id, object) in objects_per_id + member_ids = get(members_per_group, id, ()) + members = isempty(member_ids) ? [object] : [objects_per_id[member_id] for member_id in member_ids] + append!(object.members, members) end - # Specify `members` for each `ObjectGroup` - for group_id in group_ids - obj_grp = objects_per_id[group_id] - members = [objects_per_id[member_id] for member_id in members_per_group[group_id]] - append!(obj_grp.members, members) + # Specify `groups` for each member + for (id, object) in objects_per_id + group_ids = get(groups_per_member, id, ()) + groups = [objects_per_id[group_id] for group_id in group_ids] + append!(object.groups, groups) end objects_per_id end @@ -225,7 +237,8 @@ function using_spinedb(db_map::PyObject, mod=@__MODULE__) param_defs = py"[x._asdict() for x in $db_map.query($db_map.parameter_definition_sq)]" param_vals = py"[x._asdict() for x in $db_map.query($db_map.parameter_value_sq)]" members_per_group = _members_per_group(groups) - full_objs_per_id = _full_objects_per_id(objects, members_per_group) + groups_per_member = _groups_per_member(groups) + full_objs_per_id = _full_objects_per_id(objects, members_per_group, groups_per_member) objs_per_cls = _entities_per_class(objects) rels_per_cls = _entities_per_class(relationships) param_defs_per_cls = _parameter_definitions_per_class(param_defs) diff --git a/test/using_spinedb.jl b/test/using_spinedb.jl index bd49c6d..d01056d 100644 --- a/test/using_spinedb.jl +++ b/test/using_spinedb.jl @@ -22,19 +22,27 @@ @testset "object_class" begin object_classes = ["institution"] institutions = ["VTT", "KTH", "KUL", "ER", "UCD"] - objects = [["institution", x] for x in institutions] + objects = [["institution", x] for x in (institutions..., "Spine")] + object_groups =[["institution", "Spine", [x for x in institutions]]] db_api.create_new_spine_database(url) - db_api.import_data_to_url(url; object_classes=object_classes, objects=objects) + db_api.import_data_to_url(url; object_classes=object_classes, objects=objects, object_groups=object_groups) using_spinedb(url) - @test length(institution()) === 5 + @test length(institution()) === 6 @test all(x isa Object for x in institution()) - @test [x.name for x in institution()] == Symbol.(institutions) + @test [x.name for x in institution()] == vcat(Symbol.(institutions), :Spine) @test institution(:FIFA) === nothing @test length(object_class()) === 1 @test all(x isa ObjectClass for x in object_class()) max_object_id = maximum(obj.id for obj in institution()) FIFA = Object(:FIFA) @test FIFA.id === max_object_id + 1 + Spine = institution(:Spine) + @test members(Spine) == institution()[1:end - 1] + @test isempty(groups(Spine)) + @testset for i in institution()[1:end - 1] + @test members(i) == [i] + @test groups(i) == [Spine] + end end @testset "relationship_class" begin object_classes = ["institution", "country"]