neo4jmapper src/conditionalparameters.js

    Description

    Builds a string from mongodb-like-query object

    Source

    if (typeof window === 'object') {
      var _ = window._;
      var helpers = window.Neo4jMapper.helpers;
    } else {
      var _ = require('underscore');
      var helpers = require('./helpers');
    }
    
    var ConditionalParameters = function ConditionalParameters(conditions, options) {
    
      ConditionalParameters.parameterRuleset = {
        $IN: function(value) {
          var s = '';
          if ((typeof value === 'object') && (value.length > 0)) {
            for (var i=0; i < value.length; i++) {
              value[i] = (typeof value[i] === 'string') ? "'"+helpers.escapeString(value[i])+"'" : helpers.valueToStringForCypherQuery(value[i]);
            }
            s = value.join(', ');
          }
          return 'IN [ ' + s + ' ]';
        },
        $in: function(value) { return this.$IN(value); }
    
      };
    
      ConditionalParameters.prototype.addValue = function(value) {
        if (!this.parameters)
          this.parameters = {};
        var property = '_value'+(this.parametersStartCountAt + this.parametersCount())+'_';
        this.parameters[property] = value;
        return '{'+property+'}';
      }
    
      ConditionalParameters.prototype.values = function() {
        var values = [];
        for (var prop in this.parameters) {
          values.push(this.parameters[prop]);
        }
        return values;
      }
    
      ConditionalParameters.prototype.parametersCount = function() {
        if ((typeof this.parameters !== 'object')||(this.parameters === null))
          return 0;
        else
          return Object.keys(this.parameters).length;
      }
    
      ConditionalParameters.prototype.hasParameters = function() {
        return (this.parametersCount() > 0);
      }
    
      ConditionalParameters.prototype.cypherKeyValueToString = function(key, originalValue, identifier) {
        // call cypherKeyValueToString with this object context
        return helpers.cypherKeyValueToString(key, originalValue, identifier, this);
      }
    
      ConditionalParameters.prototype.convert = function(condition, operator) {
        if (typeof condition === 'undefined')
          condition = this.conditions;
        var options = _.extend({}, this.defaultOptions, this.options);
        if (options.firstLevel)
          options.firstLevel = false;
        if (options.parametersStartCountAt)
          this.parametersStartCountAt = options.parametersStartCountAt;
        // TODO: if $not : [ {name: 'a'}] ~> NOT (name = a)
        if (typeof condition === 'string')
          condition = [ condition ];
        if (typeof operator === 'undefined')
          operator = this.operator; // AND
        if (typeof condition === 'object')
          for (var key in condition) {
            var value = condition[key];
            var property = null;
            if (_.isObject(condition[key])) {
              var properties = [];
              var firstKey = (_.keys(value)) ? _.keys(value)[0] : null;
              if ((firstKey)&&(ConditionalParameters.is_operator.test(firstKey))) {
                properties.push(this.convert(condition[key][firstKey], firstKey.replace(/\$/g,' ').trim().toUpperCase(), options));
              } else {
                for (var k in condition[key]) {
                  // k = key/property, remove identifier e.g. n.name
                  var property = k.replace(/^[nmrp]\./,'');
                  value = condition[key][k];
    
                  // only check for attributes if not s.th. like `n.name? = …`
                  var identifierWithProperty = (/\?$/.test(property)) ? '' : property;
                  if (identifierWithProperty) {
                    if (options.identifier)
                      // we have s.th. like options.identifier = n; property = '`'+identifierWithProperty+'`'
                      identifierWithProperty = options.identifier + '.' + identifierWithProperty;
                    else
                      // we have no explicit identifier, so we use the complete key/property and expecting it contains identifier
                      identifierWithProperty = k;
                    identifierWithProperty = helpers.escapeProperty(identifierWithProperty);
                  }
                  var hasAttribute = (identifierWithProperty) ? 'HAS ('+identifierWithProperty+') AND ' : '';
                  if (value === k) {
                    properties.push(hasAttribute+value);
                  // do we have s.th. like { name: { $IN: [ … ] } }
                  } else if ((typeof value === 'object')&&(value !== null)&&(Object.keys(value).length === 1)&&(typeof ConditionalParameters.parameterRuleset[Object.keys(value)[0]] === 'function')) {
                    properties.push(hasAttribute+' '+(identifierWithProperty || k)+' '+ConditionalParameters.parameterRuleset[Object.keys(value)[0]](value[Object.keys(value)[0]]));
                  } else {
                    properties.push(hasAttribute+this.cypherKeyValueToString(k, value,
                      // only add an identifier if we have NOT s.th. like
                      // n.name = ''  or r.since …
                      (/^[a-zA-Z\_\-]+\./).test(k) ? null : options.identifier
                    ));
                  }
                }
              }
              // merge sub conditions
              condition[key] = properties.join(' '+operator+' ');
            }
          }
    
        if ((condition.length === 1)&&(options.firstLevel === false)&&(/NOT/i.test(operator)))
          return operator + ' ( '+condition.join('')+' )';
        else
          return '( '+condition.join(' '+operator+' ')+' )';
      }
    
      ConditionalParameters.prototype.toString = function() {
        if (this.conditions)
          this._s = this.convert();
        return this._s;
      }
    
      // assign parameters and option(s)
      if (typeof conditions === 'object') {
        this.conditions = (conditions) ? conditions : {};
      } else if (typeof conditions === 'string') {
        this.conditions = null;
        this._s = '( ' + conditions + ' )';
        return;
      } else {
        throw Error('First argument must be an object with conditional parameters or a plain string');
      }
      if (typeof options === 'object') {
        this.options = options;
        // assign some options if they exists to current object
        if (typeof this.options.valuesToParameters !== 'undefined')
          this.valuesToParameters = this.options.valuesToParameters;
        if (typeof this.options.identifier !== 'undefined')
          this.identifier = this.options.identifier;
        if (typeof this.options.operator !== 'undefined')
          this.operator = this.options.operator;
      }
    }
    
    ConditionalParameters.is_operator = /^\$(AND|OR|XOR|NOT|AND\$NOT|OR\$NOT)$/i;
    
    ConditionalParameters.prototype.operator               = 'AND';
    ConditionalParameters.prototype.identifier             = 'n';
    ConditionalParameters.prototype.conditions             = null;
    
    // options are used to prevent overriding object attributes on recursive calls
    ConditionalParameters.prototype.options                = null;
    ConditionalParameters.prototype.defaultOptions         = { firstLevel: true, identifier: null };
    
    ConditionalParameters.prototype.parameters             = null;
    ConditionalParameters.prototype.valuesToParameters     = true;
    ConditionalParameters.prototype._s                     = '';
    ConditionalParameters.prototype.parametersStartCountAt = 0;
    
    if (typeof window === 'object') {
      window.Neo4jMapper.ConditionalParameters = ConditionalParameters;
    } else {
      module.exports = exports = ConditionalParameters;
    }