Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support .orderBy() and .limit() in update and delete statements (MySQL | SQLite) #2981

Merged
merged 9 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 26 additions & 16 deletions drizzle-orm/src/mysql-core/dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
type TablesRelationalConfig,
} from '~/relations.ts';
import { Param, SQL, sql, View } from '~/sql/sql.ts';
import type { Name, QueryWithTypings, SQLChunk } from '~/sql/sql.ts';
import type { Name, Placeholder, QueryWithTypings, SQLChunk } from '~/sql/sql.ts';
import { Subquery } from '~/subquery.ts';
import { getTableName, getTableUniqueName, Table } from '~/table.ts';
import { type Casing, orderSelectedFields, type UpdateSet } from '~/utils.ts';
Expand Down Expand Up @@ -112,7 +112,7 @@ export class MySqlDialect {
return sql.join(withSqlChunks);
}

buildDeleteQuery({ table, where, returning, withList }: MySqlDeleteConfig): SQL {
buildDeleteQuery({ table, where, returning, withList, limit, orderBy }: MySqlDeleteConfig): SQL {
const withSql = this.buildWithCTE(withList);

const returningSql = returning
Expand All @@ -121,7 +121,11 @@ export class MySqlDialect {

const whereSql = where ? sql` where ${where}` : undefined;

return sql`${withSql}delete from ${table}${whereSql}${returningSql}`;
const orderBySql = this.buildOrderBy(orderBy);

const limitSql = this.buildLimit(limit);

return sql`${withSql}delete from ${table}${whereSql}${orderBySql}${limitSql}${returningSql}`;
}

buildUpdateSet(table: MySqlTable, set: UpdateSet): SQL {
Expand All @@ -145,7 +149,7 @@ export class MySqlDialect {
}));
}

buildUpdateQuery({ table, set, where, returning, withList }: MySqlUpdateConfig): SQL {
buildUpdateQuery({ table, set, where, returning, withList, limit, orderBy }: MySqlUpdateConfig): SQL {
const withSql = this.buildWithCTE(withList);

const setSql = this.buildUpdateSet(table, set);
Expand All @@ -156,7 +160,11 @@ export class MySqlDialect {

const whereSql = where ? sql` where ${where}` : undefined;

return sql`${withSql}update ${table} set ${setSql}${whereSql}${returningSql}`;
const orderBySql = this.buildOrderBy(orderBy);

const limitSql = this.buildLimit(limit);

return sql`${withSql}update ${table} set ${setSql}${whereSql}${orderBySql}${limitSql}${returningSql}`;
}

/**
Expand Down Expand Up @@ -221,6 +229,16 @@ export class MySqlDialect {
return sql.join(chunks);
}

private buildLimit(limit: number | Placeholder | undefined): SQL | undefined {
return typeof limit === 'object' || (typeof limit === 'number' && limit >= 0)
? sql` limit ${limit}`
: undefined;
}

private buildOrderBy(orderBy: (MySqlColumn | SQL | SQL.Aliased)[] | undefined): SQL | undefined {
return orderBy && orderBy.length > 0 ? sql` order by ${sql.join(orderBy, sql`, `)}` : undefined;
}

buildSelectQuery(
{
withList,
Expand Down Expand Up @@ -328,19 +346,11 @@ export class MySqlDialect {

const havingSql = having ? sql` having ${having}` : undefined;

let orderBySql;
if (orderBy && orderBy.length > 0) {
orderBySql = sql` order by ${sql.join(orderBy, sql`, `)}`;
}
const orderBySql = this.buildOrderBy(orderBy);

let groupBySql;
if (groupBy && groupBy.length > 0) {
groupBySql = sql` group by ${sql.join(groupBy, sql`, `)}`;
}
const groupBySql = groupBy && groupBy.length > 0 ? sql` group by ${sql.join(groupBy, sql`, `)}` : undefined;

const limitSql = typeof limit === 'object' || (typeof limit === 'number' && limit >= 0)
? sql` limit ${limit}`
: undefined;
const limitSql = this.buildLimit(limit);

const offsetSql = offset ? sql` offset ${offset}` : undefined;

Expand Down
39 changes: 38 additions & 1 deletion drizzle-orm/src/mysql-core/query-builders/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ import type {
} from '~/mysql-core/session.ts';
import type { MySqlTable } from '~/mysql-core/table.ts';
import { QueryPromise } from '~/query-promise.ts';
import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts';
import { SelectionProxyHandler } from '~/selection-proxy.ts';
import type { Placeholder, Query, SQL, SQLWrapper } from '~/sql/sql.ts';
import type { Subquery } from '~/subquery.ts';
import { Table } from '~/table.ts';
import type { ValueOrArray } from '~/utils.ts';
import type { MySqlColumn } from '../columns/common.ts';
import type { SelectedFieldsOrdered } from './select.types.ts';

export type MySqlDeleteWithout<
Expand All @@ -39,6 +43,8 @@ export type MySqlDelete<

export interface MySqlDeleteConfig {
where?: SQL | undefined;
limit?: number | Placeholder;
orderBy?: (MySqlColumn | SQL | SQL.Aliased)[];
table: MySqlTable;
returning?: SelectedFieldsOrdered;
withList?: Subquery[];
Expand Down Expand Up @@ -134,6 +140,37 @@ export class MySqlDeleteBase<
return this as any;
}

orderBy(
builder: (deleteTable: TTable) => ValueOrArray<MySqlColumn | SQL | SQL.Aliased>,
): MySqlDeleteWithout<this, TDynamic, 'orderBy'>;
orderBy(...columns: (MySqlColumn | SQL | SQL.Aliased)[]): MySqlDeleteWithout<this, TDynamic, 'orderBy'>;
orderBy(
...columns:
| [(deleteTable: TTable) => ValueOrArray<MySqlColumn | SQL | SQL.Aliased>]
| (MySqlColumn | SQL | SQL.Aliased)[]
): MySqlDeleteWithout<this, TDynamic, 'orderBy'> {
if (typeof columns[0] === 'function') {
const orderBy = columns[0](
new Proxy(
this.config.table[Table.Symbol.Columns],
new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }),
) as any,
);

const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy];
this.config.orderBy = orderByArray;
} else {
const orderByArray = columns as (MySqlColumn | SQL | SQL.Aliased)[];
this.config.orderBy = orderByArray;
}
return this as any;
}

limit(limit: number | Placeholder): MySqlDeleteWithout<this, TDynamic, 'limit'> {
this.config.limit = limit;
return this as any;
}

/** @internal */
getSQL(): SQL {
return this.dialect.buildDeleteQuery(this.config);
Expand Down
40 changes: 38 additions & 2 deletions drizzle-orm/src/mysql-core/query-builders/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ import type {
} from '~/mysql-core/session.ts';
import type { MySqlTable } from '~/mysql-core/table.ts';
import { QueryPromise } from '~/query-promise.ts';
import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts';
import { SelectionProxyHandler } from '~/selection-proxy.ts';
import type { Placeholder, Query, SQL, SQLWrapper } from '~/sql/sql.ts';
import type { Subquery } from '~/subquery.ts';
import { mapUpdateSet, type UpdateSet } from '~/utils.ts';
import { Table } from '~/table.ts';
import { mapUpdateSet, type UpdateSet, type ValueOrArray } from '~/utils.ts';
import type { MySqlColumn } from '../columns/common.ts';
import type { SelectedFieldsOrdered } from './select.types.ts';

export interface MySqlUpdateConfig {
where?: SQL | undefined;
limit?: number | Placeholder;
orderBy?: (MySqlColumn | SQL | SQL.Aliased)[];
set: UpdateSet;
table: MySqlTable;
returning?: SelectedFieldsOrdered;
Expand Down Expand Up @@ -173,6 +178,37 @@ export class MySqlUpdateBase<
return this as any;
}

orderBy(
builder: (updateTable: TTable) => ValueOrArray<MySqlColumn | SQL | SQL.Aliased>,
): MySqlUpdateWithout<this, TDynamic, 'orderBy'>;
orderBy(...columns: (MySqlColumn | SQL | SQL.Aliased)[]): MySqlUpdateWithout<this, TDynamic, 'orderBy'>;
orderBy(
...columns:
| [(updateTable: TTable) => ValueOrArray<MySqlColumn | SQL | SQL.Aliased>]
| (MySqlColumn | SQL | SQL.Aliased)[]
): MySqlUpdateWithout<this, TDynamic, 'orderBy'> {
if (typeof columns[0] === 'function') {
const orderBy = columns[0](
new Proxy(
this.config.table[Table.Symbol.Columns],
new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }),
) as any,
);

const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy];
this.config.orderBy = orderByArray;
} else {
const orderByArray = columns as (MySqlColumn | SQL | SQL.Aliased)[];
this.config.orderBy = orderByArray;
}
return this as any;
}

limit(limit: number | Placeholder): MySqlUpdateWithout<this, TDynamic, 'limit'> {
this.config.limit = limit;
return this as any;
}

/** @internal */
getSQL(): SQL {
return this.dialect.buildUpdateQuery(this.config);
Expand Down
57 changes: 37 additions & 20 deletions drizzle-orm/src/sqlite-core/dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
type TableRelationalConfig,
type TablesRelationalConfig,
} from '~/relations.ts';
import type { Name } from '~/sql/index.ts';
import type { Name, Placeholder } from '~/sql/index.ts';
import { and, eq } from '~/sql/index.ts';
import { Param, type QueryWithTypings, SQL, sql, type SQLChunk } from '~/sql/sql.ts';
import { SQLiteColumn } from '~/sqlite-core/columns/index.ts';
Expand Down Expand Up @@ -75,7 +75,7 @@ export abstract class SQLiteDialect {
return sql.join(withSqlChunks);
}

buildDeleteQuery({ table, where, returning, withList }: SQLiteDeleteConfig): SQL {
buildDeleteQuery({ table, where, returning, withList, limit, orderBy }: SQLiteDeleteConfig): SQL {
const withSql = this.buildWithCTE(withList);

const returningSql = returning
Expand All @@ -84,7 +84,11 @@ export abstract class SQLiteDialect {

const whereSql = where ? sql` where ${where}` : undefined;

return sql`${withSql}delete from ${table}${whereSql}${returningSql}`;
const orderBySql = this.buildOrderBy(orderBy);

const limitSql = this.buildLimit(limit);

return sql`${withSql}delete from ${table}${whereSql}${returningSql}${orderBySql}${limitSql}`;
}

buildUpdateSet(table: SQLiteTable, set: UpdateSet): SQL {
Expand All @@ -108,7 +112,7 @@ export abstract class SQLiteDialect {
}));
}

buildUpdateQuery({ table, set, where, returning, withList }: SQLiteUpdateConfig): SQL {
buildUpdateQuery({ table, set, where, returning, withList, limit, orderBy }: SQLiteUpdateConfig): SQL {
const withSql = this.buildWithCTE(withList);

const setSql = this.buildUpdateSet(table, set);
Expand All @@ -119,7 +123,11 @@ export abstract class SQLiteDialect {

const whereSql = where ? sql` where ${where}` : undefined;

return sql`${withSql}update ${table} set ${setSql}${whereSql}${returningSql}`;
const orderBySql = this.buildOrderBy(orderBy);

const limitSql = this.buildLimit(limit);

return sql`${withSql}update ${table} set ${setSql}${whereSql}${returningSql}${orderBySql}${limitSql}`;
}

/**
Expand Down Expand Up @@ -185,6 +193,28 @@ export abstract class SQLiteDialect {
return sql.join(chunks);
}

private buildLimit(limit: number | Placeholder | undefined): SQL | undefined {
return typeof limit === 'object' || (typeof limit === 'number' && limit >= 0)
? sql` limit ${limit}`
: undefined;
}

private buildOrderBy(orderBy: (SQLiteColumn | SQL | SQL.Aliased)[] | undefined): SQL | undefined {
const orderByList: (SQLiteColumn | SQL | SQL.Aliased)[] = [];

if (orderBy) {
for (const [index, orderByValue] of orderBy.entries()) {
orderByList.push(orderByValue);

if (index < orderBy.length - 1) {
orderByList.push(sql`, `);
}
}
}

return orderByList.length > 0 ? sql` order by ${sql.join(orderByList)}` : undefined;
}

buildSelectQuery(
{
withList,
Expand Down Expand Up @@ -280,17 +310,6 @@ export abstract class SQLiteDialect {

const havingSql = having ? sql` having ${having}` : undefined;

const orderByList: (SQLiteColumn | SQL | SQL.Aliased)[] = [];
if (orderBy) {
for (const [index, orderByValue] of orderBy.entries()) {
orderByList.push(orderByValue);

if (index < orderBy.length - 1) {
orderByList.push(sql`, `);
}
}
}

const groupByList: (SQL | AnyColumn | SQL.Aliased)[] = [];
if (groupBy) {
for (const [index, groupByValue] of groupBy.entries()) {
Expand All @@ -304,11 +323,9 @@ export abstract class SQLiteDialect {

const groupBySql = groupByList.length > 0 ? sql` group by ${sql.join(groupByList)}` : undefined;

const orderBySql = orderByList.length > 0 ? sql` order by ${sql.join(orderByList)}` : undefined;
const orderBySql = this.buildOrderBy(orderBy);

const limitSql = typeof limit === 'object' || (typeof limit === 'number' && limit >= 0)
? sql` limit ${limit}`
: undefined;
const limitSql = this.buildLimit(limit);

const offsetSql = offset ? sql` offset ${offset}` : undefined;

Expand Down
39 changes: 37 additions & 2 deletions drizzle-orm/src/sqlite-core/query-builders/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { entityKind } from '~/entity.ts';
import type { SelectResultFields } from '~/query-builders/select.types.ts';
import { QueryPromise } from '~/query-promise.ts';
import type { RunnableQuery } from '~/runnable-query.ts';
import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts';
import { SelectionProxyHandler } from '~/selection-proxy.ts';
import type { Placeholder, Query, SQL, SQLWrapper } from '~/sql/sql.ts';
import type { SQLiteDialect } from '~/sqlite-core/dialect.ts';
import type { SQLitePreparedQuery, SQLiteSession } from '~/sqlite-core/session.ts';
import { SQLiteTable } from '~/sqlite-core/table.ts';
import type { Subquery } from '~/subquery.ts';
import { type DrizzleTypeError, orderSelectedFields } from '~/utils.ts';
import { Table } from '~/table.ts';
import { type DrizzleTypeError, orderSelectedFields, type ValueOrArray } from '~/utils.ts';
import type { SQLiteColumn } from '../columns/common.ts';
import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types.ts';

Expand Down Expand Up @@ -37,6 +39,8 @@ export type SQLiteDelete<

export interface SQLiteDeleteConfig {
where?: SQL | undefined;
limit?: number | Placeholder;
orderBy?: (SQLiteColumn | SQL | SQL.Aliased)[];
table: SQLiteTable;
returning?: SelectedFieldsOrdered;
withList?: Subquery[];
Expand Down Expand Up @@ -185,6 +189,37 @@ export class SQLiteDeleteBase<
return this as any;
}

orderBy(
builder: (deleteTable: TTable) => ValueOrArray<SQLiteColumn | SQL | SQL.Aliased>,
): SQLiteDeleteWithout<this, TDynamic, 'orderBy'>;
orderBy(...columns: (SQLiteColumn | SQL | SQL.Aliased)[]): SQLiteDeleteWithout<this, TDynamic, 'orderBy'>;
orderBy(
...columns:
| [(deleteTable: TTable) => ValueOrArray<SQLiteColumn | SQL | SQL.Aliased>]
| (SQLiteColumn | SQL | SQL.Aliased)[]
): SQLiteDeleteWithout<this, TDynamic, 'orderBy'> {
if (typeof columns[0] === 'function') {
const orderBy = columns[0](
new Proxy(
this.config.table[Table.Symbol.Columns],
new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }),
) as any,
);

const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy];
this.config.orderBy = orderByArray;
} else {
const orderByArray = columns as (SQLiteColumn | SQL | SQL.Aliased)[];
this.config.orderBy = orderByArray;
}
return this as any;
}

limit(limit: number | Placeholder): SQLiteDeleteWithout<this, TDynamic, 'limit'> {
this.config.limit = limit;
return this as any;
}

/**
* Adds a `returning` clause to the query.
*
Expand Down
Loading