[BUG] ODPS 解析连续 INNER JOIN 时将 INNER 错误识别为别名
Author: cymoftCreated Aug 30, 2026Updated Aug 30, 2026
Database Type
MaxCompute / ODPS
Database Version
不适用(纯离线 SQL 解析和序列化,无需数据库连接)
Druid Version
1.2.28(Maven Central 官方构件 com.alibaba:druid:1.2.28)
JDK Version
OpenJDK 21.0.11
Error SQL
SELECT c.id
FROM context_table c
INNER JOIN loan_table l ON c.id = l.id
INNER JOIN adjust_table a ON l.id = a.idTestcase Code
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
public class OdpsInnerJoinTest {
public static void main(String[] args) {
String sql =
"SELECT c.id " +
"FROM context_table c " +
"INNER JOIN loan_table l ON c.id = l.id " +
"INNER JOIN adjust_table a ON l.id = a.id";
SQLSelectStatement statement = (SQLSelectStatement)
SQLUtils.parseSingleStatement(sql, DbType.odps);
SQLJoinTableSource outer = (SQLJoinTableSource)
statement.getSelect().getQueryBlock().getFrom();
SQLJoinTableSource inner = (SQLJoinTableSource) outer.getLeft();
System.out.println(SQLUtils.toOdpsString(statement));
System.out.println("outer: type=" + outer.getJoinType()
+ ", alias=" + outer.getAlias());
System.out.println("inner: type=" + inner.getJoinType()
+ ", alias=" + inner.getAlias());
}
}Stacktrace Info
没有抛出异常。解析器静默生成了语义错误的 AST。
Error Info
实际结果
序列化后 SQL 被错误改写为:
SELECT c.id
FROM context_table c
INNER JOIN loan_table l ON c.id = l.id AS INNER
JOIN adjust_table a ON l.id = a.idAST 结果:
outer: type=JOIN, alias=null
inner: type=INNER_JOIN, alias=INNER预期结果
- 两个 Join 节点的类型都应为
INNER_JOIN。 - 两个 Join 节点都不应具有
INNER别名。 - 序列化结果不应凭空增加
AS INNER。 - 对合法 ODPS SQL 执行“解析 -> 序列化”不应改变 Join 语义。
影响范围
问题不只发生在两个连续的 INNER JOIN 之间。LEFT JOIN、RIGHT JOIN、FULL JOIN 等 Join 后面紧跟显式 INNER JOIN 时,也可能把后一个 INNER 错误设置为前一个 Join 节点的别名。
由于错误结果可以再次被同一个解析器接受,使用同一解析器进行二次语法校验无法发现该语义损坏。
初步根因
SQLSelectParser.parseTableSourceRest() 在判断当前 Token 是下一个 Join 的边界还是表别名时,对 LEFT、RIGHT、FULL 做了向前查看,但没有对 INNER 做同等处理:
随后回退到别名解析,而 SQLParser.alias() / tableAlias() 允许把 Token.INNER 作为别名:
因此,后一个 INNER JOIN 中的 INNER 被前一个表源或 Join 节点消费为别名,只剩下 JOIN 被继续解析为普通 Join。
建议回归用例
INNER JOIN后跟INNER JOINLEFT/RIGHT/FULL JOIN后跟INNER JOIN- 三个连续的
INNER JOIN - 验证“解析 -> 序列化 -> 再解析”前后的 Join 类型及别名保持一致
- 验证序列化结果不会引入
AS INNER
该问题已使用 Maven Central 官方 com.alibaba:druid:1.2.28 稳定复现。检查当前 1.2.29-SNAPSHOT 的 master 源码(commit fa8dc9912637a2f729eef9f55356621fec18d40e),相关解析逻辑仍然存在。
Source: alibaba/druid