diff --git a/cliff.toml b/cliff.toml index 6d8aad57e92b55555aab98348dafeaae71e6bc06..973276425f772745e3165724b641393be9d84f2a 100644 --- a/cliff.toml +++ b/cliff.toml @@ -50,6 +50,7 @@ commit_preprocessors = [ # regex for parsing and grouping commits commit_parsers = [ { message = "^[f,F]eat", group = "Features" }, + { message = "^feat(qol)|qol", group = "QoL Enhancements"}, { message = "^fix", group = "Bug Fixes" }, { message = "^doc", group = "Documentation" }, { message = "^perf", group = "Performance" }, diff --git a/src/query/mod.rs b/src/query/mod.rs index becd60d87b9265ae97714df272fde19266dec313..e5f157f63b6a26302bf95e37b1f1e8d477b02a19 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -7,6 +7,8 @@ use std::fmt::Display; use crate::seal::Sealed; +use self::ops::Operable; + pub mod fns; pub mod ops; @@ -267,6 +269,97 @@ impl Display for Scalar { impl Sealed for Scalar {} impl ops::Operable for Scalar {} +/// Raw Expression type that allows raw strings to be used in the builder API where necessary +/// +/// This also allows combining complex queries, especially when a function is generic over a single type +/// `T` that implements `Operable`, you can convert the existing type into this to make it work. +/// This can be done easily with the [`Expression`] trait which has a blanket implementation for all types +/// which implement [`Operable`] (all query builder API types). +/// +/// Note: This is generally not recommended unless it is necessary to use. This is because the query builder +/// API is lazy. It doesn't build the query until `to_string` or `into_query` methods are called. This avoids +/// many intermediate string allocations. The [`RawExpr`] type, however, is built with a `String`, which means +/// that when you turn `T: Operable` into [`RawExpr`], the `to_string` method is called early. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RawExpr(String); + +impl RawExpr { + /// Construct a raw expression type from a string + /// + /// See type level docs for additional details + fn new<T: ToOwned<Owned = String>>(query: T) -> Self { + RawExpr(query.to_owned()) + } + /// Construct a raw expression type from a type that that implements `Operable` + /// + /// See type level docs for additional details + fn from_expr<E: Operable>(expr: E) -> Self { + RawExpr(expr.to_string()) + } + /// Get the inner string + fn into_inner(self) -> String { + self.0 + } +} + +impl AsRef<str> for RawExpr { + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +impl From<&str> for RawExpr { + fn from(value: &str) -> Self { + RawExpr(value.to_owned()) + } +} + +impl From<String> for RawExpr { + fn from(value: String) -> Self { + RawExpr(value) + } +} + +/// Helper trait to convert a query builder API type into a [`RawExpr`] type +/// +/// This is useful when a function takes a sequence (array/vector/iterator) of a generic type `T: Operable` +/// but you want to combine different types (produced from query fns or other complex types). +/// +/// This trait has a blanket implementation for all `T` that implement `Display` which include types +/// that implement `Operable` +/// +/// While the `String` type could have been allowed to be part of the builder API directly, +/// it is by design that a user must explicitly turn a string into a [`RawExpr`] saying that +/// they're opting in to use a raw string in the query builder API. +pub trait Expression { + /// Build the query early and produce a [`RawExpr`] + /// + /// See the type's documentation for additional details. + fn to_expr(&self) -> RawExpr; +} + +impl<T: Display> Expression for T { + fn to_expr(&self) -> RawExpr { + RawExpr(self.to_string()) + } +} + +impl IntoQuery for RawExpr { + type Target = String; + fn into_query(self) -> Self::Target { + self.0 + } +} + +impl Operable for RawExpr {} +impl Sealed for RawExpr {} + +impl Display for RawExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + #[cfg(test)] mod tests { use super::*;