Repository navigation
db: ODBC 进主干 —— 六条打包腿显式带全功能,缺驱动管理器时优雅降级(1.7.2-dev) - #36
Merged
Merged
Conversation
`src/db/uvcpp_db_odbc.cpp`(新增,1478 行)走 unixODBC / Windows odbc32 那套 `SQLDriverConnectW` + 预处理语句,落进既有的 `uvcpp_db_driver` 接口,于是 `uvcpp_db_client` / `uvcpp_db_async` / `uvcpp_db_pool` 那三层一个字没改。进它的 直接原因是 SQL Server:微软没给 SQL Server 一个可链接的 C 客户端库,官方 `msodbcsql18` **本身就是** ODBC 驱动,想接它只能走这条路。 三处与另外三个后端**不同**的地方: - 它对着一个**接口**而不是一种库:连什么、哪种方言、哪个厂商,全由连接串里的 `Driver=`(或 `DSN=`)决定,本驱动**只试那一个驱动名、不遍历**。所以 `UVCPP_DB_ODBC_ENABLE`("这份产物里有没有这个后端")与"要连的数据源装没装 驱动"是**两件事**,后者只有 `open()` 才知道 —— 那时报的是驱动管理器的原文 (驱动名写错时就是它那句"找不到数据源、也没有默认驱动"),不是我们编的话。 - **没有 `UVCPP_DB_RUNTIME_LOAD` 那条路**:驱动管理器本来就该在、而且很小; 真正按需加载的是它下面的**具体驱动**,那件事归驱动管理器自己管。 - 配置期只要 `sql.h` 与驱动管理器(Linux 的 `libodbc`、Windows 的系统 `odbc32`)。 找的是 `sql.h` 而不是 `sqltypes.h`:后者在很多包的残留里都有,前者才代表一套 完整的 ODBC 头。只装运行时 unixodbc 的机器上没有 `.so` 开发符号链接 ⇒ 落到 "找不到 sql.h" 那条 warning,而不是在库那一格报一句让人去猜原因的话。 一处头文件陷阱单独记:unixODBC 的 `sqltypes.h` 第 75 行是 `#define BOOL int`,它把 本文件里唯一一处 `uvcpp_db_type::BOOL` 展开成 `uvcpp_db_type::int`,报出来的错是 "expected unqualified-id before 'int'" —— 离真正的原因隔着一层。收尾是 ODBC 头 预处理完之后一句 `#undef BOOL`(本文件从头到尾不用 ODBC 的 `BOOL`)。微软那套 `sqltypes.h` 不发这个宏(Windows 上 `BOOL` 是 `<windows.h>` 里的 typedef,对 `enum class` 的限定名没影响),所以那行在 Windows 上是空操作。 实测:`cmake -S . -B build-odbc -DUVCPP_ENABLE_DB=ON` 输出 `-- db: ODBC 后端开(驱动管理器 …/libodbc.so)`,`cmake --build build-odbc` rc=0 (全树 [100%]),生成的 `uvcpp_config.h` 里 `UVCPP_DB_ODBC_ENABLE 1`。 门禁:`check_doc_versions.py` / `check_docs.py` / `check_doc_lines.py` 三条 rc=0。 行号锁:插进 `CMakeLists.txt` 的那几段把 65 处引用整体挪了号,按**内容**逐条重算 (不是盲跑 `--update`),刷锁后核过 —— 新旧锁的「哈希 + 尾部锚点」多重集**完全 一致**,65 个消失的键在锁里都能找到同哈希的新键;只有 `CMakeLists.txt:604` 这条是 同一个键换了所指内容(它原本指 web 那个强制置 OFF 的 `if()`,整体 +9 行之后那段 挪到 613,而这个键按 `doc/http2-guide.md` 的意思该指 OpenSSL 那个 —— 两行内容都 逐字核过)。`check_doc_snippets.py` 前提没满足(要 `--pkg`,本机没有装好的包), 一条都没判。 本条只做后端本体,还欠两件:`doc/db-guide.md` 的 ODBC 一节与 `tests/functional/db_odbc_func.cpp`。分支基点是 tag `v1.7.0`;`origin/master` 已经在 `1.7.2-dev`(领先 4 个提交),合并前要不要 rebase 待定。
共用套件第一次跑在 ODBC 上,立刻抓出六处在只"编得过、链得上"时看不见的错 ——
每一条都是量出来的,不是推出来的:
- `SQLBindParameter` 只记指针(缓冲与 `StrLen_or_IndPtr` 都记),所以绑定的存储
必须活到 `SQLExecute` 之后,而且**每个参数一份**。共用一份时四个参数
`(1, 2, 't0', 'uu0')` 存进去读回来是 `(2, 2, 't', 'u')`:每个都只剩最后一次
写入的一个字节。改成 `std::vector<bound_param> bound_`,绑之前 `resize` 一次,
循环里只按下标取,不再 `push_back`(那会让已经交出去的指针悬空)。
- `SQL_C_WCHAR` 的长度指示器单位是**字节**:给字符数会把 `"张三"` 截成 `"张"`、
`'abcdef'` 截成 `'abc'`。改成 `w.size() * sizeof(SQLWCHAR)`。
- 空串与 NULL 是靠**指针**分的,不是靠长度:有效指针 + `ind=0` 是空串,
`nullptr` + `ind=0` 是 NULL。空串上 `std::string::data()` 给不给非空指针是实现
自由,所以备了两个哑元 `kEmptyByte` / `kEmptyWide`。这条错了的症状是写空串撞在
`NOT NULL DEFAULT ''` 上响亮地失败(比静默存成 NULL 好,但仍然是错)。
- 分块读时一个**满**块只给 `BufferUnits - 1` 个数据单元(驱动给 NUL 留位),按整块
计数会把 3000 字节读成 3001。宽字符那条路改成 `kChunk - 1`;二进制那条路保持
`kChunk`(`SQL_C_BINARY` 没有终止符,满块给满块)。
- `SQLGetInfo` 的 W 形态返回的长度也**是字节**:拿它当单元数会让引号字符 `"` 变成
2 个"字符",于是"内层引号双写"那一支被跳过,怪表名 `uvcpp_odd"name` 被拼成
`"<NUL>uvcpp_odd"name"<NUL>`。改成认尾部的 NUL,不看那个长度。
- 无参数那条快路径绕过了 `SQLNumParams`:语句里有 `?` 却一个参数都不给时报的是
`EXEC_FAILED`,而这是**绑定**阶段的错,该报 `BIND_FAILED`。改成
`!params.empty() || sql.find('?') != npos` 才走直连那条路。
另有两处是**驱动的自由**,不是我们的 bug —— 套件改成按声明的能力判,不硬凑断言:
- 新增 `dialect::prepares_eagerly()`(默认 `true`):sqliteodbc 的 `SQLPrepare` 只
登记不解析,语法错与表不存在要到 `SQLExecute` 才报,于是这类后端报的是
`EXEC_FAILED`。`test_errors()` 的两条判据按这个能力分开写,标签也改成只写"是
什么错"、不写"该报哪个码"。
- `accepts_dollar_placeholder()` 在 ODBC→SQLite 上是 `false`:sqliteodbc 只认 `?`
(实测 `$1` 报 `parameter marker count incorrect`)。原来那个 `true` 是照
"ODBC 应该有"猜的。
新用例 `tests/functional/db_odbc_func.cpp`:方言按 `Driver=` 认出来的驱动族分派
(`odbc_sqlite_dialect` / `odbc_mssql_dialect`),认不出的族退 **3**,
`UVCPP_DB_TEST_ODBC_URL` 不在也退 3。它还补了共用套件没走到的两条路:二进制跨块
(3 KB BLOB,逐字节相等)与 4 字节 UTF-8 字符(UTF-16 是代理对)正好骑在块边界上
—— 都过,且都钉成了断言。
读数(本机):ODBC 到 SQLite(sqliteodbc + unixODBC)**181 通过 / 0 失败 / 3 不适用**,
另有 **57 条 ODBC 专属**;SQLite 那一家按改过的共用套件重跑 **196 / 0 / 1**,证明
套件里那两处改动没有扰动别的后端。**ODBC 到 SQL Server 那一族没跑**,别当成对过。
文档:`doc/db-guide.md` §6 不再说"ODBC 不在共用套件里",补上它的读数与「ODBC 换了
一列」(差别在**驱动**里,不在后端里)、§10 补两条 ODBC 跑法、§11 补实测到哪一步;
`CHANGELOG.md` 逐条记上面这些实测。顺带一处旧读数:SQLite 那一行原写 157 / 2,在
干净 `v1.7.0` 树上重编重跑复现不出来(同样是 196 / 1),按现测改掉,并把口径
(这台机器、这份构建编了哪些后端)写进表下。
门禁:`check_doc_versions.py` / `check_docs.py` / `check_doc_lines.py` 三条 rc=0。
`check_doc_snippets.py` 用本机唯一一个装好的前缀(`/tmp/inst-web`,1.6.10)跑:
`doc/db-guide.md` **7/7 条绿**,全仓只有 `doc/grpc-guide.md:277` 一条红 —— 那条与
本次改动无关,在干净 `v1.7.0` 树上用同一个包**逐字复现**:是包比文档旧(1.6.10 的
`uvcpp_web_request` 与 1.7 的 `body_str()` 不是一回事),不是这一支引入的。
上一个提交里写着"ODBC **没有** `UVCPP_DB_RUNTIME_LOAD` 那条路:驱动管理器本来就该在、 而且很小"。那句话**只在 Windows 上成立**(`odbc32.dll` 属于操作系统,和 `ws2_32` 同类, 链接它没有代价);Linux / macOS 上驱动管理器是一个普通包(unixODBC),链上它就是在 产物里加一条 `DT_NEEDED libodbc.so.2` —— 与 libmysqlclient / libpq 完全同构:**没装过 的机器连加载这份 `.so` 都做不到**,哪怕一行 db 代码都不碰。所以 ODBC 与 MySQL/PG 走同 一条规矩,**默认档不动**(`OFF`)。 实测(两棵树只差这一个开关,其余开关逐条相同): - `/tmp/uvcpp-off`(`OFF`):`readelf -d` 的 `NEEDED` 里有 `libodbc.so.2`; `nm -u …/uvcpp_db_odbc.cpp.o` 里 `SQL*` 未定义符号 **24** 个。 - `/tmp/uvcpp-rt`(`-DUVCPP_DB_RUNTIME_LOAD=ON`):那条 `NEEDED` 不见了;同一份目标 文件里 `SQL*` 未定义符号 **0** 个。 - 两棵树各跑两套(都对着 sqliteodbc + unixODBC):ODBC **181 通过 / 0 失败 / 3 不适用** (另有 **57** 条 ODBC 专属),SQLite **196 / 0 / 1** —— 换个开关不动读数。 - 负路径:`UVCPP_DB_ODBC_LIBRARY=/nonexistent/libodbc.so.2` ⇒ `no_driver`,消息里逐个 列出试过的库名与各自的原因,并说明这是**管理器**那一层。 实现与 MySQL/PG 同形:新私有头 `src/db/uvcpp_db_odbc_api.h`(24 个入口,**从 `nm -u` 的目标文件里数出来的**,不是照 grep 抄的 —— `SQLGetInfo` 在文件里只出现在一句注释里, 照文本抄就是往表里塞一个没人提供的符号),64 处调用点改成 `api().SQLxxx(...)`,`open()` 第一件事是看 `uvcpp_db_odbc_load_error()`。候选名按平台惯例(Linux `libodbc.so.2` / `libodbc.so`、macOS `libodbc.2.dylib` / `libodbc.dylib`、Windows `odbc32.dll`); **iODBC 有意不在候选名里**:它是另一个驱动管理器实现,符号名一样但没在本仓量过,要用 就显式指路,别让"打开成功"盖住"换了一个没量过的实现"。`ODBC_STD` / `UNICODE` 那两个 条件重定向宏(`SQLAllocHandle` → `SQLAllocHandleStd`、`SQLSetStmtAttr` → `SQLSetStmtAttrW`)会让"按名字取符号"与"直接链"两种构建**分叉**,所以那份 api 头把它们 堵成 `#error` —— 与 pgsql 那份同一个形状。 `CMakeLists.txt`:ODBC 选项的注释与 `UVCPP_DB_RUNTIME_LOAD` 的描述按新事实改写("链上 的是驱动管理器"与"Windows 上 `odbc32.dll` 是系统件"这条例外写清); `UVCPP_DB_RUNTIME_LOAD=ON` 时 Windows 腿不再链 `odbc32` 导入库、非 Windows 腿**不找库**(于是"这台机器没装 unixODBC 运行库"不再等于"编不出这个后端"); `uvcpp_db_odbc_api.h` 进 `DB_HEADER_FILES` 的安装排除,`package_release.py` 的 `PRIVATE_HEADERS` 同步(三份 api 头)。`--no-undefined` 那条配套因此多一个由头:这一档 下 `nm -u` 里 0 个 `SQL*`,漏一处调用点当场红。 文档:`doc/db-guide.md` §1 的 ODBC 条目改成"它也走这条路"(含上面那对读数、候选名、 iODBC 之所以不在)、「运行时加载」那一节两张表与 `--no-undefined` 段落补 ODBC 一列、 §6 的读数表;两版 README 的 `UVCPP_ENABLE_DB_ODBC` / `UVCPP_DB_RUNTIME_LOAD` 两行;上面 那条 ODBC 条目里"没有这条路"一句一并改掉 —— 同一个 `1.7.1-dev` 里自相矛盾,留着比改掉 更糟。 门禁:`check_doc_versions.py` / `check_docs.py` / `check_doc_lines.py` 三条 rc=0。 `CMakeLists.txt` 这一改撑动了行号(净 +23 行,另有 +4 那一处),`doc_line_refs.lock` 按 `--update` 的复核报告刷新:**平移 63 条**(内容逐字节相同,改了 22 个文档里的 80 处行 号)、人工定的 **2 条**(`FetchContent_Declare` 那两行内容在文件里重了 4 处,按引用正文 认下 pugixml ↔ yyjson)、**1 条 `[胀]`**(`RELEASE.md` 引的 install 规则那一段,本次在 它内部插了一行 `list(FILTER DB_HEADER_FILES …)`,已核过引述仍然成立)。刷新前后 1051 条 键的哈希多重集逐条比对:只有那一段(已复核)的内容哈希变了,其余是纯平移。 `check_doc_snippets.py --pkg /tmp/inst-web`:`doc/db-guide.md` **7/7 绿**,全仓仍只有 `doc/grpc-guide.md:277` 那一条红,与本次无关(本机那个包是 1.6.10,比文档旧,上一提交 已在干净 `v1.7.0` 树上逐字复现过)。 没动、但要记一笔:`.github/workflows/release.yml` 的六条腿传的是 `UVCPP_ENABLE_DB=ON … -DUVCPP_DB_RUNTIME_LOAD=ON`,**没有** `-DUVCPP_ENABLE_DB_ODBC`,而 Linux 腿只装 `zlib1g-dev ninja-build` —— 没有 `sql.h`,ODBC 后端在那些包上会被 warning 强制关掉。 要让发布的 Linux 包带 ODBC,得补 `unixodbc-dev` 并显式打开这个开关;那是另一件事,本次 没做。
`uvcpp_db_odbc_api.h` 直接 `#include <sql.h>`,之前没有 `<windows.h>`。Linux 上
这件事看不见:unixODBC 的 `sqltypes.h` 在没有 `<windows.h>` 的平台上自己补一套
类型表,所以顺序漏了也编得过。微软那套不是 —— 它拿 `INT64`/`UINT64`(winnt.h)、
`HWND`(windef.h)、`GUID`(guiddef.h)拼出 ODBC 的类型表,全在 `<windows.h>` 里。
少了它,第一条错是
sqltypes.h(64,9): error C4430: missing type specifier - int assumed
sqltypes.h(64,25): error C2146: syntax error: missing ';' before identifier 'SQLLEN'
指着 `typedef INT64 SQLLEN;`,看上去像 Windows SDK 装坏了。照着这个方向去换 SDK
版本只会白烧时间:换到最新的(26100)与最老的(17763)报的是同一批。
顺带两条卫生:`WIN32_LEAN_AND_MEAN` 与 `NOMINMAX`。这是个公共头,包含它就等于把
`<windows.h>` 拖进别人的 TU,而跟着来的是 `min`/`max` 两个宏 —— 本仓
`uvcpp_web_json_reflect.h` 里已经为这个坑写过一段注释(`std::numeric_limits<T>::max`
被当成宏调用)。本文件自己没有 `std::min`/`std::max`,所以只是把门关上。
`#undef BOOL` 那条注释跟着改了:之前写"Windows 上 BOOL 是 `<windows.h>` 里的
typedef",那还只是**假设**它在那儿;现在它是真的在那儿了。
`feature/db-odbc` 从 v1.7.0 的发布态 `1ef8b6f` 分出去,master 之后走了 12 个提交
(QUIC / webapp / db 三块的性能与文档),两边把 `CHANGELOG.md`、两版 README、
`src/uvcpp/uvcpp_version.h` 这四份文件改到了同一行上,于是有四处真冲突;其余
31 个文件自动合。
**用 merge,不用 rebase。** 下游(wlzaweb)的发布流程钉的是这条分支上的
`57c93d8d2f8b9193273a2ece4eda2a6cf4896154` 并 `--depth 1` 单独取它;把分支改写成
新历史会让那个 SHA 从可达变成不可达,那边 CI 会当场红。merge 让它留在祖先里。
四处冲突的取法:
* `src/uvcpp/uvcpp_version.h` —— 取 master 的 `1.7.2-dev`。两条线都从 1.7.0 各自
升过 `1.7.1-dev`(master 是 `ebeaa20`,分支是 ODBC 这四个提交),1.7.1 这个号
从没发布过(tag 只有 `v1.7.0`),master 后来自己升到了 `1.7.2-dev`,以它为准。
版本号全仓唯一来源就是这个头,`CMakeLists.txt` 在 configure 期读的也是它。
* `CHANGELOG.md` —— 顶部「当前源码树」取 master 的 `1.7.2-dev`。这一节是**主题式**
清单(不是按版本分节),所以没有"1.7.1 段"需要并走;分支那三条 ODBC 条目括号里
的 `1.7.1-dev` **保持原样** —— 那是"这处改动首次出现时的开发档号",那几棵树当时
确实是 1.7.1-dev,而 master 自己的 `ebeaa20` 条目也是这个号,同一个号在本文件里
并不孤立。
* `README.md` / `README.zh.md` —— 顶上两处版本号(badge 与 **Version** 行)取
master。分支在选项表里加的那行 `UVCPP_ENABLE_DB_ODBC` 与 master 的改动不在同一
段,自动合进来了,**不能丢** —— `tests/tools/check_docs.py` 判据 1 要求
`CMakeLists.txt` 的 `option()` 与两版 README 的选项表双向相等,少一行就红。
合并后逐条核过:
* `git diff origin/master -- src/db/` 只有那 4 个文件(`uvcpp_db_driver.h`、
`uvcpp_db_factory.cpp` 与两个新文件)—— 也就是说 master 那 5 个 db 性能提交
(`uvcpp_db.cpp` 的懒日志、`uvcpp_db_table.h` 的 reserve、pgsql 的物化分配账、
mysql/sqlite 各几行)**逐字节保留**,两个方向在 `src/db/` 里没碰过同一个文件。
* 连接池 `uvcpp_db_pool.*` 与异步 `uvcpp_db_async.*` 相对 master **零差异**。它们
本来就不碰 `uvcpp_db_driver`(一律经 `uvcpp_db_client`),所以"多一个后端"在结构
上就动不到它们 —— 这是"新增 ODBC 不拖累 db 模块性能"的根据,不是承诺。
* `tests/tools/doc_line_refs.lock` 那个自动合的结果**不算数**:它是
`python tests/tools/check_doc_lines.py --update` 生成的,等下面几处改动落定后
重新生成并过门禁。
`release.yml` 此前**全文零次**提到 odbc:没传 `-DUVCPP_ENABLE_DB_ODBC`、六条腿没有
一条准备过 `sql.h`、那份读生成头的断言清单(17 枚宏)里也没有
`UVCPP_DB_ODBC_ENABLE`。而 `UVCPP_ENABLE_DB_ODBC` 默认就是 ON,找不到头时走的是
`message(WARNING)` + `set(... OFF)`(一句**普通变量**赋值)—— `CMakeCache.txt` 里照旧
写着 ON、生成头里却是 0、包里少一个后端,**一条断言都不会红**。也就是说今天六条腿
编出来的包,Linux 那四条不会有 ODBC,Windows 那两条要看 `sql.h` 碰不碰得到。
改动:
* 十处 configure 各补 `-DUVCPP_ENABLE_DB_ODBC=ON` 与
`-DUVCPP_DB_ODBC_INCLUDE_DIR=`(与现有那五行 db 开关同组)。
* 头按腿分两种来路。两条 MinGW 腿用 MSYS2 的
`mingw-w64-{x86_64,clang-aarch64}-unixodbc`(头在
`$MINGW_PREFIX/include/unixodbc/` 下、**不在** `include/` 下 —— 2026-10-16 在两个
arch 的包文件表上逐条核过;拿它是为了编,运行期加载的是 Windows 系统的
`odbc32.dll`,所以包里连 unixODBC 的 .dll 都不需要)。两条 MSVC 腿用新脚本
`.github/scripts/win-odbc-headers.sh` 从 Windows SDK 里挑 `<ver>/um`,**按版本号
取最大**,取不到就 exit 1 当场停。两条 Linux 腿照 mysql/pgsql 的老规矩
`apt-get download unixodbc-dev` + `dpkg -x`,**不 install**(装了会让
`find_library(odbc)` 有机会静默成功、给产物添一条 `DT_NEEDED`)。
* 六条腿各补一枚 `UVCPP_DB_ODBC_ENABLE` 断言(读的是**生成头**,不是 cache;17 → 18
枚)。对前三枚后端,这条断言拦的是"发布腿忘了传开关";对这一枚,它拦的是"这条腿
没弄到头"—— 后者更隐蔽,因为"开关默认 ON"这件事本身会让人以为它不用管。
* 两条 Linux 腿补两条负向判据:`ldd` 白名单加 `odbc` 分支、`nm -D -u` 计数加
`SQL[A-Z]`。两条 MinGW 腿的导入表白名单**一个字都没动** —— 里面没有
`odbc32.dll`,那正是"运行期加载失效就会红"的唯一守卫;不要为了让断言变绿把它加
进去。
三个**只有真编一次才会露出来**的坑(都写进注释了):
* SDK 那份**不能按字典序**挑:字典序会把 `10.0.17763.0` 排在最前,于是 `um` 来自
老 SDK 而 `<windows.h>` 来自新的,报出来是 `oaidl.h`/`propidlbase.h`/`sqltypes.h`
里成百条 `C4430`/`C2144`,看着像 SDK 坏了。
* Debian 的 `unixodbc-dev` 把 `unixodbc.h` 放在 `usr/include/<multiarch>/` 下,而
`sqltypes.h` 里有一句 `#include "unixodbc.h"`:真机上两层都在编译器的默认搜索路径
里,只有"把包摊进一个目录"的做法会掉这一层。发布腿用软链补上并加三条 `test -f`
—— 补之前本机真编红过一次(`fatal error: unixodbc.h: 没有那个文件或目录`)。
* `unixodbc.h` 里那句 `#include "unixODBC/unixodbc_conf.h"` 是按**打开它的路径**解析
的,所以 `unixODBC/` 目录要一起挂;少挂它,下一个错就是找不到 `unixodbc_conf.h`。
本机实测(Linux,`-DUVCPP_ENABLE_DB_ODBC=ON` + unixodbc-dev 那份头):生成头里
`UVCPP_DB_ODBC_ENABLE 1`;`readelf -d libuvcpp.so` 的 NEEDED 只有
libstdc++/libgcc_s/libc/ld-linux 四条(没有 libodbc);`nm -D -u | grep -cE
'mysql_|PQ|SQL[A-Z]'` = 0;`ctest` 54/54(3 条按设计 Skipped)。反向也编过一遍(同一
棵树不给 `sql.h`):只出一条 WARNING、生成头里那颗宏是 0、其余三个后端照编、
`ctest` 50/50 —— 这就是"没有 ODBC 动态库/头时其他功能正常"在构建期的样子。
ODBC 到 SQL Server 那一族**没有**写进测试集(CI 里没有真 SQL Server,写进去只会变成
一条永远跳过的用例);下游 wlzaweb 的 CI 已经在真 SQL Server 上跑通,见 doc/db-guide.md
里改准的那句。
三个后端都有 `UVCPP_DB_*_LIBRARY` 这个旋钮(把客户端库指到别的路径去加载),而
`tests/`、`bench/` 里对它**零命中**:此前"这台机器没装驱动管理器会怎样"只活在
`doc/db-guide.md` 里,是一段让人手敲的复现步骤。而这正是使用侧最要紧的一条 ——
"没有 ODBC 动态库时其他功能正常运行、用的时候报异常"。
新用例 `tests/functional/db_odbc_nodrv_func.cpp`(16 条断言):
* `open("odbc://u:p@127.0.0.1:1/uvcpp_nodrv")` 回 `uvcpp_db_status::NO_DRIVER`
—— 不是含糊的 `OPEN_FAILED`、更不是崩;`is_open()` 为假。
* `last_error()` 里有"加载 ODBC 驱动管理器失败"、`UVCPP_DB_ODBC_LIBRARY=`、那个坏
路径本身,以及一句"只用别的后端时不用管这一条" —— 报的是**试过哪几个库名**的人话,
不是空串。
* 同一进程里**再开一次** `odbc://`,同样 `NO_DRIVER`(第一次失败没把状态弄脏)。
* 同一进程里 sqlite 拿 `sqlite::memory:` 照常 CREATE / 参数化 INSERT / SELECT /
读回 —— 这才是"其他功能正常运行"。
前提不去卸载任何东西:把 `UVCPP_DB_ODBC_LIBRARY` 指到一个**不存在**的路径(这正是
`src/db/uvcpp_db_runtime_load.h` 里写明这个旋钮的用途),由 ctest 的 `ENVIRONMENT`
属性设好(Windows 用 `Z:/nonexistent/odbc32.dll`,其余平台
`/nonexistent/libodbc.so.2`)。前提是确定性的(那个变量是我们自己设的),所以**不设**
`SKIP_RETURN_CODE`:前提不成立(变量没传到、或那个路径居然存在)时返回 **1 —— 红**,
不是跳过。退回跳过只会让这条用例在 CI 上悄悄变成"永远绿"。
**它必须是独立的目标、独立的 ctest 项**:ODBC 的符号表走 magic static、句柄永不释放
(那是"句柄永不释放"的必然结果),同一个进程里成功加载过一次之后,再设环境变量就不
生效了 —— 并进 `db_odbc_func` 里跑会直接失效。
顺手修掉一处同门的老毛病:`test_db_odbc_func` 原来不在 `SKIP_RETURN_CODE 3` 那一串里
(那一串只有 mysql/pgsql),于是在装了 `unixodbc-dev`、却没配数据源的机器上(CI 就是)
它显示成 `Failed`,而真相是"未判定"。判据归位。
本机:`test_db_odbc_func` = Skipped、`test_db_odbc_nodrv_func` = Passed(手动跑两档也
核过 —— 有前提时 16 通过 / 0 失败,把变量去掉时当场红并打印该怎么跑);`ctest` 54/54。
`doc/db-guide.md`:
* 「多平台打包」那节重写。原来写的是"这六条腿的 `-D` 清单里还没有它 / Linux 腿会因
缺 `sql.h` 静默关掉",现在是一张三行表:`mingw-*` / `msvc-*` / `linux-*` 各自的
`sql.h` 从哪来、为什么是它。补上 Debian 那处两层目录的坑 —— 它**只**在"把包摊进一个
目录"的做法下出现,装了 `unixodbc-dev` 的人一辈子看不到,所以必须写下来。
* ODBC 小节加一段「为什么不学 SQLite 那份,把 ODBC 静态链进去」:要的不是某一个库,
是**驱动管理器**,而管理器存在的意义就是运行期按 `Driver=`/`DSN=` 去找具体驱动 ——
静态链它换不来"不用装东西";Windows 的 `odbc32.dll` 是系统件、不可再分发。不写这段,
下一个人还会再想一遍。
* `UVCPP_DB_ODBC_LIBRARY` 那段补上"驱动管理器不在"这一档的实际形状(`NO_DRIVER` +
一句列出试过哪些库名的话),并指向新用例。
* 「ODBC 到 SQL Server 本身没跑过」这句**原来是不准的**,改准:下游(wlzaweb)的 CI
已经用这个后端连上真 SQL Server(LocalDB + ODBC Driver 17)跑完两条业务口径并逐个
数对过;本仓自己的测试套件这一档仍然是 ODBC → SQLite。既不写成"测试集测过"(没有
那台机器,也不打算把一条永远跳过的用例放进测试集),也不写得比事实弱。
`CHANGELOG.md` 加两条:数据库节里加"驱动管理器不在"那个用例(连同
`SKIP_RETURN_CODE` 那处修正 —— 它是这条用例的同门);「配置、打包与 CI」节里加六条腿
显式开 ODBC、头的两种来路、读生成头的断言、两条 Linux 负向判据、以及 MinGW 那份导入表
白名单**没动**这件事(没动本身就是结论)。
`RELEASE.md` **不动**:它是逐版本归档,文件头写明旧小节不追改、后来的事实补进
`CHANGELOG.md`。1.7.2 的「重点」留到真正切这一版时再写 —— 这次只合并,不发版。
门禁(本机全绿):`check_docs`、`check_doc_lines`(`--update` 重刷锁文件后 5 条判据)、
`check_ci_layout`、`check_doc_versions`。`check_doc_snippets` 拿本机现打的
`libuvcpp-1.7.2-linux-x64` 包跑 `doc/db-guide.md`:7/7 条片段编过、退 0(整棵树那一次
退 3 是本地包缺 WEB/WSDL 那几个模块,与这次改动无关)。
PR 的 `config-contract` 腿抓到的,而且是这个 PR 里**唯一一条 master 上还绿着**的判据:
[红] src/db/uvcpp_db_odbc_api.h 含非 ASCII 却没有 BOM —— MSVC 消费者不带
/utf-8 时会级联报错
`tests/tools/check_config_contract.py` 的 `check_header_encoding()` 判的是 `src/` 下
**每一个** `.h`(不只安装集里那些),`src/db/uvcpp_db_odbc_api.h` 里全是中文注释、
却和它的同门不一样 —— `uvcpp_db_pgsql_api.h` / `uvcpp_db_mysql_api.h` 都带 BOM,
`src/db/` 下 24 个文件里只有这两个 ODBC 文件没带。`src/db/uvcpp_db_odbc.cpp` 一起补上
(它不受这条门禁管,但同一个目录里另外 12 个 `.cpp` 全带,留一个例外没有理由)。
为什么必须修:MSVC 读无 BOM 的源文件时按系统 ANSI 代码页(简中 936/GBK)解码,一个
三字节汉字被当成两个双字节字符、字节边界整体错位 —— 该在注释里的 `*` 变成运算符、
`*/` 落到注释外,于是其后每个头都在"注释外"被解析,一路级联到 `<algorithm>`,报错
地点与病因完全无关。仓内看不见,因为顶层 `CMakeLists.txt` 的 `/utf-8` 是**目录
作用域**的:它进不了导出集、也到不了"源码树直接消费"那条路径(`tests/tools/
package_release.py` 里那段 BOM 兜底也拦不住 —— 私有头不进包)。
这条门禁本来就该在分支自己的 CI 上红过:`feature/db-odbc` 没有对着 master 开过 PR,
而这几位 `.h` 是随 `f40700d` 进来的。这次开 PR 才算第一次被它看见。
本机复核:`check_config_contract` 的编码判据转绿("148 个公开头,含非 ASCII 的都带
BOM");`src/db/` 24 个文件的 BOM 状态现在一致;重编 + `ctest` 54/54;ODBC → SQLite
回归 180 / 0 / 4 + 57 / 0 / 0 不变;四条文档门禁全过。
PR 的 MSVC `config-contract` 腿抓到的第二处,比 BOM 那处严重 —— 它拦的是**产物**:
imports 里非 Windows 自带的: libcrypto-3-x64.dll libssl-3-x64.dll msvcp140.dll
odbc32.dll vcruntime140.dll vcruntime140_1.dll
**缺少必需的文件,不产出残缺的包**:
- dll 的依赖 odbc32.dll(导入表里有,包里没有)
##[error]Process completed with exit code 2.
`UVCPP_DB_ODBC` 在 Windows 上是**默认档就链导入库**的:`CMakeLists.txt` 那条
`elseif(WIN32)` 只在 `UVCPP_DB_RUNTIME_LOAD=ON` 时省掉它,默认(`OFF`)走的是
`list(APPEND UVCPP_DB_LINK_LIBS odbc32)`。于是 `uvcpp.dll` 的导入表里有 `odbc32.dll`,
而 `tests/tools/package_release.py` 那张 `SYSTEM_DLLS` 表里没有它 ⇒ 打包那步的
"bin/ 必须覆盖 dll 真实导入表"自检把它当第三方依赖,去 `runtime_roots` 里找不到 ⇒
`missing[]` ⇒ **退出 2,一个包都不产出**。
也就是说:**ODBC 后端接进来之后,Windows 上用默认配置(不传 `RUNTIME_LOAD`)根本
打不出包**。这不只是 CI 的事 —— 它是使用者自己出包时走的那条路。
`odbc32.dll` 本来就该在这张表里:它是 Windows 的 ODBC 驱动管理器,System32 里必有、
随系统走,与 `ws2_32.dll` 同类,既不可再分发也不需要分发。那张表自己的注释写的是
"表里少写一个,代价是启动时报错退出(响亮),多写一个才是静默的坏包,所以宁可写全"
—— 这里两头都不沾,它只是漏了。
**为什么发版路径上看不见**:六条发布腿都传 `-DUVCPP_DB_RUNTIME_LOAD=ON`,符号由
`LoadLibrary` 取、导入表里没有 `odbc32.dll`,所以打包自检不会命中。两条 MSVC
`config-contract` 腿**不传**这个开关(走默认档),于是它们量到的正是另一半。那两条腿与
发布腿"配置不逐字相同"这件事,这次的价值就是各自量到了一个方向的坏包。
(`release.yml` 里 mingw 腿那份**导入表白名单**没有跟着改,也不该改:那是"运行期加载
失效就会红"的守卫,`odbc32.dll` 不在里面正是它该有的样子。这两张表是两件事。)
本机复核:`is_system_dll("odbc32.dll")` 为真(`pe_imports()` 返回的就是小写名,表项
大小写一致);四条文档门禁全过;`package_release.py` 对 linux-x64 现打一个包,161 个
文件、正常产出(Linux 不上这张表,改的是 Windows 那条分支)。
第一次空转(run 38932780047,2026-10-17)两条 Linux 腿当场红在
`linux-odbc-headers::unixodbc-dev 里没摊出 unixodbc.h`。守卫本身没错(宁可红,
也不静默发一个少后端的包),错的是判据本身:它写的是**发行版的属性**,不是我们
的要求 —— 而两个发行版的 `unixodbc-dev` 布局根本不同。
实测两份 `.deb` 的文件表:
jammy 2.3.9-5ubuntu0.1(`ubuntu-22.04` / `ubuntu-22.04-arm`)
usr/include/{sql,sqlext,sqltypes,…}.h 平的;`sqltypes.h` 只 `#include <wchar.h>`
**全包没有 `unixodbc.h`** ⇒ 只把 `usr/include` 交给 `-D` 本来就够
noble 2.3.12-1ubuntu0.24.04.1(开发机 Mint 22.3 那份)
usr/include/<multiarch>/unixodbc.h
`sqltypes.h` 里多一句 `#include "unixodbc.h"` ⇒ 必须挂到公共头旁边
"少挂一层"只有 noble 那种布局才有;在 jammy 上要求 `unixodbc.h` 存在,等于把一个
完全正常的包判红。改法两半:
· 软链那步改成"**有就挂、没有就算了**"(`-n` 判空,不再 exit 1);
· 判据换成**编一次**:把后端真正 include 的 `<sql.h>` + `<sqlext.h>` 用
`c++ -std=c++11 -I $_inc -fsyntax-only` 过一遍。与发行版、与布局都无关,而且
直接就是"这个后端能不能编"本身(后端只有这两个 include,Windows 上多一个
`<windows.h>` 且必须排在最前)。
本地把改后的代码**从 YAML 原文抠出来逐字跑**过三种情形:jammy 平布局 ⇒ 绿且不建
链接;noble 拆布局 ⇒ 绿且挂出 `unixodbc.h`/`unixODBC`;故意放一个空 `sql.h` ⇒ 红
并打出编译器原文与摊出来的头清单。另:`ln -sfn` 的目标按**链接所在目录**解析,喂
相对路径会建出自指的坏链接,这条也写进注释了。
教训记在 CHANGELOG 与 `release.yml` 的注释里:这个坑是照 noble(开发机)验出来、
在 jammy(CI)上红的 —— **开发机的发行版不能当普遍规律**。
…-dev)
空转(run 38932780047)里两条 mingw 发布腿红在 Configure & Build,整条腿
只有一处 error:
sqltypes.h 第 109 行: error: conflicting declaration 'typedef short unsigned int WCHAR'
winnt.h 第 320 行: note: previous declaration as 'typedef wchar_t WCHAR'
unixODBC 那份 sqltypes.h 里"补 windows 类型"那一段的开关是
ALREADY_HAVE_WINDOWS_TYPE —— 这个宏谁都不定义,所以 <windows.h> 明明已经
进来过,它照样补一遍。补出来的 CHAR/WORD/DWORD/BYTE/LPDWORD 与 winnt.h
逐字相同(重复 typedef 合法),只有 WCHAR 不同。
解法是这段补丁自己的开关 SQL_WCHART_CONVERT:打开后 WCHAR 是 wchar_t,
与 winnt.h 逐字相同。SQLWCHAR 由 typedef WCHAR 派生,仍是 2 字节,所以
后端里那条按 sizeof(SQLWCHAR) 编译期分 UTF-16/UTF-32 的转换走的是 MSVC 上
已经在跑的那条路,不新增代码路径。只对"Windows 上但不是 MSVC"开:Linux 上
wchar_t 是 4 字节,开了是改 ABI 不是修 bug。
同一条腿那句 'CALLBACK' redefined 警告也一并处理(push_macro/#undef/
pop_macro 借出去再还回来)—— 它把 minwindef.h 的 __stdcall 改成空定义且
不复原。
MSVC 上碰不到:SDK 那份 sqltypes.h 是微软自己写的,没有这一整段补丁。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feature/db-odbc把 db 的第四个后端写完了(实现 + 符号表 + 共用套件 + 文档),但离主干还差一步,而这一步恰好决定"打出来的包里有没有这个功能"。这次只合并、不发版、不打 tag。问题:这个后端能在六条发布腿上静默消失,而没有一条断言会红
release.yml此前全文零次提到 odbc:没传-DUVCPP_ENABLE_DB_ODBC、六条腿没有一条准备过sql.h、那份读生成头的断言清单(17 枚宏)里也没有UVCPP_DB_ODBC_ENABLE。而
UVCPP_ENABLE_DB_ODBC默认就是 ON,找不到头时走的是message(WARNING)+set(... OFF)——一句普通变量赋值。于是现实是:CMakeCache.txt里照旧写着ON、生成头里那颗宏是 0、包里少一个后端,一条断言都不会红。今天六条腿编出来的包,Linux 那四条不会有 ODBC,Windows 那两条要看sql.h碰不碰得到。这个 PR 做了什么
1. 十处 configure 显式开它 + 显式给头,头的来路按腿分两种:
sql.h从哪来mingw-x64/mingw-arm64mingw-w64-{x86_64,clang-aarch64}-unixodbc,头在$MINGW_PREFIX/include/unixodbc/msvc-x64/msvc-arm64<SDK>/Include/<ver>/um,由新脚本.github/scripts/win-odbc-headers.sh挑linux-x64/linux-arm64apt-get download unixodbc-dev+dpkg -x,不 installlibmysqlclient-dev/libpq-dev同一条规矩:只要头。装了会让find_library(odbc)有机会静默成功、给产物添一条DT_NEEDED2. 六条腿各补一枚
UVCPP_DB_ODBC_ENABLE断言(读生成头,不是 cache;17 → 18 枚)。对前三枚后端,这条断言拦的是"发布腿忘了传开关";对这一枚,它拦的是"这条腿没弄到头"——后者更隐蔽,因为"开关默认 ON"这件事本身会让人以为它不用管。3. 两条 Linux 腿补两条负向判据:
ldd白名单加odbc分支、nm -D -u计数加SQL[A-Z]。4. 新增降级用例
tests/functional/db_odbc_nodrv_func.cpp(16 条断言):驱动管理器不在时open("odbc://…")回NO_DRIVER、last_error()是一句说得出试过哪几个库名的人话、同一进程里 sqlite 照常可用。前提不去卸载任何东西,而是把UVCPP_DB_ODBC_LIBRARY指到一个不存在的路径(这正是uvcpp_db_runtime_load.h里写明这个旋钮的用途)。顺手修掉test_db_odbc_func漏在SKIP_RETURN_CODE 3之外那处(它因此在 CI 上显示成Failed,而真相是"未判定")。5. 文档与 CHANGELOG:打包那节重写;ODBC 小节加"为什么不学 SQLite 静态链";那句"ODBC 到 SQL Server 没跑过"改准(下游已在真 SQL Server 上跑通,本仓测试集这一档仍是 ODBC → SQLite)。
顺带把 master 合了进来(用 merge,不是 rebase)
feature/db-odbc从 v1.7.0 发布态1ef8b6f分出,master 之后走了 12 个提交,四处真冲突(版本号、CHANGELOG.md顶部、两版 README 版本号),其余自动合。必须 merge:下游 wlzaweb 的发布流程钉的是这条分支上的
57c93d8d2f8b9193273a2ece4eda2a6cf4896154并--depth 1单独取它,改写成新历史会让那个 SHA 从可达变成不可达,那边 CI 当场红。这次 push 是快进(57c93d8..c84f554),那个 SHA 仍在本分支祖先里。合并后核过:
src/db/里 master 那 5 个 db 性能提交逐字节保留;连接池uvcpp_db_pool.*与异步uvcpp_db_async.*相对 master 零差异(它们一律经uvcpp_db_client、不碰uvcpp_db_driver,所以"多一个后端"在结构上就动不到它们——这是"ODBC 不拖累 db 模块性能"的根据,不是承诺)。PR 的 CI 抓到两处真问题(都已在本分支修掉)
第一次跑:27 绿 / 16 红,其中 15 条在 master 自己的
1f6a8fb上就是红的(macOS 六条 + MSVC 七条 + mingw 一条,都红在tests/functional/web_app_static_func.cpp:2447的unknown type name 'fs_count_api'——那个文件本分支一个字节都没碰,是 master 上另一条在飞的线)。剩下两处是本分支带进来的,都已修:config-contract(Linux + MSVC):src/db/uvcpp_db_odbc_api.h含非 ASCII 却没有 BOM(fix(db)那个提交)。MSVC 读无 BOM 的源文件按系统 ANSI 代码页解码,字节边界错位、注释外的*/级联报错,报错地点与病因完全无关。src/db/下 24 个文件里只有这两个 ODBC 文件没带 BOM,前两个后端的*_api.h都带 —— 这条门禁判的是src/下每一个.h,本来就该在分支自己的 CI 上红过,但这条分支没有对着 master 开过 PR。config-contract(MSVC):odbc32.dll不在package_release.py的SYSTEM_DLLS表里 ⇒ 默认档在 Windows 上打不出包(fix(build)那个提交)。CMakeLists.txt的elseif(WIN32)只在UVCPP_DB_RUNTIME_LOAD=ON时才省掉导入库,默认OFF走的是list(APPEND UVCPP_DB_LINK_LIBS odbc32),于是打包那步的"bin/ 必须覆盖导入表"自检把它当第三方依赖、找不到就missing[]+ 退出 2,一个包都不产出。六条发布腿传RUNTIME_LOAD=ON,导入表里没有它,所以发版路径上看不见;两条 MSVCconfig-contract腿不传这个开关,量到的正是另一半。odbc32.dll是 System32 里必有的系统组件(与ws2_32.dll同类,不可再分发),本来就该在那张表里。修完重跑:29 绿 / 14 红。那 14 条与 master 自带的那 14 条同一集合、同一报错(把本轮 macOS/ssl 的
--log-failed与 master 同任务116705709530的日志逐字比过)。与本功能直接相关的六条腿全绿:Linux · Ubuntu / db、Linux · Ubuntu / db-servers、Windows · MSVC / db、macOS / db、两条config-contract。也就是说 master 眼下自己是红的,红的不是这个 PR 造成的。所以本 PR 的 checks 在界面上会一直带红——评审时请按「失败的 job 集合是否等于 master 自带的那个集合」判,别按颜色判。
(
release.yml里 mingw 腿那份导入表白名单没跟着改,也不该改——见下面那条。)发布腿空转:六条腿真编一遍,抓到两处只有那条腿才有的问题(都已修)
已拿到空转的真凭据(
gh workflow run release.yml -f tag=v1.7.0 -f dry_run=true,run38932780047)。这一步的价值当场兑现:两条 Linux 腿与两条 MinGW 腿各红一次,而这两处在 PR 阶段的 CI 里都看不见(那几条腿这次没编 ODBC),也不在同一处 —— 是两个方向上的独立错误。1. 两条 Linux 腿红在
Install deps。 第一版判据要求unixodbc-dev摊出unixodbc.h,而那个文件只有 noble 那种布局才有;CI 的 jammy(ubuntu-22.04/-arm)那份2.3.9-5ubuntu0.1里全包都没有它,sqltypes.h只 include<wchar.h>。守卫本身没错(宁可红,也不静默发一个少后端的包),错的是那个谓词。改法是软链那步变成"有就挂、没有就算了",判据换成把后端真正 include 的<sql.h>+<sqlext.h>用c++ -std=c++11 -I … -fsyntax-only编一次 —— 后者与发行版、与布局都无关,它本身就是"这个后端能不能编"。(软链那步另有一处细节进了注释:ln -sfn的目标按链接所在目录解析,喂相对路径会建出自指的坏链接。)教训:开发机的发行版不能当普遍规律 —— 这个坑是照开发机的 Mint 22.3 / noble 验出来的,在 jammy 上红的。2. 两条 MinGW 腿红在
Configure & Build,整条腿只有一处 error:unixODBC 那份
sqltypes.h里"补 windows 类型"那一段的开关是ALREADY_HAVE_WINDOWS_TYPE—— 而这个宏谁都不定义,所以<windows.h>明明已经进来过,它照样补一遍。补出来的CHAR/WORD/DWORD/BYTE/LPDWORD与winnt.h逐字相同(C++ 里重复 typedef 合法,一声不响),只有WCHAR不同:它给unsigned short,winnt.h给的是wchar_t。解法是这段补丁自己的开关SQL_WCHART_CONVERT(打开后WCHAR就是wchar_t,与winnt.h逐字相同)。SQLWCHAR由typedef WCHAR派生,尺寸不变、仍是 2 字节 ⇒ 后端里那条按sizeof(SQLWCHAR)编译期分 UTF-16 / UTF-32 的转换走的还是 MSVC 上已经在跑的那条路,不新增代码路径。只对"_WIN32但不是_MSC_VER"开(Linux 上wchar_t是 4 字节,开了是改 ABI 不是修 bug);MSVC 上碰不到(SDK 那份sqltypes.h是微软自己写的,没有这一整段补丁)。同一条腿那句'CALLBACK' redefined警告也一并处理了:它把minwindef.h的#define CALLBACK __stdcall改成空定义而且不复原,所以用push_macro/#undef/pop_macro借出去再还回来。教训与上一条同源,只是换了个维度:上一条是"开发机的发行版不能当普遍规律",这一条是"Linux 上编得过不能推出 mingw 上也编得过"。这两条腿的头是按
packages.msys2.org上的包文件表核的、不是编出来的 —— 而"文件表里有sql.h"与"这份sql.h能和<windows.h>一起编"是两件事。一件评审时必须知道的事:master 的红把四条 Windows 腿的后半段全遮住了
msvc-x64的 25 条 error 全在tests/functional/test_web_app_static_func.vcxproj(fs_count_api那条在飞的线),构建停在那儿 ⇒ 后面那一步读生成头的 18 枚断言一步都没跑。所以四条 Windows 腿给不出"这个包里有 ODBC"的凭据,不是 ODBC 的问题,而是 master 眼下连发布都跑不完(发布腿会构建测试目标)。请按这个口径读六条腿:uvcpp_db_odbc.cpp+ MSYS2 那套头);断言与打包被 master 的红挡住uvcpp.vcxproj -> uvcpp.dll已建成、uvcpp_db_odbc.cpp0 报错,头来自 SDK 的10.0.26100.0/um)也就是说,发版路径上真正会拦人的除了那 18 枚断言,还有 master 自身的绿 —— 两者都得成立,而后者不在本 PR 的范围内。
第二轮(两条修复之后)的复核结果
run
38934782988,同样的-f dry_run=true。两条修好的腿都翻过来了,而且是可判定的凭据,不是"看着绿了":linux-x64/linux-arm64Assert the requested modules are actually on(那 18 枚读生成头的断言)与Packagemingw-x64/mingw-arm64conflicting declaration 'WCHAR'计数 0(修复前它是这两条腿唯一的 error);Linking CXX shared library libuvcpp.dll成功;configure 打出db: ODBC 后端开(运行时加载 odbc32.dll,不链导入库;头来自 <msys2>/<triple>/include/unixodbc);剩下的 error 全部是 master 那条fs_count_apimsvc-x64/msvc-arm64test_web_app_static_func.vcxproj;库目标uvcpp.vcxproj -> uvcpp.dll建成、uvcpp_db_odbc.cpp0 报错,头来自 SDK 的10.0.26100.0/um归纳:两条 Linux 腿第一次走完了全流程(判据修复是对的,而且是端到端 —— 此前那段 bash 只在本机拿解包出来的
.deb验过);两条 MinGW 腿证明了 ODBC 后端能在 MSYS2 那套头上编过并链成(SQL_WCHART_CONVERT那条修复是对的);四条 Windows 腿剩下的红与上一轮同集合、同报错,全是 master 的。几件请评审时留意的事
别把
odbc32.dll加进 mingw 腿的导入表白名单。 那份白名单里没有它,这是想要的性质:真被链进去时它会当场红,红了就是"运行期加载失效"的信号。为了让断言变绿而加进去,等于把"Windows 上也按需加载"这条唯一的守卫拆掉。那枚断言读的是生成头,别改成 grep
CMakeCache.txt。 静默降级时 cache 里照旧是ON——这正是上面第 1 节那个形状,读 cache 等于把这条断言变成永远绿。UVCPP_ENABLE_DB_ODBC默认 ON 这件事不改。 缺头时只 WARNING + 关掉自己、其余后端照编,正是使用侧要的形状;代价是没装unixodbc-dev的源码构建每次 configure 多一条 WARNING,比"默认 OFF 让人以为没这功能"好。没有走静态编译。 要的不是某一个库,是驱动管理器;而管理器存在的意义就是运行期按
Driver=/DSN=去找具体驱动(msodbcsql18之类),静态链它换不来"不用装东西";Windows 的odbc32.dll是系统件、不可再分发,静态链它既不合规也没意义。后端那 1688 行本来就编进产物了,缺的只是管理器这一层的一次dlopen。这一段也写进了doc/db-guide.md,免得下一个人再想一遍。发版路径自动拦,不发版不验证 —— 所以这次真去空转了一轮(见上面那节)。 六条腿只在 tag 推送(或手动 dispatch)时真跑;PR 阶段那几条 CI 腿这次没有加 ODBC(保持 PR CI 轻)。原先这里写着"ODBC 从来没在 mingw 上真编过一次",现在编过了,而且一编就红(
SQL_WCHART_CONVERT那处),已修 —— 这正是空转的收益:两个"到了发版当天才发现"的坑,换一轮六条腿的 runner 分钟。要拿到六条腿的真凭据,只有手动 dispatch 一次
release.yml—— 而且必须显式传dry_run=true。这里有个反向陷阱值得单独说:这个 workflow 的 dispatch 输入dry_run默认是false,而publish里那几个gh release步骤的条件是if: ${{ !inputs.dry_run }},所以默认档一 dispatch 就是真发版——会gh release edit/create再加gh release upload --clobber dist/*.zip;tag又是必填且"须已存在",随手填一个已有 tag,就是拿这次的 1.7.2-dev 包覆盖掉那一版的资产。空转的正确写法是gh workflow run release.yml -f tag=<已存在的tag> -f dry_run=true。(原先我在这段里写的"不带 tag 时有if: startsWith(github.ref, 'refs/tags/')挡着"是错的,release.yml里根本没有这条条件,已改。)同一把旋钮下 mysql/pgsql 的"缺库"用例这次没做(同一件事的另一半,超范围)。
本机实测(Linux,装了 unixodbc-dev)
反向(同一棵树不给
sql.h):只出一条 WARNING、生成头里那颗宏是 0、其余三个后端照编、ctest50/50——这就是"没有 ODBC 动态库/头时其他功能正常"在构建期的样子。门禁:
check_docs、check_doc_lines(--update重刷锁文件后 5 条判据)、check_ci_layout、check_doc_versions全绿;check_doc_snippets对现打的1.7.2-linux-x64包跑doc/db-guide.md7/7 条片段。