mattmassicotte/empire
It's a record store for Swift.
Integration
dependencies: [
.package(url: "https://github.com/mattmassicotte/Empire", branch: "main")
]Concept
Empire stores records in a sorted-key index, which resembles an ordered-map data structure. This has profound implications on query capabilities and how data is modeled. Ordered maps offer much less flexibility than the table-based system of an SQL database. Because of this, the queries you need to support can heavily influence how you model your data.
Record Structure
Conceptually, you can think of each record as being split into two components: the "index key" and "fields".
The "index key" component is the only means of retrieving data efficiently. It is not possible to run queries against non-key fields without doing a full scan of the data. This makes index keys a critical part of your design.
Consider the following record definition. It has a composite key, defined by the two arguments to the @IndexKeyRecord macro.
@IndexKeyRecord("lastName", "firstName")
struct Person {
let lastName: String
let firstName: String
let age: Int
}These records are stored in order, first by lastName and then by firstName. The ordering of key components is very important. Only the last component of a query can be a non-equality comparison. If you want to look for a range of a key component, you must restrict all previous components.
// scan query on the first component
store.select(lastName: .greaterThan("Dallas"))
// constrain first component, scan query on the second
store.select(lastName: "Dallas", firstName: .lessThanOrEqual("Korben"))
// ERROR: an unsupported key arrangement
store.select(lastName: .lessThan("Zorg"), firstName: .lessThanOrEqual("Jean-Baptiste"))The code generated by the @IndexKeyRecord macro makes it a compile-time error to write invalid queries. Because it is not part of the index key, it is not possible to run efficient queries that involve the age property.
Concurrency Support
You have a few different options for controlling the thread-safety and execution of your database.
// A non-Sendable type
let store = try Store(path: "/path/to/store")
// An actor-isolated and Sendable store
let backgroundStore = try BackgroundStore(path: "/path/to/store")
// A hybrid that is synchronously accessible from MainActor
// and also supports executing transactions in the background
let backgroundableStore = try BackgroundableStore(path: "/path/to/store")
backgroundableStore.main.withTransaction { ... }
await backgroundableStore.background.withTransaction { ... }You can also make your own arragements by either hanging onto a Store directly within an actor you control, or by creating and using the primitive LockingDatabase type directly.
The transaction mechanism supports cancellation. If the executing Task is cancelled, a transaction will be aborted.
Building
This is only necessary if you are interested in working on the project yourself.
Note: requires Xcode 16
- clone the repo
git submodule update --init --recursive
To run the benchmarks:
cd Benchmarksswift package --disable-sandbox benchmark --target CoreDataBenchmarksswift package benchmark --target EmpireBenchmarks
Questions
Why does this exist?
I'm not sure! I haven't used Core Data or SwiftData too much. But I have used the distributed database Cassandra quite a lot and DynamoDB a bit. Then one day I discovered [LMDB][LMDB]. Its data model is quite similar to Cassandra and I got interested in playing around with it. This just kinda materialized from those experiments.
Can I use this?
Sure!
Should I use this?
User data is important. This library has a bunch of tests, but it has no real-world testing. I plan on using this myself, but even I haven't gotten to that yet. It should be considered functional, but experimental.
Contributing and Collaboration
I would love to hear from you! Issues or pull requests work great. Both a [Matrix space][matrix] and [Discord][discord] are available for live help, but I have a strong bias towards answering in the form of documentation. You can also find me on the web.
I prefer collaboration, and would love to find ways to work together if you have a similar project.
I prefer indentation with tabs for improved accessibility. But, I'd rather you use the system you want and make a PR than hesitate because of whitespace.
By participating in this project you agree to abide by the Contributor Code of Conduct.
[build status]: https://github.com/mattmassicotte/Empire/actions [build status badge]: https://github.com/mattmassicotte/Empire/workflows/CI/badge.svg [platforms]: https://swiftpackageindex.com/mattmassicotte/Empire [platforms badge]: https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fmattmassicotte%2FEmpire%2Fbadge%3Ftype%3Dplatforms [documentation]: https://swiftpackageindex.com/mattmassicotte/Empire/main/documentation [documentation badge]: https://img.shields.io/badge/Documentation-DocC-blue [matrix]: https://matrix.to/#/%23chimehq%3Amatrix.org [matrix badge]: https://img.shields.io/matrix/chimehq%3Amatrix.org?label=Matrix [discord]: https://discord.gg/esFpX6sErJ [LMDB]: https://www.symas.com/lmdb
Package Metadata
Repository: mattmassicotte/empire
Default branch: main
README: README.md