diff --git a/drizzle-orm/src/sqlite-core/columns/blob.ts b/drizzle-orm/src/sqlite-core/columns/blob.ts index 3a095a784..c75cf8bad 100644 --- a/drizzle-orm/src/sqlite-core/columns/blob.ts +++ b/drizzle-orm/src/sqlite-core/columns/blob.ts @@ -1,10 +1,54 @@ import type { ColumnBaseConfig, ColumnHKTBase } from '~/column'; import type { ColumnBuilderBaseConfig, ColumnBuilderHKTBase, MakeColumnConfig } from '~/column-builder'; import type { AnySQLiteTable } from '~/sqlite-core/table'; -import type { Assume } from '~/utils'; +import type { Assume, Equal } from '~/utils'; import { SQLiteColumn, SQLiteColumnBuilder } from './common'; -type BlobMode = 'buffer' | 'json'; +type BlobMode = 'buffer' | 'json' | 'bigint'; + +export interface SQLiteBigIntBuilderHKT extends ColumnBuilderHKTBase { + _type: SQLiteBigIntBuilder>; + _columnHKT: SQLiteBigIntHKT; +} + +export interface SQLiteBigIntHKT extends ColumnHKTBase { + _type: SQLiteBigInt>; +} + +export type SQLiteBigIntBuilderInitial = SQLiteBigIntBuilder<{ + name: TName; + data: bigint; + driverParam: Buffer; + notNull: false; + hasDefault: false; +}>; + +export class SQLiteBigIntBuilder + extends SQLiteColumnBuilder +{ + /** @internal */ + override build( + table: AnySQLiteTable<{ name: TTableName }>, + ): SQLiteBigInt> { + return new SQLiteBigInt>(table, this.config); + } +} + +export class SQLiteBigInt extends SQLiteColumn { + declare protected $sqliteColumnBrand: 'SQLiteBigInt'; + + getSQLType(): string { + return 'blob'; + } + + override mapFromDriverValue(value: Buffer): bigint { + return BigInt(value.toString()); + } + + override mapToDriverValue(value: bigint): Buffer { + return Buffer.from(value.toString()); + } +} export interface SQLiteBlobJsonBuilderHKT extends ColumnBuilderHKTBase { _type: SQLiteBlobJsonBuilder>; @@ -88,13 +132,18 @@ export interface BlobConfig { mode: TMode; } -export function blob( +export function blob( name: TName, config?: BlobConfig, -): TMode extends 'buffer' ? SQLiteBlobBufferBuilderInitial : SQLiteBlobJsonBuilderInitial; +): Equal extends true ? SQLiteBigIntBuilderInitial + : Equal extends true ? SQLiteBlobBufferBuilderInitial + : SQLiteBlobJsonBuilderInitial; export function blob(name: string, config?: BlobConfig) { if (config?.mode === 'json') { return new SQLiteBlobJsonBuilder(name); } + if (config?.mode === 'bigint') { + return new SQLiteBigIntBuilder(name); + } return new SQLiteBlobBufferBuilder(name); } diff --git a/drizzle-zod/src/index.ts b/drizzle-zod/src/index.ts index 703b99ad2..b5a982144 100644 --- a/drizzle-zod/src/index.ts +++ b/drizzle-zod/src/index.ts @@ -69,6 +69,7 @@ import { PgVarchar, } from 'drizzle-orm/pg-core'; import { + SQLiteBigInt, SQLiteBlobJson, SQLiteCustomColumn, SQLiteInteger, @@ -287,7 +288,10 @@ function mapColumnToSchema(column: AnyColumn): z.ZodTypeAny { || column instanceof MySqlReal || column instanceof MySqlYear ) { type = z.number(); - } else if (column instanceof PgBigInt64 || column instanceof PgBigSerial64 || column instanceof MySqlBigInt64) { + } else if ( + column instanceof PgBigInt64 || column instanceof PgBigSerial64 || column instanceof MySqlBigInt64 + || column instanceof SQLiteBigInt + ) { type = z.bigint(); } else if (column instanceof PgBoolean || column instanceof MySqlBoolean) { type = z.boolean(); diff --git a/drizzle-zod/tests/sqlite.test.ts b/drizzle-zod/tests/sqlite.test.ts index 27865438e..0589379c8 100644 --- a/drizzle-zod/tests/sqlite.test.ts +++ b/drizzle-zod/tests/sqlite.test.ts @@ -11,6 +11,7 @@ const blobJsonSchema = z.object({ const users = sqliteTable('users', { id: integer('id').primaryKey(), blobJson: blob('blob', { mode: 'json' }).$type>().notNull(), + blobBigInt: blob('blob', { mode: 'bigint' }).notNull(), numeric: numeric('numeric').notNull(), createdAt: integer('created_at', { mode: 'timestamp' }).notNull(), createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }).notNull(), @@ -45,6 +46,7 @@ test('users insert schema', (t) => { const expected = z.object({ id: z.number().positive().optional(), blobJson: blobJsonSchema, + blobBigInt: z.bigint(), numeric: z.string(), createdAt: z.date(), createdAtMs: z.date(), @@ -62,6 +64,7 @@ test('users insert schema w/ defaults', (t) => { const expected = z.object({ id: z.number().optional(), blobJson: jsonSchema, + blobBigInt: z.bigint(), numeric: z.string(), createdAt: z.date(), createdAtMs: z.date(), @@ -98,6 +101,7 @@ test('users select schema', (t) => { const expected = z.object({ id: z.number(), blobJson: jsonSchema, + blobBigInt: z.bigint(), numeric: z.string(), createdAt: z.date(), createdAtMs: z.date(), @@ -115,6 +119,7 @@ test('users select schema w/ defaults', (t) => { const expected = z.object({ id: z.number(), blobJson: jsonSchema, + blobBigInt: z.bigint(), numeric: z.string(), createdAt: z.date(), createdAtMs: z.date(), diff --git a/integration-tests/tests/better-sqlite.test.ts b/integration-tests/tests/better-sqlite.test.ts index 33afe0706..199f33d2a 100644 --- a/integration-tests/tests/better-sqlite.test.ts +++ b/integration-tests/tests/better-sqlite.test.ts @@ -79,6 +79,12 @@ const pkExampleTable = sqliteTable('pk_example', { compositePk: primaryKey(table.id, table.name), })); +const bigIntExample = sqliteTable('big_int_example', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + bigInt: blob('big_int', { mode: 'bigint' }).notNull(), +}); + interface Context { db: BetterSQLite3Database; client: Database.Database; @@ -113,6 +119,7 @@ test.beforeEach((t) => { ctx.db.run(sql`drop table if exists ${coursesTable}`); ctx.db.run(sql`drop table if exists ${courseCategoriesTable}`); ctx.db.run(sql`drop table if exists ${orders}`); + ctx.db.run(sql`drop table if exists ${bigIntExample}`); ctx.db.run(sql`drop table if exists ${pkExampleTable}`); ctx.db.run(sql` @@ -167,6 +174,32 @@ test.beforeEach((t) => { primary key (id, name) ) `); + ctx.db.run(sql` + create table ${bigIntExample} ( + id integer primary key, + name text not null, + big_int blob not null + ) + `); +}); + +test.serial('insert bigint values', async (t) => { + const { db } = t.context; + + await db.insert(bigIntExample).values({ name: 'one', bigInt: BigInt('0') }).run(); + await db.insert(bigIntExample).values({ name: 'two', bigInt: BigInt('127') }).run(); + await db.insert(bigIntExample).values({ name: 'three', bigInt: BigInt('32767') }).run(); + await db.insert(bigIntExample).values({ name: 'four', bigInt: BigInt('1234567890') }).run(); + await db.insert(bigIntExample).values({ name: 'five', bigInt: BigInt('12345678900987654321') }).run(); + + const result = await db.select().from(bigIntExample).all(); + t.deepEqual(result, [ + { id: 1, name: 'one', bigInt: BigInt('0') }, + { id: 2, name: 'two', bigInt: BigInt('127') }, + { id: 3, name: 'three', bigInt: BigInt('32767') }, + { id: 4, name: 'four', bigInt: BigInt('1234567890') }, + { id: 5, name: 'five', bigInt: BigInt('12345678900987654321') }, + ]); }); test.serial('select all fields', (t) => { diff --git a/integration-tests/tests/libsql.test.ts b/integration-tests/tests/libsql.test.ts index b10ce2138..bc6f7073c 100644 --- a/integration-tests/tests/libsql.test.ts +++ b/integration-tests/tests/libsql.test.ts @@ -97,6 +97,12 @@ const pkExampleTable = sqliteTable('pk_example', { compositePk: primaryKey(table.id, table.name), })); +const bigIntExample = sqliteTable('big_int_example', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + bigInt: blob('big_int', { mode: 'bigint' }).notNull(), +}); + test.before(async (t) => { const ctx = t.context; const url = process.env['LIBSQL_URL']; @@ -139,6 +145,7 @@ test.beforeEach(async (t) => { await ctx.db.run(sql`drop table if exists ${coursesTable}`); await ctx.db.run(sql`drop table if exists ${courseCategoriesTable}`); await ctx.db.run(sql`drop table if exists ${orders}`); + await ctx.db.run(sql`drop table if exists ${bigIntExample}`); await ctx.db.run(sql`drop table if exists ${pkExampleTable}`); await ctx.db.run(sql` @@ -195,6 +202,32 @@ test.beforeEach(async (t) => { primary key (id, name) ) `); + await ctx.db.run(sql` + create table ${bigIntExample} ( + id integer primary key, + name text not null, + big_int blob not null + ) + `); +}); + +test.serial('insert bigint values', async (t) => { + const { db } = t.context; + + await db.insert(bigIntExample).values({ name: 'one', bigInt: BigInt('0') }).run(); + await db.insert(bigIntExample).values({ name: 'two', bigInt: BigInt('127') }).run(); + await db.insert(bigIntExample).values({ name: 'three', bigInt: BigInt('32767') }).run(); + await db.insert(bigIntExample).values({ name: 'four', bigInt: BigInt('1234567890') }).run(); + await db.insert(bigIntExample).values({ name: 'five', bigInt: BigInt('12345678900987654321') }).run(); + + const result = await db.select().from(bigIntExample).all(); + t.deepEqual(result, [ + { id: 1, name: 'one', bigInt: BigInt('0') }, + { id: 2, name: 'two', bigInt: BigInt('127') }, + { id: 3, name: 'three', bigInt: BigInt('32767') }, + { id: 4, name: 'four', bigInt: BigInt('1234567890') }, + { id: 5, name: 'five', bigInt: BigInt('12345678900987654321') }, + ]); }); test.serial('select all fields', async (t) => { diff --git a/integration-tests/tests/sql.js.test.ts b/integration-tests/tests/sql.js.test.ts index ccc539e12..bd5d4613c 100644 --- a/integration-tests/tests/sql.js.test.ts +++ b/integration-tests/tests/sql.js.test.ts @@ -81,6 +81,12 @@ const pkExampleTable = sqliteTable('pk_example', { compositePk: primaryKey(table.id, table.name), })); +const bigIntExample = sqliteTable('big_int_example', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + bigInt: blob('big_int', { mode: 'bigint' }).notNull(), +}); + interface Context { db: SQLJsDatabase; client: Database; @@ -110,6 +116,7 @@ test.beforeEach((t) => { ctx.db.run(sql`drop table if exists ${coursesTable}`); ctx.db.run(sql`drop table if exists ${courseCategoriesTable}`); ctx.db.run(sql`drop table if exists ${orders}`); + ctx.db.run(sql`drop table if exists ${bigIntExample}`); ctx.db.run(sql`drop table if exists ${pkExampleTable}`); ctx.db.run(sql` @@ -164,6 +171,32 @@ test.beforeEach((t) => { primary key (id, name) ) `); + ctx.db.run(sql` + create table ${bigIntExample} ( + id integer primary key, + name text not null, + big_int blob not null + ) + `); +}); + +test.serial('insert bigint values', async (t) => { + const { db } = t.context; + + await db.insert(bigIntExample).values({ name: 'one', bigInt: BigInt('0') }).run(); + await db.insert(bigIntExample).values({ name: 'two', bigInt: BigInt('127') }).run(); + await db.insert(bigIntExample).values({ name: 'three', bigInt: BigInt('32767') }).run(); + await db.insert(bigIntExample).values({ name: 'four', bigInt: BigInt('1234567890') }).run(); + await db.insert(bigIntExample).values({ name: 'five', bigInt: BigInt('12345678900987654321') }).run(); + + const result = await db.select().from(bigIntExample).all(); + t.deepEqual(result, [ + { id: 1, name: 'one', bigInt: BigInt('0') }, + { id: 2, name: 'two', bigInt: BigInt('127') }, + { id: 3, name: 'three', bigInt: BigInt('32767') }, + { id: 4, name: 'four', bigInt: BigInt('1234567890') }, + { id: 5, name: 'five', bigInt: BigInt('12345678900987654321') }, + ]); }); test.serial('select all fields', (t) => { diff --git a/integration-tests/tests/sqlite-proxy.test.ts b/integration-tests/tests/sqlite-proxy.test.ts index e8514a8b5..b799fe3c2 100644 --- a/integration-tests/tests/sqlite-proxy.test.ts +++ b/integration-tests/tests/sqlite-proxy.test.ts @@ -83,6 +83,12 @@ const pkExampleTable = sqliteTable('pk_example', { compositePk: primaryKey(table.id, table.name), })); +const bigIntExample = sqliteTable('big_int_example', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + bigInt: blob('big_int', { mode: 'bigint' }).notNull(), +}); + interface Context { db: SqliteRemoteDatabase; client: Database.Database; @@ -119,6 +125,7 @@ test.beforeEach(async (t) => { const ctx = t.context; await ctx.db.run(sql`drop table if exists ${usersTable}`); await ctx.db.run(sql`drop table if exists ${pkExampleTable}`); + await ctx.db.run(sql`drop table if exists ${bigIntExample}`); await ctx.db.run(sql` create table ${usersTable} ( @@ -137,6 +144,32 @@ test.beforeEach(async (t) => { primary key (id, name) ) `); + await ctx.db.run(sql` + create table ${bigIntExample} ( + id integer primary key, + name text not null, + big_int blob not null + ) + `); +}); + +test.serial('insert bigint values', async (t) => { + const { db } = t.context; + + await db.insert(bigIntExample).values({ name: 'one', bigInt: BigInt('0') }).run(); + await db.insert(bigIntExample).values({ name: 'two', bigInt: BigInt('127') }).run(); + await db.insert(bigIntExample).values({ name: 'three', bigInt: BigInt('32767') }).run(); + await db.insert(bigIntExample).values({ name: 'four', bigInt: BigInt('1234567890') }).run(); + await db.insert(bigIntExample).values({ name: 'five', bigInt: BigInt('12345678900987654321') }).run(); + + const result = await db.select().from(bigIntExample).all(); + t.deepEqual(result, [ + { id: 1, name: 'one', bigInt: BigInt('0') }, + { id: 2, name: 'two', bigInt: BigInt('127') }, + { id: 3, name: 'three', bigInt: BigInt('32767') }, + { id: 4, name: 'four', bigInt: BigInt('1234567890') }, + { id: 5, name: 'five', bigInt: BigInt('12345678900987654321') }, + ]); }); test.serial('select all fields', async (t) => {