diff --git a/drizzle-kit/build.ts b/drizzle-kit/build.ts
index 701e9c84c..ec7fc76c0 100644
--- a/drizzle-kit/build.ts
+++ b/drizzle-kit/build.ts
@@ -1,3 +1,4 @@
+///
import * as esbuild from 'esbuild';
import { readFileSync, writeFileSync } from 'node:fs';
import * as tsup from 'tsup';
@@ -16,6 +17,7 @@ const driversPackages = [
// sqlite drivers
'@libsql/client',
'better-sqlite3',
+ 'bun:sqlite',
];
esbuild.buildSync({
@@ -82,6 +84,7 @@ const main = async () => {
await tsup.build({
entryPoints: ['./src/index.ts', './src/api.ts'],
outDir: './dist',
+ external: ['bun:sqlite'],
splitting: false,
dts: true,
format: ['cjs', 'esm'],
diff --git a/drizzle-kit/package.json b/drizzle-kit/package.json
index 9d9e1d227..552c14d0e 100644
--- a/drizzle-kit/package.json
+++ b/drizzle-kit/package.json
@@ -74,6 +74,7 @@
"@vercel/postgres": "^0.8.0",
"ava": "^5.1.0",
"better-sqlite3": "^9.4.3",
+ "bun-types": "^0.6.6",
"camelcase": "^7.0.1",
"chalk": "^5.2.0",
"commander": "^12.1.0",
diff --git a/drizzle-orm/src/index.ts b/drizzle-orm/src/index.ts
index bc72260b9..5cabdb0d8 100644
--- a/drizzle-orm/src/index.ts
+++ b/drizzle-orm/src/index.ts
@@ -5,6 +5,7 @@ export * from './entity.ts';
export * from './errors.ts';
export * from './expressions.ts';
export * from './logger.ts';
+export * from './monodriver.ts';
export * from './operations.ts';
export * from './query-promise.ts';
export * from './relations.ts';
diff --git a/drizzle-orm/src/monodriver.ts b/drizzle-orm/src/monodriver.ts
new file mode 100644
index 000000000..b2b82af3f
--- /dev/null
+++ b/drizzle-orm/src/monodriver.ts
@@ -0,0 +1,252 @@
+/* eslint-disable import/extensions */
+import type { RDSDataClientConfig as RDSConfig } from '@aws-sdk/client-rds-data';
+import type { Config as LibsqlConfig } from '@libsql/client';
+import type {
+ HTTPTransactionOptions as NeonHttpConfig,
+ PoolConfig as NeonServerlessConfig,
+} from '@neondatabase/serverless';
+import type { Config as PlanetscaleConfig } from '@planetscale/database';
+import type { Config as TiDBServerlessConfig } from '@tidbcloud/serverless';
+import type { VercelPool } from '@vercel/postgres';
+import type { Options as BetterSQLite3Options } from 'better-sqlite3';
+import type { PoolOptions as Mysql2Config } from 'mysql2';
+import type { PoolConfig as NodePGPoolConfig } from 'pg';
+import type { Options as PostgresJSOptions, PostgresType as PostgresJSPostgresType } from 'postgres';
+import type { AwsDataApiPgDatabase, DrizzleAwsDataApiPgConfig } from './aws-data-api/pg/index.ts';
+import type { BetterSQLite3Database } from './better-sqlite3/index.ts';
+import type { BunSQLiteDatabase } from './bun-sqlite/index.ts';
+import type { DrizzleD1Database } from './d1/index.ts';
+import type { LibSQLDatabase } from './libsql/index.ts';
+import type { MySql2Database, MySql2DrizzleConfig } from './mysql2/index.ts';
+import type { NeonHttpDatabase } from './neon-http/index.ts';
+import type { NeonDatabase } from './neon-serverless/index.ts';
+import type { NodePgDatabase } from './node-postgres/index.ts';
+import type { PlanetScaleDatabase } from './planetscale-serverless/index.ts';
+import type { PostgresJsDatabase } from './postgres-js/index.ts';
+import type { TiDBServerlessDatabase } from './tidb-serverless/index.ts';
+import type { DrizzleConfig } from './utils.ts';
+import type { VercelPgDatabase } from './vercel-postgres/index.ts';
+
+type BunSqliteDatabaseOptions =
+ | number
+ | {
+ /**
+ * Open the database as read-only (no write operations, no create).
+ *
+ * Equivalent to {@link constants.SQLITE_OPEN_READONLY}
+ */
+ readonly?: boolean;
+ /**
+ * Allow creating a new database
+ *
+ * Equivalent to {@link constants.SQLITE_OPEN_CREATE}
+ */
+ create?: boolean;
+ /**
+ * Open the database as read-write
+ *
+ * Equivalent to {@link constants.SQLITE_OPEN_READWRITE}
+ */
+ readwrite?: boolean;
+ };
+
+type BunSqliteDatabaseConfig = {
+ filename?: string;
+ options?: BunSqliteDatabaseOptions;
+};
+
+type BetterSQLite3DatabaseConfig = {
+ filename?: string | Buffer;
+ options?: BetterSQLite3Options;
+};
+
+type MonodriverNeonHttpConfig = {
+ connectionString: string;
+ options?: NeonHttpConfig;
+};
+
+type ClientDrizzleInstanceMap> = {
+ 'node-postgres': NodePgDatabase;
+ 'postgres-js': PostgresJsDatabase;
+ 'neon-serverless': NeonDatabase;
+ 'neon-http': NeonHttpDatabase;
+ 'vercel-postgres': VercelPgDatabase;
+ 'aws-data-api-pg': AwsDataApiPgDatabase;
+ planetscale: PlanetScaleDatabase;
+ mysql2: MySql2Database;
+ 'tidb-serverless': TiDBServerlessDatabase;
+ libsql: LibSQLDatabase;
+ d1: DrizzleD1Database;
+ 'bun-sqlite': BunSQLiteDatabase;
+ 'better-sqlite3': BetterSQLite3Database;
+};
+
+type InitializerParams<
+ TSchema extends Record = Record,
+> =
+ | ({
+ client: 'node-postgres';
+ connection: NodePGPoolConfig;
+ } & DrizzleConfig)
+ | ({
+ client: 'postgres-js';
+ connection: PostgresJSOptions>;
+ } & DrizzleConfig)
+ | ({
+ client: 'neon-serverless';
+ connection: NeonServerlessConfig;
+ } & DrizzleConfig)
+ | ({
+ client: 'neon-http';
+ connection: MonodriverNeonHttpConfig;
+ } & DrizzleConfig)
+ | ({
+ client: 'vercel-postgres';
+ connection: VercelPool;
+ } & DrizzleConfig)
+ | ({
+ client: 'aws-data-api-pg';
+ connection: RDSConfig;
+ } & DrizzleAwsDataApiPgConfig)
+ | ({
+ client: 'planetscale';
+ connection: PlanetscaleConfig;
+ } & DrizzleConfig)
+ | ({
+ client: 'mysql2';
+ connection: Mysql2Config;
+ } & MySql2DrizzleConfig)
+ | ({
+ client: 'tidb-serverless';
+ connection: TiDBServerlessConfig;
+ } & DrizzleConfig)
+ | ({
+ client: 'libsql';
+ connection: LibsqlConfig;
+ } & DrizzleConfig)
+ | ({
+ client: 'd1';
+ connection: D1Database;
+ } & DrizzleConfig)
+ | ({
+ client: 'bun-sqlite';
+ connection: BunSqliteDatabaseConfig;
+ } & DrizzleConfig)
+ | ({
+ client: 'better-sqlite3';
+ connection: BetterSQLite3DatabaseConfig;
+ } & DrizzleConfig);
+
+type DetermineClient<
+ TParams extends InitializerParams,
+> = ClientDrizzleInstanceMap[TParams['client']];
+
+const importError = (libName: string) => {
+ throw new Error(
+ `Please install '${libName}' for Drizzle ORM to connect to database`,
+ );
+};
+
+export const drizzle = async <
+ TSchema extends Record,
+ TParams extends InitializerParams,
+>(params: TParams): Promise> => {
+ const { client, connection, ...drizzleConfig } = params;
+
+ switch (client) {
+ case 'node-postgres': {
+ const { Pool } = await import('pg').catch(() => importError('pg'));
+ const { drizzle } = await import('./node-postgres');
+ const instance = new Pool(connection as NodePGPoolConfig);
+
+ return drizzle(instance, drizzleConfig) as any;
+ }
+ case 'aws-data-api-pg': {
+ const { RDSDataClient } = await import('@aws-sdk/client-rds-data').catch(() =>
+ importError('@aws-sdk/client-rds-data')
+ );
+ const { drizzle } = await import('./aws-data-api/pg');
+ const instance = new RDSDataClient(connection);
+
+ return drizzle(instance, drizzleConfig as any as DrizzleAwsDataApiPgConfig) as any;
+ }
+ case 'better-sqlite3': {
+ const { default: Client } = await import('better-sqlite3').catch(() => importError('better-sqlite3'));
+ const { filename, options } = connection as BetterSQLite3DatabaseConfig;
+ const { drizzle } = await import('./better-sqlite3');
+ const instance = new Client(filename, options);
+
+ return drizzle(instance, drizzleConfig) as any;
+ }
+ case 'bun-sqlite': {
+ const { Database: Client } = await import('bun:sqlite').catch(() => importError('bun:sqlite'));
+ const { filename, options } = connection as BunSqliteDatabaseConfig;
+ const { drizzle } = await import('./bun-sqlite');
+ const instance = new Client(filename, options);
+
+ return drizzle(instance, drizzleConfig) as any;
+ }
+ case 'd1': {
+ const { drizzle } = await import('./d1');
+ return drizzle(connection as D1Database, drizzleConfig) as any;
+ }
+ case 'libsql': {
+ const { createClient } = await import('@libsql/client').catch(() => importError('@libsql/client'));
+ const { drizzle } = await import('./libsql');
+ const instance = createClient(connection as LibsqlConfig);
+
+ return drizzle(instance, drizzleConfig) as any;
+ }
+ case 'mysql2': {
+ const { createConnection } = await import('mysql2/promise').catch(() => importError('mysql2/promise'));
+ const instance = await createConnection(connection as Mysql2Config);
+ const { drizzle } = await import('./mysql2');
+
+ return drizzle(instance, drizzleConfig as MySql2DrizzleConfig) as any;
+ }
+ case 'neon-http': {
+ const { neon } = await import('@neondatabase/serverless').catch(() => importError('@neondatabase/serverless'));
+ const { connectionString, options } = connection as MonodriverNeonHttpConfig;
+ const { drizzle } = await import('./neon-http');
+ const instance = neon(connectionString, options);
+
+ return drizzle(instance, drizzleConfig) as any;
+ }
+ case 'neon-serverless': {
+ const { Pool } = await import('@neondatabase/serverless').catch(() => importError('@neondatabase/serverless'));
+ const { drizzle } = await import('./neon-serverless');
+ const instance = new Pool(connection as NeonServerlessConfig);
+
+ return drizzle(instance, drizzleConfig) as any;
+ }
+ case 'planetscale': {
+ const { Client } = await import('@planetscale/database').catch(() => importError('@planetscale/database'));
+ const { drizzle } = await import('./planetscale-serverless');
+ const instance = new Client(
+ connection as PlanetscaleConfig,
+ );
+
+ return drizzle(instance, drizzleConfig) as any;
+ }
+ case 'postgres-js': {
+ const { default: client } = await import('postgres').catch(() => importError('postgres'));
+ const { drizzle } = await import('./postgres-js');
+ const instance = client(connection as PostgresJSOptions>);
+
+ return drizzle(instance, drizzleConfig) as any;
+ }
+ case 'tidb-serverless': {
+ const { connect } = await import('@tidbcloud/serverless').catch(() => importError('@tidbcloud/serverless'));
+ const { drizzle } = await import('./tidb-serverless');
+ const instance = connect(connection as TiDBServerlessConfig);
+
+ return drizzle(instance, drizzleConfig) as any;
+ }
+ case 'vercel-postgres': {
+ const { sql } = await import('@vercel/postgres').catch(() => importError('@vercel/postgres'));
+ const { drizzle } = await import('./vercel-postgres');
+
+ return drizzle(sql, drizzleConfig) as any;
+ }
+ }
+};
diff --git a/drizzle-orm/src/mysql-core/db.ts b/drizzle-orm/src/mysql-core/db.ts
index 8df6ff343..419359022 100644
--- a/drizzle-orm/src/mysql-core/db.ts
+++ b/drizzle-orm/src/mysql-core/db.ts
@@ -3,10 +3,11 @@ import { entityKind } from '~/entity.ts';
import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts';
import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts';
import { SelectionProxyHandler } from '~/selection-proxy.ts';
-import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts';
+import type { ColumnsSelection, SQL, SQLWrapper } from '~/sql/sql.ts';
import { WithSubquery } from '~/subquery.ts';
import type { DrizzleTypeError } from '~/utils.ts';
import type { MySqlDialect } from './dialect.ts';
+import { MySqlCountBuilder } from './query-builders/count.ts';
import {
MySqlDeleteBase,
MySqlInsertBuilder,
@@ -27,6 +28,7 @@ import type {
} from './session.ts';
import type { WithSubqueryWithSelection } from './subquery.ts';
import type { MySqlTable } from './table.ts';
+import type { MySqlViewBase } from './view-base.ts';
export class MySqlDatabase<
TQueryResult extends MySqlQueryResultHKT,
@@ -134,6 +136,13 @@ export class MySqlDatabase<
};
}
+ $count(
+ source: MySqlTable | MySqlViewBase | SQL | SQLWrapper,
+ filters?: SQL,
+ ) {
+ return new MySqlCountBuilder({ source, filters, dialect: this.dialect, session: this.session });
+ }
+
/**
* Incorporates a previously defined CTE (using `$with`) into the main query.
*
diff --git a/drizzle-orm/src/mysql-core/query-builders/count.ts b/drizzle-orm/src/mysql-core/query-builders/count.ts
new file mode 100644
index 000000000..751ba61c7
--- /dev/null
+++ b/drizzle-orm/src/mysql-core/query-builders/count.ts
@@ -0,0 +1,82 @@
+import { entityKind, sql } from '~/index.ts';
+import type { SQLWrapper } from '~/sql/sql.ts';
+import { SQL } from '~/sql/sql.ts';
+import type { MySqlDialect } from '../dialect.ts';
+import type { MySqlSession } from '../session.ts';
+import type { MySqlTable } from '../table.ts';
+import type { MySqlViewBase } from '../view-base.ts';
+
+export class MySqlCountBuilder<
+ TSession extends MySqlSession,
+> extends SQL implements Promise, SQLWrapper {
+ private sql: SQL;
+
+ static readonly [entityKind] = 'MySqlCountBuilder';
+ [Symbol.toStringTag] = 'MySqlCountBuilder';
+
+ private session: TSession;
+
+ private static buildEmbeddedCount(
+ source: MySqlTable | MySqlViewBase | SQL | SQLWrapper,
+ filters?: SQL,
+ ): SQL {
+ return sql`(select count(*) from ${source}${sql.raw(' where ').if(filters)}${filters})`;
+ }
+
+ private static buildCount(
+ source: MySqlTable | MySqlViewBase | SQL | SQLWrapper,
+ filters?: SQL,
+ ): SQL {
+ return sql`select count(*) from ${source}${sql.raw(' where ').if(filters)}${filters}`;
+ }
+
+ constructor(
+ readonly params: {
+ source: MySqlTable | MySqlViewBase | SQL | SQLWrapper;
+ filters?: SQL;
+ dialect: MySqlDialect;
+ session: TSession;
+ },
+ ) {
+ super(MySqlCountBuilder.buildEmbeddedCount(params.source, params.filters).queryChunks);
+
+ this.session = params.session;
+
+ this.sql = MySqlCountBuilder.buildCount(
+ params.source,
+ params.filters,
+ );
+ }
+
+ then(
+ onfulfilled?: ((value: number) => TResult1 | PromiseLike) | null | undefined,
+ onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined,
+ ): Promise {
+ return Promise.resolve(this.session.all(this.sql)).then((it) => {
+ return (<[{ 'count(*)': number }]> it)[0]['count(*)'];
+ })
+ .then(
+ onfulfilled,
+ onrejected,
+ );
+ }
+
+ catch(
+ onRejected?: ((reason: any) => never | PromiseLike) | null | undefined,
+ ): Promise {
+ return this.then(undefined, onRejected);
+ }
+
+ finally(onFinally?: (() => void) | null | undefined): Promise {
+ return this.then(
+ (value) => {
+ onFinally?.();
+ return value;
+ },
+ (reason) => {
+ onFinally?.();
+ throw reason;
+ },
+ );
+ }
+}
diff --git a/drizzle-orm/src/operations.ts b/drizzle-orm/src/operations.ts
index 492bb3f2a..6fb5cbd2e 100644
--- a/drizzle-orm/src/operations.ts
+++ b/drizzle-orm/src/operations.ts
@@ -21,6 +21,7 @@ export type OptionalKeyOnly<
: T['_']['generated'] extends object ? T['_']['generated']['type'] extends 'byDefault' ? TKey : never
: never;
+// TODO: SQL -> SQLWrapper
export type SelectedFieldsFlat = Record<
string,
TColumn | SQL | SQL.Aliased
diff --git a/drizzle-orm/src/pg-core/db.ts b/drizzle-orm/src/pg-core/db.ts
index 4e8d2f354..3c8da44f1 100644
--- a/drizzle-orm/src/pg-core/db.ts
+++ b/drizzle-orm/src/pg-core/db.ts
@@ -19,15 +19,17 @@ import type { PgTable } from '~/pg-core/table.ts';
import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts';
import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts';
import { SelectionProxyHandler } from '~/selection-proxy.ts';
-import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts';
+import type { ColumnsSelection, SQL, SQLWrapper } from '~/sql/sql.ts';
import { WithSubquery } from '~/subquery.ts';
import type { DrizzleTypeError } from '~/utils.ts';
import type { PgColumn } from './columns/index.ts';
+import { PgCountBuilder } from './query-builders/count.ts';
import { RelationalQueryBuilder } from './query-builders/query.ts';
import { PgRaw } from './query-builders/raw.ts';
import { PgRefreshMaterializedView } from './query-builders/refresh-materialized-view.ts';
import type { SelectedFields } from './query-builders/select.types.ts';
import type { WithSubqueryWithSelection } from './subquery.ts';
+import type { PgViewBase } from './view-base.ts';
import type { PgMaterializedView } from './view.ts';
export class PgDatabase<
@@ -135,6 +137,13 @@ export class PgDatabase<
};
}
+ $count(
+ source: PgTable | PgViewBase | SQL | SQLWrapper,
+ filters?: SQL,
+ ) {
+ return new PgCountBuilder({ source, filters, dialect: this.dialect, session: this.session });
+ }
+
/**
* Incorporates a previously defined CTE (using `$with`) into the main query.
*
diff --git a/drizzle-orm/src/pg-core/query-builders/count.ts b/drizzle-orm/src/pg-core/query-builders/count.ts
new file mode 100644
index 000000000..7ccd722a0
--- /dev/null
+++ b/drizzle-orm/src/pg-core/query-builders/count.ts
@@ -0,0 +1,81 @@
+import { entityKind, sql } from '~/index.ts';
+import type { SQLWrapper } from '~/sql/sql.ts';
+import { SQL } from '~/sql/sql.ts';
+import type { PgDialect } from '../dialect.ts';
+import type { PgSession } from '../session.ts';
+import type { PgTable } from '../table.ts';
+
+export class PgCountBuilder<
+ TSession extends PgSession,
+> extends SQL implements Promise, SQLWrapper {
+ private sql: SQL;
+
+ static readonly [entityKind] = 'PgCountBuilder';
+ [Symbol.toStringTag] = 'PgCountBuilder';
+
+ private session: TSession;
+
+ private static buildEmbeddedCount(
+ source: PgTable | SQL | SQLWrapper,
+ filters?: SQL,
+ ): SQL {
+ return sql`(select count(*)::int from ${source}${sql.raw(' where ').if(filters)}${filters})`;
+ }
+
+ private static buildCount(
+ source: PgTable | SQL | SQLWrapper,
+ filters?: SQL,
+ ): SQL {
+ return sql`select count(*)::int from ${source}${sql.raw(' where ').if(filters)}${filters};`;
+ }
+
+ constructor(
+ readonly params: {
+ source: PgTable | SQL | SQLWrapper;
+ filters?: SQL;
+ dialect: PgDialect;
+ session: TSession;
+ },
+ ) {
+ super(PgCountBuilder.buildEmbeddedCount(params.source, params.filters).queryChunks);
+
+ this.session = params.session;
+
+ this.sql = PgCountBuilder.buildCount(
+ params.source,
+ params.filters,
+ );
+ }
+
+ then(
+ onfulfilled?: ((value: number) => TResult1 | PromiseLike) | null | undefined,
+ onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined,
+ ): Promise {
+ return Promise.resolve(this.session.all(this.sql)).then((it) => {
+ return (<[{ count: number }]> it)[0]['count'] as number;
+ })
+ .then(
+ onfulfilled,
+ onrejected,
+ );
+ }
+
+ catch(
+ onRejected?: ((reason: any) => never | PromiseLike) | null | undefined,
+ ): Promise {
+ return this.then(undefined, onRejected);
+ }
+
+ finally(onFinally?: (() => void) | null | undefined): Promise {
+ return this.then(
+ (value) => {
+ onFinally?.();
+ return value;
+ },
+ (reason) => {
+ onFinally?.();
+ throw reason;
+ },
+ );
+ }
+}
diff --git a/drizzle-orm/src/sqlite-core/db.ts b/drizzle-orm/src/sqlite-core/db.ts
index 65f807d08..75b088f6d 100644
--- a/drizzle-orm/src/sqlite-core/db.ts
+++ b/drizzle-orm/src/sqlite-core/db.ts
@@ -2,7 +2,7 @@ import { entityKind } from '~/entity.ts';
import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts';
import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts';
import { SelectionProxyHandler } from '~/selection-proxy.ts';
-import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts';
+import type { ColumnsSelection, SQL, SQLWrapper } from '~/sql/sql.ts';
import type { SQLiteAsyncDialect, SQLiteSyncDialect } from '~/sqlite-core/dialect.ts';
import {
QueryBuilder,
@@ -21,10 +21,12 @@ import type {
import type { SQLiteTable } from '~/sqlite-core/table.ts';
import { WithSubquery } from '~/subquery.ts';
import type { DrizzleTypeError } from '~/utils.ts';
+import { SQLiteCountBuilder } from './query-builders/count.ts';
import { RelationalQueryBuilder } from './query-builders/query.ts';
import { SQLiteRaw } from './query-builders/raw.ts';
import type { SelectedFields } from './query-builders/select.types.ts';
import type { WithSubqueryWithSelection } from './subquery.ts';
+import type { SQLiteViewBase } from './view-base.ts';
export class BaseSQLiteDatabase<
TResultKind extends 'sync' | 'async',
@@ -134,6 +136,13 @@ export class BaseSQLiteDatabase<
};
}
+ $count(
+ source: SQLiteTable | SQLiteViewBase | SQL | SQLWrapper,
+ filters?: SQL,
+ ) {
+ return new SQLiteCountBuilder({ source, filters, dialect: this.dialect, session: this.session });
+ }
+
/**
* Incorporates a previously defined CTE (using `$with`) into the main query.
*
diff --git a/drizzle-orm/src/sqlite-core/query-builders/count.ts b/drizzle-orm/src/sqlite-core/query-builders/count.ts
new file mode 100644
index 000000000..ed6cd9a1d
--- /dev/null
+++ b/drizzle-orm/src/sqlite-core/query-builders/count.ts
@@ -0,0 +1,79 @@
+import { entityKind, sql } from '~/index.ts';
+import type { SQLWrapper } from '~/sql/sql.ts';
+import { SQL } from '~/sql/sql.ts';
+import type { SQLiteDialect } from '../dialect.ts';
+import type { SQLiteSession } from '../session.ts';
+import type { SQLiteTable } from '../table.ts';
+import type { SQLiteView } from '../view.ts';
+
+export class SQLiteCountBuilder<
+ TSession extends SQLiteSession,
+> extends SQL implements Promise, SQLWrapper {
+ private sql: SQL;
+
+ static readonly [entityKind] = 'SQLiteCountBuilderAsync';
+ [Symbol.toStringTag] = 'SQLiteCountBuilderAsync';
+
+ private session: TSession;
+
+ private static buildEmbeddedCount(
+ source: SQLiteTable | SQLiteView | SQL | SQLWrapper,
+ filters?: SQL,
+ ): SQL {
+ return sql`(select count(*) from ${source}${sql.raw(' where ').if(filters)}${filters})`;
+ }
+
+ private static buildCount(
+ source: SQLiteTable | SQLiteView | SQL | SQLWrapper,
+ filters?: SQL,
+ ): SQL {
+ return sql`select count(*) from ${source}${sql.raw(' where ').if(filters)}${filters}`;
+ }
+
+ constructor(
+ readonly params: {
+ source: SQLiteTable | SQLiteView | SQL | SQLWrapper;
+ filters?: SQL;
+ dialect: SQLiteDialect;
+ session: TSession;
+ },
+ ) {
+ super(SQLiteCountBuilder.buildEmbeddedCount(params.source, params.filters).queryChunks);
+
+ this.session = params.session;
+
+ this.sql = SQLiteCountBuilder.buildCount(
+ params.source,
+ params.filters,
+ );
+ }
+
+ then(
+ onfulfilled?: ((value: number) => TResult1 | PromiseLike) | null | undefined,
+ onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined,
+ ): Promise {
+ return Promise.resolve(this.session.values(this.sql)).then((it) => it[0]![0] as number).then(
+ onfulfilled,
+ onrejected,
+ );
+ }
+
+ catch(
+ onRejected?: ((reason: any) => never | PromiseLike) | null | undefined,
+ ): Promise {
+ return this.then(undefined, onRejected);
+ }
+
+ finally(onFinally?: (() => void) | null | undefined): Promise {
+ return this.then(
+ (value) => {
+ onFinally?.();
+ return value;
+ },
+ (reason) => {
+ onFinally?.();
+ throw reason;
+ },
+ );
+ }
+}
diff --git a/drizzle-orm/type-tests/mysql/count.ts b/drizzle-orm/type-tests/mysql/count.ts
new file mode 100644
index 000000000..d9b9ba9ff
--- /dev/null
+++ b/drizzle-orm/type-tests/mysql/count.ts
@@ -0,0 +1,61 @@
+import { Expect } from 'type-tests/utils.ts';
+import { and, gt, ne } from '~/expressions.ts';
+import { int, mysqlTable, serial, text } from '~/mysql-core/index.ts';
+import type { Equal } from '~/utils.ts';
+import { db } from './db.ts';
+
+const names = mysqlTable('names', {
+ id: serial('id').primaryKey(),
+ name: text('name'),
+ authorId: int('author_id'),
+});
+
+const separate = await db.$count(names);
+
+const separateFilters = await db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden')));
+
+const embedded = await db
+ .select({
+ id: names.id,
+ name: names.name,
+ authorId: names.authorId,
+ count1: db.$count(names).as('count1'),
+ })
+ .from(names);
+
+const embeddedFilters = await db
+ .select({
+ id: names.id,
+ name: names.name,
+ authorId: names.authorId,
+ count1: db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))).as('count1'),
+ })
+ .from(names);
+
+Expect>;
+
+Expect>;
+
+Expect<
+ Equal<
+ {
+ id: number;
+ name: string | null;
+ authorId: number | null;
+ count1: number;
+ }[],
+ typeof embedded
+ >
+>;
+
+Expect<
+ Equal<
+ {
+ id: number;
+ name: string | null;
+ authorId: number | null;
+ count1: number;
+ }[],
+ typeof embeddedFilters
+ >
+>;
diff --git a/drizzle-orm/type-tests/pg/count.ts b/drizzle-orm/type-tests/pg/count.ts
new file mode 100644
index 000000000..9ed5eeaf9
--- /dev/null
+++ b/drizzle-orm/type-tests/pg/count.ts
@@ -0,0 +1,61 @@
+import { Expect } from 'type-tests/utils.ts';
+import { and, gt, ne } from '~/expressions.ts';
+import { integer, pgTable, serial, text } from '~/pg-core/index.ts';
+import type { Equal } from '~/utils.ts';
+import { db } from './db.ts';
+
+const names = pgTable('names', {
+ id: serial('id').primaryKey(),
+ name: text('name'),
+ authorId: integer('author_id'),
+});
+
+const separate = await db.$count(names);
+
+const separateFilters = await db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden')));
+
+const embedded = await db
+ .select({
+ id: names.id,
+ name: names.name,
+ authorId: names.authorId,
+ count1: db.$count(names).as('count1'),
+ })
+ .from(names);
+
+const embeddedFilters = await db
+ .select({
+ id: names.id,
+ name: names.name,
+ authorId: names.authorId,
+ count1: db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))).as('count1'),
+ })
+ .from(names);
+
+Expect>;
+
+Expect>;
+
+Expect<
+ Equal<
+ {
+ id: number;
+ name: string | null;
+ authorId: number | null;
+ count1: number;
+ }[],
+ typeof embedded
+ >
+>;
+
+Expect<
+ Equal<
+ {
+ id: number;
+ name: string | null;
+ authorId: number | null;
+ count1: number;
+ }[],
+ typeof embeddedFilters
+ >
+>;
diff --git a/drizzle-orm/type-tests/sqlite/count.ts b/drizzle-orm/type-tests/sqlite/count.ts
new file mode 100644
index 000000000..04350f000
--- /dev/null
+++ b/drizzle-orm/type-tests/sqlite/count.ts
@@ -0,0 +1,61 @@
+import { Expect } from 'type-tests/utils.ts';
+import { and, gt, ne } from '~/expressions.ts';
+import { integer, sqliteTable, text } from '~/sqlite-core/index.ts';
+import type { Equal } from '~/utils.ts';
+import { db } from './db.ts';
+
+const names = sqliteTable('names', {
+ id: integer('id').primaryKey(),
+ name: text('name'),
+ authorId: integer('author_id'),
+});
+
+const separate = await db.$count(names);
+
+const separateFilters = await db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden')));
+
+const embedded = await db
+ .select({
+ id: names.id,
+ name: names.name,
+ authorId: names.authorId,
+ count1: db.$count(names).as('count1'),
+ })
+ .from(names);
+
+const embeddedFilters = await db
+ .select({
+ id: names.id,
+ name: names.name,
+ authorId: names.authorId,
+ count1: db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))).as('count1'),
+ })
+ .from(names);
+
+Expect>;
+
+Expect>;
+
+Expect<
+ Equal<
+ {
+ id: number;
+ name: string | null;
+ authorId: number | null;
+ count1: number;
+ }[],
+ typeof embedded
+ >
+>;
+
+Expect<
+ Equal<
+ {
+ id: number;
+ name: string | null;
+ authorId: number | null;
+ count1: number;
+ }[],
+ typeof embeddedFilters
+ >
+>;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d2d091ad6..209d2db3b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -182,6 +182,9 @@ importers:
better-sqlite3:
specifier: ^9.4.3
version: 9.6.0
+ bun-types:
+ specifier: ^0.6.6
+ version: 0.6.14
camelcase:
specifier: ^7.0.1
version: 7.0.1
@@ -10210,7 +10213,7 @@ snapshots:
'@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0)
'@aws-sdk/client-sts': 3.583.0
'@aws-sdk/core': 3.582.0
- '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.583.0)
+ '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0)
'@aws-sdk/middleware-host-header': 3.577.0
'@aws-sdk/middleware-logger': 3.577.0
'@aws-sdk/middleware-recursion-detection': 3.577.0
@@ -10300,7 +10303,7 @@ snapshots:
'@aws-crypto/sha256-js': 3.0.0
'@aws-sdk/client-sts': 3.583.0
'@aws-sdk/core': 3.582.0
- '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.583.0)
+ '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0)
'@aws-sdk/middleware-host-header': 3.577.0
'@aws-sdk/middleware-logger': 3.577.0
'@aws-sdk/middleware-recursion-detection': 3.577.0
@@ -10610,7 +10613,7 @@ snapshots:
'@aws-crypto/sha256-js': 3.0.0
'@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0)
'@aws-sdk/core': 3.582.0
- '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.583.0)
+ '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0)
'@aws-sdk/middleware-host-header': 3.577.0
'@aws-sdk/middleware-logger': 3.577.0
'@aws-sdk/middleware-recursion-detection': 3.577.0
@@ -10799,12 +10802,12 @@ snapshots:
- '@aws-sdk/client-sso-oidc'
- aws-crt
- '@aws-sdk/credential-provider-ini@3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.583.0)':
+ '@aws-sdk/credential-provider-ini@3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0)':
dependencies:
'@aws-sdk/client-sts': 3.583.0
'@aws-sdk/credential-provider-env': 3.577.0
'@aws-sdk/credential-provider-process': 3.577.0
- '@aws-sdk/credential-provider-sso': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))
+ '@aws-sdk/credential-provider-sso': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)
'@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.583.0)
'@aws-sdk/types': 3.577.0
'@smithy/credential-provider-imds': 3.0.0
@@ -10889,13 +10892,13 @@ snapshots:
- '@aws-sdk/client-sts'
- aws-crt
- '@aws-sdk/credential-provider-node@3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.583.0)':
+ '@aws-sdk/credential-provider-node@3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0)':
dependencies:
'@aws-sdk/credential-provider-env': 3.577.0
'@aws-sdk/credential-provider-http': 3.582.0
- '@aws-sdk/credential-provider-ini': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.583.0)
+ '@aws-sdk/credential-provider-ini': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0)
'@aws-sdk/credential-provider-process': 3.577.0
- '@aws-sdk/credential-provider-sso': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))
+ '@aws-sdk/credential-provider-sso': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)
'@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.583.0)
'@aws-sdk/types': 3.577.0
'@smithy/credential-provider-imds': 3.0.0
@@ -10970,10 +10973,10 @@ snapshots:
- '@aws-sdk/client-sso-oidc'
- aws-crt
- '@aws-sdk/credential-provider-sso@3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))':
+ '@aws-sdk/credential-provider-sso@3.583.0(@aws-sdk/client-sso-oidc@3.583.0)':
dependencies:
'@aws-sdk/client-sso': 3.583.0
- '@aws-sdk/token-providers': 3.577.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))
+ '@aws-sdk/token-providers': 3.577.0(@aws-sdk/client-sso-oidc@3.583.0)
'@aws-sdk/types': 3.577.0
'@smithy/property-provider': 3.0.0
'@smithy/shared-ini-file-loader': 3.0.0
@@ -11216,7 +11219,7 @@ snapshots:
'@smithy/types': 2.12.0
tslib: 2.6.2
- '@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))':
+ '@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.583.0)':
dependencies:
'@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0)
'@aws-sdk/types': 3.577.0