Experiments in applying Entity-Component-System patterns to durable data storage APIs.
1use crate::{sql::Changes, EntityId};
2
3use super::{sql::Components, Component};
4use std::{any, marker::PhantomData};
5
6pub trait Filter {
7 fn sql_query() -> sea_query::SelectStatement;
8}
9
10pub trait DataFilter: Sized {
11 fn sql_query(self) -> sea_query::SelectStatement;
12}
13
14impl DataFilter for () {
15 fn sql_query(self) -> sea_query::SelectStatement {
16 <Self as Filter>::sql_query()
17 }
18}
19
20impl DataFilter for EntityId {
21 fn sql_query(self) -> sea_query::SelectStatement {
22 use sea_query::*;
23 Query::select()
24 .column(Components::Entity)
25 .from(Components::Table)
26 .and_where(Expr::col(Components::Entity).eq(self))
27 .limit(1)
28 .take()
29 }
30}
31
32impl<C> DataFilter for C
33where
34 C: Component,
35{
36 fn sql_query(self) -> sea_query::SelectStatement {
37 use sea_query::*;
38 let expr = match C::to_rusqlite(self).unwrap() {
39 rusqlite::types::Value::Blob(blob) => {
40 struct Unhex;
41 impl Iden for Unhex {
42 fn unquoted(&self, s: &mut dyn Write) {
43 write!(s, "unhex").unwrap();
44 }
45 }
46
47 Expr::col(Components::Data).eq(Func::cust(Unhex).arg(hex::encode_upper(blob)))
48 }
49 rusqlite::types::Value::Text(json) => Expr::col(Components::Data).eq(json),
50 rusqlite::types::Value::Null => Expr::col(Components::Data).is_null(),
51 other => unreachable!("{other:?}"),
52 };
53 <Self as Filter>::sql_query().and_where(expr).take()
54 }
55}
56
57pub struct Query<'a, F, D = ()>
58where
59 F: ?Sized,
60{
61 pub(crate) ecs: &'a crate::Ecs,
62 pub(crate) component_filter: PhantomData<F>,
63 pub(crate) data_filter: D,
64}
65
66impl<'a, F, D> Query<'a, F, D> {
67 pub fn new(ecs: &'a crate::Ecs, data_filter: D) -> Self {
68 Self {
69 ecs,
70 component_filter: PhantomData::default(),
71 data_filter,
72 }
73 }
74}
75
76impl<'a, F, D> Query<'a, F, D>
77where
78 F: Filter,
79 D: DataFilter + Copy,
80{
81 fn as_sql_query(&self) -> sea_query::SelectStatement {
82 let Query { data_filter, .. } = self;
83 and(<F as Filter>::sql_query(), data_filter.sql_query())
84 .distinct()
85 .take()
86 }
87
88 pub fn try_iter(&self) -> Result<impl Iterator<Item = crate::Entity<'a>> + 'a, crate::Error> {
89 let mut query = self.as_sql_query();
90 query.order_by(Components::Entity, sea_query::Order::Asc);
91 self.ecs.fetch(query)
92 }
93
94 pub fn iter(&self) -> impl Iterator<Item = crate::Entity<'a>> + 'a {
95 self.try_iter().unwrap()
96 }
97
98 pub fn try_reverse_iter(
99 &self,
100 ) -> Result<impl Iterator<Item = crate::Entity<'a>> + 'a, crate::Error> {
101 let mut query = self.as_sql_query();
102 query.order_by(Components::Entity, sea_query::Order::Desc);
103 self.ecs.fetch(query)
104 }
105
106 pub fn reverse_iter(&self) -> impl Iterator<Item = crate::Entity<'a>> + 'a {
107 self.try_reverse_iter().unwrap()
108 }
109}
110
111impl<'a, F, D> Query<'a, F, D>
112where
113 F: Filter,
114 D: DataFilter,
115{
116 pub(crate) fn into_sql_query(self) -> sea_query::SelectStatement {
117 let Query { data_filter, .. } = self;
118
119 // Short circuit to skip `select * from components intersect <real
120 // filter>` type of queries
121 let filter_allows_all = any::type_name::<F>() == any::type_name::<()>();
122 let data_filter_allows_all = any::type_name::<D>() == any::type_name::<()>();
123
124 let mut query = match (filter_allows_all, data_filter_allows_all) {
125 (true, false) => data_filter.sql_query(),
126 (false, true) => <F as Filter>::sql_query(),
127 _ => and(<F as Filter>::sql_query(), data_filter.sql_query()),
128 };
129
130 query.distinct().take()
131 }
132
133 pub fn db(&self) -> &crate::Ecs {
134 &self.ecs
135 }
136
137 pub fn try_into_iter(
138 self,
139 ) -> Result<impl Iterator<Item = crate::Entity<'a>> + 'a, crate::Error> {
140 self.ecs.fetch(self.into_sql_query())
141 }
142
143 pub fn into_iter(self) -> impl Iterator<Item = crate::Entity<'a>> + 'a {
144 self.try_into_iter().unwrap()
145 }
146}
147
148impl Filter for () {
149 fn sql_query() -> sea_query::SelectStatement {
150 use sea_query::*;
151 Query::select()
152 .column(Components::Entity)
153 .from(Components::Table)
154 .take()
155 }
156}
157
158impl<C: Component> Filter for C {
159 fn sql_query() -> sea_query::SelectStatement {
160 use sea_query::*;
161 sea_query::Query::select()
162 .column(Components::Entity)
163 .from(Components::Table)
164 .and_where(Expr::col(Components::Component).eq(C::component_name()))
165 .take()
166 }
167}
168
169pub struct Without<C>(PhantomData<C>);
170
171impl<C: Component> Filter for Without<C> {
172 fn sql_query() -> sea_query::SelectStatement {
173 use sea_query::*;
174 Query::select()
175 .column(Components::Entity)
176 .from(Components::Table)
177 .and_where(Expr::col(Components::Entity).not_in_subquery(<C as Filter>::sql_query()))
178 .take()
179 }
180}
181
182pub struct Attached<C>(PhantomData<C>);
183
184impl<C: Component> Filter for Attached<C> {
185 fn sql_query() -> sea_query::SelectStatement {
186 use sea_query::*;
187 Query::select()
188 .column(Changes::Entity)
189 .from(Changes::Table)
190 .and_where(Expr::col(Changes::Component).eq(C::component_name()))
191 .and_where(Expr::col(Changes::Change).eq("attach"))
192 .take()
193 }
194}
195
196pub struct Detached<C>(PhantomData<C>);
197
198impl<C: Component> Filter for Detached<C> {
199 fn sql_query() -> sea_query::SelectStatement {
200 use sea_query::*;
201 Query::select()
202 .column(Changes::Entity)
203 .from(Changes::Table)
204 .and_where(Expr::col(Changes::Component).eq(C::component_name()))
205 .and_where(Expr::col(Changes::Change).eq("detach"))
206 .take()
207 }
208}
209
210pub struct Created;
211
212impl Filter for Created {
213 fn sql_query() -> sea_query::SelectStatement {
214 use sea_query::*;
215 Query::select()
216 .column(Changes::Entity)
217 .from(Changes::Table)
218 .and_where(Expr::col(Changes::Change).eq("create"))
219 .take()
220 }
221}
222
223pub struct Destroyed;
224
225impl Filter for Destroyed {
226 fn sql_query() -> sea_query::SelectStatement {
227 use sea_query::*;
228 Query::select()
229 .column(Changes::Entity)
230 .from(Changes::Table)
231 .and_where(Expr::col(Changes::Change).eq("destroy"))
232 .take()
233 }
234}
235
236pub struct Or<T>(PhantomData<T>);
237
238macro_rules! filter_tuple_impl {
239 ($t:tt, $($ts:tt),+) => {
240 impl<$t, $($ts,)+> Filter for ($t, $($ts,)+)
241 where
242 $t: Filter,
243 $($ts: Filter,)+
244 {
245 fn sql_query() -> sea_query::SelectStatement {
246 and($t::sql_query(), <($($ts,)+)>::sql_query())
247 }
248 }
249
250 impl<$t, $($ts,)+> Filter for Or<($t, $($ts,)+)>
251 where
252 $t: Filter,
253 $($ts: Filter,)+
254 {
255 fn sql_query() -> sea_query::SelectStatement {
256 or($t::sql_query(), <Or<($($ts,)+)>>::sql_query())
257 }
258 }
259
260 impl<$t, $($ts,)+> Filter for Without<($t, $($ts,)+)>
261 where
262 $t: Component,
263 $($ts: Component,)+
264 {
265 fn sql_query() -> sea_query::SelectStatement {
266 and(Without::<$t>::sql_query(), Without::<($($ts,)+)>::sql_query())
267 }
268 }
269
270 impl<$t, $($ts,)+> DataFilter for ($t, $($ts,)+)
271 where
272 $t: DataFilter,
273 $($ts: DataFilter,)+
274 {
275 fn sql_query(self) -> sea_query::SelectStatement {
276 and(self.0.sql_query(), self.1.sql_query())
277 }
278 }
279
280
281 filter_tuple_impl!($($ts),+);
282 };
283 ($t:tt) => {
284 impl<$t: Filter> Filter for ($t,) {
285 fn sql_query() -> sea_query::SelectStatement {
286 $t::sql_query().take()
287 }
288 }
289
290 impl<$t: Filter> Filter for Or<($t,)> {
291 fn sql_query() -> sea_query::SelectStatement {
292 $t::sql_query().take()
293 }
294 }
295
296 impl<$t: Component> Filter for Without<($t,)> {
297 fn sql_query() -> sea_query::SelectStatement {
298 Without::<$t>::sql_query().take()
299 }
300 }
301
302 impl<$t: DataFilter> DataFilter for ($t,) {
303 fn sql_query(self) -> sea_query::SelectStatement {
304 self.0.sql_query()
305 }
306 }
307 };
308}
309
310filter_tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L, M, O, P, Q);
311// filter_tuple_impl!(A, B, C);
312
313fn and(a: sea_query::SelectStatement, b: sea_query::SelectStatement) -> sea_query::SelectStatement {
314 use sea_query::*;
315 Query::select()
316 .column(Asterisk)
317 .from_subquery(a, NullAlias)
318 .union(
319 UnionType::Intersect,
320 Query::select()
321 .column(Asterisk)
322 .from_subquery(b, NullAlias)
323 .take(),
324 )
325 .take()
326}
327
328fn or(a: sea_query::SelectStatement, b: sea_query::SelectStatement) -> sea_query::SelectStatement {
329 use sea_query::*;
330 Query::select()
331 .column(Asterisk)
332 .from_subquery(a, NullAlias)
333 .union(
334 UnionType::Distinct,
335 Query::select()
336 .column(Asterisk)
337 .from_subquery(b, NullAlias)
338 .take(),
339 )
340 .take()
341}
342
343#[cfg(test)]
344mod tests {
345 use serde::{Deserialize, Serialize};
346
347 use super::*;
348 use crate as ecsdb;
349
350 #[derive(Debug, Serialize, Deserialize, Component)]
351 struct A;
352
353 #[derive(Debug, Serialize, Deserialize, Component)]
354 struct B;
355
356 #[test]
357 #[allow(unused)]
358 fn system_fns() {
359 fn sys_a(query: Query<A>) {}
360 fn sys_b(query: Query<(A, Without<B>)>) {}
361 fn sys_c(query: Query<Or<(A, B)>>) {}
362 }
363}