Skip to content

Commit c7ea923

Browse files
committed
feat: add typings argument to db.$values / db.$withValues
You can provide a Drizzle table definition or a plain object with column names mapped to database type names. These typings are used to cast values in the very first row of the values list, so that ambiguous values don’t cause the query to fail.
1 parent f45dcf0 commit c7ea923

File tree

1 file changed

+53
-12
lines changed

1 file changed

+53
-12
lines changed

src/generated/$values.ts

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,32 @@
22
import {
33
AnyRelations,
44
DrizzleError,
5+
getTableColumns,
6+
is,
57
SQL,
68
SQLChunk,
79
Subquery,
810
TablesRelationalConfig,
911
} from 'drizzle-orm'
1012
import type * as V1 from 'drizzle-orm/_relations'
11-
import { PgDatabase, WithSubqueryWithSelection } from 'drizzle-orm/pg-core'
13+
import {
14+
PgColumn,
15+
PgDatabase,
16+
PgTable,
17+
TableConfig,
18+
WithSubqueryWithSelection,
19+
} from 'drizzle-orm/pg-core'
1220
import { sql, SQLWrapper } from 'drizzle-orm/sql'
21+
import type { SQLType } from 'drizzle-plus/pg'
1322
import { SelectionFromAnyObject } from 'drizzle-plus/types'
1423
import { pushStringChunk } from 'drizzle-plus/utils'
1524
import { createWithSubquery } from './as'
1625
import { setWithSubqueryAddons } from './internal'
1726

27+
type PgTableWithTheseColumns<K extends string> = PgTable<
28+
Omit<TableConfig, 'columns'> & { columns: Record<K, PgColumn> }
29+
>
30+
1831
declare module 'drizzle-orm/pg-core' {
1932
interface PgDatabase<
2033
// sqlite-insert: TResultKind extends 'sync' | 'async',
@@ -39,9 +52,12 @@ declare module 'drizzle-orm/pg-core' {
3952
* db.select().from(myValues.as('my_values'))
4053
* ```
4154
*/
42-
$values<T extends Record<string, unknown>>(
43-
rows: readonly T[]
44-
): ValuesList<T>
55+
$values<TRow extends Record<string, unknown>>(
56+
rows: readonly TRow[],
57+
typings?:
58+
| { [K in keyof TRow]?: SQLType }
59+
| PgTableWithTheseColumns<string & keyof TRow>
60+
): ValuesList<TRow>
4561
/**
4662
* Allows you to declare a values list in a CTE.
4763
*
@@ -55,7 +71,10 @@ declare module 'drizzle-orm/pg-core' {
5571
$withValues: {
5672
<TAlias extends string, TRow extends Record<string, unknown>>(
5773
alias: TAlias,
58-
rows: TRow[]
74+
rows: TRow[],
75+
typings?:
76+
| { [K in keyof TRow]?: SQLType }
77+
| PgTableWithTheseColumns<string & keyof TRow>
5978
): WithSubqueryWithSelection<SelectionFromAnyObject<TRow>, string>
6079
}
6180
}
@@ -74,21 +93,23 @@ declare module 'drizzle-orm/pg-core' {
7493
* ```
7594
*/
7695
PgDatabase.prototype.$values = function (
77-
rows: readonly Record<string, unknown>[]
96+
rows: readonly Record<string, unknown>[],
97+
typings?: Partial<Record<string, SQLType>> | PgTable
7898
): ValuesList<any> {
7999
if (!rows.length) {
80100
throw new DrizzleError({ message: 'No rows provided' })
81101
}
82102
const casing = (this as any).dialect.casing
83-
return new ValuesList(casing, Object.keys(rows[0]), rows)
103+
return new ValuesList(casing, Object.keys(rows[0]), rows, typings)
84104
}
85105

86106
PgDatabase.prototype.$withValues = function (
87107
alias: string,
88-
rows: Record<string, unknown>[]
108+
rows: Record<string, unknown>[],
109+
typings?: Partial<Record<string, SQLType>> | PgTable
89110
): any {
90111
const withSubquery = createWithSubquery(
91-
this.$values(rows).getSQL(),
112+
this.$values(rows, typings).getSQL(),
92113
rows[0],
93114
alias
94115
)
@@ -105,11 +126,16 @@ export class ValuesList<
105126
selectedFields: TSelectedFields
106127
}
107128
private shouldInlineParams = false
129+
private typings?: Partial<Record<string, SQLType | PgColumn>>
108130
constructor(
109131
private casing: { convert: (key: string) => string },
110132
private keys: string[],
111-
private rows: readonly object[]
112-
) {}
133+
private rows: readonly object[],
134+
typings?: Partial<Record<string, SQLType>> | PgTable
135+
) {
136+
this.typings =
137+
typings && (is(typings, PgTable) ? getTableColumns(typings) : typings)
138+
}
113139

114140
as<TAlias extends string>(alias: TAlias): Subquery<TAlias, TSelectedFields> {
115141
const columnList = this.keys.map(key => this.casing.convert(key))
@@ -141,7 +167,22 @@ export class ValuesList<
141167

142168
let row: any = this.rows[rowIndex]
143169
this.keys.forEach((key, keyIndex) => {
144-
chunks.push(row[key] as SQLChunk)
170+
let value = row[key] as SQLChunk
171+
if (value === undefined) {
172+
throw new DrizzleError({
173+
message: 'Undefined values are not allowed in a ValuesList.',
174+
})
175+
}
176+
177+
// The first row is used to infer the type of the values list.
178+
if (rowIndex === 0) {
179+
let type = this.typings?.[key]
180+
if (type) {
181+
value = sql`cast(${value} as ${sql.raw(is(type, PgColumn<any>) ? type.getSQLType() : type)})`
182+
}
183+
}
184+
185+
chunks.push(value)
145186

146187
if (keyIndex < this.keys.length - 1) {
147188
pushStringChunk(chunks, ', ')

0 commit comments

Comments
 (0)