6363import java .util .Set ;
6464import java .util .function .BiConsumer ;
6565import java .util .function .Consumer ;
66+ import java .util .function .Predicate ;
6667import java .util .stream .Stream ;
6768
6869import static org .elasticsearch .xpack .esql .common .Failure .fail ;
@@ -627,9 +628,16 @@ private static void checkRemoteEnrich(LogicalPlan plan, Set<Failure> failures) {
627628 private static void checkMatchQueryPredicates (LogicalPlan plan , Set <Failure > failures ) {
628629 if (plan instanceof Filter f ) {
629630 Expression condition = f .condition ();
630- checkMatchPredicateTypes (plan , failures );
631- checkCommandsBeforeMatchPredicates (plan , condition , failures );
632- checkMatchPredicateConditions (condition , failures );
631+ checkMatchPredicateArgumentTypes (plan , failures );
632+ checkCommandsBeforeExpression (
633+ plan ,
634+ condition ,
635+ MatchQueryPredicate .class ,
636+ lp -> lp instanceof Limit == false ,
637+ e -> "[:] operator" ,
638+ failures
639+ );
640+ checkNotPresentInDisjunctions (condition , MatchQueryPredicate .class , mqp -> "[:] operator" , failures );
633641 } else {
634642 plan .forEachExpression (
635643 MatchQueryPredicate .class ,
@@ -647,7 +655,7 @@ private static void checkMatchQueryPredicates(LogicalPlan plan, Set<Failure> fai
647655 * early in the execution, rather than fail at the compute engine level.
648656 * In the future we will be able to handle MATCH at the compute and we will no longer need these checks.
649657 */
650- private static void checkMatchPredicateTypes (LogicalPlan plan , Set <Failure > failures ) {
658+ private static void checkMatchPredicateArgumentTypes (LogicalPlan plan , Set <Failure > failures ) {
651659 if (plan instanceof Filter f ) {
652660 Expression condition = f .condition ();
653661
@@ -670,37 +678,76 @@ private static void checkMatchPredicateTypes(LogicalPlan plan, Set<Failure> fail
670678 }
671679 }
672680
673- private static void checkCommandsBeforeMatchPredicates (LogicalPlan plan , Expression condition , Set <Failure > failures ) {
674- condition .forEachDown (MatchQueryPredicate .class , qsf -> {
675- plan .forEachDown (LogicalPlan .class , lp -> {
676- if (lp instanceof Limit ) {
677- failures .add (
678- fail (plan , "[:] operator cannot be used after {}" , lp .sourceText ().split (" " )[0 ].toUpperCase (Locale .ROOT ))
679- );
680- }
681- });
682- });
683- }
684-
685- private static void checkMatchPredicateConditions (Expression condition , Set <Failure > failures ) {
681+ /**
682+ * Checks a whether a condition contains a disjunction with the specified typeToken. Adds to failure if it does.
683+ *
684+ * @param condition condition to check for disjunctions
685+ * @param typeToken type that is forbidden in disjunctions
686+ * @param typeNameProvider provider for the type name to add in the failure message
687+ * @param failures failures collection to add to
688+ * @param <E> type of the token to look for
689+ */
690+ private static <E extends Expression > void checkNotPresentInDisjunctions (
691+ Expression condition ,
692+ Class <E > typeToken ,
693+ java .util .function .Function <E , String > typeNameProvider ,
694+ Set <Failure > failures
695+ ) {
686696 condition .forEachUp (Or .class , or -> {
687- checkMatchPredicateInDisjunction ( failures , or , or .left ());
688- checkMatchPredicateInDisjunction ( failures , or , or .right ());
697+ checkNotPresentInDisjunctions ( or .left (), or , typeToken , typeNameProvider , failures );
698+ checkNotPresentInDisjunctions ( or .right (), or , typeToken , typeNameProvider , failures );
689699 });
690700 }
691701
692- private static void checkMatchPredicateInDisjunction (Set <Failure > failures , Or or , Expression expression ) {
693- expression .forEachDown (MatchQueryPredicate .class , ftp -> {
694- failures .add (fail (or , "Invalid condition [{}]. [:] operator can't be used as part of an or condition" , or .sourceText ()));
702+ /**
703+ * Checks a whether a condition contains a disjunction with the specified typeToken. Adds to failure if it does.
704+ *
705+ * @param parentExpression parent expression to add to the failure message
706+ * @param or disjunction that is being checked
707+ * @param typeToken type that is forbidden in disjunctions
708+ * @param failures failures collection to add to
709+ * @param <E> type of the token to look for
710+ */
711+ private static <E extends Expression > void checkNotPresentInDisjunctions (
712+ Expression parentExpression ,
713+ Or or ,
714+ Class <E > typeToken ,
715+ java .util .function .Function <E , String > elementName ,
716+ Set <Failure > failures
717+ ) {
718+ parentExpression .forEachDown (typeToken , ftp -> {
719+ failures .add (
720+ fail (or , "Invalid condition [{}]. " + elementName .apply (ftp ) + " can't be used as part of an or condition" , or .sourceText ())
721+ );
695722 });
696723 }
697724
725+ /**
726+ * Checks full text query functions for invalid usage.
727+ *
728+ * @param plan root plan to check
729+ * @param failures failures found
730+ */
698731 private static void checkFullTextQueryFunctions (LogicalPlan plan , Set <Failure > failures ) {
699732 if (plan instanceof Filter f ) {
700733 Expression condition = f .condition ();
701- checkCommandsBeforeQueryStringFunction (plan , condition , failures );
702- checkCommandsBeforeMatchFunction (plan , condition , failures );
703- checkFullTextFunctionsConditions (condition , failures );
734+ checkCommandsBeforeExpression (
735+ plan ,
736+ condition ,
737+ QueryString .class ,
738+ lp -> (lp instanceof Filter || lp instanceof OrderBy || lp instanceof EsRelation ),
739+ e -> "[" + e .functionName () + "] function" ,
740+ failures
741+ );
742+ checkCommandsBeforeExpression (
743+ plan ,
744+ condition ,
745+ Match .class ,
746+ lp -> (lp instanceof Limit == false ),
747+ e -> "[" + e .functionName () + "] function" ,
748+ failures
749+ );
750+ checkNotPresentInDisjunctions (condition , FullTextFunction .class , ftf -> "[" + ftf .functionName () + "] function" , failures );
704751 checkFullTextFunctionsParents (condition , failures );
705752 } else {
706753 plan .forEachExpression (FullTextFunction .class , ftf -> {
@@ -709,32 +756,33 @@ private static void checkFullTextQueryFunctions(LogicalPlan plan, Set<Failure> f
709756 }
710757 }
711758
712- private static void checkCommandsBeforeQueryStringFunction (LogicalPlan plan , Expression condition , Set <Failure > failures ) {
713- condition .forEachDown (QueryString .class , qsf -> {
714- plan .forEachDown (LogicalPlan .class , lp -> {
715- if ((lp instanceof Filter || lp instanceof OrderBy || lp instanceof EsRelation ) == false ) {
716- failures .add (
717- fail (
718- plan ,
719- "[{}] function cannot be used after {}" ,
720- qsf .functionName (),
721- lp .sourceText ().split (" " )[0 ].toUpperCase (Locale .ROOT )
722- )
723- );
724- }
725- });
726- });
727- }
728-
729- private static void checkCommandsBeforeMatchFunction (LogicalPlan plan , Expression condition , Set <Failure > failures ) {
730- condition .forEachDown (Match .class , qsf -> {
759+ /**
760+ * Checks all commands that exist before a specific type satisfy conditions.
761+ *
762+ * @param plan plan that contains the condition
763+ * @param condition condition to check
764+ * @param typeToken type to check for. When a type is found in the condition, all plans before the root plan are checked
765+ * @param commandCheck check to perform on each command that precedes the plan that contains the typeToken
766+ * @param typeErrorMsgProvider provider for the type name in the error message
767+ * @param failures failures to add errors to
768+ * @param <E> class of the type to look for
769+ */
770+ private static <E extends Expression > void checkCommandsBeforeExpression (
771+ LogicalPlan plan ,
772+ Expression condition ,
773+ Class <E > typeToken ,
774+ Predicate <LogicalPlan > commandCheck ,
775+ java .util .function .Function <E , String > typeErrorMsgProvider ,
776+ Set <Failure > failures
777+ ) {
778+ condition .forEachDown (typeToken , exp -> {
731779 plan .forEachDown (LogicalPlan .class , lp -> {
732- if (lp instanceof Limit ) {
780+ if (commandCheck . test ( lp ) == false ) {
733781 failures .add (
734782 fail (
735783 plan ,
736- "[{}] function cannot be used after {}" ,
737- qsf . functionName ( ),
784+ "{} cannot be used after {}" ,
785+ typeErrorMsgProvider . apply ( exp ),
738786 lp .sourceText ().split (" " )[0 ].toUpperCase (Locale .ROOT )
739787 )
740788 );
@@ -743,26 +791,11 @@ private static void checkCommandsBeforeMatchFunction(LogicalPlan plan, Expressio
743791 });
744792 }
745793
746- private static void checkFullTextFunctionsConditions (Expression condition , Set <Failure > failures ) {
747- condition .forEachUp (Or .class , or -> {
748- checkFullTextFunctionInDisjunction (failures , or , or .left ());
749- checkFullTextFunctionInDisjunction (failures , or , or .right ());
750- });
751- }
752-
753- private static void checkFullTextFunctionInDisjunction (Set <Failure > failures , Or or , Expression expression ) {
754- expression .forEachDown (FullTextFunction .class , ftf -> {
755- failures .add (
756- fail (
757- or ,
758- "Invalid condition [{}]. [{}] function can't be used as part of an or condition" ,
759- or .sourceText (),
760- ftf .functionName ()
761- )
762- );
763- });
764- }
765-
794+ /**
795+ * Checks parents of a full text function to ensure they are allowed
796+ * @param condition condition that contains the full text function
797+ * @param failures failures to add errors to
798+ */
766799 private static void checkFullTextFunctionsParents (Expression condition , Set <Failure > failures ) {
767800 forEachFullTextFunctionParent (condition , (ftf , parent ) -> {
768801 if ((parent instanceof FullTextFunction == false )
0 commit comments