Skip to content

[C++][FlightRPC][ODBC] SQLDescribeCol writes only 2 of 8 bytes of column_size for numeric/decimal columns #50560

Description

@vikrantpuppala

Describe the bug, including details regarding any error messages, version, and platform.

In the Arrow Flight SQL ODBC driver, SQLDescribeCol returns garbage in the
upper bytes of the column_size (a.k.a. ColumnSizePtr) output argument for
numeric / decimal columns. Only the low 2 bytes are written; the upper 6 bytes
are left uninitialized.

Root cause — a write-width mismatch.

DescriptorRecord::precision is declared SQLSMALLINT (2 bytes) in
cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.h:

SQLSMALLINT precision = 0;   // ~line 58
...
SQLSMALLINT scale = 0;       // ~line 60

ODBCDescriptor::GetField(SQL_DESC_PRECISION, ...) forwards to the GetAttribute
template in cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.cc
(~line 417):

case SQL_DESC_PRECISION:
  GetAttribute(record.precision, value, buffer_length, output_length);
  break;

and that template (cpp/src/arrow/flight/sql/odbc/odbc_impl/attribute_utils.h)
copies exactly sizeof(T) bytes — T being deduced from the field, i.e.
SQLSMALLINT (2 bytes). The buffer_length argument is ignored for the
write:

template <typename T, typename O>
inline void GetAttribute(T attribute_value, SQLPOINTER output, O output_size,
                         O* output_len_ptr) {
  if (output) {
    T* typed_output = reinterpret_cast<T*>(output);  // reinterprets the caller's pointer
    *typed_output = attribute_value;                 // writes only sizeof(T) = 2 bytes
  }
  ...
}

But in SQLDescribeCol
(cpp/src/arrow/flight/sql/odbc/odbc_api.cc, ~line 1584) the numeric path
passes the SQLULEN* column_size_ptr (8 bytes) directly and claims
sizeof(SQLULEN):

case SQL_DOUBLE: {
  ird->GetField(column_number, SQL_DESC_PRECISION, column_size_ptr,
                sizeof(SQLULEN), nullptr);
  break;
}

GetAttribute then reinterprets the SQLULEN* as a SQLSMALLINT* and writes
only 2 bytes. The upper 6 bytes of the caller's SQLULEN remain
uninitialized garbage.
An application that reads the full 8-byte column_size
(the correct ODBC type for ColumnSizePtr) sees a wildly wrong value for every
DECIMAL / NUMERIC / integer / REAL / FLOAT / DOUBLE column.

Two related, lower-severity smells at the decimal_digits sites. The
SQL_DESC_SCALE path (~line 1606) and the datetime/interval SQL_DESC_PRECISION
path (~line 1624) pass sizeof(SQLULEN) (8) as the buffer size even though
decimal_digits_ptr is a SQLSMALLINT* (2 bytes) and the underlying field is a
2-byte SQLSMALLINT. These do not corrupt memory today (the write width is
2 bytes, matching the 2-byte target), but the claimed buffer size is wrong and
misleading. They should pass sizeof(SQLSMALLINT).

Trace of the affected call sites (all in odbc_api.cc::SQLDescribeCol):

  1. SQL_DESC_PRECISION → column_size_ptr (numeric/decimal) — the real bug:
    2-byte write into an 8-byte SQLULEN, upper 6 bytes garbage.
  2. SQL_DESC_SCALE → decimal_digits_ptr (exact numeric) — wrong sizeof, no
    corruption.
  3. SQL_DESC_PRECISION → decimal_digits_ptr (datetime / interval-with-seconds)
    — wrong sizeof, no corruption.

Introduced by

PR #48052 (GH-47724, "[C++][FlightRPC][ODBC] implement
SQLDescribeCol"). The bug is still present on main.

Component(s)

C++, FlightRPC

Activity

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions