Эта статья в основном посвящена использованию и подробно описывает, как анализировать оператор SQL и расширенные операции, такие как перезапись SQL.Синтаксический анализ SQL широко используется в области распределенных данных, таких как модуль маршрутизации подбазы данных и подтаблицы. компонентов, для разбора бизнес-SQL, извлечения условных маршрутов. Типичными представителями являются Mycat и так далее. Как работать, включает в себя требования к разбору или переписыванию SQL, основные требования этой статьи должны быть решены.
Содержимое анализатора SQL в основном состоит из трех частей:
1、Parser
a、词法分析
b、语法分析
2、AST(Abstract Syntax Tree,抽象语法树)
3、Visitor:遍历SQL元素
Введение в АСТ
В информатике,абстрактное синтаксическое дерево(AbstractSyntaxTри, АСТ), или простосинтаксическое дерево(Синтаксическое дерево) представляет собой абстрактное представление грамматической структуры исходного кода.Druid также анализирует SQL.Следует определенным правилам для анализа и построения SQL в синтаксическое дерево AST.
Основная функция Parser — генерировать AST.Парсер в основном состоит из двух частей: лексического анализа и синтаксического анализа.
После того, как Друид получает запрос типа select a, b из таблицы userTable, где user_id = 10, ему необходимо разобрать слово и записать позицию слова, на этапе лексического анализа нет необходимости понимать значение этого SQL Профессиональный термин Лексер
После того, как лексический анализ завершен, это грамматический анализ, который используется для уточнения значения SQL, Например, слово «имя», мы можем знать, что это слово состоит из букв п, а, м и е. Если мы не знаем значения, это не практично.Значение, но знать, что оно означает, это то, что должен делать грамматический анализ.Результатом грамматического анализа является прояснение значения слова.
AST — это только представление семантики, но как выразить эту семантику, вам нужно посетить этот AST, чтобы увидеть, что он означает. Обычно просматривают синтаксическое дерево и используют режим ПОСЕТИТЕЛЯ для обхода, начиная с корневого узла до последнего конечного узла.В процессе обхода информация непрерывно собирается в контекст.После завершения всего процесса обхода выполняется обход дерева. выраженное грамматическое значение было сохранено в контексте. Иногда одного обхода недостаточно, и нужен второй обход. Способ обхода, обход в ширину самый распространенный, или без посетителя, мы знаем структуру AST и вручную пройти по структуре AST не проблема, но очень громоздко, я думаю разница между ручным обходом и использование посетителя аналогично разнице между использованием JQuery и JavaScript для реализации одной и той же функции, иногда для реализации функции с использованием посетителя может потребоваться дюжина строк кода, и для прохождения могут потребоваться сотни строк кода. Заявление о себе, которое является личным пониманием.
Создайте полное абстрактное синтаксическое дерево AST с помощью Parser.
2. Тип узла AST в Druid SQL
Существует три основных типа часто используемых узлов AST: SQLObject, SQLExpr и SQLStatement.Наиболее часто используется SQLStatement, а его подклассы — DruidSelectStatement, DruidInsertStatement, DruidUpdateStatement и т. д.
public interface SQLStatement extends SQLObject {}
public interface SQLObject {}
public interface SQLExpr extends SQLObject, Cloneable {}
2.1, обычный SQLExpr
package com.alibaba.druid.sql.ast.expr;
// SQLName是一种的SQLExpr的Expr,包括SQLIdentifierExpr、SQLPropertyExpr等
public interface SQLName extends SQLExpr {}
// 例如 ID = 3 这里的ID是一个SQLIdentifierExpr
class SQLIdentifierExpr implements SQLExpr, SQLName {
String name;
}
// 例如 A.ID = 3 这里的A.ID是一个SQLPropertyExpr
class SQLPropertyExpr implements SQLExpr, SQLName {
SQLExpr owner;
String name;
}
// 例如 ID = 3 这是一个SQLBinaryOpExpr
// left是ID (SQLIdentifierExpr)
// right是3 (SQLIntegerExpr)
class SQLBinaryOpExpr implements SQLExpr {
SQLExpr left;
SQLExpr right;
SQLBinaryOperator operator;
}
// 例如 select * from where id = ?,这里的?是一个SQLVariantRefExpr,name是'?'
class SQLVariantRefExpr extends SQLExprImpl {
String name;
}
// 例如 ID = 3 这里的3是一个SQLIntegerExpr
public class SQLIntegerExpr extends SQLNumericLiteralExpr implements SQLValuableExpr {
Number number;
// 所有实现了SQLValuableExpr接口的SQLExpr都可以直接调用这个方法求值
@Override
public Object getValue() {
return this.number;
}
}
// 例如 NAME = 'jobs' 这里的'jobs'是一个SQLCharExpr
public class SQLCharExpr extends SQLTextLiteralExpr implements SQLValuableExpr{
String text;
}
2.2, общий SQLStatement
Наиболее часто используемым оператором является, конечно, SELECT/UPDATE/DELETE/INSERT.
package com.alibaba.druid.sql.ast.statement;
class SQLSelectStatement implements SQLStatement {
SQLSelect select;
}
class SQLUpdateStatement implements SQLStatement {
SQLExprTableSource tableSource;
List<SQLUpdateSetItem> items;
SQLExpr where;
}
class SQLDeleteStatement implements SQLStatement {
SQLTableSource tableSource;
SQLExpr where;
}
class SQLInsertStatement implements SQLStatement {
SQLExprTableSource tableSource;
List<SQLExpr> columns;
SQLSelect query;
}
2.3, общий SQLTableSource
Общие источники SQLTableSource включают SQLExprTableSource, SQLJoinTableSource, SQLSubqueryTableSource, SQLWithSubqueryClause.Entry.
class SQLTableSourceImpl extends SQLObjectImpl implements SQLTableSource {
String alias;
}
// 例如 select * from emp where i = 3,这里的from emp是一个SQLExprTableSource
// 其中expr是一个name=emp的SQLIdentifierExpr
class SQLExprTableSource extends SQLTableSourceImpl {
SQLExpr expr;
}
// 例如 select * from emp e inner join org o on e.org_id = o.id
// 其中left 'emp e' 是一个SQLExprTableSource,right 'org o'也是一个SQLExprTableSource
// condition 'e.org_id = o.id'是一个SQLBinaryOpExpr
class SQLJoinTableSource extends SQLTableSourceImpl {
SQLTableSource left;
SQLTableSource right;
JoinType joinType; // INNER_JOIN/CROSS_JOIN/LEFT_OUTER_JOIN/RIGHT_OUTER_JOIN/...
SQLExpr condition;
}
// 例如 select * from (select * from temp) a,这里第一层from(...)是一个SQLSubqueryTableSource
SQLSubqueryTableSource extends SQLTableSourceImpl {
SQLSelect select;
}
/*
例如
WITH RECURSIVE ancestors AS (
SELECT *
FROM org
UNION
SELECT f.*
FROM org f, ancestors a
WHERE f.id = a.parent_id
)
SELECT *
FROM ancestors;
这里的ancestors AS (...) 是一个SQLWithSubqueryClause.Entry
*/
class SQLWithSubqueryClause {
static class Entry extends SQLTableSourceImpl {
SQLSelect subQuery;
}
}
2.4, SQLSelect и SQLSelectQuery
SQLSelectStatement содержит SQLSelect, а SQLSelect содержит SQLSelectQuery, оба из которых состоят из отношений. SQLSelectQuery имеет два основных производных класса, SQLSelectQueryBlock и SQLUnionQuery.
class SQLSelect extends SQLObjectImpl {
SQLWithSubqueryClause withSubQuery;
SQLSelectQuery query;
}
interface SQLSelectQuery extends SQLObject {}
class SQLSelectQueryBlock implements SQLSelectQuery {
List<SQLSelectItem> selectList;
SQLTableSource from;
SQLExprTableSource into;
SQLExpr where;
SQLSelectGroupByClause groupBy;
SQLOrderBy orderBy;
SQLLimit limit;
}
class SQLUnionQuery implements SQLSelectQuery {
SQLSelectQuery left;
SQLSelectQuery right;
SQLUnionOperator operator; // UNION/UNION_ALL/MINUS/INTERSECT
}
2.5. SQLCreateTableStatement
Конструкция оператора таблицы содержит ряд методов, используемых для облегчения различных операций.
public class SQLCreateTableStatement extends SQLStatementImpl implements SQLDDLStatement, SQLCreateStatement {
SQLExprTableSource tableSource;
List<SQLTableElement> tableElementList;
Select select;
// 忽略大小写的查找SQLCreateTableStatement中的SQLColumnDefinition
public SQLColumnDefinition findColumn(String columName) {}
// 忽略大小写的查找SQLCreateTableStatement中的column关联的索引
public SQLTableElement findIndex(String columnName) {}
// 是否外键依赖另外一个表
public boolean isReferenced(String tableName) {}
}
3. Создайте абстрактное синтаксическое дерево (AST)
Сгенерируйте AST из входящего текста, а именно sql, используя парсер Mysql.
public SQLStatement parserSQL(String originSql) throws SQLSyntaxErrorException {
SQLStatementParser parser = new MySqlStatementParser(originSql);
/**
* thrown SQL SyntaxError if parser error
*/
try {
List<SQLStatement> list = parser.parseStatementList();
if (list.size() > 1) {
throw new SQLSyntaxErrorException("MultiQueries is not supported,use single query instead ");
}
return list.get(0);
} catch (Exception t) {
LOGGER.info("routeNormalSqlWithAST", t);
if (t.getMessage() != null) {
throw new SQLSyntaxErrorException(t.getMessage());
} else {
throw new SQLSyntaxErrorException(t);
}
}
}
Или сгенерируйте в соответствии с типом базы данных, Druid генерирует AST в соответствии с различными типами баз данных.
/**
* @author Qi.qingshan
*/
public class SqlParser {
public static void main(String[] args) throws SQLSyntaxErrorException {
String sql = "";
String dbType = "oracle";
SQLStatement statement = parser(sql, dbType);
}
public static SQLStatement parser(String sql,String dbType) throws SQLSyntaxErrorException {
List<SQLStatement> list = SQLUtils.parseStatements(sql, dbType);
if (list.size() > 1) {
throw new SQLSyntaxErrorException("MultiQueries is not supported,use single query instead ");
}
return list.get(0);
}
}
После того, как синтаксическое дерево AST сгенерировано, мы можем извлечь то, что нам нужно в соответствии с составом синтаксического дерева, например, мы хотим извлечь имя таблицы оператора select name,id из acct, где id = 10, и заменить таблицу имя в выписке с acct_1
/**
* @author Qi.qingshan
*/
public class SqlParser {
public static void main(String[] args) throws SQLSyntaxErrorException {
String sql = "select name ,id from acct where id =10";
String dbType = "mysql";
System.out.println("原始SQL 为 : "+sql);
SQLSelectStatement statement = (SQLSelectStatement) parser(sql, dbType);
SQLSelect select = statement.getSelect();
SQLSelectQueryBlock query = (SQLSelectQueryBlock) select.getQuery();
SQLExprTableSource tableSource = (SQLExprTableSource) query.getFrom();
String tableName = tableSource.getExpr().toString();
System.out.println("获取的表名为 tableName :" + tableName);
//修改表名为acct_1
tableSource.setExpr("acct_1");
System.out.println("修改表名后的SQL 为 : [" + statement.toString() +"]");
}
public static SQLStatement parser(String sql,String dbType) throws SQLSyntaxErrorException {
List<SQLStatement> list = SQLUtils.parseStatements(sql, dbType);
if (list.size() > 1) {
throw new SQLSyntaxErrorException("MultiQueries is not supported,use single query instead ");
}
return list.get(0);
}
}
Реализация функции разделения таблицы в промежуточном программном обеспечении базы данных в основном основана на этом принципе.Лучше всего клонировать новый объект для изменения оператора.Метод клонирования был предоставлен внутренне.
4. Посетитель
Druid предоставляет множество посетителей по умолчанию, которые могут удовлетворить основные потребности.Если посетители по умолчанию не соответствуют требованиям, вы можете реализовать собственный посетитель самостоятельно.
Например, мы хотим посчитать, какие таблицы участвуют в следующем SQL select name, id, select money from user from acct, где id =10, если нам не нужен посетитель и мы сами пройдём через AST, этого можно добиться, но это очень громоздко, схема внутреннего устройства ниже
Используйте реализацию посетителя по умолчанию, чтобы извлечь имя таблицы в операторе SQL, или легко использовать пользовательскую реализацию.
/**
* @author Qi.qingshan
* @date 20190526
*/
public class SqlParser {
public static void main(String[] args) throws SQLSyntaxErrorException {
String sql = "select money from acct where id =10";
String dbType = "mysql";
System.out.println("原始SQL 为 : "+sql);
SQLSelectStatement statement = (SQLSelectStatement) parser(sql, dbType);
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
statement.accept(visitor);
System.out.println(visitor.getTables().toString());
}
public static SQLStatement parser(String sql,String dbType) throws SQLSyntaxErrorException {
List<SQLStatement> list = SQLUtils.parseStatements(sql, dbType);
if (list.size() > 1) {
throw new SQLSyntaxErrorException("MultiQueries is not supported,use single query instead ");
}
return list.get(0);
}
}