Skip to content
Open
8 changes: 6 additions & 2 deletions psl/parser-database/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,15 @@ fn resolve_enum_attributes<'db>(enum_id: crate::EnumId, ast_enum: &'db ast::Enum
}
ctx.validate_visited_arguments();
}

// @ignore
if ctx.visit_optional_single_attr("ignore") {
enum_attributes.ignored_values.insert(value_id);
ctx.validate_visited_arguments();
}
ctx.validate_visited_attributes();
}

// Now validate the enum attributes.

ctx.visit_attributes(enum_id);

// @@map
Expand Down
2 changes: 2 additions & 0 deletions psl/parser-database/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,8 @@ pub(super) struct EnumAttributes {
pub(super) mapped_name: Option<StringId>,
/// @map on enum values.
pub(super) mapped_values: HashMap<EnumValueId, StringId>,
/// @ignore on enum values.
pub(super) ignored_values: std::collections::HashSet<EnumValueId>,
Comment on lines +741 to +742
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using FxHashSet for consistency and performance.

The file uses rustc_hash::FxHashMap (aliased as HashMap) for other collections like mapped_values, but ignored_values uses std::collections::HashSet. For small integer keys like EnumValueId, FxHashSet provides better performance and maintains consistency with the rest of the codebase.

♻️ Suggested change
+use rustc_hash::FxHashSet;
 // ... in EnumAttributes struct:
     /// `@ignore` on enum values.
-    pub(super) ignored_values: std::collections::HashSet<EnumValueId>,
+    pub(super) ignored_values: FxHashSet<EnumValueId>,

Note: You'll need to add FxHashSet to the imports at the top of the file alongside FxHashMap.

/// ```ignore
/// @@schema("public")
/// ^^^^^^^^
Expand Down
7 changes: 7 additions & 0 deletions psl/parser-database/src/walkers/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,11 @@ impl<'db> EnumValueWalker<'db> {
.get(&(self.id.1))
.map(|id| &self.db[*id])
}

/// True if the enum value is ignored.
pub fn is_ignored(self) -> bool {
self.db.types.enum_attributes[&self.id.0]
.ignored_values
.contains(&self.id.1)
}
}
1 change: 1 addition & 0 deletions psl/psl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ all = ["postgresql", "sqlite", "mysql", "cockroachdb", "mssql", "mongodb"]
psl-core.workspace = true

[dev-dependencies]
psl-core = { workspace = true, features = ["postgresql", "sqlite", "mysql", "cockroachdb", "mssql", "mongodb"] }
base64.workspace = true
dissimilar.workspace = true
expect-test.workspace = true
Expand Down
18 changes: 18 additions & 0 deletions psl/psl/tests/attributes/ignore_positive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,21 @@ fn allow_ignore_on_relation_fields_on_valid_models() {
.assert_has_relation_field("rel_e")
.assert_ignored(true);
}

#[test]
fn allow_ignore_on_enum_values() {
let dml = r#"
enum SupportedCarTypes {
COUPE @ignore
SEDAN
VAN
}
"#;

let datamodel = parse_schema(dml);
let enum_walker = datamodel.assert_has_enum("SupportedCarTypes");

enum_walker.assert_has_value("COUPE").assert_ignored(true);
enum_walker.assert_has_value("SEDAN").assert_ignored(false);
enum_walker.assert_has_value("VAN").assert_ignored(false);
}
33 changes: 33 additions & 0 deletions psl/psl/tests/common/asserts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use psl::schema_ast::ast::{self, FieldArity};
pub(crate) trait DatamodelAssert<'a> {
fn assert_has_model(&'a self, name: &str) -> walkers::ModelWalker<'a>;
fn assert_has_type(&'a self, name: &str) -> walkers::CompositeTypeWalker<'a>;
fn assert_has_enum(&'a self, name: &str) -> walkers::EnumWalker<'a>;
}

pub(crate) trait WarningAsserts {
Expand Down Expand Up @@ -139,6 +140,14 @@ impl<'a> DatamodelAssert<'a> for psl::ValidatedSchema {
.find(|m| m.name() == name)
.expect("Type {name} not found")
}

#[track_caller]
fn assert_has_enum(&'a self, name: &str) -> walkers::EnumWalker<'a> {
self.db
.walk_enums()
.find(|e| e.name() == name)
.expect("Enum {name} not found")
}
Comment on lines +143 to +150
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the expect() message formatting.

The error message uses {name} as a literal string rather than interpolating the variable. This should use a format string.

🐛 Proposed fix
     #[track_caller]
     fn assert_has_enum(&'a self, name: &str) -> walkers::EnumWalker<'a> {
         self.db
             .walk_enums()
             .find(|e| e.name() == name)
-            .expect("Enum {name} not found")
+            .unwrap_or_else(|| panic!("Enum {name} not found"))
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[track_caller]
fn assert_has_enum(&'a self, name: &str) -> walkers::EnumWalker<'a> {
self.db
.walk_enums()
.find(|e| e.name() == name)
.expect("Enum {name} not found")
}
#[track_caller]
fn assert_has_enum(&'a self, name: &str) -> walkers::EnumWalker<'a> {
self.db
.walk_enums()
.find(|e| e.name() == name)
.unwrap_or_else(|| panic!("Enum {} not found", name))
}

}

impl RelationFieldAssert for walkers::RelationFieldWalker<'_> {
Expand Down Expand Up @@ -792,3 +801,27 @@ impl IndexAssert for walkers::PrimaryKeyWalker<'_> {
self
}
}
pub(crate) trait EnumAssert<'a> {
fn assert_has_value(&'a self, name: &str) -> walkers::EnumValueWalker<'a>;
}

impl<'a> EnumAssert<'a> for walkers::EnumWalker<'a> {
#[track_caller]
fn assert_has_value(&'a self, name: &str) -> walkers::EnumValueWalker<'a> {
self.values()
.find(|v| v.name() == name)
.expect("Enum value {name} not found")
}
}
Comment on lines +804 to +815
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the expect() message formatting.

Same issue as assert_has_enum - the error message doesn't interpolate {name}.

🐛 Proposed fix
     #[track_caller]
     fn assert_has_value(&'a self, name: &str) -> walkers::EnumValueWalker<'a> {
         self.values()
             .find(|v| v.name() == name)
-            .expect("Enum value {name} not found")
+            .unwrap_or_else(|| panic!("Enum value {name} not found"))
     }


pub(crate) trait EnumValueAssert {
fn assert_ignored(&self, ignored: bool) -> &Self;
}

impl EnumValueAssert for walkers::EnumValueWalker<'_> {
#[track_caller]
fn assert_ignored(&self, ignored: bool) -> &Self {
assert_eq!(self.is_ignored(), ignored);
self
}
}
17 changes: 5 additions & 12 deletions query-compiler/core/src/query_document/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,18 @@ impl QueryDocumentParser {
.iter()
.filter_map(|input_field| {
// Match schema argument field to an argument field in the incoming document.
let selection_arg = given_arguments
let selection_arg: Option<(String, ArgumentValue)> = given_arguments
.iter()
.find(|given_argument| given_argument.0 == input_field.name)
.map(|(_, value)| value)
.cloned();

let argument_path = argument_path.add(input_field.name.clone().into_owned());

let validate_other_required_args = || {
let validate_other_required_args = |name: &str| {
for req_name in input_field.requires_other_fields() {
if !given_arguments.iter().any(|(name, _)| name == req_name) {
let Some(req_field) = schema_field.arguments().iter().find(|f| f.name == *req_name) else {
panic!("argument {} requires unknown argument {req_name}", input_field.name)
panic!("argument {name} requires unknown argument {req_name}")
};
return Err(ValidationError::conditionally_required_argument_missing(
&selection_path.segments(),
Expand All @@ -210,14 +209,8 @@ impl QueryDocumentParser {
// If optional and not present ignore the field.
// If present or has a default, process the value.
// If not present but required, throw a validation error.
match selection_arg.or_else(|| {
input_field
.default_value
.as_ref()
.and_then(DefaultKind::get)
.map(ArgumentValue::from)
}) {
Some(value) => Some(validate_other_required_args().and_then(|_| {
match selection_arg {
Some((name, value)) => Some(validate_other_required_args(&name).and_then(|_| {
self.parse_input_value(
selection_path.clone(),
argument_path,
Expand Down
3 changes: 3 additions & 0 deletions query-compiler/dmmf/src/ast_builders/datamodel_ast_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ fn enum_to_dmmf(en: walkers::EnumWalker<'_>) -> Enum {
};

for enum_value in en.values() {
if enum_value.is_ignored() {
continue;
}
enm.values.push(enum_value_to_dmmf(enum_value));
}
Comment on lines 50 to 55
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check for test coverage of ignored enum values in DMMF rendering

# Look for `@ignore` on enum values in test files
echo "=== Checking DMMF test files for `@ignore` on enum values ==="
fd -e prisma . query-compiler/dmmf/test_files --exec grep -l '@ignore' {} \; 2>/dev/null || echo "No `@ignore` found in DMMF test files"

# Show any matches in detail
echo ""
echo "=== Detailed matches ==="
rg -n '@ignore' query-compiler/dmmf/test_files/ 2>/dev/null || echo "No matches"

# Also check for enum-related ignore tests in PSL tests
echo ""
echo "=== PSL ignore tests for enum values ==="
rg -n -A5 'ignore.*enum|enum.*ignore' psl/psl/tests/attributes/ --type rust 2>/dev/null | head -50

Repository: prisma/prisma-engines

Length of output: 1202


Consider using .filter() for consistency with existing patterns.

The change correctly excludes ignored enum values from DMMF output. However, other filtering operations in this file use the .filter() iterator method (e.g., lines 28-29, 75, 141), whereas this uses an if/continue pattern.

♻️ Suggested refactor using `.filter()`
-    for enum_value in en.values() {
-        if enum_value.is_ignored() {
-            continue;
-        }
+    for enum_value in en.values().filter(|v| !v.is_ignored()) {
         enm.values.push(enum_value_to_dmmf(enum_value));
     }

Alternatively, restructure the entire function to match the more declarative style used elsewhere:

fn enum_to_dmmf(en: walkers::EnumWalker<'_>) -> Enum {
    Enum {
        name: en.name().to_owned(),
        values: en
            .values()
            .filter(|v| !v.is_ignored())
            .map(enum_value_to_dmmf)
            .collect(),
        db_name: en.mapped_name().map(ToOwned::to_owned),
        documentation: en.ast_enum().documentation().map(ToOwned::to_owned),
    }
}

Test coverage exists in PSL tests (psl/psl/tests/attributes/ignore_positive.rs::allow_ignore_on_enum_values) validating that enum values with @ignore are handled correctly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for enum_value in en.values() {
if enum_value.is_ignored() {
continue;
}
enm.values.push(enum_value_to_dmmf(enum_value));
}
for enum_value in en.values().filter(|v| !v.is_ignored()) {
enm.values.push(enum_value_to_dmmf(enum_value));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@query-compiler/dmmf/src/ast_builders/datamodel_ast_builder.rs` around lines
50 - 55, Refactor the enum value loop in enum_to_dmmf to use iterator adapters:
replace the for/if/continue pattern over en.values() with en.values().filter(|v|
!v.is_ignored()).map(enum_value_to_dmmf).collect() so Enum.values is built
declaratively; update the Enum construction in enum_to_dmmf to assign values via
that filtered map and ensure db_name and documentation are preserved as before.


Expand Down
19 changes: 19 additions & 0 deletions schema-engine/datamodel-renderer/src/datamodel/enumerator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub struct EnumVariant<'a> {
comment_out: bool,
map: Option<FieldAttribute<'a>>,
documentation: Option<Documentation<'a>>,
ignore: Option<FieldAttribute<'a>>,
}

impl<'a> EnumVariant<'a> {
Expand All @@ -26,6 +27,7 @@ impl<'a> EnumVariant<'a> {
comment_out: false,
map: None,
documentation: None,
ignore: None,
}
}

Expand Down Expand Up @@ -55,6 +57,18 @@ impl<'a> EnumVariant<'a> {
self.comment_out = true;
}

/// Ignores the variant.
///
/// ```ignore
/// enum Foo {
/// Bar @ignore
/// ^^^^^ this
/// }
/// ```
pub fn ignore(&mut self) {
self.ignore = Some(FieldAttribute::new(Function::new("ignore")));
}

/// Documentation of a variant.
///
/// ```ignore
Expand Down Expand Up @@ -91,6 +105,11 @@ impl fmt::Display for EnumVariant<'_> {
map.fmt(f)?;
}

if let Some(ref ignore) = self.ignore {
f.write_str(" ")?;
ignore.fmt(f)?;
}

Ok(())
}
}
Expand Down