Skip to content

fix(mdb): NUL-terminate the ODBC connection string - #103

Open
Curricane wants to merge 6 commits into
systemxlabs:masterfrom
Curricane:bug/mdb_conn
Open

Curricane wants to merge 6 commits into
systemxlabs:masterfrom
Curricane:bug/mdb_conn

Conversation

@Curricane

@Curricane Curricane commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Problem

Reading .mdb files over ODBC failed intermittently with:

File not found
Unable to locate database /home/cc/Downloads/cmc_003_test_db.mdb<E2><7F>

Those trailing bytes after the path are the tell: the path handed to stat() was corrupted.

Root cause

mdbtools' libmdbodbc.so SQLDriverConnect ignores the cbConnStrIn length argument and parses the connection string with NUL-terminated C string functions (strcasestr, strchr, g_strsplit). odbc-api 28 passes a Rust &str pointer plus an explicit length, and a Rust &str has no trailing NUL — so the driver reads past the string into uninitialized heap and appends whatever it finds to the DBQ= value.

Because the garbage depends on prior heap contents, the failure looked intermittent and was reported as a concurrency problem. Reproduced under strace:

newfstatat(AT_FDCWD, "/home/cc/Downloads/cmc_003_test_db.mdb\342\177", ...) = -1 ENOENT (No such file or directory)

Changes

  1. NUL-terminate the connection string. MdbPool::get now borrows the string from a CString, whose buffer has the terminator immediately after the bytes, and keeps it alive across the call. This is the only mdbtools entry point that over-reads: SQLExecDirect delegates to SQLPrepare, which does respect the explicit length.

  2. Treat every MDB column as nullable. mdbtools reports SQLDescribeCol nullability as !col->is_fixed (src/odbc/odbc.c), which describes physical storage layout rather than whether a column can hold NULL. Fixed-width columns that do contain NULLs were reported NOT NULL, tripping Arrow's declared as non-nullable but contains null values. The reported flag carries no usable information, so it is no longer consulted.

  3. Tests and CI. New integration-tests::setup_mdb_layers() loader plus a committed 1.9 MB esri_layers.mdb fixture (two feature layers, 47 and 242 rows, plus ESRI system tables). tests/mdb_concurrent.rs reads the layers sequentially, from several tasks at once, and through repeated Pool::get calls on distinct connection identities. Added to the CI integration test invocation.

Known issues

Not addressed here, tracked separately in #105: mdbtools answers SQL_NO_DATA for a text, memo or OLE value of length zero, and odbc-api turns that into a panic. Any table holding such a cell — the ESRI GDB_Items system table, for one — still fails to read.

Verification

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test -p integration-tests --test mdb (15 passed), --test mdb_concurrent (4 passed), cargo test -p datafusion-remote-table
  • strace confirms repeated_pool_get opens the fixture 40 times (previously once, because of the process-global connection cache), and connecting to the file that triggered the report succeeds 10/10 runs where it previously failed 10/10.
  • The layer reads exercise multi-call reads of large binary values: the largest SHAPE blob is 17,152 bytes and 34 of them exceed 4 KiB; their ESRI headers decode to shape type 3 (PolyLine), as expected for 境界线.

chenmch added 5 commits September 10, 2026 12:30
mdbtools' SQLDriverConnect ignores cbConnStrIn and scans for a NUL terminator, while odbc-api passes a Rust &str plus an explicit length. The driver read past the string into uninitialized heap, appended the garbage to the DBQ value and stat'ed a nonexistent path, so connections failed intermittently with "Unable to locate database". Borrow the connection string from a CString so the terminator sits right after it.
mdbtools reports SQLDescribeCol nullability as `!col->is_fixed`, which describes the physical storage layout rather than whether a column can hold NULL. Fixed-width columns containing NULLs were reported NOT NULL and tripped Arrow's non-nullable checks. The reported flag carries no usable information, so always mark MDB columns nullable.
Add integration-tests::setup_mdb_layers() plus the esri_layers.mdb fixture, and tests that read its two feature layers sequentially, from several tasks at once, and through repeated Pool::get calls on distinct connection identities. Wire them into CI.
mdbtools' SQLGetData returns SQL_NO_DATA for a text, memo or OLE value of length zero: `to_c_char` and the MDB_OLE case compare the read position against the value length, and 0 >= 0 holds before anything is copied. odbc-api turns SQL_NO_DATA into a panic, so any table with an empty text or memo cell (such as the ESRI GDB_Items system table) failed to read at all.

Read variable-length columns through the raw SQLGetData call instead, where SQL_NO_DATA simply means there is nothing left to read. The bytes gathered so far become the cell value, so an empty value stays empty rather than failing the query.
GDB_Items in the bundled fixture has an empty Path in 10 of its 16 rows, which made the table unreadable before the SQLGetData fix. Rename tests/mdb_concurrent.rs to tests/mdb_layers.rs, since it now covers reads over the ESRI layers rather than only concurrent ones.
Reading a table with a zero-length text, memo or OLE cell panics again with
"Unexepcted SQL_NO_DATA returned by ODBC function", so the ESRI GDB_Items
system table is unreadable again. The fix is out of scope for this change
set and is tracked in systemxlabs#105 instead.

This reverts 7673c14 and 4f2831d, restoring the tree of 9208101.
@Curricane Curricane mentioned this pull request Sep 10, 2026
@Curricane

Copy link
Copy Markdown
Collaborator Author

后续跟踪转到 #106:分支已由 bug/mdb_conn 重命名为 datafusion-53,本 PR 与 #106 指向同一份提交(bab0a9923a620b96421344b4913362fd1cc99e9e)。本 PR 的描述已同步到该 PR 上,这里的讨论历史保留备查。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant