Files
Fuchs_Intranet/Fuchs/Docs/Decisions/0036-database-project-build-is-the-tsql-gate.md
Stefan c741535aec
Playwright Tests / test (push) Canceled after 0s
feat: Implement mfr truncation fix and enhance invoice handling
- Added a new SQL script to address the "String or binary data would be truncated" error in mfr sync by widening the Note fields in service objects and companies to NVARCHAR(MAX).
- Updated the table types and associated stored procedures to accommodate the changes, ensuring compatibility with existing data.
- Queued a re-sync for previously rejected rows due to truncation issues.
- Created application logins for fuchs_fds and fuchs_web with placeholder passwords for initial setup.
- Introduced functions for invoice backdating and invoice date retrieval to streamline invoice processing.
- Developed a password generation function with customizable parameters for length and character types.
- Added a random number generation function to support password creation.
2026-09-30 16:56:08 +02:00

86 lines
5.0 KiB
Markdown

---
status: Accepted
date: 2026-09-30
applyTo:
- "Fuchs_Database/**"
supersededBy: ""
---
# 0036 — The database project build is the T-SQL gate
## Context
A rewrite of `fds__getDatevExports` added a `JOIN @inv as i` next to `fds__invoices as iv`
and left an unqualified `[file_guid]` in the select list. SQL Server rejects that statement
with Msg 209 (ambiguous column name) — but only when it runs, and the working-tree SQL runs
nowhere: the schema lives in the `Fuchs_Database` SSDT project, the dev database only gets
what is explicitly published to it, and the xUnit suite cannot bind T-SQL. The defect reached
code review as the only check.
The SSDT build would have caught it: it reported the line as `SQL71502` ("unresolved reference
… or ambiguous"). Nobody saw that because
- the project did not build at all — the two `CREATE USER … FOR LOGIN` files referenced logins
that were not in the model, which SSDT treats as error `SQL71501`;
- the genuine warning sat among ~160 identical `SQL71502` warnings caused by references to
`sys.*`, `INFORMATION_SCHEMA`, `sp_executesql`, `sp_getapplock` (no `master` reference) and
to `ocms_fn_generatePassword`, an OCMS helper the project never contained;
- a warning does not fail anything.
The same sweep found that `ocms_fn_generatePassword` (and its dependency `ocms_fn_randbetween`)
is missing on the dev database as well, so every id-generating function (`fds__fn_invoice_id`,
`fds__fn_reminder_id`, `fis_fn_id`, …) fails there with Msg 4121.
## Decision
1. `FuchsDatabase.sqlproj` references the `master` system dacpac
(`$(DacPacRootPath)\…\SqlServer\170\SQLSchemas\master.dacpac`), so system objects bind.
2. Everything the SQL references is in the project. The OCMS helpers `ocms_fn_generatePassword`
and `ocms_fn_randbetween` are vendored copies of the OCMSsharp `ocms_mt_database` files.
`Security/Logins.sql` scripts the two application logins with a placeholder,
`MUST_CHANGE` password so the users bind; real passwords stay in Key Vault and SSDT never
compares passwords, so existing logins are untouched on publish.
3. `TreatTSqlWarningsAsErrors` is on. Suppressed: `71501` (dynamic SQL / cross-database
references) and `71558` (identifier differs only by case — harmless under the `CI` model
collation). Every other T-SQL warning, in particular `SQL71502`, fails the build.
4. Building the database project is part of the workflow whenever `Fuchs_Database/**` changes
(`msbuild Fuchs_Database/FuchsDatabase.sqlproj -p:Configuration=Debug`, from the Visual
Studio MSBuild — `dotnet build` cannot build the legacy SSDT project format). A red database
build is a failure like a red C# build.
5. The C# side of the contract is a test: `Fuchs.Tests/SqlContractTests` scans every
`[dbo].[…]` named in C# strings (`Fuchs`, `Fuchs_DataService`) and checks it against the
SSDT files — the object exists, every `EXECUTE` binds (no unknown names, no surplus
positional arguments, every parameter without a default supplied), every function call
passes the declared argument count, and every `mfr__updt__*` proc declares the
`@tblname, @referencetable, @tgtid` signature `FdsMfrClient` calls dynamically.
## Consequences
- An unqualified column that becomes ambiguous, a typo in a column name, or a call to a
function that is not in the project is a build error before review, not a Msg 209/4121 at
a customer.
- `SELECT *` inside `EXISTS (…)` over joined tables trips `SQL71502` in SSDT even though SQL
Server accepts it; write `SELECT 1` there (done for `fds__fn_IntermediateIsAllocatedToOther`
and `fis_admin_testGroupMembership`).
- New procedures that use `sys.*`/`sp_*` need no suppression any more. A new reference that is
genuinely outside the model (another database, dynamic SQL) is suppressed per statement or
by adding the object to the project — never by widening `SuppressTSqlWarnings` for `71502`.
- A proc signature change that breaks a C# caller fails `dotnet test`, not the first request.
Only literal SQL is covered: dynamically built names (`FuchsVisualization`'s report procs,
resolved from `sys.parameters` at runtime) and result-set column names are not.
- The build proves binding, not behaviour: publishing to the dev database (`SqlPackage
/Action:Publish` with `ExcludeObjectTypes=Logins;Users;Permissions;RoleMembership`, or the
schema compare) and exercising the code path remains the runtime check.
- The dev database needs the two `ocms_*` helpers published before any id-generating path
works there.
## Alternatives considered
- **Run the changed procedures against the dev database before review.** Needs a publish of
every working-tree change first, and does not cover code paths the reviewer does not think
to exercise; the compile-time check is free and total.
- **Suppress `SQL71502` and keep the build warning-free that way.** Removes exactly the signal
that catches this class of bug.
- **A C#-side test that parses the SQL for unqualified columns.** A parser-level heuristic
without a schema cannot decide ambiguity; the SSDT model can.