Repository navigation
feat(model): run long date and timestamp IN lists on SQL Server through STRING_SPLIT - #4329
Conversation
…gh STRING_SPLIT
A statement whose date or timestamp IN lists push it past SQL Server's
parameter limit threw Wheels.TooManyParameters. On compatibility level
130+, such a list now binds as one STRING_SPLIT parameter, each value cast
to the type the driver binds a cf_sql_timestamp / cf_sql_date as
(datetime2(7) / date), so it matches exactly the rows the same list
matches bound one parameter per value. Casting to the column's own type
would match more rows than that on DATETIME and SMALLDATETIME columns.
The bind types are read once per datasource with SQL_VARIANT_PROPERTY and
kept in application.wheels; a driver that binds other types keeps the
error. Values must be yyyy-mm-dd with an optional HH:nn[:ss[.lll]], or
CFML's {ts}/{d} form; time lists and other forms keep the error.
Fixes #4318
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
Review @ aae4cc3: source looks good, no blocking findings. Final verdict after matrix 37278068958 and PR CIDifferential spec (
The probe fails safe
Strict parsing, no locale dependence
Findings
On the matrix I'll read |
Wheels Test Results221 735 tests 216 876 ✅ 1h 27m 14s ⏱️ Results for commit ed691cc. ♻️ This comment has been updated with latest results. |
Final @ aae4cc3: APPROVE on code and on this matrix, gated on a develop merge and a fresh matrixMatrix 37278068958 (all 32 legs read)
PR CI: 26 pass, 3 skipping, 0 fail, 0 pending. Gate: the branch isn't up to date with develop.
|
Final: APPROVE @ ed691cc (develop merged, including #4325; matrix 37281004808, all 32 legs read; PR CI read)Delta since aae4cc3
Matrix
PR CI: 26 pass, 3 skipping, 0 fail, 0 pending. The gate from my previous comment is met. No open findings. The INFOs (a failed probe cached until reload; |
The SQL Server adapter reads two facts once per datasource and keeps them in application.wheels: the database's compatibility level (STRING_SPLIT, #4321) and the types the driver binds dates as (#4329). A failed read was kept too, so one transient error disabled STRING_SPLIT, or date-list splitting, until the next reload. A failed read is no longer kept. Its time is recorded, the answer is "not supported" until $probeRetrySeconds() (60) have passed, and then the probe runs again; a good read is kept as before. The back-off stops a broken probe from running before every long statement. Signed-off-by: Peter Amiri <peter@alurium.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #4318
Summary
On SQL Server, a statement whose IN lists push it past the 2,100-parameter limit binds its largest lists as one
STRING_SPLITparameter each (#4103 / #4321). Date and timestamp lists were left out and still threwWheels.TooManyParameters. They now split too, matching exactly the rows the same list matches when it fits.What the driver binds
Before choosing a cast, I measured what each engine's driver sends for a date or time parameter (
SQL_VARIANT_PROPERTYof the bound value, SQL Server 2022):cf_sql_timestampdatetime2(7)datetime2(7)datetime2(7)cf_sql_datedatedatedatecf_sql_timedatetimetime(7), fraction droppedWhat that means for the cast. Because the normal path compares a
datetime2(7)value, it misses fractional values onDATETIMEcolumns and anything but whole minutes onSMALLDATETIMEcolumns. Casting split values to the column's own type would match more rows than today. So the split casts to the bound type instead:CAST(value AS DATETIME2(7));CAST(value AS DATE).In a probe over every column type, those matched the normal path on 54 of 54 cases on both Lucee 7 and Adobe 2023. The normal-path behaviour itself is filed separately as #4326 and #4327; they will move the split cast together with the normal one.
Changes (
MicrosoftSQLServerModel.cfc)$stringSplitDateCasts(dataSource)reads the bind types once per datasource and keeps them inapplication.wheels.sqlServerDateBindTypes, next to the compatibility level, so a reload reads them again. The read itself is in$readDateBindTypes().$dateCastsFor()gives a cast only for the bind types this is proven to mirror:datetime2/7anddate/0. A driver that binds anything else keepsWheels.TooManyParameters. No engine names are involved.$isoDateTimeValue()acceptsyyyy-mm-dd, optionally withHH:nn,:ssand.lll, or CFML's{ts '…'}/{d '…'}form. Each is written asyyyy-mm-ddTHH:nn:ss.lll, which SQL Server reads the same way under every language andDATEFORMATsetting.Z, other fraction lengths, or invalid dates. Those depend on the engine's own date parsing, which the split can't mirror.$inListsToSplit,$stringSplitPartsand$stringSplitListtake an optionaldateCastsargument. Without it, dates aren't split, as before.Wheels.TooManyParametersadvice, the v4-2-0 query-builder guide, and anaddedchangelog fragment.Tests
New
vendor/wheels/tests/specs/model/sqlServerDateStringSplitSpec.cfc(SQL Server only; it callsskip()elsewhere). It uses a table with one column of each type:date,time(7),datetime,smalldatetime,datetime2(7),datetime2(3)anddatetimeoffset(7). The stored values include.003,.123and.999fractions,23:59:59.997and.999, midnight, a NULL row, and:29/:30seconds.whereIn,whereNotInand awherestring IN return the same ids for a short list (one parameter per value) as for the same list padded past the limit. The padded list can only run if it really splits; otherwise it throwsWheels.TooManyParameters.mm/dd/yyyydates past the limit still throwWheels.TooManyParameters.sqlServerStringSplitSpec's date case now expects an ISO date list past the limit to run, instead of throwing.RED (these specs on develop's adapter, Lucee 7 + SQL Server): the differential, date-object, far-past-limit and ISO-date specs fail, and the unit specs error because the helpers don't exist yet.
GREEN:
modeldirectory on SQL Server (pass / fail / error)sqlServerDateStringSplitSpecinternalspecs: 113 passed;modeldirectory: 1375 / 0 / 0. The new spec compiles there and skips.🤖 Generated with Claude Code