@prisma/client-engine-runtime 7.7.0-dev.1 → 7.7.0-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +444 -2
- package/dist/index.mjs +442 -2
- package/dist/parameterization/classify.d.ts +48 -0
- package/dist/parameterization/classify.test.d.ts +1 -0
- package/dist/parameterization/parameterize-tests/batch.test.d.ts +1 -0
- package/dist/parameterization/parameterize-tests/cache-key.test.d.ts +1 -0
- package/dist/parameterization/parameterize-tests/filter-operators.test.d.ts +1 -0
- package/dist/parameterization/parameterize-tests/nested-queries.test.d.ts +1 -0
- package/dist/parameterization/parameterize-tests/placeholder-naming.test.d.ts +1 -0
- package/dist/parameterization/parameterize-tests/placeholder-reuse.test.d.ts +1 -0
- package/dist/parameterization/parameterize-tests/scalar-values.test.d.ts +1 -0
- package/dist/parameterization/parameterize-tests/structural-values.test.d.ts +1 -0
- package/dist/parameterization/parameterize-tests/tagged-values.test.d.ts +1 -0
- package/dist/parameterization/parameterize-tests/test-fixtures.d.ts +18 -0
- package/dist/parameterization/parameterize.d.ts +43 -0
- package/dist/parameterization/parameterize.test.d.ts +1 -0
- package/package.json +10 -6
package/dist/index.d.mts
CHANGED
|
@@ -4,6 +4,9 @@ import { ConnectionInfo } from '@prisma/driver-adapter-utils';
|
|
|
4
4
|
import { Context } from '@opentelemetry/api';
|
|
5
5
|
import { Decimal } from '@prisma/client-runtime-utils';
|
|
6
6
|
import type { IsolationLevel } from '@prisma/driver-adapter-utils';
|
|
7
|
+
import type { JsonBatchQuery } from '@prisma/json-protocol';
|
|
8
|
+
import type { JsonQuery } from '@prisma/json-protocol';
|
|
9
|
+
import { ParamGraph } from '@prisma/param-graph';
|
|
7
10
|
import { Span } from '@opentelemetry/api';
|
|
8
11
|
import { SpanOptions } from '@opentelemetry/api';
|
|
9
12
|
import type { SqlCommenterContext } from '@prisma/sqlcommenter';
|
|
@@ -224,6 +227,44 @@ export declare type Pagination = {
|
|
|
224
227
|
skip: number | null;
|
|
225
228
|
};
|
|
226
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Parameterizes a batch of queries using the schema-aware approach.
|
|
232
|
+
*
|
|
233
|
+
* @param batch - The batch to parameterize
|
|
234
|
+
* @param view - The ParamGraph for schema lookups
|
|
235
|
+
* @returns The parameterized batch with extracted placeholder values
|
|
236
|
+
*/
|
|
237
|
+
export declare function parameterizeBatch(batch: JsonBatchQuery, view: ParamGraph): ParameterizeBatchResult;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Result of parameterizing a batch of queries.
|
|
241
|
+
*/
|
|
242
|
+
declare interface ParameterizeBatchResult {
|
|
243
|
+
/** The batch with user data values replaced by placeholders */
|
|
244
|
+
parameterizedBatch: JsonBatchQuery;
|
|
245
|
+
/** Combined map of placeholder names to their actual values */
|
|
246
|
+
placeholderValues: Record<string, unknown>;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Parameterizes a single query using the schema-aware approach.
|
|
251
|
+
*
|
|
252
|
+
* @param query - The query to parameterize
|
|
253
|
+
* @param view - The ParamGraph for schema lookups
|
|
254
|
+
* @returns The parameterized query with extracted placeholder values
|
|
255
|
+
*/
|
|
256
|
+
export declare function parameterizeQuery(query: JsonQuery, view: ParamGraph): ParameterizeResult;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Result of parameterizing a single query.
|
|
260
|
+
*/
|
|
261
|
+
declare interface ParameterizeResult {
|
|
262
|
+
/** The query with user data values replaced by placeholders */
|
|
263
|
+
parameterizedQuery: JsonQuery;
|
|
264
|
+
/** Map of placeholder names to their actual values */
|
|
265
|
+
placeholderValues: Record<string, unknown>;
|
|
266
|
+
}
|
|
267
|
+
|
|
227
268
|
export declare interface PlaceholderFormat {
|
|
228
269
|
prefix: string;
|
|
229
270
|
hasNumbering: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ import { ConnectionInfo } from '@prisma/driver-adapter-utils';
|
|
|
4
4
|
import { Context } from '@opentelemetry/api';
|
|
5
5
|
import { Decimal } from '@prisma/client-runtime-utils';
|
|
6
6
|
import type { IsolationLevel } from '@prisma/driver-adapter-utils';
|
|
7
|
+
import type { JsonBatchQuery } from '@prisma/json-protocol';
|
|
8
|
+
import type { JsonQuery } from '@prisma/json-protocol';
|
|
9
|
+
import { ParamGraph } from '@prisma/param-graph';
|
|
7
10
|
import { Span } from '@opentelemetry/api';
|
|
8
11
|
import { SpanOptions } from '@opentelemetry/api';
|
|
9
12
|
import type { SqlCommenterContext } from '@prisma/sqlcommenter';
|
|
@@ -224,6 +227,44 @@ export declare type Pagination = {
|
|
|
224
227
|
skip: number | null;
|
|
225
228
|
};
|
|
226
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Parameterizes a batch of queries using the schema-aware approach.
|
|
232
|
+
*
|
|
233
|
+
* @param batch - The batch to parameterize
|
|
234
|
+
* @param view - The ParamGraph for schema lookups
|
|
235
|
+
* @returns The parameterized batch with extracted placeholder values
|
|
236
|
+
*/
|
|
237
|
+
export declare function parameterizeBatch(batch: JsonBatchQuery, view: ParamGraph): ParameterizeBatchResult;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Result of parameterizing a batch of queries.
|
|
241
|
+
*/
|
|
242
|
+
declare interface ParameterizeBatchResult {
|
|
243
|
+
/** The batch with user data values replaced by placeholders */
|
|
244
|
+
parameterizedBatch: JsonBatchQuery;
|
|
245
|
+
/** Combined map of placeholder names to their actual values */
|
|
246
|
+
placeholderValues: Record<string, unknown>;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Parameterizes a single query using the schema-aware approach.
|
|
251
|
+
*
|
|
252
|
+
* @param query - The query to parameterize
|
|
253
|
+
* @param view - The ParamGraph for schema lookups
|
|
254
|
+
* @returns The parameterized query with extracted placeholder values
|
|
255
|
+
*/
|
|
256
|
+
export declare function parameterizeQuery(query: JsonQuery, view: ParamGraph): ParameterizeResult;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Result of parameterizing a single query.
|
|
260
|
+
*/
|
|
261
|
+
declare interface ParameterizeResult {
|
|
262
|
+
/** The query with user data values replaced by placeholders */
|
|
263
|
+
parameterizedQuery: JsonQuery;
|
|
264
|
+
/** Map of placeholder names to their actual values */
|
|
265
|
+
placeholderValues: Record<string, unknown>;
|
|
266
|
+
}
|
|
267
|
+
|
|
227
268
|
export declare interface PlaceholderFormat {
|
|
228
269
|
prefix: string;
|
|
229
270
|
hasNumbering: boolean;
|
package/dist/index.js
CHANGED
|
@@ -45,6 +45,8 @@ __export(index_exports, {
|
|
|
45
45
|
noopTracingHelper: () => noopTracingHelper,
|
|
46
46
|
normalizeJsonProtocolValues: () => normalizeJsonProtocolValues,
|
|
47
47
|
normalizeRawJsonProtocolResponse: () => normalizeRawJsonProtocolResponse,
|
|
48
|
+
parameterizeBatch: () => parameterizeBatch,
|
|
49
|
+
parameterizeQuery: () => parameterizeQuery,
|
|
48
50
|
safeJsonStringify: () => safeJsonStringify
|
|
49
51
|
});
|
|
50
52
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -1070,10 +1072,11 @@ function renderQuery(dbQuery, scope, generators, maxChunkSize) {
|
|
|
1070
1072
|
case "templateSql": {
|
|
1071
1073
|
const chunks = dbQuery.chunkable ? chunkParams(dbQuery.fragments, args, maxChunkSize) : [args];
|
|
1072
1074
|
return chunks.map((params) => {
|
|
1073
|
-
|
|
1075
|
+
const rendered = renderTemplateSql(dbQuery.fragments, dbQuery.placeholderFormat, params, dbQuery.argTypes);
|
|
1076
|
+
if (maxChunkSize !== void 0 && rendered.args.length > maxChunkSize) {
|
|
1074
1077
|
throw new UserFacingError("The query parameter limit supported by your database is exceeded.", "P2029");
|
|
1075
1078
|
}
|
|
1076
|
-
return
|
|
1079
|
+
return rendered;
|
|
1077
1080
|
});
|
|
1078
1081
|
}
|
|
1079
1082
|
default:
|
|
@@ -1988,6 +1991,443 @@ function cloneObject(value) {
|
|
|
1988
1991
|
return (0, import_klona2.klona)(value);
|
|
1989
1992
|
}
|
|
1990
1993
|
|
|
1994
|
+
// src/parameterization/parameterize.ts
|
|
1995
|
+
var import_param_graph = require("@prisma/param-graph");
|
|
1996
|
+
|
|
1997
|
+
// src/parameterization/classify.ts
|
|
1998
|
+
var SCALAR_TAGS = /* @__PURE__ */ new Set(["DateTime", "Decimal", "BigInt", "Bytes", "Json", "Raw"]);
|
|
1999
|
+
function classifyValue(value) {
|
|
2000
|
+
if (value === null || value === void 0) {
|
|
2001
|
+
return { kind: "null" };
|
|
2002
|
+
}
|
|
2003
|
+
if (typeof value === "string") {
|
|
2004
|
+
return { kind: "primitive", value };
|
|
2005
|
+
}
|
|
2006
|
+
if (typeof value === "number") {
|
|
2007
|
+
return { kind: "primitive", value };
|
|
2008
|
+
}
|
|
2009
|
+
if (typeof value === "boolean") {
|
|
2010
|
+
return { kind: "primitive", value };
|
|
2011
|
+
}
|
|
2012
|
+
if (Array.isArray(value)) {
|
|
2013
|
+
return { kind: "array", items: value };
|
|
2014
|
+
}
|
|
2015
|
+
if (typeof value === "object") {
|
|
2016
|
+
const obj = value;
|
|
2017
|
+
if ("$type" in obj && typeof obj.$type === "string") {
|
|
2018
|
+
const tag = obj.$type;
|
|
2019
|
+
if (SCALAR_TAGS.has(tag)) {
|
|
2020
|
+
return { kind: "taggedScalar", tag, value: obj.value };
|
|
2021
|
+
}
|
|
2022
|
+
return { kind: "structural", value: obj.value };
|
|
2023
|
+
}
|
|
2024
|
+
return { kind: "object", entries: obj };
|
|
2025
|
+
}
|
|
2026
|
+
return { kind: "structural", value };
|
|
2027
|
+
}
|
|
2028
|
+
function isPlainObject(value) {
|
|
2029
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !("$type" in value);
|
|
2030
|
+
}
|
|
2031
|
+
function isTaggedValue2(value) {
|
|
2032
|
+
return typeof value === "object" && value !== null && "$type" in value && typeof value.$type === "string";
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
// src/parameterization/parameterize.ts
|
|
2036
|
+
function parameterizeQuery(query, view) {
|
|
2037
|
+
const parameterizer = new Parameterizer(view);
|
|
2038
|
+
const rootKey = query.modelName ? `${query.modelName}.${query.action}` : query.action;
|
|
2039
|
+
const root = view.root(rootKey);
|
|
2040
|
+
const parameterizedQuery = {
|
|
2041
|
+
...query,
|
|
2042
|
+
query: parameterizer.parameterizeFieldSelection(query.query, root?.argsNodeId, root?.outputNodeId)
|
|
2043
|
+
};
|
|
2044
|
+
return {
|
|
2045
|
+
parameterizedQuery,
|
|
2046
|
+
placeholderValues: parameterizer.getPlaceholderValues()
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
function parameterizeBatch(batch, view) {
|
|
2050
|
+
const parameterizer = new Parameterizer(view);
|
|
2051
|
+
const parameterizedQueries = [];
|
|
2052
|
+
for (let i = 0; i < batch.batch.length; i++) {
|
|
2053
|
+
const query = batch.batch[i];
|
|
2054
|
+
const rootKey = query.modelName ? `${query.modelName}.${query.action}` : query.action;
|
|
2055
|
+
const root = view.root(rootKey);
|
|
2056
|
+
parameterizedQueries.push({
|
|
2057
|
+
...query,
|
|
2058
|
+
query: parameterizer.parameterizeFieldSelection(query.query, root?.argsNodeId, root?.outputNodeId)
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
return {
|
|
2062
|
+
parameterizedBatch: { ...batch, batch: parameterizedQueries },
|
|
2063
|
+
placeholderValues: parameterizer.getPlaceholderValues()
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
var Parameterizer = class {
|
|
2067
|
+
#view;
|
|
2068
|
+
#placeholders = /* @__PURE__ */ new Map();
|
|
2069
|
+
#valueToPlaceholder = /* @__PURE__ */ new Map();
|
|
2070
|
+
#nextPlaceholderId = 1;
|
|
2071
|
+
constructor(view) {
|
|
2072
|
+
this.#view = view;
|
|
2073
|
+
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Returns the collected placeholder values as a plain object.
|
|
2076
|
+
*/
|
|
2077
|
+
getPlaceholderValues() {
|
|
2078
|
+
return Object.fromEntries(this.#placeholders);
|
|
2079
|
+
}
|
|
2080
|
+
/**
|
|
2081
|
+
* Gets or creates a placeholder for the given value.
|
|
2082
|
+
* If a placeholder already exists for this value, returns a reference to the existing name.
|
|
2083
|
+
* Otherwise, registers a new placeholder with a sequential name.
|
|
2084
|
+
* We reuse the placeholders for equal values because the query compiler needs to be able
|
|
2085
|
+
* to compare the values for equality for certain optimizations (at the time of writing,
|
|
2086
|
+
* only for deciding whether to use native or emulated upserts).
|
|
2087
|
+
*/
|
|
2088
|
+
#getOrCreatePlaceholder(value, type) {
|
|
2089
|
+
const valueKey = createValueKey(value, type);
|
|
2090
|
+
const existingName = this.#valueToPlaceholder.get(valueKey);
|
|
2091
|
+
if (existingName !== void 0) {
|
|
2092
|
+
return createPlaceholder(existingName, type);
|
|
2093
|
+
}
|
|
2094
|
+
const name = `%${this.#nextPlaceholderId++}`;
|
|
2095
|
+
this.#valueToPlaceholder.set(valueKey, name);
|
|
2096
|
+
this.#placeholders.set(name, value);
|
|
2097
|
+
return createPlaceholder(name, type);
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Parameterizes a field selection (arguments + selection).
|
|
2101
|
+
*/
|
|
2102
|
+
parameterizeFieldSelection(sel, argsNodeId, outNodeId) {
|
|
2103
|
+
const argsNode = this.#view.inputNode(argsNodeId);
|
|
2104
|
+
const outNode = this.#view.outputNode(outNodeId);
|
|
2105
|
+
const result = { ...sel };
|
|
2106
|
+
if (sel.arguments && sel.arguments.$type !== "Raw") {
|
|
2107
|
+
result.arguments = this.#parameterizeObject(sel.arguments, argsNode);
|
|
2108
|
+
}
|
|
2109
|
+
if (sel.selection) {
|
|
2110
|
+
result.selection = this.#parameterizeSelection(sel.selection, outNode);
|
|
2111
|
+
}
|
|
2112
|
+
return result;
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Parameterizes an object by traversing its fields with the input node.
|
|
2116
|
+
*/
|
|
2117
|
+
#parameterizeObject(obj, node) {
|
|
2118
|
+
if (!node) {
|
|
2119
|
+
return obj;
|
|
2120
|
+
}
|
|
2121
|
+
const result = {};
|
|
2122
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2123
|
+
const edge = this.#view.inputEdge(node, key);
|
|
2124
|
+
if (edge) {
|
|
2125
|
+
result[key] = this.#parameterizeValue(value, edge);
|
|
2126
|
+
} else {
|
|
2127
|
+
result[key] = value;
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
return result;
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* Core parameterization logic for a single value.
|
|
2134
|
+
*/
|
|
2135
|
+
#parameterizeValue(value, edge) {
|
|
2136
|
+
const classified = classifyValue(value);
|
|
2137
|
+
switch (classified.kind) {
|
|
2138
|
+
case "null":
|
|
2139
|
+
return value;
|
|
2140
|
+
case "structural":
|
|
2141
|
+
return value;
|
|
2142
|
+
case "primitive":
|
|
2143
|
+
return this.#handlePrimitive(classified.value, edge);
|
|
2144
|
+
case "taggedScalar":
|
|
2145
|
+
return this.#handleTaggedScalar(value, classified.tag, edge);
|
|
2146
|
+
case "array":
|
|
2147
|
+
return this.#handleArray(classified.items, value, edge);
|
|
2148
|
+
case "object":
|
|
2149
|
+
return this.#handleObject(classified.entries, edge);
|
|
2150
|
+
default:
|
|
2151
|
+
throw new Error(`Unknown value kind ${classified.kind}`);
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
/**
|
|
2155
|
+
* Handles parameterization of primitive values (string, number, boolean).
|
|
2156
|
+
*/
|
|
2157
|
+
#handlePrimitive(value, edge) {
|
|
2158
|
+
if ((0, import_param_graph.hasFlag)(edge, import_param_graph.EdgeFlag.ParamEnum) && edge.enumNameIndex !== void 0 && typeof value === "string") {
|
|
2159
|
+
const enumValues = this.#view.enumValues(edge);
|
|
2160
|
+
if (enumValues && Object.hasOwn(enumValues, value)) {
|
|
2161
|
+
const type2 = { type: "Enum" };
|
|
2162
|
+
return this.#getOrCreatePlaceholder(enumValues[value], type2);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
if (!(0, import_param_graph.hasFlag)(edge, import_param_graph.EdgeFlag.ParamScalar)) {
|
|
2166
|
+
return value;
|
|
2167
|
+
}
|
|
2168
|
+
const mask = (0, import_param_graph.getScalarMask)(edge);
|
|
2169
|
+
if (mask === 0) {
|
|
2170
|
+
return value;
|
|
2171
|
+
}
|
|
2172
|
+
const type = getPrimitivePlaceholderType(value);
|
|
2173
|
+
if (!matchesPrimitiveMask(type, mask)) {
|
|
2174
|
+
return value;
|
|
2175
|
+
}
|
|
2176
|
+
if (mask & import_param_graph.ScalarMask.Json) {
|
|
2177
|
+
value = JSON.stringify(value);
|
|
2178
|
+
}
|
|
2179
|
+
return this.#getOrCreatePlaceholder(value, type);
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Handles parameterization of tagged scalar values (DateTime, Decimal, etc.).
|
|
2183
|
+
*/
|
|
2184
|
+
#handleTaggedScalar(tagged, tag, edge) {
|
|
2185
|
+
if (!(0, import_param_graph.hasFlag)(edge, import_param_graph.EdgeFlag.ParamScalar)) {
|
|
2186
|
+
return tagged;
|
|
2187
|
+
}
|
|
2188
|
+
const mask = (0, import_param_graph.getScalarMask)(edge);
|
|
2189
|
+
if (mask === 0 || !matchesTaggedMask(tag, mask)) {
|
|
2190
|
+
return tagged;
|
|
2191
|
+
}
|
|
2192
|
+
const type = getTaggedPlaceholderType(tagged.$type);
|
|
2193
|
+
const decoded = decodeTaggedValue(tagged);
|
|
2194
|
+
return this.#getOrCreatePlaceholder(decoded, type);
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Handles parameterization of array values.
|
|
2198
|
+
*/
|
|
2199
|
+
#handleArray(items, originalValue, edge) {
|
|
2200
|
+
if ((0, import_param_graph.hasFlag)(edge, import_param_graph.EdgeFlag.ParamScalar) && (0, import_param_graph.getScalarMask)(edge) & import_param_graph.ScalarMask.Json) {
|
|
2201
|
+
const jsonValue = safeJsonStringify(deserializeJsonObject(items));
|
|
2202
|
+
const type = { type: "Json" };
|
|
2203
|
+
return this.#getOrCreatePlaceholder(jsonValue, type);
|
|
2204
|
+
}
|
|
2205
|
+
if ((0, import_param_graph.hasFlag)(edge, import_param_graph.EdgeFlag.ParamEnum)) {
|
|
2206
|
+
const enumValues = this.#view.enumValues(edge);
|
|
2207
|
+
if (enumValues && items.every((item) => typeof item === "string" && Object.hasOwn(enumValues, item))) {
|
|
2208
|
+
const type = { type: "List", inner: { type: "Enum" } };
|
|
2209
|
+
return this.#getOrCreatePlaceholder(items, type);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
if ((0, import_param_graph.hasFlag)(edge, import_param_graph.EdgeFlag.ParamListScalar)) {
|
|
2213
|
+
const allValid = items.every((item) => validateListElement(item, edge));
|
|
2214
|
+
if (allValid && items.length > 0) {
|
|
2215
|
+
const decodedItems = items.map((item) => decodeIfTagged(item));
|
|
2216
|
+
const innerType = inferListElementType(items);
|
|
2217
|
+
const type = { type: "List", inner: innerType };
|
|
2218
|
+
return this.#getOrCreatePlaceholder(decodedItems, type);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
if ((0, import_param_graph.hasFlag)(edge, import_param_graph.EdgeFlag.ListObject)) {
|
|
2222
|
+
const childNode = this.#view.inputNode(edge.childNodeId);
|
|
2223
|
+
if (childNode) {
|
|
2224
|
+
return items.map((item) => {
|
|
2225
|
+
if (isPlainObject(item)) {
|
|
2226
|
+
return this.#parameterizeObject(item, childNode);
|
|
2227
|
+
}
|
|
2228
|
+
return item;
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
return originalValue;
|
|
2233
|
+
}
|
|
2234
|
+
/**
|
|
2235
|
+
* Handles parameterization of object values.
|
|
2236
|
+
*/
|
|
2237
|
+
#handleObject(obj, edge) {
|
|
2238
|
+
if ((0, import_param_graph.hasFlag)(edge, import_param_graph.EdgeFlag.Object)) {
|
|
2239
|
+
const childNode = this.#view.inputNode(edge.childNodeId);
|
|
2240
|
+
if (childNode) {
|
|
2241
|
+
return this.#parameterizeObject(obj, childNode);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
const mask = (0, import_param_graph.getScalarMask)(edge);
|
|
2245
|
+
if (mask & import_param_graph.ScalarMask.Json) {
|
|
2246
|
+
const jsonValue = safeJsonStringify(deserializeJsonObject(obj));
|
|
2247
|
+
const type = { type: "Json" };
|
|
2248
|
+
return this.#getOrCreatePlaceholder(jsonValue, type);
|
|
2249
|
+
}
|
|
2250
|
+
return obj;
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Parameterizes a selection set using output nodes.
|
|
2254
|
+
*/
|
|
2255
|
+
#parameterizeSelection(selection, node) {
|
|
2256
|
+
if (!selection || !node) {
|
|
2257
|
+
return selection;
|
|
2258
|
+
}
|
|
2259
|
+
const result = {};
|
|
2260
|
+
for (const [key, value] of Object.entries(selection)) {
|
|
2261
|
+
if (key === "$scalars" || key === "$composites" || typeof value === "boolean") {
|
|
2262
|
+
result[key] = value;
|
|
2263
|
+
continue;
|
|
2264
|
+
}
|
|
2265
|
+
const edge = this.#view.outputEdge(node, key);
|
|
2266
|
+
if (edge) {
|
|
2267
|
+
const nested = value;
|
|
2268
|
+
const argsNode = this.#view.inputNode(edge.argsNodeId);
|
|
2269
|
+
const childOutNode = this.#view.outputNode(edge.outputNodeId);
|
|
2270
|
+
const processedValue = {
|
|
2271
|
+
selection: nested.selection ? this.#parameterizeSelection(nested.selection, childOutNode) : {}
|
|
2272
|
+
};
|
|
2273
|
+
if (nested.arguments) {
|
|
2274
|
+
processedValue.arguments = this.#parameterizeObject(nested.arguments, argsNode);
|
|
2275
|
+
}
|
|
2276
|
+
result[key] = processedValue;
|
|
2277
|
+
} else {
|
|
2278
|
+
result[key] = value;
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
return result;
|
|
2282
|
+
}
|
|
2283
|
+
};
|
|
2284
|
+
function createPlaceholder(name, type) {
|
|
2285
|
+
return { $type: "Param", value: { name, ...type } };
|
|
2286
|
+
}
|
|
2287
|
+
function serializePlaceholderType(type) {
|
|
2288
|
+
if (type.type === "List") {
|
|
2289
|
+
return `List<${serializePlaceholderType(type.inner)}>`;
|
|
2290
|
+
}
|
|
2291
|
+
return type.type;
|
|
2292
|
+
}
|
|
2293
|
+
function serializeValue(value) {
|
|
2294
|
+
if (ArrayBuffer.isView(value)) {
|
|
2295
|
+
const bufView = Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
2296
|
+
return bufView.toString("base64");
|
|
2297
|
+
}
|
|
2298
|
+
return JSON.stringify(value);
|
|
2299
|
+
}
|
|
2300
|
+
function createValueKey(value, type) {
|
|
2301
|
+
const typeKey = serializePlaceholderType(type);
|
|
2302
|
+
const valueKey = serializeValue(value);
|
|
2303
|
+
return `${typeKey}:${valueKey}`;
|
|
2304
|
+
}
|
|
2305
|
+
var MAX_INT = 2 ** 31 - 1;
|
|
2306
|
+
var MIN_INT = -(2 ** 31);
|
|
2307
|
+
function getPrimitivePlaceholderType(value) {
|
|
2308
|
+
switch (typeof value) {
|
|
2309
|
+
case "boolean":
|
|
2310
|
+
return { type: "Boolean" };
|
|
2311
|
+
case "number":
|
|
2312
|
+
if (!Number.isInteger(value)) {
|
|
2313
|
+
return { type: "Float" };
|
|
2314
|
+
}
|
|
2315
|
+
if (MIN_INT <= value && value <= MAX_INT) {
|
|
2316
|
+
return { type: "Int" };
|
|
2317
|
+
}
|
|
2318
|
+
return { type: "BigInt" };
|
|
2319
|
+
case "string":
|
|
2320
|
+
return { type: "String" };
|
|
2321
|
+
default:
|
|
2322
|
+
throw new Error("unreachable");
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
function matchesPrimitiveMask({ type }, mask) {
|
|
2326
|
+
switch (type) {
|
|
2327
|
+
case "Boolean":
|
|
2328
|
+
return (mask & import_param_graph.ScalarMask.Boolean) !== 0;
|
|
2329
|
+
case "Int":
|
|
2330
|
+
return (mask & (import_param_graph.ScalarMask.Int | import_param_graph.ScalarMask.BigInt | import_param_graph.ScalarMask.Float)) !== 0;
|
|
2331
|
+
case "BigInt":
|
|
2332
|
+
return (mask & import_param_graph.ScalarMask.BigInt) !== 0;
|
|
2333
|
+
case "Float":
|
|
2334
|
+
return (mask & import_param_graph.ScalarMask.Float) !== 0;
|
|
2335
|
+
case "String":
|
|
2336
|
+
return (mask & import_param_graph.ScalarMask.String) !== 0;
|
|
2337
|
+
default:
|
|
2338
|
+
return false;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
function getTaggedPlaceholderType(tag) {
|
|
2342
|
+
switch (tag) {
|
|
2343
|
+
case "BigInt":
|
|
2344
|
+
case "Bytes":
|
|
2345
|
+
case "DateTime":
|
|
2346
|
+
case "Json":
|
|
2347
|
+
return { type: tag };
|
|
2348
|
+
case "Decimal":
|
|
2349
|
+
return { type: "Float" };
|
|
2350
|
+
default:
|
|
2351
|
+
return void 0;
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
function inferListElementType(items) {
|
|
2355
|
+
let widest = { type: "Any" };
|
|
2356
|
+
for (const item of items) {
|
|
2357
|
+
const classified = classifyValue(item);
|
|
2358
|
+
let itemType;
|
|
2359
|
+
switch (classified.kind) {
|
|
2360
|
+
case "primitive":
|
|
2361
|
+
itemType = getPrimitivePlaceholderType(classified.value);
|
|
2362
|
+
break;
|
|
2363
|
+
case "taggedScalar":
|
|
2364
|
+
itemType = getTaggedPlaceholderType(classified.tag) ?? { type: "Any" };
|
|
2365
|
+
break;
|
|
2366
|
+
default:
|
|
2367
|
+
return { type: "Any" };
|
|
2368
|
+
}
|
|
2369
|
+
widest = widenType(widest, itemType);
|
|
2370
|
+
}
|
|
2371
|
+
return widest;
|
|
2372
|
+
}
|
|
2373
|
+
function widenType(a, b) {
|
|
2374
|
+
if (a.type === "Any") return b;
|
|
2375
|
+
if (b.type === "Any") return a;
|
|
2376
|
+
if (a.type === b.type) return a;
|
|
2377
|
+
const NUMERIC_WIDTH = { Int: 0, BigInt: 1, Float: 2 };
|
|
2378
|
+
const aWidth = NUMERIC_WIDTH[a.type];
|
|
2379
|
+
const bWidth = NUMERIC_WIDTH[b.type];
|
|
2380
|
+
if (aWidth !== void 0 && bWidth !== void 0) {
|
|
2381
|
+
return aWidth >= bWidth ? a : b;
|
|
2382
|
+
}
|
|
2383
|
+
return { type: "Any" };
|
|
2384
|
+
}
|
|
2385
|
+
function matchesTaggedMask(tag, mask) {
|
|
2386
|
+
switch (tag) {
|
|
2387
|
+
case "DateTime":
|
|
2388
|
+
return (mask & import_param_graph.ScalarMask.DateTime) !== 0;
|
|
2389
|
+
case "Decimal":
|
|
2390
|
+
return (mask & import_param_graph.ScalarMask.Decimal) !== 0;
|
|
2391
|
+
case "BigInt":
|
|
2392
|
+
return (mask & import_param_graph.ScalarMask.BigInt) !== 0;
|
|
2393
|
+
case "Bytes":
|
|
2394
|
+
return (mask & import_param_graph.ScalarMask.Bytes) !== 0;
|
|
2395
|
+
case "Json":
|
|
2396
|
+
return (mask & import_param_graph.ScalarMask.Json) !== 0;
|
|
2397
|
+
default:
|
|
2398
|
+
return false;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
function validateListElement(item, edge) {
|
|
2402
|
+
const classified = classifyValue(item);
|
|
2403
|
+
switch (classified.kind) {
|
|
2404
|
+
case "structural":
|
|
2405
|
+
return false;
|
|
2406
|
+
case "null":
|
|
2407
|
+
return false;
|
|
2408
|
+
case "primitive": {
|
|
2409
|
+
const type = getPrimitivePlaceholderType(classified.value);
|
|
2410
|
+
const mask = (0, import_param_graph.getScalarMask)(edge);
|
|
2411
|
+
return mask !== 0 && matchesPrimitiveMask(type, mask);
|
|
2412
|
+
}
|
|
2413
|
+
case "taggedScalar": {
|
|
2414
|
+
const mask = (0, import_param_graph.getScalarMask)(edge);
|
|
2415
|
+
return mask !== 0 && matchesTaggedMask(classified.tag, mask);
|
|
2416
|
+
}
|
|
2417
|
+
default:
|
|
2418
|
+
return false;
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
function decodeIfTagged(value) {
|
|
2422
|
+
if (isTaggedValue2(value)) {
|
|
2423
|
+
return decodeTaggedValue(value);
|
|
2424
|
+
}
|
|
2425
|
+
return value;
|
|
2426
|
+
}
|
|
2427
|
+
function decodeTaggedValue(tagged) {
|
|
2428
|
+
return tagged.value;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
1991
2431
|
// src/raw-json-protocol.ts
|
|
1992
2432
|
var import_client_runtime_utils4 = require("@prisma/client-runtime-utils");
|
|
1993
2433
|
function normalizeRawJsonProtocolResponse(response) {
|
|
@@ -2477,5 +2917,7 @@ function createTimeoutIfDefined(cb, ms) {
|
|
|
2477
2917
|
noopTracingHelper,
|
|
2478
2918
|
normalizeJsonProtocolValues,
|
|
2479
2919
|
normalizeRawJsonProtocolResponse,
|
|
2920
|
+
parameterizeBatch,
|
|
2921
|
+
parameterizeQuery,
|
|
2480
2922
|
safeJsonStringify
|
|
2481
2923
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1019,10 +1019,11 @@ function renderQuery(dbQuery, scope, generators, maxChunkSize) {
|
|
|
1019
1019
|
case "templateSql": {
|
|
1020
1020
|
const chunks = dbQuery.chunkable ? chunkParams(dbQuery.fragments, args, maxChunkSize) : [args];
|
|
1021
1021
|
return chunks.map((params) => {
|
|
1022
|
-
|
|
1022
|
+
const rendered = renderTemplateSql(dbQuery.fragments, dbQuery.placeholderFormat, params, dbQuery.argTypes);
|
|
1023
|
+
if (maxChunkSize !== void 0 && rendered.args.length > maxChunkSize) {
|
|
1023
1024
|
throw new UserFacingError("The query parameter limit supported by your database is exceeded.", "P2029");
|
|
1024
1025
|
}
|
|
1025
|
-
return
|
|
1026
|
+
return rendered;
|
|
1026
1027
|
});
|
|
1027
1028
|
}
|
|
1028
1029
|
default:
|
|
@@ -1937,6 +1938,443 @@ function cloneObject(value) {
|
|
|
1937
1938
|
return klona2(value);
|
|
1938
1939
|
}
|
|
1939
1940
|
|
|
1941
|
+
// src/parameterization/parameterize.ts
|
|
1942
|
+
import { EdgeFlag, getScalarMask, hasFlag, ScalarMask } from "@prisma/param-graph";
|
|
1943
|
+
|
|
1944
|
+
// src/parameterization/classify.ts
|
|
1945
|
+
var SCALAR_TAGS = /* @__PURE__ */ new Set(["DateTime", "Decimal", "BigInt", "Bytes", "Json", "Raw"]);
|
|
1946
|
+
function classifyValue(value) {
|
|
1947
|
+
if (value === null || value === void 0) {
|
|
1948
|
+
return { kind: "null" };
|
|
1949
|
+
}
|
|
1950
|
+
if (typeof value === "string") {
|
|
1951
|
+
return { kind: "primitive", value };
|
|
1952
|
+
}
|
|
1953
|
+
if (typeof value === "number") {
|
|
1954
|
+
return { kind: "primitive", value };
|
|
1955
|
+
}
|
|
1956
|
+
if (typeof value === "boolean") {
|
|
1957
|
+
return { kind: "primitive", value };
|
|
1958
|
+
}
|
|
1959
|
+
if (Array.isArray(value)) {
|
|
1960
|
+
return { kind: "array", items: value };
|
|
1961
|
+
}
|
|
1962
|
+
if (typeof value === "object") {
|
|
1963
|
+
const obj = value;
|
|
1964
|
+
if ("$type" in obj && typeof obj.$type === "string") {
|
|
1965
|
+
const tag = obj.$type;
|
|
1966
|
+
if (SCALAR_TAGS.has(tag)) {
|
|
1967
|
+
return { kind: "taggedScalar", tag, value: obj.value };
|
|
1968
|
+
}
|
|
1969
|
+
return { kind: "structural", value: obj.value };
|
|
1970
|
+
}
|
|
1971
|
+
return { kind: "object", entries: obj };
|
|
1972
|
+
}
|
|
1973
|
+
return { kind: "structural", value };
|
|
1974
|
+
}
|
|
1975
|
+
function isPlainObject(value) {
|
|
1976
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !("$type" in value);
|
|
1977
|
+
}
|
|
1978
|
+
function isTaggedValue2(value) {
|
|
1979
|
+
return typeof value === "object" && value !== null && "$type" in value && typeof value.$type === "string";
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
// src/parameterization/parameterize.ts
|
|
1983
|
+
function parameterizeQuery(query, view) {
|
|
1984
|
+
const parameterizer = new Parameterizer(view);
|
|
1985
|
+
const rootKey = query.modelName ? `${query.modelName}.${query.action}` : query.action;
|
|
1986
|
+
const root = view.root(rootKey);
|
|
1987
|
+
const parameterizedQuery = {
|
|
1988
|
+
...query,
|
|
1989
|
+
query: parameterizer.parameterizeFieldSelection(query.query, root?.argsNodeId, root?.outputNodeId)
|
|
1990
|
+
};
|
|
1991
|
+
return {
|
|
1992
|
+
parameterizedQuery,
|
|
1993
|
+
placeholderValues: parameterizer.getPlaceholderValues()
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
function parameterizeBatch(batch, view) {
|
|
1997
|
+
const parameterizer = new Parameterizer(view);
|
|
1998
|
+
const parameterizedQueries = [];
|
|
1999
|
+
for (let i = 0; i < batch.batch.length; i++) {
|
|
2000
|
+
const query = batch.batch[i];
|
|
2001
|
+
const rootKey = query.modelName ? `${query.modelName}.${query.action}` : query.action;
|
|
2002
|
+
const root = view.root(rootKey);
|
|
2003
|
+
parameterizedQueries.push({
|
|
2004
|
+
...query,
|
|
2005
|
+
query: parameterizer.parameterizeFieldSelection(query.query, root?.argsNodeId, root?.outputNodeId)
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
return {
|
|
2009
|
+
parameterizedBatch: { ...batch, batch: parameterizedQueries },
|
|
2010
|
+
placeholderValues: parameterizer.getPlaceholderValues()
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
var Parameterizer = class {
|
|
2014
|
+
#view;
|
|
2015
|
+
#placeholders = /* @__PURE__ */ new Map();
|
|
2016
|
+
#valueToPlaceholder = /* @__PURE__ */ new Map();
|
|
2017
|
+
#nextPlaceholderId = 1;
|
|
2018
|
+
constructor(view) {
|
|
2019
|
+
this.#view = view;
|
|
2020
|
+
}
|
|
2021
|
+
/**
|
|
2022
|
+
* Returns the collected placeholder values as a plain object.
|
|
2023
|
+
*/
|
|
2024
|
+
getPlaceholderValues() {
|
|
2025
|
+
return Object.fromEntries(this.#placeholders);
|
|
2026
|
+
}
|
|
2027
|
+
/**
|
|
2028
|
+
* Gets or creates a placeholder for the given value.
|
|
2029
|
+
* If a placeholder already exists for this value, returns a reference to the existing name.
|
|
2030
|
+
* Otherwise, registers a new placeholder with a sequential name.
|
|
2031
|
+
* We reuse the placeholders for equal values because the query compiler needs to be able
|
|
2032
|
+
* to compare the values for equality for certain optimizations (at the time of writing,
|
|
2033
|
+
* only for deciding whether to use native or emulated upserts).
|
|
2034
|
+
*/
|
|
2035
|
+
#getOrCreatePlaceholder(value, type) {
|
|
2036
|
+
const valueKey = createValueKey(value, type);
|
|
2037
|
+
const existingName = this.#valueToPlaceholder.get(valueKey);
|
|
2038
|
+
if (existingName !== void 0) {
|
|
2039
|
+
return createPlaceholder(existingName, type);
|
|
2040
|
+
}
|
|
2041
|
+
const name = `%${this.#nextPlaceholderId++}`;
|
|
2042
|
+
this.#valueToPlaceholder.set(valueKey, name);
|
|
2043
|
+
this.#placeholders.set(name, value);
|
|
2044
|
+
return createPlaceholder(name, type);
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
* Parameterizes a field selection (arguments + selection).
|
|
2048
|
+
*/
|
|
2049
|
+
parameterizeFieldSelection(sel, argsNodeId, outNodeId) {
|
|
2050
|
+
const argsNode = this.#view.inputNode(argsNodeId);
|
|
2051
|
+
const outNode = this.#view.outputNode(outNodeId);
|
|
2052
|
+
const result = { ...sel };
|
|
2053
|
+
if (sel.arguments && sel.arguments.$type !== "Raw") {
|
|
2054
|
+
result.arguments = this.#parameterizeObject(sel.arguments, argsNode);
|
|
2055
|
+
}
|
|
2056
|
+
if (sel.selection) {
|
|
2057
|
+
result.selection = this.#parameterizeSelection(sel.selection, outNode);
|
|
2058
|
+
}
|
|
2059
|
+
return result;
|
|
2060
|
+
}
|
|
2061
|
+
/**
|
|
2062
|
+
* Parameterizes an object by traversing its fields with the input node.
|
|
2063
|
+
*/
|
|
2064
|
+
#parameterizeObject(obj, node) {
|
|
2065
|
+
if (!node) {
|
|
2066
|
+
return obj;
|
|
2067
|
+
}
|
|
2068
|
+
const result = {};
|
|
2069
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2070
|
+
const edge = this.#view.inputEdge(node, key);
|
|
2071
|
+
if (edge) {
|
|
2072
|
+
result[key] = this.#parameterizeValue(value, edge);
|
|
2073
|
+
} else {
|
|
2074
|
+
result[key] = value;
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
return result;
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Core parameterization logic for a single value.
|
|
2081
|
+
*/
|
|
2082
|
+
#parameterizeValue(value, edge) {
|
|
2083
|
+
const classified = classifyValue(value);
|
|
2084
|
+
switch (classified.kind) {
|
|
2085
|
+
case "null":
|
|
2086
|
+
return value;
|
|
2087
|
+
case "structural":
|
|
2088
|
+
return value;
|
|
2089
|
+
case "primitive":
|
|
2090
|
+
return this.#handlePrimitive(classified.value, edge);
|
|
2091
|
+
case "taggedScalar":
|
|
2092
|
+
return this.#handleTaggedScalar(value, classified.tag, edge);
|
|
2093
|
+
case "array":
|
|
2094
|
+
return this.#handleArray(classified.items, value, edge);
|
|
2095
|
+
case "object":
|
|
2096
|
+
return this.#handleObject(classified.entries, edge);
|
|
2097
|
+
default:
|
|
2098
|
+
throw new Error(`Unknown value kind ${classified.kind}`);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Handles parameterization of primitive values (string, number, boolean).
|
|
2103
|
+
*/
|
|
2104
|
+
#handlePrimitive(value, edge) {
|
|
2105
|
+
if (hasFlag(edge, EdgeFlag.ParamEnum) && edge.enumNameIndex !== void 0 && typeof value === "string") {
|
|
2106
|
+
const enumValues = this.#view.enumValues(edge);
|
|
2107
|
+
if (enumValues && Object.hasOwn(enumValues, value)) {
|
|
2108
|
+
const type2 = { type: "Enum" };
|
|
2109
|
+
return this.#getOrCreatePlaceholder(enumValues[value], type2);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
if (!hasFlag(edge, EdgeFlag.ParamScalar)) {
|
|
2113
|
+
return value;
|
|
2114
|
+
}
|
|
2115
|
+
const mask = getScalarMask(edge);
|
|
2116
|
+
if (mask === 0) {
|
|
2117
|
+
return value;
|
|
2118
|
+
}
|
|
2119
|
+
const type = getPrimitivePlaceholderType(value);
|
|
2120
|
+
if (!matchesPrimitiveMask(type, mask)) {
|
|
2121
|
+
return value;
|
|
2122
|
+
}
|
|
2123
|
+
if (mask & ScalarMask.Json) {
|
|
2124
|
+
value = JSON.stringify(value);
|
|
2125
|
+
}
|
|
2126
|
+
return this.#getOrCreatePlaceholder(value, type);
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* Handles parameterization of tagged scalar values (DateTime, Decimal, etc.).
|
|
2130
|
+
*/
|
|
2131
|
+
#handleTaggedScalar(tagged, tag, edge) {
|
|
2132
|
+
if (!hasFlag(edge, EdgeFlag.ParamScalar)) {
|
|
2133
|
+
return tagged;
|
|
2134
|
+
}
|
|
2135
|
+
const mask = getScalarMask(edge);
|
|
2136
|
+
if (mask === 0 || !matchesTaggedMask(tag, mask)) {
|
|
2137
|
+
return tagged;
|
|
2138
|
+
}
|
|
2139
|
+
const type = getTaggedPlaceholderType(tagged.$type);
|
|
2140
|
+
const decoded = decodeTaggedValue(tagged);
|
|
2141
|
+
return this.#getOrCreatePlaceholder(decoded, type);
|
|
2142
|
+
}
|
|
2143
|
+
/**
|
|
2144
|
+
* Handles parameterization of array values.
|
|
2145
|
+
*/
|
|
2146
|
+
#handleArray(items, originalValue, edge) {
|
|
2147
|
+
if (hasFlag(edge, EdgeFlag.ParamScalar) && getScalarMask(edge) & ScalarMask.Json) {
|
|
2148
|
+
const jsonValue = safeJsonStringify(deserializeJsonObject(items));
|
|
2149
|
+
const type = { type: "Json" };
|
|
2150
|
+
return this.#getOrCreatePlaceholder(jsonValue, type);
|
|
2151
|
+
}
|
|
2152
|
+
if (hasFlag(edge, EdgeFlag.ParamEnum)) {
|
|
2153
|
+
const enumValues = this.#view.enumValues(edge);
|
|
2154
|
+
if (enumValues && items.every((item) => typeof item === "string" && Object.hasOwn(enumValues, item))) {
|
|
2155
|
+
const type = { type: "List", inner: { type: "Enum" } };
|
|
2156
|
+
return this.#getOrCreatePlaceholder(items, type);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
if (hasFlag(edge, EdgeFlag.ParamListScalar)) {
|
|
2160
|
+
const allValid = items.every((item) => validateListElement(item, edge));
|
|
2161
|
+
if (allValid && items.length > 0) {
|
|
2162
|
+
const decodedItems = items.map((item) => decodeIfTagged(item));
|
|
2163
|
+
const innerType = inferListElementType(items);
|
|
2164
|
+
const type = { type: "List", inner: innerType };
|
|
2165
|
+
return this.#getOrCreatePlaceholder(decodedItems, type);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
if (hasFlag(edge, EdgeFlag.ListObject)) {
|
|
2169
|
+
const childNode = this.#view.inputNode(edge.childNodeId);
|
|
2170
|
+
if (childNode) {
|
|
2171
|
+
return items.map((item) => {
|
|
2172
|
+
if (isPlainObject(item)) {
|
|
2173
|
+
return this.#parameterizeObject(item, childNode);
|
|
2174
|
+
}
|
|
2175
|
+
return item;
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
return originalValue;
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Handles parameterization of object values.
|
|
2183
|
+
*/
|
|
2184
|
+
#handleObject(obj, edge) {
|
|
2185
|
+
if (hasFlag(edge, EdgeFlag.Object)) {
|
|
2186
|
+
const childNode = this.#view.inputNode(edge.childNodeId);
|
|
2187
|
+
if (childNode) {
|
|
2188
|
+
return this.#parameterizeObject(obj, childNode);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
const mask = getScalarMask(edge);
|
|
2192
|
+
if (mask & ScalarMask.Json) {
|
|
2193
|
+
const jsonValue = safeJsonStringify(deserializeJsonObject(obj));
|
|
2194
|
+
const type = { type: "Json" };
|
|
2195
|
+
return this.#getOrCreatePlaceholder(jsonValue, type);
|
|
2196
|
+
}
|
|
2197
|
+
return obj;
|
|
2198
|
+
}
|
|
2199
|
+
/**
|
|
2200
|
+
* Parameterizes a selection set using output nodes.
|
|
2201
|
+
*/
|
|
2202
|
+
#parameterizeSelection(selection, node) {
|
|
2203
|
+
if (!selection || !node) {
|
|
2204
|
+
return selection;
|
|
2205
|
+
}
|
|
2206
|
+
const result = {};
|
|
2207
|
+
for (const [key, value] of Object.entries(selection)) {
|
|
2208
|
+
if (key === "$scalars" || key === "$composites" || typeof value === "boolean") {
|
|
2209
|
+
result[key] = value;
|
|
2210
|
+
continue;
|
|
2211
|
+
}
|
|
2212
|
+
const edge = this.#view.outputEdge(node, key);
|
|
2213
|
+
if (edge) {
|
|
2214
|
+
const nested = value;
|
|
2215
|
+
const argsNode = this.#view.inputNode(edge.argsNodeId);
|
|
2216
|
+
const childOutNode = this.#view.outputNode(edge.outputNodeId);
|
|
2217
|
+
const processedValue = {
|
|
2218
|
+
selection: nested.selection ? this.#parameterizeSelection(nested.selection, childOutNode) : {}
|
|
2219
|
+
};
|
|
2220
|
+
if (nested.arguments) {
|
|
2221
|
+
processedValue.arguments = this.#parameterizeObject(nested.arguments, argsNode);
|
|
2222
|
+
}
|
|
2223
|
+
result[key] = processedValue;
|
|
2224
|
+
} else {
|
|
2225
|
+
result[key] = value;
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
return result;
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
function createPlaceholder(name, type) {
|
|
2232
|
+
return { $type: "Param", value: { name, ...type } };
|
|
2233
|
+
}
|
|
2234
|
+
function serializePlaceholderType(type) {
|
|
2235
|
+
if (type.type === "List") {
|
|
2236
|
+
return `List<${serializePlaceholderType(type.inner)}>`;
|
|
2237
|
+
}
|
|
2238
|
+
return type.type;
|
|
2239
|
+
}
|
|
2240
|
+
function serializeValue(value) {
|
|
2241
|
+
if (ArrayBuffer.isView(value)) {
|
|
2242
|
+
const bufView = Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
2243
|
+
return bufView.toString("base64");
|
|
2244
|
+
}
|
|
2245
|
+
return JSON.stringify(value);
|
|
2246
|
+
}
|
|
2247
|
+
function createValueKey(value, type) {
|
|
2248
|
+
const typeKey = serializePlaceholderType(type);
|
|
2249
|
+
const valueKey = serializeValue(value);
|
|
2250
|
+
return `${typeKey}:${valueKey}`;
|
|
2251
|
+
}
|
|
2252
|
+
var MAX_INT = 2 ** 31 - 1;
|
|
2253
|
+
var MIN_INT = -(2 ** 31);
|
|
2254
|
+
function getPrimitivePlaceholderType(value) {
|
|
2255
|
+
switch (typeof value) {
|
|
2256
|
+
case "boolean":
|
|
2257
|
+
return { type: "Boolean" };
|
|
2258
|
+
case "number":
|
|
2259
|
+
if (!Number.isInteger(value)) {
|
|
2260
|
+
return { type: "Float" };
|
|
2261
|
+
}
|
|
2262
|
+
if (MIN_INT <= value && value <= MAX_INT) {
|
|
2263
|
+
return { type: "Int" };
|
|
2264
|
+
}
|
|
2265
|
+
return { type: "BigInt" };
|
|
2266
|
+
case "string":
|
|
2267
|
+
return { type: "String" };
|
|
2268
|
+
default:
|
|
2269
|
+
throw new Error("unreachable");
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
function matchesPrimitiveMask({ type }, mask) {
|
|
2273
|
+
switch (type) {
|
|
2274
|
+
case "Boolean":
|
|
2275
|
+
return (mask & ScalarMask.Boolean) !== 0;
|
|
2276
|
+
case "Int":
|
|
2277
|
+
return (mask & (ScalarMask.Int | ScalarMask.BigInt | ScalarMask.Float)) !== 0;
|
|
2278
|
+
case "BigInt":
|
|
2279
|
+
return (mask & ScalarMask.BigInt) !== 0;
|
|
2280
|
+
case "Float":
|
|
2281
|
+
return (mask & ScalarMask.Float) !== 0;
|
|
2282
|
+
case "String":
|
|
2283
|
+
return (mask & ScalarMask.String) !== 0;
|
|
2284
|
+
default:
|
|
2285
|
+
return false;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
function getTaggedPlaceholderType(tag) {
|
|
2289
|
+
switch (tag) {
|
|
2290
|
+
case "BigInt":
|
|
2291
|
+
case "Bytes":
|
|
2292
|
+
case "DateTime":
|
|
2293
|
+
case "Json":
|
|
2294
|
+
return { type: tag };
|
|
2295
|
+
case "Decimal":
|
|
2296
|
+
return { type: "Float" };
|
|
2297
|
+
default:
|
|
2298
|
+
return void 0;
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
function inferListElementType(items) {
|
|
2302
|
+
let widest = { type: "Any" };
|
|
2303
|
+
for (const item of items) {
|
|
2304
|
+
const classified = classifyValue(item);
|
|
2305
|
+
let itemType;
|
|
2306
|
+
switch (classified.kind) {
|
|
2307
|
+
case "primitive":
|
|
2308
|
+
itemType = getPrimitivePlaceholderType(classified.value);
|
|
2309
|
+
break;
|
|
2310
|
+
case "taggedScalar":
|
|
2311
|
+
itemType = getTaggedPlaceholderType(classified.tag) ?? { type: "Any" };
|
|
2312
|
+
break;
|
|
2313
|
+
default:
|
|
2314
|
+
return { type: "Any" };
|
|
2315
|
+
}
|
|
2316
|
+
widest = widenType(widest, itemType);
|
|
2317
|
+
}
|
|
2318
|
+
return widest;
|
|
2319
|
+
}
|
|
2320
|
+
function widenType(a, b) {
|
|
2321
|
+
if (a.type === "Any") return b;
|
|
2322
|
+
if (b.type === "Any") return a;
|
|
2323
|
+
if (a.type === b.type) return a;
|
|
2324
|
+
const NUMERIC_WIDTH = { Int: 0, BigInt: 1, Float: 2 };
|
|
2325
|
+
const aWidth = NUMERIC_WIDTH[a.type];
|
|
2326
|
+
const bWidth = NUMERIC_WIDTH[b.type];
|
|
2327
|
+
if (aWidth !== void 0 && bWidth !== void 0) {
|
|
2328
|
+
return aWidth >= bWidth ? a : b;
|
|
2329
|
+
}
|
|
2330
|
+
return { type: "Any" };
|
|
2331
|
+
}
|
|
2332
|
+
function matchesTaggedMask(tag, mask) {
|
|
2333
|
+
switch (tag) {
|
|
2334
|
+
case "DateTime":
|
|
2335
|
+
return (mask & ScalarMask.DateTime) !== 0;
|
|
2336
|
+
case "Decimal":
|
|
2337
|
+
return (mask & ScalarMask.Decimal) !== 0;
|
|
2338
|
+
case "BigInt":
|
|
2339
|
+
return (mask & ScalarMask.BigInt) !== 0;
|
|
2340
|
+
case "Bytes":
|
|
2341
|
+
return (mask & ScalarMask.Bytes) !== 0;
|
|
2342
|
+
case "Json":
|
|
2343
|
+
return (mask & ScalarMask.Json) !== 0;
|
|
2344
|
+
default:
|
|
2345
|
+
return false;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
function validateListElement(item, edge) {
|
|
2349
|
+
const classified = classifyValue(item);
|
|
2350
|
+
switch (classified.kind) {
|
|
2351
|
+
case "structural":
|
|
2352
|
+
return false;
|
|
2353
|
+
case "null":
|
|
2354
|
+
return false;
|
|
2355
|
+
case "primitive": {
|
|
2356
|
+
const type = getPrimitivePlaceholderType(classified.value);
|
|
2357
|
+
const mask = getScalarMask(edge);
|
|
2358
|
+
return mask !== 0 && matchesPrimitiveMask(type, mask);
|
|
2359
|
+
}
|
|
2360
|
+
case "taggedScalar": {
|
|
2361
|
+
const mask = getScalarMask(edge);
|
|
2362
|
+
return mask !== 0 && matchesTaggedMask(classified.tag, mask);
|
|
2363
|
+
}
|
|
2364
|
+
default:
|
|
2365
|
+
return false;
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
function decodeIfTagged(value) {
|
|
2369
|
+
if (isTaggedValue2(value)) {
|
|
2370
|
+
return decodeTaggedValue(value);
|
|
2371
|
+
}
|
|
2372
|
+
return value;
|
|
2373
|
+
}
|
|
2374
|
+
function decodeTaggedValue(tagged) {
|
|
2375
|
+
return tagged.value;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
1940
2378
|
// src/raw-json-protocol.ts
|
|
1941
2379
|
import { Decimal as Decimal4 } from "@prisma/client-runtime-utils";
|
|
1942
2380
|
function normalizeRawJsonProtocolResponse(response) {
|
|
@@ -2425,5 +2863,7 @@ export {
|
|
|
2425
2863
|
noopTracingHelper,
|
|
2426
2864
|
normalizeJsonProtocolValues,
|
|
2427
2865
|
normalizeRawJsonProtocolResponse,
|
|
2866
|
+
parameterizeBatch,
|
|
2867
|
+
parameterizeQuery,
|
|
2428
2868
|
safeJsonStringify
|
|
2429
2869
|
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value classification for parameterization.
|
|
3
|
+
*
|
|
4
|
+
* This module classifies runtime values into categories before applying
|
|
5
|
+
* schema rules during parameterization.
|
|
6
|
+
*/
|
|
7
|
+
import { JsonInputTaggedValue } from '@prisma/json-protocol';
|
|
8
|
+
/**
|
|
9
|
+
* Classification result for a runtime value.
|
|
10
|
+
* Used to determine how to handle the value during parameterization.
|
|
11
|
+
*/
|
|
12
|
+
export type ValueClass = {
|
|
13
|
+
kind: 'null';
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'primitive';
|
|
16
|
+
value: string | number | boolean;
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'taggedScalar';
|
|
19
|
+
tag: JsonInputTaggedValue['$type'];
|
|
20
|
+
value: unknown;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'structural';
|
|
23
|
+
value: unknown;
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'array';
|
|
26
|
+
items: unknown[];
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'object';
|
|
29
|
+
entries: Record<string, unknown>;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Classifies a runtime value for parameterization purposes.
|
|
33
|
+
*
|
|
34
|
+
* @param value - The value to classify
|
|
35
|
+
* @returns The classification result indicating how to handle the value
|
|
36
|
+
*/
|
|
37
|
+
export declare function classifyValue(value: unknown): ValueClass;
|
|
38
|
+
/**
|
|
39
|
+
* Checks if a value is a plain object (not a tagged value or array).
|
|
40
|
+
*/
|
|
41
|
+
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
42
|
+
/**
|
|
43
|
+
* Checks if a value is a tagged value with a $type property.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isTaggedValue(value: unknown): value is {
|
|
46
|
+
$type: string;
|
|
47
|
+
value: unknown;
|
|
48
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ParamGraph } from '@prisma/param-graph';
|
|
2
|
+
export declare const testRuntimeDataModel: {
|
|
3
|
+
models: {};
|
|
4
|
+
enums: {
|
|
5
|
+
Status: {
|
|
6
|
+
values: {
|
|
7
|
+
name: string;
|
|
8
|
+
dbName: null;
|
|
9
|
+
}[];
|
|
10
|
+
dbName: null;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
types: {};
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Returns the ParamGraph for the test schema, building it on first call.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getParamGraph(): ParamGraph;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-aware traversal algorithm for parameterization.
|
|
3
|
+
*
|
|
4
|
+
* This module implements the core parameterization logic that walks the
|
|
5
|
+
* JsonQuery tree guided by ParamGraph. It only parameterizes values when
|
|
6
|
+
* both schema rules and runtime value types agree.
|
|
7
|
+
*/
|
|
8
|
+
import type { JsonBatchQuery, JsonQuery } from '@prisma/json-protocol';
|
|
9
|
+
import { ParamGraph } from '@prisma/param-graph';
|
|
10
|
+
/**
|
|
11
|
+
* Result of parameterizing a single query.
|
|
12
|
+
*/
|
|
13
|
+
export interface ParameterizeResult {
|
|
14
|
+
/** The query with user data values replaced by placeholders */
|
|
15
|
+
parameterizedQuery: JsonQuery;
|
|
16
|
+
/** Map of placeholder names to their actual values */
|
|
17
|
+
placeholderValues: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Result of parameterizing a batch of queries.
|
|
21
|
+
*/
|
|
22
|
+
export interface ParameterizeBatchResult {
|
|
23
|
+
/** The batch with user data values replaced by placeholders */
|
|
24
|
+
parameterizedBatch: JsonBatchQuery;
|
|
25
|
+
/** Combined map of placeholder names to their actual values */
|
|
26
|
+
placeholderValues: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parameterizes a single query using the schema-aware approach.
|
|
30
|
+
*
|
|
31
|
+
* @param query - The query to parameterize
|
|
32
|
+
* @param view - The ParamGraph for schema lookups
|
|
33
|
+
* @returns The parameterized query with extracted placeholder values
|
|
34
|
+
*/
|
|
35
|
+
export declare function parameterizeQuery(query: JsonQuery, view: ParamGraph): ParameterizeResult;
|
|
36
|
+
/**
|
|
37
|
+
* Parameterizes a batch of queries using the schema-aware approach.
|
|
38
|
+
*
|
|
39
|
+
* @param batch - The batch to parameterize
|
|
40
|
+
* @param view - The ParamGraph for schema lookups
|
|
41
|
+
* @returns The parameterized batch with extracted placeholder values
|
|
42
|
+
*/
|
|
43
|
+
export declare function parameterizeBatch(batch: JsonBatchQuery, view: ParamGraph): ParameterizeBatchResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma/client-engine-runtime",
|
|
3
|
-
"version": "7.7.0-dev.
|
|
3
|
+
"version": "7.7.0-dev.2",
|
|
4
4
|
"description": "This package is intended for Prisma's internal use",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -31,16 +31,20 @@
|
|
|
31
31
|
"nanoid": "5.1.5",
|
|
32
32
|
"ulid": "3.0.0",
|
|
33
33
|
"uuid": "11.1.0",
|
|
34
|
-
"@prisma/client-runtime-utils": "7.7.0-dev.
|
|
35
|
-
"@prisma/
|
|
36
|
-
"@prisma/
|
|
37
|
-
"@prisma/
|
|
34
|
+
"@prisma/client-runtime-utils": "7.7.0-dev.2",
|
|
35
|
+
"@prisma/param-graph": "7.7.0-dev.2",
|
|
36
|
+
"@prisma/debug": "7.7.0-dev.2",
|
|
37
|
+
"@prisma/sqlcommenter": "7.7.0-dev.2",
|
|
38
|
+
"@prisma/json-protocol": "7.7.0-dev.2",
|
|
39
|
+
"@prisma/driver-adapter-utils": "7.7.0-dev.2"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
40
42
|
"@codspeed/benchmark.js-plugin": "4.0.0",
|
|
41
43
|
"@types/benchmark": "2.1.5",
|
|
42
44
|
"@types/node": "~20.19.24",
|
|
43
|
-
"benchmark": "2.1.4"
|
|
45
|
+
"benchmark": "2.1.4",
|
|
46
|
+
"@prisma/get-dmmf": "7.7.0-dev.2",
|
|
47
|
+
"@prisma/param-graph-builder": "7.7.0-dev.2"
|
|
44
48
|
},
|
|
45
49
|
"files": [
|
|
46
50
|
"dist"
|