OrderedIndex
public final class OrderedIndex : Codable, @unchecked Sendable
An in-memory sorted array-based index that maps FieldValue keys to
posting lists of RecordPointer values.
Architecture choice. This index uses a sorted array with binary search, giving O(log n) point lookups and trivially correct range scans — the same asymptotic performance as a B-tree. An array is significantly simpler to implement, test, and debug. Since the entire index is kept in memory, the O(n) insertion cost of shifting array elements is acceptable for the typical mobile workloads NyaruDB targets (hundreds to low thousands of unique keys).
Persistence is O(n): the entire array is serialised at once using MsgPack and compressed with gzip. For the expected key counts this is negligible.
Class semantics. OrderedIndex is a final class rather than a value
type to avoid Swift’s Copy-on-Write (COW) penalties during bulk inserts.
With COW, mutating an array-backed value type duplicates the entire
internal storage on every mutation, which would make building indexes from
a large shard scan quadratic.
Thread safety. The @unchecked Sendable conformance is safe because
OrderedIndex is always accessed from within a single CollectionCore
actor, which serialises all mutations.
-
The total number of index entries (sum of all posting-list lengths).
Declaration
Swift
public var entryCount: Int { get } -
The number of unique keys in the index.
Declaration
Swift
public var uniqueKeyCount: Int { get } -
Undocumented
Declaration
Swift
public init() -
Declaration
Swift
public required init(from decoder: Decoder) throws
-
Returns whether the given key exists in the index.
Declaration
Swift
@inlinable public func contains(_ key: FieldValue) -> BoolParameters
keyThe key to look up.
Return Value
trueif at least one record is indexed under this key. -
Returns all record pointers indexed under the given key.
Declaration
Swift
@inlinable public func search(_ key: FieldValue) -> [RecordPointer]Parameters
keyThe key to look up.
Return Value
The posting list for that key, or an empty array if the key is not present.
-
Returns all record pointers whose keys fall within the specified range.
Each bound can be
nil(unbounded on that side). The inclusivity of each bound is controlled separately.Declaration
Swift
@inlinable public func range( lower: FieldValue?, lowerInclusive: Bool, upper: FieldValue?, upperInclusive: Bool, maxCount: Int? = nil ) -> [RecordPointer]Parameters
lowerThe lower bound, or
nilfor no lower bound.lowerInclusiveWhether the lower bound is inclusive.
upperThe upper bound, or
nilfor no upper bound.upperInclusiveWhether the upper bound is inclusive.
maxCountStop after collecting this many pointers (
nil= all). Results are collected in ascending key order, so the firstmaxCountpointers are the lowest-keyed matches.Return Value
The pointers in the range, in ascending key order.
-
Inserts a pointer for the given key. If the key already exists, the pointer is appended to its posting list.
Declaration
Swift
public func insert(key: FieldValue, pointer: RecordPointer)Parameters
keyThe index key.
pointerThe record pointer to insert.
-
Efficiently loads a batch of index entries in O(n + m) time.
This method performs a bulk insertion of multiple index entries without causing O(n²) array-shifting overhead. It uses a single-pass merge algorithm that combines existing keys with new entries in one operation.
The algorithm works as follows:
- Sorts incoming entries by key (O(m log m))
- Allocates new arrays with pre-calculated capacity (O(1) amortized)
- Merges existing and new entries in a single pass (O(n + m))
- Groups multiple pointers for duplicate keys
Complexity
O(n + m log m) where n is the existing entry count and m is the number of new entries.
Note
This method replaces the entire internal storage with newly allocated arrays, which is acceptable for batch operations where the index is being rebuilt. For incremental inserts, prefer
insert(key:pointer:).See also
insert(key:pointer:)for single-entry insertion,rebuildAllIndexes()for rebuilding indexes from scratch.Declaration
Swift
public func bulkLoad(_ entries: [(key: FieldValue, pointer: RecordPointer)])Parameters
entriesAn array of
(key: FieldValue, pointer: RecordPointer)tuples representing the new entries to load. -
Removes a specific pointer from the posting list for the given key. If the posting list becomes empty, the key entry is removed entirely.
Declaration
Swift
public func remove(key: FieldValue, pointer: RecordPointer)Parameters
keyThe index key.
pointerThe record pointer to remove.
-
Removes a batch of
(key, pointer)entries in a single sweep — the removal analogue ofbulkLoad. Removing entries one at a time shifts the tail of the key array on every emptied key, which is quadratic for large batches; this rebuilds the arrays once instead.Declaration
Swift
public func bulkRemove(_ entries: [(key: FieldValue, pointer: RecordPointer)])Parameters
entriesThe entries to remove. Pointers not present under their key are ignored.
-
Removes every occurrence of the given pointer across all keys in a single pass. Keys whose posting lists become empty are removed.
Used when a record disappears (stale pointer, corruption) and its index keys are unknown.
Declaration
Swift
public func removeAll(pointer: RecordPointer)Parameters
pointerThe record pointer to purge from the index.
-
Rewrites pointer offsets after shard compaction using per-shard offset maps, without re-reading or re-parsing any document.
For every pointer whose shard appears in
mapping:- If the old offset has a new offset, the pointer is rewritten.
- If the old offset is absent (the record no longer exists), the stale entry is dropped.
Pointers into shards not present in
mappingare kept unchanged.Declaration
Swift
public func compactRemap(_ mapping: [String : [UInt64 : UInt64]])Parameters
mappingShard ID → (old offset → new offset).
-
Replaces
oldpointer withnewpointer for a given key.Assumes the key has not changed — this is used when a record is updated in place (same key, same shard) and only the file offset changes.
Declaration
Swift
@discardableResult public func replace(key: FieldValue, old: RecordPointer, new: RecordPointer) -> BoolParameters
keyThe index key.
oldThe old pointer to replace.
newThe new pointer.
Return Value
trueif the replacement was performed,falseif the old pointer was not found.
-
Serialises the index to disk using a hand-rolled binary layout, gzip compression, and optional AES-256-GCM encryption.
The binary layout is roughly an order of magnitude faster to encode and decode than Codable+MsgPack for large indexes, and it interns shard IDs into a string table so each pointer costs 10 bytes instead of carrying a repeated string:
"NYI1" | u16 version | u32 shardCount | shardCount × (u16 len + utf8) u32 keyCount keyCount × ( FieldValue | u32 postingCount | postingCount × (u16 shardIdx + u64 offset) ) FieldValue: u8 tag — 0 null, 1 false, 2 true, 3 int (+ i64), 4 double (+ f64 bits), 5 string (+ u32 len + utf8)The whole payload is then gzip-compressed and, when a key is provided, AES-GCM sealed — same envelope as before.
Throws
NyaruError.encryptionFailedif sealing fails.Declaration
Swift
public func persist(to url: URL, encryptionKey: SymmetricKey?) throwsParameters
urlThe destination file URL.
encryptionKeyOptional AES-256-GCM key.
-
Loads an index from disk, handling decompression and optional decryption.
Reads the binary snapshot format; snapshots written by older versions (Codable+MsgPack) are decoded through the legacy path, so upgrades do not force an index rebuild.
Throws
NyaruError.decryptionFailedif decryption fails, or a decoding error if neither format matches.Declaration
Swift
public static func load(from url: URL, encryptionKey: SymmetricKey?) throws -> OrderedIndexParameters
urlThe source file URL.
encryptionKeyOptional AES-256-GCM key.
Return Value
A fully restored
OrderedIndex, or an empty index if the file is empty.
View on GitHub