Repository: knex/knex
Pull request #5620 (2023-07-06 09:16:01)

Bump @types/node from 18.16.19 to 20.4.0
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 18.16.19 to 20.4.0.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@types/node&package-manager=npm_and_yarn&previous-version=18.16.19&new-version=20.4.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index f5ff4003..41bcff96 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -28,7 +28,7 @@ jobs:
           fetch-depth: 1
 
       - name: Setup Node ${{ matrix.node-version }}
-        uses: actions/setup-node@v3.6.0
+        uses: actions/setup-node@v3.7.0
         with:
           always-auth: false
           node-version: ${{ matrix.node-version }}
diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml
index 2e451413..31304736 100644
--- a/.github/workflows/linting.yml
+++ b/.github/workflows/linting.yml
@@ -29,7 +29,7 @@ jobs:
           fetch-depth: 1
 
       - name: Setup Node ${{ matrix.node-version }}
-        uses: actions/setup-node@v3.6.0
+        uses: actions/setup-node@v3.7.0
         with:
           always-auth: false
           node-version: ${{ matrix.node-version }}
diff --git a/lib/dialects/postgres/query/pg-querybuilder.js b/lib/dialects/postgres/query/pg-querybuilder.js
index 2d4868dc..b5bb5b75 100644
--- a/lib/dialects/postgres/query/pg-querybuilder.js
+++ b/lib/dialects/postgres/query/pg-querybuilder.js
@@ -1,6 +1,11 @@
 const QueryBuilder = require('../../../query/querybuilder.js');
 
 module.exports = class QueryBuilder_PostgreSQL extends QueryBuilder {
+  updateFrom(name) {
+    this._single.updateFrom = name;
+    return this;
+  }
+
   using(tables) {
     this._single.using = tables;
     return this;
diff --git a/lib/dialects/postgres/query/pg-querycompiler.js b/lib/dialects/postgres/query/pg-querycompiler.js
index e4dcff4e..137486f7 100644
--- a/lib/dialects/postgres/query/pg-querycompiler.js
+++ b/lib/dialects/postgres/query/pg-querycompiler.js
@@ -50,12 +50,13 @@ class QueryCompiler_PG extends QueryCompiler {
     const withSQL = this.with();
     const updateData = this._prepUpdate(this.single.update);
     const wheres = this.where();
-    const { returning } = this.single;
+    const { returning, updateFrom } = this.single;
     return {
       sql:
         withSQL +
         `update ${this.single.only ? 'only ' : ''}${this.tableName} ` +
         `set ${updateData.join(', ')}` +
+        this._updateFrom(updateFrom) +
         (wheres ? ` ${wheres}` : '') +
         this._returning(returning),
       returning,
@@ -141,6 +142,10 @@ class QueryCompiler_PG extends QueryCompiler {
     return value ? ` returning ${this.formatter.columnize(value)}` : '';
   }
 
+  _updateFrom(name) {
+    return name ? ` from ${this.formatter.wrap(name)}` : '';
+  }
+
   _ignore(columns) {
     if (columns === true) {
       return ' on conflict do nothing';
diff --git a/package.json b/package.json
index ccf5d729..6cdc726b 100644
--- a/package.json
+++ b/package.json
@@ -106,7 +106,7 @@
   },
   "devDependencies": {
     "@tsconfig/recommended": "^1.0.1",
-    "@types/node": "^18.7.14",
+    "@types/node": "^20.4.0",
     "better-sqlite3": "^7.6.2",
     "chai": "^4.3.6",
     "chai-as-promised": "^7.1.1",
diff --git a/types/index.d.ts b/types/index.d.ts
index 3f1b78d2..5f72e3fe 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -1151,6 +1151,8 @@ export declare namespace Knex {
 
     onConflict(): OnConflictQueryBuilder<TRecord, TResult>;
 
+    updateFrom: Table<TRecord, TResult>;
+
     del(
       returning: '*',
       options?: DMLOptions


Test patch:
diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index 72a865f7..1fa73d0f 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -31,7 +31,7 @@ jobs:
           fetch-depth: 1
 
       - name: Setup Node ${{ matrix.node-version }}
-        uses: actions/setup-node@v3.6.0
+        uses: actions/setup-node@v3.7.0
         with:
           always-auth: false
           node-version: ${{ matrix.node-version }}
@@ -108,7 +108,7 @@ jobs:
         with: { fetch-depth: 1 }
 
       - name: Setup Node ${{ matrix.node-version }}
-        uses: actions/setup-node@v3.6.0
+        uses: actions/setup-node@v3.7.0
         with:
           always-auth: false
           node-version: ${{ matrix.node-version }}
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index a57dfd69..0a215bd4 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -29,7 +29,7 @@ jobs:
           fetch-depth: 1
 
       - name: Setup Node ${{ matrix.node-version }}
-        uses: actions/setup-node@v3.6.0
+        uses: actions/setup-node@v3.7.0
         with:
           always-auth: false
           node-version: ${{ matrix.node-version }}
diff --git a/test/integration2/query/update/updates.spec.js b/test/integration2/query/update/updates.spec.js
index 55e61ef6..e699870c 100644
--- a/test/integration2/query/update/updates.spec.js
+++ b/test/integration2/query/update/updates.spec.js
@@ -3,6 +3,7 @@
 const { expect } = require('chai');
 
 const { TEST_TIMESTAMP } = require('../../../util/constants');
+const { isPostgreSQL } = require('../../../util/db-helpers');
 const {
   getAllDbs,
   getKnexForDb,
@@ -534,6 +535,30 @@ describe('Updates', function () {
         expect(results[0].last_name).to.equal('olivier');
       });
 
+      it('should allow explicit from', async function () {
+        if (!isPostgreSQL(knex)) {
+          return this.skip();
+        }
+
+        await knex('accounts')
+          .update({ last_name: 'olivier' })
+          .with('withClause', function () {
+            this.select('id', 'last_name')
+              .from('accounts')
+              .where('email', '=', 'test1@example.com');
+          })
+          .updateFrom('withClause')
+          .where('withClause.id', '=', knex.ref('accounts.id'))
+          .testSql(function (tester) {
+            tester(
+              'pg',
+              'with "withClause" as (select "id", "last_name" from "accounts" where "email" = ?) update "accounts" set "last_name" = ? from "withClause" where "withClause"."id" = "accounts"."id"',
+              ['test1@example.com', 'olivier'],
+              1
+            );
+          });
+      });
+
       it('should escaped json objects when update value #5059', async function () {
         await knex.schema.dropTableIfExists('testing');
         await knex.schema.createTable('testing', (t) => {
diff --git a/test/unit/query/builder.js b/test/unit/query/builder.js
index 00aff178..3a2d11d7 100644
--- a/test/unit/query/builder.js
+++ b/test/unit/query/builder.js
@@ -6080,6 +6080,22 @@ describe('QueryBuilder', () => {
     );
   });
 
+  it('update from table', () => {
+    testsql(
+      qb()
+        .update({ email: 'foo', name: 'bar' })
+        .table('users')
+        .updateFrom('others')
+        .where('id', '=', 1),
+      {
+        pg: {
+          sql: 'update "users" set "email" = ?, "name" = ? from "others" where "id" = ?',
+          bindings: ['foo', 'bar', 1],
+        },
+      }
+    );
+  });
+
   it('should not update columns undefined values', () => {
     testsql(
       qb()