Skip to content

Commit b06e557

Browse files
committed
add support for partial indexes based on the Prisma syntax
1 parent 8609d5b commit b06e557

6 files changed

Lines changed: 106 additions & 3 deletions

File tree

packages/language/res/stdlib.zmodel

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ attribute @@id(_ fields: FieldReference[], name: String?, map: String?, length:
250250
* @param sort: Allows you to specify in what order the entries of the constraint are stored in the database. The available options are Asc and Desc.
251251
* @param clustered: Boolean Defines whether the constraint is clustered or non-clustered. Defaults to false.
252252
*/
253-
attribute @@unique(_ fields: FieldReference[], name: String?, map: String?, length: Int?, sort: SortOrder?, clustered: Boolean?) @@@prisma
253+
attribute @@unique(_ fields: FieldReference[], name: String?, map: String?, length: Int?, sort: SortOrder?, clustered: Boolean?, where: Any?) @@@prisma
254254

255255
/**
256256
* Index types
@@ -353,7 +353,7 @@ enum SortOrder {
353353
* @params clustered: Defines whether the index is clustered or non-clustered. Defaults to false.
354354
* @params type: Allows you to specify an index access method. Defaults to BTree.
355355
*/
356-
attribute @@index(_ fields: FieldReference[], name: String?, map: String?, length: Int?, sort: SortOrder?, clustered: Boolean?, type: IndexType?) @@@prisma
356+
attribute @@index(_ fields: FieldReference[], name: String?, map: String?, length: Int?, sort: SortOrder?, clustered: Boolean?, type: IndexType?, where: Any?) @@@prisma
357357

358358
/**
359359
* Defines meta information about the relation.

packages/sdk/src/prisma/prisma-builder.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ export class AttributeArg {
258258

259259
export class AttributeArgValue {
260260
constructor(
261-
public type: 'String' | 'FieldReference' | 'Number' | 'Boolean' | 'Array' | 'FunctionCall',
261+
public type: 'String' | 'FieldReference' | 'Number' | 'Boolean' | 'Array' | 'FunctionCall' | 'Raw',
262262
public value: string | number | boolean | FieldReference | FunctionCall | AttributeArgValue[],
263263
) {
264264
switch (type) {
@@ -282,6 +282,9 @@ export class AttributeArgValue {
282282
case 'FunctionCall':
283283
if (!(value instanceof FunctionCall)) throw new Error('Value must be FunctionCall');
284284
break;
285+
case 'Raw':
286+
if (typeof value !== 'string') throw new Error('Value must be string');
287+
break;
285288
}
286289
}
287290

@@ -310,6 +313,8 @@ export class AttributeArgValue {
310313
return this.value ? 'true' : 'false';
311314
case 'Array':
312315
return '[' + (this.value as AttributeArgValue[]).map((v) => v.toString()).join(', ') + ']';
316+
case 'Raw':
317+
return this.value as string;
313318
default:
314319
throw new Error(`Unknown attribute value type ${this.type}`);
315320
}

packages/sdk/src/prisma/prisma-schema-generator.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
isInvocationExpr,
2525
isLiteralExpr,
2626
isNullExpr,
27+
isObjectExpr,
2728
isReferenceExpr,
2829
isStringLiteral,
2930
isTypeDef,
@@ -368,6 +369,8 @@ export class PrismaSchemaGenerator {
368369
node.args.map((arg) => new PrismaFieldReferenceArg(arg.name, this.exprToText(arg.value))),
369370
),
370371
);
372+
} else if (isObjectExpr(node)) {
373+
return new PrismaAttributeArgValue('Raw', this.exprToText(node));
371374
} else if (isInvocationExpr(node)) {
372375
// invocation
373376
return new PrismaAttributeArgValue('FunctionCall', this.makeFunctionCall(node));
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { execSync } from 'child_process';
3+
import path from 'path';
4+
import fs from 'fs';
5+
6+
const SCHEMA_DIR = path.join(__dirname, 'schemas/partial-index');
7+
const SCHEMA_FILE = path.join(SCHEMA_DIR, 'schema.zmodel');
8+
const GENERATED_PRISMA = path.join(SCHEMA_DIR, 'schema.prisma');
9+
10+
function runGenerate() {
11+
execSync(`npx zenstack generate --schema ${SCHEMA_FILE} --output ${SCHEMA_DIR}`, { stdio: 'inherit' });
12+
}
13+
14+
describe('e2e: partial index in ZModel', () => {
15+
it('should generate Prisma schema with partial index (object literal)', () => {
16+
runGenerate();
17+
const prismaSchema = fs.readFileSync(GENERATED_PRISMA, 'utf-8');
18+
expect(prismaSchema).toContain('@@index([title], where: { published: true })');
19+
expect(prismaSchema).toContain('@@unique([title], where: { published: true })');
20+
});
21+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
datasource db {
2+
provider = "sqlite"
3+
url = "file:./dev.db"
4+
}
5+
6+
model Post {
7+
id Int @id @default(autoincrement())
8+
title String
9+
published Boolean
10+
11+
@@index([title], where: { published: true })
12+
@@unique([title], where: { published: true })
13+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { PrismaSchemaGenerator } from '@zenstackhq/sdk';
2+
import { loadSchema } from '@zenstackhq/testtools';
3+
import { describe, expect, it } from 'vitest';
4+
5+
describe('PrismaSchemaGenerator partial index', () => {
6+
it('should pass @@index where string argument through to Prisma schema', async () => {
7+
const model = await loadSchema(`
8+
datasource db {
9+
provider = 'postgresql'
10+
url = 'env("DATABASE_URL")'
11+
}
12+
13+
model Foo {
14+
id Int @id
15+
@@index([id], where: "id > 0")
16+
}
17+
`);
18+
19+
const generator = new PrismaSchemaGenerator(model);
20+
const schema = await generator.generate();
21+
expect(schema).toContain('@@index([id], where: "id > 0")');
22+
});
23+
24+
it('should pass @@index where object argument through to Prisma schema', async () => {
25+
const model = await loadSchema(`
26+
datasource db {
27+
provider = 'postgresql'
28+
url = 'env("DATABASE_URL")'
29+
}
30+
31+
model Foo {
32+
id Int @id
33+
published Boolean
34+
@@index([id], where: { published: true })
35+
}
36+
`);
37+
38+
const generator = new PrismaSchemaGenerator(model);
39+
const schema = await generator.generate();
40+
expect(schema).toContain('@@index([id], where: { published: true })');
41+
});
42+
43+
it('should pass @@unique where argument through to Prisma schema', async () => {
44+
const model = await loadSchema(`
45+
datasource db {
46+
provider = 'postgresql'
47+
url = 'env("DATABASE_URL")'
48+
}
49+
50+
model Foo {
51+
id Int @id
52+
email String
53+
@@unique([email], where: "email IS NOT NULL")
54+
}
55+
`);
56+
57+
const generator = new PrismaSchemaGenerator(model);
58+
const schema = await generator.generate();
59+
expect(schema).toContain('@@unique([email], where: "email IS NOT NULL")');
60+
});
61+
});

0 commit comments

Comments
 (0)