Skip to content

Commit ee97199

Browse files
committed
Fix RQB behavior for tables with same names in different schemas
1 parent a78eefe commit ee97199

24 files changed

+12621
-9058
lines changed

changelogs/drizzle-orm/0.30.11.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- 🛠️ Fixed RQB behavior for tables with same names in different schemas

drizzle-orm/package.json

+32-32
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "drizzle-orm",
3-
"version": "0.30.10",
3+
"version": "0.30.11",
44
"description": "Drizzle ORM package for SQL databases",
55
"type": "module",
66
"scripts": {
@@ -147,39 +147,39 @@
147147
}
148148
},
149149
"devDependencies": {
150-
"@aws-sdk/client-rds-data": "^3.549.0",
151-
"@cloudflare/workers-types": "^4.20230904.0",
150+
"@aws-sdk/client-rds-data": "^3.569.0",
151+
"@cloudflare/workers-types": "^4.20240502.0",
152152
"@electric-sql/pglite": "^0.1.1",
153-
"@libsql/client": "^0.5.6",
154-
"@neondatabase/serverless": "^0.9.0",
155-
"@op-engineering/op-sqlite": "^2.0.16",
156-
"@opentelemetry/api": "^1.4.1",
153+
"@libsql/client": "^0.6.0",
154+
"@neondatabase/serverless": "^0.9.1",
155+
"@op-engineering/op-sqlite": "^5.0.6",
156+
"@opentelemetry/api": "^1.8.0",
157157
"@originjs/vite-plugin-commonjs": "^1.0.3",
158-
"@planetscale/database": "^1.16.0",
159-
"@types/better-sqlite3": "^7.6.4",
160-
"@types/node": "^20.2.5",
161-
"@types/pg": "^8.10.1",
162-
"@types/react": "^18.2.45",
163-
"@types/sql.js": "^1.4.4",
158+
"@planetscale/database": "^1.18.0",
159+
"@types/better-sqlite3": "^7.6.10",
160+
"@types/node": "^20.12.10",
161+
"@types/pg": "^8.11.6",
162+
"@types/react": "^18.3.1",
163+
"@types/sql.js": "^1.4.9",
164164
"@vercel/postgres": "^0.8.0",
165-
"@xata.io/client": "^0.29.3",
166-
"better-sqlite3": "^8.4.0",
167-
"bun-types": "^0.6.6",
168-
"cpy": "^10.1.0",
169-
"expo-sqlite": "^13.2.0",
170-
"knex": "^2.4.2",
171-
"kysely": "^0.25.0",
172-
"mysql2": "^3.3.3",
173-
"pg": "^8.11.0",
174-
"postgres": "^3.3.5",
175-
"react": "^18.2.0",
176-
"sql.js": "^1.8.0",
177-
"sqlite3": "^5.1.2",
178-
"tslib": "^2.5.2",
179-
"tsx": "^3.12.7",
180-
"vite-tsconfig-paths": "^4.2.0",
181-
"vitest": "^0.31.4",
182-
"zod": "^3.20.2",
183-
"zx": "^7.2.2"
165+
"@xata.io/client": "^0.29.4",
166+
"better-sqlite3": "^9.6.0",
167+
"bun-types": "^1.1.7",
168+
"cpy": "^11.0.1",
169+
"expo-sqlite": "^14.0.3",
170+
"knex": "^3.1.0",
171+
"kysely": "^0.27.3",
172+
"mysql2": "^3.9.7",
173+
"pg": "^8.11.5",
174+
"postgres": "^3.4.4",
175+
"react": "^18.3.1",
176+
"sql.js": "^1.10.3",
177+
"sqlite3": "^5.1.7",
178+
"tslib": "^2.6.2",
179+
"tsx": "^4.9.3",
180+
"vite-tsconfig-paths": "^4.3.2",
181+
"vitest": "^1.6.0",
182+
"zod": "^3.23.7",
183+
"zx": "^8.0.2"
184184
}
185185
}

drizzle-orm/scripts/build.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ await fs.remove('dist.new');
5050

5151
await Promise.all([
5252
(async () => {
53-
await $`tsup`;
53+
await $`tsup`.stdio('pipe', 'pipe', 'pipe');
5454
})(),
5555
(async () => {
56-
await $`tsc -p tsconfig.dts.json`;
56+
await $`tsc -p tsconfig.dts.json`.stdio('pipe', 'pipe', 'pipe');
5757
await cpy('dist-dts/**/*.d.ts', 'dist.new', {
5858
rename: (basename) => basename.replace(/\.d\.ts$/, '.d.cts'),
5959
});
@@ -64,8 +64,8 @@ await Promise.all([
6464
]);
6565

6666
await Promise.all([
67-
$`tsup src/version.ts --no-config --dts --format esm --outDir dist.new`,
68-
$`tsup src/version.ts --no-config --dts --format cjs --outDir dist.new`,
67+
$`tsup src/version.ts --no-config --dts --format esm --outDir dist.new`.stdio('pipe', 'pipe', 'pipe'),
68+
$`tsup src/version.ts --no-config --dts --format cjs --outDir dist.new`.stdio('pipe', 'pipe', 'pipe'),
6969
]);
7070

7171
await $`scripts/fix-imports.ts`;

drizzle-orm/src/d1/session.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ function d1ToRawMapping(results: any) {
149149
}
150150

151151
export class D1PreparedQuery<T extends PreparedQueryConfig = PreparedQueryConfig> extends SQLitePreparedQuery<
152-
{ type: 'async'; run: D1Result; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] }
152+
{ type: 'async'; run: D1Response; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] }
153153
> {
154154
static readonly [entityKind]: string = 'D1PreparedQuery';
155155

@@ -177,7 +177,7 @@ export class D1PreparedQuery<T extends PreparedQueryConfig = PreparedQueryConfig
177177
this.stmt = stmt;
178178
}
179179

180-
run(placeholderValues?: Record<string, unknown>): Promise<D1Result> {
180+
run(placeholderValues?: Record<string, unknown>): Promise<D1Response> {
181181
const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
182182
this.logger.logQuery(this.query.sql, params);
183183
return this.stmt.bind(...params).run();

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from '~/relations.ts';
1717
import { Param, type QueryWithTypings, SQL, sql, type SQLChunk, View } from '~/sql/sql.ts';
1818
import { Subquery } from '~/subquery.ts';
19-
import { getTableName, Table } from '~/table.ts';
19+
import { getTableName, getTableUniqueName, Table } from '~/table.ts';
2020
import { orderSelectedFields, type UpdateSet } from '~/utils.ts';
2121
import { and, DrizzleError, eq, type Name, ViewBaseConfig } from '../index.ts';
2222
import { MySqlColumn } from './columns/common.ts';
@@ -612,7 +612,7 @@ export class MySqlDialect {
612612
} of selectedRelations
613613
) {
614614
const normalizedRelation = normalizeRelation(schema, tableNamesMap, relation);
615-
const relationTableName = relation.referencedTable[Table.Symbol.Name];
615+
const relationTableName = getTableUniqueName(relation.referencedTable);
616616
const relationTableTsName = tableNamesMap[relationTableName]!;
617617
const relationTableAlias = `${tableAlias}_${selectedRelationTsKey}`;
618618
const joinOn = and(
@@ -909,7 +909,7 @@ export class MySqlDialect {
909909
} of selectedRelations
910910
) {
911911
const normalizedRelation = normalizeRelation(schema, tableNamesMap, relation);
912-
const relationTableName = relation.referencedTable[Table.Symbol.Name];
912+
const relationTableName = getTableUniqueName(relation.referencedTable);
913913
const relationTableTsName = tableNamesMap[relationTableName]!;
914914
const relationTableAlias = `${tableAlias}_${selectedRelationTsKey}`;
915915
const joinOn = and(

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import {
4747
type SQLChunk,
4848
} from '~/sql/sql.ts';
4949
import { Subquery } from '~/subquery.ts';
50-
import { getTableName, Table } from '~/table.ts';
50+
import { getTableName, getTableUniqueName, Table } from '~/table.ts';
5151
import { orderSelectedFields, type UpdateSet } from '~/utils.ts';
5252
import { ViewBaseConfig } from '~/view-common.ts';
5353
import type { PgSession } from './session.ts';
@@ -1218,7 +1218,7 @@ export class PgDialect {
12181218
} of selectedRelations
12191219
) {
12201220
const normalizedRelation = normalizeRelation(schema, tableNamesMap, relation);
1221-
const relationTableName = relation.referencedTable[Table.Symbol.Name];
1221+
const relationTableName = getTableUniqueName(relation.referencedTable);
12221222
const relationTableTsName = tableNamesMap[relationTableName]!;
12231223
const relationTableAlias = `${tableAlias}_${selectedRelationTsKey}`;
12241224
const joinOn = and(

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

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { entityKind, is } from '~/entity.ts';
2+
import type { SQLWrapper } from '~/index.ts';
3+
import { SQL, sql } from '~/index.ts';
24
import type { pgEnum } from './columns/enum.ts';
35
import { pgEnumWithSchema } from './columns/enum.ts';
46
import { type PgTableFn, pgTableWithSchema } from './table.ts';
57
import { type pgMaterializedView, pgMaterializedViewWithSchema, type pgView, pgViewWithSchema } from './view.ts';
68

7-
export class PgSchema<TName extends string = string> {
9+
export class PgSchema<TName extends string = string> implements SQLWrapper {
810
static readonly [entityKind]: string = 'PgSchema';
911
constructor(
1012
public readonly schemaName: TName,
@@ -25,6 +27,14 @@ export class PgSchema<TName extends string = string> {
2527
enum: typeof pgEnum = ((name, values) => {
2628
return pgEnumWithSchema(name, values, this.schemaName);
2729
});
30+
31+
getSQL(): SQL {
32+
return new SQL([sql.identifier(this.schemaName)]);
33+
}
34+
35+
shouldOmitSQLParens(): boolean {
36+
return true;
37+
}
2838
}
2939

3040
export function isPgSchema(obj: unknown): obj is PgSchema {

drizzle-orm/src/relations.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type AnyTable, type InferModelFromColumns, isTable, Table } from '~/table.ts';
1+
import { type AnyTable, getTableUniqueName, type InferModelFromColumns, isTable, Table } from '~/table.ts';
22
import { type AnyColumn, Column } from './column.ts';
33
import { entityKind, is } from './entity.ts';
44
import { PrimaryKeyBuilder } from './pg-core/primary-keys.ts';
@@ -430,7 +430,7 @@ export function extractTablesRelationalConfig<
430430
const tablesConfig: TablesRelationalConfig = {};
431431
for (const [key, value] of Object.entries(schema)) {
432432
if (isTable(value)) {
433-
const dbName = value[Table.Symbol.Name];
433+
const dbName = getTableUniqueName(value);
434434
const bufferedRelations = relationsBuffer[dbName];
435435
tableNamesMap[dbName] = key;
436436
tablesConfig[key] = {
@@ -462,7 +462,7 @@ export function extractTablesRelationalConfig<
462462
}
463463
}
464464
} else if (is(value, Relations)) {
465-
const dbName: string = value.table[Table.Symbol.Name];
465+
const dbName = getTableUniqueName(value.table);
466466
const tableName = tableNamesMap[dbName];
467467
const relations: Record<string, Relation> = value.config(
468468
configHelpers(value.table),
@@ -561,7 +561,7 @@ export function normalizeRelation(
561561
};
562562
}
563563

564-
const referencedTableTsName = tableNamesMap[relation.referencedTable[Table.Symbol.Name]];
564+
const referencedTableTsName = tableNamesMap[getTableUniqueName(relation.referencedTable)];
565565
if (!referencedTableTsName) {
566566
throw new Error(
567567
`Table "${relation.referencedTable[Table.Symbol.Name]}" not found in schema`,
@@ -574,7 +574,7 @@ export function normalizeRelation(
574574
}
575575

576576
const sourceTable = relation.sourceTable;
577-
const sourceTableTsName = tableNamesMap[sourceTable[Table.Symbol.Name]];
577+
const sourceTableTsName = tableNamesMap[getTableUniqueName(sourceTable)];
578578
if (!sourceTableTsName) {
579579
throw new Error(
580580
`Table "${sourceTable[Table.Symbol.Name]}" not found in schema`,

drizzle-orm/src/sql/sql.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export interface QueryWithTypings extends Query {
6060
*/
6161
export interface SQLWrapper {
6262
getSQL(): SQL;
63+
shouldOmitSQLParens?(): boolean;
6364
}
6465

6566
export function isSQLWrapper(value: unknown): value is SQLWrapper {
@@ -209,15 +210,15 @@ export class SQL<T = unknown> implements SQLWrapper {
209210
}
210211

211212
let typings: QueryTypingsValue[] | undefined;
212-
if (prepareTyping !== undefined) {
213+
if (prepareTyping) {
213214
typings = [prepareTyping(chunk.encoder)];
214215
}
215216

216217
return { sql: escapeParam(paramStartIndex.value++, mappedValue), params: [mappedValue], typings };
217218
}
218219

219220
if (is(chunk, Placeholder)) {
220-
return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk] };
221+
return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk], typings: ['none'] };
221222
}
222223

223224
if (is(chunk, SQL.Aliased) && chunk.fieldAlias !== undefined) {
@@ -244,6 +245,9 @@ export class SQL<T = unknown> implements SQLWrapper {
244245
}
245246

246247
if (isSQLWrapper(chunk)) {
248+
if (chunk.shouldOmitSQLParens?.()) {
249+
return this.buildQueryFromSourceParams([chunk.getSQL()], config);
250+
}
247251
return this.buildQueryFromSourceParams([
248252
new StringChunk('('),
249253
chunk.getSQL(),
@@ -437,11 +441,10 @@ export type SQLChunk =
437441

438442
export function sql<T>(strings: TemplateStringsArray, ...params: any[]): SQL<T>;
439443
/*
440-
The type of `params` is specified as `SQLSourceParam[]`, but that's slightly incorrect -
444+
The type of `params` is specified as `SQLChunk[]`, but that's slightly incorrect -
441445
in runtime, users won't pass `FakePrimitiveParam` instances as `params` - they will pass primitive values
442-
which will be wrapped in `Param` using `buildChunksFromParam(...)`. That's why the overload
443-
specify `params` as `any[]` and not as `SQLSourceParam[]`. This type is used to make our lives easier and
444-
the type checker happy.
446+
which will be wrapped in `Param`. That's why the overload specifies `params` as `any[]` and not as `SQLSourceParam[]`.
447+
This type is used to make our lives easier and the type checker happy.
445448
*/
446449
export function sql(strings: TemplateStringsArray, ...params: SQLChunk[]): SQL {
447450
const queryChunks: SQLChunk[] = [];

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { SQLiteColumn } from '~/sqlite-core/columns/index.ts';
2323
import type { SQLiteDeleteConfig, SQLiteInsertConfig, SQLiteUpdateConfig } from '~/sqlite-core/query-builders/index.ts';
2424
import { SQLiteTable } from '~/sqlite-core/table.ts';
2525
import { Subquery } from '~/subquery.ts';
26-
import { getTableName, Table } from '~/table.ts';
26+
import { getTableName, getTableUniqueName, Table } from '~/table.ts';
2727
import { orderSelectedFields, type UpdateSet } from '~/utils.ts';
2828
import { ViewBaseConfig } from '~/view-common.ts';
2929
import type {
@@ -584,7 +584,7 @@ export abstract class SQLiteDialect {
584584
} of selectedRelations
585585
) {
586586
const normalizedRelation = normalizeRelation(schema, tableNamesMap, relation);
587-
const relationTableName = relation.referencedTable[Table.Symbol.Name];
587+
const relationTableName = getTableUniqueName(relation.referencedTable);
588588
const relationTableTsName = tableNamesMap[relationTableName]!;
589589
const relationTableAlias = `${tableAlias}_${selectedRelationTsKey}`;
590590
// const relationTable = schema[relationTableTsName]!;
@@ -778,7 +778,7 @@ export class SQLiteAsyncDialect extends SQLiteDialect {
778778

779779
async migrate(
780780
migrations: MigrationMeta[],
781-
session: SQLiteSession<'async', unknown, any, TablesRelationalConfig>,
781+
session: SQLiteSession<'async', any, any, any>,
782782
config?: string | MigrationConfig,
783783
): Promise<void> {
784784
const migrationsTable = config === undefined

drizzle-orm/src/table.ts

+4
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ export function getTableName<T extends Table>(table: T): T['_']['name'] {
138138
return table[TableName];
139139
}
140140

141+
export function getTableUniqueName<T extends Table>(table: T): `${T['_']['schema']}.${T['_']['name']}` {
142+
return `${table[Schema] ?? 'public'}.${table[TableName]}`;
143+
}
144+
141145
export type MapColumnName<TName extends string, TColumn extends Column, TDBColumNames extends boolean> =
142146
TDBColumNames extends true ? TColumn['_']['name']
143147
: TName;

drizzle-orm/src/utils.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,11 @@ export type UpdateSet = Record<string, SQL | Param | null | undefined>;
132132

133133
export type OneOrMany<T> = T | T[];
134134

135-
export type Update<T, TUpdate> = Simplify<
135+
export type Update<T, TUpdate> =
136136
& {
137137
[K in Exclude<keyof T, keyof TUpdate>]: T[K];
138138
}
139-
& TUpdate
140-
>;
139+
& TUpdate;
141140

142141
export type Simplify<T> =
143142
& {

drizzle-orm/tests/relation.test.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { expect, test } from 'vitest';
2+
3+
import { pgSchema, pgTable } from '~/pg-core/index.ts';
4+
import { createTableRelationsHelpers, extractTablesRelationalConfig } from '~/relations.ts';
5+
6+
test('tables with same name in different schemas', () => {
7+
const folder = pgSchema('folder');
8+
const schema = {
9+
folder: {
10+
usersInFolder: folder.table('users', {}),
11+
},
12+
public: {
13+
users: pgTable('users', {}),
14+
},
15+
};
16+
17+
const relationalSchema = {
18+
...Object.fromEntries(
19+
Object.entries(schema)
20+
.flatMap(([key, val]) => {
21+
// have unique keys across schemas
22+
23+
const mappedTableEntries = Object.entries(val).map((tableEntry) => {
24+
return [`__${key}__.${tableEntry[0]}`, tableEntry[1]];
25+
});
26+
27+
return mappedTableEntries;
28+
}),
29+
),
30+
};
31+
32+
const relationsConfig = extractTablesRelationalConfig(
33+
relationalSchema,
34+
createTableRelationsHelpers,
35+
);
36+
37+
expect(Object.keys(relationsConfig)).toHaveLength(2);
38+
});

drizzle-orm/type-tests/mysql/set-operators.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ const exceptAll2Test = await exceptAll(
159159
db.select({
160160
userId: newYorkers.userId,
161161
cityId: newYorkers.cityId,
162-
}).from(newYorkers).leftJoin(newYorkers, sql``),
162+
}).from(newYorkers).leftJoin(users, sql``),
163163
);
164164

165165
Expect<Equal<{ userId: number; cityId: number | null }[], typeof exceptAll2Test>>;

drizzle-orm/type-tests/pg/set-operators.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ const exceptAll2Test = await exceptAll(
151151
db.select({
152152
userId: newYorkers.userId,
153153
cityId: newYorkers.cityId,
154-
}).from(newYorkers).leftJoin(newYorkers, sql``),
154+
}).from(newYorkers).leftJoin(users, sql``),
155155
);
156156

157157
Expect<Equal<{ userId: number; cityId: number | null }[], typeof exceptAll2Test>>;

0 commit comments

Comments
 (0)