You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A bound NULL parameter is bound with the wrong type whenever it is not the first bound parameter on MS SQL Server, producing a spurious conversion error (e.g. conversion from data type char to varbinary is not allowed) for NULLs into non-char columns (binary, and potentially others). The affected path is SqlStatement::ExecuteWithVariants together with SqlDataBinder<SqlNullType>::InputParameter.
This is the general form of the bug worked around for the query-builder path in #613. #613 stops the query builder from emitting a bound NULL (it renders the SQL literal NULL instead), so the builder path is safe. Any caller that binds a NULL SqlVariantdirectly — ExecuteWithVariants with a null in the vector, or a SqlWildcard/? bound to a null at runtime — still hits this.
Root cause (proven)
SqlDataBinder<SqlNullType>::InputParameter resolves the column's SQL type lazily, while binding:
The MS SQL Server ODBC driver rejects SQLDescribeParam once any parameter on the statement has been bound, returning:
SQLSTATE 07009 — "Invalid Descriptor Index"
So for a NULL at ordinal N > 1, parameters 1..N-1 are already bound by the time the null binder calls SQLDescribeParam for ordinal N → the call fails → sqlType falls back to SQL_C_CHAR → the NULL is bound as a char parameter → SQL Server refuses to assign it to a binary column.
Evidence (standalone ODBC probe, no Lightweight)
Prepared INSERT INTO MITARBEITER (MITARB_NR, NAME, KUERZEL, LOHNKOST_HIST, PW) VALUES (?,?,?,?,?) where PW is varbinary:
When SQLDescribeParam is called
Result
Before any SQLBindParameter
Succeeds for every parameter, incl. PW (typed SQL_VARBINARY, code -3)
After earlier parameters are bound (ExecuteWithVariants order)
Fails, 07009 "Invalid Descriptor Index"
sp_describe_undeclared_parameters (which backs SQLDescribeParam) types the parameter as varbinary(250) correctly on its own — the failure is purely the describe-after-bind ordering, not the column or table shape.
Reproduction
INSERT (or UPDATE) a NULL into a binary column where the null parameter is not the first bound parameter, executed via ExecuteWithVariants (or any direct null bind), against MS SQL Server. The NULL is bound as char and the driver returns the conversion error.
Impact
Silent-until-execution wrong parameter typing for any directly-bound runtime NULL after the first parameter on MS SQL Server.
Manifests as a data-dependent write failure — only when the null lands in a column that rejects a char NULL (binary today; a latent risk for other strict types).
Resolve the null parameter's type before any parameter is bound, then bind with the discovered type. Options:
Pre-describe pass in ExecuteWithVariants: before the bind loop, for each argument that is null, call SQLDescribeParam (all describes happen before any bind, so none fails), cache the ordinal→SQL-type map, and have the null binder consume the cached type via the SqlDataBinderCallback.
Callback-supplied type hint: extend SqlDataBinderCallback so a caller that already knows the column type (e.g. a schema-driven gateway) can hand it to the null binder, avoiding SQLDescribeParam entirely.
SQL_ATTR_ENABLE_AUTO_IPD: evaluate whether enabling auto-populated IPD lets the driver supply the type without an explicit SQLDescribeParam after binds (needs verification per driver).
Option 1 is self-contained and driver-correct; option 2 is the most efficient when the type is already known.
Add a Lightweight ODBC test: ExecuteWithVariants inserting a NULL into a binary column at a non-first parameter position on MS SQL Server, asserting the row is written with the column NULL.
#613 is the correct, minimal fix for the query-builder path (a null constant is SQL NULL, not a bound parameter) and unblocks the consumer that surfaced this. This issue tracks the remaining general-case defect for directly-bound NULLs.
Summary
A bound NULL parameter is bound with the wrong type whenever it is not the first bound parameter on MS SQL Server, producing a spurious conversion error (e.g.
conversion from data type char to varbinary is not allowed) for NULLs into non-char columns (binary, and potentially others). The affected path isSqlStatement::ExecuteWithVariantstogether withSqlDataBinder<SqlNullType>::InputParameter.This is the general form of the bug worked around for the query-builder path in #613. #613 stops the query builder from emitting a bound NULL (it renders the SQL literal
NULLinstead), so the builder path is safe. Any caller that binds a NULLSqlVariantdirectly —ExecuteWithVariantswith a null in the vector, or aSqlWildcard/?bound to a null at runtime — still hits this.Root cause (proven)
SqlDataBinder<SqlNullType>::InputParameterresolves the column's SQL type lazily, while binding:https://github.com/LASTRADA-Software/Lightweight/blob/master/src/Lightweight/DataBinder/SqlNullValue.hpp#L38-L59
ExecuteWithVariantsbinds parameters in ordinal order in a loop:https://github.com/LASTRADA-Software/Lightweight/blob/master/src/Lightweight/SqlStatement.cpp#L659-L667
The MS SQL Server ODBC driver rejects
SQLDescribeParamonce any parameter on the statement has been bound, returning:So for a NULL at ordinal N > 1, parameters 1..N-1 are already bound by the time the null binder calls
SQLDescribeParamfor ordinal N → the call fails →sqlTypefalls back toSQL_C_CHAR→ the NULL is bound as a char parameter → SQL Server refuses to assign it to a binary column.Evidence (standalone ODBC probe, no Lightweight)
Prepared
INSERT INTO MITARBEITER (MITARB_NR, NAME, KUERZEL, LOHNKOST_HIST, PW) VALUES (?,?,?,?,?)wherePWisvarbinary:SQLDescribeParamis calledSQLBindParameterPW(typedSQL_VARBINARY, code -3)ExecuteWithVariantsorder)07009"Invalid Descriptor Index"sp_describe_undeclared_parameters(which backsSQLDescribeParam) types the parameter asvarbinary(250)correctly on its own — the failure is purely the describe-after-bind ordering, not the column or table shape.Reproduction
INSERT (or UPDATE) a NULL into a binary column where the null parameter is not the first bound parameter, executed via
ExecuteWithVariants(or any direct null bind), against MS SQL Server. The NULL is bound as char and the driver returns the conversion error.Impact
Proposed fix
Resolve the null parameter's type before any parameter is bound, then bind with the discovered type. Options:
ExecuteWithVariants: before the bind loop, for each argument that is null, callSQLDescribeParam(all describes happen before any bind, so none fails), cache the ordinal→SQL-type map, and have the null binder consume the cached type via theSqlDataBinderCallback.SqlDataBinderCallbackso a caller that already knows the column type (e.g. a schema-driven gateway) can hand it to the null binder, avoidingSQLDescribeParamentirely.SQL_ATTR_ENABLE_AUTO_IPD: evaluate whether enabling auto-populated IPD lets the driver supply the type without an explicitSQLDescribeParamafter binds (needs verification per driver).Option 1 is self-contained and driver-correct; option 2 is the most efficient when the type is already known.
Add a Lightweight ODBC test:
ExecuteWithVariantsinserting a NULL into a binary column at a non-first parameter position on MS SQL Server, asserting the row is written with the column NULL.Relationship to #613
#613 is the correct, minimal fix for the query-builder path (a null constant is SQL
NULL, not a bound parameter) and unblocks the consumer that surfaced this. This issue tracks the remaining general-case defect for directly-bound NULLs.