Comparing version 0.2.11 to 0.3.0
!function(){ | ||
var Class = require('ee-class') | ||
, EventEmitter = require('ee-event-emitter') | ||
, log = require('ee-log') | ||
, Model = require('./Model') | ||
, Transaction = require('./Transaction'); | ||
var Class = require('ee-class') | ||
, EventEmitter = require('ee-event-emitter') | ||
, log = require('ee-log') | ||
, argv = require('ee-argv') | ||
, Entity = require('./Entity') | ||
, Transaction = require('./Transaction'); | ||
var dev = argv.has('dev-orm'); | ||
module.exports = new Class({ | ||
inherits: EventEmitter | ||
, init: function(options) { | ||
// remove deprecated parent property | ||
delete this.parent; | ||
// orm | ||
this._setProperty('_orm', options.orm); | ||
this._setProperty('_database', options.database); | ||
this._setProperty('_queryBuilders', {}); | ||
module.exports = new Class({ | ||
inherits: EventEmitter | ||
// initialize the orm | ||
this._initialize(options.definition); | ||
, init: function(options) { | ||
if (dev) log.warn('initialize new db instance for «'+options.definition.getDatabaseName()+'»...'); | ||
// emit load not before the next main loop execution | ||
process.nextTick(function(){ | ||
this.emit('load'); | ||
}.bind(this)); | ||
} | ||
Class.define(this, '_orm', Class(options.orm)); | ||
Class.define(this, '_database', Class(options.database)); | ||
Class.define(this, '_queryBuilders', Class({})); | ||
// initialize the orm | ||
this._initialize(options.definition); | ||
// emit load not before the next main loop execution | ||
process.nextTick(function(){ | ||
this.emit('load'); | ||
}.bind(this)); | ||
} | ||
, createTransaction: function() { | ||
return new Transaction(this); | ||
} | ||
, createTransaction: function() { | ||
return new Transaction(this); | ||
} | ||
, executeQuery: function(mode, query, callback) { | ||
this._database.query(mode, query, callback); | ||
} | ||
, executeQuery: function(mode, query, callback) { | ||
this._database.query(mode, query, callback); | ||
} | ||
, _setProperty: function(name, value){ | ||
Object.defineProperty(this, name, {value: value, writable: true}); | ||
} | ||
, _getDatabase: function(){ | ||
return this; | ||
} | ||
, _getDatabase: function(){ | ||
return this; | ||
} | ||
, _initialize: function(definition){ | ||
Object.keys(definition).forEach(function(tablename){ | ||
if (this[tablename]) next(new Error('Failed to load ORM for database «'+definition.getDatabaseName()+'», the tablename «'+tablename+'» is reserved for the orm.').setName('ORMException')); | ||
, _initialize: function(definition){ | ||
Object.keys(definition).forEach(function(tablename){ | ||
if (this[tablename]) next(new Error('Failed to load ORM for database «'+definition.getDatabaseName()+'», the tablename «'+tablename+'» is reserved for the orm.').setName('ORMException')); | ||
if (dev) log.debug('['+definition.getDatabaseName()+'] initializing new model «'+tablename+'» ...'); | ||
this[tablename] = new Entity({ | ||
orm : this._orm | ||
, definition : definition[tablename] | ||
, queryBuilders : this._queryBuilders | ||
, getDatabase : this._getDatabase.bind(this) | ||
}); | ||
}.bind(this)); | ||
} | ||
this[tablename] = new Model({ | ||
orm : this._orm | ||
, definition : definition[tablename] | ||
, queryBuilders : this._queryBuilders | ||
, getDatabase : this._getDatabase.bind(this) | ||
}); | ||
}.bind(this)); | ||
} | ||
}); | ||
}); | ||
}(); |
!function(){ | ||
var Class = require('ee-class') | ||
, EventEmitter = require('ee-event-emitter') | ||
, log = require('ee-log'); | ||
var Class = require('ee-class') | ||
, EventEmitter = require('ee-event-emitter') | ||
, log = require('ee-log'); | ||
@@ -10,45 +10,45 @@ | ||
module.exports = new Class({ | ||
module.exports = new Class({ | ||
unformatted: true | ||
unformatted: true | ||
, init: function(options) { | ||
this.type = options.type || 'inner'; | ||
this.source = options.source; | ||
this.target = options.target; | ||
} | ||
, init: function(options) { | ||
this.type = options.type || 'inner'; | ||
this.source = options.source; | ||
this.target = options.target; | ||
} | ||
, reverseFormat: function(aliasSuffix, secondAliasSuffix) { | ||
return { | ||
type : this.type | ||
, source : { | ||
table : this.target.table + (secondAliasSuffix === undefined ? '' : secondAliasSuffix) | ||
, column : this.target.column | ||
} | ||
, target : this.source | ||
, alias : this.source.table + (aliasSuffix === undefined ? '' : aliasSuffix) | ||
}; | ||
} | ||
, reverseFormat: function(aliasSuffix, secondAliasSuffix) { | ||
return { | ||
type : this.type | ||
, source : { | ||
table : this.target.table + (secondAliasSuffix === undefined ? '' : secondAliasSuffix) | ||
, column : this.target.column | ||
} | ||
, target : this.source | ||
, alias : this.source.table + (aliasSuffix === undefined ? '' : aliasSuffix) | ||
}; | ||
} | ||
, format: function(aliasSuffix, secondAliasSuffix) { | ||
return { | ||
type : this.type | ||
, source : this.source | ||
, target : { | ||
table : this.target.table + (secondAliasSuffix === undefined ? '' : secondAliasSuffix) | ||
, column : this.target.column | ||
} | ||
, alias : this.target.table + (aliasSuffix === undefined ? '' : aliasSuffix) | ||
}; | ||
} | ||
, format: function(aliasSuffix, secondAliasSuffix) { | ||
return { | ||
type : this.type | ||
, source : this.source | ||
, target : { | ||
table : this.target.table + (secondAliasSuffix === undefined ? '' : secondAliasSuffix) | ||
, column : this.target.column | ||
} | ||
, alias : this.target.table + (aliasSuffix === undefined ? '' : aliasSuffix) | ||
}; | ||
} | ||
, _addAlias: function(config, alias) { | ||
config.table = config.table +(alias === undefined ? '' : alias); | ||
return config; | ||
} | ||
}); | ||
, _addAlias: function(config, alias) { | ||
config.table = config.table +(alias === undefined ? '' : alias); | ||
return config; | ||
} | ||
}); | ||
}(); |
711
lib/Model.js
!function(){ | ||
var Class = require('ee-class') | ||
, EventEmitter = require('ee-event-emitter') | ||
, QueryBuilder = require('./QueryBuilder') | ||
, DefaultModel = require('./DefaultModel') | ||
, RelatingSet = require('./RelatingSet') | ||
, clone = require('clone') | ||
, log = require('ee-log'); | ||
var Class = require('ee-class') | ||
, Arguments = require('ee-arguments') | ||
, async = require('ee-async') | ||
, RelatingSet = require('./RelatingSet') | ||
, EventEmitter = require('ee-event-emitter') | ||
, type = require('ee-types') | ||
, log = require('ee-log'); | ||
// model initializer | ||
module.exports = new Class({ | ||
init: function(_options){ | ||
var that = this; | ||
module.exports = new Class({ | ||
inherits: EventEmitter | ||
this._definition = _options.definition; | ||
this._options = _options; | ||
this._orm = _options.orm; | ||
, init: function(options) { | ||
// add properties for this specific model | ||
this.Model = this.createModel(DefaultModel, _options); | ||
this.QueryBuilder = new QueryBuilder({ | ||
orm : _options.orm | ||
, queryBuilders : _options.queryBuilders | ||
, definition : _options.definition | ||
, getDatabase : _options.getDatabase | ||
}); | ||
Class.define(this, '_defintion' , Class(options.definition)); | ||
Class.define(this, '_orm' , Class(options.orm)); | ||
Class.define(this, '_values' , Class({})); | ||
Class.define(this, '_changedValues' , Class([])); | ||
Class.define(this, '_mappings' , Class({})); | ||
Class.define(this, '_belongsTo' , Class({})); | ||
Class.define(this, '_references' , Class({})); | ||
Class.define(this, '_changedReferences' , Class([])); | ||
Class.define(this, '_mappingIds' , Class([])); | ||
Class.define(this, '_hasChanges' , Class(false).Writable()); | ||
Class.define(this, '_relatingSets' , Class(options.relatingSets)); | ||
Class.define(this, '_getDatabase' , Class(options.getDatabase)); | ||
Class.define(this, '_fromDb' , Class(options.isFromDB || false).Writable()); | ||
_options.queryBuilders[_options.definition.getTableName()] = this.QueryBuilder; | ||
// check for changes | ||
//this.on('change', this._setChanged.bind(this)); | ||
if (options.parameters) this._setValues(options.parameters); | ||
} | ||
// constructor to expose | ||
var Constructor = function(options, relatingSets) { | ||
if (this instanceof Constructor) { | ||
, _setChanged: function() { | ||
this._hasChanges = true; | ||
} | ||
// new model instance | ||
var instance = new that.Model({ | ||
parameters : options | ||
, orm : _options.orm | ||
, definition : _options.definition | ||
, isFromDB : options && options._isFromDB | ||
, relatingSets : relatingSets | ||
, getDatabase : _options.getDatabase | ||
}); | ||
// clone events from global model | ||
/*if (this.$$$$_events) { | ||
Object.keys(this.$$$$_events).forEach(function(event){ | ||
this.$$$$_events[event].forEach(function(listener){ | ||
if (!instance.$$$$_events[event]) nstance.$$$$_events[event] = []; | ||
instance.$$$$_events[event].push(listener); | ||
}); | ||
}); | ||
}*/ | ||
, getDefinition: function() { | ||
return this._defintion; | ||
} | ||
return instance; | ||
} | ||
else { | ||
// return a querybuilder | ||
var qb = new that.QueryBuilder({ | ||
parameters: Array.prototype.slice.call(arguments) | ||
}); | ||
, isModel: function() { | ||
return true; | ||
} | ||
// call the specific method on the querybuilder | ||
// qb[_options.definition.getTableName()].apply(qb, Array.prototype.slice.call(arguments)); | ||
return qb; | ||
} | ||
}; | ||
, getEntityName: function() { | ||
return this._defintion.name; | ||
} | ||
// the constructor implements the event interface | ||
// the events are global listeners for all model instances | ||
//Constructor.__proto__ = new EventEmitter(); | ||
// the model definition must be accesible publicly | ||
Constructor.definition = _options.definition; | ||
, _setValues: function(values){ | ||
if (!type.undefined(values) && !type.object(values)) throw new Error('expecting an object contining value sfor a new model instance, got «'+type(values)+'»!'); | ||
Object.keys(values).forEach(function(property){ | ||
var item = values[property]; | ||
// expose if its a mapping table | ||
if (this._definition.isMapping) Constructor.isMapping = true; | ||
if (type.object(item) && this._addQueryObject(property, item)) return; | ||
else if (type.array(item)) { | ||
item.forEach(function(obj){ | ||
if (type.object(obj) && this._addQueryObject(property, obj)) return; | ||
else throw new Error('Expected a Query or a Model for the key «'+property+'»!'); | ||
}.bind(this)); | ||
} | ||
else if (property === '____id____') this._mappingIds.push(item); | ||
else this._values[property] = item; | ||
}.bind(this)); | ||
} | ||
// let the user define accessornames | ||
Constructor.setMappingAccessorName = this.setMappingAccessorName.bind(this); | ||
Constructor.setReferenceAccessorName = this.setReferenceAccessorName.bind(this); | ||
Constructor.getDefinition = this.getDefinition.bind(this); | ||
return Constructor; | ||
} | ||
, _addQueryObject: function(propertyName, item) { | ||
var name = propertyName; | ||
if (type.object(item) && !type.null(item) && ((item.isModel && item.isModel()) || item.isQuery)) { | ||
// we got a model | ||
// name = item.getEntityName(); | ||
// log.wtf(name, this._columns[name].type); | ||
, getDefinition: function() { | ||
return this._definition; | ||
} | ||
if (this._columns[name] && (this._columns[name].type === 'mapping' || this._columns[name].type === 'belongsTo')) this[name].push(item); | ||
else if (this._columns[name] && this._columns[name].type === 'reference') this[name] = item; | ||
else if (this._genericAccessors[name]) throw new Error('Cannot add «'+name+'» model to «'+this.getEntityName()+'» model! please use the generic accessor!'); | ||
else throw new Error('Cannot create a relation between the «'+name+'» and «'+this.getEntityName()+'» models! there is no relation between the two models!'); | ||
return true; | ||
} | ||
return false; | ||
} | ||
, setMappingAccessorName: function(mappingName, name) { | ||
if (!this.Model[name]) { | ||
this._mappingMap[mappingName].definition.name = this._orm._getPluralAccessorName(name); | ||
this._mappingMap[mappingName].definition.useGenericAccessor = false; | ||
this.Model = this.createModel(DefaultModel, this._options); | ||
} | ||
else throw new Error('The mapping accessor «'+name+'» on the model «'+this._model.name+'» is already in use!'); | ||
} | ||
, _createMappingGetter: function(mappingName, options) { | ||
return { | ||
enumerable: true | ||
, get: function() { | ||
// create referencing set only when used | ||
if (!this._mappings[mappingName]) { | ||
this._mappings[mappingName] = new RelatingSet({ | ||
orm: options.orm | ||
, definition: options.definition | ||
, column: options.column | ||
, related: this | ||
, database: options.databaseName | ||
, isMapping: true | ||
}); | ||
} | ||
this._mappings[mappingName].on('change', function(){ | ||
this._setChanged(); | ||
}.bind(this)); | ||
return this._mappings[mappingName]; | ||
} | ||
}; | ||
} | ||
, loadAll: function(callback){ | ||
return this.reload(callback); | ||
} | ||
, isFromDatabase: function(){ | ||
return this._fromDb; | ||
} | ||
, setReferenceAccessorName: function(referenceName, name) { | ||
if (!this.Model[name]) { | ||
this._referenceMap[referenceName].aliasName = name; | ||
this._referenceMap[referenceName].useGenericAccessor = false; | ||
this.Model = this.createModel(DefaultModel, this._options); | ||
} | ||
else throw new Error('The reference accessor «'+name+'» on the model «'+this._model.name+'» is already in use!'); | ||
} | ||
, isSaved: function() { | ||
this.isFromDatabase() && !this._hasChanges; | ||
} | ||
, createModel: function(model, options, additionalProperties){ | ||
var CustomModel = clone(model.definition) | ||
, databaseName = this._definition.getDatabaseName() | ||
, properties = additionalProperties || {} | ||
, mappingMap = this._mappingMap = {} | ||
, belongsToMap = this._belongsToMap = {} | ||
, referenceMap = this._referenceMap = {}; | ||
, reload: function(callback, transaction){ | ||
if (!this.isFromDatabase()) return callback(new Error('Cannot reload record «'+this.getEntityName()+'» without saving it first!')); | ||
// make sure the instantiated model can get the correct orm instance (support for transactions) | ||
CustomModel.getDatabase = options.getDatabase; | ||
var query = { | ||
select : ['*'] | ||
, from : this._defintion.getTableName() | ||
, database : this._defintion.getDatabaseName() | ||
, filter : {} | ||
}; | ||
callback = callback || function(){}; | ||
properties._columns = { | ||
value: {} | ||
}; | ||
this._defintion.primaryKeys.forEach(function(key){ | ||
query.filter[key] = this[key]; | ||
}.bind(this)); | ||
(transaction || this._getDatabase()).executeQuery(query, function(err, data){ | ||
if (err) callback(err); | ||
else { | ||
if (!data.length) callback(new Error('Failed to load data from database, record doesn\'t exist!')); | ||
else { | ||
this._setValues(data[0]); | ||
this._fromDb = true; | ||
// build model | ||
Object.keys(this._definition.columns).forEach(function(columnName){ | ||
var column = this._definition.columns[columnName] | ||
, definition = properties[columnName] = {} | ||
, referenceDefinition | ||
, referenceName; | ||
this._reloadRelated(function(err){ | ||
callback(err, this); | ||
}.bind(this), transaction); | ||
} | ||
} | ||
}.bind(this)); | ||
return this; | ||
} | ||
// mappings | ||
if (column.mapsTo){ | ||
column.mapsTo.filter(function(mapping){ | ||
mappingMap[mapping.via.model.name] = { | ||
column: column | ||
, definition: mapping | ||
}; | ||
return !mapping.useGenericAccessor; | ||
}).forEach(function(mapping){ | ||
var mappingName = mapping.via.model.name; | ||
, _reloadRelated: function(callback, transaction) { | ||
async.each(Object.keys(this._mappings), function(keyName, next){ | ||
this._mappings[keyName].reload(next, transaction); | ||
}.bind(this), callback); | ||
/* | ||
Object.keys(this._belongsTo).forEach(function(keyName){ | ||
}.bind(this)); | ||
properties._columns.value[mappingName] = { | ||
type : 'mapping' | ||
, column : column | ||
}; | ||
Object.keys(this._references).forEach(function(keyName){ | ||
}.bind(this));*/ | ||
} | ||
properties[mapping.name] = this._createMappingGetter(mappingName, { | ||
orm: options.orm | ||
, definition: mapping | ||
, column: column | ||
, database: databaseName | ||
}); | ||
}.bind(this)); | ||
} | ||
// belongs to | ||
if (column.belongsTo){ | ||
column.belongsTo.filter(function(belongs){ | ||
belongsToMap[belongs.model.name] = { | ||
column: column | ||
, definition: belongs | ||
}; | ||
return !belongs.useGenericAccessor; | ||
}).forEach(function(belongs){ | ||
var relationName = belongs.model.name; | ||
, _getChangedValues: function() { | ||
var data = {}; | ||
properties._columns.value[relationName] = { | ||
type : 'belongsTo' | ||
, column : column | ||
}; | ||
this._changedValues.forEach(function(key){ | ||
data[key] = this[key]; | ||
}.bind(this)); | ||
properties[belongs.name] = { | ||
enumerable: true | ||
, get: function() { | ||
// create referencing set only when used | ||
if (!this._belongsTo[relationName]) { | ||
this._belongsTo[relationName] = new RelatingSet({ | ||
orm: options.orm | ||
, definition: belongs | ||
, column: column | ||
, related: this | ||
, database: databaseName | ||
, isMapping: false | ||
}); | ||
return data; | ||
} | ||
this._belongsTo[relationName].on('chnage', function(){ | ||
this._setChanged(); | ||
}.bind(this)); | ||
} | ||
return this._belongsTo[relationName]; | ||
} | ||
}; | ||
}.bind(this)); | ||
} | ||
// references | ||
if (column.referencedModel) { | ||
referenceMap[column.name] = column; | ||
if (!column.useGenericAccessor) { | ||
referenceName = column.aliasName || column.referencedModel.name; | ||
referenceDefinition = properties[referenceName] = {enumerable: true}; | ||
properties._columns.value[referenceName] = { | ||
type : 'reference' | ||
, column : column | ||
}; | ||
, delete: function(callback) { | ||
var args = new Arguments(arguments) | ||
, callback = args.getFunction(function(){}) | ||
, transaction = args.getObject(); | ||
referenceDefinition.get = function(){ | ||
return this._references[referenceName]; | ||
}; | ||
if (transaction) { | ||
this._delete(transaction, callback); | ||
} | ||
else { | ||
transaction = this._getDatabase().createTransaction(); | ||
referenceDefinition.set = function(newValue){ | ||
if (this._references[referenceName] !== newValue) { | ||
this._changedReferences.push(referenceName); | ||
this._references[referenceName] = newValue; | ||
this._setChanged(); | ||
} | ||
}; | ||
} | ||
} | ||
else { | ||
definition.enumerable = true; | ||
properties._columns.value[columnName] = { | ||
type : 'scalar' | ||
, column : column | ||
}; | ||
} | ||
this._delete(transaction, function(err){ | ||
if (err) { | ||
transaction.rollback(function(transactionErr){ | ||
if (transactionErr) callback(transactionErr); | ||
else callback(err); | ||
}.bind(this)); | ||
} | ||
else { | ||
transaction.commit(function(err){ | ||
if (err) callback(err); | ||
else { | ||
this._fromDb = false; | ||
callback(null, this); | ||
} | ||
}.bind(this)); | ||
} | ||
}.bind(this)); | ||
} | ||
return this; | ||
} | ||
definition.get = function(){ | ||
return this._values[columnName]; | ||
}; | ||
definition.set = function(value){ | ||
if (this._values[columnName] !== value) { | ||
this._changedValues.push(columnName); | ||
this._values[columnName] = value; | ||
this._setChanged(); | ||
} | ||
}; | ||
}.bind(this)); | ||
, _delete: function(transaction, callback) { | ||
// generic belongsTo, mapping, refernce accessor | ||
properties.get = { | ||
value: function(targetName) { | ||
var query = { | ||
from : this._defintion.getTableName() | ||
, database : this._defintion.getDatabaseName() | ||
, filter : {} | ||
, limit : 1 | ||
}; | ||
} | ||
} | ||
// icannot delete a model not loaded from the database | ||
if (this._fromDb){ | ||
this._defintion.primaryKeys.forEach(function(key){ | ||
query.filter[key] = this[key]; | ||
}.bind(this)); | ||
// generic accessor method for mappings | ||
properties.getMapping = { | ||
value: function(mappingName){ | ||
if (mappingMap[mappingName]) { | ||
// create referencing set only when used | ||
if (!this._mappings[mappingName]) { | ||
this._mappings[mappingName] = new RelatingSet({ | ||
orm: options._orm | ||
, definition: mappingMap[mappingName].definition | ||
, column: mappingMap[mappingName].column | ||
, related: this | ||
, database: databaseName | ||
}); | ||
if (!Object.keys(query.filter).length) { | ||
log.dir(query); | ||
throw new Error('Failed to create proper delete query, no filter was created (see query definition above)'); | ||
} | ||
else transaction.executeQuery('delete', query, callback); | ||
} | ||
else callback(new Error('Cannot delete model, it wasn\'t loaded from the database!')); | ||
} | ||
this._mappings[mappingName].on('chnage', function(){ | ||
this._setChanged(); | ||
}.bind(this)); | ||
} | ||
return this._mappings[mappingName]; | ||
} | ||
else throw new Error('Mapping via «'+mappingName+'» on entity «'+options.definition.name+'» doesn\'t exist!'); | ||
} | ||
}; | ||
// generic accessor method for belongTo | ||
properties.getBelongsTo = { | ||
value: function(belongingName){ | ||
if (belongsToMap[belongingName]) { | ||
// create referencing set only when used | ||
if (!this._belongsTo[belongingName]) { | ||
this._belongsTo[belongingName] = new RelatingSet({ | ||
orm: options._orm | ||
, definition: belongsToMap[belongingName].definition | ||
, column: belongsToMap[belongingName].column | ||
, related: this | ||
, database: databaseName | ||
}); | ||
this._belongsTo[belongingName].on('chnage', function(){ | ||
this._setChanged(); | ||
}.bind(this)); | ||
} | ||
return this._belongsTo[belongingName]; | ||
} | ||
else throw new Error('Belongs to «'+belongingName+'» on entity «'+options.definition.name+'» doesn\'t exist!'); | ||
} | ||
}; | ||
, clone: function() { | ||
var clonedInstance = new (this._getDatabase()[this._defintion.model.name])(); | ||
} | ||
// generic accessor method for reference | ||
properties.getReference = { | ||
value: function(referenceName){ | ||
if (referenceMap[referenceName]) { | ||
return this._references[referenceName]; | ||
} | ||
else throw new Error('Reference on «'+referenceName+'» on entity «'+options.definition.name+'» doesn\'t exist!'); | ||
} | ||
}; | ||
properties.setReference = { | ||
value: function(referenceName, newReferenceModel, existing){ | ||
if (referenceMap[referenceName]) {log.wtf(this._references[referenceName] !== newReferenceModel); | ||
if (!existing && this._references[referenceName] !== newReferenceModel) { | ||
this._changedReferences.push(referenceName); | ||
this._references[referenceName] = newReferenceModel; | ||
this._setChanged(); | ||
} | ||
} | ||
else throw new Error('Reference on «'+referenceName+'» on entity «'+options.definition.name+'» doesn\'t exist!'); | ||
} | ||
}; | ||
return new Class(CustomModel, properties); | ||
} | ||
, save: function() { | ||
var args = new Arguments(arguments) | ||
, callback = args.getFunction(function(){}) | ||
, noReload = args.getBoolean(false) | ||
, transaction = args.getObject(); | ||
// transactio management | ||
if (transaction) { | ||
this._save(transaction, noReload, callback); | ||
} | ||
else { | ||
transaction = this._getDatabase().createTransaction(); | ||
this._save(transaction, noReload, function(err){ | ||
if (err) { | ||
transaction.rollback(function(transactionErr){ | ||
if (transactionErr) callback(transactionErr); | ||
else callback(err); | ||
}.bind(this)); | ||
} | ||
else { | ||
transaction.commit(function(err){ | ||
if (err) callback(err); | ||
else { | ||
this._fromDb = true; | ||
this.reload(function(err){ | ||
if (err) callback(err); | ||
else callback(null, this); | ||
}.bind(this)); | ||
} | ||
}.bind(this)); | ||
} | ||
}.bind(this)); | ||
} | ||
, capitalize: function(input) { | ||
return input[0].toUpperCase() + input.slice(1); | ||
} | ||
}); | ||
return this; | ||
} | ||
, _save: function(transaction, noReload, callback) { | ||
this._saveReferences(transaction, noReload, function(err){ | ||
if (err) callback(err); | ||
else { | ||
var query = { | ||
from : this._defintion.getTableName() | ||
, database : this._defintion.getDatabaseName() | ||
, filter : {} | ||
, values : this._fromDb ? this._getChangedValues() : this._values | ||
}; | ||
if (this._defintion.primaryKeys.length) { | ||
query.returning = this._defintion.primaryKeys; | ||
} | ||
//log.error(this._defintion.getTableName()); | ||
//log(query); | ||
if (this._fromDb){ | ||
if (this._changedValues.length) { | ||
this._defintion.primaryKeys.forEach(function(key){ | ||
query.filter[key] = this[key]; | ||
}.bind(this)); | ||
transaction.executeQuery('update', query, function(err){ | ||
if (err) callback(err); | ||
else this._saveChildren(transaction, noReload, callback); | ||
}.bind(this)); | ||
} | ||
else this._saveChildren(transaction, noReload, callback); | ||
} | ||
else { | ||
transaction.executeQuery('insert', query, function(err, result){ | ||
if (err) callback(err); | ||
else { | ||
if (result.type === 'id'){ | ||
if(result.id) { | ||
if (this._defintion.primaryKeys.length === 1){ | ||
this[this._defintion.primaryKeys[0]] = result.id; | ||
} | ||
else throw new Error('Cannot load record with more than one primarykey when at least on of the primary keys has an autoincermented value!'); | ||
} | ||
this._saveChildren(transaction, noReload, callback); | ||
} | ||
else throw new Error('not implemented!'); | ||
} | ||
}.bind(this)); | ||
} | ||
} | ||
}.bind(this)); | ||
} | ||
, _saveChildren: function(transaction, noReload, callback) { | ||
async.wait(function(done) { | ||
this._saveMappings(transaction, noReload, done); | ||
}.bind(this) | ||
, function(done) { | ||
this._saveBelongsTo(transaction, noReload, done); | ||
}.bind(this), callback); | ||
} | ||
, _saveBelongsTo: function(transaction, noReload, callback) { | ||
async.each(Object.keys(this._belongsTo), function(belongsToId, next){ | ||
this._belongsTo[belongsToId].save(transaction, noReload, next); | ||
}.bind(this), callback); | ||
} | ||
, _saveMappings: function(transaction, noReload, callback) { | ||
async.each(Object.keys(this._mappings), function(mappingId, next){ | ||
this._mappings[mappingId].save(transaction, noReload, next); | ||
}.bind(this), callback); | ||
} | ||
, _saveReferences: function(transaction, noReload, callback) { | ||
async.each(this._changedReferences, function(key, next){ | ||
var value = this._references[key] | ||
, column = this._columns[key].column; | ||
// a query was set as reference | ||
if (value.isQuery) { | ||
value.limit(1); | ||
value.findOne(function(err, model) { | ||
if (err) next(err); | ||
else if (!model) { | ||
this[column.name] = null; | ||
this[value.tableName] = null; | ||
next(); | ||
} | ||
else { | ||
this[column.name] = model[column.referencedColumn]; | ||
this[value.tableName] = model; | ||
next(); | ||
} | ||
}.bind(this)); | ||
} | ||
else { | ||
if (value.isSaved()) { | ||
this[column.name] = value[column.referencedColumn]; | ||
next(); | ||
} | ||
else value._save(transaction, noReload, function(err){ | ||
if (err) next(err); | ||
else { | ||
this[column.name] = value[column.referencedColumn]; | ||
next(); | ||
} | ||
}.bind(this)); | ||
} | ||
}.bind(this), callback); | ||
} | ||
, dir: function(returnResult) { | ||
var obj = {}; | ||
Object.keys(this._columns).forEach(function(keyName) { | ||
var column = this._columns[keyName]; | ||
switch (column.type) { | ||
case 'mapping': | ||
if (this._mappings[keyName]) obj[keyName] = this[keyName].dir(true); | ||
break; | ||
case 'belongsTo': | ||
if (this._belongsTo[keyName]) obj[keyName] = this[keyName].dir(true); | ||
break; | ||
case 'reference': | ||
if (this._references[keyName]) obj[keyName] = (typeof this[keyName] === 'object' && typeof this[keyName].dir === 'function' ? this[keyName].dir(true) : this[keyName]); | ||
break; | ||
case 'scalar': | ||
if (!type.undefined(this[keyName])) obj[keyName] = this[keyName]; | ||
break; | ||
default: | ||
throw new Exception('Column type «'+column.type+'» is not supported!'); | ||
} | ||
}.bind(this)); | ||
if (returnResult) return obj; | ||
else log(obj); | ||
} | ||
, toJSON: function(){ | ||
var obj = {}; | ||
Object.keys(this._columns).forEach(function(keyName) { | ||
var column = this._columns[keyName]; | ||
switch (column.type) { | ||
case 'mapping': | ||
if (this._mappings[keyName]) obj[keyName] = this[keyName]; | ||
break; | ||
case 'belongsTo': | ||
if (this._belongsTo[keyName]) obj[keyName] = this[keyName]; | ||
break; | ||
case 'reference': | ||
if (this._references[keyName]) obj[keyName] = this[keyName]; | ||
break; | ||
case 'scalar': | ||
if (!type.undefined(this[keyName])) obj[keyName] = this[keyName];; | ||
break; | ||
default: | ||
throw new Exception('Column type «'+column.type+'» is not supported!'); | ||
} | ||
}.bind(this)); | ||
return obj; | ||
} | ||
}); | ||
}(); |
324
lib/ORM.js
!function(){ | ||
var Class = require('ee-class') | ||
, log = require('ee-log') | ||
, type = require('ee-types') | ||
, EventEmitter = require('ee-event-emitter') | ||
, arg = require('ee-arguments') | ||
, async = require('ee-async') | ||
, DBCluster = require('ee-db-cluster'); //*/require('../../ee-db-cluster'); | ||
var Class = require('ee-class') | ||
, log = require('ee-log') | ||
, type = require('ee-types') | ||
, EventEmitter = require('ee-event-emitter') | ||
, debug = require('ee-argv').has('debug-sql') | ||
, async = require('ee-async') | ||
, argv = require('ee-argv') | ||
, DBCluster = require('ee-db-cluster'); //*/require('../../ee-db-cluster'); | ||
var Database = require('./Database') | ||
, StaticORM = require('./StaticORM') | ||
, staticORM; | ||
var Database = require('./Database') | ||
, StaticORM = require('./StaticORM') | ||
, staticORM; | ||
var dev = argv.has('dev-orm'); | ||
var ORM = new Class({ | ||
inherits: EventEmitter | ||
, init: function(options) { | ||
this._setProperty('_options', options); | ||
this._setProperty('_dbNames', []); | ||
this._setProperty('_databases', {}); | ||
var ORM = new Class({ | ||
inherits: EventEmitter | ||
// db connectivity | ||
this._initializeDatabases(options); | ||
, init: function(options) { | ||
Class.define(this, '_options', Class(options)); | ||
Class.define(this, '_dbNames', Class([])); | ||
Class.define(this, '_databases', Class({})); | ||
this._initializeOrm(function(err){ | ||
this.emit('load', err); | ||
}.bind(this)); | ||
} | ||
// db connectivity | ||
this._initializeDatabases(options); | ||
this._initializeOrm(function(err){ | ||
this.emit('load', err); | ||
}.bind(this)); | ||
} | ||
, _setProperty: function(name, value){ | ||
Object.defineProperty(this, name, {value: value}); | ||
} | ||
// reload definitions | ||
, reload: function(callback) { | ||
this._initializeOrm(callback); | ||
} | ||
, _initializeOrm: function(callback) { | ||
async.each(this._dbNames | ||
// get definition from database | ||
, function(databaseName, next){ | ||
this._databases[databaseName].describe([databaseName], function(err, databases){ | ||
if (err) next(err); | ||
else { | ||
// push config to next step | ||
next(null, databaseName, databases[databaseName]); | ||
} | ||
}.bind(this)); | ||
}.bind(this) | ||
// initialize orm per databse | ||
, function(databaseName, definition, next){ | ||
if (this[databaseName]) next(new Error('Failed to load ORM for database «'+databaseName+'», the name is reserved for the orm.').setName('ORMException')); | ||
else { | ||
, _initializeOrm: function(callback) { | ||
if (dev) log.debug('initializing ORM ...'); | ||
// create names for mapping / reference accessor, handle duplicates | ||
this._manageAccessorNames(definition); | ||
async.each(this._dbNames | ||
this[databaseName] = new Database({ | ||
orm: this | ||
, definition: definition | ||
, database: this._databases[databaseName] | ||
, on: { | ||
load: next | ||
} | ||
}); | ||
} | ||
}.bind(this) | ||
// remove existing | ||
, function(databaseName, next){ | ||
if (this[databaseName] && this[databaseName].createTransaction) { | ||
if (dev) log.debug('removing existing db instance «'+databaseName+'»...'); | ||
delete this[databaseName]; | ||
} | ||
// check for errors | ||
, function(err, results){ | ||
if (err) callback(err); | ||
else callback(); | ||
}.bind(this)); | ||
} | ||
next(null, databaseName); | ||
}.bind(this) | ||
// get definition from database | ||
, function(databaseName, next){ | ||
this._databases[databaseName].describe([databaseName], function(err, databases){ | ||
if (dev) log.debug('got db definition for «'+databaseName+'»...'); | ||
if (err) next(err); | ||
else { | ||
// push config to next step | ||
next(null, databaseName, databases[databaseName]); | ||
} | ||
}.bind(this)); | ||
}.bind(this) | ||
, _manageAccessorNames: function(definition) { | ||
Object.keys(definition).forEach(function(tablename){ | ||
var model = definition[tablename] | ||
, usedNames = {}; | ||
// initialize orm per databse | ||
, function(databaseName, definition, next){ | ||
if (this[databaseName]) next(new Error('Failed to load ORM for database «'+databaseName+'», the name is reserved for the orm.').setName('ORMException')); | ||
else { | ||
Object.keys(model.columns).forEach(function(columnName){ | ||
var column = model.columns[columnName] | ||
, name; | ||
// create names for mapping / reference accessor, handle duplicates | ||
this._manageAccessorNames(definition); | ||
if (column.mapsTo) { | ||
column.mapsTo.forEach(function(mapping){ | ||
name = mapping.name; | ||
if (dev) log.debug('creating new db instance for «'+databaseName+'»...'); | ||
if (model.columns[name]) { | ||
// the name is used by a column, cannot reference directly | ||
log.warn('«'+model.name+'» cannot use the accessor «'+name+'», it\'s already used by another property'); | ||
mapping.useGenericAccessor = true; | ||
} | ||
else if (usedNames[name]) { | ||
// the name was used before by either a mapping or a reference | ||
// we cannot use it | ||
log.warn('«'+model.name+'» cannot use the accessor «'+name+'», it\'s already used by another property'); | ||
usedNames[name].useGenericAccessor = true; | ||
mapping.useGenericAccessor = true; | ||
} | ||
else usedNames[name] = mapping; | ||
}.bind(this)); | ||
} | ||
if (column.belongsTo) { | ||
column.belongsTo.forEach(function(beloning){ | ||
name = beloning.name; | ||
this[databaseName] = new Database({ | ||
orm: this | ||
, definition: definition | ||
, database: this._databases[databaseName] | ||
}); | ||
if (model.columns[name]) { | ||
log.warn('«'+model.name+'» cannot use the accessor «'+name+'», it\'s already used by another property'); | ||
// the name is used by a column, cannot reference directly | ||
beloning.useGenericAccessor = true; | ||
} | ||
else if (usedNames[name]) { | ||
log.warn('«'+model.name+'» cannot use the accessor «'+name+'», it\'s already used by another property'); | ||
// the name was used before by either a mapping or a reference | ||
// we cannot use it | ||
usedNames[name].useGenericAccessor = true; | ||
beloning.useGenericAccessor = true; | ||
} | ||
else usedNames[name] = beloning; | ||
}.bind(this)); | ||
} | ||
if (column.referencedModel) { | ||
name = column.referencedModel.name; | ||
if (model.columns[name]) { | ||
log.warn('«'+model.name+'» cannot use the accessor «'+name+'», it\'s already used by another property'); | ||
// the name is used by a column, cannot reference directly | ||
column.useGenericAccessor = true; | ||
} | ||
else if (usedNames[name]) { | ||
log.warn('«'+model.name+'» cannot use the accessor «'+name+'», it\'s already used by another property'); | ||
// the name was used before by either a mapping or a reference | ||
// we cannot use it | ||
usedNames[name].useGenericAccessor = true; | ||
column.useGenericAccessor = true; | ||
} | ||
else usedNames[name] = column; | ||
} | ||
}.bind(this)); | ||
}.bind(this)); | ||
} | ||
this[databaseName].on('load', next); | ||
} | ||
}.bind(this) | ||
// check for errors | ||
, function(err, results){ | ||
if (dev) log.warn('all dbs loaded ...'); | ||
, getDatabase: function(id){ | ||
if (!type.string(id) || !id.length) throw new Error('cannot return a db without knowing which on to return (argument 0 must be the db id!)'); | ||
return this._databases[id]; | ||
} | ||
if (err) callback(err); | ||
else callback(); | ||
}.bind(this)); | ||
} | ||
, _initializeDatabases: function(options){ | ||
if (type.object(options)) { | ||
Object.keys(options).forEach(function(databaseName){ | ||
if (!type.string(options[databaseName].type)) throw new Error('['+databaseName+'] > Database type not in config specified (type: \'mysql\' / \'postgres\')!'); | ||
if (!type.array(options[databaseName].hosts) || !options[databaseName].hosts.length) throw new Error('['+databaseName+'] > Please add at least one host per db in the config!'); | ||
this._dbNames.push(databaseName); | ||
this._databases[databaseName] = new DBCluster({type: options[databaseName].type}); | ||
, _manageAccessorNames: function(definition) { | ||
Object.keys(definition).forEach(function(tablename){ | ||
var model = definition[tablename] | ||
, usedNames = {}; | ||
options[databaseName].hosts.forEach(function(config){ | ||
config.database = config.database || databaseName; | ||
this._databases[databaseName].addNode(config); | ||
}.bind(this)); | ||
}.bind(this)); | ||
} | ||
else throw new Error('no database configuration present!'); | ||
} | ||
}); | ||
Object.keys(model.columns).forEach(function(columnName){ | ||
var column = model.columns[columnName] | ||
, name; | ||
if (column.mapsTo) { | ||
column.mapsTo.forEach(function(mapping){ | ||
name = mapping.name; | ||
staticORM = new StaticORM(); | ||
Object.keys(Object.getPrototypeOf(staticORM)).forEach(function(key){ | ||
if(!ORM[key]) ORM[key] = staticORM[key]; | ||
}); | ||
module.exports = ORM; | ||
if (model.columns[name]) { | ||
// the name is used by a column, cannot reference directly | ||
if (debug) log.warn('«'+model.name+'» cannot use the accessor «'+name+'», it\'s already used by another property'); | ||
mapping.useGenericAccessor = true; | ||
} | ||
else if (usedNames[name]) { | ||
// the name was used before by either a mapping or a reference | ||
// we cannot use it | ||
if (debug) log.warn('«'+model.name+'» cannot use the accessor «'+name+'», it\'s already used by another property'); | ||
usedNames[name].useGenericAccessor = true; | ||
mapping.useGenericAccessor = true; | ||
} | ||
else usedNames[name] = mapping; | ||
}.bind(this)); | ||
} | ||
if (column.belongsTo) { | ||
column.belongsTo.forEach(function(beloning){ | ||
name = beloning.name; | ||
if (model.columns[name]) { | ||
if (debug) log.warn('«'+model.name+'» cannot use the accessor «'+name+'», it\'s already used by another property'); | ||
// the name is used by a column, cannot reference directly | ||
beloning.useGenericAccessor = true; | ||
} | ||
else if (usedNames[name]) { | ||
if (debug) log.warn('«'+model.name+'» cannot use the accessor «'+name+'», it\'s already used by another property'); | ||
// the name was used before by either a mapping or a reference | ||
// we cannot use it | ||
usedNames[name].useGenericAccessor = true; | ||
beloning.useGenericAccessor = true; | ||
} | ||
else usedNames[name] = beloning; | ||
}.bind(this)); | ||
} | ||
if (column.referencedModel) { | ||
name = column.referencedModel.name; | ||
if (model.columns[name]) { | ||
if (debug) log.warn('«'+model.name+'» cannot use the accessor «'+name+'», it\'s already used by another property'); | ||
// the name is used by a column, cannot reference directly | ||
column.useGenericAccessor = true; | ||
} | ||
else if (usedNames[name]) { | ||
if (debug) log.warn('«'+model.name+'» cannot use the accessor «'+name+'», it\'s already used by another property'); | ||
// the name was used before by either a mapping or a reference | ||
// we cannot use it | ||
usedNames[name].useGenericAccessor = true; | ||
column.useGenericAccessor = true; | ||
} | ||
else usedNames[name] = column; | ||
} | ||
}.bind(this)); | ||
}.bind(this)); | ||
} | ||
, getDatabase: function(id){ | ||
if (!type.string(id) || !id.length) throw new Error('cannot return a db without knowing which on to return (argument 0 must be the db id!)'); | ||
return this._databases[id]; | ||
} | ||
, _initializeDatabases: function(options){ | ||
if (type.object(options)) { | ||
Object.keys(options).forEach(function(databaseName){ | ||
if (!type.string(options[databaseName].type)) throw new Error('['+databaseName+'] > Database type not in config specified (type: \'mysql\' / \'postgres\')!'); | ||
if (!type.array(options[databaseName].hosts) || !options[databaseName].hosts.length) throw new Error('['+databaseName+'] > Please add at least one host per db in the config!'); | ||
this._dbNames.push(databaseName); | ||
this._databases[databaseName] = new DBCluster({type: options[databaseName].type}); | ||
options[databaseName].hosts.forEach(function(config){ | ||
config.database = config.database || databaseName; | ||
this._databases[databaseName].addNode(config); | ||
}.bind(this)); | ||
}.bind(this)); | ||
} | ||
else throw new Error('no database configuration present!'); | ||
} | ||
}); | ||
// set static methods on the ORM constructor | ||
Class.implement(new StaticORM(), ORM); | ||
module.exports = ORM; | ||
}(); |
!function(){ | ||
var Class = require('ee-class') | ||
, EventEmitter = require('ee-event-emitter') | ||
, log = require('ee-log'); | ||
var Class = require('ee-class') | ||
, EventEmitter = require('ee-event-emitter') | ||
, log = require('ee-log'); | ||
@@ -10,26 +10,26 @@ | ||
module.exports = new Class({ | ||
module.exports = new Class({ | ||
init: function(options) { | ||
this.filter = options.filter || {}; | ||
this.select = options.select || []; | ||
this.from = options.from || 'undefined'; | ||
this.database = options.database || 'undefined'; | ||
this.join = options.join || []; | ||
this.group = options.group || []; | ||
} | ||
init: function(options) { | ||
this.filter = options.filter || {}; | ||
this.select = options.select || []; | ||
this.from = options.from || 'undefined'; | ||
this.database = options.database || 'undefined'; | ||
this.join = options.join || []; | ||
this.group = options.group || []; | ||
} | ||
, addSeleted: function(select) { | ||
this.select = this.select.concat(select); | ||
} | ||
, addSeleted: function(select) { | ||
this.select = this.select.concat(select); | ||
} | ||
, formatJoins: function() { | ||
this.join = this.join.map(function(join){ | ||
return join.unformatted ? join.format() : join; | ||
}); | ||
} | ||
}); | ||
, formatJoins: function() { | ||
this.join = this.join.map(function(join){ | ||
return join.unformatted ? join.format() : join; | ||
}); | ||
} | ||
}); | ||
}(); |
!function(){ | ||
var Class = require('ee-class') | ||
, EventEmitter = require('ee-event-emitter') | ||
, log = require('ee-log') | ||
, clone = require('clone') | ||
, args = require('ee-arguments') | ||
, type = require('ee-types') | ||
, Set = require('./Set') | ||
, Query = require('./Query') | ||
, Resource = require('./Resource') | ||
, JoinStatement = require('./JoinStatement') | ||
, QueryCompiler = require('./QueryCompiler') | ||
, ORM; | ||
var Class = require('ee-class') | ||
, EventEmitter = require('ee-event-emitter') | ||
, log = require('ee-log') | ||
, clone = require('clone') | ||
, Arguments = require('ee-arguments') | ||
, type = require('ee-types') | ||
, Set = require('./Set') | ||
, Query = require('./Query') | ||
, Resource = require('./Resource') | ||
, JoinStatement = require('./JoinStatement') | ||
, QueryCompiler = require('./QueryCompiler') | ||
, ORM; | ||
@@ -20,673 +20,429 @@ | ||
var QueryBuilder = { | ||
module.exports = new Class({ | ||
isQuery: true | ||
isQuery: true | ||
, getEntityName: function() { | ||
return this.tableName; | ||
} | ||
, getEntityName: function() { | ||
return this.tableName; | ||
} | ||
, init: function(options) { | ||
, init: function(options) { | ||
// remove deprecated parent property (ee-class implementation) | ||
delete this.parent; | ||
// load circular depency if not alread loaded | ||
if (!ORM) ORM = require('./ORM'); | ||
// load circular depency if not alread loaded | ||
if (!ORM) ORM = require('./ORM'); | ||
// the mapping that us got here | ||
Class.define(this, '_mapping', Class(options.mapping)); | ||
// this will give us acces to the orm, the querybuilders and the | ||
// table definition, this method is set by the exported class below | ||
this._setProperties(); | ||
// we need to access this properts often | ||
this.tableName = this._definition.getTableName(); | ||
this.databaseName = this._definition.getDatabaseName(); | ||
// the mapping that us got here | ||
this._setProperty('_mapping', options.mapping); | ||
// subjoins used for eager loading | ||
Class.define(this, '_joins', Class(options.joins || [])); | ||
// we need to access this properts often | ||
this.tableName = this._definition.getTableName(); | ||
this.databaseName = this._definition.getDatabaseName(); | ||
// set or define the root resource | ||
if (options.rootResource) Class.define(this, '_rootResource', Class(options.rootResource)); | ||
else { | ||
Class.define(this, '_rootResource', Class(new Resource({ | ||
Model : this._orm[this.databaseName][this.tableName] | ||
, name : this.tableName | ||
, primaryKeys : this._definition.primaryKeys | ||
, rootFiltered : true | ||
, query : new Query({ | ||
from: this.tableName | ||
, database: this.databaseName | ||
}) | ||
}))); | ||
this._addPrimarySelect(); | ||
} | ||
// subjoins used for eager loading | ||
this._setProperty('_joins', options.joins || []); | ||
// the query we're currently working on | ||
Class.define(this, '_resource', Class(options.resource || this._rootResource)); | ||
// set or define the root resource | ||
if (options.rootResource) this._setProperty('_rootResource', options.rootResource); | ||
else { | ||
this._setProperty('_rootResource', new Resource({ | ||
Model : this._orm[this.databaseName][this.tableName] | ||
, name : this.tableName | ||
, primaryKeys : this._definition.primaryKeys | ||
, rootFiltered : true | ||
, query : new Query({ | ||
from: this.tableName | ||
, database: this.databaseName | ||
}) | ||
})); | ||
this._addPrimarySelect(); | ||
} | ||
// parse passed parameters (happens only on the root of the query) | ||
if (options.parameters) { | ||
this._parseFilter(this._resource.query, this.tableName, this._getFilters(options.parameters)); | ||
this._parseSelect(this._resource.query, this.tableName, this._getSelect(options.parameters)); | ||
} | ||
} | ||
// the query we're currently working on | ||
this._setProperty('_resource', options.resource || this._rootResource); | ||
/** | ||
* the _handleReference method is called when the user selects | ||
* a referenced subresource on the querybuilder | ||
* | ||
* column <Object> definition of the column of this | ||
* table which refrences the other table | ||
* targetModel <Object> definition of the targeted model of | ||
* the reference | ||
* queryParameters <Array> parameters passed like the filter & | ||
* selcet statements | ||
* returnTarget <Boolean> optional, if we're returning the reference | ||
* for this model or the reference of the | ||
* targeted model | ||
*/ | ||
, _handleReference: function(column, targetModel, queryParameters, returnTarget) { | ||
var select = this._getSelect(queryParameters) | ||
, filter = this._getFilters(queryParameters) | ||
, targetModelName = targetModel.name | ||
, group = [] | ||
, resource | ||
, joins; | ||
// parse passed parameters (happens only on the root of the query) | ||
if (options.parameters) { | ||
this._parseFilter(this._resource.query, this.tableName, this._getFilters(options.parameters)); | ||
this._parseSelect(this._resource.query, this.tableName, this._getSelect(options.parameters)); | ||
} | ||
} | ||
// create basic join definition, it will be converted to more specific join | ||
// statetments later on | ||
joins = [new JoinStatement({ | ||
source : { | ||
table : column.referencedTable | ||
, column : column.referencedColumn | ||
} | ||
, target: { | ||
table : this.tableName | ||
, column : column.name | ||
} | ||
})]; | ||
targetModel.primaryKeys.forEach(function(key){ | ||
group.push({ | ||
table : targetModelName | ||
, column : key | ||
}); | ||
}); | ||
/** | ||
* the _handleReference method is called when the user selects | ||
* a referenced subresource on the querybuilder | ||
* | ||
* column <Object> definition of the column of this | ||
* table which refrences the other table | ||
* targetModel <Object> definition of the targeted model of | ||
* the reference | ||
* queryParameters <Array> parameters passed like the filter & | ||
* selcet statements | ||
* returnTarget <Boolean> optional, if we're returning the reference | ||
* for this model or the reference of the | ||
* targeted model | ||
*/ | ||
, _handleReference: function(column, targetModel, queryParameters, returnTarget){ | ||
var select = this._getSelect(queryParameters) | ||
, filter = this._getFilters(queryParameters) | ||
, targetModelName = targetModel.name | ||
, group = [] | ||
, resource | ||
, joins; | ||
// create basic join definition, it will be converted to more specific join | ||
// statetments later on | ||
joins = [new JoinStatement({ | ||
source : { | ||
table : column.referencedTable | ||
, column : column.referencedColumn | ||
} | ||
, target: { | ||
table : this.tableName | ||
, column : column.name | ||
} | ||
})]; | ||
// create a child tree node for the querybuilder | ||
resource = new Resource({ | ||
name : targetModelName | ||
, selected : !!Object.keys(select).length | ||
, parentResource : this._resource | ||
, Model : this._orm[this.databaseName][targetModelName] | ||
, referencedParentColumn : column.name | ||
, referencedParentTable : this.tableName | ||
, joins : joins | ||
, filters : filter | ||
, rootResource : this._rootResource | ||
, primaryKeys : targetModel.primaryKeys | ||
, type : 'reference' | ||
, loaderId : column.name | ||
, query : new Query({ | ||
join : joins.concat(this._joins) | ||
, database : this.databaseName | ||
, from : targetModelName | ||
, filter : clone(this._resource.query.filter) | ||
, group : group | ||
}) | ||
}); | ||
targetModel.primaryKeys.forEach(function(key){ | ||
group.push({ | ||
table : targetModelName | ||
, column : key | ||
}); | ||
}); | ||
// add primary keys to default seleczt | ||
targetModel.primaryKeys.forEach(function(key){resource.defaultSelect.push(key);}); | ||
// process options / select on subquery | ||
this._parseSelect(resource.query, targetModelName, select); | ||
// store the subquery on the current query | ||
this._resource.children.push(resource); | ||
// create a child tree node for the querybuilder | ||
resource = new Resource({ | ||
name : targetModelName | ||
, selected : !!Object.keys(select).length | ||
, parentResource : this._resource | ||
, Model : this._orm[this.databaseName][targetModelName] | ||
, referencedParentColumn : column.name | ||
, referencedParentTable : this.tableName | ||
, joins : joins | ||
, filters : filter | ||
, rootResource : this._rootResource | ||
, primaryKeys : targetModel.primaryKeys | ||
, type : 'reference' | ||
, loaderId : column.name | ||
, query : new Query({ | ||
join : joins.concat(this._joins) | ||
, database : this.databaseName | ||
, from : targetModelName | ||
, filter : clone(this._resource.query.filter) | ||
, group : group | ||
}) | ||
}); | ||
// we may stay at the same level (e.g. fetchModel vs. getModel) | ||
if (returnTarget) { | ||
return new this._queryBuilders[targetModelName]({ | ||
resource : resource | ||
, rootResource : this._rootResource | ||
, joins : joins.concat(this._joins) | ||
}); | ||
} | ||
else { | ||
return this; | ||
} | ||
} | ||
// add primary keys to default seleczt | ||
targetModel.primaryKeys.forEach(function(key){resource.defaultSelect.push(key);}); | ||
// process options / select on subquery | ||
this._parseSelect(resource.query, targetModelName, select); | ||
// store the subquery on the current query | ||
this._resource.children.push(resource); | ||
, _handleBelongsTo: function(column, targetModel, queryParameters, returnTarget) { | ||
var select = this._getSelect(queryParameters) | ||
, filter = this._getFilters(queryParameters) | ||
, targetModelName = targetModel.model.name | ||
, group = [] | ||
, resource | ||
, joins; | ||
// create basic join definition, it will be converted to more specific join | ||
// statetments later on | ||
joins = [new JoinStatement({ | ||
source : { | ||
table : targetModel.model.name | ||
, column : targetModel.targetColumn | ||
} | ||
, target: { | ||
table : this.tableName | ||
, column : column.name | ||
} | ||
})]; | ||
// we may stay at the same level (e.g. fetchModel vs. getModel) | ||
if (returnTarget) { | ||
return new this._queryBuilders[targetModelName]({ | ||
resource : resource | ||
, rootResource : this._rootResource | ||
, joins : joins.concat(this._joins) | ||
}); | ||
} | ||
else { | ||
return this; | ||
} | ||
} | ||
targetModel.model.primaryKeys.forEach(function(key){ | ||
group.push({ | ||
table : targetModelName | ||
, column : key | ||
}); | ||
}); | ||
// create a child tree node for the querybuilder | ||
resource = new Resource({ | ||
name : targetModelName | ||
, selected : !!Object.keys(select).length | ||
, parentResource : this._resource | ||
, Model : this._orm[this.databaseName][targetModelName] | ||
, referencedParentColumn : column.name | ||
, referencedParentTable : this.tableName | ||
, joins : joins | ||
, filters : filter | ||
, rootResource : this._rootResource | ||
, primaryKeys : targetModel.model.primaryKeys | ||
, type : 'belongsTo' | ||
, loaderId : targetModel.model.name | ||
, query : new Query({ | ||
join : joins.concat(this._joins) | ||
, database : this.databaseName | ||
, from : targetModelName | ||
, filter : clone(this._resource.query.filter) | ||
, group : group | ||
}) | ||
}); | ||
, _handleBelongsTo: function(column, targetModel, queryParameters, returnTarget) { | ||
var select = this._getSelect(queryParameters) | ||
, filter = this._getFilters(queryParameters) | ||
, targetModelName = targetModel.model.name | ||
, group = [] | ||
, resource | ||
, joins; | ||
// add primary keys to default select | ||
targetModel.model.primaryKeys.forEach(function(key){resource.defaultSelect.push(key);}); | ||
// create basic join definition, it will be converted to more specific join | ||
// statetments later on | ||
joins = [new JoinStatement({ | ||
source : { | ||
table : targetModel.model.name | ||
, column : targetModel.targetColumn | ||
} | ||
, target: { | ||
table : this.tableName | ||
, column : column.name | ||
} | ||
})]; | ||
// process options / select on subquery | ||
this._parseSelect(resource.query, targetModelName, select); | ||
// store the subquery on the current query | ||
this._resource.children.push(resource); | ||
targetModel.model.primaryKeys.forEach(function(key){ | ||
group.push({ | ||
table : targetModelName | ||
, column : key | ||
}); | ||
}); | ||
// create a child tree node for the querybuilder | ||
resource = new Resource({ | ||
name : targetModelName | ||
, selected : !!Object.keys(select).length | ||
, parentResource : this._resource | ||
, Model : this._orm[this.databaseName][targetModelName] | ||
, referencedParentColumn : column.name | ||
, referencedParentTable : this.tableName | ||
, joins : joins | ||
, filters : filter | ||
, rootResource : this._rootResource | ||
, primaryKeys : targetModel.model.primaryKeys | ||
, type : 'belongsTo' | ||
, loaderId : targetModel.model.name | ||
, query : new Query({ | ||
join : joins.concat(this._joins) | ||
, database : this.databaseName | ||
, from : targetModelName | ||
, filter : clone(this._resource.query.filter) | ||
, group : group | ||
}) | ||
}); | ||
// we may stay at the same level (e.g. fetchModel vs. getModel) | ||
if (returnTarget) { | ||
return new this._queryBuilders[targetModelName]({ | ||
resource : resource | ||
, rootResource : this._rootResource | ||
, joins : joins.concat(this._joins) | ||
}); | ||
} | ||
else { | ||
return this; | ||
} | ||
} | ||
// add primary keys to default seleczt | ||
targetModel.model.primaryKeys.forEach(function(key){resource.defaultSelect.push(key);}); | ||
/* | ||
* the _handleMapping method builds the queries for a mapping | ||
* | ||
* @param <String> the name of the column on our side of the mapping | ||
* @param <Object> the definition of the targeted table of the mapping | ||
* @param <Array> the parameters passed as filter / select / options for the mapping | ||
* @param <Boolean> wheter to return a querybuilder instance on the targeted table | ||
* of the paping or to stay on the same querybuilder | ||
*/ | ||
, _handleMapping: function(column, targetModel, queryParameters, returnTarget){ | ||
var select = this._getSelect(queryParameters) | ||
, filter = this._getFilters(queryParameters) | ||
, targetModelName = targetModel.name | ||
, group = [] | ||
, resource | ||
, joins; | ||
// process options / select on subquery | ||
this._parseSelect(resource.query, targetModelName, select); | ||
// create basic join definition, it will be converted to more specific join | ||
// statetments later on | ||
joins = [new JoinStatement({ | ||
source : { | ||
table : targetModel.model.name | ||
, column : targetModel.column.name | ||
} | ||
, target: { | ||
table : targetModel.via.model.name | ||
, column : targetModel.via.otherFk | ||
} | ||
}), new JoinStatement({ | ||
source : { | ||
table : targetModel.via.model.name | ||
, column : targetModel.via.fk | ||
} | ||
, target: { | ||
table : this.tableName | ||
, column : column.name | ||
} | ||
})]; | ||
// store the subquery on the current query | ||
this._resource.children.push(resource); | ||
// group by pk of taarget and by pk of the parent model (this model) | ||
targetModel.model.primaryKeys.forEach(function(key){ | ||
group.push({ | ||
table : targetModel.model.name | ||
, column : key | ||
}); | ||
}.bind(this)); | ||
//log(this._definition); | ||
this._definition.primaryKeys.forEach(function(key){ | ||
group.push({ | ||
table : this.tableName | ||
, column : key | ||
}); | ||
}.bind(this)); | ||
// we may stay at the same level (e.g. fetchModel vs. getModel) | ||
if (returnTarget) { | ||
return new this._queryBuilders[targetModelName]({ | ||
resource : resource | ||
, rootResource : this._rootResource | ||
, joins : joins.concat(this._joins) | ||
}); | ||
} | ||
else { | ||
return this; | ||
} | ||
} | ||
// create a child tree node for the querybuilder | ||
resource = new Resource({ | ||
name : targetModelName | ||
, selected : !!Object.keys(select).length | ||
, parentResource : this._resource | ||
, Model : this._orm[this.databaseName][targetModel.model.name] | ||
, referencedParentColumn : column.name | ||
, referencedParentTable : this.tableName | ||
, joins : joins | ||
, filters : filter | ||
, rootResource : this._rootResource | ||
, primaryKeys : targetModel.model.primaryKeys | ||
, type : 'mapping' | ||
, loaderId : targetModel.via.model.name | ||
, query : new Query({ | ||
join : joins.concat(this._joins) | ||
, database : this.databaseName | ||
, from : targetModel.model.name | ||
, filter : clone(this._resource.query.filter) | ||
, group : group | ||
}) | ||
}); | ||
/* | ||
* the _handleMapping method builds the queries for a mapping | ||
* | ||
* @param <String> the name of the column on our side of the mapping | ||
* @param <Object> the definition of the targeted table of the mapping | ||
* @param <Array> the parameters passed as filter / select / options for the mapping | ||
* @param <Boolean> wheter to return a querybuilder instance on the targeted table | ||
* of the paping or to stay on the same querybuilder | ||
*/ | ||
, _handleMapping: function(column, targetModel, queryParameters, returnTarget){ | ||
var select = this._getSelect(queryParameters) | ||
, filter = this._getFilters(queryParameters) | ||
, targetModelName = targetModel.name | ||
, group = [] | ||
, resource | ||
, joins; | ||
// create basic join definition, it will be converted to more specific join | ||
// statetments later on | ||
joins = [new JoinStatement({ | ||
source : { | ||
table : targetModel.model.name | ||
, column : targetModel.column.name | ||
} | ||
, target: { | ||
table : targetModel.via.model.name | ||
, column : targetModel.via.otherFk | ||
} | ||
}), new JoinStatement({ | ||
source : { | ||
table : targetModel.via.model.name | ||
, column : targetModel.via.fk | ||
} | ||
, target: { | ||
table : this.tableName | ||
, column : column.name | ||
} | ||
})]; | ||
// add primary keys to default seleczt | ||
targetModel.model.primaryKeys.forEach(function(key){resource.defaultSelect.push(key);}); | ||
// process options / select on subquery | ||
this._parseSelect(resource.query, targetModel.model.name, select); | ||
// group by pk of taarget and by pk of the parent model (this model) | ||
targetModel.model.primaryKeys.forEach(function(key){ | ||
group.push({ | ||
table : targetModel.model.name | ||
, column : key | ||
}); | ||
}.bind(this)); | ||
// store the subquery on the current query | ||
this._resource.children.push(resource); | ||
//log(this._definition); | ||
this._definition.primaryKeys.forEach(function(key){ | ||
group.push({ | ||
table : this.tableName | ||
, column : key | ||
}); | ||
}.bind(this)); | ||
// we may stay at the same level (e.g. fetchModel vs. getModel) | ||
if (returnTarget) { | ||
return new this._queryBuilders[targetModel.model.name]({ | ||
resource : resource | ||
, rootResource : this._rootResource | ||
, joins : joins.concat(this._joins) | ||
}); | ||
} | ||
else { | ||
return this; | ||
} | ||
} | ||
// create a child tree node for the querybuilder | ||
resource = new Resource({ | ||
name : targetModelName | ||
, selected : !!Object.keys(select).length | ||
, parentResource : this._resource | ||
, Model : this._orm[this.databaseName][targetModel.model.name] | ||
, referencedParentColumn : column.name | ||
, referencedParentTable : this.tableName | ||
, joins : joins | ||
, filters : filter | ||
, rootResource : this._rootResource | ||
, primaryKeys : targetModel.model.primaryKeys | ||
, type : 'mapping' | ||
, loaderId : targetModel.via.model.name | ||
, query : new Query({ | ||
join : joins.concat(this._joins) | ||
, database : this.databaseName | ||
, from : targetModel.model.name | ||
, filter : clone(this._resource.query.filter) | ||
, group : group | ||
}) | ||
}); | ||
// add primary keys to default seleczt | ||
targetModel.model.primaryKeys.forEach(function(key){resource.defaultSelect.push(key);}); | ||
, _getFilters: function(parameters) { | ||
return new Arguments(parameters).getObject({}); | ||
} | ||
// process options / select on subquery | ||
this._parseSelect(resource.query, targetModel.model.name, select); | ||
, _getSelect: function(parameters) { | ||
var args = new Arguments(parameters) | ||
, stringSelect = args.getString(); | ||
// store the subquery on the current query | ||
this._resource.children.push(resource); | ||
return args.getArray((stringSelect? [stringSelect] : [])); | ||
} | ||
, _getOptions: function(parameters) { | ||
return new Arguments(parameters).getObjectByIndex(1, {}); | ||
} | ||
// we may stay at the same level (e.g. fetchModel vs. getModel) | ||
if (returnTarget) { | ||
return new this._queryBuilders[targetModel.model.name]({ | ||
resource : resource | ||
, rootResource : this._rootResource | ||
, joins : joins.concat(this._joins) | ||
}); | ||
} | ||
else { | ||
return this; | ||
} | ||
} | ||
, _parseFilter: function(query, tablename, filter) { | ||
Object.keys(filter).forEach(function(property){ | ||
if (!query.filter[tablename]) query.filter[tablename] = {}; | ||
query.filter[tablename][property] = filter[property]; | ||
}.bind(this)); | ||
} | ||
, _parseSelect: function(query, tablename, select) { | ||
select.forEach(function(item){ | ||
query.select.push(item); | ||
}.bind(this)); | ||
} | ||
, _getFilters: function(parameters) { | ||
return args(parameters, 'object', {}); | ||
} | ||
, _getSelect: function(parameters) { | ||
var stringSelect = args(parameters, 'string'); | ||
return args(parameters, 'array', (stringSelect? [stringSelect] : [])); | ||
} | ||
, limit: function(limit) { | ||
if (type.number(limit)) this._resource.query.limit = limit; | ||
return this; | ||
} | ||
, _getOptions: function(parameters) { | ||
return args(parameters, 'object', {}, 1); | ||
} | ||
, offset: function(offset) { | ||
if (type.number(offset)) this._resource.query.offset = offset; | ||
return this; | ||
} | ||
, _parseFilter: function(query, tablename, filter) { | ||
Object.keys(filter).forEach(function(property){ | ||
if (!query.filter[tablename]) query.filter[tablename] = {}; | ||
query.filter[tablename][property] = filter[property]; | ||
}.bind(this)); | ||
} | ||
, filter: function(filter) { | ||
this._resource.query.filter = filter; | ||
//this._parseFilter(this._resource.query, this.tableName, options); | ||
return this; | ||
} | ||
, _parseSelect: function(query, tablename, select) { | ||
select.forEach(function(item){ | ||
query.select.push(item); | ||
}.bind(this)); | ||
} | ||
, _addPrimarySelect: function() { | ||
if (this._rootResource.query.select.length) { | ||
this._definition.primaryKeys.forEach(function(key){ | ||
this._rootResource.query.select.push(key); | ||
}.bind(this)); | ||
} | ||
} | ||
, limit: function(limit) { | ||
if (type.number(limit)) this._resource.query.limit = limit; | ||
return this; | ||
} | ||
, offset: function(offset) { | ||
if (type.number(offset)) this._resource.query.offset = offset; | ||
return this; | ||
} | ||
, find: function(callback) { | ||
new QueryCompiler({ | ||
orm : this._orm | ||
, getDatabase : this._getDatabase | ||
, resource : this._rootResource | ||
}).find(callback); | ||
} | ||
, filter: function(filter) { | ||
this._resource.query.filter = filter; | ||
//this._parseFilter(this._resource.query, this.tableName, options); | ||
return this; | ||
} | ||
, findOne: function(callback) { | ||
new QueryCompiler({ | ||
orm : this._orm | ||
, getDatabase : this._getDatabase | ||
, resource : this._rootResource | ||
}).findOne(callback); | ||
} | ||
, _addPrimarySelect: function() { | ||
if (this._rootResource.query.select.length) { | ||
this._definition.primaryKeys.forEach(function(key){ | ||
this._rootResource.query.select.push(key); | ||
}.bind(this)); | ||
} | ||
} | ||
, find: function(callback) { | ||
new QueryCompiler({ | ||
orm : this._orm | ||
, getDatabase : this._getDatabase | ||
, resource : this._rootResource | ||
}).find(callback); | ||
} | ||
, findOne: function(callback) { | ||
new QueryCompiler({ | ||
orm : this._orm | ||
, getDatabase : this._getDatabase | ||
, resource : this._rootResource | ||
}).findOne(callback); | ||
} | ||
, delete: function(callback) { | ||
new QueryCompiler({ | ||
orm : this._orm | ||
, getDatabase : this._getDatabase | ||
, resource : this._rootResource | ||
}).delete(callback); | ||
} | ||
, _setProperty: function(name, value) { | ||
Object.defineProperty(this, name, {value:value}); | ||
} | ||
}; | ||
// initialize the querybuilder for the specific model | ||
module.exports = new Class({ | ||
init: function(options) { | ||
var QB = {} | ||
, mappingMap = {} | ||
, belongsToMap = {} | ||
, referenceMap = {} | ||
, description = {} | ||
, accessorMap = { | ||
get: {} | ||
, fetch: {} | ||
}; | ||
QB._setProperties = function(){ | ||
this._setProperty('_queryBuilders', options.queryBuilders); | ||
this._setProperty('_definition', options.definition); | ||
this._setProperty('_orm', options.orm); | ||
this._setProperty('_getDatabase', options.getDatabase); | ||
} | ||
// clone class | ||
Object.keys(QueryBuilder).forEach(function (key) { | ||
QB[key] = QueryBuilder[key]; | ||
}.bind(this)); | ||
// create accessor methods | ||
Object.keys(options.definition.columns).forEach(function (columnName) { | ||
var column = options.definition.columns[columnName] | ||
, id; | ||
// mappin grelations | ||
if (column.mapsTo.length) { | ||
column.mapsTo.filter(function(target){ | ||
// store reference into map for generic methods | ||
mappingMap[target.via.model.name] = { | ||
column: column | ||
, target: target | ||
}; | ||
// filter thoose which must be accessed via the generic accessor | ||
return !target.useGenericAccessor; | ||
}).forEach(function(target){ | ||
accessorMap.get[target.name] = function() { | ||
return this._handleMapping(column, target, Array.prototype.slice.call(arguments), true); | ||
}; | ||
accessorMap.fetch[target.name] = function() { | ||
return this._handleMapping(column, target, Array.prototype.slice.call(arguments)); | ||
}; | ||
description[this._getAccessorName(target.name)] = 'Mapping accessor for the «'+target.name+'» Model'; | ||
description[this._getAccessorName(target.name, true)] = 'Mapping accessor for the «'+target.name+'» Model'; | ||
//log.warn(definition.name, column.name, target.model.name, this.mapperGetterName(target.model.name), target.via.model.name); | ||
Object.defineProperty(QB, this._getAccessorName(target.name), { | ||
value : accessorMap.get[target.name] | ||
, enumerable : true | ||
}); | ||
Object.defineProperty(QB, this._getAccessorName(target.name, true), { | ||
value : accessorMap.fetch[target.name] | ||
, enumerable : true | ||
}); | ||
}.bind(this)); | ||
} | ||
// reference | ||
if (column.referencedModel) { | ||
referenceMap[column.name] = { | ||
column: column | ||
, target: column.referencedModel | ||
}; | ||
if (!column.useGenericAccessor) { | ||
accessorMap.get[column.referencedModel.name] = function() { | ||
return this._handleReference(column, column.referencedModel, Array.prototype.slice.call(arguments)); | ||
}; | ||
accessorMap.fetch[column.referencedModel.name] = function() { | ||
return this._handleReference(column, column.referencedModel, Array.prototype.slice.call(arguments), true); | ||
}; | ||
description[this._getAccessorName(column.referencedModel.name)] = 'Reference accessor for the «'+column.referencedModel.name+'» Model'; | ||
description[this._getAccessorName(column.referencedModel.name, true)] = 'Reference accessor for the «'+column.referencedModel.name+'» Model'; | ||
QB[this._getAccessorName(column.referencedModel.name, true)] = accessorMap.get[column.referencedModel.name]; | ||
QB[this._getAccessorName(column.referencedModel.name)] = accessorMap.fetch[column.referencedModel.name]; | ||
} | ||
} | ||
// belongs to | ||
if (column.belongsTo.length) { | ||
column.belongsTo.filter(function(target){ | ||
// store reference into map for generic methods | ||
belongsToMap[target.model.name] = { | ||
column: column | ||
, target: target | ||
}; | ||
// filter thoose which must be accessed via the generic accessor | ||
return !target.useGenericAccessor; | ||
}).forEach(function(target){ | ||
//log.warn(definition.name, column.name, target.name, this.mapperGetterName(target.name)); | ||
accessorMap.get[target.name] = function() { | ||
return this._handleBelongsTo(column, target, Array.prototype.slice.call(arguments)); | ||
}; | ||
accessorMap.fetch[target.name] = function() { | ||
return this._handleBelongsTo(column, target, Array.prototype.slice.call(arguments), true); | ||
}; | ||
description[this._getAccessorName(target.name)] = 'Belongs to accessor for the «'+target.name+'» Model'; | ||
description[this._getAccessorName(target.name, true)] = 'Belongs to accessor for the «'+target.name+'» Model'; | ||
QB[this._getAccessorName(target.name, true)] = accessorMap.get[target.name]; | ||
QB[this._getAccessorName(target.name)] = accessorMap.fetch[target.name]; | ||
}.bind(this)); | ||
} | ||
}.bind(this)); | ||
// generic accessor for everything | ||
Object.defineProperty(QB, 'get', { | ||
value: function(targetName) { | ||
if (Object.hasOwnProperty.call(accessorMap.get, targetName)) { | ||
return accessorMap.get[targetName].apply(this, Array.prototype.slice.call(arguments, 1)); | ||
} | ||
else throw new Error('The QueryBuilder has no property «'+targetName+'»!'); | ||
} | ||
, enumerable: true | ||
}); | ||
Object.defineProperty(QB, 'fetch', { | ||
value: function(targetName) { | ||
if (Object.hasOwnProperty.call(accessorMap.fetch, targetName)) { | ||
return accessorMap.fetch[targetName].apply(this, Array.prototype.slice.call(arguments, 1)); | ||
} | ||
else throw new Error('The QueryBuilder has no property «'+targetName+'»!'); | ||
} | ||
, enumerable: true | ||
}); | ||
// generic accessors for mappings | ||
Object.defineProperty(QB, 'getMapping', { | ||
value: function(mappingTableName) { | ||
var info = mappingMap[mappingTableName]; | ||
if (info) return this._handleMapping(info.column, info.target, Array.prototype.slice.call(arguments, 1), true); | ||
else throw new Error('Unknown mapping «'+mappingTableName+'» on entity «'+options.definition.name+'»!'); | ||
} | ||
, enumerable: true | ||
}); | ||
Object.defineProperty(QB, 'fetchMapping', { | ||
value: function(mappingTableName) { | ||
var info = mappingMap[mappingTableName]; | ||
if (info) return this._handleMapping(info.column, info.target, Array.prototype.slice.call(arguments, 1)); | ||
else throw new Error('Unknown mapping «'+mappingTableName+'» on entity «'+options.definition.name+'»!'); | ||
} | ||
, enumerable: true | ||
}); | ||
// generic accessors for references | ||
Object.defineProperty(QB, 'getReference', { | ||
value: function(referencedTable) { | ||
var info = referenceMap[referencedTable]; | ||
if (info) return this._handleReference(info.column, info.target, Array.prototype.slice.call(arguments, 1), true); | ||
else throw new Error('Unknown reference «'+referencedTable+'» on entity «'+options.definition.name+'»!'); | ||
} | ||
, enumerable: true | ||
}); | ||
Object.defineProperty(QB, 'fetchReference', { | ||
value: function(referencedTable) { | ||
var info = referenceMap[referencedTable]; | ||
if (info) return this._handleReference(info.column, info.target, Array.prototype.slice.call(arguments, 1)); | ||
else throw new Error('Unknown reference «'+referencedTable+'» on entity «'+options.definition.name+'»!'); | ||
} | ||
, enumerable: true | ||
}); | ||
// generic accessors for belongsto | ||
Object.defineProperty(QB, 'getBelongsTo', { | ||
value: function(belongingTableName) { | ||
var info = belongsToMap[belongingTableName]; | ||
if (info) return this._handleBelongsTo(info.column, info.target, Array.prototype.slice.call(arguments, 1), true); | ||
else throw new Error('Unknown belongstTo «'+mappingTableName+'» on entity «'+options.definition.name+'»!'); | ||
} | ||
, enumerable: true | ||
}); | ||
Object.defineProperty(QB, 'fetchBelongsTo', { | ||
value: function(belongingTableName) { | ||
var info = belongsToMap[belongingTableName]; | ||
if (info) return this._handleBelongsTo(info.column, info.target, Array.prototype.slice.call(arguments, 1)); | ||
else throw new Error('Unknown belongstTo «'+mappingTableName+'» on entity «'+options.definition.name+'»!'); | ||
} | ||
, enumerable: true | ||
}); | ||
// describe methods function | ||
Object.defineProperty(QB, 'describeMethods', { | ||
value: function() { | ||
log(description); | ||
return this; | ||
} | ||
, enumerable: true | ||
}); | ||
return new Class(QB); | ||
} | ||
, _getAccessorName: function(id, useFetch) { | ||
return (useFetch ? 'fetch' : 'get') + id[0].toUpperCase()+id.slice(1); | ||
} | ||
}); | ||
, delete: function(callback) { | ||
new QueryCompiler({ | ||
orm : this._orm | ||
, getDatabase : this._getDatabase | ||
, resource : this._rootResource | ||
}).delete(callback); | ||
} | ||
}); | ||
}(); |
!function(){ | ||
var Class = require('ee-class') | ||
, EventEmitter = require('ee-event-emitter') | ||
, log = require('ee-log') | ||
, async = require('ee-async') | ||
, Set = require('./Set'); | ||
var Class = require('ee-class') | ||
, log = require('ee-log') | ||
, async = require('ee-async') | ||
, Set = require('./Set'); | ||
@@ -12,54 +11,54 @@ | ||
module.exports = new Class({ | ||
init: function(options) { | ||
this._resource = options.resource; | ||
this._orm = options.orm; | ||
this.getDatabase = options.getDatabase; | ||
} | ||
module.exports = new Class({ | ||
init: function(options) { | ||
this._resource = options.resource; | ||
this._orm = options.orm; | ||
this.getDatabase = options.getDatabase; | ||
} | ||
, findOne: function(callback) { | ||
this._resource.query.limit = 1; | ||
this.find(function(err, results) { | ||
if (err) callback(err); | ||
else if (results && results.length) callback(null, results.first()); | ||
else callback(); | ||
}.bind(this)); | ||
} | ||
, findOne: function(callback) { | ||
this._resource.query.limit = 1; | ||
this.find(function(err, results) { | ||
if (err) callback(err); | ||
else if (results && results.length) callback(null, results.first()); | ||
else callback(); | ||
}.bind(this)); | ||
} | ||
, find: function(callback) { | ||
var resource = this._resource; | ||
, find: function(callback) { | ||
var resource = this._resource; | ||
// prepare child queries | ||
this._prepareChildResources(resource); | ||
// prepare child queries | ||
this._prepareChildResources(resource); | ||
// execut ebase query | ||
this._executeQuery('query', resource.query, function(err, rows){ | ||
if (err) callback(err); | ||
else { | ||
var queries; | ||
// execut ebase query | ||
this._executeQuery('query', resource.query, function(err, rows){ | ||
if (err) callback(err); | ||
else { | ||
var queries; | ||
// create set | ||
resource.set = this._makeSet(rows, resource); | ||
// create set | ||
resource.set = this._makeSet(rows, resource); | ||
if (resource.set.length) { | ||
if (resource.set.length) { | ||
// collect queries | ||
queries = this._collectQueries(resource, []); | ||
// collect queries | ||
queries = this._collectQueries(resource, []); | ||
if (queries && queries.length) { | ||
// execute queries | ||
this._executeSubqueries(resource, queries, callback); | ||
} | ||
else callback(null, resource.set); | ||
} | ||
else callback(null, resource.set); | ||
} | ||
}.bind(this)); | ||
} | ||
if (queries && queries.length) { | ||
// execute queries | ||
this._executeSubqueries(resource, queries, callback); | ||
} | ||
else callback(null, resource.set); | ||
} | ||
else callback(null, resource.set); | ||
} | ||
}.bind(this)); | ||
} | ||
@@ -69,5 +68,5 @@ | ||
, delete: function(callback) { | ||
this._executeQuery('delete', this._resource.query, callback); | ||
} | ||
, delete: function(callback) { | ||
this._executeQuery('delete', this._resource.query, callback); | ||
} | ||
@@ -77,164 +76,166 @@ | ||
, _executeSubqueries: function(rootResource, queries, callback) { | ||
, _executeSubqueries: function(rootResource, queries, callback) { | ||
async.each(queries, | ||
async.each(queries, | ||
function(resource, next){ | ||
function(resource, next){ //log.wtf(resource.name); | ||
// filter the resource by the ids of the root resource | ||
rootResource.applyFilter(resource); | ||
// filter the resource by the ids of the root resource | ||
rootResource.applyFilter(resource); | ||
rootResource.applyGroup(resource); | ||
if (resource.parentResource) resource.parentResource.applyGroup(resource); | ||
//log(resource); | ||
this._executeQuery('query', resource.query, function(err, rows){ | ||
if (err) next(err); | ||
else { | ||
resource.set = this._makeSet(rows, resource); | ||
next(); | ||
} | ||
}.bind(this)); | ||
}.bind(this), | ||
this._executeQuery('query', resource.query, function(err, rows){ | ||
if (err) next(err); | ||
else { | ||
resource.set = this._makeSet(rows, resource); | ||
next(); | ||
} | ||
}.bind(this)); | ||
}.bind(this), | ||
function(err, results){ | ||
if (err) callback(err); | ||
else { | ||
this._buildRelations(this._resource); | ||
callback(null, this._resource.set); | ||
} | ||
}.bind(this)); | ||
} | ||
function(err, results){ | ||
if (err) callback(err); | ||
else { | ||
this._buildRelations(this._resource); | ||
callback(null, this._resource.set); | ||
} | ||
}.bind(this)); | ||
} | ||
, _buildRelations: function(resource) { log.error(resource.hasChildren(), resource.query.from); | ||
if (resource.set && resource.hasChildren()) { | ||
resource.children.forEach(function(childResource){ | ||
if (childResource.set) { | ||
, _buildRelations: function(resource) { | ||
if (resource.set && resource.hasChildren()) { | ||
resource.children.forEach(function(childResource) { | ||
if (childResource.set) { | ||
childResource.set.forEach(function(record){ | ||
if (childResource.set.length) { | ||
childResource.set.forEach(function(record) { | ||
//log(childResource); | ||
record._mappingIds.forEach(function(mappingId) { | ||
var parentRecord = resource.set.getByColumnValue(childResource.referencedParentColumn, mappingId); | ||
record._mappingIds.forEach(function(mappingId){ log(mappingId); | ||
var parentRecord = resource.set.getByColumnValue(childResource.referencedParentColumn, mappingId); | ||
if (parentRecord) { | ||
//log(childResource.loaderId); | ||
if (childResource.type === 'mapping') { | ||
parentRecord.getMapping(childResource.loaderId).addExisiting(record); | ||
//parentRecord[childResource.name].addExisiting(record); | ||
} | ||
else if (childResource.type === 'belongsTo') { | ||
parentRecord.getBelongsTo(childResource.loaderId).addExisiting(record); | ||
//parentRecord[childResource.name].addExisiting(record); | ||
} | ||
else { | ||
// reference | ||
//parentRecord.setReference(childResource.loaderId, record, true); | ||
parentRecord[childResource.name] = record; | ||
} | ||
} | ||
}.bind(this)); | ||
}.bind(this)); | ||
} | ||
this._buildRelations(childResource); | ||
} | ||
}.bind(this)); | ||
} | ||
} | ||
if (parentRecord) { | ||
//log(childResource.loaderId); | ||
if (childResource.type === 'mapping') { | ||
parentRecord.getMapping(childResource.loaderId).addExisiting(record); | ||
//parentRecord[childResource.name].addExisiting(record); | ||
} | ||
else if (childResource.type === 'belongsTo') { | ||
parentRecord.getBelongsTo(childResource.loaderId).addExisiting(record); | ||
//parentRecord[childResource.name].addExisiting(record); | ||
} | ||
else { | ||
// reference | ||
//parentRecord.setReference(childResource.loaderId, record, true); | ||
parentRecord[childResource.name] = record; | ||
} | ||
} | ||
}.bind(this)); | ||
}.bind(this)); | ||
this._buildRelations(childResource); | ||
} | ||
}.bind(this)); | ||
} | ||
} | ||
// get all selected queries, add the correct filter to them | ||
, _collectQueries: function(resource, queries) { | ||
if (resource.hasChildren()) { | ||
resource.children.forEach(function(childResource){ | ||
if (childResource.selected) queries.push(childResource); | ||
this._collectQueries(childResource, queries); | ||
}.bind(this)); | ||
} | ||
return queries; | ||
} | ||
// get all selected queries, add the correct filter to them | ||
, _collectQueries: function(resource, queries) { | ||
if (resource.hasChildren()) { | ||
resource.children.forEach(function(childResource){ | ||
if (childResource.selected) queries.push(childResource); | ||
this._collectQueries(childResource, queries); | ||
}.bind(this)); | ||
} | ||
return queries; | ||
} | ||
// parse the the resource tree, check which queriies to execute | ||
// traverse the tree, check if the children are selected, if yes: | ||
// select all parents | ||
, _prepareChildResources: function(resource) { | ||
if (resource.hasChildren()) { | ||
resource.children.forEach(function(childResource){ | ||
if (childResource.selected) this._selectParents(childResource); | ||
if (childResource.hasRootFilter) this._filterParents(childResource); | ||
if (childResource.filtered) this._filterByChildren(childResource, [], childResource.query.filter, childResource.query.from); | ||
this._prepareChildResources(childResource); | ||
}.bind(this)); | ||
} | ||
} | ||
// parse the the resource tree, check which queriies to execute | ||
// traverse the tree, check if the children are selected, if yes: | ||
// select all parents | ||
, _prepareChildResources: function(resource) { | ||
if (resource.hasChildren()) { | ||
resource.children.forEach(function(childResource){ | ||
if (childResource.selected) this._selectParents(childResource); | ||
if (childResource.hasRootFilter) this._filterParents(childResource); | ||
if (childResource.filtered) this._filterByChildren(childResource, [], childResource.query.filter, childResource.query.from); | ||
this._prepareChildResources(childResource); | ||
}.bind(this)); | ||
} | ||
} | ||
, _filterByChildren: function(resource, joins, filter, resourceName) { | ||
if (!resource.selected) { | ||
if (!resource.childrenFiltered) { | ||
joins = joins.concat(resource.joins.map(function(joinStatement){ | ||
return joinStatement.reverseFormat(); | ||
})); | ||
resource.childrenFiltered = true; | ||
} | ||
, _filterByChildren: function(resource, joins, filter, resourceName) { | ||
if (!resource.selected) { | ||
if (!resource.childrenFiltered) { | ||
joins = joins.concat(resource.joins.map(function(joinStatement){ | ||
return joinStatement.reverseFormat(); | ||
})); | ||
if (resource.parentResource) this._filterByChildren(resource.parentResource, joins, filter, resourceName); | ||
} | ||
else if(resource.query.filter !== filter) { | ||
// apply filter & joins | ||
resource.query.filter[resourceName] = filter; | ||
resource.query.join = resource.query.join.concat(joins); | ||
} | ||
} | ||
resource.childrenFiltered = true; | ||
} | ||
if (resource.parentResource) this._filterByChildren(resource.parentResource, joins, filter, resourceName); | ||
} | ||
else if(resource.query.filter !== filter) { | ||
// apply filter & joins | ||
resource.query.filter[resourceName] = filter; | ||
resource.query.join = resource.query.join.concat(joins); | ||
} | ||
} | ||
// recursive select | ||
, _selectParents: function(resource) { | ||
resource.select(); | ||
if (resource.parentResource) { | ||
resource.parentResource.loadRelatingSet(resource.name); | ||
this._selectParents(resource.parentResource); | ||
} | ||
} | ||
// recursive select | ||
, _selectParents: function(resource) { | ||
resource.select(); | ||
if (resource.parentResource) { | ||
resource.parentResource.loadRelatingSet(resource.name); | ||
this._selectParents(resource.parentResource); | ||
} | ||
} | ||
, _filterParents: function(resource, originalResource) { | ||
if (resource.parentResource) this._filterParents(resource.parentResource); | ||
resource.filter(); | ||
} | ||
, _filterParents: function(resource, originalResource) { | ||
if (resource.parentResource) this._filterParents(resource.parentResource); | ||
resource.filter(); | ||
} | ||
, _executeQuery: function(mode, query, callback){ | ||
this.getDatabase().executeQuery(mode, query, callback); | ||
} | ||
, _executeQuery: function(mode, query, callback){ | ||
this.getDatabase().executeQuery(mode, query, callback); | ||
} | ||
, _makeSet: function(rows, resource) { | ||
var records = new Set({ | ||
primaryKeys: resource.primaryKeys | ||
, name: resource.name | ||
}); | ||
(rows || []).forEach(function(row){ | ||
Object.defineProperty(row, '_isFromDB', {value:true}); | ||
records.push(new resource.Model(row, resource.relatingSets)); | ||
}.bind(this)); | ||
, _makeSet: function(rows, resource) { | ||
var records = new Set({ | ||
primaryKeys: resource.primaryKeys | ||
, name: resource.name | ||
}); | ||
return records; | ||
} | ||
(rows || []).forEach(function(row){ | ||
Object.defineProperty(row, '_isFromDB', {value:true}); | ||
records.push(new resource.Model(row, resource.relatingSets)); | ||
}.bind(this)); | ||
return records; | ||
} | ||
}); | ||
}); | ||
}(); |
!function(){ | ||
var Class = require('ee-class') | ||
, type = require('ee-types') | ||
, async = require('ee-async') | ||
, arg = require('ee-arguments') | ||
, log = require('ee-log'); | ||
var Class = require('ee-class') | ||
, type = require('ee-types') | ||
, async = require('ee-async') | ||
, Arguments = require('ee-arguments') | ||
, log = require('ee-log'); | ||
var proto = Array.prototype; | ||
var proto = Array.prototype; | ||
var RelatingPrototype = Object.create(proto, { | ||
module.exports = new Class({ | ||
inherits: Array | ||
// queries beeing executed | ||
_workers: { | ||
get: function(){ return this._workerCount;} | ||
, set: function(value) { | ||
this._workerCount = value; | ||
if (value === 0) this.emit('idle'); | ||
} | ||
} | ||
, init: function(options) { | ||
Class.define(this, '_orm' , Class(options.orm)); | ||
Class.define(this, '_definition' , Class(options.definition)); | ||
Class.define(this, '_relatesTo' , Class(options.related)); | ||
Class.define(this, '_column' , Class(options.column)); | ||
Class.define(this, '_database' , Class(options.database)); | ||
Class.define(this, '_workerCount' , Class(0).Writable()); | ||
Class.define(this, '_originalRecords' , Class([]).Writable()); | ||
Class.define(this, '_error' , Class(null).Writable()); | ||
Class.define(this, 'isMapping' , Class(!!options.isMapping)); | ||
} | ||
// save changes on the | ||
, save: {value: function() { | ||
var callback = arg(arguments, 'function', function(){}) | ||
, noReload = arg(arguments, 'boolean', false) | ||
, transaction = arg(arguments, 'object'); | ||
if (transaction) { | ||
this._save(transaction, noReload, callback); | ||
} | ||
else { | ||
transaction = this.getDatabase().createTransaction(); | ||
, _workers: { | ||
get: function(){ return this._workerCount;} | ||
, set: function(value) { | ||
this._workerCount = value; | ||
if (value === 0) this.emit('idle'); | ||
} | ||
} | ||
this._save(transaction, noReload, function(err){ | ||
if (err) { | ||
transaction.rollback(function(transactionErr){ | ||
if (transactionErr) callback(transactionErr); | ||
else callback(err); | ||
}.bind(this)); | ||
} | ||
else { | ||
transaction.commit(function(err){ | ||
if (err) callback(err); | ||
else callback(null, this); | ||
}.bind(this)); | ||
} | ||
}.bind(this)); | ||
} | ||
return this; | ||
}} | ||
// save changes on the | ||
, save: {value: function() { | ||
var args = new Arguments(arguments) | ||
, callback = args.getFunction(function(){}) | ||
, noReload = args.getBoolean(false) | ||
, transaction = args.getObject(); | ||
if (transaction) { | ||
this._save(transaction, noReload, callback); | ||
} | ||
else { | ||
transaction = this.getDatabase().createTransaction(); | ||
// save changes on the | ||
, _save: {value: function(transaction, noReload, callback) { | ||
// wait until the relatingset is idle, then store all records and create relations | ||
if (this._error) callback(this._error); | ||
else { | ||
if (this._workers) { | ||
this.once('idle', function(){ | ||
this._executeSave(transaction, noReload, callback); | ||
}.bind(this)); | ||
} | ||
else { | ||
this._executeSave(transaction, noReload, callback); | ||
} | ||
} | ||
}} | ||
this._save(transaction, noReload, function(err){ | ||
if (err) { | ||
transaction.rollback(function(transactionErr){ | ||
if (transactionErr) callback(transactionErr); | ||
else callback(err); | ||
}.bind(this)); | ||
} | ||
else { | ||
transaction.commit(function(err){ | ||
if (err) callback(err); | ||
else callback(null, this); | ||
}.bind(this)); | ||
} | ||
}.bind(this)); | ||
} | ||
return this; | ||
}} | ||
, _executeSave: {value: function(transaction, noReload, callback) { | ||
// check for query items | ||
var queries = []; | ||
for (var i = 0; i < this.length; i++) { | ||
if (this[i].isQuery) { | ||
queries.push(proto.splice.call(this, i, 1)[0]); | ||
i--; | ||
} | ||
} | ||
// save changes on the | ||
, _save: {value: function(transaction, noReload, callback) { | ||
// wait until the relatingset is idle, then store all records and create relations | ||
if (this._error) callback(this._error); | ||
else { | ||
if (this._workers) { | ||
this.once('idle', function(){ | ||
this._executeSave(transaction, noReload, callback); | ||
}.bind(this)); | ||
} | ||
else { | ||
this._executeSave(transaction, noReload, callback); | ||
} | ||
} | ||
}} | ||
// execute all queries | ||
async.each(queries, function(query, next){ | ||
this._addQueryItem(item, next); | ||
}.bind(this), function(err){ | ||
if (err) callback(err); | ||
else { | ||
// save all unsaved items | ||
async.each(this, function(item, next){ | ||
if (item.isQuery) { | ||
this._addQueryItem(item, next); | ||
} | ||
else if (!item.isFromDatabase() || !item.isSaved()) { | ||
// we have to save this item before we can proceed | ||
// if we're a belongs to set we need to set our id on the item | ||
if (!this.isMapping) { | ||
item[this._definition.targetColumn] = this._relatesTo[this._column.name]; | ||
item[this._relatesTo.getDefinition().name] = this._relatesTo; | ||
} | ||
item.save(transaction, noReload, next); | ||
} | ||
else next(); | ||
}.bind(this), function(err){ | ||
if (err) callback(err); | ||
else { | ||
this._getChangedRecords(function(err, added, removed){ | ||
if (err) callback(err); | ||
else { | ||
// create / remove relational records | ||
async.wait(function(done){ | ||
this._deleteRelationRecords(removed, transaction, noReload, done); | ||
}.bind(this), function(done){ | ||
this._createRelationRecords(added, transaction, noReload, done); | ||
}.bind(this), callback); | ||
} | ||
}.bind(this)); | ||
} | ||
}.bind(this)); | ||
} | ||
}.bind(this)); | ||
}} | ||
, _executeSave: {value: function(transaction, noReload, callback) { | ||
// check for query items | ||
var queries = []; | ||
for (var i = 0; i < this.length; i++) { | ||
if (this[i].isQuery) { log.warn('query'); | ||
queries.push(proto.splice.call(this, i, 1)[0]); | ||
i--; | ||
} | ||
} | ||
// execute all queries | ||
async.each(queries, function(query, next){ | ||
this._addQueryItem(item, next); | ||
}.bind(this), function(err){ | ||
if (err) callback(err); | ||
else { | ||
// save all unsaved items | ||
async.each(this, function(item, next){ | ||
if (item.isQuery) { | ||
this._addQueryItem(item, next); | ||
} | ||
else if (!item.isFromDatabase() || !item.isSaved()) { | ||
// we have to save this item before we can proceed | ||
// if we're a belongs to set we need to set our id on the item | ||
//log.warn('unsaved item'); | ||
if (!this.isMapping) { | ||
//log(this._definition.targetColumn, this._relatesTo[this._column.name]); | ||
item[this._definition.targetColumn] = this._relatesTo[this._column.name]; | ||
//item[this._relatesTo.getDefinition().name] = this._relatesTo; | ||
} | ||
item.save(transaction, noReload, next); | ||
} | ||
else next(); | ||
}.bind(this), function(err){ | ||
if (err) callback(err); | ||
else { | ||
this._getChangedRecords(function(err, added, removed){ | ||
if (err) callback(err); | ||
else { | ||
// create / remove relational records | ||
async.wait(function(done){ | ||
this._deleteRelationRecords(removed, transaction, noReload, done); | ||
}.bind(this), function(done){ | ||
this._createRelationRecords(added, transaction, noReload, done); | ||
}.bind(this), callback); | ||
} | ||
}.bind(this)); | ||
} | ||
}.bind(this)); | ||
} | ||
}.bind(this)); | ||
}} | ||
, _getChangedRecords: {value: function(callback){ | ||
var removed = [] | ||
, added = [] | ||
, originalMap = this._createMap(this._originalRecords) | ||
, currentMap = this._createMap(this); | ||
// adde items | ||
Object.keys(currentMap).forEach(function(newItemKey){ | ||
if (!originalMap[newItemKey]) { | ||
// new item | ||
added.push(currentMap[newItemKey]); | ||
} | ||
}.bind(this)); | ||
// removed items | ||
Object.keys(originalMap).forEach(function(oldItemKey){ | ||
if (!currentMap[oldItemKey]) { | ||
// new item | ||
removed.Push(originalMap[oldItemKey]); | ||
} | ||
}.bind(this)); | ||
, _getChangedRecords: {value: function(callback){ | ||
var removed = [] | ||
, added = [] | ||
, originalMap = this._createMap(this._originalRecords) | ||
, currentMap = this._createMap(this); | ||
callback(null, added, removed); | ||
}} | ||
// adde items | ||
Object.keys(currentMap).forEach(function(newItemKey){ | ||
if (!originalMap[newItemKey]) { | ||
// new item | ||
added.push(currentMap[newItemKey]); | ||
} | ||
}.bind(this)); | ||
// removed items | ||
Object.keys(originalMap).forEach(function(oldItemKey){ | ||
if (!currentMap[oldItemKey]) { | ||
// new item | ||
removed.Push(originalMap[oldItemKey]); | ||
} | ||
}.bind(this)); | ||
, _createRelationRecords: { value: function(addedRecords, transaction, noReload, callback) { | ||
async.each(addedRecords, function(record, next){ | ||
if (this.isMapping) { | ||
var values = {}; | ||
callback(null, added, removed); | ||
}} | ||
values[this._definition.via.fk] = this._relatesTo[this._column.name]; | ||
values[this._definition.via.otherFk] = record[this._definition.column.name]; | ||
new this._orm[this._definition.model.getDatabaseName()][this._definition.via.model.name](values).save(transaction, next); | ||
} | ||
else next(); | ||
}.bind(this), callback); | ||
}} | ||
, _createRelationRecords: { value: function(addedRecords, transaction, noReload, callback) { | ||
async.each(addedRecords, function(record, next){ | ||
if (this.isMapping) { | ||
var values = {}; | ||
values[this._definition.via.fk] = this._relatesTo[this._column.name]; | ||
values[this._definition.via.otherFk] = record[this._definition.column.name]; | ||
, _deleteRelationRecords: {value: function(removedRecords, transaction, noReload, callback) { | ||
async.each(removedRecords, function(record, next){ | ||
if (this.isMapping) { | ||
var values = {}; | ||
new this._orm[this._definition.model.getDatabaseName()][this._definition.via.model.name](values).save(transaction, next); | ||
} | ||
else next(); | ||
}.bind(this), callback); | ||
}} | ||
values[this._definition.via.fk] = this._relatesTo[this._column.name]; | ||
values[this._definition.via.otherFk] = record[this._definition.column.name]; | ||
transaction[this._definition.via.model.name](values).delete(next) | ||
} | ||
else next(); | ||
}.bind(this), callback); | ||
}} | ||
, _deleteRelationRecords: {value: function(removedRecords, transaction, noReload, callback) { | ||
async.each(removedRecords, function(record, next){ | ||
if (this.isMapping) { | ||
var values = {}; | ||
values[this._definition.via.fk] = this._relatesTo[this._column.name]; | ||
values[this._definition.via.otherFk] = record[this._definition.column.name]; | ||
transaction[this._definition.via.model.name](values).delete(next) | ||
} | ||
else next(); | ||
}.bind(this), callback); | ||
}} | ||
, _createMap: {value: function(items){ | ||
var map = {} | ||
, primaryKeys = this._relatesTo.getDefinition().primaryKeys; | ||
items.forEach(function(item){ | ||
var compositeKey = ''; | ||
primaryKeys.forEach(function(key){ | ||
compositeKey += '|'+item[key]; | ||
}.bind(this), ''); | ||
, _createMap: {value: function(items){ | ||
var map = {} | ||
, primaryKeys = this._relatesTo.getDefinition().primaryKeys; | ||
map[compositeKey] = item; | ||
}); | ||
items.forEach(function(item){ | ||
var compositeKey = ''; | ||
return map; | ||
}} | ||
primaryKeys.forEach(function(key){ | ||
compositeKey += '|'+item[key]; | ||
}.bind(this), ''); | ||
map[compositeKey] = item; | ||
}); | ||
return map; | ||
}} | ||
// reload all records | ||
, reload: {value: function(callback, transaction) { | ||
// check if there are unsaved values on the relation, then reload all of them | ||
var doRelaod = function(){ | ||
this._reload(callback, (transaction || this._relatesTo._getDatabase())); | ||
}.bind(this); | ||
// wait until we become idle | ||
if (this._workers) this.on('idle', doRelaod); | ||
else doRelaod(); | ||
}} | ||
// reload all records | ||
, reload: {value: function(callback, transaction) { | ||
// check if there are unsaved values on the relation, then reload all of them | ||
var doRelaod = function(){ | ||
this._reload(callback, (transaction || this._relatesTo._getDatabase())); | ||
}.bind(this); | ||
, _reload: {value: function(callback, transaction) { | ||
// check if there are unsaved values on the relation, then reload all of them | ||
this._getChangedRecords(function(err, added, removed){ | ||
if (err) callback(err); | ||
else { | ||
log.error('reloading for relating sets needs to be implemented.'); | ||
return callback(); | ||
if (added.length || removed.length) callback(new Error('Cannot reload relation «'+this._definition.name+'» on model «'+this._relatesTo.getEntityName()+'», there are unsaved changes!')); | ||
else { | ||
if (this.isMapping) { | ||
// create a map of existing ids | ||
var query = { | ||
// wait until we become idle | ||
if (this._workers) this.on('idle', doRelaod); | ||
else doRelaod(); | ||
}} | ||
}; | ||
} | ||
else { | ||
} | ||
} | ||
} | ||
}.bind(this)); | ||
}} | ||
, _reload: {value: function(callback, transaction) { | ||
// check if there are unsaved values on the relation, then reload all of them | ||
this._getChangedRecords(function(err, added, removed){ | ||
if (err) callback(err); | ||
else { | ||
//log.error('reloading for relating sets needs to be implemented.'); | ||
return callback(); | ||
if (added.length || removed.length) callback(new Error('Cannot reload relation «'+this._definition.name+'» on model «'+this._relatesTo.getEntityName()+'», there are unsaved changes!')); | ||
else { | ||
if (this.isMapping) { | ||
// create a map of existing ids | ||
var query = { | ||
}; | ||
} | ||
else { | ||
} | ||
} | ||
} | ||
}.bind(this)); | ||
}} | ||
/* | ||
* the push() method accepts eiteher a quer or a saved or not saved | ||
* model of the target entity. if it gets a query it execute it immediatelly | ||
* in order to get the records which it shoudl link. it wil lpush all models | ||
* added or found via query to the _addedRecords array which will be stored / | ||
* linked when the save method gets calleds | ||
* | ||
* @param <Object> item: query or model | ||
* @param <Function> callback | ||
*/ | ||
, push: { value: function push (item, callback) { | ||
if (type.object(item)) { | ||
// check if the item is a model or a query | ||
if (item.isQuery) { | ||
// execute the query, add it to | ||
this._addQueryItem(item, callback); | ||
} | ||
else { | ||
if (this._isCorrectType(item)){ | ||
proto.push.call(this, item); | ||
if (callback) callback(null, this.length); | ||
} | ||
else { | ||
this._error = new Error('Attempt to add models of type «'+item.getName()+'» to a relatingset of type «'+this._definition.name+'» via a query!'); | ||
if (callback) callback(this._error); | ||
} | ||
} | ||
} | ||
else { | ||
this._error = new Error('Push was called with an invalid value: «'+type(item)+'»') | ||
if (callback) callback(this._error); | ||
} | ||
return this.length; | ||
}} | ||
/* | ||
* the push() method accepts eiteher a quer or a saved or not saved | ||
* model of the target entity. if it gets a query it execute it immediatelly | ||
* in order to get the records which it shoudl link. it wil lpush all models | ||
* added or found via query to the _addedRecords array which will be stored / | ||
* linked when the save method gets calleds | ||
* | ||
* @param <Object> item: query or model | ||
* @param <Function> callback | ||
*/ | ||
, push: { value: function push (item, callback) { | ||
if (type.object(item)) { | ||
// check if the item is a model or a query | ||
if (item.isQuery) { | ||
// execute the query, add it to | ||
this._addQueryItem(item, callback); | ||
} | ||
else { | ||
if (this._isCorrectType(item)){ | ||
proto.push.call(this, item); | ||
if (callback) callback(null, this.length); | ||
} | ||
else { | ||
this._error = new Error('Attempt to add models of type «'+item.getName()+'» to a relatingset of type «'+this._definition.name+'» via a query!'); | ||
if (callback) callback(this._error); | ||
} | ||
} | ||
} | ||
else { | ||
this._error = new Error('Push was called with an invalid value: «'+type(item)+'»') | ||
if (callback) callback(this._error); | ||
} | ||
return this.length; | ||
}} | ||
, splice: {value:function(index, howMany) { | ||
var newItems = proto.slice.call(arguments, 2) | ||
, removedItems = proto.splice.call(this, index, howMany) | ||
, callback = arg(arguments, 'function'); | ||
// remove the callback if present | ||
if (callback) newItems.pop(); | ||
if (newItems.length) { | ||
asyn.each(newItems, function(item, next){ | ||
if (type.object(item)) { | ||
if (item.isQuery) this._addQueryItem(item, next, index); | ||
else { | ||
if (this._isCorrectType(item)) { | ||
proto.splice.call(this, index, 0, item); | ||
next(); | ||
} | ||
else { | ||
this._error = new Error('Attempt to add models of type «'+item.getName()+'» to a relatingset of type «'+this._definition.name+'»!'); | ||
next(this._error); | ||
} | ||
} | ||
} | ||
else { | ||
this._error = new Error('Push was called with an invalid value: «'+type(item)+'»') | ||
next(this._error); | ||
} | ||
}.bind(this) | ||
, splice: {value:function(index, howMany) { | ||
var newItems = proto.slice.call(arguments, 2) | ||
, removedItems = proto.splice.call(this, index, howMany) | ||
, callback = arg(arguments, 'function'); | ||
, function(err){ | ||
if (err) { | ||
this._error = err; | ||
callback(err, removedItems); | ||
} | ||
else callback(null, removedItems); | ||
}.bind(this)); | ||
} | ||
else { | ||
if (callback) callback(null, removedItems); | ||
} | ||
// remove the callback if present | ||
if (callback) newItems.pop(); | ||
return removedItems; | ||
}} | ||
if (newItems.length) { | ||
asyn.each(newItems, function(item, next){ | ||
if (type.object(item)) { | ||
if (item.isQuery) this._addQueryItem(item, next, index); | ||
else { | ||
if (this._isCorrectType(item)) { | ||
proto.splice.call(this, index, 0, item); | ||
next(); | ||
} | ||
else { | ||
this._error = new Error('Attempt to add models of type «'+item.getName()+'» to a relatingset of type «'+this._definition.name+'»!'); | ||
next(this._error); | ||
} | ||
} | ||
} | ||
else { | ||
this._error = new Error('Push was called with an invalid value: «'+type(item)+'»') | ||
next(this._error); | ||
} | ||
}.bind(this) | ||
, function(err){ | ||
if (err) { | ||
this._error = err; | ||
callback(err, removedItems); | ||
} | ||
else callback(null, removedItems); | ||
}.bind(this)); | ||
} | ||
else { | ||
if (callback) callback(null, removedItems); | ||
} | ||
return removedItems; | ||
}} | ||
, unshift: {value: function(item, callback){ | ||
if (type.object(item)) { | ||
// check if the item is a model or a query | ||
if (item.isQuery) { | ||
// execute the query, add it to | ||
this._addQueryItem(item, callback, null, true); | ||
} | ||
else { | ||
if (this._isCorrectType(item)){ | ||
proto.unshift.call(this, item); | ||
if (callback) callback(null, this.length); | ||
} | ||
else { | ||
this._error = new Error('Attempt to add models of type «'+item.getName()+'» to a relatingset of type «'+this._definition.name+'»!'); | ||
if (callback) callback(this._error); | ||
} | ||
} | ||
} | ||
else { | ||
this._error = new Error('Unshift was called with an invalid value: «'+type(item)+'»') | ||
if (callback) callback(this._error); | ||
} | ||
return this.length; | ||
}} | ||
, unshift: {value: function(item, callback){ | ||
if (type.object(item)) { | ||
// check if the item is a model or a query | ||
if (item.isQuery) { | ||
// execute the query, add it to | ||
this._addQueryItem(item, callback, null, true); | ||
} | ||
else { | ||
if (this._isCorrectType(item)){ | ||
proto.unshift.call(this, item); | ||
if (callback) callback(null, this.length); | ||
} | ||
else { | ||
this._error = new Error('Attempt to add models of type «'+item.getName()+'» to a relatingset of type «'+this._definition.name+'»!'); | ||
if (callback) callback(this._error); | ||
} | ||
} | ||
} | ||
else { | ||
this._error = new Error('Unshift was called with an invalid value: «'+type(item)+'»') | ||
if (callback) callback(this._error); | ||
} | ||
return this.length; | ||
}} | ||
, _addQueryItem: {value: function(item, callback, index, unshift){ | ||
this._workers++; | ||
item.find(function(err, records){ | ||
var err; | ||
this._workers--; | ||
, _addQueryItem: {value: function(item, callback, index, unshift){ | ||
this._workers++; | ||
if (err) { | ||
if (callback) callback(err); | ||
} | ||
else { | ||
records.forEach(function(model){ | ||
if (this._isCorrectType(model)){ | ||
if (type.number(index)) proto.splice.call(this, index, 0, model); | ||
else if (unshift) proto.unshift.call(this, model); | ||
else proto.push.call(this, model); | ||
} | ||
else { | ||
err = this._error = new Error('Attempt to add models of type «'+model.getName()+'» to a relatingset of type «'+this._definition.name+'» via a query!'); | ||
} | ||
}.bind(this)); | ||
item.find(function(err, records){ | ||
var err; | ||
if (callback) callback(err, this.length); | ||
} | ||
}.bind(this)); | ||
}} | ||
this._workers--; | ||
if (err) { | ||
if (callback) callback(err); | ||
} | ||
else { | ||
records.forEach(function(model){ | ||
if (this._isCorrectType(model)){ | ||
if (type.number(index)) proto.splice.call(this, index, 0, model); | ||
else if (unshift) proto.unshift.call(this, model); | ||
else proto.push.call(this, model); | ||
} | ||
else { | ||
err = this._error = new Error('Attempt to add models of type «'+model.getName()+'» to a relatingset of type «'+this._definition.name+'» via a query!'); | ||
} | ||
}.bind(this)); | ||
if (callback) callback(err, this.length); | ||
} | ||
}.bind(this)); | ||
}} | ||
// internal only method for adding records which were already mappend to the collection | ||
, addExisiting: { value: function(item){ | ||
this._originalRecords.push(item); | ||
proto.push.call(this, item); | ||
}} | ||
// check if a model is typeof this | ||
, _isCorrectType: { value: function(model){ | ||
return model.getEntityName() === this._definition.model.name; | ||
}} | ||
// internal only method for adding records which were already mappend to the collection | ||
, addExisiting: { value: function(item){ | ||
this._originalRecords.push(item); | ||
proto.push.call(this, item); | ||
}} | ||
// check if a model is typeof this | ||
, _isCorrectType: { value: function(model){ | ||
return model.getEntityName() === this._definition.model.name; | ||
}} | ||
, pop: { value: function pop () { | ||
pop.parent(); | ||
}} | ||
, pop: { value: function pop () { | ||
pop.parent(); | ||
}} | ||
, shift: { value: function shift () { | ||
shift.parent(); | ||
}} | ||
, unshift: { value: function unshift () { | ||
unshift.parent(); | ||
}} | ||
, shift: { value: function shift () { | ||
shift.parent(); | ||
}} | ||
, unshift: { value: function unshift () { | ||
unshift.parent(); | ||
}} | ||
// inheriting from the array type, have to implement event by myself | ||
, _events: {value: {}, writable: true} | ||
// on | ||
, on: {value: function(evt, listener){ | ||
if (!this._events[evt]) this._events[evt] = []; | ||
this._events[evt].push({fn: listener}); | ||
}} | ||
// once | ||
, once: {value: function(evt, listener){ | ||
if (!this._events[evt]) this._events[evt] = []; | ||
this._events[evt].push({fn: listener, once: true}); | ||
}} | ||
// inheriting from the array type, have to implement event by myself | ||
, _events: {value: {}, writable: true} | ||
// emit | ||
, emit: {value: function(evt){ | ||
var rm = []; | ||
// on | ||
, on: {value: function(evt, listener){ | ||
if (!this._events[evt]) this._events[evt] = []; | ||
this._events[evt].push({fn: listener}); | ||
}} | ||
if (this._events[evt]) { | ||
this._events[evt].forEach(function(listener){ | ||
listener.fn.apply(null, Array.prototype.slice.call(arguments, 1)); | ||
if (listener.once) rm.push(listener); | ||
}); | ||
// once | ||
, once: {value: function(evt, listener){ | ||
if (!this._events[evt]) this._events[evt] = []; | ||
this._events[evt].push({fn: listener, once: true}); | ||
}} | ||
rm.forEach(function(listener){ | ||
this.off(evt, listener); | ||
}); | ||
} | ||
}} | ||
// emit | ||
, emit: {value: function(evt){ | ||
var rm = []; | ||
// off | ||
, off: {value: function(evt, listener){ | ||
var index; | ||
if (this._events[evt]) { | ||
this._events[evt].forEach(function(listener){ | ||
listener.fn.apply(null, Array.prototype.slice.call(arguments, 1)); | ||
if (listener.once) rm.push(listener); | ||
}); | ||
if (evt === undefined) this._events = {}; | ||
else if (evt) { | ||
if (listener === undefined) delete this._events[evt]; | ||
else if(this._events[evt]) { | ||
index = this._events[evt].indexOf(listener); | ||
if (index >= 0) this._events[evt].splice(index, 1); | ||
} | ||
} | ||
}} | ||
rm.forEach(function(listener){ | ||
this.off(evt, listener); | ||
}); | ||
} | ||
}} | ||
, toJSON: {value: function() { | ||
return Array.prototype.slice.call(this); | ||
}} | ||
// off | ||
, off: {value: function(evt, listener){ | ||
var index; | ||
}); | ||
if (evt === undefined) this._events = {}; | ||
else if (evt) { | ||
if (listener === undefined) delete this._events[evt]; | ||
else if(this._events[evt]) { | ||
index = this._events[evt].indexOf(listener); | ||
if (index >= 0) this._events[evt].splice(index, 1); | ||
} | ||
} | ||
}} | ||
, dir: {value: function(returnResult) { | ||
var result = []; | ||
this.forEach(function(item){ | ||
result.push(item.dir(true)); | ||
}); | ||
if (returnResult) return result; | ||
else log(result); | ||
}} | ||
module.exports = function(options){ | ||
return Object.create(RelatingPrototype, { | ||
_orm: { | ||
value: options.orm | ||
} | ||
, _definition: { | ||
value: options.definition | ||
} | ||
, _relatesTo: { | ||
value: options.related | ||
} | ||
, _column: { | ||
value: options.column | ||
} | ||
, _database: { | ||
value: options.database | ||
} | ||
, _workerCount: { | ||
value: 0 | ||
} | ||
// the records which were on the collection when it was initialized | ||
, _originalRecords: { | ||
value: [] | ||
} | ||
// collect errors when selecting records to add to this set | ||
, _error: { | ||
value: null | ||
} | ||
, isMapping: { | ||
value: !!options.isMapping | ||
} | ||
}); | ||
}; | ||
, toJSON: {value: function() { | ||
return Array.prototype.slice.call(this); | ||
}} | ||
}); | ||
}(); |
!function(){ | ||
var Class = require('ee-class') | ||
, EventEmitter = require('ee-event-emitter') | ||
, log = require('ee-log') | ||
, ORM; | ||
var Class = require('ee-class') | ||
, EventEmitter = require('ee-event-emitter') | ||
, log = require('ee-log') | ||
, ORM; | ||
@@ -11,145 +11,158 @@ | ||
module.exports = new Class({ | ||
module.exports = new Class({ | ||
// id counter for the root resource | ||
_id: 0 | ||
// child resources | ||
children: [] | ||
// flags if this resource was already joined into the root resource | ||
, joined: false | ||
// id counter for the root resource | ||
, _id: 0 | ||
// select was executeed? | ||
, selectExecuted: false | ||
// flags if this resource was already joined into the root resource | ||
, joined: false | ||
// select was executeed? | ||
, selectExecuted: false | ||
, filtered: {get: function() { | ||
return !!Object.keys(this.query.filter); | ||
}} | ||
// which relating sets must be laoded | ||
, relatingSets: {} | ||
, init: function(options) { | ||
this.query = options.query; | ||
this.filters = options.filters; | ||
this.joins = options.joins || []; | ||
this.type = options.type; | ||
this.name = options.name; | ||
this.defaultSelect = options.defaultSelect || []; | ||
this.selected = options.selected; | ||
//this.filtered = options.filtered; | ||
this.parentResource = options.parentResource; | ||
this.Model = options.Model; | ||
this.referencedParentColumn = options.referencedParentColumn; | ||
this.referencedParentTable = options.referencedParentTable; | ||
this.primaryKeys = options.primaryKeys; | ||
this.rootResource = options.rootResource; | ||
this.rootFiltered = options.rootFiltered; // flags if this filter was added to the root query | ||
this.loaderId = options.loaderId; | ||
Class.define(this, 'children', Class([]).Enumerable()) | ||
Class.define(this, 'relatingSets', Class({}).Enumerable()) | ||
, get filtered() { | ||
return !!Object.keys(this.query.filter); | ||
} | ||
this.hasRootFilter = this.filters && !!Object.keys(this.filters).length; | ||
, init: function(options) { | ||
this.query = options.query; | ||
this.filters = options.filters; | ||
this.joins = options.joins || []; | ||
this.type = options.type; | ||
this.name = options.name; | ||
this.defaultSelect = options.defaultSelect || []; | ||
this.selected = options.selected; | ||
//this.filtered = options.filtered; | ||
this.parentResource = options.parentResource; | ||
this.Model = options.Model; | ||
this.referencedParentColumn = options.referencedParentColumn; | ||
this.referencedParentTable = options.referencedParentTable; | ||
this.primaryKeys = options.primaryKeys; | ||
this.rootResource = options.rootResource; | ||
this.rootFiltered = options.rootFiltered; // flags if this filter was added to the root query | ||
this.loaderId = options.loaderId; | ||
if (this.primaryKeys) this.defaultSelect = this.defaultSelect.concat(this.primaryKeys); | ||
this.hasRootFilter = this.filters && !!Object.keys(this.filters).length; | ||
if (!ORM) ORM = require('./ORM'); | ||
if (this.primaryKeys) this.defaultSelect = this.defaultSelect.concat(this.primaryKeys); | ||
// get an unique id | ||
if (this.parentResource) this.id = this.getUniqueId(); | ||
} | ||
if (!ORM) ORM = require('./ORM'); | ||
// get an unique id | ||
if (this.parentResource) this.id = this.getUniqueId(); | ||
} | ||
// get an unique id for this query ( get it fromt the root resource ) | ||
, getUniqueId: function() { | ||
return this.rootResource.getId(); | ||
} | ||
// get an unique id for this query ( get it fromt the root resource ) | ||
, getUniqueId: function() { | ||
return this.rootResource.getId(); | ||
} | ||
// only called on the root resource | ||
, getId: function() { | ||
return ++this._id; | ||
} | ||
// only called on the root resource | ||
, getId: function() { | ||
return ++this._id; | ||
} | ||
// check which relating sets to load | ||
, loadRelatingSet: function(name) { | ||
this.relatingSets[name] = true; | ||
} | ||
// check which relating sets to load | ||
, loadRelatingSet: function(name) { | ||
this.relatingSets[name] = true; | ||
} | ||
, select: function() { | ||
if (!this.selectExecuted) { | ||
this.selectExecuted = true; | ||
this.selected = true; | ||
// add parent reference to selects | ||
if (this.referencedParentColumn) { | ||
this.defaultSelect.push(ORM.alias('____id____', this.referencedParentTable, this.referencedParentColumn)); | ||
, select: function() { | ||
if (!this.selectExecuted) { | ||
this.selectExecuted = true; | ||
this.selected = true; | ||
this.parentResource.selectReferencedColumn(this.referencedParentColumn); | ||
} | ||
// add parent reference to selects | ||
if (this.referencedParentColumn) { | ||
this.defaultSelect.push(ORM.alias('____id____', this.referencedParentTable, this.referencedParentColumn)); | ||
// add additional selected fields to query | ||
this.query.addSeleted(this.defaultSelect); | ||
this.parentResource.selectReferencedColumn(this.referencedParentColumn); | ||
} | ||
// create join statements | ||
this.query.formatJoins(); | ||
} | ||
} | ||
// add additional selected fields to query | ||
this.query.addSeleted(this.defaultSelect); | ||
// create join statements | ||
this.query.formatJoins(); | ||
} | ||
} | ||
// add my joins to the root (at the end) | ||
, filter: function() { | ||
var filter; | ||
if (!this.rootFiltered) { // filter not yet added to root query? | ||
this.rootFiltered = true; | ||
// add my joins to the root (at the end) | ||
, filter: function() { | ||
var filter; | ||
// create join with alias, add it to the root resource( end ) | ||
this.joins.reverse().forEach(function(joinStatement, index){ | ||
this.rootResource.query.join.push(joinStatement.reverseFormat(this.id, (index > 0 ? this.id: this.parentResource.id))); | ||
}.bind(this)); | ||
if (!this.rootFiltered) { // filter not yet added to root query? | ||
this.rootFiltered = true; | ||
// create join with alias, add it to the root resource( end ) | ||
this.joins.reverse().forEach(function(joinStatement, index){ | ||
this.rootResource.query.join.push(joinStatement.reverseFormat(this.id, (index > 0 ? this.id: this.parentResource.id))); | ||
}.bind(this)); | ||
// filters | ||
if (this.filters && Object.keys(this.filters).length){ | ||
filter = this.rootResource.query.filter[this.query.from+this.id] = {}; | ||
Object.keys(this.filters).forEach(function(key){ | ||
filter[key] = this.filters[key]; | ||
}.bind(this)); | ||
} | ||
} | ||
} | ||
// filters | ||
if (this.filters && Object.keys(this.filters).length){ | ||
filter = this.rootResource.query.filter[this.query.from+this.id] = {}; | ||
Object.keys(this.filters).forEach(function(key){ | ||
filter[key] = this.filters[key]; | ||
}.bind(this)); | ||
} | ||
} | ||
} | ||
, selectReferencedColumn: function(columnName) { | ||
this.query.select.push(columnName); | ||
} | ||
, selectReferencedColumn: function(columnName) { | ||
this.query.select.push(columnName); | ||
} | ||
, applyFilter: function(resource) { | ||
var q = resource.query; | ||
/*if (!q.filter) q.filter = {}; | ||
if (!q.filter[this.name]) q.filter[this.name] = {}; | ||
q.filter[this.name][resource.referencedParentColumn] = ORM.in(this.set.getColumnValues(resource.referencedParentColumn));*/ | ||
, applyFilter: function(resource) { | ||
var q = resource.query; | ||
/*this.primaryKeys.forEach(function(pk){ | ||
q.group.push({ | ||
table : this.name | ||
, column : pk | ||
}); | ||
}.bind(this));*/ | ||
/* | ||
q.group.push({ | ||
table : this.name | ||
, column : resource.referencedParentColumn | ||
});*/ | ||
} | ||
if (!q.filter) q.filter = {}; | ||
if (!q.filter[this.name]) q.filter[this.name] = {}; | ||
q.filter[this.name][resource.referencedParentColumn] = ORM.in(this.set.getColumnValues(resource.referencedParentColumn)); | ||
q.group.push({ | ||
table : this.name | ||
, column : resource.referencedParentColumn | ||
}); | ||
} | ||
, applyGroup: function(resource) { //log.highlight(this.name, resource.name); | ||
this.primaryKeys.forEach(function(pk){ //log.info(this.name, pk); | ||
resource.query.group.push({ | ||
table : this.name | ||
, column : pk | ||
}); | ||
}.bind(this)); | ||
} | ||
, hasChildren: function() { | ||
return !!this.children.length; | ||
} | ||
}); | ||
, hasChildren: function() { | ||
return !!this.children.length; | ||
} | ||
}); | ||
}(); |
128
lib/Set.js
!function(){ | ||
var Class = require('ee-class') | ||
, type = require('ee-types') | ||
, log = require('ee-log'); | ||
var Class = require('ee-class') | ||
, type = require('ee-types') | ||
, log = require('ee-log'); | ||
module.exports = new Class({ | ||
inherits: Array | ||
module.exports = new Class({ | ||
inherits: Array | ||
, init: function(options){ | ||
Object.defineProperty(this, '_primaryKeys', {value: options.primaryKeys}); | ||
Object.defineProperty(this, '_name', {value: options.name}); | ||
Object.defineProperty(this, '_maps', {value: {_primary: {}}}); | ||
} | ||
, init: function(options){ | ||
Object.defineProperty(this, '_primaryKeys', {value: options.primaryKeys}); | ||
Object.defineProperty(this, '_name', {value: options.name}); | ||
Object.defineProperty(this, '_maps', {value: {_primary: {}}}); | ||
} | ||
, push: function(item) { | ||
// we need unique items by primary key | ||
var key = ''; | ||
, push: function(item) { | ||
// we need unique items by primary key | ||
var key = ''; | ||
this._primaryKeys.forEach(function(keyName){ | ||
key += item[keyName]; | ||
}.bind(this)); | ||
this._primaryKeys.forEach(function(keyName){ | ||
key += item[keyName]; | ||
}.bind(this)); | ||
if (this._maps._primary[key]){ | ||
// this value was added bvefore | ||
this._maps._primary[key]._mappingIds = this._maps._primary[key]._mappingIds.concat(item._mappingIds); | ||
return this.length; | ||
} | ||
else { | ||
this._maps._primary[key] = item; | ||
return Array.prototype.push.call(this, item); | ||
} | ||
} | ||
if (this._maps._primary[key]){ | ||
// this value was added before | ||
this._maps._primary[key]._mappingIds = this._maps._primary[key]._mappingIds.concat(item._mappingIds); | ||
return this.length; | ||
} | ||
else { | ||
this._maps._primary[key] = item; | ||
return Array.prototype.push.call(this, item); | ||
} | ||
} | ||
, first: function(){ | ||
return this[0]; | ||
} | ||
, first: function(){ | ||
return this[0]; | ||
} | ||
, last: function(){ | ||
return this.length ? this[this.length-1] : undefined; | ||
} | ||
, last: function(){ | ||
return this.length ? this[this.length-1] : undefined; | ||
} | ||
, getColumnValues: function(column){ | ||
column = column || 'id'; | ||
, getColumnValues: function(column){ | ||
column = column || 'id'; | ||
return this.map(function(row){ | ||
return row[column]; | ||
}); | ||
} | ||
return this.map(function(row){ | ||
return row[column]; | ||
}); | ||
} | ||
, getIds: function(){ | ||
return this.map(function(item){ return item.id;}); | ||
} | ||
, getIds: function(){ | ||
return this.map(function(item){ return item.id;}); | ||
} | ||
, getByColumnValue: function(column, value){ | ||
if (!this._maps[column]) this.createMap(column); | ||
return this._maps[column] ? this._maps[column][value] : undefined; | ||
} | ||
, getByColumnValue: function(column, value){ | ||
if (!this._maps[column]) this.createMap(column); | ||
return this._maps[column] ? this._maps[column][value] : undefined; | ||
} | ||
, createMap: function(column){ | ||
if (!this._maps[column]){ | ||
this._maps[column] = {}; | ||
, createMap: function(column){ | ||
if (!this._maps[column]){ | ||
this._maps[column] = {}; | ||
this.forEach(function(item){ | ||
this._maps[column][item[column]] = item; | ||
}.bind(this)); | ||
} | ||
} | ||
this.forEach(function(item){ | ||
this._maps[column][item[column]] = item; | ||
}.bind(this)); | ||
} | ||
} | ||
, toJSON: function() { | ||
return Array.prototype.slice.call(this); | ||
} | ||
}); | ||
, dir: function(returnResult) { | ||
var result = []; | ||
this.forEach(function(item){ | ||
result.push(item.dir(true)); | ||
}); | ||
if (returnResult) return result; | ||
else log(result); | ||
} | ||
, toJSON: function() { | ||
return Array.prototype.slice.call(this); | ||
} | ||
}); | ||
}(); |
!function(){ | ||
var Class = require('ee-class') | ||
, log = require('ee-log') | ||
, type = require('ee-types'); | ||
var Class = require('ee-class') | ||
, log = require('ee-log') | ||
, type = require('ee-types'); | ||
@@ -10,59 +10,59 @@ | ||
module.exports = new Class({ | ||
module.exports = new Class({ | ||
alias: function(){ | ||
var len = arguments.length | ||
, tableName = len === 3 ? arguments[1] : null | ||
, columnName = arguments[len === 3 ? 2 : 1] | ||
, alias = arguments[0]; | ||
alias: function(){ | ||
var len = arguments.length | ||
, tableName = len === 3 ? arguments[1] : null | ||
, columnName = arguments[len === 3 ? 2 : 1] | ||
, alias = arguments[0]; | ||
return function(){ | ||
return { | ||
table : tableName | ||
, column : columnName | ||
, alias : alias | ||
} | ||
}; | ||
} | ||
return function(){ | ||
return { | ||
table : tableName | ||
, column : columnName | ||
, alias : alias | ||
} | ||
}; | ||
} | ||
, in: function(values) { | ||
return function(){ | ||
return { | ||
fn: 'in' | ||
, values: values | ||
}; | ||
}; | ||
} | ||
, in: function(values) { | ||
return function(){ | ||
return { | ||
fn: 'in' | ||
, values: values | ||
}; | ||
}; | ||
} | ||
, notIn: function(values) { | ||
return function(){ | ||
return { | ||
fn: 'notIn' | ||
, values: values | ||
}; | ||
}; | ||
} | ||
, notIn: function(values) { | ||
return function(){ | ||
return { | ||
fn: 'notIn' | ||
, values: values | ||
}; | ||
}; | ||
} | ||
, notNull: function() { | ||
return function(){ | ||
return { | ||
fn: 'notNull' | ||
}; | ||
}; | ||
} | ||
, notNull: function() { | ||
return function(){ | ||
return { | ||
fn: 'notNull' | ||
}; | ||
}; | ||
} | ||
, gt: function(value) { | ||
return function(){ | ||
return { | ||
operator: '>' | ||
, value: value | ||
} | ||
} | ||
} | ||
}); | ||
, gt: function(value) { | ||
return function(){ | ||
return { | ||
operator: '>' | ||
, value: value | ||
} | ||
} | ||
} | ||
}); | ||
@@ -72,3 +72,3 @@ | ||
}(); |
!function(){ | ||
var Class = require('ee-class') | ||
, log = require('ee-log') | ||
, type = require('ee-types'); | ||
var Class = require('ee-class') | ||
, log = require('ee-log') | ||
, type = require('ee-types'); | ||
@@ -11,43 +11,43 @@ | ||
module.exports = function(database) { | ||
return Object.create(database, { | ||
module.exports = function(database) { | ||
return Object.create(database, { | ||
commit: {enumerable: true, value: function(callback){ | ||
if (this._transaction) this._transaction.commit(callback); | ||
else callback(new Error('Cannot commit! The transaction has already eneded.')); | ||
this._endTransaction(); | ||
}} | ||
commit: {enumerable: true, value: function(callback){ | ||
if (this._transaction) this._transaction.commit(callback); | ||
else callback(new Error('Cannot commit! The transaction has already eneded.')); | ||
this._endTransaction(); | ||
}} | ||
, rollback: {enumerable: true, value: function(callback){ | ||
if (this._transaction) this._transaction.rollback(callback); | ||
else callback(new Error('Cannot rollback! The transaction has already eneded.')); | ||
this._endTransaction(); | ||
}} | ||
, rollback: {enumerable: true, value: function(callback){ | ||
if (this._transaction) this._transaction.rollback(callback); | ||
else callback(new Error('Cannot rollback! The transaction has already eneded.')); | ||
this._endTransaction(); | ||
}} | ||
, executeQuery: {enumerable: true, value: function(mode, query, callback){ | ||
if (this._transaction) this._transaction.query(mode, query, callback); | ||
else { | ||
this._database.getConnection(false, function(err, connection){ | ||
if (err) callback(err); | ||
else { | ||
this._transaction = connection; | ||
this._transaction.startTransaction(); | ||
this._transaction.on('end', this._endTransaction.bind(this)); | ||
, executeQuery: {enumerable: true, value: function(mode, query, callback){ | ||
if (this._transaction) this._transaction.query(mode, query, callback); | ||
else { | ||
this._database.getConnection(false, function(err, connection){ | ||
if (err) callback(err); | ||
else { | ||
this._transaction = connection; | ||
this._transaction.startTransaction(); | ||
this._transaction.on('end', this._endTransaction.bind(this)); | ||
this.executeQuery(mode, query, callback); | ||
} | ||
}.bind(this)); | ||
} | ||
}} | ||
this.executeQuery(mode, query, callback); | ||
} | ||
}.bind(this)); | ||
} | ||
}} | ||
, _endTransaction: {value: function(){ | ||
this._transaction = null; | ||
}} | ||
, _endTransaction: {value: function(){ | ||
this._transaction = null; | ||
}} | ||
, _transaction: {value: null, writable:true, configurable: true} | ||
}); | ||
} | ||
, _transaction: {value: null, writable:true, configurable: true} | ||
}); | ||
} | ||
}(); |
{ | ||
"name" : "ee-orm" | ||
, "description" : "An easy to use ORM for node.js. Supports advanced eager loading, complex queries, joins, transactions, complex database clusters & connection pooling." | ||
, "version" : "0.2.11" | ||
, "version" : "0.3.0" | ||
, "homepage" : "https://github.com/eventEmitter/ee-orm" | ||
@@ -19,3 +19,3 @@ , "author" : "Michael van der Weg <michael@eventemitter.com> (http://eventemitter.com/)" | ||
, "dependencies": { | ||
"ee-class" : "0.4.x" | ||
"ee-class" : "1.0.x" | ||
, "ee-event-emitter" : "0.1.x" | ||
@@ -25,3 +25,4 @@ , "ee-types" : "0.1.x" | ||
, "ee-async" : "0.2.x" | ||
, "ee-arguments" : "0.1.x" | ||
, "ee-argv" : "0.1.x" | ||
, "ee-arguments" : "1.0.x" | ||
, "clone" : "0.1.x" | ||
@@ -32,12 +33,11 @@ , "ee-db-cluster" : "0.1.x" | ||
"mocha" : "1.18.x" | ||
, "ee-travis" : "0.1.x" | ||
, "ee-mysql-connection" : "0.1.x" | ||
, "ee-postgres-connection" : "0.1.x" | ||
, "ee-project" : "0.2.x" | ||
, "ee-postgres-connection" : "0.2.x" | ||
, "ee-project" : "0.2.x" | ||
} | ||
, "optionalDependencies": {} | ||
, "keywords" : ["orm", "mysql", "postgres", "object relationale mapper", "eager loading", "connection pooling", "cluster"] | ||
, "keywords" : ["orm", "mysql", "postgres", "object relational mapper", "eager loading", "connection pooling", "cluster"] | ||
, "scripts": { | ||
"test" : "./node_modules/mocha/bin/mocha --reporter spec" | ||
"test" : "./node_modules/mocha/bin/mocha --reporter spec --bail" | ||
} | ||
} |
@@ -63,3 +63,3 @@ # ee-orm | ||
// get first event | ||
log(events.first()); // {id:1, title: "prodigy", startdate: "2012-07-08 22:00:00", venues: [{id: 45, name: "via felsenau"}]}; | ||
events.first().dir() // {id:1, title: "prodigy", startdate: "2012-07-08 22:00:00", venues: [{id: 45, name: "via felsenau"}]}; | ||
@@ -78,7 +78,7 @@ // add new venue to the second event. the events list is an array with advanced functions | ||
orm.eventdata.events(['id']).fetchVenues({id: 56}).find(function(err, events){ | ||
log(events); // [{id:1}, {id:2}] | ||
events.dir(); // [{id:1}, {id:2}] | ||
}); | ||
orm.eventdata.venues({id: 56}).events(['id']).find(function(err, venues){ | ||
log(venues); // [{id:56, events: [{id:1}, {id:2}]}] | ||
venues.dir(); // [{id:56, events: [{id:1}, {id:2}]}] | ||
}); | ||
@@ -85,0 +85,0 @@ } |
54
test.js
@@ -15,18 +15,50 @@ | ||
log('orm loaded'); | ||
var role5 | ||
, arr = [] | ||
, start | ||
, i = 10000; | ||
var db = orm.ee_orm_test | ||
, start; | ||
while(i--) arr.push(1); | ||
log(orm); | ||
var cb = function(err, data){ | ||
if (err) log(err); | ||
if (data) data.dir(); | ||
} | ||
/*return new db.eventLocale({ | ||
description : 'some text' | ||
, language : db.language({id:1}) | ||
, event : db.event({id:1}) | ||
}).save(log);*/ | ||
db.image.setMappingAccessorName('venue_image', 'venue'); | ||
db.image().describeMethods(); | ||
db.event({id:1}).getImage(['*']).getVenue(['*']).find(cb); | ||
/* | ||
orm.ee_orm_test.venue.setMappingAccessorName('venue_image', 'image');*/ | ||
//log(orm.eventbooster.resource().describeMethods()); | ||
/*new orm.eventbooster.resource({ | ||
/*var db = orm.ee_orm_test; | ||
db.venue.setReferenceAccessorName('id_image', 'logo'); | ||
db.venue.setMappingAccessorName('venue_image', 'images');*/ | ||
/* | ||
new db.venue({ | ||
name: 'Dachstock Reitschule' | ||
, municipality: db.municipality({ | ||
name: 'Bern' | ||
}) | ||
, id_image: 1 | ||
}).save(function(err, image){ | ||
log(err, image); | ||
}); | ||
*/ | ||
/* | ||
console.time("insert") | ||
new orm.eventbooster.resource({ | ||
key: 'email.test.1'+Math.random() | ||
, id_tenant: 0 | ||
, resourceLocale: new orm.eventbooster.resourceLocale({ | ||
id_language: 1 | ||
id_language: 5 | ||
, text: 'hi' | ||
@@ -39,6 +71,6 @@ }) | ||
}).save(function(err, resource){ log(err); | ||
});*/ | ||
orm.eventbooster.resource(['*']).getResourceLocale(['*']).find(log) | ||
console.timeEnd("insert") | ||
}); | ||
*/ | ||
//orm.eventbooster.resourceLocale(['*'], {id_resource:63}).getResource(['*']).find(log) | ||
return; | ||
@@ -45,0 +77,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
156531
4
26
2899
9
1
1
+ Addedee-argv@0.1.x
+ Addedee-arguments@1.0.4(transitive)
Updatedee-arguments@1.0.x
Updatedee-class@1.0.x