Skip to content

Commit 26ec506

Browse files
authored
Merge pull request #383 from drizzle-team/bugfixes
- 🐛 Fixed referencing the selected aliased field in the same query - 🐛 Fixed decimal column data type in MySQL - 🐛 Fixed mode autocompletion for integer column in SQLite - 🐛 Fixed extra parentheses in the generated SQL for the `IN` operator (#382) - 🐛 Fixed regression in `pgEnum.enumValues` type (#358) - 🎉 Allowed readonly arrays to be passed to `pgEnum`
2 parents e6573f7 + 5600800 commit 26ec506

File tree

24 files changed

+252
-32
lines changed

24 files changed

+252
-32
lines changed

changelogs/drizzle-orm/0.23.6.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- 🐛 Fixed referencing the selected aliased field in the same query
2+
- 🐛 Fixed decimal column data type in MySQL
3+
- 🐛 Fixed mode autocompletion for integer column in SQLite
4+
- 🐛 Fixed extra parentheses in the generated SQL for the `IN` operator (#382)
5+
- 🐛 Fixed regression in `pgEnum.enumValues` type (#358)
6+
- 🎉 Allowed readonly arrays to be passed to `pgEnum`

drizzle-orm/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "drizzle-orm",
3-
"version": "0.23.5",
3+
"version": "0.23.6",
44
"description": "Drizzle ORM package for SQL databases",
55
"scripts": {
66
"build": "tsc && resolve-tspaths && cp ../README.md package.json dist/",
@@ -126,6 +126,7 @@
126126
"sql.js": "^1.8.0",
127127
"sqlite3": "^5.1.2",
128128
"vite-tsconfig-paths": "^4.0.7",
129-
"vitest": "^0.29.8"
129+
"vitest": "^0.29.8",
130+
"zod": "^3.20.2"
130131
}
131132
}

drizzle-orm/src/mysql-core/columns/decimal.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ export interface MySqlDecimalHKT extends ColumnHKTBase {
1515

1616
export type MySqlDecimalBuilderInitial<TName extends string> = MySqlDecimalBuilder<{
1717
name: TName;
18-
data: number;
19-
driverParam: number | string;
18+
data: string;
19+
driverParam: string;
2020
notNull: false;
2121
hasDefault: false;
2222
}>;

drizzle-orm/src/mysql-core/columns/enum.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ export class MySqlEnumColumnBuilder<T extends ColumnBuilderBaseConfig>
4242
}
4343

4444
export class MySqlEnumColumn<T extends ColumnBaseConfig>
45-
extends MySqlColumn<MySqlEnumColumnHKT, T, { values: string[] }>
45+
extends MySqlColumn<MySqlEnumColumnHKT, T, { values: readonly string[] }>
4646
{
47-
readonly values: string[] = this.config.values;
47+
readonly values: readonly string[] = this.config.values;
4848

4949
getSQLType(): string {
5050
return `enum(${this.values.map((value) => `'${value}'`).join(',')})`;
@@ -53,8 +53,9 @@ export class MySqlEnumColumn<T extends ColumnBaseConfig>
5353

5454
export function mysqlEnum<TName extends string, U extends string, T extends Readonly<[U, ...U[]]>>(
5555
name: TName,
56-
values: Writable<T>,
57-
): MySqlEnumColumnBuilderInitial<TName, Writable<T>> {
56+
values: T | Writable<T>,
57+
): MySqlEnumColumnBuilderInitial<TName, Writable<T>>;
58+
export function mysqlEnum(name: string, values: string[]) {
5859
if (values.length === 0) throw Error(`You have an empty array for "${name}" enum values`);
5960

6061
return new MySqlEnumColumnBuilder(name, values);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class MySqlDatabase<TQueryResult extends QueryResultHKT, TSession extends
3535

3636
return new Proxy(
3737
new WithSubquery(qb.getSQL(), qb.getSelectedFields() as SelectedFields, alias, true),
38-
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'subquery_selection', sqlBehavior: 'error' }),
38+
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }),
3939
) as WithSubqueryWithSelection<TSelection, TAlias>;
4040
},
4141
};

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export abstract class MySqlSelectQueryBuilder<
182182
on = on(
183183
new Proxy(
184184
this.config.fields,
185-
new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }),
185+
new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }),
186186
) as TSelection,
187187
);
188188
}
@@ -322,7 +322,7 @@ export abstract class MySqlSelectQueryBuilder<
322322
): SubqueryWithSelection<BuildSubquerySelection<TSelection, TNullabilityMap>, TAlias> {
323323
return new Proxy(
324324
new Subquery(this.getSQL(), this.config.fields, alias),
325-
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'subquery_selection', sqlBehavior: 'error' }),
325+
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }),
326326
) as SubqueryWithSelection<BuildSubquerySelection<TSelection, TNullabilityMap>, TAlias>;
327327
}
328328
}

drizzle-orm/src/pg-core/columns/enum.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface PgEnum<TValues extends [string, ...string[]]> {
2727
<TName extends string>(name: TName): PgEnumColumnBuilderInitial<TName, TValues>;
2828

2929
readonly enumName: string;
30-
readonly enumValues: string[];
30+
readonly enumValues: TValues;
3131
/** @internal */
3232
[isPgEnumSym]: true;
3333
}
@@ -71,7 +71,7 @@ export class PgEnumColumn<T extends ColumnBaseConfig> extends PgColumn<PgEnumCol
7171
// Gratitude to zod for the enum function types
7272
export function pgEnum<U extends string, T extends Readonly<[U, ...U[]]>>(
7373
enumName: string,
74-
values: Writable<T>,
74+
values: T | Writable<T>,
7575
): PgEnum<Writable<T>> {
7676
const enumInstance = Object.assign(
7777
<TName extends string>(name: TName): PgEnumColumnBuilderInitial<TName, Writable<T>> =>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class PgDatabase<TQueryResult extends QueryResultHKT, TSession extends Pg
3030

3131
return new Proxy(
3232
new WithSubquery(qb.getSQL(), qb.getSelectedFields() as SelectedFields, alias, true),
33-
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'subquery_selection', sqlBehavior: 'error' }),
33+
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }),
3434
) as WithSubqueryWithSelection<TSelection, TAlias>;
3535
},
3636
};

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export abstract class PgSelectQueryBuilder<
175175
on = on(
176176
new Proxy(
177177
this.config.fields,
178-
new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }),
178+
new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }),
179179
) as TSelection,
180180
);
181181
}
@@ -315,7 +315,7 @@ export abstract class PgSelectQueryBuilder<
315315
): SubqueryWithSelection<BuildSubquerySelection<TSelection, TNullabilityMap>, TAlias> {
316316
return new Proxy(
317317
new Subquery(this.getSQL(), this.config.fields, alias),
318-
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'subquery_selection', sqlBehavior: 'error' }),
318+
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }),
319319
) as SubqueryWithSelection<BuildSubquerySelection<TSelection, TNullabilityMap>, TAlias>;
320320
}
321321
}

drizzle-orm/src/sql/expressions/conditions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export function inArray(
172172
if (values.length === 0) {
173173
throw new Error('inArray requires at least one value');
174174
}
175-
return sql`${column} in (${values.map((v) => bindIfParam(v, column))})`;
175+
return sql`${column} in ${values.map((v) => bindIfParam(v, column))}`;
176176
}
177177

178178
return sql`${column} in ${bindIfParam(values, column)}`;

drizzle-orm/src/sqlite-core/columns/integer.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
} from '~/column-builder';
99
import { sql } from '~/sql';
1010
import type { OnConflict } from '~/sqlite-core/utils';
11-
import type { Assume } from '~/utils';
11+
import type { Assume, Equal, Or } from '~/utils';
1212
import type { AnySQLiteTable } from '../table';
1313
import { SQLiteColumn, SQLiteColumnBuilder } from './common';
1414

@@ -155,10 +155,11 @@ export interface IntegerConfig<
155155
mode: TMode;
156156
}
157157

158-
export function integer<TName extends string, TMode extends IntegerConfig['mode'] = 'number'>(
158+
export function integer<TName extends string, TMode extends IntegerConfig['mode']>(
159159
name: TName,
160160
config?: IntegerConfig<TMode>,
161-
): TMode extends 'number' ? SQLiteIntegerBuilderInitial<TName> : SQLiteTimestampBuilderInitial<TName>;
161+
): Or<Equal<TMode, 'timestamp'>, Equal<TMode, 'timestamp_ms'>> extends true ? SQLiteTimestampBuilderInitial<TName>
162+
: SQLiteIntegerBuilderInitial<TName>;
162163
export function integer(name: string, config?: IntegerConfig) {
163164
if (config?.mode === 'timestamp' || config?.mode === 'timestamp_ms') {
164165
return new SQLiteTimestampBuilder(name, config.mode);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class BaseSQLiteDatabase<TResultType extends 'sync' | 'async', TRunResult
3535

3636
return new Proxy(
3737
new WithSubquery(qb.getSQL(), qb.getSelectedFields() as SelectedFields, alias, true),
38-
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'subquery_selection', sqlBehavior: 'error' }),
38+
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }),
3939
) as WithSubqueryWithSelection<TSelection, TAlias>;
4040
},
4141
};

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ export abstract class SQLiteSelectQueryBuilder<
197197
on = on(
198198
new Proxy(
199199
this.config.fields,
200-
new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }),
200+
new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }),
201201
) as TSelection,
202202
);
203203
}
@@ -332,7 +332,7 @@ export abstract class SQLiteSelectQueryBuilder<
332332
): SubqueryWithSelection<BuildSubquerySelection<TSelection, TNullabilityMap>, TAlias> {
333333
return new Proxy(
334334
new Subquery(this.getSQL(), this.config.fields, alias),
335-
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'subquery_selection', sqlBehavior: 'error' }),
335+
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }),
336336
) as SubqueryWithSelection<BuildSubquerySelection<TSelection, TNullabilityMap>, TAlias>;
337337
}
338338
}

drizzle-orm/src/subquery.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,25 @@ export class SelectionProxyHandler<T extends Subquery | SelectedFields<AnyColumn
3939
implements ProxyHandler<Subquery | SelectedFields<AnyColumn, Table> | View>
4040
{
4141
private config: {
42+
/**
43+
* Table alias for the columns
44+
*/
4245
alias?: string;
43-
sqlAliasedBehavior: 'sql' | 'alias' | 'subquery_selection';
46+
/**
47+
* What to do when a field is an instance of `SQL.Aliased` and it's not a selection field (from a subquery)
48+
*
49+
* `sql` - return the underlying SQL expression
50+
*
51+
* `alias` - return the field alias
52+
*/
53+
sqlAliasedBehavior: 'sql' | 'alias';
54+
/**
55+
* What to do when a field is an instance of `SQL` and it doesn't have an alias declared
56+
*
57+
* `sql` - return the underlying SQL expression
58+
*
59+
* `error` - return a DrizzleTypeError on type level and throw an error on runtime
60+
*/
4461
sqlBehavior: 'sql' | 'error';
4562
};
4663

@@ -61,10 +78,8 @@ export class SelectionProxyHandler<T extends Subquery | SelectedFields<AnyColumn
6178
const value: unknown = columns[prop as keyof typeof columns];
6279

6380
if (value instanceof SQL.Aliased) {
64-
if (
65-
(this.config.sqlAliasedBehavior !== 'subquery_selection' && !value.isSelectionField)
66-
|| this.config.sqlAliasedBehavior === 'sql'
67-
) {
81+
// Never return the underlying SQL expression for a field previously selected in a subquery
82+
if (this.config.sqlAliasedBehavior === 'sql' && !value.isSelectionField) {
6883
return value.sql;
6984
}
7085

@@ -79,7 +94,7 @@ export class SelectionProxyHandler<T extends Subquery | SelectedFields<AnyColumn
7994
}
8095

8196
throw new Error(
82-
`You tried to reference "${prop}" field from a subquery, which is a raw SQL field, but it doesn't have an alias. Please add an alias to the field using ".as('alias')" method.`,
97+
`You tried to reference "${prop}" field from a subquery, which is a raw SQL field, but it doesn't have an alias declared. Please add an alias to the field using ".as('alias')" method.`,
8398
);
8499
}
85100

drizzle-orm/type-tests/mysql/tables.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
customType,
77
date,
88
datetime,
9+
decimal,
910
foreignKey,
1011
index,
1112
int,
@@ -358,3 +359,15 @@ Expect<
358359
timestamp4: timestamp('timestamp4', { mode: undefined }).default(new Date()),
359360
});
360361
}
362+
363+
{
364+
const test = mysqlTable('test', {
365+
col1: decimal('col1').default('1'),
366+
});
367+
}
368+
369+
{
370+
const a = ['a', 'b', 'c'] as const;
371+
const test1 = mysqlEnum('test', a);
372+
const test2 = mysqlEnum('test', ['a', 'b', 'c']);
373+
}

drizzle-orm/type-tests/pg/tables.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import type { Equal } from 'type-tests/utils';
22
import { Expect } from 'type-tests/utils';
3+
import { z } from 'zod';
34
import { eq, gt } from '~/expressions';
45
import {
56
bigint,
67
bigserial,
78
customType,
9+
decimal,
810
type PgInteger,
911
type PgSerial,
1012
type PgTableWithColumns,
@@ -741,3 +743,21 @@ await db.refreshMaterializedView(newYorkers2).withNoData().concurrently();
741743
timestamp4: timestamp('timestamp4', { mode: 'string' }).default('2020-01-01'),
742744
});
743745
}
746+
747+
{
748+
const test = pgTable('test', {
749+
col1: decimal('col1', { precision: 10, scale: 2 }).notNull().default('10.2'),
750+
});
751+
Expect<Equal<{ col1: string }, InferModel<typeof test>>>;
752+
}
753+
754+
{
755+
const a = ['a', 'b', 'c'] as const;
756+
const b = pgEnum('test', a);
757+
const c = z.enum(b.enumValues);
758+
}
759+
760+
{
761+
const b = pgEnum('test', ['a', 'b', 'c']);
762+
const c = z.enum(b.enumValues);
763+
}

drizzle-orm/type-tests/sqlite/tables.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,14 @@ Expect<
232232
>
233233
>;
234234
}
235+
236+
{
237+
const test = sqliteTable('test', {
238+
col1: integer('col1').default(1),
239+
col2: integer('col2', { mode: 'number' }).default(1),
240+
col3: integer('col3', { mode: 'timestamp' }).default(new Date()),
241+
col4: integer('col4', { mode: 'timestamp_ms' }).default(new Date()),
242+
// @ts-expect-error
243+
col5: integer('col4', { mode: undefined }).default(new Date()),
244+
});
245+
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,3 +1222,13 @@ test.serial('prefixed table', (t) => {
12221222

12231223
db.run(sql`drop table ${users}`);
12241224
});
1225+
1226+
test.serial('orderBy with aliased column', (t) => {
1227+
const { db } = t.context;
1228+
1229+
const query = db.select({
1230+
test: sql`something`.as('test'),
1231+
}).from(users2Table).orderBy((fields) => fields.test).toSQL();
1232+
1233+
t.deepEqual(query.sql, 'select something as "test" from "users2" order by "test"');
1234+
});

integration-tests/tests/libsql.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,3 +1177,13 @@ test.serial('prefixed table', async (t) => {
11771177

11781178
await db.run(sql`drop table ${users}`);
11791179
});
1180+
1181+
test.serial('orderBy with aliased column', (t) => {
1182+
const { db } = t.context;
1183+
1184+
const query = db.select({
1185+
test: sql`something`.as('test'),
1186+
}).from(users2Table).orderBy((fields) => fields.test).toSQL();
1187+
1188+
t.deepEqual(query.sql, 'select something as "test" from "users2" order by "test"');
1189+
});

integration-tests/tests/mysql.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,3 +1263,13 @@ test.serial('prefixed table', async (t) => {
12631263

12641264
await db.execute(sql`drop table ${users}`);
12651265
});
1266+
1267+
test.serial('orderBy with aliased column', (t) => {
1268+
const { db } = t.context;
1269+
1270+
const query = db.select({
1271+
test: sql`something`.as('test'),
1272+
}).from(users2Table).orderBy((fields) => fields.test).toSQL();
1273+
1274+
t.deepEqual(query.sql, 'select something as `test` from `users2` order by `test`');
1275+
});

0 commit comments

Comments
 (0)