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
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 @@ -61,3 +61,7 @@
## 后续修复:本地时区时间戳绑定

`TIMESTAMP WITH LOCAL TIME ZONE` 列原先拒绝带时区的 Python `datetime`,返回 6015 日期时间格式错误。原因是 Python 默认格式把 `+05:30` 等偏移量紧贴时间,而该列要求偏移量前有空格。现在只在绑定 Python 带时区时间对象时规范化格式;正负偏移量与跨日输入均通过。Python 3.10 在 UTF-8 和 GB18030 官方 ARM DM8 实例上的完整真实库回归各为 **168 passed、0 skipped**,另各有 2 个非真实库用例未选入。

## 后续修复:区间子类型

桥接层原先只识别 `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 个非真实库用例未选入。
22 changes: 22 additions & 0 deletions dpi_bridge/dpi_stmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -710,8 +710,30 @@ func mapGoTypeToDPI(ct *sql.ColumnType) (sqlType int16, precision uint64, scale
return DSQL_TIMESTAMP, 26, 6, 26
case "TIMESTAMP WITH TIME ZONE", "DATETIME WITH TIME ZONE":
return DSQL_TIMESTAMP_TZ, 34, 6, 34
case "INTERVAL DAY":
return DSQL_INTERVAL_DAY, 0, 0, 64
case "INTERVAL HOUR":
return DSQL_INTERVAL_HOUR, 0, 0, 64
case "INTERVAL MINUTE":
return DSQL_INTERVAL_MINUTE, 0, 0, 64
case "INTERVAL SECOND":
return DSQL_INTERVAL_SECOND, 0, 6, 64
case "INTERVAL DAY TO HOUR":
return DSQL_INTERVAL_DAY_TO_HOUR, 0, 0, 64
case "INTERVAL DAY TO MINUTE":
return DSQL_INTERVAL_DAY_TO_MINUTE, 0, 0, 64
case "INTERVAL DAY TO SECOND":
return DSQL_INTERVAL_DAY_TO_SECOND, 0, 6, 64
case "INTERVAL HOUR TO MINUTE":
return DSQL_INTERVAL_HOUR_TO_MINUTE, 0, 0, 64
case "INTERVAL HOUR TO SECOND":
return DSQL_INTERVAL_HOUR_TO_SECOND, 0, 6, 64
case "INTERVAL MINUTE TO SECOND":
return DSQL_INTERVAL_MINUTE_TO_SECOND, 0, 6, 64
case "INTERVAL YEAR":
return DSQL_INTERVAL_YEAR, 0, 0, 64
case "INTERVAL MONTH":
return DSQL_INTERVAL_MONTH, 0, 0, 64
case "INTERVAL YEAR TO MONTH":
return DSQL_INTERVAL_YEAR_TO_MONTH, 0, 0, 64
case "BIT", "BOOL", "BOOLEAN":
Expand Down
15 changes: 15 additions & 0 deletions dpi_bridge/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,21 @@ const (
DSQL_INTERVAL_DAY_TO_SECOND = 109
)

// Remaining DPI interval subtype codes (DPI.h, base 100).
const (
DSQL_INTERVAL_YEAR = 100
DSQL_INTERVAL_MONTH = 101
DSQL_INTERVAL_DAY = 102
DSQL_INTERVAL_HOUR = 103
DSQL_INTERVAL_MINUTE = 104
DSQL_INTERVAL_SECOND = 105
DSQL_INTERVAL_DAY_TO_HOUR = 107
DSQL_INTERVAL_DAY_TO_MINUTE = 108
DSQL_INTERVAL_HOUR_TO_MINUTE = 110
DSQL_INTERVAL_HOUR_TO_SECOND = 111
DSQL_INTERVAL_MINUTE_TO_SECOND = 112
)

// Environment/Connection attributes (from DPIext.h)
const (
DSQL_ATTR_LOCAL_CODE = 12345
Expand Down
95 changes: 95 additions & 0 deletions tests/integration/test_p1_type_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,98 @@ def test_year_month_interval_text_roundtrip(conn, table_name_factory, drop_table
drop_table(cur, table)
conn.commit()
cur.close()


@pytest.mark.parametrize(
("qualifier", "literal", "expected"),
[
("DAY(9)", "12", dt.timedelta(days=12)),
("HOUR(9)", "27", dt.timedelta(hours=27)),
("MINUTE(9)", "1600", dt.timedelta(minutes=1600)),
("SECOND(9,6)", "123.123456", dt.timedelta(seconds=123, microseconds=123456)),
("DAY(9) TO HOUR", "12 03", dt.timedelta(days=12, hours=3)),
("DAY(9) TO MINUTE", "12 03:04", dt.timedelta(days=12, hours=3, minutes=4)),
("HOUR(9) TO MINUTE", "27:04", dt.timedelta(hours=27, minutes=4)),
(
"HOUR(9) TO SECOND(6)",
"27:04:05.123456",
dt.timedelta(hours=27, minutes=4, seconds=5, microseconds=123456),
),
(
"MINUTE(9) TO SECOND(6)",
"1600:05.123456",
dt.timedelta(minutes=1600, seconds=5, microseconds=123456),
),
],
)
def test_day_second_interval_qualifiers_return_timedelta(
conn, table_name_factory, drop_table, qualifier, literal, expected
):
table = table_name_factory("DMPY_INTERVAL_PART")
cur = conn.cursor()
try:
cur.execute(f"CREATE TABLE {table} (v INTERVAL {qualifier})")
cur.execute(f"INSERT INTO {table} VALUES (INTERVAL '{literal}' {qualifier})")
conn.commit()
cur.execute(f"SELECT v FROM {table}")
assert cur.description[0][1] is dmPython.INTERVAL
assert cur.fetchone() == (expected,)
finally:
drop_table(cur, table)
conn.commit()
cur.close()


@pytest.mark.parametrize(
("qualifier", "value"),
[
("DAY(9) TO HOUR", dt.timedelta(days=12, hours=3)),
("HOUR(9) TO MINUTE", -dt.timedelta(hours=27, minutes=4)),
(
"HOUR(9) TO SECOND(6)",
dt.timedelta(hours=27, minutes=4, seconds=5, microseconds=123456),
),
(
"MINUTE(9) TO SECOND(6)",
dt.timedelta(minutes=1600, seconds=5, microseconds=123456),
),
],
)
def test_day_second_interval_qualifiers_accept_timedelta(
conn, table_name_factory, drop_table, qualifier, value
):
table = table_name_factory("DMPY_INTERVAL_BIND_PART")
cur = conn.cursor()
try:
cur.execute(f"CREATE TABLE {table} (v INTERVAL {qualifier})")
cur.execute(f"INSERT INTO {table} VALUES (?)", (value,))
conn.commit()
cur.execute(f"SELECT v FROM {table}")
assert cur.description[0][1] is dmPython.INTERVAL
assert cur.fetchone() == (value,)
finally:
drop_table(cur, table)
conn.commit()
cur.close()


@pytest.mark.parametrize(
("qualifier", "literal"),
[("YEAR(9)", "123"), ("MONTH(9)", "15")],
)
def test_year_month_interval_qualifiers_keep_type(
conn, table_name_factory, drop_table, qualifier, literal
):
table = table_name_factory("DMPY_INTERVAL_YM_PART")
cur = conn.cursor()
try:
cur.execute(f"CREATE TABLE {table} (v INTERVAL {qualifier})")
cur.execute(f"INSERT INTO {table} VALUES (INTERVAL '{literal}' {qualifier})")
conn.commit()
cur.execute(f"SELECT v FROM {table}")
assert cur.description[0][1] is dmPython.YEAR_MONTH_INTERVAL
assert re.search(rf"'{literal.zfill(9)}'", cur.fetchone()[0])
finally:
drop_table(cur, table)
conn.commit()
cur.close()
Loading