tests: cover every SQL column type's ddl2cpp mapping on all three DBMS - #579
Merged
Conversation
The ddl2cpp column-type mapping was only covered two ways, both partial: `CxxModelPrinterTests.cpp` feeds `CxxModelPrinter::MakeType` a hand-written `SqlSchema::Column`, so it cannot notice a type that fails to survive the DDL or the catalog round trip; and the one end-to-end check in CI runs ddl2cpp over the Chinook schema, which exercises only the handful of types Chinook uses. Issue #191 proposed a DDL script for the missing types but noted it would be pinned to one dialect. Take the route the issue suggests instead: drive the round trip from C++ through the dialect-agnostic migration query builder, read each column back out of the *live* catalog via `SqlSchema::ReadAllTables`, and assert the type `MakeType` generates. The whole file is therefore dialect-agnostic and runs unchanged against SQLite, MS SQL Server and PostgreSQL. Covered: tinyint, smallint, integer, bigint, decimal, real, double, boolean, date, time, datetime, timestamp, char, varchar, text, binary, varbinary, nchar, nvarchar, guid; nullable vs. not-null; auto-increment, GUID and VARCHAR primary keys. Where a backend stores something other than what was asked for, the deviation is recorded as a `DialectException` carrying the reason, so the per-DBMS divergences are written down in one place and continuously verified. Two of them are marked KNOWN DEFECT rather than dressed up as correct behaviour: - PostgreSQL `double precision` generates `float`. The schema reader's float fixup matches only the type names `float`/`real`, so PostgreSQL's `float8` misses it and an 8-byte column silently narrows to float32. - MS SQL Server `Timestamp{}` generates `Light::SqlDynamicBinary<8>`, because `TIMESTAMP` on SQL Server is a synonym for `rowversion` — a server-generated binary counter, not a point in time. Closes #191 Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Applying /code-review findings on this branch. INFO() was used as the unbraced substatement of an if, so the Catch2 ScopedMessage it creates was destroyed at the end of that if -- before the assertion it was meant to annotate ran. Every dialect-exception reason was therefore dropped from the failure report, in both the primary-key check and the main probe-table loop. Extract a FailureContext(testCase, serverType) helper that builds the message unconditionally and hoist a single scoped INFO above the assertions. Confirmed against a deliberately broken expectation: the failure now prints the dialect reason the previous form suppressed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Windows SQLite3 leg failed 7 assertions across 4 cases: every character column generated `Light::SqlDynamicUtf16String<N>` where the table expected the narrow `Light::SqlAnsiString<N>` (and `SqlDynamicAnsiString<0>` for GUID and unbounded TEXT). The expectation was not wrong about SQLite, it was wrong about the driver. SQLite has one TEXT storage class, and which C++ type ddl2cpp emits for it is decided by the ODBC driver build: the unixODBC drivers on the Linux and macOS legs report the narrow ODBC types, while the Windows build is the Unicode one and reports SQL_WCHAR / SQL_WVARCHAR - the same divergence already recorded for the PostgreSQL Unicode driver. The macOS leg passing while Windows failed is exactly that split. Route every SQLite character expectation through a `SqliteText(narrow, wide)` selector, and record the two columns that only deviate on a wide-reporting driver (varcharColumn, varcharPK) as dialect exceptions of their own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Closes #191
What & why
The ddl2cpp column-type mapping was covered two ways, both partial:
src/tests/CxxModelPrinterTests.cppfeedsCxxModelPrinter::MakeTypea hand-writtenSqlSchema::Column, so it cannot notice a column type that fails to survive the DDL or the catalog round trip.ddl2cppjob) runs ddl2cpp over the Chinook schema, which exercises only the handful of column types Chinook happens to use.Issue #191 proposed a DDL script for the missing types, and then noted the problem with it: a hand-written script is pinned to one dialect, so it could only ever run against SQLite.
This PR takes the route the issue suggests instead.
src/tests/Ddl2CppColumnTypeTests.cppnever speaks SQL: every probe table is created through the dialect-agnostic migration query builder, read back out of the live catalog viaSqlSchema::ReadAllTables, and only then handed toCxxModelPrinter::MakeType— the very function ddl2cpp emits record members with. The whole file runs unchanged against SQLite, MS SQL Server and PostgreSQL.Note on the issue's other question ("would it make sense to move the actual logic of ddl2cpp into the core library?"): that has already happened —
CxxModelPrinterlives insrc/Lightweight/Tools/, andsrc/tools/ddl2cpp.cppis only a CLI front end. So no library restructuring was needed here.Coverage added
Column types:
tinyint,smallint,integer,bigint,decimal(10,2),real,double,boolean,date,time,datetime,timestamp,char(8),varchar(30),text,binary(16),varbinary(16),nchar(8),nvarchar(30),guid.Column properties: nullable vs. not-null (
std::optionalwrapping), auto-increment primary key, GUID primary key, VARCHAR primary key.7 test cases / 88 assertions per database.
Per-DBMS divergences are recorded, not hidden
Where a backend stores something other than what was asked for, the deviation is a
DialectExceptioncarrying its reason, so the divergences are written down in one place and continuously verified. The reason string is printed as Catch2INFOwhen an assertion fails, so a future breakage explains itself.Legitimate divergences captured: PostgreSQL has no 1-byte integer (
Tinyint→int16_t); the PostgreSQL Unicode driver reports every character column as its wide ODBC type; SQLite has a single dynamically typed TEXT class, soCHAR(n)padding and the Unicode distinction are both lost;BYTEAcarries no declared length; the SQL Server formatter emitsVARBINARY(n)forBinary{n}; the schema reader deliberately widensfloat/realtoReal{53}.Both are asserted as-is and labelled
KNOWN DEFECTin the test, so the behaviour is recorded rather than going unnoticed. I did not fix either — each is a separate change with its own risk, and the second involves choosing a replacement type. Happy to split off follow-up issues/PRs if you want them handled.PostgreSQL
double precisiongeneratesfloat. The schema reader's float fixup (src/Lightweight/SqlSchema.cpp:1439) matches only the dialect type namesfloat/real. PostgreSQL names its typesfloat4/float8, sofloat8misses the fixup and the driver-reported width narrows the column to 4 bytes.PostgreSqlFormatter::ColumnTypealready carries a comment about exactly this hazard on the write side ("or restore silently narrows it to float32"); the read side still has it. Generated records therefore lose precision on everydouble precisioncolumn.MS SQL Server
Timestamp{}generatesLight::SqlDynamicBinary<8>.SqlServerFormatter::ColumnTypeemitsTIMESTAMPverbatim, but on SQL ServerTIMESTAMPis a deprecated synonym forrowversion— an 8-byte, server-generated, non-writable binary counter, not a point in time. So a migration asking for a timestamp gets a read-only binary column, and ddl2cpp then generates a binary member for it.DATETIME2is the natural replacement.Testing
clang-debug(PEDANTIC + ASan + UBSan)sqlite3,mssql2022(Docker, 16.00.4265),postgres(Docker 16.4)clang-tidyNOLINTused); two findings fixed at the source (cppcoreguidelines-pro-type-member-init,readability-qualified-auto)clang-formatCompilers: Clang only, locally. The
gcc-releasepreset is Linux-gated and CMake refuses it on this macOS host (Cannot use disabled configure preset), so perAGENT.mdI am flagging that explicitly rather than claiming coverage I do not have — CI'subuntu_build_cc_matrix/dbms_test_matrixlegs are the GCC check for this branch. The change is a single self-contained test translation unit with no namespace-scope entities in public headers, so the modules and reflection configurations are unaffected.TEXT, whose reported length is the driver'sMaxLongVarcharSizerather than a property of the column) is asserted by shape viaStartsWith, not by exact number, so it will not break on a different psqlODBC build.