From c063144dc08726cc15323582fe377210329e579e Mon Sep 17 00:00:00 2001 From: Dan Kochetov Date: Sun, 13 Aug 2023 01:16:43 +0300 Subject: [PATCH] Add async API to SQLite --- drizzle-orm/src/better-sqlite3/session.ts | 19 +- drizzle-orm/src/bun-sqlite/session.ts | 18 +- drizzle-orm/src/d1/session.ts | 11 +- drizzle-orm/src/libsql/session.ts | 24 +- drizzle-orm/src/mysql-core/README.md | 979 --------------- drizzle-orm/src/pg-core/README.md | 1057 ----------------- .../src/query-builders/select.types.ts | 2 +- drizzle-orm/src/sql-js/session.ts | 36 +- drizzle-orm/src/sqlite-core/db.ts | 25 +- .../src/sqlite-core/query-builders/delete.ts | 28 +- .../src/sqlite-core/query-builders/insert.ts | 34 +- .../src/sqlite-core/query-builders/query.ts | 227 ++-- .../src/sqlite-core/query-builders/select.ts | 23 +- .../src/sqlite-core/query-builders/update.ts | 38 +- drizzle-orm/src/sqlite-core/session.ts | 51 +- drizzle-orm/src/sqlite-proxy/session.ts | 16 +- drizzle-orm/src/utils.ts | 5 +- drizzle-orm/type-tests/sqlite/delete.ts | 25 +- drizzle-orm/type-tests/sqlite/insert.ts | 13 +- drizzle-orm/type-tests/sqlite/select.ts | 15 +- drizzle-orm/type-tests/sqlite/update.ts | 9 +- integration-tests/tests/better-sqlite.test.ts | 133 ++- integration-tests/tests/d1.test.ts | 236 +++- integration-tests/tests/libsql.test.ts | 109 +- .../tests/relational/bettersqlite.test.ts | 218 ++-- .../tests/relational/turso.test.ts | 14 + integration-tests/tests/sql.js.test.ts | 123 +- integration-tests/tests/sqlite-proxy.test.ts | 107 ++ 28 files changed, 1113 insertions(+), 2482 deletions(-) delete mode 100644 drizzle-orm/src/mysql-core/README.md delete mode 100644 drizzle-orm/src/pg-core/README.md diff --git a/drizzle-orm/src/better-sqlite3/session.ts b/drizzle-orm/src/better-sqlite3/session.ts index 1b223991c..863e7385d 100644 --- a/drizzle-orm/src/better-sqlite3/session.ts +++ b/drizzle-orm/src/better-sqlite3/session.ts @@ -7,8 +7,13 @@ import { fillPlaceholders, type Query, sql } from '~/sql'; import { SQLiteTransaction } from '~/sqlite-core'; import type { SQLiteSyncDialect } from '~/sqlite-core/dialect'; import type { SelectedFieldsOrdered } from '~/sqlite-core/query-builders/select.types'; -import type { PreparedQueryConfig as PreparedQueryConfigBase, SQLiteTransactionConfig } from '~/sqlite-core/session'; -import { PreparedQuery as PreparedQueryBase, SQLiteSession } from '~/sqlite-core/session'; +import { + PreparedQuery as PreparedQueryBase, + type PreparedQueryConfig as PreparedQueryConfigBase, + type SQLiteExecuteMethod, + SQLiteSession, + type SQLiteTransactionConfig, +} from '~/sqlite-core/session'; import { mapResultRow } from '~/utils'; export interface BetterSQLiteSessionOptions { @@ -38,10 +43,11 @@ export class BetterSQLiteSession< prepareQuery>( query: Query, fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, customResultMapper?: (rows: unknown[][]) => unknown, ): PreparedQuery { const stmt = this.client.prepare(query.sql); - return new PreparedQuery(stmt, query.sql, query.params, this.logger, fields, customResultMapper); + return new PreparedQuery(stmt, query.sql, query.params, this.logger, fields, executeMethod, customResultMapper); } override transaction( @@ -76,7 +82,7 @@ export class BetterSQLiteTransaction< } export class PreparedQuery extends PreparedQueryBase< - { type: 'sync'; run: RunResult; all: T['all']; get: T['get']; values: T['values'] } + { type: 'sync'; run: RunResult; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { static readonly [entityKind]: string = 'BetterSQLitePreparedQuery'; @@ -86,9 +92,10 @@ export class PreparedQuery private params: unknown[], private logger: Logger, private fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, private customResultMapper?: (rows: unknown[][]) => unknown, ) { - super(); + super('sync', executeMethod); } run(placeholderValues?: Record): RunResult { @@ -105,7 +112,7 @@ export class PreparedQuery return stmt.all(...params); } - const rows = this.values(placeholderValues); + const rows = this.values(placeholderValues) as unknown[][]; if (customResultMapper) { return customResultMapper(rows) as T['all']; } diff --git a/drizzle-orm/src/bun-sqlite/session.ts b/drizzle-orm/src/bun-sqlite/session.ts index 2b18095cb..30151ad93 100644 --- a/drizzle-orm/src/bun-sqlite/session.ts +++ b/drizzle-orm/src/bun-sqlite/session.ts @@ -9,7 +9,11 @@ import { fillPlaceholders, type Query, sql } from '~/sql'; import { SQLiteTransaction } from '~/sqlite-core'; import type { SQLiteSyncDialect } from '~/sqlite-core/dialect'; import type { SelectedFieldsOrdered } from '~/sqlite-core/query-builders/select.types'; -import type { PreparedQueryConfig as PreparedQueryConfigBase, SQLiteTransactionConfig } from '~/sqlite-core/session'; +import type { + PreparedQueryConfig as PreparedQueryConfigBase, + SQLiteExecuteMethod, + SQLiteTransactionConfig, +} from '~/sqlite-core/session'; import { PreparedQuery as PreparedQueryBase, SQLiteSession } from '~/sqlite-core/session'; import { mapResultRow } from '~/utils'; @@ -44,11 +48,12 @@ export class SQLiteBunSession< prepareQuery>( query: Query, - fields?: SelectedFieldsOrdered, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, customResultMapper?: (rows: unknown[][]) => unknown, ): PreparedQuery { const stmt = this.client.prepare(query.sql); - return new PreparedQuery(stmt, query.sql, query.params, this.logger, fields, customResultMapper); + return new PreparedQuery(stmt, query.sql, query.params, this.logger, fields, executeMethod, customResultMapper); } override transaction( @@ -87,7 +92,7 @@ export class SQLiteBunTransaction< } export class PreparedQuery extends PreparedQueryBase< - { type: 'sync'; run: void; all: T['all']; get: T['get']; values: T['values'] } + { type: 'sync'; run: void; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { static readonly [entityKind]: string = 'SQLiteBunPreparedQuery'; @@ -97,9 +102,10 @@ export class PreparedQuery private params: unknown[], private logger: Logger, private fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, private customResultMapper?: (rows: unknown[][]) => unknown, ) { - super(); + super('sync', executeMethod); } run(placeholderValues?: Record): void { @@ -116,7 +122,7 @@ export class PreparedQuery return stmt.all(...params); } - const rows = this.values(placeholderValues); + const rows = this.values(placeholderValues) as unknown[][]; if (customResultMapper) { return customResultMapper(rows) as T['all']; diff --git a/drizzle-orm/src/d1/session.ts b/drizzle-orm/src/d1/session.ts index 0c4a4880a..f4dae3ac7 100644 --- a/drizzle-orm/src/d1/session.ts +++ b/drizzle-orm/src/d1/session.ts @@ -11,6 +11,7 @@ import type { SQLiteAsyncDialect } from '~/sqlite-core/dialect'; import type { SelectedFieldsOrdered } from '~/sqlite-core/query-builders/select.types'; import { type PreparedQueryConfig as PreparedQueryConfigBase, + type SQLiteExecuteMethod, type SQLiteTransactionConfig, } from '~/sqlite-core/session'; import { PreparedQuery as PreparedQueryBase, SQLiteSession } from '~/sqlite-core/session'; @@ -42,11 +43,12 @@ export class SQLiteD1Session< prepareQuery( query: Query, - fields?: SelectedFieldsOrdered, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, customResultMapper?: (rows: unknown[][]) => unknown, ): PreparedQuery { const stmt = this.client.prepare(query.sql); - return new PreparedQuery(stmt, query.sql, query.params, this.logger, fields, customResultMapper); + return new PreparedQuery(stmt, query.sql, query.params, this.logger, fields, executeMethod, customResultMapper); } override async transaction( @@ -88,7 +90,7 @@ export class D1Transaction< } export class PreparedQuery extends PreparedQueryBase< - { type: 'async'; run: D1Result; all: T['all']; get: T['get']; values: T['values'] } + { type: 'async'; run: D1Result; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { static readonly [entityKind]: string = 'D1PreparedQuery'; @@ -98,9 +100,10 @@ export class PreparedQuery private params: unknown[], private logger: Logger, private fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, private customResultMapper?: (rows: unknown[][]) => unknown, ) { - super(); + super('async', executeMethod); } run(placeholderValues?: Record): Promise { diff --git a/drizzle-orm/src/libsql/session.ts b/drizzle-orm/src/libsql/session.ts index 4677d16d0..436e80cc9 100644 --- a/drizzle-orm/src/libsql/session.ts +++ b/drizzle-orm/src/libsql/session.ts @@ -9,6 +9,7 @@ import type { SQLiteAsyncDialect } from '~/sqlite-core/dialect'; import type { SelectedFieldsOrdered } from '~/sqlite-core/query-builders/select.types'; import { type PreparedQueryConfig as PreparedQueryConfigBase, + type SQLiteExecuteMethod, type SQLiteTransactionConfig, } from '~/sqlite-core/session'; import { PreparedQuery as PreparedQueryBase, SQLiteSession } from '~/sqlite-core/session'; @@ -41,10 +42,20 @@ export class LibSQLSession< prepareQuery>( query: Query, - fields: SelectedFieldsOrdered, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, customResultMapper?: (rows: unknown[][]) => unknown, ): PreparedQuery { - return new PreparedQuery(this.client, query.sql, query.params, this.logger, fields, this.tx, customResultMapper); + return new PreparedQuery( + this.client, + query.sql, + query.params, + this.logger, + fields, + this.tx, + executeMethod, + customResultMapper, + ); } /*override */ batch(queries: SQL[]): Promise { @@ -96,7 +107,7 @@ export class LibSQLTransaction< } export class PreparedQuery extends PreparedQueryBase< - { type: 'async'; run: ResultSet; all: T['all']; get: T['get']; values: T['values'] } + { type: 'async'; run: ResultSet; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { static readonly [entityKind]: string = 'LibSQLPreparedQuery'; @@ -107,9 +118,10 @@ export class PreparedQuery private logger: Logger, private fields: SelectedFieldsOrdered | undefined, private tx: Transaction | undefined, + executeMethod: SQLiteExecuteMethod, private customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown, ) { - super(); + super('async', executeMethod); } run(placeholderValues?: Record): Promise { @@ -128,7 +140,7 @@ export class PreparedQuery return (tx ? tx.execute(stmt) : client.execute(stmt)).then(({ rows }) => rows.map((row) => normalizeRow(row))); } - const rows = await this.values(placeholderValues); + const rows = await this.values(placeholderValues) as unknown[][]; if (customResultMapper) { return customResultMapper(rows, normalizeFieldValue) as T['all']; @@ -152,7 +164,7 @@ export class PreparedQuery return (tx ? tx.execute(stmt) : client.execute(stmt)).then(({ rows }) => normalizeRow(rows[0])); } - const rows = await this.values(placeholderValues); + const rows = await this.values(placeholderValues) as unknown[][]; if (!rows[0]) { return undefined; diff --git a/drizzle-orm/src/mysql-core/README.md b/drizzle-orm/src/mysql-core/README.md deleted file mode 100644 index c0c6495e0..000000000 --- a/drizzle-orm/src/mysql-core/README.md +++ /dev/null @@ -1,979 +0,0 @@ -
-

Drizzle ORM | MySQL npm

-npm -Driver version -npm bundle size -Discord -NPM -
If you know SQL, you know Drizzle ORM
-
-
- -Drizzle ORM is a TypeScript ORM for SQL databases designed with maximum type safety in mind. It comes with a [drizzle-kit](https://github.com/drizzle-team/drizzle-kit-mirror) CLI companion for automatic SQL migrations generation. This is the documentation for Drizzle ORM version for MySQL. - -| Driver | Support | -|:---------------------------------------------------------------------|:-------:| -| [mysql2](https://github.com/sidorares/node-mysql2) | โœ… | -| [Planetscale Serverless](https://github.com/planetscale/database-js) | โœ… | - -## Installation - -```bash -# npm -npm i drizzle-orm mysql2 -npm i -D drizzle-kit - -# yarn -yarn add drizzle-orm mysql2 -yarn add -D drizzle-kit - -# pnpm -pnpm add drizzle-orm mysql2 -pnpm add -D drizzle-kit -``` - -## SQL schema declaration - -With `drizzle-orm` you declare SQL schema in TypeScript. You can have either one `schema.ts` file with all declarations or you can group them logically in multiple files. We prefer to use single file schema. - -### Single schema file example - -```plaintext -๐Ÿ“ฆ - โ”” ๐Ÿ“‚ src - โ”” ๐Ÿ“‚ db - โ”” ๐Ÿ“œschema.ts -``` - -### Multiple schema files example - -```plaintext -๐Ÿ“ฆ - โ”” ๐Ÿ“‚ src - โ”” ๐Ÿ“‚ db - โ”” ๐Ÿ“‚ schema - โ”œ ๐Ÿ“œusers.ts - โ”œ ๐Ÿ“œcountries.ts - โ”œ ๐Ÿ“œcities.ts - โ”œ ๐Ÿ“œproducts.ts - โ”œ ๐Ÿ“œclients.ts - โ”œ ๐Ÿ“œenums.ts - โ”” ๐Ÿ“œetc.ts -``` - -## Quick start - -```typescript -// schema.ts -import { mysqlTable, serial, text, varchar } from "drizzle-orm/mysql-core"; - -export const users = mysqlTable('users', { - id: serial('id').primaryKey(), - fullName: text('full_name'), - phone: varchar('phone', { length: 256 }), -}); -``` - -### Using Drizzle ORM in Next.js app router - -In order to use Drizzle ORM in the Next.js new app router mode you have to add `mysql2` dependendency to the `experimental.serverComponentsExternalPackages` array in `next.config.js` config file. - -Example `next.config.js` should look like this: - -```ts -/** @type {import("next").NextConfig} */ -const config = { - reactStrictMode: true, - experimental: { - appDir: true, - serverComponentsExternalPackages: ["mysql2"], - }, -} -export default config -``` - -More details about `serverComponentsExternalPackages` can be found in the [Next.js beta docs](https://beta.nextjs.org/docs/api-reference/next-config#servercomponentsexternalpackages). - -> **Note**: New next.js beta docs changes frequently so if the link above doesn't work try this one: [Next.js beta docs](https://beta.nextjs.org/docs/api-reference/next-config.js#servercomponentsexternalpackages). - -### Connect using mysql2 Pool (recommended) - -```typescript -// db.ts -import { drizzle } from 'drizzle-orm/mysql2'; - -import mysql from 'mysql2/promise'; -import { users } from './schema'; - -// create the connection -const poolConnection = mysql.createPool({ - host: 'localhost', - user: 'root', - database: 'test', -}); - -const db = drizzle(poolConnection); - -const allUsers = await db.select().from(users); -``` - -### Connect using mysql2 Client - -```typescript -// db.ts -import { drizzle } from 'drizzle-orm/mysql2'; - -import mysql from 'mysql2/promise'; -import { users } from './schema'; - -// create the connection -const connection = await mysql.createConnection({ - host: 'localhost', - user: 'root', - database: 'test', -}); - -const db = drizzle(connection); - -const allUsers = await db.select().from(users); -``` - -### Connect using PlanetScale Serverless client - -```typescript -// db.ts -import { drizzle } from 'drizzle-orm/planetscale-serverless'; - -import { connect } from '@planetscale/database'; -import { users } from './schema'; - -// create the connection -const connection = connect({ - host: process.env['DATABASE_HOST'], - username: process.env['DATABASE_USERNAME'], - password: process.env['DATABASE_PASSWORD'], -}); - -const db = drizzle(connection); - -const allUsers = await db.select().from(users); -``` - -## Schema declaration - -This is how you declare SQL schema in `schema.ts`. You can declare tables, indexes and constraints, foreign keys and enums. Please pay attention to `export` keyword, they are mandatory if you'll be using [drizzle-kit SQL migrations generator](#migrations). - -```typescript -// db.ts -import { - int, - mysqlEnum, - mysqlTable, - serial, - uniqueIndex, - varchar, -} from 'drizzle-orm/mysql-core'; - -// declaring enum in database -export const countries = mysqlTable('countries', { - id: serial('id').primaryKey(), - name: varchar('name', { length: 256 }), -}, (countries) => ({ - nameIndex: uniqueIndex('name_idx').on(countries.name), -})); - -export const cities = mysqlTable('cities', { - id: serial('id').primaryKey(), - name: varchar('name', { length: 256 }), - countryId: int('country_id').references(() => countries.id), - popularity: mysqlEnum('popularity', ['unknown', 'known', 'popular']), -}); -``` - -### Database and table entity types - -```typescript -// db.ts -import { MySqlDatabase, mysqlTable, serial, text, varchar } from 'drizzle-orm/mysql-core'; -import { InferModel } from 'drizzle-orm'; -import mysql from 'mysql2/promise'; -import { drizzle, MySqlRawQueryResult } from 'drizzle-orm/mysql2'; - -const users = mysqlTable('users', { - id: serial('id').primaryKey(), - fullName: text('full_name'), - phone: varchar('phone', { length: 256 }), -}); - -export type User = InferModel; // return type when queried -export type NewUser = InferModel; // insert type -... - -// init mysql2 Pool or Client -const poolConnection = mysql.createPool({ - host:'localhost', - user: 'root', - database: 'test' -}); - -export const db: MySqlDatabase = drizzle(poolConnection); - -const result: User[] = await db.select().from(users); - -/* type MySqlRawQueryExample is a response from mysql2 driver - type MySqlRawQueryResult = [ResultSetHeader, FieldPacket[]]; - type ResultSetHeader = { - affectedRows: number; - fieldCount: number; - info: string; - insertId: number; - serverStatus: number; - warningStatus: number; - changedRows?: number; - } -*/ -export async function insertUser(user: NewUser): Promise { - return db.insert(users).values(user); -} -``` - -### Declaring indexes, foreign keys and composite primary keys - -```typescript -// db.ts -import { foreignKey, index, int, mysqlTable, serial, uniqueIndex, varchar, AnyMySqlColumn } from 'drizzle-orm/mysql-core'; - -export const countries = mysqlTable('countries', { - id: serial('id').primaryKey(), - name: varchar('name', { length: 256 }), - population: int('population'), - }, (table) => ({ - nameIdx: index('name_idx').on(table.name), // one column - namePopulationIdx: index('name_population_idx').on(table.name, table.population), // multiple columns - uniqueIdx: uniqueIndex('unique_idx').on(table.name), // unique index - }) -); - -export const cities = mysqlTable('cities', { - id: serial('id').primaryKey(), - name: varchar('name', { length: 256 }), - countryId: int('country_id').references(() => countries.id), // inline foreign key - countryName: varchar('country_id', { length: 256 }), - sisterCityId: int('sister_city_id').references((): AnyMySqlColumn => cities.id), // self-referencing foreign key -}, (cities) => ({ - // explicit foreign key with 1 column - countryFk: foreignKey(({ - columns: [cities.countryId], - foreignColumns: [countries.id], - })), - // explicit foreign key with multiple columns - countryIdNameFk: foreignKey(({ - columns: [cities.countryId, cities.countryName], - foreignColumns: [countries.id, countries.name], - })), -})); - -export const cpkTable = mysqlTable('table', { - simple: int('simple'), - columnNotNull: int('column_not_null').notNull(), - columnDefault: int('column_default').default(100), -}, (table) => ({ - cpk: primaryKey(table.simple, table.columnDefault), -})); - -// Index declaration reference -index('name_idx') - .on(table.column1, table.column2, ...) - .using('btree' | 'hash') - .lock('default' | 'none' | 'shared' | 'exclusive') - .algorythm('default' | 'inplace' | 'copy') -``` - -### Customizing the table name - -There are "table creators" available for each dialect, which allow you to customize the table name, for example, to add a prefix or suffix. This is useful if you need to have tables for different environments or applications in the same database. - -> **Note:**: this feature should only be used to customize the table name. If you need to put the table into a different schema, refer to the [Table schemas](#table-schemas) section. - -```ts -import { mysqlTableCreator } from 'drizzle-orm/mysql-core'; - -const mysqlTable = mysqlTableCreator((name) => `myprefix_${name}`); - -const users = mysqlTable('users', { - id: int('id').primaryKey(), - name: text('name').notNull(), -}); -``` - -## Column types - -The list of all column types. You can also create custom types - [see here](https://github.com/drizzle-team/drizzle-orm/blob/main/docs/custom-types.md). - -```typescript -mysqlEnum('popularity', ['unknown', 'known', 'popular']) - -int('...'); -tinyint('name'); -smallint('name'); -mediumint('name'); -bigint('...', { mode: 'number' }); - -real('name', { precision: 1, scale: 1 }); -decimal('name', { precision: 1, scale: 1 }); -double('name', { precision: 1, scale: 1 }); -float('name',); - -serial('name'); - -binary('name'); -varbinary('name', { length: 2 }); - -char('name'); -varchar('name', { length: 2, enum: ['a', 'b'] }); -text('name', { enum: ['a', 'b'] }); - -boolean('name'); - -date('...'); -datetime('...', { mode: 'date' | 'string', fsp: 0..6 }); -time('...', { mode: 'date' | 'string', fsp: 0..6 }); -year('...'); - -timestamp('name'); -timestamp('...', { mode: 'date' | 'string', fsp: 0..6 }) -timestamp('...').defaultNow() - -json('name'); -json('name').$type(); -``` - -### Customizing column data type - -Every column builder has a `.$type()` method, which allows you to customize the data type of the column. This is useful, for example, with branded types. - -```ts -const users = mysqlTable('users', { - id: serial('id').$type().primaryKey(), - jsonField: json('json_field').$type(), -}); -``` - -## Table schemas - -> **Warning** -> If you have tables with same names in different schemas, Drizzle will set result types to `never[]` and return an error from the database. -> -> In this case you may use [alias syntax](/drizzle-orm/src/mysql-core/README.md#join-aliases-and-self-joins). - ---- - -Usage example - -```typescript -// Table in default schema -const publicUsersTable = mysqlTable('users', { - id: serial('id').primaryKey(), - name: text('name').notNull(), - verified: boolean('verified').notNull().default(false), - jsonb: json('jsonb'), - createdAt: timestamp('created_at', { fsp: 2 }).notNull().defaultNow(), -}); - -// Table in custom schema -const mySchema = mysqlSchema('mySchema'); - -const mySchemaUsersTable = mySchema('users', { - id: serial('id').primaryKey(), - name: text('name').notNull(), - verified: boolean('verified').notNull().default(false), - jsonb: json('jsonb'), - createdAt: timestamp('created_at', { fsp: 2 }).notNull().defaultNow(), -}); -``` - -## Select, Insert, Update, Delete - -### Select - -Querying, sorting and filtering. We also support partial select. - -```typescript -... -import { mysqlTable, serial, text, varchar } from 'drizzle-orm/mysql-core'; -import { drizzle } from 'drizzle-orm/mysql2'; -import { and, asc, desc, eq, or } from 'drizzle-orm'; - -const users = mysqlTable('users', { - id: serial('id').primaryKey(), - name: text('full_name'), -}); - -const db = drizzle(...); - -await db.select().from(users); -await db.select().from(users).where(eq(users.id, 42)); - -// you can combine filters with and(...) / or(...) -await db.select().from(users).where(and(eq(users.id, 42), eq(users.name, 'Dan'))); - -await db.select().from(users) - .where(or(eq(users.id, 42), eq(users.id, 1))); - -// partial select -const result = await db - .select({ - mapped1: users.id, - mapped2: users.name, - }) - .from(users); -const { mapped1, mapped2 } = result[0]; - -// limit, offset & order by -await db.select().from(users).limit(10).offset(10); -await db.select().from(users).orderBy(users.name); -await db.select().from(users).orderBy(desc(users.name)); -// you can pass multiple order args -await db.select().from(users).orderBy(asc(users.name), desc(users.name)); -``` - -#### Select from/join raw SQL - -```typescript -await db.select({ x: sql`x` }).from(sql`(select 1) as t(x)`); - -await db - .select({ - x1: sql`g1.x`, - x2: sql`g2.x` - }) - .from(sql`(select 1) as g1(x)`) - .leftJoin(sql`(select 2) as g2(x)`); -``` - -#### Conditionally select fields - -```typescript -async function selectUsers(withName: boolean) { - return db - .select({ - id: users.id, - ...(withName ? { name: users.name } : {}), - }) - .from(users); -} - -const users = await selectUsers(true); -``` - -#### WITH clause - -```typescript -const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); -const result = await db.with(sq).select().from(sq); -``` - -> **Note**: Keep in mind that if you need to select raw `sql` in a WITH subquery and reference that field in other queries, you must add an alias to it: - -```typescript -const sq = db.$with('sq').as(db.select({ name: sql`upper(${users.name})`.as('name') }).from(users)); -const result = await db.with(sq).select({ name: sq.name }).from(sq); -``` - -Otherwise, the field type will become `DrizzleTypeError` and you won't be able to reference it in other queries. If you ignore the type error and still try to reference the field, you will get a runtime error, because we cannot reference that field without an alias. - -#### Select from subquery - -```typescript -const sq = db.select().from(users).where(eq(users.id, 42)).as('sq'); -const result = await db.select().from(sq); -``` - -Subqueries in joins are supported, too: - -```typescript -const result = await db.select().from(users).leftJoin(sq, eq(users.id, sq.id)); -``` - -#### Querying large datasets - -If you need to return a very large amount of rows from a query and you don't want to load them all into memory, you can use `.iterator()` to convert the query into an async iterator: - -```typescript -const iterator = await db.select().from(users).iterator(); -for await (const row of iterator) { - console.log(row); -} -``` - -It also works with prepared statements: - -```typescript -const query = await db.select().from(users).prepare(); -const iterator = await query.iterator(); -for await (const row of iterator) { - console.log(row); -} -``` - -#### List of all filter operators - -```typescript -eq(column, value) -eq(column1, column2) -ne(column, value) -ne(column1, column2) - -less(column, value) -lessEq(column, value) - -gt(column, value) -gt(column1, column2) -gte(column, value) -gte(column1, column2) -lt(column, value) -lt(column1, column2) -lte(column, value) -lte(column1, column2) - -isNull(column) -isNotNull(column) - -inArray(column, values[]) -inArray(column, sqlSubquery) -notInArray(column, values[]) -notInArray(column, sqlSubquery) - -exists(sqlSubquery) -notExists(sqlSubquery) - -between(column, min, max) -notBetween(column, min, max) - -like(column, value) -notLike(column, value) - -not(sqlExpression) - -and(...expressions: SQL[]) -or(...expressions: SQL[]) - -``` - -### Insert - -```typescript -import { mysqlTable, serial, text, timestamp } from 'drizzle-orm/mysql-core'; -import { InferModel } from 'drizzle-orm'; -import { drizzle } from 'drizzle-orm/mysql2'; - -const users = mysqlTable('users', { - id: serial('id').primaryKey(), - name: text('name'), - createdAt: timestamp('created_at'), -}); - -type NewUser = InferModel; - -const db = drizzle(...); - -const newUser: NewUser = { - name: 'Andrew', - createdAt: new Date(), -}; - -await db.insert(users).values(newUser); -``` - -#### Insert several items - -```ts -await db.insert(users) - .values( - { - name: 'Andrew', - createdAt: new Date(), - }, - { - name: 'Dan', - createdAt: new Date(), - }, - ); -``` - -#### Insert array of items - -```ts -const newUsers: NewUser[] = [ - { - name: 'Andrew', - createdAt: new Date(), - }, - { - name: 'Dan', - createdAt: new Date(), - }, -]; - -await db.insert(users).values(newUsers); -``` - -### Update and Delete - -```typescript -await db.update(users) - .set({ name: 'Mr. Dan' }) - .where(eq(users.name, 'Dan')); - -await db.delete(users) - .where(eq(users.name, 'Dan')); -``` - -### Joins - -> **Note**: for in-depth partial select joins documentation, refer to [this page](/docs/joins.md). - -#### Many-to-one - -```typescript -const cities = mysqlTable('cities', { - id: serial('id').primaryKey(), - name: text('name'), -}); - -const users = mysqlTable('users', { - id: serial('id').primaryKey(), - name: text('name'), - cityId: int('city_id').references(() => cities.id), -}); - -const result = db.select().from(cities).leftJoin(users, eq(cities.id, users.cityId)); -``` - -#### Many-to-many - -```typescript -const users = mysqlTable('users', { - id: serial('id').primaryKey(), - name: text('name'), -}); - -const chatGroups = mysqlTable('chat_groups', { - id: serial('id').primaryKey(), - name: text('name'), -}); - -const usersToChatGroups = mysqlTable('usersToChatGroups', { - userId: int('user_id').notNull().references(() => users.id), - groupId: int('group_id').notNull().references(() => chatGroups.id), -}); - -// querying user group with id 1 and all the participants(users) -const result = await db - .select() - .from(usersToChatGroups) - .leftJoin(users, eq(usersToChatGroups.userId, users.id)) - .leftJoin(chatGroups, eq(usersToChatGroups.groupId, chatGroups.id)) - .where(eq(chatGroups.id, 1)); -``` - -#### Join aliases and self-joins - -```typescript -import { ..., alias } from 'drizzle-orm/mysql-core'; - -export const files = mysqlTable('folders', { - name: text('name').notNull(), - parent: text('parent_folder') -}) - -const nestedFiles = alias(files, 'nested_files'); - -// will return files and folders and nested files for each folder at root dir -const result = await db - .select() - .from(files) - .leftJoin(nestedFiles, eq(files.name, nestedFiles.name)) - .where(eq(files.parent, '/')); -``` - -#### Join using partial select - -```typescript -// Select user ID and city ID and name -const result1 = await db - .select({ - userId: users.id, - cityId: cities.id, - cityName: cities.name, - }) - .from(cities).leftJoin(users, eq(users.cityId, cities.id)); - -// Select all fields from users and only id and name from cities -const result2 = await db - .select({ - user: users, - city: { - id: cities.id, - name: cities.name, - }, - }) - .from(cities).leftJoin(users, eq(users.cityId, cities.id)); -``` - -## Transactions - -```ts -await db.transaction(async (tx) => { - await tx.insert(users).values(newUser); - await tx.update(users).set({ name: 'Mr. Dan' }).where(eq(users.name, 'Dan')); - await tx.delete(users).where(eq(users.name, 'Dan')); -}); -``` - -### Nested transactions - -```ts -await db.transaction(async (tx) => { - await tx.insert(users).values(newUser); - await tx.transaction(async (tx2) => { - await tx2.update(users).set({ name: 'Mr. Dan' }).where(eq(users.name, 'Dan')); - await tx2.delete(users).where(eq(users.name, 'Dan')); - }); -}); -``` - -### Transaction settings - -```ts -interface MySqlTransactionConfig { - withConsistentSnapshot?: boolean; - accessMode?: 'read only' | 'read write'; - isolationLevel: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable'; -} - -await db.transaction(async (tx) => { ... }, { - withConsistentSnapshot: true, - accessMode: 'read only', - isolationLevel: 'read committed', -}); -``` - -## Query builder - -Drizzle ORM provides a standalone query builder that allows you to build queries without creating a database instance. - -```ts -import { queryBuilder as qb } from 'drizzle-orm/mysql-core'; - -const query = qb.select().from(users).where(eq(users.name, 'Dan')); -const { sql, params } = query.toSQL(); -``` - -## Views (WIP) - -> **Warning**: views are currently only implemented on the ORM side. That means you can query the views that already exist in the database, but they won't be added to drizzle-kit migrations or `db push` yet. - -### Creating a view - -```ts -import { mysqlView } from 'drizzle-orm/mysql-core'; - -const newYorkers = mysqlView('new_yorkers').as((qb) => qb.select().from(users).where(eq(users.cityId, 1))); -``` - -#### Full view definition syntax - -```ts -const newYorkers = mysqlView('new_yorkers') - .algorithm('merge') - .definer('root@localhost') - .sqlSecurity('definer') - .as((qb) => { - const sq = qb - .$with('sq') - .as( - qb.select({ userId: users.id, cityId: cities.id }) - .from(users) - .leftJoin(cities, eq(cities.id, users.homeCity)) - .where(sql`${users.age1} > 18`), - ); - return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`); - }); -``` - -> **Warning**: All the parameters inside the query will be inlined, instead of replaced by `$1`, `$2`, etc. - -You can also use the [`queryBuilder` instance](#query-builder) directly instead of passing a callback, if you already have it imported. - -```ts -import { queryBuilder as qb } from 'drizzle-orm/mysql-core'; - -const newYorkers = mysqlView('new_yorkers').as(qb.select().from(users2Table).where(eq(users2Table.cityId, 1))); -``` - -### Using raw SQL in a view query - -In case you need to specify the view query using a syntax that is not supported by the query builder, you can directly use SQL. In that case, you also need to specify the view shape. - -```ts -const newYorkers = mysqlView('new_yorkers', { - id: serial('id').primaryKey(), - name: text('name').notNull(), - cityId: int('city_id').notNull(), -}).as(sql`select * from ${users} where ${eq(users.cityId, 1)}`); -``` - -### Describing existing views - -There are cases when you are given readonly access to an existing view. In such cases you can just describe the view shape without specifying the query itself or using it in the migrations. - -```ts -const newYorkers = mysqlView('new_yorkers', { - userId: int('user_id').notNull(), - cityId: int('city_id'), -}).existing(); -``` - -## Prepared statements - -```typescript -const query = db.select().from(users).where(eq(users.name, 'Dan')).prepare(); - -const result = await query.execute(); -``` - -### Prepared statements with parameters - -```typescript -import { placeholder } from 'drizzle-orm/mysql-core'; - -const query = db.select().from(users).where(eq(users.name, placeholder('name'))).prepare(); - -const result = await query.execute({ name: 'Dan' }); -``` - -## Raw queries execution - -If you have some complex queries to execute and drizzle-orm can't handle them yet, you can use the `db.execute` method to execute raw queries. - -```typescript -// it will automatically run a parametrized query! -const res: MySqlQueryResult<{ id: number; name: string }> = await db.execute< - { id: number; name: string } ->(sql`select * from ${users} where ${users.id} = ${userId}`); -``` - -## Migrations - -### Automatic SQL migrations generation with drizzle-kit - -[Drizzle Kit](https://www.npmjs.com/package/drizzle-kit) is a CLI migrator tool for Drizzle ORM. It is probably one and only tool that lets you completely automatically generate SQL migrations and covers ~95% of the common cases like deletions and renames by prompting user input. - -Check out the [docs for Drizzle Kit](https://github.com/drizzle-team/drizzle-kit-mirror) - -For schema file: - -```typescript -import { index, int, mysqlTable, serial, varchar } from 'drizzle-orm/mysql-core'; - -export const users = mysqlTable('users', { - id: serial('id').primaryKey(), - fullName: varchar('full_name', { length: 256 }), -}, (users) => ({ - nameIdx: index('name_idx').on(users.fullName), -})); - -export const authOtps = mysqlTable('auth_otp', { - id: serial('id').primaryKey(), - phone: varchar('phone', { length: 256 }), - userId: int('user_id').references(() => users.id), -}); -``` - -It will generate: - -```SQL -CREATE TABLE `users` ( - `id` int PRIMARY KEY, - `full_name` varchar(256) -); - - -CREATE TABLE `auth_otp` ( - `id` serial PRIMARY KEY, - `phone` varchar(256), - `user_id` int -); - - -ALTER TABLE auth_otp ADD CONSTRAINT auth_otp_user_id_users_id_fk FOREIGN KEY (`user_id`) REFERENCES users(`id`) ; -CREATE INDEX name_idx ON users (`full_name`); -``` - -And you can run migrations manually or using our embedded migrations module - -```typescript -import { drizzle } from 'drizzle-orm/mysql2'; -import { migrate } from 'drizzle-orm/mysql2/migrator'; -import mysql from 'mysql2/promise'; - -// create the connection -const poolConnection = mysql.createPool({ - host: 'localhost', - user: 'root', - database: 'test', - multipleStatements: true, -}); - -const db = drizzle(poolConnection); - -// this will automatically run needed migrations on the database -await migrate(db, { migrationsFolder: './drizzle' }); -``` - -## Logging - -To enable default query logging, just pass `{ logger: true }` to the `drizzle` function: - -```typescript -import { drizzle } from 'drizzle-orm/mysql2'; - -const db = drizzle(pool, { logger: true }); -``` - -You can change the logs destination by creating a `DefaultLogger` instance and providing a custom `writer` to it: - -```typescript -import { DefaultLogger, LogWriter } from 'drizzle-orm/logger'; -import { drizzle } from 'drizzle-orm/mysql2'; - -class MyLogWriter implements LogWriter { - write(message: string) { - // Write to file, console, etc. - } -} - -const logger = new DefaultLogger({ writer: new MyLogWriter() }); - -const db = drizzle(pool, { logger }); -``` - -You can also create a custom logger: - -```typescript -import { Logger } from 'drizzle-orm'; -import { drizzle } from 'drizzle-orm/mysql2'; - -class MyLogger implements Logger { - logQuery(query: string, params: unknown[]): void { - console.log({ query, params }); - } -} - -const db = drizzle(pool, { logger: new MyLogger() }); -``` - -## Table introspect API - -See [dedicated docs](/docs/table-introspect-api.md). diff --git a/drizzle-orm/src/pg-core/README.md b/drizzle-orm/src/pg-core/README.md deleted file mode 100644 index 5444bc401..000000000 --- a/drizzle-orm/src/pg-core/README.md +++ /dev/null @@ -1,1057 +0,0 @@ -
-

Drizzle ORM | PostgreSQL npm

-npm -npm bundle size -Discord -License -
If you know SQL, you know Drizzle ORM
-
-
- -Drizzle ORM is a TypeScript ORM for SQL databases designed with maximum type safety in mind. It comes with a [drizzle-kit](https://github.com/drizzle-team/drizzle-kit-mirror) CLI companion for automatic SQL migrations generation. This is the documentation for Drizzle ORM version for PostgreSQL. - -| Driver | Support | | -|:-------------------------------------------------------------------------------------------------|:-------:|:----------------------------------------------:| -| [node-postgres](https://github.com/brianc/node-postgres) | โœ… | | -| [postgres.js](https://github.com/porsager/postgres) | โœ… | [Docs](/drizzle-orm/src/postgres-js/README.md) | -| [NeonDB Serverless](https://github.com/neondatabase/serverless) | โœ… | | -| [AWS Data API](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-rds-data/README.md) | โœ… | | -| [Vercel Postgres](https://vercel.com/docs/storage/vercel-postgres/quickstart) | โœ… | | - -## Installation - -```bash -# npm -npm i drizzle-orm pg -npm i -D @types/pg drizzle-kit - -# yarn -yarn add drizzle-orm pg -yarn add -D @types/pg drizzle-kit - -# pnpm -pnpm add drizzle-orm pg -pnpm add -D @types/pg drizzle-kit -``` - -## SQL schema declaration - -In Drizzle ORM, you declare SQL schema with TypeScript. You can either have a single `schema.ts` file with all declarations or you can group them logically in multiple files. We prefer to use single file schema. - -### Single schema file example - -```plaintext -๐Ÿ“ฆ - โ”” ๐Ÿ“‚ src - โ”” ๐Ÿ“‚ db - โ”” ๐Ÿ“œschema.ts -``` - -### Multiple schema files example - -```plaintext -๐Ÿ“ฆ - โ”” ๐Ÿ“‚ src - โ”” ๐Ÿ“‚ db - โ”” ๐Ÿ“‚ schema - โ”œ ๐Ÿ“œusers.ts - โ”œ ๐Ÿ“œcountries.ts - โ”œ ๐Ÿ“œcities.ts - โ”œ ๐Ÿ“œproducts.ts - โ”œ ๐Ÿ“œclients.ts - โ”œ ๐Ÿ“œenums.ts - โ”” ๐Ÿ“œetc.ts -``` - -## Quick start - -```typescript -// schema.ts -export const users = pgTable('users', { - id: serial('id').primaryKey(), - fullName: text('full_name'), - phone: varchar('phone', { length: 256 }), -}); -``` - -### Connect using node-postgres Pool (recommended) - -```typescript -// db.ts -import { pgTable, serial, text, varchar } from 'drizzle-orm/pg-core'; -import { drizzle } from 'drizzle-orm/node-postgres'; -import { Pool } from 'pg'; - -import { users } from './schema'; - -const pool = new Pool({ - connectionString: 'postgres://user:password@host:port/db', -}); -// or -const pool = new Pool({ - host: '127.0.0.1', - port: 5432, - user: 'postgres', - password: 'password', - database: 'db_name', -}); - -const db = drizzle(pool); - -const allUsers = await db.select().from(users); -``` - -### Connect using node-postgres Client - -```typescript -// db.ts -import { pgTable, serial, text, varchar } from 'drizzle-orm/pg-core'; -import { drizzle } from 'drizzle-orm/node-postgres'; -import { Client } from 'pg'; - -import { users } from './schema'; - -const client = new Client({ - connectionString: 'postgres://user:password@host:port/db', -}); -// or -const client = new Client({ - host: '127.0.0.1', - port: 5432, - user: 'postgres', - password: 'password', - database: 'db_name', -}); - -await client.connect(); - -const db = drizzle(client); - -const allUsers = await db.select().from(users); -``` - -### Connect using aws data api client - -```typescript -import { drizzle, migrate } from 'drizzle-orm/aws-data-api/pg'; - -const rdsClient = new RDSDataClient({}); - -const db = drizzle(rdsClient, { - database: '', - secretArn: '', - resourceArn: '', -}); -``` - -### Connect to Vercel Postgres - -```typescript -import { drizzle } from 'drizzle-orm/vercel-postgres'; -import { sql } from "@vercel/postgres"; - -const db = drizzle(sql); - -db.select(...) -``` - -## Schema declaration - -This is how you declare SQL schema in `schema.ts`. You can declare tables, indexes and constraints, foreign keys and enums. Please pay attention to `export` keyword, they are mandatory if you'll be using [drizzle-kit SQL migrations generator](#migrations). - -```typescript -import { pgEnum, pgTable, serial, integer, uniqueIndex, varchar } from 'drizzle-orm/pg-core'; - -// declaring enum in database -export const popularityEnum = pgEnum('popularity', ['unknown', 'known', 'popular']); - -export const countries = pgTable('countries', { - id: serial('id').primaryKey(), - name: varchar('name', { length: 256 }), -}, (countries) => { - return { - nameIndex: uniqueIndex('name_idx').on(countries.name), - } -}); - -export const cities = pgTable('cities', { - id: serial('id').primaryKey(), - name: varchar('name', { length: 256 }), - countryId: integer('country_id').references(() => countries.id), - popularity: popularityEnum('popularity'), -}); -``` - -### Database and table entity types - -```typescript -import { pgTable, serial, text, varchar } from 'drizzle-orm/pg-core'; -import { InferModel } from 'drizzle-orm'; -import { drizzle, NodePgDatabase } from 'drizzle-orm/node-postgres'; - -const users = pgTable('users', { - id: serial('id').primaryKey(), - fullName: text('full_name'), - phone: varchar('phone', { length: 256 }), -}); - -export type User = InferModel; // return type when queried -export type NewUser = InferModel; // insert type -... - -// init node-postgres Pool or Client -const pool = new Pool(...); - -export const db: NodePgDatabase = drizzle(pool); - -const result: User[] = await db.select().from(users); - -export async function insertUser(user: NewUser): Promise { - return db.insert(users).values(user).returning(); -} -``` - -### Declaring indexes, foreign keys and composite primary keys - -```typescript -import { foreignKey, index, uniqueIndex, integer, pgTable, serial, varchar, AnyPgColumn } from 'drizzle-orm/pg-core'; - -export const countries = pgTable('countries', { - id: serial('id').primaryKey(), - name: varchar('name', { length: 256 }), - population: integer('population'), - }, (countries) => { - return { - nameIdx: index('name_idx').on(countries.name), // one column - namePopulationIdx: index('name_population_idx').on(countries.name, countries.population), // multiple columns - uniqueIdx: uniqueIndex('unique_idx').on(countries.name), // unique index - } - }) -); - -export const cities = pgTable('cities', { - id: serial('id').primaryKey(), - name: varchar('name', { length: 256 }), - countryId: integer('country_id').references(() => countries.id), // inline foreign key - countryName: varchar('country_name'), - sisterCityId: integer('sister_city_id').references((): AnyPgColumn => cities.id), // self-referencing foreign key -}, (cities) => { - return { - // explicit foreign key with 1 column - countryFk: foreignKey({ - columns: [cities.countryId], - foreignColumns: [countries.id], - }), - // explicit foreign key with multiple columns - countryIdNameFk: foreignKey({ - columns: [cities.countryId, cities.countryName], - foreignColumns: [countries.id, countries.name], - }, - } -}); - -export const cpkTable = pgTable('table', { - column1: integer('column1').default(10).notNull(), - column2: integer('column2'), - column3: integer('column3'), -}, (table) => ({ - cpk: primaryKey(table.column1, table.column2), -})); - -// Index declaration reference -index('name') - .on(table.column1, table.column2, ...) - .onOnly(table.column1, table.column2, ...) - .concurrently() - .using(sql``) // sql expression - .asc() - .desc() - .nullsFirst() - .nullsLast() - .where(sql``) // sql expression -``` - -### Customizing the table name - -There is a "table creator" available, which allow you to customize the table name, for example, to add a prefix or suffix. This is useful if you need to have tables for different environments or applications in the same database. - -> **Note:**: this feature should only be used to customize the table name. If you need to put the table into a different schema, refer to the [Table schemas](#table-schemas) section. - -```ts -import { pgTableCreator } from 'drizzle-orm/pg-core'; - -const pgTable = pgTableCreator((name) => `myprefix_${name}`); - -const users = pgTable('users', { - id: int('id').primaryKey(), - name: text('name').notNull(), -}); -``` - -## Column types - -The list of all column types. You can also create custom types - [see here](/docs/custom-types.md). - -```typescript -export const popularityEnum = pgEnum('popularity', ['unknown', 'known', 'popular']); // declare enum type -popularityEnum('column_name'); // declare enum column - -smallint('...'); -integer('...'); -bigint('...', { mode: 'number' | 'bigint' }); - -boolean('...'); -text('...'); -text('...', { enum: ['one', 'two', 'three'] }); -varchar('...'); -varchar('...', { enum: ['one', 'two', 'three'] }); -varchar('...', { length: 256 }); // with length limit - -serial('...'); -bigserial('...', { mode: 'number' | 'bigint' }); - -decimal('...', { precision: 100, scale: 2 }); -numeric('...', { precision: 100, scale: 2 }); - -real('...'); -doublePrecision('...'); - -json('...').$type<...>(); -json('...').$type(); -jsonb('...').$type<...>(); -jsonb('...').$type(); - -time('...'); -time('...', { precision: 6, withTimezone: true }); -timestamp('...'); -timestamp('...', { mode: 'date' | 'string', precision: 0..6, withTimezone: true }); -timestamp('...').defaultNow(); -date('...'); -date('...', { mode: 'string' | 'date' }); -interval('...'); -interval('...', { fields: 'day' | 'month' | '...' , precision: 0..6 }); - -column.primaryKey(); -column.notNull(); -column.default(...); -timeColumn.defaultNow(); -uuidColumn.defaultRandom(); - -integer('...').array(3).array(4); -``` - -### Customizing column data type - -Every column builder has a `.$type()` method, which allows you to customize the data type of the column. This is useful, for example, with branded types. - -```ts -const users = pgTable('users', { - id: serial('id').$type().primaryKey(), - jsonField: json('json_field').$type(), -}); -``` - -## Table schemas - -Drizzle won't append any schema before table definition by default. So if your tables are in `public` schema drizzle generate -> `select * from "users"` - -But if you will specify any custom schema you want, then drizzle will generate -> `select * from "custom_schema"."users"` - -> **Warning** -> If you will have tables with same names in different schemas then drizzle will respond with `never[]` error in result types and error from database -> -> In this case you may use [alias syntax](/drizzle-orm/src/pg-core/README.md#join-aliases-and-self-joins) - -### Usage example - -```typescript -// Table in default schema -const publicUsersTable = pgTable('users', { - id: serial('id').primaryKey(), - name: text('name').notNull(), - verified: boolean('verified').notNull().default(false), - jsonb: jsonb('jsonb').$type(), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), -}); - - -// Table in custom schema -const mySchema = pgSchema('mySchema'); - -const usersTable = mySchema.table('users', { - id: serial('id').primaryKey(), - name: text('name').notNull(), - verified: boolean('verified').notNull().default(false), - jsonb: jsonb('jsonb').$type(), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), -}); -``` - -## Select, Insert, Update, Delete - -### Select - -Querying, sorting and filtering. We also support partial select. - -```typescript -... -import { pgTable, serial, text, varchar } from 'drizzle-orm/pg-core'; -import { drizzle } from 'drizzle-orm/node-postgres'; -import { and, asc, desc, eq, or } from 'drizzle-orm'; - -const users = pgTable('users', { - id: serial('id').primaryKey(), - name: text('full_name'), -}); - -const db = drizzle(...); - -await db.select().from(users); -await db.select().from(users).where(eq(users.id, 42)); - -// you can combine filters with and(...) / or(...) -await db.select().from(users).where(and(eq(users.id, 42), eq(users.name, 'Dan'))); - -await db.select().from(users).where(or(eq(users.id, 42), eq(users.id, 1))); - -// partial select -const result = await db.select({ - mapped1: users.id, - mapped2: users.name, - }).from(users); -const { mapped1, mapped2 } = result[0]; - -// limit, offset & order by -await db.select().from(users).limit(10).offset(10); -await db.select().from(users).orderBy(users.name); -await db.select().from(users).orderBy(desc(users.name)); -// you can pass multiple order args -await db.select().from(users).orderBy(asc(users.name), desc(users.name)); -``` - -#### Select from/join raw SQL - -```typescript -await db.select({ x: sql`x` }).from(sql`generate_series(2, 4) as g(x)`); - -await db - .select({ - x1: sql`g1.x`, - x2: sql`g2.x` - }) - .from(sql`generate_series(2, 4) as g1(x)`) - .leftJoin(sql`generate_series(2, 4) as g2(x)`); -``` - -#### Conditionally select fields - -```typescript -async function selectUsers(withName: boolean) { - return db - .select({ - id: users.id, - ...(withName ? { name: users.name } : {}), - }) - .from(users); -} - -const users = await selectUsers(true); -``` - -#### WITH clause - -```typescript -const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); -const result = await db.with(sq).select().from(sq); -``` - -> **Note**: Keep in mind that if you need to select raw `sql` in a WITH subquery and reference that field in other queries, you must add an alias to it: - -```typescript -const sq = db.$with('sq').as(db.select({ name: sql`upper(${users.name})`.as('name') }).from(users)); -const result = await db.with(sq).select({ name: sq.name }).from(sq); -``` - -Otherwise, the field type will become `DrizzleTypeError` and you won't be able to reference it in other queries. If you ignore the type error and still try to reference the field, you will get a runtime error, because we cannot reference that field without an alias. - -#### Select from subquery - -```typescript -const sq = db.select().from(users).where(eq(users.id, 42)).as('sq'); -const result = await db.select().from(sq); -``` - -Subqueries in joins are supported, too: - -```typescript -const result = await db.select().from(users).leftJoin(sq, eq(users.id, sq.id)); -``` - -#### List of all filter operators - -```typescript -eq(column, value) -eq(column1, column2) -ne(column, value) -ne(column1, column2) - -less(column, value) -lessEq(column, value) - -gt(column, value) -gt(column1, column2) -gte(column, value) -gte(column1, column2) -lt(column, value) -lt(column1, column2) -lte(column, value) -lte(column1, column2) - -isNull(column) -isNotNull(column) - -inArray(column, values[]) -inArray(column, sqlSubquery) -notInArray(column, values[]) -notInArray(column, sqlSubquery) - -exists(sqlSubquery) -notExists(sqlSubquery) - -between(column, min, max) -notBetween(column, min, max) - -like(column, value) -like(column, value) -ilike(column, value) -notIlike(column, value) - -not(sqlExpression) - -and(...expressions: SQL[]) -or(...expressions: SQL[]) -``` - -### Insert - -```typescript -import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core'; -import { InferModel } from 'drizzle-orm'; -import { drizzle } from 'drizzle-orm/node-postgres'; - -const users = pgTable('users', { - id: serial('id').primaryKey(), - name: text('name'), - createdAt: timestamp('created_at'), -}); - -type NewUser = InferModel; - -const db = drizzle(...); - -const newUser: NewUser = { - name: 'Andrew', - createdAt: new Date(), -}; - -await db.insert(users).values(newUser); - -const insertedUsers/*: NewUser[]*/ = await db.insert(users).values(newUsers).returning(); - -const insertedUsersIds/*: { insertedId: number }[]*/ = await db.insert(users) - .values(newUsers) - .returning({ insertedId: users.id }); -``` - -#### Insert several items - -```ts -await db.insert(users) - .values( - { - name: 'Andrew', - createdAt: new Date(), - }, - { - name: 'Dan', - createdAt: new Date(), - }, - ); -``` - -#### Insert array of items - -```ts -const newUsers: NewUser[] = [ - { - name: 'Andrew', - createdAt: new Date(), - }, - { - name: 'Dan', - createdAt: new Date(), - }, -]; - -await db.insert(users).values(newUsers); -``` - -### Upsert (Insert with on conflict statement) - -```typescript -await db.insert(users) - .values({ id: 1, name: 'Dan' }) - .onConflictDoUpdate({ target: users.id, set: { name: 'John' } }); - -await db.insert(users) - .values({ id: 1, name: 'John' }) - .onConflictDoNothing(); - -await db.insert(users) - .values({ id: 1, name: 'John' }) - .onConflictDoNothing({ target: users.id }); - -await db.insert(users) - .values({ id: 1, name: 'John' }) - .onConflictDoUpdate({ - target: users.id, - set: { name: 'John1' }, - where: sql`${users.createdAt} > '2023-01-01'::date`, - }); -``` - -### Update and Delete - -```typescript -await db.update(users) - .set({ name: 'Mr. Dan' }) - .where(eq(users.name, 'Dan')); - -const updatedUser: InferModel = await db.update(users) - .set({ name: 'Mr. Dan' }) - .where(eq(users.name, 'Dan')) - .returning(); - -const updatedUserId: { updatedId: number }[] = await db.update(users) - .set({ name: 'Mr. Dan' }) - .where(eq(users.name, 'Dan')) - .returning({ updatedId: users.id }); - -await db.delete(users) - .where(eq(users.name, 'Dan')); - -const deletedUser: InferModel = await db.delete(users) - .where(eq(users.name, 'Dan')) - .returning(); - -const deletedUserId: { deletedId: number }[] = await db.delete(users) - .where(eq(users.name, 'Dan')) - .returning({ deletedId: users.id }); -``` - -### Joins - -> **Note**: for in-depth partial select joins documentation, refer to [this page](/docs/joins.md). - -#### Many-to-one - -```typescript -const cities = pgTable('cities', { - id: serial('id').primaryKey(), - name: text('name'), -}); - -const users = pgTable('users', { - id: serial('id').primaryKey(), - name: text('name'), - cityId: integer('city_id').references(() => cities.id), -}); - -const result = db.select().from(cities).leftJoin(users, eq(cities.id, users.cityId)); -``` - -#### Many-to-many - -```typescript -const users = pgTable('users', { - id: serial('id').primaryKey(), - name: text('name'), -}); - -const chatGroups = pgTable('chat_groups', { - id: serial('id').primaryKey(), - name: text('name'), -}); - -const usersToChatGroups = pgTable('usersToChatGroups', { - userId: integer('user_id').notNull().references(() => users.id), - groupId: integer('group_id').notNull().references(() => chatGroups.id), -}); - -// querying user group with id 1 and all the participants(users) -const result = await db - .select() - .from(usersToChatGroups) - .leftJoin(users, eq(usersToChatGroups.userId, users.id)) - .leftJoin(chatGroups, eq(usersToChatGroups.groupId, chatGroups.id)) - .where(eq(chatGroups.id, 1)); -``` - -#### Join aliases and self-joins - -```typescript -import { ..., alias } from 'drizzle-orm/pg-core'; - -export const files = pgTable('folders', { - name: text('name').notNull(), - parent: text('parent_folder') -}) - -const nestedFiles = alias(files, 'nested_files'); - -// will return files and folders and nested files for each folder at root dir -const result = await db - .select() - .from(files) - .leftJoin(nestedFiles, eq(files.name, nestedFiles.name)) - .where(eq(files.parent, '/')); -``` - -#### Join using partial select - -```typescript -// Select user ID and city ID and name -const result1 = await db - .select({ - userId: users.id, - cityId: cities.id, - cityName: cities.name, - }) - .from(cities) - .leftJoin(users, eq(users.cityId, cities.id)); - -// Select all fields from users and only id and name from cities -const result2 = await db.select({ - user: users, - city: { - id: cities.id, - name: cities.name, - }, -}).from(cities).leftJoin(users, eq(users.cityId, cities.id)); -``` - -## Transactions - -```ts -await db.transaction(async (tx) => { - await tx.insert(users).values(newUser); - await tx.update(users).set({ name: 'Mr. Dan' }).where(eq(users.name, 'Dan')); - await tx.delete(users).where(eq(users.name, 'Dan')); -}); -``` - -### Nested transactions - -```ts -await db.transaction(async (tx) => { - await tx.insert(users).values(newUser); - await tx.transaction(async (tx2) => { - await tx2.update(users).set({ name: 'Mr. Dan' }).where(eq(users.name, 'Dan')); - await tx2.delete(users).where(eq(users.name, 'Dan')); - }); -}); -``` - -### Transaction settings - -```ts -interface PgTransactionConfig { - isolationLevel?: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable'; - accessMode?: 'read only' | 'read write'; - deferrable?: boolean; -} - -await db.transaction(async (tx) => { ... }, { - isolationLevel: 'read committed', - accessMode: 'read write', - deferrable: true, -}); -``` - -## Query builder - -Drizzle ORM provides a standalone query builder that allows you to build queries without creating a database instance. - -```ts -import { queryBuilder as qb } from 'drizzle-orm/pg-core'; - -const query = qb.select().from(users).where(eq(users.name, 'Dan')); -const { sql, params } = query.toSQL(); -``` - -## Views (WIP) - -> **Warning**: views are currently only implemented on the ORM side. That means you can query the views that already exist in the database, but they won't be added to drizzle-kit migrations or `db push` yet. - -There are two types of views in PostgreSQL: [regular](https://www.postgresql.org/docs/current/sql-createview.html) and [materialized](https://www.postgresql.org/docs/current/sql-creatematerializedview.html). - -### Creating a view - -```ts -import { pgView } from 'drizzle-orm/pg-core'; - -// regular view -const newYorkers = pgView('new_yorkers').as((qb) => qb.select().from(users).where(eq(users.cityId, 1))); - -// materialized view -const newYorkers = pgMaterializedView('new_yorkers').as((qb) => qb.select().from(users).where(eq(users.cityId, 1))); -``` - -#### Full view definition syntax - -```ts -// regular view -const newYorkers = pgView('new_yorkers') - .with({ - checkOption: 'cascaded', - securityBarrier: true, - securityInvoker: true, - }) - .as((qb) => { - const sq = qb - .$with('sq') - .as( - qb.select({ userId: users.id, cityId: cities.id }) - .from(users) - .leftJoin(cities, eq(cities.id, users.homeCity)) - .where(sql`${users.age1} > 18`), - ); - return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`); - }); - -// materialized view -const newYorkers2 = pgMaterializedView('new_yorkers') - .using('btree') - .with({ - fillfactor: 90, - toast_tuple_target: 0.5, - autovacuum_enabled: true, - ... - }) - .tablespace('custom_tablespace') - .withNoData() - .as((qb) => { - const sq = qb - .$with('sq') - .as( - qb.select({ userId: users.id, cityId: cities.id }) - .from(users) - .leftJoin(cities, eq(cities.id, users.homeCity)) - .where(sql`${users.age1} > 18`), - ); - return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`); - }); -``` - -> **Warning**: All the parameters inside the query will be inlined, instead of replaced by `$1`, `$2`, etc. - -You can also use the [`queryBuilder` instance](#query-builder) directly instead of passing a callback, if you already have it imported. - -```ts -import { queryBuilder as qb } from 'drizzle-orm/pg-core'; - -// regular view -const newYorkers = pgView('new_yorkers').as(qb.select().from(users2Table).where(eq(users2Table.cityId, 1))); - -// materialized view -const newYorkers = pgMaterializedView('new_yorkers').as(qb.select().from(users2Table).where(eq(users2Table.cityId, 1))); -``` - -### Using raw SQL in a view query - -In case you need to specify the view query using a syntax that is not supported by the query builder, you can directly use SQL. In that case, you also need to specify the view shape. - -```ts -// regular view -const newYorkers = pgView('new_yorkers', { - id: serial('id').primaryKey(), - name: text('name').notNull(), - cityId: integer('city_id').notNull(), -}).as(sql`select * from ${users} where ${eq(users.cityId, 1)}`); - -// materialized view -const newYorkers = pgMaterializedView('new_yorkers', { - id: serial('id').primaryKey(), - name: text('name').notNull(), - cityId: integer('city_id').notNull(), -}).as(sql`select * from ${users} where ${eq(users.cityId, 1)}`); -``` - -### Describing existing views - -There are cases when you are given readonly access to an existing view. In such cases you can just describe the view shape without specifying the query itself or using it in the migrations. - -```ts -// regular view -const newYorkers = pgView('new_yorkers', { - userId: integer('user_id').notNull(), - cityId: integer('city_id'), -}).existing(); - -// materialized view won't make any difference in this case, but you can still use it for consistency -const newYorkers = pgMaterializedView('new_yorkers', { - userId: integer('user_id').notNull(), - cityId: integer('city_id'), -}).existing(); -``` - -### Refreshing materialized views - -```ts -await db.refreshMaterializedView(newYorkers); - -await db.refreshMaterializedView(newYorkers).concurrently(); - -await db.refreshMaterializedView(newYorkers).withNoData(); -``` - -## Prepared statements - -```typescript -const query = db.select().from(users).where(eq(users.name, 'Dan')).prepare(); - -const result = await query.execute(); -``` - -### Parametrized queries - -```typescript -import { placeholder } from 'drizzle-orm/pg-core'; - -const query = db.select().from(users).where(eq(users.name, placeholder('name'))).prepare(); - -const result = await query.execute({ name: 'Dan' }); -``` - -## Raw queries execution - -If you have some complex queries to execute and drizzle-orm can't handle them yet, you can use the `db.execute` method to execute raw queries. - -```typescript -// it will automatically run a parametrized query! -const res/*: QueryResult<{ id: number; name: string }>*/ = await db.execute< - { id: number; name: string } ->(sql`select * from ${users} where ${users.id} = ${userId}`); -``` - -## Migrations - -### Automatic SQL migrations generation with drizzle-kit - -[Drizzle Kit](https://www.npmjs.com/package/drizzle-kit) is a CLI migrator tool for Drizzle ORM. It is probably one and only tool that lets you completely automatically generate SQL migrations and covers ~95% of the common cases like deletions and renames by prompting user input. - -Check out the [docs for Drizzle Kit](https://github.com/drizzle-team/drizzle-kit-mirror) - -For schema file: - -```typescript -import { index, integer, pgTable, serial, varchar } from 'drizzle-orm/pg-core'; - -export const users = pgTable('users', { - id: serial('id').primaryKey(), - fullName: varchar('full_name', { length: 256 }), -}, (users) => ({ - nameIdx: index('name_idx').on(users.fullName), -})); - -export const authOtps = pgTable('auth_otp', { - id: serial('id').primaryKey(), - phone: varchar('phone', { length: 256 }), - userId: integer('user_id').references(() => users.id), -}); -``` - -It will generate: - -```SQL -CREATE TABLE IF NOT EXISTS auth_otp ( - 'id' SERIAL PRIMARY KEY, - 'phone' character varying(256), - 'user_id' INT -); - -CREATE TABLE IF NOT EXISTS users ( - 'id' SERIAL PRIMARY KEY, - 'full_name' character varying(256) -); - -DO $$ BEGIN - ALTER TABLE auth_otp ADD CONSTRAINT auth_otp_user_id_fkey FOREIGN KEY ('user_id') REFERENCES users(id); -EXCEPTION - WHEN duplicate_object THEN null; -END $$; - -CREATE INDEX IF NOT EXISTS users_full_name_index ON users (full_name); -``` - -And you can run migrations manually or using our embedded migrations module - -```typescript -import { drizzle } from 'drizzle-orm/node-postgres'; -import { migrate } from 'drizzle-orm/node-postgres/migrator'; -import { Pool } from 'pg'; - -const pool = new Pool({ - connectionString: 'postgres://user:password@host:port/db', -}); -const db = drizzle(pool); - -// this will automatically run needed migrations on the database -await migrate(db, { migrationsFolder: './drizzle' }); -``` - -## Logging - -To enable default query logging, just pass `{ logger: true }` to the `drizzle` function: - -```typescript -import { drizzle } from 'drizzle-orm/node-postgres'; - -const db = drizzle(pool, { logger: true }); -``` - -You can change the logs destination by creating a `DefaultLogger` instance and providing a custom `writer` to it: - -```typescript -import { DefaultLogger, LogWriter } from 'drizzle-orm/logger'; -import { drizzle } from 'drizzle-orm/node-postgres'; - -class MyLogWriter implements LogWriter { - write(message: string) { - // Write to file, console, etc. - } -} - -const logger = new DefaultLogger({ writer: new MyLogWriter() }); - -const db = drizzle(pool, { logger }); -``` - -You can also create a custom logger: - -```typescript -import { Logger } from 'drizzle-orm'; -import { drizzle } from 'drizzle-orm/node-postgres'; - -class MyLogger implements Logger { - logQuery(query: string, params: unknown[]): void { - console.log({ query, params }); - } -} - -const db = drizzle(pool, { logger: new MyLogger() }); -``` - -## Table introspect API - -See [dedicated docs](/docs/table-introspect-api.md). diff --git a/drizzle-orm/src/query-builders/select.types.ts b/drizzle-orm/src/query-builders/select.types.ts index fb816aa90..70470a212 100644 --- a/drizzle-orm/src/query-builders/select.types.ts +++ b/drizzle-orm/src/query-builders/select.types.ts @@ -155,7 +155,7 @@ export type GetSelectTableSelection = TTable extends T export type SelectResultField = T extends DrizzleTypeError ? T : T extends Table ? Equal extends true ? SelectResultField : never - : T extends Column ? GetColumnData + : T extends Column ? GetColumnData : T extends SQL | SQL.Aliased ? T['_']['type'] : T extends Record ? SelectResultFields : never; diff --git a/drizzle-orm/src/sql-js/session.ts b/drizzle-orm/src/sql-js/session.ts index 892610cfd..941a7c542 100644 --- a/drizzle-orm/src/sql-js/session.ts +++ b/drizzle-orm/src/sql-js/session.ts @@ -7,7 +7,11 @@ import { fillPlaceholders, type Query, sql } from '~/sql'; import { SQLiteTransaction } from '~/sqlite-core'; import type { SQLiteSyncDialect } from '~/sqlite-core/dialect'; import type { SelectedFieldsOrdered } from '~/sqlite-core/query-builders/select.types'; -import type { PreparedQueryConfig as PreparedQueryConfigBase, SQLiteTransactionConfig } from '~/sqlite-core/session'; +import type { + PreparedQueryConfig as PreparedQueryConfigBase, + SQLiteExecuteMethod, + SQLiteTransactionConfig, +} from '~/sqlite-core/session'; import { PreparedQuery as PreparedQueryBase, SQLiteSession } from '~/sqlite-core/session'; import { mapResultRow } from '~/utils'; @@ -37,19 +41,30 @@ export class SQLJsSession< prepareQuery>( query: Query, - fields?: SelectedFieldsOrdered, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, ): PreparedQuery { const stmt = this.client.prepare(query.sql); - return new PreparedQuery(stmt, query.sql, query.params, this.logger, fields); + return new PreparedQuery(stmt, query.sql, query.params, this.logger, fields, executeMethod); } override prepareOneTimeQuery>( query: Query, - fields?: SelectedFieldsOrdered, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, customResultMapper?: (rows: unknown[][]) => unknown, ): PreparedQuery { const stmt = this.client.prepare(query.sql); - return new PreparedQuery(stmt, query.sql, query.params, this.logger, fields, customResultMapper, true); + return new PreparedQuery( + stmt, + query.sql, + query.params, + this.logger, + fields, + executeMethod, + customResultMapper, + true, + ); } override transaction( @@ -91,7 +106,7 @@ export class SQLJsTransaction< } export class PreparedQuery extends PreparedQueryBase< - { type: 'sync'; run: void; all: T['all']; get: T['get']; values: T['values'] } + { type: 'sync'; run: void; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { static readonly [entityKind]: string = 'SQLJsPreparedQuery'; @@ -101,10 +116,11 @@ export class PreparedQuery private params: unknown[], private logger: Logger, private fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, private customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown, private isOneTimeQuery = false, ) { - super(); + super('sync', executeMethod); } run(placeholderValues?: Record): void { @@ -125,7 +141,7 @@ export class PreparedQuery const params = fillPlaceholders(this.params, placeholderValues ?? {}); logger.logQuery(queryString, params); stmt.bind(params as BindParams); - const rows: T['all'] = []; + const rows: unknown[] = []; while (stmt.step()) { rows.push(stmt.getAsObject()); } @@ -137,7 +153,7 @@ export class PreparedQuery return rows; } - const rows = this.values(placeholderValues); + const rows = this.values(placeholderValues) as unknown[][]; if (customResultMapper) { return customResultMapper(rows, normalizeFieldValue) as T['all']; @@ -182,7 +198,7 @@ export class PreparedQuery const params = fillPlaceholders(this.params, placeholderValues ?? {}); this.logger.logQuery(this.queryString, params); this.stmt.bind(params as BindParams); - const rows: T['values'] = []; + const rows: unknown[] = []; while (this.stmt.step()) { rows.push(this.stmt.get()); } diff --git a/drizzle-orm/src/sqlite-core/db.ts b/drizzle-orm/src/sqlite-core/db.ts index aab33e87a..d3e7154d2 100644 --- a/drizzle-orm/src/sqlite-core/db.ts +++ b/drizzle-orm/src/sqlite-core/db.ts @@ -15,7 +15,7 @@ import type { AnySQLiteTable } from '~/sqlite-core/table'; import { SelectionProxyHandler, WithSubquery } from '~/subquery'; import { type DrizzleTypeError } from '~/utils'; import { type ColumnsSelection } from '~/view'; -import { AsyncRelationalQueryBuilder, SyncRelationalQueryBuilder } from './query-builders/query'; +import { RelationalQueryBuilder } from './query-builders/query'; import type { SelectedFields } from './query-builders/select.types'; import type { WithSubqueryWithSelection } from './subquery'; @@ -35,8 +35,7 @@ export class BaseSQLiteDatabase< query: TFullSchema extends Record ? DrizzleTypeError<'Seems like the schema generic is missing - did you forget to add it to your DB type?'> : { - [K in keyof TSchema]: TResultKind extends 'async' ? AsyncRelationalQueryBuilder - : SyncRelationalQueryBuilder; + [K in keyof TSchema]: RelationalQueryBuilder; }; constructor( @@ -53,16 +52,16 @@ export class BaseSQLiteDatabase< this.query = {} as typeof this['query']; if (this._.schema) { for (const [tableName, columns] of Object.entries(this._.schema)) { - this.query[tableName as keyof TSchema] = - new (resultKind === 'async' ? AsyncRelationalQueryBuilder : SyncRelationalQueryBuilder)( - schema!.fullSchema, - this._.schema, - this._.tableNamesMap, - schema!.fullSchema[tableName] as AnySQLiteTable, - columns, - dialect, - session as SQLiteSession as any, - ) as this['query'][keyof TSchema]; + this.query[tableName as keyof TSchema] = new RelationalQueryBuilder( + resultKind, + schema!.fullSchema, + this._.schema, + this._.tableNamesMap, + schema!.fullSchema[tableName] as AnySQLiteTable, + columns, + dialect, + session as SQLiteSession as any, + ) as this['query'][keyof TSchema]; } } } diff --git a/drizzle-orm/src/sqlite-core/query-builders/delete.ts b/drizzle-orm/src/sqlite-core/query-builders/delete.ts index ad8f18b10..031bdaeba 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/delete.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/delete.ts @@ -1,11 +1,12 @@ import { entityKind } from '~/entity'; import type { SelectResultFields } from '~/query-builders/select.types'; +import { QueryPromise } from '~/query-promise'; import type { Query, SQL, SQLWrapper } from '~/sql'; import type { SQLiteDialect } from '~/sqlite-core/dialect'; import type { PreparedQuery, SQLiteSession } from '~/sqlite-core/session'; import { SQLiteTable } from '~/sqlite-core/table'; import type { InferModel } from '~/table'; -import { orderSelectedFields } from '~/utils'; +import { type DrizzleTypeError, orderSelectedFields } from '~/utils'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types'; export interface SQLiteDeleteConfig { @@ -31,7 +32,7 @@ export class SQLiteDelete< TResultType extends 'sync' | 'async', TRunResult, TReturning = undefined, -> implements SQLWrapper { +> extends QueryPromise implements SQLWrapper { static readonly [entityKind]: string = 'SQLiteDelete'; private config: SQLiteDeleteConfig; @@ -41,6 +42,7 @@ export class SQLiteDelete< private session: SQLiteSession, private dialect: SQLiteDialect, ) { + super(); this.config = { table }; } @@ -49,10 +51,10 @@ export class SQLiteDelete< return this; } - returning(): Omit>, 'where' | 'returning'>; + returning(): SQLiteDelete>; returning( fields: TSelectedFields, - ): Omit>, 'where' | 'returning'>; + ): SQLiteDelete>; returning( fields: SelectedFieldsFlat = this.table[SQLiteTable.Symbol.Columns], ): SQLiteDelete { @@ -73,14 +75,17 @@ export class SQLiteDelete< prepare(isOneTimeQuery?: boolean): PreparedQuery<{ type: TResultType; run: TRunResult; - all: TReturning extends undefined ? never : TReturning[]; - get: TReturning extends undefined ? never : TReturning | undefined; - values: TReturning extends undefined ? never : any[][]; + all: TReturning extends undefined ? DrizzleTypeError<'.all() cannot be used without .returning()'> : TReturning[]; + get: TReturning extends undefined ? DrizzleTypeError<'.get() cannot be used without .returning()'> + : TReturning | undefined; + values: TReturning extends undefined ? DrizzleTypeError<'.values() cannot be used without .returning()'> : any[][]; + execute: TReturning extends undefined ? TRunResult : TReturning[]; }> { return this.session[isOneTimeQuery ? 'prepareOneTimeQuery' : 'prepareQuery']( this.dialect.sqlToQuery(this.getSQL()), this.config.returning, - ); + this.config.returning ? 'all' : 'run', + ) as ReturnType; } run: ReturnType['run'] = (placeholderValues) => { @@ -98,4 +103,11 @@ export class SQLiteDelete< values: ReturnType['values'] = (placeholderValues) => { return this.prepare(true).values(placeholderValues); }; + + override async execute( + placeholderValues?: Record, + ): Promise { + return this.prepare(true).execute(placeholderValues) as TReturning extends undefined ? TRunResult + : TReturning[]; + } } diff --git a/drizzle-orm/src/sqlite-core/query-builders/insert.ts b/drizzle-orm/src/sqlite-core/query-builders/insert.ts index a019116aa..d768f4f69 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/insert.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/insert.ts @@ -1,5 +1,6 @@ import { entityKind, is } from '~/entity'; import type { SelectResultFields } from '~/query-builders/select.types'; +import { QueryPromise } from '~/query-promise'; import type { Placeholder, Query, SQLWrapper } from '~/sql'; import { Param, SQL, sql } from '~/sql'; import type { SQLiteDialect } from '~/sqlite-core/dialect'; @@ -8,7 +9,7 @@ import type { PreparedQuery, SQLiteSession } from '~/sqlite-core/session'; import type { AnySQLiteTable } from '~/sqlite-core/table'; import { SQLiteTable } from '~/sqlite-core/table'; import { type InferModel, Table } from '~/table'; -import { mapUpdateSet, orderSelectedFields, type Simplify } from '~/utils'; +import { type DrizzleTypeError, mapUpdateSet, orderSelectedFields, type Simplify } from '~/utils'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types'; import type { SQLiteUpdateSetSource } from './update'; @@ -77,14 +78,14 @@ export interface SQLiteInsert< TRunResult, // eslint-disable-next-line @typescript-eslint/no-unused-vars TReturning = undefined, -> extends SQLWrapper {} +> extends SQLWrapper, QueryPromise {} export class SQLiteInsert< TTable extends AnySQLiteTable, TResultType extends 'sync' | 'async', TRunResult, TReturning = undefined, -> implements SQLWrapper { +> extends QueryPromise implements SQLWrapper { static readonly [entityKind]: string = 'SQLiteInsert'; declare readonly _: { @@ -102,19 +103,14 @@ export class SQLiteInsert< private session: SQLiteSession, private dialect: SQLiteDialect, ) { + super(); this.config = { table, values }; } - returning(): Omit< - SQLiteInsert>, - 'returning' | `onConflict${string}` - >; + returning(): SQLiteInsert>; returning( fields: TSelectedFields, - ): Omit< - SQLiteInsert>, - 'returning' | `onConflict${string}` - >; + ): SQLiteInsert>; returning( fields: SelectedFieldsFlat = this.config.table[SQLiteTable.Symbol.Columns], ): SQLiteInsert { @@ -159,15 +155,18 @@ export class SQLiteInsert< { type: TResultType; run: TRunResult; - all: TReturning extends undefined ? never : TReturning[]; - get: TReturning extends undefined ? never : TReturning; - values: TReturning extends undefined ? never : any[][]; + all: TReturning extends undefined ? DrizzleTypeError<'.all() cannot be used without .returning()'> : TReturning[]; + get: TReturning extends undefined ? DrizzleTypeError<'.get() cannot be used without .returning()'> : TReturning; + values: TReturning extends undefined ? DrizzleTypeError<'.values() cannot be used without .returning()'> + : any[][]; + execute: TReturning extends undefined ? TRunResult : TReturning[]; } > { return this.session[isOneTimeQuery ? 'prepareOneTimeQuery' : 'prepareQuery']( this.dialect.sqlToQuery(this.getSQL()), this.config.returning, - ); + this.config.returning ? 'all' : 'run', + ) as ReturnType; } run: ReturnType['run'] = (placeholderValues) => { @@ -185,4 +184,9 @@ export class SQLiteInsert< values: ReturnType['values'] = (placeholderValues) => { return this.prepare(true).values(placeholderValues); }; + + override async execute(): Promise { + return (this.config.returning ? this.all() : this.run()) as TReturning extends undefined ? TRunResult + : TReturning[]; + } } diff --git a/drizzle-orm/src/sqlite-core/query-builders/query.ts b/drizzle-orm/src/sqlite-core/query-builders/query.ts index 9b03b3fd8..563a42082 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/query.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/query.ts @@ -8,152 +8,93 @@ import { type TablesRelationalConfig, } from '~/relations'; import { type SQL } from '~/sql'; -import { applyMixins, type KnownKeysOnly } from '~/utils'; +import { type KnownKeysOnly } from '~/utils'; import { type SQLiteDialect } from '../dialect'; -import { type PreparedQuery, type PreparedQueryConfig, type Result, type SQLiteSession } from '../session'; -import { type AnySQLiteTable } from '../table'; +import { type PreparedQuery, type PreparedQueryConfig, type SQLiteSession } from '../session'; +import { type SQLiteTable } from '../table'; -export class AsyncRelationalQueryBuilder< - TFullSchema extends Record, - TSchema extends TablesRelationalConfig, - TFields extends TableRelationalConfig, -> { - static readonly [entityKind]: string = 'SQLiteAsyncRelationalQueryBuilder'; - - constructor( - private fullSchema: Record, - private schema: TSchema, - private tableNamesMap: Record, - private table: AnySQLiteTable, - private tableConfig: TableRelationalConfig, - private dialect: SQLiteDialect, - private session: SQLiteSession<'async', unknown, TFullSchema, TSchema>, - ) {} - - findMany>( - config?: KnownKeysOnly>, - ): SQLiteAsyncRelationalQuery[]> { - return new SQLiteRelationalQuery( - this.fullSchema, - this.schema, - this.tableNamesMap, - this.table, - this.tableConfig, - this.dialect, - this.session, - config ? (config as DBQueryConfig<'many', true>) : {}, - 'many', - ) as SQLiteAsyncRelationalQuery[]>; - } - - findFirst, 'limit'>>( - config?: KnownKeysOnly, 'limit'>>, - ): SQLiteAsyncRelationalQuery | undefined> { - return new SQLiteRelationalQuery( - this.fullSchema, - this.schema, - this.tableNamesMap, - this.table, - this.tableConfig, - this.dialect, - this.session, - config ? { ...(config as DBQueryConfig<'many', true> | undefined), limit: 1 } : { limit: 1 }, - 'first', - ) as SQLiteAsyncRelationalQuery | undefined>; - } -} +export type SQLiteRelationalQueryKind = TMode extends 'async' + ? SQLiteRelationalQuery + : SQLiteSyncRelationalQuery; -export class SyncRelationalQueryBuilder< +export class RelationalQueryBuilder< + TMode extends 'sync' | 'async', TFullSchema extends Record, TSchema extends TablesRelationalConfig, TFields extends TableRelationalConfig, > { - static readonly [entityKind]: string = 'SQLiteSyncRelationalQueryBuilder'; + static readonly [entityKind]: string = 'SQLiteAsyncRelationalQueryBuilder'; constructor( - private fullSchema: Record, - private schema: TSchema, - private tableNamesMap: Record, - private table: AnySQLiteTable, - private tableConfig: TableRelationalConfig, - private dialect: SQLiteDialect, - private session: SQLiteSession<'sync', unknown, TFullSchema, TSchema>, + protected mode: TMode, + protected fullSchema: Record, + protected schema: TSchema, + protected tableNamesMap: Record, + protected table: SQLiteTable, + protected tableConfig: TableRelationalConfig, + protected dialect: SQLiteDialect, + protected session: SQLiteSession<'async', unknown, TFullSchema, TSchema>, ) {} - prepareFindMany>( - config?: KnownKeysOnly>, - ): { - execute: PreparedQuery< - PreparedQueryConfig & { - type: 'sync'; - all: BuildQueryResult[]; - } - >['all']; - } { - const query = new SQLiteRelationalQuery<'sync', BuildQueryResult[]>( - this.fullSchema, - this.schema, - this.tableNamesMap, - this.table, - this.tableConfig, - this.dialect, - this.session, - config ? (config as DBQueryConfig<'many', true>) : {}, - 'many', - ).prepare(); - - return { - execute: query.all.bind(query), - }; - } - findMany>( config?: KnownKeysOnly>, - ): BuildQueryResult[] { - return this.prepareFindMany(config).execute(); - } - - prepareFindFirst>( - config?: KnownKeysOnly>, - ): { - execute: PreparedQuery< - PreparedQueryConfig & { - type: 'sync'; - get: BuildQueryResult | undefined; - } - >['get']; - } { - const query = new SQLiteRelationalQuery( - this.fullSchema, - this.schema, - this.tableNamesMap, - this.table, - this.tableConfig, - this.dialect, - this.session, - config ? { ...(config as DBQueryConfig<'many', true> | undefined), limit: 1 } : { limit: 1 }, - 'first', - ).prepare(); - - return { - execute: query.get.bind(query) as PreparedQuery< - PreparedQueryConfig & { - type: 'sync'; - get: BuildQueryResult | undefined; - } - >['get'], - }; + ): SQLiteRelationalQueryKind[]> { + return (this.mode === 'sync' + ? new SQLiteSyncRelationalQuery( + this.fullSchema, + this.schema, + this.tableNamesMap, + this.table, + this.tableConfig, + this.dialect, + this.session, + config ? (config as DBQueryConfig<'many', true>) : {}, + 'many', + ) + : new SQLiteRelationalQuery( + this.fullSchema, + this.schema, + this.tableNamesMap, + this.table, + this.tableConfig, + this.dialect, + this.session, + config ? (config as DBQueryConfig<'many', true>) : {}, + 'many', + )) as SQLiteRelationalQueryKind[]>; } findFirst, 'limit'>>( config?: KnownKeysOnly, 'limit'>>, - ): BuildQueryResult | undefined { - return this.prepareFindFirst(config).execute(); + ): SQLiteRelationalQueryKind | undefined> { + return (this.mode === 'sync' + ? new SQLiteSyncRelationalQuery( + this.fullSchema, + this.schema, + this.tableNamesMap, + this.table, + this.tableConfig, + this.dialect, + this.session, + config ? { ...(config as DBQueryConfig<'many', true> | undefined), limit: 1 } : { limit: 1 }, + 'first', + ) + : new SQLiteRelationalQuery( + this.fullSchema, + this.schema, + this.tableNamesMap, + this.table, + this.tableConfig, + this.dialect, + this.session, + config ? { ...(config as DBQueryConfig<'many', true> | undefined), limit: 1 } : { limit: 1 }, + 'first', + )) as SQLiteRelationalQueryKind | undefined>; } } -export class SQLiteRelationalQuery { - static readonly [entityKind]: string = 'SQLiteRelationalQuery'; +export class SQLiteRelationalQuery extends QueryPromise { + static readonly [entityKind]: string = 'SQLiteAsyncRelationalQuery'; declare protected $brand: 'SQLiteRelationalQuery'; @@ -161,15 +102,17 @@ export class SQLiteRelationalQuery, private schema: TablesRelationalConfig, private tableNamesMap: Record, - private table: AnySQLiteTable, + private table: SQLiteTable, private tableConfig: TableRelationalConfig, private dialect: SQLiteDialect, - private session: SQLiteSession, TablesRelationalConfig>, + private session: SQLiteSession<'sync' | 'async', unknown, Record, TablesRelationalConfig>, private config: DBQueryConfig<'many', true> | true, private mode: 'many' | 'first', - ) {} + ) { + super(); + } - prepare(): PreparedQuery { + prepare(): PreparedQuery { const query = this.dialect.buildRelationalQuery({ fullSchema: this.fullSchema, schema: this.schema, @@ -184,6 +127,7 @@ export class SQLiteRelationalQuery { const rows = rawRows.map((row) => mapRelationalRow(this.schema, this.tableConfig, row, query.selection, mapColumnValue) @@ -193,19 +137,26 @@ export class SQLiteRelationalQuery; } - execute(): Result { + /** @internal */ + executeRaw(): TResult { if (this.mode === 'first') { - return this.prepare().get(); + return this.prepare().get() as TResult; } - return this.prepare().all(); + return this.prepare().all() as TResult; + } + + override async execute(): Promise { + return this.executeRaw(); } } -export interface SQLiteAsyncRelationalQuery - extends SQLiteRelationalQuery<'async', TResult>, QueryPromise -{} +export class SQLiteSyncRelationalQuery extends SQLiteRelationalQuery<'sync', TResult> { + static readonly [entityKind]: string = 'SQLiteSyncRelationalQuery'; -applyMixins(SQLiteRelationalQuery, [QueryPromise]); + sync(): TResult { + return this.executeRaw(); + } +} diff --git a/drizzle-orm/src/sqlite-core/query-builders/select.ts b/drizzle-orm/src/sqlite-core/query-builders/select.ts index 928bfcee9..d7e6e9f8c 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/select.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/select.ts @@ -16,9 +16,17 @@ import type { SelectMode, SelectResult, } from '~/query-builders/select.types'; +import { QueryPromise } from '~/query-promise'; import type { SubqueryWithSelection } from '~/sqlite-core/subquery'; import { SelectionProxyHandler, Subquery, SubqueryConfig } from '~/subquery'; -import { getTableColumns, getTableLikeName, orderSelectedFields, type ValueOrArray } from '~/utils'; +import { + applyMixins, + getTableColumns, + getTableLikeName, + orderSelectedFields, + type PromiseOf, + type ValueOrArray, +} from '~/utils'; import { type ColumnsSelection, View, ViewBaseConfig } from '~/view'; import { SQLiteViewBase } from '../view'; import type { @@ -376,7 +384,8 @@ export interface SQLiteSelect< TSelection, TSelectMode, TNullabilityMap - > + >, + QueryPromise[]> {} export class SQLiteSelect< @@ -405,6 +414,7 @@ export class SQLiteSelect< all: SelectResult[]; get: SelectResult | undefined; values: any[][]; + execute: SelectResult[]; } > { if (!this.session) { @@ -414,9 +424,10 @@ export class SQLiteSelect< const query = this.session[isOneTimeQuery ? 'prepareOneTimeQuery' : 'prepareQuery']( this.dialect.sqlToQuery(this.getSQL()), fieldsList, + 'all', ); query.joinsNotNullableMap = this.joinsNotNullableMap; - return query; + return query as ReturnType; } run: ReturnType['run'] = (placeholderValues) => { @@ -434,4 +445,10 @@ export class SQLiteSelect< values: ReturnType['values'] = (placeholderValues) => { return this.prepare(true).values(placeholderValues); }; + + async execute(): Promise[]> { + return this.all() as PromiseOf>; + } } + +applyMixins(SQLiteSelect, [QueryPromise]); diff --git a/drizzle-orm/src/sqlite-core/query-builders/update.ts b/drizzle-orm/src/sqlite-core/query-builders/update.ts index 90c932543..32a083a9a 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/update.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/update.ts @@ -1,13 +1,14 @@ import type { GetColumnData } from '~/column'; import { entityKind } from '~/entity'; import type { SelectResultFields } from '~/query-builders/select.types'; +import { QueryPromise } from '~/query-promise'; import type { Query, SQL, SQLWrapper } from '~/sql'; import type { SQLiteDialect } from '~/sqlite-core/dialect'; import type { PreparedQuery, SQLiteSession } from '~/sqlite-core/session'; import type { AnySQLiteTable } from '~/sqlite-core/table'; import { SQLiteTable } from '~/sqlite-core/table'; import type { InferModel } from '~/table'; -import { mapUpdateSet, orderSelectedFields, type UpdateSet } from '~/utils'; +import { type DrizzleTypeError, mapUpdateSet, orderSelectedFields, type UpdateSet } from '~/utils'; import type { SelectedFields, SelectedFieldsOrdered } from './select.types'; export interface SQLiteUpdateConfig { @@ -57,14 +58,14 @@ export interface SQLiteUpdate< TRunResult, // eslint-disable-next-line @typescript-eslint/no-unused-vars TReturning = undefined, -> extends SQLWrapper {} +> extends SQLWrapper, QueryPromise {} export class SQLiteUpdate< TTable extends AnySQLiteTable, TResultType extends 'sync' | 'async', TRunResult, TReturning = undefined, -> implements SQLWrapper { +> extends QueryPromise implements SQLWrapper { static readonly [entityKind]: string = 'SQLiteUpdate'; declare readonly _: { @@ -79,6 +80,7 @@ export class SQLiteUpdate< private session: SQLiteSession, private dialect: SQLiteDialect, ) { + super(); this.config = { set, table }; } @@ -87,21 +89,15 @@ export class SQLiteUpdate< return this; } - returning(): Omit< - SQLiteUpdate>, - 'where' | 'returning' - >; + returning(): SQLiteUpdate>; returning( fields: TSelectedFields, - ): Omit< - SQLiteUpdate>, - 'where' | 'returning' - >; + ): SQLiteUpdate>; returning( fields: SelectedFields = this.config.table[SQLiteTable.Symbol.Columns], - ): Omit, 'where' | 'returning'> { + ): SQLiteUpdate> { this.config.returning = orderSelectedFields(fields); - return this; + return this as SQLiteUpdate>; } /** @internal */ @@ -118,15 +114,18 @@ export class SQLiteUpdate< { type: TResultType; run: TRunResult; - all: TReturning extends undefined ? never : TReturning[]; - get: TReturning extends undefined ? never : TReturning; - values: TReturning extends undefined ? never : any[][]; + all: TReturning extends undefined ? DrizzleTypeError<'.all() cannot be used without .returning()'> : TReturning[]; + get: TReturning extends undefined ? DrizzleTypeError<'.get() cannot be used without .returning()'> : TReturning; + values: TReturning extends undefined ? DrizzleTypeError<'.values() cannot be used without .returning()'> + : any[][]; + execute: TReturning extends undefined ? TRunResult : TReturning[]; } > { return this.session[isOneTimeQuery ? 'prepareOneTimeQuery' : 'prepareQuery']( this.dialect.sqlToQuery(this.getSQL()), this.config.returning, - ); + this.config.returning ? 'all' : 'run', + ) as ReturnType; } run: ReturnType['run'] = (placeholderValues) => { @@ -144,4 +143,9 @@ export class SQLiteUpdate< values: ReturnType['values'] = (placeholderValues) => { return this.prepare(true).values(placeholderValues); }; + + override async execute(): Promise { + return (this.config.returning ? this.all() : this.run()) as TReturning extends undefined ? TRunResult + : TReturning[]; + } } diff --git a/drizzle-orm/src/sqlite-core/session.ts b/drizzle-orm/src/sqlite-core/session.ts index df52a0cfc..cf3522083 100644 --- a/drizzle-orm/src/sqlite-core/session.ts +++ b/drizzle-orm/src/sqlite-core/session.ts @@ -3,23 +3,49 @@ import { DrizzleError, TransactionRollbackError } from '~/errors'; import { type TablesRelationalConfig } from '~/relations'; import type { Query, SQL } from '~/sql'; import type { SQLiteAsyncDialect, SQLiteSyncDialect } from '~/sqlite-core/dialect'; +import { QueryPromise } from '..'; import { BaseSQLiteDatabase } from './db'; import type { SelectedFieldsOrdered } from './query-builders/select.types'; export interface PreparedQueryConfig { type: 'sync' | 'async'; run: unknown; - all: unknown[]; + all: unknown; get: unknown; - values: unknown[][]; + values: unknown; + execute: unknown; } +export class ExecuteResultSync extends QueryPromise { + static readonly [entityKind]: string = 'ExecuteResultSync'; + + constructor(private resultCb: () => T) { + super(); + } + + override async execute(): Promise { + return this.resultCb(); + } + + sync(): T { + return this.resultCb(); + } +} + +export type ExecuteResult = TType extends 'async' ? Promise + : ExecuteResultSync; + export abstract class PreparedQuery { static readonly [entityKind]: string = 'PreparedQuery'; /** @internal */ joinsNotNullableMap?: Record; + constructor( + private mode: 'sync' | 'async', + private executeMethod: SQLiteExecuteMethod, + ) {} + abstract run(placeholderValues?: Record): Result; abstract all(placeholderValues?: Record): Result; @@ -27,12 +53,21 @@ export abstract class PreparedQuery { abstract get(placeholderValues?: Record): Result; abstract values(placeholderValues?: Record): Result; + + execute(placeholderValues?: Record): ExecuteResult { + if (this.mode === 'async') { + return this[this.executeMethod](placeholderValues) as ExecuteResult; + } + return new ExecuteResultSync(() => this[this.executeMethod](placeholderValues)); + } } export interface SQLiteTransactionConfig { behavior?: 'deferred' | 'immediate' | 'exclusive'; } +export type SQLiteExecuteMethod = 'run' | 'all' | 'get'; + export abstract class SQLiteSession< TResultKind extends 'sync' | 'async', TRunResult, @@ -49,14 +84,16 @@ export abstract class SQLiteSession< abstract prepareQuery( query: Query, fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown, ): PreparedQuery; prepareOneTimeQuery( query: Query, fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, ): PreparedQuery { - return this.prepareQuery(query, fields); + return this.prepareQuery(query, fields, executeMethod); } abstract transaction( @@ -67,24 +104,24 @@ export abstract class SQLiteSession< run(query: SQL): Result { const staticQuery = this.dialect.sqlToQuery(query); try { - return this.prepareOneTimeQuery(staticQuery, undefined).run(); + return this.prepareOneTimeQuery(staticQuery, undefined, 'run').run(); } catch (err) { throw DrizzleError.wrap(err, `Failed to run the query '${staticQuery.sql}'`); } } all(query: SQL): Result { - return this.prepareOneTimeQuery(this.dialect.sqlToQuery(query), undefined).all(); + return this.prepareOneTimeQuery(this.dialect.sqlToQuery(query), undefined, 'run').all(); } get(query: SQL): Result { - return this.prepareOneTimeQuery(this.dialect.sqlToQuery(query), undefined).get(); + return this.prepareOneTimeQuery(this.dialect.sqlToQuery(query), undefined, 'run').get(); } values( query: SQL, ): Result { - return this.prepareOneTimeQuery(this.dialect.sqlToQuery(query), undefined).values(); + return this.prepareOneTimeQuery(this.dialect.sqlToQuery(query), undefined, 'run').values(); } } diff --git a/drizzle-orm/src/sqlite-proxy/session.ts b/drizzle-orm/src/sqlite-proxy/session.ts index ff6955a0d..b040f9cc2 100644 --- a/drizzle-orm/src/sqlite-proxy/session.ts +++ b/drizzle-orm/src/sqlite-proxy/session.ts @@ -6,7 +6,11 @@ import { fillPlaceholders, type Query, sql } from '~/sql'; import { SQLiteTransaction } from '~/sqlite-core'; import type { SQLiteAsyncDialect } from '~/sqlite-core/dialect'; import type { SelectedFieldsOrdered } from '~/sqlite-core/query-builders/select.types'; -import type { PreparedQueryConfig as PreparedQueryConfigBase, SQLiteTransactionConfig } from '~/sqlite-core/session'; +import type { + PreparedQueryConfig as PreparedQueryConfigBase, + SQLiteExecuteMethod, + SQLiteTransactionConfig, +} from '~/sqlite-core/session'; import { PreparedQuery as PreparedQueryBase, SQLiteSession } from '~/sqlite-core/session'; import { mapResultRow } from '~/utils'; import { type RemoteCallback, type SqliteRemoteResult } from './driver'; @@ -37,9 +41,10 @@ export class SQLiteRemoteSession< prepareQuery>( query: Query, - fields?: SelectedFieldsOrdered, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, ): PreparedQuery { - return new PreparedQuery(this.client, query.sql, query.params, this.logger, fields); + return new PreparedQuery(this.client, query.sql, query.params, this.logger, fields, executeMethod); } override async transaction( @@ -83,7 +88,7 @@ export class SQLiteProxyTransaction< } export class PreparedQuery extends PreparedQueryBase< - { type: 'async'; run: SqliteRemoteResult; all: T['all']; get: T['get']; values: T['values'] } + { type: 'async'; run: SqliteRemoteResult; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { static readonly [entityKind]: string = 'SQLiteProxyPreparedQuery'; @@ -93,8 +98,9 @@ export class PreparedQuery private params: unknown[], private logger: Logger, private fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, ) { - super(); + super('async', executeMethod); } run(placeholderValues?: Record): Promise { diff --git a/drizzle-orm/src/utils.ts b/drizzle-orm/src/utils.ts index d5811a249..a27c5e3ab 100644 --- a/drizzle-orm/src/utils.ts +++ b/drizzle-orm/src/utils.ts @@ -137,9 +137,8 @@ export type Assume = T extends U ? T : U; export type Equal = (() => T extends X ? 1 : 2) extends (() => T extends Y ? 1 : 2) ? true : false; -export interface DrizzleTypeError { - $brand: 'DrizzleTypeError'; - $error: T; +export interface DrizzleTypeError { + $drizzleTypeError: T; } export type ValueOrArray = T | T[]; diff --git a/drizzle-orm/type-tests/sqlite/delete.ts b/drizzle-orm/type-tests/sqlite/delete.ts index 156afcad1..d10a8103d 100644 --- a/drizzle-orm/type-tests/sqlite/delete.ts +++ b/drizzle-orm/type-tests/sqlite/delete.ts @@ -4,6 +4,7 @@ import { eq } from '~/expressions'; import type { Equal } from 'type-tests/utils'; import { Expect } from 'type-tests/utils'; import type { InferModel } from '~/table'; +import { type DrizzleTypeError } from '~/utils'; import { bunDb, db } from './db'; import { users } from './tables'; @@ -11,49 +12,49 @@ const deleteRun = db.delete(users).run(); Expect>; const deleteAll = db.delete(users).all(); -Expect>; +Expect, typeof deleteAll>>; const deleteGet = db.delete(users).get(); -Expect>; +Expect, typeof deleteGet>>; const deleteValues = db.delete(users).values(); -Expect>; +Expect, typeof deleteValues>>; const deleteRunBun = bunDb.delete(users).run(); Expect>; const deleteAllBun = bunDb.delete(users).all(); -Expect>; +Expect, typeof deleteAllBun>>; const deleteGetBun = bunDb.delete(users).get(); -Expect>; +Expect, typeof deleteGetBun>>; const deleteValuesBun = bunDb.delete(users).values(); -Expect>; +Expect, typeof deleteValuesBun>>; const deleteRunWhere = db.delete(users).where(eq(users.id, 1)).run(); Expect>; const deleteAllWhere = db.delete(users).where(eq(users.id, 1)).all(); -Expect>; +Expect, typeof deleteAllWhere>>; const deleteGetWhere = db.delete(users).where(eq(users.id, 1)).get(); -Expect>; +Expect, typeof deleteGetWhere>>; const deleteValuesWhere = db.delete(users).where(eq(users.id, 1)).values(); -Expect>; +Expect, typeof deleteValuesWhere>>; const deleteRunBunWhere = bunDb.delete(users).where(eq(users.id, 1)).run(); Expect>; const deleteAllBunWhere = bunDb.delete(users).where(eq(users.id, 1)).all(); -Expect>; +Expect, typeof deleteAllBunWhere>>; const deleteGetBunWhere = bunDb.delete(users).where(eq(users.id, 1)).get(); -Expect>; +Expect, typeof deleteGetBunWhere>>; const deleteValuesBunWhere = bunDb.delete(users).where(eq(users.id, 1)).values(); -Expect>; +Expect, typeof deleteValuesBunWhere>>; const deleteRunReturning = db.delete(users).returning().run(); Expect>; diff --git a/drizzle-orm/type-tests/sqlite/insert.ts b/drizzle-orm/type-tests/sqlite/insert.ts index 87694fe8a..0e80b2e92 100644 --- a/drizzle-orm/type-tests/sqlite/insert.ts +++ b/drizzle-orm/type-tests/sqlite/insert.ts @@ -4,6 +4,7 @@ import { Expect } from 'type-tests/utils'; import { and, eq } from '~/expressions'; import { placeholder, sql } from '~/sql'; import type { InferModel } from '~/table'; +import { type DrizzleTypeError } from '~/utils'; import { bunDb, db } from './db'; import type { NewUser } from './tables'; import { users } from './tables'; @@ -23,22 +24,22 @@ const insertRunBun = bunDb.insert(users).values(newUser).run(); Expect>; const insertAll = db.insert(users).values(newUser).all(); -Expect>; +Expect, typeof insertAll>>; const insertAllBun = bunDb.insert(users).values(newUser).all(); -Expect>; +Expect, typeof insertAllBun>>; const insertGet = db.insert(users).values(newUser).get(); -Expect>; +Expect, typeof insertGet>>; const insertGetBun = bunDb.insert(users).values(newUser).get(); -Expect>; +Expect, typeof insertGetBun>>; const insertValues = db.insert(users).values(newUser).values(); -Expect>; +Expect, typeof insertValues>>; const insertValuesBun = bunDb.insert(users).values(newUser).values(); -Expect>; +Expect, typeof insertValuesBun>>; const insertRunReturningAll = db.insert(users).values(newUser).returning().run(); Expect>; diff --git a/drizzle-orm/type-tests/sqlite/select.ts b/drizzle-orm/type-tests/sqlite/select.ts index 3ecba48a2..2bf71591d 100644 --- a/drizzle-orm/type-tests/sqlite/select.ts +++ b/drizzle-orm/type-tests/sqlite/select.ts @@ -57,12 +57,15 @@ const joinGet = db .rightJoin(city1, eq(city1.id, users.id)) .get(); Expect< - Equal<{ - users_table: InferModel | null; - cities_table: InferModel | null; - city: InferModel | null; - city1: InferModel; - } | undefined, typeof joinGet> + Equal< + { + users_table: InferModel | null; + cities_table: InferModel | null; + city: InferModel | null; + city1: InferModel; + } | undefined, + typeof joinGet + > >; const joinValues = db diff --git a/drizzle-orm/type-tests/sqlite/update.ts b/drizzle-orm/type-tests/sqlite/update.ts index 2538b1e28..6db7f13bc 100644 --- a/drizzle-orm/type-tests/sqlite/update.ts +++ b/drizzle-orm/type-tests/sqlite/update.ts @@ -3,6 +3,7 @@ import type { Equal } from 'type-tests/utils'; import { Expect } from 'type-tests/utils'; import { eq } from '~/expressions'; import type { InferModel } from '~/table'; +import { type DrizzleTypeError } from '~/utils'; import { bunDb, db } from './db'; import { users } from './tables'; @@ -30,7 +31,7 @@ const updateAll = db.update(users) age1: 30, }) .all(); -Expect>; +Expect, typeof updateAll>>; const updateAllBun = bunDb.update(users) .set({ @@ -38,21 +39,21 @@ const updateAllBun = bunDb.update(users) age1: 30, }) .all(); -Expect>; +Expect, typeof updateAllBun>>; const updateGet = db.update(users) .set({ name: 'John', age1: 30, }).get(); -Expect>; +Expect, typeof updateGet>>; const updateGetBun = bunDb.update(users) .set({ name: 'John', age1: 30, }).get(); -Expect>; +Expect, typeof updateGetBun>>; const updateAllReturningAll = db.update(users) .set({ diff --git a/integration-tests/tests/better-sqlite.test.ts b/integration-tests/tests/better-sqlite.test.ts index 9f5b47217..be06341a8 100644 --- a/integration-tests/tests/better-sqlite.test.ts +++ b/integration-tests/tests/better-sqlite.test.ts @@ -187,7 +187,7 @@ test.beforeEach((t) => { `); }); -test.serial('table configs: unique third param', async (t) => { +test.serial('table configs: unique third param', (t) => { const cities1Table = sqliteTable('cities1', { id: int('id').primaryKey(), name: text('name').notNull(), @@ -211,7 +211,7 @@ test.serial('table configs: unique third param', async (t) => { t.assert(tableConfig.uniqueConstraints[1]?.name === 'custom'); }); -test.serial('table configs: unique in column', async (t) => { +test.serial('table configs: unique in column', (t) => { const cities1Table = sqliteTable('cities1', { id: int('id').primaryKey(), name: text('name').notNull().unique(), @@ -234,16 +234,16 @@ test.serial('table configs: unique in column', async (t) => { t.assert(columnField?.uniqueName === uniqueKeyName(cities1Table, [columnField!.name])); }); -test.serial('insert bigint values', async (t) => { +test.serial('insert bigint values', (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(); + db.insert(bigIntExample).values({ name: 'one', bigInt: BigInt('0') }).run(); + db.insert(bigIntExample).values({ name: 'two', bigInt: BigInt('127') }).run(); + db.insert(bigIntExample).values({ name: 'three', bigInt: BigInt('32767') }).run(); + db.insert(bigIntExample).values({ name: 'four', bigInt: BigInt('1234567890') }).run(); + db.insert(bigIntExample).values({ name: 'five', bigInt: BigInt('12345678900987654321') }).run(); - const result = await db.select().from(bigIntExample).all(); + const result = db.select().from(bigIntExample).all(); t.deepEqual(result, [ { id: 1, name: 'one', bigInt: BigInt('0') }, { id: 2, name: 'two', bigInt: BigInt('127') }, @@ -1148,7 +1148,7 @@ test.serial('query check: insert multiple empty rows', (t) => { }); }); -test.serial('Insert all defaults in 1 row', async (t) => { +test.serial('Insert all defaults in 1 row', (t) => { const { db } = t.context; const users = sqliteTable('empty_insert_single', { @@ -1170,7 +1170,7 @@ test.serial('Insert all defaults in 1 row', async (t) => { t.deepEqual(res, [{ id: 1, name: 'Dan', state: null }]); }); -test.serial('Insert all defaults in multiple rows', async (t) => { +test.serial('Insert all defaults in multiple rows', (t) => { const { db } = t.context; const users = sqliteTable('empty_insert_multiple', { @@ -1185,7 +1185,7 @@ test.serial('Insert all defaults in multiple rows', async (t) => { sql`create table ${users} (id integer primary key, name text default 'Dan', state text)`, ); - db.insert(users).values([{}, {}]).run() + db.insert(users).values([{}, {}]).run(); const res = db.select().from(users).all(); @@ -1567,7 +1567,7 @@ test.serial('nested transaction', (t) => { db.transaction((tx) => { tx.insert(users).values({ balance: 100 }).run(); - tx.transaction(async (tx) => { + tx.transaction((tx) => { tx.update(users).set({ balance: 200 }).run(); }); }); @@ -1879,3 +1879,110 @@ test.serial('update undefined', (t) => { db.run(sql`drop table ${users}`); }); + +test.serial('async api - CRUD', async (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + await db.insert(users).values({ id: 1, name: 'John' }); + + const res = await db.select().from(users); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + await db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)); + + const res1 = await db.select().from(users); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + await db.delete(users).where(eq(users.id, 1)); + + const res2 = await db.select().from(users); + + t.deepEqual(res2, []); +}); + +test.serial('async api - insert + select w/ prepare + async execute', async (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + const insertStmt = db.insert(users).values({ id: 1, name: 'John' }).prepare(); + await insertStmt.execute(); + + const selectStmt = db.select().from(users).prepare(); + const res = await selectStmt.execute(); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + const updateStmt = db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)).prepare(); + await updateStmt.execute(); + + const res1 = await selectStmt.execute(); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + const deleteStmt = db.delete(users).where(eq(users.id, 1)).prepare(); + await deleteStmt.execute(); + + const res2 = await selectStmt.execute(); + + t.deepEqual(res2, []); +}); + +test.serial('async api - insert + select w/ prepare + sync execute', (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + const insertStmt = db.insert(users).values({ id: 1, name: 'John' }).prepare(); + insertStmt.execute().sync(); + + const selectStmt = db.select().from(users).prepare(); + const res = selectStmt.execute().sync(); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + const updateStmt = db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)).prepare(); + updateStmt.execute().sync(); + + const res1 = selectStmt.execute().sync(); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + const deleteStmt = db.delete(users).where(eq(users.id, 1)).prepare(); + deleteStmt.execute().sync(); + + const res2 = selectStmt.execute().sync(); + + t.deepEqual(res2, []); +}); diff --git a/integration-tests/tests/d1.test.ts b/integration-tests/tests/d1.test.ts index 133674387..28af3f44a 100644 --- a/integration-tests/tests/d1.test.ts +++ b/integration-tests/tests/d1.test.ts @@ -1,52 +1,61 @@ -import anyTest from "ava"; -import type { TestFn } from "ava"; -import { createSQLiteDB } from "@miniflare/shared"; -import { D1Database, D1DatabaseAPI } from "@miniflare/d1"; -import { sql, eq, placeholder, asc, gt, inArray, type Equal, TransactionRollbackError } from "drizzle-orm"; -import type { DrizzleD1Database } from "drizzle-orm/d1"; -import { drizzle } from "drizzle-orm/d1"; -import { migrate } from "drizzle-orm/d1/migrator"; -import { alias, blob, getViewConfig, integer, sqliteTable, sqliteTableCreator, sqliteView, text } from "drizzle-orm/sqlite-core"; +import { D1Database, D1DatabaseAPI } from '@miniflare/d1'; +import { createSQLiteDB } from '@miniflare/shared'; +import anyTest from 'ava'; +import type { TestFn } from 'ava'; +import { asc, eq, type Equal, gt, inArray, placeholder, sql, TransactionRollbackError } from 'drizzle-orm'; +import type { DrizzleD1Database } from 'drizzle-orm/d1'; +import { drizzle } from 'drizzle-orm/d1'; +import { migrate } from 'drizzle-orm/d1/migrator'; +import { + alias, + blob, + getViewConfig, + integer, + sqliteTable, + sqliteTableCreator, + sqliteView, + text, +} from 'drizzle-orm/sqlite-core'; import { Expect } from './utils'; -const usersTable = sqliteTable("users", { - id: integer("id").primaryKey(), - name: text("name").notNull(), - verified: integer("verified").notNull().default(0), - json: blob("json", { mode: "json" }).$type(), - createdAt: integer("created_at", { mode: "timestamp" }) +const usersTable = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + verified: integer('verified').notNull().default(0), + json: blob('json', { mode: 'json' }).$type(), + createdAt: integer('created_at', { mode: 'timestamp' }) .notNull() .default(sql`strftime('%s', 'now')`), }); -const users2Table = sqliteTable("users2", { - id: integer("id").primaryKey(), - name: text("name").notNull(), - cityId: integer("city_id").references(() => citiesTable.id), +const users2Table = sqliteTable('users2', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + cityId: integer('city_id').references(() => citiesTable.id), }); -const citiesTable = sqliteTable("cities", { - id: integer("id").primaryKey(), - name: text("name").notNull(), +const citiesTable = sqliteTable('cities', { + id: integer('id').primaryKey(), + name: text('name').notNull(), }); -const coursesTable = sqliteTable("courses", { - id: integer("id").primaryKey(), - name: text("name").notNull(), - categoryId: integer("category_id").references(() => courseCategoriesTable.id), +const coursesTable = sqliteTable('courses', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + categoryId: integer('category_id').references(() => courseCategoriesTable.id), }); -const courseCategoriesTable = sqliteTable("course_categories", { - id: integer("id").primaryKey(), - name: text("name").notNull(), +const courseCategoriesTable = sqliteTable('course_categories', { + id: integer('id').primaryKey(), + name: text('name').notNull(), }); -const orders = sqliteTable("orders", { - id: integer("id").primaryKey(), - region: text("region").notNull(), - product: text("product").notNull(), - amount: integer("amount").notNull(), - quantity: integer("quantity").notNull(), +const orders = sqliteTable('orders', { + id: integer('id').primaryKey(), + region: text('region').notNull(), + product: text('product').notNull(), + amount: integer('amount').notNull(), + quantity: integer('quantity').notNull(), }); const usersMigratorTable = sqliteTable('users12', { @@ -70,7 +79,7 @@ const test = anyTest as TestFn; test.before(async (t) => { const ctx = t.context; - const sqliteDb = await createSQLiteDB(":memory:"); + const sqliteDb = await createSQLiteDB(':memory:'); const db = new D1Database(new D1DatabaseAPI(sqliteDb)); ctx.d1 = db; /** @@ -127,9 +136,11 @@ test.beforeEach(async (t) => { create table ${coursesTable} ( id integer primary key, name text not null, - category_id integer references ${courseCategoriesTable}(${sql.identifier( - courseCategoriesTable.id.name - )}) + category_id integer references ${courseCategoriesTable}(${ + sql.identifier( + courseCategoriesTable.id.name, + ) + }) ) `); await ctx.db.run(sql` @@ -143,12 +154,12 @@ test.beforeEach(async (t) => { `); }); -test.serial("select all fields", async (t) => { +test.serial('select all fields', async (t) => { const { db } = t.context; const now = Date.now(); - await db.insert(usersTable).values({ name: "John" }).run(); + await db.insert(usersTable).values({ name: 'John' }).run(); const result = await db.select().from(usersTable).all(); t.assert(result[0]!.createdAt instanceof Date); // eslint-disable-line no-instanceof/no-instanceof @@ -156,7 +167,7 @@ test.serial("select all fields", async (t) => { t.deepEqual(result, [ { id: 1, - name: "John", + name: 'John', verified: 0, json: null, createdAt: result[0]!.createdAt, @@ -164,19 +175,19 @@ test.serial("select all fields", async (t) => { ]); }); -test.serial("select partial", async (t) => { +test.serial('select partial', async (t) => { const { db } = t.context; - await db.insert(usersTable).values({ name: "John" }).run(); + await db.insert(usersTable).values({ name: 'John' }).run(); const result = await db.select({ name: usersTable.name }).from(usersTable).all(); - t.deepEqual(result, [{ name: "John" }]); + t.deepEqual(result, [{ name: 'John' }]); }); -test.serial("select sql", async (t) => { +test.serial('select sql', async (t) => { const { db } = t.context; - await db.insert(usersTable).values({ name: "John" }).run(); + await db.insert(usersTable).values({ name: 'John' }).run(); const users = await db .select({ name: sql`upper(${usersTable.name})`, @@ -184,7 +195,7 @@ test.serial("select sql", async (t) => { .from(usersTable) .all(); - t.deepEqual(users, [{ name: "JOHN" }]); + t.deepEqual(users, [{ name: 'JOHN' }]); }); test.serial('select typed sql', async (t) => { @@ -406,8 +417,8 @@ test.serial('json insert', async (t) => { id: usersTable.id, name: usersTable.name, json: usersTable.json, - }).from(usersTable).all() - ) + }).from(usersTable).all(), + ); // Uncomment when the above bug is fixed // t.deepEqual(result, [{ id: 1, name: 'John', json: ['foo', 'bar'] }]); @@ -436,8 +447,8 @@ test.serial('insert many', async (t) => { name: usersTable.name, json: usersTable.json, verified: usersTable.verified, - }).from(usersTable).all() - ) + }).from(usersTable).all(), + ); // Uncomment when the above bug is fixed // t.deepEqual(result, [ @@ -460,10 +471,10 @@ test.serial('insert many with returning', async (t) => { */ await t.throwsAsync( db.insert(usersTable).values([ - { name: 'John' }, - { name: 'Bruce', json: ['foo', 'bar'] }, - { name: 'Jane' }, - { name: 'Austin', verified: 1 }, + { name: 'John' }, + { name: 'Bruce', json: ['foo', 'bar'] }, + { name: 'Jane' }, + { name: 'Austin', verified: 1 }, ]) .returning({ id: usersTable.id, @@ -471,7 +482,7 @@ test.serial('insert many with returning', async (t) => { json: usersTable.json, verified: usersTable.verified, }) - .all() + .all(), ); // Uncomment when the above bug is fixed @@ -1413,7 +1424,7 @@ test.serial('transaction rollback', async (t) => { await db.transaction(async (tx) => { await tx.insert(users).values({ balance: 100 }).run(); tx.rollback(); - }), new TransactionRollbackError()); + }), new TransactionRollbackError()); const result = await db.select().from(users).all(); @@ -1687,7 +1698,114 @@ test.serial('update undefined', async (t) => { ); await t.throwsAsync(async () => db.update(users).set({ name: undefined }).run()); - await t.notThrowsAsync(async () => await db.update(users).set({ id: 1, name: undefined }).run()); + await t.notThrowsAsync(async () => await db.update(users).set({ id: 1, name: undefined }).run()); await db.run(sql`drop table ${users}`); }); + +test.serial('async api - CRUD', async (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + await db.insert(users).values({ id: 1, name: 'John' }); + + const res = await db.select().from(users); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + await db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)); + + const res1 = await db.select().from(users); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + await db.delete(users).where(eq(users.id, 1)); + + const res2 = await db.select().from(users); + + t.deepEqual(res2, []); +}); + +test.serial('async api - insert + select w/ prepare + async execute', async (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + const insertStmt = db.insert(users).values({ id: 1, name: 'John' }).prepare(); + await insertStmt.execute(); + + const selectStmt = db.select().from(users).prepare(); + const res = await selectStmt.execute(); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + const updateStmt = db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)).prepare(); + await updateStmt.execute(); + + const res1 = await selectStmt.execute(); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + const deleteStmt = db.delete(users).where(eq(users.id, 1)).prepare(); + await deleteStmt.execute(); + + const res2 = await selectStmt.execute(); + + t.deepEqual(res2, []); +}); + +test.serial('async api - insert + select w/ prepare + sync execute', async (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + const insertStmt = db.insert(users).values({ id: 1, name: 'John' }).prepare(); + await insertStmt.execute(); + + const selectStmt = db.select().from(users).prepare(); + const res = await selectStmt.execute(); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + const updateStmt = db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)).prepare(); + await updateStmt.execute(); + + const res1 = await selectStmt.execute(); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + const deleteStmt = db.delete(users).where(eq(users.id, 1)).prepare(); + await deleteStmt.execute(); + + const res2 = await selectStmt.execute(); + + t.deepEqual(res2, []); +}); diff --git a/integration-tests/tests/libsql.test.ts b/integration-tests/tests/libsql.test.ts index e0112e95e..c76fa4e6e 100644 --- a/integration-tests/tests/libsql.test.ts +++ b/integration-tests/tests/libsql.test.ts @@ -398,7 +398,7 @@ test.serial('Insert all defaults in multiple rows', async (t) => { sql`create table ${users} (id integer primary key, name text default 'Dan', state text)`, ); - await db.insert(users).values([{}, {}]).run() + await db.insert(users).values([{}, {}]).run(); const res = await db.select().from(users).all(); @@ -1794,3 +1794,110 @@ test.serial('update undefined', async (t) => { await db.run(sql`drop table ${users}`); }); + +test.serial('async api - CRUD', async (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + await db.insert(users).values({ id: 1, name: 'John' }); + + const res = await db.select().from(users); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + await db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)); + + const res1 = await db.select().from(users); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + await db.delete(users).where(eq(users.id, 1)); + + const res2 = await db.select().from(users); + + t.deepEqual(res2, []); +}); + +test.serial('async api - insert + select w/ prepare + async execute', async (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + const insertStmt = db.insert(users).values({ id: 1, name: 'John' }).prepare(); + await insertStmt.execute(); + + const selectStmt = db.select().from(users).prepare(); + const res = await selectStmt.execute(); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + const updateStmt = db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)).prepare(); + await updateStmt.execute(); + + const res1 = await selectStmt.execute(); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + const deleteStmt = db.delete(users).where(eq(users.id, 1)).prepare(); + await deleteStmt.execute(); + + const res2 = await selectStmt.execute(); + + t.deepEqual(res2, []); +}); + +test.serial('async api - insert + select w/ prepare + sync execute', async (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + const insertStmt = db.insert(users).values({ id: 1, name: 'John' }).prepare(); + await insertStmt.execute(); + + const selectStmt = db.select().from(users).prepare(); + const res = await selectStmt.execute(); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + const updateStmt = db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)).prepare(); + await updateStmt.execute(); + + const res1 = await selectStmt.execute(); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + const deleteStmt = db.delete(users).where(eq(users.id, 1)).prepare(); + await deleteStmt.execute(); + + const res2 = await selectStmt.execute(); + + t.deepEqual(res2, []); +}); diff --git a/integration-tests/tests/relational/bettersqlite.test.ts b/integration-tests/tests/relational/bettersqlite.test.ts index a59bae1b6..716b4efbe 100644 --- a/integration-tests/tests/relational/bettersqlite.test.ts +++ b/integration-tests/tests/relational/bettersqlite.test.ts @@ -112,7 +112,7 @@ test('[Find Many] Get users with posts', () => { with: { posts: true, }, - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -180,7 +180,7 @@ test('[Find Many] Get users with posts + limit posts', () => { limit: 1, }, }, - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -252,7 +252,7 @@ test('[Find Many] Get users with posts + limit posts and users', () => { limit: 1, }, }, - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -315,7 +315,7 @@ test('[Find Many] Get users with posts + custom fields', () => { extras: ({ name }) => ({ lowerName: sql`lower(${name})`.as('name_lower'), }), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -409,7 +409,7 @@ test('[Find Many] Get users with posts + custom fields + limits', () => { extras: (usersTable, { sql }) => ({ lowerName: sql`lower(${usersTable.name})`.as('name_lower'), }), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -462,7 +462,7 @@ test('[Find Many] Get users with posts + orderBy', () => { }, }, orderBy: (usersTable, { desc }) => [desc(usersTable.id)], - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -539,7 +539,7 @@ test('[Find Many] Get users with posts + where', () => { where: (({ id }, { eq }) => eq(id, 1)), }, }, - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -595,7 +595,7 @@ test('[Find Many] Get users with posts + where + partial', () => { }, }, where: (({ id }, { eq }) => eq(id, 1)), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -645,7 +645,7 @@ test('[Find Many] Get users with posts + where + partial. Did not select posts i }, }, where: (({ id }, { eq }) => eq(id, 1)), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -695,7 +695,7 @@ test('[Find Many] Get users with posts + where + partial(true + false)', () => { }, }, where: (({ id }, { eq }) => eq(id, 1)), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -740,7 +740,7 @@ test('[Find Many] Get users with posts + where + partial(false)', () => { }, }, where: (({ id }, { eq }) => eq(id, 1)), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -799,7 +799,7 @@ test('[Find Many] Get users with posts in transaction', () => { where: (({ id }, { eq }) => eq(id, 1)), }, }, - }); + }).sync(); }); expectTypeOf(usersWithPosts).toEqualTypeOf<{ @@ -865,7 +865,7 @@ test('[Find Many] Get users with posts in rollbacked transaction', () => { where: (({ id }, { eq }) => eq(id, 1)), }, }, - }); + }).sync(); }) ).toThrow(TransactionRollbackError); @@ -916,7 +916,7 @@ test('[Find Many] Get only custom fields', () => { extras: ({ name }) => ({ lowerName: sql`lower(${name})`.as('name_lower'), }), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ lowerName: string; @@ -995,7 +995,7 @@ test('[Find Many] Get only custom fields + where', () => { extras: ({ name }) => ({ lowerName: sql`lower(${name})`.as('name_lower'), }), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ lowerName: string; @@ -1046,7 +1046,7 @@ test('[Find Many] Get only custom fields + where + limit', () => { extras: ({ name }) => ({ lowerName: sql`lower(${name})`.as('name_lower'), }), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ lowerName: string; @@ -1097,7 +1097,7 @@ test('[Find Many] Get only custom fields + where + orderBy', () => { extras: ({ name }) => ({ lowerName: sql`lower(${name})`.as('name_lower'), }), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ lowerName: string; @@ -1146,7 +1146,7 @@ test('[Find One] Get only custom fields', () => { extras: ({ name }) => ({ lowerName: sql`lower(${name})`.as('name_lower'), }), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -1206,7 +1206,7 @@ test('[Find One] Get only custom fields + where', () => { extras: ({ name }) => ({ lowerName: sql`lower(${name})`.as('name_lower'), }), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -1258,7 +1258,7 @@ test('[Find One] Get only custom fields + where + limit', () => { extras: ({ name }) => ({ lowerName: sql`lower(${name})`.as('name_lower'), }), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -1310,7 +1310,7 @@ test('[Find One] Get only custom fields + where + orderBy', () => { extras: ({ name }) => ({ lowerName: sql`lower(${name})`.as('name_lower'), }), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -1340,7 +1340,7 @@ test('[Find Many] Get select {}', () => { expect(() => db.query.usersTable.findMany({ columns: {}, - }) + }).sync() ).toThrow(DrizzleError); }); @@ -1355,7 +1355,7 @@ test('[Find One] Get select {}', () => { expect(() => db.query.usersTable.findFirst({ columns: {}, - }) + }).sync() ).toThrow(DrizzleError); }); @@ -1381,7 +1381,7 @@ test('[Find Many] Get deep select {}', () => { columns: {}, }, }, - }) + }).sync() ).toThrow(DrizzleError); }); @@ -1407,7 +1407,7 @@ test('[Find One] Get deep select {}', () => { columns: {}, }, }, - }) + }).sync() ).toThrow(DrizzleError); }); @@ -1431,15 +1431,15 @@ test('[Find Many] Get users with posts + prepared limit', () => { { ownerId: 3, content: 'Post3.1' }, ]).run(); - const prepared = db.query.usersTable.prepareFindMany({ + const prepared = db.query.usersTable.findMany({ with: { posts: { limit: placeholder('limit'), }, }, - }); + }).prepare(); - const usersWithPosts = prepared.execute({ limit: 1 }); + const usersWithPosts = prepared.execute({ limit: 1 }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -1499,7 +1499,7 @@ test('[Find Many] Get users with posts + prepared limit + offset', () => { { ownerId: 3, content: 'Post3.1' }, ]).run(); - const prepared = db.query.usersTable.prepareFindMany({ + const prepared = db.query.usersTable.findMany({ limit: placeholder('uLimit'), offset: placeholder('uOffset'), with: { @@ -1507,9 +1507,9 @@ test('[Find Many] Get users with posts + prepared limit + offset', () => { limit: placeholder('pLimit'), }, }, - }); + }).prepare(); - const usersWithPosts = prepared.execute({ pLimit: 1, uLimit: 3, uOffset: 1 }); + const usersWithPosts = prepared.execute({ pLimit: 1, uLimit: 3, uOffset: 1 }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -1558,16 +1558,16 @@ test('[Find Many] Get users with posts + prepared where', () => { { ownerId: 3, content: 'Post3' }, ]).run(); - const prepared = db.query.usersTable.prepareFindMany({ + const prepared = db.query.usersTable.findMany({ where: (({ id }, { eq }) => eq(id, placeholder('id'))), with: { posts: { where: (({ id }, { eq }) => eq(id, 1)), }, }, - }); + }).prepare(); - const usersWithPosts = prepared.execute({ id: 1 }); + const usersWithPosts = prepared.execute({ id: 1 }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -1611,7 +1611,7 @@ test('[Find Many] Get users with posts + prepared + limit + offset + where', () { ownerId: 3, content: 'Post3.1' }, ]).run(); - const prepared = db.query.usersTable.prepareFindMany({ + const prepared = db.query.usersTable.findMany({ limit: placeholder('uLimit'), offset: placeholder('uOffset'), where: (({ id }, { eq, or }) => or(eq(id, placeholder('id')), eq(id, 3))), @@ -1621,9 +1621,9 @@ test('[Find Many] Get users with posts + prepared + limit + offset + where', () limit: placeholder('pLimit'), }, }, - }); + }).prepare(); - const usersWithPosts = prepared.execute({ pLimit: 1, uLimit: 3, uOffset: 1, id: 2, pid: 6 }); + const usersWithPosts = prepared.execute({ pLimit: 1, uLimit: 3, uOffset: 1, id: 2, pid: 6 }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf<{ id: number; @@ -1671,7 +1671,7 @@ test('[Find One] Get users with posts', () => { with: { posts: true, }, - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -1722,7 +1722,7 @@ test('[Find One] Get users with posts + limit posts', () => { limit: 1, }, }, - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -1757,7 +1757,7 @@ test('[Find One] Get users with posts no results found', () => { limit: 1, }, }, - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -1800,7 +1800,7 @@ test('[Find One] Get users with posts + limit posts and users', () => { limit: 1, }, }, - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -1852,7 +1852,7 @@ test('[Find One] Get users with posts + custom fields', () => { extras: ({ name }) => ({ lowerName: sql`lower(${name})`.as('name_lower'), }), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -1926,7 +1926,7 @@ test('[Find One] Get users with posts + custom fields + limits', () => { extras: (usersTable, { sql }) => ({ lowerName: sql`lower(${usersTable.name})`.as('name_lower'), }), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -1980,7 +1980,7 @@ test.skip('[Find One] Get users with posts + orderBy', () => { }, }, orderBy: (usersTable, { desc }) => [desc(usersTable.id)], - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -2034,7 +2034,7 @@ test('[Find One] Get users with posts + where', () => { where: (({ id }, { eq }) => eq(id, 1)), }, }, - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -2091,7 +2091,7 @@ test('[Find One] Get users with posts + where + partial', () => { }, }, where: (({ id }, { eq }) => eq(id, 1)), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -2142,7 +2142,7 @@ test('[Find One] Get users with posts + where + partial. Did not select posts id }, }, where: (({ id }, { eq }) => eq(id, 1)), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -2193,7 +2193,7 @@ test('[Find One] Get users with posts + where + partial(true + false)', () => { }, }, where: (({ id }, { eq }) => eq(id, 1)), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -2239,7 +2239,7 @@ test('[Find One] Get users with posts + where + partial(false)', () => { }, }, where: (({ id }, { eq }) => eq(id, 1)), - }); + }).sync(); expectTypeOf(usersWithPosts).toEqualTypeOf< { @@ -2280,7 +2280,7 @@ test('Get user with invitee', () => { with: { invitee: true, }, - }); + }).sync(); expectTypeOf(usersWithInvitee).toEqualTypeOf< { @@ -2348,7 +2348,7 @@ test('Get user + limit with invitee', () => { invitee: true, }, limit: 2, - }); + }).sync(); expectTypeOf(usersWithInvitee).toEqualTypeOf< { @@ -2402,7 +2402,7 @@ test('Get user with invitee and custom fields', () => { extras: (invitee, { sql }) => ({ lower: sql`lower(${invitee.name})`.as('lower_name') }), }, }, - }); + }).sync(); expectTypeOf(usersWithInvitee).toEqualTypeOf< { @@ -2479,7 +2479,7 @@ test('Get user with invitee and custom fields + limits', () => { extras: (invitee, { sql }) => ({ lower: sql`lower(${invitee.name})`.as('lower_name') }), }, }, - }); + }).sync(); expectTypeOf(usersWithInvitee).toEqualTypeOf< { @@ -2544,7 +2544,7 @@ test('Get user with invitee + order by', () => { with: { invitee: true, }, - }); + }).sync(); expectTypeOf(usersWithInvitee).toEqualTypeOf< { @@ -2610,7 +2610,7 @@ test('Get user with invitee + where', () => { with: { invitee: true, }, - }); + }).sync(); expectTypeOf(usersWithInvitee).toEqualTypeOf< { @@ -2669,7 +2669,7 @@ test('Get user with invitee + where + partial', () => { }, }, }, - }); + }).sync(); expectTypeOf(usersWithInvitee).toEqualTypeOf< { @@ -2719,7 +2719,7 @@ test('Get user with invitee + where + partial. Did not select users id, but use }, }, }, - }); + }).sync(); expectTypeOf(usersWithInvitee).toEqualTypeOf< { @@ -2769,7 +2769,7 @@ test('Get user with invitee + where + partial(true+false)', () => { }, }, }, - }); + }).sync(); expectTypeOf(usersWithInvitee).toEqualTypeOf< { @@ -2818,7 +2818,7 @@ test('Get user with invitee + where + partial(false)', () => { }, }, }, - }); + }).sync(); expectTypeOf(usersWithInvitee).toEqualTypeOf< { @@ -2874,7 +2874,7 @@ test('Get user with invitee and posts', () => { invitee: true, posts: true, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -2964,7 +2964,7 @@ test('Get user with invitee and posts + limit posts and users', () => { limit: 1, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -3049,7 +3049,7 @@ test('Get user with invitee and posts + limits + custom fields in each', () => { extras: (posts, { sql }) => ({ lower: sql`lower(${posts.content})`.as('lower_content') }), }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -3137,7 +3137,7 @@ test('Get user with invitee and posts + custom fields in each', () => { extras: (posts, { sql }) => ({ lower: sql`lower(${posts.content})`.as('lower_name') }), }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -3256,7 +3256,7 @@ test.skip('Get user with invitee and posts + orderBy', () => { orderBy: (posts, { desc }) => [desc(posts.id)], }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -3357,7 +3357,7 @@ test('Get user with invitee and posts + where', () => { where: (posts, { eq }) => (eq(posts.ownerId, 2)), }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -3430,7 +3430,7 @@ test('Get user with invitee and posts + limit posts and users + where', () => { limit: 1, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -3495,7 +3495,7 @@ test('Get user with invitee and posts + orderBy + where + custom', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -3596,7 +3596,7 @@ test('Get user with invitee and posts + orderBy + where + partial + custom', () }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -3671,7 +3671,7 @@ test('Get user with posts and posts with comments', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -3830,7 +3830,7 @@ test('Get user with posts and posts with comments and comments with owner', () = }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf<{ id: number; @@ -3970,7 +3970,7 @@ test('[Find Many] Get users with groups', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf<{ id: number; @@ -4072,7 +4072,7 @@ test('[Find Many] Get groups with users', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf<{ id: number; @@ -4177,7 +4177,7 @@ test('[Find Many] Get users with groups + limit', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf<{ id: number; @@ -4260,7 +4260,7 @@ test('[Find Many] Get groups with users + limit', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf<{ id: number; @@ -4344,7 +4344,7 @@ test('[Find Many] Get users with groups + limit + where', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf<{ id: number; @@ -4414,7 +4414,7 @@ test('[Find Many] Get groups with users + limit + where', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf<{ id: number; @@ -4482,7 +4482,7 @@ test('[Find Many] Get users with groups + where', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf<{ id: number; @@ -4559,7 +4559,7 @@ test('[Find Many] Get groups with users + where', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf<{ id: number; @@ -4635,7 +4635,7 @@ test('[Find Many] Get users with groups + orderBy', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf<{ id: number; @@ -4737,7 +4737,7 @@ test('[Find Many] Get groups with users + orderBy', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf<{ id: number; @@ -4842,7 +4842,7 @@ test('[Find Many] Get users with groups + orderBy + limit', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf<{ id: number; @@ -4927,7 +4927,7 @@ test('[Find One] Get users with groups', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -4991,7 +4991,7 @@ test('[Find One] Get groups with users', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -5056,7 +5056,7 @@ test('[Find One] Get users with groups + limit', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -5121,7 +5121,7 @@ test('[Find One] Get groups with users + limit', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -5187,7 +5187,7 @@ test('[Find One] Get users with groups + limit + where', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -5254,7 +5254,7 @@ test('[Find One] Get groups with users + limit + where', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -5320,7 +5320,7 @@ test('[Find One] Get users with groups + where', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -5380,7 +5380,7 @@ test('[Find One] Get groups with users + where', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -5446,7 +5446,7 @@ test('[Find One] Get users with groups + orderBy', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -5518,7 +5518,7 @@ test('[Find One] Get groups with users + orderBy', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -5585,7 +5585,7 @@ test('[Find One] Get users with groups + orderBy + limit', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -5653,7 +5653,7 @@ test('Get groups with users + orderBy + limit', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -5741,7 +5741,7 @@ test('Get users with groups + custom', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -5865,7 +5865,7 @@ test('Get groups with users + custom', () => { }, }, }, - }); + }).sync(); expectTypeOf(response).toEqualTypeOf< { @@ -5950,6 +5950,34 @@ test('Get groups with users + custom', () => { }); }); +test('async api', async () => { + await db.insert(usersTable).values([{ id: 1, name: 'Dan' }]); + const users = await db.query.usersTable.findMany(); + expect(users).toEqual([{ id: 1, name: 'Dan', verified: 0, invitedBy: null }]); +}); + +test('async api - sync()', () => { + db.insert(usersTable).values([{ id: 1, name: 'Dan' }]).run(); + const users = db.query.usersTable.findMany().sync(); + expect(users).toEqual([{ id: 1, name: 'Dan', verified: 0, invitedBy: null }]); +}); + +test('async api - prepare', async () => { + const insertStmt = db.insert(usersTable).values([{ id: 1, name: 'Dan' }]).prepare(); + await insertStmt.execute(); + const queryStmt = db.query.usersTable.findMany().prepare(); + const users = await queryStmt.execute(); + expect(users).toEqual([{ id: 1, name: 'Dan', verified: 0, invitedBy: null }]); +}); + +test('async api - sync() + prepare', () => { + const insertStmt = db.insert(usersTable).values([{ id: 1, name: 'Dan' }]).prepare(); + insertStmt.execute().sync(); + const queryStmt = db.query.usersTable.findMany().prepare(); + const users = queryStmt.execute().sync(); + expect(users).toEqual([{ id: 1, name: 'Dan', verified: 0, invitedBy: null }]); +}); + // + custom + where + orderby // + custom + where + orderby + limit diff --git a/integration-tests/tests/relational/turso.test.ts b/integration-tests/tests/relational/turso.test.ts index cc9027c9a..525a0bc16 100644 --- a/integration-tests/tests/relational/turso.test.ts +++ b/integration-tests/tests/relational/turso.test.ts @@ -5976,6 +5976,20 @@ test('Get groups with users + custom', async () => { }); }); +test('async api', async () => { + await db.insert(usersTable).values([{ id: 1, name: 'Dan' }]); + const users = await db.query.usersTable.findMany(); + expect(users).toEqual([{ id: 1, name: 'Dan', verified: 0, invitedBy: null }]); +}); + +test('async api - prepare', async () => { + const insertStmt = db.insert(usersTable).values([{ id: 1, name: 'Dan' }]).prepare(); + await insertStmt.execute(); + const queryStmt = db.query.usersTable.findMany().prepare(); + const users = await queryStmt.execute(); + expect(users).toEqual([{ id: 1, name: 'Dan', verified: 0, invitedBy: null }]); +}); + // + custom + where + orderby // + custom + where + orderby + limit diff --git a/integration-tests/tests/sql.js.test.ts b/integration-tests/tests/sql.js.test.ts index 6e91f5765..a5f5922bd 100644 --- a/integration-tests/tests/sql.js.test.ts +++ b/integration-tests/tests/sql.js.test.ts @@ -180,16 +180,16 @@ test.beforeEach((t) => { `); }); -test.serial('insert bigint values', async (t) => { +test.serial('insert bigint values', (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(); + db.insert(bigIntExample).values({ name: 'one', bigInt: BigInt('0') }).run(); + db.insert(bigIntExample).values({ name: 'two', bigInt: BigInt('127') }).run(); + db.insert(bigIntExample).values({ name: 'three', bigInt: BigInt('32767') }).run(); + db.insert(bigIntExample).values({ name: 'four', bigInt: BigInt('1234567890') }).run(); + db.insert(bigIntExample).values({ name: 'five', bigInt: BigInt('12345678900987654321') }).run(); - const result = await db.select().from(bigIntExample).all(); + const result = db.select().from(bigIntExample).all(); t.deepEqual(result, [ { id: 1, name: 'one', bigInt: BigInt('0') }, { id: 2, name: 'two', bigInt: BigInt('127') }, @@ -1367,7 +1367,7 @@ test.serial('nested transaction', (t) => { db.transaction((tx) => { tx.insert(users).values({ balance: 100 }).run(); - tx.transaction(async (tx) => { + tx.transaction((tx) => { tx.update(users).set({ balance: 200 }).run(); }); }); @@ -1679,3 +1679,110 @@ test.serial('update undefined', (t) => { db.run(sql`drop table ${users}`); }); + +test.serial('async api - CRUD', async (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + await db.insert(users).values({ id: 1, name: 'John' }); + + const res = await db.select().from(users); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + await db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)); + + const res1 = await db.select().from(users); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + await db.delete(users).where(eq(users.id, 1)); + + const res2 = await db.select().from(users); + + t.deepEqual(res2, []); +}); + +test.serial('async api - insert + select w/ prepare + async execute', async (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + const insertStmt = db.insert(users).values({ id: 1, name: 'John' }).prepare(); + await insertStmt.execute(); + + const selectStmt = db.select().from(users).prepare(); + const res = await selectStmt.execute(); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + const updateStmt = db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)).prepare(); + await updateStmt.execute(); + + const res1 = await selectStmt.execute(); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + const deleteStmt = db.delete(users).where(eq(users.id, 1)).prepare(); + await deleteStmt.execute(); + + const res2 = await selectStmt.execute(); + + t.deepEqual(res2, []); +}); + +test.serial('async api - insert + select w/ prepare + sync execute', (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + const insertStmt = db.insert(users).values({ id: 1, name: 'John' }).prepare(); + insertStmt.execute().sync(); + + const selectStmt = db.select().from(users).prepare(); + const res = selectStmt.execute().sync(); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + const updateStmt = db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)).prepare(); + updateStmt.execute().sync(); + + const res1 = selectStmt.execute().sync(); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + const deleteStmt = db.delete(users).where(eq(users.id, 1)).prepare(); + deleteStmt.execute().sync(); + + const res2 = selectStmt.execute().sync(); + + t.deepEqual(res2, []); +}); diff --git a/integration-tests/tests/sqlite-proxy.test.ts b/integration-tests/tests/sqlite-proxy.test.ts index afbd53bc3..f562f963e 100644 --- a/integration-tests/tests/sqlite-proxy.test.ts +++ b/integration-tests/tests/sqlite-proxy.test.ts @@ -978,3 +978,110 @@ test.serial('update undefined', async (t) => { await db.run(sql`drop table ${users}`); }); + +test.serial('async api - CRUD', async (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + await db.insert(users).values({ id: 1, name: 'John' }); + + const res = await db.select().from(users); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + await db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)); + + const res1 = await db.select().from(users); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + await db.delete(users).where(eq(users.id, 1)); + + const res2 = await db.select().from(users); + + t.deepEqual(res2, []); +}); + +test.serial('async api - insert + select w/ prepare + async execute', async (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + const insertStmt = db.insert(users).values({ id: 1, name: 'John' }).prepare(); + await insertStmt.execute(); + + const selectStmt = db.select().from(users).prepare(); + const res = await selectStmt.execute(); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + const updateStmt = db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)).prepare(); + await updateStmt.execute(); + + const res1 = await selectStmt.execute(); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + const deleteStmt = db.delete(users).where(eq(users.id, 1)).prepare(); + await deleteStmt.execute(); + + const res2 = await selectStmt.execute(); + + t.deepEqual(res2, []); +}); + +test.serial('async api - insert + select w/ prepare + sync execute', async (t) => { + const { db } = t.context; + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + db.run(sql`drop table if exists ${users}`); + + db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + const insertStmt = db.insert(users).values({ id: 1, name: 'John' }).prepare(); + await insertStmt.execute(); + + const selectStmt = db.select().from(users).prepare(); + const res = await selectStmt.execute(); + + t.deepEqual(res, [{ id: 1, name: 'John' }]); + + const updateStmt = db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)).prepare(); + await updateStmt.execute(); + + const res1 = await selectStmt.execute(); + + t.deepEqual(res1, [{ id: 1, name: 'John1' }]); + + const deleteStmt = db.delete(users).where(eq(users.id, 1)).prepare(); + await deleteStmt.execute(); + + const res2 = await selectStmt.execute(); + + t.deepEqual(res2, []); +});