From ec952f14b8e3c515645e85e2ef842a1973644a95 Mon Sep 17 00:00:00 2001 From: he_sk Date: Sun, 27 Sep 2026 00:57:14 +0800 Subject: [PATCH] fix: apply connection compression option --- docs/api-reference.md | 2 + .../2026-09-26-type-connection-matrix.md | 6 ++- dpi_bridge/dpi_conn.go | 42 ++++++++++++++++++- .../third_party/chunanyong_dm/PATCHES.md | 9 ++++ .../chunanyong_dm/bridge_options.go | 9 ++++ .../integration/test_p1_connection_matrix.py | 31 +++++++++++++- 6 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 dpi_bridge/third_party/chunanyong_dm/bridge_options.go diff --git a/docs/api-reference.md b/docs/api-reference.md index 0477aeb..c8c68e8 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -137,6 +137,8 @@ dmPython.connect( - `packet_size` - `port` +`compress_msg` 仅在建连时接受 `DSQL_FALSE` 或 `DSQL_TRUE`,属性读回为协商后的布尔压缩状态。语句句柄缓存尚未实现:`use_stmt_pool=0` 可用,`use_stmt_pool=1` 会报错。 + 只读: - `server_code` diff --git a/docs/test-results/2026-09-26-type-connection-matrix.md b/docs/test-results/2026-09-26-type-connection-matrix.md index 43ff2ff..7bf4262 100644 --- a/docs/test-results/2026-09-26-type-connection-matrix.md +++ b/docs/test-results/2026-09-26-type-connection-matrix.md @@ -14,7 +14,7 @@ | 空值 | 整数、十进制、文本、二进制、日期、时间戳列的 `NULL` 往返 | | 连接地址 | `server`、`host`、`dsn=host:port` 均可连接;`host` 与 `server` 同时传入会报错 | | 连接行为 | 默认元组与 `DictCursor` 行形状、`schema`、`autoCommit` 开关及开启后无需显式提交的持久化、`txn_isolation` 读回 | -| 选项基础检查 | 非数字 `port` 报错;`connection_timeout`、`login_timeout`、`compress_msg`、`use_stmt_pool` 可建连并执行查询 | +| 选项基础检查 | 非数字 `port` 报错;`connection_timeout`、`login_timeout` 可建连并执行查询;消息压缩和语句池行为见下文 | `DECIMAL`、`FLOAT`、`DOUBLE`、`TIME`、`TIMESTAMP` 在当前默认读取路径通常返回字符串。矩阵断言数值或时间内容,同时保留未来改善 Python 返回类型的空间。 @@ -65,3 +65,7 @@ ## 后续修复:区间子类型 桥接层原先只识别 `DAY TO SECOND` 和 `YEAR TO MONTH` 两种区间列;真实 DM8 中其余 11 种区间限定形式均被读成普通字符串。现在按 DPI 类型码识别全部 13 种形式。`DAY`、`HOUR`、`MINUTE`、`SECOND` 及其组合读为 `datetime.timedelta`,`YEAR`、`MONTH` 和 `YEAR TO MONTH` 保留 `dmPython.YEAR_MONTH_INTERVAL` 描述类型;部分子类型的正负 `timedelta` 写入也验证通过。Python 3.10 在 UTF-8 和 GB18030 官方 ARM DM8 实例上的完整真实库回归各为 **183 passed、0 skipped**,另各有 2 个非真实库用例未选入。 + +## 后续修复:连接消息压缩 + +`compress_msg` 原先被静默忽略;现在只接受 `DSQL_FALSE`/`DSQL_TRUE`,将选择传给 Go 驱动并从协商后的连接状态读回。两种取值均已在真实 DM8 上建连和查询成功。底层 Go 驱动只解析 `StmtPoolSize`,没有使用对应缓存大小;因此显式请求 `use_stmt_pool=1` 现在会报不支持,`use_stmt_pool=0` 可用。语句句柄缓存仍待实现。 diff --git a/dpi_bridge/dpi_conn.go b/dpi_bridge/dpi_conn.go index ba383c1..fc298dc 100644 --- a/dpi_bridge/dpi_conn.go +++ b/dpi_bridge/dpi_conn.go @@ -60,6 +60,7 @@ type connHandle struct { loginTimeout int connTimeout int appName string + compressMsg int svcPath string mppLogin int rwSeparate int @@ -82,6 +83,7 @@ func newConnHandle(env *envHandle) *connHandle { port: DSQL_DEAFAULT_TCPIP_PORT, autocommit: false, loginTimeout: 5000, + compressMsg: -1, mppLogin: -1, rwSeparate: -1, rwPercent: -1, @@ -227,6 +229,21 @@ func dpi_set_con_attr(hcon C.dhcon, attrID C.sdint4, val C.dpointer, valLen C.sd return DSQL_ERROR } conn.rwPercent = intVal + case DSQL_ATTR_COMPRESS_MSG: + if conn.conn != nil { + conn.lastErr = &diagInfo{errorCode: -1, message: "compress_msg can only be set before login"} + return DSQL_ERROR + } + if intVal != 0 && intVal != 1 { + conn.lastErr = &diagInfo{errorCode: -1, message: "compress_msg must be DSQL_FALSE (0) or DSQL_TRUE (1)"} + return DSQL_ERROR + } + conn.compressMsg = intVal + case DSQL_ATTR_USE_STMT_POOL: + if intVal != 0 { + conn.lastErr = &diagInfo{errorCode: -1, message: "use_stmt_pool is not supported by this bridge"} + return DSQL_ERROR + } case DSQL_ATTR_SSL_PATH, DSQL_ATTR_SSL_PWD, DSQL_ATTR_UKEY_NAME, DSQL_ATTR_UKEY_PIN: if val != nil && C.GoString((*C.char)(val)) != "" { name := map[int32]string{ @@ -236,8 +253,7 @@ func dpi_set_con_attr(hcon C.dhcon, attrID C.sdint4, val C.dpointer, valLen C.sd conn.lastErr = &diagInfo{errorCode: -1, message: name + " is not supported by this bridge"} return DSQL_ERROR } - case DSQL_ATTR_COMPRESS_MSG, DSQL_ATTR_USE_STMT_POOL, - DSQL_ATTR_CURSOR_ROLLBACK_BEHAVIOR, + case DSQL_ATTR_CURSOR_ROLLBACK_BEHAVIOR, DSQL_ATTR_OSAUTH_TYPE, DSQL_ATTR_DDL_AUTOCOMMIT, DSQL_ATTR_COMPATIBLE_MODE, DSQL_ATTR_SHAKE_CRYPTO, DSQL_ATTR_NLS_NUMERIC_CHARACTERS, @@ -304,6 +320,20 @@ func dpi_get_con_attr(hcon C.dhcon, attrID C.sdint4, val C.dpointer, bufLen C.sd if valLen != nil { *valLen = 4 } + case DSQL_ATTR_COMPRESS_MSG: + value := conn.compressMsg + if value < 0 { + value = 0 + } + *(*C.sdint4)(val) = C.sdint4(value) + if valLen != nil { + *valLen = 4 + } + case DSQL_ATTR_USE_STMT_POOL: + *(*C.sdint4)(val) = 0 + if valLen != nil { + *valLen = 4 + } case DSQL_ATTR_AUTOCOMMIT: v := C.udint4(0) if conn.autocommit { @@ -448,6 +478,9 @@ func dpi_login(hcon C.dhcon, svr *C.sdbyte, user *C.sdbyte, pwd *C.sdbyte) C.DPI if conn.appName != "" { params = append(params, "appName="+url.QueryEscape(conn.appName)) } + if conn.compressMsg >= 0 { + params = append(params, "compress="+strconv.Itoa(conn.compressMsg)) + } if conn.svcPath != "" { params = append(params, "svcConfPath="+url.QueryEscape(filepath.Join(conn.svcPath, "dm_svc.conf"))) } @@ -512,6 +545,11 @@ func dpi_login(hcon C.dhcon, svr *C.sdbyte, user *C.sdbyte, pwd *C.sdbyte) C.DPI conn.db = db conn.conn = dmConn + if dmConn.CompressionMode() != 0 { + conn.compressMsg = 1 + } else { + conn.compressMsg = 0 + } // Try to get server version var version string diff --git a/dpi_bridge/third_party/chunanyong_dm/PATCHES.md b/dpi_bridge/third_party/chunanyong_dm/PATCHES.md index 5ef0ce9..4f75e42 100644 --- a/dpi_bridge/third_party/chunanyong_dm/PATCHES.md +++ b/dpi_bridge/third_party/chunanyong_dm/PATCHES.md @@ -43,6 +43,15 @@ against both UTF-8 and GB18030 databases; `clob_encoding_test.go` covers ASCII, two-byte, and four-byte boundaries. +## Patch: expose negotiated compression mode + +- File: `bridge_options.go` +- Expose the negotiated compression mode so the DPI bridge can report the + effective `compress_msg` setting after login rather than only echoing the + requested value. +- Regression: `test_compress_msg_is_applied` connects with compression both + disabled and enabled against a real DM8 instance. + ## Patch: initial connection timeout through endpoint groups - Files: `a.go`, `n.go`, `x.go`, `y.go`, `m.go` diff --git a/dpi_bridge/third_party/chunanyong_dm/bridge_options.go b/dpi_bridge/third_party/chunanyong_dm/bridge_options.go new file mode 100644 index 0000000..6d29463 --- /dev/null +++ b/dpi_bridge/third_party/chunanyong_dm/bridge_options.go @@ -0,0 +1,9 @@ +package dm + +// CompressionMode reports the negotiated wire-compression mode to the DPI bridge. +func (dc *DmConnection) CompressionMode() int { + if dc.dmConnector == nil { + return 0 + } + return dc.dmConnector.compress +} diff --git a/tests/integration/test_p1_connection_matrix.py b/tests/integration/test_p1_connection_matrix.py index 5ec788b..c6e9495 100644 --- a/tests/integration/test_p1_connection_matrix.py +++ b/tests/integration/test_p1_connection_matrix.py @@ -260,8 +260,7 @@ def test_port_rejects_non_numeric_value(conn_params): [ {"connection_timeout": 5}, {"login_timeout": 5000}, - {"compress_msg": 0}, - {"use_stmt_pool": 1}, + {"use_stmt_pool": 0}, ], ) def test_optional_setting_connects_and_queries(conn_params, option): @@ -271,6 +270,34 @@ def test_optional_setting_connects_and_queries(conn_params, option): assert cur.fetchone() == (1,) +@pytest.mark.parametrize("enabled", [dmPython.DSQL_FALSE, dmPython.DSQL_TRUE]) +def test_compress_msg_is_applied(conn_params, enabled): + with dmPython.connect(**conn_params, compress_msg=enabled) as conn: + assert conn.compress_msg == enabled + with conn.cursor() as cur: + cur.execute("SELECT 1") + assert cur.fetchone() == (1,) + + +@pytest.mark.parametrize("value", [-1, 2]) +def test_compress_msg_rejects_invalid_values(conn_params, value): + with pytest.raises((dmPython.Error, OverflowError)) as exc: + dmPython.connect(**conn_params, compress_msg=value) + if isinstance(exc.value, dmPython.Error): + assert "compress_msg" in str(exc.value) + + +def test_use_stmt_pool_rejects_unavailable_cache(conn_params): + with pytest.raises(dmPython.Error, match="use_stmt_pool.*not supported"): + dmPython.connect(**conn_params, use_stmt_pool=1) + + +def test_compress_msg_rejects_post_login_change(conn_params): + with dmPython.connect(**conn_params) as conn: + with pytest.raises(dmPython.Error, match="before login"): + conn.compress_msg = dmPython.DSQL_TRUE + + def test_connection_timeout_options_are_reported(conn_params): with dmPython.connect( **conn_params,