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
3 changes: 2 additions & 1 deletion .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ jobs:
strategy:
matrix:
node-version: [20.x]
provider: [sqlite, postgresql]

steps:
- name: Checkout
Expand Down Expand Up @@ -76,4 +77,4 @@ jobs:
run: pnpm run lint

- name: Test
run: pnpm run test
run: TEST_DB_PROVIDER=${{ matrix.provider }} pnpm run test
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"build": "turbo run build",
"watch": "turbo run watch build",
"lint": "turbo run lint",
"test": "turbo run test",
"test": "vitest run",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"pr": "gh pr create --fill-first --base dev",
"merge-main": "gh pr create --title \"merge dev to main\" --body \"\" --base main --head dev",
Expand Down
20 changes: 14 additions & 6 deletions packages/language/res/stdlib.zmodel
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,10 @@ function future(): Any {
} @@@expressionContext([AccessPolicy])

/**
* If the field value contains the search string. By default, the search is case-sensitive,
* but you can override the behavior with the "caseInSensitive" argument.
* Checks if the field value contains the search string. By default, the search is case-sensitive, and
* "LIKE" operator is used to match. If `caseInSensitive` is true, "ILIKE" operator is used if
* supported, otherwise it still falls back to "LIKE" and delivers whatever the database's
* behavior is.
*/
function contains(field: String, search: String, caseInSensitive: Boolean?): Boolean {
} @@@expressionContext([AccessPolicy, ValidationRule])
Expand All @@ -136,15 +138,21 @@ function search(field: String, search: String): Boolean {
} @@@expressionContext([AccessPolicy])

/**
* If the field value starts with the search string
* Checks the field value starts with the search string. By default, the search is case-sensitive, and
* "LIKE" operator is used to match. If `caseInSensitive` is true, "ILIKE" operator is used if
* supported, otherwise it still falls back to "LIKE" and delivers whatever the database's
* behavior is.
*/
function startsWith(field: String, search: String): Boolean {
function startsWith(field: String, search: String, caseInSensitive: Boolean?): Boolean {
} @@@expressionContext([AccessPolicy, ValidationRule])

/**
* If the field value ends with the search string
* Checks if the field value ends with the search string. By default, the search is case-sensitive, and
* "LIKE" operator is used to match. If `caseInSensitive` is true, "ILIKE" operator is used if
* supported, otherwise it still falls back to "LIKE" and delivers whatever the database's
* behavior is.
*/
function endsWith(field: String, search: String): Boolean {
function endsWith(field: String, search: String, caseInSensitive: Boolean?): Boolean {
} @@@expressionContext([AccessPolicy, ValidationRule])

/**
Expand Down
25 changes: 25 additions & 0 deletions packages/language/test/delegate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import { loadSchema, loadSchemaWithError } from './utils';
describe('Delegate Tests', () => {
it('supports inheriting from delegate', async () => {
const model = await loadSchema(`
datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

model A {
id Int @id @default(autoincrement())
x String
Expand All @@ -24,6 +29,11 @@ describe('Delegate Tests', () => {
it('rejects inheriting from non-delegate models', async () => {
await loadSchemaWithError(
`
datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

model A {
id Int @id @default(autoincrement())
x String
Expand All @@ -40,6 +50,11 @@ describe('Delegate Tests', () => {
it('can detect cyclic inherits', async () => {
await loadSchemaWithError(
`
datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

model A extends B {
x String
@@delegate(x)
Expand All @@ -57,6 +72,11 @@ describe('Delegate Tests', () => {
it('can detect duplicated fields from base model', async () => {
await loadSchemaWithError(
`
datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

model A {
id String @id
x String
Expand All @@ -74,6 +94,11 @@ describe('Delegate Tests', () => {
it('can detect duplicated attributes from base model', async () => {
await loadSchemaWithError(
`
datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

model A {
id String @id
x String
Expand Down
21 changes: 19 additions & 2 deletions packages/language/test/import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ describe('Import tests', () => {
fs.writeFileSync(
path.join(name, 'a.zmodel'),
`
datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

model A {
id Int @id
name String
Expand Down Expand Up @@ -48,6 +53,12 @@ enum Role {
path.join(name, 'b.zmodel'),
`
import './a'

datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

model User {
id Int @id
role Role
Expand All @@ -56,7 +67,7 @@ model User {
);

const model = await expectLoaded(path.join(name, 'b.zmodel'));
expect((model.declarations[0] as DataModel).fields[1].type.reference?.ref?.name).toBe('Role');
expect((model.declarations[1] as DataModel).fields[1].type.reference?.ref?.name).toBe('Role');
});

it('supports cyclic imports', async () => {
Expand All @@ -65,6 +76,12 @@ model User {
path.join(name, 'a.zmodel'),
`
import './b'

datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

model A {
id Int @id
b B?
Expand All @@ -86,7 +103,7 @@ model B {
const modelB = await expectLoaded(path.join(name, 'b.zmodel'));
expect((modelB.declarations[0] as DataModel).fields[1].type.reference?.ref?.name).toBe('A');
const modelA = await expectLoaded(path.join(name, 'a.zmodel'));
expect((modelA.declarations[0] as DataModel).fields[1].type.reference?.ref?.name).toBe('B');
expect((modelA.declarations[1] as DataModel).fields[1].type.reference?.ref?.name).toBe('B');
});

async function expectLoaded(file: string) {
Expand Down
15 changes: 15 additions & 0 deletions packages/language/test/mixin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import { DataModel, TypeDef } from '../src/ast';
describe('Mixin Tests', () => {
it('supports model mixing types to Model', async () => {
const model = await loadSchema(`
datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

type A {
x String
}
Expand All @@ -25,6 +30,11 @@ describe('Mixin Tests', () => {

it('supports model mixing types to type', async () => {
const model = await loadSchema(`
datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

type A {
x String
}
Expand Down Expand Up @@ -52,6 +62,11 @@ describe('Mixin Tests', () => {
it('can detect cyclic mixins', async () => {
await loadSchemaWithError(
`
datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}

type A with B {
x String
}
Expand Down
2 changes: 2 additions & 0 deletions packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"watch": "tsup-node --watch",
"lint": "eslint src --ext ts",
"test": "vitest run && pnpm test:typecheck",
"test:sqlite": "TEST_DB_PROVIDER=sqlite vitest run",
"test:postgresql": "TEST_DB_PROVIDER=postgresql vitest run",
"test:generate": "tsx test/scripts/generate.ts",
"test:typecheck": "tsc --project tsconfig.test.json",
"pack": "pnpm pack"
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime/src/client/client-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class ClientImpl<Schema extends SchemaDef> {
executor?: QueryExecutor,
) {
this.$schema = schema;
this.$options = options ?? ({} as ClientOptions<Schema>);
this.$options = options;

this.$options.functions = {
...BuiltinFunctions,
Expand Down Expand Up @@ -326,7 +326,7 @@ export class ClientImpl<Schema extends SchemaDef> {

function createClientProxy<Schema extends SchemaDef>(client: ClientImpl<Schema>): ClientImpl<Schema> {
const inputValidator = new InputValidator(client.$schema);
const resultProcessor = new ResultProcessor(client.$schema);
const resultProcessor = new ResultProcessor(client.$schema, client.$options);

return new Proxy(client, {
get: (target, prop, receiver) => {
Expand Down
14 changes: 14 additions & 0 deletions packages/runtime/src/client/crud/dialects/base-dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
return value;
}

transformOutput(value: unknown, _type: BuiltinType) {
return value;
}

// #region common query builders

buildSelectModel(eb: ExpressionBuilder<any, any>, model: string, modelAlias: string) {
Expand Down Expand Up @@ -1255,5 +1259,15 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
*/
abstract get supportInsertWithDefault(): boolean;

/**
* Gets the SQL column type for the given field definition.
*/
abstract getFieldSqlType(fieldDef: FieldDef): string;

/*
* Gets the string casing behavior for the dialect.
*/
abstract getStringCasingBehavior(): { supportsILike: boolean; likeCaseSensitive: boolean };

// #endregion
}
Loading