jsqlparser基本使用

9,634 阅读4分钟

JSqlParser基本使用

1、表达式评估示例

这是一个使用 JSqlParser 进行加法和乘法运算的非常简单的表达式求值器。 ExpressionDeParser 用于遍历和解释解析树。

import java.util.Stack;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
import net.sf.jsqlparser.expression.operators.arithmetic.Multiplication;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;

public class SimpleEvaluateExpr {
    
    public static void main( String[] args ) throws JSQLParserException {
	evaluate("4+5*6");
	evaluate("4*5+6");
	evaluate("4*(5+6)");
	evaluate("4*(5+6)*(2+3)");
    }

    static void evaluate(String expr) throws JSQLParserException {
	final Stack<Long> stack = new Stack<Long>();
	System.out.println("expr=" + expr);
	Expression parseExpression = CCJSqlParserUtil.parseExpression(expr);
	ExpressionDeParser deparser = new ExpressionDeParser() {
	    @Override
	    public void visit(Addition addition) {
		super.visit(addition); 
		
		long sum1 = stack.pop();
		long sum2 = stack.pop();
		
		stack.push(sum1 + sum2);
	    }

	    @Override
	    public void visit(Multiplication multiplication) {
		super.visit(multiplication); 
		
		long fac1 = stack.pop();
		long fac2 = stack.pop();
		
		stack.push(fac1 * fac2);
	    }

	    @Override
	    public void visit(LongValue longValue) {
		super.visit(longValue); 
		stack.push(longValue.getValue());
	    }
	};
	StringBuilder b = new StringBuilder();
	deparser.setBuffer(b);
	parseExpression.accept(deparser);
	
	System.out.println(expr + " = " + stack.pop() );
    }
}

这是结果输出:

expr=4+5*6
4+5*6 = 34
expr=4*5+6
4*5+6 = 26
expr=4*(5+6)
4*(5+6) = 44
expr=4*(5+6)*(2+3)
4*(5+6)*(2+3) = 220

2、SQL分析示例

2.1、检索 SQL 语句的所有选定项

这里的任务是构建一个映射,其中别名是键,该别名的表达式是一个映射条目的值。

Select stmt = (Select) CCJSqlParserUtil.parse("SELECT col1 AS a, col2 AS b, col3 AS c FROM table WHERE col1 = 10 AND col2 = 20 AND col3 = 30");
        
Map<String, Expression> map = new HashMap<>();        
for (SelectItem selectItem : ((PlainSelect)stmt.getSelectBody()).getSelectItems()) {
    selectItem.accept(new SelectItemVisitorAdapter() {
        @Override
        public void visit(SelectExpressionItem item) {
            map.put(item.getAlias().getName(), item.getExpression());
        }
    });
}
    
System.out.println("map " + map);

SQL 的选定项可以是不同的类型。我们现在对所谓的 SelectExpressionItems 感兴趣。这些是由可选的别名命名的表达式组成的那些。因此,我们使用 SelectItemVisitor 过滤这些项目。直接从我们的 SelectExpressionItem 我们可以得到别名和表达式。

这将返回:

map {a=col1, b=col2, c=col3}

3、SQL构建示例

3.1、构建一个简单的select

Select select = SelectUtils.buildSelectFromTable(new Table("mytable"));

select contains 现在从 mytable 中选择 *。

Select select = SelectUtils.buildSelectFromTableAndExpressions(new Table("mytable"), new Column("a"), new Column("b"));

select contains 现在从 mytable 中select a、b。

或者更简单,如果您不想构建正确的表达式树,您可以提供简单的文本表达式,这些表达式将被解析并包含在您的select中。

Select select = SelectUtils.buildSelectFromTableAndExpressions(new Table("mytable"), "a+b", "test");

3.2、扩展一个简单的insert

Insert insert = (Insert)CCJSqlParserUtil.parse("insert into mytable (col1) values (1)");
System.out.println(insert.toString());

//adding a column
insert.getColumns().add(new Column("col2"));

//adding a value using a visitor
insert.getItemsList().accept(new ItemsListVisitor() {

    public void visit(SubSelect subSelect) {
	throw new UnsupportedOperationException("Not supported yet.");
    }

    public void visit(ExpressionList expressionList) {
	expressionList.getExpressions().add(new LongValue(5));
    }

    public void visit(MultiExpressionList multiExprList) {
	throw new UnsupportedOperationException("Not supported yet.");
    }
});
System.out.println(insert.toString());

//adding another column
insert.getColumns().add(new Column("col3"));

//adding another value (the easy way)
((ExpressionList)insert.getItemsList()).getExpressions().add(new LongValue(10));

System.out.println(insert.toString());

3.3、替换字符串值

有人想发布一些 SQL,但想打乱所有具体值。所以这里有一个小例子来说明如何实现这一点。简而言之,访问者扫描整个树,找到所有 StringValues 并将当前值替换为 XXXX。

String sql ="SELECT NAME, ADDRESS, COL1 FROM USER WHERE SSN IN ('11111111111111', '22222222222222');";
Select select = (Select) CCJSqlParserUtil.parse(sql);

//Start of value modification
StringBuilder buffer = new StringBuilder();
ExpressionDeParser expressionDeParser = new ExpressionDeParser() {

    @Override
    public void visit(StringValue stringValue) {
	this.getBuffer().append("XXXX");
    }
    
};
SelectDeParser deparser = new SelectDeParser(expressionDeParser,buffer );
expressionDeParser.setSelectVisitor(deparser);
expressionDeParser.setBuffer(buffer);
select.getSelectBody().accept(deparser);
//End of value modification


System.out.println(buffer.toString());
//Result is: SELECT NAME, ADDRESS, COL1 FROM USER WHERE SSN IN (XXXX, XXXX)

3.4、更一般地替换语句中的字符串值

这是一种更通用的方法,用于替换各种语句中的字符串和长值。

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
import net.sf.jsqlparser.util.deparser.SelectDeParser;
import net.sf.jsqlparser.util.deparser.StatementDeParser;

public class ReplaceColumnValues {

    static class ReplaceColumnAndLongValues extends ExpressionDeParser {

        @Override
        public void visit(StringValue stringValue) {
            this.getBuffer().append("?");
        }

        @Override
        public void visit(LongValue longValue) {
            this.getBuffer().append("?");
        }
    }

    public static String cleanStatement(String sql) throws JSQLParserException {
        StringBuilder buffer = new StringBuilder();
        ExpressionDeParser expr = new ReplaceColumnAndLongValues();

        SelectDeParser selectDeparser = new SelectDeParser(expr, buffer);
        expr.setSelectVisitor(selectDeparser);
        expr.setBuffer(buffer);
        StatementDeParser stmtDeparser = new StatementDeParser(expr, selectDeparser, buffer);

        Statement stmt = CCJSqlParserUtil.parse(sql);

        stmt.accept(stmtDeparser);
        return stmtDeparser.getBuffer().toString();
    }

    public static void main(String[] args) throws JSQLParserException {
        System.out.println(cleanStatement("SELECT 'abc', 5 FROM mytable WHERE col='test'"));
        System.out.println(cleanStatement("UPDATE table1 A SET A.columna = 'XXX' WHERE A.cod_table = 'YYY'"));
        System.out.println(cleanStatement("INSERT INTO example (num, name, address, tel) VALUES (1, 'name', 'test ', '1234-1234')"));
        System.out.println(cleanStatement("DELETE FROM table1 where col=5 and col2=4"));
    }
}

它输出:

SELECT ?, ? FROM mytable WHERE col = ?
UPDATE table1 A SET A.columna = ? WHERE A.cod_table = ?
INSERT INTO example (num, name, address, tel) VALUES (?, ?, ?, ?)
DELETE FROM table1 WHERE col = ? AND col2 = ?

3.5、替换语句的 where 子句中的列名

SELECT col1 AS a, col2 AS b, col3 AS c FROM table WHERE col_1 = 10 AND col_2 = 20 AND col_3 = 30

在此 sql 中的所有列名中,应从中删除下划线。

Select stmt = (Select) CCJSqlParserUtil.parse("SELECT col1 AS a, col2 AS b, col3 AS c FROM table WHERE col_1 = 10 AND col_2 = 20 AND col_3 = 30");
System.out.println("before " + stmt.toString());
    
((PlainSelect)stmt.getSelectBody()).getWhere().accept(new ExpressionVisitorAdapter() {
    @Override
    public void visit(Column column) {
        column.setColumnName(column.getColumnName().replace("_", ""));
    }
});
    
System.out.println("after " + stmt.toString());

有趣的部分从将 getSelectBody() 转换为 PlainSelect 开始。如果有更复杂的 sql,也应该在这里使用访问者。现在使用 ExpressionVisitor 遍历来自 getWhere 的表达式。 ExpressionVisitorAdapter 是 ExpressionVisitor 的完整实现。因此,我们只需要重写 visit(Column column) 方法即可访问 where 表达式中的所有 Column 实例。替换本身是微不足道的。

4、SQL解析示例

4.1、简单的 SQL 解析

Statement stmt = CCJSqlParserUtil.parse("SELECT * FROM tab1");

从 stmt 开始,您可以深入了解解析结果。

4.2、SQL脚本解析

Statements stmt = CCJSqlParserUtil.parseStatements("SELECT * FROM tab1; SELECT * FROM tab2");

从 stmt 开始,您可以使用解析结果。

4.3、简单的表达式解析

Expression expr = CCJSqlParserUtil.parseExpression("a*(5+mycolumn)");

从 expr 开始,您可以使用解析结果。

4.4、括号引述

一些数据库使用括号引号作为标识符 (SqlServer):[mytable].[mycolumn]。由于 JSqlParser 的第 3 版括号被认为是每个默认数组寻址。

要使括号引号成为默认行为,您必须使用以下命令启用它:

CCJSqlParserUtil.parse("select * from mytable", parser -> parser.withSquareBracketQuotation(true));

例如查看#1001。

4.5、从 SQL 中提取表名

Statement statement = CCJSqlParserUtil.parse("SELECT * FROM MY_TABLE1");
Select selectStatement = (Select) statement;
TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
List<String> tableList = tablesNamesFinder.getTableList(selectStatement);

tableList 中是解析后的 SQL 语句的所有表名。表名查找器是 JSqlParser 访问者模式结构的一个示例。您可以使用访问者模式来遍历解析结果并对其进行计算。

4.6、将别名应用于所有表达式

Select select = (Select) CCJSqlParserUtil.parse("select a,b,c from test");
final AddAliasesVisitor instance = new AddAliasesVisitor();
select.getSelectBody().accept(instance);

结果,您将获得 SELECT a AS A1, b AS A2, c AS A3 FROM test。目前可以配置前缀。

4.7、将列或表达式添加到选择

Select select = (Select) CCJSqlParserUtil.parse("select a from mytable");
SelectUtils.addExpression(select, new Column("b"));

现在select包含 SELECT a, b FROM mytable。

4.8、可视化解析

有时您需要知道,JSqlParser 正在做什么来解析特殊的 SQL 语句。所以实现这一点的最简单方法是生成一个输出调试消息的解析器。

  1. 所以克隆 JSqlParser 存储库。

  2. 打开文件 JSqlParser.jjt。

  3. 编辑此文件中的选项部分。寻找以 DEBUG_ 开头的特殊选项。

    options{ ... DEBUG_PARSER=true; DEBUG_LOOKAHEAD=false ; DEBUG_TOKEN_MANAGER=false; ... }

  4. 构建解析器。

你会得到这样的输出:

Call:   Statement
  Call:   SingleStatement
    Call:   Select
      Call:   SelectBody
	Call:   PlainSelect
	  Consumed token: <"SELECT" at line 1 column 1>
	  Call:   SelectItemsList
	    Call:   SelectItem
	      Consumed token: <"*" at line 1 column 8>
	    Return: SelectItem
	  Return: SelectItemsList
	  Consumed token: <"FROM" at line 1 column 10>
	  Call:   FromItem
	    Call:   Table
	      Call:   RelObjectName
		Consumed token: <<S_IDENTIFIER>: "MYTABLE" at line 1 column 15>
	      Return: RelObjectName
	    Return: Table
	  Return: FromItem

5、SQL 验证示例

5.1、SQL 验证框架

从 4.0 开始,JSQLParser 框架包括一个验证框架。

验证框架映射了几种类型的验证,它们是接口 ValidationCapability 的实现。

目前存在以下实现:

  • ParseCapability: 检查是否可以解析语句(始终包含在 Validation#validate() 中)
  • FeatureSetValidation:
    • DatabaseType: 检查已解析的语句对于支持的数据库语法是否有效
    • Version: 检查已解析语句对于特定数据库版本是否有效。
    • FeaturesAllowed: 检查语句是否仅使用允许的语法元素。
  • DatabaseMetaDataValidation: 验证元数据,例如表、视图、列的名称是否存在

5.2、SQL 验证数据库类型/版本

检查已解析语句是否对所选数据库有效

String sql = "DROP INDEX IF EXISTS idx_tab2_id;";

// validate statement if it's valid for all given databases.
Validation validation = new Validation(Arrays.asList(DatabaseType.SQLSERVER, DatabaseType.MARIADB,
                DatabaseType.POSTGRESQL, DatabaseType.H2), sql);
List<ValidationError> errors = validation.validate();

检查已解析语句是否对特定数据库版本有效。

// validate statement if it's valid for all given databases.
Validation validation = new Validation(Arrays.asList(PostgresqlVersion.V10), sql);
List<ValidationError> errors = validation.validate();

5.3、SQL 验证功能允许

针对预定义的 FeaturesAllowed.DML 集进行验证

// validate against pre-defined FeaturesAllowed.DML set
String sql = "CREATE TABLE tab1 (id NUMERIC(10), val VARCHAR(30))";
Validation validation = new Validation(Arrays.asList(FeaturesAllowed.DML), sql);
List<ValidationError> errors = validation.validate();
// only DML is allowed, got error for using a DDL statement
log.error (errors);

针对预定义的 FeaturesAllowed.SELECT 集进行验证

String sql = "SELECT * FROM myview v JOIN secondview v2 ON v.id = v2.ref";
Validation validation = new Validation(Arrays.asList(FeaturesAllowed.SELECT), sql);
List<ValidationError> errors = validation.validate();
// no errors, select - statement is allowed
if (errors.isEmpty()) {
	// do something else with the parsed statements
	Statements statements = validation.getParsedStatements();
}

针对您自己的 FeatureSet 进行验证

FeaturesAllowed exec = new FeaturesAllowed("EXECUTE", Feature.execute).unmodifyable();

组合多个预定义的 FeaturesAllowed 集

FeaturesAllowed myAllowedFeatures = new FeaturesAllowed("myAllowedFeatures")
	.add (FeaturesAllowed.DDL, FeaturesAllowed.DML);

5.4、SQL 验证数据库MetaDataValidation

验证元数据,例如表、视图、列的名称是否存在

java.sql.Connection connection = ...;
String sql = "ALTER TABLE mytable ADD price numeric(10,5) not null";
Validation validation = new Validation(Arrays.asList(new JdbcDatabaseMetaDataCapability(connection, 	
// NamesLookup: Databases handle names differently
        NamesLookup.UPPERCASE)), sql);
List<ValidationError> errors = validation.validate();

// do something else with the parsed statements
Statements statements = validation.getParsedStatements();

// check for validation-errors
if (!errors.isEmpty()) {
    ...
}

5.5、具有多个 ValidationCapability 的 SQL 验证

java.sql.Connection connection = ...;
String sql = "ALTER TABLE mytable ADD price numeric(10,5) not null";
Validation validation = new Validation(Arrays.asList(
    DatabaseType.POSTGRESQL, 
    new JdbcDatabaseMetaDataCapability(connection,NamesLookup.LOWERCASE)), sql);
List<ValidationError> errors = validation.validate();    

5.6、验证已解析的语句:

Statement statement = ...;
ValidationContext context = Validation.createValidationContext(
    new FeatureConfiguration(), Arrays.asList(DatabaseType.POSTGRESQL));
Map<ValidationCapability, Set<ValidationException>> errors = 
    Validation.validate(statement, context);