Package com.thinkaurelius.titan.graphdb.query

Source Code of com.thinkaurelius.titan.graphdb.query.AbstractVertexCentricQueryBuilder

package com.thinkaurelius.titan.graphdb.query;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.thinkaurelius.titan.core.*;
import com.thinkaurelius.titan.core.attribute.Cmp;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.SliceQuery;
import com.thinkaurelius.titan.graphdb.database.EdgeSerializer;
import com.thinkaurelius.titan.graphdb.internal.InternalType;
import com.thinkaurelius.titan.graphdb.internal.InternalVertex;
import com.thinkaurelius.titan.graphdb.internal.RelationType;
import com.thinkaurelius.titan.graphdb.query.condition.*;
import com.thinkaurelius.titan.graphdb.transaction.StandardTitanTx;
import com.thinkaurelius.titan.util.datastructures.Interval;
import com.thinkaurelius.titan.util.datastructures.PointInterval;
import com.thinkaurelius.titan.util.datastructures.ProperInterval;
import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.util.*;

abstract class AbstractVertexCentricQueryBuilder implements BaseVertexQuery {
    @SuppressWarnings("unused")
    private static final Logger log = LoggerFactory.getLogger(AbstractVertexCentricQueryBuilder.class);

    private static final String[] NO_TYPES = new String[0];
    private static final List<PredicateCondition<String, TitanRelation>> NO_CONSTRAINTS = ImmutableList.of();

    protected final StandardTitanTx tx;

    //Initial query configuration
    protected Direction dir = Direction.BOTH;
    protected String[] types = NO_TYPES;
    protected List<PredicateCondition<String, TitanRelation>> constraints = NO_CONSTRAINTS;

    protected boolean includeHidden = false;
    protected int limit = Query.NO_LIMIT;


    public AbstractVertexCentricQueryBuilder(final StandardTitanTx tx, final EdgeSerializer serializer) {
        assert serializer != null;
        assert tx != null;
        this.tx = tx;
    }

    Direction getDirection() {
        return dir;
    }

    /* ---------------------------------------------------------------
     * Query Construction
   * ---------------------------------------------------------------
   */


    private AbstractVertexCentricQueryBuilder addConstraint(String type, TitanPredicate rel, Object value) {
        assert type != null;
        assert rel != null;
        if (constraints==NO_CONSTRAINTS) constraints = new ArrayList<PredicateCondition<String, TitanRelation>>(5);
        constraints.add(new PredicateCondition<String, TitanRelation>(type, rel, value));
        return this;
    }

    @Override
    public AbstractVertexCentricQueryBuilder has(TitanKey key, Object value) {
        return has(key.getName(), value);
    }

    @Override
    public AbstractVertexCentricQueryBuilder has(TitanLabel label, TitanVertex vertex) {
        return has(label.getName(), vertex);
    }

    @Override
    public AbstractVertexCentricQueryBuilder has(String type, Object value) {
        return addConstraint(type, Cmp.EQUAL, value);
    }

    @Override
    public AbstractVertexCentricQueryBuilder hasNot(String key, Object value) {
        return has(key, Cmp.NOT_EQUAL, value);
    }

    @Override
    public AbstractVertexCentricQueryBuilder has(String key, Predicate predicate, Object value) {
        return addConstraint(key, TitanPredicate.Converter.convert(predicate), value);
    }

    @Override
    public AbstractVertexCentricQueryBuilder has(TitanKey key, Predicate predicate, Object value) {
        return has(key.getName(), predicate, value);
    }

    @Override
    public AbstractVertexCentricQueryBuilder has(String key) {
        return has(key, Cmp.NOT_EQUAL, (Object) null);
    }

    @Override
    public AbstractVertexCentricQueryBuilder hasNot(String key) {
        return has(key, Cmp.EQUAL, (Object) null);
    }

    @Override
    public <T extends Comparable<?>> AbstractVertexCentricQueryBuilder interval(TitanKey key, T start, T end) {
        return interval(key.getName(), start, end);
    }

    @Override
    public <T extends Comparable<?>> AbstractVertexCentricQueryBuilder interval(String key, T start, T end) {
        addConstraint(key, Cmp.GREATER_THAN_EQUAL, start);
        return addConstraint(key, Cmp.LESS_THAN, end);
    }

    @Deprecated
    public <T extends Comparable<T>> AbstractVertexCentricQueryBuilder has(String key, T value, com.tinkerpop.blueprints.Query.Compare compare) {
        return addConstraint(key, TitanPredicate.Converter.convert(compare), value);
    }

    @Override
    public AbstractVertexCentricQueryBuilder types(TitanType... type) {
        for (TitanType t : type) type(t);
        return this;
    }

    @Override
    public AbstractVertexCentricQueryBuilder labels(String... labels) {
        types = labels;
        return this;
    }

    @Override
    public AbstractVertexCentricQueryBuilder keys(String... keys) {
        types = keys;
        return this;
    }

    public AbstractVertexCentricQueryBuilder type(TitanType type) {
        types = new String[]{type.getName()};
        return this;
    }

    @Override
    public AbstractVertexCentricQueryBuilder direction(Direction d) {
        assert d != null;
        dir = d;
        return this;
    }

    public AbstractVertexCentricQueryBuilder includeHidden() {
        includeHidden = true;
        return this;
    }

    @Override
    public AbstractVertexCentricQueryBuilder limit(int limit) {
        assert limit >= 0;
        this.limit = limit;
        return this;
    }

    /* ---------------------------------------------------------------
     * Utility Methods
   * ---------------------------------------------------------------
   */

    protected InternalType getType(String typeName) {
        TitanType t = tx.getType(typeName);
        if (t == null && !tx.getConfiguration().getAutoEdgeTypeMaker().ignoreUndefinedQueryTypes()) {
            throw new IllegalArgumentException("Undefined type used in query: " + typeName);
        }
        return (InternalType) t;
    }

    protected final boolean hasTypes() {
        return types.length>0;
    }

    protected static Iterable<TitanVertex> edges2Vertices(final Iterable<TitanEdge> edges, final TitanVertex other) {
        return Iterables.transform(edges, new Function<TitanEdge, TitanVertex>() {
            @Nullable
            @Override
            public TitanVertex apply(@Nullable TitanEdge titanEdge) {
                return titanEdge.getOtherVertex(other);
            }
        });
    }

    protected VertexList edges2VertexIds(final Iterable<TitanEdge> edges, final TitanVertex other) {
        VertexArrayList vertices = new VertexArrayList();
        for (TitanEdge edge : edges) vertices.add(edge.getOtherVertex(other));
        return vertices;
    }

    /* ---------------------------------------------------------------
     * Query Optimization
   * ---------------------------------------------------------------
   */

    protected EdgeSerializer.VertexConstraint getVertexConstraint() {
        return null;
    }

    private static final int HARD_MAX_LIMIT   = 300000;

    protected BaseVertexCentricQuery constructQuery(RelationType returnType) {
        assert returnType != null;
        if (limit == 0)
            return BaseVertexCentricQuery.emptyQuery();

        //Prepare direction
        if (returnType == RelationType.PROPERTY) {
            if (dir == Direction.IN)
                return BaseVertexCentricQuery.emptyQuery();

            dir = Direction.OUT;
        }

        assert getVertexConstraint() == null || returnType == RelationType.EDGE;

        //Prepare constraints
        And<TitanRelation> conditions = QueryUtil.constraints2QNF(tx, constraints);
        if (conditions == null)
            return BaseVertexCentricQuery.emptyQuery();

        assert limit > 0;
        //Don't be smart with query limit adjustments - it just messes up the caching layer and penalizes when appropriate limits are set by the user!
        int sliceLimit = limit;

        //Construct (optimal) SliceQueries
        EdgeSerializer serializer = tx.getEdgeSerializer();
        List<BackendQueryHolder<SliceQuery>> queries;
        if (!hasTypes()) {
            BackendQueryHolder<SliceQuery> query = new BackendQueryHolder<SliceQuery>(serializer.getQuery(returnType),
                    ((dir == Direction.BOTH || (returnType == RelationType.PROPERTY && dir == Direction.OUT))
                            && !conditions.hasChildren() && includeHidden), true, null);
            if (sliceLimit!=Query.NO_LIMIT && sliceLimit<Integer.MAX_VALUE/3) {
                //If only one direction is queried, ask for twice the limit from backend since approximately half will be filtered
                if (dir != Direction.BOTH && (returnType == RelationType.EDGE || returnType == RelationType.RELATION))
                    sliceLimit *= 2;
                //on properties, add some for the hidden properties on a vertex
                if (!includeHidden && (returnType == RelationType.PROPERTY || returnType == RelationType.RELATION))
                    sliceLimit += 3;
            }
            query.getBackendQuery().setLimit(computeLimit(conditions,sliceLimit));
            queries = ImmutableList.of(query);
            //Add remaining conditions that only apply if no type is defined
            if (!includeHidden)
                conditions.add(new HiddenFilterCondition<TitanRelation>());

            conditions.add(returnType);
        } else {
            Set<TitanType> ts = new HashSet<TitanType>(types.length);
            queries = new ArrayList<BackendQueryHolder<SliceQuery>>(types.length + 4);

            for (String typeName : types) {
                InternalType type = getType(typeName);
                if (type != null && (includeHidden || !type.isHidden())) {
                    ts.add(type);
                    if (type.isPropertyKey()) {
                        if (returnType == RelationType.EDGE)
                            throw new IllegalArgumentException("Querying for edges but including a property key: " + type.getName());
                        returnType = RelationType.PROPERTY;
                    }
                    if (type.isEdgeLabel()) {
                        if (returnType == RelationType.PROPERTY)
                            throw new IllegalArgumentException("Querying for properties but including an edge label: " + type.getName());
                        returnType = RelationType.EDGE;
                    }
                    //Construct sort key constraints (if any, and if not direction==Both)
                    EdgeSerializer.TypedInterval[] sortKeyConstraints = new EdgeSerializer.TypedInterval[type.getSortKey().length];
                    And<TitanRelation> remainingConditions = conditions;
                    boolean vertexConstraintApplies = type.getSortKey().length == 0 || conditions.hasChildren();
                    if (type.getSortKey().length > 0 && conditions.hasChildren()) {
                        remainingConditions = conditions.clone();
                        sortKeyConstraints = compileSortKeyConstraints(type,tx,remainingConditions);
                        if (sortKeyConstraints==null) continue; //Constraints cannot be matched

                        Interval interval;
                        if (sortKeyConstraints[sortKeyConstraints.length-1] == null ||
                                (interval=sortKeyConstraints[sortKeyConstraints.length-1].interval) == null || !interval.isPoint()) {
                            vertexConstraintApplies = false;

                        }
                    }
                    Direction[] dirs = {dir};
                    EdgeSerializer.VertexConstraint vertexConstraint = getVertexConstraint();
                    if (dir == Direction.BOTH &&
                            (hasSortKeyConstraints(sortKeyConstraints) || (vertexConstraintApplies && vertexConstraint != null))) {
                        //Split on direction in the presence of effective sort key constraints
                        dirs = new Direction[]{Direction.OUT, Direction.IN};
                    }
                    for (Direction dir : dirs) {
                        EdgeSerializer.VertexConstraint vertexCon = vertexConstraint;
                        if (vertexCon == null || !vertexConstraintApplies || type.isUnique(dir)) vertexCon = null;
                        EdgeSerializer.TypedInterval[] sortConstraints = sortKeyConstraints;
                        if (hasSortKeyConstraints(sortKeyConstraints) && type.isUnique(dir)) {
                            sortConstraints = new EdgeSerializer.TypedInterval[type.getSortKey().length];
                        }

                        boolean isFitted = !remainingConditions.hasChildren()
                                && vertexConstraint == vertexCon && sortConstraints == sortKeyConstraints;
                        SliceQuery q = serializer.getQuery(type, dir, sortConstraints, vertexCon);
                        q.setLimit(computeLimit(remainingConditions, sliceLimit));
                        queries.add(new BackendQueryHolder<SliceQuery>(q, isFitted, true, null));
                    }
                }
            }
            if (queries.isEmpty())
                return BaseVertexCentricQuery.emptyQuery();

            conditions.add(getTypeCondition(ts));
        }

        return new BaseVertexCentricQuery(QueryUtil.simplifyQNF(conditions), dir, queries, limit);
    }

    public EdgeSerializer.TypedInterval[] getFittingKeyConstraints(InternalType type) {
        if (constraints.isEmpty()) return new EdgeSerializer.TypedInterval[type.getSortKey().length];
        else if (dir==Direction.BOTH || type.isUnique(dir) || type.getSortKey().length<=0) return null;

        And<TitanRelation> conditions = QueryUtil.constraints2QNF(tx, constraints);
        if (conditions == null) return null;
        EdgeSerializer.TypedInterval[] sortKeyConstraints = compileSortKeyConstraints(type,tx,conditions);
        if (!conditions.isEmpty()) return null;
        return sortKeyConstraints;
    }

    private static EdgeSerializer.TypedInterval[] compileSortKeyConstraints(InternalType type, StandardTitanTx tx, And<TitanRelation> conditions) {
        long[] sortKeys = type.getSortKey();
        EdgeSerializer.TypedInterval[] sortKeyConstraints = new EdgeSerializer.TypedInterval[type.getSortKey().length];

        for (int i = 0; i < sortKeys.length; i++) {
            InternalType pktype = (InternalType) tx.getExistingType(sortKeys[i]);
            Interval interval = null;
            //First check for equality constraints, since those are the most constraining
            for (Iterator<Condition<TitanRelation>> iter = conditions.iterator(); iter.hasNext(); ) {
                Condition<TitanRelation> cond = iter.next();
                if (cond instanceof PredicateCondition) {
                    PredicateCondition<TitanType, TitanRelation> atom = (PredicateCondition) cond;
                    if (atom.getKey().equals(pktype) && atom.getPredicate() == Cmp.EQUAL && interval == null) {
                        interval = new PointInterval(atom.getValue());
                        iter.remove();
                    }
                }
            }

            //If there are no equality constraints, check if the sort key's datatype allows comparison
            //and if so, find a bounding interval from the remaining constraints
            if (interval == null && pktype.isPropertyKey()
                    && Comparable.class.isAssignableFrom(((TitanKey) pktype).getDataType())) {
                ProperInterval pint = new ProperInterval();
                for (Iterator<Condition<TitanRelation>> iter = conditions.iterator(); iter.hasNext(); ) {
                    Condition<TitanRelation> cond = iter.next();
                    if (cond instanceof PredicateCondition) {
                        PredicateCondition<TitanType, TitanRelation> atom = (PredicateCondition) cond;
                        if (atom.getKey().equals(pktype)) {
                            TitanPredicate predicate = atom.getPredicate();
                            Object value = atom.getValue();
                            if (predicate instanceof Cmp) {
                                switch ((Cmp) predicate) {
                                    case NOT_EQUAL:
                                        break;
                                    case LESS_THAN:
                                        if (pint.getEnd() == null || pint.getEnd().compareTo(value) >= 0) {
                                            pint.setEnd((Comparable) value);
                                            pint.setEndInclusive(false);
                                        }
                                        iter.remove();
                                        break;
                                    case LESS_THAN_EQUAL:
                                        if (pint.getEnd() == null || pint.getEnd().compareTo(value) > 0) {
                                            pint.setEnd((Comparable) value);
                                            pint.setEndInclusive(true);
                                        }
                                        iter.remove();
                                        break;
                                    case GREATER_THAN:
                                        if (pint.getStart() == null || pint.getStart().compareTo(value) <= 0) {
                                            pint.setStart((Comparable) value);
                                            pint.setStartInclusive(false);
                                        }
                                        iter.remove();
                                        break;
                                    case GREATER_THAN_EQUAL:
                                        if (pint.getStart() == null || pint.getStart().compareTo(value) < 0) {
                                            pint.setStart((Comparable) value);
                                            pint.setStartInclusive(true);
                                        }
                                        iter.remove();
                                        break;
                                }
                            }
                        }
                    } else if (cond instanceof Or) {
                        //Grab a probe so we can investigate what type of or-condition this is and whether it allows us to constrain this sort key
                        Condition probe = ((Or) cond).get(0);
                        if (probe instanceof PredicateCondition && ((PredicateCondition) probe).getKey().equals(pktype) &&
                                ((PredicateCondition) probe).getPredicate() == Cmp.EQUAL) {
                            //We make the assumption that this or-condition is a group of equality constraints for the same type (i.e. an unrolled Contain.IN)
                            //This assumption is enforced by precondition statements below
                            //TODO: Consider splitting query on sort key with a limited number (<=3) of possible values in or-clause

                            //Now, we find the smallest and largest value in this group of equality constraints to bound the interval
                            Comparable smallest = null, largest = null;
                            for (Condition child : cond.getChildren()) {
                                assert child instanceof PredicateCondition;
                                PredicateCondition pc = (PredicateCondition) child;
                                assert pc.getKey().equals(pktype);
                                assert pc.getPredicate() == Cmp.EQUAL;

                                Object v = pc.getValue();
                                if (smallest == null) {
                                    smallest = (Comparable) v;
                                    largest = (Comparable) v;
                                } else {
                                    if (smallest.compareTo(v) > 0) {
                                        smallest = (Comparable) v;
                                    } else if (largest.compareTo(v) < 0) {
                                        largest = (Comparable) v;
                                    }
                                }
                            }
                            //After finding the smallest and largest value respectively, we constrain the interval
                            assert smallest != null && largest != null; //due to probing, there must be at least one
                            if (pint.getEnd() == null || pint.getEnd().compareTo(largest) > 0) {
                                pint.setEnd(largest);
                                pint.setEndInclusive(true);
                            }
                            if (pint.getStart() == null || pint.getStart().compareTo(smallest) < 0) {
                                pint.setStart(smallest);
                                pint.setStartInclusive(true);
                            }
                            //We cannot remove this condition from remainingConditions, since its not exactly fulfilled (only bounded)
                        }
                    }
                }
                if (pint.isEmpty()) return null;
                if (pint.getStart() != null || pint.getEnd() != null) interval = pint;
            }

            sortKeyConstraints[i] = new EdgeSerializer.TypedInterval(pktype, interval);
            if (interval == null || !interval.isPoint()) {
                break;
            }
        }
        return sortKeyConstraints;
    }

    public static boolean hasSortKeyConstraints(EdgeSerializer.TypedInterval[] cons) {
        return cons.length > 0 && cons[0] != null;
    }

    private static Condition<TitanRelation> getTypeCondition(Set<TitanType> types) {
        assert !types.isEmpty();
        if (types.size() == 1)
            return new LabelCondition<TitanRelation>(types.iterator().next());

        Or<TitanRelation> typeCond = new Or<TitanRelation>(types.size());

        for (TitanType type : types)
            typeCond.add(new LabelCondition<TitanRelation>(type));

        return typeCond;
    }

    private int computeLimit(And<TitanRelation> conditions, int baseLimit) {
        if (baseLimit==Query.NO_LIMIT) return baseLimit;
        assert baseLimit>0;
        baseLimit = Math.max(baseLimit,Math.min(HARD_MAX_LIMIT, QueryUtil.adjustLimitForTxModifications(tx, conditions.size(), baseLimit)));
        assert baseLimit>0;
        return baseLimit;
    }


}
TOP

Related Classes of com.thinkaurelius.titan.graphdb.query.AbstractVertexCentricQueryBuilder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.