heavily modified sections of postgres adapter, postgres is now passing all tests
This commit is contained in:
parent
61a0795447
commit
3dcdb1e911
File diff suppressed because one or more lines are too long
|
@ -5,6 +5,7 @@ var safeRequire = require('../utils').safeRequire;
|
|||
*/
|
||||
var pg = safeRequire('pg');
|
||||
var BaseSQL = require('../sql');
|
||||
var util = require('util');
|
||||
|
||||
exports.initialize = function initializeSchema(schema, callback) {
|
||||
if (!pg) return;
|
||||
|
@ -187,9 +188,6 @@ PG.prototype.fromDatabase = function (model, data) {
|
|||
var props = this._models[model].properties;
|
||||
Object.keys(data).forEach(function (key) {
|
||||
var val = data[key];
|
||||
if (props[key]) {
|
||||
// if (props[key])
|
||||
}
|
||||
data[key] = val;
|
||||
});
|
||||
return data;
|
||||
|
@ -269,12 +267,24 @@ PG.prototype.toFilter = function (model, filter) {
|
|||
return out;
|
||||
};
|
||||
|
||||
function getTableStatus(model, cb){
|
||||
function decoratedCallback(err, data){
|
||||
data.forEach(function(field){
|
||||
field.Type = mapPostgresDatatypes(field.Type);
|
||||
});
|
||||
cb(err, data);
|
||||
};
|
||||
this.query('SELECT column_name as "Field", udt_name as "Type", is_nullable as "Null", column_default as "Default" FROM information_schema.COLUMNS WHERE table_name = \'' + this.table(model) + '\'', decoratedCallback);
|
||||
};
|
||||
|
||||
PG.prototype.autoupdate = function (cb) {
|
||||
var self = this;
|
||||
var wait = 0;
|
||||
Object.keys(this._models).forEach(function (model) {
|
||||
wait += 1;
|
||||
self.query('SELECT column_name as "Field", udt_name as "Type", is_nullable as "Null", column_default as "Default" FROM information_schema.COLUMNS WHERE table_name = \''+ self.table(model) + '\'', function (err, fields) {
|
||||
var fields;
|
||||
getTableStatus.call(self, model, function(err, fields){
|
||||
if(err) console.log(err);
|
||||
self.alterTable(model, fields, done);
|
||||
});
|
||||
});
|
||||
|
@ -286,64 +296,173 @@ PG.prototype.autoupdate = function (cb) {
|
|||
if (--wait === 0 && cb) {
|
||||
cb();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
PG.prototype.isActual = function(cb) {
|
||||
var self = this;
|
||||
var wait = 0;
|
||||
changes = [];
|
||||
Object.keys(this._models).forEach(function (model) {
|
||||
wait += 1;
|
||||
getTableStatus.call(self, model, function(err, fields){
|
||||
changes = changes.concat(getPendingChanges.call(self, model, fields));
|
||||
done(err, changes);
|
||||
});
|
||||
});
|
||||
|
||||
function done(err, fields) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
if (--wait === 0 && cb) {
|
||||
var actual = (changes.length === 0);
|
||||
cb(null, actual);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
PG.prototype.alterTable = function (model, actualFields, done) {
|
||||
var self = this;
|
||||
var m = this._models[model];
|
||||
var pendingChanges = getPendingChanges.call(self, model, actualFields);
|
||||
applySqlChanges.call(self, model, pendingChanges, done);
|
||||
};
|
||||
|
||||
function getPendingChanges(model, actualFields){
|
||||
var sql = [];
|
||||
var self = this;
|
||||
sql = sql.concat(getColumnsToAdd.call(self, model, actualFields));
|
||||
sql = sql.concat(getPropertiesToModify.call(self, model, actualFields));
|
||||
sql = sql.concat(getColumnsToDrop.call(self, model, actualFields));
|
||||
return sql;
|
||||
};
|
||||
|
||||
function getColumnsToAdd(model, actualFields){
|
||||
var self = this;
|
||||
var m = self._models[model];
|
||||
var propNames = Object.keys(m.properties);
|
||||
var sql = [];
|
||||
|
||||
// change/add new fields
|
||||
propNames.forEach(function (propName) {
|
||||
var found;
|
||||
var found = searchForPropertyInActual.call(self, propName, actualFields);
|
||||
if(!found && propertyHasNotBeenDeleted.call(self, model, propName)){
|
||||
sql.push(addPropertyToActual.call(self, model, propName));
|
||||
}
|
||||
});
|
||||
return sql;
|
||||
};
|
||||
|
||||
function addPropertyToActual(model, propName){
|
||||
var self = this;
|
||||
var p = self._models[model].properties[propName];
|
||||
sqlCommand = 'ADD COLUMN "' + propName + '" ' + datatype(p) + " " + (propertyCanBeNull.call(self, model, propName) ? "" : " NOT NULL");
|
||||
return sqlCommand;
|
||||
};
|
||||
|
||||
function searchForPropertyInActual(propName, actualFields){
|
||||
var found = false;
|
||||
actualFields.forEach(function (f) {
|
||||
if (f.Field === propName) {
|
||||
found = f;
|
||||
return;
|
||||
}
|
||||
});
|
||||
return found;
|
||||
};
|
||||
|
||||
function getPropertiesToModify(model, actualFields){
|
||||
var self = this;
|
||||
var sql = [];
|
||||
var m = self._models[model];
|
||||
var propNames = Object.keys(m.properties);
|
||||
var found;
|
||||
propNames.forEach(function (propName) {
|
||||
found = searchForPropertyInActual.call(self, propName, actualFields);
|
||||
if(found && propertyHasNotBeenDeleted.call(self, model, propName)){
|
||||
if (datatypeChanged(propName, found)) {
|
||||
sql.push(modifyDatatypeInActual.call(self, model, propName));
|
||||
}
|
||||
if (nullabilityChanged(propName, found)){
|
||||
sql.push(modifyNullabilityInActual.call(self, model, propName));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (found) {
|
||||
actualize(propName, found);
|
||||
} else {
|
||||
sql.push('ADD COLUMN "' + propName + '" ' + self.propertySettingsSQL(model, propName));
|
||||
}
|
||||
});
|
||||
return sql;
|
||||
|
||||
// drop columns
|
||||
actualFields.forEach(function (f) {
|
||||
var notFound = !~propNames.indexOf(f.Field);
|
||||
if (f.Field === 'id') return;
|
||||
if (notFound || !m.properties[f.Field]) {
|
||||
sql.push('DROP COLUMN "' + f.Field + '"');
|
||||
}
|
||||
});
|
||||
|
||||
if (sql.length) {
|
||||
this.query('ALTER TABLE ' + this.tableEscaped(model) + ' ' + sql.join(',\n'), done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
|
||||
function actualize(propName, oldSettings) {
|
||||
function datatypeChanged(propName, oldSettings){
|
||||
var newSettings = m.properties[propName];
|
||||
if (newSettings && changed(newSettings, oldSettings)) {
|
||||
sql.push('CHANGE COLUMN "' + propName + '" "' + propName + '" ' + self.propertySettingsSQL(model, propName));
|
||||
if(!newSettings) return false;
|
||||
return oldSettings.Type.toLowerCase() !== datatype(newSettings);
|
||||
};
|
||||
|
||||
function nullabilityChanged(propName, oldSettings){
|
||||
var newSettings = m.properties[propName];
|
||||
if(!newSettings) return false;
|
||||
var changed = false;
|
||||
if (oldSettings.Null === 'YES' && (newSettings.allowNull === false || newSettings.null === false)) changed = true;
|
||||
if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false || newSettings.null === false)) changed = true;
|
||||
return changed;
|
||||
};
|
||||
};
|
||||
|
||||
function modifyDatatypeInActual(model, propName) {
|
||||
var self = this;
|
||||
var sqlCommand = 'ALTER COLUMN "' + propName + '" TYPE ' + datatype(self._models[model].properties[propName]);
|
||||
return sqlCommand;
|
||||
};
|
||||
|
||||
function modifyNullabilityInActual(model, propName) {
|
||||
var self = this;
|
||||
var sqlCommand = 'ALTER COLUMN "' + propName + '" ';
|
||||
if(propertyCanBeNull.call(self, model, propName)){
|
||||
sqlCommand = sqlCommand + "DROP ";
|
||||
} else {
|
||||
sqlCommand = sqlCommand + "SET ";
|
||||
}
|
||||
sqlCommand = sqlCommand + "NOT NULL";
|
||||
return sqlCommand;
|
||||
};
|
||||
|
||||
function getColumnsToDrop(model, actualFields){
|
||||
var self = this;
|
||||
var sql = [];
|
||||
actualFields.forEach(function (actualField) {
|
||||
if (actualField.Field === 'id') return;
|
||||
if (actualFieldNotPresentInModel(actualField, model)) {
|
||||
sql.push('DROP COLUMN "' + actualField.Field + '"');
|
||||
}
|
||||
});
|
||||
return sql;
|
||||
|
||||
function actualFieldNotPresentInModel(actualField, model){
|
||||
return !(self._models[model].properties[actualField.Field]);
|
||||
};
|
||||
};
|
||||
|
||||
function applySqlChanges(model, pendingChanges, done){
|
||||
var self = this;
|
||||
if (pendingChanges.length) {
|
||||
var thisQuery = 'ALTER TABLE ' + self.tableEscaped(model);
|
||||
var ranOnce = false;
|
||||
pendingChanges.forEach(function(change){
|
||||
if(ranOnce) thisQuery = thisQuery + ',';
|
||||
thisQuery = thisQuery + ' ' + change;
|
||||
ranOnce = true;
|
||||
});
|
||||
thisQuery = thisQuery + ';';
|
||||
self.query(thisQuery, callback);
|
||||
}
|
||||
|
||||
function changed(newSettings, oldSettings) {
|
||||
if (oldSettings.Null === 'YES' && (newSettings.allowNull === false || newSettings.null === false)) return true;
|
||||
if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false || newSettings.null === false)) return true;
|
||||
if (oldSettings.Type.toUpperCase() !== datatype(newSettings)) return true;
|
||||
return false;
|
||||
function callback(err, data){
|
||||
if(err) console.log(err);
|
||||
}
|
||||
|
||||
done();
|
||||
};
|
||||
|
||||
PG.prototype.propertiesSQL = function (model) {
|
||||
var self = this;
|
||||
var sql = ['"id" SERIAL NOT NULL UNIQUE PRIMARY KEY'];
|
||||
var sql = ['"id" SERIAL PRIMARY KEY'];
|
||||
Object.keys(this._models[model].properties).forEach(function (prop) {
|
||||
sql.push('"' + prop + '" ' + self.propertySettingsSQL(model, prop));
|
||||
});
|
||||
|
@ -351,10 +470,17 @@ PG.prototype.propertiesSQL = function (model) {
|
|||
|
||||
};
|
||||
|
||||
PG.prototype.propertySettingsSQL = function (model, prop) {
|
||||
var p = this._models[model].properties[prop];
|
||||
return datatype(p) + ' ' +
|
||||
(p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL');
|
||||
PG.prototype.propertySettingsSQL = function (model, propName) {
|
||||
var self = this;
|
||||
var p = self._models[model].properties[propName];
|
||||
var result = datatype(p) + ' ';
|
||||
if(!propertyCanBeNull.call(self, model, propName)) result = result + 'NOT NULL ';
|
||||
return result;
|
||||
};
|
||||
|
||||
function propertyCanBeNull(model, propName){
|
||||
var p = this._models[model].properties[propName];
|
||||
return !(p.allowNull === false || p['null'] === false);
|
||||
};
|
||||
|
||||
function escape(val) {
|
||||
|
@ -390,14 +516,32 @@ function escape(val) {
|
|||
function datatype(p) {
|
||||
switch (p.type.name) {
|
||||
case 'String':
|
||||
return 'VARCHAR(' + (p.limit || 255) + ')';
|
||||
return 'varchar';
|
||||
case 'Text':
|
||||
return 'TEXT';
|
||||
return 'text';
|
||||
case 'Number':
|
||||
return 'INTEGER';
|
||||
return 'integer';
|
||||
case 'Date':
|
||||
return 'TIMESTAMP';
|
||||
return 'timestamp';
|
||||
case 'Boolean':
|
||||
return 'BOOLEAN';
|
||||
return 'boolean';
|
||||
default:
|
||||
console.log("Warning: postgres adapter does not explicitly handle type '" + p.type.name +"'");
|
||||
return p.type.toLowerCase();
|
||||
//TODO a default case might not be the safest thing here... postgres has a fair number of extra types though
|
||||
}
|
||||
};
|
||||
|
||||
function mapPostgresDatatypes(typeName) {
|
||||
//TODO there are a lot of synonymous type names that should go here-- this is just what i've run into so far
|
||||
switch (typeName){
|
||||
case 'int4':
|
||||
return 'integer';
|
||||
default:
|
||||
return typeName;
|
||||
}
|
||||
};
|
||||
|
||||
function propertyHasNotBeenDeleted(model, propName){
|
||||
return !!this._models[model].properties[propName];
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ juggling = require('../index')
|
|||
Schema = juggling.Schema
|
||||
Text = Schema.Text
|
||||
|
||||
DBNAME = process.env.DBNAME || 'myapp_test'
|
||||
DBNAME = process.env.DBNAME || 'myapp_test' #this db must already exist and will be destroyed
|
||||
DBUSER = process.env.DBUSER || 'root'
|
||||
DBPASS = ''
|
||||
DBENGINE = process.env.DBENGINE || 'postgres'
|
||||
|
|
|
@ -2,7 +2,7 @@ juggling = require('../index')
|
|||
Schema = juggling.Schema
|
||||
Text = Schema.Text
|
||||
|
||||
DBNAME = process.env.DBNAME || 'myapp_test'
|
||||
DBNAME = process.env.DBNAME || 'myapp_test' #this db must already exist and will be destroyed
|
||||
DBUSER = process.env.DBUSER || 'root'
|
||||
DBPASS = ''
|
||||
DBENGINE = process.env.DBENGINE || 'postgres'
|
||||
|
|
Loading…
Reference in New Issue