Skip to content

Commit 0cae7ae

Browse files
authored
fix: support where argument on field-level @unique for partial indexes (#5774)
## fix: support `where` argument on field-level `@unique` for partial indexes ### Summary - Add `where` clause parsing to field-level `@unique`, fixing "No such argument" validation error when `db pull` generates `@unique(where: raw("..."))` for single-column partial unique indexes. ### Changes - `psl/parser-database/src/attributes.rs`: Call `parse_where_clause()` in `visit_field_unique`, matching the existing behavior in `model_unique` / `model_index`. - `psl/psl/tests/attributes/partial_index.rs`: 4 new tests covering raw syntax, object syntax, missing preview feature error, and `map` + `where` combination. ### Test plan - [x] All existing partial index tests pass (52 total) - [x] `make test-unit` passes - [x] Verified with reproduction repo from prisma/prisma#29172. `prisma generate` succeeds after fix Closes #5773
1 parent 6f4d6f1 commit 0cae7ae

2 files changed

Lines changed: 82 additions & 2 deletions

File tree

psl/parser-database/src/attributes.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ fn visit_scalar_field_attributes(
271271

272272
// @unique
273273
if ctx.visit_optional_single_attr("unique") {
274-
visit_field_unique(scalar_field_id, model_data, ctx);
274+
visit_field_unique(scalar_field_id, model_id, model_data, ctx);
275275
ctx.validate_visited_arguments();
276276
}
277277

@@ -284,7 +284,12 @@ fn visit_scalar_field_attributes(
284284
ctx.validate_visited_attributes();
285285
}
286286

287-
fn visit_field_unique(scalar_field_id: ScalarFieldId, model_data: &mut ModelAttributes, ctx: &mut Context<'_>) {
287+
fn visit_field_unique(
288+
scalar_field_id: ScalarFieldId,
289+
model_id: crate::ModelId,
290+
model_data: &mut ModelAttributes,
291+
ctx: &mut Context<'_>,
292+
) {
288293
let mapped_name = match ctx
289294
.visit_optional_arg("map")
290295
.and_then(|arg| coerce::string(arg, ctx.diagnostics))
@@ -318,6 +323,7 @@ fn visit_field_unique(scalar_field_id: ScalarFieldId, model_data: &mut ModelAttr
318323
};
319324

320325
let clustered = validate_clustering_setting(ctx);
326+
let where_clause = parse_where_clause(model_id, ctx);
321327

322328
let attribute_id = ctx.current_attribute_id();
323329
model_data.ast_indexes.push((
@@ -333,6 +339,7 @@ fn visit_field_unique(scalar_field_id: ScalarFieldId, model_data: &mut ModelAttr
333339
source_field: Some(scalar_field_id),
334340
mapped_name,
335341
clustered,
342+
where_clause,
336343
..Default::default()
337344
},
338345
))

psl/psl/tests/attributes/partial_index.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,3 +1030,76 @@ fn partial_index_object_syntax_rejects_boolean_for_enum_field() {
10301030
"#]];
10311031
expected.assert_eq(&err);
10321032
}
1033+
1034+
#[test]
1035+
fn field_level_partial_unique_with_raw_on_postgres() {
1036+
let dml = indoc! {r#"
1037+
model User {
1038+
id Int @id
1039+
email String @unique(where: raw("status = 'active'"))
1040+
status String
1041+
}
1042+
"#};
1043+
1044+
psl::parse_schema_without_extensions(with_header(dml, Provider::Postgres, &["partialIndexes"]))
1045+
.unwrap()
1046+
.assert_has_model("User")
1047+
.assert_unique_on_fields(&["email"])
1048+
.assert_raw_where_clause("status = 'active'");
1049+
}
1050+
1051+
#[test]
1052+
fn field_level_partial_unique_with_object_syntax() {
1053+
let dml = indoc! {r#"
1054+
model User {
1055+
id Int @id
1056+
email String @unique(where: { active: true })
1057+
active Boolean
1058+
}
1059+
"#};
1060+
1061+
psl::parse_schema_without_extensions(with_header(dml, Provider::Postgres, &["partialIndexes"]))
1062+
.unwrap()
1063+
.assert_has_model("User")
1064+
.assert_unique_on_fields(&["email"])
1065+
.assert_where_object(&[("active", WhereCondition::Equals(WhereValue::Boolean(true)))]);
1066+
}
1067+
1068+
#[test]
1069+
fn field_level_partial_unique_requires_preview_feature() {
1070+
let dml = indoc! {r#"
1071+
model User {
1072+
id Int @id
1073+
email String @unique(where: raw("status = 'active'"))
1074+
status String
1075+
}
1076+
"#};
1077+
1078+
let err = parse_unwrap_err(&with_header(dml, Provider::Postgres, &[]));
1079+
let expected = expect![[r#"
1080+
error: Error parsing attribute "@unique": Partial indexes are a preview feature. Add "partialIndexes" to previewFeatures in your generator block.
1081+
--> schema.prisma:12
1082+
 | 
1083+
11 |  id Int @id
1084+
12 |  email String @unique(where: raw("status = 'active'"))
1085+
 | 
1086+
"#]];
1087+
expected.assert_eq(&err);
1088+
}
1089+
1090+
#[test]
1091+
fn field_level_partial_unique_with_map() {
1092+
let dml = indoc! {r#"
1093+
model User {
1094+
id Int @id
1095+
email String @unique(map: "email_active_idx", where: raw("status = 'active'"))
1096+
status String
1097+
}
1098+
"#};
1099+
1100+
psl::parse_schema_without_extensions(with_header(dml, Provider::Postgres, &["partialIndexes"]))
1101+
.unwrap()
1102+
.assert_has_model("User")
1103+
.assert_unique_on_fields(&["email"])
1104+
.assert_raw_where_clause("status = 'active'");
1105+
}

0 commit comments

Comments
 (0)