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
8 changes: 5 additions & 3 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ permissions:

jobs:
real-dm-arm:
name: Real DM8 / ARM Linux / Python ${{ matrix.python-version }}
name: Real DM8 / ARM Linux / Python ${{ matrix.python-version }} / UNICODE ${{ matrix.charset }}
runs-on: ubuntu-24.04-arm
timeout-minutes: 35
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
charset: [1, 0]
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -48,11 +49,12 @@ jobs:
echo 'DM_TEST_HOST=127.0.0.1'
echo 'DM_TEST_PORT=15236'
echo 'DM_BFILE_TEST_CONTAINER=dmpython-ci-dm8'
echo 'DM_EXPECT_UNICODE=${{ matrix.charset }}'
} >> "$GITHUB_ENV"
docker run -d --user root --name dmpython-ci-dm8 \
-e SYSDBA_PWD="$admin_password" \
-e SYSAUDITOR_PWD="$admin_password" \
-e CHARSET=1 -e DB_NAME=DMPYTEST -e INSTANCE_NAME=DMPYTEST \
-e CHARSET=${{ matrix.charset }} -e DB_NAME=DMPYTEST -e INSTANCE_NAME=DMPYTEST \
-p 127.0.0.1:15236:5236 \
yhl452493373/dm8@sha256:5b9d23c04b148d5765d6077d95b64032be64daedabeef9cddc7a4216b9ad0a1a
mkdir -p dpi_include
Expand Down Expand Up @@ -86,7 +88,7 @@ jobs:
if: always()
uses: actions/upload-artifact@v4
with:
name: real-dm-arm-py${{ matrix.python-version }}
name: real-dm-arm-py${{ matrix.python-version }}-unicode${{ matrix.charset }}
path: |
pytest-dm.xml
trend-dm.json
Expand Down
4 changes: 4 additions & 0 deletions docs/test-results/2026-09-26-type-connection-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@
## 后续回归:十进制与时区边界

`DECIMAL(30,8)` 的最大绝对值、正负最小非零值与零值单条写入通过;混合 `Decimal`、文本、科学计数法和 `NULL` 的 `executemany` 批量写入也保留精度。`TIME WITH TIME ZONE` 与 `TIMESTAMP WITH TIME ZONE` 在 `+14:00`、`-12:59` 偏移量和跨日附近的微秒值往返通过。本机官方 DM8 上 Python 3.9 至 3.13 各有 18 项针对性用例通过。

## 后续修复:GB18030 大字段边界

新增本机官方 ARM DM8 的 GB18030(`UNICODE=0`)隔离实例。修复前,包含中文和四字节 emoji 的 CLOB 跨越 16,000 字节写入边界时,文本读回虽完整,数据库的 `LENGTH(c)` 和 `DBMS_LOB.GETLENGTH(c)` 会少计 1 至 2 个字符。写入时现在让 GB18030 数据块结束在完整字符处。Python 3.10 在 UTF-8(`UNICODE=1`)和 GB18030 两套实例上的完整真实库回归各为 **166 passed、0 skipped**,另各有 2 个非真实库用例未选入。CI 增加 Python 3.9 至 3.13 × 两种编码的矩阵,并校验数据库实际 `UNICODE` 值;CI 结果待核实。
12 changes: 12 additions & 0 deletions dpi_bridge/third_party/chunanyong_dm/PATCHES.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@
- `test_clob_unicode_problem_patterns_length_contract`
- `test_clob_unicode_problem_patterns_subprocess_no_crash`

## Patch: GB18030 CLOB write boundaries

- Files: `a.go`, `clob_encoding.go`
- Problem: a 16,000-byte `PUT_DATA` chunk could split a GB18030 two- or
four-byte character. The CLOB content read back intact, but the database's
`LENGTH(c)` and `DBMS_LOB.GETLENGTH(c)` undercounted the characters.
- Fix: end each full GB18030 CLOB chunk at a complete character. Binary and
other parameter types retain their existing chunking.
- Regression: `test_clob_unicode_problem_patterns_length_contract` now runs
against both UTF-8 and GB18030 databases; `clob_encoding_test.go` covers
ASCII, two-byte, and four-byte boundaries.

## Patch: initial connection timeout through endpoint groups

- Files: `a.go`, `n.go`, `x.go`, `y.go`, `m.go`
Expand Down
24 changes: 16 additions & 8 deletions dpi_bridge/third_party/chunanyong_dm/a.go
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,10 @@ func (dm_build_666 *dm_build_414) dm_build_665(dm_build_667 *DmStatement, dm_bui
var dm_build_671 = Dm_build_4()
dm_build_670.read(dm_build_671)
var dm_build_672 = 0
var utf8ClobParam = dm_build_668.colType == CLOB && dm_build_666.dm_build_418.getServerEncoding() == "UTF-8"
clobEncoding := ""
if dm_build_668.colType == CLOB {
clobEncoding = dm_build_666.dm_build_418.getServerEncoding()
}
for !dm_build_670.isReadOver() || dm_build_671.Dm_build_5() > 0 {
if !dm_build_670.isReadOver() && dm_build_671.Dm_build_5() < Dm_build_820 {
dm_build_670.read(dm_build_671)
Expand All @@ -831,13 +834,18 @@ func (dm_build_666 *dm_build_414) dm_build_665(dm_build_667 *DmStatement, dm_bui
} else {
dm_build_672 = dm_build_671.Dm_build_5()
}
if utf8ClobParam && dm_build_672 == Dm_build_820 && dm_build_671.Dm_build_5() > dm_build_672 {
safeLen := dm_build_672
for safeLen > 0 && (dm_build_671.dm_build_32(safeLen)&0xC0) == 0x80 {
safeLen--
}
if safeLen > 0 {
dm_build_672 = safeLen
if dm_build_672 == Dm_build_820 && dm_build_671.Dm_build_5() > dm_build_672 {
switch clobEncoding {
case ENCODING_UTF8:
safeLen := dm_build_672
for safeLen > 0 && (dm_build_671.dm_build_32(safeLen)&0xC0) == 0x80 {
safeLen--
}
if safeLen > 0 {
dm_build_672 = safeLen
}
case ENCODING_GB18030:
dm_build_672 = gb18030SafePrefix(dm_build_671, dm_build_672)
}
}

Expand Down
27 changes: 27 additions & 0 deletions dpi_bridge/third_party/chunanyong_dm/clob_encoding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dm

// gb18030SafePrefix returns the longest whole-character prefix within limit.
// The CLOB PUT_DATA protocol must not split a two- or four-byte character.
func gb18030SafePrefix(buffer *Dm_build_0, limit int) int {
position := 0
for position < limit {
width := 1
first := buffer.dm_build_32(position)
if first >= 0x81 && first <= 0xFE {
if position+1 >= limit {
break
}
second := buffer.dm_build_32(position + 1)
if second >= 0x30 && second <= 0x39 {
width = 4
} else {
width = 2
}
}
if position+width > limit {
break
}
position += width
}
return position
}
16 changes: 16 additions & 0 deletions dpi_bridge/third_party/chunanyong_dm/clob_encoding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dm

import "testing"

func TestGB18030SafePrefix(t *testing.T) {
// ASCII, two-byte Chinese, four-byte supplementary character, ASCII.
encoded := []byte{'A', 0xD6, 0xD0, 0x95, 0x30, 0x8B, 0x34, 'B'}
buffer := Dm_build_4()
buffer.Dm_build_26(encoded, 0, len(encoded))
want := []int{0, 1, 1, 3, 3, 3, 3, 7, 8}
for limit, expected := range want {
if got := gb18030SafePrefix(buffer, limit); got != expected {
t.Errorf("limit %d: got %d, want %d", limit, got, expected)
}
}
}
8 changes: 8 additions & 0 deletions scripts/prepare_dm_ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@
assert cur.fetchone() == (1,)
cur.execute("SELECT ID_CODE FROM V$INSTANCE")
server_id_code = cur.fetchone()[0]
cur.execute("SELECT UNICODE")
server_unicode = int(cur.fetchone()[0])
expected_unicode = int(os.environ["DM_EXPECT_UNICODE"])
if server_unicode != expected_unicode:
raise RuntimeError(
f"DM8 UNICODE={server_unicode}, expected {expected_unicode}"
)
finally:
test_conn.close()
Path("dm-ci-environment.json").write_text(
Expand All @@ -55,6 +62,7 @@
"machine": platform.machine(),
"driver": dmPython.version,
"server_id_code": server_id_code,
"server_unicode": server_unicode,
},
indent=2,
) + "\n",
Expand Down
Loading