Repository: Brendonovich/prisma-client-rust (language: rust)

Issue:
Traits for making extensible queries
Traits like `Where`, `Order` etc for making wrapper functions that apply preset settings.


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 7aeb1c9c..30ed79fe 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,7 +25,7 @@ rspc = ["dep:rspc"]
 sqlite-create-many = ["psl/sqlite-create-many"]
 migrations = ["migration-core", "include_dir", "tempdir", "tokio/fs", "tracing"]
 mocking = ["tokio"]
-mutation-callbacks = []
+# mutation-callbacks = []
 
 mysql = ["query-core/mysql", "migration-core/mysql"]
 sqlite = ["query-core/sqlite", "migration-core/sqlite"]
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index b29438a9..552e1c24 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -10,7 +10,7 @@ default = ["mysql", "sqlite", "mssql", "postgresql", "mongodb"]
 rspc = []
 migrations = []
 sqlite-create-many = ["prisma-client-rust-sdk/sqlite-create-many"]
-mutation-callbacks = []
+# mutation-callbacks = []
 mocking = []
 
 mysql = ["prisma-client-rust-sdk/mysql"]
diff --git a/cli/src/generator/client.rs b/cli/src/generator/client.rs
index 68dce121..bb94387b 100644
--- a/cli/src/generator/client.rs
+++ b/cli/src/generator/client.rs
@@ -55,8 +55,8 @@ pub fn generate(args: &GenerateArgs) -> TokenStream {
 
     let mock_ctor = cfg!(feature = "mocking").then(|| {
         quote! {
-            pub async fn _mock() -> (Self, #pcr::MockStore) {
-                let (internals, store) = #pcr::PrismaClientInternals::new_mock(#pcr::ActionNotifier::new()).await;
+            pub fn _mock() -> (Self, #pcr::MockStore) {
+                let (internals, store) = #pcr::PrismaClientInternals::new_mock(#pcr::ActionNotifier::new());
 
                 (Self(internals), store)
             }
@@ -111,7 +111,7 @@ pub fn generate(args: &GenerateArgs) -> TokenStream {
 
             #mock_ctor
 
-            pub fn _query_raw<T: serde::de::DeserializeOwned>(&self, query: #pcr::Raw) -> #pcr::QueryRaw<T> {
+            pub fn _query_raw<T: #pcr::Data>(&self, query: #pcr::Raw) -> #pcr::QueryRaw<T> {
                 #pcr::QueryRaw::new(
                     &self.0,
                     query,
@@ -127,7 +127,7 @@ pub fn generate(args: &GenerateArgs) -> TokenStream {
                 )
             }
 
-            pub async fn _batch<T: #pcr::BatchContainer<Marker>, Marker>(&self, queries: T) -> #pcr::Result<T::ReturnType> {
+            pub async fn _batch<'a, T: #pcr::BatchContainer<'a, Marker>, Marker>(&self, queries: T) -> #pcr::Result<<T as #pcr::BatchContainer<'a, Marker>>::ReturnType> {
                 #pcr::batch(queries, &self.0).await
             }
 
diff --git a/cli/src/generator/models/include_select.rs b/cli/src/generator/models/include_select.rs
index 91234b14..4ea51595 100644
--- a/cli/src/generator/models/include_select.rs
+++ b/cli/src/generator/models/include_select.rs
@@ -421,7 +421,7 @@ fn model_macro<'a>(
     let selection_struct = quote! {
         pub struct Selection(Vec<::prisma_client_rust::Selection>);
 
-        impl ::prisma_client_rust::#variant_ident::#selection_type for Selection {
+        impl ::prisma_client_rust::#selection_type for Selection {
             type Data = Data;
             type ModelData = $crate::#module_path::#model_name_snake::Data;
             
diff --git a/cli/src/generator/models/mod.rs b/cli/src/generator/models/mod.rs
index 00675911..21ca1751 100644
--- a/cli/src/generator/models/mod.rs
+++ b/cli/src/generator/models/mod.rs
@@ -291,17 +291,17 @@ pub fn generate(args: &GenerateArgs, module_path: TokenStream) -> Vec<TokenStrea
                 pub type UniqueArgs = ::prisma_client_rust::UniqueArgs<Actions<'static>>;
                 pub type ManyArgs = ::prisma_client_rust::ManyArgs<Actions<'static>>;
                 
-                pub type Count<'a> = ::prisma_client_rust::Count<'a, Actions<'static>>;
-                pub type Create<'a> = ::prisma_client_rust::Create<'a, Actions<'static>>;
-                pub type CreateMany<'a> = ::prisma_client_rust::CreateMany<'a, Actions<'static>>;
-                pub type FindUnique<'a> = ::prisma_client_rust::FindUnique<'a, Actions<'static>>;
-                pub type FindMany<'a> = ::prisma_client_rust::FindMany<'a, Actions<'static>>;
-                pub type FindFirst<'a> = ::prisma_client_rust::FindFirst<'a, Actions<'static>>;
-                pub type Update<'a> = ::prisma_client_rust::Update<'a, Actions<'static>>;
-                pub type UpdateMany<'a> = ::prisma_client_rust::UpdateMany<'a, Actions<'static>>;
-                pub type Upsert<'a> = ::prisma_client_rust::Upsert<'a, Actions<'static>>;
-                pub type Delete<'a> = ::prisma_client_rust::Delete<'a, Actions<'static>>;
-                pub type DeleteMany<'a> = ::prisma_client_rust::DeleteMany<'a, Actions<'static>>;
+                pub type Count<'a> = ::prisma_client_rust::Count<'a, Actions<'a>>;
+                pub type Create<'a> = ::prisma_client_rust::Create<'a, Actions<'a>>;
+                pub type CreateMany<'a> = ::prisma_client_rust::CreateMany<'a, Actions<'a>>;
+                pub type FindUnique<'a> = ::prisma_client_rust::FindUnique<'a, Actions<'a>>;
+                pub type FindMany<'a> = ::prisma_client_rust::FindMany<'a, Actions<'a>>;
+                pub type FindFirst<'a> = ::prisma_client_rust::FindFirst<'a, Actions<'a>>;
+                pub type Update<'a> = ::prisma_client_rust::Update<'a, Actions<'a>>;
+                pub type UpdateMany<'a> = ::prisma_client_rust::UpdateMany<'a, Actions<'a>>;
+                pub type Upsert<'a> = ::prisma_client_rust::Upsert<'a, Actions<'a>>;
+                pub type Delete<'a> = ::prisma_client_rust::Delete<'a, Actions<'a>>;
+                pub type DeleteMany<'a> = ::prisma_client_rust::DeleteMany<'a, Actions<'a>>;
               
                 #actions_struct
             }
diff --git a/prisma-cli/Cargo.toml b/prisma-cli/Cargo.toml
index c770470c..8a350192 100644
--- a/prisma-cli/Cargo.toml
+++ b/prisma-cli/Cargo.toml
@@ -11,6 +11,6 @@ prisma-client-rust-cli = { features = [
   "sqlite-create-many",
   "sqlite",
   "migrations",
-  "mutation-callbacks",
+  # "mutation-callbacks",
   "mocking"
 ], default_features = false, path = "../cli" }
diff --git a/src/actions.rs b/src/actions.rs
index 006446ac..d9776399 100644
--- a/src/actions.rs
+++ b/src/actions.rs
@@ -1,106 +1,12 @@
-use prisma_models::PrismaValue;
-use query_core::{Selection, SelectionArgument};
-use serde::de::DeserializeOwned;
-
-use crate::SerializedWhereInput;
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum ModelQueryType {
-    FindUnique,
-    FindFirst,
-    FindMany,
-    Count,
-}
-
-impl ModelQueryType {
-    pub fn name(&self) -> &'static str {
-        match self {
-            Self::FindUnique => "findUnique",
-            Self::FindFirst => "findFirst",
-            Self::FindMany => "findMany",
-            Self::Count => "aggregate",
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum ModelMutationType {
-    Create,
-    CreateMany,
-    Update,
-    UpdateMany,
-    Delete,
-    DeleteMany,
-    Upsert,
-}
-
-impl ModelMutationType {
-    pub fn name(&self) -> &'static str {
-        match self {
-            Self::Create => "createOne",
-            Self::CreateMany => "createMany",
-            Self::Update => "updateOne",
-            Self::UpdateMany => "updateMany",
-            Self::Delete => "deleteOne",
-            Self::DeleteMany => "deleteMany",
-            Self::Upsert => "upsertOne",
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum ModelActionType {
-    Query(ModelQueryType),
-    Mutation(ModelMutationType),
-}
-
-impl ModelActionType {
-    pub fn name(&self) -> &'static str {
-        match self {
-            Self::Query(q) => q.name(),
-            Self::Mutation(q) => q.name(),
-        }
-    }
-}
-
-pub trait ModelActions {
-    type Data: DeserializeOwned;
-    type Where: WhereInput;
-    type Set: Into<(String, PrismaValue)>;
-    type With: Into<Selection>;
-    type OrderBy: Into<(String, PrismaValue)>;
-    type Cursor: Into<Self::Where>;
-
-    const MODEL: &'static str;
-
-    fn scalar_selections() -> Vec<Selection>;
-}
+use crate::{ModelWriteOperation, SerializedWhereInput};
 
 pub trait WhereInput {
     fn serialize(self) -> SerializedWhereInput;
 }
 
-pub trait ModelAction {
-    type Actions: ModelActions;
-
-    const TYPE: ModelActionType;
-
-    fn base_selection(
-        arguments: impl IntoIterator<Item = SelectionArgument>,
-        nested_selections: impl IntoIterator<Item = Selection>,
-    ) -> Selection {
-        Selection::new(
-            format!("{}{}", Self::TYPE.name(), Self::Actions::MODEL),
-            Some("result".to_string()),
-            arguments.into_iter().collect::<Vec<_>>(),
-            nested_selections.into_iter().collect::<Vec<_>>(),
-        )
-    }
-}
-
 #[derive(Debug, PartialEq, Eq)]
 pub struct ModelMutationCallbackData {
-    pub action: ModelMutationType,
+    pub action: ModelWriteOperation,
     pub model: &'static str,
 }
 
diff --git a/src/client.rs b/src/client.rs
index 46f172ec..77b928f7 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -1,15 +1,13 @@
-use std::sync::Arc;
-
+use crate::queries::ModelActions;
+use crate::{ActionNotifier, ModelQuery};
 use diagnostics::Diagnostics;
 use query_core::{schema_builder, CoreError, Operation};
 use schema::QuerySchema;
 use serde::de::{DeserializeOwned, IntoDeserializer};
+use std::sync::Arc;
 use thiserror::Error;
 
-use crate::{
-    prisma_value, ActionNotifier, MockStore, ModelAction, ModelActionType, ModelActions,
-    ModelMutationCallbackData, QueryError, Result,
-};
+use crate::{prisma_value, ModelMutationCallbackData, ModelOperation, QueryError, Result};
 
 pub type Executor = Box<dyn query_core::QueryExecutor + Send + Sync + 'static>;
 
@@ -19,7 +17,8 @@ pub(crate) enum ExecutionEngine {
         query_schema: Arc<QuerySchema>,
         url: String,
     },
-    Mock(MockStore),
+    #[cfg(feature = "mocking")]
+    Mock(crate::MockStore),
 }
 
 impl ExecutionEngine {
@@ -39,6 +38,7 @@ impl ExecutionEngine {
 
                 Ok(serde_value::to_value(data)?)
             }
+            #[cfg(feature = "mocking")]
             Self::Mock(store) => Ok(store.get_op(&op).await.expect("Mock data not found")),
         }
     }
@@ -70,6 +70,7 @@ impl ExecutionEngine {
                     })
                     .collect())
             }
+            #[cfg(feature = "mocking")]
             Self::Mock(store) => {
                 let mut ret = vec![];
 
@@ -100,12 +101,12 @@ impl PrismaClientInternals {
         Ok(ret)
     }
 
-    pub fn notify_model_mutation<Action>(&self)
+    pub fn notify_model_mutation<'a, Action>(&self)
     where
-        Action: ModelAction,
+        Action: ModelQuery<'a>,
     {
         match Action::TYPE {
-            ModelActionType::Mutation(action) => {
+            ModelOperation::Write(action) => {
                 for callback in &self.action_notifier.model_mutation_callbacks {
                     (callback)(ModelMutationCallbackData {
                         model: Action::Actions::MODEL,
@@ -113,7 +114,7 @@ impl PrismaClientInternals {
                     })
                 }
             }
-            ModelActionType::Query(_) => {
+            ModelOperation::Read(_) => {
                 println!("notify_model_mutation only acceps mutations, not queries!")
             }
         }
@@ -174,8 +175,9 @@ impl PrismaClientInternals {
         })
     }
 
-    pub async fn new_mock(action_notifier: ActionNotifier) -> (Self, MockStore) {
-        let mock_store = MockStore::new();
+    #[cfg(feature = "mocking")]
+    pub fn new_mock(action_notifier: ActionNotifier) -> (Self, crate::MockStore) {
+        let mock_store = crate::MockStore::new();
 
         (
             Self {
@@ -188,6 +190,7 @@ impl PrismaClientInternals {
 
     pub fn url(&self) -> &str {
         match &self.engine {
+            #[cfg(feature = "mocking")]
             ExecutionEngine::Mock(_) => "mock",
             ExecutionEngine::Real { url, .. } => url,
         }
diff --git a/src/lib.rs b/src/lib.rs
index 706e7f6f..d9ce62fd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,6 +2,7 @@ pub mod actions;
 mod client;
 #[cfg(feature = "migrations")]
 pub mod migrations;
+#[cfg(feature = "mocking")]
 mod mock;
 pub mod operator;
 mod prisma_value;
@@ -30,6 +31,7 @@ pub use user_facing_errors as prisma_errors;
 
 pub use actions::*;
 pub use client::*;
+#[cfg(feature = "mocking")]
 pub use mock::*;
 pub use operator::Operator;
 pub use queries::*;
diff --git a/src/mock.rs b/src/mock.rs
index 18c37ba3..db7a32a3 100644
--- a/src/mock.rs
+++ b/src/mock.rs
@@ -6,7 +6,7 @@ use serde::Serialize;
 use serde_value::Value;
 use tokio::sync::Mutex;
 
-use crate::BatchQuery;
+use crate::Query;
 
 #[derive(Default, Clone)]
 pub struct MockStore {
@@ -29,11 +29,11 @@ impl MockStore {
         mutex.lock().await.push((sel, expected))
     }
 
-    pub async fn expect<Q: BatchQuery>(&self, query: Q, expected: Q::RawType)
+    pub async fn expect<'a, Q: Query<'a>>(&self, query: Q, expected: Q::RawType)
     where
-        <Q as BatchQuery>::RawType: Serialize,
+        <Q as Query<'a>>::RawType: Serialize,
     {
-        self.add_op(query.graphql(), serde_value::to_value(expected).unwrap())
+        self.add_op(query.graphql().0, serde_value::to_value(expected).unwrap())
             .await;
     }
 
diff --git a/src/queries/batch.rs b/src/queries/batch.rs
index 20443c15..60a2d974 100644
--- a/src/queries/batch.rs
+++ b/src/queries/batch.rs
@@ -6,12 +6,12 @@ use serde::{
     Deserialize,
 };
 
-use crate::PrismaClientInternals;
+use crate::{PrismaClientInternals, Query};
 
-pub async fn batch<T: BatchContainer<Marker>, Marker>(
+pub async fn batch<'a, T: BatchContainer<'a, Marker>, Marker>(
     container: T,
     client: &PrismaClientInternals,
-) -> super::Result<T::ReturnType> {
+) -> super::Result<<T as BatchContainer<'a, Marker>>::ReturnType> {
     let data = client
         .engine
         .execute_all(container.graphql())
@@ -23,20 +23,8 @@ pub async fn batch<T: BatchContainer<Marker>, Marker>(
     Ok(T::convert(data))
 }
 
-/// A query that can be used within a transaction
-pub trait BatchQuery {
-    type RawType: DeserializeOwned;
-    type ReturnType;
-
-    fn graphql(self) -> Operation;
-
-    /// Function for converting between raw database data and the type expected by the user.
-    /// Necessary for things like raw queries
-    fn convert(raw: Self::RawType) -> Self::ReturnType;
-}
-
 /// A container that can hold queries to batch into a transaction
-pub trait BatchContainer<Marker> {
+pub trait BatchContainer<'a, Marker> {
     type RawType: DeserializeOwned;
     type ReturnType;
 
@@ -44,12 +32,15 @@ pub trait BatchContainer<Marker> {
     fn convert(raw: VecDeque<Self::RawType>) -> Self::ReturnType;
 }
 
-impl<T: BatchQuery, I: IntoIterator<Item = T>> BatchContainer<()> for I {
+impl<'a, T: Query<'a>, I: IntoIterator<Item = T>> BatchContainer<'a, ()> for I {
     type RawType = T::RawType;
     type ReturnType = Vec<T::ReturnType>;
 
     fn graphql(self) -> Vec<Operation> {
-        self.into_iter().map(BatchQuery::graphql).collect()
+        self.into_iter()
+            .map(Query::graphql)
+            .map(|(o, _)| o)
+            .collect()
     }
 
     fn convert(raw: VecDeque<Self::RawType>) -> Self::ReturnType {
@@ -62,14 +53,14 @@ pub enum TupleMarker {}
 macro_rules! impl_tuple {
     ($($generic:ident),+) => {
         #[allow(warnings)]
-        impl<$($generic: BatchQuery),+> BatchContainer<TupleMarker> for ($($generic),+) {
+        impl<'a, $($generic: Query<'a>),+> BatchContainer<'a, TupleMarker> for ($($generic),+) {
             type RawType = serde_json::Value;
             type ReturnType = ($($generic::ReturnType),+);
 
             fn graphql(self) -> Vec<$crate::query_core::Operation> {
                 let ($($generic),+) = self;
 
-                vec![$($generic.graphql()),+]
+                vec![$($generic.graphql().0),+]
             }
 
             fn convert(mut raw: VecDeque<Self::RawType>) -> Self::ReturnType {
diff --git a/src/queries/count.rs b/src/queries/count.rs
index 0a4abce6..facbf498 100644
--- a/src/queries/count.rs
+++ b/src/queries/count.rs
@@ -3,14 +3,11 @@ use query_core::{Operation, Selection};
 use serde::Deserialize;
 
 use crate::{
-    merge_fields, BatchQuery, ModelAction, ModelActionType, ModelActions, ModelQueryType,
-    PrismaClientInternals, SerializedWhereInput, WhereInput,
+    merge_fields, ModelActions, ModelOperation, ModelQuery, ModelReadOperation, OrderByQuery,
+    PaginatedQuery, PrismaClientInternals, Query, SerializedWhereInput, WhereInput, WhereQuery,
 };
 
-pub struct Count<'a, Actions>
-where
-    Actions: ModelActions,
-{
+pub struct Count<'a, Actions: ModelActions> {
     client: &'a PrismaClientInternals,
     pub where_params: Vec<Actions::Where>,
     pub order_by_params: Vec<Actions::OrderBy>,
@@ -19,19 +16,7 @@ where
     pub take: Option<i64>,
 }
 
-impl<'a, Actions> ModelAction for Count<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type Actions = Actions;
-
-    const TYPE: ModelActionType = ModelActionType::Query(ModelQueryType::Count);
-}
-
-impl<'a, Actions> Count<'a, Actions>
-where
-    Actions: ModelActions,
-{
+impl<'a, Actions: ModelActions> Count<'a, Actions> {
     pub fn new(client: &'a PrismaClientInternals, where_params: Vec<Actions::Where>) -> Self {
         Self {
             client,
@@ -63,7 +48,26 @@ where
         self
     }
 
-    pub(crate) fn exec_operation(self) -> (Operation, &'a PrismaClientInternals) {
+    pub async fn exec(self) -> super::Result<i64> {
+        super::exec(self).await
+    }
+}
+
+#[derive(Deserialize)]
+pub struct CountAggregateResult {
+    _count: CountResult,
+}
+
+#[derive(Deserialize)]
+pub struct CountResult {
+    _all: i64,
+}
+
+impl<'a, Actions: ModelActions> Query<'a> for Count<'a, Actions> {
+    type RawType = CountAggregateResult;
+    type ReturnType = i64;
+
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
         (
             Operation::Read(Self::base_selection(
                 [
@@ -125,39 +129,39 @@ where
         )
     }
 
-    pub(crate) fn convert(data: CountAggregateResult) -> i64 {
-        data._count._all
+    fn convert(raw: Self::RawType) -> Self::ReturnType {
+        raw._count._all
     }
+}
 
-    pub async fn exec(self) -> super::Result<i64> {
-        let (op, client) = self.exec_operation();
+impl<'a, Actions: ModelActions> ModelQuery<'a> for Count<'a, Actions> {
+    type Actions = Actions;
 
-        client.execute(op).await.map(Self::convert)
-    }
+    const TYPE: ModelOperation = ModelOperation::Read(ModelReadOperation::Count);
 }
 
-#[derive(Deserialize)]
-pub struct CountAggregateResult {
-    _count: CountResult,
+impl<'a, Actions: ModelActions> WhereQuery<'a> for Count<'a, Actions> {
+    fn add_where(&mut self, param: Actions::Where) {
+        self.where_params.push(param);
+    }
 }
 
-#[derive(Deserialize)]
-pub struct CountResult {
-    _all: i64,
+impl<'a, Actions: ModelActions> OrderByQuery<'a> for Count<'a, Actions> {
+    fn add_order_by(&mut self, param: Actions::OrderBy) {
+        self.order_by_params.push(param);
+    }
 }
 
-impl<'a, Actions> BatchQuery for Count<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type RawType = CountAggregateResult;
-    type ReturnType = i64;
+impl<'a, Actions: ModelActions> PaginatedQuery<'a> for Count<'a, Actions> {
+    fn add_cursor(&mut self, param: Actions::Cursor) {
+        self.cursor_params.push(param);
+    }
 
-    fn graphql(self) -> Operation {
-        self.exec_operation().0
+    fn set_skip(&mut self, skip: i64) {
+        self.skip = Some(skip);
     }
 
-    fn convert(raw: Self::RawType) -> Self::ReturnType {
-        Self::convert(raw)
+    fn set_take(&mut self, take: i64) {
+        self.take = Some(take);
     }
 }
diff --git a/src/queries/create.rs b/src/queries/create.rs
index d6454e82..39422a64 100644
--- a/src/queries/create.rs
+++ b/src/queries/create.rs
@@ -2,35 +2,17 @@ use prisma_models::PrismaValue;
 use query_core::{Operation, Selection};
 
 use crate::{
-    include::{Include, IncludeType},
-    merge_fields,
-    select::{Select, SelectType},
-    BatchQuery, ModelAction, ModelActionType, ModelActions, ModelMutationType,
-    PrismaClientInternals,
+    merge_fields, Include, IncludeType, ModelActions, ModelOperation, ModelQuery,
+    ModelWriteOperation, PrismaClientInternals, Query, Select, SelectType, SetQuery, WithQuery,
 };
 
-pub struct Create<'a, Actions>
-where
-    Actions: ModelActions,
-{
+pub struct Create<'a, Actions: ModelActions> {
     client: &'a PrismaClientInternals,
     pub set_params: Vec<Actions::Set>,
     pub with_params: Vec<Actions::With>,
 }
 
-impl<'a, Actions> ModelAction for Create<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type Actions = Actions;
-
-    const TYPE: ModelActionType = ModelActionType::Mutation(ModelMutationType::Create);
-}
-
-impl<'a, Actions> Create<'a, Actions>
-where
-    Actions: ModelActions,
-{
+impl<'a, Actions: ModelActions> Create<'a, Actions> {
     pub fn new(client: &'a PrismaClientInternals, set_params: Vec<Actions::Set>) -> Self {
         Self {
             client,
@@ -81,7 +63,16 @@ where
         )
     }
 
-    pub(crate) fn exec_operation(self) -> (Operation, &'a PrismaClientInternals) {
+    pub async fn exec(self) -> super::Result<Actions::Data> {
+        super::exec(self).await
+    }
+}
+
+impl<'a, Actions: ModelActions> Query<'a> for Create<'a, Actions> {
+    type RawType = Actions::Data;
+    type ReturnType = Self::RawType;
+
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
         let mut scalar_selections = Actions::scalar_selections();
 
         scalar_selections.extend(self.with_params.into_iter().map(Into::into));
@@ -92,30 +83,28 @@ where
         )
     }
 
-    pub async fn exec(self) -> super::Result<Actions::Data> {
-        let (op, client) = self.exec_operation();
-
-        let res = client.execute(op).await?;
-
-        #[cfg(feature = "mutation-callbacks")]
-        client.notify_model_mutation::<Self>();
-
-        Ok(res)
+    fn convert(raw: Self::RawType) -> Self::ReturnType {
+        raw
     }
 }
 
-impl<'a, Actions> BatchQuery for Create<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type RawType = Actions::Data;
-    type ReturnType = Self::RawType;
+impl<'a, Actions: ModelActions> ModelQuery<'a> for Create<'a, Actions> {
+    type Actions = Actions;
 
-    fn graphql(self) -> Operation {
-        self.exec_operation().0
+    const TYPE: ModelOperation = ModelOperation::Write(ModelWriteOperation::Create);
+}
+
+impl<'a, Actions: ModelActions> SetQuery<'a> for Create<'a, Actions> {
+    fn add_set(&mut self, param: Actions::Set) {
+        self.set_params.push(param);
     }
+}
 
-    fn convert(raw: Self::RawType) -> Self::ReturnType {
-        raw
+impl<'a, Actions: ModelActions> WithQuery<'a> for Create<'a, Actions> {
+    fn add_with(
+        &mut self,
+        param: impl Into<<<Self as ModelQuery<'a>>::Actions as ModelActions>::With>,
+    ) {
+        self.with_params.push(param.into());
     }
 }
diff --git a/src/queries/create_many.rs b/src/queries/create_many.rs
index d218c6c9..10783a16 100644
--- a/src/queries/create_many.rs
+++ b/src/queries/create_many.rs
@@ -1,33 +1,18 @@
 use prisma_models::PrismaValue;
-use query_core::{Operation, QueryValue, Selection};
+use query_core::{Operation, Selection};
 
 use crate::{
-    merge_fields, BatchQuery, BatchResult, ModelAction, ModelActionType, ModelActions,
-    ModelMutationType, PrismaClientInternals,
+    merge_fields, BatchResult, ModelActions, ModelOperation, ModelQuery, ModelWriteOperation,
+    PrismaClientInternals, Query,
 };
 
-pub struct CreateMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
+pub struct CreateMany<'a, Actions: ModelActions> {
     client: &'a PrismaClientInternals,
     pub set_params: Vec<Vec<Actions::Set>>,
     pub skip_duplicates: bool,
 }
 
-impl<'a, Actions> ModelAction for CreateMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type Actions = Actions;
-
-    const TYPE: ModelActionType = ModelActionType::Mutation(ModelMutationType::CreateMany);
-}
-
-impl<'a, Actions> CreateMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
+impl<'a, Actions: ModelActions> CreateMany<'a, Actions> {
     pub fn new(client: &'a PrismaClientInternals, set_params: Vec<Vec<Actions::Set>>) -> Self {
         Self {
             client,
@@ -66,14 +51,23 @@ where
                 #[cfg(not(any(feature = "mongodb", feature = "mssql")))]
                 (
                     "skipDuplicates".to_string(),
-                    QueryValue::Boolean(_skip_duplicates),
+                    PrismaValue::Boolean(_skip_duplicates).into(),
                 ),
             ],
             nested_selections,
         )
     }
 
-    pub(crate) fn exec_operation(self) -> (Operation, &'a PrismaClientInternals) {
+    pub async fn exec(self) -> super::Result<i64> {
+        super::exec(self).await
+    }
+}
+
+impl<'a, Actions: ModelActions> Query<'a> for CreateMany<'a, Actions> {
+    type RawType = BatchResult;
+    type ReturnType = i64;
+
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
         (
             Operation::Write(Self::to_selection(
                 self.set_params,
@@ -84,34 +78,13 @@ where
         )
     }
 
-    pub(crate) fn convert(raw: BatchResult) -> i64 {
+    fn convert(raw: Self::RawType) -> Self::ReturnType {
         raw.count
     }
-
-    pub async fn exec(self) -> super::Result<i64> {
-        let (op, client) = self.exec_operation();
-
-        let res = client.execute(op).await.map(Self::convert)?;
-
-        #[cfg(feature = "mutation-callbacks")]
-        client.notify_model_mutation::<Self>();
-
-        Ok(res)
-    }
 }
 
-impl<'a, Actions> BatchQuery for CreateMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type RawType = BatchResult;
-    type ReturnType = i64;
-
-    fn graphql(self) -> Operation {
-        self.exec_operation().0
-    }
+impl<'a, Actions: ModelActions> ModelQuery<'a> for CreateMany<'a, Actions> {
+    type Actions = Actions;
 
-    fn convert(raw: Self::RawType) -> Self::ReturnType {
-        Self::convert(raw)
-    }
+    const TYPE: ModelOperation = ModelOperation::Write(ModelWriteOperation::CreateMany);
 }
diff --git a/src/queries/delete.rs b/src/queries/delete.rs
index 5d18ed0a..6bb3301b 100644
--- a/src/queries/delete.rs
+++ b/src/queries/delete.rs
@@ -2,34 +2,17 @@ use prisma_models::PrismaValue;
 use query_core::{Operation, Selection};
 
 use crate::{
-    include::{Include, IncludeType},
-    select::{Select, SelectType},
-    BatchQuery, ModelAction, ModelActionType, ModelActions, ModelMutationType,
-    PrismaClientInternals, WhereInput,
+    Include, IncludeType, ModelActions, ModelOperation, ModelQuery, ModelWriteOperation,
+    PrismaClientInternals, Query, Select, SelectType, WhereInput, WithQuery,
 };
 
-pub struct Delete<'a, Actions>
-where
-    Actions: ModelActions,
-{
+pub struct Delete<'a, Actions: ModelActions> {
     client: &'a PrismaClientInternals,
     pub where_param: Actions::Where,
     pub with_params: Vec<Actions::With>,
 }
 
-impl<'a, Actions> ModelAction for Delete<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type Actions = Actions;
-
-    const TYPE: ModelActionType = ModelActionType::Mutation(ModelMutationType::Delete);
-}
-
-impl<'a, Actions> Delete<'a, Actions>
-where
-    Actions: ModelActions,
-{
+impl<'a, Actions: ModelActions> Delete<'a, Actions> {
     pub fn new(
         client: &'a PrismaClientInternals,
         where_param: Actions::Where,
@@ -80,7 +63,16 @@ where
         )
     }
 
-    pub(crate) fn exec_operation(self) -> (Operation, &'a PrismaClientInternals) {
+    pub async fn exec(self) -> super::Result<Actions::Data> {
+        super::exec(self).await
+    }
+}
+
+impl<'a, Actions: ModelActions> Query<'a> for Delete<'a, Actions> {
+    type RawType = Actions::Data;
+    type ReturnType = Actions::Data;
+
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
         let mut scalar_selections = Actions::scalar_selections();
 
         scalar_selections.extend(self.with_params.into_iter().map(Into::into));
@@ -91,30 +83,19 @@ where
         )
     }
 
-    pub async fn exec(self) -> super::Result<Actions::Data> {
-        let (op, client) = self.exec_operation();
-
-        let res = client.execute(op).await?;
-
-        #[cfg(feature = "mutation-callbacks")]
-        client.notify_model_mutation::<Self>();
-
-        Ok(res)
+    fn convert(raw: Self::RawType) -> Self::ReturnType {
+        raw
     }
 }
 
-impl<'a, Actions> BatchQuery for Delete<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type RawType = Actions::Data;
-    type ReturnType = Actions::Data;
+impl<'a, Actions: ModelActions> ModelQuery<'a> for Delete<'a, Actions> {
+    type Actions = Actions;
 
-    fn graphql(self) -> Operation {
-        self.exec_operation().0
-    }
+    const TYPE: ModelOperation = ModelOperation::Write(ModelWriteOperation::Delete);
+}
 
-    fn convert(raw: Self::RawType) -> Self::ReturnType {
-        raw
+impl<'a, Actions: ModelActions> WithQuery<'a> for Delete<'a, Actions> {
+    fn add_with(&mut self, param: impl Into<Actions::With>) {
+        self.with_params.push(param.into());
     }
 }
diff --git a/src/queries/delete_many.rs b/src/queries/delete_many.rs
index 127c3326..4a45939b 100644
--- a/src/queries/delete_many.rs
+++ b/src/queries/delete_many.rs
@@ -1,32 +1,17 @@
 use query_core::Operation;
 
 use crate::{
-    merge_fields, BatchQuery, BatchResult, ModelAction, ModelActionType, ModelActions,
-    ModelMutationType, PrismaClientInternals, WhereInput,
+    merge_fields, BatchResult, ModelActions, ModelOperation, ModelQuery, ModelWriteOperation,
+    PrismaClientInternals, Query, WhereInput, WhereQuery,
 };
 use prisma_models::PrismaValue;
 
-pub struct DeleteMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
+pub struct DeleteMany<'a, Actions: ModelActions> {
     client: &'a PrismaClientInternals,
     pub where_params: Vec<Actions::Where>,
 }
 
-impl<'a, Actions> ModelAction for DeleteMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type Actions = Actions;
-
-    const TYPE: ModelActionType = ModelActionType::Mutation(ModelMutationType::DeleteMany);
-}
-
-impl<'a, Actions> DeleteMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
+impl<'a, Actions: ModelActions> DeleteMany<'a, Actions> {
     pub fn new(client: &'a PrismaClientInternals, where_params: Vec<Actions::Where>) -> Self {
         Self {
             client,
@@ -34,7 +19,20 @@ where
         }
     }
 
-    pub(crate) fn exec_operation(self) -> (Operation, &'a PrismaClientInternals) {
+    pub(crate) fn convert(raw: BatchResult) -> i64 {
+        raw.count
+    }
+
+    pub async fn exec(self) -> super::Result<i64> {
+        super::exec(self).await
+    }
+}
+
+impl<'a, Actions: ModelActions> Query<'a> for DeleteMany<'a, Actions> {
+    type RawType = BatchResult;
+    type ReturnType = i64;
+
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
         (
             Operation::Write(Self::base_selection(
                 [(!self.where_params.is_empty()).then(|| {
@@ -58,34 +56,19 @@ where
         )
     }
 
-    pub(crate) fn convert(raw: BatchResult) -> i64 {
-        raw.count
-    }
-
-    pub async fn exec(self) -> super::Result<i64> {
-        let (op, client) = self.exec_operation();
-
-        let res = client.execute(op).await.map(Self::convert)?;
-
-        #[cfg(feature = "mutation-callbacks")]
-        client.notify_model_mutation::<Self>();
-
-        Ok(res)
+    fn convert(raw: Self::RawType) -> Self::ReturnType {
+        Self::convert(raw)
     }
 }
 
-impl<'a, Actions> BatchQuery for DeleteMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type RawType = BatchResult;
-    type ReturnType = i64;
+impl<'a, Actions: ModelActions> ModelQuery<'a> for DeleteMany<'a, Actions> {
+    type Actions = Actions;
 
-    fn graphql(self) -> Operation {
-        self.exec_operation().0
-    }
+    const TYPE: ModelOperation = ModelOperation::Write(ModelWriteOperation::DeleteMany);
+}
 
-    fn convert(raw: Self::RawType) -> Self::ReturnType {
-        Self::convert(raw)
+impl<'a, Actions: ModelActions> WhereQuery<'a> for DeleteMany<'a, Actions> {
+    fn add_where(&mut self, param: Actions::Where) {
+        self.where_params.push(param);
     }
 }
diff --git a/src/queries/error.rs b/src/queries/error.rs
new file mode 100644
index 00000000..d28c7b86
--- /dev/null
+++ b/src/queries/error.rs
@@ -0,0 +1,39 @@
+use thiserror::Error;
+use user_facing_errors::UserFacingError;
+
+#[derive(Debug, Error)]
+pub enum QueryError {
+    #[error("Error executing query: {} - {}", .0.as_known().map(|k| k.error_code.to_string()).unwrap_or("Unknown".to_string()), .0.message())]
+    Execute(user_facing_errors::Error),
+
+    #[error("Error serializing query result: {0}")]
+    Serialize(#[from] serde_value::SerializerError),
+
+    #[error("Error deserializing query result into return type: {0}")]
+    Deserialize(#[from] serde_value::DeserializerError),
+}
+
+impl QueryError {
+    pub fn is_prisma_error<T: UserFacingError>(&self) -> bool {
+        match self {
+            Self::Execute(error) => error
+                .as_known()
+                .map(|e| e.error_code == <T as UserFacingError>::ERROR_CODE)
+                .unwrap_or(false),
+            _ => false,
+        }
+    }
+}
+
+pub type Result<T> = std::result::Result<T, QueryError>;
+
+#[cfg(feature = "rspc")]
+impl From<QueryError> for rspc::Error {
+    fn from(err: QueryError) -> Self {
+        rspc::Error::with_cause(
+            rspc::ErrorCode::InternalServerError,
+            "Internal server error occurred while completing database operation!".into(),
+            err,
+        )
+    }
+}
diff --git a/src/queries/execute_raw.rs b/src/queries/execute_raw.rs
index 1e3bb495..baa210e9 100644
--- a/src/queries/execute_raw.rs
+++ b/src/queries/execute_raw.rs
@@ -2,7 +2,7 @@ use prisma_models::PrismaValue;
 use query_core::{Operation, Selection};
 use serde_json::Value;
 
-use crate::{raw::Raw, BatchQuery, PrismaClientInternals};
+use crate::{raw::Raw, PrismaClientInternals, Query};
 
 pub struct ExecuteRaw<'a> {
     client: &'a PrismaClientInternals,
@@ -21,7 +21,16 @@ impl<'a> ExecuteRaw<'a> {
         }
     }
 
-    pub(crate) fn exec_operation(self) -> (Operation, &'a PrismaClientInternals) {
+    pub async fn exec(self) -> super::Result<i64> {
+        super::exec(self).await
+    }
+}
+
+impl<'a> Query<'a> for ExecuteRaw<'a> {
+    type RawType = i64;
+    type ReturnType = Self::RawType;
+
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
         (
             Operation::Write(Selection::new(
                 "executeRaw".to_string(),
@@ -39,21 +48,6 @@ impl<'a> ExecuteRaw<'a> {
         )
     }
 
-    pub async fn exec(self) -> super::Result<i64> {
-        let (op, client) = self.exec_operation();
-
-        client.execute(op).await
-    }
-}
-
-impl<'a> BatchQuery for ExecuteRaw<'a> {
-    type RawType = i64;
-    type ReturnType = Self::RawType;
-
-    fn graphql(self) -> Operation {
-        self.exec_operation().0
-    }
-
     fn convert(raw: Self::RawType) -> Self::ReturnType {
         raw
     }
diff --git a/src/queries/find_first.rs b/src/queries/find_first.rs
index d1effc8d..b45b775f 100644
--- a/src/queries/find_first.rs
+++ b/src/queries/find_first.rs
@@ -2,19 +2,14 @@ use prisma_models::PrismaValue;
 use query_core::{Operation, QueryValue, Selection};
 
 use crate::{
-    include::{Include, IncludeType},
-    merge_fields,
-    select::{Select, SelectType},
-    BatchQuery, ModelAction, ModelActionType, ModelActions, ModelQueryType, PrismaClientInternals,
-    WhereInput,
+    merge_fields, Include, IncludeType, ModelActions, ModelOperation, ModelQuery,
+    ModelReadOperation, OrderByQuery, PaginatedQuery, PrismaClientInternals, Query, Select,
+    SelectType, WhereInput, WhereQuery, WithQuery,
 };
 
 use super::SerializedWhereInput;
 
-pub struct FindFirst<'a, Actions>
-where
-    Actions: ModelActions,
-{
+pub struct FindFirst<'a, Actions: ModelActions> {
     client: &'a PrismaClientInternals,
     pub where_params: Vec<Actions::Where>,
     pub with_params: Vec<Actions::With>,
@@ -24,19 +19,7 @@ where
     pub take: Option<i64>,
 }
 
-impl<'a, Actions> ModelAction for FindFirst<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type Actions = Actions;
-
-    const TYPE: ModelActionType = ModelActionType::Query(ModelQueryType::FindFirst);
-}
-
-impl<'a, Actions> FindFirst<'a, Actions>
-where
-    Actions: ModelActions,
-{
+impl<'a, Actions: ModelActions> FindFirst<'a, Actions> {
     pub fn new(client: &'a PrismaClientInternals, where_params: Vec<Actions::Where>) -> Self {
         Self {
             client,
@@ -167,7 +150,16 @@ where
         )
     }
 
-    pub(crate) fn exec_operation(self) -> (Operation, &'a PrismaClientInternals) {
+    pub async fn exec(self) -> super::Result<Option<Actions::Data>> {
+        super::exec(self).await
+    }
+}
+
+impl<'a, Actions: ModelActions> Query<'a> for FindFirst<'a, Actions> {
+    type RawType = Option<Actions::Data>;
+    type ReturnType = Self::RawType;
+
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
         let mut scalar_selections = Actions::scalar_selections();
 
         scalar_selections.extend(self.with_params.into_iter().map(Into::into));
@@ -185,25 +177,45 @@ where
         )
     }
 
-    pub async fn exec(self) -> super::Result<Option<Actions::Data>> {
-        let (op, client) = self.exec_operation();
+    fn convert(raw: Self::RawType) -> Self::ReturnType {
+        raw
+    }
+}
+
+impl<'a, Actions: ModelActions> ModelQuery<'a> for FindFirst<'a, Actions> {
+    type Actions = Actions;
 
-        client.execute(op).await
+    const TYPE: ModelOperation = ModelOperation::Read(ModelReadOperation::FindFirst);
+}
+
+impl<'a, Actions: ModelActions> WhereQuery<'a> for FindFirst<'a, Actions> {
+    fn add_where(&mut self, param: Actions::Where) {
+        self.where_params.push(param);
     }
 }
 
-impl<'a, Actions> BatchQuery for FindFirst<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type RawType = Actions::Data;
-    type ReturnType = Self::RawType;
+impl<'a, Actions: ModelActions> WithQuery<'a> for FindFirst<'a, Actions> {
+    fn add_with(&mut self, param: impl Into<Actions::With>) {
+        self.with_params.push(param.into());
+    }
+}
 
-    fn graphql(self) -> Operation {
-        self.exec_operation().0
+impl<'a, Actions: ModelActions> OrderByQuery<'a> for FindFirst<'a, Actions> {
+    fn add_order_by(&mut self, param: Actions::OrderBy) {
+        self.order_by_params.push(param);
     }
+}
 
-    fn convert(raw: Self::RawType) -> Self::ReturnType {
-        raw
+impl<'a, Actions: ModelActions> PaginatedQuery<'a> for FindFirst<'a, Actions> {
+    fn add_cursor(&mut self, param: Actions::Cursor) {
+        self.cursor_params.push(param);
+    }
+
+    fn set_skip(&mut self, skip: i64) {
+        self.skip = Some(skip);
+    }
+
+    fn set_take(&mut self, take: i64) {
+        self.take = Some(take);
     }
 }
diff --git a/src/queries/find_many.rs b/src/queries/find_many.rs
index 86e844f5..345f2879 100644
--- a/src/queries/find_many.rs
+++ b/src/queries/find_many.rs
@@ -2,19 +2,14 @@ use prisma_models::PrismaValue;
 use query_core::{Operation, QueryValue, Selection};
 
 use crate::{
-    actions::ModelActions,
-    include::{Include, IncludeType},
-    merge_fields,
-    select::{Select, SelectType},
-    BatchQuery, ModelAction, ModelActionType, ModelQueryType, PrismaClientInternals, WhereInput,
+    merge_fields, Include, IncludeType, ModelActions, ModelOperation, ModelQuery,
+    ModelReadOperation, OrderByQuery, PaginatedQuery, PrismaClientInternals, Query, Select,
+    SelectType, WhereInput, WhereQuery, WithQuery,
 };
 
 use super::SerializedWhereInput;
 
-pub struct FindMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
+pub struct FindMany<'a, Actions: ModelActions> {
     client: &'a PrismaClientInternals,
     pub where_params: Vec<Actions::Where>,
     pub with_params: Vec<Actions::With>,
@@ -24,19 +19,7 @@ where
     pub take: Option<i64>,
 }
 
-impl<'a, Actions> ModelAction for FindMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type Actions = Actions;
-
-    const TYPE: ModelActionType = ModelActionType::Query(ModelQueryType::FindMany);
-}
-
-impl<'a, Actions> FindMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
+impl<'a, Actions: ModelActions> FindMany<'a, Actions> {
     pub fn new(client: &'a PrismaClientInternals, where_params: Vec<Actions::Where>) -> Self {
         Self {
             client,
@@ -167,7 +150,16 @@ where
         )
     }
 
-    pub(crate) fn exec_operation(self) -> (Operation, &'a PrismaClientInternals) {
+    pub async fn exec(self) -> super::Result<Vec<Actions::Data>> {
+        super::exec(self).await
+    }
+}
+
+impl<'a, Actions: ModelActions> Query<'a> for FindMany<'a, Actions> {
+    type RawType = Vec<Actions::Data>;
+    type ReturnType = Self::RawType;
+
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
         let mut scalar_selections = Actions::scalar_selections();
 
         scalar_selections.extend(self.with_params.into_iter().map(Into::into));
@@ -185,34 +177,51 @@ where
         )
     }
 
-    pub async fn exec(self) -> super::Result<Vec<Actions::Data>> {
-        let (op, client) = self.exec_operation();
+    fn convert(raw: Self::RawType) -> Self::ReturnType {
+        raw
+    }
+}
+
+impl<'a, Actions: ModelActions> ModelQuery<'a> for FindMany<'a, Actions> {
+    type Actions = Actions;
 
-        client.execute(op).await
+    const TYPE: ModelOperation = ModelOperation::Read(ModelReadOperation::FindMany);
+}
+
+impl<'a, Actions: ModelActions> WhereQuery<'a> for FindMany<'a, Actions> {
+    fn add_where(&mut self, param: Actions::Where) {
+        self.where_params.push(param);
     }
 }
 
-impl<'a, Actions> BatchQuery for FindMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type RawType = Vec<Actions::Data>;
-    type ReturnType = Self::RawType;
+impl<'a, Actions: ModelActions> WithQuery<'a> for FindMany<'a, Actions> {
+    fn add_with(&mut self, param: impl Into<Actions::With>) {
+        self.with_params.push(param.into());
+    }
+}
+
+impl<'a, Actions: ModelActions> OrderByQuery<'a> for FindMany<'a, Actions> {
+    fn add_order_by(&mut self, param: Actions::OrderBy) {
+        self.order_by_params.push(param);
+    }
+}
 
-    fn graphql(self) -> Operation {
-        self.exec_operation().0
+impl<'a, Actions: ModelActions> PaginatedQuery<'a> for FindMany<'a, Actions> {
+    fn add_cursor(&mut self, param: Actions::Cursor) {
+        self.cursor_params.push(param);
     }
 
-    fn convert(raw: Self::RawType) -> Self::ReturnType {
-        raw
+    fn set_skip(&mut self, skip: i64) {
+        self.skip = Some(skip);
+    }
+
+    fn set_take(&mut self, take: i64) {
+        self.take = Some(take);
     }
 }
 
 #[derive(Clone)]
-pub struct ManyArgs<Actions>
-where
-    Actions: ModelActions,
-{
+pub struct ManyArgs<Actions: ModelActions> {
     pub where_params: Vec<Actions::Where>,
     pub with_params: Vec<Actions::With>,
     pub order_by_params: Vec<Actions::OrderBy>,
@@ -221,10 +230,7 @@ where
     pub take: Option<i64>,
 }
 
-impl<Actions> ManyArgs<Actions>
-where
-    Actions: ModelActions,
-{
+impl<Actions: ModelActions> ManyArgs<Actions> {
     pub fn new(where_params: Vec<Actions::Where>) -> Self {
         Self {
             where_params,
diff --git a/src/queries/find_unique.rs b/src/queries/find_unique.rs
index fdeff8e3..97930103 100644
--- a/src/queries/find_unique.rs
+++ b/src/queries/find_unique.rs
@@ -4,35 +4,18 @@ use prisma_models::PrismaValue;
 use query_core::{Operation, Selection};
 
 use crate::{
-    include::{Include, IncludeType},
-    select::{Select, SelectType},
-    BatchQuery, ModelAction, ModelActionType, ModelActions, ModelQueryType, PrismaClientInternals,
-    WhereInput,
+    Include, IncludeType, ModelActions, ModelOperation, ModelQuery, ModelReadOperation,
+    PrismaClientInternals, Query, Select, SelectType, WhereInput, WithQuery,
 };
 
-pub struct FindUnique<'a, Actions>
-where
-    Actions: ModelActions,
-{
+pub struct FindUnique<'a, Actions: ModelActions> {
     client: &'a PrismaClientInternals,
     pub where_param: Actions::Where,
     pub with_params: Vec<Actions::With>,
     _data: PhantomData<(Actions::Set, Actions::Data)>,
 }
 
-impl<'a, Actions> ModelAction for FindUnique<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type Actions = Actions;
-
-    const TYPE: ModelActionType = ModelActionType::Query(ModelQueryType::FindUnique);
-}
-
-impl<'a, Actions> FindUnique<'a, Actions>
-where
-    Actions: ModelActions,
-{
+impl<'a, Actions: ModelActions> FindUnique<'a, Actions> {
     pub fn new(client: &'a PrismaClientInternals, where_param: Actions::Where) -> Self {
         Self {
             client,
@@ -83,7 +66,16 @@ where
         )
     }
 
-    pub(crate) fn exec_operation(self) -> (Operation, &'a PrismaClientInternals) {
+    pub async fn exec(self) -> super::Result<Option<Actions::Data>> {
+        super::exec(self).await
+    }
+}
+
+impl<'a, Actions: ModelActions> Query<'a> for FindUnique<'a, Actions> {
+    type RawType = Option<Actions::Data>;
+    type ReturnType = Self::RawType;
+
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
         let mut scalar_selections = Actions::scalar_selections();
 
         scalar_selections.extend(self.with_params.into_iter().map(Into::into));
@@ -94,26 +86,20 @@ where
         )
     }
 
-    pub async fn exec(self) -> super::Result<Option<Actions::Data>> {
-        let (op, client) = self.exec_operation();
-
-        client.execute(op).await
+    fn convert(raw: Self::RawType) -> Self::ReturnType {
+        raw
     }
 }
 
-impl<'a, Actions> BatchQuery for FindUnique<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type RawType = Option<Actions::Data>;
-    type ReturnType = Self::RawType;
+impl<'a, Actions: ModelActions> ModelQuery<'a> for FindUnique<'a, Actions> {
+    type Actions = Actions;
 
-    fn graphql(self) -> Operation {
-        self.exec_operation().0
-    }
+    const TYPE: ModelOperation = ModelOperation::Read(ModelReadOperation::FindUnique);
+}
 
-    fn convert(raw: Self::RawType) -> Self::ReturnType {
-        raw
+impl<'a, Actions: ModelActions> WithQuery<'a> for FindUnique<'a, Actions> {
+    fn add_with(&mut self, param: impl Into<Actions::With>) {
+        self.with_params.push(param.into());
     }
 }
 
diff --git a/src/queries/include.rs b/src/queries/include.rs
index bb242480..4ef5fc32 100644
--- a/src/queries/include.rs
+++ b/src/queries/include.rs
@@ -1,25 +1,25 @@
+use query_core::{Operation, Selection};
 use std::marker::PhantomData;
 
-use query_core::{Operation, Selection};
-use serde::de::DeserializeOwned;
+use crate::{PrismaClientInternals, Query};
 
-use crate::{BatchQuery, PrismaClientInternals};
+use super::query;
 
 pub trait IncludeType {
     // TODO: ModelActions
-    type Data: DeserializeOwned;
+    type Data: query::Data;
     type ModelData;
 
     fn to_selections(self) -> Vec<Selection>;
 }
 
-pub struct Include<'a, Data: DeserializeOwned> {
+pub struct Include<'a, Data> {
     operation: Operation,
     client: &'a PrismaClientInternals,
     _data: PhantomData<Data>,
 }
 
-impl<'a, Data: DeserializeOwned> Include<'a, Data> {
+impl<'a, Data: query::Data> Include<'a, Data> {
     pub fn new(client: &'a PrismaClientInternals, operation: Operation) -> Self {
         Self {
             client,
@@ -29,16 +29,16 @@ impl<'a, Data: DeserializeOwned> Include<'a, Data> {
     }
 
     pub async fn exec(self) -> super::Result<Data> {
-        self.client.execute(self.operation).await
+        super::exec(self).await
     }
 }
 
-impl<'a, Data: DeserializeOwned> BatchQuery for Include<'a, Data> {
+impl<'a, Data: query::Data> Query<'a> for Include<'a, Data> {
     type RawType = Data;
     type ReturnType = Self::RawType;
 
-    fn graphql(self) -> Operation {
-        self.operation
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
+        (self.operation, self.client)
     }
 
     fn convert(raw: Self::RawType) -> Self::ReturnType {
diff --git a/src/queries/mod.rs b/src/queries/mod.rs
index 99499470..12658d3b 100644
--- a/src/queries/mod.rs
+++ b/src/queries/mod.rs
@@ -1,19 +1,21 @@
-pub mod batch;
-pub mod count;
-pub mod create;
-pub mod create_many;
-pub mod delete;
-pub mod delete_many;
-pub mod execute_raw;
-pub mod find_first;
-pub mod find_many;
-pub mod find_unique;
-pub mod include;
-pub mod query_raw;
-pub mod select;
-pub mod update;
-pub mod update_many;
-pub mod upsert;
+mod batch;
+mod count;
+mod create;
+mod create_many;
+mod delete;
+mod delete_many;
+mod error;
+mod execute_raw;
+mod find_first;
+mod find_many;
+mod find_unique;
+mod include;
+mod query;
+mod query_raw;
+mod select;
+mod update;
+mod update_many;
+mod upsert;
 
 pub use batch::*;
 pub use count::*;
@@ -21,18 +23,20 @@ pub use create::*;
 pub use create_many::*;
 pub use delete::*;
 pub use delete_many::*;
+pub use error::*;
 pub use execute_raw::*;
 pub use find_first::*;
 pub use find_many::*;
 pub use find_unique::*;
+pub use include::*;
+pub use query::*;
 pub use query_raw::*;
+pub use select::*;
 pub use update::*;
 pub use update_many::*;
 pub use upsert::*;
 
 pub use query_core::{schema::QuerySchemaRef, Operation, Selection};
-use thiserror::Error;
-use user_facing_errors::UserFacingError;
 
 pub enum SerializedWhereValue {
     Object(Vec<(String, prisma_models::PrismaValue)>),
@@ -94,39 +98,11 @@ impl Into<(String, prisma_models::PrismaValue)> for SerializedWhereInput {
     }
 }
 
-#[derive(Debug, Error)]
-pub enum QueryError {
-    #[error("Error executing query: {} - {}", .0.as_known().map(|k| k.error_code.to_string()).unwrap_or("Unknown".to_string()), .0.message())]
-    Execute(user_facing_errors::Error),
+pub(crate) async fn exec<'a, Q: Query<'a>>(
+    query: Q,
+) -> error::Result<<Q as Query<'a>>::ReturnType> {
+    let (op, client) = query.graphql();
+    let res = client.execute(op).await;
 
-    #[error("Error serializing query result: {0}")]
-    Serialize(#[from] serde_value::SerializerError),
-
-    #[error("Error deserializing query result into return type: {0}")]
-    Deserialize(#[from] serde_value::DeserializerError),
-}
-
-impl QueryError {
-    pub fn is_prisma_error<T: UserFacingError>(&self) -> bool {
-        match self {
-            Self::Execute(error) => error
-                .as_known()
-                .map(|e| e.error_code == <T as UserFacingError>::ERROR_CODE)
-                .unwrap_or(false),
-            _ => false,
-        }
-    }
-}
-
-pub type Result<T> = std::result::Result<T, QueryError>;
-
-#[cfg(feature = "rspc")]
-impl From<QueryError> for rspc::Error {
-    fn from(err: QueryError) -> Self {
-        rspc::Error::with_cause(
-            rspc::ErrorCode::InternalServerError,
-            "Internal server error occurred while completing database operation!".into(),
-            err,
-        )
-    }
+    res.map(Q::convert)
 }
diff --git a/src/queries/query.rs b/src/queries/query.rs
new file mode 100644
index 00000000..cc720c62
--- /dev/null
+++ b/src/queries/query.rs
@@ -0,0 +1,135 @@
+use dml::PrismaValue;
+use query_core::{Operation, Selection, SelectionArgument};
+use serde::de::DeserializeOwned;
+
+use crate::{PrismaClientInternals, WhereInput};
+
+pub trait Query<'a> {
+    type RawType: DeserializeOwned;
+    type ReturnType: 'static;
+
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals);
+
+    /// Function for converting between raw database data and the type expected by the user.
+    /// Necessary for things like raw queries
+    fn convert(raw: Self::RawType) -> Self::ReturnType;
+}
+
+pub trait ModelActions {
+    type Data: Data;
+    type Where: WhereInput;
+    type Set: Into<(String, PrismaValue)>;
+    type With: Into<Selection>;
+    type OrderBy: Into<(String, PrismaValue)>;
+    type Cursor: Into<Self::Where>;
+
+    const MODEL: &'static str;
+
+    fn scalar_selections() -> Vec<Selection>;
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum ModelReadOperation {
+    FindUnique,
+    FindFirst,
+    FindMany,
+    Count,
+}
+
+impl ModelReadOperation {
+    pub fn name(&self) -> &'static str {
+        match self {
+            Self::FindUnique => "findUnique",
+            Self::FindFirst => "findFirst",
+            Self::FindMany => "findMany",
+            Self::Count => "aggregate",
+        }
+    }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum ModelWriteOperation {
+    Create,
+    CreateMany,
+    Update,
+    UpdateMany,
+    Delete,
+    DeleteMany,
+    Upsert,
+}
+
+impl ModelWriteOperation {
+    pub fn name(&self) -> &'static str {
+        match self {
+            Self::Create => "createOne",
+            Self::CreateMany => "createMany",
+            Self::Update => "updateOne",
+            Self::UpdateMany => "updateMany",
+            Self::Delete => "deleteOne",
+            Self::DeleteMany => "deleteMany",
+            Self::Upsert => "upsertOne",
+        }
+    }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum ModelOperation {
+    Read(ModelReadOperation),
+    Write(ModelWriteOperation),
+}
+
+impl ModelOperation {
+    pub fn name(&self) -> &'static str {
+        match self {
+            Self::Read(q) => q.name(),
+            Self::Write(q) => q.name(),
+        }
+    }
+}
+
+pub trait ModelQuery<'a>: Query<'a> {
+    type Actions: ModelActions;
+
+    const TYPE: ModelOperation;
+
+    fn base_selection(
+        arguments: impl IntoIterator<Item = SelectionArgument>,
+        nested_selections: impl IntoIterator<Item = Selection>,
+    ) -> Selection {
+        Selection::new(
+            format!("{}{}", Self::TYPE.name(), Self::Actions::MODEL),
+            Some("result".to_string()),
+            arguments.into_iter().collect::<Vec<_>>(),
+            nested_selections.into_iter().collect::<Vec<_>>(),
+        )
+    }
+}
+
+pub trait WhereQuery<'a>: ModelQuery<'a> {
+    fn add_where(&mut self, param: <<Self as ModelQuery<'a>>::Actions as ModelActions>::Where);
+}
+
+pub trait WithQuery<'a>: ModelQuery<'a> {
+    fn add_with(
+        &mut self,
+        param: impl Into<<<Self as ModelQuery<'a>>::Actions as ModelActions>::With>,
+    );
+}
+
+pub trait OrderByQuery<'a>: ModelQuery<'a> {
+    fn add_order_by(&mut self, param: <<Self as ModelQuery<'a>>::Actions as ModelActions>::OrderBy);
+}
+
+pub trait PaginatedQuery<'a>: ModelQuery<'a> {
+    fn add_cursor(&mut self, param: <<Self as ModelQuery<'a>>::Actions as ModelActions>::Cursor);
+    fn set_skip(&mut self, skip: i64);
+    fn set_take(&mut self, take: i64);
+}
+
+pub trait SetQuery<'a>: ModelQuery<'a> {
+    fn add_set(&mut self, param: <<Self as ModelQuery<'a>>::Actions as ModelActions>::Set);
+}
+
+pub trait Data: DeserializeOwned + 'static {}
+
+impl<T: DeserializeOwned + 'static> Data for T {}
diff --git a/src/queries/query_raw.rs b/src/queries/query_raw.rs
index 24fd4b2d..f8b1d239 100644
--- a/src/queries/query_raw.rs
+++ b/src/queries/query_raw.rs
@@ -7,7 +7,7 @@ use serde_json::Value;
 
 use crate::{
     raw::{Raw, RawOperationData, RawPrismaValue},
-    BatchQuery, PrismaClientInternals,
+    PrismaClientInternals, Query,
 };
 
 pub struct QueryRaw<'a, Data>
@@ -22,7 +22,7 @@ where
 
 impl<'a, Data> QueryRaw<'a, Data>
 where
-    Data: DeserializeOwned,
+    Data: DeserializeOwned + 'static,
 {
     pub fn new(client: &'a PrismaClientInternals, query: Raw, database: &'static str) -> Self {
         let (sql, params) = query.convert(database);
@@ -35,24 +35,6 @@ where
         }
     }
 
-    pub(crate) fn exec_operation(self) -> (Operation, &'a PrismaClientInternals) {
-        (
-            Operation::Write(Selection::new(
-                "queryRaw".to_string(),
-                None,
-                [
-                    ("query".to_string(), PrismaValue::String(self.sql).into()),
-                    (
-                        "parameters".to_string(),
-                        PrismaValue::String(serde_json::to_string(&self.params).unwrap()).into(),
-                    ),
-                ],
-                [],
-            )),
-            self.client,
-        )
-    }
-
     pub(crate) fn convert(raw: RawOperationData) -> super::Result<Vec<Data>> {
         let typed_data: Vec<HashMap<String, RawPrismaValue>> = raw
             .into_iter()
@@ -74,21 +56,33 @@ where
     }
 
     pub async fn exec(self) -> super::Result<Vec<Data>> {
-        let (op, client) = self.exec_operation();
-
-        client.execute(op).await.and_then(Self::convert)
+        super::exec(self).await
     }
 }
 
-impl<'a, Data> BatchQuery for QueryRaw<'a, Data>
+impl<'a, Data> Query<'a> for QueryRaw<'a, Data>
 where
-    Data: DeserializeOwned,
+    Data: DeserializeOwned + 'static,
 {
     type RawType = RawOperationData;
     type ReturnType = Vec<Data>;
 
-    fn graphql(self) -> Operation {
-        self.exec_operation().0
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
+        (
+            Operation::Write(Selection::new(
+                "queryRaw".to_string(),
+                None,
+                [
+                    ("query".to_string(), PrismaValue::String(self.sql).into()),
+                    (
+                        "parameters".to_string(),
+                        PrismaValue::String(serde_json::to_string(&self.params).unwrap()).into(),
+                    ),
+                ],
+                [],
+            )),
+            self.client,
+        )
     }
 
     fn convert(raw: Self::RawType) -> Self::ReturnType {
diff --git a/src/queries/select.rs b/src/queries/select.rs
index acc7f66c..f523a01e 100644
--- a/src/queries/select.rs
+++ b/src/queries/select.rs
@@ -3,7 +3,7 @@ use std::marker::PhantomData;
 use query_core::{Operation, Selection};
 use serde::de::DeserializeOwned;
 
-use crate::{BatchQuery, PrismaClientInternals};
+use crate::{PrismaClientInternals, Query};
 
 pub trait SelectType {
     // TODO: ModelActions
@@ -33,12 +33,12 @@ impl<'a, Data: DeserializeOwned> Select<'a, Data> {
     }
 }
 
-impl<'a, Data: DeserializeOwned> BatchQuery for Select<'a, Data> {
+impl<'a, Data: DeserializeOwned + 'static> Query<'a> for Select<'a, Data> {
     type RawType = Data;
     type ReturnType = Self::RawType;
 
-    fn graphql(self) -> Operation {
-        self.operation
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
+        (self.operation, self.client)
     }
 
     fn convert(raw: Self::RawType) -> Self::ReturnType {
diff --git a/src/queries/update.rs b/src/queries/update.rs
index a55859d4..b79c4f94 100644
--- a/src/queries/update.rs
+++ b/src/queries/update.rs
@@ -2,36 +2,19 @@ use prisma_models::PrismaValue;
 use query_core::{Operation, Selection};
 
 use crate::{
-    include::{Include, IncludeType},
-    merge_fields,
-    select::{Select, SelectType},
-    BatchQuery, ModelAction, ModelActionType, ModelActions, ModelMutationType,
-    PrismaClientInternals, WhereInput,
+    merge_fields, Include, IncludeType, ModelActions, ModelOperation, ModelQuery,
+    ModelWriteOperation, PrismaClientInternals, Query, Select, SelectType, SetQuery, WhereInput,
+    WithQuery,
 };
 
-pub struct Update<'a, Actions>
-where
-    Actions: ModelActions,
-{
+pub struct Update<'a, Actions: ModelActions> {
     client: &'a PrismaClientInternals,
     pub where_param: Actions::Where,
     pub set_params: Vec<Actions::Set>,
     pub with_params: Vec<Actions::With>,
 }
 
-impl<'a, Actions> ModelAction for Update<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type Actions = Actions;
-
-    const TYPE: ModelActionType = ModelActionType::Mutation(ModelMutationType::Update);
-}
-
-impl<'a, Actions> Update<'a, Actions>
-where
-    Actions: ModelActions,
-{
+impl<'a, Actions: ModelActions> Update<'a, Actions> {
     pub fn new(
         client: &'a PrismaClientInternals,
         where_param: Actions::Where,
@@ -102,7 +85,16 @@ where
         )
     }
 
-    pub(crate) fn exec_operation(self) -> (Operation, &'a PrismaClientInternals) {
+    pub async fn exec(self) -> super::Result<Actions::Data> {
+        super::exec(self).await
+    }
+}
+
+impl<'a, Actions: ModelActions> Query<'a> for Update<'a, Actions> {
+    type RawType = Actions::Data;
+    type ReturnType = Self::RawType;
+
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
         let mut scalar_selections = Actions::scalar_selections();
 
         scalar_selections.extend(self.with_params.into_iter().map(Into::into));
@@ -117,30 +109,25 @@ where
         )
     }
 
-    pub async fn exec(self) -> super::Result<Actions::Data> {
-        let (op, client) = self.exec_operation();
-
-        let res = client.execute(op).await?;
-
-        #[cfg(feature = "mutation-callbacks")]
-        client.notify_model_mutation::<Self>();
-
-        Ok(res)
+    fn convert(raw: Self::RawType) -> Self::ReturnType {
+        raw
     }
 }
 
-impl<'a, Actions> BatchQuery for Update<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type RawType = Actions::Data;
-    type ReturnType = Self::RawType;
+impl<'a, Actions: ModelActions> ModelQuery<'a> for Update<'a, Actions> {
+    type Actions = Actions;
 
-    fn graphql(self) -> Operation {
-        self.exec_operation().0
+    const TYPE: ModelOperation = ModelOperation::Write(ModelWriteOperation::Update);
+}
+
+impl<'a, Actions: ModelActions> SetQuery<'a> for Update<'a, Actions> {
+    fn add_set(&mut self, param: Actions::Set) {
+        self.set_params.push(param);
     }
+}
 
-    fn convert(raw: Self::RawType) -> Self::ReturnType {
-        raw
+impl<'a, Actions: ModelActions> WithQuery<'a> for Update<'a, Actions> {
+    fn add_with(&mut self, param: impl Into<Actions::With>) {
+        self.with_params.push(param.into());
     }
 }
diff --git a/src/queries/update_many.rs b/src/queries/update_many.rs
index ca3ce479..e8e1d095 100644
--- a/src/queries/update_many.rs
+++ b/src/queries/update_many.rs
@@ -2,32 +2,17 @@ use prisma_models::PrismaValue;
 use query_core::Operation;
 
 use crate::{
-    merge_fields, BatchQuery, BatchResult, ModelAction, ModelActionType, ModelActions,
-    ModelMutationType, PrismaClientInternals, WhereInput,
+    merge_fields, BatchResult, ModelActions, ModelOperation, ModelQuery, ModelWriteOperation,
+    PrismaClientInternals, Query, SetQuery, WhereInput, WhereQuery,
 };
 
-pub struct UpdateMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
+pub struct UpdateMany<'a, Actions: ModelActions> {
     client: &'a PrismaClientInternals,
     pub where_params: Vec<Actions::Where>,
     pub set_params: Vec<Actions::Set>,
 }
 
-impl<'a, Actions> ModelAction for UpdateMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type Actions = Actions;
-
-    const TYPE: ModelActionType = ModelActionType::Mutation(ModelMutationType::UpdateMany);
-}
-
-impl<'a, Actions> UpdateMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
+impl<'a, Actions: ModelActions> UpdateMany<'a, Actions> {
     pub fn new(
         client: &'a PrismaClientInternals,
         where_params: Vec<Actions::Where>,
@@ -40,7 +25,16 @@ where
         }
     }
 
-    pub(crate) fn exec_operation(self) -> (Operation, &'a PrismaClientInternals) {
+    pub async fn exec(self) -> super::Result<i64> {
+        super::exec(self).await
+    }
+}
+
+impl<'a, Actions: ModelActions> Query<'a> for UpdateMany<'a, Actions> {
+    type RawType = BatchResult;
+    type ReturnType = i64;
+
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
         (
             Operation::Write(Self::base_selection(
                 [
@@ -73,34 +67,25 @@ where
         )
     }
 
-    pub(crate) fn convert(raw: BatchResult) -> i64 {
+    fn convert(raw: Self::RawType) -> Self::ReturnType {
         raw.count
     }
+}
 
-    pub async fn exec(self) -> super::Result<i64> {
-        let (op, client) = self.exec_operation();
-
-        let res = client.execute(op).await.map(Self::convert)?;
-
-        #[cfg(feature = "mutation-callbacks")]
-        client.notify_model_mutation::<Self>();
+impl<'a, Actions: ModelActions> ModelQuery<'a> for UpdateMany<'a, Actions> {
+    type Actions = Actions;
 
-        Ok(res)
-    }
+    const TYPE: ModelOperation = ModelOperation::Write(ModelWriteOperation::UpdateMany);
 }
 
-impl<'a, Actions> BatchQuery for UpdateMany<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type RawType = BatchResult;
-    type ReturnType = i64;
-
-    fn graphql(self) -> Operation {
-        self.exec_operation().0
+impl<'a, Actions: ModelActions> WhereQuery<'a> for UpdateMany<'a, Actions> {
+    fn add_where(&mut self, param: Actions::Where) {
+        self.where_params.push(param);
     }
+}
 
-    fn convert(raw: Self::RawType) -> Self::ReturnType {
-        Self::convert(raw)
+impl<'a, Actions: ModelActions> SetQuery<'a> for UpdateMany<'a, Actions> {
+    fn add_set(&mut self, param: Actions::Set) {
+        self.set_params.push(param);
     }
 }
diff --git a/src/queries/upsert.rs b/src/queries/upsert.rs
index 1a9ed1a9..a6327dbe 100644
--- a/src/queries/upsert.rs
+++ b/src/queries/upsert.rs
@@ -2,16 +2,11 @@ use prisma_models::PrismaValue;
 use query_core::{Operation, Selection};
 
 use crate::{
-    include::{Include, IncludeType},
-    select::{Select, SelectType},
-    BatchQuery, ModelAction, ModelActionType, ModelActions, ModelMutationType,
-    PrismaClientInternals, WhereInput,
+    Include, IncludeType, ModelActions, ModelOperation, ModelQuery, ModelWriteOperation,
+    PrismaClientInternals, Query, Select, SelectType, WhereInput, WithQuery,
 };
 
-pub struct Upsert<'a, Actions>
-where
-    Actions: ModelActions,
-{
+pub struct Upsert<'a, Actions: ModelActions> {
     client: &'a PrismaClientInternals,
     pub where_param: Actions::Where,
     pub create_params: Vec<Actions::Set>,
@@ -19,19 +14,7 @@ where
     pub with_params: Vec<Actions::With>,
 }
 
-impl<'a, Actions> ModelAction for Upsert<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type Actions = Actions;
-
-    const TYPE: ModelActionType = ModelActionType::Mutation(ModelMutationType::Upsert);
-}
-
-impl<'a, Actions> Upsert<'a, Actions>
-where
-    Actions: ModelActions,
-{
+impl<'a, Actions: ModelActions> Upsert<'a, Actions> {
     pub fn new(
         client: &'a PrismaClientInternals,
         where_param: Actions::Where,
@@ -107,7 +90,16 @@ where
         )
     }
 
-    pub(crate) fn exec_operation(self) -> (Operation, &'a PrismaClientInternals) {
+    pub async fn exec(self) -> super::Result<Actions::Data> {
+        super::exec(self).await
+    }
+}
+
+impl<'a, Actions: ModelActions> Query<'a> for Upsert<'a, Actions> {
+    type RawType = Actions::Data;
+    type ReturnType = Self::RawType;
+
+    fn graphql(self) -> (Operation, &'a PrismaClientInternals) {
         let mut scalar_selections = Actions::scalar_selections();
 
         scalar_selections.extend(self.with_params.into_iter().map(Into::into));
@@ -123,28 +115,19 @@ where
         )
     }
 
-    pub async fn exec(self) -> super::Result<Actions::Data> {
-        let (op, client) = self.exec_operation();
-
-        let res = client.execute(op).await?;
-        client.notify_model_mutation::<Self>();
-
-        Ok(res)
+    fn convert(raw: Self::RawType) -> Self::ReturnType {
+        raw
     }
 }
 
-impl<'a, Actions> BatchQuery for Upsert<'a, Actions>
-where
-    Actions: ModelActions,
-{
-    type RawType = Actions::Data;
-    type ReturnType = Self::RawType;
+impl<'a, Actions: ModelActions> ModelQuery<'a> for Upsert<'a, Actions> {
+    type Actions = Actions;
 
-    fn graphql(self) -> Operation {
-        self.exec_operation().0
-    }
+    const TYPE: ModelOperation = ModelOperation::Write(ModelWriteOperation::Upsert);
+}
 
-    fn convert(raw: Self::RawType) -> Self::ReturnType {
-        raw
+impl<'a, Actions: ModelActions> WithQuery<'a> for Upsert<'a, Actions> {
+    fn add_with(&mut self, param: impl Into<Actions::With>) {
+        self.with_params.push(param.into());
     }
 }


Test patch:
diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml
index 002e424b..5c474b2b 100644
--- a/integration-tests/Cargo.toml
+++ b/integration-tests/Cargo.toml
@@ -18,6 +18,6 @@ prisma-client-rust = { path = "..", features = [
   "sqlite-create-many",
   "sqlite",
   "migrations",
-  "mutation-callbacks",
+  # "mutation-callbacks",
   "mocking",
 ], default-features = false }
diff --git a/integration-tests/tests/count.rs b/integration-tests/tests/count.rs
index 58a6f217..fc3f35a4 100644
--- a/integration-tests/tests/count.rs
+++ b/integration-tests/tests/count.rs
@@ -1,19 +1,29 @@
 use crate::db::*;
 use crate::utils::*;
 
-#[tokio::test]
-async fn basic() -> TestResult {
-    let client = client().await;
-
+async fn create_posts(client: &PrismaClient) -> TestResult {
     client
         .post()
-        .create("Hi from Prisma!".to_string(), true, vec![])
+        .create_many(vec![
+            post::create("Hi from Prisma!".to_string(), true, vec![]),
+            post::create("Hi from Prisma!".to_string(), true, vec![]),
+            post::create("Hi from Prisma!".to_string(), false, vec![]),
+        ])
         .exec()
         .await?;
 
+    Ok(())
+}
+
+#[tokio::test]
+async fn basic() -> TestResult {
+    let client = client().await;
+
+    create_posts(&client).await?;
+
     let count = client.post().count(vec![]).exec().await?;
 
-    assert_eq!(count, 1);
+    assert_eq!(count, 3);
 
     cleanup(client).await
 }
@@ -37,23 +47,7 @@ async fn no_results() -> TestResult {
 async fn where_() -> TestResult {
     let client = client().await;
 
-    client
-        .post()
-        .create("Hi from Prisma!".to_string(), true, vec![])
-        .exec()
-        .await?;
-
-    client
-        .post()
-        .create("Hi from Prisma!".to_string(), false, vec![])
-        .exec()
-        .await?;
-
-    client
-        .post()
-        .create("Hi from Prisma!".to_string(), true, vec![])
-        .exec()
-        .await?;
+    create_posts(&client).await?;
 
     let published_count = client
         .post()
@@ -76,23 +70,7 @@ async fn where_() -> TestResult {
 async fn take() -> TestResult {
     let client = client().await;
 
-    client
-        .post()
-        .create("Hi from Prisma!".to_string(), true, vec![])
-        .exec()
-        .await?;
-
-    client
-        .post()
-        .create("Hi from Prisma!".to_string(), true, vec![])
-        .exec()
-        .await?;
-
-    client
-        .post()
-        .create("Hi from Prisma!".to_string(), true, vec![])
-        .exec()
-        .await?;
+    create_posts(&client).await?;
 
     let count = client.post().count(vec![]).take(1).exec().await?;
 
@@ -105,23 +83,7 @@ async fn take() -> TestResult {
 async fn skip() -> TestResult {
     let client = client().await;
 
-    client
-        .post()
-        .create("Hi from Prisma!".to_string(), true, vec![])
-        .exec()
-        .await?;
-
-    client
-        .post()
-        .create("Hi from Prisma!".to_string(), true, vec![])
-        .exec()
-        .await?;
-
-    client
-        .post()
-        .create("Hi from Prisma!".to_string(), true, vec![])
-        .exec()
-        .await?;
+    create_posts(&client).await?;
 
     let count = client.post().count(vec![]).skip(1).exec().await?;
 
diff --git a/integration-tests/tests/lib.rs b/integration-tests/tests/lib.rs
index 9d07d5c1..9c0890ea 100644
--- a/integration-tests/tests/lib.rs
+++ b/integration-tests/tests/lib.rs
@@ -19,7 +19,7 @@ async fn aaaa_run_migrations() -> TestResult {
 }
 
 mod batch;
-mod callbacks;
+// mod callbacks;
 mod count;
 mod create;
 mod create_many;
diff --git a/integration-tests/tests/mock.rs b/integration-tests/tests/mock.rs
index a839c679..5c7e14ee 100644
--- a/integration-tests/tests/mock.rs
+++ b/integration-tests/tests/mock.rs
@@ -3,7 +3,7 @@ use crate::utils::*;
 
 #[tokio::test]
 async fn returns() -> TestResult {
-    let (client, mock) = PrismaClient::_mock().await;
+    let (client, mock) = PrismaClient::_mock();
 
     user::select!(basic_user { id name });
 
@@ -31,7 +31,7 @@ async fn returns() -> TestResult {
 
 #[tokio::test]
 async fn returns_many() -> TestResult {
-    let (client, mock) = PrismaClient::_mock().await;
+    let (client, mock) = PrismaClient::_mock();
 
     user::select!(basic_user { id name });