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

org.neo4j.cypher.internal.logical.plans.IndexSeek.scala Maven / Gradle / Ivy

/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [http://neo4j.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.logical.plans

import org.neo4j.cypher.internal.expressions.EntityType
import org.neo4j.cypher.internal.expressions.Expression
import org.neo4j.cypher.internal.expressions.LabelToken
import org.neo4j.cypher.internal.expressions.ListLiteral
import org.neo4j.cypher.internal.expressions.NODE_TYPE
import org.neo4j.cypher.internal.expressions.PropertyKeyName
import org.neo4j.cypher.internal.expressions.PropertyKeyToken
import org.neo4j.cypher.internal.expressions.RELATIONSHIP_TYPE
import org.neo4j.cypher.internal.expressions.RelationshipTypeToken
import org.neo4j.cypher.internal.expressions.SignedDecimalIntegerLiteral
import org.neo4j.cypher.internal.expressions.StringLiteral
import org.neo4j.cypher.internal.util.InputPosition
import org.neo4j.cypher.internal.util.LabelId
import org.neo4j.cypher.internal.util.NonEmptyList
import org.neo4j.cypher.internal.util.PropertyKeyId
import org.neo4j.cypher.internal.util.RelTypeId
import org.neo4j.cypher.internal.util.attribution.IdGen
import org.neo4j.graphdb.schema.IndexType

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer

/**
 * Helper object for constructing node and relationship index operators from strings.
 */
object IndexSeek {

  // primitives
  private val ID = "([a-zA-Z][a-zA-Z0-9_]*)"
  private val VALUE = "([0-9]+|'.*'|\\?\\?\\?)"
  private val INT = "([0-9]+)".r
  private val STRING = s"'(.*)'".r
  private val PARAM = "???"

  // entry points
  private val NODE_INDEX_SEEK_PATTERN = s"$ID: ?$ID ?\\(([^\\)]+)\\)".r
  private val REL_INDEX_SEEK_PATTERN =  s"\\($ID\\)(?)\\($ID\\)".r


  // predicates
  private val EXACT = s"$ID ?= ?$VALUE".r
  private val EXACT_TWO = s"$ID = ?$VALUE OR ?$VALUE".r
  private val IN = s"$ID IN \\?\\?\\?".r
  private val EXISTS = s"$ID".r
  private val LESS_THAN = s"$ID ?< ?$VALUE".r
  private val LESS_THAN_OR_EQ = s"$ID ?<= ?$VALUE".r
  private val GREATER_THAN = s"$ID ?> ?$VALUE".r
  private val GREATER_THAN_OR_EQ = s"$ID ?>= ?$VALUE".r
  private val GREATER_THAN_LESS_THAN = s"$VALUE ?< $ID ?< $VALUE".r
  private val GREATER_THAN_EQ_LESS_THAN = s"$VALUE ?<= $ID ?< $VALUE".r
  private val GREATER_THAN_LESS_THAN_EQ = s"$VALUE ?< $ID ?<= $VALUE".r
  private val GREATER_THAN_EQ_LESS_THAN_EQ = s"$VALUE ?<= $ID ?<= $VALUE".r
  private val LESS_THAN_LESS_THAN = s"$VALUE > $ID < $VALUE".r
  private val LESS_THAN_EQ_LESS_THAN = s"$VALUE >= $ID < $VALUE".r
  private val LESS_THAN_EQ_LESS_THAN_EQ = s"$VALUE >= $ID <= $VALUE".r
  private val LESS_THAN_LESS_THAN_EQ = s"$VALUE > $ID <= $VALUE".r
  private val GREATER_THAN_GREATER_THAN = s"$VALUE < $ID > $VALUE".r
  private val GREATER_THAN_EQ_GREATER_THAN = s"$VALUE <= $ID > $VALUE".r
  private val GREATER_THAN_EQ_GREATER_THAN_EQ = s"$VALUE <= $ID >= $VALUE".r
  private val GREATER_THAN_GREATER_THAN_EQ = s"$VALUE < $ID >= $VALUE".r
  private val STARTS_WITH = s"$ID STARTS WITH $VALUE".r
  private val ENDS_WITH = s"$ID ENDS WITH $VALUE".r
  private val CONTAINS = s"$ID CONTAINS $VALUE".r

  private val pos = InputPosition.NONE

  /**
   * Extracts just the label from an index seek string
   */
  def labelFromIndexSeekString(indexSeekString: String): String = {
    val NODE_INDEX_SEEK_PATTERN(_, labelStr, _) = indexSeekString.trim
    labelStr
  }

  /**
   * Extracts just the relationship type from an index seek string
   */
  def relTypeFromIndexSeekString(indexSeekString: String): String = {
    val REL_INDEX_SEEK_PATTERN(_, _, _, typeStr, _, _, _) = indexSeekString.trim
    typeStr
  }

  /**
   * Construct a node index seek/scan operator by parsing a string.
   */
  def nodeIndexSeek(indexSeekString: String,
                    getValue: String => GetValueFromIndexBehavior = _ => DoNotGetValue,
                    indexOrder: IndexOrder = IndexOrderNone,
                    paramExpr: Iterable[Expression] = Seq.empty,
                    argumentIds: Set[String] = Set.empty,
                    propIds: Option[PartialFunction[String, Int]] = None,
                    labelId: Int = 0,
                    unique: Boolean = false,
                    customQueryExpression: Option[QueryExpression[Expression]] = None,
                    indexType: IndexType = IndexType.BTREE)(implicit idGen: IdGen): NodeIndexLeafPlan = {

    val NODE_INDEX_SEEK_PATTERN(node, labelStr, predicateStr) = indexSeekString.trim
    val label = LabelToken(labelStr, LabelId(labelId))
    val predicates = predicateStr.split(',').map(_.trim)

    def createSeek(properties: Seq[IndexedProperty],
                   valueExpr: QueryExpression[Expression]): NodeIndexSeekLeafPlan =
      if (unique) {
        NodeUniqueIndexSeek(node, label, properties, valueExpr, argumentIds, indexOrder, indexType)
      } else {
        NodeIndexSeek(node, label, properties, valueExpr, argumentIds, indexOrder, indexType)
      }


    def createEndsWithScan(property: IndexedProperty, valueExpr: Expression): NodeIndexLeafPlan = {
      NodeIndexEndsWithScan(node, label, property, valueExpr, argumentIds, indexOrder, indexType)
    }

    def createContainsScan(property: IndexedProperty, valueExpr: Expression): NodeIndexLeafPlan = {
      NodeIndexContainsScan(node, label, property, valueExpr, argumentIds, indexOrder, indexType)
    }

    def createScan(properties: Seq[IndexedProperty]): NodeIndexLeafPlan = {
      NodeIndexScan(node, label, properties, argumentIds, indexOrder, indexType)
    }

    createPlan[NodeIndexLeafPlan](
      predicates,
      NODE_TYPE,
      getValue,
      paramExpr,
      propIds,
      customQueryExpression,
      createSeek,
      createScan,
      createEndsWithScan,
      createContainsScan)
  }

  /**
   * Construct a relationship index seek/scan operator by parsing a string.
   */
  def relationshipIndexSeek(indexSeekString: String,
                            getValue: String => GetValueFromIndexBehavior = _ => DoNotGetValue,
                            indexOrder: IndexOrder = IndexOrderNone,
                            paramExpr: Iterable[Expression] = Seq.empty,
                            argumentIds: Set[String] = Set.empty,
                            propIds: Option[PartialFunction[String, Int]] = None,
                            typeId: Int = 0,
                            customQueryExpression: Option[QueryExpression[Expression]] = None,
                            indexType: IndexType = IndexType.BTREE)(implicit idGen: IdGen): RelationshipIndexLeafPlan = {

    val REL_INDEX_SEEK_PATTERN(leftNode, incoming, rel, typeStr, predicateStr, outgoing, rightNode) = indexSeekString.trim
    val (startNode, endNode, directed) = (incoming, outgoing) match {
      case ("<", "") => (rightNode, leftNode, true)
      case ("", ">") => (leftNode, rightNode, true)
      case ("", "") => (leftNode, rightNode, false)
      case _ => throw new UnsupportedOperationException(s"Direction $incoming-$outgoing not supported")
    }
    val typeToken = RelationshipTypeToken(typeStr, RelTypeId(typeId))
    val predicates: Array[String] = predicateStr.split(',').map(_.trim)

    def createSeek(properties: Seq[IndexedProperty],
                   valueExpr: QueryExpression[Expression]): RelationshipIndexLeafPlan = {
      if (directed) {
        DirectedRelationshipIndexSeek(rel,  startNode, endNode, typeToken, properties, valueExpr, argumentIds, indexOrder, indexType)
      } else {
        UndirectedRelationshipIndexSeek(rel,  startNode, endNode, typeToken, properties, valueExpr, argumentIds, indexOrder, indexType)
      }
    }

    def createEndsWithScan(property: IndexedProperty, valueExpr: Expression): RelationshipIndexLeafPlan = {
      if (directed) {
        DirectedRelationshipIndexEndsWithScan(rel, startNode, endNode, typeToken, property, valueExpr, argumentIds, indexOrder, indexType)
      } else {
        UndirectedRelationshipIndexEndsWithScan(rel, startNode, endNode, typeToken, property, valueExpr, argumentIds, indexOrder, indexType)
      }
    }

    def createContainsScan(property: IndexedProperty, valueExpr: Expression): RelationshipIndexLeafPlan = {
      if (directed) {
        DirectedRelationshipIndexContainsScan(rel, startNode, endNode, typeToken, property, valueExpr, argumentIds, indexOrder, indexType)
      } else {
        UndirectedRelationshipIndexContainsScan(rel, startNode, endNode, typeToken, property, valueExpr, argumentIds, indexOrder, indexType)
      }
    }

    def createScan(properties: Seq[IndexedProperty]): RelationshipIndexLeafPlan = {
      if (directed) {
        DirectedRelationshipIndexScan(rel, startNode, endNode, typeToken, properties, argumentIds, indexOrder, indexType)
      } else {
        UndirectedRelationshipIndexScan(rel, startNode, endNode, typeToken, properties, argumentIds, indexOrder, indexType)
      }
    }

    createPlan[RelationshipIndexLeafPlan](
      predicates,
      RELATIONSHIP_TYPE,
      getValue,
      paramExpr,
      propIds,
      customQueryExpression,
      createSeek,
      createScan,
      createEndsWithScan,
      createContainsScan)
  }

  private def createPlan[T <: LogicalLeafPlan](predicates: Array[String],
                                               entityType: EntityType,
                                               getValue: String => GetValueFromIndexBehavior = _ => DoNotGetValue,
                                               paramExpr: Iterable[Expression],
                                               propIds: Option[PartialFunction[String, Int]],
                                               customQueryExpression: Option[QueryExpression[Expression]],
                                               createSeek: (Seq[IndexedProperty], QueryExpression[Expression]) => T,
                                               createScan: (Seq[IndexedProperty]) => T,
                                               createEndsWithScan: (IndexedProperty, Expression) => T,
                                               createContainsScan: (IndexedProperty, Expression) => T): T = {
    var propId = -1
    def nextPropId(): Int = {
      propId += 1
      propId
    }

    def prop(prop: String): IndexedProperty = {
      val id =
        if (propIds.isDefined) {
          val func = propIds.get
          if (func.isDefinedAt(prop)) {
            func(prop)
          } else {
            throw new IllegalArgumentException(s"Property `$prop` has no provided id. Either provide ids for all properties, or provide none.")
          }
        } else {
          nextPropId()
        }

      IndexedProperty(PropertyKeyToken(PropertyKeyName(prop)(pos), PropertyKeyId(id)), getValue(prop), entityType)
    }

    val paramQueue = mutable.Queue(paramExpr.toArray:_*)
    def value(value: String): Expression =
      value match {
        case INT(int) => SignedDecimalIntegerLiteral(int)(pos)
        case STRING(str) => StringLiteral(str)(pos)
        case PARAM =>
          if (paramQueue.isEmpty) throw new IllegalArgumentException("Cannot use parameter syntax '???' without providing parameter expression 'paramExpr'")
          paramQueue.dequeue()
        case _ => throw new IllegalArgumentException(s"Value `$value` is not supported")
      }

    if (predicates.length == 1) {
      predicates.head match {
        case EXACT_TWO(propStr, valueAStr, valueBStr) =>
          val valueExpr = ManyQueryExpression(ListLiteral(Seq(value(valueAStr), value(valueBStr)))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case IN(propStr) =>
          val valueExpr = ManyQueryExpression(paramQueue.dequeue())
          createSeek(List(prop(propStr)), valueExpr)

        case EXACT(propStr, valueStr) =>
          val valueExpr = SingleQueryExpression(value(valueStr))
          createSeek(List(prop(propStr)), valueExpr)

        case LESS_THAN(propStr, valueStr) =>
          val valueExpr = RangeQueryExpression(InequalitySeekRangeWrapper(RangeLessThan(NonEmptyList(ExclusiveBound(value(valueStr)))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case LESS_THAN_OR_EQ(propStr, valueStr) =>
          val valueExpr = RangeQueryExpression(InequalitySeekRangeWrapper(RangeLessThan(NonEmptyList(InclusiveBound(value(valueStr)))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case GREATER_THAN(propStr, valueStr) =>
          val valueExpr = RangeQueryExpression(InequalitySeekRangeWrapper(RangeGreaterThan(NonEmptyList(ExclusiveBound(value(valueStr)))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case GREATER_THAN_OR_EQ(propStr, valueStr) =>
          val valueExpr = RangeQueryExpression(InequalitySeekRangeWrapper(RangeGreaterThan(NonEmptyList(InclusiveBound(value(valueStr)))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case GREATER_THAN_LESS_THAN(firstValueStr, propStr, secondValueStr) =>
          val valueExpr = RangeQueryExpression(InequalitySeekRangeWrapper(
            RangeBetween(
              RangeGreaterThan(NonEmptyList(ExclusiveBound(value(firstValueStr)))),
              RangeLessThan(NonEmptyList(ExclusiveBound(value(secondValueStr))))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case GREATER_THAN_EQ_LESS_THAN(firstValueStr, propStr, secondValueStr) =>
          val valueExpr = RangeQueryExpression(InequalitySeekRangeWrapper(
            RangeBetween(
              RangeGreaterThan(NonEmptyList(InclusiveBound(value(firstValueStr)))),
              RangeLessThan(NonEmptyList(ExclusiveBound(value(secondValueStr))))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case GREATER_THAN_LESS_THAN_EQ(firstValueStr, propStr, secondValueStr) =>
          val valueExpr = RangeQueryExpression(InequalitySeekRangeWrapper(
            RangeBetween(
              RangeGreaterThan(NonEmptyList(ExclusiveBound(value(firstValueStr)))),
              RangeLessThan(NonEmptyList(InclusiveBound(value(secondValueStr))))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case GREATER_THAN_EQ_LESS_THAN_EQ(firstValueStr, propStr, secondValueStr) =>
          val valueExpr = RangeQueryExpression(InequalitySeekRangeWrapper(
            RangeBetween(
              RangeGreaterThan(NonEmptyList(InclusiveBound(value(firstValueStr)))),
              RangeLessThan(NonEmptyList(InclusiveBound(value(secondValueStr))))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case LESS_THAN_LESS_THAN(firstValueStr, propStr, secondValueStr) =>
          val valueExpr =
            RangeQueryExpression(InequalitySeekRangeWrapper(RangeLessThan(NonEmptyList(ExclusiveBound(value(firstValueStr)),
              ExclusiveBound(value(secondValueStr)))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case LESS_THAN_EQ_LESS_THAN(firstValueStr, propStr, secondValueStr) =>
          val valueExpr =
            RangeQueryExpression(InequalitySeekRangeWrapper(RangeLessThan(NonEmptyList(InclusiveBound(value(firstValueStr)),
              ExclusiveBound(value(secondValueStr)))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case LESS_THAN_LESS_THAN_EQ(firstValueStr, propStr, secondValueStr) =>
          val valueExpr =
            RangeQueryExpression(InequalitySeekRangeWrapper(RangeLessThan(NonEmptyList(ExclusiveBound(value(firstValueStr)),
              InclusiveBound(value(secondValueStr)))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case LESS_THAN_EQ_LESS_THAN_EQ(firstValueStr, propStr, secondValueStr) =>
          val valueExpr =
            RangeQueryExpression(InequalitySeekRangeWrapper(RangeLessThan(NonEmptyList(InclusiveBound(value(firstValueStr)),
              InclusiveBound(value(secondValueStr)))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case GREATER_THAN_GREATER_THAN(firstValueStr, propStr, secondValueStr) =>
          val valueExpr =
            RangeQueryExpression(InequalitySeekRangeWrapper(RangeGreaterThan(NonEmptyList(ExclusiveBound(value(firstValueStr)),
              ExclusiveBound(value(secondValueStr)))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case GREATER_THAN_EQ_GREATER_THAN(firstValueStr, propStr, secondValueStr) =>
          val valueExpr =
            RangeQueryExpression(InequalitySeekRangeWrapper(RangeGreaterThan(NonEmptyList(InclusiveBound(value(firstValueStr)),
              ExclusiveBound(value(secondValueStr)))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case GREATER_THAN_GREATER_THAN_EQ(firstValueStr, propStr, secondValueStr) =>
          val valueExpr =
            RangeQueryExpression(InequalitySeekRangeWrapper(RangeGreaterThan(NonEmptyList(ExclusiveBound(value(firstValueStr)),
              InclusiveBound(value(secondValueStr)))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case GREATER_THAN_EQ_GREATER_THAN_EQ(firstValueStr, propStr, secondValueStr) =>
          val valueExpr =
            RangeQueryExpression(InequalitySeekRangeWrapper(RangeGreaterThan(NonEmptyList(InclusiveBound(value(firstValueStr)),
              InclusiveBound(value(secondValueStr)))))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case STARTS_WITH(propStr, string) =>
          val valueExpr = RangeQueryExpression(PrefixSeekRangeWrapper(PrefixRange(value(string)))(pos))
          createSeek(List(prop(propStr)), valueExpr)

        case ENDS_WITH(propStr, string) =>
          createEndsWithScan(prop(propStr), value(string))

        case CONTAINS(propStr, string) =>
          createContainsScan(prop(propStr), value(string))

        case EXISTS(propStr) if customQueryExpression.isDefined =>
          createSeek(List(prop(propStr)), customQueryExpression.get)

        case EXISTS(propStr) =>
          createScan(Seq(prop(propStr)))
      }
    } else if (predicates.length > 1 && customQueryExpression.isEmpty) {
      val properties = new ArrayBuffer[IndexedProperty]()
      val valueExprs = new ArrayBuffer[QueryExpression[Expression]]()

      val equalityPred = predicates.takeWhile {
        case EXACT_TWO(_, _, _) => true
        case EXACT(_, _) => true
        case _ => false
      }
      val equalityAndNextPred =
        if (equalityPred sameElements predicates) equalityPred
        else equalityPred :+ predicates(equalityPred.length)

      val restOfPred = predicates.slice(equalityAndNextPred.length, predicates.length)

      for (predicate <- equalityAndNextPred)
        predicate match {
          case EXACT_TWO(propStr, valueAStr, valueBStr) =>
            valueExprs += ManyQueryExpression(ListLiteral(Seq(value(valueAStr), value(valueBStr)))(pos))
            properties += prop(propStr)
          case EXACT(propStr, valueStr) =>
            valueExprs += SingleQueryExpression(value(valueStr))
            properties += prop(propStr)
          case LESS_THAN(propStr, valueStr) =>
            valueExprs += RangeQueryExpression(InequalitySeekRangeWrapper(RangeLessThan(NonEmptyList(ExclusiveBound(value(valueStr)))))(pos))
            properties += prop(propStr)
          case LESS_THAN_OR_EQ(propStr, valueStr) =>
            valueExprs += RangeQueryExpression(InequalitySeekRangeWrapper(RangeLessThan(NonEmptyList(InclusiveBound(value(valueStr)))))(pos))
            properties += prop(propStr)
          case GREATER_THAN(propStr, valueStr) =>
            valueExprs += RangeQueryExpression(InequalitySeekRangeWrapper(RangeGreaterThan(NonEmptyList(ExclusiveBound(value(valueStr)))))(pos))
            properties += prop(propStr)
          case GREATER_THAN_OR_EQ(propStr, valueStr) =>
            valueExprs += RangeQueryExpression(InequalitySeekRangeWrapper(RangeGreaterThan(NonEmptyList(InclusiveBound(value(valueStr)))))(pos))
            properties += prop(propStr)
          case STARTS_WITH(propStr, string) =>
            valueExprs += RangeQueryExpression(PrefixSeekRangeWrapper(PrefixRange(value(string)))(pos))
            properties += prop(propStr)
          case EXISTS(propStr) =>
            valueExprs += ExistenceQueryExpression()
            properties += prop(propStr)
          case ENDS_WITH(propStr, _) =>
            valueExprs += ExistenceQueryExpression()
            properties += prop(propStr)
          case CONTAINS(propStr, _) =>
            valueExprs += ExistenceQueryExpression()
            properties += prop(propStr)
          case _ => throw new IllegalArgumentException(s"$predicate is not allowed in composite seeks.")
        }

      for (predicate <- restOfPred)
        predicate match {
          case EXACT_TWO(propStr, _, _) =>
            valueExprs += ExistenceQueryExpression()
            properties += prop(propStr)
          case EXACT(propStr, _) =>
            valueExprs += ExistenceQueryExpression()
            properties += prop(propStr)
          case LESS_THAN(propStr, _) =>
            valueExprs += ExistenceQueryExpression()
            properties += prop(propStr)
          case LESS_THAN_OR_EQ(propStr, _) =>
            valueExprs += ExistenceQueryExpression()
            properties += prop(propStr)
          case GREATER_THAN(propStr, _) =>
            valueExprs += ExistenceQueryExpression()
            properties += prop(propStr)
          case GREATER_THAN_OR_EQ(propStr, _) =>
            valueExprs += ExistenceQueryExpression()
            properties += prop(propStr)
          case STARTS_WITH(propStr, _) =>
            valueExprs += ExistenceQueryExpression()
            properties += prop(propStr)
          case EXISTS(propStr) =>
            valueExprs += ExistenceQueryExpression()
            properties += prop(propStr)
          case ENDS_WITH(propStr, _) =>
            valueExprs += ExistenceQueryExpression()
            properties += prop(propStr)
          case CONTAINS(propStr, _) =>
            valueExprs += ExistenceQueryExpression()
            properties += prop(propStr)
          case _ => throw new IllegalArgumentException(s"$predicate is not allowed in composite seeks.")
        }

      if (equalityAndNextPred.length == 1 && (equalityAndNextPred.head match {
        case EXISTS(_) => true
        case ENDS_WITH(_, _) => true
        case CONTAINS(_, _) => true
        case _ => false
      }))
        createScan(properties)
      else
        createSeek(properties, CompositeQueryExpression(valueExprs))
    } else if (predicates.length > 1 && customQueryExpression.isDefined) {
      createSeek(predicates.map(prop), customQueryExpression.get)
    } else
      throw new IllegalArgumentException(s"Cannot parse `${predicates.mkString("Array(", ", ", ")")}` as an index seek.")
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy