From 05d2dcbd0c6781f5ccd99a8797074fba0debb03c Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Mon, 7 Sep 2026 02:01:03 -0300 Subject: [PATCH 1/5] tests: exercise column-wise parameter arrays Eleven ArrayBindingTest cases were skipped citing a crash in the column-wise indicator offset. That crash was fixed in 0a7cc1f (SQLINTEGER to SQLLEN), so the skips come out. ParamOperationPtrSkipRows stays skipped because SQL_ATTR_PARAM_OPERATION_PTR is still not honoured, and the reactivated cases get the Firebird 6 guard the row-wise ones already have. Two new cases bind fixed-length C types with BufferLength = 0, the usage the ODBC specification describes for those types: one with the shape reported in #299 (INTEGER and BIGINT with a NULL in the third parameter set), one across DOUBLE, DATE and TIMESTAMP. --- tests/test_array_binding.cpp | 184 ++++++++++++++++++++++++++++++++--- 1 file changed, 173 insertions(+), 11 deletions(-) diff --git a/tests/test_array_binding.cpp b/tests/test_array_binding.cpp index 6e4b7075..4a2bc558 100644 --- a/tests/test_array_binding.cpp +++ b/tests/test_array_binding.cpp @@ -68,7 +68,7 @@ class ArrayBindingTest : public OdbcConnectedTest { // (ported from psqlodbc arraybinding-test.c test 1) // ============================================================================ TEST_F(ArrayBindingTest, ColumnWiseInsert) { - GTEST_SKIP() << "Crashes on vanilla master: sizeof(SQLINTEGER) vs sizeof(SQLLEN) bug in OdbcStatement.cpp line 2891"; + SKIP_ON_FIREBIRD6(); const int ARRAY_SIZE = 100; SQLRETURN ret; @@ -134,7 +134,7 @@ TEST_F(ArrayBindingTest, ColumnWiseInsert) { // 2. Column-wise binding — using SQLPrepare + SQLExecute // ============================================================================ TEST_F(ArrayBindingTest, ColumnWisePrepareExecute) { - GTEST_SKIP() << "Crashes on vanilla master: sizeof(SQLINTEGER) vs sizeof(SQLLEN) bug in OdbcStatement.cpp line 2891"; + SKIP_ON_FIREBIRD6(); const int ARRAY_SIZE = 10; SQLRETURN ret; @@ -257,7 +257,7 @@ TEST_F(ArrayBindingTest, RowWiseInsert) { // 4. Column-wise binding with NULL values // ============================================================================ TEST_F(ArrayBindingTest, ColumnWiseWithNulls) { - GTEST_SKIP() << "Crashes on vanilla master: sizeof(SQLINTEGER) vs sizeof(SQLLEN) bug in OdbcStatement.cpp line 2891"; + SKIP_ON_FIREBIRD6(); const int ARRAY_SIZE = 5; SQLRETURN ret; @@ -316,7 +316,7 @@ TEST_F(ArrayBindingTest, ColumnWiseWithNulls) { // 5. SQL_ATTR_PARAM_OPERATION_PTR — skip individual rows // ============================================================================ TEST_F(ArrayBindingTest, ParamOperationPtrSkipRows) { - GTEST_SKIP() << "Crashes on vanilla master: sizeof(SQLINTEGER) vs sizeof(SQLLEN) bug in OdbcStatement.cpp line 2891"; + GTEST_SKIP() << "Vanilla driver does not properly handle SQL_ATTR_PARAM_OPERATION_PTR"; const int ARRAY_SIZE = 5; SQLRETURN ret; @@ -384,7 +384,7 @@ TEST_F(ArrayBindingTest, ParamOperationPtrSkipRows) { // 6. Large array — column-wise (like psqlodbc's 10000-row test) // ============================================================================ TEST_F(ArrayBindingTest, LargeColumnWiseArray) { - GTEST_SKIP() << "Crashes on vanilla master: sizeof(SQLINTEGER) vs sizeof(SQLLEN) bug in OdbcStatement.cpp line 2891"; + SKIP_ON_FIREBIRD6(); const int ARRAY_SIZE = 1000; SQLRETURN ret; @@ -442,7 +442,7 @@ TEST_F(ArrayBindingTest, LargeColumnWiseArray) { // (from psqlodbc params-batch-exec-test.c) // ============================================================================ TEST_F(ArrayBindingTest, ReExecuteWithDifferentData) { - GTEST_SKIP() << "Crashes on vanilla master: sizeof(SQLINTEGER) vs sizeof(SQLLEN) bug in OdbcStatement.cpp line 2891"; + SKIP_ON_FIREBIRD6(); const int BATCH_SIZE = 5; SQLRETURN ret; @@ -516,7 +516,7 @@ TEST_F(ArrayBindingTest, ReExecuteWithDifferentData) { // SQLSetStmtAttr survive SQLFreeStmt") // ============================================================================ TEST_F(ArrayBindingTest, NewHandleAfterArrayExec) { - GTEST_SKIP() << "Crashes on vanilla master: sizeof(SQLINTEGER) vs sizeof(SQLLEN) bug in OdbcStatement.cpp line 2891"; + SKIP_ON_FIREBIRD6(); const int ARRAY_SIZE = 3; SQLRETURN ret; @@ -658,7 +658,7 @@ TEST_F(ArrayBindingTest, RowWiseMultipleTypes) { // 10. Column-wise binding with UPDATE statement // ============================================================================ TEST_F(ArrayBindingTest, ColumnWiseUpdate) { - GTEST_SKIP() << "Crashes on vanilla master: sizeof(SQLINTEGER) vs sizeof(SQLLEN) bug in OdbcStatement.cpp line 2891"; + SKIP_ON_FIREBIRD6(); // Insert some initial data { SQLHSTMT hStmt2 = SQL_NULL_HSTMT; @@ -717,7 +717,7 @@ TEST_F(ArrayBindingTest, ColumnWiseUpdate) { // 11. Column-wise binding with DELETE statement // ============================================================================ TEST_F(ArrayBindingTest, ColumnWiseDelete) { - GTEST_SKIP() << "Crashes on vanilla master: sizeof(SQLINTEGER) vs sizeof(SQLLEN) bug in OdbcStatement.cpp line 2891"; + SKIP_ON_FIREBIRD6(); // Insert initial data { SQLHSTMT hStmt2 = SQL_NULL_HSTMT; @@ -828,7 +828,7 @@ TEST_F(ArrayBindingTest, GetInfoParamArraySelects) { // 15. Column-wise binding with integer-only (no strings) // ============================================================================ TEST_F(ArrayBindingTest, ColumnWiseIntegerOnly) { - GTEST_SKIP() << "Crashes on vanilla master: sizeof(SQLINTEGER) vs sizeof(SQLLEN) bug in OdbcStatement.cpp line 2891"; + SKIP_ON_FIREBIRD6(); // Recreate table with integer-only columns ExecIgnoreError("DROP TABLE ARRAY_BIND_TEST"); Commit(); @@ -959,7 +959,7 @@ TEST_F(ArrayBindingTest, RowWiseWithOperationPtr) { // 17. Without status/processed pointers (optional per spec) // ============================================================================ TEST_F(ArrayBindingTest, WithoutStatusPointers) { - GTEST_SKIP() << "Crashes on vanilla master: sizeof(SQLINTEGER) vs sizeof(SQLLEN) bug in OdbcStatement.cpp line 2891"; + SKIP_ON_FIREBIRD6(); const int ARRAY_SIZE = 3; SQLRETURN ret; @@ -987,3 +987,165 @@ TEST_F(ArrayBindingTest, WithoutStatusPointers) { Commit(); EXPECT_EQ(CountRows(), ARRAY_SIZE); } + +// ============================================================================ +// 18. Column-wise binding, fixed-length C types with BufferLength = 0 (#299) +// The ODBC specification ignores BufferLength for fixed-length C types, +// so the element stride must come from the C type size. +// ============================================================================ +TEST_F(ArrayBindingTest, ColumnWiseFixedLengthBufferLengthZero) { + SKIP_ON_FIREBIRD6(); + ExecIgnoreError("DROP TABLE ARRAY_BIND_TEST"); + Commit(); + ReallocStmt(); + ExecDirect("CREATE TABLE ARRAY_BIND_TEST (I INTEGER, V BIGINT)"); + Commit(); + ReallocStmt(); + + const int N = 5; + SQLINTEGER ids[N] = {1, 2, 3, 4, 5}; + SQLBIGINT vals[N] = {10, 20, 30, 40, 50}; + SQLLEN id_ind[N] = {0, 0, 0, 0, 0}; + SQLLEN val_ind[N] = {0, 0, SQL_NULL_DATA, 0, 0}; + SQLUSMALLINT status[N] = {}; + SQLULEN nprocessed = 0; + SQLRETURN ret; + + ret = SQLSetStmtAttr(hStmt, SQL_ATTR_PARAM_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLSetStmtAttr(hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)(intptr_t)N, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLSetStmtAttr(hStmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &nprocessed, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLSetStmtAttr(hStmt, SQL_ATTR_PARAM_STATUS_PTR, status, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLPrepare(hStmt, (SQLCHAR*)"INSERT INTO ARRAY_BIND_TEST (I, V) VALUES (?, ?)", SQL_NTS); + ASSERT_TRUE(SQL_SUCCEEDED(ret)) << GetOdbcError(SQL_HANDLE_STMT, hStmt); + + // BufferLength = 0 on both parameters + ret = SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, + ids, 0, id_ind); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLBindParameter(hStmt, 2, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, 0, 0, + vals, 0, val_ind); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLExecute(hStmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)) << GetOdbcError(SQL_HANDLE_STMT, hStmt); + EXPECT_EQ(nprocessed, (SQLULEN)N); + for (int i = 0; i < N; i++) { + EXPECT_EQ(status[i], SQL_PARAM_SUCCESS) << "Row " << i; + } + Commit(); + ReallocStmt(); + + ret = SQLExecDirect(hStmt, (SQLCHAR*)"SELECT I, V FROM ARRAY_BIND_TEST ORDER BY I", SQL_NTS); + ASSERT_TRUE(SQL_SUCCEEDED(ret)) << GetOdbcError(SQL_HANDLE_STMT, hStmt); + SQLINTEGER id = 0; + SQLBIGINT val = 0; + SQLLEN idInd = 0, valInd = 0; + SQLBindCol(hStmt, 1, SQL_C_SLONG, &id, sizeof(id), &idInd); + SQLBindCol(hStmt, 2, SQL_C_SBIGINT, &val, sizeof(val), &valInd); + for (int i = 0; i < N; i++) { + ret = SQLFetch(hStmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)) << "Row " << i; + EXPECT_EQ(id, ids[i]) << "Row " << i; + if (val_ind[i] == SQL_NULL_DATA) { + EXPECT_EQ(valInd, SQL_NULL_DATA) << "Row " << i; + } else { + EXPECT_NE(valInd, SQL_NULL_DATA) << "Row " << i; + EXPECT_EQ(val, vals[i]) << "Row " << i; + } + } + EXPECT_EQ(SQLFetch(hStmt), SQL_NO_DATA); +} + +// ============================================================================ +// 19. Column-wise binding, BufferLength = 0 across DOUBLE, DATE and TIMESTAMP +// ============================================================================ +TEST_F(ArrayBindingTest, ColumnWiseFixedLengthTypesBufferLengthZero) { + SKIP_ON_FIREBIRD6(); + ExecIgnoreError("DROP TABLE ARRAY_BIND_TEST"); + Commit(); + ReallocStmt(); + ExecDirect("CREATE TABLE ARRAY_BIND_TEST (I INTEGER, D DOUBLE PRECISION, DT DATE, TS TIMESTAMP)"); + Commit(); + ReallocStmt(); + + const int N = 3; + SQLINTEGER ids[N] = {1, 2, 3}; + double dbls[N] = {1.5, 2.5, 3.5}; + SQL_DATE_STRUCT dates[N] = {}; + SQL_TIMESTAMP_STRUCT stamps[N] = {}; + SQLLEN ind[N] = {0, 0, 0}; + SQLULEN nprocessed = 0; + SQLRETURN ret; + + for (int i = 0; i < N; i++) { + dates[i].year = (SQLSMALLINT)(2020 + i); + dates[i].month = (SQLUSMALLINT)(1 + i); + dates[i].day = (SQLUSMALLINT)(10 + i); + stamps[i].year = (SQLSMALLINT)(2021 + i); + stamps[i].month = (SQLUSMALLINT)(2 + i); + stamps[i].day = (SQLUSMALLINT)(20 + i); + stamps[i].hour = (SQLUSMALLINT)i; + stamps[i].minute = 30; + stamps[i].second = 45; + stamps[i].fraction = 0; + } + + ret = SQLSetStmtAttr(hStmt, SQL_ATTR_PARAM_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLSetStmtAttr(hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)(intptr_t)N, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLSetStmtAttr(hStmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &nprocessed, 0); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, + ids, 0, ind); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLBindParameter(hStmt, 2, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_DOUBLE, 0, 0, + dbls, 0, ind); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLBindParameter(hStmt, 3, SQL_PARAM_INPUT, SQL_C_TYPE_DATE, SQL_TYPE_DATE, 0, 0, + dates, 0, ind); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = SQLBindParameter(hStmt, 4, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, 0, 0, + stamps, 0, ind); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLExecDirect(hStmt, (SQLCHAR*)"INSERT INTO ARRAY_BIND_TEST (I, D, DT, TS) VALUES (?, ?, ?, ?)", SQL_NTS); + ASSERT_TRUE(SQL_SUCCEEDED(ret)) << GetOdbcError(SQL_HANDLE_STMT, hStmt); + EXPECT_EQ(nprocessed, (SQLULEN)N); + Commit(); + ReallocStmt(); + + ret = SQLExecDirect(hStmt, (SQLCHAR*)"SELECT I, D, DT, TS FROM ARRAY_BIND_TEST ORDER BY I", SQL_NTS); + ASSERT_TRUE(SQL_SUCCEEDED(ret)) << GetOdbcError(SQL_HANDLE_STMT, hStmt); + SQLINTEGER id = 0; + double dbl = 0; + SQL_DATE_STRUCT date = {}; + SQL_TIMESTAMP_STRUCT stamp = {}; + SQLLEN idInd = 0, dblInd = 0, dateInd = 0, stampInd = 0; + SQLBindCol(hStmt, 1, SQL_C_SLONG, &id, sizeof(id), &idInd); + SQLBindCol(hStmt, 2, SQL_C_DOUBLE, &dbl, sizeof(dbl), &dblInd); + SQLBindCol(hStmt, 3, SQL_C_TYPE_DATE, &date, sizeof(date), &dateInd); + SQLBindCol(hStmt, 4, SQL_C_TYPE_TIMESTAMP, &stamp, sizeof(stamp), &stampInd); + for (int i = 0; i < N; i++) { + ret = SQLFetch(hStmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)) << "Row " << i; + EXPECT_EQ(id, ids[i]) << "Row " << i; + EXPECT_DOUBLE_EQ(dbl, dbls[i]) << "Row " << i; + EXPECT_EQ(date.year, dates[i].year) << "Row " << i; + EXPECT_EQ(date.month, dates[i].month) << "Row " << i; + EXPECT_EQ(date.day, dates[i].day) << "Row " << i; + EXPECT_EQ(stamp.year, stamps[i].year) << "Row " << i; + EXPECT_EQ(stamp.month, stamps[i].month) << "Row " << i; + EXPECT_EQ(stamp.day, stamps[i].day) << "Row " << i; + EXPECT_EQ(stamp.hour, stamps[i].hour) << "Row " << i; + EXPECT_EQ(stamp.minute, stamps[i].minute) << "Row " << i; + EXPECT_EQ(stamp.second, stamps[i].second) << "Row " << i; + } + EXPECT_EQ(SQLFetch(hStmt), SQL_NO_DATA); +} From 9348d2f2b5f404934455c58efc8f235f78e55ead Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Mon, 7 Sep 2026 02:01:03 -0300 Subject: [PATCH 2/5] Step column-wise parameter arrays by the C type size when BufferLength is 0 SQLBindParameter stores BufferLength as the element stride of a column-wise parameter array. The ODBC specification ignores BufferLength for fixed-length C types and applications pass 0 there, so every parameter set read element 0: the driver inserted N rows carrying the first set's values and reported SQL_SUCCESS. bindInputOutputParam now falls back to the C type size when the stride is 0, the way bindOutputColumn already does for SQLBindCol. getConciseSize gains SQL_C_GUID, for which it returned the type code (-11) and would have produced a negative stride. Character and binary C types keep BufferLength as their stride, so string arrays behave as before. Fixes #299 --- OdbcDesc.cpp | 3 +++ OdbcStatement.cpp | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/OdbcDesc.cpp b/OdbcDesc.cpp index fdc252e2..ead5816d 100644 --- a/OdbcDesc.cpp +++ b/OdbcDesc.cpp @@ -1566,6 +1566,9 @@ int OdbcDesc::getConciseSize(int type, int length) case SQL_C_UBIGINT: return 8; + case SQL_C_GUID: + return sizeof(SQLGUID); + case SQL_DECIMAL: case SQL_C_NUMERIC: return sizeof(tagSQL_NUMERIC_STRUCT); diff --git a/OdbcStatement.cpp b/OdbcStatement.cpp index 028a40d9..77097a75 100644 --- a/OdbcStatement.cpp +++ b/OdbcStatement.cpp @@ -2744,6 +2744,18 @@ void OdbcStatement::bindInputOutputParam(int param, DescRecord * recordApp) recordApp->fnConv = convert->getAdressFunction( recordApp, record ); + switch ( recordApp->conciseType ) + { + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_BINARY: + break; + + default: + if ( !recordApp->sizeColumnExtendedFetch ) + recordApp->sizeColumnExtendedFetch = ipd->getConciseSize( recordApp->conciseType, recordApp->length ); + } + // if ( convert->isIdentity() ) addBindParam ( param, record, recordApp ); } From f0ccd92bc487584cd74e5f242c11093c0000dfd7 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Mon, 7 Sep 2026 02:01:03 -0300 Subject: [PATCH 3/5] Run the parameter-array executor when SQL_ATTR_PARAMSET_SIZE is set after SQLPrepare The executor is chosen at prepare time from the paramset size in force at that moment. An application that prepares first and sets SQL_ATTR_PARAMSET_SIZE afterwards, which the specification allows and which pyodbc's fast_executemany does, got a single execution of the first parameter set, reported as success. sqlExecute and sqlExecDirect now switch to executeStatementParamArray when the paramset size is greater than 1 at execute time. --- OdbcStatement.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/OdbcStatement.cpp b/OdbcStatement.cpp index 77097a75..426fa0a4 100644 --- a/OdbcStatement.cpp +++ b/OdbcStatement.cpp @@ -1987,7 +1987,11 @@ SQLRETURN OdbcStatement::sqlExecute() enFetch = NoneFetch; releaseResultSet(); parameterNeedData = 0; - retcode = (this->*execute)(); + if ( statement->isActiveModify() && applicationParamDescriptor->headArraySize > 1 + && execute != &OdbcStatement::executeStatementParamArray ) + retcode = executeStatementParamArray(); + else + retcode = (this->*execute)(); } catch (const SQLException &ex) { @@ -2015,7 +2019,11 @@ SQLRETURN OdbcStatement::sqlExecDirect(SQLCHAR * sql, int sqlLength) { enFetch = NoneFetch; parameterNeedData = 0; - retcode = (this->*execute)(); + if ( statement->isActiveModify() && applicationParamDescriptor->headArraySize > 1 + && execute != &OdbcStatement::executeStatementParamArray ) + retcode = executeStatementParamArray(); + else + retcode = (this->*execute)(); } catch (const SQLException &ex) { From b3969d441d42ec6284c89d7bb3d1d85898aaf5f9 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Mon, 7 Sep 2026 02:29:24 -0300 Subject: [PATCH 4/5] tests: activate the SQL_ATTR_PARAM_OPERATION_PTR cases ParamOperationPtrSkipRows and RowWiseWithOperationPtr were skipped because the driver does not honour the operation array. Both get the Firebird 6 guard the other array cases carry and run from here on. --- tests/test_array_binding.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_array_binding.cpp b/tests/test_array_binding.cpp index 4a2bc558..aac04983 100644 --- a/tests/test_array_binding.cpp +++ b/tests/test_array_binding.cpp @@ -316,7 +316,7 @@ TEST_F(ArrayBindingTest, ColumnWiseWithNulls) { // 5. SQL_ATTR_PARAM_OPERATION_PTR — skip individual rows // ============================================================================ TEST_F(ArrayBindingTest, ParamOperationPtrSkipRows) { - GTEST_SKIP() << "Vanilla driver does not properly handle SQL_ATTR_PARAM_OPERATION_PTR"; + SKIP_ON_FIREBIRD6(); const int ARRAY_SIZE = 5; SQLRETURN ret; @@ -894,7 +894,7 @@ TEST_F(ArrayBindingTest, ColumnWiseIntegerOnly) { // 16. Row-wise with SQL_ATTR_PARAM_OPERATION_PTR // ============================================================================ TEST_F(ArrayBindingTest, RowWiseWithOperationPtr) { - GTEST_SKIP() << "Vanilla driver does not properly handle SQL_ATTR_PARAM_OPERATION_PTR"; + SKIP_ON_FIREBIRD6(); const int ARRAY_SIZE = 4; SQLRETURN ret; From d862a80664d87f7566e9a9c4b9681eda61cd6a37 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Mon, 7 Sep 2026 02:29:24 -0300 Subject: [PATCH 5/5] Honour SQL_ATTR_PARAM_OPERATION_PTR in parameter-array execution The operation array was stored in the APD (SQL_DESC_ARRAY_STATUS_PTR) but executeStatementParamArray never read it, so a set marked SQL_PARAM_IGNORE was executed like any other and an application had no way to skip a row short of rebuilding its arrays. The loop now skips ignored sets, leaves their status at SQL_PARAM_UNUSED and does not count them in SQL_ATTR_PARAMS_PROCESSED_PTR. The status array is filled with SQL_PARAM_UNUSED before the loop, so sets that were never reached read as unused, and status elements are addressed by set number instead of through a moving pointer. The processed count is kept per set instead of being written once after the loop, so it includes a failed set as the specification describes. --- OdbcStatement.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/OdbcStatement.cpp b/OdbcStatement.cpp index 426fa0a4..ca4140ff 100644 --- a/OdbcStatement.cpp +++ b/OdbcStatement.cpp @@ -3008,6 +3008,7 @@ SQLRETURN OdbcStatement::executeStatementParamArray() : &rowCount; SQLUSMALLINT *statusPtr = implementationParamDescriptor->headArrayStatusPtr ? implementationParamDescriptor->headArrayStatusPtr : NULL; + SQLUSMALLINT *operationPtr = applicationParamDescriptor->headArrayStatusPtr; int rowSize = applicationParamDescriptor->headBindType; int nCountRow = applicationParamDescriptor->headArraySize; SQLLEN *&headBindOffsetPtr = applicationParamDescriptor->headBindOffsetPtr; @@ -3018,8 +3019,21 @@ SQLRETURN OdbcStatement::executeStatementParamArray() headBindOffsetPtr = &bindOffsetPtrTmp; *rowCountPt = rowNumberParamArray = 0; + if ( statusPtr ) + for ( int n = 0; n < nCountRow; ++n ) + statusPtr[n] = SQL_PARAM_UNUSED; + while ( rowNumberParamArray < nCountRow ) { + if ( operationPtr && operationPtr[rowNumberParamArray] == SQL_PARAM_IGNORE ) + { + bindOffsetPtrTmp += rowSize; + ++rowNumberParamArray; + continue; + } + + ++*rowCountPt; + if ( arrayColumnWiseBinding ) bindOffsetIndColumnWiseBinding = ( bindOffsetPtrTmp + rowNumberParamArray ) * sizeof ( SQLLEN ); @@ -3028,21 +3042,20 @@ SQLRETURN OdbcStatement::executeStatementParamArray() headBindOffsetPtr = bindOffsetPtrSave; convert->setBindOffsetPtrFrom ( applicationParamDescriptor->headBindOffsetPtr, applicationParamDescriptor->headBindOffsetPtr ); if ( statusPtr ) - *statusPtr = SQL_PARAM_ERROR; + statusPtr[rowNumberParamArray] = SQL_PARAM_ERROR; return ret; } statement->executeStatement(); if ( statusPtr ) - *statusPtr++ = ret == SQL_SUCCESS_WITH_INFO ? SQL_PARAM_SUCCESS_WITH_INFO : SQL_PARAM_SUCCESS; + statusPtr[rowNumberParamArray] = ret == SQL_SUCCESS_WITH_INFO ? SQL_PARAM_SUCCESS_WITH_INFO : SQL_PARAM_SUCCESS; bindOffsetPtrTmp += rowSize; parameterNeedData = 1; ++rowNumberParamArray; } - *rowCountPt = rowNumberParamArray; headBindOffsetPtr = bindOffsetPtrSave; convert->setBindOffsetPtrFrom ( applicationParamDescriptor->headBindOffsetPtr, applicationParamDescriptor->headBindOffsetPtr );