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
18 changes: 9 additions & 9 deletions packages/runtime/src/client/crud/dialects/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {

abstract get provider(): DataSourceProviderType;

transformPrimitive(value: unknown, _type: BuiltinType) {
transformPrimitive(value: unknown, _type: BuiltinType, _forArrayField: boolean) {
return value;
}

Expand Down Expand Up @@ -363,7 +363,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
continue;
}

const value = this.transformPrimitive(_value, fieldType);
const value = this.transformPrimitive(_value, fieldType, !!fieldDef.array);

switch (key) {
case 'equals': {
Expand Down Expand Up @@ -437,7 +437,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
}

private buildLiteralFilter(eb: ExpressionBuilder<any, any>, lhs: Expression<any>, type: BuiltinType, rhs: unknown) {
return eb(lhs, '=', rhs !== null && rhs !== undefined ? this.transformPrimitive(rhs, type) : rhs);
return eb(lhs, '=', rhs !== null && rhs !== undefined ? this.transformPrimitive(rhs, type, false) : rhs);
}

private buildStandardFilter(
Expand Down Expand Up @@ -588,7 +588,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
type,
payload,
buildFieldRef(this.schema, model, field, this.options, eb),
(value) => this.transformPrimitive(value, type),
(value) => this.transformPrimitive(value, type, false),
(value) => this.buildNumberFilter(eb, model, table, field, type, value),
);
return this.and(eb, ...conditions);
Expand All @@ -605,7 +605,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
'Boolean',
payload,
sql.ref(`${table}.${field}`),
(value) => this.transformPrimitive(value, 'Boolean'),
(value) => this.transformPrimitive(value, 'Boolean', false),
(value) => this.buildBooleanFilter(eb, table, field, value as BooleanFilter<true>),
true,
['equals', 'not'],
Expand All @@ -624,7 +624,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
'DateTime',
payload,
sql.ref(`${table}.${field}`),
(value) => this.transformPrimitive(value, 'DateTime'),
(value) => this.transformPrimitive(value, 'DateTime', false),
(value) => this.buildDateTimeFilter(eb, table, field, value as DateTimeFilter<true>),
true,
);
Expand All @@ -642,7 +642,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
'Bytes',
payload,
sql.ref(`${table}.${field}`),
(value) => this.transformPrimitive(value, 'Bytes'),
(value) => this.transformPrimitive(value, 'Bytes', false),
(value) => this.buildBytesFilter(eb, table, field, value as BytesFilter<true>),
true,
['equals', 'in', 'notIn', 'not'],
Expand Down Expand Up @@ -793,11 +793,11 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
}

public true(eb: ExpressionBuilder<any, any>): Expression<SqlBool> {
return eb.lit<SqlBool>(this.transformPrimitive(true, 'Boolean') as boolean);
return eb.lit<SqlBool>(this.transformPrimitive(true, 'Boolean', false) as boolean);
}

public false(eb: ExpressionBuilder<any, any>): Expression<SqlBool> {
return eb.lit<SqlBool>(this.transformPrimitive(false, 'Boolean') as boolean);
return eb.lit<SqlBool>(this.transformPrimitive(false, 'Boolean', false) as boolean);
}

public isTrue(expression: Expression<SqlBool>) {
Expand Down
11 changes: 9 additions & 2 deletions packages/runtime/src/client/crud/dialects/postgresql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,20 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends BaseCrudDiale
return 'postgresql' as const;
}

override transformPrimitive(value: unknown, type: BuiltinType): unknown {
override transformPrimitive(value: unknown, type: BuiltinType, forArrayField: boolean): unknown {
if (value === undefined) {
return value;
}

if (Array.isArray(value)) {
return value.map((v) => this.transformPrimitive(v, type));
if (type === 'Json' && !forArrayField) {
// node-pg incorrectly handles array values passed to non-array JSON fields,
// the workaround is to JSON stringify the value
// https://github.com/brianc/node-postgres/issues/374
return JSON.stringify(value);
} else {
return value.map((v) => this.transformPrimitive(v, type, false));
}
} else {
return match(type)
.with('DateTime', () =>
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime/src/client/crud/dialects/sqlite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ export class SqliteCrudDialect<Schema extends SchemaDef> extends BaseCrudDialect
return 'sqlite' as const;
}

override transformPrimitive(value: unknown, type: BuiltinType): unknown {
override transformPrimitive(value: unknown, type: BuiltinType, _forArrayField: boolean): unknown {
if (value === undefined) {
return value;
}

if (Array.isArray(value)) {
return value.map((v) => this.transformPrimitive(v, type));
return value.map((v) => this.transformPrimitive(v, type, false));
} else {
return match(type)
.with('Boolean', () => (value ? 1 : 0))
Expand Down
34 changes: 25 additions & 9 deletions packages/runtime/src/client/crud/operations/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,9 +464,17 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
Array.isArray(value.set)
) {
// deal with nested "set" for scalar lists
createFields[field] = this.dialect.transformPrimitive(value.set, fieldDef.type as BuiltinType);
createFields[field] = this.dialect.transformPrimitive(
value.set,
fieldDef.type as BuiltinType,
true,
);
} else {
createFields[field] = this.dialect.transformPrimitive(value, fieldDef.type as BuiltinType);
createFields[field] = this.dialect.transformPrimitive(
value,
fieldDef.type as BuiltinType,
!!fieldDef.array,
);
}
} else {
const subM2M = getManyToManyRelation(this.schema, model, field);
Expand Down Expand Up @@ -788,7 +796,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
for (const [name, value] of Object.entries(item)) {
const fieldDef = this.requireField(model, name);
invariant(!fieldDef.relation, 'createMany does not support relations');
newItem[name] = this.dialect.transformPrimitive(value, fieldDef.type as BuiltinType);
newItem[name] = this.dialect.transformPrimitive(value, fieldDef.type as BuiltinType, !!fieldDef.array);
}
if (fromRelation) {
for (const { fk, pk } of relationKeyPairs) {
Expand Down Expand Up @@ -831,7 +839,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
}
} else if (fields[field]?.updatedAt) {
// TODO: should this work at kysely level instead?
values[field] = this.dialect.transformPrimitive(new Date(), 'DateTime');
values[field] = this.dialect.transformPrimitive(new Date(), 'DateTime', false);
}
}
}
Expand Down Expand Up @@ -934,7 +942,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
if (finalData === data) {
finalData = clone(data);
}
finalData[fieldName] = this.dialect.transformPrimitive(new Date(), 'DateTime');
finalData[fieldName] = this.dialect.transformPrimitive(new Date(), 'DateTime', false);
}
}

Expand Down Expand Up @@ -972,7 +980,11 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
continue;
}

updateFields[field] = this.dialect.transformPrimitive(finalData[field], fieldDef.type as BuiltinType);
updateFields[field] = this.dialect.transformPrimitive(
finalData[field],
fieldDef.type as BuiltinType,
!!fieldDef.array,
);
} else {
if (!allowRelationUpdate) {
throw new QueryError(`Relation update not allowed for field "${field}"`);
Expand Down Expand Up @@ -1054,7 +1066,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
);

const key = Object.keys(payload)[0];
const value = this.dialect.transformPrimitive(payload[key!], fieldDef.type as BuiltinType);
const value = this.dialect.transformPrimitive(payload[key!], fieldDef.type as BuiltinType, false);
const eb = expressionBuilder<any, any>();
const fieldRef = buildFieldRef(this.schema, model, field, this.options, eb);

Expand All @@ -1077,7 +1089,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
) {
invariant(Object.keys(payload).length === 1, 'Only one of "set", "push" can be provided');
const key = Object.keys(payload)[0];
const value = this.dialect.transformPrimitive(payload[key!], fieldDef.type as BuiltinType);
const value = this.dialect.transformPrimitive(payload[key!], fieldDef.type as BuiltinType, true);
const eb = expressionBuilder<any, any>();
const fieldRef = buildFieldRef(this.schema, model, field, this.options, eb);

Expand Down Expand Up @@ -1125,7 +1137,11 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
if (isRelationField(this.schema, model, field)) {
continue;
}
updateFields[field] = this.dialect.transformPrimitive(data[field], fieldDef.type as BuiltinType);
updateFields[field] = this.dialect.transformPrimitive(
data[field],
fieldDef.type as BuiltinType,
!!fieldDef.array,
);
}

let query = kysely.updateTable(model).set(updateFields);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ export class ExpressionTransformer<Schema extends SchemaDef> {
}

private transformValue(value: unknown, type: BuiltinType) {
return ValueNode.create(this.dialect.transformPrimitive(value, type) ?? null);
return ValueNode.create(this.dialect.transformPrimitive(value, type, false) ?? null);
}

@expr('unary')
Expand Down
8 changes: 6 additions & 2 deletions packages/runtime/src/plugins/policy/policy-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,16 @@ export class PolicyHandler<Schema extends SchemaDef> extends OperationNodeTransf
invariant(item.kind === 'ValueNode', 'expecting a ValueNode');
result.push({
node: ValueNode.create(
this.dialect.transformPrimitive((item as ValueNode).value, fieldDef.type as BuiltinType),
this.dialect.transformPrimitive(
(item as ValueNode).value,
fieldDef.type as BuiltinType,
!!fieldDef.array,
),
),
raw: (item as ValueNode).value,
});
} else {
const value = this.dialect.transformPrimitive(item, fieldDef.type as BuiltinType);
const value = this.dialect.transformPrimitive(item, fieldDef.type as BuiltinType, !!fieldDef.array);
if (Array.isArray(value)) {
result.push({
node: RawNode.createWithSql(this.dialect.buildArrayLiteralSQL(value)),
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime/src/plugins/policy/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ import type { SchemaDef } from '../../schema';
* Creates a `true` value node.
*/
export function trueNode<Schema extends SchemaDef>(dialect: BaseCrudDialect<Schema>) {
return ValueNode.createImmediate(dialect.transformPrimitive(true, 'Boolean'));
return ValueNode.createImmediate(dialect.transformPrimitive(true, 'Boolean', false));
}

/**
* Creates a `false` value node.
*/
export function falseNode<Schema extends SchemaDef>(dialect: BaseCrudDialect<Schema>) {
return ValueNode.createImmediate(dialect.transformPrimitive(false, 'Boolean'));
return ValueNode.createImmediate(dialect.transformPrimitive(false, 'Boolean', false));
}

/**
Expand Down
70 changes: 65 additions & 5 deletions packages/runtime/test/client-api/type-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ describe.each(['sqlite', 'postgresql'] as const)('zmodel type coverage tests', (
`
model Foo {
id String @id @default(cuid())

String String
Int Int
BigInt BigInt
Expand All @@ -36,8 +35,6 @@ describe.each(['sqlite', 'postgresql'] as const)('zmodel type coverage tests', (
Boolean Boolean
Bytes Bytes
Json Json

@@allow('all', true)
}
`,
{ provider, dbName: PG_DB_NAME },
Expand All @@ -50,6 +47,42 @@ describe.each(['sqlite', 'postgresql'] as const)('zmodel type coverage tests', (
}
});

it('supports all types - default values', async () => {
let db: any;
try {
db = await createTestClient(
`
model Foo {
id String @id @default(cuid())
String String @default("default")
Int Int @default(100)
BigInt BigInt @default(9007199254740991)
DateTime DateTime @default("2021-01-01T00:00:00.000Z")
Float Float @default(1.23)
Decimal Decimal @default(1.2345)
Boolean Boolean @default(true)
Json Json @default("{\\"foo\\":\\"bar\\"}")
}
`,
{ provider, dbName: PG_DB_NAME },
);

await db.foo.create({ data: { id: '1' } });
await expect(db.foo.findUnique({ where: { id: '1' } })).resolves.toMatchObject({
String: 'default',
Int: 100,
BigInt: BigInt(9007199254740991),
DateTime: expect.any(Date),
Float: 1.23,
Decimal: new Decimal(1.2345),
Boolean: true,
Json: { foo: 'bar' },
});
} finally {
await db?.$disconnect();
}
});

it('supports all types - array', async () => {
if (provider === 'sqlite') {
return;
Expand All @@ -66,7 +99,7 @@ describe.each(['sqlite', 'postgresql'] as const)('zmodel type coverage tests', (
Decimal: [new Decimal(1.2345)],
Boolean: [true],
Bytes: [new Uint8Array([1, 2, 3, 4])],
Json: [{ foo: 'bar' }],
Json: [{ hello: 'world' }],
};

let db: any;
Expand All @@ -85,8 +118,35 @@ describe.each(['sqlite', 'postgresql'] as const)('zmodel type coverage tests', (
Boolean Boolean[]
Bytes Bytes[]
Json Json[]
}
`,
{ provider, dbName: PG_DB_NAME },
);

@@allow('all', true)
await db.foo.create({ data });
await expect(db.foo.findUnique({ where: { id: '1' } })).resolves.toMatchObject(data);
} finally {
await db?.$disconnect();
}
});

it('supports all types - array for plain json field', async () => {
if (provider === 'sqlite') {
return;
}

const data = {
id: '1',
Json: [{ hello: 'world' }],
};

let db: any;
try {
db = await createTestClient(
`
model Foo {
id String @id @default(cuid())
Json Json
}
`,
{ provider, dbName: PG_DB_NAME },
Expand Down