Skip to content

Implementation descriptors: records empty until the first bind or execute, raw type fields, 32-bit length writes; SQLColAttribute type fields #316

Description

@fdcastel

Summary

After SQLPrepare, reading a result column through the IRD (SQLGetDescField with SQL_DESC_TYPE, SQL_DESC_CONCISE_TYPE, SQL_DESC_NAME, ...) returns SQL_SUCCESS with the record's constructor defaults: type 99 (SQL_C_DEFAULT) and an empty name, for every column type. SQLDescribeCol and SQLColAttribute on the same column answer correctly. Once the column has been bound with SQLBindCol, or the statement executed, the descriptor answers correctly as well.

Environment

Windows x64, master 35fae3e with #305 and #315 applied (the code involved is not touched by either), Firebird 5.0.3, Microsoft driver manager. The code involved is platform-independent.

Reproduction

SELECT CAST(1 AS INTEGER) AS A FROM RDB$DATABASE, SQLPrepare, SQLGetStmtAttr(SQL_ATTR_IMP_ROW_DESC), then SQLGetDescField(hIrd, 1, SQL_DESC_TYPE, ...) and SQL_DESC_NAME:

before the descriptor read SQL_DESC_TYPE SQL_DESC_NAME
prepare only 99 empty
prepare, then SQLDescribeCol 99 empty
prepare, then SQLBindCol 4 A
prepare, then SQLExecute 4 A

Same 99 for BIGINT, VARCHAR and a table's SMALLINT column, while SQLDescribeCol and SQLColAttribute(SQL_DESC_TYPE) give -5, -9 and 5. SQL_DESC_CONCISE_TYPE behaves the same way.

Cause

SQLPrepare sizes the IRD (setDefaultImplDesc allocates headCount records, so SQL_DESC_COUNT is right) but does not fill the records: each starts with type = conciseType = SQL_C_DEFAULT and no name (DescRecord.cpp). defFromMetaDataOut fills a record when the column is bound (bindOutputColumn) or when the statement executes. SQLDescribeCol and SQLColAttribute read the statement metadata directly and do not fill the record. OdbcDesc::sqlGetDescField (and sqlGetDescRec) fetch the record and read its fields as they are.

Expected

The IRD describes the result set from SQLPrepare on; SQLDescribeCol and SQLColAttribute are defined as views onto it. Reading an undefined IRD record through the descriptor API should fill it from the metadata first, the way SQLBindCol does.

Found while writing the tests for #307.

Second part: the type fields once the record is filled

Even after the record is filled (after SQLBindCol on master), the descriptor getters hand out the record's raw fields, and the record keeps the concise SQL type in type and the converter's C type in conciseType. Measured on the #314 driver after SQLBindCol: SQLColAttribute (TYPE / CONCISE_TYPE) versus SQLGetDescField (TYPE / CONCISE_TYPE / DATETIME_INTERVAL_CODE) and SQLGetDescRec (type / subtype):

column SQLColAttribute SQLGetDescField SQLGetDescRec
INTEGER 4 / 4 4 / -16 / 0 4 / 0
BIGINT -5 / -5 -5 / -25 / 0 -5 / 0
NUMERIC(9,2) 2 / 2 2 / -16 / 0 2 / 0
VARCHAR(5) -9 / -9 -9 / -8 / 0 -9 / 0
DATE 91 / 91 91 / 91 / 0 91 / 0
TIMESTAMP 93 / 93 93 / 93 / 0 93 / 0
BLOB SUB_TYPE TEXT -1 / -1 -1 / 1 / 0 -1 / 0

SQL_DESC_CONCISE_TYPE is the C type (SQL_C_SLONG, SQL_C_SBIGINT, SQL_C_WCHAR, SQL_C_CHAR), right for the datetime types only because the C and SQL codes coincide. ODBC expects the concise SQL type there, SQL_DATETIME (9) in SQL_DESC_TYPE for datetime columns with SQL_DESC_DATETIME_INTERVAL_CODE 1, 2 or 3, and the same pair from SQLGetDescRec.

Full sweep of the metadata paths

A probe over seventeen Firebird column types (SMALLINT, INTEGER, BIGINT, NUMERIC(9,2), DECIMAL(18,4), FLOAT, DOUBLE PRECISION, CHAR(5), VARCHAR(5), DATE, TIME, TIMESTAMP, text and binary BLOB, BOOLEAN, CHAR(4) and VARCHAR(4) CHARACTER SET OCTETS), comparing SQLDescribeCol, SQLColAttribute, SQLGetDescField and SQLGetDescRec on the IRD before and after SQLBindCol, SQLCopyDesc, and SQLDescribeParam against the IPD, in CHARSET=UTF8 and NONE, on the #314 driver. Beyond the two parts above it found:

  • The IPD has both defects as well: records at the constructor defaults until the parameter is bound (type 99, nullable 0, scale 0 while SQLDescribeParam says SQL_NULLABLE and 2), then the raw type fields.
  • SQLColAttribute(SQL_DESC_TYPE) answers the concise code for datetime columns (91 for a DATE) and SQLColAttribute(SQL_DESC_DATETIME_INTERVAL_CODE) is rejected with HY091.
  • SQLGetDescField writes SQL_DESC_LENGTH, SQL_DESC_OCTET_LENGTH and SQL_DESC_DISPLAY_SIZE as 32-bit integers; on a 64-bit build the upper half of the application's SQLLEN keeps its previous content (a variable preset to -1 reads back as -4294967291 for a length of 5).
  • SQLGetDescRec on an unprepared IRD answers HY091 where SQLGetDescField answers HY007 since Keep SQL_ATTR_KEYSET_SIZE apart from the rowset size; HY007 for an unprepared IRD (#307) #314.

Conventions the sweep shows and that are not defects of the descriptor code, listed so they are not rediscovered:

  • SQL_DESC_LENGTH and SQL_DESC_OCTET_LENGTH for numeric and datetime columns are crossed between the two APIs: SQLColAttribute gives display size / precision (6 / 5 for SMALLINT), the descriptor gives precision / display size (5 / 6); the specification's transfer octet length would be 2 (SQL_SMALLINT), 6 (SQL_TYPE_DATE), 16 (SQL_TYPE_TIMESTAMP).
  • CHARACTER SET OCTETS columns are reported as SQL_CHAR / SQL_VARCHAR, not SQL_BINARY / SQL_VARBINARY (a 16-byte one is SQL_GUID).
  • SQL_DESC_PRECISION of a BLOB is INT_MAX through SQLColAttribute and -1 through the descriptor, whose field is an SQLSMALLINT.
  • Text columns follow their own character set: a VARCHAR column created without one in a NONE database is SQL_VARCHAR even on a CHARSET=UTF8 connection.

Linux: SQLColAttribute and SQLColAttributeW were never exported

Found while the CI ran the sweep's tests under unixODBC. The two entry points declare their numeric attribute as SQLPOINTER on every platform but 64-bit Windows (an #ifdef _WIN64 mirroring Microsoft's header), while unixODBC's sql.h declares SQLLEN *NumericAttribute. The compiler treats the driver's definition as an overload and gives it C++ linkage: an ELF scan of the Linux CI build shows _Z15SQLColAttributePvttS_sPsS_ and _Z16SQLColAttributeWPvttS_sPsS_ and no C-linkage SQLColAttribute / SQLColAttributeW; the other 112 entry points are fine. unixODBC then falls back to the ODBC 2 SQLColAttributesW for every SQLColAttribute call, mapping SQL_DESC_TYPE to SQL_COLUMN_TYPE (so the verbose type could never be observed there), and the same #ifdef writes the numeric attribute as a 32-bit integer on Linux x64, so a negative type code read from a zeroed SQLLEN comes back as a large positive number (4294967287 for SQL_WVARCHAR).

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