Add odbc connector by claude code#148
Conversation
What was built
New connector under include/sqlpp23/odbc/, enabled with -DBUILD_ODBC_CONNECTOR=ON (uses CMake's built-in FindODBC, which found your Homebrew unixODBC 2.3.14).
Your requirements, and how they're addressed:
ODBC 3.8: the environment requests SQL_OV_ODBC3_80 at allocation (connection_handle.h). Errors are surfaced as sqlpp::odbc::exception carrying the full diagnostic-record chain, SQLSTATE, and native error code.
Variant config: connection_config.h holds std::variant<data_source, connection_string> source — either a DSN (SQLConnect with name/user/password) or a full connection string (SQLDriverConnect, no prompt). Exactly one way to connect per config, dispatched with std::visit.
Multi-row fetch: cursor_result.h uses an ODBC block cursor when possible — columns bound via SQLBindCol to column-wise arrays, SQL_ATTR_ROW_ARRAY_SIZE rows (config row_array_size, default 64) fetched per driver round trip. If any text/blob column is unbounded or exceeds max_bound_column_size (default 4096 bytes/row, text budgeted at 4 bytes/char for UTF-8), it transparently falls back to row-at-a-time streaming via SQLGetData with a growing buffer — the portable pattern that works on every driver (columns are read in increasing order). It also handles drivers that lower the requested array size (reads back the actual value) or reject block cursors entirely.
Meaningful names: the result type is cursor_result_t ("block cursor" is the official ODBC term), not bind_result_t; helpers are execute_direct, prepare_statement, stream_value_if_required, release_cursor, etc.
No single-caller _impl functions: connection.h only has private helpers with multiple callers (execute_direct ×5, prepare_statement ×5, run_prepared_command ×4, allocate_statement ×2); the _select/_insert/… entry points do their work inline.
Other design points worth knowing: transactions use SQL_ATTR_AUTOCOMMIT + SQLEndTran (ODBC has no BEGIN), isolation levels map to SQL_ATTR_TXN_ISOLATION; date/time literals serialize as ODBC escape sequences ({d ...}, {t ...}, {ts ...}) and parameters as ?; statement handles are shared_ptrs so a result from a prepared statement keeps it alive, and the result's destructor closes/unbinds the cursor so the prepared statement can re-execute; there is deliberately no last_insert_id (nothing portable exists — documented in docs/connectors/odbc.md). The C++20 module interface sqlpp23.odbc.cppm mirrors the sqlite3 one.
Tests
Under tests/odbc/, all runnable offline:
serialize: date/time/timestamp escape sequences, string escaping, ? parameter markers, full select statements (escaping needs no live driver, so SQLPP_COMPARE works against an unconnected connection).
usage/ConnectionConfig: variant behavior, config equality, and a real driver-manager round trip — connecting to a nonexistent DSN and verifying the exception carries a SQLSTATE.
usage/Statements: compiles every execution path (direct + prepared select/insert/update/delete, NULL parameters, transactions); it runs the statements for real if you ever set SQLPP_ODBC_CONNECTION_STRING.
Limitations
The BUILD_WITH_MODULES build can't be validated on this machine — the system AppleClang lacks clang-scan-deps, which breaks all module targets (core included), not just the new one.
sqlpp::time values lose sub-second precision (ODBC's SQL_TIME_STRUCT/{t} escape have none); timestamps keep microseconds. This and other portability notes are in the docs page.
| float, int64_t, short, size_t>, | ||
| short>::value, | ||
| ""); | ||
| static_assert( |
|
That's pretty cool. Did you already have the chance to play with the connector beyond running the tests? At first glance, this looks reasonable, but it is a lot of code. It would certainly take a few days for me to take a closer look. Based on my own experience with AI-generated code, I wonder if some parts might be a bit bloated? Stupid question: How would I test this on Linux, do you have step-by-step description? Probably starting with
|
Verified end-to-end against the SQLite ODBC driver
(`brew install sqliteodbc`):
- All data types round trip (bool, int64, uint64, double, text with
embedded quotes, 100 KB blob, date/time/timestamp) via both direct
execution (literals) and prepared statements (bound parameters).
- NULL values in both directions.
- Block fetch across rowset boundaries (`row_array_size = 3`, 10 rows).
- The `SQLGetData` streaming fallback including the buffer-growth loop.
- Transactions: commit and rollback verified by row counts.
- `affected_rows` for update/delete.
Live testing caught a real-world quirk: the SQLite driver reports
`blob` columns as `SQL_BINARY` with a fabricated size of 255, so
blob result columns (and long-typed columns) now always use the
streaming path instead of trusting driver-reported sizes. Bound
timestamp parameters are rounded to milliseconds by that driver; the
connector itself preserves microseconds.
To run the tests (serialization/config tests need no driver; the
statement test runs against a real database only when the environment
variable is set):
cmake -S . -B build -DBUILD_ODBC_CONNECTOR=ON
cmake --build build
SQLPP_ODBC_CONNECTION_STRING="Driver=/opt/homebrew/lib/libsqlite3odbc.dylib;Database=/tmp/sqlpp23_odbc_test.db;" \
ctest --test-dir build -R odbc
Not covered: the `BUILD_WITH_MODULES` variant of `sqlpp23.odbc.cppm`
cannot be built on this machine (no `clang-scan-deps`; this equally
affects the existing core/sqlite3 modules).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
For the chat: `Read ODBC_HANDOFF.md — continue testing the ODBC connector with Windows drivers.`
I have tested it with sqlite on mac and postgres and sqlserver on windows (have local changes).
Only the 28 new files creates 28 * ~27 lines = 750 new lines of file headers. All the code that I had read was relative concise. I will test everything myself and will add documentation how to test. I will also change some code again (for example always bind variables). So I don't recommend reviewing right now until this is "Ready for review". PS: And I am on a festival the next days, so don't wonder if there will be no activity :D |
|
Thanks for the status! Enjoy the festival :-) |
After doing this the last few days I thought I could simply ask claude code which some learnings from the last days. It did everything I did in the last days + actually compiles + tests in one hour 😅