Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CALCITE-5241] Implement CHAR function #2878

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/main/codegen/templates/Parser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -6949,6 +6949,7 @@ SqlIdentifier ReservedFunctionName() :
| <AVG>
| <CARDINALITY>
| <CEILING>
| <CHAR>
| <CHAR_LENGTH>
| <CHARACTER_LENGTH>
| <COALESCE>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_REVERSE;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.BOOL_AND;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.BOOL_OR;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.CHAR;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.CHR;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.COMPRESS;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONCAT2;
Expand Down Expand Up @@ -366,7 +367,7 @@ public class RexImpTable {
defineMethod(RIGHT, BuiltInMethod.RIGHT.method, NullPolicy.ANY);
defineMethod(REPLACE, BuiltInMethod.REPLACE.method, NullPolicy.STRICT);
defineMethod(TRANSLATE3, BuiltInMethod.TRANSLATE3.method, NullPolicy.STRICT);
defineMethod(CHR, "chr", NullPolicy.STRICT);
defineMethod(CHR, BuiltInMethod.CHR.method, NullPolicy.STRICT);
defineMethod(CHARACTER_LENGTH, BuiltInMethod.CHAR_LENGTH.method,
NullPolicy.STRICT);
defineMethod(CHAR_LENGTH, BuiltInMethod.CHAR_LENGTH.method,
Expand All @@ -380,6 +381,7 @@ public class RexImpTable {
defineMethod(OVERLAY, BuiltInMethod.OVERLAY.method, NullPolicy.STRICT);
defineMethod(POSITION, BuiltInMethod.POSITION.method, NullPolicy.STRICT);
defineMethod(ASCII, BuiltInMethod.ASCII.method, NullPolicy.STRICT);
defineMethod(CHAR, BuiltInMethod.CHAR.method, NullPolicy.STRICT);
defineMethod(REPEAT, BuiltInMethod.REPEAT.method, NullPolicy.STRICT);
defineMethod(SPACE, BuiltInMethod.SPACE.method, NullPolicy.STRICT);
defineMethod(STRCMP, BuiltInMethod.STRCMP.method, NullPolicy.STRICT);
Expand Down
17 changes: 16 additions & 1 deletion core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,22 @@ public static ByteString right(ByteString s, int n) {
return s.substring(len - n);
}

/** SQL CHR(long) function. */
/**
* SQL CHAR(long) function.
* Returns the ASCII character having the binary equivalent to long;
* If long is larger than 256 the result is equivalent to char(long % 256).
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explain the difference between this and chr. It isn't int vs long.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also make the javadoc consistent with other javadoc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it should be bigint.

public static @Nullable String charN(long n) {
if (n < 0) {
return null;
}
return String.valueOf(Character.toChars((int) (n % 256)));
}

/**
* SQL CHR(long) function.
* Returns the UTF-8 character having the binary equivalent to long.
*/
public static String chr(long n) {
return String.valueOf(Character.toChars((int) n));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,7 @@ private JdbcToInternalLookupTable() {
map.put("TRUNCATE", simple(SqlStdOperatorTable.TRUNCATE));

map.put("ASCII", simple(SqlStdOperatorTable.ASCII));
map.put("CHAR", simple(SqlLibraryOperators.CHAR));
map.put("CONCAT", simple(SqlStdOperatorTable.CONCAT));
map.put("DIFFERENCE", simple(SqlLibraryOperators.DIFFERENCE));
map.put("INSERT",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,22 @@ private SqlLibraryOperators() {
ReturnTypes.BIGINT_NULLABLE, null, OperandTypes.TIMESTAMP,
SqlFunctionCategory.TIMEDATE);

@LibraryOperator(libraries = {ORACLE})
/**
* The "CHAR(bigint)" function; returns the ASCII character having the binary equivalent
* to bigint; If bigint is larger than 256 the result is equivalent to char(bigint % 256). */
@LibraryOperator(libraries = {MYSQL, SPARK})
public static final SqlFunction CHAR =
new SqlFunction("CHAR",
SqlKind.OTHER_FUNCTION,
ReturnTypes.CHAR_FORCE_NULLABLE,
null,
OperandTypes.INTEGER,
SqlFunctionCategory.STRING);

/**
* The "CHR(bigint)" function; returns the UTF-8 character
* having the binary equivalent to bigint. */
@LibraryOperator(libraries = {ORACLE, POSTGRESQL})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CHR and CHAR need javadoc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added javadoc.

public static final SqlFunction CHR =
new SqlFunction("CHR",
SqlKind.OTHER_FUNCTION,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,13 @@ public static SqlCall stripSeparator(SqlCall call) {
public static final SqlReturnTypeInference CHAR =
explicit(SqlTypeName.CHAR);

/**
* Type-inference strategy whereby the result type of a call is a nullable
* CHAR(1).
*/
public static final SqlReturnTypeInference CHAR_FORCE_NULLABLE =
CHAR.andThen(SqlTypeTransforms.FORCE_NULLABLE);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not 'nullable Char', but 'nullable CHAR(1)'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to it.

/**
* Type-inference strategy whereby the result type of a call is an Integer.
*/
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ public enum BuiltInMethod {
UPPER(SqlFunctions.class, "upper", String.class),
LOWER(SqlFunctions.class, "lower", String.class),
ASCII(SqlFunctions.class, "ascii", String.class),
CHAR(SqlFunctions.class, "charN", long.class),
CHR(SqlFunctions.class, "chr", long.class),
REPEAT(SqlFunctions.class, "repeat", String.class, int.class),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be CHAR(SqlFunctions.class, "charN", long.class)?

and don't you also need CHR(SqlFunctions.class, "chr", long.class)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your careful review, I changed to them.

SPACE(SqlFunctions.class, "space", int.class),
SOUNDEX(SqlFunctions.class, "soundex", String.class),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class SqlAdvisorTest extends SqlValidatorTestCase {
"KEYWORD(CAST)",
"KEYWORD(CEIL)",
"KEYWORD(CEILING)",
"KEYWORD(CHAR)",
"KEYWORD(CHARACTER_LENGTH)",
"KEYWORD(CHAR_LENGTH)",
"KEYWORD(CLASSIFIER)",
Expand Down
10 changes: 10 additions & 0 deletions core/src/test/resources/sql/functions.iq
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ SELECT ExtractValue('<a>c</a>', '//a');
!ok

# STRING Functions
#CHAR
SELECT char(null), char(-1), char(65), char(233), char(256+66);
+--------+--------+--------+--------+--------+
| EXPR$0 | EXPR$1 | EXPR$2 | EXPR$3 | EXPR$4 |
+--------+--------+--------+--------+--------+
| | | A | é | B |
+--------+--------+--------+--------+--------+
(1 row)

!ok

#CONCAT
SELECT CONCAT('c', 'h', 'a', 'r');
Expand Down
8 changes: 3 additions & 5 deletions site/_docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1749,6 +1749,7 @@ period:
| Operator syntax | Description
|:--------------- |:-----------
| {fn ASCII(string)} | Returns the ASCII code of the first character of *string*; if the first character is a non-ASCII character, returns its Unicode code point; returns 0 if *string* is empty
| {fn CHAR(integer)} | Returns the ASCII character having the binary equivalent to *integer*; If *integer* is larger than 256 the result is equivalent to char(*integer* % 256)
| {fn CONCAT(character, character)} | Returns the concatenation of character strings
| {fn INSERT(string1, start, length, string2)} | Inserts *string2* into a slot in *string1*
| {fn LCASE(string)} | Returns a string in which all alphabetic characters in *string* have been converted to lower case
Expand All @@ -1763,10 +1764,6 @@ period:
| {fn SUBSTRING(string, offset, length)} | Returns a character string that consists of *length* characters from *string* starting at the *offset* position
| {fn UCASE(string)} | Returns a string in which all alphabetic characters in *string* have been converted to upper case

Not implemented:

* {fn CHAR(string)}

#### Date/time

| Operator syntax | Description
Expand Down Expand Up @@ -2564,7 +2561,8 @@ semantics.
| b | ARRAY_CONCAT(array [, array ]*) | Concatenates one or more arrays. If any input argument is `NULL` the function returns `NULL`
| b | ARRAY_LENGTH(array) | Synonym for `CARDINALITY`
| b | ARRAY_REVERSE(array) | Reverses elements of *array*
| o | CHR(integer) | Returns the character having the binary equivalent to *integer* as a CHAR value
| m s | CHAR(integer) | Returns the ASCII character having the binary equivalent to *integer*; If *integer* is larger than 256 the result is equivalent to char(*integer* % 256)
| o p | CHR(integer) | Returns the UTF-8 character having the binary equivalent to *integer* as a CHAR value
| o | COSH(numeric) | Returns the hyperbolic cosine of *numeric*
| o | CONCAT(string, string) | Concatenates two strings
| m p | CONCAT(string [, string ]*) | Concatenates two or more strings
Expand Down
22 changes: 19 additions & 3 deletions testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1492,9 +1492,8 @@ protected static Calendar getCalendarNotTooNear(int timeUnit) {
f.checkScalar("{fn ASCII('ABC')}", "65", "INTEGER NOT NULL");
f.checkNull("{fn ASCII(cast(null as varchar(1)))}");

if (false) {
f.checkScalar("{fn CHAR(code)}", null, "");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove this test? The goal of the change is to implement JDBC '{fn CHAR}'

Copy link
Contributor Author

@JiajunBernoulli JiajunBernoulli Aug 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, my mistake.

f.checkScalar("{fn CHAR(97)}", "a", "CHAR(1)");

f.checkScalar("{fn CONCAT('foo', 'bar')}", "foobar", "CHAR(6) NOT NULL");

f.checkScalar("{fn DIFFERENCE('Miller', 'miller')}", "4",
Expand Down Expand Up @@ -1630,6 +1629,23 @@ protected static Calendar getCalendarNotTooNear(int timeUnit) {

}

@Test void testChar() {
final SqlOperatorFixture f0 = fixture()
.setFor(SqlLibraryOperators.CHR, VM_FENNEL, VM_JAVA);
f0.checkFails("^char(97)^",
"No match found for function signature CHAR\\(<NUMERIC>\\)", false);
final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.MYSQL);
f.checkScalar("char(null)", isNullValue(), "CHAR(1)");
f.checkScalar("char(-1)", isNullValue(), "CHAR(1)");
f.checkScalar("char(97)", "a", "CHAR(1)");
f.checkScalar("char(48)", "0", "CHAR(1)");
f.checkScalar("char(0)", String.valueOf('\u0000'), "CHAR(1)");
f.checkFails("^char(97.1)^",
"Cannot apply 'CHAR' to arguments of type 'CHAR\\(<DECIMAL\\(3, 1\\)>\\)'\\. "
+ "Supported form\\(s\\): 'CHAR\\(<INTEGER>\\)'",
false);
}

@Test void testChr() {
final SqlOperatorFixture f0 = fixture()
.setFor(SqlLibraryOperators.CHR, VM_FENNEL, VM_JAVA);
Expand Down