diff --git a/package.json b/package.json index 1574efab49c8..863a58809025 100644 --- a/package.json +++ b/package.json @@ -162,10 +162,12 @@ "pnpm": { "overrides": { "form-data": ">=4.0.4", - "hono": ">=4.12.2", + "hono": ">=4.12.4", + "@hono/node-server": ">=1.19.10", "jws": ">=4.0.1", "tar-fs": ">=2.1.4", - "lodash": ">=4.17.23" + "lodash": ">=4.17.23", + "@tootallnate/once": ">=3.0.1" } } } diff --git a/packages/client-engine-runtime/src/interpreter/render-query.ts b/packages/client-engine-runtime/src/interpreter/render-query.ts index cbb4441e9ba9..88a43a3173d2 100644 --- a/packages/client-engine-runtime/src/interpreter/render-query.ts +++ b/packages/client-engine-runtime/src/interpreter/render-query.ts @@ -48,7 +48,17 @@ export function evaluateArg(arg: unknown, scope: ScopeBindings, generators: Gene if (found === undefined) { throw new Error(`Missing value for query variable ${arg.prisma__value.name}`) } - arg = found + if (arg.prisma__value.type === 'DateTime' && typeof found === 'string') { + // Convert input datetime strings to Date objects. This is done to prevent issues that + // arise when query input values end up being directly compared to values retrieved from + // the database. One example of this is a query containing a DateTime cursor value being + // used against a DATE MySQL column. The pagination logic doesn't have parameter type + // information, therefore it ends up comparing the two datetimes as strings and would yield + // false even if the two date datetime strings represent the same Date. + arg = new Date(found) + } else { + arg = found + } } else if (isPrismaValueGenerator(arg)) { const { name, args } = arg.prisma__value const generator = generators[name] diff --git a/packages/client/tests/functional/issues/29309-datetime-cursor/_matrix.ts b/packages/client/tests/functional/issues/29309-datetime-cursor/_matrix.ts new file mode 100644 index 000000000000..6d51b8a6c027 --- /dev/null +++ b/packages/client/tests/functional/issues/29309-datetime-cursor/_matrix.ts @@ -0,0 +1,4 @@ +import { defineMatrix } from '../../_utils/defineMatrix' +import { Providers, sqlProviders } from '../../_utils/providers' + +export default defineMatrix(() => [sqlProviders.filter(({ provider }) => provider !== Providers.SQLITE)]) diff --git a/packages/client/tests/functional/issues/29309-datetime-cursor/prisma/_schema.ts b/packages/client/tests/functional/issues/29309-datetime-cursor/prisma/_schema.ts new file mode 100644 index 000000000000..81055a05d1b7 --- /dev/null +++ b/packages/client/tests/functional/issues/29309-datetime-cursor/prisma/_schema.ts @@ -0,0 +1,21 @@ +import testMatrix from '../_matrix' + +export default testMatrix.setupSchema(({ provider }) => { + return /* Prisma */ ` + generator client { + provider = "prisma-client-js" + } + + datasource db { + provider = "${provider}" + } + + model Event { + appId Int + createdAt DateTime @db.Date + value Int + + @@id([appId, createdAt]) + } + ` +}) diff --git a/packages/client/tests/functional/issues/29309-datetime-cursor/tests.ts b/packages/client/tests/functional/issues/29309-datetime-cursor/tests.ts new file mode 100644 index 000000000000..bb77306b46c9 --- /dev/null +++ b/packages/client/tests/functional/issues/29309-datetime-cursor/tests.ts @@ -0,0 +1,65 @@ +import testMatrix from './_matrix' +// @ts-ignore +import type { Event, PrismaClient } from './generated/prisma/client' + +declare let prisma: PrismaClient + +testMatrix.setupTestSuite( + () => { + test('retrieves a cursor against a DATE column', async () => { + const rows: Event[] = [] + for (let day = 1; day <= 10; day++) { + rows.push({ + appId: 1, + createdAt: new Date(`2025-01-${String(day).padStart(2, '0')}Z`), + value: day * 100, + }) + } + await prisma.event.createMany({ data: rows }) + + const firstThree = await prisma.event.findMany({ + where: { appId: 1 }, + orderBy: { createdAt: 'asc' }, + take: 3, + }) + const cursorRow = firstThree[2] // 2025-01-03 + + const withCursor = await prisma.event.findMany({ + where: { appId: 1 }, + cursor: { + appId_createdAt: { + appId: cursorRow.appId, + createdAt: cursorRow.createdAt, + }, + }, + orderBy: { createdAt: 'asc' }, + take: 3, + skip: 1, + }) + + expect(withCursor).toEqual([ + { + appId: 1, + createdAt: new Date('2025-01-04T00:00:00.000Z'), + value: 400, + }, + { + appId: 1, + createdAt: new Date('2025-01-05T00:00:00.000Z'), + value: 500, + }, + { + appId: 1, + createdAt: new Date('2025-01-06T00:00:00.000Z'), + value: 600, + }, + ]) + }) + }, + { + optOut: { + from: ['mongodb', 'sqlite'], + reason: 'MongoDB and SQLite do not support DATE columns', + }, + }, +) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0279eb76867b..6b28550c0552 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,10 +6,12 @@ settings: overrides: form-data: '>=4.0.4' - hono: '>=4.12.2' + hono: '>=4.12.4' + '@hono/node-server': '>=1.19.10' jws: '>=4.0.1' tar-fs: '>=2.1.4' lodash: '>=4.17.23' + '@tootallnate/once': '>=3.0.1' importers: @@ -441,8 +443,8 @@ importers: version: 3.4.7 devDependencies: '@hono/node-server': - specifier: 1.19.9 - version: 1.19.9(hono@4.12.3) + specifier: '>=1.19.10' + version: 1.19.11(hono@4.12.7) '@inquirer/prompts': specifier: 7.3.3 version: 7.3.3(@types/node@20.19.25) @@ -552,8 +554,8 @@ importers: specifier: 4.10.0 version: 4.10.0 hono: - specifier: '>=4.12.2' - version: 4.12.3 + specifier: '>=4.12.4' + version: 4.12.7 jest: specifier: 29.7.0 version: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.11.5)(@types/node@20.19.25)(typescript@5.4.5)) @@ -637,8 +639,8 @@ importers: specifier: 2.0.3 version: 2.0.3(@jest/expect@29.7.0)(@jest/globals@29.7.0) '@hono/node-server': - specifier: 1.19.0 - version: 1.19.0(hono@4.12.3) + specifier: '>=1.19.10' + version: 1.19.11(hono@4.12.7) '@inquirer/prompts': specifier: 7.3.3 version: 7.3.3(@types/node@20.19.25) @@ -1765,11 +1767,11 @@ importers: packages/query-plan-executor: devDependencies: '@hono/node-server': - specifier: 1.19.0 - version: 1.19.0(hono@4.12.3) + specifier: '>=1.19.10' + version: 1.19.11(hono@4.12.7) '@hono/zod-validator': specifier: 0.7.2 - version: 0.7.2(hono@4.12.3)(zod@4.1.3) + version: 0.7.2(hono@4.12.7)(zod@4.1.3) '@opentelemetry/api': specifier: 1.9.0 version: 1.9.0 @@ -1795,8 +1797,8 @@ importers: specifier: workspace:* version: link:../driver-adapter-utils hono: - specifier: '>=4.12.2' - version: 4.12.3 + specifier: '>=4.12.4' + version: 4.12.7 temporal-polyfill: specifier: 0.3.0 version: 0.3.0 @@ -2792,22 +2794,16 @@ packages: '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} - '@hono/node-server@1.19.0': - resolution: {integrity: sha512-1k8/8OHf5VIymJEcJyVksFpT+AQ5euY0VA5hUkCnlKpD4mr8FSbvXaHblxeTTEr90OaqWzAkQaqD80qHZQKxBA==} - engines: {node: '>=18.14.1'} - peerDependencies: - hono: '>=4.12.2' - - '@hono/node-server@1.19.9': - resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==} + '@hono/node-server@1.19.11': + resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==} engines: {node: '>=18.14.1'} peerDependencies: - hono: '>=4.12.2' + hono: '>=4.12.4' '@hono/zod-validator@0.7.2': resolution: {integrity: sha512-ub5eL/NeZ4eLZawu78JpW/J+dugDAYhwqUIdp9KYScI6PZECij4Hx4UsrthlEUutqDDhPwRI0MscUfNkvn/mqQ==} peerDependencies: - hono: '>=4.12.2' + hono: '>=4.12.4' zod: ^3.25.0 || ^4.0.0 '@humanfs/core@0.19.1': @@ -3879,12 +3875,8 @@ packages: '@timsuchanek/sleep-promise@8.0.1': resolution: {integrity: sha512-cxHYbrXfnCWsklydIHSw5GCMHUPqpJ/enxWSyVHNOgNe61sit/+aOXTTI+VOdWkvVaJsI2vsB9N4+YDNITawOQ==} - '@tootallnate/once@1.1.2': - resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} - engines: {node: '>= 6'} - - '@tootallnate/once@2.0.0': - resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + '@tootallnate/once@3.0.1': + resolution: {integrity: sha512-VyMVKRrpHTT8PnotUeV8L/mDaMwD5DaAKCFLP73zAqAtvF0FCqky+Ki7BYbFCYQmqFyTe9316Ed5zS70QUR9eg==} engines: {node: '>= 10'} '@tsconfig/node10@1.0.9': @@ -5636,8 +5628,8 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hono@4.12.3: - resolution: {integrity: sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg==} + hono@4.12.7: + resolution: {integrity: sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==} engines: {node: '>=16.9.0'} hosted-git-info@2.8.9: @@ -9179,17 +9171,13 @@ snapshots: '@gar/promisify@1.1.3': optional: true - '@hono/node-server@1.19.0(hono@4.12.3)': - dependencies: - hono: 4.12.3 - - '@hono/node-server@1.19.9(hono@4.12.3)': + '@hono/node-server@1.19.11(hono@4.12.7)': dependencies: - hono: 4.12.3 + hono: 4.12.7 - '@hono/zod-validator@0.7.2(hono@4.12.3)(zod@4.1.3)': + '@hono/zod-validator@0.7.2(hono@4.12.7)(zod@4.1.3)': dependencies: - hono: 4.12.3 + hono: 4.12.7 zod: 4.1.3 '@humanfs/core@0.19.1': {} @@ -10042,13 +10030,13 @@ snapshots: '@electric-sql/pglite': 0.3.15 '@electric-sql/pglite-socket': 0.0.20(@electric-sql/pglite@0.3.15) '@electric-sql/pglite-tools': 0.2.20(@electric-sql/pglite@0.3.15) - '@hono/node-server': 1.19.9(hono@4.12.3) + '@hono/node-server': 1.19.11(hono@4.12.7) '@mrleebo/prisma-ast': 0.13.1 '@prisma/get-platform': 7.2.0 '@prisma/query-plan-executor': 7.2.0 foreground-child: 3.3.1 get-port-please: 3.2.0 - hono: 4.12.3 + hono: 4.12.7 http-status-codes: 2.3.0 pathe: 2.0.3 proper-lockfile: 4.1.2 @@ -10343,10 +10331,7 @@ snapshots: '@timsuchanek/sleep-promise@8.0.1': {} - '@tootallnate/once@1.1.2': - optional: true - - '@tootallnate/once@2.0.0': {} + '@tootallnate/once@3.0.1': {} '@tsconfig/node10@1.0.9': {} @@ -12334,7 +12319,7 @@ snapshots: dependencies: function-bind: 1.1.2 - hono@4.12.3: {} + hono@4.12.7: {} hosted-git-info@2.8.9: {} @@ -12357,7 +12342,7 @@ snapshots: http-proxy-agent@4.0.1: dependencies: - '@tootallnate/once': 1.1.2 + '@tootallnate/once': 3.0.1 agent-base: 6.0.2 debug: 4.4.1 transitivePeerDependencies: @@ -12366,7 +12351,7 @@ snapshots: http-proxy-agent@5.0.0: dependencies: - '@tootallnate/once': 2.0.0 + '@tootallnate/once': 3.0.1 agent-base: 6.0.2 debug: 4.4.1 transitivePeerDependencies: