Repository: knex/knex (js)

Issue:
hasTable with Schema in mssql
# Environment

Knex version: 2.0.0
Database + version: mssql
OS: windows 10

@smorey2 

# Bug

1. hasTable does not work with schema (withSchema("NAME")) in mssql. You get an error. The issue seems to be caused by this commit:   https://github.com/knex/knex/commit/c4a3abc42d34f47c3306abadc93d09c3ed0480b5

The SQL Statement does not filter for the schema anymore and only for the name of the table. 
![image](https://user-images.githubusercontent.com/58333113/167437740-dda2c4b4-c5cd-4b3b-a0da-92b39f8f7fcb.png)

This part seems to be missing:
![image](https://user-images.githubusercontent.com/58333113/167438175-8fa072c4-ab7d-45b3-bd7c-598f3f3e86d2.png)
(taken from the mysql compiler)


2. Error message
Returns false, when it should return true.

3. Config

`const config: Knex.Config = {
            client: "mssql",
            connection: {
                user: process.env.DATABASE_USER,
                password: process.env.DATABASE_PASSWORD,
                server: process.env.DATABASE_HOST,
                port: Number(process.env.DATABASE_PORT),
                database: process.env.DATABASE_NAME,
                connectionTimeout: 60000,
                options: {
                    trustServerCertificate: true,
                    encrypt: true,
                },
            },
        };`

![image](https://user-images.githubusercontent.com/58333113/167437312-753c7c85-dc3e-4009-8fc9-3b93baea843b.png)
![image](https://user-images.githubusercontent.com/58333113/167437355-305e54e5-0758-4a63-9f0f-4d836ef3d581.png)
![image](https://user-images.githubusercontent.com/58333113/167437414-1793c057-7d8a-4818-aee1-87d066675475.png)



Pull request description:
MSSQL: Fix .hasTable result when using .withSchema
This PR fixes an issue where `hasTable` would return false if used on `withSchema`. This happens only on MSSQL.

I added a new test to ensure the behavior and also cleaned the test file a bit.

Closes #5170 

Patch:
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index a40f981b..c919020c 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -22,7 +22,7 @@ jobs:
           fetch-depth: 1
 
       - name: Setup Node ${{ matrix.node-version }}
-        uses: actions/setup-node@v3.1.1
+        uses: actions/setup-node@v3.2.0
         with:
           always-auth: false
           node-version: ${{ matrix.node-version }}
diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml
index b958c8c9..ae072088 100644
--- a/.github/workflows/linting.yml
+++ b/.github/workflows/linting.yml
@@ -26,7 +26,7 @@ jobs:
           fetch-depth: 1
 
       - name: Setup Node ${{ matrix.node-version }}
-        uses: actions/setup-node@v3.1.1
+        uses: actions/setup-node@v3.2.0
         with:
           always-auth: false
           node-version: ${{ matrix.node-version }}
diff --git a/.gitignore b/.gitignore
index b0d47e0f..4cc39126 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ node_modules
 test.sqlite3
 npm-debug.log
 tmp
+knex-*.tgz
 
 # Testing for the knex cli:
 bin/migrations-folder
diff --git a/.husky/post-checkout b/.husky/post-checkout
new file mode 100755
index 00000000..9b3d2e81
--- /dev/null
+++ b/.husky/post-checkout
@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+npm run clean && npm run build
diff --git a/.husky/pre-commit b/.husky/pre-commit
old mode 100644
new mode 100755
index 36af2198..d24fdfc6
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,4 +1,4 @@
-#!/bin/sh
-. "$(dirname "$0")/_/husky.sh"
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
 
 npx lint-staged
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1c146567..634539df 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -171,11 +171,11 @@ psql -h localhost -U postgres -d knex_test
 
 ## Typescript source files
 
-> TL;DR: Starting with release 2.0.0 Knex is adding support for Typescript source files. Thus to develop in this repo you will need to run `npm run build` each time you edit `.ts` files to generate the resulting `.js` files. This is automatically run whenever you run `npm install` so when developing in Javascript you don't have to worry about it. It is encouraged that new functionality and sources be written in Typescript but this is not required.
+> TL;DR: Starting with release 2.0.0 Knex is adding support for Typescript source files. Thus to develop in this repo you will need to run `npm run build` each time you edit `.ts` files to generate the resulting `.js` files. This is automatically run whenever you run `npm install` or checkout a new Git branch so when developing in Javascript you don't have to worry about it. It is encouraged that new functionality and sources be written in Typescript but this is not required.
 
 Starting with release 2.0.0, Knex is support source additions in Typescript! This allows for better safety in the code added. However, pre-2.0.0 Knex was always written in pure Javascript and thus a "hybrid" approach is being used for 2.0.0 to allow for the new `.ts` files to exist along `.js` files that make up the majority of this repository.
 
-To develop in this repository use the `npm run build` and `npm run clean` commands to compile and delete the `.js` and related files from `.ts` files. If you wish to have the `tsc` compiled watch and recompile on changes then run `npm run build:ts -- --watch`. Note that for easy integration with Javascript the outputted files are done in a "side-by-side" manner meaning that `lib/foo/bar.ts` will result in `lib/foo/bar.js`. This is done automatically via the npm `postinstall` script trigger which executes when you run commands like `npm install` or `npm ci`, thus making it easier to not have to worry about this if you're working in pure Javascript.
+To develop in this repository use the `npm run build` and `npm run clean` commands to compile and delete the `.js` and related files from `.ts` files. If you wish to have the `tsc` compiled watch and recompile on changes then run `npm run build:ts -- --watch`. Note that for easy integration with Javascript the outputted files are done in a "side-by-side" manner meaning that `lib/foo/bar.ts` will result in `lib/foo/bar.js`. This is done automatically via the npm script command `"prepare"` whenever you run `npm install` and Git hook for `post-checkout` (added by Husky) which executes when you run commands like `git checkout` , thus making it easier to not have to worry about this if you're working in pure Javascript.
 
 The script file `./scripts/update_gitignore_for_tsc_output.js` file is called as part of the `npm run build` command which will update the `lib/.gitignore` file which is used to ensure generated `.js` and related files from `tsc` compilation are not checked into the git repo.
 
diff --git a/lib/dialects/mssql/schema/mssql-compiler.js b/lib/dialects/mssql/schema/mssql-compiler.js
index 6491f871..135222af 100644
--- a/lib/dialects/mssql/schema/mssql-compiler.js
+++ b/lib/dialects/mssql/schema/mssql-compiler.js
@@ -49,10 +49,17 @@ class SchemaCompiler_MSSQL extends SchemaCompiler {
       this.builder,
       this.bindingsHolder
     );
-    const sql =
+    const bindings = [tableName];
+    let sql =
       `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES ` +
       `WHERE TABLE_NAME = ${formattedTable}`;
-    this.pushQuery({ sql, output: (resp) => resp.length > 0 });
+
+    if (this.schema) {
+      sql += ' AND TABLE_SCHEMA = ?';
+      bindings.push(this.schema);
+    }
+
+    this.pushQuery({ sql, bindings, output: (resp) => resp.length > 0 });
   }
 
   // Check whether a column exists on the schema.
diff --git a/lib/dialects/mysql/query/mysql-querycompiler.js b/lib/dialects/mysql/query/mysql-querycompiler.js
index 52dd18b2..a235c64f 100644
--- a/lib/dialects/mysql/query/mysql-querycompiler.js
+++ b/lib/dialects/mysql/query/mysql-querycompiler.js
@@ -131,7 +131,7 @@ class QueryCompiler_MySQL extends QueryCompiler {
       output(resp) {
         const out = resp.reduce(function (columns, val) {
           columns[val.COLUMN_NAME] = {
-            defaultValue: val.COLUMN_DEFAULT,
+            defaultValue: val.COLUMN_DEFAULT === 'NULL' ? null : val.COLUMN_DEFAULT,
             type: val.DATA_TYPE,
             maxLength: val.CHARACTER_MAXIMUM_LENGTH,
             nullable: val.IS_NULLABLE === 'YES',
diff --git a/lib/dialects/oracledb/index.js b/lib/dialects/oracledb/index.js
index cba691d8..a0444209 100644
--- a/lib/dialects/oracledb/index.js
+++ b/lib/dialects/oracledb/index.js
@@ -111,7 +111,8 @@ class Client_Oracledb extends Client_Oracle {
         this
       );
     } else if (value instanceof BlobHelper) {
-      return 'EMPTY_BLOB()';
+      formatter.bindings.push(value.value);
+      return '?';
     }
     return unwrapRaw(value, true, builder, this, formatter) || '?';
   }
diff --git a/lib/knex-builder/Knex.js b/lib/knex-builder/Knex.js
index fd59b6d1..3f89233e 100644
--- a/lib/knex-builder/Knex.js
+++ b/lib/knex-builder/Knex.js
@@ -5,6 +5,10 @@ const QueryInterface = require('../query/method-constants');
 const makeKnex = require('./make-knex');
 const { KnexTimeoutError } = require('../util/timeout');
 const { resolveConfig } = require('./internal/config-resolver');
+const SchemaBuilder = require('../schema/builder');
+const ViewBuilder = require('../schema/viewbuilder');
+const ColumnBuilder = require('../schema/columnbuilder');
+const TableBuilder = require('../schema/tablebuilder');
 
 function knex(config) {
   const { resolvedConfig, Dialect } = resolveConfig(...arguments);
@@ -28,4 +32,28 @@ knex.QueryBuilder = {
   },
 };
 
+knex.SchemaBuilder = {
+  extend: function (methodName, fn) {
+    SchemaBuilder.extend(methodName, fn);
+  },
+};
+
+knex.ViewBuilder = {
+  extend: function (methodName, fn) {
+    ViewBuilder.extend(methodName, fn);
+  },
+};
+
+knex.ColumnBuilder = {
+  extend: function (methodName, fn) {
+    ColumnBuilder.extend(methodName, fn);
+  },
+};
+
+knex.TableBuilder  = {
+  extend: function (methodName, fn) {
+    TableBuilder.extend(methodName, fn);
+  },
+};
+
 module.exports = knex;
diff --git a/lib/migrations/migrate/MigrationGenerator.js b/lib/migrations/migrate/MigrationGenerator.js
index a33d55bd..979510a2 100644
--- a/lib/migrations/migrate/MigrationGenerator.js
+++ b/lib/migrations/migrate/MigrationGenerator.js
@@ -41,7 +41,7 @@ class MigrationGenerator {
 
   _getNewMigrationName(name) {
     if (name[0] === '-') name = name.slice(1);
-    return yyyymmddhhmmss() + '_' + name + '.' + this.config.extension;
+    return yyyymmddhhmmss() + '_' + name + '.' + this.config.extension.split('-')[0];
   }
 
   _getNewMigrationPath(name) {
diff --git a/lib/migrations/migrate/stub/js-schema.stub b/lib/migrations/migrate/stub/js-schema.stub
new file mode 100644
index 00000000..bf8d2743
--- /dev/null
+++ b/lib/migrations/migrate/stub/js-schema.stub
@@ -0,0 +1,22 @@
+/**
+ * @param { import("knex").Knex } knex
+ * @returns { Promise<void> }
+ */
+exports.up = function({schema}) {
+  <% if (d.tableName) { %>
+  return schema.createTable("<%= d.tableName %>", function(t) {
+    t.increments();
+    t.timestamp();
+  });
+  <% } %>
+};
+
+/**
+ * @param { import("knex").Knex } knex
+ * @returns { Promise<void> }
+ */
+exports.down = function({schema}) {
+  <% if (d.tableName) { %>
+  return schema.dropTable("<%= d.tableName %>");
+  <% } %>
+};
diff --git a/lib/migrations/migrate/stub/ts-schema.stub b/lib/migrations/migrate/stub/ts-schema.stub
new file mode 100644
index 00000000..850d1bfc
--- /dev/null
+++ b/lib/migrations/migrate/stub/ts-schema.stub
@@ -0,0 +1,21 @@
+import { Knex } from "knex";
+
+<% if (d.tableName) { %>
+export async function up({schema}: Knex): Promise<Knex.SchemaBuilder> {
+    return schema.createTable("<%= d.tableName %>", (t) => {
+        t.increments();
+        t.timestamps();
+    });
+}
+<% } else { %>
+export async function up({schema}: Knex): Promise<void> {
+}
+<% } %>
+<% if (d.tableName) { %>
+export async function down({schema}: Knex): Promise<Knex.SchemaBuilder> {
+    return schema.dropTable("<%= d.tableName %>");
+}
+<% } else { %>
+export async function down({schema}: Knex): Promise<void> {
+}
+<% } %>
diff --git a/lib/schema/builder.js b/lib/schema/builder.js
index f9a38e97..fc4fa959 100644
--- a/lib/schema/builder.js
+++ b/lib/schema/builder.js
@@ -1,5 +1,6 @@
 const { EventEmitter } = require('events');
 const toArray = require('lodash/toArray');
+const assign = require('lodash/assign');
 const { addQueryContext } = require('../util/helpers');
 const saveAsyncStack = require('../util/save-async-stack');
 const {
@@ -96,6 +97,17 @@ class SchemaBuilder extends EventEmitter {
   };
 });
 
+
+SchemaBuilder.extend = (methodName, fn) => {
+  if (Object.prototype.hasOwnProperty.call(SchemaBuilder.prototype, methodName)) {
+    throw new Error(
+      `Can't extend SchemaBuilder with existing method ('${methodName}').`
+    );
+  }
+
+  assign(SchemaBuilder.prototype, { [methodName]: fn });
+};
+
 augmentWithBuilderInterface(SchemaBuilder);
 addQueryContext(SchemaBuilder);
 
diff --git a/lib/schema/columnbuilder.js b/lib/schema/columnbuilder.js
index 685872c0..d520644c 100644
--- a/lib/schema/columnbuilder.js
+++ b/lib/schema/columnbuilder.js
@@ -1,4 +1,5 @@
 const extend = require('lodash/extend');
+const assign = require('lodash/assign');
 const toArray = require('lodash/toArray');
 const { addQueryContext } = require('../util/helpers');
 
@@ -87,6 +88,17 @@ ColumnBuilder.prototype.notNull = ColumnBuilder.prototype.notNullable =
   };
 });
 
+
+ColumnBuilder.extend = (methodName, fn) => {
+  if (Object.prototype.hasOwnProperty.call(ColumnBuilder.prototype, methodName)) {
+    throw new Error(
+      `Can't extend ColumnBuilder with existing method ('${methodName}').`
+    );
+  }
+
+  assign(ColumnBuilder.prototype, { [methodName]: fn });
+};
+
 const AlterMethods = {};
 
 // Specify that the column is to be dropped. This takes precedence
diff --git a/lib/schema/tablebuilder.js b/lib/schema/tablebuilder.js
index b52c031e..953b09cc 100644
--- a/lib/schema/tablebuilder.js
+++ b/lib/schema/tablebuilder.js
@@ -8,6 +8,7 @@
 // ------
 const each = require('lodash/each');
 const extend = require('lodash/extend');
+const assign = require('lodash/assign');
 const toArray = require('lodash/toArray');
 const helpers = require('../util/helpers');
 const { isString, isFunction, isObject } = require('../util/is');
@@ -360,4 +361,15 @@ AlterMethods.dropColumn = AlterMethods.dropColumns = function () {
   return this;
 };
 
+
+TableBuilder.extend = (methodName, fn) => {
+  if (Object.prototype.hasOwnProperty.call(TableBuilder.prototype, methodName)) {
+    throw new Error(
+      `Can't extend TableBuilder with existing method ('${methodName}').`
+    );
+  }
+
+  assign(TableBuilder.prototype, { [methodName]: fn });
+};
+
 module.exports = TableBuilder;
diff --git a/lib/schema/viewbuilder.js b/lib/schema/viewbuilder.js
index f33e7067..dd7e0e99 100644
--- a/lib/schema/viewbuilder.js
+++ b/lib/schema/viewbuilder.js
@@ -1,5 +1,6 @@
 const helpers = require('../util/helpers');
 const extend = require('lodash/extend');
+const assign = require('lodash/assign');
 
 class ViewBuilder {
   constructor(client, method, viewName, fn) {
@@ -78,4 +79,15 @@ const AlterMethods = {
 
 helpers.addQueryContext(ViewBuilder);
 
+
+ViewBuilder.extend = (methodName, fn) => {
+  if (Object.prototype.hasOwnProperty.call(ViewBuilder.prototype, methodName)) {
+    throw new Error(
+      `Can't extend ViewBuilder with existing method ('${methodName}').`
+    );
+  }
+
+  assign(ViewBuilder.prototype, { [methodName]: fn });
+};
+
 module.exports = ViewBuilder;
diff --git a/package.json b/package.json
index 1189e333..62f63deb 100644
--- a/package.json
+++ b/package.json
@@ -9,9 +9,9 @@
   },
   "scripts": {
     "build": "npm run build:gitignore && npm run build:ts",
-    "clean": "git clean -f -X lib/",
+    "clean": "node scripts/clean.js",
     "build:ts": "tsc",
-    "build:gitignore": "./scripts/update_gitignore_for_tsc_output.js run",
+    "build:gitignore": "node scripts/update_gitignore_for_tsc_output.js run",
     "format": "prettier --write \"{lib,bin,scripts,test}/**/*.js\"",
     "debug:test": "mocha --inspect-brk --exit -t 0 test/all-tests-suite.js",
     "debug:tape": "node --inspect-brk test/tape/index.js",
@@ -56,9 +56,7 @@
     "stress:init": "docker-compose -f scripts/stress-test/docker-compose.yml up --no-start && docker-compose -f scripts/stress-test/docker-compose.yml start",
     "stress:test": "node scripts/stress-test/knex-stress-test.js | grep -A 5 -B 60 -- '- STATS '",
     "stress:destroy": "docker-compose -f scripts/stress-test/docker-compose.yml stop",
-    "prepare": "husky install",
-    "prepublishOnly": "npm run clean && npm run build",
-    "postinstall": "npm run clean && npm run build"
+    "prepare": "husky install && npm run clean && npm run build"
   },
   "dependencies": {
     "colorette": "2.0.16",
@@ -101,8 +99,7 @@
   },
   "lint-staged": {
     "*.{js,json}": [
-      "prettier --write",
-      "git add"
+      "prettier --write"
     ]
   },
   "devDependencies": {
@@ -120,11 +117,11 @@
     "eslint-config-prettier": "^8.5.0",
     "eslint-plugin-import": "^2.26.0",
     "eslint-plugin-mocha-no-only": "^1.1.1",
-    "husky": "^7.0.4",
+    "husky": "^8.0.1",
     "jake": "^10.8.5",
     "JSONStream": "^1.3.5",
     "lint-staged": "^12.3.7",
-    "mocha": "^9.2.2",
+    "mocha": "^10.0.0",
     "mock-fs": "^5.1.2",
     "mysql": "^2.18.1",
     "mysql2": "^2.3.3",
@@ -201,9 +198,9 @@
     }
   ],
   "browser": {
-    "./lib/migrate/Migrator.js": "./lib/util/noop.js",
+    "./lib/migrations/migrate/Migrator.js": "./lib/util/noop.js",
     "./lib/bin/cli.js": "./lib/util/noop.js",
-    "./lib/seed/Seeder.js": "./lib/util/noop.js",
+    "./lib/migrations/seed/Seeder.js": "./lib/util/noop.js",
     "tedious": false,
     "mysql": false,
     "mysql2": false,
@@ -221,7 +218,8 @@
   },
   "files": [
     "bin/*",
-    "lib/*",
+    "lib/",
+    "lib/**/*.js",
     "!lib/**/*.ts",
     "!lib/**/*.d.ts",
     "!lib/**/*.js.map",
diff --git a/scripts/clean.js b/scripts/clean.js
new file mode 100755
index 00000000..fa1b7884
--- /dev/null
+++ b/scripts/clean.js
@@ -0,0 +1,29 @@
+#!/usr/bin/env node
+
+const fs = require('fs')
+const path = require('path')
+const { execSync } = require("child_process");
+
+function main() {
+  const repoDir = path.dirname(__dirname)
+  const gitDir = path.join(repoDir, '.git')
+  const gitDirExists = doesDirectoryExist(gitDir)
+  if (!gitDirExists) {
+    console.log("No .git directory detected so can not clean 'lib/'. Exiting.")
+    process.exit(0)
+  }
+  console.log("Cleaning 'lib/' of outputted files from Typescript compilation ...")
+  const cmd = 'git clean -f -X lib/'
+  const output = execSync(cmd, { cwd: repoDir })
+  console.log(output.toString('utf8'))
+  console.log('Done')
+}
+
+function doesDirectoryExist(p) {
+  if (fs.existsSync(p)) {
+    return fs.lstatSync(p).isDirectory()
+  }
+  return false
+}
+
+main()
diff --git a/types/index.d.ts b/types/index.d.ts
index 1c999f53..3b8ac2f3 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -384,6 +384,43 @@ export declare namespace knex {
     ): void;
   }
 
+  class TableBuilder {
+    static extend(
+      methodName: string,
+      fn: (
+        this: Knex.TableBuilder,
+        ...args: any[]
+      ) => Knex.TableBuilder
+    ): void;
+  }
+  class ViewBuilder {
+    static extend(
+      methodName: string,
+      fn: (
+        this: Knex.ViewBuilder,
+        ...args: any[]
+      ) => Knex.ViewBuilder
+    ): void;
+  }
+  class SchemaBuilder {
+    static extend(
+      methodName: string,
+      fn: (
+        this: Knex.SchemaBuilder,
+        ...args: any[]
+      ) => Knex.SchemaBuilder
+    ): void;
+  }
+  class ColumnBuilder {
+    static extend(
+      methodName: string,
+      fn: (
+        this: Knex.ColumnBuilder,
+        ...args: any[]
+      ) => Knex.ColumnBuilder
+    ): void;
+  }
+
   export class KnexTimeoutError extends Error {}
 
   export const Client: typeof Knex.Client;
@@ -2570,17 +2607,28 @@ export declare namespace Knex {
   interface PgConnectionConfig {
     user?: string;
     database?: string;
-    password?: string;
+    password?: string | (() => string | Promise<string>);
     port?: number;
     host?: string;
     connectionString?: string;
     keepAlive?: boolean;
     stream?: stream.Duplex;
     statement_timeout?: false | number;
-    connectionTimeoutMillis?: number;
-    keepAliveInitialDelayMillis?: number;
+    parseInputDatesAsUTC?: boolean;
     ssl?: boolean | ConnectionOptions;
+    query_timeout?: number;
+    keepAliveInitialDelayMillis?: number;
+    idle_in_transaction_session_timeout?: number;
     application_name?: string;
+    connectionTimeoutMillis?: number;
+    types?: PgCustomTypesConfig;
+    options?: string;
+  }
+
+  type PgGetTypeParser = (oid: number, format: string) => any;
+
+  interface PgCustomTypesConfig {
+      getTypeParser: PgGetTypeParser;
   }
 
   type RedshiftConnectionConfig = PgConnectionConfig;


Test patch:
diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index f1bab091..0d57eaa4 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -28,7 +28,7 @@ jobs:
           fetch-depth: 1
 
       - name: Setup Node ${{ matrix.node-version }}
-        uses: actions/setup-node@v3.1.1
+        uses: actions/setup-node@v3.2.0
         with:
           always-auth: false
           node-version: ${{ matrix.node-version }}
@@ -91,3 +91,29 @@ jobs:
             --file "scripts/docker-compose.yml" \
             down
         if: matrix.database-type != 'sqlite3' && matrix.database-type != 'better-sqlite3'
+
+  user_experience:
+    runs-on: ${{ matrix.os }}
+    name: Test user experience
+    strategy:
+      matrix:
+        node-version: [18.x, 16.x, 14.x, 12.x]
+        os: ['ubuntu-latest', 'macos-latest', 'windows-latest']
+    steps:
+      - name: Checkout Repository
+        uses: actions/checkout@v3
+        with: { fetch-depth: 1 }
+
+      - name: Setup Node ${{ matrix.node-version }}
+        uses: actions/setup-node@v3.2.0
+        with:
+          always-auth: false
+          node-version: ${{ matrix.node-version }}
+      
+      - name: Test npm git dependency
+        shell: bash
+        run: |
+          mkdir TMP/
+          echo "{ \"dependencies\": { \"knex\": \"git+file://$(pwd)/\" } }" > TMP/package.json
+          cd TMP/
+          npm install --verbose
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index 47053054..200efa32 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -26,7 +26,7 @@ jobs:
           fetch-depth: 1
 
       - name: Setup Node ${{ matrix.node-version }}
-        uses: actions/setup-node@v3.1.1
+        uses: actions/setup-node@v3.2.0
         with:
           always-auth: false
           node-version: ${{ matrix.node-version }}
diff --git a/test/db-less-test-suite.js b/test/db-less-test-suite.js
index ed5a5935..5dd92807 100644
--- a/test/db-less-test-suite.js
+++ b/test/db-less-test-suite.js
@@ -20,6 +20,7 @@ describe('Query Building Tests', function () {
   require('./unit/query/string');
   require('./unit/schema-builder/mysql')('mysql');
   require('./unit/schema-builder/mysql')('mysql2');
+  require('./unit/schema-builder/extensions');
   require('./unit/schema-builder/postgres');
   require('./unit/schema-builder/redshift');
   require('./unit/schema-builder/sqlite3');
diff --git a/test/integration2/dialects/oracledb.spec.js b/test/integration2/dialects/oracledb.spec.js
new file mode 100644
index 00000000..8d747715
--- /dev/null
+++ b/test/integration2/dialects/oracledb.spec.js
@@ -0,0 +1,46 @@
+const { expect } = require('chai');
+const { getAllDbs, getKnexForDb } = require('../util/knex-instance-provider');
+
+describe('Oracledb dialect', () => {
+  describe('Connection configuration', () => {
+    const dbs = getAllDbs().filter((db) => db === 'oracledb');
+
+    dbs.forEach((db) => {
+      describe(db, () => {
+        let knex;
+        before(() => {
+          knex = getKnexForDb(db, {
+            connection: {
+              user: 'user',
+              password: 'password',
+              connectString: 'connect-string',
+              externalAuth: true,
+              host: 'host',
+              database: 'database',
+            },
+          });
+        });
+
+        after(() => {
+          return knex.destroy();
+        });
+
+        describe('#4869 inserting Buffer', async () => {
+          it('.toSQL().toNative() generates correct sql and bindings for INSERT of Buffer', async () => {
+            const b = Buffer.from('hello', 'utf-8')
+            const query = knex('table1').insert({ value: b })
+            const queryObj = query.toSQL().toNative()
+            // Ensure we have two bindings, before fix for #4869 there would only have been one due
+            // to silent dropping of the Buffer.
+            expect(queryObj.bindings.length).to.eql(2)
+            expect(queryObj).to.eql({
+              sql: 'insert into "table1" ("value") values (:1) returning "value" into :2',
+              bindings: [b, { type: 2019, dir: 3003 }],
+            });
+          });
+        });
+
+      });
+    });
+  });
+});
diff --git a/test/integration2/query/misc/additional.spec.js b/test/integration2/query/misc/additional.spec.js
index 30c92371..913ee7d8 100644
--- a/test/integration2/query/misc/additional.spec.js
+++ b/test/integration2/query/misc/additional.spec.js
@@ -423,6 +423,27 @@ describe('Additional', function () {
           expect(expectedUuid).to.equal(originalUuid);
         });
 
+        it ('#5154 - should properly mark COLUMN_DEFAULT as null', async function () {
+          if (!isMysql(knex)) {
+            return this.skip();
+          }
+
+          await knex.schema.dropTableIfExists('null_col');
+          await knex.schema.createTable('null_col', function (table) {
+            table.date('foo').defaultTo(null).nullable();
+          })
+          const columnInfo = await knex('null_col').columnInfo();
+
+          expect(columnInfo).to.deep.equal({
+            foo: {
+              type: 'date',
+              maxLength: null,
+              nullable: true,
+              defaultValue: null,
+            },
+          });
+        });
+
         it('#2184 - should properly escape table name for SQLite columnInfo', async function () {
           if (!isSQLite(knex)) {
             return this.skip();
diff --git a/test/integration2/schema/misc.spec.js b/test/integration2/schema/misc.spec.js
index 3f754faa..0a7dfceb 100644
--- a/test/integration2/schema/misc.spec.js
+++ b/test/integration2/schema/misc.spec.js
@@ -1597,7 +1597,7 @@ describe('Schema (misc)', () => {
       });
 
       describe('hasTable', () => {
-        it('checks whether a table exists', () =>
+        it('should be true if a table exists', () =>
           knex.schema.hasTable('test_table_two').then((resp) => {
             expect(resp).to.equal(true);
           }));
@@ -1663,7 +1663,7 @@ describe('Schema (misc)', () => {
 
           describe('sqlite and mysql only', () => {
             it('checks whether a column exists without being case sensitive, resolving with a boolean', async function () {
-              if (!isSQLite(knex)&& !isMysql(knex)) {
+              if (!isSQLite(knex) && !isMysql(knex)) {
                 return this.skip();
               }
 
@@ -2046,7 +2046,7 @@ describe('Schema (misc)', () => {
 
       describe('withSchema', () => {
         describe('mssql only', () => {
-          if (!knex || !knex.client || !/mssql/i.test(knex.client.dialect)) {
+          if (!isMssql(knex)) {
             return Promise.resolve(true);
           }
 
@@ -2085,10 +2085,13 @@ describe('Schema (misc)', () => {
           }
 
           const defaultSchemaName = 'public';
-          const testSchemaName = 'test';
+          const testSchemaName = 'test_schema';
 
           before(() => createSchema(testSchemaName));
 
+          afterEach(() =>
+            knex.schema.withSchema(testSchemaName).dropTableIfExists('test')
+          );
           after(() => knex.schema.raw('DROP SCHEMA ' + testSchemaName));
 
           it('should not find non-existent tables', () =>
@@ -2096,6 +2099,11 @@ describe('Schema (misc)', () => {
               checkTable(defaultSchemaName, 'test', false)
             ));
 
+          it('should find existent tables', () =>
+            createTable(testSchemaName, 'test').then(() =>
+              checkTable(testSchemaName, 'test', true)
+            ));
+
           it('should create and drop tables', () =>
             createTable(testSchemaName, 'test')
               .then(() => checkColumn(testSchemaName, 'test'))
diff --git a/test/unit/schema-builder/extensions.js b/test/unit/schema-builder/extensions.js
new file mode 100644
index 00000000..d300b3dd
--- /dev/null
+++ b/test/unit/schema-builder/extensions.js
@@ -0,0 +1,52 @@
+'use strict';
+
+const expect = require('chai').expect;
+const MySQL_Client = require('../../../lib/dialects/mysql');
+const knex = require('../../../knex');
+describe('SchemaBuilder Extensions', () => {
+  const client = new MySQL_Client({ client: 'mysql' });
+
+  const equal = require('assert').equal;
+  const functionToExtend = () => true;
+  knex.SchemaBuilder.extend('tryCreateTable', functionToExtend);
+  knex.TableBuilder.extend('testCol', functionToExtend);
+  knex.ColumnBuilder.extend('testCol', functionToExtend);
+  knex.ViewBuilder.extend('testCol', functionToExtend);
+
+  it('schemabuilder has method', () => {
+    equal(client.schemaBuilder().tryCreateTable, functionToExtend);
+    expect(client.schemaBuilder().tryCreateTable()).to.be.true;
+  });
+
+  it('tablebuilder has method', () => {
+    client
+      .schemaBuilder()
+      .createTable('users', function (table) {
+        equal(table.testCol, functionToExtend);
+      })
+      .toSQL();
+  });
+
+  it('columnbuilder has method', () => {
+    client
+      .schemaBuilder()
+      .createTable('users', function (table) {
+        equal(table.string('t').testCol, functionToExtend);
+      })
+      .toSQL();
+  });
+
+  it('viewbuilder has method', () => {
+    client
+      .schemaBuilder()
+      .createView('users', function (view) {
+        equal(view.testCol, functionToExtend);
+        view.as(
+          knex('users')
+            .join('user_roles', 'users.user_id', 'user_roles.user_id')
+            .select('users.*', 'user_roles.role_code')
+        );
+      })
+      .toSQL();
+  });
+});
diff --git a/test/unit/schema-builder/mssql.js b/test/unit/schema-builder/mssql.js
index 87f85669..d33200f9 100644
--- a/test/unit/schema-builder/mssql.js
+++ b/test/unit/schema-builder/mssql.js
@@ -511,9 +511,9 @@ describe('MSSQL SchemaBuilder', function () {
 
     equal(1, tableSql.length);
     expect(tableSql[0].sql).to.equal(
-      'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ?'
+      'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ? AND TABLE_SCHEMA = ?'
     );
-    expect(tableSql[0].bindings[0]).to.equal('schema.users');
+    expect(tableSql[0].bindings[0]).to.equal('users');
   });
 
   it('test rename table with schema', function () {