This is an automated email from the ASF dual-hosted git repository.
zhehu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 901aadbe8b [CALCITE-6730] Add CONVERT function(enabled in Oracle
library)
901aadbe8b is described below
commit 901aadbe8bd55e2da791d9d827185a8c803b1b76
Author: Zhe Hu <[email protected]>
AuthorDate: Sun Dec 22 11:28:17 2024 +0800
[CALCITE-6730] Add CONVERT function(enabled in Oracle library)
---
core/src/main/codegen/templates/Parser.jj | 15 ++-
.../calcite/adapter/enumerable/RexImpTable.java | 2 +
.../org/apache/calcite/runtime/SqlFunctions.java | 23 ++++
.../main/java/org/apache/calcite/sql/SqlKind.java | 7 +-
.../calcite/sql/fun/SqlLibraryOperators.java | 4 +
.../calcite/sql/fun/SqlOracleConvertFunction.java | 123 +++++++++++++++++++++
.../calcite/sql/fun/SqlStdOperatorTable.java | 11 ++
.../calcite/sql/validate/SqlValidatorImpl.java | 6 +-
.../calcite/sql2rel/StandardConvertletTable.java | 38 +++++--
.../org/apache/calcite/util/BuiltInMethod.java | 1 +
.../java/org/apache/calcite/test/JdbcTest.java | 42 +++++++
.../org/apache/calcite/test/SqlFunctionsTest.java | 6 +
core/src/test/resources/sql/functions.iq | 21 ++++
site/_docs/reference.md | 1 +
.../apache/calcite/sql/parser/SqlParserTest.java | 13 +++
.../org/apache/calcite/test/SqlOperatorTest.java | 47 ++++++++
16 files changed, 345 insertions(+), 15 deletions(-)
diff --git a/core/src/main/codegen/templates/Parser.jj
b/core/src/main/codegen/templates/Parser.jj
index f826c4241e..279c6caf6b 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -6319,10 +6319,17 @@ SqlNode BuiltinFunctionCall() :
}
|
<COMMA> e = SimpleIdentifier() { args.add(e); }
- <COMMA> e = SimpleIdentifier() { args.add(e); }
- <RPAREN> {
- return SqlStdOperatorTable.CONVERT.createCall(s.end(this),
args);
- }
+ (
+ <COMMA> e = SimpleIdentifier() { args.add(e); }
+ <RPAREN> {
+ SqlOperator op =
SqlStdOperatorTable.getConvertFuncByConformance(this.conformance);
+ return op.createCall(s.end(this), args);
+ }
+ |
+ <RPAREN> {
+ return
SqlLibraryOperators.CONVERT_ORACLE.createCall(s.end(this), args);
+ }
+ )
)
|
// MSSql CONVERT(type, val [,style])
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index fd7aa0cf76..72b5c44cb1 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -181,6 +181,7 @@ import static
org.apache.calcite.sql.fun.SqlLibraryOperators.CONCAT_WS_MSSQL;
import static
org.apache.calcite.sql.fun.SqlLibraryOperators.CONCAT_WS_POSTGRESQL;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONCAT_WS_SPARK;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONTAINS_SUBSTR;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONVERT_ORACLE;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.COSD;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.COSH;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.COTH;
@@ -740,6 +741,7 @@ public class RexImpTable {
defineMethod(CONCAT_WS_SPARK,
BuiltInMethod.MULTI_TYPE_STRING_ARRAY_CONCAT_WITH_SEPARATOR.method,
NullPolicy.ARG0);
+ defineMethod(CONVERT_ORACLE, BuiltInMethod.CONVERT_ORACLE.method,
NullPolicy.ARG0);
defineMethod(OVERLAY, BuiltInMethod.OVERLAY.method, NullPolicy.STRICT);
defineMethod(POSITION, BuiltInMethod.POSITION.method, NullPolicy.STRICT);
defineMethod(ASCII, BuiltInMethod.ASCII.method, NullPolicy.STRICT);
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index ba6cf04f73..2ed54e868e 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -1681,6 +1681,29 @@ public class SqlFunctions {
}
}
+ /** Oracle's {@code CONVERT(charValue, destCharsetName[, srcCharsetName])}
function,
+ * return null if s is null or empty. */
+ public static String convertOracle(String s, String... args) {
+ final Charset src;
+ final Charset dest;
+ if (args.length == 1) {
+ // srcCharsetName is not specified
+ src = Charset.defaultCharset();
+ dest = SqlUtil.getCharset(args[0]);
+ } else {
+ dest = SqlUtil.getCharset(args[0]);
+ src = SqlUtil.getCharset(args[1]);
+ }
+ byte[] bytes = s.getBytes(src);
+ final CharsetDecoder decoder = dest.newDecoder();
+ final ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ try {
+ return decoder.decode(buffer).toString();
+ } catch (CharacterCodingException ex) {
+ throw RESOURCE.charsetEncoding(s, dest.name()).ex();
+ }
+ }
+
/** State for {@code PARSE_URL}. */
@Deterministic
public static class ParseUrlFunction {
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
index 5457f205b1..84744b4c62 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -147,6 +147,9 @@ public enum SqlKind {
/** {@code CONVERT} function. */
CONVERT,
+ /** Oracle's {@code CONVERT} function. */
+ CONVERT_ORACLE,
+
/** {@code TRANSLATE} function. */
TRANSLATE,
@@ -1455,8 +1458,8 @@ public enum SqlKind {
public static final Set<SqlKind> EXPRESSION =
EnumSet.complementOf(
concat(
- EnumSet.of(AS, ARGUMENT_ASSIGNMENT, CONVERT, TRANSLATE, DEFAULT,
- RUNNING, FINAL, LAST, FIRST, PREV, NEXT,
+ EnumSet.of(AS, ARGUMENT_ASSIGNMENT, CONVERT, CONVERT_ORACLE,
TRANSLATE,
+ DEFAULT, RUNNING, FINAL, LAST, FIRST, PREV, NEXT,
FILTER, WITHIN_GROUP, IGNORE_NULLS, RESPECT_NULLS, SEPARATOR,
DESCENDING, CUBE, ROLLUP, GROUPING_SETS, EXTEND, LATERAL,
SELECT, JOIN, OTHER_FUNCTION, POSITION, CAST, TRIM, FLOOR,
CEIL,
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
index b0173db832..9dbb0d8334 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
@@ -458,6 +458,10 @@ public abstract class SqlLibraryOperators {
public static final SqlFunction SUBSTR_ORACLE =
SUBSTR.withKind(SqlKind.SUBSTR_ORACLE);
+ @LibraryOperator(libraries = {ORACLE})
+ public static final SqlFunction CONVERT_ORACLE =
+ new SqlOracleConvertFunction("CONVERT");
+
/** PostgreSQL's "SUBSTR(string, position [, substringLength ])" function. */
@LibraryOperator(libraries = {POSTGRESQL})
public static final SqlFunction SUBSTR_POSTGRESQL =
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlOracleConvertFunction.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlOracleConvertFunction.java
new file mode 100644
index 0000000000..218b3ba5f1
--- /dev/null
+++
b/core/src/main/java/org/apache/calcite/sql/fun/SqlOracleConvertFunction.java
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+package org.apache.calcite.sql.fun;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexCallBinding;
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlCallBinding;
+import org.apache.calcite.sql.SqlFunctionCategory;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlOperandCountRange;
+import org.apache.calcite.sql.SqlOperatorBinding;
+import org.apache.calcite.sql.SqlUtil;
+import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.sql.type.SqlOperandCountRanges;
+import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.sql.validate.SqlValidator;
+import org.apache.calcite.sql.validate.SqlValidatorScope;
+
+import java.nio.charset.Charset;
+import java.util.List;
+
+import static org.apache.calcite.sql.type.NonNullableAccessors.getCollation;
+
+import static java.util.Objects.requireNonNull;
+
+/** Oracle's "CONVERT(charValue, destCharsetName[, srcCharsetName])" function.
+ *
+ * <p>It has a slight different semantics to standard SQL's
+ * {@link SqlStdOperatorTable#CONVERT} function on operands' order, and default
+ * charset will be used if the {@code srcCharsetName} is not specified.
+ *
+ * <p>Returns null if {@code charValue} is null or empty. */
+public class SqlOracleConvertFunction extends SqlConvertFunction {
+ //~ Constructors
-----------------------------------------------------------
+
+ public SqlOracleConvertFunction(String name) {
+ super(name, SqlKind.CONVERT_ORACLE, ReturnTypes.ARG0, null, null,
+ SqlFunctionCategory.STRING);
+ }
+
+ //~ Methods
----------------------------------------------------------------
+
+ @Override public void validateCall(SqlCall call, SqlValidator validator,
+ SqlValidatorScope scope, SqlValidatorScope operandScope) {
+ final List<SqlNode> operands = call.getOperandList();
+ operands.get(0).validateExpr(validator, scope);
+ // validate if the Charsets are legal.
+ assert operands.get(1) instanceof SqlIdentifier;
+ final String src_charset = operands.get(1).toString();
+ SqlUtil.getCharset(src_charset);
+ if (operands.size() == 3) {
+ assert operands.get(2) instanceof SqlIdentifier;
+ final String dest_charset = operands.get(2).toString();
+ SqlUtil.getCharset(dest_charset);
+ }
+ super.validateQuantifier(validator, call);
+ }
+
+ @Override public RelDataType inferReturnType(
+ SqlOperatorBinding opBinding) {
+ final RelDataType ret = opBinding.getOperandType(0);
+ if (SqlTypeUtil.isNull(ret)) {
+ return ret;
+ }
+ final String descCharsetName;
+ if (opBinding instanceof SqlCallBinding) {
+ descCharsetName = ((SqlCallBinding)
opBinding).getCall().operand(1).toString();
+ } else {
+ descCharsetName = ((RexCallBinding)
opBinding).getStringLiteralOperand(1);
+ }
+ assert descCharsetName != null;
+ Charset descCharset = SqlUtil.getCharset(descCharsetName);
+ return opBinding
+ .getTypeFactory().createTypeWithCharsetAndCollation(ret, descCharset,
getCollation(ret));
+ }
+
+ @Override public RelDataType deriveType(SqlValidator validator,
+ SqlValidatorScope scope, SqlCall call) {
+ RelDataType nodeType =
+ validator.deriveType(scope, call.operand(0));
+ requireNonNull(nodeType, "nodeType");
+ RelDataType ret = validateOperands(validator, scope, call);
+ if (SqlTypeUtil.isNull(ret)) {
+ return ret;
+ }
+ Charset descCharset = SqlUtil.getCharset(call.operand(1).toString());
+ return validator.getTypeFactory()
+ .createTypeWithCharsetAndCollation(ret, descCharset,
getCollation(ret));
+ }
+
+ @Override public String getSignatureTemplate(final int operandsCount) {
+ switch (operandsCount) {
+ case 2:
+ return "{0}({1}, {2})";
+ case 3:
+ return "{0}({1}, {2}, {3})";
+ default:
+ throw new IllegalStateException("operandsCount should be 2 or 3, got "
+ + operandsCount);
+ }
+ }
+
+ @Override public SqlOperandCountRange getOperandCountRange() {
+ return SqlOperandCountRanges.between(2, 3);
+ }
+}
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index 9e0ca8bb0a..b7bf8ca25b 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -2832,4 +2832,15 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
return floor ? SqlStdOperatorTable.FLOOR : SqlStdOperatorTable.CEIL;
}
}
+
+ /** Returns the operator for standard {@code CONVERT} and Oracle's {@code
CONVERT}
+ * with the given library. */
+ public static SqlOperator getConvertFuncByConformance(SqlConformance
conformance) {
+ if (SqlConformanceEnum.ORACLE_10 == conformance
+ || SqlConformanceEnum.ORACLE_12 == conformance) {
+ return SqlLibraryOperators.CONVERT_ORACLE;
+ } else {
+ return SqlStdOperatorTable.CONVERT;
+ }
+ }
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index f216081fce..f992321140 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -4287,8 +4287,10 @@ public class SqlValidatorImpl implements
SqlValidatorWithHints {
// can be another SqlCall, or an SqlIdentifier.
checkRollUp(grandParent, parent, stripDot, scope, contextClause);
} else if (stripDot.getKind() == SqlKind.CONVERT
- || stripDot.getKind() == SqlKind.TRANSLATE) {
- // only need to check operand[0] for CONVERT or TRANSLATE
+ || stripDot.getKind() == SqlKind.TRANSLATE
+ || stripDot.getKind() == SqlKind.CONVERT_ORACLE) {
+ // only need to check operand[0] for
+ // CONVERT, TRANSLATE or CONVERT_ORACLE
SqlNode child = ((SqlCall) stripDot).getOperandList().get(0);
checkRollUp(parent, current, child, scope, contextClause);
} else if (stripDot.getKind() == SqlKind.LAMBDA) {
diff --git
a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
index ff0afa6037..78f8865915 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
@@ -296,6 +296,7 @@ public class StandardConvertletTable extends
ReflectiveConvertletTable {
(cx, call) -> cx.convertExpression(call.operand(0)));
registerOp(SqlStdOperatorTable.CONVERT, this::convertCharset);
+ registerOp(SqlLibraryOperators.CONVERT_ORACLE, this::convertCharset);
registerOp(SqlStdOperatorTable.TRANSLATE, this::translateCharset);
// "SQRT(x)" is equivalent to "POWER(x, .5)"
registerOp(SqlStdOperatorTable.SQRT,
@@ -855,15 +856,38 @@ public class StandardConvertletTable extends
ReflectiveConvertletTable {
protected RexNode convertCharset(
@UnknownInitialization StandardConvertletTable this,
SqlRexContext cx, SqlCall call) {
+ final RexBuilder rexBuilder = cx.getRexBuilder();
final SqlParserPos pos = call.getParserPosition();
final SqlNode expr = call.operand(0);
- final String srcCharset = call.operand(1).toString();
- final String destCharset = call.operand(2).toString();
- final RexBuilder rexBuilder = cx.getRexBuilder();
- return rexBuilder.makeCall(pos, SqlStdOperatorTable.CONVERT,
- cx.convertExpression(expr),
- rexBuilder.makeLiteral(srcCharset),
- rexBuilder.makeLiteral(destCharset));
+ final SqlOperator op = call.getOperator();
+ final String srcCharset;
+ final String destCharset;
+ switch (op.getKind()) {
+ case CONVERT:
+ srcCharset = call.operand(1).toString();
+ destCharset = call.operand(2).toString();
+ return rexBuilder.makeCall(pos, op,
+ cx.convertExpression(expr),
+ rexBuilder.makeLiteral(srcCharset),
+ rexBuilder.makeLiteral(destCharset));
+ case CONVERT_ORACLE:
+ destCharset = call.operand(1).toString();
+ switch (call.operandCount()) {
+ case 2:
+ // when srcCharsetName is not specified
+ return rexBuilder.makeCall(pos, op,
+ cx.convertExpression(expr),
+ rexBuilder.makeLiteral(destCharset));
+ default:
+ srcCharset = call.operand(2).toString();
+ return rexBuilder.makeCall(pos, op,
+ cx.convertExpression(expr),
+ rexBuilder.makeLiteral(destCharset),
+ rexBuilder.makeLiteral(srcCharset));
+ }
+ default:
+ throw Util.unexpected(op.getKind());
+ }
}
protected RexNode translateCharset(
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 6ea6701a65..fd96d99a28 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -411,6 +411,7 @@ public enum BuiltInMethod {
TO_CODE_POINTS(SqlFunctions.class, "toCodePoints", String.class),
CONVERT(SqlFunctions.class, "convertWithCharset", String.class, String.class,
String.class),
+ CONVERT_ORACLE(SqlFunctions.class, "convertOracle", String.class,
String[].class),
EXP(SqlFunctions.class, "exp", double.class),
MOD(SqlFunctions.class, "mod", long.class, long.class),
POWER(SqlFunctions.class, "power", double.class, double.class),
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 0dcc797d4e..f378fcca44 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -85,6 +85,7 @@ import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.parser.impl.SqlParserImpl;
import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.sql2rel.SqlToRelConverter.Config;
import org.apache.calcite.test.schemata.catchall.CatchallSchema;
import org.apache.calcite.test.schemata.foodmart.FoodmartSchema;
@@ -7352,6 +7353,47 @@ public class JdbcTest {
.throws_("No match found for function signature NVL(<NUMERIC>,
<NUMERIC>)");
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-6730">[CALCITE-6730]
+ * Add CONVERT function(enabled in Oracle library)</a>. */
+ @Test void testConvertOracle() {
+ CalciteAssert.AssertThat withOracle10 =
+ CalciteAssert.hr()
+ .with(SqlConformanceEnum.ORACLE_10);
+ testConvertOracleInternal(withOracle10);
+
+ CalciteAssert.AssertThat withOracle12
+ = withOracle10.with(SqlConformanceEnum.ORACLE_12);
+ testConvertOracleInternal(withOracle12);
+ }
+
+ private void testConvertOracleInternal(CalciteAssert.AssertThat with) {
+ with.query("select \"name\", \"empid\" from \"hr\".\"emps\"\n"
+ + "where convert(\"name\", GBK)=_GBK'Eric'")
+ .returns("name=Eric; empid=200\n");
+ with.query("select \"name\", \"empid\" from \"hr\".\"emps\"\n"
+ + "where _BIG5'Eric'=convert(\"name\", LATIN1)")
+ .throws_("Cannot apply operation '=' to strings with "
+ + "different charsets 'Big5' and 'ISO-8859-1'");
+ // use LATIN1 as dest charset, not BIG5
+ with.query("select \"name\", \"empid\" from \"hr\".\"emps\"\n"
+ + "where _BIG5'Eric'=convert(\"name\", LATIN1, BIG5)")
+ .throws_("Cannot apply operation '=' to strings with "
+ + "different charsets 'Big5' and 'ISO-8859-1'");
+
+ // check cast
+ with.query("select \"name\", \"empid\" from \"hr\".\"emps\"\n"
+ + "where cast(convert(\"name\", LATIN1, UTF8) as varchar)='Eric'")
+ .returns("name=Eric; empid=200\n");
+ // the result of convert(\"name\", GBK) has GBK charset
+ // while CHAR(5) has ISO-8859-1 charset, which is not allowed to cast
+ with.query("select \"name\", \"empid\" from \"hr\".\"emps\"\n"
+ + "where cast(convert(\"name\", GBK) as varchar)='Eric'")
+ .throws_(
+ "cannot convert value of type "
+ + "JavaType(class java.lang.String CHARACTER SET \"GBK\") to
type VARCHAR NOT NULL");
+ }
+
@Test void testIf() {
CalciteAssert.that(CalciteAssert.Config.REGULAR)
.with(CalciteConnectionProperty.FUN, "bigquery")
diff --git a/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
b/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
index 34f833eeb6..907390828d 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
@@ -50,6 +50,7 @@ import static
org.apache.calcite.runtime.SqlFunctions.concatMultiTypeWithSeparat
import static org.apache.calcite.runtime.SqlFunctions.concatMultiWithNull;
import static org.apache.calcite.runtime.SqlFunctions.concatMultiWithSeparator;
import static org.apache.calcite.runtime.SqlFunctions.concatWithNull;
+import static org.apache.calcite.runtime.SqlFunctions.convertOracle;
import static org.apache.calcite.runtime.SqlFunctions.fromBase64;
import static org.apache.calcite.runtime.SqlFunctions.greater;
import static org.apache.calcite.runtime.SqlFunctions.initcap;
@@ -319,6 +320,11 @@ class SqlFunctionsTest {
assertThat(concatMultiObjectWithSeparator("abc", null, null), is(""));
}
+ @Test void testConvertOracle() {
+ assertThat(convertOracle("a", "UTF8", "LATIN1"), is("a"));
+ assertThat(convertOracle("a", "UTF8"), is("a"));
+ }
+
@Test void testPosixRegex() {
final SqlFunctions.PosixRegexFunction f =
new SqlFunctions.PosixRegexFunction();
diff --git a/core/src/test/resources/sql/functions.iq
b/core/src/test/resources/sql/functions.iq
index 2ffc73eb45..1322a332a7 100644
--- a/core/src/test/resources/sql/functions.iq
+++ b/core/src/test/resources/sql/functions.iq
@@ -540,6 +540,27 @@ from t;
!ok
+# [CALCITE-6730] Add CONVERT function(enabled in Oracle library)
+select convert('abcd', utf8);
++--------+
+| EXPR$0 |
++--------+
+| abcd |
++--------+
+(1 row)
+
+!ok
+
+select convert('abcd', utf8, latin1);
++--------+
+| EXPR$0 |
++--------+
+| abcd |
++--------+
+(1 row)
+
+!ok
+
SELECT XMLTRANSFORM(
'<?xml version="1.0"?>
<Article>
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index e48d6afc6b..6f8afb154a 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2831,6 +2831,7 @@ In the following:
| m | COMPRESS(string) | Compresses a string
using zlib compression and returns the result as a binary string
| b | CONTAINS_SUBSTR(expression, string [ , json_scope => json_scope_value
]) | Returns whether *string* exists as a substring in *expression*. Optional
*json_scope* argument specifies what scope to search if *expression* is in JSON
format. Returns NULL if a NULL exists in *expression* that does not result in a
match
| q | CONVERT(type, expression [ , style ]) | Equivalent to
`CAST(expression AS type)`; ignores the *style* operand
+| o | CONVERT(string, destCharSet[, srcCharSet]) | Converts *string* from
*srcCharSet* to *destCharSet*. If the *srcCharSet* parameter is not specified,
then it uses the default CharSet
| p r | CONVERT_TIMEZONE(tz1, tz2, datetime) | Converts the timezone
of *datetime* from *tz1* to *tz2*
| p | COSD(numeric) | Returns the cosine of
*numeric* in degrees as a double. Returns NaN if *numeric* is NaN. Fails if
*numeric* is greater than the maximum double value.
| * | COSH(numeric) | Returns the hyperbolic
cosine of *numeric*
diff --git
a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 39faceb01b..705cfc3e7f 100644
--- a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -5741,6 +5741,19 @@ public class SqlParserTest {
+ "FROM (VALUES (ROW(TRUE))))");
}
+ @Test void testConvertOracle() {
+ // If there are 3 params in CONVERT_ORACLE operator, it's valid when
+ // the ORACLE function library is enabled ('fun=oracle').
+ // But the parser can always parse it.
+ expr("convert('abc', utf8, gbk)")
+ .ok("CONVERT('abc', `UTF8`, `GBK`)");
+ expr("convert('abc', utf8)")
+ .ok("CONVERT('abc', `UTF8`)");
+ sql("select convert(name, latin1) as newName from t")
+ .ok("SELECT CONVERT(`NAME`, `LATIN1`) AS `NEWNAME`\n"
+ + "FROM `T`");
+ }
+
@Test void testTranslate3() {
expr("translate('aaabbbccc', 'ab', '+-')")
.ok("TRANSLATE('aaabbbccc', 'ab', '+-')");
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index 9e10c67b67..733df20be2 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -4646,6 +4646,53 @@ public class SqlOperatorTest {
false);
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-6730">[CALCITE-6730]
+ * Add CONVERT function(enabled in Oracle library)</a>. */
+ @Test void testConvertOracleFunc() {
+ final SqlOperatorFixture f = fixture()
+ .setFor(SqlLibraryOperators.CONVERT_ORACLE, VM_JAVA)
+ .withLibrary(SqlLibrary.ORACLE);
+
+ final Consumer<SqlOperatorFixture> consumer = f0 -> {
+ f0.checkFails("convert('a', utf8, utf10)", "UTF10", false);
+ f0.checkFails("convert('a', utf8, ^null^)",
+ "(?s).*Encountered \\\"null\\\" at.*", false);
+ f0.checkFails("convert('a', ^null^, utf8)",
+ "(?s).*Encountered \\\"null\\\" at.*", false);
+ f0.checkFails("^convert(1, utf8, gbk)^",
+ "Invalid type 'INTEGER NOT NULL' in 'CONVERT' function\\. "
+ + "Only 'CHARACTER' type is supported",
+ false);
+ f0.checkType("convert('a', utf16, gbk)", "CHAR(1) NOT NULL");
+ f0.checkType("convert('a', utf16)", "CHAR(1) NOT NULL");
+ f0.checkType("convert(null, utf16, gbk)", "NULL");
+ f0.checkType("convert('', utf16, gbk)", "CHAR(0) NOT NULL");
+ f0.checkType("convert(cast(1 as varchar(2)), utf8, latin1)", "VARCHAR(2)
NOT NULL");
+
+ // cast check
+ f.check("select 'a' as alia\n"
+ + " from (values(true)) where cast(convert('col', latin1) as
char(3))='col'",
+ SqlTests.ANY_TYPE_CHECKER, 'a');
+ f.checkFails("select 'a' as alia\n"
+ + " from (values(true)) where ^cast(convert('col', latin1) as
char(3))=_GBK'col'^",
+ "Cannot apply operation '=' to strings with "
+ + "different charsets 'ISO-8859-1' and 'GBK'",
+ false);
+ // the result of convert('col', gbk) has GBK charset
+ // while CHAR(3) has ISO-8859-1 charset, which is not allowed to cast
+ f.checkFails("select 'a' as alia\n"
+ + " from (values(true)) where ^cast(convert('col', gbk) as
char(3))^=_GBK'col'",
+ "Cast function cannot convert value of type "
+ + "CHAR\\(3\\) CHARACTER SET \"GBK\" NOT NULL to type
CHAR\\(3\\) NOT NULL",
+ false);
+ };
+
+ final List<SqlConformanceEnum> conformances =
+ list(SqlConformanceEnum.ORACLE_10, SqlConformanceEnum.ORACLE_12);
+ f.forEachConformance(conformances, consumer);
+ }
+
@Test void testTranslateFunc() {
final SqlOperatorFixture f = fixture();
f.setFor(SqlStdOperatorTable.TRANSLATE, VM_JAVA);