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::*;