Repository navigation
fix(model): compare SQL Server date and time conditions in the column's type - #4333
Conversation
…'s type Every engine binds cf_sql_timestamp as DATETIME2(7), and SQL Server then converts a DATETIME or SMALLDATETIME column exactly, so a value with a fraction of a second never equalled what the column stored in 1/300-second (or one-minute) steps. timestamps() creates DATETIME columns on SQL Server, so createdAt = <a saved value> could match nothing. cf_sql_time bound as DATETIME on Lucee (an error against a TIME column), not at all on BoxLang, and without its fraction on Adobe. A condition param now carries its operator, and the SQL Server adapter casts: - =, <>, IN and NOT IN on a DATETIME / SMALLDATETIME column to that type, so the value rounds the way the stored value did. Ranges keep the exact comparison, and DATETIME2 / DATE columns are unchanged; - any comparison on a TIME column, bound as text, to TIME(7). An IN list becomes one CAST per value; the parameter count is unchanged. $temporalComparisonType() names the type for each column. Fixes #4326 Fixes #4327 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
…mporal-conditions
… type A long IN list on a DATETIME or SMALLDATETIME column now splits with the column's own CAST, as the same list does when it fits, so the two paths match the same rows (sqlServerDateStringSplitSpec). A long TIME list splits too, cast to TIME(7) like a time condition; it used to keep Wheels.TooManyParameters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
…mporal-conditions
Review @ 2895e57: source looks good, one MEDIUM on the docs claim. Final verdict after matrix 37293563373 and PR CIMechanism
orch1's checks
Finding
INFOs
On the matrix I'll read |
…e out rows Comparing at the column's precision lets = and IN match rows they never matched, and makes <> and NOT IN leave out a row whose stored value the rounded value equals: createdAt <> '...10:00:00.001' no longer returns a row stored as 10:00:00.000. The fragment and upgrade guide now say so, and a spec pins the <> / NOT IN case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
Wheels Test Results222 279 tests 217 180 ✅ 1h 23m 8s ⏱️ Results for commit e5ae6be. ♻️ This comment has been updated with latest results. |
Matrix 37294384312 @ dd7c565: not approvable yet (one MUST-FIX, RustCFML)The delta itself is good. The fragment and guide now state the The 32 engine × database legs are all green.
MUST-FIX: the RustCFML lane fails, which reddens the matrix gate and the PR's "RustCFML (Linux)" check.
PR CI: 25 pass, 1 fail (RustCFML (Linux), the same cause), 3 skipping. I'll re-read the RustCFML lane, the matrix gate and the SQL Server legs on the next head. |
…tCFML RustCFML treats a date object as a simple value whose text drops the milliseconds, so $timeText() read it as a date-time string and lost them. A date-time string is now cut to its time only when it carries a fraction; any other date goes through TimeFormat() and Millisecond(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
Final: APPROVE @ e5ae6be (matrix 37298981849, all 32 legs read, plus the RustCFML lane and the gate; PR CI read)Delta since dd7c565. The MUST-FIX is fixed.
Matrix
PR CI: 26 pass (including RustCFML (Linux)), 3 skipping, 0 fail, 0 pending. No open findings. |
Summary
Fixes #4326. Fixes #4327.
On SQL Server, a condition compares in the parameter's type, and that type depends on the engine:
cf_sql_timestampasDATETIME2(7). SQL Server then converts aDATETIME/SMALLDATETIMEcolumn exactly, so a value with a fraction of a second never equals what the column stored in 1/300-second (or one-minute) steps.timestamps()createsDATETIMEcolumns on SQL Server, socreatedAt = <a saved value>could match nothing.cf_sql_timebinds asDATETIMEon Lucee, which errors against aTIMEcolumn (The data types time and datetime are incompatible). BoxLang doesn't bind it at all (long overflow). Adobe binds it without the fraction, so only whole seconds match.The fix: compare in the column's own type. The SQL Server adapter's
$performQuery()hook rewrites a condition parameter intoCAST(? AS <type>), the same mechanism #4190 uses for wide decimals. The WHERE parameter already carries the column's nativedataType. One line inmodel/sql.cfmnow passes its operator along too.DATETIME/SMALLDATETIME=,<>/!=,IN,NOT INCAST(? AS DATETIME)/SMALLDATETIME: the value rounds the way the stored value didDATETIME/SMALLDATETIME<,<=,>,>=,BETWEEN)TIMEHH:mm:ss.fffffff),CAST(? AS TIME(7)), which holds anyTIME(n)exactlyDATETIME2/DATE/DATETIMEOFFSETCASTper value, so the parameter count is unchanged.DATETIMEbreaksDATETIME2columns.$temporalComparisonType(dataType)names the type for each column.sqlServerDateStringSplitSpecis the guard. A longTIMElist now splits too, cast toTIME(7); it used to keepWheels.TooManyParameters.Wheels.TooManyParametersadvice no longer list times as unsupported.Risk. The value is now compared at the column's precision. So an
=orINcondition with a fractional value can now match the row that holds it, and a<>orNOT INcondition now leaves that row out, as SQL Server does with a literal.createdAt <> '2026-01-02 10:00:00.001'used to return a row stored as10:00:00.000and now doesn't, because.001rounds to.000in aDATETIME. A spec pins this case.<>/NOT IN.)queryExecuteandparameterize=falseare unaffected; SQL Server already converts an inline literal to the column's type.23:59:59.999rounds to the next midnight onDATETIME, so it equals a stored00:00:00.000.Docs
changelog.d/4326-sqlserver-datetime-equality.changed.md.changelog.d/4327-sqlserver-time-conditions.fixed.md.DATETIMEconditions match fractional values".Tests
model/sqlServerTemporalConditionSpecuses a SQL Server table withDATETIME,SMALLDATETIME,DATETIME2(7),DATETIME2(3)andTIME(7)columns. The rows hold the #4318 probe values: whole seconds, fractions DATETIME can't hold, a value that rounds to the next day, and half-minute boundaries. The checks:=through the model path matches exactly whatcol = CAST(? AS <column type>)matches, for every column and value;IN/NOT INlists match value by value;DATETIME2(7)result);<>andNOT INwith.001leave out the row stored as.000: one row fewer than the oldDATETIME2(7)comparison, and the same as the column-type comparison;RED on develop (only the spec and its model asset added), Lucee 7 × SQL Server: 7 checks fail or error.
=andINmatch 0 rows.The data types time and datetime are incompatible in the equal to operator.GREEN at the final head, after merging develop (with #4318 and #4332):
All 0 fail / 0 error. Before that merge, these also passed with 0 fail / 0 error:
operatorkey on the other adapters.Adobe 2023 × SQL Server was not run locally. The local Adobe 2023 engine never becomes healthy (
The Runtime service is not available), as during #4103, so no request reaches Wheels; the compat matrix covers that leg.The guides build passes.
🤖 Generated with Claude Code