Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,17 @@ void populate() {
registerOperator(AND, SqlStdOperatorTable.AND);
registerOperator(OR, SqlStdOperatorTable.OR);
registerOperator(NOT, SqlStdOperatorTable.NOT);
registerOperator(ADD, SqlStdOperatorTable.PLUS);

// Register ADD (+ symbol) for numeric addition
register(
Comment thread
ahkcs marked this conversation as resolved.
ADD,
(RexBuilder builder, RexNode... args) -> builder.makeCall(SqlStdOperatorTable.PLUS, args),
new PPLTypeChecker.PPLFamilyTypeChecker(SqlTypeFamily.NUMERIC, SqlTypeFamily.NUMERIC));

// Register ADD (+ symbol) for string concatenation
registerOperator(ADD, SqlStdOperatorTable.CONCAT);

// Register ADDFUNCTION for numeric addition only
registerOperator(ADDFUNCTION, SqlStdOperatorTable.PLUS);
registerOperator(SUBTRACT, SqlStdOperatorTable.MINUS);
registerOperator(SUBTRACTFUNCTION, SqlStdOperatorTable.MINUS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,4 +354,102 @@ public void testDependedLateralEval() {
+ "GROUP BY `DEPTNO`";
verifyPPLToSparkSQL(root, expectedSparkSql);
}

@Test
public void testEvalStringConcatenationWithPlus() {
Comment thread
ahkcs marked this conversation as resolved.
Outdated
String ppl =
"source=EMP | eval full_name = ENAME + ' ' + JOB | fields EMPNO, ENAME, JOB, full_name |"
+ " head 3";
RelNode root = getRelNode(ppl);
String expectedLogical =
"LogicalSort(fetch=[3])\n"
+ " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], full_name=[||(||($1, ' '),"
+ " $2)])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verifyLogical(root, expectedLogical);

String expectedSparkSql =
"SELECT `EMPNO`, `ENAME`, `JOB`, `ENAME` || ' ' || `JOB` `full_name`\n"
+ "FROM `scott`.`EMP`\n"
+ "LIMIT 3";
verifyPPLToSparkSQL(root, expectedSparkSql);
}

@Test
public void testEvalStringConcatenationWithNullableField() {
Copy link
Copy Markdown
Collaborator

@yuancu yuancu Aug 19, 2025

Choose a reason for hiding this comment

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

Can be removed since concatenating string with null operands is actually verified in IT

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed

// Test with COMM field which naturally contains nulls in the EMP table
// When COMM is null, concatenation should result in null per SQL standard
String ppl =
"source=EMP | eval "
+ "comm_desc = 'Commission: ' + CAST(COMM AS STRING) "
+ "| fields EMPNO, ENAME, COMM, comm_desc | head 5";
RelNode root = getRelNode(ppl);

String expectedLogical =
"LogicalSort(fetch=[5])\n"
+ " LogicalProject(EMPNO=[$0], ENAME=[$1], COMM=[$6], "
+ "comm_desc=[||('Commission: ':VARCHAR, NUMBER_TO_STRING($6))])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verifyLogical(root, expectedLogical);

String expectedSparkSql =
"SELECT `EMPNO`, `ENAME`, `COMM`, "
+ "'Commission: ' || `NUMBER_TO_STRING`(`COMM`) `comm_desc`\n"
+ "FROM `scott`.`EMP`\n"
+ "LIMIT 5";
verifyPPLToSparkSQL(root, expectedSparkSql);
}

@Test
public void testEvalStringConcatenationChained() {
// Test chained concatenation
String ppl =
"source=EMP | eval "
+ "description = ENAME + ' - ' + JOB + ' (Dept: ' + CAST(DEPTNO AS STRING) + ')' "
+ "| where EMPNO IN (7369, 7499) | fields EMPNO, description";
RelNode root = getRelNode(ppl);

String expectedLogical =
"LogicalProject(EMPNO=[$0], description=[$8])\n"
+ " LogicalFilter(condition=[SEARCH($0, Sarg[7369, 7499])])\n"
+ " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],"
+ " SAL=[$5], COMM=[$6], DEPTNO=[$7], description=[||(||(||(||(||($1, ' - ':VARCHAR),"
+ " $2), ' (Dept: ':VARCHAR), SAFE_CAST($7)), ')')])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verifyLogical(root, expectedLogical);

String expectedSparkSql =
"SELECT `EMPNO`, `description`\n"
+ "FROM (SELECT `EMPNO`, `ENAME`, `JOB`, `MGR`, `HIREDATE`, `SAL`, `COMM`, `DEPTNO`,"
+ " `ENAME` || ' - ' || `JOB` || ' (Dept: ' || SAFE_CAST(`DEPTNO` AS STRING) || ')'"
+ " `description`\n"
+ "FROM `scott`.`EMP`) `t`\n"
+ "WHERE `EMPNO` IN (7369, 7499)";
verifyPPLToSparkSQL(root, expectedSparkSql);
}

@Test
public void testEvalStringConcatenationMultipleExpressions() {
// Test multiple concatenation expressions in a single eval command
String ppl =
"source=EMP | eval "
+ "name_job = ENAME + ' - ' + JOB, "
+ "name_dept = ENAME + ' (Dept ' + CAST(DEPTNO AS STRING) + ')' "
+ "| fields EMPNO, name_job, name_dept | head 2";
RelNode root = getRelNode(ppl);

String expectedLogical =
"LogicalSort(fetch=[2])\n"
+ " LogicalProject(EMPNO=[$0], name_job=[||(||($1, ' - ':VARCHAR), $2)], "
+ "name_dept=[||(||(||($1, ' (Dept ':VARCHAR), SAFE_CAST($7)), ')')])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verifyLogical(root, expectedLogical);

String expectedSparkSql =
"SELECT `EMPNO`, `ENAME` || ' - ' || `JOB` `name_job`, "
+ "`ENAME` || ' (Dept ' || SAFE_CAST(`DEPTNO` AS STRING) || ')' `name_dept`\n"
+ "FROM `scott`.`EMP`\n"
+ "LIMIT 2";
verifyPPLToSparkSQL(root, expectedSparkSql);
}
}
Loading