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

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions