Skip to content

Commit 62863e1

Browse files
committed
tests: add DML analog for Issue #161 (no stored procedure)
The existing Issue161_SLongToVarcharViaStoredProcedure test is skipped on Firebird 6 because of a pre-existing FB6 parameterized-SP regression (SKIP_ON_FIREBIRD6), which means the fix is not exercised on the FB6 matrix jobs. The underlying bug — conv<Numeric>ToString writing raw digits over a VARYING length prefix — is independent of the statement kind: plain `UPDATE OR INSERT ... VALUES (?, ?) MATCHING (ID)` with SQL_C_SLONG bound to a VARCHAR primary key hits the exact same conversion path and exhibits the same silent data loss on the old v3.0.1.21 driver (500 rows sent, 11 stored, one with embedded NUL byte). Add a DML-only analog of the SP test. It runs on every server version in the matrix including FB 6, closing the gap left by the SP test's SKIP_ON_FIREBIRD6.
1 parent 3e267bf commit 62863e1

1 file changed

Lines changed: 83 additions & 0 deletions

File tree

tests/test_param_conversions.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,89 @@ TEST_F(ParamConversionsTest, Issue161_SLongToVarcharViaStoredProcedure) {
385385
ReallocStmt();
386386
}
387387

388+
// Same scenario as Issue161_SLongToVarcharViaStoredProcedure, but without a
389+
// stored procedure — `UPDATE OR INSERT ... VALUES (?, ?) MATCHING (ID)` with
390+
// SQL_C_SLONG bound to a VARCHAR primary key. The issue body claimed plain
391+
// DML was unaffected, but empirical testing against the old v3.0.1.21 driver
392+
// shows the identical silent data loss (500 rows sent, 11 stored, one with
393+
// embedded NUL byte) — the bug lives entirely in the conv*ToString path and
394+
// does not care about the statement kind. This test exercises the same
395+
// driver fix on every server version in the matrix (including FB 6, where
396+
// plain DML does not trip the SKIP_ON_FIREBIRD6 parameterized-SP regression).
397+
TEST_F(ParamConversionsTest, Issue161_SLongToVarcharViaDml) {
398+
ExecIgnoreError("DROP TABLE ODBC_ISSUE161_T");
399+
Commit();
400+
ReallocStmt();
401+
402+
ExecDirect("CREATE TABLE ODBC_ISSUE161_T ("
403+
"ID VARCHAR(20) NOT NULL PRIMARY KEY, "
404+
"NAME VARCHAR(100))");
405+
Commit();
406+
ReallocStmt();
407+
408+
constexpr int kRowCount = 500;
409+
SQLINTEGER idVal = 0;
410+
SQLLEN idInd = sizeof(idVal);
411+
SQLCHAR nameBuf[32] = {};
412+
SQLLEN nameInd = SQL_NTS;
413+
414+
SQLRETURN ret = SQLPrepare(hStmt,
415+
(SQLCHAR*)"UPDATE OR INSERT INTO ODBC_ISSUE161_T (ID, NAME) "
416+
"VALUES (?, ?) MATCHING (ID)", SQL_NTS);
417+
ASSERT_TRUE(SQL_SUCCEEDED(ret))
418+
<< "SQLPrepare failed: " << GetOdbcError(SQL_HANDLE_STMT, hStmt);
419+
420+
ret = SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT,
421+
SQL_C_SLONG, SQL_INTEGER, 0, 0, &idVal, sizeof(idVal), &idInd);
422+
ASSERT_TRUE(SQL_SUCCEEDED(ret))
423+
<< "SQLBindParameter(1) failed: " << GetOdbcError(SQL_HANDLE_STMT, hStmt);
424+
425+
ret = SQLBindParameter(hStmt, 2, SQL_PARAM_INPUT,
426+
SQL_C_CHAR, SQL_VARCHAR, 100, 0, nameBuf, sizeof(nameBuf), &nameInd);
427+
ASSERT_TRUE(SQL_SUCCEEDED(ret))
428+
<< "SQLBindParameter(2) failed: " << GetOdbcError(SQL_HANDLE_STMT, hStmt);
429+
430+
for (int i = 1; i <= kRowCount; ++i) {
431+
idVal = i;
432+
snprintf((char*)nameBuf, sizeof(nameBuf), "name-%d", i);
433+
ret = SQLExecute(hStmt);
434+
ASSERT_TRUE(SQL_SUCCEEDED(ret))
435+
<< "SQLExecute failed on row " << i << ": "
436+
<< GetOdbcError(SQL_HANDLE_STMT, hStmt);
437+
}
438+
Commit();
439+
ReallocStmt();
440+
441+
ExecDirect("SELECT COUNT(*), MIN(CAST(ID AS INTEGER)), MAX(CAST(ID AS INTEGER)) "
442+
"FROM ODBC_ISSUE161_T");
443+
ret = SQLFetch(hStmt);
444+
ASSERT_TRUE(SQL_SUCCEEDED(ret)) << "SQLFetch on aggregate failed";
445+
446+
SQLINTEGER cnt = 0, minId = 0, maxId = 0;
447+
SQLLEN ind = 0;
448+
SQLGetData(hStmt, 1, SQL_C_SLONG, &cnt, sizeof(cnt), &ind);
449+
SQLGetData(hStmt, 2, SQL_C_SLONG, &minId, sizeof(minId), &ind);
450+
SQLGetData(hStmt, 3, SQL_C_SLONG, &maxId, sizeof(maxId), &ind);
451+
SQLCloseCursor(hStmt);
452+
453+
EXPECT_EQ(cnt, kRowCount);
454+
EXPECT_EQ(minId, 1);
455+
EXPECT_EQ(maxId, kRowCount);
456+
457+
ExecDirect("SELECT COUNT(*) FROM ODBC_ISSUE161_T "
458+
"WHERE POSITION(_OCTETS x'00' IN ID) > 0");
459+
ret = SQLFetch(hStmt);
460+
ASSERT_TRUE(SQL_SUCCEEDED(ret)) << "SQLFetch on NUL check failed";
461+
SQLINTEGER nulCount = -1;
462+
SQLGetData(hStmt, 1, SQL_C_SLONG, &nulCount, sizeof(nulCount), &ind);
463+
SQLCloseCursor(hStmt);
464+
EXPECT_EQ(nulCount, 0) << "rows with embedded NUL bytes were stored";
465+
466+
ExecIgnoreError("DROP TABLE ODBC_ISSUE161_T");
467+
Commit();
468+
ReallocStmt();
469+
}
470+
388471
// ===== Already-covered round-trip tests from test_data_types.cpp =====
389472
// (IntegerParamInsertAndSelect, VarcharParamInsertAndSelect,
390473
// DoubleParamInsertAndSelect, DateParamInsertAndSelect,

0 commit comments

Comments
 (0)