From 8d325f16930045b995403c11b03abbb690d41a7b Mon Sep 17 00:00:00 2001 From: freemine Date: Tue, 13 Oct 2020 15:20:56 +0800 Subject: [PATCH] 1. support SQLWCHAR 2. more precise error message, according ODBC doc --- src/client/src/tscPrepare.c | 56 ++ src/connector/odbc/src/todbc.c | 1228 ++++++++++++++++----------- src/connector/odbc/src/todbc_util.c | 226 +++++ src/connector/odbc/src/todbc_util.h | 52 ++ src/connector/odbc/tests/main.c | 2 +- src/connector/odbc/tests/odbc.py | 44 + src/inc/taos.h | 17 + src/inc/taoserror.h | 10 + 8 files changed, 1122 insertions(+), 513 deletions(-) create mode 100644 src/connector/odbc/src/todbc_util.c create mode 100644 src/connector/odbc/src/todbc_util.h diff --git a/src/client/src/tscPrepare.c b/src/client/src/tscPrepare.c index b3bb4566be..2f42f8ac56 100644 --- a/src/client/src/tscPrepare.c +++ b/src/client/src/tscPrepare.c @@ -637,3 +637,59 @@ TAOS_RES *taos_stmt_use_result(TAOS_STMT* stmt) { pStmt->pSql = NULL; return result; } + +int taos_stmt_num_params(TAOS_STMT *stmt, int *nums) { + STscStmt* pStmt = (STscStmt*)stmt; + + if (stmt == NULL || pStmt->taos == NULL || pStmt->pSql == NULL) { + terrno = TSDB_CODE_TSC_DISCONNECTED; + return TSDB_CODE_TSC_DISCONNECTED; + } + + SSqlObj* pSql = pStmt->pSql; + SSqlCmd *pCmd = &pSql->cmd; + + *nums = pCmd->numOfParams; + + return TSDB_CODE_SUCCESS; +} + +int taos_stmt_get_param(TAOS_STMT *stmt, int idx, int *type, int *bytes) { + STscStmt* pStmt = (STscStmt*)stmt; + + if (stmt == NULL || pStmt->taos == NULL || pStmt->pSql == NULL) { + terrno = TSDB_CODE_TSC_DISCONNECTED; + return TSDB_CODE_TSC_DISCONNECTED; + } + + SSqlObj* pSql = pStmt->pSql; + SSqlCmd *pCmd = &pSql->cmd; + STableDataBlocks* pBlock = taosArrayGetP(pCmd->pDataBlocks, 0); + + assert(pCmd->numOfParams == pBlock->numOfParams); + if (idx < 0 || idx >= pBlock->numOfParams) return -1; + + SParamInfo* param = pBlock->params + idx; + if (type) *type = param->type; + if (bytes) *bytes = param->bytes; + + return TSDB_CODE_SUCCESS; +} + +const char *taos_data_type(int type) { + switch (type) { + case TSDB_DATA_TYPE_NULL: return "TSDB_DATA_TYPE_NULL"; + case TSDB_DATA_TYPE_BOOL: return "TSDB_DATA_TYPE_BOOL"; + case TSDB_DATA_TYPE_TINYINT: return "TSDB_DATA_TYPE_TINYINT"; + case TSDB_DATA_TYPE_SMALLINT: return "TSDB_DATA_TYPE_SMALLINT"; + case TSDB_DATA_TYPE_INT: return "TSDB_DATA_TYPE_INT"; + case TSDB_DATA_TYPE_BIGINT: return "TSDB_DATA_TYPE_BIGINT"; + case TSDB_DATA_TYPE_FLOAT: return "TSDB_DATA_TYPE_FLOAT"; + case TSDB_DATA_TYPE_DOUBLE: return "TSDB_DATA_TYPE_DOUBLE"; + case TSDB_DATA_TYPE_BINARY: return "TSDB_DATA_TYPE_BINARY"; + case TSDB_DATA_TYPE_TIMESTAMP: return "TSDB_DATA_TYPE_TIMESTAMP"; + case TSDB_DATA_TYPE_NCHAR: return "TSDB_DATA_TYPE_NCHAR"; + default: return "UNKNOWN"; + } +} + diff --git a/src/connector/odbc/src/todbc.c b/src/connector/odbc/src/todbc.c index 6eaaba762f..9576a981fb 100644 --- a/src/connector/odbc/src/todbc.c +++ b/src/connector/odbc/src/todbc.c @@ -13,34 +13,22 @@ * along with this program. If not, see . */ +// #define _BSD_SOURCE +#define _XOPEN_SOURCE +#define _DEFAULT_SOURCE + #include "taos.h" #include "os.h" #include "taoserror.h" +#include "todbc_util.h" #include #include -#include -#include -#include -#include #include -#define D(fmt, ...) \ - fprintf(stderr, \ - "%s[%d]:%s() " fmt "\n", \ - basename((char*)__FILE__), __LINE__, __func__, \ - ##__VA_ARGS__) - -#define DASSERT(statement) \ -do { \ - if (statement) break; \ - D("Assertion failure: %s", #statement); \ - abort(); \ -} while (0) - #define GET_REF(obj) atomic_load_64(&obj->refcount) #define INC_REF(obj) atomic_add_fetch_64(&obj->refcount, 1) #define DEC_REF(obj) atomic_sub_fetch_64(&obj->refcount, 1) @@ -76,6 +64,7 @@ do { if (NativeError) *NativeError = obj->err.err_no; \ snprintf((char*)MessageText, BufferLength, "%s", obj->err.err_str); \ if (TextLength && obj->err.err_str) *TextLength = strlen(obj->err.err_str); \ + if (TextLength && obj->err.err_str) *TextLength = utf8_chars(obj->err.err_str); \ } while (0) #define FREE_ERROR(obj) \ @@ -90,7 +79,7 @@ do { \ #define SET_UNSUPPORT_ERROR(obj, sqlstate, err_fmt, ...) \ do { \ - SET_ERROR(obj, sqlstate, TSDB_CODE_COM_OPS_NOT_SUPPORT, err_fmt, ##__VA_ARGS__); \ + SET_ERROR(obj, sqlstate, TSDB_CODE_ODBC_NOT_SUPPORT, err_fmt, ##__VA_ARGS__); \ } while (0) \ #define SET_HANDLE_INVALID(obj, sqlstate, err_fmt, ...) \ @@ -108,6 +97,22 @@ do { \ } \ } while (0) +#define CHK_CONN(obj) \ +do { \ + if (!obj->conn) { \ + SET_ERROR(obj, "HY000", TSDB_CODE_ODBC_INVALID_HANDLE, "connection closed or not ready"); \ + return SQL_ERROR; \ + } \ +} while (0); + +#define CHK_CONN_TAOS(obj) \ +do { \ + if (!obj->conn->taos) { \ + SET_ERROR(obj, "HY000", TSDB_CODE_ODBC_INVALID_HANDLE, "connection to data source closed or not ready"); \ + return SQL_ERROR; \ + } \ +} while (0); + #define CHK_RS(r_091c, sql_091c, fmt_091c, ...) \ do { \ r_091c = SQL_ERROR; \ @@ -184,7 +189,6 @@ struct sql_s { conn_t *conn; TAOS_STMT *stmt; - TAOS_BIND *binds; param_bind_t *params; int n_params; TAOS_RES *rs; @@ -205,8 +209,9 @@ struct c_target_s { static pthread_once_t init_once = PTHREAD_ONCE_INIT; static void init_routine(void); + + static int do_field_display_size(TAOS_FIELD *field); -static void do_convert(SQLPOINTER TargetValue, SQLLEN BufferLength, SQLLEN *StrLen_or_Ind, TAOS_FIELD *field, void *row); static SQLRETURN doSQLAllocEnv(SQLHENV *EnvironmentHandle) { @@ -269,7 +274,7 @@ static SQLRETURN doSQLAllocConnect(SQLHENV EnvironmentHandle, do { conn = (conn_t*)calloc(1, sizeof(*conn)); if (!conn) { - SET_ERROR(env, "HY000", TSDB_CODE_ODBC_OOM, "failed to alloc connection handle"); + SET_ERROR(env, "HY001", TSDB_CODE_ODBC_OOM, ""); break; } @@ -336,7 +341,7 @@ static SQLRETURN doSQLConnect(SQLHDBC ConnectionHandle, if (!conn) return SQL_ERROR; if (conn->taos) { - SET_ERROR(conn, "HY000", TSDB_CODE_TSC_APP_ERROR, "connection still in use"); + SET_ERROR(conn, "08002", TSDB_CODE_ODBC_CONNECTION_BUSY, "connection still in use"); return SQL_ERROR; } @@ -346,7 +351,7 @@ static SQLRETURN doSQLConnect(SQLHDBC ConnectionHandle, do { if ((ServerName && !serverName) || (UserName && !userName) || (Authentication && !auth)) { - SET_ERROR(conn, "HY000", TSDB_CODE_ODBC_OOM, "failed to allocate resources"); + SET_ERROR(conn, "HY001", TSDB_CODE_ODBC_OOM, ""); break; } @@ -354,7 +359,7 @@ static SQLRETURN doSQLConnect(SQLHDBC ConnectionHandle, // TODO: shall receive ip/port from odbc.ini conn->taos = taos_connect("localhost", userName, auth, NULL, 0); if (!conn->taos) { - SET_ERROR(conn, "HY000", terrno, "failed to connect to data source"); + SET_ERROR(conn, "08001", terrno, "failed to connect to data source"); break; } } while (0); @@ -409,7 +414,7 @@ static SQLRETURN doSQLAllocStmt(SQLHDBC ConnectionHandle, do { sql_t *sql = (sql_t*)calloc(1, sizeof(*sql)); if (!sql) { - SET_ERROR(conn, "HY000", TSDB_CODE_ODBC_OOM, "failed to alloc statement handle"); + SET_ERROR(conn, "HY001", TSDB_CODE_ODBC_OOM, ""); break; } @@ -442,7 +447,7 @@ static SQLRETURN doSQLFreeStmt(SQLHSTMT StatementHandle, if (Option == SQL_CLOSE) return SQL_SUCCESS; if (Option != SQL_DROP) { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, "free statement with Option[%x] not supported yet", Option); + SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_NOT_SUPPORT, "free statement with Option[%x] not supported yet", Option); return SQL_ERROR; } @@ -458,10 +463,6 @@ static SQLRETURN doSQLFreeStmt(SQLHSTMT StatementHandle, sql->stmt = NULL; } - if (sql->binds) { - free(sql->binds); - sql->binds = NULL; - } if (sql->params) { free(sql->params); sql->params = NULL; @@ -494,15 +495,8 @@ static SQLRETURN doSQLExecDirect(SQLHSTMT StatementHandle, sql_t *sql = (sql_t*)StatementHandle; if (!sql) return SQL_ERROR; - if (!sql->conn) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection yet"); - return SQL_ERROR; - } - - if (!sql->conn->taos) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection to data source yet"); - return SQL_ERROR; - } + CHK_CONN(sql); + CHK_CONN_TAOS(sql); if (sql->rs) { taos_free_result(sql->rs); @@ -515,11 +509,6 @@ static SQLRETURN doSQLExecDirect(SQLHSTMT StatementHandle, sql->stmt = NULL; } - if (sql->binds) { - free(sql->binds); - sql->binds = NULL; - } - if (sql->params) { free(sql->params); sql->params = NULL; @@ -531,11 +520,11 @@ static SQLRETURN doSQLExecDirect(SQLHSTMT StatementHandle, SQLRETURN r = SQL_ERROR; do { if (!stxt) { - SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_OOM, "failed to allocate resources"); + SET_ERROR(sql, "HY001", TSDB_CODE_ODBC_OOM, ""); break; } sql->rs = taos_query(sql->conn->taos, stxt); - CHK_RS(r, sql, "failed to query"); + CHK_RS(r, sql, "failed to execute"); } while (0); SFRE(stxt, StatementText, TextLength); @@ -551,24 +540,24 @@ SQLRETURN SQL_API SQLExecDirect(SQLHSTMT StatementHandle, return r; } +SQLRETURN SQL_API SQLExecDirectW(SQLHSTMT hstmt, SQLWCHAR *szSqlStr, SQLINTEGER cbSqlStr) +{ + size_t bytes = 0; + SQLCHAR *utf8 = wchars_to_chars(szSqlStr, cbSqlStr, &bytes); + return SQLExecDirect(hstmt, utf8, bytes); +} + static SQLRETURN doSQLNumResultCols(SQLHSTMT StatementHandle, SQLSMALLINT *ColumnCount) { sql_t *sql = (sql_t*)StatementHandle; if (!sql) return SQL_ERROR; - if (!sql->conn) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection yet"); - return SQL_ERROR; - } - - if (!sql->conn->taos) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection to data source yet"); - return SQL_ERROR; - } + CHK_CONN(sql); + CHK_CONN_TAOS(sql); if (!sql->rs) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_QUERY_CACHE_ERASED, "no result set cached or not ready"); + SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_NO_RESULT, ""); return SQL_ERROR; } @@ -594,18 +583,11 @@ static SQLRETURN doSQLRowCount(SQLHSTMT StatementHandle, sql_t *sql = (sql_t*)StatementHandle; if (!sql) return SQL_ERROR; - if (!sql->conn) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection yet"); - return SQL_ERROR; - } - - if (!sql->conn->taos) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection to data source yet"); - return SQL_ERROR; - } + CHK_CONN(sql); + CHK_CONN_TAOS(sql); if (!sql->rs) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_QUERY_CACHE_ERASED, "no result set cached or not ready"); + SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_NO_RESULT, ""); return SQL_ERROR; } @@ -629,22 +611,14 @@ static SQLRETURN doSQLColAttribute(SQLHSTMT StatementHandle, SQLPOINTER CharacterAttribute, SQLSMALLINT BufferLength, SQLSMALLINT *StringLength, SQLLEN *NumericAttribute ) { - D("......"); sql_t *sql = (sql_t*)StatementHandle; if (!sql) return SQL_ERROR; - if (!sql->conn) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection yet"); - return SQL_ERROR; - } - - if (!sql->conn->taos) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection to data source yet"); - return SQL_ERROR; - } + CHK_CONN(sql); + CHK_CONN_TAOS(sql); if (!sql->rs) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_QUERY_CACHE_ERASED, "no result set cached or not ready"); + SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_NO_RESULT, ""); return SQL_ERROR; } @@ -652,22 +626,12 @@ static SQLRETURN doSQLColAttribute(SQLHSTMT StatementHandle, TAOS_FIELD *fields = taos_fetch_fields(sql->rs); if (nfields==0 || fields==NULL) { - SET_ERROR(sql, "HY000", TSDB_CODE_MND_FIELD_NOT_EXIST, "no fields in result set"); - return SQL_ERROR; - } - - if (ColumnNumber<0) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_APP_ERROR, "ColumnNumber[%d] underflow", ColumnNumber); + SET_ERROR(sql, "07005", TSDB_CODE_ODBC_NO_FIELDS, ""); return SQL_ERROR; } - if (ColumnNumber==0) { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, "ColumnNumber[0] not supported"); - return SQL_ERROR; - } - - if (ColumnNumber>nfields) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_APP_ERROR, "ColumnNumber[%d] overflow", ColumnNumber); + if (ColumnNumber<=0 || ColumnNumber>nfields) { + SET_ERROR(sql, "07009", TSDB_CODE_ODBC_OUT_OF_RANGE, "invalid column number [%d]", ColumnNumber); return SQL_ERROR; } @@ -685,9 +649,9 @@ static SQLRETURN doSQLColAttribute(SQLHSTMT StatementHandle, *NumericAttribute = SQL_FALSE; } break; default: { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, - "ColumnNumber[%d] FieldIdentifier[%d] not supported yet", - ColumnNumber, FieldIdentifier); + SET_ERROR(sql, "HY091", TSDB_CODE_ODBC_OUT_OF_RANGE, + "FieldIdentifier[%d/0x%x] for Column [%d] not supported yet", + FieldIdentifier, FieldIdentifier, ColumnNumber); return SQL_ERROR; } break; } @@ -772,23 +736,16 @@ static SQLRETURN doSQLGetData(SQLHSTMT StatementHandle, sql_t *sql = (sql_t*)StatementHandle; if (!sql) return SQL_ERROR; - if (!sql->conn) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection yet"); - return SQL_ERROR; - } - - if (!sql->conn->taos) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection to data source yet"); - return SQL_ERROR; - } + CHK_CONN(sql); + CHK_CONN_TAOS(sql); if (!sql->rs) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_QUERY_CACHE_ERASED, "no result set cached or not ready"); + SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_NO_RESULT, ""); return SQL_ERROR; } if (!sql->row) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_QUERY_CACHE_ERASED, "no rows cached or not ready"); + SET_ERROR(sql, "24000", TSDB_CODE_ODBC_INVALID_CURSOR, ""); return SQL_ERROR; } @@ -797,23 +754,8 @@ static SQLRETURN doSQLGetData(SQLHSTMT StatementHandle, int nfields = taos_field_count(sql->rs); TAOS_FIELD *fields = taos_fetch_fields(sql->rs); - if (nfields==0 || fields==NULL) { - SET_ERROR(sql, "HY000", TSDB_CODE_MND_FIELD_NOT_EXIST, "no fields in result set"); - return SQL_ERROR; - } - - if (ColumnNumber<0) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_APP_ERROR, "ColumnNumber[%d] underflow", ColumnNumber); - return SQL_ERROR; - } - - if (ColumnNumber==0) { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, "ColumnNumber[0] not supported"); - return SQL_ERROR; - } - - if (ColumnNumber>nfields) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_APP_ERROR, "ColumnNumber[%d] overflow", ColumnNumber); + if (ColumnNumber<=0 || ColumnNumber>nfields) { + SET_ERROR(sql, "07009", TSDB_CODE_ODBC_OUT_OF_RANGE, "invalid column number [%d]", ColumnNumber); return SQL_ERROR; } @@ -834,8 +776,6 @@ static SQLRETURN doSQLGetData(SQLHSTMT StatementHandle, target.len = BufferLength; target.soi = StrLen_or_Ind; - SQLRETURN r = SQL_ERROR; - switch (field->type) { case TSDB_DATA_TYPE_BOOL: { int8_t v = *(int8_t*)row; @@ -851,7 +791,9 @@ static SQLRETURN doSQLGetData(SQLHSTMT StatementHandle, case SQL_C_CHAR: return conv_tsdb_bool_to_c_char(sql, &target, field, v); case SQL_C_BINARY: return conv_tsdb_bool_to_c_binary(sql, &target, field, v); default: { - SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_CONV_NOT_SUPPORT, "from TSDB_DATA_TYPE [%d] to SQL_C_TYPE [%d] not supported", field->type, target.ct); + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_NOT_SUPPORT, + "no convertion from [%s] to [%s[%d][0x%x]] for col [%d]", + taos_data_type(field->type), sql_c_type(target.ct), target.ct, target.ct, ColumnNumber); return SQL_ERROR; } } @@ -868,7 +810,9 @@ static SQLRETURN doSQLGetData(SQLHSTMT StatementHandle, case SQL_C_CHAR: return conv_tsdb_v1_to_c_char(sql, &target, field, v); case SQL_C_BINARY: return conv_tsdb_v1_to_c_binary(sql, &target, field, v); default: { - SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_CONV_NOT_SUPPORT, "from TSDB_DATA_TYPE [%d] to SQL_C_TYPE [%d] not supported", field->type, target.ct); + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_NOT_SUPPORT, + "no convertion from [%s] to [%s[%d][0x%x]] for col [%d]", + taos_data_type(field->type), sql_c_type(target.ct), target.ct, target.ct, ColumnNumber); return SQL_ERROR; } } @@ -884,7 +828,9 @@ static SQLRETURN doSQLGetData(SQLHSTMT StatementHandle, case SQL_C_CHAR: return conv_tsdb_v2_to_c_char(sql, &target, field, v); case SQL_C_BINARY: return conv_tsdb_v2_to_c_binary(sql, &target, field, v); default: { - SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_CONV_NOT_SUPPORT, "from TSDB_DATA_TYPE [%d] to SQL_C_TYPE [%d] not supported", field->type, target.ct); + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_NOT_SUPPORT, + "no convertion from [%s] to [%s[%d][0x%x]] for col [%d]", + taos_data_type(field->type), sql_c_type(target.ct), target.ct, target.ct, ColumnNumber); return SQL_ERROR; } } @@ -899,7 +845,9 @@ static SQLRETURN doSQLGetData(SQLHSTMT StatementHandle, case SQL_C_CHAR: return conv_tsdb_v4_to_c_char(sql, &target, field, v); case SQL_C_BINARY: return conv_tsdb_v4_to_c_binary(sql, &target, field, v); default: { - SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_CONV_NOT_SUPPORT, "from TSDB_DATA_TYPE [%d] to SQL_C_TYPE [%d] not supported", field->type, target.ct); + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_NOT_SUPPORT, + "no convertion from [%s] to [%s[%d][0x%x]] for col [%d]", + taos_data_type(field->type), sql_c_type(target.ct), target.ct, target.ct, ColumnNumber); return SQL_ERROR; } } @@ -913,7 +861,9 @@ static SQLRETURN doSQLGetData(SQLHSTMT StatementHandle, case SQL_C_CHAR: return conv_tsdb_v8_to_c_char(sql, &target, field, v); case SQL_C_BINARY: return conv_tsdb_v8_to_c_binary(sql, &target, field, v); default: { - SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_CONV_NOT_SUPPORT, "from TSDB_DATA_TYPE [%d] to SQL_C_TYPE [%d] not supported", field->type, target.ct); + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_NOT_SUPPORT, + "no convertion from [%s] to [%s[%d][0x%x]] for col [%d]", + taos_data_type(field->type), sql_c_type(target.ct), target.ct, target.ct, ColumnNumber); return SQL_ERROR; } } @@ -926,7 +876,9 @@ static SQLRETURN doSQLGetData(SQLHSTMT StatementHandle, case SQL_C_CHAR: return conv_tsdb_f4_to_c_char(sql, &target, field, v); case SQL_C_BINARY: return conv_tsdb_f4_to_c_binary(sql, &target, field, v); default: { - SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_CONV_NOT_SUPPORT, "from TSDB_DATA_TYPE [%d] to SQL_C_TYPE [%d] not supported", field->type, target.ct); + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_NOT_SUPPORT, + "no convertion from [%s] to [%s[%d][0x%x]] for col [%d]", + taos_data_type(field->type), sql_c_type(target.ct), target.ct, target.ct, ColumnNumber); return SQL_ERROR; } } @@ -938,7 +890,9 @@ static SQLRETURN doSQLGetData(SQLHSTMT StatementHandle, case SQL_C_CHAR: return conv_tsdb_f8_to_c_char(sql, &target, field, v); case SQL_C_BINARY: return conv_tsdb_f8_to_c_binary(sql, &target, field, v); default: { - SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_CONV_NOT_SUPPORT, "from TSDB_DATA_TYPE [%d] to SQL_C_TYPE [%d] not supported", field->type, target.ct); + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_NOT_SUPPORT, + "no convertion from [%s] to [%s[%d][0x%x]] for col [%d]", + taos_data_type(field->type), sql_c_type(target.ct), target.ct, target.ct, ColumnNumber); return SQL_ERROR; } } @@ -962,7 +916,9 @@ static SQLRETURN doSQLGetData(SQLHSTMT StatementHandle, case SQL_C_BINARY: return conv_tsdb_ts_to_c_bin(sql, &target, field, &ts); case SQL_C_TIMESTAMP: return conv_tsdb_ts_to_c_ts(sql, &target, field, &ts); default: { - SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_CONV_NOT_SUPPORT, "from TSDB_DATA_TYPE [%d] to SQL_C_TYPE [%d] not supported", field->type, target.ct); + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_NOT_SUPPORT, + "no convertion from [%s] to [%s[%d][0x%x]] for col [%d]", + taos_data_type(field->type), sql_c_type(target.ct), target.ct, target.ct, ColumnNumber); return SQL_ERROR; } } @@ -973,7 +929,9 @@ static SQLRETURN doSQLGetData(SQLHSTMT StatementHandle, case SQL_C_CHAR: return conv_tsdb_bin_to_c_str(sql, &target, field, bin); case SQL_C_BINARY: return conv_tsdb_bin_to_c_bin(sql, &target, field, bin); default: { - SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_CONV_NOT_SUPPORT, "from TSDB_DATA_TYPE [%d] to SQL_C_TYPE [%d] not supported", field->type, target.ct); + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_NOT_SUPPORT, + "no convertion from [%s] to [%s[%d][0x%x]] for col [%d]", + taos_data_type(field->type), sql_c_type(target.ct), target.ct, target.ct, ColumnNumber); return SQL_ERROR; } } @@ -991,81 +949,21 @@ static SQLRETURN doSQLGetData(SQLHSTMT StatementHandle, case SQL_C_CHAR: return conv_tsdb_str_to_c_str(sql, &target, field, str); case SQL_C_BINARY: return conv_tsdb_str_to_c_bin(sql, &target, field, str); default: { - SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_CONV_NOT_SUPPORT, "from TSDB_DATA_TYPE [%d] to SQL_C_TYPE [%d] not supported", field->type, target.ct); + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_NOT_SUPPORT, + "no convertion from [%s] to [%s[%d][0x%x]] for col [%d]", + taos_data_type(field->type), sql_c_type(target.ct), target.ct, target.ct, ColumnNumber); return SQL_ERROR; } } } break; default: { - SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_CONV_NOT_SUPPORT, "field [@%d] type [%d] not supported yet", ColumnNumber, field->type); + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_OUT_OF_RANGE, + "no convertion from [%s[%d/0x%x]] to [%s[%d/0x%x]] for col [%d]", + taos_data_type(field->type), field->type, field->type, + sql_c_type(target.ct), target.ct, target.ct, ColumnNumber); return SQL_ERROR; } break; } - - if (1) return r; - - switch (TargetType) { - case SQL_C_CHAR: { - do_convert(TargetValue, BufferLength, StrLen_or_Ind, field, row); - } break; - case SQL_C_TIMESTAMP: { - TIMESTAMP_STRUCT ts = {0}; - DASSERT(BufferLength == sizeof(ts)); - int64_t v = *(int64_t*)row; - time_t t = v/1000; - struct tm tm = {0}; - localtime_r(&t, &tm); - ts.year = tm.tm_year + 1900; - ts.month = tm.tm_mon + 1; - ts.day = tm.tm_mday; - ts.hour = tm.tm_hour; - ts.minute = tm.tm_min; - ts.second = tm.tm_sec; - ts.fraction = 0; - - memcpy(TargetValue, &ts, sizeof(ts)); - } break; - case SQL_C_LONG: { - int32_t v = *(int32_t*)row; - DASSERT(BufferLength == sizeof(v)); - memcpy(TargetValue, &v, sizeof(v)); - } break; - case SQL_C_SBIGINT: { - int64_t v = *(int64_t*)row; - DASSERT(BufferLength == sizeof(v)); - memcpy(TargetValue, &v, sizeof(v)); - } break; - case SQL_C_FLOAT: { - float v = *(float*)row; - DASSERT(BufferLength == sizeof(v)); - memcpy(TargetValue, &v, sizeof(v)); - } break; - case SQL_C_DOUBLE: { - double v = *(double*)row; - DASSERT(BufferLength == sizeof(v)); - memcpy(TargetValue, &v, sizeof(v)); - } break; - case SQL_C_BINARY: { - if (StrLen_or_Ind) { - if (field->type == TSDB_DATA_TYPE_NCHAR) { - *StrLen_or_Ind = strnlen((const char*)row, field->bytes); - } else { - *StrLen_or_Ind = field->bytes; - } - } - size_t n = field->bytes; - if (n>BufferLength) n = BufferLength; - memcpy(TargetValue, (const char*)row, n); - } break; - default: { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, - "ColumnNumber[%d] TargetType[%d] BufferLength[%ld] not supported yet", - ColumnNumber, TargetType, BufferLength); - return SQL_ERROR; - } break; - } - - return SQL_SUCCESS; } SQLRETURN SQL_API SQLGetData(SQLHSTMT StatementHandle, @@ -1085,18 +983,11 @@ static SQLRETURN doSQLFetch(SQLHSTMT StatementHandle) sql_t *sql = (sql_t*)StatementHandle; if (!sql) return SQL_ERROR; - if (!sql->conn) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection yet"); - return SQL_ERROR; - } - - if (!sql->conn->taos) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection to data source yet"); - return SQL_ERROR; - } + CHK_CONN(sql); + CHK_CONN_TAOS(sql); if (!sql->rs) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_QUERY_CACHE_ERASED, "no result set cached or not ready"); + SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_NO_RESULT, ""); return SQL_ERROR; } @@ -1117,15 +1008,8 @@ static SQLRETURN doSQLPrepare(SQLHSTMT StatementHandle, sql_t *sql = (sql_t*)StatementHandle; if (!sql) return SQL_ERROR; - if (!sql->conn) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection yet"); - return SQL_ERROR; - } - - if (!sql->conn->taos) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection to data source yet"); - return SQL_ERROR; - } + CHK_CONN(sql); + CHK_CONN_TAOS(sql); if (sql->rs) { taos_free_result(sql->rs); @@ -1138,10 +1022,6 @@ static SQLRETURN doSQLPrepare(SQLHSTMT StatementHandle, sql->stmt = NULL; } - if (sql->binds) { - free(sql->binds); - sql->binds = NULL; - } if (sql->params) { free(sql->params); sql->params = NULL; @@ -1151,13 +1031,13 @@ static SQLRETURN doSQLPrepare(SQLHSTMT StatementHandle, do { sql->stmt = taos_stmt_init(sql->conn->taos); if (!sql->stmt) { - SET_ERROR(sql, "HY000", terrno, "failed to initialize statement internally"); + SET_ERROR(sql, "HY001", terrno, "failed to initialize TAOS statement internally"); break; } int r = taos_stmt_prepare(sql->stmt, (const char *)StatementText, TextLength); if (r) { - SET_ERROR(sql, "HY000", r, "failed to prepare a statement"); + SET_ERROR(sql, "HY000", r, "failed to prepare a TAOS statement"); taos_stmt_close(sql->stmt); sql->stmt = NULL; break; @@ -1175,89 +1055,525 @@ SQLRETURN SQL_API SQLPrepare(SQLHSTMT StatementHandle, return r; } -static SQLRETURN doSQLExecute(SQLHSTMT StatementHandle) -{ - sql_t *sql = (sql_t*)StatementHandle; - if (!sql) return SQL_ERROR; +static const int yes = 1; +static const int no = 0; - if (!sql->conn) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection yet"); +static SQLRETURN do_bind_param_value(sql_t *sql, int idx, param_bind_t *param, TAOS_BIND *bind) +{ + if (!param->valid) { + SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_NOT_SUPPORT, "parameter [@%d] not bound yet", idx+1); return SQL_ERROR; } - - if (!sql->conn->taos) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection to data source yet"); - return SQL_ERROR; + if (param->StrLen_or_Ind && *param->StrLen_or_Ind == SQL_NULL_DATA) { + bind->is_null = (int*)&yes; + return SQL_SUCCESS; } - - if (!sql->stmt) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_SQL, "no statement cached or not ready"); + bind->is_null = (int*)&no; + int type = 0; + int bytes = 0; + int r = taos_stmt_get_param(sql->stmt, idx, &type, &bytes); + if (r) { + SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_OUT_OF_RANGE, "parameter [@%d] not valid", idx+1); return SQL_ERROR; } - if (sql->rs) { - taos_free_result(sql->rs); - sql->rs = NULL; - sql->row = NULL; - } - - int r = 0; - - for (int i=0; in_params; ++i) { - param_bind_t *pb = sql->params + i; - if (!pb->valid) { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, "default parameter [@%d] not supported yet", i+1); - return SQL_ERROR; - } - TAOS_BIND *b = sql->binds + i; - int yes = 1; - int no = 0; - if (pb->StrLen_or_Ind && *pb->StrLen_or_Ind == SQL_NULL_DATA) { - b->is_null = &yes; - } else { - b->is_null = &no; - switch (b->buffer_type) { - case TSDB_DATA_TYPE_BOOL: - case TSDB_DATA_TYPE_TINYINT: - case TSDB_DATA_TYPE_SMALLINT: - case TSDB_DATA_TYPE_INT: - case TSDB_DATA_TYPE_BIGINT: - case TSDB_DATA_TYPE_FLOAT: - case TSDB_DATA_TYPE_DOUBLE: - case TSDB_DATA_TYPE_TIMESTAMP: { - b->length = &b->buffer_length; - b->buffer = pb->ParameterValue; + // ref: https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/converting-data-from-c-to-sql-data-types?view=sql-server-ver15 + switch (type) { + case TSDB_DATA_TYPE_BOOL: { + bind->buffer_type = type; + bind->buffer_length = sizeof(bind->u.b); + bind->buffer = &bind->u.b; + bind->length = &bind->buffer_length; + switch (param->ValueType) { + case SQL_C_LONG: { + bind->u.b = *(int32_t*)param->ParameterValue; + } break; + case SQL_C_BIT: { + bind->u.b = *(int8_t*)param->ParameterValue; + } break; + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_SHORT: + case SQL_C_SSHORT: + case SQL_C_USHORT: + case SQL_C_SLONG: + case SQL_C_ULONG: + case SQL_C_FLOAT: + case SQL_C_DOUBLE: + case SQL_C_TINYINT: + case SQL_C_STINYINT: + case SQL_C_UTINYINT: + case SQL_C_SBIGINT: + case SQL_C_UBIGINT: + case SQL_C_BINARY: + case SQL_C_DATE: + case SQL_C_TIME: + case SQL_C_TIMESTAMP: + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + case SQL_C_NUMERIC: + case SQL_C_GUID: + default: { + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_OUT_OF_RANGE, + "no convertion from [%s[%d/0x%x]] to [%s[%d/0x%x]] for parameter [%d]", + sql_c_type(param->ValueType), param->ValueType, param->ValueType, + taos_data_type(type), type, type, idx+1); + return SQL_ERROR; + } break; + } + } break; + case TSDB_DATA_TYPE_TINYINT: { + bind->buffer_type = type; + bind->buffer_length = sizeof(bind->u.v1); + bind->buffer = &bind->u.v1; + bind->length = &bind->buffer_length; + switch (param->ValueType) { + case SQL_C_TINYINT: { + bind->u.v1 = *(int8_t*)param->ParameterValue; + } break; + case SQL_C_SHORT: { + bind->u.v1 = *(int16_t*)param->ParameterValue; + } break; + case SQL_C_LONG: { + bind->u.v1 = *(int32_t*)param->ParameterValue; + } break; + case SQL_C_SBIGINT: { + bind->u.v1 = *(int64_t*)param->ParameterValue; + } break; + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_SSHORT: + case SQL_C_USHORT: + case SQL_C_SLONG: + case SQL_C_ULONG: + case SQL_C_FLOAT: + case SQL_C_DOUBLE: + case SQL_C_BIT: + case SQL_C_STINYINT: + case SQL_C_UTINYINT: + case SQL_C_UBIGINT: + case SQL_C_BINARY: + case SQL_C_DATE: + case SQL_C_TIME: + case SQL_C_TIMESTAMP: + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + case SQL_C_NUMERIC: + case SQL_C_GUID: + default: { + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_OUT_OF_RANGE, + "no convertion from [%s[%d/0x%x]] to [%s[%d/0x%x]] for parameter [%d]", + sql_c_type(param->ValueType), param->ValueType, param->ValueType, + taos_data_type(type), type, type, idx+1); + return SQL_ERROR; + } break; + } + } break; + case TSDB_DATA_TYPE_SMALLINT: { + bind->buffer_type = type; + bind->buffer_length = sizeof(bind->u.v2); + bind->buffer = &bind->u.v2; + bind->length = &bind->buffer_length; + switch (param->ValueType) { + case SQL_C_LONG: { + bind->u.v2 = *(int32_t*)param->ParameterValue; + } break; + case SQL_C_SHORT: { + bind->u.v2 = *(int16_t*)param->ParameterValue; + } break; + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_SSHORT: + case SQL_C_USHORT: + case SQL_C_SLONG: + case SQL_C_ULONG: + case SQL_C_FLOAT: + case SQL_C_DOUBLE: + case SQL_C_BIT: + case SQL_C_TINYINT: + case SQL_C_STINYINT: + case SQL_C_UTINYINT: + case SQL_C_SBIGINT: + case SQL_C_UBIGINT: + case SQL_C_BINARY: + case SQL_C_DATE: + case SQL_C_TIME: + case SQL_C_TIMESTAMP: + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + case SQL_C_NUMERIC: + case SQL_C_GUID: + default: { + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_OUT_OF_RANGE, + "no convertion from [%s[%d/0x%x]] to [%s[%d/0x%x]] for parameter [%d]", + sql_c_type(param->ValueType), param->ValueType, param->ValueType, + taos_data_type(type), type, type, idx+1); + return SQL_ERROR; + } break; + } + } break; + case TSDB_DATA_TYPE_INT: { + bind->buffer_type = type; + bind->buffer_length = sizeof(bind->u.v4); + bind->buffer = &bind->u.v4; + bind->length = &bind->buffer_length; + switch (param->ValueType) { + case SQL_C_LONG: { + bind->u.v4 = *(int32_t*)param->ParameterValue; + } break; + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_SHORT: + case SQL_C_SSHORT: + case SQL_C_USHORT: + case SQL_C_SLONG: + case SQL_C_ULONG: + case SQL_C_FLOAT: + case SQL_C_DOUBLE: + case SQL_C_BIT: + case SQL_C_TINYINT: + case SQL_C_STINYINT: + case SQL_C_UTINYINT: + case SQL_C_SBIGINT: + case SQL_C_UBIGINT: + case SQL_C_BINARY: + case SQL_C_DATE: + case SQL_C_TIME: + case SQL_C_TIMESTAMP: + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + case SQL_C_NUMERIC: + case SQL_C_GUID: + default: { + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_OUT_OF_RANGE, + "no convertion from [%s[%d/0x%x]] to [%s[%d/0x%x]] for parameter [%d]", + sql_c_type(param->ValueType), param->ValueType, param->ValueType, + taos_data_type(type), type, type, idx+1); + return SQL_ERROR; + } break; + } + } break; + case TSDB_DATA_TYPE_BIGINT: { + bind->buffer_type = type; + bind->buffer_length = sizeof(bind->u.v8); + bind->buffer = &bind->u.v8; + bind->length = &bind->buffer_length; + switch (param->ValueType) { + case SQL_C_SBIGINT: { + bind->u.v8 = *(int64_t*)param->ParameterValue; + } break; + case SQL_C_LONG: { + bind->u.v8 = *(int32_t*)param->ParameterValue; + } break; + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_SHORT: + case SQL_C_SSHORT: + case SQL_C_USHORT: + case SQL_C_SLONG: + case SQL_C_ULONG: + case SQL_C_FLOAT: + case SQL_C_DOUBLE: + case SQL_C_BIT: + case SQL_C_TINYINT: + case SQL_C_STINYINT: + case SQL_C_UTINYINT: + case SQL_C_UBIGINT: + case SQL_C_BINARY: + case SQL_C_DATE: + case SQL_C_TIME: + case SQL_C_TIMESTAMP: + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + case SQL_C_NUMERIC: + case SQL_C_GUID: + default: { + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_OUT_OF_RANGE, + "no convertion from [%s[%d/0x%x]] to [%s[%d/0x%x]] for parameter [%d]", + sql_c_type(param->ValueType), param->ValueType, param->ValueType, + taos_data_type(type), type, type, idx+1); + return SQL_ERROR; + } break; + } + } break; + case TSDB_DATA_TYPE_FLOAT: { + bind->buffer_type = type; + bind->buffer_length = sizeof(bind->u.f4); + bind->buffer = &bind->u.f4; + bind->length = &bind->buffer_length; + switch (param->ValueType) { + case SQL_C_DOUBLE: { + bind->u.f4 = *(double*)param->ParameterValue; + } break; + case SQL_C_FLOAT: { + bind->u.f4 = *(float*)param->ParameterValue; + } break; + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_SHORT: + case SQL_C_SSHORT: + case SQL_C_USHORT: + case SQL_C_LONG: + case SQL_C_SLONG: + case SQL_C_ULONG: + case SQL_C_BIT: + case SQL_C_TINYINT: + case SQL_C_STINYINT: + case SQL_C_UTINYINT: + case SQL_C_SBIGINT: + case SQL_C_UBIGINT: + case SQL_C_BINARY: + case SQL_C_DATE: + case SQL_C_TIME: + case SQL_C_TIMESTAMP: + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + case SQL_C_NUMERIC: + case SQL_C_GUID: + default: { + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_OUT_OF_RANGE, + "no convertion from [%s[%d/0x%x]] to [%s[%d/0x%x]] for parameter [%d]", + sql_c_type(param->ValueType), param->ValueType, param->ValueType, + taos_data_type(type), type, type, idx+1); + return SQL_ERROR; + } break; + } + } break; + case TSDB_DATA_TYPE_DOUBLE: { + bind->buffer_type = type; + bind->buffer_length = sizeof(bind->u.f8); + bind->buffer = &bind->u.f8; + bind->length = &bind->buffer_length; + switch (param->ValueType) { + case SQL_C_DOUBLE: { + bind->u.f8 = *(double*)param->ParameterValue; + } break; + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_SHORT: + case SQL_C_SSHORT: + case SQL_C_USHORT: + case SQL_C_LONG: + case SQL_C_SLONG: + case SQL_C_ULONG: + case SQL_C_FLOAT: + case SQL_C_BIT: + case SQL_C_TINYINT: + case SQL_C_STINYINT: + case SQL_C_UTINYINT: + case SQL_C_SBIGINT: + case SQL_C_UBIGINT: + case SQL_C_BINARY: + case SQL_C_DATE: + case SQL_C_TIME: + case SQL_C_TIMESTAMP: + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + case SQL_C_NUMERIC: + case SQL_C_GUID: + default: { + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_OUT_OF_RANGE, + "no convertion from [%s[%d/0x%x]] to [%s[%d/0x%x]] for parameter [%d]", + sql_c_type(param->ValueType), param->ValueType, param->ValueType, + taos_data_type(type), type, type, idx+1); + return SQL_ERROR; + } break; + } + } break; + case TSDB_DATA_TYPE_BINARY: { + bind->buffer_type = type; + bind->length = &bind->buffer_length; + switch (param->ValueType) { + case SQL_C_WCHAR: { + DASSERT(param->StrLen_or_Ind); + DASSERT(*param->StrLen_or_Ind != SQL_NTS); + size_t bytes = 0; + SQLCHAR *utf8 = wchars_to_chars(param->ParameterValue, *param->StrLen_or_Ind/2, &bytes); + bind->allocated = 1; + bind->u.bin = utf8; + bind->buffer_length = bytes; + bind->buffer = bind->u.bin; } break; - case TSDB_DATA_TYPE_BINARY: - case TSDB_DATA_TYPE_NCHAR: { - if (!pb->StrLen_or_Ind) { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, "value [@%d] bad StrLen_or_Ind", i+1); - return SQL_ERROR; + case SQL_C_BINARY: { + bind->u.bin = (unsigned char*)param->ParameterValue; + if (*param->StrLen_or_Ind == SQL_NTS) { + bind->buffer_length = strlen((const char*)param->ParameterValue); + } else { + bind->buffer_length = *param->StrLen_or_Ind; } - size_t n = *pb->StrLen_or_Ind; - if (n == SQL_NTS) { - n = strlen(pb->ParameterValue); - } else if (n < 0 || n > b->buffer_length) { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, "value [@%d] bad StrLen_or_Ind", i+1); - return SQL_ERROR; + bind->buffer = bind->u.bin; + } break; + case SQL_C_CHAR: + case SQL_C_SHORT: + case SQL_C_SSHORT: + case SQL_C_USHORT: + case SQL_C_LONG: + case SQL_C_SLONG: + case SQL_C_ULONG: + case SQL_C_FLOAT: + case SQL_C_DOUBLE: + case SQL_C_BIT: + case SQL_C_TINYINT: + case SQL_C_STINYINT: + case SQL_C_UTINYINT: + case SQL_C_SBIGINT: + case SQL_C_UBIGINT: + case SQL_C_DATE: + case SQL_C_TIME: + case SQL_C_TIMESTAMP: + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + case SQL_C_NUMERIC: + case SQL_C_GUID: + default: { + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_OUT_OF_RANGE, + "no convertion from [%s[%d/0x%x]] to [%s[%d/0x%x]] for parameter [%d]", + sql_c_type(param->ValueType), param->ValueType, param->ValueType, + taos_data_type(type), type, type, idx+1); + return SQL_ERROR; + } break; + } + } break; + case TSDB_DATA_TYPE_TIMESTAMP: { + bind->buffer_type = type; + bind->buffer_length = sizeof(bind->u.v8); + bind->buffer = &bind->u.v8; + bind->length = &bind->buffer_length; + switch (param->ValueType) { + case SQL_C_WCHAR: { + DASSERT(param->StrLen_or_Ind); + DASSERT(*param->StrLen_or_Ind != SQL_NTS); + size_t bytes = 0; + SQLCHAR *utf8 = wchars_to_chars(param->ParameterValue, *param->StrLen_or_Ind/2, &bytes); + struct tm tm = {0}; + strptime((const char*)utf8, "%Y-%m-%d %H:%M:%S", &tm); + int64_t t = (int64_t)mktime(&tm); + t *= 1000; + bind->u.v8 = t; + free(utf8); + } break; + case SQL_C_SBIGINT: { + int64_t t = *(int64_t*)param->ParameterValue; + bind->u.v8 = t; + } break; + case SQL_C_SHORT: + case SQL_C_SSHORT: + case SQL_C_USHORT: + case SQL_C_LONG: + case SQL_C_SLONG: + case SQL_C_ULONG: + case SQL_C_FLOAT: + case SQL_C_DOUBLE: + case SQL_C_BIT: + case SQL_C_TINYINT: + case SQL_C_STINYINT: + case SQL_C_UTINYINT: + case SQL_C_UBIGINT: + case SQL_C_BINARY: + case SQL_C_DATE: + case SQL_C_TIME: + case SQL_C_TIMESTAMP: + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + case SQL_C_NUMERIC: + case SQL_C_GUID: + default: { + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_OUT_OF_RANGE, + "no convertion from [%s[%d/0x%x]] to [%s[%d/0x%x]] for parameter [%d]", + sql_c_type(param->ValueType), param->ValueType, param->ValueType, + taos_data_type(type), type, type, idx+1); + return SQL_ERROR; + } break; + } + } break; + case TSDB_DATA_TYPE_NCHAR: { + bind->buffer_type = type; + bind->length = &bind->buffer_length; + switch (param->ValueType) { + case SQL_C_WCHAR: { + DASSERT(param->StrLen_or_Ind); + DASSERT(*param->StrLen_or_Ind != SQL_NTS); + size_t bytes = 0; + SQLCHAR *utf8 = wchars_to_chars(param->ParameterValue, *param->StrLen_or_Ind/2, &bytes); + bind->allocated = 1; + bind->u.nchar = (char*)utf8; + bind->buffer_length = bytes; + bind->buffer = bind->u.nchar; + } break; + case SQL_C_CHAR: { + bind->u.nchar = (char*)param->ParameterValue; + if (*param->StrLen_or_Ind == SQL_NTS) { + bind->buffer_length = strlen((const char*)param->ParameterValue); + } else { + bind->buffer_length = *param->StrLen_or_Ind; } - - b->buffer_length = n; - b->length = &b->buffer_length; - b->buffer = pb->ParameterValue; + bind->buffer = bind->u.nchar; } break; + case SQL_C_SHORT: + case SQL_C_SSHORT: + case SQL_C_USHORT: + case SQL_C_LONG: + case SQL_C_SLONG: + case SQL_C_ULONG: + case SQL_C_FLOAT: + case SQL_C_DOUBLE: + case SQL_C_BIT: + case SQL_C_TINYINT: + case SQL_C_STINYINT: + case SQL_C_UTINYINT: + case SQL_C_SBIGINT: + case SQL_C_UBIGINT: + case SQL_C_BINARY: + case SQL_C_DATE: + case SQL_C_TIME: + case SQL_C_TIMESTAMP: + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + case SQL_C_NUMERIC: + case SQL_C_GUID: default: { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, "value [@%d] not supported yet", i+1); + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_OUT_OF_RANGE, + "no convertion from [%s[%d/0x%x]] to [%s[%d/0x%x]] for parameter [%d]", + sql_c_type(param->ValueType), param->ValueType, param->ValueType, + taos_data_type(type), type, type, idx+1); return SQL_ERROR; } break; } - } + } break; + default: { + SET_ERROR(sql, "HYC00", TSDB_CODE_ODBC_OUT_OF_RANGE, + "no convertion from [%s[%d/0x%x]] to [%s[%d/0x%x]] for parameter [%d]", + sql_c_type(param->ValueType), param->ValueType, param->ValueType, + taos_data_type(type), type, type, idx+1); + return SQL_ERROR; + } break; + } + return SQL_SUCCESS; +} + +static SQLRETURN do_execute(sql_t *sql, TAOS_BIND *binds) +{ + SQLRETURN r = SQL_SUCCESS; + for (int i=0; in_params; ++i) { + r = do_bind_param_value(sql, i, sql->params+i, binds+i); + if (r==SQL_SUCCESS) continue; + return r; } if (sql->n_params > 0) { - PROFILE(r = taos_stmt_bind_param(sql->stmt, sql->binds)); + PROFILE(r = taos_stmt_bind_param(sql->stmt, binds)); if (r) { - SET_ERROR(sql, "HY000", r, "failed to bind parameters"); + SET_ERROR(sql, "HY000", r, "failed to bind parameters[%d in total]", sql->n_params); return SQL_ERROR; } @@ -1274,12 +1590,54 @@ static SQLRETURN doSQLExecute(SQLHSTMT StatementHandle) return SQL_ERROR; } - SQLRETURN ret = SQL_ERROR; - PROFILE(sql->rs = taos_stmt_use_result(sql->stmt)); - CHK_RS(ret, sql, "failed to use result"); + CHK_RS(r, sql, "failed to use result"); - return ret; + return r; +} + +static SQLRETURN doSQLExecute(SQLHSTMT StatementHandle) +{ + sql_t *sql = (sql_t*)StatementHandle; + if (!sql) return SQL_ERROR; + + CHK_CONN(sql); + CHK_CONN_TAOS(sql); + + if (!sql->stmt) { + SET_ERROR(sql, "HY010", TSDB_CODE_ODBC_STATEMENT_NOT_READY, ""); + return SQL_ERROR; + } + + if (sql->rs) { + taos_free_result(sql->rs); + sql->rs = NULL; + sql->row = NULL; + } + + TAOS_BIND *binds = NULL; + if (sql->n_params>0) { + binds = (TAOS_BIND*)calloc(sql->n_params, sizeof(*binds)); + if (!binds) { + SET_ERROR(sql, "HY001", TSDB_CODE_ODBC_OOM, ""); + return SQL_ERROR; + } + } + + SQLRETURN r = do_execute(sql, binds); + + if (binds) { + for (int i = 0; in_params; ++i) { + TAOS_BIND *bind = binds + i; + if (bind->allocated) { + free(bind->u.nchar); + bind->u.nchar = NULL; + } + } + free(binds); + } + + return r; } SQLRETURN SQL_API SQLExecute(SQLHSTMT StatementHandle) @@ -1373,159 +1731,30 @@ static SQLRETURN doSQLBindParameter( sql_t *sql = (sql_t*)StatementHandle; if (!sql) return SQL_ERROR; - if (!sql->conn) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection yet"); - return SQL_ERROR; - } - - if (!sql->conn->taos) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection to data source yet"); - return SQL_ERROR; - } + CHK_CONN(sql); + CHK_CONN_TAOS(sql); if (!sql->stmt) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_SQL, "no statement cached or not ready"); + SET_ERROR(sql, "HY010", TSDB_CODE_ODBC_STATEMENT_NOT_READY, ""); return SQL_ERROR; } if (fParamType != SQL_PARAM_INPUT) { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, "non-input parameter [@%d] not supported yet", ParameterNumber); + SET_ERROR(sql, "HY105", TSDB_CODE_ODBC_NOT_SUPPORT, "non-input parameter [@%d] not supported yet", ParameterNumber); return SQL_ERROR; } - int buffer_type = 0; - - // ref: https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/converting-data-from-c-to-sql-data-types?view=sql-server-ver15 - switch (ValueType) { - case SQL_C_BIT: { - switch (ParameterType) { - case SQL_BIT: buffer_type = TSDB_DATA_TYPE_BOOL; break; - case SQL_TINYINT: buffer_type = TSDB_DATA_TYPE_TINYINT; break; - case SQL_SMALLINT: buffer_type = TSDB_DATA_TYPE_SMALLINT; break; - case SQL_INTEGER: buffer_type = TSDB_DATA_TYPE_INT; break; - case SQL_BIGINT: buffer_type = TSDB_DATA_TYPE_BIGINT; break; - case SQL_FLOAT: buffer_type = TSDB_DATA_TYPE_FLOAT; break; - case SQL_DOUBLE: buffer_type = TSDB_DATA_TYPE_DOUBLE; break; - case SQL_VARCHAR: buffer_type = TSDB_DATA_TYPE_NCHAR; break; - default: { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, - "parameter[@%d] no conversion from [%d] to [%d]", - ParameterNumber, ValueType, ParameterType); - return SQL_ERROR; - } break; - } - } break; - case SQL_C_TINYINT: - case SQL_C_SHORT: - case SQL_C_LONG: - case SQL_C_SBIGINT: - case SQL_C_FLOAT: - case SQL_C_DOUBLE: - case SQL_C_NUMERIC: { - switch (ParameterType) { - case SQL_BIT: buffer_type = TSDB_DATA_TYPE_BOOL; break; - case SQL_TINYINT: buffer_type = TSDB_DATA_TYPE_TINYINT; break; - case SQL_SMALLINT: buffer_type = TSDB_DATA_TYPE_SMALLINT; break; - case SQL_INTEGER: buffer_type = TSDB_DATA_TYPE_INT; break; - case SQL_BIGINT: buffer_type = TSDB_DATA_TYPE_BIGINT; break; - case SQL_FLOAT: buffer_type = TSDB_DATA_TYPE_FLOAT; break; - case SQL_DOUBLE: buffer_type = TSDB_DATA_TYPE_DOUBLE; break; - case SQL_VARCHAR: buffer_type = TSDB_DATA_TYPE_NCHAR; break; - case SQL_TIMESTAMP: buffer_type = TSDB_DATA_TYPE_TIMESTAMP; break; // extention to taos - default: { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, - "parameter[@%d] no conversion from [%d] to [%d]", - ParameterNumber, ValueType, ParameterType); - return SQL_ERROR; - } break; - } - } break; - case SQL_C_DATE: - case SQL_C_TIME: - case SQL_C_TIMESTAMP: { - switch (ParameterType) { - case SQL_VARCHAR: buffer_type = TSDB_DATA_TYPE_NCHAR; break; - case SQL_TIMESTAMP: buffer_type = TSDB_DATA_TYPE_TIMESTAMP; break; // extention to taos - default: { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, - "parameter[@%d] no conversion from [%d] to [%d]", - ParameterNumber, ValueType, ParameterType); - return SQL_ERROR; - } break; - } - } break; - case SQL_C_CHAR: { - switch (ParameterType) { - case SQL_BIT: buffer_type = TSDB_DATA_TYPE_BOOL; break; - case SQL_TINYINT: buffer_type = TSDB_DATA_TYPE_TINYINT; break; - case SQL_SMALLINT: buffer_type = TSDB_DATA_TYPE_SMALLINT; break; - case SQL_INTEGER: buffer_type = TSDB_DATA_TYPE_INT; break; - case SQL_BIGINT: buffer_type = TSDB_DATA_TYPE_BIGINT; break; - case SQL_FLOAT: buffer_type = TSDB_DATA_TYPE_FLOAT; break; - case SQL_DOUBLE: buffer_type = TSDB_DATA_TYPE_DOUBLE; break; - case SQL_VARCHAR: buffer_type = TSDB_DATA_TYPE_NCHAR; break; - case SQL_VARBINARY: buffer_type = TSDB_DATA_TYPE_BINARY; break; - case SQL_TIMESTAMP: buffer_type = TSDB_DATA_TYPE_TIMESTAMP; break; // extention to taos - default: { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, - "parameter[@%d] no conversion from [%d] to [%d]", - ParameterNumber, ValueType, ParameterType); - return SQL_ERROR; - } break; - } - } break; - case SQL_C_BINARY: { - switch (ParameterType) { - case SQL_BIT: buffer_type = TSDB_DATA_TYPE_BOOL; break; - case SQL_TINYINT: buffer_type = TSDB_DATA_TYPE_TINYINT; break; - case SQL_SMALLINT: buffer_type = TSDB_DATA_TYPE_SMALLINT; break; - case SQL_INTEGER: buffer_type = TSDB_DATA_TYPE_INT; break; - case SQL_BIGINT: buffer_type = TSDB_DATA_TYPE_BIGINT; break; - case SQL_FLOAT: buffer_type = TSDB_DATA_TYPE_FLOAT; break; - case SQL_DOUBLE: buffer_type = TSDB_DATA_TYPE_DOUBLE; break; - case SQL_VARCHAR: buffer_type = TSDB_DATA_TYPE_NCHAR; break; - case SQL_VARBINARY: buffer_type = TSDB_DATA_TYPE_BINARY; break; - case SQL_TIMESTAMP: buffer_type = TSDB_DATA_TYPE_TIMESTAMP; break; // extention to taos - default: { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, - "parameter[@%d] no conversion from [%d] to [%d]", - ParameterNumber, ValueType, ParameterType); - return SQL_ERROR; - } break; - } - } break; - default: { - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, - "parameter[@%d] no conversion from [%d] to [%d]", - ParameterNumber, ValueType, ParameterType); - return SQL_ERROR; - } break; - } - param_bind_t *ar = (param_bind_t*)(sql->n_params>=ParameterNumber ? sql->params : realloc(sql->params, ParameterNumber * sizeof(*ar))); - TAOS_BIND *binds = (TAOS_BIND*)(sql->n_params>=ParameterNumber ? sql->binds : realloc(sql->binds, ParameterNumber * sizeof(*binds))); - if (!ar || !binds) { - SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_OOM, "failed to allocate resources for Parameter[%d]", ParameterNumber); - if (ar) sql->params = ar; - if (binds) sql->binds = binds; + if (!ar) { + SET_ERROR(sql, "HY001", TSDB_CODE_ODBC_OOM, ""); return SQL_ERROR; } sql->params = ar; - sql->binds = binds; if (sql->n_paramsn_params = ParameterNumber; } param_bind_t *pb = ar + ParameterNumber - 1; - TAOS_BIND *b = binds + ParameterNumber - 1; - - b->buffer_type = buffer_type; - b->buffer_length = LengthPrecision; - b->buffer = NULL; - b->length = NULL; - b->is_null = NULL; - b->is_unsigned = 0; - b->error = NULL; pb->ParameterNumber = ParameterNumber; pb->ValueType = ValueType; @@ -1571,12 +1800,12 @@ static SQLRETURN doSQLDriverConnect( if (!conn) return SQL_ERROR; if (fDriverCompletion!=SQL_DRIVER_NOPROMPT) { - SET_ERROR(conn, "HY000", TSDB_CODE_TSC_APP_ERROR, "option[%d] other than SQL_DRIVER_NOPROMPT not supported yet", fDriverCompletion); + SET_ERROR(conn, "HYC00", TSDB_CODE_ODBC_NOT_SUPPORT, "option[%d] other than SQL_DRIVER_NOPROMPT not supported yet", fDriverCompletion); return SQL_ERROR; } if (conn->taos) { - SET_ERROR(conn, "HY000", TSDB_CODE_TSC_APP_ERROR, "connection still in use"); + SET_ERROR(conn, "08002", TSDB_CODE_ODBC_CONNECTION_BUSY, "connection still in use"); return SQL_ERROR; } @@ -1591,18 +1820,16 @@ static SQLRETURN doSQLDriverConnect( do { if (szConnStrIn && !connStr) { - SET_ERROR(conn, "HY000", TSDB_CODE_ODBC_OOM, "failed to allocate resources"); + SET_ERROR(conn, "HY001", TSDB_CODE_ODBC_OOM, ""); break; } int n = sscanf((const char*)connStr, "DSN=%m[^;]; UID=%m[^;]; PWD=%m[^;] %n", &serverName, &userName, &auth, &bytes); if (n<1) { - SET_ERROR(conn, "HY000", TSDB_CODE_RPC_NETWORK_UNAVAIL, "unrecognized connection string: [%s]", (const char*)szConnStrIn); + SET_ERROR(conn, "HY000", TSDB_CODE_ODBC_BAD_CONNSTR, "unrecognized connection string: [%s]", (const char*)szConnStrIn); break; } - D("DSN:[%s];UID:[%s];PWD:[%s]", serverName, userName, auth); - // TODO: data-race // TODO: shall receive ip/port from odbc.ini conn->taos = taos_connect("localhost", userName, auth, NULL, 0); @@ -1652,11 +1879,11 @@ static SQLRETURN doSQLSetConnectAttr(SQLHDBC ConnectionHandle, if (!conn) return SQL_ERROR; if (Attribute != SQL_ATTR_AUTOCOMMIT) { - SET_ERROR(conn, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, "Attribute other than SQL_ATTR_AUTOCOMMIT not supported yet"); + SET_ERROR(conn, "HYC00", TSDB_CODE_ODBC_NOT_SUPPORT, "Attribute other than SQL_ATTR_AUTOCOMMIT not supported yet"); return SQL_ERROR; } if (Value != (SQLPOINTER)SQL_AUTOCOMMIT_ON) { - SET_ERROR(conn, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, "Attribute Value other than SQL_AUTOCOMMIT_ON not supported yet[%p]", Value); + SET_ERROR(conn, "HYC00", TSDB_CODE_ODBC_NOT_SUPPORT, "Attribute Value other than SQL_AUTOCOMMIT_ON not supported yet[%p]", Value); return SQL_ERROR; } @@ -1681,29 +1908,19 @@ static SQLRETURN doSQLDescribeCol(SQLHSTMT StatementHandle, sql_t *sql = (sql_t*)StatementHandle; if (!sql) return SQL_ERROR; - if (!sql->conn) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection yet"); - return SQL_ERROR; - } - - if (!sql->conn->taos) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_INVALID_CONNECTION, "no connection to data source yet"); - return SQL_ERROR; - } + CHK_CONN(sql); + CHK_CONN_TAOS(sql); if (!sql->rs) { - SET_ERROR(sql, "HY000", TSDB_CODE_TSC_QUERY_CACHE_ERASED, "no result set cached or not ready"); + SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_NO_RESULT, ""); return SQL_ERROR; } - TAOS_FIELD *fields = taos_fetch_fields(sql->rs); - if (!fields) { - SET_ERROR(sql, "HY000", TSDB_CODE_MND_FIELD_NOT_EXIST, "fields not ready or unavailable"); - return SQL_ERROR; - } int nfields = taos_field_count(sql->rs); - if (ColumnNumber<1 || ColumnNumber>nfields) { - SET_ERROR(sql, "HY000", TSDB_CODE_MND_FIELD_NOT_EXIST, "ColumnNumber not in valid range"); + TAOS_FIELD *fields = taos_fetch_fields(sql->rs); + + if (ColumnNumber<=0 || ColumnNumber>nfields) { + SET_ERROR(sql, "07009", TSDB_CODE_ODBC_OUT_OF_RANGE, "invalid column number [%d]", ColumnNumber); return SQL_ERROR; } @@ -1759,7 +1976,8 @@ static SQLRETURN doSQLDescribeCol(SQLHSTMT StatementHandle, } break; default: - SET_ERROR(sql, "HY000", TSDB_CODE_COM_OPS_NOT_SUPPORT, "unknown TSDB_DATA_TYPE [%x]", field->type); + SET_ERROR(sql, "HY000", TSDB_CODE_ODBC_NOT_SUPPORT, + "unknown [%s[%d/0x%x]]", taos_data_type(field->type), field->type, field->type); return SQL_ERROR; break; } @@ -1795,9 +2013,52 @@ SQLRETURN SQL_API SQLDescribeCol(SQLHSTMT StatementHandle, return r; } +static SQLRETURN doSQLNumParams(SQLHSTMT hstmt, SQLSMALLINT *pcpar) +{ + sql_t *sql = (sql_t*)hstmt; + if (!sql) return SQL_ERROR; + + CHK_CONN(sql); + CHK_CONN_TAOS(sql); + + if (!sql->stmt) { + SET_ERROR(sql, "HY010", TSDB_CODE_ODBC_STATEMENT_NOT_READY, ""); + return SQL_ERROR; + } + + int params = 0; + int r = taos_stmt_num_params(sql->stmt, ¶ms); + if (r) { + SET_ERROR(sql, "HY000", terrno, "fetch num of statement params failed"); + return SQL_ERROR; + } + + if (pcpar) *pcpar = params; + + return SQL_SUCCESS; +} + +SQLRETURN SQL_API SQLNumParams(SQLHSTMT hstmt, SQLSMALLINT *pcpar) +{ + SQLRETURN r; + r = doSQLNumParams(hstmt, pcpar); + return r; +} + + + + + + + static void init_routine(void) { + if (0) { + string_conv(NULL, NULL, NULL, 0, NULL, 0, NULL, NULL); + utf8_to_ucs4le(NULL, NULL); + ucs4le_to_utf8(NULL, 0, NULL); + } taos_init(); } @@ -1845,65 +2106,6 @@ static int do_field_display_size(TAOS_FIELD *field) { return 10; } -static void do_convert(SQLPOINTER TargetValue, SQLLEN BufferLength, SQLLEN *StrLen_or_Ind, TAOS_FIELD *field, void *row) { - switch (field->type) { - case TSDB_DATA_TYPE_TINYINT: - snprintf((char*)TargetValue, BufferLength, "%d", *((int8_t *)row)); - break; - - case TSDB_DATA_TYPE_SMALLINT: - snprintf((char*)TargetValue, BufferLength, "%d", *((int16_t *)row)); - break; - - case TSDB_DATA_TYPE_INT: - snprintf((char*)TargetValue, BufferLength, "%d", *((int32_t *)row)); - break; - - case TSDB_DATA_TYPE_BIGINT: - snprintf((char*)TargetValue, BufferLength, "%" PRId64, *((int64_t *)row)); - break; - - case TSDB_DATA_TYPE_FLOAT: { - float fv = 0; - fv = GET_FLOAT_VAL(row); - snprintf((char*)TargetValue, BufferLength, "%f", fv); - } break; - - case TSDB_DATA_TYPE_DOUBLE: { - double dv = 0; - dv = GET_DOUBLE_VAL(row); - snprintf((char*)TargetValue, BufferLength, "%lf", dv); - } break; - - case TSDB_DATA_TYPE_BINARY: - case TSDB_DATA_TYPE_NCHAR: { - size_t xlen = 0; - char *p = (char*)TargetValue; - size_t n = BufferLength; - for (xlen = 0; xlen < field->bytes - VARSTR_HEADER_SIZE; xlen++) { - char c = ((char *)row)[xlen]; - if (c == 0) break; - int v = snprintf(p, n, "%c", c); - p += v; - n -= v; - if (n<=0) break; - } - if (n>0) *p = '\0'; - ((char*)TargetValue)[BufferLength-1] = '\0'; - *StrLen_or_Ind = BufferLength - n; - } break; - - case TSDB_DATA_TYPE_TIMESTAMP: - snprintf((char*)TargetValue, BufferLength, "%" PRId64, *((int64_t *)row)); - break; - - case TSDB_DATA_TYPE_BOOL: - snprintf((char*)TargetValue, BufferLength, "%d", *((int8_t *)row)); - default: - break; - } -} - // convertion from TSDB_DATA_TYPE_XXX to SQL_C_XXX static SQLRETURN conv_tsdb_bool_to_c_bit(sql_t *sql, c_target_t *target, TAOS_FIELD *field, int8_t b) { @@ -2353,6 +2555,7 @@ static SQLRETURN conv_tsdb_ts_to_c_ts(sql_t *sql, c_target_t *target, TAOS_FIELD static SQLRETURN conv_tsdb_bin_to_c_str(sql_t *sql, c_target_t *target, TAOS_FIELD *field, const unsigned char *bin) { size_t n = field->bytes; + n = strlen((const char*)bin); *target->soi = n; if (target->ptr) memcpy(target->ptr, bin, (n>target->len ? target->len : n)); if (n <= target->len) return SQL_SUCCESS; @@ -2364,6 +2567,7 @@ static SQLRETURN conv_tsdb_bin_to_c_str(sql_t *sql, c_target_t *target, TAOS_FIE static SQLRETURN conv_tsdb_bin_to_c_bin(sql_t *sql, c_target_t *target, TAOS_FIELD *field, const unsigned char *bin) { size_t n = field->bytes; + n = strlen((const char*)bin); *target->soi = n; if (target->ptr) memcpy(target->ptr, bin, (n>target->len ? target->len : n)); if (n <= target->len) return SQL_SUCCESS; diff --git a/src/connector/odbc/src/todbc_util.c b/src/connector/odbc/src/todbc_util.c new file mode 100644 index 0000000000..9bbe8e6987 --- /dev/null +++ b/src/connector/odbc/src/todbc_util.c @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2019 TAOS Data, Inc. + * + * This program is free software: you can use, redistribute, and/or modify + * it under the terms of the GNU Affero General Public License, version 3 + * or later ("AGPL"), as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include "todbc_util.h" + +#include "iconv.h" + +#include +#include +#include +#include +#include + +const char* sql_sql_type(int type) { + switch (type) { + case SQL_BIT: return "SQL_BIT"; + case SQL_TINYINT: return "SQL_TINYINT"; + case SQL_SMALLINT: return "SQL_SMALLINT"; + case SQL_INTEGER: return "SQL_INTEGER"; + case SQL_BIGINT: return "SQL_BIGINT"; + case SQL_FLOAT: return "SQL_FLOAT"; + case SQL_DOUBLE: return "SQL_DOUBLE"; + case SQL_DECIMAL: return "SQL_DECIMAL"; + case SQL_NUMERIC: return "SQL_NUMERIC"; + case SQL_REAL: return "SQL_REAL"; + case SQL_CHAR: return "SQL_CHAR"; + case SQL_VARCHAR: return "SQL_VARCHAR"; + case SQL_LONGVARCHAR: return "SQL_LONGVARCHAR"; + case SQL_WCHAR: return "SQL_WCHAR"; + case SQL_WVARCHAR: return "SQL_WVARCHAR"; + case SQL_WLONGVARCHAR: return "SQL_WLONGVARCHAR"; + case SQL_BINARY: return "SQL_BINARY"; + case SQL_VARBINARY: return "SQL_VARBINARY"; + case SQL_LONGVARBINARY: return "SQL_LONGVARBINARY"; + case SQL_DATE: return "SQL_DATE"; + case SQL_TIME: return "SQL_TIME"; + case SQL_TIMESTAMP: return "SQL_TIMESTAMP"; + case SQL_TYPE_DATE: return "SQL_TYPE_DATE"; + case SQL_TYPE_TIME: return "SQL_TYPE_TIME"; + case SQL_TYPE_TIMESTAMP: return "SQL_TYPE_TIMESTAMP"; + case SQL_INTERVAL_MONTH: return "SQL_INTERVAL_MONTH"; + case SQL_INTERVAL_YEAR: return "SQL_INTERVAL_YEAR"; + case SQL_INTERVAL_YEAR_TO_MONTH: return "SQL_INTERVAL_YEAR_TO_MONTH"; + case SQL_INTERVAL_DAY: return "SQL_INTERVAL_DAY"; + case SQL_INTERVAL_HOUR: return "SQL_INTERVAL_HOUR"; + case SQL_INTERVAL_MINUTE: return "SQL_INTERVAL_MINUTE"; + case SQL_INTERVAL_SECOND: return "SQL_INTERVAL_SECOND"; + case SQL_INTERVAL_DAY_TO_HOUR: return "SQL_INTERVAL_DAY_TO_HOUR"; + case SQL_INTERVAL_DAY_TO_MINUTE: return "SQL_INTERVAL_DAY_TO_MINUTE"; + case SQL_INTERVAL_DAY_TO_SECOND: return "SQL_INTERVAL_DAY_TO_SECOND"; + case SQL_INTERVAL_HOUR_TO_MINUTE: return "SQL_INTERVAL_HOUR_TO_MINUTE"; + case SQL_INTERVAL_HOUR_TO_SECOND: return "SQL_INTERVAL_HOUR_TO_SECOND"; + case SQL_INTERVAL_MINUTE_TO_SECOND: return "SQL_INTERVAL_MINUTE_TO_SECOND"; + case SQL_GUID: return "SQL_GUID"; + default: return "UNKNOWN"; + } +} + +const char* sql_c_type(int type) { + switch (type) { + case SQL_C_CHAR: return "SQL_C_CHAR"; + case SQL_C_WCHAR: return "SQL_C_WCHAR"; + case SQL_C_SHORT: return "SQL_C_SHORT"; + case SQL_C_SSHORT: return "SQL_C_SSHORT"; + case SQL_C_USHORT: return "SQL_C_USHORT"; + case SQL_C_LONG: return "SQL_C_LONG"; + case SQL_C_SLONG: return "SQL_C_SLONG"; + case SQL_C_ULONG: return "SQL_C_ULONG"; + case SQL_C_FLOAT: return "SQL_C_FLOAT"; + case SQL_C_DOUBLE: return "SQL_C_DOUBLE"; + case SQL_C_BIT: return "SQL_C_BIT"; + case SQL_C_TINYINT: return "SQL_C_TINYINT"; + case SQL_C_STINYINT: return "SQL_C_STINYINT"; + case SQL_C_UTINYINT: return "SQL_C_UTINYINT"; + case SQL_C_SBIGINT: return "SQL_C_SBIGINT"; + case SQL_C_UBIGINT: return "SQL_C_UBIGINT"; + case SQL_C_BINARY: return "SQL_C_BINARY"; + case SQL_C_DATE: return "SQL_C_DATE"; + case SQL_C_TIME: return "SQL_C_TIME"; + case SQL_C_TIMESTAMP: return "SQL_C_TIMESTAMP"; + case SQL_C_TYPE_DATE: return "SQL_C_TYPE_DATE"; + case SQL_C_TYPE_TIME: return "SQL_C_TYPE_TIME"; + case SQL_C_TYPE_TIMESTAMP: return "SQL_C_TYPE_TIMESTAMP"; + case SQL_C_NUMERIC: return "SQL_C_NUMERIC"; + case SQL_C_GUID: return "SQL_C_GUID"; + default: return "UNKNOWN"; + } +} + +int string_conv(const char *fromcode, const char *tocode, + const unsigned char *src, size_t sbytes, + unsigned char *dst, size_t dbytes, + size_t *consumed, size_t *generated) +{ + if (consumed) *consumed = 0; + if (generated) *generated = 0; + + if (dbytes <= 0) return -1; + dst[0] = '\0'; + + iconv_t conv = iconv_open(tocode, fromcode); + if (!conv) return -1; + + int r = 0; + do { + char *s = (char*)src; + char *d = (char*)dst; + size_t sl = sbytes; + size_t dl = dbytes; + + r = iconv(conv, &s, &sl, &d, &dl); + *d = '\0'; + + if (consumed) *consumed = sbytes - sl; + if (generated) *generated = dbytes - dl; + + } while (0); + + iconv_close(conv); + return r; +} + +int utf8_chars(const char *src) +{ + const char *fromcode = "UTF-8"; + const char *tocode = "UCS-2LE"; + iconv_t conv = iconv_open(tocode, fromcode); + if (!conv) return -1; + + size_t slen = strlen(src); + char buf[4096]; + size_t dlen = sizeof(buf); + char *ps = (char*)src; + char *pd = buf; + iconv(conv, &ps, &slen, &pd, &dlen); + DASSERT(slen==0); + + size_t chars = (sizeof(buf) - dlen) / 2; + iconv_close(conv); + return chars; +} + +unsigned char* utf8_to_ucs4le(const char *utf8, size_t *chars) +{ + const char *tocode = "UCS-4LE"; + const char *fromcode = "UTF-8"; + + iconv_t conv = iconv_open(tocode, fromcode); + if (!conv) return NULL; + + unsigned char *ucs4le = NULL; + + do { + size_t slen = strlen(utf8); + size_t dlen = slen * 4; + + ucs4le = (unsigned char*)malloc(dlen+1); + if (!ucs4le) break; + + char *src = (char*)utf8; + char *dst = (char*)ucs4le; + size_t s = slen; + size_t d = dlen; + iconv(conv, &src, &s, &dst, &d); + dst[0] = '\0'; + + if (chars) *chars = (dlen - d) / 4; + } while (0); + + iconv_close(conv); + return ucs4le; +} + +char* ucs4le_to_utf8(const unsigned char *ucs4le, size_t slen, size_t *chars) +{ + const char *fromcode = "UCS-4LE"; + const char *tocode = "UTF-8"; + + iconv_t conv = iconv_open(tocode, fromcode); + if (!conv) return NULL; + + char *utf8 = NULL; + + do { + size_t dlen = slen; + + utf8 = (char*)malloc(dlen+1); + if (!utf8) break; + + char *dst = utf8; + char *src = (char*)ucs4le; + size_t s = slen; + size_t d = dlen; + iconv(conv, &src, &s, &dst, &d); + dst[0] = '\0'; + + if (chars) *chars = (slen - s) / 4; + } while (0); + + iconv_close(conv); + return utf8; +} + +SQLCHAR* wchars_to_chars(const SQLWCHAR *wchars, size_t chs, size_t *bytes) +{ + size_t dlen = chs * 4; + SQLCHAR *dst = (SQLCHAR*)malloc(dlen + 1); + if (!dst) return NULL; + + string_conv("UCS-2LE", "UTF-8", (const unsigned char*)wchars, chs * sizeof(*wchars), dst, dlen + 1, NULL, bytes); + + return dst; +} + diff --git a/src/connector/odbc/src/todbc_util.h b/src/connector/odbc/src/todbc_util.h new file mode 100644 index 0000000000..0400da59fe --- /dev/null +++ b/src/connector/odbc/src/todbc_util.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 TAOS Data, Inc. + * + * This program is free software: you can use, redistribute, and/or modify + * it under the terms of the GNU Affero General Public License, version 3 + * or later ("AGPL"), as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#ifndef _TODBC_UTIL_H_ +#define _TODBC_UTIL_H_ + +#include +#include +#include +#include +#include + +#define D(fmt, ...) \ + fprintf(stderr, \ + "%s[%d]:%s() " fmt "\n", \ + basename((char*)__FILE__), __LINE__, __func__, \ + ##__VA_ARGS__) + +#define DASSERT(statement) \ +do { \ + if (statement) break; \ + D("Assertion failure: %s", #statement); \ + abort(); \ +} while (0) + +const char* sql_sql_type(int type); +const char* sql_c_type(int type); + + +int string_conv(const char *fromcode, const char *tocode, + const unsigned char *src, size_t sbytes, + unsigned char *dst, size_t dbytes, + size_t *consumed, size_t *generated); +int utf8_chars(const char *src); + +unsigned char* utf8_to_ucs4le(const char *utf8, size_t *chars); +char* ucs4le_to_utf8(const unsigned char *ucs4le, size_t slen, size_t *chars); +SQLCHAR* wchars_to_chars(const SQLWCHAR *wchars, size_t chs, size_t *bytes); + +#endif // _TODBC_UTIL_H_ diff --git a/src/connector/odbc/tests/main.c b/src/connector/odbc/tests/main.c index 1d403002a8..1ac9b71369 100644 --- a/src/connector/odbc/tests/main.c +++ b/src/connector/odbc/tests/main.c @@ -193,7 +193,7 @@ int main(int argc, char *argv[]) { data.f8 = 9999999.999999; memset(data.bin, 0, sizeof(data.bin)); memset(data.blob, 0, sizeof(data.blob)); - snprintf(data.bin, sizeof(data.bin), "hello"); + snprintf(data.bin, sizeof(data.bin), "hel我lo"); snprintf(data.blob, sizeof(data.blob), "world"); snprintf(data.blob, sizeof(data.blob), "wo人rld"); SQLHSTMT stmt = {0}; diff --git a/src/connector/odbc/tests/odbc.py b/src/connector/odbc/tests/odbc.py index b12292b238..c2f2f33d2b 100644 --- a/src/connector/odbc/tests/odbc.py +++ b/src/connector/odbc/tests/odbc.py @@ -9,4 +9,48 @@ row = cursor.fetchone() while row: print(row) row = cursor.fetchone() +cursor.close() + +#cursor = cnxn.cursor() +#cursor.execute(""" +#INSERT INTO db.t values (?,?,?,?,?,?,?,?,?,?) +#""", +#"2020-12-12 00:00:00", +#1, +#27, +#32767, +#147483647, +#223372036854775807, +#23.456, +#899.999999, +#"foo", +#"bar") + +cursor = cnxn.cursor() +cursor.execute("drop database if exists db"); +cursor.close() + +cursor = cnxn.cursor() +cursor.execute("create database db"); +cursor.close() + +cursor = cnxn.cursor() +cursor.execute("create table db.t (ts timestamp, b bool, v1 tinyint, v2 smallint, v4 int, v8 bigint, f4 float, f8 double, bin binary(40), blob nchar(10))"); +cursor.close() + +cursor = cnxn.cursor() +cursor.execute("insert into db.t values('2020-10-13 06:44:00', 1, 127, 32767, 32768, 32769, 123.456, 789.987, 'hello', 'world')") +cursor.close() + +cursor = cnxn.cursor() +cursor.execute("insert into db.t values(?,?,?,?,?,?,?,?,?,?)", "2020-10-13 07:06:00", 0, 127, 32767, 32768, 32769, 123.456, 789.987, "hel后lo", "wo哈rld"); +cursor.close() + +cursor = cnxn.cursor() +cursor.execute("SELECT * from db.t") +row = cursor.fetchone() +while row: + print(row) + row = cursor.fetchone() +cursor.close() diff --git a/src/inc/taos.h b/src/inc/taos.h index 7e8f174b7c..e4fba58228 100644 --- a/src/inc/taos.h +++ b/src/inc/taos.h @@ -69,6 +69,8 @@ DLL_EXPORT int taos_options(TSDB_OPTION option, const void *arg, ...); DLL_EXPORT TAOS *taos_connect(const char *ip, const char *user, const char *pass, const char *db, uint16_t port); DLL_EXPORT void taos_close(TAOS *taos); +const char *taos_data_type(int type); + typedef struct TAOS_BIND { int buffer_type; void * buffer; @@ -77,10 +79,25 @@ typedef struct TAOS_BIND { int * is_null; int is_unsigned; // unused int * error; // unused + union { + int64_t ts; + int8_t b; + int8_t v1; + int16_t v2; + int32_t v4; + int64_t v8; + float f4; + double f8; + unsigned char *bin; + char *nchar; + } u; + unsigned int allocated; } TAOS_BIND; TAOS_STMT *taos_stmt_init(TAOS *taos); int taos_stmt_prepare(TAOS_STMT *stmt, const char *sql, unsigned long length); +int taos_stmt_num_params(TAOS_STMT *stmt, int *nums); +int taos_stmt_get_param(TAOS_STMT *stmt, int idx, int *type, int *bytes); int taos_stmt_bind_param(TAOS_STMT *stmt, TAOS_BIND *bind); int taos_stmt_add_batch(TAOS_STMT *stmt); int taos_stmt_execute(TAOS_STMT *stmt); diff --git a/src/inc/taoserror.h b/src/inc/taoserror.h index 17aa1fbfd5..1d8c50280e 100644 --- a/src/inc/taoserror.h +++ b/src/inc/taoserror.h @@ -355,6 +355,16 @@ TAOS_DEFINE_ERROR(TSDB_CODE_ODBC_OOM, 0, 0x2101, "out of mem TAOS_DEFINE_ERROR(TSDB_CODE_ODBC_CONV_UNDEF, 0, 0x2102, "convertion undefined") TAOS_DEFINE_ERROR(TSDB_CODE_ODBC_CONV_TRUNC, 0, 0x2103, "convertion truncated") TAOS_DEFINE_ERROR(TSDB_CODE_ODBC_CONV_NOT_SUPPORT, 0, 0x2104, "convertion not supported") +TAOS_DEFINE_ERROR(TSDB_CODE_ODBC_OUT_OF_RANGE, 0, 0x2105, "out of range") +TAOS_DEFINE_ERROR(TSDB_CODE_ODBC_NOT_SUPPORT, 0, 0x2106, "not supported yet") +TAOS_DEFINE_ERROR(TSDB_CODE_ODBC_INVALID_HANDLE, 0, 0x2107, "invalid handle") +TAOS_DEFINE_ERROR(TSDB_CODE_ODBC_NO_RESULT, 0, 0x2108, "no result set") +TAOS_DEFINE_ERROR(TSDB_CODE_ODBC_NO_FIELDS, 0, 0x2109, "no fields returned") +TAOS_DEFINE_ERROR(TSDB_CODE_ODBC_INVALID_CURSOR, 0, 0x2110, "invalid cursor") +TAOS_DEFINE_ERROR(TSDB_CODE_ODBC_STATEMENT_NOT_READY, 0, 0x2111, "statement not ready") +TAOS_DEFINE_ERROR(TSDB_CODE_ODBC_CONNECTION_BUSY, 0, 0x2112, "connection still busy") +TAOS_DEFINE_ERROR(TSDB_CODE_ODBC_BAD_CONNSTR, 0, 0x2113, "bad connection string") + #ifdef TAOS_ERROR_C }; -- GitLab