From 77c66760029efd99278b58dc528afed0a89bf6f2 Mon Sep 17 00:00:00 2001
From: Dominic Petrick <dompetrick@gmail.com>
Date: Thu, 31 Dec 2020 14:01:31 +0100
Subject: [PATCH] `findOne` deprecation (#1487)

* Add deprecation mechanism to DMMF fields.

* Deprecate `findOne`.

* Deprecate with 2.15
---
 query-engine/core/src/schema/input_types.rs   | 16 +++++++++++
 query-engine/core/src/schema/mod.rs           |  7 +++++
 query-engine/core/src/schema/output_types.rs  | 15 ++++++++++
 query-engine/core/src/schema/query_schema.rs  |  2 ++
 .../schema_builder/output_types/query_type.rs | 28 +++++++++++++++++--
 query-engine/core/src/schema_builder/utils.rs |  2 ++
 .../query-engine/src/dmmf/schema/ast.rs       | 27 ++++++++++++++++++
 .../src/dmmf/schema/field_renderer.rs         |  2 ++
 8 files changed, 97 insertions(+), 2 deletions(-)

diff --git a/query-engine/core/src/schema/input_types.rs b/query-engine/core/src/schema/input_types.rs
index f6ac7d21887e..1f147fa1c387 100644
--- a/query-engine/core/src/schema/input_types.rs
+++ b/query-engine/core/src/schema/input_types.rs
@@ -81,6 +81,7 @@ impl InputObjectType {
 pub struct InputField {
     pub name: String,
     pub default_value: Option<dml::DefaultValue>,
+    pub deprecation: Option<Deprecation>,
 
     /// Possible field types, represented as a union of input types, but only one can be provided at any time.
     pub field_types: Vec<InputType>,
@@ -125,6 +126,21 @@ impl InputField {
         self.field_types.push(typ);
         self
     }
+
+    pub fn deprecate<T, S, U>(mut self, reason: T, since_version: S, planned_removal_version: Option<U>) -> Self
+    where
+        T: Into<String>,
+        S: Into<String>,
+        U: Into<String>,
+    {
+        self.deprecation = Some(Deprecation {
+            reason: reason.into(),
+            since_version: since_version.into(),
+            planned_removal_version: planned_removal_version.map(Into::into),
+        });
+
+        self
+    }
 }
 
 #[derive(Clone)]
diff --git a/query-engine/core/src/schema/mod.rs b/query-engine/core/src/schema/mod.rs
index 875447d99df9..121c49d29b9c 100644
--- a/query-engine/core/src/schema/mod.rs
+++ b/query-engine/core/src/schema/mod.rs
@@ -40,3 +40,10 @@ impl<T> IntoArc<T> for Weak<T> {
         self.upgrade().expect("Expected weak reference to be valid.")
     }
 }
+
+#[derive(Debug, PartialEq)]
+pub struct Deprecation {
+    pub since_version: String,
+    pub planned_removal_version: Option<String>,
+    pub reason: String,
+}
diff --git a/query-engine/core/src/schema/output_types.rs b/query-engine/core/src/schema/output_types.rs
index cee7b0c0b13d..dda8d616f5c5 100644
--- a/query-engine/core/src/schema/output_types.rs
+++ b/query-engine/core/src/schema/output_types.rs
@@ -142,6 +142,7 @@ impl ObjectType {
 pub struct OutputField {
     pub name: String,
     pub field_type: OutputTypeRef,
+    pub deprecation: Option<Deprecation>,
 
     /// Arguments are input fields, but positioned in context of an output field
     /// instead of being attached to an input object.
@@ -168,4 +169,18 @@ impl OutputField {
             self
         }
     }
+
+    pub fn deprecate<T, S>(mut self, reason: T, since_version: S, planned_removal_version: Option<String>) -> Self
+    where
+        T: Into<String>,
+        S: Into<String>,
+    {
+        self.deprecation = Some(Deprecation {
+            reason: reason.into(),
+            since_version: since_version.into(),
+            planned_removal_version,
+        });
+
+        self
+    }
 }
diff --git a/query-engine/core/src/schema/query_schema.rs b/query-engine/core/src/schema/query_schema.rs
index dcb55efa615c..a67034329460 100644
--- a/query-engine/core/src/schema/query_schema.rs
+++ b/query-engine/core/src/schema/query_schema.rs
@@ -89,6 +89,7 @@ pub struct QueryInfo {
 #[derive(Debug, Clone, PartialEq)]
 pub enum QueryTag {
     FindOne,
+    FindUnique,
     FindFirst,
     FindMany,
     CreateOne,
@@ -107,6 +108,7 @@ impl fmt::Display for QueryTag {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let s = match self {
             Self::FindOne => "findOne",
+            Self::FindUnique => "findUnique",
             Self::FindFirst => "findFirst",
             Self::FindMany => "findMany",
             Self::CreateOne => "createOne",
diff --git a/query-engine/core/src/schema_builder/output_types/query_type.rs b/query-engine/core/src/schema_builder/output_types/query_type.rs
index 8dae05c17685..e6a71e50a780 100644
--- a/query-engine/core/src/schema_builder/output_types/query_type.rs
+++ b/query-engine/core/src/schema_builder/output_types/query_type.rs
@@ -17,6 +17,7 @@ pub(crate) fn build(ctx: &mut BuilderContext) -> (OutputType, ObjectTypeStrongRe
             }
 
             append_opt(&mut vec, find_one_field(ctx, &model));
+            append_opt(&mut vec, find_unique_field(ctx, &model));
             vec
         })
         .flatten()
@@ -28,8 +29,7 @@ pub(crate) fn build(ctx: &mut BuilderContext) -> (OutputType, ObjectTypeStrongRe
     (OutputType::Object(Arc::downgrade(&strong_ref)), strong_ref)
 }
 
-/// Builds a "single" query arity item field (e.g. "user", "post" ...) for given model.
-/// Find one unique semantics.
+/// Deprecated field. Semantics are identical to `findUnique`.
 fn find_one_field(ctx: &mut BuilderContext, model: &ModelRef) -> Option<OutputField> {
     arguments::where_unique_argument(ctx, model).map(|arg| {
         let field_name = ctx.pluralize_internal(camel_case(&model.name), format!("findOne{}", model.name));
@@ -44,6 +44,30 @@ fn find_one_field(ctx: &mut BuilderContext, model: &ModelRef) -> Option<OutputFi
             }),
         )
         .optional()
+        .deprecate(
+            "The `findOne` query has been deprecated and replaced with `findUnique`.",
+            "2.14",
+            Some("2.15".to_owned()),
+        )
+    })
+}
+
+/// Builds a "single" query arity item field (e.g. "user", "post" ...) for given model.
+/// Find one unique semantics.
+fn find_unique_field(ctx: &mut BuilderContext, model: &ModelRef) -> Option<OutputField> {
+    arguments::where_unique_argument(ctx, model).map(|arg| {
+        let field_name = ctx.pluralize_internal(camel_case(&model.name), format!("findUnique{}", model.name));
+
+        field(
+            field_name,
+            vec![arg],
+            OutputType::object(output_objects::map_model_object_type(ctx, &model)),
+            Some(QueryInfo {
+                model: Some(Arc::clone(&model)),
+                tag: QueryTag::FindUnique,
+            }),
+        )
+        .optional()
     })
 }
 
diff --git a/query-engine/core/src/schema_builder/utils.rs b/query-engine/core/src/schema_builder/utils.rs
index c44db87324b1..089bf62fe109 100644
--- a/query-engine/core/src/schema_builder/utils.rs
+++ b/query-engine/core/src/schema_builder/utils.rs
@@ -57,6 +57,7 @@ where
         field_type: Arc::new(field_type),
         query_info,
         is_required: true,
+        deprecation: None,
     }
 }
 
@@ -71,6 +72,7 @@ where
         field_types: field_types.into(),
         default_value,
         is_required: true,
+        deprecation: None,
     }
 }
 
diff --git a/query-engine/query-engine/src/dmmf/schema/ast.rs b/query-engine/query-engine/src/dmmf/schema/ast.rs
index f92635e86f88..5b1837b3740d 100644
--- a/query-engine/query-engine/src/dmmf/schema/ast.rs
+++ b/query-engine/query-engine/src/dmmf/schema/ast.rs
@@ -1,5 +1,6 @@
 use std::collections::HashMap;
 
+use query_core::Deprecation;
 use serde::{Deserialize, Serialize};
 
 #[derive(Debug, Serialize, Deserialize, Default)]
@@ -18,6 +19,9 @@ pub struct DmmfOutputField {
     pub is_required: bool,
     pub is_nullable: bool,
     pub output_type: DmmfTypeReference,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub deprecation: Option<DmmfDeprecation>,
 }
 
 #[derive(Debug, Serialize, Deserialize)]
@@ -49,6 +53,9 @@ pub struct DmmfInputField {
     pub is_required: bool,
     pub is_nullable: bool,
     pub input_types: Vec<DmmfTypeReference>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub deprecation: Option<DmmfDeprecation>,
 }
 
 #[derive(Debug, Serialize, Deserialize)]
@@ -78,3 +85,23 @@ pub struct DmmfEnum {
     pub name: String,
     pub values: Vec<String>,
 }
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DmmfDeprecation {
+    pub since_version: String,
+    pub reason: String,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub planned_removal_version: Option<String>,
+}
+
+impl From<&Deprecation> for DmmfDeprecation {
+    fn from(deprecation: &Deprecation) -> Self {
+        Self {
+            since_version: deprecation.since_version.clone(),
+            planned_removal_version: deprecation.planned_removal_version.clone(),
+            reason: deprecation.reason.clone(),
+        }
+    }
+}
diff --git a/query-engine/query-engine/src/dmmf/schema/field_renderer.rs b/query-engine/query-engine/src/dmmf/schema/field_renderer.rs
index 25c088766aa8..f97b126be222 100644
--- a/query-engine/query-engine/src/dmmf/schema/field_renderer.rs
+++ b/query-engine/query-engine/src/dmmf/schema/field_renderer.rs
@@ -16,6 +16,7 @@ pub(super) fn render_input_field(input_field: &InputFieldRef, ctx: &mut RenderCo
         input_types: type_references,
         is_required: input_field.is_required,
         is_nullable: nullable,
+        deprecation: input_field.deprecation.as_ref().map(Into::into),
     };
 
     field
@@ -31,6 +32,7 @@ pub(super) fn render_output_field(field: &OutputFieldRef, ctx: &mut RenderContex
         output_type,
         is_required: field.is_required,
         is_nullable: !field.is_required,
+        deprecation: field.deprecation.as_ref().map(Into::into),
     };
 
     ctx.add_mapping(field.name.clone(), field.query_info.as_ref());