Repository: knex/knex
Pull request #4806 (2021-11-05 09:10:38)

Bump sinon from 11.1.2 to 12.0.1
Bumps [sinon](https://github.com/sinonjs/sinon) from 11.1.2 to 12.0.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/sinonjs/sinon/blob/master/docs/changelog.md">sinon's changelog</a>.</em></p>
<blockquote>
<h2>12.0.1</h2>
<ul>
<li><a href="https://github.com/sinonjs/sinon/commit/3f598221045904681f2b3b3ba1df617ed5e230e3"><code>3f598221</code></a>
Fix issue with npm unlink for npm version &gt; 6 (Carl-Erik Kopseng)
<blockquote>
<p>'npm unlink' would implicitly unlink the current dir
until version 7, which requires an argument</p>
</blockquote>
</li>
<li><a href="https://github.com/sinonjs/sinon/commit/51417a38111eeeb7cd14338bfb762cc2df487e1b"><code>51417a38</code></a>
Fix bundling of cjs module (<a href="https://github-redirect.dependabot.com/sinonjs/sinon/issues/2412">#2412</a>) (Julian Grinblat)
<blockquote>
<ul>
<li>Fix bundling of cjs module
&gt;</li>
<li>Run prettier</li>
</ul>
</blockquote>
</li>
</ul>
<p><em>Released by <a href="https://github.com/fatso83">Carl-Erik Kopseng</a> on 2021-11-04.</em></p>
<h2>12.0.0</h2>
<ul>
<li><a href="https://github.com/sinonjs/sinon/commit/b20ef9e4940e9384a6d0707b917a38e7bbfcd816"><code>b20ef9e4</code></a>
Upgrade to fake-timers@8 (Carl-Erik Kopseng). This is potentially breaking, but should not be,
as the breaking change deals with the Node timer object created by fake timers.</li>
<li><a href="https://github.com/sinonjs/sinon/commit/eba42cc38dbaf5417178a12cec11e35014e335ea"><code>eba42cc3</code></a>
Enable esm named exports (<a href="https://github-redirect.dependabot.com/sinonjs/sinon/issues/2382">#2382</a>) (Julian Grinblat)</li>
<li><a href="https://github.com/sinonjs/sinon/commit/b0cf5448993c2ace607cdf430b7e389d02c2f296"><code>b0cf5448</code></a>
Spelling (<a href="https://github-redirect.dependabot.com/sinonjs/sinon/issues/2398">#2398</a>) (Josh Soref)</li>
<li><a href="https://github.com/sinonjs/sinon/commit/e78a670611682c7e35cf7d27887b409d6397d27c"><code>e78a6706</code></a>
Make calledWith() assertions idempotent (<a href="https://github-redirect.dependabot.com/sinonjs/sinon/issues/2407">#2407</a>) (Joel Bradshaw)</li>
<li><a href="https://github.com/sinonjs/sinon/commit/2814c0a212ab6b79c7251e4b0a1bebc9918257d4"><code>2814c0a2</code></a>
Generate CHANGES.md using <code>@​studio/changes</code> (Morgan Roderick)
<blockquote>
<p>This will bring us closer to having the same release process as the
other <code>@sinonjs</code> packages.</p>
</blockquote>
</li>
<li><a href="https://github.com/sinonjs/sinon/commit/2d5d6ad4cd89c2063834991da5073f7640d0d722"><code>2d5d6ad4</code></a>
Run tests in Node 16 in GitHub Actions (Morgan Roderick)</li>
</ul>
<p><em>Released by <a href="https://github.com/fatso83">Carl-Erik Kopseng</a> on 2021-11-03.</em></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/sinonjs/sinon/commit/09039d758a2dfd46724eaf8705ef099602533853"><code>09039d7</code></a> 12.0.1</li>
<li><a href="https://github.com/sinonjs/sinon/commit/3f598221045904681f2b3b3ba1df617ed5e230e3"><code>3f59822</code></a> Fix issue with npm unlink for npm version &gt; 6</li>
<li><a href="https://github.com/sinonjs/sinon/commit/51417a38111eeeb7cd14338bfb762cc2df487e1b"><code>51417a3</code></a> Fix bundling of cjs module (<a href="https://github-redirect.dependabot.com/sinonjs/sinon/issues/2412">#2412</a>)</li>
<li><a href="https://github.com/sinonjs/sinon/commit/14b4f5e069b4a0b359037ad484c047f6fc37196f"><code>14b4f5e</code></a> Remove Sauce Labs badge</li>
<li><a href="https://github.com/sinonjs/sinon/commit/68a7cc09d29fb0b37da44ca99d49e8455c524491"><code>68a7cc0</code></a> Fix prettier error</li>
<li><a href="https://github.com/sinonjs/sinon/commit/5167e7dda4acb0773fc0f607486a0efb6d6c9ab7"><code>5167e7d</code></a> Update docs/changelog.md and set new release id in docs/_config.yml</li>
<li><a href="https://github.com/sinonjs/sinon/commit/fedeebfbc65be749e353cd8cd568e41067c7907b"><code>fedeebf</code></a> Add release documentation for v12.0.0</li>
<li><a href="https://github.com/sinonjs/sinon/commit/0cf9235509a4d2ee86d3dfc8b41b19d219c170ab"><code>0cf9235</code></a> update file path</li>
<li><a href="https://github.com/sinonjs/sinon/commit/dfa4ecb1c0949dafb47c46e422b30dae0ffd5a28"><code>dfa4ecb</code></a> update scriptname</li>
<li><a href="https://github.com/sinonjs/sinon/commit/d97a1ef5bf58990abe884491e1855157370188cc"><code>d97a1ef</code></a> Rename</li>
<li>Additional commits viewable in <a href="https://github.com/sinonjs/sinon/compare/v11.1.2...v12.0.1">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sinon&package-manager=npm_and_yarn&previous-version=11.1.2&new-version=12.0.1)](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/lib/dialects/mssql/query/mssql-querycompiler.js b/lib/dialects/mssql/query/mssql-querycompiler.js
index b323714b..e8a45208 100644
--- a/lib/dialects/mssql/query/mssql-querycompiler.js
+++ b/lib/dialects/mssql/query/mssql-querycompiler.js
@@ -502,11 +502,7 @@ class QueryCompiler_MSSQL extends QueryCompiler {
     const noLimit = !this.single.limit && this.single.limit !== 0;
     const noOffset = !this.single.offset;
     if (noLimit || !noOffset) return '';
-    return `top (${this.client.parameter(
-      this.single.limit,
-      this.builder,
-      this.bindingsHolder
-    )})`;
+    return `top (${this._getValueOrParameterFromAttribute('limit')})`;
   }
 
   limit() {
@@ -518,19 +514,11 @@ class QueryCompiler_MSSQL extends QueryCompiler {
     const noOffset = !this.single.offset;
     if (noOffset) return '';
     let offset = `offset ${
-      noOffset
-        ? '0'
-        : this.client.parameter(
-            this.single.offset,
-            this.builder,
-            this.bindingsHolder
-          )
+      noOffset ? '0' : this._getValueOrParameterFromAttribute('offset')
     } rows`;
     if (!noLimit) {
-      offset += ` fetch next ${this.client.parameter(
-        this.single.limit,
-        this.builder,
-        this.bindingsHolder
+      offset += ` fetch next ${this._getValueOrParameterFromAttribute(
+        'limit'
       )} rows only`;
     }
     return offset;
diff --git a/lib/dialects/mysql/index.js b/lib/dialects/mysql/index.js
index d4eb4efb..ddace51d 100644
--- a/lib/dialects/mysql/index.js
+++ b/lib/dialects/mysql/index.js
@@ -89,13 +89,9 @@ class Client_MySQL extends Client {
   }
 
   validateConnection(connection) {
-    if (
-      connection.state === 'connected' ||
-      connection.state === 'authenticated'
-    ) {
-      return true;
-    }
-    return false;
+    return (
+      connection.state === 'connected' || connection.state === 'authenticated'
+    );
   }
 
   // Grab a connection, run the query via the MySQL streaming interface,
diff --git a/lib/dialects/mysql/query/mysql-querycompiler.js b/lib/dialects/mysql/query/mysql-querycompiler.js
index 313f5191..382f2f7c 100644
--- a/lib/dialects/mysql/query/mysql-querycompiler.js
+++ b/lib/dialects/mysql/query/mysql-querycompiler.js
@@ -146,11 +146,7 @@ class QueryCompiler_MySQL extends QueryCompiler {
     const limit =
       this.single.offset && noLimit
         ? '18446744073709551615'
-        : this.client.parameter(
-            this.single.limit,
-            this.builder,
-            this.bindingsHolder
-          );
+        : this._getValueOrParameterFromAttribute('limit');
     return `limit ${limit}`;
   }
 
diff --git a/lib/dialects/mysql2/index.js b/lib/dialects/mysql2/index.js
index bc0bd8fd..63bfa4f0 100644
--- a/lib/dialects/mysql2/index.js
+++ b/lib/dialects/mysql2/index.js
@@ -15,10 +15,13 @@ class Client_MySQL2 extends Client_MySQL {
     return require('mysql2');
   }
   validateConnection(connection) {
-    if (connection._fatalError) {
-      return false;
-    }
-    return true;
+    return (
+      connection &&
+      !connection._fatalError &&
+      !connection._protocolError &&
+      !connection._closing &&
+      !connection.stream.destroyed
+    );
   }
 }
 
diff --git a/lib/dialects/oracle/query/oracle-querycompiler.js b/lib/dialects/oracle/query/oracle-querycompiler.js
index 0a1c9792..032281e7 100644
--- a/lib/dialects/oracle/query/oracle-querycompiler.js
+++ b/lib/dialects/oracle/query/oracle-querycompiler.js
@@ -315,10 +315,9 @@ class QueryCompiler_Oracle extends QueryCompiler {
     query = query || '';
 
     if (hasLimit && !offset) {
-      return `select * from (${query}) where rownum <= ${this.client.parameter(
-        limit,
-        this.builder,
-        this.bindingsHolder
+      return `select * from (${query}) where rownum <= ${this._getValueOrParameterFromAttribute(
+        'limit',
+        limit
       )}`;
     }
 
@@ -330,10 +329,12 @@ class QueryCompiler_Oracle extends QueryCompiler {
       query +
       ') row_ ' +
       'where rownum <= ' +
-      this.client.parameter(endRow, this.builder, this.bindingsHolder) +
+      (this.single.skipBinding['offset']
+        ? endRow
+        : this.client.parameter(endRow, this.builder, this.bindingsHolder)) +
       ') ' +
       'where rownum_ > ' +
-      this.client.parameter(offset, this.builder, this.bindingsHolder)
+      this._getValueOrParameterFromAttribute('offset', offset)
     );
   }
 }
diff --git a/lib/dialects/postgres/index.js b/lib/dialects/postgres/index.js
index ce44a0c7..5215e91d 100644
--- a/lib/dialects/postgres/index.js
+++ b/lib/dialects/postgres/index.js
@@ -7,6 +7,7 @@ const Client = require('../../client');
 
 const Transaction = require('./execution/pg-transaction');
 const QueryCompiler = require('./query/pg-querycompiler');
+const QueryBuilder = require('./query/pg-querybuilder');
 const ColumnCompiler = require('./schema/pg-columncompiler');
 const TableCompiler = require('./schema/pg-tablecompiler');
 const ViewCompiler = require('./schema/pg-viewcompiler');
@@ -30,6 +31,10 @@ class Client_PG extends Client {
     return new Transaction(this, ...arguments);
   }
 
+  queryBuilder() {
+    return new QueryBuilder(this);
+  }
+
   queryCompiler(builder, formatter) {
     return new QueryCompiler(this, builder, formatter);
   }
diff --git a/lib/dialects/postgres/query/pg-querybuilder.js b/lib/dialects/postgres/query/pg-querybuilder.js
new file mode 100644
index 00000000..2c425928
--- /dev/null
+++ b/lib/dialects/postgres/query/pg-querybuilder.js
@@ -0,0 +1,8 @@
+const QueryBuilder = require('../../../query/querybuilder.js');
+
+module.exports = class QueryBuilder_PostgreSQL extends QueryBuilder {
+  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 872a538d..24b5bd7d 100644
--- a/lib/dialects/postgres/query/pg-querycompiler.js
+++ b/lib/dialects/postgres/query/pg-querycompiler.js
@@ -4,7 +4,10 @@ const identity = require('lodash/identity');
 const reduce = require('lodash/reduce');
 
 const QueryCompiler = require('../../../query/querycompiler');
-const { wrapString } = require('../../../formatter/wrappingFormatter');
+const {
+  wrapString,
+  wrap: wrap_,
+} = require('../../../formatter/wrappingFormatter');
 
 class QueryCompiler_PG extends QueryCompiler {
   constructor(client, builder, formatter) {
@@ -57,9 +60,70 @@ class QueryCompiler_PG extends QueryCompiler {
     };
   }
 
-  // Compiles an `update` query, allowing for a return value.
+  using() {
+    const usingTables = this.single.using;
+    if (!usingTables) return;
+    let sql = 'using ';
+    if (Array.isArray(usingTables)) {
+      sql += usingTables
+        .map((table) => {
+          return this.formatter.wrap(table);
+        })
+        .join(',');
+    } else {
+      sql += this.formatter.wrap(usingTables);
+    }
+    return sql;
+  }
+
+  // Compiles an `delete` query, allowing for a return value.
   del() {
-    const sql = super.del(...arguments);
+    // Make sure tableName is processed by the formatter first.
+    const { tableName } = this;
+    const withSQL = this.with();
+    let wheres = this.where() || '';
+    let using = this.using() || '';
+    const joins = this.grouped.join;
+
+    const tableJoins = [];
+    if (Array.isArray(joins)) {
+      for (const join of joins) {
+        tableJoins.push(
+          wrap_(
+            this._joinTable(join),
+            undefined,
+            this.builder,
+            this.client,
+            this.bindingsHolder
+          )
+        );
+
+        const joinWheres = [];
+        for (const clause of join.clauses) {
+          joinWheres.push(
+            this.whereBasic({
+              column: clause.column,
+              operator: '=',
+              value: clause.value,
+              asColumn: true,
+            })
+          );
+        }
+        if (joinWheres.length > 0) {
+          wheres += (wheres ? ' and ' : '') + joinWheres.join(' ');
+        }
+      }
+      if (tableJoins.length > 0) {
+        using += (using ? ',' : 'using ') + tableJoins.join(',');
+      }
+    }
+
+    // With 'using' syntax, no tablename between DELETE and FROM.
+    const sql =
+      withSQL +
+      `delete from ${this.single.only ? 'only ' : ''}${tableName}` +
+      (using ? ` ${using}` : '') +
+      (wheres ? ` ${wheres}` : '');
     const { returning } = this.single;
     return {
       sql: sql + this._returning(returning),
diff --git a/lib/dialects/sqlite3/query/sqlite-querycompiler.js b/lib/dialects/sqlite3/query/sqlite-querycompiler.js
index 39c0f934..44d3931b 100644
--- a/lib/dialects/sqlite3/query/sqlite-querycompiler.js
+++ b/lib/dialects/sqlite3/query/sqlite-querycompiler.js
@@ -241,11 +241,8 @@ class QueryCompiler_SQLite3 extends QueryCompiler {
 
     // Workaround for offset only,
     // see http://stackoverflow.com/questions/10491492/sqllite-with-skip-offset-only-not-limit
-    return `limit ${this.client.parameter(
-      noLimit ? -1 : this.single.limit,
-      this.builder,
-      this.bindingsHolder
-    )}`;
+    this.single.limit = noLimit ? -1 : this.single.limit;
+    return `limit ${this._getValueOrParameterFromAttribute('limit')}`;
   }
 }
 
diff --git a/lib/migrations/migrate/stub/js.stub b/lib/migrations/migrate/stub/js.stub
index dfae79b9..566e2f59 100644
--- a/lib/migrations/migrate/stub/js.stub
+++ b/lib/migrations/migrate/stub/js.stub
@@ -1,4 +1,7 @@
-
+/**
+ * @param { import("knex").Knex } knex
+ * @returns { Promise<void> }
+ */
 exports.up = function(knex) {
   <% if (d.tableName) { %>
   return knex.schema.createTable("<%= d.tableName %>", function(t) {
@@ -8,6 +11,10 @@ exports.up = function(knex) {
   <% } %>
 };
 
+/**
+ * @param { import("knex").Knex } knex
+ * @returns { Promise<void> }
+ */
 exports.down = function(knex) {
   <% if (d.tableName) { %>
   return knex.schema.dropTable("<%= d.tableName %>");
diff --git a/lib/migrations/migrate/stub/knexfile-js.stub b/lib/migrations/migrate/stub/knexfile-js.stub
index 35e9b489..565609ec 100644
--- a/lib/migrations/migrate/stub/knexfile-js.stub
+++ b/lib/migrations/migrate/stub/knexfile-js.stub
@@ -1,5 +1,8 @@
 // Update with your config settings.
 
+/**
+ * @type { Object.<string, import("knex").Knex.Config> }
+ */
 module.exports = {
 
   development: {
diff --git a/lib/migrations/seed/stub/js.stub b/lib/migrations/seed/stub/js.stub
index 333db5ad..980bca05 100644
--- a/lib/migrations/seed/stub/js.stub
+++ b/lib/migrations/seed/stub/js.stub
@@ -1,4 +1,7 @@
-
+/**
+ * @param { import("knex").Knex } knex
+ * @returns { Promise<void> } 
+ */
 exports.seed = function(knex) {
   // Deletes ALL existing entries
   return knex('table_name').del()
diff --git a/lib/query/querybuilder.js b/lib/query/querybuilder.js
index 50c76c1a..4b923879 100644
--- a/lib/query/querybuilder.js
+++ b/lib/query/querybuilder.js
@@ -269,6 +269,12 @@ class Builder extends EventEmitter {
     return this;
   }
 
+  using(tables) {
+    throw new Error(
+      "'using' function is only available in PostgreSQL dialect with Delete statements."
+    );
+  }
+
   // JOIN blocks:
   innerJoin(...args) {
     return this._joinType('inner').join(...args);
@@ -943,8 +949,18 @@ class Builder extends EventEmitter {
     return this._bool('or').havingRaw(sql, bindings);
   }
 
+  // set the skip binding parameter (= insert the raw value in the query) for an attribute.
+  _setSkipBinding(attribute, options) {
+    let skipBinding = options;
+    if (isObject(options)) {
+      skipBinding = options.skipBinding;
+    }
+    this._single.skipBinding = this._single.skipBinding || {};
+    this._single.skipBinding[attribute] = skipBinding;
+  }
+
   // Only allow a single "offset" to be set for the current query.
-  offset(value) {
+  offset(value, options) {
     if (value == null || value.isRawInstance || value instanceof Builder) {
       // Builder for backward compatibility
       this._single.offset = value;
@@ -958,16 +974,18 @@ class Builder extends EventEmitter {
         this._single.offset = val;
       }
     }
+    this._setSkipBinding('offset', options);
     return this;
   }
 
   // Only allow a single "limit" to be set for the current query.
-  limit(value) {
+  limit(value, options) {
     const val = parseInt(value, 10);
     if (isNaN(val)) {
       this.client.logger.warn('A valid integer must be provided to limit');
     } else {
       this._single.limit = val;
+      this._setSkipBinding('limit', options);
     }
     return this;
   }
diff --git a/lib/query/querycompiler.js b/lib/query/querycompiler.js
index 784232e2..4acb8f1d 100644
--- a/lib/query/querycompiler.js
+++ b/lib/query/querycompiler.js
@@ -333,6 +333,12 @@ class QueryCompiler {
     })`;
   }
 
+  _joinTable(join) {
+    return join.schema && !(join.table instanceof Raw)
+      ? `${join.schema}.${join.table}`
+      : join.table;
+  }
+
   // Compiles all each of the `join` clauses on the query,
   // including any nested join queries.
   join() {
@@ -342,10 +348,7 @@ class QueryCompiler {
     if (!joins) return '';
     while (++i < joins.length) {
       const join = joins[i];
-      const table =
-        join.schema && !(join.table instanceof Raw)
-          ? `${join.schema}.${join.table}`
-          : join.table;
+      const table = this._joinTable(join);
       if (i > 0) sql += ' ';
       if (join.joinType === 'raw') {
         sql += unwrapRaw_(
@@ -709,23 +712,28 @@ class QueryCompiler {
     );
   }
 
+  _getValueOrParameterFromAttribute(attribute, rawValue) {
+    if (this.single.skipBinding[attribute] === true) {
+      return rawValue !== undefined && rawValue !== null
+        ? rawValue
+        : this.single[attribute];
+    }
+    return this.client.parameter(
+      this.single[attribute],
+      this.builder,
+      this.bindingsHolder
+    );
+  }
+
   limit() {
     const noLimit = !this.single.limit && this.single.limit !== 0;
     if (noLimit) return '';
-    return `limit ${this.client.parameter(
-      this.single.limit,
-      this.builder,
-      this.bindingsHolder
-    )}`;
+    return `limit ${this._getValueOrParameterFromAttribute('limit')}`;
   }
 
   offset() {
     if (!this.single.offset) return '';
-    return `offset ${this.client.parameter(
-      this.single.offset,
-      this.builder,
-      this.bindingsHolder
-    )}`;
+    return `offset ${this._getValueOrParameterFromAttribute('offset')}`;
   }
 
   // Compiles a `delete` query.
diff --git a/package.json b/package.json
index d01a7880..2f51ab38 100644
--- a/package.json
+++ b/package.json
@@ -119,7 +119,7 @@
     "pg-query-stream": "^4.2.1",
     "prettier": "2.4.1",
     "rimraf": "^3.0.2",
-    "sinon": "^11.1.2",
+    "sinon": "^12.0.1",
     "sinon-chai": "^3.7.0",
     "source-map-support": "^0.5.20",
     "sqlite3": "^5.0.2",
diff --git a/types/index.d.ts b/types/index.d.ts
index 9c77a5f9..57d474c3 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -482,7 +482,7 @@ export declare namespace Knex {
   //
   // QueryInterface
   //
-  type ClearStatements = "with" | "select" | "columns" | "hintComments" | "where" | "union" | "join" | "group" | "order" | "having" | "limit" | "offset" | "counter" | "counters";
+  type ClearStatements = "with" | "select" | "columns" | "hintComments" | "where" | "union" | "using" | "join" | "group" | "order" | "having" | "limit" | "offset" | "counter" | "counters";
 
   interface QueryInterface<TRecord extends {} = any, TResult = any> {
     select: Select<TRecord, TResult>;
@@ -509,6 +509,9 @@ export declare namespace Knex {
     fullOuterJoin: Join<TRecord, TResult>;
     crossJoin: Join<TRecord, TResult>;
 
+    // Using
+    using: Using<TRecord, TResult>;
+
     // Withs
     with: With<TRecord, TResult>;
     withRecursive: With<TRecord, TResult>;
@@ -605,8 +608,8 @@ export declare namespace Knex {
     clear(statement: ClearStatements): QueryBuilder<TRecord, TResult>;
 
     // Paging
-    offset(offset: number): QueryBuilder<TRecord, TResult>;
-    limit(limit: number): QueryBuilder<TRecord, TResult>;
+    offset(offset: number, options?: boolean | Readonly<{skipBinding?: boolean}>): QueryBuilder<TRecord, TResult>;
+    limit(limit: number, options?: string | Readonly<{skipBinding?: boolean}>): QueryBuilder<TRecord, TResult>;
 
     // Aggregation
     count: AsymmetricAggregation<TRecord, TResult, Lookup<ResultTypes.Registry, "Count", number | string>>;
@@ -1338,6 +1341,10 @@ export declare namespace Knex {
     >;
   }
 
+  interface Using<TRecord = any, TResult = unknown[]> {
+    (tables: string[]): QueryBuilder<TRecord, TResult>;
+  }
+
   interface With<TRecord = any, TResult = unknown[]>
     extends WithRaw<TRecord, TResult>,
     WithWrapped<TRecord, TResult> {}


Test patch:
diff --git a/test/integration/query/deletes.js b/test/integration/query/deletes.js
index e24a442f..9f76c5dd 100644
--- a/test/integration/query/deletes.js
+++ b/test/integration/query/deletes.js
@@ -2,12 +2,8 @@
 
 const { expect } = require('chai');
 const { TEST_TIMESTAMP } = require('../../util/constants');
-const {
-  isSQLite,
-  isPostgreSQL,
-  isOracle,
-  isCockroachDB,
-} = require('../../util/db-helpers');
+const { isSQLite, isOracle, isCockroachDB } = require('../../util/db-helpers');
+const { isPostgreSQL } = require('../../util/db-helpers.js');
 
 module.exports = function (knex) {
   describe('Deletes', function () {
@@ -96,12 +92,7 @@ module.exports = function (knex) {
           .join('accounts', 'accounts.id', 'test_table_two.account_id')
           .where({ 'accounts.email': 'test3@example.com' })
           .del();
-        if (
-          isSQLite(knex) ||
-          isPostgreSQL(knex) ||
-          isCockroachDB(knex) ||
-          isOracle(knex)
-        ) {
+        if (isSQLite(knex) || isCockroachDB(knex) || isOracle(knex)) {
           await expect(query).to.be.rejected;
           return;
         }
@@ -112,6 +103,12 @@ module.exports = function (knex) {
             ['test3@example.com'],
             1
           );
+          tester(
+            'pg',
+            'delete from "test_table_two" using "accounts" where "accounts"."email" = ? and "accounts"."id" = "test_table_two"."account_id"',
+            ['test3@example.com'],
+            1
+          );
           tester(
             'mssql',
             'delete [test_table_two] from [test_table_two] inner join [accounts] on [accounts].[id] = [test_table_two].[account_id] where [accounts].[email] = ?;select @@rowcount',
@@ -120,6 +117,35 @@ module.exports = function (knex) {
           );
         });
       });
+
+      it('should handle basic delete with join and "using" syntax in PostgreSQL', async function () {
+        if (!isPostgreSQL(knex)) {
+          this.skip();
+        }
+        await knex('test_table_two').insert({
+          account_id: 4,
+          details: '',
+          status: 1,
+        });
+        const query = knex('test_table_two')
+          .using('accounts')
+          .where({ 'accounts.email': 'test4@example.com' })
+          .whereRaw('"accounts"."id" = "test_table_two"."account_id"')
+          .del();
+        if (!isPostgreSQL(knex)) {
+          await expect(query).to.be.rejected;
+          return;
+        }
+        return query.testSql(function (tester) {
+          tester(
+            'pg',
+            'delete from "test_table_two" using "accounts" where "accounts"."email" = ? and "accounts"."id" = "test_table_two"."account_id"',
+            ['test4@example.com'],
+            1
+          );
+        });
+      });
+
       it('should handle returning', async function () {
         await knex('test_table_two').insert({
           account_id: 4,
@@ -130,12 +156,7 @@ module.exports = function (knex) {
           .join('accounts', 'accounts.id', 'test_table_two.account_id')
           .where({ 'accounts.email': 'test4@example.com' })
           .del('*');
-        if (
-          isSQLite(knex) ||
-          isPostgreSQL(knex) ||
-          isCockroachDB(knex) ||
-          isOracle(knex)
-        ) {
+        if (isSQLite(knex) || isCockroachDB(knex) || isOracle(knex)) {
           await expect(query).to.be.rejected;
           return;
         }
@@ -146,6 +167,28 @@ module.exports = function (knex) {
             ['test4@example.com'],
             1
           );
+          tester(
+            'pg',
+            'delete from "test_table_two" using "accounts" where "accounts"."email" = ? and "accounts"."id" = "test_table_two"."account_id" returning *',
+            ['test4@example.com'],
+            [
+              {
+                about: 'Lorem ipsum Dolore labore incididunt enim.',
+                balance: 0,
+                id: '4',
+                account_id: 4,
+                details: '',
+                status: 1,
+                phone: null,
+                logins: 2,
+                email: 'test4@example.com',
+                first_name: 'Test',
+                last_name: 'User',
+                created_at: TEST_TIMESTAMP,
+                updated_at: TEST_TIMESTAMP,
+              },
+            ]
+          );
           tester(
             'mssql',
             'delete [test_table_two] output deleted.* from [test_table_two] inner join [accounts] on [accounts].[id] = [test_table_two].[account_id] where [accounts].[email] = ?',
diff --git a/test/tape/pool.js b/test/tape/pool.js
index 7019bc0b..1780dad1 100644
--- a/test/tape/pool.js
+++ b/test/tape/pool.js
@@ -23,7 +23,7 @@ test(`pool evicts dead resources when factory.validate rejects`, async (t) => {
     },
 
     validate: (res) => {
-      return res.error ? false : true;
+      return !res.error;
     },
 
     destroy: (res) => {
diff --git a/test/unit/query/builder.js b/test/unit/query/builder.js
index 9ba0246d..68f5f765 100644
--- a/test/unit/query/builder.js
+++ b/test/unit/query/builder.js
@@ -8,6 +8,7 @@ const Redshift_Client = require('../../../lib/dialects/redshift');
 const Oracledb_Client = require('../../../lib/dialects/oracledb');
 const SQLite3_Client = require('../../../lib/dialects/sqlite3');
 const MSSQL_Client = require('../../../lib/dialects/mssql');
+const CockroachDB_Client = require('../../../lib/dialects/cockroachdb');
 
 // use driverName as key
 const clients = {
@@ -17,6 +18,7 @@ const clients = {
   oracledb: new Oracledb_Client({ client: 'oracledb' }),
   sqlite3: new SQLite3_Client({ client: 'sqlite3' }),
   mssql: new MSSQL_Client({ client: 'mssql' }),
+  cockroachdb: new CockroachDB_Client({ client: 'cockroachdb' }),
 };
 
 const useNullAsDefaultConfig = { useNullAsDefault: true };
@@ -2108,15 +2110,6 @@ describe('QueryBuilder', () => {
     });
   });
 
-  // it("handles grouped mysql unions", function() {
-  //   chain = myqb().union(
-  //     raw(myqb().select('*').from('users').where('id', '=', 1)).wrap('(', ')'),
-  //     raw(myqb().select('*').from('users').where('id', '=', 2)).wrap('(', ')')
-  //   ).orderBy('id').limit(10).toSQL();
-  //   expect(chain.sql).to.equal('(select * from `users` where `id` = ?) union (select * from `users` where `id` = ?) order by `id` asc limit ?');
-  //   expect(chain.bindings).to.eql([1, 2, 10]);
-  // });
-
   it('union alls', () => {
     const chain = qb()
       .select('*')
@@ -3616,6 +3609,39 @@ describe('QueryBuilder', () => {
     });
   });
 
+  it('limits with skip binding', () => {
+    testsql(
+      qb()
+        .select('*')
+        .from('users')
+        .limit(10, { skipBinding: true })
+        .offset(5, true),
+      {
+        mysql: {
+          sql: 'select * from `users` limit 10 offset 5',
+        },
+        sqlite3: {
+          sql: 'select * from `users` limit 10 offset 5',
+        },
+        mssql: {
+          sql: 'select * from [users] offset 5 rows fetch next 10 rows only',
+        },
+        oracledb: {
+          sql: 'select * from (select row_.*, ROWNUM rownum_ from (select * from "users") row_ where rownum <= 15) where rownum_ > 5',
+        },
+        pg: {
+          sql: 'select * from "users" limit 10 offset 5',
+        },
+        cockroachdb: {
+          sql: 'select * from "users" limit 10 offset 5',
+        },
+        'pg-redshift': {
+          sql: 'select * from "users" limit 10 offset 5',
+        },
+      }
+    );
+  });
+
   it('limits and raw selects', () => {
     testsql(
       qb()
@@ -10190,55 +10216,152 @@ describe('QueryBuilder', () => {
     );
   });
 
-  it('should include join when deleting', () => {
-    testsql(
-      qb()
-        .del()
-        .from('users')
-        .join('photos', 'photos.id', 'users.id')
-        .where({ 'user.email': 'mock@example.com' }),
-      {
-        mysql: {
-          sql: 'delete `users` from `users` inner join `photos` on `photos`.`id` = `users`.`id` where `user`.`email` = ?',
-          bindings: ['mock@example.com'],
-        },
-        mssql: {
-          sql: 'delete [users] from [users] inner join [photos] on [photos].[id] = [users].[id] where [user].[email] = ?;select @@rowcount',
-          bindings: ['mock@example.com'],
-        },
-        oracledb: {
-          sql: 'delete "users" from "users" inner join "photos" on "photos"."id" = "users"."id" where "user"."email" = ?',
-          bindings: ['mock@example.com'],
-        },
-        pg: {
-          sql: 'delete "users" from "users" inner join "photos" on "photos"."id" = "users"."id" where "user"."email" = ?',
-          bindings: ['mock@example.com'],
-        },
-        'pg-redshift': {
-          sql: 'delete "users" from "users" inner join "photos" on "photos"."id" = "users"."id" where "user"."email" = ?',
-          bindings: ['mock@example.com'],
-        },
-        sqlite3: {
-          sql: 'delete `users` from `users` inner join `photos` on `photos`.`id` = `users`.`id` where `user`.`email` = ?',
-          bindings: ['mock@example.com'],
-        },
-      }
-    );
-  });
-  it('should include join when deleting with mssql triggers', () => {
-    const triggerOptions = { includeTriggerModifications: true };
-    testsql(
-      qb()
-        .del('*', triggerOptions)
-        .from('users')
-        .join('photos', 'photos.id', 'users.id')
-        .where({ 'user.email': 'mock@example.com' }),
-      {
-        mssql: {
-          sql: 'select top(0) [t].* into #out from [users] as t left join [users] on 0=1;delete [users] output deleted.* into #out from [users] inner join [photos] on [photos].[id] = [users].[id] where [user].[email] = ?; select * from #out; drop table #out;',
-          bindings: ['mock@example.com'],
-        },
-      }
-    );
+  describe('deleting with joins', () => {
+    it('should transform joins into "using" syntax with PostgreSQL', () => {
+      // Knex transform joins into "using" syntax for PostgreSQL
+      testsql(
+        qb()
+          .del()
+          .from('users')
+          .join('photos', 'photos.id', 'users.id')
+          .where({ 'user.email': 'mock@example.com' }),
+        {
+          pg: {
+            sql: 'delete from "users" using "photos" where "user"."email" = ? and "photos"."id" = "users"."id"',
+            bindings: ['mock@example.com'],
+          },
+        }
+      );
+    });
+
+    it('should transform multiple joins into multiple "using" syntax with PostgreSQL', () => {
+      // Knex transform joins into "using" syntax for PostgreSQL
+      testsql(
+        qb()
+          .del()
+          .from('users')
+          .join('photos', 'photos.id', 'users.id')
+          .join('docs', 'docs.id', 'users.id')
+          .where({ 'user.email': 'mock@example.com' }),
+        {
+          pg: {
+            sql: 'delete from "users" using "photos","docs" where "user"."email" = ? and "photos"."id" = "users"."id" and "docs"."id" = "users"."id"',
+            bindings: ['mock@example.com'],
+          },
+        }
+      );
+    });
+
+    it('should join with "using" explicit syntax with PostgreSQL', () => {
+      // explicit using syntax
+      testsql(
+        qb()
+          .del()
+          .from('users')
+          .using('photos')
+          .where({ 'user.email': 'mock@example.com' })
+          .whereRaw('"photos"."id" = "users"."id"'),
+        {
+          pg: {
+            sql: 'delete from "users" using "photos" where "user"."email" = ? and "photos"."id" = "users"."id"',
+            bindings: ['mock@example.com'],
+          },
+        }
+      );
+    });
+
+    it('should join with multiple tables and "using" explicit syntax with PostgreSQL', () => {
+      // Test with multiple tables 'using'
+      testsql(
+        qb()
+          .del()
+          .from('users')
+          .using(['photos', 'docs'])
+          .where({
+            'user.email': 'mock@example.com',
+          })
+          .whereRaw(
+            '"photos"."id" = "users"."id" and "docs"."id" = "users"."id"'
+          ),
+        {
+          pg: {
+            sql: 'delete from "users" using "photos","docs" where "user"."email" = ? and "photos"."id" = "users"."id" and "docs"."id" = "users"."id"',
+            bindings: ['mock@example.com'],
+          },
+        }
+      );
+    });
+
+    it('should joins with mixed joins and "using" explicit syntax with PostgreSQL', () => {
+      // you can use explicit 'using' and joins, all are merged at the end.
+      testsql(
+        qb()
+          .del()
+          .from('users')
+          .using('photos')
+          .join('docs', 'docs.id', 'users.id')
+          .whereRaw('"photos"."id" = "users"."id"')
+          .where({ 'user.email': 'mock@example.com' }),
+        {
+          pg: {
+            sql: 'delete from "users" using "photos","docs" where "photos"."id" = "users"."id" and "user"."email" = ? and "docs"."id" = "users"."id"',
+            bindings: ['mock@example.com'],
+          },
+        }
+      );
+    });
+
+    it('should include join when deleting', () => {
+      testsql(
+        qb()
+          .del()
+          .from('users')
+          .join('photos', 'photos.id', 'users.id')
+          .where({ 'user.email': 'mock@example.com' }),
+        {
+          mysql: {
+            sql: 'delete `users` from `users` inner join `photos` on `photos`.`id` = `users`.`id` where `user`.`email` = ?',
+            bindings: ['mock@example.com'],
+          },
+          mssql: {
+            sql: 'delete [users] from [users] inner join [photos] on [photos].[id] = [users].[id] where [user].[email] = ?;select @@rowcount',
+            bindings: ['mock@example.com'],
+          },
+          oracledb: {
+            sql: 'delete "users" from "users" inner join "photos" on "photos"."id" = "users"."id" where "user"."email" = ?',
+            bindings: ['mock@example.com'],
+          },
+          pg: {
+            sql: 'delete from "users" using "photos" where "user"."email" = ? and "photos"."id" = "users"."id"',
+            bindings: ['mock@example.com'],
+          },
+          'pg-redshift': {
+            sql: 'delete "users" from "users" inner join "photos" on "photos"."id" = "users"."id" where "user"."email" = ?',
+            bindings: ['mock@example.com'],
+          },
+          sqlite3: {
+            sql: 'delete `users` from `users` inner join `photos` on `photos`.`id` = `users`.`id` where `user`.`email` = ?',
+            bindings: ['mock@example.com'],
+          },
+        }
+      );
+    });
+
+    it('should include join when deleting with mssql triggers', () => {
+      const triggerOptions = { includeTriggerModifications: true };
+      testsql(
+        qb()
+          .del('*', triggerOptions)
+          .from('users')
+          .join('photos', 'photos.id', 'users.id')
+          .where({ 'user.email': 'mock@example.com' }),
+        {
+          mssql: {
+            sql: 'select top(0) [t].* into #out from [users] as t left join [users] on 0=1;delete [users] output deleted.* into #out from [users] inner join [photos] on [photos].[id] = [users].[id] where [user].[email] = ?; select * from #out; drop table #out;',
+            bindings: ['mock@example.com'],
+          },
+        }
+      );
+    });
   });
 });