From 91fe8e71b513804de36d08b03c37b0c175580906 Mon Sep 17 00:00:00 2001 From: Yiming Date: Wed, 3 Jan 2024 20:21:13 +0800 Subject: [PATCH] fix: prisma schema generation issue with calling attribute function with literal (#930) --- .../src/plugins/prisma/prisma-builder.ts | 11 ++----- .../src/plugins/prisma/schema-generator.ts | 19 +++++++---- .../tests/generator/prisma-builder.test.ts | 2 +- .../tests/generator/prisma-generator.test.ts | 32 +++++++++++++++++++ packages/sdk/src/prisma.ts | 4 +-- 5 files changed, 50 insertions(+), 18 deletions(-) diff --git a/packages/schema/src/plugins/prisma/prisma-builder.ts b/packages/schema/src/plugins/prisma/prisma-builder.ts index 19a545185..64777b62e 100644 --- a/packages/schema/src/plugins/prisma/prisma-builder.ts +++ b/packages/schema/src/plugins/prisma/prisma-builder.ts @@ -304,17 +304,10 @@ export class FunctionCall { } export class FunctionCallArg { - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any - constructor(public name: string | undefined, public value: any) {} + constructor(public name: string | undefined, public value: string) {} toString(): string { - const val = - this.value === null || this.value === undefined - ? 'null' - : typeof this.value === 'string' - ? `"${this.value}"` - : this.value.toString(); - return this.name ? `${this.name}: ${val}` : val; + return this.name ? `${this.name}: ${this.value}` : this.value; } } diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index 45a025d27..508e379f3 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -18,7 +18,9 @@ import { isArrayExpr, isInvocationExpr, isLiteralExpr, + isNullExpr, isReferenceExpr, + isStringLiteral, LiteralExpr, Model, NumberLiteral, @@ -35,6 +37,7 @@ import { PluginOptions, resolved, resolvePath, + ZModelCodeGenerator, } from '@zenstackhq/sdk'; import fs from 'fs'; import { writeFile } from 'fs/promises'; @@ -45,6 +48,7 @@ import { name } from '.'; import { getStringLiteral } from '../../language-server/validator/utils'; import telemetry from '../../telemetry'; import { execSync } from '../../utils/exec-utils'; +import { getPackageJson } from '../../utils/pkg-utils'; import { ModelFieldType, AttributeArg as PrismaAttributeArg, @@ -62,8 +66,6 @@ import { PassThroughAttribute as PrismaPassThroughAttribute, SimpleField, } from './prisma-builder'; -import { ZModelCodeGenerator } from '@zenstackhq/sdk'; -import { getPackageJson } from '../../utils/pkg-utils'; const MODEL_PASSTHROUGH_ATTR = '@@prisma.passthrough'; const FIELD_PASSTHROUGH_ATTR = '@prisma.passthrough'; @@ -377,10 +379,15 @@ export default class PrismaSchemaGenerator { return new PrismaFunctionCall( resolved(node.function).name, node.args.map((arg) => { - if (!isLiteralExpr(arg.value)) { - throw new PluginError(name, 'Function call argument must be literal'); - } - return new PrismaFunctionCallArg(arg.name, arg.value.value); + const val = match(arg.value) + .when(isStringLiteral, (v) => `"${v.value}"`) + .when(isLiteralExpr, (v) => v.value.toString()) + .when(isNullExpr, () => 'null') + .otherwise(() => { + throw new PluginError(name, 'Function call argument must be literal or null'); + }); + + return new PrismaFunctionCallArg(arg.name, val); }) ); } diff --git a/packages/schema/tests/generator/prisma-builder.test.ts b/packages/schema/tests/generator/prisma-builder.test.ts index 12882a13f..48e465362 100644 --- a/packages/schema/tests/generator/prisma-builder.test.ts +++ b/packages/schema/tests/generator/prisma-builder.test.ts @@ -102,7 +102,7 @@ describe('Prisma Builder Tests', () => { undefined, new AttributeArgValue( 'FunctionCall', - new FunctionCall('dbgenerated', [new FunctionCallArg(undefined, 'timestamp_id()')]) + new FunctionCall('dbgenerated', [new FunctionCallArg(undefined, '"timestamp_id()"')]) ) ), ]), diff --git a/packages/schema/tests/generator/prisma-generator.test.ts b/packages/schema/tests/generator/prisma-generator.test.ts index 049104b1e..30a477026 100644 --- a/packages/schema/tests/generator/prisma-generator.test.ts +++ b/packages/schema/tests/generator/prisma-generator.test.ts @@ -96,6 +96,38 @@ describe('Prisma generator test', () => { expect(content).toContain('unsupported Unsupported("foo")'); }); + it('attribute function', async () => { + const model = await loadModel(` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id @default(nanoid(6)) + x String @default(nanoid()) + y String @default(dbgenerated("gen_random_uuid()")) + } + `); + + const { name } = tmp.fileSync({ postfix: '.prisma' }); + await new PrismaSchemaGenerator().generate(model, { + name: 'Prisma', + provider: '@core/prisma', + schemaPath: 'schema.zmodel', + output: name, + generateClient: false, + }); + + const content = fs.readFileSync(name, 'utf-8'); + // "nanoid()" is only available in later versions of Prisma + await getDMMF({ datamodel: content }, '5.0.0'); + + expect(content).toContain('@default(nanoid(6))'); + expect(content).toContain('@default(nanoid())'); + expect(content).toContain('@default(dbgenerated("gen_random_uuid()"))'); + }); + it('triple slash comments', async () => { const model = await loadModel(` datasource db { diff --git a/packages/sdk/src/prisma.ts b/packages/sdk/src/prisma.ts index 4b4e461a1..970ce58ba 100644 --- a/packages/sdk/src/prisma.ts +++ b/packages/sdk/src/prisma.ts @@ -81,8 +81,8 @@ export type GetDMMFOptions = { /** * Loads Prisma DMMF with appropriate version */ -export function getDMMF(options: GetDMMFOptions): Promise { - const prismaVersion = getPrismaVersion(); +export function getDMMF(options: GetDMMFOptions, defaultPrismaVersion?: string): Promise { + const prismaVersion = getPrismaVersion() ?? defaultPrismaVersion; if (prismaVersion && semver.gte(prismaVersion, '5.0.0')) { const _getDMMF = require('@prisma/internals-v5').getDMMF; return _getDMMF(options);