betajs/betajs-data

View on GitHub
src/data/queries/query_engine.js

Summary

Maintainability
D
1 day
Test Coverage
Scoped.define("module:Queries.Engine", [
    "module:Queries",
    "module:Queries.Constrained",
    "base:Strings",
    "base:Types",
    "base:Objs",
    "base:Promise",
    "base:Comparators",
    "base:Iterators.SkipIterator",
    "base:Iterators.LimitIterator",
    "base:Iterators.SortedIterator",
    "base:Iterators.FilteredIterator",
    "base:Iterators.SortedOrIterator",
    "base:Iterators.PartiallySortedIterator",
    "base:Iterators.ArrayIterator",
    "base:Iterators.LazyMultiArrayIterator"
], function(Queries, Constrained, Strings, Types, Objs, Promise, Comparators, SkipIterator, LimitIterator, SortedIterator, FilteredIterator, SortedOrIterator, PartiallySortedIterator, ArrayIterator, LazyMultiArrayIterator) {
    return {

        indexQueryConditionsSize: function(conds, index, ignoreCase) {
            var postfix = ignoreCase ? "_ic" : "";
            var info = index.info();
            var subSize = info.row_count;
            var rows_per_key = info.row_count / Math.max(info["key_count" + postfix], 1);
            if (conds.$eq)
                subSize = rows_per_key;
            else if (conds.$in)
                subSize = rows_per_key * conds.$in.length;
            else {
                var keys = 0;
                var g = null;
                if (conds.$gt || conds.$gte) {
                    g = conds.$gt || conds.$gte;
                    if (conds.$gt)
                        keys--;
                }
                var l = null;
                if (conds.$lt || conds.$lte) {
                    l = conds.$lt || conds.$lte;
                    if (conds.$lt)
                        keys--;
                }
                if (g !== null && l !== null)
                    keys += index["key_count_distance" + postfix](g, l);
                else if (g !== null)
                    keys += index["key_count_right" + postfix](g);
                else if (l !== null)
                    keys += index["key_count_left" + postfix](l);
                subSize = keys * rows_per_key;
            }
            return subSize;
        },

        indexQuerySize: function(queryDNF, key, index) {
            var acc = 0;
            var info = index.info();
            Objs.iter(queryDNF.$or, function(q) {
                if (!(key in q)) {
                    acc = null;
                    return false;
                }
                var conds = q[key];
                var findSize = info.row_count;
                if (index.options().exact)
                    findSize = Math.min(findSize, this.indexQueryConditionsSize(conds, index, false));
                if (index.options().ignoreCase)
                    findSize = Math.min(findSize, this.indexQueryConditionsSize(conds, index, true));
                acc += findSize;
            }, this);
            return acc;
        },

        queryPartially: function(constrainedQuery, constrainedQueryCapabilities) {
            var simplified = {
                query: constrainedQuery.query,
                options: {}
            };
            if (constrainedQuery.options.sort) {
                var first = Objs.ithKey(constrainedQuery.options.sort, 0);
                simplified.options.sort = {};
                simplified.options.sort[first] = constrainedQuery.options.sort[first];
            }
            return Constrained.validate(simplified, constrainedQueryCapabilities);
        },

        compileQuery: function(constrainedQuery, constrainedQueryCapabilities, constrainedQueryFunction, constrainedQueryContext) {
            constrainedQuery = Constrained.rectify(constrainedQuery);
            var sorting_supported = Constrained.sortValidate(constrainedQuery.options, constrainedQueryCapabilities);
            var query_supported = Queries.validate(constrainedQuery.query, constrainedQueryCapabilities.query || {});
            var skip_supported = Constrained.skipValidate(constrainedQuery.options, constrainedQueryCapabilities);
            var limit_supported = Constrained.limitValidate(constrainedQuery.options, constrainedQueryCapabilities);
            var post_actions = {
                skip: null,
                limit: null,
                filter: null,
                sort: null
            };
            if (!query_supported || !sorting_supported || !skip_supported) {
                post_actions.skip = constrainedQuery.options.skip;
                delete constrainedQuery.options.skip;
                if ("limit" in constrainedQuery.options && limit_supported && query_supported && sorting_supported)
                    constrainedQuery.options.limit += post_actions.skip;
            }
            if (!query_supported || !sorting_supported || !limit_supported) {
                post_actions.limit = constrainedQuery.options.limit;
                delete constrainedQuery.options.limit;
            }
            if (!sorting_supported) {
                post_actions.sort = constrainedQuery.options.sort;
                delete constrainedQuery.options.sort;
            }
            if (!query_supported) {
                post_actions.filter = constrainedQuery.query;
                constrainedQuery.query = {};
            }
            var query_result = constrainedQueryFunction.call(constrainedQueryContext, constrainedQuery);
            return query_result.mapSuccess(function(iter) {
                iter = this._queryResultRectify(iter, false);
                if (post_actions.filter) {
                    iter = (new FilteredIterator(iter, function(row) {
                        return Queries.evaluate(post_actions.filter, row);
                    })).auto_destroy(iter, true);
                }
                if (post_actions.sort)
                    iter = (new SortedIterator(iter, Comparators.byObject(post_actions.sort))).auto_destroy(iter, true);
                if (post_actions.skip)
                    iter = (new SkipIterator(iter, post_actions.skip)).auto_destroy(iter, true);
                if (post_actions.limit)
                    iter = (new LimitIterator(iter, post_actions.limit)).auto_destroy(iter, true);
                return iter;
            }, this);
        },

        compileIndexQuery: function(constrainedDNFQuery, key, index) {
            var fullQuery = Objs.exists(constrainedDNFQuery.query.$or, function(query) {
                return !(key in query);
            });
            var primaryKeySort = constrainedDNFQuery.options.sort && Objs.ithKey(constrainedDNFQuery.options.sort, 0) === key;
            var primarySortDirection = primaryKeySort ? constrainedDNFQuery.options.sort[key] : 1;
            var iter;
            var ignoreCase = !index.options().exact;
            if (fullQuery) {
                var materialized = [];
                index["itemIterate" + (ignoreCase ? "_ic" : "")](null, primarySortDirection, function(dataKey, data) {
                    materialized.push(data);
                });
                iter = new ArrayIterator(materialized);
            } else {
                iter = new SortedOrIterator(Objs.map(constrainedDNFQuery.query.$or, function(query) {
                    var iter;
                    var conds = query[key];
                    if (!primaryKeySort && index.options().ignoreCase && index.options().exact) {
                        if (this.indexQueryConditionsSize(conds, index, true) < this.indexQueryConditionsSize(conds, index, false))
                            ignoreCase = true;
                    }
                    var postfix = ignoreCase ? "_ic" : "";
                    if (conds.$eq || !Types.is_object(conds)) {
                        var materialized = [];
                        var value = Types.is_object(conds) ? conds.$eq : conds;
                        index["itemIterate" + postfix](value, primarySortDirection, function(dataKey, data) {
                            if (dataKey !== value)
                                return false;
                            materialized.push(data);
                        });
                        iter = new ArrayIterator(materialized);
                    } else if (conds.$in) {
                        var i = 0;
                        iter = new LazyMultiArrayIterator(function() {
                            if (i >= conds.$in.length)
                                return null;
                            var materialized = [];
                            index["itemIterate" + postfix](conds.$in[i], primarySortDirection, function(dataKey, data) {
                                if (dataKey !== conds["in"][i])
                                    return false;
                                materialized.push(data);
                            });
                            i++;
                            return materialized;
                        });
                    } else {
                        var currentKey = null;
                        var lastKey = null;
                        if (conds.$gt || conds.$gte)
                            currentKey = conds.$gt || conds.$gte;
                        if (conds.$lt || conds.$lte)
                            lastKey = conds.$lt || conds.$lte;
                        if (primarySortDirection < 0) {
                            var temp = currentKey;
                            currentKey = lastKey;
                            lastKey = temp;
                        }
                        iter = new LazyMultiArrayIterator(function() {
                            if (currentKey !== null && lastKey !== null) {
                                if (Math.sign((index.comparator())(currentKey, lastKey)) === Math.sign(primarySortDirection))
                                    return null;
                            }
                            var materialized = [];
                            index["itemIterate" + postfix](currentKey, primarySortDirection, function(dataKey, data) {
                                if (currentKey === null)
                                    currentKey = dataKey;
                                if (dataKey !== currentKey) {
                                    currentKey = dataKey;
                                    return false;
                                }
                                materialized.push(data);
                            });
                            return materialized;
                        });
                    }
                    return iter;
                }, this), index.comparator());
            }
            iter = (new FilteredIterator(iter, function(row) {
                return Queries.evaluate(constrainedDNFQuery.query, row);
            })).auto_destroy(iter, true);
            if (constrainedDNFQuery.options.sort) {
                if (primaryKeySort)
                    iter = (new PartiallySortedIterator(iter, Comparators.byObject(constrainedDNFQuery.options.sort), function(first, next) {
                        return first[key] === next[key];
                    })).auto_destroy(iter, true);
                else
                    iter = (new SortedIterator(iter, Comparators.byObject(constrainedDNFQuery.options.sort))).auto_destroy(iter, true);
            }
            if (constrainedDNFQuery.options.skip)
                iter = (new SkipIterator(iter, constrainedDNFQuery.options.skip)).auto_destroy(iter, true);
            if (constrainedDNFQuery.options.limit)
                iter = (new LimitIterator(iter, constrainedDNFQuery.options.limit)).auto_destroy(iter, true);
            return Promise.value(iter);
        },

        compileIndexedQuery: function(constrainedQuery, constrainedQueryCapabilities, constrainedQueryFunction, constrainedQueryContext, indices) {
            constrainedQuery = Constrained.rectify(constrainedQuery);
            indices = indices || {};
            if (this.queryPartially(constrainedQuery, constrainedQueryCapabilities) || Types.is_empty(indices))
                return this.compileQuery(constrainedQuery, constrainedQueryCapabilities, constrainedQueryFunction, constrainedQueryContext);
            var dnf = Queries.simplifiedDNF(constrainedQuery.query, true);
            if (constrainedQuery.options.sort) {
                var first = Objs.ithKey(constrainedQuery.options.sort, 0);
                if (indices[first]) {
                    return this.compileIndexQuery({
                        query: dnf,
                        options: constrainedQuery.options
                    }, first, indices[first]);
                }
            }
            var smallestSize = null;
            var smallestKey = null;
            Objs.iter(indices, function(index, key) {
                var size = this.indexQuerySize(dnf, key, index);
                if (size !== null && (smallestSize === null || size < smallestSize)) {
                    smallestSize = size;
                    smallestKey = key;
                }
            }, this);
            if (smallestKey !== null)
                return this.compileIndexQuery({
                    query: dnf,
                    options: constrainedQuery.options
                }, smallestKey, indices[smallestKey]);
            else
                return this.compileQuery(constrainedQuery, constrainedQueryCapabilities, constrainedQueryFunction, constrainedQueryContext);
        },

        _queryResultRectify: function(result, materialize) {
            result = result || [];
            if (Types.is_array(result) === materialize)
                return result;
            if (materialize)
                return result.asArray();
            return new ArrayIterator(result);
        }

    };
});