diff --git a/packages/adapter-pg/src/__tests__/errors.test.ts b/packages/adapter-pg/src/__tests__/errors.test.ts index 7577cadb75a4..07d7a2d3b409 100644 --- a/packages/adapter-pg/src/__tests__/errors.test.ts +++ b/packages/adapter-pg/src/__tests__/errors.test.ts @@ -126,11 +126,22 @@ describe('convertDriverError', () => { }) }) - it('should handle ColumnNotFound (42703)', () => { - const error = { code: '42703', message: 'column "foo" does not exist', severity: 'ERROR' } + it.each([ + ['unquoted column name', 'column foo does not exist', 'foo'], + ['quoted column name', 'column "foo" does not exist', 'foo'], + ['unquoted qualified column name', 'column users.first_name does not exist', 'users.first_name'], + ['quoted qualified column name', 'column "users"."first name" does not exist', 'users.first name'], + ['partially quoted qualified column name (1)', 'column users."first name" does not exist', 'users.first name'], + ['partially quoted qualified column name (2)', 'column "users".first_name does not exist', 'users.first_name'], + ['quoted column name containing spaces', 'column "first name" does not exist', 'first name'], + ['quoted column name containing dots', 'column "first.name" does not exist', 'first.name'], + ['quoted qualified column name containing dots', 'column "users"."first.name" does not exist', 'users.first.name'], + ['quoted column name containing escaped quotes', 'column "a""b" does not exist', 'a"b'], + ])('should handle ColumnNotFound (42703) with %s', (description, message, expectedColumn) => { + const error = { code: '42703', message, severity: 'ERROR' } expect(convertDriverError(error)).toEqual({ kind: 'ColumnNotFound', - column: 'foo', + column: expectedColumn, originalCode: error.code, originalMessage: error.message, }) diff --git a/packages/adapter-pg/src/__tests__/pg.test.ts b/packages/adapter-pg/src/__tests__/pg.test.ts index 125163af994d..13b75a5288f0 100644 --- a/packages/adapter-pg/src/__tests__/pg.test.ts +++ b/packages/adapter-pg/src/__tests__/pg.test.ts @@ -34,6 +34,17 @@ describe('PrismaPgAdapterFactory', () => { await adapter.dispose() }) + it('should accept a connection string URL', async () => { + const connectionString = 'postgresql://test:test@localhost:5432/test' + const factory = new PrismaPgAdapterFactory(connectionString) + + expect((factory as any).config).toEqual({ connectionString }) + + const adapter = await factory.connect() + expect(adapter.underlyingDriver().options.connectionString).toBe(connectionString) + await adapter.dispose() + }) + it('should add and remove error event listener when using an external Pool', async () => { const pool = new pg.Pool({ user: 'test', password: 'test', database: 'test', port: 5432, host: 'localhost' }) pool.on('error', () => {}) diff --git a/packages/adapter-pg/src/errors.ts b/packages/adapter-pg/src/errors.ts index e7188172231c..1c17555317a9 100644 --- a/packages/adapter-pg/src/errors.ts +++ b/packages/adapter-pg/src/errors.ts @@ -136,11 +136,13 @@ function mapDriverError(error: DatabaseError): MappedError { kind: 'TableDoesNotExist', table: error.message.split(' ').at(1)?.split('"').at(1), } - case '42703': + case '42703': { + const rawColumn = error.message.match(/^column (.+) does not exist$/)?.at(1) return { kind: 'ColumnNotFound', - column: error.message.split(' ').at(1)?.split('"').at(1), + column: rawColumn?.replace(/"((?:""|[^"])*)"/g, (_, id) => id.replaceAll('""', '"')), } + } case '42P04': return { kind: 'DatabaseAlreadyExists', diff --git a/packages/adapter-pg/src/pg.ts b/packages/adapter-pg/src/pg.ts index 8d01ee593875..481b94415213 100644 --- a/packages/adapter-pg/src/pg.ts +++ b/packages/adapter-pg/src/pg.ts @@ -278,12 +278,15 @@ export class PrismaPgAdapterFactory implements SqlMigrationAwareDriverAdapterFac private externalPool: pg.Pool | null constructor( - poolOrConfig: pg.Pool | pg.PoolConfig, + poolOrConfig: pg.Pool | pg.PoolConfig | string, private readonly options?: PrismaPgOptions, ) { if (poolOrConfig instanceof pg.Pool) { this.externalPool = poolOrConfig this.config = poolOrConfig.options + } else if (typeof poolOrConfig === 'string') { + this.externalPool = null + this.config = { connectionString: poolOrConfig } } else { this.externalPool = null this.config = poolOrConfig diff --git a/packages/cli/src/Generate.ts b/packages/cli/src/Generate.ts index a3e10b0134a3..b245177e2170 100644 --- a/packages/cli/src/Generate.ts +++ b/packages/cli/src/Generate.ts @@ -4,6 +4,7 @@ import { enginesVersion } from '@prisma/engines' import { SqlQueryOutput } from '@prisma/generator' import { arg, + BuiltInProvider, Command, createSchemaPathInput, format, @@ -179,7 +180,7 @@ ${bold('Examples')} } else { // Only used for CLI output, ie Go client doesn't want JS example output const jsClient = generators.find( - (g) => g.options && parseEnvValue(g.options.generator.provider) === 'prisma-client-js', + (g) => g.options && parseEnvValue(g.options.generator.provider) === BuiltInProvider.PrismaClientJs, ) clientGeneratorVersion = jsClient?.manifest?.version ?? null @@ -230,7 +231,7 @@ Please run \`prisma generate\` manually.` if (!watchMode) { const prismaClientJSGenerator = generators?.find( ({ options }) => - options?.generator.provider && parseEnvValue(options?.generator.provider) === 'prisma-client-js', + options?.generator.provider && parseEnvValue(options?.generator.provider) === BuiltInProvider.PrismaClientJs, ) let hint = '' diff --git a/packages/cli/src/utils/checkpoint.ts b/packages/cli/src/utils/checkpoint.ts index 426720649dfa..00353d35855e 100644 --- a/packages/cli/src/utils/checkpoint.ts +++ b/packages/cli/src/utils/checkpoint.ts @@ -1,6 +1,7 @@ import { Debug } from '@prisma/debug' import { arg, + BuiltInProvider, createSchemaPathInput, getCLIPathHash, getProjectHash, @@ -134,7 +135,7 @@ export async function tryToReadDataFromSchema(schemaPath: SchemaPathInput) { .filter((generator) => generator && generator.provider) .map((generator) => parseEnvValue(generator.provider)) - const clientGeneratorProviders = ['prisma-client', 'prisma-client-js'] + const clientGeneratorProviders: string[] = [BuiltInProvider.PrismaClientTs, BuiltInProvider.PrismaClientJs] const previewFeatures = schemaContext.generators .filter((generator) => { const provider = generator?.provider ? parseEnvValue(generator.provider) : undefined diff --git a/packages/client-generator-js/src/generator.ts b/packages/client-generator-js/src/generator.ts index 998aaa0f2df8..ac32a4b8ca9b 100755 --- a/packages/client-generator-js/src/generator.ts +++ b/packages/client-generator-js/src/generator.ts @@ -2,7 +2,7 @@ import path from 'node:path' import { enginesVersion } from '@prisma/engines-version' import { Generator, GeneratorConfig, GeneratorManifest, GeneratorOptions } from '@prisma/generator' -import { parseEnvValue } from '@prisma/internals' +import { BuiltInProvider, parseEnvValue } from '@prisma/internals' import { version as clientVersion } from '../package.json' import { generateClient } from './generateClient' @@ -20,7 +20,7 @@ type PrismaClientJsGeneratorOptions = { // visit https://pris.ly/cli/output-path` export class PrismaClientJsGenerator implements Generator { - readonly name = 'prisma-client-js' + readonly name = BuiltInProvider.PrismaClientJs #shouldResolvePrismaClient: boolean #runtimePath?: string diff --git a/packages/client-generator-js/src/utils/types/dmmfToTypes.ts b/packages/client-generator-js/src/utils/types/dmmfToTypes.ts index d298d1891fbf..69f10075aebc 100644 --- a/packages/client-generator-js/src/utils/types/dmmfToTypes.ts +++ b/packages/client-generator-js/src/utils/types/dmmfToTypes.ts @@ -1,6 +1,7 @@ import path from 'node:path' import type * as DMMF from '@prisma/dmmf' +import { BuiltInProvider } from '@prisma/internals' import { TSClient } from '../../TSClient/TSClient' @@ -24,9 +25,9 @@ export function dmmfToTypes(dmmf: DMMF.Document) { generator: { binaryTargets: [], config: {}, - name: 'prisma-client-js', + name: BuiltInProvider.PrismaClientJs, output: null, - provider: { value: 'prisma-client-js', fromEnvVar: null }, + provider: { value: BuiltInProvider.PrismaClientJs, fromEnvVar: null }, previewFeatures: [], isCustomOutput: false, sourceFilePath: 'schema.prisma', diff --git a/packages/client-generator-ts/src/TSClient/Model.ts b/packages/client-generator-ts/src/TSClient/Model.ts index bf8d99c4d49a..35e85452f7c3 100644 --- a/packages/client-generator-ts/src/TSClient/Model.ts +++ b/packages/client-generator-ts/src/TSClient/Model.ts @@ -207,7 +207,7 @@ ${indent( ${ts.stringify(buildOutputType(groupByType))} -type ${getGroupByPayloadName(model.name)} = Prisma.PrismaPromise< +export type ${getGroupByPayloadName(model.name)} = Prisma.PrismaPromise< Array< Prisma.PickEnumerable<${groupByType.name}, T['by']> & { diff --git a/packages/client/src/utils/getTestClient.ts b/packages/client/src/utils/getTestClient.ts index 2b2493b5b5dd..0be6a8ef8608 100644 --- a/packages/client/src/utils/getTestClient.ts +++ b/packages/client/src/utils/getTestClient.ts @@ -1,6 +1,7 @@ import { dmmfToRuntimeDataModel, GetPrismaClientConfig } from '@prisma/client-common' import { getDMMF } from '@prisma/client-generator-js' import { + BuiltInProvider, extractPreviewFeatures, getConfig, getSchemaWithPath, @@ -34,7 +35,7 @@ export async function getTestClient(schemaDir?: string, printWarnings?: boolean) printConfigWarnings(config.warnings) } - const generator = config.generators.find((g) => parseEnvValue(g.provider) === 'prisma-client-js') + const generator = config.generators.find((g) => parseEnvValue(g.provider) === BuiltInProvider.PrismaClientJs) const previewFeatures = extractPreviewFeatures(config.generators) ;(global as any).TARGET_BUILD_TYPE = 'client' diff --git a/packages/internals/src/built-in-provider.ts b/packages/internals/src/built-in-provider.ts new file mode 100644 index 000000000000..5435a29ca1c1 --- /dev/null +++ b/packages/internals/src/built-in-provider.ts @@ -0,0 +1,5 @@ +/** Built-in generator provider identifiers used across Prisma packages. */ +export const BuiltInProvider = { + PrismaClientJs: 'prisma-client-js', + PrismaClientTs: 'prisma-client', +} as const diff --git a/packages/internals/src/cli/getGeneratorSuccessMessage.ts b/packages/internals/src/cli/getGeneratorSuccessMessage.ts index 157a324708ec..3249190cad26 100644 --- a/packages/internals/src/cli/getGeneratorSuccessMessage.ts +++ b/packages/internals/src/cli/getGeneratorSuccessMessage.ts @@ -1,6 +1,7 @@ import { bold, dim } from 'kleur/colors' import path from 'path' +import { BuiltInProvider } from '../built-in-provider' import type { Generator } from '../Generator' import { formatms } from '../utils/formatms' import { parseEnvValue } from '../utils/parseEnvValue' @@ -20,7 +21,7 @@ export function getGeneratorSuccessMessage(generator: Generator, time: number): function formatVersion(generator: Generator): string | undefined { const version = generator.manifest?.version - if (generator.getProvider() === 'prisma-client-js') { + if (generator.getProvider() === BuiltInProvider.PrismaClientJs) { // version is always defined for prisma-client-js return `v${version ?? '?.?.?'}` } diff --git a/packages/internals/src/index.ts b/packages/internals/src/index.ts index 16e3b045b985..5ff294386fcf 100644 --- a/packages/internals/src/index.ts +++ b/packages/internals/src/index.ts @@ -1,3 +1,4 @@ +export { BuiltInProvider } from './built-in-provider' export { checkUnsupportedDataProxy } from './cli/checkUnsupportedDataProxy' export { type DirectoryConfig, inferDirectoryConfig } from './cli/directoryConfig' export { getGeneratorSuccessMessage } from './cli/getGeneratorSuccessMessage' diff --git a/packages/internals/src/utils/extractPreviewFeatures.ts b/packages/internals/src/utils/extractPreviewFeatures.ts index 0f6232dcd63a..e99e6088f3c0 100644 --- a/packages/internals/src/utils/extractPreviewFeatures.ts +++ b/packages/internals/src/utils/extractPreviewFeatures.ts @@ -1,7 +1,8 @@ import type { GeneratorConfig } from '@prisma/generator' +import { BuiltInProvider } from '../built-in-provider' import { parseEnvValue } from './parseEnvValue' export function extractPreviewFeatures(generators: GeneratorConfig[]): string[] { - return generators.find((g) => parseEnvValue(g.provider) === 'prisma-client-js')?.previewFeatures || [] + return generators.find((g) => parseEnvValue(g.provider) === BuiltInProvider.PrismaClientJs)?.previewFeatures || [] }