diff --git a/drizzle-orm/src/mysql-core/columns/datetime.ts b/drizzle-orm/src/mysql-core/columns/datetime.ts index ea5b917b92..8c6b27028d 100644 --- a/drizzle-orm/src/mysql-core/columns/datetime.ts +++ b/drizzle-orm/src/mysql-core/columns/datetime.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import type { Placeholder, SQL } from '~/sql/sql.ts'; import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumn, MySqlColumnBuilder } from './common.ts'; @@ -53,8 +54,9 @@ export class MySqlDateTime> return `datetime${precision}`; } - override mapToDriverValue(value: Date): unknown { - return value.toISOString().replace('T', ' ').replace('Z', ''); + override mapToDriverValue(value: Date | SQL | Placeholder): unknown { + // eslint-disable-next-line no-instanceof/no-instanceof + return value instanceof Date ? value.toISOString().replace('T', ' ').replace('Z', '') : value; } override mapFromDriverValue(value: string): Date { diff --git a/drizzle-orm/src/mysql-core/columns/json.ts b/drizzle-orm/src/mysql-core/columns/json.ts index 12d11bd4a5..a05ce31dbb 100644 --- a/drizzle-orm/src/mysql-core/columns/json.ts +++ b/drizzle-orm/src/mysql-core/columns/json.ts @@ -1,7 +1,8 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; import type { ColumnBaseConfig } from '~/column.ts'; -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import { Placeholder, SQL } from '~/sql/sql.ts'; import { MySqlColumn, MySqlColumnBuilder } from './common.ts'; export type MySqlJsonBuilderInitial = MySqlJsonBuilder<{ @@ -35,8 +36,8 @@ export class MySqlJson> extends return 'json'; } - override mapToDriverValue(value: T['data']): string { - return JSON.stringify(value); + override mapToDriverValue(value: T['data'] | SQL | Placeholder): string | SQL | Placeholder { + return is(value, SQL) || is(value, Placeholder) ? value : JSON.stringify(value); } } diff --git a/drizzle-orm/src/mysql-core/columns/timestamp.ts b/drizzle-orm/src/mysql-core/columns/timestamp.ts index 2ccc2925f4..de3f4480ea 100644 --- a/drizzle-orm/src/mysql-core/columns/timestamp.ts +++ b/drizzle-orm/src/mysql-core/columns/timestamp.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import type { Placeholder, SQL } from '~/sql/sql.ts'; import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { MySqlDateBaseColumn, MySqlDateColumnBaseBuilder } from './date.common.ts'; @@ -51,8 +52,9 @@ export class MySqlTimestamp return new Date(value + '+0000'); } - override mapToDriverValue(value: Date): string { - return value.toISOString().slice(0, -1).replace('T', ' '); + override mapToDriverValue(value: Date | SQL | Placeholder): string | SQL | Placeholder { + // eslint-disable-next-line no-instanceof/no-instanceof + return value instanceof Date ? value.toISOString().slice(0, -1).replace('T', ' ') : value; } } diff --git a/drizzle-orm/src/pg-core/columns/date.ts b/drizzle-orm/src/pg-core/columns/date.ts index 54f3d1a0ea..4e2863a22d 100644 --- a/drizzle-orm/src/pg-core/columns/date.ts +++ b/drizzle-orm/src/pg-core/columns/date.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import type { Placeholder, SQL } from '~/sql/sql.ts'; import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn } from './common.ts'; import { PgDateColumnBaseBuilder } from './date.common.ts'; @@ -41,8 +42,9 @@ export class PgDate> extends PgColu return new Date(value); } - override mapToDriverValue(value: Date): string { - return value.toISOString(); + override mapToDriverValue(value: Date | SQL | Placeholder): string | SQL | Placeholder { + // eslint-disable-next-line no-instanceof/no-instanceof + return value instanceof Date ? value.toISOString() : value; } } diff --git a/drizzle-orm/src/pg-core/columns/json.ts b/drizzle-orm/src/pg-core/columns/json.ts index deed256b91..687b2f8bdf 100644 --- a/drizzle-orm/src/pg-core/columns/json.ts +++ b/drizzle-orm/src/pg-core/columns/json.ts @@ -1,7 +1,8 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; import type { ColumnBaseConfig } from '~/column.ts'; -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import { Placeholder, SQL } from '~/sql/sql.ts'; import { PgColumn, PgColumnBuilder } from './common.ts'; export type PgJsonBuilderInitial = PgJsonBuilder<{ @@ -41,8 +42,8 @@ export class PgJson> extends PgColu return 'json'; } - override mapToDriverValue(value: T['data']): string { - return JSON.stringify(value); + override mapToDriverValue(value: T['data'] | SQL | Placeholder): string | SQL | Placeholder { + return is(value, SQL) || is(value, Placeholder) ? value : JSON.stringify(value); } override mapFromDriverValue(value: T['data'] | string): T['data'] { diff --git a/drizzle-orm/src/pg-core/columns/jsonb.ts b/drizzle-orm/src/pg-core/columns/jsonb.ts index 14af7c9e9a..949b2a6ef3 100644 --- a/drizzle-orm/src/pg-core/columns/jsonb.ts +++ b/drizzle-orm/src/pg-core/columns/jsonb.ts @@ -1,7 +1,8 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; import type { ColumnBaseConfig } from '~/column.ts'; -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import { Placeholder, SQL } from '~/sql/sql.ts'; import { PgColumn, PgColumnBuilder } from './common.ts'; export type PgJsonbBuilderInitial = PgJsonbBuilder<{ @@ -39,8 +40,8 @@ export class PgJsonb> extends PgCo return 'jsonb'; } - override mapToDriverValue(value: T['data']): string { - return JSON.stringify(value); + override mapToDriverValue(value: T['data'] | SQL | Placeholder): string | SQL | Placeholder { + return is(value, SQL) || is(value, Placeholder) ? value : JSON.stringify(value); } override mapFromDriverValue(value: T['data'] | string): T['data'] { diff --git a/drizzle-orm/src/pg-core/columns/line.ts b/drizzle-orm/src/pg-core/columns/line.ts index 9378d1aa16..f4f67070b5 100644 --- a/drizzle-orm/src/pg-core/columns/line.ts +++ b/drizzle-orm/src/pg-core/columns/line.ts @@ -1,8 +1,8 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; import type { ColumnBaseConfig } from '~/column.ts'; -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; - +import { Placeholder, SQL } from '~/sql/sql.ts'; import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from './common.ts'; @@ -45,8 +45,8 @@ export class PgLineTuple> extends return [Number.parseFloat(a!), Number.parseFloat(b!), Number.parseFloat(c!)]; } - override mapToDriverValue(value: [number, number, number]): string { - return `{${value[0]},${value[1]},${value[2]}}`; + override mapToDriverValue(value: [number, number, number] | SQL | Placeholder): string | SQL | Placeholder { + return Array.isArray(value) ? `{${value[0]},${value[1]},${value[2]}}` : value; } } @@ -89,8 +89,12 @@ export class PgLineABC> extends return { a: Number.parseFloat(a!), b: Number.parseFloat(b!), c: Number.parseFloat(c!) }; } - override mapToDriverValue(value: { a: number; b: number; c: number }): string { - return `{${value.a},${value.b},${value.c}}`; + override mapToDriverValue( + value: { a: number; b: number; c: number } | SQL | Placeholder, + ): string | SQL | Placeholder { + return is(value, SQL) || is(value, Placeholder) + ? value + : `{${(value as any).a},${(value as any).b},${(value as any).c}}`; } } diff --git a/drizzle-orm/src/pg-core/columns/point.ts b/drizzle-orm/src/pg-core/columns/point.ts index c204aedeae..4fb99da300 100644 --- a/drizzle-orm/src/pg-core/columns/point.ts +++ b/drizzle-orm/src/pg-core/columns/point.ts @@ -1,8 +1,8 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; import type { ColumnBaseConfig } from '~/column.ts'; -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; - +import { Placeholder, SQL } from '~/sql/sql.ts'; import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from './common.ts'; @@ -50,8 +50,8 @@ export class PgPointTuple> e return [value.x, value.y]; } - override mapToDriverValue(value: [number, number]): string { - return `(${value[0]},${value[1]})`; + override mapToDriverValue(value: [number, number] | SQL | Placeholder): string | SQL | Placeholder { + return Array.isArray(value) ? `(${value[0]},${value[1]})` : value; } } @@ -99,8 +99,8 @@ export class PgPointObject> return value; } - override mapToDriverValue(value: { x: number; y: number }): string { - return `(${value.x},${value.y})`; + override mapToDriverValue(value: { x: number; y: number } | SQL | Placeholder): string | SQL | Placeholder { + return is(value, SQL) || is(value, Placeholder) ? value : `(${(value as any).x},${(value as any).y})`; } } diff --git a/drizzle-orm/src/pg-core/columns/postgis_extension/geometry.ts b/drizzle-orm/src/pg-core/columns/postgis_extension/geometry.ts index 93632d31c9..e5f80b2382 100644 --- a/drizzle-orm/src/pg-core/columns/postgis_extension/geometry.ts +++ b/drizzle-orm/src/pg-core/columns/postgis_extension/geometry.ts @@ -1,8 +1,8 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; import type { ColumnBaseConfig } from '~/column.ts'; -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; - +import { Placeholder, SQL } from '~/sql/sql.ts'; import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from '../common.ts'; import { parseEWKB } from './utils.ts'; @@ -45,8 +45,8 @@ export class PgGeometry> exten return parseEWKB(value); } - override mapToDriverValue(value: [number, number]): string { - return `point(${value[0]} ${value[1]})`; + override mapToDriverValue(value: [number, number] | SQL | Placeholder): string | SQL | Placeholder { + return Array.isArray(value) ? `point(${value[0]} ${value[1]})` : value; } } @@ -91,8 +91,8 @@ export class PgGeometryObject> exte return `timestamp${precision}${this.withTimezone ? ' with time zone' : ''}`; } - override mapFromDriverValue = (value: string): Date | null => { + override mapFromDriverValue = (value: string): Date => { return new Date(this.withTimezone ? value : value + '+0000'); }; - override mapToDriverValue = (value: Date): string => { - return value.toISOString(); + override mapToDriverValue = (value: Date | SQL | Placeholder): string | SQL | Placeholder => { + // eslint-disable-next-line no-instanceof/no-instanceof + return value instanceof Date ? value.toISOString() : value; }; } diff --git a/drizzle-orm/src/pg-core/columns/vector_extension/halfvec.ts b/drizzle-orm/src/pg-core/columns/vector_extension/halfvec.ts index 5f52f5f166..fba3cf0513 100644 --- a/drizzle-orm/src/pg-core/columns/vector_extension/halfvec.ts +++ b/drizzle-orm/src/pg-core/columns/vector_extension/halfvec.ts @@ -1,7 +1,8 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; import type { ColumnBaseConfig } from '~/column.ts'; -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import { Placeholder, SQL } from '~/sql/sql.ts'; import { getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from '../common.ts'; @@ -52,7 +53,7 @@ export class PgHalfVector & } override mapToDriverValue(value: unknown): unknown { - return JSON.stringify(value); + return is(value, SQL) || is(value, Placeholder) ? value : JSON.stringify(value); } override mapFromDriverValue(value: string): unknown { diff --git a/drizzle-orm/src/pg-core/columns/vector_extension/vector.ts b/drizzle-orm/src/pg-core/columns/vector_extension/vector.ts index f36a9b1caf..9b89007565 100644 --- a/drizzle-orm/src/pg-core/columns/vector_extension/vector.ts +++ b/drizzle-orm/src/pg-core/columns/vector_extension/vector.ts @@ -1,7 +1,8 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; import type { ColumnBaseConfig } from '~/column.ts'; -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import { Placeholder, SQL } from '~/sql/sql.ts'; import { getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from '../common.ts'; @@ -52,7 +53,7 @@ export class PgVector & { dimens } override mapToDriverValue(value: unknown): unknown { - return JSON.stringify(value); + return is(value, SQL) || is(value, Placeholder) ? value : JSON.stringify(value); } override mapFromDriverValue(value: string): unknown { diff --git a/drizzle-orm/src/sqlite-core/columns/blob.ts b/drizzle-orm/src/sqlite-core/columns/blob.ts index dfd2795a40..97fabd4acf 100644 --- a/drizzle-orm/src/sqlite-core/columns/blob.ts +++ b/drizzle-orm/src/sqlite-core/columns/blob.ts @@ -1,6 +1,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; import type { ColumnBaseConfig } from '~/column.ts'; -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; +import { Placeholder, SQL } from '~/sql/sql.ts'; import type { AnySQLiteTable } from '~/sqlite-core/table.ts'; import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { SQLiteColumn, SQLiteColumnBuilder } from './common.ts'; @@ -55,8 +56,8 @@ export class SQLiteBigInt> return BigInt(String.fromCodePoint(...value)); } - override mapToDriverValue(value: bigint): Buffer { - return Buffer.from(value.toString()); + override mapToDriverValue(value: bigint | SQL | Placeholder): Buffer | SQL | Placeholder { + return typeof value === 'bigint' ? Buffer.from(value.toString()) : value; } } @@ -111,8 +112,8 @@ export class SQLiteBlobJson return JSON.parse(String.fromCodePoint(...value)); } - override mapToDriverValue(value: T['data']): Buffer { - return Buffer.from(JSON.stringify(value)); + override mapToDriverValue(value: T['data'] | SQL | Placeholder): Buffer | SQL | Placeholder { + return is(value, SQL) || is(value, Placeholder) ? value : Buffer.from(JSON.stringify(value)); } } diff --git a/drizzle-orm/src/sqlite-core/columns/integer.ts b/drizzle-orm/src/sqlite-core/columns/integer.ts index 13d52c0eaf..8b28cb806b 100644 --- a/drizzle-orm/src/sqlite-core/columns/integer.ts +++ b/drizzle-orm/src/sqlite-core/columns/integer.ts @@ -8,8 +8,8 @@ import type { NotNull, } from '~/column-builder.ts'; import type { ColumnBaseConfig } from '~/column.ts'; -import { entityKind } from '~/entity.ts'; -import { sql } from '~/sql/sql.ts'; +import { entityKind, is } from '~/entity.ts'; +import { Placeholder, SQL, sql } from '~/sql/sql.ts'; import type { OnConflict } from '~/sqlite-core/utils.ts'; import { type Equal, getColumnNameAndConfig, type Or } from '~/utils.ts'; import type { AnySQLiteTable } from '../table.ts'; @@ -147,8 +147,11 @@ export class SQLiteTimestamp return JSON.parse(value); } - override mapToDriverValue(value: T['data']): string { - return JSON.stringify(value); + override mapToDriverValue(value: T['data'] | SQL | Placeholder): string | SQL | Placeholder { + return is(value, SQL) || is(value, Placeholder) ? value : JSON.stringify(value); } } diff --git a/integration-tests/tests/mysql/mysql-common.ts b/integration-tests/tests/mysql/mysql-common.ts index 98e425f9cd..b66532928a 100644 --- a/integration-tests/tests/mysql/mysql-common.ts +++ b/integration-tests/tests/mysql/mysql-common.ts @@ -3220,6 +3220,47 @@ export function tests(driver?: string) { } }); + test('test $onUpdateFn and $onUpdate works with sql value', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + updatedAt: timestamp('updated_at', { + fsp: 6, + }) + .notNull() + .$onUpdate(() => sql`current_timestamp`), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute( + sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` text not null, + \`updated_at\` timestamp not null + ) + `, + ); + + await db.insert(users).values({ + name: 'John', + }); + const insertResp = await db.select({ updatedAt: users.updatedAt }).from(users); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const now = Date.now(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + await db.update(users).set({ + name: 'John', + }); + const updateResp = await db.select({ updatedAt: users.updatedAt }).from(users); + + expect(insertResp[0]?.updatedAt.getTime() ?? 0).lessThan(now); + expect(updateResp[0]?.updatedAt.getTime() ?? 0).greaterThan(now); + }); + // mySchema tests test('mySchema :: select all fields', async (ctx) => { const { db } = ctx.mysql; diff --git a/integration-tests/tests/pg/pg-common.ts b/integration-tests/tests/pg/pg-common.ts index 5e5f4ec729..12710a4cd1 100644 --- a/integration-tests/tests/pg/pg-common.ts +++ b/integration-tests/tests/pg/pg-common.ts @@ -3875,6 +3875,45 @@ export function tests() { } }); + test('test $onUpdateFn and $onUpdate works with sql value', async (ctx) => { + const { db } = ctx.pg; + + const users = pgTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().$onUpdate(() => sql`now()`), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute( + sql` + create table ${users} ( + "id" serial primary key, + "name" text not null, + "updated_at" timestamp(3) + ) + `, + ); + + const insertResp = await db.insert(users).values({ + name: 'John', + }).returning({ + updatedAt: users.updatedAt, + }); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const now = Date.now(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + const updateResp = await db.update(users).set({ + name: 'John', + }).returning({ + updatedAt: users.updatedAt, + }); + + expect(insertResp[0]?.updatedAt.getTime() ?? 0).lessThan(now); + expect(updateResp[0]?.updatedAt.getTime() ?? 0).greaterThan(now); + }); + test('test if method with sql operators', async (ctx) => { const { db } = ctx.pg; diff --git a/integration-tests/tests/sqlite/sqlite-common.ts b/integration-tests/tests/sqlite/sqlite-common.ts index c6d67cee3d..b355ca2461 100644 --- a/integration-tests/tests/sqlite/sqlite-common.ts +++ b/integration-tests/tests/sqlite/sqlite-common.ts @@ -2774,6 +2774,49 @@ export function tests() { } }); + test('test $onUpdateFn and $onUpdate works with sql value', async (ctx) => { + const { db } = ctx.sqlite; + + const users = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + updatedAt: integer('updated_at') + .notNull() + .$onUpdate(() => + sql`(strftime('%s', 'now') * 1000) + (strftime('%f', 'now') - strftime('%S', 'now')) * 1000` + ), + }); + + await db.run(sql`drop table if exists ${users}`); + await db.run( + sql` + create table ${users} ( + \`id\` integer primary key autoincrement, + \`name\` text not null, + \`updated_at\` integer not null + ) + `, + ); + + const insertResp = await db.insert(users).values({ + name: 'John', + }).returning({ + updatedAt: users.updatedAt, + }); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const now = Date.now(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + const updateResp = await db.update(users).set({ + name: 'John', + }).returning({ + updatedAt: users.updatedAt, + }); + + expect(insertResp[0]?.updatedAt ?? 0).lessThan(now); + expect(updateResp[0]?.updatedAt ?? 0).greaterThan(now); + }); + test('$count separate', async (ctx) => { const { db } = ctx.sqlite;