From 55c8c6d26bb965966a45e97f4f735ec62378cf66 Mon Sep 17 00:00:00 2001 From: favilo Date: Thu, 28 Mar 2024 15:42:17 -0700 Subject: [PATCH 1/6] Adding meta queries to support combinations of queries as queries. - `AndQuery` - `OrQuery` - `NotQuery` - `AnyQuery` - `AllQuery` --- src/x/query.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/x/query.rs b/src/x/query.rs index 53d98539..83cef9d3 100644 --- a/src/x/query.rs +++ b/src/x/query.rs @@ -98,3 +98,67 @@ where } } } + +/// A meta [Query] for combining two queries with a logical AND. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct AndQuery(pub Q1, pub Q2); + +impl, Q2: Query> Query for AndQuery { + fn run(&self, id: Xid, x: &X) -> Result { + Ok(self.0.run(id, x)? && self.1.run(id, x)?) + } +} + +/// A meta [Query] for combining two queries with a logical OR. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct OrQuery(pub Q1, pub Q2); + +impl, Q2: Query> Query for OrQuery { + fn run(&self, id: Xid, x: &X) -> Result { + Ok(self.0.run(id, x)? || self.1.run(id, x)?) + } +} + +/// A meta [Query] for applying a logical NOT to a query. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct NotQuery(pub Q); + +impl> Query for NotQuery { + fn run(&self, id: Xid, x: &X) -> Result { + Ok(!self.0.run(id, x)?) + } +} + +/// A meta [Query] for combining multiple queries with a logical OR. +pub struct AnyQuery(pub Vec>>); + +impl fmt::Debug for AnyQuery { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AnyQuery").finish() + } +} + +impl Query for AnyQuery { + fn run(&self, id: Xid, x: &X) -> Result { + self.0 + .iter() + .try_fold(false, |acc, query| Ok(acc || query.run(id, x)?)) + } +} + +/// A meta [Query] for combining multiple queries with a logical AND. +pub struct AllQuery(pub Vec>>); + +impl fmt::Debug for AllQuery { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AllQuery").finish() + } +} + +impl Query for AllQuery { + fn run(&self, id: Xid, x: &X) -> Result { + self.0 + .iter() + .try_fold(true, |acc, query| Ok(acc && query.run(id, x)?)) + } +} From 525206e9509be84d5255659845f4608de2d506b2 Mon Sep 17 00:00:00 2001 From: favilo Date: Mon, 15 Apr 2024 13:28:04 -0700 Subject: [PATCH 2/6] Add ergonomic extension trait to simplify creating Meta queries --- src/x/query.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/x/query.rs b/src/x/query.rs index 83cef9d3..22431e54 100644 --- a/src/x/query.rs +++ b/src/x/query.rs @@ -162,3 +162,37 @@ impl Query for AllQuery { .try_fold(true, |acc, query| Ok(acc && query.run(id, x)?)) } } + +trait QueryExt: Query +where + X: XConn, +{ + fn and(self, other: impl Query) -> impl Query + where + Self: Sized, + { + AndQuery(self, other) + } + + fn or(self, other: impl Query) -> impl Query + where + Self: Sized, + { + OrQuery(self, other) + } + + fn not(self) -> impl Query + where + Self: Sized, + { + NotQuery(self) + } +} + +impl QueryExt for Q +where + X: XConn, + Q: Query, +{ +} + From a563e9a3454acb1848bb4345da5a17485ff0bcce Mon Sep 17 00:00:00 2001 From: favilo Date: Mon, 15 Apr 2024 13:36:08 -0700 Subject: [PATCH 3/6] Make return types more explicit --- src/x/query.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/x/query.rs b/src/x/query.rs index 22431e54..664a8161 100644 --- a/src/x/query.rs +++ b/src/x/query.rs @@ -167,21 +167,21 @@ trait QueryExt: Query where X: XConn, { - fn and(self, other: impl Query) -> impl Query + fn and(self, other: impl Query) -> AndQuery> where Self: Sized, { AndQuery(self, other) } - fn or(self, other: impl Query) -> impl Query + fn or(self, other: impl Query) -> OrQuery> where Self: Sized, { OrQuery(self, other) } - fn not(self) -> impl Query + fn not(self) -> NotQuery> where Self: Sized, { @@ -195,4 +195,3 @@ where Q: Query, { } - From 898b9a2f9ad532dd2f88c8036f44c62461ce9fdb Mon Sep 17 00:00:00 2001 From: favilo Date: Mon, 17 Jun 2024 13:00:22 -0700 Subject: [PATCH 4/6] Make the changes requested by @sminez --- src/x/query.rs | 129 +++++++++++++++++++++---------------------------- 1 file changed, 54 insertions(+), 75 deletions(-) diff --git a/src/x/query.rs b/src/x/query.rs index 664a8161..ad06da59 100644 --- a/src/x/query.rs +++ b/src/x/query.rs @@ -10,6 +10,41 @@ use std::fmt; pub trait Query { /// Run this query for a given window ID. fn run(&self, id: Xid, x: &X) -> Result; + + /// Combine this query with another query using a logical AND. + fn and>(self, other: Other) -> AndQuery + where + Self: Sized, + { + AndQuery { + first: self, + second: other, + _phantom: std::marker::PhantomData, + } + } + + /// Combine this query with another query using a logical OR. + fn or>(self, other: Other) -> OrQuery + where + Self: Sized, + { + OrQuery { + first: self, + second: other, + _phantom: std::marker::PhantomData, + } + } + + /// Apply a logical NOT to this query. + fn not(self) -> NotQuery + where + Self: Sized, + { + NotQuery { + inner: self, + _phantom: std::marker::PhantomData, + } + } } impl fmt::Debug for Box> { @@ -101,97 +136,41 @@ where /// A meta [Query] for combining two queries with a logical AND. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct AndQuery(pub Q1, pub Q2); +pub struct AndQuery { + first: Q1, + second: Q2, + _phantom: std::marker::PhantomData, +} -impl, Q2: Query> Query for AndQuery { +impl, Q2: Query> Query for AndQuery { fn run(&self, id: Xid, x: &X) -> Result { - Ok(self.0.run(id, x)? && self.1.run(id, x)?) + Ok(self.first.run(id, x)? && self.second.run(id, x)?) } } /// A meta [Query] for combining two queries with a logical OR. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct OrQuery(pub Q1, pub Q2); +pub struct OrQuery { + first: Q1, + second: Q2, + _phantom: std::marker::PhantomData, +} -impl, Q2: Query> Query for OrQuery { +impl, Q2: Query> Query for OrQuery { fn run(&self, id: Xid, x: &X) -> Result { - Ok(self.0.run(id, x)? || self.1.run(id, x)?) + Ok(self.first.run(id, x)? || self.second.run(id, x)?) } } /// A meta [Query] for applying a logical NOT to a query. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct NotQuery(pub Q); - -impl> Query for NotQuery { - fn run(&self, id: Xid, x: &X) -> Result { - Ok(!self.0.run(id, x)?) - } +pub struct NotQuery { + inner: Q, + _phantom: std::marker::PhantomData, } -/// A meta [Query] for combining multiple queries with a logical OR. -pub struct AnyQuery(pub Vec>>); - -impl fmt::Debug for AnyQuery { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("AnyQuery").finish() - } -} - -impl Query for AnyQuery { +impl> Query for NotQuery { fn run(&self, id: Xid, x: &X) -> Result { - self.0 - .iter() - .try_fold(false, |acc, query| Ok(acc || query.run(id, x)?)) + Ok(!self.inner.run(id, x)?) } } - -/// A meta [Query] for combining multiple queries with a logical AND. -pub struct AllQuery(pub Vec>>); - -impl fmt::Debug for AllQuery { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("AllQuery").finish() - } -} - -impl Query for AllQuery { - fn run(&self, id: Xid, x: &X) -> Result { - self.0 - .iter() - .try_fold(true, |acc, query| Ok(acc && query.run(id, x)?)) - } -} - -trait QueryExt: Query -where - X: XConn, -{ - fn and(self, other: impl Query) -> AndQuery> - where - Self: Sized, - { - AndQuery(self, other) - } - - fn or(self, other: impl Query) -> OrQuery> - where - Self: Sized, - { - OrQuery(self, other) - } - - fn not(self) -> NotQuery> - where - Self: Sized, - { - NotQuery(self) - } -} - -impl QueryExt for Q -where - X: XConn, - Q: Query, -{ -} From 4a9f16a4ab87c5c6cf4bdd83d53863a826333bc0 Mon Sep 17 00:00:00 2001 From: favilo Date: Tue, 18 Jun 2024 16:41:37 -0700 Subject: [PATCH 5/6] Add dynamic dispatch to combinator internal types This makes the public interface more simple. The `'static` bound is not terribly onerous, since we are not passing a reference to `self`, we are consuming self, and returning a new thing. Since owned objects are by definition `'static`, this shouldn't matter. --- src/x/query.rs | 58 ++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/src/x/query.rs b/src/x/query.rs index ad06da59..74b5b925 100644 --- a/src/x/query.rs +++ b/src/x/query.rs @@ -12,36 +12,44 @@ pub trait Query { fn run(&self, id: Xid, x: &X) -> Result; /// Combine this query with another query using a logical AND. - fn and>(self, other: Other) -> AndQuery + /// + /// NOTE: This follows typical short-circuiting behavior, i.e. if the first query + /// returns false, the second query will not be run. + fn and(self, other: Other) -> AndQuery where - Self: Sized, + Self: Sized + 'static, + Other: Query + 'static, { AndQuery { - first: self, - second: other, + first: Box::new(self), + second: Box::new(other), _phantom: std::marker::PhantomData, } } /// Combine this query with another query using a logical OR. - fn or>(self, other: Other) -> OrQuery + /// + /// NOTE: This follows typical short-circuiting behavior, i.e. if the first query + /// returns true, the second query will not be run. + fn or(self, other: Other) -> OrQuery where - Self: Sized, + Self: Sized + 'static, + Other: Query + 'static, { OrQuery { - first: self, - second: other, + first: Box::new(self), + second: Box::new(other), _phantom: std::marker::PhantomData, } } /// Apply a logical NOT to this query. - fn not(self) -> NotQuery + fn not(self) -> NotQuery where - Self: Sized, + Self: Sized + 'static, { NotQuery { - inner: self, + inner: Box::new(self), _phantom: std::marker::PhantomData, } } @@ -135,41 +143,41 @@ where } /// A meta [Query] for combining two queries with a logical AND. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct AndQuery { - first: Q1, - second: Q2, +#[derive(Debug)] +pub struct AndQuery { + first: Box>, + second: Box>, _phantom: std::marker::PhantomData, } -impl, Q2: Query> Query for AndQuery { +impl Query for AndQuery { fn run(&self, id: Xid, x: &X) -> Result { Ok(self.first.run(id, x)? && self.second.run(id, x)?) } } /// A meta [Query] for combining two queries with a logical OR. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct OrQuery { - first: Q1, - second: Q2, +#[derive(Debug)] +pub struct OrQuery { + first: Box>, + second: Box>, _phantom: std::marker::PhantomData, } -impl, Q2: Query> Query for OrQuery { +impl Query for OrQuery { fn run(&self, id: Xid, x: &X) -> Result { Ok(self.first.run(id, x)? || self.second.run(id, x)?) } } /// A meta [Query] for applying a logical NOT to a query. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct NotQuery { - inner: Q, +#[derive(Debug)] +pub struct NotQuery { + inner: Box>, _phantom: std::marker::PhantomData, } -impl> Query for NotQuery { +impl Query for NotQuery { fn run(&self, id: Xid, x: &X) -> Result { Ok(!self.inner.run(id, x)?) } From cce60121e6a2e3edd54809e70db0d39cddf19413 Mon Sep 17 00:00:00 2001 From: favilo Date: Tue, 18 Jun 2024 16:46:34 -0700 Subject: [PATCH 6/6] Clean up doc comments --- src/x/query.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/x/query.rs b/src/x/query.rs index 74b5b925..c7cc6d17 100644 --- a/src/x/query.rs +++ b/src/x/query.rs @@ -13,7 +13,7 @@ pub trait Query { /// Combine this query with another query using a logical AND. /// - /// NOTE: This follows typical short-circuiting behavior, i.e. if the first query + /// This follows typical short-circuiting behavior, i.e. if the first query /// returns false, the second query will not be run. fn and(self, other: Other) -> AndQuery where @@ -29,7 +29,7 @@ pub trait Query { /// Combine this query with another query using a logical OR. /// - /// NOTE: This follows typical short-circuiting behavior, i.e. if the first query + /// This follows typical short-circuiting behavior, i.e. if the first query /// returns true, the second query will not be run. fn or(self, other: Other) -> OrQuery where @@ -44,6 +44,8 @@ pub trait Query { } /// Apply a logical NOT to this query. + /// + /// This will invert the result of the query. fn not(self) -> NotQuery where Self: Sized + 'static,