Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
- `QueryInterpreter` class in `packages/client-engine-runtime/src/interpreter/query-interpreter.ts` executes query plans against `SqlQueryable` (driver adapter interface).
- Query plans are cached and shared between requests (`QueryPlanCache` in `packages/client/src/runtime/core/engines/client/query-plan-cache.ts`), so the interpreter receives them as `DeepReadonly` and must never mutate them (#29262 fixed such a bug and added a functional regression test in `packages/client/tests/functional/issues/29254-query-plan-cache-mutation`). Code that needs a modified plan must copy the affected nodes; structural sharing of unaffected subtrees is fine (see `purifyQueryPlan`).
- Query flow: `PrismaClient` → `ClientEngine.request()` → query compiler → `executor.execute()` → `QueryInterpreter.run()` → driver adapter.
- Many driver adapters construct fresh `SqlResultSet.columnNames` / `columnTypes` arrays for each query result (e.g. via driver field metadata maps). Runtime caches that need to survive repeated real DB calls should not rely only on result metadata array identity; cache by stable column shape when appropriate.
- `ExecutePlanParams` interface in `packages/client/src/runtime/core/engines/client/Executor.ts` defines what's passed through the execution chain.
- `TransactionManager` in `packages/client-engine-runtime/src/transaction-manager/transaction-manager.ts` owns interactive transaction IDs and implements nested transactions using savepoints. Savepoint SQL is provider-specific (e.g. PostgreSQL uses `ROLLBACK TO SAVEPOINT <name>`, MySQL/SQLite use `ROLLBACK TO <name>`, SQL Server uses `SAVE TRANSACTION <name>` / `ROLLBACK TRANSACTION <name>` and has no release statement).
- `Transaction` in `packages/driver-adapter-utils` models savepoint behavior as async methods (`createSavepoint`, `rollbackToSavepoint`, optional `releaseSavepoint`) instead of returning SQL via `savepoint(action, name)`. `TransactionManager` expects adapter methods for savepoints and does not synthesize provider fallback SQL.
Expand Down
159 changes: 159 additions & 0 deletions packages/client-engine-runtime/src/interpreter/data-mapper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { ColumnTypeEnum } from '@prisma/driver-adapter-utils'
import { expect, test } from 'vitest'

import type { ResultNode } from '../query-plan'
import { applyDataMap, applyDataMapToResultSet, DataMapperError } from './data-mapper'
import { serializeSql } from './serialize-sql'

test('maps result sets directly like serialized SQL rows', () => {
const resultSet = {
columnTypes: [ColumnTypeEnum.Int32, ColumnTypeEnum.Text],
columnNames: ['id', 'name'],
rows: [
[1, 'Alice'],
[2, 'Bob'],
],
}
const structure = {
type: 'object',
serializedName: null,
skipNulls: false,
fields: {
id: { type: 'field', dbName: 'id', fieldType: { type: 'int', arity: 'scalar' } },
name: { type: 'field', dbName: 'name', fieldType: { type: 'string', arity: 'scalar' } },
},
} satisfies ResultNode

expect(applyDataMapToResultSet(resultSet, structure, {})).toEqual(
applyDataMap(serializeSql(resultSet), structure, {}),
)
})

test('direct result-set mapping uses the last duplicate column name', () => {
const resultSet = {
columnTypes: [ColumnTypeEnum.Int32, ColumnTypeEnum.Int32],
columnNames: ['id', 'id'],
rows: [[1, 2]],
}
const structure = {
type: 'object',
serializedName: null,
skipNulls: false,
fields: {
id: { type: 'field', dbName: 'id', fieldType: { type: 'int', arity: 'scalar' } },
},
} satisfies ResultNode

expect(applyDataMapToResultSet(resultSet, structure, {})).toEqual(
applyDataMap(serializeSql(resultSet), structure, {}),
)
})

test('maps nested row objects directly like serialized SQL rows', () => {
const resultSet = {
columnTypes: [ColumnTypeEnum.Int32, ColumnTypeEnum.Text],
columnNames: ['id', 'author_name'],
rows: [[1, 'Alice']],
}
const structure = {
type: 'object',
serializedName: null,
skipNulls: false,
fields: {
id: { type: 'field', dbName: 'id', fieldType: { type: 'int', arity: 'scalar' } },
author: {
type: 'object',
serializedName: null,
skipNulls: false,
fields: {
name: { type: 'field', dbName: 'author_name', fieldType: { type: 'string', arity: 'scalar' } },
},
},
},
} satisfies ResultNode

expect(applyDataMapToResultSet(resultSet, structure, {})).toEqual(
applyDataMap(serializeSql(resultSet), structure, {}),
)
})

test('maps JSON value objects directly like serialized SQL rows', () => {
const resultSet = {
columnTypes: [ColumnTypeEnum.Json],
columnNames: ['profile'],
rows: [['{"display_name":"Alice"}']],
}
const structure = {
type: 'object',
serializedName: null,
skipNulls: false,
fields: {
profile: {
type: 'object',
serializedName: 'profile',
skipNulls: false,
fields: {
displayName: { type: 'field', dbName: 'display_name', fieldType: { type: 'string', arity: 'scalar' } },
},
},
},
} satisfies ResultNode

expect(applyDataMapToResultSet(resultSet, structure, {})).toEqual(
applyDataMap(serializeSql(resultSet), structure, {}),
)
})

test('throws DataMapperError when a mapped column is missing from the result set', () => {
const resultSet = {
columnTypes: [ColumnTypeEnum.Int32],
columnNames: ['id'],
rows: [[1]],
}
const structure = {
type: 'object',
serializedName: null,
skipNulls: false,
fields: {
name: { type: 'field', dbName: 'name', fieldType: { type: 'string', arity: 'scalar' } },
},
} satisfies ResultNode

expect(() => applyDataMapToResultSet(resultSet, structure, {})).toThrowError(DataMapperError)
})

test('direct result-set mapping caches independently for different column orders', () => {
const structure = {
type: 'object',
serializedName: null,
skipNulls: false,
fields: {
id: { type: 'field', dbName: 'id', fieldType: { type: 'int', arity: 'scalar' } },
name: { type: 'field', dbName: 'name', fieldType: { type: 'string', arity: 'scalar' } },
},
} satisfies ResultNode

expect(
applyDataMapToResultSet(
{
columnTypes: [ColumnTypeEnum.Int32, ColumnTypeEnum.Text],
columnNames: ['id', 'name'],
rows: [[1, 'Alice']],
},
structure,
{},
),
).toEqual([{ id: 1, name: 'Alice' }])

expect(
applyDataMapToResultSet(
{
columnTypes: [ColumnTypeEnum.Text, ColumnTypeEnum.Int32],
columnNames: ['name', 'id'],
rows: [['Bob', 2]],
},
structure,
{},
),
).toEqual([{ id: 2, name: 'Bob' }])
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading
Loading