Repository: knex/knex
Pull request #4602 (2021-08-02 17:45:02)

Fix Analytic orderBy and partitionBy to follow the SQL documentation
This is a little annoyance I found while writing query calls using `rowNumber` as the typescript types shows you are allowed to do this but if you try to use an array of objects the SQL request fail with `column ?? does not exist` where ?? is the array of objects columnized.


Patch:
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 0fcd71be..d2792397 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -24,7 +24,7 @@ jobs:
           fetch-depth: 1
 
       - name: Setup Node ${{ matrix.node-version }}
-        uses: actions/setup-node@v2.3.0
+        uses: actions/setup-node@v2.4.0
         with:
           always-auth: false
           node-version: ${{ matrix.node-version }}
diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml
index 5baf0d04..3570bf03 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@v2.3.0
+        uses: actions/setup-node@v2.4.0
         with:
           always-auth: false
           node-version: ${{ matrix.node-version }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 130ad4c4..81ade8e6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 # Master (Unreleased)
 
+# 0.95.10 - 20 August, 2021
+
+### Improvements:
+
+- Use sys info function instead of connection db name #4623
+
+### Typings:
+
+- Deferrable and withkeyName should not be in ColumnBuilder #4600
+
 # 0.95.9 - 31 July, 2021
 
 ### New features:
diff --git a/ECOSYSTEM.md b/ECOSYSTEM.md
index b0cb5ec3..a137b1bd 100644
--- a/ECOSYSTEM.md
+++ b/ECOSYSTEM.md
@@ -9,7 +9,9 @@ Plugins and tools maintained by the knex team are listed under [Core](#core) whi
 
 #### [Community](#community)
 - [`knemm`](https://github.com/asteinarson/knemm) A CLI tool to manage SQL schema in declarative YAML files (`claims` and `states`). It allows for database dependencies between loosely coupled modules. 
+- [`knex-mock-client`](https://github.com/felixmosh/knex-mock-client) A mock client which helps you write integration tests which includes DB interactions.
 - [`knex-paginate`](https://github.com/felixmosh/knex-paginate) Extension of the query builder with `paginate` method that helps with pagination tasks.
 - [`knex-postgis`](https://github.com/jfgodoy/knex-postgis) Extension of Knex with [PostGIS](https://postgis.net/) (geographic objects in PostgreSQL) support.
 - [`knex-serverless-mysql`](https://github.com/MatissJanis/knex-serverless-mysql) Knex dialect for usage with [`serverless-mysql`](https://github.com/jeremydaly/serverless-mysql): persist database connection over multiple serverless (such as AWS Lambda) function invocations.
 - [`pg-mem`](https://github.com/oguimbal/pg-mem) An in-memory emulation of PostgreSQL instance, which can be used for writing blazingly fast tests. Has [adapter for Knex](https://github.com/oguimbal/pg-mem/wiki/Libraries-adapters#-knex).
+- [`sqlcommenter-knex`](https://github.com/google/sqlcommenter/tree/master/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-knex) A [sqlcommenter](https://google.github.io/sqlcommenter/) plugin with [support for Knex](https://google.github.io/sqlcommenter/node/knex/) to augment SQL statements with comments that can be used later to correlate application code with SQL statements.
diff --git a/lib/dialects/mssql/mssql-formatter.js b/lib/dialects/mssql/mssql-formatter.js
index ba02076c..354afef0 100644
--- a/lib/dialects/mssql/mssql-formatter.js
+++ b/lib/dialects/mssql/mssql-formatter.js
@@ -12,6 +12,23 @@ class MSSQL_Formatter extends Formatter {
     }
     return str;
   }
+
+  /**
+   * Returns its argument with single quotes escaped, so it can be included into a single-quoted string.
+   *
+   * For example, it converts "has'quote" to "has''quote".
+   *
+   * This assumes QUOTED_IDENTIFIER ON so it is only ' that need escaping,
+   * never ", because " cannot be used to quote a string when that's on;
+   * otherwise we'd need to be aware of whether the string is quoted with " or '.
+   *
+   * This assumption is consistent with the SQL Knex generates.
+   * @param {string} string
+   * @returns {string}
+   */
+  escapingStringDelimiters(string) {
+    return (string || '').replace(/'/g, "''");
+  }
 }
 
 module.exports = MSSQL_Formatter;
diff --git a/lib/dialects/mssql/query/mssql-querycompiler.js b/lib/dialects/mssql/query/mssql-querycompiler.js
index 82a9be6a..2ea5b3fb 100644
--- a/lib/dialects/mssql/query/mssql-querycompiler.js
+++ b/lib/dialects/mssql/query/mssql-querycompiler.js
@@ -31,6 +31,30 @@ class QueryCompiler_MSSQL extends QueryCompiler {
     this._emptyInsertValue = 'default values';
   }
 
+  with() {
+    // WITH RECURSIVE is a syntax error:
+    // SQL Server does not syntactically distinguish recursive and non-recursive CTEs.
+    // So mark all statements as non-recursive, generate the SQL, then restore.
+    // This approach ensures any changes in base class with() get propagated here.
+    const undoList = [];
+    if (this.grouped.with) {
+      for (const stmt of this.grouped.with) {
+        if (stmt.recursive) {
+          undoList.push(stmt);
+          stmt.recursive = false;
+        }
+      }
+    }
+
+    const result = super.with();
+
+    // Restore the recursive markings, in case this same query gets cloned and passed to other drivers.
+    for (const stmt of undoList) {
+      stmt.recursive = true;
+    }
+    return result;
+  }
+
   select() {
     const sql = this.with();
     const statements = components.map((component) => this[component](this));
@@ -429,7 +453,8 @@ class QueryCompiler_MSSQL extends QueryCompiler {
       schema = this.client.customWrapIdentifier(schema, identity);
     }
 
-    let sql = `select [COLUMN_NAME], [COLUMN_DEFAULT], [DATA_TYPE], [CHARACTER_MAXIMUM_LENGTH], [IS_NULLABLE] from information_schema.columns where table_name = ? and table_catalog = ?`;
+    // GOTCHA: INFORMATION_SCHEMA.COLUMNS must be capitalized to work when the database has a case-sensitive collation. [#4573]
+    let sql = `select [COLUMN_NAME], [COLUMN_DEFAULT], [DATA_TYPE], [CHARACTER_MAXIMUM_LENGTH], [IS_NULLABLE] from INFORMATION_SCHEMA.COLUMNS where table_name = ? and table_catalog = ?`;
     const bindings = [table, this.client.database()];
 
     if (schema) {
diff --git a/lib/dialects/mssql/schema/mssql-columncompiler.js b/lib/dialects/mssql/schema/mssql-columncompiler.js
index 06b59efe..87941d0f 100644
--- a/lib/dialects/mssql/schema/mssql-columncompiler.js
+++ b/lib/dialects/mssql/schema/mssql-columncompiler.js
@@ -92,7 +92,11 @@ class ColumnCompiler_MSSQL extends ColumnCompiler {
     )} DEFAULT ${formattedValue}`;
   }
 
-  comment(comment) {
+  comment(/** @type {string} */ comment) {
+    if (!comment) {
+      return;
+    }
+
     // XXX: This is a byte limit, not character, so we cannot definitively say they'll exceed the limit without database collation info.
     // (Yes, even if the column has its own collation, the sqlvariant still uses the database collation.)
     // I'm not sure we even need to raise a warning, as MSSQL will return an error when the limit is exceeded itself.
@@ -101,6 +105,25 @@ class ColumnCompiler_MSSQL extends ColumnCompiler {
         'Your comment might be longer than the max comment length for MSSQL of 7,500 bytes.'
       );
     }
+
+    // See: https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-addextendedproperty-transact-sql?view=sql-server-ver15#b-adding-an-extended-property-to-a-column-in-a-table
+    const value = this.formatter.escapingStringDelimiters(comment);
+    const level0name = this.tableCompiler.schemaNameRaw || 'dbo';
+    const level1name = this.formatter.escapingStringDelimiters(
+      this.tableCompiler.tableNameRaw
+    );
+    const level2name = this.formatter.escapingStringDelimiters(
+      this.args[0] || this.defaults('columnName')
+    );
+
+    const args = `N'MS_Description', N'${value}', N'Schema', N'${level0name}', N'Table', N'${level1name}', N'Column', N'${level2name}'`;
+
+    this.pushAdditional(function () {
+      const isAlreadyDefined = `EXISTS(SELECT * FROM sys.fn_listextendedproperty(N'MS_Description', N'Schema', N'${level0name}', N'Table', N'${level1name}', N'Column', N'${level2name}'))`;
+      this.pushQuery(
+        `IF ${isAlreadyDefined}\n  EXEC sys.sp_updateextendedproperty ${args}\nELSE\n  EXEC sys.sp_addextendedproperty ${args}`
+      );
+    });
     return '';
   }
 }
diff --git a/lib/dialects/mssql/schema/mssql-tablecompiler.js b/lib/dialects/mssql/schema/mssql-tablecompiler.js
index 84ed890f..8e3546de 100644
--- a/lib/dialects/mssql/schema/mssql-tablecompiler.js
+++ b/lib/dialects/mssql/schema/mssql-tablecompiler.js
@@ -24,16 +24,39 @@ class TableCompiler_MSSQL extends TableCompiler {
       columns.sql.join(this._formatting ? ',\n    ' : ', ') +
       ')';
 
+    this.pushQuery(sql);
+
     if (this.single.comment) {
-      const { comment } = this.single;
-      // XXX: This is a byte limit, not character, so we cannot definitively say they'll exceed the limit without database collation info.
-      if (comment.length > 7500 / 2)
-        this.client.logger.warn(
-          'Your comment might be longer than the max comment length for MSSQL of 7,500 bytes.'
-        );
+      this.comment(this.single.comment);
     }
+  }
 
-    this.pushQuery(sql);
+  comment(/** @type {string} */ comment) {
+    if (!comment) {
+      return;
+    }
+
+    // XXX: This is a byte limit, not character, so we cannot definitively say they'll exceed the limit without server collation info.
+    // When I checked in SQL Server 2019, the ctext column in sys.syscomments is defined as a varbinary(8000), so it doesn't even have its own defined collation.
+    if (comment.length > 7500 / 2) {
+      this.client.logger.warn(
+        'Your comment might be longer than the max comment length for MSSQL of 7,500 bytes.'
+      );
+    }
+
+    // See: https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-addextendedproperty-transact-sql?view=sql-server-ver15#f-adding-an-extended-property-to-a-table
+    const value = this.formatter.escapingStringDelimiters(comment);
+    const level0name = this.formatter.escapingStringDelimiters(
+      this.schemaNameRaw || 'dbo'
+    );
+    const level1name = this.formatter.escapingStringDelimiters(
+      this.tableNameRaw
+    );
+    const args = `N'MS_Description', N'${value}', N'Schema', N'${level0name}', N'Table', N'${level1name}'`;
+    const isAlreadyDefined = `EXISTS(SELECT * FROM sys.fn_listextendedproperty(N'MS_Description', N'Schema', N'${level0name}', N'Table', N'${level1name}', NULL, NULL))`;
+    this.pushQuery(
+      `IF ${isAlreadyDefined}\n  EXEC sys.sp_updateextendedproperty ${args}\nELSE\n  EXEC sys.sp_addextendedproperty ${args}`
+    );
   }
 
   // Compiles column add.  Multiple columns need only one ADD clause (not one ADD per column) so core addColumns doesn't work.  #1348
@@ -129,9 +152,6 @@ class TableCompiler_MSSQL extends TableCompiler {
     );
   }
 
-  // Compiles the comment on the table.
-  comment() {}
-
   changeType() {}
 
   // Renames a column on the table.
diff --git a/lib/dialects/postgres/query/pg-querycompiler.js b/lib/dialects/postgres/query/pg-querycompiler.js
index 99142654..6b249d64 100644
--- a/lib/dialects/postgres/query/pg-querycompiler.js
+++ b/lib/dialects/postgres/query/pg-querycompiler.js
@@ -186,8 +186,8 @@ class QueryCompiler_PG extends QueryCompiler {
     }
 
     let sql =
-      'select * from information_schema.columns where table_name = ? and table_catalog = ?';
-    const bindings = [table, this.client.database()];
+      'select * from information_schema.columns where table_name = ? and table_catalog = current_database()';
+    const bindings = [table];
 
     if (schema) {
       sql += ' and table_schema = ?';
diff --git a/lib/dialects/postgres/schema/pg-tablecompiler.js b/lib/dialects/postgres/schema/pg-tablecompiler.js
index bf8b3fe9..89385ca3 100644
--- a/lib/dialects/postgres/schema/pg-tablecompiler.js
+++ b/lib/dialects/postgres/schema/pg-tablecompiler.js
@@ -21,6 +21,16 @@ class TableCompiler_PG extends TableCompiler {
     });
   }
 
+  _setNullableState(column, isNullable) {
+    const constraintAction = isNullable ? 'drop not null' : 'set not null';
+    const sql = `alter table ${this.tableName()} alter column ${this.formatter.wrap(
+      column
+    )} ${constraintAction}`;
+    return this.pushQuery({
+      sql: sql,
+    });
+  }
+
   compileAdd(builder) {
     const table = this.formatter.wrap(builder);
     const columns = this.prefixArray('add column', this.getColumns(builder));
diff --git a/lib/dialects/sqlite3/schema/ddl.js b/lib/dialects/sqlite3/schema/ddl.js
index a160a005..5aaf5694 100644
--- a/lib/dialects/sqlite3/schema/ddl.js
+++ b/lib/dialects/sqlite3/schema/ddl.js
@@ -4,19 +4,12 @@
 // columns and changing datatypes.
 // -------
 
-const find = require('lodash/find');
-const fromPairs = require('lodash/fromPairs');
-const isEmpty = require('lodash/isEmpty');
-const negate = require('lodash/negate');
-const omit = require('lodash/omit');
 const identity = require('lodash/identity');
 const { nanonum } = require('../../../util/nanoid');
-const { COMMA_NO_PAREN_REGEX } = require('../../../constants');
 const {
   createNewTable,
-  copyAllData,
-  dropOriginal,
   copyData,
+  dropOriginal,
   renameTable,
   getTableSql,
 } = require('./internal/sqlite-ddl-operations');
@@ -25,6 +18,7 @@ const {
   compileCreateTable,
   compileCreateIndex,
 } = require('./internal/compiler');
+const { isEqualId, includesId } = require('./internal/utils');
 
 // So altering the schema in SQLite3 is a major pain.
 // We have our own object to deal with the renaming and altering the types
@@ -46,20 +40,6 @@ class SQLite3_DDL {
     return this.formatter(this.tableNameRaw);
   }
 
-  async getColumn(column) {
-    const currentCol = find(this.pragma, (col) => {
-      return (
-        this.client.wrapIdentifier(col.name).toLowerCase() ===
-        this.client.wrapIdentifier(column).toLowerCase()
-      );
-    });
-    if (!currentCol)
-      throw new Error(
-        `The column ${column} is not in the ${this.tableName()} table`
-      );
-    return currentCol;
-  }
-
   getTableSql() {
     const tableName = this.tableName();
 
@@ -83,16 +63,8 @@ class SQLite3_DDL {
     return this.trx.raw(dropOriginal(this.tableName()));
   }
 
-  async copyData(iterator) {
-    const commands = await copyData(
-      this.trx,
-      iterator,
-      this.tableName(),
-      this.alteredName
-    );
-    for (const command of commands) {
-      await this.trx.raw(command);
-    }
+  copyData(columns) {
+    return this.trx.raw(copyData(this.tableName(), this.alteredName, columns));
   }
 
   createNewTable(sql) {
@@ -101,147 +73,7 @@ class SQLite3_DDL {
     );
   }
 
-  _doReplace(sql, from, to) {
-    const oneLineSql = sql.replace(/\s+/g, ' ');
-    const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
-
-    const tableName = matched[1];
-    const defs = matched[2];
-
-    if (!defs) {
-      throw new Error('No column definitions in this statement!');
-    }
-
-    let parens = 0,
-      args = [],
-      ptr = 0;
-    let i = 0;
-    const x = defs.length;
-    for (i = 0; i < x; i++) {
-      switch (defs[i]) {
-        case '(':
-          parens++;
-          break;
-        case ')':
-          parens--;
-          break;
-        case ',':
-          if (parens === 0) {
-            args.push(defs.slice(ptr, i));
-            ptr = i + 1;
-          }
-          break;
-        case ' ':
-          if (ptr === i) {
-            ptr = i + 1;
-          }
-          break;
-      }
-    }
-    args.push(defs.slice(ptr, i));
-
-    const fromIdentifier = from.replace(/[`"'[\]]/g, '');
-
-    args = args.map((item) => {
-      let split = item.trim().split(' ');
-
-      // SQLite supports all quoting mechanisms prevalent in all major dialects of SQL
-      // and preserves the original quoting in sqlite_master.
-      //
-      // Also, identifiers are never case sensitive, not even when quoted.
-      //
-      // Ref: https://www.sqlite.org/lang_keywords.html
-      const fromMatchCandidates = [
-        new RegExp(`\`${fromIdentifier}\``, 'i'),
-        new RegExp(`"${fromIdentifier}"`, 'i'),
-        new RegExp(`'${fromIdentifier}'`, 'i'),
-        new RegExp(`\\[${fromIdentifier}\\]`, 'i'),
-      ];
-      if (fromIdentifier.match(/^\S+$/)) {
-        fromMatchCandidates.push(new RegExp(`\\b${fromIdentifier}\\b`, 'i'));
-      }
-
-      const doesMatchFromIdentifier = (target) =>
-        fromMatchCandidates.some((c) => target.match(c));
-
-      const replaceFromIdentifier = (target) =>
-        fromMatchCandidates.reduce(
-          (result, candidate) => result.replace(candidate, to),
-          target
-        );
-
-      if (doesMatchFromIdentifier(split[0])) {
-        // column definition
-        if (to) {
-          split[0] = to;
-          return split.join(' ');
-        }
-        return ''; // for deletions
-      }
-
-      // skip constraint name
-      const idx = /constraint/i.test(split[0]) ? 2 : 0;
-
-      // primary key and unique constraints have one or more
-      // columns from this table listed between (); replace
-      // one if it matches
-      if (/primary|unique/i.test(split[idx])) {
-        const ret = item.replace(/\(.*\)/, replaceFromIdentifier);
-        // If any member columns are dropped then uniqueness/pk constraint
-        // can not be retained
-        if (ret !== item && isEmpty(to)) return '';
-        return ret;
-      }
-
-      // foreign keys have one or more columns from this table
-      // listed between (); replace one if it matches
-      // foreign keys also have a 'references' clause
-      // which may reference THIS table; if it does, replace
-      // column references in that too!
-      if (/foreign/.test(split[idx])) {
-        split = item.split(/ references /i);
-        // the quoted column names save us from having to do anything
-        // other than a straight replace here
-        const replacedKeySpec = replaceFromIdentifier(split[0]);
-
-        if (split[0] !== replacedKeySpec) {
-          // If we are removing one or more columns of a foreign
-          // key, then we should not retain the key at all
-          if (isEmpty(to)) return '';
-          else split[0] = replacedKeySpec;
-        }
-
-        if (split[1].slice(0, tableName.length) === tableName) {
-          // self-referential foreign key
-          const replacedKeyTargetSpec = split[1].replace(
-            /\(.*\)/,
-            replaceFromIdentifier
-          );
-          if (split[1] !== replacedKeyTargetSpec) {
-            // If we are removing one or more columns of a foreign
-            // key, then we should not retain the key at all
-            if (isEmpty(to)) return '';
-            else split[1] = replacedKeyTargetSpec;
-          }
-        }
-        return split.join(' references ');
-      }
-
-      return item;
-    });
-
-    args = args.filter(negate(isEmpty));
-
-    if (args.length === 0) {
-      throw new Error('Unable to drop last column from table');
-    }
-
-    return oneLineSql
-      .replace(/\(.*\)/, () => `(${args.join(', ')})`)
-      .replace(/,\s*([,)])/, '$1');
-  }
-
-  async alterColumn(columns) {
+  alterColumn(columns) {
     return this.client.transaction(
       async (trx) => {
         this.trx = trx;
@@ -251,7 +83,9 @@ class SQLite3_DDL {
         const parsedTable = parseCreateTable(createTable);
 
         parsedTable.columns = parsedTable.columns.map((column) => {
-          const newColumnInfo = columns.find((c) => c.name === column.name);
+          const newColumnInfo = columns.find((c) =>
+            isEqualId(c.name, column.name)
+          );
 
           if (newColumnInfo) {
             column.type = newColumnInfo.type;
@@ -279,62 +113,84 @@ class SQLite3_DDL {
 
         const newTable = compileCreateTable(parsedTable, this.wrap);
 
-        return this.alter(newTable, createIndices, (row) => {
-          return row;
-        });
+        return this.alter(newTable, createIndices);
       },
       { connection: this.connection }
     );
   }
 
-  async dropColumn(columns) {
+  dropColumn(columns) {
     return this.client.transaction(
-      (trx) => {
+      async (trx) => {
         this.trx = trx;
-        return Promise.all(columns.map((column) => this.getColumn(column)))
-          .then(() => this.getTableSql())
-          .then(({ createTable, createIndices }) => {
-            let newSql = createTable.slice();
-            columns.forEach((column) => {
-              const a = this.client.wrapIdentifier(column);
-              newSql = this._doReplace(newSql, a, '');
-            });
-            if (createTable === newSql) {
-              throw new Error('Unable to find the column to change');
-            }
-            const mappedColumns = Object.keys(
-              this.client.postProcessResponse(
-                fromPairs(columns.map((column) => [column, column]))
-              )
-            );
-
-            const newIndices = [];
-            for (const createIndex of createIndices) {
-              const parsedIndex = parseCreateIndex(createIndex);
-
-              parsedIndex.columns = parsedIndex.columns.filter(
-                (newColumn) =>
-                  newColumn.expression ||
-                  !columns.some(
-                    (column) => newColumn.name === this.formatter(column)
-                  )
-              );
 
-              if (parsedIndex.columns.length > 0) {
-                newIndices.push(compileCreateIndex(parsedIndex, this.wrap));
-              }
+        const { createTable, createIndices } = await this.getTableSql();
+
+        const parsedTable = parseCreateTable(createTable);
+
+        parsedTable.columns = parsedTable.columns.filter(
+          (parsedColumn) =>
+            parsedColumn.expression || !includesId(columns, parsedColumn.name)
+        );
+
+        if (parsedTable.columns.length === 0) {
+          throw new Error('Unable to drop last column from table');
+        }
+
+        parsedTable.constraints = parsedTable.constraints.filter(
+          (constraint) => {
+            if (
+              constraint.type === 'PRIMARY KEY' ||
+              constraint.type === 'UNIQUE'
+            ) {
+              return constraint.columns.every(
+                (constraintColumn) =>
+                  constraintColumn.expression ||
+                  !includesId(columns, constraintColumn.name)
+              );
+            } else if (constraint.type === 'FOREIGN KEY') {
+              return (
+                constraint.columns.every(
+                  (constraintColumnName) =>
+                    !includesId(columns, constraintColumnName)
+                ) &&
+                (constraint.references.table !== parsedTable.table ||
+                  constraint.references.columns.every(
+                    (referenceColumnName) =>
+                      !includesId(columns, referenceColumnName)
+                  ))
+              );
+            } else {
+              return true;
             }
+          }
+        );
+
+        const newColumns = parsedTable.columns.map((column) => column.name);
 
-            return this.alter(newSql, newIndices, (row) =>
-              omit(row, ...mappedColumns)
-            );
-          });
+        const newTable = compileCreateTable(parsedTable, this.wrap);
+
+        const newIndices = [];
+        for (const createIndex of createIndices) {
+          const parsedIndex = parseCreateIndex(createIndex);
+
+          parsedIndex.columns = parsedIndex.columns.filter(
+            (parsedColumn) =>
+              parsedColumn.expression || !includesId(columns, parsedColumn.name)
+          );
+
+          if (parsedIndex.columns.length > 0) {
+            newIndices.push(compileCreateIndex(parsedIndex, this.wrap));
+          }
+        }
+
+        return this.alter(newTable, newIndices, newColumns);
       },
       { connection: this.connection }
     );
   }
 
-  async dropForeign(columns, foreignKeyName) {
+  dropForeign(columns, foreignKeyName) {
     return this.client.transaction(
       async (trx) => {
         this.trx = trx;
@@ -346,7 +202,7 @@ class SQLite3_DDL {
         if (!foreignKeyName) {
           parsedTable.columns = parsedTable.columns.map((column) => ({
             ...column,
-            references: columns.includes(column.name)
+            references: includesId(columns, column.name)
               ? null
               : column.references,
           }));
@@ -354,181 +210,143 @@ class SQLite3_DDL {
 
         parsedTable.constraints = parsedTable.constraints.filter(
           (constraint) => {
-            if (foreignKeyName) {
-              return constraint.name !== foreignKeyName;
-            }
+            if (constraint.type === 'FOREIGN KEY') {
+              if (foreignKeyName) {
+                return (
+                  !constraint.name ||
+                  !isEqualId(constraint.name, foreignKeyName)
+                );
+              }
 
-            return (
-              constraint.columns.some((column) => columns.includes(column)) ===
-              false
-            );
+              return constraint.columns.every(
+                (constraintColumnName) =>
+                  !includesId(columns, constraintColumnName)
+              );
+            } else {
+              return true;
+            }
           }
         );
 
         const newTable = compileCreateTable(parsedTable, this.wrap);
 
-        return this.alter(newTable, createIndices, (row) => {
-          return row;
-        });
+        return this.alter(newTable, createIndices);
       },
       { connection: this.connection }
     );
   }
 
-  async dropPrimary(constraintName) {
+  dropPrimary(constraintName) {
     return this.client.transaction(
       async (trx) => {
         this.trx = trx;
 
         const { createTable, createIndices } = await this.getTableSql();
 
-        const oneLineSql = createTable.replace(/\s+/g, ' ');
-        const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
-
-        const defs = matched[2];
+        const parsedTable = parseCreateTable(createTable);
 
-        if (!defs) {
-          throw new Error('No column definitions in this statement!');
-        }
+        parsedTable.columns = parsedTable.columns.map((column) => ({
+          ...column,
+          primary: null,
+        }));
 
-        const updatedDefs = defs
-          .split(COMMA_NO_PAREN_REGEX)
-          .map((line) => line.trim())
-          .filter((defLine) => {
-            if (
-              defLine.startsWith('constraint') === false &&
-              defLine.includes('primary key') === false
-            )
-              return true;
-
-            if (constraintName) {
-              if (defLine.includes(constraintName)) return false;
-              return true;
+        parsedTable.constraints = parsedTable.constraints.filter(
+          (constraint) => {
+            if (constraint.type === 'PRIMARY KEY') {
+              if (constraintName) {
+                return (
+                  !constraint.name ||
+                  !isEqualId(constraint.name, constraintName)
+                );
+              } else {
+                return false;
+              }
             } else {
               return true;
             }
-          })
-          .join(', ');
+          }
+        );
 
-        const newSql = oneLineSql.replace(defs, updatedDefs);
+        const newTable = compileCreateTable(parsedTable, this.wrap);
 
-        return this.alter(newSql, createIndices, (row) => {
-          return row;
-        });
+        return this.alter(newTable, createIndices);
       },
       { connection: this.connection }
     );
   }
 
-  async primary(columns, constraintName) {
+  primary(columns, constraintName) {
     return this.client.transaction(
       async (trx) => {
         this.trx = trx;
 
         const { createTable, createIndices } = await this.getTableSql();
 
-        const oneLineSql = createTable.replace(/\s+/g, ' ');
-        const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
-
-        const columnDefinitions = matched[2];
+        const parsedTable = parseCreateTable(createTable);
 
-        if (!columnDefinitions) {
-          throw new Error('No column definitions in this statement!');
-        }
+        parsedTable.columns = parsedTable.columns.map((column) => ({
+          ...column,
+          primary: null,
+        }));
 
-        const primaryKeyDef = `primary key(${columns.join(',')})`;
-        const constraintDef = constraintName
-          ? `constraint ${constraintName} ${primaryKeyDef}`
-          : primaryKeyDef;
-
-        const newColumnDefinitions = [
-          ...columnDefinitions
-            .split(COMMA_NO_PAREN_REGEX)
-            .map((line) => line.trim())
-            .filter((line) => line.startsWith('primary') === false)
-            .map((line) => line.replace(/primary key/i, '')),
-          constraintDef,
-        ].join(', ');
-
-        const newSQL = oneLineSql.replace(
-          columnDefinitions,
-          newColumnDefinitions
+        parsedTable.constraints = parsedTable.constraints.filter(
+          (constraint) => constraint.type !== 'PRIMARY KEY'
         );
 
-        return this.alter(newSQL, createIndices, (row) => {
-          return row;
+        parsedTable.constraints.push({
+          type: 'PRIMARY KEY',
+          name: constraintName || null,
+          columns: columns.map((column) => ({
+            name: column,
+            expression: false,
+            collation: null,
+            order: null,
+          })),
+          conflict: null,
         });
+
+        const newTable = compileCreateTable(parsedTable, this.wrap);
+
+        return this.alter(newTable, createIndices);
       },
       { connection: this.connection }
     );
   }
 
-  async foreign(foreignInfo) {
+  foreign(foreignInfo) {
     return this.client.transaction(
       async (trx) => {
         this.trx = trx;
 
         const { createTable, createIndices } = await this.getTableSql();
 
-        const oneLineSql = createTable.replace(/\s+/g, ' ');
-        const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
-
-        const columnDefinitions = matched[2];
-
-        if (!columnDefinitions) {
-          throw new Error('No column definitions in this statement!');
-        }
-
-        const newColumnDefinitions = columnDefinitions
-          .split(COMMA_NO_PAREN_REGEX)
-          .map((line) => line.trim());
-
-        let newForeignSQL = '';
-
-        if (foreignInfo.keyName) {
-          newForeignSQL += `CONSTRAINT ${foreignInfo.keyName}`;
-        }
-
-        newForeignSQL += ` FOREIGN KEY (${foreignInfo.column.join(', ')}) `;
-        newForeignSQL += ` REFERENCES ${foreignInfo.inTable} (${foreignInfo.references})`;
-
-        if (foreignInfo.onUpdate) {
-          newForeignSQL += ` ON UPDATE ${foreignInfo.onUpdate}`;
-        }
-
-        if (foreignInfo.onDelete) {
-          newForeignSQL += ` ON DELETE ${foreignInfo.onDelete}`;
-        }
+        const parsedTable = parseCreateTable(createTable);
 
-        newColumnDefinitions.push(newForeignSQL);
+        parsedTable.constraints.push({
+          type: 'FOREIGN KEY',
+          name: foreignInfo.keyName || null,
+          columns: foreignInfo.column,
+          references: {
+            table: foreignInfo.inTable,
+            columns: foreignInfo.references,
+            delete: foreignInfo.onDelete || null,
+            update: foreignInfo.onUpdate || null,
+            match: null,
+            deferrable: null,
+          },
+        });
 
-        const newSQL = oneLineSql.replace(
-          columnDefinitions,
-          newColumnDefinitions.join(', ')
-        );
+        const newTable = compileCreateTable(parsedTable, this.wrap);
 
-        return await this.generateAlterCommands(
-          newSQL,
-          createIndices,
-          (row) => {
-            return row;
-          }
-        );
+        return this.generateAlterCommands(newTable, createIndices);
       },
       { connection: this.connection }
     );
   }
 
-  /**
-   * @fixme
-   *
-   * There's a bunch of overlap between renameColumn/dropColumn/dropForeign/primary/foreign.
-   * It'll be helpful to refactor this file heavily to combine/optimize some of these calls
-   */
-
-  async alter(newSql, createIndices, mapRow) {
+  async alter(newSql, createIndices, columns) {
     await this.createNewTable(newSql);
-    await this.copyData(mapRow);
+    await this.copyData(columns);
     await this.dropOriginal();
     await this.renameTable();
 
@@ -537,11 +355,11 @@ class SQLite3_DDL {
     }
   }
 
-  async generateAlterCommands(newSql, createIndices, mapRow) {
+  generateAlterCommands(newSql, createIndices, columns) {
     const result = [];
 
     result.push(createNewTable(newSql, this.tableName(), this.alteredName));
-    result.push(copyAllData(this.tableName(), this.alteredName));
+    result.push(copyData(this.tableName(), this.alteredName, columns));
     result.push(dropOriginal(this.tableName()));
     result.push(renameTable(this.alteredName, this.tableName()));
 
diff --git a/lib/dialects/sqlite3/schema/internal/parser.js b/lib/dialects/sqlite3/schema/internal/parser.js
index 82f72b5f..535faf7b 100644
--- a/lib/dialects/sqlite3/schema/internal/parser.js
+++ b/lib/dialects/sqlite3/schema/internal/parser.js
@@ -2,7 +2,8 @@ const { tokenize } = require('./tokenizer');
 const { s, a, m, o, l, n, t, e, f } = require('./parser-combinator');
 
 const TOKENS = {
-  keyword: /(?:ABORT|ACTION|ADD|AFTER|ALL|ALTER|ALWAYS|ANALYZE|AND|AS|ASC|ATTACH|AUTOINCREMENT|BEFORE|BEGIN|BETWEEN|BY|CASCADE|CASE|CAST|CHECK|COLLATE|COLUMN|COMMIT|CONFLICT|CONSTRAINT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|DATABASE|DEFAULT|DEFERRED|DEFERRABLE|DELETE|DESC|DETACH|DISTINCT|DO|DROP|END|EACH|ELSE|ESCAPE|EXCEPT|EXCLUSIVE|EXCLUDE|EXISTS|EXPLAIN|FAIL|FILTER|FIRST|FOLLOWING|FOR|FOREIGN|FROM|FULL|GENERATED|GLOB|GROUP|GROUPS|HAVING|IF|IGNORE|IMMEDIATE|IN|INDEX|INDEXED|INITIALLY|INNER|INSERT|INSTEAD|INTERSECT|INTO|IS|ISNULL|JOIN|KEY|LAST|LEFT|LIKE|LIMIT|MATCH|MATERIALIZED|NATURAL|NO|NOT|NOTHING|NOTNULL|NULL|NULLS|OF|OFFSET|ON|OR|ORDER|OTHERS|OUTER|OVER|PARTITION|PLAN|PRAGMA|PRECEDING|PRIMARY|QUERY|RAISE|RANGE|RECURSIVE|REFERENCES|REGEXP|REINDEX|RELEASE|RENAME|REPLACE|RESTRICT|RETURNING|RIGHT|ROLLBACK|ROW|ROWS|SAVEPOINT|SELECT|SET|TABLE|TEMP|TEMPORARY|THEN|TIES|TO|TRANSACTION|TRIGGER|UNBOUNDED|UNION|UNIQUE|UPDATE|USING|VACUUM|VALUES|VIEW|VIRTUAL|WHEN|WHERE|WINDOW|WITH|WITHOUT)(?=\s+|-|\(|\)|;|\+|\*|\/|%|==|=|<=|<>|<<|<|>=|>>|>|!=|,|&|~|\|\||\||\.)/,
+  keyword:
+    /(?:ABORT|ACTION|ADD|AFTER|ALL|ALTER|ALWAYS|ANALYZE|AND|AS|ASC|ATTACH|AUTOINCREMENT|BEFORE|BEGIN|BETWEEN|BY|CASCADE|CASE|CAST|CHECK|COLLATE|COLUMN|COMMIT|CONFLICT|CONSTRAINT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|DATABASE|DEFAULT|DEFERRED|DEFERRABLE|DELETE|DESC|DETACH|DISTINCT|DO|DROP|END|EACH|ELSE|ESCAPE|EXCEPT|EXCLUSIVE|EXCLUDE|EXISTS|EXPLAIN|FAIL|FILTER|FIRST|FOLLOWING|FOR|FOREIGN|FROM|FULL|GENERATED|GLOB|GROUP|GROUPS|HAVING|IF|IGNORE|IMMEDIATE|IN|INDEX|INDEXED|INITIALLY|INNER|INSERT|INSTEAD|INTERSECT|INTO|IS|ISNULL|JOIN|KEY|LAST|LEFT|LIKE|LIMIT|MATCH|MATERIALIZED|NATURAL|NO|NOT|NOTHING|NOTNULL|NULL|NULLS|OF|OFFSET|ON|OR|ORDER|OTHERS|OUTER|OVER|PARTITION|PLAN|PRAGMA|PRECEDING|PRIMARY|QUERY|RAISE|RANGE|RECURSIVE|REFERENCES|REGEXP|REINDEX|RELEASE|RENAME|REPLACE|RESTRICT|RETURNING|RIGHT|ROLLBACK|ROW|ROWS|SAVEPOINT|SELECT|SET|TABLE|TEMP|TEMPORARY|THEN|TIES|TO|TRANSACTION|TRIGGER|UNBOUNDED|UNION|UNIQUE|UPDATE|USING|VACUUM|VALUES|VIEW|VIRTUAL|WHEN|WHERE|WINDOW|WITH|WITHOUT)(?=\s+|-|\(|\)|;|\+|\*|\/|%|==|=|<=|<>|<<|<|>=|>>|>|!=|,|&|~|\|\||\||\.)/,
   id: /"[^"]*(?:""[^"]*)*"|`[^`]*(?:``[^`]*)*`|\[[^[\]]*\]|[a-z_][a-z0-9_$]*/,
   string: /'[^']*(?:''[^']*)*'/,
   blob: /x'(?:[0-9a-f][0-9a-f])+'/,
@@ -591,10 +592,8 @@ function expression(ctx) {
 }
 
 function identifier(ctx) {
-  return t({ type: 'id' }, (v) =>
-    /^["`[][^]*["`\]]$/.test(v.text)
-      ? v.text.substring(1, v.text.length - 1)
-      : v.text
+  return a([t({ type: 'id' }), t({ type: 'string' })], (v) =>
+    /^["`['][^]*["`\]']$/.test(v) ? v.substring(1, v.length - 1) : v
   )(ctx);
 }
 
@@ -615,6 +614,7 @@ function literalValue(ctx) {
   return a([
     t({ type: 'numeric' }),
     t({ type: 'string' }),
+    t({ type: 'id' }),
     t({ type: 'blob' }),
     t({ text: 'NULL' }),
     t({ text: 'TRUE' }),
diff --git a/lib/dialects/sqlite3/schema/internal/sqlite-ddl-operations.js b/lib/dialects/sqlite3/schema/internal/sqlite-ddl-operations.js
index ad1d1596..16a7057d 100644
--- a/lib/dialects/sqlite3/schema/internal/sqlite-ddl-operations.js
+++ b/lib/dialects/sqlite3/schema/internal/sqlite-ddl-operations.js
@@ -1,30 +1,13 @@
-const identity = require('lodash/identity');
-const chunk = require('lodash/chunk');
-
-function insertChunked(trx, chunkSize, target, iterator, existingData) {
-  const result = [];
-  iterator = iterator || identity;
-  const chunked = chunk(existingData, chunkSize);
-  for (const batch of chunked) {
-    result.push(
-      trx.queryBuilder().table(target).insert(batch.map(iterator)).toQuery()
-    );
-  }
-  return result;
-}
-
 function createNewTable(sql, tablename, alteredName) {
   return sql.replace(tablename, alteredName);
 }
 
-// ToDo To be removed
-async function copyData(trx, iterator, tableName, alteredName) {
-  const existingData = await trx.raw(`SELECT * FROM "${tableName}"`);
-  return insertChunked(trx, 20, alteredName, iterator, existingData);
-}
-
-function copyAllData(sourceTable, targetTable) {
-  return `INSERT INTO ${targetTable} SELECT * FROM ${sourceTable};`;
+function copyData(sourceTable, targetTable, columns) {
+  return `INSERT INTO ${targetTable} SELECT ${
+    columns === undefined
+      ? '*'
+      : columns.map((column) => `"${column}"`).join(', ')
+  } FROM ${sourceTable};`;
 }
 
 function dropOriginal(tableName) {
@@ -40,10 +23,9 @@ function getTableSql(tableName) {
 }
 
 module.exports = {
-  copyAllData,
   createNewTable,
-  dropOriginal,
   copyData,
+  dropOriginal,
   renameTable,
   getTableSql,
 };
diff --git a/lib/dialects/sqlite3/schema/internal/utils.js b/lib/dialects/sqlite3/schema/internal/utils.js
new file mode 100644
index 00000000..ff6774b8
--- /dev/null
+++ b/lib/dialects/sqlite3/schema/internal/utils.js
@@ -0,0 +1,12 @@
+function isEqualId(first, second) {
+  return first.toLowerCase() === second.toLowerCase();
+}
+
+function includesId(list, id) {
+  return list.some((item) => isEqualId(item, id));
+}
+
+module.exports = {
+  isEqualId,
+  includesId,
+};
diff --git a/lib/dialects/sqlite3/schema/sqlite-tablecompiler.js b/lib/dialects/sqlite3/schema/sqlite-tablecompiler.js
index 24a087eb..5aba13ed 100644
--- a/lib/dialects/sqlite3/schema/sqlite-tablecompiler.js
+++ b/lib/dialects/sqlite3/schema/sqlite-tablecompiler.js
@@ -81,6 +81,12 @@ class TableCompiler_SQLite3 extends TableCompiler {
   dropForeign(columns, indexName) {
     const compiler = this;
 
+    columns = Array.isArray(columns) ? columns : [columns];
+    columns = columns.map((column) =>
+      this.client.customWrapIdentifier(column, identity)
+    );
+    indexName = this.client.customWrapIdentifier(indexName, identity);
+
     this.pushQuery({
       sql: `PRAGMA table_info(${this.tableName()})`,
       output(pragma) {
@@ -95,6 +101,8 @@ class TableCompiler_SQLite3 extends TableCompiler {
   dropPrimary(constraintName) {
     const compiler = this;
 
+    constraintName = this.client.customWrapIdentifier(constraintName, identity);
+
     this.pushQuery({
       sql: `PRAGMA table_info(${this.tableName()})`,
       output(pragma) {
@@ -146,8 +154,11 @@ class TableCompiler_SQLite3 extends TableCompiler {
   primary(columns, constraintName) {
     const compiler = this;
 
-    columns = this.formatter.columnize(columns);
     columns = Array.isArray(columns) ? columns : [columns];
+    columns = columns.map((column) =>
+      this.client.customWrapIdentifier(column, identity)
+    );
+    constraintName = this.client.customWrapIdentifier(constraintName, identity);
 
     if (this.method !== 'create' && this.method !== 'createIfNot') {
       this.pushQuery({
@@ -170,7 +181,7 @@ class TableCompiler_SQLite3 extends TableCompiler {
    * @param {object} foreignInfo - Information about the current column foreign setup
    * @param {string | string[]} [foreignInfo.column] - Column in the current constraint
    * @param {string | undefined} foreignInfo.keyName - Name of the foreign key constraint
-   * @param {string} foreignInfo.references - What column it references in the other table
+   * @param {string | string[]} foreignInfo.references - What column it references in the other table
    * @param {string} foreignInfo.inTable - What table is referenced in this constraint
    * @param {string} [foreignInfo.onUpdate] - What to do on updates
    * @param {string} [foreignInfo.onDelete] - What to do on deletions
@@ -179,12 +190,22 @@ class TableCompiler_SQLite3 extends TableCompiler {
     const compiler = this;
 
     if (this.method !== 'create' && this.method !== 'createIfNot') {
-      foreignInfo.column = this.formatter.columnize(foreignInfo.column);
       foreignInfo.column = Array.isArray(foreignInfo.column)
         ? foreignInfo.column
         : [foreignInfo.column];
-      foreignInfo.inTable = this.formatter.columnize(foreignInfo.inTable);
-      foreignInfo.references = this.formatter.columnize(foreignInfo.references);
+      foreignInfo.column = foreignInfo.column.map((column) =>
+        this.client.customWrapIdentifier(column, identity)
+      );
+      foreignInfo.inTable = this.client.customWrapIdentifier(
+        foreignInfo.inTable,
+        identity
+      );
+      foreignInfo.references = Array.isArray(foreignInfo.references)
+        ? foreignInfo.references
+        : [foreignInfo.references];
+      foreignInfo.references = foreignInfo.references.map((column) =>
+        this.client.customWrapIdentifier(column, identity)
+      );
 
       this.pushQuery({
         sql: `PRAGMA table_info(${this.tableName()})`,
@@ -244,15 +265,25 @@ class TableCompiler_SQLite3 extends TableCompiler {
     });
   }
 
+  _setNullableState(column, isNullable) {
+    const fnCalled = isNullable ? '.setNullable' : '.dropNullable';
+    throw new Error(`${fnCalled} is not supported for SQLite.`);
+  }
+
   dropColumn() {
     const compiler = this;
     const columns = values(arguments);
+
+    const columnsWrapped = columns.map((column) =>
+      this.client.customWrapIdentifier(column, identity)
+    );
+
     this.pushQuery({
       sql: `PRAGMA table_info(${this.tableName()})`,
       output(pragma) {
         return compiler.client
           .ddl(compiler, pragma, this.connection)
-          .dropColumn(columns);
+          .dropColumn(columnsWrapped);
       },
     });
   }
diff --git a/lib/execution/runner.js b/lib/execution/runner.js
index 64cbe322..ce6a011b 100644
--- a/lib/execution/runner.js
+++ b/lib/execution/runner.js
@@ -238,7 +238,7 @@ class Runner {
 
     const results = [];
     for (const query of queries) {
-      results.push(await this.query(query));
+      results.push(await this.queryArray([query]));
     }
     return results;
   }
diff --git a/lib/migrations/migrate/stub/mjs.stub b/lib/migrations/migrate/stub/mjs.stub
new file mode 100644
index 00000000..b59f739e
--- /dev/null
+++ b/lib/migrations/migrate/stub/mjs.stub
@@ -0,0 +1,15 @@
+
+export const up = async (knex) => {
+  <% if (d.tableName) { %>
+  await knex.schema.createTable("<%= d.tableName %>", function(t) {
+    t.increments();
+    t.timestamp();
+  });
+  <% } %>
+};
+
+export const down = async (knex) => {
+  <% if (d.tableName) { %>
+  await knex.schema.dropTable("<%= d.tableName %>");
+  <% } %>
+};
diff --git a/lib/migrations/seed/stub/mjs.stub b/lib/migrations/seed/stub/mjs.stub
new file mode 100644
index 00000000..a324caee
--- /dev/null
+++ b/lib/migrations/seed/stub/mjs.stub
@@ -0,0 +1,12 @@
+
+export const seed = async (knex) => {
+  // Deletes ALL existing entries
+  await knex('table_name').del();
+  
+  // Inserts seed entries
+  await knex('table_name').insert([
+    {id: 1, colName: 'rowValue1'},
+    {id: 2, colName: 'rowValue2'},
+    {id: 3, colName: 'rowValue3'}
+  ]);
+};
diff --git a/lib/query/analytic.js b/lib/query/analytic.js
index 98255020..94cde5d8 100644
--- a/lib/query/analytic.js
+++ b/lib/query/analytic.js
@@ -18,7 +18,7 @@ class Analytic {
     this.grouping = 'columns';
   }
 
-  partitionBy(column) {
+  partitionBy(column, direction) {
     assert(
       Array.isArray(column) || typeof column === 'string',
       `The argument to an analytic partitionBy function must be either a string
@@ -28,12 +28,12 @@ class Analytic {
     if (Array.isArray(column)) {
       this.partitions = this.partitions.concat(column);
     } else {
-      this.partitions.push(column);
+      this.partitions.push({ column: column, order: direction });
     }
     return this;
   }
 
-  orderBy(column) {
+  orderBy(column, direction) {
     assert(
       Array.isArray(column) || typeof column === 'string',
       `The argument to an analytic orderBy function must be either a string
@@ -43,7 +43,7 @@ class Analytic {
     if (Array.isArray(column)) {
       this.order = this.order.concat(column);
     } else {
-      this.order.push(column);
+      this.order.push({ column: column, order: direction });
     }
     return this;
   }
diff --git a/lib/query/querybuilder.js b/lib/query/querybuilder.js
index 70fceb67..7b7e76ef 100644
--- a/lib/query/querybuilder.js
+++ b/lib/query/querybuilder.js
@@ -1247,15 +1247,18 @@ class Builder extends EventEmitter {
       typeof second === 'function' ||
         second.isRawInstance ||
         Array.isArray(second) ||
-        typeof second === 'string',
+        typeof second === 'string' ||
+        typeof second === 'object',
       `The second argument to an analytic function must be either a function, a raw,
-       an array of string or a single string.`
+       an array of string or object, an object or a single string.`
     );
 
     if (third) {
       assert(
-        Array.isArray(third) || typeof third === 'string',
-        'The third argument to an analytic function must be either a string or an array of string.'
+        Array.isArray(third) ||
+          typeof third === 'string' ||
+          typeof third === 'object',
+        'The third argument to an analytic function must be either a string, an array of string or object or an object.'
       );
     }
 
@@ -1272,9 +1275,9 @@ class Builder extends EventEmitter {
         alias: alias,
       };
     } else {
-      const order = typeof second === 'string' ? [second] : second;
+      const order = !Array.isArray(second) ? [second] : second;
       let partitions = third || [];
-      partitions = typeof partitions === 'string' ? [partitions] : partitions;
+      partitions = !Array.isArray(partitions) ? [partitions] : partitions;
       analytic = {
         grouping: 'columns',
         type: 'analytic',
diff --git a/lib/query/querycompiler.js b/lib/query/querycompiler.js
index 47f2e8ef..e446b3b8 100644
--- a/lib/query/querycompiler.js
+++ b/lib/query/querycompiler.js
@@ -1035,13 +1035,17 @@ class QueryCompiler {
         sql += 'partition by ';
         sql +=
           map(stmt.partitions, function (partition) {
-            return self.formatter.columnize(partition);
+            if (isString(partition)) {
+              return self.formatter.columnize(partition);
+            } else return self.formatter.columnize(partition.column) + (partition.order ? ' ' + partition.order : '');
           }).join(', ') + ' ';
       }
 
       sql += 'order by ';
       sql += map(stmt.order, function (order) {
-        return self.formatter.columnize(order);
+        if (isString(order)) {
+          return self.formatter.columnize(order);
+        } else return self.formatter.columnize(order.column) + (order.order ? ' ' + order.order : '');
       }).join(', ');
     }
 
diff --git a/lib/schema/tablebuilder.js b/lib/schema/tablebuilder.js
index a861f3ad..3a8343f7 100644
--- a/lib/schema/tablebuilder.js
+++ b/lib/schema/tablebuilder.js
@@ -284,6 +284,26 @@ const AlterMethods = {
     return this.dropColumns(['created_at', 'updated_at']);
   },
 
+  setNullable(column) {
+    this._statements.push({
+      grouping: 'alterTable',
+      method: 'setNullable',
+      args: [column],
+    });
+
+    return this;
+  },
+
+  dropNullable(column) {
+    this._statements.push({
+      grouping: 'alterTable',
+      method: 'dropNullable',
+      args: [column],
+    });
+
+    return this;
+  },
+
   // TODO: changeType
 };
 
diff --git a/lib/schema/tablecompiler.js b/lib/schema/tablecompiler.js
index d7825cb7..671e936c 100644
--- a/lib/schema/tablecompiler.js
+++ b/lib/schema/tablecompiler.js
@@ -286,6 +286,49 @@ class TableCompiler {
     );
   }
 
+  //Default implementation of setNullable. Overwrite on dialect-specific tablecompiler when needed
+  //(See postgres/mssql for reference)
+  _setNullableState(column, nullable) {
+    const tableName = this.tableName();
+    const columnName = this.formatter.columnize(column);
+    const alterColumnPrefix = this.alterColumnsPrefix;
+    return this.pushQuery({
+      sql: 'SELECT 1',
+      output: () => {
+        return this.client
+          .queryBuilder()
+          .from(this.tableNameRaw)
+          .columnInfo(column)
+          .then((columnInfo) => {
+            if (isEmpty(columnInfo)) {
+              throw new Error(
+                `.setNullable: Column ${columnName} does not exist in table ${tableName}.`
+              );
+            }
+            const nullableType = nullable ? 'null' : 'not null';
+            const columnType =
+              columnInfo.type +
+              (columnInfo.maxLength ? `(${columnInfo.maxLength})` : '');
+            const defaultValue =
+              columnInfo.defaultValue !== null &&
+              columnInfo.defaultValue !== void 0
+                ? `default '${columnInfo.defaultValue}'`
+                : '';
+            const sql = `alter table ${tableName} ${alterColumnPrefix} ${columnName} ${columnType} ${nullableType} ${defaultValue}`;
+            return this.client.raw(sql);
+          });
+      },
+    });
+  }
+
+  setNullable(column) {
+    return this._setNullableState(column, true);
+  }
+
+  dropNullable(column) {
+    return this._setNullableState(column, false);
+  }
+
   // If no name was specified for this index, we will create one using a basic
   // convention of the table name, followed by the columns, followed by an
   // index type, such as primary or index, which makes the index unique.
@@ -310,6 +353,7 @@ TableCompiler.prototype.lowerCase = true;
 TableCompiler.prototype.createAlterTableMethods = null;
 TableCompiler.prototype.addColumnsPrefix = 'add column ';
 TableCompiler.prototype.alterColumnsPrefix = 'alter column ';
+TableCompiler.prototype.modifyColumnPrefix = 'modify column ';
 TableCompiler.prototype.dropColumnPrefix = 'drop column ';
 
 module.exports = TableCompiler;
diff --git a/package.json b/package.json
index e298a619..02dd4e61 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "knex",
-  "version": "0.95.9",
+  "version": "0.95.10",
   "description": "A batteries-included SQL query & schema builder for Postgres, MySQL and SQLite3 and the Browser",
   "main": "knex",
   "types": "types/index.d.ts",
@@ -80,42 +80,42 @@
     ]
   },
   "devDependencies": {
-    "@types/node": "^16.4.2",
+    "@types/node": "^16.6.2",
     "chai": "^4.3.4",
     "chai-as-promised": "^7.1.1",
     "chai-subset-in-order": "^3.1.0",
     "cli-testlab": "^2.2.0",
     "coveralls": "^3.1.1",
     "cross-env": "^7.0.3",
-    "dtslint": "4.1.2",
-    "eslint": "^7.31.0",
+    "dtslint": "4.1.6",
+    "eslint": "^7.32.0",
     "eslint-config-prettier": "^8.3.0",
-    "eslint-plugin-import": "^2.23.4",
+    "eslint-plugin-import": "^2.24.1",
     "husky": "^4.3.8",
     "jake": "^8.1.1",
     "JSONStream": "^1.3.5",
-    "lint-staged": "^11.1.1",
-    "mocha": "^9.0.3",
+    "lint-staged": "^11.1.2",
+    "mocha": "^9.1.0",
     "mock-fs": "^4.13.0",
     "mysql": "^2.18.1",
-    "mysql2": "^2.2.5",
+    "mysql2": "^2.3.0",
     "nyc": "^15.1.0",
     "oracledb": "^5.2.0",
-    "pg": "^8.6.0",
-    "pg-query-stream": "^4.1.0",
+    "pg": "^8.7.1",
+    "pg-query-stream": "^4.2.1",
     "prettier": "2.3.2",
     "rimraf": "^3.0.2",
-    "sinon": "^11.1.1",
+    "sinon": "^11.1.2",
     "sinon-chai": "^3.7.0",
     "source-map-support": "^0.5.19",
     "sqlite3": "^5.0.2",
     "tap-spec": "^5.0.0",
-    "tape": "^5.2.2",
+    "tape": "^5.3.1",
     "tedious": "^11.4.0",
     "toxiproxy-node-client": "^2.0.6",
-    "ts-node": "^10.1.0",
+    "ts-node": "^10.2.1",
     "tsd": "^0.17.0",
-    "typescript": "4.3.5"
+    "typescript": "4.4.2"
   },
   "buildDependencies": [
     "rimraf"
diff --git a/types/index.d.ts b/types/index.d.ts
index da10201e..a33841a5 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -1083,15 +1083,15 @@ export declare namespace Knex {
       TJoinTargetRecord extends {} = any,
       TRecord2 extends {} = TRecord & TJoinTargetRecord,
       TResult2 = DeferredKeySelection.ReplaceBase<TResult, TRecord2>
-    >(
+      >(
       raw: Raw
     ): QueryBuilder<TRecord2, TResult2>;
     <
       TTable extends TableNames,
       TRecord2 = ResolveTableType<TRecord> &
-        ResolveTableType<TableType<TTable>>,
+      ResolveTableType<TableType<TTable>>,
       TResult2 = DeferredKeySelection.ReplaceBase<TResult, TRecord2>
-    >(
+      >(
       tableName: TTable,
       clause: JoinCallback
     ): QueryBuilder<TRecord2, TResult2>;
@@ -1099,7 +1099,7 @@ export declare namespace Knex {
       TJoinTargetRecord extends {} = any,
       TRecord2 extends {} = TRecord & TJoinTargetRecord,
       TResult2 = DeferredKeySelection.ReplaceBase<TResult, TRecord2>
-    >(
+      >(
       tableName: TableDescriptor | AliasDict | QueryCallback,
       clause: JoinCallback
     ): QueryBuilder<TRecord2, TResult2>;
@@ -1107,7 +1107,7 @@ export declare namespace Knex {
       TJoinTargetRecord extends {} = any,
       TRecord2 extends {} = TRecord & TJoinTargetRecord,
       TResult2 = DeferredKeySelection.ReplaceBase<TResult, TRecord2>
-    >(
+      >(
       tableName: TableDescriptor | AliasDict | QueryCallback,
       columns: { [key: string]: string | number | boolean | Raw }
     ): QueryBuilder<TRecord2, TResult2>;
@@ -1115,7 +1115,7 @@ export declare namespace Knex {
       TJoinTargetRecord extends {} = any,
       TRecord2 extends {} = TRecord & TJoinTargetRecord,
       TResult2 = DeferredKeySelection.ReplaceBase<TResult, TRecord2>
-    >(
+      >(
       tableName: TableDescriptor | AliasDict | QueryCallback,
       raw: Raw
     ): QueryBuilder<TRecord2, TResult2>;
@@ -1127,7 +1127,7 @@ export declare namespace Knex {
       TRecord1 = ResolveTableType<TRecord>,
       TRecord2 = TRecord1 & ResolveTableType<TableType<TTable2>>,
       TResult2 = DeferredKeySelection.ReplaceBase<TResult, TRecord2>
-    >(
+      >(
       tableName: TTable2,
       column1: `${TTable1}.${TKey1}`,
       column2: `${TTable2}.${TKey2}`
@@ -1140,7 +1140,7 @@ export declare namespace Knex {
       TRecord1 = ResolveTableType<TRecord>,
       TRecord2 = TRecord1 & ResolveTableType<TableType<TTable2>>,
       TResult2 = DeferredKeySelection.ReplaceBase<TResult, TRecord2>
-    >(
+      >(
       tableName: TTable2,
       column1: `${TTable2}.${TKey2}`,
       column2: `${TTable1}.${TKey1}`
@@ -1149,7 +1149,7 @@ export declare namespace Knex {
       TJoinTargetRecord extends {} = any,
       TRecord2 extends {} = TRecord & TJoinTargetRecord,
       TResult2 = DeferredKeySelection.ReplaceBase<TResult, TRecord2>
-    >(
+      >(
       tableName: TableDescriptor | AliasDict | QueryCallback,
       column1: string,
       column2: string
@@ -1158,7 +1158,7 @@ export declare namespace Knex {
       TJoinTargetRecord extends {} = any,
       TRecord2 extends {} = TRecord & TJoinTargetRecord,
       TResult2 = DeferredKeySelection.ReplaceBase<TResult, TRecord2>
-    >(
+      >(
       tableName: TableDescriptor | AliasDict | QueryCallback,
       column1: string,
       raw: Raw
@@ -1171,7 +1171,7 @@ export declare namespace Knex {
       TRecord1 = ResolveTableType<TRecord>,
       TRecord2 = TRecord1 & ResolveTableType<TableType<TTable2>>,
       TResult2 = DeferredKeySelection.ReplaceBase<TResult, TRecord2>
-    >(
+      >(
       tableName: TTable2,
       column1: `${TTable1}.${TKey1}`,
       operator: string,
@@ -1185,7 +1185,7 @@ export declare namespace Knex {
       TRecord1 = ResolveTableType<TRecord>,
       TRecord2 = TRecord1 & ResolveTableType<TableType<TTable2>>,
       TResult2 = DeferredKeySelection.ReplaceBase<TResult, TRecord2>
-    >(
+      >(
       tableName: TTable2,
       column1: `${TTable2}.${TKey2}`,
       operator: string,
@@ -1195,7 +1195,7 @@ export declare namespace Knex {
       TJoinTargetRecord extends {} = any,
       TRecord2 extends {} = TRecord & TJoinTargetRecord,
       TResult2 = DeferredKeySelection.ReplaceBase<TResult, TRecord2>
-    >(
+      >(
       tableName: TableDescriptor | AliasDict | QueryCallback,
       column1: string,
       operator: string,
@@ -1267,7 +1267,7 @@ export declare namespace Knex {
 
   interface With<TRecord = any, TResult = unknown[]>
     extends WithRaw<TRecord, TResult>,
-      WithWrapped<TRecord, TResult> {}
+    WithWrapped<TRecord, TResult> {}
 
   interface WithRaw<TRecord = any, TResult = unknown[]> {
     (alias: string, raw: Raw | QueryBuilder): QueryBuilder<TRecord, TResult>;
@@ -1415,7 +1415,7 @@ export declare namespace Knex {
     <
       TOptions extends { "as": string },
       TResult2 = AggregationQueryResult<TResult, {[k in TOptions["as"]]: TValue}>
-    >(
+      >(
       columnName: Readonly<keyof ResolveTableType<TRecord>>,
       options: Readonly<TOptions>
     ): QueryBuilder<TRecord, TResult2>;
@@ -1425,7 +1425,7 @@ export declare namespace Knex {
     <
       TAliases extends {} = Record<string, string | string[] | Knex.Raw>,
       TResult2 = AggregationQueryResult<TResult, {[k in keyof TAliases]?: TValue}>
-    >(aliases: TAliases): QueryBuilder<TRecord, TResult2>;
+      >(aliases: TAliases): QueryBuilder<TRecord, TResult2>;
     <TResult2 = AggregationQueryResult<TResult, Dict<TValue>>>(
       ...columnNames: ReadonlyArray<Readonly<Record<string, string | string[] | Knex.Raw>> | Knex.Raw | string>
     ): QueryBuilder<TRecord, TResult2>;
@@ -1438,14 +1438,14 @@ export declare namespace Knex {
       TResult2 = AggregationQueryResult<TResult, {
         [k in TOptions["as"]]: ResolveTableType<TRecord>[TKey]
       }>
-    >(
+      >(
       columnName: Readonly<TKey>,
       options: Readonly<TOptions>
     ): QueryBuilder<TRecord, TResult2>;
     <
       TKey extends keyof ResolveTableType<TRecord>,
       TResult2 = AggregationQueryResult<TResult, Dict<ResolveTableType<TRecord>[TKey]>>
-    >(
+      >(
       ...columnNames: readonly TKey[]
     ): QueryBuilder<TRecord, TResult2>;
     <
@@ -1454,10 +1454,10 @@ export declare namespace Knex {
         // We have optional here because in most dialects aggregating by multiple keys simultaneously
         // causes rest of the keys to be dropped and only first to be considered
         [K in keyof TAliases]?: K extends keyof TRecord ?
-          TRecord[K] :
-          TValue
+        TRecord[K] :
+        TValue
       }>
-    >(aliases: TAliases): QueryBuilder<TRecord, TResult2>;
+      >(aliases: TAliases): QueryBuilder<TRecord, TResult2>;
     <TResult2 = AggregationQueryResult<TResult, Dict<TValue>>>(
       ...columnNames: ReadonlyArray<Readonly<Record<string, string | readonly string[] | Knex.Raw>> | Knex.Raw | string>
     ): QueryBuilder<TRecord, TResult2>;
@@ -1467,20 +1467,20 @@ export declare namespace Knex {
     <
       TAlias extends string,
       TResult2 = AggregationQueryResult<TResult, {[x in TAlias]: number}>
-    >(alias: TAlias, raw: Raw | QueryCallback<TRecord, TResult>): QueryBuilder<TRecord, TResult2>;
+      >(alias: TAlias, raw: Raw | QueryCallback<TRecord, TResult>): QueryBuilder<TRecord, TResult2>;
     <
       TAlias extends string,
       TKey extends keyof ResolveTableType<TRecord>,
       TResult2 = AggregationQueryResult<TResult, {[x in TAlias]: number}>
-    >(alias: TAlias, orderBy: TKey | TKey[], partitionBy?: TKey | TKey[]): QueryBuilder<
-      TRecord,
-      TResult2
-    >;
+      >(alias: TAlias, orderBy: TKey | TKey[] | { columnName: TKey, order?: 'asc' | 'desc' }, partitionBy?: TKey | TKey[] | { columnName: TKey, order?: 'asc' | 'desc' }): QueryBuilder<
+        TRecord,
+        TResult2
+      >;
   }
 
   interface GroupBy<TRecord = any, TResult = unknown[]>
     extends RawQueryBuilder<TRecord, TResult>,
-      ColumnNameQueryBuilder<TRecord, TResult> {}
+    ColumnNameQueryBuilder<TRecord, TResult> {}
 
   interface OrderBy<TRecord = any, TResult = unknown[]> {
     (columnName: keyof TRecord | QueryBuilder, order?: 'asc' | 'desc'): QueryBuilder<
@@ -1505,8 +1505,7 @@ export declare namespace Knex {
   }
 
   interface PartitionBy<TRecord = any, TResult = unknown[]>
-    extends RawQueryBuilder<TRecord, TResult>,
-      ColumnNameQueryBuilder<TRecord, TResult> {}
+    extends OrderBy<TRecord, TResult> {}
 
   interface Intersect<TRecord = any, TResult = unknown[]> {
     (
@@ -1566,7 +1565,7 @@ export declare namespace Knex {
         ResolveTableType<TRecord>,
         ColNameUT & string
       >[]
-    >(
+      >(
       ...columnNames: readonly ColNameUT[]
     ): QueryBuilder<TRecord, TResult2>;
 
@@ -1577,7 +1576,7 @@ export declare namespace Knex {
         ResolveTableType<TRecord>,
         ColNameUT & string
       >[]
-    >(
+      >(
       columnNames: readonly ColNameUT[]
     ): QueryBuilder<TRecord, TResult2>;
 
@@ -1589,7 +1588,7 @@ export declare namespace Knex {
         SafePartial<TRecord>,
         keyof TRecord & string
       >[]
-    >(
+      >(
       ...columnNames: readonly ColumnDescriptor<TRecord, TResult>[]
     ): QueryBuilder<TRecord, TResult2>;
 
@@ -1599,7 +1598,7 @@ export declare namespace Knex {
         SafePartial<TRecord>,
         keyof TRecord & string
       >[]
-    >(
+      >(
       columnNames: readonly ColumnDescriptor<TRecord, TResult>[]
     ): QueryBuilder<TRecord, TResult2>;
   }
@@ -1621,7 +1620,7 @@ export declare namespace Knex {
 
   interface Raw<TResult = any>
     extends events.EventEmitter,
-      ChainableInterface<ResolveResult<TResult>> {
+    ChainableInterface<ResolveResult<TResult>> {
     timeout(ms: number, options?: {cancel?: boolean}): Raw<TResult>;
     wrap<TResult2 = TResult>(before: string, after: string): Raw<TResult>;
     toSQL(): Sql;
@@ -1638,27 +1637,27 @@ export declare namespace Knex {
   const RefMemberTag: unique symbol;
 
   interface Ref<TSrc extends string, TMapping extends {}> extends Raw<string> {
-      // TypeScript can behave weirdly if type parameters are not
-      // actually used in the members of type.
-      //
-      // See: https://github.com/knex/knex/issues/3932
-      //
-      // We simply need to propagate the type context so that we can extract
-      // them later, but we just add a "phantom" property so that typescript
-      // doesn't think that these parameters are unused
-      //
-      // Because unique symbol is used here, there is no way to actually
-      // access this at runtime
-      [RefMemberTag]: {
-        src: TSrc,
-        mapping: TMapping
-      };
-      withSchema(schema: string): this;
-      as<TAlias extends string>(alias: TAlias): Ref<TSrc, {[K in TAlias]: TSrc}>;
+    // TypeScript can behave weirdly if type parameters are not
+    // actually used in the members of type.
+    //
+    // See: https://github.com/knex/knex/issues/3932
+    //
+    // We simply need to propagate the type context so that we can extract
+    // them later, but we just add a "phantom" property so that typescript
+    // doesn't think that these parameters are unused
+    //
+    // Because unique symbol is used here, there is no way to actually
+    // access this at runtime
+    [RefMemberTag]: {
+      src: TSrc,
+      mapping: TMapping
+    };
+    withSchema(schema: string): this;
+    as<TAlias extends string>(alias: TAlias): Ref<TSrc, {[K in TAlias]: TSrc}>;
   }
 
   interface RefBuilder {
-      <TSrc extends string>(src: TSrc): Ref<TSrc, {[K in TSrc]: TSrc}>;
+    <TSrc extends string>(src: TSrc): Ref<TSrc, {[K in TSrc]: TSrc}>;
   }
 
   interface BatchInsertBuilder<TRecord extends {} = any, TResult = number[]> extends Promise<ResolveResult<TResult>> {
@@ -1671,18 +1670,18 @@ export declare namespace Knex {
         UnwrapArrayMember<TResult>,
         ResolveTableType<TRecord>,
         TKey
-        >[]
-      >(
-        column: TKey
+      >[]
+    >(
+      column: TKey
     ): BatchInsertBuilder<TRecord, TResult2>;
     returning<
       TKey extends StrKey<ResolveTableType<TRecord>>,
       TResult2 = DeferredKeySelection.SetSingle<
         DeferredKeySelection.Augment<UnwrapArrayMember<TResult>, ResolveTableType<TRecord>, TKey>,
         false
-        >[]
-      >(
-        columns: readonly TKey[]
+      >[]
+    >(
+      columns: readonly TKey[]
     ): BatchInsertBuilder<TRecord, TResult2>;
     // if data with specific type passed, exclude this method
     returning<TResult2 = SafePartial<TRecord>[]>(
@@ -1708,9 +1707,9 @@ export declare namespace Knex {
   interface QueryBuilder<
     TRecord extends {} = any,
     TResult = any
-  >
+    >
     extends QueryInterface<TRecord, TResult>,
-      ChainableInterface<ResolveResult<TResult>> {
+    ChainableInterface<ResolveResult<TResult>> {
     client: Client;
     or: QueryBuilder<TRecord, TResult>;
     not: QueryBuilder<TRecord, TResult>;
@@ -1961,12 +1960,9 @@ export declare namespace Knex {
     comment(value: string): ColumnBuilder;
     alter(): ColumnBuilder;
     queryContext(context: any): ColumnBuilder;
-    withKeyName(keyName: string): ColumnBuilder;
     after(columnName: string): ColumnBuilder;
     first(): ColumnBuilder;
-    deferrable(type: deferrableType): ColumnBuilder;
   }
-
   interface ForeignConstraintBuilder {
     references(columnName: string): ReferencingColumnBuilder;
   }
@@ -1980,7 +1976,11 @@ export declare namespace Knex {
   }
 
   interface ReferencingColumnBuilder extends ColumnBuilder {
-    inTable(tableName: string): ColumnBuilder;
+    inTable(tableName: string): ReferencingColumnBuilder;
+    deferrable(type: deferrableType): ReferencingColumnBuilder;
+    withKeyName(keyName: string): ReferencingColumnBuilder;
+    onDelete(command: string): ReferencingColumnBuilder;
+    onUpdate(command: string): ReferencingColumnBuilder;
   }
 
   interface AlterColumnBuilder extends ColumnBuilder {}
@@ -2059,105 +2059,105 @@ export declare namespace Knex {
     | 'azure-active-directory-msi-app-service'
     | 'azure-active-directory-service-principal-secret';
 
-interface MsSqlDefaultAuthenticationConfig extends MsSqlConnectionConfigBase {
-  type?: 'default' | never;
-}
-
-interface MsSqlAzureActiveDirectoryMsiAppServiceAuthenticationConfig
-  extends MsSqlConnectionConfigBase {
-  type: 'azure-active-directory-msi-app-service';
-  /**
-   * If you user want to connect to an Azure app service using a specific client account
-   * they need to provide `clientId` asscoiate to their created idnetity.
-   *
-   * This is optional for retrieve token from azure web app service
-   */
-  clientId?: string;
-  /**
-   * A msi app service environment need to provide `msiEndpoint` for retriving the accesstoken.
-   */
-  msiEndpoint?: string;
-  /**
-   * A msi app service environment need to provide `msiSecret` for retriving the accesstoken.
-   */
-  msiSecret?: string;
-}
-
-interface MsSqlAzureActiveDirectoryMsiVmAuthenticationConfig
-  extends MsSqlConnectionConfigBase {
-  type: 'azure-active-directory-msi-vm';
-  /**
-   * If you user want to connect to an Azure app service using a specific client account
-   * they need to provide `clientId` asscoiate to their created idnetity.
-   *
-   * This is optional for retrieve token from azure web app service
-   */
-  clientId?: string;
-  /**
-   * A user need to provide `msiEndpoint` for retriving the accesstoken.
-   */
-  msiEndpoint?: string;
-}
-
-interface MsSqlAzureActiveDirectoryAccessTokenAuthenticationConfig
-  extends MsSqlConnectionConfigBase {
-  type: 'azure-active-directory-access-token';
-  /**
-   * A user-provided access token
-   */
-  token: string;
-}
-interface MsSqlAzureActiveDirectoryPasswordAuthenticationConfig
-  extends MsSqlConnectionConfigBase {
-  type: 'azure-active-directory-password';
-  /**
-   * Optional parameter for specific Azure tenant ID
-   */
-  domain: string;
-  userName: string;
-  password: string;
-}
-
-interface MsSqlAzureActiveDirectoryServicePrincipalSecretConfig
-  extends MsSqlConnectionConfigBase {
-  type: 'azure-active-directory-service-principal-secret';
-  /**
-   * Application (`client`) ID from your registered Azure application
-   */
-  clientId: string;
-  /**
-   * The created `client secret` for this registered Azure application
-   */
-  clientSecret: string;
-  /**
-   * Directory (`tenant`) ID from your registered Azure application
-   */
-  tenantId: string;
-}
+  interface MsSqlDefaultAuthenticationConfig extends MsSqlConnectionConfigBase {
+    type?: 'default' | never;
+  }
+
+  interface MsSqlAzureActiveDirectoryMsiAppServiceAuthenticationConfig
+    extends MsSqlConnectionConfigBase {
+    type: 'azure-active-directory-msi-app-service';
+    /**
+     * If you user want to connect to an Azure app service using a specific client account
+     * they need to provide `clientId` asscoiate to their created idnetity.
+     *
+     * This is optional for retrieve token from azure web app service
+     */
+    clientId?: string;
+    /**
+     * A msi app service environment need to provide `msiEndpoint` for retriving the accesstoken.
+     */
+    msiEndpoint?: string;
+    /**
+     * A msi app service environment need to provide `msiSecret` for retriving the accesstoken.
+     */
+    msiSecret?: string;
+  }
+
+  interface MsSqlAzureActiveDirectoryMsiVmAuthenticationConfig
+    extends MsSqlConnectionConfigBase {
+    type: 'azure-active-directory-msi-vm';
+    /**
+     * If you user want to connect to an Azure app service using a specific client account
+     * they need to provide `clientId` asscoiate to their created idnetity.
+     *
+     * This is optional for retrieve token from azure web app service
+     */
+    clientId?: string;
+    /**
+     * A user need to provide `msiEndpoint` for retriving the accesstoken.
+     */
+    msiEndpoint?: string;
+  }
+
+  interface MsSqlAzureActiveDirectoryAccessTokenAuthenticationConfig
+    extends MsSqlConnectionConfigBase {
+    type: 'azure-active-directory-access-token';
+    /**
+     * A user-provided access token
+     */
+    token: string;
+  }
+  interface MsSqlAzureActiveDirectoryPasswordAuthenticationConfig
+    extends MsSqlConnectionConfigBase {
+    type: 'azure-active-directory-password';
+    /**
+     * Optional parameter for specific Azure tenant ID
+     */
+    domain: string;
+    userName: string;
+    password: string;
+  }
 
-interface MsSqlNtlmAuthenticationConfig extends MsSqlConnectionConfigBase {
-  type: 'ntlm';
-  /**
-   * Once you set domain for ntlm authentication type, driver will connect to SQL Server using domain login.
-   *
-   * This is necessary for forming a connection using ntlm type
-   */
-  domain: string;
-  userName: string;
-  password: string;
-}
+  interface MsSqlAzureActiveDirectoryServicePrincipalSecretConfig
+    extends MsSqlConnectionConfigBase {
+    type: 'azure-active-directory-service-principal-secret';
+    /**
+     * Application (`client`) ID from your registered Azure application
+     */
+    clientId: string;
+    /**
+     * The created `client secret` for this registered Azure application
+     */
+    clientSecret: string;
+    /**
+     * Directory (`tenant`) ID from your registered Azure application
+     */
+    tenantId: string;
+  }
+
+  interface MsSqlNtlmAuthenticationConfig extends MsSqlConnectionConfigBase {
+    type: 'ntlm';
+    /**
+     * Once you set domain for ntlm authentication type, driver will connect to SQL Server using domain login.
+     *
+     * This is necessary for forming a connection using ntlm type
+     */
+    domain: string;
+    userName: string;
+    password: string;
+  }
 
-type MsSqlConnectionConfig =
-  | MsSqlDefaultAuthenticationConfig
-  | MsSqlNtlmAuthenticationConfig
-  | MsSqlAzureActiveDirectoryAccessTokenAuthenticationConfig
-  | MsSqlAzureActiveDirectoryMsiAppServiceAuthenticationConfig
-  | MsSqlAzureActiveDirectoryMsiVmAuthenticationConfig
-  | MsSqlAzureActiveDirectoryPasswordAuthenticationConfig
-  | MsSqlAzureActiveDirectoryServicePrincipalSecretConfig;
+  type MsSqlConnectionConfig =
+    | MsSqlDefaultAuthenticationConfig
+    | MsSqlNtlmAuthenticationConfig
+    | MsSqlAzureActiveDirectoryAccessTokenAuthenticationConfig
+    | MsSqlAzureActiveDirectoryMsiAppServiceAuthenticationConfig
+    | MsSqlAzureActiveDirectoryMsiVmAuthenticationConfig
+    | MsSqlAzureActiveDirectoryPasswordAuthenticationConfig
+    | MsSqlAzureActiveDirectoryServicePrincipalSecretConfig;
 
   // Config object for tedious: see http://tediousjs.github.io/tedious/api-connection.html
-interface MsSqlConnectionConfigBase {
+  interface MsSqlConnectionConfigBase {
     type?: MsSqlAuthenticationTypeOptions;
 
     driver?: string;
@@ -2366,14 +2366,14 @@ interface MsSqlConnectionConfigBase {
   }
 
   interface Migration {
-      up: (knex: Knex) => PromiseLike<any>;
-      down?: (kenx: Knex) => PromiseLike<any>;
+    up: (knex: Knex) => PromiseLike<any>;
+    down?: (kenx: Knex) => PromiseLike<any>;
   }
 
   interface MigrationSource<TMigrationSpec> {
-      getMigrations(loadExtensions: readonly string[]): Promise<TMigrationSpec[]>;
-      getMigrationName(migration: TMigrationSpec): string;
-      getMigration(migration: TMigrationSpec): Migration;
+    getMigrations(loadExtensions: readonly string[]): Promise<TMigrationSpec[]>;
+    getMigrationName(migration: TMigrationSpec): string;
+    getMigration(migration: TMigrationSpec): Migration;
   }
 
   interface MigratorConfig {
@@ -2477,9 +2477,9 @@ interface MsSqlConnectionConfigBase {
     initializeDriver(): void;
     driver: any;
     poolDefaults(): {
-        min: number;
-        max: number;
-        propagateCreateError: boolean;
+      min: number;
+      max: number;
+      propagateCreateError: boolean;
     };
     getPoolSettings(poolConfig: any): any;
     initializePool(config?: {}): void;


Test patch:
diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index ddd1960b..452a181c 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@v2.3.0
+        uses: actions/setup-node@v2.4.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 925bf0e7..1dbc5efd 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@v2.3.0
+        uses: actions/setup-node@v2.4.0
         with:
           always-auth: false
           node-version: ${{ matrix.node-version }}
diff --git a/test-tsd/tables.test-d.ts b/test-tsd/tables.test-d.ts
index 7c3f8b4e..7389a3d0 100644
--- a/test-tsd/tables.test-d.ts
+++ b/test-tsd/tables.test-d.ts
@@ -1,6 +1,6 @@
 import { knex, Knex } from '../types';
 import { clientConfig, User, Department, Article } from './common';
-import { expectType } from 'tsd';
+import { expectType, expectAssignable } from 'tsd';
 
 const knexInstance = knex(clientConfig);
 
@@ -65,4 +65,20 @@ const main = async () => {
   expectType<Pick<User, 'id'> | undefined>(
     await knexInstance.first('id').from('users_composite')
   );
+
+  //These tests simply check if type work by showing that it does not throw syntax error
+
+  knexInstance.schema.createTable('testTable',(table) => {
+    table.foreign('fkey_three').references('non_exist.id').withKeyName('non_for1').deferrable('deferred');
+    table.foreign('fkey_threee').references('non_exist.id').deferrable('deferred').withKeyName('non_for2');
+    table.integer('num').references('non_exist.id').deferrable('immediate').withKeyName('non_for3');
+    table.integer('num').references('non_exist.id').withKeyName('non_for4').deferrable('deferred').onDelete('CASCADE');
+    table.integer('num').references('non_exist.id').withKeyName('non_for5').deferrable('deferred').onDelete('CASCADE');
+    table.integer('num').references('id').inTable('non_exist').withKeyName('non_for6').deferrable('deferred').onDelete('CASCADE');
+    table.integer('num').references('id').withKeyName('non_for7').deferrable('deferred').inTable('non_exist').onDelete('CASCADE');
+    table.integer('num').references('id').inTable('non_exist').onDelete('CASCADE').withKeyName('non_for6').deferrable('deferred');
+    table.integer('num').references('id').withKeyName('non_for7').onDelete('CASCADE').deferrable('deferred').inTable('non_exist');
+    table.integer('num').references('id').withKeyName('non_for7').onDelete('CASCADE').deferrable('deferred').inTable('non_exist');
+  })
 };
+
diff --git a/test/db-less-test-suite.js b/test/db-less-test-suite.js
index 7df1de1e..ed5a5935 100644
--- a/test/db-less-test-suite.js
+++ b/test/db-less-test-suite.js
@@ -44,13 +44,6 @@ if (config.postgres) {
   require('./unit/dialects/postgres');
 }
 
-if (config.sqlite3) {
-  describe('Sqlite driver tests', function () {
-    this.timeout(process.env.KNEX_TEST_TIMEOUT || 5000);
-    require('./unit/dialects/sqlite3');
-  });
-}
-
 describe('CLI tests', function () {
   this.timeout(process.env.KNEX_TEST_TIMEOUT || 5000);
   require('./cli/help.spec');
diff --git a/test/integration/query/additional.js b/test/integration/query/additional.js
index deec506d..a4540700 100644
--- a/test/integration/query/additional.js
+++ b/test/integration/query/additional.js
@@ -300,7 +300,7 @@ module.exports = function (knex) {
         [drivers.SQLite]: "SELECT name FROM sqlite_master WHERE type='table';",
         [drivers.Oracle]: 'select TABLE_NAME from USER_TABLES',
         [drivers.MsSQL]:
-          "SELECT table_name FROM information_schema.tables WHERE table_schema='dbo'",
+          "SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='dbo'",
       };
       return knex
         .raw(tables[knex.client.driverName])
@@ -346,7 +346,7 @@ module.exports = function (knex) {
           );
           tester(
             'pg',
-            'select * from information_schema.columns where table_name = ? and table_catalog = ? and table_schema = current_schema()',
+            'select * from information_schema.columns where table_name = ? and table_catalog = current_database() and table_schema = current_schema()',
             null,
             {
               enum_value: {
@@ -417,7 +417,7 @@ module.exports = function (knex) {
           );
           tester(
             'mssql',
-            "select [COLUMN_NAME], [COLUMN_DEFAULT], [DATA_TYPE], [CHARACTER_MAXIMUM_LENGTH], [IS_NULLABLE] from information_schema.columns where table_name = ? and table_catalog = ? and table_schema = 'dbo'",
+            "select [COLUMN_NAME], [COLUMN_DEFAULT], [DATA_TYPE], [CHARACTER_MAXIMUM_LENGTH], [IS_NULLABLE] from INFORMATION_SCHEMA.COLUMNS where table_name = ? and table_catalog = ? and table_schema = 'dbo'",
             ['datatype_test', 'knex_test'],
             {
               enum_value: {
@@ -454,7 +454,7 @@ module.exports = function (knex) {
           );
           tester(
             'pg',
-            'select * from information_schema.columns where table_name = ? and table_catalog = ? and table_schema = current_schema()',
+            'select * from information_schema.columns where table_name = ? and table_catalog = current_database() and table_schema = current_schema()',
             null,
             {
               defaultValue: null,
@@ -493,7 +493,7 @@ module.exports = function (knex) {
           );
           tester(
             'mssql',
-            "select [COLUMN_NAME], [COLUMN_DEFAULT], [DATA_TYPE], [CHARACTER_MAXIMUM_LENGTH], [IS_NULLABLE] from information_schema.columns where table_name = ? and table_catalog = ? and table_schema = 'dbo'",
+            "select [COLUMN_NAME], [COLUMN_DEFAULT], [DATA_TYPE], [CHARACTER_MAXIMUM_LENGTH], [IS_NULLABLE] from INFORMATION_SCHEMA.COLUMNS where table_name = ? and table_catalog = ? and table_schema = 'dbo'",
             null,
             {
               defaultValue: null,
@@ -940,7 +940,8 @@ module.exports = function (knex) {
 
       const getProcessesQuery = getProcessesQueries[driverName]();
 
-      return knex.transaction((trx) => addTimeout().transacting(trx))
+      return knex
+        .transaction((trx) => addTimeout().transacting(trx))
         .then(function () {
           expect(true).to.equal(false);
         })
@@ -1022,7 +1023,7 @@ module.exports = function (knex) {
       const getProcessesForDriver = {
         pg: async () => {
           const results = await knex.raw('SELECT * from pg_stat_activity');
-          return _.map(_.filter(results.rows, {state: 'active'}), 'query');
+          return _.map(_.filter(results.rows, { state: 'active' }), 'query');
         },
         mysql: async () => {
           const results = await knex.raw('SHOW PROCESSLIST');
@@ -1043,11 +1044,11 @@ module.exports = function (knex) {
       const getProcesses = getProcessesForDriver[driverName];
 
       try {
-        const promise = query.timeout(50, { cancel: true }).then(_.identity)
+        const promise = query.timeout(50, { cancel: true }).then(_.identity);
 
-        await delay(10)
+        await delay(10);
         const processesBeforeTimeout = await getProcesses();
-        expect(processesBeforeTimeout).to.include(query.toString())
+        expect(processesBeforeTimeout).to.include(query.toString());
 
         await expect(promise).to.eventually.be.rejected.and.to.deep.include({
           timeout: 50,
@@ -1056,7 +1057,7 @@ module.exports = function (knex) {
         });
 
         const processesAfterTimeout = await getProcesses();
-        expect(processesAfterTimeout).to.not.include(query.toString())
+        expect(processesAfterTimeout).to.not.include(query.toString());
       } finally {
         await knexDb.destroy();
       }
diff --git a/test/integration/query/selects.js b/test/integration/query/selects.js
index 748394de..ef2a8fd2 100644
--- a/test/integration/query/selects.js
+++ b/test/integration/query/selects.js
@@ -1214,6 +1214,44 @@ module.exports = function (knex) {
         .select('first_name', 'last_name');
     });
 
+    describe('recursive CTE support', function () {
+      before(async function() {
+        await knex.schema.dropTableIfExists('rcte')
+        await knex.schema.createTable('rcte', (table) => {
+          table.string('name')
+          table.string('parentName').nullable()
+        })
+
+        // We will check later that this name was found by chaining up parentId using an rCTE.
+        await knex('rcte').insert({name: 'parent'})
+        let parentName = 'parent'
+        for (const name of ['child', 'grandchild']) {
+          await knex('rcte').insert({name, parentName})
+          parentName = name
+        }
+
+        // We will check later that this name is not returned.
+        await knex('rcte').insert({name: 'nope'})
+      })
+      it('supports recursive CTEs', async function () {
+        // FIXME: Oracle requires to omit RECURSIVE. [#4514]
+        if (isOracle(knex)) {
+          return this.skip()
+        }
+
+        const results = await knex.withRecursive('family', (qb) => {
+          qb.select('name', 'parentName').from('rcte').where({name: 'grandchild'}).unionAll(
+            (qb) => qb.select('rcte.name', 'rcte.parentName').from('rcte').join('family', knex.ref('family.parentName'), knex.ref('rcte.name'))
+          )
+        }).select('name').from('family')
+        const names = results.map(({name}) => name)
+
+        expect(names).to.have.length('parent child grandchild'.split(' ').length)
+        expect(names).to.contain('parent')
+        expect(names).not.to.contain('nope')
+      })
+    })
+
     it('supports the <> operator', function () {
       return knex('accounts').where('id', '<>', 2).select('email', 'logins');
     });
diff --git a/test/integration/schema/index.js b/test/integration/schema/index.js
index 59fd6bbf..1fea4d8a 100644
--- a/test/integration/schema/index.js
+++ b/test/integration/schema/index.js
@@ -4,7 +4,15 @@ const { expect } = require('chai');
 
 const _ = require('lodash');
 const { isString, isObject } = require('../../../lib/util/is');
-const { isPgBased, isMysql, isOracle, isPostgreSQL, isSQLite, isRedshift, isMssql } = require('../../util/db-helpers');
+const {
+  isPgBased,
+  isMysql,
+  isOracle,
+  isPostgreSQL,
+  isSQLite,
+  isRedshift,
+  isMssql,
+} = require('../../util/db-helpers');
 
 const wrapIdentifier = (value, wrap) => {
   return wrap(value ? value.toUpperCase() : value);
@@ -475,6 +483,8 @@ module.exports = (knex) => {
             ]);
             tester('mssql', [
               "CREATE TABLE [test_table_one] ([id] bigint identity(1,1) not null primary key, [first_name] nvarchar(255), [last_name] nvarchar(255), [email] nvarchar(255) null, [logins] int CONSTRAINT [test_table_one_logins_default] DEFAULT '1', [balance] float CONSTRAINT [test_table_one_balance_default] DEFAULT '0', [about] nvarchar(max), [created_at] datetime2, [updated_at] datetime2)",
+              "IF EXISTS(SELECT * FROM sys.fn_listextendedproperty(N'MS_Description', N'Schema', N'dbo', N'Table', N'test_table_one', NULL, NULL))\n  EXEC sys.sp_updateextendedproperty N'MS_Description', N'A table comment.', N'Schema', N'dbo', N'Table', N'test_table_one'\nELSE\n  EXEC sys.sp_addextendedproperty N'MS_Description', N'A table comment.', N'Schema', N'dbo', N'Table', N'test_table_one'",
+              "IF EXISTS(SELECT * FROM sys.fn_listextendedproperty(N'MS_Description', N'Schema', N'dbo', N'Table', N'test_table_one', N'Column', N'about'))\n  EXEC sys.sp_updateextendedproperty N'MS_Description', N'A comment.', N'Schema', N'dbo', N'Table', N'test_table_one', N'Column', N'about'\nELSE\n  EXEC sys.sp_addextendedproperty N'MS_Description', N'A comment.', N'Schema', N'dbo', N'Table', N'test_table_one', N'Column', N'about'",
               'CREATE INDEX [test_table_one_first_name_index] ON [test_table_one] ([first_name])',
               'CREATE UNIQUE INDEX [test_table_one_email_unique] ON [test_table_one] ([email]) WHERE [email] IS NOT NULL',
               'CREATE INDEX [test_table_one_logins_index] ON [test_table_one] ([logins])',
@@ -921,7 +931,12 @@ module.exports = (knex) => {
           .then(() => knex.schema.dropTable('test_table_numerics2')));
 
       it('allows alter column syntax', function () {
-        if (isSQLite(knex) || isRedshift(knex) || isMssql(knex) || isOracle(knex)) {
+        if (
+          isSQLite(knex) ||
+          isRedshift(knex) ||
+          isMssql(knex) ||
+          isOracle(knex)
+        ) {
           return;
         }
 
@@ -1316,6 +1331,9 @@ module.exports = (knex) => {
 
         context('when table is created using raw create table', () => {
           beforeEach(async () => {
+            await knex.schema.raw(`create table bar(
+              \`i3\` integer primary key
+            )`);
             await knex.schema.raw(`create table TEST(
               "i0" integer,
               'i1' integer,
@@ -1349,33 +1367,33 @@ module.exports = (knex) => {
             expect(await hasCol('i0')).to.equal(false);
             // Constraint i0 should be unaffected:
             expect(await getCreateTableExpr()).to.equal(
-              'CREATE TABLE "TEST"(\'i1\' integer, [i2] integer, `i3` integer, i4 ' +
-                'integer, I5 integer, unique(i4, i5), constraint i0 primary ' +
-                'key([i3], "i4"), unique([i2]), foreign key (i1) references bar ' +
-                '("i3") )'
+              'CREATE TABLE "TEST" (`i1` integer, `i2` integer, `i3` integer, `i4` ' +
+                'integer, `I5` integer, UNIQUE (`i4`, `i5`), CONSTRAINT `i0` PRIMARY ' +
+                'KEY (`i3`, `i4`), UNIQUE (`i2`), FOREIGN KEY (`i1`) REFERENCES `bar` ' +
+                '(`i3`))'
             );
             await dropCol('i1');
             expect(await hasCol('i1')).to.equal(false);
             // Foreign key on i1 should also be dropped:
             expect(await getCreateTableExpr()).to.equal(
-              'CREATE TABLE "TEST"([i2] integer, `i3` integer, i4 integer, I5 integer, ' +
-                'unique(i4, i5), constraint i0 primary key([i3], "i4"), unique([i2]))'
+              'CREATE TABLE "TEST" (`i2` integer, `i3` integer, `i4` integer, `I5` integer, ' +
+                'UNIQUE (`i4`, `i5`), CONSTRAINT `i0` PRIMARY KEY (`i3`, `i4`), UNIQUE (`i2`))'
             );
             await dropCol('i2');
             expect(await hasCol('i2')).to.equal(false);
             expect(await getCreateTableExpr()).to.equal(
-              'CREATE TABLE "TEST"(`i3` integer, i4 integer, I5 integer, ' +
-                'unique(i4, i5), constraint i0 primary key([i3], "i4"))'
+              'CREATE TABLE "TEST" (`i3` integer, `i4` integer, `I5` integer, ' +
+                'UNIQUE (`i4`, `i5`), CONSTRAINT `i0` PRIMARY KEY (`i3`, `i4`))'
             );
             await dropCol('i3');
             expect(await hasCol('i3')).to.equal(false);
             expect(await getCreateTableExpr()).to.equal(
-              'CREATE TABLE "TEST"(i4 integer, I5 integer, unique(i4, i5))'
+              'CREATE TABLE "TEST" (`i4` integer, `I5` integer, UNIQUE (`i4`, `i5`))'
             );
             await dropCol('i4');
             expect(await hasCol('i4')).to.equal(false);
             expect(await getCreateTableExpr()).to.equal(
-              'CREATE TABLE "TEST"(I5 integer)'
+              'CREATE TABLE "TEST" (`I5` integer)'
             );
             let lastColDeletionError;
             await knex.schema
@@ -1463,6 +1481,49 @@ module.exports = (knex) => {
             .then(() =>
               knex.schema.withSchema(testSchemaName).dropTableIfExists('test2')
             ));
+
+        describe('case-sensitive collation support', () => {
+          let k;
+          const databaseName = 'knex_test_CS_AS_SC';
+          const collation = 'Latin1_General_100_CS_AS_SC';
+          const tableName = 'Test';
+          before(async () => {
+            await knex.schema.raw(
+              `IF EXISTS(SELECT name FROM sys.databases WHERE name = :databaseName) DROP DATABASE :databaseName:; CREATE DATABASE :databaseName: COLLATE ${collation}`,
+              { databaseName }
+            );
+
+            const Knex = require('../../../knex');
+            const config = require('../../knexfile');
+            k = Knex({
+              client: 'mssql',
+              connection: {
+                ...config.mssql.connection,
+                database: databaseName,
+              },
+            });
+
+            // Verify configuration is using the correct database.
+            const [{ name }] = await k.raw('SELECT DB_NAME() AS name');
+            expect(name).to.equal(databaseName);
+
+            await k.schema.createTable(tableName, function () {
+              this.increments();
+            });
+          });
+          after(async () => {
+            await k.schema.dropTable(tableName);
+            await k.destroy();
+            await knex.schema.raw(
+              `IF EXISTS(SELECT name FROM sys.databases WHERE name = :databaseName) DROP DATABASE :databaseName:`,
+              { databaseName }
+            );
+          });
+          it('should get columnInfo from a case-sensitive database', async () => {
+            const info = await k(tableName).columnInfo();
+            expect(info).not.to.equal(undefined);
+          });
+        });
       });
     });
 
diff --git a/test/integration2/dialects/mssql.spec.js b/test/integration2/dialects/mssql.spec.js
index 2b14871b..710f58ca 100644
--- a/test/integration2/dialects/mssql.spec.js
+++ b/test/integration2/dialects/mssql.spec.js
@@ -169,6 +169,138 @@ describe('MSSQL dialect', () => {
             );
           });
         });
+
+        describe('comment support', () => {
+          const schemaName = 'dbo';
+          const tableName = 'test_attaches_comments';
+          const columnName = 'column_with_comment';
+          const columnWithoutComment = 'column_without_comment';
+
+          // Comments intentionally include single quotes, which require escaping in strings.
+          const tableComment = "table's comment";
+          const columnComment = "``column comment''";
+
+          const tableWithoutComment = 'table_without_comment';
+
+          before(async () => {
+            await knex.schema.createTable(tableName, function () {
+              this.comment(tableComment);
+              this.string(columnName).comment(columnComment);
+              this.string(columnWithoutComment);
+            });
+            await knex.schema.createTable(tableWithoutComment, function () {
+              this.increments();
+            });
+          });
+
+          after(async () => {
+            await knex.schema.dropTable(tableName);
+            await knex.schema.dropTable(tableWithoutComment);
+          });
+
+          it('attaches table comments', async () => {
+            const commentText = await commentFor(knex, {
+              schemaName,
+              tableName,
+            });
+
+            expect(commentText).to.equal(tableComment);
+          });
+
+          it('attaches column comments', async () => {
+            const commentText = await commentFor(knex, {
+              schemaName,
+              tableName,
+              columnName,
+            });
+
+            expect(commentText).to.equal(columnComment);
+          });
+
+          it('updates table comments', async () => {
+            const updatedTableComment = 'updated table comment';
+            await knex.schema.alterTable(tableName, function () {
+              this.comment(updatedTableComment);
+            });
+
+            const commentText = await commentFor(knex, {
+              schemaName,
+              tableName,
+            });
+
+            expect(commentText).to.equal(updatedTableComment);
+          });
+
+          it('updates column comments', async () => {
+            const updatedColumnComment = 'updated column comment';
+            await knex.schema.alterTable(tableName, function () {
+              this.string(columnName).comment(updatedColumnComment).alter();
+            });
+
+            const commentText = await commentFor(knex, {
+              schemaName,
+              tableName,
+              columnName,
+            });
+
+            expect(commentText).to.equal(updatedColumnComment);
+          });
+
+          it('adds column comments when altering and no pre-existing comment', async () => {
+            const expectedComment = 'well it has a comment now';
+            await knex.schema.alterTable(tableName, function () {
+              this.string(columnWithoutComment)
+                .comment(expectedComment)
+                .alter();
+            });
+
+            const commentText = await commentFor(knex, {
+              schemaName,
+              tableName,
+              columnName: columnWithoutComment,
+            });
+
+            expect(commentText).to.equal(expectedComment);
+          });
+
+          it('adds table comments when updating and no pre-existing comment', async () => {
+            const tableName = tableWithoutComment;
+            const expectedComment = 'comment';
+            await knex.schema.alterTable(tableName, function () {
+              this.comment(expectedComment);
+            });
+
+            const commentText = await commentFor(knex, {
+              schemaName,
+              tableName,
+            });
+
+            expect(commentText).to.equal(expectedComment);
+          });
+
+          /**
+           * Returns the comment for `target`, if any.
+           *
+           * @param {Knex} knex
+           * @param {{schemaName: string, tableName: string, columnName?: string}} target
+           * @returns {Promise<string|undefined>}
+           */
+          async function commentFor(knex, target) {
+            const columnSpecifier = target.columnName
+              ? `N'Column', :columnName`
+              : 'NULL, NULL';
+            const result = await knex
+              .from(
+                knex.raw(
+                  `fn_listextendedproperty(N'MS_Description', N'Schema', :schemaName, N'Table', :tableName, ${columnSpecifier})`,
+                  target
+                )
+              )
+              .select('value AS comment')
+              .first();
+            return result ? result.comment : undefined;
+          }
+        });
       });
     });
 });
diff --git a/test/integration2/schema/foreign-keys.spec.js b/test/integration2/schema/foreign-keys.spec.js
index 3006edf4..a8657f4c 100644
--- a/test/integration2/schema/foreign-keys.spec.js
+++ b/test/integration2/schema/foreign-keys.spec.js
@@ -66,7 +66,7 @@ describe('Schema', () => {
 
             if (isSQLite(knex)) {
               expect(queries.sql).to.eql([
-                'CREATE TABLE `_knex_temp_alter111` (`id` integer not null primary key autoincrement, `fkey_two` integer not null, `fkey_three` integer not null, CONSTRAINT fk_fkey_threeee FOREIGN KEY (`fkey_three`)  REFERENCES `foreign_keys_table_three` (`id`))',
+                'CREATE TABLE `_knex_temp_alter111` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `fkey_two` integer NOT NULL, `fkey_three` integer NOT NULL, CONSTRAINT `fk_fkey_threeee` FOREIGN KEY (`fkey_three`) REFERENCES `foreign_keys_table_three` (`id`))',
                 'INSERT INTO _knex_temp_alter111 SELECT * FROM foreign_keys_table_one;',
                 'DROP TABLE "foreign_keys_table_one"',
                 'ALTER TABLE "_knex_temp_alter111" RENAME TO "foreign_keys_table_one"',
@@ -77,8 +77,7 @@ describe('Schema', () => {
               expect(queries.sql).to.eql([
                 {
                   bindings: [],
-                  sql:
-                    'alter table "foreign_keys_table_one" add constraint "fk_fkey_threeee" foreign key ("fkey_three") references "foreign_keys_table_three" ("id")',
+                  sql: 'alter table "foreign_keys_table_one" add constraint "fk_fkey_threeee" foreign key ("fkey_three") references "foreign_keys_table_three" ("id")',
                 },
               ]);
             }
@@ -103,7 +102,7 @@ describe('Schema', () => {
             const queries = await builder.generateDdlCommands();
 
             expect(queries.sql).to.eql([
-              'CREATE TABLE `_knex_temp_alter111` (`id` integer not null primary key autoincrement, `fkey_two` integer not null, `fkey_three` integer not null, CONSTRAINT fk_fkey_threeee FOREIGN KEY (`fkey_three`)  REFERENCES `foreign_keys_table_three` (`id`) ON DELETE CASCADE)',
+              'CREATE TABLE `_knex_temp_alter111` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `fkey_two` integer NOT NULL, `fkey_three` integer NOT NULL, CONSTRAINT `fk_fkey_threeee` FOREIGN KEY (`fkey_three`) REFERENCES `foreign_keys_table_three` (`id`) ON DELETE CASCADE)',
               'INSERT INTO _knex_temp_alter111 SELECT * FROM foreign_keys_table_one;',
               'DROP TABLE "foreign_keys_table_one"',
               'ALTER TABLE "_knex_temp_alter111" RENAME TO "foreign_keys_table_one"',
@@ -129,7 +128,7 @@ describe('Schema', () => {
             const queries = await builder.generateDdlCommands();
 
             expect(queries.sql).to.eql([
-              'CREATE TABLE `_knex_temp_alter111` (`id` integer not null primary key autoincrement, `fkey_two` integer not null, `fkey_three` integer not null, CONSTRAINT fk_fkey_threeee FOREIGN KEY (`fkey_three`)  REFERENCES `foreign_keys_table_three` (`id`) ON UPDATE CASCADE)',
+              'CREATE TABLE `_knex_temp_alter111` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `fkey_two` integer NOT NULL, `fkey_three` integer NOT NULL, CONSTRAINT `fk_fkey_threeee` FOREIGN KEY (`fkey_three`) REFERENCES `foreign_keys_table_three` (`id`) ON UPDATE CASCADE)',
               'INSERT INTO _knex_temp_alter111 SELECT * FROM foreign_keys_table_one;',
               'DROP TABLE "foreign_keys_table_one"',
               'ALTER TABLE "_knex_temp_alter111" RENAME TO "foreign_keys_table_one"',
@@ -196,6 +195,27 @@ describe('Schema', () => {
             }
           });
 
+          it('can add a new column with a foreign key constraint', async () => {
+            await knex.schema.alterTable('foreign_keys_table_one', (table) => {
+              table
+                .integer('fkey_new')
+                .unsigned()
+                .notNull()
+                .references('foreign_keys_table_two.id');
+            });
+
+            await knex('foreign_keys_table_two').insert({});
+            await knex('foreign_keys_table_three').insert({});
+
+            await expect(
+              knex('foreign_keys_table_one').insert({
+                fkey_two: 1,
+                fkey_three: 1,
+                fkey_new: 2,
+              })
+            ).to.be.eventually.rejected;
+          });
+
           it('can drop added foreign keys in sqlite after a table rebuild', async () => {
             if (!isSQLite(knex)) {
               return;
diff --git a/test/integration2/schema/index.spec.js b/test/integration2/schema/index.spec.js
index e1aba4d5..81d8ae0a 100644
--- a/test/integration2/schema/index.spec.js
+++ b/test/integration2/schema/index.spec.js
@@ -171,15 +171,13 @@ describe('Schema', () => {
                   type: 'index',
                   name: 'index_table_one_column_one_index',
                   tbl_name: 'index_table_one',
-                  sql:
-                    'CREATE INDEX `index_table_one_column_one_index` on `index_table_one` (`column_one`)',
+                  sql: 'CREATE INDEX `index_table_one_column_one_index` on `index_table_one` (`column_one`)',
                 },
                 {
                   type: 'index',
                   name: 'index_table_one_column_two_column_three_index',
                   tbl_name: 'index_table_one',
-                  sql:
-                    'CREATE INDEX `index_table_one_column_two_column_three_index` on `index_table_one` (`column_three`)',
+                  sql: 'CREATE INDEX `index_table_one_column_two_column_three_index` on `index_table_one` (`column_three`)',
                 },
               ];
               const indicesTwoBeforeWithoutIndex = [
@@ -187,15 +185,13 @@ describe('Schema', () => {
                   type: 'index',
                   name: 'index_table_two_column_one_unique',
                   tbl_name: 'index_table_two',
-                  sql:
-                    'CREATE UNIQUE INDEX `index_table_two_column_one_unique` on `index_table_two` (`column_one`)',
+                  sql: 'CREATE UNIQUE INDEX `index_table_two_column_one_unique` on `index_table_two` (`column_one`)',
                 },
                 {
                   type: 'index',
                   name: 'index_table_two_column_two_column_three_unique',
                   tbl_name: 'index_table_two',
-                  sql:
-                    'CREATE UNIQUE INDEX `index_table_two_column_two_column_three_unique` on `index_table_two` (`column_three`)',
+                  sql: 'CREATE UNIQUE INDEX `index_table_two_column_two_column_three_unique` on `index_table_two` (`column_three`)',
                 },
               ];
 
@@ -254,7 +250,7 @@ describe('Schema', () => {
               );
 
               await knex.schema.alterTable('index_table_one', (table) => {
-                table.dropPrimary('id');
+                table.dropPrimary();
               });
 
               const indicesAfter = await knex.raw(QUERY_TABLE_ONE_INDICES);
diff --git a/test/integration2/schema/set-nullable.spec.js b/test/integration2/schema/set-nullable.spec.js
new file mode 100644
index 00000000..612a63b3
--- /dev/null
+++ b/test/integration2/schema/set-nullable.spec.js
@@ -0,0 +1,73 @@
+const chai = require('chai');
+chai.use(require('chai-as-promised'));
+const expect = chai.expect;
+const { isSQLite, isPostgreSQL, isMysql } = require('../../util/db-helpers');
+const { getAllDbs, getKnexForDb } = require('../util/knex-instance-provider');
+
+describe('Schema', () => {
+  describe('alter nullable', () => {
+    getAllDbs().forEach((db) => {
+      describe(db, () => {
+        let knex;
+
+        before(function () {
+          knex = getKnexForDb(db);
+          if (isSQLite(knex)) {
+            return this.skip();
+          }
+        });
+
+        after(() => {
+          return knex.destroy();
+        });
+
+        beforeEach(async () => {
+          await knex.schema.createTable('primary_table', (table) => {
+            table.integer('id_nullable').nullable();
+            table.integer('id_not_nullable').notNull();
+          });
+        });
+
+        afterEach(async () => {
+          await knex.schema.dropTable('primary_table');
+        });
+
+        describe('setNullable', () => {
+          it('sets column to be nullable', async () => {
+            await knex.schema.table('primary_table', (table) => {
+              table.setNullable('id_not_nullable');
+            });
+
+            await knex('primary_table').insert({
+              id_nullable: null,
+              id_not_nullable: null,
+            });
+          });
+        });
+
+        describe('dropNullable', () => {
+          it('sets column to be not nullable', async () => {
+            await knex.schema.table('primary_table', (table) => {
+              table.dropNullable('id_nullable');
+            });
+
+            let errorMessage;
+            if (isPostgreSQL()) {
+              errorMessage = 'violates not-null constraint';
+            }
+            if (isMysql()) {
+              errorMessage = 'cannot be null';
+            }
+
+            await expect(
+              knex('primary_table').insert({
+                id_nullable: null,
+                id_not_nullable: 1,
+              })
+            ).to.eventually.be.rejectedWith(errorMessage);
+          });
+        });
+      });
+    });
+  });
+});
diff --git a/test/unit/dialects/sqlite3.js b/test/unit/dialects/sqlite3.js
deleted file mode 100644
index d732a87a..00000000
--- a/test/unit/dialects/sqlite3.js
+++ /dev/null
@@ -1,102 +0,0 @@
-'use strict';
-
-const { expect } = require('chai');
-
-const sinon = require('sinon');
-const DDL = require('../../../lib/dialects/sqlite3/schema/ddl');
-
-it('[backwards compatible] can rename column with double quotes', function () {
-  const client = sinon.stub();
-  const tableCompiler = sinon.stub();
-  const pragma = sinon.stub();
-  const connection = sinon.stub();
-  const ddl = new DDL(client, tableCompiler, pragma, connection);
-
-  const sql =
-    'CREATE TABLE "accounts" ("id" varchar(24) not null primary key, "about" varchar(24))';
-
-  const newSql = ddl._doReplace(sql, '`about`', '`about_me`');
-  expect(newSql).to.eql(
-    'CREATE TABLE "accounts" ("id" varchar(24) not null primary key, `about_me` varchar(24))'
-  );
-});
-
-it('[backwards compatible] can rename column with double quotes', function () {
-  const client = sinon.stub();
-  const tableCompiler = sinon.stub();
-  const pragma = sinon.stub();
-  const connection = sinon.stub();
-  const ddl = new DDL(client, tableCompiler, pragma, connection);
-
-  const sql =
-    'CREATE TABLE "accounts" ("id" varchar(24) not null primary key, "about" varchar(24))';
-
-  const newSql = ddl._doReplace(sql, '"about"', '"about_me"');
-  expect(newSql).to.eql(
-    'CREATE TABLE "accounts" ("id" varchar(24) not null primary key, "about_me" varchar(24))'
-  );
-});
-
-it('[backwards compatible] can rename column with double quotes', function () {
-  const client = sinon.stub();
-  const tableCompiler = sinon.stub();
-  const pragma = sinon.stub();
-  const connection = sinon.stub();
-  const ddl = new DDL(client, tableCompiler, pragma, connection);
-
-  const sql =
-    'CREATE TABLE "accounts" ("id" varchar(24) not null primary key, "about" varchar(24))';
-
-  const newSql = ddl._doReplace(sql, '"about"', '`about_me`');
-  expect(newSql).to.eql(
-    'CREATE TABLE "accounts" ("id" varchar(24) not null primary key, `about_me` varchar(24))'
-  );
-});
-
-it('can rename column with back sticks', function () {
-  const client = sinon.stub();
-  const tableCompiler = sinon.stub();
-  const pragma = sinon.stub();
-  const connection = sinon.stub();
-  const ddl = new DDL(client, tableCompiler, pragma, connection);
-
-  const sql =
-    'CREATE TABLE `accounts` (`id` varchar(24) not null primary key, `about` varchar(24))';
-
-  const newSql = ddl._doReplace(sql, '`about`', '`about_me`');
-  expect(newSql).to.eql(
-    'CREATE TABLE `accounts` (`id` varchar(24) not null primary key, `about_me` varchar(24))'
-  );
-});
-
-it('can rename column with multiline and tabulated sql statement', function () {
-  const client = sinon.stub();
-  const tableCompiler = sinon.stub();
-  const pragma = sinon.stub();
-  const connection = sinon.stub();
-  const ddl = new DDL(client, tableCompiler, pragma, connection);
-
-  const sql =
-    'CREATE TABLE `accounts` (\n\n`id`\tvarchar(24) not null primary key,\r\n`about`\t \t\t \tvarchar(24)\n\n)';
-
-  const newSql = ddl._doReplace(sql, '`about`', '`about_me`');
-  expect(newSql).to.eql(
-    'CREATE TABLE `accounts` (`id` varchar(24) not null primary key, `about_me` varchar(24))'
-  );
-});
-
-it('can drop column with multiline and tabulated sql statement', function () {
-  const client = sinon.stub();
-  const tableCompiler = sinon.stub();
-  const pragma = sinon.stub();
-  const connection = sinon.stub();
-  const ddl = new DDL(client, tableCompiler, pragma, connection);
-
-  const sql =
-    'CREATE TABLE `accounts` (\n\n`id`\tvarchar(24) not null primary key,\r\n`about`\t \tvarchar(24)\n)';
-
-  const newSql = ddl._doReplace(sql, '`about`', '');
-  expect(newSql).to.eql(
-    'CREATE TABLE `accounts` (`id` varchar(24) not null primary key)'
-  );
-});
diff --git a/test/unit/query/builder.js b/test/unit/query/builder.js
index 3aac961f..a240cc82 100644
--- a/test/unit/query/builder.js
+++ b/test/unit/query/builder.js
@@ -7854,6 +7854,29 @@ describe('QueryBuilder', () => {
     );
   });
 
+  it('row_number with object', function () {
+    testsql(
+      qb()
+        .select('*')
+        .from('accounts')
+        .rowNumber(null, { column: 'email', order: 'asc' }, [
+          { column: 'address', order: 'asc' },
+          'phone',
+        ]),
+      {
+        mssql: {
+          sql: 'select *, row_number() over (partition by [address] asc, [phone] order by [email] asc) from [accounts]',
+        },
+        pg: {
+          sql: 'select *, row_number() over (partition by "address" asc, "phone" order by "email" asc) from "accounts"',
+        },
+        oracledb: {
+          sql: 'select *, row_number() over (partition by "address" asc, "phone" order by "email" asc) from "accounts"',
+        },
+      }
+    );
+  });
+
   it('row_number with string', function () {
     testsql(
       qb().select('*').from('accounts').rowNumber(null, 'email', 'address'),
@@ -9367,13 +9390,16 @@ describe('QueryBuilder', () => {
         .select('*')
         .from('secondWithClause'),
       {
+        // mssql does not allow the RECURSIVE keyword.
         mssql:
-          'with recursive [firstWithClause] as (with recursive [firstWithSubClause] as ((select [foo] from [users]) as [foz]) select * from [firstWithSubClause]), [secondWithClause] as (with recursive [secondWithSubClause] as ((select [bar] from [users]) as [baz]) select * from [secondWithSubClause]) select * from [secondWithClause]',
+          'with [firstWithClause] as (with [firstWithSubClause] as ((select [foo] from [users]) as [foz]) select * from [firstWithSubClause]), [secondWithClause] as (with [secondWithSubClause] as ((select [bar] from [users]) as [baz]) select * from [secondWithSubClause]) select * from [secondWithClause]',
         sqlite3:
           'with recursive `firstWithClause` as (with recursive `firstWithSubClause` as ((select `foo` from `users`) as `foz`) select * from `firstWithSubClause`), `secondWithClause` as (with recursive `secondWithSubClause` as ((select `bar` from `users`) as `baz`) select * from `secondWithSubClause`) select * from `secondWithClause`',
         pg: 'with recursive "firstWithClause" as (with recursive "firstWithSubClause" as ((select "foo" from "users") as "foz") select * from "firstWithSubClause"), "secondWithClause" as (with recursive "secondWithSubClause" as ((select "bar" from "users") as "baz") select * from "secondWithSubClause") select * from "secondWithClause"',
         'pg-redshift':
           'with recursive "firstWithClause" as (with recursive "firstWithSubClause" as ((select "foo" from "users") as "foz") select * from "firstWithSubClause"), "secondWithClause" as (with recursive "secondWithSubClause" as ((select "bar" from "users") as "baz") select * from "secondWithSubClause") select * from "secondWithClause"',
+        // FIXME: oracledb does not allow the RECURSIVE keyword, but does require a list of column aliases for a recursive query. [#4514]
+        // https://github.com/knex/knex/issues/4514#issuecomment-903727391
         oracledb:
           'with recursive "firstWithClause" as (with recursive "firstWithSubClause" as ((select "foo" from "users") "foz") select * from "firstWithSubClause"), "secondWithClause" as (with recursive "secondWithSubClause" as ((select "bar" from "users") "baz") select * from "secondWithSubClause") select * from "secondWithClause"',
       }
diff --git a/test/unit/schema-builder/sqlite3.js b/test/unit/schema-builder/sqlite3.js
index 487c172d..08bde609 100644
--- a/test/unit/schema-builder/sqlite3.js
+++ b/test/unit/schema-builder/sqlite3.js
@@ -7,7 +7,6 @@ let tableSql;
 const sinon = require('sinon');
 const SQLite3_Client = require('../../../lib/dialects/sqlite3');
 const client = new SQLite3_Client({ client: 'sqlite3' });
-const SQLite3_DDL = require('../../../lib/dialects/sqlite3/schema/ddl');
 const {
   parseCreateTable,
   parseCreateIndex,
@@ -724,35 +723,6 @@ describe('SQLite SchemaBuilder', function () {
     );
   });
 
-  describe('SQLite3_DDL.prototype._doReplace', function () {
-    it('should not change a query that has no matches', function () {
-      return client
-        .schemaBuilder()
-        .table('foo', function () {
-          const doReplace = SQLite3_DDL.prototype._doReplace;
-
-          const sql1 =
-            'CREATE TABLE `foo` (`id` integer not null primary key autoincrement, ' +
-            '"parent_id_test" integer, foreign key("parent_id") references `foo`(`id`))';
-          const sql2 =
-            'CREATE TABLE `foo` (`id` integer not null primary key autoincrement, ' +
-            '"parent_id_test" integer, foreign key("parent_id") references `bar`(`id`))';
-
-          const sql1b =
-            'CREATE TABLE `foo` ("id_foo" integer not null primary key autoincrement, ' +
-            '"parent_id_test" integer, foreign key("parent_id") references `foo`("id_foo"))';
-          const sql2b =
-            'CREATE TABLE `foo` ("id_foo" integer not null primary key autoincrement, ' +
-            '"parent_id_test" integer, foreign key("parent_id") references `bar`(`id`))';
-
-          expect(doReplace(sql1, '`bar`', '"lar"')).to.equal(sql1);
-          expect(doReplace(sql1, '`id`', '"id_foo"')).to.equal(sql1b);
-          expect(doReplace(sql2, '`id`', '"id_foo"')).to.equal(sql2b);
-        })
-        .toSQL();
-    });
-  });
-
   describe('queryContext', function () {
     let spy;
     let originalWrapIdentifier;
@@ -3974,9 +3944,9 @@ describe('SQLite parser and compiler', function () {
 
     it('wrap', function () {
       const sql =
-        'CREATE TABLE "schema".users (`foo`, [bar], CONSTRAINT [foreign_constraint] FOREIGN KEY (foo, `bar`) REFERENCES other ([baz], "bar"))';
+        'CREATE TABLE "schema".users (`foo`, [bar], \'baz\' DEFAULT "lar", CONSTRAINT [foreign_constraint] FOREIGN KEY (foo, `bar`) REFERENCES other ([baz], "bar"))';
       const newSql =
-        'CREATE TABLE "schema"."users" ("foo", "bar", CONSTRAINT "foreign_constraint" FOREIGN KEY ("foo", "bar") REFERENCES "other" ("baz", "bar"))';
+        'CREATE TABLE "schema"."users" ("foo", "bar", "baz" DEFAULT "lar", CONSTRAINT "foreign_constraint" FOREIGN KEY ("foo", "bar") REFERENCES "other" ("baz", "bar"))';
 
       const parsedSql = compileCreateTable(parseCreateTable(sql), wrap);
 
@@ -4277,9 +4247,9 @@ describe('SQLite parser and compiler', function () {
 
     it('wrap', function () {
       const sql =
-        'CREATE INDEX "schema".[users index] on `users` ("foo " ASC, [b a z] DESC, ` bar`)';
+        'CREATE INDEX "schema".[users index] on `users` ("foo " ASC, [b a z] DESC, ` bar`, \'lar\')';
       const newSql =
-        'CREATE INDEX "schema"."users index" on "users" ("foo " ASC, "b a z" DESC, " bar")';
+        'CREATE INDEX "schema"."users index" on "users" ("foo " ASC, "b a z" DESC, " bar", "lar")';
 
       const parsedSql = compileCreateIndex(parseCreateIndex(sql), wrap);