1717 */
1818package org .apache .drill .exec .physical .impl .union ;
1919
20- import java .util .ArrayList ;
21- import java .util .Collections ;
22- import java .util .Iterator ;
23- import java .util .List ;
24- import java .util .NoSuchElementException ;
25- import java .util .Stack ;
26-
20+ import com .google .common .base .Preconditions ;
2721import org .apache .calcite .util .Pair ;
2822import org .apache .drill .common .exceptions .DrillRuntimeException ;
2923import org .apache .drill .common .expression .ErrorCollector ;
5953import org .apache .drill .exec .vector .FixedWidthVector ;
6054import org .apache .drill .exec .vector .SchemaChangeCallBack ;
6155import org .apache .drill .exec .vector .ValueVector ;
62- import com .google .common .base .Preconditions ;
6356import org .slf4j .Logger ;
6457import org .slf4j .LoggerFactory ;
6558
59+ import java .util .ArrayList ;
60+ import java .util .Collections ;
61+ import java .util .Iterator ;
62+ import java .util .List ;
63+ import java .util .NoSuchElementException ;
64+ import java .util .Stack ;
65+
6666public class UnionAllRecordBatch extends AbstractBinaryRecordBatch <UnionAll > {
6767 private static final Logger logger = LoggerFactory .getLogger (UnionAllRecordBatch .class );
6868
@@ -278,10 +278,14 @@ private void inferOutputFieldsBothSide(final BatchSchema leftSchema, final Batch
278278 final Iterator <MaterializedField > leftIter = leftSchema .iterator ();
279279 final Iterator <MaterializedField > rightIter = rightSchema .iterator ();
280280
281+ logger .debug ("UnionAll inferring schema: isGroupingSetsExpansion={}" , popConfig .isGroupingSetsExpansion ());
281282 int index = 1 ;
282283 while (leftIter .hasNext () && rightIter .hasNext ()) {
283284 MaterializedField leftField = leftIter .next ();
284285 MaterializedField rightField = rightIter .next ();
286+ logger .debug ("Column {}: left='{}' type={}, right='{}' type={}" ,
287+ index , leftField .getName (), leftField .getType ().getMinorType (),
288+ rightField .getName (), rightField .getType ().getMinorType ());
285289
286290 if (Types .isSameTypeAndMode (leftField .getType (), rightField .getType ())) {
287291 MajorType .Builder builder = MajorType .newBuilder ()
@@ -301,15 +305,7 @@ private void inferOutputFieldsBothSide(final BatchSchema leftSchema, final Batch
301305 builder .setMinorType (leftField .getType ().getMinorType ());
302306 builder = Types .calculateTypePrecisionAndScale (leftField .getType (), rightField .getType (), builder );
303307 } else {
304- TypeProtos .MinorType outputMinorType = TypeCastRules .getLeastRestrictiveType (
305- leftField .getType ().getMinorType (),
306- rightField .getType ().getMinorType ()
307- );
308- if (outputMinorType == null ) {
309- throw new DrillRuntimeException ("Type mismatch between " + leftField .getType ().getMinorType ().toString () +
310- " on the left side and " + rightField .getType ().getMinorType ().toString () +
311- " on the right side in column " + index + " of UNION ALL" );
312- }
308+ TypeProtos .MinorType outputMinorType = resolveUnionColumnType (leftField , rightField , index );
313309 builder .setMinorType (outputMinorType );
314310 }
315311
@@ -328,6 +324,46 @@ private void inferOutputFieldsBothSide(final BatchSchema leftSchema, final Batch
328324 "Mismatch of column count should have been detected when validating sqlNode at planning" ;
329325 }
330326
327+ /**
328+ * Determines the output type for a UNION ALL column when combining two types.
329+ * <p>
330+ * Special handling is applied for GROUPING SETS expansion:
331+ * - Drill represents NULL columns as INT during grouping sets expansion.
332+ * - If one side is INT (likely a NULL placeholder) and the other is not, prefer the non-INT type.
333+ * <p>
334+ * For all other cases, the least restrictive type according to Drill's type cast rules is returned.
335+ *
336+ * @param leftField The type of the left column
337+ * @param rightField The type of the right column
338+ * @param index The column index (for logging)
339+ * @return The resolved output type
340+ * @throws DrillRuntimeException if types are incompatible
341+ */
342+ private TypeProtos .MinorType resolveUnionColumnType (MaterializedField leftField ,
343+ MaterializedField rightField ,
344+ int index ) {
345+ TypeProtos .MinorType leftType = leftField .getType ().getMinorType ();
346+ TypeProtos .MinorType rightType = rightField .getType ().getMinorType ();
347+
348+ boolean isGroupingSets = popConfig .isGroupingSetsExpansion ();
349+ boolean leftIsPlaceholder = leftType == TypeProtos .MinorType .INT && rightType != TypeProtos .MinorType .INT ;
350+ boolean rightIsPlaceholder = rightType == TypeProtos .MinorType .INT && leftType != TypeProtos .MinorType .INT ;
351+
352+ if (isGroupingSets && (leftIsPlaceholder || rightIsPlaceholder )) {
353+ TypeProtos .MinorType outputType = leftIsPlaceholder ? rightType : leftType ;
354+ logger .debug ("GROUPING SETS: Preferring {} over INT for column {}" , outputType , index );
355+ return outputType ;
356+ }
357+
358+ TypeProtos .MinorType outputType = TypeCastRules .getLeastRestrictiveType (leftType , rightType );
359+ if (outputType == null ) {
360+ throw new DrillRuntimeException ("Type mismatch between " + leftType +
361+ " and " + rightType + " in column " + index + " of UNION ALL" );
362+ }
363+ logger .debug ("Using standard type rules: {} + {} -> {}" , leftType , rightType , outputType );
364+ return outputType ;
365+ }
366+
331367 private void inferOutputFieldsOneSide (final BatchSchema schema ) {
332368 for (MaterializedField field : schema ) {
333369 container .addOrGet (field , callBack );
0 commit comments