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 pg = safeRequire('pg');
|
||||||
var BaseSQL = require('../sql');
|
var BaseSQL = require('../sql');
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
exports.initialize = function initializeSchema(schema, callback) {
|
exports.initialize = function initializeSchema(schema, callback) {
|
||||||
if (!pg) return;
|
if (!pg) return;
|
||||||
|
@ -187,9 +188,6 @@ PG.prototype.fromDatabase = function (model, data) {
|
||||||
var props = this._models[model].properties;
|
var props = this._models[model].properties;
|
||||||
Object.keys(data).forEach(function (key) {
|
Object.keys(data).forEach(function (key) {
|
||||||
var val = data[key];
|
var val = data[key];
|
||||||
if (props[key]) {
|
|
||||||
// if (props[key])
|
|
||||||
}
|
|
||||||
data[key] = val;
|
data[key] = val;
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
|
@ -269,12 +267,24 @@ PG.prototype.toFilter = function (model, filter) {
|
||||||
return out;
|
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) {
|
PG.prototype.autoupdate = function (cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var wait = 0;
|
var wait = 0;
|
||||||
Object.keys(this._models).forEach(function (model) {
|
Object.keys(this._models).forEach(function (model) {
|
||||||
wait += 1;
|
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);
|
self.alterTable(model, fields, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -286,64 +296,173 @@ PG.prototype.autoupdate = function (cb) {
|
||||||
if (--wait === 0 && cb) {
|
if (--wait === 0 && cb) {
|
||||||
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) {
|
PG.prototype.alterTable = function (model, actualFields, done) {
|
||||||
|
var self = this;
|
||||||
|
var pendingChanges = getPendingChanges.call(self, model, actualFields);
|
||||||
|
applySqlChanges.call(self, model, pendingChanges, done);
|
||||||
|
};
|
||||||
|
|
||||||
|
function getPendingChanges(model, actualFields){
|
||||||
|
var sql = [];
|
||||||
var self = this;
|
var self = this;
|
||||||
var m = this._models[model];
|
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 propNames = Object.keys(m.properties);
|
||||||
var sql = [];
|
var sql = [];
|
||||||
|
|
||||||
// change/add new fields
|
|
||||||
propNames.forEach(function (propName) {
|
propNames.forEach(function (propName) {
|
||||||
var found;
|
var found = searchForPropertyInActual.call(self, propName, actualFields);
|
||||||
actualFields.forEach(function (f) {
|
if(!found && propertyHasNotBeenDeleted.call(self, model, propName)){
|
||||||
if (f.Field === propName) {
|
sql.push(addPropertyToActual.call(self, model, propName));
|
||||||
found = f;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (found) {
|
|
||||||
actualize(propName, found);
|
|
||||||
} else {
|
|
||||||
sql.push('ADD COLUMN "' + propName + '" ' + self.propertySettingsSQL(model, propName));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return sql;
|
||||||
|
};
|
||||||
|
|
||||||
// drop columns
|
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) {
|
actualFields.forEach(function (f) {
|
||||||
var notFound = !~propNames.indexOf(f.Field);
|
if (f.Field === propName) {
|
||||||
if (f.Field === 'id') return;
|
found = f;
|
||||||
if (notFound || !m.properties[f.Field]) {
|
return;
|
||||||
sql.push('DROP COLUMN "' + f.Field + '"');
|
}
|
||||||
|
});
|
||||||
|
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 (sql.length) {
|
return sql;
|
||||||
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];
|
var newSettings = m.properties[propName];
|
||||||
if (newSettings && changed(newSettings, oldSettings)) {
|
if(!newSettings) return false;
|
||||||
sql.push('CHANGE COLUMN "' + propName + '" "' + propName + '" ' + self.propertySettingsSQL(model, propName));
|
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) {
|
function callback(err, data){
|
||||||
if (oldSettings.Null === 'YES' && (newSettings.allowNull === false || newSettings.null === false)) return true;
|
if(err) console.log(err);
|
||||||
if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false || newSettings.null === false)) return true;
|
|
||||||
if (oldSettings.Type.toUpperCase() !== datatype(newSettings)) return true;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
done();
|
||||||
};
|
};
|
||||||
|
|
||||||
PG.prototype.propertiesSQL = function (model) {
|
PG.prototype.propertiesSQL = function (model) {
|
||||||
var self = this;
|
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) {
|
Object.keys(this._models[model].properties).forEach(function (prop) {
|
||||||
sql.push('"' + prop + '" ' + self.propertySettingsSQL(model, prop));
|
sql.push('"' + prop + '" ' + self.propertySettingsSQL(model, prop));
|
||||||
});
|
});
|
||||||
|
@ -351,10 +470,17 @@ PG.prototype.propertiesSQL = function (model) {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
PG.prototype.propertySettingsSQL = function (model, prop) {
|
PG.prototype.propertySettingsSQL = function (model, propName) {
|
||||||
var p = this._models[model].properties[prop];
|
var self = this;
|
||||||
return datatype(p) + ' ' +
|
var p = self._models[model].properties[propName];
|
||||||
(p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL');
|
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) {
|
function escape(val) {
|
||||||
|
@ -390,14 +516,32 @@ function escape(val) {
|
||||||
function datatype(p) {
|
function datatype(p) {
|
||||||
switch (p.type.name) {
|
switch (p.type.name) {
|
||||||
case 'String':
|
case 'String':
|
||||||
return 'VARCHAR(' + (p.limit || 255) + ')';
|
return 'varchar';
|
||||||
case 'Text':
|
case 'Text':
|
||||||
return 'TEXT';
|
return 'text';
|
||||||
case 'Number':
|
case 'Number':
|
||||||
return 'INTEGER';
|
return 'integer';
|
||||||
case 'Date':
|
case 'Date':
|
||||||
return 'TIMESTAMP';
|
return 'timestamp';
|
||||||
case 'Boolean':
|
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
|
Schema = juggling.Schema
|
||||||
Text = Schema.Text
|
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'
|
DBUSER = process.env.DBUSER || 'root'
|
||||||
DBPASS = ''
|
DBPASS = ''
|
||||||
DBENGINE = process.env.DBENGINE || 'postgres'
|
DBENGINE = process.env.DBENGINE || 'postgres'
|
||||||
|
|
|
@ -2,7 +2,7 @@ juggling = require('../index')
|
||||||
Schema = juggling.Schema
|
Schema = juggling.Schema
|
||||||
Text = Schema.Text
|
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'
|
DBUSER = process.env.DBUSER || 'root'
|
||||||
DBPASS = ''
|
DBPASS = ''
|
||||||
DBENGINE = process.env.DBENGINE || 'postgres'
|
DBENGINE = process.env.DBENGINE || 'postgres'
|
||||||
|
|
Loading…
Reference in New Issue