Skip to content

Commit b5aba1c

Browse files
committed
[fix](fe) Preserve the enclosing MAP path through nested containers in default decimal v3 precision promotion
For a nonconstant MAP<DECIMAL(38,0), ARRAY<DECIMAL(38,18)>> the previous recursion dropped the incoming MAP_VALUE when entering an ARRAY, so the array item was merged globally with the key and became DECIMAL(38,6) under the default overflow scale, letting expectedInputTypes() insert a lossy cast that discarded low-order fractional digits before map_values() executed. Nested MAP recursion overwrote the outer branch too and could apply an outer-key lookup promotion to an unrelated inner key. Carry the full structural/logical group path (e.g. key, value, value/array, value/key) through ARRAY/MAP recursion instead of a flat key/value marker: - ARRAY items nested in a MAP value keep the enclosing value group, so MAP<K, ARRAY<D>> no longer truncates the item to the global wider type. - the key/value of a nested MAP stay on their own path and never merge with the outer leaves, and an outer-key linked scalar promotion cannot leak to an unrelated inner key of the same resolved type. - leaves outside any MAP (top-level scalar, ARRAY item) keep the original single wider-type behavior. Tests: 3 new unit tests for nested ARRAY in MAP value, nested MAP, and outer-key linked scalar not promoting an unrelated inner key; regression suite extended with nonconstant nested-container columns (map_values/map_keys over MAP<DECIMAL(38,0), ARRAY<DECIMAL(38,18)>> and MAP<DECIMAL(38,0), MAP<DECIMAL(9,2), DECIMAL(5,2)>>), .out regenerated and verified stable.
1 parent 6fe56a5 commit b5aba1c

4 files changed

Lines changed: 230 additions & 79 deletions

File tree

fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/ComputeSignatureHelper.java

Lines changed: 96 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public class ComputeSignatureHelper {
6262

6363
private static final String MAP_KEY = "key";
6464
private static final String MAP_VALUE = "value";
65+
private static final String ARRAY_ITEM = "array";
6566

6667
/** implementAbstractReturnType */
6768
public static FunctionSignature implementFollowToArgumentReturnType(
@@ -570,58 +571,54 @@ private static FunctionSignature defaultTimePrecisionPromotion(FunctionSignature
570571
private static FunctionSignature defaultDecimalV3PrecisionPromotion(
571572
FunctionSignature signature, List<Expression> arguments) {
572573
// The wider type across all decimal slots, used for decimal slots that are not
573-
// the key/value of a MAP (keeping the original behavior), for the placeholder
574-
// return type, and for MAP leaves whose group has no concrete type information.
574+
// inside a MAP (keeping the original behavior), for the placeholder return type,
575+
// and for MAP-nested leaves whose group has no concrete type information.
575576
DecimalV3Type widerType = null;
576577

577-
// The KEY leaves and the VALUE leaves of a MAP are independent type variables:
578-
// they must keep their own precision/scale instead of being merged into one wider
579-
// type, otherwise widening one leaf (e.g. the scale of a big integral key) may
580-
// overflow the other leaf. They are grouped by the resolved leaf type, so the
581-
// corresponding leaves of different (or repeated) MAP arguments aggregate while
582-
// the key leaves and the value leaves stay independent.
583-
Map<DecimalV3Type, DecimalV3Type> mapKeyWider = Maps.newHashMap();
584-
Map<DecimalV3Type, DecimalV3Type> mapValueWider = Maps.newHashMap();
585-
586-
// Top-level scalar decimal leaves with a concrete resolved type. After
587-
// Any/Follow resolution such a slot carries the same concrete type as the MAP
588-
// key/value leaf it was resolved from (e.g. the lookup argument of element_at
589-
// follows the MAP key type). It must be promoted together with that MAP leaf,
590-
// otherwise the BE compares columns of different concrete decimal classes.
578+
// Decimal leaves inside a MAP are independent type variables: they must keep
579+
// their own precision/scale instead of being merged into one wider type,
580+
// otherwise widening one leaf (e.g. the scale of a big integral key) may overflow
581+
// the other leaf. They are grouped by the full structural path through nested
582+
// containers (e.g. "key", "value", "value/array", "value/key") and the resolved
583+
// leaf type, so the leaves of different (or repeated) MAP arguments on the same
584+
// path aggregate while leaves on different paths stay independent.
585+
Map<String, DecimalV3Type> groupWider = Maps.newHashMap();
586+
587+
// The outermost MAP leaf group of each resolved type, used to link a top-level
588+
// scalar slot (e.g. element_at's lookup) with the MAP leaf it was resolved from:
589+
// after Any/Follow resolution both carry the same concrete type.
590+
Map<DecimalV3Type, String> mapLeafGroupByType = Maps.newHashMap();
591+
592+
// Top-level scalar decimal leaves with a concrete resolved type, whose promoted
593+
// type must also be folded into the linked MAP leaf group.
591594
List<DecimalLeaf> scalarLeaves = Lists.newArrayList();
592595

593596
DecimalV3Type[] widerHolder = new DecimalV3Type[1];
594597
for (int i = 0; i < arguments.size(); i++) {
595598
DataType targetType = getSignatureArgumentType(signature, i);
596599
collectDecimalLeaf(targetType, arguments.get(i).getDataType(), arguments.get(i),
597-
null, true, mapKeyWider, mapValueWider, scalarLeaves, widerHolder);
600+
"", mapLeafGroupByType, groupWider, scalarLeaves, widerHolder);
598601
}
599602
widerType = widerHolder[0];
600603
if (widerType == null) {
601604
return signature;
602605
}
603606

604607
// Fold the promoted type of every top-level scalar slot into the MAP leaf group
605-
// of the same resolved type (if any), so the MAP key/value and the scalar slot
606-
// linked with it are promoted to one type.
608+
// of the same resolved type (if any), so the MAP leaf and the scalar slot linked
609+
// with it are promoted to one type.
607610
for (DecimalLeaf scalarLeaf : scalarLeaves) {
608-
DecimalV3Type linkedWider = mapKeyWider.get(scalarLeaf.resolvedType);
609-
if (linkedWider == null) {
610-
linkedWider = mapValueWider.get(scalarLeaf.resolvedType);
611-
if (linkedWider != null) {
612-
mapValueWider.put(scalarLeaf.resolvedType,
613-
mergeDecimalV3Type(linkedWider, scalarLeaf.promotedType));
614-
}
615-
} else {
616-
mapKeyWider.put(scalarLeaf.resolvedType,
617-
mergeDecimalV3Type(linkedWider, scalarLeaf.promotedType));
611+
String linkedGroup = mapLeafGroupByType.get(scalarLeaf.resolvedType);
612+
if (linkedGroup != null) {
613+
groupWider.merge(linkedGroup, scalarLeaf.promotedType,
614+
ComputeSignatureHelper::mergeDecimalV3Type);
618615
}
619616
}
620617

621618
List<DataType> newArgTypes = Lists.newArrayListWithCapacity(signature.argumentsTypes.size());
622619
for (int i = 0; i < signature.argumentsTypes.size(); i++) {
623-
newArgTypes.add(replaceDecimalV3Leaf(signature.argumentsTypes.get(i), null, true,
624-
mapKeyWider, mapValueWider, widerType));
620+
newArgTypes.add(replaceDecimalV3Leaf(signature.argumentsTypes.get(i), "",
621+
mapLeafGroupByType, groupWider, widerType));
625622
}
626623
signature = signature.withArgumentTypes(signature.hasVarArgs, newArgTypes);
627624
if (signature.returnType instanceof DecimalV3Type
@@ -653,16 +650,16 @@ private static DecimalV3Type promotedDecimalV3Type(Expression arg, DataType argT
653650

654651
/**
655652
* Collect every decimal leaf of one argument and fold its promoted type into the
656-
* corresponding group. {@code mapSide} is {@link #MAP_KEY} or {@link #MAP_VALUE}
657-
* when the leaf is directly the key/value of a MAP (possibly nested in a MAP),
658-
* otherwise {@code null}. {@code linkable} indicates the leaf is a top-level scalar
659-
* slot (not nested in any MAP or ARRAY), which may be linked to a MAP key/value leaf
660-
* of the same resolved type. {@code widerHolder} accumulates the wider type across
661-
* all decimal leaves.
653+
* corresponding group. {@code path} is the full structural path through nested
654+
* containers (empty for a top-level slot, {@link #MAP_KEY}/{@link #MAP_VALUE} for
655+
* the key/value of a MAP, {@link #ARRAY_ITEM} for an ARRAY item), so an ARRAY nested
656+
* in a MAP value (e.g. "value/array") or the key/value of a nested MAP (e.g.
657+
* "value/key") keep the enclosing group instead of being merged with the outer
658+
* leaves. {@code widerHolder} accumulates the wider type across all decimal leaves.
662659
*/
663660
private static void collectDecimalLeaf(DataType sigType, DataType argType, Expression arg,
664-
String mapSide, boolean linkable, Map<DecimalV3Type, DecimalV3Type> mapKeyWider,
665-
Map<DecimalV3Type, DecimalV3Type> mapValueWider, List<DecimalLeaf> scalarLeaves,
661+
String path, Map<DecimalV3Type, String> mapLeafGroupByType,
662+
Map<String, DecimalV3Type> groupWider, List<DecimalLeaf> scalarLeaves,
666663
DecimalV3Type[] widerHolder) {
667664
if (sigType instanceof DecimalV3Type) {
668665
DecimalV3Type sigDecimal = (DecimalV3Type) sigType;
@@ -671,33 +668,38 @@ private static void collectDecimalLeaf(DataType sigType, DataType argType, Expre
671668
promoted = promotedDecimalV3Type(arg, argType);
672669
widerHolder[0] = mergeDecimalV3Type(widerHolder[0], promoted);
673670
}
674-
if (mapSide == null) {
675-
if (linkable && promoted != null && sigDecimal.getPrecision() > 0) {
676-
// top-level scalar slot with a concrete resolved type may be linked
677-
// with a MAP key/value leaf of the same type below
671+
if (path.isEmpty()) {
672+
// top-level scalar slot: keep the original behavior of the single wider
673+
// type, but a concrete resolved type may be linked with a MAP leaf below
674+
if (promoted != null && sigDecimal.getPrecision() > 0) {
678675
scalarLeaves.add(new DecimalLeaf(sigDecimal, promoted));
679676
}
680-
} else if (promoted != null) {
681-
if (mapSide == MAP_KEY) {
682-
mapKeyWider.merge(sigDecimal, promoted, ComputeSignatureHelper::mergeDecimalV3Type);
683-
} else {
684-
mapValueWider.merge(sigDecimal, promoted, ComputeSignatureHelper::mergeDecimalV3Type);
685-
}
686-
}
677+
} else if (isMapNested(path) && promoted != null) {
678+
String groupKey = path + ":" + sigDecimal;
679+
groupWider.merge(groupKey, promoted, ComputeSignatureHelper::mergeDecimalV3Type);
680+
// keep the outermost group (shortest path, key before value) for linking
681+
mapLeafGroupByType.putIfAbsent(sigDecimal, groupKey);
682+
}
683+
// other leaves (e.g. ARRAY items not nested in a MAP) keep the original
684+
// behavior of the single wider type
687685
return;
688686
} else if (sigType instanceof MapType) {
689687
MapType mapType = (MapType) sigType;
690688
if (argType instanceof MapType) {
691689
MapType argMapType = (MapType) argType;
692690
collectDecimalLeaf(mapType.getKeyType(), argMapType.getKeyType(), arg,
693-
MAP_KEY, false, mapKeyWider, mapValueWider, scalarLeaves, widerHolder);
691+
appendPath(path, MAP_KEY), mapLeafGroupByType, groupWider,
692+
scalarLeaves, widerHolder);
694693
collectDecimalLeaf(mapType.getValueType(), argMapType.getValueType(), arg,
695-
MAP_VALUE, false, mapKeyWider, mapValueWider, scalarLeaves, widerHolder);
694+
appendPath(path, MAP_VALUE), mapLeafGroupByType, groupWider,
695+
scalarLeaves, widerHolder);
696696
} else if (argType instanceof NullType) {
697697
collectDecimalLeaf(mapType.getKeyType(), argType, arg,
698-
MAP_KEY, false, mapKeyWider, mapValueWider, scalarLeaves, widerHolder);
698+
appendPath(path, MAP_KEY), mapLeafGroupByType, groupWider,
699+
scalarLeaves, widerHolder);
699700
collectDecimalLeaf(mapType.getValueType(), argType, arg,
700-
MAP_VALUE, false, mapKeyWider, mapValueWider, scalarLeaves, widerHolder);
701+
appendPath(path, MAP_VALUE), mapLeafGroupByType, groupWider,
702+
scalarLeaves, widerHolder);
701703
}
702704
return;
703705
} else if (sigType instanceof ArrayType) {
@@ -709,53 +711,69 @@ private static void collectDecimalLeaf(DataType sigType, DataType argType, Expre
709711
} else {
710712
return;
711713
}
712-
// ARRAY items are not MAP key/value leaves, keep the original behavior
714+
// carry the enclosing MAP path through the ARRAY so items nested in a MAP
715+
// value stay in the value group
713716
collectDecimalLeaf(((ArrayType) sigType).getItemType(), itemArgType, arg,
714-
null, false, mapKeyWider, mapValueWider, scalarLeaves, widerHolder);
717+
appendPath(path, ARRAY_ITEM), mapLeafGroupByType, groupWider,
718+
scalarLeaves, widerHolder);
715719
}
716720
// StructType and other types are not supported
717721
}
718722

719723
/**
720-
* Replace every decimal leaf in {@code sigType}: MAP key/value leaves use the wider
721-
* type of their own group, all other leaves (scalar, ARRAY item, etc.) keep the
722-
* original behavior of using the single wider type across all decimal slots.
724+
* Replace every decimal leaf in {@code sigType}: leaves inside a MAP use the wider
725+
* type of their own structural group, all other leaves (scalar, ARRAY item, etc.)
726+
* keep the original behavior of using the single wider type across all decimal
727+
* slots.
723728
*/
724-
private static DataType replaceDecimalV3Leaf(DataType sigType, String mapSide, boolean topLevel,
725-
Map<DecimalV3Type, DecimalV3Type> mapKeyWider,
726-
Map<DecimalV3Type, DecimalV3Type> mapValueWider, DecimalV3Type widerType) {
729+
private static DataType replaceDecimalV3Leaf(DataType sigType, String path,
730+
Map<DecimalV3Type, String> mapLeafGroupByType, Map<String, DecimalV3Type> groupWider,
731+
DecimalV3Type widerType) {
727732
if (sigType instanceof DecimalV3Type) {
728733
DecimalV3Type sigDecimal = (DecimalV3Type) sigType;
729-
if (mapSide == null) {
734+
if (path.isEmpty()) {
730735
// a top-level scalar slot linked with a MAP leaf keeps the type of that
731736
// leaf (e.g. element_at's lookup must match the MAP key type)
732-
if (topLevel && sigDecimal.getPrecision() > 0) {
733-
DecimalV3Type linkedWider = mapKeyWider.get(sigDecimal);
734-
if (linkedWider == null) {
735-
linkedWider = mapValueWider.get(sigDecimal);
736-
}
737-
if (linkedWider != null) {
738-
return linkedWider;
737+
if (sigDecimal.getPrecision() > 0) {
738+
String linkedGroup = mapLeafGroupByType.get(sigDecimal);
739+
if (linkedGroup != null) {
740+
DecimalV3Type linkedWider = groupWider.get(linkedGroup);
741+
if (linkedWider != null) {
742+
return linkedWider;
743+
}
739744
}
740745
}
741746
return widerType;
742747
}
743-
DecimalV3Type groupWider = (mapSide == MAP_KEY ? mapKeyWider : mapValueWider).get(sigDecimal);
744-
return groupWider != null ? groupWider : widerType;
748+
if (isMapNested(path)) {
749+
DecimalV3Type groupType = groupWider.get(path + ":" + sigDecimal);
750+
return groupType != null ? groupType : widerType;
751+
}
752+
// other leaves (e.g. ARRAY items not nested in a MAP) keep the original
753+
// behavior of the single wider type
754+
return widerType;
745755
} else if (sigType instanceof ArrayType) {
746-
return ArrayType.of(replaceDecimalV3Leaf(((ArrayType) sigType).getItemType(), null, false,
747-
mapKeyWider, mapValueWider, widerType));
756+
return ArrayType.of(replaceDecimalV3Leaf(((ArrayType) sigType).getItemType(),
757+
appendPath(path, ARRAY_ITEM), mapLeafGroupByType, groupWider, widerType));
748758
} else if (sigType instanceof MapType) {
749759
MapType mapType = (MapType) sigType;
750760
return MapType.of(
751-
replaceDecimalV3Leaf(mapType.getKeyType(), MAP_KEY, false,
752-
mapKeyWider, mapValueWider, widerType),
753-
replaceDecimalV3Leaf(mapType.getValueType(), MAP_VALUE, false,
754-
mapKeyWider, mapValueWider, widerType));
761+
replaceDecimalV3Leaf(mapType.getKeyType(), appendPath(path, MAP_KEY),
762+
mapLeafGroupByType, groupWider, widerType),
763+
replaceDecimalV3Leaf(mapType.getValueType(), appendPath(path, MAP_VALUE),
764+
mapLeafGroupByType, groupWider, widerType));
755765
}
756766
return sigType;
757767
}
758768

769+
private static String appendPath(String path, String segment) {
770+
return path.isEmpty() ? segment : path + "/" + segment;
771+
}
772+
773+
private static boolean isMapNested(String path) {
774+
return path.contains(MAP_KEY) || path.contains(MAP_VALUE);
775+
}
776+
759777
private static DecimalV3Type mergeDecimalV3Type(DecimalV3Type left, DecimalV3Type right) {
760778
if (left == null) {
761779
return right;

0 commit comments

Comments
 (0)