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

org.neo4j.cypher.internal.logical.plans.ResolvedFunctionInvocation.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.ast.semantics.SemanticCheck
import org.neo4j.cypher.internal.ast.semantics.SemanticCheckResult.error
import org.neo4j.cypher.internal.ast.semantics.SemanticCheckResult.success
import org.neo4j.cypher.internal.ast.semantics.SemanticCheckableExpression
import org.neo4j.cypher.internal.ast.semantics.SemanticError
import org.neo4j.cypher.internal.ast.semantics.SemanticExpressionCheck
import org.neo4j.cypher.internal.ast.semantics.SemanticState
import org.neo4j.cypher.internal.expressions.CoerceTo
import org.neo4j.cypher.internal.expressions.Expression
import org.neo4j.cypher.internal.expressions.Expression.SemanticContext
import org.neo4j.cypher.internal.expressions.FunctionInvocation
import org.neo4j.cypher.internal.expressions.FunctionName
import org.neo4j.cypher.internal.expressions.Namespace
import org.neo4j.cypher.internal.expressions.functions.UserDefinedFunctionInvocation
import org.neo4j.cypher.internal.util.InputPosition

object ResolvedFunctionInvocation {

  def apply(signatureLookup: QualifiedName => Option[UserFunctionSignature])(unresolved: FunctionInvocation): ResolvedFunctionInvocation = {
    val position = unresolved.position
    val name = QualifiedName(unresolved)
    ResolvedFunctionInvocation(name, signatureLookup(name), unresolved.args)(position)
  }
}

/**
 * A ResolvedFunctionInvocation is a user-defined function where the signature
 * has been resolved, i.e. verified that it exists in the database
 *
 * @param qualifiedName The qualified name of the function.
 * @param fcnSignature Either `Some(signature)` if the signature was resolved, or
 *                     `None` if the function didn't exist
 * @param callArguments The argument list to the function
 * @param position The position in the original query string.
 */
case class ResolvedFunctionInvocation(qualifiedName: QualifiedName,
                                      fcnSignature: Option[UserFunctionSignature],
                                      callArguments: IndexedSeq[Expression])
                                     (val position: InputPosition)
  extends Expression with UserDefinedFunctionInvocation with SemanticCheckableExpression {

  def coerceArguments: ResolvedFunctionInvocation = fcnSignature match {
    case Some(signature) =>
      val optInputFields = signature.inputSignature.map(Some(_)).toStream ++ Stream.continually(None)
      val coercedArguments =
        callArguments
          .zip(optInputFields)
          .map {
            case (arg, optField) =>
              optField.map { field => CoerceTo(arg, field.typ) }.getOrElse(arg)
          }
      copy(callArguments = coercedArguments)(position)

    case None => this
  }

  override def semanticCheck(ctx: SemanticContext): SemanticCheck = fcnSignature match {
    case None =>
      qualifiedName match {
        case QualifiedName(Seq(), qn) if qn.equalsIgnoreCase("not") =>
          SemanticError(s"Unknown function '$qualifiedName'. " +
            s"If you intended to use the negation expression, surround it with parentheses.", position)
        case QualifiedName(_, "toInt") =>
          SemanticError(s"The function toInt() is no longer supported. Please use toInteger() instead", position)
        case QualifiedName(_, "lower") =>
          SemanticError(s"The function lower() is no longer supported. Please use toLower() instead", position)
        case QualifiedName(_, "upper") =>
          SemanticError(s"The function upper() is no longer supported. Please use toUpper() instead", position)
        case QualifiedName(_, "rels") =>
          SemanticError(s"The function rels() is no longer supported. Please use relationships() instead", position)
        case _ => SemanticError(s"Unknown function '$qualifiedName'", position)
      }
    case Some(signature) =>
      val expectedNumArgs = signature.inputSignature.length
      val usedDefaultArgs = signature.inputSignature.drop(callArguments.length).flatMap(_.default)
      val actualNumArgs = callArguments.length + usedDefaultArgs.length

        if (expectedNumArgs == actualNumArgs) {
          //this zip is fine since it will only verify provided args in callArguments
          //default values are checked at load time
          signature.inputSignature.zip(callArguments).map {
            case (field, arg) =>
              SemanticExpressionCheck.check(SemanticContext.Results, arg) chain
                SemanticExpressionCheck.expectType(field.typ.covariant, arg)
          }.foldLeft(success)(_ chain _) chain
            SemanticExpressionCheck.specifyType(signature.outputType.covariant, this)
        } else {
          val msg = (if (signature.inputSignature.isEmpty) "arguments"
          else if (signature.inputSignature.size == 1) s"argument of type ${signature.inputSignature.head.typ.toNeoTypeString}"
          else s"arguments of type ${signature.inputSignature.map(_.typ.toNeoTypeString).mkString(", ")}") +
            signature.description.map(d => s"${System.lineSeparator()}Description: $d").getOrElse("")
          error(_: SemanticState, SemanticError( s"""Function call does not provide the required number of arguments: expected $expectedNumArgs got $actualNumArgs.
             |
             |Function ${signature.name} has signature: $signature
             |meaning that it expects $expectedNumArgs $msg""".stripMargin, position))
        }
  }

  override def isAggregate: Boolean = fcnSignature.exists(_.isAggregate)

 override def asUnresolvedFunction: FunctionInvocation = FunctionInvocation(
    namespace = Namespace(qualifiedName.namespace.toList)(position),
    functionName = FunctionName(qualifiedName.name)(position),
    distinct = false,
    args = arguments.toIndexedSeq,
  )(position)

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy