From c11aa24855fe60795d2fb5fe501e7d01e8c2eb80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20HUBERT=20/=20PALO-IT?= Date: Tue, 29 Nov 2022 16:22:55 +0100 Subject: [PATCH] make SQL entities to create their projection --- mithril-common/src/database/db_version.rs | 56 +++++++------------ mithril-common/src/sqlite/entity.rs | 5 ++ mithril-common/src/sqlite/projection.rs | 65 ++++++++++------------- mithril-common/src/sqlite/provider.rs | 36 ++++--------- 4 files changed, 63 insertions(+), 99 deletions(-) diff --git a/mithril-common/src/database/db_version.rs b/mithril-common/src/database/db_version.rs index 4826b73332b..2a2ff2909a0 100644 --- a/mithril-common/src/database/db_version.rs +++ b/mithril-common/src/database/db_version.rs @@ -8,7 +8,7 @@ use std::{ use chrono::NaiveDateTime; use sqlite::{Connection, Row, Value}; -use crate::sqlite::{HydrationError, Projection, ProjectionField, Provider, SqLiteEntity}; +use crate::sqlite::{HydrationError, Projection, Provider, SqLiteEntity}; use super::DbVersion; @@ -69,35 +69,9 @@ impl SqLiteEntity for DatabaseVersion { .map_err(|e| HydrationError::InvalidData(format!("{}", e)))?, }) } -} - -impl PartialOrd for DatabaseVersion { - fn partial_cmp(&self, other: &Self) -> Option { - if self.application_type != other.application_type { - None - } else { - self.version.partial_cmp(&other.version) - } - } -} -/// Projection dedicated to [DatabaseVersion] entities. -struct DatabaseVersionProjection { - fields: Vec, -} - -impl Projection for DatabaseVersionProjection { - fn set_field(&mut self, field: ProjectionField) { - self.fields.push(field); - } - - fn get_fields(&self) -> &Vec { - &self.fields - } -} -impl DatabaseVersionProjection { - pub fn new() -> Self { - let mut projection = Self { fields: Vec::new() }; + fn get_projection() -> Projection { + let mut projection = Projection::default(); projection.add_field("version", "{:db_version:}.version", "text"); projection.add_field( "application_type", @@ -110,10 +84,20 @@ impl DatabaseVersionProjection { } } +impl PartialOrd for DatabaseVersion { + fn partial_cmp(&self, other: &Self) -> Option { + if self.application_type != other.application_type { + None + } else { + self.version.partial_cmp(&other.version) + } + } +} + /// Provider for the [DatabaseVersion] entities using the `DatabaseVersionProjection`. pub struct DatabaseVersionProvider<'conn> { connection: &'conn Connection, - projection: DatabaseVersionProjection, + projection: Projection, } impl<'conn> DatabaseVersionProvider<'conn> { @@ -121,7 +105,7 @@ impl<'conn> DatabaseVersionProvider<'conn> { pub fn new(connection: &'conn Connection) -> Self { Self { connection, - projection: DatabaseVersionProjection::new(), + projection: DatabaseVersion::get_projection(), } } @@ -169,7 +153,7 @@ insert into db_version (application_type, version) values ('{application_type}', impl<'conn> Provider<'conn> for DatabaseVersionProvider<'conn> { type Entity = DatabaseVersion; - fn get_projection(&self) -> &dyn Projection { + fn get_projection(&self) -> &Projection { &self.projection } @@ -197,7 +181,7 @@ where {where_clause} /// This will perform an UPSERT and return the updated entity. pub struct DatabaseVersionUpdater<'conn> { connection: &'conn Connection, - projection: DatabaseVersionProjection, + projection: Projection, } impl<'conn> DatabaseVersionUpdater<'conn> { @@ -205,7 +189,7 @@ impl<'conn> DatabaseVersionUpdater<'conn> { pub fn new(connection: &'conn Connection) -> Self { Self { connection, - projection: DatabaseVersionProjection::new(), + projection: DatabaseVersion::get_projection(), } } @@ -227,7 +211,7 @@ impl<'conn> DatabaseVersionUpdater<'conn> { impl<'conn> Provider<'conn> for DatabaseVersionUpdater<'conn> { type Entity = DatabaseVersion; - fn get_projection(&self) -> &dyn Projection { + fn get_projection(&self) -> &Projection { &self.projection } @@ -257,7 +241,7 @@ mod tests { #[test] fn test_projection() { - let projection = DatabaseVersionProjection::new(); + let projection = DatabaseVersion::get_projection(); let mut aliases: HashMap = HashMap::new(); let _ = aliases.insert("{:db_version:}".to_string(), "whatever".to_string()); diff --git a/mithril-common/src/sqlite/entity.rs b/mithril-common/src/sqlite/entity.rs index df70586e19f..1ff644d31c8 100644 --- a/mithril-common/src/sqlite/entity.rs +++ b/mithril-common/src/sqlite/entity.rs @@ -1,6 +1,8 @@ use sqlite::Row; use thiserror::Error; +use super::Projection; + /// SqLite hydration error #[derive(Error, Debug, Clone)] pub enum HydrationError { @@ -25,4 +27,7 @@ pub trait SqLiteEntity { fn hydrate(row: Row) -> Result where Self: Sized; + + /// Construct a [Projection] that will allow to hydrate this `SqLiteEntity`. + fn get_projection() -> Projection; } diff --git a/mithril-common/src/sqlite/projection.rs b/mithril-common/src/sqlite/projection.rs index 71b68142ff5..e6ece002004 100644 --- a/mithril-common/src/sqlite/projection.rs +++ b/mithril-common/src/sqlite/projection.rs @@ -39,27 +39,34 @@ impl ProjectionField { /// Projection is a definition of field mapping during a query. /// Fields come from one or several source structures (can be tables, views or /// sub queries) and are mapped to a Provider query as output. -pub trait Projection { +pub struct Projection { + fields: Vec, +} + +impl Projection { + /// Instanciate a new Projection + pub fn new(fields: Vec) -> Self { + Self { fields } + } + /// Add a new field to the definition. This is one of the projection /// building tool to create a projection out of an existing structure. /// This is a blanket implementation. - fn add_field(&mut self, field_name: &str, definition: &str, output_type: &str) { - self.set_field(ProjectionField { + pub fn add_field(&mut self, field_name: &str, definition: &str, output_type: &str) { + self.fields.push(ProjectionField { name: field_name.to_string(), definition: definition.to_string(), output_type: output_type.to_string(), }) } - /// This method is requested by `add_field` to actually save the state in - /// the current Provider implementation. - fn set_field(&mut self, field: ProjectionField); - /// Returns the list of the ProjectionFields of this Projection. - fn get_fields(&self) -> &Vec; + pub fn get_fields(&self) -> &Vec { + &self.fields + } /// Turn the Projection into a string suitable for use in SQL queries. - fn expand(&self, aliases: HashMap) -> String { + pub fn expand(&self, aliases: HashMap) -> String { let mut fields: String = self .get_fields() .iter() @@ -75,40 +82,24 @@ pub trait Projection { } } +impl Default for Projection { + fn default() -> Self { + Self::new(Vec::new()) + } +} + #[cfg(test)] mod tests { use super::*; - struct TestProjection { - fields: Vec, - } - - impl TestProjection { - pub fn new() -> Self { - let mut projection = Self { fields: Vec::new() }; - - projection.add_field("test_id", "{:test:}.test_id", "integer"); - projection.add_field("name", "{:test:}.name", "text"); - projection.add_field("created_at", "{:test:}.created_at", "timestamp"); - projection.add_field("thing_count", "count({:thing:}.*)", "integer"); - - projection - } - } - - impl Projection for TestProjection { - fn get_fields(&self) -> &Vec { - &self.fields - } - - fn set_field(&mut self, field: ProjectionField) { - self.fields.push(field); - } - } - #[test] fn simple_projection() { - let projection = TestProjection::new(); + let mut projection = Projection::default(); + projection.add_field("test_id", "{:test:}.test_id", "integer"); + projection.add_field("name", "{:test:}.name", "text"); + projection.add_field("created_at", "{:test:}.created_at", "timestamp"); + projection.add_field("thing_count", "count({:thing:}.*)", "integer"); + let aliases: HashMap = [("{:test:}", "pika"), ("{:thing:}", "thing_alias")] .into_iter() .map(|(a, b)| (a.to_string(), b.to_string())) diff --git a/mithril-common/src/sqlite/provider.rs b/mithril-common/src/sqlite/provider.rs index 9f6442e5511..e00a8c9f249 100644 --- a/mithril-common/src/sqlite/provider.rs +++ b/mithril-common/src/sqlite/provider.rs @@ -14,7 +14,7 @@ pub trait Provider<'conn> { fn get_connection(&'conn self) -> &'conn Connection; /// Share the projection. - fn get_projection(&self) -> &dyn Projection; + fn get_projection(&self) -> &Projection; /// Perform the parametrized definition query. fn find( @@ -41,7 +41,7 @@ pub trait Provider<'conn> { #[cfg(test)] mod tests { - use super::super::{entity::HydrationError, ProjectionField, SqLiteEntity}; + use super::super::{entity::HydrationError, SqLiteEntity}; use super::*; #[derive(Debug, PartialEq)] @@ -61,25 +61,9 @@ mod tests { maybe_null: row.get::, _>(3), }) } - } - - pub struct TestProjection { - fields: Vec, - } - - impl Projection for TestProjection { - fn get_fields(&self) -> &Vec { - &self.fields - } - - fn set_field(&mut self, field: ProjectionField) { - self.fields.push(field); - } - } - impl TestProjection { - pub fn new() -> Self { - let mut projection = Self { fields: Vec::new() }; + fn get_projection() -> Projection { + let mut projection = Projection::default(); projection.add_field("text_data", "{:test:}.text_data", "text"); projection.add_field("real_data", "{:test:}.real_data", "real"); @@ -92,14 +76,14 @@ mod tests { struct TestEntityProvider { connection: Connection, - projection: TestProjection, + projection: Projection, } impl TestEntityProvider { pub fn new(connection: Connection) -> Self { Self { connection, - projection: TestProjection::new(), + projection: TestEntity::get_projection(), } } } @@ -111,7 +95,7 @@ mod tests { &self.connection } - fn get_projection(&self) -> &dyn Projection { + fn get_projection(&self) -> &Projection { &self.projection } @@ -128,14 +112,14 @@ mod tests { struct TestEntityUpdateProvider { connection: Connection, - projection: TestProjection, + projection: Projection, } impl TestEntityUpdateProvider { pub fn new(connection: Connection) -> Self { Self { connection, - projection: TestProjection::new(), + projection: TestEntity::get_projection(), } } } @@ -147,7 +131,7 @@ mod tests { &self.connection } - fn get_projection(&self) -> &dyn Projection { + fn get_projection(&self) -> &Projection { &self.projection }