Skip to content

Commit 798ef83

Browse files
iam-abduligalklebanov
authored andcommitted
implemented setRef #1413
1 parent 1b97a04 commit 798ef83

5 files changed

Lines changed: 140 additions & 49 deletions

File tree

src/parser/reference-parser.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -109,30 +109,6 @@ export type ExtractColumnNameFromOrderedColumnName<C extends string> =
109109
: never
110110
: C
111111

112-
/**
113-
* Helper type that filters columns by matching their UpdateType against TargetType.
114-
*/
115-
type FilterMatchingColumns<
116-
DB,
117-
TB extends keyof DB,
118-
Columns extends string,
119-
TargetType,
120-
> = {
121-
[C in Columns]: UpdateType<
122-
ExtractRawTypeFromReferenceExpression<DB, TB, C>
123-
> extends TargetType
124-
? C
125-
: never
126-
}[Columns]
127-
128-
/**
129-
* Filters reference expressions to only include columns whose type matches the target type.
130-
* This is useful for type-safe column-to-column operations where both columns must have the same type.
131-
*/
132-
export type MatchingReferenceExpression<DB, TB extends keyof DB, TargetType> =
133-
| FilterMatchingColumns<DB, TB, AnyColumn<DB, TB>, TargetType>
134-
| FilterMatchingColumns<DB, TB, AnyColumnWithTable<DB, TB>, TargetType>
135-
136112
export function parseSimpleReferenceExpression(
137113
exp: SimpleReferenceExpression<any, any>,
138114
): SimpleReferenceExpressionNode {

src/parser/update-set-parser.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ export type UpdateObjectExpression<
3838
UT extends keyof DB = TB,
3939
> = UpdateObject<DB, TB, UT> | UpdateObjectFactory<DB, TB, UT>
4040

41+
export type UpdateObjectWithRef<
42+
DB,
43+
TB extends keyof DB,
44+
UT extends keyof DB = TB,
45+
> = DrainOuterGeneric<{
46+
[C in AnyColumn<DB, UT>]?: ReferenceExpression<DB, TB>
47+
}>
48+
4149
export type ExtractUpdateTypeFromReferenceExpression<
4250
DB,
4351
TB extends keyof DB,
@@ -63,15 +71,33 @@ export function parseUpdate(
6371
}
6472

6573
export function parseUpdateWithRef(
66-
key: ReferenceExpression<any, any>,
67-
value: ReferenceExpression<any, any>,
74+
...args:
75+
| [ReferenceExpression<any, any>, ReferenceExpression<any, any>]
76+
| [UpdateObjectWithRef<any, any, any>]
77+
): ReadonlyArray<ColumnUpdateNode> {
78+
if (args.length === 2) {
79+
return [
80+
ColumnUpdateNode.create(
81+
parseReferenceExpression(args[0]),
82+
parseReferenceExpression(args[1]),
83+
),
84+
]
85+
}
86+
87+
return parseUpdateObjectWithRef(args[0])
88+
}
89+
90+
export function parseUpdateObjectWithRef(
91+
update: UpdateObjectWithRef<any, any, any>,
6892
): ReadonlyArray<ColumnUpdateNode> {
69-
return [
70-
ColumnUpdateNode.create(
71-
parseReferenceExpression(key),
72-
parseReferenceExpression(value),
73-
),
74-
]
93+
return Object.entries(update)
94+
.filter(([_, value]) => value !== undefined)
95+
.map(([key, value]) => {
96+
return ColumnUpdateNode.create(
97+
ColumnNode.create(key),
98+
parseReferenceExpression(value!),
99+
)
100+
})
75101
}
76102

77103
export function parseUpdateObjectExpression(

src/query-builder/update-query-builder.ts

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -745,47 +745,76 @@ export class UpdateQueryBuilder<DB, UT extends keyof DB, TB extends keyof DB, O>
745745
}
746746

747747
/**
748-
* Sets a column to the value of another column with type-safe column matching.
748+
* Sets the values to update for an {@link Kysely.updateTable | update} query
749+
* using column references instead of values.
749750
*
750-
* Unlike {@link set}, this method only allows you to reference columns that have
751-
* the same type as the target column, ensuring type safety at compile time.
751+
* This method is similar to {@link set} but allows you to update columns by
752+
* referencing other columns instead of providing literal values. This is useful
753+
* when you want to copy values from one column to another or perform updates
754+
* based on existing column values.
755+
*
756+
* You can provide either two arguments (column name and reference) or a single
757+
* object where keys are column names and values are column references.
752758
*
753759
* ### Examples
754760
*
761+
* Update a column by referencing another column using the two-argument form:
762+
*
755763
* ```ts
756-
* await db.updateTable('person')
757-
* .setRef('first_name', 'last_name')
764+
* const result = await db
765+
* .updateTable('person')
766+
* .setRef('last_name', 'first_name')
758767
* .where('id', '=', 1)
759-
* .execute()
768+
* .executeTakeFirst()
760769
* ```
761770
*
762771
* The generated SQL (PostgreSQL):
763772
*
764773
* ```sql
765-
* update "person" set "first_name" = "last_name" where "id" = $1
774+
* update "person" set "last_name" = "first_name" where "id" = $1
766775
* ```
767776
*
768-
* Type errors for mismatched types:
777+
* You can reference columns from joined tables in a PostgreSQL `from` query:
769778
*
770779
* ```ts
771-
* await db.updateTable('person')
772-
* .setRef('first_name', 'id') // Error: 'id' is number, 'first_name' is string
773-
* .execute()
780+
* const result = await db
781+
* .updateTable('person')
782+
* .from('pet')
783+
* .setRef({
784+
* first_name: 'pet.name',
785+
* })
786+
* .whereRef('pet.owner_id', '=', 'person.id')
787+
* .executeTakeFirst()
788+
* ```
789+
*
790+
* The generated SQL (PostgreSQL):
791+
*
792+
* ```sql
793+
* update "person"
794+
* set "first_name" = "pet"."name"
795+
* from "pet"
796+
* where "pet"."owner_id" = "person"."id"
774797
* ```
775798
*/
776799
setRef<RE extends ReferenceExpression<DB, UT>>(
777800
key: RE,
778-
value: MatchingReferenceExpression<
779-
DB,
780-
UT,
781-
ExtractUpdateTypeFromReferenceExpression<DB, UT, RE>
782-
>,
801+
value: RE,
802+
): UpdateQueryBuilder<DB, UT, TB, O>
803+
804+
setRef(
805+
updates: UpdateObjectWithRef<DB, TB, UT>,
806+
): UpdateQueryBuilder<DB, UT, TB, O>
807+
808+
setRef(
809+
...args:
810+
| [ReferenceExpression<DB, UT>, ReferenceExpression<DB, UT>]
811+
| [UpdateObjectWithRef<DB, TB, UT>]
783812
): UpdateQueryBuilder<DB, UT, TB, O> {
784813
return new UpdateQueryBuilder({
785814
...this.#props,
786815
queryNode: UpdateQueryNode.cloneWithUpdates(
787816
this.#props.queryNode,
788-
parseUpdateWithRef(key, value),
817+
parseUpdateWithRef(...args),
789818
),
790819
})
791820
}

test/node/src/update.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,52 @@ for (const dialect of DIALECTS) {
399399
expect(jennifer.last_name).to.equal('Jennifer')
400400
})
401401

402+
it('should update one row using setRef with object', async () => {
403+
const query = ctx.db
404+
.updateTable('person')
405+
.setRef({
406+
last_name: 'first_name',
407+
})
408+
.where('first_name', '=', 'Jennifer')
409+
410+
testSql(query, dialect, {
411+
postgres: {
412+
sql: 'update "person" set "last_name" = "first_name" where "first_name" = $1',
413+
parameters: ['Jennifer'],
414+
},
415+
mysql: {
416+
sql: 'update `person` set `last_name` = `first_name` where `first_name` = ?',
417+
parameters: ['Jennifer'],
418+
},
419+
mssql: {
420+
sql: 'update "person" set "last_name" = "first_name" where "first_name" = @1',
421+
parameters: ['Jennifer'],
422+
},
423+
sqlite: {
424+
sql: 'update "person" set "last_name" = "first_name" where "first_name" = ?',
425+
parameters: ['Jennifer'],
426+
},
427+
})
428+
429+
const result = await query.executeTakeFirst()
430+
431+
expect(result).to.be.instanceOf(UpdateResult)
432+
expect(result.numUpdatedRows).to.equal(1n)
433+
if (sqlSpec === 'mysql') {
434+
expect(result.numChangedRows).to.equal(1n)
435+
} else {
436+
expect(result.numChangedRows).to.undefined
437+
}
438+
439+
const jennifer = await ctx.db
440+
.selectFrom('person')
441+
.where('first_name', '=', 'Jennifer')
442+
.select('last_name')
443+
.executeTakeFirstOrThrow()
444+
445+
expect(jennifer.last_name).to.equal('Jennifer')
446+
})
447+
402448
it('should update one row while ignoring undefined values', async () => {
403449
const query = ctx.db
404450
.updateTable('person')

test/typings/test-d/update.test-d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ async function testUpdate(db: Kysely<Database>) {
6969
.setRef('name', 'not_a_column'),
7070
)
7171

72+
expectError(
73+
db
74+
.updateTable('pet as p')
75+
.where('p.id', '=', '1')
76+
.setRef({ name: 'not_a_column' }),
77+
)
78+
79+
expectError(
80+
db
81+
.updateTable('pet as p')
82+
.where('p.id', '=', '1')
83+
.setRef({ not_a_column: 'not_a_column' }),
84+
)
85+
7286
// Non-existent column in a callback
7387
expectError(
7488
db

0 commit comments

Comments
 (0)