Skip to content

Commit b955471

Browse files
Added count tracking to the datastore
1 parent eeb2ac7 commit b955471

File tree

5 files changed

+144
-26
lines changed

5 files changed

+144
-26
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ targets: [
5151
### Road to 0.1 Betas
5252

5353
As this project matures towards its first beta, a number of features still need to be fleshed out:
54-
- Keeping track of the number of entries
5554
- Update snapshots to have multiple manifests
5655
- Fleshing out historical edit metadata
5756
- Migrating entries

Sources/CodableDatastore/Datastore/Datastore.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,18 @@ extension Datastore where AccessMode == ReadWrite {
237237
// MARK: - Loading
238238

239239
extension Datastore {
240+
/// The number of objects in the datastore.
241+
///
242+
/// - Note: This count may not reflect an up to dat value while data is being written concurrently, but will be acurate after such a transaction finishes.
243+
public var count: Int {
244+
get async throws {
245+
return try await persistence._withTransaction(options: [.idempotent, .readOnly]) { transaction in
246+
let descriptor = try await transaction.datastoreDescriptor(for: self.key)
247+
return descriptor?.size ?? 0
248+
}
249+
}
250+
}
251+
240252
public func load(_ idenfifier: IdentifierType) async throws -> CodedType? {
241253
try await warmupIfNeeded()
242254

Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/DatastoreRoot.swift

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,91 @@ extension DiskPersistence.Datastore.RootObject {
139139
// MARK: - Mutations
140140

141141
extension DiskPersistence.Datastore.RootObject {
142+
func manifest(
143+
applying descriptor: DatastoreDescriptor
144+
) async throws -> (
145+
manifest: DatastoreRootManifest,
146+
createdIndexes: Set<DiskPersistence.Datastore.Index>
147+
/// Note that indexes are not removed here.
148+
) {
149+
let originalManifest = try await manifest
150+
var manifest = originalManifest
151+
152+
manifest.descriptor.version = descriptor.version
153+
manifest.descriptor.codedType = descriptor.codedType
154+
manifest.descriptor.identifierType = descriptor.identifierType
155+
156+
var createdIndexes: Set<DiskPersistence.Datastore.Index> = []
157+
158+
for (_, indexDescriptor) in descriptor.directIndexes {
159+
let key = indexDescriptor.key
160+
let indexType = indexDescriptor.indexType
161+
var version = indexDescriptor.version
162+
163+
if let originalVersion = originalManifest.descriptor.directIndexes[key]?.version {
164+
version = originalVersion
165+
} else {
166+
let indexInfo = DatastoreRootManifest.IndexInfo(
167+
key: key,
168+
id: DatastoreIndexIdentifier(name: key),
169+
root: DatastoreIndexManifestIdentifier()
170+
)
171+
let index = DiskPersistence.Datastore.Index(
172+
datastore: datastore,
173+
id: .direct(index: indexInfo.id, manifest: indexInfo.root),
174+
manifest: DatastoreIndexManifest(
175+
id: indexInfo.root,
176+
orderedPages: []
177+
)
178+
)
179+
createdIndexes.insert(index)
180+
}
181+
182+
manifest.descriptor.directIndexes[key] = DatastoreDescriptor.IndexDescriptor(
183+
version: version,
184+
key: key,
185+
indexType: indexType
186+
)
187+
}
188+
189+
for (_, indexDescriptor) in descriptor.secondaryIndexes {
190+
let key = indexDescriptor.key
191+
let indexType = indexDescriptor.indexType
192+
var version = indexDescriptor.version
193+
194+
if let originalVersion = originalManifest.descriptor.secondaryIndexes[key]?.version {
195+
version = originalVersion
196+
} else {
197+
let indexInfo = DatastoreRootManifest.IndexInfo(
198+
key: key,
199+
id: DatastoreIndexIdentifier(name: key),
200+
root: DatastoreIndexManifestIdentifier()
201+
)
202+
let index = DiskPersistence.Datastore.Index(
203+
datastore: datastore,
204+
id: .secondary(index: indexInfo.id, manifest: indexInfo.root),
205+
manifest: DatastoreIndexManifest(
206+
id: indexInfo.root,
207+
orderedPages: []
208+
)
209+
)
210+
createdIndexes.insert(index)
211+
}
212+
213+
manifest.descriptor.secondaryIndexes[key] = DatastoreDescriptor.IndexDescriptor(
214+
version: version,
215+
key: key,
216+
indexType: indexType
217+
)
218+
}
219+
220+
if originalManifest.descriptor != manifest.descriptor {
221+
manifest.id = DatastoreRootIdentifier()
222+
manifest.modificationDate = Date()
223+
}
224+
return (manifest: manifest, createdIndexes: createdIndexes)
225+
}
226+
142227
func manifest(
143228
replacing index: DiskPersistence.Datastore.Index.ID
144229
) async throws -> DatastoreRootManifest {
@@ -168,6 +253,7 @@ extension DiskPersistence.Datastore.RootObject {
168253

169254
if manifest != updatedManifest {
170255
updatedManifest.id = DatastoreRootIdentifier()
256+
updatedManifest.modificationDate = Date()
171257
}
172258
return updatedManifest
173259
}

Sources/CodableDatastore/Persistence/Disk Persistence/Transaction/Transaction.swift

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -265,27 +265,28 @@ extension DiskPersistence.Transaction: DatastoreInterfaceProtocol {
265265
func apply(descriptor: DatastoreDescriptor, for datastoreKey: DatastoreKey) async throws {
266266
try checkIsActive()
267267

268-
if let rootObject = try await rootObject(for: datastoreKey) {
269-
var manifest = try await rootObject.manifest
268+
if let existingRootObject = try await rootObject(for: datastoreKey) {
269+
let datastore = existingRootObject.datastore
270270

271-
// TODO: Do a better merge of these descriptors, especially since size is something we want to preserve, amongst other properties
272-
guard manifest.descriptor != descriptor else { return }
271+
let (rootManifest, newIndexes) = try await existingRootObject.manifest(applying: descriptor)
273272

274-
manifest.id = DatastoreRootIdentifier()
275-
manifest.modificationDate = Date()
276-
manifest.descriptor = descriptor
273+
/// No change occured, bail early
274+
guard existingRootObject.id != rootManifest.id else { return }
277275

278-
let newRoot = DiskPersistence.Datastore.RootObject(
279-
datastore: rootObject.datastore,
280-
id: manifest.id,
281-
rootObject: manifest
282-
)
283-
rootObjects[datastoreKey] = newRoot
284-
createdRootObjects.insert(newRoot)
285-
deletedRootObjects.insert(rootObject)
286-
await newRoot.datastore.adopt(rootObject: newRoot)
276+
for newIndex in newIndexes {
277+
createdIndexes.insert(newIndex)
278+
await datastore.adopt(index: newIndex)
279+
}
287280

288-
// TODO: Don't forget to create the new index objects too!
281+
let newRootObject = DiskPersistence.Datastore.RootObject(
282+
datastore: datastore,
283+
id: rootManifest.id,
284+
rootObject: rootManifest
285+
)
286+
createdRootObjects.insert(newRootObject)
287+
deletedRootObjects.insert(existingRootObject)
288+
await datastore.adopt(rootObject: newRootObject)
289+
rootObjects[datastoreKey] = newRootObject
289290
} else {
290291
let (datastore, _) = try await persistence.persistenceDatastore(for: datastoreKey)
291292

@@ -345,6 +346,9 @@ extension DiskPersistence.Transaction: DatastoreInterfaceProtocol {
345346
await datastore.adopt(index: index)
346347
}
347348

349+
var descriptor = descriptor
350+
descriptor.size = 0
351+
348352
/// Create the root object from the indexes that were created
349353
let manifest = DatastoreRootManifest(
350354
id: DatastoreRootIdentifier(),
@@ -555,12 +559,13 @@ extension DiskPersistence.Transaction {
555559

556560
let datastore = existingRootObject.datastore
557561

558-
let (indexManifest, newPages, removedPages) = try await {
562+
/// Depending on the cursor type, insert or replace the entry in the index, capturing the new manifesr, added and removed pages, and change in the number of entries.
563+
let ((indexManifest, newPages, removedPages), newEntryCount) = try await {
559564
switch try cursor(for: someCursor) {
560565
case .insertion(let cursor):
561-
return try await existingIndex.manifest(inserting: entry, at: cursor)
566+
return (try await existingIndex.manifest(inserting: entry, at: cursor), 1)
562567
case .instance(let cursor):
563-
return try await existingIndex.manifest(replacing: entry, at: cursor)
568+
return (try await existingIndex.manifest(replacing: entry, at: cursor), 0)
564569
}
565570
}()
566571

@@ -582,7 +587,12 @@ extension DiskPersistence.Transaction {
582587
deletedIndexes.insert(existingIndex)
583588
await datastore.adopt(index: newIndex)
584589

585-
let rootManifest = try await existingRootObject.manifest(replacing: newIndex.id)
590+
var rootManifest = try await existingRootObject.manifest(replacing: newIndex.id)
591+
592+
/// If the index we are modifying is the primary one, update the number of entries we are managing.
593+
if case .primary = newIndex.id {
594+
rootManifest.descriptor.size += newEntryCount
595+
}
586596

587597
/// No change occured, bail early
588598
guard existingRootObject.id != rootManifest.id else { return }
@@ -652,16 +662,21 @@ extension DiskPersistence.Transaction {
652662
}
653663
deletedPages.formUnion(removedPages)
654664

655-
let newPrimaryIndex = DiskPersistence.Datastore.Index(
665+
let newIndex = DiskPersistence.Datastore.Index(
656666
datastore: datastore,
657667
id: existingIndex.id.with(manifestID: indexManifest.id),
658668
manifest: indexManifest
659669
)
660-
createdIndexes.insert(newPrimaryIndex)
670+
createdIndexes.insert(newIndex)
661671
deletedIndexes.insert(existingIndex)
662-
await datastore.adopt(index: newPrimaryIndex)
672+
await datastore.adopt(index: newIndex)
663673

664-
let rootManifest = try await existingRootObject.manifest(replacing: newPrimaryIndex.id)
674+
var rootManifest = try await existingRootObject.manifest(replacing: newIndex.id)
675+
676+
/// If the index we are modifying is the primary one, update the number of entries we are managing.
677+
if case .primary = newIndex.id {
678+
rootManifest.descriptor.size -= 1
679+
}
665680

666681
/// No change occured, bail early
667682
guard existingRootObject.id != rootManifest.id else { return }

Tests/CodableDatastoreTests/DiskPersistenceDatastoreTests.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ final class DiskPersistenceDatastoreTests: XCTestCase {
4646
try await datastore.persist(TestStruct(id: "3", value: "My name is Dimitri"))
4747
try await datastore.persist(TestStruct(id: "1", value: "Hello, World!"))
4848
try await datastore.persist(TestStruct(id: "2", value: "Twenty Three is Number One"))
49+
50+
let count = try await datastore.count
51+
XCTAssertEqual(count, 3)
4952
}
5053

5154
func testWritingEntryWithIndex() async throws {
@@ -74,6 +77,9 @@ final class DiskPersistenceDatastoreTests: XCTestCase {
7477
try await datastore.persist(TestStruct(id: "3", value: "My name is Dimitri"))
7578
try await datastore.persist(TestStruct(id: "1", value: "Hello, World!"))
7679
try await datastore.persist(TestStruct(id: "2", value: "Twenty Three is Number One"))
80+
81+
let count = try await datastore.count
82+
XCTAssertEqual(count, 3)
7783
}
7884

7985
func testWritingManyEntries() async throws {

0 commit comments

Comments
 (0)