Repository navigation
feat(model): run IN lists past SQL Server's parameter limit as one STRING_SPLIT parameter - #4321
Conversation
…ING_SPLIT SQL Server binds at most 2100 parameters per statement and Wheels binds each IN list value as its own parameter, so a statement past the adapter limit of 2097 was refused with Wheels.TooManyParameters (#3906). On compatibility level 130+ (STRING_SPLIT), such a statement now binds its largest IN lists, until it fits, as one parameter each: IN (SELECT CAST(value AS <type>) FROM STRING_SPLIT(?, NCHAR(31))). Integer and decimal values are validated first and CAST (never TRY_CAST, which turns an empty value into 0), so NOT IN keeps its semantics; decimals cast to DECIMAL(38, s) exactly. String lists are compared as they are. Statements within the limit keep their SQL. Date and time lists, and databases below level 130, keep Wheels.TooManyParameters. The level is read once per datasource and kept in application.wheels, so a reload re-reads it. Fixes #4103 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
…fail Wheels.TooManyParameters on SQL Server now says that long integer, decimal, string and uniqueidentifier lists run as one STRING_SPLIT parameter on compatibility level 130+, and that date and time lists (#4318) or an older level still need batching. The query builder guide covers long IN lists on Oracle and SQL Server. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
Review @ e662b45: source looks good, one LOW. Final verdict after matrix 37270864773SQL shape
NOT IN stays exact
Empty strings and nulls
Compatibility-level read
Injection
Findings
On the matrix I'll read |
Only lists of integers, plain decimals, strings and uniqueidentifiers run as one STRING_SPLIT parameter. The advice, guide note and fragment now say so, and that any other list (dates, times, float, real, bit, text or binary values, or numbers such as +5, .5 or 1E5) still needs batching. A spec pins the message for a float list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
Wheels Test Results219 303 tests 214 888 ✅ 1h 15m 2s ⏱️ Results for commit 0cd3310. |
Final: APPROVE @ 0cd3310 (matrix 37271475480, all 32 legs read)Delta since e662b45. My LOW is fixed.
Matrix
No open findings. INFOs from the source review stand: a failed compatibility read caches 0 until reload, which is safe; and a |
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>
Summary
Closes #4103.
SQL Server binds at most 2,100 parameters per statement, and Wheels binds every IN-list value as its own parameter. Since #3906, a query over the adapter limit (2097) is refused with
Wheels.TooManyParameters. This PR lets such a query run on SQL Server 2016 and later, with every value still bound.What changes
Only over the limit. A statement that fits keeps its exact SQL and plan. Over the limit, the largest IN lists are converted, one at a time, until the statement fits:
Each converted list is bound as one parameter.
Gated on compatibility level 130+,
STRING_SPLIT's requirement.SELECT compatibility_level FROM sys.databases WHERE name = DB_NAME()and kept in the application's Wheels settings, so a reload re-reads it.Wheels.TooManyParametersstays.CAST, notTRY_CAST. Integer and decimal values are validated in CFML first, thenCAST.TRY_CAST('' AS INT)is0on SQL Server, which would silently turn an empty value into id 0.NOT INkeeps exactly the meaning of a literal list.Types
DECIMAL(38, s), exact (as in fix(model): bind decimals beyond 15 significant digits exactly #4190)UNIQUEIDENTIFIER, after a format checkNot covered. Every other list keeps
Wheels.TooManyParameters. That means dates and times (follow-up feat(model): bind long date and time IN lists on SQL Server through STRING_SPLIT #4318), float, real, bit, text and binary values, and numbers written another way, such as+5,.5,1E5or more than 38 digits.Delimiter.
NCHAR(31). A value containing it is refused with a clear error, as the WHERE masker does for its own markers.Clearer error. On SQL Server,
Wheels.TooManyParameters's details now name the lists that run this way (integers, plain decimals up to 38 digits, strings, uniqueidentifiers). They also say that any other list, or a database below level 130, still needs batching.$tooManyParametersAdvice(), which the SQL Server adapter extends.Hook. The SQL Server
$performQuery()override from fix(model): bind decimals beyond 15 significant digits exactly #4190. It runs before the wide-decimal cast, so a long, wide decimal list goes through the split path, which is exact.Docs
changelog.d/4103-sqlserver-long-in-lists.added.md.Tests
model/sqlServerStringSplitSpecchecks:INandNOT IN);Wheels.TooManyParameters, with the new advice naming the lists that are combined;Wheels.TooManyParameters, with the new advice;The 5,000-key
whereIn/whereNotInchecks inwhereLargeIntegerListSpecandinListLimitsSpecnow expect SQL Server to run the list instead of refusing it.Local results, at the current develop base
Adobe 2023 × SQL Server was not run locally. The local Adobe 2023 engine never became healthy (
The Runtime service is not available), so no request reached Wheels; the compat matrix covers that leg. The guides site build passes.🤖 Generated with Claude Code