Skip to content
Merged
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
12 changes: 12 additions & 0 deletions src/BLite.Core/Collections/BaseMappers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ public abstract class DocumentMapperBase<TId, T> : IDocumentMapper<TId, T> where
public virtual IndexKey ToIndexKey(TId id) => IndexKey.Create(id);
public virtual TId FromIndexKey(IndexKey key) => key.As<TId>();

/// <summary>
/// The BSON field <see cref="Serialize"/> stores <paramref name="propertyName"/> in, when
/// <see cref="Deserialize"/> gives the property exactly the value of that field (a plain
/// auto-property with no converter), so the value of the materialised property can be read from
/// the raw document instead; <c>null</c> when that does not hold or is not known.
/// </summary>
/// <remarks>
/// Generated mappers override this. The default returns <c>null</c>, so queries that could read a
/// property from raw BSON materialise the entities instead.
/// </remarks>
public virtual string? GetVerbatimFieldName(string propertyName) => null;

#pragma warning disable IL2026
public virtual IEnumerable<string> UsedKeys => GetSchema().GetAllKeys();
#pragma warning restore IL2026
Expand Down
418 changes: 390 additions & 28 deletions src/BLite.Core/Collections/DocumentCollection.cs

Large diffs are not rendered by default.

38 changes: 33 additions & 5 deletions src/BLite.Core/Indexing/BTreeIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,34 @@ public IEnumerable<IndexEntry> Range(IndexKey minKey, IndexKey maxKey, IndexDire
/// Equivalent to <c>Range(min, max).Count()</c> but with zero per-entry heap allocations.
/// </summary>
internal int CountRange(IndexKey minKey, IndexKey maxKey, ulong? transactionId = null)
{
var counter = new CountingVisitor();
ScanRange(minKey, maxKey, transactionId, ref counter);
return counter.Count;
}

/// <summary>Receives the keys of a <see cref="ScanRange{TVisitor}"/>, in key order.</summary>
internal interface IKeyVisitor
{
/// <summary>Handles one key; returns <c>false</c> to stop the scan.</summary>
bool Visit(ReadOnlySpan<byte> key);
}

private struct CountingVisitor : IKeyVisitor
{
public int Count;
public bool Visit(ReadOnlySpan<byte> key) { Count++; return true; }
}

/// <summary>
/// Hands every leaf key in [<paramref name="minKey"/>, <paramref name="maxKey"/>] (inclusive)
/// to <paramref name="visitor"/> straight from the page buffer, in ascending order, without
/// allocating per entry. The key span is only valid during the call.
/// </summary>
internal void ScanRange<TVisitor>(IndexKey minKey, IndexKey maxKey, ulong? transactionId, ref TVisitor visitor)
where TVisitor : struct, IKeyVisitor
{
var txnId = transactionId ?? 0;
int count = 0;
var pageBuffer = System.Buffers.ArrayPool<byte>.Shared.Rent(_storage.PageSize);
try
{
Expand Down Expand Up @@ -329,13 +354,16 @@ internal int CountRange(IndexKey minKey, IndexKey maxKey, ulong? transactionId =
}

// inRange: compare against maxKey only.
if (IndexKey.CompareRaw(entryKeySpan, maxKey.Data) <= 0)
count++;
else
if (IndexKey.CompareRaw(entryKeySpan, maxKey.Data) > 0)
{
stopScan = true; // past maxKey — no more matches in any leaf
break;
}
if (!visitor.Visit(entryKeySpan))
{
stopScan = true; // the visitor has seen enough
break;
}

dataOffset += 4 + keyLength + DocumentLocation.SerializedSize;
}
Expand All @@ -348,9 +376,9 @@ internal int CountRange(IndexKey minKey, IndexKey maxKey, ulong? transactionId =
{
System.Buffers.ArrayPool<byte>.Shared.Return(pageBuffer);
}
return count;
}


/// <summary>
/// Finds the first leaf entry in [minKey, maxKey] (inclusive) without allocating an
/// iterator state machine. Equivalent to <c>Range(min,max).FirstOrDefault()</c> but with
Expand Down
40 changes: 39 additions & 1 deletion src/BLite.Core/Indexing/CollectionSecondaryIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,44 @@ public int CountRange(object? minKey, object? maxKey, bool startInclusive, bool
// [userKey][realId] are <= this boundary, so they are included.
// Exclusive end: lower composite boundary [userKey][MinId] — all real entries
// [userKey][realId] are > this boundary, so they are excluded.
var (actualMinKey, actualMaxKey) = CompositeRange(minKey, maxKey, startInclusive, endInclusive);
return _btreeIndex.CountRange(actualMinKey, actualMaxKey, transaction?.TransactionId);
}

/// <summary>
/// Hands the stored keys of the entries in the range to <paramref name="visitor"/>, in ascending
/// order and without allocating per entry; bounds as for <see cref="CountRange"/>. Each key is
/// <c>[user key][document id]</c>, so the indexed value is its prefix.
/// </summary>
internal void ScanKeys<TVisitor>(object? minKey, object? maxKey, bool startInclusive, bool endInclusive, ref TVisitor visitor, ITransaction? transaction = null)
where TVisitor : struct, BTreeIndex.IKeyVisitor
{
if (_btreeIndex == null) return;

var (actualMinKey, actualMaxKey) = CompositeRange(minKey, maxKey, startInclusive, endInclusive);
_btreeIndex.ScanRange(actualMinKey, actualMaxKey, transaction?.TransactionId, ref visitor);
}

/// <summary>
/// The stored key of the last entry in the range (bounds as for <see cref="CountRange"/>), read
/// from the end of the range; <c>null</c> when the range is empty.
/// </summary>
internal IndexKey? LastKey(object? minKey, object? maxKey, bool startInclusive, bool endInclusive, ITransaction? transaction = null)
{
if (_btreeIndex == null) return null;

var (actualMinKey, actualMaxKey) = CompositeRange(minKey, maxKey, startInclusive, endInclusive);
foreach (var entry in _btreeIndex.Range(actualMinKey, actualMaxKey, IndexDirection.Backward, transaction?.TransactionId))
return entry.Key;
return null;
}

/// <summary>
/// The composite B-Tree bounds covering every entry whose user key lies between
/// <paramref name="minKey"/> and <paramref name="maxKey"/> (<c>null</c> = unbounded).
/// </summary>
private (IndexKey Min, IndexKey Max) CompositeRange(object? minKey, object? maxKey, bool startInclusive, bool endInclusive)
{
IndexKey actualMinKey = IndexKey.MinKey;
IndexKey actualMaxKey = s_maxBoundaryKey;

Expand All @@ -464,7 +502,7 @@ public int CountRange(object? minKey, object? maxKey, bool startInclusive, bool
actualMaxKey = CreateCompositeKeyBoundary(userMax, useMinObjectId: !endInclusive);
}

return _btreeIndex.CountRange(actualMinKey, actualMaxKey, transaction?.TransactionId);
return (actualMinKey, actualMaxKey);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/BLite.Core/Indexing/IndexKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public struct IndexKey : IEquatable<IndexKey>, IComparable<IndexKey>
// Guarantees NullSentinel (0x00) and NullSentinelNext (0x01) sort strictly before
// all encoded user values, including int.MinValue which encodes to { 0x00, 0x00, 0x00, 0x00 }
// without the prefix.
private const byte KeyPrefix = 0x02;
internal const byte KeyPrefix = 0x02;

// Prefix of decimal keys. Distinct from KeyPrefix so that an index whose keys were written by an
// older version (decimal stored as double under KeyPrefix) can be told apart from a current one.
Expand Down
Loading
Loading