diff --git a/src/main/java/com/alibaba/druid/mock/MockConnection.java b/src/main/java/com/alibaba/druid/mock/MockConnection.java index 9c8f655a27..fbd2bb2b4e 100644 --- a/src/main/java/com/alibaba/druid/mock/MockConnection.java +++ b/src/main/java/com/alibaba/druid/mock/MockConnection.java @@ -37,6 +37,7 @@ public class MockConnection extends ConnectionBase implements Connection { private final long createdTimeMillis = System.currentTimeMillis(); private long lastActiveTimeMillis = System.currentTimeMillis(); + private int networkTimeout; private SQLException error; @@ -455,11 +456,11 @@ public class MockConnection extends ConnectionBase implements Connection { } public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { - throw new SQLFeatureNotSupportedException(); + this.networkTimeout = milliseconds; } public int getNetworkTimeout() throws SQLException { - throw new SQLFeatureNotSupportedException(); + return networkTimeout; } @Override diff --git a/src/main/java/com/alibaba/druid/pool/DruidAbstractDataSource.java b/src/main/java/com/alibaba/druid/pool/DruidAbstractDataSource.java index 6cde51a8bd..43c075b498 100644 --- a/src/main/java/com/alibaba/druid/pool/DruidAbstractDataSource.java +++ b/src/main/java/com/alibaba/druid/pool/DruidAbstractDataSource.java @@ -43,6 +43,7 @@ import java.io.Serializable; import java.sql.*; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicLongFieldUpdater; @@ -70,6 +71,8 @@ public abstract class DruidAbstractDataSource extends WrapperAdapter implements public static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = 60 * 1000L; public static final long DEFAULT_TIME_BETWEEN_CONNECT_ERROR_MILLIS = 500; public static final int DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3; + public static final int DEFAULT_TIME_CONNECT_TIMEOUT_MILLIS = 10_000; + public static final int DEFAULT_TIME_SOCKET_TIMEOUT_MILLIS = 10_000; public static final long DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 30L; public static final long DEFAULT_MAX_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 60L * 7; @@ -119,8 +122,11 @@ public abstract class DruidAbstractDataSource extends WrapperAdapter implements protected Driver driver; - protected volatile int queryTimeout; - protected volatile int transactionQueryTimeout; + protected volatile int connectTimeout = DEFAULT_TIME_CONNECT_TIMEOUT_MILLIS; // milliSeconds + protected volatile int socketTimeout = DEFAULT_TIME_SOCKET_TIMEOUT_MILLIS; // milliSeconds + + protected volatile int queryTimeout; // seconds + protected volatile int transactionQueryTimeout; // seconds protected long createTimespan; @@ -251,6 +257,8 @@ public abstract class DruidAbstractDataSource extends WrapperAdapter implements protected volatile long failContinuousTimeMillis; protected ScheduledExecutorService destroyScheduler; protected ScheduledExecutorService createScheduler; + protected Executor netTimeoutExecutor; + protected volatile boolean netTimeoutError; static final AtomicLongFieldUpdater failContinuousTimeMillisUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "failContinuousTimeMillis"); static final AtomicIntegerFieldUpdater failContinuousUpdater = AtomicIntegerFieldUpdater.newUpdater(DruidAbstractDataSource.class, "failContinuous"); @@ -995,6 +1003,38 @@ public abstract class DruidAbstractDataSource extends WrapperAdapter implements this.queryTimeout = seconds; } + /** + * + * @since 1.2.12 + */ + public int getConnectTimeout() { + return connectTimeout; + } + + /** + * + * @since 1.2.12 + */ + public void setConnectTimeout(int milliSeconds) { + this.connectTimeout = milliSeconds; + } + + /** + * + * @since 1.2.12 + */ + public int getSocketTimeout() { + return socketTimeout; + } + + /** + * + * @since 1.2.12 + */ + public void setSocketTimeout(int milliSeconds) { + this.socketTimeout = milliSeconds; + } + public String getName() { if (name != null) { return name; @@ -1691,6 +1731,17 @@ public abstract class DruidAbstractDataSource extends WrapperAdapter implements physicalConnectProperties.put("password", password); } + if (connectTimeout > 0) { + if (isMySql) { + physicalConnectProperties.put("connectTimeout", connectTimeout); + } else if (isOracle) { + physicalConnectProperties.put("oracle.net.CONNECT_TIMEOUT", connectTimeout); + } else if (driver != null && "org.postgresql.Driver".equals(driver.getClass().getName())) { + physicalConnectProperties.put("loginTimeout", connectTimeout); + physicalConnectProperties.put("socketTimeout", connectTimeout); + } + } + Connection conn = null; long connectStartNanos = System.nanoTime(); @@ -1716,6 +1767,16 @@ public abstract class DruidAbstractDataSource extends WrapperAdapter implements initPhysicalConnection(conn, variables, globalVariables); initedNanos = System.nanoTime(); + if (socketTimeout > 0 && !netTimeoutError) { + try { + conn.setNetworkTimeout(netTimeoutExecutor, socketTimeout); + } catch (SQLFeatureNotSupportedException | AbstractMethodError e) { + netTimeoutError = true; + } catch (Exception ignored) { + // ignored + } + } + validateConnection(conn); validatedNanos = System.nanoTime(); @@ -2225,4 +2286,19 @@ public abstract class DruidAbstractDataSource extends WrapperAdapter implements return globalVairiables; } } + + class SynchronousExecutor implements Executor { + @Override + public void execute(Runnable command) { + try { + command.run(); + } catch (AbstractMethodError error) { + netTimeoutError = true; + } catch (Exception ignored) { + if (LOG.isDebugEnabled()) { + LOG.debug("failed to execute command " + command); + } + } + } + } } diff --git a/src/main/java/com/alibaba/druid/pool/DruidDataSource.java b/src/main/java/com/alibaba/druid/pool/DruidDataSource.java index 4eb62baf21..ce54dad024 100644 --- a/src/main/java/com/alibaba/druid/pool/DruidDataSource.java +++ b/src/main/java/com/alibaba/druid/pool/DruidDataSource.java @@ -866,6 +866,8 @@ public class DruidDataSource extends DruidAbstractDataSource implements DruidDat initCheck(); + this.netTimeoutExecutor = new SynchronousExecutor(); + initExceptionSorter(); initValidConnectionChecker(); validationQueryCheck(); @@ -1221,7 +1223,9 @@ public class DruidDataSource extends DruidAbstractDataSource implements DruidDat } else if (dbType == DbType.db2) { db2ValidationQueryCheck(); } else if (dbType == DbType.mysql - || JdbcUtils.MYSQL_DRIVER_6.equals(this.driverClass)) { + || JdbcUtils.MYSQL_DRIVER.equals(this.driverClass) + || JdbcUtils.MYSQL_DRIVER_6.equals(this.driverClass) + ) { isMySql = true; } diff --git a/src/main/java/com/alibaba/druid/sql/SQLUtils.java b/src/main/java/com/alibaba/druid/sql/SQLUtils.java index 55197a48bd..154d0a74c0 100644 --- a/src/main/java/com/alibaba/druid/sql/SQLUtils.java +++ b/src/main/java/com/alibaba/druid/sql/SQLUtils.java @@ -36,6 +36,7 @@ import com.alibaba.druid.sql.dialect.mysql.ast.clause.MySqlSelectIntoStatement; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement; import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor; import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor; +import com.alibaba.druid.sql.dialect.odps.visitor.OdpsASTVisitorAdapter; import com.alibaba.druid.sql.dialect.odps.visitor.OdpsOutputVisitor; import com.alibaba.druid.sql.dialect.odps.visitor.OdpsSchemaStatVisitor; import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; @@ -49,10 +50,7 @@ import com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerOutputVisitor; import com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerSchemaStatVisitor; import com.alibaba.druid.sql.parser.*; import com.alibaba.druid.sql.repository.SchemaRepository; -import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor; -import com.alibaba.druid.sql.visitor.SQLASTVisitorAdapter; -import com.alibaba.druid.sql.visitor.SchemaStatVisitor; -import com.alibaba.druid.sql.visitor.VisitorFeature; +import com.alibaba.druid.sql.visitor.*; import com.alibaba.druid.support.logging.Log; import com.alibaba.druid.support.logging.LogFactory; import com.alibaba.druid.util.*; @@ -63,6 +61,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TimeZone; +import java.util.function.Consumer; +import java.util.function.Predicate; public class SQLUtils { public static final Charset UTF8 = Charset.forName("UTF-8"); @@ -795,6 +795,55 @@ public class SQLUtils { return addSelectItem(selectSql, expr, alias, false, dbType); } + public static void acceptBooleanOr(String sql, DbType dbType, Consumer consumer) { + acceptBinaryOpExpr(sql, dbType, consumer, e -> e.getOperator() == SQLBinaryOperator.BooleanOr); + } + + public static void acceptBinaryOpExpr(String sql, DbType dbType, Consumer consumer, Predicate filter) { + if (sql == null || sql.isEmpty()) { + return; + } + + List stmtList = new ArrayList<>(); + + try { + SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType, SQLParserFeature.EnableMultiUnion, SQLParserFeature.KeepComments, SQLParserFeature.EnableSQLBinaryOpExprGroup); + parser.parseStatementList(stmtList, -1, null); + } catch (Exception ignored) { + return; + } + + SQLASTVisitor visitor; + switch (dbType) { + case odps: + visitor = new OdpsASTVisitorAdapter() { + @Override + public boolean visit(SQLBinaryOpExprGroup x) { + if (filter == null || filter.test(x)) { + consumer.accept(x); + } + return super.visit(x); + } + }; + break; + default: + visitor = new SQLASTVisitorAdapter() { + @Override + public boolean visit(SQLBinaryOpExprGroup x) { + if (filter == null || filter.test(x)) { + consumer.accept(x); + } + return super.visit(x); + } + }; + break; + } + + for (SQLStatement stmt : stmtList) { + stmt.accept(visitor); + } + } + public static String addSelectItem(String selectSql, String expr, String alias, boolean first, DbType dbType) { List stmtList = parseStatements(selectSql, dbType); diff --git a/src/main/java/com/alibaba/druid/sql/dialect/db2/visitor/DB2OutputVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/db2/visitor/DB2OutputVisitor.java index 1ab61522cf..61aa313244 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/db2/visitor/DB2OutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/db2/visitor/DB2OutputVisitor.java @@ -19,9 +19,7 @@ import com.alibaba.druid.DbType; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLName; import com.alibaba.druid.sql.ast.SQLPartitionBy; -import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; -import com.alibaba.druid.sql.ast.expr.SQLIntervalExpr; -import com.alibaba.druid.sql.ast.expr.SQLIntervalUnit; +import com.alibaba.druid.sql.ast.expr.*; import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddColumn; import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition; import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; @@ -153,7 +151,13 @@ public class DB2OutputVisitor extends SQLASTOutputVisitor implements DB2ASTVisit public boolean visit(SQLIntervalExpr x) { SQLExpr value = x.getValue(); - value.accept(this); + if (value instanceof SQLLiteralExpr || value instanceof SQLName || value instanceof SQLVariantRefExpr) { + value.accept(this); + } else { + print('('); + value.accept(this); + print(')'); + } SQLIntervalUnit unit = x.getUnit(); if (unit != null) { diff --git a/src/main/java/com/alibaba/druid/sql/parser/Lexer.java b/src/main/java/com/alibaba/druid/sql/parser/Lexer.java index 8505a8b395..98728455ae 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/Lexer.java +++ b/src/main/java/com/alibaba/druid/sql/parser/Lexer.java @@ -2255,7 +2255,7 @@ public class Lexer { boolean ident = false; for (; ; ) { ch = charAt(++pos); - if (isEOF()) { + if (isEOF() || ch == ';' || ch == ';') { pos--; bufPos--; break; @@ -2287,7 +2287,7 @@ public class Lexer { this.ch = charAt(pos); if (dbType == DbType.odps) { - while (isIdentifierChar(this.ch)) { + while (isIdentifierChar(this.ch) && ch != ';') { ++pos; bufPos++; this.ch = charAt(pos); diff --git a/src/main/java/com/alibaba/druid/sql/parser/SQLParserUtils.java b/src/main/java/com/alibaba/druid/sql/parser/SQLParserUtils.java index 7cb4f736a4..ca07498c85 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/SQLParserUtils.java +++ b/src/main/java/com/alibaba/druid/sql/parser/SQLParserUtils.java @@ -26,6 +26,8 @@ import com.alibaba.druid.sql.dialect.ads.parser.AdsStatementParser; import com.alibaba.druid.sql.dialect.antspark.parser.AntsparkLexer; import com.alibaba.druid.sql.dialect.antspark.parser.AntsparkStatementParser; import com.alibaba.druid.sql.dialect.blink.parser.BlinkStatementParser; +import com.alibaba.druid.sql.dialect.clickhouse.parser.ClickhouseExprParser; +import com.alibaba.druid.sql.dialect.clickhouse.parser.ClickhouseLexer; import com.alibaba.druid.sql.dialect.clickhouse.parser.ClickhouseStatementParser; import com.alibaba.druid.sql.dialect.db2.ast.stmt.DB2SelectQueryBlock; import com.alibaba.druid.sql.dialect.db2.parser.DB2ExprParser; @@ -48,10 +50,6 @@ import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectQueryBlock; import com.alibaba.druid.sql.dialect.oracle.parser.OracleExprParser; import com.alibaba.druid.sql.dialect.oracle.parser.OracleLexer; import com.alibaba.druid.sql.dialect.oracle.parser.OracleStatementParser; -import com.alibaba.druid.sql.dialect.oscar.ast.stmt.OscarSelectQueryBlock; -import com.alibaba.druid.sql.dialect.oscar.parser.OscarExprParser; -import com.alibaba.druid.sql.dialect.oscar.parser.OscarLexer; -import com.alibaba.druid.sql.dialect.oscar.visitor.OscarStatementParser; import com.alibaba.druid.sql.dialect.phoenix.parser.PhoenixExprParser; import com.alibaba.druid.sql.dialect.phoenix.parser.PhoenixLexer; import com.alibaba.druid.sql.dialect.phoenix.parser.PhoenixStatementParser; @@ -144,8 +142,6 @@ public class SQLParserUtils { return new AntsparkStatementParser(sql); case clickhouse: return new ClickhouseStatementParser(sql); - case oscar: - return new OscarStatementParser(sql); default: return new SQLStatementParser(sql, dbType); } @@ -186,6 +182,8 @@ public class SQLParserUtils { return new PrestoExprParser(sql, features); case hive: return new HiveExprParser(sql, features); + case clickhouse: + return new ClickhouseExprParser(sql, features); case oscar: return new OscarExprParser(sql, features); default: @@ -231,6 +229,8 @@ public class SQLParserUtils { return new AntsparkLexer(sql); case oscar: return new OscarLexer(sql); + case clickhouse: + return new ClickhouseLexer(sql); default: return new Lexer(sql, null, dbType); } diff --git a/src/main/java/com/alibaba/druid/util/JdbcUtils.java b/src/main/java/com/alibaba/druid/util/JdbcUtils.java index 72cb6e9715..61d61b3cfb 100644 --- a/src/main/java/com/alibaba/druid/util/JdbcUtils.java +++ b/src/main/java/com/alibaba/druid/util/JdbcUtils.java @@ -637,6 +637,8 @@ public final class JdbcUtils implements JdbcConstants { return DbType.highgo; } else if (rawUrl.startsWith("jdbc:pivotal:greenplum:") || rawUrl.startsWith("jdbc:datadirect:greenplum:")) { return DbType.greenplum; + } else if (rawUrl.startsWith("jdbc:gaussdb:") || rawUrl.startsWith("jdbc:dws:iam:")) { + return DbType.gaussdb; } else { return null; } diff --git a/src/test/java/com/alibaba/druid/sql/dialect/db2/visitor/DB2OutputVisitorTest.java b/src/test/java/com/alibaba/druid/sql/dialect/db2/visitor/DB2OutputVisitorTest.java new file mode 100644 index 0000000000..4bc7833c59 --- /dev/null +++ b/src/test/java/com/alibaba/druid/sql/dialect/db2/visitor/DB2OutputVisitorTest.java @@ -0,0 +1,59 @@ +package com.alibaba.druid.sql.dialect.db2.visitor; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.parser.SQLParserUtils; +import com.alibaba.druid.sql.parser.SQLStatementParser; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +/** + * @author abomb4 2022-06-19 + */ +public class DB2OutputVisitorTest { + + @Test + public void test() { + + List testCases = Arrays.asList( + new TestCase("interval 表达式应该带括号 select", + "SELECT A, B, C FROM D WHERE C < CURRENT TIMESTAMP + (10 * 60) SECONDS", + "SELECT A, B, C FROM D WHERE C < CURRENT TIMESTAMP + (10 * 60) SECONDS"), + new TestCase("interval 表达式应该带括号 insert", + "INSERT INTO TEST VALUES (1, CURRENT TIMESTAMP + ((10 + 60) SECONDS))", + "INSERT INTO TEST VALUES (1, CURRENT TIMESTAMP + (10 + 60) SECONDS)"), + new TestCase("interval 表达式应该带括号 update", + "UPDATE test SET created = CURRENT TIMESTAMP + 10 SECONDS WHERE created < CURRENT TIMESTAMP + (10 * 60) SECONDS", + "UPDATE test SET created = CURRENT TIMESTAMP + (10) SECONDS WHERE created < CURRENT TIMESTAMP + (10 * 60) SECONDS") + ); + + for (TestCase testCase : testCases) { + SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(testCase.origin, DbType.db2); + StringBuilder builder = new StringBuilder(); + DB2OutputVisitor visitor = new DB2OutputVisitor(builder); + visitor.setUppCase(false); + visitor.setPrettyFormat(false); + visitor.setParameterized(false); + SQLStatement stmt = parser.parseStatement(); + stmt.accept(visitor); + String result = builder.toString(); + Assert.assertEquals(testCase.name, testCase.expected, result); + } + System.out.println("DB2 Interval test ok."); + } + + private static class TestCase { + String name; + String origin; + String expected; + + public TestCase(String name, String origin, String expected) { + this.name = name; + this.origin = origin; + this.expected = expected; + } + } +}