2014-09-18 10:59:18 -07:00

173 lines
5.3 KiB
JavaScript

'use strict';
// SQLite3_DDL
//
// All of the SQLite3 specific DDL helpers for renaming/dropping
// columns and changing datatypes.
// -------
module.exports = function(client) {
var _ = require('lodash');
var Promise = require('../../../promise');
// So altering the schema in SQLite3 is a major pain.
// We have our own object to deal with the renaming and altering the types
// for sqlite3 things.
function SQLite3_DDL(runner, tableCompiler, pragma) {
this.tableCompiler = tableCompiler;
this.pragma = pragma;
this.runner = runner;
this.formatter = new client.Formatter();
this.tableName = this.tableCompiler.tableNameRaw;
this.alteredName = '_knex_temp_alter' + _.uniqueId();
}
SQLite3_DDL.prototype.getColumn = Promise.method(function(column) {
var currentCol = _.findWhere(this.pragma, {name: column});
if (!currentCol) throw new Error('The column ' + column + ' is not in the ' + this.tableName + ' table');
return currentCol;
});
SQLite3_DDL.prototype.ensureTransaction = Promise.method(function() {
if (!this.runner.transaction) {
return this.runner.beginTransaction();
}
});
SQLite3_DDL.prototype.commitTransaction = Promise.method(function() {
if (!this.runner.transaction) {
return this.runner.commitTransaction();
}
});
SQLite3_DDL.prototype.rollbackTransaction = function(e) {
if (this.runner.transaction) throw e;
return this.runner.rollbackTransaction().throw(e);
};
SQLite3_DDL.prototype.getTableSql = function() {
return this.runner.query({sql: 'SELECT name, sql FROM sqlite_master WHERE type="table" AND name="' + this.tableName + '"'});
};
SQLite3_DDL.prototype.renameTable = Promise.method(function() {
return this.runner.query({sql: 'ALTER TABLE "' + this.tableName + '" RENAME TO "' + this.alteredName + '"'});
});
SQLite3_DDL.prototype.dropOriginal = function() {
return this.runner.query({sql: 'DROP TABLE "' + this.tableName + '"'});
};
SQLite3_DDL.prototype.dropTempTable = function() {
return this.runner.query({sql: 'DROP TABLE "' + this.alteredName + '"'});
};
SQLite3_DDL.prototype.copyData = function() {
return this.runner.query({sql: 'SELECT * FROM "' + this.tableName + '"'})
.bind(this)
.then(this.insertChunked(20, this.alteredName));
};
SQLite3_DDL.prototype.reinsertData = function(iterator) {
return function() {
return this.runner.query({sql: 'SELECT * FROM "' + this.alteredName + '"'})
.bind(this)
.then(this.insertChunked(20, this.tableName, iterator));
};
};
SQLite3_DDL.prototype.insertChunked = function(amount, target, iterator) {
iterator = iterator || function(noop) { return noop; };
return function(result) {
var batch = [];
var ddl = this;
return Promise.reduce(result, function(memo, row) {
memo++;
batch.push(row);
if (memo % 20 === 0 || memo === result.length) {
return new client.QueryBuilder()
.connection(ddl.runner.connection)
.table(target)
.insert(_.map(batch, iterator))
.then(function() { batch = []; })
.thenReturn(memo);
}
return memo;
}, 0);
};
};
SQLite3_DDL.prototype.createTempTable = function(createTable) {
return function() {
return this.runner.query({sql: createTable.sql.replace(this.tableName, this.alteredName)});
};
};
// Boy, this is quite a method.
SQLite3_DDL.prototype.renameColumn = Promise.method(function(from, to) {
var currentCol;
return this.ensureTransaction()
.bind(this)
.then(function() {
return this.getColumn(from);
})
.tap(function(col) { currentCol = col; })
.then(this.getTableSql)
.then(function(sql) {
var createTable = sql[0];
var a = this.formatter.wrap(from) + ' ' + currentCol.type;
var b = this.formatter.wrap(to) + ' ' + currentCol.type;
if (createTable.sql.indexOf(a) === -1) {
throw new Error('Unable to find the column to change');
}
return Promise.bind(this)
.then(this.createTempTable(createTable))
.then(this.copyData)
.then(this.dropOriginal)
.then(function() {
return this.runner.query({sql: createTable.sql.replace(a, b)});
})
.then(this.reinsertData(function(row) {
row[to] = row[from];
return _.omit(row, from);
}))
.then(this.dropTempTable);
})
.tap(this.commitTransaction)
.catch(this.rollbackTransaction);
});
SQLite3_DDL.prototype.dropColumn = Promise.method(function(column) {
var currentCol;
return this.ensureTransaction()
.bind(this)
.then(function() {
return this.getColumn(column);
})
.tap(function(col) { currentCol = col; })
.then(this.getTableSql)
.then(function(sql) {
var createTable = sql[0];
var a = this.formatter.wrap(column) + ' ' + currentCol.type + ', ';
if (createTable.sql.indexOf(a) === -1) {
throw new Error('Unable to find the column to change');
}
return Promise.bind(this)
.then(this.createTempTable(createTable))
.then(this.copyData)
.then(this.dropOriginal)
.then(function() {
return this.runner.query({sql: createTable.sql.replace(a, '')});
})
.then(this.reinsertData(function(row) {
return _.omit(row, column);
}))
.then(this.dropTempTable);
})
.tap(this.commitTransaction)
.catch(this.rollbackTransaction);
});
client.SQLite3_DDL = SQLite3_DDL;
};