Compare commits

...

59 Commits

Author SHA1 Message Date
c2190475a8 support loongnix-server 2024-11-27 11:06:50 +08:00
opengauss_bot
97a8892795
!237 【回合】将uint8类型映射为BigInteger
Merge pull request !237 from zhangtingtingting/7.2.0
2024-05-15 13:14:54 +00:00
zhangting
4b3f04fb47 将uint8类型映射为BigInteger 2024-05-15 20:36:23 +08:00
opengauss_bot
c0a7188ca5
!236 处理JDBC连接B库获取blob类型报错问题
Merge pull request !236 from zhangtingtingting/7.2.0
2024-05-15 08:13:51 +00:00
zhangting
230add6d79 处理blob类型报错问题 2024-05-15 16:05:28 +08:00
opengauss_bot
4151a42621
!234 修复B模式下 uint1/uint2/uint4/uint8 的getObject类型为PGobject与M*不一致问题
Merge pull request !234 from zhangtingtingting/8.0.4
2024-05-13 07:50:33 +00:00
zhangting
ec6435b8ce 修复B模式下 uint1/uint2/uint4/uint8 的getObject类型为PGobject与M*不一致问题 2024-05-13 14:53:03 +08:00
opengauss_bot
1c82cd33d3
!228 【5.0.2补丁版本】回合PR-181
Merge pull request !228 from zhangxubo/5.0.0
2024-05-13 03:07:35 +00:00
zhang_xubo
558399c514 fix merge conflict 2024-05-09 21:32:30 +08:00
zhangting
bbd7de4e5e 解决jdbc插入blob类型报错invalid hexadecimal digit 2024-05-09 21:26:27 +08:00
luozihao
71a6ee0f3d 增加getString适配 2024-05-09 21:25:40 +08:00
opengauss_bot
814e2067d7
!222 【5.0.2 补丁版本】 更新版本号
Merge pull request !222 from zhangxubo/5.0.0
2024-04-23 08:28:56 +00:00
zhang_xubo
c2f768aa42 updat 5.0.2 build version 2024-04-20 18:10:48 +08:00
opengauss_bot
fd771c93ae
!215 【5.0.2】补丁版本,主干缺陷回合
Merge pull request !215 from zhangxubo/5.0.0
2024-04-16 09:27:20 +00:00
zhangting
e8acecb4d4 解决jdbc插入blob类型报错invalid hexadecimal digit 2024-04-11 17:22:00 +08:00
zhangpeng
176dffb258 fix hibernate setClob exceed clob length 0 error 2024-04-11 16:41:19 +08:00
zhangting
796dcb6f89 解决jdbc获取cast(X as binary)的类型/值与mysql不同问题 2024-04-11 16:40:12 +08:00
zhangting
2f8c0eb442 处理JDBC连接B库获取blob类型报错问题 2024-04-11 16:39:38 +08:00
zhangting
721a63ed82 将year类型转为Date类型返回 2024-04-11 16:39:14 +08:00
zhangting
8015570f40 兼容性jdbc获取year类型值报错(I8WBKN),兼容性jdbc获取cast(X as time)报错(I8YUCT) 2024-04-11 16:36:32 +08:00
zhangpeng
8d308deceb fix: repair sp out parameter refcursor type error when set OracleTypes.CURSOR = -10 2024-04-11 16:34:06 +08:00
justbk
811ff0d5f6 修复batchMode=ON时仅插入一条数据抛错的问题 2024-04-11 16:32:49 +08:00
justbk
dc90667310 修复select语句中包含function相关的关键字,导致除号分割异常的问题 2024-04-11 16:32:18 +08:00
justbk
1209b7cc95 remove UTFEncoding 2024-04-11 16:29:47 +08:00
opengauss_bot
702ee16132
!201 [bugfix] 回合blob在b兼容性导致的问题。
Merge pull request !201 from 周斌/blob_support_5.0.0
2024-03-13 02:51:28 +00:00
luozihao
bac0d9e045 add long blob support 2024-03-13 09:35:35 +08:00
luozihao
90e5efa073 为jdbc增加B兼容模式下tinyblob等类型的适配 2024-03-12 20:46:52 +08:00
opengauss_bot
1676ae752d
!184 bug修复
Merge pull request !184 from 吴北斗/5.0.0
2023-12-01 02:10:07 +00:00
wubeidou
1560a356f1 bug修改 2023-11-30 17:05:11 +08:00
opengauss_bot
2eed94f897
!163 【5.0.0代码回合】#130 修复setBinaryStream 重载方法没有对BLOB MODE逻辑生效的问题
Merge pull request !163 from zhangxubo/5.0.0
2023-10-25 03:27:07 +00:00
justbk
73d74d2c2a repair jdbc blob only get index 1 issue 2023-10-23 21:39:46 +08:00
opengauss_bot
6061008de0
!146 【5.0.1补丁版本回合】主干bugfix回合5.0.0分支
Merge pull request !146 from zhangxubo/5.0.1
2023-08-18 12:24:58 +00:00
zhang_xubo
420d1d76ff change version to 5.0.1 2023-08-18 20:21:51 +08:00
justbk
b132816223 repair setTimestamp format parse issue in B dbcompatibility mode 2023-08-18 20:19:36 +08:00
travelliu
b5416067e3 feat: getblob support bytea/blob 2023-08-01 19:15:25 +08:00
saxisuer
e271d2ace5 1. 修复org.postgresql.jdbc.PgPreparedStatement#setBinaryStream(int, java.io.InputStream)
org.postgresql.jdbc.PgPreparedStatement.setBinaryStream(int, java.io.InputStream, long)
方法没有生效blobMode的问题
2023-08-01 19:15:10 +08:00
chen-czywj
49a46ed280 修复集群探测主节点执行sql时没有字符编码类型报错 2023-08-01 19:14:52 +08:00
congzhou2603
ab4e38c7d1 【bugfix】修复leastconn模式并发场景下有小概率没有完全负载均衡的问题 2023-08-01 19:14:38 +08:00
saxisuer
29315f133c 1. 修复了 驱动字符集设置参数非线程安全的问题
2. 修复CODE-STYLE 问题
2023-08-01 19:14:23 +08:00
opengauss-bot
c0cec35626
!124 修复集群主机心跳线程退出问题
Merge pull request !124 from 陈紫阳/master
2023-03-27 12:19:02 +00:00
opengauss-bot
6342f8f70d
!123 【5.0.0】【bugfix】定时关闭部分连接线程,间隔5s进行kill连接,但是实际隔了20s进行kill
Merge pull request !123 from 周聪/5.0.0huihe2_bugfix_closeconnection_time_timeinterval
2023-03-21 06:58:30 +00:00
congzhou2603
637b9305b9 bugfix closeConnectionThread的时间间隔错误 2023-03-21 09:02:43 +08:00
opengauss-bot
431fe81352
!121 【5.0.0回合】判断是否开启心跳
Merge pull request !121 from 陈紫阳/master
2023-03-16 11:34:50 +00:00
opengauss-bot
66074eddaf
!119 【5.0.0回合】释放心跳检测中探活创建的PGStream
Merge pull request !119 from 陈紫阳/master
2023-03-15 12:47:35 +00:00
opengauss-bot
e788fcb3df
!117 【5.0.0】【bugfix】 leastconn负载均衡模式下,url串只有一个节点没挂掉,建连时会报异常"CachedCreatingConnectionSize should not be less than 0, reset to 0."
Merge pull request !117 from 周聪/huihe_5.0.0_bugfix_cachedCreatingConnectionSize_toSmall
2023-03-15 07:16:27 +00:00
congzhou2603
d1df457bd0 leastconn负载均衡模式下,url串只有一个节点没挂掉,建连时会报异常‘CachedCreatingConnectionSize should not be less than 0, reset to 0.’ 2023-03-15 15:02:17 +08:00
opengauss-bot
be68803370
!115 【5.0.0回合】【bugfix】【测试类型:接口功能】【测试版本:5.0.0】 leastconn负载均衡模式下,JDBC连接失效,数据库端连接未kill
Merge pull request !115 from 周聪/5.0.0_bugfix_kill_cleared_connection
2023-03-13 14:36:55 +00:00
congzhou2603
e8dd235b5e 【bugfix】 【测试类型:接口功能】【测试版本:5.0.0】 leastconn负载均衡模式下,JDBC连接失效,数据库端连接未kill 2023-03-13 20:34:39 +08:00
opengauss-bot
94e2139993
!113 【5.0.0回合】修复JDBC节点加入心跳线程,进程不能正常退出
Merge pull request !113 from 陈紫阳/master
2023-03-10 01:39:03 +00:00
opengauss-bot
6c5cacc2f2
!112 【5.0.0回合】【bugfix】【测试类型:接口功能】【测试版本:5.0.0】 JDBC连接参数autoBalance=leastconn后,java主程序执行完成后进程不能正常退出
Merge pull request !112 from 周聪/5.0.0_bugfix_add_thread_close_method
2023-03-09 15:39:15 +00:00
congzhou2603
cf955cc1aa 【测试类型:接口功能】【测试版本:5.0.0】 JDBC连接参数autoBalance=leastconn后,java主程序执行完成后进程不能正常退出 2023-03-09 23:04:41 +08:00
opengauss-bot
d12e07384f
!111 【5.0.0】【bugfix】 JDBC连接快速负载均衡相关参数设置异常值,应该先抛错,不进行连接,现状是抛错后仍连接
Merge pull request !111 from 周聪/5.0.0_bugfix_params_parse_before_getConnection
2023-03-09 11:55:02 +00:00
congzhou2603
8f146abcf9 【bugfix】 JDBC连接快速负载均衡相关参数设置异常值,应该先抛错,不进行连接,现状是抛错后仍连接 2023-03-09 19:36:00 +08:00
opengauss-bot
19d4aa4707
!105 JDBC高可用优化
Merge pull request !105 from 陈紫阳/master
2023-02-28 14:07:46 +00:00
opengauss-bot
95a2c0031d
!103 【feature】 jdbc实现最小连接模式和集群状态变化时触发快速负载均衡
Merge pull request !103 from 周聪/dev_loadbalance_20230223_pr
2023-02-28 11:08:08 +00:00
congzhou2603
7dfd142535 jdbc实现leastconn最小连接模式和集群状态变化时的快速负载均衡 2023-02-27 22:31:38 +08:00
opengauss-bot
d780554942
!106 JDBC的cleanupTimer锁机制优化
Merge pull request !106 from 陈紫阳/master
2023-02-27 14:24:06 +00:00
chen-czywj
c74202ee5f 修改cleanupTimer原子更新器 2023-02-24 15:39:51 +08:00
opengauss-bot
803aed58bc
!102 更新版本号至5.0.0
Merge pull request !102 from 蒋宏博/master
2023-02-08 08:15:00 +00:00
55 changed files with 5604 additions and 183 deletions

View File

@ -18,6 +18,7 @@
#############################################################################
set -e
PKG_VERSION=5.0.2
BUILD_FAILED=1
JDBC_DIR=$(dirname $(readlink -f $0))
LOG_FILE=$JDBC_DIR/logfile
@ -68,6 +69,8 @@ elif [ X"$kernel" = X"asianux" ]; then
dist_version="Asianux"
elif [ X"$kernel" = X"Darwin" ]; then
dist_version="Darwin"
elif [ X"$kernel" = X"loongnix-server" ]; then
dist_version="loongnix-server"
else
echo "WARN:Only EulerOS, OPENEULER(aarch64), SUSE, CentOS and Asianux platform support, there will set to UNKNOWN"
dist_version="UNKNOWN"
@ -113,7 +116,7 @@ function install_jdbc()
export COMMIT=$(git rev-parse --short HEAD)
export OPENGAUSS_PACKAGE_NAME="org.opengauss";
export GS_VERSION="compiled at $(date +%Y-%m-%d-%H:%M:%S) build ${COMMIT}"
export GS_VERSION="openGauss ${PKG_VERSION} build ${COMMIT} compiled at $(date '+%Y-%m-%d %H:%M:%S')"
export OUTPUT_DIR="${JDBC_DIR}/output"
echo "Begin make jdbc..."
export CLASSPATH=".:${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/tools.jar"

View File

@ -142,6 +142,10 @@ Connection conn = DriverManager.getConnection(url);
The driver supports the V3 frontend/backend protocols. The V3 protocol was introduced in 7.4 and
the driver will by default try to connect using the V3 protocol.
* **quoteReturningIdentifiers** = boolean
By default we double quote returning identifiers. Some ORM's alraedy allows quote them. Switch allows them to turn this off.
* **loggerLevel** = String

View File

@ -4,7 +4,7 @@
<groupId>org.opengauss</groupId>
<artifactId>opengauss-jdbc</artifactId>
<name>openGauss JDBC Driver</name>
<version>5.0.0</version>
<version>5.0.2</version>
<description>Java JDBC driver for openGauss</description>
<url>https://gitee.com/opengauss/openGauss-connector-jdbc</url>

View File

@ -11,6 +11,8 @@ import org.postgresql.jdbc.PgConnection;
import org.postgresql.log.Logger;
import org.postgresql.log.Log;
import org.postgresql.log.Tracer;
import org.postgresql.quickautobalance.ConnectionManager;
import org.postgresql.quickautobalance.LoadBalanceHeartBeating;
import org.postgresql.util.DriverInfo;
import org.postgresql.util.GT;
import org.postgresql.util.HostSpec;
@ -558,8 +560,10 @@ public class Driver implements java.sql.Driver {
* @throws SQLException if the connection could not be made
*/
private static Connection makeConnection(String url, Properties props) throws SQLException {
ConnectionManager.getInstance().setCluster(props);
PgConnection pgConnection = new PgConnection(hostSpecs(props), user(props), database(props), props, url);
GlobalConnectionTracker.possessConnectionReference(pgConnection.getQueryExecutor(), props);
LoadBalanceHeartBeating.setConnection(pgConnection, props);
return pgConnection;
}

View File

@ -61,6 +61,17 @@ public enum PGProperty {
"Force use of a particular protocol version when connecting, currently only version 3 is supported.",
false, "3"),
/**
* Quote returning columns.
* There are some ORM's that quote everything, including returning columns
* If we quote them, then we end up sending ""colname"" to the backend
* which will not be found
*/
QUOTE_RETURNING_IDENTIFIERS("quoteReturningIdentifiers",
"true",
"Quote identifiers provided in returning array",
false),
/**
* <p>Logger level of the driver. Allowed values: {@code OFF}, {@code DEBUG} or {@code TRACE}.</p>
*
@ -472,7 +483,43 @@ public enum PGProperty {
+ "which will allow the connection to be used for logical replication "
+ "from that database. "
+ "(backend >= 9.4)"),
/**
* Enable quick auto balancing.
*
*/
ENABLE_QUICK_AUTO_BALANCE("enableQuickAutoBalance", "false",
"If the connection enable quickAutoBalance, this parameter only takes effect when autoBalance=leastconn."
+ "value: true or false.",
false, "true", "false"),
/**
* Idle time threshold of connections when quick auto balancing filters connections.
*/
MAX_IDLE_TIME_BEFORE_TERMINAL("maxIdleTimeBeforeTerminal", "30",
"During quick load balancing, if the connection is idle for more than maxIdleTimeBeforeTerminal seconds, "
+ "the connection may be closed to achieve load balancing for each data node."
+ "Value range: long && [0, Long.MAX_VALUE / 1000]."
+ "This parameter only takes effect when autoBalance=leastconn and enableQuickAutoBalance=true"),
/**
* Percentage of min reserved connections pre cluster when executing quick auto balancing.
*/
MIN_RESERVED_CON_PER_CLUSTER("minReservedConPerCluster", null,
"Percentage of min reserved connections pre cluster when executing quick auto balancing, "
+ "jdbc will retain minReservedConPerCluster percent of the connections per cluster that meet the closing conditions during quick auto balancing."
+ "Value range: int && [0, 100]."
+ "This parameter only takes effect when autoBalance=leastconn and enableQuickAutoBalance=true"),
/**
* Percentage of min reserved connections pre data node when executing quick auto balancing.
*/
MIN_RESERVED_CON_PER_DATANODE("minReservedConPerDatanode", null,
"Percentage of min reserved connections pre data node when executing quick auto balancing, "
+ "jdbc will retain minReservedConPerCluster percent of the connections pre data node that meet the closing conditions during quick auto balancing."
+ "Value range: int && [0, 100]."
+ "This parameter only takes effect when autoBalance=leastconn and enableQuickAutoBalance=true"),
/**
* Supported TLS cipher suites
*/
@ -500,6 +547,11 @@ public enum PGProperty {
* It is used to detect the thread interval of the survival task on the primary node in the high availability scenario.
*/
HEARTBEAT_PERIOD("heartbeatPeriod", "0", "heartbeat interval time"),
/**
* It is used to change timestamp parameter in TimestampUtils convert.
*/
TIMESTAMP_NANO_FORMAT("timestampNanoFormat", "0", "0 main's already add nana seconds. 1 mains nano<1000 will not add"),
/**
* In the scenario where heartbeat maintenance is enabled for the active node,
@ -510,8 +562,10 @@ public enum PGProperty {
*/
MASTER_FAILURE_HEARTBEAT_TIMEOUT("masterFailureHeartbeatTimeout", "30000", "In the scenario where heartbeat maintenance is enabled for the active node, " +
"if the active node is down, set the timeout threshold for searching for the active node. If the active node is not detected within this timeout period, " +
"the cluster is considered to have no active node and no maintenance is performed on the current cluster. This time should include the RTO time of the active node.")
"the cluster is considered to have no active node and no maintenance is performed on the current cluster. This time should include the RTO time of the active node."),
B_CMPT_MODE("BCmptMode", "true", "Specify 'dolphin.b_compatibility_mode'"
+ " connection initialization parameter.")
;
private String _name;

View File

@ -52,10 +52,11 @@ public class ClusterHeartBeat {
public static final Map<HostSpec, Set<Properties>> CLUSTER_PROPERTIES = new ConcurrentHashMap<>();
private Log LOGGER = Logger.getLogger(ClusterHeartBeat.class.getName());
private final ConnectionFactoryImpl FACTORY = new ConnectionFactoryImpl();
private final String clientEncoding = "UTF8";
private volatile boolean detection;
private final Long DEFAULT_INTERVAL = 5000L;
private final Long defaultInterval = 5000L;
private final String DEFAULT_TIMEOUT = "30000";
private volatile AtomicLong periodTime = new AtomicLong(DEFAULT_INTERVAL);
private volatile AtomicLong periodTime = new AtomicLong(defaultInterval);
/**
@ -93,7 +94,7 @@ public class ClusterHeartBeat {
}
public void initPeriodTime() {
periodTime.set(DEFAULT_INTERVAL);
periodTime.set(defaultInterval);
}
/**
@ -216,22 +217,21 @@ public class ClusterHeartBeat {
String user = props.getProperty("user", "");
String database = props.getProperty("PGDBNAME", "");
PGStream pgStream = FACTORY.tryConnect(user, database, props, socketFactory, hostSpec, sslMode);
return new QueryExecutorImpl(pgStream, user, database,
QueryExecutor queryExecutor = new QueryExecutorImpl(pgStream, user, database,
1000, new Properties());
queryExecutor.setClientEncoding(pgStream.getEncoding() != null
? pgStream.getEncoding().name() : clientEncoding);
return queryExecutor;
}
} catch (SQLException e) {
String sqlState = e.getSQLState();
if (CONNECTION_REJECTED.getState().equals(sqlState) || "28P01".equals(sqlState)) {
LOGGER.debug("node " + hostSpec + " is active, and connenction authentication fails.");
LOGGER.debug("remove before propSet size :" + propSet.size());
LOGGER.error("node " + hostSpec + " is active, and connenction authentication fails.");
removeProperties(hostSpec, props);
LOGGER.debug("remove after propSet size :" + propSet.size());
}
LOGGER.debug("acquire QueryExecutor failure " + e.getMessage());
LOGGER.error("acquire QueryExecutor failure " + e.getMessage());
} catch (IOException e) {
LOGGER.debug("acquire QueryExecutor failure " + e.getMessage());
LOGGER.debug(e.getCause());
LOGGER.error(e.getCause());
}
throw new SQLException();
}

View File

@ -8,6 +8,7 @@ package org.postgresql.core;
import org.postgresql.PGConnection;
import org.postgresql.jdbc.ClientLogic;
import org.postgresql.jdbc.FieldMetadata;
import org.postgresql.jdbc.PgStatement;
import org.postgresql.jdbc.TimestampUtils;
import org.postgresql.log.Log;
import org.postgresql.util.LruCache;
@ -17,6 +18,7 @@ import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* Driver-internal connection interface. Application code should not use this interface.
@ -228,4 +230,10 @@ public interface BaseConnection extends PGConnection, Connection {
PGXmlFactoryFactory getXmlFactoryFactory() throws SQLException;
public String getSocketAddress();
/**
* Gets the timertask atomic updater for a statement
* @return AtomicReferenceFieldUpdater<PgStatement, TimerTask>
*/
AtomicReferenceFieldUpdater<PgStatement, TimerTask> getTimerUpdater();
}

View File

@ -7,6 +7,8 @@ package org.postgresql.core;
import org.postgresql.util.CanEstimateSize;
import java.util.ArrayList;
/**
* Stores information on the parsed JDBC query. It is used to cut parsing overhead when executing
* the same query through {@link java.sql.Connection#prepareStatement(String)}.
@ -20,7 +22,8 @@ public class CachedQuery implements CanEstimateSize {
public final boolean isFunction;
public final boolean isACompatibilityFunction;
private int executeCount;
// record queries after rewrite
private final ArrayList<Query> rewriteQueries = new ArrayList<>();
public CachedQuery(Object key, Query query, boolean isFunction, boolean isACompatibilityFunction) {
assert key instanceof String || key instanceof CanEstimateSize
: "CachedQuery.key should either be String or implement CanEstimateSize."
@ -66,6 +69,42 @@ public class CachedQuery implements CanEstimateSize {
+ 100L /* entry in hash map, CachedQuery wrapper, etc */;
}
/**
* add rewrite query to list
*
* @param query the query to add
*/
public void addRewriteQueries(Query query) {
rewriteQueries.add(query);
}
/**
* get rewrite queries list
*
* @return rewrite queries list
*/
public ArrayList<Query> getRewriteQueries() {
return rewriteQueries;
}
/**
* check if rewrite queries list empty
*
* @return check result
*/
public boolean isRewriteQueriesEmpty() {
return rewriteQueries.isEmpty();
}
/**
* clear rewrite queries
*/
public void clearRewriteQueries() {
if (rewriteQueries.isEmpty()) {
return;
}
rewriteQueries.clear();
}
@Override
public String toString() {
return "CachedQuery{"

View File

@ -65,7 +65,8 @@ class CachedQueryCreateAction implements LruCache.CreateAction<Object, CachedQue
List<NativeQuery> queries = Parser.parseJdbcSql(parsedSql,
queryExecutor.getStandardConformingStrings(), isParameterized, splitStatements,
queryExecutor.isReWriteBatchedInsertsEnabled(), returningColumns);
queryExecutor.isReWriteBatchedInsertsEnabled(), queryExecutor.getQuoteReturningIdentifiers(),
returningColumns);
Query query = queryExecutor.wrap(queries);
return new CachedQuery(key, query, isFunction, isACompatibilityFunction);

View File

@ -119,9 +119,6 @@ public class Encoding {
* default JVM encoding if the specified encoding is unavailable.
*/
public static Encoding getJVMEncoding(String jvmEncoding) {
if ("UTF-8".equals(jvmEncoding)) {
return new UTF8Encoding(jvmEncoding);
}
if (Charset.isSupported(jvmEncoding)) {
return new Encoding(jvmEncoding);
} else {

View File

@ -39,6 +39,7 @@ public class Oid {
public static final int BOOL = 16;
public static final int BOOL_ARRAY = 1000;
public static final int DATE = 1082;
public static final int YEAR = 16658;
public static final int DATE_ARRAY = 1182;
public static final int TIME = 1083;
public static final int TIME_ARRAY = 1183;

View File

@ -15,8 +15,10 @@ import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
/**
* Basic query parser infrastructure.
@ -28,6 +30,16 @@ import java.util.Locale;
public class Parser {
private static final int[] NO_BINDS = new int[0];
private static final char[] chars = new char[]{' ','\n','\r','\t'};
private static final Set<String> FUNCTION_MATCH_FIRST = new HashSet<String>() {
{
add("BEGIN");
}
};
private static final Set<String> FUNCTION_SKIP_FIRST = new HashSet<String>() {
{
add("SELECT");
}
};
/**
* Parses JDBC query into PostgreSQL's native format. Several queries might be given if separated
@ -39,6 +51,7 @@ public class Parser {
* @param withParameters whether to replace ?, ? with $1, $2, etc
* @param splitStatements whether to split statements by semicolon
* @param isBatchedReWriteConfigured whether re-write optimization is enabled
* @param isQuotedReturningIdentifiers whether to quote identifiers returned using returning clause
* @param returningColumnNames for simple insert, update, delete add returning with given column names
* @return list of native queries
* @throws SQLException if unable to add returning clause (invalid column names)
@ -46,6 +59,7 @@ public class Parser {
public static List<NativeQuery> parseJdbcSql(String query, boolean standardConformingStrings,
boolean withParameters, boolean splitStatements,
boolean isBatchedReWriteConfigured,
boolean isQuotedReturningIdentifiers,
String... returningColumnNames) throws SQLException {
int numOfOverSymble = 0;
if(startWithComment(query)) {
@ -139,8 +153,8 @@ public class Parser {
}
fragmentStart = i + 1;
if (nativeSql.length() > 0) {
if (addReturning(
nativeSql, currentCommandType, returningColumnNames, isReturningPresent)) {
if (addReturning(nativeSql, currentCommandType, returningColumnNames, isReturningPresent,
isQuotedReturningIdentifiers)) {
isReturningPresent = true;
}
@ -244,7 +258,8 @@ public class Parser {
}
fragmentStart = i + 1;
if (nativeSql.length() > 0) {
if (addReturning(nativeSql, currentCommandType, returningColumnNames, isReturningPresent)) {
if (addReturning(nativeSql, currentCommandType, returningColumnNames, isReturningPresent,
isQuotedReturningIdentifiers)) {
isReturningPresent = true;
}
@ -404,7 +419,8 @@ public class Parser {
return nativeQueries != null ? nativeQueries : Collections.<NativeQuery>emptyList();
}
if (addReturning(nativeSql, currentCommandType, returningColumnNames, isReturningPresent)) {
if (addReturning(nativeSql, currentCommandType, returningColumnNames, isReturningPresent,
isQuotedReturningIdentifiers)) {
isReturningPresent = true;
}
@ -513,8 +529,9 @@ public class Parser {
return null;
}
private static boolean addReturning(StringBuilder nativeSql, SqlCommandType currentCommandType,
String[] returningColumnNames, boolean isReturningPresent) throws SQLException {
private static boolean addReturning(StringBuilder nativeSql, SqlCommandType currentCommandType,
String[] returningColumnNames, boolean isReturningPresent, boolean isQuotedReturningIdentifiers)
throws SQLException {
if (isReturningPresent || returningColumnNames.length == 0) {
return false;
}
@ -535,7 +552,11 @@ public class Parser {
if (col > 0) {
nativeSql.append(", ");
}
Utils.escapeIdentifier(nativeSql, columnName);
if (isQuotedReturningIdentifiers) {
Utils.escapeIdentifier(nativeSql, columnName);
} else {
nativeSql.append(columnName);
}
}
return true;
}
@ -640,7 +661,11 @@ public class Parser {
* @return boolean
*/
private static boolean isContainSpecialKeyword(final String[] queryArr) {
if (queryArr[0].toUpperCase(Locale.ENGLISH).equals("BEGIN")) {
String upperFirst = queryArr[0].toUpperCase(Locale.ENGLISH);
if (FUNCTION_SKIP_FIRST.contains(upperFirst)) {
return false;
}
if (FUNCTION_MATCH_FIRST.contains(upperFirst)) {
return true;
}
boolean haveCreate = false;
@ -654,6 +679,7 @@ public class Parser {
case "PROCEDURE":
case "FUNCTION":
case "DECLARE":
case "TRIGGER":
return true;
case "CREATE":
if (i == 0) {

View File

@ -445,6 +445,12 @@ public interface QueryExecutor extends TypeTransferModeRegistry {
boolean getStandardConformingStrings();
/**
* get quote identifier
*
* @return true if we are going to quote identifier provided in the returning array default is true
*/
boolean getQuoteReturningIdentifiers();
/**
* Returns backend timezone in java format.
* @return backend timezone in java format.
*/
@ -514,4 +520,17 @@ public interface QueryExecutor extends TypeTransferModeRegistry {
* @param enableOutparamOveride true or false
*/
void setEnableOutparamOveride(boolean enableOutparamOveride);
/**
* set encoding for this client
* @param clientEncoding encoding str
*/
void setClientEncoding(String clientEncoding);
/**
* Get encoding for this client
*
* @return encoding str
*/
String getClientEncoding();
}

View File

@ -41,6 +41,7 @@ public abstract class QueryExecutorBase implements QueryExecutor {
private TransactionState transactionState;
private final boolean reWriteBatchedInserts;
private final boolean columnSanitiserDisabled;
private final boolean isQuotedReturningIdentifiers;
private final PreferQueryMode preferQueryMode;
private AutoSave autoSave;
private boolean flushCacheOnDeallocate = true;
@ -64,22 +65,29 @@ public abstract class QueryExecutorBase implements QueryExecutor {
this.cancelSignalTimeout = cancelSignalTimeout;
this.reWriteBatchedInserts = PGProperty.REWRITE_BATCHED_INSERTS.getBoolean(info);
this.columnSanitiserDisabled = PGProperty.DISABLE_COLUMN_SANITISER.getBoolean(info);
this.isQuotedReturningIdentifiers = PGProperty.QUOTE_RETURNING_IDENTIFIERS.getBoolean(info);
String preferMode = PGProperty.PREFER_QUERY_MODE.get(info);
this.preferQueryMode = PreferQueryMode.of(preferMode);
this.autoSave = AutoSave.of(PGProperty.AUTOSAVE.get(info));
this.cachedQueryCreateAction = new CachedQueryCreateAction(this);
this.props = info;
statementCache = new LruCache<Object, CachedQuery>(
Math.max(0, PGProperty.PREPARED_STATEMENT_CACHE_QUERIES.getInt(info)),
Math.max(0, PGProperty.PREPARED_STATEMENT_CACHE_SIZE_MIB.getInt(info) * 1024 * 1024),
false,
cachedQueryCreateAction,
new LruCache.EvictAction<CachedQuery>() {
@Override
public void evict(CachedQuery cachedQuery) throws SQLException {
cachedQuery.query.close();
}
});
Math.max(0, PGProperty.PREPARED_STATEMENT_CACHE_QUERIES.getInt(info)),
Math.max(0, PGProperty.PREPARED_STATEMENT_CACHE_SIZE_MIB.getInt(info) * 1024 * 1024),
false,
cachedQueryCreateAction,
new LruCache.EvictAction<CachedQuery>() {
@Override
public void evict(CachedQuery cachedQuery) throws SQLException {
cachedQuery.query.close();
if (!cachedQuery.isRewriteQueriesEmpty()) {
for (Query query : cachedQuery.getRewriteQueries()) {
query.close();
}
cachedQuery.clearRewriteQueries();
}
}
});
}
protected abstract void sendCloseMessage() throws IOException;
@ -261,6 +269,11 @@ public abstract class QueryExecutorBase implements QueryExecutor {
return standardConformingStrings;
}
@Override
public boolean getQuoteReturningIdentifiers() {
return isQuotedReturningIdentifiers;
}
@Override
public synchronized TransactionState getTransactionState() {
return transactionState;

View File

@ -9,11 +9,11 @@ package org.postgresql.core;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import org.postgresql.core.v3.ConnectionFactoryImpl;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.Locale;
/**
* Collection of utilities used by the protocol-level code.
@ -45,7 +45,22 @@ public class Utils {
// for performance measurements.
// In OracleJDK 6u65, 7u55, and 8u40 String.getBytes(Charset) is
// 3 times faster than other JDK approaches.
return str.getBytes(Charset.forName(ConnectionFactoryImpl.CLIENT_ENCODING));
return encodeUTF8(str, StandardCharsets.UTF_8.name());
}
/**
* Encode a String with encoding
*
* @param str the string to encode
* @param encoding encoding type default utf-8
* @return the encoding representation of {@code str}
*/
public static byte[] encodeUTF8(String str, String encoding) {
// See org.postgresql.benchmark.encoding.UTF8Encoding#string_getBytes
// for performance measurements.
// In OracleJDK 6u65, 7u55, and 8u40 String.getBytes(Charset) is
// 3 times faster than other JDK approaches.
return str.getBytes(Charset.forName(encoding));
}
/**
@ -60,8 +75,7 @@ public class Utils {
* @return the sbuf argument; or a new string builder for sbuf == null
* @throws SQLException if the string contains a <tt>\0</tt> character
*/
public static StringBuilder escapeLiteral(StringBuilder sbuf, String value,
boolean standardConformingStrings) throws SQLException {
public static StringBuilder escapeLiteral(StringBuilder sbuf, String value, boolean standardConformingStrings) throws SQLException {
if (sbuf == null) {
sbuf = new StringBuilder((value.length() + 10) / 10 * 11); // Add 10% for escaping.
}

View File

@ -5,10 +5,10 @@
package org.postgresql.core.v3;
import org.postgresql.core.CachedQuery;
import org.postgresql.core.NativeQuery;
import org.postgresql.core.ParameterList;
/**
* Purpose of this object is to support batched query re write behaviour. Responsibility for
* tracking the batch size and implement the clean up of the query fragments after the batch execute
@ -27,6 +27,8 @@ public class BatchedQuery extends SimpleQuery {
private final int batchSize;
private BatchedQuery[] blocks;
// record the origin query of the rewrite query
private CachedQuery originalPrepareQuery;
public BatchedQuery(NativeQuery query, TypeTransferModeRegistry transferModeRegistry,
int valuesBraceOpenPosition,
int valuesBraceClosePosition, boolean sanitiserDisabled) {
@ -85,6 +87,24 @@ public class BatchedQuery extends SimpleQuery {
return sql;
}
/**
* get original prepareQuery
*
* @return the originalPrepareQuery
*/
public CachedQuery getOriginalPrepareQuery() {
return originalPrepareQuery;
}
/**
* set original prepareQuery
*
* @param originalPrepareQuery the originalPrepareQuery to be set
*/
public void setOriginalPrepareQuery(CachedQuery originalPrepareQuery) {
this.originalPrepareQuery = originalPrepareQuery;
}
private String buildNativeSql(ParameterList params) {
String buildSql = null;
// dynamically build sql with parameters for batches

View File

@ -21,6 +21,7 @@ import org.postgresql.core.Version;
import org.postgresql.hostchooser.*;
import org.postgresql.jdbc.SslMode;
import org.postgresql.QueryCNListUtils;
import org.postgresql.quickautobalance.ConnectionManager;
import org.postgresql.util.*;
import org.postgresql.log.Logger;
import org.postgresql.log.Log;
@ -59,16 +60,16 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
private static final int AUTH_REQ_GSS_CONTINUE = 8;
private static final int AUTH_REQ_SSPI = 9;
public static String CLIENT_ENCODING = "UTF8";
public String CLIENT_ENCODING = "UTF8";
public static String USE_BOOLEAN = "false";
private static final int AUTH_REQ_SHA256 = 10;
private static final int AUTH_REQ_MD5_SHA256 = 11;
private static final int AUTH_REQ_SM3 = 13;
private static final int AUTH_REQ_SM3 = 13;
private static final int PLAIN_PASSWORD = 0;
private static final int MD5_PASSWORD = 1;
private static final int SHA256_PASSWORD = 2;
private static final int SM3_PASSWORD = 3;
private static final int ERROR_PASSWORD = 4;
private static final int MD5_PASSWORD = 1;
private static final int SHA256_PASSWORD = 2;
private static final int SM3_PASSWORD = 3;
private static final int ERROR_PASSWORD = 4;
private static final int PROTOCOL_VERSION_351 = 351;
private static final int PROTOCOL_VERSION_350 = 350;
private int protocolVerion = PROTOCOL_VERSION_351;
@ -85,11 +86,11 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
CLIENT_ENCODING_WHITELIST.put("GBK", "GBK");
CLIENT_ENCODING_WHITELIST.put("LATIN1", "LATIN1");
}
public static void setStaticClientEncoding(String client) {
ConnectionFactoryImpl.CLIENT_ENCODING = client;
}
// public static void setStaticClientEncoding(String client) {
// this.CLIENT_ENCODING = client;
// }
public void setClientEncoding(String client) {
setStaticClientEncoding(client);
this.CLIENT_ENCODING = client;
}
public static void setStaticUseBoolean(String useBoolean) {
ConnectionFactoryImpl.USE_BOOLEAN = useBoolean;
@ -218,6 +219,7 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
HostChooserFactory.createHostChooser(currentHostSpecs, targetServerType, info);
Iterator<CandidateHost> hostIter = hostChooser.iterator();
boolean isMasterCluster = false;
boolean isFirstIter = true;
while (hostIter.hasNext()) {
CandidateHost candidateHost = hostIter.next();
HostSpec hostSpec = candidateHost.hostSpec;
@ -227,13 +229,19 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
// In that case, the system tries to connect to each host in order, thus it should not look into
// GlobalHostStatusTracker
HostStatus knownStatus = knownStates.get(hostSpec);
if (isFirstIter) {
isFirstIter = false;
} else {
ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpec, info);
}
if (knownStatus != null && !candidateHost.targetServerType.allowConnectingTo(knownStatus)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Known status of host " + hostSpec + " is " + knownStatus + ", and required status was " + candidateHost.targetServerType + ". Will try next host");
}
ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpec, info);
continue;
}
//
// Establish a connection.
//
@ -300,7 +308,8 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
QueryExecutor queryExecutor = new QueryExecutorImpl(newStream, user, database,
cancelSignalTimeout, info);
queryExecutor.setProtocolVersion(this.protocolVerion);
// set encoding for queryExecutor
queryExecutor.setClientEncoding(this.CLIENT_ENCODING);
//Check MasterCluster or SecondaryCluster
if (PGProperty.PRIORITY_SERVERS.get(info) != null) {
ClusterStatus currentClusterStatus = queryClusterStatus(queryExecutor);
@ -312,6 +321,7 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
GlobalClusterStatusTracker.reportMasterCluster(info, clusterSpec);
} else {
queryExecutor.close();
ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpec, info);
break;
}
}
@ -326,6 +336,7 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
knownStates.put(hostSpec, hostStatus);
if (!candidateHost.targetServerType.allowConnectingTo(hostStatus)) {
queryExecutor.close();
ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpec, info);
continue;
}
@ -353,12 +364,14 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
if (hostIter.hasNext() || clusterIter.hasNext()) {
LOGGER.info("ConnectException occured while connecting to {0}" + hostSpec, cex);
exception.addSuppressed(cex);
ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpec, info);
// still more addresses to try
continue;
}
if (exception.getSuppressed().length > 0) {
cex.addSuppressed(exception);
}
ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpec, info);
throw new PSQLException(GT.tr(
"Connection to {0} refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.",
hostSpec), PSQLState.CONNECTION_UNABLE_TO_CONNECT, cex);
@ -369,12 +382,14 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
if (hostIter.hasNext() || clusterIter.hasNext()) {
LOGGER.info("IOException occured while connecting to " + hostSpec, ioe);
exception.addSuppressed(ioe);
ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpec, info);
// still more addresses to try
continue;
}
if (exception.getSuppressed().length > 0) {
ioe.addSuppressed(exception);
}
ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpec, info);
throw new PSQLException(GT.tr("The connection attempt failed."),
PSQLState.CONNECTION_UNABLE_TO_CONNECT, ioe);
} catch (SQLException se) {
@ -385,11 +400,13 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
LOGGER.info("SQLException occured while connecting to " + hostSpec, se);
exception.addSuppressed(se);
// still more addresses to try
ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpec, info);
continue;
}
if (exception.getSuppressed().length > 0) {
se.addSuppressed(exception);
}
ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpec, info);
throw se;
}
}
@ -575,8 +592,8 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
pgStream.sendInteger2(0); // protocol minor
else if(this.protocolVerion == PROTOCOL_VERSION_350)
pgStream.sendInteger2(50); // protocol minor
else if(this.protocolVerion == PROTOCOL_VERSION_351)
pgStream.sendInteger2(51); // protocol minor
else if(this.protocolVerion == PROTOCOL_VERSION_351)
pgStream.sendInteger2(51); // protocol minor
for (byte[] encodedParam : encodedParams) {
pgStream.send(encodedParam);
pgStream.sendChar(0);

View File

@ -10,6 +10,7 @@ import org.postgresql.core.ResultCursor;
import org.postgresql.core.Utils;
import java.lang.ref.PhantomReference;
import java.nio.charset.StandardCharsets;
/**
* V3 ResultCursor implementation in terms of backend Portals. This holds the state of a single
@ -18,10 +19,15 @@ import java.lang.ref.PhantomReference;
* @author Oliver Jowett (oliver@opencloud.com)
*/
class Portal implements ResultCursor {
Portal(SimpleQuery query, String portalName) {
this(query, portalName, StandardCharsets.UTF_8.name());
}
Portal(SimpleQuery query, String portalName, String clientEncoding) {
this.query = query;
this.portalName = portalName;
this.encodedName = Utils.encodeUTF8(portalName);
this.encodedName = Utils.encodeUTF8(portalName, clientEncoding);
}
public void close() {

View File

@ -5,11 +5,12 @@
// Copyright (c) 2004, Open Cloud Limited.
package org.postgresql.core.v3;
import org.postgresql.Driver;
import org.postgresql.PGProperty;
import org.postgresql.copy.CopyIn;
import org.postgresql.copy.CopyOperation;
import org.postgresql.copy.CopyOut;
import org.postgresql.core.CachedQuery;
import org.postgresql.core.CommandCompleteParser;
import org.postgresql.core.Encoding;
import org.postgresql.core.EncodingPredictor;
@ -36,13 +37,14 @@ import org.postgresql.core.v3.replication.V3ReplicationProtocol;
import org.postgresql.jdbc.AutoSave;
import org.postgresql.jdbc.BatchResultHandler;
import org.postgresql.jdbc.TimestampUtils;
import org.postgresql.log.Log;
import org.postgresql.log.Logger;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import org.postgresql.util.PSQLWarning;
import org.postgresql.util.ServerErrorMessage;
import org.postgresql.log.Logger;
import org.postgresql.log.Log;
import java.io.IOException;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
@ -128,6 +130,8 @@ public class QueryExecutorImpl extends QueryExecutorBase {
private boolean enableOutparamOveride;
private String clientEncoding;
/**
* {@code CommandComplete(B)} messages are quite common, so we reuse instance to parse those
*/
@ -183,6 +187,16 @@ public class QueryExecutorImpl extends QueryExecutorBase {
this.enableOutparamOveride = enableOutparamOveride;
}
@Override
public void setClientEncoding(String clientEncoding) {
this.clientEncoding = clientEncoding;
}
@Override
public String getClientEncoding() {
return this.clientEncoding;
}
/**
* When database compatibility mode is A database and the parameter overload function
* is turned on, add out parameter description message.
@ -270,7 +284,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
public Query createSimpleQuery(String sql) throws SQLException {
List<NativeQuery> queries = Parser.parseJdbcSql(sql,
getStandardConformingStrings(), false, true,
isReWriteBatchedInsertsEnabled());
isReWriteBatchedInsertsEnabled(), getQuoteReturningIdentifiers());
return wrap(queries);
}
@ -745,7 +759,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
if (params.isNull(i)) {
encodedSize += 4;
} else {
encodedSize += 4 + params.getV3Length(i);
encodedSize += 4 + params.getV3Length(i, getClientEncoding());
}
}
@ -762,8 +776,8 @@ public class QueryExecutorImpl extends QueryExecutorBase {
if (params.isNull(i)) {
pgStream.sendInteger4(-1);
} else {
pgStream.sendInteger4(params.getV3Length(i)); // Parameter size
params.writeV3Value(i, pgStream);
pgStream.sendInteger4(params.getV3Length(i, getClientEncoding())); // Parameter size
params.writeV3Value(i, pgStream, getClientEncoding());
}
}
pgStream.sendInteger2(1); // Binary result format
@ -943,7 +957,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
if (!suppressBegin) {
doSubprotocolBegin();
}
byte[] buf = Utils.encodeUTF8(sql);
byte[] buf = Utils.encodeUTF8(sql, getClientEncoding());
try {
LOGGER.trace(" FE=> Query(CopyStart)");
@ -1540,6 +1554,17 @@ public class QueryExecutorImpl extends QueryExecutorBase {
pendingDescribePortalQueue.add(sync);
}
private void checkAndUpdateRewriteQueries(SimpleQuery query) {
if (!(query instanceof BatchedQuery)) {
return;
}
BatchedQuery batchedQuery = (BatchedQuery) query;
CachedQuery originalPrepareQuery = batchedQuery.getOriginalPrepareQuery();
if (originalPrepareQuery == null) {
return;
}
originalPrepareQuery.addRewriteQueries(batchedQuery);
}
private void sendParse(SimpleQuery query, SimpleParameterList params, boolean oneShot)
throws IOException {
// Already parsed, or we have a Parse pending and the types are right?
@ -1566,9 +1591,10 @@ public class QueryExecutorImpl extends QueryExecutorBase {
// NB: Must clone the OID array, as it's a direct reference to
// the SimpleParameterList's internal array that might be modified
// under us.
query.setStatementName(statementName, deallocateEpoch);
query.setStatementName(statementName, deallocateEpoch, getClientEncoding());
query.setPrepareTypes(typeOIDs);
registerParsedQuery(query, statementName);
checkAndUpdateRewriteQueries(query);
}
byte[] encodedStatementName = query.getEncodedStatementName();
@ -1622,7 +1648,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
// Send Parse.
//
byte[] queryUtf8 = Utils.encodeUTF8(nativeSql);
byte[] queryUtf8 = Utils.encodeUTF8(nativeSql, getClientEncoding());
// Total size = 4 (size field)
// + N + 1 (statement name, zero-terminated)
@ -1694,7 +1720,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
if (params.isNull(i)) {
encodedSize += 4;
} else {
encodedSize += (long) 4 + params.getV3Length(i);
encodedSize += (long) 4 + params.getV3Length(i, getClientEncoding());
}
}
@ -1774,9 +1800,9 @@ public class QueryExecutorImpl extends QueryExecutorBase {
if (params.isNull(i)) {
pgStream.sendInteger4(-1); // Magic size of -1 means NULL
} else {
pgStream.sendInteger4(params.getV3Length(i)); // Parameter size
pgStream.sendInteger4(params.getV3Length(i, getClientEncoding())); // Parameter size
try {
params.writeV3Value(i, pgStream); // Parameter value
params.writeV3Value(i, pgStream, getClientEncoding()); // Parameter value
} catch (PGBindException be) {
bindException = be;
}
@ -1823,7 +1849,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
if (params.isNull(i)) {
encodedSize += 4;
} else {
encodedSize += (long) 4 + params.getV3Length(i);
encodedSize += (long) 4 + params.getV3Length(i, getClientEncoding());
}
}
}
@ -1925,9 +1951,9 @@ public class QueryExecutorImpl extends QueryExecutorBase {
if (params.isNull(i)) {
pgStream.sendInteger4(-1); // Magic size of -1 means NULL
} else {
pgStream.sendInteger4(params.getV3Length(i)); // Parameter size
pgStream.sendInteger4(params.getV3Length(i, getClientEncoding())); // Parameter size
try {
params.writeV3Value(i, pgStream); // Parameter value
params.writeV3Value(i, pgStream, getClientEncoding()); // Parameter value
} catch (PGBindException be) {
bindException = be;
}
@ -2045,7 +2071,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
LOGGER.trace("[" + socketAddress + "] " + " FE=> ClosePortal(" + portalName + ")");
byte[] encodedPortalName = (portalName == null ? null : Utils.encodeUTF8(portalName));
byte[] encodedPortalName = (portalName == null ? null : Utils.encodeUTF8(portalName, getClientEncoding()));
int encodedSize = (encodedPortalName == null ? 0 : encodedPortalName.length);
// Total size = 4 (size field) + 1 (close type, 'P') + 1 + N (portal name)
@ -2065,7 +2091,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
LOGGER.trace("[" + socketAddress + "] " + " FE=> CloseStatement(" + statementName + ")");
byte[] encodedStatementName = Utils.encodeUTF8(statementName);
byte[] encodedStatementName = Utils.encodeUTF8(statementName, getClientEncoding());
// Total size = 4 (size field) + 1 (close type, 'S') + N + 1 (statement name)
pgStream.sendChar('C'); // Close
@ -2171,7 +2197,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
Portal portal = null;
if (usePortal) {
String portalName = "C_" + (nextUniqueID++);
portal = new Portal(query, portalName);
portal = new Portal(query, portalName, getClientEncoding());
}
sendBind(query, params, portal, noBinaryTransfer);
@ -2272,7 +2298,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
Portal portal = null;
if (usePortal) {
String portalName = "C_" + (nextUniqueID++);
portal = new Portal(query, portalName);
portal = new Portal(query, portalName, getClientEncoding());
}
sendBatchBind(query, parameterLists, portal, noBinaryTransfer, batchnum, rows);
@ -2859,6 +2885,10 @@ public class QueryExecutorImpl extends QueryExecutorBase {
int tableOid = pgStream.receiveInteger4();
short positionInTable = (short) pgStream.receiveInteger2();
int typeOid = pgStream.receiveInteger4();
if (typeOid == Oid.YEAR) {
//将year类型转为date类型opengauss最新版内核支持year类型
typeOid = Oid.DATE;
}
int typeLength = pgStream.receiveInteger2();
int typeModifier = pgStream.receiveInteger4();
int formatType = pgStream.receiveInteger2();

View File

@ -400,7 +400,7 @@ class SimpleParameterList implements V3ParameterList {
return (byte) (flags[index] & INOUT);
}
int getV3Length(int index) {
int getV3Length(int index, String clientEncoding) {
--index;
// Null?
@ -421,13 +421,13 @@ class SimpleParameterList implements V3ParameterList {
// Already encoded?
if (encoded[index] == null) {
// Encode value and compute actual length using UTF-8.
encoded[index] = Utils.encodeUTF8(paramValues[index].toString());
encoded[index] = Utils.encodeUTF8(paramValues[index].toString(), clientEncoding);
}
return encoded[index].length;
}
void writeV3Value(int index, PGStream pgStream) throws IOException {
void writeV3Value(int index, PGStream pgStream, String clientEncoding) throws IOException {
--index;
// Null?
@ -449,7 +449,7 @@ class SimpleParameterList implements V3ParameterList {
// Encoded string.
if (encoded[index] == null) {
encoded[index] = Utils.encodeUTF8((String) paramValues[index]);
encoded[index] = Utils.encodeUTF8((String) paramValues[index], clientEncoding);
}
pgStream.send(encoded[index]);
}

View File

@ -127,10 +127,10 @@ class SimpleQuery implements Query {
nativeQuery.nativeSql = sql;
}
void setStatementName(String statementName, short deallocateEpoch) {
void setStatementName(String statementName, short deallocateEpoch, String clientEncoding) {
assert statementName != null : "statement name should not be null";
this.statementName = statementName;
this.encodedStatementName = Utils.encodeUTF8(statementName);
this.encodedStatementName = Utils.encodeUTF8(statementName, clientEncoding);
this.deallocateEpoch = deallocateEpoch;
}

View File

@ -322,6 +322,22 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
}
/**
* @return quoteReturningIdentifiers
* @see PGProperty#QUOTE_RETURNING_IDENTIFIERS
*/
public boolean getQuoteReturningIdentifiers() {
return PGProperty.QUOTE_RETURNING_IDENTIFIERS.getBoolean(properties);
}
/**
* @param isQuotedIdentifiers indicate whether to quote identifiers
* @see PGProperty#QUOTE_RETURNING_IDENTIFIERS
*/
public void setQuoteReturningIdentifiers(boolean isQuotedIdentifiers) {
PGProperty.QUOTE_RETURNING_IDENTIFIERS.set(properties, isQuotedIdentifiers);
}
/**
* @return receive buffer size
* @see PGProperty#RECEIVE_BUFFER_SIZE
*/

View File

@ -12,6 +12,8 @@ import org.postgresql.PGProperty;
import org.postgresql.log.Log;
import org.postgresql.log.Logger;
import org.postgresql.QueryCNListUtils;
import org.postgresql.quickautobalance.ConnectionManager;
import org.postgresql.quickautobalance.Cluster;
import org.postgresql.util.HostSpec;
import org.postgresql.util.PSQLException;
@ -81,6 +83,9 @@ public class MultiHostChooser implements HostChooser {
private List<HostSpec> loadBalance(List<HostSpec> allHosts) {
Boolean isOutPutLog = true;
if (allHosts.size() <= 1) {
if (allHosts.size() == 1 && Objects.equals(LoadBalanceType.LeastConn, loadBalanceType)) {
ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(allHosts.get(0), info);
}
return allHosts;
}
switch (loadBalanceType) {
@ -95,6 +100,7 @@ public class MultiHostChooser implements HostChooser {
allHosts = priorityRoundRobin(allHosts);
break;
case LeastConn:
allHosts = leastConn(allHosts);
break;
default:
isOutPutLog = false;
@ -108,6 +114,7 @@ public class MultiHostChooser implements HostChooser {
}
return allHosts;
}
// Returns a counter and increments it by one.
// Because it is possible to use it in multiple instances, use synchronized (MultiHostChooser.class).
private int getRRIndex() {
@ -137,6 +144,17 @@ public class MultiHostChooser implements HostChooser {
Collections.shuffle(result.subList(1, result.size()));
return result;
}
private List<HostSpec> leastConn(List<HostSpec> hostSpecs) {
if (hostSpecs.size() <= 1) {
return hostSpecs;
}
Cluster cluster = ConnectionManager.getInstance().getCluster(URLIdentifier);
if (cluster == null) {
return hostSpecs;
}
return cluster.sortDnsByLeastConn(hostSpecs);
}
/*
* Use for RR algorithm. In case of first CN is not been connected, jdbc will

View File

@ -243,7 +243,7 @@ public class ClientLogic {
//Replace the query syntax from jdbc syntax with ? for bind parameters to $1 $2 ... $n
List<NativeQuery> queries;
try {
queries = Parser.parseJdbcSql(query, true, true, true, true);
queries = Parser.parseJdbcSql(query, true, true, true, true, true);
} catch (SQLException e) {
throw new ClientLogicException(ERROR_PARSER_FAILURE, ERROR_TEXT_PARSER_FAILURE, true);
}

View File

@ -275,6 +275,9 @@ class PgCallableStatement extends PgPreparedStatement implements CallableStateme
case Types.NCHAR:
sqlType = Types.CHAR;
break;
case -10:
sqlType = Types.REF_CURSOR;
break;
default:
break;
}

View File

@ -78,6 +78,8 @@ import java.util.TimerTask;
import java.util.concurrent.Executor;
import java.io.File;
import java.net.URISyntaxException;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.postgresql.core.types.PGClob;
import org.postgresql.core.types.PGBlob;
@ -103,6 +105,16 @@ public class PgConnection implements BaseConnection {
CONNECTION_INFO_REPORT_BLACK_LIST.put("PGDBNAME","");
}
/**set
* Protects current statement from cancelTask starting, waiting for a bit, and waking up exactly
* on subsequent query execution. The idea is to atomically compare and swap the reference to the
* task, so the task can detect that statement executes different query than the one the
* cancelTask was created. Note: the field must be set/get/compareAndSet via
* {@link #CANCEL_TIMER_UPDATER} as per {@link AtomicReferenceFieldUpdater} javadoc.
*/
private AtomicReferenceFieldUpdater<PgStatement, TimerTask> CANCEL_TIMER_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(PgStatement.class, TimerTask.class, "cancelTimerTask");
//
// Data initialized on construction:
//
@ -182,6 +194,7 @@ public class PgConnection implements BaseConnection {
private final String xmlFactoryFactoryClass;
private PGXmlFactoryFactory xmlFactoryFactory;
private String socketAddress;
private boolean isDolphinCmpt = false;
final CachedQuery borrowQuery(String sql) throws SQLException {
return queryExecutor.borrowQuery(sql);
}
@ -301,6 +314,7 @@ public class PgConnection implements BaseConnection {
return queryExecutor.getTimeZone();
}
});
timestampUtils.setTimestampNanoFormat(PGProperty.TIMESTAMP_NANO_FORMAT.getInteger(info));
// Initialize common queries.
// isParameterized==true so full parse is performed and the engine knows the query
@ -443,6 +457,9 @@ public class PgConnection implements BaseConnection {
LOGGER.trace("WARNING, unrecognized batchmode type");
batchInsert = false;
}
/* set dolphin.b_compatibility_mode to the value of PGProperty.B_CMPT_MODE */
this.setDolphinCmpt(PGProperty.B_CMPT_MODE.getBoolean(info));
initClientLogic(info);
}
@ -2061,5 +2078,27 @@ public class PgConnection implements BaseConnection {
return this.socketAddress;
}
public AtomicReferenceFieldUpdater<PgStatement, TimerTask> getTimerUpdater() {
return CANCEL_TIMER_UPDATER;
}
public boolean isDolphinCmpt() {
return isDolphinCmpt;
}
private void updateDolphinCmpt(boolean isDolphinCmpt) throws SQLException {
/* set parameter cannot use prepareStatement to set the value */
try (Statement stmt = createStatement()) {
String sql = "set dolphin.b_compatibility_mode to " + (isDolphinCmpt ? "on" : "off");
stmt.execute(sql);
} catch (SQLException e) {
throw e;
}
}
public void setDolphinCmpt(boolean isDolphinCmpt) throws SQLException {
checkClosed();
updateDolphinCmpt(isDolphinCmpt);
this.isDolphinCmpt = isDolphinCmpt;
}
}

View File

@ -32,19 +32,17 @@ import org.postgresql.util.ReaderInputStream;
import org.postgresql.log.Logger;
import org.postgresql.log.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.charset.Charset;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
@ -267,7 +265,7 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
}
bindLiteral(parameterIndex, Integer.toString(x), Oid.INT2);
}
public void setInt(int parameterIndex, int x) throws SQLException {
checkClosed();
if (connection.binaryTransferSend(Oid.INT4)) {
@ -911,12 +909,12 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
GT.tr("Cannot convert an instance of {0} to type {1}", fromType, toType),
PSQLState.INVALID_PARAMETER_TYPE, cause);
}
@Override
public void setObject(final int parameterIndex, final Object x, final SQLType targetSqlType) throws SQLException {
setObject(parameterIndex, x, targetSqlType.getVendorTypeNumber());
}
public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
setObject(parameterIndex, x, targetSqlType, -1);
}
@ -1235,7 +1233,13 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
return;
}
String str = x.getSubString(1, (int) x.length());
String str = null;
long cloblen = x.length();
if (cloblen == 0L) {
str = "";
} else {
str = x.getSubString(1, (int) cloblen);
}
setString(i, str, (connection.getStringVarcharFlag() ? Oid.VARCHAR : Oid.UNSPECIFIED));
}
@ -1447,12 +1451,30 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
throw new PSQLException(GT.tr("Object is too large to send over the protocol."),
PSQLState.NUMERIC_CONSTANT_OUT_OF_RANGE);
}
preparedParameters.setBytea(parameterIndex, value, (int) length);
setBinaryStream(parameterIndex, value, (int) length);
}
public void setBinaryStream(int parameterIndex, InputStream value) throws SQLException {
preparedParameters.setBytea(parameterIndex, value);
}
public void setBinaryStream(int parameterIndex, InputStream inputStream) throws SQLException {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
long totalLength = 0;
byte[] buffer = new byte[2048];
int readLength = inputStream.read(buffer);
while (readLength > 0) {
totalLength += readLength;
outputStream.write(buffer, 0, readLength);
if (totalLength >= Integer.MAX_VALUE) {
throw new PSQLException(GT.tr("Object is too large to send over the protocol."), PSQLState.NUMERIC_CONSTANT_OUT_OF_RANGE);
}
readLength = inputStream.read(buffer);
}
byte[] sourceBytes = outputStream.toByteArray();
InputStream copiedInputStream = new ByteArrayInputStream(sourceBytes);
setBinaryStream(parameterIndex, copiedInputStream, sourceBytes.length);
} catch (IOException e) {
throw new PSQLException(GT.tr("Read ObjectLength error."), PSQLState.NUMERIC_CONSTANT_OUT_OF_RANGE);
}
}
public void setAsciiStream(int parameterIndex, InputStream value, long length)
throws SQLException {
@ -1528,7 +1550,7 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
throw new SQLException(e1.getMessage());
}
}
@Override
public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
checkClosed();
@ -1539,7 +1561,7 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
return;
}
preparedParameters.setBlob(parameterIndex, inputStream, (int)length);
}
@ -1584,7 +1606,7 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
// Note: in batch prepared statements batchStatements == 1, and batchParameters is equal
// to the number of addBatch calls
// batchParameters might be empty in case of empty batch
if (batchParameters != null && batchParameters.size() > 1 && m_prepareThreshold > 0) {
if (batchParameters != null && batchParameters.size() >= 1 && m_prepareThreshold > 0) {
// Use server-prepared statements when there's more than one statement in a batch
// Technically speaking, it might cause to create a server-prepared statement
// just for 2 executions even for prepareThreshold=5. That however should be
@ -1658,6 +1680,7 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
}
// Find appropriate batch for block count.
BatchedQuery bq = originalQuery.deriveForMultiBatch(valueBlock);
bq.setOriginalPrepareQuery(preparedQuery);
ParameterList newPl = bq.createParameterList();
for (int j = 0; j < valueBlock; j++) {
ParameterList pl = batchParameters.get(offset++);

View File

@ -57,12 +57,15 @@ import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.UUID;
@ -119,6 +122,23 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
private ResultSetMetaData rsMetaData;
private static final String TINYBLOB_TYPNAME = "tinyblob";
private static final String BLOB_TYPNAME = "blob";
private static final String MEDIUMBLOB_TYPNAME = "mediumblob";
private static final String LONGBLOB_TYPNAME = "longblob";
private static final String BINARY = "binary";
private static final String VARBINARY = "varbinary";
private static final Set<String> blobSet =
new HashSet<>(Arrays.asList(TINYBLOB_TYPNAME, BLOB_TYPNAME, MEDIUMBLOB_TYPNAME, LONGBLOB_TYPNAME));
private static final Set<String> binarySet = new HashSet<>(Arrays.asList(BINARY, VARBINARY));
protected ResultSetMetaData createMetaData() throws SQLException {
return new PgResultSetMetaData(connection, fields);
}
@ -250,6 +270,8 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
return getInt(columnIndex);
case Types.BIGINT:
return getLong(columnIndex);
case TypeInfoCache.bIntegerType:
return getBigInteger(columnIndex);
case Types.NUMERIC:
case Types.DECIMAL:
return getBigDecimal(columnIndex,
@ -490,7 +512,21 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
return bytes;
}
private String getBlobRaw(int i) throws SQLException {
Encoding encoding = connection.getEncoding();
try {
return trimString(i, encoding.decode(this_row[i - 1]));
} catch (IOException ioe) {
throw new PSQLException(
GT.tr("Invalid character data was found. "
+ "This is most likely caused by stored data containing characters that are invalid for the "
+ "character set the database was created in. The most common example of this is storing 8bit "
+ "data in a SQL_ASCII database."),
PSQLState.DATA_ERROR, ioe);
}
}
public Blob getBlob(int i) throws SQLException {
checkResultSet(i);
if (wasNullFlag) {
@ -501,8 +537,15 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
blob.setBytes(1, this_row[i - 1]);
return blob;
}
String s = getString(i);
byte[] byt = toBytes(s);
int oid = this.fields[i - 1].getOID();
byte[] byt;
if (oid == Oid.BYTEA) {
byt = trimBytes(i, PGbytea.toBytes(this_row[i - 1]));
} else if (oid == Oid.BLOB || blobSet.contains(getPGType(i))) {
byt = toBytes(getBlobRaw(i));
} else {
byt = trimBytes(i, this_row[i - 1]);
}
PGBlob blob = new PGBlob();
blob.setBytes(1, byt);
return blob;
@ -2016,27 +2059,43 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
// varchar in binary is same as text, other binary fields are converted to their text format
if (isBinary(columnIndex) && getSQLType(columnIndex) != Types.VARCHAR) {
Field field = fields[columnIndex - 1];
Object obj = internalGetObject(columnIndex, field);
if (obj == null) {
// internalGetObject() knows jdbc-types and some extra like hstore. It does not know of
// PGobject based types like geometric types but getObject does
obj = getObject(columnIndex);
if (obj == null) {
return null;
}
return obj.toString();
return binaryIndex(columnIndex);
}
Encoding encoding = connection.getEncoding();
try {
String typeName = getPGType(columnIndex);
String result = trimString(columnIndex, encoding.decode(this_row[columnIndex - 1]));
if (("blob".equals(typeName))) {
if (connection.unwrap(PgConnection.class).isDolphinCmpt()) {
return new String(toBytes(result));
}
} else if (blobSet.contains(typeName)) {
return new String(toBytes(result));
}
// hack to be compatible with text protocol
if (obj instanceof java.util.Date) {
int oid = field.getOID();
return connection.getTimestampUtils().timeToString((java.util.Date) obj,
oid == Oid.TIMESTAMPTZ || oid == Oid.TIMETZ);
}
if ("hstore".equals(getPGType(columnIndex))) {
return HStoreConverter.toString((Map<?, ?>) obj);
}
return trimString(columnIndex, obj.toString());
return result;
} catch (IOException ioe) {
throw new PSQLException(
GT.tr(
"Invalid character data was found. This is most likely caused by stored data containing characters that are invalid " +
"for the character set the database was created in. The most common example of this is storing 8bit data in a SQL_ASCII database."),
PSQLState.DATA_ERROR, ioe);
}
}
/**
* Processing of Blob related types
*/
public String getBlobSetString(int columnIndex) throws SQLException {
connection.getLogger().trace("[" + connection.getSocketAddress() + "] " + " getString columnIndex: " + columnIndex);
checkResultSet(columnIndex);
if (wasNullFlag) {
return null;
}
// varchar in binary is same as text, other binary fields are converted to their text format
if (isBinary(columnIndex) && getSQLType(columnIndex) != Types.VARCHAR) {
return binaryIndex(columnIndex);
}
Encoding encoding = connection.getEncoding();
@ -2045,11 +2104,36 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
} catch (IOException ioe) {
throw new PSQLException(
GT.tr(
"Invalid character data was found. This is most likely caused by stored data containing characters that are invalid for the character set the database was created in. The most common example of this is storing 8bit data in a SQL_ASCII database."),
"Invalid character data was found. This is most likely caused by stored data containing characters that are invalid " +
"for the character set the database was created in. The most common example of this is storing 8bit data in a SQL_ASCII database."),
PSQLState.DATA_ERROR, ioe);
}
}
private String binaryIndex(int columnIndex) throws SQLException {
Field field = fields[columnIndex - 1];
Object obj = internalGetObject(columnIndex, field);
if (obj == null) {
// internalGetObject() knows jdbc-types and some extra like hstore. It does not know of
// PGobject based types like geometric types but getObject does
obj = getObject(columnIndex);
if (obj == null) {
return null;
}
return obj.toString();
}
// hack to be compatible with text protocol
if (obj instanceof java.util.Date) {
int oid = field.getOID();
return connection.getTimestampUtils().timeToString((java.util.Date) obj,
oid == Oid.TIMESTAMPTZ || oid == Oid.TIMETZ);
}
if ("hstore".equals(getPGType(columnIndex))) {
return HStoreConverter.toString((Map<?, ?>) obj);
}
return trimString(columnIndex, obj.toString());
}
/**
* <p>Retrieves the value of the designated column in the current row of this <code>ResultSet</code>
* object as a <code>boolean</code> in the Java programming language.</p>
@ -2233,6 +2317,19 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
return toLong(getFixedString(columnIndex));
}
public BigInteger getBigInteger(int columnIndex) throws SQLException {
String stringVal = getString(columnIndex);
if (stringVal == null) {
return null;
}
try {
return new BigInteger(stringVal);
} catch (NumberFormatException ex) {
connection.getLogger().trace("[" + connection.getSocketAddress() + "] " + "format BigInteger failed.");
}
return null;
}
/**
* A dummy exception thrown when fast byte[] to number parsing fails and no value can be returned.
* The exact stack trace does not matter because the exception is always caught and is not visible
@ -2706,9 +2803,9 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
wasNullFlag = true;
return null;
}
if(getPGType(columnIndex).equals("blob")){
return toBytes(getString(columnIndex));
if(blobSet.contains(getPGType(columnIndex))){
return toBytes(getBlobSetString(columnIndex));
}
Object result = internalGetObject(columnIndex, field);
@ -2716,6 +2813,9 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
return result;
}
if (binarySet.contains(getPGType(columnIndex))) {
return this_row[columnIndex - 1];
}
if (isBinary(columnIndex)) {
return connection.getObject(getPGType(columnIndex), null, this_row[columnIndex - 1]);
}

View File

@ -7,6 +7,8 @@ package org.postgresql.jdbc;
import org.postgresql.Driver;
import org.postgresql.core.*;
import org.postgresql.quickautobalance.ConnectionManager;
import org.postgresql.quickautobalance.LoadBalanceHeartBeating;
import org.postgresql.util.GT;
import org.postgresql.util.PGbytea;
import org.postgresql.util.PSQLException;
@ -22,7 +24,6 @@ import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.TimerTask;
import org.postgresql.core.v3.ConnectionFactoryImpl;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
@ -52,11 +53,9 @@ public class PgStatement implements Statement, BaseStatement {
* on subsequent query execution. The idea is to atomically compare and swap the reference to the
* task, so the task can detect that statement executes different query than the one the
* cancelTask was created. Note: the field must be set/get/compareAndSet via
* {@link #CANCEL_TIMER_UPDATER} as per {@link AtomicReferenceFieldUpdater} javadoc.
* {@link PgConnection CANCEL_TIMER_UPDATER} as per {@link AtomicReferenceFieldUpdater} javadoc.
*/
private volatile TimerTask cancelTimerTask = null;
private static final AtomicReferenceFieldUpdater<PgStatement, TimerTask> CANCEL_TIMER_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(PgStatement.class, TimerTask.class, "cancelTimerTask");
public volatile TimerTask cancelTimerTask = null;
/**
* Protects statement from out-of-order cancels. It protects from both
@ -301,7 +300,7 @@ public class PgStatement implements Statement, BaseStatement {
public boolean executeWithFlags(String sql, int flags) throws SQLException {
return executeCachedSql(sql, flags, NO_RETURNING_COLUMNS);
}
private boolean executeCachedSql(String sql, int flags, String[] columnNames) throws SQLException {
PreferQueryMode preferQueryMode = connection.getPreferQueryMode();
// Simple statements should not replace ?, ? with $1, $2
@ -955,13 +954,12 @@ public class PgStatement implements Statement, BaseStatement {
flags |= QueryExecutor.QUERY_EXECUTE_AS_SIMPLE;
}
boolean sameQueryAhead = queries.length > 1 && queries[0] == queries[1];
boolean sameQueryAhead = (queries.length == 1) || (queries.length > 1 && queries[0] == queries[1]);
if (!sameQueryAhead
if (!sameQueryAhead || isOneShotQuery(null)) {
// If executing the same query twice in a batch, make sure the statement
// is server-prepared. In other words, "oneshot" only if the query is one in the batch
// or the queries are different
|| isOneShotQuery(null)) {
flags |= QueryExecutor.QUERY_ONESHOT;
} else {
// If a batch requests generated keys and isn't already described,
@ -1117,12 +1115,14 @@ public class PgStatement implements Statement, BaseStatement {
// Not in query, there's nothing to cancel
return;
}
setConnectionState(StatementCancelState.CANCELING);
// Synchronize on connection to avoid spinning in killTimerTask
synchronized (connection) {
try {
connection.cancelQuery();
} finally {
STATE_UPDATER.set(this, StatementCancelState.CANCELLED);
setConnectionState(StatementCancelState.CANCELLED);
connection.notifyAll(); // wake-up killTimerTask
}
}
@ -1166,14 +1166,14 @@ public class PgStatement implements Statement, BaseStatement {
fetchSize = rows;
}
private void startTimer() {
private void startTimer() throws SQLException {
/*
* there shouldn't be any previous timer active, but better safe than sorry.
*/
cleanupTimer();
STATE_UPDATER.set(this, StatementCancelState.IN_QUERY);
setConnectionState(StatementCancelState.IN_QUERY);
if (timeout == 0) {
return;
}
@ -1181,7 +1181,7 @@ public class PgStatement implements Statement, BaseStatement {
TimerTask cancelTask = new TimerTask() {
public void run() {
try {
if (!CANCEL_TIMER_UPDATER.compareAndSet(PgStatement.this, this, null)) {
if (!connection.getTimerUpdater().compareAndSet(PgStatement.this, this, null)) {
// Nothing to do here, statement has already finished and cleared
// cancelTimerTask reference
return;
@ -1193,7 +1193,7 @@ public class PgStatement implements Statement, BaseStatement {
}
};
CANCEL_TIMER_UPDATER.set(this, cancelTask);
connection.getTimerUpdater().set(this, cancelTask);
connection.addTimerTask(cancelTask, timeout);
}
@ -1202,12 +1202,12 @@ public class PgStatement implements Statement, BaseStatement {
* never invoke {@link #cancel()}.
*/
private boolean cleanupTimer() {
TimerTask timerTask = CANCEL_TIMER_UPDATER.get(this);
TimerTask timerTask = connection.getTimerUpdater().get(this);
if (timerTask == null) {
// If timeout is zero, then timer task did not exist, so we safely report "all clear"
return timeout == 0;
}
if (!CANCEL_TIMER_UPDATER.compareAndSet(this, timerTask, null)) {
if (!connection.getTimerUpdater().compareAndSet(this, timerTask, null)) {
// Failed to update reference -> timer has just fired, so we must wait for the query state to
// become "cancelling".
return false;
@ -1218,13 +1218,20 @@ public class PgStatement implements Statement, BaseStatement {
return true;
}
private void killTimerTask() {
private void setConnectionState(StatementCancelState state) throws SQLException {
if (LoadBalanceHeartBeating.isLoadBalanceHeartBeatingStarted() && this.connection instanceof PgConnection) {
ConnectionManager.getInstance().setConnectionState(this.connection.unwrap(PgConnection.class), state);
}
}
private void killTimerTask() throws SQLException {
boolean timerTaskIsClear = cleanupTimer();
// The order is important here: in case we need to wait for the cancel task, the state must be
// kept StatementCancelState.IN_QUERY, so cancelTask would be able to cancel the query.
// It is believed that this case is very rare, so "additional cancel and wait below" would not
// harm it.
if (timerTaskIsClear && STATE_UPDATER.compareAndSet(this, StatementCancelState.IN_QUERY, StatementCancelState.IDLE)) {
setConnectionState(StatementCancelState.IDLE);
return;
}
@ -1245,6 +1252,7 @@ public class PgStatement implements Statement, BaseStatement {
interrupted = true;
}
}
setConnectionState(StatementCancelState.IDLE);
}
if (interrupted) {
Thread.currentThread().interrupt();

View File

@ -8,7 +8,7 @@ package org.postgresql.jdbc;
/**
* Represents {@link PgStatement#cancel()} state.
*/
enum StatementCancelState {
public enum StatementCancelState {
IDLE,
IN_QUERY,
CANCELING,

View File

@ -59,6 +59,7 @@ public class TimestampUtils {
private TimeZone prevDefaultZoneFieldValue;
private TimeZone defaultTimeZoneCache;
private int timestampNanoFormat = 0;
static {
// The expected maximum value is 60 (seconds), so 64 is used "just in case"
@ -130,6 +131,10 @@ public class TimestampUtils {
this.usesDouble = usesDouble;
this.timeZoneProvider = timeZoneProvider;
}
public void setTimestampNanoFormat(int format) {
this.timestampNanoFormat = format;
}
private Calendar getCalendar(int sign, int hr, int min, int sec) {
int rawOffset = sign * (((hr * 60 + min) * 60 + sec) * 1000);
@ -211,7 +216,7 @@ public class TimestampUtils {
// trailing whitespace
try {
int start = skipWhitespace(s, 0); // Skip leading whitespace
int start = firstDigit(s, 0); // Skip leading whitespace
int end = firstNonDigit(s, start);
int num;
char sep;
@ -243,6 +248,11 @@ public class TimestampUtils {
result.day = number(s, start, end);
start = skipWhitespace(s, end); // Skip trailing whitespace
} else if (s.length == end) {
result.year = number(s, start, end);
result.month = 1;
result.day = 1;
return result;
}
// Possibly read time.
@ -661,7 +671,7 @@ public class TimestampUtils {
appendDate(sbuf, cal);
sbuf.append(' ');
appendTime(sbuf, cal, nanos);
appendTime(sbuf, cal, nanos, timestampNanoFormat);
if (withTimeZone) {
appendTimeZone(sbuf, cal);
}
@ -708,7 +718,7 @@ public class TimestampUtils {
sbuf.setLength(0);
appendTime(sbuf, cal, cal.get(Calendar.MILLISECOND) * 1000000);
appendTime(sbuf, cal, cal.get(Calendar.MILLISECOND) * 1000000, timestampNanoFormat);
// The 'time' parser for <= 7.3 doesn't like timezones.
if (withTimeZone) {
@ -742,11 +752,11 @@ public class TimestampUtils {
sb.append(NUMBERS[day]);
}
private static void appendTime(StringBuilder sb, Calendar cal, int nanos) {
private static void appendTime(StringBuilder sb, Calendar cal, int nanos, int format) {
int hours = cal.get(Calendar.HOUR_OF_DAY);
int minutes = cal.get(Calendar.MINUTE);
int seconds = cal.get(Calendar.SECOND);
appendTime(sb, hours, minutes, seconds, nanos);
appendTime(sb, hours, minutes, seconds, nanos, format);
}
/**
@ -759,7 +769,7 @@ public class TimestampUtils {
* @param seconds seconds
* @param nanos nanoseconds
*/
private static void appendTime(StringBuilder sb, int hours, int minutes, int seconds, int nanos) {
private static void appendTime(StringBuilder sb, int hours, int minutes, int seconds, int nanos, int format) {
sb.append(NUMBERS[hours]);
sb.append(':');
@ -773,21 +783,24 @@ public class TimestampUtils {
// a two digit fractional second, but we don't need to support 7.1
// anymore and getting the version number here is difficult.
//
if (nanos < 1000) {
if (nanos < 1000 && format != 0) {
return;
}
sb.append('.');
int len = sb.length();
sb.append(nanos / 1000); // append microseconds
int needZeros = 6 - (sb.length() - len);
final int NANO_BITS = 6;
int needZeros = NANO_BITS - (sb.length() - len);
if (needZeros > 0) {
sb.insert(len, ZEROS, 0, needZeros);
}
int end = sb.length() - 1;
while (sb.charAt(end) == '0') {
int needDelete = NANO_BITS - 1;
while (sb.charAt(end) == '0' && needDelete != 0) {
sb.deleteCharAt(end);
end--;
needDelete --;
}
}
@ -855,7 +868,7 @@ public class TimestampUtils {
// it relies on the fact that appendTime just truncates 000..999 nanosecond part
localTime = localTime.plus(ONE_MICROSECOND);
}
appendTime(sbuf, localTime);
appendTime(sbuf, localTime, timestampNanoFormat);
return sbuf.toString();
}
@ -879,7 +892,7 @@ public class TimestampUtils {
LocalDate localDate = localDateTime.toLocalDate();
appendDate(sbuf, localDate);
sbuf.append(' ');
appendTime(sbuf, localDateTime.toLocalTime());
appendTime(sbuf, localDateTime.toLocalTime(), timestampNanoFormat);
appendTimeZone(sbuf, offsetDateTime.getOffset());
appendEra(sbuf, localDate);
@ -911,12 +924,12 @@ public class TimestampUtils {
appendDate(sb, year, month, day);
}
private static void appendTime(StringBuilder sb, LocalTime localTime) {
private static void appendTime(StringBuilder sb, LocalTime localTime, int format) {
int hours = localTime.getHour();
int minutes = localTime.getMinute();
int seconds = localTime.getSecond();
int nanos = localTime.getNano();
appendTime(sb, hours, minutes, seconds, nanos);
appendTime(sb, hours, minutes, seconds, nanos, format);
}
private void appendTimeZone(StringBuilder sb, java.time.ZoneOffset offset) {
@ -941,6 +954,16 @@ public class TimestampUtils {
return slen;
}
private static int firstDigit(char[] s, int start) {
int slen = s.length;
for (int i = start; i < slen; i++) {
if (Character.isDigit(s[i])) {
return i;
}
}
return slen;
}
private static int firstNonDigit(char[] s, int start) {
int slen = s.length;
for (int i = start; i < slen; i++) {

View File

@ -61,6 +61,8 @@ public class TypeInfoCache implements TypeInfo {
private PreparedStatement _getArrayDelimiterStatement;
private PreparedStatement _getTypeInfoStatement;
public static final int bIntegerType = 95;
// basic pg types info:
// 0 - type name
// 1 - type oid
@ -73,6 +75,10 @@ public class TypeInfoCache implements TypeInfo {
{"int4", Oid.INT4, Types.INTEGER, "java.lang.Integer", Oid.INT4_ARRAY},
{"oid", Oid.OID, Types.BIGINT, "java.lang.Long", Oid.OID_ARRAY},
{"int8", Oid.INT8, Types.BIGINT, "java.lang.Long", Oid.INT8_ARRAY},
{"uint1", Oid.UNSPECIFIED, Types.SMALLINT, "java.lang.Integer", Oid.INT1_ARRAY},
{"uint2", Oid.UNSPECIFIED, Types.INTEGER, "java.lang.Integer", Oid.INT2_ARRAY},
{"uint4", Oid.UNSPECIFIED, Types.BIGINT, "java.lang.Long", Oid.INT4_ARRAY},
{"uint8", Oid.UNSPECIFIED, bIntegerType, "java.math.BigInteger", Oid.INT8_ARRAY},
{"money", Oid.MONEY, Types.DOUBLE, "java.lang.Double", Oid.MONEY_ARRAY},
{"numeric", Oid.NUMERIC, Types.NUMERIC, "java.math.BigDecimal", Oid.NUMERIC_ARRAY},
{"float4", Oid.FLOAT4, Types.REAL, "java.lang.Float", Oid.FLOAT4_ARRAY},

View File

@ -0,0 +1,669 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.postgresql.quickautobalance;
import org.postgresql.Driver;
import org.postgresql.PGProperty;
import org.postgresql.jdbc.PgConnection;
import org.postgresql.jdbc.StatementCancelState;
import org.postgresql.log.Log;
import org.postgresql.log.Logger;
import org.postgresql.quickautobalance.DataNode.CheckDnStateResult;
import org.postgresql.util.GT;
import org.postgresql.util.HostSpec;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* A Cluster, which cached the information of the dataNodes in this cluster.
*/
public class Cluster {
private static Log LOGGER = Logger.getLogger(Cluster.class.getName());
private static final double CLOSE_CONNECTION_PERCENTAGE_EACH_TIME = 0.2d;
private static final int MIN_RESERVED_CON_UNSET_PARAMS = -1;
private final String urlIdentifier;
private final Set<HostSpec> dns;
private final Queue<ConnectionInfo> abandonedConnectionList;
private final Map<HostSpec, DataNode> cachedDnList;
private final List<Properties> cachedPropertiesList;
// Percentage of minimum reserved connections that can be closed in a cluster, value range: [0,100].
private volatile int minReservedConPerCluster;
private volatile boolean enableMinReservedConPerCluster;
// Percentage of minimum reserved connections that can be closed in a datanode, value range: [0,100].
private volatile int minReservedConPerDatanode;
private volatile boolean enableMinReservedConPerDatanode;
private volatile long quickAutoBalanceStartTime;
private int totalAbandonedConnectionSize;
public Cluster(final String urlIdentifier, final Properties properties) throws PSQLException {
this.urlIdentifier = urlIdentifier;
HostSpec[] hostSpecs = Driver.getURLHostSpecs(properties);
this.dns = new HashSet<>();
this.dns.addAll(Arrays.asList(hostSpecs));
this.cachedDnList = new ConcurrentHashMap<>();
for (HostSpec hostSpec : hostSpecs) {
DataNode dataNode = new DataNode(hostSpec);
this.cachedDnList.put(hostSpec, dataNode);
}
updateParams(properties);
this.abandonedConnectionList = new ConcurrentLinkedQueue<>();
this.cachedPropertiesList = new Vector<>();
this.cachedPropertiesList.add(properties);
this.quickAutoBalanceStartTime = 0;
this.totalAbandonedConnectionSize = 0;
}
/**
* set connection state of cached connections if it exists.
*
* @param pgConnection pgConnection
* @param state new state
*/
public void setConnectionState(final PgConnection pgConnection, final StatementCancelState state) {
String socketAddress = pgConnection.getSocketAddress();
HostSpec hostSpec = calculateHostSpec(socketAddress);
if (hostSpec != null && !dns.contains(hostSpec)) {
return;
}
DataNode dataNode = cachedDnList.get(hostSpec);
if (dataNode != null) {
dataNode.setConnectionState(pgConnection, state);
}
}
// calculate the hostSpec of destination
private HostSpec calculateHostSpec(String socketAddress) {
String urlClient = socketAddress.split("/")[1];
String[] urlClientSplit = urlClient.split(":");
if (urlClientSplit.length == 2) {
String host = urlClientSplit[0];
int port = Integer.parseInt(urlClientSplit[1]);
return new HostSpec(host, port);
} else {
return null;
}
}
/**
* Add a new connection.
*
* @param pgConnection pgConnection
* @param properties properties
*/
public void setConnection(final PgConnection pgConnection, final Properties properties)
throws PSQLException {
if (pgConnection == null || properties == null) {
return;
}
String socketAddress = pgConnection.getSocketAddress();
HostSpec hostSpec = calculateHostSpec(socketAddress);
if (hostSpec != null && !dns.contains(hostSpec)) {
return;
}
setProperties(properties);
synchronized (cachedDnList) {
cachedDnList.get(hostSpec).setConnection(pgConnection, properties, hostSpec);
decrementCachedCreatingConnectionSize(hostSpec);
updateParams(properties);
}
}
/**
* Set new properties if the user doesn't exist in cachedPropertiesList,
* or update properties if exists.
*
* @param properties properties
*/
public void setProperties(Properties properties) {
synchronized (cachedPropertiesList) {
for (int i = 0; i < cachedPropertiesList.size(); i++) {
Properties prop = cachedPropertiesList.get(i);
if (prop.getProperty("user", "").equals(properties.getProperty("user", null))) {
cachedPropertiesList.set(i, properties);
return;
}
}
cachedPropertiesList.add(properties);
}
}
/**
* CacheCreatingConnectionNum - 1;
*
* @param hostSpec hostSpec
* @return cachedCreatingConnectionNum
*/
public int decrementCachedCreatingConnectionSize(final HostSpec hostSpec) {
if (!cachedDnList.containsKey(hostSpec)) {
LOGGER.error(GT.tr("Can not find hostSpec: {0} in Cluster: {1}.", hostSpec.toString(), urlIdentifier));
return 0;
}
DataNode dataNode = cachedDnList.get(hostSpec);
if (dataNode != null) {
return dataNode.decrementCachedCreatingConnectionSize();
} else {
LOGGER.error(GT.tr("Can not find hostSpec: {0} in Cluster: {1}.", hostSpec.toString(), urlIdentifier));
return 0;
}
}
private void updateMinReservedConPerCluster(Properties properties) throws PSQLException {
int perCluster = parseMinReservedConPerCluster(properties);
if (perCluster == MIN_RESERVED_CON_UNSET_PARAMS) {
return;
}
if (this.enableMinReservedConPerCluster) {
this.minReservedConPerCluster = Math.min(this.minReservedConPerCluster, perCluster);
} else {
this.enableMinReservedConPerCluster = true;
this.minReservedConPerCluster = perCluster;
}
}
private void updateMinReservedConPerDatanode(Properties properties) throws PSQLException {
int perDatanode = parseMinReservedConPerDatanode(properties);
if (perDatanode == MIN_RESERVED_CON_UNSET_PARAMS) {
return;
}
if (this.enableMinReservedConPerDatanode) {
this.minReservedConPerDatanode = Math.min(this.minReservedConPerDatanode, perDatanode);
} else {
this.enableMinReservedConPerDatanode = true;
this.minReservedConPerDatanode = perDatanode;
}
}
/**
* Parse minReservedConPerCluster, value range: [0, 100].
* return -1 if minReservedConPerCluster isn't configured.
*
* @param properties properties
* @return minReservedConPerCluster
* @throws PSQLException minReservedConPerCluster parse failed
*/
public static int parseMinReservedConPerCluster(Properties properties) throws PSQLException {
int perCluster;
String param = PGProperty.MIN_RESERVED_CON_PER_CLUSTER.get(properties);
if (param == null) {
return MIN_RESERVED_CON_UNSET_PARAMS;
}
try {
perCluster = Integer.parseInt(param);
} catch (NumberFormatException e) {
throw new PSQLException(GT.tr("Parameter minReservedConPerCluster={0} parsed failed, value range: int && [0, 100]."
, PGProperty.MIN_RESERVED_CON_PER_CLUSTER.get(properties)), PSQLState.INVALID_PARAMETER_TYPE);
}
if (perCluster < 0 || perCluster > 100) {
throw new PSQLException(GT.tr("Parameter minReservedConPerCluster={0} parsed failed, value range: int && [0, 100]."
, String.valueOf(perCluster)), PSQLState.INVALID_PARAMETER_VALUE);
} else {
return perCluster;
}
}
/**
* Parse minReservedConPerDatanode, value range: [0, 100].
* return -1 if minReservedConPerDatanode isn't configured.
*
* @param properties properties
* @return minReservedConPerDatanode
* @throws PSQLException minReservedConPerDatanode parse failed
*/
public static int parseMinReservedConPerDatanode(Properties properties) throws PSQLException {
int perDatanode;
String param = PGProperty.MIN_RESERVED_CON_PER_DATANODE.get(properties);
if (param == null) {
return MIN_RESERVED_CON_UNSET_PARAMS;
}
try {
perDatanode = Integer.parseInt(param);
} catch (NumberFormatException e) {
throw new PSQLException(GT.tr("Parameter minReservedConPerDatanode={0} parsed failed, value range: int && [0, 100]."
, PGProperty.MIN_RESERVED_CON_PER_DATANODE.get(properties)), PSQLState.INVALID_PARAMETER_TYPE);
}
if (perDatanode < 0 || perDatanode > 100) {
throw new PSQLException(GT.tr("Parameter minReservedConPerDatanode={0} parsed failed, value range: " +
"int && [0, 100].", String.valueOf(perDatanode)), PSQLState.INVALID_PARAMETER_VALUE);
} else {
return perDatanode;
}
}
private void updateParams(Properties properties) throws PSQLException {
updateMinReservedConPerCluster(properties);
updateMinReservedConPerDatanode(properties);
}
/**
* Get connection info of the connection if exists.
*
* @param connection connection
* @return connection info
*/
public ConnectionInfo getConnectionInfo(PgConnection connection) {
String socketAddress = connection.getSocketAddress();
HostSpec hostSpec = calculateHostSpec(socketAddress);
DataNode dataNode = cachedDnList.get(hostSpec);
return hostSpec != null && dataNode != null ? dataNode.getConnectionInfo(connection) : null;
}
/**
* Sort hostSpec list in ascending order by amount of connections.
* Put the hostSpec to the tail, if dataNodeState = false.
*
* @param hostSpecs host specs
* @return hostSpec list
*/
public synchronized List<HostSpec> sortDnsByLeastConn(List<HostSpec> hostSpecs) {
// Copy dataNodeCompareInfo from cachedDnList.
Map<HostSpec, DataNodeCompareInfo> dataNodeCompareInfoMap = hostSpecs.stream()
.collect(Collectors.toMap(Function.identity(), hostSpec -> {
DataNode dataNode = cachedDnList.get(hostSpec);
int cachedConnectionListSize = dataNode.getCachedConnectionListSize();
int cachedCreatingConnectionSize = dataNode.getCachedCreatingConnectionSize();
boolean dataNodeState = dataNode.getDataNodeState();
return new DataNodeCompareInfo(cachedConnectionListSize, cachedCreatingConnectionSize, dataNodeState);
}));
hostSpecs.sort((o1, o2) -> {
boolean o1State = dataNodeCompareInfoMap.get(o1).getDataNodeState();
boolean o2State = dataNodeCompareInfoMap.get(o2).getDataNodeState();
if (!o1State && o2State) {
return 1;
}
if (!o2State && o1State) {
return -1;
}
int o1ConnectionSize = dataNodeCompareInfoMap.get(o1).getConnectionListSize() + dataNodeCompareInfoMap.get(o1).getCachedCreatedConnectionSize();
int o2ConnectionSize = dataNodeCompareInfoMap.get(o2).getConnectionListSize() + dataNodeCompareInfoMap.get(o2).getCachedCreatedConnectionSize();
return o1ConnectionSize - o2ConnectionSize;
});
if (hostSpecs.get(0) != null) {
this.cachedDnList.get(hostSpecs.get(0)).incrementCachedCreatingConnectionSize();
}
LOGGER.info(GT.tr("SortDnsByLeastConn: {0}."
, dataNodeCompareInfoMap));
return hostSpecs;
}
/**
* The necessary info when sorting data nodes.
*/
class DataNodeCompareInfo {
int connectionListSize;
int cachedCreatedConnectionSize;
boolean dataNodeState;
public DataNodeCompareInfo(final int connectionListSize, final int cachedCreatedConnectionSize, final boolean dataNodeState) {
this.connectionListSize = connectionListSize;
this.cachedCreatedConnectionSize = cachedCreatedConnectionSize;
this.dataNodeState = dataNodeState;
}
/**
* Get connectionList size.
*
* @return size of connectionList
*/
public int getConnectionListSize() {
return connectionListSize;
}
/**
* Get cached created connection size.
*
* @return cachedCreateConnectionSize
*/
public int getCachedCreatedConnectionSize() {
return cachedCreatedConnectionSize;
}
/**
* Get data node state.
*
* @return dataNodeState
*/
public boolean getDataNodeState() {
return dataNodeState;
}
@Override
public String toString() {
return "{"
+ "connectionListSize=" + connectionListSize
+ ", cachedCreatedConnectionSize=" + cachedCreatedConnectionSize
+ ", dataNodeState=" + dataNodeState
+ '}';
}
}
/**
* Check each data nodes' validity of cluster,
* use cached properties to tryConnect to each data nodes,
* if cached properties is invalid, remove cached properties,
* if state change to valid from invalid, quick load balancing start,
* if state change to invalid from valid, clear cached connections of this node.
*
* @return the number of invalid data nodes
*/
public int checkClusterState() {
// Count the state of all data nodes before and after checking cluster state.
Map<HostSpec, Boolean> oldStates = cachedDnList.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey,
(val) -> val.getValue().getDataNodeState()));
Map<HostSpec, Boolean> newStates = cachedDnList.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey,
(val) -> this.checkDnState(val.getKey())));
Map<DataNodeChangedState, List<HostSpec>> checkResult = new HashMap<>();
for (DataNodeChangedState dataNodeChangedState : DataNodeChangedState.values()) {
checkResult.put(dataNodeChangedState, new ArrayList<>());
}
for (Map.Entry<HostSpec, DataNode> entry : cachedDnList.entrySet()) {
HostSpec hostSpec = entry.getKey();
boolean oldState = oldStates.get(hostSpec);
boolean newState = newStates.get(hostSpec);
if (oldState && !newState) {
// If data node fails, clear its cacheConnectionList.
int removed = cachedDnList.get(hostSpec).clearCachedConnections();
checkResult.get(DataNodeChangedState.CHANGE_TO_INVALID).add(hostSpec);
LOGGER.info(GT.tr("A data node failed, clear cached connections, cluster: {0}, " +
"hostSpec: {1}, cached connections: {2}.",
urlIdentifier, hostSpec.toString(), removed));
} else if (!oldState && newState) {
checkResult.get(DataNodeChangedState.CHANGE_TO_VALID).add(hostSpec);
} else if (oldState) {
checkResult.get(DataNodeChangedState.KEEP_VALID).add(hostSpec);
} else {
checkResult.get(DataNodeChangedState.KEEP_INVALID).add(hostSpec);
}
}
LOGGER.info(GT.tr("Check cluster states in cluster: {0}, result: {1}.",
urlIdentifier, checkResult.toString()));
// Start to quickLoadBalance.
if (checkResult.get(DataNodeChangedState.CHANGE_TO_VALID).size() != 0
&& checkResult.get(DataNodeChangedState.KEEP_VALID).size() != 0
&& LoadBalanceHeartBeating.isQuickAutoBalanceStarted()) {
quickLoadBalance(checkResult.get(DataNodeChangedState.KEEP_VALID));
}
return checkResult.get(DataNodeChangedState.KEEP_INVALID).size() +
checkResult.get(DataNodeChangedState.CHANGE_TO_INVALID).size();
}
enum DataNodeChangedState {
KEEP_VALID, KEEP_INVALID, CHANGE_TO_VALID, CHANGE_TO_INVALID
}
/**
* Check dn state by cached properties, and remove invalid properties.
*
* @param hostSpec hostSpec
* @return state of the date node
*/
public boolean checkDnState(HostSpec hostSpec) {
synchronized (cachedPropertiesList) {
DataNode dataNode = cachedDnList.get(hostSpec);
if (dataNode == null) {
return false;
}
for (Iterator<Properties> iterator = cachedPropertiesList.iterator(); iterator.hasNext(); ) {
Properties properties = iterator.next();
CheckDnStateResult result = dataNode.checkDnStateAndProperties(properties);
if (CheckDnStateResult.DN_VALID.equals(result)) {
dataNode.setDataNodeState(true);
return true;
} else if (CheckDnStateResult.DN_INVALID.equals(result)) {
dataNode.setDataNodeState(false);
return false;
} else {
iterator.remove();
}
}
dataNode.setDataNodeState(false);
return false;
}
}
private int quickLoadBalance(List<HostSpec> validDns) {
synchronized (abandonedConnectionList) {
this.quickAutoBalanceStartTime = System.currentTimeMillis();
// the connections added into abandonedConnectionList
int removed = 0;
// cachedConnectionList size
int total = 0;
// idle connection filtered from cachedConnectionList
int idle = 0;
int minReservedConnectionPercentage;
if (!enableMinReservedConPerCluster) {
minReservedConnectionPercentage = minReservedConPerDatanode;
} else if (!enableMinReservedConPerDatanode) {
minReservedConnectionPercentage = minReservedConPerCluster;
} else {
minReservedConnectionPercentage = Math.max(minReservedConPerDatanode, minReservedConPerCluster);
}
for (Entry<HostSpec, DataNode> entry : cachedDnList.entrySet()) {
DataNode dataNode = entry.getValue();
if (dataNode != null) {
total += dataNode.getCachedConnectionListSize();
}
}
// Start to quickLoadBalance.
HashSet<ConnectionInfo> removedConnectionList = new HashSet<>();
for (HostSpec hostSpec : validDns) {
DataNode dataNode = cachedDnList.get(hostSpec);
if (dataNode != null) {
List<ConnectionInfo> idleConnections = dataNode.filterIdleConnections(quickAutoBalanceStartTime);
idle += idleConnections.size();
int removedConnectionsSize = (int) (idleConnections.size() * (((double) (100 - minReservedConnectionPercentage)) / 100.0));
for (int i = 0; i < removedConnectionsSize; i++) {
removedConnectionList.add(idleConnections.get(i));
removed++;
}
}
}
this.abandonedConnectionList.clear();
this.abandonedConnectionList.addAll(removedConnectionList);
this.totalAbandonedConnectionSize = abandonedConnectionList.size();
LOGGER.info(GT.tr("QuickLoadBalancing executes in cluster: {0}, " +
"put {1} idle connections into abandonedConnectionList, connections can be closed: {2}, " +
"total connection: {3}, minReservedConPerCluster: {4}, minReservedConPerDatanode: {5}.",
urlIdentifier, removed, idle, total, this.minReservedConPerCluster, this.minReservedConPerDatanode));
return removed;
}
}
/**
* Check cached connections validity, and remove invalid connections.
*
* @return the amount of removed connections of each dn.
*/
public List<Integer> checkConnectionsValidity() {
List<Integer> ans = new ArrayList<>();
for (Entry<HostSpec, DataNode> entry : cachedDnList.entrySet()) {
DataNode dataNode = entry.getValue();
ans.add(dataNode.checkConnectionsValidity());
}
return ans;
}
/**
* CachedCreatingConnection + 1
*
* @param hostSpec hostSpec
* @return cachedCreatingConnection
*/
public int incrementCachedCreatingConnectionSize(final HostSpec hostSpec) {
if (!cachedDnList.containsKey(hostSpec)) {
LOGGER.error(GT.tr("Can not find hostSpec: {0} in Cluster: {1}.",
hostSpec.toString(), urlIdentifier));
return 0;
}
DataNode dataNode = cachedDnList.get(hostSpec);
if (dataNode != null) {
return dataNode.incrementCachedCreatingConnectionSize();
} else {
LOGGER.error(GT.tr("Can not find hostSpec: {0} in Cluster: {1}.",
hostSpec.toString(), urlIdentifier));
return 0;
}
}
/**
* Get minReservedConPerCluster.
*
* @return minReservedConPerCluster
*/
public int getMinReservedConPerCluster() {
return minReservedConPerCluster;
}
/**
* Get enableMinReservedConPerCluster.
*
* @return enableMinReservedConPerCluster
*/
public boolean isEnableMinReservedConPerCluster() {
return enableMinReservedConPerCluster;
}
/**
* Get minReservedConPerDatanode.
*
* @return minReservedConPerDatanode
*/
public int getMinReservedConPerDatanode() {
return minReservedConPerDatanode;
}
/**
* Get enableMInReservedConPerDatanode.
*
* @return enableMInReservedConPerDatanode
*/
public boolean isEnableMinReservedConPerDatanode() {
return enableMinReservedConPerDatanode;
}
@Override
public int hashCode() {
return Objects.hash(urlIdentifier, dns, abandonedConnectionList, cachedDnList, cachedPropertiesList);
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Cluster that = (Cluster) o;
return Objects.equals(urlIdentifier, that.urlIdentifier) && Objects.equals(dns, that.dns);
}
/**
* Close connections from abandonedConnectionList.Jdbc will continue to pop connections from abandonedConnectionList
* and determine whether each connection can be closed. If it does, the connection will be close. If it does not,
* the connection won't be put back into abandonedConnectionList. The number of connections closed at each cluster
* for each scheduled task at most: 20% * totalAbandonedConnectionSize.
*
* @return the number of connections which are closed
*/
public int closeConnections() {
int closed = 0;
double ceilError = 0.001;
int atMost = (int) (Math.ceil(CLOSE_CONNECTION_PERCENTAGE_EACH_TIME * totalAbandonedConnectionSize) + ceilError);
synchronized (abandonedConnectionList) {
if (abandonedConnectionList.isEmpty()) {
return closed;
}
int oldSize = abandonedConnectionList.size();
while (!abandonedConnectionList.isEmpty() && closed < atMost) {
ConnectionInfo connectionInfo = abandonedConnectionList.poll();
HostSpec hostSpec = connectionInfo.getHostSpec();
// The connections shouldn't be null.
if (hostSpec == null) {
continue;
}
// The connections should be valid.
if (!connectionInfo.checkConnectionIsValid()) {
continue;
}
// The state of connection may change after put into abandonedConnectionList,
// so it's necessary to recheck it which can be closed.
if (!connectionInfo.checkConnectionCanBeClosed(quickAutoBalanceStartTime)) {
continue;
}
DataNode dataNode = cachedDnList.get(hostSpec);
if (dataNode == null) {
continue;
}
boolean hasClosed = dataNode.closeConnection(connectionInfo.getPgConnection());
if (hasClosed) {
closed++;
}
}
if (abandonedConnectionList.isEmpty()) {
this.quickAutoBalanceStartTime = 0;
this.totalAbandonedConnectionSize = 0;
}
LOGGER.info(GT.tr("Close connections execute in cluster: {0}, closed connections: {1}, "
+ "size of abandonedConnectionList before closing: {2},"
+ " size of abandonedConnectionList after closing: {3}.",
urlIdentifier, closed, oldSize, abandonedConnectionList.size()));
}
return closed;
}
/**
* Get cached connection size.
*
* @return cachedConnectionSize
*/
public int getCachedConnectionSize() {
return cachedDnList.values().stream().mapToInt(DataNode::getCachedConnectionListSize).sum();
}
}

View File

@ -0,0 +1,266 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.postgresql.quickautobalance;
import org.postgresql.PGProperty;
import org.postgresql.core.QueryExecutor;
import org.postgresql.core.SetupQueryRunner;
import org.postgresql.jdbc.PgConnection;
import org.postgresql.jdbc.StatementCancelState;
import org.postgresql.log.Log;
import org.postgresql.log.Logger;
import org.postgresql.util.GT;
import org.postgresql.util.HostSpec;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Objects;
import java.util.Properties;
/**
* Connection info used in quick auto balance.
*/
public class ConnectionInfo {
/**
* Default maxIdleTimeBeforeTerminal.
*/
public static final long DEFAULT_MAX_IDLE_TIME_BEFORE_TERMINAL = 30L;
public static final String ENABLE_QUICK_AUTO_BALANCE_PARAMS = "true";
private static final long MAX_IDLE_TIME_BEFORE_TERMINAL_MAX_VALUE = 9223372036854775L;
private static Log LOGGER = Logger.getLogger(ConnectionInfo.class.getName());
private final PgConnection pgConnection;
private final long createTimeStamp;
private final String autoBalance;
private boolean enableQuickAutoBalance;
// max idle time of connection, units: second
private long maxIdleTimeBeforeTerminal;
private final HostSpec hostSpec;
private volatile StatementCancelState connectionState;
// the timestamp when state change last time
private volatile long stateLastChangedTimeStamp;
@Override
public int hashCode() {
return Objects.hash(pgConnection, createTimeStamp, autoBalance, enableQuickAutoBalance,
maxIdleTimeBeforeTerminal, hostSpec);
}
public ConnectionInfo(PgConnection pgConnection, Properties properties, HostSpec hostSpec)
throws PSQLException {
this.pgConnection = pgConnection;
this.connectionState = StatementCancelState.IDLE;
this.createTimeStamp = System.currentTimeMillis();
this.stateLastChangedTimeStamp = createTimeStamp;
this.autoBalance = properties.getProperty("autoBalance", "");
this.maxIdleTimeBeforeTerminal = DEFAULT_MAX_IDLE_TIME_BEFORE_TERMINAL;
this.hostSpec = hostSpec;
this.maxIdleTimeBeforeTerminal = parseMaxIdleTimeBeforeTerminal(properties);
this.enableQuickAutoBalance = parseEnableQuickAutoBalance(properties);
}
/**
* Parse enableQuickAutoBalance.
*
* @param properties properties
* @return enableQuickAutoBalance
* @throws PSQLException EnableQuickAutoBalance parsed failed.
*/
public static boolean parseEnableQuickAutoBalance(Properties properties) throws PSQLException {
if (EnableQuickAutoBalanceParams.TRUE.getValue()
.equals(PGProperty.ENABLE_QUICK_AUTO_BALANCE.get(properties))) {
return true;
} else if (EnableQuickAutoBalanceParams.FALSE.getValue()
.equals(PGProperty.ENABLE_QUICK_AUTO_BALANCE.get(properties))) {
return false;
} else {
throw new PSQLException(
GT.tr("Parameter enableQuickAutoBalance={0} parsed failed, value range: '{true, false'}).",
PGProperty.ENABLE_QUICK_AUTO_BALANCE.get(properties)), PSQLState.INVALID_PARAMETER_VALUE);
}
}
/**
* Parse maxIdleTimeBeforeTerminal.
*
* @param properties properties
* @return maxIdleTimeBeforeTerminal
* @throws PSQLException MaxIdleTimeBeforeTerminal parse failed.
*/
public static long parseMaxIdleTimeBeforeTerminal(Properties properties) throws PSQLException {
long inputMaxIdleTime;
try {
String param = PGProperty.MAX_IDLE_TIME_BEFORE_TERMINAL.get(properties);
inputMaxIdleTime = Long.parseLong(param);
if (inputMaxIdleTime >= MAX_IDLE_TIME_BEFORE_TERMINAL_MAX_VALUE) {
throw new PSQLException(
GT.tr("Parameter maxIdleTimeBeforeTerminal={0} can not be bigger than {1}, value range: long & [0,{1}).",
String.valueOf(inputMaxIdleTime), String.valueOf(MAX_IDLE_TIME_BEFORE_TERMINAL_MAX_VALUE)),
PSQLState.INVALID_PARAMETER_VALUE);
}
if (inputMaxIdleTime < 0) {
throw new PSQLException(
GT.tr("Parameter maxIdleTimeBeforeTerminal={0} can not be less than 0, value range: long & [0,{1}).",
String.valueOf(inputMaxIdleTime), String.valueOf(MAX_IDLE_TIME_BEFORE_TERMINAL_MAX_VALUE)),
PSQLState.INVALID_PARAMETER_VALUE);
}
} catch (NumberFormatException e) {
throw new PSQLException(
GT.tr("Parameter maxIdleTimeBeforeTerminal parsed failed, value range: long & [0,{0}).",
String.valueOf(MAX_IDLE_TIME_BEFORE_TERMINAL_MAX_VALUE)), PSQLState.INVALID_PARAMETER_TYPE);
}
return inputMaxIdleTime;
}
enum EnableQuickAutoBalanceParams {
TRUE("true"),
FALSE("false");
private final String value;
EnableQuickAutoBalanceParams(String value) {
this.value = value;
}
/**
* Get value.
*
* @return value
*/
public String getValue() {
return this.value;
}
}
public StatementCancelState getConnectionState() {
return connectionState;
}
public synchronized void setConnectionState(StatementCancelState state) {
if (state != null && !connectionState.equals(state)) {
connectionState = state;
stateLastChangedTimeStamp = System.currentTimeMillis();
}
}
public long getMaxIdleTimeBeforeTerminal() {
return maxIdleTimeBeforeTerminal;
}
public String getAutoBalance() {
return autoBalance;
}
public PgConnection getPgConnection() {
return pgConnection;
}
@Override
public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final ConnectionInfo that = (ConnectionInfo) o;
return createTimeStamp == that.createTimeStamp && enableQuickAutoBalance == that.enableQuickAutoBalance &&
maxIdleTimeBeforeTerminal == that.maxIdleTimeBeforeTerminal && pgConnection.equals(that.pgConnection) &&
autoBalance.equals(that.autoBalance) && hostSpec.equals(that.hostSpec);
}
/**
* Check whether the connection can be closed.
* The judgement conditions are as follows:
* 1. The connection enables quickAutoBalance.
* 2. The quickAutoBalance start time is later than the connection create time.
* 3. The connection state is idle.
* 4. The connection keeps idle at least maxIdleTimeBeforeTerminal seconds.
*
* @param quickAutoBalanceStartTime quickAutoBalance start time
* @return whether the connection can be closed
*/
public synchronized boolean checkConnectionCanBeClosed(long quickAutoBalanceStartTime) {
if (pgConnection == null) {
return false;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(GT.tr("checkConnectionCanBeClosed: server ip={0}, enableQuickAutoBalance={1}, " +
"quickAutoBalanceStartTime={2}, createTimeStamp={3}, connectionState={4}, " +
"stateLastChangedTimeStamp={5}, currentTimeMillis={6}",
hostSpec.toString(), isEnableQuickAutoBalance(), quickAutoBalanceStartTime, createTimeStamp,
connectionState, stateLastChangedTimeStamp, System.currentTimeMillis()));
}
if (!isEnableQuickAutoBalance()) {
return false;
}
if (quickAutoBalanceStartTime < createTimeStamp) {
return false;
}
if (!connectionState.equals(StatementCancelState.IDLE)) {
return false;
}
return System.currentTimeMillis() - stateLastChangedTimeStamp > maxIdleTimeBeforeTerminal * 1000;
}
public boolean isEnableQuickAutoBalance() {
return enableQuickAutoBalance;
}
/**
* Check whether a connection is valid.
*
* @return whether a connection is valid
*/
public boolean checkConnectionIsValid() {
boolean isConnectionValid;
try {
QueryExecutor queryExecutor = pgConnection.getQueryExecutor();
byte[][] bit = SetupQueryRunner.run(queryExecutor, "select 1", true);
if (bit == null) {
return false;
}
String result = queryExecutor.getEncoding().decode(bit[0]);
isConnectionValid = result != null && result.equals("1");
} catch (SQLException | IOException e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(GT.tr("CheckConnectionIsValid failed."));
}
isConnectionValid = false;
}
return isConnectionValid;
}
/**
* get hostSpec
*
* @return hostSpec
*/
public HostSpec getHostSpec() {
return hostSpec;
}
}

View File

@ -0,0 +1,332 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.postgresql.quickautobalance;
import org.postgresql.Driver;
import org.postgresql.PGProperty;
import org.postgresql.QueryCNListUtils;
import org.postgresql.jdbc.PgConnection;
import org.postgresql.jdbc.StatementCancelState;
import org.postgresql.log.Log;
import org.postgresql.log.Logger;
import org.postgresql.util.GT;
import org.postgresql.util.HostSpec;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
/**
* Connection manager of quick load balancing.
*/
public class ConnectionManager {
private static final Log LOGGER = Logger.getLogger(ConnectionManager.class.getName());
private static final String AUTO_BALANCE = "autoBalance";
private static final String LEAST_CONN = "leastconn";
private final Map<String, Cluster> cachedClusters;
private ConnectionManager() {
this.cachedClusters = new ConcurrentHashMap<>();
}
/**
* Choose abandoned connections from abandonedConnections of each cluster.
* The number of connections that each cluster closes : CLOSE_CONNECTION_PERIOD * CLOSE_CONNECTION_PER_SECOND.
*
* @return the number of connections closed per cluster
*/
public List<Integer> closeConnections() {
List<Integer> ans = new ArrayList<>();
for (Entry<String, Cluster> entry : cachedClusters.entrySet()) {
Cluster cluster = entry.getValue();
int num = 0;
num += cluster != null ? cluster.closeConnections() : 0;
ans.add(num);
}
return ans;
}
/**
* check whether the properties enable leastconn.
*
* @param properties properties
* @return whether the properties enable leastconn.
*/
public static boolean checkEnableLeastConn(Properties properties) {
if (properties == null) {
return false;
}
if (!LEAST_CONN.equals(properties.getProperty(AUTO_BALANCE, ""))) {
return false;
}
HostSpec[] hostSpecs = Driver.getURLHostSpecs(properties);
return hostSpecs.length > 1;
}
/**
* Check a properties if enable quickAutoBalance.
*
* @param properties properties
* @return if enable quickAutoBalance
*/
public static boolean checkEnableQuickAutoBalance(Properties properties) {
if (!checkEnableLeastConn(properties)) {
return false;
}
return ConnectionInfo.ENABLE_QUICK_AUTO_BALANCE_PARAMS
.equals(PGProperty.ENABLE_QUICK_AUTO_BALANCE.get(properties));
}
private static class Holder {
private static final ConnectionManager INSTANCE = new ConnectionManager();
}
/**
* Get instance.
*
* @return connectionManager
*/
public static ConnectionManager getInstance() {
return Holder.INSTANCE;
}
/**
* Set cluster into connection manager.
*
* @param properties properties
* @return set or not.
*/
public boolean setCluster(Properties properties) throws PSQLException {
if (!checkEnableLeastConn(properties)) {
return false;
}
checkQuickAutoBalanceParams(properties);
String urlIdentifier = QueryCNListUtils.keyFromURL(properties);
// create a cluster if it doesn't exist in cachedClusters.
if (!cachedClusters.containsKey(urlIdentifier)) {
synchronized (cachedClusters) {
if (!cachedClusters.containsKey(urlIdentifier)) {
Cluster cluster = new Cluster(urlIdentifier, properties);
cachedClusters.put(urlIdentifier, cluster);
return true;
} else {
return false;
}
}
} else {
return false;
}
}
private void checkQuickAutoBalanceParams(Properties properties) throws PSQLException {
ConnectionInfo.parseEnableQuickAutoBalance(properties);
ConnectionInfo.parseMaxIdleTimeBeforeTerminal(properties);
Cluster.parseMinReservedConPerCluster(properties);
Cluster.parseMinReservedConPerDatanode(properties);
}
/**
* Cache this connection, if it's configured with autoBalance = "leastconn".
*
* @param pgConnection connection
* @param properties properties
* @return if insert the connection into connectionManager.
*/
public boolean setConnection(PgConnection pgConnection, Properties properties) throws PSQLException {
if (!checkEnableLeastConn(properties)) {
return false;
}
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
// create a cluster if it doesn't exist in cachedClusters.
if (!cachedClusters.containsKey(URLIdentifier)) {
synchronized (cachedClusters) {
if (!cachedClusters.containsKey(URLIdentifier)) {
Cluster cluster = new Cluster(URLIdentifier, properties);
cluster.setConnection(pgConnection, properties);
cachedClusters.put(URLIdentifier, cluster);
}
}
} else {
cachedClusters.get(URLIdentifier).setConnection(pgConnection, properties);
}
return true;
}
/**
* Set connection state of cached connections if it exists.
*
* @param pgConnection pgConnection
* @param state state
*/
public void setConnectionState(PgConnection pgConnection, StatementCancelState state) throws PSQLException {
String url;
try {
url = pgConnection.getURL();
} catch (SQLException e) {
LOGGER.error(GT.tr("Can't get url from pgConnection."));
return;
}
String URLIdentifier = getURLIdentifierFromUrl(url);
Cluster cluster = cachedClusters.get(URLIdentifier);
if (cluster != null) {
cluster.setConnectionState(pgConnection, state);
}
}
public Cluster getCluster(String URLIdentifier) {
return cachedClusters.get(URLIdentifier);
}
/**
* Get URLIdentifier from url, which is a unique id of cluster.
*
* @param url url
* @return URLIdentifier
*/
public static String getURLIdentifierFromUrl(String url) throws PSQLException {
HostSpec[] hostSpecs;
try {
String pgHostUrl = url.split("//")[1].split("/")[0];
String[] pgHosts = pgHostUrl.split(",");
hostSpecs = new HostSpec[pgHosts.length];
for (int i = 0; i < hostSpecs.length; i++) {
hostSpecs[i] = new HostSpec(pgHosts[i].split(":")[0],
Integer.parseInt(pgHosts[i].split(":")[1]));
}
Arrays.sort(hostSpecs);
} catch (ArrayIndexOutOfBoundsException e) {
throw new PSQLException(
GT.tr("Parsed url={0} failed.", url), PSQLState.INVALID_PARAMETER_VALUE);
}
return Arrays.toString(hostSpecs);
}
/**
* Check whether the connections are valid in each cluster, and remove invalid connections.
*
* @return the number of connections removed from cache.
*/
public List<Integer> checkConnectionsValidity() {
List<Integer> ans = new ArrayList<>();
for (Entry<String, Cluster> entry : cachedClusters.entrySet()) {
Cluster cluster = entry.getValue();
int num = 0;
if (cluster != null) {
List<Integer> removes = cluster.checkConnectionsValidity();
for (int remove : removes) {
num += remove;
}
}
ans.add(num);
}
return ans;
}
/**
* Check datanode states of each cluster.
*
* @return the number of invalid data nodes of all cluster
*/
public int checkClusterStates() {
int invalidDataNodes = 0;
for (Entry<String, Cluster> entry : cachedClusters.entrySet()) {
Cluster cluster = entry.getValue();
if (cluster != null) {
invalidDataNodes += cluster.checkClusterState();
}
}
return invalidDataNodes;
}
/**
* Increment cachedCreatingConnectionSize.
* CachedCreatingConnectionSize indicates the number of connections that have been load balanced,
* but haven't been cached into cachedConnectionList yet.
*
* @param hostSpec hostSpec
* @param properties properties
* @return cachedCreatingConnectionSize after updating
*/
public int incrementCachedCreatingConnectionSize(HostSpec hostSpec, Properties properties) {
if (!checkEnableLeastConn(properties)) {
return 0;
}
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
if (cachedClusters.containsKey(URLIdentifier)) {
Cluster cluster = cachedClusters.get(URLIdentifier);
if (cluster != null) {
return cluster.incrementCachedCreatingConnectionSize(hostSpec);
}
} else {
LOGGER.info(GT.tr("Can not find cluster: {0} in cached clusters.", URLIdentifier));
}
return 0;
}
/**
* Decrement cachedCreatingConnectionSize.
* CachedCreatingConnectionSize indicates the number of connections that have been load balanced,
* but haven't been cached into cachedConnectionList yet.
*
* @param hostSpec hostSpec
* @param properties properties
* @return cachedCreatingConnectionSize after updating
*/
public int decrementCachedCreatingConnectionSize(HostSpec hostSpec, Properties properties) {
if (!checkEnableLeastConn(properties)) {
return 0;
}
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
if (cachedClusters.containsKey(URLIdentifier)) {
Cluster cluster = cachedClusters.get(URLIdentifier);
if (cluster != null) {
return cluster.decrementCachedCreatingConnectionSize(hostSpec);
}
} else {
LOGGER.info(GT.tr("Can not find cluster: {0} in cached clusters.", URLIdentifier));
}
return 0;
}
/**
* Clear connection manager.
*/
public void clear() {
synchronized (cachedClusters) {
cachedClusters.clear();
}
}
/**
* Get cached connection size.
*
* @return cached connection size
*/
public int getCachedConnectionSize() {
return cachedClusters.values().stream().mapToInt(Cluster::getCachedConnectionSize).sum();
}
}

View File

@ -0,0 +1,376 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.postgresql.quickautobalance;
import org.postgresql.PGProperty;
import org.postgresql.core.PGStream;
import org.postgresql.core.QueryExecutor;
import org.postgresql.core.SocketFactoryFactory;
import org.postgresql.core.v3.ConnectionFactoryImpl;
import org.postgresql.jdbc.PgConnection;
import org.postgresql.jdbc.SslMode;
import org.postgresql.jdbc.StatementCancelState;
import org.postgresql.log.Log;
import org.postgresql.log.Logger;
import org.postgresql.util.GT;
import org.postgresql.util.HostSpec;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import javax.net.SocketFactory;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Datanode.
*/
public class DataNode {
private static Log LOGGER = Logger.getLogger(DataNode.class.getName());
private static final String USERNAME_OR_PASSWORD_INVALID_ERROR_CODE = "28P01";
// the host of datanode (ip + port)
private final HostSpec hostSpec;
// cached connections
private final Map<PgConnection, ConnectionInfo> cachedConnectionList;
// number of cached connections, before set into cachedConnectionList, and after load balance by leastconn.
private final AtomicInteger cachedCreatingConnectionSize;
private volatile boolean dataNodeState;
public DataNode(final HostSpec hostSpec) {
this.hostSpec = hostSpec;
this.cachedConnectionList = new ConcurrentHashMap<>();
this.cachedCreatingConnectionSize = new AtomicInteger(0);
this.dataNodeState = true;
}
/**
* Set connection state.
*
* @param pgConnection pgConnection
* @param state state
*/
public void setConnectionState(final PgConnection pgConnection, final StatementCancelState state) {
ConnectionInfo connectionInfo = cachedConnectionList.get(pgConnection);
if (connectionInfo != null) {
connectionInfo.setConnectionState(state);
}
}
/**
* Set connection.
*
* @param pgConnection pgConnection
* @param properties properties
* @param hostSpec hostSpec
*/
public void setConnection(final PgConnection pgConnection, final Properties properties, final HostSpec hostSpec)
throws PSQLException {
if (pgConnection == null || properties == null || hostSpec == null) {
return;
}
if (!hostSpec.equals(this.hostSpec)) {
return;
}
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
cachedConnectionList.put(pgConnection, connectionInfo);
}
/**
* Get connection info if the connection exits.
*
* @param pgConnection pgConnection
* @return connectionInfo
*/
public ConnectionInfo getConnectionInfo(PgConnection pgConnection) {
if (pgConnection == null) {
return null;
}
return cachedConnectionList.get(pgConnection);
}
/**
* Get the size of cachedConnectionList.
*
* @return size of cachedConnectionList
*/
public int getCachedConnectionListSize() {
return cachedConnectionList.size();
}
/**
* Check dn state and the validity of properties.
*
* @param properties properties
* @return result (dnValid, dnInvalid, propertiesInvalid)
*/
public CheckDnStateResult checkDnStateAndProperties(Properties properties) {
boolean isDataNodeValid;
Properties singleNodeProperties = new Properties();
PGProperty.USER.set(singleNodeProperties, PGProperty.USER.get(properties));
PGProperty.PASSWORD.set(singleNodeProperties, PGProperty.PASSWORD.get(properties));
PGProperty.PG_DBNAME.set(singleNodeProperties, PGProperty.PG_DBNAME.get(properties));
PGProperty.PG_HOST.set(singleNodeProperties, hostSpec.getHost());
PGProperty.PG_PORT.set(singleNodeProperties, hostSpec.getPort());
try {
isDataNodeValid = checkDnState(singleNodeProperties);
} catch (PSQLException e) {
String cause = e.getCause() != null ? e.getCause().getMessage() : "";
LOGGER.info(GT.tr("Can not try connect to dn: {0}, {1}.", hostSpec.toString(), cause.toString()));
return CheckDnStateResult.DN_INVALID;
} catch (InvocationTargetException e) {
Throwable invocationTargetExceptionCause = e.getCause();
if (invocationTargetExceptionCause instanceof PSQLException) {
PSQLException psqlException = (PSQLException) invocationTargetExceptionCause;
String sqlState = psqlException.getSQLState();
if (USERNAME_OR_PASSWORD_INVALID_ERROR_CODE.equals(sqlState)) {
String cause = e.getCause() != null ? e.getCause().getMessage() : "";
LOGGER.info(GT.tr("Cached properties is invalid: {0}.", cause.toString()));
return CheckDnStateResult.PROPERTIES_INVALID;
}
}
String cause = e.getCause() != null ? e.getCause().getMessage() : "";
LOGGER.info(GT.tr("Can not try connect to dn: {0}, {1}.", hostSpec.toString(), cause.toString()));
return CheckDnStateResult.DN_INVALID;
}
if (isDataNodeValid) {
return CheckDnStateResult.DN_VALID;
} else {
return CheckDnStateResult.DN_INVALID;
}
}
/**
* Filter idle connections from cachedConnectionsList.
*
* @param quickAutoBalanceStartTime the time since the start of quickAutoBalance
* @return idle Connection list
*/
public List<ConnectionInfo> filterIdleConnections(final long quickAutoBalanceStartTime) {
synchronized (cachedConnectionList) {
List<ConnectionInfo> idleConnectionList = new ArrayList<>();
for (Entry<PgConnection, ConnectionInfo> entry : cachedConnectionList.entrySet()) {
ConnectionInfo connectionInfo = entry.getValue();
if (connectionInfo != null && connectionInfo.checkConnectionCanBeClosed(quickAutoBalanceStartTime)) {
idleConnectionList.add(connectionInfo);
}
}
return idleConnectionList;
}
}
/**
* The result of checking dn state.
*/
public enum CheckDnStateResult {
DN_VALID,
DN_INVALID,
PROPERTIES_INVALID
}
public void setDataNodeState(boolean isDnValid) {
this.dataNodeState = isDnValid;
}
public boolean getDataNodeState() {
return this.dataNodeState;
}
/**
* check a dn of the cluster if valid,
*
* @param properties properties
* @return if the dn is valid.
* @throws PSQLException psql exception
* @throws InvocationTargetException invocation target exception
*/
public boolean checkDnState(Properties properties) throws PSQLException, InvocationTargetException {
Object pgStream;
try {
HostSpec dnHostSpec = new HostSpec(properties.getProperty("PGHOST")
, Integer.parseInt(properties.getProperty("PGPORT")));
SocketFactory socketFactory = SocketFactoryFactory.getSocketFactory(properties);
SslMode sslMode = SslMode.of(properties);
Class<?> classForName = Class.forName("org.postgresql.core.v3.ConnectionFactoryImpl");
Object object = classForName.newInstance();
if (!(object instanceof ConnectionFactoryImpl)) {
LOGGER.error(GT.tr("classForName.newInstance() doesn't instanceof ConnectionFactoryImpl."));
return false;
}
ConnectionFactoryImpl connectionFactory = (ConnectionFactoryImpl) object;
Method method = connectionFactory.getClass().getDeclaredMethod("tryConnect", String.class,
String.class, Properties.class, SocketFactory.class, HostSpec.class, SslMode.class);
method.setAccessible(true);
pgStream = method.invoke(connectionFactory, properties.getProperty("user"),
properties.getProperty("PGDBNAME"), properties, socketFactory, dnHostSpec, sslMode);
if (pgStream instanceof PGStream) {
((PGStream) pgStream).close();
}
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException |
IOException e) {
throw new PSQLException("The queryExecutor of connection can't execute tryConnect",
PSQLState.WRONG_OBJECT_TYPE);
}
if (pgStream instanceof PGStream) {
return true;
} else {
LOGGER.error(GT.tr("Stream doesn't instanceof PGStream."));
return false;
}
}
/**
* Check cached connections validity, and remove invalid connections.
*
* @return the amount of removed connections.
*/
public int checkConnectionsValidity() {
int num = 0;
for (Entry<PgConnection, ConnectionInfo> entry : cachedConnectionList.entrySet()) {
PgConnection pgConnection = entry.getKey();
ConnectionInfo connectionInfo = entry.getValue();
if (!connectionInfo.checkConnectionIsValid()) {
cachedConnectionList.remove(pgConnection);
num++;
}
}
return num;
}
/**
* Close cached connections, and clear cachedConnectionList.
* JDBC execute clearCachedConnections when jdbc find an invalid datanode.
*
* @return size of cachedConnectionList before cleared
*/
public int clearCachedConnections() {
synchronized (cachedConnectionList) {
int num = cachedConnectionList.size();
for (Map.Entry<PgConnection, ConnectionInfo> entry : cachedConnectionList.entrySet()) {
PgConnection pgConnection = entry.getKey();
if (pgConnection != null) {
QueryExecutor queryExecutor = pgConnection.getQueryExecutor();
if (queryExecutor != null && !queryExecutor.isClosed()) {
queryExecutor.close();
queryExecutor.setAvailability(false);
}
} else {
LOGGER.error(GT.tr("Fail to close connection, pgConnection = null."));
}
}
cachedConnectionList.clear();
return num;
}
}
/**
* Close connection.
*
* @param pgConnection pgConnection
* @return if closed
*/
public boolean closeConnection(PgConnection pgConnection) {
if (pgConnection == null) {
return false;
}
ConnectionInfo connectionInfo = cachedConnectionList.remove(pgConnection);
if (connectionInfo != null) {
try {
pgConnection.close();
return true;
} catch (SQLException e) {
LOGGER.info(GT.tr("Connection closed failed."), e);
return false;
}
}
return false;
}
/**
* get cachedCreatingConnectionSize
*
* @return cachedCreatingConnectionSize
*/
public int getCachedCreatingConnectionSize() {
return cachedCreatingConnectionSize.get();
}
/**
* increment cachedCreatingConnectionSize
*
* @return cachedCreatingConnectionSize after updated
*/
public int incrementCachedCreatingConnectionSize() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(GT.tr("【quickAutoBalance】incrementCachedCreatingConnectionSize, hostSpec: {0}, before " +
"increment: {1}", hostSpec.toString(), cachedCreatingConnectionSize.get()));
}
return cachedCreatingConnectionSize.incrementAndGet();
}
/**
* decrement cachedCreatingConnectionSize
*
* @return cachedCreatingConnectionSize after updated
*/
public int decrementCachedCreatingConnectionSize() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(GT.tr("【quickAutoBalance】decrementCachedCreatingConnectionSize, hostSpec: {0}, before " +
"decrement: {1}", hostSpec.toString(), cachedCreatingConnectionSize.get()));
}
if (cachedCreatingConnectionSize.get() == 0) {
// Some of junit tests don't load balance, but setConnection, can generate this error.
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(GT.tr("CachedCreatingConnectionSize should not be less than 0, reset to 0."));
}
return 0;
}
return cachedCreatingConnectionSize.decrementAndGet();
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final DataNode dataNode = (DataNode) obj;
return dataNodeState == dataNode.dataNodeState && Objects.equals(hostSpec, dataNode.hostSpec) &&
Objects.equals(cachedConnectionList, dataNode.cachedConnectionList);
}
@Override
public int hashCode() {
return Objects.hash(hostSpec);
}
}

View File

@ -0,0 +1,235 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.postgresql.quickautobalance;
import org.postgresql.jdbc.PgConnection;
import org.postgresql.log.Log;
import org.postgresql.log.Logger;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Load balance heartBeating.
*/
public class LoadBalanceHeartBeating {
private static final int INITIAL_DELAY = 1000;
private static final int CHECK_CLUSTER_STATE_PERIOD = 1000 * 20;
// unit: CLOSE_CONNECTION_PER_SECOND
private static final int CLOSE_CONNECTION_PERIOD = 1000 * 5;
// A heartBeating thread used to check clusters' state.
// If user has configured 'autoBalance=leastconn', jdbc will start checkConnectionScheduledExecutorService.
private static ScheduledExecutorService checkClusterStateScheduledExecutorService = null;
// A heartBeating thread used to close abandoned connections.
// If user has configured 'autoBalance=leastconn&enableQuickAutoBalance=true', jdbc will start closeConnectionExecutorService.
private static ScheduledExecutorService closeConnectionExecutorService = null;
private static Log LOGGER = Logger.getLogger(LoadBalanceHeartBeating.class.getName());
// CheckClusterStateScheduledExecutorService will count the number of cached connections in ConnectionManager
// each time it executes. If it's 0 for two consecutive times, jdbc will close heartbeat thread, and clear
// ConnectionManager.
private static final AtomicInteger emptyCacheTime = new AtomicInteger(0);
private static final ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();
private static final ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();
private static final ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();
// If singleton checkConnectionScheduledExecutorService has started.
private static volatile boolean leastConnStarted = false;
// If singleton closeConnectionExecutorService has started.
private static volatile boolean quickAutoBalanceStarted = false;
/**
* Whether quickAutoBalance has started.
*
* @return whether quickAutoBalance has started
*/
public static boolean isLoadBalanceHeartBeatingStarted() {
return leastConnStarted && quickAutoBalanceStarted;
}
/**
* Get if quickAutoBalance has started.
*
* @return if quickAutoBalance has started
*/
public static boolean isQuickAutoBalanceStarted() {
return quickAutoBalanceStarted;
}
/**
* Get if leastConnStarted has started.
*
* @return if leastConnStarted has started
*/
public static boolean isLeastConnStarted() {
return leastConnStarted;
}
/**
* Set connection into connection manager. If set, start singleton heart beating thread.
*
* @param pgConnection pgConnection
* @param props properties
* @throws PSQLException parameters parsed failed.
*/
public static void setConnection(PgConnection pgConnection, Properties props) throws PSQLException {
if (!ConnectionManager.checkEnableLeastConn(props)) {
return;
}
try {
readLock.lock();
if (ConnectionManager.getInstance().setConnection(pgConnection, props)) {
LoadBalanceHeartBeating.startScheduledExecutorService(props);
}
} finally {
readLock.unlock();
}
}
/**
* Start scheduled executor service. There are two singleton scheduled executor service.
* If cluster has configured 'autoBalance=leastconn', jdbc will start checkConnectionScheduledExecutorService.
* If cluster has configured 'autoBalance=leastconn&enableQuickAutoBalance=true', jdbc will start
* closeConnectionExecutorService.
*
* @param properties properties
*/
public static void startScheduledExecutorService(Properties properties) {
// Both two heartBeating thread has started.
if (leastConnStarted && quickAutoBalanceStarted) {
return;
}
// The connection doesn't enable leastconn.
if (!ConnectionManager.checkEnableLeastConn(properties)) {
return;
}
// CheckClusterStateHeartBeatingThread has started, and the connection doesn't enable quickAutoBalance.
if (leastConnStarted && !ConnectionManager.checkEnableQuickAutoBalance(properties)) {
return;
}
synchronized (LoadBalanceHeartBeating.class) {
if (!leastConnStarted && ConnectionManager.checkEnableLeastConn(properties)) {
leastConnStarted = true;
checkClusterStateScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(r ->
new Thread(r, "checkClusterStateHeartBeatingThread"));
checkClusterStateScheduledExecutorService.scheduleAtFixedRate(LoadBalanceHeartBeating::checkClusterStateScheduleTask,
INITIAL_DELAY, CHECK_CLUSTER_STATE_PERIOD, TimeUnit.MILLISECONDS);
LOGGER.info(GT.tr("Start scheduleExecutorService, period:{0} milliseconds.",
CHECK_CLUSTER_STATE_PERIOD));
}
if (!quickAutoBalanceStarted && ConnectionManager.checkEnableQuickAutoBalance(properties)) {
quickAutoBalanceStarted = true;
closeConnectionExecutorService = Executors.newSingleThreadScheduledExecutor(r ->
new Thread(r, "closeConnectionsHeartBeatingThread"));
closeConnectionExecutorService.scheduleAtFixedRate(LoadBalanceHeartBeating::closeAbandonedConnections,
INITIAL_DELAY, CLOSE_CONNECTION_PERIOD, TimeUnit.MILLISECONDS);
LOGGER.info(GT.tr("Start closeConnectionScheduledFuture, period:{0} milliseconds.",
CLOSE_CONNECTION_PERIOD));
}
}
}
private static void checkClusterStateScheduleTask() {
checkClusterState();
checkConnectionValidity();
checkHeartBeatingThreadShouldStop();
}
private static void closeAbandonedConnections() {
List<Integer> closedConnections = ConnectionManager.getInstance().closeConnections();
int sum = closedConnections.stream().mapToInt(Integer::intValue).sum();
LOGGER.info(GT.tr("Scheduled task: closeAbandonedConnections(), thread id: {0}, " +
"amount of closed connections: {1}.", Thread.currentThread().getId(), sum));
}
private static void checkClusterState() {
int invalidDataNodes = ConnectionManager.getInstance().checkClusterStates();
LOGGER.info(GT.tr("Scheduled task: checkClusterState(), thread id: {0}, " +
"amount of invalid data nodes: {1}.", Thread.currentThread().getId(), invalidDataNodes));
}
private static void checkConnectionValidity() {
List<Integer> removes = ConnectionManager.getInstance().checkConnectionsValidity();
int sum = removes.stream().mapToInt(Integer::intValue).sum();
LOGGER.info(GT.tr("Scheduled task: checkConnectionValidity(), thread id: {0}, " +
"amount of removed connections: {1}.", Thread.currentThread().getId(), sum));
}
private static void checkHeartBeatingThreadShouldStop() {
int maxCachedConnectionsEmptyTimesBeforeClear = 2;
int cachedConnectionSize = ConnectionManager.getInstance().getCachedConnectionSize();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(GT.tr("CachedConnectionSize = {0}.", cachedConnectionSize));
}
if (cachedConnectionSize != 0) {
emptyCacheTime.set(0);
return;
}
emptyCacheTime.incrementAndGet();
if (emptyCacheTime.get() >= maxCachedConnectionsEmptyTimesBeforeClear) {
try{
writeLock.lock();
if (ConnectionManager.getInstance().getCachedConnectionSize() == 0) {
emptyCacheTime.set(0);
stopHeartBeatingThread();
}
} finally {
writeLock.unlock();
}
}
}
/**
* Stop scheduled executor service, and clear connection manager.
*/
public static void stopHeartBeatingThread() {
if (!leastConnStarted && !quickAutoBalanceStarted) {
return;
}
synchronized (LoadBalanceHeartBeating.class) {
if (leastConnStarted) {
checkClusterStateScheduledExecutorService.shutdownNow();
checkClusterStateScheduledExecutorService = null;
leastConnStarted = false;
LOGGER.info(GT.tr("ScheduledExecutorService: {0} close.", "loadBalanceHeartBeatingThread"));
}
if (quickAutoBalanceStarted) {
closeConnectionExecutorService.shutdownNow();
closeConnectionExecutorService = null;
quickAutoBalanceStarted = false;
LOGGER.info(GT.tr("ScheduledExecutorService: {0} close.", "closeConnectionsHeartBeatingThread"));
}
ConnectionManager.getInstance().clear();
}
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.postgresql.quickautobalance;
import org.postgresql.log.Log;
import org.postgresql.log.Logger;
import org.postgresql.util.GT;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Reflect util
*/
public class ReflectUtil {
private static Log LOGGER = Logger.getLogger(ReflectUtil.class.getName());
/**
*
* @param classz classz
* @param object object
* @param methodName methodName
*/
public static void invoke(Class classz, Object object, String methodName) {
try {
Method method = classz.getDeclaredMethod(methodName);
method.setAccessible(true);
method.invoke(object);
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
LOGGER.error(GT.tr("call reflect method {}.{} error.", classz, methodName));
}
}
/**
* Get the private property of an object.
*
* @param classz class of object
* @param object object
* @param t class of the private property
* @param fieldName of the private property
* @param <T> class of the private property
* @return the private property
*/
public static <T> T getField(Class classz, Object object, Class<T> t, String fieldName) {
try {
Field field = classz.getDeclaredField(fieldName);
field.setAccessible(true);
return (T) field.get(object);
} catch (NoSuchFieldException | IllegalAccessException e) {
LOGGER.error("get reflect field " + classz + "." + fieldName + " error.");
}
return null;
}
/**
* Set the private property of an object.
*
* @param classz class of object
* @param object object
* @param fieldName of the private property
* @param value value of the private property
*/
public static void setField(Class classz, Object object, String fieldName, Object value) {
try {
Field field = classz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
} catch (NoSuchFieldException | IllegalAccessException e) {
LOGGER.error("set reflect field " + classz + "." + fieldName + " error.");
}
}
}

View File

@ -155,7 +155,7 @@ public class ParserTest {
"insert test(id, name) select 1, 'value' as RETURNING from test2";
List<NativeQuery> qry =
Parser.parseJdbcSql(
query, true, true, true, true);
query, true, true, true, true, true);
boolean returningKeywordPresent = qry.get(0).command.isReturningKeywordPresent();
Assert.assertFalse("Query does not have returning clause " + query, returningKeywordPresent);
}
@ -166,7 +166,7 @@ public class ParserTest {
"insert test(id, name) select 1, 'value' from test2 RETURNING id";
List<NativeQuery> qry =
Parser.parseJdbcSql(
query, true, true, true, true);
query, true, true, true, true, true);
boolean returningKeywordPresent = qry.get(0).command.isReturningKeywordPresent();
Assert.assertTrue("Query has a returning clause " + query, returningKeywordPresent);
}
@ -177,7 +177,7 @@ public class ParserTest {
"with x as (insert into mytab(x) values(1) returning x) insert test(id, name) select 1, 'value' from test2";
List<NativeQuery> qry =
Parser.parseJdbcSql(
query, true, true, true, true);
query, true, true, true, true, true);
boolean returningKeywordPresent = qry.get(0).command.isReturningKeywordPresent();
Assert.assertFalse("There's no top-level <<returning>> clause " + query, returningKeywordPresent);
}
@ -185,7 +185,7 @@ public class ParserTest {
@Test
public void insertBatchedReWriteOnConflict() throws SQLException {
String query = "insert into test(id, name) values (:id,:name) ON CONFLICT (id) DO NOTHING";
List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true);
List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true, true);
SqlCommand command = qry.get(0).getCommand();
Assert.assertEquals(34, command.getBatchRewriteValuesBraceOpenPosition());
Assert.assertEquals(44, command.getBatchRewriteValuesBraceClosePosition());
@ -194,7 +194,7 @@ public class ParserTest {
@Test
public void insertBatchedReWriteOnConflictUpdateBind() throws SQLException {
String query = "insert into test(id, name) values (?,?) ON CONFLICT (id) UPDATE SET name=?";
List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true);
List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true, true);
SqlCommand command = qry.get(0).getCommand();
Assert.assertFalse("update set name=? is NOT compatible with insert rewrite", command.isBatchedReWriteCompatible());
}
@ -202,7 +202,7 @@ public class ParserTest {
@Test
public void insertBatchedReWriteOnConflictUpdateConstant() throws SQLException {
String query = "insert into test(id, name) values (?,?) ON CONFLICT (id) UPDATE SET name='default'";
List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true);
List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true, true);
SqlCommand command = qry.get(0).getCommand();
Assert.assertTrue("update set name='default' is compatible with insert rewrite", command.isBatchedReWriteCompatible());
}
@ -211,7 +211,7 @@ public class ParserTest {
public void insertMultiInsert() throws SQLException {
String query =
"insert into test(id, name) values (:id,:name),(:id,:name) ON CONFLICT (id) DO NOTHING";
List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true);
List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true, true);
SqlCommand command = qry.get(0).getCommand();
Assert.assertEquals(34, command.getBatchRewriteValuesBraceOpenPosition());
Assert.assertEquals(56, command.getBatchRewriteValuesBraceClosePosition());
@ -241,7 +241,7 @@ public class ParserTest {
for (String mySql: sqlTests) {
List<NativeQuery> queries = Parser.parseJdbcSql(mySql,
false,false,
true, false, new String[0]);
true, false, true, new String[0]);
assertEquals(1, queries.size());
}
}

View File

@ -53,7 +53,7 @@ public class ReturningParserTest {
String query =
"insert into\"prep\"(a, " + prefix + columnName + suffix + ")values(1,2)" + prefix
+ returning + suffix;
List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true);
List<NativeQuery> qry = Parser.parseJdbcSql(query, true, true, true, true, true);
boolean returningKeywordPresent = qry.get(0).command.isReturningKeywordPresent();
boolean expectedReturning = this.returning.equalsIgnoreCase("returning")

View File

@ -108,14 +108,42 @@ public class TestUtil {
public static String getServer() {
return System.getProperty("server", "localhost");
}
/*
* Returns the Secondary Test server
*/
public static String getSecondaryServer() {
return System.getProperty("secondaryServer", "localhost");
}
/*
* Returns the third Test server
*/
public static String getSecondaryServer2() {
return System.getProperty("secondaryServer2", "localhost");
}
/*
* Returns the Test port
*/
public static int getPort() {
return Integer.parseInt(System.getProperty("port", System.getProperty("def_pgport")));
}
/*
* Returns the Secondary Test port
*/
public static int getSecondaryPort() {
return Integer.parseInt(System.getProperty("secondaryPort", System.getProperty("def_pgport")));
}
/*
* Returns the Third Test port
*/
public static int getSecondaryServerPort2() {
return Integer.parseInt(System.getProperty("secondaryServerPort2", System.getProperty("def_pgport")));
}
/*
* Returns the server side prepared statement threshold.
*/
@ -335,6 +363,7 @@ public class TestUtil {
props.put(PGProperty.PREFER_QUERY_MODE.getName(), value);
}
}
PGProperty.QUOTE_RETURNING_IDENTIFIERS.set(props, false);
// Enable Base4 tests to override host,port,database
String hostport = props.getProperty(SERVER_HOST_PORT_PROP, getServer() + ":" + getPort());
String database = props.getProperty(DATABASE_PROP, getDatabase());

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2012-2019. All rights reserved.
*/
package org.postgresql.test.dolphintest;
import org.junit.Test;
import org.postgresql.core.types.PGBlob;
import org.postgresql.jdbc.PgConnection;
import org.postgresql.test.TestUtil;
import org.postgresql.test.jdbc2.BaseTest4;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
public class BlobTest extends BaseTest4 {
public static void executeSql(Connection connection, String sql) throws SQLException {
try (PreparedStatement st = connection.prepareStatement(sql)) {
st.execute();
}
}
@Test
public void test1() throws Exception {
String sqlCreate = "create table if not exists t1"
+ "(id int, data1 tinyblob, data2 blob, data3 mediumblob, data4 longblob);";
String sqlDrop = "drop table if exists t1;";
String sqlDropUser = "drop user test_user cascade;";
String sqlQuery = "select * from t1";
String sqlInsert = "insert into t1 values (?, ?, ?, ?, ?);";
String sqlCreateUser = "CREATE USER test_user with password 'openGauss@123'";
String sqlGrantUser = "GRANT ALL PRIVILEGES TO test_user";
Properties props = new Properties();
props.put("blobMode", "ON");
props.put("binaryTransfer", "true");
/* test about not b_comp */
try (Connection con1 = TestUtil.openDB(props)) {
/* cannot create the table */
executeSql(con1, sqlCreate);
executeSql(con1, sqlDropUser);
executeSql(con1, sqlCreateUser);
executeSql(con1, sqlGrantUser);
}
/* test about b_comp but don't have dolphin plugin */
props.put("username", "test_user");
props.put("password", "openGauss@123");
try (Connection con1 = TestUtil.openDB(props)) {
/* cannot create the table */
executeSql(con1, sqlCreate);
}
Properties props1 = new Properties();
props1.put("blobMode", "ON");
props1.put("binaryTransfer", "true");
props1.put("database", "test_db");
try (Connection con1 = TestUtil.openDB(props1)) {
con1.unwrap(PgConnection.class).setDolphinCmpt(true);
executeSql(con1, sqlDrop);
executeSql(con1, sqlCreate);
try (PreparedStatement ps = con1.prepareStatement(sqlInsert)) {
ps.setInt(1, 1);
PGBlob blob = new PGBlob();
blob.setBytes(1, "abcdefgh\0ijklmn".getBytes(StandardCharsets.UTF_8));
ps.setBlob(2, blob);
ps.setBlob(3, blob);
ps.setBlob(4, blob);
ps.setBlob(5, blob);
ps.execute();
}
Statement statement = con1.createStatement();
ResultSet set = statement.executeQuery(sqlQuery);
while (set.next()) {
assertEquals("abcdefgh\0ijklmn", new String(set.getBlob(2).getBytes(1, 15), StandardCharsets.UTF_8));
assertEquals("abcdefgh\0ijklmn", new String(set.getBlob(3).getBytes(1, 15), StandardCharsets.UTF_8));
assertEquals("abcdefgh\0ijklmn", new String(set.getBlob(4).getBytes(1, 15), StandardCharsets.UTF_8));
assertEquals("abcdefgh\0ijklmn", new String(set.getBlob(5).getBytes(1, 15), StandardCharsets.UTF_8));
}
}
}
@Test
public void test2() throws Exception {
Properties props1 = new Properties();
props1.put("blobMode", "ON");
props1.put("binaryTransfer", "true");
props1.put("database", "test_db");
String sqlQuery = "select * from t1";
ResultSet set1 = null;
ResultSet set2 = null;
try (Connection con1 = TestUtil.openDB(props1)) {
con1.unwrap(PgConnection.class).setDolphinCmpt(true);
Statement statement = con1.createStatement();
set1 = statement.executeQuery(sqlQuery);
while (set1.next()) {
assertEquals("abcdefgh\0ijklmn", set1.getString(2));
assertEquals("abcdefgh\0ijklmn", set1.getString(3));
assertEquals("abcdefgh\0ijklmn", set1.getString(4));
assertEquals("abcdefgh\0ijklmn", set1.getString(5));
}
con1.unwrap(PgConnection.class).setDolphinCmpt(false);
set2 = statement.executeQuery(sqlQuery);
while (set2.next()) {
assertEquals("abcdefgh\0ijklmn", set2.getString(2));
assertEquals("616263646566676800696A6B6C6D6E", set2.getString(3));
assertEquals("abcdefgh\0ijklmn", set2.getString(4));
assertEquals("abcdefgh\0ijklmn", set2.getString(5));
}
} finally {
if (set1 != null) {
set1.close();
}
if (set2 != null) {
set2.close();
}
}
}
}

View File

@ -95,16 +95,16 @@ public class CompositeQueryParseTest {
@Test
public void testHasReturning() throws SQLException {
List<NativeQuery> queries = Parser.parseJdbcSql("insert into foo (a,b,c) values (?,?,?) RetuRning a", true, true, false,
true);
true, true);
NativeQuery query = queries.get(0);
assertTrue("The parser should find the word returning", query.command.isReturningKeywordPresent());
queries = Parser.parseJdbcSql("insert into foo (a,b,c) values (?,?,?)", true, true, false, true);
queries = Parser.parseJdbcSql("insert into foo (a,b,c) values (?,?,?)", true, true, false, true, true);
query = queries.get(0);
assertFalse("The parser should not find the word returning", query.command.isReturningKeywordPresent());
queries = Parser.parseJdbcSql("insert into foo (a,b,c) values ('returning',?,?)", true, true, false,
true);
true, true);
query = queries.get(0);
assertFalse("The parser should not find the word returning as it is in quotes ", query.command.isReturningKeywordPresent());
}
@ -112,7 +112,7 @@ public class CompositeQueryParseTest {
@Test
public void testSelect() throws SQLException {
List<NativeQuery> queries;
queries = Parser.parseJdbcSql("select 1 as returning from (update table)", true, true, false, true);
queries = Parser.parseJdbcSql("select 1 as returning from (update table)", true, true, false, true, true);
NativeQuery query = queries.get(0);
assertEquals("This is a select ", SqlCommandType.SELECT, query.command.getType());
assertTrue("Returning is OK here as it is not an insert command ", query.command.isReturningKeywordPresent());
@ -121,7 +121,7 @@ public class CompositeQueryParseTest {
@Test
public void testDelete() throws SQLException {
List<NativeQuery> queries = Parser.parseJdbcSql("DeLeTe from foo where a=1", true, true, false,
true);
true, true);
NativeQuery query = queries.get(0);
assertEquals("This is a delete command", SqlCommandType.DELETE, query.command.getType());
}
@ -130,7 +130,7 @@ public class CompositeQueryParseTest {
public void testMultiQueryWithBind() throws SQLException {
// braces around (42) are required to puzzle the parser
String sql = "INSERT INTO inttable(a) VALUES (?);SELECT (42)";
List<NativeQuery> queries = Parser.parseJdbcSql(sql, true, true, true,true);
List<NativeQuery> queries = Parser.parseJdbcSql(sql, true, true, true,true, true);
NativeQuery query = queries.get(0);
assertEquals("query(0) of " + sql,
"INSERT: INSERT INTO inttable(a) VALUES ($1)",
@ -143,7 +143,7 @@ public class CompositeQueryParseTest {
@Test
public void testMove() throws SQLException {
List<NativeQuery> queries = Parser.parseJdbcSql("MoVe NEXT FROM FOO", true, true, false, true);
List<NativeQuery> queries = Parser.parseJdbcSql("MoVe NEXT FROM FOO", true, true, false, true, true);
NativeQuery query = queries.get(0);
assertEquals("This is a move command", SqlCommandType.MOVE, query.command.getType());
}
@ -152,7 +152,7 @@ public class CompositeQueryParseTest {
public void testUpdate() throws SQLException {
List<NativeQuery> queries;
NativeQuery query;
queries = Parser.parseJdbcSql("update foo set (a=?,b=?,c=?)", true, true, false, true);
queries = Parser.parseJdbcSql("update foo set (a=?,b=?,c=?)", true, true, false, true, true);
query = queries.get(0);
assertEquals("This is an UPDATE command", SqlCommandType.UPDATE, query.command.getType());
}
@ -160,11 +160,11 @@ public class CompositeQueryParseTest {
@Test
public void testInsert() throws SQLException {
List<NativeQuery> queries = Parser.parseJdbcSql("InSeRt into foo (a,b,c) values (?,?,?) returning a", true, true, false,
true);
true, true);
NativeQuery query = queries.get(0);
assertEquals("This is an INSERT command", SqlCommandType.INSERT, query.command.getType());
queries = Parser.parseJdbcSql("select 1 as insert", true, true, false, true);
queries = Parser.parseJdbcSql("select 1 as insert", true, true, false, true, true);
query = queries.get(0);
assertEquals("This is a SELECT command", SqlCommandType.SELECT, query.command.getType());
}
@ -172,7 +172,7 @@ public class CompositeQueryParseTest {
@Test
public void testWithSelect() throws SQLException {
List<NativeQuery> queries;
queries = Parser.parseJdbcSql("with update as (update foo set (a=?,b=?,c=?)) select * from update", true, true, false, true);
queries = Parser.parseJdbcSql("with update as (update foo set (a=?,b=?,c=?)) select * from update", true, true, false, true, true);
NativeQuery query = queries.get(0);
assertEquals("with ... () select", SqlCommandType.SELECT, query.command.getType());
}
@ -180,7 +180,7 @@ public class CompositeQueryParseTest {
@Test
public void testWithInsert() throws SQLException {
List<NativeQuery> queries;
queries = Parser.parseJdbcSql("with update as (update foo set (a=?,b=?,c=?)) insert into table(select) values(1)", true, true, false, true);
queries = Parser.parseJdbcSql("with update as (update foo set (a=?,b=?,c=?)) insert into table(select) values(1)", true, true, false, true, true);
NativeQuery query = queries.get(0);
assertEquals("with ... () insert", SqlCommandType.INSERT, query.command.getType());
}
@ -201,7 +201,7 @@ public class CompositeQueryParseTest {
boolean splitStatements) {
try {
return toString(
Parser.parseJdbcSql(query, standardConformingStrings, withParameters, splitStatements, false));
Parser.parseJdbcSql(query, standardConformingStrings, withParameters, splitStatements, false, true));
} catch (SQLException e) {
throw new IllegalStateException("Parser.parseJdbcSql: " + e.getMessage(), e);
}

View File

@ -47,7 +47,7 @@ public class SqlCommandParseTest {
@Test
public void run() throws SQLException {
List<NativeQuery> queries;
queries = Parser.parseJdbcSql(sql, true, true, false, true);
queries = Parser.parseJdbcSql(sql, true, true, false, true, true);
NativeQuery query = queries.get(0);
assertEquals(sql, type, query.command.getType());
}

View File

@ -0,0 +1,49 @@
package org.postgresql.test.jdbc4;
import org.junit.Test;
import org.postgresql.test.TestUtil;
import org.postgresql.test.jdbc2.BaseTest4;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.*;
import java.util.Properties;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
public class UnsignedTest extends BaseTest4 {
/**
* test uint8 type
* @throws Exception
*/
@Test
public void testUint8() throws SQLException {
TestUtil.createTable(con, "test_unit8", "id uint8");
PreparedStatement pstmt = con.prepareStatement("INSERT INTO test_unit8 VALUES (?)");
BigInteger b = new BigInteger("9223372036859999999");
pstmt.setObject(1, b, Types.NUMERIC);
pstmt.executeUpdate();
BigInteger b2 = new BigInteger("15223372036859999999");
pstmt.setObject(1, b2, Types.NUMERIC);
pstmt.executeUpdate();
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id FROM test_unit8");
assertTrue(rs.next());
Object r1 = rs.getObject(1);
assertNotNull(r1);
assertEquals(b, r1);
assertTrue(rs.next());
Object r2 = rs.getObject(1);
assertNotNull(r2);
assertEquals(b2, r2);
TestUtil.dropTable(con, "test_unit8");
}
}

View File

@ -0,0 +1,906 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.postgresql.test.quickautobalance;
import org.junit.Test;
import org.postgresql.QueryCNListUtils;
import org.postgresql.jdbc.PgConnection;
import org.postgresql.jdbc.StatementCancelState;
import org.postgresql.log.Log;
import org.postgresql.log.Logger;
import org.postgresql.quickautobalance.Cluster;
import org.postgresql.quickautobalance.ConnectionInfo;
import org.postgresql.quickautobalance.ConnectionManager;
import org.postgresql.quickautobalance.DataNode;
import org.postgresql.quickautobalance.ReflectUtil;
import org.postgresql.test.TestUtil;
import org.postgresql.util.GT;
import org.postgresql.util.HostSpec;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Cluster test
*/
public class ClusterTest {
private static Log LOGGER = Logger.getLogger(ClusterTest.class.getName());
private static final String FAKE_HOST = "127.127.217.217";
private static final String FAKE_PORT = "1";
private static final String FAKE_USER = "fakeuser";
private static final String FAKE_PASSWORD = "fakepassword";
private static final int DN_NUM = 3;
private PgConnection getConnection(String url, Properties properties) throws SQLException {
return DriverManager.getConnection(url, properties).unwrap(PgConnection.class);
}
private HostSpec[] initHostSpecs() {
HostSpec[] hostSpecs = new HostSpec[DN_NUM];
hostSpecs[0] = new HostSpec(TestUtil.getServer(), TestUtil.getPort());
hostSpecs[1] = new HostSpec(TestUtil.getSecondaryServer(), TestUtil.getSecondaryPort());
hostSpecs[2] = new HostSpec(TestUtil.getSecondaryServer2(), TestUtil.getSecondaryServerPort2());
return hostSpecs;
}
private HostSpec[] initHostSpecsWithInvalidNode() {
HostSpec[] hostSpecs = new HostSpec[DN_NUM];
hostSpecs[0] = new HostSpec(TestUtil.getServer(), TestUtil.getPort());
hostSpecs[1] = new HostSpec(TestUtil.getSecondaryServer(), TestUtil.getSecondaryPort());
hostSpecs[2] = new HostSpec(FAKE_HOST, Integer.parseInt(FAKE_PORT));
return hostSpecs;
}
private boolean checkHostSpecs(HostSpec[] hostSpecs) {
if (hostSpecs.length != DN_NUM) {
return false;
}
for (int i = 0; i < DN_NUM; i++) {
if ("localhost".equals(hostSpecs[i].getHost())) {
return false;
}
}
return true;
}
private Properties initPriority(HostSpec[] hostSpecs) {
String host = hostSpecs[0].getHost() + "," + hostSpecs[1].getHost() + "," + hostSpecs[2].getHost();
String port = hostSpecs[0].getPort() + "," + hostSpecs[1].getPort() + "," + hostSpecs[2].getPort();
Properties properties = new Properties();
properties.setProperty("PGDBNAME", TestUtil.getDatabase());
properties.setProperty("PGHOSTURL", host);
properties.setProperty("PGPORT", port);
properties.setProperty("PGPORTURL", port);
properties.setProperty("PGHOST", host);
properties.setProperty("user", TestUtil.getUser());
properties.setProperty("password", TestUtil.getPassword());
return properties;
}
private String initURL(HostSpec[] hostSpecs) {
String host1 = hostSpecs[0].getHost() + ":" + hostSpecs[0].getPort();
String host2 = hostSpecs[1].getHost() + ":" + hostSpecs[1].getPort();
String host3 = hostSpecs[2].getHost() + ":" + hostSpecs[2].getPort();
return "jdbc:postgresql://" + host1 + "," + host2 + "," + host3 + "/" + TestUtil.getDatabase();
}
@Test
public void checkConnectionsValidityTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url = initURL(hostSpecs) + "?autoBalance=leastconn";
int total = 20;
int remove = 5;
List<PgConnection> pgConnectionList = new ArrayList<>();
for (int i = 0; i < total; i++) {
try {
PgConnection pgConnection = getConnection(url, properties);
pgConnectionList.add(pgConnection);
cluster.setConnection(pgConnection, properties);
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
List<Integer> result = cluster.checkConnectionsValidity();
int sum = result.stream().reduce(Integer::sum).orElse(0);
assertEquals(0, sum);
for (int i = 0; i < remove; i++) {
try {
pgConnectionList.get(i).close();
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
sum = 0;
result = cluster.checkConnectionsValidity();
sum = result.stream().reduce(Integer::sum).orElse(0);
assertEquals(remove, sum);
ConnectionManager.getInstance().clear();
}
@Test
public void setMinReservedConPerClusterDefaultParamsTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
assertFalse(cluster.isEnableMinReservedConPerCluster());
assertFalse(cluster.isEnableMinReservedConPerDatanode());
assertEquals(cluster.getMinReservedConPerCluster(), 0);
assertEquals(cluster.getMinReservedConPerDatanode(), 0);
}
@Test (expected = SQLException.class)
public void setMinReservedConPerClusterParsedFailedTest() throws SQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url1 = initURL(hostSpecs) + "?autoBalance=leastconn";
properties.setProperty("minReservedConPerCluster", "asafaas");
try (PgConnection pgConnection = getConnection(url1, properties)) {
cluster.setConnection(pgConnection, properties);
} catch (SQLException e) {
e.printStackTrace();
assertEquals(PSQLState.INVALID_PARAMETER_TYPE.getState(), e.getSQLState());
throw e;
}
}
@Test (expected = SQLException.class)
public void setMinReservedConPerClusterParsedTooSmallTest() throws SQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url1 = initURL(hostSpecs) + "?autoBalance=leastconn";
properties.setProperty("minReservedConPerCluster", "-1");
try (PgConnection pgConnection = getConnection(url1, properties)) {
cluster.setConnection(pgConnection, properties);
} catch (SQLException e) {
e.printStackTrace();
assertEquals(PSQLState.INVALID_PARAMETER_VALUE.getState(), e.getSQLState());
throw e;
}
}
@Test (expected = SQLException.class)
public void setMinReservedConPerClusterParsedTooBigTest() throws SQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url1 = initURL(hostSpecs) + "?autoBalance=leastconn";
properties.setProperty("minReservedConPerCluster", "200");
try (PgConnection pgConnection = getConnection(url1, properties)) {
cluster.setConnection(pgConnection, properties);
} catch (SQLException e) {
e.printStackTrace();
assertEquals(PSQLState.INVALID_PARAMETER_VALUE.getState(), e.getSQLState());
throw e;
}
}
@Test (expected = SQLException.class)
public void setMinReservedConPerDatanodeParsedFailedTest() throws SQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url1 = initURL(hostSpecs) + "?autoBalance=leastconn";
properties.setProperty("minReservedConPerDatanode", "asafaas");
try (PgConnection pgConnection = getConnection(url1, properties)) {
cluster.setConnection(pgConnection, properties);
} catch (SQLException e) {
e.printStackTrace();
assertEquals(PSQLState.INVALID_PARAMETER_TYPE.getState(), e.getSQLState());
throw e;
}
}
@Test (expected = SQLException.class)
public void setMinReservedConPerDatanodeParsedTooSmallTest() throws SQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url1 = initURL(hostSpecs) + "?autoBalance=leastconn";
properties.setProperty("minReservedConPerDatanode", "-1");
try (PgConnection pgConnection = getConnection(url1, properties)) {
cluster.setConnection(pgConnection, properties);
} catch (SQLException e) {
e.printStackTrace();
assertEquals(PSQLState.INVALID_PARAMETER_VALUE.getState(), e.getSQLState());
throw e;
}
}
@Test (expected = SQLException.class)
public void setMinReservedConPerDatanodeParsedTooBigTest() throws SQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String paramsOutOfRange = "2000000000";
String url1 = initURL(hostSpecs) + "?autoBalance=leastconn";
properties.setProperty("minReservedConPerDatanode", paramsOutOfRange);
try (PgConnection pgConnection = getConnection(url1, properties)) {
cluster.setConnection(pgConnection, properties);
} catch (SQLException e) {
e.printStackTrace();
assertEquals(PSQLState.INVALID_PARAMETER_VALUE.getState(), e.getSQLState());
throw e;
}
}
@Test
public void updateParamsFailedTest() throws SQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
assertFalse(cluster.isEnableMinReservedConPerCluster());
assertFalse(cluster.isEnableMinReservedConPerDatanode());
assertEquals(cluster.getMinReservedConPerCluster(), 0);
assertEquals(cluster.getMinReservedConPerDatanode(), 0);
String url2 = initURL(hostSpecs) + "?autoBalance=leastconn";
properties.setProperty("minReservedConPerCluster", "40");
properties.setProperty("minReservedConPerDatanode", "50");
PgConnection pgConnection1 = getConnection(url2, properties);
cluster.setConnection(pgConnection1, properties);
assertTrue(cluster.isEnableMinReservedConPerCluster());
assertTrue(cluster.isEnableMinReservedConPerDatanode());
assertEquals(cluster.getMinReservedConPerCluster(), 40);
assertEquals(cluster.getMinReservedConPerDatanode(), 50);
pgConnection1.close();
String url3 = initURL(hostSpecs) + "?autoBalance=leastconn";
properties.setProperty("minReservedConPerCluster", "60");
properties.setProperty("minReservedConPerDatanode", "70");
PgConnection pgConnection2 = getConnection(url3, properties);
cluster.setConnection(pgConnection2, properties);
assertTrue(cluster.isEnableMinReservedConPerCluster());
assertTrue(cluster.isEnableMinReservedConPerDatanode());
assertEquals(cluster.getMinReservedConPerCluster(), 40);
assertEquals(cluster.getMinReservedConPerDatanode(), 50);
pgConnection2.close();
ConnectionManager.getInstance().clear();
}
@Test
public void updateParamsSuccessTest() throws SQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
assertFalse(cluster.isEnableMinReservedConPerCluster());
assertFalse(cluster.isEnableMinReservedConPerDatanode());
assertEquals(cluster.getMinReservedConPerCluster(), 0);
assertEquals(cluster.getMinReservedConPerDatanode(), 0);
String url2 = initURL(hostSpecs) + "?autoBalance=leastconn";
properties.setProperty("minReservedConPerCluster", "40");
properties.setProperty("minReservedConPerDatanode", "50");
PgConnection pgConnection1 = getConnection(url2, properties);
cluster.setConnection(pgConnection1, properties);
assertTrue(cluster.isEnableMinReservedConPerCluster());
assertTrue(cluster.isEnableMinReservedConPerDatanode());
assertEquals(cluster.getMinReservedConPerCluster(), 40);
assertEquals(cluster.getMinReservedConPerDatanode(), 50);
pgConnection1.close();
String url3 = initURL(hostSpecs) + "?autoBalance=leastconn";
properties.setProperty("minReservedConPerCluster", "20");
properties.setProperty("minReservedConPerDatanode", "30");
PgConnection pgConnection2 = getConnection(url3, properties);
cluster.setConnection(pgConnection2, properties);
assertTrue(cluster.isEnableMinReservedConPerCluster());
assertTrue(cluster.isEnableMinReservedConPerDatanode());
assertEquals(cluster.getMinReservedConPerCluster(), 20);
assertEquals(cluster.getMinReservedConPerDatanode(), 30);
pgConnection2.close();
ConnectionManager.getInstance().clear();
}
@Test
public void setConnectionTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url = initURL(hostSpecs) + "?autoBalance=leastconn";
PgConnection pgConnection;
try {
pgConnection = getConnection(url, properties);
cluster.setConnection(pgConnection, properties);
ConnectionInfo connectionInfo = cluster.getConnectionInfo(pgConnection);
assertEquals(pgConnection, connectionInfo.getPgConnection());
cluster.setConnection(null, properties);
cluster.setConnection(pgConnection, null);
ConnectionManager.getInstance().clear();
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
private void setConnection(Cluster cluster, String ip, int port, Properties properties) {
String url = "jdbc:postgresql://" + ip + ":" + port + "/" + TestUtil.getDatabase();
try {
final PgConnection pgConnection = getConnection(url, properties);
cluster.incrementCachedCreatingConnectionSize(new HostSpec(ip, port));
cluster.setConnection(pgConnection, properties);
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
@Test
public void sortDnsByLeastConnTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
int num1 = 4;
int num2 = 6;
int num3 = 5;
for (int i = 0; i < num1; i++) {
setConnection(cluster, TestUtil.getServer(), TestUtil.getPort(), properties);
}
for (int i = 0; i < num2; i++) {
setConnection(cluster, TestUtil.getSecondaryServer(), TestUtil.getSecondaryPort(), properties);
}
for (int i = 0; i < num3; i++) {
setConnection(cluster, TestUtil.getSecondaryServer2(), TestUtil.getSecondaryServerPort2(), properties);
}
List<HostSpec> result = cluster.sortDnsByLeastConn(Arrays.asList(hostSpecs));
LOGGER.info(GT.tr("after sort: {0}", result.toString()));
assertEquals(TestUtil.getServer(), result.get(0).getHost());
assertEquals(TestUtil.getPort(), result.get(0).getPort());
assertEquals(TestUtil.getSecondaryServer(), result.get(2).getHost());
assertEquals(TestUtil.getSecondaryPort(), result.get(2).getPort());
assertEquals(TestUtil.getSecondaryServer2(), result.get(1).getHost());
assertEquals(TestUtil.getSecondaryServerPort2(), result.get(1).getPort());
ConnectionManager.getInstance().clear();
}
@Test
public void sortDnsByLeastConnWithOneNodeFailedTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
int num1 = 4;
int num2 = 6;
int num3 = 5;
for (int i = 0; i < num1; i++) {
setConnection(cluster, TestUtil.getServer(), TestUtil.getPort(), properties);
}
for (int i = 0; i < num2; i++) {
setConnection(cluster, TestUtil.getSecondaryServer(), TestUtil.getSecondaryPort(), properties);
}
for (int i = 0; i < num3; i++) {
setConnection(cluster, TestUtil.getSecondaryServer2(), TestUtil.getSecondaryServerPort2(), properties);
}
Map<HostSpec, DataNode> cachedDnList = ReflectUtil.getField(Cluster.class, cluster, Map.class, "cachedDnList");
DataNode dataNode = cachedDnList.getOrDefault(hostSpecs[0], null);
ReflectUtil.setField(DataNode.class, dataNode, "dataNodeState", false);
List<HostSpec> result = cluster.sortDnsByLeastConn(Arrays.asList(hostSpecs));
LOGGER.info(GT.tr("after sort: {0}", result.toString()));
assertEquals(TestUtil.getServer(), result.get(2).getHost());
assertEquals(TestUtil.getPort(), result.get(2).getPort());
assertEquals(TestUtil.getSecondaryServer(), result.get(1).getHost());
assertEquals(TestUtil.getSecondaryPort(), result.get(1).getPort());
assertEquals(TestUtil.getSecondaryServer2(), result.get(0).getHost());
assertEquals(TestUtil.getSecondaryServerPort2(), result.get(0).getPort());
ConnectionManager.getInstance().clear();
}
@Test
public void checkDnStateSpecSuccessTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Arrays.sort(hostSpecs);
String URLIdentifier = String.valueOf(hostSpecs);
String pgHostUrl = hostSpecs[0].getHost() + "," + hostSpecs[1].getHost() + "," + hostSpecs[2].getHost();
String pgPortUrl = hostSpecs[0].getPort() + "," + hostSpecs[1].getPort() + "," + hostSpecs[2].getPort();
Properties clusterProperties = new Properties();
clusterProperties.setProperty("user", TestUtil.getUser());
clusterProperties.setProperty("password", TestUtil.getPassword());
clusterProperties.setProperty("PGDBNAME", TestUtil.getDatabase());
clusterProperties.setProperty("PGHOSTURL", pgHostUrl);
clusterProperties.setProperty("PGPORT", pgPortUrl);
clusterProperties.setProperty("PGPORTURL", pgPortUrl);
clusterProperties.setProperty("PGHOST", pgHostUrl);
Cluster cluster = new Cluster(URLIdentifier, clusterProperties);
for (int i = 0; i < DN_NUM; i++) {
assertTrue(cluster.checkDnState(hostSpecs[i]));
}
ConnectionManager.getInstance().clear();
}
@Test
public void checkDnStateUserOrPasswordErrorTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Arrays.sort(hostSpecs);
String URLIdentifier = String.valueOf(hostSpecs);
String pgHostUrl = hostSpecs[0].getHost() + "," + hostSpecs[1].getHost() + "," + hostSpecs[2].getHost();
String pgPortUrl = hostSpecs[0].getPort() + "," + hostSpecs[1].getPort() + "," + hostSpecs[2].getPort();
Properties invalidProperties = new Properties();
invalidProperties.setProperty("user", FAKE_USER);
invalidProperties.setProperty("password", FAKE_PASSWORD);
invalidProperties.setProperty("PGDBNAME", TestUtil.getDatabase());
invalidProperties.setProperty("PGHOSTURL", pgHostUrl);
invalidProperties.setProperty("PGPORT", pgPortUrl);
invalidProperties.setProperty("PGPORTURL", pgPortUrl);
invalidProperties.setProperty("PGHOST", pgHostUrl);
Cluster cluster = new Cluster(URLIdentifier, invalidProperties);
for (int i = 0; i < DN_NUM; i++) {
assertFalse(cluster.checkDnState(hostSpecs[i]));
}
Properties validProperties = new Properties();
validProperties.setProperty("user", TestUtil.getUser());
validProperties.setProperty("password", TestUtil.getPassword());
validProperties.setProperty("PGDBNAME", TestUtil.getDatabase());
validProperties.setProperty("PGHOSTURL", pgHostUrl);
validProperties.setProperty("PGPORT", pgPortUrl);
validProperties.setProperty("PGPORTURL", pgPortUrl);
validProperties.setProperty("PGHOST", pgHostUrl);
cluster.setProperties(invalidProperties);
cluster.setProperties(validProperties);
for (int i = 0; i < DN_NUM; i++) {
assertTrue(cluster.checkDnState(hostSpecs[i]));
}
ConnectionManager.getInstance().clear();
}
@Test
public void checkDnStateConnectFailedTest() throws PSQLException {
HostSpec[] hostSpecs = new HostSpec[DN_NUM];
for (int i = 0; i < DN_NUM; i++) {
hostSpecs[i] = new HostSpec(FAKE_HOST, Integer.parseInt(FAKE_PORT));
}
if (!checkHostSpecs(hostSpecs)) {
return;
}
Arrays.sort(hostSpecs);
String URLIdentifier = Arrays.toString(hostSpecs);
String pgHostUrl = hostSpecs[0].getHost() + "," + hostSpecs[1].getHost() + "," + hostSpecs[2].getHost();
String pgPortUrl = hostSpecs[0].getPort() + "," + hostSpecs[1].getPort() + "," + hostSpecs[2].getPort();
Properties invalidProperties = new Properties();
invalidProperties.setProperty("user", TestUtil.getUser());
invalidProperties.setProperty("password", TestUtil.getPassword());
invalidProperties.setProperty("PGDBNAME", TestUtil.getDatabase());
invalidProperties.setProperty("PGHOSTURL", pgHostUrl);
invalidProperties.setProperty("PGPORT", pgPortUrl);
invalidProperties.setProperty("PGPORTURL", pgPortUrl);
invalidProperties.setProperty("PGHOST", pgHostUrl);
Cluster cluster = new Cluster(URLIdentifier, invalidProperties);
for (int i = 0; i < DN_NUM; i++) {
assertFalse(cluster.checkDnState(hostSpecs[i]));
}
ConnectionManager.getInstance().clear();
}
@Test
public void checkClusterStateSuccessTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
Arrays.sort(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url = initURL(hostSpecs) + "?autoBalance=leastconn";
int total = 10;
for (int i = 0; i < total; i++) {
try {
PgConnection pgConnection = getConnection(url, properties);
cluster.setConnection(pgConnection, properties);
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
int invalidDn = cluster.checkClusterState();
assertEquals(0, invalidDn);
ConnectionManager.getInstance().clear();
}
@Test
public void checkClusterStateOneNodeFailedTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecsWithInvalidNode();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
Arrays.sort(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url = initURL(hostSpecs) + "?autoBalance=leastconn";
int total = 10;
for (int i = 0; i < total; i++) {
try {
PgConnection pgConnection = getConnection(url, properties);
cluster.setConnection(pgConnection, properties);
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
int invalidDn = cluster.checkClusterState();
assertEquals(1, invalidDn);
ConnectionManager.getInstance().clear();
}
@Test
public void checkClusterStateAndQuickLoadBalanceTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
properties.setProperty("autoBalance", "leastconn");
properties.setProperty("maxIdleTimeBeforeTerminal", "1");
properties.setProperty("enableQuickAutoBalance", "true");
Arrays.sort(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url = initURL(hostSpecs) + "?autoBalance=leastconn";
int total = 9;
for (int i = 0; i < total; i++) {
try {
PgConnection pgConnection = getConnection(url, properties);
cluster.setConnection(pgConnection, properties);
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
try {
Thread.sleep(1100);
} catch (InterruptedException e) {
e.printStackTrace();
fail();
}
// set dnState='false' of dn1
Map<HostSpec, DataNode> cachedDnList = ReflectUtil.getField(Cluster.class, cluster,
Map.class, "cachedDnList");
DataNode dataNode = cachedDnList.get(hostSpecs[0]);
dataNode.setDataNodeState(false);
int invalidDn = cluster.checkClusterState();
assertEquals(0, invalidDn);
// check cluster state: jdbc will find dnState change to true from false, and execute quickAutoBalance.
Queue<ConnectionInfo> abandonedConnectionList = ReflectUtil.getField(Cluster.class, cluster,
Queue.class, "abandonedConnectionList");
assertEquals(6, abandonedConnectionList.size());
ConnectionManager.getInstance().clear();
}
@Test
public void checkClusterStateAndQuickLoadBalanceWithParamsTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
// init properties with quickLoadBalance.
Properties properties = initPriority(hostSpecs);
properties.setProperty("autoBalance", "leastconn");
properties.setProperty("maxIdleTimeBeforeTerminal", "1");
properties.setProperty("enableQuickAutoBalance", "true");
properties.setProperty("minReservedConPerDatanode", "0");
properties.setProperty("minReservedConPerCluster", "75");
Arrays.sort(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url = initURL(hostSpecs) + "?autoBalance=leastconn";
int total = 12;
// set connection
for (int i = 0; i < total; i++) {
try {
PgConnection pgConnection = getConnection(url, properties);
cluster.setConnection(pgConnection, properties);
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
try {
Thread.sleep(1100);
} catch (InterruptedException e) {
e.printStackTrace();
fail();
}
// set dnState='false' of dn1
Map<HostSpec, DataNode> cachedDnList = ReflectUtil.getField(Cluster.class, cluster, Map.class,
"cachedDnList");
DataNode dataNode = cachedDnList.get(hostSpecs[0]);
dataNode.setDataNodeState(false);
// check cluster state: jdbc will find dnState change to true from false, and execute quickAutoBalance.
int invalidDn = cluster.checkClusterState();
assertEquals(0, invalidDn);
Queue<ConnectionInfo> abandonedConnectionList = ReflectUtil.getField(Cluster.class, cluster,
Queue.class, "abandonedConnectionList");
assertEquals((int) (total / 3 * 2 * (100 - 75) / 100), abandonedConnectionList.size());
ConnectionManager.getInstance().clear();
}
@Test
public void setConnectionStateTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url = initURL(hostSpecs) + "?autoBalance=leastconn";
PgConnection pgConnection;
try {
pgConnection = getConnection(url, properties);
cluster.setConnection(pgConnection, properties);
assertEquals(StatementCancelState.IDLE, cluster.getConnectionInfo(pgConnection).getConnectionState());
cluster.setConnectionState(pgConnection, StatementCancelState.IN_QUERY);
assertEquals(StatementCancelState.IN_QUERY, cluster.getConnectionInfo(pgConnection).getConnectionState());
cluster.setConnectionState(pgConnection, StatementCancelState.CANCELLED);
assertEquals(StatementCancelState.CANCELLED, cluster.getConnectionInfo(pgConnection).getConnectionState());
cluster.setConnectionState(pgConnection, StatementCancelState.CANCELING);
assertEquals(StatementCancelState.CANCELING, cluster.getConnectionInfo(pgConnection).getConnectionState());
ConnectionManager.getInstance().clear();
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
@Test
public void incrementCachedCreatingConnectionSizeTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
// init properties with quickLoadBalance.
Properties properties = initPriority(hostSpecs);
properties.setProperty("autoBalance", "leastconn");
Arrays.sort(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
Map<HostSpec, DataNode> cachedDnList = ReflectUtil.getField(Cluster.class, cluster, Map.class,
"cachedDnList");
cluster.incrementCachedCreatingConnectionSize(hostSpecs[0]);
assertEquals(1, cachedDnList.get(hostSpecs[0]).getCachedCreatingConnectionSize());
cluster.incrementCachedCreatingConnectionSize(hostSpecs[0]);
assertEquals(2, cachedDnList.get(hostSpecs[0]).getCachedCreatingConnectionSize());
cluster.incrementCachedCreatingConnectionSize(hostSpecs[1]);
assertEquals(1, cachedDnList.get(hostSpecs[1]).getCachedCreatingConnectionSize());
assertEquals(0, cluster.incrementCachedCreatingConnectionSize(new HostSpec(FAKE_HOST,
Integer.parseInt(FAKE_PORT))));
ConnectionManager.getInstance().clear();
}
@Test
public void decrementCachedCreatingConnectionSizeTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
// init properties with quickLoadBalance.
Properties properties = initPriority(hostSpecs);
properties.setProperty("autoBalance", "leastconn");
Arrays.sort(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
Map<HostSpec, DataNode> cachedDnList = ReflectUtil.getField(Cluster.class, cluster,
Map.class, "cachedDnList");
cluster.incrementCachedCreatingConnectionSize(hostSpecs[0]);
cluster.incrementCachedCreatingConnectionSize(hostSpecs[0]);
assertEquals(2, cachedDnList.get(hostSpecs[0]).getCachedCreatingConnectionSize());
cluster.decrementCachedCreatingConnectionSize(hostSpecs[0]);
assertEquals(1, cachedDnList.get(hostSpecs[0]).getCachedCreatingConnectionSize());
cluster.decrementCachedCreatingConnectionSize(hostSpecs[0]);
assertEquals(0, cachedDnList.get(hostSpecs[0]).getCachedCreatingConnectionSize());
cluster.decrementCachedCreatingConnectionSize(hostSpecs[0]);
assertEquals(0, cachedDnList.get(hostSpecs[0]).getCachedCreatingConnectionSize());
assertEquals(0, cluster.incrementCachedCreatingConnectionSize(new HostSpec(FAKE_HOST,
Integer.parseInt(FAKE_PORT))));
ConnectionManager.getInstance().clear();
}
@Test
public void setPropertiesTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties1 = initPriority(hostSpecs);
properties1.setProperty("autoBalance", "leastconn");
String URLIdentifier = QueryCNListUtils.keyFromURL(properties1);
Cluster cluster = new Cluster(URLIdentifier, properties1);
List<Properties> cachedPropertiesList = ReflectUtil.getField(Cluster.class, cluster, List.class,
"cachedPropertiesList");
assertEquals(1, cachedPropertiesList.size());
assertEquals(TestUtil.getUser(), cachedPropertiesList.get(0).getProperty("user", ""));
assertEquals(TestUtil.getPassword(), cachedPropertiesList.get(0).getProperty("password", ""));
Properties properties2 = initPriority(hostSpecs);
properties2.setProperty("user", "fakeuser");
properties2.setProperty("password", "fakepassword");
cluster.setProperties(properties2);
assertEquals(2, cachedPropertiesList.size());
assertEquals("fakeuser", cachedPropertiesList.get(1).getProperty("user", ""));
assertEquals("fakepassword", cachedPropertiesList.get(1).getProperty("password", ""));
Properties properties3 = initPriority(hostSpecs);
properties3.setProperty("password", "fakepassword2");
cluster.setProperties(properties3);
assertEquals(2, cachedPropertiesList.size());
assertEquals(TestUtil.getUser(), cachedPropertiesList.get(0).getProperty("user", ""));
assertEquals("fakepassword2", cachedPropertiesList.get(0).getProperty("password", ""));
ConnectionManager.getInstance().clear();
}
@Test
public void closeConnectionTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
properties.setProperty("autoBalance", "leastconn");
properties.setProperty("enableQuickAutoBalance", "true");
properties.setProperty("maxIdleTimeBeforeTerminal", "1");
Arrays.sort(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url = initURL(hostSpecs);
int total = 50;
List<PgConnection> pgConnectionList = new ArrayList<>();
for (int i = 0; i < total; i++) {
try {
PgConnection pgConnection = getConnection(url, properties);
cluster.setConnection(pgConnection, properties);
pgConnectionList.add(pgConnection);
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
try {
Thread.sleep(1100);
} catch (InterruptedException e) {
e.printStackTrace();
fail();
}
Queue<ConnectionInfo> abandonedConnectionList = ReflectUtil.getField(Cluster.class, cluster,
Queue.class, "abandonedConnectionList");
ReflectUtil.setField(Cluster.class, cluster, "totalAbandonedConnectionSize", total);
ReflectUtil.setField(Cluster.class, cluster, "quickAutoBalanceStartTime", Long.MAX_VALUE);
for (PgConnection pgConnection : pgConnectionList) {
ConnectionInfo connectionInfo = cluster.getConnectionInfo(pgConnection);
abandonedConnectionList.add(connectionInfo);
}
assertEquals((int) (Math.ceil((double) total * 0.2)), cluster.closeConnections());
assertEquals((int) (Math.ceil((double) total * 0.2)), cluster.closeConnections());
assertEquals((int) (Math.ceil((double) total * 0.2)), cluster.closeConnections());
assertEquals((int) (Math.ceil((double) total * 0.2)), cluster.closeConnections());
assertEquals(total - 4 * (int) (Math.ceil((double) total * 0.2)), cluster.closeConnections());
ConnectionManager.getInstance().clear();
}
@Test
public void getCachedConnectionSizeTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Properties properties = initPriority(hostSpecs);
properties.setProperty("autoBalance", "leastconn");
properties.setProperty("enableQuickAutoBalance", "true");
Arrays.sort(hostSpecs);
String URLIdentifier = QueryCNListUtils.keyFromURL(properties);
Cluster cluster = new Cluster(URLIdentifier, properties);
String url = initURL(hostSpecs);
int total = 50;
for (int i = 0; i < total; i++) {
try {
PgConnection pgConnection = getConnection(url, properties);
cluster.setConnection(pgConnection, properties);
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
assertEquals(total, cluster.getCachedConnectionSize());
}
}

View File

@ -0,0 +1,290 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.postgresql.test.quickautobalance;
import org.junit.Test;
import org.postgresql.jdbc.PgConnection;
import org.postgresql.jdbc.StatementCancelState;
import org.postgresql.quickautobalance.ConnectionInfo;
import org.postgresql.test.TestUtil;
import org.postgresql.util.HostSpec;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* ConnectionInfo Test
*/
public class ConnectionInfoTest {
private HostSpec initHost() {
return new HostSpec(TestUtil.getServer(), TestUtil.getPort());
}
private Properties initProperties() {
Properties properties = new Properties();
properties.setProperty("PGDBNAME", TestUtil.getDatabase());
properties.setProperty("PGHOSTURL", TestUtil.getServer());
properties.setProperty("PGPORT", String.valueOf(TestUtil.getPort()));
properties.setProperty("PGPORTURL", String.valueOf(TestUtil.getPort()));
properties.setProperty("PGHOST", TestUtil.getServer());
properties.setProperty("user", TestUtil.getUser());
properties.setProperty("password", TestUtil.getPassword());
return properties;
}
@Test
public void createConnectionInfoWithDefaultParamsTest() throws SQLException {
// ConnectionInfo without quickAutoBalance
HostSpec hostSpec = initHost();
Properties properties = initProperties();
PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties)
.unwrap(PgConnection.class);
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
assertEquals(connectionInfo.getPgConnection(), pgConnection);
assertEquals(connectionInfo.getHostSpec(), hostSpec);
assertEquals(connectionInfo.getConnectionState(), StatementCancelState.IDLE);
assertEquals(connectionInfo.getMaxIdleTimeBeforeTerminal(), 30);
assertEquals(connectionInfo.getAutoBalance(), "");
assertFalse(connectionInfo.isEnableQuickAutoBalance());
pgConnection.close();
}
@Test(expected = SQLException.class)
public void createConnectionInfoEnableQuickAutoBalanceParsedFailedTest() throws SQLException {
Properties properties = initProperties();
properties.setProperty("autoBalance", "luanma");
properties.setProperty("enableQuickAutoBalance", "luanma");
HostSpec hostSpec = initHost();
try (PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties)
.unwrap(PgConnection.class)) {
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
} catch (PSQLException e) {
e.printStackTrace();
assertEquals(PSQLState.INVALID_PARAMETER_VALUE.getState(), e.getSQLState());
throw e;
}
}
@Test
public void createConnectionInfoEnableQuickAutoBalanceFalseTest() throws SQLException {
Properties properties = initProperties();
properties.setProperty("autoBalance", "luanma");
properties.setProperty("enableQuickAutoBalance", "false");
HostSpec hostSpec = initHost();
PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties)
.unwrap(PgConnection.class);
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
assertEquals("luanma", connectionInfo.getAutoBalance());
assertFalse(connectionInfo.isEnableQuickAutoBalance());
}
@Test
public void createConnectionInfoSuccessTest() throws SQLException {
String requestMaxIdleTimeBeforeTerminal = "9223372036854774";
long exceptMaxIdleTimeBeforeTerminal = 9223372036854774L;
Properties properties = initProperties();
HostSpec hostSpec = initHost();
properties.setProperty("maxIdleTimeBeforeTerminal", requestMaxIdleTimeBeforeTerminal);
properties.setProperty("autoBalance", "leastconn");
properties.setProperty("enableQuickAutoBalance", "true");
PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties)
.unwrap(PgConnection.class);
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
assertEquals(connectionInfo.getMaxIdleTimeBeforeTerminal(), exceptMaxIdleTimeBeforeTerminal);
assertTrue(connectionInfo.isEnableQuickAutoBalance());
assertEquals("leastconn", connectionInfo.getAutoBalance());
pgConnection.close();
}
@Test
public void createConnectionInfoEqualTo0Test() throws SQLException {
Properties properties = initProperties();
HostSpec hostSpec = initHost();
properties.setProperty("maxIdleTimeBeforeTerminal", "0");
properties.setProperty("autoBalance", "leastconn");
properties.setProperty("enableQuickAutoBalance", "true");
PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties)
.unwrap(PgConnection.class);
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
assertEquals(connectionInfo.getMaxIdleTimeBeforeTerminal(), 0);
assertTrue(connectionInfo.isEnableQuickAutoBalance());
assertEquals("leastconn", connectionInfo.getAutoBalance());
pgConnection.close();
}
@Test(expected = PSQLException.class)
public void createConnectionInfoMaxIdleTimeBeforeTerminalParsedFailedTest() throws SQLException {
Properties properties = initProperties();
properties.setProperty("maxIdleTimeBeforeTerminal", "111111@@");
HostSpec hostSpec = initHost();
try {
Connection connection = DriverManager.getConnection(TestUtil.getURL(), properties);
PgConnection pgConnection = connection.unwrap(PgConnection.class);
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
} catch (SQLException e) {
e.printStackTrace();
assertEquals(PSQLState.INVALID_PARAMETER_TYPE.getState(), e.getSQLState());
throw e;
}
}
@Test(expected = SQLException.class)
public void createConnectionInfoMaxIdleTimeBeforeTerminalTooBigTest() throws SQLException {
String maxIdleTimeBeforeTerminalMaxThreshold = "9223372036854775";
Properties properties = initProperties();
HostSpec hostSpec = initHost();
properties.setProperty("maxIdleTimeBeforeTerminal", maxIdleTimeBeforeTerminalMaxThreshold);
properties.setProperty("enableQuickAutoBalance", "false");
try (PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties)
.unwrap(PgConnection.class)) {
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
} catch (SQLException e) {
e.printStackTrace();
assertEquals(PSQLState.INVALID_PARAMETER_VALUE.getState(), e.getSQLState());
throw e;
}
}
@Test(expected = SQLException.class)
public void createConnectionInfoMaxIdleTimeBeforeTerminalTooSmallTest() throws SQLException {
Properties properties = initProperties();
HostSpec hostSpec = initHost();
properties.setProperty("maxIdleTimeBeforeTerminal", String.valueOf(-1));
properties.setProperty("enableQuickAutoBalance", "false");
try (PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties)
.unwrap(PgConnection.class)) {
new ConnectionInfo(pgConnection, properties, hostSpec);
} catch (SQLException e) {
e.printStackTrace();
assertEquals(PSQLState.INVALID_PARAMETER_VALUE.getState(), e.getSQLState());
throw e;
}
}
@Test
public void checkConnectionCanBeClosedTestPgConnectionNullTest() throws InterruptedException, PSQLException {
HostSpec hostSpec = initHost();
Properties properties = initProperties();
PgConnection pgConnection;
properties.setProperty("enableQuickAutoBalance", "true");
properties.setProperty("maxIdleTimeBeforeTerminal", "1");
Thread.sleep(1100);
ConnectionInfo connectionInfo = new ConnectionInfo(null, properties, hostSpec);
assertFalse(connectionInfo.checkConnectionCanBeClosed(System.currentTimeMillis()));
}
@Test
public void checkConnectionCanBeClosedTestPgConnectionUnableQuickAutoBalance() throws SQLException,
InterruptedException {
HostSpec hostSpec = initHost();
Properties properties = initProperties();
properties.setProperty("enableQuickAutoBalance", "false");
properties.setProperty("maxIdleTimeBeforeTerminal", "1");
Thread.sleep(1100);
PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties)
.unwrap(PgConnection.class);
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
assertFalse(connectionInfo.checkConnectionCanBeClosed(System.currentTimeMillis()));
pgConnection.close();
}
@Test
public void checkConnectionCanBeClosedTestStartTimeSmallerThanCreateTimeStamp() throws SQLException,
InterruptedException {
HostSpec hostSpec = initHost();
Properties properties = initProperties();
properties.setProperty("enableQuickAutoBalance", "true");
properties.setProperty("maxIdleTimeBeforeTerminal", "1");
Thread.sleep(1100);
PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties).unwrap(PgConnection.class);
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
assertFalse(connectionInfo.checkConnectionCanBeClosed(System.currentTimeMillis() - 1000 * 10));
pgConnection.close();
}
@Test
public void checkConnectionCanBeClosedTestConnectionStateNoEqualsToIDLE() throws SQLException, InterruptedException {
HostSpec hostSpec = initHost();
Properties properties = initProperties();
properties.setProperty("enableQuickAutoBalance", "true");
properties.setProperty("maxIdleTimeBeforeTerminal", "1");
PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties)
.unwrap(PgConnection.class);
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
connectionInfo.setConnectionState(StatementCancelState.IN_QUERY);
Thread.sleep(1100);
assertFalse(connectionInfo.checkConnectionCanBeClosed(System.currentTimeMillis()));
pgConnection.close();
}
@Test
public void checkConnectionCanBeCloseTestIDLETimeToShort() throws SQLException, InterruptedException {
HostSpec hostSpec = initHost();
Properties properties = initProperties();
properties.setProperty("enableQuickAutoBalance", "true");
properties.setProperty("maxIdleTimeBeforeTerminal", "10");
PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties).unwrap(PgConnection.class);
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
Thread.sleep(1100);
assertFalse(connectionInfo.checkConnectionCanBeClosed(System.currentTimeMillis()));
pgConnection.close();
}
@Test
public void checkConnectionCanBeCloseTestSuccessTest() throws SQLException, InterruptedException {
HostSpec hostSpec = initHost();
Properties properties = initProperties();
properties.setProperty("enableQuickAutoBalance", "true");
properties.setProperty("maxIdleTimeBeforeTerminal", "1");
PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties).unwrap(PgConnection.class);
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
Thread.sleep(1100);
assertTrue(connectionInfo.checkConnectionCanBeClosed(System.currentTimeMillis()));
pgConnection.close();
}
@Test
public void checkConnectionIsValidTest() throws SQLException {
HostSpec hostSpec = initHost();
Properties properties = initProperties();
PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties)
.unwrap(PgConnection.class);
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
assertTrue(connectionInfo.checkConnectionIsValid());
}
@Test
public void checkConnectionIsValidFailedTest() throws SQLException {
HostSpec hostSpec = initHost();
Properties properties = initProperties();
PgConnection pgConnection = DriverManager.getConnection(TestUtil.getURL(), properties)
.unwrap(PgConnection.class);
ConnectionInfo connectionInfo = new ConnectionInfo(pgConnection, properties, hostSpec);
pgConnection.getQueryExecutor().setAvailability(false);
pgConnection.close();
long before = System.currentTimeMillis();
assertFalse(connectionInfo.checkConnectionIsValid());
long after = System.currentTimeMillis();
}
}

View File

@ -0,0 +1,403 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.postgresql.test.quickautobalance;
import org.junit.Test;
import org.postgresql.jdbc.PgConnection;
import org.postgresql.jdbc.StatementCancelState;
import org.postgresql.log.Log;
import org.postgresql.log.Logger;
import org.postgresql.quickautobalance.Cluster;
import org.postgresql.quickautobalance.ConnectionInfo;
import org.postgresql.quickautobalance.ConnectionManager;
import org.postgresql.test.TestUtil;
import org.postgresql.util.GT;
import org.postgresql.util.HostSpec;
import org.postgresql.util.PSQLException;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
*
*/
public class ConnectionManagerTest {
private static Log LOGGER = Logger.getLogger(ConnectionManagerTest.class.getName());
private static final String USER = TestUtil.getUser();
private static final String PASSWORD = TestUtil.getPassword();
private static final String MASTER_1 = TestUtil.getServer() + ":" + TestUtil.getPort();
private static final String SECONDARY_1 = TestUtil.getSecondaryServer() + ":" + TestUtil.getSecondaryPort();
private static final String SECONDARY_2 = TestUtil.getSecondaryServer2() + ":" + TestUtil.getSecondaryServerPort2();
private static final String FAKE_HOST = "127.127.217.217";
private static final int FAKE_PORT = 1;
private static final String DATABASE = TestUtil.getDatabase();
private static final int DN_NUM = 3;
private String initURLWithLeastConn(HostSpec[] hostSpecs) {
return "jdbc:postgresql://" + hostSpecs[0].toString() + "," + hostSpecs[1].toString()
+ "," + hostSpecs[2].toString() + "/" + DATABASE + "?autoBalance=leastconn";
}
private PgConnection getConnection(String url, String user, String password) throws SQLException {
return DriverManager.getConnection(url, user, password).unwrap(PgConnection.class);
}
private HostSpec[] initHostSpec() {
HostSpec[] hostSpecs = new HostSpec[DN_NUM];
hostSpecs[0] = new HostSpec(TestUtil.getServer(), TestUtil.getPort());
hostSpecs[1] = new HostSpec(TestUtil.getSecondaryServer(), TestUtil.getSecondaryPort());
hostSpecs[2] = new HostSpec(TestUtil.getSecondaryServer2(), TestUtil.getSecondaryServerPort2());
return hostSpecs;
}
private HostSpec[] initHostSpecWithOneDnFailed() {
HostSpec[] hostSpecs = new HostSpec[DN_NUM];
hostSpecs[0] = new HostSpec(TestUtil.getServer(), TestUtil.getPort());
hostSpecs[1] = new HostSpec(TestUtil.getSecondaryServer(), TestUtil.getSecondaryPort());
hostSpecs[2] = new HostSpec(FAKE_HOST, FAKE_PORT);
return hostSpecs;
}
private Properties initPriority(HostSpec[] hostSpecs) {
String host = hostSpecs[0].getHost() + "," + hostSpecs[1].getHost() + "," + hostSpecs[2].getHost();
String port = hostSpecs[0].getPort() + "," + hostSpecs[1].getPort() + "," + hostSpecs[2].getPort();
Properties properties = new Properties();
properties.setProperty("PGDBNAME", TestUtil.getDatabase());
properties.setProperty("PGHOSTURL", host);
properties.setProperty("PGPORT", port);
properties.setProperty("PGPORTURL", port);
properties.setProperty("PGHOST", host);
properties.setProperty("user", TestUtil.getUser());
properties.setProperty("password", TestUtil.getPassword());
return properties;
}
private Properties initPriorityWithLeastConn(HostSpec[] hostSpecs) {
Properties properties = initPriority(hostSpecs);
properties.setProperty("autoBalance", "leastconn");
return properties;
}
@Test
public void setConnectionTest() throws ClassNotFoundException, SQLException {
if (String.valueOf(TestUtil.getSecondaryPort()).equals(System.getProperty("def_pgport"))
|| String.valueOf(TestUtil.getSecondaryServerPort2()).equals(System.getProperty("def_pgport"))) {
return;
}
Class.forName("org.postgresql.Driver");
String url = initURLWithLeastConn(initHostSpec());
PgConnection connection1 = getConnection(url, USER, PASSWORD);
String urlIdentifier = ConnectionManager.getURLIdentifierFromUrl(url);
Cluster cluster = ConnectionManager.getInstance().getCluster(urlIdentifier);
assertNotEquals(cluster, null);
ConnectionInfo connectionInfo = cluster.getConnectionInfo(connection1);
assertNotEquals(connectionInfo, null);
assertEquals(connectionInfo.getPgConnection(), connection1);
PgConnection connection2 = getConnection(url, USER, PASSWORD);
urlIdentifier = ConnectionManager.getURLIdentifierFromUrl(url);
cluster = ConnectionManager.getInstance().getCluster(urlIdentifier);
assertNotEquals(cluster, null);
connectionInfo = cluster.getConnectionInfo(connection2);
assertNotEquals(connectionInfo, null);
assertEquals(connectionInfo.getPgConnection(), connection2);
ConnectionManager.getInstance().clear();
}
@Test
public void setConnectionStateTest() throws ClassNotFoundException, SQLException {
if (String.valueOf(TestUtil.getSecondaryPort()).equals(System.getProperty("def_pgport"))
|| String.valueOf(TestUtil.getSecondaryServerPort2()).equals(System.getProperty("def_pgport"))) {
return;
}
Class.forName("org.postgresql.Driver");
String url = initURLWithLeastConn(initHostSpec());
// set connection state which exists.
PgConnection connection = getConnection(url, USER, PASSWORD);
ConnectionManager.getInstance().setConnectionState(connection, StatementCancelState.IDLE);
String urlIdentifier = ConnectionManager.getURLIdentifierFromUrl(url);
ConnectionInfo connectionInfo = ConnectionManager.getInstance()
.getCluster(urlIdentifier).getConnectionInfo(connection);
assertEquals(connectionInfo.getConnectionState(), StatementCancelState.IDLE);
ConnectionManager.getInstance().setConnectionState(connection, StatementCancelState.IN_QUERY);
urlIdentifier = ConnectionManager.getURLIdentifierFromUrl(url);
connectionInfo = ConnectionManager.getInstance().getCluster(urlIdentifier).getConnectionInfo(connection);
assertEquals(connectionInfo.getConnectionState(), StatementCancelState.IN_QUERY);
ConnectionManager.getInstance().setConnectionState(connection, StatementCancelState.CANCELING);
urlIdentifier = ConnectionManager.getURLIdentifierFromUrl(url);
connectionInfo = ConnectionManager.getInstance().getCluster(urlIdentifier).getConnectionInfo(connection);
assertEquals(connectionInfo.getConnectionState(), StatementCancelState.CANCELING);
ConnectionManager.getInstance().setConnectionState(connection, StatementCancelState.CANCELLED);
urlIdentifier = ConnectionManager.getURLIdentifierFromUrl(url);
connectionInfo = ConnectionManager.getInstance().getCluster(urlIdentifier).getConnectionInfo(connection);
assertEquals(connectionInfo.getConnectionState(), StatementCancelState.CANCELLED);
ConnectionManager.getInstance().clear();
}
@Test
public void cachedCreatingConnectionSizeTest() throws ClassNotFoundException, SQLException {
if (String.valueOf(TestUtil.getSecondaryPort()).equals(System.getProperty("def_pgport"))
|| String.valueOf(TestUtil.getSecondaryServerPort2()).equals(System.getProperty("def_pgport"))) {
return;
}
Class.forName("org.postgresql.Driver");
HostSpec[] hostSpecs = initHostSpec();
Properties properties = initPriorityWithLeastConn(hostSpecs);
String url = initURLWithLeastConn(hostSpecs);
int num = 10;
for (int i = 0; i < num; i++) {
PgConnection connection = getConnection(url, USER, PASSWORD);
assertEquals(1, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[0],
properties));
assertEquals(0, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[0],
properties));
assertEquals(1, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[1],
properties));
assertEquals(0, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[1],
properties));
assertEquals(1, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[2],
properties));
assertEquals(0, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[2],
properties));
}
ConnectionManager.getInstance().clear();
}
@Test
public void cachedCreatingConnectionSizeOneFailTest() throws ClassNotFoundException, SQLException {
if (String.valueOf(TestUtil.getSecondaryPort()).equals(System.getProperty("def_pgport"))
|| String.valueOf(TestUtil.getSecondaryServerPort2()).equals(System.getProperty("def_pgport"))) {
return;
}
Class.forName("org.postgresql.Driver");
HostSpec[] hostSpecs = initHostSpecWithOneDnFailed();
Properties properties = initPriorityWithLeastConn(hostSpecs);
String url = initURLWithLeastConn(hostSpecs);
int num = 10;
for (int i = 0; i < num; i++) {
PgConnection connection = getConnection(url, USER, PASSWORD);
assertEquals(1, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[0],
properties));
assertEquals(0, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[0],
properties));
assertEquals(1, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[1],
properties));
assertEquals(0, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[1],
properties));
assertEquals(1, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[2],
properties));
assertEquals(0, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[2],
properties));
}
ConnectionManager.getInstance().clear();
}
@Test
public void leastConnTest() throws SQLException, ClassNotFoundException {
if (String.valueOf(TestUtil.getSecondaryPort()).equals(System.getProperty("def_pgport"))
|| String.valueOf(TestUtil.getSecondaryServerPort2()).equals(System.getProperty("def_pgport"))) {
return;
}
Class.forName("org.postgresql.Driver");
String url = initURLWithLeastConn(initHostSpec());
int num = 100;
HashMap<String, Integer> dns = new HashMap<>();
for (int i = 0; i < num; i++) {
PgConnection connection = getConnection(url, USER, PASSWORD);
String socketAddress = connection.getSocketAddress().split("/")[1];
dns.put(socketAddress, dns.getOrDefault(socketAddress, 0) + 1);
}
List<Integer> connectionCounts = new ArrayList<>();
for (Entry<String, Integer> entry : dns.entrySet()) {
connectionCounts.add(entry.getValue());
}
for (Integer connectionCount : connectionCounts) {
LOGGER.info(GT.tr("{0}", connectionCount));
}
for (int i = 0; i < connectionCounts.size() - 1; i++) {
assertTrue(Math.abs(connectionCounts.get(i) - connectionCounts.get(i + 1)) <= 1);
}
ConnectionManager.getInstance().clear();
}
@Test
public void leastConnIntoOneDnTest() throws SQLException, ClassNotFoundException {
if (String.valueOf(TestUtil.getSecondaryPort()).equals(System.getProperty("def_pgport"))
|| String.valueOf(TestUtil.getSecondaryServerPort2()).equals(System.getProperty("def_pgport"))) {
return;
}
Class.forName("org.postgresql.Driver");
String url1 = initURLWithLeastConn(initHostSpec());
url1 += "&targetServerType=slave";
int num1 = 100;
HashMap<String, Integer> dns = new HashMap<>();
for (int i = 0; i < num1; i++) {
PgConnection connection = getConnection(url1, USER, PASSWORD);
String socketAddress = connection.getSocketAddress().split("/")[1];
dns.put(socketAddress, dns.getOrDefault(socketAddress, 0) + 1);
}
LOGGER.info(GT.tr("create 100 connections to slave nodes."));
for (Entry<String, Integer> entry : dns.entrySet()) {
int num = entry.getValue();
LOGGER.info(GT.tr("{0} : {1}", entry.getKey(), entry.getValue()));
assertEquals(50, num);
}
int num2 = 20;
String url2 = initURLWithLeastConn(initHostSpec());
String lastAddress = "";
for (int i = 0; i < num2; i++) {
PgConnection connection = getConnection(url2, TestUtil.getUser(), TestUtil.getPassword());
String socketAddress = connection.getSocketAddress().split("/")[1];
dns.put(socketAddress, dns.getOrDefault(socketAddress, 0) + 1);
if (i > 1) {
assertEquals(lastAddress, socketAddress);
}
lastAddress = socketAddress;
}
LOGGER.info(GT.tr("Create 20 connections to all nodes."));
for (Entry<String, Integer> entry : dns.entrySet()) {
LOGGER.info(GT.tr("{0} : {1}", entry.getKey(), entry.getValue()));
}
ConnectionManager.getInstance().clear();
}
@Test
public void checkConnectionStateTest() {
// If this testcase fail, maybe heartBeatingThread remove invalid connections.
try {
if (String.valueOf(TestUtil.getSecondaryPort()).equals(System.getProperty("def_pgport"))
|| String.valueOf(TestUtil.getSecondaryServerPort2()).equals(System.getProperty("def_pgport"))) {
return;
}
Class.forName("org.postgresql.Driver");
String url = initURLWithLeastConn(initHostSpec());
List<PgConnection> pgConnections = new ArrayList<>();
int total = 100;
int remove = 10;
for (int i = 0; i < total; i++) {
PgConnection connection = getConnection(url, USER, PASSWORD);
pgConnections.add(connection);
}
for (int i = 0; i < remove; i++) {
pgConnections.get(i).close();
}
assertEquals(Integer.valueOf(remove), ConnectionManager.getInstance().checkConnectionsValidity().get(0));
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
fail();
}
ConnectionManager.getInstance().clear();
}
@Test
public void setClusterTest() throws PSQLException {
HostSpec[] hostSpecs = initHostSpec();
Properties properties = initPriority(hostSpecs);
assertFalse(ConnectionManager.getInstance().setCluster(properties));
assertFalse(ConnectionManager.getInstance().setCluster(null));
properties = new Properties();
properties.setProperty("PGDBNAME", TestUtil.getDatabase());
properties.setProperty("PGHOSTURL", TestUtil.getServer());
properties.setProperty("PGPORT", String.valueOf(TestUtil.getPort()));
properties.setProperty("PGPORTURL", String.valueOf(TestUtil.getPort()));
properties.setProperty("PGHOST", TestUtil.getServer());
properties.setProperty("user", TestUtil.getUser());
properties.setProperty("password", TestUtil.getPassword());
assertFalse(ConnectionManager.getInstance().setCluster(properties));
properties = initPriorityWithLeastConn(hostSpecs);
assertTrue(ConnectionManager.getInstance().setCluster(properties));
assertFalse(ConnectionManager.getInstance().setCluster(properties));
ConnectionManager.getInstance().clear();
}
@Test
public void incrementCachedCreatingConnectionSize() throws PSQLException {
HostSpec[] hostSpecs = initHostSpec();
Properties properties = initPriorityWithLeastConn(hostSpecs);
ConnectionManager.getInstance().setCluster(properties);
assertEquals(1, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[0], properties));
assertEquals(2, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[0], properties));
assertEquals(1, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[0], properties));
assertEquals(0, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[0], properties));
assertEquals(0, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[0], properties));
assertEquals(0, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[0], null));
assertEquals(1, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[0], properties));
assertEquals(2, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[0], properties));
properties.setProperty("autoBalance", "");
assertEquals(0, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[0], properties));
properties.setProperty("autoBalance", "leastconn");
assertEquals(0, ConnectionManager.getInstance()
.incrementCachedCreatingConnectionSize(new HostSpec("127.127.127.127", 93589), properties));
ConnectionManager.getInstance().clear();
}
@Test
public void decrementCachedCreatingConnectionSize() throws PSQLException {
HostSpec[] hostSpecs = initHostSpec();
Properties properties = initPriorityWithLeastConn(hostSpecs);
ConnectionManager.getInstance().setCluster(properties);
assertEquals(1, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[0], properties));
assertEquals(2, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[0], properties));
assertEquals(1, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[0], properties));
assertEquals(0, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[0], properties));
assertEquals(0, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[0], properties));
assertEquals(0, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[0], null));
assertEquals(1, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[0], properties));
assertEquals(2, ConnectionManager.getInstance().incrementCachedCreatingConnectionSize(hostSpecs[0], properties));
properties.setProperty("autoBalance", "");
assertEquals(0, ConnectionManager.getInstance().decrementCachedCreatingConnectionSize(hostSpecs[0], properties));
properties.setProperty("autoBalance", "leastconn");
assertEquals(0, ConnectionManager.getInstance()
.decrementCachedCreatingConnectionSize(new HostSpec("127.127.127.127", 93589), properties));
ConnectionManager.getInstance().clear();
}
@Test
public void getCachedConnectionSizeTest() throws SQLException {
String url = initURLWithLeastConn(initHostSpec());
int total = 10;
for (int i = 0; i < total; i++) {
PgConnection pgConnection = getConnection(url, TestUtil.getUser(), TestUtil.getPassword());
}
assertEquals(total, ConnectionManager.getInstance().getCachedConnectionSize());
}
}

View File

@ -0,0 +1,489 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.postgresql.test.quickautobalance;
import org.junit.Test;
import org.postgresql.jdbc.PgConnection;
import org.postgresql.jdbc.StatementCancelState;
import org.postgresql.quickautobalance.ConnectionInfo;
import org.postgresql.quickautobalance.DataNode;
import org.postgresql.quickautobalance.DataNode.CheckDnStateResult;
import org.postgresql.test.TestUtil;
import org.postgresql.util.HostSpec;
import org.postgresql.util.PSQLException;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
*
*/
public class DataNodeTest {
private static final int DN_NUM = 3;
private static final String FAKE_HOST = "127.127.217.217";
private static final String FAKE_PORT = "1";
private static final String FAKE_USER = "fakeuser";
private static final String FAKE_DATABASE = "fakedatabase";
private static final String FAKE_PASSWORD = "fakepassword";
private HostSpec initHost() {
return new HostSpec(TestUtil.getServer(), TestUtil.getPort());
}
private PgConnection getConnection(String url, Properties properties) throws SQLException {
Connection connection = DriverManager.getConnection(url, properties);
assertTrue(connection instanceof PgConnection);
return (PgConnection) connection;
}
private Properties initProperties() {
Properties properties = new Properties();
properties.setProperty("PGDBNAME", TestUtil.getDatabase());
properties.setProperty("PGHOSTURL", TestUtil.getServer());
properties.setProperty("PGPORT", String.valueOf(TestUtil.getPort()));
properties.setProperty("PGPORTURL", String.valueOf(TestUtil.getPort()));
properties.setProperty("PGHOST", TestUtil.getServer());
properties.setProperty("user", TestUtil.getUser());
properties.setProperty("password", TestUtil.getPassword());
properties.setProperty("autoBalance", "leastconn");
return properties;
}
private HostSpec[] initHostSpecs() {
HostSpec[] hostSpecs = new HostSpec[DN_NUM];
hostSpecs[0] = new HostSpec(TestUtil.getServer(), TestUtil.getPort());
hostSpecs[1] = new HostSpec(TestUtil.getSecondaryServer(), TestUtil.getSecondaryPort());
hostSpecs[2] = new HostSpec(TestUtil.getSecondaryServer2(), TestUtil.getSecondaryServerPort2());
return hostSpecs;
}
private boolean checkHostSpecs(HostSpec[] hostSpecs) {
if (hostSpecs.length != DN_NUM) {
return false;
}
for (int i = 0; i < DN_NUM; i++) {
if ("localhost".equals(hostSpecs[i].getHost())) {
return false;
}
}
return true;
}
@Test
public void setConnectionStateTest() throws SQLException {
HostSpec hostSpec = initHost();
DataNode dataNode = new DataNode(hostSpec);
Properties properties = initProperties();
PgConnection pgConnection = getConnection(TestUtil.getURL(), properties);
dataNode.setConnection(pgConnection, properties, hostSpec);
dataNode.setConnectionState(pgConnection, StatementCancelState.IDLE);
assertEquals(StatementCancelState.IDLE, dataNode.getConnectionInfo(pgConnection).getConnectionState());
dataNode.setConnectionState(pgConnection, StatementCancelState.IN_QUERY);
assertEquals(StatementCancelState.IN_QUERY, dataNode.getConnectionInfo(pgConnection).getConnectionState());
dataNode.setConnectionState(pgConnection, StatementCancelState.CANCELLED);
assertEquals(StatementCancelState.CANCELLED, dataNode.getConnectionInfo(pgConnection).getConnectionState());
dataNode.setConnectionState(pgConnection, StatementCancelState.CANCELING);
assertEquals(StatementCancelState.CANCELING, dataNode.getConnectionInfo(pgConnection).getConnectionState());
}
@Test
public void setConnectionTest() throws SQLException {
HostSpec hostSpec = initHost();
DataNode dataNode = new DataNode(hostSpec);
Properties properties = initProperties();
PgConnection pgConnection = getConnection(TestUtil.getURL(), properties);
dataNode.setConnection(pgConnection, properties, hostSpec);
ConnectionInfo connectionInfo = dataNode.getConnectionInfo(pgConnection);
assertNotNull(connectionInfo);
assertEquals(pgConnection, connectionInfo.getPgConnection());
dataNode.setConnection(null, properties, hostSpec);
dataNode.setConnection(pgConnection, null, hostSpec);
dataNode.setConnection(pgConnection, properties, null);
}
@Test
public void getConnectionTest() throws SQLException {
HostSpec hostSpec = initHost();
DataNode dataNode = new DataNode(hostSpec);
Properties properties = initProperties();
PgConnection pgConnection = getConnection(TestUtil.getURL(), properties);
dataNode.setConnection(pgConnection, properties, hostSpec);
ConnectionInfo connectionInfo = dataNode.getConnectionInfo(pgConnection);
assertNotNull(connectionInfo);
assertEquals(pgConnection, connectionInfo.getPgConnection());
connectionInfo = dataNode.getConnectionInfo(null);
assertNull(connectionInfo);
}
@Test
public void getCachedConnectionListSizeTest() throws SQLException {
int num = 10;
HostSpec hostSpec = initHost();
DataNode dataNode = new DataNode(hostSpec);
Properties properties = initProperties();
assertEquals(0, dataNode.getCachedConnectionListSize());
for (int i = 0; i < num; i++) {
PgConnection pgConnection = getConnection(TestUtil.getURL(), properties);
dataNode.setConnection(pgConnection, properties, hostSpec);
}
int result = dataNode.getCachedConnectionListSize();
assertEquals(num, result);
}
@Test
public void checkDnStateWithPropertiesSuccessTest() {
HostSpec hostSpec = new HostSpec(TestUtil.getServer(), TestUtil.getPort());
Properties properties = new Properties();
properties.setProperty("PGDBNAME", TestUtil.getDatabase());
properties.setProperty("PGHOSTURL", TestUtil.getServer());
properties.setProperty("PGPORT", String.valueOf(TestUtil.getPort()));
properties.setProperty("PGPORTURL", String.valueOf(TestUtil.getPort()));
properties.setProperty("PGHOST", TestUtil.getServer());
properties.setProperty("user", TestUtil.getUser());
properties.setProperty("password", TestUtil.getPassword());
DataNode dataNode = new DataNode(hostSpec);
try {
assertTrue(dataNode.checkDnState(properties));
} catch (InvocationTargetException | PSQLException e) {
e.printStackTrace();
fail();
}
}
@Test
public void checkDnStateWithPropertiesUsernameOrPasswordErrorTest() {
HostSpec hostSpec = new HostSpec(TestUtil.getServer(), TestUtil.getPort());
Properties properties = new Properties();
properties.setProperty("PGDBNAME", TestUtil.getDatabase());
properties.setProperty("PGHOSTURL", TestUtil.getServer());
properties.setProperty("PGPORT", String.valueOf(TestUtil.getPort()));
properties.setProperty("PGPORTURL", String.valueOf(TestUtil.getPort()));
properties.setProperty("PGHOST", TestUtil.getServer());
properties.setProperty("user", FAKE_USER);
properties.setProperty("password", FAKE_PASSWORD);
DataNode dataNode = new DataNode(hostSpec);
try {
dataNode.checkDnState(properties);
} catch (InvocationTargetException e) {
assertTrue(e.getCause() instanceof PSQLException);
if (e.getCause() instanceof PSQLException) {
PSQLException psqlException = (PSQLException) (e.getCause());
String sqlState = psqlException.getSQLState();
assertEquals("28P01", sqlState);
} else {
e.printStackTrace();
fail();
}
} catch (PSQLException e) {
e.printStackTrace();
fail();
}
}
@Test
public void checkDnStateWithPropertiesDatabaseErrorTest() {
// Invalid parameter "PGDBNAME" doesn't affect to tryConnect().
HostSpec hostSpec = new HostSpec(TestUtil.getServer(), TestUtil.getPort());
Properties properties = new Properties();
properties.setProperty("PGDBNAME", FAKE_DATABASE);
properties.setProperty("PGHOSTURL", TestUtil.getServer());
properties.setProperty("PGPORT", String.valueOf(TestUtil.getPort()));
properties.setProperty("PGPORTURL", String.valueOf(TestUtil.getPort()));
properties.setProperty("PGHOST", TestUtil.getServer());
properties.setProperty("user", TestUtil.getUser());
properties.setProperty("password", TestUtil.getPassword());
DataNode dataNode = new DataNode(hostSpec);
try {
assertTrue(dataNode.checkDnState(properties));
} catch (InvocationTargetException | PSQLException e) {
e.printStackTrace();
fail();
}
}
@Test
public void checkDnStateWithPropertiesConnectionFailedTest() {
HostSpec hostSpec = new HostSpec(FAKE_HOST, Integer.parseInt(FAKE_PORT));
Properties properties = new Properties();
properties.setProperty("PGDBNAME", TestUtil.getDatabase());
properties.setProperty("PGHOSTURL", FAKE_HOST);
properties.setProperty("PGPORT", FAKE_PORT);
properties.setProperty("PGPORTURL", FAKE_PORT);
properties.setProperty("PGHOST", FAKE_HOST);
properties.setProperty("user", TestUtil.getUser());
properties.setProperty("password", TestUtil.getPassword());
DataNode dataNode = new DataNode(hostSpec);
try {
dataNode.checkDnState(properties);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof PSQLException) {
PSQLException psqlException = (PSQLException) (e.getCause());
String SQLState = psqlException.getSQLState();
assertNotEquals("28P01", SQLState);
} else {
assertTrue(true);
}
} catch (PSQLException e) {
e.printStackTrace();
fail();
}
}
@Test
public void checkDnStateWithHostSpecSuccessTest() {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Arrays.sort(hostSpecs);
String pgHostUrl = hostSpecs[0].getHost() + "," + hostSpecs[1].getHost() + "," + hostSpecs[2].getHost();
String pgPortUrl = hostSpecs[0].getPort() + "," + hostSpecs[1].getPort() + "," + hostSpecs[2].getPort();
Properties clusterProperties = new Properties();
clusterProperties.setProperty("user", TestUtil.getUser());
clusterProperties.setProperty("password", TestUtil.getPassword());
clusterProperties.setProperty("PGDBNAME", TestUtil.getDatabase());
clusterProperties.setProperty("PGHOSTURL", pgHostUrl);
clusterProperties.setProperty("PGPORT", pgPortUrl);
clusterProperties.setProperty("PGPORTURL", pgPortUrl);
clusterProperties.setProperty("PGHOST", pgHostUrl);
DataNode dataNode = new DataNode(new HostSpec(TestUtil.getServer(), TestUtil.getPort()));
CheckDnStateResult result = dataNode.checkDnStateAndProperties(clusterProperties);
assertEquals(CheckDnStateResult.DN_VALID, result);
}
@Test
public void checkDnStateWithHostSpecUserOrPasswordExpiredTest() {
HostSpec[] hostSpecs = initHostSpecs();
if (!checkHostSpecs(hostSpecs)) {
return;
}
Arrays.sort(hostSpecs);
String pgHostUrl = hostSpecs[0].getHost() + "," + hostSpecs[1].getHost() + "," + hostSpecs[2].getHost();
String pgPortUrl = hostSpecs[0].getPort() + "," + hostSpecs[1].getPort() + "," + hostSpecs[2].getPort();
Properties invalidProperties = new Properties();
invalidProperties.setProperty("user", FAKE_USER);
invalidProperties.setProperty("password", FAKE_PASSWORD);
invalidProperties.setProperty("PGDBNAME", TestUtil.getDatabase());
invalidProperties.setProperty("PGHOSTURL", pgHostUrl);
invalidProperties.setProperty("PGPORT", pgPortUrl);
invalidProperties.setProperty("PGPORTURL", pgPortUrl);
invalidProperties.setProperty("PGHOST", pgHostUrl);
DataNode dataNode = new DataNode(new HostSpec(TestUtil.getServer(), TestUtil.getPort()));
CheckDnStateResult result = dataNode.checkDnStateAndProperties(invalidProperties);
assertEquals(CheckDnStateResult.PROPERTIES_INVALID, result);
}
@Test
public void checkDnStateWithHostSpecConnectFailedTest() {
HostSpec[] hostSpecs = initHostSpecs();
HostSpec hostSpec = new HostSpec(FAKE_HOST, Integer.parseInt(FAKE_PORT));
if (!checkHostSpecs(hostSpecs)) {
return;
}
Arrays.sort(hostSpecs);
Properties clusterProperties = new Properties();
clusterProperties.setProperty("user", TestUtil.getUser());
clusterProperties.setProperty("password", TestUtil.getPassword());
clusterProperties.setProperty("PGDBNAME", TestUtil.getDatabase());
clusterProperties.setProperty("PGHOSTURL", FAKE_HOST);
clusterProperties.setProperty("PGPORT", FAKE_PORT);
clusterProperties.setProperty("PGPORTURL", FAKE_PORT);
clusterProperties.setProperty("PGHOST", FAKE_HOST);
DataNode dataNode = new DataNode(hostSpec);
CheckDnStateResult result = dataNode.checkDnStateAndProperties(clusterProperties);
assertEquals(CheckDnStateResult.DN_INVALID, result);
}
@Test
public void checkConnectionValidityTest() {
HostSpec hostSpec = initHost();
DataNode dataNode = new DataNode(hostSpec);
int total = 10;
int remove = 2;
List<PgConnection> pgConnections = new ArrayList<>();
Properties properties = initProperties();
try {
for (int i = 0; i < total; i++) {
PgConnection pgConnection = getConnection(TestUtil.getURL(), properties);
dataNode.setConnection(pgConnection, properties, hostSpec);
pgConnections.add(pgConnection);
}
assertEquals(0, dataNode.checkConnectionsValidity());
assertEquals(10, dataNode.getCachedConnectionListSize());
for (int i = 0; i < remove; i++) {
pgConnections.get(i).close();
}
assertEquals(2, dataNode.checkConnectionsValidity());
assertEquals(10 - 2, dataNode.getCachedConnectionListSize());
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
@Test
public void filterIdleConnectionsTest() {
HostSpec hostSpec = initHost();
DataNode dataNode = new DataNode(hostSpec);
int idleSize = 10;
int querySize = 20;
Properties properties = initProperties();
properties.setProperty("enableQuickAutoBalance", "true");
properties.setProperty("maxIdleTimeBeforeTerminal", "1");
for (int i = 0; i < idleSize; i++) {
try {
PgConnection pgConnection = getConnection(TestUtil.getURL(), properties);
dataNode.setConnection(pgConnection, properties, hostSpec);
ConnectionInfo connectionInfo = dataNode.getConnectionInfo(pgConnection);
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
for (int i = 0; i < querySize; i++) {
try {
PgConnection pgConnection = getConnection(TestUtil.getURL(), properties);
dataNode.setConnection(pgConnection, properties, hostSpec);
ConnectionInfo connectionInfo = dataNode.getConnectionInfo(pgConnection);
connectionInfo.setConnectionState(StatementCancelState.IN_QUERY);
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
fail();
}
List<ConnectionInfo> connectionInfos = dataNode.filterIdleConnections(System.currentTimeMillis());
assertEquals(idleSize, connectionInfos.size());
for (ConnectionInfo connectionInfo : connectionInfos) {
assertEquals(StatementCancelState.IDLE, connectionInfo.getConnectionState());
}
}
@Test
public void getAndSetDNStateTest() {
HostSpec hostSpec = initHost();
DataNode dataNode = new DataNode(hostSpec);
assertTrue(dataNode.getDataNodeState());
dataNode.setDataNodeState(false);
assertFalse(dataNode.getDataNodeState());
}
@Test
public void clearCachedConnectionsTest() {
HostSpec hostSpec = initHost();
DataNode dataNode = new DataNode(hostSpec);
int idleSize = 10;
Properties properties = initProperties();
properties.setProperty("enableQuickAutoBalance", "true");
properties.setProperty("maxIdleTimeBeforeTerminal", "1");
for (int i = 0; i < idleSize; i++) {
try {
PgConnection pgConnection = getConnection(TestUtil.getURL(), properties);
dataNode.setConnection(pgConnection, properties, hostSpec);
ConnectionInfo connectionInfo = dataNode.getConnectionInfo(pgConnection);
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
assertEquals(idleSize, dataNode.getCachedConnectionListSize());
dataNode.clearCachedConnections();
assertEquals(0, dataNode.getCachedConnectionListSize());
}
@Test
public void closeConnectionsTest() {
HostSpec hostSpec = initHost();
DataNode dataNode = new DataNode(hostSpec);
int total = 10;
int closed = 5;
Properties properties = initProperties();
List<PgConnection> connectionList = new ArrayList<>();
for (int i = 0; i < total; i++) {
try {
PgConnection pgConnection = getConnection(TestUtil.getURL(), properties);
dataNode.setConnection(pgConnection, properties, hostSpec);
connectionList.add(pgConnection);
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
assertEquals(total, dataNode.getCachedConnectionListSize());
for (int i = 0; i < total; i++) {
try {
assertTrue(connectionList.get(i).isValid(4));
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
for (int i = 0; i < closed; i++) {
assertTrue(dataNode.closeConnection(connectionList.get(i)));
}
assertEquals(total - closed, dataNode.getCachedConnectionListSize());
for (int i = 0; i < closed; i++) {
try {
assertFalse(connectionList.get(i).isValid(4));
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
}
@Test
public void getAndSetCachedCreatingConnectionSizeTest() {
HostSpec hostSpec = initHost();
DataNode dataNode = new DataNode(hostSpec);
assertEquals(0, dataNode.getCachedCreatingConnectionSize());
assertEquals(1, dataNode.incrementCachedCreatingConnectionSize());
assertEquals(1, dataNode.getCachedCreatingConnectionSize());
assertEquals(0, dataNode.decrementCachedCreatingConnectionSize());
assertEquals(0, dataNode.getCachedCreatingConnectionSize());
assertEquals(0, dataNode.decrementCachedCreatingConnectionSize());
}
}

View File

@ -0,0 +1,204 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.postgresql.test.quickautobalance;
import org.junit.Test;
import org.postgresql.jdbc.PgConnection;
import org.postgresql.quickautobalance.Cluster;
import org.postgresql.quickautobalance.ConnectionManager;
import org.postgresql.quickautobalance.LoadBalanceHeartBeating;
import org.postgresql.quickautobalance.ReflectUtil;
import org.postgresql.test.TestUtil;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* LoadBalanceHeartBeatingTest
*/
public class LoadBalanceHeartBeatingTest {
private static final String USER = TestUtil.getUser();
private static final String PASSWORD = TestUtil.getPassword();
private static final String MASTER_1 = TestUtil.getServer() + ":" + TestUtil.getPort();
private static final String SECONDARY_1 = TestUtil.getSecondaryServer() + ":" + TestUtil.getSecondaryPort();
private static final String SECONDARY_2 = TestUtil.getSecondaryServer2() + ":" + TestUtil.getSecondaryServerPort2();
private static final String DATABASE = TestUtil.getDatabase();
private Properties initProperties() {
Properties properties = new Properties();
properties.setProperty("PGPORTURL", TestUtil.getPort() + ","
+ TestUtil.getSecondaryPort() + "," + TestUtil.getSecondaryServerPort2());
properties.setProperty("PGHOSTURL", TestUtil.getServer() + ","
+ TestUtil.getSecondaryServer() + "," + TestUtil.getSecondaryServer2());
return properties;
}
private String initURLWithLeastConn() {
return "jdbc:postgresql://" + MASTER_1 + "," + SECONDARY_1
+ "," + SECONDARY_2 + "/" + DATABASE + "?autoBalance=leastconn&loggerLevel=OFF";
}
@Test
public void startCheckConnectionScheduledExecutorServiceSuccessTest() {
Properties properties = initProperties();
properties.setProperty("autoBalance", "leastconn");
assertFalse(LoadBalanceHeartBeating.isLeastConnStarted());
assertFalse(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
LoadBalanceHeartBeating.startScheduledExecutorService(properties);
assertTrue(LoadBalanceHeartBeating.isLeastConnStarted());
assertFalse(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
LoadBalanceHeartBeating.startScheduledExecutorService(properties);
assertTrue(LoadBalanceHeartBeating.isLeastConnStarted());
assertFalse(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
assertFalse(LoadBalanceHeartBeating.isLoadBalanceHeartBeatingStarted());
LoadBalanceHeartBeating.stopHeartBeatingThread();
}
@Test
public void startCloseConnectionExecutorServiceSuccessTest() {
Properties properties = initProperties();
properties.setProperty("autoBalance", "leastconn");
properties.setProperty("enableQuickAutoBalance", "true");
assertFalse(LoadBalanceHeartBeating.isLeastConnStarted());
assertFalse(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
LoadBalanceHeartBeating.startScheduledExecutorService(properties);
assertTrue(LoadBalanceHeartBeating.isLeastConnStarted());
assertTrue(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
LoadBalanceHeartBeating.startScheduledExecutorService(properties);
assertTrue(LoadBalanceHeartBeating.isLeastConnStarted());
assertTrue(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
assertTrue(LoadBalanceHeartBeating.isLoadBalanceHeartBeatingStarted());
LoadBalanceHeartBeating.stopHeartBeatingThread();
}
@Test
public void startCloseConnectionExecutorServiceFailedTest() {
Properties properties = initProperties();
properties.setProperty("autoBalance", "leastconn");
LoadBalanceHeartBeating.startScheduledExecutorService(properties);
assertTrue(LoadBalanceHeartBeating.isLeastConnStarted());
assertFalse(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
properties.setProperty("enableQuickAutoBalance", "fsfsfs");
LoadBalanceHeartBeating.startScheduledExecutorService(properties);
assertTrue(LoadBalanceHeartBeating.isLeastConnStarted());
assertFalse(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
properties.setProperty("enableQuickAutoBalance", "false");
LoadBalanceHeartBeating.startScheduledExecutorService(properties);
assertTrue(LoadBalanceHeartBeating.isLeastConnStarted());
assertFalse(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
assertFalse(LoadBalanceHeartBeating.isLoadBalanceHeartBeatingStarted());
LoadBalanceHeartBeating.stopHeartBeatingThread();
}
@Test
public void startExecutorServiceWithSingleHostTest() {
Properties properties = new Properties();
properties.setProperty("PGPORTURL", String.valueOf(TestUtil.getPort()));
properties.setProperty("PGHOSTURL", TestUtil.getServer());
properties.setProperty("autoBalance", "leastconn");
properties.setProperty("enableQuickAutoBalance", "true");
assertFalse(LoadBalanceHeartBeating.isLeastConnStarted());
assertFalse(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
LoadBalanceHeartBeating.startScheduledExecutorService(properties);
assertFalse(LoadBalanceHeartBeating.isLeastConnStarted());
assertFalse(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
LoadBalanceHeartBeating.startScheduledExecutorService(properties);
assertFalse(LoadBalanceHeartBeating.isLeastConnStarted());
assertFalse(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
assertFalse(LoadBalanceHeartBeating.isLoadBalanceHeartBeatingStarted());
LoadBalanceHeartBeating.stopHeartBeatingThread();
}
@Test
public void setConnectionWithLeastConnTest() throws SQLException {
String url = initURLWithLeastConn();
DriverManager.getConnection(url, USER, PASSWORD).unwrap(PgConnection.class);
assertTrue(LoadBalanceHeartBeating.isLeastConnStarted());
assertFalse(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
LoadBalanceHeartBeating.stopHeartBeatingThread();
}
@Test
public void setConnectionWithQuickAutoBalanceTest() throws SQLException {
String url = initURLWithLeastConn() + "&enableQuickAutoBalance=true";
DriverManager.getConnection(url, USER, PASSWORD).unwrap(PgConnection.class);
assertTrue(LoadBalanceHeartBeating.isLeastConnStarted());
assertTrue(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
assertTrue(LoadBalanceHeartBeating.isLoadBalanceHeartBeatingStarted());
LoadBalanceHeartBeating.stopHeartBeatingThread();
}
@Test
public void stopHeartBeatingThreadTest() throws SQLException {
String url = initURLWithLeastConn() + "&enableQuickAutoBalance=true";
Map<String, Cluster> cachedClusters = ReflectUtil.getField(ConnectionManager.class, ConnectionManager.getInstance(),
Map.class,
"cachedClusters");
// Start heartBeating thread.
DriverManager.getConnection(url, USER, PASSWORD).unwrap(PgConnection.class);
assertTrue(LoadBalanceHeartBeating.isLeastConnStarted());
assertTrue(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
assertTrue(LoadBalanceHeartBeating.isLoadBalanceHeartBeatingStarted());
assertEquals(1, cachedClusters.size());
// Stop heartBeating thread.
LoadBalanceHeartBeating.stopHeartBeatingThread();
assertFalse(LoadBalanceHeartBeating.isLeastConnStarted());
assertFalse(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
assertFalse(LoadBalanceHeartBeating.isLoadBalanceHeartBeatingStarted());
assertEquals(0, cachedClusters.size());
// Restart heartBeating thread.
DriverManager.getConnection(url, USER, PASSWORD).unwrap(PgConnection.class);
assertTrue(LoadBalanceHeartBeating.isLeastConnStarted());
assertTrue(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
assertTrue(LoadBalanceHeartBeating.isLoadBalanceHeartBeatingStarted());
assertEquals(1, cachedClusters.size());
// Stop heartBeating thread.
LoadBalanceHeartBeating.stopHeartBeatingThread();
assertFalse(LoadBalanceHeartBeating.isLeastConnStarted());
assertFalse(LoadBalanceHeartBeating.isQuickAutoBalanceStarted());
assertFalse(LoadBalanceHeartBeating.isLoadBalanceHeartBeatingStarted());
assertEquals(0, cachedClusters.size());
}
@Test
public void checkHeartBeatingThreadShouldStopTest() throws SQLException, InterruptedException {
String url = initURLWithLeastConn() + "&enableQuickAutoBalance=true";
Map<String, Cluster> cachedClusters = ReflectUtil.getField(ConnectionManager.class, ConnectionManager.getInstance(),
Map.class,
"cachedClusters");
// Start heartBeating thread.
PgConnection connection = DriverManager.getConnection(url, USER, PASSWORD).unwrap(PgConnection.class);
assertTrue(LoadBalanceHeartBeating.isLoadBalanceHeartBeatingStarted());
connection.close();
Thread.sleep(2 * 1000);
assertTrue(LoadBalanceHeartBeating.isLoadBalanceHeartBeatingStarted());
assertEquals(1, cachedClusters.size());
Thread.sleep(20 * 1000);
assertFalse(LoadBalanceHeartBeating.isLoadBalanceHeartBeatingStarted());
assertEquals(0, cachedClusters.size());
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) Huawei Technologies Co.,Ltd. 2023. All rights reserved.
*/
package org.postgresql.util;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
/**
* Title: the BitTest class.
* <p>
* Description:
*
* @author justbk
* @version [Tools 0.0.1, 2023/11/3]
* @since 2023/11/3
*/
public class ExecuteUtil {
public static <T> List<T> execute(Connection conn, String sql, RsParser<T> parser) throws SQLException {
List<T> results = new LinkedList<>();
try (Statement st = conn.createStatement()) {
try (ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) {
T parseObj = parser.parse(rs);
if (parseObj != null) {
results.add(parseObj);
}
}
}
}
return results;
}
public static Object executeGetOne(Connection conn, String sql) throws SQLException {
try (Statement st = conn.createStatement()) {
try (ResultSet rs = st.executeQuery(sql)) {
if (rs.next()) {
return rs.getObject(1);
}
}
}
return null;
}
public static void execute(Connection conn, String sql) throws SQLException {
try (Statement st = conn.createStatement()) {
st.execute(sql);
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) Huawei Technologies Co.,Ltd. 2023. All rights reserved.
*/
package org.postgresql.util;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Locale;
/**
* Title: the RsParse class, a simple ORM implements of parser.
* <p> aaa
* Description:
*
* @author justbk
* @version [Tools 0.0.1, 2023/11/11]
* @since 2023/11/11
*/
public interface RsParser<T> {
default T parse(ResultSet rs) {
Class<T> type = getGenericType();
Field[] fields = type.getDeclaredFields();
T obj = null;
try {
obj = type.newInstance();
for (Field f : fields) {
f.setAccessible(true);
try {
f.set(obj, parseObj(f, rs));
} catch (SQLException sqlException) {
sqlException.printStackTrace();
}
}
} catch (Exception exp) {
exp.printStackTrace();
}
return obj;
}
default Class<T> getGenericType() {
Class<T> type = (Class<T>) ((ParameterizedType) getClass()
.getGenericInterfaces()[0]).getActualTypeArguments()[0];
return type;
}
default Object parseObj(Field field, ResultSet rs) throws SQLException {
Class<?> clz = field.getType();
String name = field.getName().toLowerCase(Locale.ENGLISH);
return RsTypeFactory.getValue(clz, rs, name);
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) Huawei Technologies Co.,Ltd. 2023. All rights reserved.
*/
package org.postgresql.util;
import org.postgresql.jdbc.PgArray;
import java.sql.Blob;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/**
* Title: the RsTypeConvert class, convert database type to java type.
* <p>
* Description:
*
* @author justbk
* @version [Tools 0.0.1, 2023/11/11]
* @since 2023/11/11
*/
public class RsTypeFactory {
@FunctionalInterface
public interface RsTypeConvert {
Object apply(ResultSet rs, String name) throws SQLException;
}
Map<Class, RsTypeConvert> converts = new HashMap<>();
private static RsTypeConvert defaultConverts = (rs, name) -> rs.getObject(name);
private static RsTypeConvert intConvert = (rs, name) -> rs.getInt(name);
private static RsTypeConvert longConvert = (rs, name) -> rs.getLong(name);
private static RsTypeConvert booleanConvert = (rs, name) -> rs.getBoolean(name);
private static RsTypeConvert blobConvert = (rs, name) -> rs.getBlob(name);
private static RsTypeConvert strConvert = (rs, name) -> rs.getString(name);
private static RsTypeConvert pgArrayConvert = (rs, name) -> rs.getArray(name);
private static RsTypeConvert doubleConvert = (rs, name) -> rs.getDouble(name);
private static RsTypeConvert floatConvert = (rs, name) -> rs.getFloat(name);
// must keep this instance under all convert!!!
private static RsTypeFactory instance = new RsTypeFactory();
private RsTypeFactory() {
converts.put(int.class, intConvert);
converts.put(Integer.class, intConvert);
converts.put(long.class, longConvert);
converts.put(Long.class, longConvert);
converts.put(float.class, floatConvert);
converts.put(Float.class, floatConvert);
converts.put(double.class, doubleConvert);
converts.put(Double.class, doubleConvert);
converts.put(boolean.class, booleanConvert);
converts.put(Boolean.class, booleanConvert);
converts.put(Blob.class, blobConvert);
converts.put(String.class, strConvert);
converts.put(PgArray.class, pgArrayConvert);
}
public static final RsTypeFactory getInstance() {
return instance;
}
public RsTypeConvert getConvert(Class clz) {
return converts.getOrDefault(clz, defaultConverts);
}
public static Object getValue(Class clz, ResultSet rs, String name) throws SQLException {
return getInstance().getConvert(clz).apply(rs, name);
}
}

View File

@ -0,0 +1,165 @@
/*
* Copyright (c) Huawei Technologies Co.,Ltd. 2023. All rights reserved.
*/
package org.postgresql.v511;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.postgresql.test.TestUtil;
import org.postgresql.util.ExecuteUtil;
import org.postgresql.util.RsParser;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
/**
* Title: the BatchTest class.
* <p>
* Description:
*
* @author justbk
* @version [Tools 0.0.1, 2023/11/11]
* @since 2023/11/11
*/
@RunWith(Parameterized.class)
public class BatchAutoGenerateKeysTest {
private static Connection connStatic;
private Connection conn;
@Parameter
public int count;
@Parameter(1)
public String sql;
@Parameter(2)
public boolean needGenerateKey;
@Parameter(3)
public int repeatBatch;
@Parameter(4)
public boolean batchMode;
// @Parameters
public static Iterable<Object[]> data1() {
Object[] one = {1, "insert into t1 (data) values (?) returning *", true, 1, true};
LinkedList<Object[]> results = new LinkedList<>();
results.add(one);
return results;
}
@Parameters
public static Iterable<Object[]> datas() {
List<Object[]> datas = new LinkedList<>();
String[] sqls = {"insert into t1 (data) values(?) returning *", "insert into t1 (data) values(?)"};
Integer[] counts = {1, 2, 127, 200};
Integer[] repeatBatchs = {1, 2, 5, 6};
for (boolean batchMode: new boolean[]{true, false}) {
for (Integer count: counts) {
for (String sql:sqls) {
for (Integer repeatBatch: repeatBatchs) {
datas.add(new Object[] {count, sql, true, repeatBatch, batchMode});
datas.add(new Object[] {count, sql, false, repeatBatch, batchMode});
}
}
}
}
return datas;
}
public static class BatchData {
public int id;
public String data;
}
@BeforeClass
public static void onlyOneSetUp() throws Exception {
Properties props = new Properties();
props.put("batchMode", "ON");
connStatic = createConnection(props);
ExecuteUtil.execute(connStatic, "drop table if exists t1;");
ExecuteUtil.execute(connStatic, "create table t1 (id serial primary key, data varchar2(100))");
}
@AfterClass
public static void onlyOneTeardown() throws SQLException {
ExecuteUtil.execute(connStatic, "drop table if exists t1;");
connStatic.close();
}
@Before
public void setUp() throws Exception {
Properties props = new Properties();
props.put("batchMode", batchMode ? "ON" :"OFF");
conn = createConnection(props);
ExecuteUtil.execute(conn, "truncate t1");
}
@After
public void tearDown() throws SQLException {
conn.close();
}
@Test
public void testBatchs() throws SQLException {
System.out.println(String.format("test count=%d, sql=%s, wantKeys:%s, repeat=%s batch=%s", count, sql, needGenerateKey, repeatBatch, batchMode));
if (needGenerateKey) {
testBatchInsertAndGenerateKey(sql, count, repeatBatch);
} else {
testBatchInsert(sql, count, repeatBatch);
}
Assert.assertEquals(count * repeatBatch, queryAll().size());
}
public void testBatchInsert(String sql, int number, int repeatBatch) throws SQLException {
try (PreparedStatement ps = conn.prepareStatement(sql)) {
for (int j = 0; j < repeatBatch; j ++) {
for (int i = 0; i < number; i++) {
ps.setString(1, "aaa");
ps.addBatch();
}
ps.executeBatch();
}
}
}
public void testBatchInsertAndGenerateKey(String sql, int number, int repeatBatch) throws SQLException {
try (PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
for (int j = 0; j < repeatBatch; j++) {
for (int i = 0; i < number; i++) {
ps.setString(1, "aaa");
ps.addBatch();
}
ps.executeBatch();
List<Integer> results = new LinkedList<>();
try (ResultSet rs = ps.getGeneratedKeys()) {
while (rs.next()) {
results.add(rs.getInt(1));
}
}
Assert.assertEquals(number, results.size());
}
}
}
private List<BatchData> queryAll() throws SQLException {
return ExecuteUtil.execute(conn, "select id, data from t1", new RsParser<BatchData>() {});
}
private static Connection createConnection(Properties props) throws Exception {
return TestUtil.openDB(props);
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) Huawei Technologies Co.,Ltd. 2023. All rights reserved.
*/
package org.postgresql.v511;
import org.junit.Assert;
import org.junit.Test;
import org.postgresql.test.TestUtil;
import org.postgresql.util.ExecuteUtil;
import org.postgresql.util.RsParser;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
/**
* Title: the BitTest class.
* <p>
* Description:
*
* @author justbk
* @version [Tools 0.0.1, 2023/11/3]
* @since 2023/11/3
*/
public class SelectFunctionTest {
public static class Memory {
long total;
long free;
int count;
}
@Test
public void testFunctionQuery() throws Exception {
String sql = "SELECT 'PL/pgSQL function context' context_name\n" +
",sum(totalsize) / 1024 / 1024 / 1024 AS \"total\"\n" +
",sum(freesize) / 1024 / 1024 / 1024 AS \"free\"\n" +
",count(*) as count\n" +
"FROM gs_thread_memory_context\n" +
"WHERE contextname LIKE 'PL/pgSQL%'";
try (Connection conn = createConnection()) {
List<Memory> results = ExecuteUtil.execute(conn, sql, new RsParser<Memory>() {});
Assert.assertNotNull(results);
}
}
@Test
public void testFunctionQuery1() throws Exception {
String sql = " SELECT 'PL/pgSQL function context' context_name\n" +
",sum(totalsize) / 1024 / 1024 / 1024 AS \"total\"\n" +
",sum(freesize) / 1024 / 1024 / 1024 AS \"free\"\n" +
",count(*) as count\n" +
"FROM gs_thread_memory_context\n" +
"WHERE contextname LIKE 'PL/pgSQL%'";
try (Connection conn = createConnection()) {
List<Memory> results = ExecuteUtil.execute(conn, sql, new RsParser<Memory>() {});
Assert.assertNotNull(results);
}
}
@Test
public void testTriggerQuery() throws Exception {
String sqlTable = "create table t_tinyint0006 (" + "id int primary key auto_increment,"
+ "my_data tinyint" + ");";
String sqlTrigger = "create trigger trigger_tinyint0006 before insert on t_tinyint0006" + " for each row "
+ "begin" + " update t_tinyint0006 set my_data=1;" + "end;";
try (Connection conn = createConnection()) {
ExecuteUtil.execute(conn, sqlTable);
ExecuteUtil.execute(conn, sqlTrigger);
}
}
@Test
public void testReturningQuery() throws Exception {
String returnString = "INSERT INTO CIMMIT (DATA_ENABLE) VALUES (1)";
try (Connection conn = createConnection()) {
PreparedStatement st = conn.prepareStatement(returnString, new String[] {"ID"});
st.execute();
}
}
@Test
public void testBatchInsert() throws Exception {
Properties props = new Properties();
props.put("preparedStatementCacheQueries", "2");
props.put("prepareThreshold", "2");
props.put("fetchSize", "5");
props.put("batchMode", "OFF");
props.put("reWriteBatchedInserts", "true");
try (Connection conn = TestUtil.openDB(props)) {
for (int j = 1; j <= 1000; j++) {
ExecuteUtil.execute(conn, "set session_timeout = 0;");
ExecuteUtil.execute(conn, "drop table if exists t" + j);
ExecuteUtil.execute(conn, "create table t" + j
+ "(id int, id1 int, id2 int, id3 int, id4 int, id5 int, data varchar(2048));");
String batchInsert = "insert into t" + j + " values (?,?,?,?,?,?,?)";
PreparedStatement preparedStatement = conn.prepareStatement(batchInsert);
for (int i = 1; i <= 1000; i++) {
preparedStatement.setInt(1, 1);
preparedStatement.setInt(2, i);
preparedStatement.setInt(3, i);
preparedStatement.setInt(4, i);
preparedStatement.setInt(5, i);
preparedStatement.setInt(6, i);
preparedStatement.setString(7, "Huawei");
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
preparedStatement.close();
}
// block
System.in.read();
}
}
private static Connection createConnection() throws Exception {
return TestUtil.openDB();
}
}