github-actions[bot] commented on code in PR #67154: URL: https://github.com/apache/doris/pull/67154#discussion_r4036813091
########## regression-test/suites/nereids_function_p0/scalar_function/nereids_scalar_fn_array_decimal_precision.groovy: ########## @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Independent non-MAP ARRAY slots are separate logical type variables: array_sortby's +// src/sort_keys, array_zip's arrays and the expanded arguments of array_enumerate_uniq. +// The default decimal v3 precision promotion must keep one Decimal group per occurrence +// instead of merging them into the wider type of another slot, otherwise a +// cross-range/cross-scale argument truncates the other one (the sort keys widening the +// sorted source and dropping its low order digits, array_zip returning values that do +// not match its Struct type, and array_enumerate_uniq collapsing distinct composite +// keys). +suite("nereids_scalar_fn_array_decimal_precision") { + sql "set enable_decimal256 = true;" Review Comment: [P2] Keep this regression on an overflowing Decimal boundary With `enable_decimal256=true`, these `DECIMAL(38,38)` and `DECIMAL(38,32)` slots merge losslessly to `DECIMAL(44,38)` (range 6 plus scale 38). The old broken global merge therefore preserves the two low-order-distinct `src` values, so `array_enumerate_uniq` still returns `[1, 1]` and this end-to-end case passes without exercising the slot-scoping fix. Please run the fixture with Decimal256 disabled, or use max-width Decimal256 types such as `DECIMAL(76,76)` versus `DECIMAL(76,70)`, so merging necessarily removes the discriminating digits. ########## fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/ComputeSignatureHelper.java: ########## @@ -606,52 +623,548 @@ private static FunctionSignature defaultTimePrecisionPromotion(FunctionSignature } private static FunctionSignature defaultDecimalV3PrecisionPromotion( - FunctionSignature signature, List<Expression> arguments) { - DecimalV3Type finalType = null; + FunctionSignature signature, List<Expression> arguments, ComputeSignature computeSignature) { + // The wider type across all decimal slots, used for decimal slots that are not + // inside a MAP (keeping the original behavior), for the placeholder return type, + // and for MAP-nested leaves whose group has no concrete type information. + DecimalV3Type widerType = null; + + // Decimal leaves inside a MAP are independent type variables: they must keep + // their own precision/scale instead of being merged into one wider type, + // otherwise widening one leaf (e.g. the scale of a big integral key) may overflow + // the other leaf. They are grouped by the full structural path through nested + // containers (e.g. "key", "value", "value/array", "value/key") and the resolved + // leaf type, so the leaves of different (or repeated) MAP arguments on the same + // path aggregate while leaves on different paths stay independent. + Map<String, DecimalV3Type> groupWider = Maps.newHashMap(); + + // The template signature carrying the original Any/Follow slots that the resolved + // signature was derived from. It lets us link a top-level scalar slot with the MAP + // leaf it belongs to by the original Any/Follow group identity (the index) instead + // of the resolved concrete type, which can collide when independent slots resolve + // to the same type (e.g. the key and the value of a MAP both becoming DECIMAL(10,3)). + FunctionSignature template = findDecimalV3Template(computeSignature, signature); + + // The outermost MAP leaf group of each Any/Follow index (from the template), used + // to link a top-level scalar slot (e.g. map_contains_value's probe, element_at's + // lookup) with the MAP leaf that carries the same index. + Map<Integer, String> indexToMapLeafGroup = Maps.newHashMap(); + + // Fallback used when the template can not be recovered: the outermost MAP leaf + // group of each resolved type, used to link a top-level scalar slot with the MAP + // leaf it was resolved from (after Any/Follow resolution both carry the same type). + Map<DecimalV3Type, String> mapLeafGroupByType = Maps.newHashMap(); + + // Top-level scalar decimal leaves with a concrete resolved type, whose promoted + // type must also be folded into the linked MAP leaf group. + List<DecimalLeaf> scalarLeaves = Lists.newArrayList(); + + // Top-level scalar decimal slots are independent logical type variables + // (e.g. the key/value of map_agg(k, v) are Any(0) and Any(1)); group them by + // the resolved type so the slots of one logical group aggregate while the slots + // of different groups keep their own precision/scale. + Map<DecimalV3Type, DecimalV3Type> scalarGroupWider = Maps.newHashMap(); + + DecimalV3Type[] widerHolder = new DecimalV3Type[1]; for (int i = 0; i < arguments.size(); i++) { - DataType targetType; - if (i >= signature.argumentsTypes.size()) { - Preconditions.checkState(signature.getVarArgType().isPresent(), - "argument size larger than signature"); - targetType = signature.getVarArgType().get(); + DataType targetType = getSignatureArgumentType(signature, i); + DataType templateType = template == null ? null : getSignatureArgumentType(template, i); + String slotScope = slotScope(template, i, templateType); + collectDecimalLeaf(targetType, arguments.get(i).getDataType(), arguments.get(i), + "", templateType, -1, slotScope, indexToMapLeafGroup, mapLeafGroupByType, groupWider, + scalarGroupWider, scalarLeaves, widerHolder); + } + widerType = widerHolder[0]; + if (widerType == null) { + return signature; + } + + // Fold the promoted type of every top-level scalar slot into the MAP leaf group it + // is linked with (by the original Any/Follow identity when available, otherwise by + // the resolved type), so the MAP leaf and the scalar slot linked with it are + // promoted to one type. + for (DecimalLeaf scalarLeaf : scalarLeaves) { + String linkedGroup; + if (scalarLeaf.index >= 0) { + linkedGroup = indexToMapLeafGroup.get(scalarLeaf.index); } else { - targetType = signature.getArgType(i); + linkedGroup = mapLeafGroupByType.get(scalarLeaf.resolvedType); } - List<DataType> argTypes = extractArgumentTypeBySignature(DecimalV3Type.class, targetType, - arguments.get(i).getDataType()); - if (argTypes.isEmpty()) { - continue; + if (linkedGroup != null) { + groupWider.merge(linkedGroup, scalarLeaf.promotedType, + ComputeSignatureHelper::mergeDecimalV3Type); } + } - for (DataType argType : argTypes) { - Expression arg = arguments.get(i); - DecimalV3Type decimalV3Type; - if (arg.isLiteral() && arg.getDataType().isIntegralType()) { - // create decimalV3 with minimum scale enough to hold the integral literal - decimalV3Type = DecimalV3Type.createDecimalV3Type(new BigDecimal(((Literal) arg).getStringValue())); + List<DataType> newArgTypes = Lists.newArrayListWithCapacity(signature.argumentsTypes.size()); + for (int i = 0; i < signature.argumentsTypes.size(); i++) { + DataType templateType = template == null ? null : getSignatureArgumentType(template, i); + String slotScope = slotScope(template, i, templateType); + newArgTypes.add(replaceDecimalV3Leaf(signature.argumentsTypes.get(i), "", templateType, -1, + slotScope, indexToMapLeafGroup, mapLeafGroupByType, groupWider, scalarGroupWider, + widerType)); + } + signature = signature.withArgumentTypes(signature.hasVarArgs, newArgTypes); + if (signature.returnType instanceof DecimalV3Type + && ((DecimalV3Type) signature.returnType).getPrecision() <= 0) { + signature = signature.withReturnType(widerType); + } + return signature; + } + + private static DataType getSignatureArgumentType(FunctionSignature signature, int index) { + if (index >= signature.argumentsTypes.size()) { + Preconditions.checkState(signature.getVarArgType().isPresent(), + "argument size larger than signature"); + return signature.getVarArgType().get(); + } + return signature.getArgType(index); + } + + /** + * Compute the promoted DecimalV3Type for one decimal slot from its argument type. + */ + private static DecimalV3Type promotedDecimalV3Type(Expression arg, DataType argType) { + if (arg.isLiteral() && arg.getDataType().isIntegralType()) { + // create decimalV3 with minimum scale enough to hold the integral literal + return DecimalV3Type.createDecimalV3Type(new BigDecimal(((Literal) arg).getStringValue())); + } + return DecimalV3Type.forType(argType); + } + + /** + * Collect every decimal leaf of one argument and fold its promoted type into the + * corresponding group. {@code path} is the full structural path through nested + * containers (empty for a top-level slot, {@link #MAP_KEY}/{@link #MAP_VALUE} for + * the key/value of a MAP, {@link #ARRAY_ITEM} for an ARRAY item), so an ARRAY nested + * in a MAP value (e.g. "value/array") or the key/value of a nested MAP (e.g. + * "value/key") keep the enclosing group instead of being merged with the outer + * leaves. {@code templateType} is the corresponding slot of the template signature + * that still carries the original Any/Follow identity of this leaf, and + * {@code containerIndex} is the Any/Follow index of an enclosing MAP container that + * owns this leaf as a whole (i.e. the container itself is an Any/Follow slot), or -1 + * when there is none. {@code slotScope} is the group key prefix of the argument slot + * (see {@link #slotScope}): empty for a common exact vararg slot whose expanded + * arguments have to aggregate, otherwise a per-slot prefix so every independent + * occurrence keeps its own group. {@code widerHolder} accumulates the wider type + * across all decimal leaves. + */ + private static void collectDecimalLeaf(DataType sigType, DataType argType, Expression arg, + String path, DataType templateType, int containerIndex, String slotScope, + Map<Integer, String> indexToMapLeafGroup, Map<DecimalV3Type, String> mapLeafGroupByType, + Map<String, DecimalV3Type> groupWider, Map<DecimalV3Type, DecimalV3Type> scalarGroupWider, + List<DecimalLeaf> scalarLeaves, DecimalV3Type[] widerHolder) { + if (sigType instanceof DecimalV3Type) { + DecimalV3Type sigDecimal = (DecimalV3Type) sigType; + DecimalV3Type promoted = null; + if (!(argType instanceof NullType)) { + promoted = promotedDecimalV3Type(arg, argType); + widerHolder[0] = mergeDecimalV3Type(widerHolder[0], promoted); + } + if (path.isEmpty()) { + // top-level scalar slot: a concrete resolved type may be linked with a + // MAP leaf below by the original Any/Follow identity, and otherwise the + // slots of the same resolved type form one logical group (e.g. the two + // arguments of map_agg) and stay independent from the slots of other groups + if (promoted != null && sigDecimal.getPrecision() > 0) { + scalarLeaves.add(new DecimalLeaf(sigDecimal, promoted, anyFollowIndex(templateType))); + scalarGroupWider.merge(sigDecimal, promoted, + ComputeSignatureHelper::mergeDecimalV3Type); + } + } else if (containerIndex >= 0) { + // the leaf is inside a MAP container that is itself an Any/Follow slot: the + // container absorbs the outer structural path, so the descendants of all the + // containers that share this identity aggregate (e.g. the key of ARRAY<MAP> + // and the key of a plain MAP argument of the same Any(0) slot) + if (promoted != null) { + String groupKey = MAP_CONTAINER_GROUP + containerIndex + "/" + path + ":" + sigDecimal; + groupWider.merge(groupKey, promoted, ComputeSignatureHelper::mergeDecimalV3Type); + } + } else if (isMapNested(path)) { + String groupKey; + int index = anyFollowIndex(templateType); + if (index >= 0) { + // leaves that share the original Any/Follow identity aggregate into one + // group (e.g. the value of map_contains_value and its probe). The group + // is registered even when this leaf is NULL so a concrete linked scalar + // can still join it; only non-NULL evidence is merged below. + groupKey = ANY_INDEX_GROUP + index; + indexToMapLeafGroup.putIfAbsent(index, groupKey); + } else if (isInstanceWithoutIndex(templateType)) { + // an INSTANCE_WITHOUT_INDEX MAP key/value occurrence is an individual + // slot: it keeps its own group instead of merging with the other MAP + // arguments that happen to resolve to the same type + groupKey = slotScope + path + ":" + sigDecimal; + mapLeafGroupByType.putIfAbsent(sigDecimal, groupKey); } else { - decimalV3Type = DecimalV3Type.forType(argType); + // no identity: keep the outermost group (shortest path, key before value) + // for linking by the resolved type + groupKey = path + ":" + sigDecimal; + mapLeafGroupByType.putIfAbsent(sigDecimal, groupKey); + } + if (promoted != null) { + groupWider.merge(groupKey, promoted, ComputeSignatureHelper::mergeDecimalV3Type); } - if (finalType == null) { - finalType = decimalV3Type; + } else if (promoted != null) { + // non-MAP ARRAY leaf: an indexed Any/Follow keeps the original identity so + // it is promoted together with the linked scalar slot of the same group + // (e.g. array_contains(ARRAY<Any(0)>, Any(0))), otherwise the leaf keeps its + // own slot group (see slotScope), so independent occurrences such as + // array_sortby's src/keys, array_zip's arrays and the expanded arguments of + // array_enumerate_uniq are never merged into one truncated type + int index = anyFollowIndex(templateType); + String groupKey = index >= 0 + ? ANY_INDEX_GROUP + index + : slotScope + path + ":" + sigDecimal; + if (index >= 0) { + indexToMapLeafGroup.putIfAbsent(index, groupKey); + } + groupWider.merge(groupKey, promoted, ComputeSignatureHelper::mergeDecimalV3Type); + } + return; + } else if (sigType instanceof MapType) { + MapType mapType = (MapType) sigType; + DataType templateKey = null; + DataType templateValue = null; + int childContainerIndex = containerIndex; + String childPath = path; + if (templateType instanceof MapType) { + templateKey = ((MapType) templateType).getKeyType(); + templateValue = ((MapType) templateType).getValueType(); + } else { + // the whole MAP container is an Any/Follow slot: propagate the group + // identity into the descendant-relative keys and absorb the outer path + int index = anyFollowIndex(templateType); + if (index >= 0) { + childContainerIndex = index; + childPath = ""; + } + } + if (argType instanceof MapType) { + MapType argMapType = (MapType) argType; + collectDecimalLeaf(mapType.getKeyType(), argMapType.getKeyType(), arg, + appendPath(childPath, MAP_KEY), templateKey, childContainerIndex, slotScope, + indexToMapLeafGroup, mapLeafGroupByType, groupWider, + scalarGroupWider, scalarLeaves, widerHolder); + collectDecimalLeaf(mapType.getValueType(), argMapType.getValueType(), arg, + appendPath(childPath, MAP_VALUE), templateValue, childContainerIndex, slotScope, + indexToMapLeafGroup, mapLeafGroupByType, groupWider, + scalarGroupWider, scalarLeaves, widerHolder); + } else if (argType instanceof NullType) { + collectDecimalLeaf(mapType.getKeyType(), argType, arg, + appendPath(childPath, MAP_KEY), templateKey, childContainerIndex, slotScope, + indexToMapLeafGroup, mapLeafGroupByType, groupWider, + scalarGroupWider, scalarLeaves, widerHolder); + collectDecimalLeaf(mapType.getValueType(), argType, arg, + appendPath(childPath, MAP_VALUE), templateValue, childContainerIndex, slotScope, + indexToMapLeafGroup, mapLeafGroupByType, groupWider, + scalarGroupWider, scalarLeaves, widerHolder); + } + return; + } else if (sigType instanceof ArrayType) { + DataType itemArgType; + if (argType instanceof ArrayType) { + itemArgType = ((ArrayType) argType).getItemType(); + } else if (argType instanceof NullType) { + itemArgType = argType; + } else { + return; + } + // carry the enclosing MAP path through the ARRAY so items nested in a MAP + // value stay in the value group; when the ARRAY itself is an Any/Follow slot + // (e.g. the item of ARRAY<Any(0)>), propagate the container identity into the + // item and absorb the outer structural path so all occurrences of the slot + // share descendant-relative keys + DataType templateItem = null; + int childContainerIndex = containerIndex; + String childPath = path; + if (templateType instanceof ArrayType) { + templateItem = ((ArrayType) templateType).getItemType(); + } else { + int index = anyFollowIndex(templateType); + if (index >= 0) { + childContainerIndex = index; + childPath = ""; + } + } + collectDecimalLeaf(((ArrayType) sigType).getItemType(), itemArgType, arg, + appendPath(childPath, ARRAY_ITEM), templateItem, childContainerIndex, slotScope, + indexToMapLeafGroup, mapLeafGroupByType, groupWider, + scalarGroupWider, scalarLeaves, widerHolder); + } + // StructType and other types are not supported + } + + /** + * Replace every decimal leaf in {@code sigType}: leaves inside a MAP use the wider + * type of their own structural group, top-level scalar slots use the wider type of + * their own logical group (slots of the same resolved type), the item of an ARRAY uses + * the wider type of its own slot group (see {@link #slotScope}), and every remaining + * leaf keeps the original behavior of using the single wider type across all decimal + * slots. + */ + private static DataType replaceDecimalV3Leaf(DataType sigType, String path, DataType templateType, + int containerIndex, String slotScope, Map<Integer, String> indexToMapLeafGroup, + Map<DecimalV3Type, String> mapLeafGroupByType, + Map<String, DecimalV3Type> groupWider, Map<DecimalV3Type, DecimalV3Type> scalarGroupWider, + DecimalV3Type widerType) { + if (sigType instanceof DecimalV3Type) { + DecimalV3Type sigDecimal = (DecimalV3Type) sigType; + if (path.isEmpty()) { + // a top-level scalar slot linked with a MAP leaf keeps the type of that + // leaf (e.g. map_contains_value's probe / element_at's lookup must match + // the MAP value/key type). The link is resolved by the original Any/Follow + // identity, falling back to the resolved type when the template can not be + // recovered. + if (sigDecimal.getPrecision() > 0) { + String linkedGroup = null; + int index = anyFollowIndex(templateType); + if (index >= 0) { + linkedGroup = indexToMapLeafGroup.get(index); + } else { + linkedGroup = mapLeafGroupByType.get(sigDecimal); + } + if (linkedGroup != null) { + DecimalV3Type linkedWider = groupWider.get(linkedGroup); + if (linkedWider != null) { + return linkedWider; + } + } + // independent logical Any groups (e.g. the key/value arguments of + // map_agg) keep their own precision/scale instead of being merged + // into one wider type + DecimalV3Type scalarWider = scalarGroupWider.get(sigDecimal); + if (scalarWider != null) { + return scalarWider; + } + } + return widerType; + } + if (containerIndex >= 0) { + DecimalV3Type groupType = groupWider.get( + MAP_CONTAINER_GROUP + containerIndex + "/" + path + ":" + sigDecimal); + return groupType != null ? groupType : widerType; + } + if (isMapNested(path)) { + int index = anyFollowIndex(templateType); + String groupKey; + if (index >= 0) { + groupKey = ANY_INDEX_GROUP + index; + } else if (isInstanceWithoutIndex(templateType)) { + // an INSTANCE_WITHOUT_INDEX MAP key/value occurrence keeps its own group + groupKey = slotScope + path + ":" + sigDecimal; } else { - finalType = (DecimalV3Type) DecimalV3Type.widerDecimalV3Type(finalType, decimalV3Type, false); + groupKey = path + ":" + sigDecimal; + } + DecimalV3Type groupType = groupWider.get(groupKey); + return groupType != null ? groupType : widerType; + } + // non-MAP ARRAY leaf (e.g. the item of ARRAY<Any(index)>): keep the original + // Any/Follow identity so it stays promoted together with the linked scalar + // slot of the same group, otherwise the array and the probe diverge. An + // independent occurrence (array_sortby's src/keys, array_zip's arrays, the + // expanded arguments of array_enumerate_uniq) uses its own slot group instead, + // so it is not truncated to the wider type of the other slots. + int index = anyFollowIndex(templateType); + if (index >= 0) { + DecimalV3Type groupType = groupWider.get(ANY_INDEX_GROUP + index); + if (groupType != null) { + return groupType; + } + } else { + DecimalV3Type groupType = groupWider.get(slotScope + path + ":" + sigDecimal); + if (groupType != null) { + return groupType; + } + } + // otherwise keep the original behavior of the single wider type + return widerType; + } else if (sigType instanceof ArrayType) { + DataType templateItem = null; + int childContainerIndex = containerIndex; + String childPath = path; + if (templateType instanceof ArrayType) { + templateItem = ((ArrayType) templateType).getItemType(); + } else { + // the whole ARRAY is an Any/Follow slot: propagate the group identity into + // the item and absorb the outer structural path (mirror of the MAP branch) + int index = anyFollowIndex(templateType); + if (index >= 0) { + childContainerIndex = index; + childPath = ""; + } + } + return ArrayType.of(replaceDecimalV3Leaf(((ArrayType) sigType).getItemType(), + appendPath(childPath, ARRAY_ITEM), templateItem, childContainerIndex, slotScope, + indexToMapLeafGroup, mapLeafGroupByType, groupWider, scalarGroupWider, widerType)); + } else if (sigType instanceof MapType) { + MapType mapType = (MapType) sigType; + DataType templateKey = null; + DataType templateValue = null; + int childContainerIndex = containerIndex; + String childPath = path; + if (templateType instanceof MapType) { + templateKey = ((MapType) templateType).getKeyType(); + templateValue = ((MapType) templateType).getValueType(); + } else { + // the whole MAP container is an Any/Follow slot: propagate the group + // identity into the descendant-relative keys and absorb the outer path + int idx = anyFollowIndex(templateType); + if (idx >= 0) { + childContainerIndex = idx; + childPath = ""; } } + return MapType.of( + replaceDecimalV3Leaf(mapType.getKeyType(), appendPath(childPath, MAP_KEY), + templateKey, childContainerIndex, slotScope, indexToMapLeafGroup, + mapLeafGroupByType, groupWider, scalarGroupWider, widerType), + replaceDecimalV3Leaf(mapType.getValueType(), appendPath(childPath, MAP_VALUE), + templateValue, childContainerIndex, slotScope, indexToMapLeafGroup, + mapLeafGroupByType, groupWider, scalarGroupWider, widerType)); } - DecimalV3Type argType = finalType; - if (finalType == null) { - return signature; + return sigType; + } + + private static String appendPath(String path, String segment) { + return path.isEmpty() ? segment : path + "/" + segment; + } + + private static boolean isMapNested(String path) { + return path.contains(MAP_KEY) || path.contains(MAP_VALUE); + } + + private static DecimalV3Type mergeDecimalV3Type(DecimalV3Type left, DecimalV3Type right) { + if (left == null) { + return right; } - List<DataType> newArgTypes = signature.argumentsTypes.stream() - .map(at -> TypeCoercionUtils.replaceDecimalV3WithTarget(at, argType)) - .collect(Collectors.toList()); - signature = signature.withArgumentTypes(signature.hasVarArgs, newArgTypes); - if (signature.returnType instanceof DecimalV3Type - && ((DecimalV3Type) signature.returnType).getPrecision() <= 0) { - signature = signature.withReturnType(argType); + return (DecimalV3Type) DecimalV3Type.widerDecimalV3Type(left, right, false); + } + + /** A top-level scalar decimal leaf that may be linked with a MAP key/value leaf. */ + private static class DecimalLeaf { + final DecimalV3Type resolvedType; + final DecimalV3Type promotedType; + final int index; + + DecimalLeaf(DecimalV3Type resolvedType, DecimalV3Type promotedType, int index) { + this.resolvedType = resolvedType; + this.promotedType = promotedType; + this.index = index; } - return signature; + } + + /** + * The index of the original Any/Follow slot this (template) type carries, or -1 when + * it is not an Any/Follow slot. {@link AnyDataType#INSTANCE_WITHOUT_INDEX} has index + * -1, so MAP leaves declared without an index never take part in the scalar linking. + */ + private static int anyFollowIndex(DataType dataType) { + if (dataType instanceof AnyDataType) { + return ((AnyDataType) dataType).getIndex(); + } else if (dataType instanceof FollowToAnyDataType) { + return ((FollowToAnyDataType) dataType).getIndex(); + } + return -1; + } + + /** + * Whether this (template) type is a declared {@link AnyDataType#INSTANCE_WITHOUT_INDEX} + * slot, i.e. an individual slot whose type is taken from the argument alone instead of + * being unified with the other slots (array_sortby's src/keys, the expanded arguments + * of array_enumerate_uniq, a MAP key/value declared without an index). + */ + private static boolean isInstanceWithoutIndex(DataType dataType) { + return dataType instanceof AnyDataType + && ((AnyDataType) dataType).getIndex() == AnyDataType.INDEX_OF_INSTANCE_WITHOUT_INDEX; + } + + /** + * Whether the declared slot type contains an {@link AnyDataType#INSTANCE_WITHOUT_INDEX} + * leaf, looking through nested ARRAY/MAP containers. + */ + private static boolean declaresInstanceWithoutIndex(DataType dataType) { + if (dataType instanceof AnyDataType) { + return isInstanceWithoutIndex(dataType); + } else if (dataType instanceof ArrayType) { + return declaresInstanceWithoutIndex(((ArrayType) dataType).getItemType()); + } else if (dataType instanceof MapType) { + return declaresInstanceWithoutIndex(((MapType) dataType).getKeyType()) + || declaresInstanceWithoutIndex(((MapType) dataType).getValueType()); + } + return false; + } + + /** + * The group key prefix of one argument slot. It is empty for a common exact vararg + * slot, whose expanded arguments all share the declared type and therefore have to + * aggregate into one decimal group (e.g. a variadic MAP argument). Every other + * occurrence is an independent logical type variable - an INSTANCE_WITHOUT_INDEX + * declaration such as array_sortby's src/keys or the expanded arguments of + * array_enumerate_uniq, and a fixed slot such as array_zip's arrays - and gets a + * per-slot prefix so it keeps its own precision/scale instead of being truncated to + * the wider type of the other slots. + */ + private static String slotScope(FunctionSignature template, int index, DataType templateType) { + if (template != null && template.hasVarArgs && index >= template.arity - 1 + && !declaresInstanceWithoutIndex(templateType)) { + return ""; + } + return SLOT_GROUP + index + "/"; Review Comment: [P1] Preserve common fixed ARRAY branch types For a reduced plan `Project[if(flag, a, b)] -> Scan[flag BOOLEAN, a ARRAY<DECIMAL(9,2)>, b ARRAY<DECIMAL(10,3)>]`, `If.customSignature()` deliberately resolves both branches and the return to `ARRAY<DECIMAL(10,3)>`. This blanket fixed-slot scope then gives the branches separate groups, replacing the first expected input back with `ARRAY<DECIMAL(9,2)>` while the return remains `ARRAY<DECIMAL(10,3)>`. The analyzer therefore omits the cast; BE builds the common return column and inserts a Decimal32 nested column into Decimal64, which hits the concrete `ColumnDecimal` cast (and same-width scale splits copy the wrong raw value). This is the converse of the already-reported independent `array_sortby`/`array_zip` case: preserve sharing when a fixed signature has already made slots common, and add an `if` regression covering both branch outcomes across a storage-width boundary. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
