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 字符串变成JQuickQueryNodeAST。
逻辑改写
- 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>implementation 'io.github.paohaijiao:jquick-sql-runtime:5.0.0'jquick-sql-runtime 是入口产物,会传递引入 jquick-sql-query、jquick-sql-plan、jquick-sql-parser 与 jquick-sql-core。
1. 解析 SQL 为强类型 AST
Section titled “1. 解析 SQL 为强类型 AST”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); }}2. 在进程内执行查询
Section titled “2. 在进程内执行查询”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; }}遍历 AST 检查各子句
Section titled “遍历 AST 检查各子句”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)); } }}XML 动态 SQL
Section titled “XML 动态 SQL”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; }}