From c24e818248fe1b60403283a3b2ee2533ab0c20d6 Mon Sep 17 00:00:00 2001 From: Dan Kochetov Date: Wed, 20 Mar 2024 21:04:12 +0200 Subject: [PATCH 1/9] Add custom schema support to Postgres enums --- changelogs/drizzle-orm/0.30.5.md | 8 ++++++++ drizzle-orm/package.json | 2 +- drizzle-orm/src/pg-core/columns/enum.ts | 13 ++++++++++++- drizzle-orm/src/pg-core/schema.ts | 6 ++++++ drizzle-orm/src/sql/sql.ts | 11 +++++++++-- drizzle-valibot/src/index.ts | 3 ++- integration-tests/tests/pg-schema.test.ts | 23 ++++++++++++++++++++++- 7 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 changelogs/drizzle-orm/0.30.5.md diff --git a/changelogs/drizzle-orm/0.30.5.md b/changelogs/drizzle-orm/0.30.5.md new file mode 100644 index 000000000..b19461bb3 --- /dev/null +++ b/changelogs/drizzle-orm/0.30.5.md @@ -0,0 +1,8 @@ +- 🎉 Added custom schema support to enums in Postgres: + + ```ts + import { pgSchema } from 'drizzle-orm/pg-core'; + + const mySchema = pgSchema('mySchema'); + const colors = mySchema.enum('colors', ['red', 'green', 'blue']); + ``` diff --git a/drizzle-orm/package.json b/drizzle-orm/package.json index 0b431121c..dbb7e4b47 100644 --- a/drizzle-orm/package.json +++ b/drizzle-orm/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-orm", - "version": "0.30.4", + "version": "0.30.5", "description": "Drizzle ORM package for SQL databases", "type": "module", "scripts": { diff --git a/drizzle-orm/src/pg-core/columns/enum.ts b/drizzle-orm/src/pg-core/columns/enum.ts index 7f3840271..912e9057e 100644 --- a/drizzle-orm/src/pg-core/columns/enum.ts +++ b/drizzle-orm/src/pg-core/columns/enum.ts @@ -21,6 +21,7 @@ export interface PgEnum { readonly enumName: string; readonly enumValues: TValues; + readonly schema: string | undefined; /** @internal */ [isPgEnumSym]: true; } @@ -76,12 +77,22 @@ export function pgEnum>( enumName: string, values: T | Writable, ): PgEnum> { - const enumInstance = Object.assign( + return pgEnumWithSchema(enumName, values, undefined); +} + +/** @internal */ +export function pgEnumWithSchema>( + enumName: string, + values: T | Writable, + schema?: string, +): PgEnum> { + const enumInstance: PgEnum> = Object.assign( (name: TName): PgEnumColumnBuilderInitial> => new PgEnumColumnBuilder(name, enumInstance), { enumName, enumValues: values, + schema, [isPgEnumSym]: true, } as const, ); diff --git a/drizzle-orm/src/pg-core/schema.ts b/drizzle-orm/src/pg-core/schema.ts index 5e3237428..35f674729 100644 --- a/drizzle-orm/src/pg-core/schema.ts +++ b/drizzle-orm/src/pg-core/schema.ts @@ -1,4 +1,6 @@ import { entityKind, is } from '~/entity.ts'; +import type { pgEnum } from './columns/enum.ts'; +import { pgEnumWithSchema } from './columns/enum.ts'; import { type PgTableFn, pgTableWithSchema } from './table.ts'; import { type pgMaterializedView, pgMaterializedViewWithSchema, type pgView, pgViewWithSchema } from './view.ts'; @@ -19,6 +21,10 @@ export class PgSchema { materializedView = ((name, columns) => { return pgMaterializedViewWithSchema(name, columns, this.schemaName); }) as typeof pgMaterializedView; + + enum: typeof pgEnum = ((name, values) => { + return pgEnumWithSchema(name, values, this.schemaName); + }); } export function isPgSchema(obj: unknown): obj is PgSchema { diff --git a/drizzle-orm/src/sql/sql.ts b/drizzle-orm/src/sql/sql.ts index 2ed161028..317329223 100644 --- a/drizzle-orm/src/sql/sql.ts +++ b/drizzle-orm/src/sql/sql.ts @@ -1,5 +1,6 @@ import { entityKind, is } from '~/entity.ts'; import type { SelectedFields } from '~/operations.ts'; +import { isPgEnum } from '~/pg-core/columns/enum.ts'; import { Subquery, SubqueryConfig } from '~/subquery.ts'; import { tracer } from '~/tracing.ts'; import { ViewBaseConfig } from '~/view-common.ts'; @@ -62,8 +63,7 @@ export interface SQLWrapper { } export function isSQLWrapper(value: unknown): value is SQLWrapper { - return typeof value === 'object' && value !== null && 'getSQL' in value - && typeof (value as any).getSQL === 'function'; + return value !== null && value !== undefined && typeof (value as any).getSQL === 'function'; } function mergeQueries(queries: QueryWithTypings[]): QueryWithTypings { @@ -236,6 +236,13 @@ export class SQL implements SQLWrapper { ], config); } + if (isPgEnum(chunk)) { + if (chunk.schema) { + return { sql: escapeName(chunk.schema) + '.' + escapeName(chunk.enumName), params: [] }; + } + return { sql: escapeName(chunk.enumName), params: [] }; + } + if (isSQLWrapper(chunk)) { return this.buildQueryFromSourceParams([ new StringChunk('('), diff --git a/drizzle-valibot/src/index.ts b/drizzle-valibot/src/index.ts index 71f7b2947..0c84c5052 100644 --- a/drizzle-valibot/src/index.ts +++ b/drizzle-valibot/src/index.ts @@ -83,7 +83,8 @@ type GetValibotType = TColumn['_']['dataType'] extends i : TColumn extends { enumValues: [string, ...string[]] } ? Equal extends true ? StringSchema : PicklistSchema - : TDataType extends 'array' ? ArraySchema>> + : TDataType extends 'array' + ? TColumn['_']['baseColumn'] extends Column ? ArraySchema> : never : TDataType extends 'bigint' ? BigintSchema : TDataType extends 'number' ? NumberSchema : TDataType extends 'string' ? StringSchema diff --git a/integration-tests/tests/pg-schema.test.ts b/integration-tests/tests/pg-schema.test.ts index f2c116805..9194d14e0 100644 --- a/integration-tests/tests/pg-schema.test.ts +++ b/integration-tests/tests/pg-schema.test.ts @@ -14,6 +14,7 @@ import { getViewConfig, integer, jsonb, + PgDialect, pgSchema, pgTable, pgTableCreator, @@ -27,6 +28,8 @@ import { v4 as uuid } from 'uuid'; const { Client } = pg; +const ENABLE_LOGGING = false; + const mySchema = pgSchema('mySchema'); const usersTable = mySchema.table('users', { @@ -119,7 +122,7 @@ test.before(async (t) => { await ctx.pgContainer?.stop().catch(console.error); throw lastError; } - ctx.db = drizzle(ctx.client /* , { logger: new DefaultLogger() } */); + ctx.db = drizzle(ctx.client, { logger: ENABLE_LOGGING }); }); test.after.always(async (t) => { @@ -971,3 +974,21 @@ test.serial('materialized view', async (t) => { await db.execute(sql`drop materialized view ${newYorkers1}`); }); + +test.serial('enum', async (t) => { + const { db } = t.context; + + const colors = mySchema.enum('colors', ['red', 'green', 'blue']); + + t.deepEqual(colors.schema, 'mySchema'); + + const { sql: query } = new PgDialect().sqlToQuery(sql`${colors}`); + t.deepEqual(query, '"mySchema"."colors"'); + + await db.execute(sql`create type ${colors} as enum ('red', 'green', 'blue')`); + + const result = await db.execute<{ enum_range: string }>(sql`select enum_range(null::${colors})`); + t.deepEqual(result.rows, [{ enum_range: '{red,green,blue}' }]); + + await db.execute(sql`drop type ${colors}`); +}); From 9e6a6eee4f6df8e26d6a1bcf862431288712d540 Mon Sep 17 00:00:00 2001 From: Dan Kochetov Date: Wed, 20 Mar 2024 22:11:14 +0200 Subject: [PATCH 2/9] Add Xata credentials to branch pipeline --- .github/workflows/release-feature-branch.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release-feature-branch.yaml b/.github/workflows/release-feature-branch.yaml index d451c4bbf..4d3cc84af 100644 --- a/.github/workflows/release-feature-branch.yaml +++ b/.github/workflows/release-feature-branch.yaml @@ -114,6 +114,8 @@ jobs: MYSQL_CONNECTION_STRING: mysql://root:root@localhost:3306/drizzle PLANETSCALE_CONNECTION_STRING: ${{ secrets.PLANETSCALE_CONNECTION_STRING }} NEON_CONNECTION_STRING: ${{ secrets.NEON_CONNECTION_STRING }} + XATA_API_KEY: ${{ secrets.XATA_API_KEY }} + XATA_BRANCH: ${{ secrets.XATA_BRANCH }} LIBSQL_URL: file:local.db run: | if [[ ${{ github.event_name }} != "push" && "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then From 629bc9c7c8fc61e9bf0ba6279397689db29edae2 Mon Sep 17 00:00:00 2001 From: Dan Kochetov Date: Fri, 22 Mar 2024 19:09:53 +0200 Subject: [PATCH 3/9] Add setWhere and targetWhere to on conflict clause in Postgres, fix where location for on conflict do nothing Fixes #1628, #1302 --- changelogs/drizzle-orm/0.30.5.md | 3 +++ .../src/pg-core/query-builders/insert.ts | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/changelogs/drizzle-orm/0.30.5.md b/changelogs/drizzle-orm/0.30.5.md index b19461bb3..932c01653 100644 --- a/changelogs/drizzle-orm/0.30.5.md +++ b/changelogs/drizzle-orm/0.30.5.md @@ -6,3 +6,6 @@ const mySchema = pgSchema('mySchema'); const colors = mySchema.enum('colors', ['red', 'green', 'blue']); ``` + +- 🐛 Split `where` clause in Postgres `.onConflictDoUpdate` method into `setWhere` and `targetWhere` clauses, to support both `where` cases in `on conflict ...` clause (#1628, #1302) +- 🐛 Fix query generation for `where` clause in Postgres `.onConflictDoNothing` method, as it was placed in a wrong spot (#1628) diff --git a/drizzle-orm/src/pg-core/query-builders/insert.ts b/drizzle-orm/src/pg-core/query-builders/insert.ts index 1e9e19aaf..8d99da045 100644 --- a/drizzle-orm/src/pg-core/query-builders/insert.ts +++ b/drizzle-orm/src/pg-core/query-builders/insert.ts @@ -102,7 +102,11 @@ export type PgInsertReturningAll { target: IndexColumn | IndexColumn[]; + /** @deprecated use either `targetWhere` or `setWhere` */ where?: SQL; + // TODO: add tests for targetWhere and setWhere + targetWhere?: SQL; + setWhere?: SQL; set: PgUpdateSetSource; } @@ -242,7 +246,7 @@ export class PgInsertBase< : this.dialect.escapeName(config.target.name); const whereSql = config.where ? sql` where ${config.where}` : undefined; - this.config.onConflict = sql`(${sql.raw(targetColumn)}) do nothing${whereSql}`; + this.config.onConflict = sql`(${sql.raw(targetColumn)}) ${whereSql} do nothing`; } return this as any; } @@ -272,20 +276,29 @@ export class PgInsertBase< * .onConflictDoUpdate({ * target: cars.id, * set: { brand: 'newBMW' }, - * where: sql`${cars.createdAt} > '2023-01-01'::date`, + * targetWhere: sql`${cars.createdAt} > '2023-01-01'::date`, * }); * ``` */ onConflictDoUpdate( config: PgInsertOnConflictDoUpdateConfig, ): PgInsertWithout { + if (config.where && (config.targetWhere || config.setWhere)) { + throw new Error( + 'You cannot use both "where" and "targetWhere"/"setWhere" at the same time - "where" is deprecated, use "targetWhere" or "setWhere" instead.', + ); + } const whereSql = config.where ? sql` where ${config.where}` : undefined; + const targetWhereSql = config.targetWhere ? sql` where ${config.targetWhere}` : undefined; + const setWhereSql = config.setWhere ? sql` where ${config.setWhere}` : undefined; const setSql = this.dialect.buildUpdateSet(this.config.table, mapUpdateSet(this.config.table, config.set)); let targetColumn = ''; targetColumn = Array.isArray(config.target) ? config.target.map((it) => this.dialect.escapeName(it.name)).join(',') : this.dialect.escapeName(config.target.name); - this.config.onConflict = sql`(${sql.raw(targetColumn)}) do update set ${setSql}${whereSql}`; + this.config.onConflict = sql`(${ + sql.raw(targetColumn) + })${targetWhereSql} do update set ${setSql}${whereSql}${setWhereSql}`; return this as any; } From 35c8b2be1aa09001a4a72974711b3d3c9c77bd84 Mon Sep 17 00:00:00 2001 From: Dan Kochetov Date: Fri, 22 Mar 2024 19:14:36 +0200 Subject: [PATCH 4/9] Fix query generation --- drizzle-orm/src/pg-core/query-builders/insert.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drizzle-orm/src/pg-core/query-builders/insert.ts b/drizzle-orm/src/pg-core/query-builders/insert.ts index 8d99da045..64d72b125 100644 --- a/drizzle-orm/src/pg-core/query-builders/insert.ts +++ b/drizzle-orm/src/pg-core/query-builders/insert.ts @@ -246,7 +246,7 @@ export class PgInsertBase< : this.dialect.escapeName(config.target.name); const whereSql = config.where ? sql` where ${config.where}` : undefined; - this.config.onConflict = sql`(${sql.raw(targetColumn)}) ${whereSql} do nothing`; + this.config.onConflict = sql`(${sql.raw(targetColumn)})${whereSql} do nothing`; } return this as any; } From c94bf3208977398517bf020b66cc0330815c3e82 Mon Sep 17 00:00:00 2001 From: Dan Kochetov Date: Mon, 25 Mar 2024 12:07:49 +0200 Subject: [PATCH 5/9] Expose subquery config as API --- drizzle-orm/src/mysql-core/dialect.ts | 6 +-- .../src/mysql-core/query-builders/select.ts | 6 +-- drizzle-orm/src/pg-core/dialect.ts | 6 +-- .../src/pg-core/query-builders/select.ts | 6 +-- drizzle-orm/src/selection-proxy.ts | 12 +++--- drizzle-orm/src/sql/sql.ts | 10 ++--- drizzle-orm/src/sqlite-core/dialect.ts | 6 +-- .../src/sqlite-core/query-builders/select.ts | 6 +-- drizzle-orm/src/subquery.ts | 37 ++++++++++--------- drizzle-orm/src/utils.ts | 4 +- 10 files changed, 51 insertions(+), 48 deletions(-) diff --git a/drizzle-orm/src/mysql-core/dialect.ts b/drizzle-orm/src/mysql-core/dialect.ts index 37cd9a2f5..396a5853e 100644 --- a/drizzle-orm/src/mysql-core/dialect.ts +++ b/drizzle-orm/src/mysql-core/dialect.ts @@ -15,7 +15,7 @@ import { type TablesRelationalConfig, } from '~/relations.ts'; import { Param, type QueryWithTypings, SQL, sql, type SQLChunk, View } from '~/sql/sql.ts'; -import { Subquery, SubqueryConfig } from '~/subquery.ts'; +import { Subquery } from '~/subquery.ts'; import { getTableName, Table } from '~/table.ts'; import { orderSelectedFields, type UpdateSet } from '~/utils.ts'; import { and, DrizzleError, eq, type Name, ViewBaseConfig } from '../index.ts'; @@ -88,7 +88,7 @@ export class MySqlDialect { const withSqlChunks = [sql`with `]; for (const [i, w] of queries.entries()) { - withSqlChunks.push(sql`${sql.identifier(w[SubqueryConfig].alias)} as (${w[SubqueryConfig].sql})`); + withSqlChunks.push(sql`${sql.identifier(w._.alias)} as (${w._.sql})`); if (i < queries.length - 1) { withSqlChunks.push(sql`, `); } @@ -226,7 +226,7 @@ export class MySqlDialect { is(f.field, Column) && getTableName(f.field.table) !== (is(table, Subquery) - ? table[SubqueryConfig].alias + ? table._.alias : is(table, MySqlViewBase) ? table[ViewBaseConfig].name : is(table, SQL) diff --git a/drizzle-orm/src/mysql-core/query-builders/select.ts b/drizzle-orm/src/mysql-core/query-builders/select.ts index d429840e4..59dbe914e 100644 --- a/drizzle-orm/src/mysql-core/query-builders/select.ts +++ b/drizzle-orm/src/mysql-core/query-builders/select.ts @@ -19,7 +19,7 @@ import { QueryPromise } from '~/query-promise.ts'; import { SelectionProxyHandler } from '~/selection-proxy.ts'; import type { ColumnsSelection, Query } from '~/sql/sql.ts'; import { SQL, View } from '~/sql/sql.ts'; -import { Subquery, SubqueryConfig } from '~/subquery.ts'; +import { Subquery } from '~/subquery.ts'; import { Table } from '~/table.ts'; import { applyMixins, getTableColumns, getTableLikeName, haveSameKeys, type ValueOrArray } from '~/utils.ts'; import { orderSelectedFields } from '~/utils.ts'; @@ -93,7 +93,7 @@ export class MySqlSelectBuilder< } else if (is(source, Subquery)) { // This is required to use the proxy handler to get the correct field values from the subquery fields = Object.fromEntries( - Object.keys(source[SubqueryConfig].selection).map(( + Object.keys(source._.selectedFields).map(( key, ) => [key, source[key as unknown as keyof typeof source] as unknown as SelectedFields[string]]), ); @@ -207,7 +207,7 @@ export abstract class MySqlSelectQueryBuilderBase< } if (typeof tableName === 'string' && !is(table, SQL)) { const selection = is(table, Subquery) - ? table[SubqueryConfig].selection + ? table._.selectedFields : is(table, View) ? table[ViewBaseConfig].selectedFields : table[Table.Symbol.Columns]; diff --git a/drizzle-orm/src/pg-core/dialect.ts b/drizzle-orm/src/pg-core/dialect.ts index 7e55eac71..0855be7f7 100644 --- a/drizzle-orm/src/pg-core/dialect.ts +++ b/drizzle-orm/src/pg-core/dialect.ts @@ -35,7 +35,7 @@ import { sql, type SQLChunk, } from '~/sql/sql.ts'; -import { Subquery, SubqueryConfig } from '~/subquery.ts'; +import { Subquery } from '~/subquery.ts'; import { getTableName, Table } from '~/table.ts'; import { orderSelectedFields, type UpdateSet } from '~/utils.ts'; import { ViewBaseConfig } from '~/view-common.ts'; @@ -104,7 +104,7 @@ export class PgDialect { const withSqlChunks = [sql`with `]; for (const [i, w] of queries.entries()) { - withSqlChunks.push(sql`${sql.identifier(w[SubqueryConfig].alias)} as (${w[SubqueryConfig].sql})`); + withSqlChunks.push(sql`${sql.identifier(w._.alias)} as (${w._.sql})`); if (i < queries.length - 1) { withSqlChunks.push(sql`, `); } @@ -242,7 +242,7 @@ export class PgDialect { is(f.field, Column) && getTableName(f.field.table) !== (is(table, Subquery) - ? table[SubqueryConfig].alias + ? table._.alias : is(table, PgViewBase) ? table[ViewBaseConfig].name : is(table, SQL) diff --git a/drizzle-orm/src/pg-core/query-builders/select.ts b/drizzle-orm/src/pg-core/query-builders/select.ts index dfdb4a2c2..d2406995b 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.ts @@ -21,7 +21,7 @@ import type { RunnableQuery } from '~/runnable-query.ts'; import { SelectionProxyHandler } from '~/selection-proxy.ts'; import { SQL, View } from '~/sql/sql.ts'; import type { ColumnsSelection, Placeholder, Query, SQLWrapper } from '~/sql/sql.ts'; -import { Subquery, SubqueryConfig } from '~/subquery.ts'; +import { Subquery } from '~/subquery.ts'; import { Table } from '~/table.ts'; import { tracer } from '~/tracing.ts'; import { applyMixins, getTableColumns, getTableLikeName, haveSameKeys, type ValueOrArray } from '~/utils.ts'; @@ -103,7 +103,7 @@ export class PgSelectBuilder< } else if (is(source, Subquery)) { // This is required to use the proxy handler to get the correct field values from the subquery fields = Object.fromEntries( - Object.keys(source[SubqueryConfig].selection).map(( + Object.keys(source._.selectedFields).map(( key, ) => [key, source[key as unknown as keyof typeof source] as unknown as SelectedFields[string]]), ); @@ -215,7 +215,7 @@ export abstract class PgSelectQueryBuilderBase< } if (typeof tableName === 'string' && !is(table, SQL)) { const selection = is(table, Subquery) - ? table[SubqueryConfig].selection + ? table._.selectedFields : is(table, View) ? table[ViewBaseConfig].selectedFields : table[Table.Symbol.Columns]; diff --git a/drizzle-orm/src/selection-proxy.ts b/drizzle-orm/src/selection-proxy.ts index 9127793ac..7cf46415a 100644 --- a/drizzle-orm/src/selection-proxy.ts +++ b/drizzle-orm/src/selection-proxy.ts @@ -2,7 +2,7 @@ import { ColumnAliasProxyHandler, TableAliasProxyHandler } from './alias.ts'; import { Column } from './column.ts'; import { entityKind, is } from './entity.ts'; import { SQL, View } from './sql/sql.ts'; -import { Subquery, SubqueryConfig } from './subquery.ts'; +import { Subquery } from './subquery.ts'; import { ViewBaseConfig } from './view-common.ts'; export class SelectionProxyHandler | View> @@ -45,11 +45,11 @@ export class SelectionProxyHandler } get(subquery: T, prop: string | symbol): any { - if (prop === SubqueryConfig) { + if (prop === '_') { return { - ...subquery[SubqueryConfig as keyof typeof subquery], - selection: new Proxy( - (subquery as Subquery)[SubqueryConfig].selection, + ...subquery['_' as keyof typeof subquery], + selectedFields: new Proxy( + (subquery as Subquery)._.selectedFields, this as ProxyHandler>, ), }; @@ -70,7 +70,7 @@ export class SelectionProxyHandler } const columns = is(subquery, Subquery) - ? subquery[SubqueryConfig].selection + ? subquery._.selectedFields : is(subquery, View) ? subquery[ViewBaseConfig].selectedFields : subquery; diff --git a/drizzle-orm/src/sql/sql.ts b/drizzle-orm/src/sql/sql.ts index 317329223..306f82519 100644 --- a/drizzle-orm/src/sql/sql.ts +++ b/drizzle-orm/src/sql/sql.ts @@ -1,7 +1,7 @@ import { entityKind, is } from '~/entity.ts'; import type { SelectedFields } from '~/operations.ts'; import { isPgEnum } from '~/pg-core/columns/enum.ts'; -import { Subquery, SubqueryConfig } from '~/subquery.ts'; +import { Subquery } from '~/subquery.ts'; import { tracer } from '~/tracing.ts'; import { ViewBaseConfig } from '~/view-common.ts'; import type { AnyColumn } from '../column.ts'; @@ -225,14 +225,14 @@ export class SQL implements SQLWrapper { } if (is(chunk, Subquery)) { - if (chunk[SubqueryConfig].isWith) { - return { sql: escapeName(chunk[SubqueryConfig].alias), params: [] }; + if (chunk._.isWith) { + return { sql: escapeName(chunk._.alias), params: [] }; } return this.buildQueryFromSourceParams([ new StringChunk('('), - chunk[SubqueryConfig].sql, + chunk._.sql, new StringChunk(') '), - new Name(chunk[SubqueryConfig].alias), + new Name(chunk._.alias), ], config); } diff --git a/drizzle-orm/src/sqlite-core/dialect.ts b/drizzle-orm/src/sqlite-core/dialect.ts index 324f631a7..ccd46b546 100644 --- a/drizzle-orm/src/sqlite-core/dialect.ts +++ b/drizzle-orm/src/sqlite-core/dialect.ts @@ -22,7 +22,7 @@ import { Param, type QueryWithTypings, SQL, sql, type SQLChunk } from '~/sql/sql import { SQLiteColumn } from '~/sqlite-core/columns/index.ts'; import type { SQLiteDeleteConfig, SQLiteInsertConfig, SQLiteUpdateConfig } from '~/sqlite-core/query-builders/index.ts'; import { SQLiteTable } from '~/sqlite-core/table.ts'; -import { Subquery, SubqueryConfig } from '~/subquery.ts'; +import { Subquery } from '~/subquery.ts'; import { getTableName, Table } from '~/table.ts'; import { orderSelectedFields, type UpdateSet } from '~/utils.ts'; import { ViewBaseConfig } from '~/view-common.ts'; @@ -54,7 +54,7 @@ export abstract class SQLiteDialect { const withSqlChunks = [sql`with `]; for (const [i, w] of queries.entries()) { - withSqlChunks.push(sql`${sql.identifier(w[SubqueryConfig].alias)} as (${w[SubqueryConfig].sql})`); + withSqlChunks.push(sql`${sql.identifier(w._.alias)} as (${w._.sql})`); if (i < queries.length - 1) { withSqlChunks.push(sql`, `); } @@ -193,7 +193,7 @@ export abstract class SQLiteDialect { is(f.field, Column) && getTableName(f.field.table) !== (is(table, Subquery) - ? table[SubqueryConfig].alias + ? table._.alias : is(table, SQLiteViewBase) ? table[ViewBaseConfig].name : is(table, SQL) diff --git a/drizzle-orm/src/sqlite-core/query-builders/select.ts b/drizzle-orm/src/sqlite-core/query-builders/select.ts index 5985cbd70..b7f4b0465 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/select.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/select.ts @@ -20,7 +20,7 @@ import type { SQLiteDialect } from '~/sqlite-core/dialect.ts'; import type { SQLiteSession } from '~/sqlite-core/session.ts'; import type { SubqueryWithSelection } from '~/sqlite-core/subquery.ts'; import type { SQLiteTable } from '~/sqlite-core/table.ts'; -import { Subquery, SubqueryConfig } from '~/subquery.ts'; +import { Subquery } from '~/subquery.ts'; import { Table } from '~/table.ts'; import { applyMixins, @@ -99,7 +99,7 @@ export class SQLiteSelectBuilder< } else if (is(source, Subquery)) { // This is required to use the proxy handler to get the correct field values from the subquery fields = Object.fromEntries( - Object.keys(source[SubqueryConfig].selection).map(( + Object.keys(source._.selectedFields).map(( key, ) => [key, source[key as unknown as keyof typeof source] as unknown as SelectedFields[string]]), ); @@ -214,7 +214,7 @@ export abstract class SQLiteSelectQueryBuilderBase< } if (typeof tableName === 'string' && !is(table, SQL)) { const selection = is(table, Subquery) - ? table[SubqueryConfig].selection + ? table._.selectedFields : is(table, View) ? table[ViewBaseConfig].selectedFields : table[Table.Symbol.Columns]; diff --git a/drizzle-orm/src/subquery.ts b/drizzle-orm/src/subquery.ts index 42ee96ade..320ec46e6 100644 --- a/drizzle-orm/src/subquery.ts +++ b/drizzle-orm/src/subquery.ts @@ -1,34 +1,34 @@ import { entityKind } from './entity.ts'; -import type { ColumnsSelection, SQL, SQLWrapper } from './sql/sql.ts'; +import type { SQL, SQLWrapper } from './sql/sql.ts'; -export const SubqueryConfig = Symbol.for('drizzle:SubqueryConfig'); - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export interface Subquery extends SQLWrapper { +export interface Subquery< + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TAlias extends string = string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TSelectedFields extends Record = Record, +> extends SQLWrapper { // SQLWrapper runtime implementation is defined in 'sql/sql.ts' } -export class Subquery implements SQLWrapper { +export class Subquery< + TAlias extends string = string, + TSelectedFields extends Record = Record, +> implements SQLWrapper { static readonly [entityKind]: string = 'Subquery'; declare _: { brand: 'Subquery'; + sql: SQL; selectedFields: TSelectedFields; alias: TAlias; - }; - - /** @internal */ - [SubqueryConfig]: { - sql: SQL; - selection: ColumnsSelection; - alias: string; isWith: boolean; }; constructor(sql: SQL, selection: Record, alias: string, isWith = false) { - this[SubqueryConfig] = { + this._ = { + brand: 'Subquery', sql, - selection, - alias, + selectedFields: selection as TSelectedFields, + alias: alias as TAlias, isWith, }; } @@ -38,6 +38,9 @@ export class Subquery // } } -export class WithSubquery extends Subquery { +export class WithSubquery< + TAlias extends string = string, + TSelection extends Record = Record, +> extends Subquery { static readonly [entityKind]: string = 'WithSubquery'; } diff --git a/drizzle-orm/src/utils.ts b/drizzle-orm/src/utils.ts index e7b1190cd..07b8290fd 100644 --- a/drizzle-orm/src/utils.ts +++ b/drizzle-orm/src/utils.ts @@ -6,7 +6,7 @@ import type { SelectedFieldsOrdered } from './operations.ts'; import type { TableLike } from './query-builders/select.types.ts'; import { Param, SQL, View } from './sql/sql.ts'; import type { DriverValueDecoder } from './sql/sql.ts'; -import { Subquery, SubqueryConfig } from './subquery.ts'; +import { Subquery } from './subquery.ts'; import { getTableName, Table } from './table.ts'; import { ViewBaseConfig } from './view-common.ts'; @@ -192,7 +192,7 @@ export function getTableColumns(table: T): T['_']['columns'] { /** @internal */ export function getTableLikeName(table: TableLike): string | undefined { return is(table, Subquery) - ? table[SubqueryConfig].alias + ? table._.alias : is(table, View) ? table[ViewBaseConfig].name : is(table, SQL) From 05c0ab3a8eb1f883f12b031388179b8265b9f8ed Mon Sep 17 00:00:00 2001 From: Dan Kochetov Date: Mon, 25 Mar 2024 13:15:17 +0200 Subject: [PATCH 6/9] Add missing awaits to Xata tests --- integration-tests/tests/xata-http.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/tests/xata-http.test.ts b/integration-tests/tests/xata-http.test.ts index fc95e84fa..04f93e56c 100644 --- a/integration-tests/tests/xata-http.test.ts +++ b/integration-tests/tests/xata-http.test.ts @@ -2317,7 +2317,7 @@ test('insert undefined', async () => { sql`create table ${users} (id serial not null primary key, name text)`, ); - expect(async () => await db.insert(users).values({ name: undefined })).not.toThrowError(); + await expect(async () => await db.insert(users).values({ name: undefined })).resolves.not.toThrowError(); await db.execute(sql`drop table ${users}`); }); @@ -2335,7 +2335,7 @@ test('update undefined', async () => { ); await expect(async () => await db.update(users).set({ name: undefined })).rejects.toThrowError(); - expect(async () => await db.update(users).set({ id: 1, name: undefined })).not.toThrowError(); + await expect(async () => await db.update(users).set({ id: 1, name: undefined })).resolves.not.toThrowError(); await db.execute(sql`drop table ${users}`); }); From 033da881e8e8a8d886e99fe709a13a9f10fde666 Mon Sep 17 00:00:00 2001 From: Dan Kochetov Date: Mon, 25 Mar 2024 13:45:36 +0200 Subject: [PATCH 7/9] Fix async expect --- integration-tests/tests/xata-http.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-tests/tests/xata-http.test.ts b/integration-tests/tests/xata-http.test.ts index 04f93e56c..22416ae58 100644 --- a/integration-tests/tests/xata-http.test.ts +++ b/integration-tests/tests/xata-http.test.ts @@ -2106,11 +2106,11 @@ test('transaction', async () => { const user = await db.insert(users).values({ balance: 100 }).returning().then((rows) => rows[0]!); const product = await db.insert(products).values({ price: 10, stock: 10 }).returning().then((rows) => rows[0]!); - await expect(async () => + await expect( db.transaction(async (tx) => { await tx.update(users).set({ balance: user.balance - product.price }).where(eq(users.id, user.id)); await tx.update(products).set({ stock: product.stock - 1 }).where(eq(products.id, product.id)); - }) + }), ).rejects.toThrowError('No transactions support in Xata Http driver'); // t.is(error!.message, 'No transactions support in Xata Http driver'); @@ -2317,7 +2317,7 @@ test('insert undefined', async () => { sql`create table ${users} (id serial not null primary key, name text)`, ); - await expect(async () => await db.insert(users).values({ name: undefined })).resolves.not.toThrowError(); + await expect(db.insert(users).values({ name: undefined })).resolves.not.toThrowError(); await db.execute(sql`drop table ${users}`); }); @@ -2334,8 +2334,8 @@ test('update undefined', async () => { sql`create table ${users} (id serial not null primary key, name text)`, ); - await expect(async () => await db.update(users).set({ name: undefined })).rejects.toThrowError(); - await expect(async () => await db.update(users).set({ id: 1, name: undefined })).resolves.not.toThrowError(); + await expect(await db.update(users).set({ name: undefined })).rejects.toThrowError(); + await expect(await db.update(users).set({ id: 1, name: undefined })).resolves.not.toThrowError(); await db.execute(sql`drop table ${users}`); }); From e2d2da4612861f22514287a9d9a7e6af7015e8a0 Mon Sep 17 00:00:00 2001 From: Dan Kochetov Date: Mon, 25 Mar 2024 13:56:32 +0200 Subject: [PATCH 8/9] Remove await --- integration-tests/tests/xata-http.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/tests/xata-http.test.ts b/integration-tests/tests/xata-http.test.ts index 22416ae58..94181129f 100644 --- a/integration-tests/tests/xata-http.test.ts +++ b/integration-tests/tests/xata-http.test.ts @@ -2334,8 +2334,8 @@ test('update undefined', async () => { sql`create table ${users} (id serial not null primary key, name text)`, ); - await expect(await db.update(users).set({ name: undefined })).rejects.toThrowError(); - await expect(await db.update(users).set({ id: 1, name: undefined })).resolves.not.toThrowError(); + await expect(db.update(users).set({ name: undefined })).rejects.toThrowError(); + await expect(db.update(users).set({ id: 1, name: undefined })).resolves.not.toThrowError(); await db.execute(sql`drop table ${users}`); }); From ab9feb7a71248464307eebc29adedbf12960eaeb Mon Sep 17 00:00:00 2001 From: Dan Kochetov Date: Mon, 25 Mar 2024 14:06:45 +0200 Subject: [PATCH 9/9] Fix error test --- integration-tests/tests/xata-http.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/tests/xata-http.test.ts b/integration-tests/tests/xata-http.test.ts index 94181129f..8a70aca6c 100644 --- a/integration-tests/tests/xata-http.test.ts +++ b/integration-tests/tests/xata-http.test.ts @@ -2334,7 +2334,7 @@ test('update undefined', async () => { sql`create table ${users} (id serial not null primary key, name text)`, ); - await expect(db.update(users).set({ name: undefined })).rejects.toThrowError(); + expect(() => db.update(users).set({ name: undefined })).toThrowError(); await expect(db.update(users).set({ id: 1, name: undefined })).resolves.not.toThrowError(); await db.execute(sql`drop table ${users}`);