Manual Reference Source

app/modules/entities/crud/query.js

// @flow
const _ = require('lodash');
const Operator = require('./enums/operator');
const ZeroTermsQuery = require('./enums/zero_terms_query');
const Fuzziness = require('./enums/fuzziness');

class Query {
    _object: Object;
    _query: ?Query;
    _minimum_should_match: ?string;
    _boost: ?number;

    constructor(object: Object = {}) {
        this._object = object;
        this._query = 'query' in object ? object.query : null;
        this._boost = null;
        this._minimum_should_match = 'minimum_should_match' in object ? object.minimum_should_match : null;
    }

    get inner_query(): ?Query {
        return this._query;
    }

    boost(n: number) {
        this._boost = n;
        return this;
    }

    minimum_should_match(m: string) {
        this._minimum_should_match = m;
        return this;
    }

    query(q: Query) {
        this._query = q;
        return this;
    }

    generate(): Object {
        return this._query != null ? this._query.generate() : {};
    }
}

class RawQuery extends Query {
    generate(): Object {
        return this._object;
    }
}


class Bool extends Query {
    _must: Array<Query>;
    _must_not: Array<Query>;
    _should: Array<Query>;
    _filter: Array<Query>;

    constructor(object: Object = {}) {
        super(object);

        this._must = 'must' in object ? object.must : [];
        this._must_not = 'must_not' in object ? object.must_not : [];
        this._should = 'should' in object && object.should instanceof Array ? object.should : [];
        this._filter = 'filter' in object ? object.filter : [];
    }

    must(query: Query) {
        if (!(query instanceof Query)) {
            throw Error('query should be a genuine query');
        }
        this._must.push(query);
        return this;
    }

    must_not(query: Query) {
        if (!(query instanceof Query)) {
            throw Error('query should be a genuine query');
        }
        this._must_not.push(query);
        return this;
    }

    should(query: Query) {
        if (!(query instanceof Query)) {
            throw Error('query should be a genuine query');
        }
        this._should.push(query);
        return this;
    }

    filter(query: Query) {
        if (!(query instanceof Query)) {
            throw Error('query should be a genuine query');
        }
        this._filter.push(query);
        return this;
    }

    generate() {
        const pairs = [
            ['must', this._must],
            ['must_not', this._must_not],
            ['filter', this._filter],
            ['should', this._should],
        ];


        const obj = pairs.reduce((acc, p) => {
            const [field, o] = p;
            if (o.length > 0) {
                acc.bool[field] = o.map(q => q.generate());
            }
            return acc;
        }, { bool: {} });

        if (this._minimum_should_match) {
            obj.bool.minimum_should_match = this._minimum_should_match;
        }

        if (this._boost) {
            obj.bool.boost = this._boost;
        }

        if (Object.keys(obj.bool).length > 0) {
            return obj;
        }
        return null;
    }
}

class ConstantScore extends Query {}
class DisMax extends Query {}
class Filtered extends Query {}
class Indices extends Query {}

class Ids extends Query {
    constructor(object: Object = {}) {
        super(object);
    }

    values(ids: Array<string>): Ids {
        this._object.values = ids;
        return this;
    }

    type(type: string | Array<string>): Ids {
        this._object.type = type;
        return this;
    }

    generate() {
        const info = {
            ids: {
                values: this._object.values || [],
            },
        };

        if (this._object.type) {
            info.ids.type = this._object.type;
        }

        return info;
    }
}

class Nested extends Query {
    _path: string
    _nested_query: ?Query;

    constructor(object: Object = {}) {
        super(object);
        this._path = 'path' in object ? object.path : '';
        this._nested_query = 'query' in object && object.query != null ? object.query : new Query({});
    }

    path(p: string) {
        this._path = p;
        return this;
    }

    query(query: Query) {
        this._nested_query = query;
        return this;
    }

    generate() {
        return {
            nested: {
                path: this._path,
                query: this._nested_query.generate(),
            },
        };
    }
}
class HasChild extends Query {}
class HasParent extends Query {}
class TopChildren extends Query {}

class FullTextQuery extends Query {
    _analyzer: ?string;
    _fuzziness: ?Fuzziness;
    _operator: ?Operator;
    _zero_terms_query: ?ZeroTermsQuery;

    constructor(object: Object = {}) {
        super(object);
        this._analyzer = null;
        this._fuzziness = null;
        this._operator = null;
        this._zero_terms_query = null;

        if ('analyzer' in object) {
            this.analyzer(object.analyzer);
        }

        if ('fuzziness' in object) {
            this.fuzziness(object.fuzziness);
        }

        if ('operator' in object) {
            this.operator(object.operator);
        }

        if ('zero_terms_query' in object) {
            this.zero_terms_query(object.zero_terms_query);
        }
    }

    zero_terms_query(zq: string) {
        this._zero_terms_query = ZeroTermsQuery.enumValueOf(zq.toUpperCase());
        return this;
    }

    operator(o: string) {
        this._operator = Operator.enumValueOf(o.toUpperCase());
        return this;
    }

    fuzziness(f: string) {
        this._fuzziness = Fuzziness.enumValueOf(f.toUpperCase());
        return this;
    }

    analyzer(a: string) {
        this._analyzer = a;
        return this;
    }
}

class Match extends FullTextQuery {
    match(object: Object = {}) {
        this._object = object;
        return this;
    }

    generate() {
        const keys = Object.keys(this._object);
        const obj = { match: {} };
        if (keys.length > 0) {
            const key = keys[0];
            const q = this._object[key];
            obj.match[key] = {
                query: q,
            };

            if (this._operator) {
                obj.match[key].operator = this._operator.toString();
            }

            if (this._fuzziness) {
                obj.match[key].fuzziness = this._fuzziness.toString();
            }

            if (this._minimum_should_match) {
                obj.match[key].minimum_should_match = this._minimum_should_match;
            }

            if (this._boost) {
                obj.match[key].boost = this._boost;
            }

            if (this._zero_terms_query) {
                obj.match[key].zero_terms_query = this._zero_terms_query.toString();
            }

            if (this._analyzer) {
                obj.match[key].analyzer = this._analyzer;
            }

            return obj;
        }

        return null;
    }
}

class MatchPhrase extends FullTextQuery {
    match(obj: Object): MatchPhrase {
        this._object = _.merge({}, this._object, obj);
        return this;
    }

    generate() {
        return {
            match_phrase: this._object,
        };
    }
}

class MatchAll extends FullTextQuery {
    generate() {
        return { match_all: {} };
    }
}

class QueryString extends FullTextQuery {
    _default_field: ?string;
    _qs: ?string;
    constructor(object: Object = {}) {
        super(object);
        this._default_field = null;
        this._qs = null;
    }

    default_field(key: string) {
        this._default_field = key;
        return this;
    }

    qs(query_string: string) {
        this._qs = query_string;
        return this;
    }

    generate() {
        if (this._qs == null) {
            return {};
        }

        const o = { query_string: { query: this._qs } };

        if (this._default_field != null) {
            o.query_string.default_field = this._default_field;
        }

        return o;
    }

}

class Term extends Query {
    term(object: Object = {}) {
        this._object = object;
        return this;
    }

    generate() {
        const keys = Object.keys(this._object);
        const obj = { term: {} };
        if (keys.length > 0) {
            const key = keys[0];
            const q = this._object[key];
            obj.term[key] = {
                value: q,
            };

            if (this._boost) {
                obj.term[key].boost = this._boost;
            }
            return obj;
        }

        return null;
    }
}

class Terms extends Query {
    terms(object: Object = {}) {
        this._object = object;
        return this;
    }

    generate() {
        return { terms: this._object };
    }
}

class Range extends Query {
    _object: Object;
    _key: ?string;
    _lt: ?string;
    _gt: ?string;
    _lte: ?string;
    _gte: ?string;
    _format: ?string;
    _timezone: ?string;

    constructor(object: Object = {}) {
        super(object);
        this._key = null;
        this._lt = null;
        this._gt = null;
        this._lte = null;
        this._gte = null;
        this._format = null;
        this._timezone = null;
    }

    field(key: string): Range {
        this._key = key;
        return this;
    }

    lt(val: string): Range {
        this._lt = val;
        return this;
    }

    lte(val: string): Range {
        this._lte = val;
        return this;
    }

    gt(val: string): Range {
        this._gt = val;
        return this;
    }

    gte(val: string): Range {
        this._gte = val;
        return this;
    }

    format(f: string): Range {
        this._format = f;
        return this;
    }

    timezone(t: string): Range {
        this._timezone = t;
        return this;
    }

    operators(ops: Object): Range {
        _.forEach(ops, (val, key) => {
            switch (key) {
            case 'lt':
            case '<':
                this.lt(val);
                break;
            case 'gt':
            case '>':
                this.gt(val);
                break;
            case 'gte':
            case '>=':
                this.gte(val);
                break;
            case 'lte':
            case '<=':
                this.lte(val);
                break;
            case 'format':
            case 'f':
                this.format(val);
                break;
            case 'timezone':
            case 'time_zone':
            case 'tz':
                this.timezone(val);
                break;
            default:
                break;
            }
        });
        return this;
    }

    generate() {
        if (!this._key) {
            return {};
        }

        const not_null = [{ lt: this._lt }, { lte: this._lte },
              { gte: this._gte }, { gt: this._gt }]
        .filter((e) => {
            const keys = Object.keys(e);
            return e[keys[0]] != null;
        });

        if (not_null.length === 0) {
            return {};
        }

        const q = {
            range: {
                [this._key]: {},
            },
        };

        const sq = not_null.reduce((obj, i) => _.merge(obj, i), {});

        if (this._format) {
            sq.format = this._format;
        }

        if (this._timezone) {
            sq.time_zone = this._timezone;
        }

        q.range[this._key] = sq;
        return q;
    }
}

module.exports = {
    RawQuery,
    Query,
    Ids,
    Bool,
    ConstantScore,
    DisMax,
    Filtered,
    Indices,
    Nested,
    HasChild,
    HasParent,
    TopChildren,
    Match,
    MatchAll,
    MatchPhrase,
    QueryString,
    Term,
    Terms,
    Range,
};