opensearch-project / opensearch-project/sql-odbc

Any character value converted to SQL_C_TYPE_TIMESTAMP or SQL_C_TYPE_DATE comes back as today's date under SQL_SUCCESS (convert.c fills an unparsed SIMPLE_TIME from localtime)

Open
#101 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

untriaged
Dominant language
C++
Stars
10
Forks
25
PR merge metrics
No merged PRs in 30d

Description

What happens

Reading any character-typed result column (keyword/text, which the driver describes as SQL_WVARCHAR) with SQLGetData or SQLBindCol as SQL_C_TYPE_TIMESTAMP or SQL_C_TYPE_DATE returns SQL_SUCCESS and today's date at midnight, whatever the string holds — '2024-02-29 13:45:10.123', '2024-02-29', 'hello' and '12.345' all convert to 2026-08-29 (the host date when the program ran). A TIMESTAMP '…' literal takes the same path, because the SQL plugin types it timestamp, which the driver's result-set type map does not know (so it too is described SQL_WVARCHAR); only a DATE '…' literal, which maps to SQL_TYPE_TIMESTAMP, converts correctly.

ODBC specifies 22018 (invalid character value for cast specification) for a character value that is not a valid timestamp, and the stored value for one that is. Returning success with a fabricated value is the worst of the three outcomes for an application: it cannot tell that anything went wrong.

Environment

  • Driver built from main at dcd08dc (2026-08-07), Linux x86_64, unixODBC 2.3.12 — SQL_DRIVER_VER 1.6.0.0
  • OpenSearch 3.8.0 with the SQL plugin, auth=NONE
  • Connected through SQLDriverConnectW (the narrow SQLDriverConnect fails to connect at all on this build — separate problem)

Reproduction (plain ODBC, no index needed — the SQL plugin accepts a FROM-less SELECT; gcc -O1 -Wall char_to_timestamp.c -lodbc):

/* OpenSearch SQL ODBC: any character value converted to SQL_C_TYPE_TIMESTAMP / SQL_C_TYPE_DATE
 * comes back as today's date under SQL_SUCCESS.  Build: gcc -O1 -Wall char_to_timestamp.c -lodbc
 * Run: ODBC_CONN='Driver=/path/libsqlodbc.so;host=127.0.0.1;port=9200;auth=NONE;useSSL=0;' ./char_to_timestamp */
#include <sql.h>
#include <sqlext.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
static SQLHENV env; static SQLHDBC dbc;
static void diag(SQLSMALLINT t, SQLHANDLE h) {
  SQLCHAR st[6], msg[1024]; SQLINTEGER nat; SQLSMALLINT len, i = 1;
  while (SQLGetDiagRec(t, h, i++, st, &nat, msg, sizeof msg, &len) == SQL_SUCCESS) printf("    %s (%d): %s\n", st, (int)nat, msg);
}
static void conv(const char* sql, SQLSMALLINT ctype) {
  SQLHSTMT h; SQLAllocHandle(SQL_HANDLE_STMT, dbc, &h);
  SQLRETURN rc = SQLExecDirect(h, (SQLCHAR*)sql, SQL_NTS);
  if (!SQL_SUCCEEDED(rc)) { printf("%s -> rc=%d\n", sql, (int)rc); diag(SQL_HANDLE_STMT, h); return; }
  SQLCHAR name[64]; SQLSMALLINT nl, dt, dd, nu; SQLULEN sz; SQLDescribeCol(h, 1, name, sizeof name, &nl, &dt, &sz, &dd, &nu);
  SQLFetch(h);
  SQL_TIMESTAMP_STRUCT ts; SQL_DATE_STRUCT ds; SQLLEN ind = -99; memset(&ts, 0, sizeof ts); memset(&ds, 0, sizeof ds);
  if (ctype == SQL_C_TYPE_TIMESTAMP) {
    rc = SQLGetData(h, 1, SQL_C_TYPE_TIMESTAMP, &ts, sizeof ts, &ind);
    printf("%-52s described type %3d -> SQL_C_TYPE_TIMESTAMP rc=%d ind=%ld value %04d-%02d-%02d %02d:%02d:%02d.%09u\n",
           sql, (int)dt, (int)rc, (long)ind, ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, (unsigned)ts.fraction);
  } else {
    rc = SQLGetData(h, 1, SQL_C_TYPE_DATE, &ds, sizeof ds, &ind);
    printf("%-52s described type %3d -> SQL_C_TYPE_DATE      rc=%d ind=%ld value %04d-%02d-%02d\n",
           sql, (int)dt, (int)rc, (long)ind, ds.year, ds.month, ds.day);
  }
  if (rc != SQL_SUCCESS) diag(SQL_HANDLE_STMT, h);
  SQLFreeHandle(SQL_HANDLE_STMT, h);
}
int main(void) {
  setvbuf(stdout, NULL, _IONBF, 0);
  time_t now = time(NULL); struct tm* lt = localtime(&now);
  printf("host date today = %04d-%02d-%02d\n", lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday);
  SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
  SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);
  SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
  /* the driver's ANSI SQLDriverConnect cannot connect (separate problem), so connect wide */
  const char* c = getenv("ODBC_CONN"); size_t n = strlen(c); SQLWCHAR w[1024]; for (size_t i = 0; i <= n; i++) w[i] = (SQLWCHAR)(unsigned char)c[i];
  SQLRETURN rc = SQLDriverConnectW(dbc, NULL, w, (SQLSMALLINT)n, NULL, 0, NULL, SQL_DRIVER_NOPROMPT);
  if (!SQL_SUCCEEDED(rc)) { printf("connect rc=%d\n", (int)rc); diag(SQL_HANDLE_DBC, dbc); return 1; }
  SQLCHAR v[64]; SQLSMALLINT l; SQLGetInfo(dbc, SQL_DRIVER_VER, v, sizeof v, &l); printf("SQL_DRIVER_VER=%s ", v);
  SQLGetInfo(dbc, SQL_DBMS_VER, v, sizeof v, &l); printf("SQL_DBMS_VER=%s\n", v);
  conv("SELECT '2024-02-29 13:45:10.123'", SQL_C_TYPE_TIMESTAMP);
  conv("SELECT '2024-02-29'", SQL_C_TYPE_DATE);
  conv("SELECT 'hello'", SQL_C_TYPE_TIMESTAMP);
  conv("SELECT '12.345'", SQL_C_TYPE_DATE);
  conv("SELECT TIMESTAMP '2024-02-29 13:45:10'", SQL_C_TYPE_TIMESTAMP);
  conv("SELECT DATE '2024-02-29'", SQL_C_TYPE_DATE);
  SQLDisconnect(dbc); return 0;
}
$ ./char_to_timestamp
host date today = 2026-08-29
SQL_DRIVER_VER=1.6.0.0 SQL_DBMS_VER=3.8.0
SELECT '2024-02-29 13:45:10.123'                     described type  -9 -> SQL_C_TYPE_TIMESTAMP rc=0 ind=16 value 2026-08-29 00:00:00.000000000
SELECT '2024-02-29'                                  described type  -9 -> SQL_C_TYPE_DATE      rc=0 ind=6 value 2026-08-29
SELECT 'hello'                                       described type  -9 -> SQL_C_TYPE_TIMESTAMP rc=0 ind=16 value 2026-08-29 00:00:00.000000000
SELECT '12.345'                                      described type  -9 -> SQL_C_TYPE_DATE      rc=0 ind=6 value 2026-08-29
SELECT TIMESTAMP '2024-02-29 13:45:10'               described type  -9 -> SQL_C_TYPE_TIMESTAMP rc=0 ind=16 value 2026-08-29 00:00:00.000000000
SELECT DATE '2024-02-29'                             described type  93 -> SQL_C_TYPE_DATE      rc=0 ind=6 value 2024-02-29

Expected: the first four calls to fail with 22018 (or, for the two well-formed strings, to return 2024-02-29 13:45:10.123 / 2024-02-29 — either is defensible), never 2026-08-29.

Where it comes from (src/sqlodbc/convert.c at dcd08dc)

copy_and_convert_field() zeroes a SIMPLE_TIME std_time (line ~1044) and then parses the value into it only for the OPENSEARCH_TYPE_DATE/TIME/TIMESTAMP field types (the switch (field_type) at ~1082, whose own comment says "Conversions from ES char/varchar of a date/time/timestamp value to SQL_C_DATE, SQL_C_TIME, SQL_C_TIMESTAMP not supported" and "$$$ need to add parsing for date/time/timestamp strings in OPENSEARCH_TYPE_CHAR,VARCHAR $$$"). For a character field type nothing is parsed, so std_time stays all zero — and the SQL_C_TYPE_DATE (~1368) and SQL_C_TYPE_TIMESTAMP (~1416) output branches then run

tim = SC_get_localtime(stmt);
if (std_time.m == 0) std_time.m = tim->tm_mon + 1;
if (std_time.d == 0) std_time.d = tim->tm_mday;
if (std_time.y == 0) std_time.y = tim->tm_year + 1900;

— a fallback meant to supply a date part for a time source — and return len = 16 / len = 6 with COPY_OK. So an unparsed string becomes "today", and the caller sees SQL_SUCCESS.

Suggested fix

Either parse character sources with timestamp2stime() (as the TIMESTAMP branch does) and return COPY_INVALID_STRING_CONVERSION22018 when it fails, or return COPY_UNSUPPORTED_CONVERSION for character→datetime outright. The timestamp type-name gap in the result-set map (type_to_oid_map, opensearch_parse_result.cpp) is a separate, smaller fix: SQLColumns/SQLGetTypeInfo already map timestampSQL_TYPE_TIMESTAMP, the result-set path does not.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the conversion behavior with the provided char_to_timestamp.c program, then inspect copy_and_convert_field() in src/sqlodbc/convert.c, especially the character-field switch and date/time output branches. Check type_to_oid_map in opensearch_parse_result.cpp for the separate timestamp mapping gap. Done means character-to-date/time conversions no longer return fabricated local dates with SQL_SUCCESS, and timestamp literals receive the intended result type.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, cpp, sql
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
75/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.