diff --git a/packages/adapter-neon/src/conversion.ts b/packages/adapter-neon/src/conversion.ts index ac3003b2a07b..84aba7aa6a11 100644 --- a/packages/adapter-neon/src/conversion.ts +++ b/packages/adapter-neon/src/conversion.ts @@ -321,6 +321,13 @@ function normalize_money(money: string): string { return money.slice(1) } +/******************/ +/* XML handling */ +/******************/ +function normalize_xml(xml: string): string { + return xml +} + /*****************/ /* JSON handling */ /*****************/ @@ -401,6 +408,7 @@ export const customParsers = { [ArrayColumnType.BYTEA_ARRAY]: normalizeByteaArray, [ArrayColumnType.BIT_ARRAY]: normalize_array(normalizeBit), [ArrayColumnType.VARBIT_ARRAY]: normalize_array(normalizeBit), + [ArrayColumnType.XML_ARRAY]: normalize_array(normalize_xml), } // https://github.com/brianc/node-postgres/pull/2930 diff --git a/packages/adapter-pg-worker/src/conversion.ts b/packages/adapter-pg-worker/src/conversion.ts index ffea33d4d8e9..8efa7df6d1ab 100644 --- a/packages/adapter-pg-worker/src/conversion.ts +++ b/packages/adapter-pg-worker/src/conversion.ts @@ -1,9 +1,9 @@ +import { type ColumnType, ColumnTypeEnum } from '@prisma/driver-adapter-utils' import * as pg from '@prisma/pg-worker' import { parse as parseArray } from 'postgres-array' const { types } = pg const { builtins: ScalarColumnType, getTypeParser } = types -import { type ColumnType, ColumnTypeEnum } from '@prisma/driver-adapter-utils' /** * PostgreSQL array column types (not defined in ScalarColumnType). @@ -322,6 +322,13 @@ function normalize_money(money: string): string { return money.slice(1) } +/******************/ +/* XML handling */ +/******************/ +function normalize_xml(xml: string): string { + return xml +} + /*****************/ /* JSON handling */ /*****************/ @@ -402,6 +409,7 @@ export const customParsers = { [ArrayColumnType.BYTEA_ARRAY]: normalizeByteaArray, [ArrayColumnType.BIT_ARRAY]: normalize_array(normalizeBit), [ArrayColumnType.VARBIT_ARRAY]: normalize_array(normalizeBit), + [ArrayColumnType.XML_ARRAY]: normalize_array(normalize_xml), } // https://github.com/brianc/node-postgres/pull/2930 diff --git a/packages/adapter-pg/src/conversion.ts b/packages/adapter-pg/src/conversion.ts index 8f5c0aa4181f..a6fbfdd1d1e4 100644 --- a/packages/adapter-pg/src/conversion.ts +++ b/packages/adapter-pg/src/conversion.ts @@ -1,10 +1,10 @@ // @ts-ignore: this is used to avoid the `Module '"/node_modules/@types/pg/index"' has no default export.` error. +import { type ColumnType, ColumnTypeEnum } from '@prisma/driver-adapter-utils' import pg from 'pg' import { parse as parseArray } from 'postgres-array' const { types } = pg const { builtins: ScalarColumnType, getTypeParser } = types -import { type ColumnType, ColumnTypeEnum } from '@prisma/driver-adapter-utils' /** * PostgreSQL array column types (not defined in ScalarColumnType). @@ -323,6 +323,13 @@ function normalize_money(money: string): string { return money.slice(1) } +/******************/ +/* XML handling */ +/******************/ +function normalize_xml(xml: string): string { + return xml +} + /*****************/ /* JSON handling */ /*****************/ @@ -403,6 +410,7 @@ export const customParsers = { [ArrayColumnType.BYTEA_ARRAY]: normalizeByteaArray, [ArrayColumnType.BIT_ARRAY]: normalize_array(normalizeBit), [ArrayColumnType.VARBIT_ARRAY]: normalize_array(normalizeBit), + [ArrayColumnType.XML_ARRAY]: normalize_array(normalize_xml), } // https://github.com/brianc/node-postgres/pull/2930 diff --git a/packages/cli/src/Generate.ts b/packages/cli/src/Generate.ts index 2079c8d01b59..305e04eac697 100644 --- a/packages/cli/src/Generate.ts +++ b/packages/cli/src/Generate.ts @@ -1,4 +1,5 @@ import { enginesVersion } from '@prisma/engines' +import { SqlQueryOutput } from '@prisma/generator-helper' import { arg, Command, @@ -27,6 +28,7 @@ import path from 'path' import resolvePkg from 'resolve-pkg' import { getHardcodedUrlWarning } from './generate/getHardcodedUrlWarning' +import { introspectSql, sqlDirPath } from './generate/introspectSql' import { Watcher } from './generate/Watcher' import { breakingChangesMessage } from './utils/breakingChanges' import { getRandomPromotion } from './utils/handlePromotions' @@ -57,6 +59,7 @@ ${bold('Options')} --no-engine Generate a client for use with Accelerate only --no-hints Hides the hint messages but still outputs errors and warnings --allow-no-models Allow generating a client without models + --sql Generate typed sql module ${bold('Examples')} @@ -112,6 +115,7 @@ ${bold('Examples')} '--postinstall': String, '--telemetry-information': String, '--allow-no-models': Boolean, + '--sql': Boolean, }) const postinstallCwd = process.env.PRISMA_GENERATE_IN_POSTINSTALL @@ -144,6 +148,10 @@ ${bold('Examples')} let hasJsClient let generators: Generator[] | undefined let clientGeneratorVersion: string | null = null + let typedSql: SqlQueryOutput[] | undefined + if (args['--sql']) { + typedSql = await introspectSql(schemaPath) + } try { generators = await getGenerators({ schemaPath, @@ -152,6 +160,7 @@ ${bold('Examples')} cliVersion: pkg.version, generatorNames: args['--generator'], postinstall: Boolean(args['--postinstall']), + typedSql, noEngine: Boolean(args['--no-engine']) || Boolean(args['--data-proxy']) || // legacy, keep for backwards compatibility @@ -281,18 +290,26 @@ Please run \`${getCommandWithExecutor('prisma generate')}\` to see the errors.`) } else { logUpdate(watchingText + '\n' + this.logText) - const watcher = new Watcher(schemaResult.schemaRootDir) + const watcher = new Watcher(schemaPath) + if (args['--sql']) { + watcher.add(sqlDirPath(schemaPath)) + } for await (const changedPath of watcher) { logUpdate(`Change in ${path.relative(process.cwd(), changedPath)}`) let generatorsWatch: Generator[] | undefined try { + if (args['--sql']) { + typedSql = await introspectSql(schemaPath) + } + generatorsWatch = await getGenerators({ schemaPath, printDownloadProgress: !watchMode, version: enginesVersion, cliVersion: pkg.version, generatorNames: args['--generator'], + typedSql, }) if (!generatorsWatch || generatorsWatch.length === 0) { diff --git a/packages/cli/src/__tests__/commands/Generate.test.ts b/packages/cli/src/__tests__/commands/Generate.test.ts index 7a143e7002dc..726ee1a6157d 100644 --- a/packages/cli/src/__tests__/commands/Generate.test.ts +++ b/packages/cli/src/__tests__/commands/Generate.test.ts @@ -915,3 +915,33 @@ describe('--schema from parent directory', () => { ) }) }) + +describe('with --sql', () => { + it('should throw error on invalid sql', async () => { + ctx.fixture('typed-sql-invalid') + await expect(Generate.new().parse(['--sql'])).rejects.toMatchInlineSnapshot(` + "Errors while reading sql files: + + In prisma/sql/invalidQuery.sql: + Error: Error describing the query. + error returned from database: (code: 1) near "Not": syntax error + + + " + `) + }) + + it('throws error on mssql', async () => { + ctx.fixture('typed-sql-invalid-mssql') + await expect(Generate.new().parse(['--sql'])).rejects.toMatchInlineSnapshot( + `"Typed SQL is supported only for postgresql, cockroachdb, mysql, sqlite providers"`, + ) + }) + + it('throws error on mongo', async () => { + ctx.fixture('typed-sql-invalid-mongo') + await expect(Generate.new().parse(['--sql'])).rejects.toMatchInlineSnapshot( + `"Typed SQL is supported only for postgresql, cockroachdb, mysql, sqlite providers"`, + ) + }) +}) diff --git a/packages/cli/src/__tests__/fixtures/typed-sql-invalid-mongo/prisma/schema.prisma b/packages/cli/src/__tests__/fixtures/typed-sql-invalid-mongo/prisma/schema.prisma new file mode 100644 index 000000000000..5553bb82408e --- /dev/null +++ b/packages/cli/src/__tests__/fixtures/typed-sql-invalid-mongo/prisma/schema.prisma @@ -0,0 +1,15 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["typedSql"] +} + +datasource db { + provider = "mongodb" + url = env("TEST_MONGO_URI") +} + +model User { + id String @default(auto()) @id @map("_id") @db.ObjectId + email String @unique + name String? +} diff --git a/packages/cli/src/__tests__/fixtures/typed-sql-invalid-mongo/prisma/sql/invalidQuery.sql b/packages/cli/src/__tests__/fixtures/typed-sql-invalid-mongo/prisma/sql/invalidQuery.sql new file mode 100644 index 000000000000..9d6aaf67f4d2 --- /dev/null +++ b/packages/cli/src/__tests__/fixtures/typed-sql-invalid-mongo/prisma/sql/invalidQuery.sql @@ -0,0 +1 @@ +WHAT WOULD YOU EVEN WRITE FOR MONGO SQL? \ No newline at end of file diff --git a/packages/cli/src/__tests__/fixtures/typed-sql-invalid-mssql/prisma/schema.prisma b/packages/cli/src/__tests__/fixtures/typed-sql-invalid-mssql/prisma/schema.prisma new file mode 100644 index 000000000000..1ca6c6eb30bf --- /dev/null +++ b/packages/cli/src/__tests__/fixtures/typed-sql-invalid-mssql/prisma/schema.prisma @@ -0,0 +1,15 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["typedSql"] +} + +datasource db { + provider = "sqlserver" + url = env("TEST_MSSQL_JDBC_URI") +} + +model User { + id Int @default(autoincrement()) @id + email String @unique + name String? +} diff --git a/packages/cli/src/__tests__/fixtures/typed-sql-invalid-mssql/prisma/sql/invalidQuery.sql b/packages/cli/src/__tests__/fixtures/typed-sql-invalid-mssql/prisma/sql/invalidQuery.sql new file mode 100644 index 000000000000..b04938ee7601 --- /dev/null +++ b/packages/cli/src/__tests__/fixtures/typed-sql-invalid-mssql/prisma/sql/invalidQuery.sql @@ -0,0 +1 @@ +SELECT * FROM User; \ No newline at end of file diff --git a/packages/cli/src/__tests__/fixtures/typed-sql-invalid/prisma/schema.prisma b/packages/cli/src/__tests__/fixtures/typed-sql-invalid/prisma/schema.prisma new file mode 100644 index 000000000000..a91d5ac2a1f5 --- /dev/null +++ b/packages/cli/src/__tests__/fixtures/typed-sql-invalid/prisma/schema.prisma @@ -0,0 +1,15 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["typedSql"] +} + +datasource db { + provider = "sqlite" + url = "file:./dev.db" +} + +model User { + id Int @default(autoincrement()) @id + email String @unique + name String? +} diff --git a/packages/cli/src/__tests__/fixtures/typed-sql-invalid/prisma/sql/invalidQuery.sql b/packages/cli/src/__tests__/fixtures/typed-sql-invalid/prisma/sql/invalidQuery.sql new file mode 100644 index 000000000000..df1f3dd5a041 --- /dev/null +++ b/packages/cli/src/__tests__/fixtures/typed-sql-invalid/prisma/sql/invalidQuery.sql @@ -0,0 +1 @@ +SELECT * FROM Not!A!Valid!Table; \ No newline at end of file diff --git a/packages/cli/src/generate/introspectSql.ts b/packages/cli/src/generate/introspectSql.ts new file mode 100644 index 000000000000..4dfd4b2fcc39 --- /dev/null +++ b/packages/cli/src/generate/introspectSql.ts @@ -0,0 +1,57 @@ +import { isValidJsIdentifier } from '@prisma/internals' +import { introspectSql as migrateIntrospectSql, IntrospectSqlError, IntrospectSqlInput } from '@prisma/migrate' +import fs from 'fs/promises' +import { bold } from 'kleur/colors' +import path from 'path' + +const SQL_DIR = 'sql' + +export async function introspectSql(schemaPath: string) { + const sqlFiles = await readTypedSqlFiles(schemaPath) + const introspectionResult = await migrateIntrospectSql(schemaPath, sqlFiles) + if (introspectionResult.ok) { + return introspectionResult.queries + } + throw new Error(renderErrors(introspectionResult.errors)) +} + +export function sqlDirPath(schemaPath: string) { + return path.join(path.dirname(schemaPath), SQL_DIR) +} + +async function readTypedSqlFiles(schemaPath: string): Promise { + const sqlPath = path.join(path.dirname(schemaPath), SQL_DIR) + const files = await fs.readdir(sqlPath) + const results: IntrospectSqlInput[] = [] + for (const fileName of files) { + const { name, ext } = path.parse(fileName) + if (ext !== '.sql') { + continue + } + const absPath = path.join(sqlPath, fileName) + if (!isValidJsIdentifier(name)) { + throw new Error(`${absPath} can not be used as a typed sql query: name must be a valid JS identifier`) + } + if (name.startsWith('$')) { + throw new Error(`${absPath} can not be used as a typed sql query: name must not start with $`) + } + const source = await fs.readFile(path.join(sqlPath, fileName), 'utf8') + results.push({ + name, + source, + fileName: absPath, + }) + } + + return results +} + +function renderErrors(errors: IntrospectSqlError[]) { + const lines: string[] = [`Errors while reading sql files:\n`] + for (const { fileName, message } of errors) { + lines.push(`In ${bold(path.relative(process.cwd(), fileName))}:`) + lines.push(message) + lines.push('') + } + return lines.join('\n') +} diff --git a/packages/client/package.json b/packages/client/package.json index 035e13dcb610..c922bab75e47 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -107,6 +107,19 @@ "import": "./generator-build/index.js", "default": "./generator-build/index.js" }, + "./sql": { + "require": { + "types": "./sql.d.ts", + "node": "./sql.js", + "default": "./sql.js" + }, + "import": { + "types": "./sql.d.ts", + "node": "./sql.mjs", + "default": "./sql.mjs" + }, + "default": "./sql.js" + }, "./*": "./*" }, "license": "Apache-2.0", @@ -154,7 +167,10 @@ "default.d.ts", "index-browser.js", "extension.js", - "extension.d.ts" + "extension.d.ts", + "sql.d.ts", + "sql.js", + "sql.mjs" ], "devDependencies": { "@cloudflare/workers-types": "4.20240614.0", diff --git a/packages/client/sql.d.ts b/packages/client/sql.d.ts new file mode 100644 index 000000000000..ff2b18fd3fe1 --- /dev/null +++ b/packages/client/sql.d.ts @@ -0,0 +1 @@ +export * from '.prisma/client/sql' diff --git a/packages/client/sql.js b/packages/client/sql.js new file mode 100644 index 000000000000..6d54621ba062 --- /dev/null +++ b/packages/client/sql.js @@ -0,0 +1,4 @@ +'use strict' +module.exports = { + ...require('.prisma/client/sql'), +} diff --git a/packages/client/sql.mjs b/packages/client/sql.mjs new file mode 100644 index 000000000000..9349dbf5088d --- /dev/null +++ b/packages/client/sql.mjs @@ -0,0 +1 @@ +export * from '../../.prisma/client/sql/index.mjs' diff --git a/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema-mongo/__snapshots__/binary.test.ts.snap b/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema-mongo/__snapshots__/binary.test.ts.snap index 8f37762ee346..76d48de93515 100644 --- a/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema-mongo/__snapshots__/binary.test.ts.snap +++ b/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema-mongo/__snapshots__/binary.test.ts.snap @@ -9,7 +9,7 @@ const { objectEnumValues, makeStrictEnum, Public, - getRuntime, + getRuntime } = require('@prisma/client/runtime/index-browser.js') @@ -770,51 +770,13 @@ export namespace Prisma { * Utility Types */ - /** - * From https://github.com/sindresorhus/type-fest/ - * Matches a JSON object. - * This type can be useful to enforce some input to be JSON-compatible or as a super-type to be extended from. - */ - export type JsonObject = {[Key in string]?: JsonValue} - - /** - * From https://github.com/sindresorhus/type-fest/ - * Matches a JSON array. - */ - export interface JsonArray extends Array {} - - /** - * From https://github.com/sindresorhus/type-fest/ - * Matches any valid JSON value. - */ - export type JsonValue = string | number | boolean | JsonObject | JsonArray | null - - /** - * Matches a JSON object. - * Unlike \`JsonObject\`, this type allows undefined and read-only properties. - */ - export type InputJsonObject = {readonly [Key in string]?: InputJsonValue | null} - - /** - * Matches a JSON array. - * Unlike \`JsonArray\`, readonly arrays are assignable to this type. - */ - export interface InputJsonArray extends ReadonlyArray {} - /** - * Matches any valid value that can be used as an input for operations like - * create and update as the value of a JSON field. Unlike \`JsonValue\`, this - * type allows read-only arrays and read-only object properties and disallows - * \`null\` at the top level. - * - * \`null\` cannot be used as the value of a JSON field because its meaning - * would be ambiguous. Use \`Prisma.JsonNull\` to store the JSON null value or - * \`Prisma.DbNull\` to clear the JSON value and set the field to the database - * NULL value instead. - * - * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-by-null-values - */ - export type InputJsonValue = string | number | boolean | InputJsonObject | InputJsonArray | { toJSON(): unknown } + export import JsonObject = runtime.JsonObject + export import JsonArray = runtime.JsonArray + export import JsonValue = runtime.JsonValue + export import InputJsonObject = runtime.InputJsonObject + export import InputJsonArray = runtime.InputJsonArray + export import InputJsonValue = runtime.InputJsonValue /** * Types of the values used to represent different kinds of \`null\` values when working with JSON fields. diff --git a/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema-mongo/__snapshots__/library.test.ts.snap b/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema-mongo/__snapshots__/library.test.ts.snap index edd2b8253401..00283b91e6db 100644 --- a/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema-mongo/__snapshots__/library.test.ts.snap +++ b/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema-mongo/__snapshots__/library.test.ts.snap @@ -9,7 +9,7 @@ const { objectEnumValues, makeStrictEnum, Public, - getRuntime, + getRuntime } = require('@prisma/client/runtime/index-browser.js') @@ -770,51 +770,13 @@ export namespace Prisma { * Utility Types */ - /** - * From https://github.com/sindresorhus/type-fest/ - * Matches a JSON object. - * This type can be useful to enforce some input to be JSON-compatible or as a super-type to be extended from. - */ - export type JsonObject = {[Key in string]?: JsonValue} - - /** - * From https://github.com/sindresorhus/type-fest/ - * Matches a JSON array. - */ - export interface JsonArray extends Array {} - - /** - * From https://github.com/sindresorhus/type-fest/ - * Matches any valid JSON value. - */ - export type JsonValue = string | number | boolean | JsonObject | JsonArray | null - - /** - * Matches a JSON object. - * Unlike \`JsonObject\`, this type allows undefined and read-only properties. - */ - export type InputJsonObject = {readonly [Key in string]?: InputJsonValue | null} - - /** - * Matches a JSON array. - * Unlike \`JsonArray\`, readonly arrays are assignable to this type. - */ - export interface InputJsonArray extends ReadonlyArray {} - /** - * Matches any valid value that can be used as an input for operations like - * create and update as the value of a JSON field. Unlike \`JsonValue\`, this - * type allows read-only arrays and read-only object properties and disallows - * \`null\` at the top level. - * - * \`null\` cannot be used as the value of a JSON field because its meaning - * would be ambiguous. Use \`Prisma.JsonNull\` to store the JSON null value or - * \`Prisma.DbNull\` to clear the JSON value and set the field to the database - * NULL value instead. - * - * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-by-null-values - */ - export type InputJsonValue = string | number | boolean | InputJsonObject | InputJsonArray | { toJSON(): unknown } + export import JsonObject = runtime.JsonObject + export import JsonArray = runtime.JsonArray + export import JsonValue = runtime.JsonValue + export import InputJsonObject = runtime.InputJsonObject + export import InputJsonArray = runtime.InputJsonArray + export import InputJsonValue = runtime.InputJsonValue /** * Types of the values used to represent different kinds of \`null\` values when working with JSON fields. diff --git a/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema/__snapshots__/binary.test.ts.snap b/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema/__snapshots__/binary.test.ts.snap index 1835d2cc45ab..3d6015f1b401 100644 --- a/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema/__snapshots__/binary.test.ts.snap +++ b/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema/__snapshots__/binary.test.ts.snap @@ -9,7 +9,7 @@ const { objectEnumValues, makeStrictEnum, Public, - getRuntime, + getRuntime } = require('@prisma/client/runtime/index-browser.js') @@ -579,6 +579,7 @@ export class PrismaClient< */ $queryRawUnsafe(query: string, ...values: any[]): Prisma.PrismaPromise; + /** * Allows the running of a sequence of read/write operations that are guaranteed to either succeed or fail as a whole. * @example @@ -798,51 +799,13 @@ export namespace Prisma { * Utility Types */ - /** - * From https://github.com/sindresorhus/type-fest/ - * Matches a JSON object. - * This type can be useful to enforce some input to be JSON-compatible or as a super-type to be extended from. - */ - export type JsonObject = {[Key in string]?: JsonValue} - - /** - * From https://github.com/sindresorhus/type-fest/ - * Matches a JSON array. - */ - export interface JsonArray extends Array {} - /** - * From https://github.com/sindresorhus/type-fest/ - * Matches any valid JSON value. - */ - export type JsonValue = string | number | boolean | JsonObject | JsonArray | null - - /** - * Matches a JSON object. - * Unlike \`JsonObject\`, this type allows undefined and read-only properties. - */ - export type InputJsonObject = {readonly [Key in string]?: InputJsonValue | null} - - /** - * Matches a JSON array. - * Unlike \`JsonArray\`, readonly arrays are assignable to this type. - */ - export interface InputJsonArray extends ReadonlyArray {} - - /** - * Matches any valid value that can be used as an input for operations like - * create and update as the value of a JSON field. Unlike \`JsonValue\`, this - * type allows read-only arrays and read-only object properties and disallows - * \`null\` at the top level. - * - * \`null\` cannot be used as the value of a JSON field because its meaning - * would be ambiguous. Use \`Prisma.JsonNull\` to store the JSON null value or - * \`Prisma.DbNull\` to clear the JSON value and set the field to the database - * NULL value instead. - * - * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-by-null-values - */ - export type InputJsonValue = string | number | boolean | InputJsonObject | InputJsonArray | { toJSON(): unknown } + export import JsonObject = runtime.JsonObject + export import JsonArray = runtime.JsonArray + export import JsonValue = runtime.JsonValue + export import InputJsonObject = runtime.InputJsonObject + export import InputJsonArray = runtime.InputJsonArray + export import InputJsonValue = runtime.InputJsonValue /** * Types of the values used to represent different kinds of \`null\` values when working with JSON fields. @@ -2152,15 +2115,11 @@ export namespace Prisma { other: { payload: any operations: { - $executeRawUnsafe: { - args: [query: string, ...values: any[]], - result: any - } $executeRaw: { args: [query: TemplateStringsArray | Prisma.Sql, ...values: any[]], result: any } - $queryRawUnsafe: { + $executeRawUnsafe: { args: [query: string, ...values: any[]], result: any } @@ -2168,6 +2127,10 @@ export namespace Prisma { args: [query: TemplateStringsArray | Prisma.Sql, ...values: any[]], result: any } + $queryRawUnsafe: { + args: [query: string, ...values: any[]], + result: any + } } } } diff --git a/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema/__snapshots__/library.test.ts.snap b/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema/__snapshots__/library.test.ts.snap index d820d1a186c2..9263ed2b4845 100644 --- a/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema/__snapshots__/library.test.ts.snap +++ b/packages/client/src/__tests__/integration/happy/not-so-exhaustive-schema/__snapshots__/library.test.ts.snap @@ -9,7 +9,7 @@ const { objectEnumValues, makeStrictEnum, Public, - getRuntime, + getRuntime } = require('@prisma/client/runtime/index-browser.js') @@ -579,6 +579,7 @@ export class PrismaClient< */ $queryRawUnsafe(query: string, ...values: any[]): Prisma.PrismaPromise; + /** * Allows the running of a sequence of read/write operations that are guaranteed to either succeed or fail as a whole. * @example @@ -798,51 +799,13 @@ export namespace Prisma { * Utility Types */ - /** - * From https://github.com/sindresorhus/type-fest/ - * Matches a JSON object. - * This type can be useful to enforce some input to be JSON-compatible or as a super-type to be extended from. - */ - export type JsonObject = {[Key in string]?: JsonValue} - - /** - * From https://github.com/sindresorhus/type-fest/ - * Matches a JSON array. - */ - export interface JsonArray extends Array {} - /** - * From https://github.com/sindresorhus/type-fest/ - * Matches any valid JSON value. - */ - export type JsonValue = string | number | boolean | JsonObject | JsonArray | null - - /** - * Matches a JSON object. - * Unlike \`JsonObject\`, this type allows undefined and read-only properties. - */ - export type InputJsonObject = {readonly [Key in string]?: InputJsonValue | null} - - /** - * Matches a JSON array. - * Unlike \`JsonArray\`, readonly arrays are assignable to this type. - */ - export interface InputJsonArray extends ReadonlyArray {} - - /** - * Matches any valid value that can be used as an input for operations like - * create and update as the value of a JSON field. Unlike \`JsonValue\`, this - * type allows read-only arrays and read-only object properties and disallows - * \`null\` at the top level. - * - * \`null\` cannot be used as the value of a JSON field because its meaning - * would be ambiguous. Use \`Prisma.JsonNull\` to store the JSON null value or - * \`Prisma.DbNull\` to clear the JSON value and set the field to the database - * NULL value instead. - * - * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-by-null-values - */ - export type InputJsonValue = string | number | boolean | InputJsonObject | InputJsonArray | { toJSON(): unknown } + export import JsonObject = runtime.JsonObject + export import JsonArray = runtime.JsonArray + export import JsonValue = runtime.JsonValue + export import InputJsonObject = runtime.InputJsonObject + export import InputJsonArray = runtime.InputJsonArray + export import InputJsonValue = runtime.InputJsonValue /** * Types of the values used to represent different kinds of \`null\` values when working with JSON fields. @@ -2152,15 +2115,11 @@ export namespace Prisma { other: { payload: any operations: { - $executeRawUnsafe: { - args: [query: string, ...values: any[]], - result: any - } $executeRaw: { args: [query: TemplateStringsArray | Prisma.Sql, ...values: any[]], result: any } - $queryRawUnsafe: { + $executeRawUnsafe: { args: [query: string, ...values: any[]], result: any } @@ -2168,6 +2127,10 @@ export namespace Prisma { args: [query: TemplateStringsArray | Prisma.Sql, ...values: any[]], result: any } + $queryRawUnsafe: { + args: [query: string, ...values: any[]], + result: any + } } } } diff --git a/packages/client/src/__tests__/serializeRawParameters.test.ts b/packages/client/src/__tests__/serializeRawParameters.test.ts index 2e23d2e41db2..1574f33f4560 100644 --- a/packages/client/src/__tests__/serializeRawParameters.test.ts +++ b/packages/client/src/__tests__/serializeRawParameters.test.ts @@ -182,7 +182,7 @@ describe('serializeRawParameters', () => { array: ['2020-06-22T17:07:16.348Z', '321804719213721'], }, }, - [123, '321804719213721'], + [123, { prisma__type: 'bigint', prisma__value: '321804719213721' }], ]) }) }) diff --git a/packages/client/src/generation/TSClient/Model.ts b/packages/client/src/generation/TSClient/Model.ts index f6b604b76af2..db8e5e7cc728 100644 --- a/packages/client/src/generation/TSClient/Model.ts +++ b/packages/client/src/generation/TSClient/Model.ts @@ -744,7 +744,7 @@ function buildFluentWrapperDefinition(modelName: string, outputType: DMMF.Output definition.addGenericParameter(ts.genericParameter('ClientOptions').default(ts.objectType())) } - definition.add(ts.property('[Symbol.toStringTag]', ts.stringLiteral('PrismaPromise')).readonly()) + definition.add(ts.property(ts.toStringTag, ts.stringLiteral('PrismaPromise')).readonly()) definition.addMultiple( outputType.fields .filter( diff --git a/packages/client/src/generation/TSClient/PrismaClient.ts b/packages/client/src/generation/TSClient/PrismaClient.ts index 2f7ec182082b..1d5666b06c92 100644 --- a/packages/client/src/generation/TSClient/PrismaClient.ts +++ b/packages/client/src/generation/TSClient/PrismaClient.ts @@ -2,6 +2,7 @@ import type { DataSource, DMMF } from '@prisma/generator-helper' import { assertNever } from '@prisma/internals' import indent from 'indent-string' +import { ClientOtherOps } from '../../runtime' import { Operation } from '../../runtime/core/types/exported/Result' import * as ts from '../ts-builders' import { @@ -15,7 +16,7 @@ import { getPayloadName, } from '../utils' import { lowerCase } from '../utils/common' -import { runtimeImport } from '../utils/runtimeImport' +import { runtimeImport, runtimeImportedType } from '../utils/runtimeImport' import { TAB_SIZE } from './constants' import { Datasources } from './Datasources' import type { Generable } from './Generable' @@ -101,12 +102,17 @@ function payloadToResult(modelName: string) { } function clientTypeMapOthersDefinition(context: GenerateContext) { - const otherOperationsNames = context.dmmf.getOtherOperationNames().flatMap((n) => { - if (n === 'executeRaw' || n === 'queryRaw') { - return [`$${n}Unsafe`, `$${n}`] + const otherOperationsNames = context.dmmf.getOtherOperationNames().flatMap((name) => { + const results = [`$${name}`] + if (name === 'executeRaw' || name === 'queryRaw') { + results.push(`$${name}Unsafe`) } - return `$${n}` + if (name === 'queryRaw' && context.isPreviewFeatureOn('typedSql')) { + results.push(`$queryRawTyped`) + } + + return results }) const argsResultMap = { @@ -115,7 +121,8 @@ function clientTypeMapOthersDefinition(context: GenerateContext) { $executeRawUnsafe: { args: '[query: string, ...values: any[]]', result: 'any' }, $queryRawUnsafe: { args: '[query: string, ...values: any[]]', result: 'any' }, $runCommandRaw: { args: 'Prisma.InputJsonObject', result: 'Prisma.JsonObject' }, - } + $queryRawTyped: { args: 'runtime.UnknownTypedSql', result: 'Prisma.JsonObject' }, + } satisfies Record return `{ other: { @@ -304,6 +311,42 @@ function executeRawDefinition(context: GenerateContext) { $executeRawUnsafe(query: string, ...values: any[]): Prisma.PrismaPromise;` } +function queryRawTypedDefinition(context: GenerateContext) { + if (!context.isPreviewFeatureOn('typedSql')) { + return '' + } + if (!context.dmmf.mappings.otherOperations.write.includes('queryRaw')) { + return '' + } + + const param = ts.genericParameter('T') + const method = ts + .method('$queryRawTyped') + .setDocComment( + ts.docComment` + Executes a typed SQL query and returns a typed result + @example + \`\`\` + import { myQuery } from '@prisma/client/sql' + + const result = await prisma.$queryRawTyped(myQuery()) + \`\`\` + `, + ) + .addGenericParameter(param) + .addParameter( + ts.parameter( + 'typedSql', + runtimeImportedType('TypedSql') + .addGenericArgument(ts.array(ts.unknownType)) + .addGenericArgument(param.toArgument()), + ), + ) + .setReturnType(ts.prismaPromise(ts.array(param.toArgument()))) + + return ts.stringify(method, { indentLevel: 1, newLine: 'leading' }) +} + function metricDefinition(context: GenerateContext) { if (!context.isPreviewFeatureOn('metrics')) { return '' @@ -451,6 +494,7 @@ export class PrismaClient< ${[ executeRawDefinition(this.context), queryRawDefinition(this.context), + queryRawTypedDefinition(this.context), batchingTransactionDefinition(this.context), interactiveTransactionDefinition(this.context), runCommandRawDefinition(this.context), diff --git a/packages/client/src/generation/TSClient/TSClient.ts b/packages/client/src/generation/TSClient/TSClient.ts index fc4384d8afaa..d4809b3531c9 100644 --- a/packages/client/src/generation/TSClient/TSClient.ts +++ b/packages/client/src/generation/TSClient/TSClient.ts @@ -164,7 +164,7 @@ ${buildNFTAnnotations(edge || !copyEngine, clientEngineType, binaryTargets, rela // in some cases, we just re-export the existing types if (reusedTs) { - const topExports = ts.moduleExportFrom('*', `./${reusedTs}`) + const topExports = ts.moduleExportFrom(`./${reusedTs}`) return ts.stringify(topExports) } diff --git a/packages/client/src/generation/TSClient/common.ts b/packages/client/src/generation/TSClient/common.ts index 54bd4d11cd69..a8fd0fe20ddd 100644 --- a/packages/client/src/generation/TSClient/common.ts +++ b/packages/client/src/generation/TSClient/common.ts @@ -34,7 +34,7 @@ import { Extensions, defineDmmfProperty, Public, - getRuntime, + getRuntime } from '${runtimeBase}/${runtimeNameJs}.js'` : browser ? ` @@ -43,7 +43,7 @@ const { objectEnumValues, makeStrictEnum, Public, - getRuntime, + getRuntime } = require('${runtimeBase}/${runtimeNameJs}.js') ` : ` @@ -211,51 +211,13 @@ export const prismaVersion: PrismaVersion * Utility Types */ -/** - * From https://github.com/sindresorhus/type-fest/ - * Matches a JSON object. - * This type can be useful to enforce some input to be JSON-compatible or as a super-type to be extended from. - */ -export type JsonObject = {[Key in string]?: JsonValue} - -/** - * From https://github.com/sindresorhus/type-fest/ - * Matches a JSON array. - */ -export interface JsonArray extends Array {} - -/** - * From https://github.com/sindresorhus/type-fest/ - * Matches any valid JSON value. - */ -export type JsonValue = string | number | boolean | JsonObject | JsonArray | null - -/** - * Matches a JSON object. - * Unlike \`JsonObject\`, this type allows undefined and read-only properties. - */ -export type InputJsonObject = {readonly [Key in string]?: InputJsonValue | null} -/** - * Matches a JSON array. - * Unlike \`JsonArray\`, readonly arrays are assignable to this type. - */ -export interface InputJsonArray extends ReadonlyArray {} - -/** - * Matches any valid value that can be used as an input for operations like - * create and update as the value of a JSON field. Unlike \`JsonValue\`, this - * type allows read-only arrays and read-only object properties and disallows - * \`null\` at the top level. - * - * \`null\` cannot be used as the value of a JSON field because its meaning - * would be ambiguous. Use \`Prisma.JsonNull\` to store the JSON null value or - * \`Prisma.DbNull\` to clear the JSON value and set the field to the database - * NULL value instead. - * - * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-by-null-values - */ -export type InputJsonValue = string | number | boolean | InputJsonObject | InputJsonArray | { toJSON(): unknown } +export import JsonObject = runtime.JsonObject +export import JsonArray = runtime.JsonArray +export import JsonValue = runtime.JsonValue +export import InputJsonObject = runtime.InputJsonObject +export import InputJsonArray = runtime.InputJsonArray +export import InputJsonValue = runtime.InputJsonValue /** * Types of the values used to represent different kinds of \`null\` values when working with JSON fields. diff --git a/packages/client/src/generation/generateClient.ts b/packages/client/src/generation/generateClient.ts index 7e8927795dc4..9bcfd3c9bedf 100644 --- a/packages/client/src/generation/generateClient.ts +++ b/packages/client/src/generation/generateClient.ts @@ -7,6 +7,7 @@ import type { DataSource, DMMF, GeneratorConfig, + SqlQueryOutput, } from '@prisma/generator-helper' import { assertNever, @@ -31,7 +32,7 @@ import type { DMMF as PrismaClientDMMF } from './dmmf-types' import { getPrismaClientDMMF } from './getDMMF' import { BrowserJS, JS, TS, TSClient } from './TSClient' import { TSClientOptions } from './TSClient/TSClient' -import type { Dictionary } from './utils/common' +import { buildTypedSql } from './typedSql/typedSql' const debug = Debug('prisma:client:generateClient') @@ -69,10 +70,15 @@ export interface GenerateClientOptions { postinstall?: boolean /** When --no-engine is passed via CLI */ copyEngine?: boolean + typedSql?: SqlQueryOutput[] +} + +export interface FileMap { + [name: string]: string | FileMap } export interface BuildClientResult { - fileMap: Dictionary + fileMap: FileMap prismaClientDmmf: PrismaClientDMMF.Document } @@ -92,6 +98,7 @@ export async function buildClient({ postinstall, copyEngine, envPaths, + typedSql, }: O.Required): Promise { // we define the basic options for the client generation const clientEngineType = getClientEngineType(generator) @@ -187,7 +194,7 @@ export async function buildClient({ } // we store the generated contents here - const fileMap: Record = {} + const fileMap: FileMap = {} fileMap['index.js'] = JS(nodeClient) fileMap['index.d.ts'] = TS(nodeClient) fileMap['default.js'] = JS(defaultClient) @@ -201,7 +208,9 @@ export async function buildClient({ fileMap['react-native.d.ts'] = TS(rnTsClient) } - if (generator.previewFeatures.includes('driverAdapters')) { + const usesWasmRuntime = generator.previewFeatures.includes('driverAdapters') + + if (usesWasmRuntime) { // The trampoline client points to #main-entry-point (see below). We use // imports similar to an exports map to ensure correct imports.❗ Before // going GA, please notify @millsp as some things can be cleaned up: @@ -287,6 +296,37 @@ export * from './edge.js'` fileMap['deno/polyfill.js'] = 'globalThis.process = { env: Deno.env.toObject() }; globalThis.global = globalThis' } + if (typedSql && typedSql.length > 0) { + const edgeRuntimeName = usesWasmRuntime ? 'wasm' : 'edge' + const cjsEdgeIndex = `./sql/index.${edgeRuntimeName}.js` + const esmEdgeIndex = `./sql/index.${edgeRuntimeName}.mjs` + pkgJson.exports['./sql'] = { + require: { + types: './sql/index.d.ts', + 'edge-light': cjsEdgeIndex, + workerd: cjsEdgeIndex, + worker: cjsEdgeIndex, + node: './sql/index.js', + default: './sql/index.js', + }, + import: { + types: './sql/index.d.ts', + 'edge-light': esmEdgeIndex, + workerd: esmEdgeIndex, + worker: esmEdgeIndex, + node: './sql/index.mjs', + default: './sql/index.mjs', + }, + default: './sql/index.js', + } as any + fileMap['sql'] = buildTypedSql({ + dmmf, + runtimeBase: getTypedSqlRuntimeBase(runtimeBase), + mainRuntimeName: getNodeRuntimeName(clientEngineType), + queries: typedSql, + edgeRuntimeName, + }) + } fileMap['package.json'] = JSON.stringify(pkgJson, null, 2) return { @@ -295,6 +335,22 @@ export * from './edge.js'` } } +// relativizes runtime import base for typed sql +// absolute path stays unmodified, relative goes up a level +function getTypedSqlRuntimeBase(runtimeBase: string) { + if (!runtimeBase.startsWith('.')) { + // absolute path + return runtimeBase + } + + if (runtimeBase.startsWith('./')) { + // replace ./ with ../ + return `.${runtimeBase}` + } + + return `../${runtimeBase}` +} + // TODO: explore why we have a special case for excluding pnpm async function getDefaultOutdir(outputDir: string): Promise { if (outputDir.endsWith('node_modules/@prisma/client')) { @@ -337,6 +393,7 @@ export async function generateClient(options: GenerateClientOptions): Promise { - const filePath = path.join(outputDir, fileName) - // The deletion of the file is necessary, so VSCode - // picks up the changes. - if (existsSync(filePath)) { - await fs.unlink(filePath) - } - await fs.writeFile(filePath, file) - }), - ) + await writeFileMap(outputDir, fileMap) const runtimeDir = path.join(__dirname, `${testMode ? '../' : ''}../runtime`) @@ -476,6 +524,25 @@ export async function generateClient(options: GenerateClientOptions): Promise { + const absolutePath = path.join(outputDir, fileName) + // The deletion of the file is necessary, so VSCode + // picks up the changes. + await fs.rm(absolutePath, { recursive: true, force: true }) + if (typeof content === 'string') { + // file + await fs.writeFile(absolutePath, content) + } else { + // subdirectory + await fs.mkdir(absolutePath) + await writeFileMap(absolutePath, content) + } + }), + ) +} + function isWasmEngineSupported(provider: ConnectorType) { return provider === 'postgresql' || provider === 'postgres' || provider === 'mysql' || provider === 'sqlite' } diff --git a/packages/client/src/generation/generator.ts b/packages/client/src/generation/generator.ts index 2926624be30a..d5351bfcb82a 100755 --- a/packages/client/src/generation/generator.ts +++ b/packages/client/src/generation/generator.ts @@ -48,6 +48,7 @@ if (process.argv[1] === __filename) { activeProvider: options.datasources[0]?.activeProvider, postinstall: options.postinstall, copyEngine: !options.noEngine, + typedSql: options.typedSql, }) }, }) diff --git a/packages/client/src/generation/ts-builders/AnyDeclarationBuilder.ts b/packages/client/src/generation/ts-builders/AnyDeclarationBuilder.ts index 213cc7a6924b..c729e23e9731 100644 --- a/packages/client/src/generation/ts-builders/AnyDeclarationBuilder.ts +++ b/packages/client/src/generation/ts-builders/AnyDeclarationBuilder.ts @@ -1,7 +1,13 @@ import { ClassDeclaration } from './Class' import { ConstDeclaration } from './ConstDeclaration' import { InterfaceDeclaration } from './Interface' +import { NamespaceDeclaration } from './NamespaceDeclaration' import { TypeDeclaration } from './TypeDeclaration' // TODO: enum -export type AnyDeclarationBuilder = TypeDeclaration | ConstDeclaration | InterfaceDeclaration | ClassDeclaration +export type AnyDeclarationBuilder = + | TypeDeclaration + | ConstDeclaration + | InterfaceDeclaration + | ClassDeclaration + | NamespaceDeclaration diff --git a/packages/client/src/generation/ts-builders/ExportFrom.test.ts b/packages/client/src/generation/ts-builders/ExportFrom.test.ts new file mode 100644 index 000000000000..3580dc23856f --- /dev/null +++ b/packages/client/src/generation/ts-builders/ExportFrom.test.ts @@ -0,0 +1,31 @@ +import { moduleExportFrom, namedExport } from './ExportFrom' +import { stringify } from './stringify' + +test('module name only', () => { + expect(stringify(moduleExportFrom('someModule'))).toMatchInlineSnapshot(`"export * from "someModule""`) +}) + +test('namespace', () => { + const exportDecl = moduleExportFrom('myModule').asNamespace('ns') + expect(stringify(exportDecl)).toMatchInlineSnapshot(`"export * as ns from 'myModule'"`) +}) + +test('named', () => { + const exportDecl = moduleExportFrom('myModule').named('func') + expect(stringify(exportDecl)).toMatchInlineSnapshot(`"export { func } from "myModule""`) +}) + +test('named with alias', () => { + const exportDecl = moduleExportFrom('myModule').named(namedExport('func').as('myFunc')) + expect(stringify(exportDecl)).toMatchInlineSnapshot(`"export { func as myFunc } from "myModule""`) +}) + +test('multiple named', () => { + const exportDecl = moduleExportFrom('myModule') + .named('func1') + .named('func2') + .named(namedExport('func3').as('aliasedFunc3')) + expect(stringify(exportDecl)).toMatchInlineSnapshot( + `"export { func1, func2, func3 as aliasedFunc3 } from "myModule""`, + ) +}) diff --git a/packages/client/src/generation/ts-builders/ExportFrom.ts b/packages/client/src/generation/ts-builders/ExportFrom.ts index f5e276a89484..de91bed21377 100644 --- a/packages/client/src/generation/ts-builders/ExportFrom.ts +++ b/packages/client/src/generation/ts-builders/ExportFrom.ts @@ -1,18 +1,76 @@ import { BasicBuilder } from './BasicBuilder' import { Writer } from './Writer' -export class ExportFrom implements BasicBuilder { - constructor(private modules: '*' | string[], private from: string) {} +export type ExportFrom = NamespaceExport | BindingsExport | ExportAllFrom + +export class NamespaceExport implements BasicBuilder { + constructor(private from: string, private namespace: string) {} + + write(writer: Writer): void { + writer.write(`export * as ${this.namespace} from '${this.from}'`) + } +} + +export class BindingsExport implements BasicBuilder { + private namedExports: NamedExport[] = [] + constructor(private from: string) {} + + named(namedExport: string | NamedExport) { + if (typeof namedExport === 'string') { + namedExport = new NamedExport(namedExport) + } + this.namedExports.push(namedExport) + return this + } + + write(writer: Writer): void { + writer + .write('export ') + .write('{ ') + .writeJoined(', ', this.namedExports) + .write(' }') + + .write(` from "${this.from}"`) + } +} + +export class NamedExport implements BasicBuilder { + private alias: string | undefined + constructor(readonly name: string) {} + + as(alias: string) { + this.alias = alias + return this + } write(writer: Writer): void { - if (this.modules === '*') { - writer.write(`export * from '${this.from}'`) - } else { - writer.write(`export { ${this.modules.join(', ')} } from '${this.from}'`) + writer.write(this.name) + if (this.alias) { + writer.write(' as ').write(this.alias) } } } -export function moduleExportFrom(modules: '*' | string[], from: string) { - return new ExportFrom(modules, from) +export class ExportAllFrom implements BasicBuilder { + constructor(private from: string) {} + + asNamespace(namespace: string) { + return new NamespaceExport(this.from, namespace) + } + + named(binding: string | NamedExport) { + return new BindingsExport(this.from).named(binding) + } + + write(writer: Writer): void { + writer.write(`export * from "${this.from}"`) + } +} + +export function moduleExportFrom(from: string) { + return new ExportAllFrom(from) +} + +export function namedExport(name: string) { + return new NamedExport(name) } diff --git a/packages/client/src/generation/ts-builders/File.ts b/packages/client/src/generation/ts-builders/File.ts new file mode 100644 index 000000000000..c9e7a5ad0393 --- /dev/null +++ b/packages/client/src/generation/ts-builders/File.ts @@ -0,0 +1,39 @@ +import { AnyDeclarationBuilder } from './AnyDeclarationBuilder' +import { BasicBuilder } from './BasicBuilder' +import { Export } from './Export' +import { ExportFrom } from './ExportFrom' +import { Import } from './Import' +import { Writer } from './Writer' + +export type FileItem = AnyDeclarationBuilder | Export | ExportFrom +export class File implements BasicBuilder { + private imports: Import[] = [] + private declarations: FileItem[] = [] + + addImport(moduleImport: Import) { + this.imports.push(moduleImport) + return this + } + add(declaration: FileItem) { + this.declarations.push(declaration) + } + + write(writer: Writer): void { + for (const moduleImport of this.imports) { + writer.writeLine(moduleImport) + } + if (this.imports.length > 0) { + writer.newLine() + } + for (const [i, declaration] of this.declarations.entries()) { + writer.writeLine(declaration) + if (i < this.declarations.length - 1) { + writer.newLine() + } + } + } +} + +export function file(): File { + return new File() +} diff --git a/packages/client/src/generation/ts-builders/Import.test.ts b/packages/client/src/generation/ts-builders/Import.test.ts new file mode 100644 index 000000000000..15ef074fe052 --- /dev/null +++ b/packages/client/src/generation/ts-builders/Import.test.ts @@ -0,0 +1,41 @@ +import { moduleImport, namedImport } from './Import' +import { stringify } from './stringify' + +test('module name only', () => { + expect(stringify(moduleImport('someModule'))).toMatchInlineSnapshot(`"import "someModule""`) +}) + +test('default', () => { + const importDecl = moduleImport('myModule').default('mod') + expect(stringify(importDecl)).toMatchInlineSnapshot(`"import mod from "myModule""`) +}) + +test('namespace', () => { + const importDecl = moduleImport('myModule').asNamespace('ns') + expect(stringify(importDecl)).toMatchInlineSnapshot(`"import * as ns from "myModule""`) +}) + +test('named', () => { + const importDecl = moduleImport('myModule').named('func') + expect(stringify(importDecl)).toMatchInlineSnapshot(`"import { func } from "myModule""`) +}) + +test('named with alias', () => { + const importDecl = moduleImport('myModule').named(namedImport('func').as('myFunc')) + expect(stringify(importDecl)).toMatchInlineSnapshot(`"import { func as myFunc } from "myModule""`) +}) + +test('multiple named', () => { + const importDecl = moduleImport('myModule') + .named('func1') + .named('func2') + .named(namedImport('func3').as('aliasedFunc3')) + expect(stringify(importDecl)).toMatchInlineSnapshot( + `"import { func1, func2, func3 as aliasedFunc3 } from "myModule""`, + ) +}) + +test('default and named', () => { + const importDecl = moduleImport('myModule').default('mod').named('func1').named('func2') + expect(stringify(importDecl)).toMatchInlineSnapshot(`"import mod, { func1, func2 } from "myModule""`) +}) diff --git a/packages/client/src/generation/ts-builders/Import.ts b/packages/client/src/generation/ts-builders/Import.ts index fa9abab945f3..66d4cdb292f4 100644 --- a/packages/client/src/generation/ts-builders/Import.ts +++ b/packages/client/src/generation/ts-builders/Import.ts @@ -1,14 +1,95 @@ import { BasicBuilder } from './BasicBuilder' import { Writer } from './Writer' -export class Import implements BasicBuilder { - constructor(private modules: string[], private from: string) {} +export type Import = NamespaceImport | BindingsImport | ModuleImport + +export class NamespaceImport implements BasicBuilder { + constructor(readonly alias: string, readonly from: string) {} + write(writer: Writer): void { + writer.write('import * as ').write(this.alias).write(` from "${this.from}"`) + } +} + +export class BindingsImport implements BasicBuilder { + private defaultImport: string | undefined + private namedImports: NamedImport[] = [] + constructor(readonly from: string) {} + + default(name: string) { + this.defaultImport = name + return this + } + + named(namedImport: string | NamedImport) { + if (typeof namedImport === 'string') { + namedImport = new NamedImport(namedImport) + } + this.namedImports.push(namedImport) + return this + } + + write(writer: Writer): void { + writer.write('import ') + if (this.defaultImport) { + writer.write(this.defaultImport) + if (this.hasNamedImports()) { + writer.write(', ') + } + } + + if (this.hasNamedImports()) { + writer.write('{ ').writeJoined(', ', this.namedImports).write(' }') + } + + writer.write(` from "${this.from}"`) + } + + private hasNamedImports() { + return this.namedImports.length > 0 + } +} + +export class NamedImport implements BasicBuilder { + private alias: string | undefined + constructor(readonly name: string) {} + + as(alias: string) { + this.alias = alias + return this + } + + write(writer: Writer): void { + writer.write(this.name) + if (this.alias) { + writer.write(' as ').write(this.alias) + } + } +} + +export class ModuleImport implements BasicBuilder { + constructor(readonly from: string) {} + + asNamespace(alias: string): NamespaceImport { + return new NamespaceImport(alias, this.from) + } + + default(alias: string): BindingsImport { + return new BindingsImport(this.from).default(alias) + } + + named(namedImport: string | NamedImport) { + return new BindingsImport(this.from).named(namedImport) + } write(writer: Writer): void { - writer.write(`import { ${this.modules.join(', ')} } from '${this.from}'`) + writer.write('import ').write(`"${this.from}"`) } } -export function moduleImport(modules: string[], from: string) { - return new Import(modules, from) +export function moduleImport(from: string) { + return new ModuleImport(from) +} + +export function namedImport(name: string) { + return new NamedImport(name) } diff --git a/packages/client/src/generation/ts-builders/NamespaceDeclaration.ts b/packages/client/src/generation/ts-builders/NamespaceDeclaration.ts new file mode 100644 index 000000000000..5cc4a35a2aaf --- /dev/null +++ b/packages/client/src/generation/ts-builders/NamespaceDeclaration.ts @@ -0,0 +1,29 @@ +import { AnyDeclarationBuilder } from './AnyDeclarationBuilder' +import { BasicBuilder } from './BasicBuilder' +import { Export } from './Export' +import { Writer } from './Writer' + +export type NamespaceItem = AnyDeclarationBuilder | Export +export class NamespaceDeclaration implements BasicBuilder { + private items: NamespaceItem[] = [] + constructor(readonly name: string) {} + + add(declaration: NamespaceItem) { + this.items.push(declaration) + } + + write(writer: Writer): void { + writer + .writeLine(`namespace ${this.name} {`) + .withIndent(() => { + for (const item of this.items) { + writer.writeLine(item) + } + }) + .write('}') + } +} + +export function namespace(name: string): NamespaceDeclaration { + return new NamespaceDeclaration(name) +} diff --git a/packages/client/src/generation/ts-builders/Property.test.ts b/packages/client/src/generation/ts-builders/Property.test.ts index f3eeaca8ee7f..b1cec3558f28 100644 --- a/packages/client/src/generation/ts-builders/Property.test.ts +++ b/packages/client/src/generation/ts-builders/Property.test.ts @@ -2,6 +2,7 @@ import { docComment } from './DocComment' import { namedType } from './NamedType' import { property } from './Property' import { stringify } from './stringify' +import { toStringTag } from './WellKnownSymbol' const A = namedType('A') @@ -11,6 +12,18 @@ test('name and type', () => { expect(stringify(prop)).toMatchInlineSnapshot(`"foo: A"`) }) +test('invalid identifier', () => { + const prop = property('this is not a valid JS identifier', A) + + expect(stringify(prop)).toMatchInlineSnapshot(`"["this is not a valid JS identifier"]: A"`) +}) + +test('well-known symbol', () => { + const prop = property(toStringTag, A) + + expect(stringify(prop)).toMatchInlineSnapshot(`"[Symbol.toStringTag]: A"`) +}) + test('optional', () => { const prop = property('foo', A).optional() diff --git a/packages/client/src/generation/ts-builders/Property.ts b/packages/client/src/generation/ts-builders/Property.ts index 2108a24fe3f2..246f2839efe1 100644 --- a/packages/client/src/generation/ts-builders/Property.ts +++ b/packages/client/src/generation/ts-builders/Property.ts @@ -1,6 +1,9 @@ +import { isValidJsIdentifier } from '@prisma/internals' + import { BasicBuilder } from './BasicBuilder' import { DocComment } from './DocComment' import { TypeBuilder } from './TypeBuilder' +import { WellKnownSymbol } from './WellKnownSymbol' import { Writer } from './Writer' export class Property implements BasicBuilder { @@ -8,7 +11,7 @@ export class Property implements BasicBuilder { private isReadonly = false private docComment?: DocComment - constructor(private name: string, private type: TypeBuilder) {} + constructor(private name: string | WellKnownSymbol, private type: TypeBuilder) {} optional(): this { this.isOptional = true @@ -32,7 +35,17 @@ export class Property implements BasicBuilder { if (this.isReadonly) { writer.write('readonly ') } - writer.write(this.name) + + if (typeof this.name === 'string') { + if (isValidJsIdentifier(this.name)) { + writer.write(this.name) + } else { + writer.write('[').write(JSON.stringify(this.name)).write(']') + } + } else { + writer.write('[').write(this.name).write(']') + } + if (this.isOptional) { writer.write('?') } @@ -40,6 +53,6 @@ export class Property implements BasicBuilder { } } -export function property(name: string, type: TypeBuilder) { +export function property(name: string | WellKnownSymbol, type: TypeBuilder) { return new Property(name, type) } diff --git a/packages/client/src/generation/ts-builders/TupleType.test.ts b/packages/client/src/generation/ts-builders/TupleType.test.ts new file mode 100644 index 000000000000..9aca10e92218 --- /dev/null +++ b/packages/client/src/generation/ts-builders/TupleType.test.ts @@ -0,0 +1,23 @@ +import { namedType } from './NamedType' +import { stringify } from './stringify' +import { tupleItem, tupleType } from './TupleType' + +test('empty', () => { + const tuple = tupleType() + expect(stringify(tuple)).toMatchInlineSnapshot(`"[]"`) +}) + +test('one item', () => { + const tuple = tupleType().add(namedType('A')) + expect(stringify(tuple)).toMatchInlineSnapshot(`"[A]"`) +}) + +test('with named item', () => { + const tuple = tupleType().add(tupleItem(namedType('A')).setName('foo')) + expect(stringify(tuple)).toMatchInlineSnapshot(`"[foo: A]"`) +}) + +test('multiple items', () => { + const tuple = tupleType().add(namedType('A')).add(namedType('B')).add(namedType('C')) + expect(stringify(tuple)).toMatchInlineSnapshot(`"[A, B, C]"`) +}) diff --git a/packages/client/src/generation/ts-builders/TupleType.ts b/packages/client/src/generation/ts-builders/TupleType.ts new file mode 100644 index 000000000000..019d3eebeecc --- /dev/null +++ b/packages/client/src/generation/ts-builders/TupleType.ts @@ -0,0 +1,44 @@ +import { BasicBuilder } from './BasicBuilder' +import { TypeBuilder } from './TypeBuilder' +import { Writer } from './Writer' + +export class TupleItem implements BasicBuilder { + private name: string | undefined + constructor(readonly type: TypeBuilder) {} + + setName(name: string) { + this.name = name + return this + } + + write(writer: Writer): void { + if (this.name) { + writer.write(this.name).write(': ') + } + writer.write(this.type) + } +} + +export class TupleType extends TypeBuilder { + readonly items: TupleItem[] = [] + + add(item: TypeBuilder | TupleItem) { + if (item instanceof TypeBuilder) { + item = new TupleItem(item) + } + this.items.push(item) + return this + } + + override write(writer: Writer): void { + writer.write('[').writeJoined(', ', this.items).write(']') + } +} + +export function tupleType() { + return new TupleType() +} + +export function tupleItem(type: TypeBuilder) { + return new TupleItem(type) +} diff --git a/packages/client/src/generation/ts-builders/WellKnownSymbol.ts b/packages/client/src/generation/ts-builders/WellKnownSymbol.ts new file mode 100644 index 000000000000..65dc73235d3f --- /dev/null +++ b/packages/client/src/generation/ts-builders/WellKnownSymbol.ts @@ -0,0 +1,15 @@ +import { BasicBuilder } from './BasicBuilder' +import { Writer } from './Writer' + +export class WellKnownSymbol implements BasicBuilder { + constructor(readonly name: string) {} + write(writer: Writer): void { + writer.write('Symbol.').write(this.name) + } +} + +export function wellKnownSymbol(name: string) { + return new WellKnownSymbol(name) +} + +export const toStringTag = wellKnownSymbol('toStringTag') diff --git a/packages/client/src/generation/ts-builders/index.ts b/packages/client/src/generation/ts-builders/index.ts index a4eab77d8832..23403a3c52f8 100644 --- a/packages/client/src/generation/ts-builders/index.ts +++ b/packages/client/src/generation/ts-builders/index.ts @@ -6,6 +6,7 @@ export * from './ConstDeclaration' export * from './DocComment' export * from './Export' export * from './ExportFrom' +export * from './File' export * from './FunctionType' export * from './GenericParameter' export * from './helpers' @@ -14,12 +15,15 @@ export * from './Interface' export * from './KeyofType' export * from './Method' export * from './NamedType' +export * from './NamespaceDeclaration' export * from './ObjectType' export * from './Parameter' export * from './PrimitiveType' export * from './Property' export * from './stringify' export * from './StringLiteralType' +export * from './TupleType' export * from './TypeBuilder' export * from './TypeDeclaration' export * from './UnionType' +export * from './WellKnownSymbol' diff --git a/packages/client/src/generation/typedSql/buildDbEnums.ts b/packages/client/src/generation/typedSql/buildDbEnums.ts new file mode 100644 index 000000000000..e63648ba13b9 --- /dev/null +++ b/packages/client/src/generation/typedSql/buildDbEnums.ts @@ -0,0 +1,55 @@ +import { DMMF, SqlQueryOutput } from '@prisma/generator-helper' + +import * as ts from '../ts-builders' + +type DbEnum = { + name: string + values: string[] +} +export class DbEnumsList { + private enums: DbEnum[] + constructor(enums: readonly DMMF.DatamodelEnum[]) { + this.enums = enums.map((dmmfEnum) => ({ + name: dmmfEnum.dbName ?? dmmfEnum.name, + values: dmmfEnum.values.map((dmmfValue) => dmmfValue.dbName ?? dmmfValue.name), + })) + } + + isEmpty() { + return this.enums.length === 0 + } + + hasEnum(name: string) { + return Boolean(this.enums.find((dbEnum) => dbEnum.name === name)) + } + + *[Symbol.iterator]() { + for (const dbEnum of this.enums) { + yield dbEnum + } + } +} + +export function buildDbEnums(list: DbEnumsList) { + const file = ts.file() + for (const dbEntry of list) { + file.add(buildDbEnum(dbEntry)) + } + + return ts.stringify(file) +} + +function buildDbEnum(dbEnum: DbEnum) { + const type = ts.unionType(dbEnum.values.map(ts.stringLiteral)) + return ts.moduleExport(ts.typeDeclaration(dbEnum.name, type)) +} + +export function queryUsesEnums(query: SqlQueryOutput, enums: DbEnumsList): boolean { + if (enums.isEmpty()) { + return false + } + return ( + query.parameters.some((param) => enums.hasEnum(param.typ)) || + query.resultColumns.some((column) => enums.hasEnum(column.typ)) + ) +} diff --git a/packages/client/src/generation/typedSql/buildIndex.ts b/packages/client/src/generation/typedSql/buildIndex.ts new file mode 100644 index 000000000000..6895feada8de --- /dev/null +++ b/packages/client/src/generation/typedSql/buildIndex.ts @@ -0,0 +1,35 @@ +import { SqlQueryOutput } from '@prisma/generator-helper' + +import * as ts from '../ts-builders' +import { Writer } from '../ts-builders/Writer' +import { DbEnumsList } from './buildDbEnums' + +export function buildIndexTs(queries: SqlQueryOutput[], enums: DbEnumsList) { + const file = ts.file() + if (!enums.isEmpty()) { + file.add(ts.moduleExportFrom('./$DbEnums').asNamespace('$DbEnums')) + } + for (const query of queries) { + file.add(ts.moduleExportFrom(`./${query.name}`)) + } + return ts.stringify(file) +} + +export function buildIndexCjs(queries: SqlQueryOutput[], edgeRuntimeSuffix?: 'wasm' | 'edge' | undefined) { + const writer = new Writer(0, undefined) + writer.writeLine('"use strict"') + for (const { name } of queries) { + const fileName = edgeRuntimeSuffix ? `${name}.${edgeRuntimeSuffix}` : name + writer.writeLine(`exports.${name} = require("./${fileName}.js").${name}`) + } + return writer.toString() +} + +export function buildIndexEsm(queries: SqlQueryOutput[], edgeRuntimeSuffix?: 'wasm' | 'edge' | undefined) { + const writer = new Writer(0, undefined) + for (const { name } of queries) { + const fileName = edgeRuntimeSuffix ? `${name}.${edgeRuntimeSuffix}` : name + writer.writeLine(`export * from "./${fileName}.mjs"`) + } + return writer.toString() +} diff --git a/packages/client/src/generation/typedSql/buildTypedQuery.ts b/packages/client/src/generation/typedSql/buildTypedQuery.ts new file mode 100644 index 000000000000..33f0a70af494 --- /dev/null +++ b/packages/client/src/generation/typedSql/buildTypedQuery.ts @@ -0,0 +1,76 @@ +import { SqlQueryOutput } from '@prisma/generator-helper' + +import * as ts from '../ts-builders' +import { Writer } from '../ts-builders/Writer' +import { DbEnumsList, queryUsesEnums } from './buildDbEnums' +import { getInputType, getOutputType } from './mapTypes' + +type BuildTypedQueryOptions = { + runtimeBase: string + runtimeName: string + query: SqlQueryOutput + enums: DbEnumsList +} + +export function buildTypedQueryTs({ query, runtimeBase, runtimeName, enums }: BuildTypedQueryOptions) { + const file = ts.file() + + file.addImport(ts.moduleImport(`${runtimeBase}/${runtimeName}`).asNamespace('$runtime')) + if (queryUsesEnums(query, enums)) { + file.addImport(ts.moduleImport('./$DbEnums').asNamespace('$DbEnums')) + } + + const doc = ts.docComment(query.documentation ?? undefined) + const factoryType = ts.functionType() + const parametersType = ts.tupleType() + + for (const param of query.parameters) { + const paramType = getInputType(param.typ, param.nullable, enums) + factoryType.addParameter(ts.parameter(param.name, paramType)) + parametersType.add(ts.tupleItem(paramType).setName(param.name)) + if (param.documentation) { + doc.addText(`@param ${param.name} ${param.documentation}`) + } else { + doc.addText(`@param ${param.name}`) + } + } + factoryType.setReturnType( + ts + .namedType('$runtime.TypedSql') + .addGenericArgument(ts.namedType(`${query.name}.Parameters`)) + .addGenericArgument(ts.namedType(`${query.name}.Result`)), + ) + file.add(ts.moduleExport(ts.constDeclaration(query.name, factoryType)).setDocComment(doc)) + + const namespace = ts.namespace(query.name) + namespace.add(ts.moduleExport(ts.typeDeclaration('Parameters', parametersType))) + namespace.add(buildResultType(query, enums)) + file.add(ts.moduleExport(namespace)) + return ts.stringify(file) +} + +function buildResultType(query: SqlQueryOutput, enums: DbEnumsList) { + const type = ts + .objectType() + .addMultiple( + query.resultColumns.map((column) => ts.property(column.name, getOutputType(column.typ, column.nullable, enums))), + ) + return ts.moduleExport(ts.typeDeclaration('Result', type)) +} + +export function buildTypedQueryCjs({ query, runtimeBase, runtimeName }: BuildTypedQueryOptions) { + const writer = new Writer(0, undefined) + writer.writeLine('"use strict"') + writer.writeLine(`const { makeTypedQueryFactory: $mkFactory } = require("${runtimeBase}/${runtimeName}")`) + // https://github.com/javascript-compiler-hints/compiler-notations-spec/blob/main/pure-notation-spec.md + writer.writeLine(`exports.${query.name} = /*#__PURE__*/ $mkFactory(${JSON.stringify(query.source)})`) + return writer.toString() +} + +export function buildTypedQueryEsm({ query, runtimeBase, runtimeName }: BuildTypedQueryOptions) { + const writer = new Writer(0, undefined) + writer.writeLine(`import { makeTypedQueryFactory as $mkFactory } from "${runtimeBase}/${runtimeName}"`) + // https://github.com/javascript-compiler-hints/compiler-notations-spec/blob/main/pure-notation-spec.md + writer.writeLine(`export const ${query.name} = /*#__PURE__*/ $mkFactory(${JSON.stringify(query.source)})`) + return writer.toString() +} diff --git a/packages/client/src/generation/typedSql/mapTypes.ts b/packages/client/src/generation/typedSql/mapTypes.ts new file mode 100644 index 000000000000..ffa4b841db5e --- /dev/null +++ b/packages/client/src/generation/typedSql/mapTypes.ts @@ -0,0 +1,123 @@ +import { QueryIntrospectionBuiltinType, QueryIntrospectionType } from '@prisma/generator-helper' + +import * as ts from '../ts-builders' +import { DbEnumsList } from './buildDbEnums' + +type TypeMappingConfig = { + in: ts.TypeBuilder + out: ts.TypeBuilder +} + +const decimal = ts.namedType('$runtime.Decimal') +const buffer = ts.namedType('Buffer') +const date = ts.namedType('Date') +const inputJsonValue = ts.namedType('$runtime.InputJsonObject') +const jsonValue = ts.namedType('$runtime.JsonValue') + +const bigintIn = ts.unionType([ts.numberType, ts.bigintType]) +const decimalIn = ts.unionType([ts.numberType, decimal]) + +const typeMappings: Record = { + unknown: ts.unknownType, + string: ts.stringType, + int: ts.numberType, + bigint: { + in: bigintIn, + out: ts.bigintType, + }, + decimal: { + in: decimalIn, + out: decimal, + }, + float: ts.numberType, + double: ts.numberType, + enum: ts.stringType, // TODO: + bytes: buffer, + bool: ts.booleanType, + char: ts.stringType, + json: { + in: inputJsonValue, + out: jsonValue, + }, + xml: ts.stringType, + uuid: ts.stringType, + date: date, + datetime: date, + time: date, + null: ts.nullType, + 'int-array': ts.array(ts.numberType), + 'string-array': ts.array(ts.stringType), + 'json-array': { + in: ts.array(inputJsonValue), + out: ts.array(jsonValue), + }, + 'uuid-array': ts.array(ts.stringType), + 'xml-array': ts.array(ts.stringType), + 'bigint-array': { + in: ts.array(bigintIn), + out: ts.array(ts.bigintType), + }, + 'float-array': ts.array(ts.numberType), + 'double-array': ts.array(ts.numberType), + 'char-array': ts.array(ts.stringType), + 'bytes-array': ts.array(buffer), + 'bool-array': ts.array(ts.booleanType), + 'date-array': ts.array(date), + 'time-array': ts.array(date), + 'datetime-array': ts.array(date), + 'decimal-array': { + in: ts.array(decimalIn), + out: ts.array(decimal), + }, +} + +export function getInputType( + introspectionType: QueryIntrospectionType, + nullable: boolean, + enums: DbEnumsList, +): ts.TypeBuilder { + const inn = getMappingConfig(introspectionType, enums).in + + if (!nullable) { + return inn + } else { + return new ts.UnionType(inn).addVariant(ts.nullType) + } +} + +export function getOutputType( + introspectionType: QueryIntrospectionType, + nullable: boolean, + enums: DbEnumsList, +): ts.TypeBuilder { + const out = getMappingConfig(introspectionType, enums).out + + if (!nullable) { + return out + } else { + return new ts.UnionType(out).addVariant(ts.nullType) + } +} + +function getMappingConfig( + introspectionType: QueryIntrospectionType, + enums: DbEnumsList, +): { in: ts.TypeBuilder; out: ts.TypeBuilder } { + const config = typeMappings[introspectionType] + + if (!config) { + if (enums.hasEnum(introspectionType)) { + const type = ts.namedType(`$DbEnums.${introspectionType}`) + + return { in: type, out: type } + } + + throw new Error('Unknown type') + } + + if (config instanceof ts.TypeBuilder) { + return { in: config, out: config } + } + + return config +} diff --git a/packages/client/src/generation/typedSql/typedSql.ts b/packages/client/src/generation/typedSql/typedSql.ts new file mode 100644 index 000000000000..51c2968caf53 --- /dev/null +++ b/packages/client/src/generation/typedSql/typedSql.ts @@ -0,0 +1,44 @@ +import { DMMF, SqlQueryOutput } from '@prisma/generator-helper' + +import { FileMap } from '../generateClient' +import { buildDbEnums, DbEnumsList } from './buildDbEnums' +import { buildIndexCjs, buildIndexEsm, buildIndexTs } from './buildIndex' +import { buildTypedQueryCjs, buildTypedQueryEsm, buildTypedQueryTs } from './buildTypedQuery' + +type TypeSqlBuildOptions = { + runtimeBase: string + mainRuntimeName: string + edgeRuntimeName: 'wasm' | 'edge' + dmmf: DMMF.Document + queries: SqlQueryOutput[] +} + +export function buildTypedSql({ + queries, + runtimeBase, + edgeRuntimeName, + mainRuntimeName, + dmmf, +}: TypeSqlBuildOptions): FileMap { + const fileMap = {} + + const enums = new DbEnumsList(dmmf.datamodel.enums) + if (!enums.isEmpty()) { + fileMap['$DbEnums.d.ts'] = buildDbEnums(enums) + } + for (const query of queries) { + const options = { query, runtimeBase, runtimeName: mainRuntimeName, enums } + const edgeOptions = { ...options, runtimeName: `${edgeRuntimeName}.js` } + fileMap[`${query.name}.d.ts`] = buildTypedQueryTs(options) + fileMap[`${query.name}.js`] = buildTypedQueryCjs(options) + fileMap[`${query.name}.${edgeRuntimeName}.js`] = buildTypedQueryCjs(edgeOptions) + fileMap[`${query.name}.mjs`] = buildTypedQueryEsm(options) + fileMap[`${query.name}.edge.mjs`] = buildTypedQueryEsm(edgeOptions) + } + fileMap['index.d.ts'] = buildIndexTs(queries, enums) + fileMap['index.js'] = buildIndexCjs(queries) + fileMap['index.mjs'] = buildIndexEsm(queries) + fileMap[`index.${edgeRuntimeName}.mjs`] = buildIndexEsm(queries, edgeRuntimeName) + fileMap[`index.${edgeRuntimeName}.js`] = buildIndexCjs(queries, edgeRuntimeName) + return fileMap +} diff --git a/packages/client/src/generation/utils/runtimeImport.ts b/packages/client/src/generation/utils/runtimeImport.ts index 618ef171d6db..fe1c1330f033 100644 --- a/packages/client/src/generation/utils/runtimeImport.ts +++ b/packages/client/src/generation/utils/runtimeImport.ts @@ -1,3 +1,5 @@ +import * as ts from '../ts-builders' + type RuntimeExports = typeof import('../../runtime') /** @@ -9,3 +11,7 @@ type RuntimeExports = typeof import('../../runtime') export function runtimeImport(name: keyof RuntimeExports): string { return name } + +export function runtimeImportedType(name: keyof RuntimeExports): ts.NamedType { + return ts.namedType(`runtime.${name}`) +} diff --git a/packages/client/src/runtime/core/raw-query/rawQueryArgsMapper.ts b/packages/client/src/runtime/core/raw-query/rawQueryArgsMapper.ts index 9a793f7b07aa..116f881b17ff 100644 --- a/packages/client/src/runtime/core/raw-query/rawQueryArgsMapper.ts +++ b/packages/client/src/runtime/core/raw-query/rawQueryArgsMapper.ts @@ -4,6 +4,7 @@ import { Sql } from 'sql-template-tag' import { MiddlewareArgsMapper } from '../../getPrismaClient' import { mssqlPreparedStatement } from '../../utils/mssqlPreparedStatement' import { serializeRawParameters } from '../../utils/serializeRawParameters' +import { TypedSql } from '../types/exported' import { RawQueryArgs } from '../types/exported/RawQueryArgs' const ALTER_RE = /^(\s*alter\s)/i @@ -39,8 +40,13 @@ export const rawQueryArgsMapper = // TODO Clean up types let queryString = '' let parameters: { values: string; __prismaRawParameters__: true } | undefined - - if (Array.isArray(args)) { + if (args instanceof TypedSql) { + queryString = args.sql + parameters = { + values: serializeRawParameters(args.values), + __prismaRawParameters__: true, + } + } else if (Array.isArray(args)) { // If this was called as prisma.$executeRaw(, [...values]), assume it is a pre-prepared SQL statement, and forward it without any changes const [query, ...values] = args queryString = query diff --git a/packages/client/src/runtime/core/types/exported/Extensions.ts b/packages/client/src/runtime/core/types/exported/Extensions.ts index 5da638223b9c..27b125bf0163 100644 --- a/packages/client/src/runtime/core/types/exported/Extensions.ts +++ b/packages/client/src/runtime/core/types/exported/Extensions.ts @@ -2,10 +2,12 @@ import { Sql } from 'sql-template-tag' import { RequiredExtensionArgs as UserArgs } from './ExtensionArgs' import { ITXClientDenyList } from './itxClientDenyList' +import { InputJsonObject, JsonObject } from './Json' import { OperationPayload } from './Payload' import { PrismaPromise } from './Public' import { FluentOperation, GetFindResult, GetResult as GetOperationResult, Operation } from './Result' -import { Call, ComputeDeep, Exact, Fn, Optional, Path, Return, Select, ToTuple, UnwrapTuple } from './Utils' +import { TypedSql } from './TypedSql' +import { Call, ComputeDeep, Exact, Fn, Optional, Path, Return, Select, UnwrapTuple } from './Utils' export type InternalArgs< R = { [K in string]: { [K in string]: unknown } }, @@ -279,11 +281,7 @@ export type DynamicClientExtensionThis< [P in Exclude]: DynamicModelExtensionThis, ExtArgs, ClientOptions> } & { - [P in Exclude]: < - R = GetOperationResult, - >( - ...args: ToTuple - ) => PrismaPromise + [P in Exclude]: P extends keyof ClientOtherOps ? ClientOtherOps[P] : never } & { [P in Exclude]: DynamicClientExtensionThisBuiltin[P] @@ -407,6 +405,15 @@ export type ClientOptionDef = [K in string]: any } +export type ClientOtherOps = { + $queryRaw(query: TemplateStringsArray | Sql, ...values: any[]): PrismaPromise + $queryRawTyped(query: TypedSql): PrismaPromise + $queryRawUnsafe(query: string, ...values: any[]): PrismaPromise + $executeRaw(query: TemplateStringsArray | Sql, ...values: any[]): PrismaPromise + $executeRawUnsafe(query: string, ...values: any[]): PrismaPromise + $runCommandRaw(command: InputJsonObject): PrismaPromise +} + export type TypeMapCbDef = Fn<{ extArgs: InternalArgs; clientOptions: ClientOptionDef }, TypeMapDef> export type ModelKey = M extends keyof TypeMap['model'] diff --git a/packages/client/src/runtime/core/types/exported/Json.ts b/packages/client/src/runtime/core/types/exported/Json.ts new file mode 100644 index 000000000000..e11c952fb869 --- /dev/null +++ b/packages/client/src/runtime/core/types/exported/Json.ts @@ -0,0 +1,45 @@ +/** + * From https://github.com/sindresorhus/type-fest/ + * Matches a JSON object. + * This type can be useful to enforce some input to be JSON-compatible or as a super-type to be extended from. + */ +export type JsonObject = { [Key in string]?: JsonValue } + +/** + * From https://github.com/sindresorhus/type-fest/ + * Matches a JSON array. + */ +export interface JsonArray extends Array {} + +/** + * From https://github.com/sindresorhus/type-fest/ + * Matches any valid JSON value. + */ +export type JsonValue = string | number | boolean | JsonObject | JsonArray | null + +/** + * Matches a JSON object. + * Unlike \`JsonObject\`, this type allows undefined and read-only properties. + */ +export type InputJsonObject = { readonly [Key in string]?: InputJsonValue | null } + +/** + * Matches a JSON array. + * Unlike \`JsonArray\`, readonly arrays are assignable to this type. + */ +export interface InputJsonArray extends ReadonlyArray {} + +/** + * Matches any valid value that can be used as an input for operations like + * create and update as the value of a JSON field. Unlike \`JsonValue\`, this + * type allows read-only arrays and read-only object properties and disallows + * \`null\` at the top level. + * + * \`null\` cannot be used as the value of a JSON field because its meaning + * would be ambiguous. Use \`Prisma.JsonNull\` to store the JSON null value or + * \`Prisma.DbNull\` to clear the JSON value and set the field to the database + * NULL value instead. + * + * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-by-null-values + */ +export type InputJsonValue = string | number | boolean | InputJsonObject | InputJsonArray | { toJSON(): unknown } diff --git a/packages/client/src/runtime/core/types/exported/RawQueryArgs.ts b/packages/client/src/runtime/core/types/exported/RawQueryArgs.ts index b9561a7104cc..4a97ca720c36 100644 --- a/packages/client/src/runtime/core/types/exported/RawQueryArgs.ts +++ b/packages/client/src/runtime/core/types/exported/RawQueryArgs.ts @@ -1,3 +1,5 @@ import { RawValue, Sql } from 'sql-template-tag' -export type RawQueryArgs = Sql | [query: string, ...values: RawValue[]] +import { UnknownTypedSql } from './TypedSql' + +export type RawQueryArgs = Sql | UnknownTypedSql | [query: string, ...values: RawValue[]] diff --git a/packages/client/src/runtime/core/types/exported/Result.ts b/packages/client/src/runtime/core/types/exported/Result.ts index b28236af8d90..24252902a450 100644 --- a/packages/client/src/runtime/core/types/exported/Result.ts +++ b/packages/client/src/runtime/core/types/exported/Result.ts @@ -1,5 +1,6 @@ +import { JsonObject } from './Json' import { OperationPayload } from './Payload' -import { Compute, Equals, JsonObject, PatchFlat, Select } from './Utils' +import { Compute, Equals, PatchFlat, Select } from './Utils' // prettier-ignore export type Operation = @@ -152,6 +153,7 @@ export type GetResult, groupBy: GetGroupByResult, $queryRaw: unknown, + $queryRawTyped: unknown, $executeRaw: number, $queryRawUnsafe: unknown, $executeRawUnsafe: number, diff --git a/packages/client/src/runtime/core/types/exported/TypedSql.ts b/packages/client/src/runtime/core/types/exported/TypedSql.ts new file mode 100644 index 000000000000..cfd58c1b51bc --- /dev/null +++ b/packages/client/src/runtime/core/types/exported/TypedSql.ts @@ -0,0 +1,33 @@ +type TypedSqlInternal = { + sql: string + values: readonly unknown[] +} + +const internals = new WeakMap, TypedSqlInternal>() + +export declare const PrivateResultType: unique symbol + +export class TypedSql { + declare [PrivateResultType]: Result + + constructor(sql: string, values: Values) { + internals.set(this, { + sql, + values, + }) + } + + get sql(): string { + return internals.get(this)!.sql + } + + get values(): Values { + return internals.get(this)!.values as Values + } +} + +export type UnknownTypedSql = TypedSql + +export function makeTypedQueryFactory(sql: string) { + return (...values) => new TypedSql(sql, values) +} diff --git a/packages/client/src/runtime/core/types/exported/Utils.ts b/packages/client/src/runtime/core/types/exported/Utils.ts index 3783908576e7..69d8eb5f584c 100644 --- a/packages/client/src/runtime/core/types/exported/Utils.ts +++ b/packages/client/src/runtime/core/types/exported/Utils.ts @@ -41,10 +41,6 @@ export type Exact = export type Cast = A extends W ? A : W -export type JsonObject = { [Key in string]?: JsonValue } -export interface JsonArray extends Array {} -export type JsonValue = string | number | boolean | JsonObject | JsonArray | null - export type Record = { [P in T]: U } diff --git a/packages/client/src/runtime/core/types/exported/index.ts b/packages/client/src/runtime/core/types/exported/index.ts index 3b4eb0577a1e..950a06ee592f 100644 --- a/packages/client/src/runtime/core/types/exported/index.ts +++ b/packages/client/src/runtime/core/types/exported/index.ts @@ -5,9 +5,11 @@ export * from './Extensions' export * from './FieldRef' export * from './itxClientDenyList' export * from './JsApi' +export * from './Json' export * from './ObjectEnums' export * from './Payload' export * from './Public' export * from './RawQueryArgs' export * from './Result' +export * from './TypedSql' export * from './Utils' diff --git a/packages/client/src/runtime/core/types/index.ts b/packages/client/src/runtime/core/types/index.ts index d29c1a451525..cf371e1bff9d 100644 --- a/packages/client/src/runtime/core/types/index.ts +++ b/packages/client/src/runtime/core/types/index.ts @@ -10,5 +10,7 @@ export { Extensions } export { Utils } export { Public } +export { type UnknownTypedSql } from './exported/TypedSql' + /** General types */ export { type OperationPayload as Payload } diff --git a/packages/client/src/runtime/getPrismaClient.ts b/packages/client/src/runtime/getPrismaClient.ts index 5e05f422ca2f..efbda034ab73 100644 --- a/packages/client/src/runtime/getPrismaClient.ts +++ b/packages/client/src/runtime/getPrismaClient.ts @@ -61,6 +61,7 @@ import { getLockCountPromise } from './core/transaction/utils/createLockCountPro import { itxClientDenyList } from './core/types/exported/itxClientDenyList' import { JsInputValue } from './core/types/exported/JsApi' import { RawQueryArgs } from './core/types/exported/RawQueryArgs' +import { UnknownTypedSql } from './core/types/exported/TypedSql' import { getLogLevel } from './getLogLevel' import type { QueryMiddleware, QueryMiddlewareParams } from './MiddlewareHandler' import { MiddlewareHandler } from './MiddlewareHandler' @@ -713,6 +714,22 @@ Or read our docs at https://www.prisma.io/docs/concepts/components/prisma-client }) } + /** + * Counterpart to $queryRaw, that returns strongly typed results + * @param typedSql + */ + $queryRawTyped(typedSql: UnknownTypedSql) { + return this._createPrismaPromise((transaction) => { + if (!this._hasPreviewFlag('typedSql')) { + throw new PrismaClientValidationError( + '`typedSql` preview feature must be enabled in order to access $queryRawTyped API', + { clientVersion: this._clientVersion }, + ) + } + return this.$queryRawInternal(transaction, '$queryRawTyped', typedSql) + }) + } + /** * Unsafe counterpart of `$queryRaw` that is susceptible to SQL injections * @see https://github.com/prisma/prisma/issues/7142 diff --git a/packages/client/src/runtime/index.ts b/packages/client/src/runtime/index.ts index e8edb5a62d66..22e215cd949c 100644 --- a/packages/client/src/runtime/index.ts +++ b/packages/client/src/runtime/index.ts @@ -24,6 +24,7 @@ export { defineDmmfProperty } from './core/runtimeDataModel' export type * from './core/types/exported' export type { ITXClientDenyList } from './core/types/exported/itxClientDenyList' export { objectEnumValues } from './core/types/exported/ObjectEnums' +export { makeTypedQueryFactory } from './core/types/exported/TypedSql' export type { PrismaClientOptions } from './getPrismaClient' export { getPrismaClient } from './getPrismaClient' export { makeStrictEnum } from './strictEnum' diff --git a/packages/client/src/runtime/utils/deserializeRawResults.ts b/packages/client/src/runtime/utils/deserializeRawResults.ts index 11bb32d3126f..7bcec26dc834 100644 --- a/packages/client/src/runtime/utils/deserializeRawResults.ts +++ b/packages/client/src/runtime/utils/deserializeRawResults.ts @@ -1,48 +1,13 @@ +import type { QueryIntrospectionBuiltinType } from '@prisma/generator-helper' import Decimal from 'decimal.js' -// This must remain in sync with the `quaint::ColumnType` enum in the QueryEngine. -// ./quaint/src/connector/column_type.rs -type PrismaType = - | 'int' - | 'bigint' - | 'float' - | 'double' - | 'string' - | 'enum' - | 'bytes' - | 'bool' - | 'char' - | 'decimal' - | 'json' - | 'xml' - | 'uuid' - | 'datetime' - | 'date' - | 'time' - | 'int-array' - | 'bigint-array' - | 'float-array' - | 'double-array' - | 'string-array' - | 'bytes-array' - | 'bool-array' - | 'char-array' - | 'decimal-array' - | 'json-array' - | 'xml-array' - | 'uuid-array' - | 'datetime-array' - | 'date-array' - | 'time-array' - | 'unknown' - export type RawResponse = { columns: string[] - types: PrismaType[] + types: QueryIntrospectionBuiltinType[] rows: unknown[][] } -function deserializeValue(type: PrismaType, value: unknown): unknown { +function deserializeValue(type: QueryIntrospectionBuiltinType, value: unknown): unknown { if (value === null) { return value } diff --git a/packages/client/src/runtime/utils/serializeRawParameters.ts b/packages/client/src/runtime/utils/serializeRawParameters.ts index ce874c8cda3e..d3ee7f16b2f2 100644 --- a/packages/client/src/runtime/utils/serializeRawParameters.ts +++ b/packages/client/src/runtime/utils/serializeRawParameters.ts @@ -17,6 +17,9 @@ function serializeRawParametersInternal(parameters: any[], objectSerialization: } function encodeParameter(parameter: any, objectSerialization: 'fast' | 'slow'): unknown { + if (Array.isArray(parameter)) { + return parameter.map((item) => encodeParameter(item, objectSerialization)) + } if (typeof parameter === 'bigint') { return { prisma__type: 'bigint', diff --git a/packages/client/tests/e2e/typed-sql/_steps.ts b/packages/client/tests/e2e/typed-sql/_steps.ts new file mode 100644 index 000000000000..cba2164c181c --- /dev/null +++ b/packages/client/tests/e2e/typed-sql/_steps.ts @@ -0,0 +1,21 @@ +import { $ } from 'zx' + +import { executeSteps } from '../_utils/executeSteps' + +void executeSteps({ + setup: async () => { + await $`pnpm install` + await $`pnpm exec prisma db push --force-reset --skip-generate` + await $`pnpm prisma generate --sql` + }, + test: async () => { + await $`ts-node src/index.ts` + await $`node src/esm-import.mjs` + await $`pnpm exec tsc --noEmit` + await $`pnpm exec jest` + }, + finish: async () => { + await $`echo "done"` + }, + // keep: true, // keep docker open to debug it +}) diff --git a/packages/client/tests/e2e/typed-sql/docker-compose.yaml b/packages/client/tests/e2e/typed-sql/docker-compose.yaml new file mode 100644 index 000000000000..9207bbac0dde --- /dev/null +++ b/packages/client/tests/e2e/typed-sql/docker-compose.yaml @@ -0,0 +1,19 @@ +services: + test-e2e: + environment: + - POSTGRES_URL=postgres://prisma:prisma@postgres:5432/tests + depends_on: + postgres: + condition: service_healthy + + postgres: + image: postgres:16 + environment: + - POSTGRES_DB=tests + - POSTGRES_USER=prisma + - POSTGRES_PASSWORD=prisma + healthcheck: + test: ['CMD', 'pg_isready', '-U', 'prisma', '-d', 'tests'] + interval: 5s + timeout: 2s + retries: 20 diff --git a/packages/client/tests/e2e/typed-sql/package.json b/packages/client/tests/e2e/typed-sql/package.json new file mode 100644 index 000000000000..3c838499011e --- /dev/null +++ b/packages/client/tests/e2e/typed-sql/package.json @@ -0,0 +1,14 @@ +{ + "private": true, + "version": "0.0.0", + "main": "index.js", + "scripts": {}, + "dependencies": { + "@prisma/client": "/tmp/prisma-client-0.0.0.tgz" + }, + "devDependencies": { + "@types/jest": "29.5.12", + "@types/node": "16.18.98", + "prisma": "/tmp/prisma-0.0.0.tgz" + } +} diff --git a/packages/client/tests/e2e/typed-sql/pnpm-lock.yaml b/packages/client/tests/e2e/typed-sql/pnpm-lock.yaml new file mode 100644 index 000000000000..d6a7cc4963ff --- /dev/null +++ b/packages/client/tests/e2e/typed-sql/pnpm-lock.yaml @@ -0,0 +1,486 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@prisma/client': + specifier: /tmp/prisma-client-0.0.0.tgz + version: file:../../tmp/prisma-client-0.0.0.tgz(prisma@file:../../tmp/prisma-0.0.0.tgz) + devDependencies: + '@types/jest': + specifier: 29.5.12 + version: 29.5.12 + '@types/node': + specifier: 16.18.98 + version: 16.18.98 + prisma: + specifier: /tmp/prisma-0.0.0.tgz + version: file:../../tmp/prisma-0.0.0.tgz + +packages: + + '@babel/code-frame@7.24.7': + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.7': + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@prisma/client@file:../../tmp/prisma-client-0.0.0.tgz': + resolution: {integrity: sha512-K67utmIifUVp69IEiPrpO2tt/4pW8eXOs8AXLCfhuMPlO77kDJoklblUxbSrWLZBAGwPhLfhYHEemW1B5iB9Xw==, tarball: file:../../tmp/prisma-client-0.0.0.tgz} + version: 0.0.0 + engines: {node: '>=16.13'} + peerDependencies: + prisma: '*' + peerDependenciesMeta: + prisma: + optional: true + + '@prisma/debug@file:../../tmp/prisma-debug-0.0.0.tgz': + resolution: {integrity: sha512-wwja1z9F/AlHQnxNbyWP2rCo1E5tOwJEC97Dtj3G+/8ssgr8jyHrdi9lCexo81vApcmPeoDhizCIKe47z118dQ==, tarball: file:../../tmp/prisma-debug-0.0.0.tgz} + version: 0.0.0 + + '@prisma/engines-version@5.19.0-25.feat-typed-sql-nullability-860858bb818708261f36cd05bc915e603aae5004': + resolution: {integrity: sha512-g6aboVH18zfUsNfW7GCv05xevhiNG2UOb3VtQ3KDjHQ7rbseABfFAvruMz55UbObEU6vWbUme+qPhk0XH0XHkg==} + + '@prisma/engines@file:../../tmp/prisma-engines-0.0.0.tgz': + resolution: {integrity: sha512-CT3ZVnyIAClH1XVKA3DipGQjciRAIEDeRYaTmYgJjmZV7CCbALNQ2x5lmH1fatsmLnWqhodtLJg5jdFwEg85Yg==, tarball: file:../../tmp/prisma-engines-0.0.0.tgz} + version: 0.0.0 + + '@prisma/fetch-engine@file:../../tmp/prisma-fetch-engine-0.0.0.tgz': + resolution: {integrity: sha512-dF4fYczM8EjdW9txkvkVVkXpdxOOoCo+Tl18wjFssHkaYv4EPtoNXJjh6Gdw774IQtoHhpFMj8hMkNzLaLpkmw==, tarball: file:../../tmp/prisma-fetch-engine-0.0.0.tgz} + version: 0.0.0 + + '@prisma/get-platform@file:../../tmp/prisma-get-platform-0.0.0.tgz': + resolution: {integrity: sha512-s1xjGocOenORqrtWbwjU0g5SKOCBWD8MseL7qXSUXYAg/gfA7tE38qW/T8QnTkRklH3qluJGaF//kMKRZ4M+Tw==, tarball: file:../../tmp/prisma-get-platform-0.0.0.tgz} + version: 0.0.0 + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.12': + resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} + + '@types/node@16.18.98': + resolution: {integrity: sha512-fpiC20NvLpTLAzo3oVBKIqBGR6Fx/8oAK/SSf7G+fydnXMY1x4x9RZ6sBXhqKlCU21g2QapUsbLlhv3+a7wS+Q==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.32': + resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + prisma@file:../../tmp/prisma-0.0.0.tgz: + resolution: {integrity: sha512-MS9R30uryWX9q89Jzmx6yjWWA0NrvdZAajsn132v4a/2Qbwl7W9/lHHhl9+SIKX58rjKr/NRcrPhVAFzMzEUmg==, tarball: file:../../tmp/prisma-0.0.0.tgz} + version: 0.0.0 + engines: {node: '>=16.13'} + hasBin: true + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + +snapshots: + + '@babel/code-frame@7.24.7': + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.1 + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/highlight@7.24.7': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 16.18.98 + '@types/yargs': 17.0.32 + chalk: 4.1.2 + + '@prisma/client@file:../../tmp/prisma-client-0.0.0.tgz(prisma@file:../../tmp/prisma-0.0.0.tgz)': + optionalDependencies: + prisma: file:../../tmp/prisma-0.0.0.tgz + + '@prisma/debug@file:../../tmp/prisma-debug-0.0.0.tgz': {} + + '@prisma/engines-version@5.19.0-25.feat-typed-sql-nullability-860858bb818708261f36cd05bc915e603aae5004': {} + + '@prisma/engines@file:../../tmp/prisma-engines-0.0.0.tgz': + dependencies: + '@prisma/debug': file:../../tmp/prisma-debug-0.0.0.tgz + '@prisma/engines-version': 5.19.0-25.feat-typed-sql-nullability-860858bb818708261f36cd05bc915e603aae5004 + '@prisma/fetch-engine': file:../../tmp/prisma-fetch-engine-0.0.0.tgz + '@prisma/get-platform': file:../../tmp/prisma-get-platform-0.0.0.tgz + + '@prisma/fetch-engine@file:../../tmp/prisma-fetch-engine-0.0.0.tgz': + dependencies: + '@prisma/debug': file:../../tmp/prisma-debug-0.0.0.tgz + '@prisma/engines-version': 5.19.0-25.feat-typed-sql-nullability-860858bb818708261f36cd05bc915e603aae5004 + '@prisma/get-platform': file:../../tmp/prisma-get-platform-0.0.0.tgz + + '@prisma/get-platform@file:../../tmp/prisma-get-platform-0.0.0.tgz': + dependencies: + '@prisma/debug': file:../../tmp/prisma-debug-0.0.0.tgz + + '@sinclair/typebox@0.27.8': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.12': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/node@16.18.98': {} + + '@types/stack-utils@2.0.3': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.32': + dependencies: + '@types/yargs-parser': 21.0.3 + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + ci-info@3.9.0: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + diff-sequences@29.6.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@2.0.0: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + fsevents@2.3.3: + optional: true + + graceful-fs@4.2.11: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + is-number@7.0.0: {} + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.24.7 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.7 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 16.18.98 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + js-tokens@4.0.0: {} + + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + picocolors@1.0.1: {} + + picomatch@2.3.1: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + prisma@file:../../tmp/prisma-0.0.0.tgz: + dependencies: + '@prisma/engines': file:../../tmp/prisma-engines-0.0.0.tgz + optionalDependencies: + fsevents: 2.3.3 + + react-is@18.3.1: {} + + slash@3.0.0: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 diff --git a/packages/client/tests/e2e/typed-sql/prisma/schema.prisma b/packages/client/tests/e2e/typed-sql/prisma/schema.prisma new file mode 100644 index 000000000000..b49fbadac02a --- /dev/null +++ b/packages/client/tests/e2e/typed-sql/prisma/schema.prisma @@ -0,0 +1,17 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" + previewFeatures = ["typedSql"] +} + +datasource db { + provider = "postgresql" + url = env("POSTGRES_URL") +} + +model User { + id Int @id @default(autoincrement()) + email String @unique +} diff --git a/packages/client/tests/e2e/typed-sql/prisma/sql/getEmail.sql b/packages/client/tests/e2e/typed-sql/prisma/sql/getEmail.sql new file mode 100644 index 000000000000..00b3603b8878 --- /dev/null +++ b/packages/client/tests/e2e/typed-sql/prisma/sql/getEmail.sql @@ -0,0 +1 @@ +SELECT "email" FROM "public"."User" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/e2e/typed-sql/readme.md b/packages/client/tests/e2e/typed-sql/readme.md new file mode 100644 index 000000000000..296970ea377b --- /dev/null +++ b/packages/client/tests/e2e/typed-sql/readme.md @@ -0,0 +1,3 @@ +# Readme + +Test for typed-sql default import diff --git a/packages/client/tests/e2e/typed-sql/src/esm-import.mjs b/packages/client/tests/e2e/typed-sql/src/esm-import.mjs new file mode 100644 index 000000000000..0901b774fdf6 --- /dev/null +++ b/packages/client/tests/e2e/typed-sql/src/esm-import.mjs @@ -0,0 +1,12 @@ +import { PrismaClient } from '@prisma/client' +import { getEmail } from '@prisma/client/sql' + +const prisma = new PrismaClient() + +const { id } = await prisma.user.create({ + data: { email: 'john-esm@doe.io' }, +}) + +const userRaw = await prisma.$queryRawTyped(getEmail(id)) + +console.log(userRaw) diff --git a/packages/client/tests/e2e/typed-sql/src/index.ts b/packages/client/tests/e2e/typed-sql/src/index.ts new file mode 100644 index 000000000000..a890fce592ea --- /dev/null +++ b/packages/client/tests/e2e/typed-sql/src/index.ts @@ -0,0 +1,16 @@ +import { PrismaClient } from '@prisma/client' +import { getEmail } from '@prisma/client/sql' + +async function main() { + const prisma = new PrismaClient() + + const { id } = await prisma.user.create({ + data: { email: 'john@doe.io' }, + }) + + const userRaw = await prisma.$queryRawTyped(getEmail(id)) + + console.log(userRaw) +} + +void main() diff --git a/packages/client/tests/e2e/typed-sql/tests/main.ts b/packages/client/tests/e2e/typed-sql/tests/main.ts new file mode 100644 index 000000000000..7ece96b8257a --- /dev/null +++ b/packages/client/tests/e2e/typed-sql/tests/main.ts @@ -0,0 +1,22 @@ +import { PrismaClient } from '@prisma/client' +import { getEmail } from '@prisma/client/sql' + +const prisma = new PrismaClient() + +beforeAll(async () => { + await prisma.user.deleteMany() + await prisma.user.create({ data: { id: 123, email: 'user@example.com' } }) +}) + +test('basic functionality', async () => { + const result = await prisma.$queryRawTyped(getEmail(123)) + expect(result).toMatchInlineSnapshot(` +[ + { + "email": "user@example.com", + }, +] +`) +}) + +export {} diff --git a/packages/client/tests/e2e/typed-sql/tsconfig.json b/packages/client/tests/e2e/typed-sql/tsconfig.json new file mode 100644 index 000000000000..1d0b21415338 --- /dev/null +++ b/packages/client/tests/e2e/typed-sql/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.base.json", + "exclude": ["_steps.ts"] +} diff --git a/packages/client/tests/functional/_utils/getTestSuiteInfo.ts b/packages/client/tests/functional/_utils/getTestSuiteInfo.ts index dc356bc0040a..03a2080b516c 100644 --- a/packages/client/tests/functional/_utils/getTestSuiteInfo.ts +++ b/packages/client/tests/functional/_utils/getTestSuiteInfo.ts @@ -1,4 +1,5 @@ import { ClientEngineType } from '@prisma/internals' +import fs from 'fs/promises' import path from 'path' import { matrix } from '../../../../../helpers/blaze/matrix' @@ -246,6 +247,7 @@ export function getTestSuiteMeta() { const prismaPath = path.join(testRoot, 'prisma') const _matrixPath = path.join(testRoot, '_matrix') const _schemaPath = path.join(prismaPath, '_schema') + const sqlPath = path.join(prismaPath, 'sql') return { testName, @@ -255,6 +257,7 @@ export function getTestSuiteMeta() { rootRelativeTestDir, testFileName, prismaPath, + sqlPath, _matrixPath, _schemaPath, } @@ -290,3 +293,19 @@ export function getTestSuiteClientMeta({ driverAdapter: isDriverAdapterProviderLabel(suiteConfig.driverAdapter), } } + +export async function testSuiteHasTypedSql(meta: TestSuiteMeta) { + return await isDirectory(meta.sqlPath) +} + +async function isDirectory(path: string) { + try { + const stat = await fs.stat(path) + return stat.isDirectory() + } catch (e) { + if (e.code === 'ENOENT') { + return false + } + throw e + } +} diff --git a/packages/client/tests/functional/_utils/setupTestSuiteClient.ts b/packages/client/tests/functional/_utils/setupTestSuiteClient.ts index 683fdd7c0fb6..8ee1aecc0c36 100644 --- a/packages/client/tests/functional/_utils/setupTestSuiteClient.ts +++ b/packages/client/tests/functional/_utils/setupTestSuiteClient.ts @@ -1,9 +1,11 @@ import type { D1Database } from '@cloudflare/workers-types' +import { SqlQueryOutput } from '@prisma/generator-helper' import { getConfig, getDMMF, parseEnvValue } from '@prisma/internals' import { readFile } from 'fs/promises' import path from 'path' import { fetch, WebSocket } from 'undici' +import { introspectSql } from '../../../../cli/src/generate/introspectSql' import { generateClient } from '../../../src/generation/generateClient' import { PrismaClientOptions } from '../../../src/runtime/getPrismaClient' import type { NamedTestSuiteConfig } from './getTestSuiteInfo' @@ -12,6 +14,7 @@ import { getTestSuitePreviewFeatures, getTestSuiteSchema, getTestSuiteSchemaPath, + testSuiteHasTypedSql, } from './getTestSuiteInfo' import { AdapterProviders } from './providers' import { DatasourceInfo, setupTestSuiteDatabase, setupTestSuiteFiles, setupTestSuiteSchema } from './setupTestSuiteEnv' @@ -24,7 +27,7 @@ const runtimeBase = path.join(__dirname, '..', '..', '..', 'runtime') * Does the necessary setup to get a test suite client ready to run. * @param suiteMeta * @param suiteConfig - * @returns loaded client module + * @returns tuple of loaded client folder + loaded sql folder */ export async function setupTestSuiteClient({ cliMeta, @@ -52,6 +55,7 @@ export async function setupTestSuiteClient({ const dmmf = await getDMMF({ datamodel: [[schemaPath, schema]], previewFeatures }) const config = await getConfig({ datamodel: [[schemaPath, schema]], ignoreEnvVarErrors: true }) const generator = config.generators.find((g) => parseEnvValue(g.provider) === 'prisma-client-js')! + const hasTypedSql = await testSuiteHasTypedSql(suiteMeta) await setupTestSuiteFiles({ suiteMeta, suiteConfig }) await setupTestSuiteSchema({ suiteMeta, suiteConfig, schema }) @@ -63,6 +67,11 @@ export async function setupTestSuiteClient({ await setupTestSuiteDatabase({ suiteMeta, suiteConfig, alterStatementCallback, cfWorkerBindings }) } + let typedSql: SqlQueryOutput[] | undefined + if (hasTypedSql) { + typedSql = await introspectSql(schemaPath) + } + if (clientMeta.dataProxy === true) { process.env[datasourceInfo.envVarName] = datasourceInfo.dataProxyUrl } else { @@ -84,15 +93,31 @@ export async function setupTestSuiteClient({ activeProvider: suiteConfig.matrixOptions.provider, runtimeBase: runtimeBase, copyEngine: !clientMeta.dataProxy, + typedSql, }) - const clientPathForRuntime: Record = { - node: 'node_modules/@prisma/client', - edge: 'node_modules/@prisma/client/edge', - wasm: 'node_modules/@prisma/client/wasm', + const clientPathForRuntime: Record = { + node: { + client: 'node_modules/@prisma/client', + sql: 'node_modules/@prisma/client/sql', + }, + edge: { + client: 'node_modules/@prisma/client/edge', + sql: 'node_modules/@prisma/client/sql/index.edge.js', + }, + wasm: { + client: 'node_modules/@prisma/client/wasm', + sql: 'node_modules/@prisma/client/sql/index.wasm.js', + }, + } + + const clientModule = require(path.join(suiteFolderPath, clientPathForRuntime[clientMeta.runtime].client)) + let sqlModule = undefined + if (hasTypedSql) { + sqlModule = require(path.join(suiteFolderPath, clientPathForRuntime[clientMeta.runtime].sql)) } - return require(path.join(suiteFolderPath, clientPathForRuntime[clientMeta.runtime])) + return [clientModule, sqlModule] } /** diff --git a/packages/client/tests/functional/_utils/setupTestSuiteEnv.ts b/packages/client/tests/functional/_utils/setupTestSuiteEnv.ts index d00b540ab7e7..b14e1fb2714b 100644 --- a/packages/client/tests/functional/_utils/setupTestSuiteEnv.ts +++ b/packages/client/tests/functional/_utils/setupTestSuiteEnv.ts @@ -12,7 +12,7 @@ import { DbDrop } from '../../../../migrate/src/commands/DbDrop' import { DbExecute } from '../../../../migrate/src/commands/DbExecute' import { DbPush } from '../../../../migrate/src/commands/DbPush' import type { NamedTestSuiteConfig } from './getTestSuiteInfo' -import { getTestSuiteFolderPath, getTestSuiteSchemaPath } from './getTestSuiteInfo' +import { getTestSuiteFolderPath, getTestSuiteSchemaPath, testSuiteHasTypedSql } from './getTestSuiteInfo' import { AdapterProviders, Providers } from './providers' import type { TestSuiteMeta } from './setupTestSuiteMatrix' import { AlterStatementCallback, ClientMeta } from './types' @@ -33,6 +33,9 @@ export async function setupTestSuiteFiles({ // we copy the minimum amount of files needed for the test suite await fs.copy(path.join(suiteMeta.testRoot, 'prisma'), path.join(suiteFolder, 'prisma')) + if (await testSuiteHasTypedSql(suiteMeta)) { + await fs.copy(suiteMeta.sqlPath, path.join(suiteFolder, 'prisma', 'sql')) + } await fs.mkdir(path.join(suiteFolder, suiteMeta.rootRelativeTestDir), { recursive: true }) await copyPreprocessed({ from: suiteMeta.testPath, diff --git a/packages/client/tests/functional/_utils/setupTestSuiteMatrix.ts b/packages/client/tests/functional/_utils/setupTestSuiteMatrix.ts index 7aa6ec127030..ee6a86dc9b46 100644 --- a/packages/client/tests/functional/_utils/setupTestSuiteMatrix.ts +++ b/packages/client/tests/functional/_utils/setupTestSuiteMatrix.ts @@ -108,7 +108,7 @@ function setupTestSuiteMatrix( cfWorkerBindings = env } - globalThis['loaded'] = await setupTestSuiteClient({ + const [clientModule, sqlModule] = await setupTestSuiteClient({ cliMeta, suiteMeta, suiteConfig, @@ -119,6 +119,8 @@ function setupTestSuiteMatrix( cfWorkerBindings, }) + globalThis['loaded'] = clientModule + const newDriverAdapter = () => setupTestSuiteClientDriverAdapter({ suiteConfig, @@ -128,7 +130,7 @@ function setupTestSuiteMatrix( }) globalThis['newPrismaClient'] = (args: any) => { - const { PrismaClient, Prisma } = globalThis['loaded'] + const { PrismaClient, Prisma } = clientModule const options = { ...newDriverAdapter(), ...args } const client = new PrismaClient(options) @@ -143,7 +145,9 @@ function setupTestSuiteMatrix( globalThis['prisma'] = globalThis['newPrismaClient']() as Client } - globalThis['Prisma'] = (await global['loaded'])['Prisma'] + globalThis['Prisma'] = clientModule['Prisma'] + + globalThis['sql'] = sqlModule globalThis['db'] = { setupDb: () => @@ -208,6 +212,7 @@ function setupTestSuiteMatrix( delete globalThis['loaded'] delete globalThis['prisma'] delete globalThis['Prisma'] + delete globalThis['sql'] delete globalThis['newPrismaClient'] }, 180_000) diff --git a/packages/client/tests/functional/extensions/client.ts b/packages/client/tests/functional/extensions/client.ts index 8f2d2a2a421f..3239ef6fcd16 100644 --- a/packages/client/tests/functional/extensions/client.ts +++ b/packages/client/tests/functional/extensions/client.ts @@ -288,11 +288,11 @@ testMatrix.setupTestSuite(() => { const typeDataSql = await xprisma.$transaction([ // @ts-test-if: provider !== Providers.MONGODB - xprisma.$executeRaw<1>(Prisma.sql`...`), + xprisma.$executeRaw(Prisma.sql`...`), // @ts-test-if: provider !== Providers.MONGODB - xprisma.$executeRaw<2>`...`, + xprisma.$executeRaw`...`, // @ts-test-if: provider !== Providers.MONGODB - xprisma.$executeRawUnsafe<3>('...'), + xprisma.$executeRawUnsafe('...'), // @ts-test-if: provider !== Providers.MONGODB xprisma.$queryRaw<4>(Prisma.sql`...`), // @ts-test-if: provider !== Providers.MONGODB @@ -302,7 +302,7 @@ testMatrix.setupTestSuite(() => { ]) // @ts-test-if: provider !== Providers.MONGODB - expectTypeOf(typeDataSql).toEqualTypeOf<[1, 2, 3, 4, 5, 6]>() + expectTypeOf(typeDataSql).toEqualTypeOf<[number, number, number, 4, 5, 6]>() const defaultDataSql = await xprisma.$transaction([ // @ts-test-if: provider !== Providers.MONGODB @@ -322,14 +322,6 @@ testMatrix.setupTestSuite(() => { // @ts-test-if: provider !== Providers.MONGODB expectTypeOf(defaultDataSql).toEqualTypeOf<[number, number, number, unknown, unknown, unknown]>() - const typeDataMongo = await xprisma.$transaction([ - // @ts-test-if: provider === Providers.MONGODB - xprisma.$runCommandRaw<1>({ value: '...' }), - ]) - - // @ts-test-if: provider === Providers.MONGODB - expectTypeOf(typeDataMongo).toEqualTypeOf<[1]>() - const defaultDataMongo = await xprisma.$transaction([ // @ts-test-if: provider === Providers.MONGODB xprisma.$runCommandRaw({ value: '...' }), diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/_matrix.ts b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/_matrix.ts new file mode 100644 index 000000000000..3585f7b579f9 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/_matrix.ts @@ -0,0 +1,4 @@ +import { defineMatrix } from '../../_utils/defineMatrix' +import { Providers } from '../../_utils/providers' + +export default defineMatrix(() => [[{ provider: Providers.MYSQL }]]) diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/_schema.ts b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/_schema.ts new file mode 100644 index 000000000000..ed49a5dda3a3 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/_schema.ts @@ -0,0 +1,38 @@ +import { idForProvider } from '../../../_utils/idForProvider' +import testMatrix from '../_matrix' + +export default testMatrix.setupSchema(({ provider }) => { + return /* Prisma */ ` + generator client { + provider = "prisma-client-js" + previewFeatures = ["typedSql"] + } + + datasource db { + provider = "${provider}" + url = env("DATABASE_URI_${provider}") + } + + model TestModel { + id ${idForProvider(provider)} + string String? + int Int? @db.SmallInt + bytes Bytes? + json Json? + float Float? @db.Float + double Float? + bool Boolean? + bigInt BigInt? + decimal Decimal? + dateTime DateTime? + enum Enum? + date DateTime? @db.Date + time DateTime? @db.Time + } + + enum Enum { + ONE + TWO + } + ` +}) diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findBigInt.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findBigInt.sql new file mode 100644 index 000000000000..e6310bf31fb5 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findBigInt.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `bigInt` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findBytes.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findBytes.sql new file mode 100644 index 000000000000..bed1d50b6c0a --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findBytes.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `bytes` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findDate.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findDate.sql new file mode 100644 index 000000000000..397f5fa277aa --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findDate.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `date` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findDateTime.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findDateTime.sql new file mode 100644 index 000000000000..71fa6bb79a41 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findDateTime.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `dateTime` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findDecimal.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findDecimal.sql new file mode 100644 index 000000000000..9ccc597b35de --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findDecimal.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `decimal` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findDouble.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findDouble.sql new file mode 100644 index 000000000000..1d122c45504c --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findDouble.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `double` < ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findFloat.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findFloat.sql new file mode 100644 index 000000000000..ab7150550ff2 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findFloat.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `float` < ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findInt.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findInt.sql new file mode 100644 index 000000000000..234afde1970e --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findInt.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `int` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findJson.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findJson.sql new file mode 100644 index 000000000000..4db3eba21b53 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findJson.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `json` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findString.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findString.sql new file mode 100644 index 000000000000..03cfa84ca7cc --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findString.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `string` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findTime.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findTime.sql new file mode 100644 index 000000000000..70f0ece56314 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/findTime.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `time` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getBigInt.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getBigInt.sql new file mode 100644 index 000000000000..85dce8622e46 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getBigInt.sql @@ -0,0 +1 @@ +SELECT `bigInt` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getBytes.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getBytes.sql new file mode 100644 index 000000000000..63c1ac206c8b --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getBytes.sql @@ -0,0 +1 @@ +SELECT `bytes` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getDate.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getDate.sql new file mode 100644 index 000000000000..cb6502926705 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getDate.sql @@ -0,0 +1 @@ +SELECT `date` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getDateTime.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getDateTime.sql new file mode 100644 index 000000000000..e1fbd06c02fb --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getDateTime.sql @@ -0,0 +1 @@ +SELECT `dateTime` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getDecimal.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getDecimal.sql new file mode 100644 index 000000000000..f3c693dbd894 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getDecimal.sql @@ -0,0 +1 @@ +SELECT `decimal` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getDouble.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getDouble.sql new file mode 100644 index 000000000000..412f0b72407b --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getDouble.sql @@ -0,0 +1 @@ +SELECT `double` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getFloat.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getFloat.sql new file mode 100644 index 000000000000..e7094df4586f --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getFloat.sql @@ -0,0 +1 @@ +SELECT `float` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getInt.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getInt.sql new file mode 100644 index 000000000000..e83c7ce4d06b --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getInt.sql @@ -0,0 +1 @@ +SELECT `int` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getJson.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getJson.sql new file mode 100644 index 000000000000..36c45fc94550 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getJson.sql @@ -0,0 +1 @@ +SELECT `json` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getString.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getString.sql new file mode 100644 index 000000000000..e1f37d25fba3 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getString.sql @@ -0,0 +1 @@ +SELECT `string` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getTime.sql b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getTime.sql new file mode 100644 index 000000000000..ca8e2963da23 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/prisma/sql/getTime.sql @@ -0,0 +1 @@ +SELECT `time` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/test.ts b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/test.ts new file mode 100644 index 000000000000..112d1ff3091e --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars-nullable/test.ts @@ -0,0 +1,179 @@ +import { expectTypeOf } from 'expect-type' + +import testMatrix from './_matrix' +// @ts-ignore +import type { Prisma as PrismaNamespace, PrismaClient } from './node_modules/@prisma/client' +// @ts-ignore +import * as Sql from './node_modules/@prisma/client/sql' + +declare let prisma: PrismaClient +declare let Prisma: typeof PrismaNamespace +declare let sql: typeof Sql + +const id = '1234' +const bigInt = BigInt('12345') +const dateTime = new Date('2024-07-31T14:37:36.570Z') +const date = new Date('2024-07-31T00:00:00.000Z') +const time = new Date('1970-01-01T14:37:36.000Z') +const bytes = Buffer.from('hello') +testMatrix.setupTestSuite( + ({ clientRuntime }) => { + beforeAll(async () => { + await prisma.testModel.create({ + data: { + id, + int: 123, + double: 12.3, + float: 12.3, + bool: true, + string: 'hello', + enum: 'ONE', + json: { hello: 'world' }, + bigInt, + dateTime, + date, + time, + bytes, + decimal: new Prisma.Decimal('12.34'), + }, + }) + }) + test('int - output', async () => { + const result = await prisma.$queryRawTyped(sql.getInt(id)) + expect(result[0].int).toBe(123) + expectTypeOf(result[0].int).toEqualTypeOf() + }) + + test('int - input', async () => { + const result = await prisma.$queryRawTyped(sql.findInt(123)) + expect(result[0].id).toBe(id) + }) + + test('float - output', async () => { + const result = await prisma.$queryRawTyped(sql.getFloat(id)) + expect(result[0].float).toBe(12.3) + expectTypeOf(result[0].float).toEqualTypeOf() + }) + + test('float - input', async () => { + const result = await prisma.$queryRawTyped(sql.findFloat(13.1)) + expect(result[0].id).toBe(id) + }) + + test('double - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDouble(id)) + expect(result[0].double).toBe(12.3) + expectTypeOf(result[0].double).toEqualTypeOf() + }) + + test('double - input', async () => { + const result = await prisma.$queryRawTyped(sql.findDouble(13.1)) + expect(result[0].id).toBe(id) + }) + + test('string - output', async () => { + const result = await prisma.$queryRawTyped(sql.getString(id)) + expect(result[0].string).toEqual('hello') + expectTypeOf(result[0].string).toEqualTypeOf() + }) + + test('string - input', async () => { + const result = await prisma.$queryRawTyped(sql.findString('hello')) + expect(result[0].id).toEqual(id) + }) + + test('BigInt - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBigInt(id)) + expect(result[0].bigInt).toEqual(bigInt) + expectTypeOf(result[0].bigInt).toEqualTypeOf() + }) + + test('BigInt - input', async () => { + const numberResult = await prisma.$queryRawTyped(sql.findBigInt(12345)) + expect(numberResult[0].id).toEqual(id) + + const bigintResult = await prisma.$queryRawTyped(sql.findBigInt(bigInt)) + expect(bigintResult[0].id).toEqual(id) + }) + + test('DateTime - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDateTime(id)) + expect(result[0].dateTime).toEqual(dateTime) + expectTypeOf(result[0].dateTime).toEqualTypeOf() + }) + + test('DateTime - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findDateTime(dateTime)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Date - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDate(id)) + expect(result[0].date).toEqual(date) + expectTypeOf(result[0].date).toEqualTypeOf() + }) + + test('Date - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findDate(date)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Time - output', async () => { + const result = await prisma.$queryRawTyped(sql.getTime(id)) + expect(result[0].time).toEqual(time) + expectTypeOf(result[0].time).toEqualTypeOf() + }) + + test('Time - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findTime(time)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Decimal - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDecimal(id)) + expect(result[0].decimal).toBeInstanceOf(Prisma.Decimal) + expect(result[0].decimal).toEqual(new Prisma.Decimal('12.34')) + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + expectTypeOf(result[0].decimal).toEqualTypeOf() + }) + + test('Decimal - input', async () => { + const resultDecimal = await prisma.$queryRawTyped(sql.findDecimal(new Prisma.Decimal('12.34'))) + expect(resultDecimal[0].id).toBe(id) + + const resultNumber = await prisma.$queryRawTyped(sql.findDecimal(12.34)) + expect(resultNumber[0].id).toBe(id) + }) + + test('bytes - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBytes(id)) + if (clientRuntime == 'node') { + // edge/wasm runtimes polyfill Buffer and so this assertion does not work + expect(result[0].bytes).toEqual(Buffer.from('hello')) + } + expectTypeOf(result[0].bytes).toEqualTypeOf() + }) + + test('bytes - input', async () => { + const result = await prisma.$queryRawTyped(sql.findBytes(bytes)) + expect(result[0].id).toEqual(id) + }) + + test('json - output', async () => { + const result = await prisma.$queryRawTyped(sql.getJson(id)) + expect(result[0].json).toEqual({ hello: 'world' }) + + expectTypeOf(result[0].json).toEqualTypeOf() + }) + }, + { + optOut: { + from: ['sqlite', 'postgresql', 'mongodb', 'cockroachdb', 'sqlserver'], + reason: 'Focusing on mysql only', + }, + skipDriverAdapter: { + from: ['js_planetscale'], + reason: 'Type inference for inputs is broken on vitess and requires explicit type annotations', + }, + }, +) diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/_matrix.ts b/packages/client/tests/functional/typed-sql/mysql-scalars/_matrix.ts new file mode 100644 index 000000000000..3585f7b579f9 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/_matrix.ts @@ -0,0 +1,4 @@ +import { defineMatrix } from '../../_utils/defineMatrix' +import { Providers } from '../../_utils/providers' + +export default defineMatrix(() => [[{ provider: Providers.MYSQL }]]) diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/_schema.ts b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/_schema.ts new file mode 100644 index 000000000000..e963950d3bbf --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/_schema.ts @@ -0,0 +1,38 @@ +import { idForProvider } from '../../../_utils/idForProvider' +import testMatrix from '../_matrix' + +export default testMatrix.setupSchema(({ provider }) => { + return /* Prisma */ ` + generator client { + provider = "prisma-client-js" + previewFeatures = ["typedSql"] + } + + datasource db { + provider = "${provider}" + url = env("DATABASE_URI_${provider}") + } + + model TestModel { + id ${idForProvider(provider)} + string String + int Int @db.SmallInt + bytes Bytes + json Json + float Float @db.Float + double Float + bool Boolean + bigInt BigInt + decimal Decimal + dateTime DateTime + enum Enum + date DateTime @db.Date + time DateTime @db.Time + } + + enum Enum { + ONE + TWO + } + ` +}) diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findBigInt.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findBigInt.sql new file mode 100644 index 000000000000..e6310bf31fb5 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findBigInt.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `bigInt` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findBytes.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findBytes.sql new file mode 100644 index 000000000000..bed1d50b6c0a --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findBytes.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `bytes` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findDate.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findDate.sql new file mode 100644 index 000000000000..397f5fa277aa --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findDate.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `date` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findDateTime.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findDateTime.sql new file mode 100644 index 000000000000..71fa6bb79a41 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findDateTime.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `dateTime` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findDecimal.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findDecimal.sql new file mode 100644 index 000000000000..9ccc597b35de --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findDecimal.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `decimal` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findDouble.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findDouble.sql new file mode 100644 index 000000000000..1d122c45504c --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findDouble.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `double` < ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findFloat.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findFloat.sql new file mode 100644 index 000000000000..ab7150550ff2 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findFloat.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `float` < ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findInt.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findInt.sql new file mode 100644 index 000000000000..234afde1970e --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findInt.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `int` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findJson.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findJson.sql new file mode 100644 index 000000000000..4db3eba21b53 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findJson.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `json` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findString.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findString.sql new file mode 100644 index 000000000000..03cfa84ca7cc --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findString.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `string` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findTime.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findTime.sql new file mode 100644 index 000000000000..70f0ece56314 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/findTime.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `time` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getBigInt.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getBigInt.sql new file mode 100644 index 000000000000..85dce8622e46 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getBigInt.sql @@ -0,0 +1 @@ +SELECT `bigInt` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getBytes.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getBytes.sql new file mode 100644 index 000000000000..63c1ac206c8b --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getBytes.sql @@ -0,0 +1 @@ +SELECT `bytes` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getDate.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getDate.sql new file mode 100644 index 000000000000..cb6502926705 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getDate.sql @@ -0,0 +1 @@ +SELECT `date` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getDateTime.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getDateTime.sql new file mode 100644 index 000000000000..e1fbd06c02fb --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getDateTime.sql @@ -0,0 +1 @@ +SELECT `dateTime` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getDecimal.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getDecimal.sql new file mode 100644 index 000000000000..f3c693dbd894 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getDecimal.sql @@ -0,0 +1 @@ +SELECT `decimal` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getDouble.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getDouble.sql new file mode 100644 index 000000000000..412f0b72407b --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getDouble.sql @@ -0,0 +1 @@ +SELECT `double` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getFloat.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getFloat.sql new file mode 100644 index 000000000000..e7094df4586f --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getFloat.sql @@ -0,0 +1 @@ +SELECT `float` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getInt.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getInt.sql new file mode 100644 index 000000000000..e83c7ce4d06b --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getInt.sql @@ -0,0 +1 @@ +SELECT `int` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getJson.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getJson.sql new file mode 100644 index 000000000000..36c45fc94550 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getJson.sql @@ -0,0 +1 @@ +SELECT `json` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getString.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getString.sql new file mode 100644 index 000000000000..e1f37d25fba3 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getString.sql @@ -0,0 +1 @@ +SELECT `string` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getTime.sql b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getTime.sql new file mode 100644 index 000000000000..ca8e2963da23 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/prisma/sql/getTime.sql @@ -0,0 +1 @@ +SELECT `time` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/mysql-scalars/test.ts b/packages/client/tests/functional/typed-sql/mysql-scalars/test.ts new file mode 100644 index 000000000000..5c8e9fb6860e --- /dev/null +++ b/packages/client/tests/functional/typed-sql/mysql-scalars/test.ts @@ -0,0 +1,177 @@ +import { expectTypeOf } from 'expect-type' + +import testMatrix from './_matrix' +// @ts-ignore +import type { Prisma as PrismaNamespace, PrismaClient } from './node_modules/@prisma/client' +// @ts-ignore +import * as Sql from './node_modules/@prisma/client/sql' + +declare let prisma: PrismaClient +declare let Prisma: typeof PrismaNamespace +declare let sql: typeof Sql + +const id = '1234' +const bigInt = BigInt('12345') +const dateTime = new Date('2024-07-31T14:37:36.570Z') +const date = new Date('2024-07-31T00:00:00.000Z') +const time = new Date('1970-01-01T14:37:36.000Z') +testMatrix.setupTestSuite( + ({ clientRuntime }) => { + beforeAll(async () => { + await prisma.testModel.create({ + data: { + id, + int: 123, + double: 12.3, + float: 12.3, + bool: true, + string: 'hello', + enum: 'ONE', + json: { hello: 'world' }, + bigInt, + dateTime, + date, + time, + bytes: Buffer.from('hello'), + decimal: new Prisma.Decimal('12.34'), + }, + }) + }) + test('int - output', async () => { + const result = await prisma.$queryRawTyped(sql.getInt(id)) + expect(result[0].int).toBe(123) + expectTypeOf(result[0].int).toBeNumber() + }) + + test('int - input', async () => { + const result = await prisma.$queryRawTyped(sql.findInt(123)) + expect(result[0].id).toBe(id) + }) + + test('float - output', async () => { + const result = await prisma.$queryRawTyped(sql.getFloat(id)) + expect(result[0].float).toBe(12.3) + expectTypeOf(result[0].float).toBeNumber() + }) + + test('float - input', async () => { + const result = await prisma.$queryRawTyped(sql.findFloat(13.1)) + expect(result[0].id).toBe(id) + }) + + test('double - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDouble(id)) + expect(result[0].double).toBe(12.3) + expectTypeOf(result[0].double).toBeNumber() + }) + + test('double - input', async () => { + const result = await prisma.$queryRawTyped(sql.findDouble(13.1)) + expect(result[0].id).toBe(id) + }) + + test('string - output', async () => { + const result = await prisma.$queryRawTyped(sql.getString(id)) + expect(result[0].string).toEqual('hello') + expectTypeOf(result[0].string).toBeString() + }) + + test('string - input', async () => { + const result = await prisma.$queryRawTyped(sql.findString('hello')) + expect(result[0].id).toEqual(id) + }) + + test('BigInt - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBigInt(id)) + expect(result[0].bigInt).toEqual(bigInt) + expectTypeOf(result[0].bigInt).toEqualTypeOf() + }) + + test('BigInt - input', async () => { + const numberResult = await prisma.$queryRawTyped(sql.findBigInt(12345)) + expect(numberResult[0].id).toEqual(id) + + const bigintResult = await prisma.$queryRawTyped(sql.findBigInt(bigInt)) + expect(bigintResult[0].id).toEqual(id) + }) + + test('DateTime - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDateTime(id)) + expect(result[0].dateTime).toEqual(dateTime) + expectTypeOf(result[0].dateTime).toEqualTypeOf() + }) + + test('DateTime - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findDateTime(dateTime)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Date - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDate(id)) + expect(result[0].date).toEqual(date) + expectTypeOf(result[0].date).toEqualTypeOf() + }) + + test('Date - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findDate(date)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Time - output', async () => { + const result = await prisma.$queryRawTyped(sql.getTime(id)) + expect(result[0].time).toEqual(time) + expectTypeOf(result[0].time).toEqualTypeOf() + }) + + test('Time - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findTime(time)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Decimal - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDecimal(id)) + expect(result[0].decimal).toBeInstanceOf(Prisma.Decimal) + expect(result[0].decimal).toEqual(new Prisma.Decimal('12.34')) + expectTypeOf(result[0].decimal).toEqualTypeOf() + }) + + test('Decimal - input', async () => { + const resultDecimal = await prisma.$queryRawTyped(sql.findDecimal(new Prisma.Decimal('12.34'))) + expect(resultDecimal[0].id).toBe(id) + + const resultNumber = await prisma.$queryRawTyped(sql.findDecimal(12.34)) + expect(resultNumber[0].id).toBe(id) + }) + + test('bytes - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBytes(id)) + if (clientRuntime === 'node') { + // edge/wasm runtimes polyfill Buffer and so this assertion does not work + expect(result[0].bytes).toEqual(Buffer.from('hello')) + } + expectTypeOf(result[0].bytes).toEqualTypeOf() + }) + + test('bytes - input', async () => { + const result = await prisma.$queryRawTyped(sql.findBytes(Buffer.from('hello'))) + expect(result[0].id).toEqual(id) + }) + + test('json - output', async () => { + const result = await prisma.$queryRawTyped(sql.getJson(id)) + expect(result[0].json).toEqual({ hello: 'world' }) + + expectTypeOf(result[0].json).toEqualTypeOf() + }) + }, + { + optOut: { + from: ['sqlite', 'postgresql', 'mongodb', 'cockroachdb', 'sqlserver'], + reason: 'Focusing on mysql only', + }, + skipDriverAdapter: { + from: ['js_planetscale'], + reason: 'Type inference for inputs is broken on vitess and requires explicit type annotations', + }, + }, +) diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/_matrix.ts b/packages/client/tests/functional/typed-sql/postgres-lists/_matrix.ts new file mode 100644 index 000000000000..e0b8310fd2b4 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/_matrix.ts @@ -0,0 +1,4 @@ +import { defineMatrix } from '../../_utils/defineMatrix' +import { Providers } from '../../_utils/providers' + +export default defineMatrix(() => [[{ provider: Providers.POSTGRESQL }]]) diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/_schema.ts b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/_schema.ts new file mode 100644 index 000000000000..a0ff0dc3d54c --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/_schema.ts @@ -0,0 +1,40 @@ +import { idForProvider } from '../../../_utils/idForProvider' +import testMatrix from '../_matrix' + +export default testMatrix.setupSchema(({ provider }) => { + return /* Prisma */ ` + generator client { + provider = "prisma-client-js" + previewFeatures = ["typedSql"] + } + + datasource db { + provider = "${provider}" + url = env("DATABASE_URI_${provider}") + } + + model TestModel { + id ${idForProvider(provider)} + string String[] + xml String[] @db.Xml + uuid String[] @db.Uuid + int Int[] + real Float[] @db.Real + bytes Bytes[] + json Json[] + double Float[] + bool Boolean[] + bigInt BigInt[] + decimal Decimal[] + dateTime DateTime[] + enum Enum[] + date DateTime[] @db.Date + time DateTime[] @db.Time + } + + enum Enum { + ONE + TWO + } + ` +}) diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findBigInt.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findBigInt.sql new file mode 100644 index 000000000000..ee9a25c40c4c --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findBigInt.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "bigInt" = $1::BIGINT[] \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findBool.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findBool.sql new file mode 100644 index 000000000000..aa1c5c458dc7 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findBool.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "bool" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findBytes.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findBytes.sql new file mode 100644 index 000000000000..f0fb4858bd21 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findBytes.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "bytes" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findDate.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findDate.sql new file mode 100644 index 000000000000..89b32e258809 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findDate.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "date" = $1::DATE[] \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findDateTime.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findDateTime.sql new file mode 100644 index 000000000000..78bddb2e3346 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findDateTime.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "dateTime" = $1::TIMESTAMP[] \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findDecimal.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findDecimal.sql new file mode 100644 index 000000000000..1ba90a847db3 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findDecimal.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "decimal" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findDouble.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findDouble.sql new file mode 100644 index 000000000000..42b9eff01d4b --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findDouble.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "double" = $1::double precision[] \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findInt.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findInt.sql new file mode 100644 index 000000000000..17017916900e --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findInt.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "int" = $1::INT[] \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findJson.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findJson.sql new file mode 100644 index 000000000000..937e159f18ce --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findJson.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "json" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findReal.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findReal.sql new file mode 100644 index 000000000000..f759f30104b2 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findReal.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "real" = $1::real[] \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findString.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findString.sql new file mode 100644 index 000000000000..948dbbc289c7 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findString.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "string" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findTime.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findTime.sql new file mode 100644 index 000000000000..f34349ba925a --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findTime.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "time" = $1::TIME[] \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findUuid.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findUuid.sql new file mode 100644 index 000000000000..3445ad69b7b1 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/findUuid.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "uuid" = $1::UUID[] \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getBigInt.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getBigInt.sql new file mode 100644 index 000000000000..19db47e9c1d3 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getBigInt.sql @@ -0,0 +1 @@ +SELECT "bigInt" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getBool.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getBool.sql new file mode 100644 index 000000000000..14e93354bce5 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getBool.sql @@ -0,0 +1 @@ +SELECT "bool" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getBytes.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getBytes.sql new file mode 100644 index 000000000000..78116bfea43e --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getBytes.sql @@ -0,0 +1 @@ +SELECT "bytes" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getDate.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getDate.sql new file mode 100644 index 000000000000..e66db37fe356 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getDate.sql @@ -0,0 +1 @@ +SELECT "date" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getDateTime.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getDateTime.sql new file mode 100644 index 000000000000..1f9e147f1291 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getDateTime.sql @@ -0,0 +1 @@ +SELECT "dateTime" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getDecimal.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getDecimal.sql new file mode 100644 index 000000000000..0c537fb417ae --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getDecimal.sql @@ -0,0 +1 @@ +SELECT "decimal" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getDouble.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getDouble.sql new file mode 100644 index 000000000000..63cc69e77723 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getDouble.sql @@ -0,0 +1 @@ +SELECT "double" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getInt.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getInt.sql new file mode 100644 index 000000000000..43bc3892f182 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getInt.sql @@ -0,0 +1 @@ +SELECT "int" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getJson.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getJson.sql new file mode 100644 index 000000000000..9d09882c6fce --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getJson.sql @@ -0,0 +1 @@ +SELECT "json" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getReal.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getReal.sql new file mode 100644 index 000000000000..936038a45046 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getReal.sql @@ -0,0 +1 @@ +SELECT "real" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getString.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getString.sql new file mode 100644 index 000000000000..268b8c3d5580 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getString.sql @@ -0,0 +1 @@ +SELECT "string" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getTime.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getTime.sql new file mode 100644 index 000000000000..88f94f4a3e6e --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getTime.sql @@ -0,0 +1 @@ +SELECT "time" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getUuid.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getUuid.sql new file mode 100644 index 000000000000..4642489be928 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getUuid.sql @@ -0,0 +1 @@ +SELECT "uuid" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getXml.sql b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getXml.sql new file mode 100644 index 000000000000..203811b923df --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/prisma/sql/getXml.sql @@ -0,0 +1 @@ +SELECT "xml" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-lists/test.ts b/packages/client/tests/functional/typed-sql/postgres-lists/test.ts new file mode 100644 index 000000000000..43a06360e257 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-lists/test.ts @@ -0,0 +1,198 @@ +import { faker } from '@faker-js/faker' +import { expectTypeOf } from 'expect-type' + +import testMatrix from './_matrix' +// @ts-ignore +import type { Prisma as PrismaNamespace, PrismaClient } from './node_modules/@prisma/client' +// @ts-ignore +import * as Sql from './node_modules/@prisma/client/sql' + +declare let prisma: PrismaClient +declare let Prisma: typeof PrismaNamespace +declare let sql: typeof Sql + +const id = '1234' +const bigInt = [BigInt('12'), BigInt('34')] +const dateTime = [new Date('2024-07-31T14:37:36.570Z'), new Date('2024-08-01T15:38:39.571Z')] +const date = [new Date('2024-08-01T00:00:00.000Z'), new Date('2024-07-31T00:00:00.000Z')] +const time = [new Date('1970-01-01T14:37:36.570Z'), new Date('1970-01-01T15:37:36.570Z')] +const uuid = [faker.string.uuid(), faker.string.uuid()] +const bytes = [Buffer.from('hello'), Buffer.from('world')] + +testMatrix.setupTestSuite( + ({ clientRuntime }) => { + beforeAll(async () => { + await prisma.testModel.create({ + data: { + id, + int: [1, 2, 3], + real: [1.1, 2.2, 3.3], + double: [1.1, 2.2, 3.3], + bool: [true, false], + string: ['hello', 'world'], + xml: ['', ''], + enum: ['ONE', 'TWO'], + json: [{ hello: 'world' }, { goodbye: 'blue sky' }], + uuid, + bigInt, + dateTime, + date, + time, + bytes, + decimal: [new Prisma.Decimal('12.34'), new Prisma.Decimal('45.67')], + }, + }) + }) + test('int - output', async () => { + const result = await prisma.$queryRawTyped(sql.getInt(id)) + expect(result[0].int).toEqual([1, 2, 3]) + expectTypeOf(result[0].int).toEqualTypeOf() + }) + + test('int - input', async () => { + const result = await prisma.$queryRawTyped(sql.findInt([1, 2, 3])) + expect(result[0].id).toBe(id) + }) + + test('real - output', async () => { + const result = await prisma.$queryRawTyped(sql.getReal(id)) + expect(result[0].real).toEqual([1.1, 2.2, 3.3]) + expectTypeOf(result[0].real).toEqualTypeOf() + }) + + test('real - input', async () => { + const result = await prisma.$queryRawTyped(sql.findReal([1.1, 2.2, 3.3])) + expect(result[0].id).toBe(id) + }) + + test('double - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDouble(id)) + expect(result[0].double).toEqual([1.1, 2.2, 3.3]) + expectTypeOf(result[0].double).toEqualTypeOf() + }) + + test('double - input', async () => { + const result = await prisma.$queryRawTyped(sql.findDouble([1.1, 2.2, 3.3])) + expect(result[0].id).toBe(id) + }) + + test('string - output', async () => { + const result = await prisma.$queryRawTyped(sql.getString(id)) + expect(result[0].string).toEqual(['hello', 'world']) + expectTypeOf(result[0].string).toEqualTypeOf() + }) + + test('string - input', async () => { + const result = await prisma.$queryRawTyped(sql.findString(['hello', 'world'])) + expect(result[0].id).toEqual(id) + }) + + test('BigInt - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBigInt(id)) + expect(result[0].bigInt).toEqual(bigInt) + expectTypeOf(result[0].bigInt).toEqualTypeOf() + }) + + test('BigInt - input', async () => { + const numberResult = await prisma.$queryRawTyped(sql.findBigInt([12, 34])) + expect(numberResult[0].id).toEqual(id) + + const bigintResult = await prisma.$queryRawTyped(sql.findBigInt(bigInt)) + expect(bigintResult[0].id).toEqual(id) + }) + + test('DateTime - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDateTime(id)) + expect(result[0].dateTime).toEqual(dateTime) + expectTypeOf(result[0].dateTime).toEqualTypeOf() + }) + + test('DateTime - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findDateTime(dateTime)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Date - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDate(id)) + expect(result[0].date).toEqual(date) + expectTypeOf(result[0].date).toEqualTypeOf() + }) + + test('Date - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findDate(date)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Time - output', async () => { + const result = await prisma.$queryRawTyped(sql.getTime(id)) + expect(result[0].time).toEqual(time) + expectTypeOf(result[0].time).toEqualTypeOf() + }) + + test('Time - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findTime(time)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Decimal - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDecimal(id)) + expect(result[0].decimal![0]).toBeInstanceOf(Prisma.Decimal) + expect(result[0].decimal).toEqual([new Prisma.Decimal('12.34'), new Prisma.Decimal('45.67')]) + expectTypeOf(result[0].decimal).toEqualTypeOf() + }) + + test('Decimal - input', async () => { + const resultDecimal = await prisma.$queryRawTyped( + sql.findDecimal([new Prisma.Decimal('12.34'), new Prisma.Decimal('45.67')]), + ) + expect(resultDecimal[0].id).toBe(id) + + const resultNumber = await prisma.$queryRawTyped(sql.findDecimal([12.34, 45.67])) + expect(resultNumber[0].id).toBe(id) + }) + + test('xml - output', async () => { + const result = await prisma.$queryRawTyped(sql.getXml(id)) + expect(result[0].xml).toEqual(['', '']) + expectTypeOf(result[0].xml).toEqualTypeOf() + }) + + test('uuid - output', async () => { + const result = await prisma.$queryRawTyped(sql.getUuid(id)) + expect(result[0].uuid).toEqual(uuid) + expectTypeOf(result[0].uuid).toEqualTypeOf() + }) + + test('uuid - input', async () => { + const result = await prisma.$queryRawTyped(sql.findUuid(uuid)) + expect(result[0].id).toEqual(id) + }) + + test('bytes - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBytes(id)) + if (clientRuntime == 'node') { + // edge/wasm runtimes polyfill Buffer and so this assertion does not work + expect(result[0].bytes).toEqual(bytes) + } + expectTypeOf(result[0].bytes).toEqualTypeOf() + }) + + test('bytes - input', async () => { + const result = await prisma.$queryRawTyped(sql.findBytes([Buffer.from('hello'), Buffer.from('world')])) + expect(result[0].id).toEqual(id) + }) + + test('json - output', async () => { + const result = await prisma.$queryRawTyped(sql.getJson(id)) + expect(result[0].json).toEqual([{ hello: 'world' }, { goodbye: 'blue sky' }]) + + expectTypeOf(result[0].json).toEqualTypeOf() + }) + }, + { + optOut: { + from: ['sqlite', 'mysql', 'mongodb', 'cockroachdb', 'sqlserver'], + reason: 'Focusing on postgres only', + }, + }, +) diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/_matrix.ts b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/_matrix.ts new file mode 100644 index 000000000000..e0b8310fd2b4 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/_matrix.ts @@ -0,0 +1,4 @@ +import { defineMatrix } from '../../_utils/defineMatrix' +import { Providers } from '../../_utils/providers' + +export default defineMatrix(() => [[{ provider: Providers.POSTGRESQL }]]) diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/_schema.ts b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/_schema.ts new file mode 100644 index 000000000000..b186d480a393 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/_schema.ts @@ -0,0 +1,40 @@ +import { idForProvider } from '../../../_utils/idForProvider' +import testMatrix from '../_matrix' + +export default testMatrix.setupSchema(({ provider }) => { + return /* Prisma */ ` + generator client { + provider = "prisma-client-js" + previewFeatures = ["typedSql"] + } + + datasource db { + provider = "${provider}" + url = env("DATABASE_URI_${provider}") + } + + model TestModel { + id ${idForProvider(provider)} + string String? + xml String? @db.Xml + uuid String? @db.Uuid + int Int? + real Float? @db.Real + bytes Bytes? + json Json? + double Float? + bool Boolean? + bigInt BigInt? + decimal Decimal? + dateTime DateTime? + enum Enum? + date DateTime? @db.Date + time DateTime? @db.Time + } + + enum Enum { + ONE + TWO + } + ` +}) diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findBigInt.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findBigInt.sql new file mode 100644 index 000000000000..8902641be8f5 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findBigInt.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "bigInt" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findBool.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findBool.sql new file mode 100644 index 000000000000..aa1c5c458dc7 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findBool.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "bool" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findBytes.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findBytes.sql new file mode 100644 index 000000000000..f0fb4858bd21 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findBytes.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "bytes" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findDate.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findDate.sql new file mode 100644 index 000000000000..4de6171b5061 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findDate.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "date" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findDateTime.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findDateTime.sql new file mode 100644 index 000000000000..88f3f57f3858 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findDateTime.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "dateTime" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findDecimal.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findDecimal.sql new file mode 100644 index 000000000000..1ba90a847db3 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findDecimal.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "decimal" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findDouble.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findDouble.sql new file mode 100644 index 000000000000..7bd3a1ccb062 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findDouble.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "double" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findEnum.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findEnum.sql new file mode 100644 index 000000000000..ebe9142b8734 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findEnum.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "enum" = $1::"Enum" \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findInt.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findInt.sql new file mode 100644 index 000000000000..5daf2905198b --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findInt.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "int" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findJson.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findJson.sql new file mode 100644 index 000000000000..937e159f18ce --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findJson.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "json" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findReal.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findReal.sql new file mode 100644 index 000000000000..b90b37343029 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findReal.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "real" < $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findString.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findString.sql new file mode 100644 index 000000000000..948dbbc289c7 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findString.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "string" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findTime.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findTime.sql new file mode 100644 index 000000000000..d80813409fe6 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findTime.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "time" = $1::TIME \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findUuid.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findUuid.sql new file mode 100644 index 000000000000..be0101787fb3 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findUuid.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "uuid" = $1::UUID \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findXml.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findXml.sql new file mode 100644 index 000000000000..a6eedb27ce02 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/findXml.sql @@ -0,0 +1 @@ +SELECT xmlconcat("xml", $1::XML) as "concatResult" FROM "public"."TestModel" \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getBigInt.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getBigInt.sql new file mode 100644 index 000000000000..19db47e9c1d3 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getBigInt.sql @@ -0,0 +1 @@ +SELECT "bigInt" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getBool.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getBool.sql new file mode 100644 index 000000000000..14e93354bce5 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getBool.sql @@ -0,0 +1 @@ +SELECT "bool" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getBytes.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getBytes.sql new file mode 100644 index 000000000000..78116bfea43e --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getBytes.sql @@ -0,0 +1 @@ +SELECT "bytes" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getDate.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getDate.sql new file mode 100644 index 000000000000..e66db37fe356 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getDate.sql @@ -0,0 +1 @@ +SELECT "date" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getDateTime.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getDateTime.sql new file mode 100644 index 000000000000..1f9e147f1291 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getDateTime.sql @@ -0,0 +1 @@ +SELECT "dateTime" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getDecimal.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getDecimal.sql new file mode 100644 index 000000000000..0c537fb417ae --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getDecimal.sql @@ -0,0 +1 @@ +SELECT "decimal" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getDouble.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getDouble.sql new file mode 100644 index 000000000000..63cc69e77723 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getDouble.sql @@ -0,0 +1 @@ +SELECT "double" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getEnum.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getEnum.sql new file mode 100644 index 000000000000..e1cb6655955a --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getEnum.sql @@ -0,0 +1 @@ +SELECT "enum" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getInt.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getInt.sql new file mode 100644 index 000000000000..43bc3892f182 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getInt.sql @@ -0,0 +1 @@ +SELECT "int" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getJson.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getJson.sql new file mode 100644 index 000000000000..9d09882c6fce --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getJson.sql @@ -0,0 +1 @@ +SELECT "json" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getReal.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getReal.sql new file mode 100644 index 000000000000..936038a45046 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getReal.sql @@ -0,0 +1 @@ +SELECT "real" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getString.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getString.sql new file mode 100644 index 000000000000..268b8c3d5580 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getString.sql @@ -0,0 +1 @@ +SELECT "string" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getTime.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getTime.sql new file mode 100644 index 000000000000..88f94f4a3e6e --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getTime.sql @@ -0,0 +1 @@ +SELECT "time" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getUuid.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getUuid.sql new file mode 100644 index 000000000000..4642489be928 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getUuid.sql @@ -0,0 +1 @@ +SELECT "uuid" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getXml.sql b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getXml.sql new file mode 100644 index 000000000000..203811b923df --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/prisma/sql/getXml.sql @@ -0,0 +1 @@ +SELECT "xml" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/test.ts b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/test.ts new file mode 100644 index 000000000000..c056bcc7b563 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars-nullable/test.ts @@ -0,0 +1,217 @@ +import { faker } from '@faker-js/faker' +import { expectTypeOf } from 'expect-type' + +import testMatrix from './_matrix' +// @ts-ignore +import type { Prisma as PrismaNamespace, PrismaClient } from './node_modules/@prisma/client' +// @ts-ignore +import * as Sql from './node_modules/@prisma/client/sql' + +declare let prisma: PrismaClient +declare let Prisma: typeof PrismaNamespace +declare let sql: typeof Sql + +const id = '1234' +const bigInt = BigInt('12345') +const dateTime = new Date('2024-07-31T14:37:36.570Z') +const date = new Date('2024-07-31T00:00:00.000Z') +const time = new Date('1970-01-01T14:37:36.570Z') +const uuid = faker.string.uuid() +const bytes = Buffer.from('hello') +testMatrix.setupTestSuite( + ({ clientRuntime }) => { + beforeAll(async () => { + await prisma.testModel.create({ + data: { + id, + int: 123, + real: 12.3, + double: 12.3, + bool: true, + string: 'hello', + xml: '', + enum: 'ONE', + json: { hello: 'world' }, + uuid, + bigInt, + dateTime, + date, + time, + bytes, + decimal: new Prisma.Decimal('12.34'), + }, + }) + }) + test('int - output', async () => { + const result = await prisma.$queryRawTyped(sql.getInt(id)) + expect(result[0].int).toBe(123) + expectTypeOf(result[0].int).toEqualTypeOf() + }) + + test('int - input', async () => { + const result = await prisma.$queryRawTyped(sql.findInt(123)) + expect(result[0].id).toBe(id) + }) + + test('real - output', async () => { + const result = await prisma.$queryRawTyped(sql.getReal(id)) + expect(result[0].real).toBe(12.3) + expectTypeOf(result[0].real).toEqualTypeOf() + }) + + test('real - input', async () => { + const result = await prisma.$queryRawTyped(sql.findReal(13)) + expect(result[0].id).toBe(id) + }) + + test('double - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDouble(id)) + expect(result[0].double).toBe(12.3) + expectTypeOf(result[0].double).toEqualTypeOf() + }) + + test('double - input', async () => { + const result = await prisma.$queryRawTyped(sql.findDouble(12.3)) + expect(result[0].id).toBe(id) + }) + + test('string - output', async () => { + const result = await prisma.$queryRawTyped(sql.getString(id)) + expect(result[0].string).toEqual('hello') + expectTypeOf(result[0].string).toEqualTypeOf() + }) + + test('string - input', async () => { + const result = await prisma.$queryRawTyped(sql.findString('hello')) + expect(result[0].id).toEqual(id) + }) + + test('enum - output', async () => { + const result = await prisma.$queryRawTyped(sql.getEnum(id)) + expect(result[0].enum).toEqual('ONE') + expectTypeOf(result[0].enum).toEqualTypeOf<'ONE' | 'TWO' | null>() + // rule does not function correctly until test is run + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + expectTypeOf(result[0].enum).toEqualTypeOf() + }) + + test('enum - input', async () => { + const result = await prisma.$queryRawTyped(sql.findEnum('ONE')) + expect(result[0].id).toEqual(id) + }) + + test('BigInt - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBigInt(id)) + expect(result[0].bigInt).toEqual(bigInt) + expectTypeOf(result[0].bigInt).toEqualTypeOf() + }) + + test('BigInt - input', async () => { + const numberResult = await prisma.$queryRawTyped(sql.findBigInt(12345)) + expect(numberResult[0].id).toEqual(id) + + const bigintResult = await prisma.$queryRawTyped(sql.findBigInt(bigInt)) + expect(bigintResult[0].id).toEqual(id) + }) + + test('DateTime - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDateTime(id)) + expect(result[0].dateTime).toEqual(dateTime) + expectTypeOf(result[0].dateTime).toEqualTypeOf() + }) + + test('DateTime - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findDateTime(dateTime)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Date - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDate(id)) + expect(result[0].date).toEqual(date) + expectTypeOf(result[0].date).toEqualTypeOf() + }) + + test('Date - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findDate(date)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Time - output', async () => { + const result = await prisma.$queryRawTyped(sql.getTime(id)) + expect(result[0].time).toEqual(time) + expectTypeOf(result[0].time).toEqualTypeOf() + }) + + test('Time - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findTime(time)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Decimal - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDecimal(id)) + expect(result[0].decimal).toBeInstanceOf(Prisma.Decimal) + expect(result[0].decimal).toEqual(new Prisma.Decimal('12.34')) + + // rule does not function correctly until test is run + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + expectTypeOf(result[0].decimal).toEqualTypeOf() + }) + + test('Decimal - input', async () => { + const resultDecimal = await prisma.$queryRawTyped(sql.findDecimal(new Prisma.Decimal('12.34'))) + expect(resultDecimal[0].id).toBe(id) + + const resultNumber = await prisma.$queryRawTyped(sql.findDecimal(12.34)) + expect(resultNumber[0].id).toBe(id) + }) + + test('xml - output', async () => { + const result = await prisma.$queryRawTyped(sql.getXml(id)) + expect(result[0].xml).toEqual('') + expectTypeOf(result[0].xml).toEqualTypeOf() + }) + + test('xml - input', async () => { + const result = await prisma.$queryRawTyped(sql.findXml('')) + expect(result[0].concatResult).toEqual('') + }) + + test('uuid - output', async () => { + const result = await prisma.$queryRawTyped(sql.getUuid(id)) + expect(result[0].uuid).toEqual(uuid) + expectTypeOf(result[0].uuid).toEqualTypeOf() + }) + + test('uuid - input', async () => { + const result = await prisma.$queryRawTyped(sql.findUuid(uuid)) + expect(result[0].id).toEqual(id) + }) + + test('bytes - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBytes(id)) + if (clientRuntime == 'node') { + // edge/wasm runtimes polyfill Buffer and so this assertion does not work + expect(result[0].bytes).toEqual(Buffer.from('hello')) + } + expectTypeOf(result[0].bytes).toEqualTypeOf() + }) + + test('bytes - input', async () => { + const result = await prisma.$queryRawTyped(sql.findBytes(bytes)) + expect(result[0].id).toEqual(id) + }) + + test('json - output', async () => { + const result = await prisma.$queryRawTyped(sql.getJson(id)) + expect(result[0].json).toEqual({ hello: 'world' }) + + expectTypeOf(result[0].json).toEqualTypeOf() + }) + }, + { + optOut: { + from: ['sqlite', 'mysql', 'mongodb', 'cockroachdb', 'sqlserver'], + reason: 'Focusing on postgres only', + }, + }, +) diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/_matrix.ts b/packages/client/tests/functional/typed-sql/postgres-scalars/_matrix.ts new file mode 100644 index 000000000000..e0b8310fd2b4 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/_matrix.ts @@ -0,0 +1,4 @@ +import { defineMatrix } from '../../_utils/defineMatrix' +import { Providers } from '../../_utils/providers' + +export default defineMatrix(() => [[{ provider: Providers.POSTGRESQL }]]) diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/_schema.ts b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/_schema.ts new file mode 100644 index 000000000000..3c22ad546f7f --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/_schema.ts @@ -0,0 +1,40 @@ +import { idForProvider } from '../../../_utils/idForProvider' +import testMatrix from '../_matrix' + +export default testMatrix.setupSchema(({ provider }) => { + return /* Prisma */ ` + generator client { + provider = "prisma-client-js" + previewFeatures = ["typedSql"] + } + + datasource db { + provider = "${provider}" + url = env("DATABASE_URI_${provider}") + } + + model TestModel { + id ${idForProvider(provider)} + string String + xml String @db.Xml + uuid String @db.Uuid + int Int + real Float @db.Real + bytes Bytes + json Json + double Float + bool Boolean + bigInt BigInt + decimal Decimal + dateTime DateTime + enum Enum + date DateTime @db.Date + time DateTime @db.Time + } + + enum Enum { + ONE + TWO + } + ` +}) diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findBigInt.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findBigInt.sql new file mode 100644 index 000000000000..8902641be8f5 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findBigInt.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "bigInt" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findBool.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findBool.sql new file mode 100644 index 000000000000..aa1c5c458dc7 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findBool.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "bool" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findBytes.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findBytes.sql new file mode 100644 index 000000000000..f0fb4858bd21 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findBytes.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "bytes" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findDate.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findDate.sql new file mode 100644 index 000000000000..4de6171b5061 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findDate.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "date" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findDateTime.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findDateTime.sql new file mode 100644 index 000000000000..88f3f57f3858 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findDateTime.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "dateTime" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findDecimal.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findDecimal.sql new file mode 100644 index 000000000000..1ba90a847db3 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findDecimal.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "decimal" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findDouble.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findDouble.sql new file mode 100644 index 000000000000..7bd3a1ccb062 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findDouble.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "double" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findEnum.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findEnum.sql new file mode 100644 index 000000000000..ebe9142b8734 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findEnum.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "enum" = $1::"Enum" \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findInt.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findInt.sql new file mode 100644 index 000000000000..5daf2905198b --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findInt.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "int" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findJson.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findJson.sql new file mode 100644 index 000000000000..937e159f18ce --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findJson.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "json" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findReal.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findReal.sql new file mode 100644 index 000000000000..b90b37343029 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findReal.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "real" < $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findString.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findString.sql new file mode 100644 index 000000000000..948dbbc289c7 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findString.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "string" = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findTime.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findTime.sql new file mode 100644 index 000000000000..d80813409fe6 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findTime.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "time" = $1::TIME \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findUuid.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findUuid.sql new file mode 100644 index 000000000000..be0101787fb3 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findUuid.sql @@ -0,0 +1 @@ +SELECT "id" FROM "public"."TestModel" WHERE "uuid" = $1::UUID \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findXml.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findXml.sql new file mode 100644 index 000000000000..a6eedb27ce02 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/findXml.sql @@ -0,0 +1 @@ +SELECT xmlconcat("xml", $1::XML) as "concatResult" FROM "public"."TestModel" \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getBigInt.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getBigInt.sql new file mode 100644 index 000000000000..19db47e9c1d3 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getBigInt.sql @@ -0,0 +1 @@ +SELECT "bigInt" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getBool.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getBool.sql new file mode 100644 index 000000000000..14e93354bce5 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getBool.sql @@ -0,0 +1 @@ +SELECT "bool" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getBytes.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getBytes.sql new file mode 100644 index 000000000000..78116bfea43e --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getBytes.sql @@ -0,0 +1 @@ +SELECT "bytes" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getDate.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getDate.sql new file mode 100644 index 000000000000..e66db37fe356 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getDate.sql @@ -0,0 +1 @@ +SELECT "date" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getDateTime.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getDateTime.sql new file mode 100644 index 000000000000..1f9e147f1291 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getDateTime.sql @@ -0,0 +1 @@ +SELECT "dateTime" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getDecimal.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getDecimal.sql new file mode 100644 index 000000000000..0c537fb417ae --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getDecimal.sql @@ -0,0 +1 @@ +SELECT "decimal" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getDouble.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getDouble.sql new file mode 100644 index 000000000000..63cc69e77723 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getDouble.sql @@ -0,0 +1 @@ +SELECT "double" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getEnum.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getEnum.sql new file mode 100644 index 000000000000..e1cb6655955a --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getEnum.sql @@ -0,0 +1 @@ +SELECT "enum" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getInt.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getInt.sql new file mode 100644 index 000000000000..43bc3892f182 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getInt.sql @@ -0,0 +1 @@ +SELECT "int" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getJson.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getJson.sql new file mode 100644 index 000000000000..9d09882c6fce --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getJson.sql @@ -0,0 +1 @@ +SELECT "json" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getReal.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getReal.sql new file mode 100644 index 000000000000..936038a45046 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getReal.sql @@ -0,0 +1 @@ +SELECT "real" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getString.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getString.sql new file mode 100644 index 000000000000..268b8c3d5580 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getString.sql @@ -0,0 +1 @@ +SELECT "string" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getTime.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getTime.sql new file mode 100644 index 000000000000..88f94f4a3e6e --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getTime.sql @@ -0,0 +1 @@ +SELECT "time" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getUuid.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getUuid.sql new file mode 100644 index 000000000000..4642489be928 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getUuid.sql @@ -0,0 +1 @@ +SELECT "uuid" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getXml.sql b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getXml.sql new file mode 100644 index 000000000000..203811b923df --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/prisma/sql/getXml.sql @@ -0,0 +1 @@ +SELECT "xml" FROM "public"."TestModel" WHERE id = $1 \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/postgres-scalars/test.ts b/packages/client/tests/functional/typed-sql/postgres-scalars/test.ts new file mode 100644 index 000000000000..10007d069485 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/postgres-scalars/test.ts @@ -0,0 +1,211 @@ +import { faker } from '@faker-js/faker' +// @ts-ignore +import * as Sql from '@prisma/client/sql' +import { expectTypeOf } from 'expect-type' + +import testMatrix from './_matrix' +// @ts-ignore +import type { Prisma as PrismaNamespace, PrismaClient } from './node_modules/@prisma/client' + +declare let prisma: PrismaClient +declare let Prisma: typeof PrismaNamespace +declare let sql: typeof Sql + +const id = '1234' +const bigInt = BigInt('12345') +const dateTime = new Date('2024-07-31T14:37:36.570Z') +const date = new Date('2024-07-31T00:00:00.000Z') +const time = new Date('1970-01-01T14:37:36.570Z') +const uuid = faker.string.uuid() +testMatrix.setupTestSuite( + ({ clientRuntime }) => { + beforeAll(async () => { + await prisma.testModel.create({ + data: { + id, + int: 123, + real: 12.3, + double: 12.3, + bool: true, + string: 'hello', + xml: '', + enum: 'ONE', + json: { hello: 'world' }, + uuid, + bigInt, + dateTime, + date, + time, + bytes: Buffer.from('hello'), + decimal: new Prisma.Decimal('12.34'), + }, + }) + }) + test('int - output', async () => { + const result = await prisma.$queryRawTyped(sql.getInt(id)) + expect(result[0].int).toBe(123) + expectTypeOf(result[0].int).toBeNumber() + }) + + test('int - input', async () => { + const result = await prisma.$queryRawTyped(sql.findInt(123)) + expect(result[0].id).toBe(id) + }) + + test('real - output', async () => { + const result = await prisma.$queryRawTyped(sql.getReal(id)) + expect(result[0].real).toBe(12.3) + expectTypeOf(result[0].real).toBeNumber() + }) + + test('real - input', async () => { + const result = await prisma.$queryRawTyped(sql.findReal(13)) + expect(result[0].id).toBe(id) + }) + + test('double - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDouble(id)) + expect(result[0].double).toBe(12.3) + expectTypeOf(result[0].double).toBeNumber() + }) + + test('double - input', async () => { + const result = await prisma.$queryRawTyped(sql.findDouble(12.3)) + expect(result[0].id).toBe(id) + }) + + test('string - output', async () => { + const result = await prisma.$queryRawTyped(sql.getString(id)) + expect(result[0].string).toEqual('hello') + expectTypeOf(result[0].string).toBeString() + }) + + test('string - input', async () => { + const result = await prisma.$queryRawTyped(sql.findString('hello')) + expect(result[0].id).toEqual(id) + }) + + test('enum - output', async () => { + const result = await prisma.$queryRawTyped(sql.getEnum(id)) + expect(result[0].enum).toEqual('ONE') + expectTypeOf(result[0].enum).toEqualTypeOf<'ONE' | 'TWO'>() + expectTypeOf(result[0].enum).toEqualTypeOf() + }) + + test('enum - input', async () => { + const result = await prisma.$queryRawTyped(sql.findEnum('ONE')) + expect(result[0].id).toEqual(id) + }) + + test('BigInt - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBigInt(id)) + expect(result[0].bigInt).toEqual(bigInt) + expectTypeOf(result[0].bigInt).toEqualTypeOf() + }) + + test('BigInt - input', async () => { + const numberResult = await prisma.$queryRawTyped(sql.findBigInt(12345)) + expect(numberResult[0].id).toEqual(id) + + const bigintResult = await prisma.$queryRawTyped(sql.findBigInt(bigInt)) + expect(bigintResult[0].id).toEqual(id) + }) + + test('DateTime - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDateTime(id)) + expect(result[0].dateTime).toEqual(dateTime) + expectTypeOf(result[0].dateTime).toEqualTypeOf() + }) + + test('DateTime - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findDateTime(dateTime)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Date - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDate(id)) + expect(result[0].date).toEqual(date) + expectTypeOf(result[0].date).toEqualTypeOf() + }) + + test('Date - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findDate(date)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Time - output', async () => { + const result = await prisma.$queryRawTyped(sql.getTime(id)) + expect(result[0].time).toEqual(time) + expectTypeOf(result[0].time).toEqualTypeOf() + }) + + test('Time - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findTime(time)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Decimal - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDecimal(id)) + expect(result[0].decimal).toBeInstanceOf(Prisma.Decimal) + expect(result[0].decimal).toEqual(new Prisma.Decimal('12.34')) + expectTypeOf(result[0].decimal).toEqualTypeOf() + }) + + test('Decimal - input', async () => { + const resultDecimal = await prisma.$queryRawTyped(sql.findDecimal(new Prisma.Decimal('12.34'))) + expect(resultDecimal[0].id).toBe(id) + + const resultNumber = await prisma.$queryRawTyped(sql.findDecimal(12.34)) + expect(resultNumber[0].id).toBe(id) + }) + + test('xml - output', async () => { + const result = await prisma.$queryRawTyped(sql.getXml(id)) + expect(result[0].xml).toEqual('') + expectTypeOf(result[0].xml).toBeString() + }) + + test('xml - input', async () => { + const result = await prisma.$queryRawTyped(sql.findXml('')) + expect(result[0].concatResult).toEqual('') + }) + + test('uuid - output', async () => { + const result = await prisma.$queryRawTyped(sql.getUuid(id)) + expect(result[0].uuid).toEqual(uuid) + expectTypeOf(result[0].uuid).toBeString() + }) + + test('uuid - input', async () => { + const result = await prisma.$queryRawTyped(sql.findUuid(uuid)) + expect(result[0].id).toEqual(id) + }) + + test('bytes - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBytes(id)) + if (clientRuntime == 'node') { + // edge/wasm runtimes polyfill Buffer and so this assertion does not work + expect(result[0].bytes).toEqual(Buffer.from('hello')) + } + expectTypeOf(result[0].bytes).toEqualTypeOf() + }) + + test('bytes - input', async () => { + const result = await prisma.$queryRawTyped(sql.findBytes(Buffer.from('hello'))) + expect(result[0].id).toEqual(id) + }) + + test('json - output', async () => { + const result = await prisma.$queryRawTyped(sql.getJson(id)) + expect(result[0].json).toEqual({ hello: 'world' }) + + expectTypeOf(result[0].json).toEqualTypeOf() + }) + }, + { + optOut: { + from: ['sqlite', 'mysql', 'mongodb', 'cockroachdb', 'sqlserver'], + reason: 'Focusing on postgres only', + }, + }, +) diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/_matrix.ts b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/_matrix.ts new file mode 100644 index 000000000000..c87757d60fb9 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/_matrix.ts @@ -0,0 +1,4 @@ +import { defineMatrix } from '../../_utils/defineMatrix' +import { Providers } from '../../_utils/providers' + +export default defineMatrix(() => [[{ provider: Providers.SQLITE }]]) diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/_schema.ts b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/_schema.ts new file mode 100644 index 000000000000..61f73bc8b355 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/_schema.ts @@ -0,0 +1,28 @@ +import { idForProvider } from '../../../_utils/idForProvider' +import testMatrix from '../_matrix' + +export default testMatrix.setupSchema(({ provider }) => { + return /* Prisma */ ` + generator client { + provider = "prisma-client-js" + previewFeatures = ["typedSql"] + } + + datasource db { + provider = "${provider}" + url = env("DATABASE_URI_${provider}") + } + + model TestModel { + id ${idForProvider(provider)} + string String? + int Int? + bytes Bytes? + double Float? + bool Boolean? + bigInt BigInt? + decimal Decimal? + dateTime DateTime? + } + ` +}) diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findBigInt.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findBigInt.sql new file mode 100644 index 000000000000..e6310bf31fb5 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findBigInt.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `bigInt` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findBytes.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findBytes.sql new file mode 100644 index 000000000000..bed1d50b6c0a --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findBytes.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `bytes` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findDateTime.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findDateTime.sql new file mode 100644 index 000000000000..71fa6bb79a41 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findDateTime.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `dateTime` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findDecimal.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findDecimal.sql new file mode 100644 index 000000000000..9ccc597b35de --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findDecimal.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `decimal` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findDouble.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findDouble.sql new file mode 100644 index 000000000000..1d122c45504c --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findDouble.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `double` < ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findInt.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findInt.sql new file mode 100644 index 000000000000..234afde1970e --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findInt.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `int` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findString.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findString.sql new file mode 100644 index 000000000000..03cfa84ca7cc --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/findString.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `string` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getBigInt.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getBigInt.sql new file mode 100644 index 000000000000..4fd404d88408 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getBigInt.sql @@ -0,0 +1 @@ +SELECT `bigInt` FROM `TestModel` WHERE `id` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getBytes.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getBytes.sql new file mode 100644 index 000000000000..19c70851bf7a --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getBytes.sql @@ -0,0 +1 @@ +SELECT `bytes` FROM `TestModel` WHERE `id` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getDateTime.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getDateTime.sql new file mode 100644 index 000000000000..c7a1caf792ce --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getDateTime.sql @@ -0,0 +1 @@ +SELECT `dateTime` FROM `TestModel` WHERE `id` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getDecimal.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getDecimal.sql new file mode 100644 index 000000000000..efe6285afbd3 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getDecimal.sql @@ -0,0 +1 @@ +SELECT `decimal` FROM `TestModel` WHERE `id` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getDouble.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getDouble.sql new file mode 100644 index 000000000000..85db5de9b574 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getDouble.sql @@ -0,0 +1 @@ +SELECT `double` FROM `TestModel` WHERE `id` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getInt.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getInt.sql new file mode 100644 index 000000000000..4892aad692fb --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getInt.sql @@ -0,0 +1 @@ +SELECT `int` FROM `TestModel` WHERE `id` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getString.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getString.sql new file mode 100644 index 000000000000..a1016c71d3a5 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/getString.sql @@ -0,0 +1 @@ +SELECT `string` FROM `TestModel` WHERE `id` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/nullableColumn.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/nullableColumn.sql new file mode 100644 index 000000000000..2f07303bc554 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/nullableColumn.sql @@ -0,0 +1 @@ +SELECT 1 AS `value?`; \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/nullableParam.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/nullableParam.sql new file mode 100644 index 000000000000..d31536ea6df4 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/prisma/sql/nullableParam.sql @@ -0,0 +1,2 @@ +-- @param {Int} $1:alias? +SELECT COALESCE($1, 0) as `value`; \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/test.ts b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/test.ts new file mode 100644 index 000000000000..147acb1dec66 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars-nullable/test.ts @@ -0,0 +1,146 @@ +// @ts-ignore +import type { Prisma as PrismaNamespace, PrismaClient } from '@prisma/client' +// @ts-ignore +import * as Sql from '@prisma/client/sql' +import { expectTypeOf } from 'expect-type' + +import testMatrix from './_matrix' + +declare let prisma: PrismaClient +declare let Prisma: typeof PrismaNamespace +declare let sql: typeof Sql + +const id = '1234' +const bigInt = BigInt('12345') +const dateTime = new Date('2024-07-31T14:37:36.570Z') +const bytes = Buffer.from('hello') +testMatrix.setupTestSuite( + ({ clientRuntime }) => { + beforeAll(async () => { + await prisma.testModel.create({ + data: { + id, + int: 123, + double: 12.3, + bool: true, + string: 'hello', + bigInt, + dateTime, + bytes, + decimal: new Prisma.Decimal('12.34'), + }, + }) + }) + test('int - output', async () => { + const result = await prisma.$queryRawTyped(sql.getInt(id)) + expect(result[0].int).toBe(123) + expectTypeOf(result[0].int).toEqualTypeOf() + }) + + test('int - input', async () => { + const result = await prisma.$queryRawTyped(sql.findInt(123)) + expect(result[0].id).toBe(id) + }) + + test('double - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDouble(id)) + expect(result[0].double).toBe(12.3) + expectTypeOf(result[0].double).toEqualTypeOf() + }) + + test('double - input', async () => { + const result = await prisma.$queryRawTyped(sql.findDouble(13.1)) + expect(result[0].id).toBe(id) + }) + + test('string - output', async () => { + const result = await prisma.$queryRawTyped(sql.getString(id)) + expect(result[0].string).toEqual('hello') + expectTypeOf(result[0].string).toEqualTypeOf() + }) + + test('string - input', async () => { + const result = await prisma.$queryRawTyped(sql.findString('hello')) + expect(result[0].id).toEqual(id) + }) + + test('BigInt - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBigInt(id)) + expect(result[0].bigInt).toEqual(bigInt) + expectTypeOf(result[0].bigInt).toEqualTypeOf() + }) + + test('BigInt - input', async () => { + const numberResult = await prisma.$queryRawTyped(sql.findBigInt(12345)) + expect(numberResult[0].id).toEqual(id) + + const bigintResult = await prisma.$queryRawTyped(sql.findBigInt(bigInt)) + expect(bigintResult[0].id).toEqual(id) + }) + + test('DateTime - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDateTime(id)) + expect(result[0].dateTime).toEqual(dateTime) + expectTypeOf(result[0].dateTime).toEqualTypeOf() + }) + + test('DateTime - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findDateTime(dateTime)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Decimal - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDecimal(id)) + expect(result[0].decimal).toBeInstanceOf(Prisma.Decimal) + expect(result[0].decimal).toEqual(new Prisma.Decimal('12.34')) + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + expectTypeOf(result[0].decimal).toEqualTypeOf() + }) + + test('Decimal - input', async () => { + const resultDecimal = await prisma.$queryRawTyped(sql.findDecimal(new Prisma.Decimal('12.34'))) + expect(resultDecimal[0].id).toBe(id) + + const resultNumber = await prisma.$queryRawTyped(sql.findDecimal(12.34)) + expect(resultNumber[0].id).toBe(id) + }) + + test('bytes - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBytes(id)) + if (clientRuntime === 'node') { + expect(result[0].bytes).toEqual(bytes) + } + expectTypeOf(result[0].bytes).toEqualTypeOf() + }) + + test('bytes - input', async () => { + const result = await prisma.$queryRawTyped(sql.findBytes(bytes)) + expect(result[0].id).toEqual(id) + }) + + test('forced nullable param', async () => { + const result = await prisma.$queryRawTyped(sql.nullableParam(null)) + + expect(result[0].value).toEqual(0n) + expectTypeOf(result[0].value).toEqualTypeOf() + expectTypeOf(sql.nullableParam).parameters.toEqualTypeOf<[number | null]>() + }) + + test('forced nullable column', async () => { + const result = await prisma.$queryRawTyped(sql.nullableColumn()) + + expect(result[0]['value?']).toEqual(1n) + expectTypeOf(result[0]['value?']).toEqualTypeOf() + }) + }, + { + optOut: { + from: ['postgresql', 'mongodb', 'cockroachdb', 'sqlserver', 'mysql'], + reason: 'Focusing on sqlite only', + }, + skipDriverAdapter: { + from: ['js_d1'], + reason: '--sql does not work on D1', + }, + }, +) diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/_matrix.ts b/packages/client/tests/functional/typed-sql/sqlite-scalars/_matrix.ts new file mode 100644 index 000000000000..c87757d60fb9 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/_matrix.ts @@ -0,0 +1,4 @@ +import { defineMatrix } from '../../_utils/defineMatrix' +import { Providers } from '../../_utils/providers' + +export default defineMatrix(() => [[{ provider: Providers.SQLITE }]]) diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/_schema.ts b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/_schema.ts new file mode 100644 index 000000000000..8cfdd631a9fd --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/_schema.ts @@ -0,0 +1,28 @@ +import { idForProvider } from '../../../_utils/idForProvider' +import testMatrix from '../_matrix' + +export default testMatrix.setupSchema(({ provider }) => { + return /* Prisma */ ` + generator client { + provider = "prisma-client-js" + previewFeatures = ["typedSql"] + } + + datasource db { + provider = "${provider}" + url = env("DATABASE_URI_${provider}") + } + + model TestModel { + id ${idForProvider(provider)} + string String + int Int + bytes Bytes + double Float + bool Boolean + bigInt BigInt + decimal Decimal + dateTime DateTime + } + ` +}) diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findBigInt.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findBigInt.sql new file mode 100644 index 000000000000..e6310bf31fb5 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findBigInt.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `bigInt` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findBytes.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findBytes.sql new file mode 100644 index 000000000000..bed1d50b6c0a --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findBytes.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `bytes` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findDateTime.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findDateTime.sql new file mode 100644 index 000000000000..71fa6bb79a41 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findDateTime.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `dateTime` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findDecimal.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findDecimal.sql new file mode 100644 index 000000000000..9ccc597b35de --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findDecimal.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `decimal` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findDouble.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findDouble.sql new file mode 100644 index 000000000000..1d122c45504c --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findDouble.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `double` < ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findInt.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findInt.sql new file mode 100644 index 000000000000..234afde1970e --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findInt.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `int` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findString.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findString.sql new file mode 100644 index 000000000000..03cfa84ca7cc --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/findString.sql @@ -0,0 +1 @@ +SELECT `id` FROM `TestModel` WHERE `string` = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getBigInt.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getBigInt.sql new file mode 100644 index 000000000000..85dce8622e46 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getBigInt.sql @@ -0,0 +1 @@ +SELECT `bigInt` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getBytes.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getBytes.sql new file mode 100644 index 000000000000..63c1ac206c8b --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getBytes.sql @@ -0,0 +1 @@ +SELECT `bytes` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getDateTime.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getDateTime.sql new file mode 100644 index 000000000000..e1fbd06c02fb --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getDateTime.sql @@ -0,0 +1 @@ +SELECT `dateTime` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getDecimal.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getDecimal.sql new file mode 100644 index 000000000000..f3c693dbd894 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getDecimal.sql @@ -0,0 +1 @@ +SELECT `decimal` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getDouble.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getDouble.sql new file mode 100644 index 000000000000..412f0b72407b --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getDouble.sql @@ -0,0 +1 @@ +SELECT `double` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getInt.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getInt.sql new file mode 100644 index 000000000000..e83c7ce4d06b --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getInt.sql @@ -0,0 +1 @@ +SELECT `int` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getString.sql b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getString.sql new file mode 100644 index 000000000000..e1f37d25fba3 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/prisma/sql/getString.sql @@ -0,0 +1 @@ +SELECT `string` FROM `TestModel` WHERE id = ? \ No newline at end of file diff --git a/packages/client/tests/functional/typed-sql/sqlite-scalars/test.ts b/packages/client/tests/functional/typed-sql/sqlite-scalars/test.ts new file mode 100644 index 000000000000..503b843c8a80 --- /dev/null +++ b/packages/client/tests/functional/typed-sql/sqlite-scalars/test.ts @@ -0,0 +1,130 @@ +// @ts-ignore +import type { Prisma as PrismaNamespace, PrismaClient } from '@prisma/client' +// @ts-ignore +import * as Sql from '@prisma/client/sql' +import { expectTypeOf } from 'expect-type' + +import testMatrix from './_matrix' + +declare let prisma: PrismaClient +declare let Prisma: typeof PrismaNamespace +declare let sql: typeof Sql + +const id = '1234' +const bigInt = BigInt('12345') +const dateTime = new Date('2024-07-31T14:37:36.570Z') +const bytes = Buffer.from('hello') +testMatrix.setupTestSuite( + ({ clientRuntime }) => { + beforeAll(async () => { + await prisma.testModel.create({ + data: { + id, + int: 123, + double: 12.3, + bool: true, + string: 'hello', + bigInt, + dateTime, + bytes, + decimal: new Prisma.Decimal('12.34'), + }, + }) + }) + test('int - output', async () => { + const result = await prisma.$queryRawTyped(sql.getInt(id)) + expect(result[0].int).toBe(123) + expectTypeOf(result[0].int).toBeNumber() + }) + + test('int - input', async () => { + const result = await prisma.$queryRawTyped(sql.findInt(123)) + expect(result[0].id).toBe(id) + }) + + test('double - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDouble(id)) + expect(result[0].double).toBe(12.3) + expectTypeOf(result[0].double).toBeNumber() + }) + + test('double - input', async () => { + const result = await prisma.$queryRawTyped(sql.findDouble(13.1)) + expect(result[0].id).toBe(id) + }) + + test('string - output', async () => { + const result = await prisma.$queryRawTyped(sql.getString(id)) + expect(result[0].string).toEqual('hello') + expectTypeOf(result[0].string).toBeString() + }) + + test('string - input', async () => { + const result = await prisma.$queryRawTyped(sql.findString('hello')) + expect(result[0].id).toEqual(id) + }) + + test('BigInt - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBigInt(id)) + expect(result[0].bigInt).toEqual(bigInt) + expectTypeOf(result[0].bigInt).toEqualTypeOf() + }) + + test('BigInt - input', async () => { + const numberResult = await prisma.$queryRawTyped(sql.findBigInt(12345)) + expect(numberResult[0].id).toEqual(id) + + const bigintResult = await prisma.$queryRawTyped(sql.findBigInt(bigInt)) + expect(bigintResult[0].id).toEqual(id) + }) + + test('DateTime - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDateTime(id)) + expect(result[0].dateTime).toEqual(dateTime) + expectTypeOf(result[0].dateTime).toEqualTypeOf() + }) + + test('DateTime - input', async () => { + const resultDate = await prisma.$queryRawTyped(sql.findDateTime(dateTime)) + expect(resultDate[0].id).toEqual(id) + }) + + test('Decimal - output', async () => { + const result = await prisma.$queryRawTyped(sql.getDecimal(id)) + expect(result[0].decimal).toBeInstanceOf(Prisma.Decimal) + expect(result[0].decimal).toEqual(new Prisma.Decimal('12.34')) + expectTypeOf(result[0].decimal).toEqualTypeOf() + }) + + test('Decimal - input', async () => { + const resultDecimal = await prisma.$queryRawTyped(sql.findDecimal(new Prisma.Decimal('12.34'))) + expect(resultDecimal[0].id).toBe(id) + + const resultNumber = await prisma.$queryRawTyped(sql.findDecimal(12.34)) + expect(resultNumber[0].id).toBe(id) + }) + + test('bytes - output', async () => { + const result = await prisma.$queryRawTyped(sql.getBytes(id)) + if (clientRuntime === 'node') { + expect(result[0].bytes).toEqual(bytes) + } + expectTypeOf(result[0].bytes).toEqualTypeOf() + }) + + test('bytes - input', async () => { + const result = await prisma.$queryRawTyped(sql.findBytes(bytes)) + expect(result[0].id).toEqual(id) + }) + }, + { + optOut: { + from: ['postgresql', 'mongodb', 'cockroachdb', 'sqlserver', 'mysql'], + reason: 'Focusing on sqlite only', + }, + skipDriverAdapter: { + from: ['js_d1'], + reason: '--sql does not work on D1', + }, + }, +) diff --git a/packages/generator-helper/src/types.ts b/packages/generator-helper/src/types.ts index e9c4836c401c..a97327141548 100644 --- a/packages/generator-helper/src/types.ts +++ b/packages/generator-helper/src/types.ts @@ -114,6 +114,7 @@ export type GeneratorOptions = { noHints?: boolean allowNoModels?: boolean envPaths?: EnvPaths + typedSql?: SqlQueryOutput[] } export type EngineType = 'queryEngine' | 'libqueryEngine' | 'schemaEngine' @@ -130,3 +131,65 @@ export type GeneratorManifest = { version?: string requiresEngineVersion?: string } + +export type SqlQueryOutput = { + name: string + source: string + documentation: string | null + parameters: SqlQueryParameterOutput[] + resultColumns: SqlQueryColumnOutput[] +} + +export type SqlQueryParameterOutput = { + name: string + query: string + typ: QueryIntrospectionType + documentation: string | null + nullable: boolean +} + +export type SqlQueryColumnOutput = { + name: string + typ: QueryIntrospectionType + nullable: boolean +} + +// can refer to user-defined enums, so does not map to QueryIntrospectionType 1:1 +export type QueryIntrospectionType = QueryIntrospectionBuiltinType | (string & {}) + +// This must remain in sync with the `quaint::ColumnType` enum in the QueryEngine. +// ./quaint/src/connector/column_type.rs +export type QueryIntrospectionBuiltinType = + | 'int' + | 'bigint' + | 'float' + | 'double' + | 'string' + | 'enum' + | 'bytes' + | 'bool' + | 'char' + | 'decimal' + | 'json' + | 'xml' + | 'uuid' + | 'datetime' + | 'date' + | 'time' + | 'int-array' + | 'bigint-array' + | 'float-array' + | 'double-array' + | 'string-array' + | 'char-array' + | 'bytes-array' + | 'bool-array' + | 'decimal-array' + | 'json-array' + | 'xml-array' + | 'uuid-array' + | 'datetime-array' + | 'date-array' + | 'time-array' + | 'null' + | 'unknown' diff --git a/packages/internals/package.json b/packages/internals/package.json index d8a237a3024f..8da57da90603 100644 --- a/packages/internals/package.json +++ b/packages/internals/package.json @@ -28,9 +28,11 @@ ], "devDependencies": { "@antfu/ni": "0.21.12", + "@babel/helper-validator-identifier": "7.24.7", "@opentelemetry/api": "1.9.0", "@swc/core": "1.2.204", "@swc/jest": "0.2.36", + "@types/babel__helper-validator-identifier": "7.15.2", "@types/jest": "29.5.12", "@types/node": "18.19.31", "@types/resolve": "1.20.6", @@ -48,6 +50,7 @@ "fs-jetpack": "5.1.0", "global-dirs": "4.0.0", "globby": "11.1.0", + "identifier-regex": "1.0.0", "indent-string": "4.0.0", "is-windows": "1.0.2", "is-wsl": "3.1.0", diff --git a/packages/internals/src/get-generators/getGenerators.ts b/packages/internals/src/get-generators/getGenerators.ts index c9d51d257a19..43dd182313d4 100644 --- a/packages/internals/src/get-generators/getGenerators.ts +++ b/packages/internals/src/get-generators/getGenerators.ts @@ -2,7 +2,13 @@ import Debug from '@prisma/debug' import { enginesVersion, getCliQueryEngineBinaryType } from '@prisma/engines' import type { DownloadOptions } from '@prisma/fetch-engine' import { download } from '@prisma/fetch-engine' -import type { BinaryTargetsEnvValue, EngineType, GeneratorConfig, GeneratorOptions } from '@prisma/generator-helper' +import type { + BinaryTargetsEnvValue, + EngineType, + GeneratorConfig, + GeneratorOptions, + SqlQueryOutput, +} from '@prisma/generator-helper' import type { BinaryTarget } from '@prisma/get-platform' import { binaryTargets, getBinaryTargetForCurrentPlatform } from '@prisma/get-platform' import { bold, gray, green, red, underline, yellow } from 'kleur/colors' @@ -61,6 +67,7 @@ export type GetGeneratorOptions = { postinstall?: boolean noEngine?: boolean allowNoModels?: boolean + typedSql?: SqlQueryOutput[] } /** * Makes sure that all generators have the binaries they deserve and returns a @@ -83,6 +90,7 @@ export async function getGenerators(options: GetGeneratorOptions): Promise { + return this.runCommand(this.getRPCPayload('introspectSql', args)) + } + public stop(): void { if (this.child) { this.child.kill() diff --git a/packages/migrate/src/index.ts b/packages/migrate/src/index.ts index 2ec17f181535..6ac3110eb05c 100644 --- a/packages/migrate/src/index.ts +++ b/packages/migrate/src/index.ts @@ -17,3 +17,9 @@ export * from './types' export { default as byline } from './utils/byline' export { getDatabaseVersionSafe } from './utils/getDatabaseVersionSafe' export { getSchemaPathAndPrint, printSchemaLoadedMessage } from './utils/getSchemaPathAndPrint' +export { + introspectSql, + type IntrospectSqlError, + type IntrospectSqlInput, + type IntrospectSqlResult, +} from './utils/introspectSql' diff --git a/packages/migrate/src/types.ts b/packages/migrate/src/types.ts index eccc57dcc2c4..74fbba45fa61 100644 --- a/packages/migrate/src/types.ts +++ b/packages/migrate/src/types.ts @@ -3,6 +3,7 @@ // // https://www.jsonrpc.org/specification +import type { SqlQueryOutput } from '@prisma/generator-helper' import type { MigrateTypes } from '@prisma/internals' import type { IntrospectionViewDefinition } from './views/handleViewsIO' @@ -216,6 +217,16 @@ export namespace EngineArgs { schema: MigrateTypes.SchemasContainer force: boolean } + + export interface IntrospectSqlParams { + url: string + queries: SqlQueryInput[] + } + + export interface SqlQueryInput { + name: string + source: string + } } // eslint-disable-next-line @typescript-eslint/no-namespace @@ -283,6 +294,10 @@ export namespace EngineResults { warnings: string[] unexecutable: string[] } + + export interface IntrospectSqlOutput { + queries: SqlQueryOutput[] + } } export interface FileMap { diff --git a/packages/migrate/src/utils/introspectSql.ts b/packages/migrate/src/utils/introspectSql.ts new file mode 100644 index 000000000000..bfb210de1c3c --- /dev/null +++ b/packages/migrate/src/utils/introspectSql.ts @@ -0,0 +1,121 @@ +import { SqlQueryOutput } from '@prisma/generator-helper' +import { type ConfigMetaFormat, getConfig, getEffectiveUrl, getSchemaWithPath } from '@prisma/internals' + +import { SchemaEngine } from '../SchemaEngine' +import { EngineArgs } from '../types' + +const supportedProviders = ['postgresql', 'cockroachdb', 'mysql', 'sqlite'] + +export interface IntrospectSqlInput extends EngineArgs.SqlQueryInput { + fileName: string +} + +export type IntrospectSqlError = { + fileName: string + message: string +} + +type IntrospectSingleResult = + | { + ok: true + result: SqlQueryOutput + } + | { + ok: false + error: IntrospectSqlError + } + +export type IntrospectSqlResult = + | { + ok: true + queries: SqlQueryOutput[] + } + | { + ok: false + errors: IntrospectSqlError[] + } + +export async function introspectSql( + schemaPath: string | undefined, + queries: IntrospectSqlInput[], +): Promise { + const schema = await getSchemaWithPath(schemaPath) + const config = await getConfig({ datamodel: schema.schemas }) + if (!supportedProviders.includes(config.datasources?.[0]?.activeProvider)) { + throw new Error(`Typed SQL is supported only for ${supportedProviders.join(', ')} providers`) + } + if (!isTypedSqlEnabled(config)) { + throw new Error(`\`typedSql\` preview feature needs to be enabled in ${schema.schemaPath}`) + } + + const firstDatasource = config.datasources[0] + if (!firstDatasource) { + throw new Error(`Could not find datasource in schema ${schema.schemaPath}`) + } + const url = getEffectiveUrl(firstDatasource).value + if (!url) { + throw new Error(`Could not get url from datasource ${firstDatasource.name} in ${schema.schemaPath}`) + } + + const schemaEngine = new SchemaEngine({ schemaPath: schema.schemaPath }) + const results: SqlQueryOutput[] = [] + const errors: IntrospectSqlError[] = [] + try { + for (const query of queries) { + const queryResult = await introspectSingleQuery(schemaEngine, url, query) + if (queryResult.ok) { + results.push(queryResult.result) + } else { + errors.push(queryResult.error) + } + } + } finally { + schemaEngine.stop() + } + + if (errors.length > 0) { + return { ok: false, errors } + } + return { ok: true, queries: results } +} + +// engine is capabale of introspecting queries as a batch, but we need to map them back to files they defined in, so, +// for now, we sending them in one by one +async function introspectSingleQuery( + schemaEngine: SchemaEngine, + url: string, + query: IntrospectSqlInput, +): Promise { + try { + const result = await schemaEngine.introspectSql({ + url, + queries: [query], + }) + const queryResult = result.queries[0] + if (!queryResult) { + return { + ok: false, + error: { + fileName: query.fileName, + message: 'Invalid response from schema engine', + }, + } + } + return { + ok: true, + result: queryResult, + } + } catch (error) { + return { + ok: false, + error: { + fileName: query.fileName, + message: String(error), + }, + } + } +} + +function isTypedSqlEnabled(config: ConfigMetaFormat) { + return config.generators.some((gen) => gen?.previewFeatures?.includes('typedSql')) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b71600ccad4..988707536236 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1245,6 +1245,9 @@ importers: '@antfu/ni': specifier: 0.21.12 version: 0.21.12 + '@babel/helper-validator-identifier': + specifier: 7.24.7 + version: 7.24.7 '@opentelemetry/api': specifier: 1.9.0 version: 1.9.0 @@ -1254,6 +1257,9 @@ importers: '@swc/jest': specifier: 0.2.36 version: 0.2.36(@swc/core@1.2.204) + '@types/babel__helper-validator-identifier': + specifier: 7.15.2 + version: 7.15.2 '@types/jest': specifier: 29.5.12 version: 29.5.12 @@ -1305,6 +1311,9 @@ importers: globby: specifier: 11.1.0 version: 11.1.0 + identifier-regex: + specifier: 1.0.0 + version: 1.0.0 indent-string: specifier: 4.0.0 version: 4.0.0 @@ -1844,7 +1853,7 @@ packages: '@babel/helper-module-imports': 7.21.4 '@babel/helper-simple-access': 7.21.5 '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.19.1 + '@babel/helper-validator-identifier': 7.22.20 '@babel/template': 7.20.7 '@babel/traverse': 7.21.5 '@babel/types': 7.21.5 @@ -1876,13 +1885,13 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/helper-validator-identifier@7.19.1: - resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + /@babel/helper-validator-identifier@7.22.20: + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} dev: true - /@babel/helper-validator-identifier@7.22.20: - resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + /@babel/helper-validator-identifier@7.24.7: + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} dev: true @@ -1915,7 +1924,7 @@ packages: resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 dev: true @@ -2089,7 +2098,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/helper-string-parser': 7.21.5 - '@babel/helper-validator-identifier': 7.19.1 + '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 dev: true @@ -4341,6 +4350,10 @@ packages: '@babel/types': 7.21.5 dev: true + /@types/babel__helper-validator-identifier@7.15.2: + resolution: {integrity: sha512-l3dkwCt890NFhMwPKXbxsWXC0Por0/+KaFIiQP1j38/vWSH2P3Tn6m+IuJHkx2SBI3VLFqincFe9JtBk2NFHOw==} + dev: true + /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: @@ -7540,6 +7553,13 @@ packages: safer-buffer: 2.1.2 dev: true + /identifier-regex@1.0.0: + resolution: {integrity: sha512-Rcy5cjBOM9iTR+Vwy0Llyip9u0cA99T1yiWOhDW/+PDaTQhyski0tMovsipQ/FRNDkudjLWusJ/IMVIlG5WZnQ==} + engines: {node: '>=18'} + dependencies: + reserved-identifiers: 1.0.0 + dev: true + /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true @@ -10379,6 +10399,11 @@ packages: transitivePeerDependencies: - supports-color + /reserved-identifiers@1.0.0: + resolution: {integrity: sha512-h0bP2Katmvf3hv4Z3WtDl4+6xt/OglQ2Xa6TnhZ/Rm9/7IH1crXQqMwD4J2ngKBonVv+fB55zfGgNDAmsevLVQ==} + engines: {node: '>=18'} + dev: true + /resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'}