You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SqlConnection: allow configurable encryption via SQL_COPT_SS_ENCRYPT (#578)
Closes#14
## What
Adds `SqlEncryptionMode` (`DriverDefault` / `Disabled` / `Enabled`) and
a matching
`SqlConnectionDataSource::encryption` field, so an application can
explicitly request — or refuse —
a TLS-encrypted connection.
```cpp
SqlConnection::SetDefaultDataSource(SqlConnectionDataSource {
.datasource = "MyServerDSN",
.username = "user",
.password = "password",
.encryption = SqlEncryptionMode::Enabled,
});
```
## Why this shape
Previously the only way to configure encryption was to spell out a
driver keyword in a raw
connection string. On the DSN-based connect path (`SQLConnect`) there is
no connection string to put
that keyword in, so encryption was not configurable there at all — which
is exactly the gap
`SQL_COPT_SS_ENCRYPT` exists to close.
Three judgment calls worth reviewing, since the issue specifies the
mechanism but not the API:
1. **Tri-state with a `DriverDefault` sentinel**, following the existing
`SqlIsolationMode::DriverDefault`
precedent (`SqlTransaction.cpp:19`). The default leaves the attribute
untouched, so anything that
does not opt in behaves bit-for-bit as before.
2. **Fail-closed.** `SQL_COPT_SS_ENCRYPT` is a *pre-connect* attribute,
so the server type is not known
yet and cannot be branched on — which is why only an explicit opt-in
touches it at all. If the
caller did opt in and the driver rejects the attribute, the connection
**fails** instead of being
established. Silently downgrading a requested encrypted connection to
plaintext seemed like the
wrong failure mode for a security setting; happy to flip this if you
disagree.
3. **The setting survives flattening into a connection string.**
`ToConnectionString()` emits
`Encrypt=yes|no` (nothing at all for `DriverDefault`),
`FromConnectionString()` parses it back, and
`SetDefaultDataSource()` now delegates to `ToConnectionString()` rather
than re-formatting a subset
of the fields — otherwise that path would silently drop the knob.
(`std::formatter<SqlConnectInfo>`
was duplicating the same format string and is now delegated too.)
`SQL_COPT_SS_ENCRYPT` and its `SQL_EN_*` values live in Microsoft's
`msodbcsql.h`, which unixODBC does
not ship, so the values are mirrored locally rather than adding a
dependency on that header.
## Tests
- 11 new unit cases in `SqlConnectInfoEdgeTests.cpp`: keyword parsing
(all documented spellings,
case-insensitive, whitespace-tolerant, unknown → `DriverDefault`),
rendering, round-tripping,
comparison, and a regression guard that a non-opted-in data source
renders byte-for-byte as before.
- 1 new DB case in `SqlConnectionDbTests.cpp`: opens an explicitly
encrypted connection against SQL
Server and round-trips a query over it. `UNSUPPORTED_DATABASE`-gated for
the other backends, which
configure TLS through their own keywords.
**Coverage gap, stated explicitly:** the
`SQLSetConnectAttr(SQL_COPT_SS_ENCRYPT)` call itself is on the
DSN connect path, which needs a registered DSN and so is not reachable
from the CI harness (all test
envs use connection strings). The end-to-end test therefore exercises
the equivalent
connection-string path. The attribute application itself is covered only
by construction and review.
## Databases tested
| Database | Result |
|---|---|
| `sqlite3` | 1414 cases, 1413 passed, 1 pre-existing skip |
| `mssql2022` (Docker, `mcr.microsoft.com/mssql/server:2022-latest`) |
1414 cases, 1411 passed, 3 pre-existing skips |
| `postgres` (Docker 16.4) | 1414 cases, 1412 passed, 2 pre-existing
skips |
The new encrypted-connection case was confirmed to actually *run* (not
skip) and pass on `mssql2022`.
## Compilers tested
- `clang-debug` (ASan + UBSan + pedantic `-Werror`) — all three
databases above. **This is the one that
ran the suite.**
- **GCC was not exercised**, contrary to `AGENT.md` step 3. The
`gcc-release` preset is Linux-gated
(`Cannot use disabled configure preset`) and this is macOS. I configured
GCC 15 manually with
`LIGHTWEIGHT_BUILD_MODULES=ON` instead; `SqlConnection.cpp` and
`SqlConnectInfo.cpp` both compiled
clean under it, but the build cannot complete on macOS for reasons
unrelated to this change (see
below). **The GCC and modules legs need CI to be the judge.**
## Pre-existing issues found while validating (not fixed here)
Building `gcc-release -D LIGHTWEIGHT_BUILD_MODULES=ON` with GCC 15 on
macOS fails on untouched code.
Flagging in case they bite on a compiler bump — CI currently pins GCC
14:
- `SqlLogger.cpp:291`: `'std::stacktrace' has not been declared`
(Homebrew libstdc++ lacks it; the
`LIGHTWEIGHT_HAVE_STDCXXEXP` probe fails and the `#if` guard then leaves
the call unguarded).
- GCC 15 tightened the module TU-local-exposure diagnostic and now
rejects three pre-existing entities:
`SqlConnectInfo.hpp`'s `PrefetchDepthDefault` (namespace-scope
`constexpr`, needs `inline constexpr`),
`detail::kDefaultRowArrayFetchDepth`, and
`Reflection::MaxReflectionMemerCount` (in the vendored
`reflection-cpp` dep). None involve this PR's new symbols.
## Performance impact
None. One extra `SQLSetConnectAttr` per connection, and only when the
caller opts in; the default path
adds a single predictable enum comparison. No allocation added on any
hot path.
## Risk assessment
**Low.** The entire feature is inert unless a caller sets `encryption`
to something other than
`DriverDefault`. The one behaviour change that reaches non-opted-in code
is `SetDefaultDataSource()`
delegating to `ToConnectionString()` — verified to produce a
byte-identical string in that case, with a
regression test pinning it.
ABI: `SqlConnectionDataSource` grows a member, so this is a breaking ABI
change for that struct (source
compatible; the defaulted `operator<=>` now also compares the new
field).
0 commit comments