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; }