跳转到内容

JQuick-Sql

JQuick-Sql 是面向 Java 应用的 SQL AST 解析与逻辑改写工具包。它把 SQL 文本解析为强类型 AST,转换为逻辑计划与物理计划,再通过基于规则的重写器优化,最后在你注册的数据集上执行 —— 不需要数据库服务、不需要 JDBC 驱动,也不需要用正则去切分 SQL 字符串。

整个库围绕四个阶段构建:

  • Parse(解析):SQL 文本经词法、语法分析生成语法树,再转换为强类型 AST(JQuickQueryNode 与 30 余种节点类型)。
  • Transform(改写):AST 转换为逻辑计划,经 12 条优化规则重写,再下降为物理计划。
  • Assemble(拼装):SQL 语句可维护在 XML 中,通过 #{param} 占位符绑定到 Java 接口。
  • Execute(执行):计划在嵌入式引擎中运行,数据来自你注册的数据集(内存行或其他 JQuick 组件产出的行)。
场景 JQuick-Sql 能做什么
遗留系统 SQL 拆分 把存量语句解析为 AST,按子句遍历拆分,而不是按字符串查找
跨数据库兼容 单一语法、单一 AST,方言差异在 AST / 计划层处理,而非零散文本替换
SQL 审计与治理 以强类型节点检查表、列、谓词与函数调用,无需连上真实数据库
动态条件拼装 XML 驱动 SQL,#{param} 占位符绑定 Java 接口方法
进程内分析 注册数据集即可跑 SELECT / JOIN / GROUP BY,无需部署数据库

解析器

  • 强类型 AST 模型,30 余种节点类:select 规格、from / join / where / group by / having / order by / limit 子句、集合运算、子查询、CTE、case-when、函数调用。
  • 基于 Visitor 的遍历(JQuickSQLVisitor、JQuickSQLCommonVisistor、JQuickSQLSelectSpecVisitor)与 Listener API,便于自定义分析。
  • JQuickSQLExecutor:一行代码把 SQL 字符串变成 JQuickQueryNode AST。

逻辑改写

  • AST → 逻辑计划转换(JQuickASTToLogicalPlanVisitor)。
  • 12 条基于规则的优化实现:谓词下推、投影下推、投影合并、过滤合并、冗余过滤消除、常量折叠、表达式化简、limit 下推、聚合下推、连接重排、子查询转连接、分布式优化。
  • 逻辑计划 → 物理计划下降(JQuickPhysicalPlanGenerator)。
  • 16 种物理节点:表扫描、过滤、投影、values、empty、排序、Top-N、limit、哈希连接、嵌套循环连接、哈希聚合、exchange、集合运算、递归 union、窗口与抽象基类。

执行

  • JQuickSQL 门面提供嵌入式模式:JQuickSQL.embedded() / embedded(n) / builder()。
  • Fragmenter 把物理计划切分为分片,coordinator 负责派发与收集结果。
  • 表达式求值覆盖比较、逻辑、算术、LIKE / REGEXP、IN、BETWEEN、CASE WHEN、子查询与函数调用。
  • 可扩展函数 SPI,自定义函数通过 ServiceLoader 自动发现。

集成

  • iBatis 风格的 XML 动态 SQL 代理:SQL 写在 XML 中,接口由 JDK 动态代理实现,#{param} 传参。
  • 随包提供 DTD 用于校验 XML(paohaijiao/dtd/Jquick-sql.dtd)。
  • 执行层的 Protobuf / gRPC 定义(jquick-sql-proto)。
<dependency>
<groupId>io.github.paohaijiao</groupId>
<artifactId>jquick-sql-runtime</artifactId>
<version>5.0.0</version>
</dependency>

jquick-sql-runtime 是入口产物,会传递引入 jquick-sql-query、jquick-sql-plan、jquick-sql-parser 与 jquick-sql-core。

package demo;
import com.github.paohaijiao.ast.JQuickQueryNode;
import com.github.paohaijiao.executor.JQuickSQLExecutor;
/**
* 用途:把 SQL 字符串解析为强类型 AST。
* 场景:SQL 审计、语句拆分、自定义静态分析。
*/
public class ParseDemo {
public static void main(String[] args) {
JQuickSQLExecutor executor = new JQuickSQLExecutor();
JQuickQueryNode ast = executor.execute(
"SELECT dept, COUNT(*) FROM emp WHERE salary > 20000 GROUP BY dept HAVING COUNT(*) > 1"
);
System.out.println(ast);
}
}
package demo;
import com.github.paohaijiao.engine.JQuickSQL;
import com.github.paohaijiao.statement.JQuickColumnMeta;
import com.github.paohaijiao.statement.JQuickDataSet;
import com.github.paohaijiao.statement.JQuickRow;
import java.util.Arrays;
import java.util.List;
/**
* 用途:注册内存数据集并对其执行 SQL。
* 场景:单元测试、离线报表计算、进程内分析。
* 注意:嵌入式 worker 会占用本机 19001+ 端口,务必调用 shutdown()。
*/
public class QueryDemo {
public static void main(String[] args) {
JQuickSQL sql = JQuickSQL.embedded();
try {
List<JQuickColumnMeta> columns = Arrays.asList(
new JQuickColumnMeta("id", Integer.class, "users"),
new JQuickColumnMeta("name", String.class, "users"),
new JQuickColumnMeta("age", Integer.class, "users")
);
List<JQuickRow> rows = Arrays.asList(
row("id", 1, "name", "Alice", "age", 25),
row("id", 2, "name", "Bob", "age", 30),
row("id", 3, "name", "Charlie", "age", 20)
);
sql.registerTable("users", columns, rows);
JQuickDataSet result = sql.execute(
"SELECT id, name, age FROM users WHERE age >= 25 ORDER BY age DESC"
);
result.printTable();
System.out.println("rows: " + result.size());
} finally {
sql.shutdown();
}
}
private static JQuickRow row(Object... kv) {
JQuickRow r = new JQuickRow();
for (int i = 0; i < kv.length; i += 2) {
r.put((String) kv[i], kv[i + 1]);
}
return r;
}
}
package demo;
import com.github.paohaijiao.ast.JQuickQueryNode;
import com.github.paohaijiao.ast.JQuickSelectClauseNode;
import com.github.paohaijiao.ast.JQuickSelectExpressionNode;
import com.github.paohaijiao.executor.JQuickSQLExecutor;
/**
* 用途:解析 SQL 并通过强类型访问器检查 AST。
* 场景:SQL 审计 —— 在语句进入数据库之前统计子句使用情况。
*/
public class AstAuditDemo {
public static void main(String[] args) {
JQuickQueryNode ast = new JQuickSQLExecutor().execute(
"SELECT u.name, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.amount > 100"
);
JQuickSelectExpressionNode expression = ast.getSelectStatement().getSelectExpression();
for (JQuickSelectClauseNode clause : expression.getDataSetOp().getSelectClauses()) {
System.out.println("nodeType : " + clause.getNodeType());
System.out.println("distinct : " + clause.isDistinct());
System.out.println("fromClause : " + (clause.getFromClause() != null));
System.out.println("joinClauses : " + clause.getJoinClauses().size());
System.out.println("whereClause : " + (clause.getWhereClause() != null));
System.out.println("groupByClause : " + (clause.getGroupByClause() != null));
System.out.println("orderByClause : " + (clause.getOrderByClause() != null));
}
}
}

SQL 写在 XML 中,接口方法调用时绑定 #{param},风格与 iBatis 一致。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqls PUBLIC "-//PAOHAIJIAO//DTD API JAVA 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-sql.dtd">
<sqls namespace="demo.XmlProxyDemo.UserService">
<sql name="topUsers" returnClass="java.util.List">
select id, name, age from users order by age desc limit #{limit}
</sql>
</sqls>
package demo;
import com.github.paohaijiao.domain.JQuickTable;
import com.github.paohaijiao.statement.JQuickColumnMeta;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.JQuickJavaXmlParseFactory;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.param.Param;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class XmlProxyDemo {
public interface UserService {
List<JQuickRow> topUsers(@Param("limit") Integer limit);
}
public static void main(String[] args) {
List<JQuickColumnMeta> columns = Arrays.asList(
new JQuickColumnMeta("id", Integer.class, "users"),
new JQuickColumnMeta("name", String.class, "users"),
new JQuickColumnMeta("age", Integer.class, "users")
);
List<JQuickRow> rows = Collections.singletonList(
row("id", 1, "name", "Alice", "age", 25)
);
List<JQuickTable> tables = Collections.singletonList(
new JQuickTable("users", columns, rows)
);
JQuickJavaXmlParseFactory handler = new JQuickJavaXmlParseFactory(tables);
JQuickFactory factory = new JQuickXmlFactory(handler, "jquick-sql.xml");
UserService service = factory.createApi(UserService.class);
service.topUsers(2).forEach(System.out::println);
}
private static JQuickRow row(Object... kv) {
JQuickRow r = new JQuickRow();
for (int i = 0; i < kv.length; i += 2) {
r.put((String) kv[i], kv[i + 1]);
}
return r;
}
}