Skip to content

Commit

Permalink
Optimized scanning to skip decoding index entries when iterating over…
Browse files Browse the repository at this point in the history
… an extent in either direction
  • Loading branch information
dimitribouniol committed Apr 11, 2024
1 parent 22c0de8 commit 9cf005f
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 79 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.7
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand Down
7 changes: 7 additions & 0 deletions Sources/CodableDatastore/Indexes/IndexRangeExpression.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,13 @@ public struct IndexRange<Bound: Comparable>: IndexRangeExpression {
self.upperBoundExpression = upperBoundExpression
self.order = order
}

/// An index range spanning a single value.
public init(only value: Bound) {
self.lowerBoundExpression = .including(value)
self.upperBoundExpression = .including(value)
self.order = .ascending
}
}

infix operator ..>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ enum SortOrder {
case ascending
case equal
case descending

init(_ order: RangeOrder) {
switch order {
case .ascending: self = .ascending
case .descending: self = .descending
}
}
}

extension Comparable {
Expand All @@ -22,25 +29,13 @@ extension Comparable {

extension RangeBoundExpression {
func sortOrder(comparedTo rhs: Bound, order: RangeOrder) -> SortOrder {
switch order {
case .ascending:
switch self {
case .extent:
return .ascending
case .excluding(let bound):
return bound < rhs ? .ascending : .descending
case .including(let bound):
return bound <= rhs ? .ascending : .descending
}
case .descending:
switch self {
case .extent:
return .descending
case .excluding(let bound):
return bound <= rhs ? .ascending : .descending
case .including(let bound):
return bound < rhs ? .ascending : .descending
}
switch (order, self) {
case (.ascending, .extent): .ascending
case (.ascending, .excluding(let bound)): bound < rhs ? .ascending : .descending
case (.ascending, .including(let bound)): bound <= rhs ? .ascending : .descending
case (.descending, .extent): .descending
case (.descending, .excluding(let bound)): rhs < bound ? .descending : .ascending
case (.descending, .including(let bound)): rhs <= bound ? .descending : .ascending
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -626,8 +626,9 @@ private func primaryIndexBoundComparator<IdentifierType: Indexable>(lhs: (bound:
guard rhs.headers.count == 2
else { throw DiskPersistenceError.invalidEntryFormat }

let identifierBytes = rhs.headers[1]
if case .extent = lhs.bound { return SortOrder(lhs.order) }

let identifierBytes = rhs.headers[1]
let entryIdentifier = try JSONDecoder.shared.decode(IdentifierType.self, from: Data(identifierBytes))

return lhs.bound.sortOrder(comparedTo: entryIdentifier, order: lhs.order)
Expand All @@ -637,8 +638,9 @@ private func directIndexBoundComparator<IndexType: Indexable>(lhs: (bound: Range
guard rhs.headers.count == 3
else { throw DiskPersistenceError.invalidEntryFormat }

let indexBytes = rhs.headers[1]
if case .extent = lhs.bound { return SortOrder(lhs.order) }

let indexBytes = rhs.headers[1]
let indexedValue = try JSONDecoder.shared.decode(IndexType.self, from: Data(indexBytes))

return lhs.bound.sortOrder(comparedTo: indexedValue, order: lhs.order)
Expand All @@ -648,8 +650,9 @@ private func secondaryIndexBoundComparator<IndexType: Indexable>(lhs: (bound: Ra
guard rhs.headers.count == 1
else { throw DiskPersistenceError.invalidEntryFormat }

let indexBytes = rhs.headers[0]
if case .extent = lhs.bound { return SortOrder(lhs.order) }

let indexBytes = rhs.headers[0]
let indexedValue = try JSONDecoder.shared.decode(IndexType.self, from: Data(indexBytes))

return lhs.bound.sortOrder(comparedTo: indexedValue, order: lhs.order)
Expand All @@ -670,22 +673,23 @@ extension DiskPersistence.Transaction {

switch range.order {
case .ascending:
let startCursor: DiskPersistence.InsertionCursor
if range.lowerBoundExpression == .extent {
startCursor = await index.firstInsertionCursor
let startCursor = if range.lowerBoundExpression == .extent {
await index.firstInsertionCursor
} else {
startCursor = try await index.insertionCursor(
for: (range.lowerBoundExpression, range.order),
try await index.insertionCursor(
for: (range.lowerBoundExpression, .ascending),
comparator: primaryIndexBoundComparator
)
}

try await index.forwardScanEntries(after: startCursor) { entry in
guard case .descending = try primaryIndexBoundComparator(
lhs: (bound: range.upperBoundExpression, order: .descending),
rhs: entry
)
else { return false }
if range.upperBoundExpression != .extent {
guard case .descending = try primaryIndexBoundComparator(
lhs: (bound: range.upperBoundExpression, order: .descending),
rhs: entry
)
else { return false }
}

let versionData = Data(entry.headers[0])
let instanceData = Data(entry.content)
Expand All @@ -694,22 +698,23 @@ extension DiskPersistence.Transaction {
return true
}
case .descending:
let startCursor: DiskPersistence.InsertionCursor
if range.upperBoundExpression == .extent {
startCursor = try await index.lastInsertionCursor
let startCursor = if range.upperBoundExpression == .extent {
try await index.lastInsertionCursor
} else {
startCursor = try await index.insertionCursor(
for: (range.upperBoundExpression, range.order),
try await index.insertionCursor(
for: (range.upperBoundExpression, .descending),
comparator: primaryIndexBoundComparator
)
}

try await index.backwardScanEntries(before: startCursor) { entry in
guard case .ascending = try primaryIndexBoundComparator(
lhs: (bound: range.lowerBoundExpression, order: .ascending),
rhs: entry
)
else { return false }
if range.lowerBoundExpression != .extent {
guard case .ascending = try primaryIndexBoundComparator(
lhs: (bound: range.lowerBoundExpression, order: .ascending),
rhs: entry
)
else { return false }
}

let versionData = Data(entry.headers[0])
let instanceData = Data(entry.content)
Expand All @@ -736,17 +741,23 @@ extension DiskPersistence.Transaction {

switch range.order {
case .ascending:
let startCursor = try await index.insertionCursor(
for: (range.lowerBoundExpression, range.order),
comparator: directIndexBoundComparator
)
let startCursor = if range.lowerBoundExpression == .extent {
await index.firstInsertionCursor
} else {
try await index.insertionCursor(
for: (range.lowerBoundExpression, .ascending),
comparator: directIndexBoundComparator
)
}

try await index.forwardScanEntries(after: startCursor) { entry in
guard case .descending = try directIndexBoundComparator(
lhs: (bound: range.upperBoundExpression, order: .descending),
rhs: entry
)
else { return false }
if range.upperBoundExpression != .extent {
guard case .descending = try directIndexBoundComparator(
lhs: (bound: range.upperBoundExpression, order: .descending),
rhs: entry
)
else { return false }
}

let versionData = Data(entry.headers[0])
let instanceData = Data(entry.content)
Expand All @@ -755,17 +766,23 @@ extension DiskPersistence.Transaction {
return true
}
case .descending:
let startCursor = try await index.insertionCursor(
for: (range.upperBoundExpression, range.order),
comparator: directIndexBoundComparator
)
let startCursor = if range.upperBoundExpression == .extent {
try await index.lastInsertionCursor
} else {
try await index.insertionCursor(
for: (range.upperBoundExpression, .descending),
comparator: directIndexBoundComparator
)
}

try await index.backwardScanEntries(before: startCursor) { entry in
guard case .ascending = try directIndexBoundComparator(
lhs: (bound: range.lowerBoundExpression, order: .ascending),
rhs: entry
)
else { return false }
if range.lowerBoundExpression != .extent {
guard case .ascending = try directIndexBoundComparator(
lhs: (bound: range.lowerBoundExpression, order: .ascending),
rhs: entry
)
else { return false }
}

let versionData = Data(entry.headers[0])
let instanceData = Data(entry.content)
Expand All @@ -792,34 +809,46 @@ extension DiskPersistence.Transaction {

switch range.order {
case .ascending:
let startCursor = try await index.insertionCursor(
for: (range.lowerBoundExpression, range.order),
comparator: secondaryIndexBoundComparator
)
let startCursor = if range.lowerBoundExpression == .extent {
await index.firstInsertionCursor
} else {
try await index.insertionCursor(
for: (range.lowerBoundExpression, .ascending),
comparator: secondaryIndexBoundComparator
)
}

try await index.forwardScanEntries(after: startCursor) { entry in
guard case .descending = try secondaryIndexBoundComparator(
lhs: (bound: range.upperBoundExpression, order: .descending),
rhs: entry
)
else { return false }
if range.upperBoundExpression != .extent {
guard case .descending = try secondaryIndexBoundComparator(
lhs: (bound: range.upperBoundExpression, order: .descending),
rhs: entry
)
else { return false }
}

let entryIdentifier = try JSONDecoder.shared.decode(IdentifierType.self, from: Data(entry.content))
try await identifierConsumer(entryIdentifier)
return true
}
case .descending:
let startCursor = try await index.insertionCursor(
for: (range.upperBoundExpression, range.order),
comparator: secondaryIndexBoundComparator
)
let startCursor = if range.upperBoundExpression == .extent {
try await index.lastInsertionCursor
} else {
try await index.insertionCursor(
for: (range.upperBoundExpression, .descending),
comparator: secondaryIndexBoundComparator
)
}

try await index.backwardScanEntries(before: startCursor) { entry in
guard case .ascending = try secondaryIndexBoundComparator(
lhs: (bound: range.lowerBoundExpression, order: .ascending),
rhs: entry
)
else { return false }
if range.lowerBoundExpression != .extent {
guard case .ascending = try secondaryIndexBoundComparator(
lhs: (bound: range.lowerBoundExpression, order: .ascending),
rhs: entry
)
else { return false }
}

let entryIdentifier = try JSONDecoder.shared.decode(IdentifierType.self, from: Data(entry.content))
try await identifierConsumer(entryIdentifier)
Expand Down

0 comments on commit 9cf005f

Please sign in to comment.