1515import org .elasticsearch .compute .data .BooleanBlock ;
1616import org .elasticsearch .compute .data .BytesRefBlock ;
1717import org .elasticsearch .compute .data .DoubleBlock ;
18+ import org .elasticsearch .compute .data .ElementType ;
1819import org .elasticsearch .compute .data .IntBlock ;
1920import org .elasticsearch .compute .data .LongBlock ;
20- import org .elasticsearch .compute .operator .EvalOperator ;
21+ import org .elasticsearch .compute .data .Page ;
22+ import org .elasticsearch .compute .operator .DriverContext ;
2123import org .elasticsearch .compute .operator .EvalOperator .ExpressionEvaluator ;
24+ import org .elasticsearch .core .Releasables ;
2225import org .elasticsearch .xpack .esql .EsqlIllegalArgumentException ;
2326import org .elasticsearch .xpack .esql .core .expression .Expression ;
2427import org .elasticsearch .xpack .esql .core .expression .FoldContext ;
28+ import org .elasticsearch .xpack .esql .core .expression .Nullability ;
2529import org .elasticsearch .xpack .esql .core .expression .function .scalar .BinaryScalarFunction ;
2630import org .elasticsearch .xpack .esql .core .tree .NodeInfo ;
2731import org .elasticsearch .xpack .esql .core .tree .Source ;
@@ -50,17 +54,16 @@ public class MvContainsAll extends BinaryScalarFunction implements EvaluatorMapp
5054 "MvContainsAll" ,
5155 MvContainsAll ::new
5256 );
53- private DataType dataType ;
5457
5558 @ FunctionInfo (
5659 returnType = "boolean" ,
57- description = "\" Checks if all values yielded by the second multivalue expression are present in the values yielded by "
58- + "the first multivalue expression. Returns a boolean, or null if either expression is null ." ,
60+ description = "Checks if all values yielded by the second multivalue expression are present in the values yielded by "
61+ + "the first multivalue expression. Returns a boolean. Null values are treated as an empty set ." ,
5962 examples = {
6063 @ Example (file = "string" , tag = "mv_contains_all" ),
6164 @ Example (file = "string" , tag = "mv_contains_all_bothsides" ),
62- @ Example (file = "string" , tag = "mv_contains_all_where" ), },
63- appliesTo = { @ FunctionAppliesTo (lifeCycle = FunctionAppliesToLifecycle .PREVIEW , version = "9.2.0" ) }
65+ @ Example (file = "string" , tag = "mv_contains_all_where" ),},
66+ appliesTo = {@ FunctionAppliesTo (lifeCycle = FunctionAppliesToLifecycle .PREVIEW , version = "9.2.0" )}
6467 )
6568 public MvContainsAll (
6669 Source source ,
@@ -81,7 +84,7 @@ public MvContainsAll(
8184 "long" ,
8285 "text" ,
8386 "unsigned_long" ,
84- "version" },
87+ "version" },
8588 description = "Multivalue expression."
8689 ) Expression superset ,
8790 @ Param (
@@ -101,7 +104,7 @@ public MvContainsAll(
101104 "long" ,
102105 "text" ,
103106 "unsigned_long" ,
104- "version" },
107+ "version" },
105108 description = "Multivalue expression."
106109 ) Expression subset
107110 ) {
@@ -127,24 +130,24 @@ protected TypeResolution resolveType() {
127130 if (resolution .unresolved ()) {
128131 return resolution ;
129132 }
130- dataType = left ().dataType () == DataType .NULL ? DataType .NULL : DataType .BOOLEAN ;
131133 if (left ().dataType () == DataType .NULL ) {
132- dataType = right ().dataType () == DataType .NULL ? DataType .NULL : DataType .BOOLEAN ;
133134 return isRepresentableExceptCounters (right (), sourceText (), SECOND );
134135 }
135136 return isType (right (), t -> t .noText () == left ().dataType ().noText (), sourceText (), SECOND , left ().dataType ().noText ().typeName ());
136137 }
137138
138139 @ Override
139140 public DataType dataType () {
140- if (dataType == null ) {
141- resolveType ();
142- }
143- return dataType ;
141+ return DataType .BOOLEAN ;
142+ }
143+
144+ @ Override
145+ public Nullability nullable () {
146+ return Nullability .FALSE ;
144147 }
145148
146149 @ Override
147- protected BinaryScalarFunction replaceChildren (Expression newLeft , Expression newRight ) {
150+ protected MvContainsAll replaceChildren (Expression newLeft , Expression newRight ) {
148151 return new MvContainsAll (source (), newLeft , newRight );
149152 }
150153
@@ -162,7 +165,7 @@ public Object fold(FoldContext ctx) {
162165 public ExpressionEvaluator .Factory toEvaluator (ToEvaluator toEvaluator ) {
163166 var supersetType = PlannerUtils .toElementType (left ().dataType ());
164167 var subsetType = PlannerUtils .toElementType (right ().dataType ());
165- if (supersetType != subsetType ) {
168+ if (supersetType != ElementType . NULL && subsetType != ElementType . NULL && supersetType != subsetType ) {
166169 throw new EsqlIllegalArgumentException (
167170 "Incompatible data types for MvContainsAll, superset type({}) value({}) and subset type({}) value({}) don't match." ,
168171 supersetType ,
@@ -177,11 +180,12 @@ public ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) {
177180 case DOUBLE -> new MvContainsAllDoubleEvaluator .Factory (source (), toEvaluator .apply (left ()), toEvaluator .apply (right ()));
178181 case INT -> new MvContainsAllIntEvaluator .Factory (source (), toEvaluator .apply (left ()), toEvaluator .apply (right ()));
179182 case LONG -> new MvContainsAllLongEvaluator .Factory (source (), toEvaluator .apply (left ()), toEvaluator .apply (right ()));
180- case NULL -> EvalOperator . CONSTANT_NULL_FACTORY ;
183+ case NULL -> new MvContainsAllNullEvaluator ( toEvaluator . apply ( right ())) ;
181184 default -> throw EsqlIllegalArgumentException .illegalDataType (dataType ());
182185 };
183186 }
184187
188+
185189 @ Evaluator (extraName = "Int" )
186190 static void process (BooleanBlock .Builder builder , int position , IntBlock field1 , IntBlock field2 ) {
187191 appendTo (builder , containsAll (field1 , field2 , position , IntBlock ::getInt ));
@@ -228,10 +232,10 @@ static void appendTo(BooleanBlock.Builder builder, Boolean bool) {
228232 * A block is considered a subset if the superset contains values that test equal for all the values in the subset, independent of
229233 * order. Duplicates are ignored in the sense that for each duplicate in the subset, we will search/match against the first/any value
230234 * in the superset.
235+ *
231236 * @param superset block to check against
232- * @param subset block containing values that should be present in the other block.
233- * @return {@code true} if the given blocks are a superset and subset to each other, {@code false} if not and {@code null} if the subset
234- * or superset contains only null values.
237+ * @param subset block containing values that should be present in the other block.
238+ * @return {@code true} if the given blocks are a superset and subset to each other, {@code false} if not.
235239 */
236240 static <BlockType extends Block , Type > Boolean containsAll (
237241 BlockType superset ,
@@ -242,8 +246,8 @@ static <BlockType extends Block, Type> Boolean containsAll(
242246 if (superset == subset ) {
243247 return true ;
244248 }
245- if (subset .areAllValuesNull () || superset . areAllValuesNull () ) {
246- return null ;
249+ if (subset .areAllValuesNull ()) {
250+ return true ;
247251 }
248252
249253 final var subsetCount = subset .getValueCount (position );
@@ -283,4 +287,26 @@ static <BlockType extends Block, Type> boolean hasValue(
283287 interface ValueExtractor <BlockType extends Block , Type > {
284288 Type extractValue (BlockType block , int position );
285289 }
290+
291+ private record MvContainsAllNullEvaluator (ExpressionEvaluator .Factory toEvaluator ) implements ExpressionEvaluator .Factory {
292+ @ Override
293+ public ExpressionEvaluator get (DriverContext context ) {
294+ return new ExpressionEvaluator () {
295+ final ExpressionEvaluator subsetField = toEvaluator .get (context );
296+
297+ @ Override
298+ public Block eval (Page page ) {
299+ try (Block block = subsetField .eval (page )) {
300+ var position = page .getPositionCount ();
301+ return context .blockFactory ().newConstantBooleanBlockWith (block .isNull (position ), position );
302+ }
303+ }
304+
305+ @ Override
306+ public void close () {
307+ Releasables .closeExpectNoException (subsetField );
308+ }
309+ };
310+ }
311+ }
286312}
0 commit comments