Skip to main content
Glama

Prisma MCP Server

Official
by prisma
Apache 2.0
4
44,192
  • Linux
  • Apple
render-query.ts11.5 kB
import { ArgType, SqlQuery } from '@prisma/driver-adapter-utils' import { DynamicArgType, type Fragment, isPrismaValueGenerator, isPrismaValuePlaceholder, type PlaceholderFormat, type PrismaValueGenerator, type PrismaValuePlaceholder, type QueryPlanDbQuery, } from '../query-plan' import { UserFacingError } from '../user-facing-error' import { assertNever } from '../utils' import { GeneratorRegistrySnapshot } from './generators' import { ScopeBindings } from './scope' export function renderQuery( dbQuery: QueryPlanDbQuery, scope: ScopeBindings, generators: GeneratorRegistrySnapshot, maxChunkSize?: number, ): SqlQuery[] { const args = dbQuery.args.map((arg) => evaluateArg(arg, scope, generators)) switch (dbQuery.type) { case 'rawSql': return [renderRawSql(dbQuery.sql, args, dbQuery.argTypes)] case 'templateSql': { const chunks = dbQuery.chunkable ? chunkParams(dbQuery.fragments, args, maxChunkSize) : [args] return chunks.map((params) => { if (maxChunkSize !== undefined && params.length > maxChunkSize) { throw new UserFacingError('The query parameter limit supported by your database is exceeded.', 'P2029') } return renderTemplateSql(dbQuery.fragments, dbQuery.placeholderFormat, params, dbQuery.argTypes) }) } default: assertNever(dbQuery['type'], `Invalid query type`) } } export function evaluateArg(arg: unknown, scope: ScopeBindings, generators: GeneratorRegistrySnapshot): unknown { while (doesRequireEvaluation(arg)) { if (isPrismaValuePlaceholder(arg)) { const found = scope[arg.prisma__value.name] if (found === undefined) { throw new Error(`Missing value for query variable ${arg.prisma__value.name}`) } arg = found } else if (isPrismaValueGenerator(arg)) { const { name, args } = arg.prisma__value const generator = generators[name] if (!generator) { throw new Error(`Encountered an unknown generator '${name}'`) } arg = generator.generate(...args.map((arg) => evaluateArg(arg, scope, generators))) } else { assertNever(arg, `Unexpected unevaluated value type: ${arg}`) } } if (Array.isArray(arg)) { arg = arg.map((el) => evaluateArg(el, scope, generators)) } return arg } function renderTemplateSql( fragments: Fragment[], placeholderFormat: PlaceholderFormat, params: unknown[], argTypes: DynamicArgType[], ): SqlQuery { let sql = '' const ctx = { placeholderNumber: 1 } const flattenedParams: unknown[] = [] const flattenedArgTypes: ArgType[] = [] for (const fragment of pairFragmentsWithParams(fragments, params, argTypes)) { sql += renderFragment(fragment, placeholderFormat, ctx) if (fragment.type === 'stringChunk') { continue } const length = flattenedParams.length const added = flattenedParams.push(...flattenedFragmentParams(fragment)) - length if (fragment.argType.arity === 'tuple') { if (added % fragment.argType.elements.length !== 0) { throw new Error( `Malformed query template. Expected the number of parameters to match the tuple arity, but got ${added} parameters for a tuple of arity ${fragment.argType.elements.length}.`, ) } // If we have a tuple, we just expand its elements repeatedly. for (let i = 0; i < added / fragment.argType.elements.length; i++) { flattenedArgTypes.push(...fragment.argType.elements) } } else { // If we have a non-tuple, we just expand the single type repeatedly. for (let i = 0; i < added; i++) { flattenedArgTypes.push(fragment.argType) } } } return { sql, args: flattenedParams, argTypes: flattenedArgTypes, } } function renderFragment<Type extends DynamicArgType | undefined>( fragment: FragmentWithParams<Type>, placeholderFormat: PlaceholderFormat, ctx: { placeholderNumber: number }, ): string { const fragmentType = fragment.type switch (fragmentType) { case 'parameter': return formatPlaceholder(placeholderFormat, ctx.placeholderNumber++) case 'stringChunk': return fragment.chunk case 'parameterTuple': { const placeholders = fragment.value.length == 0 ? 'NULL' : fragment.value.map(() => formatPlaceholder(placeholderFormat, ctx.placeholderNumber++)).join(',') return `(${placeholders})` } case 'parameterTupleList': { return fragment.value .map((tuple) => { const elements = tuple .map(() => formatPlaceholder(placeholderFormat, ctx.placeholderNumber++)) .join(fragment.itemSeparator) return `${fragment.itemPrefix}${elements}${fragment.itemSuffix}` }) .join(fragment.groupSeparator) } default: assertNever(fragmentType, 'Invalid fragment type') } } function formatPlaceholder(placeholderFormat: PlaceholderFormat, placeholderNumber: number): string { return placeholderFormat.hasNumbering ? `${placeholderFormat.prefix}${placeholderNumber}` : placeholderFormat.prefix } function renderRawSql(sql: string, args: unknown[], argTypes: ArgType[]): SqlQuery { return { sql, args: args, argTypes, } } function doesRequireEvaluation(param: unknown): param is PrismaValuePlaceholder | PrismaValueGenerator { return isPrismaValuePlaceholder(param) || isPrismaValueGenerator(param) } type FragmentWithParams<Type extends DynamicArgType | undefined = undefined> = Fragment & ( | { type: 'stringChunk' } | { type: 'parameter'; value: unknown; argType: Type } | { type: 'parameterTuple'; value: unknown[]; argType: Type } | { type: 'parameterTupleList'; value: unknown[][]; argType: Type } ) function* pairFragmentsWithParams<Types>( fragments: Fragment[], params: unknown[], argTypes: Types, ): Generator<FragmentWithParams<Types extends DynamicArgType[] ? DynamicArgType : undefined>> { let index = 0 for (const fragment of fragments) { switch (fragment.type) { case 'parameter': { if (index >= params.length) { throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`) } yield { ...fragment, value: params[index], argType: argTypes?.[index] } index++ break } case 'stringChunk': { yield fragment break } case 'parameterTuple': { if (index >= params.length) { throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`) } const value = params[index] yield { ...fragment, value: Array.isArray(value) ? value : [value], argType: argTypes?.[index] } index++ break } case 'parameterTupleList': { if (index >= params.length) { throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`) } const value = params[index] if (!Array.isArray(value)) { throw new Error(`Malformed query template. Tuple list expected.`) } if (value.length === 0) { throw new Error(`Malformed query template. Tuple list cannot be empty.`) } for (const tuple of value) { if (!Array.isArray(tuple)) { throw new Error(`Malformed query template. Tuple expected.`) } } yield { ...fragment, value, argType: argTypes?.[index] } index++ break } } } } function* flattenedFragmentParams<Type extends DynamicArgType | undefined>( fragment: FragmentWithParams<Type>, ): Generator<unknown, undefined, undefined> { switch (fragment.type) { case 'parameter': yield fragment.value break case 'stringChunk': break case 'parameterTuple': yield* fragment.value break case 'parameterTupleList': for (const tuple of fragment.value) { yield* tuple } break } } function chunkParams(fragments: Fragment[], params: unknown[], maxChunkSize?: number): unknown[][] { // Find out the total number of parameters once flattened and what the maximum number of // parameters in a single fragment is. let totalParamCount = 0 let maxParamsPerFragment = 0 for (const fragment of pairFragmentsWithParams(fragments, params, undefined)) { let paramSize = 0 for (const _ of flattenedFragmentParams(fragment)) { void _ paramSize++ } maxParamsPerFragment = Math.max(maxParamsPerFragment, paramSize) totalParamCount += paramSize } let chunkedParams: unknown[][] = [[]] for (const fragment of pairFragmentsWithParams(fragments, params, undefined)) { switch (fragment.type) { case 'parameter': { for (const params of chunkedParams) { params.push(fragment.value) } break } case 'stringChunk': { break } case 'parameterTuple': { const thisParamCount = fragment.value.length let chunks: unknown[][] = [] if ( maxChunkSize && // Have we split the parameters into chunks already? chunkedParams.length === 1 && // Is this the fragment that has the most parameters? thisParamCount === maxParamsPerFragment && // Do we need chunking to fit the parameters? totalParamCount > maxChunkSize && // Would chunking enable us to fit the parameters? totalParamCount - thisParamCount < maxChunkSize ) { const availableSize = maxChunkSize - (totalParamCount - thisParamCount) chunks = chunkArray(fragment.value, availableSize) } else { chunks = [fragment.value] } chunkedParams = chunkedParams.flatMap((params) => chunks.map((chunk) => [...params, chunk])) break } case 'parameterTupleList': { const thisParamCount = fragment.value.reduce((acc, tuple) => acc + tuple.length, 0) const completeChunks: unknown[][][] = [] let currentChunk: unknown[][] = [] let currentChunkParamCount = 0 for (const tuple of fragment.value) { if ( maxChunkSize && // Have we split the parameters into chunks already? chunkedParams.length === 1 && // Is this the fragment that has the most parameters? thisParamCount === maxParamsPerFragment && // Is there anything in the current chunk? currentChunk.length > 0 && // Will adding this tuple exceed the max chunk size? totalParamCount - thisParamCount + currentChunkParamCount + tuple.length > maxChunkSize ) { completeChunks.push(currentChunk) currentChunk = [] currentChunkParamCount = 0 } currentChunk.push(tuple) currentChunkParamCount += tuple.length } if (currentChunk.length > 0) { completeChunks.push(currentChunk) } chunkedParams = chunkedParams.flatMap((params) => completeChunks.map((chunk) => [...params, chunk])) break } } } return chunkedParams } function chunkArray<T>(array: T[], chunkSize: number): T[][] { const result: T[][] = [] for (let i = 0; i < array.length; i += chunkSize) { result.push(array.slice(i, i + chunkSize)) } return result }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/prisma/prisma'

If you have feedback or need assistance with the MCP directory API, please join our Discord server