1#![doc = include_str!("../README.md")]
2#![warn(clippy::all, clippy::pedantic)]
3#![allow(clippy::missing_errors_doc, clippy::cast_sign_loss)]
4
5mod stores;
10
11pub use stores::{
12 artifact::{ArtifactKind, ArtifactStore},
13 assets::AssetStore,
14 cache::{CacheDependency, CacheKind, CacheStore, Lookup},
15 database::UpdateError,
16 secrets::SecretsStore,
17};
18
19use flexbuffers::Reader;
20use ordinary_config::{DatabaseModelConfig, StorageLimits};
21use ordinary_types::{Field, Kind, TimeUnit};
22use saferlmdb::{
23 ConstAccessor, Environment, ReadTransaction, Stat, WriteAccessor, WriteTransaction,
24};
25use std::sync::Arc;
26
27pub use bytes;
28use bytes::{BufMut, Bytes, BytesMut};
29
30use crate::stores::database::DatabaseStore;
31pub use saferlmdb;
32
33fn field_to_bytes(field: &Field, reader: &Reader<&[u8]>) -> Bytes {
34 let mut out = BytesMut::new();
35
36 match &field.kind {
37 Kind::Uuid => {
38 out.put(reader.as_blob().0);
39 }
40 Kind::Bool => {
41 if reader.as_bool() {
42 out.put_u8(1);
43 } else {
44 out.put_u8(0);
45 }
46 }
47 Kind::F32 => out.put_f32(reader.as_f32()),
48 Kind::F64 => out.put_f64(reader.as_f64()),
49 Kind::U8 => out.put_u8(reader.as_u8()),
50 Kind::U16 => out.put_u16(reader.as_u16()),
51 Kind::U32 => out.put_u32(reader.as_u32()),
52 Kind::U64 => out.put_u64(reader.as_u64()),
53 Kind::I8 => out.put_i8(reader.as_i8()),
54 Kind::I16 => out.put_i16(reader.as_i16()),
55 Kind::I32 => out.put_i32(reader.as_i32()),
56 Kind::I64 => out.put_i64(reader.as_i64()),
57 Kind::String | Kind::Markdown | Kind::Json | Kind::Url => {
58 out.put(reader.as_str().as_bytes());
59 }
60 Kind::Timestamp { unit, .. } => match unit {
61 TimeUnit::Seconds => out.put_i64(reader.as_i64()),
62 },
63 _ => {
64 tracing::error!(
65 "kind '{:?}' does not support encrypted/compressed",
66 field.kind
67 );
68 }
69 }
70
71 out.into()
72}
73
74fn push_field_from_bytes(
75 field: &Field,
76 bytes: &[u8],
77 dest: &mut flexbuffers::VectorBuilder,
78) -> anyhow::Result<()> {
79 match &field.kind {
80 Kind::Uuid => dest.push(flexbuffers::Blob(bytes)),
81 Kind::Bool => dest.push(if bytes.len() == 1 {
82 bytes[0] == 1
83 } else {
84 false
85 }),
86 Kind::F32 => dest.push(f32::from_be_bytes(bytes.try_into()?)),
87 Kind::F64 => dest.push(f64::from_be_bytes(bytes.try_into()?)),
88 Kind::U8 => dest.push(u8::from_be_bytes(bytes.try_into()?)),
89 Kind::U16 => dest.push(u16::from_be_bytes(bytes.try_into()?)),
90 Kind::U32 => dest.push(u32::from_be_bytes(bytes.try_into()?)),
91 Kind::U64 => dest.push(u64::from_be_bytes(bytes.try_into()?)),
92 Kind::I8 => dest.push(i8::from_be_bytes(bytes.try_into()?)),
93 Kind::I16 => dest.push(i16::from_be_bytes(bytes.try_into()?)),
94 Kind::I32 => dest.push(i32::from_be_bytes(bytes.try_into()?)),
95 Kind::I64 => dest.push(i64::from_be_bytes(bytes.try_into()?)),
96 Kind::String | Kind::Markdown | Kind::Json | Kind::Url => {
97 dest.push(std::str::from_utf8(bytes)?);
98 }
99 Kind::Timestamp { unit, .. } => match unit {
100 TimeUnit::Seconds => dest.push(i64::from_be_bytes(bytes.try_into()?)),
101 },
102 _ => {
103 tracing::error!(
104 "kind '{:?}' does not support encrypted/compressed",
105 field.kind
106 );
107 }
108 }
109
110 Ok(())
111}
112
113pub enum Transaction<'a> {
114 Read(&'a ReadTransaction<'a>),
115 Write(WriteTransaction<'a>),
116}
117
118enum Accessor<'a> {
119 Const(&'a ConstAccessor<'a>),
120 Write(&'a WriteAccessor<'a>),
121}
122
123#[derive(Clone, Debug)]
126#[allow(clippy::type_complexity)]
127pub struct RefDepth(pub Vec<((u8, u8, Option<[u8; 16]>), RefDepth)>);
128
129#[derive(Clone, Debug)]
131pub enum QueryExpression {
132 Gte,
133 Gt,
134 Lte,
135 Lt,
136 Eq,
137 BeginsWith,
138}
139
140impl QueryExpression {
141 #[must_use]
142 pub fn as_byte(&self) -> u8 {
143 match self {
144 Self::Gte => 0,
145 Self::Lte => 1,
146 Self::Eq => 2,
147 Self::Gt => 3,
148 Self::Lt => 4,
149 Self::BeginsWith => 5,
150 }
151 }
152}
153
154pub struct OrdinaryStorage {
155 env: Arc<Environment>,
157
158 pub artifact: ArtifactStore,
159 pub asset: AssetStore,
160 pub cache: CacheStore,
161 pub secrets: SecretsStore,
162 pub database: DatabaseStore,
163}
164
165impl OrdinaryStorage {
167 #[allow(clippy::too_many_lines)]
168 pub fn new(
169 limits: StorageLimits,
170 model_configs: Vec<DatabaseModelConfig>,
171 encryption_key: [u8; 32],
172 env: &Arc<Environment>,
173 log_sizes: bool,
174 ) -> anyhow::Result<Self> {
175 Ok(Self {
176 env: env.clone(),
177
178 database: DatabaseStore::new(
179 limits.database,
180 model_configs,
181 encryption_key,
182 env,
183 log_sizes,
184 )?,
185 artifact: ArtifactStore::new(limits.artifact, env, log_sizes)?,
186 asset: AssetStore::new(limits.assets, env, log_sizes)?,
187 cache: CacheStore::new(limits.cache, env, log_sizes)?,
188 secrets: SecretsStore::new(env, encryption_key)?,
189 })
190 }
191
192 pub fn stat(&self) -> anyhow::Result<Stat> {
193 let stat = self.env.stat()?;
194 Ok(stat)
195 }
196
197 pub fn write_txn(&self) -> anyhow::Result<WriteTransaction<'_>> {
198 let txn = WriteTransaction::new(self.env.clone())?;
199 Ok(txn)
200 }
201
202 pub fn read_txn(&self) -> anyhow::Result<ReadTransaction<'_>> {
203 let txn = ReadTransaction::new(self.env.clone())?;
204 Ok(txn)
205 }
206}