This is an automated email from the ASF dual-hosted git repository.
morrysnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new e9a016487ba [test](analysis) add comprehensive unit tests for
Expr.toSql() (#60849)
e9a016487ba is described below
commit e9a016487ba2b5bbb6d584ad3ff0da1c483c0de4
Author: morrySnow <[email protected]>
AuthorDate: Fri Feb 27 11:49:16 2026 +0800
[test](analysis) add comprehensive unit tests for Expr.toSql() (#60849)
Add ExprToSqlTest.java with ~100 test methods covering toSql() output
for all 39 concrete Expr subclasses, including:
- All literal types (Bool, String, Int, Float/FLOAT/DOUBLE, Decimal,
LargeInt, Null, Max, Json, IPv4, IPv6, VarBinary, Date/DateV2/DatetimeV2
with scales, Array, Map, Struct, PlaceHolder)
- TimeV2Literal with all scales (0-6) and negative/large-hour variants
- DateLiteral with Date, DateTime, DatetimeV2 types and fractional
scales
- Reference exprs (SlotRef, ColumnRefExpr, InformationFunction,
EncryptKeyRef)
- All predicate types (Binary, IsNull, Compound AND/OR/NOT, In, Like,
Match, Between)
- ArithmeticExpr for all operators (+, -, *, /, %, DIV, &, |, ^, ~)
including nested expressions
- CastExpr and TryCastExpr (default and external SQL)
- CaseExpr with single/multiple WHEN clauses, with and without ELSE
- FunctionCallExpr (no-arg, multi-arg, DISTINCT, star, nested)
- TimestampArithmeticExpr for all time units,
date_add/sub/TIMESTAMPDIFF/ TIMESTAMPADD variants
- LambdaFunctionExpr (1-arg and 2-arg)
These tests establish a correctness baseline prior to visitor-pattern
refactoring of the toSql() API.
---
.../org/apache/doris/analysis/ExprToSqlTest.java | 1019 ++++++++++++++++++++
1 file changed, 1019 insertions(+)
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/analysis/ExprToSqlTest.java
b/fe/fe-core/src/test/java/org/apache/doris/analysis/ExprToSqlTest.java
new file mode 100644
index 00000000000..6c38ff38978
--- /dev/null
+++ b/fe/fe-core/src/test/java/org/apache/doris/analysis/ExprToSqlTest.java
@@ -0,0 +1,1019 @@
+// 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.doris.analysis;
+
+import org.apache.doris.catalog.ArrayType;
+import org.apache.doris.catalog.Function.NullableMode;
+import org.apache.doris.catalog.MapType;
+import org.apache.doris.catalog.ScalarType;
+import org.apache.doris.catalog.StructField;
+import org.apache.doris.catalog.StructType;
+import org.apache.doris.catalog.Type;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.info.TableNameInfo;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Unit tests verifying toSql() output for all 39 concrete Expr subclasses.
+ * These tests validate current behavior prior to visitor-pattern refactoring.
+ */
+public class ExprToSqlTest {
+
+ // -----------------------------------------------------------------------
+ // Literals
+ // -----------------------------------------------------------------------
+
+ @Test
+ public void testBoolLiteralTrue() {
+ BoolLiteral expr = new BoolLiteral(true);
+ Assertions.assertEquals("TRUE", expr.toSql());
+ }
+
+ @Test
+ public void testBoolLiteralFalse() {
+ BoolLiteral expr = new BoolLiteral(false);
+ Assertions.assertEquals("FALSE", expr.toSql());
+ }
+
+ @Test
+ public void testStringLiteralSimple() {
+ StringLiteral expr = new StringLiteral("hello");
+ Assertions.assertEquals("'hello'", expr.toSql());
+ }
+
+ @Test
+ public void testStringLiteralWithSingleQuote() {
+ // Single quotes inside the value should be escaped as ''
+ StringLiteral expr = new StringLiteral("it's");
+ Assertions.assertEquals("'it''s'", expr.toSql());
+ }
+
+ @Test
+ public void testIntLiteral() {
+ IntLiteral expr = new IntLiteral(42L);
+ Assertions.assertEquals("42", expr.toSql());
+ }
+
+ @Test
+ public void testFloatLiteralDouble() {
+ FloatLiteral expr = new FloatLiteral(3.14, Type.DOUBLE);
+ Assertions.assertEquals("3.14", expr.toSql());
+ }
+
+ @Test
+ public void testFloatLiteralDoubleWholeNumber() {
+ // Trailing zeros are stripped: 1.0 → "1"
+ FloatLiteral expr = new FloatLiteral(1.0, Type.DOUBLE);
+ Assertions.assertEquals("1", expr.toSql());
+ }
+
+ @Test
+ public void testFloatLiteralFloat() {
+ // FLOAT type uses maxFractionDigits=7; 1.5f fits exactly
+ FloatLiteral expr = new FloatLiteral(1.5, Type.FLOAT);
+ Assertions.assertEquals("1.5", expr.toSql());
+ }
+
+ @Test
+ public void testFloatLiteralFloatPrecisionArtifact() {
+ // 3.14 cannot be represented exactly as a 32-bit float, exposing a
precision artifact
+ FloatLiteral expr = new FloatLiteral((double) 3.14f, Type.FLOAT);
+ Assertions.assertTrue(expr.toSql().startsWith("3.1400"));
+ }
+
+ @Test
+ public void testDecimalLiteral() {
+ DecimalLiteral expr = new DecimalLiteral(new
java.math.BigDecimal("1.5"));
+ Assertions.assertEquals("1.5", expr.toSql());
+ }
+
+ @Test
+ public void testLargeIntLiteral() {
+ LargeIntLiteral expr = new
LargeIntLiteral(java.math.BigInteger.valueOf(12345678901234L));
+ Assertions.assertEquals("12345678901234", expr.toSql());
+ }
+
+ @Test
+ public void testNullLiteral() {
+ NullLiteral expr = new NullLiteral();
+ Assertions.assertEquals("NULL", expr.toSql());
+ }
+
+ @Test
+ public void testMaxLiteral() {
+ MaxLiteral expr = MaxLiteral.MAX_VALUE;
+ Assertions.assertEquals("MAXVALUE", expr.toSql());
+ }
+
+ @Test
+ public void testJsonLiteral() throws AnalysisException {
+ JsonLiteral expr = new JsonLiteral("{\"k\":1}");
+ Assertions.assertEquals("'{\"k\":1}'", expr.toSql());
+ }
+
+ @Test
+ public void testIPv4Literal() throws AnalysisException {
+ IPv4Literal expr = new IPv4Literal("192.168.1.1");
+ Assertions.assertEquals("\"192.168.1.1\"", expr.toSql());
+ }
+
+ @Test
+ public void testIPv6Literal() throws AnalysisException {
+ IPv6Literal expr = new IPv6Literal("::1");
+ // IPv6Literal.toSqlImpl() returns '"' + value + '"'
+ Assertions.assertEquals("\"::1\"", expr.toSql());
+ }
+
+ @Test
+ public void testTimeV2LiteralPositive() {
+ // 01:02:03.000000, scale 6, not negative
+ TimeV2Literal expr = new TimeV2Literal(1, 2, 3, 0, 6, false);
+ Assertions.assertEquals("\"01:02:03.000000\"", expr.toSql());
+ }
+
+ @Test
+ public void testTimeV2LiteralNegative() {
+ TimeV2Literal expr = new TimeV2Literal(10, 20, 30, 0, 0, true);
+ Assertions.assertEquals("\"-10:20:30\"", expr.toSql());
+ }
+
+ @Test
+ public void testVarBinaryLiteral() throws AnalysisException {
+ byte[] bytes = new byte[]{0x68, 0x65, 0x6c, 0x6c, 0x6f};
+ VarBinaryLiteral expr = new VarBinaryLiteral(bytes);
+ Assertions.assertEquals("X'68656C6C6F'", expr.toSql());
+ }
+
+ @Test
+ public void testDateLiteral() throws AnalysisException {
+ DateLiteral expr = new DateLiteral("2024-01-15", Type.DATEV2);
+ Assertions.assertEquals("'2024-01-15'", expr.toSql());
+ }
+
+ @Test
+ public void testArrayLiteral() {
+ ArrayType arrayType = new ArrayType(Type.INT);
+ ArrayLiteral expr = new ArrayLiteral(arrayType,
+ new IntLiteral(1L), new IntLiteral(2L), new IntLiteral(3L));
+ Assertions.assertEquals("[1, 2, 3]", expr.toSql());
+ }
+
+ @Test
+ public void testArrayLiteralEmpty() {
+ ArrayLiteral expr = new ArrayLiteral();
+ Assertions.assertEquals("[]", expr.toSql());
+ }
+
+ @Test
+ public void testMapLiteral() {
+ MapType mapType = new MapType(Type.VARCHAR, Type.INT);
+ List<LiteralExpr> keys = Arrays.asList(new StringLiteral("a"), new
StringLiteral("b"));
+ List<LiteralExpr> values = Arrays.asList(new IntLiteral(1L), new
IntLiteral(2L));
+ MapLiteral expr = new MapLiteral(mapType, keys, values);
+ Assertions.assertEquals("MAP{'a':1, 'b':2}", expr.toSql());
+ }
+
+ @Test
+ public void testStructLiteral() throws AnalysisException {
+ StructType structType = new StructType(
+ new StructField("f1", Type.INT),
+ new StructField("f2", Type.VARCHAR));
+ StructLiteral expr = new StructLiteral(structType, new
IntLiteral(10L), new StringLiteral("x"));
+ Assertions.assertEquals("STRUCT(10, 'x')", expr.toSql());
+ }
+
+ @Test
+ public void testPlaceHolderExprNull() {
+ // null lExpr case → "?"
+ PlaceHolderExpr expr = new PlaceHolderExpr();
+ Assertions.assertEquals("?", expr.toSql());
+ }
+
+ @Test
+ public void testPlaceHolderExprWithLiteral() throws AnalysisException {
+ PlaceHolderExpr expr = PlaceHolderExpr.create("42", Type.INT);
+ Assertions.assertEquals("_placeholder_(42)", expr.toSql());
+ }
+
+ // -----------------------------------------------------------------------
+ // References / identifiers
+ // -----------------------------------------------------------------------
+
+ @Test
+ public void testColumnRefExpr() {
+ ColumnRefExpr expr = new ColumnRefExpr(false);
+ expr.setName("my_col");
+ Assertions.assertEquals("my_col", expr.toSql());
+ }
+
+ @Test
+ public void testInformationFunction() {
+ InformationFunction expr = new InformationFunction("DATABASE");
+ Assertions.assertEquals("DATABASE()", expr.toSql());
+ }
+
+ @Test
+ public void testEncryptKeyRefNoDb() {
+ // parts = ["mykey"] → db=null, keyName="mykey"
+ EncryptKeyName keyName = new
EncryptKeyName(Collections.singletonList("mykey"));
+ EncryptKeyRef expr = new EncryptKeyRef(keyName);
+ Assertions.assertEquals("KEY mykey", expr.toSql());
+ }
+
+ @Test
+ public void testEncryptKeyRefWithDb() {
+ // parts = ["mydb", "mykey"]
+ EncryptKeyName keyName = new EncryptKeyName(Arrays.asList("mydb",
"mykey"));
+ EncryptKeyRef expr = new EncryptKeyRef(keyName);
+ Assertions.assertEquals("KEY mydb.mykey", expr.toSql());
+ }
+
+ @Test
+ public void testSlotRefNoTable() {
+ // No table, no desc → falls back to label
+ SlotRef expr = new SlotRef(null, "col1");
+ Assertions.assertEquals("`col1`", expr.toSql());
+ }
+
+ @Test
+ public void testSlotRefWithTable() {
+ SlotRef expr = new SlotRef(new TableNameInfo("mytbl"), "col1");
+ // TableNameInfo("mytbl").toSql() = "`mytbl`"
+ Assertions.assertEquals("`mytbl`.`col1`", expr.toSql());
+ }
+
+ // -----------------------------------------------------------------------
+ // Predicates
+ // -----------------------------------------------------------------------
+
+ @Test
+ public void testBinaryPredicateEq() {
+ Expr e1 = new SlotRef(null, "a");
+ Expr e2 = new IntLiteral(1L);
+ BinaryPredicate expr = new
BinaryPredicate(BinaryPredicate.Operator.EQ, e1, e2);
+ Assertions.assertEquals("(`a` = 1)", expr.toSql());
+ }
+
+ @Test
+ public void testBinaryPredicateNe() {
+ Expr e1 = new SlotRef(null, "x");
+ Expr e2 = new IntLiteral(0L);
+ BinaryPredicate expr = new
BinaryPredicate(BinaryPredicate.Operator.NE, e1, e2);
+ Assertions.assertEquals("(`x` != 0)", expr.toSql());
+ }
+
+ @Test
+ public void testIsNullPredicate() {
+ Expr e1 = new SlotRef(null, "col");
+ IsNullPredicate expr = new IsNullPredicate(e1, false);
+ Assertions.assertEquals("`col` IS NULL", expr.toSql());
+ }
+
+ @Test
+ public void testIsNotNullPredicate() {
+ Expr e1 = new SlotRef(null, "col");
+ IsNullPredicate expr = new IsNullPredicate(e1, true);
+ Assertions.assertEquals("`col` IS NOT NULL", expr.toSql());
+ }
+
+ @Test
+ public void testCompoundPredicateAnd() {
+ Expr e1 = new BoolLiteral(true);
+ Expr e2 = new BoolLiteral(false);
+ CompoundPredicate expr = new
CompoundPredicate(CompoundPredicate.Operator.AND, e1, e2);
+ Assertions.assertEquals("(TRUE AND FALSE)", expr.toSql());
+ }
+
+ @Test
+ public void testCompoundPredicateOr() {
+ Expr e1 = new BoolLiteral(true);
+ Expr e2 = new BoolLiteral(false);
+ CompoundPredicate expr = new
CompoundPredicate(CompoundPredicate.Operator.OR, e1, e2);
+ Assertions.assertEquals("(TRUE OR FALSE)", expr.toSql());
+ }
+
+ @Test
+ public void testCompoundPredicateNotDefault() {
+ // NOT with default SQL: "NOT child"
+ Expr e1 = new BoolLiteral(true);
+ CompoundPredicate expr = new
CompoundPredicate(CompoundPredicate.Operator.NOT, e1, null);
+ Assertions.assertEquals("NOT TRUE", expr.toSql());
+ }
+
+ @Test
+ public void testCompoundPredicateNotExternal() {
+ // NOT with external SQL: "(NOT child)"
+ Expr e1 = new BoolLiteral(true);
+ CompoundPredicate expr = new
CompoundPredicate(CompoundPredicate.Operator.NOT, e1, null);
+ String sql = expr.toSql(true, true, null, null);
+ Assertions.assertEquals("(NOT TRUE)", sql);
+ }
+
+ @Test
+ public void testInPredicate() {
+ Expr e1 = new SlotRef(null, "col");
+ List<Expr> inList = Arrays.asList(new IntLiteral(1L), new
IntLiteral(2L));
+ InPredicate expr = new InPredicate(e1, inList, false);
+ Assertions.assertEquals("`col` IN (1, 2)", expr.toSql());
+ }
+
+ @Test
+ public void testNotInPredicate() {
+ Expr e1 = new SlotRef(null, "col");
+ List<Expr> inList = Arrays.asList(new IntLiteral(1L), new
IntLiteral(2L));
+ InPredicate expr = new InPredicate(e1, inList, true);
+ Assertions.assertEquals("`col` NOT IN (1, 2)", expr.toSql());
+ }
+
+ @Test
+ public void testLikePredicate() {
+ Expr e1 = new SlotRef(null, "name");
+ Expr e2 = new StringLiteral("foo%");
+ LikePredicate expr = new LikePredicate(LikePredicate.Operator.LIKE,
e1, e2);
+ Assertions.assertEquals("`name` LIKE 'foo%'", expr.toSql());
+ }
+
+ @Test
+ public void testRegexpPredicate() {
+ Expr e1 = new SlotRef(null, "name");
+ Expr e2 = new StringLiteral("^foo");
+ LikePredicate expr = new LikePredicate(LikePredicate.Operator.REGEXP,
e1, e2);
+ Assertions.assertEquals("`name` REGEXP '^foo'", expr.toSql());
+ }
+
+ @Test
+ public void testMatchPredicate() {
+ Expr e1 = new SlotRef(null, "content");
+ Expr e2 = new StringLiteral("word");
+ MatchPredicate expr = new MatchPredicate(
+ MatchPredicate.Operator.MATCH_ANY, e1, e2, Type.BOOLEAN,
+ NullableMode.ALWAYS_NULLABLE, null, false);
+ Assertions.assertEquals("`content` MATCH_ANY 'word'", expr.toSql());
+ }
+
+ @Test
+ public void testBetweenPredicate() throws Exception {
+ // BetweenPredicate has no public constructor; use reflection.
+ Expr col = new SlotRef(null, "age");
+ Expr low = new IntLiteral(18L);
+ Expr high = new IntLiteral(65L);
+
+ java.lang.reflect.Constructor<BetweenPredicate> ctor =
+ BetweenPredicate.class.getDeclaredConstructor();
+ ctor.setAccessible(true);
+ BetweenPredicate expr = ctor.newInstance();
+
+ java.lang.reflect.Field inbField =
BetweenPredicate.class.getDeclaredField("isNotBetween");
+ inbField.setAccessible(true);
+ inbField.set(expr, false);
+
+ expr.addChild(col);
+ expr.addChild(low);
+ expr.addChild(high);
+
+ Assertions.assertEquals("`age` BETWEEN 18 AND 65", expr.toSql());
+ }
+
+ @Test
+ public void testNotBetweenPredicate() throws Exception {
+ Expr col = new SlotRef(null, "age");
+ Expr low = new IntLiteral(18L);
+ Expr high = new IntLiteral(65L);
+
+ java.lang.reflect.Constructor<BetweenPredicate> ctor =
+ BetweenPredicate.class.getDeclaredConstructor();
+ ctor.setAccessible(true);
+ BetweenPredicate expr = ctor.newInstance();
+
+ java.lang.reflect.Field inbField =
BetweenPredicate.class.getDeclaredField("isNotBetween");
+ inbField.setAccessible(true);
+ inbField.set(expr, true);
+
+ expr.addChild(col);
+ expr.addChild(low);
+ expr.addChild(high);
+
+ Assertions.assertEquals("`age` NOT BETWEEN 18 AND 65", expr.toSql());
+ }
+
+ // -----------------------------------------------------------------------
+ // Arithmetic / Cast
+ // -----------------------------------------------------------------------
+
+ @Test
+ public void testArithmeticExprAdd() {
+ Expr e1 = new IntLiteral(1L);
+ Expr e2 = new IntLiteral(2L);
+ ArithmeticExpr expr = new ArithmeticExpr(
+ ArithmeticExpr.Operator.ADD, e1, e2, Type.BIGINT,
+ NullableMode.DEPEND_ON_ARGUMENT, false);
+ Assertions.assertEquals("(1 + 2)", expr.toSql());
+ }
+
+ @Test
+ public void testArithmeticExprBitNot() {
+ // unary operator (BITNOT): "~ child"
+ Expr e1 = new IntLiteral(5L);
+ ArithmeticExpr expr = new ArithmeticExpr(
+ ArithmeticExpr.Operator.BITNOT, e1, null, Type.BIGINT,
+ NullableMode.DEPEND_ON_ARGUMENT, false);
+ Assertions.assertEquals("~ 5", expr.toSql());
+ }
+
+ @Test
+ public void testCastExprDefault() {
+ CastExpr expr = new CastExpr(Type.BIGINT, new IntLiteral(1L), false);
+ Assertions.assertEquals("CAST(1 AS bigint)", expr.toSql());
+ }
+
+ @Test
+ public void testCastExprExternal() {
+ // External SQL strips the cast and returns just the child
+ CastExpr expr = new CastExpr(Type.BIGINT, new IntLiteral(1L), false);
+ String sql = expr.toSql(false, true, null, null);
+ Assertions.assertEquals("1", sql);
+ }
+
+ @Test
+ public void testTryCastExprDefault() {
+ TryCastExpr expr = new TryCastExpr(Type.INT, new IntLiteral(1L),
false, false);
+ Assertions.assertEquals("TRY_CAST(1 AS int)", expr.toSql());
+ }
+
+ @Test
+ public void testTryCastExprExternal() {
+ TryCastExpr expr = new TryCastExpr(Type.INT, new IntLiteral(1L),
false, false);
+ String sql = expr.toSql(false, true, null, null);
+ Assertions.assertEquals("1", sql);
+ }
+
+ // -----------------------------------------------------------------------
+ // CASE / Variable / Functions
+ // -----------------------------------------------------------------------
+
+ @Test
+ public void testCaseExprSimple() {
+ // CASE WHEN TRUE THEN 1 END
+ Expr whenExpr = new BoolLiteral(true);
+ Expr thenExpr = new IntLiteral(1L);
+ CaseWhenClause clause = new CaseWhenClause(whenExpr, thenExpr);
+ CaseExpr expr = new CaseExpr(Collections.singletonList(clause), null,
false);
+ Assertions.assertEquals("CASE WHEN TRUE THEN 1 END", expr.toSql());
+ }
+
+ @Test
+ public void testCaseExprWithElse() {
+ // CASE WHEN TRUE THEN 1 ELSE 0 END
+ Expr whenExpr = new BoolLiteral(true);
+ Expr thenExpr = new IntLiteral(1L);
+ Expr elseExpr = new IntLiteral(0L);
+ CaseWhenClause clause = new CaseWhenClause(whenExpr, thenExpr);
+ CaseExpr expr = new CaseExpr(Collections.singletonList(clause),
elseExpr, false);
+ Assertions.assertEquals("CASE WHEN TRUE THEN 1 ELSE 0 END",
expr.toSql());
+ }
+
+ @Test
+ public void testVariableExprUser() {
+ VariableExpr expr = new VariableExpr("myvar", SetType.USER);
+ Assertions.assertEquals("@myvar", expr.toSql());
+ }
+
+ @Test
+ public void testVariableExprSession() {
+ VariableExpr expr = new VariableExpr("version", SetType.SESSION);
+ Assertions.assertEquals("@@version", expr.toSql());
+ }
+
+ @Test
+ public void testVariableExprGlobal() {
+ VariableExpr expr = new VariableExpr("timeout", SetType.GLOBAL);
+ Assertions.assertEquals("@@GLOBAL.timeout", expr.toSql());
+ }
+
+ @Test
+ public void testFunctionCallExpr() {
+ FunctionCallExpr expr = new FunctionCallExpr("upper",
+ Arrays.asList(new StringLiteral("hello")), false);
+ Assertions.assertEquals("upper('hello')", expr.toSql());
+ }
+
+ @Test
+ public void testFunctionCallExprNoArgs() {
+ FunctionCallExpr expr = new FunctionCallExpr("now",
+ Collections.emptyList(), false);
+ Assertions.assertEquals("now()", expr.toSql());
+ }
+
+ @Test
+ public void testTimestampArithmeticExprFuncStyle() {
+ // date_add(col, INTERVAL 1 YEAR)
+ Expr e1 = new SlotRef(null, "dt");
+ Expr e2 = new IntLiteral(1L);
+ TimestampArithmeticExpr expr = new TimestampArithmeticExpr(
+ "date_add", ArithmeticExpr.Operator.ADD,
+ e1, e2, "YEAR", Type.DATETIMEV2, false);
+ Assertions.assertEquals("date_add(`dt`, INTERVAL 1 YEAR)",
expr.toSql());
+ }
+
+ @Test
+ public void testTimestampArithmeticExprTimestampdiff() {
+ // TIMESTAMPDIFF(YEAR, e2, e1)
+ Expr e1 = new SlotRef(null, "end_dt");
+ Expr e2 = new SlotRef(null, "start_dt");
+ TimestampArithmeticExpr expr = new TimestampArithmeticExpr(
+ "TIMESTAMPDIFF", ArithmeticExpr.Operator.ADD,
+ e1, e2, "YEAR", Type.DATETIMEV2, false);
+ Assertions.assertEquals("TIMESTAMPDIFF(YEAR, `start_dt`, `end_dt`)",
expr.toSql());
+ }
+
+ @Test
+ public void testLambdaFunctionExprOneArg() {
+ // x -> x + 1
+ Expr slot = new SlotRef(null, "x");
+ Expr body = new ArithmeticExpr(
+ ArithmeticExpr.Operator.ADD, slot, new IntLiteral(1L),
+ Type.BIGINT, NullableMode.DEPEND_ON_ARGUMENT, false);
+ LambdaFunctionExpr expr = new LambdaFunctionExpr(
+ body, Collections.singletonList("x"),
+ Collections.singletonList(slot), false);
+ Assertions.assertEquals("x -> (`x` + 1)", expr.toSql());
+ }
+
+ @Test
+ public void testLambdaFunctionExprTwoArgs() {
+ // (x,y) -> x + y
+ Expr slotX = new SlotRef(null, "x");
+ Expr slotY = new SlotRef(null, "y");
+ Expr body = new ArithmeticExpr(
+ ArithmeticExpr.Operator.ADD, slotX, slotY,
+ Type.BIGINT, NullableMode.DEPEND_ON_ARGUMENT, false);
+ LambdaFunctionExpr expr = new LambdaFunctionExpr(
+ body, Arrays.asList("x", "y"),
+ Arrays.asList(slotX, slotY), false);
+ Assertions.assertEquals("(x,y) -> (`x` + `y`)", expr.toSql());
+ }
+
+ // -----------------------------------------------------------------------
+ // ArithmeticExpr – additional operators
+ // -----------------------------------------------------------------------
+
+ @Test
+ public void testArithmeticExprSubtract() {
+ Expr e1 = new IntLiteral(10L);
+ Expr e2 = new IntLiteral(3L);
+ ArithmeticExpr expr = new ArithmeticExpr(
+ ArithmeticExpr.Operator.SUBTRACT, e1, e2, Type.BIGINT,
+ NullableMode.DEPEND_ON_ARGUMENT, false);
+ Assertions.assertEquals("(10 - 3)", expr.toSql());
+ }
+
+ @Test
+ public void testArithmeticExprMultiply() {
+ Expr e1 = new IntLiteral(4L);
+ Expr e2 = new IntLiteral(5L);
+ ArithmeticExpr expr = new ArithmeticExpr(
+ ArithmeticExpr.Operator.MULTIPLY, e1, e2, Type.BIGINT,
+ NullableMode.DEPEND_ON_ARGUMENT, false);
+ Assertions.assertEquals("(4 * 5)", expr.toSql());
+ }
+
+ @Test
+ public void testArithmeticExprDivide() {
+ Expr e1 = new IntLiteral(10L);
+ Expr e2 = new IntLiteral(2L);
+ ArithmeticExpr expr = new ArithmeticExpr(
+ ArithmeticExpr.Operator.DIVIDE, e1, e2, Type.DOUBLE,
+ NullableMode.DEPEND_ON_ARGUMENT, false);
+ Assertions.assertEquals("(10 / 2)", expr.toSql());
+ }
+
+ @Test
+ public void testArithmeticExprMod() {
+ Expr e1 = new IntLiteral(7L);
+ Expr e2 = new IntLiteral(3L);
+ ArithmeticExpr expr = new ArithmeticExpr(
+ ArithmeticExpr.Operator.MOD, e1, e2, Type.BIGINT,
+ NullableMode.DEPEND_ON_ARGUMENT, false);
+ Assertions.assertEquals("(7 % 3)", expr.toSql());
+ }
+
+ @Test
+ public void testArithmeticExprIntDivide() {
+ Expr e1 = new IntLiteral(7L);
+ Expr e2 = new IntLiteral(3L);
+ ArithmeticExpr expr = new ArithmeticExpr(
+ ArithmeticExpr.Operator.INT_DIVIDE, e1, e2, Type.BIGINT,
+ NullableMode.DEPEND_ON_ARGUMENT, false);
+ Assertions.assertEquals("(7 DIV 3)", expr.toSql());
+ }
+
+ @Test
+ public void testArithmeticExprBitAnd() {
+ Expr e1 = new IntLiteral(12L);
+ Expr e2 = new IntLiteral(10L);
+ ArithmeticExpr expr = new ArithmeticExpr(
+ ArithmeticExpr.Operator.BITAND, e1, e2, Type.BIGINT,
+ NullableMode.DEPEND_ON_ARGUMENT, false);
+ Assertions.assertEquals("(12 & 10)", expr.toSql());
+ }
+
+ @Test
+ public void testArithmeticExprBitOr() {
+ Expr e1 = new IntLiteral(12L);
+ Expr e2 = new IntLiteral(10L);
+ ArithmeticExpr expr = new ArithmeticExpr(
+ ArithmeticExpr.Operator.BITOR, e1, e2, Type.BIGINT,
+ NullableMode.DEPEND_ON_ARGUMENT, false);
+ Assertions.assertEquals("(12 | 10)", expr.toSql());
+ }
+
+ @Test
+ public void testArithmeticExprBitXor() {
+ Expr e1 = new IntLiteral(12L);
+ Expr e2 = new IntLiteral(10L);
+ ArithmeticExpr expr = new ArithmeticExpr(
+ ArithmeticExpr.Operator.BITXOR, e1, e2, Type.BIGINT,
+ NullableMode.DEPEND_ON_ARGUMENT, false);
+ Assertions.assertEquals("(12 ^ 10)", expr.toSql());
+ }
+
+ @Test
+ public void testArithmeticExprNestedBinary() {
+ // (1 + 2) * 3
+ Expr add = new ArithmeticExpr(
+ ArithmeticExpr.Operator.ADD, new IntLiteral(1L), new
IntLiteral(2L),
+ Type.BIGINT, NullableMode.DEPEND_ON_ARGUMENT, false);
+ ArithmeticExpr expr = new ArithmeticExpr(
+ ArithmeticExpr.Operator.MULTIPLY, add, new IntLiteral(3L),
+ Type.BIGINT, NullableMode.DEPEND_ON_ARGUMENT, false);
+ Assertions.assertEquals("((1 + 2) * 3)", expr.toSql());
+ }
+
+ // -----------------------------------------------------------------------
+ // CaseExpr – additional cases
+ // -----------------------------------------------------------------------
+
+ @Test
+ public void testCaseExprMultipleWhenClauses() {
+ // CASE WHEN col > 10 THEN 'big' WHEN col > 5 THEN 'mid' ELSE 'small'
END
+ Expr col = new SlotRef(null, "col");
+ Expr when1 = new BinaryPredicate(BinaryPredicate.Operator.GT, col, new
IntLiteral(10L));
+ Expr when2 = new BinaryPredicate(BinaryPredicate.Operator.GT, col, new
IntLiteral(5L));
+ List<CaseWhenClause> clauses = Arrays.asList(
+ new CaseWhenClause(when1, new StringLiteral("big")),
+ new CaseWhenClause(when2, new StringLiteral("mid")));
+ CaseExpr expr = new CaseExpr(clauses, new StringLiteral("small"),
false);
+ Assertions.assertEquals("CASE WHEN (`col` > 10) THEN 'big' WHEN (`col`
> 5) THEN 'mid' ELSE 'small' END",
+ expr.toSql());
+ }
+
+ @Test
+ public void testCaseExprNoElse() {
+ // CASE WHEN TRUE THEN 1 WHEN FALSE THEN 2 END (no else)
+ List<CaseWhenClause> clauses = Arrays.asList(
+ new CaseWhenClause(new BoolLiteral(true), new IntLiteral(1L)),
+ new CaseWhenClause(new BoolLiteral(false), new
IntLiteral(2L)));
+ CaseExpr expr = new CaseExpr(clauses, null, false);
+ Assertions.assertEquals("CASE WHEN TRUE THEN 1 WHEN FALSE THEN 2 END",
expr.toSql());
+ }
+
+ @Test
+ public void testCaseExprNullElse() {
+ // CASE WHEN col IS NULL THEN 'null_val' ELSE col END
+ Expr col = new SlotRef(null, "val");
+ Expr when1 = new IsNullPredicate(col, false);
+ CaseExpr expr = new CaseExpr(
+ Collections.singletonList(new CaseWhenClause(when1, new
StringLiteral("null_val"))),
+ col, false);
+ Assertions.assertEquals("CASE WHEN `val` IS NULL THEN 'null_val' ELSE
`val` END", expr.toSql());
+ }
+
+ @Test
+ public void testCaseExprNestedInArithmetic() {
+ // (CASE WHEN TRUE THEN 1 ELSE 0 END) + 5
+ CaseExpr caseExpr = new CaseExpr(
+ Collections.singletonList(new CaseWhenClause(new
BoolLiteral(true), new IntLiteral(1L))),
+ new IntLiteral(0L), false);
+ ArithmeticExpr expr = new ArithmeticExpr(
+ ArithmeticExpr.Operator.ADD, caseExpr, new IntLiteral(5L),
+ Type.BIGINT, NullableMode.DEPEND_ON_ARGUMENT, false);
+ Assertions.assertEquals("(CASE WHEN TRUE THEN 1 ELSE 0 END + 5)",
expr.toSql());
+ }
+
+ // -----------------------------------------------------------------------
+ // FunctionCallExpr – additional cases
+ // -----------------------------------------------------------------------
+
+ @Test
+ public void testFunctionCallExprMultipleArgs() {
+ FunctionCallExpr expr = new FunctionCallExpr("concat",
+ Arrays.asList(new StringLiteral("a"), new StringLiteral("b"),
new StringLiteral("c")), false);
+ Assertions.assertEquals("concat('a', 'b', 'c')", expr.toSql());
+ }
+
+ @Test
+ public void testFunctionCallExprDistinct() throws Exception {
+ // count(DISTINCT col) — set fnParams via reflection since no public
(FunctionName, FunctionParams) ctor
+ FunctionCallExpr expr = new FunctionCallExpr("count",
+ Collections.singletonList(new SlotRef(null, "col")), false);
+ FunctionParams distinctParams = new FunctionParams(true,
+ Collections.singletonList(new SlotRef(null, "col")));
+ java.lang.reflect.Field fnParamsField =
FunctionCallExpr.class.getDeclaredField("fnParams");
+ fnParamsField.setAccessible(true);
+ fnParamsField.set(expr, distinctParams);
+ Assertions.assertEquals("count(DISTINCT `col`)", expr.toSql());
+ }
+
+ @Test
+ public void testFunctionCallExprStar() throws Exception {
+ // count(*) — set fnParams via reflection
+ FunctionCallExpr expr = new FunctionCallExpr("count",
Collections.emptyList(), false);
+ FunctionParams starParams = FunctionParams.createStarParam();
+ java.lang.reflect.Field fnParamsField =
FunctionCallExpr.class.getDeclaredField("fnParams");
+ fnParamsField.setAccessible(true);
+ fnParamsField.set(expr, starParams);
+ Assertions.assertEquals("count(*)", expr.toSql());
+ }
+
+ @Test
+ public void testFunctionCallExprNestedFunctions() {
+ // length(upper('hello'))
+ FunctionCallExpr inner = new FunctionCallExpr("upper",
+ Collections.singletonList(new StringLiteral("hello")), false);
+ FunctionCallExpr expr = new FunctionCallExpr("length",
+ Collections.singletonList(inner), false);
+ Assertions.assertEquals("length(upper('hello'))", expr.toSql());
+ }
+
+ @Test
+ public void testFunctionCallExprWithSlotRef() {
+ // coalesce(col, 0)
+ FunctionCallExpr expr = new FunctionCallExpr("coalesce",
+ Arrays.asList(new SlotRef(null, "col"), new IntLiteral(0L)),
false);
+ Assertions.assertEquals("coalesce(`col`, 0)", expr.toSql());
+ }
+
+ @Test
+ public void testFunctionCallExprIfNull() {
+ // ifnull(a, b)
+ FunctionCallExpr expr = new FunctionCallExpr("ifnull",
+ Arrays.asList(new SlotRef(null, "a"), new
StringLiteral("default")), false);
+ Assertions.assertEquals("ifnull(`a`, 'default')", expr.toSql());
+ }
+
+ // -----------------------------------------------------------------------
+ // TimestampArithmeticExpr – additional cases
+ // -----------------------------------------------------------------------
+
+ @Test
+ public void testTimestampArithmeticExprDateSub() {
+ // date_sub(col, INTERVAL 7 DAY)
+ Expr e1 = new SlotRef(null, "dt");
+ Expr e2 = new IntLiteral(7L);
+ TimestampArithmeticExpr expr = new TimestampArithmeticExpr(
+ "date_sub", ArithmeticExpr.Operator.SUBTRACT,
+ e1, e2, "DAY", Type.DATETIMEV2, false);
+ Assertions.assertEquals("date_sub(`dt`, INTERVAL 7 DAY)",
expr.toSql());
+ }
+
+ @Test
+ public void testTimestampArithmeticExprDaysAdd() {
+ // days_add(col, INTERVAL 30 DAY)
+ Expr e1 = new SlotRef(null, "created_at");
+ Expr e2 = new IntLiteral(30L);
+ TimestampArithmeticExpr expr = new TimestampArithmeticExpr(
+ "days_add", ArithmeticExpr.Operator.ADD,
+ e1, e2, "DAY", Type.DATETIMEV2, false);
+ Assertions.assertEquals("days_add(`created_at`, INTERVAL 30 DAY)",
expr.toSql());
+ }
+
+ @Test
+ public void testTimestampArithmeticExprMonthsAdd() {
+ // months_add(col, INTERVAL 3 MONTH)
+ Expr e1 = new SlotRef(null, "dt");
+ Expr e2 = new IntLiteral(3L);
+ TimestampArithmeticExpr expr = new TimestampArithmeticExpr(
+ "months_add", ArithmeticExpr.Operator.ADD,
+ e1, e2, "MONTH", Type.DATETIMEV2, false);
+ Assertions.assertEquals("months_add(`dt`, INTERVAL 3 MONTH)",
expr.toSql());
+ }
+
+ @Test
+ public void testTimestampArithmeticExprHoursAdd() {
+ // hours_add(col, INTERVAL 2 HOUR)
+ Expr e1 = new SlotRef(null, "ts");
+ Expr e2 = new IntLiteral(2L);
+ TimestampArithmeticExpr expr = new TimestampArithmeticExpr(
+ "hours_add", ArithmeticExpr.Operator.ADD,
+ e1, e2, "HOUR", Type.DATETIMEV2, false);
+ Assertions.assertEquals("hours_add(`ts`, INTERVAL 2 HOUR)",
expr.toSql());
+ }
+
+ @Test
+ public void testTimestampArithmeticExprMinutesAdd() {
+ // minutes_add(col, INTERVAL 15 MINUTE)
+ Expr e1 = new SlotRef(null, "ts");
+ Expr e2 = new IntLiteral(15L);
+ TimestampArithmeticExpr expr = new TimestampArithmeticExpr(
+ "minutes_add", ArithmeticExpr.Operator.ADD,
+ e1, e2, "MINUTE", Type.DATETIMEV2, false);
+ Assertions.assertEquals("minutes_add(`ts`, INTERVAL 15 MINUTE)",
expr.toSql());
+ }
+
+ @Test
+ public void testTimestampArithmeticExprSecondsAdd() {
+ // seconds_add(col, INTERVAL 30 SECOND)
+ Expr e1 = new SlotRef(null, "ts");
+ Expr e2 = new IntLiteral(30L);
+ TimestampArithmeticExpr expr = new TimestampArithmeticExpr(
+ "seconds_add", ArithmeticExpr.Operator.ADD,
+ e1, e2, "SECOND", Type.DATETIMEV2, false);
+ Assertions.assertEquals("seconds_add(`ts`, INTERVAL 30 SECOND)",
expr.toSql());
+ }
+
+ @Test
+ public void testTimestampArithmeticExprTimestampadd() {
+ // TIMESTAMPADD(MONTH, 1, col)
+ Expr e1 = new SlotRef(null, "dt");
+ Expr e2 = new IntLiteral(1L);
+ TimestampArithmeticExpr expr = new TimestampArithmeticExpr(
+ "TIMESTAMPADD", ArithmeticExpr.Operator.ADD,
+ e1, e2, "MONTH", Type.DATETIMEV2, false);
+ // TIMESTAMPADD(unit, interval, timestamp) — same layout as
TIMESTAMPDIFF
+ Assertions.assertEquals("TIMESTAMPADD(MONTH, 1, `dt`)", expr.toSql());
+ }
+
+ @Test
+ public void testTimestampArithmeticExprTimestampdiffDay() {
+ // TIMESTAMPDIFF(DAY, start, end)
+ Expr e1 = new SlotRef(null, "end_date");
+ Expr e2 = new SlotRef(null, "start_date");
+ TimestampArithmeticExpr expr = new TimestampArithmeticExpr(
+ "TIMESTAMPDIFF", ArithmeticExpr.Operator.ADD,
+ e1, e2, "DAY", Type.DATETIMEV2, false);
+ Assertions.assertEquals("TIMESTAMPDIFF(DAY, `start_date`,
`end_date`)", expr.toSql());
+ }
+
+ @Test
+ public void testTimestampArithmeticExprWeekAdd() {
+ // weeks_add(col, INTERVAL 2 WEEK)
+ Expr e1 = new SlotRef(null, "dt");
+ Expr e2 = new IntLiteral(2L);
+ TimestampArithmeticExpr expr = new TimestampArithmeticExpr(
+ "weeks_add", ArithmeticExpr.Operator.ADD,
+ e1, e2, "WEEK", Type.DATETIMEV2, false);
+ Assertions.assertEquals("weeks_add(`dt`, INTERVAL 2 WEEK)",
expr.toSql());
+ }
+
+ @Test
+ public void testTimestampArithmeticExprWithLiteral() {
+ // date_add('2024-01-01', INTERVAL 1 YEAR)
+ Expr e1;
+ try {
+ e1 = new DateLiteral("2024-01-01", Type.DATEV2);
+ } catch (AnalysisException e) {
+ throw new RuntimeException(e);
+ }
+ Expr e2 = new IntLiteral(1L);
+ TimestampArithmeticExpr expr = new TimestampArithmeticExpr(
+ "date_add", ArithmeticExpr.Operator.ADD,
+ e1, e2, "YEAR", Type.DATETIMEV2, false);
+ Assertions.assertEquals("date_add('2024-01-01', INTERVAL 1 YEAR)",
expr.toSql());
+ }
+
+ // -----------------------------------------------------------------------
+ // TimeV2Literal – different scales
+ // -----------------------------------------------------------------------
+
+ @Test
+ public void testTimeV2LiteralScale0() {
+ // scale=0 → no fractional part: "01:02:03"
+ TimeV2Literal expr = new TimeV2Literal(1, 2, 3, 0, 0, false);
+ Assertions.assertEquals("\"01:02:03\"", expr.toSql());
+ }
+
+ @Test
+ public void testTimeV2LiteralScale1() {
+ // scale=1 → 1 digit: "01:02:03.1"
+ // microsecond=100000 → scaled: 100000 / 10^(6-1) = 100000/100000 = 1
+ TimeV2Literal expr = new TimeV2Literal(1, 2, 3, 100000, 1, false);
+ Assertions.assertEquals("\"01:02:03.1\"", expr.toSql());
+ }
+
+ @Test
+ public void testTimeV2LiteralScale3() {
+ // scale=3 → 3 digits: "01:02:03.123"
+ // microsecond=123000 → scaled: 123000 / 10^(6-3) = 123000/1000 = 123
+ TimeV2Literal expr = new TimeV2Literal(1, 2, 3, 123000, 3, false);
+ Assertions.assertEquals("\"01:02:03.123\"", expr.toSql());
+ }
+
+ @Test
+ public void testTimeV2LiteralScale6() {
+ // scale=6 → 6 digits: "01:02:03.123456"
+ TimeV2Literal expr = new TimeV2Literal(1, 2, 3, 123456, 6, false);
+ Assertions.assertEquals("\"01:02:03.123456\"", expr.toSql());
+ }
+
+ @Test
+ public void testTimeV2LiteralNegativeWithScale() {
+ // negative, scale=3: "-10:20:30.500"
+ TimeV2Literal expr = new TimeV2Literal(10, 20, 30, 500000, 3, true);
+ Assertions.assertEquals("\"-10:20:30.500\"", expr.toSql());
+ }
+
+ @Test
+ public void testTimeV2LiteralLargeHour() {
+ // hour > 99, scale=0: "838:59:59"
+ TimeV2Literal expr = new TimeV2Literal(838, 59, 59, 0, 0, false);
+ Assertions.assertEquals("\"838:59:59\"", expr.toSql());
+ }
+
+ @Test
+ public void testTimeV2LiteralZero() {
+ // 00:00:00, scale=0
+ TimeV2Literal expr = new TimeV2Literal(0, 0, 0, 0, 0, false);
+ Assertions.assertEquals("\"00:00:00\"", expr.toSql());
+ }
+
+ // -----------------------------------------------------------------------
+ // DateLiteral – different types and scales
+ // -----------------------------------------------------------------------
+
+ @Test
+ public void testDateLiteralDateV2() throws AnalysisException {
+ // DATE type: 'YYYY-MM-DD'
+ DateLiteral expr = new DateLiteral("2024-03-15", Type.DATEV2);
+ Assertions.assertEquals("'2024-03-15'", expr.toSql());
+ }
+
+ @Test
+ public void testDateLiteralDatetime() throws AnalysisException {
+ // DATETIME (v1) type: 'YYYY-MM-DD HH:MM:SS'
+ DateLiteral expr = new DateLiteral("2024-03-15 10:30:00",
Type.DATETIME);
+ Assertions.assertEquals("'2024-03-15 10:30:00'", expr.toSql());
+ }
+
+ @Test
+ public void testDateLiteralDatetimeV2Scale0() throws AnalysisException {
+ // DATETIMEV2 scale=0: 'YYYY-MM-DD HH:MM:SS'
+ DateLiteral expr = new DateLiteral("2024-03-15 10:30:00",
ScalarType.createDatetimeV2Type(0));
+ Assertions.assertEquals("'2024-03-15 10:30:00'", expr.toSql());
+ }
+
+ @Test
+ public void testDateLiteralDatetimeV2Scale3() throws AnalysisException {
+ // DATETIMEV2 scale=3: 'YYYY-MM-DD HH:MM:SS.mmm'
+ DateLiteral expr = new DateLiteral("2024-03-15 10:30:00.123",
ScalarType.createDatetimeV2Type(3));
+ Assertions.assertEquals("'2024-03-15 10:30:00.123'", expr.toSql());
+ }
+
+ @Test
+ public void testDateLiteralDatetimeV2Scale6() throws AnalysisException {
+ // DATETIMEV2 scale=6: 'YYYY-MM-DD HH:MM:SS.mmmmmm'
+ DateLiteral expr = new DateLiteral("2024-03-15 10:30:00.123456",
ScalarType.createDatetimeV2Type(6));
+ Assertions.assertEquals("'2024-03-15 10:30:00.123456'", expr.toSql());
+ }
+
+ @Test
+ public void testDateLiteralDateV2FromLongConstructor() {
+ // Long-based constructor: year=2023, month=12, day=31
+ DateLiteral expr = new DateLiteral(2023L, 12L, 31L, Type.DATEV2);
+ Assertions.assertEquals("'2023-12-31'", expr.toSql());
+ }
+
+ @Test
+ public void testDateLiteralDatetimeFromLongConstructor() {
+ // Long-based constructor with time: 2023-06-01 08:00:00
+ DateLiteral expr = new DateLiteral(2023L, 6L, 1L, 8L, 0L, 0L,
Type.DATETIME);
+ Assertions.assertEquals("'2023-06-01 08:00:00'", expr.toSql());
+ }
+
+ @Test
+ public void testDateLiteralDatetimeV2Scale1() throws AnalysisException {
+ // DATETIMEV2 scale=1: 'YYYY-MM-DD HH:MM:SS.m'
+ DateLiteral expr = new DateLiteral("2024-01-01 00:00:00.5",
ScalarType.createDatetimeV2Type(1));
+ Assertions.assertEquals("'2024-01-01 00:00:00.5'", expr.toSql());
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]