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}