1use crate::path_reference::CanonicalPathReference;
2use crate::value::PropertyValue;
3use crate::{
4 AssetId, AssetLocation, AssetName, DataSet, DataSetAssetInfo, DataSetResult, HashSet,
5 NullOverride, OrderedSet, SchemaSet,
6};
7use uuid::Uuid;
8
9#[derive(Debug)]
10pub struct DynamicArrayEntryDelta {
11 key: String,
12 entries: OrderedSet<Uuid>,
14}
15
16#[derive(Default, Debug)]
17pub struct AssetDiff {
18 set_name: Option<AssetName>,
19 set_location: Option<AssetLocation>,
20 set_prototype: Option<Option<AssetId>>,
21 set_properties: Vec<(String, PropertyValue)>,
22 remove_properties: Vec<String>,
23 set_null_overrides: Vec<(String, NullOverride)>,
24 remove_null_overrides: Vec<String>,
25 add_properties_in_replace_mode: Vec<String>,
26 remove_properties_in_replace_mode: Vec<String>,
27 dynamic_array_entry_deltas: Vec<DynamicArrayEntryDelta>,
28 set_canonical_path_references: Vec<(CanonicalPathReference, AssetId)>,
29 remove_canonical_path_references: Vec<CanonicalPathReference>,
30}
31
32impl AssetDiff {
33 pub fn has_changes(&self) -> bool {
34 self.set_name.is_some()
35 || self.set_location.is_some()
36 || self.set_prototype.is_some()
37 || !self.set_properties.is_empty()
38 || !self.remove_properties.is_empty()
39 || !self.set_null_overrides.is_empty()
40 || !self.remove_null_overrides.is_empty()
41 || !self.add_properties_in_replace_mode.is_empty()
42 || !self.remove_properties_in_replace_mode.is_empty()
43 || !self.dynamic_array_entry_deltas.is_empty()
44 || !self.set_canonical_path_references.is_empty()
45 || !self.remove_canonical_path_references.is_empty()
46 }
47
48 pub fn apply(
49 &self,
50 asset: &mut DataSetAssetInfo,
51 ) {
52 if let Some(set_name) = &self.set_name {
53 asset.asset_name = set_name.clone();
54 }
55
56 if let Some(set_location) = &self.set_location {
57 asset.asset_location = set_location.clone();
58 }
59
60 if let Some(set_prototype) = self.set_prototype {
61 asset.prototype = set_prototype;
62 }
63
64 for (k, v) in &self.set_properties {
65 asset.properties.insert(k.clone(), v.as_value());
66 }
67
68 for k in &self.remove_properties {
69 asset.properties.remove(k);
70 }
71
72 for (k, v) in &self.set_null_overrides {
73 asset.property_null_overrides.insert(k.clone(), *v);
74 }
75
76 for k in &self.remove_null_overrides {
77 asset.property_null_overrides.remove(k);
78 }
79
80 for k in &self.add_properties_in_replace_mode {
81 asset.properties_in_replace_mode.insert(k.clone());
82 }
83
84 for k in &self.remove_properties_in_replace_mode {
85 asset.properties_in_replace_mode.remove(k);
86 }
87
88 for delta in &self.dynamic_array_entry_deltas {
89 if delta.entries.is_empty() {
90 asset.dynamic_collection_entries.remove(&delta.key);
92 } else {
93 *asset
95 .dynamic_collection_entries
96 .entry(delta.key.clone())
97 .or_default() = delta.entries.clone();
98 }
99 }
100
101 for (k, v) in &self.set_canonical_path_references {
102 asset
103 .build_info
104 .path_reference_overrides
105 .insert(k.clone(), *v);
106 }
107
108 for k in &self.remove_canonical_path_references {
109 asset.build_info.path_reference_overrides.remove(k);
110 }
111 }
112}
113
114pub struct AssetDiffSet {
115 pub apply_diff: AssetDiff,
116 pub revert_diff: AssetDiff,
117}
118
119impl AssetDiffSet {
120 pub fn has_changes(&self) -> bool {
121 self.apply_diff.has_changes()
123 }
124
125 pub fn diff_assets(
126 before_data_set: &DataSet,
127 before_asset_id: AssetId,
128 after_data_set: &DataSet,
129 after_asset_id: AssetId,
130 modified_locations: &mut HashSet<AssetLocation>,
131 ) -> Self {
132 let before_obj = before_data_set.assets().get(&before_asset_id).unwrap();
133 let after_obj = after_data_set.assets().get(&after_asset_id).unwrap();
134
135 assert_eq!(
136 before_obj.schema().fingerprint(),
137 after_obj.schema().fingerprint()
138 );
139
140 let mut apply_diff = AssetDiff::default();
141 let mut revert_diff = AssetDiff::default();
142
143 if before_obj.asset_name != after_obj.asset_name {
144 apply_diff.set_name = Some(after_obj.asset_name.clone());
145 revert_diff.set_name = Some(before_obj.asset_name.clone());
146 }
147
148 if before_obj.asset_location != after_obj.asset_location {
149 apply_diff.set_location = Some(after_obj.asset_location.clone());
150 revert_diff.set_location = Some(before_obj.asset_location.clone());
151 }
152
153 if before_obj.prototype != after_obj.prototype {
157 apply_diff.set_prototype = Some(after_obj.prototype);
158 revert_diff.set_prototype = Some(before_obj.prototype);
159 }
160
161 for (key, before_value) in &before_obj.properties {
165 if let Some(after_value) = after_obj.properties.get(key) {
166 if !PropertyValue::are_matching_property_values(before_value, after_value) {
167 apply_diff
169 .set_properties
170 .push((key.clone(), after_value.as_property_value().unwrap()));
171 revert_diff
172 .set_properties
173 .push((key.clone(), before_value.as_property_value().unwrap()));
174 } else {
175 }
177 } else {
178 apply_diff.remove_properties.push(key.clone());
180 revert_diff
181 .set_properties
182 .push((key.clone(), before_value.as_property_value().unwrap()));
183 }
184 }
185
186 for (key, after_value) in &after_obj.properties {
187 if !before_obj.properties.contains_key(key) {
188 apply_diff
190 .set_properties
191 .push((key.clone(), after_value.as_property_value().unwrap()));
192 revert_diff.remove_properties.push(key.clone());
193 }
194 }
195
196 for (key, &before_value) in &before_obj.property_null_overrides {
200 if let Some(after_value) = after_obj.property_null_overrides.get(key).copied() {
201 if before_value != after_value {
202 apply_diff
204 .set_null_overrides
205 .push((key.clone(), after_value));
206 revert_diff
207 .set_null_overrides
208 .push((key.clone(), before_value));
209 } else {
210 }
212 } else {
213 apply_diff.remove_null_overrides.push(key.clone());
215 revert_diff
216 .set_null_overrides
217 .push((key.clone(), before_value));
218 }
219 }
220
221 for (key, &after_value) in &after_obj.property_null_overrides {
222 if !before_obj.property_null_overrides.contains_key(key) {
223 apply_diff
225 .set_null_overrides
226 .push((key.clone(), after_value));
227 revert_diff.remove_null_overrides.push(key.clone());
228 }
229 }
230
231 for replace_mode_property in &before_obj.properties_in_replace_mode {
235 if !after_obj
236 .properties_in_replace_mode
237 .contains(replace_mode_property)
238 {
239 apply_diff
241 .remove_properties_in_replace_mode
242 .push(replace_mode_property.clone());
243 revert_diff
244 .add_properties_in_replace_mode
245 .push(replace_mode_property.clone());
246 }
247 }
248
249 for replace_mode_property in &after_obj.properties_in_replace_mode {
250 if !before_obj
251 .properties_in_replace_mode
252 .contains(replace_mode_property)
253 {
254 apply_diff
256 .add_properties_in_replace_mode
257 .push(replace_mode_property.clone());
258 revert_diff
259 .remove_properties_in_replace_mode
260 .push(replace_mode_property.clone());
261 }
262 }
263
264 let mut all_dynamic_entry_keys = HashSet::default();
269 for key in before_obj.dynamic_collection_entries().keys() {
270 all_dynamic_entry_keys.insert(key);
271 }
272 for key in after_obj.dynamic_collection_entries().keys() {
273 all_dynamic_entry_keys.insert(key);
274 }
275 for key in all_dynamic_entry_keys {
276 let empty_set = OrderedSet::<Uuid>::default();
277 let old = before_obj
278 .dynamic_collection_entries
279 .get(key)
280 .unwrap_or(&empty_set);
281 let new = after_obj
282 .dynamic_collection_entries
283 .get(key)
284 .unwrap_or(&empty_set);
285
286 if old != new {
287 apply_diff
288 .dynamic_array_entry_deltas
289 .push(DynamicArrayEntryDelta {
290 key: key.clone(),
291 entries: new.clone(),
292 });
293 revert_diff
294 .dynamic_array_entry_deltas
295 .push(DynamicArrayEntryDelta {
296 key: key.clone(),
297 entries: old.clone(),
298 });
299 }
300 }
301
302 for (key, &before_value) in &before_obj.build_info.path_reference_overrides {
306 if let Some(&after_value) = after_obj.build_info.path_reference_overrides.get(key) {
307 if before_value != after_value {
308 apply_diff
310 .set_canonical_path_references
311 .push((key.clone(), after_value));
312 revert_diff
313 .set_canonical_path_references
314 .push((key.clone(), before_value));
315 } else {
316 }
318 } else {
319 apply_diff
321 .remove_canonical_path_references
322 .push(key.clone());
323 revert_diff
324 .set_canonical_path_references
325 .push((key.clone(), before_value));
326 }
327 }
328
329 for (key, &after_value) in &after_obj.build_info.path_reference_overrides {
330 if !before_obj
331 .build_info
332 .path_reference_overrides
333 .contains_key(key)
334 {
335 apply_diff
337 .set_canonical_path_references
338 .push((key.clone(), after_value));
339 revert_diff
340 .remove_canonical_path_references
341 .push(key.clone());
342 }
343 }
344
345 if apply_diff.has_changes() {
348 if !modified_locations.contains(&after_obj.asset_location) {
349 modified_locations.insert(after_obj.asset_location.clone());
350 }
351
352 if before_obj.asset_location != after_obj.asset_location {
354 if !modified_locations.contains(&before_obj.asset_location) {
355 modified_locations.insert(before_obj.asset_location.clone());
356 }
357 }
358 }
359
360 AssetDiffSet {
361 apply_diff,
362 revert_diff,
363 }
364 }
365}
366
367#[derive(Default, Debug)]
368pub struct DataSetDiff {
369 creates: Vec<(AssetId, DataSetAssetInfo)>,
370 deletes: Vec<AssetId>,
371 changes: Vec<(AssetId, AssetDiff)>,
372}
373
374impl DataSetDiff {
375 pub fn has_changes(&self) -> bool {
376 !self.creates.is_empty() || !self.deletes.is_empty() || !self.changes.is_empty()
377 }
378
379 pub fn apply(
380 &self,
381 data_set: &mut DataSet,
382 schema_set: &SchemaSet,
383 ) -> DataSetResult<()> {
384 for delete in &self.deletes {
385 data_set.delete_asset(*delete)?;
386 }
387
388 for (id, create) in &self.creates {
389 data_set.restore_asset(
390 *id,
391 create.asset_name.clone(),
392 create.asset_location.clone(),
393 create.import_info.clone(),
394 create.build_info.clone(),
395 schema_set,
396 create.prototype,
397 create.schema().fingerprint(),
398 create.properties.clone(),
399 create.property_null_overrides.clone(),
400 create.properties_in_replace_mode.clone(),
401 create.dynamic_collection_entries.clone(),
402 )?;
403 }
404
405 for (asset_id, v) in &self.changes {
406 if let Some(asset) = data_set.assets_mut().get_mut(asset_id) {
407 v.apply(asset);
408 }
409 }
410
411 Ok(())
412 }
413
414 pub fn get_modified_assets(
415 &self,
416 modified_assets: &mut HashSet<AssetId>,
417 ) {
418 for (id, _) in &self.creates {
419 modified_assets.insert(*id);
420 }
421
422 for id in &self.deletes {
423 modified_assets.insert(*id);
424 }
425
426 for (id, _) in &self.changes {
427 modified_assets.insert(*id);
428 }
429 }
430}
431
432#[derive(Debug)]
433pub struct DataSetDiffSet {
434 pub apply_diff: DataSetDiff,
435 pub revert_diff: DataSetDiff,
436 pub modified_assets: HashSet<AssetId>,
437 pub modified_locations: HashSet<AssetLocation>,
438}
439
440impl DataSetDiffSet {
441 pub fn has_changes(&self) -> bool {
442 self.apply_diff.has_changes()
444 }
445
446 pub fn diff_data_set(
447 before: &DataSet,
448 after: &DataSet,
449 tracked_assets: &HashSet<AssetId>,
450 ) -> Self {
451 let mut apply_diff = DataSetDiff::default();
452 let mut revert_diff = DataSetDiff::default();
453 let mut modified_assets: HashSet<AssetId> = Default::default();
454 let mut modified_locations: HashSet<AssetLocation> = Default::default();
455
456 for &asset_id in tracked_assets {
458 let existed_before = before.assets().contains_key(&asset_id);
459 let existed_after = after.assets().contains_key(&asset_id);
460 if existed_before {
461 if existed_after {
462 let diff = AssetDiffSet::diff_assets(
464 before,
465 asset_id,
466 &after,
467 asset_id,
468 &mut modified_locations,
469 );
470 if diff.has_changes() {
471 modified_assets.insert(asset_id);
472 apply_diff.changes.push((asset_id, diff.apply_diff));
473 revert_diff.changes.push((asset_id, diff.revert_diff));
474 }
475 } else {
476 let before_asset_info = before.assets().get(&asset_id).unwrap().clone();
478 modified_assets.insert(asset_id);
479 if !modified_locations.contains(&before_asset_info.asset_location) {
480 modified_locations.insert(before_asset_info.asset_location.clone());
481 }
482
483 apply_diff.deletes.push(asset_id);
485 revert_diff
486 .creates
487 .push((asset_id, before_asset_info.clone()));
488 }
489 } else if existed_after {
490 let after_asset_info = after.assets().get(&asset_id).unwrap();
492 if !modified_locations.contains(&after_asset_info.asset_location) {
493 modified_locations.insert(after_asset_info.asset_location.clone());
494 }
495
496 apply_diff
498 .creates
499 .push((asset_id, after_asset_info.clone()));
500 revert_diff.deletes.push(asset_id);
501 }
502 }
503
504 DataSetDiffSet {
505 apply_diff,
506 revert_diff,
507 modified_locations,
508 modified_assets,
509 }
510 }
511}