diff --git a/lib/dialects/sqlite3/schema/ddl.js b/lib/dialects/sqlite3/schema/ddl.js index 5aaf569419..74f3b26981 100644 --- a/lib/dialects/sqlite3/schema/ddl.js +++ b/lib/dialects/sqlite3/schema/ddl.js @@ -344,6 +344,40 @@ class SQLite3_DDL { ); } + setNullable(column, isNullable) { + return this.client.transaction( + async (trx) => { + this.trx = trx; + + const { createTable, createIndices } = await this.getTableSql(); + + const parsedTable = parseCreateTable(createTable); + const parsedColumn = parsedTable.columns.find((c) => + isEqualId(column, c.name) + ); + + if (!parsedColumn) { + throw new Error( + `.setNullable: Column ${column} does not exist in table ${this.tableName()}.` + ); + } + + parsedColumn.constraints.notnull = isNullable + ? null + : { name: null, conflict: null }; + + parsedColumn.constraints.null = isNullable + ? parsedColumn.constraints.null + : null; + + const newTable = compileCreateTable(parsedTable, this.wrap); + + return this.generateAlterCommands(newTable, createIndices); + }, + { connection: this.connection } + ); + } + async alter(newSql, createIndices, columns) { await this.createNewTable(newSql); await this.copyData(columns); diff --git a/lib/dialects/sqlite3/schema/sqlite-tablecompiler.js b/lib/dialects/sqlite3/schema/sqlite-tablecompiler.js index 5aba13edbe..4627c70a9d 100644 --- a/lib/dialects/sqlite3/schema/sqlite-tablecompiler.js +++ b/lib/dialects/sqlite3/schema/sqlite-tablecompiler.js @@ -266,8 +266,16 @@ class TableCompiler_SQLite3 extends TableCompiler { } _setNullableState(column, isNullable) { - const fnCalled = isNullable ? '.setNullable' : '.dropNullable'; - throw new Error(`${fnCalled} is not supported for SQLite.`); + const compiler = this; + + this.pushQuery({ + sql: `PRAGMA table_info(${this.tableName()})`, + statementsProducer(pragma, connection) { + return compiler.client + .ddl(compiler, pragma, connection) + .setNullable(column, isNullable); + }, + }); } dropColumn() { diff --git a/test/integration2/schema/set-nullable.spec.js b/test/integration2/schema/set-nullable.spec.js index 4061b279ce..03a46116ff 100644 --- a/test/integration2/schema/set-nullable.spec.js +++ b/test/integration2/schema/set-nullable.spec.js @@ -1,6 +1,7 @@ const chai = require('chai'); chai.use(require('chai-as-promised')); const expect = chai.expect; +const sinon = require('sinon'); const { isSQLite, isPostgreSQL, @@ -16,13 +17,12 @@ describe('Schema', () => { let knex; before(function () { + sinon.stub(Math, 'random').returns(0.1); knex = getKnexForDb(db); - if (isSQLite(knex)) { - return this.skip(); - } }); after(() => { + sinon.restore(); return knex.destroy(); }); @@ -38,6 +38,31 @@ describe('Schema', () => { }); describe('setNullable', () => { + it('should generate correct SQL for set nullable operation', async () => { + const builder = knex.schema.table('primary_table', (table) => { + table.setNullable('id_not_nullable'); + }); + const queries = await builder.generateDdlCommands(); + + if (isSQLite(knex)) { + expect(queries.sql).to.eql([ + 'CREATE TABLE `_knex_temp_alter111` (`id_nullable` integer NULL, `id_not_nullable` integer)', + 'INSERT INTO _knex_temp_alter111 SELECT * FROM primary_table;', + 'DROP TABLE "primary_table"', + 'ALTER TABLE "_knex_temp_alter111" RENAME TO "primary_table"', + ]); + } + + if (isPostgreSQL(knex)) { + expect(queries.sql).to.eql([ + { + bindings: [], + sql: 'alter table "primary_table" alter column "id_not_nullable" drop not null', + }, + ]); + } + }); + it('sets column to be nullable', async () => { await knex.schema.table('primary_table', (table) => { table.setNullable('id_not_nullable'); @@ -63,6 +88,9 @@ describe('Schema', () => { errorMessage = 'cannot be null'; } else if (isOracle(knex)) { errorMessage = 'ORA-01400: cannot insert NULL into'; + } else if (isSQLite(knex)) { + errorMessage = + 'insert into `primary_table` (`id_not_nullable`, `id_nullable`) values (1, NULL) - SQLITE_CONSTRAINT: NOT NULL constraint failed: primary_table.id_nullable'; } await expect(