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