Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fmt): add quick-fix for missing indexes when relationMode = "prisma" #3431

Merged
merged 9 commits into from
Nov 28, 2022
35 changes: 24 additions & 11 deletions prisma-fmt/src/code_actions.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
mod relations;

use lsp_types::{CodeActionOrCommand, CodeActionParams, Diagnostic};
use psl::{
parser_database::{ast, walkers::RefinedRelationWalker, ParserDatabase, SourceFile},
Diagnostics,
};
use psl::parser_database::{ast, walkers::RefinedRelationWalker, SourceFile};
use std::sync::Arc;

pub(crate) fn empty_code_actions() -> Vec<CodeActionOrCommand> {
Expand All @@ -16,22 +13,38 @@ pub(crate) fn available_actions(schema: String, params: CodeActionParams) -> Vec

let file = SourceFile::new_allocated(Arc::from(schema.into_boxed_str()));

let db = {
let mut diag = Diagnostics::new();
ParserDatabase::new(file.clone(), &mut diag)
};
let validated_schema = psl::validate(file);

for relation in db.walk_relations() {
for relation in validated_schema.db.walk_relations() {
if let RefinedRelationWalker::Inline(relation) = relation.refine() {
let complete_relation = match relation.as_complete() {
Some(relation) => relation,
None => continue,
};

relations::add_referenced_side_unique(&mut actions, &params, file.as_str(), complete_relation);
relations::add_referenced_side_unique(
&mut actions,
&params,
validated_schema.db.source(),
complete_relation,
);

if relation.is_one_to_one() {
relations::add_referencing_side_unique(&mut actions, &params, file.as_str(), complete_relation);
relations::add_referencing_side_unique(
&mut actions,
&params,
validated_schema.db.source(),
complete_relation,
);
}

if validated_schema.relation_mode().is_prisma() {
relations::add_reference_index(
&mut actions,
&params,
validated_schema.db.source(),
complete_relation.referencing_field(),
);
}
}
}
Expand Down
99 changes: 98 additions & 1 deletion prisma-fmt/src/code_actions/relations.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use lsp_types::{CodeAction, CodeActionKind, CodeActionOrCommand, CodeActionParams, Range, TextEdit, WorkspaceEdit};
use psl::parser_database::{
ast::WithSpan,
walkers::{CompleteInlineRelationWalker, ModelWalker, ScalarFieldWalker},
walkers::{CompleteInlineRelationWalker, ModelWalker, RelationFieldWalker, ScalarFieldWalker},
};
use std::collections::HashMap;

use crate::offset_to_position;

/// If the referencing side of the one-to-one relation does not point
/// to a unique constraint, the action adds the attribute.
///
Expand Down Expand Up @@ -221,3 +223,98 @@ fn create_missing_unique<'a>(

TextEdit { range, new_text }
}

/// For schema's with emulated relations,
/// If the referenced side of the relation does not point to a unique
/// constraint, the action adds the attribute.
///
/// If referencing a single field:
///
/// ```ignore
/// model A {
/// id Int @id
/// field1 B @relation(fields: [bId], references: [id])
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // Warn
/// bId Int
///
/// // <- suggest @@index([bId]) here
/// }
///
/// model B {
/// id Int @id
/// as A[]
/// }
/// ```
///
/// If referencing multiple fields:
///
/// ```ignore
/// model A {
/// id Int @id
/// field1 B @relation(fields: [bId1, bId2, bId3], references: [id1, id2, id3])
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // Warn
/// bId1 Int
/// bId2 Int
/// bId3 Int
///
/// // <- suggest @@index([bId1, bId2, bId3]) here
/// }
///
/// model B {
/// id1 Int
/// id2 Int
/// id3 Int
/// as A[]
///
/// @@id([id1, id2, id3])
/// }
/// ```
pub(super) fn add_reference_index(
Druue marked this conversation as resolved.
Show resolved Hide resolved
actions: &mut Vec<CodeActionOrCommand>,
params: &CodeActionParams,
schema: &str,
relation: RelationFieldWalker<'_>,
) {
let Some(fields) = relation.fields() else { return; };
if relation.model().indexes().any(|index| {
index
.fields()
.zip(fields.clone())
.all(|(index_field, relation_field)| index_field.field_id() == relation_field.field_id())
}) {
return;
}

let model_end = Range {
start: offset_to_position(relation.model().ast_model().span().end, schema).unwrap(),
end: offset_to_position(relation.model().ast_model().span().end, schema).unwrap(),
};

let text = TextEdit {
range: model_end,
Druue marked this conversation as resolved.
Show resolved Hide resolved
new_text: String::from("new_text"),
};
let mut changes = HashMap::new();
changes.insert(params.text_document.uri.clone(), vec![text]);

let edit = WorkspaceEdit {
changes: Some(changes),
..Default::default()
};

let diagnostics = super::diagnostics_for_span(
schema,
&params.context.diagnostics,
relation.relation_attribute().unwrap().span(),
);

Druue marked this conversation as resolved.
Show resolved Hide resolved
let action = CodeAction {
title: String::from("Index relations"),
Druue marked this conversation as resolved.
Show resolved Hide resolved
kind: Some(CodeActionKind::QUICKFIX),
edit: Some(edit),
diagnostics,
..Default::default()
};

actions.push(CodeActionOrCommand::CodeAction(action))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Druue marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}

model A {
id String @id @map("_id")
field1 B @relation(fields: [bId], references: [id])

bId String
}

model B {
id String @id @map("_id")
as A[]
}
1 change: 1 addition & 0 deletions prisma-fmt/tests/code_actions/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ scenarios! {
one_to_one_referencing_side_misses_unique_single_field
one_to_one_referencing_side_misses_unique_compound_field
one_to_one_referencing_side_misses_unique_compound_field_indentation_four_spaces
relation_mode_prisma_missing_index
}
2 changes: 1 addition & 1 deletion psl/parser-database/src/walkers/relation_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ impl<'db> RelationFieldWalker<'db> {
}

/// The fields in the `fields: [...]` argument in the forward relation field.
pub fn fields(self) -> Option<impl ExactSizeIterator<Item = ScalarFieldWalker<'db>>> {
pub fn fields(self) -> Option<impl ExactSizeIterator<Item = ScalarFieldWalker<'db>> + Clone> {
let model_id = self.model_id;
let attributes = self.attributes();
attributes.fields.as_ref().map(move |fields| {
Expand Down