GeoKnow/Jassa-Core

View on GitHub
lib/service/ServiceUtils.js

Summary

Maintainability
B
4 hrs
Test Coverage
var union = require('lodash.union');
var shared = require('../util/shared');
var Promise = shared.Promise;

var ResultSetArrayIteratorBinding = require('./result_set/ResultSetArrayIteratorBinding');

var ArrayUtils = require('../util/ArrayUtils');
var IteratorArray = require('../util/collection/IteratorArray');

var ExprVar = require('../sparql/expr/ExprVar');
var ElementFilter = require('../sparql/element/ElementFilter');
var E_OneOf = require('../sparql/expr/E_OneOf');
var ElementSubQuery = require('../sparql/element/ElementSubQuery');
var VarUtils = require('../sparql/VarUtils');
var Binding = require('../sparql/Binding');

var ConceptUtils = require('../sparql/ConceptUtils');
var ElementUtils = require('../sparql/ElementUtils');
var QueryUtils = require('../sparql/QueryUtils');

var ResultSetUtils = require('./ResultSetUtils');


var PromiseUtils = require('../util/PromiseUtils');

//var Triple = require('../rdf/Triple');
////var VarUtils = require('../sparql/VarUtils');
//var Query = require('../sparql/Query');
////var ExprVar = jassa.sparql.ExprVar;
////var E_OneOf = require('../sparql/E_OneOf')
//var ElementGroup = require('../sparql/element/ElementGroup');
//var ElementTriplesBlock = require('../sparql/element/ElementTriplesBlock');
//var ElementFilter = jassa.sparql.ElementFilter;
//var GraphImpl = require('../rdf/GraphImpl');


var ServiceUtils = {

    // FIXME constrainQueryVar, constrainQueryExprVar, chunkQuery should go to a different place, such as sparql.QueryUtils
    constrainQueryVar: function(query, v, nodes) {
        var exprVar = new ExprVar(v);
        var result = this.constrainQueryExprVar(query, exprVar, nodes);
        return result;
    },

    constrainQueryExprVar: function(query, exprVar, nodes) {
        var result = query.clone();
        var e = new ElementFilter(new E_OneOf(exprVar, nodes));
        result.getElements().push(e);

        return result;
    },

    /**
     * Returns an array of queries where the variable v has been constraint to elements in nodes.
     */
    chunkQuery: function(query, v, nodes, maxChunkSize) {
        var chunks = ArrayUtils.chunk(nodes, maxChunkSize);
        var exprVar = new ExprVar(v);

        var self = this;
        var result = chunks.map(function() { // chunk) {
            var r = self.constrainQueryExprVar(query, exprVar, nodes);
            return r;
        });

        return result;
    },

    mergeResultSets: function(arrayOfResultSets) {
        var bindings = [];
        var varNames = [];
        arrayOfResultSets.forEach(function(rs) {
            var vns = rs.getVarNames();
            varNames = union(varNames, vns);

            var arr = rs.getIterator().getArray();
            bindings.push.apply(bindings, arr);
        });

        var itBinding = new IteratorArray(bindings);
        var result = new ResultSetArrayIteratorBinding(itBinding, varNames);

        return result;
    },

    execSelectForNodes: function(sparqlService, query, v, nodes, maxChunkSize) {
        var queries = this.chunkQuery(query, v, nodes, maxChunkSize);

        var promises = queries.map(function(query) {
            var qe = sparqlService.createQueryExecution(query);
            var r = qe.execSelect();
            return r;
        });

        var masterTask = PromiseUtils.all(promises);

        var self = this;
        var result = masterTask.then(function( /* arguments will be result sets */ ) {
            var r = self.mergeResultSets(arguments);
            return r;
        });

        return result;
    },

    /**
     * TODO Rather use .close()
     *
     * @param {Object} rs
     * @returns
     */
    consumeResultSet: function(rs) {
        while (rs.hasNext()) {
            rs.nextBinding();
        }
    },

    resultSetToList: function(rs, variable) {
        var result = [];
        while (rs.hasNext()) {
            var binding = rs.nextBinding();

            var node = binding.get(variable);
            result.push(node);
        }
        return result;
    },

    // TODO: If there is only one variable in the rs, use it.
    resultSetToInt: function(rs, variable) {
        var result = null;

        if (rs.hasNext()) {
            var binding = rs.nextBinding();

            var node = binding.get(variable);

            // TODO Validate that the result actually is int.
            result = node.getLiteralValue();
        }

        return result;
    },

    fetchList: function(queryExecution, variable) {
        var self = this;
        var result = queryExecution.execSelect().then(function(rs) {
            var r = self.resultSetToList(rs, variable);
            return r;
        });

        return result;
    },

    fetchInt: function(sparqlService, query, v) {
        var qe = sparqlService.createQueryExecution(query);
        var result = this.fetchIntQe(qe, v);
        return result;
    },

    /**
     * Fetches the first column of the first row of a result set and parses it as int.
     *
     */
    fetchIntQe: function(queryExecution, variable) {
        var self = this;
        var result = queryExecution.execSelect().then(function(rs) {
            var r = self.resultSetToInt(rs, variable);
            return r;
        });

        return result;
    },

    // NOTE: If there is a rowLimit, we can't determine whether there are more items or not
    fetchCountConcept: Promise.method(function(sparqlService, concept, itemLimit, rowLimit) {

        var outputVar = ConceptUtils.freshVar(concept);

        var xitemLimit = itemLimit == null ? null : itemLimit + 1;
        var xrowLimit = rowLimit == null ? null : rowLimit + 1;

        var countQuery = ConceptUtils.createQueryCount(concept, outputVar, xitemLimit, xrowLimit);

        //var qe = sparqlService.createQueryExecution(countQuery);

        return ServiceUtils
            .fetchInt(sparqlService, countQuery, outputVar)
            .then(function(count) {
                var hasMoreItems = rowLimit != null
                    ? null
                    : (itemLimit != null ? count > itemLimit : false)
                    ;

                var r = {
                    hasMoreItems: hasMoreItems,
                    count: hasMoreItems ? itemLimit : count,
                    itemLimit: itemLimit,
                    rowLimit: rowLimit
                };

                return r;
            });
    }),

    fetchCountRows: function(sparqlService, element, rowLimit) {
        var v = ElementUtils.freshVar(element, 'c');
        var query = ElementUtils.createQueryCountRows(element, v, rowLimit + 1);
        var result = this.fetchInt(sparqlService, query, v).then(function(count) {
            var r = {
                count: Math.min(count, rowLimit),
                hasMoreItems: count > rowLimit,
                rowLimit: rowLimit
            };

            return r;
        });

        return result;
    },


    /**
     * Count the results of a query, whith fallback on timeouts
     *
     * Attempt to count the full result set based on firstTimeoutInMs
     *
     * if this fails, repeat the count attempt using the scanLimit
     *
     * TODO Finish
     */
    fetchCountQuery: Promise.method(function(sparqlService, query, firstTimeoutInMs, limit) {

        var elements = [
            new ElementSubQuery(query),
        ];

        var varsMentioned = query.getVarsMentioned();

        var varGen = VarUtils.createVarGen('c', varsMentioned);

        var outputVar = varGen.next();
        // var outputVar = rdf.NodeFactory.createVar('_cnt_');

        // createQueryCount(elements, limit, variable, outputVar, groupVars, useDistinct, options)
        var countQuery = QueryUtils.createQueryCount(elements, null, null, outputVar, null, null, null);

        var qe = sparqlService.createQueryExecution(countQuery);
        qe.setTimeout(firstTimeoutInMs);

        return ServiceUtils.fetchIntQe(qe, outputVar)
            .then(function(count) {
                return {
                    count: count,
                    limit: null,
                    hasMoreItems: false,
                };
            })
            //.catch // Prevent the eclipse default js parser to complain about this
            // https://github.com/petkaantonov/bluebird/blob/master/API.md -> For compatibility with earlier ECMAScript version, an alias .caught() is provided for .catch().
            .caught
            (function() {
                // Try counting with the fallback size
                var countQuery = QueryUtils.createQueryCount(elements, limit + 1, null, outputVar, null, null, null);
                var qe = sparqlService.createQueryExecution(countQuery);
                return ServiceUtils.fetchIntQe(qe, outputVar)
                    .then(function(count) {
                        return {
                            count: count,
                            limit: limit,
                            hasMoreItems: count > limit,
                        };
                    });
            });
    }),

    // ns.globalSparqlCacheQueue = [];

    fetchItemsConcept: function(sparqlService, concept, limit, offset) {
        var query = ConceptUtils.createQueryList(concept, limit, offset);
        var qe = sparqlService.createQueryExecution(query);

        var result = qe.execSelect().then(function(rs) {
            var r = ServiceUtils.resultSetToList(rs, concept.getVar());
            return r;
        });

        return result;
    },


    /**
     * @param sparqlService
     * @param iris
     * @returns jassa.rdf.Graph
     */
    execDescribeViaSelect: function(sparqlService, iris) {
        var query = QueryUtils.createQueryDescribeViaSelect(iris, VarUtils.s, VarUtils.p, VarUtils.o);

        var qe = sparqlService.createQueryExecution(query);
        var result = qe.execSelect().then(function(rs) {
            var r = ResultSetUtils.resultSetToGraph(rs, VarUtils.s, VarUtils.p, VarUtils.o);
            return r;
        });

        return result;
    }

};

module.exports = ServiceUtils;