diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attribute.cs
index b04d1046..e5174599 100644
--- a/src/NexusMods.MnemonicDB.Abstractions/Attribute.cs
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attribute.cs
@@ -66,6 +66,14 @@ protected virtual TValueType FromLowLevel(ushort value, ValueTags tags)
throw new NotSupportedException("Unsupported low-level type " + value + " on attribute " + Id);
}
+ ///
+ /// Converts a low-level value to a high-level value
+ ///
+ protected virtual TValueType FromLowLevel(uint value, ValueTags tags)
+ {
+ throw new NotSupportedException("Unsupported low-level type " + value + " on attribute " + Id);
+ }
+
///
/// Converts a low-level value to a high-level value
@@ -331,6 +339,7 @@ public TValueType ReadValue(ReadOnlySpan span)
ValueTags.Null => NullFromLowLevel(),
ValueTags.UInt8 => FromLowLevel(ReadUnmanaged(rest), tag),
ValueTags.UInt16 => FromLowLevel(ReadUnmanaged(rest), tag),
+ ValueTags.UInt32 => FromLowLevel(ReadUnmanaged(rest), tag),
ValueTags.UInt64 => FromLowLevel(ReadUnmanaged(rest), tag),
ValueTags.UInt128 => FromLowLevel(ReadUnmanaged(rest), tag),
ValueTags.Int16 => FromLowLevel(ReadUnmanaged(rest), tag),
diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/UnknownAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UnknownAttribute.cs
new file mode 100644
index 00000000..3148d2fa
--- /dev/null
+++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UnknownAttribute.cs
@@ -0,0 +1,166 @@
+using System;
+using NexusMods.MnemonicDB.Abstractions.ElementComparers;
+
+namespace NexusMods.MnemonicDB.Abstractions.Attributes;
+
+///
+/// An unknown attribute is an attribute that exists in the database but is not known to the application. This is
+/// either because the attribute was removed, or didn't show up in the DI system.
+///
+///
+///
+public class UnknownAttribute(DbAttribute dbAttribute) :
+ Attribute(dbAttribute.LowLevelType, dbAttribute.UniqueId.Namespace, dbAttribute.UniqueId.Name)
+{
+ ///
+ protected override TLowLevel ToLowLevel(TLowLevel value)
+ {
+ return value;
+ }
+
+ ///
+ protected override TLowLevel FromLowLevel(byte value, ValueTags tags)
+ {
+ if (tags != LowLevelType)
+ {
+ throw new ArgumentException($"Cannot convert {tags} to {LowLevelType}");
+ }
+
+ return (TLowLevel)(object)value;
+ }
+
+ ///
+ protected override TLowLevel FromLowLevel(ushort value, ValueTags tags)
+ {
+ if (tags != LowLevelType)
+ {
+ throw new ArgumentException($"Cannot convert {tags} to {LowLevelType}");
+ }
+
+ return (TLowLevel)(object)value;
+ }
+
+ ///
+ protected override TLowLevel FromLowLevel(uint value, ValueTags tags)
+ {
+ if (tags != LowLevelType)
+ {
+ throw new ArgumentException($"Cannot convert {tags} to {LowLevelType}");
+ }
+
+ return (TLowLevel)(object)value;
+ }
+
+
+ ///
+ protected override TLowLevel FromLowLevel(ulong value, ValueTags tags)
+ {
+ if (tags != LowLevelType)
+ {
+ throw new ArgumentException($"Cannot convert {tags} to {LowLevelType}");
+ }
+
+ return (TLowLevel)(object)value;
+ }
+
+ ///
+ protected override TLowLevel FromLowLevel(short value, ValueTags tags)
+ {
+ if (tags != LowLevelType)
+ {
+ throw new ArgumentException($"Cannot convert {tags} to {LowLevelType}");
+ }
+
+ return (TLowLevel)(object)value;
+ }
+
+ ///
+ protected override TLowLevel FromLowLevel(int value, ValueTags tags)
+ {
+ if (tags != LowLevelType)
+ {
+ throw new ArgumentException($"Cannot convert {tags} to {LowLevelType}");
+ }
+
+ return (TLowLevel)(object)value;
+ }
+
+ ///
+ protected override TLowLevel FromLowLevel(long value, ValueTags tags)
+ {
+ if (tags != LowLevelType)
+ {
+ throw new ArgumentException($"Cannot convert {tags} to {LowLevelType}");
+ }
+
+ return (TLowLevel)(object)value;
+ }
+
+ ///
+ protected override TLowLevel FromLowLevel(float value, ValueTags tags)
+ {
+ if (tags != LowLevelType)
+ {
+ throw new ArgumentException($"Cannot convert {tags} to {LowLevelType}");
+ }
+
+ return (TLowLevel)(object)value;
+ }
+
+ ///
+ protected override TLowLevel FromLowLevel(double value, ValueTags tags)
+ {
+ if (tags != LowLevelType)
+ {
+ throw new ArgumentException($"Cannot convert {tags} to {LowLevelType}");
+ }
+
+ return (TLowLevel)(object)value;
+ }
+
+ ///
+ protected override TLowLevel FromLowLevel(string value, ValueTags tags)
+ {
+ if (tags != LowLevelType)
+ {
+ throw new ArgumentException($"Cannot convert {tags} to {LowLevelType}");
+ }
+
+ return (TLowLevel)(object)value;
+ }
+}
+
+///
+/// Helper class to create unknown attributes
+///
+public static class UnknownAttribute
+{
+ ///
+ /// Creates an unknown attribute from a database attribute
+ ///
+ public static IAttribute Create(DbAttribute dbAttribute)
+ {
+ return dbAttribute.LowLevelType switch
+ {
+ ValueTags.Null => new UnknownAttribute(dbAttribute),
+ ValueTags.UInt8 => new UnknownAttribute(dbAttribute),
+ ValueTags.UInt16 => new UnknownAttribute(dbAttribute),
+ ValueTags.UInt32 => new UnknownAttribute(dbAttribute),
+ ValueTags.UInt64 => new UnknownAttribute(dbAttribute),
+ ValueTags.UInt128 => new UnknownAttribute(dbAttribute),
+ ValueTags.Int16 => new UnknownAttribute(dbAttribute),
+ ValueTags.Int32 => new UnknownAttribute(dbAttribute),
+ ValueTags.Int64 => new UnknownAttribute(dbAttribute),
+ ValueTags.Int128 => new UnknownAttribute(dbAttribute),
+ ValueTags.Float32 => new UnknownAttribute(dbAttribute),
+ ValueTags.Float64 => new UnknownAttribute(dbAttribute),
+ ValueTags.Ascii => new UnknownAttribute(dbAttribute),
+ ValueTags.Utf8 => new UnknownAttribute(dbAttribute),
+ ValueTags.Utf8Insensitive => new UnknownAttribute(dbAttribute),
+ ValueTags.Blob => new UnknownAttribute(dbAttribute),
+ ValueTags.HashedBlob => new UnknownAttribute(dbAttribute),
+ ValueTags.Reference => new UnknownAttribute(dbAttribute),
+ _ => throw new ArgumentOutOfRangeException(nameof(dbAttribute.LowLevelType), dbAttribute.LowLevelType, null)
+ };
+ }
+}
diff --git a/src/NexusMods.MnemonicDB.Storage/AttributeRegistry.cs b/src/NexusMods.MnemonicDB.Storage/AttributeRegistry.cs
index 8fde023f..265a081a 100644
--- a/src/NexusMods.MnemonicDB.Storage/AttributeRegistry.cs
+++ b/src/NexusMods.MnemonicDB.Storage/AttributeRegistry.cs
@@ -5,6 +5,7 @@
using System.Runtime.InteropServices;
using System.Threading;
using NexusMods.MnemonicDB.Abstractions;
+using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.Internals;
using Reloaded.Memory.Extensions;
@@ -69,6 +70,7 @@ public AttributeRegistry(IEnumerable attributes)
///
public RegistryId Id { get; }
+ ///
public IReadDatom Resolve(ReadOnlySpan datom)
{
var c = MemoryMarshal.Read(datom);
@@ -77,11 +79,18 @@ public IReadDatom Resolve(ReadOnlySpan datom)
return attr.Resolve(c.E, c.A, datom.SliceFast(KeyPrefix.Size), c.T, c.IsRetract);
}
+ ///
public void Populate(DbAttribute[] attributes)
{
foreach (var dbAttribute in attributes)
{
- var instance = _attributesBySymbol[dbAttribute.UniqueId];
+ // Do a try/get here because an attribute may not exist in code that exists in the database
+ if (!_attributesBySymbol.TryGetValue(dbAttribute.UniqueId, out var instance))
+ {
+ instance = UnknownAttribute.Create(dbAttribute);
+ _attributesBySymbol[dbAttribute.UniqueId] = instance;
+ }
+
instance.SetDbId(Id, dbAttribute.AttrEntityId);
_attributes[dbAttribute.AttrEntityId.Value] = instance;
}