-
Notifications
You must be signed in to change notification settings - Fork 240
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP(schema-wasm): support schema split into multiple files (#4787)
* Implement multi-file schema handling in PSL This commit implements multi-file schema handling in the Prisma Schema Language. At a high level, instead of accepting a single string, `psl::validate_multi_file()` is an alternative to `psl::validate()` that accepts something morally equivalent to: ```json { "./prisma/schema/a.prisma": "datasource db { ... }", "./prisma/schema/nested/b.prisma": "model Test { ... }" } ``` There are tests for PSL validation with multiple schema files, but most of the rest of engines still consumes the single file version of `psl::validate()`. The implementation and the return type are shared between `psl::validate_multi_file()` and `psl::validate()`, so the change is completely transparent, other than the expectation of passing in a list of (file_name, file_contents) instead of a single string. The `psl::validate()` entry point should behave exactly the same as `psl::multi_schema()` with a single file named `schema.prisma`. In particular, it has the exact same return type. Implementation ============== This is achieved by extending `Span` to contain, in addition to a start and end offset, a `FileId`. The `FileId` is a unique identifier for a file and its parsed `SchemaAst` inside `ParserDatabase`. The identifier types for AST items in `ParserDatabase` are also extended to contain the `FileId`, so that they can be uniquely referred to in the context of the (multi-file) schema. After the analysis phase (the `parser_database` crate), consumers of the analyzed schema become multi-file aware completely transparently, no change is necessary in the other engines. The only changes that will be required at scattered points across the codebase are the `psl::validate()` call sites that will need to receive a `Vec<Box<Path>, SourceFile>` instead of a single `SourceFile`. This PR does _not_ deal with that, but it makes where these call sites are obvious by what entry points they use: `psl::validate()`, `psl::parse_schema()` and the various `*_assert_single()` methods on `ParserDatabase`. The PR contains tests confirming that schema analysis, validation and displaying diagnostics across multiple files works as expected. Status of this PR ================= This is going to be directly mergeable after review, and it will not affect the current schema handling behaviour when dealing with a single schema file. Next steps ========== - Replace all calls to `psl::validate()` with calls to `psl::validate_multi_file()`. - The `*_assert_single()` calls should be progressively replaced with their multi-file counterparts across engines. - The language server should start sending multiple files to prisma-schema-wasm in all calls. This is not in the spirit of the language server spec, but that is the most immediate solution. We'll have to make `range_to_span()` in `prisma-fmt` multi-schema aware by taking a FileId param. Links ===== Relevant issue: prisma/prisma#2377 Also see the [internal design doc](https://www.notion.so/prismaio/Multi-file-Schema-24d68fe8664048ad86252fe446caac24?d=68ef128f25974e619671a9855f65f44d#2889a038e68c4fe1ac9afe3cd34978bd). * WIP(schema-wasm): Support schema split into multiple files * Reformat support (psl crate) * Add multifile reformatting tests * Clippy * feat(prisma-fmt): addd support for mergeSchemas, expose functions to prisma-fmt-wasm * chore(prisma-fmt): removed unused function * chore: fix typo Co-authored-by: Serhii Tatarintsev <tatarintsev@prisma.io> * feat(prisma-fmt): apply validation to merge_schemas * chore(prisma-fmt): update unit test * chore: fix bad merge * chore: fix tests --------- Co-authored-by: Tom Houlé <tom@tomhoule.com> Co-authored-by: Alberto Schiabel <jkomyno@users.noreply.github.com> Co-authored-by: jkomyno <skiabo97@gmail.com>
- Loading branch information
1 parent
dcdb692
commit 87bc6b8
Showing
43 changed files
with
750 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
use psl::reformat_validated_schema_into_single; | ||
use serde::Deserialize; | ||
|
||
use crate::schema_file_input::SchemaFileInput; | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct MergeSchemasParams { | ||
schema: SchemaFileInput, | ||
} | ||
|
||
pub(crate) fn merge_schemas(params: &str) -> Result<String, String> { | ||
let params: MergeSchemasParams = match serde_json::from_str(params) { | ||
Ok(params) => params, | ||
Err(serde_err) => { | ||
panic!("Failed to deserialize MergeSchemasParams: {serde_err}"); | ||
} | ||
}; | ||
|
||
let validated_schema = crate::validate::run(params.schema, false)?; | ||
|
||
let indent_width = 2usize; | ||
let merged_schema = reformat_validated_schema_into_single(validated_schema, indent_width).unwrap(); | ||
|
||
Ok(merged_schema) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use expect_test::expect; | ||
use serde_json::json; | ||
|
||
#[test] | ||
fn merge_two_valid_schemas_succeeds() { | ||
let schema = vec![ | ||
( | ||
"b.prisma", | ||
r#" | ||
model B { | ||
id String @id | ||
a A? | ||
} | ||
"#, | ||
), | ||
( | ||
"a.prisma", | ||
r#" | ||
datasource db { | ||
provider = "postgresql" | ||
url = env("DBURL") | ||
} | ||
model A { | ||
id String @id | ||
b_id String @unique | ||
b B @relation(fields: [b_id], references: [id]) | ||
} | ||
"#, | ||
), | ||
]; | ||
|
||
let request = json!({ | ||
"schema": schema, | ||
}); | ||
|
||
let expected = expect![[r#" | ||
model B { | ||
id String @id | ||
a A? | ||
} | ||
datasource db { | ||
provider = "postgresql" | ||
url = env("DBURL") | ||
} | ||
model A { | ||
id String @id | ||
b_id String @unique | ||
b B @relation(fields: [b_id], references: [id]) | ||
} | ||
"#]]; | ||
|
||
let response = merge_schemas(&request.to_string()).unwrap(); | ||
expected.assert_eq(&response); | ||
} | ||
|
||
#[test] | ||
fn merge_two_invalid_schemas_panics() { | ||
let schema = vec![ | ||
( | ||
"b.prisma", | ||
r#" | ||
model B { | ||
id String @id | ||
a A? | ||
} | ||
"#, | ||
), | ||
( | ||
"a.prisma", | ||
r#" | ||
datasource db { | ||
provider = "postgresql" | ||
url = env("DBURL") | ||
} | ||
model A { | ||
id String @id | ||
b_id String @unique | ||
} | ||
"#, | ||
), | ||
]; | ||
|
||
let request = json!({ | ||
"schema": schema, | ||
}); | ||
|
||
let expected = expect![[ | ||
r#"{"error_code":"P1012","message":"\u001b[1;91merror\u001b[0m: \u001b[1mError validating field `a` in model `B`: The relation field `a` on model `B` is missing an opposite relation field on the model `A`. Either run `prisma format` or add it manually.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mb.prisma:4\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 3 | \u001b[0m id String @id\n\u001b[1;94m 4 | \u001b[0m \u001b[1;91ma A?\u001b[0m\n\u001b[1;94m 5 | \u001b[0m }\n\u001b[1;94m | \u001b[0m\n\nValidation Error Count: 1"}"# | ||
]]; | ||
|
||
let response = merge_schemas(&request.to_string()).unwrap_err(); | ||
expected.assert_eq(&response); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
use psl::SourceFile; | ||
use serde::Deserialize; | ||
|
||
/// Struct for supporting multiple files | ||
/// in a backward-compatible way: can either accept | ||
/// a single file contents or vector of (filePath, content) tuples. | ||
/// Can be converted to the input for `psl::validate_multi_file` from | ||
/// any of the variants. | ||
#[derive(Deserialize, Debug)] | ||
#[serde(untagged)] | ||
pub(crate) enum SchemaFileInput { | ||
Single(String), | ||
Multiple(Vec<(String, String)>), | ||
} | ||
|
||
impl From<SchemaFileInput> for Vec<(String, SourceFile)> { | ||
fn from(value: SchemaFileInput) -> Self { | ||
match value { | ||
SchemaFileInput::Single(content) => vec![("schema.prisma".to_owned(), content.into())], | ||
SchemaFileInput::Multiple(file_list) => file_list | ||
.into_iter() | ||
.map(|(filename, content)| (filename, content.into())) | ||
.collect(), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.