java.lang.NumberFormatException: Character a is neither a decimal digit number, decimal point, nor "e" notation exponential mark. at java.base/java.math.BigDecimal.<init>(BigDecimal.java:522) at java.base/java.math.BigDecimal.<init>(BigDecimal.java:405) at java.base/java.math.BigDecimal.<init>(BigDecimal.java:838) at org.apache.calcite.linq4j.tree.Primitive.charToDecimalCast(Primitive.java:433) at Baz$1$1.current(Unknown Source) at org.apache.shardingsphere.sqlfederation.resultset.SQLFederationResultSet.next(SQLFederationResultSet.java:105) at com.zaxxer.hikari.pool.HikariProxyResultSet.next(HikariProxyResultSet.java) at org.apache.shardingsphere.test.e2e.engine.type.dql.BaseDQLE2EIT.assertRows(BaseDQLE2EIT.java:157) at org.apache.shardingsphere.test.e2e.engine.type.dql.BaseDQLE2EIT.assertResultSet(BaseDQLE2EIT.java:107) at org.apache.shardingsphere.test.e2e.engine.type.dql.GeneralDQLE2EIT.assertExecuteQueryForStatement(GeneralDQLE2EIT.java:99) at org.apache.shardingsphere.test.e2e.engine.type.dql.GeneralDQLE2EIT.assertExecuteQueryWithExpectedDataSource(GeneralDQLE2EIT.java:85) at org.apache.shardingsphere.test.e2e.engine.type.dql.GeneralDQLE2EIT.assertExecuteQuery(GeneralDQLE2EIT.java:62) at org.apache.shardingsphere.test.e2e.engine.type.dql.GeneralDQLE2EIT.assertExecuteQuery(GeneralDQLE2EIT.java:55)
Caused by: org.apache.calcite.runtime.CalciteContextException: At line 0, column 0: Cannot apply 'BIT_COUNT' to arguments of type 'BIT_COUNT(<JAVATYPE(CLASS JAVA.LANG.BOOLEAN)>)'. Supported form(s): 'BIT_COUNT(<NUMERIC>)' 'BIT_COUNT(<BINARY>)' at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490) at org.apache.calcite.runtime.Resources$ExInstWithCause.ex(Resources.java:511) at org.apache.calcite.sql.SqlUtil.newContextException(SqlUtil.java:952) at org.apache.calcite.sql.SqlUtil.newContextException(SqlUtil.java:937) at org.apache.calcite.sql.validate.SqlValidatorImpl.newValidationError(SqlValidatorImpl.java:5899) at org.apache.calcite.sql.SqlCallBinding.newValidationSignatureError(SqlCallBinding.java:399) at org.apache.calcite.sql.type.FamilyOperandTypeChecker.checkSingleOperandType(FamilyOperandTypeChecker.java:137) at org.apache.calcite.sql.type.FamilyOperandTypeChecker.checkOperandTypes(FamilyOperandTypeChecker.java:172) at org.apache.calcite.sql.type.CompositeOperandTypeChecker.check(CompositeOperandTypeChecker.java:345) at org.apache.calcite.sql.type.CompositeOperandTypeChecker.checkOperandTypes(CompositeOperandTypeChecker.java:275) at org.apache.calcite.sql.SqlOperator.checkOperandTypes(SqlOperator.java:754) at org.apache.calcite.sql.SqlOperator.validateOperands(SqlOperator.java:496) at org.apache.calcite.sql.SqlFunction.deriveType(SqlFunction.java:350) at org.apache.calcite.sql.SqlFunction.deriveType(SqlFunction.java:232) at org.apache.calcite.sql.validate.SqlValidatorImpl$DeriveTypeVisitor.visit(SqlValidatorImpl.java:6967) at org.apache.calcite.sql.validate.SqlValidatorImpl$DeriveTypeVisitor.visit(SqlValidatorImpl.java:6954) at org.apache.calcite.sql.SqlCall.accept(SqlCall.java:168) at org.apache.calcite.sql.validate.SqlValidatorImpl.deriveTypeImpl(SqlValidatorImpl.java:2006) at org.apache.calcite.sql.validate.SqlValidatorImpl.deriveType(SqlValidatorImpl.java:1993) at org.apache.calcite.sql.validate.SqlValidatorImpl.expandSelectItem(SqlValidatorImpl.java:505) at org.apache.calcite.sql.validate.SqlValidatorImpl.validateSelectList(SqlValidatorImpl.java:5015) at org.apache.calcite.sql.validate.SqlValidatorImpl.validateSelect(SqlValidatorImpl.java:4096) at org.apache.calcite.sql.validate.SelectNamespace.validateImpl(SelectNamespace.java:62) at org.apache.calcite.sql.validate.AbstractNamespace.validate(AbstractNamespace.java:95) at org.apache.calcite.sql.validate.SqlValidatorImpl.validateNamespace(SqlValidatorImpl.java:1206) at org.apache.calcite.sql.validate.SqlValidatorImpl.validateQuery(SqlValidatorImpl.java:1177) at org.apache.calcite.sql.SqlSelect.validate(SqlSelect.java:282) at org.apache.calcite.sql.validate.SqlValidatorImpl.validateScopedExpression(SqlValidatorImpl.java:1143) at org.apache.calcite.sql.validate.SqlValidatorImpl.validate(SqlValidatorImpl.java:849) at org.apache.calcite.sql2rel.SqlToRelConverter.convertQuery(SqlToRelConverter.java:624) at org.apache.shardingsphere.sqlfederation.optimizer.statement.SQLStatementCompiler.compile(SQLStatementCompiler.java:55) at org.apache.shardingsphere.sqlfederation.optimizer.statement.SQLStatementCompilerEngine.compile(SQLStatementCompilerEngine.java:45) at org.apache.shardingsphere.sqlfederation.optimizer.SQLFederationCompilerEngine.compile(SQLFederationCompilerEngine.java:44) at org.apache.shardingsphere.sqlfederation.engine.SQLFederationEngine.compileQuery(SQLFederationEngine.java:227) at org.apache.shardingsphere.sqlfederation.engine.SQLFederationEngine.executeQuery(SQLFederationEngine.java:208) at org.apache.shardingsphere.driver.executor.engine.DriverExecuteQueryExecutor.executeQuery(DriverExecuteQueryExecutor.java:85) at org.apache.shardingsphere.driver.executor.engine.facade.DriverExecutorFacade.executeQuery(DriverExecutorFacade.java:104) at org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement.executeQuery(ShardingSpherePreparedStatement.java:180)
MySQL BIT_COUNT 调研
初步分析了 ShardingSphere 联邦查询中的 BIT_COUNT 函数异常后,我们再来调研下 MySQL BIT_COUNT 函数,看下该函数的实际作用,以及它支持的参数类型。
根据 MySQL BIT_COUNT 函数文档说明,函数语法格式为 BIT_COUNT(N),用于计算参数 N 的二进制形式中 1 的个数,如果参数为 NULL,BIT_COUNT 函数也会返回 NULL。
1 2
Returns the number of bits that are set in the argument N as an unsigned 64-bit integer, or NULL if the argument is NULL. 以无符号 64 位整数形式返回参数 N 中设置的位数,如果参数为 NULL,则返回 NULL。
/** * Helper function for implementing <code>BITCOUNT</code>. Counts the number * of bits set in an integer value. */ publicstaticlongbitCount(long b) { return Long.bitCount(b); }
/** * Helper function for implementing <code>BITCOUNT</code>. Counts the number * of bits set in the integer portion of a decimal value. */ publicstaticlongbitCount(BigDecimal b) { finalintcomparison= b.compareTo(BITCOUNT_MAX); if (comparison < 0) { if (b.compareTo(BITCOUNT_MIN) <= 0) { return1; } else { return bitCount(b.setScale(0, RoundingMode.DOWN).longValue()); } } elseif (comparison == 0) { return64; } else { return63; } }
对于二进制类型,BIT_COUNT 函数会使用 0xff 逐字节去计算,然后将 BIT 位为 1 的个数进行累加,得到最终 BIT_COUNT 结果。
1 2 3 4 5 6 7 8 9 10 11
/** * Helper function for implementing <code>BITCOUNT</code>. Counts the number * of bits set in a ByteString value. */ publicstaticlongbitCount(ByteString b) { longbitsSet=0; for (inti=0; i < b.length(); i++) { bitsSet += Integer.bitCount(0xff & b.byteAt(i)); } return bitsSet; }
从 Calcite BIT_COUNT 实现逻辑可以了解到,目前,Calcite 还没有适配非数值字符、Boolean 以及日期/时间类型,下面我们来探究下如何增强 BIT_COUNT 实现逻辑,来支持 MySQL 的这些特殊类型。
/** * Visit {@code RexCall}. For most {@code SqlOperator}s, we can get the implementor * from {@code RexImpTable}. Several operators (e.g., CaseWhen) with special semantics * need to be implemented separately. */ @Override public Result visitCall(RexCall call) { if (rexResultMap.containsKey(call)) { return rexResultMap.get(call); } ... // 从 RexImpTable.INSTANCE 中获取函数实现器 Implementor final RexImpTable.RexCallImplementorimplementor= RexImpTable.INSTANCE.get(operator); if (implementor == null) { thrownewRuntimeException("cannot translate call " + call); } // 获取函数操作数 final List<RexNode> operandList = call.getOperands(); // 转换为内部类型 final List<@Nullable Type> storageTypes = EnumUtils.internalTypes(operandList); final List<Result> operandResults = newArrayList<>(); // 遍历操作数,实现函数逻辑 for (inti=0; i < operandList.size(); i++) { finalResultoperandResult= implementCallOperand(operandList.get(i), storageTypes.get(i), this); operandResults.add(operandResult); } callOperandResultMap.put(call, operandResults); finalResultresult= implementor.implement(this, call, operandResults); rexResultMap.put(call, result); return result; }
EnumUtils.internalTypes 会将操作数类型转换为内部类型,可以看到 DATE、TIME 类型会被转换为 Integer 或 int 类型,而 TIMESTAMP 类型则会被转换为 Long 或 long 类型。由于我们测试的 Date 类型,内部最终会使用 Integer 或 int 类型,因此在生成代码时需要调用 toInt 方法进行转换。
1 2 3 4 5 6 7 8 9 10 11 12
// EnumUtils#toInternal 方法 static@Nullable Type toInternal(RelDataType type, boolean forceNotNull) { switch (type.getSqlTypeName()) { case DATE: case TIME: return type.isNullable() && !forceNotNull ? Integer.class : int.class; case TIMESTAMP: return type.isNullable() && !forceNotNull ? Long.class : long.class; default: returnnull; // we don't care; use the default storage type } }
/** * Implementor for MYSQL {@code BIT_COUNT} function. */ privatestaticclassBitCountMySQLImplementorextendsAbstractRexCallImplementor { BitCountMySQLImplementor() { super("bitCount", NullPolicy.STRICT, false); } @Override Expression implementSafe(final RexToLixTranslator translator, final RexCall call, final List<Expression> argValueList) { Expressionexpr= argValueList.get(0); RelDataTyperelDataType= call.getOperands().get(0).getType(); if (SqlTypeUtil.isNull(relDataType)) { return argValueList.get(0); } // In MySQL, BIT_COUNT(TIMESTAMP '1996-08-03 16:22:34') is converted to // BIT_COUNT('19960803162234') for calculation, so the internal int value // needs to be converted to DATE/TIME and TIMESTAMP. SqlTypeNametype= relDataType.getSqlTypeName(); switch (type) { case VARBINARY: case BINARY: return Expressions.call(SqlFunctions.class, "bitCount", expr); case DATE: expr = Expressions.call(BuiltInMethod.INTERNAL_TO_DATE.method, expr); break; case TIME: expr = Expressions.call(BuiltInMethod.INTERNAL_TO_TIME.method, expr); break; case TIMESTAMP: expr = Expressions.call(BuiltInMethod.INTERNAL_TO_TIMESTAMP.method, expr); break; default: break; } return Expressions.call(SqlFunctions.class, "bitCountMySQL", Expressions.box(expr)); } }