diff --git a/community/cypher/cypher-logical-plans-3.4/src/main/scala/org/neo4j/cypher/internal/v3_4/logical/plans/SeekRange.scala b/community/cypher/cypher-logical-plans-3.4/src/main/scala/org/neo4j/cypher/internal/v3_4/logical/plans/SeekRange.scala index dcbe4db1da1b5..8038b5a551318 100644 --- a/community/cypher/cypher-logical-plans-3.4/src/main/scala/org/neo4j/cypher/internal/v3_4/logical/plans/SeekRange.scala +++ b/community/cypher/cypher-logical-plans-3.4/src/main/scala/org/neo4j/cypher/internal/v3_4/logical/plans/SeekRange.scala @@ -19,8 +19,6 @@ */ package org.neo4j.cypher.internal.v3_4.logical.plans -import org.neo4j.cypher.internal.v3_4.expressions.Expression - /* Seek ranges describe intervals. In practice they are used to summarize all inequalities over the same node and property (n.prop) during planning, esp. for generating index seek by range plans. diff --git a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/compatibility/v3_4/ExceptionTranslatingQueryContext.scala b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/compatibility/v3_4/ExceptionTranslatingQueryContext.scala index 3ed5e9a159efc..8174611fc7054 100644 --- a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/compatibility/v3_4/ExceptionTranslatingQueryContext.scala +++ b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/compatibility/v3_4/ExceptionTranslatingQueryContext.scala @@ -28,7 +28,7 @@ import org.neo4j.cypher.internal.runtime.interpreted.{DelegatingOperations, Dele import org.neo4j.cypher.internal.v3_4.expressions.SemanticDirection import org.neo4j.cypher.internal.v3_4.logical.plans.QualifiedName import org.neo4j.graphdb.{Node, Path, PropertyContainer} -import org.neo4j.internal.kernel.api.IndexReference +import org.neo4j.internal.kernel.api.{IndexQuery, IndexReference} import org.neo4j.internal.kernel.api.helpers.RelationshipSelectionCursor import org.neo4j.kernel.impl.api.store.RelationshipIterator import org.neo4j.kernel.impl.core.EmbeddedProxySPI @@ -100,7 +100,7 @@ class ExceptionTranslatingQueryContext(val inner: QueryContext) extends QueryCon override def dropIndexRule(descriptor: IndexDescriptor) = translateException(inner.dropIndexRule(descriptor)) - override def indexSeek(index: IndexReference, values: Seq[Any]): Iterator[NodeValue] = + override def indexSeek(index: IndexReference, values: Seq[IndexQuery]): Iterator[NodeValue] = translateException(inner.indexSeek(index, values)) override def getNodesByLabel(id: Int): Iterator[NodeValue] = @@ -198,7 +198,7 @@ class ExceptionTranslatingQueryContext(val inner: QueryContext) extends QueryCon override def getRelTypeName(id: Int) = translateException(inner.getRelTypeName(id)) - override def lockingUniqueIndexSeek(index: IndexReference, values: Seq[Any]) = + override def lockingUniqueIndexSeek(index: IndexReference, values: Seq[IndexQuery.ExactPredicate]): Option[NodeValue] = translateException(inner.lockingUniqueIndexSeek(index, values)) override def getImportURL(url: URL) = @@ -228,9 +228,6 @@ class ExceptionTranslatingQueryContext(val inner: QueryContext) extends QueryCon override def getRelationshipFor(relationshipId: Long, typeId: Int, startNodeId: Long, endNodeId: Long): RelationshipValue = translateException(inner.getRelationshipFor(relationshipId, typeId, startNodeId, endNodeId)) - override def indexSeekByRange(index: IndexReference, value: Any) = - translateException(inner.indexSeekByRange(index, value)) - override def indexScanByContains(index: IndexReference, value: String) = translateException(inner.indexScanByContains(index, value)) diff --git a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/DelegatingQueryContext.scala b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/DelegatingQueryContext.scala index b271d839e3be0..52b491c575f67 100644 --- a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/DelegatingQueryContext.scala +++ b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/DelegatingQueryContext.scala @@ -114,12 +114,9 @@ abstract class DelegatingQueryContext(val inner: QueryContext) extends QueryCont override def indexReference(label: Int, properties: Int*): IndexReference = singleDbHit(inner.indexReference(label, properties:_*)) - override def indexSeek(index: IndexReference, values: Seq[Any]): Iterator[NodeValue] = + override def indexSeek(index: IndexReference, values: Seq[IndexQuery]): Iterator[NodeValue] = manyDbHits(inner.indexSeek(index, values)) - override def indexSeekByRange(index: IndexReference, value: Any): Iterator[NodeValue] = - manyDbHits(inner.indexSeekByRange(index, value)) - override def indexScan(index: IndexReference): Iterator[NodeValue] = manyDbHits(inner.indexScan(index)) override def indexScanPrimitive(index: IndexReference): PrimitiveLongIterator = manyDbHits(inner.indexScanPrimitive(index)) @@ -163,7 +160,7 @@ abstract class DelegatingQueryContext(val inner: QueryContext) extends QueryCont override def withAnyOpenQueryContext[T](work: (QueryContext) => T): T = inner.withAnyOpenQueryContext(work) - override def lockingUniqueIndexSeek(index: IndexReference, values: Seq[Any]): Option[NodeValue] = + override def lockingUniqueIndexSeek(index: IndexReference, values: Seq[IndexQuery.ExactPredicate]): Option[NodeValue] = singleDbHit(inner.lockingUniqueIndexSeek(index, values)) override def getRelTypeId(relType: String): Int = singleDbHit(inner.getRelTypeId(relType)) diff --git a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/TransactionBoundQueryContext.scala b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/TransactionBoundQueryContext.scala index 6968cb6455899..3c73a893be520 100644 --- a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/TransactionBoundQueryContext.scala +++ b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/TransactionBoundQueryContext.scala @@ -20,20 +20,18 @@ package org.neo4j.cypher.internal.runtime.interpreted import java.net.URL -import java.time.LocalDate import java.util.function.Predicate import org.neo4j.collection.RawIterator import org.neo4j.collection.primitive.{PrimitiveLongIterator, PrimitiveLongResourceIterator} -import org.neo4j.cypher.InternalException import org.neo4j.cypher.internal.javacompat.GraphDatabaseCypherService import org.neo4j.cypher.internal.planner.v3_4.spi.{IdempotentResult, IndexDescriptor} import org.neo4j.cypher.internal.runtime._ -import org.neo4j.cypher.internal.runtime.interpreted.CypherOrdering.{BY_DATE, BY_NUMBER, BY_POINT, BY_STRING, BY_VALUE} import org.neo4j.cypher.internal.runtime.interpreted.TransactionBoundQueryContext.IndexSearchMonitor import org.neo4j.cypher.internal.runtime.interpreted.commands.convert.DirectionConverter.toGraphDb import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.{OnlyDirectionExpander, TypeAndDirectionExpander} -import org.neo4j.cypher.internal.util.v3_4.{EntityNotFoundException, FailedIndexException, NonEmptyList} +import org.neo4j.cypher.internal.runtime.interpreted.pipes.IncorrectIndexError +import org.neo4j.cypher.internal.util.v3_4.{EntityNotFoundException, FailedIndexException} import org.neo4j.cypher.internal.v3_4.expressions.SemanticDirection import org.neo4j.cypher.internal.v3_4.expressions.SemanticDirection.{BOTH, INCOMING, OUTGOING} import org.neo4j.cypher.internal.v3_4.logical.plans._ @@ -62,7 +60,7 @@ import org.neo4j.kernel.impl.coreapi.PropertyContainerLocker import org.neo4j.kernel.impl.query.Neo4jTransactionalContext import org.neo4j.kernel.impl.util.ValueUtils.{fromNodeProxy, fromRelationshipProxy} import org.neo4j.kernel.impl.util.{DefaultValueMapper, NodeProxyWrappingNodeValue, RelationshipProxyWrappingValue} -import org.neo4j.values.storable.{PointValue, TextValue, Value, Values, _} +import org.neo4j.values.storable.{TextValue, Value, Values, _} import org.neo4j.values.virtual.{ListValue, NodeValue, RelationshipValue, VirtualValues} import org.neo4j.values.{AnyValue, ValueMapper} @@ -241,110 +239,26 @@ sealed class TransactionBoundQueryContext(val transactionalContext: Transactiona case e: NotFoundException => throw new EntityNotFoundException(s"Relationship with id $relationshipId", e) } - override def indexSeek(index: IndexReference, values: Seq[Any]): Iterator[NodeValue] = { - indexSearchMonitor.indexSeek(index, values) - val predicates = index.properties.zip(values).map(p => IndexQuery.exact(p._1, p._2)) - seek(index, predicates:_*) - } - - override def indexReference(label: Int, - properties: Int*): IndexReference = - transactionalContext.kernelTransaction.schemaRead().index(label, properties:_*) - - override def indexSeekByRange(index: IndexReference, value: Any): Iterator[NodeValue] = value match { - - case PrefixRange(null) => Iterator.empty - case PrefixRange(prefix: String) => - indexSeekByPrefixRange(index, prefix) - case range: InequalitySeekRange[Any] => - indexSeekByPrefixRange(index, range) - case range: PointDistanceRange[_] => - val distance = range.distance match { - case n:NumberValue => n.doubleValue() - case n:Number => n.doubleValue() - case _ => - // We can't compare against something that is not a number, so no rows will match - return Iterator.empty - } - - val bbox = range.point match { - case p: PointValue => p.getCoordinateReferenceSystem.getCalculator.boundingBox(p, distance) - case _ => - // when it tries to evaluate the distance on something that is not a point - return Iterator.empty - } - val (from, to) = (bbox.first(), bbox.other()) - - val (fromBounds, toBounds) = - if (range.inclusive) - (NonEmptyList(InclusiveBound(from)), NonEmptyList(InclusiveBound(to))) - else - (NonEmptyList(ExclusiveBound(from)), NonEmptyList(ExclusiveBound(to))) - - indexSeekByGeometryRange(index, RangeBetween(RangeGreaterThan(fromBounds), RangeLessThan(toBounds))) - case range => - throw new InternalException(s"Unsupported index seek by range: $range") - } + override def indexSeek(index: IndexReference, predicates: Seq[IndexQuery]): Iterator[NodeValue] = { - private def indexSeekByPrefixRange(index: IndexReference, range: InequalitySeekRange[Any]): scala.Iterator[NodeValue] = { - val groupedRanges = range.groupBy { (bound: Bound[Any]) => - bound.endPoint match { - case n: Number => classOf[Number] - case s: String => classOf[String] - case c: Character => classOf[String] - case p: PointValue => classOf[PointValue] - case d: LocalDate => classOf[LocalDate] - case _ => classOf[Any] + val SEEKABLE_VALUE_GROUPS = Array(ValueGroup.NUMBER, ValueGroup.TEXT, ValueGroup.GEOMETRY, ValueGroup.DATE) + val impossiblePredicate = + predicates.exists { + case p:IndexQuery.ExactPredicate => p.value() == Values.NO_VALUE + case p:IndexQuery => + if (p.valueGroup() == ValueGroup.NO_VALUE) true + else if (!SEEKABLE_VALUE_GROUPS.contains(p.valueGroup())) + throw new IncorrectIndexError() + else false } - } - - val optNumericRange = groupedRanges.get(classOf[Number]).map(_.asInstanceOf[InequalitySeekRange[Number]]) - val optStringRange = groupedRanges.get(classOf[String]).map(_.mapBounds(_.toString)) - val optGeometricRange = groupedRanges.get(classOf[PointValue]).map(_.asInstanceOf[InequalitySeekRange[PointValue]]) - val optDateRange = groupedRanges.get(classOf[LocalDate]).map(_.asInstanceOf[InequalitySeekRange[LocalDate]]) - val anyRange = groupedRanges.get(classOf[Any]) - - if (anyRange.nonEmpty) { - // If we get back an exclusion test, the range could return values otherwise it is empty - anyRange.get.inclusionTest[Any](BY_VALUE).map { test => - throw new IllegalArgumentException( - "Cannot compare a property against values that are neither strings nor numbers.") - }.getOrElse(Iterator.empty) - } else { - (optNumericRange, optStringRange, optGeometricRange, optDateRange) match { - case (Some(numericRange), None, None, None) => indexSeekByNumericalRange(index, numericRange) - case (None, Some(stringRange), None, None) => indexSeekByStringRange(index, stringRange) - case (None, None, Some(geometricRange), None) => indexSeekByGeometryRange(index, geometricRange) - case (None, None, None, Some(dateRange)) => indexSeekByDateRange(index, dateRange) - case (None, None, None, None) => - // If we get here, the non-empty list of range bounds was partitioned into two empty ones - throw new IllegalStateException("Failed to partition range bounds") - - case (_, _, _, _) => - // Consider MATCH (n:Person) WHERE n.prop < 1 AND n.prop > "London": - // The order of predicate evaluation is unspecified, i.e. - // LabelScan fby Filter(n.prop < 1) fby Filter(n.prop > "London") is a valid plan - // If the first filter returns no results, the plan returns no results. - // If the first filter returns any result, the following filter will fail since - // comparing string against numbers throws an exception. Same for the reverse case. - // - // Below we simulate this behaviour: - // - if (optNumericRange.exists(indexSeekByNumericalRange(index, _).isEmpty) - || optStringRange.exists(indexSeekByStringRange(index, _).isEmpty) - || optGeometricRange.exists(indexSeekByGeometryRange(index, _).isEmpty)) { - Iterator.empty - } else { - throw new IllegalArgumentException( - s"Cannot compare a property against combinations of numbers, strings and geometries. They are incomparable.") - } - } - } + if (impossiblePredicate) Iterator.empty + else seek(index, predicates:_*) } - private def indexSeekByPrefixRange(index: IndexReference, prefix: String): scala.Iterator[NodeValue] = - seek(index, IndexQuery.stringPrefix(index.properties()(0), prefix)) + override def indexReference(label: Int, + properties: Int*): IndexReference = + transactionalContext.kernelTransaction.schemaRead().index(label, properties:_*) private def seek(index: IndexReference, query: IndexQuery*) = { val nodeCursor = allocateAndTraceNodeValueIndexCursor() @@ -370,125 +284,6 @@ sealed class TransactionBoundQueryContext(val transactionalContext: Transactiona } } - private def indexSeekByNumericalRange(index: IndexReference, - range: InequalitySeekRange[Number]): scala.Iterator[NodeValue] = (range match { - - case rangeLessThan: RangeLessThan[Number] => - rangeLessThan.limit(BY_NUMBER).map { limit => - val rangePredicate = IndexQuery.range(index.properties().head, null, false, limit.endPoint, limit.isInclusive) - seek(index, rangePredicate) - } - - case rangeGreaterThan: RangeGreaterThan[Number] => - rangeGreaterThan.limit(BY_NUMBER).map { limit => - val rangePredicate = IndexQuery.range(index.properties().head, limit.endPoint, limit.isInclusive, null, false) - seek(index, rangePredicate) - } - - case RangeBetween(rangeGreaterThan, rangeLessThan) => - rangeGreaterThan.limit(BY_NUMBER).flatMap { greaterThanLimit => - rangeLessThan.limit(BY_NUMBER).map { lessThanLimit => - val compare = - Values.COMPARATOR.compare( - Values.numberValue(greaterThanLimit.endPoint), - Values.numberValue(lessThanLimit.endPoint) - ) - if (compare < 0) { - val rangePredicate = IndexQuery - .range(index.properties().head, greaterThanLimit.endPoint, greaterThanLimit.isInclusive, - lessThanLimit.endPoint, - lessThanLimit.isInclusive) - seek(index, rangePredicate) - } else if (compare == 0 && greaterThanLimit.isInclusive && lessThanLimit.isInclusive) { - seek(index, IndexQuery.exact(index.properties().head, greaterThanLimit.endPoint)) - } else { - Iterator.empty - } - } - } - }).getOrElse(Iterator.empty) - - private def indexSeekByGeometryRange(index: IndexReference, - range: InequalitySeekRange[PointValue]): scala.Iterator[NodeValue] = (range match { - - case rangeLessThan: RangeLessThan[PointValue] => - rangeLessThan.limit(BY_POINT).map { limit => - val rangePredicate = IndexQuery.range(index.properties()(0), null, false, limit.endPoint, limit.isInclusive) - seek(index, rangePredicate) - } - - case rangeGreaterThan: RangeGreaterThan[PointValue] => - rangeGreaterThan.limit(BY_POINT).map { limit => - val rangePredicate = IndexQuery.range(index.properties()(0), limit.endPoint, limit.isInclusive, null, false) - seek(index, rangePredicate) - } - - case RangeBetween(rangeGreaterThan, rangeLessThan) => - rangeGreaterThan.limit(BY_POINT).flatMap { greaterThanLimit => - rangeLessThan.limit(BY_POINT).map { lessThanLimit => - val rangePredicate = IndexQuery - .range(index.properties()(0), greaterThanLimit.endPoint, greaterThanLimit.isInclusive, - lessThanLimit.endPoint, - lessThanLimit.isInclusive) - seek(index, rangePredicate) - } - } - }).getOrElse(Iterator.empty) - - private def indexSeekByDateRange(index: IndexReference, - range: InequalitySeekRange[LocalDate]): scala.Iterator[NodeValue] = - (range match { - case rangeLessThan: RangeLessThan[LocalDate] => - rangeLessThan.limit(BY_DATE).map { limit => - val rangePredicate = IndexQuery.range(index.properties()(0), null, false, DateValue.date(limit.endPoint), limit.isInclusive) - seek(index, rangePredicate) - } - - case rangeGreaterThan: RangeGreaterThan[LocalDate] => - rangeGreaterThan.limit(BY_DATE).map { limit => - val rangePredicate = IndexQuery.range(index.properties()(0), DateValue.date(limit.endPoint), limit.isInclusive, null, false) - seek(index, rangePredicate) - } - - case RangeBetween(rangeGreaterThan, rangeLessThan) => - rangeGreaterThan.limit(BY_DATE).flatMap { greaterThanLimit => - rangeLessThan.limit(BY_DATE).map { lessThanLimit => - val rangePredicate = IndexQuery.range(index.properties()(0), - DateValue.date(greaterThanLimit.endPoint), greaterThanLimit.isInclusive, - DateValue.date(lessThanLimit.endPoint), lessThanLimit.isInclusive) - seek(index, rangePredicate) - } - } - }).getOrElse(Iterator.empty) - - private def indexSeekByStringRange(index: IndexReference, - range: InequalitySeekRange[String]): scala.Iterator[NodeValue] = range match { - - case rangeLessThan: RangeLessThan[String] => - rangeLessThan.limit(BY_STRING).map { limit => - val rangePredicate = IndexQuery - .range(index.properties()(0), null, false, limit.endPoint.asInstanceOf[String], limit.isInclusive) - seek(index, rangePredicate) - }.getOrElse(Iterator.empty) - - case rangeGreaterThan: RangeGreaterThan[String] => - rangeGreaterThan.limit(BY_STRING).map { limit => - val rangePredicate = IndexQuery - .range(index.properties()(0), limit.endPoint.asInstanceOf[String], limit.isInclusive, null, false) - seek(index, rangePredicate) - }.getOrElse(Iterator.empty) - - case RangeBetween(rangeGreaterThan, rangeLessThan) => - rangeGreaterThan.limit(BY_STRING).flatMap { greaterThanLimit => - rangeLessThan.limit(BY_STRING).map { lessThanLimit => - val rangePredicate = IndexQuery - .range(index.properties()(0), greaterThanLimit.endPoint.asInstanceOf[String], greaterThanLimit.isInclusive, - lessThanLimit.endPoint.asInstanceOf[String], lessThanLimit.isInclusive) - seek(index, rangePredicate) - } - }.getOrElse(Iterator.empty) - } - override def indexScan(index: IndexReference): Iterator[NodeValue] = JavaConversionSupport.mapToScalaENFXSafe(indexScanPrimitive(index))(nodeOps.getByIdIfExists) @@ -500,10 +295,10 @@ sealed class TransactionBoundQueryContext(val transactionalContext: Transactiona override def indexScanByEndsWith(index: IndexReference, value: String): Iterator[NodeValue] = seek(index, IndexQuery.stringSuffix(index.properties()(0), value)) - override def lockingUniqueIndexSeek(indexReference: IndexReference, values: Seq[Any]): Option[NodeValue] = { - indexSearchMonitor.lockingUniqueIndexSeek(indexReference, values) + override def lockingUniqueIndexSeek(indexReference: IndexReference, queries: Seq[IndexQuery.ExactPredicate]): Option[NodeValue] = { + indexSearchMonitor.lockingUniqueIndexSeek(indexReference, queries) val index = DefaultIndexReference.general(indexReference.label(), indexReference.properties():_*) - val predicates = indexReference.properties.zip(values).map(p => IndexQuery.exact(p._1, p._2)) + val predicates = indexReference.properties.zip(queries).map(p => IndexQuery.exact(p._1, p._2.value())) val nodeId = transactionalContext.kernelTransaction.dataRead().nodeUniqueIndexSeek(index, predicates:_*) if (StatementConstants.NO_SUCH_NODE == nodeId) None else Some(nodeOps.getById(nodeId)) } diff --git a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/indexQuery.scala b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/indexQuery.scala deleted file mode 100644 index eb70be6bee450..0000000000000 --- a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/indexQuery.scala +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2002-2018 "Neo Technology," - * Network Engine for Objects in Lund AB [http://neotechnology.com] - * - * This file is part of Neo4j. - * - * Neo4j is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.neo4j.cypher.internal.runtime.interpreted.commands - -import org.neo4j.cypher.internal.frontend.v3_4.helpers.SeqCombiner.combine -import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.{Expression, InequalitySeekRangeExpression, PointDistanceSeekRangeExpression, PrefixSeekRangeExpression} -import org.neo4j.cypher.internal.runtime.interpreted.pipes.QueryState -import org.neo4j.cypher.internal.runtime.interpreted._ -import org.neo4j.cypher.internal.util.v3_4.{CypherTypeException, InternalException} -import org.neo4j.cypher.internal.v3_4.logical.plans._ -import org.neo4j.values.AnyValue -import org.neo4j.values.storable.Values -import org.neo4j.values.virtual.NodeValue - -import scala.collection.GenTraversableOnce -import scala.collection.JavaConverters._ - -object indexQuery extends GraphElementPropertyFunctions { - def apply(queryExpression: QueryExpression[Expression], - m: ExecutionContext, - state: QueryState, - index: Seq[Any] => GenTraversableOnce[NodeValue], - labelName: String, - propertyNames: Seq[String]): Iterator[NodeValue] = queryExpression match { - - // Index exact value seek on single value - case SingleQueryExpression(inner) => - val value: AnyValue = inner(m, state) - lookupNodes(Seq(value), index) - - // Index exact value seek on multiple values, by combining the results of multiple index seeks - case ManyQueryExpression(inner) => - inner(m, state) match { - case IsList(coll) => coll.iterator().asScala.toSet.toIndexedSeq.flatMap { - value: AnyValue => lookupNodes(Seq(value), index) - }.iterator - case v if v == Values.NO_VALUE => Iterator.empty - case _ => throw new CypherTypeException(s"Expected the value for looking up :$labelName(${propertyNames.mkString(",")}) to be a collection but it was not.") - } - - // Index exact value seek on multiple values, making use of a composite index over all values - case CompositeQueryExpression(innerExpressions) => - assert(innerExpressions.size == propertyNames.size) - val seekValues = innerExpressions.map(expressionValues(m, state)) - val combined = combine(seekValues) - val results = combined map { values => - lookupNodes(values, index) - } - if (results.size == 1) - results.head - else - results.iterator.flatten - - // Index range seek over range of values - case RangeQueryExpression(rangeWrapper) => - val range = rangeWrapper match { - case s: PrefixSeekRangeExpression => - s.range.map(expression => makeValueNeoSafe(expression(m, state)).asObject()) - - case InequalitySeekRangeExpression(innerRange) => - innerRange.mapBounds(expression => makeValueNeoSafe(expression(m, state)).asObject()) - - case PointDistanceSeekRangeExpression(innerRange) => - innerRange.map(expression => makeValueNeoSafe(expression(m, state)).asObject()) - } - index(Seq(range)).toIterator - } - - private def lookupNodes(values: Seq[AnyValue], index: Seq[Any] => GenTraversableOnce[NodeValue]): Iterator[NodeValue] = { - // If any of the values we are searching for is null, the whole expression that this index seek represents - // collapses into a null value, which will not match any nodes. - if (values.contains(Values.NO_VALUE)) - Iterator.empty - else { - val neoValues = values.map(makeValueNeoSafe).map(_.asObject()) - index(neoValues).toIterator - } - } - - private def expressionValues(m: ExecutionContext, state: QueryState)(queryExpression: QueryExpression[Expression]): Seq[AnyValue] = { - queryExpression match { - - case SingleQueryExpression(inner) => - Seq(inner(m, state)) - - case ManyQueryExpression(inner) => - inner(m, state) match { - case IsList(coll) => coll.asArray() - case null => Seq.empty - case _ => throw new CypherTypeException(s"Expected the value for $inner to be a collection but it was not.") - } - - case CompositeQueryExpression(innerExpressions) => - throw new InternalException("A CompositeQueryExpression can't be nested in a CompositeQueryExpression") - - case RangeQueryExpression(rangeWrapper) => - throw new InternalException("Range queries on composite indexes not yet supported") - } - } -} - - diff --git a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/IncorrectIndexError.scala b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/IncorrectIndexError.scala new file mode 100644 index 0000000000000..788ae3f983b08 --- /dev/null +++ b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/IncorrectIndexError.scala @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.cypher.internal.runtime.interpreted.pipes + +/** + * Exception which signals that an index seek was attempted with a predicate set that cannot + * match any node. This is not how index lookup should ever work, but added it in order not to change behaviour + * while refactoring. Hoping to change this soon. + * + * The only time when this is reasonable is when the non-match can be statically determined at compile + * time, and in that case maybe a warning is enough? + */ +// TODO: remove this error and just return no rows for there cases. +@Deprecated +class IncorrectIndexError extends IllegalArgumentException( + "Cannot compare a property against both numbers and strings. They are incomparable.") diff --git a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/IndexSeekMode.scala b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/IndexSeekMode.scala index d3ae4bb9bff0f..d36cf861b420e 100644 --- a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/IndexSeekMode.scala +++ b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/IndexSeekMode.scala @@ -19,10 +19,8 @@ */ package org.neo4j.cypher.internal.runtime.interpreted.pipes -import org.neo4j.cypher.internal.runtime.interpreted.pipes.IndexSeekMode.{MultipleValueQuery, assertSingleValue} import org.neo4j.cypher.internal.util.v3_4.InternalException import org.neo4j.cypher.internal.v3_4.logical.plans.{QueryExpression, RangeQueryExpression} -import org.neo4j.internal.kernel.api.IndexReference import org.neo4j.values.virtual.NodeValue case class IndexSeekModeFactory(unique: Boolean, readOnly: Boolean) { @@ -43,48 +41,24 @@ object IndexSeekMode { throw new InternalException("Composite lookups not yet supported") values.head } - } - -sealed trait IndexSeekMode { - def indexFactory(index: IndexReference): MultipleValueQuery - - def name: String } +sealed abstract class IndexSeekMode(val name: String) + sealed trait ExactSeek { self: IndexSeekMode => - override def indexFactory(index: IndexReference): MultipleValueQuery = - (state: QueryState) => (values: Seq[Any]) => state.query.indexSeek(index, values) -} - -case object IndexSeek extends IndexSeekMode with ExactSeek { - override def name: String = "NodeIndexSeek" } -case object UniqueIndexSeek extends IndexSeekMode with ExactSeek { - override def name: String = "NodeUniqueIndexSeek" -} - -case object LockingUniqueIndexSeek extends IndexSeekMode { +case object IndexSeek extends IndexSeekMode("NodeIndexSeek") with ExactSeek - override def indexFactory(index: IndexReference): MultipleValueQuery = - (state: QueryState) => (x: Seq[Any]) => { - state.query.lockingUniqueIndexSeek(index, x).toIterator - } +case object UniqueIndexSeek extends IndexSeekMode("NodeUniqueIndexSeek") with ExactSeek - override def name: String = "NodeUniqueIndexSeek(Locking)" -} +case object LockingUniqueIndexSeek extends IndexSeekMode("NodeUniqueIndexSeek(Locking)") sealed trait SeekByRange { self: IndexSeekMode => - override def indexFactory(index: IndexReference): MultipleValueQuery = - (state: QueryState) => (x: Seq[Any]) => state.query.indexSeekByRange(index, assertSingleValue(x)) } -case object IndexSeekByRange extends IndexSeekMode with SeekByRange { - override def name: String = "NodeIndexSeekByRange" -} +case object IndexSeekByRange extends IndexSeekMode("NodeIndexSeekByRange") with SeekByRange -case object UniqueIndexSeekByRange extends IndexSeekMode with SeekByRange { - override def name: String = "NodeUniqueIndexSeekByRange" -} +case object UniqueIndexSeekByRange extends IndexSeekMode("NodeUniqueIndexSeekByRange") with SeekByRange diff --git a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeekPipe.scala b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeekPipe.scala index 1763cd3c28a8f..d40ce15c5d832 100644 --- a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeekPipe.scala +++ b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeekPipe.scala @@ -22,20 +22,20 @@ package org.neo4j.cypher.internal.runtime.interpreted.pipes import org.neo4j.cypher.internal.runtime.QueryContext import org.neo4j.cypher.internal.runtime.interpreted.ExecutionContext import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.Expression -import org.neo4j.cypher.internal.runtime.interpreted.commands.indexQuery import org.neo4j.cypher.internal.util.v3_4.attribution.Id import org.neo4j.cypher.internal.v3_4.expressions.{LabelToken, PropertyKeyToken} -import org.neo4j.cypher.internal.v3_4.logical.plans.QueryExpression +import org.neo4j.cypher.internal.v3_4.logical.plans._ import org.neo4j.internal.kernel.api.{CapableIndexReference, IndexReference} +import org.neo4j.values.virtual.NodeValue case class NodeIndexSeekPipe(ident: String, label: LabelToken, propertyKeys: Seq[PropertyKeyToken], valueExpr: QueryExpression[Expression], indexMode: IndexSeekMode = IndexSeek) - (val id: Id = Id.INVALID_ID) extends Pipe { + (val id: Id = Id.INVALID_ID) extends Pipe with NodeIndexSeeker { - private val propertyIds: Array[Int] = propertyKeys.map(_.nameId.id).toArray + override val propertyIds: Array[Int] = propertyKeys.map(_.nameId.id).toArray private var reference: IndexReference = CapableIndexReference.NO_INDEX @@ -46,15 +46,12 @@ case class NodeIndexSeekPipe(ident: String, reference } - private def indexFactory(context: QueryContext) = indexMode.indexFactory(reference(context)) - valueExpr.expressions.foreach(_.registerOwningPipe(this)) protected def internalCreateResults(state: QueryState): Iterator[ExecutionContext] = { - val index = indexFactory(state.query)(state) + val indexReference = reference(state.query) val baseContext = state.createOrGetInitialContext(executionContextFactory) - val resultNodes = indexQuery(valueExpr, baseContext, state, index, label.name, propertyKeys.map(_.name)) + val resultNodes: Iterator[NodeValue] = indexSeek(state, indexReference, baseContext) resultNodes.map(node => executionContextFactory.copyWith(baseContext, ident, node)) } - } diff --git a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeeker.scala b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeeker.scala new file mode 100644 index 0000000000000..c4c533d1223e1 --- /dev/null +++ b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeeker.scala @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.cypher.internal.runtime.interpreted.pipes + +import org.neo4j.cypher.internal.frontend.v3_4.helpers.SeqCombiner.combine +import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.{Expression, InequalitySeekRangeExpression, PointDistanceSeekRangeExpression, PrefixSeekRangeExpression} +import org.neo4j.cypher.internal.runtime.interpreted.{ExecutionContext, IsList, makeValueNeoSafe} +import org.neo4j.cypher.internal.util.v3_4.{CypherTypeException, InternalException} +import org.neo4j.cypher.internal.v3_4.logical.plans._ +import org.neo4j.internal.kernel.api.{IndexQuery, IndexReference} +import org.neo4j.values.AnyValue +import org.neo4j.values.storable._ +import org.neo4j.values.virtual.NodeValue + +/** + * Mixin trait with functionality for executing logical index queries. + * + * This trait maps the logical IndexSeekMode and QueryExpression into the kernel IndexQuery classes, which + * are passed to the QueryContext for executing the index seek. + */ +trait NodeIndexSeeker { + + // dependencies + + def indexMode: IndexSeekMode + def valueExpr: QueryExpression[Expression] + def propertyIds: Array[Int] + + // index seek + + protected def indexSeek(state: QueryState, + indexReference: IndexReference, + baseContext: ExecutionContext): Iterator[NodeValue] = + indexMode match { + case _: ExactSeek => + val indexQueries = computeIndexQueries(state, baseContext) + indexQueries.flatMap(query => state.query.indexSeek(indexReference, query)).toIterator + + case LockingUniqueIndexSeek => + val indexQueries = computeExactQueries(state, baseContext) + indexQueries.flatMap(indexQuery => state.query.lockingUniqueIndexSeek(indexReference, indexQuery)).toIterator + + case _: SeekByRange => + val indexQueries = computeIndexQueries(state, baseContext) + indexQueries.flatMap(query => state.query.indexSeek(indexReference, query)).toIterator + } + + // helpers + + private val BY_VALUE: MinMaxOrdering[Value] = MinMaxOrdering(Ordering.comparatorToOrdering(Values.COMPARATOR)) + + private def computeIndexQueries(state: QueryState, row: ExecutionContext): Seq[Seq[IndexQuery]] = + valueExpr match { + + // Index range seek over range of values + case RangeQueryExpression(rangeWrapper) => + assert(propertyIds.length == 1) + rangeWrapper match { + case PrefixSeekRangeExpression(range) => + val expr = range.prefix + expr(row, state) match { + case text: TextValue => + Array(Seq(IndexQuery.stringPrefix(propertyIds.head, text.stringValue()))) + case other => + throw new CypherTypeException("Expected TextValue, got "+other ) + } + + case InequalitySeekRangeExpression(innerRange) => + val valueRange: InequalitySeekRange[Value] = innerRange.mapBounds(expr => makeValueNeoSafe(expr(row, state))) + val groupedRanges = valueRange.groupBy(bound => bound.endPoint.valueGroup()) + if (groupedRanges.size > 1) { + throw new IncorrectIndexError() + } else { + val (valueGroup, range) = groupedRanges.head + range match { + case rangeLessThan: RangeLessThan[Value] => + rangeLessThan.limit(BY_VALUE).map( limit => + List(IndexQuery.range(propertyIds.head, null, false, limit.endPoint, limit.isInclusive)) + ).toSeq + + case rangeGreaterThan: RangeGreaterThan[Value] => + rangeGreaterThan.limit(BY_VALUE).map( limit => + List(IndexQuery.range(propertyIds.head, limit.endPoint, limit.isInclusive, null, false)) + ).toSeq + + case RangeBetween(rangeGreaterThan, rangeLessThan) => + val greaterThanLimit = rangeGreaterThan.limit(BY_VALUE).get + val lessThanLimit = rangeLessThan.limit(BY_VALUE).get + + val compare = Values.COMPARATOR.compare(greaterThanLimit.endPoint, lessThanLimit.endPoint) + if (compare < 0) { + List(List(IndexQuery.range(propertyIds.head, + greaterThanLimit.endPoint, + greaterThanLimit.isInclusive, + lessThanLimit.endPoint, + lessThanLimit.isInclusive))) + } else if (compare == 0 && greaterThanLimit.isInclusive && lessThanLimit.isInclusive) { + List(List(IndexQuery.exact(propertyIds.head, lessThanLimit.endPoint))) + } else { + Nil + } + } + } + + case PointDistanceSeekRangeExpression(range) => + val valueRange = range.map(expr => makeValueNeoSafe(expr(row, state))) + (valueRange.distance, valueRange.point) match { + case (distance: NumberValue, point: PointValue) => + val bbox = point.getCoordinateReferenceSystem.getCalculator.boundingBox(point, distance.doubleValue()) + List(List(IndexQuery.range(propertyIds.head, + bbox.first(), + range.inclusive, + bbox.other(), + range.inclusive + ))) + case _ => Nil + } + } + + case exactQuery => + computeExactQueries(state, row) + } + + private def computeExactQueries(state: QueryState, row: ExecutionContext): Seq[Seq[IndexQuery.ExactPredicate]] = + valueExpr match { + case ScanQueryExpression(expr) => throw new IllegalArgumentException("this won't happen!") + + // Index exact value seek on single value + case SingleQueryExpression(expr) => + val seekValue = makeValueNeoSafe(expr(row, state)) + Array(List(IndexQuery.exact(propertyIds.head, seekValue))) + + // Index exact value seek on multiple values, by combining the results of multiple index seeks + case ManyQueryExpression(expr) => + expr(row, state) match { + case IsList(coll) => + coll.asArray().toSet[AnyValue].map( + seekAnyValue => + List(IndexQuery.exact( + propertyIds.head, + makeValueNeoSafe(seekAnyValue) + )) + ).toSeq + + case v if v == Values.NO_VALUE => Array[Seq[IndexQuery.ExactPredicate]]() + case other => throw new CypherTypeException(s"Expected list, got $other") + } + + // Index exact value seek on multiple values, making use of a composite index over all values + // eg: x in [1] AND y in ["a", "b"] AND z in [3.0] + case CompositeQueryExpression(exprs) => + assert(exprs.size == propertyIds.size) + + // seekValues = [[1], ["a", "b"], [3.0]] + val seekValues = exprs.map(expressionValues(row, state)) + + // combined = [[1, "a", 3.0], [1, "b", 3.0]] + val combined = combine(seekValues) + combined.map(seekTuple => seekTuple.zip(propertyIds) + .map { case (v,propId) => IndexQuery.exact(propId, makeValueNeoSafe(v))} + ) + } + + private def expressionValues(m: ExecutionContext, state: QueryState)(queryExpression: QueryExpression[Expression]): Seq[AnyValue] = { + queryExpression match { + + case SingleQueryExpression(inner) => + Seq(inner(m, state)) + + case ManyQueryExpression(inner) => + inner(m, state) match { + case IsList(coll) => coll.asArray() + case null => Seq.empty + case _ => throw new CypherTypeException(s"Expected the value for $inner to be a collection but it was not.") + } + + case CompositeQueryExpression(innerExpressions) => + throw new InternalException("A CompositeQueryExpression can't be nested in a CompositeQueryExpression") + + case RangeQueryExpression(rangeWrapper) => + throw new InternalException("Range queries on composite indexes not yet supported") + } + } +} diff --git a/community/cypher/interpreted-runtime/src/test/scala/org/neo4j/cypher/internal/runtime/interpreted/QueryContextAdaptation.scala b/community/cypher/interpreted-runtime/src/test/scala/org/neo4j/cypher/internal/runtime/interpreted/QueryContextAdaptation.scala index f101147613482..237534317ad2a 100644 --- a/community/cypher/interpreted-runtime/src/test/scala/org/neo4j/cypher/internal/runtime/interpreted/QueryContextAdaptation.scala +++ b/community/cypher/interpreted-runtime/src/test/scala/org/neo4j/cypher/internal/runtime/interpreted/QueryContextAdaptation.scala @@ -27,8 +27,8 @@ import org.neo4j.cypher.internal.runtime._ import org.neo4j.cypher.internal.v3_4.expressions.SemanticDirection import org.neo4j.cypher.internal.v3_4.logical.plans.QualifiedName import org.neo4j.graphdb.{Node, Path, PropertyContainer} -import org.neo4j.internal.kernel.api.IndexReference import org.neo4j.internal.kernel.api.helpers.RelationshipSelectionCursor +import org.neo4j.internal.kernel.api.{IndexQuery, IndexReference} import org.neo4j.kernel.impl.api.store.RelationshipIterator import org.neo4j.kernel.impl.core.EmbeddedProxySPI import org.neo4j.values.AnyValue @@ -91,7 +91,7 @@ trait QueryContextAdaptation { override def indexReference(label: Int, properties: Int*): IndexReference = ??? - override def indexSeek(index: IndexReference, value: Seq[Any]): scala.Iterator[NodeValue] = ??? + override def indexSeek(index: IndexReference, value: Seq[IndexQuery]): scala.Iterator[NodeValue] = ??? override def getRelationshipsForIds(node: Long, dir: SemanticDirection, types: Option[Array[Int]]): scala.Iterator[RelationshipValue] = ??? @@ -130,8 +130,6 @@ trait QueryContextAdaptation { override def nodeIsDense(node: Long): Boolean = ??? - override def indexSeekByRange(index: IndexReference, value: Any): scala.Iterator[NodeValue] = ??? - override def setLabelsOnNode(node: Long, labelIds: scala.Iterator[Int]): Int = ??? override def createRelationshipPropertyExistenceConstraint(relTypeId: Int, propertyKeyId: Int): Boolean = ??? @@ -146,7 +144,7 @@ trait QueryContextAdaptation { override def getNodesByLabelPrimitive(id: Int): PrimitiveLongIterator = ??? - override def lockingUniqueIndexSeek(index: IndexReference, values: Seq[Any]): Option[NodeValue] = ??? + override def lockingUniqueIndexSeek(index: IndexReference, values: Seq[IndexQuery.ExactPredicate]): Option[NodeValue] = ??? override def callReadOnlyProcedure(id: Int, args: Seq[Any], allowed: Array[String]): scala.Iterator[Array[AnyRef]] = ??? diff --git a/community/cypher/interpreted-runtime/src/test/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeekPipeTest.scala b/community/cypher/interpreted-runtime/src/test/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeekPipeTest.scala index 1d9ad428e6eb8..fac8968cd7f25 100644 --- a/community/cypher/interpreted-runtime/src/test/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeekPipeTest.scala +++ b/community/cypher/interpreted-runtime/src/test/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeekPipeTest.scala @@ -30,7 +30,7 @@ import org.neo4j.cypher.internal.util.v3_4.test_helpers.{CypherFunSuite, Windows import org.neo4j.cypher.internal.util.v3_4.{CypherTypeException, LabelId, PropertyKeyId} import org.neo4j.cypher.internal.v3_4.expressions.{LabelName, LabelToken, PropertyKeyName, PropertyKeyToken} import org.neo4j.cypher.internal.v3_4.logical.plans.{CompositeQueryExpression, ManyQueryExpression, SingleQueryExpression} -import org.neo4j.internal.kernel.api.IndexReference +import org.neo4j.internal.kernel.api.{IndexQuery, IndexReference} import org.neo4j.values.storable.Values.stringValue import org.neo4j.values.virtual.NodeValue @@ -253,12 +253,14 @@ class NodeIndexSeekPipeTest extends CypherFunSuite with ImplicitDummyPos { result.map(_("n")).toList should equal(List(node)) } - private def indexFor(values: (Seq[Any], Iterator[NodeValue])*): QueryContext = { + private def indexFor(values: (Seq[AnyRef], Iterator[NodeValue])*): QueryContext = { val query = mock[QueryContext] when(query.indexSeek(any(), any())).thenReturn(Iterator.empty) values.foreach { - case (searchTerm, result) => when(query.indexSeek(any(), ArgumentMatchers.eq(searchTerm))).thenReturn(result) + case (searchTerm, result) => + val indexQueries = searchTerm.map(v => IndexQuery.exact(-1, v)) + when(query.indexSeek(any(), ArgumentMatchers.eq(indexQueries))).thenReturn(result) } query diff --git a/community/cypher/runtime-util/src/main/scala/org/neo4j/cypher/internal/runtime/QueryContext.scala b/community/cypher/runtime-util/src/main/scala/org/neo4j/cypher/internal/runtime/QueryContext.scala index 6a084cc6f76e0..d56223992c2b0 100644 --- a/community/cypher/runtime-util/src/main/scala/org/neo4j/cypher/internal/runtime/QueryContext.scala +++ b/community/cypher/runtime-util/src/main/scala/org/neo4j/cypher/internal/runtime/QueryContext.scala @@ -101,10 +101,7 @@ trait QueryContext extends TokenContext { def indexReference(label: Int, properties: Int*): IndexReference - //TODO this should be `Seq[AnyValue]` - def indexSeek(index: IndexReference, values: Seq[Any]): Iterator[NodeValue] - - def indexSeekByRange(index: IndexReference, value: Any): Iterator[NodeValue] + def indexSeek(index: IndexReference, queries: Seq[IndexQuery]): Iterator[NodeValue] def indexScanByContains(index: IndexReference, value: String): Iterator[NodeValue] @@ -114,7 +111,7 @@ trait QueryContext extends TokenContext { def indexScanPrimitive(index: IndexReference): PrimitiveLongIterator - def lockingUniqueIndexSeek(index: IndexReference, values: Seq[Any]): Option[NodeValue] + def lockingUniqueIndexSeek(index: IndexReference, queries: Seq[IndexQuery.ExactPredicate]): Option[NodeValue] def getNodesByLabel(id: Int): Iterator[NodeValue] diff --git a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/IndexQuery.java b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/IndexQuery.java index d90bcfe0bdfb9..8ffff92f7e09d 100644 --- a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/IndexQuery.java +++ b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/IndexQuery.java @@ -98,14 +98,27 @@ public static RangePredicate range( int propertyKeyId, } ValueGroup valueGroup = from != null ? from.valueGroup() : to.valueGroup(); - if ( valueGroup == ValueGroup.GEOMETRY ) + switch ( valueGroup ) { + case NUMBER: + return new NumberRangePredicate( propertyKeyId, + (NumberValue)from, fromInclusive, + (NumberValue)to, toInclusive ); + + case TEXT: + return new TextRangePredicate( propertyKeyId, + (TextValue)from, fromInclusive, + (TextValue)to, toInclusive ); + + case GEOMETRY: PointValue pFrom = (PointValue)from; PointValue pTo = (PointValue)to; CoordinateReferenceSystem crs = pFrom != null ? pFrom.getCoordinateReferenceSystem() : pTo.getCoordinateReferenceSystem(); return new GeometryRangePredicate( propertyKeyId, crs, pFrom, fromInclusive, pTo, toInclusive ); + + default: + return new RangePredicate<>( propertyKeyId, valueGroup, from, fromInclusive, to, toInclusive ); } - return new RangePredicate<>( propertyKeyId, valueGroup, from, fromInclusive, to, toInclusive ); } /** diff --git a/community/lucene-index/src/main/java/org/neo4j/kernel/api/impl/schema/reader/SimpleIndexReader.java b/community/lucene-index/src/main/java/org/neo4j/kernel/api/impl/schema/reader/SimpleIndexReader.java index ec0c357e4aa60..e34f21641b4b2 100644 --- a/community/lucene-index/src/main/java/org/neo4j/kernel/api/impl/schema/reader/SimpleIndexReader.java +++ b/community/lucene-index/src/main/java/org/neo4j/kernel/api/impl/schema/reader/SimpleIndexReader.java @@ -148,14 +148,15 @@ private Query toLuceneQuery( IndexQuery... predicates ) throws IndexNotApplicabl switch ( predicate.valueGroup() ) { case NUMBER: - IndexQuery.RangePredicate np = (IndexQuery.RangePredicate) predicate; - return LuceneDocumentStructure.newInclusiveNumericRangeSeekQuery( (Number)np.fromValue().asObject(), - (Number)np.toValue().asObject() ); + IndexQuery.NumberRangePredicate np = (IndexQuery.NumberRangePredicate) predicate; + return LuceneDocumentStructure.newInclusiveNumericRangeSeekQuery( np.from(), + np.to() ); case TEXT: - IndexQuery.RangePredicate sp = (IndexQuery.RangePredicate) predicate; - return LuceneDocumentStructure.newRangeSeekByStringQuery( (String)sp.fromValue().asObject(), sp.fromInclusive(), - (String)sp.toValue().asObject(), sp.toInclusive() ); + IndexQuery.TextRangePredicate sp = (IndexQuery.TextRangePredicate) predicate; + return LuceneDocumentStructure.newRangeSeekByStringQuery( sp.from(), sp.fromInclusive(), + sp.to(), sp.toInclusive() ); + default: throw new UnsupportedOperationException( format( "Range scans of value group %s are not supported", predicate.valueGroup() ) ); diff --git a/enterprise/cypher/slotted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/slotted/pipes/NodeIndexSeekSlottedPipe.scala b/enterprise/cypher/slotted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/slotted/pipes/NodeIndexSeekSlottedPipe.scala index 37c57296de2ef..2f1b44c7160c6 100644 --- a/enterprise/cypher/slotted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/slotted/pipes/NodeIndexSeekSlottedPipe.scala +++ b/enterprise/cypher/slotted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/slotted/pipes/NodeIndexSeekSlottedPipe.scala @@ -23,13 +23,13 @@ import org.neo4j.cypher.internal.compatibility.v3_4.runtime.SlotConfiguration import org.neo4j.cypher.internal.runtime.QueryContext import org.neo4j.cypher.internal.runtime.interpreted.ExecutionContext import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.Expression -import org.neo4j.cypher.internal.runtime.interpreted.commands.indexQuery import org.neo4j.cypher.internal.runtime.interpreted.pipes._ import org.neo4j.cypher.internal.runtime.slotted.SlottedExecutionContext import org.neo4j.cypher.internal.util.v3_4.attribution.Id import org.neo4j.cypher.internal.v3_4.expressions.{LabelToken, PropertyKeyToken} import org.neo4j.cypher.internal.v3_4.logical.plans.QueryExpression import org.neo4j.internal.kernel.api.{CapableIndexReference, IndexReference} +import org.neo4j.values.virtual.NodeValue case class NodeIndexSeekSlottedPipe(ident: String, label: LabelToken, @@ -38,11 +38,11 @@ case class NodeIndexSeekSlottedPipe(ident: String, indexMode: IndexSeekMode = IndexSeek, slots: SlotConfiguration, argumentSize: SlotConfiguration.Size) - (val id: Id = Id.INVALID_ID) extends Pipe { + (val id: Id = Id.INVALID_ID) extends Pipe with NodeIndexSeeker { private val offset = slots.getLongOffsetFor(ident) - private val propertyIds: Array[Int] = propertyKeys.map(_.nameId.id).toArray + override val propertyIds: Array[Int] = propertyKeys.map(_.nameId.id).toArray private var reference: IndexReference = CapableIndexReference.NO_INDEX @@ -53,14 +53,12 @@ case class NodeIndexSeekSlottedPipe(ident: String, reference } - private def indexFactory(context: QueryContext) = indexMode.indexFactory(reference(context)) - valueExpr.expressions.foreach(_.registerOwningPipe(this)) protected def internalCreateResults(state: QueryState): Iterator[ExecutionContext] = { - val index = indexFactory(state.query)(state) + val indexReference = reference(state.query) val baseContext = state.createOrGetInitialContext(executionContextFactory) - val resultNodes = indexQuery(valueExpr, baseContext, state, index, label.name, propertyKeys.map(_.name)) + val resultNodes: Iterator[NodeValue] = indexSeek(state, indexReference, baseContext) resultNodes.map { node => val context = SlottedExecutionContext(slots) state.copyArgumentStateTo(context, argumentSize.nLongs, argumentSize.nReferences)