Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ dist
*.db-journal
*.tgz
.pnpm-store
*.vsix
10 changes: 10 additions & 0 deletions packages/language/src/validators/expression-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
isReferenceExpr,
isThisExpr,
MemberAccessExpr,
UnaryExpr,
type ExpressionType,
} from '../generated/ast';

Expand Down Expand Up @@ -67,6 +68,9 @@ export default class ExpressionValidator implements AstValidator<Expression> {
case 'BinaryExpr':
this.validateBinaryExpr(expr, accept);
break;
case 'UnaryExpr':
this.validateUnaryExpr(expr, accept);
break;
}
}

Expand Down Expand Up @@ -245,6 +249,12 @@ export default class ExpressionValidator implements AstValidator<Expression> {
}
}

private validateUnaryExpr(expr: UnaryExpr, accept: ValidationAcceptor) {
if (expr.operand.$resolvedType && expr.operand.$resolvedType.decl !== 'Boolean') {
accept('error', `operand of "${expr.operator}" must be of Boolean type`, { node: expr.operand });
}
}

private validateCollectionPredicate(expr: BinaryExpr, accept: ValidationAcceptor) {
if (!expr.$resolvedType) {
accept('error', 'collection predicate can only be used on an array of model type', { node: expr });
Expand Down
14 changes: 11 additions & 3 deletions packages/orm/src/client/executor/zenstack-query-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export class ZenStackQueryExecutor extends DefaultQueryExecutor {
if (parameters) {
compiled = { ...compiled, parameters };
}
return connection.executeQuery<any>(compiled);
return this.internalExecuteQuery(connection, compiled);
}

if (
Expand Down Expand Up @@ -246,7 +246,7 @@ export class ZenStackQueryExecutor extends DefaultQueryExecutor {
queryId,
);

const result = await connection.executeQuery<any>(compiled);
const result = await this.internalExecuteQuery(connection, compiled);

if (!this.driver.isTransactionConnection(connection)) {
// not in a transaction, just call all after-mutation hooks
Expand Down Expand Up @@ -470,7 +470,7 @@ export class ZenStackQueryExecutor extends DefaultQueryExecutor {
const compiled = this.compileQuery(selectQueryNode, createQueryId());
// execute the query directly with the given connection to avoid triggering
// any other side effects
const result = await connection.executeQuery(compiled);
const result = await this.internalExecuteQuery(connection, compiled);
return result.rows as Record<string, unknown>[];
}

Expand All @@ -483,4 +483,12 @@ export class ZenStackQueryExecutor extends DefaultQueryExecutor {
return condition2;
}
}

private async internalExecuteQuery(connection: DatabaseConnection, compiledQuery: CompiledQuery) {
try {
return await connection.executeQuery<any>(compiledQuery);
} catch (err) {
throw createDBQueryError('Failed to execute query', err, compiledQuery.sql, compiledQuery.parameters);
}
}
}
2 changes: 1 addition & 1 deletion tests/regression/test/issue-503/regression.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createTestClient } from '@zenstackhq/testtools';
import { describe, expect, it } from 'vitest';
import { schema } from './schema';

describe('Regression tests for issues #503', () => {
describe('Regression tests for issue #503', () => {
it('verifies the issue', async () => {
const db = await createTestClient(schema);
const r = await db.internalChat.create({
Expand Down
2 changes: 1 addition & 1 deletion tests/regression/test/issue-505.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createTestClient } from '@zenstackhq/testtools';
import { describe, expect, it } from 'vitest';

describe('Regression tests for issues 505', () => {
describe('Regression tests for issue 505', () => {
it('verifies the issue', async () => {
const db = await createTestClient(
`
Expand Down
103 changes: 103 additions & 0 deletions tests/regression/test/issue-510.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { createPolicyTestClient } from '@zenstackhq/testtools';
import { describe, expect, it } from 'vitest';

describe('Regression tests for issue 510', () => {
it('verifies the issue', async () => {
const schema = `
type ID {
id String @id @default(nanoid())
}

type Timestamps {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

type Base with ID, Timestamps {
}

type AuthInfo {
id String
username String
role Role

@@auth
}

enum Role {
SUPERADMIN
ADMIN
USER
}

enum FileStatus {
PENDING
UPLOADED
FAILED
}

model User with Base {
username String @unique
passwordHash String
name String
role Role

RefreshToken RefreshToken[]
File File[]
Post Post[]

@@allow('all', auth().id == id)
}

model File with Timestamps {
key String @id

userId String
User User @relation(fields: [userId], references: [id])

originalFilename String
filename String
contentType String
size Int?
status FileStatus

Post Post[]

@@allow('all', auth().id == userId)
}

model AuditLog {
timestamp DateTime @id @default(now())

action String
data Json

@@deny('all', true)
}

model RefreshToken with Base {
userId String
User User @relation(fields: [userId], references: [id])

revoked Boolean

@@deny('all', true)
}

model Post with Base {
userId String
User User @relation(fields: [userId], references: [id])

content String
imageKey String?
Image File? @relation(fields: [imageKey], references: [key])

@@allow('read', true)
@@allow('create', auth().id == userId && (!Image || auth().id == Image.userId))
@@allow('update,delete', auth().id == userId)
}
`;

await expect(createPolicyTestClient(schema)).rejects.toThrow(/operand of "!" must be of Boolean type/);
});
});