Skip to content

Commit 11076dd

Browse files
committed
clippy + docs
1 parent dc5ae3b commit 11076dd

File tree

12 files changed

+413
-226
lines changed

12 files changed

+413
-226
lines changed

Cargo.lock

Lines changed: 319 additions & 192 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,80 @@ A simplified [Datomic](https://www.datomic.com/) clone built in Rust.
99
## What is this?
1010

1111
This is a side project I've been working on, trying to mimic Datomic's functionality using Rust.
12-
I'm a complete novice with Rust, so I wanted to try learn it with something that feels real: dependencies, tests, etc.
12+
I'm a complete novice with Rust, so I wanted to try learn it with something that feels real:
13+
dependencies, tests, etc.
1314

1415
### Why Datomic?
1516

1617
* Datomic has a well defined and documented API, which can guide me.
1718
* Datomic is closed source, so I don't know the implementatoin details of the real product.
18-
* Datomic is a database - meaning it has to deal with a lot of real-world complexity (I/O, concurrency).
19-
* Datomic (and datalog) introduced very different and interesting conpects compared to traditional databases.
20-
* This project can grow in complexity as much as I want to, depending on what I'll end up implementing.
21-
* Challenging myself to translate an API initially designed for a dynamic language (Clojure) to a staticly typed language.
19+
* Datomic is a database - meaning it has to deal with a lot of real-world complexity (I/O,
20+
concurrency).
21+
* Datomic (and datalog) introduced very different and interesting conpects compared to traditional
22+
databases.
23+
* This project can grow in complexity as much as I want to, depending on what I'll end up
24+
implementing.
25+
* Challenging myself to translate an API initially designed for a dynamic language (Clojure) to a
26+
staticly typed language.
27+
28+
## Query Engine
29+
30+
The project implements a Datalog-style query engine that supports:
31+
32+
### Core Features
33+
34+
* Pattern matching against the database using entity-attribute-value-transaction patterns
35+
* Variable binding and resolution across multiple clauses
36+
* Custom predicate filtering
37+
* Aggregation support (count, min, max, sum, average, count-distinct)
38+
* Attribute name resolution
39+
* Streaming result processing
40+
* Early filtering through predicate evaluation
41+
42+
### Example Query
43+
44+
```rust
45+
// Find all people born after 1980
46+
let query = Query::new()
47+
.find(Find::variable("?person"))
48+
.where(Clause::new()
49+
.with_entity(Pattern::variable("?person"))
50+
.with_attribute(Pattern::ident("person/born"))
51+
.with_value(Pattern::variable("?born")))
52+
.value_pred(
53+
"?born",
54+
|value| matches!(value, &Value::I64(born) if born > 1980),
55+
);
56+
```
57+
58+
## Transactor
59+
60+
The transactor is a core component that handles all data modifications in the database. It
61+
implements Datomic's transactional model with ACID guarantees.
62+
63+
### Features
64+
65+
* **Entity Operations**
66+
- Create new entities with auto-generated IDs
67+
- Update existing entities by ID
68+
- Support for temporary IDs within transactions
69+
- Assert or retract attribute values
70+
71+
* **Transaction Processing**
72+
- Atomic execution of multiple operations
73+
- Automatic timestamp recording for each transaction
74+
- Value type validation against schema
75+
- Uniqueness constraints enforcement
76+
- Cardinality handling (one vs. many)
77+
78+
### Example Transaction
79+
80+
```rust
81+
let tx = Transaction::new()
82+
.with(EntityOperation::on_new()
83+
.assert("person/name", "John Doe")
84+
.assert("person/age", 30))
85+
.with(EntityOperation::on_temp_id("employee-1")
86+
.assert("employee/role", "Developer")
87+
.assert("employee/start-date", "2024-01-01"));
88+
```

src/lib.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod tx;
88
#[cfg(test)]
99
mod tests {
1010
use googletest::prelude::*;
11+
use std::result::Result;
1112
use std::time::SystemTime;
1213

1314
use crate::clock::Instant;
@@ -79,7 +80,7 @@ mod tests {
7980
.query(&self.storage, &self.resolver, query)
8081
.await
8182
.expect("Unable to query");
82-
results.filter_map(|result| result.ok()).collect()
83+
results.filter_map(Result::ok).collect()
8384
}
8485

8586
async fn try_query(
@@ -909,10 +910,10 @@ mod tests {
909910
.with_attribute(Pattern::ident("person/name"))
910911
.with_value(Pattern::variable("?name")),
911912
)
912-
.value_pred("?born", |value| match value {
913-
&Value::I64(born) => born > 1940,
914-
_ => false,
915-
}),
913+
.value_pred(
914+
"?born",
915+
|value| matches!(value, &Value::I64(born) if born > 1940),
916+
),
916917
)
917918
.await;
918919

src/query/aggregation.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use crate::datom::Value;
22
use crate::query::*;
33
use rust_decimal::Decimal;
44
use std::collections::HashSet;
5-
use std::u64;
65

76
#[derive(Clone)]
87
pub enum AggregationState<'a> {

src/query/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use crate::storage::attribute_resolver::ResolveError;
1414
use std::collections::HashMap;
1515
use std::fmt::Debug;
1616
use std::sync::Arc;
17-
use std::u64;
1817
use thiserror::Error;
1918

2019
/// An assignment is a mapping between variables and values such that the clauses are satisfied.
@@ -72,7 +71,7 @@ impl Query {
7271
) -> Self {
7372
self.pred(move |assignment| {
7473
let value = assignment.get(variable);
75-
value.map_or(true, &predicate)
74+
value.is_none_or(&predicate)
7675
})
7776
}
7877
}

src/storage/attribute_resolver.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,14 @@ mod tests {
128128
}
129129
}
130130

131-
impl<'a> WriteStorage for CountingStorage {
131+
impl WriteStorage for CountingStorage {
132132
type Error = <InMemoryStorage as WriteStorage>::Error;
133133
fn save(&mut self, datoms: &[Datom]) -> Result<(), Self::Error> {
134134
self.inner.save(datoms)
135135
}
136136
}
137137

138-
fn create_storage<'a>() -> CountingStorage {
138+
fn create_storage() -> CountingStorage {
139139
let mut storage = CountingStorage::new();
140140
storage.save(&default_datoms()).unwrap();
141141
storage

src/storage/disk.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::marker::PhantomData;
22
use std::path::Path;
3-
use std::u64;
43

54
use rocksdb::*;
65
use thiserror::Error;

src/storage/restricts.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ impl Restricts {
7575

7676
pub fn test(&self, datom: &Datom) -> bool {
7777
datom.op == Op::Assert
78-
&& self.entity.map_or(true, |e| datom.entity == e)
79-
&& self.attribute.map_or(true, |a| datom.attribute == a)
80-
&& self.value.as_ref().map_or(true, |v| &datom.value == v)
78+
&& self.entity.is_none_or(|e| datom.entity == e)
79+
&& self.attribute.is_none_or(|a| datom.attribute == a)
80+
&& self.value.as_ref().is_none_or(|v| &datom.value == v)
8181
&& self.tx.test(datom.tx)
8282
}
8383
}
@@ -91,8 +91,7 @@ pub enum TxRestrict {
9191
impl TxRestrict {
9292
pub fn value(&self) -> u64 {
9393
match *self {
94-
TxRestrict::Exact(tx) => tx,
95-
TxRestrict::AtMost(tx) => tx,
94+
TxRestrict::Exact(tx) | TxRestrict::AtMost(tx) => tx,
9695
}
9796
}
9897

src/storage/serde.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::fmt::Debug;
33
use std::io::Cursor;
44
use std::io::Read;
55
use std::mem::size_of;
6-
use std::u16;
76
use thiserror::Error;
87

98
use crate::datom::*;
@@ -383,10 +382,9 @@ mod value {
383382
match self {
384383
Self::Nil => 0,
385384
Self::Decimal(value) => value.size_hint(),
386-
Self::U64(value) => value.size_hint(),
385+
Self::U64(value) | Self::Ref(value) => value.size_hint(),
387386
Self::I64(value) => value.size_hint(),
388387
Self::Str(value) => value.size_hint(),
389-
Self::Ref(value) => value.size_hint(),
390388
}
391389
}
392390

src/tx/transactor.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
use std::collections::HashMap;
22
use std::collections::HashSet;
3-
use std::u64;
43

54
use crate::clock::Instant;
65
use crate::schema::attribute::*;
76
use crate::schema::*;
87
use crate::storage::attribute_resolver::*;
9-
use crate::storage::restricts::*;
10-
use crate::storage::*;
8+
use crate::storage::restricts::Restricts;
9+
use crate::storage::ReadStorage;
1110
use crate::tx::{
1211
AttributeValue, Datom, EntityOperation, OperatedEntity, Result, Transaction, TransactionError,
1312
TransctionResult, Value, ValueType,

0 commit comments

Comments
 (0)