Skip to content

Commit 30ae298

Browse files
authored
KE-45007 update check node is decimal or number (#369)
* KE-45007 update check node is decimal or number * KE-45007 update UT, style
1 parent c5f646e commit 30ae298

File tree

6 files changed

+1716
-44
lines changed

6 files changed

+1716
-44
lines changed

core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ dependencies {
8585

8686
testImplementation(project(":testkit"))
8787
testImplementation("commons-lang:commons-lang")
88+
testImplementation("org.mockito:mockito-core")
8889
testImplementation("net.bytebuddy:byte-buddy")
8990
testImplementation("net.hydromatic:foodmart-queries")
9091
testImplementation("net.hydromatic:quidem")

core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,8 @@ default RelDataType leastRestrictive(List<RelDataType> types, boolean convertToV
211211
return leastRestrictive(types, convertToVarying, false);
212212
}
213213

214-
@Nullable RelDataType leastRestrictive(List<RelDataType> types, boolean convertToVarying, boolean coerce);
214+
@Nullable RelDataType leastRestrictive(List<RelDataType> types, boolean convertToVarying,
215+
boolean coerce);
215216

216217
/**
217218
* Creates a SQL type with no precision or scale.

core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java

Lines changed: 261 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424
import org.apache.calcite.rel.type.RelProtoDataType;
2525
import org.apache.calcite.rex.RexCallBinding;
2626
import org.apache.calcite.rex.RexLiteral;
27+
import org.apache.calcite.rex.RexNode;
2728
import org.apache.calcite.sql.ExplicitOperatorBinding;
2829
import org.apache.calcite.sql.SqlCall;
2930
import org.apache.calcite.sql.SqlCallBinding;
3031
import org.apache.calcite.sql.SqlCollation;
3132
import org.apache.calcite.sql.SqlKind;
33+
import org.apache.calcite.sql.SqlNode;
3234
import org.apache.calcite.sql.SqlNodeList;
3335
import org.apache.calcite.sql.SqlNumericLiteral;
3436
import org.apache.calcite.sql.SqlOperatorBinding;
@@ -234,8 +236,7 @@ public static SqlCall stripSeparator(SqlCall call) {
234236
*/
235237
public static final SqlReturnTypeInference ARG0_NULLABLE_IF_EMPTY =
236238
new OrdinalReturnTypeInference(0) {
237-
@Override public RelDataType
238-
inferReturnType(SqlOperatorBinding opBinding) {
239+
@Override public RelDataType inferReturnType(SqlOperatorBinding opBinding) {
239240
final RelDataType type = super.inferReturnType(opBinding);
240241
if (opBinding.getGroupCount() == 0 || opBinding.hasFilter()) {
241242
return opBinding.getTypeFactory()
@@ -704,62 +705,290 @@ public static SqlCall stripSeparator(SqlCall call) {
704705
result.right);
705706
};
706707

707-
private static Pair<RelDataType, RelDataType> getDecimalMultiplyBindingType(SqlOperatorBinding opBinding,
708-
RelDataTypeFactory typeFactory) {
708+
/**
709+
* Determines the appropriate data types for decimal multiplication operations.
710+
*
711+
* <p>This method serves as a dispatcher that routes to the appropriate handler
712+
* based on the type of operator binding. It handles both SQL parse tree bindings
713+
* (SqlCallBinding) and relational expression bindings (RexCallBinding), with a
714+
* fallback for other binding types.</p>
715+
*
716+
* <p>The method is crucial for decimal multiplication type inference because it
717+
* needs to analyze the actual operand values (not just their declared types) to
718+
* determine if integer literals should be converted to decimal types for proper
719+
* decimal arithmetic.</p>
720+
*
721+
* @param opBinding the operator binding containing operand information and types
722+
* @param typeFactory the type factory used to create new data types if needed
723+
* @return a Pair containing the left and right operand types to be used for
724+
* decimal multiplication, possibly with type conversions applied
725+
*/
726+
private static Pair<RelDataType, RelDataType> getDecimalMultiplyBindingType(
727+
SqlOperatorBinding opBinding, RelDataTypeFactory typeFactory) {
728+
// Route to SqlCallBinding handler for SQL parse tree scenarios
709729
if (opBinding instanceof SqlCallBinding) {
710-
RelDataType type1 = createDecimalTypeOrDefault(typeFactory, (SqlCallBinding) opBinding, 0);
711-
RelDataType type2 = createDecimalTypeOrDefault(typeFactory, (SqlCallBinding) opBinding, 1);
712-
return Pair.of(type1, type2);
730+
return getRelDataTypeRelDataTypePair((SqlCallBinding) opBinding, typeFactory);
713731
}
732+
// Route to RexCallBinding handler for relational expression scenarios
714733
if (opBinding instanceof RexCallBinding) {
715-
RelDataType type1 = createDecimalTypeOrDefault(typeFactory, (RexCallBinding) opBinding, 0);
716-
RelDataType type2 = createDecimalTypeOrDefault(typeFactory, (RexCallBinding) opBinding, 1);
717-
return Pair.of(type1, type2);
734+
return getRelDataTypeRelDataTypePair((RexCallBinding) opBinding, typeFactory);
718735
}
736+
// Fallback: return original operand types for other binding types
719737
return Pair.of(opBinding.getOperandType(0), opBinding.getOperandType(1));
720738
}
721739

722-
private static RelDataType createDecimalTypeOrDefault(RelDataTypeFactory typeFactory,
723-
RexCallBinding opBinding, int ordinal) {
724-
RelDataType defaultType = opBinding.getOperandType(ordinal);
740+
/**
741+
* Determines operand types for decimal multiplication in RexCallBinding scenarios.
742+
*
743+
* <p>This method handles relational expression bindings (RexCallBinding) where operands
744+
* are represented as RexNode objects. It analyzes both the declared types and actual
745+
* values to determine if type conversions are needed for proper decimal arithmetic.</p>
746+
*
747+
* <p>The method implements a comprehensive type inference strategy that considers:
748+
* <ul>
749+
* <li>Whether operands have decimal types or contain decimal constants</li>
750+
* <li>Whether operands are numeric (including integer literals)</li>
751+
* <li>Whether integer literals should be converted to decimal types</li>
752+
* </ul>
753+
* </p>
754+
*
755+
* <p>Key scenarios handled:
756+
* <ul>
757+
* <li>Decimal × Decimal: both operands maintain decimal types</li>
758+
* <li>Decimal × Integer: integer may be converted to decimal</li>
759+
* <li>Integer × Decimal: integer may be converted to decimal</li>
760+
* <li>Integer × Integer: no conversion, return original types</li>
761+
* </ul>
762+
* </p>
763+
*
764+
* @param opBinding the RexCallBinding containing RexNode operands
765+
* @param typeFactory the type factory for creating new data types
766+
* @return a Pair containing the potentially converted left and right operand types
767+
*/
768+
private static Pair<RelDataType, RelDataType> getRelDataTypeRelDataTypePair(
769+
RexCallBinding opBinding, RelDataTypeFactory typeFactory) {
770+
// Get the default types for both operands
771+
RelDataType leftDefaultType = opBinding.getOperandType(0);
772+
RelDataType rightDefaultType = opBinding.getOperandType(1);
725773
try {
726-
if (!SqlNodeUtils.isNumericLiteral(opBinding, ordinal)) {
727-
return defaultType;
774+
// Extract the actual RexNode operands for value analysis
775+
RexNode leftOperand = opBinding.operands().get(0);
776+
RexNode rightOperand = opBinding.operands().get(1);
777+
778+
// Determine if operands are decimal (either by type or by constant value)
779+
// Check if left operand is decimal by type or contains decimal constant
780+
boolean leftIsDecimal = SqlTypeUtil.isDecimal(leftDefaultType)
781+
|| SqlNodeUtils.isDecimalConstantRexNode(leftOperand);
782+
// Check if right operand is decimal by type or contains decimal constant
783+
boolean rightIsDecimal = SqlTypeUtil.isDecimal(rightDefaultType)
784+
|| SqlNodeUtils.isDecimalConstantRexNode(rightOperand);
785+
// Determine if operands are numeric (either by type or by literal value)
786+
boolean leftIsNumeric = SqlTypeUtil.isNumeric(leftDefaultType)
787+
|| SqlNodeUtils.isNumericLiteralRexNode(leftOperand);
788+
boolean rightIsNumeric = SqlTypeUtil.isNumeric(rightDefaultType)
789+
|| SqlNodeUtils.isNumericLiteralRexNode(rightOperand);
790+
791+
// Apply decimal type inference if at least one operand is decimal and both are numeric
792+
// This covers scenarios where we need decimal arithmetic precision
793+
if ((leftIsDecimal || rightIsDecimal) && leftIsNumeric && rightIsNumeric) {
794+
// Convert operands to appropriate decimal types if needed
795+
RelDataType type1 = createDecimalTypeOrDefault(typeFactory, opBinding, 0, leftDefaultType);
796+
RelDataType type2 = createDecimalTypeOrDefault(typeFactory, opBinding, 1, rightDefaultType);
797+
return Pair.of(type1, type2);
728798
}
729799

730-
RexLiteral literal = (RexLiteral) opBinding.operands().get(ordinal);
800+
// Fallback: both operands are numeric but neither is decimal, no conversion needed
801+
return Pair.of(leftDefaultType, rightDefaultType);
802+
} catch (Exception e) {
803+
// Exception safety: return original types if any error occurs during analysis
804+
return Pair.of(leftDefaultType, rightDefaultType);
805+
}
806+
}
731807

732-
if (SqlNodeUtils.isDecimalConstant(literal)) {
808+
/**
809+
* Converts integer literals to decimal types for RexCallBinding scenarios.
810+
*
811+
* <p>This method is responsible for type conversion in relational expression scenarios
812+
* where integer literals need to be promoted to decimal types for proper decimal arithmetic.
813+
* The conversion is essential when multiplying integers with decimals to maintain
814+
* precision and avoid unintended integer arithmetic.</p>
815+
*
816+
* <p>Conversion logic:
817+
* <ul>
818+
* <li>If the operand is already a decimal constant, return the default type</li>
819+
* <li>If the operand is an integer literal, convert it to DECIMAL with appropriate
820+
* precision</li>
821+
* <li>For other cases, return the default type unchanged</li>
822+
* </ul>
823+
* </p>
824+
*
825+
* <p>The precision calculation for converted integers uses the number of digits
826+
* in the integer value. For example:
827+
* <ul>
828+
* <li>123 → DECIMAL(3, 0) (3 digits)</li>
829+
* <li>45 → DECIMAL(2, 0) (2 digits)</li>
830+
* <li>0 → DECIMAL(1, 0) (special case)</li>
831+
* </ul>
832+
* </p>
833+
*
834+
* @param typeFactory the type factory for creating new DECIMAL types
835+
* @param opBinding the RexCallBinding containing the operands
836+
* @param ordinal the zero-based index of the operand to analyze
837+
* @param defaultType the default type to return if no conversion is needed
838+
* @return either a converted DECIMAL type or the original default type
839+
*/
840+
private static RelDataType createDecimalTypeOrDefault(RelDataTypeFactory typeFactory,
841+
RexCallBinding opBinding, int ordinal, RelDataType defaultType) {
842+
// Check if the operand at the specified position is a numeric literal
843+
if (SqlNodeUtils.isNumericLiteral(opBinding, ordinal)) {
844+
RexNode node = opBinding.operands().get(ordinal);
845+
// If it's already a decimal constant, no conversion needed
846+
if (SqlNodeUtils.isDecimalConstant(node)) {
733847
return defaultType;
734848
}
735849

736-
Long value = literal.getValueAs(Long.class);
737-
int length = (int) Math.floor(Math.log10(Math.abs(value))) + 1;
738-
return typeFactory.createSqlType(SqlTypeName.DECIMAL, length, 0);
739-
} catch (IllegalArgumentException | AssertionError e) {
740-
return defaultType;
850+
// Attempt to convert integer literals to decimal for proper decimal multiplication
851+
RexLiteral literal = (RexLiteral) node;
852+
RelDataType type = literal.getType();
853+
// Check if it's an exact numeric type (INTEGER, BIGINT, etc.) but not already DECIMAL
854+
if (SqlTypeUtil.isExactNumeric(type) && !SqlTypeUtil.isDecimal(type)) {
855+
// Convert integer types (INTEGER, BIGINT, etc.) to DECIMAL
856+
Long value = literal.getValueAs(Long.class);
857+
if (value != null) {
858+
// Calculate precision based on the number of digits in the integer
859+
// For example: 123 → 3 digits, 45 → 2 digits, 0 → 1 digit, -123 → 4 digits
860+
int length;
861+
if (value == 0) {
862+
// Special case: 0 should have precision 1
863+
length = 1;
864+
} else if (value < 0) {
865+
// like sqlNode, negative number add 1 for '-'
866+
length = (int) Math.floor(Math.log10(Math.abs(value))) + 2;
867+
} else {
868+
length = (int) Math.floor(Math.log10(value)) + 1;
869+
}
870+
// Create DECIMAL type with calculated precision and 0 scale
871+
return typeFactory.createSqlType(SqlTypeName.DECIMAL, length, 0);
872+
}
873+
}
741874
}
875+
// Return default type for non-literals or when conversion is not applicable
876+
return defaultType;
742877
}
743878

744-
private static RelDataType createDecimalTypeOrDefault(RelDataTypeFactory typeFactory,
745-
SqlCallBinding opBinding, int ordinal) {
746-
RelDataType defaultType = opBinding.getOperandType(ordinal);
879+
/**
880+
* Determines operand types for decimal multiplication in SqlCallBinding scenarios.
881+
*
882+
* <p>This method handles SQL parse tree bindings (SqlCallBinding) where operands
883+
* are represented as SqlNode objects. It analyzes both the declared types and actual
884+
* values to determine if type conversions are needed for proper decimal arithmetic.</p>
885+
*
886+
* <p>Similar to the RexCallBinding version, this method implements comprehensive
887+
* type inference but operates on SqlNode objects instead of RexNode objects.
888+
* The key difference is that during SQL parsing, integer literals are automatically
889+
* converted to DECIMAL type, which affects the conversion logic.</p>
890+
*
891+
* <p>Key scenarios handled:
892+
* <ul>
893+
* <li>Decimal × Decimal: both operands maintain decimal types</li>
894+
* <li>Decimal × Integer: integer may be converted to decimal</li>
895+
* <li>Integer × Decimal: integer may be converted to decimal</li>
896+
* <li>Integer × Integer: no conversion, return original types</li>
897+
* </ul>
898+
* </p>
899+
*
900+
* @param opBinding the SqlCallBinding containing SqlNode operands
901+
* @param typeFactory the type factory for creating new data types
902+
* @return a Pair containing the potentially converted left and right operand types
903+
*/
904+
private static Pair<RelDataType, RelDataType> getRelDataTypeRelDataTypePair(
905+
SqlCallBinding opBinding, RelDataTypeFactory typeFactory) {
906+
// Get the default types for both operands
907+
RelDataType leftDefaultType = opBinding.getOperandType(0);
908+
RelDataType rightDefaultType = opBinding.getOperandType(1);
747909
try {
748-
if (!SqlNodeUtils.isNumericLiteral(opBinding, ordinal)) {
749-
return defaultType;
910+
// Extract the actual SqlNode operands for value analysis
911+
SqlNode leftOperand = opBinding.operands().get(0);
912+
SqlNode rightOperand = opBinding.operands().get(1);
913+
914+
// Determine if operands are decimal (either by type or by constant value)
915+
// Check if left operand is decimal by type or contains decimal constant
916+
boolean leftIsDecimal = SqlTypeUtil.isDecimal(leftDefaultType)
917+
|| SqlNodeUtils.isDecimalConstantSqlNode(leftOperand);
918+
// Check if right operand is decimal by type or contains decimal constant
919+
boolean rightIsDecimal = SqlTypeUtil.isDecimal(rightDefaultType)
920+
|| SqlNodeUtils.isDecimalConstantSqlNode(rightOperand);
921+
// Determine if operands are numeric (either by type or by literal value)
922+
boolean leftIsNumeric = SqlTypeUtil.isNumeric(leftDefaultType)
923+
|| SqlNodeUtils.isNumericLiteralSqlNode(leftOperand);
924+
boolean rightIsNumeric = SqlTypeUtil.isNumeric(rightDefaultType)
925+
|| SqlNodeUtils.isNumericLiteralSqlNode(rightOperand);
926+
927+
// Apply decimal type inference if at least one operand is decimal and both are numeric
928+
// This covers scenarios where we need decimal arithmetic precision
929+
if ((leftIsDecimal || rightIsDecimal) && leftIsNumeric && rightIsNumeric) {
930+
// Convert operands to appropriate decimal types if needed
931+
RelDataType type1 = createDecimalTypeOrDefault(typeFactory, opBinding, 0, leftDefaultType);
932+
RelDataType type2 = createDecimalTypeOrDefault(typeFactory, opBinding, 1, rightDefaultType);
933+
return Pair.of(type1, type2);
750934
}
751935

936+
// Return default types for scenarios where both operands are numeric but neither is decimal
937+
return Pair.of(leftDefaultType, rightDefaultType);
938+
} catch (Exception e) {
939+
// Exception safety: return original types if any error occurs during analysis
940+
return Pair.of(leftDefaultType, rightDefaultType);
941+
}
942+
}
943+
944+
/**
945+
* Converts numeric literals to decimal types for SqlCallBinding scenarios.
946+
*
947+
* <p>This method handles type conversion in SQL parse tree scenarios where numeric literals
948+
* need to be processed for decimal arithmetic. Unlike the RexCallBinding version, this method
949+
* operates on SqlNode objects where integer literals have already been converted to DECIMAL
950+
* type during SQL parsing.</p>
951+
*
952+
* <p>Key difference from RexCallBinding version:
953+
* <ul>
954+
* <li>During SQL parsing, integer literals are automatically converted to DECIMAL type</li>
955+
* <li>This method primarily ensures proper precision and scale are preserved</li>
956+
* <li>No need to manually calculate precision from integer values</li>
957+
* </ul>
958+
* </p>
959+
*
960+
* <p>Conversion logic:
961+
* <ul>
962+
* <li>If the operand is a numeric literal (DECIMAL or INTEGER), create DECIMAL type</li>
963+
* <li>Use the literal's existing precision and scale information</li>
964+
* <li>For non-literals, return the default type unchanged</li>
965+
* </ul>
966+
* </p>
967+
*
968+
* @param typeFactory the type factory for creating new DECIMAL types
969+
* @param opBinding the SqlCallBinding containing SqlNode operands
970+
* @param ordinal the zero-based index of the operand to analyze
971+
* @param defaultType the default type to return if no conversion is needed
972+
* @return either a converted DECIMAL type or the original default type
973+
*/
974+
private static RelDataType createDecimalTypeOrDefault(RelDataTypeFactory typeFactory,
975+
SqlCallBinding opBinding, int ordinal, RelDataType defaultType) {
976+
// Check if the operand at the specified position is a numeric literal
977+
if (SqlNodeUtils.isNumericLiteral(opBinding, ordinal)) {
978+
// Extract the SqlNumericLiteral from the call's operand list
752979
SqlNumericLiteral literal =
753980
(SqlNumericLiteral) opBinding.getCall().getOperandList().get(ordinal);
754-
// When parsing into a **SqlNode**, integers are converted to **DECIMAL** type.
755-
if (SqlNodeUtils.isDecimalConstant(literal)) {
981+
// When parsing into a SqlNode, integers are converted to DECIMAL type
982+
// This method ensures both decimal and integer literals are properly handled
983+
if (SqlNodeUtils.isDecimalOrIntegerConstant(literal)) {
984+
// Create DECIMAL type using the literal's existing precision and scale
985+
// This preserves the original precision/scale information from parsing
756986
return typeFactory.createSqlType(SqlTypeName.DECIMAL, literal.getPrec(),
757987
literal.getScale());
758988
}
759-
return defaultType;
760-
} catch (IllegalArgumentException e) {
761-
return defaultType;
762989
}
990+
// Return default type for non-literals or when conversion is not applicable
991+
return defaultType;
763992
}
764993

765994
/**

0 commit comments

Comments
 (0)