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"]