UNPKG

leaf-orient

Version:

OrientDB ODM with support for schemas inspired by mongoose

584 lines (479 loc) 12.1 kB
import OrientjsQuery from 'orientjs/lib/db/query'; import debug from 'debug'; import _ from 'lodash'; import Document from './document'; import GraphSchema from './schemas/graph'; import EdgeSchema from './schemas/edge'; import { RecordID } from 'orientjs'; import LogicOperators from './constants/logicoperators'; import ComparisonOperators from './constants/comparisonoperators'; import extend from "node.extend"; import Promise from 'bluebird'; const log = debug('orientose:query'); const Operation = { DELETE : 'DELETE', UPDATE : 'UPDATE', SELECT : 'SELECT', INSERT : 'INSERT' }; const Operator = { OR: 'or', AND: 'and', WHERE : 'where' }; const rRIDLike = /^[\d]+:[\d]+$/; export default class Query { constructor(model, options) { options = options || {}; if(!model) { throw new Error('Model is not defined'); } this._paramIndex = 1; this._model = model; this._target = model.name; this._first = false; this._scalar = false; this._raw = false; this._limit = null; this._skip = null; this._sort = null; this._from = null; this._to = null; this._let = {}; this._group = undefined; this._selects = []; this._operation = null; this._params = {}; this._operators = []; this._set = null; var self = this; for ( var name in this._model._documentClass) { this[name] = (function(name){ return function(){ return self._model._documentClass[name].apply(self, arguments); }; })(name); } } get model() { return this._model; } get schema() { return this.model.schema; } count(key) { return this.select('count('+key+')'); } select(key) { this._selects.push(key); return this; } paramify (key) { return key.replace(/([^A-Za-z0-9])/g, ''); } nextParamName(propertyName) { return this.paramify(propertyName)+'_op_'+this._paramIndex++; } addParam(paramName, value) { this._params[paramName] = value; } addParams(params) { params = params || {}; extend(this._params, params); } createComparisonQuery(propertyName, operator, value) { var param; var type = this.schema.getSchemaType(propertyName); if ( value && true === value.__orientose_raw__ ) { param = value; } else if ( Array.isArray(value) ) { param = []; for ( var i = 0; i < value.length; i++ ) { var paramName = this.nextParamName(propertyName); param[i] = ":"+paramName; if ((type && "LINK" === type.getDbType() || "@rid" === propertyName) && !(param instanceof RecordID) ) { value[i] = this.convertToRID(value[i]); } this.addParam(paramName, value[i]); } } else { var paramName = this.nextParamName(propertyName); param = ":"+paramName; if ((type && "LINK" === type.getDbType() || "@rid" === propertyName) && !(param instanceof RecordID) ) { value = this.convertToRID(value); } this.addParam(paramName, value); } if(value === null) { if(operator === '=') { return propertyName + ' IS NULL'; } else if(operator === '!=' || operator === '<>' || operator === 'NOT') { return propertyName + ' IS NOT NULL'; } } if ( Array.isArray(param) ) { var op = operator.toLowerCase(); if ( "between" === op ) { return propertyName + ' BETWEEN ' + param.join(' AND '); } else if ( "in" === op ) { return propertyName + ' IN [' + param.join(', ') + "] "; } } return propertyName + ' ' + operator + ' ' + param; } convertToRID(value) { if ( typeof value === "string" && rRIDLike.test(value) ) { value = "#"+value; } var oldvalue = new RecordID(value); if ( oldvalue ) { value = oldvalue; } return value; } queryLanguage(conditions) { var items = []; Object.keys(conditions).forEach(propertyName => { if(propertyName === '_id') { propertyName = '@rid'; } var value = conditions[propertyName]; if(typeof value === 'undefined') { return; } if(LogicOperators[propertyName]) { var subQueries = []; value.forEach(conditions => { var query = this.queryLanguage(conditions); if(!query) { return; } subQueries.push(query); }); if(!subQueries.length) { return; } else if(subQueries.length === 1) { return items.push(subQueries[0]); } var query = '(' + subQueries.join(') ' + LogicOperators[propertyName] + ' (') + ')'; return items.push(query); } // if(value instanceof RecordID) { // value = value.toString(); // } if(!_.isObject(value) || value instanceof RecordID) { var query = this.createComparisonQuery(propertyName, '=', value); return items.push(query); } Object.keys(value).forEach(operation => { var operationValue = value[operation]; // if(value instanceof RecordID) { // value = value.toString(); // } var query = null; if(ComparisonOperators[operation]) { query = this.createComparisonQuery(propertyName, ComparisonOperators[operation], operationValue); } if(!query) { return; } items.push(query); }); }); if(!items.length) { return null; } return items.join(' AND '); } operator(operator, conditions, callback) { var query = this.queryLanguage(conditions); if(!query) { return this; } this._operators.push({ type: operator, query: query }); return this; } condExec(conditions, callback) { if(typeof conditions === 'function') { callback = conditions; conditions = void 0; } if(typeof conditions === 'string') { this._target = conditions; conditions = void 0; } if(_.isObject(conditions)) { if(conditions instanceof Document) { this._target = conditions; conditions = void 0; } else if(conditions instanceof RecordID) { this._target = conditions; conditions = void 0; } else { this.where(conditions); } } return this; // making exec implicit } or(conditions) { var self = this; conditions.forEach(function(condition) { self = self.operator(Operator.OR, condition); }); return self; } and(conditions) { var self = this; conditions.forEach(function(condition) { self = self.operator(Operator.AND, condition); }); return self; } let(name, statement) { this._let[name] = statement; return this; } where(conditions, callback) { conditions = conditions || {}; this.operator(Operator.WHERE, conditions); return this.condExec(callback); } operation(operation) { if(this._operation && this._operation !== operation) { throw new Error('Operation is already set'); } this._operation = operation; return this; } set(doc) { this._set = doc; return this; } first(useFirst) { this._first = !!useFirst; return this; } raw() { this._raw = true; return this; } scalar(useScalar) { this._scalar = !!useScalar; return this; } limit(limit) { this._limit = limit; return this; } skip(skip) { this._skip = skip; return this; } from(value) { this._from = value; return this; } to(value) { this._to = value; return this; } group(key) { this._group = this._group || []; var args = Array.prototype.slice.call(arguments); this._group.push.apply(this._group, args); return this; } sort(sort) { if(typeof sort === 'string') { var order = {}; var parts = sort.split(' '); parts.forEach(function(part) { var direction = 1; if(part[0] === '-') { part = part.substr(1); direction = -1; } order[part] = direction; }); sort = order; } this._sort = sort; return this; } /** update(doc, [callback]) */ create(doc, callback) { if(typeof doc === 'function') { callback = doc; doc = {}; } return this .operation(Operation.INSERT) .set(doc) .first(true) .condExec(callback); } /** update(conditions, update, [options], [callback]) */ update(conditions, doc, options) { if(typeof options === 'function') { callback = options; options = {}; } options = options || {}; if(typeof conditions === 'undefined' || typeof doc === 'undefined') { throw new Error('One of parameters is missing'); } return this .operation(Operation.UPDATE) .limit(options.multi ? null : 1) .set(doc) .scalar(true) .condExec(conditions, true); } //find([conditions], [callback]) find(conditions) { return this .operation(Operation.SELECT) .condExec(conditions, true); } //findOne([criteria], [callback]) findOne(conditions) { return this .operation(Operation.SELECT) .limit(1) .first(true) .condExec(conditions, true); } //remove([conditions], [callback]) remove(conditions) { return this .operation(Operation.DELETE) .scalar(true) .condExec(conditions, true); } transaction(transaction){ this._transaction = transaction; return this; } then(fn){ return this.exec(fn); } map(fn) { return this.exec().map(fn); } exec(fn) { var model = this.model; var schema = model.schema; var operation = this._operation; if(!operation) { this.operation(Operation.SELECT); operation = this._operation; // lets default this as select unless otherwise // return Promise.reject(new Error('Operation is not defined')); } var query; if ( this._transaction ) { query = this._transaction; } else { query = new OrientjsQuery(model.connection.db); } var q = query; var target = this._target && this._target['@rid'] ? this._target['@rid'] : this._target; var isGraph = schema instanceof GraphSchema; var selects; if ( this._selects.length > 0 ) { this._selects.push("@version"); selects = this._selects.join(","); } else { selects = "*, @version" } if(isGraph) { var graphType = schema instanceof EdgeSchema ? 'EDGE' : 'VERTEX'; if(operation === Operation.INSERT) { query = query.create(graphType, target); } else if(operation === Operation.DELETE) { query = query.delete(graphType, target); } else if(operation === Operation.SELECT) { query = query.select(selects).from(target); } else { query = query.update(target); } } else { if(operation === Operation.INSERT) { query = query.insert().into(target); } else if(operation === Operation.DELETE) { query = query.delete().from(target); } else if(operation === Operation.SELECT) { query = query.select(selects).from(target); } else { query = query.update(target); } } if(this._from) { query.from(this._from && this._from['@rid'] ? this._from['@rid'] : this._from); } if(this._to) { query.to(this._to && this._to['@rid'] ? this._to['@rid'] : this._to); } if(this._set && Object.keys(this._set).length) { query.set(this._set); } this._operators.forEach(function(operator) { query = query[operator.type](operator.query); }); for ( var name in this._let ) { query = query.let(name, this._let[name]); } query.addParams(this._params); if(!this._scalar && !this._raw && !this._group && (operation === Operation.SELECT || operation === Operation.INSERT)) { query = query.transform(function(record) { return model._createDocument(record); }); } if(this._limit) { query = query.limit(this._limit); } if(this._skip) { query = query.skip(this._skip); } if(this._sort) { var order = {}; Object.keys(this._sort).forEach(key => { var value = this._sort[key]; order[key] = value === 'asc' || value === 'ascending' || value === 1 ? 'ASC' : 'DESC'; }); query = query.order(order); } if ( this._group ) { query = query.group.apply(query, this._group); } log(q.buildStatement(), q.buildOptions()); var promise = query.exec().then(results => { if(!results) { return Promise.resolve(results); } if(this._first) { results = results[0]; } if(this._scalar && results.length) { results = parseInt(results[0]); } return Promise.resolve(results); }); if ( fn ) { return promise.then(fn); } return promise; } }