From ba389d53fac6a644d2e14c6b1275056d7e49401b Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 21 Oct 2023 23:32:55 +0300 Subject: [PATCH 01/50] fix: Fix proxy migrators and tests --- drizzle-orm/src/mysql-proxy/migrator.ts | 4 +- drizzle-orm/src/pg-proxy/migrator.ts | 12 +- .../first/0000_nostalgic_carnage.sql | 7 + .../mysql-proxy/first/meta/0000_snapshot.json | 60 ++++++ .../mysql-proxy/first/meta/_journal.json | 13 ++ .../second/0000_nostalgic_carnage.sql | 7 + .../drizzle2/mysql-proxy/second/0001_test.sql | 5 + .../second/meta/0000_snapshot.json | 60 ++++++ .../second/meta/0001_snapshot.json | 96 +++++++++ .../mysql-proxy/second/meta/_journal.json | 20 ++ .../pg-proxy/first/0000_puzzling_flatman.sql | 7 + .../pg-proxy/first/meta/0000_snapshot.json | 56 ++++++ .../pg-proxy/first/meta/_journal.json | 13 ++ .../pg-proxy/second/0000_puzzling_flatman.sql | 7 + .../drizzle2/pg-proxy/second/0001_test.sql | 5 + .../pg-proxy/second/meta/0000_snapshot.json | 56 ++++++ .../pg-proxy/second/meta/0001_snapshot.json | 56 ++++++ .../pg-proxy/second/meta/_journal.json | 20 ++ integration-tests/tests/mysql-proxy.test.ts | 186 ++++++++++-------- integration-tests/tests/pg-proxy.test.ts | 71 ++++--- 20 files changed, 643 insertions(+), 118 deletions(-) create mode 100644 integration-tests/drizzle2/mysql-proxy/first/0000_nostalgic_carnage.sql create mode 100644 integration-tests/drizzle2/mysql-proxy/first/meta/0000_snapshot.json create mode 100644 integration-tests/drizzle2/mysql-proxy/first/meta/_journal.json create mode 100644 integration-tests/drizzle2/mysql-proxy/second/0000_nostalgic_carnage.sql create mode 100644 integration-tests/drizzle2/mysql-proxy/second/0001_test.sql create mode 100644 integration-tests/drizzle2/mysql-proxy/second/meta/0000_snapshot.json create mode 100644 integration-tests/drizzle2/mysql-proxy/second/meta/0001_snapshot.json create mode 100644 integration-tests/drizzle2/mysql-proxy/second/meta/_journal.json create mode 100644 integration-tests/drizzle2/pg-proxy/first/0000_puzzling_flatman.sql create mode 100644 integration-tests/drizzle2/pg-proxy/first/meta/0000_snapshot.json create mode 100644 integration-tests/drizzle2/pg-proxy/first/meta/_journal.json create mode 100644 integration-tests/drizzle2/pg-proxy/second/0000_puzzling_flatman.sql create mode 100644 integration-tests/drizzle2/pg-proxy/second/0001_test.sql create mode 100644 integration-tests/drizzle2/pg-proxy/second/meta/0000_snapshot.json create mode 100644 integration-tests/drizzle2/pg-proxy/second/meta/0001_snapshot.json create mode 100644 integration-tests/drizzle2/pg-proxy/second/meta/_journal.json diff --git a/drizzle-orm/src/mysql-proxy/migrator.ts b/drizzle-orm/src/mysql-proxy/migrator.ts index 1ca0e799b..79873a4bd 100644 --- a/drizzle-orm/src/mysql-proxy/migrator.ts +++ b/drizzle-orm/src/mysql-proxy/migrator.ts @@ -26,7 +26,7 @@ export async function migrate>( id: sql.raw('id'), hash: sql.raw('hash'), created_at: sql.raw('created_at'), - }).from(sql.raw(migrationsTable)).orderBy( + }).from(sql.identifier(migrationsTable).getSQL()).orderBy( sql.raw('created_at desc') ).limit(1); @@ -41,7 +41,7 @@ export async function migrate>( ) { queriesToRun.push( ...migration.sql, - `insert into ${sql.identifier(migrationsTable)} (\`hash\`, \`created_at\`) values(${migration.hash}, ${migration.folderMillis})`, + `insert into ${sql.identifier(migrationsTable).value} (\`hash\`, \`created_at\`) values('${migration.hash}', '${migration.folderMillis}')`, ); } } diff --git a/drizzle-orm/src/pg-proxy/migrator.ts b/drizzle-orm/src/pg-proxy/migrator.ts index d1519de84..7107cff04 100644 --- a/drizzle-orm/src/pg-proxy/migrator.ts +++ b/drizzle-orm/src/pg-proxy/migrator.ts @@ -11,7 +11,7 @@ export async function migrate>( config: string | MigrationConfig, ) { const migrations = readMigrationFiles(config); - + const migrationTableCreate = sql` CREATE TABLE IF NOT EXISTS "drizzle"."__drizzle_migrations" ( id SERIAL PRIMARY KEY, @@ -23,9 +23,13 @@ export async function migrate>( await db.execute(sql`CREATE SCHEMA IF NOT EXISTS "drizzle"`); await db.execute(migrationTableCreate); - const dbMigrations = await db.execute( + const dbMigrations = await db.execute<{ + id: number; + hash: string; + created_at: string; + }>( sql`SELECT id, hash, created_at FROM "drizzle"."__drizzle_migrations" ORDER BY created_at DESC LIMIT 1` - ) as unknown as [number, string, string][]; + ); const lastDbMigration = dbMigrations[0] ?? undefined; @@ -34,7 +38,7 @@ export async function migrate>( for (const migration of migrations) { if ( !lastDbMigration - || Number(lastDbMigration[2])! < migration.folderMillis + || Number(lastDbMigration.created_at)! < migration.folderMillis ) { queriesToRun.push( ...migration.sql, diff --git a/integration-tests/drizzle2/mysql-proxy/first/0000_nostalgic_carnage.sql b/integration-tests/drizzle2/mysql-proxy/first/0000_nostalgic_carnage.sql new file mode 100644 index 000000000..4266589a6 --- /dev/null +++ b/integration-tests/drizzle2/mysql-proxy/first/0000_nostalgic_carnage.sql @@ -0,0 +1,7 @@ +CREATE TABLE `userstest` ( + `id` serial PRIMARY KEY, + `name` text NOT NULL, + `verified` boolean NOT NULL DEFAULT false, + `jsonb` json, + `created_at` timestamp NOT NULL DEFAULT now() +); \ No newline at end of file diff --git a/integration-tests/drizzle2/mysql-proxy/first/meta/0000_snapshot.json b/integration-tests/drizzle2/mysql-proxy/first/meta/0000_snapshot.json new file mode 100644 index 000000000..1d01f3bcf --- /dev/null +++ b/integration-tests/drizzle2/mysql-proxy/first/meta/0000_snapshot.json @@ -0,0 +1,60 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "8e8c8378-0496-40f6-88e3-98aab8282b1f", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "userstest": { + "name": "userstest", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": false, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false, + "autoincrement": false + }, + "jsonb": { + "name": "jsonb", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()", + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + } + }, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} diff --git a/integration-tests/drizzle2/mysql-proxy/first/meta/_journal.json b/integration-tests/drizzle2/mysql-proxy/first/meta/_journal.json new file mode 100644 index 000000000..708471cf5 --- /dev/null +++ b/integration-tests/drizzle2/mysql-proxy/first/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "5", + "dialect": "mysql", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1680270921944, + "tag": "0000_nostalgic_carnage", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/integration-tests/drizzle2/mysql-proxy/second/0000_nostalgic_carnage.sql b/integration-tests/drizzle2/mysql-proxy/second/0000_nostalgic_carnage.sql new file mode 100644 index 000000000..4266589a6 --- /dev/null +++ b/integration-tests/drizzle2/mysql-proxy/second/0000_nostalgic_carnage.sql @@ -0,0 +1,7 @@ +CREATE TABLE `userstest` ( + `id` serial PRIMARY KEY, + `name` text NOT NULL, + `verified` boolean NOT NULL DEFAULT false, + `jsonb` json, + `created_at` timestamp NOT NULL DEFAULT now() +); \ No newline at end of file diff --git a/integration-tests/drizzle2/mysql-proxy/second/0001_test.sql b/integration-tests/drizzle2/mysql-proxy/second/0001_test.sql new file mode 100644 index 000000000..1d2757a0b --- /dev/null +++ b/integration-tests/drizzle2/mysql-proxy/second/0001_test.sql @@ -0,0 +1,5 @@ +CREATE TABLE `users12` ( + `id` serial AUTO_INCREMENT PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `email` text NOT NULL +); \ No newline at end of file diff --git a/integration-tests/drizzle2/mysql-proxy/second/meta/0000_snapshot.json b/integration-tests/drizzle2/mysql-proxy/second/meta/0000_snapshot.json new file mode 100644 index 000000000..1d01f3bcf --- /dev/null +++ b/integration-tests/drizzle2/mysql-proxy/second/meta/0000_snapshot.json @@ -0,0 +1,60 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "8e8c8378-0496-40f6-88e3-98aab8282b1f", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "userstest": { + "name": "userstest", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": false, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false, + "autoincrement": false + }, + "jsonb": { + "name": "jsonb", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()", + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + } + }, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} diff --git a/integration-tests/drizzle2/mysql-proxy/second/meta/0001_snapshot.json b/integration-tests/drizzle2/mysql-proxy/second/meta/0001_snapshot.json new file mode 100644 index 000000000..1a893f488 --- /dev/null +++ b/integration-tests/drizzle2/mysql-proxy/second/meta/0001_snapshot.json @@ -0,0 +1,96 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "8e8c8378-0496-40f6-88e3-98aab8282b1f", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "userstest": { + "name": "userstest", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": false, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false, + "autoincrement": false + }, + "jsonb": { + "name": "jsonb", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()", + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "users12": { + "name": "users12", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "my_unique_index": { + "name": "my_unique_index", + "columns": ["name"], + "isUnique": true, + "using": "btree" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {} + } + }, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} diff --git a/integration-tests/drizzle2/mysql-proxy/second/meta/_journal.json b/integration-tests/drizzle2/mysql-proxy/second/meta/_journal.json new file mode 100644 index 000000000..fdd1a2ee3 --- /dev/null +++ b/integration-tests/drizzle2/mysql-proxy/second/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "5", + "dialect": "mysql", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1680270921944, + "tag": "0000_nostalgic_carnage", + "breakpoints": true + }, + { + "idx": 1, + "version": "5", + "when": 1680270921945, + "tag": "0001_test", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/integration-tests/drizzle2/pg-proxy/first/0000_puzzling_flatman.sql b/integration-tests/drizzle2/pg-proxy/first/0000_puzzling_flatman.sql new file mode 100644 index 000000000..06ed6ebf7 --- /dev/null +++ b/integration-tests/drizzle2/pg-proxy/first/0000_puzzling_flatman.sql @@ -0,0 +1,7 @@ +CREATE TABLE "users" ( + id serial PRIMARY KEY, + name text NOT NULL, + verified boolean NOT NULL DEFAULT false, + jsonb jsonb, + created_at timestamptz NOT NULL DEFAULT now() +); \ No newline at end of file diff --git a/integration-tests/drizzle2/pg-proxy/first/meta/0000_snapshot.json b/integration-tests/drizzle2/pg-proxy/first/meta/0000_snapshot.json new file mode 100644 index 000000000..4bb618d40 --- /dev/null +++ b/integration-tests/drizzle2/pg-proxy/first/meta/0000_snapshot.json @@ -0,0 +1,56 @@ +{ + "version": "5", + "dialect": "pg", + "id": "cb1644bb-c5da-465a-8d70-f63d81e34514", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "jsonb": { + "name": "jsonb", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} diff --git a/integration-tests/drizzle2/pg-proxy/first/meta/_journal.json b/integration-tests/drizzle2/pg-proxy/first/meta/_journal.json new file mode 100644 index 000000000..6b2a35b4a --- /dev/null +++ b/integration-tests/drizzle2/pg-proxy/first/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "5", + "dialect": "pg", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1680271923328, + "tag": "0000_puzzling_flatman", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/integration-tests/drizzle2/pg-proxy/second/0000_puzzling_flatman.sql b/integration-tests/drizzle2/pg-proxy/second/0000_puzzling_flatman.sql new file mode 100644 index 000000000..06ed6ebf7 --- /dev/null +++ b/integration-tests/drizzle2/pg-proxy/second/0000_puzzling_flatman.sql @@ -0,0 +1,7 @@ +CREATE TABLE "users" ( + id serial PRIMARY KEY, + name text NOT NULL, + verified boolean NOT NULL DEFAULT false, + jsonb jsonb, + created_at timestamptz NOT NULL DEFAULT now() +); \ No newline at end of file diff --git a/integration-tests/drizzle2/pg-proxy/second/0001_test.sql b/integration-tests/drizzle2/pg-proxy/second/0001_test.sql new file mode 100644 index 000000000..bb8e5f34d --- /dev/null +++ b/integration-tests/drizzle2/pg-proxy/second/0001_test.sql @@ -0,0 +1,5 @@ +CREATE TABLE "users12" ( + "id" serial PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "email" text NOT NULL +); \ No newline at end of file diff --git a/integration-tests/drizzle2/pg-proxy/second/meta/0000_snapshot.json b/integration-tests/drizzle2/pg-proxy/second/meta/0000_snapshot.json new file mode 100644 index 000000000..4bb618d40 --- /dev/null +++ b/integration-tests/drizzle2/pg-proxy/second/meta/0000_snapshot.json @@ -0,0 +1,56 @@ +{ + "version": "5", + "dialect": "pg", + "id": "cb1644bb-c5da-465a-8d70-f63d81e34514", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "jsonb": { + "name": "jsonb", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} diff --git a/integration-tests/drizzle2/pg-proxy/second/meta/0001_snapshot.json b/integration-tests/drizzle2/pg-proxy/second/meta/0001_snapshot.json new file mode 100644 index 000000000..c5d4b7cfe --- /dev/null +++ b/integration-tests/drizzle2/pg-proxy/second/meta/0001_snapshot.json @@ -0,0 +1,56 @@ +{ + "version": "5", + "dialect": "pg", + "id": "f2a88b25-f2da-4973-879e-60b57f24e7b9", + "prevId": "cb1644bb-c5da-465a-8d70-f63d81e34514", + "tables": { + "users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "jsonb": { + "name": "jsonb", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} diff --git a/integration-tests/drizzle2/pg-proxy/second/meta/_journal.json b/integration-tests/drizzle2/pg-proxy/second/meta/_journal.json new file mode 100644 index 000000000..de7a2ac56 --- /dev/null +++ b/integration-tests/drizzle2/pg-proxy/second/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "5", + "dialect": "pg", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1680271923328, + "tag": "0000_puzzling_flatman", + "breakpoints": true + }, + { + "idx": 1, + "version": "5", + "when": 1680271923329, + "tag": "0001_test", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/integration-tests/tests/mysql-proxy.test.ts b/integration-tests/tests/mysql-proxy.test.ts index 782039bf6..274270121 100644 --- a/integration-tests/tests/mysql-proxy.test.ts +++ b/integration-tests/tests/mysql-proxy.test.ts @@ -105,22 +105,22 @@ const usersMigratorTable = mysqlTable('users12', { // eslint-disable-next-line drizzle/require-entity-kind class ServerSimulator { - constructor(private db: mysql.Connection) {} + constructor(private db: mysql.Connection) { } async query(sql: string, params: any[], method: 'all' | 'execute') { if (method === 'all') { try { const result = await this.db.query({ - sql, - values: params, + sql, + values: params, rowsAsArray: true, - typeCast: function(field: any, next: any) { - if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') { - return field.string(); - } - return next(); - }, - }); + typeCast: function (field: any, next: any) { + if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') { + return field.string(); + } + return next(); + }, + }); return { data: result[0] as any }; } catch (e: any) { @@ -129,14 +129,14 @@ class ServerSimulator { } else if (method === 'execute') { try { const result = await this.db.query({ - sql, - values: params, - typeCast: function(field: any, next: any) { - if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') { - return field.string(); - } - return next(); - }, + sql, + values: params, + typeCast: function (field: any, next: any) { + if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') { + return field.string(); + } + return next(); + }, }); return { data: result as any }; @@ -149,14 +149,15 @@ class ServerSimulator { } async migrations(queries: string[]) { - await this.db.query('BEGIN'); + await this.db.query('START TRANSACTION'); try { for (const query of queries) { await this.db.query(query); } await this.db.query('COMMIT'); - } catch { + } catch (e) { await this.db.query('ROLLBACK'); + throw e; } return {}; @@ -168,7 +169,7 @@ interface Context { mysqlContainer: Docker.Container; db: MySqlRemoteDatabase; client: mysql.Connection; - serverSimulator: ServerSimulator; + serverSimulator: ServerSimulator; } const test = anyTest as TestFn; @@ -227,21 +228,21 @@ test.before(async (t) => { throw lastError; } - ctx.serverSimulator = new ServerSimulator(ctx.client); + ctx.serverSimulator = new ServerSimulator(ctx.client); ctx.db = proxyDrizzle(async (sql, params, method) => { - try { - const response = await ctx.serverSimulator.query(sql, params, method); - - if (response.error !== undefined) { - throw response.error; - } - - return { rows: response.data }; - } catch (e: any) { - console.error('Error from mysql proxy server:', e.message); - throw e; - } + try { + const response = await ctx.serverSimulator.query(sql, params, method); + + if (response.error !== undefined) { + throw response.error; + } + + return { rows: response.data }; + } catch (e: any) { + console.error('Error from mysql proxy server:', e.message); + throw e; + } }, { logger: ENABLE_LOGGING }); }); @@ -252,47 +253,47 @@ test.after.always(async (t) => { }); test.beforeEach(async (t) => { - try { - - const ctx = t.context; - await ctx.db.execute(sql`drop table if exists \`userstest\``); - await ctx.db.execute(sql`drop table if exists \`users2\``); - await ctx.db.execute(sql`drop table if exists \`cities\``); - - await ctx.db.execute( - sql` - create table \`userstest\` ( - \`id\` serial primary key, - \`name\` text not null, - \`verified\` boolean not null default false, - \`jsonb\` json, - \`created_at\` timestamp not null default now() - ) - `, - ); - - await ctx.db.execute( - sql` - create table \`users2\` ( - \`id\` serial primary key, - \`name\` text not null, - \`city_id\` int references \`cities\`(\`id\`) - ) - `, - ); - - await ctx.db.execute( - sql` - create table \`cities\` ( - \`id\` serial primary key, - \`name\` text not null - ) - `, - ); - } catch (error) { - console.log('error', error) - throw error; - } + try { + + const ctx = t.context; + await ctx.db.execute(sql`drop table if exists \`userstest\``); + await ctx.db.execute(sql`drop table if exists \`users2\``); + await ctx.db.execute(sql`drop table if exists \`cities\``); + + await ctx.db.execute( + sql` + create table \`userstest\` ( + \`id\` serial primary key, + \`name\` text not null, + \`verified\` boolean not null default false, + \`jsonb\` json, + \`created_at\` timestamp not null default now() + ) + `, + ); + + await ctx.db.execute( + sql` + create table \`users2\` ( + \`id\` serial primary key, + \`name\` text not null, + \`city_id\` int references \`cities\`(\`id\`) + ) + `, + ); + + await ctx.db.execute( + sql` + create table \`cities\` ( + \`id\` serial primary key, + \`name\` text not null + ) + `, + ); + } catch (error) { + console.log('error', error) + throw error; + } }); test.serial('table configs: unique third param', async (t) => { @@ -1003,8 +1004,7 @@ test.serial('prepared statement with placeholder in .where', async (t) => { test.serial('migrator', async (t) => { const { db, serverSimulator } = t.context; - await db.execute(sql`drop table if exists cities_migration`); - await db.execute(sql`drop table if exists users_migration`); + await db.execute(sql`drop table if exists userstest`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists __drizzle_migrations`); @@ -1015,16 +1015,36 @@ test.serial('migrator', async (t) => { console.error(e); throw new Error('Proxy server cannot run migrations'); } - }, { migrationsFolder: './drizzle2/mysql' }); + }, { migrationsFolder: './drizzle2/mysql-proxy/first' }); - await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + await t.notThrowsAsync(async () => { + await db.insert(usersTable).values({ name: 'John' }); + }); - const result = await db.select().from(usersMigratorTable); + await t.throwsAsync(async () => { + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + }, { + message: 'Table \'drizzle.users12\' doesn\'t exist' + }); - t.deepEqual(result, [{ id: 1, name: 'John', email: 'email' }]); + await migrate(db, async (queries) => { + try { + await serverSimulator.migrations(queries); + } catch (e) { + console.error(e); + throw new Error('Proxy server cannot run migrations'); + } + }, { migrationsFolder: './drizzle2/mysql-proxy/second' }); + + await t.notThrowsAsync(async () => { + await db.insert(usersTable).values({ name: 'John' }); + }); + + await t.notThrowsAsync(async () => { + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + }); - await db.execute(sql`drop table cities_migration`); - await db.execute(sql`drop table users_migration`); + await db.execute(sql`drop table userstest`); await db.execute(sql`drop table users12`); await db.execute(sql`drop table __drizzle_migrations`); }); diff --git a/integration-tests/tests/pg-proxy.test.ts b/integration-tests/tests/pg-proxy.test.ts index 8eab46897..7cc982004 100644 --- a/integration-tests/tests/pg-proxy.test.ts +++ b/integration-tests/tests/pg-proxy.test.ts @@ -53,12 +53,12 @@ import getPort from 'get-port'; import { migrate } from 'drizzle-orm/pg-proxy/migrator'; import { drizzle as proxyDrizzle } from 'drizzle-orm/pg-proxy'; import type { PgRemoteDatabase } from 'drizzle-orm/pg-proxy'; -import type { Equal} from './utils.ts'; +import type { Equal } from './utils.ts'; import { Expect } from './utils.ts'; // eslint-disable-next-line drizzle/require-entity-kind class ServerSimulator { - constructor(private db: pg.Client) {} + constructor(private db: pg.Client) { } async query(sql: string, params: any[], method: 'all' | 'execute') { if (method === 'all') { @@ -96,8 +96,9 @@ class ServerSimulator { await this.db.query(query); } await this.db.query('COMMIT'); - } catch { + } catch (e) { await this.db.query('ROLLBACK'); + throw e; } return {}; @@ -232,7 +233,7 @@ test.before(async (t) => { ctx.serverSimulator = new ServerSimulator(ctx.client); - ctx.db = proxyDrizzle(async (sql, params, method) => { + ctx.db = proxyDrizzle(async (sql, params, method) => { try { const response = await ctx.serverSimulator.query(sql, params, method); @@ -246,8 +247,8 @@ test.before(async (t) => { throw e; } }, { - logger: ENABLE_LOGGING - }) + logger: ENABLE_LOGGING + }) }); test.after.always(async (t) => { @@ -1045,10 +1046,10 @@ test.serial('prepared statement with placeholder in .offset', async (t) => { test.serial('migrator', async (t) => { const { db, serverSimulator } = t.context; - await db.execute(sql`drop table if exists all_columns`); + await db.execute(sql`drop table if exists users`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); - + await migrate(db, async (queries) => { try { await serverSimulator.migrations(queries); @@ -1056,15 +1057,36 @@ test.serial('migrator', async (t) => { console.error(e); throw new Error('Proxy server cannot run migrations'); } - }, { migrationsFolder: './drizzle2/pg' }); + }, { migrationsFolder: './drizzle2/pg-proxy/first' }); - await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + await t.notThrowsAsync(async () => { + await db.insert(usersTable).values({ name: 'John' }); + }); - const result = await db.select().from(usersMigratorTable); + await t.throwsAsync(async () => { + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + }, { + message: 'relation "users12" does not exist' + }); - t.deepEqual(result, [{ id: 1, name: 'John', email: 'email' }]); + await migrate(db, async (queries) => { + try { + await serverSimulator.migrations(queries); + } catch (e) { + console.error(e); + throw new Error('Proxy server cannot run migrations'); + } + }, { migrationsFolder: './drizzle2/pg-proxy/second' }); + + await t.notThrowsAsync(async () => { + await db.insert(usersTable).values({ name: 'John' }); + }); - await db.execute(sql`drop table all_columns`); + await t.notThrowsAsync(async () => { + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + }); + + await db.execute(sql`drop table users`); await db.execute(sql`drop table users12`); await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`); }); @@ -1087,11 +1109,10 @@ test.serial('insert via db.execute + returning', async (t) => { const { db } = t.context; const inserted = await db.execute<{ id: number; name: string }>( - sql`insert into ${usersTable} (${ - name( - usersTable.name.name, - ) - }) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`, + sql`insert into ${usersTable} (${name( + usersTable.name.name, + ) + }) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`, ); t.deepEqual(inserted, [{ id: 1, name: 'John' }]); }); @@ -2064,19 +2085,11 @@ test.serial('select from enum', async (t) => { await db.execute(sql`drop type if exists ${name(equipmentEnum.enumName)}`); await db.execute(sql`drop type if exists ${name(categoryEnum.enumName)}`); - await db.execute( - sql`create type ${ - name(muscleEnum.enumName) - } as enum ('abdominals', 'hamstrings', 'adductors', 'quadriceps', 'biceps', 'shoulders', 'chest', 'middle_back', 'calves', 'glutes', 'lower_back', 'lats', 'triceps', 'traps', 'forearms', 'neck', 'abductors')`, - ); + await db.execute(sql`create type ${name(muscleEnum.enumName)} as enum ('abdominals', 'hamstrings', 'adductors', 'quadriceps', 'biceps', 'shoulders', 'chest', 'middle_back', 'calves', 'glutes', 'lower_back', 'lats', 'triceps', 'traps', 'forearms', 'neck', 'abductors')`,); await db.execute(sql`create type ${name(forceEnum.enumName)} as enum ('isometric', 'isotonic', 'isokinetic')`); await db.execute(sql`create type ${name(levelEnum.enumName)} as enum ('beginner', 'intermediate', 'advanced')`); await db.execute(sql`create type ${name(mechanicEnum.enumName)} as enum ('compound', 'isolation')`); - await db.execute( - sql`create type ${ - name(equipmentEnum.enumName) - } as enum ('barbell', 'dumbbell', 'bodyweight', 'machine', 'cable', 'kettlebell')`, - ); + await db.execute(sql`create type ${name(equipmentEnum.enumName)} as enum ('barbell', 'dumbbell', 'bodyweight', 'machine', 'cable', 'kettlebell')`,); await db.execute(sql`create type ${name(categoryEnum.enumName)} as enum ('upper_body', 'lower_body', 'full_body')`); await db.execute(sql` create table ${exercises} ( @@ -2625,7 +2638,7 @@ test.serial('array operators', async (t) => { const withSubQuery = await db.select({ id: posts.id }).from(posts) .where(arrayContains( posts.tags, - db.select({ tags: posts.tags }).from(posts).where(eq(posts.id, 1)) + db.select({ tags: posts.tags }).from(posts).where(eq(posts.id, 1)) )); t.deepEqual(contains, [{ id: 3 }, { id: 5 }]); From 6e9788893f4bc9a8e621ff90806b1cd1507df65e Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 23 Oct 2023 10:14:39 +0300 Subject: [PATCH 02/50] fix: Fix snapshots --- .../drizzle2/mysql-proxy/second/meta/0001_snapshot.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/drizzle2/mysql-proxy/second/meta/0001_snapshot.json b/integration-tests/drizzle2/mysql-proxy/second/meta/0001_snapshot.json index 1a893f488..bda94226a 100644 --- a/integration-tests/drizzle2/mysql-proxy/second/meta/0001_snapshot.json +++ b/integration-tests/drizzle2/mysql-proxy/second/meta/0001_snapshot.json @@ -1,8 +1,8 @@ { "version": "5", "dialect": "mysql", - "id": "8e8c8378-0496-40f6-88e3-98aab8282b1f", - "prevId": "00000000-0000-0000-0000-000000000000", + "id": "47362df0-c353-4bd1-8107-fcc36f0e61bd", + "prevId": "8e8c8378-0496-40f6-88e3-98aab8282b1f", "tables": { "userstest": { "name": "userstest", From 0183e829f2302dc5ef53d775998590014dab78ad Mon Sep 17 00:00:00 2001 From: domlen2003 <37578299+domlen2003@users.noreply.github.com> Date: Tue, 24 Oct 2023 15:23:56 +0200 Subject: [PATCH 03/50] fixes d1 example --- examples/cloudflare-d1/drizzle.config.json | 4 ++++ examples/cloudflare-d1/package.json | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 examples/cloudflare-d1/drizzle.config.json diff --git a/examples/cloudflare-d1/drizzle.config.json b/examples/cloudflare-d1/drizzle.config.json new file mode 100644 index 000000000..d25ab99a5 --- /dev/null +++ b/examples/cloudflare-d1/drizzle.config.json @@ -0,0 +1,4 @@ +{ + "out": "drizzle", + "schema": "src/schema.ts" +} \ No newline at end of file diff --git a/examples/cloudflare-d1/package.json b/examples/cloudflare-d1/package.json index 2cef3dc1d..e26a9a934 100644 --- a/examples/cloudflare-d1/package.json +++ b/examples/cloudflare-d1/package.json @@ -4,8 +4,8 @@ "description": "", "scripts": { "test:types": "tsc --noEmit", - "generate": "drizzle-kit generate:sqlite --schema=src/schema.ts", - "up": "drizzle-kit up:sqlite --schema=src/schema.ts" + "generate": "drizzle-kit generate:sqlite", + "up": "drizzle-kit up:sqlite" }, "keywords": [], "author": "", From fbf2627843dec0025eefd7ffba6effc5cd5bbac5 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Fri, 3 Nov 2023 20:50:31 -0600 Subject: [PATCH 04/50] Add `sql` to `distinct on` column separator --- drizzle-orm/src/pg-core/dialect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drizzle-orm/src/pg-core/dialect.ts b/drizzle-orm/src/pg-core/dialect.ts index 64d2eface..ec08d1b23 100644 --- a/drizzle-orm/src/pg-core/dialect.ts +++ b/drizzle-orm/src/pg-core/dialect.ts @@ -254,7 +254,7 @@ export class PgDialect { let distinctSql: SQL | undefined; if (distinct) { - distinctSql = distinct === true ? sql` distinct` : sql` distinct on (${sql.join(distinct.on, ', ')})`; + distinctSql = distinct === true ? sql` distinct` : sql` distinct on (${sql.join(distinct.on, sql`, `)})`; } const selection = this.buildSelection(fieldsList, { isSingleTable }); From 36910d7e635dec8d174201446caeea82ca86facd Mon Sep 17 00:00:00 2001 From: Mario564 Date: Sat, 4 Nov 2023 14:15:21 -0600 Subject: [PATCH 05/50] Update `select distinct` PG test --- integration-tests/tests/pg.test.ts | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/integration-tests/tests/pg.test.ts b/integration-tests/tests/pg.test.ts index 38fd1a8a3..f30f4c389 100644 --- a/integration-tests/tests/pg.test.ts +++ b/integration-tests/tests/pg.test.ts @@ -480,16 +480,18 @@ test.serial('select distinct', async (t) => { const usersDistinctTable = pgTable('users_distinct', { id: integer('id').notNull(), name: text('name').notNull(), + age: integer('age').notNull() }); await db.execute(sql`drop table if exists ${usersDistinctTable}`); - await db.execute(sql`create table ${usersDistinctTable} (id integer, name text)`); + await db.execute(sql`create table ${usersDistinctTable} (id integer, name text, age integer)`); await db.insert(usersDistinctTable).values([ - { id: 1, name: 'John' }, - { id: 1, name: 'John' }, - { id: 2, name: 'John' }, - { id: 1, name: 'Jane' }, + { id: 1, name: 'John', age: 24 }, + { id: 1, name: 'John', age: 24 }, + { id: 2, name: 'John', age: 25 }, + { id: 1, name: 'Jane', age: 24 }, + { id: 1, name: 'Jane', age: 26 } ]); const users1 = await db.selectDistinct().from(usersDistinctTable).orderBy( usersDistinctTable.id, @@ -501,10 +503,18 @@ test.serial('select distinct', async (t) => { const users3 = await db.selectDistinctOn([usersDistinctTable.name], { name: usersDistinctTable.name }).from( usersDistinctTable, ).orderBy(usersDistinctTable.name); + const users4 = await db.selectDistinctOn([usersDistinctTable.id, usersDistinctTable.age]).from( + usersDistinctTable + ).orderBy(usersDistinctTable.id, usersDistinctTable.age) await db.execute(sql`drop table ${usersDistinctTable}`); - t.deepEqual(users1, [{ id: 1, name: 'Jane' }, { id: 1, name: 'John' }, { id: 2, name: 'John' }]); + t.deepEqual(users1, [ + { id: 1, name: 'Jane', age: 24 }, + { id: 1, name: 'Jane', age: 26 }, + { id: 1, name: 'John', age: 24 }, + { id: 2, name: 'John', age: 25 } + ]); t.deepEqual(users2.length, 2); t.deepEqual(users2[0]?.id, 1); @@ -513,6 +523,12 @@ test.serial('select distinct', async (t) => { t.deepEqual(users3.length, 2); t.deepEqual(users3[0]?.name, 'Jane'); t.deepEqual(users3[1]?.name, 'John'); + + t.deepEqual(users4, [ + { id: 1, name: 'John', age: 24 }, + { id: 1, name: 'Jane', age: 26 }, + { id: 2, name: 'John', age: 25 } + ]); }); test.serial('insert returning sql', async (t) => { From 73981dc0b7864dece86c948e4eec9b095ec62a46 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Thu, 9 Nov 2023 10:50:18 -0600 Subject: [PATCH 06/50] Implement PG aggregate functions Implement `count`, `avg`, `sum`, `max` and `min` to PG dialect --- drizzle-orm/src/built-in-function.ts | 62 +++++++ drizzle-orm/src/distinct.ts | 51 ++++++ drizzle-orm/src/index.ts | 2 + drizzle-orm/src/operations.ts | 17 +- drizzle-orm/src/pg-core/dialect.ts | 9 +- .../src/pg-core/functions/aggregate.ts | 172 ++++++++++++++++++ drizzle-orm/src/pg-core/functions/common.ts | 11 ++ drizzle-orm/src/pg-core/functions/index.ts | 2 + drizzle-orm/src/pg-core/index.ts | 1 + .../src/pg-core/query-builders/delete.ts | 4 +- .../src/pg-core/query-builders/insert.ts | 4 +- .../pg-core/query-builders/select.types.ts | 7 +- .../src/pg-core/query-builders/update.ts | 4 +- .../src/query-builders/select.types.ts | 9 +- drizzle-orm/src/sql/sql.ts | 16 +- drizzle-orm/src/utils.ts | 15 +- 16 files changed, 358 insertions(+), 28 deletions(-) create mode 100644 drizzle-orm/src/built-in-function.ts create mode 100644 drizzle-orm/src/distinct.ts create mode 100644 drizzle-orm/src/pg-core/functions/aggregate.ts create mode 100644 drizzle-orm/src/pg-core/functions/common.ts create mode 100644 drizzle-orm/src/pg-core/functions/index.ts diff --git a/drizzle-orm/src/built-in-function.ts b/drizzle-orm/src/built-in-function.ts new file mode 100644 index 000000000..e8b9f870e --- /dev/null +++ b/drizzle-orm/src/built-in-function.ts @@ -0,0 +1,62 @@ +import { entityKind } from './entity.ts'; +import { Dialect } from './column-builder.ts'; +import { type SQLWrapper, type SQL, sql, DriverValueDecoder, GetDecoderResult } from './sql/sql.ts'; + +/** @internal */ +export const BuiltInFunctionSQL = Symbol.for('drizzle:BuiltInFunctionSQL'); + +export interface BuiltInFunction extends SQLWrapper { + // SQLWrapper runtime implementation is defined in 'sql/sql.ts' +} +export abstract class BuiltInFunction implements SQLWrapper { + static readonly [entityKind]: string = 'BuiltInFunction'; + + declare readonly _: { + readonly type: T; + readonly dialect: Dialect; + }; + + /** @internal */ + static readonly Symbol = { + SQL: BuiltInFunctionSQL as typeof BuiltInFunctionSQL, + }; + + /** @internal */ + get [BuiltInFunctionSQL](): SQL { + return this.sql; + }; + + protected sql: SQL; + + constructor(sql: SQL) { + this.sql = sql; + } + + as(alias: string): SQL.Aliased; + /** + * @deprecated + * Use ``sql`query`.as(alias)`` instead. + */ + as(): SQL; + /** + * @deprecated + * Use ``sql`query`.as(alias)`` instead. + */ + as(alias: string): SQL.Aliased; + as(alias?: string): SQL | SQL.Aliased { + // TODO: remove with deprecated overloads + if (alias === undefined) { + return this.sql; + } + + return this.sql.as(alias); + } + + mapWith< + TDecoder extends + | DriverValueDecoder + | DriverValueDecoder['mapFromDriverValue'], + >(decoder: TDecoder): SQL> { + return this.sql.mapWith(decoder); + } +} diff --git a/drizzle-orm/src/distinct.ts b/drizzle-orm/src/distinct.ts new file mode 100644 index 000000000..8d5d84a4b --- /dev/null +++ b/drizzle-orm/src/distinct.ts @@ -0,0 +1,51 @@ +import { entityKind, is } from './entity.ts'; +import { type SQLWrapper } from './index.ts'; + +/** @internal */ +export const DistinctValue = Symbol.for('drizzle:DistinctValue'); + +export class Distinct { + static readonly [entityKind]: string = 'Distinct'; + + declare readonly _: { + readonly type: T; + }; + + /** @internal */ + static readonly Symbol = { + Value: DistinctValue as typeof DistinctValue, + }; + + /** @internal */ + [DistinctValue]: T; + + constructor(value: T) { + this[DistinctValue] = value; + } +} + +export type MaybeDistinct = T | Distinct; + +export type WithoutDistinct = T extends Distinct ? T['_']['type'] : T; + +export function distinct(value: T) { + return new Distinct(value); +} + +/** @internal */ +export function getValueWithDistinct(value: T): { + value: WithoutDistinct; + distinct: boolean; +} { + if (is(value, Distinct)) { + return { + value: value[DistinctValue], + distinct: true + } as any; + } + + return { + value, + distinct: false + } as any; +} \ No newline at end of file diff --git a/drizzle-orm/src/index.ts b/drizzle-orm/src/index.ts index 66b45c585..f930cbd09 100644 --- a/drizzle-orm/src/index.ts +++ b/drizzle-orm/src/index.ts @@ -1,6 +1,8 @@ export * from './alias.ts'; +export * from './built-in-function.ts'; export * from './column-builder.ts'; export * from './column.ts'; +export * from './distinct.ts'; export * from './entity.ts'; export * from './errors.ts'; export * from './expressions.ts'; diff --git a/drizzle-orm/src/operations.ts b/drizzle-orm/src/operations.ts index 19927cd94..889ef737a 100644 --- a/drizzle-orm/src/operations.ts +++ b/drizzle-orm/src/operations.ts @@ -1,3 +1,4 @@ +import type { BuiltInFunction } from './built-in-function.ts'; import type { AnyColumn, Column } from './column.ts'; import type { SQL } from './sql/index.ts'; import type { Table } from './table.ts'; @@ -13,22 +14,22 @@ export type OptionalKeyOnly< T extends Column, > = TKey extends RequiredKeyOnly ? never : TKey; -export type SelectedFieldsFlat = Record< +export type SelectedFieldsFlat = Record< string, - TColumn | SQL | SQL.Aliased + TColumn | TBuiltInFunction | SQL | SQL.Aliased >; -export type SelectedFieldsFlatFull = Record< +export type SelectedFieldsFlatFull = Record< string, - TColumn | SQL | SQL.Aliased + TColumn | TBuiltInFunction | SQL | SQL.Aliased >; -export type SelectedFields = Record< +export type SelectedFields = Record< string, - SelectedFieldsFlat[string] | TTable | SelectedFieldsFlat + SelectedFieldsFlat[string] | TTable | SelectedFieldsFlat >; -export type SelectedFieldsOrdered = { +export type SelectedFieldsOrdered = { path: string[]; - field: TColumn | SQL | SQL.Aliased; + field: TColumn | TBuiltInFunction | SQL | SQL.Aliased; }[]; diff --git a/drizzle-orm/src/pg-core/dialect.ts b/drizzle-orm/src/pg-core/dialect.ts index 64d2eface..a68642172 100644 --- a/drizzle-orm/src/pg-core/dialect.ts +++ b/drizzle-orm/src/pg-core/dialect.ts @@ -43,6 +43,8 @@ import { ViewBaseConfig } from '~/view-common.ts'; import { View } from '~/view.ts'; import type { PgSession } from './session.ts'; import { type PgMaterializedView, PgViewBase } from './view.ts'; +import { BuiltInFunction, BuiltInFunctionSQL } from '~/built-in-function.ts'; +import type { PgBuiltInFunction } from './functions/common.ts'; export class PgDialect { static readonly [entityKind]: string = 'PgDialect'; @@ -154,8 +156,9 @@ export class PgDialect { if (is(field, SQL.Aliased) && field.isSelectionField) { chunk.push(sql.identifier(field.fieldAlias)); - } else if (is(field, SQL.Aliased) || is(field, SQL)) { - const query = is(field, SQL.Aliased) ? field.sql : field; + } else if (is(field, SQL.Aliased) || is(field, SQL) || is(field, BuiltInFunction)) { + const field_ = is(field, BuiltInFunction) ? field[BuiltInFunctionSQL] : field + const query = is(field_, SQL.Aliased) ? field_.sql : field_; if (isSingleTable) { chunk.push( @@ -211,7 +214,7 @@ export class PgDialect { setOperators, }: PgSelectConfig, ): SQL { - const fieldsList = fieldsFlat ?? orderSelectedFields(fields); + const fieldsList = fieldsFlat ?? orderSelectedFields(fields); for (const f of fieldsList) { if ( is(f.field, Column) diff --git a/drizzle-orm/src/pg-core/functions/aggregate.ts b/drizzle-orm/src/pg-core/functions/aggregate.ts new file mode 100644 index 000000000..36e9fd26b --- /dev/null +++ b/drizzle-orm/src/pg-core/functions/aggregate.ts @@ -0,0 +1,172 @@ +import { is, entityKind } from '~/entity.ts'; +import { PgColumn } from '../columns/index.ts'; +import { type SQL, sql, type SQLWrapper, isSQLWrapper, SQLChunk } from '~/sql/index.ts'; +import { PgBuiltInFunction } from './common.ts'; +import { type MaybeDistinct, getValueWithDistinct } from '~/distinct.ts'; + +export class PgAggregateFunction extends PgBuiltInFunction { + static readonly [entityKind]: string = 'PgAggregateFunction'; + + filterWhere(where?: SQL | undefined): this { + if (where) { + this.sql.append(sql` filter (where ${where})`); + } + return this; + } +} + +/** + * Returns the number of values in `expression`. + * + * ## Examples + * + * ```ts + * // Number employees with null values + * db.select({ value: count() }).from(employees) + * // Number of employees where `name` is not null + * db.select({ value: count(employees.name) }).from(employees) + * // Number of employees where `name` is distinct (no duplicates) + * db.select({ value: count(distinct(employees.name)) }).from(employees) + * // Number of employees where their salaries are greater than $2,000 + * db.select({ value: count().filterWhere(gt(employees.salary, 2000)) }).from(employees) + * ``` + */ +export function count(expression?: MaybeDistinct | '*', config?: { + mode: T; +}): PgAggregateFunction { + const { value, distinct } = getValueWithDistinct(expression); + const chunks: SQLChunk[] = []; + + if (distinct) { + chunks.push(sql`distinct `); + } + chunks.push(isSQLWrapper(value) ? value : sql`*`); + + const sql_ = sql + .join([sql`count(`, ...chunks, sql`)` ]) + .mapWith(config?.mode === 'number' ? Number : BigInt); + + return new PgAggregateFunction(sql_) as any; +} + +/** + * Returns the average (arithmetic mean) of all non-null values in `expression`. + * + * ## Examples + * + * ```ts + * // Average salary of an employee + * db.select({ value: avg(employees.salary) }).from(employees) + * // Average salary of an employee where `salary` is distinct (no duplicates) + * db.select({ value: avg(distinct(employees.salary)) }).from(employees) + * // Average salary of an employee where their salaries are greater than $2,000 + * db.select({ value: avg(employees.salary).filterWhere(gt(employees.salary, 2000)) }).from(employees) + * ``` + */ +export function avg(expression: MaybeDistinct, config?: { + mode: T; +}): PgAggregateFunction<(T extends 'bigint' ? bigint : T extends 'number' ? number : string) | null> { + const { value, distinct } = getValueWithDistinct(expression); + const chunks: SQLChunk[] = []; + + if (distinct) { + chunks.push(sql`distinct `); + } + chunks.push(value); + + let sql_ = sql.join([sql`avg(`, ...chunks, sql`)`]); + + if (config?.mode === 'bigint') { + sql_ = sql_.mapWith(BigInt); + } else if (config?.mode === 'number') { + sql_ = sql_.mapWith(Number); + } + + return new PgAggregateFunction(sql_) as any; +} + +/** + * Returns the sum of all non-null values in `expression`. + * + * ## Examples + * + * ```ts + * // Sum of every employee's salary + * db.select({ value: sum(employees.salary) }).from(employees) + * // Sum of every employee's salary where `salary` is distinct (no duplicates) + * db.select({ value: sum(distinct(employees.salary)) }).from(employees) + * // Sum of every employee's salary where their salaries are greater than $2,000 + * db.select({ value: sum(employees.salary).filterWhere(gt(employees.salary, 2000)) }).from(employees) + * ``` + */ +export function sum(expression: MaybeDistinct, config?: { + mode: T; +}): PgAggregateFunction<(T extends 'bigint' ? bigint : T extends 'number' ? number : string) | null> { + const { value, distinct } = getValueWithDistinct(expression); + const chunks: SQLChunk[] = []; + + if (distinct) { + chunks.push(sql`distinct `); + } + chunks.push(value); + + let sql_ = sql.join([sql`sum(`, ...chunks, sql`)`]); + + if (config?.mode === 'bigint') { + sql_ = sql_.mapWith(BigInt); + } else if (config?.mode === 'number') { + sql_ = sql_.mapWith(Number); + } + + return new PgAggregateFunction(sql_) as any; +} + +/** + * Returns the maximum value in `expression`. + * + * ## Examples + * + * ```ts + * // The employee with the highest salary + * db.select({ value: max(employees.salary) }).from(employees) + * ``` + */ +export function max(expression: T): T extends PgColumn + ? PgAggregateFunction + : PgAggregateFunction +{ + let sql_ = sql.join([sql`max(`, expression, sql`)`]); + + if (is(expression, PgColumn)) { + sql_ = sql_.mapWith(expression); + } else { + sql_ = sql_.mapWith(String); + } + + return new PgAggregateFunction(sql_) as any; +} + +/** + * Returns the minimum value in `expression`. + * + * ## Examples + * + * ```ts + * // The employee with the lowest salary + * db.select({ value: min(employees.salary) }).from(employees) + * ``` + */ +export function min(expression: T): T extends PgColumn + ? PgAggregateFunction + : PgAggregateFunction +{ + let sql_ = sql.join([sql`min(`, expression, sql`)`]); + + if (is(expression, PgColumn)) { + sql_ = sql_.mapWith(expression); + } else { + sql_ = sql_.mapWith(String); + } + + return new PgAggregateFunction(sql_) as any; +} diff --git a/drizzle-orm/src/pg-core/functions/common.ts b/drizzle-orm/src/pg-core/functions/common.ts new file mode 100644 index 000000000..3ed63ed42 --- /dev/null +++ b/drizzle-orm/src/pg-core/functions/common.ts @@ -0,0 +1,11 @@ +import { BuiltInFunction } from '~/built-in-function.ts'; +import { entityKind } from '~/entity.ts'; + +export class PgBuiltInFunction extends BuiltInFunction { + static readonly [entityKind]: string = 'PgBuiltInFunction'; + + declare readonly _: { + readonly type: T; + readonly dialect: 'pg'; + }; +} diff --git a/drizzle-orm/src/pg-core/functions/index.ts b/drizzle-orm/src/pg-core/functions/index.ts new file mode 100644 index 000000000..47b15a648 --- /dev/null +++ b/drizzle-orm/src/pg-core/functions/index.ts @@ -0,0 +1,2 @@ +export * from './aggregate.ts'; +export * from './common.ts'; diff --git a/drizzle-orm/src/pg-core/index.ts b/drizzle-orm/src/pg-core/index.ts index 1a80ff7ad..38e74216f 100644 --- a/drizzle-orm/src/pg-core/index.ts +++ b/drizzle-orm/src/pg-core/index.ts @@ -4,6 +4,7 @@ export * from './columns/index.ts'; export * from './db.ts'; export * from './dialect.ts'; export * from './foreign-keys.ts'; +export * from './functions/index.ts'; export * from './indexes.ts'; export * from './primary-keys.ts'; export * from './query-builders/index.ts'; diff --git a/drizzle-orm/src/pg-core/query-builders/delete.ts b/drizzle-orm/src/pg-core/query-builders/delete.ts index 28dfaba09..fda610fd4 100644 --- a/drizzle-orm/src/pg-core/query-builders/delete.ts +++ b/drizzle-orm/src/pg-core/query-builders/delete.ts @@ -15,6 +15,8 @@ import { Table } from '~/table.ts'; import { tracer } from '~/tracing.ts'; import { orderSelectedFields } from '~/utils.ts'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types.ts'; +import type { PgColumn } from '../columns/common.ts'; +import type { PgBuiltInFunction } from '../functions/common.ts'; export type PgDeleteWithout< T extends AnyPgDeleteBase, @@ -141,7 +143,7 @@ export class PgDeleteBase< returning( fields: SelectedFieldsFlat = this.config.table[Table.Symbol.Columns], ): PgDeleteReturning { - this.config.returning = orderSelectedFields(fields); + this.config.returning = orderSelectedFields(fields); return this as any; } diff --git a/drizzle-orm/src/pg-core/query-builders/insert.ts b/drizzle-orm/src/pg-core/query-builders/insert.ts index 93bf3aa43..fd9178159 100644 --- a/drizzle-orm/src/pg-core/query-builders/insert.ts +++ b/drizzle-orm/src/pg-core/query-builders/insert.ts @@ -18,6 +18,8 @@ import { tracer } from '~/tracing.ts'; import { mapUpdateSet, orderSelectedFields } from '~/utils.ts'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types.ts'; import type { PgUpdateSetSource } from './update.ts'; +import type { PgColumn } from '../columns/common.ts'; +import type { PgBuiltInFunction } from '../functions/common.ts' export interface PgInsertConfig { table: TTable; @@ -170,7 +172,7 @@ export class PgInsertBase< returning( fields: SelectedFieldsFlat = this.config.table[Table.Symbol.Columns], ): PgInsertWithout { - this.config.returning = orderSelectedFields(fields); + this.config.returning = orderSelectedFields(fields); return this as any; } diff --git a/drizzle-orm/src/pg-core/query-builders/select.types.ts b/drizzle-orm/src/pg-core/query-builders/select.types.ts index d07784b06..e20a653b6 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.types.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.types.ts @@ -26,6 +26,7 @@ import type { Assume, ValidateShape, ValueOrArray } from '~/utils.ts'; import type { ColumnsSelection, View } from '~/view.ts'; import type { PreparedQuery, PreparedQueryConfig } from '../session.ts'; import type { PgSelectBase, PgSelectQueryBuilderBase } from './select.ts'; +import type { PgBuiltInFunction } from '../functions/common.ts'; export interface PgSelectJoinConfig { on: SQL | undefined; @@ -120,11 +121,11 @@ export type PgJoinFn< on: ((aliases: T['_']['selection']) => SQL | undefined) | SQL | undefined, ) => PgJoin; -export type SelectedFieldsFlat = SelectedFieldsFlatBase; +export type SelectedFieldsFlat = SelectedFieldsFlatBase; -export type SelectedFields = SelectedFieldsBase; +export type SelectedFields = SelectedFieldsBase; -export type SelectedFieldsOrdered = SelectedFieldsOrderedBase; +export type SelectedFieldsOrdered = SelectedFieldsOrderedBase; export type LockStrength = 'update' | 'no key update' | 'share' | 'key share'; diff --git a/drizzle-orm/src/pg-core/query-builders/update.ts b/drizzle-orm/src/pg-core/query-builders/update.ts index 6b7b24d92..dccc6955c 100644 --- a/drizzle-orm/src/pg-core/query-builders/update.ts +++ b/drizzle-orm/src/pg-core/query-builders/update.ts @@ -15,6 +15,8 @@ import type { Query, SQL, SQLWrapper } from '~/sql/index.ts'; import { Table } from '~/table.ts'; import { mapUpdateSet, orderSelectedFields, type UpdateSet } from '~/utils.ts'; import type { SelectedFields, SelectedFieldsOrdered } from './select.types.ts'; +import type { PgColumn } from '../columns/common.ts'; +import { PgBuiltInFunction } from '../functions/common.ts' export interface PgUpdateConfig { where?: SQL | undefined; @@ -171,7 +173,7 @@ export class PgUpdateBase< returning( fields: SelectedFields = this.config.table[Table.Symbol.Columns], ): PgUpdateWithout { - this.config.returning = orderSelectedFields(fields); + this.config.returning = orderSelectedFields(fields); return this as any; } diff --git a/drizzle-orm/src/query-builders/select.types.ts b/drizzle-orm/src/query-builders/select.types.ts index 73fe41b40..632bf4d6e 100644 --- a/drizzle-orm/src/query-builders/select.types.ts +++ b/drizzle-orm/src/query-builders/select.types.ts @@ -6,6 +6,7 @@ import type { Subquery } from '~/subquery.ts'; import type { Table } from '~/table.ts'; import type { Assume, DrizzleTypeError, Equal, IsAny, Simplify } from '~/utils.ts'; import type { ColumnsSelection, View } from '~/view.ts'; +import type { BuiltInFunction } from '../built-in-function.ts'; export type JoinType = 'inner' | 'left' | 'right' | 'full'; @@ -58,9 +59,9 @@ type SelectPartialResult, TNullability[TField['_']['tableName']]> : never - : TField extends SQL | SQL.Aliased ? SelectResultField + : TField extends SQL | SQL.Aliased | BuiltInFunction ? SelectResultField : TField extends Record - ? TField[keyof TField] extends AnyColumn<{ tableName: infer TTableName extends string }> | SQL | SQL.Aliased + ? TField[keyof TField] extends AnyColumn<{ tableName: infer TTableName extends string }> | SQL | SQL.Aliased | BuiltInFunction ? Not> extends true ? ApplyNullability, TNullability[TTableName]> : SelectPartialResult @@ -101,7 +102,7 @@ export type AppendToResult< TTableName extends string | undefined, TResult, TJoinedName extends string | undefined, - TSelectedFields extends SelectedFields, + TSelectedFields extends SelectedFields, TOldSelectMode extends SelectMode, > = TOldSelectMode extends 'partial' ? TResult : TOldSelectMode extends 'single' ? @@ -156,7 +157,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 SQL | SQL.Aliased ? T['_']['type'] + : T extends SQL | SQL.Aliased | BuiltInFunction ? T['_']['type'] : T extends Record ? SelectResultFields : never; diff --git a/drizzle-orm/src/sql/sql.ts b/drizzle-orm/src/sql/sql.ts index b4ec5fca2..24f7e6712 100644 --- a/drizzle-orm/src/sql/sql.ts +++ b/drizzle-orm/src/sql/sql.ts @@ -7,6 +7,7 @@ import { View } from '~/view.ts'; import type { AnyColumn } from '../column.ts'; import { Column } from '../column.ts'; import { Table } from '../table.ts'; +import { BuiltInFunction } from '~/built-in-function.ts'; /** * This class is used to indicate a primitive param value that is used in `sql` tag. @@ -57,6 +58,7 @@ export interface QueryWithTypings extends Query { * - `SQL.Aliased` * - `Placeholder` * - `Param` + * - `BuiltInFunction` */ export interface SQLWrapper { getSQL(): SQL; @@ -172,6 +174,13 @@ export class SQL implements SQLWrapper { }); } + if (is(chunk, BuiltInFunction)) { + return this.buildQueryFromSourceParams(chunk[BuiltInFunction.Symbol.SQL].queryChunks, { + ...config, + inlineParams: inlineParams || chunk[BuiltInFunction.Symbol.SQL].shouldInlineParams, + }); + } + if (is(chunk, Table)) { const schemaName = chunk[Table.Symbol.Schema]; const tableName = chunk[Table.Symbol.Name]; @@ -428,7 +437,8 @@ export type SQLChunk = | Name | undefined | FakePrimitiveParam - | Placeholder; + | Placeholder + | BuiltInFunction; export function sql(strings: TemplateStringsArray, ...params: any[]): SQL; /* @@ -587,3 +597,7 @@ Column.prototype.getSQL = function() { Table.prototype.getSQL = function() { return new SQL([this]); }; + +BuiltInFunction.prototype.getSQL = function() { + return new SQL([this]); +}; diff --git a/drizzle-orm/src/utils.ts b/drizzle-orm/src/utils.ts index f54afa5ab..db11fd941 100644 --- a/drizzle-orm/src/utils.ts +++ b/drizzle-orm/src/utils.ts @@ -1,3 +1,4 @@ +import { BuiltInFunction } from './built-in-function.ts'; import type { AnyColumn } from './column.ts'; import { Column } from './column.ts'; import { is } from './entity.ts'; @@ -13,7 +14,7 @@ import { View } from './view.ts'; /** @internal */ export function mapResultRow( - columns: SelectedFieldsOrdered, + columns: SelectedFieldsOrdered, row: unknown[], joinsNotNullableMap: Record | undefined, ): TResult { @@ -27,6 +28,8 @@ export function mapResultRow( decoder = field; } else if (is(field, SQL)) { decoder = field.decoder; + } else if (is(field, BuiltInFunction)) { + decoder = field[BuiltInFunction.Symbol.SQL].decoder; } else { decoder = field.sql.decoder; } @@ -71,17 +74,17 @@ export function mapResultRow( } /** @internal */ -export function orderSelectedFields( +export function orderSelectedFields( fields: Record, pathPrefix?: string[], -): SelectedFieldsOrdered { - return Object.entries(fields).reduce>((result, [name, field]) => { +): SelectedFieldsOrdered { + return Object.entries(fields).reduce>((result, [name, field]) => { if (typeof name !== 'string') { return result; } const newPath = pathPrefix ? [...pathPrefix, name] : [name]; - if (is(field, Column) || is(field, SQL) || is(field, SQL.Aliased)) { + if (is(field, Column) || is(field, SQL) || is(field, SQL.Aliased) || is(field, BuiltInFunction)) { result.push({ path: newPath, field }); } else if (is(field, Table)) { result.push(...orderSelectedFields(field[Table.Symbol.Columns], newPath)); @@ -89,7 +92,7 @@ export function orderSelectedFields( result.push(...orderSelectedFields(field as Record, newPath)); } return result; - }, []) as SelectedFieldsOrdered; + }, []) as SelectedFieldsOrdered; } export function haveSameKeys(left: Record, right: Record) { From 11ab15838a034f5e995f8e2c5723a7a530c6d303 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Thu, 9 Nov 2023 15:13:19 -0600 Subject: [PATCH 07/50] Implement MySQL and SQLite aggregate functions Implement `count`, `avg`, `sum`, `max` and `min` to MySQL dialect and all of the previously mentioned and `total` to SQLite dialect --- drizzle-orm/src/mysql-core/dialect.ts | 9 +- .../src/mysql-core/functions/aggregate.ts | 159 ++++++++++++++++ .../src/mysql-core/functions/common.ts | 11 ++ drizzle-orm/src/mysql-core/functions/index.ts | 2 + drizzle-orm/src/mysql-core/index.ts | 1 + .../src/mysql-core/query-builders/select.ts | 3 +- .../mysql-core/query-builders/select.types.ts | 7 +- drizzle-orm/src/pg-core/dialect.ts | 4 +- .../src/pg-core/query-builders/select.ts | 3 +- drizzle-orm/src/sqlite-core/dialect.ts | 9 +- .../src/sqlite-core/functions/aggregate.ts | 180 ++++++++++++++++++ .../src/sqlite-core/functions/common.ts | 11 ++ .../src/sqlite-core/functions/index.ts | 2 + drizzle-orm/src/sqlite-core/index.ts | 1 + .../src/sqlite-core/query-builders/delete.ts | 4 +- .../src/sqlite-core/query-builders/insert.ts | 4 +- .../src/sqlite-core/query-builders/select.ts | 3 +- .../query-builders/select.types.ts | 8 +- .../src/sqlite-core/query-builders/update.ts | 4 +- drizzle-orm/src/view.ts | 5 +- 20 files changed, 407 insertions(+), 23 deletions(-) create mode 100644 drizzle-orm/src/mysql-core/functions/aggregate.ts create mode 100644 drizzle-orm/src/mysql-core/functions/common.ts create mode 100644 drizzle-orm/src/mysql-core/functions/index.ts create mode 100644 drizzle-orm/src/sqlite-core/functions/aggregate.ts create mode 100644 drizzle-orm/src/sqlite-core/functions/common.ts create mode 100644 drizzle-orm/src/sqlite-core/functions/index.ts diff --git a/drizzle-orm/src/mysql-core/dialect.ts b/drizzle-orm/src/mysql-core/dialect.ts index 26521bdaf..3790f97fe 100644 --- a/drizzle-orm/src/mysql-core/dialect.ts +++ b/drizzle-orm/src/mysql-core/dialect.ts @@ -28,6 +28,8 @@ import type { MySqlUpdateConfig } from './query-builders/update.ts'; import type { MySqlSession } from './session.ts'; import { MySqlTable } from './table.ts'; import { MySqlViewBase } from './view.ts'; +import { BuiltInFunction } from '~/built-in-function.ts'; +import type { MySqlBuiltInFunction } from './functions/common.ts'; // TODO find out how to use all/values. Seems like I need those functions // Build project @@ -150,8 +152,9 @@ export class MySqlDialect { if (is(field, SQL.Aliased) && field.isSelectionField) { chunk.push(sql.identifier(field.fieldAlias)); - } else if (is(field, SQL.Aliased) || is(field, SQL)) { - const query = is(field, SQL.Aliased) ? field.sql : field; + } else if (is(field, SQL.Aliased) || is(field, SQL) || is(field, BuiltInFunction)) { + const field_ = is(field, BuiltInFunction) ? field[BuiltInFunction.Symbol.SQL] : field + const query = is(field_, SQL.Aliased) ? field_.sql : field_; if (isSingleTable) { chunk.push( @@ -207,7 +210,7 @@ export class MySqlDialect { setOperators, }: MySqlSelectConfig, ): SQL { - const fieldsList = fieldsFlat ?? orderSelectedFields(fields); + const fieldsList = fieldsFlat ?? orderSelectedFields(fields); for (const f of fieldsList) { if ( is(f.field, Column) diff --git a/drizzle-orm/src/mysql-core/functions/aggregate.ts b/drizzle-orm/src/mysql-core/functions/aggregate.ts new file mode 100644 index 000000000..ba569ee6f --- /dev/null +++ b/drizzle-orm/src/mysql-core/functions/aggregate.ts @@ -0,0 +1,159 @@ +import { is, entityKind } from '~/entity.ts'; +import { MySqlColumn } from '../columns/index.ts'; +import { type SQL, sql, type SQLWrapper, isSQLWrapper, SQLChunk } from '~/sql/index.ts'; +import { MySqlBuiltInFunction } from './common.ts'; +import { type MaybeDistinct, getValueWithDistinct } from '~/distinct.ts'; + +export class MySqlAggregateFunction extends MySqlBuiltInFunction { + static readonly [entityKind]: string = 'MySqlAggregateFunction'; +} + +/** + * Returns the number of values in `expression`. + * + * ## Examples + * + * ```ts + * // Number employees with null values + * db.select({ value: count() }).from(employees) + * // Number of employees where `name` is not null + * db.select({ value: count(employees.name) }).from(employees) + * // Number of employees where `name` is distinct (no duplicates) + * db.select({ value: count(distinct(employees.name)) }).from(employees) + * ``` + */ +export function count(expression?: MaybeDistinct | '*', config?: { + mode: T; +}): MySqlAggregateFunction { + const { value, distinct } = getValueWithDistinct(expression); + const chunks: SQLChunk[] = []; + + if (distinct) { + chunks.push(sql`distinct `); + } + chunks.push(isSQLWrapper(value) ? value : sql`*`); + + const sql_ = sql + .join([sql`count(`, ...chunks, sql`)` ]) + .mapWith(config?.mode === 'number' ? Number : BigInt); + + return new MySqlAggregateFunction(sql_) as any; +} + +/** + * Returns the average (arithmetic mean) of all non-null values in `expression`. + * + * ## Examples + * + * ```ts + * // Average salary of an employee + * db.select({ value: avg(employees.salary) }).from(employees) + * // Average salary of an employee where `salary` is distinct (no duplicates) + * db.select({ value: avg(distinct(employees.salary)) }).from(employees) + * ``` + */ +export function avg(expression: MaybeDistinct, config?: { + mode: T; +}): MySqlAggregateFunction<(T extends 'bigint' ? bigint : T extends 'number' ? number : string) | null> { + const { value, distinct } = getValueWithDistinct(expression); + const chunks: SQLChunk[] = []; + + if (distinct) { + chunks.push(sql`distinct `); + } + chunks.push(value); + + let sql_ = sql.join([sql`avg(`, ...chunks, sql`)`]); + + if (config?.mode === 'bigint') { + sql_ = sql_.mapWith(BigInt); + } else if (config?.mode === 'number') { + sql_ = sql_.mapWith(Number); + } + + return new MySqlAggregateFunction(sql_) as any; +} + +/** + * Returns the sum of all non-null values in `expression`. + * + * ## Examples + * + * ```ts + * // Sum of every employee's salary + * db.select({ value: sum(employees.salary) }).from(employees) + * // Sum of every employee's salary where `salary` is distinct (no duplicates) + * db.select({ value: sum(distinct(employees.salary)) }).from(employees) + * ``` + */ +export function sum(expression: MaybeDistinct, config?: { + mode: T; +}): MySqlAggregateFunction<(T extends 'bigint' ? bigint : T extends 'number' ? number : string) | null> { + const { value, distinct } = getValueWithDistinct(expression); + const chunks: SQLChunk[] = []; + + if (distinct) { + chunks.push(sql`distinct `); + } + chunks.push(value); + + let sql_ = sql.join([sql`sum(`, ...chunks, sql`)`]); + + if (config?.mode === 'bigint') { + sql_ = sql_.mapWith(BigInt); + } else if (config?.mode === 'number') { + sql_ = sql_.mapWith(Number); + } + + return new MySqlAggregateFunction(sql_) as any; +} + +/** + * Returns the maximum value in `expression`. + * + * ## Examples + * + * ```ts + * // The employee with the highest salary + * db.select({ value: max(employees.salary) }).from(employees) + * ``` + */ +export function max(expression: T): T extends MySqlColumn + ? MySqlAggregateFunction + : MySqlAggregateFunction +{ + let sql_ = sql.join([sql`max(`, expression, sql`)`]); + + if (is(expression, MySqlColumn)) { + sql_ = sql_.mapWith(expression); + } else { + sql_ = sql_.mapWith(String); + } + + return new MySqlAggregateFunction(sql_) as any; +} + +/** + * Returns the minimum value in `expression`. + * + * ## Examples + * + * ```ts + * // The employee with the lowest salary + * db.select({ value: min(employees.salary) }).from(employees) + * ``` + */ +export function min(expression: T): T extends MySqlColumn + ? MySqlAggregateFunction + : MySqlAggregateFunction +{ + let sql_ = sql.join([sql`min(`, expression, sql`)`]); + + if (is(expression, MySqlColumn)) { + sql_ = sql_.mapWith(expression); + } else { + sql_ = sql_.mapWith(String); + } + + return new MySqlAggregateFunction(sql_) as any; +} diff --git a/drizzle-orm/src/mysql-core/functions/common.ts b/drizzle-orm/src/mysql-core/functions/common.ts new file mode 100644 index 000000000..a3a43e1ab --- /dev/null +++ b/drizzle-orm/src/mysql-core/functions/common.ts @@ -0,0 +1,11 @@ +import { BuiltInFunction } from '~/built-in-function.ts'; +import { entityKind } from '~/entity.ts'; + +export class MySqlBuiltInFunction extends BuiltInFunction { + static readonly [entityKind]: string = 'MySqlBuiltInFunction'; + + declare readonly _: { + readonly type: T; + readonly dialect: 'mysql'; + }; +} diff --git a/drizzle-orm/src/mysql-core/functions/index.ts b/drizzle-orm/src/mysql-core/functions/index.ts new file mode 100644 index 000000000..47b15a648 --- /dev/null +++ b/drizzle-orm/src/mysql-core/functions/index.ts @@ -0,0 +1,2 @@ +export * from './aggregate.ts'; +export * from './common.ts'; diff --git a/drizzle-orm/src/mysql-core/index.ts b/drizzle-orm/src/mysql-core/index.ts index 204e0af3c..b7f13c739 100644 --- a/drizzle-orm/src/mysql-core/index.ts +++ b/drizzle-orm/src/mysql-core/index.ts @@ -4,6 +4,7 @@ export * from './columns/index.ts'; export * from './db.ts'; export * from './dialect.ts'; export * from './foreign-keys.ts'; +export * from './functions/index.ts'; export * from './indexes.ts'; export * from './primary-keys.ts'; export * from './query-builders/index.ts'; diff --git a/drizzle-orm/src/mysql-core/query-builders/select.ts b/drizzle-orm/src/mysql-core/query-builders/select.ts index aac36dc04..89751aa9a 100644 --- a/drizzle-orm/src/mysql-core/query-builders/select.ts +++ b/drizzle-orm/src/mysql-core/query-builders/select.ts @@ -43,6 +43,7 @@ import type { SelectedFields, SetOperatorRightSelect, } from './select.types.ts'; +import type { MySqlBuiltInFunction } from '../index.ts'; export class MySqlSelectBuilder< TSelection extends SelectedFields | undefined, @@ -523,7 +524,7 @@ export class MySqlSelectBase< if (!this.session) { throw new Error('Cannot execute a query on a query builder. Please use a database instance instead.'); } - const fieldsList = orderSelectedFields(this.config.fields); + const fieldsList = orderSelectedFields(this.config.fields); const query = this.session.prepareQuery< PreparedQueryConfig & { execute: SelectResult[] }, TPreparedQueryHKT diff --git a/drizzle-orm/src/mysql-core/query-builders/select.types.ts b/drizzle-orm/src/mysql-core/query-builders/select.types.ts index 022219522..8d40460b3 100644 --- a/drizzle-orm/src/mysql-core/query-builders/select.types.ts +++ b/drizzle-orm/src/mysql-core/query-builders/select.types.ts @@ -26,6 +26,7 @@ import type { Assume, ValidateShape } from '~/utils.ts'; import type { ColumnsSelection, View } from '~/view.ts'; import type { PreparedQueryConfig, PreparedQueryHKTBase, PreparedQueryKind } from '../session.ts'; import type { MySqlSelectBase, MySqlSelectQueryBuilderBase } from './select.ts'; +import type { MySqlBuiltInFunction } from '../functions/common.ts'; export interface MySqlSelectJoinConfig { on: SQL | undefined; @@ -118,11 +119,11 @@ export type MySqlJoinFn< on: ((aliases: T['_']['selection']) => SQL | undefined) | SQL | undefined, ) => MySqlJoin; -export type SelectedFieldsFlat = SelectedFieldsFlatBase; +export type SelectedFieldsFlat = SelectedFieldsFlatBase; -export type SelectedFields = SelectedFieldsBase; +export type SelectedFields = SelectedFieldsBase; -export type SelectedFieldsOrdered = SelectedFieldsOrderedBase; +export type SelectedFieldsOrdered = SelectedFieldsOrderedBase; export type LockStrength = 'update' | 'share'; diff --git a/drizzle-orm/src/pg-core/dialect.ts b/drizzle-orm/src/pg-core/dialect.ts index a68642172..bc3ae83fd 100644 --- a/drizzle-orm/src/pg-core/dialect.ts +++ b/drizzle-orm/src/pg-core/dialect.ts @@ -43,7 +43,7 @@ import { ViewBaseConfig } from '~/view-common.ts'; import { View } from '~/view.ts'; import type { PgSession } from './session.ts'; import { type PgMaterializedView, PgViewBase } from './view.ts'; -import { BuiltInFunction, BuiltInFunctionSQL } from '~/built-in-function.ts'; +import { BuiltInFunction } from '~/built-in-function.ts'; import type { PgBuiltInFunction } from './functions/common.ts'; export class PgDialect { @@ -157,7 +157,7 @@ export class PgDialect { if (is(field, SQL.Aliased) && field.isSelectionField) { chunk.push(sql.identifier(field.fieldAlias)); } else if (is(field, SQL.Aliased) || is(field, SQL) || is(field, BuiltInFunction)) { - const field_ = is(field, BuiltInFunction) ? field[BuiltInFunctionSQL] : field + const field_ = is(field, BuiltInFunction) ? field[BuiltInFunction.Symbol.SQL] : field const query = is(field_, SQL.Aliased) ? field_.sql : field_; if (isSingleTable) { diff --git a/drizzle-orm/src/pg-core/query-builders/select.ts b/drizzle-orm/src/pg-core/query-builders/select.ts index f9e4b41b7..3c31816e8 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.ts @@ -44,6 +44,7 @@ import type { SelectedFields, SetOperatorRightSelect, } from './select.types.ts'; +import type { PgBuiltInFunction } from '../functions/common.ts'; export class PgSelectBuilder< TSelection extends SelectedFields | undefined, @@ -635,7 +636,7 @@ export class PgSelectBase< throw new Error('Cannot execute a query on a query builder. Please use a database instance instead.'); } return tracer.startActiveSpan('drizzle.prepareQuery', () => { - const fieldsList = orderSelectedFields(config.fields); + const fieldsList = orderSelectedFields(config.fields); const query = session.prepareQuery< PreparedQueryConfig & { execute: TResult } >(dialect.sqlToQuery(this.getSQL()), fieldsList, name); diff --git a/drizzle-orm/src/sqlite-core/dialect.ts b/drizzle-orm/src/sqlite-core/dialect.ts index f4251309a..211a4ed93 100644 --- a/drizzle-orm/src/sqlite-core/dialect.ts +++ b/drizzle-orm/src/sqlite-core/dialect.ts @@ -31,6 +31,8 @@ import type { } from './query-builders/select.types.ts'; import type { SQLiteSession } from './session.ts'; import { SQLiteViewBase } from './view.ts'; +import type { SQLiteBuiltInFunction } from './functions/common.ts'; +import { BuiltInFunction } from '~/built-in-function.ts'; export abstract class SQLiteDialect { static readonly [entityKind]: string = 'SQLiteDialect'; @@ -109,8 +111,9 @@ export abstract class SQLiteDialect { if (is(field, SQL.Aliased) && field.isSelectionField) { chunk.push(sql.identifier(field.fieldAlias)); - } else if (is(field, SQL.Aliased) || is(field, SQL)) { - const query = is(field, SQL.Aliased) ? field.sql : field; + } else if (is(field, SQL.Aliased) || is(field, SQL) || is(field, BuiltInFunction)) { + const field_ = is(field, BuiltInFunction) ? field[BuiltInFunction.Symbol.SQL] : field + const query = is(field_, SQL.Aliased) ? field_.sql : field_; if (isSingleTable) { chunk.push( @@ -167,7 +170,7 @@ export abstract class SQLiteDialect { setOperators, }: SQLiteSelectConfig, ): SQL { - const fieldsList = fieldsFlat ?? orderSelectedFields(fields); + const fieldsList = fieldsFlat ?? orderSelectedFields(fields); for (const f of fieldsList) { if ( is(f.field, Column) diff --git a/drizzle-orm/src/sqlite-core/functions/aggregate.ts b/drizzle-orm/src/sqlite-core/functions/aggregate.ts new file mode 100644 index 000000000..c7bfa42cb --- /dev/null +++ b/drizzle-orm/src/sqlite-core/functions/aggregate.ts @@ -0,0 +1,180 @@ +import { is, entityKind } from '~/entity.ts'; +import { SQLiteColumn } from '../columns/index.ts'; +import { type SQL, sql, type SQLWrapper, isSQLWrapper, SQLChunk } from '~/sql/index.ts'; +import { SQLiteBuiltInFunction } from './common.ts'; +import { type MaybeDistinct, getValueWithDistinct } from '~/distinct.ts'; + +export class SQLiteAggregateFunction extends SQLiteBuiltInFunction { + static readonly [entityKind]: string = 'SQLiteAggregateFunction'; + + filterWhere(where?: SQL | undefined): this { + if (where) { + this.sql.append(sql` filter (where ${where})`); + } + return this; + } +} + +/** + * Returns the number of values in `expression`. + * + * ## Examples + * + * ```ts + * // Number employees with null values + * db.select({ value: count() }).from(employees) + * // Number of employees where `name` is not null + * db.select({ value: count(employees.name) }).from(employees) + * // Number of employees where `name` is distinct (no duplicates) + * db.select({ value: count(distinct(employees.name)) }).from(employees) + * // Number of employees where their salaries are greater than $2,000 + * db.select({ value: count().filterWhere(gt(employees.salary, 2000)) }).from(employees) + * ``` + */ +export function count(expression?: MaybeDistinct | '*'): SQLiteAggregateFunction { + const { value, distinct } = getValueWithDistinct(expression); + const chunks: SQLChunk[] = []; + + if (distinct) { + chunks.push(sql`distinct `); + } + chunks.push(isSQLWrapper(value) ? value : sql`*`); + + const sql_ = sql.join([sql`count(`, ...chunks, sql`)` ]).mapWith(Number); + return new SQLiteAggregateFunction(sql_); +} + +/** + * Returns the average (arithmetic mean) of all non-null values in `expression`. + * + * ## Examples + * + * ```ts + * // Average salary of an employee + * db.select({ value: avg(employees.salary) }).from(employees) + * // Average salary of an employee where `salary` is distinct (no duplicates) + * db.select({ value: avg(distinct(employees.salary)) }).from(employees) + * // Average salary of an employee where their salaries are greater than $2,000 + * db.select({ value: avg(employees.salary).filterWhere(gt(employees.salary, 2000)) }).from(employees) + * ``` + */ +export function avg(expression: MaybeDistinct): SQLiteAggregateFunction { + const { value, distinct } = getValueWithDistinct(expression); + const chunks: SQLChunk[] = []; + + if (distinct) { + chunks.push(sql`distinct `); + } + chunks.push(value); + + const sql_ = sql.join([sql`avg(`, ...chunks, sql`)`]).mapWith(Number); + return new SQLiteAggregateFunction(sql_); +} + +/** + * Returns the sum of all non-null values in `expression`. + * + * ## Examples + * + * ```ts + * // Sum of every employee's salary + * db.select({ value: sum(employees.salary) }).from(employees) + * // Sum of every employee's salary where `salary` is distinct (no duplicates) + * db.select({ value: sum(distinct(employees.salary)) }).from(employees) + * // Sum of every employee's salary where their salaries are greater than $2,000 + * db.select({ value: sum(employees.salary).filterWhere(gt(employees.salary, 2000)) }).from(employees) + * ``` + * + * @see total for a function with the same purpose that's not part of the SQL standard and always returns a number + */ +export function sum(expression: MaybeDistinct): SQLiteAggregateFunction { + const { value, distinct } = getValueWithDistinct(expression); + const chunks: SQLChunk[] = []; + + if (distinct) { + chunks.push(sql`distinct `); + } + chunks.push(value); + + const sql_ = sql.join([sql`sum(`, ...chunks, sql`)`]).mapWith(Number); + return new SQLiteAggregateFunction(sql_); +} + +/** + * Returns the sum of all non-null values in `expression`. + * + * ## Examples + * + * ```ts + * // Sum of every employee's salary + * db.select({ value: total(employees.salary) }).from(employees) + * // Sum of every employee's salary where `salary` is distinct (no duplicates) + * db.select({ value: total(distinct(employees.salary)) }).from(employees) + * // Sum of every employee's salary where their salaries are greater than $2,000 + * db.select({ value: total(employees.salary).filterWhere(gt(employees.salary, 2000)) }).from(employees) + * ``` + * + * @see sum for a function with the same purpose that's part of the SQL standard and can return `null` + */ +export function total(expression: MaybeDistinct): SQLiteAggregateFunction { + const { value, distinct } = getValueWithDistinct(expression); + const chunks: SQLChunk[] = []; + + if (distinct) { + chunks.push(sql`distinct `); + } + chunks.push(value); + + const sql_ = sql.join([sql`total(`, ...chunks, sql`)`]).mapWith(Number); + return new SQLiteAggregateFunction(sql_); +} + +/** + * Returns the maximum value in `expression`. + * + * ## Examples + * + * ```ts + * // The employee with the highest salary + * db.select({ value: max(employees.salary) }).from(employees) + * ``` + */ +export function max(expression: SQLWrapper): T extends SQLiteColumn + ? SQLiteAggregateFunction + : SQLiteAggregateFunction +{ + let sql_ = sql.join([sql`max(`, expression, sql`)`]); + + if (is(expression, SQLiteColumn)) { + sql_ = sql_.mapWith(expression); + } else { + sql_ = sql_.mapWith(String); + } + + return new SQLiteAggregateFunction(sql_) as any; +} + +/** + * Returns the minimum value in `expression`. + * + * ## Examples + * + * ```ts + * // The employee with the lowest salary + * db.select({ value: min(employees.salary) }).from(employees) + * ``` + */ +export function min(expression: T): T extends SQLiteColumn + ? SQLiteAggregateFunction + : SQLiteAggregateFunction +{ + let sql_ = sql.join([sql`min(`, expression, sql`)`]); + + if (is(expression, SQLiteColumn)) { + sql_ = sql_.mapWith(expression); + } else { + sql_ = sql_.mapWith(String); + } + + return new SQLiteAggregateFunction(sql_) as any; +} diff --git a/drizzle-orm/src/sqlite-core/functions/common.ts b/drizzle-orm/src/sqlite-core/functions/common.ts new file mode 100644 index 000000000..37cccfd75 --- /dev/null +++ b/drizzle-orm/src/sqlite-core/functions/common.ts @@ -0,0 +1,11 @@ +import { BuiltInFunction } from '~/built-in-function.ts'; +import { entityKind } from '~/entity.ts'; + +export class SQLiteBuiltInFunction extends BuiltInFunction { + static readonly [entityKind]: string = 'SQLiteBuiltInFunction'; + + declare readonly _: { + readonly type: T; + readonly dialect: 'sqlite'; + }; +} diff --git a/drizzle-orm/src/sqlite-core/functions/index.ts b/drizzle-orm/src/sqlite-core/functions/index.ts new file mode 100644 index 000000000..47b15a648 --- /dev/null +++ b/drizzle-orm/src/sqlite-core/functions/index.ts @@ -0,0 +1,2 @@ +export * from './aggregate.ts'; +export * from './common.ts'; diff --git a/drizzle-orm/src/sqlite-core/index.ts b/drizzle-orm/src/sqlite-core/index.ts index ac2a19f0a..b7be85559 100644 --- a/drizzle-orm/src/sqlite-core/index.ts +++ b/drizzle-orm/src/sqlite-core/index.ts @@ -4,6 +4,7 @@ export * from './columns/index.ts'; export * from './db.ts'; export * from './dialect.ts'; export * from './foreign-keys.ts'; +export * from './functions/index.ts'; export * from './indexes.ts'; export * from './primary-keys.ts'; export * from './query-builders/index.ts'; diff --git a/drizzle-orm/src/sqlite-core/query-builders/delete.ts b/drizzle-orm/src/sqlite-core/query-builders/delete.ts index 746bb28fb..7be27fbab 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/delete.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/delete.ts @@ -8,6 +8,8 @@ import type { SQLitePreparedQuery, SQLiteSession } from '~/sqlite-core/session.t import { SQLiteTable } from '~/sqlite-core/table.ts'; import { type DrizzleTypeError, orderSelectedFields } from '~/utils.ts'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types.ts'; +import type { SQLiteBuiltInFunction } from '../functions/common.ts'; +import type { SQLiteColumn } from '../columns/common.ts'; export type SQLiteDeleteWithout< T extends AnySQLiteDeleteBase, @@ -155,7 +157,7 @@ export class SQLiteDeleteBase< returning( fields: SelectedFieldsFlat = this.table[SQLiteTable.Symbol.Columns], ): SQLiteDeleteReturning { - this.config.returning = orderSelectedFields(fields); + this.config.returning = orderSelectedFields(fields); return this as any; } diff --git a/drizzle-orm/src/sqlite-core/query-builders/insert.ts b/drizzle-orm/src/sqlite-core/query-builders/insert.ts index 669ee5ac5..d48c9b154 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/insert.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/insert.ts @@ -12,6 +12,8 @@ import { Table } from '~/table.ts'; import { type DrizzleTypeError, mapUpdateSet, orderSelectedFields, type Simplify } from '~/utils.ts'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types.ts'; import type { SQLiteUpdateSetSource } from './update.ts'; +import type { SQLiteBuiltInFunction } from '../functions/common.ts'; +import type { SQLiteColumn } from '../columns/common.ts'; export interface SQLiteInsertConfig { table: TTable; @@ -213,7 +215,7 @@ export class SQLiteInsertBase< returning( fields: SelectedFieldsFlat = this.config.table[SQLiteTable.Symbol.Columns], ): SQLiteInsertWithout { - this.config.returning = orderSelectedFields(fields); + this.config.returning = orderSelectedFields(fields); return this as any; } diff --git a/drizzle-orm/src/sqlite-core/query-builders/select.ts b/drizzle-orm/src/sqlite-core/query-builders/select.ts index 54a1feb5a..728741c6f 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/select.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/select.ts @@ -49,6 +49,7 @@ import type { SQLiteSetOperatorExcludedMethods, SQLiteSetOperatorWithResult, } from './select.types.ts'; +import type { SQLiteBuiltInFunction } from '../functions/common.ts'; export class SQLiteSelectBuilder< TSelection extends SelectedFields | undefined, @@ -525,7 +526,7 @@ export class SQLiteSelectBase< if (!this.session) { throw new Error('Cannot execute a query on a query builder. Please use a database instance instead.'); } - const fieldsList = orderSelectedFields(this.config.fields); + const fieldsList = orderSelectedFields(this.config.fields); const query = this.session[isOneTimeQuery ? 'prepareOneTimeQuery' : 'prepareQuery']( this.dialect.sqlToQuery(this.getSQL()), fieldsList, diff --git a/drizzle-orm/src/sqlite-core/query-builders/select.types.ts b/drizzle-orm/src/sqlite-core/query-builders/select.types.ts index 3e8b4359f..659d76c46 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/select.types.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/select.types.ts @@ -1,8 +1,8 @@ import type { Placeholder, SQL } from '~/sql/index.ts'; import type { Assume, ValidateShape } from '~/utils.ts'; - import type { SQLiteColumn } from '~/sqlite-core/columns/index.ts'; import type { SQLiteTable, SQLiteTableWithColumns } from '~/sqlite-core/table.ts'; +import type { SQLiteBuiltInFunction } from '../functions/common.ts'; import type { SelectedFields as SelectFieldsBase, @@ -116,11 +116,11 @@ export type SQLiteJoinFn< on: ((aliases: T['_']['selection']) => SQL | undefined) | SQL | undefined, ) => SQLiteJoin; -export type SelectedFieldsFlat = SelectFieldsFlatBase; +export type SelectedFieldsFlat = SelectFieldsFlatBase; -export type SelectedFields = SelectFieldsBase; +export type SelectedFields = SelectFieldsBase; -export type SelectedFieldsOrdered = SelectFieldsOrderedBase; +export type SelectedFieldsOrdered = SelectFieldsOrderedBase; export interface SQLiteSelectHKTBase { tableName: string | undefined; diff --git a/drizzle-orm/src/sqlite-core/query-builders/update.ts b/drizzle-orm/src/sqlite-core/query-builders/update.ts index aaafada7e..1217378aa 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/update.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/update.ts @@ -9,6 +9,8 @@ import type { SQLitePreparedQuery, SQLiteSession } from '~/sqlite-core/session.t import { SQLiteTable } from '~/sqlite-core/table.ts'; import { type DrizzleTypeError, mapUpdateSet, orderSelectedFields, type UpdateSet } from '~/utils.ts'; import type { SelectedFields, SelectedFieldsOrdered } from './select.types.ts'; +import type { SQLiteBuiltInFunction } from '../functions/common.ts'; +import type { SQLiteColumn } from '../columns/common.ts'; export interface SQLiteUpdateConfig { where?: SQL | undefined; @@ -186,7 +188,7 @@ export class SQLiteUpdateBase< returning( fields: SelectedFields = this.config.table[SQLiteTable.Symbol.Columns], ): SQLiteUpdateWithout { - this.config.returning = orderSelectedFields(fields); + this.config.returning = orderSelectedFields(fields); return this as any; } diff --git a/drizzle-orm/src/view.ts b/drizzle-orm/src/view.ts index bd1485bda..70afb4829 100644 --- a/drizzle-orm/src/view.ts +++ b/drizzle-orm/src/view.ts @@ -1,5 +1,6 @@ import type { AnyColumn } from './column.ts'; import { entityKind } from './entity.ts'; +import type { BuiltInFunction } from './built-in-function.ts'; import type { SelectedFields } from './operations.ts'; import { SQL, type SQLWrapper } from './sql/index.ts'; import type { Table } from './table.ts'; @@ -27,7 +28,7 @@ export abstract class View< name: TName; originalName: TName; schema: string | undefined; - selectedFields: SelectedFields; + selectedFields: SelectedFields; isExisting: TExisting; query: TExisting extends true ? undefined : SQL; isAlias: boolean; @@ -37,7 +38,7 @@ export abstract class View< { name, schema, selectedFields, query }: { name: TName; schema: string | undefined; - selectedFields: SelectedFields; + selectedFields: SelectedFields; query: SQL | undefined; }, ) { From d4eb356f964a60a7d04741be4447dd852837763b Mon Sep 17 00:00:00 2001 From: Mario564 Date: Thu, 9 Nov 2023 22:55:31 -0600 Subject: [PATCH 08/50] Create tests for aggregate functions --- .../src/mysql-core/functions/aggregate.ts | 4 +- .../src/pg-core/functions/aggregate.ts | 4 +- .../src/sqlite-core/functions/aggregate.ts | 2 +- integration-tests/tests/libsql.test.ts | 154 ++++++++++++++++++ integration-tests/tests/mysql.test.ts | 117 +++++++++++++ integration-tests/tests/pg.test.ts | 143 ++++++++++++++++ 6 files changed, 419 insertions(+), 5 deletions(-) diff --git a/drizzle-orm/src/mysql-core/functions/aggregate.ts b/drizzle-orm/src/mysql-core/functions/aggregate.ts index ba569ee6f..97fb7d82d 100644 --- a/drizzle-orm/src/mysql-core/functions/aggregate.ts +++ b/drizzle-orm/src/mysql-core/functions/aggregate.ts @@ -66,7 +66,7 @@ export function avg BigInt(Number.parseInt(value))); } else if (config?.mode === 'number') { sql_ = sql_.mapWith(Number); } @@ -100,7 +100,7 @@ export function sum BigInt(Number.parseInt(value))); } else if (config?.mode === 'number') { sql_ = sql_.mapWith(Number); } diff --git a/drizzle-orm/src/pg-core/functions/aggregate.ts b/drizzle-orm/src/pg-core/functions/aggregate.ts index 36e9fd26b..b3ff6888b 100644 --- a/drizzle-orm/src/pg-core/functions/aggregate.ts +++ b/drizzle-orm/src/pg-core/functions/aggregate.ts @@ -77,7 +77,7 @@ export function avg BigInt(Number.parseInt(value))); } else if (config?.mode === 'number') { sql_ = sql_.mapWith(Number); } @@ -113,7 +113,7 @@ export function sum BigInt(Number.parseInt(value))); } else if (config?.mode === 'number') { sql_ = sql_.mapWith(Number); } diff --git a/drizzle-orm/src/sqlite-core/functions/aggregate.ts b/drizzle-orm/src/sqlite-core/functions/aggregate.ts index c7bfa42cb..821aafe2d 100644 --- a/drizzle-orm/src/sqlite-core/functions/aggregate.ts +++ b/drizzle-orm/src/sqlite-core/functions/aggregate.ts @@ -139,7 +139,7 @@ export function total(expression: MaybeDistinct): SQLiteAggregateFun * db.select({ value: max(employees.salary) }).from(employees) * ``` */ -export function max(expression: SQLWrapper): T extends SQLiteColumn +export function max(expression: T): T extends SQLiteColumn ? SQLiteAggregateFunction : SQLiteAggregateFunction { diff --git a/integration-tests/tests/libsql.test.ts b/integration-tests/tests/libsql.test.ts index b8e224e1b..da79d9b96 100644 --- a/integration-tests/tests/libsql.test.ts +++ b/integration-tests/tests/libsql.test.ts @@ -15,12 +15,17 @@ import { placeholder, sql, TransactionRollbackError, + distinct, + isNotNull, + lt, } from 'drizzle-orm'; import { drizzle, type LibSQLDatabase } from 'drizzle-orm/libsql'; import { migrate } from 'drizzle-orm/libsql/migrator'; import { alias, + avg, blob, + count, except, foreignKey, getTableConfig, @@ -28,11 +33,15 @@ import { int, integer, intersect, + max, + min, primaryKey, sqliteTable, sqliteTableCreator, sqliteView, + sum, text, + total, union, unionAll, } from 'drizzle-orm/sqlite-core'; @@ -111,6 +120,16 @@ const bigIntExample = sqliteTable('big_int_example', { bigInt: blob('big_int', { mode: 'bigint' }).notNull(), }); +// To test aggregate functions +const aggregateTable = sqliteTable('aggregate_table', { + id: integer('id').primaryKey({ autoIncrement: true }).notNull(), + name: text('name').notNull(), + a: integer('a'), + b: integer('b'), + c: integer('c'), + nullOnly: integer('null_only') +}); + test.before(async (t) => { const ctx = t.context; const url = process.env['LIBSQL_URL']; @@ -255,6 +274,31 @@ async function setupSetOperationTest(db: LibSQLDatabase>) ]); } +async function setupAggregateFunctionsTest(db: LibSQLDatabase>) { + await db.run(sql`drop table if exists "aggregate_table"`); + await db.run( + sql` + create table "aggregate_table" ( + "id" integer primary key autoincrement not null, + "name" text not null, + "a" integer, + "b" integer, + "c" integer, + "null_only" integer + ); + `, + ); + await db.insert(aggregateTable).values([ + { name: 'value 1', a: 5, b: 10, c: 20 }, + { name: 'value 1', a: 5, b: 20, c: 30 }, + { name: 'value 2', a: 10, b: 50, c: 60 }, + { name: 'value 3', a: 20, b: 20, c: null }, + { name: 'value 4', a: null, b: 90, c: 120 }, + { name: 'value 5', a: 80, b: 10, c: null }, + { name: 'value 6', a: null, b: null, c: 150 }, + ]); +} + test.serial('table config: foreign keys name', async (t) => { const table = sqliteTable('cities', { id: int('id').primaryKey(), @@ -2423,3 +2467,113 @@ test.serial('set operations (mixed all) as function with subquery', async (t) => ).orderBy(asc(sql`id`)); }); }); + +test.serial('aggregate function: count', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: count('*') }).from(table); + const result2 = await db.select({ value: count(table.a) }).from(table); + const result3 = await db.select({ value: count(distinct(table.name)) }).from(table); + const result4 = await db.select({ + all: count(), + filtered: count().filterWhere(isNotNull(table.c)) + }).from(table); + + t.deepEqual(result1[0]?.value, 7); + t.deepEqual(result2[0]?.value, 5); + t.deepEqual(result3[0]?.value, 6); + t.deepEqual(result4[0], { all: 7, filtered: 5 }); +}); + +test.serial('aggregate function: avg', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: avg(table.a) }).from(table); + const result2 = await db.select({ value: avg(table.nullOnly) }).from(table); + const result3 = await db.select({ value: avg(distinct(table.b)) }).from(table); + const result4 = await db.select({ + all: avg(table.c), + filtered: avg(table.c).filterWhere(lt(table.c, 60)) + }).from(table); + + t.deepEqual(result1[0]?.value, 24); + t.deepEqual(result2[0]?.value, null); + t.deepEqual(result3[0]?.value, 42.5); + t.deepEqual(result4[0], { all: 76, filtered: 25 }); +}); + +test.serial('aggregate function: sum', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: sum(table.b) }).from(table); + const result2 = await db.select({ value: sum(table.nullOnly) }).from(table); + const result3 = await db.select({ value: sum(distinct(table.b)) }).from(table); + const result4 = await db.select({ + all: sum(table.c), + filtered: sum(table.c).filterWhere(lt(table.c, 100)) + }).from(table); + + t.deepEqual(result1[0]?.value, 200); + t.deepEqual(result2[0]?.value, null); + t.deepEqual(result3[0]?.value, 170); + t.deepEqual(result4[0], { all: 380, filtered: 110 }); +}); + +test.serial('aggregate function: total', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: total(table.b) }).from(table); + const result2 = await db.select({ value: total(table.nullOnly) }).from(table); + const result3 = await db.select({ value: total(distinct(table.b)) }).from(table); + const result4 = await db.select({ + all: total(table.c), + filtered: total(table.c).filterWhere(lt(table.c, 100)) + }).from(table); + + t.deepEqual(result1[0]?.value, 200); + t.deepEqual(result2[0]?.value, 0); + t.deepEqual(result3[0]?.value, 170); + t.deepEqual(result4[0], { all: 380, filtered: 110 }); +}); + +test.serial('aggregate function: max', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: max(table.b) }).from(table); + const result2 = await db.select({ value: max(table.nullOnly) }).from(table); + const result3 = await db.select({ + all: max(table.c), + filtered: max(table.c).filterWhere(lt(table.c, 100)) + }).from(table); + + t.deepEqual(result1[0]?.value, 90); + t.deepEqual(result2[0]?.value, null); + t.deepEqual(result3[0], { all: 150, filtered: 60 }); +}); + +test.serial('aggregate function: min', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: min(table.b) }).from(table); + const result2 = await db.select({ value: min(table.nullOnly) }).from(table); + const result3 = await db.select({ + all: min(table.c), + filtered: min(table.c).filterWhere(gt(table.c, 20)) + }).from(table); + + t.deepEqual(result1[0]?.value, 10); + t.deepEqual(result2[0]?.value, null); + t.deepEqual(result3[0], { all: 20, filtered: 30 }); +}); diff --git a/integration-tests/tests/mysql.test.ts b/integration-tests/tests/mysql.test.ts index 3b545fcd8..25ec90b41 100644 --- a/integration-tests/tests/mysql.test.ts +++ b/integration-tests/tests/mysql.test.ts @@ -15,11 +15,14 @@ import { placeholder, sql, TransactionRollbackError, + distinct, } from 'drizzle-orm'; import { alias, + avg, bigint, boolean, + count, date, datetime, except, @@ -31,7 +34,9 @@ import { intersect, intersectAll, json, + max, mediumint, + min, mysqlEnum, mysqlTable, mysqlTableCreator, @@ -39,6 +44,7 @@ import { primaryKey, serial, smallint, + sum, text, time, timestamp, @@ -119,6 +125,16 @@ const usersMigratorTable = mysqlTable('users12', { }; }); +// To test aggregate functions +const aggregateTable = mysqlTable('aggregate_table', { + id: serial('id').notNull(), + name: text('name').notNull(), + a: int('a'), + b: int('b'), + c: int('c'), + nullOnly: int('null_only') +}); + interface Context { docker: Docker; mysqlContainer: Docker.Container; @@ -268,6 +284,31 @@ async function setupSetOperationTest(db: MySql2Database) { ]); } +async function setupAggregateFunctionsTest(db: MySql2Database) { + await db.execute(sql`drop table if exists \`aggregate_table\``); + await db.execute( + sql` + create table \`aggregate_table\` ( + \`id\` integer primary key auto_increment not null, + \`name\` text not null, + \`a\` integer, + \`b\` integer, + \`c\` integer, + \`null_only\` integer + ); + `, + ); + await db.insert(aggregateTable).values([ + { name: 'value 1', a: 5, b: 10, c: 20 }, + { name: 'value 1', a: 5, b: 20, c: 30 }, + { name: 'value 2', a: 10, b: 50, c: 60 }, + { name: 'value 3', a: 20, b: 20, c: null }, + { name: 'value 4', a: null, b: 90, c: 120 }, + { name: 'value 5', a: 80, b: 10, c: null }, + { name: 'value 6', a: null, b: null, c: 150 }, + ]); +} + test.serial('table config: unsigned ints', async (t) => { const unsignedInts = mysqlTable('cities1', { bigint: bigint('bigint', { mode: 'number', unsigned: true }), @@ -2654,3 +2695,79 @@ test.serial('set operations (mixed all) as function with subquery', async (t) => ); }); }); + +test.serial('aggregate function: count', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: count('*') }).from(table); + const result2 = await db.select({ value: count('*', { mode: 'number' }) }).from(table); + const result3 = await db.select({ value: count(table.a) }).from(table); + const result4 = await db.select({ value: count(distinct(table.name)) }).from(table); + + t.deepEqual(result1[0]?.value, BigInt(7)); + t.deepEqual(result2[0]?.value, 7); + t.deepEqual(result3[0]?.value, BigInt(5)); + t.deepEqual(result4[0]?.value, BigInt(6)); +}); + +test.serial('aggregate function: avg', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: avg(table.b) }).from(table); + const result2 = await db.select({ value: avg(table.b, { mode: 'number' }) }).from(table); + const result3 = await db.select({ value: avg(table.b, { mode: 'bigint' }) }).from(table); + const result4 = await db.select({ value: avg(table.nullOnly) }).from(table); + const result5 = await db.select({ value: avg(distinct(table.b)) }).from(table); + + t.deepEqual(result1[0]?.value, '33.3333'); + t.deepEqual(result2[0]?.value, 33.3333); + t.deepEqual(result3[0]?.value, BigInt(33)); + t.deepEqual(result4[0]?.value, null); + t.deepEqual(result5[0]?.value, '42.5000'); +}); + +test.serial('aggregate function: sum', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: sum(table.b) }).from(table); + const result2 = await db.select({ value: sum(table.b, { mode: 'number' }) }).from(table); + const result3 = await db.select({ value: sum(table.b, { mode: 'bigint' }) }).from(table); + const result4 = await db.select({ value: sum(table.nullOnly) }).from(table); + const result5 = await db.select({ value: sum(distinct(table.b)) }).from(table); + + t.deepEqual(result1[0]?.value, '200'); + t.deepEqual(result2[0]?.value, 200); + t.deepEqual(result3[0]?.value, BigInt(200)); + t.deepEqual(result4[0]?.value, null); + t.deepEqual(result5[0]?.value, '170'); +}); + +test.serial('aggregate function: max', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: max(table.b) }).from(table); + const result2 = await db.select({ value: max(table.nullOnly) }).from(table); + + t.deepEqual(result1[0]?.value, 90); + t.deepEqual(result2[0]?.value, null); +}); + +test.serial('aggregate function: min', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: min(table.b) }).from(table); + const result2 = await db.select({ value: min(table.nullOnly) }).from(table); + + t.deepEqual(result1[0]?.value, 10); + t.deepEqual(result2[0]?.value, null); +}); diff --git a/integration-tests/tests/pg.test.ts b/integration-tests/tests/pg.test.ts index 38fd1a8a3..2008688a1 100644 --- a/integration-tests/tests/pg.test.ts +++ b/integration-tests/tests/pg.test.ts @@ -20,6 +20,8 @@ import { sql, type SQLWrapper, TransactionRollbackError, + isNotNull, + distinct, } from 'drizzle-orm'; import { drizzle, type NodePgDatabase } from 'drizzle-orm/node-postgres'; import { migrate } from 'drizzle-orm/node-postgres/migrator'; @@ -57,6 +59,11 @@ import { uuid as pgUuid, varchar, pgEnum, + count, + avg, + sum, + max, + min, } from 'drizzle-orm/pg-core'; import getPort from 'get-port'; import pg from 'pg'; @@ -134,6 +141,16 @@ const usersMigratorTable = pgTable('users12', { email: text('email').notNull(), }); +// To test aggregate functions +const aggregateTable = pgTable('aggregate_table', { + id: serial('id').notNull(), + name: text('name').notNull(), + a: integer('a'), + b: integer('b'), + c: integer('c'), + nullOnly: integer('null_only') +}); + interface Context { docker: Docker; pgContainer: Docker.Container; @@ -333,6 +350,31 @@ async function setupSetOperationTest(db: NodePgDatabase) { ]); } +async function setupAggregateFunctionsTest(db: NodePgDatabase) { + await db.execute(sql`drop table if exists "aggregate_table"`); + await db.execute( + sql` + create table "aggregate_table" ( + "id" serial not null, + "name" text not null, + "a" integer, + "b" integer, + "c" integer, + "null_only" integer + ); + `, + ); + await db.insert(aggregateTable).values([ + { name: 'value 1', a: 5, b: 10, c: 20 }, + { name: 'value 1', a: 5, b: 20, c: 30 }, + { name: 'value 2', a: 10, b: 50, c: 60 }, + { name: 'value 3', a: 20, b: 20, c: null }, + { name: 'value 4', a: null, b: 90, c: 120 }, + { name: 'value 5', a: 80, b: 10, c: null }, + { name: 'value 6', a: null, b: null, c: 150 }, + ]); +} + test.serial('table configs: unique third param', async (t) => { const cities1Table = pgTable('cities1', { id: serial('id').primaryKey(), @@ -3151,3 +3193,104 @@ test.serial('set operations (mixed all) as function', async (t) => { ).orderBy(asc(sql`id`)); }); }); + +test.serial('aggregate function: count', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: count('*') }).from(table); + const result2 = await db.select({ value: count('*', { mode: 'number' }) }).from(table); + const result3 = await db.select({ value: count(table.a) }).from(table); + const result4 = await db.select({ value: count(distinct(table.name)) }).from(table); + const result5 = await db.select({ + all: count(), + filtered: count().filterWhere(isNotNull(table.c)) + }).from(table); + + t.deepEqual(result1[0]?.value, BigInt(7)); + t.deepEqual(result2[0]?.value, 7); + t.deepEqual(result3[0]?.value, BigInt(5)); + t.deepEqual(result4[0]?.value, BigInt(6)); + t.deepEqual(result5[0], { all: BigInt(7), filtered: BigInt(5) }); +}); + +test.serial('aggregate function: avg', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: avg(table.b) }).from(table); + const result2 = await db.select({ value: avg(table.b, { mode: 'number' }) }).from(table); + const result3 = await db.select({ value: avg(table.b, { mode: 'bigint' }) }).from(table); + const result4 = await db.select({ value: avg(table.nullOnly) }).from(table); + const result5 = await db.select({ value: avg(distinct(table.b)) }).from(table); + const result6 = await db.select({ + all: avg(table.c), + filtered: avg(table.c).filterWhere(lt(table.c, 100)) + }).from(table); + + t.deepEqual(result1[0]?.value, '33.3333333333333333'); + t.deepEqual(result2[0]?.value, 33.333333333333336); + t.deepEqual(result3[0]?.value, BigInt(33)); + t.deepEqual(result4[0]?.value, null); + t.deepEqual(result5[0]?.value, '42.5000000000000000'); + t.deepEqual(result6[0], { all: '76.0000000000000000', filtered: '36.6666666666666667' }); +}); + +test.serial('aggregate function: sum', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: sum(table.b) }).from(table); + const result2 = await db.select({ value: sum(table.b, { mode: 'number' }) }).from(table); + const result3 = await db.select({ value: sum(table.b, { mode: 'bigint' }) }).from(table); + const result4 = await db.select({ value: sum(table.nullOnly) }).from(table); + const result5 = await db.select({ value: sum(distinct(table.b)) }).from(table); + const result6 = await db.select({ + all: sum(table.c), + filtered: sum(table.c).filterWhere(lt(table.c, 100)) + }).from(table); + + t.deepEqual(result1[0]?.value, '200'); + t.deepEqual(result2[0]?.value, 200); + t.deepEqual(result3[0]?.value, BigInt(200)); + t.deepEqual(result4[0]?.value, null); + t.deepEqual(result5[0]?.value, '170'); + t.deepEqual(result6[0], { all: '380', filtered: '110' }); +}); + +test.serial('aggregate function: max', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: max(table.b) }).from(table); + const result2 = await db.select({ value: max(table.nullOnly) }).from(table); + const result3 = await db.select({ + all: max(table.c), + filtered: max(table.c).filterWhere(lt(table.c, 100)) + }).from(table); + + t.deepEqual(result1[0]?.value, 90); + t.deepEqual(result2[0]?.value, null); + t.deepEqual(result3[0], { all: 150, filtered: 60 }); +}); + +test.serial('aggregate function: min', async (t) => { + const { db } = t.context; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: min(table.b) }).from(table); + const result2 = await db.select({ value: min(table.nullOnly) }).from(table); + const result3 = await db.select({ + all: min(table.c), + filtered: min(table.c).filterWhere(gt(table.c, 20)) + }).from(table); + + t.deepEqual(result1[0]?.value, 10); + t.deepEqual(result2[0]?.value, null); + t.deepEqual(result3[0], { all: 20, filtered: 30 }); +}); From a6b1e6860469cfd0c728b4b5d8605559b8d87c67 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Fri, 10 Nov 2023 21:35:47 -0600 Subject: [PATCH 09/50] Fix linting issues --- drizzle-orm/src/built-in-function.ts | 7 +++--- drizzle-orm/src/distinct.ts | 2 +- .../src/mysql-core/functions/aggregate.ts | 24 ++++++------------- .../src/pg-core/functions/aggregate.ts | 24 ++++++------------- .../src/pg-core/query-builders/update.ts | 2 +- .../src/sqlite-core/functions/aggregate.ts | 24 ++++++------------- 6 files changed, 27 insertions(+), 56 deletions(-) diff --git a/drizzle-orm/src/built-in-function.ts b/drizzle-orm/src/built-in-function.ts index e8b9f870e..894d7bec2 100644 --- a/drizzle-orm/src/built-in-function.ts +++ b/drizzle-orm/src/built-in-function.ts @@ -1,10 +1,11 @@ import { entityKind } from './entity.ts'; -import { Dialect } from './column-builder.ts'; -import { type SQLWrapper, type SQL, sql, DriverValueDecoder, GetDecoderResult } from './sql/sql.ts'; +import type { Dialect } from './column-builder.ts'; +import type { SQLWrapper, SQL, DriverValueDecoder, GetDecoderResult } from './sql/sql.ts'; /** @internal */ export const BuiltInFunctionSQL = Symbol.for('drizzle:BuiltInFunctionSQL'); +// eslint-disable-next-line @typescript-eslint/no-unused-vars export interface BuiltInFunction extends SQLWrapper { // SQLWrapper runtime implementation is defined in 'sql/sql.ts' } @@ -24,7 +25,7 @@ export abstract class BuiltInFunction implements SQLWrapper { /** @internal */ get [BuiltInFunctionSQL](): SQL { return this.sql; - }; + } protected sql: SQL; diff --git a/drizzle-orm/src/distinct.ts b/drizzle-orm/src/distinct.ts index 8d5d84a4b..8cf12c22d 100644 --- a/drizzle-orm/src/distinct.ts +++ b/drizzle-orm/src/distinct.ts @@ -1,5 +1,5 @@ import { entityKind, is } from './entity.ts'; -import { type SQLWrapper } from './index.ts'; +import type { SQLWrapper } from './index.ts'; /** @internal */ export const DistinctValue = Symbol.for('drizzle:DistinctValue'); diff --git a/drizzle-orm/src/mysql-core/functions/aggregate.ts b/drizzle-orm/src/mysql-core/functions/aggregate.ts index 97fb7d82d..8ce4b9404 100644 --- a/drizzle-orm/src/mysql-core/functions/aggregate.ts +++ b/drizzle-orm/src/mysql-core/functions/aggregate.ts @@ -1,6 +1,6 @@ import { is, entityKind } from '~/entity.ts'; import { MySqlColumn } from '../columns/index.ts'; -import { type SQL, sql, type SQLWrapper, isSQLWrapper, SQLChunk } from '~/sql/index.ts'; +import { sql, type SQLWrapper, isSQLWrapper, type SQLChunk } from '~/sql/index.ts'; import { MySqlBuiltInFunction } from './common.ts'; import { type MaybeDistinct, getValueWithDistinct } from '~/distinct.ts'; @@ -122,14 +122,9 @@ export function max(expression: T): T extends MySqlColumn ? MySqlAggregateFunction : MySqlAggregateFunction { - let sql_ = sql.join([sql`max(`, expression, sql`)`]); - - if (is(expression, MySqlColumn)) { - sql_ = sql_.mapWith(expression); - } else { - sql_ = sql_.mapWith(String); - } - + const sql_ = sql + .join([sql`max(`, expression, sql`)`]) + .mapWith(is(expression, MySqlColumn) ? expression : String); return new MySqlAggregateFunction(sql_) as any; } @@ -147,13 +142,8 @@ export function min(expression: T): T extends MySqlColumn ? MySqlAggregateFunction : MySqlAggregateFunction { - let sql_ = sql.join([sql`min(`, expression, sql`)`]); - - if (is(expression, MySqlColumn)) { - sql_ = sql_.mapWith(expression); - } else { - sql_ = sql_.mapWith(String); - } - + const sql_ = sql + .join([sql`min(`, expression, sql`)`]) + .mapWith(is(expression, MySqlColumn) ? expression : String); return new MySqlAggregateFunction(sql_) as any; } diff --git a/drizzle-orm/src/pg-core/functions/aggregate.ts b/drizzle-orm/src/pg-core/functions/aggregate.ts index b3ff6888b..e8cdb55dd 100644 --- a/drizzle-orm/src/pg-core/functions/aggregate.ts +++ b/drizzle-orm/src/pg-core/functions/aggregate.ts @@ -1,6 +1,6 @@ import { is, entityKind } from '~/entity.ts'; import { PgColumn } from '../columns/index.ts'; -import { type SQL, sql, type SQLWrapper, isSQLWrapper, SQLChunk } from '~/sql/index.ts'; +import { type SQL, sql, type SQLWrapper, isSQLWrapper, type SQLChunk } from '~/sql/index.ts'; import { PgBuiltInFunction } from './common.ts'; import { type MaybeDistinct, getValueWithDistinct } from '~/distinct.ts'; @@ -135,14 +135,9 @@ export function max(expression: T): T extends PgColumn ? PgAggregateFunction : PgAggregateFunction { - let sql_ = sql.join([sql`max(`, expression, sql`)`]); - - if (is(expression, PgColumn)) { - sql_ = sql_.mapWith(expression); - } else { - sql_ = sql_.mapWith(String); - } - + const sql_ = sql + .join([sql`max(`, expression, sql`)`]) + .mapWith(is(expression, PgColumn) ? expression : String); return new PgAggregateFunction(sql_) as any; } @@ -160,13 +155,8 @@ export function min(expression: T): T extends PgColumn ? PgAggregateFunction : PgAggregateFunction { - let sql_ = sql.join([sql`min(`, expression, sql`)`]); - - if (is(expression, PgColumn)) { - sql_ = sql_.mapWith(expression); - } else { - sql_ = sql_.mapWith(String); - } - + const sql_ = sql + .join([sql`min(`, expression, sql`)`]) + .mapWith(is(expression, PgColumn) ? expression : String); return new PgAggregateFunction(sql_) as any; } diff --git a/drizzle-orm/src/pg-core/query-builders/update.ts b/drizzle-orm/src/pg-core/query-builders/update.ts index 584c7b351..12027b009 100644 --- a/drizzle-orm/src/pg-core/query-builders/update.ts +++ b/drizzle-orm/src/pg-core/query-builders/update.ts @@ -16,7 +16,7 @@ import { Table } from '~/table.ts'; import { mapUpdateSet, orderSelectedFields, type UpdateSet } from '~/utils.ts'; import type { SelectedFields, SelectedFieldsOrdered } from './select.types.ts'; import type { PgColumn } from '../columns/common.ts'; -import { PgBuiltInFunction } from '../functions/common.ts' +import type { PgBuiltInFunction } from '../functions/common.ts' export interface PgUpdateConfig { where?: SQL | undefined; diff --git a/drizzle-orm/src/sqlite-core/functions/aggregate.ts b/drizzle-orm/src/sqlite-core/functions/aggregate.ts index 821aafe2d..705bf367c 100644 --- a/drizzle-orm/src/sqlite-core/functions/aggregate.ts +++ b/drizzle-orm/src/sqlite-core/functions/aggregate.ts @@ -1,6 +1,6 @@ import { is, entityKind } from '~/entity.ts'; import { SQLiteColumn } from '../columns/index.ts'; -import { type SQL, sql, type SQLWrapper, isSQLWrapper, SQLChunk } from '~/sql/index.ts'; +import { type SQL, sql, type SQLWrapper, isSQLWrapper, type SQLChunk } from '~/sql/index.ts'; import { SQLiteBuiltInFunction } from './common.ts'; import { type MaybeDistinct, getValueWithDistinct } from '~/distinct.ts'; @@ -143,14 +143,9 @@ export function max(expression: T): T extends SQLiteColumn ? SQLiteAggregateFunction : SQLiteAggregateFunction { - let sql_ = sql.join([sql`max(`, expression, sql`)`]); - - if (is(expression, SQLiteColumn)) { - sql_ = sql_.mapWith(expression); - } else { - sql_ = sql_.mapWith(String); - } - + const sql_ = sql + .join([sql`max(`, expression, sql`)`]) + .mapWith(is(expression, SQLiteColumn) ? expression : String) return new SQLiteAggregateFunction(sql_) as any; } @@ -168,13 +163,8 @@ export function min(expression: T): T extends SQLiteColumn ? SQLiteAggregateFunction : SQLiteAggregateFunction { - let sql_ = sql.join([sql`min(`, expression, sql`)`]); - - if (is(expression, SQLiteColumn)) { - sql_ = sql_.mapWith(expression); - } else { - sql_ = sql_.mapWith(String); - } - + const sql_ = sql + .join([sql`min(`, expression, sql`)`]) + .mapWith(is(expression, SQLiteColumn) ? expression : String); return new SQLiteAggregateFunction(sql_) as any; } From 8eebe5befec4f4425cf4e5abc447ff1948ae05ae Mon Sep 17 00:00:00 2001 From: Angelelz Date: Tue, 14 Nov 2023 19:53:26 -0500 Subject: [PATCH 10/50] Moved eslint-plugin-drizzle to drizzle-orm repository --- .github/workflows/release-latest.yaml | 5 +- changelogs/eslint-plugin-drizzle/0.2.0.md | 4 + eslint-plugin-drizzle/.gitignore | 3 + eslint-plugin-drizzle/package.json | 36 ++ eslint-plugin-drizzle/readme.md | 71 +++ eslint-plugin-drizzle/src/configs/all.ts | 14 + .../src/configs/recommended.ts | 14 + .../src/enforce-delete-with-where.ts | 39 ++ .../src/enforce-update-with-where.ts | 47 ++ eslint-plugin-drizzle/src/index.ts | 15 + eslint-plugin-drizzle/tests/delete.test.ts | 39 ++ eslint-plugin-drizzle/tests/update.test.ts | 46 ++ eslint-plugin-drizzle/tsconfig.json | 29 ++ eslint-plugin-drizzle/vitest.config.ts | 8 + pnpm-lock.yaml | 465 +++++++++++++++++- pnpm-workspace.yaml | 1 + 16 files changed, 824 insertions(+), 12 deletions(-) create mode 100644 changelogs/eslint-plugin-drizzle/0.2.0.md create mode 100644 eslint-plugin-drizzle/.gitignore create mode 100644 eslint-plugin-drizzle/package.json create mode 100644 eslint-plugin-drizzle/readme.md create mode 100644 eslint-plugin-drizzle/src/configs/all.ts create mode 100644 eslint-plugin-drizzle/src/configs/recommended.ts create mode 100644 eslint-plugin-drizzle/src/enforce-delete-with-where.ts create mode 100644 eslint-plugin-drizzle/src/enforce-update-with-where.ts create mode 100644 eslint-plugin-drizzle/src/index.ts create mode 100644 eslint-plugin-drizzle/tests/delete.test.ts create mode 100644 eslint-plugin-drizzle/tests/update.test.ts create mode 100644 eslint-plugin-drizzle/tsconfig.json create mode 100644 eslint-plugin-drizzle/vitest.config.ts diff --git a/.github/workflows/release-latest.yaml b/.github/workflows/release-latest.yaml index 3153adb32..181f9e65b 100644 --- a/.github/workflows/release-latest.yaml +++ b/.github/workflows/release-latest.yaml @@ -13,6 +13,7 @@ jobs: - drizzle-zod - drizzle-typebox - drizzle-valibot + - eslint-plugin-drizzle runs-on: ubuntu-20.04 services: postgres: @@ -82,8 +83,8 @@ jobs: is_version_published="$(npm view ${{ matrix.package }} versions --json | jq -r '.[] | select(. == "'$version'") | . == "'$version'"')" if [[ "$is_version_published" == "true" ]]; then - echo "\`${{ matrix.package }}@$version\` already published, adding tag \`latest\`" >> $GITHUB_STEP_SUMMARY - npm dist-tag add ${{ matrix.package }}@$version latest + echo "\`${{ matrix.package }}@ $version\` already published, adding tag \`latest\`" >> $GITHUB_STEP_SUMMARY + npm dist-tag add ${{ matrix.package }}@ $version latest elif [[ "$latest" != "$version" ]]; then echo "Latest: $latest" echo "Current: $version" diff --git a/changelogs/eslint-plugin-drizzle/0.2.0.md b/changelogs/eslint-plugin-drizzle/0.2.0.md new file mode 100644 index 000000000..c94ced11f --- /dev/null +++ b/changelogs/eslint-plugin-drizzle/0.2.0.md @@ -0,0 +1,4 @@ +# eslint-plugin-drizzle 0.1.0 + +- Initial release +- 2 rules available diff --git a/eslint-plugin-drizzle/.gitignore b/eslint-plugin-drizzle/.gitignore new file mode 100644 index 000000000..11e45930a --- /dev/null +++ b/eslint-plugin-drizzle/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +tsconfig.tsbuildinfo diff --git a/eslint-plugin-drizzle/package.json b/eslint-plugin-drizzle/package.json new file mode 100644 index 000000000..1e81096ec --- /dev/null +++ b/eslint-plugin-drizzle/package.json @@ -0,0 +1,36 @@ +{ + "name": "eslint-plugin-drizzle", + "version": "0.2.0", + "description": "Eslint plugin for drizzle users to avoid common pitfalls", + "main": "src/index.js", + "scripts": { + "test": "vitest run", + "build": "tsc -b && cp ./readme.md ./dist/readme.md", + "pack": "(cd dist && npm pack --pack-destination ..) && rm -f package.tgz && mv *.tgz package.tgz", + "publish": "npm publish package.tgz" + }, + "keywords": [ + "eslint", + "eslintplugin", + "eslint-plugin", + "drizzle" + ], + "author": "Angelelz", + "repository": { + "type": "git", + "url": "git+https://github.com/drizzle-team/drizzle-orm.git" + }, + "license": "Apache-2.0", + "dependencies": {}, + "devDependencies": { + "@typescript-eslint/utils": "^6.10.0", + "@typescript-eslint/parser": "^6.10.0", + "@typescript-eslint/rule-tester": "^6.10.0", + "eslint": "^8.53.0", + "typescript": "^5.2.2", + "vitest": "^0.34.6" + }, + "peerDependencies": { + "eslint": ">=8.0.0" + } +} diff --git a/eslint-plugin-drizzle/readme.md b/eslint-plugin-drizzle/readme.md new file mode 100644 index 000000000..968e530bd --- /dev/null +++ b/eslint-plugin-drizzle/readme.md @@ -0,0 +1,71 @@ +# eslint-plugin-drizzle + +eslint plugin for drizzle users to avoid common pitfalls + +## Install + +```sh +[ npm | yarn | pnpm | bun ] install eslint eslint-plugin-drizzle +``` + +## Usage + +Use a [preset config](#preset-configs) or configure each rule in `package.json` or `eslint.config.js`. + +If you don't use the preset, ensure you use the same `env` and `parserOptions` config as below. + +```json +{ + "name": "my-awesome-project", + "eslintConfig": { + "env": { + "es2024": true + }, + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": ["drizzle"], + "rules": { + "drizzle/enforce-delete-with-where": "error", + "drizzle/enforce-update-with-where": "error" + } + } +} +``` + +## Rules + +**enforce-delete-with-where**: Enforce using `delete` with `where` in `DELETE` statement + +**enforce-update-with-where**: Enforce using `update` with `where` in `UPDATE` statement + +## Preset configs + +### Recommended config + +This plugin exports a [`recommended` config](src/configs/recommended.js) that enforces good practices. + +```json +{ + "name": "my-awesome-project", + "eslintConfig": { + "extends": "plugin:drizzle/recommended" + } +} +``` + +### All config + +This plugin exports an [`all` config](src/configs/all.js) that makes use of all rules (except for deprecated ones). + +```json +{ + "name": "my-awesome-project", + "eslintConfig": { + "extends": "plugin:drizzle/all" + } +} +``` + +At the moment, `all` is equivalent to `recommended`. diff --git a/eslint-plugin-drizzle/src/configs/all.ts b/eslint-plugin-drizzle/src/configs/all.ts new file mode 100644 index 000000000..18093b5bf --- /dev/null +++ b/eslint-plugin-drizzle/src/configs/all.ts @@ -0,0 +1,14 @@ +export default { + env: { + es2024: true, + }, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + plugins: ['drizzle'], + rules: { + 'drizzle/enforce-delete-with-where': 'error', + 'drizzle/enforce-update-with-where': 'error', + }, +}; diff --git a/eslint-plugin-drizzle/src/configs/recommended.ts b/eslint-plugin-drizzle/src/configs/recommended.ts new file mode 100644 index 000000000..18093b5bf --- /dev/null +++ b/eslint-plugin-drizzle/src/configs/recommended.ts @@ -0,0 +1,14 @@ +export default { + env: { + es2024: true, + }, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + plugins: ['drizzle'], + rules: { + 'drizzle/enforce-delete-with-where': 'error', + 'drizzle/enforce-update-with-where': 'error', + }, +}; diff --git a/eslint-plugin-drizzle/src/enforce-delete-with-where.ts b/eslint-plugin-drizzle/src/enforce-delete-with-where.ts new file mode 100644 index 000000000..8368e1886 --- /dev/null +++ b/eslint-plugin-drizzle/src/enforce-delete-with-where.ts @@ -0,0 +1,39 @@ +import type { TSESLint } from '@typescript-eslint/utils'; + +type MessageIds = 'enforceDeleteWithWhere'; + +let lastNodeName: string = ''; + +const deleteRule: TSESLint.RuleModule = { + defaultOptions: [], + meta: { + type: 'problem', + docs: { + description: 'Enforce that `delete` method is used with `where` to avoid deleting all the rows in a table.', + url: 'https://github.com/drizzle-team/eslint-plugin-drizzle', + }, + fixable: 'code', + messages: { + enforceDeleteWithWhere: 'Avoid deleting all the rows in a table. Use `db.delete(...).where(...)` instead.', + }, + schema: [], + }, + create(context) { + return { + MemberExpression: (node) => { + if (node.property.type === 'Identifier') { + if (node.property.name === 'delete' && lastNodeName !== 'where') { + context.report({ + node, + messageId: 'enforceDeleteWithWhere', + }); + } + lastNodeName = node.property.name; + } + return; + }, + }; + }, +}; + +export default deleteRule; diff --git a/eslint-plugin-drizzle/src/enforce-update-with-where.ts b/eslint-plugin-drizzle/src/enforce-update-with-where.ts new file mode 100644 index 000000000..a98176e77 --- /dev/null +++ b/eslint-plugin-drizzle/src/enforce-update-with-where.ts @@ -0,0 +1,47 @@ +import type { TSESLint } from '@typescript-eslint/utils'; + +type MessageIds = 'enforceUpdateWithWhere'; + +let lastNodeName: string = ''; + +const deleteRule: TSESLint.RuleModule = { + defaultOptions: [], + meta: { + type: 'problem', + docs: { + description: 'Enforce that `update` method is used with `where` to avoid deleting all the rows in a table.', + url: 'https://github.com/drizzle-team/eslint-plugin-drizzle', + }, + fixable: 'code', + messages: { + enforceUpdateWithWhere: + 'Avoid updating all the rows in a table. Use `db.update(...).set(...).where(...)` instead.', + }, + schema: [], + }, + create(context) { + return { + MemberExpression: (node) => { + if (node.property.type === 'Identifier') { + if ( + lastNodeName !== 'where' + && node.property.name === 'set' + && node.object.type === 'CallExpression' + && node.object.callee.type === 'MemberExpression' + && node.object.callee.property.type === 'Identifier' + && node.object.callee.property.name === 'update' + ) { + context.report({ + node, + messageId: 'enforceUpdateWithWhere', + }); + } + lastNodeName = node.property.name; + } + return; + }, + }; + }, +}; + +export default deleteRule; diff --git a/eslint-plugin-drizzle/src/index.ts b/eslint-plugin-drizzle/src/index.ts new file mode 100644 index 000000000..3f7af72b1 --- /dev/null +++ b/eslint-plugin-drizzle/src/index.ts @@ -0,0 +1,15 @@ +import type { TSESLint } from '@typescript-eslint/utils'; +import { name, version } from '../package.json'; +import all from './configs/all'; +import recommended from './configs/recommended'; +import deleteRule from './enforce-delete-with-where'; +import updateRule from './enforce-update-with-where'; + +export const rules = { + 'enforce-delete-with-where': deleteRule, + 'enforce-update-with-where': updateRule, +} satisfies Record>>; + +export const configs = { all, recommended }; + +export const meta = { name, version }; diff --git a/eslint-plugin-drizzle/tests/delete.test.ts b/eslint-plugin-drizzle/tests/delete.test.ts new file mode 100644 index 000000000..17ab6c847 --- /dev/null +++ b/eslint-plugin-drizzle/tests/delete.test.ts @@ -0,0 +1,39 @@ +// @ts-ignore +import { RuleTester } from "@typescript-eslint/rule-tester"; + +import myRule from "../src/enforce-delete-with-where"; + +const parserResolver = require.resolve("@typescript-eslint/parser"); + +const ruleTester = new RuleTester({ + parser: parserResolver, +}); + +ruleTester.run("my-rule", myRule, { + valid: [ + "const a = db.delete({}).where({});", + "delete db.something", + `dataSource + .delete() + .where()`, + ], + invalid: [ + { + code: "db.delete({})", + errors: [{ messageId: "enforceDeleteWithWhere" }], + }, + { + code: "const a = await db.delete({})", + errors: [{ messageId: "enforceDeleteWithWhere" }], + }, + { + code: "const a = db.delete({})", + errors: [{ messageId: "enforceDeleteWithWhere" }], + }, + { + code: `const a = database + .delete({})`, + errors: [{ messageId: "enforceDeleteWithWhere" }], + }, + ], +}); diff --git a/eslint-plugin-drizzle/tests/update.test.ts b/eslint-plugin-drizzle/tests/update.test.ts new file mode 100644 index 000000000..456a43f3e --- /dev/null +++ b/eslint-plugin-drizzle/tests/update.test.ts @@ -0,0 +1,46 @@ +// @ts-ignore +import { RuleTester } from "@typescript-eslint/rule-tester"; + +import myRule from "../src/enforce-update-with-where"; + +const parserResolver = require.resolve("@typescript-eslint/parser"); + +const ruleTester = new RuleTester({ + parser: parserResolver, +}); + +ruleTester.run("my-rule", myRule, { + valid: [ + "const a = db.update({}).set().where({});", + "const a = db.update();", + "update()", + `da + .update() + .set() + .where()`, + `dataSource + .update() + .set() + .where()`, + ], + invalid: [ + { + code: "db.update({}).set()", + errors: [{ messageId: "enforceUpdateWithWhere" }], + }, + { + code: "const a = await db.update({}).set()", + errors: [{ messageId: "enforceUpdateWithWhere" }], + }, + { + code: "const a = db.update({}).set", + errors: [{ messageId: "enforceUpdateWithWhere" }], + }, + { + code: `const a = database + .update({}) + .set()`, + errors: [{ messageId: "enforceUpdateWithWhere" }], + }, + ], +}); diff --git a/eslint-plugin-drizzle/tsconfig.json b/eslint-plugin-drizzle/tsconfig.json new file mode 100644 index 000000000..e11453ec3 --- /dev/null +++ b/eslint-plugin-drizzle/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "nodenext", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "resolveJsonModule": true, + "lib": [ + "esnext" + ], + "composite": false, + "incremental": false, + "skipLibCheck": true, + "outDir": "dist", + "module": "nodenext", + "target": "es6", + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true + }, + "include": [ + "src/**/*.ts", + "package.json", + ] +} diff --git a/eslint-plugin-drizzle/vitest.config.ts b/eslint-plugin-drizzle/vitest.config.ts new file mode 100644 index 000000000..2fa31886f --- /dev/null +++ b/eslint-plugin-drizzle/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + exclude: ['**/dist/**'], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15af08a5e..eff2bbd9b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -284,6 +284,27 @@ importers: specifier: ^7.2.2 version: 7.2.2 + eslint-plugin-drizzle: + devDependencies: + '@typescript-eslint/parser': + specifier: ^6.10.0 + version: 6.10.0(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/rule-tester': + specifier: ^6.10.0 + version: 6.10.0(@eslint/eslintrc@2.1.2)(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/utils': + specifier: ^6.10.0 + version: 6.10.0(eslint@8.53.0)(typescript@5.2.2) + eslint: + specifier: ^8.53.0 + version: 8.53.0 + typescript: + specifier: ^5.2.2 + version: 5.2.2(patch_hash=wmhs4olj6eveeldp6si4l46ssq) + vitest: + specifier: ^0.34.6 + version: 0.34.6 + integration-tests: dependencies: '@aws-sdk/client-rds-data': @@ -2106,6 +2127,16 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.53.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.53.0 + eslint-visitor-keys: 3.4.3 + dev: true + /@eslint-community/regexpp@4.9.0: resolution: {integrity: sha512-zJmuCWj2VLBt4c25CfBIbMZLGLyhkvs7LznyVX5HfpzeocThgIj5XQK4L+g3U36mMcx8bPMhGyPpwCATamC4jQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -2128,11 +2159,33 @@ packages: - supports-color dev: true + /@eslint/eslintrc@2.1.3: + resolution: {integrity: sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.22.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + /@eslint/js@8.50.0: resolution: {integrity: sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@eslint/js@8.53.0: + resolution: {integrity: sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /@gar/promisify@1.1.3: resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} requiresBuild: true @@ -2149,6 +2202,17 @@ packages: - supports-color dev: true + /@humanwhocodes/config-array@0.11.13: + resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 2.0.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + /@humanwhocodes/module-importer@1.0.1: resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} @@ -2158,6 +2222,10 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@humanwhocodes/object-schema@2.0.1: + resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} + dev: true + /@iarna/toml@2.2.5: resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} dev: false @@ -2174,6 +2242,13 @@ packages: wrap-ansi-cjs: /wrap-ansi@7.0.0 dev: true + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} @@ -2487,6 +2562,10 @@ packages: rollup: 3.27.2 dev: true + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + /@sinclair/typebox@0.29.6: resolution: {integrity: sha512-aX5IFYWlMa7tQ8xZr3b2gtVReCvg7f3LEhjir/JAjX2bJCMVJA5tIPv30wTD4KDfcwMd7DDYY3hFDeGmOgtrZQ==} dev: true @@ -2611,10 +2690,6 @@ packages: '@types/node': 20.8.7 dev: true - /@types/json-schema@7.0.12: - resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} - dev: true - /@types/json-schema@7.0.13: resolution: {integrity: sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==} dev: true @@ -2690,10 +2765,6 @@ packages: resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} dev: true - /@types/semver@7.5.0: - resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} - dev: true - /@types/semver@7.5.3: resolution: {integrity: sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==} dev: true @@ -2773,6 +2844,27 @@ packages: - typescript dev: true + /@typescript-eslint/parser@6.10.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 6.10.0 + '@typescript-eslint/types': 6.10.0 + '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.10.0 + debug: 4.3.4 + eslint: 8.53.0 + typescript: 5.2.2(patch_hash=wmhs4olj6eveeldp6si4l46ssq) + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.2.2): resolution: {integrity: sha512-TlutE+iep2o7R8Lf+yoer3zU6/0EAUc8QIBB3GYBc1KGz4c4TRm83xwXUZVPlZ6YCLss4r77jbu6j3sendJoiQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -2794,6 +2886,25 @@ packages: - supports-color dev: true + /@typescript-eslint/rule-tester@6.10.0(@eslint/eslintrc@2.1.2)(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-I0ZY+9ei73dlOuXwIYWsn/r/ue26Ygf4yEJPxeJRPI06YWDawmR1FI1dXL6ChAWVrmBQRvWep/1PxnV41zfcMA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@eslint/eslintrc': '>=2' + eslint: '>=8' + dependencies: + '@eslint/eslintrc': 2.1.2 + '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2) + '@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2) + ajv: 6.12.6 + eslint: 8.53.0 + lodash.merge: 4.6.2 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/scope-manager@5.62.0: resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2802,6 +2913,14 @@ packages: '@typescript-eslint/visitor-keys': 5.62.0 dev: true + /@typescript-eslint/scope-manager@6.10.0: + resolution: {integrity: sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.10.0 + '@typescript-eslint/visitor-keys': 6.10.0 + dev: true + /@typescript-eslint/scope-manager@6.7.3: resolution: {integrity: sha512-wOlo0QnEou9cHO2TdkJmzF7DFGvAKEnB82PuPNHpT8ZKKaZu6Bm63ugOTn9fXNJtvuDPanBc78lGUGGytJoVzQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -2835,6 +2954,11 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@typescript-eslint/types@6.10.0: + resolution: {integrity: sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==} + engines: {node: ^16.0.0 || >=18.0.0} + dev: true + /@typescript-eslint/types@6.7.3: resolution: {integrity: sha512-4g+de6roB2NFcfkZb439tigpAMnvEIg3rIjWQ+EM7IBaYt/CdJt6em9BJ4h4UpdgaBWdmx2iWsafHTrqmgIPNw==} engines: {node: ^16.0.0 || >=18.0.0} @@ -2861,6 +2985,27 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree@6.10.0(typescript@5.2.2): + resolution: {integrity: sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.10.0 + '@typescript-eslint/visitor-keys': 6.10.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + ts-api-utils: 1.0.3(typescript@5.2.2) + typescript: 5.2.2(patch_hash=wmhs4olj6eveeldp6si4l46ssq) + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/typescript-estree@6.7.3(typescript@5.2.2): resolution: {integrity: sha512-YLQ3tJoS4VxLFYHTw21oe1/vIZPRqAO91z6Uv0Ss2BKm/Ag7/RVQBcXTGcXhgJMdA4U+HrKuY5gWlJlvoaKZ5g==} engines: {node: ^16.0.0 || >=18.0.0} @@ -2889,8 +3034,8 @@ packages: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0) - '@types/json-schema': 7.0.12 - '@types/semver': 7.5.0 + '@types/json-schema': 7.0.13 + '@types/semver': 7.5.3 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2) @@ -2902,6 +3047,25 @@ packages: - typescript dev: true + /@typescript-eslint/utils@6.10.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0) + '@types/json-schema': 7.0.13 + '@types/semver': 7.5.3 + '@typescript-eslint/scope-manager': 6.10.0 + '@typescript-eslint/types': 6.10.0 + '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2) + eslint: 8.53.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/utils@6.7.3(eslint@8.50.0)(typescript@5.2.2): resolution: {integrity: sha512-vzLkVder21GpWRrmSR9JxGZ5+ibIUSudXlW52qeKpzUEQhRSmyZiVDDj3crAth7+5tmN1ulvgKaCU2f/bPRCzg==} engines: {node: ^16.0.0 || >=18.0.0} @@ -2929,6 +3093,14 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@typescript-eslint/visitor-keys@6.10.0: + resolution: {integrity: sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.10.0 + eslint-visitor-keys: 3.4.3 + dev: true + /@typescript-eslint/visitor-keys@6.7.3: resolution: {integrity: sha512-HEVXkU9IB+nk9o63CeICMHxFWbHWr3E1mpilIQBe9+7L/lH97rleFLVtYsfnWB+JVMaiFnEaxvknvmIzX+CqVg==} engines: {node: ^16.0.0 || >=18.0.0} @@ -2951,6 +3123,10 @@ packages: yargs: 16.2.0 dev: false + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + dev: true + /@vercel/postgres@0.3.0: resolution: {integrity: sha512-cOC+x6qMnN54B4y0Fh0DV5LJQp2M7puIKbehQBMutY/8/zpzh+oKaQmnZb2QHn489MGOQKyRLJLgHa2P8M085Q==} engines: {node: '>=14.6'} @@ -2967,6 +3143,14 @@ packages: '@vitest/utils': 0.31.4 chai: 4.3.7 + /@vitest/expect@0.34.6: + resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} + dependencies: + '@vitest/spy': 0.34.6 + '@vitest/utils': 0.34.6 + chai: 4.3.10 + dev: true + /@vitest/runner@0.31.4: resolution: {integrity: sha512-Wgm6UER+gwq6zkyrm5/wbpXGF+g+UBB78asJlFkIOwyse0pz8lZoiC6SW5i4gPnls/zUcPLWS7Zog0LVepXnpg==} dependencies: @@ -2975,6 +3159,14 @@ packages: p-limit: 4.0.0 pathe: 1.1.1 + /@vitest/runner@0.34.6: + resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} + dependencies: + '@vitest/utils': 0.34.6 + p-limit: 4.0.0 + pathe: 1.1.1 + dev: true + /@vitest/snapshot@0.31.4: resolution: {integrity: sha512-LemvNumL3NdWSmfVAMpXILGyaXPkZbG5tyl6+RQSdcHnTj6hvA49UAI8jzez9oQyE/FWLKRSNqTGzsHuk89LRA==} dependencies: @@ -2982,11 +3174,25 @@ packages: pathe: 1.1.1 pretty-format: 27.5.1 + /@vitest/snapshot@0.34.6: + resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==} + dependencies: + magic-string: 0.30.5 + pathe: 1.1.1 + pretty-format: 29.7.0 + dev: true + /@vitest/spy@0.31.4: resolution: {integrity: sha512-3ei5ZH1s3aqbEyftPAzSuunGICRuhE+IXOmpURFdkm5ybUADk+viyQfejNk6q8M5QGX8/EVKw+QWMEP3DTJDag==} dependencies: tinyspy: 2.1.1 + /@vitest/spy@0.34.6: + resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==} + dependencies: + tinyspy: 2.1.1 + dev: true + /@vitest/ui@0.31.4(vitest@0.31.4): resolution: {integrity: sha512-sKM16ITX6HrNFF+lNZ2AQAen4/6Bx2i6KlBfIvkUjcTgc5YII/j2ltcX14oCUv4EA0OTWGQuGhO3zDoAsTENGA==} peerDependencies: @@ -3008,6 +3214,14 @@ packages: loupe: 2.3.6 pretty-format: 27.5.1 + /@vitest/utils@0.34.6: + resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} + dependencies: + diff-sequences: 29.6.3 + loupe: 2.3.6 + pretty-format: 29.7.0 + dev: true + /abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} requiresBuild: true @@ -3639,6 +3853,19 @@ packages: nofilter: 3.1.0 dev: true + /chai@4.3.10: + resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.3 + get-func-name: 2.0.2 + loupe: 2.3.6 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + /chai@4.3.7: resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} engines: {node: '>=4'} @@ -3681,6 +3908,12 @@ packages: /check-error@1.0.2: resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: true + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -4095,6 +4328,11 @@ packages: resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} engines: {node: '>=8'} + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /diff@5.1.0: resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} engines: {node: '>=0.3.1'} @@ -4924,6 +5162,53 @@ packages: - supports-color dev: true + /eslint@8.53.0: + resolution: {integrity: sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0) + '@eslint-community/regexpp': 4.9.0 + '@eslint/eslintrc': 2.1.3 + '@eslint/js': 8.53.0 + '@humanwhocodes/config-array': 0.11.13 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.22.0 + graphemer: 1.4.0 + ignore: 5.2.4 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + /esm@3.2.25: resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} engines: {node: '>=6'} @@ -5383,6 +5668,10 @@ packages: /get-func-name@2.0.0: resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + /get-intrinsic@1.2.0: resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} dependencies: @@ -6320,6 +6609,13 @@ packages: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 + /magic-string@0.30.5: + resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -6592,6 +6888,15 @@ packages: pkg-types: 1.0.3 ufo: 1.1.2 + /mlly@1.4.2: + resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} + dependencies: + acorn: 8.10.0 + pathe: 1.1.1 + pkg-types: 1.0.3 + ufo: 1.3.1 + dev: true + /mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -7318,6 +7623,15 @@ packages: ansi-styles: 5.2.0 react-is: 17.0.2 + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + /pretty-ms@8.0.0: resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==} engines: {node: '>=14.16'} @@ -7418,6 +7732,10 @@ packages: /react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -8250,6 +8568,11 @@ packages: resolution: {integrity: sha512-paHQtnrlS1QZYKF/GnLoOM/DN9fqaGOFbCbxzAhwniySnzl9Ebk8w73/dd34DAhe/obUbPAOldTyYXQZxnPBPQ==} engines: {node: '>=14.0.0'} + /tinypool@0.7.0: + resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} + engines: {node: '>=14.0.0'} + dev: true + /tinyspy@2.1.1: resolution: {integrity: sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==} engines: {node: '>=14.0.0'} @@ -8578,6 +8901,10 @@ packages: /ufo@1.1.2: resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==} + /ufo@1.3.1: + resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==} + dev: true + /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: @@ -8726,6 +9053,27 @@ packages: - supports-color - terser + /vite-node@0.34.6(@types/node@20.8.7): + resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} + engines: {node: '>=v14.18.0'} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + mlly: 1.4.2 + pathe: 1.1.1 + picocolors: 1.0.0 + vite: 4.3.9(@types/node@20.8.7) + transitivePeerDependencies: + - '@types/node' + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite-tsconfig-paths@4.2.0(typescript@5.2.2)(vite@4.3.9): resolution: {integrity: sha512-jGpus0eUy5qbbMVGiTxCL1iB9ZGN6Bd37VGLJU39kTDD6ZfULTTb1bcc5IeTWqWJKiWV5YihCaibeASPiGi8kw==} peerDependencies: @@ -8775,6 +9123,39 @@ packages: optionalDependencies: fsevents: 2.3.3 + /vite@4.3.9(@types/node@20.8.7): + resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.8.7 + esbuild: 0.17.19 + postcss: 8.4.24 + rollup: 3.27.2 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /vitest@0.31.4(@vitest/ui@0.31.4): resolution: {integrity: sha512-GoV0VQPmWrUFOZSg3RpQAPN+LPmHg2/gxlMNJlyxJihkz6qReHDV6b0pPDcqFLNEPya4tWJ1pgwUNP9MLmUfvQ==} engines: {node: '>=v14.18.0'} @@ -8840,6 +9221,70 @@ packages: - supports-color - terser + /vitest@0.34.6: + resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} + engines: {node: '>=v14.18.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + happy-dom: '*' + jsdom: '*' + playwright: '*' + safaridriver: '*' + webdriverio: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + dependencies: + '@types/chai': 4.3.5 + '@types/chai-subset': 1.3.3 + '@types/node': 20.8.7 + '@vitest/expect': 0.34.6 + '@vitest/runner': 0.34.6 + '@vitest/snapshot': 0.34.6 + '@vitest/spy': 0.34.6 + '@vitest/utils': 0.34.6 + acorn: 8.10.0 + acorn-walk: 8.2.0 + cac: 6.7.14 + chai: 4.3.10 + debug: 4.3.4 + local-pkg: 0.4.3 + magic-string: 0.30.5 + pathe: 1.1.1 + picocolors: 1.0.0 + std-env: 3.3.3 + strip-literal: 1.0.1 + tinybench: 2.5.0 + tinypool: 0.7.0 + vite: 4.3.9(@types/node@20.8.7) + vite-node: 0.34.6(@types/node@20.8.7) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /web-streams-polyfill@3.2.1: resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} engines: {node: '>= 8'} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4e2fa3436..1c10dc1ec 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,3 +4,4 @@ packages: - drizzle-typebox - drizzle-valibot - integration-tests + - eslint-plugin-drizzle From b5c8fe9c1a4b2d4158c6edade48931bf4ada834c Mon Sep 17 00:00:00 2001 From: Angelelz Date: Tue, 14 Nov 2023 20:03:31 -0500 Subject: [PATCH 11/50] added to release feature branch and unpublish release branch workflows --- .github/workflows/release-feature-branch.yaml | 5 +++-- .github/workflows/unpublish-release-feature-branch.yaml | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-feature-branch.yaml b/.github/workflows/release-feature-branch.yaml index 3126e994a..f00ce0dcb 100644 --- a/.github/workflows/release-feature-branch.yaml +++ b/.github/workflows/release-feature-branch.yaml @@ -17,6 +17,7 @@ jobs: - drizzle-zod - drizzle-typebox - drizzle-valibot + - eslint-plugin-drizzle runs-on: ubuntu-20.04 permissions: contents: read @@ -91,8 +92,8 @@ jobs: is_version_published="$(npm view ${{ matrix.package }} versions --json | jq -r '.[] | select(. == "'$version'") | . == "'$version'"')" if [[ "$is_version_published" == "true" ]]; then - echo "\`${{ matrix.package }}@$version\` already published, adding tag \`$tag\`" >> $GITHUB_STEP_SUMMARY - npm dist-tag add ${{ matrix.package }}@$version $tag + echo "\`${{ matrix.package }}@ $version\` already published, adding tag \`$tag\`" >> $GITHUB_STEP_SUMMARY + npm dist-tag add ${{ matrix.package }}@ $version $tag else { echo "version=$version" diff --git a/.github/workflows/unpublish-release-feature-branch.yaml b/.github/workflows/unpublish-release-feature-branch.yaml index a821291e5..dd9f11420 100644 --- a/.github/workflows/unpublish-release-feature-branch.yaml +++ b/.github/workflows/unpublish-release-feature-branch.yaml @@ -12,6 +12,7 @@ jobs: - drizzle-zod - drizzle-typebox - drizzle-valibot + - eslint-plugin-drizzle runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 From 641b779acb40f194d065c4e9baeb1e7628e37cc7 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Tue, 14 Nov 2023 19:26:30 -0600 Subject: [PATCH 12/50] Refactor aggregate functions --- .../src/mysql-core/functions/aggregate.ts | 91 +++------------ .../src/pg-core/functions/aggregate.ts | 102 +++------------- drizzle-orm/src/sql/functions/aggregate.ts | 109 ++++++++++++++++++ drizzle-orm/src/sql/functions/index.ts | 1 + drizzle-orm/src/sql/index.ts | 1 + .../src/sqlite-core/functions/aggregate.ts | 85 ++++---------- 6 files changed, 166 insertions(+), 223 deletions(-) create mode 100644 drizzle-orm/src/sql/functions/aggregate.ts create mode 100644 drizzle-orm/src/sql/functions/index.ts diff --git a/drizzle-orm/src/mysql-core/functions/aggregate.ts b/drizzle-orm/src/mysql-core/functions/aggregate.ts index 8ce4b9404..f8e88b6c7 100644 --- a/drizzle-orm/src/mysql-core/functions/aggregate.ts +++ b/drizzle-orm/src/mysql-core/functions/aggregate.ts @@ -1,12 +1,7 @@ -import { is, entityKind } from '~/entity.ts'; import { MySqlColumn } from '../columns/index.ts'; -import { sql, type SQLWrapper, isSQLWrapper, type SQLChunk } from '~/sql/index.ts'; -import { MySqlBuiltInFunction } from './common.ts'; -import { type MaybeDistinct, getValueWithDistinct } from '~/distinct.ts'; - -export class MySqlAggregateFunction extends MySqlBuiltInFunction { - static readonly [entityKind]: string = 'MySqlAggregateFunction'; -} +import { count$, avg$, sum$, max$, min$ } from '~/sql/functions/index.ts'; +import type { SQLWrapper, SQL } from '~/sql/sql.ts'; +import type { MaybeDistinct } from '~/distinct.ts'; /** * Returns the number of values in `expression`. @@ -22,22 +17,8 @@ export class MySqlAggregateFunction extends MySqlBuiltInFunction * db.select({ value: count(distinct(employees.name)) }).from(employees) * ``` */ -export function count(expression?: MaybeDistinct | '*', config?: { - mode: T; -}): MySqlAggregateFunction { - const { value, distinct } = getValueWithDistinct(expression); - const chunks: SQLChunk[] = []; - - if (distinct) { - chunks.push(sql`distinct `); - } - chunks.push(isSQLWrapper(value) ? value : sql`*`); - - const sql_ = sql - .join([sql`count(`, ...chunks, sql`)` ]) - .mapWith(config?.mode === 'number' ? Number : BigInt); - - return new MySqlAggregateFunction(sql_) as any; +export function count(expression?: MaybeDistinct | '*'): SQL { + return count$('mysql', expression); } /** @@ -52,26 +33,8 @@ export function count(exp * db.select({ value: avg(distinct(employees.salary)) }).from(employees) * ``` */ -export function avg(expression: MaybeDistinct, config?: { - mode: T; -}): MySqlAggregateFunction<(T extends 'bigint' ? bigint : T extends 'number' ? number : string) | null> { - const { value, distinct } = getValueWithDistinct(expression); - const chunks: SQLChunk[] = []; - - if (distinct) { - chunks.push(sql`distinct `); - } - chunks.push(value); - - let sql_ = sql.join([sql`avg(`, ...chunks, sql`)`]); - - if (config?.mode === 'bigint') { - sql_ = sql_.mapWith((value: string) => BigInt(Number.parseInt(value))); - } else if (config?.mode === 'number') { - sql_ = sql_.mapWith(Number); - } - - return new MySqlAggregateFunction(sql_) as any; +export function avg(expression: MaybeDistinct): SQL { + return avg$('mysql', expression); } /** @@ -86,26 +49,8 @@ export function avg(expression: MaybeDistinct, config?: { - mode: T; -}): MySqlAggregateFunction<(T extends 'bigint' ? bigint : T extends 'number' ? number : string) | null> { - const { value, distinct } = getValueWithDistinct(expression); - const chunks: SQLChunk[] = []; - - if (distinct) { - chunks.push(sql`distinct `); - } - chunks.push(value); - - let sql_ = sql.join([sql`sum(`, ...chunks, sql`)`]); - - if (config?.mode === 'bigint') { - sql_ = sql_.mapWith((value: string) => BigInt(Number.parseInt(value))); - } else if (config?.mode === 'number') { - sql_ = sql_.mapWith(Number); - } - - return new MySqlAggregateFunction(sql_) as any; +export function sum(expression: MaybeDistinct): SQL { + return sum$('mysql', expression); } /** @@ -119,13 +64,10 @@ export function sum(expression: T): T extends MySqlColumn - ? MySqlAggregateFunction - : MySqlAggregateFunction + ? SQL + : SQL { - const sql_ = sql - .join([sql`max(`, expression, sql`)`]) - .mapWith(is(expression, MySqlColumn) ? expression : String); - return new MySqlAggregateFunction(sql_) as any; + return max$('mysql', expression) as any; } /** @@ -139,11 +81,8 @@ export function max(expression: T): T extends MySqlColumn * ``` */ export function min(expression: T): T extends MySqlColumn - ? MySqlAggregateFunction - : MySqlAggregateFunction + ? SQL + : SQL { - const sql_ = sql - .join([sql`min(`, expression, sql`)`]) - .mapWith(is(expression, MySqlColumn) ? expression : String); - return new MySqlAggregateFunction(sql_) as any; + return min$('mysql', expression) as any; } diff --git a/drizzle-orm/src/pg-core/functions/aggregate.ts b/drizzle-orm/src/pg-core/functions/aggregate.ts index e8cdb55dd..fb3d8dbf3 100644 --- a/drizzle-orm/src/pg-core/functions/aggregate.ts +++ b/drizzle-orm/src/pg-core/functions/aggregate.ts @@ -1,19 +1,7 @@ -import { is, entityKind } from '~/entity.ts'; import { PgColumn } from '../columns/index.ts'; -import { type SQL, sql, type SQLWrapper, isSQLWrapper, type SQLChunk } from '~/sql/index.ts'; -import { PgBuiltInFunction } from './common.ts'; -import { type MaybeDistinct, getValueWithDistinct } from '~/distinct.ts'; - -export class PgAggregateFunction extends PgBuiltInFunction { - static readonly [entityKind]: string = 'PgAggregateFunction'; - - filterWhere(where?: SQL | undefined): this { - if (where) { - this.sql.append(sql` filter (where ${where})`); - } - return this; - } -} +import { type AggregateFunction, count$, avg$, sum$, max$, min$ } from '~/sql/functions/index.ts'; +import type { SQLWrapper } from '~/sql/sql.ts'; +import type { MaybeDistinct } from '~/distinct.ts'; /** * Returns the number of values in `expression`. @@ -31,22 +19,8 @@ export class PgAggregateFunction extends PgBuiltInFunction { * db.select({ value: count().filterWhere(gt(employees.salary, 2000)) }).from(employees) * ``` */ -export function count(expression?: MaybeDistinct | '*', config?: { - mode: T; -}): PgAggregateFunction { - const { value, distinct } = getValueWithDistinct(expression); - const chunks: SQLChunk[] = []; - - if (distinct) { - chunks.push(sql`distinct `); - } - chunks.push(isSQLWrapper(value) ? value : sql`*`); - - const sql_ = sql - .join([sql`count(`, ...chunks, sql`)` ]) - .mapWith(config?.mode === 'number' ? Number : BigInt); - - return new PgAggregateFunction(sql_) as any; +export function count(expression?: MaybeDistinct | '*'): AggregateFunction { + return count$('pg', expression); } /** @@ -63,26 +37,8 @@ export function count(exp * db.select({ value: avg(employees.salary).filterWhere(gt(employees.salary, 2000)) }).from(employees) * ``` */ -export function avg(expression: MaybeDistinct, config?: { - mode: T; -}): PgAggregateFunction<(T extends 'bigint' ? bigint : T extends 'number' ? number : string) | null> { - const { value, distinct } = getValueWithDistinct(expression); - const chunks: SQLChunk[] = []; - - if (distinct) { - chunks.push(sql`distinct `); - } - chunks.push(value); - - let sql_ = sql.join([sql`avg(`, ...chunks, sql`)`]); - - if (config?.mode === 'bigint') { - sql_ = sql_.mapWith((value: string) => BigInt(Number.parseInt(value))); - } else if (config?.mode === 'number') { - sql_ = sql_.mapWith(Number); - } - - return new PgAggregateFunction(sql_) as any; +export function avg(expression: MaybeDistinct): AggregateFunction { + return avg$('pg', expression); } /** @@ -99,26 +55,8 @@ export function avg(expression: MaybeDistinct, config?: { - mode: T; -}): PgAggregateFunction<(T extends 'bigint' ? bigint : T extends 'number' ? number : string) | null> { - const { value, distinct } = getValueWithDistinct(expression); - const chunks: SQLChunk[] = []; - - if (distinct) { - chunks.push(sql`distinct `); - } - chunks.push(value); - - let sql_ = sql.join([sql`sum(`, ...chunks, sql`)`]); - - if (config?.mode === 'bigint') { - sql_ = sql_.mapWith((value: string) => BigInt(Number.parseInt(value))); - } else if (config?.mode === 'number') { - sql_ = sql_.mapWith(Number); - } - - return new PgAggregateFunction(sql_) as any; +export function sum(expression: MaybeDistinct): AggregateFunction { + return sum$('pg', expression); } /** @@ -129,16 +67,15 @@ export function sum(expression: T): T extends PgColumn - ? PgAggregateFunction - : PgAggregateFunction + ? AggregateFunction + : AggregateFunction { - const sql_ = sql - .join([sql`max(`, expression, sql`)`]) - .mapWith(is(expression, PgColumn) ? expression : String); - return new PgAggregateFunction(sql_) as any; + return max$('pg', expression) as any; } /** @@ -149,14 +86,13 @@ export function max(expression: T): T extends PgColumn * ```ts * // The employee with the lowest salary * db.select({ value: min(employees.salary) }).from(employees) + * // The employee with the lowest salary but that's greater than $1,000 + * db.select({ value: min(employees.salary).filterWhere(gt(employees.salary, 1000)) }).from(employees) * ``` */ export function min(expression: T): T extends PgColumn - ? PgAggregateFunction - : PgAggregateFunction + ? AggregateFunction + : AggregateFunction { - const sql_ = sql - .join([sql`min(`, expression, sql`)`]) - .mapWith(is(expression, PgColumn) ? expression : String); - return new PgAggregateFunction(sql_) as any; + return min$('pg', expression) as any; } diff --git a/drizzle-orm/src/sql/functions/aggregate.ts b/drizzle-orm/src/sql/functions/aggregate.ts new file mode 100644 index 000000000..fe0b2278d --- /dev/null +++ b/drizzle-orm/src/sql/functions/aggregate.ts @@ -0,0 +1,109 @@ +import { is, entityKind } from '~/entity.ts'; +import { SQL, sql, type SQLWrapper, type SQLChunk, isSQLWrapper, type DriverValueDecoder, type GetDecoderResult } from '../sql.ts'; +import { type MaybeDistinct, getValueWithDistinct } from '~/distinct.ts'; +import { PgColumn } from '~/pg-core/columns/common.ts'; +import { MySqlColumn } from '~/mysql-core/columns/common.ts'; +import { SQLiteColumn } from '~/sqlite-core/columns/common.ts'; +import type { Dialect } from '~/column-builder.ts'; +import type { Column } from '~/column.ts'; + +export interface AggregateFunction extends SQL { + mapWith< + TDecoder extends + | DriverValueDecoder + | DriverValueDecoder['mapFromDriverValue'], + >(decoder: TDecoder): AggregateFunction> +} +export class AggregateFunction extends SQL { + static readonly [entityKind]: string = 'AggregateFunction'; + + constructor(sql: SQL) { + super(sql.queryChunks); + } + + filterWhere(where?: SQL | undefined): this { + if (where) { + this.append(sql` filter (where ${where})`); + } + return this; + } +} + +/** @internal */ +export function count$(dialect: T, expression?: MaybeDistinct | '*'): T extends 'pg' + ? AggregateFunction + : T extends 'mysql' + ? SQL + : AggregateFunction +{ + const { value, distinct } = getValueWithDistinct(expression); + const chunks: SQLChunk[] = []; + + if (distinct) { + chunks.push(sql`distinct `); + } + chunks.push(isSQLWrapper(value) ? value : sql`*`); + + let fn = sql.join([sql`count(`, ...chunks, sql`)` ]); + fn = (dialect === 'mysql' ? fn : new AggregateFunction(fn)) + .mapWith(dialect === 'sqlite' ? Number : BigInt); + return fn as any; +} + +/** @internal */ +export function avg$(dialect: T, expression: MaybeDistinct): T extends 'mysql' + ? SQL + : AggregateFunction +{ + const { value, distinct } = getValueWithDistinct(expression); + const chunks: SQLChunk[] = []; + + if (distinct) { + chunks.push(sql`distinct `); + } + chunks.push(value); + + let fn = sql.join([sql`avg(`, ...chunks, sql`)`]) + fn = (dialect === 'mysql' ? fn : new AggregateFunction(fn)).mapWith(String); + return fn as any; +} + +/** @internal */ +export function sum$(dialect: T, expression: MaybeDistinct): T extends 'mysql' + ? SQL + : AggregateFunction +{ + const { value, distinct } = getValueWithDistinct(expression); + const chunks: SQLChunk[] = []; + + if (distinct) { + chunks.push(sql`distinct `); + } + chunks.push(value); + + let fn = sql.join([sql`sum(`, ...chunks, sql`)`]); + fn = (dialect === 'mysql' ? fn : new AggregateFunction(fn)).mapWith(String); + return fn as any; +} + +/** @internal */ +export function max$(dialect: T1, expression: T2): T2 extends Column + ? (T1 extends 'mysql' ? SQL : AggregateFunction) + : (T1 extends 'mysql' ? SQL : AggregateFunction) +{ + let fn = sql.join([sql`max(`, expression, sql`)`]) + fn = (dialect === 'mysql' ? fn : new AggregateFunction(fn)) + .mapWith(is(expression, PgColumn) || is(expression, MySqlColumn) || is(expression, SQLiteColumn) ? expression : String); + return fn as any; +} + +/** @internal */ +export function min$(dialect: T1, expression: T2): T2 extends Column + ? (T1 extends 'mysql' ? SQL : AggregateFunction) + : (T1 extends 'mysql' ? SQL : AggregateFunction) +{ + let fn = sql.join([sql`min(`, expression, sql`)`]); + fn = (dialect === 'mysql' ? fn : new AggregateFunction(fn)) + .mapWith(is(expression, PgColumn) || is(expression, MySqlColumn) || is(expression, SQLiteColumn) ? expression : String); + return fn as any; +} diff --git a/drizzle-orm/src/sql/functions/index.ts b/drizzle-orm/src/sql/functions/index.ts new file mode 100644 index 000000000..64ed5f04f --- /dev/null +++ b/drizzle-orm/src/sql/functions/index.ts @@ -0,0 +1 @@ +export * from './aggregate.ts'; diff --git a/drizzle-orm/src/sql/index.ts b/drizzle-orm/src/sql/index.ts index ec7f9ed76..19d77cd9b 100644 --- a/drizzle-orm/src/sql/index.ts +++ b/drizzle-orm/src/sql/index.ts @@ -1,2 +1,3 @@ export * from './expressions/index.ts'; export * from './sql.ts'; +export * from './functions/index.ts'; diff --git a/drizzle-orm/src/sqlite-core/functions/aggregate.ts b/drizzle-orm/src/sqlite-core/functions/aggregate.ts index 705bf367c..4a47a8fa0 100644 --- a/drizzle-orm/src/sqlite-core/functions/aggregate.ts +++ b/drizzle-orm/src/sqlite-core/functions/aggregate.ts @@ -1,19 +1,7 @@ -import { is, entityKind } from '~/entity.ts'; import { SQLiteColumn } from '../columns/index.ts'; -import { type SQL, sql, type SQLWrapper, isSQLWrapper, type SQLChunk } from '~/sql/index.ts'; -import { SQLiteBuiltInFunction } from './common.ts'; -import { type MaybeDistinct, getValueWithDistinct } from '~/distinct.ts'; - -export class SQLiteAggregateFunction extends SQLiteBuiltInFunction { - static readonly [entityKind]: string = 'SQLiteAggregateFunction'; - - filterWhere(where?: SQL | undefined): this { - if (where) { - this.sql.append(sql` filter (where ${where})`); - } - return this; - } -} +import { AggregateFunction, count$, avg$, sum$, max$, min$ } from '~/sql/functions/index.ts'; +import { type SQLWrapper, type SQLChunk, sql } from '~/sql/sql.ts'; +import { getValueWithDistinct, type MaybeDistinct } from '~/distinct.ts'; /** * Returns the number of values in `expression`. @@ -31,17 +19,8 @@ export class SQLiteAggregateFunction extends SQLiteBuiltInFunction< * db.select({ value: count().filterWhere(gt(employees.salary, 2000)) }).from(employees) * ``` */ -export function count(expression?: MaybeDistinct | '*'): SQLiteAggregateFunction { - const { value, distinct } = getValueWithDistinct(expression); - const chunks: SQLChunk[] = []; - - if (distinct) { - chunks.push(sql`distinct `); - } - chunks.push(isSQLWrapper(value) ? value : sql`*`); - - const sql_ = sql.join([sql`count(`, ...chunks, sql`)` ]).mapWith(Number); - return new SQLiteAggregateFunction(sql_); +export function count(expression?: MaybeDistinct | '*'): AggregateFunction { + return count$('sqlite', expression); } /** @@ -58,17 +37,8 @@ export function count(expression?: MaybeDistinct | '*'): SQLiteAggre * db.select({ value: avg(employees.salary).filterWhere(gt(employees.salary, 2000)) }).from(employees) * ``` */ -export function avg(expression: MaybeDistinct): SQLiteAggregateFunction { - const { value, distinct } = getValueWithDistinct(expression); - const chunks: SQLChunk[] = []; - - if (distinct) { - chunks.push(sql`distinct `); - } - chunks.push(value); - - const sql_ = sql.join([sql`avg(`, ...chunks, sql`)`]).mapWith(Number); - return new SQLiteAggregateFunction(sql_); +export function avg(expression: MaybeDistinct): AggregateFunction { + return avg$('sqlite', expression); } /** @@ -87,17 +57,8 @@ export function avg(expression: MaybeDistinct): SQLiteAggregateFunct * * @see total for a function with the same purpose that's not part of the SQL standard and always returns a number */ -export function sum(expression: MaybeDistinct): SQLiteAggregateFunction { - const { value, distinct } = getValueWithDistinct(expression); - const chunks: SQLChunk[] = []; - - if (distinct) { - chunks.push(sql`distinct `); - } - chunks.push(value); - - const sql_ = sql.join([sql`sum(`, ...chunks, sql`)`]).mapWith(Number); - return new SQLiteAggregateFunction(sql_); +export function sum(expression: MaybeDistinct): AggregateFunction { + return sum$('sqlite', expression); } /** @@ -116,8 +77,8 @@ export function sum(expression: MaybeDistinct): SQLiteAggregateFunct * * @see sum for a function with the same purpose that's part of the SQL standard and can return `null` */ -export function total(expression: MaybeDistinct): SQLiteAggregateFunction { - const { value, distinct } = getValueWithDistinct(expression); +export function total(expression: MaybeDistinct): AggregateFunction { + const { value, distinct } = getValueWithDistinct(expression); const chunks: SQLChunk[] = []; if (distinct) { @@ -125,8 +86,8 @@ export function total(expression: MaybeDistinct): SQLiteAggregateFun } chunks.push(value); - const sql_ = sql.join([sql`total(`, ...chunks, sql`)`]).mapWith(Number); - return new SQLiteAggregateFunction(sql_); + const fn = sql.join([sql`total(`, ...chunks, sql`)`]); + return new AggregateFunction(fn).mapWith(String); } /** @@ -137,16 +98,15 @@ export function total(expression: MaybeDistinct): SQLiteAggregateFun * ```ts * // The employee with the highest salary * db.select({ value: max(employees.salary) }).from(employees) + * // The employee with the highest salary but that's less than $2,000 + * db.select({ value: max(employees.salary).filterWhere(lt(employees.salary, 2000)) }).from(employees) * ``` */ export function max(expression: T): T extends SQLiteColumn - ? SQLiteAggregateFunction - : SQLiteAggregateFunction + ? AggregateFunction + : AggregateFunction { - const sql_ = sql - .join([sql`max(`, expression, sql`)`]) - .mapWith(is(expression, SQLiteColumn) ? expression : String) - return new SQLiteAggregateFunction(sql_) as any; + return max$('sqlite', expression) as any; } /** @@ -160,11 +120,8 @@ export function max(expression: T): T extends SQLiteColumn * ``` */ export function min(expression: T): T extends SQLiteColumn - ? SQLiteAggregateFunction - : SQLiteAggregateFunction + ? AggregateFunction + : AggregateFunction { - const sql_ = sql - .join([sql`min(`, expression, sql`)`]) - .mapWith(is(expression, SQLiteColumn) ? expression : String); - return new SQLiteAggregateFunction(sql_) as any; + return min$('sqlite', expression) as any; } From 73c7af917280056af782bbe6b868d7e2ceecb2dc Mon Sep 17 00:00:00 2001 From: Angelelz Date: Wed, 15 Nov 2023 17:52:33 +0000 Subject: [PATCH 13/50] Added cpy-cli for a crossplatform build --- eslint-plugin-drizzle/package-lock.json | 944 ++++++++++++++++++++++++ eslint-plugin-drizzle/package.json | 6 +- pnpm-lock.yaml | 10 +- 3 files changed, 952 insertions(+), 8 deletions(-) create mode 100644 eslint-plugin-drizzle/package-lock.json diff --git a/eslint-plugin-drizzle/package-lock.json b/eslint-plugin-drizzle/package-lock.json new file mode 100644 index 000000000..f337527f6 --- /dev/null +++ b/eslint-plugin-drizzle/package-lock.json @@ -0,0 +1,944 @@ +{ + "name": "eslint-plugin-drizzle", + "version": "0.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "eslint-plugin-drizzle", + "version": "0.2.0", + "license": "Apache-2.0", + "devDependencies": { + "@typescript-eslint/parser": "^6.10.0", + "@typescript-eslint/rule-tester": "^6.10.0", + "@typescript-eslint/utils": "^6.10.0", + "cpy-cli": "^5.0.0", + "eslint": "^8.53.0", + "typescript": "^5.2.2", + "vitest": "^0.34.6" + }, + "peerDependencies": { + "eslint": ">=8.0.0" + } + }, + "../node_modules/.pnpm/@typescript-eslint+parser@6.10.0_eslint@8.53.0_typescript@5.2.2/node_modules/@typescript-eslint/parser": { + "version": "6.10.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", + "debug": "^4.3.4" + }, + "devDependencies": { + "@types/glob": "*", + "downlevel-dts": "*", + "glob": "*", + "jest": "29.7.0", + "prettier": "^3.0.3", + "rimraf": "*", + "typescript": "*" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "../node_modules/.pnpm/@typescript-eslint+rule-tester@6.10.0_@eslint+eslintrc@2.1.3_eslint@8.53.0_typescript@5.2.2/node_modules/@typescript-eslint/rule-tester": { + "version": "6.10.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/utils": "6.10.0", + "ajv": "^6.10.0", + "lodash.merge": "4.6.2", + "semver": "^7.5.4" + }, + "devDependencies": { + "@types/lodash.merge": "4.6.8", + "@typescript-eslint/parser": "6.10.0", + "chai": "^4.3.7", + "mocha": "^10.0.0", + "sinon": "^16.0.0", + "source-map-support": "^0.5.21" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@eslint/eslintrc": ">=2", + "eslint": ">=8" + } + }, + "../node_modules/.pnpm/@typescript-eslint+utils@6.10.0_eslint@8.53.0_typescript@5.2.2/node_modules/@typescript-eslint/utils": { + "version": "6.10.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/typescript-estree": "6.10.0", + "semver": "^7.5.4" + }, + "devDependencies": { + "@typescript-eslint/parser": "6.10.0", + "downlevel-dts": "*", + "jest": "29.7.0", + "prettier": "^3.0.3", + "rimraf": "*", + "typescript": "*" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "../node_modules/.pnpm/eslint@8.53.0/node_modules/eslint": { + "version": "8.53.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "devDependencies": { + "@babel/core": "^7.4.3", + "@babel/preset-env": "^7.4.3", + "@wdio/browser-runner": "^8.14.6", + "@wdio/cli": "^8.14.6", + "@wdio/concise-reporter": "^8.14.0", + "@wdio/globals": "^8.14.6", + "@wdio/mocha-framework": "^8.14.0", + "babel-loader": "^8.0.5", + "c8": "^7.12.0", + "chai": "^4.0.1", + "cheerio": "^0.22.0", + "common-tags": "^1.8.0", + "core-js": "^3.1.3", + "ejs": "^3.0.2", + "eslint": "file:.", + "eslint-config-eslint": "file:packages/eslint-config-eslint", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-eslint-plugin": "^5.1.0", + "eslint-plugin-internal-rules": "file:tools/internal-rules", + "eslint-plugin-jsdoc": "^46.2.5", + "eslint-plugin-n": "^16.0.0", + "eslint-plugin-unicorn": "^42.0.0", + "eslint-release": "^3.2.0", + "eslump": "^3.0.0", + "esprima": "^4.0.1", + "fast-glob": "^3.2.11", + "fs-teardown": "^0.1.3", + "glob": "^7.1.6", + "got": "^11.8.3", + "gray-matter": "^4.0.3", + "lint-staged": "^11.0.0", + "load-perf": "^0.2.0", + "markdownlint": "^0.25.1", + "markdownlint-cli": "^0.31.1", + "marked": "^4.0.8", + "memfs": "^3.0.1", + "mocha": "^8.3.2", + "mocha-junit-reporter": "^2.0.0", + "node-polyfill-webpack-plugin": "^1.0.3", + "npm-license": "^0.3.3", + "pirates": "^4.0.5", + "progress": "^2.0.3", + "proxyquire": "^2.0.1", + "recast": "^0.20.4", + "regenerator-runtime": "^0.13.2", + "rollup-plugin-node-polyfills": "^0.2.1", + "semver": "^7.5.3", + "shelljs": "^0.8.2", + "sinon": "^11.0.0", + "vite-plugin-commonjs": "^0.8.2", + "webdriverio": "^8.14.6", + "webpack": "^5.23.0", + "webpack-cli": "^4.5.0", + "yorkie": "^2.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/.pnpm/typescript@5.2.2_patch_hash=wmhs4olj6eveeldp6si4l46ssq/node_modules/typescript": { + "version": "5.2.2", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "devDependencies": { + "@esfx/canceltoken": "^1.0.0", + "@octokit/rest": "^19.0.13", + "@types/chai": "^4.3.4", + "@types/fs-extra": "^9.0.13", + "@types/glob": "^8.1.0", + "@types/microsoft__typescript-etw": "^0.1.1", + "@types/minimist": "^1.2.2", + "@types/mocha": "^10.0.1", + "@types/ms": "^0.7.31", + "@types/node": "latest", + "@types/source-map-support": "^0.5.6", + "@types/which": "^2.0.1", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "@typescript-eslint/utils": "^6.0.0", + "azure-devops-node-api": "^12.0.0", + "c8": "^7.14.0", + "chai": "^4.3.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "del": "^6.1.1", + "diff": "^5.1.0", + "esbuild": "^0.18.1", + "eslint": "^8.22.0", + "eslint-formatter-autolinkable-stylish": "^1.2.0", + "eslint-plugin-local": "^1.0.0", + "eslint-plugin-no-null": "^1.0.2", + "eslint-plugin-simple-import-sort": "^10.0.0", + "fast-xml-parser": "^4.0.11", + "fs-extra": "^9.1.0", + "glob": "^8.1.0", + "hereby": "^1.6.4", + "jsonc-parser": "^3.2.0", + "minimist": "^1.2.8", + "mocha": "^10.2.0", + "mocha-fivemat-progress-reporter": "^0.1.0", + "ms": "^2.1.3", + "node-fetch": "^3.2.10", + "source-map-support": "^0.5.21", + "tslib": "^2.5.0", + "typescript": "^5.0.2", + "which": "^2.0.2" + }, + "engines": { + "node": ">=14.17" + } + }, + "../node_modules/.pnpm/vitest@0.34.6/node_modules/vitest": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^4.3.5", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.34.6", + "@vitest/runner": "0.34.6", + "@vitest/snapshot": "0.34.6", + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "acorn": "^8.9.0", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "strip-literal": "^1.0.1", + "tinybench": "^2.5.0", + "tinypool": "^0.7.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.6", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "devDependencies": { + "@ampproject/remapping": "^2.2.1", + "@antfu/install-pkg": "^0.1.1", + "@edge-runtime/vm": "3.0.3", + "@sinonjs/fake-timers": "^11.0.0", + "@types/diff": "^5.0.3", + "@types/estree": "^1.0.1", + "@types/istanbul-lib-coverage": "^2.0.4", + "@types/istanbul-reports": "^3.0.1", + "@types/jsdom": "^21.1.1", + "@types/micromatch": "^4.0.2", + "@types/prompts": "^2.4.4", + "@types/sinonjs__fake-timers": "^8.1.2", + "birpc": "0.2.12", + "chai-subset": "^1.6.0", + "cli-truncate": "^3.1.0", + "event-target-polyfill": "^0.0.3", + "execa": "^7.1.1", + "expect-type": "^0.16.0", + "fast-glob": "^3.3.0", + "find-up": "^6.3.0", + "flatted": "^3.2.7", + "get-tsconfig": "^4.6.2", + "happy-dom": "^9.20.3", + "jsdom": "^22.1.0", + "log-update": "^5.0.1", + "micromatch": "^4.0.5", + "mlly": "^1.4.0", + "p-limit": "^4.0.0", + "pkg-types": "^1.0.3", + "playwright": "^1.35.1", + "pretty-format": "^29.5.0", + "prompts": "^2.4.2", + "safaridriver": "^0.0.5", + "strip-ansi": "^7.1.0", + "webdriverio": "^8.11.2", + "ws": "^8.13.0" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*", + "playwright": "*", + "safaridriver": "*", + "webdriverio": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true + } + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@typescript-eslint/parser": { + "resolved": "../node_modules/.pnpm/@typescript-eslint+parser@6.10.0_eslint@8.53.0_typescript@5.2.2/node_modules/@typescript-eslint/parser", + "link": true + }, + "node_modules/@typescript-eslint/rule-tester": { + "resolved": "../node_modules/.pnpm/@typescript-eslint+rule-tester@6.10.0_@eslint+eslintrc@2.1.3_eslint@8.53.0_typescript@5.2.2/node_modules/@typescript-eslint/rule-tester", + "link": true + }, + "node_modules/@typescript-eslint/utils": { + "resolved": "../node_modules/.pnpm/@typescript-eslint+utils@6.10.0_eslint@8.53.0_typescript@5.2.2/node_modules/@typescript-eslint/utils", + "link": true + }, + "node_modules/aggregate-error": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", + "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", + "dev": true, + "dependencies": { + "clean-stack": "^4.0.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/arrify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", + "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", + "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cp-file": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-10.0.0.tgz", + "integrity": "sha512-vy2Vi1r2epK5WqxOLnskeKeZkdZvTKfFZQCplE3XWsP+SUJyd5XAUFC9lFgTjjXJF2GMne/UML14iEmkAaDfFg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.10", + "nested-error-stacks": "^2.1.1", + "p-event": "^5.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cpy": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cpy/-/cpy-10.1.0.tgz", + "integrity": "sha512-VC2Gs20JcTyeQob6UViBLnyP0bYHkBh6EiKzot9vi2DmeGlFT9Wd7VG3NBrkNx/jYvFBeyDOMMHdHQhbtKLgHQ==", + "dev": true, + "dependencies": { + "arrify": "^3.0.0", + "cp-file": "^10.0.0", + "globby": "^13.1.4", + "junk": "^4.0.1", + "micromatch": "^4.0.5", + "nested-error-stacks": "^2.1.1", + "p-filter": "^3.0.0", + "p-map": "^6.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cpy-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cpy-cli/-/cpy-cli-5.0.0.tgz", + "integrity": "sha512-fb+DZYbL9KHc0BC4NYqGRrDIJZPXUmjjtqdw4XRRg8iV8dIfghUX/WiL+q4/B/KFTy3sK6jsbUhBaz0/Hxg7IQ==", + "dev": true, + "dependencies": { + "cpy": "^10.1.0", + "meow": "^12.0.1" + }, + "bin": { + "cpy": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "resolved": "../node_modules/.pnpm/eslint@8.53.0/node_modules/eslint", + "link": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/junk": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", + "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/nested-error-stacks": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", + "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==", + "dev": true + }, + "node_modules/p-event": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-5.0.1.tgz", + "integrity": "sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==", + "dev": true, + "dependencies": { + "p-timeout": "^5.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-3.0.0.tgz", + "integrity": "sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg==", + "dev": true, + "dependencies": { + "p-map": "^5.1.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-filter/node_modules/p-map": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz", + "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==", + "dev": true, + "dependencies": { + "aggregate-error": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-6.0.0.tgz", + "integrity": "sha512-T8BatKGY+k5rU+Q/GTYgrEf2r4xRMevAN5mtXc2aPc4rS1j3s+vWTaO2Wag94neXuCAUAs8cxBL9EeB5EA6diw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", + "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "resolved": "../node_modules/.pnpm/typescript@5.2.2_patch_hash=wmhs4olj6eveeldp6si4l46ssq/node_modules/typescript", + "link": true + }, + "node_modules/vitest": { + "resolved": "../node_modules/.pnpm/vitest@0.34.6/node_modules/vitest", + "link": true + } + } +} diff --git a/eslint-plugin-drizzle/package.json b/eslint-plugin-drizzle/package.json index 1e81096ec..b9c46b991 100644 --- a/eslint-plugin-drizzle/package.json +++ b/eslint-plugin-drizzle/package.json @@ -5,7 +5,7 @@ "main": "src/index.js", "scripts": { "test": "vitest run", - "build": "tsc -b && cp ./readme.md ./dist/readme.md", + "build": "tsc -b && pnpm cpy readme.md dist/", "pack": "(cd dist && npm pack --pack-destination ..) && rm -f package.tgz && mv *.tgz package.tgz", "publish": "npm publish package.tgz" }, @@ -21,11 +21,11 @@ "url": "git+https://github.com/drizzle-team/drizzle-orm.git" }, "license": "Apache-2.0", - "dependencies": {}, "devDependencies": { - "@typescript-eslint/utils": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", "@typescript-eslint/rule-tester": "^6.10.0", + "@typescript-eslint/utils": "^6.10.0", + "cpy-cli": "^5.0.0", "eslint": "^8.53.0", "typescript": "^5.2.2", "vitest": "^0.34.6" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eff2bbd9b..3146e0bd6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -291,7 +291,7 @@ importers: version: 6.10.0(eslint@8.53.0)(typescript@5.2.2) '@typescript-eslint/rule-tester': specifier: ^6.10.0 - version: 6.10.0(@eslint/eslintrc@2.1.2)(eslint@8.53.0)(typescript@5.2.2) + version: 6.10.0(@eslint/eslintrc@2.1.3)(eslint@8.53.0)(typescript@5.2.2) '@typescript-eslint/utils': specifier: ^6.10.0 version: 6.10.0(eslint@8.53.0)(typescript@5.2.2) @@ -2886,14 +2886,14 @@ packages: - supports-color dev: true - /@typescript-eslint/rule-tester@6.10.0(@eslint/eslintrc@2.1.2)(eslint@8.53.0)(typescript@5.2.2): + /@typescript-eslint/rule-tester@6.10.0(@eslint/eslintrc@2.1.3)(eslint@8.53.0)(typescript@5.2.2): resolution: {integrity: sha512-I0ZY+9ei73dlOuXwIYWsn/r/ue26Ygf4yEJPxeJRPI06YWDawmR1FI1dXL6ChAWVrmBQRvWep/1PxnV41zfcMA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: '@eslint/eslintrc': '>=2' eslint: '>=8' dependencies: - '@eslint/eslintrc': 2.1.2 + '@eslint/eslintrc': 2.1.3 '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2) '@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2) ajv: 6.12.6 @@ -4288,7 +4288,7 @@ packages: resolution: {integrity: sha512-tQbV/4u5WVB8HMJr08pgw0b6nG4RGt/tj+7Numvq+zqcvUFeMaIWWOUFltiU+6go8BSO2/ogsB4EasDaj0y68Q==} engines: {node: '>=14.16'} dependencies: - globby: 13.1.3 + globby: 13.1.4 graceful-fs: 4.2.11 is-glob: 4.0.3 is-path-cwd: 3.0.0 @@ -5827,7 +5827,7 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: dir-glob: 3.0.1 - fast-glob: 3.2.12 + fast-glob: 3.3.1 ignore: 5.2.4 merge2: 1.4.1 slash: 4.0.0 From 6aa0cd76cc0cd2afb0cc8dbdd1d185a0799fd818 Mon Sep 17 00:00:00 2001 From: Angelelz Date: Wed, 15 Nov 2023 18:22:01 +0000 Subject: [PATCH 14/50] fixed lock file --- eslint-plugin-drizzle/package-lock.json | 944 ------------------------ pnpm-lock.yaml | 17 + 2 files changed, 17 insertions(+), 944 deletions(-) delete mode 100644 eslint-plugin-drizzle/package-lock.json diff --git a/eslint-plugin-drizzle/package-lock.json b/eslint-plugin-drizzle/package-lock.json deleted file mode 100644 index f337527f6..000000000 --- a/eslint-plugin-drizzle/package-lock.json +++ /dev/null @@ -1,944 +0,0 @@ -{ - "name": "eslint-plugin-drizzle", - "version": "0.2.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "eslint-plugin-drizzle", - "version": "0.2.0", - "license": "Apache-2.0", - "devDependencies": { - "@typescript-eslint/parser": "^6.10.0", - "@typescript-eslint/rule-tester": "^6.10.0", - "@typescript-eslint/utils": "^6.10.0", - "cpy-cli": "^5.0.0", - "eslint": "^8.53.0", - "typescript": "^5.2.2", - "vitest": "^0.34.6" - }, - "peerDependencies": { - "eslint": ">=8.0.0" - } - }, - "../node_modules/.pnpm/@typescript-eslint+parser@6.10.0_eslint@8.53.0_typescript@5.2.2/node_modules/@typescript-eslint/parser": { - "version": "6.10.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "6.10.0", - "@typescript-eslint/types": "6.10.0", - "@typescript-eslint/typescript-estree": "6.10.0", - "@typescript-eslint/visitor-keys": "6.10.0", - "debug": "^4.3.4" - }, - "devDependencies": { - "@types/glob": "*", - "downlevel-dts": "*", - "glob": "*", - "jest": "29.7.0", - "prettier": "^3.0.3", - "rimraf": "*", - "typescript": "*" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "../node_modules/.pnpm/@typescript-eslint+rule-tester@6.10.0_@eslint+eslintrc@2.1.3_eslint@8.53.0_typescript@5.2.2/node_modules/@typescript-eslint/rule-tester": { - "version": "6.10.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "6.10.0", - "@typescript-eslint/utils": "6.10.0", - "ajv": "^6.10.0", - "lodash.merge": "4.6.2", - "semver": "^7.5.4" - }, - "devDependencies": { - "@types/lodash.merge": "4.6.8", - "@typescript-eslint/parser": "6.10.0", - "chai": "^4.3.7", - "mocha": "^10.0.0", - "sinon": "^16.0.0", - "source-map-support": "^0.5.21" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@eslint/eslintrc": ">=2", - "eslint": ">=8" - } - }, - "../node_modules/.pnpm/@typescript-eslint+utils@6.10.0_eslint@8.53.0_typescript@5.2.2/node_modules/@typescript-eslint/utils": { - "version": "6.10.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.10.0", - "@typescript-eslint/types": "6.10.0", - "@typescript-eslint/typescript-estree": "6.10.0", - "semver": "^7.5.4" - }, - "devDependencies": { - "@typescript-eslint/parser": "6.10.0", - "downlevel-dts": "*", - "jest": "29.7.0", - "prettier": "^3.0.3", - "rimraf": "*", - "typescript": "*" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "../node_modules/.pnpm/eslint@8.53.0/node_modules/eslint": { - "version": "8.53.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.53.0", - "@humanwhocodes/config-array": "^0.11.13", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "devDependencies": { - "@babel/core": "^7.4.3", - "@babel/preset-env": "^7.4.3", - "@wdio/browser-runner": "^8.14.6", - "@wdio/cli": "^8.14.6", - "@wdio/concise-reporter": "^8.14.0", - "@wdio/globals": "^8.14.6", - "@wdio/mocha-framework": "^8.14.0", - "babel-loader": "^8.0.5", - "c8": "^7.12.0", - "chai": "^4.0.1", - "cheerio": "^0.22.0", - "common-tags": "^1.8.0", - "core-js": "^3.1.3", - "ejs": "^3.0.2", - "eslint": "file:.", - "eslint-config-eslint": "file:packages/eslint-config-eslint", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-eslint-plugin": "^5.1.0", - "eslint-plugin-internal-rules": "file:tools/internal-rules", - "eslint-plugin-jsdoc": "^46.2.5", - "eslint-plugin-n": "^16.0.0", - "eslint-plugin-unicorn": "^42.0.0", - "eslint-release": "^3.2.0", - "eslump": "^3.0.0", - "esprima": "^4.0.1", - "fast-glob": "^3.2.11", - "fs-teardown": "^0.1.3", - "glob": "^7.1.6", - "got": "^11.8.3", - "gray-matter": "^4.0.3", - "lint-staged": "^11.0.0", - "load-perf": "^0.2.0", - "markdownlint": "^0.25.1", - "markdownlint-cli": "^0.31.1", - "marked": "^4.0.8", - "memfs": "^3.0.1", - "mocha": "^8.3.2", - "mocha-junit-reporter": "^2.0.0", - "node-polyfill-webpack-plugin": "^1.0.3", - "npm-license": "^0.3.3", - "pirates": "^4.0.5", - "progress": "^2.0.3", - "proxyquire": "^2.0.1", - "recast": "^0.20.4", - "regenerator-runtime": "^0.13.2", - "rollup-plugin-node-polyfills": "^0.2.1", - "semver": "^7.5.3", - "shelljs": "^0.8.2", - "sinon": "^11.0.0", - "vite-plugin-commonjs": "^0.8.2", - "webdriverio": "^8.14.6", - "webpack": "^5.23.0", - "webpack-cli": "^4.5.0", - "yorkie": "^2.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "../node_modules/.pnpm/typescript@5.2.2_patch_hash=wmhs4olj6eveeldp6si4l46ssq/node_modules/typescript": { - "version": "5.2.2", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "devDependencies": { - "@esfx/canceltoken": "^1.0.0", - "@octokit/rest": "^19.0.13", - "@types/chai": "^4.3.4", - "@types/fs-extra": "^9.0.13", - "@types/glob": "^8.1.0", - "@types/microsoft__typescript-etw": "^0.1.1", - "@types/minimist": "^1.2.2", - "@types/mocha": "^10.0.1", - "@types/ms": "^0.7.31", - "@types/node": "latest", - "@types/source-map-support": "^0.5.6", - "@types/which": "^2.0.1", - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0", - "@typescript-eslint/utils": "^6.0.0", - "azure-devops-node-api": "^12.0.0", - "c8": "^7.14.0", - "chai": "^4.3.7", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "del": "^6.1.1", - "diff": "^5.1.0", - "esbuild": "^0.18.1", - "eslint": "^8.22.0", - "eslint-formatter-autolinkable-stylish": "^1.2.0", - "eslint-plugin-local": "^1.0.0", - "eslint-plugin-no-null": "^1.0.2", - "eslint-plugin-simple-import-sort": "^10.0.0", - "fast-xml-parser": "^4.0.11", - "fs-extra": "^9.1.0", - "glob": "^8.1.0", - "hereby": "^1.6.4", - "jsonc-parser": "^3.2.0", - "minimist": "^1.2.8", - "mocha": "^10.2.0", - "mocha-fivemat-progress-reporter": "^0.1.0", - "ms": "^2.1.3", - "node-fetch": "^3.2.10", - "source-map-support": "^0.5.21", - "tslib": "^2.5.0", - "typescript": "^5.0.2", - "which": "^2.0.2" - }, - "engines": { - "node": ">=14.17" - } - }, - "../node_modules/.pnpm/vitest@0.34.6/node_modules/vitest": { - "version": "0.34.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^4.3.5", - "@types/chai-subset": "^1.3.3", - "@types/node": "*", - "@vitest/expect": "0.34.6", - "@vitest/runner": "0.34.6", - "@vitest/snapshot": "0.34.6", - "@vitest/spy": "0.34.6", - "@vitest/utils": "0.34.6", - "acorn": "^8.9.0", - "acorn-walk": "^8.2.0", - "cac": "^6.7.14", - "chai": "^4.3.10", - "debug": "^4.3.4", - "local-pkg": "^0.4.3", - "magic-string": "^0.30.1", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.3.3", - "strip-literal": "^1.0.1", - "tinybench": "^2.5.0", - "tinypool": "^0.7.0", - "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", - "vite-node": "0.34.6", - "why-is-node-running": "^2.2.2" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "devDependencies": { - "@ampproject/remapping": "^2.2.1", - "@antfu/install-pkg": "^0.1.1", - "@edge-runtime/vm": "3.0.3", - "@sinonjs/fake-timers": "^11.0.0", - "@types/diff": "^5.0.3", - "@types/estree": "^1.0.1", - "@types/istanbul-lib-coverage": "^2.0.4", - "@types/istanbul-reports": "^3.0.1", - "@types/jsdom": "^21.1.1", - "@types/micromatch": "^4.0.2", - "@types/prompts": "^2.4.4", - "@types/sinonjs__fake-timers": "^8.1.2", - "birpc": "0.2.12", - "chai-subset": "^1.6.0", - "cli-truncate": "^3.1.0", - "event-target-polyfill": "^0.0.3", - "execa": "^7.1.1", - "expect-type": "^0.16.0", - "fast-glob": "^3.3.0", - "find-up": "^6.3.0", - "flatted": "^3.2.7", - "get-tsconfig": "^4.6.2", - "happy-dom": "^9.20.3", - "jsdom": "^22.1.0", - "log-update": "^5.0.1", - "micromatch": "^4.0.5", - "mlly": "^1.4.0", - "p-limit": "^4.0.0", - "pkg-types": "^1.0.3", - "playwright": "^1.35.1", - "pretty-format": "^29.5.0", - "prompts": "^2.4.2", - "safaridriver": "^0.0.5", - "strip-ansi": "^7.1.0", - "webdriverio": "^8.11.2", - "ws": "^8.13.0" - }, - "engines": { - "node": ">=v14.18.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@vitest/browser": "*", - "@vitest/ui": "*", - "happy-dom": "*", - "jsdom": "*", - "playwright": "*", - "safaridriver": "*", - "webdriverio": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "playwright": { - "optional": true - }, - "safaridriver": { - "optional": true - }, - "webdriverio": { - "optional": true - } - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@typescript-eslint/parser": { - "resolved": "../node_modules/.pnpm/@typescript-eslint+parser@6.10.0_eslint@8.53.0_typescript@5.2.2/node_modules/@typescript-eslint/parser", - "link": true - }, - "node_modules/@typescript-eslint/rule-tester": { - "resolved": "../node_modules/.pnpm/@typescript-eslint+rule-tester@6.10.0_@eslint+eslintrc@2.1.3_eslint@8.53.0_typescript@5.2.2/node_modules/@typescript-eslint/rule-tester", - "link": true - }, - "node_modules/@typescript-eslint/utils": { - "resolved": "../node_modules/.pnpm/@typescript-eslint+utils@6.10.0_eslint@8.53.0_typescript@5.2.2/node_modules/@typescript-eslint/utils", - "link": true - }, - "node_modules/aggregate-error": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", - "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", - "dev": true, - "dependencies": { - "clean-stack": "^4.0.0", - "indent-string": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/arrify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", - "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/clean-stack": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", - "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cp-file": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-10.0.0.tgz", - "integrity": "sha512-vy2Vi1r2epK5WqxOLnskeKeZkdZvTKfFZQCplE3XWsP+SUJyd5XAUFC9lFgTjjXJF2GMne/UML14iEmkAaDfFg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.10", - "nested-error-stacks": "^2.1.1", - "p-event": "^5.0.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cpy": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/cpy/-/cpy-10.1.0.tgz", - "integrity": "sha512-VC2Gs20JcTyeQob6UViBLnyP0bYHkBh6EiKzot9vi2DmeGlFT9Wd7VG3NBrkNx/jYvFBeyDOMMHdHQhbtKLgHQ==", - "dev": true, - "dependencies": { - "arrify": "^3.0.0", - "cp-file": "^10.0.0", - "globby": "^13.1.4", - "junk": "^4.0.1", - "micromatch": "^4.0.5", - "nested-error-stacks": "^2.1.1", - "p-filter": "^3.0.0", - "p-map": "^6.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cpy-cli": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cpy-cli/-/cpy-cli-5.0.0.tgz", - "integrity": "sha512-fb+DZYbL9KHc0BC4NYqGRrDIJZPXUmjjtqdw4XRRg8iV8dIfghUX/WiL+q4/B/KFTy3sK6jsbUhBaz0/Hxg7IQ==", - "dev": true, - "dependencies": { - "cpy": "^10.1.0", - "meow": "^12.0.1" - }, - "bin": { - "cpy": "cli.js" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "resolved": "../node_modules/.pnpm/eslint@8.53.0/node_modules/eslint", - "link": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/junk": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", - "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", - "dev": true, - "engines": { - "node": ">=16.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/nested-error-stacks": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", - "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==", - "dev": true - }, - "node_modules/p-event": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-5.0.1.tgz", - "integrity": "sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==", - "dev": true, - "dependencies": { - "p-timeout": "^5.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-3.0.0.tgz", - "integrity": "sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg==", - "dev": true, - "dependencies": { - "p-map": "^5.1.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-filter/node_modules/p-map": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz", - "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==", - "dev": true, - "dependencies": { - "aggregate-error": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-6.0.0.tgz", - "integrity": "sha512-T8BatKGY+k5rU+Q/GTYgrEf2r4xRMevAN5mtXc2aPc4rS1j3s+vWTaO2Wag94neXuCAUAs8cxBL9EeB5EA6diw==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", - "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/typescript": { - "resolved": "../node_modules/.pnpm/typescript@5.2.2_patch_hash=wmhs4olj6eveeldp6si4l46ssq/node_modules/typescript", - "link": true - }, - "node_modules/vitest": { - "resolved": "../node_modules/.pnpm/vitest@0.34.6/node_modules/vitest", - "link": true - } - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3146e0bd6..0c6406ebb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -295,6 +295,9 @@ importers: '@typescript-eslint/utils': specifier: ^6.10.0 version: 6.10.0(eslint@8.53.0)(typescript@5.2.2) + cpy-cli: + specifier: ^5.0.0 + version: 5.0.0 eslint: specifier: ^8.53.0 version: 8.53.0 @@ -4169,6 +4172,15 @@ packages: dev: false optional: true + /cpy-cli@5.0.0: + resolution: {integrity: sha512-fb+DZYbL9KHc0BC4NYqGRrDIJZPXUmjjtqdw4XRRg8iV8dIfghUX/WiL+q4/B/KFTy3sK6jsbUhBaz0/Hxg7IQ==} + engines: {node: '>=16'} + hasBin: true + dependencies: + cpy: 10.1.0 + meow: 12.1.1 + dev: true + /cpy@10.1.0: resolution: {integrity: sha512-VC2Gs20JcTyeQob6UViBLnyP0bYHkBh6EiKzot9vi2DmeGlFT9Wd7VG3NBrkNx/jYvFBeyDOMMHdHQhbtKLgHQ==} engines: {node: '>=16'} @@ -6719,6 +6731,11 @@ packages: timers-ext: 0.1.7 dev: true + /meow@12.1.1: + resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} + engines: {node: '>=16.10'} + dev: true + /merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} dev: false From f4156aa0bb6744d341f40adea7979babd7605e3c Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Thu, 16 Nov 2023 22:11:34 +0100 Subject: [PATCH 15/50] Add jsdoc for Pg delete --- drizzle-orm/src/pg-core/db.ts | 21 +++++++++ .../src/pg-core/query-builders/delete.ts | 47 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/drizzle-orm/src/pg-core/db.ts b/drizzle-orm/src/pg-core/db.ts index 8a1be58de..bf7d668bc 100644 --- a/drizzle-orm/src/pg-core/db.ts +++ b/drizzle-orm/src/pg-core/db.ts @@ -152,6 +152,27 @@ export class PgDatabase< return new PgInsertBuilder(table, this.session, this.dialect); } + /** + * Creates a delete query. + * + * Calling this method without `.where()` clause will delete all rows in a table. The `.where()` clause specifies which records should be deleted. + * + * @param table The table to delete from. + * ### Examples + * + * ```ts + * // Delete all rows in the 'cars' table + * await db.delete(cars); + * + * // Delete rows with filters and conditions + * await db.delete(cars).where(eq(cars.color, 'green')); + * + * // Delete with returning clause + * const deletedCar: Car[] = await db.delete(cars) + * .where(eq(cars.id, 1)) + * .returning(); + * ``` + */ delete(table: TTable): PgDeleteBase { return new PgDeleteBase(table, this.session, this.dialect); } diff --git a/drizzle-orm/src/pg-core/query-builders/delete.ts b/drizzle-orm/src/pg-core/query-builders/delete.ts index 5f446e03f..4aca56a38 100644 --- a/drizzle-orm/src/pg-core/query-builders/delete.ts +++ b/drizzle-orm/src/pg-core/query-builders/delete.ts @@ -129,11 +129,58 @@ export class PgDeleteBase< this.config = { table }; } + /** + * Adds a `where` clause to the query. + * + * Calling this method will delete only those records that fulfill a specified condition. + * + * @param where the `where` clause. + * + * ### Examples + * + * You can use conditional operators and `sql function` to filter the records to be deleted. + * + * ```ts + * // Delete all cars with green color + * db.delete(cars).where(eq(cars.color, 'green')); + * // or + * db.delete(cars).where(sql`${cars.color} = 'green'`) + * ``` + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Delete all BMW cars with a green color + * db.delete(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Delete all cars with the green or blue color + * db.delete(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + * + */ where(where: SQL | undefined): PgDeleteWithout { this.config.where = where; return this as any; } + /** + * Adds a `returning` clause to the query. + * + * Calling this method will return the specified fields of the deleted records. If no fields are specified, all fields will be returned. + * + * ### Examples + * + * ```ts + * // Delete all cars with the green color and return all fields + * const deletedCars: Car[] = await db.delete(cars) + * .where(eq(cars.color, 'green')) + * .returning(); + * + * // Delete all cars with the green color and return only their id and brand fields + * const deletedCarsIdsAndBrands: { id: number, brand: string }[] = await db.delete(cars) + * .where(eq(cars.color, 'green')) + * .returning({ id: cars.id, brand: cars.brand }); + * ``` + */ returning(): PgDeleteReturningAll; returning( fields: TSelectedFields, From 610acead27577730d29f9aa2428e9e23ab2e21ed Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Fri, 17 Nov 2023 13:22:10 +0100 Subject: [PATCH 16/50] Update jsdoc for Pg delete --- drizzle-orm/src/pg-core/db.ts | 2 ++ drizzle-orm/src/pg-core/query-builders/delete.ts | 3 +++ 2 files changed, 5 insertions(+) diff --git a/drizzle-orm/src/pg-core/db.ts b/drizzle-orm/src/pg-core/db.ts index bf7d668bc..e255c9ea6 100644 --- a/drizzle-orm/src/pg-core/db.ts +++ b/drizzle-orm/src/pg-core/db.ts @@ -172,6 +172,8 @@ export class PgDatabase< * .where(eq(cars.id, 1)) * .returning(); * ``` + * + * See docs: {@link} https://orm.drizzle.team/docs/delete */ delete(table: TTable): PgDeleteBase { return new PgDeleteBase(table, this.session, this.dialect); diff --git a/drizzle-orm/src/pg-core/query-builders/delete.ts b/drizzle-orm/src/pg-core/query-builders/delete.ts index 4aca56a38..ad04e07cc 100644 --- a/drizzle-orm/src/pg-core/query-builders/delete.ts +++ b/drizzle-orm/src/pg-core/query-builders/delete.ts @@ -156,6 +156,7 @@ export class PgDeleteBase< * db.delete(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); * ``` * + * See docs: {@link} https://orm.drizzle.team/docs/delete */ where(where: SQL | undefined): PgDeleteWithout { this.config.where = where; @@ -180,6 +181,8 @@ export class PgDeleteBase< * .where(eq(cars.color, 'green')) * .returning({ id: cars.id, brand: cars.brand }); * ``` + * + * See docs: {@link} https://orm.drizzle.team/docs/delete#delete-with-return */ returning(): PgDeleteReturningAll; returning( From 8fd0a472011c136d192e367065578e32757bc599 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Sat, 18 Nov 2023 15:13:29 -0600 Subject: [PATCH 17/50] Remove mentions of `BuiltInFunction` --- drizzle-orm/src/built-in-function.ts | 63 ------------------- drizzle-orm/src/index.ts | 1 - drizzle-orm/src/mysql-core/dialect.ts | 9 +-- .../src/mysql-core/functions/common.ts | 11 ---- drizzle-orm/src/mysql-core/functions/index.ts | 1 - .../src/mysql-core/query-builders/select.ts | 3 +- .../mysql-core/query-builders/select.types.ts | 7 +-- drizzle-orm/src/operations.ts | 17 +++-- drizzle-orm/src/pg-core/dialect.ts | 9 +-- drizzle-orm/src/pg-core/functions/common.ts | 11 ---- drizzle-orm/src/pg-core/functions/index.ts | 1 - .../src/pg-core/query-builders/delete.ts | 3 +- .../src/pg-core/query-builders/insert.ts | 3 +- .../src/pg-core/query-builders/select.ts | 3 +- .../pg-core/query-builders/select.types.ts | 7 +-- .../src/pg-core/query-builders/update.ts | 3 +- .../src/query-builders/select.types.ts | 9 ++- drizzle-orm/src/sql/sql.ts | 21 +------ drizzle-orm/src/sqlite-core/dialect.ts | 9 +-- .../src/sqlite-core/functions/common.ts | 11 ---- .../src/sqlite-core/functions/index.ts | 1 - .../src/sqlite-core/query-builders/delete.ts | 3 +- .../src/sqlite-core/query-builders/insert.ts | 3 +- .../src/sqlite-core/query-builders/select.ts | 3 +- .../query-builders/select.types.ts | 7 +-- .../src/sqlite-core/query-builders/update.ts | 3 +- drizzle-orm/src/utils.ts | 15 ++--- 27 files changed, 48 insertions(+), 189 deletions(-) delete mode 100644 drizzle-orm/src/built-in-function.ts delete mode 100644 drizzle-orm/src/mysql-core/functions/common.ts delete mode 100644 drizzle-orm/src/pg-core/functions/common.ts delete mode 100644 drizzle-orm/src/sqlite-core/functions/common.ts diff --git a/drizzle-orm/src/built-in-function.ts b/drizzle-orm/src/built-in-function.ts deleted file mode 100644 index 894d7bec2..000000000 --- a/drizzle-orm/src/built-in-function.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { entityKind } from './entity.ts'; -import type { Dialect } from './column-builder.ts'; -import type { SQLWrapper, SQL, DriverValueDecoder, GetDecoderResult } from './sql/sql.ts'; - -/** @internal */ -export const BuiltInFunctionSQL = Symbol.for('drizzle:BuiltInFunctionSQL'); - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export interface BuiltInFunction extends SQLWrapper { - // SQLWrapper runtime implementation is defined in 'sql/sql.ts' -} -export abstract class BuiltInFunction implements SQLWrapper { - static readonly [entityKind]: string = 'BuiltInFunction'; - - declare readonly _: { - readonly type: T; - readonly dialect: Dialect; - }; - - /** @internal */ - static readonly Symbol = { - SQL: BuiltInFunctionSQL as typeof BuiltInFunctionSQL, - }; - - /** @internal */ - get [BuiltInFunctionSQL](): SQL { - return this.sql; - } - - protected sql: SQL; - - constructor(sql: SQL) { - this.sql = sql; - } - - as(alias: string): SQL.Aliased; - /** - * @deprecated - * Use ``sql`query`.as(alias)`` instead. - */ - as(): SQL; - /** - * @deprecated - * Use ``sql`query`.as(alias)`` instead. - */ - as(alias: string): SQL.Aliased; - as(alias?: string): SQL | SQL.Aliased { - // TODO: remove with deprecated overloads - if (alias === undefined) { - return this.sql; - } - - return this.sql.as(alias); - } - - mapWith< - TDecoder extends - | DriverValueDecoder - | DriverValueDecoder['mapFromDriverValue'], - >(decoder: TDecoder): SQL> { - return this.sql.mapWith(decoder); - } -} diff --git a/drizzle-orm/src/index.ts b/drizzle-orm/src/index.ts index 757c81de6..4ce6cbc76 100644 --- a/drizzle-orm/src/index.ts +++ b/drizzle-orm/src/index.ts @@ -1,5 +1,4 @@ export * from './alias.ts'; -export * from './built-in-function.ts'; export * from './column-builder.ts'; export * from './column.ts'; export * from './distinct.ts'; diff --git a/drizzle-orm/src/mysql-core/dialect.ts b/drizzle-orm/src/mysql-core/dialect.ts index ae8e40422..31baa8d19 100644 --- a/drizzle-orm/src/mysql-core/dialect.ts +++ b/drizzle-orm/src/mysql-core/dialect.ts @@ -26,8 +26,6 @@ import type { MySqlSelectConfig, MySqlSelectJoinConfig, SelectedFieldsOrdered } import type { MySqlUpdateConfig } from './query-builders/update.ts'; import type { MySqlSession } from './session.ts'; import { MySqlTable } from './table.ts'; -import { BuiltInFunction } from '~/built-in-function.ts'; -import type { MySqlBuiltInFunction } from './functions/common.ts'; // TODO find out how to use all/values. Seems like I need those functions // Build project @@ -151,9 +149,8 @@ export class MySqlDialect { if (is(field, SQL.Aliased) && field.isSelectionField) { chunk.push(sql.identifier(field.fieldAlias)); - } else if (is(field, SQL.Aliased) || is(field, SQL) || is(field, BuiltInFunction)) { - const field_ = is(field, BuiltInFunction) ? field[BuiltInFunction.Symbol.SQL] : field - const query = is(field_, SQL.Aliased) ? field_.sql : field_; + } else if (is(field, SQL.Aliased) || is(field, SQL)) { + const query = is(field, SQL.Aliased) ? field.sql : field; if (isSingleTable) { chunk.push( @@ -209,7 +206,7 @@ export class MySqlDialect { setOperators, }: MySqlSelectConfig, ): SQL { - const fieldsList = fieldsFlat ?? orderSelectedFields(fields); + const fieldsList = fieldsFlat ?? orderSelectedFields(fields); for (const f of fieldsList) { if ( is(f.field, Column) diff --git a/drizzle-orm/src/mysql-core/functions/common.ts b/drizzle-orm/src/mysql-core/functions/common.ts deleted file mode 100644 index a3a43e1ab..000000000 --- a/drizzle-orm/src/mysql-core/functions/common.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BuiltInFunction } from '~/built-in-function.ts'; -import { entityKind } from '~/entity.ts'; - -export class MySqlBuiltInFunction extends BuiltInFunction { - static readonly [entityKind]: string = 'MySqlBuiltInFunction'; - - declare readonly _: { - readonly type: T; - readonly dialect: 'mysql'; - }; -} diff --git a/drizzle-orm/src/mysql-core/functions/index.ts b/drizzle-orm/src/mysql-core/functions/index.ts index 47b15a648..64ed5f04f 100644 --- a/drizzle-orm/src/mysql-core/functions/index.ts +++ b/drizzle-orm/src/mysql-core/functions/index.ts @@ -1,2 +1 @@ export * from './aggregate.ts'; -export * from './common.ts'; diff --git a/drizzle-orm/src/mysql-core/query-builders/select.ts b/drizzle-orm/src/mysql-core/query-builders/select.ts index 7ca09d24a..23f31342e 100644 --- a/drizzle-orm/src/mysql-core/query-builders/select.ts +++ b/drizzle-orm/src/mysql-core/query-builders/select.ts @@ -44,7 +44,6 @@ import type { SelectedFields, SetOperatorRightSelect, } from './select.types.ts'; -import type { MySqlBuiltInFunction } from '../index.ts'; export class MySqlSelectBuilder< TSelection extends SelectedFields | undefined, @@ -525,7 +524,7 @@ export class MySqlSelectBase< if (!this.session) { throw new Error('Cannot execute a query on a query builder. Please use a database instance instead.'); } - const fieldsList = orderSelectedFields(this.config.fields); + const fieldsList = orderSelectedFields(this.config.fields); const query = this.session.prepareQuery< PreparedQueryConfig & { execute: SelectResult[] }, TPreparedQueryHKT diff --git a/drizzle-orm/src/mysql-core/query-builders/select.types.ts b/drizzle-orm/src/mysql-core/query-builders/select.types.ts index a37b77809..711df48f9 100644 --- a/drizzle-orm/src/mysql-core/query-builders/select.types.ts +++ b/drizzle-orm/src/mysql-core/query-builders/select.types.ts @@ -24,7 +24,6 @@ import type { Table, UpdateTableConfig } from '~/table.ts'; import type { Assume, ValidateShape } from '~/utils.ts'; import type { PreparedQueryConfig, PreparedQueryHKTBase, PreparedQueryKind } from '../session.ts'; import type { MySqlSelectBase, MySqlSelectQueryBuilderBase } from './select.ts'; -import type { MySqlBuiltInFunction } from '../functions/common.ts'; import type { MySqlViewBase } from '../view-base.ts'; import type { MySqlViewWithSelection } from '../view.ts'; @@ -119,11 +118,11 @@ export type MySqlJoinFn< on: ((aliases: T['_']['selection']) => SQL | undefined) | SQL | undefined, ) => MySqlJoin; -export type SelectedFieldsFlat = SelectedFieldsFlatBase; +export type SelectedFieldsFlat = SelectedFieldsFlatBase; -export type SelectedFields = SelectedFieldsBase; +export type SelectedFields = SelectedFieldsBase; -export type SelectedFieldsOrdered = SelectedFieldsOrderedBase; +export type SelectedFieldsOrdered = SelectedFieldsOrderedBase; export type LockStrength = 'update' | 'share'; diff --git a/drizzle-orm/src/operations.ts b/drizzle-orm/src/operations.ts index cf5c9e4a5..09cf41b8a 100644 --- a/drizzle-orm/src/operations.ts +++ b/drizzle-orm/src/operations.ts @@ -1,4 +1,3 @@ -import type { BuiltInFunction } from './built-in-function.ts'; import type { AnyColumn, Column } from './column.ts'; import type { SQL } from './sql/sql.ts'; import type { Table } from './table.ts'; @@ -14,22 +13,22 @@ export type OptionalKeyOnly< T extends Column, > = TKey extends RequiredKeyOnly ? never : TKey; -export type SelectedFieldsFlat = Record< +export type SelectedFieldsFlat = Record< string, - TColumn | TBuiltInFunction | SQL | SQL.Aliased + TColumn | SQL | SQL.Aliased >; -export type SelectedFieldsFlatFull = Record< +export type SelectedFieldsFlatFull = Record< string, - TColumn | TBuiltInFunction | SQL | SQL.Aliased + TColumn | SQL | SQL.Aliased >; -export type SelectedFields = Record< +export type SelectedFields = Record< string, - SelectedFieldsFlat[string] | TTable | SelectedFieldsFlat + SelectedFieldsFlat[string] | TTable | SelectedFieldsFlat >; -export type SelectedFieldsOrdered = { +export type SelectedFieldsOrdered = { path: string[]; - field: TColumn | TBuiltInFunction | SQL | SQL.Aliased; + field: TColumn | SQL | SQL.Aliased; }[]; diff --git a/drizzle-orm/src/pg-core/dialect.ts b/drizzle-orm/src/pg-core/dialect.ts index 5363a324b..366436e29 100644 --- a/drizzle-orm/src/pg-core/dialect.ts +++ b/drizzle-orm/src/pg-core/dialect.ts @@ -42,8 +42,6 @@ import type { PgSession } from './session.ts'; import type { PgMaterializedView } from './view.ts'; import { View, and, eq } from '~/sql/index.ts'; import { PgViewBase } from './view-base.ts'; -import { BuiltInFunction } from '~/built-in-function.ts'; -import type { PgBuiltInFunction } from './functions/common.ts'; export class PgDialect { static readonly [entityKind]: string = 'PgDialect'; @@ -155,9 +153,8 @@ export class PgDialect { if (is(field, SQL.Aliased) && field.isSelectionField) { chunk.push(sql.identifier(field.fieldAlias)); - } else if (is(field, SQL.Aliased) || is(field, SQL) || is(field, BuiltInFunction)) { - const field_ = is(field, BuiltInFunction) ? field[BuiltInFunction.Symbol.SQL] : field - const query = is(field_, SQL.Aliased) ? field_.sql : field_; + } else if (is(field, SQL.Aliased) || is(field, SQL)) { + const query = is(field, SQL.Aliased) ? field.sql : field; if (isSingleTable) { chunk.push( @@ -213,7 +210,7 @@ export class PgDialect { setOperators, }: PgSelectConfig, ): SQL { - const fieldsList = fieldsFlat ?? orderSelectedFields(fields); + const fieldsList = fieldsFlat ?? orderSelectedFields(fields); for (const f of fieldsList) { if ( is(f.field, Column) diff --git a/drizzle-orm/src/pg-core/functions/common.ts b/drizzle-orm/src/pg-core/functions/common.ts deleted file mode 100644 index 3ed63ed42..000000000 --- a/drizzle-orm/src/pg-core/functions/common.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BuiltInFunction } from '~/built-in-function.ts'; -import { entityKind } from '~/entity.ts'; - -export class PgBuiltInFunction extends BuiltInFunction { - static readonly [entityKind]: string = 'PgBuiltInFunction'; - - declare readonly _: { - readonly type: T; - readonly dialect: 'pg'; - }; -} diff --git a/drizzle-orm/src/pg-core/functions/index.ts b/drizzle-orm/src/pg-core/functions/index.ts index 47b15a648..64ed5f04f 100644 --- a/drizzle-orm/src/pg-core/functions/index.ts +++ b/drizzle-orm/src/pg-core/functions/index.ts @@ -1,2 +1 @@ export * from './aggregate.ts'; -export * from './common.ts'; diff --git a/drizzle-orm/src/pg-core/query-builders/delete.ts b/drizzle-orm/src/pg-core/query-builders/delete.ts index 2de8c072e..2e14f1b39 100644 --- a/drizzle-orm/src/pg-core/query-builders/delete.ts +++ b/drizzle-orm/src/pg-core/query-builders/delete.ts @@ -16,7 +16,6 @@ import { tracer } from '~/tracing.ts'; import { orderSelectedFields } from '~/utils.ts'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types.ts'; import type { PgColumn } from '../columns/common.ts'; -import type { PgBuiltInFunction } from '../functions/common.ts'; export type PgDeleteWithout< T extends AnyPgDeleteBase, @@ -143,7 +142,7 @@ export class PgDeleteBase< returning( fields: SelectedFieldsFlat = this.config.table[Table.Symbol.Columns], ): PgDeleteReturning { - this.config.returning = orderSelectedFields(fields); + this.config.returning = orderSelectedFields(fields); return this as any; } diff --git a/drizzle-orm/src/pg-core/query-builders/insert.ts b/drizzle-orm/src/pg-core/query-builders/insert.ts index 29f22687e..417b267f8 100644 --- a/drizzle-orm/src/pg-core/query-builders/insert.ts +++ b/drizzle-orm/src/pg-core/query-builders/insert.ts @@ -19,7 +19,6 @@ import { mapUpdateSet, orderSelectedFields } from '~/utils.ts'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types.ts'; import type { PgUpdateSetSource } from './update.ts'; import type { PgColumn } from '../columns/common.ts'; -import type { PgBuiltInFunction } from '../functions/common.ts' export interface PgInsertConfig { table: TTable; @@ -172,7 +171,7 @@ export class PgInsertBase< returning( fields: SelectedFieldsFlat = this.config.table[Table.Symbol.Columns], ): PgInsertWithout { - this.config.returning = orderSelectedFields(fields); + this.config.returning = orderSelectedFields(fields); return this as any; } diff --git a/drizzle-orm/src/pg-core/query-builders/select.ts b/drizzle-orm/src/pg-core/query-builders/select.ts index 2ee22e6e8..b76c9c778 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.ts @@ -45,7 +45,6 @@ import type { SelectedFields, SetOperatorRightSelect, } from './select.types.ts'; -import type { PgBuiltInFunction } from '../functions/common.ts'; export class PgSelectBuilder< TSelection extends SelectedFields | undefined, @@ -637,7 +636,7 @@ export class PgSelectBase< throw new Error('Cannot execute a query on a query builder. Please use a database instance instead.'); } return tracer.startActiveSpan('drizzle.prepareQuery', () => { - const fieldsList = orderSelectedFields(config.fields); + const fieldsList = orderSelectedFields(config.fields); const query = session.prepareQuery< PreparedQueryConfig & { execute: TResult } >(dialect.sqlToQuery(this.getSQL()), fieldsList, name); diff --git a/drizzle-orm/src/pg-core/query-builders/select.types.ts b/drizzle-orm/src/pg-core/query-builders/select.types.ts index 65a589045..226a68ac2 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.types.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.types.ts @@ -26,7 +26,6 @@ import type { Table, UpdateTableConfig } from '~/table.ts'; import type { Assume, ValidateShape, ValueOrArray } from '~/utils.ts'; import type { PreparedQuery, PreparedQueryConfig } from '../session.ts'; import type { PgSelectBase, PgSelectQueryBuilderBase } from './select.ts'; -import type { PgBuiltInFunction } from '../functions/common.ts'; export interface PgSelectJoinConfig { on: SQL | undefined; @@ -121,11 +120,11 @@ export type PgJoinFn< on: ((aliases: T['_']['selection']) => SQL | undefined) | SQL | undefined, ) => PgJoin; -export type SelectedFieldsFlat = SelectedFieldsFlatBase; +export type SelectedFieldsFlat = SelectedFieldsFlatBase; -export type SelectedFields = SelectedFieldsBase; +export type SelectedFields = SelectedFieldsBase; -export type SelectedFieldsOrdered = SelectedFieldsOrderedBase; +export type SelectedFieldsOrdered = SelectedFieldsOrderedBase; export type LockStrength = 'update' | 'no key update' | 'share' | 'key share'; diff --git a/drizzle-orm/src/pg-core/query-builders/update.ts b/drizzle-orm/src/pg-core/query-builders/update.ts index 12027b009..6377d0d41 100644 --- a/drizzle-orm/src/pg-core/query-builders/update.ts +++ b/drizzle-orm/src/pg-core/query-builders/update.ts @@ -16,7 +16,6 @@ import { Table } from '~/table.ts'; import { mapUpdateSet, orderSelectedFields, type UpdateSet } from '~/utils.ts'; import type { SelectedFields, SelectedFieldsOrdered } from './select.types.ts'; import type { PgColumn } from '../columns/common.ts'; -import type { PgBuiltInFunction } from '../functions/common.ts' export interface PgUpdateConfig { where?: SQL | undefined; @@ -173,7 +172,7 @@ export class PgUpdateBase< returning( fields: SelectedFields = this.config.table[Table.Symbol.Columns], ): PgUpdateWithout { - this.config.returning = orderSelectedFields(fields); + this.config.returning = orderSelectedFields(fields); return this as any; } diff --git a/drizzle-orm/src/query-builders/select.types.ts b/drizzle-orm/src/query-builders/select.types.ts index 8c2f5dde0..0078330e4 100644 --- a/drizzle-orm/src/query-builders/select.types.ts +++ b/drizzle-orm/src/query-builders/select.types.ts @@ -5,7 +5,6 @@ import type { ColumnsSelection, SQL, View } from '~/sql/sql.ts'; import type { Subquery } from '~/subquery.ts'; import type { Table } from '~/table.ts'; import type { Assume, DrizzleTypeError, Equal, IsAny, Simplify } from '~/utils.ts'; -import type { BuiltInFunction } from '../built-in-function.ts'; export type JoinType = 'inner' | 'left' | 'right' | 'full'; @@ -58,9 +57,9 @@ type SelectPartialResult, TNullability[TField['_']['tableName']]> : never - : TField extends SQL | SQL.Aliased | BuiltInFunction ? SelectResultField + : TField extends SQL | SQL.Aliased ? SelectResultField : TField extends Record - ? TField[keyof TField] extends AnyColumn<{ tableName: infer TTableName extends string }> | SQL | SQL.Aliased | BuiltInFunction + ? TField[keyof TField] extends AnyColumn<{ tableName: infer TTableName extends string }> | SQL | SQL.Aliased ? Not> extends true ? ApplyNullability, TNullability[TTableName]> : SelectPartialResult @@ -101,7 +100,7 @@ export type AppendToResult< TTableName extends string | undefined, TResult, TJoinedName extends string | undefined, - TSelectedFields extends SelectedFields, + TSelectedFields extends SelectedFields, TOldSelectMode extends SelectMode, > = TOldSelectMode extends 'partial' ? TResult : TOldSelectMode extends 'single' ? @@ -156,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 SQL | SQL.Aliased | BuiltInFunction ? T['_']['type'] + : T extends SQL | SQL.Aliased ? T['_']['type'] : T extends Record ? SelectResultFields : never; diff --git a/drizzle-orm/src/sql/sql.ts b/drizzle-orm/src/sql/sql.ts index fba8c45aa..cad140d38 100644 --- a/drizzle-orm/src/sql/sql.ts +++ b/drizzle-orm/src/sql/sql.ts @@ -6,7 +6,6 @@ import { ViewBaseConfig } from '~/view-common.ts'; import type { AnyColumn } from '../column.ts'; import { Column } from '../column.ts'; import { Table } from '../table.ts'; -import { BuiltInFunction } from '~/built-in-function.ts'; import type { SelectedFields } from '~/operations.ts'; /** @@ -58,7 +57,6 @@ export interface QueryWithTypings extends Query { * - `SQL.Aliased` * - `Placeholder` * - `Param` - * - `BuiltInFunction` */ export interface SQLWrapper { getSQL(): SQL; @@ -174,13 +172,6 @@ export class SQL implements SQLWrapper { }); } - if (is(chunk, BuiltInFunction)) { - return this.buildQueryFromSourceParams(chunk[BuiltInFunction.Symbol.SQL].queryChunks, { - ...config, - inlineParams: inlineParams || chunk[BuiltInFunction.Symbol.SQL].shouldInlineParams, - }); - } - if (is(chunk, Table)) { const schemaName = chunk[Table.Symbol.Schema]; const tableName = chunk[Table.Symbol.Name]; @@ -437,8 +428,7 @@ export type SQLChunk = | Name | undefined | FakePrimitiveParam - | Placeholder - | BuiltInFunction; + | Placeholder; export function sql(strings: TemplateStringsArray, ...params: any[]): SQL; /* @@ -610,7 +600,7 @@ export abstract class View< name: TName; originalName: TName; schema: string | undefined; - selectedFields: SelectedFields; + selectedFields: SelectedFields; isExisting: TExisting; query: TExisting extends true ? undefined : SQL; isAlias: boolean; @@ -620,7 +610,7 @@ export abstract class View< { name, schema, selectedFields, query }: { name: TName; schema: string | undefined; - selectedFields: SelectedFields; + selectedFields: SelectedFields; query: SQL | undefined; }, ) { @@ -650,11 +640,6 @@ Table.prototype.getSQL = function() { return new SQL([this]); }; -// Defined separately from the Column class to resolve circular dependency -BuiltInFunction.prototype.getSQL = function() { - return new SQL([this]); -}; - // Defined separately from the Column class to resolve circular dependency Subquery.prototype.getSQL = function() { return new SQL([this]); diff --git a/drizzle-orm/src/sqlite-core/dialect.ts b/drizzle-orm/src/sqlite-core/dialect.ts index 240ab9cb0..d58ef419e 100644 --- a/drizzle-orm/src/sqlite-core/dialect.ts +++ b/drizzle-orm/src/sqlite-core/dialect.ts @@ -32,8 +32,6 @@ import type { SQLiteSelectJoinConfig, } from './query-builders/select.types.ts'; import type { SQLiteSession } from './session.ts'; -import type { SQLiteBuiltInFunction } from './functions/common.ts'; -import { BuiltInFunction } from '~/built-in-function.ts'; import { SQLiteViewBase } from './view-base.ts'; export abstract class SQLiteDialect { @@ -113,9 +111,8 @@ export abstract class SQLiteDialect { if (is(field, SQL.Aliased) && field.isSelectionField) { chunk.push(sql.identifier(field.fieldAlias)); - } else if (is(field, SQL.Aliased) || is(field, SQL) || is(field, BuiltInFunction)) { - const field_ = is(field, BuiltInFunction) ? field[BuiltInFunction.Symbol.SQL] : field - const query = is(field_, SQL.Aliased) ? field_.sql : field_; + } else if (is(field, SQL.Aliased) || is(field, SQL)) { + const query = is(field, SQL.Aliased) ? field.sql : field; if (isSingleTable) { chunk.push( @@ -172,7 +169,7 @@ export abstract class SQLiteDialect { setOperators, }: SQLiteSelectConfig, ): SQL { - const fieldsList = fieldsFlat ?? orderSelectedFields(fields); + const fieldsList = fieldsFlat ?? orderSelectedFields(fields); for (const f of fieldsList) { if ( is(f.field, Column) diff --git a/drizzle-orm/src/sqlite-core/functions/common.ts b/drizzle-orm/src/sqlite-core/functions/common.ts deleted file mode 100644 index 37cccfd75..000000000 --- a/drizzle-orm/src/sqlite-core/functions/common.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BuiltInFunction } from '~/built-in-function.ts'; -import { entityKind } from '~/entity.ts'; - -export class SQLiteBuiltInFunction extends BuiltInFunction { - static readonly [entityKind]: string = 'SQLiteBuiltInFunction'; - - declare readonly _: { - readonly type: T; - readonly dialect: 'sqlite'; - }; -} diff --git a/drizzle-orm/src/sqlite-core/functions/index.ts b/drizzle-orm/src/sqlite-core/functions/index.ts index 47b15a648..64ed5f04f 100644 --- a/drizzle-orm/src/sqlite-core/functions/index.ts +++ b/drizzle-orm/src/sqlite-core/functions/index.ts @@ -1,2 +1 @@ export * from './aggregate.ts'; -export * from './common.ts'; diff --git a/drizzle-orm/src/sqlite-core/query-builders/delete.ts b/drizzle-orm/src/sqlite-core/query-builders/delete.ts index ae743937e..6fe7db69a 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/delete.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/delete.ts @@ -8,7 +8,6 @@ import type { SQLitePreparedQuery, SQLiteSession } from '~/sqlite-core/session.t import { SQLiteTable } from '~/sqlite-core/table.ts'; import { type DrizzleTypeError, orderSelectedFields } from '~/utils.ts'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types.ts'; -import type { SQLiteBuiltInFunction } from '../functions/common.ts'; import type { SQLiteColumn } from '../columns/common.ts'; export type SQLiteDeleteWithout< @@ -157,7 +156,7 @@ export class SQLiteDeleteBase< returning( fields: SelectedFieldsFlat = this.table[SQLiteTable.Symbol.Columns], ): SQLiteDeleteReturning { - this.config.returning = orderSelectedFields(fields); + this.config.returning = orderSelectedFields(fields); return this as any; } diff --git a/drizzle-orm/src/sqlite-core/query-builders/insert.ts b/drizzle-orm/src/sqlite-core/query-builders/insert.ts index bfbb5a40c..c933527a8 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/insert.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/insert.ts @@ -12,7 +12,6 @@ import { Table } from '~/table.ts'; import { type DrizzleTypeError, mapUpdateSet, orderSelectedFields, type Simplify } from '~/utils.ts'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types.ts'; import type { SQLiteUpdateSetSource } from './update.ts'; -import type { SQLiteBuiltInFunction } from '../functions/common.ts'; import type { SQLiteColumn } from '../columns/common.ts'; export interface SQLiteInsertConfig { @@ -215,7 +214,7 @@ export class SQLiteInsertBase< returning( fields: SelectedFieldsFlat = this.config.table[SQLiteTable.Symbol.Columns], ): SQLiteInsertWithout { - this.config.returning = orderSelectedFields(fields); + this.config.returning = orderSelectedFields(fields); return this as any; } diff --git a/drizzle-orm/src/sqlite-core/query-builders/select.ts b/drizzle-orm/src/sqlite-core/query-builders/select.ts index d50067d68..67cae227f 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/select.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/select.ts @@ -47,7 +47,6 @@ import type { SQLiteSetOperatorExcludedMethods, SQLiteSetOperatorWithResult, } from './select.types.ts'; -import type { SQLiteBuiltInFunction } from '../functions/common.ts'; import { Subquery, SubqueryConfig } from '~/subquery.ts'; import { SQLiteViewBase } from '../view-base.ts'; import { SelectionProxyHandler } from '~/selection-proxy.ts'; @@ -527,7 +526,7 @@ export class SQLiteSelectBase< if (!this.session) { throw new Error('Cannot execute a query on a query builder. Please use a database instance instead.'); } - const fieldsList = orderSelectedFields(this.config.fields); + const fieldsList = orderSelectedFields(this.config.fields); const query = this.session[isOneTimeQuery ? 'prepareOneTimeQuery' : 'prepareQuery']( this.dialect.sqlToQuery(this.getSQL()), fieldsList, diff --git a/drizzle-orm/src/sqlite-core/query-builders/select.types.ts b/drizzle-orm/src/sqlite-core/query-builders/select.types.ts index d6222051e..36100a2c2 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/select.types.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/select.types.ts @@ -2,7 +2,6 @@ import type { ColumnsSelection, Placeholder, SQL, View } from '~/sql/sql.ts'; import type { Assume, ValidateShape } from '~/utils.ts'; import type { SQLiteColumn } from '~/sqlite-core/columns/index.ts'; import type { SQLiteTable, SQLiteTableWithColumns } from '~/sqlite-core/table.ts'; -import type { SQLiteBuiltInFunction } from '../functions/common.ts'; import type { SelectedFields as SelectFieldsBase, @@ -116,11 +115,11 @@ export type SQLiteJoinFn< on: ((aliases: T['_']['selection']) => SQL | undefined) | SQL | undefined, ) => SQLiteJoin; -export type SelectedFieldsFlat = SelectFieldsFlatBase; +export type SelectedFieldsFlat = SelectFieldsFlatBase; -export type SelectedFields = SelectFieldsBase; +export type SelectedFields = SelectFieldsBase; -export type SelectedFieldsOrdered = SelectFieldsOrderedBase; +export type SelectedFieldsOrdered = SelectFieldsOrderedBase; export interface SQLiteSelectHKTBase { tableName: string | undefined; diff --git a/drizzle-orm/src/sqlite-core/query-builders/update.ts b/drizzle-orm/src/sqlite-core/query-builders/update.ts index 6cc263ffe..6859407c4 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/update.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/update.ts @@ -9,7 +9,6 @@ import type { SQLitePreparedQuery, SQLiteSession } from '~/sqlite-core/session.t import { SQLiteTable } from '~/sqlite-core/table.ts'; import { type DrizzleTypeError, mapUpdateSet, orderSelectedFields, type UpdateSet } from '~/utils.ts'; import type { SelectedFields, SelectedFieldsOrdered } from './select.types.ts'; -import type { SQLiteBuiltInFunction } from '../functions/common.ts'; import type { SQLiteColumn } from '../columns/common.ts'; export interface SQLiteUpdateConfig { @@ -188,7 +187,7 @@ export class SQLiteUpdateBase< returning( fields: SelectedFields = this.config.table[SQLiteTable.Symbol.Columns], ): SQLiteUpdateWithout { - this.config.returning = orderSelectedFields(fields); + this.config.returning = orderSelectedFields(fields); return this as any; } diff --git a/drizzle-orm/src/utils.ts b/drizzle-orm/src/utils.ts index 365fe2785..8d44db98c 100644 --- a/drizzle-orm/src/utils.ts +++ b/drizzle-orm/src/utils.ts @@ -1,4 +1,3 @@ -import { BuiltInFunction } from './built-in-function.ts'; import type { AnyColumn } from './column.ts'; import { Column } from './column.ts'; import { is } from './entity.ts'; @@ -13,7 +12,7 @@ import { ViewBaseConfig } from './view-common.ts'; /** @internal */ export function mapResultRow( - columns: SelectedFieldsOrdered, + columns: SelectedFieldsOrdered, row: unknown[], joinsNotNullableMap: Record | undefined, ): TResult { @@ -27,8 +26,6 @@ export function mapResultRow( decoder = field; } else if (is(field, SQL)) { decoder = field.decoder; - } else if (is(field, BuiltInFunction)) { - decoder = field[BuiltInFunction.Symbol.SQL].decoder; } else { decoder = field.sql.decoder; } @@ -73,17 +70,17 @@ export function mapResultRow( } /** @internal */ -export function orderSelectedFields( +export function orderSelectedFields( fields: Record, pathPrefix?: string[], -): SelectedFieldsOrdered { - return Object.entries(fields).reduce>((result, [name, field]) => { +): SelectedFieldsOrdered { + return Object.entries(fields).reduce>((result, [name, field]) => { if (typeof name !== 'string') { return result; } const newPath = pathPrefix ? [...pathPrefix, name] : [name]; - if (is(field, Column) || is(field, SQL) || is(field, SQL.Aliased) || is(field, BuiltInFunction)) { + if (is(field, Column) || is(field, SQL) || is(field, SQL.Aliased)) { result.push({ path: newPath, field }); } else if (is(field, Table)) { result.push(...orderSelectedFields(field[Table.Symbol.Columns], newPath)); @@ -91,7 +88,7 @@ export function orderSelectedFields, newPath)); } return result; - }, []) as SelectedFieldsOrdered; + }, []) as SelectedFieldsOrdered; } export function haveSameKeys(left: Record, right: Record) { From 7e2166f3b593854c1b2271880882e5d7eb620b69 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Sat, 18 Nov 2023 16:36:09 -0600 Subject: [PATCH 18/50] Make aggregate functions common across dialects --- drizzle-orm/src/distinct.ts | 51 ----- drizzle-orm/src/index.ts | 1 - .../src/mysql-core/functions/aggregate.ts | 88 -------- drizzle-orm/src/mysql-core/functions/index.ts | 1 - drizzle-orm/src/mysql-core/index.ts | 1 - .../src/pg-core/functions/aggregate.ts | 98 --------- drizzle-orm/src/pg-core/functions/index.ts | 1 - drizzle-orm/src/pg-core/index.ts | 1 - drizzle-orm/src/sql/functions/aggregate.ts | 208 ++++++++++-------- .../src/sqlite-core/functions/aggregate.ts | 127 ----------- .../src/sqlite-core/functions/index.ts | 1 - drizzle-orm/src/sqlite-core/index.ts | 1 - 12 files changed, 114 insertions(+), 465 deletions(-) delete mode 100644 drizzle-orm/src/distinct.ts delete mode 100644 drizzle-orm/src/mysql-core/functions/aggregate.ts delete mode 100644 drizzle-orm/src/mysql-core/functions/index.ts delete mode 100644 drizzle-orm/src/pg-core/functions/aggregate.ts delete mode 100644 drizzle-orm/src/pg-core/functions/index.ts delete mode 100644 drizzle-orm/src/sqlite-core/functions/aggregate.ts delete mode 100644 drizzle-orm/src/sqlite-core/functions/index.ts diff --git a/drizzle-orm/src/distinct.ts b/drizzle-orm/src/distinct.ts deleted file mode 100644 index 8cf12c22d..000000000 --- a/drizzle-orm/src/distinct.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { entityKind, is } from './entity.ts'; -import type { SQLWrapper } from './index.ts'; - -/** @internal */ -export const DistinctValue = Symbol.for('drizzle:DistinctValue'); - -export class Distinct { - static readonly [entityKind]: string = 'Distinct'; - - declare readonly _: { - readonly type: T; - }; - - /** @internal */ - static readonly Symbol = { - Value: DistinctValue as typeof DistinctValue, - }; - - /** @internal */ - [DistinctValue]: T; - - constructor(value: T) { - this[DistinctValue] = value; - } -} - -export type MaybeDistinct = T | Distinct; - -export type WithoutDistinct = T extends Distinct ? T['_']['type'] : T; - -export function distinct(value: T) { - return new Distinct(value); -} - -/** @internal */ -export function getValueWithDistinct(value: T): { - value: WithoutDistinct; - distinct: boolean; -} { - if (is(value, Distinct)) { - return { - value: value[DistinctValue], - distinct: true - } as any; - } - - return { - value, - distinct: false - } as any; -} \ No newline at end of file diff --git a/drizzle-orm/src/index.ts b/drizzle-orm/src/index.ts index 4ce6cbc76..bc72260b9 100644 --- a/drizzle-orm/src/index.ts +++ b/drizzle-orm/src/index.ts @@ -1,7 +1,6 @@ export * from './alias.ts'; export * from './column-builder.ts'; export * from './column.ts'; -export * from './distinct.ts'; export * from './entity.ts'; export * from './errors.ts'; export * from './expressions.ts'; diff --git a/drizzle-orm/src/mysql-core/functions/aggregate.ts b/drizzle-orm/src/mysql-core/functions/aggregate.ts deleted file mode 100644 index f8e88b6c7..000000000 --- a/drizzle-orm/src/mysql-core/functions/aggregate.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { MySqlColumn } from '../columns/index.ts'; -import { count$, avg$, sum$, max$, min$ } from '~/sql/functions/index.ts'; -import type { SQLWrapper, SQL } from '~/sql/sql.ts'; -import type { MaybeDistinct } from '~/distinct.ts'; - -/** - * Returns the number of values in `expression`. - * - * ## Examples - * - * ```ts - * // Number employees with null values - * db.select({ value: count() }).from(employees) - * // Number of employees where `name` is not null - * db.select({ value: count(employees.name) }).from(employees) - * // Number of employees where `name` is distinct (no duplicates) - * db.select({ value: count(distinct(employees.name)) }).from(employees) - * ``` - */ -export function count(expression?: MaybeDistinct | '*'): SQL { - return count$('mysql', expression); -} - -/** - * Returns the average (arithmetic mean) of all non-null values in `expression`. - * - * ## Examples - * - * ```ts - * // Average salary of an employee - * db.select({ value: avg(employees.salary) }).from(employees) - * // Average salary of an employee where `salary` is distinct (no duplicates) - * db.select({ value: avg(distinct(employees.salary)) }).from(employees) - * ``` - */ -export function avg(expression: MaybeDistinct): SQL { - return avg$('mysql', expression); -} - -/** - * Returns the sum of all non-null values in `expression`. - * - * ## Examples - * - * ```ts - * // Sum of every employee's salary - * db.select({ value: sum(employees.salary) }).from(employees) - * // Sum of every employee's salary where `salary` is distinct (no duplicates) - * db.select({ value: sum(distinct(employees.salary)) }).from(employees) - * ``` - */ -export function sum(expression: MaybeDistinct): SQL { - return sum$('mysql', expression); -} - -/** - * Returns the maximum value in `expression`. - * - * ## Examples - * - * ```ts - * // The employee with the highest salary - * db.select({ value: max(employees.salary) }).from(employees) - * ``` - */ -export function max(expression: T): T extends MySqlColumn - ? SQL - : SQL -{ - return max$('mysql', expression) as any; -} - -/** - * Returns the minimum value in `expression`. - * - * ## Examples - * - * ```ts - * // The employee with the lowest salary - * db.select({ value: min(employees.salary) }).from(employees) - * ``` - */ -export function min(expression: T): T extends MySqlColumn - ? SQL - : SQL -{ - return min$('mysql', expression) as any; -} diff --git a/drizzle-orm/src/mysql-core/functions/index.ts b/drizzle-orm/src/mysql-core/functions/index.ts deleted file mode 100644 index 64ed5f04f..000000000 --- a/drizzle-orm/src/mysql-core/functions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './aggregate.ts'; diff --git a/drizzle-orm/src/mysql-core/index.ts b/drizzle-orm/src/mysql-core/index.ts index b7f13c739..204e0af3c 100644 --- a/drizzle-orm/src/mysql-core/index.ts +++ b/drizzle-orm/src/mysql-core/index.ts @@ -4,7 +4,6 @@ export * from './columns/index.ts'; export * from './db.ts'; export * from './dialect.ts'; export * from './foreign-keys.ts'; -export * from './functions/index.ts'; export * from './indexes.ts'; export * from './primary-keys.ts'; export * from './query-builders/index.ts'; diff --git a/drizzle-orm/src/pg-core/functions/aggregate.ts b/drizzle-orm/src/pg-core/functions/aggregate.ts deleted file mode 100644 index fb3d8dbf3..000000000 --- a/drizzle-orm/src/pg-core/functions/aggregate.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { PgColumn } from '../columns/index.ts'; -import { type AggregateFunction, count$, avg$, sum$, max$, min$ } from '~/sql/functions/index.ts'; -import type { SQLWrapper } from '~/sql/sql.ts'; -import type { MaybeDistinct } from '~/distinct.ts'; - -/** - * Returns the number of values in `expression`. - * - * ## Examples - * - * ```ts - * // Number employees with null values - * db.select({ value: count() }).from(employees) - * // Number of employees where `name` is not null - * db.select({ value: count(employees.name) }).from(employees) - * // Number of employees where `name` is distinct (no duplicates) - * db.select({ value: count(distinct(employees.name)) }).from(employees) - * // Number of employees where their salaries are greater than $2,000 - * db.select({ value: count().filterWhere(gt(employees.salary, 2000)) }).from(employees) - * ``` - */ -export function count(expression?: MaybeDistinct | '*'): AggregateFunction { - return count$('pg', expression); -} - -/** - * Returns the average (arithmetic mean) of all non-null values in `expression`. - * - * ## Examples - * - * ```ts - * // Average salary of an employee - * db.select({ value: avg(employees.salary) }).from(employees) - * // Average salary of an employee where `salary` is distinct (no duplicates) - * db.select({ value: avg(distinct(employees.salary)) }).from(employees) - * // Average salary of an employee where their salaries are greater than $2,000 - * db.select({ value: avg(employees.salary).filterWhere(gt(employees.salary, 2000)) }).from(employees) - * ``` - */ -export function avg(expression: MaybeDistinct): AggregateFunction { - return avg$('pg', expression); -} - -/** - * Returns the sum of all non-null values in `expression`. - * - * ## Examples - * - * ```ts - * // Sum of every employee's salary - * db.select({ value: sum(employees.salary) }).from(employees) - * // Sum of every employee's salary where `salary` is distinct (no duplicates) - * db.select({ value: sum(distinct(employees.salary)) }).from(employees) - * // Sum of every employee's salary where their salaries are greater than $2,000 - * db.select({ value: sum(employees.salary).filterWhere(gt(employees.salary, 2000)) }).from(employees) - * ``` - */ -export function sum(expression: MaybeDistinct): AggregateFunction { - return sum$('pg', expression); -} - -/** - * Returns the maximum value in `expression`. - * - * ## Examples - * - * ```ts - * // The employee with the highest salary - * db.select({ value: max(employees.salary) }).from(employees) - * // The employee with the highest salary but that's less than $2,000 - * db.select({ value: max(employees.salary).filterWhere(lt(employees.salary, 2000)) }).from(employees) - * ``` - */ -export function max(expression: T): T extends PgColumn - ? AggregateFunction - : AggregateFunction -{ - return max$('pg', expression) as any; -} - -/** - * Returns the minimum value in `expression`. - * - * ## Examples - * - * ```ts - * // The employee with the lowest salary - * db.select({ value: min(employees.salary) }).from(employees) - * // The employee with the lowest salary but that's greater than $1,000 - * db.select({ value: min(employees.salary).filterWhere(gt(employees.salary, 1000)) }).from(employees) - * ``` - */ -export function min(expression: T): T extends PgColumn - ? AggregateFunction - : AggregateFunction -{ - return min$('pg', expression) as any; -} diff --git a/drizzle-orm/src/pg-core/functions/index.ts b/drizzle-orm/src/pg-core/functions/index.ts deleted file mode 100644 index 64ed5f04f..000000000 --- a/drizzle-orm/src/pg-core/functions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './aggregate.ts'; diff --git a/drizzle-orm/src/pg-core/index.ts b/drizzle-orm/src/pg-core/index.ts index 38e74216f..1a80ff7ad 100644 --- a/drizzle-orm/src/pg-core/index.ts +++ b/drizzle-orm/src/pg-core/index.ts @@ -4,7 +4,6 @@ export * from './columns/index.ts'; export * from './db.ts'; export * from './dialect.ts'; export * from './foreign-keys.ts'; -export * from './functions/index.ts'; export * from './indexes.ts'; export * from './primary-keys.ts'; export * from './query-builders/index.ts'; diff --git a/drizzle-orm/src/sql/functions/aggregate.ts b/drizzle-orm/src/sql/functions/aggregate.ts index fe0b2278d..3ca6a78d2 100644 --- a/drizzle-orm/src/sql/functions/aggregate.ts +++ b/drizzle-orm/src/sql/functions/aggregate.ts @@ -1,109 +1,129 @@ -import { is, entityKind } from '~/entity.ts'; -import { SQL, sql, type SQLWrapper, type SQLChunk, isSQLWrapper, type DriverValueDecoder, type GetDecoderResult } from '../sql.ts'; -import { type MaybeDistinct, getValueWithDistinct } from '~/distinct.ts'; -import { PgColumn } from '~/pg-core/columns/common.ts'; -import { MySqlColumn } from '~/mysql-core/columns/common.ts'; -import { SQLiteColumn } from '~/sqlite-core/columns/common.ts'; -import type { Dialect } from '~/column-builder.ts'; -import type { Column } from '~/column.ts'; +import { is } from '~/entity.ts'; +import { type SQL, sql, type SQLWrapper } from '../sql.ts'; +import { type AnyColumn, Column } from '~/column.ts'; -export interface AggregateFunction extends SQL { - mapWith< - TDecoder extends - | DriverValueDecoder - | DriverValueDecoder['mapFromDriverValue'], - >(decoder: TDecoder): AggregateFunction> +/** + * Returns the number of values in `expression`. + * + * ## Examples + * + * ```ts + * // Number employees with null values + * db.select({ value: count() }).from(employees) + * // Number of employees where `name` is not null + * db.select({ value: count(employees.name) }).from(employees) + * ``` + * + * @see countDistinct to get the number of non-duplicate values in `expression` + */ +export function count(expression?: SQLWrapper): SQL { + return sql`count(${expression || sql.raw('*')})`.mapWith(Number); } -export class AggregateFunction extends SQL { - static readonly [entityKind]: string = 'AggregateFunction'; - constructor(sql: SQL) { - super(sql.queryChunks); - } - - filterWhere(where?: SQL | undefined): this { - if (where) { - this.append(sql` filter (where ${where})`); - } - return this; - } +/** + * Returns the number of non-duplicate values in `expression`. + * + * ## Examples + * + * ```ts + * // Number of employees where `name` is distinct + * db.select({ value: countDistinct(employees.name) }).from(employees) + * ``` + * + * @see count to get the number of values in `expression`, including duplicates + */ +export function countDistinct(expression: SQLWrapper): SQL { + return sql`count(distinct ${expression})`.mapWith(Number); } -/** @internal */ -export function count$(dialect: T, expression?: MaybeDistinct | '*'): T extends 'pg' - ? AggregateFunction - : T extends 'mysql' - ? SQL - : AggregateFunction -{ - const { value, distinct } = getValueWithDistinct(expression); - const chunks: SQLChunk[] = []; - - if (distinct) { - chunks.push(sql`distinct `); - } - chunks.push(isSQLWrapper(value) ? value : sql`*`); - - let fn = sql.join([sql`count(`, ...chunks, sql`)` ]); - fn = (dialect === 'mysql' ? fn : new AggregateFunction(fn)) - .mapWith(dialect === 'sqlite' ? Number : BigInt); - return fn as any; +/** + * Returns the average (arithmetic mean) of all non-null values in `expression`. + * + * ## Examples + * + * ```ts + * // Average salary of an employee + * db.select({ value: avg(employees.salary) }).from(employees) + * ``` + * + * @see avgDistinct to get the average of all non-null and non-duplicate values in `expression` + */ +export function avg(expression: SQLWrapper): SQL { + return sql`avg(${expression})`.mapWith(String); } -/** @internal */ -export function avg$(dialect: T, expression: MaybeDistinct): T extends 'mysql' - ? SQL - : AggregateFunction -{ - const { value, distinct } = getValueWithDistinct(expression); - const chunks: SQLChunk[] = []; - - if (distinct) { - chunks.push(sql`distinct `); - } - chunks.push(value); - - let fn = sql.join([sql`avg(`, ...chunks, sql`)`]) - fn = (dialect === 'mysql' ? fn : new AggregateFunction(fn)).mapWith(String); - return fn as any; +/** + * Returns the average (arithmetic mean) of all non-null and non-duplicate values in `expression`. + * + * ## Examples + * + * ```ts + * // Average salary of an employee where `salary` is distinct + * db.select({ value: avgDistinct(employees.salary) }).from(employees) + * ``` + * + * @see avg to get the average of all non-null values in `expression`, including duplicates + */ +export function avgDistinct(expression: SQLWrapper): SQL { + return sql`avg(distinct ${expression})`.mapWith(String); } -/** @internal */ -export function sum$(dialect: T, expression: MaybeDistinct): T extends 'mysql' - ? SQL - : AggregateFunction -{ - const { value, distinct } = getValueWithDistinct(expression); - const chunks: SQLChunk[] = []; - - if (distinct) { - chunks.push(sql`distinct `); - } - chunks.push(value); +/** + * Returns the sum of all non-null values in `expression`. + * + * ## Examples + * + * ```ts + * // Sum of every employee's salary + * db.select({ value: sum(employees.salary) }).from(employees) + * ``` + * + * @see sumDistinct to get the sum of all non-null and non-duplicate values in `expression` + */ +export function sum(expression: SQLWrapper): SQL { + return sql`sum(${expression})`.mapWith(String); +} - let fn = sql.join([sql`sum(`, ...chunks, sql`)`]); - fn = (dialect === 'mysql' ? fn : new AggregateFunction(fn)).mapWith(String); - return fn as any; +/** + * Returns the sum of all non-null and non-duplicate values in `expression`. + * + * ## Examples + * + * ```ts + * // Sum of every employee's salary where `salary` is distinct (no duplicates) + * db.select({ value: sumDistinct(employees.salary) }).from(employees) + * ``` + * + * @see sum to get the sum of all non-null values in `expression`, including duplicates + */ +export function sumDistinct(expression: SQLWrapper): SQL { + return sql`sum(distinct ${expression})`.mapWith(String); } -/** @internal */ -export function max$(dialect: T1, expression: T2): T2 extends Column - ? (T1 extends 'mysql' ? SQL : AggregateFunction) - : (T1 extends 'mysql' ? SQL : AggregateFunction) -{ - let fn = sql.join([sql`max(`, expression, sql`)`]) - fn = (dialect === 'mysql' ? fn : new AggregateFunction(fn)) - .mapWith(is(expression, PgColumn) || is(expression, MySqlColumn) || is(expression, SQLiteColumn) ? expression : String); - return fn as any; +/** + * Returns the maximum value in `expression`. + * + * ## Examples + * + * ```ts + * // The employee with the highest salary + * db.select({ value: max(employees.salary) }).from(employees) + * ``` + */ +export function max(expression: T): SQL<(T extends AnyColumn ? T['_']['data'] : string) | null> { + return sql`max(${expression})`.mapWith(is(expression, Column) ? expression : String) as any; } -/** @internal */ -export function min$(dialect: T1, expression: T2): T2 extends Column - ? (T1 extends 'mysql' ? SQL : AggregateFunction) - : (T1 extends 'mysql' ? SQL : AggregateFunction) -{ - let fn = sql.join([sql`min(`, expression, sql`)`]); - fn = (dialect === 'mysql' ? fn : new AggregateFunction(fn)) - .mapWith(is(expression, PgColumn) || is(expression, MySqlColumn) || is(expression, SQLiteColumn) ? expression : String); - return fn as any; +/** + * Returns the minimum value in `expression`. + * + * ## Examples + * + * ```ts + * // The employee with the lowest salary + * db.select({ value: min(employees.salary) }).from(employees) + * ``` + */ +export function min(expression: T): SQL<(T extends AnyColumn ? T['_']['data'] : string) | null> { + return sql`min(${expression})`.mapWith(is(expression, Column) ? expression : String) as any; } diff --git a/drizzle-orm/src/sqlite-core/functions/aggregate.ts b/drizzle-orm/src/sqlite-core/functions/aggregate.ts deleted file mode 100644 index 4a47a8fa0..000000000 --- a/drizzle-orm/src/sqlite-core/functions/aggregate.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { SQLiteColumn } from '../columns/index.ts'; -import { AggregateFunction, count$, avg$, sum$, max$, min$ } from '~/sql/functions/index.ts'; -import { type SQLWrapper, type SQLChunk, sql } from '~/sql/sql.ts'; -import { getValueWithDistinct, type MaybeDistinct } from '~/distinct.ts'; - -/** - * Returns the number of values in `expression`. - * - * ## Examples - * - * ```ts - * // Number employees with null values - * db.select({ value: count() }).from(employees) - * // Number of employees where `name` is not null - * db.select({ value: count(employees.name) }).from(employees) - * // Number of employees where `name` is distinct (no duplicates) - * db.select({ value: count(distinct(employees.name)) }).from(employees) - * // Number of employees where their salaries are greater than $2,000 - * db.select({ value: count().filterWhere(gt(employees.salary, 2000)) }).from(employees) - * ``` - */ -export function count(expression?: MaybeDistinct | '*'): AggregateFunction { - return count$('sqlite', expression); -} - -/** - * Returns the average (arithmetic mean) of all non-null values in `expression`. - * - * ## Examples - * - * ```ts - * // Average salary of an employee - * db.select({ value: avg(employees.salary) }).from(employees) - * // Average salary of an employee where `salary` is distinct (no duplicates) - * db.select({ value: avg(distinct(employees.salary)) }).from(employees) - * // Average salary of an employee where their salaries are greater than $2,000 - * db.select({ value: avg(employees.salary).filterWhere(gt(employees.salary, 2000)) }).from(employees) - * ``` - */ -export function avg(expression: MaybeDistinct): AggregateFunction { - return avg$('sqlite', expression); -} - -/** - * Returns the sum of all non-null values in `expression`. - * - * ## Examples - * - * ```ts - * // Sum of every employee's salary - * db.select({ value: sum(employees.salary) }).from(employees) - * // Sum of every employee's salary where `salary` is distinct (no duplicates) - * db.select({ value: sum(distinct(employees.salary)) }).from(employees) - * // Sum of every employee's salary where their salaries are greater than $2,000 - * db.select({ value: sum(employees.salary).filterWhere(gt(employees.salary, 2000)) }).from(employees) - * ``` - * - * @see total for a function with the same purpose that's not part of the SQL standard and always returns a number - */ -export function sum(expression: MaybeDistinct): AggregateFunction { - return sum$('sqlite', expression); -} - -/** - * Returns the sum of all non-null values in `expression`. - * - * ## Examples - * - * ```ts - * // Sum of every employee's salary - * db.select({ value: total(employees.salary) }).from(employees) - * // Sum of every employee's salary where `salary` is distinct (no duplicates) - * db.select({ value: total(distinct(employees.salary)) }).from(employees) - * // Sum of every employee's salary where their salaries are greater than $2,000 - * db.select({ value: total(employees.salary).filterWhere(gt(employees.salary, 2000)) }).from(employees) - * ``` - * - * @see sum for a function with the same purpose that's part of the SQL standard and can return `null` - */ -export function total(expression: MaybeDistinct): AggregateFunction { - const { value, distinct } = getValueWithDistinct(expression); - const chunks: SQLChunk[] = []; - - if (distinct) { - chunks.push(sql`distinct `); - } - chunks.push(value); - - const fn = sql.join([sql`total(`, ...chunks, sql`)`]); - return new AggregateFunction(fn).mapWith(String); -} - -/** - * Returns the maximum value in `expression`. - * - * ## Examples - * - * ```ts - * // The employee with the highest salary - * db.select({ value: max(employees.salary) }).from(employees) - * // The employee with the highest salary but that's less than $2,000 - * db.select({ value: max(employees.salary).filterWhere(lt(employees.salary, 2000)) }).from(employees) - * ``` - */ -export function max(expression: T): T extends SQLiteColumn - ? AggregateFunction - : AggregateFunction -{ - return max$('sqlite', expression) as any; -} - -/** - * Returns the minimum value in `expression`. - * - * ## Examples - * - * ```ts - * // The employee with the lowest salary - * db.select({ value: min(employees.salary) }).from(employees) - * ``` - */ -export function min(expression: T): T extends SQLiteColumn - ? AggregateFunction - : AggregateFunction -{ - return min$('sqlite', expression) as any; -} diff --git a/drizzle-orm/src/sqlite-core/functions/index.ts b/drizzle-orm/src/sqlite-core/functions/index.ts deleted file mode 100644 index 64ed5f04f..000000000 --- a/drizzle-orm/src/sqlite-core/functions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './aggregate.ts'; diff --git a/drizzle-orm/src/sqlite-core/index.ts b/drizzle-orm/src/sqlite-core/index.ts index b7be85559..ac2a19f0a 100644 --- a/drizzle-orm/src/sqlite-core/index.ts +++ b/drizzle-orm/src/sqlite-core/index.ts @@ -4,7 +4,6 @@ export * from './columns/index.ts'; export * from './db.ts'; export * from './dialect.ts'; export * from './foreign-keys.ts'; -export * from './functions/index.ts'; export * from './indexes.ts'; export * from './primary-keys.ts'; export * from './query-builders/index.ts'; From bca8684a0230e228d1707affa76ff43321a42b0c Mon Sep 17 00:00:00 2001 From: Mario564 Date: Sat, 18 Nov 2023 16:37:18 -0600 Subject: [PATCH 19/50] Update integration tests --- integration-tests/tests/libsql.test.ts | 77 ++++++------------------- integration-tests/tests/mysql.test.ts | 52 ++++++++--------- integration-tests/tests/pg.test.ts | 78 ++++++++------------------ 3 files changed, 60 insertions(+), 147 deletions(-) diff --git a/integration-tests/tests/libsql.test.ts b/integration-tests/tests/libsql.test.ts index da79d9b96..762f9ed5b 100644 --- a/integration-tests/tests/libsql.test.ts +++ b/integration-tests/tests/libsql.test.ts @@ -15,17 +15,20 @@ import { placeholder, sql, TransactionRollbackError, - distinct, - isNotNull, - lt, + count, + countDistinct, + avg, + avgDistinct, + sum, + sumDistinct, + max, + min } from 'drizzle-orm'; import { drizzle, type LibSQLDatabase } from 'drizzle-orm/libsql'; import { migrate } from 'drizzle-orm/libsql/migrator'; import { alias, - avg, blob, - count, except, foreignKey, getTableConfig, @@ -33,15 +36,11 @@ import { int, integer, intersect, - max, - min, primaryKey, sqliteTable, sqliteTableCreator, sqliteView, - sum, text, - total, union, unionAll, } from 'drizzle-orm/sqlite-core'; @@ -2473,18 +2472,13 @@ test.serial('aggregate function: count', async (t) => { const table = aggregateTable; await setupAggregateFunctionsTest(db); - const result1 = await db.select({ value: count('*') }).from(table); + const result1 = await db.select({ value: count() }).from(table); const result2 = await db.select({ value: count(table.a) }).from(table); - const result3 = await db.select({ value: count(distinct(table.name)) }).from(table); - const result4 = await db.select({ - all: count(), - filtered: count().filterWhere(isNotNull(table.c)) - }).from(table); + const result3 = await db.select({ value: countDistinct(table.name) }).from(table); t.deepEqual(result1[0]?.value, 7); t.deepEqual(result2[0]?.value, 5); t.deepEqual(result3[0]?.value, 6); - t.deepEqual(result4[0], { all: 7, filtered: 5 }); }); test.serial('aggregate function: avg', async (t) => { @@ -2494,16 +2488,11 @@ test.serial('aggregate function: avg', async (t) => { const result1 = await db.select({ value: avg(table.a) }).from(table); const result2 = await db.select({ value: avg(table.nullOnly) }).from(table); - const result3 = await db.select({ value: avg(distinct(table.b)) }).from(table); - const result4 = await db.select({ - all: avg(table.c), - filtered: avg(table.c).filterWhere(lt(table.c, 60)) - }).from(table); + const result3 = await db.select({ value: avgDistinct(table.b) }).from(table); - t.deepEqual(result1[0]?.value, 24); + t.deepEqual(result1[0]?.value, '24'); t.deepEqual(result2[0]?.value, null); - t.deepEqual(result3[0]?.value, 42.5); - t.deepEqual(result4[0], { all: 76, filtered: 25 }); + t.deepEqual(result3[0]?.value, '42.5'); }); test.serial('aggregate function: sum', async (t) => { @@ -2513,35 +2502,11 @@ test.serial('aggregate function: sum', async (t) => { const result1 = await db.select({ value: sum(table.b) }).from(table); const result2 = await db.select({ value: sum(table.nullOnly) }).from(table); - const result3 = await db.select({ value: sum(distinct(table.b)) }).from(table); - const result4 = await db.select({ - all: sum(table.c), - filtered: sum(table.c).filterWhere(lt(table.c, 100)) - }).from(table); + const result3 = await db.select({ value: sumDistinct(table.b) }).from(table); - t.deepEqual(result1[0]?.value, 200); + t.deepEqual(result1[0]?.value, '200'); t.deepEqual(result2[0]?.value, null); - t.deepEqual(result3[0]?.value, 170); - t.deepEqual(result4[0], { all: 380, filtered: 110 }); -}); - -test.serial('aggregate function: total', async (t) => { - const { db } = t.context; - const table = aggregateTable; - await setupAggregateFunctionsTest(db); - - const result1 = await db.select({ value: total(table.b) }).from(table); - const result2 = await db.select({ value: total(table.nullOnly) }).from(table); - const result3 = await db.select({ value: total(distinct(table.b)) }).from(table); - const result4 = await db.select({ - all: total(table.c), - filtered: total(table.c).filterWhere(lt(table.c, 100)) - }).from(table); - - t.deepEqual(result1[0]?.value, 200); - t.deepEqual(result2[0]?.value, 0); - t.deepEqual(result3[0]?.value, 170); - t.deepEqual(result4[0], { all: 380, filtered: 110 }); + t.deepEqual(result3[0]?.value, '170'); }); test.serial('aggregate function: max', async (t) => { @@ -2551,14 +2516,9 @@ test.serial('aggregate function: max', async (t) => { const result1 = await db.select({ value: max(table.b) }).from(table); const result2 = await db.select({ value: max(table.nullOnly) }).from(table); - const result3 = await db.select({ - all: max(table.c), - filtered: max(table.c).filterWhere(lt(table.c, 100)) - }).from(table); t.deepEqual(result1[0]?.value, 90); t.deepEqual(result2[0]?.value, null); - t.deepEqual(result3[0], { all: 150, filtered: 60 }); }); test.serial('aggregate function: min', async (t) => { @@ -2568,12 +2528,7 @@ test.serial('aggregate function: min', async (t) => { const result1 = await db.select({ value: min(table.b) }).from(table); const result2 = await db.select({ value: min(table.nullOnly) }).from(table); - const result3 = await db.select({ - all: min(table.c), - filtered: min(table.c).filterWhere(gt(table.c, 20)) - }).from(table); t.deepEqual(result1[0]?.value, 10); t.deepEqual(result2[0]?.value, null); - t.deepEqual(result3[0], { all: 20, filtered: 30 }); }); diff --git a/integration-tests/tests/mysql.test.ts b/integration-tests/tests/mysql.test.ts index 25ec90b41..d916cc22d 100644 --- a/integration-tests/tests/mysql.test.ts +++ b/integration-tests/tests/mysql.test.ts @@ -15,14 +15,19 @@ import { placeholder, sql, TransactionRollbackError, - distinct, + sum, + sumDistinct, + count, + countDistinct, + avg, + avgDistinct, + max, + min, } from 'drizzle-orm'; import { alias, - avg, bigint, boolean, - count, date, datetime, except, @@ -34,9 +39,7 @@ import { intersect, intersectAll, json, - max, mediumint, - min, mysqlEnum, mysqlTable, mysqlTableCreator, @@ -44,7 +47,6 @@ import { primaryKey, serial, smallint, - sum, text, time, timestamp, @@ -2701,15 +2703,13 @@ test.serial('aggregate function: count', async (t) => { const table = aggregateTable; await setupAggregateFunctionsTest(db); - const result1 = await db.select({ value: count('*') }).from(table); - const result2 = await db.select({ value: count('*', { mode: 'number' }) }).from(table); - const result3 = await db.select({ value: count(table.a) }).from(table); - const result4 = await db.select({ value: count(distinct(table.name)) }).from(table); + const result1 = await db.select({ value: count() }).from(table); + const result2 = await db.select({ value: count(table.a) }).from(table); + const result3 = await db.select({ value: countDistinct(table.name) }).from(table); - t.deepEqual(result1[0]?.value, BigInt(7)); - t.deepEqual(result2[0]?.value, 7); - t.deepEqual(result3[0]?.value, BigInt(5)); - t.deepEqual(result4[0]?.value, BigInt(6)); + t.deepEqual(result1[0]?.value, 7); + t.deepEqual(result2[0]?.value, 5); + t.deepEqual(result3[0]?.value, 6); }); test.serial('aggregate function: avg', async (t) => { @@ -2718,16 +2718,12 @@ test.serial('aggregate function: avg', async (t) => { await setupAggregateFunctionsTest(db); const result1 = await db.select({ value: avg(table.b) }).from(table); - const result2 = await db.select({ value: avg(table.b, { mode: 'number' }) }).from(table); - const result3 = await db.select({ value: avg(table.b, { mode: 'bigint' }) }).from(table); - const result4 = await db.select({ value: avg(table.nullOnly) }).from(table); - const result5 = await db.select({ value: avg(distinct(table.b)) }).from(table); + const result2 = await db.select({ value: avg(table.nullOnly) }).from(table); + const result3 = await db.select({ value: avgDistinct(table.b) }).from(table); t.deepEqual(result1[0]?.value, '33.3333'); - t.deepEqual(result2[0]?.value, 33.3333); - t.deepEqual(result3[0]?.value, BigInt(33)); - t.deepEqual(result4[0]?.value, null); - t.deepEqual(result5[0]?.value, '42.5000'); + t.deepEqual(result2[0]?.value, null); + t.deepEqual(result3[0]?.value, '42.5000'); }); test.serial('aggregate function: sum', async (t) => { @@ -2736,16 +2732,12 @@ test.serial('aggregate function: sum', async (t) => { await setupAggregateFunctionsTest(db); const result1 = await db.select({ value: sum(table.b) }).from(table); - const result2 = await db.select({ value: sum(table.b, { mode: 'number' }) }).from(table); - const result3 = await db.select({ value: sum(table.b, { mode: 'bigint' }) }).from(table); - const result4 = await db.select({ value: sum(table.nullOnly) }).from(table); - const result5 = await db.select({ value: sum(distinct(table.b)) }).from(table); + const result2 = await db.select({ value: sum(table.nullOnly) }).from(table); + const result3 = await db.select({ value: sumDistinct(table.b) }).from(table); t.deepEqual(result1[0]?.value, '200'); - t.deepEqual(result2[0]?.value, 200); - t.deepEqual(result3[0]?.value, BigInt(200)); - t.deepEqual(result4[0]?.value, null); - t.deepEqual(result5[0]?.value, '170'); + t.deepEqual(result2[0]?.value, null); + t.deepEqual(result3[0]?.value, '170'); }); test.serial('aggregate function: max', async (t) => { diff --git a/integration-tests/tests/pg.test.ts b/integration-tests/tests/pg.test.ts index 2008688a1..cb31c7e14 100644 --- a/integration-tests/tests/pg.test.ts +++ b/integration-tests/tests/pg.test.ts @@ -20,8 +20,14 @@ import { sql, type SQLWrapper, TransactionRollbackError, - isNotNull, - distinct, + count, + countDistinct, + avg, + avgDistinct, + sum, + sumDistinct, + max, + min, } from 'drizzle-orm'; import { drizzle, type NodePgDatabase } from 'drizzle-orm/node-postgres'; import { migrate } from 'drizzle-orm/node-postgres/migrator'; @@ -59,11 +65,6 @@ import { uuid as pgUuid, varchar, pgEnum, - count, - avg, - sum, - max, - min, } from 'drizzle-orm/pg-core'; import getPort from 'get-port'; import pg from 'pg'; @@ -3199,20 +3200,13 @@ test.serial('aggregate function: count', async (t) => { const table = aggregateTable; await setupAggregateFunctionsTest(db); - const result1 = await db.select({ value: count('*') }).from(table); - const result2 = await db.select({ value: count('*', { mode: 'number' }) }).from(table); - const result3 = await db.select({ value: count(table.a) }).from(table); - const result4 = await db.select({ value: count(distinct(table.name)) }).from(table); - const result5 = await db.select({ - all: count(), - filtered: count().filterWhere(isNotNull(table.c)) - }).from(table); + const result1 = await db.select({ value: count() }).from(table); + const result2 = await db.select({ value: count(table.a) }).from(table); + const result3 = await db.select({ value: countDistinct(table.name) }).from(table); - t.deepEqual(result1[0]?.value, BigInt(7)); - t.deepEqual(result2[0]?.value, 7); - t.deepEqual(result3[0]?.value, BigInt(5)); - t.deepEqual(result4[0]?.value, BigInt(6)); - t.deepEqual(result5[0], { all: BigInt(7), filtered: BigInt(5) }); + t.deepEqual(result1[0]?.value, 7); + t.deepEqual(result2[0]?.value, 5); + t.deepEqual(result3[0]?.value, 6); }); test.serial('aggregate function: avg', async (t) => { @@ -3221,21 +3215,12 @@ test.serial('aggregate function: avg', async (t) => { await setupAggregateFunctionsTest(db); const result1 = await db.select({ value: avg(table.b) }).from(table); - const result2 = await db.select({ value: avg(table.b, { mode: 'number' }) }).from(table); - const result3 = await db.select({ value: avg(table.b, { mode: 'bigint' }) }).from(table); - const result4 = await db.select({ value: avg(table.nullOnly) }).from(table); - const result5 = await db.select({ value: avg(distinct(table.b)) }).from(table); - const result6 = await db.select({ - all: avg(table.c), - filtered: avg(table.c).filterWhere(lt(table.c, 100)) - }).from(table); + const result2 = await db.select({ value: avg(table.nullOnly) }).from(table); + const result3 = await db.select({ value: avgDistinct(table.b) }).from(table); t.deepEqual(result1[0]?.value, '33.3333333333333333'); - t.deepEqual(result2[0]?.value, 33.333333333333336); - t.deepEqual(result3[0]?.value, BigInt(33)); - t.deepEqual(result4[0]?.value, null); - t.deepEqual(result5[0]?.value, '42.5000000000000000'); - t.deepEqual(result6[0], { all: '76.0000000000000000', filtered: '36.6666666666666667' }); + t.deepEqual(result2[0]?.value, null); + t.deepEqual(result3[0]?.value, '42.5000000000000000'); }); test.serial('aggregate function: sum', async (t) => { @@ -3244,21 +3229,12 @@ test.serial('aggregate function: sum', async (t) => { await setupAggregateFunctionsTest(db); const result1 = await db.select({ value: sum(table.b) }).from(table); - const result2 = await db.select({ value: sum(table.b, { mode: 'number' }) }).from(table); - const result3 = await db.select({ value: sum(table.b, { mode: 'bigint' }) }).from(table); - const result4 = await db.select({ value: sum(table.nullOnly) }).from(table); - const result5 = await db.select({ value: sum(distinct(table.b)) }).from(table); - const result6 = await db.select({ - all: sum(table.c), - filtered: sum(table.c).filterWhere(lt(table.c, 100)) - }).from(table); + const result2 = await db.select({ value: sum(table.nullOnly) }).from(table); + const result3 = await db.select({ value: sumDistinct(table.b) }).from(table); t.deepEqual(result1[0]?.value, '200'); - t.deepEqual(result2[0]?.value, 200); - t.deepEqual(result3[0]?.value, BigInt(200)); - t.deepEqual(result4[0]?.value, null); - t.deepEqual(result5[0]?.value, '170'); - t.deepEqual(result6[0], { all: '380', filtered: '110' }); + t.deepEqual(result2[0]?.value, null); + t.deepEqual(result3[0]?.value, '170'); }); test.serial('aggregate function: max', async (t) => { @@ -3268,14 +3244,9 @@ test.serial('aggregate function: max', async (t) => { const result1 = await db.select({ value: max(table.b) }).from(table); const result2 = await db.select({ value: max(table.nullOnly) }).from(table); - const result3 = await db.select({ - all: max(table.c), - filtered: max(table.c).filterWhere(lt(table.c, 100)) - }).from(table); t.deepEqual(result1[0]?.value, 90); t.deepEqual(result2[0]?.value, null); - t.deepEqual(result3[0], { all: 150, filtered: 60 }); }); test.serial('aggregate function: min', async (t) => { @@ -3285,12 +3256,7 @@ test.serial('aggregate function: min', async (t) => { const result1 = await db.select({ value: min(table.b) }).from(table); const result2 = await db.select({ value: min(table.nullOnly) }).from(table); - const result3 = await db.select({ - all: min(table.c), - filtered: min(table.c).filterWhere(gt(table.c, 20)) - }).from(table); t.deepEqual(result1[0]?.value, 10); t.deepEqual(result2[0]?.value, null); - t.deepEqual(result3[0], { all: 20, filtered: 30 }); }); From 165f99e5a022daf1b1e432464caaf14c645eb0dc Mon Sep 17 00:00:00 2001 From: AndriiSherman Date: Sun, 19 Nov 2023 19:05:22 +0200 Subject: [PATCH 20/50] Add getSQL to PostgreSQL and MySQL --- drizzle-orm/src/mysql-core/query-builders/query.ts | 12 +++++++++++- drizzle-orm/src/mysql-core/view.ts | 4 ++-- drizzle-orm/src/pg-core/query-builders/query.ts | 13 +++++++++++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/drizzle-orm/src/mysql-core/query-builders/query.ts b/drizzle-orm/src/mysql-core/query-builders/query.ts index f14a5a74e..8efeb0692 100644 --- a/drizzle-orm/src/mysql-core/query-builders/query.ts +++ b/drizzle-orm/src/mysql-core/query-builders/query.ts @@ -105,7 +105,7 @@ export class MySqlRelationalQuery< ) as PreparedQueryKind; } - private _toSQL(): { query: BuildRelationalQueryResult; builtQuery: QueryWithTypings } { + private _getQuery() { const query = this.mode === 'planetscale' ? this.dialect.buildRelationalQueryWithoutLateralSubqueries({ fullSchema: this.fullSchema, @@ -125,12 +125,22 @@ export class MySqlRelationalQuery< queryConfig: this.config, tableAlias: this.tableConfig.tsName, }); + return query; + } + + private _toSQL(): { query: BuildRelationalQueryResult; builtQuery: QueryWithTypings } { + const query = this._getQuery(); const builtQuery = this.dialect.sqlToQuery(query.sql as SQL); return { builtQuery, query }; } + /** @internal */ + getSQL(): SQL { + return this._getQuery().sql as SQL; + } + toSQL(): Query { return this._toSQL().builtQuery; } diff --git a/drizzle-orm/src/mysql-core/view.ts b/drizzle-orm/src/mysql-core/view.ts index dd7d2f82d..4cc7d416c 100644 --- a/drizzle-orm/src/mysql-core/view.ts +++ b/drizzle-orm/src/mysql-core/view.ts @@ -2,15 +2,15 @@ import type { BuildColumns } from '~/column-builder.ts'; import { entityKind } from '~/entity.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { AddAliasToSelection } from '~/query-builders/select.types.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; import type { ColumnsSelection, SQL } from '~/sql/sql.ts'; import { getTableColumns } from '~/utils.ts'; import type { MySqlColumn, MySqlColumnBuilderBase } from './columns/index.ts'; import { QueryBuilder } from './query-builders/query-builder.ts'; import type { SelectedFields } from './query-builders/select.types.ts'; import { mysqlTable } from './table.ts'; -import { MySqlViewConfig } from './view-common.ts'; import { MySqlViewBase } from './view-base.ts'; -import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import { MySqlViewConfig } from './view-common.ts'; export interface ViewBuilderConfig { algorithm?: 'undefined' | 'merge' | 'temptable'; diff --git a/drizzle-orm/src/pg-core/query-builders/query.ts b/drizzle-orm/src/pg-core/query-builders/query.ts index ab0a99839..ef352e734 100644 --- a/drizzle-orm/src/pg-core/query-builders/query.ts +++ b/drizzle-orm/src/pg-core/query-builders/query.ts @@ -105,8 +105,8 @@ export class PgRelationalQuery extends QueryPromise { return this._prepare(name); } - private _toSQL(): { query: BuildRelationalQueryResult; builtQuery: QueryWithTypings } { - const query = this.dialect.buildRelationalQueryWithoutPK({ + private _getQuery() { + return this.dialect.buildRelationalQueryWithoutPK({ fullSchema: this.fullSchema, schema: this.schema, tableNamesMap: this.tableNamesMap, @@ -115,6 +115,15 @@ export class PgRelationalQuery extends QueryPromise { queryConfig: this.config, tableAlias: this.tableConfig.tsName, }); + } + + /** @internal */ + getSQL(): SQL { + return this._getQuery().sql as SQL; + } + + private _toSQL(): { query: BuildRelationalQueryResult; builtQuery: QueryWithTypings } { + const query = this._getQuery(); const builtQuery = this.dialect.sqlToQuery(query.sql as SQL); From 48d0d7a8bb6b0da48ce9efb28b07c0a3f9fee18a Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Sun, 19 Nov 2023 21:57:11 +0100 Subject: [PATCH 21/50] Add jsdoc for Pg insert & update; Fix jsdoc for Pg delete --- drizzle-orm/src/pg-core/db.ts | 60 ++++++++++++++-- .../src/pg-core/query-builders/delete.ts | 19 +++-- .../src/pg-core/query-builders/insert.ts | 72 +++++++++++++++++++ .../src/pg-core/query-builders/update.ts | 55 ++++++++++++++ 4 files changed, 192 insertions(+), 14 deletions(-) diff --git a/drizzle-orm/src/pg-core/db.ts b/drizzle-orm/src/pg-core/db.ts index e255c9ea6..7e2266feb 100644 --- a/drizzle-orm/src/pg-core/db.ts +++ b/drizzle-orm/src/pg-core/db.ts @@ -144,10 +144,61 @@ export class PgDatabase< }); } + /** + * Creates an update query. + * + * Calling this method without `.where()` clause will update all rows in a table. The `.where()` clause specifies which rows should be updated. + * + * Use `.set()` method to specify which values to update. + * + * See docs: {@link https://orm.drizzle.team/docs/update} + * + * @param table The table to update. + * + * @example + * + * ```ts + * // Update all rows in the 'cars' table + * await db.update(cars).set({ color: 'red' }); + * + * // Update rows with filters and conditions + * await db.update(cars).set({ color: 'red' }).where(eq(cars.brand, 'BMW')); + * + * // Update with returning clause + * const updatedCar: Car[] = await db.update(cars) + * .set({ color: 'red' }) + * .where(eq(cars.id, 1)) + * .returning(); + * ``` + */ update(table: TTable): PgUpdateBuilder { return new PgUpdateBuilder(table, this.session, this.dialect); } + /** + * Creates an insert query. + * + * Calling this method will create new rows in a table. Use `.values()` method to specify which values to insert. + * + * See docs: {@link https://orm.drizzle.team/docs/insert} + * + * @param table The table to insert into. + * + * @example + * + * ```ts + * // Insert one row + * await db.insert(cars).values({ brand: 'BMW' }); + * + * // Insert multiple rows + * await db.insert(cars).values([{ brand: 'BMW' }, { brand: 'Porsche' }]); + * + * // Insert with returning clause + * const insertedCar: Car[] = await db.insert(cars) + * .values({ brand: 'BMW' }) + * .returning(); + * ``` + */ insert(table: TTable): PgInsertBuilder { return new PgInsertBuilder(table, this.session, this.dialect); } @@ -155,10 +206,13 @@ export class PgDatabase< /** * Creates a delete query. * - * Calling this method without `.where()` clause will delete all rows in a table. The `.where()` clause specifies which records should be deleted. + * Calling this method without `.where()` clause will delete all rows in a table. The `.where()` clause specifies which rows should be deleted. * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * * @param table The table to delete from. - * ### Examples + * + * @example * * ```ts * // Delete all rows in the 'cars' table @@ -172,8 +226,6 @@ export class PgDatabase< * .where(eq(cars.id, 1)) * .returning(); * ``` - * - * See docs: {@link} https://orm.drizzle.team/docs/delete */ delete(table: TTable): PgDeleteBase { return new PgDeleteBase(table, this.session, this.dialect); diff --git a/drizzle-orm/src/pg-core/query-builders/delete.ts b/drizzle-orm/src/pg-core/query-builders/delete.ts index ad04e07cc..ca6257746 100644 --- a/drizzle-orm/src/pg-core/query-builders/delete.ts +++ b/drizzle-orm/src/pg-core/query-builders/delete.ts @@ -132,13 +132,14 @@ export class PgDeleteBase< /** * Adds a `where` clause to the query. * - * Calling this method will delete only those records that fulfill a specified condition. + * Calling this method will delete only those rows that fulfill a specified condition. * - * @param where the `where` clause. + * See docs: {@link https://orm.drizzle.team/docs/delete} * - * ### Examples + * @param where the `where` clause. * - * You can use conditional operators and `sql function` to filter the records to be deleted. + * @example + * You can use conditional operators and `sql function` to filter the rows to be deleted. * * ```ts * // Delete all cars with green color @@ -146,6 +147,7 @@ export class PgDeleteBase< * // or * db.delete(cars).where(sql`${cars.color} = 'green'`) * ``` + * * You can logically combine conditional operators with `and()` and `or()` operators: * * ```ts @@ -155,8 +157,6 @@ export class PgDeleteBase< * // Delete all cars with the green or blue color * db.delete(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); * ``` - * - * See docs: {@link} https://orm.drizzle.team/docs/delete */ where(where: SQL | undefined): PgDeleteWithout { this.config.where = where; @@ -166,10 +166,11 @@ export class PgDeleteBase< /** * Adds a `returning` clause to the query. * - * Calling this method will return the specified fields of the deleted records. If no fields are specified, all fields will be returned. + * Calling this method will return the specified fields of the deleted rows. If no fields are specified, all fields will be returned. * - * ### Examples + * See docs: {@link https://orm.drizzle.team/docs/delete#delete-with-return} * + * @example * ```ts * // Delete all cars with the green color and return all fields * const deletedCars: Car[] = await db.delete(cars) @@ -181,8 +182,6 @@ export class PgDeleteBase< * .where(eq(cars.color, 'green')) * .returning({ id: cars.id, brand: cars.brand }); * ``` - * - * See docs: {@link} https://orm.drizzle.team/docs/delete#delete-with-return */ returning(): PgDeleteReturningAll; returning( diff --git a/drizzle-orm/src/pg-core/query-builders/insert.ts b/drizzle-orm/src/pg-core/query-builders/insert.ts index b886426be..491d667d0 100644 --- a/drizzle-orm/src/pg-core/query-builders/insert.ts +++ b/drizzle-orm/src/pg-core/query-builders/insert.ts @@ -163,6 +163,26 @@ export class PgInsertBase< this.config = { table, values }; } + /** + * Adds a `returning` clause to the query. + * + * Calling this method will return the specified fields of the inserted rows. If no fields are specified, all fields will be returned. + * + * See docs: {@link https://orm.drizzle.team/docs/insert#insert-returning} + * + * @example + * ```ts + * // Insert one row and return all fields + * const insertedCar: Car[] = await db.insert(cars) + * .values({ brand: 'BMW' }) + * .returning(); + * + * // Insert one row and return only the id + * const insertedCarId: { id: number }[] = await db.insert(cars) + * .values({ brand: 'BMW' }) + * .returning({ id: cars.id }); + * ``` + */ returning(): PgInsertWithout, TDynamic, 'returning'>; returning( fields: TSelectedFields, @@ -174,6 +194,28 @@ export class PgInsertBase< return this as any; } + /** + * Adds an `on conflict do nothing` clause to the query. + * + * Calling this method simply avoids inserting a row as its alternative action. + * + * See docs: {@link https://orm.drizzle.team/docs/insert#on-conflict-do-nothing} + * + * @param config The `target` and `where` clauses. + * + * @example + * ```ts + * // Insert one row and cancel the insert if there's a conflict + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW' }) + * .onConflictDoNothing(); + * + * // Explicitly specify conflict target + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW' }) + * .onConflictDoNothing({ target: cars.id }); + * ``` + */ onConflictDoNothing( config: { target?: IndexColumn | IndexColumn[]; where?: SQL } = {}, ): PgInsertWithout { @@ -191,6 +233,36 @@ export class PgInsertBase< return this as any; } + + /** + * Adds an `on conflict do update` clause to the query. + * + * Calling this method will update the existing row that conflicts with the row proposed for insertion as its alternative action. + * + * See docs: {@link https://orm.drizzle.team/docs/insert#upserts-and-conflicts} + * + * @param config The `target`, `set` and `where` clauses. + * + * @example + * ```ts + * // Update the row if there's a conflict + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW' }) + * .onConflictDoUpdate({ + * target: cars.id, + * set: { brand: 'Porsche' } + * }); + * + * // Upsert with 'where' clause + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW' }) + * .onConflictDoUpdate({ + * target: cars.id, + * set: { brand: 'newBMW' }, + * where: sql`${cars.createdAt} > '2023-01-01'::date`, + * }); + * ``` + */ onConflictDoUpdate( config: PgInsertOnConflictDoUpdateConfig, ): PgInsertWithout { diff --git a/drizzle-orm/src/pg-core/query-builders/update.ts b/drizzle-orm/src/pg-core/query-builders/update.ts index 449f99149..edac87836 100644 --- a/drizzle-orm/src/pg-core/query-builders/update.ts +++ b/drizzle-orm/src/pg-core/query-builders/update.ts @@ -159,11 +159,66 @@ export class PgUpdateBase< this.config = { set, table }; } + /** + * Adds a 'where' clause to the query. + * + * Calling this method will update only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/update} + * + * @param where the 'where' clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be updated. + * + * ```ts + * // Update all cars with green color + * db.update(cars).set({ color: 'red' }) + * .where(eq(cars.color, 'green')); + * // or + * db.update(cars).set({ color: 'red' }) + * .where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Update all BMW cars with a green color + * db.update(cars).set({ color: 'red' }) + * .where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Update all cars with the green or blue color + * db.update(cars).set({ color: 'red' }) + * .where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ where(where: SQL | undefined): PgUpdateWithout { this.config.where = where; return this as any; } + /** + * Adds a `returning` clause to the query. + * + * Calling this method will return the specified fields of the updated rows. If no fields are specified, all fields will be returned. + * + * See docs: {@link https://orm.drizzle.team/docs/update#update-with-returning} + * + * @example + * ```ts + * // Update all cars with the green color and return all fields + * const updatedCars: Car[] = await db.update(cars) + * .set({ color: 'red' }) + * .where(eq(cars.color, 'green')) + * .returning(); + * + * // Update all cars with the green color and return only their id and brand fields + * const updatedCarsIdsAndBrands: { id: number, brand: string }[] = await db.update(cars) + * .set({ color: 'red' }) + * .where(eq(cars.color, 'green')) + * .returning({ id: cars.id, brand: cars.brand }); + * ``` + */ returning(): PgUpdateReturningAll; returning( fields: TSelectedFields, From b2224bf54c7024ebde82d694c417939608b3c932 Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Sun, 19 Nov 2023 23:10:06 +0100 Subject: [PATCH 22/50] Add jsdoc for MySQL delete, insert & update --- drizzle-orm/src/mysql-core/db.ts | 59 +++++++++++++++++++ .../src/mysql-core/query-builders/delete.ts | 29 +++++++++ .../src/mysql-core/query-builders/insert.ts | 26 ++++++++ .../src/mysql-core/query-builders/update.ts | 33 +++++++++++ 4 files changed, 147 insertions(+) diff --git a/drizzle-orm/src/mysql-core/db.ts b/drizzle-orm/src/mysql-core/db.ts index e3a07cee3..c959fc100 100644 --- a/drizzle-orm/src/mysql-core/db.ts +++ b/drizzle-orm/src/mysql-core/db.ts @@ -147,14 +147,73 @@ export class MySqlDatabase< }); } + /** + * Creates an update query. + * + * Calling this method without `.where()` clause will update all rows in a table. The `.where()` clause specifies which rows should be updated. + * + * Use `.set()` method to specify which values to update. + * + * See docs: {@link https://orm.drizzle.team/docs/update} + * + * @param table The table to update. + * + * @example + * + * ```ts + * // Update all rows in the 'cars' table + * await db.update(cars).set({ color: 'red' }); + * + * // Update rows with filters and conditions + * await db.update(cars).set({ color: 'red' }).where(eq(cars.brand, 'BMW')); + * ``` + */ update(table: TTable): MySqlUpdateBuilder { return new MySqlUpdateBuilder(table, this.session, this.dialect); } + /** + * Creates an insert query. + * + * Calling this method will create new rows in a table. Use `.values()` method to specify which values to insert. + * + * See docs: {@link https://orm.drizzle.team/docs/insert} + * + * @param table The table to insert into. + * + * @example + * + * ```ts + * // Insert one row + * await db.insert(cars).values({ brand: 'BMW' }); + * + * // Insert multiple rows + * await db.insert(cars).values([{ brand: 'BMW' }, { brand: 'Porsche' }]); + * ``` + */ insert(table: TTable): MySqlInsertBuilder { return new MySqlInsertBuilder(table, this.session, this.dialect); } + /** + * Creates a delete query. + * + * Calling this method without `.where()` clause will delete all rows in a table. The `.where()` clause specifies which rows should be deleted. + * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * + * @param table The table to delete from. + * + * @example + * + * ```ts + * // Delete all rows in the 'cars' table + * await db.delete(cars); + * + * // Delete rows with filters and conditions + * await db.delete(cars).where(eq(cars.color, 'green')); + * ``` + */ delete(table: TTable): MySqlDeleteBase { return new MySqlDeleteBase(table, this.session, this.dialect); } diff --git a/drizzle-orm/src/mysql-core/query-builders/delete.ts b/drizzle-orm/src/mysql-core/query-builders/delete.ts index 33588fd16..ab615c603 100644 --- a/drizzle-orm/src/mysql-core/query-builders/delete.ts +++ b/drizzle-orm/src/mysql-core/query-builders/delete.ts @@ -97,6 +97,35 @@ export class MySqlDeleteBase< this.config = { table }; } + /** + * Adds a `where` clause to the query. + * + * Calling this method will delete only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * + * @param where the `where` clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be deleted. + * + * ```ts + * // Delete all cars with green color + * db.delete(cars).where(eq(cars.color, 'green')); + * // or + * db.delete(cars).where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Delete all BMW cars with a green color + * db.delete(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Delete all cars with the green or blue color + * db.delete(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ where(where: SQL | undefined): MySqlDeleteWithout { this.config.where = where; return this as any; diff --git a/drizzle-orm/src/mysql-core/query-builders/insert.ts b/drizzle-orm/src/mysql-core/query-builders/insert.ts index 4a007d06d..7390d4219 100644 --- a/drizzle-orm/src/mysql-core/query-builders/insert.ts +++ b/drizzle-orm/src/mysql-core/query-builders/insert.ts @@ -158,6 +158,32 @@ export class MySqlInsertBase< this.config = { table, values, ignore }; } + /** + * Adds an `on duplicate key update` clause to the query. + * + * Calling this method will update update the row if any unique index conflicts. MySQL will automatically determine the conflict target based on the primary key and unique indexes. + * + * See docs: {@link https://orm.drizzle.team/docs/insert#on-duplicate-key-update} + * + * @param config The `set` clause + * + * @example + * ```ts + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW'}) + * .onDuplicateKeyUpdate({ set: { brand: 'Porsche' }}); + * ``` + * + * While MySQL does not directly support doing nothing on conflict, you can perform a no-op by setting any column's value to itself and achieve the same effect: + * + * ```ts + * import { sql } from 'drizzle-orm'; + * + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW' }) + * .onDuplicateKeyUpdate({ set: { id: sql`id` } }); + * ``` + */ onDuplicateKeyUpdate( config: MySqlInsertOnDuplicateKeyUpdateConfig, ): MySqlInsertWithout { diff --git a/drizzle-orm/src/mysql-core/query-builders/update.ts b/drizzle-orm/src/mysql-core/query-builders/update.ts index 94b884058..ce83e9b4b 100644 --- a/drizzle-orm/src/mysql-core/query-builders/update.ts +++ b/drizzle-orm/src/mysql-core/query-builders/update.ts @@ -131,6 +131,39 @@ export class MySqlUpdateBase< this.config = { set, table }; } + /** + * Adds a 'where' clause to the query. + * + * Calling this method will update only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/update} + * + * @param where the 'where' clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be updated. + * + * ```ts + * // Update all cars with green color + * db.update(cars).set({ color: 'red' }) + * .where(eq(cars.color, 'green')); + * // or + * db.update(cars).set({ color: 'red' }) + * .where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Update all BMW cars with a green color + * db.update(cars).set({ color: 'red' }) + * .where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Update all cars with the green or blue color + * db.update(cars).set({ color: 'red' }) + * .where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ where(where: SQL | undefined): MySqlUpdateWithout { this.config.where = where; return this as any; From 69193e44f213be43754ba1ff734f06b03ac50592 Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Sun, 19 Nov 2023 23:31:07 +0100 Subject: [PATCH 23/50] Add jsdoc for SQLite delete, update & insert --- drizzle-orm/src/sqlite-core/db.ts | 75 +++++++++++++++++++ .../src/sqlite-core/query-builders/delete.ts | 49 ++++++++++++ .../src/sqlite-core/query-builders/insert.ts | 71 ++++++++++++++++++ .../src/sqlite-core/query-builders/update.ts | 55 ++++++++++++++ 4 files changed, 250 insertions(+) diff --git a/drizzle-orm/src/sqlite-core/db.ts b/drizzle-orm/src/sqlite-core/db.ts index 18594a3ba..15bc5cadc 100644 --- a/drizzle-orm/src/sqlite-core/db.ts +++ b/drizzle-orm/src/sqlite-core/db.ts @@ -150,14 +150,89 @@ export class BaseSQLiteDatabase< }); } + /** + * Creates an update query. + * + * Calling this method without `.where()` clause will update all rows in a table. The `.where()` clause specifies which rows should be updated. + * + * Use `.set()` method to specify which values to update. + * + * See docs: {@link https://orm.drizzle.team/docs/update} + * + * @param table The table to update. + * + * @example + * + * ```ts + * // Update all rows in the 'cars' table + * await db.update(cars).set({ color: 'red' }); + * + * // Update rows with filters and conditions + * await db.update(cars).set({ color: 'red' }).where(eq(cars.brand, 'BMW')); + * + * // Update with returning clause + * const updatedCar: Car[] = await db.update(cars) + * .set({ color: 'red' }) + * .where(eq(cars.id, 1)) + * .returning(); + * ``` + */ update(table: TTable): SQLiteUpdateBuilder { return new SQLiteUpdateBuilder(table, this.session, this.dialect); } + /** + * Creates an insert query. + * + * Calling this method will create new rows in a table. Use `.values()` method to specify which values to insert. + * + * See docs: {@link https://orm.drizzle.team/docs/insert} + * + * @param table The table to insert into. + * + * @example + * + * ```ts + * // Insert one row + * await db.insert(cars).values({ brand: 'BMW' }); + * + * // Insert multiple rows + * await db.insert(cars).values([{ brand: 'BMW' }, { brand: 'Porsche' }]); + * + * // Insert with returning clause + * const insertedCar: Car[] = await db.insert(cars) + * .values({ brand: 'BMW' }) + * .returning(); + * ``` + */ insert(into: TTable): SQLiteInsertBuilder { return new SQLiteInsertBuilder(into, this.session, this.dialect); } + /** + * Creates a delete query. + * + * Calling this method without `.where()` clause will delete all rows in a table. The `.where()` clause specifies which rows should be deleted. + * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * + * @param table The table to delete from. + * + * @example + * + * ```ts + * // Delete all rows in the 'cars' table + * await db.delete(cars); + * + * // Delete rows with filters and conditions + * await db.delete(cars).where(eq(cars.color, 'green')); + * + * // Delete with returning clause + * const deletedCar: Car[] = await db.delete(cars) + * .where(eq(cars.id, 1)) + * .returning(); + * ``` + */ delete(from: TTable): SQLiteDeleteBase { return new SQLiteDeleteBase(from, this.session, this.dialect); } diff --git a/drizzle-orm/src/sqlite-core/query-builders/delete.ts b/drizzle-orm/src/sqlite-core/query-builders/delete.ts index 2296310f5..eee70047c 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/delete.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/delete.ts @@ -143,11 +143,60 @@ export class SQLiteDeleteBase< this.config = { table }; } + /** + * Adds a `where` clause to the query. + * + * Calling this method will delete only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * + * @param where the `where` clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be deleted. + * + * ```ts + * // Delete all cars with green color + * db.delete(cars).where(eq(cars.color, 'green')); + * // or + * db.delete(cars).where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Delete all BMW cars with a green color + * db.delete(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Delete all cars with the green or blue color + * db.delete(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ where(where: SQL | undefined): SQLiteDeleteWithout { this.config.where = where; return this as any; } + /** + * Adds a `returning` clause to the query. + * + * Calling this method will return the specified fields of the deleted rows. If no fields are specified, all fields will be returned. + * + * See docs: {@link https://orm.drizzle.team/docs/delete#delete-with-return} + * + * @example + * ```ts + * // Delete all cars with the green color and return all fields + * const deletedCars: Car[] = await db.delete(cars) + * .where(eq(cars.color, 'green')) + * .returning(); + * + * // Delete all cars with the green color and return only their id and brand fields + * const deletedCarsIdsAndBrands: { id: number, brand: string }[] = await db.delete(cars) + * .where(eq(cars.color, 'green')) + * .returning({ id: cars.id, brand: cars.brand }); + * ``` + */ returning(): SQLiteDeleteReturningAll; returning( fields: TSelectedFields, diff --git a/drizzle-orm/src/sqlite-core/query-builders/insert.ts b/drizzle-orm/src/sqlite-core/query-builders/insert.ts index 1190fe79e..9be8c653a 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/insert.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/insert.ts @@ -206,6 +206,26 @@ export class SQLiteInsertBase< this.config = { table, values }; } + /** + * Adds a `returning` clause to the query. + * + * Calling this method will return the specified fields of the inserted rows. If no fields are specified, all fields will be returned. + * + * See docs: {@link https://orm.drizzle.team/docs/insert#insert-returning} + * + * @example + * ```ts + * // Insert one row and return all fields + * const insertedCar: Car[] = await db.insert(cars) + * .values({ brand: 'BMW' }) + * .returning(); + * + * // Insert one row and return only the id + * const insertedCarId: { id: number }[] = await db.insert(cars) + * .values({ brand: 'BMW' }) + * .returning({ id: cars.id }); + * ``` + */ returning(): SQLiteInsertReturningAll; returning( fields: TSelectedFields, @@ -217,6 +237,28 @@ export class SQLiteInsertBase< return this as any; } + /** + * Adds an `on conflict do nothing` clause to the query. + * + * Calling this method simply avoids inserting a row as its alternative action. + * + * See docs: {@link https://orm.drizzle.team/docs/insert#on-conflict-do-nothing} + * + * @param config The `target` and `where` clauses. + * + * @example + * ```ts + * // Insert one row and cancel the insert if there's a conflict + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW' }) + * .onConflictDoNothing(); + * + * // Explicitly specify conflict target + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW' }) + * .onConflictDoNothing({ target: cars.id }); + * ``` + */ onConflictDoNothing(config: { target?: IndexColumn | IndexColumn[]; where?: SQL } = {}): this { if (config.target === undefined) { this.config.onConflict = sql`do nothing`; @@ -228,6 +270,35 @@ export class SQLiteInsertBase< return this; } + /** + * Adds an `on conflict do update` clause to the query. + * + * Calling this method will update the existing row that conflicts with the row proposed for insertion as its alternative action. + * + * See docs: {@link https://orm.drizzle.team/docs/insert#upserts-and-conflicts} + * + * @param config The `target`, `set` and `where` clauses. + * + * @example + * ```ts + * // Update the row if there's a conflict + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW' }) + * .onConflictDoUpdate({ + * target: cars.id, + * set: { brand: 'Porsche' } + * }); + * + * // Upsert with 'where' clause + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW' }) + * .onConflictDoUpdate({ + * target: cars.id, + * set: { brand: 'newBMW' }, + * where: sql`${cars.createdAt} > '2023-01-01'::date`, + * }); + * ``` + */ onConflictDoUpdate(config: SQLiteInsertOnConflictDoUpdateConfig): this { const targetSql = Array.isArray(config.target) ? sql`${config.target}` : sql`${[config.target]}`; const whereSql = config.where ? sql` where ${config.where}` : sql``; diff --git a/drizzle-orm/src/sqlite-core/query-builders/update.ts b/drizzle-orm/src/sqlite-core/query-builders/update.ts index 857a944d8..b848b28a5 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/update.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/update.ts @@ -174,11 +174,66 @@ export class SQLiteUpdateBase< this.config = { set, table }; } + /** + * Adds a 'where' clause to the query. + * + * Calling this method will update only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/update} + * + * @param where the 'where' clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be updated. + * + * ```ts + * // Update all cars with green color + * db.update(cars).set({ color: 'red' }) + * .where(eq(cars.color, 'green')); + * // or + * db.update(cars).set({ color: 'red' }) + * .where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Update all BMW cars with a green color + * db.update(cars).set({ color: 'red' }) + * .where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Update all cars with the green or blue color + * db.update(cars).set({ color: 'red' }) + * .where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ where(where: SQL | undefined): SQLiteUpdateWithout { this.config.where = where; return this as any; } + /** + * Adds a `returning` clause to the query. + * + * Calling this method will return the specified fields of the updated rows. If no fields are specified, all fields will be returned. + * + * See docs: {@link https://orm.drizzle.team/docs/update#update-with-returning} + * + * @example + * ```ts + * // Update all cars with the green color and return all fields + * const updatedCars: Car[] = await db.update(cars) + * .set({ color: 'red' }) + * .where(eq(cars.color, 'green')) + * .returning(); + * + * // Update all cars with the green color and return only their id and brand fields + * const updatedCarsIdsAndBrands: { id: number, brand: string }[] = await db.update(cars) + * .set({ color: 'red' }) + * .where(eq(cars.color, 'green')) + * .returning({ id: cars.id, brand: cars.brand }); + * ``` + */ returning(): SQLiteUpdateReturningAll; returning( fields: TSelectedFields, From 7eef2228e4bcdaa6e0367f9fecd009443442994b Mon Sep 17 00:00:00 2001 From: Angelelz Date: Mon, 20 Nov 2023 22:39:46 -0500 Subject: [PATCH 24/50] [MySql] fix correct args passed to the functions and added tests --- drizzle-orm/src/mysql-core/db.ts | 22 +- .../tests/replicas/mysql.test.ts | 218 ++++++++++++------ 2 files changed, 163 insertions(+), 77 deletions(-) diff --git a/drizzle-orm/src/mysql-core/db.ts b/drizzle-orm/src/mysql-core/db.ts index e3a07cee3..8fefa172d 100644 --- a/drizzle-orm/src/mysql-core/db.ts +++ b/drizzle-orm/src/mysql-core/db.ts @@ -2,7 +2,9 @@ import type { ResultSetHeader } from 'mysql2/promise'; import { entityKind } from '~/entity.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts'; +import { WithSubquery } from '~/subquery.ts'; import type { DrizzleTypeError } from '~/utils.ts'; import type { MySqlDialect } from './dialect.ts'; import { @@ -25,8 +27,6 @@ import type { } from './session.ts'; import type { WithSubqueryWithSelection } from './subquery.ts'; import type { MySqlTable } from './table.ts'; -import { WithSubquery } from '~/subquery.ts'; -import { SelectionProxyHandler } from '~/selection-proxy.ts'; export class MySqlDatabase< TQueryResult extends QueryResultHKT, @@ -194,15 +194,15 @@ export const withReplicas = < replicas: [Q, ...Q[]], getReplica: (replicas: Q[]) => Q = () => replicas[Math.floor(Math.random() * replicas.length)]!, ): MySQLWithReplicas => { - const select: Q['select'] = (...args: any) => getReplica(replicas).select(args); - const selectDistinct: Q['selectDistinct'] = (...args: any) => getReplica(replicas).selectDistinct(args); - const $with: Q['with'] = (...args: any) => getReplica(replicas).with(args); - - const update: Q['update'] = (...args: any) => primary.update(args); - const insert: Q['insert'] = (...args: any) => primary.insert(args); - const $delete: Q['delete'] = (...args: any) => primary.delete(args); - const execute: Q['execute'] = (...args: any) => primary.execute(args); - const transaction: Q['transaction'] = (...args: any) => primary.transaction(args); + const select: Q['select'] = (...args: []) => getReplica(replicas).select(...args); + const selectDistinct: Q['selectDistinct'] = (...args: []) => getReplica(replicas).selectDistinct(...args); + const $with: Q['with'] = (...args: []) => getReplica(replicas).with(...args); + + const update: Q['update'] = (...args: [any]) => primary.update(...args); + const insert: Q['insert'] = (...args: [any]) => primary.insert(...args); + const $delete: Q['delete'] = (...args: [any]) => primary.delete(...args); + const execute: Q['execute'] = (...args: [any]) => primary.execute(...args); + const transaction: Q['transaction'] = (...args: [any, any]) => primary.transaction(...args); return new Proxy( { diff --git a/integration-tests/tests/replicas/mysql.test.ts b/integration-tests/tests/replicas/mysql.test.ts index 62fa6af41..a7de02411 100644 --- a/integration-tests/tests/replicas/mysql.test.ts +++ b/integration-tests/tests/replicas/mysql.test.ts @@ -9,7 +9,11 @@ const usersTable = mysqlTable('users', { verified: boolean('verified').notNull().default(false), }); -describe('[select] read replicas postgres', () => { +const users = mysqlTable('users', { + id: serial('id' as string).primaryKey(), +}); + +describe('[select] read replicas mysql', () => { it('primary select', () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -21,9 +25,11 @@ describe('[select] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'select'); const spyRead2 = vi.spyOn(read2, 'select'); - db.$primary.select().from({} as any); + const query = db.$primary.select().from(users); expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(query.toSQL().sql).toEqual('select `id` from `users`'); + expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); }); @@ -43,15 +49,18 @@ describe('[select] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'select'); const spyRead2 = vi.spyOn(read2, 'select'); - db.select().from({} as any); + const query1 = db.select({ count: sql`count(*)`.as('count') }).from(users).limit(1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); - db.select().from({} as any); + expect(query1.toSQL().sql).toEqual('select count(*) as `count` from `users` limit ?'); + + const query2 = db.select().from(users); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select `id` from `users`'); }); it('single read replica select', () => { @@ -63,13 +72,15 @@ describe('[select] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'select'); const spyRead1 = vi.spyOn(read1, 'select'); - db.select().from({} as any); + const query1 = db.select().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select `id` from `users`'); - db.select().from({} as any); + const query2 = db.select().from(users); expect(spyRead1).toHaveBeenCalledTimes(2); + expect(query2.toSQL().sql).toEqual('select `id` from `users`'); }); it('single read replica select + primary select', () => { @@ -81,14 +92,16 @@ describe('[select] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'select'); const spyRead1 = vi.spyOn(read1, 'select'); - db.select().from({} as any); + const query1 = db.select({ id: users.id }).from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select `id` from `users`'); - db.$primary.select().from({} as any); + const query2 = db.$primary.select().from(users); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select `id` from `users`'); }); it('always first read select', () => { @@ -104,19 +117,22 @@ describe('[select] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'select'); const spyRead2 = vi.spyOn(read2, 'select'); - db.select().from({} as any); + const query1 = db.select().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select `id` from `users`'); + + const query2 = db.select().from(users); - db.select().from({} as any); expect(spyRead1).toHaveBeenCalledTimes(2); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query2.toSQL().sql).toEqual('select `id` from `users`'); }); }); -describe('[selectDistinct] read replicas postgres', () => { +describe('[selectDistinct] read replicas mysql', () => { it('primary selectDistinct', () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -128,11 +144,12 @@ describe('[selectDistinct] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'selectDistinct'); const spyRead2 = vi.spyOn(read2, 'selectDistinct'); - db.$primary.selectDistinct().from({} as any); + const query = db.$primary.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query.toSQL().sql).toEqual('select distinct `id` from `users`'); }); it('random replica selectDistinct', () => { @@ -150,15 +167,17 @@ describe('[selectDistinct] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'selectDistinct'); const spyRead2 = vi.spyOn(read2, 'selectDistinct'); - db.selectDistinct().from({} as any); + const query1 = db.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select distinct `id` from `users`'); - db.selectDistinct().from({} as any); + const query2 = db.selectDistinct().from(users); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select distinct `id` from `users`'); }); it('single read replica selectDistinct', () => { @@ -170,13 +189,15 @@ describe('[selectDistinct] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'selectDistinct'); const spyRead1 = vi.spyOn(read1, 'selectDistinct'); - db.selectDistinct().from({} as any); + const query1 = db.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select distinct `id` from `users`'); - db.selectDistinct().from({} as any); + const query2 = db.selectDistinct().from(users); expect(spyRead1).toHaveBeenCalledTimes(2); + expect(query2.toSQL().sql).toEqual('select distinct `id` from `users`'); }); it('single read replica selectDistinct + primary selectDistinct', () => { @@ -188,14 +209,16 @@ describe('[selectDistinct] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'selectDistinct'); const spyRead1 = vi.spyOn(read1, 'selectDistinct'); - db.selectDistinct().from({} as any); + const query1 = db.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select distinct `id` from `users`'); - db.$primary.selectDistinct().from({} as any); + const query2 = db.$primary.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select distinct `id` from `users`'); }); it('always first read selectDistinct', () => { @@ -211,19 +234,21 @@ describe('[selectDistinct] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'selectDistinct'); const spyRead2 = vi.spyOn(read2, 'selectDistinct'); - db.selectDistinct().from({} as any); + const query1 = db.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select distinct `id` from `users`'); - db.selectDistinct().from({} as any); + const query2 = db.selectDistinct().from(users); expect(spyRead1).toHaveBeenCalledTimes(2); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query2.toSQL().sql).toEqual('select distinct `id` from `users`'); }); }); -describe('[with] read replicas postgres', () => { +describe('[with] read replicas mysql', () => { it('primary with', () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -234,12 +259,17 @@ describe('[with] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'with'); const spyRead1 = vi.spyOn(read1, 'with'); const spyRead2 = vi.spyOn(read2, 'with'); + const obj1 = {} as any; + const obj2 = {} as any; + const obj3 = {} as any; + const obj4 = {} as any; - db.$primary.with(); + db.$primary.with(obj1, obj2, obj3, obj4); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(obj1, obj2, obj3, obj4); }); it('random replica with', () => { @@ -317,20 +347,25 @@ describe('[with] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'with'); const spyRead1 = vi.spyOn(read1, 'with'); const spyRead2 = vi.spyOn(read2, 'with'); + const obj1 = {} as any; + const obj2 = {} as any; + const obj3 = {} as any; - db.with(); + db.with(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(obj1); - db.with(); + db.with(obj2, obj3); expect(spyRead1).toHaveBeenCalledTimes(2); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(obj2, obj3); }); }); -describe('[update] replicas postgres', () => { +describe('[update] replicas mysql', () => { it('primary update', () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -342,27 +377,30 @@ describe('[update] replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'update'); const spyRead2 = vi.spyOn(read2, 'update'); - db.update({} as any); + const query1 = db.update(users).set({ id: 1 }); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('update `users` set `id` = ?'); - db.update({} as any); + const query2 = db.update(users).set({ id: 1 }); expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query2.toSQL().sql).toEqual('update `users` set `id` = ?'); - db.$primary.update({} as any); + const query3 = db.$primary.update(users).set({ id: 1 }); expect(spyPrimary).toHaveBeenCalledTimes(3); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query3.toSQL().sql).toEqual('update `users` set `id` = ?'); }); }); -describe('[delete] replicas postgres', () => { +describe('[delete] replicas mysql', () => { it('primary delete', () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -374,17 +412,21 @@ describe('[delete] replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'delete'); const spyRead2 = vi.spyOn(read2, 'delete'); - db.delete({} as any); + const query1 = db.delete(users); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(users); + expect(query1.toSQL().sql).toEqual('delete from `users`'); - db.delete({} as any); + const query2 = db.delete(users); expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenNthCalledWith(2, users); + expect(query2.toSQL().sql).toEqual('delete from `users`'); db.$primary.delete({} as any); @@ -394,7 +436,7 @@ describe('[delete] replicas postgres', () => { }); }); -describe('[insert] replicas postgres', () => { +describe('[insert] replicas mysql', () => { it('primary insert', () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -406,17 +448,20 @@ describe('[insert] replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'insert'); const spyRead2 = vi.spyOn(read2, 'insert'); - db.insert({} as any); + const query = db.insert(users).values({ id: 1 }); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(users); + expect(query.toSQL().sql).toEqual('insert into `users` (`id`) values (?)'); - db.insert({} as any); + db.insert(users); expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenNthCalledWith(2, users); db.$primary.insert({} as any); @@ -426,7 +471,7 @@ describe('[insert] replicas postgres', () => { }); }); -describe('[execute] replicas postgres', () => { +describe('[execute] replicas mysql', () => { it('primary execute', async () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -438,27 +483,29 @@ describe('[execute] replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'execute'); const spyRead2 = vi.spyOn(read2, 'execute'); - // expect(db.execute(sql``)).rejects.toThrow(); + expect(db.execute(sql``)).rejects.toThrow(); - try { - db.execute(sql``); - } catch { /* empty */ } + // try { + // db.execute(sql``); + // } catch { /* empty */ } expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); - try { - db.execute(sql``); - } catch { /* empty */ } + expect(db.execute(sql``)).rejects.toThrow(); + // try { + // db.execute(sql``); + // } catch { /* empty */ } expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); - try { - db.execute(sql``); - } catch { /* empty */ } + expect(db.execute(sql``)).rejects.toThrow(); + // try { + // db.execute(sql``); + // } catch { /* empty */ } expect(spyPrimary).toHaveBeenCalledTimes(3); expect(spyRead1).toHaveBeenCalledTimes(0); @@ -466,7 +513,7 @@ describe('[execute] replicas postgres', () => { }); }); -describe('[transaction] replicas postgres', () => { +describe('[transaction] replicas mysql', () => { it('primary transaction', async () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -477,22 +524,27 @@ describe('[transaction] replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'transaction'); const spyRead1 = vi.spyOn(read1, 'transaction'); const spyRead2 = vi.spyOn(read2, 'transaction'); - - expect(db.transaction(async (tx) => { + const txFn1 = async (tx: any) => { tx.select().from({} as any); - })).rejects.toThrow(); + }; + + expect(db.transaction(txFn1)).rejects.toThrow(); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(txFn1); - expect(db.transaction(async (tx) => { + const txFn2 = async (tx: any) => { tx.select().from({} as any); - })).rejects.toThrow(); + }; + + expect(db.transaction(txFn2)).rejects.toThrow(); expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenNthCalledWith(2, txFn2); expect(db.transaction(async (tx) => { tx.select().from({} as any); @@ -504,7 +556,7 @@ describe('[transaction] replicas postgres', () => { }); }); -describe('[findFirst] read replicas postgres', () => { +describe('[findFirst] read replicas mysql', () => { it('primary findFirst', () => { const primaryDb = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); const read1 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); @@ -515,12 +567,14 @@ describe('[findFirst] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findFirst'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findFirst'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findFirst'); + const obj = {} as any; - db.$primary.query.usersTable.findFirst(); + db.$primary.query.usersTable.findFirst(obj); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(obj); }); it('random replica findFirst', () => { @@ -537,16 +591,19 @@ describe('[findFirst] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findFirst'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findFirst'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findFirst'); + const par1 = {} as any; - db.query.usersTable.findFirst(); + db.query.usersTable.findFirst(par1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(par1); - db.query.usersTable.findFirst(); + const query = db.query.usersTable.findFirst(); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable` limit ?'); }); it('single read replica findFirst', () => { @@ -611,7 +668,7 @@ describe('[findFirst] read replicas postgres', () => { }); }); -describe('[findMany] read replicas postgres', () => { +describe('[findMany] read replicas mysql', () => { it('primary findMany', () => { const primaryDb = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); const read1 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); @@ -622,12 +679,15 @@ describe('[findMany] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findMany'); + const obj = {} as any; - db.$primary.query.usersTable.findMany(); + const query = db.$primary.query.usersTable.findMany(obj); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(obj); + expect(query.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); }); it('random replica findMany', () => { @@ -644,16 +704,23 @@ describe('[findMany] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findMany'); + const obj1 = {} as any; + const obj2 = {} as any; - db.query.usersTable.findMany(); + const query1 = db.query.usersTable.findMany(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); + expect(spyRead1).toHaveBeenCalledWith(obj1); + + const query2 = db.query.usersTable.findMany(obj2); - db.query.usersTable.findMany(); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); + expect(spyRead2).toHaveBeenCalledWith(obj2); }); it('single read replica findMany', () => { @@ -664,14 +731,20 @@ describe('[findMany] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); + const obj1 = {} as any; + const obj2 = {} as any; - db.query.usersTable.findMany(); + const query1 = db.query.usersTable.findMany(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledWith(obj1); + expect(query1.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); - db.query.usersTable.findMany(); + const query2 = db.query.usersTable.findMany(obj2); expect(spyRead1).toHaveBeenCalledTimes(2); + expect(spyRead1).toHaveBeenNthCalledWith(2, obj2); + expect(query2.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); }); it('single read replica findMany + primary findMany', () => { @@ -682,15 +755,22 @@ describe('[findMany] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); + const obj1 = {} as any; + const obj2 = {} as any; - db.query.usersTable.findMany(); + const query1 = db.query.usersTable.findMany(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledWith(obj1); + expect(query1.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); + + const query2 = db.$primary.query.usersTable.findMany(obj2); - db.$primary.query.usersTable.findMany(); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyPrimary).toHaveBeenNthCalledWith(1, obj2); + expect(query2.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); }); it('always first read findMany', () => { @@ -705,15 +785,21 @@ describe('[findMany] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findMany'); + const obj1 = {} as any; + const obj2 = {} as any; - db.query.usersTable.findMany(); + const query1 = db.query.usersTable.findMany(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(obj1); + expect(query1.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); - db.query.usersTable.findMany(); + const query2 = db.query.usersTable.findMany(obj2); expect(spyRead1).toHaveBeenCalledTimes(2); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenNthCalledWith(2, obj2); + expect(query2.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); }); -}); \ No newline at end of file +}); From bb9da247a5527905d14149be0909562dcb1f7f79 Mon Sep 17 00:00:00 2001 From: Angelelz Date: Tue, 21 Nov 2023 07:54:24 -0500 Subject: [PATCH 25/50] [MySql] replaced the proxy in the replica for a getter --- drizzle-orm/src/mysql-core/db.ts | 35 +++++++++++++------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/drizzle-orm/src/mysql-core/db.ts b/drizzle-orm/src/mysql-core/db.ts index 8fefa172d..4470081de 100644 --- a/drizzle-orm/src/mysql-core/db.ts +++ b/drizzle-orm/src/mysql-core/db.ts @@ -204,26 +204,19 @@ export const withReplicas = < const execute: Q['execute'] = (...args: [any]) => primary.execute(...args); const transaction: Q['transaction'] = (...args: [any, any]) => primary.transaction(...args); - return new Proxy( - { - ...primary, - update, - insert, - delete: $delete, - execute, - transaction, - $primary: primary, - select, - selectDistinct, - with: $with, + return { + ...primary, + update, + insert, + delete: $delete, + execute, + transaction, + $primary: primary, + select, + selectDistinct, + with: $with, + get query() { + return getReplica(replicas).query; }, - { - get(target, prop, _receiver) { - if (prop === 'query') { - return getReplica(replicas).query; - } - return target[prop as keyof typeof target]; - }, - }, - ); + }; }; From 135c46f8412650daf6eba5f93ca493aae0737207 Mon Sep 17 00:00:00 2001 From: Angelelz Date: Tue, 21 Nov 2023 08:33:46 -0500 Subject: [PATCH 26/50] [sqlite] fixed arguments passed correctly, eliminated proxy from with replica implementation and added tests --- drizzle-orm/src/sqlite-core/db.ts | 65 +++-- .../tests/replicas/sqlite.test.ts | 228 ++++++++++++------ 2 files changed, 185 insertions(+), 108 deletions(-) diff --git a/drizzle-orm/src/sqlite-core/db.ts b/drizzle-orm/src/sqlite-core/db.ts index 18594a3ba..011f30701 100644 --- a/drizzle-orm/src/sqlite-core/db.ts +++ b/drizzle-orm/src/sqlite-core/db.ts @@ -1,6 +1,7 @@ import { entityKind } from '~/entity.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts'; import type { SQLiteAsyncDialect, SQLiteSyncDialect } from '~/sqlite-core/dialect.ts'; import { @@ -24,7 +25,6 @@ import { RelationalQueryBuilder } from './query-builders/query.ts'; import { SQLiteRaw } from './query-builders/raw.ts'; import type { SelectedFields } from './query-builders/select.types.ts'; import type { WithSubqueryWithSelection } from './subquery.ts'; -import { SelectionProxyHandler } from '~/selection-proxy.ts'; export class BaseSQLiteDatabase< TResultKind extends 'sync' | 'async', @@ -244,42 +244,35 @@ export const withReplicas = < replicas: [Q, ...Q[]], getReplica: (replicas: Q[]) => Q = () => replicas[Math.floor(Math.random() * replicas.length)]!, ): SQLiteWithReplicas => { - const select: Q['select'] = (...args: any) => getReplica(replicas).select(args); - const selectDistinct: Q['selectDistinct'] = (...args: any) => getReplica(replicas).selectDistinct(args); - const $with: Q['with'] = (...args: any) => getReplica(replicas).with(args); + const select: Q['select'] = (...args: []) => getReplica(replicas).select(...args); + const selectDistinct: Q['selectDistinct'] = (...args: []) => getReplica(replicas).selectDistinct(...args); + const $with: Q['with'] = (...args: []) => getReplica(replicas).with(...args); - const update: Q['update'] = (...args: any) => primary.update(args); - const insert: Q['insert'] = (...args: any) => primary.insert(args); - const $delete: Q['delete'] = (...args: any) => primary.delete(args); - const run: Q['run'] = (...args: any) => primary.run(args); - const all: Q['all'] = (...args: any) => primary.all(args); - const get: Q['get'] = (...args: any) => primary.get(args); - const values: Q['values'] = (...args: any) => primary.values(args); - const transaction: Q['transaction'] = (...args: any) => primary.transaction(args); + const update: Q['update'] = (...args: [any]) => primary.update(...args); + const insert: Q['insert'] = (...args: [any]) => primary.insert(...args); + const $delete: Q['delete'] = (...args: [any]) => primary.delete(...args); + const run: Q['run'] = (...args: [any]) => primary.run(...args); + const all: Q['all'] = (...args: [any]) => primary.all(...args); + const get: Q['get'] = (...args: [any]) => primary.get(...args); + const values: Q['values'] = (...args: [any]) => primary.values(...args); + const transaction: Q['transaction'] = (...args: [any]) => primary.transaction(...args); - return new Proxy( - { - ...primary, - update, - insert, - delete: $delete, - run, - all, - get, - values, - transaction, - $primary: primary, - select, - selectDistinct, - with: $with, - }, - { - get(target, prop, _receiver) { - if (prop === 'query') { - return getReplica(replicas).query; - } - return target[prop as keyof typeof target]; - }, + return { + ...primary, + update, + insert, + delete: $delete, + run, + all, + get, + values, + transaction, + $primary: primary, + select, + selectDistinct, + with: $with, + get query() { + return getReplica(replicas).query; }, - ); + }; }; diff --git a/integration-tests/tests/replicas/sqlite.test.ts b/integration-tests/tests/replicas/sqlite.test.ts index b607ce1a0..7f00dbd1a 100644 --- a/integration-tests/tests/replicas/sqlite.test.ts +++ b/integration-tests/tests/replicas/sqlite.test.ts @@ -1,14 +1,19 @@ import { sql } from 'drizzle-orm'; -import { sqliteTable, int, text, withReplicas } from 'drizzle-orm/sqlite-core'; import { drizzle } from 'drizzle-orm/libsql'; +import { int, sqliteTable, text, withReplicas } from 'drizzle-orm/sqlite-core'; import { describe, expect, it, vi } from 'vitest'; const usersTable = sqliteTable('users', { id: int('id' as string).primaryKey(), name: text('name').notNull(), + verified: text('verified').notNull().default('true'), }); -describe('[select] read replicas postgres', () => { +const users = sqliteTable('users', { + id: int('id' as string).primaryKey(), +}); + +describe('[select] read replicas sqlite', () => { it('primary select', () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -20,9 +25,11 @@ describe('[select] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'select'); const spyRead2 = vi.spyOn(read2, 'select'); - db.$primary.select().from({} as any); + const query = db.$primary.select().from(users); expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(query.toSQL().sql).toEqual('select "id" from "users"'); + expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); }); @@ -42,15 +49,18 @@ describe('[select] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'select'); const spyRead2 = vi.spyOn(read2, 'select'); - db.select().from({} as any); + const query1 = db.select({ count: sql`count(*)`.as('count') }).from(users).limit(1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); - db.select().from({} as any); + expect(query1.toSQL().sql).toEqual('select count(*) as "count" from "users" limit ?'); + + const query2 = db.select().from(users); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select "id" from "users"'); }); it('single read replica select', () => { @@ -62,13 +72,15 @@ describe('[select] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'select'); const spyRead1 = vi.spyOn(read1, 'select'); - db.select().from({} as any); + const query1 = db.select().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select "id" from "users"'); - db.select().from({} as any); + const query2 = db.select().from(users); expect(spyRead1).toHaveBeenCalledTimes(2); + expect(query2.toSQL().sql).toEqual('select "id" from "users"'); }); it('single read replica select + primary select', () => { @@ -80,14 +92,16 @@ describe('[select] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'select'); const spyRead1 = vi.spyOn(read1, 'select'); - db.select().from({} as any); + const query1 = db.select({ id: users.id }).from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select "id" from "users"'); - db.$primary.select().from({} as any); + const query2 = db.$primary.select().from(users); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select "id" from "users"'); }); it('always first read select', () => { @@ -103,19 +117,22 @@ describe('[select] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'select'); const spyRead2 = vi.spyOn(read2, 'select'); - db.select().from({} as any); + const query1 = db.select().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select "id" from "users"'); + + const query2 = db.select().from(users); - db.select().from({} as any); expect(spyRead1).toHaveBeenCalledTimes(2); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query2.toSQL().sql).toEqual('select "id" from "users"'); }); }); -describe('[selectDistinct] read replicas postgres', () => { +describe('[selectDistinct] read replicas sqlite', () => { it('primary selectDistinct', () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -127,11 +144,12 @@ describe('[selectDistinct] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'selectDistinct'); const spyRead2 = vi.spyOn(read2, 'selectDistinct'); - db.$primary.selectDistinct().from({} as any); + const query = db.$primary.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query.toSQL().sql).toEqual('select distinct "id" from "users"'); }); it('random replica selectDistinct', () => { @@ -149,15 +167,17 @@ describe('[selectDistinct] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'selectDistinct'); const spyRead2 = vi.spyOn(read2, 'selectDistinct'); - db.selectDistinct().from({} as any); + const query1 = db.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select distinct "id" from "users"'); - db.selectDistinct().from({} as any); + const query2 = db.selectDistinct().from(users); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select distinct "id" from "users"'); }); it('single read replica selectDistinct', () => { @@ -169,13 +189,15 @@ describe('[selectDistinct] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'selectDistinct'); const spyRead1 = vi.spyOn(read1, 'selectDistinct'); - db.selectDistinct().from({} as any); + const query1 = db.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select distinct "id" from "users"'); - db.selectDistinct().from({} as any); + const query2 = db.selectDistinct().from(users); expect(spyRead1).toHaveBeenCalledTimes(2); + expect(query2.toSQL().sql).toEqual('select distinct "id" from "users"'); }); it('single read replica selectDistinct + primary selectDistinct', () => { @@ -187,14 +209,16 @@ describe('[selectDistinct] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'selectDistinct'); const spyRead1 = vi.spyOn(read1, 'selectDistinct'); - db.selectDistinct().from({} as any); + const query1 = db.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select distinct "id" from "users"'); - db.$primary.selectDistinct().from({} as any); + const query2 = db.$primary.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select distinct "id" from "users"'); }); it('always first read selectDistinct', () => { @@ -210,19 +234,21 @@ describe('[selectDistinct] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'selectDistinct'); const spyRead2 = vi.spyOn(read2, 'selectDistinct'); - db.selectDistinct().from({} as any); + const query1 = db.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select distinct "id" from "users"'); - db.selectDistinct().from({} as any); + const query2 = db.selectDistinct().from(users); expect(spyRead1).toHaveBeenCalledTimes(2); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query2.toSQL().sql).toEqual('select distinct "id" from "users"'); }); }); -describe('[with] read replicas postgres', () => { +describe('[with] read replicas sqlite', () => { it('primary with', () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -233,12 +259,17 @@ describe('[with] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'with'); const spyRead1 = vi.spyOn(read1, 'with'); const spyRead2 = vi.spyOn(read2, 'with'); + const obj1 = {} as any; + const obj2 = {} as any; + const obj3 = {} as any; + const obj4 = {} as any; - db.$primary.with(); + db.$primary.with(obj1, obj2, obj3, obj4); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(obj1, obj2, obj3, obj4); }); it('random replica with', () => { @@ -316,20 +347,25 @@ describe('[with] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'with'); const spyRead1 = vi.spyOn(read1, 'with'); const spyRead2 = vi.spyOn(read2, 'with'); + const obj1 = {} as any; + const obj2 = {} as any; + const obj3 = {} as any; - db.with(); + db.with(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(obj1); - db.with(); + db.with(obj2, obj3); expect(spyRead1).toHaveBeenCalledTimes(2); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(obj2, obj3); }); }); -describe('[update] replicas postgres', () => { +describe('[update] replicas sqlite', () => { it('primary update', () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -341,27 +377,30 @@ describe('[update] replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'update'); const spyRead2 = vi.spyOn(read2, 'update'); - db.update({} as any); + const query1 = db.update(users).set({ id: 1 }); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('update "users" set "id" = ?'); - db.update({} as any); + const query2 = db.update(users).set({ id: 1 }); expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query2.toSQL().sql).toEqual('update "users" set "id" = ?'); - db.$primary.update({} as any); + const query3 = db.$primary.update(users).set({ id: 1 }); expect(spyPrimary).toHaveBeenCalledTimes(3); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query3.toSQL().sql).toEqual('update "users" set "id" = ?'); }); }); -describe('[delete] replicas postgres', () => { +describe('[delete] replicas sqlite', () => { it('primary delete', () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -373,17 +412,21 @@ describe('[delete] replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'delete'); const spyRead2 = vi.spyOn(read2, 'delete'); - db.delete({} as any); + const query1 = db.delete(users); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(users); + expect(query1.toSQL().sql).toEqual('delete from "users"'); - db.delete({} as any); + const query2 = db.delete(users); expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenNthCalledWith(2, users); + expect(query2.toSQL().sql).toEqual('delete from "users"'); db.$primary.delete({} as any); @@ -393,7 +436,7 @@ describe('[delete] replicas postgres', () => { }); }); -describe('[insert] replicas postgres', () => { +describe('[insert] replicas sqlite', () => { it('primary insert', () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -405,17 +448,20 @@ describe('[insert] replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'insert'); const spyRead2 = vi.spyOn(read2, 'insert'); - db.insert({} as any); + const query = db.insert(users).values({ id: 1 }); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(users); + expect(query.toSQL().sql).toEqual('insert into "users" ("id") values (?)'); - db.insert({} as any); + db.insert(users); expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenNthCalledWith(2, users); db.$primary.insert({} as any); @@ -425,7 +471,7 @@ describe('[insert] replicas postgres', () => { }); }); -describe('[execute] replicas postgres', () => { +describe('[execute] replicas sqlite', () => { it('primary execute', async () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -437,27 +483,25 @@ describe('[execute] replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'all'); const spyRead2 = vi.spyOn(read2, 'all'); - // expect(db.execute(sql``)).rejects.toThrow(); - - try { - db.all(sql``); - } catch { /* empty */ } + expect(db.all(sql``)).rejects.toThrow(); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); - try { - db.all(sql``); - } catch { /* empty */ } + expect(db.all(sql``)).rejects.toThrow(); + // try { + // db.execute(sql``); + // } catch { /* empty */ } expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); - try { - db.all(sql``); - } catch { /* empty */ } + expect(db.all(sql``)).rejects.toThrow(); + // try { + // db.execute(sql``); + // } catch { /* empty */ } expect(spyPrimary).toHaveBeenCalledTimes(3); expect(spyRead1).toHaveBeenCalledTimes(0); @@ -465,7 +509,7 @@ describe('[execute] replicas postgres', () => { }); }); -describe('[transaction] replicas postgres', () => { +describe('[transaction] replicas sqlite', () => { it('primary transaction', async () => { const primaryDb = drizzle({} as any); const read1 = drizzle({} as any); @@ -476,22 +520,27 @@ describe('[transaction] replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'transaction'); const spyRead1 = vi.spyOn(read1, 'transaction'); const spyRead2 = vi.spyOn(read2, 'transaction'); - - expect(db.transaction(async (tx) => { + const txFn1 = async (tx: any) => { tx.select().from({} as any); - })).rejects.toThrow(); + }; + + expect(db.transaction(txFn1)).rejects.toThrow(); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(txFn1); - expect(db.transaction(async (tx) => { + const txFn2 = async (tx: any) => { tx.select().from({} as any); - })).rejects.toThrow(); + }; + + expect(db.transaction(txFn2)).rejects.toThrow(); expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenNthCalledWith(2, txFn2); expect(db.transaction(async (tx) => { tx.select().from({} as any); @@ -503,7 +552,7 @@ describe('[transaction] replicas postgres', () => { }); }); -describe('[findFirst] read replicas postgres', () => { +describe('[findFirst] read replicas sqlite', () => { it('primary findFirst', () => { const primaryDb = drizzle({} as any, { schema: { usersTable } }); const read1 = drizzle({} as any, { schema: { usersTable } }); @@ -514,19 +563,21 @@ describe('[findFirst] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findFirst'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findFirst'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findFirst'); + const obj = {} as any; - db.$primary.query.usersTable.findFirst(); + db.$primary.query.usersTable.findFirst(obj); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(obj); }); it('random replica findFirst', () => { const primaryDb = drizzle({} as any, { schema: { usersTable } }); const read1 = drizzle({} as any, { schema: { usersTable } }); const read2 = drizzle({} as any, { schema: { usersTable } }); - + const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); const db = withReplicas(primaryDb, [read1, read2], () => { @@ -536,16 +587,19 @@ describe('[findFirst] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findFirst'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findFirst'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findFirst'); + const par1 = {} as any; - db.query.usersTable.findFirst(); + db.query.usersTable.findFirst(par1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(par1); - db.query.usersTable.findFirst(); + const query = db.query.usersTable.findFirst(); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query.toSQL().sql).toEqual('select "id", "name", "verified" from "users" "usersTable" limit ?'); }); it('single read replica findFirst', () => { @@ -569,7 +623,7 @@ describe('[findFirst] read replicas postgres', () => { it('single read replica findFirst + primary findFirst', () => { const primaryDb = drizzle({} as any, { schema: { usersTable } }); const read1 = drizzle({} as any, { schema: { usersTable } }); - + const db = withReplicas(primaryDb, [read1]); const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findFirst'); @@ -610,7 +664,7 @@ describe('[findFirst] read replicas postgres', () => { }); }); -describe('[findMany] read replicas postgres', () => { +describe('[findMany] read replicas sqlite', () => { it('primary findMany', () => { const primaryDb = drizzle({} as any, { schema: { usersTable } }); const read1 = drizzle({} as any, { schema: { usersTable } }); @@ -621,19 +675,22 @@ describe('[findMany] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findMany'); + const obj = {} as any; - db.$primary.query.usersTable.findMany(); + const query = db.$primary.query.usersTable.findMany(obj); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(obj); + expect(query.toSQL().sql).toEqual('select "id", "name", "verified" from "users" "usersTable"'); }); it('random replica findMany', () => { const primaryDb = drizzle({} as any, { schema: { usersTable } }); const read1 = drizzle({} as any, { schema: { usersTable } }); const read2 = drizzle({} as any, { schema: { usersTable } }); - + const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); const db = withReplicas(primaryDb, [read1, read2], () => { @@ -643,16 +700,23 @@ describe('[findMany] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findMany'); + const obj1 = {} as any; + const obj2 = {} as any; - db.query.usersTable.findMany(); + const query1 = db.query.usersTable.findMany(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select "id", "name", "verified" from "users" "usersTable"'); + expect(spyRead1).toHaveBeenCalledWith(obj1); + + const query2 = db.query.usersTable.findMany(obj2); - db.query.usersTable.findMany(); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select "id", "name", "verified" from "users" "usersTable"'); + expect(spyRead2).toHaveBeenCalledWith(obj2); }); it('single read replica findMany', () => { @@ -663,33 +727,46 @@ describe('[findMany] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); + const obj1 = {} as any; + const obj2 = {} as any; - db.query.usersTable.findMany(); + const query1 = db.query.usersTable.findMany(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledWith(obj1); + expect(query1.toSQL().sql).toEqual('select "id", "name", "verified" from "users" "usersTable"'); - db.query.usersTable.findMany(); + const query2 = db.query.usersTable.findMany(obj2); expect(spyRead1).toHaveBeenCalledTimes(2); + expect(spyRead1).toHaveBeenNthCalledWith(2, obj2); + expect(query2.toSQL().sql).toEqual('select "id", "name", "verified" from "users" "usersTable"'); }); it('single read replica findMany + primary findMany', () => { const primaryDb = drizzle({} as any, { schema: { usersTable } }); const read1 = drizzle({} as any, { schema: { usersTable } }); - + const db = withReplicas(primaryDb, [read1]); const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); + const obj1 = {} as any; + const obj2 = {} as any; - db.query.usersTable.findMany(); + const query1 = db.query.usersTable.findMany(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledWith(obj1); + expect(query1.toSQL().sql).toEqual('select "id", "name", "verified" from "users" "usersTable"'); + + const query2 = db.$primary.query.usersTable.findMany(obj2); - db.$primary.query.usersTable.findMany(); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyPrimary).toHaveBeenNthCalledWith(1, obj2); + expect(query2.toSQL().sql).toEqual('select "id", "name", "verified" from "users" "usersTable"'); }); it('always first read findMany', () => { @@ -704,15 +781,22 @@ describe('[findMany] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findMany'); + const obj1 = {} as any; + const obj2 = {} as any; - db.query.usersTable.findMany(); + const query1 = db.query.usersTable.findMany(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(obj1); + expect(query1.toSQL().sql).toEqual('select "id", "name", "verified" from "users" "usersTable"'); - db.query.usersTable.findMany(); + const query2 = db.query.usersTable.findMany(obj2); expect(spyRead1).toHaveBeenCalledTimes(2); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenNthCalledWith(2, obj2); + expect(query2.toSQL().sql).toEqual('select "id", "name", "verified" from "users" "usersTable"'); }); -}); \ No newline at end of file +}); + From 08996b4def4573181a2d06284e2084c90c325338 Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Wed, 22 Nov 2023 17:21:06 +0100 Subject: [PATCH 27/50] Add jsdoc for Pg select --- drizzle-orm/src/pg-core/db.ts | 85 ++++++++++ .../src/pg-core/query-builders/select.ts | 155 ++++++++++++------ 2 files changed, 189 insertions(+), 51 deletions(-) diff --git a/drizzle-orm/src/pg-core/db.ts b/drizzle-orm/src/pg-core/db.ts index 7e2266feb..b30ec687b 100644 --- a/drizzle-orm/src/pg-core/db.ts +++ b/drizzle-orm/src/pg-core/db.ts @@ -106,6 +106,42 @@ export class PgDatabase< return { select }; } + /** + * Creates a select query. + * + * Calling this method with no arguments will select all columns from the table. Pass a selection object to specify the columns you want to select. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select} + * + * @param fields The selection object. + * + * @example + * + * ```ts + * // Select all columns and all rows from the 'cars' table + * const allCars: Car[] = await db.select().from(cars); + * + * // Select specific columns and all rows from the 'cars' table + * const carsIdsAndBrands: { id: number; brand: string }[] = await db.select({ + * id: cars.id, + * brand: cars.brand + * }) + * .from(cars); + * ``` + * + * Like in SQL, you can use arbitrary expressions as selection fields, not just table columns: + * + * ```ts + * // Select specific columns along with expression and all rows from the 'cars' table + * const carsIdsAndLowerNames: { id: number; lowerBrand: string }[] = await db.select({ + * id: cars.id, + * lowerBrand: sql`lower(${cars.brand})`, + * }) + * .from(cars); + * ``` + */ select(): PgSelectBuilder; select(fields: TSelection): PgSelectBuilder; select(fields?: SelectedFields): PgSelectBuilder { @@ -116,6 +152,30 @@ export class PgDatabase< }); } + /** + * Adds `distinct` expression to the select query. + * + * Calling this method will return only unique values. When multiple columns are selected, it returns rows with unique combinations of values in these columns. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select#distinct} + * + * @param fields The selection object. + * + * @example + * ```ts + * // Select all unique rows from the 'cars' table + * await db.selectDistinct() + * .from(cars) + * .orderyBy(cars.id, cars.brand, cars.color); + * + * // Select all unique brands from the 'cars' table + * await db.selectDistinct({ brand: cars.brand }) + * .from(cars) + * .orderBy(cars.brand); + * ``` + */ selectDistinct(): PgSelectBuilder; selectDistinct(fields: TSelection): PgSelectBuilder; selectDistinct(fields?: SelectedFields): PgSelectBuilder { @@ -127,6 +187,31 @@ export class PgDatabase< }); } + /** + * Adds `distinct on` expression to the select query. + * + * Calling this method will specify how the unique rows are determined. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select#distinct} + * + * @param on The expression defining uniqueness. + * @param fields The selection object. + * + * @example + * ```ts + * // Select the first row for each unique brand from the 'cars' table + * await db.selectDistinctOn([cars.brand]) + * .from(cars) + * .orderBy(cars.brand); + * + * // Selects the first occurrence of each unique car brand along with its color from the 'cars' table + * await db.selectDistinctOn([cars.brand], { brand: cars.brand, color: cars.color }) + * .from(cars) + * .orderBy(cars.brand, cars.color); + * ``` + */ selectDistinctOn(on: (PgColumn | SQLWrapper)[]): PgSelectBuilder; selectDistinctOn( on: (PgColumn | SQLWrapper)[], diff --git a/drizzle-orm/src/pg-core/query-builders/select.ts b/drizzle-orm/src/pg-core/query-builders/select.ts index b76c9c778..9fa447f15 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.ts @@ -355,18 +355,35 @@ export abstract class PgSelectQueryBuilderBase< return this as any; } - /** - * Specify a condition to narrow the result set. Multiple - * conditions can be combined with the `and` and `or` - * functions. - * - * ## Examples - * + /** + * Adds a `where` clause to the query. + * + * Calling this method will select only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/select#filtering} + * + * @param where the `where` clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be selected. + * * ```ts - * // Find cars made in the year 2000 - * db.select().from(cars).where(eq(cars.year, 2000)); + * // Select all cars with green color + * await db.select().from(cars).where(eq(cars.color, 'green')); + * // or + * await db.select().from(cars).where(sql`${cars.color} = 'green'`) * ``` - */ + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Select all BMW cars with a green color + * await db.select().from(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Select all cars with the green or blue color + * await db.select().from(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ where( where: ((aliases: this['_']['selection']) => SQL | undefined) | SQL | undefined, ): PgSelectWithout { @@ -383,11 +400,26 @@ export abstract class PgSelectQueryBuilderBase< } /** - * Sets the HAVING clause of this query, which often - * used with GROUP BY and filters rows after they've been - * grouped together and combined. - * - * {@link https://www.postgresql.org/docs/current/sql-select.html#SQL-HAVING | Postgres having clause documentation} + * Adds a `having` clause to the query. + * + * Calling this method will select only those rows that fulfill a specified condition. It is typically used with aggregate functions to filter the aggregated data based on a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/select#aggregations} + * + * @param having the `having` clause. + * + * @example + * + * ```ts + * // Select all brands with more than one car + * await db.select({ + * brand: cars.brand, + * count: sql`cast(count(${cars.id}) as int)`, + * }) + * .from(cars) + * .groupBy(cars.brand) + * .having(({ count }) => gt(count, 1)); + * ``` */ having( having: ((aliases: this['_']['selection']) => SQL | undefined) | SQL | undefined, @@ -405,22 +437,23 @@ export abstract class PgSelectQueryBuilderBase< } /** - * Specify the GROUP BY of this query: given - * a list of columns or SQL expressions, Postgres will - * combine all rows with the same values in those columns - * into a single row. - * - * ## Examples + * Adds a `group by` clause to the query. + * + * Calling this method will group rows that have the same values into summary rows, often used for aggregation purposes. + * + * See docs: {@link https://orm.drizzle.team/docs/select#aggregations} * + * @example + * * ```ts * // Group and count people by their last names - * db.select({ + * await db.select({ * lastName: people.lastName, - * count: sql`count(*)::integer` - * }).from(people).groupBy(people.lastName); + * count: sql`cast(count(*) as int)` + * }) + * .from(people) + * .groupBy(people.lastName); * ``` - * - * {@link https://www.postgresql.org/docs/current/sql-select.html#SQL-GROUPBY | Postgres GROUP BY documentation} */ groupBy( builder: (aliases: this['_']['selection']) => ValueOrArray, @@ -446,19 +479,28 @@ export abstract class PgSelectQueryBuilderBase< } /** - * Specify the ORDER BY clause of this query: a number of - * columns or SQL expressions that will control sorting - * of results. You can specify whether results are in ascending - * or descending order with the `asc()` and `desc()` operators. + * Adds an `order by` clause to the query. + * + * Calling this method will sort the result-set in ascending or descending order. By default, the sort order is ascending. + * + * See docs: {@link https://orm.drizzle.team/docs/select#order-by} * - * ## Examples + * @example * * ``` - * // Select cars by year released - * db.select().from(cars).orderBy(cars.year); + * // Select cars ordered by year + * await db.select().from(cars).orderBy(cars.year); + * ``` + * + * You can specify whether results are in ascending or descending order with the `asc()` and `desc()` operators. + * + * ```ts + * // Select cars ordered by year in descending order + * await db.select().from(cars).orderBy(desc(cars.year)); + * + * // Select cars ordered by year and price + * await db.select().from(cars).orderBy(asc(cars.year), desc(cars.price)); * ``` - * - * {@link https://www.postgresql.org/docs/current/sql-select.html#SQL-ORDERBY | Postgres ORDER BY documentation} */ orderBy( builder: (aliases: this['_']['selection']) => ValueOrArray, @@ -497,17 +539,20 @@ export abstract class PgSelectQueryBuilderBase< } /** - * Set the maximum number of rows that will be - * returned by this query. + * Adds a `limit` clause to the query. + * + * Calling this method will set the maximum number of rows that will be returned by this query. * - * ## Examples + * See docs: {@link https://orm.drizzle.team/docs/select#limit--offset} + * + * @param limit the `limit` clause. + * + * @example * * ```ts * // Get the first 10 people from this query. - * db.select().from(people).limit(10); + * await db.select().from(people).limit(10); * ``` - * - * {@link https://www.postgresql.org/docs/current/sql-select.html#SQL-LIMIT | Postgres LIMIT documentation} */ limit(limit: number | Placeholder): PgSelectWithout { if (this.config.setOperators.length > 0) { @@ -519,14 +564,19 @@ export abstract class PgSelectQueryBuilderBase< } /** - * Skip a number of rows when returning results - * from this query. - * - * ## Examples + * Adds an `offset` clause to the query. + * + * Calling this method will skip a number of rows when returning results from this query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#limit--offset} + * + * @param offset the `offset` clause. + * + * @example * * ```ts * // Get the 10th-20th people from this query. - * db.select().from(people).offset(10).limit(10); + * await db.select().from(people).offset(10).limit(10); * ``` */ offset(offset: number | Placeholder): PgSelectWithout { @@ -539,11 +589,14 @@ export abstract class PgSelectQueryBuilderBase< } /** - * The FOR clause specifies a lock strength for this query - * that controls how strictly it acquires exclusive access to - * the rows being queried. - * - * {@link https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE | PostgreSQL locking clause documentation} + * Adds a `for` clause to the query. + * + * Calling this method will specify a lock strength for this query that controls how strictly it acquires exclusive access to the rows being queried. + * + * See docs: {@link https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE} + * + * @param strength the lock strength. + * @param config the lock configuration. */ for(strength: LockStrength, config: LockConfig = {}): PgSelectWithout { this.config.lockingClause = { strength, config }; From cca2c444f05f5af351bb42c26a81f46be13271b0 Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Wed, 22 Nov 2023 18:00:41 +0100 Subject: [PATCH 28/50] Add await in examples --- drizzle-orm/src/pg-core/query-builders/delete.ts | 8 ++++---- drizzle-orm/src/pg-core/query-builders/update.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drizzle-orm/src/pg-core/query-builders/delete.ts b/drizzle-orm/src/pg-core/query-builders/delete.ts index ca6257746..65d0be978 100644 --- a/drizzle-orm/src/pg-core/query-builders/delete.ts +++ b/drizzle-orm/src/pg-core/query-builders/delete.ts @@ -143,19 +143,19 @@ export class PgDeleteBase< * * ```ts * // Delete all cars with green color - * db.delete(cars).where(eq(cars.color, 'green')); + * await db.delete(cars).where(eq(cars.color, 'green')); * // or - * db.delete(cars).where(sql`${cars.color} = 'green'`) + * await db.delete(cars).where(sql`${cars.color} = 'green'`) * ``` * * You can logically combine conditional operators with `and()` and `or()` operators: * * ```ts * // Delete all BMW cars with a green color - * db.delete(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * await db.delete(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); * * // Delete all cars with the green or blue color - * db.delete(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * await db.delete(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); * ``` */ where(where: SQL | undefined): PgDeleteWithout { diff --git a/drizzle-orm/src/pg-core/query-builders/update.ts b/drizzle-orm/src/pg-core/query-builders/update.ts index edac87836..eca990b86 100644 --- a/drizzle-orm/src/pg-core/query-builders/update.ts +++ b/drizzle-orm/src/pg-core/query-builders/update.ts @@ -173,10 +173,10 @@ export class PgUpdateBase< * * ```ts * // Update all cars with green color - * db.update(cars).set({ color: 'red' }) + * await db.update(cars).set({ color: 'red' }) * .where(eq(cars.color, 'green')); * // or - * db.update(cars).set({ color: 'red' }) + * await db.update(cars).set({ color: 'red' }) * .where(sql`${cars.color} = 'green'`) * ``` * @@ -184,11 +184,11 @@ export class PgUpdateBase< * * ```ts * // Update all BMW cars with a green color - * db.update(cars).set({ color: 'red' }) + * await db.update(cars).set({ color: 'red' }) * .where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); * * // Update all cars with the green or blue color - * db.update(cars).set({ color: 'red' }) + * await db.update(cars).set({ color: 'red' }) * .where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); * ``` */ From 475a531b732991bc2b8c0554d259a2ff015ce6df Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Wed, 22 Nov 2023 19:31:48 +0100 Subject: [PATCH 29/50] Add jsdoc for MySQL select --- drizzle-orm/src/mysql-core/db.ts | 60 ++++++++ .../src/mysql-core/query-builders/select.ts | 136 ++++++++++++++++++ 2 files changed, 196 insertions(+) diff --git a/drizzle-orm/src/mysql-core/db.ts b/drizzle-orm/src/mysql-core/db.ts index c959fc100..1092594ee 100644 --- a/drizzle-orm/src/mysql-core/db.ts +++ b/drizzle-orm/src/mysql-core/db.ts @@ -128,12 +128,72 @@ export class MySqlDatabase< return { select, selectDistinct }; } + /** + * Creates a select query. + * + * Calling this method with no arguments will select all columns from the table. Pass a selection object to specify the columns you want to select. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select} + * + * @param fields The selection object. + * + * @example + * + * ```ts + * // Select all columns and all rows from the 'cars' table + * const allCars: Car[] = await db.select().from(cars); + * + * // Select specific columns and all rows from the 'cars' table + * const carsIdsAndBrands: { id: number; brand: string }[] = await db.select({ + * id: cars.id, + * brand: cars.brand + * }) + * .from(cars); + * ``` + * + * Like in SQL, you can use arbitrary expressions as selection fields, not just table columns: + * + * ```ts + * // Select specific columns along with expression and all rows from the 'cars' table + * const carsIdsAndLowerNames: { id: number; lowerBrand: string }[] = await db.select({ + * id: cars.id, + * lowerBrand: sql`lower(${cars.brand})`, + * }) + * .from(cars); + * ``` + */ select(): MySqlSelectBuilder; select(fields: TSelection): MySqlSelectBuilder; select(fields?: SelectedFields): MySqlSelectBuilder { return new MySqlSelectBuilder({ fields: fields ?? undefined, session: this.session, dialect: this.dialect }); } + /** + * Adds `distinct` expression to the select query. + * + * Calling this method will return only unique values. When multiple columns are selected, it returns rows with unique combinations of values in these columns. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select#distinct} + * + * @param fields The selection object. + * + * @example + * ```ts + * // Select all unique rows from the 'cars' table + * await db.selectDistinct() + * .from(cars) + * .orderBy(cars.id, cars.brand, cars.color); + * + * // Select all unique brands from the 'cars' table + * await db.selectDistinct({ brand: cars.brand }) + * .from(cars) + * .orderBy(cars.brand); + * ``` + */ selectDistinct(): MySqlSelectBuilder; selectDistinct( fields: TSelection, diff --git a/drizzle-orm/src/mysql-core/query-builders/select.ts b/drizzle-orm/src/mysql-core/query-builders/select.ts index 23f31342e..58c2c0c0f 100644 --- a/drizzle-orm/src/mysql-core/query-builders/select.ts +++ b/drizzle-orm/src/mysql-core/query-builders/select.ts @@ -324,6 +324,35 @@ export abstract class MySqlSelectQueryBuilderBase< return this as any; } + /** + * Adds a `where` clause to the query. + * + * Calling this method will select only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/select#filtering} + * + * @param where the `where` clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be selected. + * + * ```ts + * // Select all cars with green color + * await db.select().from(cars).where(eq(cars.color, 'green')); + * // or + * await db.select().from(cars).where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Select all BMW cars with a green color + * await db.select().from(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Select all cars with the green or blue color + * await db.select().from(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ where( where: ((aliases: this['_']['selection']) => SQL | undefined) | SQL | undefined, ): MySqlSelectWithout { @@ -339,6 +368,28 @@ export abstract class MySqlSelectQueryBuilderBase< return this as any; } + /** + * Adds a `having` clause to the query. + * + * Calling this method will select only those rows that fulfill a specified condition. It is typically used with aggregate functions to filter the aggregated data based on a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/select#aggregations} + * + * @param having the `having` clause. + * + * @example + * + * ```ts + * // Select all brands with more than one car + * await db.select({ + * brand: cars.brand, + * count: sql`cast(count(${cars.id}) as int)`, + * }) + * .from(cars) + * .groupBy(cars.brand) + * .having(({ count }) => gt(count, 1)); + * ``` + */ having( having: ((aliases: this['_']['selection']) => SQL | undefined) | SQL | undefined, ): MySqlSelectWithout { @@ -354,6 +405,25 @@ export abstract class MySqlSelectQueryBuilderBase< return this as any; } + /** + * Adds a `group by` clause to the query. + * + * Calling this method will group rows that have the same values into summary rows, often used for aggregation purposes. + * + * See docs: {@link https://orm.drizzle.team/docs/select#aggregations} + * + * @example + * + * ```ts + * // Group and count people by their last names + * await db.select({ + * lastName: people.lastName, + * count: sql`cast(count(*) as int)` + * }) + * .from(people) + * .groupBy(people.lastName); + * ``` + */ groupBy( builder: (aliases: this['_']['selection']) => ValueOrArray, ): MySqlSelectWithout; @@ -377,6 +447,30 @@ export abstract class MySqlSelectQueryBuilderBase< return this as any; } + /** + * Adds an `order by` clause to the query. + * + * Calling this method will sort the result-set in ascending or descending order. By default, the sort order is ascending. + * + * See docs: {@link https://orm.drizzle.team/docs/select#order-by} + * + * @example + * + * ``` + * // Select cars ordered by year + * await db.select().from(cars).orderBy(cars.year); + * ``` + * + * You can specify whether results are in ascending or descending order with the `asc()` and `desc()` operators. + * + * ```ts + * // Select cars ordered by year in descending order + * await db.select().from(cars).orderBy(desc(cars.year)); + * + * // Select cars ordered by year and price + * await db.select().from(cars).orderBy(asc(cars.year), desc(cars.price)); + * ``` + */ orderBy( builder: (aliases: this['_']['selection']) => ValueOrArray, ): MySqlSelectWithout; @@ -413,6 +507,22 @@ export abstract class MySqlSelectQueryBuilderBase< return this as any; } + /** + * Adds a `limit` clause to the query. + * + * Calling this method will set the maximum number of rows that will be returned by this query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#limit--offset} + * + * @param limit the `limit` clause. + * + * @example + * + * ```ts + * // Get the first 10 people from this query. + * await db.select().from(people).limit(10); + * ``` + */ limit(limit: number): MySqlSelectWithout { if (this.config.setOperators.length > 0) { this.config.setOperators.at(-1)!.limit = limit; @@ -422,6 +532,22 @@ export abstract class MySqlSelectQueryBuilderBase< return this as any; } + /** + * Adds an `offset` clause to the query. + * + * Calling this method will skip a number of rows when returning results from this query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#limit--offset} + * + * @param offset the `offset` clause. + * + * @example + * + * ```ts + * // Get the 10th-20th people from this query. + * await db.select().from(people).offset(10).limit(10); + * ``` + */ offset(offset: number): MySqlSelectWithout { if (this.config.setOperators.length > 0) { this.config.setOperators.at(-1)!.offset = offset; @@ -431,6 +557,16 @@ export abstract class MySqlSelectQueryBuilderBase< return this as any; } + /** + * Adds a `for` clause to the query. + * + * Calling this method will specify a lock strength for this query that controls how strictly it acquires exclusive access to the rows being queried. + * + * See docs: {@link https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html} + * + * @param strength the lock strength. + * @param config the lock configuration. + */ for(strength: LockStrength, config: LockConfig = {}): MySqlSelectWithout { this.config.lockingClause = { strength, config }; return this as any; From f34576f1e82a822ef62d6cb9d3e8f3c839f32a7a Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Wed, 22 Nov 2023 22:59:22 +0100 Subject: [PATCH 30/50] fix example --- drizzle-orm/src/pg-core/db.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drizzle-orm/src/pg-core/db.ts b/drizzle-orm/src/pg-core/db.ts index b30ec687b..495a720e3 100644 --- a/drizzle-orm/src/pg-core/db.ts +++ b/drizzle-orm/src/pg-core/db.ts @@ -168,7 +168,7 @@ export class PgDatabase< * // Select all unique rows from the 'cars' table * await db.selectDistinct() * .from(cars) - * .orderyBy(cars.id, cars.brand, cars.color); + * .orderBy(cars.id, cars.brand, cars.color); * * // Select all unique brands from the 'cars' table * await db.selectDistinct({ brand: cars.brand }) From 0872c4fd39ea545a3a0ee4c65f11794bd18eb86e Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Wed, 22 Nov 2023 23:01:31 +0100 Subject: [PATCH 31/50] Add jsdoc for SQLite select --- drizzle-orm/src/sqlite-core/db.ts | 61 +++++++++ .../src/sqlite-core/query-builders/select.ts | 126 ++++++++++++++++++ 2 files changed, 187 insertions(+) diff --git a/drizzle-orm/src/sqlite-core/db.ts b/drizzle-orm/src/sqlite-core/db.ts index 15bc5cadc..7e5739a26 100644 --- a/drizzle-orm/src/sqlite-core/db.ts +++ b/drizzle-orm/src/sqlite-core/db.ts @@ -127,6 +127,42 @@ export class BaseSQLiteDatabase< return { select, selectDistinct }; } + /** + * Creates a select query. + * + * Calling this method with no arguments will select all columns from the table. Pass a selection object to specify the columns you want to select. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select} + * + * @param fields The selection object. + * + * @example + * + * ```ts + * // Select all columns and all rows from the 'cars' table + * const allCars: Car[] = await db.select().from(cars); + * + * // Select specific columns and all rows from the 'cars' table + * const carsIdsAndBrands: { id: number; brand: string }[] = await db.select({ + * id: cars.id, + * brand: cars.brand + * }) + * .from(cars); + * ``` + * + * Like in SQL, you can use arbitrary expressions as selection fields, not just table columns: + * + * ```ts + * // Select specific columns along with expression and all rows from the 'cars' table + * const carsIdsAndLowerNames: { id: number; lowerBrand: string }[] = await db.select({ + * id: cars.id, + * lowerBrand: sql`lower(${cars.brand})`, + * }) + * .from(cars); + * ``` + */ select(): SQLiteSelectBuilder; select( fields: TSelection, @@ -135,6 +171,31 @@ export class BaseSQLiteDatabase< return new SQLiteSelectBuilder({ fields: fields ?? undefined, session: this.session, dialect: this.dialect }); } + /** + * Adds `distinct` expression to the select query. + * + * Calling this method will return only unique values. When multiple columns are selected, it returns rows with unique combinations of values in these columns. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select#distinct} + * + * @param fields The selection object. + * + * @example + * + * ```ts + * // Select all unique rows from the 'cars' table + * await db.selectDistinct() + * .from(cars) + * .orderBy(cars.id, cars.brand, cars.color); + * + * // Select all unique brands from the 'cars' table + * await db.selectDistinct({ brand: cars.brand }) + * .from(cars) + * .orderBy(cars.brand); + * ``` + */ selectDistinct(): SQLiteSelectBuilder; selectDistinct( fields: TSelection, diff --git a/drizzle-orm/src/sqlite-core/query-builders/select.ts b/drizzle-orm/src/sqlite-core/query-builders/select.ts index 67cae227f..cae916640 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/select.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/select.ts @@ -326,6 +326,35 @@ export abstract class SQLiteSelectQueryBuilderBase< return this as any; } + /** + * Adds a `where` clause to the query. + * + * Calling this method will select only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/select#filtering} + * + * @param where the `where` clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be selected. + * + * ```ts + * // Select all cars with green color + * await db.select().from(cars).where(eq(cars.color, 'green')); + * // or + * await db.select().from(cars).where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Select all BMW cars with a green color + * await db.select().from(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Select all cars with the green or blue color + * await db.select().from(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ where( where: ((aliases: TSelection) => SQL | undefined) | SQL | undefined, ): SQLiteSelectWithout { @@ -341,6 +370,28 @@ export abstract class SQLiteSelectQueryBuilderBase< return this as any; } + /** + * Adds a `having` clause to the query. + * + * Calling this method will select only those rows that fulfill a specified condition. It is typically used with aggregate functions to filter the aggregated data based on a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/select#aggregations} + * + * @param having the `having` clause. + * + * @example + * + * ```ts + * // Select all brands with more than one car + * await db.select({ + * brand: cars.brand, + * count: sql`cast(count(${cars.id}) as int)`, + * }) + * .from(cars) + * .groupBy(cars.brand) + * .having(({ count }) => gt(count, 1)); + * ``` + */ having( having: ((aliases: this['_']['selection']) => SQL | undefined) | SQL | undefined, ): SQLiteSelectWithout { @@ -356,6 +407,25 @@ export abstract class SQLiteSelectQueryBuilderBase< return this as any; } + /** + * Adds a `group by` clause to the query. + * + * Calling this method will group rows that have the same values into summary rows, often used for aggregation purposes. + * + * See docs: {@link https://orm.drizzle.team/docs/select#aggregations} + * + * @example + * + * ```ts + * // Group and count people by their last names + * await db.select({ + * lastName: people.lastName, + * count: sql`cast(count(*) as int)` + * }) + * .from(people) + * .groupBy(people.lastName); + * ``` + */ groupBy( builder: (aliases: this['_']['selection']) => ValueOrArray, ): SQLiteSelectWithout; @@ -379,6 +449,30 @@ export abstract class SQLiteSelectQueryBuilderBase< return this as any; } + /** + * Adds an `order by` clause to the query. + * + * Calling this method will sort the result-set in ascending or descending order. By default, the sort order is ascending. + * + * See docs: {@link https://orm.drizzle.team/docs/select#order-by} + * + * @example + * + * ``` + * // Select cars ordered by year + * await db.select().from(cars).orderBy(cars.year); + * ``` + * + * You can specify whether results are in ascending or descending order with the `asc()` and `desc()` operators. + * + * ```ts + * // Select cars ordered by year in descending order + * await db.select().from(cars).orderBy(desc(cars.year)); + * + * // Select cars ordered by year and price + * await db.select().from(cars).orderBy(asc(cars.year), desc(cars.price)); + * ``` + */ orderBy( builder: (aliases: this['_']['selection']) => ValueOrArray, ): SQLiteSelectWithout; @@ -415,6 +509,22 @@ export abstract class SQLiteSelectQueryBuilderBase< return this as any; } + /** + * Adds a `limit` clause to the query. + * + * Calling this method will set the maximum number of rows that will be returned by this query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#limit--offset} + * + * @param limit the `limit` clause. + * + * @example + * + * ```ts + * // Get the first 10 people from this query. + * await db.select().from(people).limit(10); + * ``` + */ limit(limit: number | Placeholder): SQLiteSelectWithout { if (this.config.setOperators.length > 0) { this.config.setOperators.at(-1)!.limit = limit; @@ -424,6 +534,22 @@ export abstract class SQLiteSelectQueryBuilderBase< return this as any; } + /** + * Adds an `offset` clause to the query. + * + * Calling this method will skip a number of rows when returning results from this query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#limit--offset} + * + * @param offset the `offset` clause. + * + * @example + * + * ```ts + * // Get the 10th-20th people from this query. + * await db.select().from(people).offset(10).limit(10); + * ``` + */ offset(offset: number | Placeholder): SQLiteSelectWithout { if (this.config.setOperators.length > 0) { this.config.setOperators.at(-1)!.offset = offset; From 30bbc1ae54dc4d7cfe0ebdc16103764d1aadc5e8 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Wed, 22 Nov 2023 20:06:48 -0600 Subject: [PATCH 32/50] Remove unnecessary comments --- drizzle-orm/src/mysql-core/dialect.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drizzle-orm/src/mysql-core/dialect.ts b/drizzle-orm/src/mysql-core/dialect.ts index 31baa8d19..34d5bf907 100644 --- a/drizzle-orm/src/mysql-core/dialect.ts +++ b/drizzle-orm/src/mysql-core/dialect.ts @@ -26,14 +26,6 @@ import type { MySqlSelectConfig, MySqlSelectJoinConfig, SelectedFieldsOrdered } import type { MySqlUpdateConfig } from './query-builders/update.ts'; import type { MySqlSession } from './session.ts'; import { MySqlTable } from './table.ts'; - -// TODO find out how to use all/values. Seems like I need those functions -// Build project -// copy runtime tests to be sure it's working - -// Add mysql to drizzle-kit - -// Add Planetscale Driver and create example repo import { MySqlViewBase } from './view-base.ts'; export class MySqlDialect { From 5d51ab5d854c3186723b1be4fecc0ea51ec4d01b Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Thu, 23 Nov 2023 13:41:45 +0100 Subject: [PATCH 33/50] Add jsdoc for Pg joins --- .../src/pg-core/query-builders/select.ts | 117 +++++++++++++++--- 1 file changed, 100 insertions(+), 17 deletions(-) diff --git a/drizzle-orm/src/pg-core/query-builders/select.ts b/drizzle-orm/src/pg-core/query-builders/select.ts index 9fa447f15..7160634b0 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.ts @@ -268,35 +268,118 @@ export abstract class PgSelectQueryBuilderBase< } /** - * For each row of the table, include - * values from a matching row of the joined - * table, if there is a matching row. If not, - * all of the columns of the joined table - * will be set to null. + * Executes a `left join` operation by adding another table to the current query. + * + * Calling this method associates each row of the table with the corresponding row from the joined table, if a match is found. If no matching row exists, it sets all columns of the joined table to null. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#left-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User; pets: Pet | null }[] = await db.select() + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId)) + * ``` */ leftJoin = this.createJoin('left'); /** - * Includes all of the rows of the joined table. - * If there is no matching row in the main table, - * all the columns of the main table will be - * set to null. + * Executes a `right join` operation by adding another table to the current query. + * + * Calling this method associates each row of the joined table with the corresponding row from the main table, if a match is found. If no matching row exists, it sets all columns of the main table to null. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#right-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User | null; pets: Pet }[] = await db.select() + * .from(users) + * .rightJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number | null; petId: number }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .rightJoin(pets, eq(users.id, pets.ownerId)) + * ``` */ rightJoin = this.createJoin('right'); /** - * This is the default type of join. - * - * For each row of the table, the joined table - * needs to have a matching row, or it will - * be excluded from results. + * Executes an `inner join` operation, creating a new table by combining rows from two tables that have matching values. + * + * Calling this method retrieves rows that have corresponding entries in both joined tables. Rows without matching entries in either table are excluded, resulting in a table that includes only matching pairs. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#inner-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User; pets: Pet }[] = await db.select() + * .from(users) + * .innerJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number; petId: number }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .innerJoin(pets, eq(users.id, pets.ownerId)) + * ``` */ innerJoin = this.createJoin('inner'); /** - * Rows from both the main & joined are included, - * regardless of whether or not they have matching - * rows in the other table. + * Executes a `full join` operation by combining rows from two tables into a new table. + * + * Calling this method retrieves all rows from both main and joined tables, merging rows with matching values and filling in `null` for non-matching columns. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#full-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User | null; pets: Pet | null }[] = await db.select() + * .from(users) + * .fullJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number | null; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .fullJoin(pets, eq(users.id, pets.ownerId)) + * ``` */ fullJoin = this.createJoin('full'); From b2fac79ba24993df98787ffb5252b4b8c25246e2 Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Fri, 24 Nov 2023 09:31:01 +0100 Subject: [PATCH 34/50] Add jsdoc for MySQL joins --- .../src/mysql-core/query-builders/select.ts | 112 +++++++++++++++++- 1 file changed, 110 insertions(+), 2 deletions(-) diff --git a/drizzle-orm/src/mysql-core/query-builders/select.ts b/drizzle-orm/src/mysql-core/query-builders/select.ts index 58c2c0c0f..3eaf6dc72 100644 --- a/drizzle-orm/src/mysql-core/query-builders/select.ts +++ b/drizzle-orm/src/mysql-core/query-builders/select.ts @@ -261,12 +261,120 @@ export abstract class MySqlSelectQueryBuilderBase< }; } + /** + * Executes a `left join` operation by adding another table to the current query. + * + * Calling this method associates each row of the table with the corresponding row from the joined table, if a match is found. If no matching row exists, it sets all columns of the joined table to null. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#left-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User; pets: Pet | null }[] = await db.select() + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ leftJoin = this.createJoin('left'); - + + /** + * Executes a `right join` operation by adding another table to the current query. + * + * Calling this method associates each row of the joined table with the corresponding row from the main table, if a match is found. If no matching row exists, it sets all columns of the main table to null. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#right-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User | null; pets: Pet }[] = await db.select() + * .from(users) + * .rightJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number | null; petId: number }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .rightJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ rightJoin = this.createJoin('right'); + /** + * Executes an `inner join` operation, creating a new table by combining rows from two tables that have matching values. + * + * Calling this method retrieves rows that have corresponding entries in both joined tables. Rows without matching entries in either table are excluded, resulting in a table that includes only matching pairs. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#inner-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User; pets: Pet }[] = await db.select() + * .from(users) + * .innerJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number; petId: number }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .innerJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ innerJoin = this.createJoin('inner'); - + + /** + * Executes a `full join` operation by combining rows from two tables into a new table. + * + * Calling this method retrieves all rows from both main and joined tables, merging rows with matching values and filling in `null` for non-matching columns. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#full-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User | null; pets: Pet | null }[] = await db.select() + * .from(users) + * .fullJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number | null; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .fullJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ fullJoin = this.createJoin('full'); private createSetOperator( From 38ae3e1ebfbf5caf096f01a2d40b7b489e3c0952 Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Fri, 24 Nov 2023 09:33:48 +0100 Subject: [PATCH 35/50] Add jsdoc for SQLite joins --- .../src/sqlite-core/query-builders/select.ts | 110 +++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/drizzle-orm/src/sqlite-core/query-builders/select.ts b/drizzle-orm/src/sqlite-core/query-builders/select.ts index cae916640..56079d330 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/select.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/select.ts @@ -267,12 +267,120 @@ export abstract class SQLiteSelectQueryBuilderBase< }; } + /** + * Executes a `left join` operation by adding another table to the current query. + * + * Calling this method associates each row of the table with the corresponding row from the joined table, if a match is found. If no matching row exists, it sets all columns of the joined table to null. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#left-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User; pets: Pet | null }[] = await db.select() + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ leftJoin = this.createJoin('left'); - + + /** + * Executes a `right join` operation by adding another table to the current query. + * + * Calling this method associates each row of the joined table with the corresponding row from the main table, if a match is found. If no matching row exists, it sets all columns of the main table to null. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#right-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User | null; pets: Pet }[] = await db.select() + * .from(users) + * .rightJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number | null; petId: number }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .rightJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ rightJoin = this.createJoin('right'); + /** + * Executes an `inner join` operation, creating a new table by combining rows from two tables that have matching values. + * + * Calling this method retrieves rows that have corresponding entries in both joined tables. Rows without matching entries in either table are excluded, resulting in a table that includes only matching pairs. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#inner-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User; pets: Pet }[] = await db.select() + * .from(users) + * .innerJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number; petId: number }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .innerJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ innerJoin = this.createJoin('inner'); + /** + * Executes a `full join` operation by combining rows from two tables into a new table. + * + * Calling this method retrieves all rows from both main and joined tables, merging rows with matching values and filling in `null` for non-matching columns. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#full-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User | null; pets: Pet | null }[] = await db.select() + * .from(users) + * .fullJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number | null; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .fullJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ fullJoin = this.createJoin('full'); private createSetOperator( From 32cde366ca5168a7ec881cbcd2dcf161f2c9bcc4 Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Fri, 24 Nov 2023 11:48:23 +0100 Subject: [PATCH 36/50] Add jsdoc for Pg union, intersect, except --- .../src/pg-core/query-builders/select.ts | 182 +++++++++++++++++- 1 file changed, 181 insertions(+), 1 deletion(-) diff --git a/drizzle-orm/src/pg-core/query-builders/select.ts b/drizzle-orm/src/pg-core/query-builders/select.ts index 7160634b0..53676d205 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.ts @@ -415,16 +415,196 @@ export abstract class PgSelectQueryBuilderBase< }; } + /** + * Adds `union` set operator to the query. + * + * Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union} + * + * @example + * + * ```ts + * // Select all unique names from customers and users tables + * await db.select({ name: users.name }) + * .from(users) + * .union( + * db.select({ name: customers.name }).from(customers) + * ); + * // or + * import { union } from 'drizzle-orm/pg-core' + * + * await union( + * db.select({ name: users.name }).from(users), + * db.select({ name: customers.name }).from(customers) + * ); + * ``` + */ union = this.createSetOperator('union', false); + /** + * Adds `union all` set operator to the query. + * + * Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all} + * + * @example + * + * ```ts + * // Select all transaction ids from both online and in-store sales + * await db.select({ transaction: onlineSales.transactionId }) + * .from(onlineSales) + * .unionAll( + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * // or + * import { unionAll } from 'drizzle-orm/pg-core' + * + * await unionAll( + * db.select({ transaction: onlineSales.transactionId }).from(onlineSales), + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * ``` + */ unionAll = this.createSetOperator('union', true); + /** + * Adds `intersect` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets and eliminate duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect} + * + * @example + * + * ```ts + * // Select course names that are offered in both departments A and B + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .intersect( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * import { intersect } from 'drizzle-orm/pg-core' + * + * await intersect( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ intersect = this.createSetOperator('intersect', false); - + + /** + * Adds `intersect all` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets including all duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect-all} + * + * @example + * + * ```ts + * // Select all products and quantities that are ordered by both regular and VIP customers + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders) + * .intersectAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * // or + * import { intersectAll } from 'drizzle-orm/pg-core' + * + * await intersectAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ intersectAll = this.createSetOperator('intersect', true); + /** + * Adds `except` set operator to the query. + * + * Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except} + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .except( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * import { except } from 'drizzle-orm/pg-core' + * + * await except( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ except = this.createSetOperator('except', false); + /** + * Adds `except all` set operator to the query. + * + * Calling this method will retrieve all rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except-all} + * + * @example + * + * ```ts + * // Select all products that are ordered by regular customers but not by VIP customers + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered, + * }) + * .from(regularCustomerOrders) + * .exceptAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered, + * }) + * .from(vipCustomerOrders) + * ) + * // or + * import { exceptAll } from 'drizzle-orm/pg-core' + * + * await exceptAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ exceptAll = this.createSetOperator('except', true); /** @internal */ From eb24d748f8037fcf173d5218cb9d131ad7841379 Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Mon, 27 Nov 2023 09:56:44 +0100 Subject: [PATCH 37/50] Add jsdoc for Pg set operations (functions) --- .../src/pg-core/query-builders/select.ts | 182 +++++++++++++++++- 1 file changed, 181 insertions(+), 1 deletion(-) diff --git a/drizzle-orm/src/pg-core/query-builders/select.ts b/drizzle-orm/src/pg-core/query-builders/select.ts index 53676d205..9d2cb1c34 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.ts @@ -587,7 +587,7 @@ export abstract class PgSelectQueryBuilderBase< * quantityOrdered: vipCustomerOrders.quantityOrdered, * }) * .from(vipCustomerOrders) - * ) + * ); * // or * import { exceptAll } from 'drizzle-orm/pg-core' * @@ -1010,14 +1010,194 @@ const getPgSetOperators = () => ({ exceptAll, }); + /** + * Adds `union` set operator to the query. + * + * Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union} + * + * @example + * + * ```ts + * // Select all unique names from customers and users tables + * import { union } from 'drizzle-orm/pg-core' + * + * await union( + * db.select({ name: users.name }).from(users), + * db.select({ name: customers.name }).from(customers) + * ); + * // or + * await db.select({ name: users.name }) + * .from(users) + * .union( + * db.select({ name: customers.name }).from(customers) + * ); + * ``` + */ export const union = createSetOperator('union', false); + /** + * Adds `union all` set operator to the query. + * + * Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all} + * + * @example + * + * ```ts + * // Select all transaction ids from both online and in-store sales + * import { unionAll } from 'drizzle-orm/pg-core' + * + * await unionAll( + * db.select({ transaction: onlineSales.transactionId }).from(onlineSales), + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * // or + * await db.select({ transaction: onlineSales.transactionId }) + * .from(onlineSales) + * .unionAll( + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * ``` + */ export const unionAll = createSetOperator('union', true); + /** + * Adds `intersect` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets and eliminate duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect} + * + * @example + * + * ```ts + * // Select course names that are offered in both departments A and B + * import { intersect } from 'drizzle-orm/pg-core' + * + * await intersect( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .intersect( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ export const intersect = createSetOperator('intersect', false); + /** + * Adds `intersect all` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets including all duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect-all} + * + * @example + * + * ```ts + * // Select all products and quantities that are ordered by both regular and VIP customers + * import { intersectAll } from 'drizzle-orm/pg-core' + * + * await intersectAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * // or + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders) + * .intersectAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ export const intersectAll = createSetOperator('intersect', true); + /** + * Adds `except` set operator to the query. + * + * Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except} + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * import { except } from 'drizzle-orm/pg-core' + * + * await except( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .except( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ export const except = createSetOperator('except', false); + /** + * Adds `except all` set operator to the query. + * + * Calling this method will retrieve all rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except-all} + * + * @example + * + * ```ts + * // Select all products that are ordered by regular customers but not by VIP customers + * import { exceptAll } from 'drizzle-orm/pg-core' + * + * await exceptAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * // or + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered, + * }) + * .from(regularCustomerOrders) + * .exceptAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered, + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ export const exceptAll = createSetOperator('except', true); From aec895ca1149b9735f2de24b80f6f64eb86d732d Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Mon, 27 Nov 2023 10:11:06 +0100 Subject: [PATCH 38/50] Refactor jsdoc for Pg set operators --- .../src/pg-core/query-builders/select.ts | 360 +++++++++--------- 1 file changed, 180 insertions(+), 180 deletions(-) diff --git a/drizzle-orm/src/pg-core/query-builders/select.ts b/drizzle-orm/src/pg-core/query-builders/select.ts index 9d2cb1c34..a71bd53ab 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.ts @@ -1010,194 +1010,194 @@ const getPgSetOperators = () => ({ exceptAll, }); - /** - * Adds `union` set operator to the query. - * - * Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them. - * - * See docs: {@link https://orm.drizzle.team/docs/set-operations#union} - * - * @example - * - * ```ts - * // Select all unique names from customers and users tables - * import { union } from 'drizzle-orm/pg-core' - * - * await union( - * db.select({ name: users.name }).from(users), - * db.select({ name: customers.name }).from(customers) - * ); - * // or - * await db.select({ name: users.name }) - * .from(users) - * .union( - * db.select({ name: customers.name }).from(customers) - * ); - * ``` - */ +/** + * Adds `union` set operator to the query. + * + * Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union} + * + * @example + * + * ```ts + * // Select all unique names from customers and users tables + * import { union } from 'drizzle-orm/pg-core' + * + * await union( + * db.select({ name: users.name }).from(users), + * db.select({ name: customers.name }).from(customers) + * ); + * // or + * await db.select({ name: users.name }) + * .from(users) + * .union( + * db.select({ name: customers.name }).from(customers) + * ); + * ``` + */ export const union = createSetOperator('union', false); - /** - * Adds `union all` set operator to the query. - * - * Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them. - * - * See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all} - * - * @example - * - * ```ts - * // Select all transaction ids from both online and in-store sales - * import { unionAll } from 'drizzle-orm/pg-core' - * - * await unionAll( - * db.select({ transaction: onlineSales.transactionId }).from(onlineSales), - * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) - * ); - * // or - * await db.select({ transaction: onlineSales.transactionId }) - * .from(onlineSales) - * .unionAll( - * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) - * ); - * ``` - */ +/** + * Adds `union all` set operator to the query. + * + * Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all} + * + * @example + * + * ```ts + * // Select all transaction ids from both online and in-store sales + * import { unionAll } from 'drizzle-orm/pg-core' + * + * await unionAll( + * db.select({ transaction: onlineSales.transactionId }).from(onlineSales), + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * // or + * await db.select({ transaction: onlineSales.transactionId }) + * .from(onlineSales) + * .unionAll( + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * ``` + */ export const unionAll = createSetOperator('union', true); - /** - * Adds `intersect` set operator to the query. - * - * Calling this method will retain only the rows that are present in both result sets and eliminate duplicates. - * - * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect} - * - * @example - * - * ```ts - * // Select course names that are offered in both departments A and B - * import { intersect } from 'drizzle-orm/pg-core' - * - * await intersect( - * db.select({ courseName: depA.courseName }).from(depA), - * db.select({ courseName: depB.courseName }).from(depB) - * ); - * // or - * await db.select({ courseName: depA.courseName }) - * .from(depA) - * .intersect( - * db.select({ courseName: depB.courseName }).from(depB) - * ); - * ``` - */ +/** + * Adds `intersect` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets and eliminate duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect} + * + * @example + * + * ```ts + * // Select course names that are offered in both departments A and B + * import { intersect } from 'drizzle-orm/pg-core' + * + * await intersect( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .intersect( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ export const intersect = createSetOperator('intersect', false); - /** - * Adds `intersect all` set operator to the query. - * - * Calling this method will retain only the rows that are present in both result sets including all duplicates. - * - * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect-all} - * - * @example - * - * ```ts - * // Select all products and quantities that are ordered by both regular and VIP customers - * import { intersectAll } from 'drizzle-orm/pg-core' - * - * await intersectAll( - * db.select({ - * productId: regularCustomerOrders.productId, - * quantityOrdered: regularCustomerOrders.quantityOrdered - * }) - * .from(regularCustomerOrders), - * db.select({ - * productId: vipCustomerOrders.productId, - * quantityOrdered: vipCustomerOrders.quantityOrdered - * }) - * .from(vipCustomerOrders) - * ); - * // or - * await db.select({ - * productId: regularCustomerOrders.productId, - * quantityOrdered: regularCustomerOrders.quantityOrdered - * }) - * .from(regularCustomerOrders) - * .intersectAll( - * db.select({ - * productId: vipCustomerOrders.productId, - * quantityOrdered: vipCustomerOrders.quantityOrdered - * }) - * .from(vipCustomerOrders) - * ); - * ``` - */ +/** + * Adds `intersect all` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets including all duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect-all} + * + * @example + * + * ```ts + * // Select all products and quantities that are ordered by both regular and VIP customers + * import { intersectAll } from 'drizzle-orm/pg-core' + * + * await intersectAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * // or + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders) + * .intersectAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ export const intersectAll = createSetOperator('intersect', true); - /** - * Adds `except` set operator to the query. - * - * Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query. - * - * See docs: {@link https://orm.drizzle.team/docs/set-operations#except} - * - * @example - * - * ```ts - * // Select all courses offered in department A but not in department B - * import { except } from 'drizzle-orm/pg-core' - * - * await except( - * db.select({ courseName: depA.courseName }).from(depA), - * db.select({ courseName: depB.courseName }).from(depB) - * ); - * // or - * await db.select({ courseName: depA.courseName }) - * .from(depA) - * .except( - * db.select({ courseName: depB.courseName }).from(depB) - * ); - * ``` - */ +/** + * Adds `except` set operator to the query. + * + * Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except} + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * import { except } from 'drizzle-orm/pg-core' + * + * await except( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .except( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ export const except = createSetOperator('except', false); - /** - * Adds `except all` set operator to the query. - * - * Calling this method will retrieve all rows from the left query, except for the rows that are present in the result set of the right query. - * - * See docs: {@link https://orm.drizzle.team/docs/set-operations#except-all} - * - * @example - * - * ```ts - * // Select all products that are ordered by regular customers but not by VIP customers - * import { exceptAll } from 'drizzle-orm/pg-core' - * - * await exceptAll( - * db.select({ - * productId: regularCustomerOrders.productId, - * quantityOrdered: regularCustomerOrders.quantityOrdered - * }) - * .from(regularCustomerOrders), - * db.select({ - * productId: vipCustomerOrders.productId, - * quantityOrdered: vipCustomerOrders.quantityOrdered - * }) - * .from(vipCustomerOrders) - * ); - * // or - * await db.select({ - * productId: regularCustomerOrders.productId, - * quantityOrdered: regularCustomerOrders.quantityOrdered, - * }) - * .from(regularCustomerOrders) - * .exceptAll( - * db.select({ - * productId: vipCustomerOrders.productId, - * quantityOrdered: vipCustomerOrders.quantityOrdered, - * }) - * .from(vipCustomerOrders) - * ); - * ``` - */ +/** + * Adds `except all` set operator to the query. + * + * Calling this method will retrieve all rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except-all} + * + * @example + * + * ```ts + * // Select all products that are ordered by regular customers but not by VIP customers + * import { exceptAll } from 'drizzle-orm/pg-core' + * + * await exceptAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * // or + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered, + * }) + * .from(regularCustomerOrders) + * .exceptAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered, + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ export const exceptAll = createSetOperator('except', true); From 190159e928b73923fd0f0943775973734286fd31 Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Mon, 27 Nov 2023 10:20:04 +0100 Subject: [PATCH 39/50] Add jsdoc for MySQL set operations --- .../src/mysql-core/query-builders/select.ts | 360 ++++++++++++++++++ 1 file changed, 360 insertions(+) diff --git a/drizzle-orm/src/mysql-core/query-builders/select.ts b/drizzle-orm/src/mysql-core/query-builders/select.ts index 3eaf6dc72..588b0bec3 100644 --- a/drizzle-orm/src/mysql-core/query-builders/select.ts +++ b/drizzle-orm/src/mysql-core/query-builders/select.ts @@ -409,16 +409,196 @@ export abstract class MySqlSelectQueryBuilderBase< }; } + /** + * Adds `union` set operator to the query. + * + * Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union} + * + * @example + * + * ```ts + * // Select all unique names from customers and users tables + * await db.select({ name: users.name }) + * .from(users) + * .union( + * db.select({ name: customers.name }).from(customers) + * ); + * // or + * import { union } from 'drizzle-orm/mysql-core' + * + * await union( + * db.select({ name: users.name }).from(users), + * db.select({ name: customers.name }).from(customers) + * ); + * ``` + */ union = this.createSetOperator('union', false); + /** + * Adds `union all` set operator to the query. + * + * Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all} + * + * @example + * + * ```ts + * // Select all transaction ids from both online and in-store sales + * await db.select({ transaction: onlineSales.transactionId }) + * .from(onlineSales) + * .unionAll( + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * // or + * import { unionAll } from 'drizzle-orm/mysql-core' + * + * await unionAll( + * db.select({ transaction: onlineSales.transactionId }).from(onlineSales), + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * ``` + */ unionAll = this.createSetOperator('union', true); + /** + * Adds `intersect` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets and eliminate duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect} + * + * @example + * + * ```ts + * // Select course names that are offered in both departments A and B + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .intersect( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * import { intersect } from 'drizzle-orm/mysql-core' + * + * await intersect( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ intersect = this.createSetOperator('intersect', false); + /** + * Adds `intersect all` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets including all duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect-all} + * + * @example + * + * ```ts + * // Select all products and quantities that are ordered by both regular and VIP customers + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders) + * .intersectAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * // or + * import { intersectAll } from 'drizzle-orm/mysql-core' + * + * await intersectAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ intersectAll = this.createSetOperator('intersect', true); + /** + * Adds `except` set operator to the query. + * + * Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except} + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .except( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * import { except } from 'drizzle-orm/mysql-core' + * + * await except( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ except = this.createSetOperator('except', false); + /** + * Adds `except all` set operator to the query. + * + * Calling this method will retrieve all rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except-all} + * + * @example + * + * ```ts + * // Select all products that are ordered by regular customers but not by VIP customers + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered, + * }) + * .from(regularCustomerOrders) + * .exceptAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered, + * }) + * .from(vipCustomerOrders) + * ); + * // or + * import { exceptAll } from 'drizzle-orm/mysql-core' + * + * await exceptAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ exceptAll = this.createSetOperator('except', true); /** @internal */ @@ -822,14 +1002,194 @@ const getMySqlSetOperators = () => ({ exceptAll, }); +/** + * Adds `union` set operator to the query. + * + * Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union} + * + * @example + * + * ```ts + * // Select all unique names from customers and users tables + * import { union } from 'drizzle-orm/mysql-core' + * + * await union( + * db.select({ name: users.name }).from(users), + * db.select({ name: customers.name }).from(customers) + * ); + * // or + * await db.select({ name: users.name }) + * .from(users) + * .union( + * db.select({ name: customers.name }).from(customers) + * ); + * ``` + */ export const union = createSetOperator('union', false); +/** + * Adds `union all` set operator to the query. + * + * Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all} + * + * @example + * + * ```ts + * // Select all transaction ids from both online and in-store sales + * import { unionAll } from 'drizzle-orm/mysql-core' + * + * await unionAll( + * db.select({ transaction: onlineSales.transactionId }).from(onlineSales), + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * // or + * await db.select({ transaction: onlineSales.transactionId }) + * .from(onlineSales) + * .unionAll( + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * ``` + */ export const unionAll = createSetOperator('union', true); +/** + * Adds `intersect` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets and eliminate duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect} + * + * @example + * + * ```ts + * // Select course names that are offered in both departments A and B + * import { intersect } from 'drizzle-orm/mysql-core' + * + * await intersect( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .intersect( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ export const intersect = createSetOperator('intersect', false); +/** + * Adds `intersect all` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets including all duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect-all} + * + * @example + * + * ```ts + * // Select all products and quantities that are ordered by both regular and VIP customers + * import { intersectAll } from 'drizzle-orm/mysql-core' + * + * await intersectAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * // or + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders) + * .intersectAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ export const intersectAll = createSetOperator('intersect', true); +/** + * Adds `except` set operator to the query. + * + * Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except} + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * import { except } from 'drizzle-orm/mysql-core' + * + * await except( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .except( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ export const except = createSetOperator('except', false); +/** + * Adds `except all` set operator to the query. + * + * Calling this method will retrieve all rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except-all} + * + * @example + * + * ```ts + * // Select all products that are ordered by regular customers but not by VIP customers + * import { exceptAll } from 'drizzle-orm/mysql-core' + * + * await exceptAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * // or + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered, + * }) + * .from(regularCustomerOrders) + * .exceptAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered, + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ export const exceptAll = createSetOperator('except', true); From 2aa47c52577481fbf5ed0cf8104da47128f03479 Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Mon, 27 Nov 2023 10:32:29 +0100 Subject: [PATCH 40/50] Add jsdoc for SQLite set operations --- .../src/sqlite-core/query-builders/select.ts | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/drizzle-orm/src/sqlite-core/query-builders/select.ts b/drizzle-orm/src/sqlite-core/query-builders/select.ts index 56079d330..47447bf0d 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/select.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/select.ts @@ -415,12 +415,112 @@ export abstract class SQLiteSelectQueryBuilderBase< }; } + /** + * Adds `union` set operator to the query. + * + * Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union} + * + * @example + * + * ```ts + * // Select all unique names from customers and users tables + * await db.select({ name: users.name }) + * .from(users) + * .union( + * db.select({ name: customers.name }).from(customers) + * ); + * // or + * import { union } from 'drizzle-orm/sqlite-core' + * + * await union( + * db.select({ name: users.name }).from(users), + * db.select({ name: customers.name }).from(customers) + * ); + * ``` + */ union = this.createSetOperator('union', false); + /** + * Adds `union all` set operator to the query. + * + * Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all} + * + * @example + * + * ```ts + * // Select all transaction ids from both online and in-store sales + * await db.select({ transaction: onlineSales.transactionId }) + * .from(onlineSales) + * .unionAll( + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * // or + * import { unionAll } from 'drizzle-orm/sqlite-core' + * + * await unionAll( + * db.select({ transaction: onlineSales.transactionId }).from(onlineSales), + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * ``` + */ unionAll = this.createSetOperator('union', true); + /** + * Adds `intersect` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets and eliminate duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect} + * + * @example + * + * ```ts + * // Select course names that are offered in both departments A and B + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .intersect( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * import { intersect } from 'drizzle-orm/sqlite-core' + * + * await intersect( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ intersect = this.createSetOperator('intersect', false); + /** + * Adds `except` set operator to the query. + * + * Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except} + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .except( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * import { except } from 'drizzle-orm/sqlite-core' + * + * await except( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ except = this.createSetOperator('except', false); /** @internal */ @@ -820,10 +920,110 @@ const getSQLiteSetOperators = () => ({ except, }); +/** + * Adds `union` set operator to the query. + * + * Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union} + * + * @example + * + * ```ts + * // Select all unique names from customers and users tables + * import { union } from 'drizzle-orm/sqlite-core' + * + * await union( + * db.select({ name: users.name }).from(users), + * db.select({ name: customers.name }).from(customers) + * ); + * // or + * await db.select({ name: users.name }) + * .from(users) + * .union( + * db.select({ name: customers.name }).from(customers) + * ); + * ``` + */ export const union = createSetOperator('union', false); +/** + * Adds `union all` set operator to the query. + * + * Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all} + * + * @example + * + * ```ts + * // Select all transaction ids from both online and in-store sales + * import { unionAll } from 'drizzle-orm/sqlite-core' + * + * await unionAll( + * db.select({ transaction: onlineSales.transactionId }).from(onlineSales), + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * // or + * await db.select({ transaction: onlineSales.transactionId }) + * .from(onlineSales) + * .unionAll( + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * ``` + */ export const unionAll = createSetOperator('union', true); +/** + * Adds `intersect` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets and eliminate duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect} + * + * @example + * + * ```ts + * // Select course names that are offered in both departments A and B + * import { intersect } from 'drizzle-orm/sqlite-core' + * + * await intersect( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .intersect( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ export const intersect = createSetOperator('intersect', false); +/** + * Adds `except` set operator to the query. + * + * Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except} + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * import { except } from 'drizzle-orm/sqlite-core' + * + * await except( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .except( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ export const except = createSetOperator('except', false); From d14c86d14e46a17e45db33f05b3882124cd47405 Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Mon, 27 Nov 2023 13:34:21 +0100 Subject: [PATCH 41/50] Add jsdoc for Pg with clause --- drizzle-orm/src/pg-core/db.ts | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/drizzle-orm/src/pg-core/db.ts b/drizzle-orm/src/pg-core/db.ts index 495a720e3..1e9987e1a 100644 --- a/drizzle-orm/src/pg-core/db.ts +++ b/drizzle-orm/src/pg-core/db.ts @@ -72,6 +72,38 @@ export class PgDatabase< } } + /** + * Creates a subquery that defines a temporary named result set as a CTE. + * + * It is useful for breaking down complex queries into simpler parts and for reusing the result set in subsequent parts of the query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#with-clause} + * + * @param alias The alias for the subquery. + * + * Failure to provide an alias will result in a DrizzleTypeError, preventing the subquery from being referenced in other queries. + * + * @example + * + * ```ts + * // Create a subquery with alias 'sq' and use it in the select query + * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); + * + * const result = await db.with(sq).select().from(sq); + * ``` + * + * To select arbitrary SQL values as fields in a CTE and reference them in other CTEs or in the main query, you need to add aliases to them: + * + * ```ts + * // Select an arbitrary SQL value as a field in a CTE and reference it in the main query + * 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); + * ``` + */ $with(alias: TAlias) { return { as( @@ -89,6 +121,25 @@ export class PgDatabase< }; } + /** + * Incorporates a previously defined CTE (using `$with`) into the main query. + * + * This method allows the main query to reference a temporary named result set. + * + * See docs: {@link https://orm.drizzle.team/docs/select#with-clause} + * + * @param queries The CTEs to incorporate into the main query. + * + * @example + * + * ```ts + * // Define a subquery 'sq' as a CTE using $with + * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); + * + * // Incorporate the CTE 'sq' into the main query and select from it + * const result = await db.with(sq).select().from(sq); + * ``` + */ with(...queries: WithSubquery[]) { const self = this; From 6071b5fca94464169aa5b75419cde74f52950988 Mon Sep 17 00:00:00 2001 From: realmikesolo Date: Mon, 27 Nov 2023 15:56:58 +0100 Subject: [PATCH 42/50] Add jsdoc for MySQL, SQLite with clause --- drizzle-orm/src/mysql-core/db.ts | 51 +++++++++++++++++++++++++++++++ drizzle-orm/src/sqlite-core/db.ts | 51 +++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/drizzle-orm/src/mysql-core/db.ts b/drizzle-orm/src/mysql-core/db.ts index 1092594ee..7f83cd884 100644 --- a/drizzle-orm/src/mysql-core/db.ts +++ b/drizzle-orm/src/mysql-core/db.ts @@ -76,6 +76,38 @@ export class MySqlDatabase< } } + /** + * Creates a subquery that defines a temporary named result set as a CTE. + * + * It is useful for breaking down complex queries into simpler parts and for reusing the result set in subsequent parts of the query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#with-clause} + * + * @param alias The alias for the subquery. + * + * Failure to provide an alias will result in a DrizzleTypeError, preventing the subquery from being referenced in other queries. + * + * @example + * + * ```ts + * // Create a subquery with alias 'sq' and use it in the select query + * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); + * + * const result = await db.with(sq).select().from(sq); + * ``` + * + * To select arbitrary SQL values as fields in a CTE and reference them in other CTEs or in the main query, you need to add aliases to them: + * + * ```ts + * // Select an arbitrary SQL value as a field in a CTE and reference it in the main query + * 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); + * ``` + */ $with(alias: TAlias) { return { as( @@ -93,6 +125,25 @@ export class MySqlDatabase< }; } + /** + * Incorporates a previously defined CTE (using `$with`) into the main query. + * + * This method allows the main query to reference a temporary named result set. + * + * See docs: {@link https://orm.drizzle.team/docs/select#with-clause} + * + * @param queries The CTEs to incorporate into the main query. + * + * @example + * + * ```ts + * // Define a subquery 'sq' as a CTE using $with + * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); + * + * // Incorporate the CTE 'sq' into the main query and select from it + * const result = await db.with(sq).select().from(sq); + * ``` + */ with(...queries: WithSubquery[]) { const self = this; diff --git a/drizzle-orm/src/sqlite-core/db.ts b/drizzle-orm/src/sqlite-core/db.ts index 7e5739a26..64da29aaa 100644 --- a/drizzle-orm/src/sqlite-core/db.ts +++ b/drizzle-orm/src/sqlite-core/db.ts @@ -73,6 +73,38 @@ export class BaseSQLiteDatabase< } } + /** + * Creates a subquery that defines a temporary named result set as a CTE. + * + * It is useful for breaking down complex queries into simpler parts and for reusing the result set in subsequent parts of the query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#with-clause} + * + * @param alias The alias for the subquery. + * + * Failure to provide an alias will result in a DrizzleTypeError, preventing the subquery from being referenced in other queries. + * + * @example + * + * ```ts + * // Create a subquery with alias 'sq' and use it in the select query + * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); + * + * const result = await db.with(sq).select().from(sq); + * ``` + * + * To select arbitrary SQL values as fields in a CTE and reference them in other CTEs or in the main query, you need to add aliases to them: + * + * ```ts + * // Select an arbitrary SQL value as a field in a CTE and reference it in the main query + * 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); + * ``` + */ $with(alias: TAlias) { return { as( @@ -90,6 +122,25 @@ export class BaseSQLiteDatabase< }; } + /** + * Incorporates a previously defined CTE (using `$with`) into the main query. + * + * This method allows the main query to reference a temporary named result set. + * + * See docs: {@link https://orm.drizzle.team/docs/select#with-clause} + * + * @param queries The CTEs to incorporate into the main query. + * + * @example + * + * ```ts + * // Define a subquery 'sq' as a CTE using $with + * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); + * + * // Incorporate the CTE 'sq' into the main query and select from it + * const result = await db.with(sq).select().from(sq); + * ``` + */ with(...queries: WithSubquery[]) { const self = this; From 4dc8c14ae1ea99371e3661eeb25188aad30f05c4 Mon Sep 17 00:00:00 2001 From: Angelelz Date: Mon, 27 Nov 2023 20:07:27 -0500 Subject: [PATCH 43/50] [Pg] fixed arguments passed correctly, eliminated proxy from with replica implementation and added tests --- drizzle-orm/src/pg-core/db.ts | 194 +++++----- .../tests/replicas/postgres.test.ts | 363 ++++++++---------- 2 files changed, 259 insertions(+), 298 deletions(-) diff --git a/drizzle-orm/src/pg-core/db.ts b/drizzle-orm/src/pg-core/db.ts index 1e9987e1a..a5050ce53 100644 --- a/drizzle-orm/src/pg-core/db.ts +++ b/drizzle-orm/src/pg-core/db.ts @@ -17,6 +17,7 @@ import type { import type { PgTable } from '~/pg-core/table.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts'; import { WithSubquery } from '~/subquery.ts'; import type { DrizzleTypeError } from '~/utils.ts'; @@ -26,7 +27,6 @@ import { PgRefreshMaterializedView } from './query-builders/refresh-materialized import type { SelectedFields } from './query-builders/select.types.ts'; import type { WithSubqueryWithSelection } from './subquery.ts'; import type { PgMaterializedView } from './view.ts'; -import { SelectionProxyHandler } from '~/selection-proxy.ts'; export class PgDatabase< TQueryResult extends QueryResultHKT, @@ -74,33 +74,33 @@ export class PgDatabase< /** * Creates a subquery that defines a temporary named result set as a CTE. - * + * * It is useful for breaking down complex queries into simpler parts and for reusing the result set in subsequent parts of the query. - * + * * See docs: {@link https://orm.drizzle.team/docs/select#with-clause} - * + * * @param alias The alias for the subquery. - * + * * Failure to provide an alias will result in a DrizzleTypeError, preventing the subquery from being referenced in other queries. - * + * * @example - * + * * ```ts * // Create a subquery with alias 'sq' and use it in the select query * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); - * + * * const result = await db.with(sq).select().from(sq); * ``` - * + * * To select arbitrary SQL values as fields in a CTE and reference them in other CTEs or in the main query, you need to add aliases to them: - * + * * ```ts * // Select an arbitrary SQL value as a field in a CTE and reference it in the main query * 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); * ``` */ @@ -123,19 +123,19 @@ export class PgDatabase< /** * Incorporates a previously defined CTE (using `$with`) into the main query. - * + * * This method allows the main query to reference a temporary named result set. - * + * * See docs: {@link https://orm.drizzle.team/docs/select#with-clause} - * + * * @param queries The CTEs to incorporate into the main query. - * + * * @example - * + * * ```ts * // Define a subquery 'sq' as a CTE using $with * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); - * + * * // Incorporate the CTE 'sq' into the main query and select from it * const result = await db.with(sq).select().from(sq); * ``` @@ -159,31 +159,31 @@ export class PgDatabase< /** * Creates a select query. - * + * * Calling this method with no arguments will select all columns from the table. Pass a selection object to specify the columns you want to select. - * + * * Use `.from()` method to specify which table to select from. - * + * * See docs: {@link https://orm.drizzle.team/docs/select} - * + * * @param fields The selection object. - * + * * @example - * + * * ```ts * // Select all columns and all rows from the 'cars' table * const allCars: Car[] = await db.select().from(cars); - * + * * // Select specific columns and all rows from the 'cars' table - * const carsIdsAndBrands: { id: number; brand: string }[] = await db.select({ - * id: cars.id, - * brand: cars.brand + * const carsIdsAndBrands: { id: number; brand: string }[] = await db.select({ + * id: cars.id, + * brand: cars.brand * }) * .from(cars); * ``` - * + * * Like in SQL, you can use arbitrary expressions as selection fields, not just table columns: - * + * * ```ts * // Select specific columns along with expression and all rows from the 'cars' table * const carsIdsAndLowerNames: { id: number; lowerBrand: string }[] = await db.select({ @@ -205,22 +205,22 @@ export class PgDatabase< /** * Adds `distinct` expression to the select query. - * + * * Calling this method will return only unique values. When multiple columns are selected, it returns rows with unique combinations of values in these columns. - * + * * Use `.from()` method to specify which table to select from. - * + * * See docs: {@link https://orm.drizzle.team/docs/select#distinct} - * + * * @param fields The selection object. - * + * * @example * ```ts * // Select all unique rows from the 'cars' table * await db.selectDistinct() * .from(cars) * .orderBy(cars.id, cars.brand, cars.color); - * + * * // Select all unique brands from the 'cars' table * await db.selectDistinct({ brand: cars.brand }) * .from(cars) @@ -240,23 +240,23 @@ export class PgDatabase< /** * Adds `distinct on` expression to the select query. - * - * Calling this method will specify how the unique rows are determined. - * + * + * Calling this method will specify how the unique rows are determined. + * * Use `.from()` method to specify which table to select from. - * + * * See docs: {@link https://orm.drizzle.team/docs/select#distinct} - * + * * @param on The expression defining uniqueness. * @param fields The selection object. - * + * * @example * ```ts * // Select the first row for each unique brand from the 'cars' table * await db.selectDistinctOn([cars.brand]) * .from(cars) * .orderBy(cars.brand); - * + * * // Selects the first occurrence of each unique car brand along with its color from the 'cars' table * await db.selectDistinctOn([cars.brand], { brand: cars.brand, color: cars.color }) * .from(cars) @@ -282,24 +282,24 @@ export class PgDatabase< /** * Creates an update query. - * + * * Calling this method without `.where()` clause will update all rows in a table. The `.where()` clause specifies which rows should be updated. - * + * * Use `.set()` method to specify which values to update. - * - * See docs: {@link https://orm.drizzle.team/docs/update} - * + * + * See docs: {@link https://orm.drizzle.team/docs/update} + * * @param table The table to update. - * + * * @example - * + * * ```ts * // Update all rows in the 'cars' table * await db.update(cars).set({ color: 'red' }); - * + * * // Update rows with filters and conditions * await db.update(cars).set({ color: 'red' }).where(eq(cars.brand, 'BMW')); - * + * * // Update with returning clause * const updatedCar: Car[] = await db.update(cars) * .set({ color: 'red' }) @@ -313,22 +313,22 @@ export class PgDatabase< /** * Creates an insert query. - * + * * Calling this method will create new rows in a table. Use `.values()` method to specify which values to insert. - * - * See docs: {@link https://orm.drizzle.team/docs/insert} - * + * + * See docs: {@link https://orm.drizzle.team/docs/insert} + * * @param table The table to insert into. - * + * * @example - * + * * ```ts * // Insert one row * await db.insert(cars).values({ brand: 'BMW' }); - * + * * // Insert multiple rows * await db.insert(cars).values([{ brand: 'BMW' }, { brand: 'Porsche' }]); - * + * * // Insert with returning clause * const insertedCar: Car[] = await db.insert(cars) * .values({ brand: 'BMW' }) @@ -341,22 +341,22 @@ export class PgDatabase< /** * Creates a delete query. - * - * Calling this method without `.where()` clause will delete all rows in a table. The `.where()` clause specifies which rows should be deleted. - * + * + * Calling this method without `.where()` clause will delete all rows in a table. The `.where()` clause specifies which rows should be deleted. + * * See docs: {@link https://orm.drizzle.team/docs/delete} - * + * * @param table The table to delete from. - * + * * @example - * + * * ```ts * // Delete all rows in the 'cars' table * await db.delete(cars); - * + * * // Delete rows with filters and conditions * await db.delete(cars).where(eq(cars.color, 'green')); - * + * * // Delete with returning clause * const deletedCar: Car[] = await db.delete(cars) * .where(eq(cars.id, 1)) @@ -397,40 +397,34 @@ export const withReplicas = < replicas: [Q, ...Q[]], getReplica: (replicas: Q[]) => Q = () => replicas[Math.floor(Math.random() * replicas.length)]!, ): PgWithReplicas => { - const select: Q['select'] = (...args: any) => getReplica(replicas).select(args); - const selectDistinct: Q['selectDistinct'] = (...args: any) => getReplica(replicas).selectDistinct(args); - const selectDistinctOn: Q['selectDistinctOn'] = (...args: any) => getReplica(replicas).selectDistinctOn(args); - const $with: Q['with'] = (...args: any) => getReplica(replicas).with(args); + const select: Q['select'] = (...args: []) => getReplica(replicas).select(...args); + const selectDistinct: Q['selectDistinct'] = (...args: []) => getReplica(replicas).selectDistinct(...args); + const selectDistinctOn: Q['selectDistinctOn'] = (...args: [any]) => getReplica(replicas).selectDistinctOn(...args); + const $with: Q['with'] = (...args: any) => getReplica(replicas).with(...args); - const update: Q['update'] = (...args: any) => primary.update(args); - const insert: Q['insert'] = (...args: any) => primary.insert(args); - const $delete: Q['delete'] = (...args: any) => primary.delete(args); - const execute: Q['execute'] = (...args: any) => primary.execute(args); - const transaction: Q['transaction'] = (...args: any) => primary.transaction(args); - const refreshMaterializedView: Q['refreshMaterializedView'] = (...args: any) => primary.refreshMaterializedView(args); + const update: Q['update'] = (...args: [any]) => primary.update(...args); + const insert: Q['insert'] = (...args: [any]) => primary.insert(...args); + const $delete: Q['delete'] = (...args: [any]) => primary.delete(...args); + const execute: Q['execute'] = (...args: [any]) => primary.execute(...args); + const transaction: Q['transaction'] = (...args: [any]) => primary.transaction(...args); + const refreshMaterializedView: Q['refreshMaterializedView'] = (...args: [any]) => + primary.refreshMaterializedView(...args); - return new Proxy( - { - ...primary, - update, - insert, - delete: $delete, - execute, - transaction, - refreshMaterializedView, - $primary: primary, - select, - selectDistinct, - selectDistinctOn, - with: $with, + return { + ...primary, + update, + insert, + delete: $delete, + execute, + transaction, + refreshMaterializedView, + $primary: primary, + select, + selectDistinct, + selectDistinctOn, + with: $with, + get query() { + return getReplica(replicas).query; }, - { - get(target, prop, _receiver) { - if (prop === 'query') { - return getReplica(replicas).query; - } - return target[prop as keyof typeof target]; - }, - }, - ); + }; }; diff --git a/integration-tests/tests/replicas/postgres.test.ts b/integration-tests/tests/replicas/postgres.test.ts index a782045c4..6165ae413 100644 --- a/integration-tests/tests/replicas/postgres.test.ts +++ b/integration-tests/tests/replicas/postgres.test.ts @@ -11,6 +11,10 @@ const usersTable = pgTable('users', { createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }); +const users = pgTable('users', { + id: serial('id' as string).primaryKey(), +}); + describe('[select] read replicas postgres', () => { it('primary select', () => { const primaryDb = drizzle({} as any); @@ -23,9 +27,11 @@ describe('[select] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'select'); const spyRead2 = vi.spyOn(read2, 'select'); - db.$primary.select().from({} as any); + const query = db.$primary.select().from(users); expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(query.toSQL().sql).toEqual('select "id" from "users"'); + expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); }); @@ -45,15 +51,18 @@ describe('[select] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'select'); const spyRead2 = vi.spyOn(read2, 'select'); - db.select().from({} as any); + const query1 = db.select({ count: sql`count(*)`.as('count') }).from(users).limit(1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); - db.select().from({} as any); + expect(query1.toSQL().sql).toEqual('select count(*) as "count" from "users" limit $1'); + + const query2 = db.select().from(users); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select "id" from "users"'); }); it('single read replica select', () => { @@ -65,13 +74,15 @@ describe('[select] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'select'); const spyRead1 = vi.spyOn(read1, 'select'); - db.select().from({} as any); + const query1 = db.select().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select "id" from "users"'); - db.select().from({} as any); + const query2 = db.select().from(users); expect(spyRead1).toHaveBeenCalledTimes(2); + expect(query2.toSQL().sql).toEqual('select "id" from "users"'); }); it('single read replica select + primary select', () => { @@ -83,14 +94,16 @@ describe('[select] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'select'); const spyRead1 = vi.spyOn(read1, 'select'); - db.select().from({} as any); + const query1 = db.select({ id: users.id }).from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select "id" from "users"'); - db.$primary.select().from({} as any); + const query2 = db.$primary.select().from(users); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select "id" from "users"'); }); it('always first read select', () => { @@ -106,15 +119,18 @@ describe('[select] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'select'); const spyRead2 = vi.spyOn(read2, 'select'); - db.select().from({} as any); + const query1 = db.select().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select "id" from "users"'); + + const query2 = db.select().from(users); - db.select().from({} as any); expect(spyRead1).toHaveBeenCalledTimes(2); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query2.toSQL().sql).toEqual('select "id" from "users"'); }); }); @@ -130,11 +146,12 @@ describe('[selectDistinct] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'selectDistinct'); const spyRead2 = vi.spyOn(read2, 'selectDistinct'); - db.$primary.selectDistinct().from({} as any); + const query = db.$primary.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query.toSQL().sql).toEqual('select distinct "id" from "users"'); }); it('random replica selectDistinct', () => { @@ -152,15 +169,17 @@ describe('[selectDistinct] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'selectDistinct'); const spyRead2 = vi.spyOn(read2, 'selectDistinct'); - db.selectDistinct().from({} as any); + const query1 = db.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select distinct "id" from "users"'); - db.selectDistinct().from({} as any); + const query2 = db.selectDistinct().from(users); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select distinct "id" from "users"'); }); it('single read replica selectDistinct', () => { @@ -172,13 +191,15 @@ describe('[selectDistinct] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'selectDistinct'); const spyRead1 = vi.spyOn(read1, 'selectDistinct'); - db.selectDistinct().from({} as any); + const query1 = db.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select distinct "id" from "users"'); - db.selectDistinct().from({} as any); + const query2 = db.selectDistinct().from(users); expect(spyRead1).toHaveBeenCalledTimes(2); + expect(query2.toSQL().sql).toEqual('select distinct "id" from "users"'); }); it('single read replica selectDistinct + primary selectDistinct', () => { @@ -190,14 +211,16 @@ describe('[selectDistinct] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'selectDistinct'); const spyRead1 = vi.spyOn(read1, 'selectDistinct'); - db.selectDistinct().from({} as any); + const query1 = db.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select distinct "id" from "users"'); - db.$primary.selectDistinct().from({} as any); + const query2 = db.$primary.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select distinct "id" from "users"'); }); it('always first read selectDistinct', () => { @@ -213,122 +236,17 @@ describe('[selectDistinct] read replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'selectDistinct'); const spyRead2 = vi.spyOn(read2, 'selectDistinct'); - db.selectDistinct().from({} as any); - - expect(spyPrimary).toHaveBeenCalledTimes(0); - expect(spyRead1).toHaveBeenCalledTimes(1); - expect(spyRead2).toHaveBeenCalledTimes(0); - - db.selectDistinct().from({} as any); - expect(spyRead1).toHaveBeenCalledTimes(2); - expect(spyRead2).toHaveBeenCalledTimes(0); - }); -}); - -describe('[selectDistinctOn] read replicas postgres', () => { - it('primary selectDistinctOn', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); - - const db = withReplicas(primaryDb, [read1, read2]); - - const spyPrimary = vi.spyOn(primaryDb, 'selectDistinctOn'); - const spyRead1 = vi.spyOn(read1, 'selectDistinctOn'); - const spyRead2 = vi.spyOn(read2, 'selectDistinctOn'); - - db.$primary.selectDistinctOn({} as any).from({} as any); - - expect(spyPrimary).toHaveBeenCalledTimes(1); - expect(spyRead1).toHaveBeenCalledTimes(0); - expect(spyRead2).toHaveBeenCalledTimes(0); - }); - - it('random replica selectDistinctOn', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); - - const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); - - const db = withReplicas(primaryDb, [read1, read2], () => { - return randomMockReplica(); - }); - - const spyPrimary = vi.spyOn(primaryDb, 'selectDistinctOn'); - const spyRead1 = vi.spyOn(read1, 'selectDistinctOn'); - const spyRead2 = vi.spyOn(read2, 'selectDistinctOn'); - - db.selectDistinctOn({} as any).from({} as any); - - expect(spyPrimary).toHaveBeenCalledTimes(0); - expect(spyRead1).toHaveBeenCalledTimes(1); - expect(spyRead2).toHaveBeenCalledTimes(0); - - db.selectDistinctOn({} as any).from({} as any); - expect(spyRead1).toHaveBeenCalledTimes(1); - expect(spyRead2).toHaveBeenCalledTimes(1); - }); - - it('single read replica selectDistinctOn', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - - const db = withReplicas(primaryDb, [read1]); - - const spyPrimary = vi.spyOn(primaryDb, 'selectDistinctOn'); - const spyRead1 = vi.spyOn(read1, 'selectDistinctOn'); - - db.selectDistinctOn({} as any).from({} as any); - - expect(spyPrimary).toHaveBeenCalledTimes(0); - expect(spyRead1).toHaveBeenCalledTimes(1); - - db.selectDistinctOn({} as any).from({} as any); - expect(spyRead1).toHaveBeenCalledTimes(2); - }); - - it('single read replica selectDistinctOn + primary selectDistinctOn', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - - const db = withReplicas(primaryDb, [read1]); - - const spyPrimary = vi.spyOn(primaryDb, 'selectDistinctOn'); - const spyRead1 = vi.spyOn(read1, 'selectDistinctOn'); - - db.selectDistinctOn({} as any).from({} as any); - - expect(spyPrimary).toHaveBeenCalledTimes(0); - expect(spyRead1).toHaveBeenCalledTimes(1); - - db.$primary.selectDistinctOn({} as any).from({} as any); - expect(spyPrimary).toHaveBeenCalledTimes(1); - expect(spyRead1).toHaveBeenCalledTimes(1); - }); - - it('always first read selectDistinctOn', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); - - const db = withReplicas(primaryDb, [read1, read2], (replicas) => { - return replicas[0]!; - }); - - const spyPrimary = vi.spyOn(primaryDb, 'selectDistinctOn'); - const spyRead1 = vi.spyOn(read1, 'selectDistinctOn'); - const spyRead2 = vi.spyOn(read2, 'selectDistinctOn'); - - db.selectDistinctOn({} as any).from({} as any); + const query1 = db.selectDistinct().from(users); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select distinct "id" from "users"'); - db.selectDistinctOn({} as any).from({} as any); + const query2 = db.selectDistinct().from(users); expect(spyRead1).toHaveBeenCalledTimes(2); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query2.toSQL().sql).toEqual('select distinct "id" from "users"'); }); }); @@ -343,12 +261,17 @@ describe('[with] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'with'); const spyRead1 = vi.spyOn(read1, 'with'); const spyRead2 = vi.spyOn(read2, 'with'); + const obj1 = {} as any; + const obj2 = {} as any; + const obj3 = {} as any; + const obj4 = {} as any; - db.$primary.with(); + db.$primary.with(obj1, obj2, obj3, obj4); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(obj1, obj2, obj3, obj4); }); it('random replica with', () => { @@ -426,16 +349,21 @@ describe('[with] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'with'); const spyRead1 = vi.spyOn(read1, 'with'); const spyRead2 = vi.spyOn(read2, 'with'); + const obj1 = {} as any; + const obj2 = {} as any; + const obj3 = {} as any; - db.with(); + db.with(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(obj1); - db.with(); + db.with(obj2, obj3); expect(spyRead1).toHaveBeenCalledTimes(2); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(obj2, obj3); }); }); @@ -451,23 +379,26 @@ describe('[update] replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'update'); const spyRead2 = vi.spyOn(read2, 'update'); - db.update({} as any); + const query1 = db.update(users).set({ id: 1 }); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('update "users" set "id" = $1'); - db.update({} as any); + const query2 = db.update(users).set({ id: 1 }); expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query2.toSQL().sql).toEqual('update "users" set "id" = $1'); - db.$primary.update({} as any); + const query3 = db.$primary.update(users).set({ id: 1 }); expect(spyPrimary).toHaveBeenCalledTimes(3); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query3.toSQL().sql).toEqual('update "users" set "id" = $1'); }); }); @@ -483,17 +414,21 @@ describe('[delete] replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'delete'); const spyRead2 = vi.spyOn(read2, 'delete'); - db.delete({} as any); + const query1 = db.delete(users); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(users); + expect(query1.toSQL().sql).toEqual('delete from "users"'); - db.delete({} as any); + const query2 = db.delete(users); expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenNthCalledWith(2, users); + expect(query2.toSQL().sql).toEqual('delete from "users"'); db.$primary.delete({} as any); @@ -515,17 +450,20 @@ describe('[insert] replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'insert'); const spyRead2 = vi.spyOn(read2, 'insert'); - db.insert({} as any); + const query = db.insert(users).values({ id: 1 }); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(users); + expect(query.toSQL().sql).toEqual('insert into "users" ("id") values ($1)'); - db.insert({} as any); + db.insert(users); expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenNthCalledWith(2, users); db.$primary.insert({} as any); @@ -547,27 +485,29 @@ describe('[execute] replicas postgres', () => { const spyRead1 = vi.spyOn(read1, 'execute'); const spyRead2 = vi.spyOn(read2, 'execute'); - // expect(db.execute(sql``)).rejects.toThrow(); + expect(db.execute(sql``)).rejects.toThrow(); - try { - db.execute(sql``); - } catch { /* empty */ } + // try { + // db.execute(sql``); + // } catch { /* empty */ } expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); - try { - db.execute(sql``); - } catch { /* empty */ } + expect(db.execute(sql``)).rejects.toThrow(); + // try { + // db.execute(sql``); + // } catch { /* empty */ } expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); - try { - db.execute(sql``); - } catch { /* empty */ } + expect(db.execute(sql``)).rejects.toThrow(); + // try { + // db.execute(sql``); + // } catch { /* empty */ } expect(spyPrimary).toHaveBeenCalledTimes(3); expect(spyRead1).toHaveBeenCalledTimes(0); @@ -586,22 +526,27 @@ describe('[transaction] replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb, 'transaction'); const spyRead1 = vi.spyOn(read1, 'transaction'); const spyRead2 = vi.spyOn(read2, 'transaction'); - - expect(db.transaction(async (tx) => { + const txFn1 = async (tx: any) => { tx.select().from({} as any); - })).rejects.toThrow(); + }; + + expect(db.transaction(txFn1)).rejects.toThrow(); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(txFn1); - expect(db.transaction(async (tx) => { + const txFn2 = async (tx: any) => { tx.select().from({} as any); - })).rejects.toThrow(); + }; + + expect(db.transaction(txFn2)).rejects.toThrow(); expect(spyPrimary).toHaveBeenCalledTimes(2); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenNthCalledWith(2, txFn2); expect(db.transaction(async (tx) => { tx.select().from({} as any); @@ -613,38 +558,6 @@ describe('[transaction] replicas postgres', () => { }); }); -describe('[refreshView] replicas postgres', () => { - it('primary refreshView', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); - - const db = withReplicas(primaryDb, [read1, read2]); - - const spyPrimary = vi.spyOn(primaryDb, 'refreshMaterializedView'); - const spyRead1 = vi.spyOn(read1, 'refreshMaterializedView'); - const spyRead2 = vi.spyOn(read2, 'refreshMaterializedView'); - - db.refreshMaterializedView({} as any); - - expect(spyPrimary).toHaveBeenCalledTimes(1); - expect(spyRead1).toHaveBeenCalledTimes(0); - expect(spyRead2).toHaveBeenCalledTimes(0); - - db.refreshMaterializedView({} as any); - - expect(spyPrimary).toHaveBeenCalledTimes(2); - expect(spyRead1).toHaveBeenCalledTimes(0); - expect(spyRead2).toHaveBeenCalledTimes(0); - - db.$primary.refreshMaterializedView({} as any); - - expect(spyPrimary).toHaveBeenCalledTimes(3); - expect(spyRead1).toHaveBeenCalledTimes(0); - expect(spyRead2).toHaveBeenCalledTimes(0); - }); -}); - describe('[findFirst] read replicas postgres', () => { it('primary findFirst', () => { const primaryDb = drizzle({} as any, { schema: { usersTable } }); @@ -656,19 +569,21 @@ describe('[findFirst] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findFirst'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findFirst'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findFirst'); + const obj = {} as any; - db.$primary.query.usersTable.findFirst(); + db.$primary.query.usersTable.findFirst(obj); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(obj); }); it('random replica findFirst', () => { const primaryDb = drizzle({} as any, { schema: { usersTable } }); const read1 = drizzle({} as any, { schema: { usersTable } }); const read2 = drizzle({} as any, { schema: { usersTable } }); - + const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); const db = withReplicas(primaryDb, [read1, read2], () => { @@ -678,16 +593,21 @@ describe('[findFirst] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findFirst'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findFirst'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findFirst'); + const par1 = {} as any; - db.query.usersTable.findFirst(); + db.query.usersTable.findFirst(par1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(par1); - db.query.usersTable.findFirst(); + const query = db.query.usersTable.findFirst(); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query.toSQL().sql).toEqual( + 'select "id", "name", "verified", "jsonb", "created_at" from "users" "usersTable" limit $1', + ); }); it('single read replica findFirst', () => { @@ -711,7 +631,7 @@ describe('[findFirst] read replicas postgres', () => { it('single read replica findFirst + primary findFirst', () => { const primaryDb = drizzle({} as any, { schema: { usersTable } }); const read1 = drizzle({} as any, { schema: { usersTable } }); - + const db = withReplicas(primaryDb, [read1]); const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findFirst'); @@ -763,19 +683,24 @@ describe('[findMany] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findMany'); + const obj = {} as any; - db.$primary.query.usersTable.findMany(); + const query = db.$primary.query.usersTable.findMany(obj); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(0); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(obj); + expect(query.toSQL().sql).toEqual( + 'select "id", "name", "verified", "jsonb", "created_at" from "users" "usersTable"', + ); }); it('random replica findMany', () => { const primaryDb = drizzle({} as any, { schema: { usersTable } }); const read1 = drizzle({} as any, { schema: { usersTable } }); const read2 = drizzle({} as any, { schema: { usersTable } }); - + const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); const db = withReplicas(primaryDb, [read1, read2], () => { @@ -785,16 +710,27 @@ describe('[findMany] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findMany'); + const obj1 = {} as any; + const obj2 = {} as any; - db.query.usersTable.findMany(); + const query1 = db.query.usersTable.findMany(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual( + 'select "id", "name", "verified", "jsonb", "created_at" from "users" "usersTable"', + ); + expect(spyRead1).toHaveBeenCalledWith(obj1); + + const query2 = db.query.usersTable.findMany(obj2); - db.query.usersTable.findMany(); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual( + 'select "id", "name", "verified", "jsonb", "created_at" from "users" "usersTable"', + ); + expect(spyRead2).toHaveBeenCalledWith(obj2); }); it('single read replica findMany', () => { @@ -805,33 +741,54 @@ describe('[findMany] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); + const obj1 = {} as any; + const obj2 = {} as any; - db.query.usersTable.findMany(); + const query1 = db.query.usersTable.findMany(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledWith(obj1); + expect(query1.toSQL().sql).toEqual( + 'select "id", "name", "verified", "jsonb", "created_at" from "users" "usersTable"', + ); - db.query.usersTable.findMany(); + const query2 = db.query.usersTable.findMany(obj2); expect(spyRead1).toHaveBeenCalledTimes(2); + expect(spyRead1).toHaveBeenNthCalledWith(2, obj2); + expect(query2.toSQL().sql).toEqual( + 'select "id", "name", "verified", "jsonb", "created_at" from "users" "usersTable"', + ); }); it('single read replica findMany + primary findMany', () => { const primaryDb = drizzle({} as any, { schema: { usersTable } }); const read1 = drizzle({} as any, { schema: { usersTable } }); - + const db = withReplicas(primaryDb, [read1]); const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); + const obj1 = {} as any; + const obj2 = {} as any; - db.query.usersTable.findMany(); + const query1 = db.query.usersTable.findMany(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledWith(obj1); + expect(query1.toSQL().sql).toEqual( + 'select "id", "name", "verified", "jsonb", "created_at" from "users" "usersTable"', + ); + + const query2 = db.$primary.query.usersTable.findMany(obj2); - db.$primary.query.usersTable.findMany(); expect(spyPrimary).toHaveBeenCalledTimes(1); expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyPrimary).toHaveBeenNthCalledWith(1, obj2); + expect(query2.toSQL().sql).toEqual( + 'select "id", "name", "verified", "jsonb", "created_at" from "users" "usersTable"', + ); }); it('always first read findMany', () => { @@ -846,15 +803,25 @@ describe('[findMany] read replicas postgres', () => { const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findMany'); + const obj1 = {} as any; + const obj2 = {} as any; - db.query.usersTable.findMany(); + const query1 = db.query.usersTable.findMany(obj1); expect(spyPrimary).toHaveBeenCalledTimes(0); expect(spyRead1).toHaveBeenCalledTimes(1); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(obj1); + expect(query1.toSQL().sql).toEqual( + 'select "id", "name", "verified", "jsonb", "created_at" from "users" "usersTable"', + ); - db.query.usersTable.findMany(); + const query2 = db.query.usersTable.findMany(obj2); expect(spyRead1).toHaveBeenCalledTimes(2); expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenNthCalledWith(2, obj2); + expect(query2.toSQL().sql).toEqual( + 'select "id", "name", "verified", "jsonb", "created_at" from "users" "usersTable"', + ); }); }); From 5ab7930207405b9b8ebeb71479ceeae5f666cc67 Mon Sep 17 00:00:00 2001 From: Angelelz Date: Tue, 28 Nov 2023 00:57:17 -0500 Subject: [PATCH 44/50] Added new option to define the name of the db object on delete --- .../src/enforce-delete-with-where.ts | 27 +++++++++++----- eslint-plugin-drizzle/src/utils/options.ts | 31 +++++++++++++++++++ 2 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 eslint-plugin-drizzle/src/utils/options.ts diff --git a/eslint-plugin-drizzle/src/enforce-delete-with-where.ts b/eslint-plugin-drizzle/src/enforce-delete-with-where.ts index 8368e1886..21b4824a9 100644 --- a/eslint-plugin-drizzle/src/enforce-delete-with-where.ts +++ b/eslint-plugin-drizzle/src/enforce-delete-with-where.ts @@ -1,28 +1,39 @@ -import type { TSESLint } from '@typescript-eslint/utils'; +import { ESLintUtils } from '@typescript-eslint/utils'; +import { isDrizzleObj, type Options } from './utils/options'; + +const createRule = ESLintUtils.RuleCreator(() => 'https://github.com/drizzle-team/eslint-plugin-drizzle'); type MessageIds = 'enforceDeleteWithWhere'; let lastNodeName: string = ''; -const deleteRule: TSESLint.RuleModule = { - defaultOptions: [], +const deleteRule = createRule({ + defaultOptions: [{ drizzleObjectName: [] }], + name: 'enforce-delete-with-where', meta: { type: 'problem', docs: { description: 'Enforce that `delete` method is used with `where` to avoid deleting all the rows in a table.', - url: 'https://github.com/drizzle-team/eslint-plugin-drizzle', }, fixable: 'code', messages: { enforceDeleteWithWhere: 'Avoid deleting all the rows in a table. Use `db.delete(...).where(...)` instead.', }, - schema: [], + schema: [{ + type: 'object', + properties: { + drizzleObjectName: { + type: ['string', 'array'], + }, + }, + additionalProperties: false, + }], }, - create(context) { + create(context, options) { return { MemberExpression: (node) => { if (node.property.type === 'Identifier') { - if (node.property.name === 'delete' && lastNodeName !== 'where') { + if (isDrizzleObj(node, options) && node.property.name === 'delete' && lastNodeName !== 'where') { context.report({ node, messageId: 'enforceDeleteWithWhere', @@ -34,6 +45,6 @@ const deleteRule: TSESLint.RuleModule = { }, }; }, -}; +}); export default deleteRule; diff --git a/eslint-plugin-drizzle/src/utils/options.ts b/eslint-plugin-drizzle/src/utils/options.ts new file mode 100644 index 000000000..834f18142 --- /dev/null +++ b/eslint-plugin-drizzle/src/utils/options.ts @@ -0,0 +1,31 @@ +import type { TSESTree } from '@typescript-eslint/utils'; + +export type Options = readonly [{ + drizzleObjectName: string[] | string; +}]; + +export const isDrizzleObj = ( + node: TSESTree.MemberExpression, + options: Options, +) => { + const drizzleObjectName = options[0].drizzleObjectName; + + if ( + node.object.type === 'Identifier' && typeof drizzleObjectName === 'string' + && node.object.name === drizzleObjectName + ) { + return true; + } + + if (Array.isArray(drizzleObjectName)) { + if (drizzleObjectName.length === 0) { + return true; + } + + if (node.object.type === 'Identifier' && drizzleObjectName.includes(node.object.name)) { + return true; + } + } + + return false; +}; From 685527843d93a5b9986ec43f9a7c5a63f29f10ad Mon Sep 17 00:00:00 2001 From: Angelelz Date: Tue, 28 Nov 2023 00:57:44 -0500 Subject: [PATCH 45/50] Added test for new option on delete --- eslint-plugin-drizzle/tests/delete.test.ts | 131 ++++++++++++++++----- 1 file changed, 103 insertions(+), 28 deletions(-) diff --git a/eslint-plugin-drizzle/tests/delete.test.ts b/eslint-plugin-drizzle/tests/delete.test.ts index 17ab6c847..d17cb67be 100644 --- a/eslint-plugin-drizzle/tests/delete.test.ts +++ b/eslint-plugin-drizzle/tests/delete.test.ts @@ -1,39 +1,114 @@ // @ts-ignore -import { RuleTester } from "@typescript-eslint/rule-tester"; +import { RuleTester } from '@typescript-eslint/rule-tester'; -import myRule from "../src/enforce-delete-with-where"; +import myRule from '../src/enforce-delete-with-where'; -const parserResolver = require.resolve("@typescript-eslint/parser"); +const parserResolver = require.resolve('@typescript-eslint/parser'); const ruleTester = new RuleTester({ - parser: parserResolver, + parser: parserResolver, }); -ruleTester.run("my-rule", myRule, { - valid: [ - "const a = db.delete({}).where({});", - "delete db.something", - `dataSource +ruleTester.run('enforce delete with where (default options)', myRule, { + valid: [ + 'const a = db.delete({}).where({});', + 'delete db.something', + `dataSource .delete() .where()`, - ], - invalid: [ - { - code: "db.delete({})", - errors: [{ messageId: "enforceDeleteWithWhere" }], - }, - { - code: "const a = await db.delete({})", - errors: [{ messageId: "enforceDeleteWithWhere" }], - }, - { - code: "const a = db.delete({})", - errors: [{ messageId: "enforceDeleteWithWhere" }], - }, - { - code: `const a = database + ], + invalid: [ + { + code: 'db.delete({})', + errors: [{ messageId: 'enforceDeleteWithWhere' }], + }, + { + code: 'const a = await db.delete({})', + errors: [{ messageId: 'enforceDeleteWithWhere' }], + }, + { + code: 'const a = db.delete({})', + errors: [{ messageId: 'enforceDeleteWithWhere' }], + }, + { + code: `const a = database .delete({})`, - errors: [{ messageId: "enforceDeleteWithWhere" }], - }, - ], + errors: [{ messageId: 'enforceDeleteWithWhere' }], + }, + ], +}); + +ruleTester.run('enforce delete with where (string option)', myRule, { + valid: [ + { code: 'const a = db.delete({}).where({});', options: [{ drizzleObjectName: 'db' }] }, + { code: 'delete db.something', options: [{ drizzleObjectName: 'db' }] }, + { + code: `dataSource + .delete() + .where()`, + options: [{ drizzleObjectName: 'db' }], + }, + { + code: `const a = database + .delete({})`, + options: [{ drizzleObjectName: 'db' }], + }, + ], + invalid: [ + { + code: 'db.delete({})', + errors: [{ messageId: 'enforceDeleteWithWhere' }], + options: [{ drizzleObjectName: 'db' }], + }, + { + code: 'const a = await db.delete({})', + errors: [{ messageId: 'enforceDeleteWithWhere' }], + options: [{ drizzleObjectName: 'db' }], + }, + { + code: 'const a = db.delete({})', + errors: [{ messageId: 'enforceDeleteWithWhere' }], + options: [{ drizzleObjectName: 'db' }], + }, + ], +}); + +ruleTester.run('enforce delete with where (array option)', myRule, { + valid: [ + { code: 'const a = db.delete({}).where({});', options: [{ drizzleObjectName: ['db'] }] }, + { code: 'delete db.something', options: [{ drizzleObjectName: ['db'] }] }, + { + code: `dataSource + .delete() + .where()`, + options: [{ drizzleObjectName: ['db', 'dataSource'] }], + }, + { + code: `const a = database + .delete({})`, + options: [{ drizzleObjectName: ['db'] }], + }, + ], + invalid: [ + { + code: 'db.delete({})', + errors: [{ messageId: 'enforceDeleteWithWhere' }], + options: [{ drizzleObjectName: ['db', 'anotherName'] }], + }, + { + code: 'dataSource.delete({})', + errors: [{ messageId: 'enforceDeleteWithWhere' }], + options: [{ drizzleObjectName: ['db', 'dataSource'] }], + }, + { + code: 'const a = await db.delete({})', + errors: [{ messageId: 'enforceDeleteWithWhere' }], + options: [{ drizzleObjectName: ['db'] }], + }, + { + code: 'const a = db.delete({})', + errors: [{ messageId: 'enforceDeleteWithWhere' }], + options: [{ drizzleObjectName: ['db'] }], + }, + ], }); From 3aa90ed47bb6d976b5075f78daacf8e6d680d7b9 Mon Sep 17 00:00:00 2001 From: Angelelz Date: Tue, 28 Nov 2023 01:27:07 -0500 Subject: [PATCH 46/50] Added option to update and added tests --- .../src/enforce-update-with-where.ts | 27 ++-- eslint-plugin-drizzle/tests/delete.test.ts | 1 + eslint-plugin-drizzle/tests/update.test.ts | 136 ++++++++++++++---- 3 files changed, 126 insertions(+), 38 deletions(-) diff --git a/eslint-plugin-drizzle/src/enforce-update-with-where.ts b/eslint-plugin-drizzle/src/enforce-update-with-where.ts index a98176e77..8c524b1ee 100644 --- a/eslint-plugin-drizzle/src/enforce-update-with-where.ts +++ b/eslint-plugin-drizzle/src/enforce-update-with-where.ts @@ -1,25 +1,35 @@ -import type { TSESLint } from '@typescript-eslint/utils'; +import { ESLintUtils } from '@typescript-eslint/utils'; +import { isDrizzleObj, type Options } from './utils/options'; +const createRule = ESLintUtils.RuleCreator(() => 'https://github.com/drizzle-team/eslint-plugin-drizzle'); type MessageIds = 'enforceUpdateWithWhere'; let lastNodeName: string = ''; -const deleteRule: TSESLint.RuleModule = { - defaultOptions: [], +const updateRule = createRule({ + defaultOptions: [{ drizzleObjectName: [] }], + name: 'enforce-update-with-where', meta: { type: 'problem', docs: { description: 'Enforce that `update` method is used with `where` to avoid deleting all the rows in a table.', - url: 'https://github.com/drizzle-team/eslint-plugin-drizzle', }, fixable: 'code', messages: { enforceUpdateWithWhere: 'Avoid updating all the rows in a table. Use `db.update(...).set(...).where(...)` instead.', }, - schema: [], + schema: [{ + type: 'object', + properties: { + drizzleObjectName: { + type: ['string', 'array'], + }, + }, + additionalProperties: false, + }], }, - create(context) { + create(context, options) { return { MemberExpression: (node) => { if (node.property.type === 'Identifier') { @@ -30,6 +40,7 @@ const deleteRule: TSESLint.RuleModule = { && node.object.callee.type === 'MemberExpression' && node.object.callee.property.type === 'Identifier' && node.object.callee.property.name === 'update' + && isDrizzleObj(node.object.callee, options) ) { context.report({ node, @@ -42,6 +53,6 @@ const deleteRule: TSESLint.RuleModule = { }, }; }, -}; +}); -export default deleteRule; +export default updateRule; diff --git a/eslint-plugin-drizzle/tests/delete.test.ts b/eslint-plugin-drizzle/tests/delete.test.ts index d17cb67be..dfc1f2da3 100644 --- a/eslint-plugin-drizzle/tests/delete.test.ts +++ b/eslint-plugin-drizzle/tests/delete.test.ts @@ -41,6 +41,7 @@ ruleTester.run('enforce delete with where (default options)', myRule, { ruleTester.run('enforce delete with where (string option)', myRule, { valid: [ { code: 'const a = db.delete({}).where({});', options: [{ drizzleObjectName: 'db' }] }, + { code: 'const a = something.delete({})', options: [{ drizzleObjectName: 'db' }] }, { code: 'delete db.something', options: [{ drizzleObjectName: 'db' }] }, { code: `dataSource diff --git a/eslint-plugin-drizzle/tests/update.test.ts b/eslint-plugin-drizzle/tests/update.test.ts index 456a43f3e..d148f6b4f 100644 --- a/eslint-plugin-drizzle/tests/update.test.ts +++ b/eslint-plugin-drizzle/tests/update.test.ts @@ -1,46 +1,122 @@ // @ts-ignore -import { RuleTester } from "@typescript-eslint/rule-tester"; +import { RuleTester } from '@typescript-eslint/rule-tester'; -import myRule from "../src/enforce-update-with-where"; +import myRule from '../src/enforce-update-with-where'; -const parserResolver = require.resolve("@typescript-eslint/parser"); +const parserResolver = require.resolve('@typescript-eslint/parser'); const ruleTester = new RuleTester({ - parser: parserResolver, + parser: parserResolver, }); -ruleTester.run("my-rule", myRule, { - valid: [ - "const a = db.update({}).set().where({});", - "const a = db.update();", - "update()", - `da +ruleTester.run('enforce update with where (default options)', myRule, { + valid: [ + 'const a = db.update({}).set().where({});', + 'const a = db.update();', + 'update()', + `db .update() .set() .where()`, - `dataSource + `dataSource .update() .set() .where()`, - ], - invalid: [ - { - code: "db.update({}).set()", - errors: [{ messageId: "enforceUpdateWithWhere" }], - }, - { - code: "const a = await db.update({}).set()", - errors: [{ messageId: "enforceUpdateWithWhere" }], - }, - { - code: "const a = db.update({}).set", - errors: [{ messageId: "enforceUpdateWithWhere" }], - }, - { - code: `const a = database + ], + invalid: [ + { + code: 'db.update({}).set()', + errors: [{ messageId: 'enforceUpdateWithWhere' }], + }, + { + code: 'const a = await db.update({}).set()', + errors: [{ messageId: 'enforceUpdateWithWhere' }], + }, + { + code: 'const a = db.update({}).set', + errors: [{ messageId: 'enforceUpdateWithWhere' }], + }, + { + code: `const a = database .update({}) .set()`, - errors: [{ messageId: "enforceUpdateWithWhere" }], - }, - ], + errors: [{ messageId: 'enforceUpdateWithWhere' }], + }, + ], +}); + +ruleTester.run('enforce update with where (string option)', myRule, { + valid: [ + { code: 'const a = db.update({}).set().where({});', options: [{ drizzleObjectName: 'db' }] }, + { code: 'update.db.update()', options: [{ drizzleObjectName: 'db' }] }, + { + code: `dataSource + .update() + .set()`, + options: [{ drizzleObjectName: 'db' }], + }, + { + code: `const a = database + .update({})`, + options: [{ drizzleObjectName: 'db' }], + }, + ], + invalid: [ + { + code: 'db.update({}).set({})', + errors: [{ messageId: 'enforceUpdateWithWhere' }], + options: [{ drizzleObjectName: 'db' }], + }, + { + code: 'const a = await db.update({}).set()', + errors: [{ messageId: 'enforceUpdateWithWhere' }], + options: [{ drizzleObjectName: 'db' }], + }, + { + code: 'const a = db.update({}).set()', + errors: [{ messageId: 'enforceUpdateWithWhere' }], + options: [{ drizzleObjectName: 'db' }], + }, + ], +}); + +ruleTester.run('enforce delete with where (array option)', myRule, { + valid: [ + { code: 'const a = db.update({}).set().where({});', options: [{ drizzleObjectName: ['db'] }] }, + { code: 'update.db.something', options: [{ drizzleObjectName: ['db'] }] }, + { + code: `dataSource + .update() + .set() + .where()`, + options: [{ drizzleObjectName: ['db', 'dataSource'] }], + }, + { + code: `const a = database + .update({})`, + options: [{ drizzleObjectName: ['db'] }], + }, + ], + invalid: [ + { + code: 'db.update({}).set()', + errors: [{ messageId: 'enforceUpdateWithWhere' }], + options: [{ drizzleObjectName: ['db', 'anotherName'] }], + }, + { + code: 'dataSource.update({}).set({})', + errors: [{ messageId: 'enforceUpdateWithWhere' }], + options: [{ drizzleObjectName: ['db', 'dataSource'] }], + }, + { + code: 'const a = await db.update({}).set()', + errors: [{ messageId: 'enforceUpdateWithWhere' }], + options: [{ drizzleObjectName: ['db'] }], + }, + { + code: 'const a = db.update({}).set()', + errors: [{ messageId: 'enforceUpdateWithWhere' }], + options: [{ drizzleObjectName: ['db'] }], + }, + ], }); From a425bbe5d4417c00ae33c042d69f46da1d0e768b Mon Sep 17 00:00:00 2001 From: Angelelz Date: Tue, 28 Nov 2023 01:47:26 -0500 Subject: [PATCH 47/50] Updated readme --- eslint-plugin-drizzle/readme.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/eslint-plugin-drizzle/readme.md b/eslint-plugin-drizzle/readme.md index 968e530bd..fb45efc03 100644 --- a/eslint-plugin-drizzle/readme.md +++ b/eslint-plugin-drizzle/readme.md @@ -37,8 +37,22 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c ## Rules **enforce-delete-with-where**: Enforce using `delete` with `where` in `DELETE` statement +Optionally, you can defined a `drizzleObjectName` in the plugin options that accepts a string or an array of strings. +This is useful when you have object or classes with a delete method that's not from drizzle. Such delete method will trigger the eslint rule. +To avoid that, you can define the name of the drizzle object that you use in your codebase (like `db`) so that the rule would only trigger if the delete method comes from this object: +```json +"rules": { + "drizzle/enforce-delete-with-where": ["error", { "drizzleObjectName": ["db", "dataSource", "database"] }], +} +``` **enforce-update-with-where**: Enforce using `update` with `where` in `UPDATE` statement +Similar to the delete rule, you can define the name of the drizzle object that you use in your codebase (like `db`) so that the rule would only trigger if the update method comes from this object: +```json +"rules": { + "drizzle/enforce-update-with-where": ["error", { "drizzleObjectName": "db" }], +} +``` ## Preset configs From dfc7442a2f5e77b30398de0f11b15d2cdec3387e Mon Sep 17 00:00:00 2001 From: Angelelz Date: Tue, 28 Nov 2023 01:56:00 -0500 Subject: [PATCH 48/50] fix types --- eslint-plugin-drizzle/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eslint-plugin-drizzle/src/index.ts b/eslint-plugin-drizzle/src/index.ts index 3f7af72b1..15ded747f 100644 --- a/eslint-plugin-drizzle/src/index.ts +++ b/eslint-plugin-drizzle/src/index.ts @@ -4,11 +4,12 @@ import all from './configs/all'; import recommended from './configs/recommended'; import deleteRule from './enforce-delete-with-where'; import updateRule from './enforce-update-with-where'; +import type { Options } from './utils/options'; export const rules = { 'enforce-delete-with-where': deleteRule, 'enforce-update-with-where': updateRule, -} satisfies Record>>; +} satisfies Record>; export const configs = { all, recommended }; From 758723b2e386de89d43b261692f597ebc4bbb56c Mon Sep 17 00:00:00 2001 From: AndriiSherman Date: Wed, 29 Nov 2023 17:04:52 +0200 Subject: [PATCH 49/50] Bump drizzle-orm and eslint-plugin versions --- changelogs/drizzle-orm/0.29.1.md | 272 ++++++++++++++++++ changelogs/eslint-plugin-drizzle/0.2.1.md | 4 + drizzle-orm/package.json | 2 +- eslint-plugin-drizzle/package.json | 6 +- eslint-plugin-drizzle/readme.md | 168 ++++++++--- .../src/enforce-delete-with-where.ts | 2 +- .../src/enforce-update-with-where.ts | 2 +- 7 files changed, 405 insertions(+), 51 deletions(-) create mode 100644 changelogs/drizzle-orm/0.29.1.md create mode 100644 changelogs/eslint-plugin-drizzle/0.2.1.md diff --git a/changelogs/drizzle-orm/0.29.1.md b/changelogs/drizzle-orm/0.29.1.md new file mode 100644 index 000000000..f1f526ed9 --- /dev/null +++ b/changelogs/drizzle-orm/0.29.1.md @@ -0,0 +1,272 @@ +# Fixes + +- Forward args correctly when using withReplica feature #1536. Thanks @Angelelz +- Fix selectDistinctOn not working with multiple columns #1466. Thanks @L-Mario564 + +# New Features/Helpers + +## New helpers for aggregate functions in SQL - Thanks @L-Mario564 + +> Remember, aggregation functions are often used with the GROUP BY clause of the SELECT statement. So if you are selecting using aggregating functions and other columns in one query, +be sure to use the `.groupBy` clause + +Here is a list of functions and equivalent using `sql` template + +**count** +```ts +await db.select({ value: count() }).from(users); +await db.select({ value: count(users.id) }).from(users); + +// It's equivalent to writing +await db.select({ + value: sql`count('*'))`.mapWith(Number) +}).from(users); +await db.select({ + value: sql`count(${users.id})`.mapWith(Number) +}).from(users); +``` + +**countDistinct** +```ts +await db.select({ value: countDistinct(users.id) }).from(users); + +// It's equivalent to writing +await db.select({ + value: sql`count(${users.id})`.mapWith(Number) +}).from(users); +``` + +**avg** +```ts +await db.select({ value: avg(users.id) }).from(users); + +// It's equivalent to writing +await db.select({ + value: sql`avg(${users.id})`.mapWith(String) +}).from(users); +``` + +**avgDistinct** +```ts +await db.select({ value: avgDistinct(users.id) }).from(users); + +// It's equivalent to writing +await db.select({ + value: sql`avg(distinct ${users.id})`.mapWith(String) +}).from(users); +``` + +**sum** +```ts +await db.select({ value: sum(users.id) }).from(users); + +// It's equivalent to writing +await db.select({ + value: sql`sum(${users.id})`.mapWith(String) +}).from(users); +``` + +**sumDistinct** +```ts +await db.select({ value: sumDistinct(users.id) }).from(users); + +// It's equivalent to writing +await db.select({ + value: sql`sum(distinct ${users.id})`.mapWith(String) +}).from(users); +``` + +**max** +```ts +await db.select({ value: max(users.id) }).from(users); + +// It's equivalent to writing +await db.select({ + value: sql`max(${expression})`.mapWith(users.id) +}).from(users); +``` + +**min** +```ts +await db.select({ value: min(users.id) }).from(users); + +// It's equivalent to writing +await db.select({ + value: sql`min(${users.id})`.mapWith(users.id) +}).from(users); +``` + +# New Packages +## ESLint Drizzle Plugin + +For cases where it's impossible to perform type checks for specific scenarios, or where it's possible but error messages would be challenging to understand, we've decided to create an ESLint package with recommended rules. This package aims to assist developers in handling crucial scenarios during development + +> Big thanks to @Angelelz for initiating the development of this package and transferring it to the Drizzle Team's npm + +## Install + +```sh +[ npm | yarn | pnpm | bun ] install eslint eslint-plugin-drizzle +``` +You can install those packages for typescript support in your IDE +```sh +[ npm | yarn | pnpm | bun ] install @typescript-eslint/eslint-plugin @typescript-eslint/parser +``` + +## Usage + +Create a `.eslintrc.yml` file, add `drizzle` to the `plugins`, and specify the rules you want to use. You can find a list of all existing rules below + +```yml +root: true +parser: '@typescript-eslint/parser' +parserOptions: + project: './tsconfig.json' +plugins: + - drizzle +rules: + 'drizzle/enforce-delete-with-where': "error" + 'drizzle/enforce-update-with-where': "error" +``` + +### All config + +This plugin exports an [`all` config](src/configs/all.js) that makes use of all rules (except for deprecated ones). + +```yml +root: true +extends: + - "plugin:drizzle/all" +parser: '@typescript-eslint/parser' +parserOptions: + project: './tsconfig.json' +plugins: + - drizzle +``` + +At the moment, `all` is equivalent to `recommended` + +```yml +root: true +extends: + - "plugin:drizzle/recommended" +parser: '@typescript-eslint/parser' +parserOptions: + project: './tsconfig.json' +plugins: + - drizzle +``` + +## Rules + +**enforce-delete-with-where**: Enforce using `delete` with the`.where()` clause in the `.delete()` statement. Most of the time, you don't need to delete all rows in the table and require some kind of `WHERE` statements. + +**Error Message**: +``` +Without `.where(...)` you will delete all the rows in a table. If you didn't want to do it, please use `db.delete(...).where(...)` instead. Otherwise you can ignore this rule here +``` + +Optionally, you can define a `drizzleObjectName` in the plugin options that accept a `string` or `string[]`. This is useful when you have objects or classes with a delete method that's not from Drizzle. Such a `delete` method will trigger the ESLint rule. To avoid that, you can define the name of the Drizzle object that you use in your codebase (like db) so that the rule would only trigger if the delete method comes from this object: + +Example, config 1: +```json +"rules": { + "drizzle/enforce-delete-with-where": ["error"] +} +``` + +```ts +class MyClass { + public delete() { + return {} + } +} + +const myClassObj = new MyClass(); + +// ---> Will be triggered by ESLint Rule +myClassObj.delete() + +const db = drizzle(...) +// ---> Will be triggered by ESLint Rule +db.delete() +``` + +Example, config 2: +```json +"rules": { + "drizzle/enforce-delete-with-where": ["error", { "drizzleObjectName": ["db"] }], +} +``` +```ts +class MyClass { + public delete() { + return {} + } +} + +const myClassObj = new MyClass(); + +// ---> Will NOT be triggered by ESLint Rule +myClassObj.delete() + +const db = drizzle(...) +// ---> Will be triggered by ESLint Rule +db.delete() +``` + +**enforce-update-with-where**: Enforce using `update` with the`.where()` clause in the `.update()` statement. Most of the time, you don't need to update all rows in the table and require some kind of `WHERE` statements. + +**Error Message**: +``` +Without `.where(...)` you will update all the rows in a table. If you didn't want to do it, please use `db.update(...).set(...).where(...)` instead. Otherwise you can ignore this rule here +``` + +Optionally, you can define a `drizzleObjectName` in the plugin options that accept a `string` or `string[]`. This is useful when you have objects or classes with a delete method that's not from Drizzle. Such as `update` method will trigger the ESLint rule. To avoid that, you can define the name of the Drizzle object that you use in your codebase (like db) so that the rule would only trigger if the delete method comes from this object: + +Example, config 1: +```json +"rules": { + "drizzle/enforce-update-with-where": ["error"] +} +``` + +```ts +class MyClass { + public update() { + return {} + } +} + +const myClassObj = new MyClass(); + +// ---> Will be triggered by ESLint Rule +myClassObj.update() + +const db = drizzle(...) +// ---> Will be triggered by ESLint Rule +db.update() +``` + +Example, config 2: +```json +"rules": { + "drizzle/enforce-update-with-where": ["error", { "drizzleObjectName": ["db"] }], +} +``` +```ts +class MyClass { + public update() { + return {} + } +} + +const myClassObj = new MyClass(); + +// ---> Will NOT be triggered by ESLint Rule +myClassObj.update() + +const db = drizzle(...) +// ---> Will be triggered by ESLint Rule +db.update() +``` \ No newline at end of file diff --git a/changelogs/eslint-plugin-drizzle/0.2.1.md b/changelogs/eslint-plugin-drizzle/0.2.1.md new file mode 100644 index 000000000..b29f78c3a --- /dev/null +++ b/changelogs/eslint-plugin-drizzle/0.2.1.md @@ -0,0 +1,4 @@ +# eslint-plugin-drizzle 0.2.1 + +- Update README.md +- Change error text message \ No newline at end of file diff --git a/drizzle-orm/package.json b/drizzle-orm/package.json index 6d2613d75..2a1658773 100644 --- a/drizzle-orm/package.json +++ b/drizzle-orm/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-orm", - "version": "0.29.0", + "version": "0.29.1", "description": "Drizzle ORM package for SQL databases", "type": "module", "scripts": { diff --git a/eslint-plugin-drizzle/package.json b/eslint-plugin-drizzle/package.json index b9c46b991..f2a51feb9 100644 --- a/eslint-plugin-drizzle/package.json +++ b/eslint-plugin-drizzle/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-drizzle", - "version": "0.2.0", + "version": "0.2.1", "description": "Eslint plugin for drizzle users to avoid common pitfalls", "main": "src/index.js", "scripts": { @@ -15,10 +15,10 @@ "eslint-plugin", "drizzle" ], - "author": "Angelelz", + "author": "Drizzle Team", "repository": { "type": "git", - "url": "git+https://github.com/drizzle-team/drizzle-orm.git" + "url": "git+https://github.com/drizzle-team/drizzle-orm/tree/main/eslint-plugin-drizzle.git" }, "license": "Apache-2.0", "devDependencies": { diff --git a/eslint-plugin-drizzle/readme.md b/eslint-plugin-drizzle/readme.md index fb45efc03..1d45b2345 100644 --- a/eslint-plugin-drizzle/readme.md +++ b/eslint-plugin-drizzle/readme.md @@ -1,85 +1,163 @@ # eslint-plugin-drizzle -eslint plugin for drizzle users to avoid common pitfalls +For cases where it's impossible to perform type checks for specific scenarios, or where it's possible but error messages would be challenging to understand, we've decided to create an ESLint package with recommended rules. This package aims to assist developers in handling crucial scenarios during development + +> Big thanks to @Angelelz for initiating the development of this package and transferring it to the Drizzle Team's npm ## Install ```sh [ npm | yarn | pnpm | bun ] install eslint eslint-plugin-drizzle ``` +You can install those packages for typescript support in your IDE +```sh +[ npm | yarn | pnpm | bun ] install @typescript-eslint/eslint-plugin @typescript-eslint/parser +``` ## Usage -Use a [preset config](#preset-configs) or configure each rule in `package.json` or `eslint.config.js`. +Create a `.eslintrc.yml` file, add `drizzle` to the `plugins`, and specify the rules you want to use. You can find a list of all existing rules below + +```yml +root: true +parser: '@typescript-eslint/parser' +parserOptions: + project: './tsconfig.json' +plugins: + - drizzle +rules: + 'drizzle/enforce-delete-with-where': "error" + 'drizzle/enforce-update-with-where': "error" +``` -If you don't use the preset, ensure you use the same `env` and `parserOptions` config as below. +### All config -```json -{ - "name": "my-awesome-project", - "eslintConfig": { - "env": { - "es2024": true - }, - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "plugins": ["drizzle"], - "rules": { - "drizzle/enforce-delete-with-where": "error", - "drizzle/enforce-update-with-where": "error" - } - } -} +This plugin exports an [`all` config](src/configs/all.js) that makes use of all rules (except for deprecated ones). + +```yml +root: true +extends: + - "plugin:drizzle/all" +parser: '@typescript-eslint/parser' +parserOptions: + project: './tsconfig.json' +plugins: + - drizzle +``` + +At the moment, `all` is equivalent to `recommended` + +```yml +root: true +extends: + - "plugin:drizzle/recommended" +parser: '@typescript-eslint/parser' +parserOptions: + project: './tsconfig.json' +plugins: + - drizzle ``` ## Rules -**enforce-delete-with-where**: Enforce using `delete` with `where` in `DELETE` statement -Optionally, you can defined a `drizzleObjectName` in the plugin options that accepts a string or an array of strings. -This is useful when you have object or classes with a delete method that's not from drizzle. Such delete method will trigger the eslint rule. -To avoid that, you can define the name of the drizzle object that you use in your codebase (like `db`) so that the rule would only trigger if the delete method comes from this object: +**enforce-delete-with-where**: Enforce using `delete` with the`.where()` clause in the `.delete()` statement. Most of the time, you don't need to delete all rows in the table and require some kind of `WHERE` statements. + +Optionally, you can define a `drizzleObjectName` in the plugin options that accept a `string` or `string[]`. This is useful when you have objects or classes with a delete method that's not from Drizzle. Such a `delete` method will trigger the ESLint rule. To avoid that, you can define the name of the Drizzle object that you use in your codebase (like db) so that the rule would only trigger if the delete method comes from this object: + +Example, config 1: ```json "rules": { - "drizzle/enforce-delete-with-where": ["error", { "drizzleObjectName": ["db", "dataSource", "database"] }], + "drizzle/enforce-delete-with-where": ["error"] +} +``` + +```ts +class MyClass { + public delete() { + return {} + } } + +const myClassObj = new MyClass(); + +// ---> Will be triggered by ESLint Rule +myClassObj.delete() + +const db = drizzle(...) +// ---> Will be triggered by ESLint Rule +db.delete() ``` -**enforce-update-with-where**: Enforce using `update` with `where` in `UPDATE` statement -Similar to the delete rule, you can define the name of the drizzle object that you use in your codebase (like `db`) so that the rule would only trigger if the update method comes from this object: +Example, config 2: ```json "rules": { - "drizzle/enforce-update-with-where": ["error", { "drizzleObjectName": "db" }], + "drizzle/enforce-delete-with-where": ["error", { "drizzleObjectName": ["db"] }], } ``` +```ts +class MyClass { + public delete() { + return {} + } +} + +const myClassObj = new MyClass(); -## Preset configs +// ---> Will NOT be triggered by ESLint Rule +myClassObj.delete() -### Recommended config +const db = drizzle(...) +// ---> Will be triggered by ESLint Rule +db.delete() +``` + +**enforce-update-with-where**: Enforce using `update` with the`.where()` clause in the `.update()` statement. Most of the time, you don't need to update all rows in the table and require some kind of `WHERE` statements. -This plugin exports a [`recommended` config](src/configs/recommended.js) that enforces good practices. +Optionally, you can define a `drizzleObjectName` in the plugin options that accept a `string` or `string[]`. This is useful when you have objects or classes with a delete method that's not from Drizzle. Such as `update` method will trigger the ESLint rule. To avoid that, you can define the name of the Drizzle object that you use in your codebase (like db) so that the rule would only trigger if the delete method comes from this object: +Example, config 1: ```json -{ - "name": "my-awesome-project", - "eslintConfig": { - "extends": "plugin:drizzle/recommended" - } +"rules": { + "drizzle/enforce-update-with-where": ["error"] } ``` -### All config +```ts +class MyClass { + public update() { + return {} + } +} -This plugin exports an [`all` config](src/configs/all.js) that makes use of all rules (except for deprecated ones). +const myClassObj = new MyClass(); +// ---> Will be triggered by ESLint Rule +myClassObj.update() + +const db = drizzle(...) +// ---> Will be triggered by ESLint Rule +db.update() +``` + +Example, config 2: ```json -{ - "name": "my-awesome-project", - "eslintConfig": { - "extends": "plugin:drizzle/all" - } +"rules": { + "drizzle/enforce-update-with-where": ["error", { "drizzleObjectName": ["db"] }], } ``` +```ts +class MyClass { + public update() { + return {} + } +} + +const myClassObj = new MyClass(); -At the moment, `all` is equivalent to `recommended`. +// ---> Will NOT be triggered by ESLint Rule +myClassObj.update() + +const db = drizzle(...) +// ---> Will be triggered by ESLint Rule +db.update() +``` diff --git a/eslint-plugin-drizzle/src/enforce-delete-with-where.ts b/eslint-plugin-drizzle/src/enforce-delete-with-where.ts index 21b4824a9..8b1b2d569 100644 --- a/eslint-plugin-drizzle/src/enforce-delete-with-where.ts +++ b/eslint-plugin-drizzle/src/enforce-delete-with-where.ts @@ -17,7 +17,7 @@ const deleteRule = createRule({ }, fixable: 'code', messages: { - enforceDeleteWithWhere: 'Avoid deleting all the rows in a table. Use `db.delete(...).where(...)` instead.', + enforceDeleteWithWhere: 'Without `.where(...)` you will delete all the rows in a table. If you didn\'t want to do it, please use `db.delete(...).where(...)` instead. Otherwise you can ignore this rule here', }, schema: [{ type: 'object', diff --git a/eslint-plugin-drizzle/src/enforce-update-with-where.ts b/eslint-plugin-drizzle/src/enforce-update-with-where.ts index 8c524b1ee..bbe822d43 100644 --- a/eslint-plugin-drizzle/src/enforce-update-with-where.ts +++ b/eslint-plugin-drizzle/src/enforce-update-with-where.ts @@ -17,7 +17,7 @@ const updateRule = createRule({ fixable: 'code', messages: { enforceUpdateWithWhere: - 'Avoid updating all the rows in a table. Use `db.update(...).set(...).where(...)` instead.', + 'Without `.where(...)` you will update all the rows in a table. If you didn\'t want to do it, please use `db.update(...).set(...).where(...)` instead. Otherwise you can ignore this rule here' }, schema: [{ type: 'object', From e78b53e248f7c837da4f41e60d96d45efde2f697 Mon Sep 17 00:00:00 2001 From: AndriiSherman Date: Wed, 29 Nov 2023 17:21:39 +0200 Subject: [PATCH 50/50] Add JsDoc info --- changelogs/drizzle-orm/0.29.1.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/changelogs/drizzle-orm/0.29.1.md b/changelogs/drizzle-orm/0.29.1.md index f1f526ed9..2b02b109a 100644 --- a/changelogs/drizzle-orm/0.29.1.md +++ b/changelogs/drizzle-orm/0.29.1.md @@ -5,7 +5,11 @@ # New Features/Helpers -## New helpers for aggregate functions in SQL - Thanks @L-Mario564 +## 🎉 Detailed JSDoc for all query builders in all dialects - thanks @realmikesolo + +You can now access more information, hints, documentation links, etc. while developing and using JSDoc right in your IDE. Previously, we had them only for filter expressions, but now you can see them for all parts of the Drizzle query builder + +## 🎉 New helpers for aggregate functions in SQL - thanks @L-Mario564 > Remember, aggregation functions are often used with the GROUP BY clause of the SELECT statement. So if you are selecting using aggregating functions and other columns in one query, be sure to use the `.groupBy` clause @@ -97,7 +101,7 @@ await db.select({ ``` # New Packages -## ESLint Drizzle Plugin +## 🎉 ESLint Drizzle Plugin For cases where it's impossible to perform type checks for specific scenarios, or where it's possible but error messages would be challenging to understand, we've decided to create an ESLint package with recommended rules. This package aims to assist developers in handling crucial scenarios during development