All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.janusgraph.graphdb.JanusGraphIndexTest Maven / Gradle / Ivy

There is a newer version: 100.3.2.1
Show newest version
// Copyright 2017 JanusGraph Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.janusgraph.graphdb;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import org.janusgraph.TestCategory;
import org.janusgraph.core.Cardinality;
import org.janusgraph.core.EdgeLabel;
import org.janusgraph.core.PropertyKey;
import org.janusgraph.core.JanusGraphException;
import org.janusgraph.core.JanusGraphFactory;
import org.janusgraph.core.JanusGraph;
import org.janusgraph.core.JanusGraphQuery;
import org.janusgraph.core.JanusGraphIndexQuery;
import org.janusgraph.core.JanusGraphTransaction;
import org.janusgraph.core.JanusGraphVertex;
import org.janusgraph.core.JanusGraphVertexProperty;
import org.janusgraph.core.VertexLabel;
import org.janusgraph.core.attribute.Cmp;
import org.janusgraph.core.attribute.Geo;
import org.janusgraph.core.attribute.Geoshape;
import org.janusgraph.core.attribute.Text;
import org.janusgraph.core.log.TransactionRecovery;
import org.janusgraph.core.schema.Mapping;
import org.janusgraph.core.schema.Parameter;
import org.janusgraph.core.schema.SchemaAction;
import org.janusgraph.core.schema.SchemaStatus;
import org.janusgraph.core.schema.JanusGraphIndex;
import org.janusgraph.core.schema.JanusGraphManagement;
import org.janusgraph.core.util.ManagementUtil;
import org.janusgraph.diskstorage.Backend;
import org.janusgraph.diskstorage.BackendException;
import org.janusgraph.diskstorage.configuration.ConfigElement;
import org.janusgraph.diskstorage.configuration.WriteConfiguration;
import org.janusgraph.diskstorage.indexing.IndexFeatures;
import org.janusgraph.diskstorage.indexing.IndexInformation;
import org.janusgraph.diskstorage.indexing.IndexProvider;
import org.janusgraph.diskstorage.log.kcvs.KCVSLog;
import org.janusgraph.diskstorage.util.time.TimestampProvider;
import org.janusgraph.example.GraphOfTheGodsFactory;
import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration;
import org.janusgraph.graphdb.database.management.ManagementSystem;
import org.janusgraph.graphdb.internal.ElementCategory;
import org.janusgraph.graphdb.internal.Order;
import org.janusgraph.graphdb.log.StandardTransactionLogProcessor;
import org.janusgraph.graphdb.types.ParameterType;
import org.janusgraph.graphdb.types.StandardEdgeLabelMaker;
import org.janusgraph.testutil.TestGraphConfigs;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.ElementValueComparator;
import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Element;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static org.janusgraph.graphdb.JanusGraphTest.evaluateQuery;
import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.*;
import static org.janusgraph.testutil.JanusGraphAssert.*;
import static org.apache.tinkerpop.gremlin.process.traversal.Order.decr;
import static org.apache.tinkerpop.gremlin.process.traversal.Order.incr;
import static org.junit.jupiter.api.Assertions.*;


/**
 * @author Matthias Broecheler ([email protected])
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class JanusGraphIndexTest extends JanusGraphBaseTest {

    private static final ElementValueComparator ORDER_AGE_DECR = new ElementValueComparator("age", org.apache.tinkerpop.gremlin.process.traversal.Order.decr);
    private static final ElementValueComparator ORDER_AGE_INCR = new ElementValueComparator("age", org.apache.tinkerpop.gremlin.process.traversal.Order.incr);
    private static final ElementValueComparator ORDER_LENGTH_DECR = new ElementValueComparator("length", org.apache.tinkerpop.gremlin.process.traversal.Order.decr);
    private static final ElementValueComparator ORDER_LENGTH_INCR = new ElementValueComparator("length", org.apache.tinkerpop.gremlin.process.traversal.Order.incr);

    public static final String INDEX = GraphOfTheGodsFactory.INDEX_NAME;
    public static final String VINDEX = "v" + INDEX;
    public static final String EINDEX = "e" + INDEX;
    public static final String PINDEX = "p" + INDEX;

    private static final int RETRY_COUNT = 30;
    private static final long RETRY_INTERVAL = 1000L;

    public final boolean supportsGeoPoint;
    public final boolean supportsNumeric;
    public final boolean supportsText;

    public IndexFeatures indexFeatures;

    private static final Logger log =
            LoggerFactory.getLogger(JanusGraphIndexTest.class);

    protected JanusGraphIndexTest(boolean supportsGeoPoint, boolean supportsNumeric, boolean supportsText) {
        this.supportsGeoPoint = supportsGeoPoint;
        this.supportsNumeric = supportsNumeric;
        this.supportsText = supportsText;
    }

    private Parameter getStringMapping() {
        if (indexFeatures.supportsStringMapping(Mapping.STRING)) return Mapping.STRING.asParameter();
        else if (indexFeatures.supportsStringMapping(Mapping.TEXTSTRING)) return Mapping.TEXTSTRING.asParameter();
        throw new AssertionError("String mapping not supported");
    }

    private Parameter getTextMapping() {
        if (indexFeatures.supportsStringMapping(Mapping.TEXT)) return Mapping.TEXT.asParameter();
        else if (indexFeatures.supportsStringMapping(Mapping.TEXTSTRING)) return Mapping.TEXTSTRING.asParameter();
        throw new AssertionError("Text mapping not supported");
    }

    private Parameter getFieldMap(PropertyKey key) {
        return ParameterType.MAPPED_NAME.getParameter(key.name());
    }

    public abstract boolean supportsLuceneStyleQueries();


    public abstract boolean supportsWildcardQuery();

    @Override
    public void open(WriteConfiguration config) {
        super.open(config);
        indexFeatures = graph.getBackend().getIndexFeatures().get(INDEX);
    }

    @Override
    public void clopen(Object... settings) {
        graph.tx().commit();
        super.clopen(settings);
    }

    /**
     * Tests the {@link org.janusgraph.example.GraphOfTheGodsFactory#load(org.janusgraph.core.JanusGraph)}
     * method used as the standard example that ships with JanusGraph.
     */
    @Test
    public void testGraphOfTheGods() {
        GraphOfTheGodsFactory.load(graph);
        assertGraphOfTheGods(graph);
    }

    public static void assertGraphOfTheGods(JanusGraph graphOfTheGods) {
        assertCount(12, graphOfTheGods.query().vertices());
        assertCount(3, graphOfTheGods.query().has(LABEL_NAME, "god").vertices());
        final JanusGraphVertex h = getOnlyVertex(graphOfTheGods.query().has("name", "hercules"));
        assertEquals(30, h.value("age").intValue());
        assertEquals("demigod", h.label());
        assertCount(5, h.query().direction(Direction.BOTH).edges());
        graphOfTheGods.tx().commit();
    }

    /**
     * Ensure clearing storage actually removes underlying graph and index databases.
     * @throws Exception
     */
    @Test
    public void testClearStorage() throws Exception {
        GraphOfTheGodsFactory.load(graph);
        tearDown();
        config.set(ConfigElement.getPath(GraphDatabaseConfiguration.DROP_ON_CLEAR), true);
        final Backend backend = getBackend(config, false);
        assertStorageExists(backend, true);
        clearGraph(config);
        try { backend.close(); } catch (final Exception e) { /* Most backends do not support closing after clearing */}
        try (final Backend newBackend = getBackend(config, false)) {
            assertStorageExists(newBackend, false);
        }
    }

    private static void assertStorageExists(Backend backend, boolean exists) throws Exception {
        final String suffix = exists ? "should exist before clearing" : "should not exist after clearing";
        assertTrue(backend.getStoreManager().exists() == exists, "graph " + suffix);
        for (final IndexInformation index : backend.getIndexInformation().values()) {
            assertTrue(((IndexProvider) index).exists() == exists, "index " + suffix);
        }
    }

    @Test
    public void testSimpleUpdate() {
        final PropertyKey name = makeKey("name", String.class);
        makeLabel("knows");
        mgmt.buildIndex("namev", Vertex.class).addKey(name).buildMixedIndex(INDEX);
        mgmt.buildIndex("namee", Edge.class).addKey(name).buildMixedIndex(INDEX);
        finishSchema();

        JanusGraphVertex v = tx.addVertex("name", "Marko Rodriguez");
        Edge e = v.addEdge("knows", v, "name", "Hulu Bubab");
        assertCount(1, tx.query().has("name", Text.CONTAINS, "marko").vertices());
        assertCount(1, tx.query().has("name", Text.CONTAINS, "Hulu").edges());
        for (final Vertex u : tx.getVertices()) assertEquals("Marko Rodriguez", u.value("name"));
        clopen();
        assertCount(1, tx.query().has("name", Text.CONTAINS, "marko").vertices());
        assertCount(1, tx.query().has("name", Text.CONTAINS, "Hulu").edges());
        for (final Vertex u : tx.getVertices()) assertEquals("Marko Rodriguez", u.value("name"));
        v = getOnlyVertex(tx.query().has("name", Text.CONTAINS, "marko"));
        v.property(VertexProperty.Cardinality.single, "name", "Marko");
        e = getOnlyEdge(v.query().direction(Direction.OUT));
        e.property("name", "Tubu Rubu");
        assertCount(1, tx.query().has("name", Text.CONTAINS, "marko").vertices());
        assertCount(1, tx.query().has("name", Text.CONTAINS, "Rubu").edges());
        assertCount(0, tx.query().has("name", Text.CONTAINS, "Hulu").edges());
        for (final Vertex u : tx.getVertices()) assertEquals("Marko", u.value("name"));
        clopen();
        assertCount(1, tx.query().has("name", Text.CONTAINS, "marko").vertices());
        assertCount(1, tx.query().has("name", Text.CONTAINS, "Rubu").edges());
        assertCount(0, tx.query().has("name", Text.CONTAINS, "Hulu").edges());
        for (final Vertex u : tx.getVertices()) assertEquals("Marko", u.value("name"));
    }

    @Test
    public void testListUpdate() {
        if (!indexFeatures.supportsCardinality(Cardinality.LIST)) {
            return;
        }
        final PropertyKey name = makeKey("name", String.class);
        if (!indexFeatures.supportsStringMapping(Mapping.TEXTSTRING)) {

        }
        final PropertyKey alias = mgmt.makePropertyKey("alias") .dataType(String.class).cardinality(Cardinality.LIST).make();
        mgmt.buildIndex("namev", Vertex.class).addKey(name).addKey(alias, indexFeatures.supportsStringMapping(Mapping.TEXTSTRING) ?Mapping.TEXTSTRING.asParameter(): Mapping.DEFAULT.asParameter()).buildMixedIndex(INDEX);
        finishSchema();
        JanusGraphVertex v = tx.addVertex("name", "Marko Rodriguez");
        assertCount(1, tx.query().has("name", Text.CONTAINS, "marko").vertices());
        clopen();
        assertCount(1, tx.query().has("name", Text.CONTAINS, "marko").vertices());
        v = getOnlyVertex(tx.query().has("name", Text.CONTAINS, "marko"));
        v.property(VertexProperty.Cardinality.list, "alias", "Marko");
        assertCount(1, tx.query().has("alias", Text.CONTAINS, "Marko").vertices());
        clopen();
        assertCount(1, tx.query().has("alias", Text.CONTAINS, "Marko").vertices());
        v = getOnlyVertex(tx.query().has("name", Text.CONTAINS, "marko"));
        v.property(VertexProperty.Cardinality.list, "alias", "mRodriguez");
        assertCount(1, tx.query().has("alias", Text.CONTAINS, "mRodriguez").vertices());
        clopen();
        assertCount(1, tx.query().has("alias", Text.CONTAINS, "Marko").vertices());
        assertCount(1, tx.query().has("alias", Text.CONTAINS, "mRodriguez").vertices());
        if (indexFeatures.supportsStringMapping(Mapping.TEXTSTRING)) {
            assertCount(1, tx.query().has("alias", Cmp.EQUAL, "Marko").vertices());
            assertCount(1, tx.query().has("alias", Cmp.EQUAL, "mRodriguez").vertices());
        }
    }

    @Test
    public void testSetUpdate() {
        if (!indexFeatures.supportsCardinality(Cardinality.SET)) {
            return;
        }
        final PropertyKey name = makeKey("name", String.class);
        final PropertyKey alias = mgmt.makePropertyKey("alias").dataType(String.class).cardinality(Cardinality.SET).make();
        mgmt.buildIndex("namev", Vertex.class).addKey(name).addKey(alias, indexFeatures.supportsStringMapping(Mapping.TEXTSTRING) ?Mapping.TEXTSTRING.asParameter(): Mapping.DEFAULT.asParameter()).buildMixedIndex(INDEX);
        finishSchema();
        JanusGraphVertex v = tx.addVertex("name", "Marko Rodriguez");
        assertCount(1, tx.query().has("name", Text.CONTAINS, "marko").vertices());
        clopen();
        assertCount(1, tx.query().has("name", Text.CONTAINS, "marko").vertices());
        v = getOnlyVertex(tx.query().has("name", Text.CONTAINS, "marko"));
        v.property(VertexProperty.Cardinality.set, "alias", "Marko");
        assertCount(1, tx.query().has("alias", Text.CONTAINS, "Marko").vertices());
        clopen();
        assertCount(1, tx.query().has("alias", Text.CONTAINS, "Marko").vertices());
        v = getOnlyVertex(tx.query().has("name", Text.CONTAINS, "marko"));
        v.property(VertexProperty.Cardinality.set, "alias", "mRodriguez");
        assertCount(1, tx.query().has("alias", Text.CONTAINS, "mRodriguez").vertices());
        clopen();
        assertCount(1, tx.query().has("alias", Text.CONTAINS, "Marko").vertices());
        assertCount(1, tx.query().has("alias", Text.CONTAINS, "mRodriguez").vertices());
        if (indexFeatures.supportsStringMapping(Mapping.TEXTSTRING)) {
            assertCount(1, tx.query().has("alias", Cmp.EQUAL, "Marko").vertices());
            assertCount(1, tx.query().has("alias", Cmp.EQUAL, "mRodriguez").vertices());
        }
    }


    @Test
    public void testIndexing() throws InterruptedException {

        final PropertyKey text = makeKey("text", String.class);
        createExternalVertexIndex(text, INDEX);
        createExternalEdgeIndex(text, INDEX);

        PropertyKey name = makeKey("name", String.class);
        mgmt.addIndexKey(getExternalIndex(Vertex.class,INDEX),name, Parameter.of("mapping", Mapping.TEXT));
        mgmt.addIndexKey(getExternalIndex(Edge.class,INDEX),name, Parameter.of("mapping", Mapping.TEXT));

        final PropertyKey location = makeKey("location", Geoshape.class);
        createExternalVertexIndex(location, INDEX);
        createExternalEdgeIndex(location, INDEX);

        final PropertyKey boundary = makeKey("boundary", Geoshape.class);
        mgmt.addIndexKey(getExternalIndex(Vertex.class,INDEX),boundary, Parameter.of("mapping", Mapping.PREFIX_TREE), Parameter.of("index-geo-dist-error-pct", 0.0025));
        mgmt.addIndexKey(getExternalIndex(Edge.class,INDEX),boundary, Parameter.of("mapping", Mapping.PREFIX_TREE), Parameter.of("index-geo-dist-error-pct", 0.0025));

        final PropertyKey time = makeKey("time", Long.class);
        createExternalVertexIndex(time, INDEX);
        createExternalEdgeIndex(time, INDEX);

        final PropertyKey category = makeKey("category", Integer.class);
        mgmt.buildIndex("vcategory", Vertex.class).addKey(category).buildCompositeIndex();
        mgmt.buildIndex("ecategory", Edge.class).addKey(category).buildCompositeIndex();

        final PropertyKey group = makeKey("group", Byte.class);
        createExternalVertexIndex(group, INDEX);
        createExternalEdgeIndex(group, INDEX);

        makeVertexIndexedKey("uid", Integer.class);
        ((StandardEdgeLabelMaker) mgmt.makeEdgeLabel("knows")).sortKey(time).signature(location,boundary).make();
        finishSchema();

        clopen();
        final String[] words = {"world", "aurelius", "janusgraph", "graph"};
        final int numCategories = 5;
        final int numGroups = 10;
        double offset;
        int numV = 100;
        final int originalNumV = numV;
        for (int i = 0; i < numV; i++) {
            final JanusGraphVertex v = tx.addVertex();
            v.property(VertexProperty.Cardinality.single, "uid", i);
            v.property(VertexProperty.Cardinality.single, "category", i % numCategories);
            v.property(VertexProperty.Cardinality.single, "group", i % numGroups);
            v.property(VertexProperty.Cardinality.single, "text", "Vertex " + words[i % words.length]);
            v.property(VertexProperty.Cardinality.single, "name", words[i % words.length]);
            v.property(VertexProperty.Cardinality.single, "time", i);
            offset = (i % 2 == 0 ? 1 : -1) * (i * 50.0 / numV);
            v.property(VertexProperty.Cardinality.single, "location", Geoshape.point(0.0 + offset, 0.0 + offset));
            if (i % 2 == 0) {
                v.property(VertexProperty.Cardinality.single, "boundary", Geoshape.line(Arrays.asList(new double[][] {
                        {offset-0.1, offset-0.1}, {offset+0.1, offset-0.1}, {offset+0.1, offset+0.1}, {offset-0.1, offset+0.1}})));
            } else {
                v.property(VertexProperty.Cardinality.single, "boundary", Geoshape.polygon(Arrays.asList(new double[][]
                        {{offset-0.1,offset-0.1},{offset+0.1,offset-0.1},{offset+0.1,offset+0.1},{offset-0.1,offset+0.1},{offset-0.1,offset-0.1}})));
            }
            final Edge e = v.addEdge("knows", getVertex("uid", Math.max(0, i - 1)));
            e.property("text", "Vertex " + words[i % words.length]);
            e.property("name",words[i % words.length]);
            e.property("time", i);
            e.property("category", i % numCategories);
            e.property("group", i % numGroups);
            e.property("location", Geoshape.point(0.0 + offset, 0.0 + offset));
            if (i % 2 == 0) {
                e.property("boundary", Geoshape.line(Arrays.asList(new double[][] {
                        {offset-0.1, offset-0.1}, {offset+0.1, offset-0.1}, {offset+0.1, offset+0.1}, {offset-0.1, offset+0.1}})));
            } else {
                e.property("boundary", Geoshape.polygon(Arrays.asList(new double[][]
                        {{offset-0.1,offset-0.1},{offset+0.1,offset-0.1},{offset+0.1,offset+0.1},{offset-0.1,offset+0.1},{offset-0.1,offset-0.1}})));
            }
        }

        checkIndexingCounts(words, numV, originalNumV, true);

        //--------------

        // some indexing backends may guarantee only eventual consistency
        for (int retry = 0, status = 1; retry < RETRY_COUNT && status > 0; retry++) {
            clopen();
            try {
                checkIndexingCounts(words, numV, originalNumV, true);
                status = 0;
            } catch (final AssertionError e) {
                if (retry >= RETRY_COUNT-1) throw e;
                Thread.sleep(RETRY_INTERVAL);
            }
        }

        newTx();

        final int numDelete = 12;
        for (int i = numV - numDelete; i < numV; i++) {
            getVertex("uid", i).remove();
        }

        numV -= numDelete;

        checkIndexingCounts(words, numV, originalNumV, false);
    }

    private void checkIndexingCounts(String[] words, int numV, int originalNumV, boolean checkOrder) {
        for (final String word : words) {
            final int expectedSize = numV / words.length;
            assertCount(expectedSize, tx.query().has("text", Text.CONTAINS, word).vertices());
            assertCount(expectedSize, tx.query().has("text", Text.CONTAINS, word).edges());

            //Test ordering
            if (checkOrder) {
                for (final String orderKey : new String[]{"time", "category"}) {
                    for (final Order order : Order.values()) {
                        for (final JanusGraphQuery traversal : ImmutableList.of(
                            tx.query().has("text", Text.CONTAINS, word).orderBy(orderKey, order.getTP()),
                            tx.query().has("text", Text.CONTAINS, word).orderBy(orderKey, order.getTP())
                        )) {
                            verifyElementOrder(traversal.vertices(), orderKey, order, expectedSize);
                        }
                    }
                }
            }
        }

        assertCount(3, tx.query().has("group", 3).orderBy("time", incr).limit(3).vertices());
        assertCount(3, tx.query().has("group", 3).orderBy("time", decr).limit(3).edges());

        for (int i = 0; i < numV / 2; i += numV / 10) {
            assertCount(i, tx.query().has("time", Cmp.GREATER_THAN_EQUAL, i).has("time", Cmp.LESS_THAN, i + i).vertices());
            assertCount(i, tx.query().has("time", Cmp.GREATER_THAN_EQUAL, i).has("time", Cmp.LESS_THAN, i + i).edges());
        }

        for (int i = 0; i < numV; i += 5) {
            testGeo(i, originalNumV, numV);
        }

        //Queries combining mixed and composite indexes
        assertCount(4, tx.query().has("category", 1).interval("time", 10, 28).vertices());
        assertCount(4, tx.query().has("category", 1).interval("time", 10, 28).edges());

        assertCount(5, tx.query().has("time", Cmp.GREATER_THAN_EQUAL, 10).has("time", Cmp.LESS_THAN, 30).has("text", Text.CONTAINS, words[0]).vertices());
        final double offset = (19 * 50.0 / originalNumV);
        final double distance = Geoshape.point(0.0, 0.0).getPoint().distance(Geoshape.point(offset, offset).getPoint()) + 20;
        assertCount(5, tx.query().has("location", Geo.INTERSECT, Geoshape.circle(0.0, 0.0, distance)).has("text", Text.CONTAINS, words[0]).vertices());
        assertCount(5, tx.query().has("boundary", Geo.INTERSECT, Geoshape.circle(0.0, 0.0, distance)).has("text", Text.CONTAINS, words[0]).vertices());

        assertCount(numV, tx.query().vertices());
        assertCount(numV, tx.query().edges());

        assertCount(numV/words.length, tx.query().has("name", Cmp.GREATER_THAN_EQUAL, "world").vertices());
        assertCount(numV/words.length, tx.query().has("name", Cmp.GREATER_THAN_EQUAL, "world").edges());

        assertCount(0, tx.query().has("name", Cmp.GREATER_THAN, "world").vertices());
        assertCount(0, tx.query().has("name", Cmp.GREATER_THAN, "world").edges());

        assertCount(numV-numV/words.length, tx.query().has("name", Cmp.LESS_THAN, "world").vertices());
        assertCount(numV-numV/words.length, tx.query().has("name", Cmp.LESS_THAN, "world").edges());

        assertCount(numV, tx.query().has("name", Cmp.LESS_THAN_EQUAL, "world").vertices());
        assertCount(numV, tx.query().has("name", Cmp.LESS_THAN_EQUAL, "world").edges());
    }

    /**
     * Tests indexing boolean
     */
    @Test
    public void testBooleanIndexing() {
        final PropertyKey name = makeKey("visible", Boolean.class);
        mgmt.buildIndex("booleanIndex", Vertex.class).
                addKey(name).buildMixedIndex(INDEX);
        finishSchema();
        clopen();

        final JanusGraphVertex v1 = graph.addVertex();
        v1.property("visible", true);

        final JanusGraphVertex v2 = graph.addVertex();
        v2.property("visible", false);

        assertCount(2, graph.vertices());
        assertEquals(v1, getOnlyVertex(graph.query().has("visible", true)));
        assertEquals(v2, getOnlyVertex(graph.query().has("visible", false)));
        assertEquals(v2, getOnlyVertex(graph.query().has("visible", Cmp.NOT_EQUAL, true)));
        assertEquals(v1, getOnlyVertex(graph.query().has("visible", Cmp.NOT_EQUAL, false)));

        clopen();//Flush the index
        assertCount(2, graph.vertices());
        assertEquals(v1, getOnlyVertex(graph.query().has("visible", true)));
        assertEquals(v2, getOnlyVertex(graph.query().has("visible", false)));
        assertEquals(v2, getOnlyVertex(graph.query().has("visible", Cmp.NOT_EQUAL, true)));
        assertEquals(v1, getOnlyVertex(graph.query().has("visible", Cmp.NOT_EQUAL, false)));
    }


    /**
     * Tests indexing dates
     */
    @Test
    public void testDateIndexing() {
        final PropertyKey name = makeKey("date", Date.class);
        mgmt.buildIndex("dateIndex", Vertex.class).
                addKey(name).buildMixedIndex(INDEX);
        finishSchema();
        clopen();

        final JanusGraphVertex v1 = graph.addVertex();
        v1.property("date", new Date(1));

        final JanusGraphVertex v2 = graph.addVertex();
        v2.property("date", new Date(2000));


        assertEquals(v1, getOnlyVertex(graph.query().has("date", Cmp.EQUAL, new Date(1))));
        assertEquals(v2, getOnlyVertex(graph.query().has("date", Cmp.GREATER_THAN, new Date(1))));
        assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("date", Cmp.GREATER_THAN_EQUAL, new Date(1)).vertices()));
        assertEquals(v1, getOnlyVertex(graph.query().has("date", Cmp.LESS_THAN, new Date(2000))));
        assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("date", Cmp.LESS_THAN_EQUAL, new Date(2000)).vertices()));
        assertEquals(v2, getOnlyVertex(graph.query().has("date", Cmp.NOT_EQUAL, new Date(1))));

        clopen();//Flush the index
        assertEquals(v1, getOnlyVertex(graph.query().has("date", Cmp.EQUAL, new Date(1))));
        assertEquals(v2, getOnlyVertex(graph.query().has("date", Cmp.GREATER_THAN, new Date(1))));
        assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("date", Cmp.GREATER_THAN_EQUAL, new Date(1)).vertices()));
        assertEquals(v1, getOnlyVertex(graph.query().has("date", Cmp.LESS_THAN, new Date(2000))));
        assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("date", Cmp.LESS_THAN_EQUAL, new Date(2000)).vertices()));
        assertEquals(v2, getOnlyVertex(graph.query().has("date", Cmp.NOT_EQUAL, new Date(1))));


    }


    /**
     * Tests indexing instants
     */
    @Test
    public void testInstantIndexing() {
        final PropertyKey name = makeKey("instant", Instant.class);
        mgmt.buildIndex("instantIndex", Vertex.class).
                addKey(name).buildMixedIndex(INDEX);
        finishSchema();
        clopen();
        Instant firstTimestamp = Instant.ofEpochMilli(1);
        final Instant secondTimestamp = Instant.ofEpochMilli(2000);

        JanusGraphVertex v1 = graph.addVertex();
        v1.property("instant", firstTimestamp);

        final JanusGraphVertex v2 = graph.addVertex();
        v2.property("instant", secondTimestamp);

        testInstant(firstTimestamp, secondTimestamp, v1, v2);

        firstTimestamp = Instant.ofEpochSecond(0, 1);
        v1 = (JanusGraphVertex) graph.vertices(v1.id()).next();
        v1.property("instant", firstTimestamp);
        if (indexFeatures.supportsNanoseconds()) {
            testInstant(firstTimestamp, secondTimestamp, v1, v2);
        } else {
            clopen();//Flush the index
            try {
                assertEquals(v1, getOnlyVertex(graph.query().has("instant", Cmp.EQUAL, firstTimestamp)));
                fail("Should have failed to update the index");
            } catch (final Exception ignored) {

            }
        }

    }

    private void testInstant(Instant firstTimestamp, Instant secondTimestamp, JanusGraphVertex v1, JanusGraphVertex v2) {
        assertEquals(v1, getOnlyVertex(graph.query().has("instant", Cmp.EQUAL, firstTimestamp)));
        assertEquals(v2, getOnlyVertex(graph.query().has("instant", Cmp.GREATER_THAN, firstTimestamp)));
        assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("instant", Cmp.GREATER_THAN_EQUAL, firstTimestamp).vertices()));
        assertEquals(v1, getOnlyVertex(graph.query().has("instant", Cmp.LESS_THAN, secondTimestamp)));
        assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("instant", Cmp.LESS_THAN_EQUAL, secondTimestamp).vertices()));
        assertEquals(v2, getOnlyVertex(graph.query().has("instant", Cmp.NOT_EQUAL, firstTimestamp)));


        clopen();//Flush the index
        assertEquals(v1, getOnlyVertex(graph.query().has("instant", Cmp.EQUAL, firstTimestamp)));
        assertEquals(v2, getOnlyVertex(graph.query().has("instant", Cmp.GREATER_THAN, firstTimestamp)));
        assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("instant", Cmp.GREATER_THAN_EQUAL, firstTimestamp).vertices()));
        assertEquals(v1, getOnlyVertex(graph.query().has("instant", Cmp.LESS_THAN, secondTimestamp)));
        assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("instant", Cmp.LESS_THAN_EQUAL, secondTimestamp).vertices()));
        assertEquals(v2, getOnlyVertex(graph.query().has("instant", Cmp.NOT_EQUAL, firstTimestamp)));
    }

    /**
     * Tests indexing boolean
     */
    @Test
    public void testUUIDIndexing() {
        final PropertyKey name = makeKey("uid", UUID.class);
        mgmt.buildIndex("uuidIndex", Vertex.class).
                addKey(name).buildMixedIndex(INDEX);
        finishSchema();
        clopen();

        final UUID uid1 = UUID.randomUUID();
        final UUID uid2 = UUID.randomUUID();

        final JanusGraphVertex v1 = graph.addVertex();
        v1.property("uid", uid1);

        final JanusGraphVertex v2 = graph.addVertex();
        v2.property("uid", uid2);

        assertCount(2, graph.query().vertices());
        assertEquals(v1, getOnlyVertex(graph.query().has("uid", uid1)));
        assertEquals(v2, getOnlyVertex(graph.query().has("uid", uid2)));

        assertEquals(v2, getOnlyVertex(graph.query().has("uid", Cmp.NOT_EQUAL, uid1)));
        assertEquals(v1, getOnlyVertex(graph.query().has("uid", Cmp.NOT_EQUAL, uid2)));

        clopen();//Flush the index
        assertCount(2, graph.query().vertices());
        assertEquals(v1, getOnlyVertex(graph.query().has("uid", uid1)));
        assertEquals(v2, getOnlyVertex(graph.query().has("uid", uid2)));

        assertEquals(v2, getOnlyVertex(graph.query().has("uid", Cmp.NOT_EQUAL, uid1)));
        assertEquals(v1, getOnlyVertex(graph.query().has("uid", Cmp.NOT_EQUAL, uid2)));

    }


    /**
     * Tests conditional indexing and the different management features
     */
    @Test
    public void testConditionalIndexing() {
        PropertyKey name = makeKey("name", String.class);
        PropertyKey weight = makeKey("weight", Double.class);
        PropertyKey text = makeKey("text", String.class);

        VertexLabel person = mgmt.makeVertexLabel("person").make();
        VertexLabel org = mgmt.makeVertexLabel("org").make();

        JanusGraphIndex index1 = mgmt.buildIndex("index1", Vertex.class).
                addKey(name, getStringMapping()).buildMixedIndex(INDEX);
        JanusGraphIndex index2 = mgmt.buildIndex("index2", Vertex.class).indexOnly(person).
                addKey(text, getTextMapping()).addKey(weight).buildMixedIndex(INDEX);
        JanusGraphIndex index3 = mgmt.buildIndex("index3", Vertex.class).indexOnly(org).
                addKey(text, getTextMapping()).addKey(weight).buildMixedIndex(INDEX);

        // ########### INSPECTION & FAILURE ##############
        assertTrue(mgmt.containsGraphIndex("index1"));
        assertFalse(mgmt.containsGraphIndex("index"));
        assertCount(3, mgmt.getGraphIndexes(Vertex.class));
        assertNull(mgmt.getGraphIndex("indexx"));

        name = mgmt.getPropertyKey("name");
        weight = mgmt.getPropertyKey("weight");
        text = mgmt.getPropertyKey("text");
        person = mgmt.getVertexLabel("person");
        org = mgmt.getVertexLabel("org");
        index1 = mgmt.getGraphIndex("index1");
        index2 = mgmt.getGraphIndex("index2");
        index3 = mgmt.getGraphIndex("index3");

        assertTrue(Vertex.class.isAssignableFrom(index1.getIndexedElement()));
        assertEquals("index2", index2.name());
        assertEquals(INDEX, index3.getBackingIndex());
        assertFalse(index2.isUnique());
        assertEquals(2, index3.getFieldKeys().length);
        assertEquals(1, index1.getFieldKeys().length);
        assertEquals(3, index3.getParametersFor(text).length);
        assertEquals(2, index3.getParametersFor(weight).length);

        try {
            //Already exists
            mgmt.buildIndex("index2", Vertex.class).addKey(weight).buildMixedIndex(INDEX);
            fail();
        } catch (final IllegalArgumentException ignored) {
        }
        try {
            //Already exists
            mgmt.buildIndex("index2", Vertex.class).addKey(weight).buildCompositeIndex();
            fail();
        } catch (final IllegalArgumentException ignored) {
        }
        try {
            //Key is already added
            mgmt.addIndexKey(index2, weight);
            fail();
        } catch (final IllegalArgumentException ignored) {
        }

        finishSchema();
        clopen();

        // ########### INSPECTION & FAILURE (copied from above) ##############
        assertTrue(mgmt.containsGraphIndex("index1"));
        assertFalse(mgmt.containsGraphIndex("index"));
        assertCount(3, mgmt.getGraphIndexes(Vertex.class));
        assertNull(mgmt.getGraphIndex("indexx"));

        name = mgmt.getPropertyKey("name");
        weight = mgmt.getPropertyKey("weight");
        text = mgmt.getPropertyKey("text");
        person = mgmt.getVertexLabel("person");
        org = mgmt.getVertexLabel("org");
        index1 = mgmt.getGraphIndex("index1");
        index2 = mgmt.getGraphIndex("index2");
        index3 = mgmt.getGraphIndex("index3");

        assertTrue(Vertex.class.isAssignableFrom(index1.getIndexedElement()));
        assertEquals("index2", index2.name());
        assertEquals(INDEX, index3.getBackingIndex());
        assertFalse(index2.isUnique());
        assertEquals(2, index3.getFieldKeys().length);
        assertEquals(1, index1.getFieldKeys().length);
        assertEquals(3, index3.getParametersFor(text).length);
        assertEquals(2, index3.getParametersFor(weight).length);

        try {
            //Already exists
            mgmt.buildIndex("index2", Vertex.class).addKey(weight).buildMixedIndex(INDEX);
            fail();
        } catch (final IllegalArgumentException ignored) {
        }
        try {
            //Already exists
            mgmt.buildIndex("index2", Vertex.class).addKey(weight).buildCompositeIndex();
            fail();
        } catch (final IllegalArgumentException ignored) {
        }
        try {
            //Key is already added
            mgmt.addIndexKey(index2, weight);
            fail();
        } catch (final IllegalArgumentException ignored) {
        }


        // ########### TRANSACTIONAL ##############
        weight = tx.getPropertyKey("weight");


        final int numV = 200;
        final String[] strings = {"houseboat", "humanoid", "differential", "extraordinary"};
        final String[] stringsTwo = new String[strings.length];
        for (int i = 0; i < strings.length; i++) stringsTwo[i] = strings[i] + " " + strings[i];
        final int modulo = 5;
        assert numV % (modulo * strings.length * 2) == 0;

        for (int i = 0; i < numV; i++) {
            final JanusGraphVertex v = tx.addVertex(i % 2 == 0 ? "person" : "org");
            v.property("name", strings[i % strings.length]);
            v.property("text", strings[i % strings.length]);
            v.property("weight", (i % modulo) + 0.5);
        }

        //########## QUERIES ################
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, index2.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]).has(LABEL_NAME, Cmp.EQUAL, "person").orderBy("weight", decr), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, weight, Order.DESC, index2.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[3]).has(LABEL_NAME, Cmp.EQUAL, "org"), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, index3.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[1]).has(LABEL_NAME, Cmp.EQUAL, "org").orderBy("weight", decr), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, weight, Order.DESC, index3.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]).has("weight", Cmp.EQUAL, 2.5).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX,
                numV / (modulo * strings.length), new boolean[]{true, true}, index2.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[2]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{false, true}, index1.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[3]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX,
                0, new boolean[]{false, true}, index1.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[0]), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, index1.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[2]).has("text", Text.CONTAINS, strings[2]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, index1.name(), index2.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[0]).has("text", Text.CONTAINS, strings[0]).has(LABEL_NAME, Cmp.EQUAL, "person").orderBy("weight", incr), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, weight, Order.ASC, index1.name(), index2.name());

        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{false, true});
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]).orderBy("weight", incr), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{false, false}, weight, Order.ASC);

        clopen();
        weight = tx.getPropertyKey("weight");

        //########## QUERIES (copied from above) ################
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, index2.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]).has(LABEL_NAME, Cmp.EQUAL, "person").orderBy("weight", decr), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, weight, Order.DESC, index2.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[3]).has(LABEL_NAME, Cmp.EQUAL, "org"), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, index3.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[1]).has(LABEL_NAME, Cmp.EQUAL, "org").orderBy("weight", decr), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, weight, Order.DESC, index3.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]).has("weight", Cmp.EQUAL, 2.5).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX,
                numV / (modulo * strings.length), new boolean[]{true, true}, index2.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[2]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{false, true}, index1.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[3]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX,
                0, new boolean[]{false, true}, index1.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[0]), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, index1.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[2]).has("text", Text.CONTAINS, strings[2]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, index1.name(), index2.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[0]).has("text", Text.CONTAINS, strings[0]).has(LABEL_NAME, Cmp.EQUAL, "person").orderBy("weight", incr), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, weight, Order.ASC, index1.name(), index2.name());

        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{false, true});
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]).orderBy("weight", incr), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{false, false}, weight, Order.ASC);
    }

    @Test
    public void testCompositeAndMixedIndexing() {
        final PropertyKey name = makeKey("name", String.class);
        final PropertyKey weight = makeKey("weight", Double.class);
        final PropertyKey text = makeKey("text", String.class);
        makeKey("flag", Boolean.class);

        final JanusGraphIndex composite = mgmt.buildIndex("composite", Vertex.class).addKey(name).addKey(weight).buildCompositeIndex();
        final JanusGraphIndex mixed = mgmt.buildIndex("mixed", Vertex.class).addKey(weight)
                .addKey(text, getTextMapping()).buildMixedIndex(INDEX);
        mixed.name();
        composite.name();
        finishSchema();

        final int numV = 100;
        final String[] strings = {"houseboat", "humanoid", "differential", "extraordinary"};
        final String[] stringsTwo = new String[strings.length];
        for (int i = 0; i < strings.length; i++) stringsTwo[i] = strings[i] + " " + strings[i];
        final int modulo = 5;
        final int divisor = modulo * strings.length;

        for (int i = 0; i < numV; i++) {
            final JanusGraphVertex v = tx.addVertex();
            v.property("name", strings[i % strings.length]);
            v.property("text", strings[i % strings.length]);
            v.property("weight", (i % modulo) + 0.5);
            v.property("flag", true);
        }

        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[0]), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{false, true});
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, mixed.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]).has("flag"), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{false, true}, mixed.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[0]).has("weight", Cmp.EQUAL, 1.5), ElementCategory.VERTEX,
                numV / divisor, new boolean[]{true, true}, composite.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[0]).has("weight", Cmp.EQUAL, 1.5).has("flag"), ElementCategory.VERTEX,
                numV / divisor, new boolean[]{false, true}, composite.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[2]).has("weight", Cmp.EQUAL, 2.5), ElementCategory.VERTEX,
                numV / divisor, new boolean[]{true, true}, mixed.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[2]).has("weight", Cmp.EQUAL, 2.5).has("flag"), ElementCategory.VERTEX,
                numV / divisor, new boolean[]{false, true}, mixed.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[3]).has("name", Cmp.EQUAL, strings[3]).has("weight", Cmp.EQUAL, 3.5), ElementCategory.VERTEX,
                numV / divisor, new boolean[]{true, true}, mixed.name(), composite.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[3]).has("name", Cmp.EQUAL, strings[3]).has("weight", Cmp.EQUAL, 3.5).has("flag"), ElementCategory.VERTEX,
                numV / divisor, new boolean[]{false, true}, mixed.name(), composite.name());

        clopen();

        //Same queries as above
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[0]), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{false, true});
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{true, true}, mixed.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[0]).has("flag"), ElementCategory.VERTEX,
                numV / strings.length, new boolean[]{false, true}, mixed.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[0]).has("weight", Cmp.EQUAL, 1.5), ElementCategory.VERTEX,
                numV / divisor, new boolean[]{true, true}, composite.name());
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, strings[0]).has("weight", Cmp.EQUAL, 1.5).has("flag"), ElementCategory.VERTEX,
                numV / divisor, new boolean[]{false, true}, composite.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[2]).has("weight", Cmp.EQUAL, 2.5), ElementCategory.VERTEX,
                numV / divisor, new boolean[]{true, true}, mixed.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[2]).has("weight", Cmp.EQUAL, 2.5).has("flag"), ElementCategory.VERTEX,
                numV / divisor, new boolean[]{false, true}, mixed.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[3]).has("name", Cmp.EQUAL, strings[3]).has("weight", Cmp.EQUAL, 3.5), ElementCategory.VERTEX,
                numV / divisor, new boolean[]{true, true}, mixed.name(), composite.name());
        evaluateQuery(tx.query().has("text", Text.CONTAINS, strings[3]).has("name", Cmp.EQUAL, strings[3]).has("weight", Cmp.EQUAL, 3.5).has("flag"), ElementCategory.VERTEX,
                numV / divisor, new boolean[]{false, true}, mixed.name(), composite.name());

    }


    private void setupChainGraph(int numV, String[] strings, boolean sameNameMapping) {
        clopen(option(INDEX_NAME_MAPPING, INDEX), sameNameMapping);
        final JanusGraphIndex vindex = getExternalIndex(Vertex.class, INDEX);
        final JanusGraphIndex eindex = getExternalIndex(Edge.class, INDEX);
        final JanusGraphIndex pindex = getExternalIndex(JanusGraphVertexProperty.class, INDEX);
        final PropertyKey name = makeKey("name", String.class);

        mgmt.addIndexKey(vindex, name, getStringMapping());
        mgmt.addIndexKey(eindex, name, getStringMapping());
        mgmt.addIndexKey(pindex, name, getStringMapping(), Parameter.of("mapped-name", "xstr"));
        final PropertyKey text = makeKey("text", String.class);
        mgmt.addIndexKey(vindex, text, getTextMapping(), Parameter.of("mapped-name", "xtext"));
        mgmt.addIndexKey(eindex, text, getTextMapping());
        mgmt.addIndexKey(pindex, text, getTextMapping());
        mgmt.makeEdgeLabel("knows").signature(name).make();
        mgmt.makePropertyKey("uid").dataType(String.class).signature(text).make();
        finishSchema();
        JanusGraphVertex previous = null;
        for (int i = 0; i < numV; i++) {
            final JanusGraphVertex v = graph.addVertex("name", strings[i % strings.length], "text", strings[i % strings.length]);
            v.addEdge("knows", previous == null ? v : previous,
                    "name", strings[i % strings.length], "text", strings[i % strings.length]);
            v.property("uid", "v" + i,
                    "name", strings[i % strings.length], "text", strings[i % strings.length]);
            previous = v;
        }
    }

    /**
     * Tests index parameters (mapping and names) and various string predicates
     */
    @Test
    public void testIndexParameters() {
        final int numV = 1000;
        final String[] strings = {"Uncle Berry has a farm", "and on his farm he has five ducks", "ducks are beautiful animals", "the sky is very blue today"};
        setupChainGraph(numV, strings, false);

        evaluateQuery(graph.query().has("text", Text.CONTAINS, "ducks"),
                ElementCategory.VERTEX, numV / strings.length * 2, new boolean[]{true, true}, VINDEX);
        assertCount(numV / strings.length * 2, graph.query().has("text", Text.CONTAINS, "ducks").vertices());
        assertCount(numV / strings.length * 2, graph.query().has("text", Text.CONTAINS, "farm").vertices());
        assertCount(numV / strings.length, graph.query().has("text", Text.CONTAINS, "beautiful").vertices());
        evaluateQuery(graph.query().has("text", Text.CONTAINS_PREFIX, "beauti"),
                ElementCategory.VERTEX, numV / strings.length, new boolean[]{true, true}, VINDEX);
        assertCount(numV / strings.length, graph.query().has("text", Text.CONTAINS_REGEX, "be[r]+y").vertices());
        assertCount(0, graph.query().has("text", Text.CONTAINS, "lolipop").vertices());
        assertCount(numV / strings.length, graph.query().has("name", Cmp.EQUAL, strings[1]).vertices());
        assertCount(numV / strings.length, graph.query().has("name", Cmp.EQUAL, strings[1]).vertices());
        assertCount(numV / strings.length * (strings.length - 1), graph.query().has("name", Cmp.NOT_EQUAL, strings[2]).vertices());
        assertCount(0, graph.query().has("name", Cmp.EQUAL, "farm").vertices());
        assertCount(numV / strings.length, graph.query().has("name", Text.PREFIX, "ducks").vertices());
        assertCount(numV / strings.length * 2, graph.query().has("name", Text.REGEX, "(.*)ducks(.*)").vertices());

        //Same queries for edges
        evaluateQuery(graph.query().has("text", Text.CONTAINS, "ducks"),
                ElementCategory.EDGE, numV / strings.length * 2, new boolean[]{true, true}, EINDEX);
        assertCount(numV / strings.length * 2, graph.query().has("text", Text.CONTAINS, "ducks").edges());
        assertCount(numV / strings.length * 2, graph.query().has("text", Text.CONTAINS, "farm").edges());
        assertCount(numV / strings.length, graph.query().has("text", Text.CONTAINS, "beautiful").edges());
        evaluateQuery(graph.query().has("text", Text.CONTAINS_PREFIX, "beauti"),
                ElementCategory.EDGE, numV / strings.length, new boolean[]{true, true}, EINDEX);
        assertCount(numV / strings.length, graph.query().has("text", Text.CONTAINS_REGEX, "be[r]+y").edges());
        assertCount(0, graph.query().has("text", Text.CONTAINS, "lolipop").edges());
        assertCount(numV / strings.length, graph.query().has("name", Cmp.EQUAL, strings[1]).edges());
        assertCount(numV / strings.length, graph.query().has("name", Cmp.EQUAL, strings[1]).edges());
        assertCount(numV / strings.length * (strings.length - 1), graph.query().has("name", Cmp.NOT_EQUAL, strings[2]).edges());
        assertCount(0, graph.query().has("name", Cmp.EQUAL, "farm").edges());
        assertCount(numV / strings.length, graph.query().has("name", Text.PREFIX, "ducks").edges());
        assertCount(numV / strings.length * 2, graph.query().has("name", Text.REGEX, "(.*)ducks(.*)").edges());

        //Same queries for properties
        evaluateQuery(graph.query().has("text", Text.CONTAINS, "ducks"),
                ElementCategory.PROPERTY, numV / strings.length * 2, new boolean[]{true, true}, PINDEX);
        assertCount(numV / strings.length * 2, graph.query().has("text", Text.CONTAINS, "ducks").properties());
        assertCount(numV / strings.length * 2, graph.query().has("text", Text.CONTAINS, "farm").properties());
        assertCount(numV / strings.length, graph.query().has("text", Text.CONTAINS, "beautiful").properties());
        evaluateQuery(graph.query().has("text", Text.CONTAINS_PREFIX, "beauti"),
                ElementCategory.PROPERTY, numV / strings.length, new boolean[]{true, true}, PINDEX);
        assertCount(numV / strings.length, graph.query().has("text", Text.CONTAINS_REGEX, "be[r]+y").properties());
        assertCount(0, graph.query().has("text", Text.CONTAINS, "lolipop").properties());
        assertCount(numV / strings.length, graph.query().has("name", Cmp.EQUAL, strings[1]).properties());
        assertCount(numV / strings.length, graph.query().has("name", Cmp.EQUAL, strings[1]).properties());
        assertCount(numV / strings.length * (strings.length - 1), graph.query().has(LABEL_NAME, "uid").has("name", Cmp.NOT_EQUAL, strings[2]).properties());
        assertCount(0, graph.query().has("name", Cmp.EQUAL, "farm").properties());
        assertCount(numV / strings.length, graph.query().has("name", Text.PREFIX, "ducks").properties());
        assertCount(numV / strings.length * 2, graph.query().has("name", Text.REGEX, "(.*)ducks(.*)").properties());


        clopen();
        /* =======================================
        Same queries as above but against backend */

        evaluateQuery(graph.query().has("text", Text.CONTAINS, "ducks"),
                ElementCategory.VERTEX, numV / strings.length * 2, new boolean[]{true, true}, VINDEX);
        assertCount(numV / strings.length * 2, graph.query().has("text", Text.CONTAINS, "ducks").vertices());
        assertCount(numV / strings.length * 2, graph.query().has("text", Text.CONTAINS, "farm").vertices());
        assertCount(numV / strings.length, graph.query().has("text", Text.CONTAINS, "beautiful").vertices());
        evaluateQuery(graph.query().has("text", Text.CONTAINS_PREFIX, "beauti"),
                ElementCategory.VERTEX, numV / strings.length, new boolean[]{true, true}, VINDEX);
        assertCount(numV / strings.length, graph.query().has("text", Text.CONTAINS_REGEX, "be[r]+y").vertices());
        assertCount(0, graph.query().has("text", Text.CONTAINS, "lolipop").vertices());
        assertCount(numV / strings.length, graph.query().has("name", Cmp.EQUAL, strings[1]).vertices());
        assertCount(numV / strings.length, graph.query().has("name", Cmp.EQUAL, strings[1]).vertices());
        assertCount(numV / strings.length * (strings.length - 1), graph.query().has("name", Cmp.NOT_EQUAL, strings[2]).vertices());
        assertCount(0, graph.query().has("name", Cmp.EQUAL, "farm").vertices());
        assertCount(numV / strings.length, graph.query().has("name", Text.PREFIX, "ducks").vertices());
        assertCount(numV / strings.length * 2, graph.query().has("name", Text.REGEX, "(.*)ducks(.*)").vertices());

        //Same queries for edges
        evaluateQuery(graph.query().has("text", Text.CONTAINS, "ducks"),
                ElementCategory.EDGE, numV / strings.length * 2, new boolean[]{true, true}, EINDEX);
        assertCount(numV / strings.length * 2, graph.query().has("text", Text.CONTAINS, "ducks").edges());
        assertCount(numV / strings.length * 2, graph.query().has("text", Text.CONTAINS, "farm").edges());
        assertCount(numV / strings.length, graph.query().has("text", Text.CONTAINS, "beautiful").edges());
        evaluateQuery(graph.query().has("text", Text.CONTAINS_PREFIX, "beauti"),
                ElementCategory.EDGE, numV / strings.length, new boolean[]{true, true}, EINDEX);
        assertCount(numV / strings.length, graph.query().has("text", Text.CONTAINS_REGEX, "be[r]+y").edges());
        assertCount(0, graph.query().has("text", Text.CONTAINS, "lolipop").edges());
        assertCount(numV / strings.length, graph.query().has("name", Cmp.EQUAL, strings[1]).edges());
        assertCount(numV / strings.length, graph.query().has("name", Cmp.EQUAL, strings[1]).edges());
        assertCount(numV / strings.length * (strings.length - 1), graph.query().has("name", Cmp.NOT_EQUAL, strings[2]).edges());
        assertCount(0, graph.query().has("name", Cmp.EQUAL, "farm").edges());
        assertCount(numV / strings.length, graph.query().has("name", Text.PREFIX, "ducks").edges());
        assertCount(numV / strings.length * 2, graph.query().has("name", Text.REGEX, "(.*)ducks(.*)").edges());

        //Same queries for properties
        evaluateQuery(graph.query().has("text", Text.CONTAINS, "ducks"),
                ElementCategory.PROPERTY, numV / strings.length * 2, new boolean[]{true, true}, PINDEX);
        assertCount(numV / strings.length * 2, graph.query().has("text", Text.CONTAINS, "ducks").properties());
        assertCount(numV / strings.length * 2, graph.query().has("text", Text.CONTAINS, "farm").properties());
        assertCount(numV / strings.length, graph.query().has("text", Text.CONTAINS, "beautiful").properties());
        evaluateQuery(graph.query().has("text", Text.CONTAINS_PREFIX, "beauti"),
                ElementCategory.PROPERTY, numV / strings.length, new boolean[]{true, true}, PINDEX);
        assertCount(numV / strings.length, graph.query().has("text", Text.CONTAINS_REGEX, "be[r]+y").properties());
        assertCount(0, graph.query().has("text", Text.CONTAINS, "lolipop").properties());
        assertCount(numV / strings.length, graph.query().has("name", Cmp.EQUAL, strings[1]).properties());
        assertCount(numV / strings.length, graph.query().has("name", Cmp.EQUAL, strings[1]).properties());
        assertCount(numV / strings.length * (strings.length - 1), graph.query().has(LABEL_NAME, "uid").has("name", Cmp.NOT_EQUAL, strings[2]).properties());
        assertCount(0, graph.query().has("name", Cmp.EQUAL, "farm").properties());
        assertCount(numV / strings.length, graph.query().has("name", Text.PREFIX, "ducks").properties());
        assertCount(numV / strings.length * 2, graph.query().has("name", Text.REGEX, "(.*)ducks(.*)").properties());

        //Test name mapping
        if (supportsLuceneStyleQueries()) {
            assertCount(numV / strings.length * 2, graph.indexQuery(VINDEX, "xtext:ducks").vertexStream());
            assertCount(0, graph.indexQuery(EINDEX, "xtext:ducks").edgeStream());
        }
    }

    /**
     * Tests index parameters (mapping and names) with raw indexQuery
     */
    @Test
    public void testRawQueries() {
        if (!supportsLuceneStyleQueries()) return;

        final int numV = 1000;
        final String[] strings = {"Uncle Berry has a farm", "and on his farm he has five ducks", "ducks are beautiful animals", "the sky is very blue today"};
        setupChainGraph(numV, strings, true);
        clopen();

        assertCount(numV / strings.length * 2, graph.indexQuery(VINDEX, "v.text:ducks").vertexStream());
        assertCount(numV / strings.length * 2, graph.indexQuery(VINDEX, "v.text:(farm uncle berry)").vertexStream());
        assertCount(numV / strings.length, graph.indexQuery(VINDEX, "v.text:(farm uncle berry) AND v.name:\"Uncle Berry has a farm\"").vertexStream());
        assertCount(numV / strings.length * 2, graph.indexQuery(VINDEX, "v.text:(beautiful are ducks)").vertexStream());
        assertCount(numV / strings.length * 2 - 10, graph.indexQuery(VINDEX, "v.text:(beautiful are ducks)").offset(10).vertexStream());
        long total = graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(Integer.MAX_VALUE).vertexStream().count();
        assertCount(10, graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).vertexStream());
        assertEquals(total, (long) graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).vertexTotals());
        assertCount(10, graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).offset(10).vertexStream());
        assertEquals(total, (long) graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).offset(10).vertexTotals());
        assertCount(0, graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).offset(numV).vertexStream());
        assertEquals(total, (long) graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).offset(numV).vertexTotals());
        //Test name mapping
        assertCount(numV / strings.length * 2, graph.indexQuery(VINDEX, "xtext:ducks").vertexStream());
        assertCount(0, graph.indexQuery(VINDEX, "text:ducks").vertexStream());
        //Test custom element identifier
        assertCount(numV / strings.length * 2, graph.indexQuery(VINDEX, "$v$text:ducks").setElementIdentifier("$v$").vertexStream());
        //assertCount(0, graph.indexQuery(VINDEX, "v.\"text\":ducks").setElementIdentifier("$v$").vertices()));

        //Same queries for edges
        assertCount(numV / strings.length * 2, graph.indexQuery(EINDEX, "e.text:ducks").edgeStream());
        total = graph.indexQuery(EINDEX, "e.text:ducks").limit(Integer.MAX_VALUE).edgeStream().count();
        assertEquals(total, (long) numV / strings.length * 2, graph.indexQuery(EINDEX, "e.text:ducks").edgeTotals());
        assertCount(numV / strings.length * 2, graph.indexQuery(EINDEX, "e.text:(farm uncle berry)").edgeStream());
        assertCount(numV / strings.length, graph.indexQuery(EINDEX, "e.text:(farm uncle berry) AND e.name:\"Uncle Berry has a farm\"").edgeStream());
        assertCount(numV / strings.length * 2, graph.indexQuery(EINDEX, "e.text:(beautiful are ducks)").edgeStream());
        assertCount(numV / strings.length * 2 - 10, graph.indexQuery(EINDEX, "e.text:(beautiful are ducks)").offset(10).edgeStream());
        total = graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(Integer.MAX_VALUE).edgeStream().count();
        assertCount(10, graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).edgeStream());
        assertEquals(total, (long) graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).edgeTotals());
        assertCount(10, graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).offset(10).edgeStream());
        assertEquals(total, (long) graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).offset(10).edgeTotals());
        assertCount(0, graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).offset(numV).edgeStream());
        assertEquals(total, (long) graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).offset(numV).edgeTotals());
        //Test name mapping
        assertCount(numV / strings.length * 2, graph.indexQuery(EINDEX, "text:ducks").edgeStream());

        //Same queries for properties
        assertCount(numV / strings.length * 2, graph.indexQuery(PINDEX, "p.text:ducks").propertyStream());
        total = graph.indexQuery(PINDEX, "p.text:ducks").limit(Integer.MAX_VALUE).propertyStream().count();
        assertEquals(total, (long) numV / strings.length * 2, graph.indexQuery(PINDEX, "p.text:ducks").propertyTotals());
        assertCount(numV / strings.length * 2, graph.indexQuery(PINDEX, "p.text:(farm uncle berry)").propertyStream());
        assertCount(numV / strings.length, graph.indexQuery(PINDEX, "p.text:(farm uncle berry) AND p.name:\"Uncle Berry has a farm\"").propertyStream());
        assertCount(numV / strings.length * 2, graph.indexQuery(PINDEX, "p.text:(beautiful are ducks)").propertyStream());
        assertCount(numV / strings.length * 2 - 10, graph.indexQuery(PINDEX, "p.text:(beautiful are ducks)").offset(10).propertyStream());
        total = graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(Integer.MAX_VALUE).propertyStream().count();
        assertCount(10, graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).propertyStream());
        assertEquals(total, (long) graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).propertyTotals());
        assertCount(10, graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).offset(10).propertyStream());
        assertEquals(total, (long) graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).offset(10).propertyTotals());
        assertCount(0, graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).offset(numV).propertyStream());
        assertEquals(total, (long) graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).offset(numV).propertyTotals());
        //Test name mapping
        assertCount(numV / strings.length * 2, graph.indexQuery(PINDEX, "text:ducks").propertyStream());
    }

    /**
     * Tests query parameters with raw indexQuery
     */
    @Test
    public void testRawQueriesWithParameters() {
        if (!supportsLuceneStyleQueries()) return;
        Parameter asc_sort_p = null;
        Parameter desc_sort_p = null;

        // ElasticSearch and Solr have different formats for sort parameters
        final String backend = readConfig.get(INDEX_BACKEND, INDEX);
        switch (backend) {
            case "elasticsearch":
                final Map sortAsc = new HashMap<>();
                sortAsc.put("_score", "asc");
                asc_sort_p = new Parameter("sort", Collections.singletonList(sortAsc));
                final Map sortDesc = new HashMap<>();
                sortDesc.put("_score", "desc");
                desc_sort_p = new Parameter("sort", Collections.singletonList(sortDesc));
                break;
            case "solr":
                asc_sort_p = new Parameter("sort", new String[]{"score asc"});
                desc_sort_p = new Parameter("sort", new String[]{"score desc"});
                break;
            case "lucene":
                return; // Ignore for lucene

            default:
                fail("Unknown index backend:" + backend);
                break;
        }

        final PropertyKey field1Key = mgmt.makePropertyKey("field1").dataType(String.class).make();
        mgmt.buildIndex("store1", Vertex.class).addKey(field1Key).buildMixedIndex(INDEX);
        mgmt.commit();

        final JanusGraphVertex v1 = tx.addVertex();
        final JanusGraphVertex v2 = tx.addVertex();
        final JanusGraphVertex v3 = tx.addVertex();

        v1.property("field1", "Hello Hello Hello Hello Hello Hello Hello Hello world");
        v2.property("field1", "Hello blue and yellow meet green");
        v3.property("field1", "Hello Hello world world");

        tx.commit();

        final List vertices = graph.indexQuery("store1", "v.field1:(Hello)")
            .addParameter(asc_sort_p).vertexStream()
            .map(JanusGraphIndexQuery.Result::getElement)
            .collect(Collectors.toList());
        assertNotEmpty(vertices);
        final AtomicInteger idx = new AtomicInteger(vertices.size() - 1);
        // Verify this query returns the items in reverse order.
        graph.indexQuery("store1", "v.field1:(Hello)").addParameter(desc_sort_p).vertexStream()
            .map(JanusGraphIndexQuery.Result::getElement)
            .forEachOrdered(e -> assertEquals(vertices.get(idx.getAndDecrement()), e));
    }

    @Test
    public void testDualMapping() {
        if (!indexFeatures.supportsStringMapping(Mapping.TEXTSTRING)) return;

        final PropertyKey name = makeKey("name", String.class);
        final JanusGraphIndex mixed = mgmt.buildIndex("mixed", Vertex.class).addKey(name, Mapping.TEXTSTRING.asParameter()).buildMixedIndex(INDEX);
        mixed.name();
        finishSchema();


        tx.addVertex("name", "Long John Don");
        tx.addVertex("name", "Long Little Lewis");
        tx.addVertex("name", "Middle Sister Mabel");

        clopen();
        evaluateQuery(tx.query().has("name", Cmp.EQUAL, "Long John Don"), ElementCategory.VERTEX,
                1, new boolean[]{true, true}, "mixed");
        evaluateQuery(tx.query().has("name", Text.CONTAINS, "Long"), ElementCategory.VERTEX,
                2, new boolean[]{true, true}, "mixed");
        evaluateQuery(tx.query().has("name", Text.CONTAINS, "Long Don"), ElementCategory.VERTEX,
                1, new boolean[]{true, true}, "mixed");
        evaluateQuery(tx.query().has("name", Text.CONTAINS_PREFIX, "Lon"), ElementCategory.VERTEX,
                2, new boolean[]{true, true}, "mixed");
        evaluateQuery(tx.query().has("name", Text.CONTAINS_REGEX, "Lit*le"), ElementCategory.VERTEX,
                1, new boolean[]{true, true}, "mixed");
        evaluateQuery(tx.query().has("name", Text.REGEX, "Long.*"), ElementCategory.VERTEX,
                2, new boolean[]{true, true}, "mixed");
        evaluateQuery(tx.query().has("name", Text.PREFIX, "Middle"), ElementCategory.VERTEX,
                1, new boolean[]{true, true}, "mixed");
        evaluateQuery(tx.query().has("name", Text.FUZZY, "Long john Don"), ElementCategory.VERTEX,
                1, new boolean[]{true, true}, "mixed");
        evaluateQuery(tx.query().has("name", Text.CONTAINS_FUZZY, "Midle"), ElementCategory.VERTEX,
                1, new boolean[]{true, true}, "mixed");
        for (final Vertex u : tx.getVertices()) {
            final String n = u.value("name");
            if (n.endsWith("Don")) {
                u.remove();
            } else if (n.endsWith("Lewis")) {
                u.property(VertexProperty.Cardinality.single, "name", "Big Brother Bob");
            } else if (n.endsWith("Mabel")) {
                u.property("name").remove();
            }
        }

        clopen();

        evaluateQuery(tx.query().has("name", Text.CONTAINS, "Long"), ElementCategory.VERTEX,
                0, new boolean[]{true, true}, "mixed");
        evaluateQuery(tx.query().has("name", Text.CONTAINS, "Big"), ElementCategory.VERTEX,
                1, new boolean[]{true, true}, "mixed");
        evaluateQuery(tx.query().has("name", Text.PREFIX, "Big"), ElementCategory.VERTEX,
                1, new boolean[]{true, true}, "mixed");
        evaluateQuery(tx.query().has("name", Text.PREFIX, "Middle"), ElementCategory.VERTEX,
                0, new boolean[]{true, true}, "mixed");

    }

    @Tag(TestCategory.BRITTLE_TESTS)
    @Test
    public void testIndexReplay() throws Exception {
        final TimestampProvider times = graph.getConfiguration().getTimestampProvider();
        final Instant startTime = times.getTime();
        clopen(option(SYSTEM_LOG_TRANSACTIONS), true
                , option(KCVSLog.LOG_READ_LAG_TIME, TRANSACTION_LOG), Duration.ofMillis(50)
                , option(LOG_READ_INTERVAL, TRANSACTION_LOG), Duration.ofMillis(250)
                , option(MAX_COMMIT_TIME), Duration.ofSeconds(1)
                , option(STORAGE_WRITE_WAITTIME), Duration.ofMillis(300)
                , option(TestMockIndexProvider.INDEX_BACKEND_PROXY, INDEX), readConfig.get(INDEX_BACKEND, INDEX)
                , option(INDEX_BACKEND, INDEX), TestMockIndexProvider.class.getName()
                , option(TestMockIndexProvider.INDEX_MOCK_FAILADD, INDEX), true
        );

        final PropertyKey name = mgmt.makePropertyKey("name").dataType(String.class).make();
        final PropertyKey age = mgmt.makePropertyKey("age").dataType(Integer.class).make();
        mgmt.buildIndex("mi", Vertex.class).addKey(name, getTextMapping()).addKey(age).buildMixedIndex(INDEX);
        finishSchema();
        final Vertex vs[] = new JanusGraphVertex[4];

        vs[0] = tx.addVertex("name", "Big Boy Bobson", "age", 55);
        newTx();
        vs[1] = tx.addVertex("name", "Long Little Lewis", "age", 35);
        vs[2] = tx.addVertex("name", "Tall Long Tiger", "age", 75);
        vs[3] = tx.addVertex("name", "Long John Don", "age", 15);
        newTx();
        vs[2] = getV(tx, vs[2]);
        vs[2].remove();
        vs[3] = getV(tx, vs[3]);
        vs[3].property(VertexProperty.Cardinality.single, "name", "Bad Boy Badsy");
        vs[3].property("age").remove();
        newTx();
        vs[0] = getV(tx, vs[0]);
        vs[0].property(VertexProperty.Cardinality.single, "age", 66);
        newTx();

        clopen();
        //Just to make sure nothing has been persisted to index
        evaluateQuery(tx.query().has("name", Text.CONTAINS, "boy"),
                ElementCategory.VERTEX, 0, new boolean[]{true, true}, "mi");
        /*
        Transaction Recovery
         */
        final TransactionRecovery recovery = JanusGraphFactory.startTransactionRecovery(graph, startTime);
        //wait
        Thread.sleep(12000L);

        recovery.shutdown();
        final long[] recoveryStats = ((StandardTransactionLogProcessor) recovery).getStatistics();

        clopen();

        evaluateQuery(tx.query().has("name", Text.CONTAINS, "boy"),
                ElementCategory.VERTEX, 2, new boolean[]{true, true}, "mi");
        evaluateQuery(tx.query().has("name", Text.CONTAINS, "long"),
                ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mi");
//        JanusGraphVertex v = Iterables.getOnlyElement(tx.query().has("name",Text.CONTAINS,"long").vertices());
//        System.out.println(v.getProperty("age"));
        evaluateQuery(tx.query().has("name", Text.CONTAINS, "long").interval("age", 30, 40),
                ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mi");
        evaluateQuery(tx.query().has("age", 75),
                ElementCategory.VERTEX, 0, new boolean[]{true, true}, "mi");
        evaluateQuery(tx.query().has("name", Text.CONTAINS, "boy").interval("age", 60, 70),
                ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mi");
        evaluateQuery(tx.query().interval("age", 0, 100),
                ElementCategory.VERTEX, 2, new boolean[]{true, true}, "mi");


        assertEquals(1, recoveryStats[0]); //schema transaction was successful
        assertEquals(4, recoveryStats[1]); //all 4 index transaction had provoked errors in the indexing backend
    }

    @Test
    public void testIndexUpdatesWithoutReindex() throws InterruptedException, ExecutionException {
        final Object[] settings = new Object[]{option(LOG_SEND_DELAY, MANAGEMENT_LOG), Duration.ofMillis(0),
                option(KCVSLog.LOG_READ_LAG_TIME, MANAGEMENT_LOG), Duration.ofMillis(50),
                option(LOG_READ_INTERVAL, MANAGEMENT_LOG), Duration.ofMillis(250)
        };

        clopen(settings);
        final String defText = "Mountain rocks are great friends";
        final int defTime = 5;
        final double defHeight = 101.1;
        final String[] defPhones = new String[]{"1234", "5678"};

        //Creates types and index only two keys key
        mgmt.makePropertyKey("time").dataType(Integer.class).make();
        final PropertyKey text = mgmt.makePropertyKey("text").dataType(String.class).make();

        mgmt.makePropertyKey("height").dataType(Double.class).make();
        if (indexFeatures.supportsCardinality(Cardinality.LIST)) {
            mgmt.makePropertyKey("phone").dataType(String.class).cardinality(Cardinality.LIST).make();
        }
        mgmt.buildIndex("theIndex", Vertex.class).addKey(text, getTextMapping(), getFieldMap(text)).buildMixedIndex(INDEX);
        finishSchema();

        //Add initial data
        addVertex(defTime, defText, defHeight, defPhones);

        //Indexes should not yet be in use
        clopen(settings);
        evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks"),
                ElementCategory.VERTEX, 1, new boolean[]{true, true}, "theIndex");
        evaluateQuery(tx.query().has("time", 5),
                ElementCategory.VERTEX, 1, new boolean[]{false, true});
        evaluateQuery(tx.query().interval("height", 100, 200),
                ElementCategory.VERTEX, 1, new boolean[]{false, true});
        evaluateQuery(tx.query().interval("height", 100, 200).has("time", 5),
                ElementCategory.VERTEX, 1, new boolean[]{false, true});
        evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks").has("time", 5).interval("height", 100, 200),
                ElementCategory.VERTEX, 1, new boolean[]{false, true}, "theIndex");
        if (indexFeatures.supportsCardinality(Cardinality.LIST)) {
            evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "1234"),
                    ElementCategory.VERTEX, 1, new boolean[]{false, true});
            evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "5678"),
                    ElementCategory.VERTEX, 1, new boolean[]{false, true});
        }
        newTx();

        //Add another key to index ------------------------------------------------------
        finishSchema();
        final PropertyKey time = mgmt.getPropertyKey("time");
        mgmt.addIndexKey(mgmt.getGraphIndex("theIndex"), time, getFieldMap(time));
        finishSchema();
        newTx();

        //Add more data
        addVertex(defTime, defText, defHeight, defPhones);
        tx.commit();
        //Should not yet be able to enable since not yet registered
        assertNull(mgmt.updateIndex(mgmt.getGraphIndex("theIndex"), SchemaAction.ENABLE_INDEX));
        //This call is redundant and just here to make sure it doesn't mess anything up
        mgmt.updateIndex(mgmt.getGraphIndex("theIndex"), SchemaAction.REGISTER_INDEX).get();
        mgmt.commit();

        ManagementSystem.awaitGraphIndexStatus(graph, "theIndex").timeout(10L, ChronoUnit.SECONDS).call();

        finishSchema();
        mgmt.updateIndex(mgmt.getGraphIndex("theIndex"), SchemaAction.ENABLE_INDEX).get();
        finishSchema();

        //Add more data
        addVertex(defTime, defText, defHeight, defPhones);

        //One more key should be indexed but only sees partial data
        clopen(settings);
        evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks"),
                ElementCategory.VERTEX, 3, new boolean[]{true, true}, "theIndex");
        evaluateQuery(tx.query().has("time", 5),
                ElementCategory.VERTEX, 2, new boolean[]{true, true}, "theIndex");
        evaluateQuery(tx.query().interval("height", 100, 200),
                ElementCategory.VERTEX, 3, new boolean[]{false, true});
        evaluateQuery(tx.query().interval("height", 100, 200).has("time", 5),
                ElementCategory.VERTEX, 2, new boolean[]{false, true}, "theIndex");
        evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks").has("time", 5).interval("height", 100, 200),
                ElementCategory.VERTEX, 2, new boolean[]{false, true}, "theIndex");
        if (indexFeatures.supportsCardinality(Cardinality.LIST)) {
            evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "1234"),
                    ElementCategory.VERTEX, 3, new boolean[]{false, true});
            evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "5678"),
                    ElementCategory.VERTEX, 3, new boolean[]{false, true});
        }
        newTx();

        //Add another key to index ------------------------------------------------------
        finishSchema();
        final PropertyKey height = mgmt.getPropertyKey("height");
        mgmt.addIndexKey(mgmt.getGraphIndex("theIndex"), height);
        if (indexFeatures.supportsCardinality(Cardinality.LIST)) {
            final PropertyKey phone = mgmt.getPropertyKey("phone");
            mgmt.addIndexKey(mgmt.getGraphIndex("theIndex"), phone, new Parameter("mapping", Mapping.STRING));
        }
        finishSchema();

        //Add more data
        addVertex(defTime, defText, defHeight, defPhones);
        tx.commit();
        mgmt.commit();

        ManagementUtil.awaitGraphIndexUpdate(graph, "theIndex", 10, ChronoUnit.SECONDS);

        finishSchema();
        mgmt.updateIndex(mgmt.getGraphIndex("theIndex"), SchemaAction.ENABLE_INDEX);
        finishSchema();

        JanusGraphIndex index = mgmt.getGraphIndex("theIndex");
        for (final PropertyKey key : index.getFieldKeys()) {
            assertEquals(SchemaStatus.ENABLED, index.getIndexStatus(key));
        }

        //Add more data
        addVertex(defTime, defText, defHeight, defPhones);

        //One more key should be indexed but only sees partial data
        clopen(settings);
        evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks"),
                ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex");
        evaluateQuery(tx.query().has("time", 5),
                ElementCategory.VERTEX, 4, new boolean[]{true, true}, "theIndex");
        evaluateQuery(tx.query().interval("height", 100, 200),
                ElementCategory.VERTEX, 2, new boolean[]{true, true}, "theIndex");
        evaluateQuery(tx.query().interval("height", 100, 200).has("time", 5),
                ElementCategory.VERTEX, 2, new boolean[]{true, true}, "theIndex");
        evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks").has("time", 5).interval("height", 100, 200),
                ElementCategory.VERTEX, 2, new boolean[]{true, true}, "theIndex");
        if (indexFeatures.supportsCardinality(Cardinality.LIST)) {
            evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "1234"),
                    ElementCategory.VERTEX, 2, new boolean[]{true, true}, "theIndex");
            evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "5678"),
                    ElementCategory.VERTEX, 2, new boolean[]{true, true}, "theIndex");
        }
        newTx();
        finishSchema();

        mgmt.updateIndex(mgmt.getGraphIndex("theIndex"), SchemaAction.REINDEX).get();
        mgmt.commit();

        finishSchema();

        //All the data should now be in the index
        clopen(settings);
        evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks"),
                ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex");
        evaluateQuery(tx.query().has("time", 5),
                ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex");
        evaluateQuery(tx.query().interval("height", 100, 200),
                ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex");
        evaluateQuery(tx.query().interval("height", 100, 200).has("time", 5),
                ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex");
        evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks").has("time", 5).interval("height", 100, 200),
                ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex");
        if (indexFeatures.supportsCardinality(Cardinality.LIST)) {
            evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "1234"),
                    ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex");
            evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "5678"),
                    ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex");
        }

        mgmt.updateIndex(mgmt.getGraphIndex("theIndex"), SchemaAction.DISABLE_INDEX).get();
        tx.commit();
        mgmt.commit();

        ManagementUtil.awaitGraphIndexUpdate(graph, "theIndex", 10, ChronoUnit.SECONDS);
        finishSchema();

        index = mgmt.getGraphIndex("theIndex");
        for (final PropertyKey key : index.getFieldKeys()) {
            assertEquals(SchemaStatus.DISABLED, index.getIndexStatus(key));
        }

        newTx();
        //This now requires a full graph scan
        evaluateQuery(tx.query().has("time", 5),
                ElementCategory.VERTEX, 5, new boolean[]{false, true});

    }

    private void addVertex(int time, String text, double height, String[] phones) {
        newTx();
        final JanusGraphVertex v = tx.addVertex("text", text, "time", time, "height", height);
        for (final String phone : phones) {
            v.property("phone", phone);
        }

        newTx();
    }



   /* ==================================================================================
                                     TIME-TO-LIVE
     ==================================================================================*/

    @Test
    public void testVertexTTLWithMixedIndices() throws Exception {
        if (!features.hasCellTTL() || !indexFeatures.supportsDocumentTTL()) {
            return;
        }

        final PropertyKey name = makeKey("name", String.class);
        final PropertyKey time = makeKey("time", Long.class);
        final PropertyKey text = makeKey("text", String.class);

        final VertexLabel event = mgmt.makeVertexLabel("event").setStatic().make();
        final int eventTTLSeconds = (int) TestGraphConfigs.getTTL(TimeUnit.SECONDS);
        mgmt.setTTL(event, Duration.ofSeconds(eventTTLSeconds));

        mgmt.buildIndex("index1", Vertex.class).
                addKey(name, getStringMapping()).addKey(time).buildMixedIndex(INDEX);
        mgmt.buildIndex("index2", Vertex.class).indexOnly(event).
                addKey(text, getTextMapping()).buildMixedIndex(INDEX);

        assertEquals(Duration.ZERO, mgmt.getTTL(name));
        assertEquals(Duration.ZERO, mgmt.getTTL(time));
        assertEquals(Duration.ofSeconds(eventTTLSeconds), mgmt.getTTL(event));
        finishSchema();

        JanusGraphVertex v1 = tx.addVertex("event");
        v1.property(VertexProperty.Cardinality.single, "name", "first event");
        v1.property(VertexProperty.Cardinality.single, "text", "this text will help to identify the first event");
        final long time1 = System.currentTimeMillis();
        v1.property(VertexProperty.Cardinality.single, "time", time1);
        JanusGraphVertex v2 = tx.addVertex("event");
        v2.property(VertexProperty.Cardinality.single, "name", "second event");
        v2.property(VertexProperty.Cardinality.single, "text", "this text won't match");
        final long time2 = time1 + 1;
        v2.property(VertexProperty.Cardinality.single, "time", time2);

        evaluateQuery(tx.query().has("name", "first event").orderBy("time", decr),
                ElementCategory.VERTEX, 1, new boolean[]{true, true}, tx.getPropertyKey("time"), Order.DESC, "index1");
        evaluateQuery(tx.query().has("text", Text.CONTAINS, "help").has(LABEL_NAME, "event"),
                ElementCategory.VERTEX, 1, new boolean[]{true, true}, "index2");

        clopen();

        final Object v1Id = v1.id();
        final Object v2Id = v2.id();

        evaluateQuery(tx.query().has("name", "first event").orderBy("time", decr),
                ElementCategory.VERTEX, 1, new boolean[]{true, true}, tx.getPropertyKey("time"), Order.DESC, "index1");
        evaluateQuery(tx.query().has("text", Text.CONTAINS, "help").has(LABEL_NAME, "event"),
                ElementCategory.VERTEX, 1, new boolean[]{true, true}, "index2");

        v1 = getV(tx, v1Id);
        v2 = getV(tx, v1Id);
        assertNotNull(v1);
        assertNotNull(v2);

        Thread.sleep(TimeUnit.MILLISECONDS.convert((long) Math.ceil(eventTTLSeconds * 1.25), TimeUnit.SECONDS));

        clopen();

        Thread.sleep(TimeUnit.MILLISECONDS.convert((long) Math.ceil(eventTTLSeconds * 1.25), TimeUnit.SECONDS));

        evaluateQuery(tx.query().has("text", Text.CONTAINS, "help").has(LABEL_NAME, "event"),
                ElementCategory.VERTEX, 0, new boolean[]{true, true}, "index2");
        evaluateQuery(tx.query().has("name", "first event").orderBy("time", decr),
                ElementCategory.VERTEX, 0, new boolean[]{true, true}, tx.getPropertyKey("time"), Order.DESC, "index1");


        v1 = getV(tx, v1Id);
        v2 = getV(tx, v2Id);
        assertNull(v1);
        assertNull(v2);
    }

    @Test
    public void testEdgeTTLWithMixedIndices() throws Exception {
        if (!features.hasCellTTL() || !indexFeatures.supportsDocumentTTL()) {
            return;
        }

        final PropertyKey name = mgmt.makePropertyKey("name").dataType(String.class).make();
        final PropertyKey text = mgmt.makePropertyKey("text").dataType(String.class).make();
        final PropertyKey time = makeKey("time", Long.class);

        final EdgeLabel label = mgmt.makeEdgeLabel("likes").make();
        final int likesTTLSeconds = (int) TestGraphConfigs.getTTL(TimeUnit.SECONDS);
        mgmt.setTTL(label, Duration.ofSeconds(likesTTLSeconds));

        mgmt.buildIndex("index1", Edge.class).
                addKey(name, getStringMapping()).addKey(time).buildMixedIndex(INDEX);
        mgmt.buildIndex("index2", Edge.class).indexOnly(label).
                addKey(text, getTextMapping()).buildMixedIndex(INDEX);

        assertEquals(Duration.ZERO, mgmt.getTTL(name));
        assertEquals(Duration.ofSeconds(likesTTLSeconds), mgmt.getTTL(label));
        finishSchema();

        JanusGraphVertex v1 = tx.addVertex(), v2 = tx.addVertex(), v3 = tx.addVertex();

        Edge e1 = v1.addEdge("likes", v2, "name", "v1 likes v2", "text", "this will help to identify the edge");
        final long time1 = System.currentTimeMillis();
        e1.property("time", time1);
        Edge e2 = v2.addEdge("likes", v3, "name", "v2 likes v3", "text", "this won't match anything");
        final long time2 = time1 + 1;
        e2.property("time", time2);

        tx.commit();

        clopen();
        final Object e1Id = e1.id();
        e2.id();

        evaluateQuery(tx.query().has("text", Text.CONTAINS, "help").has(LABEL_NAME, "likes"),
                ElementCategory.EDGE, 1, new boolean[]{true, true}, "index2");
        evaluateQuery(tx.query().has("name", "v2 likes v3").orderBy("time", decr),
                ElementCategory.EDGE, 1, new boolean[]{true, true}, tx.getPropertyKey("time"), Order.DESC, "index1");
        v1 = getV(tx, v1.id());
        v2 = getV(tx, v2.id());
        v3 = getV(tx, v3.id());
        e1 = getE(tx, e1Id);
        e2 = getE(tx, e1Id);
        assertNotNull(v1);
        assertNotNull(v2);
        assertNotNull(v3);
        assertNotNull(e1);
        assertNotNull(e2);
        assertNotEmpty(v1.query().direction(Direction.OUT).edges());
        assertNotEmpty(v2.query().direction(Direction.OUT).edges());


        Thread.sleep(TimeUnit.MILLISECONDS.convert((long) Math.ceil(likesTTLSeconds * 1.25), TimeUnit.SECONDS));
        clopen();

        // ...indexes have expired
        evaluateQuery(tx.query().has("text", Text.CONTAINS, "help").has(LABEL_NAME, "likes"),
                ElementCategory.EDGE, 0, new boolean[]{true, true}, "index2");
        evaluateQuery(tx.query().has("name", "v2 likes v3").orderBy("time", decr),
                ElementCategory.EDGE, 0, new boolean[]{true, true}, tx.getPropertyKey("time"), Order.DESC, "index1");

        v1 = getV(tx, v1.id());
        v2 = getV(tx, v2.id());
        v3 = getV(tx, v3.id());
        e1 = getE(tx, e1Id);
        e2 = getE(tx, e1Id);
        assertNotNull(v1);
        assertNotNull(v2);
        assertNotNull(v3);
        // edges have expired from the graph...
        assertNull(e1);
        assertNull(e2);
        assertEmpty(v1.query().direction(Direction.OUT).edges());
        assertEmpty(v2.query().direction(Direction.OUT).edges());
    }

   /* ==================================================================================
                            SPECIAL CONCURRENT UPDATE CASES
     ==================================================================================*/

    /**
     * Create a vertex with an indexed property and commit. Open two new
     * transactions; delete vertex in one and delete just the property in the
     * other, then commit in the same order. Neither commit throws an exception.
     * @throws BackendException
     */
    @Test
    public void testDeleteVertexThenDeleteProperty(TestInfo testInfo) throws BackendException {
        testNestedWrites("x", null, testInfo);
    }

    /**
     * Create a vertex and commit. Open two new transactions; delete vertex in
     * one and add an indexed property in the other, then commit in the same
     * order. Neither commit throws an exception.
     * @throws BackendException
     */
    @Test
    public void testDeleteVertexThenAddProperty(TestInfo testInfo) throws BackendException {
        testNestedWrites(null, "y", testInfo);
    }

    /**
     * Create a vertex with an indexed property and commit. Open two new
     * transactions; delete vertex in one and modify the property in the other,
     * then commit in the same order. Neither commit throws an exception.
     * @throws BackendException
     */
    @Test
    public void testDeleteVertexThenModifyProperty(TestInfo testInfo) throws BackendException {
        testNestedWrites("x", "y", testInfo);
    }

    @Test
    public void testIndexQueryWithScore() throws InterruptedException {
        final PropertyKey textKey = mgmt.makePropertyKey("text").dataType(String.class).make();
        mgmt.buildIndex("store1", Vertex.class).addKey(textKey).buildMixedIndex(INDEX);
        mgmt.commit();

        final JanusGraphVertex v1 = tx.addVertex();
        final JanusGraphVertex v2 = tx.addVertex();
        final JanusGraphVertex v3 = tx.addVertex();

        v1.property("text", "Hello Hello Hello Hello Hello Hello Hello Hello world");
        v2.property("text", "Hello abab abab fsdfsd sfdfsd sdffs fsdsdf fdf fsdfsd aera fsad abab abab fsdfsd sfdf");
        v3.property("text", "Hello Hello world world");

        tx.commit();

        final Set scores = graph.indexQuery("store1", "v.text:(Hello)").vertexStream()
            .map(JanusGraphIndexQuery.Result::getScore)
            .collect(Collectors.toSet());

        assertEquals(3, scores.size());
    }

    @Test
    // this tests a case when there as AND with a single CONTAINS condition inside AND(name:(was here))
    // which (in case of Solr) spans multiple conditions such as AND(AND(name:was, name:here))
    // so we need to make sure that we don't apply AND twice.
    public void testContainsWithMultipleValues() throws Exception {
        final PropertyKey name = makeKey("name", String.class);

        mgmt.buildIndex("store1", Vertex.class).addKey(name).buildMixedIndex(INDEX);
        mgmt.commit();

        final JanusGraphVertex v1 = tx.addVertex();
        v1.property("name", "hercules was here");

        tx.commit();

        final JanusGraphVertex r = Iterables.get(graph.query().has("name", Text.CONTAINS, "hercules here").vertices(), 0);
        assertEquals(r.property("name").value(), "hercules was here");
    }

    private void testNestedWrites(String initialValue, String updatedValue, TestInfo testInfo) throws BackendException {
        // This method touches a single vertex with multiple transactions,
        // leading to deadlock under BDB and cascading test failures. Check for
        // the hasTxIsolation() store feature, which is currently true for BDB
        // but false for HBase/Cassandra. This is kind of a hack; a more robust
        // approach might implement different methods/assertions depending on
        // whether the store is capable of deadlocking or detecting conflicting
        // writes and aborting a transaction.
        Backend b = null;
        try {
            b = graph.getConfiguration().getBackend();
            if (b.getStoreFeatures().hasTxIsolation()) {
                log.info("Skipping " + getClass().getSimpleName() + "." + testInfo.getTestMethod().toString());
                return;
            }
        } finally {
            if (null != b)
                b.close();
        }

        final String propName = "foo";

        // Write schema and one vertex
        final PropertyKey prop = makeKey(propName, String.class);
        createExternalVertexIndex(prop, INDEX);
        finishSchema();

        final JanusGraphVertex v = graph.addVertex();
        if (null != initialValue)
            v.property(VertexProperty.Cardinality.single, propName, initialValue);
        graph.tx().commit();

        final Object id = v.id();

        // Open two transactions and modify the same vertex
        final JanusGraphTransaction vertexDeleter = graph.newTransaction();
        final JanusGraphTransaction propDeleter = graph.newTransaction();

        getV(vertexDeleter, id).remove();
        if (null == updatedValue)
            getV(propDeleter, id).property(propName).remove();
        else
            getV(propDeleter, id).property(VertexProperty.Cardinality.single, propName, updatedValue);

        vertexDeleter.commit();
        propDeleter.commit();

        // The vertex must not exist after deletion
        graph.tx().rollback();
        assertEquals(null, getV(graph, id));
        assertEmpty(graph.query().has(propName).vertices());
        if (null != updatedValue)
            assertEmpty(graph.query().has(propName, updatedValue).vertices());
        graph.tx().rollback();
    }

    /**
     * Tests indexing using _all virtual field
     */
    @Test
    public void testWidcardQuery() {
        if (supportsWildcardQuery()) {
            final PropertyKey p1 = makeKey("p1", String.class);
            final PropertyKey p2 = makeKey("p2", String.class);
            mgmt.buildIndex("mixedIndex", Vertex.class).addKey(p1).addKey(p2).buildMixedIndex(INDEX);

            finishSchema();
            clopen();

            final JanusGraphVertex v1 = graph.addVertex();
            v1.property("p1", "test1");
            v1.property("p2", "test2");

            clopen();//Flush the index
            assertEquals(v1, graph.indexQuery("mixedIndex", "v.*:\"test1\"").vertexStream().findFirst().orElseThrow(IllegalStateException::new).getElement());
            assertEquals(v1, graph.indexQuery("mixedIndex", "v.*:\"test2\"").vertexStream().findFirst().orElseThrow(IllegalStateException::new).getElement());
        }

    }


    /**
     * Tests indexing lists
     */
    @Test
    public void testListIndexing() {
        testIndexing(Cardinality.LIST);
    }

    protected abstract boolean supportsCollections();

    /**
     * Tests indexing sets
     */
    @Test
    public void testSetIndexing() {
        testIndexing(Cardinality.SET);
    }


    private void testIndexing(Cardinality cardinality) {
        if (supportsCollections()) {
            final PropertyKey stringProperty = mgmt.makePropertyKey("name").dataType(String.class).cardinality(cardinality).make();
            final PropertyKey intProperty = mgmt.makePropertyKey("age").dataType(Integer.class).cardinality(cardinality).make();
            final PropertyKey longProperty = mgmt.makePropertyKey("long").dataType(Long.class).cardinality(cardinality).make();
            final PropertyKey uuidProperty = mgmt.makePropertyKey("uuid").dataType(UUID.class).cardinality(cardinality).make();
            final PropertyKey geopointProperty = mgmt.makePropertyKey("geopoint").dataType(Geoshape.class).cardinality(cardinality).make();
            mgmt.buildIndex("collectionIndex", Vertex.class).addKey(stringProperty, getStringMapping()).addKey(intProperty).addKey(longProperty).addKey(uuidProperty).addKey(geopointProperty).buildMixedIndex(INDEX);

            finishSchema();
            testCollection(cardinality, "name", "Totoro", "Hiro");
            testCollection(cardinality, "age", 1, 2);
            testCollection(cardinality, "long", 1L, 2L);
            testCollection(cardinality, "geopoint", Geoshape.point(1.0, 1.0), Geoshape.point(2.0, 2.0));
            final String backend = readConfig.get(INDEX_BACKEND, INDEX);
            // Solr 6 has issues processing UUIDs with Multivalues
            // https://issues.apache.org/jira/browse/SOLR-11264
            if (!"solr".equals(backend)) {
                testCollection(cardinality, "uuid", UUID.randomUUID(), UUID.randomUUID());
            }
        } else {
            try {
                final PropertyKey stringProperty = mgmt.makePropertyKey("name").dataType(String.class).cardinality(cardinality).make();
                //This should throw an exception
                mgmt.buildIndex("collectionIndex", Vertex.class).addKey(stringProperty, getStringMapping()).buildMixedIndex(INDEX);
                fail("Should have thrown an exception");
            } catch (final JanusGraphException ignored) {

            }
        }
    }

    private void testCollection(Cardinality cardinality, String property, Object value1, Object value2) {
        clopen();

        Vertex v1 = graph.addVertex();

        //Adding properties one at a time
        v1.property(property, value1);
        clopen();//Flush the index
        assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices()));

        v1 = getV(graph, v1.id());
        v1.property(property, value2);

        assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices()));
        assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices()));
        clopen();//Flush the index
        assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices()));
        assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices()));

        //Remove the properties
        v1 = getV(graph, v1.id());
        v1.properties(property).forEachRemaining(p -> {
            if (p.value().equals(value1)) {
                p.remove();
            }
        });

        assertFalse(graph.query().has(property, value1).vertices().iterator().hasNext());
        assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices()));
        clopen();//Flush the index
        assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices()));
        assertFalse(graph.query().has(property, value1).vertices().iterator().hasNext());

        //Re add the properties
        v1 = getV(graph, v1.id());
        v1.property(property, value1);
        assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices()));
        assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices()));
        clopen();//Flush the index
        assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices()));
        assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices()));

        //Add a duplicate property
        v1 = getV(graph, v1.id());
        v1.property(property, value1);


        assertEquals(Cardinality.SET.equals(cardinality) ? 2 : 3, Iterators.size(getOnlyVertex(graph.query().has(property, value1)).properties(property)));
        clopen();//Flush the index
        assertEquals(Cardinality.SET.equals(cardinality) ? 2 : 3, Iterators.size(getOnlyVertex(graph.query().has(property, value1)).properties(property)));


        //Add two properties at once to a fresh vertex
        graph.vertices().forEachRemaining(Element::remove);
        v1 = graph.addVertex();
        v1.property(property, value1);
        v1.property(property, value2);

        assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices()));
        assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices()));
        clopen();//Flush the index
        assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices()));
        assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices()));

        //If this is a geo test then try a within query
        if (value1 instanceof Geoshape) {
            assertEquals(v1, getOnlyElement(graph.query().has(property, Geo.WITHIN, Geoshape.circle(1.0, 1.0, 0.1)).vertices()));
            assertEquals(v1, getOnlyElement(graph.query().has(property, Geo.WITHIN, Geoshape.circle(2.0, 2.0, 0.1)).vertices()));
        }

        // Test traversal property drop Issue #408
        GraphTraversalSource g = graph.traversal();
        g.V().drop().iterate();
        clopen(); // Flush the index
        g = graph.traversal();
        v1 = g.addV().property(property, value1).property(property, value2).next();
        g.addV().property(property, value1).property(property, value2).next();
        clopen(); // Flush the index
        g = graph.traversal();
        assertEquals(2, g.V().has(property, value1).toList().size());
        g.V().properties().drop().iterate();
        clopen(); // Flush the index
        g = graph.traversal();
        assertFalse(g.V().has(property, value1).hasNext());
        assertFalse(g.V().has(property, value2).hasNext());
    }

    private void testGeo(int i, int origNumV, int numV) {
        final double offset = (i * 50.0 / origNumV);
        final double bufferKm = 20;
        final double distance = Geoshape.point(0.0, 0.0).getPoint().distance(Geoshape.point(offset, offset).getPoint()) + bufferKm;

        assertCount(i + 1, tx.query().has("location", Geo.WITHIN, Geoshape.circle(0.0, 0.0, distance)).vertices());
        assertCount(i + 1, tx.query().has("location", Geo.WITHIN, Geoshape.circle(0.0, 0.0, distance)).edges());
        assertCount(i + 1, tx.query().has("location", Geo.INTERSECT, Geoshape.circle(0.0, 0.0, distance)).vertices());
        assertCount(i + 1, tx.query().has("location", Geo.INTERSECT, Geoshape.circle(0.0, 0.0, distance)).edges());
        assertCount(numV-(i + 1), tx.query().has("location", Geo.DISJOINT, Geoshape.circle(0.0, 0.0, distance)).vertices());
        assertCount(numV-(i + 1), tx.query().has("location", Geo.DISJOINT, Geoshape.circle(0.0, 0.0, distance)).edges());
        assertCount(i + 1, tx.query().has("boundary", Geo.INTERSECT, Geoshape.circle(0.0, 0.0, distance)).vertices());
        assertCount(i + 1, tx.query().has("boundary", Geo.INTERSECT, Geoshape.circle(0.0, 0.0, distance)).edges());
        if (i > 0) {
            assertCount(i, tx.query().has("boundary", Geo.WITHIN, Geoshape.circle(0.0, 0.0, distance-bufferKm)).vertices());
            assertCount(i, tx.query().has("boundary", Geo.WITHIN, Geoshape.circle(0.0, 0.0, distance-bufferKm)).edges());
        }
        assertCount(numV-(i + 1), tx.query().has("boundary", Geo.DISJOINT, Geoshape.circle(0.0, 0.0, distance)).vertices());
        assertCount(numV-(i + 1), tx.query().has("boundary", Geo.DISJOINT, Geoshape.circle(0.0, 0.0, distance)).edges());

        if (indexFeatures.supportsGeoContains()) {
            assertCount(i % 2, tx.query().has("boundary", Geo.CONTAINS, Geoshape.point(-offset, -offset)).vertices());
            assertCount(i % 2, tx.query().has("boundary", Geo.CONTAINS, Geoshape.point(-offset, -offset)).edges());
        }

        final double buffer = bufferKm/111.;
        final double min = -Math.abs(offset);
        final double max = Math.abs(offset);
        final Geoshape bufferedBox = Geoshape.box(min-buffer, min-buffer, max+buffer, max+buffer);
        assertCount(i + 1, tx.query().has("location", Geo.WITHIN, bufferedBox).vertices());
        assertCount(i + 1, tx.query().has("location", Geo.WITHIN, bufferedBox).edges());
        assertCount(i + 1, tx.query().has("location", Geo.INTERSECT, bufferedBox).vertices());
        assertCount(i + 1, tx.query().has("location", Geo.INTERSECT, bufferedBox).edges());
        assertCount(numV-(i + 1), tx.query().has("location", Geo.DISJOINT, bufferedBox).vertices());
        assertCount(numV-(i + 1), tx.query().has("location", Geo.DISJOINT, bufferedBox).edges());
        if (i > 0) {
            final Geoshape exactBox = Geoshape.box(min, min, max, max);
            assertCount(i, tx.query().has("boundary", Geo.WITHIN, exactBox).vertices());
            assertCount(i, tx.query().has("boundary", Geo.WITHIN, exactBox).edges());
        }
        assertCount(i + 1, tx.query().has("boundary", Geo.INTERSECT, bufferedBox).vertices());
        assertCount(i + 1, tx.query().has("boundary", Geo.INTERSECT, bufferedBox).edges());
        assertCount(numV-(i + 1), tx.query().has("boundary", Geo.DISJOINT, bufferedBox).vertices());
        assertCount(numV-(i + 1), tx.query().has("boundary", Geo.DISJOINT, bufferedBox).edges());

        final Geoshape bufferedPoly = Geoshape.polygon(Arrays.asList(new double[][]
                {{min-buffer,min-buffer},{max+buffer,min-buffer},{max+buffer,max+buffer},{min-buffer,max+buffer},{min-buffer,min-buffer}}));
        if (i > 0) {
            final Geoshape exactPoly = Geoshape.polygon(Arrays.asList(new double[][]
                    {{min,min},{max,min},{max,max},{min,max},{min,min}}));
            assertCount(i, tx.query().has("boundary", Geo.WITHIN, exactPoly).vertices());
            assertCount(i, tx.query().has("boundary", Geo.WITHIN, exactPoly).edges());
        }
        assertCount(i + 1, tx.query().has("boundary", Geo.INTERSECT, bufferedPoly).vertices());
        assertCount(i + 1, tx.query().has("boundary", Geo.INTERSECT, bufferedPoly).edges());
        assertCount(numV-(i + 1), tx.query().has("boundary", Geo.DISJOINT, bufferedPoly).vertices());
        assertCount(numV-(i + 1), tx.query().has("boundary", Geo.DISJOINT, bufferedPoly).edges());
    }

    @Test
    public void shouldAwaitMultipleStatuses() throws InterruptedException, ExecutionException {
        final PropertyKey key1 = makeKey("key1", String.class);
        final JanusGraphIndex index = mgmt.buildIndex("randomMixedIndex", Vertex.class).addKey(key1).buildMixedIndex(INDEX);
        if (index.getIndexStatus(key1) == SchemaStatus.INSTALLED) {
            mgmt.updateIndex(mgmt.getGraphIndex("randomMixedIndex"), SchemaAction.REGISTER_INDEX).get();
            mgmt.updateIndex(mgmt.getGraphIndex("randomMixedIndex"), SchemaAction.ENABLE_INDEX).get();
        } else if (index.getIndexStatus(key1) == SchemaStatus.REGISTERED) {
            mgmt.updateIndex(mgmt.getGraphIndex("randomMixedIndex"), SchemaAction.ENABLE_INDEX).get();
        }
        final PropertyKey key2 = makeKey("key2", String.class);
        mgmt.addIndexKey(index, key2);
        mgmt.commit();
        //key1 now has status ENABLED, let's ensure we can watch for REGISTERED and ENABLED
        try {
            ManagementSystem.awaitGraphIndexStatus(graph, "randomMixedIndex").status(SchemaStatus.REGISTERED, SchemaStatus.ENABLED).call();
        } catch (final Exception e) {
            fail("Failed to awaitGraphIndexStatus on multiple statuses.");
        }
    }

    @Test
    public void testAndForceIndex() throws Exception {
        JanusGraph customGraph = null;
        try {
            customGraph = this.getForceIndexGraph();
            final JanusGraphManagement management = customGraph.openManagement();
            final PropertyKey nameProperty = management.makePropertyKey("name").dataType(String.class).cardinality(Cardinality.SINGLE).make();
            final PropertyKey ageProperty = management.makePropertyKey("age").dataType(Integer.class).cardinality(Cardinality.SINGLE).make();
            management.buildIndex("oridx", Vertex.class).addKey(nameProperty, getStringMapping()).addKey(ageProperty).buildMixedIndex(INDEX);
            management.commit();
            customGraph.tx().commit();
            final GraphTraversalSource g = customGraph.traversal();
            g.addV().property("name", "Hiro").property("age", 2).next();
            g.addV().property("name", "Totoro").property("age", 1).next();
            customGraph.tx().commit();
            assertCount(1, g.V().has("name", "Totoro"));
            assertCount(1, g.V().has("age", 2));
            assertCount(1, g.V().and(__.has("name", "Hiro"),__.has("age", 2)));
            assertCount(0, g.V().and(__.has("name", "Totoro"),__.has("age", 2)));
        } finally {
            if (customGraph != null) {
                JanusGraphFactory.close(customGraph);
            }
        }
    }

    @Test
    public void testOrForceIndexUniqueMixedIndex() throws Exception {
        JanusGraph customGraph = null;
        try {
            customGraph = this.getForceIndexGraph();
            final JanusGraphManagement management = customGraph.openManagement();
            final PropertyKey nameProperty = management.makePropertyKey("name").dataType(String.class).cardinality(Cardinality.SINGLE).make();
            final PropertyKey ageProperty = management.makePropertyKey("age").dataType(Integer.class).cardinality(Cardinality.SINGLE).make();
            final PropertyKey lengthProperty = management.makePropertyKey("length").dataType(Integer.class).cardinality(Cardinality.SINGLE).make();
            management.buildIndex("oridx", Vertex.class).addKey(nameProperty, getStringMapping()).addKey(ageProperty).addKey(lengthProperty).buildMixedIndex(INDEX);
            management.commit();
            customGraph.tx().commit();
            testOr(customGraph);
        } finally {
            if (customGraph != null) {
                JanusGraphFactory.close(customGraph);
            }
        }
    }

    @Test
    public void testOrForceIndexMixedAndCompositeIndex() throws Exception {
        JanusGraph customGraph = null;
        try {
            customGraph = this.getForceIndexGraph();
            final JanusGraphManagement management = customGraph.openManagement();
            final PropertyKey nameProperty = management.makePropertyKey("name").dataType(String.class).cardinality(Cardinality.SINGLE).make();
            final PropertyKey ageProperty = management.makePropertyKey("age").dataType(Integer.class).cardinality(Cardinality.SINGLE).make();
            final PropertyKey lengthProperty = management.makePropertyKey("length").dataType(Integer.class).cardinality(Cardinality.SINGLE).make();
            management.buildIndex("nameidx", Vertex.class).addKey(nameProperty, getStringMapping()).buildMixedIndex(INDEX);
            management.buildIndex("ageridx", Vertex.class).addKey(ageProperty).buildCompositeIndex();
            management.buildIndex("lengthidx", Vertex.class).addKey(lengthProperty).buildMixedIndex(INDEX);
            management.commit();
            customGraph.tx().commit();
            testOr(customGraph);
        } finally {
            if (customGraph != null) {
                JanusGraphFactory.close(customGraph);
            }
        }
    }

    @Test
    public void testOrPartialIndex() {
        final PropertyKey nameProperty = mgmt.makePropertyKey("name").dataType(String.class).cardinality(Cardinality.SINGLE).make();
        mgmt.makePropertyKey("age").dataType(Integer.class).cardinality(Cardinality.SINGLE).make();
        final PropertyKey lengthProperty = mgmt.makePropertyKey("length").dataType(Integer.class).cardinality(Cardinality.SINGLE).make();
        mgmt.buildIndex("otheridx", Vertex.class).addKey(nameProperty, getStringMapping()).addKey(lengthProperty).buildMixedIndex(INDEX);
        finishSchema();
        clopen();
        testOr(graph);
    }

    private void testOr(final Graph aGraph) {
        final GraphTraversalSource g = aGraph.traversal();
        final Vertex hiro = g.addV().property("name", "Hiro").property("age", 2).property("length", 90).next();
        final Vertex totoro = g.addV().property("name", "Totoro").property("age", 1).next();
        final Vertex john = g.addV().property("name", "John").property("age", 3).property("length", 110).next();
        final Vertex mike = g.addV().property("name", "Mike").property("age", 4).property("length", 130).next();
        aGraph.tx().commit();

        assertCount(1, g.V().has("name", "Totoro"));
        assertCount(1, g.V().has("age", 2));
        assertCount(1, g.V().or(__.has("name", "Hiro"),__.has("age", 2)));
        assertCount(2, g.V().or(__.has("name", "Totoro"),__.has("age", 2)));
        assertCount(2, g.V().or(__.has("name", "Totoro").has("age", 1),__.has("age", 2)));
        assertCount(2, g.V().or(__.and(__.has("name", "Totoro"), __.has("age", 1)),__.has("age", 2)));

        assertTraversal(g.V().has("length", P.lte(100)).or(__.has("name", "Totoro"),__.has("age", P.gte(2))), hiro);
        assertTraversal(g.V().or(__.has("name", "Totoro"),__.has("age", P.gte(2))).has("length", P.lte(100)), hiro);

        assertTraversal(g.V().or(__.has("name", "Totoro"),__.has("age", 2)).order().by(ORDER_AGE_DECR), hiro, totoro);
        assertTraversal(g.V().or(__.has("name", "Totoro"),__.has("age", 2)).order().by(ORDER_AGE_INCR), totoro, hiro);
        assertTraversal(g.V().or(__.has("name", "Hiro"),__.has("age", 2)).order().by(ORDER_AGE_INCR), hiro);

        assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120)).order().by(ORDER_LENGTH_DECR)), totoro, john, hiro);
        assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120)).order().by(ORDER_LENGTH_INCR)), totoro, hiro, john);
        assertTraversal(g.V().or(__.has("name", "John"), __.has("length", P.lte(120)).order().by(ORDER_LENGTH_INCR)), john, hiro);

        assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120)).order().by(ORDER_AGE_DECR)), totoro, john, hiro);
        assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120)).order().by(ORDER_AGE_INCR)), totoro, hiro, john);

        assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120)).order().by(ORDER_LENGTH_DECR)).order().by(ORDER_AGE_INCR), totoro, hiro, john);
        assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120)).order().by(ORDER_LENGTH_INCR)).order().by(ORDER_AGE_DECR), john, hiro, totoro);

        assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120))).order().by(ORDER_AGE_INCR).limit(2), totoro, hiro);
        assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120))).order().by(ORDER_AGE_INCR).range(2, 3), john);

        assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(130)).order().by(ORDER_LENGTH_INCR).limit(1)), totoro, hiro);
        assertTraversal(g.V().or(__.has("name", "Hiro"), __.has("length", P.lte(130)).order().by(ORDER_LENGTH_INCR).limit(1)), hiro);
        assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(130)).order().by(ORDER_LENGTH_INCR).range(1, 2)), totoro, john);
        assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(130)).order().by(ORDER_LENGTH_INCR).range(1, 3)).limit(2), totoro, john);

        assertTraversal(g.V().has("length", P.gte(130).or(P.lt(100))).order().by(ORDER_AGE_INCR), hiro, mike);
        assertTraversal(g.V().has("length", P.gte(80).and(P.gte(130).or(P.lt(100)))).order().by(ORDER_AGE_INCR), hiro, mike);
        if (indexFeatures.supportNotQueryNormalForm()) {
            assertTraversal(g.V().has("length", P.gte(80).and(P.gte(130)).or(P.gte(80).and(P.lt(100)))).order().by(ORDER_AGE_INCR), hiro, mike);
        }

    }

    @Test
    public void testOrForceIndexPartialIndex() throws Exception {
        JanusGraph customGraph = null;
        try {
            customGraph = this.getForceIndexGraph();
            final JanusGraphManagement management = customGraph.openManagement();
            final PropertyKey stringProperty = management.makePropertyKey("name").dataType(String.class).cardinality(Cardinality.SINGLE).make();
            management.makePropertyKey("age").dataType(Integer.class).cardinality(Cardinality.SINGLE).make();
            management.buildIndex("oridx", Vertex.class).addKey(stringProperty, getStringMapping()).buildMixedIndex(INDEX);
            management.commit();
            customGraph.tx().commit();
            final GraphTraversalSource g = customGraph.traversal();
            g.addV().property("name", "Hiro").property("age", 2).next();
            g.addV().property("name", "Totoro").property("age", 1).next();
            customGraph.tx().commit();
            g.V().or(__.has("name", "Totoro"),__.has("age", 2)).hasNext();
            fail("should fail");
        } catch (final JanusGraphException e){
            assertTrue(e.getMessage().contains("Could not find a suitable index to answer graph query and graph scans are disabled"));
        } finally {
            if (customGraph != null) {
                JanusGraphFactory.close(customGraph);
            }
        }
    }

    @Test
    public void testOrForceIndexComposite() throws Exception {
        JanusGraph customGraph = null;
        try {
            customGraph = this.getForceIndexGraph();
            final JanusGraphManagement management = customGraph.openManagement();
            management.makePropertyKey("name").dataType(String.class).cardinality(Cardinality.SINGLE).make();
            final PropertyKey ageProperty = management.makePropertyKey("age").dataType(Integer.class).cardinality(Cardinality.SINGLE).make();
            management.buildIndex("ageridx", Vertex.class).addKey(ageProperty).buildCompositeIndex();
            management.commit();
            customGraph.tx().commit();
            final GraphTraversalSource g = customGraph.traversal();
            g.addV().property("name", "Hiro").property("age", 2).next();
            g.addV().property("name", "Totoro").property("age", 1).next();
            customGraph.tx().commit();
            g.V().has("age", P.gte(4).or(P.lt(2))).hasNext();
            fail("should fail");
        } catch (final JanusGraphException e){
            assertTrue(e.getMessage().contains("Could not find a suitable index to answer graph query and graph scans are disabled"));
        } finally {
            if (customGraph != null) {
                JanusGraphFactory.close(customGraph);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy