Skip to content

Commit 8646f27

Browse files
committed
wip: add initial DatabaseBuilder
1 parent 2e6abcf commit 8646f27

File tree

7 files changed

+83
-233
lines changed

7 files changed

+83
-233
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ colored = "3.0.0"
4646
futures = "0.3.30"
4747
futures-util = "0.3.31"
4848
image = "0.25.5"
49-
native_db = "0.8.1"
50-
native_model = "0.4.20"
49+
native_db = { git = "https://github.com/cilki/native_db" }
50+
native_model = "0.6.1"
5151
os_info = "3.8.2"
5252
pem = "3.0.4"
5353
rand = "0.9.0"

sandpolis-database/src/lib.rs

Lines changed: 46 additions & 215 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::oid::Oid;
2-
use anyhow::{Result, anyhow};
2+
use anyhow::{Result, anyhow, bail};
33
use chrono::{DateTime, Utc};
44
use native_db::{Models, ToKey};
55
use serde::de::DeserializeOwned;
@@ -8,245 +8,76 @@ use sled::transaction::ConflictableTransactionError::Abort;
88
use std::ops::Range;
99
use std::path::Path;
1010
use std::{marker::PhantomData, sync::Arc};
11-
use tempfile::{TempDir, tempdir};
1211
use tracing::{debug, trace};
1312

1413
pub mod config;
1514
pub mod oid;
1615

17-
#[derive(Clone)]
18-
pub struct Database(pub Arc<native_db::Database<'static>>);
19-
20-
impl Database {
21-
/// Initialize a new memory-only database.
22-
pub fn new_ephemeral(models: &'static Models) -> Result<Self> {
23-
debug!("Initializing ephemeral database");
24-
Ok(Self(Arc::new(
25-
native_db::Builder::new().create_in_memory(models)?,
26-
)))
27-
}
28-
29-
/// Initialize a new persistent database from the given directory.
30-
pub fn new<P>(models: &'static Models, path: P) -> Result<Self>
31-
where
32-
P: AsRef<Path>,
33-
{
34-
let path = path.as_ref();
35-
36-
debug!(path = %path.display(), "Initializing persistent database");
37-
Ok(Self(Arc::new(
38-
native_db::Builder::new().create(models, path)?,
39-
)))
40-
}
41-
}
42-
43-
#[derive(Clone)]
44-
pub struct Collection<T>
45-
where
46-
T: Serialize + DeserializeOwned,
47-
{
48-
db: sled::Tree,
49-
oid: Oid,
50-
data: PhantomData<T>,
51-
}
52-
53-
impl<T: Serialize + DeserializeOwned + std::fmt::Debug> Collection<T> {
54-
pub fn get_document(&self, oid: impl TryInto<Oid>) -> Result<Option<Document<T>>> {
55-
let oid = self.oid.extend(oid)?;
56-
57-
Ok(if let Some(data) = self.db.get(&oid)? {
58-
Some(Document {
59-
db: self.db.clone(),
60-
data: serde_cbor::from_slice::<T>(&data)?,
61-
oid,
62-
})
63-
} else {
64-
None
65-
})
66-
}
67-
68-
pub fn documents(&self) -> impl Iterator<Item = Result<Document<T>>> + use<'_, T> {
69-
trace!(oid = %self.oid, "Querying collection");
70-
let mut start = self.oid.0.clone();
71-
start.push('/' as u8);
72-
let mut end = start.clone();
73-
end.push(0xff);
74-
75-
self.db
76-
.range::<&[u8], Range<&[u8]>>(&start..&end)
77-
.map(|r| match r {
78-
Ok((key, value)) => match (key.try_into(), serde_cbor::from_slice::<T>(&value)) {
79-
(Ok(oid), Ok(data)) => {
80-
trace!(oid = %oid, "Yielding document");
81-
Ok(Document {
82-
db: self.db.clone(),
83-
oid,
84-
data,
85-
})
86-
}
87-
(Ok(_), Err(_)) => todo!(),
88-
(Err(_), Ok(_)) => todo!(),
89-
(Err(_), Err(_)) => todo!(),
90-
},
91-
Err(_) => todo!(),
92-
})
93-
}
94-
95-
pub fn collection<U>(&self, oid: impl TryInto<Oid>) -> Result<Collection<U>>
96-
where
97-
U: Serialize + DeserializeOwned,
98-
{
99-
Ok(Collection {
100-
db: self.db.clone(),
101-
oid: self.oid.extend(oid)?,
102-
data: PhantomData {},
103-
})
104-
}
105-
106-
pub fn insert_document(&self, oid: impl TryInto<Oid>, data: T) -> Result<Document<T>> {
107-
let oid = self.oid.extend(oid)?;
108-
let d = serde_cbor::to_vec(&data)?;
109-
110-
trace!(oid = %oid, data = ?data, "Inserting new document");
111-
self.db
112-
.transaction(|tx_db| {
113-
if tx_db.get(&oid)?.is_some() {
114-
return Err(Abort(anyhow::anyhow!("Already exists")));
115-
}
116-
117-
tx_db.insert(oid.clone(), d.clone())?;
118-
Ok(())
119-
})
120-
.map_err(|_| anyhow::anyhow!(""))?;
121-
122-
Ok(Document {
123-
oid,
124-
data,
125-
db: self.db.clone(),
126-
})
127-
}
16+
pub struct DatabaseBuilder {
17+
models: &'static Models,
18+
groups: Vec<(String, native_db::Database<'static>)>,
12819
}
12920

130-
impl<T: Serialize + DeserializeOwned + Default> Collection<T> {
131-
pub fn document(&self, oid: impl TryInto<Oid>) -> Result<Document<T>> {
132-
let oid = self.oid.extend(oid)?;
133-
134-
Ok(Document {
135-
db: self.db.clone(),
136-
data: if let Some(data) = self.db.get(&oid)? {
137-
serde_cbor::from_slice::<T>(&data)?
138-
} else {
139-
// TODO insert
140-
T::default()
141-
},
142-
oid,
143-
})
21+
impl DatabaseBuilder {
22+
pub fn new(models: &'static Models) -> Self {
23+
Self {
24+
models,
25+
// Don't bother with a map because groups are few
26+
groups: Vec::with_capacity(1),
27+
}
14428
}
145-
}
14629

147-
#[derive(Clone)]
148-
pub struct Document<T>
149-
where
150-
T: Serialize + DeserializeOwned,
151-
{
152-
pub db: sled::Tree,
153-
pub oid: Oid,
154-
pub data: T, // TODO impl AsRef?
155-
}
30+
/// Create a new ephemeral database for the given group name.
31+
pub fn add_ephemeral(mut self, group: impl ToString) -> Result<Self> {
32+
let name = group.to_string();
15633

157-
impl<T: Serialize + DeserializeOwned> Document<T> {
158-
// pub fn update<U>(&mut self, update: )
159-
160-
pub fn get_document<U>(&self, oid: impl TryInto<Oid>) -> Result<Option<Document<U>>>
161-
where
162-
U: Serialize + DeserializeOwned,
163-
{
164-
let oid = self.oid.extend(oid)?;
165-
166-
Ok(if let Some(data) = self.db.get(&oid)? {
167-
Some(Document {
168-
db: self.db.clone(),
169-
data: serde_cbor::from_slice::<U>(&data)?,
170-
oid,
171-
})
172-
} else {
173-
None
174-
})
175-
}
34+
// Check for duplicates
35+
for (n, _) in self.groups.iter() {
36+
if name == *n {
37+
bail!("Duplicate group");
38+
}
39+
}
17640

177-
pub fn document<U>(&self, oid: impl TryInto<Oid>) -> Result<Document<U>>
178-
where
179-
U: Serialize + DeserializeOwned + Default,
180-
{
181-
let oid = self.oid.extend(oid)?;
41+
debug!(group = name, "Initializing ephemeral database");
42+
self.groups.push((
43+
name,
44+
native_db::Builder::new().create_in_memory(self.models)?,
45+
));
18246

183-
Ok(Document {
184-
db: self.db.clone(),
185-
data: if let Some(data) = self.db.get(&oid)? {
186-
trace!(oid = %oid, "Loading document");
187-
serde_cbor::from_slice::<U>(&data)?
188-
} else {
189-
trace!(oid = %oid, "Creating new document");
190-
// TODO insert
191-
U::default()
192-
},
193-
oid,
194-
})
47+
Ok(self)
19548
}
19649

197-
pub fn insert_document<U>(&self, oid: impl TryInto<Oid>, data: U) -> Result<Document<U>>
50+
/// Load or create a new persistent database for the given group name.
51+
pub fn add_persistent<P>(mut self, group: impl ToString, path: P) -> Result<Self>
19852
where
199-
U: Serialize + DeserializeOwned + std::fmt::Debug,
53+
P: AsRef<Path>,
20054
{
201-
let oid = self.oid.extend(oid)?;
202-
let d = serde_cbor::to_vec(&data)?;
55+
let name = group.to_string();
56+
let path = path.as_ref();
20357

204-
trace!(oid = %oid, data = ?data, "Inserting new document");
205-
self.db
206-
.transaction(|tx_db| {
207-
if tx_db.get(&oid)?.is_some() {
208-
return Err(Abort(anyhow::anyhow!("Already exists")));
209-
}
58+
// Check for duplicates
59+
for (n, _) in self.groups.iter() {
60+
if name == *n {
61+
bail!("Duplicate group");
62+
}
63+
}
21064

211-
tx_db.insert(oid.clone(), d.clone())?;
212-
Ok(())
213-
})
214-
.map_err(|_| anyhow::anyhow!(""))?;
65+
debug!(group = name, path = %path.display(), "Initializing persistent database");
66+
self.groups
67+
.push((name, native_db::Builder::new().create(self.models, path)?));
21568

216-
Ok(Document {
217-
oid,
218-
data,
219-
db: self.db.clone(),
220-
})
69+
Ok(self)
22170
}
22271

223-
pub fn collection<U>(&self, oid: impl TryInto<Oid>) -> Result<Collection<U>>
224-
where
225-
U: Serialize + DeserializeOwned,
226-
{
227-
Ok(Collection {
228-
db: self.db.clone(),
229-
oid: self.oid.extend(oid)?,
230-
data: PhantomData {},
231-
})
72+
pub fn build(self) -> Database {
73+
Database(Arc::new(self.groups))
23274
}
23375
}
23476

235-
impl<T: Serialize + DeserializeOwned + Clone> Document<T> {
236-
pub fn mutate<F>(&mut self, mutator: F) -> Result<()>
237-
where
238-
F: Fn(&mut T) -> Result<()>,
239-
{
240-
// Create a copy so we can tell what changed
241-
let mut object = self.data.clone();
242-
mutator(&mut object)?;
243-
244-
// TODO detect changes and update history as part of a transaction
77+
#[derive(Clone)]
78+
pub struct Database(Arc<Vec<(String, native_db::Database<'static>)>>);
24579

246-
self.db.insert(&self.oid, serde_cbor::to_vec(&object)?)?;
247-
Ok(())
248-
}
249-
}
80+
impl Database {}
25081

25182
// TODO Eq based only on inner?
25283
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Hash)]

sandpolis/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ clap = { workspace = true }
2020
colored = { workspace = true }
2121
fossable = "0.1.2"
2222
futures = { workspace = true }
23+
native_db = { workspace = true }
2324
rand = { workspace = true }
2425
rustls = { workspace = true }
2526
sandpolis-agent = { path = "../sandpolis-agent", version = "0.0.1" }

0 commit comments

Comments
 (0)