Skip to content

Commit 220eb3c

Browse files
committed
tests: cover CHAR target and CHARACTER SET NONE in the rebind matrix
Existing rebind coverage (Issue161_SLongToVarcharViaDmlRebind) hits only one corner of the (target column type × column charset) matrix: VARCHAR under the database's default charset (UTF8 on the CI test databases). Empirical measurements on Windows against FB 5.0.3 show the original pre-PR driver misbehaviour is identical for CHAR and VARCHAR targets and differs across UTF8 vs NONE charsets: target / charset master +1-liner this PR VARCHAR + UTF8 3/4 shapes FAIL 3/4 shapes FAIL 4/4 OK CHAR(N) + UTF8 3/4 shapes FAIL 3/4 shapes FAIL 4/4 OK VARCHAR + NONE 1/4 shape FAIL 4/4 OK 4/4 OK CHAR(N) + NONE 1/4 shape FAIL 4/4 OK 4/4 OK The CHAR and NONE corners have never been exercised by the gtest suite; a regression specific to either would slip past CI today. Refactor: extract a RunIssue161RebindVariant(pkColumnDef, nameColumnDef) helper that runs the full prepare + rebind-per-row + assert body, driven by the column definition strings. Add three new thin tests that call it with the three uncovered matrix corners: Issue161_SLongToCharViaDmlRebind — CHAR(20) UTF8 Issue161_SLongToVarcharViaDmlRebindCharsetNone — VARCHAR(20) NONE Issue161_SLongToCharViaDmlRebindCharsetNone — CHAR(20) NONE The existing Issue161_SLongToVarcharViaDmlRebind is deliberately left intact — it already covers VARCHAR + default(UTF8) via inline code, and refactoring it here would expand the blast radius of this commit. Helper also drops ODBC_ISSUE161_SP before each run so that a crash in the earlier Issue161_*ViaStoredProcedure test cannot leak a dependency that prevents the table DROP. All 7 Issue161 tests pass locally on FB 5.0.3 / CHARSET=UTF8 with the full PR applied. Tests carry SKIP_ON_FIREBIRD6() matching the existing convention.
1 parent d5daa1d commit 220eb3c

1 file changed

Lines changed: 131 additions & 0 deletions

File tree

tests/test_param_conversions.cpp

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,99 @@ class ParamConversionsTest : public OdbcConnectedTest {
3737
std::unique_ptr<TempTable> table_;
3838
int nextId_ = 1;
3939

40+
// Shared body for Issue161 rebind-shape tests across different PK column
41+
// types / charsets. pkColumnDef is the SQL text for the PK column type,
42+
// e.g. "VARCHAR(20)", "CHAR(20) CHARACTER SET UTF8", or
43+
// "VARCHAR(20) CHARACTER SET NONE". The NAME column always follows the
44+
// same definition so the test varies one dimension at a time.
45+
void RunIssue161RebindVariant(const char* pkColumnDef, const char* nameColumnDef) {
46+
// Clean up any leftover SP first (the Issue161_*ViaStoredProcedure
47+
// test above creates ODBC_ISSUE161_SP referencing ODBC_ISSUE161_T,
48+
// and if that test crashes mid-run the DROP TABLE here would
49+
// otherwise fail with a dependency error).
50+
ExecIgnoreError("DROP PROCEDURE ODBC_ISSUE161_SP");
51+
ExecIgnoreError("DROP TABLE ODBC_ISSUE161_T");
52+
Commit();
53+
ReallocStmt();
54+
55+
std::string createSql = std::string("CREATE TABLE ODBC_ISSUE161_T (")
56+
+ "ID " + pkColumnDef + " NOT NULL PRIMARY KEY, "
57+
+ "NAME " + nameColumnDef + ")";
58+
ExecDirect(createSql.c_str());
59+
Commit();
60+
ReallocStmt();
61+
62+
constexpr int kRowCount = 500;
63+
SQLINTEGER idVal = 0;
64+
SQLLEN idInd = sizeof(idVal);
65+
SQLCHAR nameBuf[32] = {};
66+
SQLLEN nameInd = SQL_NTS;
67+
68+
SQLRETURN ret = SQLPrepare(hStmt,
69+
(SQLCHAR*)"UPDATE OR INSERT INTO ODBC_ISSUE161_T (ID, NAME) "
70+
"VALUES (?, ?) MATCHING (ID)", SQL_NTS);
71+
ASSERT_TRUE(SQL_SUCCEEDED(ret))
72+
<< "SQLPrepare failed: " << GetOdbcError(SQL_HANDLE_STMT, hStmt);
73+
74+
for (int i = 1; i <= kRowCount; ++i) {
75+
ret = SQLFreeStmt(hStmt, SQL_RESET_PARAMS);
76+
ASSERT_TRUE(SQL_SUCCEEDED(ret))
77+
<< "SQLFreeStmt(RESET_PARAMS) failed on row " << i << ": "
78+
<< GetOdbcError(SQL_HANDLE_STMT, hStmt);
79+
80+
idVal = i;
81+
snprintf((char*)nameBuf, sizeof(nameBuf), "name-%d", i);
82+
83+
ret = SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT,
84+
SQL_C_SLONG, SQL_INTEGER, 0, 0, &idVal, sizeof(idVal), &idInd);
85+
ASSERT_TRUE(SQL_SUCCEEDED(ret))
86+
<< "SQLBindParameter(1) failed on row " << i << ": "
87+
<< GetOdbcError(SQL_HANDLE_STMT, hStmt);
88+
89+
ret = SQLBindParameter(hStmt, 2, SQL_PARAM_INPUT,
90+
SQL_C_CHAR, SQL_VARCHAR, 100, 0, nameBuf, sizeof(nameBuf), &nameInd);
91+
ASSERT_TRUE(SQL_SUCCEEDED(ret))
92+
<< "SQLBindParameter(2) failed on row " << i << ": "
93+
<< GetOdbcError(SQL_HANDLE_STMT, hStmt);
94+
95+
ret = SQLExecute(hStmt);
96+
ASSERT_TRUE(SQL_SUCCEEDED(ret))
97+
<< "SQLExecute failed on row " << i << ": "
98+
<< GetOdbcError(SQL_HANDLE_STMT, hStmt);
99+
}
100+
Commit();
101+
ReallocStmt();
102+
103+
ExecDirect("SELECT COUNT(*), MIN(CAST(ID AS INTEGER)), MAX(CAST(ID AS INTEGER)) "
104+
"FROM ODBC_ISSUE161_T");
105+
ret = SQLFetch(hStmt);
106+
ASSERT_TRUE(SQL_SUCCEEDED(ret)) << "SQLFetch on aggregate failed";
107+
108+
SQLINTEGER cnt = 0, minId = 0, maxId = 0;
109+
SQLLEN ind = 0;
110+
SQLGetData(hStmt, 1, SQL_C_SLONG, &cnt, sizeof(cnt), &ind);
111+
SQLGetData(hStmt, 2, SQL_C_SLONG, &minId, sizeof(minId), &ind);
112+
SQLGetData(hStmt, 3, SQL_C_SLONG, &maxId, sizeof(maxId), &ind);
113+
SQLCloseCursor(hStmt);
114+
115+
EXPECT_EQ(cnt, kRowCount);
116+
EXPECT_EQ(minId, 1);
117+
EXPECT_EQ(maxId, kRowCount);
118+
119+
ExecDirect("SELECT COUNT(*) FROM ODBC_ISSUE161_T "
120+
"WHERE POSITION(_OCTETS x'00' IN ID) > 0");
121+
ret = SQLFetch(hStmt);
122+
ASSERT_TRUE(SQL_SUCCEEDED(ret)) << "SQLFetch on NUL check failed";
123+
SQLINTEGER nulCount = -1;
124+
SQLGetData(hStmt, 1, SQL_C_SLONG, &nulCount, sizeof(nulCount), &ind);
125+
SQLCloseCursor(hStmt);
126+
EXPECT_EQ(nulCount, 0) << "rows with embedded NUL bytes were stored";
127+
128+
ExecIgnoreError("DROP TABLE ODBC_ISSUE161_T");
129+
Commit();
130+
ReallocStmt();
131+
}
132+
40133
// Insert a value using parameter binding and read it back as a string
41134
std::string insertAndReadBack(const char* colName,
42135
SQLSMALLINT cType, SQLSMALLINT sqlType,
@@ -655,6 +748,44 @@ TEST_F(ParamConversionsTest, Issue161_SLongToVarcharViaDmlDirect) {
655748
ReallocStmt();
656749
}
657750

751+
// Column-type / charset matrix for the rebind shape.
752+
//
753+
// irodushka flagged on FirebirdSQL/firebird-odbc-driver#292 that the original
754+
// fix might only have held for VARCHAR targets under the database's default
755+
// charset (UTF8 on the CI matrix's test databases), and that CHAR targets or
756+
// CHARACTER SET NONE might behave differently. Empirically on Windows
757+
// (FB 5.0.3 / FB master) all three additional corners of the matrix pass
758+
// with this PR; these tests lock that in on the CI matrix (Windows x86/x64,
759+
// Linux x64/arm64, Windows ARM64) so a future regression on any column
760+
// type × charset combination fails loudly.
761+
//
762+
// Coverage before these tests (rebind shape only, default UTF8 DB):
763+
// VARCHAR(20) ✓ Issue161_SLongToVarcharViaDmlRebind
764+
// VARCHAR(20) CHARACTER SET NONE ✗
765+
// CHAR(20) CHARACTER SET UTF8 ✗ (the specific case irodushka tested)
766+
// CHAR(20) CHARACTER SET NONE ✗
767+
768+
TEST_F(ParamConversionsTest, Issue161_SLongToCharViaDmlRebind) {
769+
SKIP_ON_FIREBIRD6();
770+
RunIssue161RebindVariant(
771+
"CHAR(20) CHARACTER SET UTF8",
772+
"CHAR(100) CHARACTER SET UTF8");
773+
}
774+
775+
TEST_F(ParamConversionsTest, Issue161_SLongToVarcharViaDmlRebindCharsetNone) {
776+
SKIP_ON_FIREBIRD6();
777+
RunIssue161RebindVariant(
778+
"VARCHAR(20) CHARACTER SET NONE",
779+
"VARCHAR(100) CHARACTER SET NONE");
780+
}
781+
782+
TEST_F(ParamConversionsTest, Issue161_SLongToCharViaDmlRebindCharsetNone) {
783+
SKIP_ON_FIREBIRD6();
784+
RunIssue161RebindVariant(
785+
"CHAR(20) CHARACTER SET NONE",
786+
"CHAR(100) CHARACTER SET NONE");
787+
}
788+
658789
// ===== Already-covered round-trip tests from test_data_types.cpp =====
659790
// (IntegerParamInsertAndSelect, VarcharParamInsertAndSelect,
660791
// DoubleParamInsertAndSelect, DateParamInsertAndSelect,

0 commit comments

Comments
 (0)