Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bulk insertion of supplemental attributes #411

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/supplemental_attribute_associations.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
const SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME = "supplemental_attributes"

struct ComponentSupplementalAttributeAssociation
component::InfrastructureSystemsComponent
supplemental_attribute::SupplementalAttribute
end

# Design note:
# Supplemental attributes and time series are stored in independent SQLite databases.
# Ideally, they would be different tables in the same database. This is not practical
Expand Down Expand Up @@ -117,6 +122,49 @@ function add_association!(
return
end

"""
Add multiple supplemental attribute associations to the associations table.
The caller must check for duplicates.
"""
function add_associations!(
associations::SupplementalAttributeAssociations,
pairs::Vector{ComponentSupplementalAttributeAssociation},
)
isempty(pairs) && return
TimerOutputs.@timeit_debug SYSTEM_TIMERS "add supplemental attribute associations" begin
columns = (
"attribute_uuid",
"attribute_type",
"component_uuid",
"component_type",
)
num_rows = length(pairs)
num_columns = length(columns)
data = OrderedDict(x => Vector{Any}(undef, num_rows) for x in columns)
for i in 1:num_rows
pair = pairs[i]
row = (
string(get_uuid(pair.supplemental_attribute)),
string(nameof(typeof(pair.supplemental_attribute))),
string(get_uuid(pair.component)),
string(nameof(typeof(pair.component))),
)
for (j, column) in enumerate(columns)
data[column][i] = row[j]
end
end
params = chop(repeat("?,", num_columns))
SQLite.DBInterface.executemany(
associations.db,
"INSERT INTO $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME VALUES($params)",
NamedTuple(Symbol(k) => v for (k, v) in data),
)
end
@debug "Added $num_rows instances of time series metadata" _group =
LOG_GROUP_TIME_SERIES
return
end

"""
Drop the supplemental attribute associations table.
"""
Expand Down Expand Up @@ -239,6 +287,29 @@ function has_association(
return !isempty(Tables.rowtable(_execute(associations, query, params)))
end

function has_associations(
associations::SupplementalAttributeAssociations,
component_attribute_pairs::Vector{ComponentSupplementalAttributeAssociation},
)
isempty(component_attribute_pairs) && return false
clauses = Vector{String}(undef, length(component_attribute_pairs))
params = Vector{String}(undef, length(component_attribute_pairs) * 2)
for (i, pair) in enumerate(component_attribute_pairs)
clauses[i] = "(attribute_uuid = ? AND component_uuid = ?)"
index = i * 2 - 1
params[index] = string(get_uuid(pair.supplemental_attribute))
params[index + 1] = string(get_uuid(pair.component))
end
where_clause = join(clauses, " OR ")
query = """
SELECT attribute_uuid
FROM $SUPPLEMENTAL_ATTRIBUTE_TABLE_NAME
WHERE $where_clause
LIMIT 1
"""
return !isempty(Tables.rowtable(_execute(associations, query, params)))
end

"""
Return the component UUIDs associated with the attribute.
"""
Expand Down
54 changes: 54 additions & 0 deletions src/supplemental_attribute_manager.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# This is used to supplemental attribute associations efficiently in SQLite.
const ADD_SUPPLEMENTAL_ATTRIBUTES_BATCH_SIZE = 1000

const SupplementalAttributesByType =
Dict{DataType, Dict{Base.UUID, <:SupplementalAttribute}}

Expand Down Expand Up @@ -39,6 +42,57 @@
return
end

function add_supplemental_attributes!(
mgr::SupplementalAttributeManager,
components_and_attributes;
allow_existing_time_series = false,
batch_size = ADD_SUPPLEMENTAL_ATTRIBUTES_BATCH_SIZE,
)
batch = ComponentSupplementalAttributeAssociation[]
sizehint!(batch, batch_size)
for (component, attribute) in components_and_attributes
push!(batch, ComponentSupplementalAttributeAssociation(component, attribute))
if length(batch) >= batch_size
add_supplemental_attributes!(

Check warning on line 56 in src/supplemental_attribute_manager.jl

View check run for this annotation

Codecov / codecov/patch

src/supplemental_attribute_manager.jl#L56

Added line #L56 was not covered by tests
mgr,
batch;
allow_existing_time_series = allow_existing_time_series,
)
empty!(batch)

Check warning on line 61 in src/supplemental_attribute_manager.jl

View check run for this annotation

Codecov / codecov/patch

src/supplemental_attribute_manager.jl#L61

Added line #L61 was not covered by tests
end
end

if !isempty(batch)
add_supplemental_attributes!(
mgr,
batch;
allow_existing_time_series = allow_existing_time_series,
)
end

return
end

function add_supplemental_attributes!(
mgr::SupplementalAttributeManager,
ca_pairs::Vector{ComponentSupplementalAttributeAssociation};
allow_existing_time_series = false,
)
if has_associations(mgr.associations, ca_pairs)
# TODO DT: report the duplicates
throw(ArgumentError("There is already an association."))

Check warning on line 83 in src/supplemental_attribute_manager.jl

View check run for this annotation

Codecov / codecov/patch

src/supplemental_attribute_manager.jl#L83

Added line #L83 was not covered by tests
end
for pair in ca_pairs
_attach_attribute!(
mgr,
pair.supplemental_attribute;
allow_existing_time_series = allow_existing_time_series,
)
end
add_associations!(mgr.associations, ca_pairs)
return
end

function _attach_attribute!(
mgr::SupplementalAttributeManager,
attribute::SupplementalAttribute;
Expand Down
28 changes: 28 additions & 0 deletions src/system_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,34 @@ function add_supplemental_attribute!(data::SystemData, component, attribute; kwa
return
end

function add_supplemental_attributes!(
data::SystemData,
ca_pairs;
batch_size = ADD_TIME_SERIES_BATCH_SIZE,
kwargs...,
)
for pair in ca_pairs
throw_if_not_attached(data.components, pair.component)
end

add_supplemental_attributes!(
data.supplemental_attribute_manager,
ca_pairs;
batch_size = batch_size,
kwargs...,
)

for pair in ca_pairs
set_shared_system_references!(
pair.supplemental_attribute,
SharedSystemReferences(;
supplemental_attribute_manager = data.supplemental_attribute_manager,
time_series_manager = data.time_series_manager,
),
)
end
end

function get_supplemental_attributes(
filter_func::Function,
::Type{T},
Expand Down
21 changes: 21 additions & 0 deletions test/test_supplemental_attributes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,24 @@ end
)
@test length(table) == 4
end

@testset "Test add supplemental attributes in bulk" begin
data = IS.SystemData()
components = IS.TestComponent[]
attr_pairs = []
for i in 1:5
component = IS.TestComponent("component_$(i)", i)
attr = IS.GeographicInfo(; geo_json = Dict("name" => string(i)))
pair = (component = component, supplemental_attribute = attr)
IS.add_component!(data, component)
push!(attr_pairs, pair)
end
IS.add_supplemental_attributes!(data, attr_pairs)

for i in 1:5
uuid = IS.get_uuid(attr_pairs[i].supplemental_attribute)
component = IS.get_component(IS.TestComponent, data, "component_$(i)")
attr = IS.get_supplemental_attribute(component, uuid)
@test attr.geo_json["name"] == string(i)
end
end
Loading