Hello everybody,
we are working on a software project using ODBC as SQL Server Data Access technology. The projects application builds complex SQL Statements dynamically.
At some point we need to know if a column of such dynamical statement is nullable or not. Unfortunately,SQLDescribeParamalways returns “1” for its “Nullable” parameter.
I can reproduce this in a small C++ example (Windows Console App, please see end of this post.):
SQLDescribeParam returns “1” for
Nullable[0] but SQLExecuteclaims
23000 Errorcode = 515
[Microsoft][SQL Server Native Client 11.0][SQL Server]Der Wert NULL kann in die
mycontentnotnull-Spalte, MS_C266513.dbo.Tab_266513-Tabelle nicht eingef³gt werden.
Die Spalte lõsst NULL-Werte nicht zu. Fehler bei INSERT.
Sorry for german language. Something like: The column does not allow NULL-Values
Alternatively I tried to fetch the information withSQLGetDescField.Same result. The IPD-Descriptor says the column “mycontentnotnull“ is nullable:
IPD
---
1 Records
Record 1
SQL_DESC_CASE_SENSITIVE : 0
SQL_DESC_CONCISE_TYPE : 12
SQL_DESC_DATETIME_INTERVAL_CODE : 0
SQL_DESC_DATETIME_INTERVAL_PRECISION : 50
SQL_DESC_FIXED_PREC_SCALE : 0
SQL_DESC_LENGTH : 50
SQL_DESC_NAME : ''
SQL_DESC_NULLABLE : 1
SQL_DESC_OCTET_LENGTH : 0
SQL_DESC_PARAMETER_TYPE : 1
SQL_DESC_PRECISION : 50
SQL_DESC_SCALE : 0
SQL_DESC_TYPE : 12
SQL_DESC_TYPE_NAME : 'varchar'
SQL_DESC_UNNAMED : 1
SQL_DESC_UNSIGNED : 1
The Microsoft documentation forSQLSetDescField(Set, notGet) says:
„In IPDs, this field is always set to SQL_NULLABLE because dynamic parameters are always nullable and
cannot be set by an application.“
Astonishing, this behavior changed from SQL Server 2008 to SQL Server 2012. If you useeither the DBMS or‘the ODBC driver’ before SQL Server 2012, the nullable Parameter is returned as expected: “0”. Also the windows build in driver called “SQL Server” (sqlsrv32.dll) returns “0”, even on Windows 10 and
even when connected to SQL Server 2012, 2014, 2016 or 2017. But the “ODBC Driver for SQL Server” (msodbcsql13.dll) and the “SQL Server Native Client” (sqlncli11.dll) returning “1” with those SQL Server.
My questions now are:
Am I doing something wrong?
Do you feel, this is an issue in the mentioned SQL Server
components, too?
Is Microsoft making things too easy when maintaining “this
field is always set to SQL_NULLABLE because dynamic parameters
are always nullable“
For me, the nullable information is significant important. Is
there a change to get an official statement from Microsoft?
Thank you for your patience.
Finally the C++ example.
And thank you in advice for any suggestions.
#include "pch.h"
#include "Windows.h"
#include "Sql.h"
#include "Sqlext.h"
#include <iostream>
#include <assert.h>
// script to create table:
//
// CREATE TABLE[dbo].[Tab_266513](
// [mycontentnotnull][varchar](50) NOT NULL
// )
// GO
#define DSN_CONNECT_STRING L"DSN=SQL2017;uid=camos;pwd=camos;"
#define SQL_STATEMENT L"INSERT INTO TAB_266513(MYCONTENTNOTNULL) VALUES(?)"
wchar_t UniBuffer[2048];
void ODBC_Err (HENV hODBC_, const char *location, HDBC hdlDBC_, HSTMT hdlStatement_)
{
SQLSMALLINT RecNumber, TextLength;
SQLINTEGER ErrCode = 0;
RETCODE retcode;
wchar_t Sqlstate[10];
std::wcout << location << ":\n";
RecNumber = 0;
while (SQL_SUCCEEDED(retcode = SQLGetDiagRecW(SQL_HANDLE_ENV, hODBC_, ++RecNumber, Sqlstate, &ErrCode, UniBuffer, sizeof(UniBuffer) / sizeof(wchar_t), &TextLength)))
std::wcout << Sqlstate << " Errorcode = " << ErrCode << "\n" << UniBuffer << "\n";
if (hdlDBC_ != SQL_NULL_HDBC) {
RecNumber = 0;
while (SQL_SUCCEEDED(retcode = SQLGetDiagRecW(SQL_HANDLE_DBC, hdlDBC_, ++RecNumber, Sqlstate, &ErrCode, UniBuffer, sizeof(UniBuffer) / sizeof(wchar_t), &TextLength)))
std::wcout << Sqlstate << " Errorcode = " << ErrCode << "\n" << UniBuffer << "\n";
}
if (hdlStatement_ != SQL_NULL_HSTMT) {
RecNumber = 0;
while (SQL_SUCCEEDED(retcode = SQLGetDiagRecW(SQL_HANDLE_STMT, hdlStatement_, ++RecNumber, Sqlstate, &ErrCode, UniBuffer, sizeof(UniBuffer) / sizeof(wchar_t), &TextLength)))
std::wcout << Sqlstate << " Errorcode = " << ErrCode << "\n" << UniBuffer << "\n";
}
exit(1);
}
int main () {
HENV hODBC = SQL_NULL_HENV;
HSTMT hStmt = SQL_NULL_HSTMT;
unsigned long ulODBCVersion = SQL_OV_ODBC3;
HDBC DBC_Handle = SQL_NULL_HDBC;
SQLSMALLINT nCount = 0;
SQLSMALLINT NumParams = 0;
if (!SQL_SUCCEEDED (SQLAllocHandle (SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hODBC)))
ODBC_Err (hODBC, "SQLAllocHandle SQL_HANDLE_ENV", SQL_NULL_HDBC, SQL_NULL_HSTMT);
if (!SQL_SUCCEEDED (SQLSetEnvAttr (hODBC, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) ulODBCVersion, 0)))
ODBC_Err (hODBC, "SQLSetEnvAttr SQL_ATTR_ODBC_VERSION", SQL_NULL_HDBC, SQL_NULL_HSTMT);
if (!SQL_SUCCEEDED (SQLAllocHandle (SQL_HANDLE_DBC, hODBC, &DBC_Handle)))
ODBC_Err (hODBC, "SQLAllocHandle SQL_HANDLE_DBC", DBC_Handle, SQL_NULL_HSTMT);
if (!SQL_SUCCEEDED (SQLDriverConnectW (DBC_Handle, NULL, (SQLWCHAR *) DSN_CONNECT_STRING, (SQLSMALLINT) wcslen (DSN_CONNECT_STRING),
UniBuffer, sizeof (UniBuffer) / sizeof (wchar_t), &nCount, SQL_DRIVER_NOPROMPT)))
ODBC_Err (hODBC, "SQLDriverConnectW", DBC_Handle, SQL_NULL_HSTMT);
if (!SQL_SUCCEEDED (SQLAllocHandle (SQL_HANDLE_STMT, DBC_Handle, &hStmt)))
ODBC_Err (hODBC, "SQLAllocHandle SQL_HANDLE_STMT", DBC_Handle, hStmt);
if (!SQL_SUCCEEDED (SQLPrepareW (hStmt, (SQLWCHAR *) SQL_STATEMENT, wcslen (SQL_STATEMENT))))
ODBC_Err (hODBC, "SQLPrepareW", DBC_Handle, hStmt);
if (!SQL_SUCCEEDED (SQLNumParams (hStmt, &NumParams)))
ODBC_Err (hODBC, "SQLNumParams", DBC_Handle, hStmt);
assert (NumParams == 1);
SQLSMALLINT DataType[1], DecimalDigits[1], Nullable[1];
SQLULEN ParamSize[1];
for (SQLUSMALLINT i = 0; i < NumParams; i++)
if (!SQL_SUCCEEDED (SQLDescribeParam (hStmt, i + 1, DataType + i, ParamSize + i, DecimalDigits + i, Nullable + i)))
ODBC_Err (hODBC, "SQLDescribeParam", DBC_Handle, hStmt);
const wchar_t Empty[] = L"";
SQLLEN Empty_Length = (Nullable[0] == SQL_NULLABLE) ? SQL_NULL_DATA : SQL_NTS;
if (!SQL_SUCCEEDED (SQLBindParameter (hStmt, 1, SQL_PARAM_INPUT, SQL_C_WCHAR, DataType[0], ParamSize[0], DecimalDigits[0], (SQLPOINTER) Empty, 0, &Empty_Length)))
ODBC_Err (hODBC, "SQLBindParameter 1", DBC_Handle, hStmt);
SQLRETURN RetCode = SQLExecute (hStmt);
assert (RetCode != SQL_NEED_DATA);
if (RetCode != SQL_NO_DATA && !SQL_SUCCEEDED (RetCode))
ODBC_Err (hODBC, "SQLExecute", DBC_Handle, hStmt);
}