Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions docs/test-results/2026-09-26-type-connection-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@

## 已发现的问题

1. **高精度十进制参数丢失小数**:向 `DECIMAL(30,8)` 绑定 `Decimal('12345678901234567890.12345678')` 或相同文本后,数据库中实际值为 `12345678901234567890.00000000`;直接在 SQL 中 `CAST` 同一字面量能保留小数。这是写入路径的准确性问题,当前 104 项通过不能代表高精度 `DECIMAL` 安全。修复后应把该值加入 CI 的往返断言。
2. **连接选项的生效尚未验证**:`connection_timeout=5`、`login_timeout=5` 和 `app_name='dmpython_matrix'` 建连后,当前属性读回分别为 `0`、`0` 和空字符串。现有用例对前两项只验证“可建连”,不声称超时配置生效。需要单独设计可控的网络故障与服务器元数据检查。
1. **连接选项的生效尚未验证**:`connection_timeout=5`、`login_timeout=5` 和 `app_name='dmpython_matrix'` 建连后,当前属性读回分别为 `0`、`0` 和空字符串。现有用例对前两项只验证“可建连”,不声称超时配置生效。需要单独设计可控的网络故障与服务器元数据检查。

本次修复了 `datetime.time` 绑定在亚洲/上海本地时区下偏移五分钟的问题:桥接层曾以公元 0 年构造无时区的 `TIME`,触发历史时区偏移;改用现代锚定日期后,上述五版用例均验证 `23:59:58` 原样写入和读回。

## 后续修复:高精度十进制写入

`DECIMAL(30,8)` 参数原先经过默认精度的二进制浮点转换,使 `12345678901234567890.12345678` 的小数位归零。现在直接解析十进制字符,并覆盖 `Decimal`、文本、负数和科学计数法四种参数形式。本机官方 DM8 上 Python 3.10 的完整真实库回归为 **108 passed、0 failed、0 skipped**;尚需以 CI 结果验证托管镜像。

## 下一轮边界

需要继续覆盖高精度/负数/指数形式十进制,带时区时间、区间、复杂对象与数组、BFILE、不同编码,以及 SSL、UKey、MPP、读写分离和超时/故障转移的实际效果。当前仅有一版官方 DM8 服务端和一版 GitHub CI 开发镜像的历史基线;不能据此推断跨达梦服务端版本兼容。
需要继续覆盖其他十进制边界、带时区时间、区间、复杂对象与数组、BFILE、不同编码,以及 SSL、UKey、MPP、读写分离和超时/故障转移的实际效果。当前仅有一版官方 DM8 服务端和一版 GitHub CI 开发镜像的历史基线;不能据此推断跨达梦服务端版本兼容。
62 changes: 59 additions & 3 deletions dpi_bridge/third_party/chunanyong_dm/o.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,67 @@ func (d DmDecimal) ToBigFloat() *big.Float {
}

func NewDecimalFromString(s string) (*DmDecimal, error) {
num, ok := new(big.Float).SetString(strings.TrimSpace(s))
if !ok {
return newDecimalFromString(s, 0, 0)
}

func newDecimalFromString(s string, prec, scale int) (*DmDecimal, error) {
s = strings.TrimSpace(s)
sign := 1
if strings.HasPrefix(s, "-") {
sign = -1
s = s[1:]
} else if strings.HasPrefix(s, "+") {
s = s[1:]
}

exponent := 0
if i := strings.IndexAny(s, "eE"); i >= 0 {
var err error
exponent, err = strconv.Atoi(s[i+1:])
if err != nil || exponent < -1000 || exponent > 1000 {
return nil, ECGO_DATA_CONVERTION_ERROR.throw()
}
s = s[:i]
}

point := strings.IndexByte(s, '.')
fractionalDigits := 0
if point >= 0 {
fractionalDigits = len(s) - point - 1
s = s[:point] + s[point+1:]
}
if len(s) == 0 {
return nil, ECGO_DATA_CONVERTION_ERROR.throw()
}
return NewDecimalFromBigFloat(num)
for i := range s {
if s[i] < '0' || s[i] > '9' {
return nil, ECGO_DATA_CONVERTION_ERROR.throw()
}
}

d := &DmDecimal{sign: sign, weight: exponent - fractionalDigits, prec: prec, scale: scale, Valid: true}
d.digits = strings.TrimLeft(s, "0")
if d.digits == "" {
d.sign = 0
return d, nil
}
trimmed := len(d.digits) - len(strings.TrimRight(d.digits, "0"))
d.digits = strings.TrimRight(d.digits, "0")
d.weight += trimmed
if len(d.digits) > XDEC_MAX_PREC {
return nil, ECGO_DATA_TOO_LONG.throw()
}
if isOdd(d.weight) {
d.digits += "0"
d.weight--
}
if isOdd(len(d.digits)) {
d.digits = "0" + d.digits
}
if len(d.digits) > XDEC_MAX_PREC {
return nil, ECGO_DATA_TOO_LONG.throw()
}
return d, nil
}

func (d DmDecimal) String() string {
Expand Down
8 changes: 7 additions & 1 deletion dpi_bridge/third_party/chunanyong_dm/zi.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,13 @@ func (G2DB g2db) fromString(val string, param parameter, conn *DmConnection) (in
}
z, _ := f.Int(nil)
return G2DB.fromBigInt(z, param, conn)
case REAL, DOUBLE, DECIMAL:
case DECIMAL:
d, err := newDecimalFromString(val, int(param.prec), int(param.scale))
if err != nil {
return nil, err
}
return d.encodeDecimal()
case REAL, DOUBLE:
f, ok := new(big.Float).SetString(val)
if ok {
return G2DB.fromBigFloat(f, param, conn)
Expand Down
27 changes: 27 additions & 0 deletions tests/integration/test_p1_type_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,30 @@ def test_typed_null_roundtrip(conn, table_name_factory, drop_table, sql_type):
drop_table(cur, table)
conn.commit()
cur.close()


@pytest.mark.parametrize(
("value", "expected"),
[
(Decimal("12345678901234567890.12345678"), Decimal("12345678901234567890.12345678")),
("12345678901234567890.12345678", Decimal("12345678901234567890.12345678")),
(Decimal("-12345678901234567890.12345678"), Decimal("-12345678901234567890.12345678")),
("1234567890123456789012345678E-8", Decimal("12345678901234567890.12345678")),
],
ids=["decimal", "text", "negative", "scientific"],
)
def test_high_precision_decimal_parameter_preserves_fraction(
conn, table_name_factory, drop_table, value, expected
):
table = table_name_factory("DMPY_DECIMAL")
cur = conn.cursor()
try:
cur.execute(f"CREATE TABLE {table} (v DECIMAL(30, 8))")
cur.execute(f"INSERT INTO {table} VALUES (?)", (value,))
conn.commit()
cur.execute(f"SELECT CAST(v AS VARCHAR(80)) FROM {table}")
assert Decimal(cur.fetchone()[0]) == expected
finally:
drop_table(cur, table)
conn.commit()
cur.close()
Loading