Skip to content

Commit

Permalink
Merge pull request #4028 from drizzle-team/on-conflict-chaining-fix
Browse files Browse the repository at this point in the history
SQLite onConflict chaining fix
  • Loading branch information
AndriiSherman authored Jan 29, 2025
2 parents f36e3ea + a42bb03 commit 3cd4f9f
Show file tree
Hide file tree
Showing 9 changed files with 432 additions and 12 deletions.
7 changes: 5 additions & 2 deletions drizzle-orm/src/alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,11 @@ export class RelationTableAliasProxyHandler<T extends Relation> implements Proxy
}
}

export function aliasedTable<T extends Table>(table: T, tableAlias: string): T {
return new Proxy(table, new TableAliasProxyHandler(tableAlias, false));
export function aliasedTable<T extends Table | View>(
table: T,
tableAlias: string,
): T {
return new Proxy(table, new TableAliasProxyHandler(tableAlias, false)) as any;
}

export function aliasedRelation<T extends Relation>(relation: T, tableAlias: string): T {
Expand Down
2 changes: 1 addition & 1 deletion drizzle-orm/src/mysql-core/query-builders/select.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export type MySqlJoin<
T['_']['selection'],
TJoinedName,
TJoinedTable extends MySqlTable ? TJoinedTable['_']['columns']
: TJoinedTable extends Subquery ? Assume<TJoinedTable['_']['selectedFields'], SelectedFields>
: TJoinedTable extends Subquery | View ? Assume<TJoinedTable['_']['selectedFields'], SelectedFields>
: never,
T['_']['selectMode']
>,
Expand Down
2 changes: 1 addition & 1 deletion drizzle-orm/src/pg-core/query-builders/select.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export type PgSelectJoin<
T['_']['selection'],
TJoinedName,
TJoinedTable extends Table ? TJoinedTable['_']['columns']
: TJoinedTable extends Subquery ? Assume<TJoinedTable['_']['selectedFields'], SelectedFields>
: TJoinedTable extends Subquery | View ? Assume<TJoinedTable['_']['selectedFields'], SelectedFields>
: never,
T['_']['selectMode']
>,
Expand Down
4 changes: 2 additions & 2 deletions drizzle-orm/src/sql/sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export class SQL<T = unknown> implements SQLWrapper {
const schemaName = chunk[Table.Symbol.Schema];
const tableName = chunk[Table.Symbol.Name];
return {
sql: schemaName === undefined
sql: schemaName === undefined || chunk[IsAlias]
? escapeName(tableName)
: escapeName(schemaName) + '.' + escapeName(tableName),
params: [],
Expand All @@ -208,7 +208,7 @@ export class SQL<T = unknown> implements SQLWrapper {
const schemaName = chunk[ViewBaseConfig].schema;
const viewName = chunk[ViewBaseConfig].name;
return {
sql: schemaName === undefined
sql: schemaName === undefined || chunk[ViewBaseConfig].isAlias
? escapeName(viewName)
: escapeName(schemaName) + '.' + escapeName(viewName),
params: [],
Expand Down
2 changes: 1 addition & 1 deletion drizzle-orm/src/sqlite-core/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { SQLiteCountBuilder } from './query-builders/count.ts';
import { RelationalQueryBuilder } from './query-builders/query.ts';
import { SQLiteRaw } from './query-builders/raw.ts';
import type { SelectedFields } from './query-builders/select.types.ts';
import type { WithBuilder, WithSubqueryWithSelection } from './subquery.ts';
import type { WithBuilder } from './subquery.ts';
import type { SQLiteViewBase } from './view-base.ts';

export class BaseSQLiteDatabase<
Expand Down
4 changes: 3 additions & 1 deletion drizzle-orm/src/sqlite-core/dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,9 @@ export abstract class SQLiteDialect {
? sql` returning ${this.buildSelection(returning, { isSingleTable: true })}`
: undefined;

const onConflictSql = onConflict ? sql` on conflict ${onConflict}` : undefined;
const onConflictSql = onConflict?.length
? sql.join(onConflict)
: undefined;

// if (isSingleValue && valuesSqlList.length === 0){
// return sql`insert into ${table} default values ${onConflictSql}${returningSql}`;
Expand Down
15 changes: 11 additions & 4 deletions drizzle-orm/src/sqlite-core/query-builders/insert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface SQLiteInsertConfig<TTable extends SQLiteTable = SQLiteTable> {
table: TTable;
values: Record<string, Param | SQL>[] | SQLiteInsertSelectQueryBuilder<TTable> | SQL;
withList?: Subquery[];
onConflict?: SQL;
onConflict?: SQL[];
returning?: SelectedFieldsOrdered;
select?: boolean;
}
Expand Down Expand Up @@ -303,12 +303,14 @@ export class SQLiteInsertBase<
* ```
*/
onConflictDoNothing(config: { target?: IndexColumn | IndexColumn[]; where?: SQL } = {}): this {
if (!this.config.onConflict) this.config.onConflict = [];

if (config.target === undefined) {
this.config.onConflict = sql`do nothing`;
this.config.onConflict.push(sql` on conflict do nothing`);
} else {
const targetSql = Array.isArray(config.target) ? sql`${config.target}` : sql`${[config.target]}`;
const whereSql = config.where ? sql` where ${config.where}` : sql``;
this.config.onConflict = sql`${targetSql} do nothing${whereSql}`;
this.config.onConflict.push(sql` on conflict ${targetSql} do nothing${whereSql}`);
}
return this;
}
Expand Down Expand Up @@ -348,12 +350,17 @@ export class SQLiteInsertBase<
'You cannot use both "where" and "targetWhere"/"setWhere" at the same time - "where" is deprecated, use "targetWhere" or "setWhere" instead.',
);
}

if (!this.config.onConflict) this.config.onConflict = [];

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 targetSql = Array.isArray(config.target) ? sql`${config.target}` : sql`${[config.target]}`;
const setSql = this.dialect.buildUpdateSet(this.config.table, mapUpdateSet(this.config.table, config.set));
this.config.onConflict = sql`${targetSql}${targetWhereSql} do update set ${setSql}${whereSql}${setWhereSql}`;
this.config.onConflict.push(
sql` on conflict ${targetSql}${targetWhereSql} do update set ${setSql}${whereSql}${setWhereSql}`,
);
return this;
}

Expand Down
232 changes: 232 additions & 0 deletions drizzle-orm/type-tests/common/aliased-table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import { type Equal, Expect } from 'type-tests/utils.ts';
import { eq } from '~/index.ts';
import { drizzle as sqlited } from '~/libsql/index.ts';
import { alias as mysqlAliasFn } from '~/mysql-core/alias.ts';
import { mysqlView } from '~/mysql-core/view.ts';
import { drizzle as mysqld } from '~/mysql2/index.ts';
import { alias as pgAliasFn } from '~/pg-core/alias.ts';
import { pgView } from '~/pg-core/view.ts';
import { drizzle as pgd } from '~/postgres-js/index.ts';
import { alias as sqliteAliasFn } from '~/sqlite-core/alias.ts';
import { sqliteView } from '~/sqlite-core/view.ts';
import { users as mysqlUsers } from '../mysql/tables.ts';
import { users as pgUsers } from '../pg/tables.ts';
import { users as sqliteUsers } from '../sqlite/tables.ts';

const pg = pgd.mock();
const sqlite = sqlited.mock();
const mysql = mysqld.mock();

const pgvUsers = pgView('users_view').as((qb) => qb.select().from(pgUsers));
const sqlitevUsers = sqliteView('users_view').as((qb) => qb.select().from(sqliteUsers));
const mysqlvUsers = mysqlView('users_view').as((qb) => qb.select().from(mysqlUsers));

const pgAlias = pgAliasFn(pgUsers, 'usersAlias');
const sqliteAlias = sqliteAliasFn(sqliteUsers, 'usersAlias');
const mysqlAlias = mysqlAliasFn(mysqlUsers, 'usersAlias');

const pgvAlias = pgAliasFn(pgvUsers, 'usersvAlias');
const sqlitevAlias = sqliteAliasFn(sqlitevUsers, 'usersvAlias');
const mysqlvAlias = mysqlAliasFn(mysqlvUsers, 'usersvAlias');

const pgRes = await pg.select().from(pgUsers).leftJoin(pgAlias, eq(pgAlias.id, pgUsers.id));
const sqliteRes = await sqlite.select().from(sqliteUsers).leftJoin(sqliteAlias, eq(sqliteAlias.id, sqliteUsers.id));
const mysqlRes = await mysql.select().from(mysqlUsers).leftJoin(mysqlAlias, eq(mysqlAlias.id, mysqlUsers.id));

const pgvRes = await pg.select().from(pgUsers).leftJoin(pgvAlias, eq(pgvAlias.id, pgUsers.id));
const sqlitevRes = await sqlite.select().from(sqliteUsers).leftJoin(sqlitevAlias, eq(sqlitevAlias.id, sqliteUsers.id));
const mysqlvRes = await mysql.select().from(mysqlUsers).leftJoin(mysqlvAlias, eq(mysqlvAlias.id, mysqlUsers.id));

Expect<
Equal<typeof pgRes, {
users_table: {
id: number;
uuid: string;
homeCity: number;
currentCity: number | null;
serialNullable: number;
serialNotNull: number;
class: 'A' | 'C';
subClass: 'B' | 'D' | null;
text: string | null;
age1: number;
createdAt: Date;
enumCol: 'a' | 'b' | 'c';
arrayCol: string[];
};
usersAlias: {
id: number;
uuid: string;
homeCity: number;
currentCity: number | null;
serialNullable: number;
serialNotNull: number;
class: 'A' | 'C';
subClass: 'B' | 'D' | null;
text: string | null;
age1: number;
createdAt: Date;
enumCol: 'a' | 'b' | 'c';
arrayCol: string[];
} | null;
}[]>
>;

Expect<
Equal<typeof sqliteRes, {
users_table: {
id: number;
homeCity: number;
currentCity: number | null;
serialNullable: number | null;
serialNotNull: number;
class: 'A' | 'C';
subClass: 'B' | 'D' | null;
name: string | null;
age1: number;
createdAt: Date;
enumCol: 'a' | 'b' | 'c';
};
usersAlias: {
id: number;
homeCity: number;
currentCity: number | null;
serialNullable: number | null;
serialNotNull: number;
class: 'A' | 'C';
subClass: 'B' | 'D' | null;
name: string | null;
age1: number;
createdAt: Date;
enumCol: 'a' | 'b' | 'c';
} | null;
}[]>
>;

Expect<
Equal<typeof mysqlRes, {
users_table: {
id: number;
homeCity: number;
currentCity: number | null;
serialNullable: number;
serialNotNull: number;
class: 'A' | 'C';
subClass: 'B' | 'D' | null;
text: string | null;
age1: number;
createdAt: Date;
enumCol: 'a' | 'b' | 'c';
};
usersAlias: {
id: number;
homeCity: number;
currentCity: number | null;
serialNullable: number;
serialNotNull: number;
class: 'A' | 'C';
subClass: 'B' | 'D' | null;
text: string | null;
age1: number;
createdAt: Date;
enumCol: 'a' | 'b' | 'c';
} | null;
}[]>
>;

Expect<
Equal<typeof pgvRes, {
users_table: {
id: number;
uuid: string;
homeCity: number;
currentCity: number | null;
serialNullable: number;
serialNotNull: number;
class: 'A' | 'C';
subClass: 'B' | 'D' | null;
text: string | null;
age1: number;
createdAt: Date;
enumCol: 'a' | 'b' | 'c';
arrayCol: string[];
};
usersvAlias: {
id: number;
uuid: string;
homeCity: number;
currentCity: number | null;
serialNullable: number;
serialNotNull: number;
class: 'A' | 'C';
subClass: 'B' | 'D' | null;
text: string | null;
age1: number;
createdAt: Date;
enumCol: 'a' | 'b' | 'c';
arrayCol: string[];
} | null;
}[]>
>;

Expect<
Equal<typeof sqlitevRes, {
users_table: {
id: number;
homeCity: number;
currentCity: number | null;
serialNullable: number | null;
serialNotNull: number;
class: 'A' | 'C';
subClass: 'B' | 'D' | null;
name: string | null;
age1: number;
createdAt: Date;
enumCol: 'a' | 'b' | 'c';
};
usersvAlias: {
id: number;
homeCity: number;
currentCity: number | null;
serialNullable: number | null;
serialNotNull: number;
class: 'A' | 'C';
subClass: 'B' | 'D' | null;
name: string | null;
age1: number;
createdAt: Date;
enumCol: 'a' | 'b' | 'c';
} | null;
}[]>
>;

Expect<
Equal<typeof mysqlvRes, {
users_table: {
id: number;
homeCity: number;
currentCity: number | null;
serialNullable: number;
serialNotNull: number;
class: 'A' | 'C';
subClass: 'B' | 'D' | null;
text: string | null;
age1: number;
createdAt: Date;
enumCol: 'a' | 'b' | 'c';
};
usersvAlias: {
id: number;
homeCity: number;
currentCity: number | null;
serialNullable: number;
serialNotNull: number;
class: 'A' | 'C';
subClass: 'B' | 'D' | null;
text: string | null;
age1: number;
createdAt: Date;
enumCol: 'a' | 'b' | 'c';
} | null;
}[]>
>;
Loading

0 comments on commit 3cd4f9f

Please sign in to comment.