Skip to content

Commit 442aac3

Browse files
authored
Merge pull request #487 from drizzle-team/fix-select-from-alias
2 parents 1d2454e + 5aa1e6f commit 442aac3

26 files changed

+494
-122
lines changed

changelogs/drizzle-orm/0.24.3.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- 🐛 Fixed query generation when selecting from alias

drizzle-orm/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "drizzle-orm",
3-
"version": "0.24.2",
3+
"version": "0.24.3",
44
"description": "Drizzle ORM package for SQL databases",
55
"scripts": {
66
"build": "tsc && resolve-tspaths && cp ../README.md package.json dist/",

drizzle-orm/src/alias.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export class TableAliasProxyHandler<T extends Table | View> implements ProxyHand
1919
constructor(private alias: string, private replaceOriginalName: boolean) {}
2020

2121
get(tableObj: T, prop: string | symbol): any {
22+
if (prop === Table.Symbol.IsAlias) {
23+
return true;
24+
}
25+
2226
if (prop === Table.Symbol.Name) {
2327
return this.alias;
2428
}
@@ -31,6 +35,7 @@ export class TableAliasProxyHandler<T extends Table | View> implements ProxyHand
3135
return {
3236
...tableObj[ViewBaseConfig as keyof typeof tableObj],
3337
name: this.alias,
38+
isAlias: true,
3439
};
3540
}
3641

drizzle-orm/src/mysql-core/dialect.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,14 @@ export class MySqlDialect {
219219

220220
const selection = this.buildSelection(fieldsList, { isSingleTable });
221221

222+
const tableSql = (() => {
223+
if (table instanceof Table && table[Table.Symbol.OriginalName] !== table[Table.Symbol.Name]) {
224+
return sql`${name(table[Table.Symbol.OriginalName])} ${name(table[Table.Symbol.Name])}`;
225+
}
226+
227+
return table;
228+
})();
229+
222230
const joinsArray: SQL[] = [];
223231

224232
for (const [index, joinMeta] of joins.entries()) {
@@ -300,7 +308,7 @@ export class MySqlDialect {
300308
}
301309
}
302310

303-
return sql`${withSql}select ${selection} from ${table}${joinsSql}${whereSql}${groupBySql}${havingSql}${orderBySql}${limitSql}${offsetSql}${lockingClausesSql}`;
311+
return sql`${withSql}select ${selection} from ${tableSql}${joinsSql}${whereSql}${groupBySql}${havingSql}${orderBySql}${limitSql}${offsetSql}${lockingClausesSql}`;
304312
}
305313

306314
buildInsertQuery({ table, values, ignore, onConflict }: MySqlInsertConfig): SQL {

drizzle-orm/src/mysql-core/query-builders/select.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { QueryPromise } from '~/query-promise';
1818
import { type Query, SQL } from '~/sql';
1919
import { SelectionProxyHandler, Subquery, SubqueryConfig } from '~/subquery';
2020
import { Table } from '~/table';
21-
import { applyMixins, getTableColumns, type Simplify, type ValueOrArray } from '~/utils';
21+
import { applyMixins, getTableColumns, getTableLikeName, type Simplify, type ValueOrArray } from '~/utils';
2222
import { orderSelectedFields } from '~/utils';
2323
import { type ColumnsSelection, View, ViewBaseConfig } from '~/view';
2424
import type {
@@ -136,13 +136,7 @@ export abstract class MySqlSelectQueryBuilder<
136136
this._ = {
137137
selectedFields: fields as BuildSubquerySelection<TSelection, TNullabilityMap>,
138138
} as this['_'];
139-
this.tableName = table instanceof Subquery
140-
? table[SubqueryConfig].alias
141-
: table instanceof MySqlViewBase
142-
? table[ViewBaseConfig].name
143-
: table instanceof SQL
144-
? undefined
145-
: table[Table.Symbol.BaseName];
139+
this.tableName = getTableLikeName(table);
146140
this.joinsNotNullableMap = typeof this.tableName === 'string' ? { [this.tableName]: true } : {};
147141
}
148142

@@ -154,13 +148,7 @@ export abstract class MySqlSelectQueryBuilder<
154148
on: ((aliases: TSelection) => SQL | undefined) | SQL | undefined,
155149
) => {
156150
const baseTableName = this.tableName;
157-
const tableName = table instanceof Subquery
158-
? table[SubqueryConfig].alias
159-
: table instanceof View
160-
? table[ViewBaseConfig].name
161-
: table instanceof SQL
162-
? undefined
163-
: table[Table.Symbol.Name];
151+
const tableName = getTableLikeName(table);
164152

165153
if (typeof tableName === 'string' && this.config.joins.some((join) => join.alias === tableName)) {
166154
throw new Error(`Alias "${tableName}" is already used in this query`);

drizzle-orm/src/pg-core/dialect.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,14 @@ export class PgDialect {
205205

206206
const selection = this.buildSelection(fieldsList, { isSingleTable });
207207

208+
const tableSql = (() => {
209+
if (table instanceof Table && table[Table.Symbol.OriginalName] !== table[Table.Symbol.Name]) {
210+
return sql`${name(table[Table.Symbol.OriginalName])} ${name(table[Table.Symbol.Name])}`;
211+
}
212+
213+
return table;
214+
})();
215+
208216
const joinsArray: SQL[] = [];
209217

210218
for (const [index, joinMeta] of joins.entries()) {
@@ -279,7 +287,7 @@ export class PgDialect {
279287
lockingClausesSql.append(clauseSql);
280288
}
281289

282-
return sql`${withSql}select ${selection} from ${table}${joinsSql}${whereSql}${groupBySql}${havingSql}${orderBySql}${limitSql}${offsetSql}${lockingClausesSql}`;
290+
return sql`${withSql}select ${selection} from ${tableSql}${joinsSql}${whereSql}${groupBySql}${havingSql}${orderBySql}${limitSql}${offsetSql}${lockingClausesSql}`;
283291
}
284292

285293
buildInsertQuery({ table, values, onConflict, returning }: PgInsertConfig): SQL {

drizzle-orm/src/pg-core/query-builders/select.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { QueryPromise } from '~/query-promise';
1818
import { type Query, SQL } from '~/sql';
1919
import { SelectionProxyHandler, Subquery, SubqueryConfig } from '~/subquery';
2020
import { Table } from '~/table';
21-
import { applyMixins, getTableColumns, type Simplify, type ValueOrArray } from '~/utils';
21+
import { applyMixins, getTableColumns, getTableLikeName, type Simplify, type ValueOrArray } from '~/utils';
2222
import { orderSelectedFields } from '~/utils';
2323
import { type ColumnsSelection, View, ViewBaseConfig } from '~/view';
2424
import type {
@@ -126,13 +126,7 @@ export abstract class PgSelectQueryBuilder<
126126
this._ = {
127127
selectedFields: fields as BuildSubquerySelection<TSelection, TNullabilityMap>,
128128
} as this['_'];
129-
this.tableName = table instanceof Subquery
130-
? table[SubqueryConfig].alias
131-
: table instanceof PgViewBase
132-
? table[ViewBaseConfig].name
133-
: table instanceof SQL
134-
? undefined
135-
: table[Table.Symbol.BaseName];
129+
this.tableName = getTableLikeName(table);
136130
this.joinsNotNullableMap = typeof this.tableName === 'string' ? { [this.tableName]: true } : {};
137131
}
138132

@@ -144,13 +138,7 @@ export abstract class PgSelectQueryBuilder<
144138
on: ((aliases: TSelection) => SQL | undefined) | SQL | undefined,
145139
) => {
146140
const baseTableName = this.tableName;
147-
const tableName = table instanceof Subquery
148-
? table[SubqueryConfig].alias
149-
: table instanceof View
150-
? table[ViewBaseConfig].name
151-
: table instanceof SQL
152-
? undefined
153-
: table[Table.Symbol.Name];
141+
const tableName = getTableLikeName(table);
154142

155143
if (typeof tableName === 'string' && this.config.joins.some((join) => join.alias === tableName)) {
156144
throw new Error(`Alias "${tableName}" is already used in this query`);

drizzle-orm/src/sqlite-core/dialect.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,14 @@ export abstract class SQLiteDialect {
173173

174174
const selection = this.buildSelection(fieldsList, { isSingleTable });
175175

176+
const tableSql = (() => {
177+
if (table instanceof Table && table[Table.Symbol.OriginalName] !== table[Table.Symbol.Name]) {
178+
return sql`${name(table[Table.Symbol.OriginalName])} ${name(table[Table.Symbol.Name])}`;
179+
}
180+
181+
return table;
182+
})();
183+
176184
const joinsArray: SQL[] = [];
177185

178186
for (const [index, joinMeta] of joins.entries()) {
@@ -233,7 +241,7 @@ export abstract class SQLiteDialect {
233241

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

236-
return sql`${withSql}select ${selection} from ${table}${joinsSql}${whereSql}${groupBySql}${havingSql}${orderBySql}${limitSql}${offsetSql}`;
244+
return sql`${withSql}select ${selection} from ${tableSql}${joinsSql}${whereSql}${groupBySql}${havingSql}${orderBySql}${limitSql}${offsetSql}`;
237245
}
238246

239247
buildInsertQuery({ table, values, onConflict, returning }: SQLiteInsertConfig): SQL {

drizzle-orm/src/sqlite-core/query-builders/select.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type {
1818
} from '~/query-builders/select.types';
1919
import type { SubqueryWithSelection } from '~/sqlite-core/subquery';
2020
import { SelectionProxyHandler, Subquery, SubqueryConfig } from '~/subquery';
21-
import { getTableColumns, orderSelectedFields, type Simplify, type ValueOrArray } from '~/utils';
21+
import { getTableColumns, getTableLikeName, orderSelectedFields, type Simplify, type ValueOrArray } from '~/utils';
2222
import { type ColumnsSelection, View, ViewBaseConfig } from '~/view';
2323
import { SQLiteViewBase } from '../view';
2424
import type {
@@ -138,13 +138,7 @@ export abstract class SQLiteSelectQueryBuilder<
138138
this._ = {
139139
selectedFields: fields as BuildSubquerySelection<TSelection, TNullabilityMap>,
140140
} as this['_'];
141-
this.tableName = table instanceof Subquery
142-
? table[SubqueryConfig].alias
143-
: table instanceof SQLiteViewBase
144-
? table[ViewBaseConfig].name
145-
: table instanceof SQL
146-
? undefined
147-
: table[Table.Symbol.BaseName];
141+
this.tableName = getTableLikeName(table);
148142
this.joinsNotNullableMap = typeof this.tableName === 'string' ? { [this.tableName]: true } : {};
149143
}
150144

@@ -156,13 +150,7 @@ export abstract class SQLiteSelectQueryBuilder<
156150
on: ((aliases: TSelection) => SQL | undefined) | SQL | undefined,
157151
) => {
158152
const baseTableName = this.tableName;
159-
const tableName = table instanceof Subquery
160-
? table[SubqueryConfig].alias
161-
: table instanceof View
162-
? table[ViewBaseConfig].name
163-
: table instanceof SQL
164-
? undefined
165-
: table[Table.Symbol.Name];
153+
const tableName = getTableLikeName(table);
166154

167155
if (typeof tableName === 'string' && this.config.joins.some((join) => join.alias === tableName)) {
168156
throw new Error(`Alias "${tableName}" is already used in this query`);

drizzle-orm/src/table.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ export const OriginalName = Symbol('OriginalName');
2727
/** @internal */
2828
export const BaseName = Symbol('BaseName');
2929

30+
/** @internal */
31+
export const IsAlias = Symbol('IsAlias');
32+
3033
export class Table<T extends TableConfig = TableConfig> {
3134
declare readonly _: {
3235
readonly brand: 'Table';
@@ -47,6 +50,7 @@ export class Table<T extends TableConfig = TableConfig> {
4750
OriginalName: OriginalName as typeof OriginalName,
4851
Columns: Columns as typeof Columns,
4952
BaseName: BaseName as typeof BaseName,
53+
IsAlias: IsAlias as typeof IsAlias,
5054
};
5155

5256
/**
@@ -73,6 +77,9 @@ export class Table<T extends TableConfig = TableConfig> {
7377
*/
7478
[BaseName]: string;
7579

80+
/** @internal */
81+
[IsAlias] = false;
82+
7683
constructor(name: string, schema: string | undefined, baseName: string) {
7784
this[TableName] = this[OriginalName] = name;
7885
this[Schema] = schema;

drizzle-orm/src/utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { Column } from './column';
33
import type { SelectedFieldsOrdered } from './operations';
44
import type { DriverValueDecoder } from './sql';
55
import { Param, SQL } from './sql';
6+
import { Subquery, SubqueryConfig } from './subquery';
67
import { type AnyTable, getTableName, Table } from './table';
8+
import { View, ViewBaseConfig } from './view';
79

810
export function mapResultRow<TResult>(
911
columns: SelectedFieldsOrdered<AnyColumn>,
@@ -231,3 +233,16 @@ export type Writable<T> = {
231233
export function getTableColumns<T extends AnyTable>(table: T): T['_']['columns'] {
232234
return table[Table.Symbol.Columns];
233235
}
236+
237+
/** @internal */
238+
export function getTableLikeName(table: AnyTable | Subquery | View | SQL): string | undefined {
239+
return table instanceof Subquery
240+
? table[SubqueryConfig].alias
241+
: table instanceof View
242+
? table[ViewBaseConfig].name
243+
: table instanceof SQL
244+
? undefined
245+
: table[Table.Symbol.IsAlias]
246+
? table[Table.Symbol.Name]
247+
: table[Table.Symbol.BaseName];
248+
}

drizzle-orm/src/view.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export abstract class View<
2828
selectedFields: SelectedFields<AnyColumn, Table>;
2929
isExisting: TExisting;
3030
query: TExisting extends true ? undefined : SQL;
31+
isAlias: boolean;
3132
};
3233

3334
constructor(
@@ -45,6 +46,7 @@ export abstract class View<
4546
selectedFields,
4647
query: query as (TExisting extends true ? undefined : SQL),
4748
isExisting: !query as TExisting,
49+
isAlias: false,
4850
};
4951
}
5052
}

integration-tests/tests/awsdatapi.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { AwsDataApiPgDatabase } from 'drizzle-orm/aws-data-api/pg';
1010
import { drizzle } from 'drizzle-orm/aws-data-api/pg';
1111
import { migrate } from 'drizzle-orm/aws-data-api/pg/migrator';
1212
import { asc, eq } from 'drizzle-orm/expressions';
13-
import { alias, boolean, integer, jsonb, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
13+
import { alias, boolean, integer, jsonb, pgTable, pgTableCreator, serial, text, timestamp } from 'drizzle-orm/pg-core';
1414
import { name, placeholder } from 'drizzle-orm/sql';
1515

1616
dotenv.config();
@@ -402,6 +402,43 @@ test.serial('full join with alias', async (t) => {
402402
}]);
403403
});
404404

405+
test.serial('select from alias', async (t) => {
406+
const { db } = t.context;
407+
408+
const pgTable = pgTableCreator((name) => `prefixed_${name}`);
409+
410+
const users = pgTable('users', {
411+
id: serial('id').primaryKey(),
412+
name: text('name').notNull(),
413+
});
414+
415+
await db.execute(sql`drop table if exists ${users}`);
416+
await db.execute(sql`create table ${users} (id serial primary key, name text not null)`);
417+
418+
const user = alias(users, 'user');
419+
const customers = alias(users, 'customer');
420+
421+
await db.insert(users).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]);
422+
const result = await db
423+
.select()
424+
.from(user)
425+
.leftJoin(customers, eq(customers.id, 11))
426+
.where(eq(user.id, 10));
427+
428+
t.deepEqual(result, [{
429+
user: {
430+
id: 10,
431+
name: 'Ivan',
432+
},
433+
customer: {
434+
id: 11,
435+
name: 'Hans',
436+
},
437+
}]);
438+
439+
await db.execute(sql`drop table ${users}`);
440+
});
441+
405442
test.serial('insert with spaces', async (t) => {
406443
const { db } = t.context;
407444

integration-tests/tests/better-sqlite.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,44 @@ test.serial('full join with alias', (t) => {
521521
db.run(sql`drop table ${users}`);
522522
});
523523

524+
test.serial('select from alias', (t) => {
525+
const { db } = t.context;
526+
527+
const sqliteTable = sqliteTableCreator((name) => `prefixed_${name}`);
528+
529+
const users = sqliteTable('users', {
530+
id: integer('id').primaryKey(),
531+
name: text('name').notNull(),
532+
});
533+
534+
db.run(sql`drop table if exists ${users}`);
535+
db.run(sql`create table ${users} (id integer primary key, name text not null)`);
536+
537+
const user = alias(users, 'user');
538+
const customers = alias(users, 'customer');
539+
540+
db.insert(users).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]).run();
541+
const result = db
542+
.select()
543+
.from(user)
544+
.leftJoin(customers, eq(customers.id, 11))
545+
.where(eq(user.id, 10))
546+
.all();
547+
548+
t.deepEqual(result, [{
549+
user: {
550+
id: 10,
551+
name: 'Ivan',
552+
},
553+
customer: {
554+
id: 11,
555+
name: 'Hans',
556+
},
557+
}]);
558+
559+
db.run(sql`drop table ${users}`);
560+
});
561+
524562
test.serial('insert with spaces', (t) => {
525563
const { db } = t.context;
526564

0 commit comments

Comments
 (0)