Skip to content

Commit 1f5ce0b

Browse files
committed
Expand integration coverage and GaussDB reflection
1 parent 27f99e3 commit 1f5ce0b

8 files changed

Lines changed: 570 additions & 20 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["main"]
8+
9+
jobs:
10+
test:
11+
name: Python ${{ matrix.python-version }}
12+
runs-on: ubuntu-latest
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
python-version: ["3.9", "3.11", "3.13"]
17+
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: ${{ matrix.python-version }}
26+
27+
- name: Install package
28+
run: |
29+
python -m pip install --upgrade pip
30+
python -m pip install -e ".[test]"
31+
32+
- name: Run unit tests
33+
run: pytest -m "not integration"
34+
35+
- name: Build wheel
36+
run: |
37+
python -m pip install build
38+
python -m build --wheel

‎README.md‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ python -m pip install gaussdb_sqlalchemy_driver-0.1.0-py3-none-any.whl
4848

4949
如果导入时报错 `no pq wrapper available`,通常说明 Python 已经能找到 `gaussdb` 包,但还没有找到可用的 GaussDB 原生客户端库。
5050

51+
可以使用环境检查脚本确认 Python 包和真实连接是否可用:
52+
53+
```powershell
54+
python scripts\check_windows_env.py
55+
python scripts\check_windows_env.py --url "gaussdb+gaussdb://user:password@host:port/postgres"
56+
```
57+
5158
## SQLAlchemy 使用示例
5259

5360
```python
@@ -130,9 +137,11 @@ pytest
130137

131138
```bash
132139
export GAUSSDB_TEST_URL='gaussdb+gaussdb://user:password@host:port/postgres'
133-
pytest
140+
pytest -m integration
134141
```
135142

143+
集成测试覆盖 SQLAlchemy Core、事务回滚、批量插入、ORM CRUD、元数据反射和连接池基础复用。
144+
136145
## 打包
137146

138147
```bash
@@ -153,7 +162,6 @@ dist/gaussdb_sqlalchemy_driver-0.1.0.tar.gz
153162

154163
后续可以继续补充:
155164

156-
- 真实 GaussDB 环境集成测试
157165
- SQLAlchemy 方言兼容性测试套件
158166
- GaussDB 特有 SQL、数据类型和系统表反射适配
159167
- Windows 离线安装包和部署脚本

‎docs/常见问题.md‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# 常见问题
2+
3+
## 1. 这个项目是不是从零实现 GaussDB 协议?
4+
5+
不是。
6+
7+
本项目定位是 GaussDB SQLAlchemy 方言和适配层。底层连接能力复用华为 `gaussdb` Python DB-API 包,项目重点解决 SQLAlchemy 连接串、方言注册、版本识别、连接参数、事务、ORM 和反射等应用侧接入问题。
8+
9+
## 2. 为什么安装了 gaussdb 仍然报 no pq wrapper available?
10+
11+
这表示 Python 已经能找到 `gaussdb` 包,但没有找到可用的 GaussDB/libpq native client 实现。
12+
13+
Windows 上通常需要:
14+
15+
- 安装 GaussDB 客户端包
16+
- 将客户端 `bin` 目录加入 `PATH`
17+
- 重新打开终端或重启应用进程
18+
- 确认 Python 位数和客户端库位数一致
19+
20+
可以运行:
21+
22+
```powershell
23+
python scripts\check_windows_env.py
24+
```
25+
26+
## 3. 为什么方言默认设置 client_encoding=UTF8?
27+
28+
部分 GaussDB 环境默认 `client_encoding` 为 `SQL_ASCII`。在这种情况下,底层 `gaussdb` DB-API 会把文本字段返回为 `bytes`。
29+
30+
本项目默认传入:
31+
32+
```text
33+
client_encoding=UTF8
34+
```
35+
36+
这样 SQLAlchemy 用户默认拿到 Python `str`。如果确实需要其他编码,可以在连接串中覆盖:
37+
38+
```text
39+
gaussdb+gaussdb://user:password@host:port/postgres?client_encoding=LATIN1
40+
```
41+
42+
## 4. 如何跑真实数据库集成测试?
43+
44+
设置环境变量:
45+
46+
```bash
47+
export GAUSSDB_TEST_URL='gaussdb+gaussdb://user:password@host:port/postgres'
48+
pytest -m integration
49+
```
50+
51+
Windows PowerShell:
52+
53+
```powershell
54+
$env:GAUSSDB_TEST_URL = 'gaussdb+gaussdb://user:password@host:port/postgres'
55+
pytest -m integration
56+
```
57+
58+
集成测试会创建并删除 `codex_*_ut` 前缀的临时表。
59+
60+
## 5. 当前已验证哪些 SQLAlchemy 能力?
61+
62+
当前集成测试覆盖:
63+
64+
- SQLAlchemy Core 基础 DDL、DML、查询
65+
- 事务回滚
66+
- 批量插入
67+
- ORM CRUD
68+
- 元数据反射
69+
- 连接池基础复用
70+
71+
真实环境中,GaussDB 内核版本会保存在 `dialect.gaussdb_server_version_info`;`dialect.server_version_info` 使用保守的 PostgreSQL 兼容版本,避免 SQLAlchemy 误用 PostgreSQL 新版本系统表字段。
72+
73+
仍需继续验证:
74+
75+
- Windows 实机连接
76+
- GaussDB 505.1 专项环境
77+
- SSL 连接
78+
- Alembic 迁移
79+
- GaussDB 特有数据类型和系统表差异

‎docs/验证记录.md‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,18 +89,40 @@ SQLAlchemy 提示 driver 方言类未显式设置 `supports_statement_cache`。
8989
supports_statement_cache = True
9090
```
9191

92+
### 5. PostgreSQL 新版本系统表字段不兼容
93+
94+
现象:
95+
96+
SQLAlchemy PostgreSQL 方言会根据 `server_version_info` 选择反射查询。GaussDB 返回 `GaussDB Kernel 507.0.0`,如果直接把该版本号作为 PostgreSQL 版本号,SQLAlchemy 会访问当前 GaussDB 不存在或不兼容的系统表字段,例如:
97+
98+
- `pg_attribute.attgenerated`
99+
- `pg_attribute.attidentity`
100+
- 与 `pg_type.typcollation` 相关的相关子查询
101+
102+
修复:
103+
104+
- 将 `gaussdb_server_version_info` 保留为真实 GaussDB 内核版本
105+
- 将 `server_version_info` 映射为保守的 PostgreSQL 兼容版本 `(9, 2)`
106+
- 覆盖 `get_columns()` 和 `get_multi_columns()`,使用更保守的 GaussDB 兼容系统表查询支持基础列反射
107+
92108
## 验证结果
93109

94110
真实环境 SQLAlchemy 验证通过:
95111

96112
- 方言 `gaussdb.gaussdb` 可加载
97-
- `server_version_info` 可识别为 `(507, 0, 0)`
113+
- `gaussdb_server_version_info` 可识别为 `(507, 0, 0)`
114+
- `server_version_info` 使用保守的 PostgreSQL 兼容版本,避免 SQLAlchemy 访问当前 GaussDB 不存在的新版本系统表列
98115
- 默认 schema 可识别为字符串
99116
- `show client_encoding` 返回 `UTF8`
100117
- `select 1` 成功
101118
- 建表成功
102119
- 插入成功
103120
- 查询成功
121+
- 事务回滚成功
122+
- 批量插入成功
123+
- SQLAlchemy ORM CRUD 成功
124+
- SQLAlchemy 元数据反射成功
125+
- 连接池基础复用成功
104126
- 文本字段返回 `str`
105127
- 清理测试表成功
106128

‎scripts/check_windows_env.py‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Check whether the local Python environment can load the GaussDB driver."""
2+
3+
from __future__ import annotations
4+
5+
import argparse
6+
import importlib
7+
import os
8+
import sys
9+
from urllib.parse import urlsplit
10+
from urllib.parse import urlunsplit
11+
12+
13+
def _mask_url(url: str) -> str:
14+
parts = urlsplit(url)
15+
if not parts.password:
16+
return url
17+
username = parts.username or ""
18+
host = parts.hostname or ""
19+
port = f":{parts.port}" if parts.port else ""
20+
netloc = f"{username}:***@{host}{port}"
21+
return urlunsplit((parts.scheme, netloc, parts.path, parts.query, parts.fragment))
22+
23+
24+
def _check_import(module_name: str) -> object | None:
25+
try:
26+
module = importlib.import_module(module_name)
27+
except Exception as exc:
28+
print(f"[FAIL] import {module_name}: {type(exc).__name__}: {exc}")
29+
return None
30+
31+
version = getattr(module, "__version__", "unknown")
32+
print(f"[ OK ] import {module_name}: {version}")
33+
return module
34+
35+
36+
def main() -> int:
37+
parser = argparse.ArgumentParser(
38+
description="Check GaussDB Python and SQLAlchemy driver availability."
39+
)
40+
parser.add_argument(
41+
"--url",
42+
default=os.environ.get("GAUSSDB_TEST_URL"),
43+
help="Optional SQLAlchemy URL used for a live select 1 check.",
44+
)
45+
args = parser.parse_args()
46+
47+
print(f"Python: {sys.version.split()[0]}")
48+
print(f"Executable: {sys.executable}")
49+
print(f"PATH: {os.environ.get('PATH', '')}")
50+
51+
gaussdb = _check_import("gaussdb")
52+
sqlalchemy = _check_import("sqlalchemy")
53+
dialect = _check_import("gaussdb_sqlalchemy")
54+
55+
if not all((gaussdb, sqlalchemy, dialect)):
56+
print()
57+
print("请确认已安装 gaussdb、SQLAlchemy 和本项目 wheel。")
58+
print("Windows 上还需要将 GaussDB 客户端 bin 目录加入 PATH。")
59+
return 1
60+
61+
if not args.url:
62+
print("[SKIP] 未提供 --url 或 GAUSSDB_TEST_URL,跳过真实连接检查。")
63+
return 0
64+
65+
from sqlalchemy import create_engine
66+
from sqlalchemy import text
67+
68+
print(f"Connecting: {_mask_url(args.url)}")
69+
try:
70+
engine = create_engine(args.url, pool_pre_ping=True)
71+
with engine.connect() as conn:
72+
value = conn.execute(text("select 1")).scalar_one()
73+
encoding = conn.execute(text("show client_encoding")).scalar_one()
74+
except Exception as exc:
75+
print(f"[FAIL] live connection: {type(exc).__name__}: {exc}")
76+
return 2
77+
78+
print(f"[ OK ] live connection: select 1 -> {value}")
79+
print(f"[ OK ] client_encoding: {encoding}")
80+
return 0
81+
82+
83+
if __name__ == "__main__":
84+
raise SystemExit(main())

0 commit comments

Comments
 (0)