Experiments in applying Entity-Component-System patterns to durable data storage APIs.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Track last_modified for components

moritz.vongoewels.de 2bfb311a 66c74710

verified
+153 -2
+9
src/entity.rs
··· 114 114 self.try_component_names().unwrap() 115 115 } 116 116 117 + pub fn last_modified(&self) -> chrono::DateTime<chrono::Utc> { 118 + self.try_last_modified().expect("Non-Error") 119 + } 120 + 117 121 #[tracing::instrument(name = "attach", level = "debug", skip_all)] 118 122 pub fn try_attach<B: Bundle>(self, component: B) -> Result<Self, Error> { 119 123 let components = B::to_rusqlite(component)?; ··· 169 173 .query_map(params![self.id()], |row| row.get(0))? 170 174 .collect::<Result<Vec<_>, _>>()?; 171 175 Ok(names.into_iter()) 176 + } 177 + 178 + #[tracing::instrument(name = "last_modified", level = "debug")] 179 + pub fn try_last_modified(&self) -> Result<chrono::DateTime<chrono::Utc>, Error> { 180 + self.0.try_last_modified(self.id()).map_err(Error::from) 172 181 } 173 182 } 174 183
+51
src/lib.rs
··· 14 14 pub use resource::*; 15 15 16 16 mod system; 17 + use ::rusqlite::params; 17 18 pub use system::*; 18 19 19 20 use std::path::Path; ··· 113 114 ) -> Result<impl Iterator<Item = Entity<'a>> + 'a, Error> { 114 115 let query = query::Query::<(), _>::new(self, components); 115 116 query.try_into_iter() 117 + } 118 + } 119 + 120 + impl Ecs { 121 + /// Returns the highest `last_modified` from all components of `entity`. 122 + /// Returns `chrono::DateTime::MIN_UTC` if `entity` has no components 123 + fn try_last_modified( 124 + &self, 125 + entity: EntityId, 126 + ) -> Result<chrono::DateTime<chrono::Utc>, rusqlite::Error> { 127 + let mut stmt = self.conn.prepare_cached( 128 + "select max(last_modified) as last_modified from components where entity = ?", 129 + )?; 130 + 131 + let last_modified = stmt 132 + .query_map(params![&entity], |row| { 133 + row.get::<_, String>("last_modified") 134 + })? 135 + .map(|dt| dt.expect("Valid chrono::DateTime")) 136 + .next(); 137 + 138 + let last_modified = if let Some(last_modified) = last_modified { 139 + chrono::DateTime::parse_from_rfc3339(&last_modified) 140 + .expect("Valid chrono::DateTime") 141 + .to_utc() 142 + } else { 143 + chrono::DateTime::<chrono::Utc>::MIN_UTC 144 + }; 145 + 146 + Ok(last_modified) 116 147 } 117 148 } 118 149 ··· 465 496 assert!(e2.matches::<A>()); 466 497 assert!(e2.matches::<B>()); 467 498 assert!(e2.matches::<(A, B)>()); 499 + } 500 + 501 + #[test] 502 + fn last_modified() { 503 + #[derive(Serialize, Deserialize, Component)] 504 + struct A; 505 + #[derive(Serialize, Deserialize, Component)] 506 + struct B; 507 + 508 + let db = super::Ecs::open_in_memory().unwrap(); 509 + let e = db.new_entity().attach(A); 510 + 511 + assert!(e.last_modified() > chrono::Utc::now() - chrono::Duration::minutes(1)); 512 + 513 + let old = e.last_modified(); 514 + 515 + std::thread::sleep(std::time::Duration::from_millis(2)); 516 + 517 + e.attach(B); 518 + assert!(e.last_modified() > old); 468 519 } 469 520 }
+65
src/migrations/01_last_modified.sql
··· 1 + begin; 2 + 3 + alter table components 4 + rename to components_old; 5 + 6 + create table components ( 7 + entity integer not null, 8 + component text not null, 9 + data blob, 10 + last_modified rfc3339 not null default (strftime ('%Y-%m-%dT%H:%M:%fZ')) 11 + ); 12 + 13 + create unique index if not exists components_entity_component_unqiue_idx on components (entity, component); 14 + 15 + create index if not exists components_component_idx on components (component); 16 + 17 + insert into 18 + components (entity, component, data) 19 + select 20 + * 21 + from 22 + components_old; 23 + 24 + create trigger if not exists components_last_modified_trigger before 25 + update on components for each row begin 26 + update components 27 + set 28 + last_modified = strftime ('%Y-%m-%dT%H:%M:%fZ') 29 + where 30 + entity = new.entity 31 + and component = new.component; 32 + 33 + end; 34 + 35 + alter table resources 36 + rename to resources_old; 37 + 38 + create table resources ( 39 + name text not null unique, 40 + data blob, 41 + last_modified rfc3339 not null default (strftime ('%Y-%m-%dT%H:%M:%fZ')) 42 + ); 43 + 44 + insert into 45 + resources (name, data) 46 + select 47 + * 48 + from 49 + resources_old; 50 + 51 + create trigger if not exists resources_last_modified_trigger before 52 + update on resources for each row begin 53 + update resources 54 + set 55 + last_modified = strftime ('%Y-%m-%dT%H:%M:%fZ') 56 + where 57 + name = new.name; 58 + 59 + end; 60 + 61 + drop table components_old; 62 + 63 + drop table resources_old; 64 + 65 + commit;
+28 -2
src/schema.sql
··· 1 1 create table if not exists components ( 2 2 entity integer not null, 3 3 component text not null, 4 - data blob 4 + data blob, 5 + last_modified rfc3339 not null default (strftime ('%Y-%m-%dT%H:%M:%fZ')) 5 6 ); 6 7 7 8 create unique index if not exists components_entity_component_unqiue_idx on components (entity, component); 8 9 9 10 create index if not exists components_component_idx on components (component); 10 11 11 - create table if not exists resources (name text not null unique, data blob); 12 + create trigger if not exists components_last_modified_trigger before 13 + update on components for each row begin 14 + update components 15 + set 16 + last_modified = strftime ('%Y-%m-%dT%H:%M:%fZ') 17 + where 18 + entity = new.entity 19 + and component = new.component; 20 + 21 + end; 22 + 23 + create table if not exists resources ( 24 + name text not null unique, 25 + data blob, 26 + last_modified rfc3339 not null default (strftime ('%Y-%m-%dT%H:%M:%fZ')) 27 + ); 28 + 29 + create trigger if not exists resources_last_modified_trigger before 30 + update on resources for each row begin 31 + update resources 32 + set 33 + last_modified = strftime ('%Y-%m-%dT%H:%M:%fZ') 34 + where 35 + name = new.name; 36 + 37 + end;