helix_db/
query_generator.rs1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3use std::path::{Path, PathBuf};
4
5pub const LEGACY_QUERY_BUNDLE_VERSION_V4: u32 = 4;
7
8pub const QUERY_BUNDLE_VERSION: u32 = 5;
10
11pub const SUPPORTED_QUERY_BUNDLE_VERSIONS: &[u32] =
13 &[LEGACY_QUERY_BUNDLE_VERSION_V4, QUERY_BUNDLE_VERSION];
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub enum QueryParamType {
18 Bool,
20 I64,
22 F64,
24 F32,
26 String,
28 DateTime,
30 Bytes,
32 Value,
34 Object,
36 Array(Box<QueryParamType>),
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub struct QueryParameter {
43 pub name: String,
45 pub ty: QueryParamType,
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51pub struct QueryBundle {
52 pub version: u32,
54 pub read_routes: BTreeMap<String, crate::ReadBatch>,
56 pub write_routes: BTreeMap<String, crate::WriteBatch>,
58 pub read_parameters: BTreeMap<String, Vec<QueryParameter>>,
60 pub write_parameters: BTreeMap<String, Vec<QueryParameter>>,
62}
63
64impl Default for QueryBundle {
65 fn default() -> Self {
66 Self {
67 version: QUERY_BUNDLE_VERSION,
68 read_routes: BTreeMap::new(),
69 write_routes: BTreeMap::new(),
70 read_parameters: BTreeMap::new(),
71 write_parameters: BTreeMap::new(),
72 }
73 }
74}
75
76pub struct RegisteredReadQuery {
78 pub name: &'static str,
80 pub build: fn() -> crate::ReadBatch,
82 pub parameters: fn() -> Vec<QueryParameter>,
84}
85
86pub struct RegisteredWriteQuery {
88 pub name: &'static str,
90 pub build: fn() -> crate::WriteBatch,
92 pub parameters: fn() -> Vec<QueryParameter>,
94}
95
96inventory::collect!(RegisteredReadQuery);
97inventory::collect!(RegisteredWriteQuery);
98
99#[derive(Debug)]
101pub enum GenerateError {
102 DuplicateQueryName(String),
104 Io(std::io::Error),
106 Json(sonic_rs::Error),
108 UnsupportedVersion {
110 found: u32,
112 expected: u32,
114 },
115}
116
117impl std::fmt::Display for GenerateError {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 match self {
120 Self::DuplicateQueryName(name) => {
121 write!(f, "duplicate generated query name: {name}")
122 }
123 Self::Io(err) => write!(f, "io error: {err}"),
124 Self::Json(err) => write!(f, "json error: {err}"),
125 Self::UnsupportedVersion { found, expected } => {
126 write!(
127 f,
128 "unsupported query bundle version {found} (expected {expected})"
129 )
130 }
131 }
132 }
133}
134
135impl std::error::Error for GenerateError {}
136
137impl From<std::io::Error> for GenerateError {
138 fn from(value: std::io::Error) -> Self {
139 Self::Io(value)
140 }
141}
142
143impl From<sonic_rs::Error> for GenerateError {
144 fn from(value: sonic_rs::Error) -> Self {
145 Self::Json(value)
146 }
147}
148
149pub fn build_query_bundle() -> Result<QueryBundle, GenerateError> {
151 let mut bundle = QueryBundle::default();
152
153 for registered in inventory::iter::<RegisteredReadQuery> {
154 if bundle.read_routes.contains_key(registered.name)
155 || bundle.write_routes.contains_key(registered.name)
156 {
157 return Err(GenerateError::DuplicateQueryName(
158 registered.name.to_string(),
159 ));
160 }
161
162 bundle
163 .read_routes
164 .insert(registered.name.to_string(), (registered.build)());
165 bundle
166 .read_parameters
167 .insert(registered.name.to_string(), (registered.parameters)());
168 }
169
170 for registered in inventory::iter::<RegisteredWriteQuery> {
171 if bundle.read_routes.contains_key(registered.name)
172 || bundle.write_routes.contains_key(registered.name)
173 {
174 return Err(GenerateError::DuplicateQueryName(
175 registered.name.to_string(),
176 ));
177 }
178
179 bundle
180 .write_routes
181 .insert(registered.name.to_string(), (registered.build)());
182 bundle
183 .write_parameters
184 .insert(registered.name.to_string(), (registered.parameters)());
185 }
186
187 Ok(bundle)
188}
189
190pub fn serialize_query_bundle(bundle: &QueryBundle) -> Result<Vec<u8>, GenerateError> {
192 Ok(sonic_rs::to_vec_pretty(bundle)?)
193}
194
195pub fn deserialize_query_bundle(bytes: &[u8]) -> Result<QueryBundle, GenerateError> {
197 let bundle: QueryBundle = sonic_rs::from_slice(bytes)?;
198
199 if !SUPPORTED_QUERY_BUNDLE_VERSIONS.contains(&bundle.version) {
200 return Err(GenerateError::UnsupportedVersion {
201 found: bundle.version,
202 expected: QUERY_BUNDLE_VERSION,
203 });
204 }
205
206 Ok(bundle)
207}
208
209pub fn write_query_bundle_to_path<P: AsRef<Path>>(
211 bundle: &QueryBundle,
212 path: P,
213) -> Result<(), GenerateError> {
214 let bytes = serialize_query_bundle(bundle)?;
215 std::fs::write(path, bytes)?;
216 Ok(())
217}
218
219pub fn read_query_bundle_from_path<P: AsRef<Path>>(path: P) -> Result<QueryBundle, GenerateError> {
221 let bytes = std::fs::read(path)?;
222 deserialize_query_bundle(&bytes)
223}
224
225pub fn generate() -> Result<PathBuf, GenerateError> {
227 generate_to_path("queries.json")
228}
229
230pub fn generate_to_path<P: AsRef<Path>>(path: P) -> Result<PathBuf, GenerateError> {
232 let path = path.as_ref();
233 let bundle = build_query_bundle()?;
234 write_query_bundle_to_path(&bundle, path)?;
235 Ok(path.to_path_buf())
236}