4.8 KiB
4.8 KiB
status, date, applyTo, supersededBy
| status | date | applyTo | supersededBy | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Accepted | 2026-09-16 |
|
0026 — Dashboard widget SQL is set-based (no per-row scalar UDFs); fds__emaillog.config is a JSON contract
Context
Two dashboard widgets were failing in the DB log (fds__debuglog, 2026-09-16):
wdg_invoices_overdue_sumtimed out after the widget command timeout of 90 s. Its SQL called[dbo].[fds__fn_InvoicePaymentAmount](Id)once per row offds__fn_unpaidInvoices(). That scalar UDF declares a table variable, so SQL Server 2019 cannot inline it (sys.sql_modules.is_inlineable = 0) and re-executes the multi-statement TVFfds__fn_bankingtransactions— a full pass over ~11 k banking rows plus aSTRING_SPLITcross-apply over the settings heap — for every invoice: ≈0.5 s × 187 unpaid invoices ≈ 100 s measured.wdg_unbez_akontohad the same pattern (≈5 s, hidden only bydaycache = 1). The same total computed set-based (one TVF pass,GROUP BY InvId, joined to the invoices) takes < 0.5 s and returns the identical figure.wgd_lastEmailsraised JSON text is not properly formatted.fds_wgd_lastEmailsparsesfds__emaillog.configwithOPENJSON/JSON_QUERY, butProcessWebComService— the replacement for the legacy mailer — wroteconfig = ''on every path. One such row was enough to error the whole widget, and the audit trail had lost recipient/subject/attachment names for every email sent through the new service.
Widget definitions are data (fis_widgets.sql), not schema, so there is no compile-time check
that a widget's SQL is cheap or that the columns it parses exist.
Decision
- Widget SQL and the functions backing it are set-based. Per-invoice payment totals come from the
inline TVF
[dbo].[fds__fn_InvoicePaymentAmounts]()(one banking pass, grouped byInvId) or from the[PaymentAmount]/[OpenAmount]columnsfds__fn_unpaidInvoices()now exposes. The scalarfds__fn_InvoicePaymentAmount(_full)remains for single-invoice callers (invoice/reminder preparation, per-rowpaymentstatus = 'p'cells in the invoice lists) but must not be called per row from a widget or any other query that iterates a whole invoice set. fds__emaillog.configis a JSON contract, written byProcessWebComService.BuildAuditConfigin the legacy mailer's shape —{subject, from, to[], cc[], bcc[], attachments[]}with addresses as"Name" <email>and attachments as file names only (never the body, never attachment bytes) — on every audit path (success, API error, exception, invalid address, service disabled). SQL consumers of the column must still tolerate non-JSON text (IIF(ISJSON(x)=1, x, N'{}')before parsing), becauseconfigisNOT NULLand older rows/other writers may hold''.- Widget-data migrations live in
Fuchs_Database/Scripts/as idempotent, dated T-SQL files (<None Include>in the sqlproj, not part of the schema build) and are run once per environment after the schema deploy they depend on.
Consequences
wdg_invoices_overdue_sum101.9 s → 0.45 s,wdg_unbez_akonto4.7 s → 0.4 s,wgd_lastEmailserror → 15 ms (dev, 2026-09-16). The widget command timeout stays at 90 s; it is a guard, not a budget.- Adding a scalar UDF call inside a widget's
SELECT/WHEREoverfds__invoicesor thefds__fn_unpaidInvoices()set is a regression of this decision. Prefer exposing a set-based column on the TVF, as done forPaymentAmount. ProcessWebComServiceTestsassert the config contract on every audit path; changing the shape requires updatingfds_wgd_lastEmailsin the same change.- The
Scripts/2026-09-16_fis_widgets_set_based_payment_amounts.sqlmigration must be run on Production after the schema deploy (it is a no-op once applied).
Alternatives considered
- Raise the widget
CommandTimeout— masks an O(N) re-scan; the cost grows with every unpaid invoice and every banking upload, and it would still block a dashboard load for minutes. - Convert
fds__fn_InvoicePaymentAmountinto an inlineable scalar UDF — impossible while it needs a TVP (fds__tt__idlist_vchar) to call the banking TVF; rewriting the banking TVF as an inline TVF is a larger change to a function shared with the banking-assignment UI. - Fix the bad
fds__emaillogrow and leave the widget function strict — the next''row (any writer, any environment) would take the widget down again; tolerance belongs in the reader.