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(schema-engine): add support for multiple split schema files #4809

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

jkomyno
Copy link
Contributor

@jkomyno jkomyno commented Apr 4, 2024

WIP

This PR will close https://github.com/prisma/team-orm/issues/1039, and is based on #4243.

tomhoule and others added 8 commits March 21, 2024 13:58
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).
@jkomyno jkomyno added this to the 5.13.0 milestone Apr 4, 2024
@jkomyno jkomyno self-assigned this Apr 4, 2024
@jkomyno jkomyno changed the title Feat/migrate multi file feat(schema-engine): add support for multiple split schema files Apr 4, 2024
@jkomyno jkomyno changed the base branch from main to psl-multi-file-schema April 4, 2024 12:14
Copy link
Contributor

github-actions bot commented Apr 4, 2024

WASM Query Engine file Size

Engine This PR Base branch Diff
Postgres 2.141MiB 2.141MiB 0.000B
Postgres (gzip) 842.649KiB 842.649KiB 0.000B
Mysql 2.110MiB 2.110MiB 0.000B
Mysql (gzip) 829.012KiB 829.013KiB -1.000B
Sqlite 2.003MiB 2.003MiB 0.000B
Sqlite (gzip) 789.710KiB 789.712KiB -2.000B

Copy link

codspeed-hq bot commented Apr 4, 2024

CodSpeed Performance Report

Merging #4809 will not alter performance

Comparing feat/migrate-multi-file (75e398c) with main (133a47f)

Summary

✅ 11 untouched benchmarks

Copy link
Contributor

github-actions bot commented Apr 4, 2024

✅ WASM query-engine performance won't change substantially (1.002x)

Full benchmark report
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/bench?schema=imdb_bench&sslmode=disable" \
node --experimental-wasm-modules query-engine/driver-adapters/executor/dist/bench.mjs
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
cpu: AMD EPYC 7763 64-Core Processor
runtime: node v18.20.2 (x64-linux)

benchmark                   time (avg)             (min … max)       p75       p99      p999
-------------------------------------------------------------- -----------------------------
• movies.findMany() (all - ~50K)
-------------------------------------------------------------- -----------------------------
Web Assembly: Baseline     374 ms/iter       (368 ms … 376 ms)    375 ms    376 ms    376 ms
Web Assembly: Latest       457 ms/iter       (456 ms … 461 ms)    460 ms    461 ms    461 ms
Web Assembly: Current      460 ms/iter       (458 ms … 462 ms)    461 ms    462 ms    462 ms
Node API: Current          199 ms/iter       (195 ms … 202 ms)    200 ms    202 ms    202 ms

summary for movies.findMany() (all - ~50K)
  Web Assembly: Current
   2.32x slower than Node API: Current
   1.23x slower than Web Assembly: Baseline
   1.01x slower than Web Assembly: Latest

• movies.findMany({ take: 2000 })
-------------------------------------------------------------- -----------------------------
Web Assembly: Baseline  15'033 µs/iter (14'709 µs … 15'801 µs) 15'098 µs 15'801 µs 15'801 µs
Web Assembly: Latest    18'839 µs/iter (18'487 µs … 21'548 µs) 18'936 µs 21'548 µs 21'548 µs
Web Assembly: Current   19'163 µs/iter (18'325 µs … 24'341 µs) 18'875 µs 24'341 µs 24'341 µs
Node API: Current        8'069 µs/iter   (7'929 µs … 8'385 µs)  8'104 µs  8'385 µs  8'385 µs

summary for movies.findMany({ take: 2000 })
  Web Assembly: Current
   2.37x slower than Node API: Current
   1.27x slower than Web Assembly: Baseline
   1.02x slower than Web Assembly: Latest

• movies.findMany({ where: {...}, take: 2000 })
-------------------------------------------------------------- -----------------------------
Web Assembly: Baseline   2'332 µs/iter   (2'204 µs … 3'532 µs)  2'321 µs  3'404 µs  3'532 µs
Web Assembly: Latest     2'879 µs/iter   (2'760 µs … 3'679 µs)  2'859 µs  3'580 µs  3'679 µs
Web Assembly: Current    2'905 µs/iter   (2'779 µs … 3'991 µs)  2'887 µs  3'773 µs  3'991 µs
Node API: Current        1'458 µs/iter   (1'301 µs … 2'504 µs)  1'439 µs  2'115 µs  2'504 µs

summary for movies.findMany({ where: {...}, take: 2000 })
  Web Assembly: Current
   1.99x slower than Node API: Current
   1.25x slower than Web Assembly: Baseline
   1.01x slower than Web Assembly: Latest

• movies.findMany({ include: { cast: true } take: 2000 }) (m2m)
-------------------------------------------------------------- -----------------------------
Web Assembly: Baseline     573 ms/iter       (566 ms … 591 ms)    575 ms    591 ms    591 ms
Web Assembly: Latest       787 ms/iter       (776 ms … 803 ms)    802 ms    803 ms    803 ms
Web Assembly: Current      783 ms/iter       (776 ms … 798 ms)    797 ms    798 ms    798 ms
Node API: Current          480 ms/iter       (472 ms … 491 ms)    487 ms    491 ms    491 ms

summary for movies.findMany({ include: { cast: true } take: 2000 }) (m2m)
  Web Assembly: Current
   1.63x slower than Node API: Current
   1.37x slower than Web Assembly: Baseline
   1.01x faster than Web Assembly: Latest

• movies.findMany({ where: {...}, include: { cast: true } take: 2000 }) (m2m)
-------------------------------------------------------------- -----------------------------
Web Assembly: Baseline  79'100 µs/iter (78'711 µs … 79'752 µs) 79'471 µs 79'752 µs 79'752 µs
Web Assembly: Latest       111 ms/iter       (110 ms … 112 ms)    112 ms    112 ms    112 ms
Web Assembly: Current      111 ms/iter       (111 ms … 112 ms)    112 ms    112 ms    112 ms
Node API: Current       62'657 µs/iter (61'204 µs … 63'460 µs) 63'376 µs 63'460 µs 63'460 µs

summary for movies.findMany({ where: {...}, include: { cast: true } take: 2000 }) (m2m)
  Web Assembly: Current
   1.77x slower than Node API: Current
   1.4x slower than Web Assembly: Baseline
   1x faster than Web Assembly: Latest

• movies.findMany({ take: 2000, include: { cast: { include: { person: true } } } })
-------------------------------------------------------------- -----------------------------
Web Assembly: Baseline   1'008 ms/iter   (1'004 ms … 1'023 ms)  1'011 ms  1'023 ms  1'023 ms
Web Assembly: Latest     1'305 ms/iter   (1'295 ms … 1'323 ms)  1'320 ms  1'323 ms  1'323 ms
Web Assembly: Current    1'307 ms/iter   (1'295 ms … 1'333 ms)  1'328 ms  1'333 ms  1'333 ms
Node API: Current          916 ms/iter       (892 ms … 940 ms)    939 ms    940 ms    940 ms

summary for movies.findMany({ take: 2000, include: { cast: { include: { person: true } } } })
  Web Assembly: Current
   1.43x slower than Node API: Current
   1.3x slower than Web Assembly: Baseline
   1x faster than Web Assembly: Latest

• movie.findMany({ where: { ... }, take: 2000, include: { cast: { include: { person: true } } } })
-------------------------------------------------------------- -----------------------------
Web Assembly: Baseline     142 ms/iter       (141 ms … 143 ms)    143 ms    143 ms    143 ms
Web Assembly: Latest       189 ms/iter       (187 ms … 190 ms)    189 ms    190 ms    190 ms
Web Assembly: Current      187 ms/iter       (186 ms … 189 ms)    188 ms    189 ms    189 ms
Node API: Current          115 ms/iter       (114 ms … 118 ms)    117 ms    118 ms    118 ms

summary for movie.findMany({ where: { ... }, take: 2000, include: { cast: { include: { person: true } } } })
  Web Assembly: Current
   1.63x slower than Node API: Current
   1.31x slower than Web Assembly: Baseline
   1.01x faster than Web Assembly: Latest

• movie.findMany({ where: { reviews: { author: { ... } }, take: 100 }) (to-many -> to-one)
-------------------------------------------------------------- -----------------------------
Web Assembly: Baseline   1'030 µs/iter     (976 µs … 1'696 µs)  1'033 µs  1'491 µs  1'696 µs
Web Assembly: Latest     1'400 µs/iter   (1'300 µs … 2'365 µs)  1'383 µs  2'235 µs  2'365 µs
Web Assembly: Current    1'389 µs/iter   (1'309 µs … 2'105 µs)  1'385 µs  1'869 µs  2'105 µs
Node API: Current          790 µs/iter     (755 µs … 1'300 µs)    800 µs    943 µs  1'300 µs

summary for movie.findMany({ where: { reviews: { author: { ... } }, take: 100 }) (to-many -> to-one)
  Web Assembly: Current
   1.76x slower than Node API: Current
   1.35x slower than Web Assembly: Baseline
   1.01x faster than Web Assembly: Latest

• movie.findMany({ where: { cast: { person: { ... } }, take: 100 }) (m2m -> to-one)
-------------------------------------------------------------- -----------------------------
Web Assembly: Baseline   1'029 µs/iter     (978 µs … 1'936 µs)  1'027 µs  1'533 µs  1'936 µs
Web Assembly: Latest     1'369 µs/iter   (1'303 µs … 2'199 µs)  1'368 µs  2'030 µs  2'199 µs
Web Assembly: Current    1'384 µs/iter   (1'317 µs … 2'075 µs)  1'386 µs  1'784 µs  2'075 µs
Node API: Current          804 µs/iter     (722 µs … 1'134 µs)    817 µs    957 µs  1'134 µs

summary for movie.findMany({ where: { cast: { person: { ... } }, take: 100 }) (m2m -> to-one)
  Web Assembly: Current
   1.72x slower than Node API: Current
   1.35x slower than Web Assembly: Baseline
   1.01x slower than Web Assembly: Latest

After changes in 75e398c

@jkomyno jkomyno changed the base branch from psl-multi-file-schema to feat/multi-schema-fmt April 5, 2024 10:00
Base automatically changed from feat/multi-schema-fmt to main April 8, 2024 12:20
@apolanc apolanc removed this from the 5.13.0 milestone Apr 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants