Parcourir la source

Allow multiple uniqueness constraints by ignoring all but the first.

Kestrel il y a 2 mois
Parent
commit
bf3d8062a7
3 fichiers modifiés avec 58 ajouts et 1 suppressions
  1. 9 0
      microrm/src/query.rs
  2. 22 1
      microrm/src/query/components.rs
  3. 27 0
      microrm/tests/query_construct.rs

+ 9 - 0
microrm/src/query.rs

@@ -327,6 +327,9 @@ pub trait Queryable: Clone {
     /// A `'static`-version of `Self`, used for `TypeId`-based caching.
     type StaticVersion: Queryable + 'static;
 
+    /// True if the query result is guaranteed to be either unique or absent.
+    const IS_UNIQUE: bool;
+
     /// Construct a concrete SQL query from this abstract type representation.
     #[doc(hidden)]
     fn build(&self) -> Query;
@@ -578,6 +581,8 @@ impl<'a, T: Entity> Queryable for &'a IDMap<T> {
     type OutputContainer = Vec<Stored<T>>;
     type StaticVersion = &'static IDMap<T>;
 
+    const IS_UNIQUE: bool = false;
+
     fn build(&self) -> Query {
         Query::new()
             .attach(QueryPart::Root, "SELECT DISTINCT")
@@ -597,6 +602,8 @@ impl<'a, AI: RelationInterface> Queryable for &'a AI {
     type OutputContainer = Vec<Stored<AI::RemoteEntity>>;
     type StaticVersion = &'static AI::StaticVersion;
 
+    const IS_UNIQUE: bool = false;
+
     fn build(&self) -> Query {
         let anames = RelationNames::collect(*self).unwrap();
         let relation_name = anames.relation_name();
@@ -648,6 +655,8 @@ impl<'a, E: Entity, EPL: EntityPartList<Entity = E>> Queryable for &'a Index<E,
     type OutputContainer = Vec<Stored<E>>;
     type StaticVersion = &'static Index<E, EPL>;
 
+    const IS_UNIQUE: bool = false;
+
     fn build(&self) -> Query {
         Query::new()
             .attach(QueryPart::Root, "SELECT DISTINCT")

+ 22 - 1
microrm/src/query/components.rs

@@ -43,6 +43,8 @@ impl<E: Entity> Queryable for TableComponent<E> {
     type OutputContainer = Vec<Stored<E>>;
     type StaticVersion = Self;
 
+    const IS_UNIQUE: bool = false;
+
     fn build(&self) -> Query {
         Query::new()
             .attach(QueryPart::Root, "SELECT DISTINCT")
@@ -91,6 +93,8 @@ impl<WEP: EntityPart, Parent: Queryable + 'static> Queryable
     type OutputContainer = Option<Stored<WEP::Entity>>;
     type StaticVersion = Self;
 
+    const IS_UNIQUE: bool = Parent::IS_UNIQUE;
+
     fn build(&self) -> Query {
         unreachable!()
     }
@@ -112,6 +116,8 @@ impl<
     type OutputContainer = Parent::OutputContainer;
     type StaticVersion = CanonicalWithComponent<WEP, Parent::StaticVersion>;
 
+    const IS_UNIQUE: bool = Parent::IS_UNIQUE;
+
     fn build(&self) -> Query {
         self.parent.build().attach(
             QueryPart::Where,
@@ -195,6 +201,8 @@ impl<E: Entity, Parent: Queryable + 'static, EPL: EntityPartList<Entity = E>> Qu
     type OutputContainer = Option<Stored<E>>;
     type StaticVersion = Self;
 
+    const IS_UNIQUE: bool = Parent::IS_UNIQUE;
+
     fn build(&self) -> Query {
         unreachable!()
     }
@@ -227,6 +235,8 @@ impl<
     type OutputContainer = Option<Stored<E>>;
     type StaticVersion = CanonicalIndexComponent<E, Parent::StaticVersion, EPL>;
 
+    const IS_UNIQUE: bool = true;
+
     fn build(&self) -> Query {
         let mut query = self.parent.build();
 
@@ -285,8 +295,15 @@ impl<Parent: Queryable> Queryable for SingleComponent<Parent> {
     type OutputContainer = Option<Stored<Self::EntityOutput>>;
     type StaticVersion = SingleComponent<Parent::StaticVersion>;
 
+    const IS_UNIQUE: bool = true;
+
     fn build(&self) -> Query {
-        self.parent.build().attach(QueryPart::Trailing, "LIMIT 1")
+        // it's not a crime to ask a second time, but it's not valid SQL to repeat the LIMIT 1, either
+        if Parent::IS_UNIQUE {
+            self.parent.build()
+        } else {
+            self.parent.build().attach(QueryPart::Trailing, "LIMIT 1")
+        }
     }
 
     fn bind(&self, stmt: &mut StatementContext, index: &mut i32) {
@@ -339,6 +356,8 @@ impl<
     type OutputContainer = Vec<Stored<R>>;
     type StaticVersion = JoinComponent<R, L, EP, Parent::StaticVersion>;
 
+    const IS_UNIQUE: bool = Parent::IS_UNIQUE;
+
     fn build(&self) -> Query {
         let remote_name = R::entity_name();
         let local_name = L::entity_name();
@@ -444,6 +463,8 @@ impl<FE: Entity, EP: EntityPart, Parent: Queryable> Queryable for ForeignCompone
     type OutputContainer =
         <Parent::OutputContainer as OutputContainer<Parent::EntityOutput>>::ReplacedEntity<FE>;
 
+    const IS_UNIQUE: bool = Parent::IS_UNIQUE;
+
     fn build(&self) -> Query {
         let subquery = self.parent.build().replace(
             QueryPart::Columns,

+ 27 - 0
microrm/tests/query_construct.rs

@@ -0,0 +1,27 @@
+use microrm::prelude::*;
+use test_log::test;
+
+mod common;
+
+#[derive(Entity)]
+struct KV {
+    #[key]
+    name: String,
+    value: String,
+}
+
+#[derive(Database)]
+struct KVDB {
+    entries: microrm::IDMap<KV>,
+}
+
+#[test]
+fn repeated_uniqueness() {
+    let db = common::open_test_db::<KVDB>("repeated_uniqueness");
+
+    db.entries
+        .first()
+        .first()
+        .get()
+        .expect("couldn't execute query with multiple uniqueness constraints");
+}