Loading the parity record
Fetching the measured run — the pinned upstream oracle, every symbol and every case.
Fetching the measured run — the pinned upstream oracle, every symbol and every case.
Parity / Prisma
Every number on this page was produced by running both implementations over the same cases: the real prisma package pinned at 5.22.0 answers first, and its answer is the expectation the Go port is held to. Nothing is a hand-written expectation, so a new upstream release re-scores the port on its own. See Prisma for the port's own documentation. Source: github.com/malcolmston/prisma.
Not a generic diagram: every node below names an artefact of this harness — the pinned package it installed, the runner files it started, the case files it streamed, and the counts it wrote out.
| Case group | Cases | Match | Mismatch | Group parity |
|---|---|---|---|---|
| filters | 24 | 19 | 5 | 79.2% |
| writes | 22 | 14 | 8 | 63.6% |
| ordering | 19 | 11 | 8 | 57.9% |
| aggregates | 14 | 11 | 3 | 78.6% |
| relations | 13 | 10 | 3 | 76.9% |
| basics | 10 | 10 | 0 | 100.0% |
| errors | 10 | 6 | 4 | 60.0% |
Every exported symbol of the upstream package, and what the port offers for it. The upstream list is derived mechanically, never from a README: cd parity/prisma/node && node inventory.js # 219 raw symbols. A symbol with no case is untested, never a match.
| Upstream symbol | Go symbol | Status | Cases | Note |
|---|---|---|---|---|
| PrismaClient#$connect | — | missing | — | the port takes an already-open *sql.DB; there is nothing to connect |
| PrismaClient#$disconnect | — | missing | — | likewise; the caller owns the pool |
| PrismaClient#$transaction | Client.Transaction / Client.Batch | untested | — | present in both; not scored because a rollback race is not deterministic across processes |
| PrismaClient#$executeRawUnsafe | Client.ExecuteRaw | match | every case (reset step) | both runners truncate the tables through it, so every case exercises it |
| PrismaClient#$executeRaw | Client.ExecuteRaw | untested | — | the tagged-template form has no Go analogue; the port only takes a string |
| PrismaClient#$queryRaw / PrismaClient#$queryRawUnsafe | Client.QueryRaw | untested | — | |
| PrismaClient#$queryRawTyped | QueryRawInto[T] | untested | — | |
| PrismaClient#$runCommandRaw | — | missing | — | MongoDB-only upstream |
| PrismaClient#$extends | — | missing | — | no client extensions |
| PrismaClient#$use | — | missing | — | no middleware pipeline |
| PrismaClient#$on | — | missing | — | no query/error event hooks |
| PrismaClient#$applyPendingMigrations | — | missing | — | no migration engine |
| <model>.findMany | Query[T].FindMany | differs | many, see include-to-many, distinct-take-projecting-id, skip-without-take | the workhorse; the divergences below are all reached through it |
| <model>.findFirst | Query[T].FindFirst | differs | find-first-missing | upstream returns null for no match, the port returns ErrNotFound |
| <model>.findUnique | Query[T].FindUnique | differs | find-unique-missing | an alias of FindFirst: uniqueness is the caller's problem, and no match is an error |
| <model>.findFirstOrThrow | Query[T].FindFirstOrThrow | match | find-first-or-throw-missing | both raise P2025 |
| <model>.findUniqueOrThrow | Query[T].FindUniqueOrThrow | match | find-unique-or-throw-missing | both raise P2025 |
| <model>.create | Query[T].Create | differs | create-user, create-null-optionals, create-post-with-fk, create-explicit-id | agrees except that an explicit integer primary key is silently dropped |
| <model>.createMany | Query[T].CreateMany | match | create-many, seed-dump, unique-violation-create-many | |
| <model>.createManyAndReturn | — | missing | — | |
| <model>.update | Query[T].Update | differs | update-one, update-set-null, update-missing-row | returns a row count, not the record, and no match is 0 rather than P2025 |
| <model>.updateMany | Query[T].UpdateMany | differs | update-increment, update-many-multi-column, update-many-with-take | an alias of Update; take/skip/orderBy are accepted and ignored |
| <model>.upsert | Query[T].Upsert | differs | upsert-updates-existing, upsert-inserts-new, upsert-where-not-matched-by-create, upsert-on-primary-key, upsert-atomic-operator | a raw ON CONFLICT: the where is only a source of conflict column names |
| <model>.delete | Query[T].Delete | differs | delete-one, delete-missing-row, delete-non-unique-where | an alias of DeleteMany, so the singular form deletes every match |
| <model>.deleteMany | Query[T].DeleteMany | match | delete-many, delete-many-all | |
| <model>.count | Query[T].Count | differs | count-where, count-all, count-with-take | agrees unless take/skip is set, which the port folds into the counted set |
| <model>.aggregate | Query[T].Aggregate | differs | aggregate-all-functions, aggregate-mixed-columns, aggregate-empty-set, aggregate-string-min-max | float64-only results, and NULL aggregates vanish |
| <model>.groupBy | Query[T].GroupBy | differs | groupby-one-key, groupby-two-keys, groupby-having-sum, groupby-having-count, groupby-having-avg, groupby-string-min-max, groupby-where-then-having | values agree; the port returns []map[string]any of driver-native values keyed _sum_views rather than a typed { _sum: { views } } |
| <model>.findRaw / <model>.aggregateRaw | — | missing | — | MongoDB-only upstream |
| where: AND | prisma.And | match | and-or-nesting, deep-and-or-not | |
| where: OR | prisma.Or | match | and-or-nesting, not-or-nesting | |
| where: NOT | prisma.NotGroup | match | not-group, not-or-nesting, deep-and-or-not | |
| where.<String>: equals / where.<String?>: equals / where.<Int>: equals / where.<Int?>: equals / where.<Float>: equals / where.<Boolean>: equals | prisma.Equals / prisma.IsNull | differs | equals-scalar, equals-explicit, equals-null, equals-null-literal | equals: null has no analogue: Equals(col, nil) renders col = ? and matches nothing, so IsNull has to be chosen by hand |
| where.<String>: not / where.<String?>: not / where.<Int>: not / where.<Int?>: not / where.<Float>: not / where.<Boolean>: not | prisma.Not / prisma.IsNotNull | differs | not-scalar, not-nested-operators, not-null, not-null-literal | same story for not: null |
| where.<String>: in / where.<String?>: in / where.<Int>: in / where.<Int?>: in / where.<Float>: in | prisma.In | differs | in-list, in-empty, in-with-null | upstream rejects a null inside the list; the port binds it and matches nothing |
| where.<String>: notIn / where.<String?>: notIn / where.<Int>: notIn / where.<Int?>: notIn / where.<Float>: notIn | prisma.NotIn | match | not-in-list, not-in-empty | |
| where.<String>: lt / where.<String?>: lt / where.<Int>: lt / where.<Int?>: lt / where.<Float>: lt | prisma.Lt | match | range-gte-lt | |
| where.<String>: lte / where.<String?>: lte / where.<Int>: lte / where.<Int?>: lte / where.<Float>: lte | prisma.Lte | match | range-gt-lte, deep-and-or-not | |
| where.<String>: gt / where.<String?>: gt / where.<Int>: gt / where.<Int?>: gt / where.<Float>: gt | prisma.Gt | match | range-gt-lte, some-with-filter | |
| where.<String>: gte / where.<String?>: gte / where.<Int>: gte / where.<Int?>: gte / where.<Float>: gte | prisma.Gte | match | range-gte-lt, is-to-one-nested-range | |
| where.<String>: contains / where.<String?>: contains | prisma.Contains | differs | contains, contains-percent, contains-underscore | the port escapes LIKE wildcards with a backslash but emits no ESCAPE clause, so SQLite matches the backslash literally |
| where.<String>: startsWith / where.<String?>: startsWith | prisma.StartsWith | match | starts-and-ends-with | the same unescaped-backslash issue applies in principle; only the plain form is scored |
| where.<String>: endsWith / where.<String?>: endsWith | prisma.EndsWith | match | starts-and-ends-with | |
| where.<to-many>: some | prisma.Some | match | some-with-filter, some-nested-and, some-any | |
| where.<to-many>: every | prisma.Every | match | every-with-filter, every-any | |
| where.<to-many>: none | prisma.None | match | none-with-filter, none-any | |
| where.<to-one>: is | prisma.Is | match | is-to-one, is-to-one-nested-range | |
| where.<to-one>: isNot | — | missing | — | NotGroup(Is(...)) composes to the same predicate, but there is no symbol |
| findMany args: select | Query.Select | differs | select-projection, select-unknown-column | the port narrows the SELECT list but still returns a full struct, so unselected fields come back as zero values rather than being absent |
| findMany args: include | Query.Include | differs | include-to-one, include-to-one-ambiguous-filter, include-to-many, include-to-many-all | to-one works; to-many emits invalid SQL, and a filter on a column both tables have is ambiguous |
| findMany args: where | Query.Where | differs | where-unknown-column | column names are unvalidated strings, so a typo becomes a database error rather than a build-time one |
| findMany args: orderBy | Query.OrderBy | differs | orderby-asc-default-nulls, orderby-desc, orderby-multi-column, orderby-unknown-column | values agree; the column name is again unvalidated |
| orderBy.<nullable>: sort | prisma.Asc / prisma.Desc | match | orderby-desc | |
| orderBy.<nullable>: nulls | Query.OrderByNulls | match | orderby-nulls-first, orderby-nulls-last, orderby-nulls-desc-last | the port emulates placement with a leading (col IS NULL) term and agrees with upstream |
| findMany args: take | Query.Take | differs | take-and-skip, take-negative | a negative take is upstream's "last n"; the port passes it to LIMIT, where it means unlimited |
| findMany args: skip | Query.Skip | differs | take-and-skip, skip-without-take | the port only emits LIMIT when Take was called, and SQLite has no bare OFFSET |
| findMany args: cursor | Query.Cursor | differs | cursor-forward, cursor-skip-one, cursor-descending | the port's cursor is EXCLUSIVE; upstream includes the cursor row |
| findMany args: distinct | Query.Distinct | differs | distinct-single-column, distinct-two-columns, distinct-descending-order, distinct-take-projecting-id, distinct-take-key-only, distinct-unknown-column | three separate faults: LIMIT before the Go-side dedupe, pointer-valued columns keyed by address, and an unresolvable key silently ignored |
| aggregate args: where | Query.Where before Aggregate | match | aggregate-empty-set | |
| aggregate args: _count | AggregateQuery.Count | match | aggregate-all-functions, aggregate-mixed-columns | |
| aggregate args: _sum | AggregateQuery.Sum | differs | aggregate-all-functions, aggregate-empty-set | a NULL sum (no rows) is omitted from the result instead of reported as null |
| aggregate args: _avg | AggregateQuery.Avg | differs | aggregate-all-functions, aggregate-mixed-columns, aggregate-empty-set | same |
| aggregate args: _min | AggregateQuery.Min | differs | aggregate-all-functions, aggregate-string-min-max | AggregateResult is map[string]float64, so MIN over text or dates cannot be represented |
| aggregate args: _max | AggregateQuery.Max | differs | aggregate-all-functions, aggregate-string-min-max | same |
| aggregate args: orderBy / aggregate args: take / aggregate args: skip / aggregate args: cursor | — | missing | — | AggregateQuery.SQL emits no ORDER BY, LIMIT, OFFSET or keyset predicate |
| aggregate _count output: _all | AggregateResult.Count | match | aggregate-all-functions | the port only offers COUNT(*), which is upstream's _all |
| groupBy args: by | Query.GroupBy | match | groupby-one-key, groupby-two-keys | |
| groupBy args: where | Query.Where before GroupBy | match | groupby-where-then-having | |
| groupBy args: orderBy | Query.OrderBy before GroupBy | match | groupby-one-key, groupby-two-keys | |
| groupBy args: having | GroupQuery.Having | match | groupby-having-sum, groupby-having-count, groupby-having-avg | the port filters on its own aggregate aliases (_sum_views), which SQLite resolves in HAVING |
| groupBy args: _count | GroupQuery.Count | match | groupby-one-key, groupby-two-keys | |
| groupBy args: _sum | GroupQuery.Sum | match | groupby-one-key, groupby-having-sum | |
| groupBy args: _avg | GroupQuery.Avg | match | groupby-having-avg | |
| groupBy args: _min | GroupQuery.Min | match | groupby-string-min-max | unlike Aggregate this scans into any, so text MIN/MAX survives |
| groupBy args: _max | GroupQuery.Max | match | groupby-string-min-max | |
| groupBy args: take / groupBy args: skip | — | missing | — | GroupQuery.SQL emits no LIMIT/OFFSET |
| having: AND / having: OR / having: NOT | prisma.And / Or / NotGroup in Having | untested | — | the conditions compose, but no case combines two HAVING predicates |
| having.<Int>: _count | prisma.Gt("_count", ...) | match | groupby-having-count | |
| having.<Int>: _sum | prisma.Gt("_sum_<col>", ...) | match | groupby-having-sum, groupby-where-then-having | |
| having.<Int>: _avg | prisma.Gt("_avg_<col>", ...) | match | groupby-having-avg | |
| having.<Int>: _min / having.<Int>: _max | prisma.Gt("_min_<col>", ...) | untested | — | |
| having.<Int>: gt | prisma.Gt on an alias | match | groupby-having-sum, groupby-having-count, groupby-having-avg | |
| having.<Int>: gte | prisma.Gte on an alias | match | groupby-where-then-having | |
| having.<Int>: equals / having.<Int>: in / having.<Int>: notIn / having.<Int>: lt / having.<Int>: lte / having.<Int>: not | prisma.Equals / In / ... on an alias | untested | — | the same alias trick applies; only gt/gte are scored |
| update data.<Int>: set | prisma.SetTo | match | update-set-operator | |
| update data.<Int>: increment | prisma.Increment | match | update-increment, update-many-multi-column | |
| update data.<Int>: decrement | prisma.Decrement | match | update-decrement | |
| update data.<Int>: multiply | prisma.Multiply | match | update-multiply | |
| update data.<Int>: divide | prisma.Divide | match | update-divide-exact, update-divide-fractional | including the fractional case, where both store the SQL division result |
| upsert args: where | the conflict-column argument of Query.Upsert | differs | upsert-where-not-matched-by-create, upsert-on-primary-key | only the KEY NAMES are used; the values are dropped, so the port updates whatever the INSERT collides with rather than the row the where selected |
| upsert args: create | the create argument of Query.Upsert | match | upsert-inserts-new | |
| upsert args: update | the update argument of Query.Upsert | differs | upsert-updates-existing, upsert-atomic-operator | UpsertSQL binds each update value directly instead of letting an UpdateOp render itself, so { increment } reaches the driver as a struct and the call fails |
| upsert args: select / upsert args: include | — | missing | — | Upsert returns a row count, so there is nothing to project |
| create data.<to-many>: create | NestedCreate.Create | match | nested-create | |
| create data.<to-many>: connect | NestedCreate.Connect | untested | — | |
| create data.<to-many>: connectOrCreate | NestedCreate.ConnectOrCreate | untested | — | |
| create data.<to-many>: createMany | — | missing | — | |
| update data.<to-many>: create / update data.<to-many>: connect / update data.<to-many>: connectOrCreate / update data.<to-many>: update / update data.<to-many>: updateMany / update data.<to-many>: upsert / update data.<to-many>: createMany / update data.<to-many>: set / update data.<to-many>: disconnect / update data.<to-many>: delete / update data.<to-many>: deleteMany | — | missing | — | nested writes exist only on the create path (NestedCreate); Update takes a flat column map, so none of the eleven nested-update operations has an analogue |
| prisma CLI: init / prisma CLI: validate / prisma CLI: format / prisma CLI: version / prisma CLI: debug / prisma CLI: studio | — | missing | — | no schema file exists to init, validate, format or browse |
| prisma CLI: generate | — | missing | — | there is no code generation: a model is a Go struct read by reflection at run time |
Every case the harness streamed to both runners, with the exact upstream symbol and Go symbol it exercised. A deliberate, documented difference is a deviation and is counted apart from a mismatch.
| Case | Group | Upstream symbol | Go symbol | Status | Note |
|---|---|---|---|---|---|
| count-where | aggregates | prisma.user.count | Query.Count | match | |
| count-all | aggregates | prisma.post.count | Query.Count | match | |
| count-with-take | aggregates | prisma.user.count({ take }) | Query.Take(...).Count | mismatch | the port deliberately counts a LIMITed subquery, believing Prisma applies take to count |
| aggregate-all-functions | aggregates | prisma.post.aggregate | Query.Aggregate().Count().Sum().Avg().Min().Max() | match | the seed makes every average exact so no floating-point rounding can enter the comparison |
| aggregate-mixed-columns | aggregates | prisma.user.aggregate | Query.Aggregate() | match | age is nullable: the average must skip the NULLs |
| aggregate-empty-set | aggregates | prisma.post.aggregate over no rows | Query.Aggregate() over no rows | mismatch | Prisma reports the column with a null value; the port omits the key entirely |
| aggregate-string-min-max | aggregates | aggregate: { _min: { name } } | Query.Aggregate().Min("name") | mismatch | AggregateResult is float64-only, so MIN/MAX over a text column has nowhere to land |
| groupby-one-key | aggregates | prisma.user.groupBy | Query.GroupBy("city").Count().Sum("score") | match | the NULL city forms its own group |
| groupby-two-keys | aggregates | groupBy: { by: [author_id, published] } | Query.GroupBy("author_id", "published") | match | |
| groupby-having-sum | aggregates | having: { views: { _sum: { gt } } } | GroupQuery.Having(prisma.Gt("_sum_views", 100)) | match | |
| groupby-having-count | aggregates | having: { id: { _count: { gt } } } | GroupQuery.Having(prisma.Gt("_count", 2)) | match | |
| groupby-having-avg | aggregates | having: { views: { _avg: { gt } } } | GroupQuery.Having(prisma.Gt("_avg_views", 45)) | match | |
| groupby-string-min-max | aggregates | groupBy: { _min: { name } } | GroupQuery.Min("name") | match | GroupQuery scans into any, unlike Aggregate, so text min/max has somewhere to land |
| groupby-where-then-having | aggregates | groupBy with where and having | Query.Where(...).GroupBy(...).Having(...) | match | |
| seed-dump | basics | prisma.user.createMany / prisma.post.createMany | Query[User].CreateMany / Query[Post].CreateMany | match | the fixed dataset, read straight back: guards every later case against a seed divergence |
| create-user | basics | prisma.user.create | Query[User].Create | match | id omitted on both sides so the database assigns it |
| create-null-optionals | basics | prisma.user.create | Query[User].Create | match | explicit nulls into the two nullable columns |
| create-post-with-fk | basics | prisma.post.create | Query[Post].Create | match | |
| create-many | basics | prisma.user.createMany | Query[User].CreateMany | match | both report the number of rows written |
| nested-create | basics | prisma.user.create({ data: { posts: { create: [...] } } }) | Query[User].NestedCreate(...).Create("Posts", ...).Exec | match | the parent id must reach both children as author_id |
| select-projection | basics | prisma.user.findMany({ select }) | Query[User].Select | match | the port returns a full struct with zeroed unselected fields, so the runner re-applies the projection before emitting |
| select-unknown-column | basics | prisma.user.findMany({ select }) | Query[User].Select | match | both must fail: Prisma at validation, the port in buildSelect |
| find-unique-by-unique-column | basics | prisma.user.findUnique | Query[User].FindUnique | match | |
| find-first-ordered | basics | prisma.post.findFirst | Query[Post].FindFirst | match | |
| unique-violation | errors | P2002 from prisma.user.create | prisma.CodeUniqueViolation via mapError | match | |
| unique-violation-create-many | errors | P2002 from prisma.user.createMany | prisma.CodeUniqueViolation via mapError | match | the whole multi-row INSERT must roll back on both sides |
| foreign-key-violation | errors | P2003 from prisma.post.create | prisma.CodeForeignKeyViolation via mapError | match | |
| foreign-key-violation-on-delete | errors | P2003 from prisma.user.delete (ON DELETE RESTRICT) | prisma.CodeForeignKeyViolation via mapError | match | |
| not-null-violation | errors | prisma.user.create({ data: { name: null } }) | Query.Create with a nil *string | mismatch | Prisma refuses before any SQL runs and its validation error carries no code; the port lets SQLite refuse and reports P2011 |
| find-first-missing | errors | prisma.user.findFirst with no match | Query.FindFirst -> prisma.ErrNotFound | mismatch | Prisma returns null; the port returns an error |
| find-unique-missing | errors | prisma.user.findUnique with no match | Query.FindUnique -> prisma.ErrNotFound | mismatch | ErrNotFound is a bare sentinel and carries no P-code, unlike ErrRecordNotFound |
| find-first-or-throw-missing | errors | P2025 from prisma.user.findFirstOrThrow | Query.FindFirstOrThrow -> prisma.ErrRecordNotFound | match | |
| find-unique-or-throw-missing | errors | P2025 from prisma.user.findUniqueOrThrow | Query.FindUniqueOrThrow -> prisma.ErrRecordNotFound | match | |
| create-explicit-id | errors | create: { data: { id: 99 } } | Query.Create with User{ID: 99} | mismatch | an integer primary key is unconditionally treated as database-generated and dropped from the INSERT: there is no tag that turns that off |
| equals-scalar | filters | where: { city } | prisma.Equals | match | |
| equals-explicit | filters | where: { city: { equals } } | prisma.Equals | match | |
| equals-null | filters | where: { city: null } | prisma.IsNull | match | the port has no null shorthand; IsNull is its documented equivalent |
| equals-null-literal | filters | where: { city: null } | prisma.Equals(col, nil) | mismatch | the same filter translated field-for-field: "col = ?" with a nil argument never matches |
| not-null | filters | where: { city: { not: null } } | prisma.IsNotNull | match | |
| not-null-literal | filters | where: { city: { not: null } } | prisma.Not(col, nil) | mismatch | |
| not-scalar | filters | where: { city: { not } } | prisma.Not | match | SQL three-valued logic drops the NULL cities on both sides |
| not-nested-operators | filters | where: { views: { not: { gte } } } | prisma.NotGroup(prisma.Gte(...)) | match | |
| in-list | filters | where: { id: { in } } | prisma.In | match | |
| not-in-list | filters | where: { id: { notIn } } | prisma.NotIn | match | |
| in-empty | filters | where: { id: { in: [] } } | prisma.In() with no values | match | an empty IN can never match |
| not-in-empty | filters | where: { id: { notIn: [] } } | prisma.NotIn() with no values | match | an empty NOT IN always matches |
| in-with-null | filters | where: { city: { in: [..., null] } } | prisma.In with a nil value | mismatch | Prisma rejects a null inside an IN list |
| range-gte-lt | filters | where: { views: { gte, lt } } | prisma.Gte + prisma.Lt | match | two operators on one field are AND-ed |
| range-gt-lte | filters | where: { score: { gt, lte } } | prisma.Gt + prisma.Lte | match | |
| contains | filters | where: { title: { contains } } | prisma.Contains | match | SQLite LIKE is case-insensitive for ASCII on both sides |
| contains-percent | filters | where: { title: { contains: "%" } } | prisma.Contains | mismatch | the port backslash-escapes LIKE wildcards but emits no ESCAPE clause, so SQLite takes the backslash literally |
| contains-underscore | filters | where: { title: { contains: "_two" } } | prisma.Contains | mismatch | same escaping question for the single-character wildcard |
| starts-and-ends-with | filters | where: { title: { startsWith, endsWith } } | prisma.StartsWith + prisma.EndsWith | match | |
| and-or-nesting | filters | where: { OR: [..., { AND: [...] }] } | prisma.Or(prisma.And(...)) | match | |
| not-group | filters | where: { NOT: [...] } | prisma.NotGroup | match | |
| not-or-nesting | filters | where: { NOT: { OR: [...] } } | prisma.NotGroup(prisma.Or(...)) | match | |
| deep-and-or-not | filters | where: { AND: [{ OR }, { NOT }] } | prisma.And(prisma.Or(...), prisma.NotGroup(...)) | match | |
| where-unknown-column | filters | where: { citty } | prisma.Equals("citty", ...) | match | both fail, but the port only finds out from the database: column names in Where are unvalidated strings |
| orderby-asc-default-nulls | ordering | orderBy: [{ city: "asc" }] | Query.OrderBy | match | |
| orderby-desc | ordering | orderBy: [{ score: "desc" }] | Query.OrderBy(..., prisma.Desc) | match | |
| orderby-nulls-first | ordering | orderBy: [{ city: { sort, nulls: "first" } }] | Query.OrderByNulls(..., prisma.NullsFirst) | match | |
| orderby-nulls-last | ordering | orderBy: [{ city: { sort, nulls: "last" } }] | Query.OrderByNulls(..., prisma.NullsLast) | match | |
| orderby-nulls-desc-last | ordering | orderBy: [{ age: { sort: "desc", nulls: "last" } }] | Query.OrderByNulls | match | |
| orderby-multi-column | ordering | orderBy: [{ published }, { views }] | two Query.OrderBy calls | match | |
| orderby-unknown-column | ordering | orderBy: [{ cityy }] | Query.OrderBy("cityy", ...) | match | both fail; the port again only at execution time |
| take-and-skip | ordering | take + skip | Query.Take + Query.Skip | match | |
| skip-without-take | ordering | skip | Query.Skip | mismatch | SQLite has no bare OFFSET, and the port only emits LIMIT when Take was called |
| take-negative | ordering | take: -2 | Query.Take(-2) | mismatch | a negative take is Prisma's "last n"; SQL LIMIT -1 means unlimited |
| cursor-forward | ordering | cursor: { id: 3 }, take: 2 | Query.Cursor("id", 3).Take(2) | mismatch | Prisma's cursor row is INCLUDED in the page |
| cursor-skip-one | ordering | cursor + skip: 1 + take | Query.Cursor + Query.Skip + Query.Take | mismatch | the idiomatic Prisma "page after the cursor" |
| cursor-descending | ordering | cursor with a descending orderBy | Query.Cursor with prisma.Desc | mismatch | |
| distinct-single-column | ordering | distinct: ["city"] | Query.Distinct("city") | mismatch | |
| distinct-two-columns | ordering | distinct: ["author_id", "published"] | Query.Distinct("author_id", "published") | match | |
| distinct-descending-order | ordering | distinct with a descending orderBy | Query.Distinct + prisma.Desc | match | which duplicate survives depends on the order |
| distinct-take-projecting-id | ordering | distinct: ["city"], take: 3 | Query.Distinct("city").Take(3) | mismatch | the port applies LIMIT in SQL and dedupes afterwards in Go |
| distinct-take-key-only | ordering | distinct: ["city"], take: 3, select: { city } | Query.Distinct("city").Take(3).Select("city") | match | same query with the primary key out of the projection, where SELECT DISTINCT can collapse rows in SQL |
| distinct-unknown-column | ordering | distinct: ["citty"] | Query.Distinct("citty") | mismatch | the port silently ignores a distinct key it cannot resolve |
| some-with-filter | relations | where: { posts: { some } } | prisma.Some("Posts", ...) | match | |
| some-nested-and | relations | where: { posts: { some: { AND } } } | prisma.Some("Posts", prisma.And(...)) | match | |
| some-any | relations | where: { posts: { some: {} } } | prisma.Some("Posts") | match | users that have any post at all |
| every-with-filter | relations | where: { posts: { every } } | prisma.Every("Posts", ...) | match | vacuously true for users with no posts |
| every-any | relations | where: { posts: { every: {} } } | prisma.Every("Posts") | match | |
| none-with-filter | relations | where: { posts: { none } } | prisma.None("Posts", ...) | match | |
| none-any | relations | where: { posts: { none: {} } } | prisma.None("Posts") | match | users with no posts at all |
| is-to-one | relations | where: { author: { is } } | prisma.Is("Author", ...) | match | |
| is-to-one-nested-range | relations | where: { author: { is: { score: { gte } } } } | prisma.Is("Author", prisma.Gte(...)) | match | |
| include-to-one | relations | include: { author: true } | Query.Include("Author") | match | the to-one LEFT JOIN, filtered on a column that exists in only one table |
| include-to-one-ambiguous-filter | relations | include: { author: true } with where: { id } | Query.Include("Author") with prisma.In("id", ...) | mismatch | the port never qualifies Where/OrderBy columns with the base table, so any filter on a column both tables have becomes ambiguous SQL |
| include-to-many | relations | include: { posts: true } | Query.Include("Posts") | mismatch | a to-many include: the port joins on base.<fk> = target.<references>, which for a to-many relation names a column the base table does not have |
| include-to-many-all | relations | include: { posts: true } | Query.Include("Posts") | mismatch | |
| update-one | writes | prisma.user.update | Query.Update | match | Prisma returns the updated record, the port a row count, so the emitted value is the count on both sides and the dump carries the comparison |
| update-set-null | writes | update: { data: { city: null } } | Query.Update with a nil value | match | |
| update-missing-row | writes | prisma.user.update on a missing row | Query.Update matching nothing | mismatch | Prisma raises P2025; the port reports 0 rows affected |
| update-increment | writes | data: { views: { increment } } | prisma.Increment | match | |
| update-decrement | writes | data: { views: { decrement } } | prisma.Decrement | match | |
| update-multiply | writes | data: { views: { multiply } } | prisma.Multiply | match | |
| update-divide-exact | writes | data: { views: { divide } } | prisma.Divide | match | |
| update-divide-fractional | writes | data: { views: { divide } } with a remainder | prisma.Divide | match | 10 / 4 into an integer column: does either side round, truncate or store 2.5? |
| update-set-operator | writes | data: { views: { set } } | prisma.SetTo | match | |
| update-many-multi-column | writes | updateMany with two assignments | Query.UpdateMany with a literal and an UpdateOp | match |