This is an automated email from the ASF dual-hosted git repository.
dataroaring pushed a commit to branch branch-3.0
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-3.0 by this push:
new 9d4ecf7c6df branch-3.0: [fix](jdbc catalog) Improve conjunct
expression handling in JdbcScanNode #50542 (#50647)
9d4ecf7c6df is described below
commit 9d4ecf7c6df0232df341af73d9bf5f17ba6db1b0
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed May 14 09:47:35 2025 +0800
branch-3.0: [fix](jdbc catalog) Improve conjunct expression handling in
JdbcScanNode #50542 (#50647)
Cherry-picked from #50542
Co-authored-by: zy-kkk <[email protected]>
---
.../doris/datasource/jdbc/source/JdbcScanNode.java | 78 +++-
.../datasource/jdbc/source/JdbcScanNodeTest.java | 496 +++++++++++++++++++++
.../jdbc/test_oracle_jdbc_catalog.out | Bin 7894 -> 10560 bytes
.../jdbc/test_oracle_jdbc_catalog.groovy | 64 +++
4 files changed, 637 insertions(+), 1 deletion(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/jdbc/source/JdbcScanNode.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/jdbc/source/JdbcScanNode.java
index 911d932adde..31026e6b877 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/jdbc/source/JdbcScanNode.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/jdbc/source/JdbcScanNode.java
@@ -21,10 +21,13 @@ import org.apache.doris.analysis.Analyzer;
import org.apache.doris.analysis.BinaryPredicate;
import org.apache.doris.analysis.BoolLiteral;
import org.apache.doris.analysis.CastExpr;
+import org.apache.doris.analysis.CompoundPredicate;
+import org.apache.doris.analysis.CompoundPredicate.Operator;
import org.apache.doris.analysis.DateLiteral;
import org.apache.doris.analysis.Expr;
import org.apache.doris.analysis.ExprSubstitutionMap;
import org.apache.doris.analysis.FunctionCallExpr;
+import org.apache.doris.analysis.InPredicate;
import org.apache.doris.analysis.NullLiteral;
import org.apache.doris.analysis.SlotDescriptor;
import org.apache.doris.analysis.SlotRef;
@@ -54,6 +57,7 @@ import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@@ -346,6 +350,25 @@ public class JdbcScanNode extends ExternalScanNode {
}
public static String conjunctExprToString(TOdbcTableType tableType, Expr
expr, TableIf tbl) {
+ if (expr == null) {
+ return "";
+ }
+ if (expr instanceof CompoundPredicate) {
+ CompoundPredicate compoundPredicate = (CompoundPredicate) expr;
+ if (compoundPredicate.getOp() == Operator.NOT) {
+ String childString = conjunctExprToString(tableType,
compoundPredicate.getChild(0), tbl);
+ return "(NOT " + childString + ")";
+ } else {
+ String leftString = conjunctExprToString(tableType,
compoundPredicate.getChild(0), tbl);
+ String rightString = "";
+ if (compoundPredicate.getChildren().size() > 1) {
+ rightString = conjunctExprToString(tableType,
compoundPredicate.getChild(1), tbl);
+ }
+ String opString = compoundPredicate.getOp().toString();
+ return "(" + leftString + " " + opString + " " + rightString +
")";
+ }
+ }
+
if (expr.contains(DateLiteral.class) && expr instanceof
BinaryPredicate) {
ArrayList<Expr> children = expr.getChildren();
String filter =
children.get(0).toExternalSql(TableType.JDBC_EXTERNAL_TABLE, tbl);
@@ -362,6 +385,34 @@ public class JdbcScanNode extends ExternalScanNode {
return filter;
}
+ if (expr.contains(DateLiteral.class) && expr instanceof InPredicate) {
+ InPredicate inPredicate = (InPredicate) expr;
+ Expr leftChild = inPredicate.getChild(0);
+ String filter =
leftChild.toExternalSql(TableType.JDBC_EXTERNAL_TABLE, tbl);
+
+ if (inPredicate.isNotIn()) {
+ filter += " NOT";
+ }
+ filter += " IN (";
+
+ List<String> inItemStrings = new ArrayList<>();
+ for (int i = 1; i < inPredicate.getChildren().size(); i++) {
+ Expr inItem = inPredicate.getChild(i);
+ if (tableType.equals(TOdbcTableType.ORACLE)) {
+ inItemStrings.add(handleOracleDateFormat(inItem, tbl));
+ } else if (tableType.equals(TOdbcTableType.TRINO) ||
tableType.equals(TOdbcTableType.PRESTO)) {
+ inItemStrings.add(handleTrinoDateFormat(inItem, tbl));
+ } else {
+
inItemStrings.add(inItem.toExternalSql(TableType.JDBC_EXTERNAL_TABLE, tbl));
+ }
+ }
+
+ filter += String.join(", ", inItemStrings);
+ filter += ")";
+
+ return filter;
+ }
+
// Only for old planner
if (expr.contains(BoolLiteral.class) &&
"1".equals(expr.getStringValue()) && expr.getChildren().isEmpty()) {
return "1 = 1";
@@ -373,11 +424,36 @@ public class JdbcScanNode extends ExternalScanNode {
private static String handleOracleDateFormat(Expr expr, TableIf tbl) {
if (expr.isConstant()
&& (expr.getType().isDatetime() ||
expr.getType().isDatetimeV2())) {
- return "to_date('" + expr.getStringValue() + "', 'yyyy-mm-dd
hh24:mi:ss')";
+ String dateStr = expr.getStringValue();
+ // Check if the date string contains milliseconds/microseconds
+ if (dateStr.contains(".")) {
+ // For Oracle, we need to use to_timestamp for fractional
seconds
+ // Extract date part and fractional seconds part
+ String formatModel = getString(dateStr);
+ return "to_timestamp('" + dateStr + "', '" + formatModel +
"')";
+ }
+ // Regular datetime without fractional seconds
+ return "to_date('" + dateStr + "', 'yyyy-mm-dd hh24:mi:ss')";
}
return expr.toExternalSql(TableType.JDBC_EXTERNAL_TABLE, tbl);
}
+ @NotNull
+ private static String getString(String dateStr) {
+ String[] parts = dateStr.split("\\.");
+ String fractionPart = parts[1];
+ // Determine the format model based on the length of fractional seconds
+ String formatModel;
+ if (fractionPart.length() <= 3) {
+ // Milliseconds (up to 3 digits)
+ formatModel = "yyyy-mm-dd hh24:mi:ss.FF3";
+ } else {
+ // Microseconds (up to 6 digits)
+ formatModel = "yyyy-mm-dd hh24:mi:ss.FF6";
+ }
+ return formatModel;
+ }
+
private static String handleTrinoDateFormat(Expr expr, TableIf tbl) {
if (expr.isConstant()) {
if (expr.getType().isDate() || expr.getType().isDateV2()) {
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/datasource/jdbc/source/JdbcScanNodeTest.java
b/fe/fe-core/src/test/java/org/apache/doris/datasource/jdbc/source/JdbcScanNodeTest.java
new file mode 100644
index 00000000000..ac78a2c6e29
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/datasource/jdbc/source/JdbcScanNodeTest.java
@@ -0,0 +1,496 @@
+// 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.datasource.jdbc.source;
+
+import org.apache.doris.analysis.BinaryPredicate;
+import org.apache.doris.analysis.BinaryPredicate.Operator;
+import org.apache.doris.analysis.BoolLiteral;
+import org.apache.doris.analysis.CompoundPredicate;
+import org.apache.doris.analysis.DateLiteral;
+import org.apache.doris.analysis.FloatLiteral;
+import org.apache.doris.analysis.IntLiteral;
+import org.apache.doris.analysis.IsNullPredicate;
+import org.apache.doris.analysis.LikePredicate;
+import org.apache.doris.analysis.SlotRef;
+import org.apache.doris.analysis.StringLiteral;
+import org.apache.doris.catalog.JdbcTable;
+import org.apache.doris.catalog.Type;
+import org.apache.doris.thrift.TOdbcTableType;
+
+import mockit.Expectations;
+import mockit.Mocked;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JdbcScanNodeTest {
+
+ @Mocked
+ private JdbcTable mockTable;
+
+ @Test
+ public void testSimpleBinaryPredicate() {
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ SlotRef idSlot = new SlotRef(null, "ID");
+ IntLiteral intLiteral = new IntLiteral(1);
+ BinaryPredicate predicate = new BinaryPredicate(Operator.EQ, idSlot,
intLiteral);
+
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL, predicate, mockTable);
+ Assert.assertEquals("(\"ID\" = 1)", result);
+
+ result = JdbcScanNode.conjunctExprToString(TOdbcTableType.ORACLE,
predicate, mockTable);
+ Assert.assertEquals("(\"ID\" = 1)", result);
+ }
+
+ @Test
+ public void testSimpleCompoundPredicate() {
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ SlotRef idSlot = new SlotRef(null, "ID");
+ IntLiteral intLiteral = new IntLiteral(1);
+ BinaryPredicate leftPred = new BinaryPredicate(Operator.EQ, idSlot,
intLiteral);
+
+ SlotRef nameSlot = new SlotRef(null, "NAME");
+ StringLiteral stringLiteral = new StringLiteral("test");
+ BinaryPredicate rightPred = new BinaryPredicate(Operator.EQ, nameSlot,
stringLiteral);
+
+ CompoundPredicate compoundPredicate = new
CompoundPredicate(CompoundPredicate.Operator.OR, leftPred, rightPred);
+
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL, compoundPredicate,
mockTable);
+ Assert.assertEquals("((\"ID\" = 1) OR (\"NAME\" = 'test'))", result);
+ }
+
+ @Test
+ public void testNestedCompoundPredicate() {
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ // ID = 1 OR (NAME = 'test' AND AGE > 18)
+ SlotRef idSlot = new SlotRef(null, "ID");
+ IntLiteral intLiteral = new IntLiteral(1);
+ BinaryPredicate leftPred = new BinaryPredicate(Operator.EQ, idSlot,
intLiteral);
+
+ SlotRef nameSlot = new SlotRef(null, "NAME");
+ StringLiteral stringLiteral = new StringLiteral("test");
+ BinaryPredicate namePred = new BinaryPredicate(Operator.EQ, nameSlot,
stringLiteral);
+
+ SlotRef ageSlot = new SlotRef(null, "AGE");
+ IntLiteral ageLiteral = new IntLiteral(18);
+ BinaryPredicate agePred = new BinaryPredicate(Operator.GT, ageSlot,
ageLiteral);
+
+ CompoundPredicate innerComp = new
CompoundPredicate(CompoundPredicate.Operator.AND, namePred, agePred);
+ CompoundPredicate outerComp = new
CompoundPredicate(CompoundPredicate.Operator.OR, leftPred, innerComp);
+
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL, outerComp, mockTable);
+ Assert.assertEquals("((\"ID\" = 1) OR ((\"NAME\" = 'test') AND
(\"AGE\" > 18)))", result);
+ }
+
+ @Test
+ public void testComplexNestedCompoundPredicate() {
+
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ // (ID = 1 OR NAME = 'test') AND (AGE > 18 OR DEPT = 'HR')
+ SlotRef idSlot = new SlotRef(null, "ID");
+ IntLiteral intLiteral = new IntLiteral(1);
+ BinaryPredicate idPred = new BinaryPredicate(Operator.EQ, idSlot,
intLiteral);
+
+ SlotRef nameSlot = new SlotRef(null, "NAME");
+ StringLiteral nameLiteral = new StringLiteral("test");
+ BinaryPredicate namePred = new BinaryPredicate(Operator.EQ, nameSlot,
nameLiteral);
+
+ SlotRef ageSlot = new SlotRef(null, "AGE");
+ IntLiteral ageLiteral = new IntLiteral(18);
+ BinaryPredicate agePred = new BinaryPredicate(Operator.GT, ageSlot,
ageLiteral);
+
+ SlotRef deptSlot = new SlotRef(null, "DEPT");
+ StringLiteral deptLiteral = new StringLiteral("HR");
+ BinaryPredicate deptPred = new BinaryPredicate(Operator.EQ, deptSlot,
deptLiteral);
+
+ CompoundPredicate leftComp = new
CompoundPredicate(CompoundPredicate.Operator.OR, idPred, namePred);
+ CompoundPredicate rightComp = new
CompoundPredicate(CompoundPredicate.Operator.OR, agePred, deptPred);
+ CompoundPredicate outerComp = new
CompoundPredicate(CompoundPredicate.Operator.AND, leftComp, rightComp);
+
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL, outerComp, mockTable);
+ Assert.assertEquals("(((\"ID\" = 1) OR (\"NAME\" = 'test')) AND
((\"AGE\" > 18) OR (\"DEPT\" = 'HR')))", result);
+ }
+
+ @Test
+ public void testDateLiteralOracle() throws Exception {
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ DateLiteral dateLiteral = new DateLiteral("2023-01-01 12:34:56",
Type.DATETIME);
+
+ SlotRef dateSlot = new SlotRef(null, "CREATE_TIME");
+ BinaryPredicate datePred = new BinaryPredicate(Operator.GE, dateSlot,
dateLiteral);
+
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.ORACLE, datePred, mockTable);
+ Assert.assertTrue(result.contains("to_date('2023-01-01 12:34:56',
'yyyy-mm-dd hh24:mi:ss')"));
+ Assert.assertTrue(result.startsWith("\"CREATE_TIME\" >= "));
+ }
+
+ @Test
+ public void testDateLiteralTrino() throws Exception {
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ DateLiteral dateLiteral = new DateLiteral("2023-01-01 12:34:56",
Type.DATETIME);
+
+ SlotRef dateSlot = new SlotRef(null, "CREATE_TIME");
+ BinaryPredicate datePred = new BinaryPredicate(Operator.GE, dateSlot,
dateLiteral);
+
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.TRINO, datePred, mockTable);
+ Assert.assertTrue(result.contains("timestamp '2023-01-01 12:34:56'"));
+ Assert.assertTrue(result.startsWith("\"CREATE_TIME\" >= "));
+ }
+
+ @Test
+ public void testDateLiteralCompoundPredicateOracle() throws Exception {
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ // ID = 1 OR CREATE_TIME >= '2023-01-01'
+ SlotRef idSlot = new SlotRef(null, "ID");
+ IntLiteral intLiteral = new IntLiteral(1);
+ BinaryPredicate idPred = new BinaryPredicate(Operator.EQ, idSlot,
intLiteral);
+
+ DateLiteral dateLiteral = new DateLiteral("2023-01-01 12:34:56",
Type.DATETIME);
+
+ SlotRef dateSlot = new SlotRef(null, "CREATE_TIME");
+ BinaryPredicate datePred = new BinaryPredicate(Operator.GE, dateSlot,
dateLiteral);
+
+ CompoundPredicate compPred = new
CompoundPredicate(CompoundPredicate.Operator.OR, idPred, datePred);
+
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.ORACLE, compPred, mockTable);
+ Assert.assertTrue(result.contains("to_date('2023-01-01 12:34:56',
'yyyy-mm-dd hh24:mi:ss')"));
+ Assert.assertTrue(result.contains("\"ID\" = 1"));
+ Assert.assertTrue(result.contains(" OR "));
+ }
+
+ @Test
+ public void testBoolLiteral() {
+ // 1 = 1 (true literal)
+ BoolLiteral boolLiteral = new BoolLiteral(true);
+
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL, boolLiteral, mockTable);
+ Assert.assertEquals("1 = 1", result);
+ }
+
+ @Test
+ public void testComplexPredicateWithDateComparisons() throws Exception {
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ // (ID = 1 OR NAME = 'test') AND (CREATE_TIME >= '2023-01-01' AND
UPDATE_TIME <= '2023-12-31')
+ SlotRef idSlot = new SlotRef(null, "ID");
+ IntLiteral intLiteral = new IntLiteral(1);
+ BinaryPredicate idPred = new BinaryPredicate(Operator.EQ, idSlot,
intLiteral);
+
+ SlotRef nameSlot = new SlotRef(null, "NAME");
+ StringLiteral nameLiteral = new StringLiteral("test");
+ BinaryPredicate namePred = new BinaryPredicate(Operator.EQ, nameSlot,
nameLiteral);
+
+ DateLiteral startDateLiteral = new DateLiteral("2023-01-01 00:00:00",
Type.DATETIME);
+ DateLiteral endDateLiteral = new DateLiteral("2023-12-31 23:59:59",
Type.DATETIME);
+
+ SlotRef createTimeSlot = new SlotRef(null, "CREATE_TIME");
+ BinaryPredicate createTimePred = new BinaryPredicate(Operator.GE,
createTimeSlot, startDateLiteral);
+
+ SlotRef updateTimeSlot = new SlotRef(null, "UPDATE_TIME");
+ BinaryPredicate updateTimePred = new BinaryPredicate(Operator.LE,
updateTimeSlot, endDateLiteral);
+
+ CompoundPredicate leftComp = new
CompoundPredicate(CompoundPredicate.Operator.OR, idPred, namePred);
+ CompoundPredicate rightComp = new
CompoundPredicate(CompoundPredicate.Operator.AND, createTimePred,
updateTimePred);
+ CompoundPredicate outerComp = new
CompoundPredicate(CompoundPredicate.Operator.AND, leftComp, rightComp);
+
+ // Test for Oracle
+ String oracleResult =
JdbcScanNode.conjunctExprToString(TOdbcTableType.ORACLE, outerComp, mockTable);
+ Assert.assertTrue(oracleResult.contains("to_date('2023-01-01
00:00:00', 'yyyy-mm-dd hh24:mi:ss')"));
+ Assert.assertTrue(oracleResult.contains("to_date('2023-12-31
23:59:59', 'yyyy-mm-dd hh24:mi:ss')"));
+
+ // Test for Trino
+ String trinoResult =
JdbcScanNode.conjunctExprToString(TOdbcTableType.TRINO, outerComp, mockTable);
+ Assert.assertTrue(trinoResult.contains("timestamp '2023-01-01
00:00:00'"));
+ Assert.assertTrue(trinoResult.contains("timestamp '2023-12-31
23:59:59'"));
+
+ // Test for MySQL (no special date formatting)
+ String mysqlResult =
JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL, outerComp, mockTable);
+ Assert.assertTrue(mysqlResult.contains("'2023-01-01 00:00:00'"));
+ Assert.assertTrue(mysqlResult.contains("'2023-12-31 23:59:59'"));
+ }
+
+ @Test
+ public void testDifferentComparisonOperators() {
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ SlotRef ageSlot = new SlotRef(null, "AGE");
+ IntLiteral ageLiteral = new IntLiteral(30);
+
+ // AGE < 30
+ BinaryPredicate ltPred = new BinaryPredicate(Operator.LT, ageSlot,
ageLiteral);
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL, ltPred, mockTable);
+ Assert.assertEquals("(\"AGE\" < 30)", result);
+
+ // AGE <= 30
+ BinaryPredicate lePred = new BinaryPredicate(Operator.LE, ageSlot,
ageLiteral);
+ result = JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL,
lePred, mockTable);
+ Assert.assertEquals("(\"AGE\" <= 30)", result);
+
+ // AGE > 30
+ BinaryPredicate gtPred = new BinaryPredicate(Operator.GT, ageSlot,
ageLiteral);
+ result = JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL,
gtPred, mockTable);
+ Assert.assertEquals("(\"AGE\" > 30)", result);
+
+ // AGE >= 30
+ BinaryPredicate gePred = new BinaryPredicate(Operator.GE, ageSlot,
ageLiteral);
+ result = JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL,
gePred, mockTable);
+ Assert.assertEquals("(\"AGE\" >= 30)", result);
+
+ // AGE != 30
+ BinaryPredicate nePred = new BinaryPredicate(Operator.NE, ageSlot,
ageLiteral);
+ result = JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL,
nePred, mockTable);
+ Assert.assertEquals("(\"AGE\" != 30)", result);
+ }
+
+ @Test
+ public void testIsNullPredicates() {
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ // NAME IS NULL
+ SlotRef nameSlot = new SlotRef(null, "NAME");
+ IsNullPredicate isNullPred = new IsNullPredicate(nameSlot, false);
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL, isNullPred, mockTable);
+ Assert.assertEquals("\"NAME\" IS NULL", result);
+
+ // NAME IS NOT NULL
+ IsNullPredicate isNotNullPred = new IsNullPredicate(nameSlot, true);
+ result = JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL,
isNotNullPred, mockTable);
+ Assert.assertEquals("\"NAME\" IS NOT NULL", result);
+ }
+
+ @Test
+ public void testCompoundIsNullPredicates() {
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ // ID = 1 AND NAME IS NULL
+ SlotRef idSlot = new SlotRef(null, "ID");
+ IntLiteral intLiteral = new IntLiteral(1);
+ BinaryPredicate idPred = new BinaryPredicate(Operator.EQ, idSlot,
intLiteral);
+
+ SlotRef nameSlot = new SlotRef(null, "NAME");
+ IsNullPredicate isNullPred = new IsNullPredicate(nameSlot, false);
+
+ CompoundPredicate compPred = new
CompoundPredicate(CompoundPredicate.Operator.AND, idPred, isNullPred);
+
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL, compPred, mockTable);
+ Assert.assertEquals("((\"ID\" = 1) AND \"NAME\" IS NULL)", result);
+ }
+
+ @Test
+ public void testLikePredicates() {
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ // NAME LIKE 'test%'
+ SlotRef nameSlot = new SlotRef(null, "NAME");
+ StringLiteral pattern = new StringLiteral("test%");
+ LikePredicate likePred = new
LikePredicate(LikePredicate.Operator.LIKE, nameSlot, pattern);
+
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL, likePred, mockTable);
+ Assert.assertEquals("\"NAME\" LIKE 'test%'", result);
+
+ }
+
+ @Test
+ public void testFloatLiteral() {
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ // SALARY > 5000.50
+ SlotRef salarySlot = new SlotRef(null, "SALARY");
+ FloatLiteral floatLiteral = new FloatLiteral(5000.50);
+ BinaryPredicate salaryPred = new BinaryPredicate(Operator.GT,
salarySlot, floatLiteral);
+
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL, salaryPred, mockTable);
+ Assert.assertEquals("(\"SALARY\" > 5000.5)", result);
+ }
+
+ @Test
+ public void testVeryComplexNestedPredicate() {
+
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ // (ID > 10 AND ID < 100) OR (NAME LIKE 'test%' AND (DEPT = 'HR' OR
SALARY > 5000.0))
+ SlotRef idSlot = new SlotRef(null, "ID");
+ IntLiteral id10 = new IntLiteral(10);
+ IntLiteral id100 = new IntLiteral(100);
+ BinaryPredicate idGt10 = new BinaryPredicate(Operator.GT, idSlot,
id10);
+ BinaryPredicate idLt100 = new BinaryPredicate(Operator.LT, idSlot,
id100);
+
+ CompoundPredicate idRange = new
CompoundPredicate(CompoundPredicate.Operator.AND, idGt10, idLt100);
+
+ SlotRef nameSlot = new SlotRef(null, "NAME");
+ StringLiteral pattern = new StringLiteral("test%");
+ LikePredicate nameLike = new
LikePredicate(LikePredicate.Operator.LIKE, nameSlot, pattern);
+
+ SlotRef deptSlot = new SlotRef(null, "DEPT");
+ StringLiteral deptLiteral = new StringLiteral("HR");
+ BinaryPredicate deptEq = new BinaryPredicate(Operator.EQ, deptSlot,
deptLiteral);
+
+ SlotRef salarySlot = new SlotRef(null, "SALARY");
+ FloatLiteral salaryLiteral = new FloatLiteral(5000.0);
+ BinaryPredicate salaryGt = new BinaryPredicate(Operator.GT,
salarySlot, salaryLiteral);
+
+ CompoundPredicate deptOrSalary = new
CompoundPredicate(CompoundPredicate.Operator.OR, deptEq, salaryGt);
+ CompoundPredicate nameAndDeptSalary = new
CompoundPredicate(CompoundPredicate.Operator.AND, nameLike, deptOrSalary);
+
+ CompoundPredicate finalPred = new
CompoundPredicate(CompoundPredicate.Operator.OR, idRange, nameAndDeptSalary);
+
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL, finalPred, mockTable);
+ Assert.assertEquals("(((\"ID\" > 10) AND (\"ID\" < 100)) OR (\"NAME\"
LIKE 'test%' AND ((\"DEPT\" = 'HR') OR (\"SALARY\" > 5000))))", result);
+ }
+
+ @Test
+ public void testTripleNestedCompoundPredicate() {
+
+ new Expectations() {{
+ mockTable.getProperRemoteColumnName((TOdbcTableType) any,
anyString);
+ result = new mockit.Delegate() {
+ String getProperColumnName(TOdbcTableType tableType,
String colName) {
+ return "\"" + colName + "\"";
+ }
+ };
+ }};
+
+ // (ID = 1 OR (NAME = 'test' AND (AGE > 18 OR DEPT = 'HR')))
+ SlotRef idSlot = new SlotRef(null, "ID");
+ IntLiteral intLiteral = new IntLiteral(1);
+ BinaryPredicate idPred = new BinaryPredicate(Operator.EQ, idSlot,
intLiteral);
+
+ SlotRef nameSlot = new SlotRef(null, "NAME");
+ StringLiteral nameLiteral = new StringLiteral("test");
+ BinaryPredicate namePred = new BinaryPredicate(Operator.EQ, nameSlot,
nameLiteral);
+
+ SlotRef ageSlot = new SlotRef(null, "AGE");
+ IntLiteral ageLiteral = new IntLiteral(18);
+ BinaryPredicate agePred = new BinaryPredicate(Operator.GT, ageSlot,
ageLiteral);
+
+ SlotRef deptSlot = new SlotRef(null, "DEPT");
+ StringLiteral deptLiteral = new StringLiteral("HR");
+ BinaryPredicate deptPred = new BinaryPredicate(Operator.EQ, deptSlot,
deptLiteral);
+
+ CompoundPredicate innerComp = new
CompoundPredicate(CompoundPredicate.Operator.OR, agePred, deptPred);
+ CompoundPredicate middleComp = new
CompoundPredicate(CompoundPredicate.Operator.AND, namePred, innerComp);
+ CompoundPredicate outerComp = new
CompoundPredicate(CompoundPredicate.Operator.OR, idPred, middleComp);
+
+ String result =
JdbcScanNode.conjunctExprToString(TOdbcTableType.MYSQL, outerComp, mockTable);
+ Assert.assertEquals("((\"ID\" = 1) OR ((\"NAME\" = 'test') AND
((\"AGE\" > 18) OR (\"DEPT\" = 'HR'))))", result);
+ }
+}
diff --git
a/regression-test/data/external_table_p0/jdbc/test_oracle_jdbc_catalog.out
b/regression-test/data/external_table_p0/jdbc/test_oracle_jdbc_catalog.out
index 1612fc30ab3..d2806d74ab6 100644
Binary files
a/regression-test/data/external_table_p0/jdbc/test_oracle_jdbc_catalog.out and
b/regression-test/data/external_table_p0/jdbc/test_oracle_jdbc_catalog.out
differ
diff --git
a/regression-test/suites/external_table_p0/jdbc/test_oracle_jdbc_catalog.groovy
b/regression-test/suites/external_table_p0/jdbc/test_oracle_jdbc_catalog.groovy
index d2b3669ab45..4dd2607d484 100644
---
a/regression-test/suites/external_table_p0/jdbc/test_oracle_jdbc_catalog.groovy
+++
b/regression-test/suites/external_table_p0/jdbc/test_oracle_jdbc_catalog.groovy
@@ -136,6 +136,70 @@ suite("test_oracle_jdbc_catalog",
"p0,external,oracle,external_docker,external_d
order_qt_date6 """ select * from TEST_DATE where (T1 < '2022-01-22
00:00:00' or T1 > '2022-01-20 00:00:00') and (T1 < '2022-01-23 00:00:00' or T1
> '2022-01-19 00:00:00'); """
order_qt_date7 """select * from TEST_TIMESTAMP where T2 <
str_to_date('2020-12-21 12:34:56', '%Y-%m-%d %H:%i:%s');"""
+ // Test compound predicates processing - Multiple test cases for NOT,
AND, OR combinations and nesting
+ order_qt_compound1 """select * from TEST_TIMESTAMP where T1 is not
null order by ID;"""
+ order_qt_compound2 """select * from TEST_TIMESTAMP where NOT (T1 is
null) order by ID;"""
+ order_qt_compound3 """select * from TEST_TIMESTAMP where T1 is not
null AND T1 < '2013-02-01 00:00:00' order by ID;"""
+ order_qt_compound4 """select * from TEST_TIMESTAMP where T1 is not
null OR T2 is not null order by ID;"""
+ order_qt_compound5 """select * from TEST_TIMESTAMP where NOT (T1 is
null OR T2 is null) order by ID;"""
+ order_qt_compound6 """select * from TEST_TIMESTAMP where (T1 is not
null AND T1 < '2013-02-01 00:00:00') OR (T2 is not null AND T2 > '2019-01-01
00:00:00') order by ID;"""
+ order_qt_compound7 """select * from TEST_TIMESTAMP where NOT ((T1 is
null AND T2 is null) OR (T3 is null AND T4 is null)) order by ID;"""
+
+ // Test IN operator with date/time values
+ order_qt_in_date1 """select * from TEST_TIMESTAMP where T1 IN
('2013-01-21 05:23:01', '2013-11-12 20:32:56') order by ID;"""
+ order_qt_in_date2 """select * from TEST_TIMESTAMP where T1 NOT IN
('2013-01-21 05:23:01') order by ID;"""
+
+ // Test date/time comparisons and combinations
+ order_qt_date_complex1 """select * from TEST_TIMESTAMP where T1 <
'2013-05-01 00:00:00' AND T1 > '2013-01-01 00:00:00' order by ID;"""
+ order_qt_date_complex2 """select * from TEST_TIMESTAMP where T1
BETWEEN '2013-01-01 00:00:00' AND '2013-12-31 23:59:59' order by ID;"""
+ order_qt_date_complex3 """select * from TEST_TIMESTAMP where T2 >=
'2019-01-01 00:00:00' OR T3 >= '2019-01-01 00:00:00' order by ID;"""
+ order_qt_date_complex4 """select * from TEST_TIMESTAMP where NOT (T1 <
'2013-01-01 00:00:00' OR T1 > '2014-01-01 00:00:00') order by ID;"""
+
+ // Test NOT operator in date/time expressions
+ order_qt_time_not1 """select * from TEST_TIMESTAMP where NOT (T1 >
'2013-06-01 00:00:00') order by ID;"""
+ order_qt_time_not2 """select * from TEST_TIMESTAMP where NOT (T1
BETWEEN '2013-05-01 00:00:00' AND '2013-12-31 23:59:59') order by ID;"""
+ order_qt_time_not3 """select * from TEST_TIMESTAMP where T1 IS NOT
NULL AND NOT (T1 > '2013-10-01 00:00:00') order by ID;"""
+
+ // Test complex date/time comparisons and multiple time field
combinations
+ order_qt_time_multi1 """select * from TEST_TIMESTAMP where (T1 <
'2013-06-01 00:00:00' OR T2 > '2019-01-01 00:00:00') order by ID;"""
+ order_qt_time_multi2 """select * from TEST_TIMESTAMP where (T1 IS NOT
NULL AND T1 < '2013-06-01 00:00:00') OR (T2 IS NOT NULL AND T2 > '2019-01-01
00:00:00') order by ID;"""
+ order_qt_time_multi3 """select * from TEST_TIMESTAMP where NOT ((T1 IS
NULL) OR (T2 IS NULL AND T3 IS NULL)) order by ID;"""
+
+ // Test date comparisons using to_date function
+ order_qt_time_func1 """select * from TEST_TIMESTAMP where T1 >
str_to_date('2013-01-01 00:00:00', '%Y-%m-%d %H:%i:%s') order by ID;"""
+ order_qt_time_func2 """select * from TEST_TIMESTAMP where T1 BETWEEN
str_to_date('2013-01-01 00:00:00', '%Y-%m-%d %H:%i:%s') AND
str_to_date('2013-12-31 23:59:59', '%Y-%m-%d %H:%i:%s') order by ID;"""
+ order_qt_time_func3 """select * from TEST_TIMESTAMP where NOT (T1 <
str_to_date('2013-01-01 00:00:00', '%Y-%m-%d %H:%i:%s')) order by ID;"""
+
+ // Test complex AND/OR/NOT combinations with date/time comparisons
+ order_qt_time_complex1 """select * from TEST_TIMESTAMP where (T1 >
'2013-01-01 00:00:00' AND T1 < '2013-12-31 23:59:59') OR (T2 > '2018-01-01
00:00:00' AND T2 < '2020-01-01 00:00:00') order by ID;"""
+ order_qt_time_complex2 """select * from TEST_TIMESTAMP where NOT ((T1
< '2013-01-01 00:00:00' OR T1 > '2014-01-01 00:00:00') AND (T2 IS NULL OR T2 <
'2018-01-01 00:00:00')) order by ID;"""
+ order_qt_time_complex3 """select * from TEST_TIMESTAMP where (T1 IN
('2013-01-21 05:23:01', '2013-11-12 20:32:56') OR T2 IS NOT NULL) AND NOT (T3
IS NULL AND T4 IS NULL) order by ID;"""
+
+ // Test deeply nested date/time conditions
+ order_qt_nested1 """select * from TEST_TIMESTAMP where NOT (NOT (T1 >
'2013-01-01 00:00:00')) order by ID;"""
+ order_qt_nested2 """select * from TEST_TIMESTAMP where NOT ((T1 IS
NULL OR T1 > '2013-10-01 00:00:00') AND (T2 IS NULL OR T2 < '2019-01-01
00:00:00')) order by ID;"""
+
+ // Test date format edge cases
+ order_qt_edge1 """select * from TEST_TIMESTAMP where T1 = '2013-01-21
05:23:01' order by ID;"""
+ order_qt_edge2 """select * from TEST_TIMESTAMP where T2 = '2019-11-12
20:33:57.999' order by ID;"""
+ order_qt_edge3 """select * from TEST_TIMESTAMP where T3 = '2019-11-12
20:33:57.999998' order by ID;"""
+
+ // Test CURDATE() function and CAST combinations
+ order_qt_curdate1 """select * from TEST_TIMESTAMP where ID = 1 OR (T2
>= CAST(CURDATE() AS DATETIME) AND T1 >= CAST(CURDATE() AS DATETIME) AND T3 NOT
IN (CAST(CURDATE() AS DATETIME))) order by ID;"""
+ order_qt_curdate2 """select * from TEST_TIMESTAMP where T1 >=
CAST(CURDATE() AS DATETIME) order by ID;"""
+ order_qt_curdate3 """select * from TEST_TIMESTAMP where T1 IN
(CAST(CURDATE() AS DATETIME), CAST(CURDATE() + INTERVAL 1 DAY AS DATETIME))
order by ID;"""
+ order_qt_curdate4 """select * from TEST_TIMESTAMP where T2 NOT IN
(CAST(CURDATE() AS DATETIME), CAST(CURDATE() + INTERVAL 1 DAY AS DATETIME))
order by ID;"""
+
+ // Test complex nested conditions with date functions
+ order_qt_complex1 """select * from TEST_TIMESTAMP where (T1 <
CAST(CURDATE() AS DATETIME) OR T2 > CAST(CURDATE() AS DATETIME)) AND NOT (T3 =
CAST(CURDATE() AS DATETIME)) order by ID;"""
+ order_qt_complex2 """select * from TEST_TIMESTAMP where NOT ((T1 <
CAST(CURDATE() AS DATETIME) AND T2 IS NULL) OR (T3 > CAST(CURDATE() AS
DATETIME))) order by ID;"""
+
+ // Test CAST, CURDATE with multiple time conditions
+ order_qt_complex3 """select * from TEST_TIMESTAMP where (ID = 1 AND T1
>= CAST(CURDATE() AS DATETIME)) OR (ID = 2 AND T2 BETWEEN CAST(CURDATE() AS
DATETIME) AND CAST(CURDATE() + INTERVAL 7 DAY AS DATETIME)) order by ID;"""
+
+ // Test mixed date functions and string dates
+ order_qt_mixed """select * from TEST_TIMESTAMP where T1 >=
CAST(CURDATE() AS DATETIME) AND T2 < '2023-01-01 00:00:00' order by ID;"""
+
// test nvl
explain {
sql("SELECT * FROM STUDENT WHERE nvl(score, 0) < 95;")
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]