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)
Nobody has claimed this yet.
- 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
mainat dcd08dc (2026-08-07), Linux x86_64, unixODBC 2.3.12 —SQL_DRIVER_VER1.6.0.0 - OpenSearch 3.8.0 with the SQL plugin,
auth=NONE - Connected through
SQLDriverConnectW(the narrowSQLDriverConnectfails 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_CONVERSION → 22018 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 timestamp → SQL_TYPE_TIMESTAMP, the result-set path does not.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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