Skip to content

Commit

Permalink
Refactor interpreted/slotted runtime index seeks
Browse files Browse the repository at this point in the history
The logic for converting logical index seeks into executable
queries has been gathered from various places into the new
trait NodeIndexSeeker. This trait converts logical index seeks,
range scans, special string seeks and distance queries into the
Kernel API IndexQuery classes, which allows direct querying.

This improves the type definitions in touched QueryContext methods,
makes the functionality easier to understand, and reduces object
creation. It also gives a big reduction of TransactionBoundQueryContext.

Note that this refactoring was enabled but the switch to cypher
versioning on the logical plan level, which allows us to utilize
kernel types in runtime code.
  • Loading branch information
fickludd authored and Lojjs committed Mar 16, 2018
1 parent 7c12033 commit 3564deb
Show file tree
Hide file tree
Showing 15 changed files with 310 additions and 428 deletions.
Expand Up @@ -19,8 +19,6 @@
*/ */
package org.neo4j.cypher.internal.v3_4.logical.plans 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 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. same node and property (n.prop) during planning, esp. for generating index seek by range plans.
Expand Down
Expand Up @@ -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.expressions.SemanticDirection
import org.neo4j.cypher.internal.v3_4.logical.plans.QualifiedName import org.neo4j.cypher.internal.v3_4.logical.plans.QualifiedName
import org.neo4j.graphdb.{Node, Path, PropertyContainer} 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.internal.kernel.api.helpers.RelationshipSelectionCursor
import org.neo4j.kernel.impl.api.store.RelationshipIterator import org.neo4j.kernel.impl.api.store.RelationshipIterator
import org.neo4j.kernel.impl.core.EmbeddedProxySPI import org.neo4j.kernel.impl.core.EmbeddedProxySPI
Expand Down Expand Up @@ -100,7 +100,7 @@ class ExceptionTranslatingQueryContext(val inner: QueryContext) extends QueryCon
override def dropIndexRule(descriptor: IndexDescriptor) = override def dropIndexRule(descriptor: IndexDescriptor) =
translateException(inner.dropIndexRule(descriptor)) 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)) translateException(inner.indexSeek(index, values))


override def getNodesByLabel(id: Int): Iterator[NodeValue] = override def getNodesByLabel(id: Int): Iterator[NodeValue] =
Expand Down Expand Up @@ -198,7 +198,7 @@ class ExceptionTranslatingQueryContext(val inner: QueryContext) extends QueryCon
override def getRelTypeName(id: Int) = override def getRelTypeName(id: Int) =
translateException(inner.getRelTypeName(id)) 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)) translateException(inner.lockingUniqueIndexSeek(index, values))


override def getImportURL(url: URL) = override def getImportURL(url: URL) =
Expand Down Expand Up @@ -228,9 +228,6 @@ class ExceptionTranslatingQueryContext(val inner: QueryContext) extends QueryCon
override def getRelationshipFor(relationshipId: Long, typeId: Int, startNodeId: Long, endNodeId: Long): RelationshipValue = override def getRelationshipFor(relationshipId: Long, typeId: Int, startNodeId: Long, endNodeId: Long): RelationshipValue =
translateException(inner.getRelationshipFor(relationshipId, typeId, startNodeId, endNodeId)) 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) = override def indexScanByContains(index: IndexReference, value: String) =
translateException(inner.indexScanByContains(index, value)) translateException(inner.indexScanByContains(index, value))


Expand Down
Expand Up @@ -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 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)) 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 indexScan(index: IndexReference): Iterator[NodeValue] = manyDbHits(inner.indexScan(index))


override def indexScanPrimitive(index: IndexReference): PrimitiveLongIterator = manyDbHits(inner.indexScanPrimitive(index)) override def indexScanPrimitive(index: IndexReference): PrimitiveLongIterator = manyDbHits(inner.indexScanPrimitive(index))
Expand Down Expand Up @@ -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 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)) singleDbHit(inner.lockingUniqueIndexSeek(index, values))


override def getRelTypeId(relType: String): Int = singleDbHit(inner.getRelTypeId(relType)) override def getRelTypeId(relType: String): Int = singleDbHit(inner.getRelTypeId(relType))
Expand Down
Expand Up @@ -20,20 +20,18 @@
package org.neo4j.cypher.internal.runtime.interpreted package org.neo4j.cypher.internal.runtime.interpreted


import java.net.URL import java.net.URL
import java.time.LocalDate
import java.util.function.Predicate import java.util.function.Predicate


import org.neo4j.collection.RawIterator import org.neo4j.collection.RawIterator
import org.neo4j.collection.primitive.{PrimitiveLongIterator, PrimitiveLongResourceIterator} import org.neo4j.collection.primitive.{PrimitiveLongIterator, PrimitiveLongResourceIterator}
import org.neo4j.cypher.InternalException
import org.neo4j.cypher.internal.javacompat.GraphDatabaseCypherService import org.neo4j.cypher.internal.javacompat.GraphDatabaseCypherService
import org.neo4j.cypher.internal.planner.v3_4.spi.{IdempotentResult, IndexDescriptor} import org.neo4j.cypher.internal.planner.v3_4.spi.{IdempotentResult, IndexDescriptor}
import org.neo4j.cypher.internal.runtime._ 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.TransactionBoundQueryContext.IndexSearchMonitor
import org.neo4j.cypher.internal.runtime.interpreted.commands.convert.DirectionConverter.toGraphDb 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.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
import org.neo4j.cypher.internal.v3_4.expressions.SemanticDirection.{BOTH, INCOMING, OUTGOING} import org.neo4j.cypher.internal.v3_4.expressions.SemanticDirection.{BOTH, INCOMING, OUTGOING}
import org.neo4j.cypher.internal.v3_4.logical.plans._ import org.neo4j.cypher.internal.v3_4.logical.plans._
Expand Down Expand Up @@ -62,7 +60,7 @@ import org.neo4j.kernel.impl.coreapi.PropertyContainerLocker
import org.neo4j.kernel.impl.query.Neo4jTransactionalContext import org.neo4j.kernel.impl.query.Neo4jTransactionalContext
import org.neo4j.kernel.impl.util.ValueUtils.{fromNodeProxy, fromRelationshipProxy} import org.neo4j.kernel.impl.util.ValueUtils.{fromNodeProxy, fromRelationshipProxy}
import org.neo4j.kernel.impl.util.{DefaultValueMapper, NodeProxyWrappingNodeValue, RelationshipProxyWrappingValue} 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.virtual.{ListValue, NodeValue, RelationshipValue, VirtualValues}
import org.neo4j.values.{AnyValue, ValueMapper} import org.neo4j.values.{AnyValue, ValueMapper}


Expand Down Expand Up @@ -241,110 +239,26 @@ sealed class TransactionBoundQueryContext(val transactionalContext: Transactiona
case e: NotFoundException => throw new EntityNotFoundException(s"Relationship with id $relationshipId", e) case e: NotFoundException => throw new EntityNotFoundException(s"Relationship with id $relationshipId", e)
} }


override def indexSeek(index: IndexReference, values: Seq[Any]): Iterator[NodeValue] = { override def indexSeek(index: IndexReference, predicates: Seq[IndexQuery]): 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")
}


private def indexSeekByPrefixRange(index: IndexReference, range: InequalitySeekRange[Any]): scala.Iterator[NodeValue] = { val SEEKABLE_VALUE_GROUPS = Array(ValueGroup.NUMBER, ValueGroup.TEXT, ValueGroup.GEOMETRY, ValueGroup.DATE)
val groupedRanges = range.groupBy { (bound: Bound[Any]) => val impossiblePredicate =
bound.endPoint match { predicates.exists {
case n: Number => classOf[Number] case p:IndexQuery.ExactPredicate => p.value() == Values.NO_VALUE
case s: String => classOf[String] case p:IndexQuery =>
case c: Character => classOf[String] if (p.valueGroup() == ValueGroup.NO_VALUE) true
case p: PointValue => classOf[PointValue] else if (!SEEKABLE_VALUE_GROUPS.contains(p.valueGroup()))
case d: LocalDate => classOf[LocalDate] throw new IncorrectIndexError()
case _ => classOf[Any] 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] = override def indexReference(label: Int,
seek(index, IndexQuery.stringPrefix(index.properties()(0), prefix)) properties: Int*): IndexReference =
transactionalContext.kernelTransaction.schemaRead().index(label, properties:_*)


private def seek(index: IndexReference, query: IndexQuery*) = { private def seek(index: IndexReference, query: IndexQuery*) = {
val nodeCursor = allocateAndTraceNodeValueIndexCursor() val nodeCursor = allocateAndTraceNodeValueIndexCursor()
Expand All @@ -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] = override def indexScan(index: IndexReference): Iterator[NodeValue] =
JavaConversionSupport.mapToScalaENFXSafe(indexScanPrimitive(index))(nodeOps.getByIdIfExists) JavaConversionSupport.mapToScalaENFXSafe(indexScanPrimitive(index))(nodeOps.getByIdIfExists)


Expand All @@ -500,10 +295,10 @@ sealed class TransactionBoundQueryContext(val transactionalContext: Transactiona
override def indexScanByEndsWith(index: IndexReference, value: String): Iterator[NodeValue] = override def indexScanByEndsWith(index: IndexReference, value: String): Iterator[NodeValue] =
seek(index, IndexQuery.stringSuffix(index.properties()(0), value)) seek(index, IndexQuery.stringSuffix(index.properties()(0), value))


override def lockingUniqueIndexSeek(indexReference: IndexReference, values: Seq[Any]): Option[NodeValue] = { override def lockingUniqueIndexSeek(indexReference: IndexReference, queries: Seq[IndexQuery.ExactPredicate]): Option[NodeValue] = {
indexSearchMonitor.lockingUniqueIndexSeek(indexReference, values) indexSearchMonitor.lockingUniqueIndexSeek(indexReference, queries)
val index = DefaultIndexReference.general(indexReference.label(), indexReference.properties():_*) 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:_*) val nodeId = transactionalContext.kernelTransaction.dataRead().nodeUniqueIndexSeek(index, predicates:_*)
if (StatementConstants.NO_SUCH_NODE == nodeId) None else Some(nodeOps.getById(nodeId)) if (StatementConstants.NO_SUCH_NODE == nodeId) None else Some(nodeOps.getById(nodeId))
} }
Expand Down

0 comments on commit 3564deb

Please sign in to comment.