Skip to content

Commit 3d3b7a2

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 3d3b7a2

1 file changed

Lines changed: 90 additions & 0 deletions

File tree

tests/test_param_conversions.cpp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,96 @@ 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.
395+
//
396+
// Also skipped on FB 6: it turns out the FB 6 "Stack overflow" regression is
397+
// not limited to parameterized EXECUTE PROCEDURE — any loop-prepared
398+
// parameterized statement trips it on CI, DML included. Local FB 6
399+
// snapshots happen to behave, but the matrix runners download a newer
400+
// snapshot and crash. The driver fix is exercised on the FB 3 / 4 / 5
401+
// matrix jobs.
402+
TEST_F(ParamConversionsTest, Issue161_SLongToVarcharViaDml) {
403+
SKIP_ON_FIREBIRD6();
404+
405+
ExecIgnoreError("DROP TABLE ODBC_ISSUE161_T");
406+
Commit();
407+
ReallocStmt();
408+
409+
ExecDirect("CREATE TABLE ODBC_ISSUE161_T ("
410+
"ID VARCHAR(20) NOT NULL PRIMARY KEY, "
411+
"NAME VARCHAR(100))");
412+
Commit();
413+
ReallocStmt();
414+
415+
constexpr int kRowCount = 500;
416+
SQLINTEGER idVal = 0;
417+
SQLLEN idInd = sizeof(idVal);
418+
SQLCHAR nameBuf[32] = {};
419+
SQLLEN nameInd = SQL_NTS;
420+
421+
SQLRETURN ret = SQLPrepare(hStmt,
422+
(SQLCHAR*)"UPDATE OR INSERT INTO ODBC_ISSUE161_T (ID, NAME) "
423+
"VALUES (?, ?) MATCHING (ID)", SQL_NTS);
424+
ASSERT_TRUE(SQL_SUCCEEDED(ret))
425+
<< "SQLPrepare failed: " << GetOdbcError(SQL_HANDLE_STMT, hStmt);
426+
427+
ret = SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT,
428+
SQL_C_SLONG, SQL_INTEGER, 0, 0, &idVal, sizeof(idVal), &idInd);
429+
ASSERT_TRUE(SQL_SUCCEEDED(ret))
430+
<< "SQLBindParameter(1) failed: " << GetOdbcError(SQL_HANDLE_STMT, hStmt);
431+
432+
ret = SQLBindParameter(hStmt, 2, SQL_PARAM_INPUT,
433+
SQL_C_CHAR, SQL_VARCHAR, 100, 0, nameBuf, sizeof(nameBuf), &nameInd);
434+
ASSERT_TRUE(SQL_SUCCEEDED(ret))
435+
<< "SQLBindParameter(2) failed: " << GetOdbcError(SQL_HANDLE_STMT, hStmt);
436+
437+
for (int i = 1; i <= kRowCount; ++i) {
438+
idVal = i;
439+
snprintf((char*)nameBuf, sizeof(nameBuf), "name-%d", i);
440+
ret = SQLExecute(hStmt);
441+
ASSERT_TRUE(SQL_SUCCEEDED(ret))
442+
<< "SQLExecute failed on row " << i << ": "
443+
<< GetOdbcError(SQL_HANDLE_STMT, hStmt);
444+
}
445+
Commit();
446+
ReallocStmt();
447+
448+
ExecDirect("SELECT COUNT(*), MIN(CAST(ID AS INTEGER)), MAX(CAST(ID AS INTEGER)) "
449+
"FROM ODBC_ISSUE161_T");
450+
ret = SQLFetch(hStmt);
451+
ASSERT_TRUE(SQL_SUCCEEDED(ret)) << "SQLFetch on aggregate failed";
452+
453+
SQLINTEGER cnt = 0, minId = 0, maxId = 0;
454+
SQLLEN ind = 0;
455+
SQLGetData(hStmt, 1, SQL_C_SLONG, &cnt, sizeof(cnt), &ind);
456+
SQLGetData(hStmt, 2, SQL_C_SLONG, &minId, sizeof(minId), &ind);
457+
SQLGetData(hStmt, 3, SQL_C_SLONG, &maxId, sizeof(maxId), &ind);
458+
SQLCloseCursor(hStmt);
459+
460+
EXPECT_EQ(cnt, kRowCount);
461+
EXPECT_EQ(minId, 1);
462+
EXPECT_EQ(maxId, kRowCount);
463+
464+
ExecDirect("SELECT COUNT(*) FROM ODBC_ISSUE161_T "
465+
"WHERE POSITION(_OCTETS x'00' IN ID) > 0");
466+
ret = SQLFetch(hStmt);
467+
ASSERT_TRUE(SQL_SUCCEEDED(ret)) << "SQLFetch on NUL check failed";
468+
SQLINTEGER nulCount = -1;
469+
SQLGetData(hStmt, 1, SQL_C_SLONG, &nulCount, sizeof(nulCount), &ind);
470+
SQLCloseCursor(hStmt);
471+
EXPECT_EQ(nulCount, 0) << "rows with embedded NUL bytes were stored";
472+
473+
ExecIgnoreError("DROP TABLE ODBC_ISSUE161_T");
474+
Commit();
475+
ReallocStmt();
476+
}
477+
388478
// ===== Already-covered round-trip tests from test_data_types.cpp =====
389479
// (IntegerParamInsertAndSelect, VarcharParamInsertAndSelect,
390480
// DoubleParamInsertAndSelect, DateParamInsertAndSelect,

0 commit comments

Comments
 (0)