浏览代码

!348 升级版本
Merge pull request !348 from AE86/dev

AE86 2 月之前
父节点
当前提交
3281947800
共有 28 个文件被更改,包括 40123 次插入49 次删除
  1. 1 1
      dbsyncer-biz/pom.xml
  2. 1 1
      dbsyncer-common/pom.xml
  3. 3 1
      dbsyncer-common/src/main/java/org/dbsyncer/common/config/AppConfig.java
  4. 1 1
      dbsyncer-connector/dbsyncer-connector-base/pom.xml
  5. 1 1
      dbsyncer-connector/dbsyncer-connector-elasticsearch/pom.xml
  6. 1 1
      dbsyncer-connector/dbsyncer-connector-file/pom.xml
  7. 1 1
      dbsyncer-connector/dbsyncer-connector-kafka/pom.xml
  8. 1 7
      dbsyncer-connector/dbsyncer-connector-mysql/pom.xml
  9. 5 5
      dbsyncer-connector/dbsyncer-connector-mysql/src/main/java/org/dbsyncer/connector/mysql/cdc/MySQLListener.java
  10. 1 7
      dbsyncer-connector/dbsyncer-connector-oracle/pom.xml
  11. 1 1
      dbsyncer-connector/dbsyncer-connector-postgresql/pom.xml
  12. 1 1
      dbsyncer-connector/dbsyncer-connector-sqlite/pom.xml
  13. 1 1
      dbsyncer-connector/dbsyncer-connector-sqlserver/pom.xml
  14. 1 1
      dbsyncer-connector/pom.xml
  15. 1 1
      dbsyncer-manager/pom.xml
  16. 1 6
      dbsyncer-parser/pom.xml
  17. 2 3
      dbsyncer-parser/src/main/java/org/dbsyncer/parser/ddl/impl/DDLParserImpl.java
  18. 1 1
      dbsyncer-plugin/pom.xml
  19. 6 1
      dbsyncer-sdk/pom.xml
  20. 39549 0
      dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/sqlparser/SimpleSqlParser.java
  21. 40 0
      dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/sqlparser/SimpleSqlParserTokenManager.java
  22. 495 0
      dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/util/SqlParserUtil.java
  23. 1 1
      dbsyncer-storage/pom.xml
  24. 1 1
      dbsyncer-web/pom.xml
  25. 3 2
      dbsyncer-web/src/main/java/org/dbsyncer/web/Version.java
  26. 1 1
      pom.xml
  27. 1 1
      version.cmd
  28. 1 1
      version.sh

+ 1 - 1
dbsyncer-biz/pom.xml

@@ -5,7 +5,7 @@
 	<parent>
         <artifactId>dbsyncer</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
 	<modelVersion>4.0.0</modelVersion>
 	<artifactId>dbsyncer-biz</artifactId>

+ 1 - 1
dbsyncer-common/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>dbsyncer-common</artifactId>

+ 3 - 1
dbsyncer-common/src/main/java/org/dbsyncer/common/config/AppConfig.java

@@ -48,7 +48,9 @@ public class AppConfig {
             copy.append("&copy;").append(LocalDate.now().getYear()).append(" ");
             copy.append(name);
             copy.append("(").append(version).append(")");
-            copy.append("<footer>Designed By <a href='https://gitee.com/ghi/dbsyncer' target='_blank' >AE86</a></footer>");
+            copy.append("<footer>Designed By <a href='https://gitee.com/ghi/dbsyncer' target='_blank' >");
+            copy.append(company);
+            copy.append("</a></footer>");
             this.copyright = copy.toString();
         }
         return copyright;

+ 1 - 1
dbsyncer-connector/dbsyncer-connector-base/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer-connector</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
dbsyncer-connector/dbsyncer-connector-elasticsearch/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer-connector</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
dbsyncer-connector/dbsyncer-connector-file/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer-connector</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
dbsyncer-connector/dbsyncer-connector-kafka/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer-connector</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 7
dbsyncer-connector/dbsyncer-connector-mysql/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer-connector</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -37,12 +37,6 @@
             <artifactId>mysql-binlog-connector-java</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>com.github.jsqlparser</groupId>
-            <artifactId>jsqlparser</artifactId>
-            <scope>provided</scope>
-        </dependency>
-
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-log4j2</artifactId>

+ 5 - 5
dbsyncer-connector/dbsyncer-connector-mysql/src/main/java/org/dbsyncer/connector/mysql/cdc/MySQLListener.java

@@ -16,7 +16,6 @@ import com.github.shyiko.mysql.binlog.event.UpdateRowsEventData;
 import com.github.shyiko.mysql.binlog.event.WriteRowsEventData;
 import com.github.shyiko.mysql.binlog.network.ServerException;
 import net.sf.jsqlparser.JSQLParserException;
-import net.sf.jsqlparser.parser.CCJSqlParserUtil;
 import net.sf.jsqlparser.statement.Statement;
 import net.sf.jsqlparser.statement.alter.Alter;
 import org.dbsyncer.common.QueueOverflowException;
@@ -33,6 +32,7 @@ import org.dbsyncer.sdk.listener.event.DDLChangedEvent;
 import org.dbsyncer.sdk.listener.event.RowChangedEvent;
 import org.dbsyncer.sdk.model.ChangedOffset;
 import org.dbsyncer.sdk.util.DatabaseUtil;
+import org.dbsyncer.sdk.util.SqlParserUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.util.Assert;
@@ -327,17 +327,17 @@ public class MySQLListener extends AbstractDatabaseListener {
             try {
                 // skip BEGIN
                 if (!StringUtil.equalsIgnoreCase("BEGIN", sql)) {
-                    return CCJSqlParserUtil.parse(sql);
+                    return SqlParserUtil.parse(sql);
                 }
             } catch (JSQLParserException e) {
-                logger.warn("不支持的ddl:{},标准的ddl请查看文档https://gitee.com/ghi/dbsyncer/wikis/%E6%93%8D%E4%BD%9C%E6%89%8B%E5%86%8C/%E8%A1%A8%E7%BB%93%E6%9E%84%E5%90%8C%E6%AD%A5", sql);
+                logger.warn("不支持的ddl:{}", sql);
             }
             return null;
         }
 
         private void parseAlter(QueryEventData data, Alter alter) {
             // ALTER TABLE `test`.`my_user` MODIFY COLUMN `name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL AFTER `id`
-            String tableName = StringUtil.replace(alter.getTable().getName(), "`", "");
+            String tableName = StringUtil.replace(alter.getTable().getName(), StringUtil.BACK_QUOTE, StringUtil.EMPTY);
             // databaseName 的取值不能为 QueryEventData#getDatabase, getDatabase 获取的是执行 SQL 语句时所在的数据库上下文,
             // 不是 alter 语句修改的表所在的数据库
             // 依次执行下面两条语句 use db1; alter table db2.my_user add column name varchar(128);
@@ -347,7 +347,7 @@ public class MySQLListener extends AbstractDatabaseListener {
             if (StringUtil.isBlank(databaseName)) {
                 databaseName = data.getDatabase();
             }
-            databaseName = StringUtil.replace(databaseName, "`", "");
+            databaseName = StringUtil.replace(databaseName, StringUtil.BACK_QUOTE, StringUtil.EMPTY);
             if (isFilterTable(databaseName, tableName)) {
                 trySendEvent(new DDLChangedEvent(tableName, ConnectorConstant.OPERTION_ALTER,
                         data.getSql(), client.getBinlogFilename(), client.getBinlogPosition()));

+ 1 - 7
dbsyncer-connector/dbsyncer-connector-oracle/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer-connector</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -30,12 +30,6 @@
             <artifactId>orai18n</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>com.github.jsqlparser</groupId>
-            <artifactId>jsqlparser</artifactId>
-            <scope>provided</scope>
-        </dependency>
-
         <!-- sqlserver-driver -->
         <dependency>
             <groupId>com.microsoft.sqlserver</groupId>

+ 1 - 1
dbsyncer-connector/dbsyncer-connector-postgresql/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer-connector</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
dbsyncer-connector/dbsyncer-connector-sqlite/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer-connector</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
dbsyncer-connector/dbsyncer-connector-sqlserver/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer-connector</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
dbsyncer-connector/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>dbsyncer-connector</artifactId>

+ 1 - 1
dbsyncer-manager/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>dbsyncer-manager</artifactId>

+ 1 - 6
dbsyncer-parser/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>dbsyncer-parser</artifactId>
@@ -25,11 +25,6 @@
             <version>${project.parent.version}</version>
         </dependency>
 
-        <dependency>
-            <groupId>com.github.jsqlparser</groupId>
-            <artifactId>jsqlparser</artifactId>
-        </dependency>
-
         <!-- antlr4-runtime -->
         <dependency>
             <groupId>org.antlr</groupId>

+ 2 - 3
dbsyncer-parser/src/main/java/org/dbsyncer/parser/ddl/impl/DDLParserImpl.java

@@ -4,13 +4,11 @@
 package org.dbsyncer.parser.ddl.impl;
 
 import net.sf.jsqlparser.JSQLParserException;
-import net.sf.jsqlparser.parser.CCJSqlParserUtil;
 import net.sf.jsqlparser.statement.Statement;
 import net.sf.jsqlparser.statement.alter.Alter;
 import net.sf.jsqlparser.statement.alter.AlterExpression;
 import net.sf.jsqlparser.statement.alter.AlterOperation;
 import org.dbsyncer.common.util.CollectionUtils;
-import org.dbsyncer.common.util.JsonUtil;
 import org.dbsyncer.common.util.StringUtil;
 import org.dbsyncer.parser.ddl.AlterStrategy;
 import org.dbsyncer.parser.ddl.DDLParser;
@@ -25,6 +23,7 @@ import org.dbsyncer.sdk.connector.database.Database;
 import org.dbsyncer.sdk.enums.DDLOperationEnum;
 import org.dbsyncer.sdk.model.Field;
 import org.dbsyncer.sdk.spi.ConnectorService;
+import org.dbsyncer.sdk.util.SqlParserUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
@@ -62,7 +61,7 @@ public class DDLParserImpl implements DDLParser {
     public DDLConfig parse(ConnectorService connectorService, TableGroup tableGroup, String sql) throws JSQLParserException {
         DDLConfig ddlConfig = new DDLConfig();
         logger.info("ddl:{}", sql);
-        Statement statement = CCJSqlParserUtil.parse(sql);
+        Statement statement = SqlParserUtil.parse(sql);
         if (statement instanceof Alter && connectorService instanceof Database) {
             Alter alter = (Alter) statement;
             Database database = (Database) connectorService;

+ 1 - 1
dbsyncer-plugin/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>dbsyncer-plugin</artifactId>

+ 6 - 1
dbsyncer-sdk/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>dbsyncer-sdk</artifactId>
@@ -45,5 +45,10 @@
             <scope>provided</scope>
         </dependency>
 
+        <dependency>
+            <groupId>com.github.jsqlparser</groupId>
+            <artifactId>jsqlparser</artifactId>
+        </dependency>
+
     </dependencies>
 </project>

文件差异内容过多而无法显示
+ 39549 - 0
dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/sqlparser/SimpleSqlParser.java


+ 40 - 0
dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/sqlparser/SimpleSqlParserTokenManager.java

@@ -0,0 +1,40 @@
+/**
+ * DBSyncer Copyright 2020-2025 All Rights Reserved.
+ */
+package org.dbsyncer.sdk.sqlparser;
+
+import net.sf.jsqlparser.parser.CCJSqlParserTokenManager;
+import net.sf.jsqlparser.parser.SimpleCharStream;
+
+/**
+ * @Author 穿云
+ * @Version 1.0.0
+ * @Date 2025-03-02 16:07
+ */
+public class SimpleSqlParserTokenManager extends CCJSqlParserTokenManager {
+
+    int curLexState = 0;
+    int defaultLexState = 0;
+
+    public SimpleSqlParserTokenManager(SimpleCharStream stream) {
+        super(stream);
+    }
+
+    public SimpleSqlParserTokenManager(SimpleCharStream stream, int lexState) {
+        super(stream, lexState);
+    }
+
+    @Override
+    public void ReInit(SimpleCharStream stream) {
+        curLexState = defaultLexState;
+        super.ReInit(stream);
+    }
+
+    /**
+     * Switch to specified lex state.
+     */
+    public void SwitchTo(int lexState) {
+        curLexState = lexState;
+        super.SwitchTo(lexState);
+    }
+}

+ 495 - 0
dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/util/SqlParserUtil.java

@@ -0,0 +1,495 @@
+/**
+ * DBSyncer Copyright 2020-2025 All Rights Reserved.
+ */
+package org.dbsyncer.sdk.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.Stack;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.parser.CCJSqlParserConstants;
+import net.sf.jsqlparser.parser.Node;
+import net.sf.jsqlparser.parser.ParseException;
+import net.sf.jsqlparser.parser.StatementListener;
+import net.sf.jsqlparser.parser.StreamProvider;
+import net.sf.jsqlparser.parser.StringProvider;
+import net.sf.jsqlparser.parser.feature.Feature;
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.Statements;
+import org.dbsyncer.sdk.sqlparser.SimpleSqlParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @Author 穿云
+ * @Version 1.0.0
+ * @Date 2025-03-02 15:05
+ */
+public abstract class SqlParserUtil {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SqlParserUtil.class);
+    private static final int ALLOWED_NESTING_DEPTH = 10;
+
+    public static Statement parse(Reader statementReader) throws JSQLParserException {
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        Statement statement = null;
+        SimpleSqlParser parser = new SimpleSqlParser(new StreamProvider(statementReader));
+        try {
+            statement = parseStatement(parser, executorService);
+        } finally {
+            executorService.shutdown();
+        }
+        return statement;
+    }
+
+    public static Statement parse(String sql) throws JSQLParserException {
+        return parse(sql, null);
+    }
+
+    public static Statement parse(String sql, Consumer<SimpleSqlParser> consumer)
+            throws JSQLParserException {
+
+        if (sql == null || sql.isEmpty()) {
+            return null;
+        }
+
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        Statement statement = null;
+        try {
+            statement = parse(sql, executorService, consumer);
+        } finally {
+            executorService.shutdown();
+        }
+        return statement;
+    }
+
+    public static Statement parse(String sql, ExecutorService executorService,
+                                  Consumer<SimpleSqlParser> consumer)
+            throws JSQLParserException {
+        if (sql == null || sql.isEmpty()) {
+            return null;
+        }
+
+        Statement statement = null;
+        // first, try to parse fast and simple
+        SimpleSqlParser parser = newParser(sql);
+        if (consumer != null) {
+            consumer.accept(parser);
+        }
+        boolean allowComplex = parser.getConfiguration().getAsBoolean(Feature.allowComplexParsing);
+        try {
+            statement = parseStatement(parser.withAllowComplexParsing(false), executorService);
+        } catch (JSQLParserException ex) {
+            LOGGER.info("Nesting Depth" + getNestingDepth(sql));
+            if (allowComplex && getNestingDepth(sql) <= ALLOWED_NESTING_DEPTH) {
+                LOGGER.info("Trying COMPLEX parsing when SIMPLE parsing failed");
+                // beware: the parser must not be reused, but needs to be re-initiated
+                parser = newParser(sql);
+                if (consumer != null) {
+                    consumer.accept(parser);
+                }
+                statement = parseStatement(parser.withAllowComplexParsing(true), executorService);
+            } else {
+                throw ex;
+            }
+        }
+        return statement;
+    }
+
+    public static SimpleSqlParser newParser(String sql) {
+        if (sql == null || sql.isEmpty()) {
+            return null;
+        }
+
+        return new SimpleSqlParser(new StringProvider(sql));
+    }
+
+    public static SimpleSqlParser newParser(InputStream is) throws IOException {
+        return new SimpleSqlParser(new StreamProvider(is));
+    }
+
+    public static SimpleSqlParser newParser(InputStream is, String encoding) throws IOException {
+        return new SimpleSqlParser(new StreamProvider(is, encoding));
+    }
+
+    public static Node parseAST(String sql) throws JSQLParserException {
+        if (sql == null || sql.isEmpty()) {
+            return null;
+        }
+
+        SimpleSqlParser parser = newParser(sql);
+        try {
+            parser.Statement();
+            return parser.jjtree.rootNode();
+        } catch (Exception ex) {
+            throw new JSQLParserException(ex);
+        }
+    }
+
+    public static Statement parse(InputStream is) throws JSQLParserException {
+        try {
+            SimpleSqlParser parser = newParser(is);
+            return parser.Statement();
+        } catch (Exception ex) {
+            throw new JSQLParserException(ex);
+        }
+    }
+
+    public static Statement parse(InputStream is, String encoding) throws JSQLParserException {
+        try {
+            SimpleSqlParser parser = newParser(is, encoding);
+            return parser.Statement();
+        } catch (Exception ex) {
+            throw new JSQLParserException(ex);
+        }
+    }
+
+    public static Expression parseExpression(String expression) throws JSQLParserException {
+        if (expression == null || expression.isEmpty()) {
+            return null;
+        }
+
+        return parseExpression(expression, true);
+    }
+
+    public static Expression parseExpression(String expression, boolean allowPartialParse)
+            throws JSQLParserException {
+        if (expression == null || expression.isEmpty()) {
+            return null;
+        }
+
+        return parseExpression(expression, allowPartialParse, p -> {
+        });
+    }
+
+    @SuppressWarnings("PMD.CyclomaticComplexity")
+    public static Expression parseExpression(String expressionStr, boolean allowPartialParse,
+                                             Consumer<SimpleSqlParser> consumer) throws JSQLParserException {
+        if (expressionStr == null || expressionStr.isEmpty()) {
+            return null;
+        }
+
+        Expression expression = null;
+        // first, try to parse fast and simple
+        try {
+            SimpleSqlParser parser = newParser(expressionStr).withAllowComplexParsing(false);
+            if (consumer != null) {
+                consumer.accept(parser);
+            }
+            try {
+                expression = parser.Expression();
+                if (parser.getNextToken().kind != CCJSqlParserConstants.EOF) {
+                    throw new JSQLParserException(
+                            "could only parse partial expression " + expression.toString());
+                }
+            } catch (ParseException ex) {
+                throw new JSQLParserException(ex);
+            }
+        } catch (JSQLParserException ex1) {
+            // when fast simple parsing fails, try complex parsing but only if it has a chance to
+            // succeed
+            if (getNestingDepth(expressionStr) <= ALLOWED_NESTING_DEPTH) {
+                SimpleSqlParser parser = newParser(expressionStr).withAllowComplexParsing(true);
+                if (consumer != null) {
+                    consumer.accept(parser);
+                }
+                try {
+                    expression = parser.Expression();
+                    if (!allowPartialParse
+                            && parser.getNextToken().kind != CCJSqlParserConstants.EOF) {
+                        throw new JSQLParserException(
+                                "could only parse partial expression " + expression.toString());
+                    }
+                } catch (JSQLParserException ex) {
+                    throw ex;
+                } catch (ParseException ex) {
+                    throw new JSQLParserException(ex);
+                }
+            }
+        }
+        return expression;
+    }
+
+    /**
+     * Parse an conditional expression. This is the expression after a where clause. Partial parsing
+     * is enabled.
+     *
+     * @param condExpr
+     * @return the expression parsed
+     * @see #parseCondExpression(String, boolean)
+     */
+    public static Expression parseCondExpression(String condExpr) throws JSQLParserException {
+        if (condExpr == null || condExpr.isEmpty()) {
+            return null;
+        }
+        return parseCondExpression(condExpr, true);
+    }
+
+    /**
+     * Parse an conditional expression. This is the expression after a where clause.
+     *
+     * @param condExpr
+     * @param allowPartialParse false: needs the whole string to be processed.
+     * @return the expression parsed
+     * @see #parseCondExpression(String)
+     */
+    public static Expression parseCondExpression(String condExpr, boolean allowPartialParse)
+            throws JSQLParserException {
+        if (condExpr == null || condExpr.isEmpty()) {
+            return null;
+        }
+        return parseCondExpression(condExpr, allowPartialParse, p -> {
+        });
+    }
+
+    @SuppressWarnings("PMD.CyclomaticComplexity")
+    public static Expression parseCondExpression(String conditionalExpressionStr,
+                                                 boolean allowPartialParse, Consumer<SimpleSqlParser> consumer) throws JSQLParserException {
+        if (conditionalExpressionStr == null || conditionalExpressionStr.isEmpty()) {
+            return null;
+        }
+
+        Expression expression = null;
+        // first, try to parse fast and simple
+        try {
+            SimpleSqlParser parser =
+                    newParser(conditionalExpressionStr).withAllowComplexParsing(false);
+            if (consumer != null) {
+                consumer.accept(parser);
+            }
+            try {
+                expression = parser.Expression();
+                if (parser.getNextToken().kind != CCJSqlParserConstants.EOF) {
+                    throw new JSQLParserException(
+                            "could only parse partial expression " + expression.toString());
+                }
+            } catch (ParseException ex) {
+                throw new JSQLParserException(ex);
+            }
+        } catch (JSQLParserException ex1) {
+            if (getNestingDepth(conditionalExpressionStr) <= ALLOWED_NESTING_DEPTH) {
+                SimpleSqlParser parser =
+                        newParser(conditionalExpressionStr).withAllowComplexParsing(true);
+                if (consumer != null) {
+                    consumer.accept(parser);
+                }
+                try {
+                    expression = parser.Expression();
+                    if (!allowPartialParse
+                            && parser.getNextToken().kind != CCJSqlParserConstants.EOF) {
+                        throw new JSQLParserException(
+                                "could only parse partial expression " + expression.toString());
+                    }
+                } catch (JSQLParserException ex) {
+                    throw ex;
+                } catch (ParseException ex) {
+                    throw new JSQLParserException(ex);
+                }
+            }
+        }
+        return expression;
+    }
+
+    /**
+     * @param parser the Parser armed with a Statement text
+     * @param executorService the Executor Service for parsing within a Thread
+     * @return the parsed Statement
+     * @throws JSQLParserException when either the Statement can't be parsed or the configured
+     *         timeout is reached
+     */
+
+    public static Statement parseStatement(SimpleSqlParser parser, ExecutorService executorService)
+            throws JSQLParserException {
+        Statement statement = null;
+        Future<Statement> future = executorService.submit(parser::Statement);
+        try {
+            statement = future.get(parser.getConfiguration().getAsLong(Feature.timeOut),
+                    TimeUnit.MILLISECONDS);
+        } catch (TimeoutException ex) {
+            parser.interrupted = true;
+            future.cancel(true);
+            throw new JSQLParserException("Time out occurred.", ex);
+        } catch (Exception ex) {
+            throw new JSQLParserException(ex);
+        }
+        return statement;
+    }
+
+    /**
+     * Parse a statement list.
+     *
+     * @return the statements parsed
+     */
+    public static Statements parseStatements(String sqls) throws JSQLParserException {
+        if (sqls == null || sqls.isEmpty()) {
+            return null;
+        }
+
+        return parseStatements(sqls, null);
+    }
+
+    public static Statements parseStatements(String sqls, Consumer<SimpleSqlParser> consumer)
+            throws JSQLParserException {
+        if (sqls == null || sqls.isEmpty()) {
+            return null;
+        }
+
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        final Statements statements = parseStatements(sqls, executorService, consumer);
+        executorService.shutdown();
+
+        return statements;
+    }
+
+    /**
+     * Parse a statement list.
+     *
+     * @return the statements parsed
+     */
+    public static Statements parseStatements(String sqls, ExecutorService executorService,
+                                             Consumer<SimpleSqlParser> consumer)
+            throws JSQLParserException {
+        if (sqls == null || sqls.isEmpty()) {
+            return null;
+        }
+
+        Statements statements = null;
+        SimpleSqlParser parser = newParser(sqls);
+        if (consumer != null) {
+            consumer.accept(parser);
+        }
+        boolean allowComplex = parser.getConfiguration().getAsBoolean(Feature.allowComplexParsing);
+
+        // first, try to parse fast and simple
+        try {
+            statements = parseStatements(parser.withAllowComplexParsing(false), executorService);
+        } catch (JSQLParserException ex) {
+            // when fast simple parsing fails, try complex parsing but only if it has a chance to
+            // succeed
+            if (allowComplex && getNestingDepth(sqls) <= ALLOWED_NESTING_DEPTH) {
+                // beware: parser must not be re-used but needs to be re-initiated
+                parser = newParser(sqls);
+                if (consumer != null) {
+                    consumer.accept(parser);
+                }
+                statements = parseStatements(parser.withAllowComplexParsing(true), executorService);
+            }
+        }
+        return statements;
+    }
+
+    /**
+     * @param parser the Parser armed with a Statement text
+     * @param executorService the Executor Service for parsing within a Thread
+     * @return the Statements (representing a List of single statements)
+     * @throws JSQLParserException when either the Statement can't be parsed or the configured
+     *         timeout is reached
+     */
+    public static Statements parseStatements(SimpleSqlParser parser, ExecutorService executorService)
+            throws JSQLParserException {
+        Statements statements;
+        Future<Statements> future = null;
+        try {
+            future = executorService.submit(parser::Statements);
+            statements = future.get(parser.getConfiguration().getAsLong(Feature.timeOut),
+                    TimeUnit.MILLISECONDS);
+        } catch (TimeoutException ex) {
+            parser.interrupted = true;
+            future.cancel(true);
+            throw new JSQLParserException("Time out occurred.", ex);
+        } catch (Exception ex) {
+            throw new JSQLParserException(ex);
+        }
+        return statements;
+    }
+
+    public static void streamStatements(StatementListener listener, InputStream is, String encoding)
+            throws JSQLParserException {
+        try {
+            SimpleSqlParser parser = newParser(is, encoding);
+            while (true) {
+                Statement stmt = parser.SingleStatement();
+                listener.accept(stmt);
+                if (parser.getToken(1).kind == CCJSqlParserConstants.ST_SEMICOLON) {
+                    parser.getNextToken();
+                }
+
+                if (parser.getToken(1).kind == CCJSqlParserConstants.EOF) {
+                    break;
+                }
+            }
+        } catch (Exception ex) {
+            throw new JSQLParserException(ex);
+        }
+    }
+
+    public static int getNestingDepth(String sql) {
+        int maxlevel = 0;
+        int level = 0;
+
+        char[] chars = sql.toCharArray();
+        for (char c : chars) {
+            switch (c) {
+                case '(':
+                    level++;
+                    break;
+                case ')':
+                    if (maxlevel < level) {
+                        maxlevel = level;
+                    }
+                    level--;
+                    break;
+                default:
+                    // Codazy/PMD insists in a Default statement
+            }
+        }
+        return maxlevel;
+    }
+
+    public static int getUnbalancedPosition(String text) {
+        Stack<Character> stack = new Stack<>();
+        boolean insideQuote = false;
+
+        for (int i = 0; i < text.length(); i++) {
+            char c = text.charAt(i);
+            if (c == '"' || c == '\'') {
+                if (!insideQuote) {
+                    stack.push(c); // Add quote to stack
+                } else if (stack.peek() == c) {
+                    stack.pop(); // Matching quote found, remove from stack
+                }
+                insideQuote = !insideQuote; // Toggle insideQuote flag
+            } else if (!insideQuote && (c == '(' || c == '[' || c == '{')) {
+                stack.push(c); // Add opening bracket to stack
+            } else if (!insideQuote && (c == ')' || c == ']' || c == '}')) {
+                if (stack.isEmpty()) {
+                    return i; // Return position of unbalanced closing bracket
+                }
+                char top = stack.pop();
+                if (c == ')' && top != '(' || c == ']' && top != '[' || c == '}' && top != '{') {
+                    return i; // Return position of unbalanced closing bracket
+                }
+            }
+        }
+
+        if (!stack.isEmpty()) {
+            char unbalanced = stack.peek();
+            for (int i = 0; i < text.length(); i++) {
+                if (text.charAt(i) == unbalanced) {
+                    return i; // Return position of unbalanced opening bracket or quote
+                }
+            }
+        }
+
+        return -1; // Return -1 if all brackets and quotes are balanced
+    }
+}

+ 1 - 1
dbsyncer-storage/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>dbsyncer-storage</artifactId>

+ 1 - 1
dbsyncer-web/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>dbsyncer</artifactId>
         <groupId>org.ghi</groupId>
-        <version>2.0.5</version>
+        <version>2.0.6</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>dbsyncer-web</artifactId>

+ 3 - 2
dbsyncer-web/src/main/java/org/dbsyncer/web/Version.java

@@ -15,8 +15,9 @@ public class Version {
      */
     private final long version;
 
-    public static final Version V_2_0_5 = new Version(20_00_05_2025_00_00_00L);
-    public static final Version CURRENT = new Version(20_00_05_2025_02_19_00L);
+    public static final Version V_2_0_5 = new Version(20_00_05_2025_02_19_00L);
+    public static final Version V_2_0_6 = new Version(20_00_06_2025_00_00_00L);
+    public static final Version CURRENT = new Version(20_00_06_2025_03_02_00L);
 
     public Version(long version) {
         this.version = version;

+ 1 - 1
pom.xml

@@ -6,7 +6,7 @@
 
     <groupId>org.ghi</groupId>
     <artifactId>dbsyncer</artifactId>
-    <version>2.0.5</version>
+    <version>2.0.6</version>
     <packaging>pom</packaging>
     <name>dbsyncer</name>
     <url>https://gitee.com/ghi/dbsyncer</url>

+ 1 - 1
version.cmd

@@ -1,7 +1,7 @@
 @echo off
 
 set CURRENT_DATE=%date:~5,2%%date:~8,2%
-set VERSION=2.0.5_%CURRENT_DATE%
+set VERSION=2.0.6_%CURRENT_DATE%
 set /p APP_VERSION=Please enter a new version number(%VERSION%): || set APP_VERSION=%VERSION%
 echo %APP_VERSION%
 

+ 1 - 1
version.sh

@@ -1,5 +1,5 @@
 #!/bin/bash
-VERSION=2.0.5_$(date +"%m%d")
+VERSION=2.0.6_$(date +"%m%d")
 read -p "Please enter a new version number($VERSION):" APP_VERSION
 if [ -z "$APP_VERSION" ]; then
   APP_VERSION=$VERSION

部分文件因为文件数量过多而无法显示