Skip to content

Column-wise binding with SQL_ATTR_ROW_BIND_OFFSET_PTR writes every row of a rowset to the same address #306

Description

@fdcastel

Summary

With SQL_ATTR_ROW_ARRAY_SIZE > 1, column-wise binding (the default, SQL_ATTR_ROW_BIND_TYPE = SQL_BIND_BY_COLUMN) and a SQL_ATTR_ROW_BIND_OFFSET_PTR set, every row of a rowset is written to the same address: the bound address plus the offset, with no per-row stride. SQLFetch returns SQL_SUCCESS, the rows-fetched counter says 4 and the status array holds four SQL_ROW_SUCCESS, but the application's arrays contain only the last row of the rowset, in their first (shifted) element; the other elements are untouched. Silent data loss.

Row-wise binding with the same offset pointer works. Column-wise binding without an offset pointer works.

Environment

Windows x64, master 35fae3e (with #303 applied), Firebird 5.0.3, Microsoft driver manager. The code involved is platform-independent.

Reproduction

BlockCursorTest.ColumnWiseBindingWithOffset in #305, currently GTEST_SKIPped with this defect as the reason. Shape: six-row UNION ALL, SQL_ATTR_ROW_ARRAY_SIZE = 4, SQLBindCol(1, SQL_C_SLONG, values, 0, indicators), offset = 8 × sizeof(SQLLEN) bytes (64 on x64, so the rows should land at values[16..19] and indicators[8..11]).

Observed after the first SQLFetch:

rows_fetched = 4, status = 0,0,0,0
values[16] = 4              rows 1..4 were all written here
values[17..19] = -1         untouched
indicators[8] = 4, indicators[9..11] untouched

Second fetch with the offset changed to 0 (expected values[0..1] = 5, 6): values[0] = 6, values[1] untouched.

Cause

OdbcStatement::getSchemaFetchData() selects the row-wise fetch branch whenever a bind-offset pointer is set, not only for row-wise binding:

bool getSchemaFetchData(){ return applicationRowDescriptor->headBindType || applicationRowDescriptor->headBindOffsetPtr; }

That branch of fetchData() (and of its twin in sqlFetchScrollCursorStatic()) uses one byte offset for every column and advances it by headBindType per row:

bindOffsetPtrTmp += rowBindType;   // 0 for SQL_BIND_BY_COLUMN

Column-wise arrays need the other branch, where returnDataFromExtendedFetch() steps each column by its own element size (sizeColumnExtendedFetch * row) and the indicators by sizeof(SQLLEN). That branch never adds the application's offset, though, and it reuses the aliased headBindOffsetPtr as its row counter, so it cannot simply be selected when an offset pointer is present.

Expected

The offset is added to every bound data and indicator address, and the rows still step by element size (ODBC, SQL_ATTR_ROW_BIND_OFFSET_PTR).

Suggested fix

Take the row-wise branch only when headBindType != 0. In the column-wise branch, read the application's offset once at the start of the fetch, start the row counter at 0, and add the offset to both bases in returnDataFromExtendedFetch(): bindOffsetPtrTo = offset + sizeColumnExtendedFetch * row, bindOffsetPtrIndTo = offset + row * sizeof(SQLLEN). Same change in the static-cursor twin. Then unskip the test in #305.

Related: #299 is the same class of defect on the parameter side.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions