Source: lib/query/Node.js

'use strict';

const Query = require('./Query');
const message = require('../message');
const Metadata = require('../util/Metadata');
const Entity = require('../binding/Entity');
const deprecated = require('../util/deprecated');

/**
 * A Query Node saves the state of the query being built
 * @alias query.Node<T>
 * @extends query.Query<T>
 */
class Node extends Query {
  /**
   * @param {EntityManager} entityManager The owning entity manager of this query
   * @param {Class<T>} resultClass The query result class
   */
  constructor(entityManager, resultClass) {
    super(entityManager, resultClass);

    /**
     * The offset how many results should be skipped
     * @type number
     * @readonly
     */
    this.firstResult = 0;

    /**
     * The limit how many objects should be returned
     * @type number
     * @readonly
     */
    this.maxResults = -1;

    /**
     * The properties which should be used sort the result
     * @type Object<string,number>
     * @readonly
     */
    this.order = {};
  }

  /**
   * @inheritDoc
   */
  eventStream() {
    throw new Error('Streaming features not available! Please use Streaming SDK!');
  }

  /**
   * @inheritDoc
   */
  resultStream() {
    throw new Error('Streaming features not available! Please use Streaming SDK!');
  }

  /**
   * @inheritDoc
   */
  resultList(options, doneCallback, failCallback) {
    if (options instanceof Function) {
      return this.resultList({}, options, doneCallback);
    }

    const type = this.resultClass ? this.entityManager.metamodel.entity(this.resultClass) : null;

    if (!type) {
      throw new Error('Only typed queries can be executed.');
    }

    const query = this.serializeQuery();
    const sort = this.serializeSort();

    const uriSize = this.entityManager.connection.host.length + query.length + sort.length;
    let msg;

    if (uriSize > Query.MAX_URI_SIZE) {
      msg = new message.AdhocQueryPOST(type.name, this.firstResult, this.maxResults, sort)
        .entity(query, 'text');
    } else {
      msg = new message.AdhocQuery(type.name, query, this.firstResult, this.maxResults, sort);
    }

    return this.entityManager.send(msg)
      .then(response => this.createResultList(response.entity, options))
      .then(doneCallback, failCallback);
  }

  /**
   * @inheritDoc
   */
  singleResult(options, doneCallback, failCallback) {
    if (options instanceof Function) {
      return this.singleResult({}, options, doneCallback);
    }

    const type = this.resultClass ? this.entityManager.metamodel.entity(this.resultClass) : null;

    if (!type) {
      throw new Error('Only typed queries can be executed.');
    }

    const query = this.serializeQuery();
    const sort = this.serializeSort();

    const uriSize = this.entityManager.connection.host.length + query.length;
    let msg;
    if (uriSize > Query.MAX_URI_SIZE) {
      msg = new message.AdhocQueryPOST(type.name, query, this.firstResult, 1, sort)
        .entity(query, 'text');
    } else {
      msg = new message.AdhocQuery(type.name, query, this.firstResult, 1, sort);
    }

    return this.entityManager.send(msg)
      .then(response => this.createResultList(response.entity, options))
      .then(list => (list.length ? list[0] : null))
      .then(doneCallback, failCallback);
  }

  /**
   * @inheritDoc
   */
  count(doneCallback, failCallback) {
    const type = this.resultClass ? this.entityManager.metamodel.entity(this.resultClass) : null;

    if (!type) {
      throw new Error('Only typed queries can be executed.');
    }

    const query = this.serializeQuery();

    const uriSize = this.entityManager.connection.host.length + query.length;
    let msg;
    if (uriSize > Query.MAX_URI_SIZE) {
      msg = new message.AdhocCountQueryPOST(type.name)
        .entity(query, 'text');
    } else {
      msg = new message.AdhocCountQuery(type.name, query);
    }

    return this.entityManager.send(msg)
      .then(response => response.entity.count)
      .then(doneCallback, failCallback);
  }

  serializeQuery() {
    return JSON.stringify(this, function argSerializer(k, v) {
      // this referees here to the objet which owns the key k
      const typedValue = this[k];
      if (typedValue instanceof Date) {
        return { $date: v };
      } else if (typedValue instanceof Entity) {
        return typedValue.id;
      }
      return v;
    });
  }

  serializeSort() {
    return JSON.stringify(this.order);
  }

  createResultList(result, options) {
    if (result.length) {
      return Promise.all(result.map((el) => {
        if (el.id) {
          const entity = this.entityManager.getReference(this.resultClass, el.id);
          const metadata = Metadata.get(entity);
          metadata.setJson(el, { persisting: true });
          return this.entityManager.resolveDepth(entity, options);
        }

        return this.entityManager.load(Object.keys(el)[0]);
      }, this))
        .then(objects => objects.filter(val => !!val));
    }

    return Promise.resolve([]);
  }

  addOrder(fieldOrSort, order) {
    if (order) {
      this.order[fieldOrSort] = order;
    } else {
      this.order = fieldOrSort;
    }
    return this;
  }

  addOffset(offset) {
    this.firstResult = offset;
    return this;
  }

  addLimit(limit) {
    this.maxResults = limit;
    return this;
  }
}

deprecated(Node.prototype, '_sort', 'order');
deprecated(Node.prototype, '_serializeQuery', 'serializeQuery');
deprecated(Node.prototype, '_serializeSort', 'serializeSort');
deprecated(Node.prototype, '_createResultList', 'createResultList');
deprecated(Node.prototype, '_addOrder', 'addOrder');
deprecated(Node.prototype, '_addOffset', 'addOffset');
deprecated(Node.prototype, '_addLimit', 'addLimit');

module.exports = Node;