odbc driver

This commit is contained in:
freemine 2021-03-13 00:03:02 +08:00
parent 25e7ff1e6b
commit 12ad3514ef
76 changed files with 12944 additions and 4968 deletions

View File

@ -171,6 +171,8 @@ matrix:
- build-essential
- cmake
- binutils-2.26
- unixodbc
- unixodbc-dev
env:
- DESC="trusty/gcc-4.8/bintuils-2.26 build"
@ -198,6 +200,8 @@ matrix:
packages:
- build-essential
- cmake
- unixodbc
- unixodbc-dev
before_script:
- export TZ=Asia/Harbin
@ -252,6 +256,8 @@ matrix:
packages:
- build-essential
- cmake
- unixodbc
- unixodbc-dev
env:
- DESC="arm64 xenial build"
@ -280,6 +286,7 @@ matrix:
addons:
homebrew:
- cmake
- unixodbc
script:
- cd ${TRAVIS_BUILD_DIR}

View File

@ -19,6 +19,6 @@ ADD_SUBDIRECTORY(tsdb)
ADD_SUBDIRECTORY(wal)
ADD_SUBDIRECTORY(cq)
ADD_SUBDIRECTORY(dnode)
#ADD_SUBDIRECTORY(connector/odbc)
ADD_SUBDIRECTORY(connector/odbc)
ADD_SUBDIRECTORY(connector/jdbc)

View File

@ -261,7 +261,7 @@ static int doBindParam(char* data, SParamInfo* param, TAOS_BIND* bind) {
return TSDB_CODE_SUCCESS;
}
if (1) {
if (0) {
// allow user bind param data with different type
union {
int8_t v1;
@ -1057,14 +1057,28 @@ int taos_stmt_get_param(TAOS_STMT *stmt, int idx, int *type, int *bytes) {
}
if (pStmt->isInsert) {
SSqlObj* pSql = pStmt->pSql;
SSqlCmd *pCmd = &pSql->cmd;
STableDataBlocks* pBlock = taosArrayGetP(pCmd->pDataBlocks, 0);
SSqlCmd* pCmd = &pStmt->pSql->cmd;
STableMetaInfo* pTableMetaInfo = tscGetTableMetaInfoFromCmd(pCmd, 0, 0);
STableMeta* pTableMeta = pTableMetaInfo->pTableMeta;
if (pCmd->pTableBlockHashList == NULL) {
pCmd->pTableBlockHashList = taosHashInit(16, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BIGINT), true, false);
}
assert(pCmd->numOfParams == pBlock->numOfParams);
if (idx < 0 || idx >= pBlock->numOfParams) return -1;
STableDataBlocks* pBlock = NULL;
SParamInfo* param = pBlock->params + idx;
int32_t ret =
tscGetDataBlockFromList(pCmd->pTableBlockHashList, pTableMeta->id.uid, TSDB_PAYLOAD_SIZE, sizeof(SSubmitBlk),
pTableMeta->tableInfo.rowSize, &pTableMetaInfo->name, pTableMeta, &pBlock, NULL);
if (ret != 0) {
// todo handle error
}
if (idx<0 || idx>=pBlock->numOfParams) {
tscError("param %d: out of range", idx);
abort();
}
SParamInfo* param = &pBlock->params[idx];
if (type) *type = param->type;
if (bytes) *bytes = param->bytes;

3
src/connector/odbc/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
!c/
node_modules/
package-lock.json

View File

@ -15,7 +15,7 @@ IF (TD_LINUX_64)
message(STATUS "unixodbc/unixodbc-dev are installed, and odbc connector will be built")
find_package(FLEX)
if(NOT FLEX_FOUND)
message(FATAL_ERROR "you need to install flex first")
message(WARNING "you need to install flex first")
else ()
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5.0.0)
message(WARNING "gcc 4.8.0 will complain too much about flex-generated code, we just bypass building ODBC driver in such case")
@ -24,7 +24,7 @@ IF (TD_LINUX_64)
SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wconversion")
ADD_SUBDIRECTORY(src)
ADD_SUBDIRECTORY(tools)
ADD_SUBDIRECTORY(tests)
ADD_SUBDIRECTORY(examples)
endif ()
endif()
endif()
@ -33,10 +33,44 @@ IF (TD_LINUX_64)
ENDIF ()
ENDIF ()
IF (TD_DARWIN)
find_program(HAVE_ODBCINST NAMES odbcinst)
IF (HAVE_ODBCINST)
include(CheckSymbolExists)
# shall we revert CMAKE_REQUIRED_LIBRARIES and how?
set(CMAKE_REQUIRED_LIBRARIES odbc)
set(CMAKE_REQUIRED_INCLUDES /usr/local/include)
set(CMAKE_REQUIRED_LINK_OPTIONS -L/usr/local/lib)
check_symbol_exists(SQLExecute "sql.h" HAVE_ODBC_DEV)
if(NOT (HAVE_ODBC_DEV))
unset(HAVE_ODBC_DEV CACHE)
message(WARNING "unixodbc-dev is not installed yet, you may install it with homebrew by typing: brew install unixodbc")
else ()
message(STATUS "unixodbc/unixodbc-dev are installed, and odbc connector will be built")
find_package(FLEX)
if(NOT FLEX_FOUND)
message(WARNING "you need to install flex first")
else ()
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5.0.0)
message(WARNING "gcc 4.8.0 will complain too much about flex-generated code, we just bypass building ODBC driver in such case")
else ()
SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wconversion")
SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wconversion")
ADD_SUBDIRECTORY(src)
ADD_SUBDIRECTORY(tools)
ADD_SUBDIRECTORY(examples)
endif ()
endif()
endif()
ELSE ()
message(WARNING "unixodbc is not installed yet, you may install it under ubuntu by typing: brew install unixodbc")
ENDIF ()
ENDIF ()
IF (TD_WINDOWS_64)
find_package(ODBC)
if (NOT ODBC_FOUND)
message(FATAL_ERROR "you need to install ODBC first")
message(WARNING "you need to install ODBC first")
else ()
message(STATUS "ODBC_INCLUDE_DIRS: ${ODBC_INCLUDE_DIRS}")
message(STATUS "ODBC_LIBRARIES: ${ODBC_LIBRARIES}")
@ -50,6 +84,7 @@ IF (TD_WINDOWS_64)
else ()
ADD_SUBDIRECTORY(src)
ADD_SUBDIRECTORY(tools)
ADD_SUBDIRECTORY(tests)
ADD_SUBDIRECTORY(examples)
endif()
ENDIF ()

View File

@ -0,0 +1,169 @@
# ODBC 驱动 #
- **TAOS ODBC驱动持续更新中**
- **目前导出的ODBC函数包括(注: 有些不常用的函数只是导出,但并未实现)**:
SQLAllocEnv
SQLFreeEnv
SQLAllocConnect
SQLFreeConnect
SQLGetEnvAttr
SQLSetEnvAttr
SQLGetConnectAttr
SQLGetConnectOption
SQLGetInfo
SQLConnect
SQLDisconnect
SQLAllocStmt
SQLAllocHandle
SQLFreeHandle
SQLFreeStmt
SQLExecDirect
SQLNumResultCols
SQLRowCount
SQLColAttribute
SQLGetData
SQLFetch
SQLPrepare
SQLExecute
SQLParamData
SQLPutData
SQLGetDiagRec
SQLBindParameter
SQLDescribeParam
SQLDriverConnect
SQLSetConnectAttr
SQLDescribeCol
SQLBindCol
SQLNumParams
SQLSetStmtAttr
SQLBindParam
SQLCancel
SQLCancelHandle
SQLCloseCursor
SQLColumns
SQLCopyDesc
SQLDataSources
SQLEndTran
SQLFetchScroll
SQLGetCursorName
SQLGetDescField
SQLGetDescRec
SQLGetStmtAttr
SQLGetStmtOption
SQLGetTypeInfo
SQLSetConnectOption
SQLSetCursorName
SQLSetDescField
SQLSetDescRec
SQLSetParam
SQLSetStmtOption
SQLSpecialColumns
SQLStatistics
SQLTables
SQLTransact
`
- **国际化。可以通过在ODBC连接串中指定针对SQLCHAR/SQLWCHAR/taos_charset/system-locale的字符集来解决常见的环境匹配问题**.
- **现有的ODBC客户端工具可以籍此驱动同TAOS数据源互联包括主流linux/macosx/windows平台**
- **现有的支持ODBC的编程语言可以籍此驱动同TAOS数据源互联, 例如: c/nodejs/python/rust/go已经在上述三个主流平台测试通过, 熟悉其他语言的同学可以发现这基本上是开箱即用**
- **持续更新中**...
# 编译和测试使用
**Note**: 下述所有步骤都在TDengine项目的根目录下进行
**Note**: 请先确保src/connector/odbc如下所示被包含在src/CMakeLists.txt源文件中
```
...
ADD_SUBDIRECTORY(dnode)
ADD_SUBDIRECTORY(connector/odbc)
ADD_SUBDIRECTORY(connector/jdbc)
```
# Linux下的编译, 以Ubuntu为例
```
sudo apt install unixodbc unixodbc-dev flex
rm -rf debug && cmake -B debug && cmake --build debug && cmake --install debug && echo yes
```
# MacOSX下的编译, 以Catalina为例依赖homebrew进行第三方工具安装[https://brew.sh/]
```
brew install unixodbc
rm -rf debug && cmake -B debug && cmake --build debug && cmake --install debug && echo yes
```
# Windows下的编译, 以Windows 10为例
- 安装windows的`flex`工具. 目前我们使用[https://github.com/lexxmark/winflexbison](url). 安装完成后请确保win_flex.exe所在目录记录于`PATH`环境变量中.
- 安装Microsoft Visual Studio工具, 以VS2019为例
- `"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"`
- `rmdir /s /q debug`
- `cmake -G "NMake Makefiles" -B debug`
- `cmake --build debug`
- `cmake --install debug`
- 以管理员身份打开`命令提示符`
- 安装ODBC驱动: 在上述打开的提示符下执行 `odbcconf /A {INSTALLDRIVER "TAOS | Driver=C:/TDengine/driver/todbc.dll | ConnectFunctions=YYN | DriverODBCVer=03.00"}`
- 新增一个数据源DSN: 执行 `odbcconf /A {CONFIGDSN "TAOS" "DSN=TAOS_DSN | Server=<host>:<port>"}`
上述步骤出现失败的话,可以参看这些链接:
1. win flex的安装: https://github.com/lexxmark/winflexbison/releases
2. PATH环境变量: https://jingyan.baidu.com/article/8ebacdf02d3c2949f65cd5d0.html
3. 管理员身份: https://blog.csdn.net/weixin_41083002/article/details/81019893
4. 安装odbc驱动/数据源: https://docs.microsoft.com/en-us/sql/odbc/odbcconf-exe?view=sql-server-ver15
# 测试使用
强烈建议您在linux上编译运行taosd服务端因为当前TAOS还没有windows侧的服务端移植.
**Note1**: <>符号所括起的内容请按您当前的系统填写
**Note2**: `.stmts` 文件存放的是测试用sql语句, 注意其格式为`UTF-8`(不带BOM导引头)
## 按官方文档在linux侧启动taosd,确保选用'UTF-8'作为其字符集
## 在linux下创建数据
```
./debug/build/bin/tcodbc --dsn TAOS_DSN --uid <uid> --pwd <pwd> --sts ./src/connector/odbc/samples/create_data.stmts
--<或指定特殊的ODBC连接字符串 -->
./debug/build/bin/tcodbc -C 'DSN=TAOS_DSN;UID=<uid>;PWD=<pwd>;Server=<fqdn>:<port>' --sts ./src/connector/odbc/samples/create_data.stmts
```
## 在windows下检索数据
```
.\debug\build\bin\tcodbc -C "DSN=TAOS_DSN;UID=<uid>;PWD=<pwd>;Server=<fqdn>:<port>;enc_char=UTF-8" --sts .\src\connector\odbc\samples\query_data.stmts
```
## 在MacOSX下检索数据
```
./debug/build/bin/tcodbc -C "DSN=TAOS_DSN;UID=<uid>;PWD=<pwd>;Server=<fqdn>:<port>" --sts ./src/connector/odbc/samples/query_data.stmts
```
## 代码示例
- src/connector/odbc/examples/c
- src/connector/odbc/examples/js
- src/connector/odbc/examples/py
- src/connector/odbc/examples/rust
- src/connector/odbc/examples/go
在linux或MacOSX上, 可以通过修改运行如下脚本来尝试各种测试:
**Note**: 不要忘记替换<host>:<port>
**Note**: 你需要在你的平台上安装nodejs/python/rust/go
**Note**: 你还需要安装对应语言的ODBC包:
-- node-odbc for nodejs: https://www.npmjs.com/package/odbc
-- pyodbc for python: https://pypi.org/project/pyodbc/
-- rust-odbc for rust: https://docs.rs/odbc/0.17.0/odbc/
-- go-odbc for go: https://github.com/alexbrainman/odbc
```
echo c &&
./debug/build/bin/tcodbc -C "DSN=TAOS_DSN;Server=<host>:<port>" --sts src/connector/odbc/samples/create_data.stmts &&
echo nodejs &&
./src/connector/odbc/examples/js/odbc.js -C 'DSN=TAOS_DSN;Server=<host>:<port>' &&
echo python &&
python3 src/connector/odbc/examples/py/odbc.py -C 'DSN=TAOS_DSN;Server=<host>:<port>' &&
echo rust &&
pushd src/connector/odbc/examples/rust/main && DSN='DSN=TAOS_DSN;Server=<host>:<port>' cargo run && popd &&
echo go &&
DSN='DSN=TAOS_DSN;Server=<host>:<port>' go run src/connector/odbc/examples/go/odbc.go &&
```
## 您可以对比测试一下prepared-batch-insert是否会带来速度的提升:
**注** src/connector/odbc/examples/c/main.c是tcodbc的源代码
```
./debug/build/bin/tcodbc -C "DSN=TAOS_DSN;Server=<host>:<port>" --insert --batch_size 200 --batchs 10000
```

View File

@ -1,20 +1,25 @@
# ODBC Driver #
- **very initial implementation of ODBC driver for TAOS
- **on-going implementation of ODBC driver for TAOS**
- **currently partially supported ODBC functions are: `
- **currently exported ODBC functions are**:
SQLAllocEnv
SQLFreeEnv
SQLAllocConnect
SQLFreeConnect
SQLGetEnvAttr
SQLSetEnvAttr
SQLGetConnectAttr
SQLGetConnectOption
SQLGetInfo
SQLConnect
SQLDisconnect
SQLAllocStmt
SQLAllocHandle
SQLFreeHandle
SQLFreeStmt
SQLExecDirect
SQLExecDirectW
SQLNumResultCols
SQLRowCount
SQLColAttribute
@ -22,29 +27,62 @@ SQLGetData
SQLFetch
SQLPrepare
SQLExecute
SQLGetDiagField
SQLParamData
SQLPutData
SQLGetDiagRec
SQLBindParameter
SQLDescribeParam
SQLDriverConnect
SQLSetConnectAttr
SQLDescribeCol
SQLBindCol
SQLNumParams
SQLSetStmtAttr
ConfigDSN
SQLBindParam
SQLCancel
SQLCancelHandle
SQLCloseCursor
SQLColumns
SQLCopyDesc
SQLDataSources
SQLEndTran
SQLFetchScroll
SQLGetCursorName
SQLGetDescField
SQLGetDescRec
SQLGetStmtAttr
SQLGetStmtOption
SQLGetTypeInfo
SQLSetConnectOption
SQLSetCursorName
SQLSetDescField
SQLSetDescRec
SQLSetParam
SQLSetStmtOption
SQLSpecialColumns
SQLStatistics
SQLTables
SQLTransact
`
- **internationalized, you can specify different charset/code page for easy going. eg.: insert `utf-8.zh_cn` characters into database located in linux machine, while query them out in `gb2312/gb18030/...` code page in your chinese windows machine, or vice-versa. and much fun, insert `gb2312/gb18030/...` characters into database located in linux box from
your japanese windows box, and query them out in your local chinese windows machine.
- **internationalized, you can specify charset for SQLCHAR/SQLWCHAR/taos_charset/system-locale to coordinate with the environment**.
- **enable ODBC-aware software to communicate with TAOS.
- **enable ODBC-aware software to communicate with TAOS, no matter what platform it's running on, currently we support linux/macosx/windows**
- **enable any language with ODBC-bindings/ODBC-plugings to communicate with TAOS
- **enable any language with ODBC-bindings/ODBC-plugings to communicate with TAOS, currently c/nodejs/python/rust/go are all passed in our test environment, we believe other languages with ODBC-bindings/plugins are available-out-of-box**
- **still going on...
- **still going on**...
# Building and Testing
**Note**: all `work` is done in TDengine's project directory
**Note**: please make sure src/connector/odbc is included in src/CMakeLists.txt
```
...
ADD_SUBDIRECTORY(dnode)
ADD_SUBDIRECTORY(connector/odbc)
ADD_SUBDIRECTORY(connector/jdbc)
```
# Building under Linux, use Ubuntu as example
```
@ -53,36 +91,68 @@ rm -rf debug && cmake -B debug && cmake --build debug && cmake --install debug &
```
# Building under Windows, use Windows 10 as example
- install windows `flex` port. We use [https://github.com/lexxmark/winflexbison](url) at the moment. Please be noted to append `<path_to_win_flex.exe>` to your `PATH`.
- install Microsoft Visual Studio, take VS2015 as example here
- `"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64`
- install Microsoft Visual Studio, take VS2019 as example here
- `"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"`
- `rmdir /s /q debug`
- `cmake -G "NMake Makefiles" -B debug`
- `cmake --build debug`
- `cmake --install debug`
- open your `Command Prompt` with Administrator's priviledge
- remove previously installed TAOS ODBC driver: run `C:\TDengine\todbcinst -u -f -n TAOS`
- install TAOS ODBC driver that was just built: run `C:\TDengine\todbcinst -i -n TAOS -p C:\TDengine\driver`
- add a new user dsn: run `odbcconf CONFIGDSN TAOS "DSN=TAOS_DSN|Server=<fqdn>:<port>`
- install TAOS ODBC driver that was just built: run `odbcconf /A {INSTALLDRIVER "TAOS | Driver=C:/TDengine/driver/todbc.dll | ConnectFunctions=YYN | DriverODBCVer=03.00"}`
- add a new user dsn: run `odbcconf /A {CONFIGDSN "TAOS" "DSN=TAOS_DSN | Server=host:port"}`
# Test
we highly suggest that you build both in linux(ubuntu) and windows(windows 10) platform, because currently TAOS only has it's server-side port on linux platform.
we highly suggest that you build both in linux(ubuntu) and windows(windows 10) platform, because currently TAOS has not server-side port on windows platform.
**Note1**: content within <> shall be modified to match your environment
**Note2**: `.stmts` source files are all encoded in `UTF-8`
## start taosd in linux, suppose charset is `UTF-8` as default
```
taosd -c ./debug/test/cfg
```
## start taosd in linux, suppose charset is `UTF-8` as default, please follow TAOS doc for starting up
## create data in linux
```
./debug/build/bin/tcodbc --dsn TAOS_DSN --uid <uid> --pwd <pwd> --sts ./src/connector/odbc/tests/create_data.stmts
./debug/build/bin/tcodbc --dsn TAOS_DSN --uid <uid> --pwd <pwd> --sts ./src/connector/odbc/samples/create_data.stmts
--<or with driver connection string -->
./debug/build/bin/tcodbc --dcs 'Driver=TAOS;UID=<uid>;PWD=<pwd>;Server=<fqdn>:<port>;client_enc=UTF-8' ./src/connector/odbc/tests/create_data.stmts
./debug/build/bin/tcodbc -C 'DSN=TAOS_DSN;UID=<uid>;PWD=<pwd>;Server=<fqdn>:<port>' --sts ./src/connector/odbc/samples/create_data.stmts
```
## query data in windows
```
.\debug\build\bin\tcodbc --dsn TAOS_DSN --uid <uid> --pwd <pwd> --sts .\src\connector\odbc\tests\query_data.stmts
--<or with driver connection string -->
.\debug\build\bin\tcodbc --dcs "Driver=TAOS;UID=<uid>;PWD=<pwd>;Server=<fqdn>:<port>;client_enc=UTF-8" .\src\connector\odbc\tests\query_data.stmts
.\debug\build\bin\tcodbc -C "DSN=TAOS_DSN;UID=<uid>;PWD=<pwd>;Server=<fqdn>:<port>;enc_char=UTF-8" --sts .\src\connector\odbc\samples\query_data.stmts
```
## query data in MacOSX
```
./debug/build/bin/tcodbc -C "DSN=TAOS_DSN;UID=<uid>;PWD=<pwd>;Server=<fqdn>:<port>" --sts ./src/connector/odbc/samples/query_data.stmts
```
## code examples
- src/connector/odbc/examples/c
- src/connector/odbc/examples/js
- src/connector/odbc/examples/py
- src/connector/odbc/examples/rust
- src/connector/odbc/examples/go
on linux/MacOSX, here after are script-snippet for you to play with:
**Note**: don't forget to replace <host>:<port> with whatever on your environment
**Note**: you need to install node/python3/rust/go on you machine
**Note**: you also need to install odbc-bindings/odbc-pluggins on those language platform, such as:
-- node-odbc for nodejs: https://www.npmjs.com/package/odbc
-- pyodbc for python: https://pypi.org/project/pyodbc/
-- rust-odbc for rust: https://docs.rs/odbc/0.17.0/odbc/
-- go-odbc for go: https://github.com/alexbrainman/odbc
```
echo c &&
./debug/build/bin/tcodbc -C "DSN=TAOS_DSN;Server=<host>:<port>" --sts src/connector/odbc/samples/create_data.stmts &&
echo nodejs &&
./src/connector/odbc/examples/js/odbc.js -C 'DSN=TAOS_DSN;Server=<host>:<port>' &&
echo python &&
python3 src/connector/odbc/examples/py/odbc.py -C 'DSN=TAOS_DSN;Server=<host>:<port>' &&
echo rust &&
pushd src/connector/odbc/examples/rust/main && DSN='DSN=TAOS_DSN;Server=<host>:<port>' cargo run && popd &&
echo go &&
DSN='DSN=TAOS_DSN;Server=<host>:<port>' go run src/connector/odbc/examples/go/odbc.go &&
```
## see how fast prepared-statment could bring up with:
```
./debug/build/bin/tcodbc -C "DSN=TAOS_DSN;Server=<host>:<port>" --insert --batch_size 200 --batchs 10000
```

View File

@ -0,0 +1,4 @@
PROJECT(TDengine)
ADD_SUBDIRECTORY(c)

View File

@ -0,0 +1,22 @@
PROJECT(TDengine)
ADD_EXECUTABLE(tcodbc main.c ../../src/todbc_log.c)
IF (TD_LINUX OR TD_DARWIN)
TARGET_LINK_LIBRARIES(tcodbc odbc)
ENDIF ()
IF (TD_DARWIN)
target_include_directories(tcodbc PRIVATE /usr/local/include)
target_link_directories(tcodbc PUBLIC /usr/local/lib)
ENDIF ()
IF (TD_WINDOWS_64)
SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /GL")
SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /GL")
TARGET_LINK_LIBRARIES(tcodbc odbc32 odbccp32 user32 legacy_stdio_definitions os)
ADD_EXECUTABLE(tms main.cpp)
TARGET_LINK_LIBRARIES(tms odbc32)
ENDIF ()

View File

@ -0,0 +1,808 @@
#include "../../src/todbc_log.h"
#ifdef _MSC_VER
#include <winsock2.h>
#include <windows.h>
#include "os.h"
#endif
#include <sql.h>
#include <sqlext.h>
#include <odbcinst.h>
#include <stdio.h>
#include <string.h>
#define CHK_TEST(statement) \
do { \
D("testing: %s", #statement); \
int r = (statement); \
if (r) { \
D("testing failed: %s", #statement); \
return 1; \
} \
} while (0);
typedef struct {
int batch_size;
int batchs;
int keep_stmt_among_batchs;
} insert_arg_t;
typedef struct db_column_s db_column_t;
struct db_column_s {
SQLSMALLINT nameLength;
char name[4096]; // seems enough
SQLSMALLINT dataType;
SQLULEN columnSize;
SQLSMALLINT decimalDigits;
SQLSMALLINT nullable;
};
static db_column_t *columns = NULL;
typedef struct data_s data_t;
struct data_s {
int64_t ts;
int8_t b;
int8_t v1;
int16_t v2;
int32_t v4;
int64_t v8;
float f4;
double f8;
char bin[40+1];
char blob[40+1]; // why 80? ref: tests/examples/c/apitest.c
};
#define CHK_RESULT(r, ht, h, fmt, ...) \
do { \
if (r==0) break; \
SQLSMALLINT i_0381 = 1; \
while (1) { \
SQLCHAR ss[10]; \
SQLINTEGER ne = 0; \
SQLCHAR es[4096]; \
SQLSMALLINT n = 0; \
ss[0] = '\0'; \
es[0] = '\0'; \
SQLRETURN ret = SQLGetDiagRec(ht, h, i_0381, ss, &ne, es, sizeof(es), &n); \
if (ret) break; \
D("[%s]%s: " fmt "", ss, es, ##__VA_ARGS__); \
++i_0381; \
} \
} while (0)
static int open_connect(const char *dsn, const char *uid, const char *pwd, SQLHENV *pEnv, SQLHDBC *pConn) {
SQLRETURN r;
SQLHENV env = {0};
SQLHDBC conn = {0};
r = SQLAllocEnv(&env);
if (r!=SQL_SUCCESS) return 1;
do {
r = SQLAllocConnect(env, &conn);
CHK_RESULT(r, SQL_HANDLE_ENV, env, "");
if (r!=SQL_SUCCESS) break;
do {
r = SQLConnect(conn, (SQLCHAR*)dsn, (SQLSMALLINT)(dsn ? strlen(dsn) : 0),
(SQLCHAR*)uid, (SQLSMALLINT)(uid ? strlen(uid) : 0),
(SQLCHAR*)pwd, (SQLSMALLINT)(pwd ? strlen(pwd) : 0));
CHK_RESULT(r, SQL_HANDLE_DBC, conn, "");
if (r==SQL_SUCCESS) {
*pEnv = env;
*pConn = conn;
return 0;
}
} while (0);
SQLFreeConnect(conn);
} while (0);
SQLFreeEnv(env);
return 1;
}
static int open_driver_connect(const char *connstr, SQLHENV *pEnv, SQLHDBC *pConn) {
SQLRETURN r;
SQLHENV env = {0};
SQLHDBC conn = {0};
r = SQLAllocEnv(&env);
if (r!=SQL_SUCCESS) return 1;
do {
r = SQLAllocConnect(env, &conn);
CHK_RESULT(r, SQL_HANDLE_ENV, env, "");
if (r!=SQL_SUCCESS) break;
do {
SQLCHAR buf[4096];
SQLSMALLINT blen = 0;
SQLHDBC ConnectionHandle = conn;
SQLHWND WindowHandle = NULL;
SQLCHAR * InConnectionString = (SQLCHAR*)connstr;
SQLSMALLINT StringLength1 = (SQLSMALLINT)(connstr ? strlen(connstr) : 0);
SQLCHAR * OutConnectionString = buf;
SQLSMALLINT BufferLength = sizeof(buf);
SQLSMALLINT * StringLength2Ptr = &blen;
SQLUSMALLINT DriverCompletion = SQL_DRIVER_NOPROMPT;
r = SQLDriverConnect(ConnectionHandle, WindowHandle, InConnectionString,
StringLength1, OutConnectionString, BufferLength,
StringLength2Ptr, DriverCompletion);
CHK_RESULT(r, SQL_HANDLE_DBC, conn, "");
if (r==SQL_SUCCESS) {
*pEnv = env;
*pConn = conn;
return 0;
}
} while (0);
SQLFreeConnect(conn);
} while (0);
SQLFreeEnv(env);
return 1;
}
static SQLRETURN traverse_cols(SQLHSTMT stmt, SQLSMALLINT cols) {
SQLRETURN r = SQL_ERROR;
for (SQLSMALLINT i=0; i<cols; ++i) {
db_column_t column = {0};
r = SQLDescribeCol(stmt, (SQLUSMALLINT)(i+1), (SQLCHAR*)column.name,
(SQLSMALLINT)sizeof(column.name), &column.nameLength,
&column.dataType, &column.columnSize,
&column.decimalDigits, &column.nullable);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
D("col%02d:[%s]%d,type:[%d],colSize:[%zu],decimalDigits:[%d],nullable:[%d]",
i+1, column.name, column.nameLength, column.dataType, column.columnSize,
column.decimalDigits, column.nullable);
db_column_t *col = (db_column_t*)realloc(columns, (size_t)(i+1)*sizeof(*col));
if (!col) {
D("out of memory");
return SQL_ERROR;
}
col[i] = column;
columns = col;
}
return SQL_SUCCESS;
}
static int do_statement(SQLHSTMT stmt, const char *statement) {
SQLRETURN r = 0;
do {
r = SQLExecDirect(stmt, (SQLCHAR*)statement, SQL_NTS);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: [%s]", statement);
if (r) break;
SQLSMALLINT cols = 0;
r = SQLNumResultCols(stmt, &cols);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
if (cols <= 0) break;
r = traverse_cols(stmt, cols);
char buf[4096];
while (1) {
SQLRETURN r = SQLFetch(stmt);
if (r==SQL_NO_DATA) break;
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
for (size_t i=0; i<cols; ++i) {
SQLLEN soi = 0;
r = SQLGetData(stmt, (SQLUSMALLINT)(i+1), SQL_C_CHAR, buf, sizeof(buf), &soi);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "i/cols:[%ld/%d]", i,cols);
if (r) {
if (r!=SQL_SUCCESS_WITH_INFO) {
if (i>0) fprintf(stdout, "\n");
return r;
}
}
if (soi==SQL_NULL_DATA) {
fprintf(stdout, "%snull", i==0?"":",");
} else {
fprintf(stdout, "%s\"%s\"", i==0?"":",", buf);
}
}
fprintf(stdout, "\n");
}
} while (0);
return r;
}
int test_statements(const char *dsn, const char *uid, const char *pwd, const char **statements) {
SQLRETURN r = SQL_SUCCESS;
SQLHENV env = {0};
SQLHDBC conn = {0};
int n = open_connect(dsn, uid, pwd, &env, &conn);
if (n) return 1;
do {
SQLHSTMT stmt = {0};
r = SQLAllocHandle(SQL_HANDLE_STMT, conn, &stmt);
if (r!=SQL_SUCCESS) break;
const char **p = statements;
while (*p) {
if (do_statement(stmt, *p)) {
r = SQL_ERROR;
break;
}
++p;
}
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
} while (0);
SQLDisconnect(conn);
SQLFreeConnect(conn);
SQLFreeEnv(env);
return r ? 1 : 0;
}
int test_driver_connect(const char *connstr) {
SQLRETURN r = SQL_SUCCESS;
SQLHENV env = {0};
SQLHDBC conn = {0};
int n = open_driver_connect(connstr, &env, &conn);
if (n) return 1;
SQLDisconnect(conn);
SQLFreeConnect(conn);
SQLFreeEnv(env);
return r ? 1 : 0;
}
int create_statement(SQLHENV env, SQLHDBC conn, SQLHSTMT *pStmt) {
SQLHSTMT stmt = {0};
SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, conn, &stmt);
CHK_RESULT(r, SQL_HANDLE_DBC, conn, "");
if (r==SQL_SUCCESS) {
*pStmt = stmt;
return 0;
}
if (r==SQL_SUCCESS_WITH_INFO) {
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
}
return 1;
}
int do_statements(SQLHSTMT stmt, const char **statements) {
const char **p = statements;
while (p && *p) {
CHK_TEST(do_statement(stmt, *p));
++p;
}
return 0;
}
int tests_stmt(SQLHENV env, SQLHDBC conn, SQLHSTMT stmt) {
const char *statements[] = {
"drop database if exists m",
"create database m",
"use m",
// "create table t (ts timestamp, b bool, v1 tinyint, v2 smallint, v4 int, v8 bigint, f4 float, f8 double, blob binary(1), name nchar(1))",
"create table t (ts timestamp, b bool)",
"insert into t values('2020-10-10 00:00:00', 0)",
"insert into t values('2020-10-10 00:00:00.001', 1)",
NULL
};
CHK_TEST(do_statements(stmt, statements));
return 0;
}
int tests(SQLHENV env, SQLHDBC conn) {
SQLHSTMT stmt = {0};
CHK_TEST(create_statement(env, conn, &stmt));
int r = tests_stmt(env, conn, stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return r ? 1 : 0;
}
int test_env(void) {
SQLRETURN r;
SQLHENV env = {0};
r = SQLAllocEnv(&env);
if (r!=SQL_SUCCESS) return 1;
SQLFreeEnv(env);
return 0;
}
static int test_sqls_in_stmt(SQLHENV env, SQLHDBC conn, SQLHSTMT stmt, const char *sqls) {
FILE *f = fopen(sqls, "rb");
if (!f) {
D("failed to open file [%s]", sqls);
return -1;
}
int r = 0;
while (!feof(f)) {
char *line = NULL;
size_t len = 0;
ssize_t n = 0;
#ifdef _MSC_VER
n = taosGetlineImp(&line, &len, f);
#else
n = getline(&line, &len, f);
#endif
if (n==-1) break;
const char *p = NULL;
do {
if (line[0] == '#') break;
if (n>0 && line[n-1] == '\n') line[n-1]='\0';
if (n>0 && line[n-1] == '\r') line[n-1]='\0';
if (n>1 && line[n-2] == '\r') line[n-2]='\0';
p = line;
while (isspace(*p)) ++p;
if (*p==0) break;
int positive = 1;
if (strncmp(p, "N:", 2)==0) {
// negative sample
positive = 0;
p += 2;
} else if (strncmp(p, "P:", 2)==0) {
// positive sample
p += 2;
}
D("statement: [%s]", p);
r = do_statement(stmt, p);
if (positive && r==0) break;
if (!positive && r) { r = 0; break; }
if (positive) return r;
D("expecting negative result, but got positive");
return -1;
} while (0);
free(line);
if (r) break;
}
fclose(f);
return r ? 1 : 0;
}
static int test_sqls_in_conn(SQLHENV env, SQLHDBC conn, const char *sqls) {
SQLHSTMT stmt = {0};
CHK_TEST(create_statement(env, conn, &stmt));
int r = test_sqls_in_stmt(env, conn, stmt, sqls);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return r ? 1 : 0;
}
static int test_sqls(const char *dsn, const char *uid, const char *pwd, const char *connstr, const char *sqls) {
int r = 0;
SQLHENV env = {0};
SQLHDBC conn = {0};
if (dsn) {
CHK_TEST(open_connect(dsn, uid, pwd, &env, &conn));
} else {
CHK_TEST(open_driver_connect(connstr, &env, &conn));
}
if (sqls) {
r = test_sqls_in_conn(env, conn, sqls);
}
SQLDisconnect(conn);
SQLFreeConnect(conn);
SQLFreeEnv(env);
return r ? 1 : 0;
}
typedef struct record_s record_t;
struct record_s {
int dummy;
char ts[64];
SQLLEN ts_len;
int32_t v1;
SQLLEN v1_len;
char ts2[64];
SQLLEN ts2_len;
};
static int do_prepare_in_stmt(SQLHENV env, SQLHDBC conn, SQLHSTMT stmt) {
SQLRETURN r = SQL_SUCCESS;
do {
const char *sql = "insert into m.v (ts, v1, ts2) values (?, ?, ?)";
r = SQLPrepare(stmt, (SQLCHAR*)sql, SQL_NTS);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
record_t records[] = {
{0, "2020-01-03 11:22:33.345", SQL_NTS, 1, sizeof(int32_t), "2020-01-02 11:22:33.455", SQL_NTS},
{0, "2020-01-03 11:22:34.346", SQL_NTS, 2, sizeof(int32_t), "2020-01-02 11:22:34.445", SQL_NTS},
{0, "2020-01-04 11:22:34.345", SQL_NTS, 2, sizeof(int32_t), "2020-01-02 11:22:34.445", SQL_NTS},
{0, "2020-01-05 11:22:34.345", SQL_NTS, 2, sizeof(int32_t), "2020-01-02 11:22:34.445", SQL_NTS},
};
record_t *base = (record_t*)0;
r = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(base->ts)-1, 0, base->ts, sizeof(base->ts), &(base->ts_len));
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
r = SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &base->v1, 0, &(base->v1_len));
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
r = SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(base->ts2)-1, 0, base->ts2, sizeof(base->ts2), &(base->ts2_len));
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
SQLSetStmtAttr(stmt, SQL_ATTR_PARAM_BIND_TYPE, (SQLPOINTER)sizeof(*base), 0);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
SQLSetStmtAttr(stmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)(sizeof(records)/sizeof(records[0])), 0);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
record_t *record = NULL;
SQLSetStmtAttr(stmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, &record, 0);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
record = records;
r = SQLExecute(stmt);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
} while (0);
return r ? -1 : 0;
}
static int do_prepare_in_conn(SQLHENV env, SQLHDBC conn) {
SQLHSTMT stmt = {0};
CHK_TEST(create_statement(env, conn, &stmt));
int r = do_prepare_in_stmt(env, conn, stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return r ? 1 : 0;
}
static int do_prepare(const char *dsn, const char *uid, const char *pwd, const char *connstr) {
int r = 0;
SQLHENV env = {0};
SQLHDBC conn = {0};
if (dsn) {
CHK_TEST(open_connect(dsn, uid, pwd, &env, &conn));
} else {
CHK_TEST(open_driver_connect(connstr, &env, &conn));
}
r = do_prepare_in_conn(env, conn);
SQLDisconnect(conn);
SQLFreeConnect(conn);
SQLFreeEnv(env);
return r ? 1 : 0;
}
typedef struct {
int dummy;
int64_t ts;
SQLLEN ts_len;
int8_t v1;
SQLLEN v1_len;
int16_t v2;
SQLLEN v2_len;
int32_t v4;
SQLLEN v4_len;
int64_t v8;
SQLLEN v8_len;
} test_v_t;
static int do_insert_in_stmt(SQLHENV env, SQLHDBC conn, SQLHSTMT stmt, int64_t *ts, insert_arg_t *arg) {
SQLRETURN r = SQL_SUCCESS;
int batch_size = arg->batch_size;
test_v_t *recs = NULL;
do {
const char *sql = "insert into test.v (ts, v1, v2, v4, v8) values (?, ?, ?, ?, ?)";
r = SQLPrepare(stmt, (SQLCHAR*)sql, SQL_NTS);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
test_v_t *base = NULL;
r = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, 0, 0, &base->ts, 0, &base->ts_len);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
r = SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_TINYINT, SQL_TINYINT, 0, 0, &base->v1, 0, &base->v1_len);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
r = SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_SHORT, SQL_SMALLINT, 0, 0, &base->v2, 0, &base->v2_len);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
r = SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &base->v4, 0, &base->v4_len);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
r = SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, 0, 0, &base->v8, 0, &base->v8_len);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
SQLSetStmtAttr(stmt, SQL_ATTR_PARAM_BIND_TYPE, (SQLPOINTER)sizeof(*base), 0);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
base = NULL;
SQLSetStmtAttr(stmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, &base, 0);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
size_t n_recs = (size_t)batch_size;
recs = (test_v_t*)calloc(n_recs, sizeof(*recs));
OILE(recs, "");
SQLSetStmtAttr(stmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)n_recs, 0);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
base = recs;
for (int batch=0; batch<arg->batchs; ++batch) {
for (int i=0; i<n_recs; ++i) {
test_v_t *rec = recs + i;
rec->dummy = 0;
rec->ts = *ts + i;
rec->ts_len = sizeof(rec->ts);
rec->v1 = (int8_t)rand();
rec->v1_len = sizeof(rec->v1);
rec->v2 = (int16_t)rand();
rec->v2_len = sizeof(rec->v2);
rec->v4 = rand();
rec->v4_len = sizeof(rec->v4);
rec->v8 = rand();
rec->v8_len = sizeof(rec->v8);
}
*ts += (int64_t)n_recs;
r = SQLExecute(stmt);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
}
} while (0);
free(recs);
return r ? -1 : 0;
}
static int do_insert_in_conn(SQLHENV env, SQLHDBC conn, insert_arg_t *arg) {
SQLHSTMT stmt = {0};
int64_t ts = 1502535178128;
int r = 0;
CHK_TEST(create_statement(env, conn, &stmt));
for (int i=0; i<1 && i<arg->batchs; ++i) {
r = do_insert_in_stmt(env, conn, stmt, &ts, arg);
if (r) break;
if (!arg->keep_stmt_among_batchs) {
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
r = create_statement(env, conn, &stmt);
if (r) break;
}
}
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return r ? 1 : 0;
}
static int do_insert_batch(const char *dsn, const char *uid, const char *pwd, const char *connstr, insert_arg_t *arg) {
int r = 0;
SQLHENV env = {0};
SQLHDBC conn = {0};
if (dsn) {
CHK_TEST(open_connect(dsn, uid, pwd, &env, &conn));
} else {
CHK_TEST(open_driver_connect(connstr, &env, &conn));
}
const char *sqls[] = {
"drop database if exists test",
"create database test",
"create table test.v (ts timestamp, v1 tinyint, v2 smallint, v4 int, v8 bigint)",
NULL
};
SQLHSTMT stmt = {0};
CHK_TEST(create_statement(env, conn, &stmt));
CHK_TEST(do_statements(stmt, sqls));
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
OD("................");
r = do_insert_in_conn(env, conn, arg);
OD("................");
SQLDisconnect(conn);
SQLFreeConnect(conn);
SQLFreeEnv(env);
return r ? 1 : 0;
}
static int do_debug_col_name_max_len(const char *dsn, const char *uid, const char *pwd, const char *connstr) {
SQLRETURN r;
SQLHENV env = {0};
SQLHDBC conn = {0};
r = SQLAllocEnv(&env);
if (r!=SQL_SUCCESS) {
D("SQLAllocEnv failed");
return 1;
};
do {
r = SQLAllocConnect(env, &conn);
CHK_RESULT(r, SQL_HANDLE_ENV, env, "");
if (r!=SQL_SUCCESS) break;
do {
if (dsn) {
r = SQLConnect(conn, (SQLCHAR*)dsn, (SQLSMALLINT)(dsn ? strlen(dsn) : 0),
(SQLCHAR*)uid, (SQLSMALLINT)(uid ? strlen(uid) : 0),
(SQLCHAR*)pwd, (SQLSMALLINT)(pwd ? strlen(pwd) : 0));
} else {
SQLCHAR buf[4096];
SQLSMALLINT blen = 0;
SQLHDBC ConnectionHandle = conn;
SQLHWND WindowHandle = NULL;
SQLCHAR * InConnectionString = (SQLCHAR*)connstr;
SQLSMALLINT StringLength1 = (SQLSMALLINT)(connstr ? strlen(connstr) : 0);
SQLCHAR * OutConnectionString = buf;
SQLSMALLINT BufferLength = sizeof(buf);
SQLSMALLINT * StringLength2Ptr = &blen;
SQLUSMALLINT DriverCompletion = SQL_DRIVER_NOPROMPT;
r = SQLDriverConnect(ConnectionHandle, WindowHandle, InConnectionString,
StringLength1, OutConnectionString, BufferLength,
StringLength2Ptr, DriverCompletion);
}
CHK_RESULT(r, SQL_HANDLE_DBC, conn, "");
if (r!=SQL_SUCCESS) break;
D("connected");
if (1) {
SQLSMALLINT maxColumnNameLength = 0;
SQLSMALLINT len = 0;
r = SQLGetInfo(conn, SQL_MAX_COLUMN_NAME_LEN, &maxColumnNameLength, sizeof(SQLSMALLINT), &len);
CHK_RESULT(r, SQL_HANDLE_DBC, conn, "");
if (r!=SQL_SUCCESS) break;
D("maxColumnNameLength: %d", maxColumnNameLength);
}
} while (0);
SQLFreeConnect(conn);
conn = NULL;
} while (0);
SQLFreeEnv(env);
env = NULL;
return (r==SQL_SUCCESS) ? 0 : 1;
}
void usage(const char *arg0) {
fprintf(stdout, "%s usage:\n", arg0);
fprintf(stdout, "%s [--dsn <dsn>] [--uid <uid>] [--pwd <pwd>] [-C <conn_str>] [--sts <sts>]\n", arg0);
fprintf(stdout, " --dsn <dsn>: DSN\n");
fprintf(stdout, " --uid <uid>: UID\n");
fprintf(stdout, " --pwd <pwd>: PWD\n");
fprintf(stdout, " -C <conn_str>: driver connection string\n");
fprintf(stdout, " --sts <sts>: file where statements store\n");
}
int main(int argc, char *argv[]) {
srand((unsigned)time(0));
const char *conn_str = NULL;
const char *dsn = NULL;
const char *uid = NULL;
const char *pwd = NULL;
const char *sts = NULL; // statements file
int debug_col_name_max_len = 0;
int prepare = 0;
int insert = 0;
insert_arg_t insert_arg = {
.batch_size = 100,
.batchs = 100,
.keep_stmt_among_batchs = 0
};
for (size_t i=1; i<argc; ++i) {
const char *arg = argv[i];
if (strcmp(arg, "-h")==0) {
usage(argv[0]);
return 0;
}
if (strcmp(arg, "-d")==0) {
debug_col_name_max_len = 1;
continue;
}
if (strcmp(arg, "--insert")==0) {
insert = 1;
continue;
}
if (strcmp(arg, "--batch_size")==0) {
++i;
if (i>=argc) {
D("<batch_size> expected but got nothing");
return 1;
}
sscanf(argv[i], "%d", &insert_arg.batch_size);
if (insert_arg.batch_size<=0) {
D("<batch_size> invalid");
return 1;
}
continue;
}
if (strcmp(arg, "--batchs")==0) {
++i;
if (i>=argc) {
D("<batchs> expected but got nothing");
return 1;
}
sscanf(argv[i], "%d", &insert_arg.batchs);
if (insert_arg.batchs<=0) {
D("<batchs> invalid");
return 1;
}
continue;
}
if (strcmp(arg, "--keep_stmt_among_batchs")==0) {
insert_arg.keep_stmt_among_batchs = 1;
continue;
}
if (strcmp(arg, "--dsn")==0) {
++i;
if (i>=argc) {
D("<dsn> expected but got nothing");
return 1;
}
if (conn_str) {
D("-C has already been specified");
return 1;
}
dsn = argv[i];
continue;
}
if (strcmp(arg, "--uid")==0) {
++i;
if (i>=argc) {
D("<uid> expected but got nothing");
return 1;
}
uid = argv[i];
continue;
}
if (strcmp(arg, "--pwd")==0) {
++i;
if (i>=argc) {
D("<pwd> expected but got nothing");
return 1;
}
pwd = argv[i];
continue;
}
if (strcmp(arg, "-C")==0) {
++i;
if (i>=argc) {
D("<connection string> expected but got nothing");
return 1;
}
if (dsn || uid || pwd) {
D("either of --dsn/--uid/--pwd has already been specified");
return 1;
}
conn_str = argv[i];
continue;
}
if (strcmp(arg, "--sts")==0) {
++i;
if (i>=argc) {
D("<sts> expected but got nothing");
return 1;
}
sts = argv[i];
continue;
}
if (strcmp(arg, "-p")==0) {
prepare = 1;
continue;
}
}
if (debug_col_name_max_len) {
int r = do_debug_col_name_max_len(dsn, uid, pwd, conn_str);
if (r) return 1;
}
if (insert) {
int r = do_insert_batch(dsn, uid, pwd, conn_str, &insert_arg);
if (r) return 1;
}
if (sts) {
int r = test_sqls(dsn, uid, pwd, conn_str, sts);
if (r) return 1;
}
if (prepare) {
int r = do_prepare(dsn, uid, pwd, conn_str);
if (r) return 1;
}
D("Done!");
return 0;
}

View File

@ -0,0 +1,654 @@
/*******************************************************************************
/* ODBCSQL: a sample program that implements an ODBC command line interpreter.
/*
/* USAGE: ODBCSQL DSN=<dsn name> or
/* ODBCSQL FILEDSN=<file dsn> or
/* ODBCSQL DRIVER={driver name}
/*
/*
/* Copyright(c) Microsoft Corporation. This is a WDAC sample program and
/* is not suitable for use in production environments.
/*
/******************************************************************************/
/* Modules:
/* Main Main driver loop, executes queries.
/* DisplayResults Display the results of the query if any
/* AllocateBindings Bind column data
/* DisplayTitles Print column titles
/* SetConsole Set console display mode
/* HandleError Show ODBC error messages
/******************************************************************************/
#define _UNICODE
#define UNICODE
#include <windows.h>
#include <sql.h>
#include <sqlext.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include <stdlib.h>
#include <sal.h>
/*******************************************/
/* Macro to call ODBC functions and */
/* report an error on failure. */
/* Takes handle, handle type, and stmt */
/*******************************************/
#define TRYODBC(h, ht, x) { RETCODE rc = x;\
if (rc != SQL_SUCCESS) \
{ \
HandleDiagnosticRecord (h, ht, rc); \
} \
if (rc == SQL_ERROR) \
{ \
fwprintf(stderr, L"Error in " L#x L"\n"); \
goto Exit; \
} \
}
/******************************************/
/* Structure to store information about */
/* a column.
/******************************************/
typedef struct STR_BINDING {
SQLSMALLINT cDisplaySize; /* size to display */
WCHAR *wszBuffer; /* display buffer */
SQLLEN indPtr; /* size or null */
BOOL fChar; /* character col? */
struct STR_BINDING *sNext; /* linked list */
} BINDING;
/******************************************/
/* Forward references */
/******************************************/
void HandleDiagnosticRecord (SQLHANDLE hHandle,
SQLSMALLINT hType,
RETCODE RetCode);
void DisplayResults(HSTMT hStmt,
SQLSMALLINT cCols);
void AllocateBindings(HSTMT hStmt,
SQLSMALLINT cCols,
BINDING** ppBinding,
SQLSMALLINT* pDisplay);
void DisplayTitles(HSTMT hStmt,
DWORD cDisplaySize,
BINDING* pBinding);
void SetConsole(DWORD cDisplaySize,
BOOL fInvert);
/*****************************************/
/* Some constants */
/*****************************************/
#define DISPLAY_MAX 50 // Arbitrary limit on column width to display
#define DISPLAY_FORMAT_EXTRA 3 // Per column extra display bytes (| <data> )
#define DISPLAY_FORMAT L"%c %*.*s "
#define DISPLAY_FORMAT_C L"%c %-*.*s "
#define NULL_SIZE 6 // <NULL>
#define SQL_QUERY_SIZE 1000 // Max. Num characters for SQL Query passed in.
#define PIPE L'|'
SHORT gHeight = 80; // Users screen height
int __cdecl wmain(int argc, _In_reads_(argc) WCHAR **argv)
{
SQLHENV hEnv = NULL;
SQLHDBC hDbc = NULL;
SQLHSTMT hStmt = NULL;
WCHAR* pwszConnStr;
WCHAR wszInput[SQL_QUERY_SIZE];
// Allocate an environment
if (SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv) == SQL_ERROR)
{
fwprintf(stderr, L"Unable to allocate an environment handle\n");
exit(-1);
}
// Register this as an application that expects 3.x behavior,
// you must register something if you use AllocHandle
TRYODBC(hEnv,
SQL_HANDLE_ENV,
SQLSetEnvAttr(hEnv,
SQL_ATTR_ODBC_VERSION,
(SQLPOINTER)SQL_OV_ODBC3,
0));
// Allocate a connection
TRYODBC(hEnv,
SQL_HANDLE_ENV,
SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc));
if (argc > 1)
{
pwszConnStr = *++argv;
}
else
{
pwszConnStr = L"";
}
// Connect to the driver. Use the connection string if supplied
// on the input, otherwise let the driver manager prompt for input.
TRYODBC(hDbc,
SQL_HANDLE_DBC,
SQLDriverConnect(hDbc,
GetDesktopWindow(),
pwszConnStr,
SQL_NTS,
NULL,
0,
NULL,
SQL_DRIVER_COMPLETE));
fwprintf(stderr, L"Connected!\n");
TRYODBC(hDbc,
SQL_HANDLE_DBC,
SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt));
wprintf(L"Enter SQL commands, type (control)Z to exit\nSQL COMMAND>");
// Loop to get input and execute queries
while(_fgetts(wszInput, SQL_QUERY_SIZE-1, stdin))
{
RETCODE RetCode;
SQLSMALLINT sNumResults;
// Execute the query
if (!(*wszInput))
{
wprintf(L"SQL COMMAND>");
continue;
}
RetCode = SQLExecDirect(hStmt,wszInput, SQL_NTS);
switch(RetCode)
{
case SQL_SUCCESS_WITH_INFO:
{
HandleDiagnosticRecord(hStmt, SQL_HANDLE_STMT, RetCode);
// fall through
}
case SQL_SUCCESS:
{
// If this is a row-returning query, display
// results
TRYODBC(hStmt,
SQL_HANDLE_STMT,
SQLNumResultCols(hStmt,&sNumResults));
if (sNumResults > 0)
{
DisplayResults(hStmt,sNumResults);
}
else
{
SQLLEN cRowCount;
TRYODBC(hStmt,
SQL_HANDLE_STMT,
SQLRowCount(hStmt,&cRowCount));
if (cRowCount >= 0)
{
wprintf(L"%Id %s affected\n",
cRowCount,
cRowCount == 1 ? L"row" : L"rows");
}
}
break;
}
case SQL_ERROR:
{
HandleDiagnosticRecord(hStmt, SQL_HANDLE_STMT, RetCode);
break;
}
default:
fwprintf(stderr, L"Unexpected return code %hd!\n", RetCode);
}
TRYODBC(hStmt,
SQL_HANDLE_STMT,
SQLFreeStmt(hStmt, SQL_CLOSE));
wprintf(L"SQL COMMAND>");
}
Exit:
// Free ODBC handles and exit
if (hStmt)
{
SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
}
if (hDbc)
{
SQLDisconnect(hDbc);
SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
}
if (hEnv)
{
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
}
wprintf(L"\nDisconnected.");
return 0;
}
/************************************************************************
/* DisplayResults: display results of a select query
/*
/* Parameters:
/* hStmt ODBC statement handle
/* cCols Count of columns
/************************************************************************/
void DisplayResults(HSTMT hStmt,
SQLSMALLINT cCols)
{
BINDING *pFirstBinding, *pThisBinding;
SQLSMALLINT cDisplaySize;
RETCODE RetCode = SQL_SUCCESS;
int iCount = 0;
// Allocate memory for each column
AllocateBindings(hStmt, cCols, &pFirstBinding, &cDisplaySize);
// Set the display mode and write the titles
DisplayTitles(hStmt, cDisplaySize+1, pFirstBinding);
// Fetch and display the data
bool fNoData = false;
do {
// Fetch a row
if (iCount++ >= gHeight - 2)
{
int nInputChar;
bool fEnterReceived = false;
while(!fEnterReceived)
{
wprintf(L" ");
SetConsole(cDisplaySize+2, TRUE);
wprintf(L" Press ENTER to continue, Q to quit (height:%hd)", gHeight);
SetConsole(cDisplaySize+2, FALSE);
nInputChar = _getch();
wprintf(L"\n");
if ((nInputChar == 'Q') || (nInputChar == 'q'))
{
goto Exit;
}
else if ('\r' == nInputChar)
{
fEnterReceived = true;
}
// else loop back to display prompt again
}
iCount = 1;
DisplayTitles(hStmt, cDisplaySize+1, pFirstBinding);
}
TRYODBC(hStmt, SQL_HANDLE_STMT, RetCode = SQLFetch(hStmt));
if (RetCode == SQL_NO_DATA_FOUND)
{
fNoData = true;
}
else
{
// Display the data. Ignore truncations
for (pThisBinding = pFirstBinding;
pThisBinding;
pThisBinding = pThisBinding->sNext)
{
if (pThisBinding->indPtr != SQL_NULL_DATA)
{
wprintf(pThisBinding->fChar ? DISPLAY_FORMAT_C:DISPLAY_FORMAT,
PIPE,
pThisBinding->cDisplaySize,
pThisBinding->cDisplaySize,
pThisBinding->wszBuffer);
}
else
{
wprintf(DISPLAY_FORMAT_C,
PIPE,
pThisBinding->cDisplaySize,
pThisBinding->cDisplaySize,
L"<NULL>");
}
}
wprintf(L" %c\n",PIPE);
}
} while (!fNoData);
SetConsole(cDisplaySize+2, TRUE);
wprintf(L"%*.*s", cDisplaySize+2, cDisplaySize+2, L" ");
SetConsole(cDisplaySize+2, FALSE);
wprintf(L"\n");
Exit:
// Clean up the allocated buffers
while (pFirstBinding)
{
pThisBinding = pFirstBinding->sNext;
free(pFirstBinding->wszBuffer);
free(pFirstBinding);
pFirstBinding = pThisBinding;
}
}
/************************************************************************
/* AllocateBindings: Get column information and allocate bindings
/* for each column.
/*
/* Parameters:
/* hStmt Statement handle
/* cCols Number of columns in the result set
/* *lppBinding Binding pointer (returned)
/* lpDisplay Display size of one line
/************************************************************************/
void AllocateBindings(HSTMT hStmt,
SQLSMALLINT cCols,
BINDING **ppBinding,
SQLSMALLINT *pDisplay)
{
SQLSMALLINT iCol;
BINDING *pThisBinding, *pLastBinding = NULL;
SQLLEN cchDisplay, ssType;
SQLSMALLINT cchColumnNameLength;
*pDisplay = 0;
for (iCol = 1; iCol <= cCols; iCol++)
{
pThisBinding = (BINDING *)(malloc(sizeof(BINDING)));
if (!(pThisBinding))
{
fwprintf(stderr, L"Out of memory!\n");
exit(-100);
}
if (iCol == 1)
{
*ppBinding = pThisBinding;
}
else
{
pLastBinding->sNext = pThisBinding;
}
pLastBinding = pThisBinding;
// Figure out the display length of the column (we will
// bind to char since we are only displaying data, in general
// you should bind to the appropriate C type if you are going
// to manipulate data since it is much faster...)
TRYODBC(hStmt,
SQL_HANDLE_STMT,
SQLColAttribute(hStmt,
iCol,
SQL_DESC_DISPLAY_SIZE,
NULL,
0,
NULL,
&cchDisplay));
// Figure out if this is a character or numeric column; this is
// used to determine if we want to display the data left- or right-
// aligned.
// SQL_DESC_CONCISE_TYPE maps to the 1.x SQL_COLUMN_TYPE.
// This is what you must use if you want to work
// against a 2.x driver.
TRYODBC(hStmt,
SQL_HANDLE_STMT,
SQLColAttribute(hStmt,
iCol,
SQL_DESC_CONCISE_TYPE,
NULL,
0,
NULL,
&ssType));
pThisBinding->fChar = (ssType == SQL_CHAR ||
ssType == SQL_VARCHAR ||
ssType == SQL_LONGVARCHAR);
pThisBinding->sNext = NULL;
// Arbitrary limit on display size
if (cchDisplay > DISPLAY_MAX)
cchDisplay = DISPLAY_MAX;
// Allocate a buffer big enough to hold the text representation
// of the data. Add one character for the null terminator
pThisBinding->wszBuffer = (WCHAR *)malloc((cchDisplay+1) * sizeof(WCHAR));
if (!(pThisBinding->wszBuffer))
{
fwprintf(stderr, L"Out of memory!\n");
exit(-100);
}
// Map this buffer to the driver's buffer. At Fetch time,
// the driver will fill in this data. Note that the size is
// count of bytes (for Unicode). All ODBC functions that take
// SQLPOINTER use count of bytes; all functions that take only
// strings use count of characters.
TRYODBC(hStmt,
SQL_HANDLE_STMT,
SQLBindCol(hStmt,
iCol,
SQL_C_TCHAR,
(SQLPOINTER) pThisBinding->wszBuffer,
(cchDisplay + 1) * sizeof(WCHAR),
&pThisBinding->indPtr));
// Now set the display size that we will use to display
// the data. Figure out the length of the column name
TRYODBC(hStmt,
SQL_HANDLE_STMT,
SQLColAttribute(hStmt,
iCol,
SQL_DESC_NAME,
NULL,
0,
&cchColumnNameLength,
NULL));
pThisBinding->cDisplaySize = max((SQLSMALLINT)cchDisplay, cchColumnNameLength);
if (pThisBinding->cDisplaySize < NULL_SIZE)
pThisBinding->cDisplaySize = NULL_SIZE;
*pDisplay += pThisBinding->cDisplaySize + DISPLAY_FORMAT_EXTRA;
}
return;
Exit:
exit(-1);
return;
}
/************************************************************************
/* DisplayTitles: print the titles of all the columns and set the
/* shell window's width
/*
/* Parameters:
/* hStmt Statement handle
/* cDisplaySize Total display size
/* pBinding list of binding information
/************************************************************************/
void DisplayTitles(HSTMT hStmt,
DWORD cDisplaySize,
BINDING *pBinding)
{
WCHAR wszTitle[DISPLAY_MAX];
SQLSMALLINT iCol = 1;
SetConsole(cDisplaySize+2, TRUE);
for (; pBinding; pBinding = pBinding->sNext)
{
TRYODBC(hStmt,
SQL_HANDLE_STMT,
SQLColAttribute(hStmt,
iCol++,
SQL_DESC_NAME,
wszTitle,
sizeof(wszTitle), // Note count of bytes!
NULL,
NULL));
wprintf(DISPLAY_FORMAT_C,
PIPE,
pBinding->cDisplaySize,
pBinding->cDisplaySize,
wszTitle);
}
Exit:
wprintf(L" %c", PIPE);
SetConsole(cDisplaySize+2, FALSE);
wprintf(L"\n");
}
/************************************************************************
/* SetConsole: sets console display size and video mode
/*
/* Parameters
/* siDisplaySize Console display size
/* fInvert Invert video?
/************************************************************************/
void SetConsole(DWORD dwDisplaySize,
BOOL fInvert)
{
HANDLE hConsole;
CONSOLE_SCREEN_BUFFER_INFO csbInfo;
// Reset the console screen buffer size if necessary
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConsole != INVALID_HANDLE_VALUE)
{
if (GetConsoleScreenBufferInfo(hConsole, &csbInfo))
{
if (csbInfo.dwSize.X < (SHORT) dwDisplaySize)
{
csbInfo.dwSize.X = (SHORT) dwDisplaySize;
SetConsoleScreenBufferSize(hConsole, csbInfo.dwSize);
}
gHeight = csbInfo.dwSize.Y;
}
if (fInvert)
{
SetConsoleTextAttribute(hConsole, (WORD)(csbInfo.wAttributes | BACKGROUND_BLUE));
}
else
{
SetConsoleTextAttribute(hConsole, (WORD)(csbInfo.wAttributes & ~(BACKGROUND_BLUE)));
}
}
}
/************************************************************************
/* HandleDiagnosticRecord : display error/warning information
/*
/* Parameters:
/* hHandle ODBC handle
/* hType Type of handle (HANDLE_STMT, HANDLE_ENV, HANDLE_DBC)
/* RetCode Return code of failing command
/************************************************************************/
void HandleDiagnosticRecord (SQLHANDLE hHandle,
SQLSMALLINT hType,
RETCODE RetCode)
{
SQLSMALLINT iRec = 0;
SQLINTEGER iError;
WCHAR wszMessage[1000];
WCHAR wszState[SQL_SQLSTATE_SIZE+1];
if (RetCode == SQL_INVALID_HANDLE)
{
fwprintf(stderr, L"Invalid handle!\n");
return;
}
while (SQLGetDiagRec(hType,
hHandle,
++iRec,
wszState,
&iError,
wszMessage,
(SQLSMALLINT)(sizeof(wszMessage) / sizeof(WCHAR)),
(SQLSMALLINT *)NULL) == SQL_SUCCESS)
{
// Hide data truncated..
if (wcsncmp(wszState, L"01004", 5))
{
fwprintf(stderr, L"[%5.5s] %s (%d)\n", wszState, wszMessage, iError);
}
}
}

View File

@ -0,0 +1,84 @@
package main
import (
"context"
"database/sql"
"flag"
"log"
"os"
"os/signal"
"time"
_ "github.com/alexbrainman/odbc"
)
var pool *sql.DB // Database connection pool.
func main() {
id := flag.Int64("id", 32768, "person ID to find")
dsn := flag.String("dsn", os.Getenv("DSN"), "connection data source name")
flag.Parse()
if len(*dsn) == 0 {
log.Fatal("missing dsn flag")
}
if *id == 0 {
log.Fatal("missing person ID")
}
var err error
// Opening a driver typically will not attempt to connect to the database.
pool, err = sql.Open("odbc", *dsn)
if err != nil {
// This will not be a connection error, but a DSN parse error or
// another initialization error.
log.Fatal("unable to use data source name", err)
}
defer pool.Close()
pool.SetConnMaxLifetime(0)
pool.SetMaxIdleConns(3)
pool.SetMaxOpenConns(3)
ctx, stop := context.WithCancel(context.Background())
defer stop()
appSignal := make(chan os.Signal, 3)
signal.Notify(appSignal, os.Interrupt)
go func() {
select {
case <-appSignal:
stop()
}
}()
Ping(ctx)
Query(ctx, *id)
}
// Ping the database to verify DSN provided by the user is valid and the
// server accessible. If the ping fails exit the program with an error.
func Ping(ctx context.Context) {
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
if err := pool.PingContext(ctx); err != nil {
log.Fatalf("unable to connect to database: %v", err)
}
}
// Query the database for the information requested and prints the results.
// If the query fails exit the program with an error.
func Query(ctx context.Context, id int64) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
var name string
err := pool.QueryRowContext(ctx, "select name from m.t").Scan(&name)
if err != nil {
log.Fatal("unable to execute search query", err)
}
log.Println("name=", name)
}

View File

View File

@ -0,0 +1,122 @@
#!/usr/bin/env node
const odbc = require('odbc');
const path = require('path');
function usage() {
var arg = path.basename(process.argv[1]);
console.error(`usage:`);
console.error(`${arg} --DSN <DSN> --UID <uid> --PWD <pwd> --Server <host:port>`);
console.error(`${arg} -C <conn_str>`);
console.error(` conn_str eg: 'DSN={TAOS_DSN};UID=root;PWD=taosdata;Server=host:port'`);
}
var cfg = { };
if (process.argv.length==2) {
usage();
process.exit(0);
}
var i;
for (i=2; i<process.argv.length; ++i) {
var arg = process.argv[i];
if (arg=='-h') {
usage();
process.exit(0);
}
if (arg=="--DSN") {
++i;
if (i>=process.argv.length) {
console.error(`expecting <dns> after --DSN but got nothing`);
usage(process.argv[1]);
process.exit(1);
}
arg = process.argv[i];
cfg.dsn = arg;
continue;
}
if (arg=="--UID") {
++i;
if (i>=process.argv.length) {
console.error(`expecting <uid> after --UID but got nothing`);
usage(process.argv[1]);
process.exit(1);
}
arg = process.argv[i];
cfg.uid = arg;
continue;
}
if (arg=="--PWD") {
++i;
if (i>=process.argv.length) {
console.error(`expecting <pwd> after --PWD but got nothing`);
usage(process.argv[1]);
process.exit(1);
}
arg = process.argv[i];
cfg.pwd = arg;
continue;
}
if (arg=="--Server") {
++i;
if (i>=process.argv.length) {
console.error(`expecting <host:port> after --Server but got nothing`);
usage(process.argv[1]);
process.exit(1);
}
arg = process.argv[i];
cfg.server = arg;
continue;
}
if (arg=="-C") {
++i;
if (i>=process.argv.length) {
console.error(`expecting <conn_str> after -C but got nothing`);
console.error(` conn_str eg: 'DSN={TAOS_DSN};UID=root;PWD=taosdata;Server=host:port'`);
process.exit(1);
}
arg = process.argv[i];
cfg.conn_str = arg;
continue;
}
console.error(`unknown argument: [${arg}]`);
usage(process.argv[1]);
process.exit(1);
}
var connectionString = cfg.conn_str;
if (!cfg.conn_str) {
connectionString = `DSN={${cfg.dsn}}; UID=${cfg.uid}; PWD=${cfg.pwd}; Server=${cfg.server}`;
}
(async function () {
const connStr = connectionString;
try {
console.log(`connecting [${connStr}]...`);
const connection = await odbc.connect(connStr);
await connection.query('create database if not exists m');
await connection.query('use m');
await connection.query('drop table if exists t');
await connection.query('create table t (ts timestamp, b bool, v1 tinyint, v2 smallint, v4 int, v8 bigint, f4 float, f8 double, bin binary(10), name nchar(3))');
await connection.query('insert into t values("2020-01-02 12:34:56.781", 1, 127, 32767, 32768, 32769, 123.456, 789.987, "hello", "我和你")');
console.log('.........');
result = await connection.query('select * from t');
console.log(result[0]);
statement = await connection.createStatement();
await statement.prepare('INSERT INTO t (ts, v1) VALUES(?, ?)');
await statement.bind(['2020-02-02 11:22:33.449', 89]);
result = await statement.execute();
console.log(result);
result = await connection.query('select * from t');
console.log(result[0]);
console.log(result[1]);
} catch (e) {
console.log('error:', e);
}
})();

View File

@ -0,0 +1,5 @@
{
"dependencies": {
"odbc": "^2.3.6"
}
}

View File

@ -0,0 +1,48 @@
package.cpath = package.cpath .. ";/usr/local/lib/lib?.dylib"
-- load driver
local driver = require "luasql.odbc"
-- create environment object
env = assert (driver.odbc())
-- connect to data source
con = assert (env:connect("TAOS_DSN", "root", "taosdata"))
-- reset our table
-- res = con:execute"DROP TABLE people"
-- res = assert (con:execute[[
-- CREATE TABLE people(
-- name varchar(50),
-- email varchar(50)
-- )
-- ]])
-- -- add a few elements
-- list = {
-- { name="Jose das Couves", email="jose@couves.com", },
-- { name="Manoel Joaquim", email="manoel.joaquim@cafundo.com", },
-- { name="Maria das Dores", email="maria@dores.com", },
-- }
-- for i, p in pairs (list) do
-- res = assert (con:execute(string.format([[
-- INSERT INTO people
-- VALUES ('%s', '%s')]], p.name, p.email)
-- ))
-- end
-- -- retrieve a cursor
-- cur = assert (con:execute"SELECT name, email from people")
-- -- print all rows, the rows will be indexed by field names
-- row = cur:fetch ({}, "a")
-- while row do
-- print(string.format("Name: %s, E-mail: %s", row.name, row.email))
-- -- reusing the table of results
-- row = cur:fetch (row, "a")
-- end
cur = assert(con:execute"select * from m.t")
row = cur:fetch({}, "a")
while row do
print(string.format("Name: %s", row.name))
row = cur:fetch(row, "a")
end
-- close everything
cur:close() -- already closed because all the result set was consumed
con:close()
env:close()

View File

@ -0,0 +1,111 @@
import pyodbc
import argparse
import sys
parser = argparse.ArgumentParser(description='Access TDengine via ODBC.')
parser.add_argument('--DSN', help='DSN to use')
parser.add_argument('--UID', help='UID to use')
parser.add_argument('--PWD', help='PWD to use')
parser.add_argument('--Server', help='Server to use')
parser.add_argument('-C', metavar='CONNSTR', help='Connection string to use')
args = parser.parse_args()
a = 'DSN=%s'%args.DSN if args.DSN else None
b = 'UID=%s'%args.UID if args.UID else None
c = 'PWD=%s'%args.PWD if args.PWD else None
d = 'Server=%s'%args.Server if args.Server else None
conn_str = ';'.join(filter(None, [a,b,c,d])) if args.DSN else None
conn_str = conn_str if conn_str else args.C
if not conn_str:
parser.print_help(file=sys.stderr)
exit()
print('connecting: [%s]' % conn_str)
cnxn = pyodbc.connect(conn_str, autocommit=True)
cnxn.setdecoding(pyodbc.SQL_CHAR, encoding='utf-8')
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.mt (ts timestamp, b bool, v1 tinyint, v2 smallint, v4 int, v8 bigint, f4 float, f8 double, bin binary(10), blob nchar(10))");
cursor.close()
cursor = cnxn.cursor()
cursor.execute("insert into db.mt values('2020-10-13 06:44:00.123', 1, 127, 32767, 2147483647, 32769, 123.456, 789.987, 'hello', 'helloworld')")
cursor.close()
cursor = cnxn.cursor()
cursor.execute("insert into db.mt values(?,?,?,?,?,?,?,?,?,?)", "2020-10-13 07:06:00.234", 0, 127, 32767, 32768, 32769, 123.456, 789.987, "hel后lo".encode('utf-8'), "wo哈rlxd129")
##cursor.execute("insert into db.mt values(?,?,?,?,?,?,?,?,?,?)", 1502535178128, 9223372036854775807, 127, 32767, 32768, 32769, 123.456, 789.987, "hel后lo".encode('utf-8'), "wo哈rlxd123");
cursor.close()
cursor = cnxn.cursor()
cursor.execute("""
INSERT INTO db.mt (ts,b,v1,v2,v4,v8,f4,f8,bin,blob) values (?,?,?,?,?,?,?,?,?,?)
""",
"2020-12-12 00:00:00",
'true',
'-127',
'-32767',
'-2147483647',
'-9223372036854775807',
'-1.23e10',
'-11.23e6',
'abcdefghij'.encode('utf-8'),
"人啊大发测试及abc")
cursor.close()
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(4), blob nchar(4))");
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, 'hell', 'w我你z')")
cursor.close()
cursor = cnxn.cursor()
cursor.execute("create table db.v (ts timestamp, v1 tinyint, v2 smallint, name nchar(10), ts2 timestamp)")
cursor.close()
params = [ ('2020-10-16 00:00:00.123', 19, '2111-01-02 01:02:03.123'),
('2020-10-16 00:00:01', 41, '2111-01-02 01:02:03.423'),
('2020-10-16 00:00:02', 57, '2111-01-02 01:02:03.153'),
('2020-10-16 00:00:03.009', 26, '2111-01-02 01:02:03.623') ]
cursor = cnxn.cursor()
cursor.fast_executemany = True
print('py:...................')
cursor.executemany("insert into db.v (ts, v1, ts2) values (?, ?, ?)", params)
print('py:...................')
cursor.close()
## cursor = cnxn.cursor()
## cursor.execute("SELECT * from db.v where v1 > ?", 4)
## row = cursor.fetchone()
## while row:
## print(row)
## row = cursor.fetchone()
## cursor.close()
##
## cursor = cnxn.cursor()
## cursor.execute("SELECT * from db.v where v1 > ?", '5')
## row = cursor.fetchone()
## while row:
## print(row)
## row = cursor.fetchone()
## cursor.close()

View File

@ -0,0 +1 @@
Cargo.lock

View File

@ -0,0 +1,12 @@
[package]
name = "main"
version = "0.1.0"
authors = ["freemine <freemine@yeah.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
odbc = "0.17.0"
env_logger = "0.8.2"

View File

@ -0,0 +1,49 @@
extern crate odbc;
// Use this crate and set environmet variable RUST_LOG=odbc to see ODBC warnings
extern crate env_logger;
use odbc::*;
use odbc_safe::AutocommitOn;
use std::env;
fn main() {
env_logger::init();
let conn_str = env::var("DSN").unwrap();
match connect(&conn_str) {
Ok(()) => println!("Success"),
Err(diag) => println!("Error: {}", diag),
}
}
fn connect(conn_str: &str) -> std::result::Result<(), DiagnosticRecord> {
let env = create_environment_v3().map_err(|e| e.unwrap())?;
let conn = env.connect_with_connection_string(conn_str)?;
execute_statement(&conn)
}
fn execute_statement<'env>(conn: &Connection<'env, AutocommitOn>) -> Result<()> {
let stmt = Statement::with_parent(conn)?;
match stmt.exec_direct("select * from m.t")? {
Data(mut stmt) => {
let cols = stmt.num_result_cols()?;
println!("cols: {}", cols);
while let Some(mut cursor) = stmt.fetch()? {
for i in 1..(cols + 1) {
match cursor.get_data::<&str>(i as u16)? {
Some(val) => print!(" {}", val),
None => print!(" NULL"),
}
}
println!("");
}
}
NoData(_) => println!("Query executed, no data returned"),
}
Ok(())
}

View File

@ -1,12 +1,17 @@
#P: positive sample
#N: negative sample
P:drop database if exists m;
P:create database m;
P:use m;
P:drop table if exists t;
P:create table t (ts timestamp, b bool, v1 tinyint, v2 smallint, v4 int, v8 bigint, f4 float, f8 double, blob binary(3), name nchar(1));
P:insert into t (ts, blob, name) values('2020-10-10 00:00:00', 0, 1);
P:insert into t (ts, blob, name) values('2020-10-10 00:00:00.001', 1, 2);
P:create table t (ts timestamp, b bool, v1 tinyint, v2 smallint, v4 int, v8 bigint, f4 float, f8 double, blob binary(3), name nchar(1), ts2 nchar(148));
#P:insert into t (ts, blob, name) values('2020-10-10 00:00:00', 0, 1);
#P:insert into t (ts, blob, name) values('2020-10-10 00:00:00.001', 1, 2);
P:insert into t (ts, blob, name) values('2020-10-10 00:00:00.002', '你', '好');
P:insert into t (ts, blob, name) values('2020-10-10 00:00:00.003', 'abc', 'd');
P:select * from t;
P:create table v (ts timestamp, b bool, v1 tinyint, v2 smallint, v4 int, v8 bigint, f4 float, f8 double, blob binary(3), name nchar(1), ts2 nchar(23));

View File

@ -1,6 +1,8 @@
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(TDengine)
add_subdirectory(base)
IF (TD_LINUX_64)
FLEX_TARGET(todbcFlexScanner
todbc_scanner.l
@ -15,12 +17,35 @@ IF (TD_LINUX_64)
ADD_LIBRARY(todbc SHARED ${SRC} ${todbc_flex_scanner_src})
SET_TARGET_PROPERTIES(todbc PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(todbc PROPERTIES VERSION ${TD_VER_NUMBER} SOVERSION 1)
TARGET_LINK_LIBRARIES(todbc taos odbcinst)
TARGET_LINK_LIBRARIES(todbc todbc_base taos odbcinst)
target_include_directories(todbc PUBLIC .)
install(CODE "execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/install.sh ${CMAKE_BINARY_DIR})")
ENDIF ()
IF (TD_DARWIN)
FLEX_TARGET(todbcFlexScanner
todbc_scanner.l
${CMAKE_CURRENT_BINARY_DIR}/todbc_scanner.c
)
set(todbc_flex_scanner_src
${FLEX_todbcFlexScanner_OUTPUTS}
)
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/todbc_scanner.c PROPERTIES COMPILE_OPTIONS "-Wno-conversion")
AUX_SOURCE_DIRECTORY(. SRC)
# generate dynamic library (*.dylib)
ADD_LIBRARY(todbc SHARED ${SRC} ${todbc_flex_scanner_src})
SET_TARGET_PROPERTIES(todbc PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(todbc PROPERTIES VERSION ${TD_VER_NUMBER} SOVERSION 1)
TARGET_LINK_LIBRARIES(todbc todbc_base taos odbcinst)
target_include_directories(todbc PUBLIC .)
target_include_directories(todbc PRIVATE /usr/local/include)
target_link_directories(todbc PUBLIC /usr/local/lib)
install(CODE "execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/install.sh ${CMAKE_BINARY_DIR})")
ENDIF ()
IF (TD_WINDOWS_64)
FLEX_TARGET(todbcFlexScanner
todbc_scanner.l
@ -37,7 +62,7 @@ IF (TD_WINDOWS_64)
${todbc_flex_scanner_src}
${CMAKE_CURRENT_BINARY_DIR}/todbc.rc
todbc.def)
TARGET_LINK_LIBRARIES(todbc taos_static odbccp32 legacy_stdio_definitions)
TARGET_LINK_LIBRARIES(todbc todbc_base taos_static odbccp32 legacy_stdio_definitions)
target_include_directories(todbc PUBLIC .)
target_compile_definitions(todbc PRIVATE "todbc_EXPORT")
@ -52,3 +77,4 @@ IF (TD_WINDOWS_64)
INSTALL(FILES ${LIBRARY_OUTPUT_PATH}/todbc.exp DESTINATION driver)
INSTALL(FILES ${LIBRARY_OUTPUT_PATH}/todbc.dll DESTINATION driver)
ENDIF ()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _base_h_
#define _base_h_
#include "todbc_buf.h"
#include "todbc_iconv.h"
#include "todbc_log.h"
#include "taos.h"
#include "taoserror.h"
#include <sql.h>
#include <sqlext.h>
typedef struct errs_s errs_t;
typedef struct env_s env_t;
typedef struct conn_s conn_t;
typedef struct stmt_s stmt_t;
typedef struct param_s param_t;
typedef struct field_s field_t;
typedef struct rs_s rs_t;
typedef struct col_s col_t;
#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)
// public
#ifdef __GNUC__
__attribute__((format(printf, 6, 7)))
#endif
SQLRETURN errs_append(errs_t *errs, const char sql_state[6], const char *file, int line, const char *func, const char *fmt, ...);
int errs_count(errs_t *errs);
// 0/-1: ok/no-error
int errs_fetch(errs_t *errs, int idx, const char **sql_state, const char **err_str);
void errs_clear(SQLSMALLINT HandleType, SQLHANDLE InputHandle);
// err: if <>0, will generate strerror
#define SET_ERR(errs, sql_state, fmt, ...) \
errs_append(errs, sql_state, __FILE__, __LINE__, __func__, "%s" fmt "", "", ##__VA_ARGS__)
#define SET_OOM(errs, fmt, ...) SET_ERR(errs, "TD001", "OOM:" fmt, ##__VA_ARGS__)
#define SET_NIY(errs, fmt, ...) SET_ERR(errs, "TDC00", "NIY:" fmt, ##__VA_ARGS__)
#define SET_GENERAL(errs, fmt, ...) SET_ERR(errs, "TD000", "GEN:" fmt, ##__VA_ARGS__)
// public
errs_t* env_get_errs(env_t *env);
void env_clr_errs(env_t *env);
void env_inc_ref(env_t *env);
void env_dec_ref(env_t *env);
// public
errs_t* conn_get_errs(conn_t *conn);
void conn_clr_errs(conn_t *conn);
// public
errs_t* stmt_get_errs(stmt_t *stmt);
void stmt_clr_errs(stmt_t *stmt);
#endif // _base_h_

View File

@ -0,0 +1,10 @@
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(TDengine)
aux_source_directory(. SRC)
add_library(todbc_base STATIC ${SRC})
if (TD_DARWIN)
target_include_directories(todbc_base PRIVATE /usr/local/include)
endif ()

View File

@ -0,0 +1,223 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "conn.h"
#include "env.h"
#include "../todbc_tls.h"
static void init_encodes(conn_t *conn, env_t *env) {
OILE(conn, "");
OILE(env, "");
const char *enc_charset = env->enc_charset;
snprintf(conn->enc_src, sizeof(conn->enc_src), "%s", UTF8_ENC); // compile-time constant
snprintf(conn->enc_wchar, sizeof(conn->enc_wchar), "%s", UTF16_ENC); // compile-time constant
snprintf(conn->enc_char, sizeof(conn->enc_char), "%s", enc_charset); // runtime default
snprintf(conn->enc_db, sizeof(conn->enc_db), "%s", enc_charset); // runtime default
snprintf(conn->enc_locale, sizeof(conn->enc_locale), "%s", enc_charset); // runtime default
OD("enc_src:[%s]; enc_wchar:[%s]; enc_char:[%s]; enc_db:[%s]; enc_locale:[%s]",
conn->enc_src, conn->enc_wchar, conn->enc_char, conn->enc_db, conn->enc_locale);
}
static int conn_init(conn_t *conn, env_t *env) {
OILE(conn, "");
OILE(env, "");
errs_t *errs = &env->errs;
OILE(conn->env==NULL, "");
int r = errs_init(&conn->errs);
if (r) return -1;
init_encodes(conn, env);
if (SQL_SUCCESS!=conn_check_charset(conn, errs)) {
return -1;
}
conn->env = env;
env_inc_ref(env);
return 0;
};
static void conn_release(conn_t *conn) {
if (!conn) return;
env_t *env = conn->env;
if (!env) return;
conn->env = NULL;
env_dec_ref(env);
}
conn_t* conn_new(env_t *env) {
conn_t *conn = (conn_t*)calloc(1, sizeof(*conn));
if (!conn) return NULL;
if (conn_init(conn, env)) {
OILE(conn->env==NULL, "");
free(conn);
return NULL;
}
return conn;
}
void conn_free(conn_t *conn) {
if (!conn) return;
// clean ext stuff
if (conn->ext.free_conn) {
conn->ext.free_conn(conn);
}
// clean conn stuffs
conn_release(conn);
free(conn);
}
static SQLRETURN do_check_charset(errs_t *errs, const char *enc_charset, todbc_enc_t *enc) {
*enc = todbc_tls_iconv_enc(enc_charset);
if (enc->enc[0]=='\0') {
if (errs) {
SET_GENERAL(errs, "unknown charset [%s]", enc_charset);
}
return SQL_ERROR;
}
return SQL_SUCCESS;
}
SQLRETURN conn_check_charset(conn_t *conn, errs_t *errs) {
OILE(conn, "");
todbc_enc_t enc;
SQLRETURN r;
r = do_check_charset(errs, conn->enc_char, &enc);
if (r!=SQL_SUCCESS) return r;
r = do_check_charset(errs, conn->enc_db, &enc);
if (r!=SQL_SUCCESS) return r;
r = do_check_charset(errs, conn->enc_locale, &enc);
if (r!=SQL_SUCCESS) return r;
r = do_check_charset(errs, conn->enc_src, &enc);
if (r!=SQL_SUCCESS) return r;
r = do_check_charset(errs, conn->enc_wchar, &enc);
if (r!=SQL_SUCCESS) return r;
if (enc.variable_char_size!=-1) {
OE("does not support [%s] for WCHAR", conn->enc_wchar);
if (errs) {
SET_GENERAL(errs, "does not support [%s] for WCHAR", conn->enc_wchar);
}
return SQL_ERROR;
}
if (enc.char_size<=0) {
if (errs) {
SET_GENERAL(errs, "unknown [%s] for WCHAR", conn->enc_wchar);
}
return SQL_ERROR;
}
conn->wchar_size = (size_t)enc.char_size;
return SQL_SUCCESS;
}
int conn_add_stmt(conn_t *conn, stmt_t *stmt) {
OILE(conn, "");
OILE(stmt, "");
OILE(stmt->owner==NULL, "");
OILE(stmt->next==NULL, "");
OILE(stmt->prev==NULL, "");
OILE(conn->ext.init_stmt, "");
if (conn->ext.init_stmt(stmt)) {
return -1;
}
stmts_t *owner = &conn->stmts;
stmt->owner = owner;
stmt->prev = owner->tail;
if (owner->tail) owner->tail->next = stmt;
else owner->head = stmt;
owner->tail = stmt;
++owner->count;
owner->conn = conn;
return 0;
}
void conn_del_stmt(conn_t *conn, stmt_t *stmt) {
OILE(conn, "");
OILE(stmt, "");
OILE(stmt->owner, "");
OILE(stmt->owner==&conn->stmts, "");
OILE(stmt->owner->conn==conn, "");
stmts_t *owner = stmt->owner;
stmt_t *next = stmt->next;
stmt_t *prev = stmt->prev;
if (next) next->prev = prev;
else owner->tail = prev;
if (prev) prev->next = next;
else owner->head = next;
--owner->count;
stmt->next = NULL;
stmt->prev = NULL;
stmt->owner = NULL;
}
SQLRETURN conn_connect(conn_t *conn) {
OILE(conn, "");
OILE(conn->ext.connect, "");
return conn->ext.connect(conn);
}
void conn_disconnect(conn_t *conn) {
OILE(conn, "");
OILE(conn->ext.disconnect, "");
conn->ext.disconnect(conn);
}
// public
errs_t* conn_get_errs(conn_t *conn) {
OILE(conn, "");
return &conn->errs;
}
void conn_clr_errs(conn_t *conn) {
if (!conn) return;
errs_reclaim(&conn->errs);
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _conn_h_
#define _conn_h_
#include "../base.h"
#include "stmt.h"
#include "../todbc_flex.h"
typedef struct conn_ext_s conn_ext_t;
struct conn_ext_s {
void *ext;
void (*free_conn)(conn_t *conn);
int (*init_stmt)(stmt_t *stmt);
SQLRETURN (*connect)(conn_t *conn);
void (*disconnect)(conn_t *conn);
};
struct conn_s {
env_t *env;
char enc_src[64]; // c source file encoding
char enc_char[64]; // SQL_CHAR encoding
char enc_wchar[64]; // SQL_WCHAR encoding
char enc_db[64]; // taos client encoding
// use this for system i/o, such as reading from stdin, writing to stdout/stderr
char enc_locale[64]; // default: current localee
size_t wchar_size; // shall be fix-length
conn_val_t val;
stmts_t stmts;
errs_t errs;
conn_ext_t ext;
unsigned int connect:2;
};
#define CONN_SET_CONNECTING(conn) (conn->connect=0x01)
#define CONN_SET_CONNECTED(conn) (conn->connect=0x02)
#define CONN_SET_NORM(conn) (conn->connect=0x00)
#define CONN_IS_CONNECTING(conn) (conn->connect==0x01)
#define CONN_IS_CONNECTED(conn) (conn->connect==0x02)
#define CONN_IS_NORM(conn) (conn->connect==0x00)
conn_t* conn_new(env_t *env);
void conn_free(conn_t *conn);
SQLRETURN conn_check_charset(conn_t *conn, errs_t *errs);
int conn_add_stmt(conn_t *conn, stmt_t *stmt);
void conn_del_stmt(conn_t *conn, stmt_t *stmt);
SQLRETURN conn_connect(conn_t *conn);
void conn_disconnect(conn_t *conn);
#endif // _conn_h_

View File

@ -0,0 +1,117 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "env.h"
#include <pthread.h>
static pthread_once_t init_once = PTHREAD_ONCE_INIT;
static char default_charset[64] = {0};
static void init_routine(void) {
OD("compiled with ODBCVER:[0x%04x]", ODBCVER);
const char *charset = NULL;
setlocale(LC_ALL, "");
const char *locale = setlocale(LC_CTYPE, NULL);
if (locale) {
const char *dot = strrchr(locale, '.');
if (dot) charset = dot + 1;
}
if (!charset) {
#ifdef _MSC_VER
charset = "CP936";
#else
charset = "UTF-8";
#endif
OD("failed to find original locale, fall back to [%s]", charset);
} else {
#ifdef _MSC_VER
char buf[64];
snprintf(buf, sizeof(buf), "CP%s", charset);
charset = buf;
#endif
OD("system default charset: [%s]", charset);
}
snprintf(default_charset, sizeof(default_charset), "%s", charset);
}
static void env_release(env_t *env) {
if (!env) return;
OILE(env->refcount==0, "");
env_clr_errs(env);
}
int env_init(env_t *env) {
OILE(env, "");
OILE(env->refcount==0, "");
pthread_once(&init_once, init_routine);
int r = errs_init(&env->errs);
if (r) return -1;
snprintf(env->enc_charset, sizeof(env->enc_charset), "%s", default_charset);
env->odbc_ver = SQL_OV_ODBC3;
env->refcount = 1;
return 0;
}
// public
errs_t* env_get_errs(env_t *env) {
OILE(env, "");
return &env->errs;
}
void env_clr_errs(env_t *env) {
if (!env) return;
errs_reclaim(&env->errs);
}
void env_inc_ref(env_t *env) {
OILE(env, "");
int64_t rc = INC_REF(env);
OILE(rc>=2, "");
}
void env_dec_ref(env_t *env) {
OILE(env, "");
int64_t rc = DEC_REF(env);
if (rc>0) return;
OILE(rc==0, "");
env_release(env);
free(env);
}
env_t* env_create(void) {
env_t *env = (env_t*)calloc(1, sizeof(*env));
if (!env) return NULL;
if (env_init(env)) {
free(env);
return NULL;
}
return env;
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _env_h_
#define _env_h_
#include "../base.h"
#include "err.h"
struct env_s {
int64_t refcount;
char enc_charset[64]; // default charset from system locale
int32_t odbc_ver; // default SQL_OV_ODBC3
errs_t errs;
void (*env_free)(env_t* env);
};
int env_init(env_t *env);
env_t* env_create(void);
#endif // _env_h_

View File

@ -0,0 +1,177 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "err.h"
#include "env.h"
#include "conn.h"
#include "stmt.h"
struct err_s {
char sql_state[6];
char *err_str; // no ownership
};
static void errs_clr(errs_t *errs) {
if (!errs) return;
errs->count = 0;
errs->errs = NULL;
}
int errs_init(errs_t *errs) {
OILE(errs && errs->cache==NULL, "");
OILE(errs->count==0 && errs->errs==NULL, "");
errs->cache = todbc_buf_create();
return errs->cache ? 0 : -1;
}
void errs_reclaim(errs_t *errs) {
if (!errs) return;
if (!errs->cache) return;
errs_clr(errs);
todbc_buf_reclaim(errs->cache);
}
void errs_release(errs_t *errs) {
if (!errs) return;
if (!errs->cache) return;
errs_clr(errs);
todbc_buf_free(errs->cache);
errs->cache = NULL;
}
// public
#ifdef __GNUC__
__attribute__((format(printf, 6, 7)))
#endif
SQLRETURN errs_append(errs_t *errs, const char sql_state[6], const char *file, int line, const char *func, const char *fmt, ...)
{
OILE(errs, "");
OILE(errs->cache, "");
todbc_buf_t *cache = errs->cache;
const char *name = basename((char*)file);
char *buf = NULL;
size_t blen = 0;
while (1) {
char *p = buf;
size_t bytes = blen;
int count = 0;
int n = 0;
va_list ap;
va_start(ap, fmt);
if (bytes<0) bytes = 0;
n = vsnprintf(p, bytes, fmt, ap);
va_end(ap);
OILE(n>=0, "");
count += n;
if (p) p += n;
if (bytes) bytes -= (size_t)n;
if (bytes<0) bytes = 0;
n = snprintf(p, bytes, "@%s[%d]%s()\n", name, line, func);
OILE(n>=0, "");
count += n;
if (p) break;
buf = todbc_buf_alloc(cache, (size_t)count + 1);
if (!buf) return SQL_ERROR;
blen = (size_t)count;
}
size_t bytes = (size_t)(errs->count + 1) * sizeof(err_t);
err_t *es = (err_t*)todbc_buf_realloc(cache, errs->errs, bytes);
if (!es) return SQL_ERROR;
errs->errs = es;
errs->count += 1;
err_t *err = errs->errs + errs->count - 1;
snprintf(err->sql_state, sizeof(err->sql_state), "%s", sql_state);
err->err_str = buf;
return SQL_SUCCESS;
}
int errs_count(errs_t *errs) {
OILE(errs, "");
OILE(errs->cache, "");
return errs->count;
}
// 0/-1: ok/no-error
int errs_fetch(errs_t *errs, int idx, const char **sql_state, const char **err_str) {
OILE(errs, "");
OILE(errs->cache, "");
if (errs->count<=0) return -1;
if (idx<0 || idx>=errs->count) return -1;
err_t *err = errs->errs + idx;
if (sql_state) *sql_state = err->sql_state;
if (err_str) *err_str = err->err_str;
return 0;
}
void errs_clear(SQLSMALLINT HandleType, SQLHANDLE InputHandle) {
errs_t *errs = NULL;
if (InputHandle==NULL) return;
switch (HandleType)
{
case SQL_HANDLE_ENV:
{
env_t *env = (env_t*)InputHandle;
errs = &env->errs;
} break;
case SQL_HANDLE_DBC:
{
conn_t *conn = (conn_t*)InputHandle;
errs = &conn->errs;
} break;
case SQL_HANDLE_STMT:
{
stmt_t *stmt = (stmt_t*)InputHandle;
errs = &stmt->errs;
} break;
default:
{
ONIY(0, "");
} break;
}
if (!errs) return;
errs_reclaim(errs);
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _err_h_
#define _err_h_
#include "../base.h"
typedef struct err_s err_t;
struct errs_s {
todbc_buf_t *cache;
int count;
err_t *errs;
};
int errs_init(errs_t *errs);
void errs_reclaim(errs_t *errs);
void errs_release(errs_t *errs);
#endif // _err_h_

View File

@ -0,0 +1,78 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "field.h"
static void fieldset_clr_fields(fieldset_t *fieldset) {
if (!fieldset) return;
fieldset->fields = NULL;
fieldset->n_fields = 0;
}
static void fieldset_clr_bindings(fieldset_t *fieldset) {
if (!fieldset) return;
fieldset->bindings = NULL;
fieldset->n_bindings = 0;
}
void fieldset_init_fields(fieldset_t *fieldset) {
if (!fieldset) return;
if (fieldset->fields_cache) return;
fieldset->fields_cache = todbc_buf_create();
}
void fieldset_init_bindings(fieldset_t *fieldset) {
if (!fieldset) return;
if (fieldset->bindings_cache) return;
fieldset->bindings_cache = todbc_buf_create();
}
void fieldset_reclaim_fields(fieldset_t *fieldset) {
if (!fieldset) return;
if (!fieldset->fields_cache) return;
todbc_buf_reclaim(fieldset->fields_cache);
fieldset_clr_fields(fieldset);
}
void fieldset_reclaim_bindings(fieldset_t *fieldset) {
if (!fieldset) return;
if (!fieldset->bindings_cache) return;
todbc_buf_reclaim(fieldset->bindings_cache);
fieldset_clr_bindings(fieldset);
}
void fieldset_release(fieldset_t *fieldset) {
if (!fieldset) return;
fieldset_reclaim_fields(fieldset);
fieldset_reclaim_bindings(fieldset);
if (fieldset->fields_cache) {
todbc_buf_free(fieldset->fields_cache);
fieldset->fields_cache = NULL;
}
if (fieldset->bindings_cache) {
todbc_buf_free(fieldset->bindings_cache);
fieldset->bindings_cache = NULL;
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _field_h_
#define _field_h_
#include "../base.h"
typedef struct fieldset_s fieldset_t;
typedef struct col_binding_s col_binding_t;
typedef struct field_arg_s field_arg_t;
// SQLDescribeCol
struct field_arg_s {
SQLUSMALLINT ColumnNumber;
SQLCHAR *ColumnName;
SQLSMALLINT BufferLength;
SQLSMALLINT *NameLength;
SQLSMALLINT *DataType; // sql data type
SQLULEN *ColumnSize;
SQLSMALLINT *DecimalDigits;
SQLSMALLINT *Nullable;
};
struct field_s {
SQLUSMALLINT ColumnNumber;
SQLCHAR ColumnName[64]; // hard-coded
SQLSMALLINT DataType; // sql data type
SQLULEN ColumnSize;
SQLSMALLINT DecimalDigits;
SQLSMALLINT Nullable;
};
// SQLBindCol; SQLGetData
struct col_binding_s {
SQLUSMALLINT ColumnNumber;
SQLSMALLINT TargetType; // sql c data type
SQLPOINTER TargetValue;
SQLLEN BufferLength;
SQLLEN *StrLen_or_IndPtr;
};
struct fieldset_s {
todbc_buf_t *fields_cache;
field_t *fields;
int n_fields;
todbc_buf_t *bindings_cache;
col_binding_t *bindings;
int n_bindings;
};
void fieldset_init_fields(fieldset_t *fieldset);
void fieldset_init_bindings(fieldset_t *fieldset);
void fieldset_reclaim_fields(fieldset_t *fieldset);
void fieldset_reclaim_bindings(fieldset_t *fieldset);
void fieldset_release(fieldset_t *fields);
#endif // _field_h_

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "null_conn.h"
#include "env.h"
// null_conn
static void null_conn_free(conn_t *conn) {
OILE(conn, "");
null_conn_t *null_conn = (null_conn_t*)conn->ext.ext;
OILE(null_conn, "");
conn->ext.ext = NULL;
conn->ext.free_conn = NULL;
free(null_conn);
}
static SQLRETURN null_conn_connect(conn_t *conn) {
OILE(conn, "");
null_conn_t *null_conn = (null_conn_t*)conn->ext.ext;
OILE(null_conn && null_conn->conn==conn, "");
OILE(CONN_IS_NORM(conn), "");
return SQL_SUCCESS;
}
static void null_conn_disconnect(conn_t *conn) {
OILE(conn, "");
OILE(CONN_IS_CONNECTED(conn), "");
}
static void null_conn_free_stmt(stmt_t *stmt) {
OILE(stmt, "");
OILE(stmt->owner, "");
}
static SQLRETURN null_conn_exec_direct(stmt_t *stmt) {
OILE(stmt, "");
OILE(stmt->owner, "");
OILE(!STMT_IS_EXECUTED(stmt), "");
STMT_SET_EXECUTED(stmt);
return SQL_SUCCESS;
}
static SQLRETURN null_conn_prepare(stmt_t *stmt) {
OILE(stmt, "");
OILE(stmt->owner, "");
return SQL_SUCCESS;
}
static SQLRETURN null_conn_proc_param(stmt_t *stmt) {
OILE(stmt, "");
OILE(stmt->owner, "");
return SQL_SUCCESS;
}
static SQLRETURN null_conn_execute(stmt_t *stmt) {
OILE(stmt, "");
OILE(stmt->owner, "");
OILE(!STMT_IS_EXECUTED(stmt), "");
STMT_SET_EXECUTED(stmt);
return SQL_SUCCESS;
}
static int null_conn_init_stmt(stmt_t *stmt) {
OILE(stmt, "");
OILE(stmt->ext.ext==NULL, "");
OILE(stmt->ext.free_stmt==NULL, "");
OILE(stmt->owner==NULL, "");
stmt->ext.ext = NULL;
stmt->ext.free_stmt = null_conn_free_stmt;
stmt->ext.exec_direct = null_conn_exec_direct;
stmt->ext.prepare = null_conn_prepare;
stmt->ext.proc_param = null_conn_proc_param;
stmt->ext.execute = null_conn_execute;
return 0;
}
int conn_init_null_conn(conn_t *conn) {
OILE(conn, "");
OILE(conn->ext.ext==NULL, "");
OILE(conn->ext.free_conn==NULL, "");
null_conn_t *null_conn = (null_conn_t*)calloc(1, sizeof(*null_conn));
if (!null_conn) return -1;
null_conn->conn = conn;
conn->ext.ext = null_conn;
conn->ext.free_conn = null_conn_free;
conn->ext.connect = null_conn_connect;
conn->ext.disconnect = null_conn_disconnect;
conn->ext.init_stmt = null_conn_init_stmt;
return 0;
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _null_conn_h_
#define _null_conn_h_
#include "../base.h"
#include "conn.h"
typedef struct null_conn_s null_conn_t;
struct null_conn_s {
conn_t *conn;
};
int conn_init_null_conn(conn_t *conn);
#endif // _null_conn_h_

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "param.h"
static void paramset_clr_params(paramset_t *paramset) {
if (!paramset) return;
paramset->params = NULL;
paramset->n_params = 0;
paramset->i_row = 0;
paramset->i_col = 0;
}
static void paramset_clr_bindings(paramset_t *paramset) {
if (!paramset) return;
paramset->bindings = NULL;
paramset->n_bindings = 0;
}
void paramset_reclaim_params(paramset_t *paramset) {
if (!paramset) return;
if (!paramset->params_cache) return;
todbc_buf_reclaim(paramset->params_cache);
paramset_clr_params(paramset);
}
void paramset_reclaim_bindings(paramset_t *paramset) {
if (!paramset) return;
if (!paramset->bindings_cache) return;
todbc_buf_reclaim(paramset->bindings_cache);
paramset_clr_bindings(paramset);
}
void paramset_init_params_cache(paramset_t *paramset) {
if (!paramset) return;
if (paramset->params_cache) return;
OILE(paramset->params==NULL, "");
OILE(paramset->n_params==0, "");
paramset->params_cache = todbc_buf_create();
}
void paramset_init_bindings_cache(paramset_t *paramset) {
if (!paramset) return;
if (paramset->bindings_cache) return;
OILE(paramset->bindings==NULL, "");
OILE(paramset->n_bindings==0, "");
paramset->bindings_cache = todbc_buf_create();
}
void paramset_release(paramset_t *paramset) {
if (!paramset) return;
paramset_reclaim_params(paramset);
paramset_reclaim_bindings(paramset);
if (paramset->params_cache) {
todbc_buf_free(paramset->params_cache);
paramset->params_cache = NULL;
}
if (paramset->bindings_cache) {
todbc_buf_free(paramset->bindings_cache);
paramset->bindings_cache = NULL;
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _param_h_
#define _param_h_
#include "../base.h"
typedef struct paramset_s paramset_t;
typedef struct param_binding_s param_binding_t;
typedef struct param_val_s param_val_t;
// SQLDescribeParam
struct param_s {
SQLUSMALLINT ParameterNumber;
SQLSMALLINT DataType; // sql data type
SQLULEN ParameterSize;
SQLSMALLINT DecimalDigits;
SQLSMALLINT Nullable;
};
// SQLBindParameter
struct param_binding_s {
SQLUSMALLINT ParameterNumber;
SQLSMALLINT InputOutputType;
SQLSMALLINT ValueType; // sql c data type
SQLSMALLINT ParameterType; // sql data type
SQLULEN ColumnSize;
SQLSMALLINT DecimalDigits;
SQLPOINTER ParameterValuePtr;
SQLLEN BufferLength;
SQLLEN *StrLen_or_IndPtr;
};
struct paramset_s {
todbc_buf_t *params_cache;
param_t *params;
int n_params;
todbc_buf_t *bindings_cache;
param_binding_t *bindings;
int n_bindings;
int i_row;
int i_col;
};
void paramset_reclaim_params(paramset_t *paramset);
void paramset_reclaim_bindings(paramset_t *paramset);
void paramset_init_params_cache(paramset_t *paramset);
void paramset_init_bindings_cache(paramset_t *paramset);
void paramset_release(paramset_t *paramset);
#endif // _param_h_

View File

@ -0,0 +1,18 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "rs.h"

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _rs_h_
#define _rs_h_
#include "../base.h"
struct rs_s {
int affected_rows;
};
#endif // _rs_h_

View File

@ -0,0 +1,465 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "stmt.h"
#include "conn.h"
#include "param.h"
static int static_app_row; // SQL_ATTR_APP_ROW_DESC
static int static_app_param; // SQL_ATTR_APP_PARAM_DESC
static int static_imp_row; // SQL_ATTR_IMP_ROW_DESC
static int static_imp_param; // SQL_ATTR_IMP_PARAM_DESC
static int stmt_init_descs(stmt_t *stmt) {
OILE(stmt, "");
descs_t *descs = &stmt->descs;
descs->app_row = &static_app_row; // SQL_ATTR_APP_ROW_DESC
descs->app_param = &static_app_param; // SQL_ATTR_APP_PARAM_DESC
descs->imp_row = &static_imp_row; // SQL_ATTR_IMP_ROW_DESC
descs->imp_param = &static_imp_param; // SQL_ATTR_IMP_PARAM_DESC
return 0;
}
static int stmt_init_sql(stmt_t *stmt) {
OILE(stmt, "");
sql_t *sql = &stmt->sql;
OILE(sql->cache==NULL, "");
OILE(sql->txt.buf==NULL, "");
OILE(sql->txt.bytes==0, "");
OILE(sql->txt.total_bytes==0, "");
sql->cache = todbc_buf_create();
if (!sql->cache) return -1;
return 0;
}
static void stmt_release_sql(stmt_t *stmt) {
OILE(stmt, "");
sql_t *sql = &stmt->sql;
if (!sql->cache) return;
todbc_buf_free(sql->cache);
sql->cache = NULL;
sql->txt.buf = NULL;
sql->txt.bytes = 0;
sql->txt.total_bytes = 0;
}
static int stmt_init(stmt_t *stmt, conn_t *conn) {
OILE(stmt, "");
OILE(conn, "");
OILE(stmt->owner==NULL, "");
stmt->typeinfo = SQL_UNKNOWN_TYPE;
int r = errs_init(&stmt->errs);
if (r) return -1;
r = stmt_init_descs(stmt);
if (r) return -1;
r = stmt_init_sql(stmt);
if (r) return -1;
stmt->attr.bind_type = SQL_PARAM_BIND_BY_COLUMN;
r = conn_add_stmt(conn, stmt);
OILE(r==0, "");
OILE(stmt->owner && stmt->owner->conn==conn, "");
return 0;
}
static void stmt_release(stmt_t *stmt) {
if (!stmt) return;
OILE(stmt->owner, "");
conn_t *conn = stmt->owner->conn;
conn_del_stmt(conn, stmt);
paramset_release(&stmt->paramset);
fieldset_release(&stmt->fieldset);
stmt_release_sql(stmt);
errs_release(&stmt->errs);
}
static SQLRETURN do_process_param(stmt_t *stmt) {
OILE(stmt->ext.proc_param, "");
return stmt->ext.proc_param(stmt);
}
static SQLRETURN do_process_param_row(stmt_t *stmt) {
paramset_t *paramset = &stmt->paramset;
int n_params = paramset->n_params;
SQLRETURN r = SQL_SUCCESS;
paramset->i_col = 0;
for (; paramset->i_col<n_params; ++paramset->i_col) {
r = do_process_param(stmt);
if (r!=SQL_SUCCESS) return r;
}
return SQL_SUCCESS;
}
stmt_t* stmt_new(conn_t *conn) {
stmt_t *stmt = (stmt_t*)calloc(1, sizeof(*stmt));
if (!stmt) return NULL;
if (stmt_init(stmt, conn)) {
free(stmt);
return NULL;
}
return stmt;
}
void stmt_free(stmt_t *stmt) {
if (!stmt) return;
// clean ext stuff
if (stmt->ext.free_stmt) {
stmt->ext.free_stmt(stmt);
stmt->ext.free_stmt = NULL;
}
// clean stmt stuff
stmt_release(stmt);
free(stmt);
}
conn_t* stmt_get_conn(stmt_t *stmt) {
if (!stmt) return NULL;
if (!stmt->owner) return NULL;
return stmt->owner->conn;
}
void stmt_reclaim_params(stmt_t *stmt) {
if (!stmt) return;
paramset_reclaim_params(&stmt->paramset);
}
void stmt_reclaim_param_bindings(stmt_t *stmt) {
if (!stmt) return;
paramset_reclaim_bindings(&stmt->paramset);
}
void stmt_reclaim_field_bindings(stmt_t *stmt) {
if (!stmt) return;
fieldset_reclaim_bindings(&stmt->fieldset);
}
void stmt_close_rs(stmt_t *stmt) {
if (!stmt) return;
OILE(stmt->ext.close_rs, "");
stmt->ext.close_rs(stmt);
stmt->typeinfo = SQL_UNKNOWN_TYPE;
stmt->eof = 0;
fieldset_reclaim_fields(&stmt->fieldset);
// for the performance
// we don't reclaim field-binds here
// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlbindcol-function?view=sql-server-ver15
STMT_SET_NORM(stmt);
}
void stmt_reset_params(stmt_t *stmt) {
if (!stmt) return;
stmt->attr.paramset_size = 0;
stmt_reclaim_param_bindings(stmt);
}
static SQLRETURN stmt_set_sql(stmt_t *stmt, todbc_string_t *txt) {
OILE(stmt, "");
OILE(txt, "");
errs_t *errs = &stmt->errs;
sql_t *sql = &stmt->sql;
OILE(txt!=&sql->txt, "");
todbc_buf_t *cache = sql->cache;
OILE(sql->cache, "");
conn_t *conn = stmt_get_conn(stmt);
OILE(conn, "");
const char *enc = conn->enc_db;
todbc_buf_reclaim(cache);
sql->txt = todbc_string_conv_to(txt, enc, cache);
if (!sql->txt.buf) {
SET_OOM(errs, "");
return SQL_ERROR;
}
return SQL_SUCCESS;
}
static SQLRETURN do_stmt_prepare(stmt_t *stmt) {
OILE(stmt->ext.prepare, "");
return stmt->ext.prepare(stmt);
}
static void do_stmt_clear_param_vals(stmt_t *stmt) {
OILE(stmt->ext.clear_param_vals, "");
stmt->ext.clear_param_vals(stmt);
}
static void do_stmt_clear_params(stmt_t *stmt) {
stmt->prepared = 0;
paramset_t *paramset = &stmt->paramset;
paramset_reclaim_params(paramset);
do_stmt_clear_param_vals(stmt);
}
SQLRETURN stmt_exec_direct(stmt_t *stmt, todbc_string_t *txt) {
OILE(stmt, "");
OILE(txt, "");
SQLRETURN r = stmt_set_sql(stmt, txt);
if (r!=SQL_SUCCESS) return r;
do_stmt_clear_params(stmt);
if (stmt->ext.exec_direct) {
r = stmt->ext.exec_direct(stmt);
} else {
r = do_stmt_prepare(stmt);
if (r!=SQL_SUCCESS) return r;
stmt->prepared = 1;
OILE(0, "");
}
return r;
}
SQLRETURN stmt_prepare(stmt_t *stmt, todbc_string_t *txt) {
OILE(stmt, "");
OILE(txt, "");
SQLRETURN r = stmt_set_sql(stmt, txt);
if (r!=SQL_SUCCESS) return r;
do_stmt_clear_params(stmt);
r = do_stmt_prepare(stmt);
if (r!=SQL_SUCCESS) return r;
stmt->prepared = 1;
return SQL_SUCCESS;
}
SQLRETURN stmt_bind_param(stmt_t *stmt, param_binding_t *arg) {
OILE(stmt, "");
OILE(arg, "");
OILE(arg->ParameterNumber>0, "");
int idx = arg->ParameterNumber - 1;
errs_t *errs = &stmt->errs;
paramset_t *paramset = &stmt->paramset;
paramset_init_bindings_cache(paramset);
if (!paramset->bindings_cache) {
SET_OOM(errs, "failed to alloc cache for param binds");
return SQL_ERROR;
}
todbc_buf_t *cache = paramset->bindings_cache;
OILE(cache, "");
param_binding_t *bindings = paramset->bindings;
if (idx>=paramset->n_bindings) {
size_t num = (size_t)(idx + 1);
// align
const size_t block = 10;
num = (num + block-1) / block * block;
bindings = (param_binding_t*)todbc_buf_realloc(cache, bindings, num * sizeof(*bindings));
if (!bindings) {
SET_OOM(errs, "failed to realloc buf for param binds");
return SQL_ERROR;
}
paramset->bindings = bindings;
paramset->n_bindings = idx + 1;
}
OILE(paramset->bindings, "");
OILE(idx<paramset->n_bindings, "");
param_binding_t *binding = bindings + idx;
*binding = *arg;
if (paramset->n_bindings>paramset->n_params) return SQL_SUCCESS;
OILE(stmt->ext.set_param_conv, "");
return stmt->ext.set_param_conv(stmt, idx);
}
SQLRETURN stmt_execute(stmt_t *stmt) {
OILE(stmt, "");
OILE(!STMT_IS_EXECUTING(stmt), "");
SQLRETURN r = SQL_SUCCESS;
if (stmt->prepared==0) {
do_stmt_clear_params(stmt);
r = do_stmt_prepare(stmt);
if (r!=SQL_SUCCESS) return r;
stmt->prepared = 1;
}
OILE(stmt->prepared==1, "");
errs_t *errs = &stmt->errs;
paramset_t *paramset = &stmt->paramset;
int n_params = paramset->n_params;
int n_bindings = paramset->n_bindings;
if (n_params>n_bindings) {
SET_GENERAL(errs, "parameters need to be bound first");
return SQL_ERROR;
}
if (n_params>0) {
int paramset_size = (int)stmt->attr.paramset_size;
if (paramset_size==0) paramset_size = 1;
stmt->attr.paramset_size = (SQLULEN)paramset_size;
paramset->i_row = 0;
for (; paramset->i_row<paramset_size; ++paramset->i_row) {
r = do_process_param_row(stmt);
if (r) return r;
OILE(stmt->ext.param_row_processed, "");
r = stmt->ext.param_row_processed(stmt);
if (r) return r;
}
}
OILE(stmt->ext.execute, "");
return stmt->ext.execute(stmt);
}
SQLRETURN stmt_fetch(stmt_t *stmt) {
OILE(stmt, "");
OILE(STMT_IS_EXECUTED(stmt), "");
if (stmt->eof) return SQL_NO_DATA;
OILE(stmt->ext.fetch, "");
SQLRETURN r = stmt->ext.fetch(stmt);
if (r!=SQL_SUCCESS) return r;
fieldset_t *fieldset = &stmt->fieldset;
if (fieldset->n_bindings==0) return SQL_SUCCESS;
OILE(fieldset->n_bindings>0, "");
for (size_t i=0; i<fieldset->n_bindings; ++i) {
OILE(fieldset->bindings, "");
col_binding_t *binding = fieldset->bindings + i;
if (binding->ColumnNumber!=i+1) {
OILE(binding->ColumnNumber==0, "");
continue;
}
OILE(stmt->ext.get_data, "");
r = stmt->ext.get_data(stmt, binding);
if (r!=SQL_SUCCESS) return r;
}
return SQL_SUCCESS;
}
SQLRETURN stmt_get_data(stmt_t *stmt, col_binding_t *binding) {
OILE(stmt, "");
OILE(STMT_IS_EXECUTED(stmt), "");
OILE(stmt->eof==0, "");
OILE(stmt->ext.get_data, "");
return stmt->ext.get_data(stmt, binding);
}
SQLRETURN stmt_bind_col(stmt_t *stmt, col_binding_t *binding) {
OILE(stmt, "");
// shall we check execute state?
errs_t *errs = &stmt->errs;
fieldset_t *fieldset = &stmt->fieldset;
todbc_buf_t *cache = fieldset->bindings_cache;
if (cache==NULL) {
fieldset_init_bindings(fieldset);
cache = fieldset->bindings_cache;
if (!cache) {
SET_OOM(errs, "");
return SQL_ERROR;
}
}
OILE(cache, "");
col_binding_t *bindings = fieldset->bindings;
OILE(binding->ColumnNumber>0, "");
if (binding->ColumnNumber>=fieldset->n_bindings) {
size_t num = (size_t)binding->ColumnNumber;
const size_t block = 10;
size_t align = (num+block-1)/block*block;
size_t total = align * sizeof(*bindings);
bindings = (col_binding_t*)todbc_buf_realloc(cache, bindings, total);
if (!bindings) {
SET_OOM(errs, "");
return SQL_ERROR;
}
for (size_t i = (size_t)fieldset->n_bindings; i<num; ++i) {
bindings[i].ColumnNumber = 0; // not set yet
}
fieldset->bindings = bindings;
fieldset->n_bindings = (int)num;
}
OILE(bindings, "");
OILE(binding->ColumnNumber<=fieldset->n_bindings, "");
bindings[binding->ColumnNumber-1] = *binding;
return SQL_SUCCESS;
}
// public
errs_t* stmt_get_errs(stmt_t *stmt) {
OILE(stmt, "");
return &stmt->errs;
}
void stmt_clr_errs(stmt_t *stmt) {
if (!stmt) return;
errs_reclaim(&stmt->errs);
}

View File

@ -0,0 +1,143 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _stmt_h_
#define _stmt_h_
#include "../base.h"
#include "err.h"
#include "field.h"
#include "param.h"
#include "rs.h"
typedef struct descs_s descs_t;
typedef struct stmts_s stmts_t;
typedef struct stmt_ext_s stmt_ext_t;
typedef struct stmt_attr_s stmt_attr_t;
typedef struct sql_s sql_t;
struct descs_s {
void *app_row; // SQL_ATTR_APP_ROW_DESC
void *app_param; // SQL_ATTR_APP_PARAM_DESC
void *imp_row; // SQL_ATTR_IMP_ROW_DESC
void *imp_param; // SQL_ATTR_IMP_PARAM_DESC
};
struct stmt_ext_s {
void *ext;
void (*free_stmt)(stmt_t *stmt);
void (*clear_params)(stmt_t *stmt);
void (*clear_param_vals)(stmt_t *stmt);
SQLRETURN (*get_param)(stmt_t *stmt, param_t *arg);
SQLRETURN (*set_param_conv)(stmt_t *stmt, int idx);
SQLRETURN (*exec_direct)(stmt_t *stmt);
SQLRETURN (*prepare)(stmt_t *stmt);
SQLRETURN (*proc_param)(stmt_t *stmt);
SQLRETURN (*param_row_processed)(stmt_t *stmt);
SQLRETURN (*execute)(stmt_t *stmt);
SQLRETURN (*get_affected_rows)(stmt_t *stmt, SQLLEN *RowCount);
SQLRETURN (*get_fields_count)(stmt_t *stmt, SQLSMALLINT *ColumnCount);
SQLRETURN (*get_field)(stmt_t *stmt, field_arg_t *arg);
SQLRETURN (*fetch)(stmt_t *stmt);
SQLRETURN (*get_data)(stmt_t *stmt, col_binding_t *col);
void (*close_rs)(stmt_t *stmt);
};
struct stmt_attr_s {
// SQL_ATTR_PARAM_BIND_TYPE: SQL_PARAM_BIND_BY_COLUMN or row size
SQLULEN bind_type; // default: SQL_PARAM_BIND_BY_COLUMN
// SQL_ATTR_PARAM_BIND_OFFSET_PTR
SQLULEN *bind_offset_ptr; // default: NULL
// SQL_ATTR_PARAMSET_SIZE
SQLULEN paramset_size; // default: 0
};
struct sql_s {
todbc_buf_t *cache;
todbc_string_t txt;
};
struct stmt_s {
stmts_t *owner;
stmt_t *next;
stmt_t *prev;
SQLSMALLINT typeinfo;
descs_t descs;
sql_t sql;
stmt_attr_t attr;
paramset_t paramset;
fieldset_t fieldset;
int affected_rows;
rs_t rs;
errs_t errs;
stmt_ext_t ext;
unsigned int prepared:1;
unsigned int execute:2;
unsigned int eof:1;
};
struct stmts_s {
conn_t *conn;
int count;
stmt_t *head;
stmt_t *tail;
};
#define STMT_TYPEINFO(stmt) (stmt->typeinfo!=SQL_UNKNOWN_TYPE)
#define STMT_SET_EXECUTING(stmt) (stmt->execute=0x01)
#define STMT_SET_EXECUTED(stmt) (stmt->execute=0x02)
#define STMT_SET_NORM(stmt) (stmt->execute=0x00)
#define STMT_IS_EXECUTING(stmt) (stmt->execute==0x01)
#define STMT_IS_EXECUTED(stmt) (stmt->execute==0x02)
#define STMT_IS_NORM(stmt) (stmt->execute==0x00)
stmt_t* stmt_new(conn_t *conn);
void stmt_free(stmt_t *stmt);
conn_t* stmt_get_conn(stmt_t *stmt);
void stmt_reclaim_params(stmt_t *stmt);
void stmt_reclaim_param_binds(stmt_t *stmt);
void stmt_reclaim_field_binds(stmt_t *stmt);
void stmt_close_rs(stmt_t *stmt);
void stmt_reset_params(stmt_t *stmt);
SQLRETURN stmt_exec_direct(stmt_t *stmt, todbc_string_t *txt);
SQLRETURN stmt_prepare(stmt_t *stmt, todbc_string_t *txt);
SQLRETURN stmt_bind_param(stmt_t *stmt, param_binding_t *arg);
SQLRETURN stmt_execute(stmt_t *stmt);
SQLRETURN stmt_fetch(stmt_t *stmt);
SQLRETURN stmt_get_data(stmt_t *stmt, col_binding_t *binding);
SQLRETURN stmt_bind_col(stmt_t *stmt, col_binding_t *binding);
#endif // _stmt_h_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _tsdb_impl_h_
#define _tsdb_impl_h_
#include "../base.h"
#include "conn.h"
#define DEFAULT_SERVER "localhost:6030"
int conn_init_tsdb_conn(conn_t *conn);
#endif // _tsdb_impl_h_

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _col_h_
#define _col_h_
#include "base.h"
struct col_s {
};
#endif // _col_h_

View File

@ -0,0 +1,6 @@
@echo off
REM install driver
odbcconf /A {INSTALLDRIVER "TAOS|Driver=C:\TDengine\driver\todbc.dll|ConnectFunctions=YYN|DriverODBCVer=03.00|FileUsage=0|SQLLevel=0"}
REM config user dsn
odbcconf /A {CONFIGDSN "TAOS" "DSN=TAOS_DSN"}

View File

@ -2,6 +2,13 @@
set -u
EXT="so"
[[ `uname` == 'Darwin' ]] && EXT="dylib"
SUDO="sudo"
[[ `uname` == 'Darwin' ]] && SUDO=""
BLD_DIR="$1"
rm -f "${BLD_DIR}/template.ini"
@ -10,18 +17,30 @@ rm -f "${BLD_DIR}/template.dsn"
cat > "${BLD_DIR}/template.ini" <<EOF
[TAOS]
Description=taos odbc driver
Driver=${BLD_DIR}/build/lib/libtodbc.so
Driver=${BLD_DIR}/build/lib/libtodbc.${EXT}
EOF
cat > "${BLD_DIR}/template.dsn" <<EOF
[TAOS_DSN]
Description=Connection to TAOS
Driver=TAOS
Server=localhost:6030
// UID=
// PWD=
// Server=localhost:6030
// https://www.npmjs.com/package/odbc
// SQL_C_FLOAT not support yet for node odbc, thus could promote to SQL_DOUBLE
// workaround:
// map.float=SQL_DOUBLE
// pyodbc: https://github.com/mkleehammer/pyodbc
// bigint seems not working properly
// workaround:
// map.bigint=SQL_CHAR
EOF
# better remove first ?
sudo odbcinst -i -d -f "${BLD_DIR}/template.ini" &&
${SUDO} odbcinst -i -d -f "${BLD_DIR}/template.ini" &&
odbcinst -i -s -f "${BLD_DIR}/template.dsn" &&
echo "odbc install done"

File diff suppressed because it is too large Load Diff

View File

@ -3,13 +3,19 @@ SQLAllocEnv
SQLFreeEnv
SQLAllocConnect
SQLFreeConnect
SQLGetEnvAttr
SQLSetEnvAttr
SQLGetConnectAttr
SQLGetConnectOption
SQLGetInfo
SQLConnect
SQLDisconnect
SQLAllocStmt
SQLAllocHandle
SQLFreeHandle
SQLFreeStmt
SQLExecDirect
SQLExecDirectW
;SQLExecDirectW
SQLNumResultCols
SQLRowCount
SQLColAttribute
@ -17,14 +23,45 @@ SQLGetData
SQLFetch
SQLPrepare
SQLExecute
SQLGetDiagField
SQLParamData
SQLPutData
;SQLGetDiagField
SQLGetDiagRec
SQLBindParameter
SQLDescribeParam
SQLDriverConnect
SQLSetConnectAttr
SQLDescribeCol
SQLBindCol
SQLNumParams
SQLSetStmtAttr
SQLBindParam
SQLCancel
SQLCancelHandle
SQLCloseCursor
SQLColumns
SQLCopyDesc
SQLDataSources
SQLEndTran
;SQLError
SQLFetchScroll
SQLGetCursorName
SQLGetDescField
SQLGetDescRec
;SQLGetFunctions
SQLGetStmtAttr
SQLGetStmtOption
SQLGetTypeInfo
SQLSetConnectOption
SQLSetCursorName
SQLSetDescField
SQLSetDescRec
SQLSetParam
SQLSetStmtOption
SQLSpecialColumns
SQLStatistics
SQLTables
SQLTransact
ConfigDSN
ConfigTranslator
ConfigDriver

View File

@ -0,0 +1,410 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "todbc_buf.h"
#include "todbc_list.h"
#include "todbc_log.h"
#include "todbc_tls.h"
#include <stdlib.h>
#include <string.h>
#define BLOCK_SIZE (1024*16)
// alignment for holding whatever struct would be
#define ALIGN(size) ((size==0?1:size) + sizeof(size_t) - 1) / sizeof(size_t) * sizeof(size_t);
typedef struct buf_rec_s buf_rec_t;
typedef struct buf_block_s buf_block_t;
struct buf_rec_s {
size_t size; // payload size couting from ptr[0]
buf_block_t *owner;
char ptr[0]; // place-holder
};
#define PTR_OFFSET() ((size_t)(((buf_rec_t*)0)->ptr))
struct buf_block_s {
todbc_buf_t *owner;
size_t cap; // total payload space available for allocation
size_t ptr; // beginning of free space
char base[0]; // place-holder
};
#define BASE_OFFSET() ((size_t)(((buf_block_t*)0)->base))
struct todbc_buf_s {
char buf[BLOCK_SIZE];
buf_block_t *block; // mapped to &buf[0]
todbc_list_t *list; // dynamic allocated blocks list
};
#define BASE_STATIC(block) (block->owner->buf + BASE_OFFSET()==block->base)
typedef struct arg_s arg_t;
struct arg_s {
todbc_buf_t *buf;
// input
char *arg_ptr;
size_t arg_size;
buf_rec_t *arg_rec;
size_t arg_align;
// output
buf_rec_t *rec;
};
static buf_rec_t* ptr_rec(todbc_buf_t *buf, void *ptr) {
char *p = (char*)ptr;
char *begin = p-PTR_OFFSET();
buf_rec_t *rec = (buf_rec_t*)begin;
OILE(rec, "");
OILE(rec->size, "");
OILE((rec->size)%sizeof(void*)==0, "");
buf_block_t *block = rec->owner;
OILE(block, "");
OILE(block->owner==buf, "");
OILE(block->base, "");
char *end = begin + sizeof(buf_rec_t) + rec->size;
OILE(begin>=block->base, "");
OILE(end<=block->base+block->ptr, "");
return rec;
}
static buf_block_t* buf_block_create(size_t align);
static buf_rec_t* buf_block_realloc(buf_block_t *block, arg_t *arg);
static void buf_block_free(buf_block_t *block);
static void buf_block_reclaim(buf_block_t *block);
#define ALLOC_LIST() do { \
if (buf->list) break; \
todbc_list_conf_t conf = {0}; \
conf.arg = buf; \
conf.val_free = do_free_buf_block; \
buf->list = todbc_list_create(conf); \
} while (0)
todbc_buf_t* todbc_buf_create(void) {
todbc_buf_t *buf = (todbc_buf_t*)calloc(1, sizeof(*buf));
if (!buf) return NULL;
buf_block_t *block = (buf_block_t*)(buf->buf);
block->owner = buf;
block->cap = sizeof(buf->buf) - sizeof(buf_block_t);
block->ptr = 0;
buf->block = block;
OILE(BASE_STATIC(buf->block), "");
return buf;
}
void todbc_buf_free(todbc_buf_t *buf) {
if (!buf) return;
todbc_list_free(buf->list);
buf->list = NULL;
free(buf);
}
void* todbc_buf_alloc(todbc_buf_t *buf, size_t size) {
return todbc_buf_realloc(buf, NULL, size);
}
void* todbc_buf_calloc(todbc_buf_t *buf, size_t count, size_t size) {
size_t align = ALIGN(size);
size_t total = count * align;
if (total > INT64_MAX) return NULL;
void *ptr = todbc_buf_realloc(buf, NULL, total);
if (!ptr) return NULL;
memset(ptr, 0, total);
return ptr;
}
static void do_free_buf_block(todbc_list_t *list, void *val, void *arg);
static int alloc_space_by(todbc_list_t *list, void *val, void *arg);
void* todbc_buf_realloc(todbc_buf_t *buf, void *ptr, size_t size) {
OILE(buf, "");
OILE(sizeof(buf_block_t)%sizeof(void*)==0, "");
OILE(sizeof(buf_rec_t)%sizeof(void*)==0, "");
size_t align = ALIGN(size);
size_t req_size = align + sizeof(buf_rec_t);
buf_rec_t *rec = NULL;
buf_block_t *block = NULL;
if (ptr) {
rec = ptr_rec(buf, ptr);
if (align<=rec->size) return ptr;
block = rec->owner;
char *tail_rec = rec->ptr + rec->size;
char *tail_block = block->base + block->ptr;
if (tail_rec==tail_block) {
char *end_rec = rec->ptr + align;
char *end_block = block->base + block->cap;
if (end_rec<=end_block) {
rec->size = align;
block->ptr = (size_t)(end_rec - block->base);
return ptr;
}
} else {
size_t remain = block->cap - block->ptr;
if (req_size<=remain) {
char *new_ptr = block->base + block->ptr;
block->ptr += req_size;
buf_rec_t *new_rec = (buf_rec_t*)new_ptr;
new_rec->size = align;
new_rec->owner = block;
memcpy(new_rec->ptr, ptr, rec->size);
return new_rec->ptr;
}
}
}
arg_t arg = {0};
arg.buf = buf;
arg.arg_ptr = ptr;
arg.arg_size = size;
arg.arg_rec = rec;
arg.arg_align = align;
if (block!=buf->block) {
buf_rec_t *new_rec = buf_block_realloc(buf->block, &arg);
if (new_rec) return new_rec->ptr;
}
ALLOC_LIST();
if (!buf->list) return NULL;
int r = todbc_list_traverse(buf->list, alloc_space_by, &arg);
if (r) {
OILE(arg.rec, "");
OILE(arg.rec->ptr, "");
return arg.rec->ptr;
}
block = buf_block_create(arg.arg_align);
if (!block) return NULL;
OILE(block->owner==NULL, "");
r = todbc_list_pushfront(buf->list, block);
OILE(r==0, "");
block->owner = buf;
buf_rec_t *p = buf_block_realloc(block, &arg);
OILE(p, "");
return p->ptr;
}
static int do_reclaim(todbc_list_t *list, void *val, void *arg);
void todbc_buf_reclaim(todbc_buf_t *buf) {
if (!buf) return;
buf_block_reclaim(buf->block);
if (!buf->list) return;
todbc_list_traverse(buf->list, do_reclaim, buf);
}
static int do_reclaim(todbc_list_t *list, void *val, void *arg) {
todbc_buf_t *buf = (todbc_buf_t*)arg;
buf_block_t *block = (buf_block_t*)val;
OILE(list, "");
OILE(block, "");
OILE(block->owner==buf, "");
buf_block_reclaim(block);
return 0;
}
static void buf_block_reclaim(buf_block_t *block) {
block->ptr = 0;
}
static void buf_block_free(buf_block_t *block) {
if (!block) return;
buf_block_reclaim(block);
if (BASE_STATIC(block)) return;
block->owner = NULL;
block->cap = 0;
block->ptr = 0;
free(block);
}
static void do_free_buf_block(todbc_list_t *list, void *val, void *arg) {
todbc_buf_t *buf = (todbc_buf_t*)arg;
OILE(buf && buf->list==list, "");
buf_block_t *block = (buf_block_t*)val;
OILE(block, "");
OILE(buf==block->owner, "");
buf_block_free(block);
}
static int alloc_space_by(todbc_list_t *list, void *val, void *arg) {
buf_block_t *block = (buf_block_t*)val;
arg_t *targ = (arg_t*)arg;
buf_rec_t *rec = targ->arg_rec;
if (rec && rec->owner == block) return 0;
buf_rec_t *new_rec = buf_block_realloc(block, targ);
if (!new_rec) return 0;
return 1;
}
static buf_block_t* buf_block_create(size_t size) {
size_t align = ALIGN(size);
size_t req_size = sizeof(buf_block_t) + sizeof(buf_rec_t) + align;
req_size = (req_size + BLOCK_SIZE - 1) / BLOCK_SIZE * BLOCK_SIZE;
buf_block_t *block = (buf_block_t*)malloc(req_size);
if (!block) return NULL;
block->owner = NULL;
block->cap = req_size - sizeof(buf_block_t);
block->ptr = 0;
return block;
}
static buf_rec_t* buf_block_realloc(buf_block_t *block, arg_t *arg) {
OILE(block, "");
OILE(arg, "");
OILE(block->base, "");
OILE(block->cap >= block->ptr, "");
char *ptr = arg->arg_ptr;
buf_rec_t *rec = arg->arg_rec;
OILE(rec==NULL || rec->owner!=block, "");
size_t align = arg->arg_align;
size_t req_size = sizeof(buf_rec_t) + align;
OILE(req_size>0, "");
size_t remain = block->cap - block->ptr;
if (req_size > remain) {
return NULL;
}
char *p = block->base + block->ptr;
buf_rec_t *new_rec = (buf_rec_t*)p;
new_rec->size = align;
new_rec->owner = block;
block->ptr += req_size;
if (ptr) {
memcpy(new_rec->ptr, ptr, arg->arg_rec->size);
}
arg->rec = new_rec;
return new_rec;
}
// test
void todbc_buf_test_random(size_t iterates, size_t size, int raw) {
size_t n_reallocs = 0;
todbc_buf_t *cache = NULL;
OD("use %s", raw ? "realloc" : "todbc_buf_t");
if (!raw) {
cache = todbc_buf_create();
OILE(cache, "");
}
srand((unsigned)time(0));
char *buf = NULL;
size_t blen = 0;
while (iterates-- > 0) {
char *p = NULL;
size_t i = 0;
size_t len = (size_t)rand()%size;
if (raw) {
p = realloc(buf, len);
} else {
p = todbc_buf_realloc(cache, buf, len);
}
OILE(p, "");
for (i=blen; i<len; ++i) {
p[i] = '\0';
}
buf = p;
blen = len;
}
if (!raw) {
todbc_buf_free(cache);
}
OD("n_reallocs: %zd", n_reallocs);
}
void todbc_buf_test(size_t iterates, size_t size, int raw) {
size_t n_reallocs = 0;
todbc_buf_t *cache = NULL;
OD("use %s", raw ? "realloc" : "todbc_buf_t");
if (!raw) {
cache = todbc_buf_create();
OILE(cache, "");
}
while (iterates-- > 0) {
size_t i = 0;
char *buf = NULL;
while (i<size) {
char *p = NULL;
if (raw) {
p = realloc(buf, i);
} else {
p = todbc_buf_realloc(cache, buf, i);
}
OILE(p, "");
if (p!=buf) {
// OD("buf/p:%zd[%p/%p]", i, buf, p);
buf = p;
++n_reallocs;
}
if (i) p[i-1] = '\0';
++i;
}
// OD("buf :%zd[%p]", i, buf);
}
if (!raw) {
todbc_buf_free(cache);
}
OD("n_reallocs: %zd", n_reallocs);
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _todbc_buf_h_
#define _todbc_buf_h_
#include <stdint.h>
#include <stddef.h>
// non-thread-safe
typedef struct todbc_buf_s todbc_buf_t;
todbc_buf_t* todbc_buf_create(void);
void todbc_buf_free(todbc_buf_t *buf);
void* todbc_buf_alloc(todbc_buf_t *buf, size_t size);
void* todbc_buf_calloc(todbc_buf_t *buf, size_t count, size_t size);
void* todbc_buf_realloc(todbc_buf_t *buf, void *ptr, size_t size);
char* todbc_buf_strdup(todbc_buf_t *buf, const char *str);
void todbc_buf_reclaim(todbc_buf_t *buf);
void todbc_buf_test_random(size_t iterates, size_t size, int raw);
void todbc_buf_test(size_t iterates, size_t size, int raw);
#endif // _todbc_buf_h_

View File

@ -1,660 +0,0 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "todbc_conv.h"
#include "todbc_log.h"
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
const char* tsdb_conv_code_str(TSDB_CONV_CODE code) {
switch (code) {
case TSDB_CONV_OK: return "TSDB_CONV_OK";
case TSDB_CONV_NOT_AVAIL: return "TSDB_CONV_NOT_AVAIL";
case TSDB_CONV_OOM: return "TSDB_CONV_OOM";
case TSDB_CONV_OOR: return "TSDB_CONV_OOR";
case TSDB_CONV_TRUNC_FRACTION: return "TSDB_CONV_TRUNC_FRACTION";
case TSDB_CONV_TRUNC: return "TSDB_CONV_TRUNC";
case TSDB_CONV_CHAR_NOT_NUM: return "TSDB_CONV_CHAR_NOT_NUM";
case TSDB_CONV_CHAR_NOT_TS: return "TSDB_CONV_CHAR_NOT_TS";
case TSDB_CONV_NOT_VALID_TS: return "TSDB_CONV_NOT_VALID_TS";
case TSDB_CONV_GENERAL: return "TSDB_CONV_GENERAL";
case TSDB_CONV_SRC_TOO_LARGE: return "TSDB_CONV_SRC_TOO_LARGE";
case TSDB_CONV_SRC_BAD_SEQ: return "TSDB_CONV_SRC_BAD_SEQ";
case TSDB_CONV_SRC_INCOMPLETE: return "TSDB_CONV_SRC_INCOMPLETE";
case TSDB_CONV_SRC_GENERAL: return "TSDB_CONV_SRC_GENERAL";
case TSDB_CONV_BAD_CHAR: return "TSDB_CONV_BAD_CHAR";
default: return "UNKNOWN";
};
}
// src: int
TSDB_CONV_CODE tsdb_int64_to_bit(int64_t src, int8_t *dst) {
*dst = (int8_t)src;
if (src==0 || src==1) return TSDB_CONV_OK;
return TSDB_CONV_OOR;
}
TSDB_CONV_CODE tsdb_int64_to_tinyint(int64_t src, int8_t *dst) {
*dst = (int8_t)src;
if (src == *dst) return TSDB_CONV_OK;
return TSDB_CONV_OOR;
}
TSDB_CONV_CODE tsdb_int64_to_smallint(int64_t src, int16_t *dst) {
*dst = (int16_t)src;
if (src == *dst) return TSDB_CONV_OK;
return TSDB_CONV_OOR;
}
TSDB_CONV_CODE tsdb_int64_to_int(int64_t src, int32_t *dst) {
*dst = (int32_t)src;
if (src == *dst) return TSDB_CONV_OK;
return TSDB_CONV_OOR;
}
TSDB_CONV_CODE tsdb_int64_to_bigint(int64_t src, int64_t *dst) {
*dst = src;
return TSDB_CONV_OK;
}
TSDB_CONV_CODE tsdb_int64_to_ts(int64_t src, int64_t *dst) {
*dst = src;
time_t t = (time_t)(src / 1000);
struct tm tm = {0};
if (localtime_r(&t, &tm)) return TSDB_CONV_OK;
return TSDB_CONV_OOR;
}
TSDB_CONV_CODE tsdb_int64_to_float(int64_t src, float *dst) {
*dst = (float)src;
int64_t v = (int64_t)*dst;
if (v==src) return TSDB_CONV_OK;
return TSDB_CONV_OOR;
}
TSDB_CONV_CODE tsdb_int64_to_double(int64_t src, double *dst) {
*dst = (double)src;
int64_t v = (int64_t)*dst;
if (v==src) return TSDB_CONV_OK;
return TSDB_CONV_OOR;
}
TSDB_CONV_CODE tsdb_int64_to_char(int64_t src, char *dst, size_t dlen) {
int n = snprintf(dst, dlen, "%" PRId64 "", src);
DASSERT(n>=0);
if (n<dlen) return TSDB_CONV_OK;
return TSDB_CONV_TRUNC;
}
// src: double
TSDB_CONV_CODE tsdb_double_to_bit(double src, int8_t *dst) {
*dst = (int8_t)src;
if (src<0 || src>=2) return TSDB_CONV_OOR;
if (src == *dst) return TSDB_CONV_OK;
int64_t v = (int64_t)src;
if (v == *dst) return TSDB_CONV_TRUNC_FRACTION;
return TSDB_CONV_TRUNC;
}
TSDB_CONV_CODE tsdb_double_to_tinyint(double src, int8_t *dst) {
*dst = (int8_t)src;
if (src<SCHAR_MIN || src>SCHAR_MAX) return TSDB_CONV_OOR;
if (src == *dst) return TSDB_CONV_OK;
int64_t v = (int64_t)src;
if (v == *dst) return TSDB_CONV_TRUNC_FRACTION;
return TSDB_CONV_TRUNC;
}
TSDB_CONV_CODE tsdb_double_to_smallint(double src, int16_t *dst) {
*dst = (int16_t)src;
if (src<SHRT_MIN || src>SHRT_MAX) return TSDB_CONV_OOR;
if (src == *dst) return TSDB_CONV_OK;
int64_t v = (int64_t)src;
if (v == *dst) return TSDB_CONV_TRUNC_FRACTION;
return TSDB_CONV_TRUNC;
}
TSDB_CONV_CODE tsdb_double_to_int(double src, int32_t *dst) {
*dst = (int32_t)src;
if (src<LONG_MIN || src>LONG_MAX) return TSDB_CONV_OOR;
if (src == *dst) return TSDB_CONV_OK;
int64_t v = (int64_t)src;
if (v == *dst) return TSDB_CONV_TRUNC_FRACTION;
return TSDB_CONV_TRUNC;
}
TSDB_CONV_CODE tsdb_double_to_bigint(double src, int64_t *dst) {
*dst = (int64_t)src;
if (src<LLONG_MIN || src>LLONG_MAX) return TSDB_CONV_OOR;
if (src == *dst) return TSDB_CONV_OK;
int64_t v = (int64_t)src;
if (v == *dst) return TSDB_CONV_TRUNC_FRACTION;
return TSDB_CONV_TRUNC;
}
TSDB_CONV_CODE tsdb_double_to_ts(double src, int64_t *dst) {
TSDB_CONV_CODE code = tsdb_double_to_bigint(src, dst);
if (code==TSDB_CONV_OK || code==TSDB_CONV_TRUNC_FRACTION) {
int64_t v = (int64_t)src;
time_t t = (time_t)(v / 1000);
struct tm tm = {0};
if (localtime_r(&t, &tm)) return TSDB_CONV_OK;
return TSDB_CONV_OOR;
}
return code;
}
TSDB_CONV_CODE tsdb_double_to_char(double src, char *dst, size_t dlen) {
int n = snprintf(dst, dlen, "%lg", src);
DASSERT(n>=0);
if (n<dlen) return TSDB_CONV_OK;
return TSDB_CONV_TRUNC;
}
// src: SQL_TIMESTAMP_STRUCT
TSDB_CONV_CODE tsdb_timestamp_to_char(SQL_TIMESTAMP_STRUCT src, char *dst, size_t dlen) {
int n = snprintf(dst, dlen, "%04d-%02d-%02d %02d:%02d:%02d.%03d",
src.year, src.month, src.day,
src.hour, src.minute, src.second,
src.fraction / 1000000);
DASSERT(n>=0);
if (n<dlen) return TSDB_CONV_OK;
if (strlen(dst)>=19) return TSDB_CONV_TRUNC_FRACTION;
return TSDB_CONV_TRUNC;
}
// src: chars
TSDB_CONV_CODE tsdb_chars_to_bit(const char *src, size_t smax, int8_t *dst) {
if (strcmp(src, "0")==0) {
*dst = 0;
return TSDB_CONV_OK;
}
if (strcmp(src, "1")==0) {
*dst = 1;
return TSDB_CONV_OK;
}
double v;
int bytes;
int n = sscanf(src, "%lg%n", &v, &bytes);
if (n!=1) return TSDB_CONV_CHAR_NOT_NUM;
if (bytes!=strlen(src)) return TSDB_CONV_CHAR_NOT_NUM;
if (v<0 || v>=2) return TSDB_CONV_OOR;
return TSDB_CONV_TRUNC_FRACTION;
}
TSDB_CONV_CODE tsdb_chars_to_tinyint(const char *src, size_t smax, int8_t *dst) {
int64_t v;
TSDB_CONV_CODE code = tsdb_chars_to_bigint(src, smax, &v);
if (code!=TSDB_CONV_OK) return code;
*dst = (int8_t)v;
if (v==*dst) return TSDB_CONV_OK;
return TSDB_CONV_OOR;
}
TSDB_CONV_CODE tsdb_chars_to_smallint(const char *src, size_t smax, int16_t *dst) {
int64_t v;
TSDB_CONV_CODE code = tsdb_chars_to_bigint(src, smax, &v);
if (code!=TSDB_CONV_OK) return code;
*dst = (int16_t)v;
if (v==*dst) return TSDB_CONV_OK;
return TSDB_CONV_OOR;
}
TSDB_CONV_CODE tsdb_chars_to_int(const char *src, size_t smax, int32_t *dst) {
int64_t v;
TSDB_CONV_CODE code = tsdb_chars_to_bigint(src, smax, &v);
if (code!=TSDB_CONV_OK) return code;
*dst = (int32_t)v;
if (v==*dst) return TSDB_CONV_OK;
return TSDB_CONV_OOR;
}
TSDB_CONV_CODE tsdb_chars_to_bigint(const char *src, size_t smax, int64_t *dst) {
int bytes;
int n = sscanf(src, "%" PRId64 "%n", dst, &bytes);
if (n!=1) return TSDB_CONV_CHAR_NOT_NUM;
if (bytes==strlen(src)) {
return TSDB_CONV_OK;
}
double v;
n = sscanf(src, "%lg%n", &v, &bytes);
if (n!=1) return TSDB_CONV_CHAR_NOT_NUM;
if (bytes==strlen(src)) {
return TSDB_CONV_TRUNC_FRACTION;
}
return TSDB_CONV_OK;
}
TSDB_CONV_CODE tsdb_chars_to_ts(const char *src, size_t smax, int64_t *dst) {
int64_t v;
TSDB_CONV_CODE code = tsdb_chars_to_bigint(src, smax, &v);
if (code!=TSDB_CONV_OK) return code;
*dst = v;
if (v==*dst) {
time_t t = (time_t)(v / 1000);
struct tm tm = {0};
if (localtime_r(&t, &tm)) return TSDB_CONV_OK;
}
return TSDB_CONV_OOR;
}
TSDB_CONV_CODE tsdb_chars_to_float(const char *src, size_t smax, float *dst) {
int bytes;
int n = sscanf(src, "%g%n", dst, &bytes);
if (n==1 && bytes==strlen(src)) {
return TSDB_CONV_OK;
}
return TSDB_CONV_CHAR_NOT_NUM;
}
TSDB_CONV_CODE tsdb_chars_to_double(const char *src, size_t smax, double *dst) {
int bytes;
int n = sscanf(src, "%lg%n", dst, &bytes);
if (n==1 && bytes==strlen(src)) {
return TSDB_CONV_OK;
}
return TSDB_CONV_CHAR_NOT_NUM;
}
TSDB_CONV_CODE tsdb_chars_to_timestamp(const char *src, size_t smax, SQL_TIMESTAMP_STRUCT *dst) {
int64_t v = 0;
// why cast to 'char*' ?
int r = taosParseTime((char*)src, &v, (int32_t)smax, TSDB_TIME_PRECISION_MILLI, 0);
if (r) {
return TSDB_CONV_CHAR_NOT_TS;
}
time_t t = v/1000;
struct tm vtm = {0};
localtime_r(&t, &vtm);
dst->year = (SQLSMALLINT)(vtm.tm_year + 1900);
dst->month = (SQLUSMALLINT)(vtm.tm_mon + 1);
dst->day = (SQLUSMALLINT)(vtm.tm_mday);
dst->hour = (SQLUSMALLINT)(vtm.tm_hour);
dst->minute = (SQLUSMALLINT)(vtm.tm_min);
dst->second = (SQLUSMALLINT)(vtm.tm_sec);
dst->fraction = (SQLUINTEGER)(v%1000 * 1000000);
return TSDB_CONV_OK;
}
TSDB_CONV_CODE tsdb_chars_to_timestamp_ts(const char *src, size_t smax, int64_t *dst) {
// why cast to 'char*' ?
int r = taosParseTime((char*)src, dst, (int32_t)smax, TSDB_TIME_PRECISION_MILLI, 0);
if (r) {
return TSDB_CONV_CHAR_NOT_TS;
}
return TSDB_CONV_OK;
}
TSDB_CONV_CODE tsdb_chars_to_char(const char *src, size_t smax, char *dst, size_t dmax) {
int n = snprintf(dst, dmax, "%s", src);
DASSERT(n>=0);
if (n<dmax) return TSDB_CONV_OK;
return TSDB_CONV_TRUNC;
}
char* stack_buffer_alloc(stack_buffer_t *buffer, size_t bytes) {
if (!buffer) return NULL;
// align-by-size_of-size_t-bytes
if (bytes==0) bytes = sizeof(size_t);
bytes = (bytes + sizeof(size_t) - 1) / sizeof(size_t) * sizeof(size_t);
size_t next = buffer->next + bytes;
if (next>sizeof(buffer->buf)) return NULL;
char *p = buffer->buf + buffer->next;
buffer->next = next;
return p;
}
int is_owned_by_stack_buffer(stack_buffer_t *buffer, const char *ptr) {
if (!buffer) return 0;
if (ptr>=buffer->buf && ptr<buffer->buf+buffer->next) return 1;
return 0;
}
struct tsdb_conv_s {
iconv_t cnv;
unsigned int direct:1;
};
static tsdb_conv_t no_conversion = {0};
static pthread_once_t once = PTHREAD_ONCE_INIT;
static void once_init(void) {
no_conversion.cnv = (iconv_t)-1;
no_conversion.direct = 1;
}
tsdb_conv_t* tsdb_conv_direct() { // get a non-conversion-converter
pthread_once(&once, once_init);
return &no_conversion;
}
tsdb_conv_t* tsdb_conv_open(const char *from_enc, const char *to_enc) {
pthread_once(&once, once_init);
tsdb_conv_t *cnv = (tsdb_conv_t*)calloc(1, sizeof(*cnv));
if (!cnv) return NULL;
if (strcmp(from_enc, to_enc)==0 && 0) {
cnv->cnv = (iconv_t)-1;
cnv->direct = 1;
return cnv;
}
cnv->cnv = iconv_open(to_enc, from_enc);
if (cnv->cnv == (iconv_t)-1) {
free(cnv);
return NULL;
}
cnv->direct = 0;
return cnv;
}
void tsdb_conv_close(tsdb_conv_t *cnv) {
if (!cnv) return;
if (cnv == &no_conversion) return;
if (!cnv->direct) {
if (cnv->cnv != (iconv_t)-1) {
iconv_close(cnv->cnv);
}
}
cnv->cnv = (iconv_t)-1;
cnv->direct = 0;
free(cnv);
}
TSDB_CONV_CODE tsdb_conv_write(tsdb_conv_t *cnv, const char *src, size_t *slen, char *dst, size_t *dlen) {
if (!cnv) return TSDB_CONV_NOT_AVAIL;
if (cnv->direct) {
size_t n = (*slen > *dlen) ? *dlen : *slen;
memcpy(dst, src, n);
*slen -= n;
*dlen -= n;
if (*dlen) dst[n] = '\0';
return TSDB_CONV_OK;
}
if (!cnv->cnv) return TSDB_CONV_NOT_AVAIL;
size_t r = iconv(cnv->cnv, (char**)&src, slen, &dst, dlen);
if (r==(size_t)-1) return TSDB_CONV_BAD_CHAR;
if (*slen) return TSDB_CONV_TRUNC;
if (*dlen) *dst = '\0';
return TSDB_CONV_OK;
}
TSDB_CONV_CODE tsdb_conv_write_int64(tsdb_conv_t *cnv, int64_t val, char *dst, size_t *dlen) {
char utf8[64];
int n = snprintf(utf8, sizeof(utf8), "%" PRId64 "", val);
DASSERT(n>=0);
DASSERT(n<sizeof(utf8));
size_t len = (size_t)n;
TSDB_CONV_CODE code = tsdb_conv_write(cnv, utf8, &len, dst, dlen);
*dlen = (size_t)n+1;
return code;
}
TSDB_CONV_CODE tsdb_conv_write_double(tsdb_conv_t *cnv, double val, char *dst, size_t *dlen) {
char utf8[256];
int n = snprintf(utf8, sizeof(utf8), "%g", val);
DASSERT(n>=0);
DASSERT(n<sizeof(utf8));
size_t len = (size_t)n;
TSDB_CONV_CODE code = tsdb_conv_write(cnv, utf8, &len, dst, dlen);
*dlen = (size_t)n+1;
return code;
}
TSDB_CONV_CODE tsdb_conv_write_timestamp(tsdb_conv_t *cnv, SQL_TIMESTAMP_STRUCT val, char *dst, size_t *dlen) {
char utf8[256];
int n = snprintf(utf8, sizeof(utf8), "%04d-%02d-%02d %02d:%02d:%02d.%03d",
val.year, val.month, val.day,
val.hour, val.minute, val.second,
val.fraction / 1000000);
DASSERT(n>=0);
DASSERT(n<sizeof(utf8));
size_t len = (size_t)n;
TSDB_CONV_CODE code = tsdb_conv_write(cnv, utf8, &len, dst, dlen);
*dlen = (size_t)n+1;
return code;
}
TSDB_CONV_CODE tsdb_conv_chars_to_bit(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int8_t *dst) {
const char *utf8 = NULL;
TSDB_CONV_CODE code = tsdb_conv(cnv, buffer, src, slen, &utf8, NULL);
if (code) return code;
code = tsdb_chars_to_bit(utf8, sizeof(utf8), dst);
tsdb_conv_free(cnv, utf8, buffer, src);
return code;
}
TSDB_CONV_CODE tsdb_conv_chars_to_tinyint(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int8_t *dst) {
const char *utf8 = NULL;
TSDB_CONV_CODE code = tsdb_conv(cnv, buffer, src, slen, &utf8, NULL);
if (code) return code;
code = tsdb_chars_to_tinyint(utf8, sizeof(utf8), dst);
tsdb_conv_free(cnv, utf8, buffer, src);
return code;
}
TSDB_CONV_CODE tsdb_conv_chars_to_smallint(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int16_t *dst) {
const char *utf8 = NULL;
TSDB_CONV_CODE code = tsdb_conv(cnv, buffer, src, slen, &utf8, NULL);
if (code) return code;
code = tsdb_chars_to_smallint(utf8, sizeof(utf8), dst);
tsdb_conv_free(cnv, utf8, buffer, src);
return code;
}
TSDB_CONV_CODE tsdb_conv_chars_to_int(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int32_t *dst) {
const char *utf8 = NULL;
TSDB_CONV_CODE code = tsdb_conv(cnv, buffer, src, slen, &utf8, NULL);
if (code) return code;
code = tsdb_chars_to_int(utf8, sizeof(utf8), dst);
tsdb_conv_free(cnv, utf8, buffer, src);
return code;
}
TSDB_CONV_CODE tsdb_conv_chars_to_bigint(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int64_t *dst) {
const char *utf8 = NULL;
TSDB_CONV_CODE code = tsdb_conv(cnv, buffer, src, slen, &utf8, NULL);
if (code) return code;
code = tsdb_chars_to_bigint(utf8, sizeof(utf8), dst);
tsdb_conv_free(cnv, utf8, buffer, src);
return code;
}
TSDB_CONV_CODE tsdb_conv_chars_to_ts(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int64_t *dst) {
const char *utf8 = NULL;
TSDB_CONV_CODE code = tsdb_conv(cnv, buffer, src, slen, &utf8, NULL);
if (code) return code;
code = tsdb_chars_to_ts(utf8, sizeof(utf8), dst);
tsdb_conv_free(cnv, utf8, buffer, src);
return code;
}
TSDB_CONV_CODE tsdb_conv_chars_to_float(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, float *dst) {
const char *utf8 = NULL;
TSDB_CONV_CODE code = tsdb_conv(cnv, buffer, src, slen, &utf8, NULL);
if (code) return code;
code = tsdb_chars_to_float(utf8, sizeof(utf8), dst);
tsdb_conv_free(cnv, utf8, buffer, src);
return code;
}
TSDB_CONV_CODE tsdb_conv_chars_to_double(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, double *dst) {
const char *utf8 = NULL;
TSDB_CONV_CODE code = tsdb_conv(cnv, buffer, src, slen, &utf8, NULL);
if (code) return code;
code = tsdb_chars_to_double(utf8, sizeof(utf8), dst);
tsdb_conv_free(cnv, utf8, buffer, src);
return code;
}
TSDB_CONV_CODE tsdb_conv_chars_to_timestamp(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, SQL_TIMESTAMP_STRUCT *dst) {
const char *utf8 = NULL;
TSDB_CONV_CODE code = tsdb_conv(cnv, buffer, src, slen, &utf8, NULL);
if (code) return code;
code = tsdb_chars_to_timestamp(utf8, sizeof(utf8), dst);
tsdb_conv_free(cnv, utf8, buffer, src);
return code;
}
TSDB_CONV_CODE tsdb_conv_chars_to_timestamp_ts(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int64_t *dst) {
const char *utf8 = NULL;
TSDB_CONV_CODE code = tsdb_conv(cnv, buffer, src, slen, &utf8, NULL);
if (code) return code;
code = tsdb_chars_to_timestamp_ts(utf8, sizeof(utf8), dst);
tsdb_conv_free(cnv, utf8, buffer, src);
return code;
}
TSDB_CONV_CODE tsdb_conv(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, const char **dst, size_t *dlen) {
if (!cnv) return TSDB_CONV_NOT_AVAIL;
char *buf;
size_t blen;
if (cnv->direct) {
if (src[slen]=='\0') { // access violation?
*dst = src;
if (dlen) *dlen = slen;
return TSDB_CONV_OK;
}
blen = slen + 1;
} else {
blen = (slen + 1) * 4;
}
buf = stack_buffer_alloc(buffer, blen);
if (!buf) {
buf = (char*)malloc(blen);
if (!buf) return TSDB_CONV_OOM;
}
if (cnv->direct) {
size_t n = slen;
DASSERT(blen > n);
memcpy(buf, src, n);
buf[n] = '\0';
*dst = buf;
if (dlen) *dlen = n;
return TSDB_CONV_OK;
}
const char *orig_s = src;
char *orig_d = buf;
size_t orig_blen = blen;
TSDB_CONV_CODE code;
size_t r = iconv(cnv->cnv, (char**)&src, &slen, &buf, &blen);
do {
if (r==(size_t)-1) {
switch(errno) {
case E2BIG: {
code = TSDB_CONV_SRC_TOO_LARGE;
} break;
case EILSEQ: {
code = TSDB_CONV_SRC_BAD_SEQ;
} break;
case EINVAL: {
code = TSDB_CONV_SRC_INCOMPLETE;
} break;
default: {
code = TSDB_CONV_SRC_GENERAL;
} break;
}
break;
}
if (slen) {
code = TSDB_CONV_TRUNC;
break;
}
DASSERT(blen);
*buf = '\0';
*dst = orig_d;
if (dlen) *dlen = orig_blen - blen;
return TSDB_CONV_OK;
} while (0);
if (orig_d!=(char*)orig_s && !is_owned_by_stack_buffer(buffer, orig_d)) free(orig_d);
return code;
}
void tsdb_conv_free(tsdb_conv_t *cnv, const char *ptr, stack_buffer_t *buffer, const char *src) {
if (ptr!=src && !is_owned_by_stack_buffer(buffer, ptr)) free((char*)ptr);
}

View File

@ -1,109 +0,0 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _todbc_conv_h_
#define _todbc_conv_h_
#include "os.h"
#include <iconv.h>
#include <sql.h>
typedef enum {
TSDB_CONV_OK = 0,
TSDB_CONV_NOT_AVAIL,
TSDB_CONV_OOM,
TSDB_CONV_OOR,
TSDB_CONV_TRUNC_FRACTION,
TSDB_CONV_TRUNC,
TSDB_CONV_CHAR_NOT_NUM,
TSDB_CONV_CHAR_NOT_TS,
TSDB_CONV_NOT_VALID_TS,
TSDB_CONV_GENERAL,
TSDB_CONV_BAD_CHAR,
TSDB_CONV_SRC_TOO_LARGE,
TSDB_CONV_SRC_BAD_SEQ,
TSDB_CONV_SRC_INCOMPLETE,
TSDB_CONV_SRC_GENERAL,
} TSDB_CONV_CODE;
const char* tsdb_conv_code_str(TSDB_CONV_CODE code);
typedef struct stack_buffer_s stack_buffer_t;
struct stack_buffer_s {
char buf[1024*16];
size_t next;
};
char* stack_buffer_alloc(stack_buffer_t *buffer, size_t bytes);
int is_owned_by_stack_buffer(stack_buffer_t *buffer, const char *ptr);
typedef struct tsdb_conv_s tsdb_conv_t;
tsdb_conv_t* tsdb_conv_direct(); // get a non-conversion-converter
tsdb_conv_t* tsdb_conv_open(const char *from_enc, const char *to_enc);
void tsdb_conv_close(tsdb_conv_t *cnv);
TSDB_CONV_CODE tsdb_conv_write(tsdb_conv_t *cnv, const char *src, size_t *slen, char *dst, size_t *dlen);
TSDB_CONV_CODE tsdb_conv_write_int64(tsdb_conv_t *cnv, int64_t val, char *dst, size_t *dlen);
TSDB_CONV_CODE tsdb_conv_write_double(tsdb_conv_t *cnv, double val, char *dst, size_t *dlen);
TSDB_CONV_CODE tsdb_conv_write_timestamp(tsdb_conv_t *cnv, SQL_TIMESTAMP_STRUCT val, char *dst, size_t *dlen);
TSDB_CONV_CODE tsdb_conv_chars_to_bit(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int8_t *dst);
TSDB_CONV_CODE tsdb_conv_chars_to_tinyint(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int8_t *dst);
TSDB_CONV_CODE tsdb_conv_chars_to_smallint(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int16_t *dst);
TSDB_CONV_CODE tsdb_conv_chars_to_int(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int32_t *dst);
TSDB_CONV_CODE tsdb_conv_chars_to_bigint(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int64_t *dst);
TSDB_CONV_CODE tsdb_conv_chars_to_ts(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int64_t *dst);
TSDB_CONV_CODE tsdb_conv_chars_to_float(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, float *dst);
TSDB_CONV_CODE tsdb_conv_chars_to_double(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, double *dst);
TSDB_CONV_CODE tsdb_conv_chars_to_timestamp(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, SQL_TIMESTAMP_STRUCT *dst);
TSDB_CONV_CODE tsdb_conv_chars_to_timestamp_ts(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, int64_t *dst);
TSDB_CONV_CODE tsdb_conv(tsdb_conv_t *cnv, stack_buffer_t *buffer, const char *src, size_t slen, const char **dst, size_t *dlen);
void tsdb_conv_free(tsdb_conv_t *cnv, const char *ptr, stack_buffer_t *buffer, const char *src);
TSDB_CONV_CODE tsdb_int64_to_bit(int64_t src, int8_t *dst);
TSDB_CONV_CODE tsdb_int64_to_tinyint(int64_t src, int8_t *dst);
TSDB_CONV_CODE tsdb_int64_to_smallint(int64_t src, int16_t *dst);
TSDB_CONV_CODE tsdb_int64_to_int(int64_t src, int32_t *dst);
TSDB_CONV_CODE tsdb_int64_to_bigint(int64_t src, int64_t *dst);
TSDB_CONV_CODE tsdb_int64_to_ts(int64_t src, int64_t *dst);
TSDB_CONV_CODE tsdb_int64_to_float(int64_t src, float *dst);
TSDB_CONV_CODE tsdb_int64_to_double(int64_t src, double *dst);
TSDB_CONV_CODE tsdb_int64_to_char(int64_t src, char *dst, size_t dlen);
TSDB_CONV_CODE tsdb_double_to_bit(double src, int8_t *dst);
TSDB_CONV_CODE tsdb_double_to_tinyint(double src, int8_t *dst);
TSDB_CONV_CODE tsdb_double_to_smallint(double src, int16_t *dst);
TSDB_CONV_CODE tsdb_double_to_int(double src, int32_t *dst);
TSDB_CONV_CODE tsdb_double_to_bigint(double src, int64_t *dst);
TSDB_CONV_CODE tsdb_double_to_ts(double src, int64_t *dst);
TSDB_CONV_CODE tsdb_double_to_char(double src, char *dst, size_t dlen);
TSDB_CONV_CODE tsdb_timestamp_to_char(SQL_TIMESTAMP_STRUCT src, char *dst, size_t dlen);
TSDB_CONV_CODE tsdb_chars_to_bit(const char *src, size_t smax, int8_t *dst);
TSDB_CONV_CODE tsdb_chars_to_tinyint(const char *src, size_t smax, int8_t *dst);
TSDB_CONV_CODE tsdb_chars_to_smallint(const char *src, size_t smax, int16_t *dst);
TSDB_CONV_CODE tsdb_chars_to_int(const char *src, size_t smax, int32_t *dst);
TSDB_CONV_CODE tsdb_chars_to_bigint(const char *src, size_t smax, int64_t *dst);
TSDB_CONV_CODE tsdb_chars_to_ts(const char *src, size_t smax, int64_t *dst);
TSDB_CONV_CODE tsdb_chars_to_float(const char *src, size_t smax, float *dst);
TSDB_CONV_CODE tsdb_chars_to_double(const char *src, size_t smax, double *dst);
TSDB_CONV_CODE tsdb_chars_to_timestamp(const char *src, size_t smax, SQL_TIMESTAMP_STRUCT *dst);
TSDB_CONV_CODE tsdb_chars_to_char(const char *src, size_t smax, char *dst, size_t dmax);
#endif // _todbc_conv_h_

View File

@ -16,16 +16,40 @@
#ifndef _TODBC_FLEX_H_
#define _TODBC_FLEX_H_
#include <sql.h>
#include <sqlext.h>
// TSDB predefined field types
// TINYINT SMALLINT INT BIGINT FLOAT DOUBLE BOOL TIMESTAMP BINARY NCHAR
typedef struct map_tsdb_type_s map_tsdb_type_t;
struct map_tsdb_type_s {
SQLSMALLINT tsdb_tinyint;
SQLSMALLINT tsdb_smallint;
SQLSMALLINT tsdb_int;
SQLSMALLINT tsdb_bigint;
SQLULEN tsdb_bigint_size;
SQLSMALLINT tsdb_float;
SQLSMALLINT tsdb_double;
SQLSMALLINT tsdb_bool;
SQLSMALLINT tsdb_timestamp;
SQLSMALLINT tsdb_binary;
SQLSMALLINT tsdb_nchar;
};
typedef struct conn_val_s conn_val_t;
struct conn_val_s {
char *key;
char *dsn;
char *uid;
char *pwd;
char *db;
char *server;
char *svr_enc;
char *cli_enc;
char *dsn;
char *uid;
char *pwd;
char *db;
char *server;
char *enc_local;
char *enc_char;
char *enc_wchar;
char *enc_db;
map_tsdb_type_t tsdb_map;
};

View File

@ -0,0 +1,195 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "todbc_hash.h"
#include "todbc_list.h"
#include "todbc_log.h"
#include <stdlib.h>
typedef struct todbc_hash_slot_s todbc_hash_slot_t;
struct todbc_hash_s {
todbc_hash_conf_t conf;
todbc_hash_slot_t *slots;
size_t n_slots;
};
struct todbc_hash_slot_s {
todbc_list_t *list;
};
todbc_hash_t* todbc_hash_create(todbc_hash_conf_t conf) {
if (!conf.key_hash) return NULL;
if (!conf.key_comp) return NULL;
if (!conf.val_free) return NULL;
todbc_hash_t *hash = (todbc_hash_t*)calloc(1, sizeof(*hash));
if (!hash) return NULL;
hash->conf = conf;
if (hash->conf.slots==0) {
hash->conf.slots = 33;
}
hash->slots = (todbc_hash_slot_t*)calloc(hash->conf.slots, sizeof(*hash->slots));
do {
if (!hash->slots) break;
hash->n_slots = hash->conf.slots;
return hash;
} while (0);
todbc_hash_free(hash);
return NULL;
}
void todbc_hash_free(todbc_hash_t *hash) {
if (!hash) return;
for (int i=0; i<hash->n_slots; ++i) {
todbc_hash_slot_t *slot = hash->slots + i;
if (!slot->list) continue;
todbc_list_free(slot->list);
slot->list = NULL;
}
free(hash->slots);
hash->n_slots = 0;
}
typedef struct kv_s kv_t;
struct kv_s {
todbc_hash_t *hash;
void *key;
void *val;
};
static void do_val_free(todbc_list_t *list, void *val, void *arg) {
todbc_hash_t *hash = (todbc_hash_t*)arg;
DASSERT(list);
DASSERT(hash);
DASSERT(hash->conf.val_free);
hash->conf.val_free(hash, val, hash->conf.arg);
}
int todbc_hash_put(todbc_hash_t *hash, void *key, void *val) {
if (!hash) return -1;
if (!hash->slots) return -1;
if (!key) return -1;
if (!val) return -1;
void *old = NULL;
int found = 0;
int r = todbc_hash_get(hash, key, &old, &found);
if (r) return r;
if (found) return -1;
unsigned long hash_val = hash->conf.key_hash(hash, key);
todbc_hash_slot_t *slot = hash->slots + (hash_val % hash->n_slots);
if (!slot->list) {
todbc_list_conf_t conf = {0};
conf.arg = hash;
conf.val_free = do_val_free;
slot->list = todbc_list_create(conf);
if (!slot->list) return -1;
}
r = todbc_list_pushback(slot->list, val);
return r;
}
static int do_comp(todbc_list_t *list, void *old, void *val, void *arg) {
kv_t *kv = (kv_t*)arg;
DASSERT(kv);
DASSERT(kv->hash);
DASSERT(kv->key);
DASSERT(kv->hash->conf.key_comp);
DASSERT(kv->key == val);
int r = kv->hash->conf.key_comp(kv->hash, kv->key, old);
if (r==0) {
kv->val = old;
}
return r;
}
int todbc_hash_get(todbc_hash_t *hash, void *key, void **val, int *found) {
if (!hash) return -1;
if (!hash->slots) return -1;
if (!key) return -1;
if (!val) return -1;
if (!found) return -1;
*found = 0;
unsigned long hash_val = hash->conf.key_hash(hash, key);
todbc_hash_slot_t *slot = hash->slots + (hash_val % hash->n_slots);
if (slot->list) {
kv_t kv = {0};
kv.hash = hash;
kv.key = key;
kv.val = NULL;
int r = todbc_list_find(slot->list, key, found, do_comp, &kv);
if (*found) {
DASSERT(r==0);
DASSERT(kv.val);
*val = kv.val;
}
return r;
}
return 0;
}
int todbc_hash_del(todbc_hash_t *hash, void *key) {
return -1;
}
typedef struct arg_s arg_t;
struct arg_s {
todbc_hash_t *hash;
void *arg;
int (*iterate)(todbc_hash_t *hash, void *val, void *arg);
};
static int do_iterate(todbc_list_t *list, void *val, void *arg);
int todbc_hash_traverse(todbc_hash_t *hash, int (*iterate)(todbc_hash_t *hash, void *val, void *arg), void *arg) {
if (!hash) return -1;
if (!iterate) return -1;
for (int i=0; i<hash->n_slots; ++i) {
todbc_hash_slot_t *slot = hash->slots + i;
if (!slot->list) continue;
arg_t targ = {0};
targ.hash = hash;
targ.arg = arg;
targ.iterate = iterate;
int r = todbc_list_traverse(slot->list, do_iterate, &targ);
if (r) return r;
}
return 0;
}
static int do_iterate(todbc_list_t *list, void *val, void *arg) {
arg_t *targ = (arg_t*)arg;
DASSERT(targ);
DASSERT(targ->iterate);
DASSERT(targ->hash);
return targ->iterate(targ->hash, val, targ->arg);
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _todbc_hash_h_
#define _todbc_hash_h_
#include <stdint.h>
#include <stddef.h>
// non-thread-safe
typedef struct todbc_hash_conf_s todbc_hash_conf_t;
typedef struct todbc_hash_s todbc_hash_t;
typedef void (*hash_val_free_f)(todbc_hash_t *hash, void *val, void *arg);
typedef unsigned long (*hash_key_hash_f)(todbc_hash_t *hash, void *key);
typedef int (*hash_key_comp_f)(todbc_hash_t *hash, void *key, void *val);
struct todbc_hash_conf_s {
void *arg;
hash_val_free_f val_free;
hash_key_hash_f key_hash;
hash_key_comp_f key_comp;
size_t slots;
};
todbc_hash_t* todbc_hash_create(todbc_hash_conf_t conf);
void todbc_hash_free(todbc_hash_t *hash);
// fail if key exists
int todbc_hash_put(todbc_hash_t *hash, void *key, void *val);
int todbc_hash_get(todbc_hash_t *hash, void *key, void **val, int *found);
int todbc_hash_del(todbc_hash_t *hash, void *key);
typedef int (*hash_iterate_f)(todbc_hash_t *hash, void *val, void *arg);
int todbc_hash_traverse(todbc_hash_t *hash, hash_iterate_f iterate, void *arg);
#endif // _todbc_hash_h_

View File

@ -0,0 +1,682 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "todbc_iconv.h"
#include "todbc_hash.h"
#include "todbc_log.h"
#include "todbc_string.h"
#include "todbc_tls.h"
#define invalid_iconv() ((iconv_t)-1)
struct todbc_iconvset_s {
todbc_hash_t *iconv_hash;
todbc_hash_t *enc_hash;
};
typedef struct todbc_iconv_key_s todbc_iconv_key_t;
struct todbc_iconv_key_s {
const char *enc_to;
const char *enc_from;
};
struct todbc_iconv_s {
char enc_to[64];
char enc_from[64];
todbc_iconv_key_t key;
iconv_t cnv;
todbc_enc_t from;
todbc_enc_t to;
unsigned int direct:1;
};
static void todbc_iconv_free(todbc_iconv_t *val);
todbc_iconvset_t* todbc_iconvset_create(void) {
todbc_iconvset_t *cnvset = (todbc_iconvset_t*)calloc(1, sizeof(*cnvset));
if (!cnvset) return NULL;
return cnvset;
}
void todbc_iconvset_free(todbc_iconvset_t *cnvset) {
if (!cnvset) return;
if (cnvset->iconv_hash) {
todbc_hash_free(cnvset->iconv_hash);
cnvset->iconv_hash = NULL;
}
if (cnvset->enc_hash) {
todbc_hash_free(cnvset->enc_hash);
cnvset->enc_hash = NULL;
}
free(cnvset);
}
static void do_iconv_hash_val_free(todbc_hash_t *hash, void *val, void *arg);
static unsigned long do_iconv_hash_key_hash(todbc_hash_t *hash, void *key);
static int do_iconv_hash_key_comp(todbc_hash_t *hash, void *key, void *val);
static void do_enc_hash_val_free(todbc_hash_t *hash, void *val, void *arg);
static unsigned long do_enc_hash_key_hash(todbc_hash_t *hash, void *key);
static int do_enc_hash_key_comp(todbc_hash_t *hash, void *key, void *val);
#define CHK(x, y, n) if (strcasecmp(x, y)==0) return n
#define SET_SIZES(cs, enc, a,b) do { \
if (strcasecmp(enc->enc, cs)==0) { \
enc->char_size = a; \
enc->variable_char_size = b; \
return; \
} \
} while (0)
static void do_set_sizes(todbc_enc_t *enc) {
if (!enc) return;
SET_SIZES("ISO-10646-UCS-2", enc, 2, -1);
SET_SIZES("UCS-2", enc, 2, -1);
SET_SIZES("CSUNICODE", enc, 2, -1);
SET_SIZES("UCS-2BE", enc, 2, -1);
SET_SIZES("UNICODE-1-1", enc, 2, -1);
SET_SIZES("UNICODEBIG", enc, 2, -1);
SET_SIZES("CSUNICODE11", enc, 2, -1);
SET_SIZES("UCS-2LE", enc, 2, -1);
SET_SIZES("UNICODELITTLE", enc, 2, -1);
SET_SIZES("UTF-16", enc, 2, -1);
SET_SIZES("UTF-16BE", enc, 2, -1);
SET_SIZES("UTF-16LE", enc, 2, -1);
SET_SIZES("UCS-2-INTERNAL", enc, 2, -1);
SET_SIZES("UCS-2-SWAPPED", enc, 2, -1);
SET_SIZES("ISO-10646-UCS-4", enc, 4, -1);
SET_SIZES("UCS-4", enc, 4, -1);
SET_SIZES("CSUCS4", enc, 4, -1);
SET_SIZES("UCS-4BE", enc, 4, -1);
SET_SIZES("UCS-4LE", enc, 4, -1);
SET_SIZES("UTF-32", enc, 4, -1);
SET_SIZES("UTF-32BE", enc, 4, -1);
SET_SIZES("UTF-32LE", enc, 4, -1);
SET_SIZES("UCS-4-INTERNAL", enc, 4, -1);
SET_SIZES("UCS-4-SWAPPED", enc, 4, -1);
SET_SIZES("UTF-8", enc, -1, 3);
SET_SIZES("UTF8", enc, -1, 3);
SET_SIZES("UTF-8-MAC", enc, -1, 3);
SET_SIZES("UTF8-MAC", enc, -1, 3);
SET_SIZES("CN-GB", enc, 2, -1);
SET_SIZES("EUC-CN", enc, 2, -1);
SET_SIZES("EUCCN", enc, 2, -1);
SET_SIZES("GB2312", enc, 2, -1);
SET_SIZES("CSGB2312", enc, 2, -1);
SET_SIZES("GBK", enc, 2, -1);
SET_SIZES("CP936", enc, 2, -1);
SET_SIZES("MS936", enc, 2, -1);
SET_SIZES("WINDOWS-936", enc, 2, -1);
SET_SIZES("GB18030", enc, 2, -1);
// add more setup here after
enc->char_size = -1;
enc->variable_char_size = -1;
}
static int do_get_null_size(const char *enc, int *null_size);
// static int do_get_unicode_char_size(const char *enc) {
// if (!enc) return -1;
//
// CHK("ISO-10646-UCS-2", enc, 2);
// CHK("UCS-2", enc, 2);
// CHK("CSUNICODE", enc, 2);
// CHK("UCS-2BE", enc, 2);
// CHK("UNICODE-1-1", enc, 2);
// CHK("UNICODEBIG", enc, 2);
// CHK("CSUNICODE11", enc, 2);
// CHK("UCS-2LE", enc, 2);
// CHK("UNICODELITTLE", enc, 2);
// CHK("UTF-16", enc, 2);
// CHK("UTF-16BE", enc, 2);
// CHK("UTF-16LE", enc, 2);
// CHK("UCS-2-INTERNAL", enc, 2);
// CHK("UCS-2-SWAPPED", enc, 2);
// CHK("ISO-10646-UCS-4", enc, 4);
// CHK("UCS-4", enc, 4);
// CHK("CSUCS4", enc, 4);
// CHK("UCS-4BE", enc, 4);
// CHK("UCS-4LE", enc, 4);
// CHK("UTF-32", enc, 4);
// CHK("UTF-32BE", enc, 4);
// CHK("UTF-32LE", enc, 4);
// CHK("UCS-4-INTERNAL", enc, 4);
// CHK("UCS-4-SWAPPED", enc, 4);
//
// return -1;
// }
todbc_enc_t todbc_iconvset_enc(todbc_iconvset_t *cnvset, const char *enc) {
do {
if (!cnvset) break;
if (!enc) break;
if (!cnvset->enc_hash) {
todbc_hash_conf_t conf = {0};
conf.arg = cnvset;
conf.val_free = do_enc_hash_val_free;
conf.key_hash = do_enc_hash_key_hash;
conf.key_comp = do_enc_hash_key_comp;
conf.slots = 7;
cnvset->enc_hash = todbc_hash_create(conf);
if (!cnvset->enc_hash) break;
}
void *old = NULL;
int found = 0;
int r = todbc_hash_get(cnvset->enc_hash, (void*)enc, &old, &found);
if (r) {
DASSERT(found==0);
DASSERT(old==NULL);
break;
}
if (found) {
DASSERT(old);
todbc_enc_t *val = (todbc_enc_t*)old;
return *val;
}
todbc_enc_t *val = (todbc_enc_t*)calloc(1, sizeof(*val));
if (!val) break;
do {
if (snprintf(val->enc, sizeof(val->enc), "%s", enc)>=sizeof(val->enc)) {
break;
}
do_set_sizes(val);
if (do_get_null_size(val->enc, &val->null_size)) break;
return *val;
} while (0);
free(val);
} while (0);
todbc_enc_t v = {0};
v.char_size = -1;
v.null_size = -1;
return v;
}
todbc_iconv_t* todbc_iconvset_get(todbc_iconvset_t *cnvset, const char *enc_to, const char *enc_from) {
if (!cnvset) return NULL;
if (!enc_to) return NULL;
if (!enc_from) return NULL;
todbc_iconv_key_t key;
key.enc_to = enc_to;
key.enc_from = enc_from;
if (!cnvset->iconv_hash) {
todbc_hash_conf_t conf = {0};
conf.arg = cnvset;
conf.val_free = do_iconv_hash_val_free;
conf.key_hash = do_iconv_hash_key_hash;
conf.key_comp = do_iconv_hash_key_comp;
conf.slots = 7;
cnvset->iconv_hash = todbc_hash_create(conf);
if (!cnvset->iconv_hash) return NULL;
}
void *old = NULL;
int found = 0;
int r = todbc_hash_get(cnvset->iconv_hash, &key, &old, &found);
if (r) {
DASSERT(found==0);
DASSERT(old==NULL);
return NULL;
}
if (found) {
DASSERT(old);
todbc_iconv_t *val = (todbc_iconv_t*)old;
// D("found [%p] for [%s->%s]", val, enc_from, enc_to);
return val;
}
todbc_iconv_t *val = (todbc_iconv_t*)calloc(1, sizeof(*val));
if (!val) return NULL;
do {
if (snprintf(val->enc_to, sizeof(val->enc_to), "%s", enc_to)>=sizeof(val->enc_to)) {
break;
}
if (snprintf(val->enc_from, sizeof(val->enc_from), "%s", enc_from)>=sizeof(val->enc_from)) {
break;
}
val->key.enc_to = val->enc_to;
val->key.enc_from = val->enc_from;
if (strcasecmp(enc_to, enc_from)==0) {
val->direct = 1;
}
val->from = todbc_tls_iconv_enc(enc_from);
val->to = todbc_tls_iconv_enc(enc_to);
val->cnv = iconv_open(enc_to, enc_from);
if (val->cnv==invalid_iconv()) break;
r = todbc_hash_put(cnvset->iconv_hash, &key, val);
if (r) break;
// D("created [%p] for [%s->%s]", val, enc_from, enc_to);
return val;
} while (0);
todbc_iconv_free(val);
return NULL;
}
iconv_t todbc_iconv_get(todbc_iconv_t *cnv) {
if (!cnv) return invalid_iconv();
return cnv->cnv;
}
// static int todbc_legal_chars_by_cnv(iconv_t cnv, const unsigned char *str, todbc_bytes_t *bc);
int todbc_iconv_get_legal_chars(todbc_iconv_t *cnv, const unsigned char *str, todbc_bytes_t *bc) {
DASSERT(0);
// if (!cnv) return -1;
// if (bc->inbytes==0 || bc->inbytes > INT64_MAX) {
// DASSERT(bc->chars<=INT64_MAX);
// if (bc->chars > 0) {
// DASSERT(cnv->from_char_size==2 || cnv->from_char_size==4);
// bc->inbytes = ((size_t)cnv->from_char_size) * bc->chars;
// bc->chars = 0;
// }
// } else {
// DASSERT(bc->chars==0);
// }
// return todbc_legal_chars_by_cnv(todbc_iconv_get(cnv), str, bc);
}
// static int todbc_legal_chars_by_block(iconv_t cnv, const unsigned char *str, todbc_bytes_t *bc);
// static int todbc_legal_chars_by_cnv(iconv_t cnv, const unsigned char *str, todbc_bytes_t *bc) {
// size_t max_bytes = bc->inbytes;
// if (max_bytes > INT64_MAX) max_bytes = (size_t)-1;
//
// if (max_bytes != (size_t)-1) {
// return todbc_legal_chars_by_block(cnv, str, bc);
// }
//
// memset(bc, 0, sizeof(*bc));
//
// size_t nbytes = 0;
// size_t ch_bytes = 0;
//
// char buf[16];
// char *inbuf = (char*)str;
// char *outbuf;
// size_t outbytes;
// size_t inbytes;
//
// size_t n = 0;
//
// int r = 0;
// inbytes = 1;
// while (1) {
// if (nbytes==max_bytes) break;
// outbytes = sizeof(buf);
// outbuf = buf;
//
// ch_bytes = inbytes;
// n = iconv(cnv, &inbuf, &inbytes, &outbuf, &outbytes);
// nbytes += 1;
// if (n==(size_t)-1) {
// int err = errno;
// if (err!=EINVAL) {
// E(".......");
// r = -1;
// break;
// }
// inbytes = ch_bytes + 1;
// continue;
// }
// DASSERT(inbytes==0);
// n = sizeof(buf) - outbytes;
//
// DASSERT(n==2);
// if (buf[0]=='\0' && buf[1]=='\0') {
// ch_bytes = 0;
// break;
// }
//
// bc->inbytes += ch_bytes;
// bc->chars += 1;
// bc->outbytes += 2;
//
// ch_bytes = 0;
// inbytes = 1;
// }
//
// outbytes = sizeof(buf);
// outbuf = buf;
// n = iconv(cnv, NULL, NULL, &outbuf, &outbytes);
//
// if (r) return -1;
// if (n==(size_t)-1) return -1;
// if (outbytes!=sizeof(buf)) return -1;
// if (outbuf!=buf) return -1;
// if (ch_bytes) return -1;
// return 0;
// }
unsigned char* todbc_iconv_conv(todbc_iconv_t *cnv, todbc_buf_t *buf, const unsigned char *src, todbc_bytes_t *bc) {
if (!buf) {
buf = todbc_tls_buf();
if (!buf) return NULL;
}
if (bc==NULL) {
todbc_bytes_t x = {0};
x.inbytes = (size_t)-1;
return todbc_iconv_conv(cnv, buf, src, &x);
}
DASSERT(buf);
DASSERT(cnv);
DASSERT(src);
todbc_string_t s = todbc_string_init(cnv->from.enc, src, bc->inbytes);
// D("total_bytes/bytes: %d/%zu/%zu", s.total_bytes, s.bytes);
DASSERT(s.buf==src);
todbc_string_t t = todbc_string_conv_to(&s, cnv->to.enc, NULL);
DASSERT(t.buf);
// bc->outbytes not counting size of null-terminator
bc->outbytes = t.bytes;
return (unsigned char*)t.buf;
}
size_t todbc_iconv_est_bytes(todbc_iconv_t *cnv, size_t inbytes) {
DASSERT(cnv);
DASSERT(inbytes<=INT64_MAX);
const todbc_enc_t *from = &cnv->from;
const todbc_enc_t *to = &cnv->to;
size_t outbytes = inbytes;
do {
if (from == to) break;
size_t inchars = inbytes;
if (from->char_size > 1) {
inchars = (inbytes + (size_t)from->char_size - 1) / (size_t)from->char_size;
}
outbytes = inchars;
size_t char_size = MAX_CHARACTER_SIZE;
if (to->char_size > 0) {
char_size = (size_t)to->char_size;
} else if (to->variable_char_size > 0) {
char_size = (size_t)to->variable_char_size;
}
outbytes *= char_size;
} while (0);
size_t nullbytes = MAX_CHARACTER_SIZE;
if (to->null_size > 0) {
nullbytes = (size_t)to->null_size;
}
// D("%s->%s: %zu->%zu", from->enc, to->enc, inbytes, outbytes);
outbytes += nullbytes;
// D("%s->%s: %zu->%zu", from->enc, to->enc, inbytes, outbytes);
return outbytes;
}
size_t todbc_iconv_bytes(todbc_iconv_t *cnv, size_t inchars) {
DASSERT(cnv);
if (inchars >= INT64_MAX) return (size_t)-1;
const todbc_enc_t *from = &cnv->from;
if (from->char_size > 0) {
return inchars * (size_t)from->char_size;
}
return (size_t)-1;
}
todbc_enc_t todbc_iconv_from(todbc_iconv_t *cnv) {
DASSERT(cnv);
return cnv->from;
}
todbc_enc_t todbc_iconv_to(todbc_iconv_t *cnv) {
DASSERT(cnv);
return cnv->to;
}
int todbc_iconv_raw(todbc_iconv_t *cnv, const unsigned char *src, size_t *slen, unsigned char *dst, size_t *dlen) {
DASSERT(cnv);
DASSERT(src && dst);
DASSERT(slen && *slen < INT64_MAX);
DASSERT(dlen && *dlen < INT64_MAX);
const int null_bytes = todbc_iconv_to(cnv).null_size;
if (*dlen<=null_bytes) {
D("target buffer too small to hold even null-terminator");
*dlen = 0; // while slen does not change
return -1;
}
char *inbuf = (char*)src;
size_t inbytes = *slen;
char *outbuf = (char*)dst;
size_t outbytes = *dlen;
size_t n = iconv(cnv->cnv, &inbuf, &inbytes, &outbuf, &outbytes);
int e = 0;
if (n==(size_t)-1) {
e = errno;
D("iconv failed: [%s->%s]:[%d]%s", cnv->from.enc, cnv->to.enc, e, strerror(e));
} else {
DASSERT(n==0);
}
const size_t inremain = inbytes;
const size_t outremain = outbytes;
*slen = inremain;
*dlen = outremain;
// writing null-terminator to make dest a real string
DASSERT(outbytes <= INT64_MAX);
if (outbytes < null_bytes) {
D("target buffer too small to hold null-terminator");
return -1;
} else {
for (int i=0; i<null_bytes; ++i) {
outbuf[i] = '\0';
}
}
iconv(cnv->cnv, NULL, NULL, NULL, NULL);
return 0;
}
todbc_string_t todbc_iconv_conv2(todbc_iconv_t *cnv, todbc_buf_t *buf, const unsigned char *src, size_t *slen) {
const todbc_string_t nul = {0};
if (!buf) {
buf = todbc_tls_buf();
if (!buf) return nul;
}
DASSERT(cnv);
DASSERT(src);
size_t inbytes = (size_t)-1;
if (slen && *slen <= INT64_MAX) inbytes = *slen;
todbc_string_t in = todbc_string_init(todbc_iconv_from(cnv).enc, src, inbytes);
if (in.buf!=src) return nul;
if (in.bytes > INT64_MAX) return nul;
inbytes = in.bytes;
size_t outblock = todbc_iconv_est_bytes(cnv, in.bytes);
if (outblock > INT64_MAX) return nul;
unsigned char *out = todbc_buf_alloc(buf, outblock);
if (!out) return nul;
const unsigned char *inbuf = src;
unsigned char *outbuf = out;
size_t outbytes = outblock;
int r = todbc_iconv_raw(cnv, inbuf, &inbytes, outbuf, &outbytes);
if (slen) *slen = inbytes;
if (r) return nul;
todbc_string_t s = {0};
s.buf = outbuf;
s.bytes = outblock - outbytes;
s.total_bytes = s.bytes;
return s;
}
// static int todbc_legal_chars_by_block(iconv_t cnv, const unsigned char *str, todbc_bytes_t *bc) {
// int r = 0;
// size_t max_bytes = bc->inbytes;
// char buf[1024*16];
// memset(bc, 0, sizeof(*bc));
// char *inbuf = (char*)str;
// while (bc->inbytes<max_bytes) {
// size_t inbytes = max_bytes - bc->inbytes;
// size_t outbytes = sizeof(buf);
// char *outbuf = buf;
// size_t n = iconv(cnv, &inbuf, &inbytes, &outbuf, &outbytes);
// int err = 0;
// if (n==(size_t)-1) {
// err = errno;
// if (err!=E2BIG && err!=EINVAL) r = -1;
// } else {
// DASSERT(n==0);
// }
// if (inbytes == max_bytes - bc->inbytes) {
// r = -1;
// }
// bc->inbytes += max_bytes - bc->inbytes - inbytes;
// bc->outbytes += sizeof(buf) - outbytes;
// if (r) break;
// }
// bc->chars = bc->outbytes / 2;
// iconv(cnv, NULL, NULL, NULL, NULL);
// return r ? -1 : 0;
// }
static void todbc_iconv_free(todbc_iconv_t *val) {
if (!val) return;
if (val->cnv!=invalid_iconv()) {
iconv_close(val->cnv);
val->cnv = invalid_iconv();
}
free(val);
}
// http://www.cse.yorku.ca/~oz/hash.html
static const unsigned long hash_seed = 5381;
static unsigned long hashing(unsigned long hash, const unsigned char *str);
static void do_enc_hash_val_free(todbc_hash_t *hash, void *val, void *arg) {
todbc_iconvset_t *cnv = (todbc_iconvset_t*)arg;
todbc_enc_t *v = (todbc_enc_t*)val;
DASSERT(hash);
DASSERT(cnv);
DASSERT(v);
free(v);
}
static unsigned long do_enc_hash_key_hash(todbc_hash_t *hash, void *key) {
const char *k = (const char*)key;
DASSERT(k);
unsigned long h = hash_seed;
h = hashing(h, (const unsigned char*)k);
return h;
}
static int do_enc_hash_key_comp(todbc_hash_t *hash, void *key, void *val) {
const char *k = (const char*)key;
todbc_enc_t *v = (todbc_enc_t*)val;
if (strcasecmp(k, v->enc)) return 1;
return 0;
}
static int do_get_null_size(const char *enc, int *null_size) {
iconv_t cnv = iconv_open(enc, UTF8_ENC);
if (cnv==(iconv_t)-1) return -1;
char src[] = "";
char dst[64];
char *inbuf = src;
size_t inbytes = 1;
char *outbuf = dst;
size_t outbytes = sizeof(dst);
size_t n = iconv(cnv, &inbuf, &inbytes, &outbuf, &outbytes);
DASSERT(n==0);
DASSERT(inbytes==0);
int size = (int)(sizeof(dst) - outbytes);
iconv_close(cnv);
if (null_size) *null_size = size;
return 0;
}
static void do_iconv_hash_val_free(todbc_hash_t *hash, void *val, void *arg) {
todbc_iconvset_t *cnv = (todbc_iconvset_t*)arg;
todbc_iconv_t *v = (todbc_iconv_t*)val;
DASSERT(hash);
DASSERT(cnv);
DASSERT(v);
todbc_iconv_free(v);
}
static unsigned long do_iconv_hash_key_hash(todbc_hash_t *hash, void *key) {
todbc_iconv_key_t *k = (todbc_iconv_key_t*)key;
DASSERT(k);
unsigned long h = hash_seed;
h = hashing(h, (const unsigned char*)k->enc_to);
h = hashing(h, (const unsigned char*)k->enc_from);
return h;
}
static int do_iconv_hash_key_comp(todbc_hash_t *hash, void *key, void *val) {
todbc_iconv_key_t *k = (todbc_iconv_key_t*)key;
todbc_iconv_t *v = (todbc_iconv_t*)val;
if (strcasecmp(k->enc_to, v->key.enc_to)) return 1;
if (strcasecmp(k->enc_from, v->key.enc_from)) return 1;
return 0;
}
static unsigned long hashing(unsigned long hash, const unsigned char *str) {
unsigned long c;
while ((c = *str++)!=0) {
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
}
return hash;
}

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _todbc_iconv_h_
#define _todbc_iconv_h_
#include "todbc_buf.h"
#include "todbc_string.h"
#include <iconv.h>
// non-thread-safe
#define ASCII_ENC "ASCII"
#define UTF8_ENC "UTF-8"
#define UTF16_ENC "UCS-2LE"
#define UNICODE_ENC "UCS-4LE"
#define GB18030_ENC "GB18030"
#define MAX_CHARACTER_SIZE 6
typedef enum {
TSDB_CONV_OK = 0,
TSDB_CONV_NOT_AVAIL,
TSDB_CONV_OOM,
TSDB_CONV_OOR,
TSDB_CONV_TRUNC_FRACTION,
TSDB_CONV_TRUNC,
TSDB_CONV_CHAR_NOT_NUM,
TSDB_CONV_CHAR_NOT_TS,
TSDB_CONV_NOT_VALID_TS,
TSDB_CONV_GENERAL,
TSDB_CONV_BAD_CHAR,
TSDB_CONV_SRC_TOO_LARGE,
TSDB_CONV_SRC_BAD_SEQ,
TSDB_CONV_SRC_INCOMPLETE,
TSDB_CONV_SRC_GENERAL,
} TSDB_CONV_CODE;
typedef struct todbc_iconvset_s todbc_iconvset_t;
typedef struct todbc_iconv_s todbc_iconv_t;
typedef struct todbc_enc_s todbc_enc_t;
struct todbc_enc_s {
char enc[64];
int char_size; // character size at most
int null_size; // size for null terminator
int variable_char_size; // such as 3 for UTF8
};
todbc_iconvset_t* todbc_iconvset_create(void);
void todbc_iconvset_free(todbc_iconvset_t *cnvset);
todbc_iconv_t* todbc_iconvset_get(todbc_iconvset_t *cnvset, const char *enc_to, const char *enc_from);
todbc_enc_t todbc_iconvset_enc(todbc_iconvset_t *cnvset, const char *enc);
typedef struct todbc_bytes_s todbc_bytes_t;
typedef struct todbc_err_s todbc_err_t;
struct todbc_err_s {
unsigned int einval:1; // EINVAL
unsigned int eilseq:1; // EILSEQ
unsigned int etoobig:1; // E2BIG
unsigned int eoom:1; // ENOMEM
};
struct todbc_bytes_s {
size_t inbytes, outbytes;
size_t chars;
todbc_err_t err;
};
typedef struct todbc_iconv_arg_s todbc_iconv_arg_t;
struct todbc_iconv_arg_s {
const unsigned char *inbuf;
size_t inbytes; // -1: not set
unsigned char *outbuf;
size_t outbytes; // -1: not set
size_t chars; // -1: not set
};
iconv_t todbc_iconv_get(todbc_iconv_t *cnv);
int todbc_iconv_get_legal_chars(todbc_iconv_t *cnv, const unsigned char *str, todbc_bytes_t *bc);
// non-thread-safe
// use todbc_buf_t as mem-allocator, if NULL, fall-back to thread-local version
unsigned char* todbc_iconv_conv(todbc_iconv_t *cnv, todbc_buf_t *buf, const unsigned char *src, todbc_bytes_t *bc);
// null-terminator-inclusive
size_t todbc_iconv_est_bytes(todbc_iconv_t *cnv, size_t inbytes);
// if inchars>=0 && enc_from has fixed-char-size, returns inchars * char_size
// otherwise -1
size_t todbc_iconv_bytes(todbc_iconv_t *cnv, size_t inchars);
todbc_enc_t todbc_iconv_from(todbc_iconv_t *cnv);
todbc_enc_t todbc_iconv_to(todbc_iconv_t *cnv);
// at return, *slen/*dlen stores the remaining #
int todbc_iconv_raw(todbc_iconv_t *cnv, const unsigned char *src, size_t *slen, unsigned char *dst, size_t *dlen);
// use todbc_buf_t as mem-allocator, if NULL, fall-back to thread-local version
// at return, *slen stores the remaining #
todbc_string_t todbc_iconv_conv2(todbc_iconv_t *cnv, todbc_buf_t *buf, const unsigned char *src, size_t *slen);
#endif // _todbc_iconv_h_

View File

@ -0,0 +1,256 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "todbc_list.h"
#include "todbc_log.h"
#include <stdlib.h>
struct todbc_list_s {
todbc_list_conf_t conf;
todbc_list_node_t *head;
todbc_list_node_t *tail;
size_t count;
};
struct todbc_list_node_s {
void *val;
todbc_list_t *list;
todbc_list_node_t *next;
todbc_list_node_t *prev;
};
static void do_remove(todbc_list_t *list, todbc_list_node_t *node);
static void do_pushback(todbc_list_t *list, todbc_list_node_t *node);
static void do_pushfront(todbc_list_t *list, todbc_list_node_t *node);
todbc_list_t* todbc_list_create(todbc_list_conf_t conf) {
todbc_list_t *list = (todbc_list_t*)calloc(1, sizeof(*list));
if (!list) return NULL;
list->conf = conf;
return list;
}
void todbc_list_free(todbc_list_t *list) {
if (!list) return;
while (list->head) {
void *val = list->head->val;
do_remove(list, list->head);
if (!list->conf.val_free) continue;
list->conf.val_free(list, val, list->conf.arg);
}
DASSERT(list->count == 0);
}
size_t todbc_list_count(todbc_list_t *list) {
if (!list) return 0;
return list->count;
}
int todbc_list_pushback(todbc_list_t *list, void *val) {
if (!list) return -1;
todbc_list_node_t *node = (todbc_list_node_t*)calloc(1, sizeof(*node));
if (!node) return -1;
node->val = val;
do_pushback(list, node);
return 0;
}
int todbc_list_pushfront(todbc_list_t *list, void *val) {
if (!list) return -1;
todbc_list_node_t *node = (todbc_list_node_t*)calloc(1, sizeof(*node));
if (!node) return -1;
node->val = val;
do_pushfront(list, node);
return 0;
}
int todbc_list_popback(todbc_list_t *list, void **val, int *found) {
if (!list) return -1;
if (!found) return -1;
*found = 0;
todbc_list_node_t *node = list->tail;
if (!node) return 0;
if (val) *val = node->val;
do_remove(list, node);
*found = 1;
return 0;
}
int todbc_list_popfront(todbc_list_t *list, void **val, int *found) {
if (!list) return -1;
if (!found) return -1;
*found = 0;
todbc_list_node_t *node = list->head;
if (!node) return 0;
if (val) *val = node->val;
do_remove(list, node);
*found = 1;
return 0;
}
int todbc_list_pop(todbc_list_t *list, void *val, int *found) {
if (!list) return -1;
if (!found) return -1;
*found = 0;
todbc_list_node_t *node = list->head;
while (node) {
if (node->val == val) break;
node = node->next;
}
if (!node) return 0;
do_remove(list, node);
*found = 1;
return 0;
}
int todbc_list_traverse(todbc_list_t *list, list_iterate_f iterate, void *arg) {
if (!list) return -1;
if (!iterate) return -1;
todbc_list_node_t *node = list->head;
while (node) {
int r = iterate(list, node->val, arg);
if (r) return r;
node = node->next;
}
return 0;
}
typedef struct comp_s comp_t;
struct comp_s {
list_comp_f comp;
void *arg;
int found;
void *val;
};
static int do_comp(todbc_list_t *list, void *val, void *arg);
int todbc_list_find(todbc_list_t *list, void *val, int *found, list_comp_f comp, void *arg) {
if (!list) return -1;
if (!found) return -1;
if (!comp) return -1;
*found = 0;
comp_t sarg = {0};
sarg.comp = comp;
sarg.arg = arg;
sarg.val = val;
todbc_list_traverse(list, do_comp, &sarg);
if (sarg.found) {
*found = 1;
}
return 0;
}
static int do_comp(todbc_list_t *list, void *val, void *arg) {
comp_t *sarg = (comp_t*)arg;
int r = sarg->comp(list, val, sarg->val, sarg->arg);
if (r==0) {
sarg->found = 1;
}
return r;
}
static void do_remove(todbc_list_t *list, todbc_list_node_t *node) {
DASSERT(node);
DASSERT(list);
DASSERT(list == node->list);
todbc_list_node_t *prev = node->prev;
todbc_list_node_t *next = node->next;
if (prev) prev->next = next;
else list->head = next;
if (next) next->prev = prev;
else list->tail = prev;
node->prev = NULL;
node->next = NULL;
node->list = NULL;
node->val = NULL;
list->count -= 1;
DASSERT(list->count <= INT64_MAX);
free(node);
}
static void do_pushback(todbc_list_t *list, todbc_list_node_t *node) {
DASSERT(list);
DASSERT(node);
DASSERT(node->list == NULL);
DASSERT(node->prev == NULL);
DASSERT(node->next == NULL);
node->list = list;
node->prev = list->tail;
if (list->tail) list->tail->next = node;
else list->head = node;
list->tail = node;
list->count += 1;
DASSERT(list->count > 0);
}
static void do_pushfront(todbc_list_t *list, todbc_list_node_t *node) {
DASSERT(node);
DASSERT(node->list == NULL);
DASSERT(node->prev == NULL);
DASSERT(node->next == NULL);
node->list = list;
node->next = list->head;
if (list->head) list->head->prev = node;
else list->tail = node;
list->head = node;
list->count += 1;
DASSERT(list->count > 0);
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _todbc_list_h_
#define _todbc_list_h_
#include <stdint.h>
#include <stddef.h>
// non-thread-safe
typedef struct todbc_list_s todbc_list_t;
typedef struct todbc_list_node_s todbc_list_node_t;
typedef struct todbc_list_conf_s todbc_list_conf_t;
typedef void (*list_val_free_f)(todbc_list_t *list, void *val, void *arg);
struct todbc_list_conf_s {
void *arg;
list_val_free_f val_free;
};
todbc_list_t* todbc_list_create(todbc_list_conf_t conf);
void todbc_list_free(todbc_list_t *list);
size_t todbc_list_count(todbc_list_t *list);
int todbc_list_pushback(todbc_list_t *list, void *val);
int todbc_list_pushfront(todbc_list_t *list, void *val);
int todbc_list_popback(todbc_list_t *list, void **val, int *found);
int todbc_list_popfront(todbc_list_t *list, void **val, int *found);
int todbc_list_pop(todbc_list_t *list, void *val, int *found);
typedef int (*list_iterate_f)(todbc_list_t *list, void *val, void *arg);
int todbc_list_traverse(todbc_list_t *list, list_iterate_f iterate, void *arg);
typedef int (*list_comp_f)(todbc_list_t *list, void *old, void *val, void *arg);
int todbc_list_find(todbc_list_t *list, void *val, int *found, list_comp_f comp, void *arg);
#endif // _todbc_list_h_

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "todbc_log.h"
uint64_t todbc_get_threadid(void) {
uint64_t tid = 0;
#ifdef __APPLE__
pthread_threadid_np(NULL, &tid);
#elif defined(__linux__)
tid = (uint64_t)syscall(__NR_gettid);
#elif defined(_MSC_VER)
tid = GetCurrentThreadId();
#else
#error you have to check the target API for thread id
#endif
return tid;
}
#ifdef _MSC_VER
static __declspec(thread) uint64_t thread_id = 0;
#else
static __thread uint64_t thread_id = 0;
#endif
#ifdef __GNUC__
__attribute__((format(printf, 6, 7)))
#endif
void todbc_log(const char *file, int line, const char *func, const char tag, int err, const char *fmt, ...) {
struct tm stm = {0};
struct timeval tv;
if (thread_id==0) {
thread_id = todbc_get_threadid();
}
gettimeofday(&tv, NULL);
time_t tt = tv.tv_sec;
localtime_r(&tt, &stm);
char buf[4096];
size_t bytes = sizeof(buf);
char *p = buf;
int n = 0;
n = snprintf(p, bytes, "%C%02d:%02d:%02d.%06d[%" PRIx64 "]%s[%d]%s()",
tag, stm.tm_hour, stm.tm_min, stm.tm_sec, (int)tv.tv_usec,
thread_id, basename((char*)file), line, func);
if (n>0) {
bytes -= (size_t)n;
p += n;
}
if (bytes>0) {
if (tag=='E' && err) {
n = snprintf(p, bytes, "[%d]%s", err, strerror(err));
if (n>0) {
bytes -= (size_t)n;
p += n;
}
}
}
if (bytes>0) {
n = snprintf(p, bytes, ": ");
if (n>0) {
bytes -= (size_t)n;
p += n;
}
}
if (bytes>0) {
va_list ap;
va_start(ap, fmt);
n = vsnprintf(p, bytes, fmt, ap);
va_end(ap);
}
fprintf(stderr, "%s\n", buf);
}

View File

@ -18,25 +18,35 @@
#include "os.h"
#define D(fmt, ...) \
fprintf(stderr, \
"%s[%d]:%s() " fmt "\n", \
basename((char*)__FILE__), __LINE__, __func__, \
##__VA_ARGS__)
#ifdef __GNUC__
__attribute__((format(printf, 6, 7)))
#endif
void todbc_log(const char *file, int line, const char *func, const char tag, int err, const char *fmt, ...);
#define DASSERT(statement) \
do { \
if (statement) break; \
D("Assertion failure: %s", #statement); \
abort(); \
#define OL(tag, err, fmt, ...) todbc_log(__FILE__, __LINE__, __func__, tag, err, "%s" fmt "", "", ##__VA_ARGS__)
#define OD(fmt, ...) OL('D', 0, fmt, ##__VA_ARGS__)
#define OE(fmt, ...) OL('E', errno, fmt, ##__VA_ARGS__)
#define OW(fmt, ...) OL('W', 0, fmt, ##__VA_ARGS__)
#define OI(fmt, ...) OL('I', 0, fmt, ##__VA_ARGS__)
#define OV(fmt, ...) OL('V', 0, fmt, ##__VA_ARGS__)
#define OA(statement, fmt, ...) do { \
if (statement) break; \
OL('A', 0, "Assertion failure:[%s]; " fmt "", #statement, ##__VA_ARGS__); \
abort(); \
} while (0)
#define OILE(statement, fmt, ...) OA(statement, "internal logic error: [" fmt "]", ##__VA_ARGS__)
#define ONIY(statement, fmt, ...) OA(statement, "not implemented yet: [" fmt "]", ##__VA_ARGS__)
#define ONSP(statement, fmt, ...) OA(statement, "not support yet: [" fmt "]", ##__VA_ARGS__)
#define D(fmt, ...) OD(fmt, ##__VA_ARGS__)
#define E(fmt, ...) OE(fmt, ##__VA_ARGS__)
#define DASSERT(statement) OA(statement, "")
#define DASSERTX(statement, fmt, ...) OA(statement, fmt, ##__VA_ARGS__)
uint64_t todbc_get_threadid(void);
#define DASSERTX(statement, fmt, ...) \
do { \
if (statement) break; \
D("Assertion failure: %s, " fmt "", #statement, ##__VA_ARGS__); \
abort(); \
} while (0)
#endif // _todbc_log_h_

View File

@ -1,11 +1,18 @@
%{
#ifdef _MSC_VER
#include <windows.h>
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#define basename PathFindFileNameA
#else
#include <libgen.h>
#endif
#include "todbc_flex.h"
#include <stdio.h>
#include <odbcinst.h>
#ifdef _MSC_VER
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#endif
static int process_map(const char *cfg, map_tsdb_type_t *tsdb_map, int type);
#define PUSH_STATE(state) yy_push_state(state, yyscanner)
#define POP_STATE() yy_pop_state(yyscanner)
@ -28,50 +35,73 @@ do { \
while (yyleng) unput(yytext[yyleng-1]); \
} while (0)
#define set_key() \
do { \
free(yyextra->key); \
yyextra->key = strdup(yytext); \
#define set_val() \
do { \
int r = 0; \
int curr; TOP_STATE(curr); \
POP_STATE(); \
int state; TOP_STATE(state); \
switch(state) { \
case DSN: { \
free(yyextra->dsn); \
yyextra->dsn = strdup(yytext); \
} break; \
case UID: { \
free(yyextra->uid); \
yyextra->uid = strdup(yytext); \
} break; \
case PWD: { \
free(yyextra->pwd); \
yyextra->pwd = strdup(yytext); \
} break; \
case SERVER: { \
free(yyextra->server); \
yyextra->server = strdup(yytext); \
} break; \
case DB: { \
free(yyextra->db); \
yyextra->db = strdup(yytext); \
} break; \
case ENC_CHAR: { \
free(yyextra->enc_char); \
yyextra->enc_char = strdup(yytext); \
} break; \
case ENC_WCHAR: { \
free(yyextra->enc_wchar); \
yyextra->enc_wchar = strdup(yytext); \
} break; \
case ENC_DB: { \
free(yyextra->enc_db); \
yyextra->enc_db = strdup(yytext); \
} break; \
case ENC_LOCAL: { \
free(yyextra->enc_local); \
yyextra->enc_local = strdup(yytext); \
} break; \
case TSDB_FLOAT: { \
if (process_map(yytext, &yyextra->tsdb_map, TSDB_FLOAT)) { \
r = -1; \
} \
} break; \
case TSDB_BIGINT: { \
if (process_map(yytext, &yyextra->tsdb_map, TSDB_BIGINT)) { \
r = -1; \
} \
} break; \
case KEY: { \
} break; \
default: { \
r = -1; \
} break; \
} \
PUSH_STATE(curr); \
if (r) return r; \
} while (0)
#define set_val() \
do { \
if (!yyextra->key) break; \
if (strcasecmp(yyextra->key, "DSN")==0) { \
free(yyextra->dsn); \
yyextra->dsn = strdup(yytext); \
break; \
} \
if (strcasecmp(yyextra->key, "UID")==0) { \
free(yyextra->uid); \
yyextra->uid = strdup(yytext); \
break; \
} \
if (strcasecmp(yyextra->key, "PWD")==0) { \
free(yyextra->pwd); \
yyextra->pwd = strdup(yytext); \
break; \
} \
if (strcasecmp(yyextra->key, "DB")==0) { \
free(yyextra->db); \
yyextra->pwd = strdup(yytext); \
break; \
} \
if (strcasecmp(yyextra->key, "Server")==0) { \
free(yyextra->server); \
yyextra->server = strdup(yytext); \
break; \
} \
if (strcasecmp(yyextra->key, "SERVER_ENC")==0) { \
free(yyextra->svr_enc); \
yyextra->svr_enc = strdup(yytext); \
break; \
} \
if (strcasecmp(yyextra->key, "CLIENT_ENC")==0) { \
free(yyextra->cli_enc); \
yyextra->cli_enc = strdup(yytext); \
break; \
} \
#define FAIL() \
do { \
/*fprintf(stderr, "==%s[%d]%s()==\n", basename(__FILE__), __LINE__, __func__);*/ \
return -1; \
} while (0)
%}
@ -89,57 +119,85 @@ do { \
%option warn
%option perf-report
%option 8bit
%option case-insensitive
%x DSN UID PWD SERVER DB
%x ENC_CHAR ENC_WCHAR ENC_DB ENC_LOCAL
%x TSDB_FLOAT TSDB_BIGINT
%x KEY EQ BRACE1 BRACE2 VAL
%%
<<EOF>> { int state; TOP_STATE(state);
if (state == INITIAL) yyterminate();
if (state == VAL) yyterminate();
return -1; }
FAIL(); }
[[:space:]]+ { }
[[:alnum:]_]+ { set_key(); PUSH_STATE(KEY); }
.|\n { return -1; }
"DSN" { PUSH_STATE(DSN); }
"UID" { PUSH_STATE(UID); }
"PWD" { PUSH_STATE(PWD); }
"Server" { PUSH_STATE(SERVER); }
"DB" { PUSH_STATE(DB); }
"ENC_CHAR" { PUSH_STATE(ENC_CHAR); }
"ENC_WCHAR" { PUSH_STATE(ENC_WCHAR); }
"ENC_DB" { PUSH_STATE(ENC_DB); }
"ENC_LOCAL" { PUSH_STATE(ENC_LOCAL); }
"map.float" { PUSH_STATE(TSDB_FLOAT); }
"map.bigint" { PUSH_STATE(TSDB_BIGINT); }
[[:alnum:]_]+ { PUSH_STATE(KEY); }
.|\n { FAIL(); }
<KEY>[[:space:]]+ { }
<KEY>[=] { CHG_STATE(EQ); }
<KEY>.|\n { return -1; }
<DSN,UID,PWD,SERVER,DB,ENC_CHAR,ENC_WCHAR,ENC_DB,ENC_LOCAL,TSDB_FLOAT,TSDB_BIGINT,KEY>[[:space:]]+ { }
<DSN,UID,PWD,SERVER,DB,ENC_CHAR,ENC_WCHAR,ENC_DB,ENC_LOCAL,TSDB_FLOAT,TSDB_BIGINT,KEY>[=] { PUSH_STATE(EQ); }
<DSN,UID,PWD,SERVER,DB,ENC_CHAR,ENC_WCHAR,ENC_DB,ENC_LOCAL,TSDB_FLOAT,TSDB_BIGINT,KEY>.|\n { FAIL(); }
<EQ>[[:space:]]+ { }
<EQ>[^][{}(),;?*=!@/\\\n[:space:]]+ { set_val(); CHG_STATE(VAL); }
<EQ>[{] { CHG_STATE(BRACE1); }
<EQ>.|\n { return -1; }
<EQ>[^][{}(),;?*=!@/\\\n[:space:]]+ { set_val(); POP_STATE(); CHG_STATE(VAL); }
<EQ>.|\n { FAIL(); }
<BRACE1>[^{}\n]+ { set_val(); CHG_STATE(BRACE2); }
<BRACE1>.|\n { return -1; }
<BRACE1>.|\n { FAIL(); }
<BRACE2>[[:space:]]+ { }
<BRACE2>[}] { CHG_STATE(VAL); }
<BRACE2>.|\n { return -1; }
<BRACE2>[}] { POP_STATE(); CHG_STATE(VAL); }
<BRACE2>.|\n { FAIL(); }
<VAL>[;] { POP_STATE(); }
<VAL>.|\n { return -1; }
<VAL>.|\n { FAIL(); }
%%
static char* get_val_by_key_from_odbc_ini(const char *dsn, const char *key);
static void conn_val_init(conn_val_t *val);
int todbc_parse_conn_string(const char *conn, conn_val_t *val) {
yyscan_t arg = {0};
yylex_init(&arg);
yyset_debug(0, arg);
yyset_extra(val, arg);
conn_val_init(val);
yy_scan_string(conn, arg);
int ret =yylex(arg);
yylex_destroy(arg);
if (val->key) free(val->key); val->key = NULL;
if (ret) {
if (ret || !val->dsn) {
conn_val_reset(val);
} else {
if (!val->uid) {
val->uid = get_val_by_key_from_odbc_ini(val->dsn, "UID");
}
if (!val->pwd) {
val->pwd = get_val_by_key_from_odbc_ini(val->dsn, "PWD");
}
if (!val->server) {
val->server = get_val_by_key_from_odbc_ini(val->dsn, "Server");
}
}
return ret ? -1 : 0;
}
void conn_val_reset(conn_val_t *val) {
if (val->key) {
free(val->key); val->key = NULL;
}
if (val->dsn) {
free(val->dsn); val->dsn = NULL;
}
@ -155,11 +213,68 @@ void conn_val_reset(conn_val_t *val) {
if (val->server) {
free(val->server); val->server = NULL;
}
if (val->svr_enc) {
free(val->svr_enc); val->svr_enc = NULL;
if (val->enc_local) {
free(val->enc_local); val->enc_local = NULL;
}
if (val->cli_enc) {
free(val->cli_enc); val->cli_enc = NULL;
if (val->enc_db) {
free(val->enc_db); val->enc_db = NULL;
}
if (val->enc_char) {
free(val->enc_char); val->enc_char = NULL;
}
if (val->enc_wchar) {
free(val->enc_wchar); val->enc_wchar = NULL;
}
}
static char* get_val_by_key_from_odbc_ini(const char *dsn, const char *key) {
char Val[4096];
Val[0] = '\0';
int n = SQLGetPrivateProfileString(dsn, key, "", Val, sizeof(Val), "odbc.ini");
if (n<=0) return NULL;
if (Val[0]=='\0') return NULL;
return strdup(Val);
}
static int process_map(const char *cfg, map_tsdb_type_t *tsdb_map, int type) {
switch (type) {
case TSDB_FLOAT: {
if (strcmp(cfg, "SQL_DOUBLE")==0) {
tsdb_map->tsdb_float = SQL_DOUBLE;
return 0;
}
} break;
case TSDB_BIGINT: {
if (strcmp(cfg, "SQL_C_SBIGINT")==0) {
tsdb_map->tsdb_bigint = SQL_C_SBIGINT;
return 0;
}
if (strcmp(cfg, "SQL_C_UBIGINT")==0) {
tsdb_map->tsdb_bigint = SQL_C_UBIGINT;
return 0;
}
if (strcmp(cfg, "SQL_CHAR")==0) {
tsdb_map->tsdb_bigint = SQL_CHAR;
return 0;
}
} break;
default: {
} break;
}
return -1;
}
static void conn_val_init(conn_val_t *val) {
if (!val) return;
val->tsdb_map.tsdb_tinyint = SQL_TINYINT;
val->tsdb_map.tsdb_smallint = SQL_SMALLINT;
val->tsdb_map.tsdb_int = SQL_INTEGER;
val->tsdb_map.tsdb_bigint = SQL_BIGINT;
val->tsdb_map.tsdb_float = SQL_REAL;
val->tsdb_map.tsdb_double = SQL_DOUBLE;
val->tsdb_map.tsdb_bool = SQL_TINYINT;
val->tsdb_map.tsdb_timestamp = SQL_CHAR;
val->tsdb_map.tsdb_binary = SQL_BINARY;
val->tsdb_map.tsdb_nchar = SQL_WCHAR;
}

View File

@ -0,0 +1,218 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "todbc_string.h"
#include "todbc_log.h"
#include "todbc_tls.h"
#include <stdlib.h>
static int do_calc_bytes(todbc_string_t *str);
todbc_string_t todbc_string_init(const char *enc, const unsigned char *src, const size_t bytes) {
DASSERT(enc);
DASSERT(src);
todbc_string_t s = {0};
todbc_string_t *str = &s;
todbc_iconv_t *cnv = todbc_tls_iconv_get(enc, UTF8_ENC);
if (!cnv) return s;
if (snprintf(str->enc, sizeof(str->enc), "%s", enc)>=sizeof(str->enc)) {
return s;
}
str->buf = src;
str->total_bytes = bytes; // need to recalc
str->bytes = 0;
if (do_calc_bytes(str)) {
str->buf = NULL;
str->total_bytes = 0;
str->bytes = 0;
}
return s;
}
todbc_string_t todbc_string_copy(todbc_string_t *str, const char *enc, unsigned char *dst, const size_t target_bytes) {
todbc_string_t val = {0};
DASSERT(str);
DASSERT(dst);
DASSERT(str->buf);
DASSERT(str->bytes<=INT64_MAX);
DASSERT(str->total_bytes<=INT64_MAX);
if (snprintf(val.enc, sizeof(val.enc), "%s", enc)>=sizeof(val.enc)) {
return val;
}
todbc_iconv_t *icnv = todbc_tls_iconv_get(enc, str->enc);
if (!icnv) return val;
iconv_t cnv = todbc_iconv_get(icnv);
if (cnv==(iconv_t)-1) return val;
val.buf = dst;
val.total_bytes = target_bytes;
const int null_bytes = todbc_iconv_to(icnv).null_size;
if (target_bytes<=null_bytes) return val;
size_t estsize = todbc_iconv_est_bytes(icnv, str->bytes);
if (estsize>INT64_MAX) return val;
// smaller is better!!!
const size_t outblock = (estsize > target_bytes) ? estsize = target_bytes : estsize;
char *inbuf = (char*)str->buf;
size_t inbytes = str->bytes; // not counting null-terminator
char *outbuf = (char*)dst;
size_t outbytes = outblock;
int r = todbc_iconv_raw(icnv, (const unsigned char*)inbuf, &inbytes, (unsigned char*)outbuf, &outbytes);
if (r) {
DASSERT(outbytes > 0);
val.bytes = outblock - outbytes;
val.total_bytes = outblock;
return val;
} else {
val.bytes = outblock - outbytes;
val.total_bytes = val.bytes;
if (inbytes > 0) {
val.total_bytes += 1; // to indicate truncation
}
return val;
}
}
todbc_string_t todbc_copy(const char *from_enc, const unsigned char *src, size_t *inbytes, const char *to_enc, unsigned char *dst, const size_t dlen) {
DASSERT(from_enc);
DASSERT(src);
DASSERT(inbytes);
DASSERT(to_enc);
DASSERT(dst);
DASSERT(dlen <= INT64_MAX);
todbc_string_t s_from = todbc_string_init(from_enc, src, *inbytes);
DASSERT(s_from.buf == src);
return todbc_string_copy(&s_from, to_enc, dst, dlen);
}
todbc_string_t todbc_string_conv_to(todbc_string_t *str, const char *enc, todbc_buf_t *buf) {
DASSERT(str);
DASSERT(str->buf);
DASSERT(str->bytes<=INT64_MAX);
DASSERT(str->total_bytes<=INT64_MAX);
todbc_string_t nul = {0};
todbc_iconv_t *icnv = todbc_tls_iconv_get(enc, str->enc);
if (!icnv) return nul;
size_t estsize = todbc_iconv_est_bytes(icnv, str->bytes);
if (estsize>INT64_MAX) return nul;
char *out = NULL;
if (!buf) out = (char*)todbc_tls_buf_alloc(estsize);
else out = (char*)todbc_buf_alloc(buf, estsize);
if (!out) return nul;
return todbc_string_copy(str, enc, (unsigned char*)out, estsize);
}
static int do_calc_bytes(todbc_string_t *str) {
iconv_t cnv = todbc_tls_iconv(UTF8_ENC, str->enc);
if (cnv == (iconv_t)-1) return -1;
size_t total_bytes = 0;
char buf[1024*16];
char *inbuf = (char*)str->buf;
while (1) {
size_t outblock = sizeof(buf);
size_t inblock = outblock;
size_t remain = (size_t)-1;
if (str->total_bytes <= INT64_MAX) {
remain = str->total_bytes - total_bytes;
if (remain==0) break;
if (inblock > remain) inblock = remain;
}
size_t inbytes = inblock;
char *outbuf = buf;
size_t outbytes = outblock;
size_t n = iconv(cnv, &inbuf, &inbytes, &outbuf, &outbytes);
total_bytes += inblock - inbytes;
int e = 0;
if (n==(size_t)-1) {
e = errno;
if (str->total_bytes<=INT64_MAX) {
D("iconv failed @[%zu], inbytes[%zd->%zd], outbytes[%zd->%zd]: [%d]%s",
(inblock-inbytes), inblock, inbytes, outblock, outbytes, e, strerror(e));
}
DASSERT(e==EILSEQ || e==E2BIG || e==EINVAL);
}
if (n>0 && n<=INT64_MAX) {
D("iconv found non-reversible seq");
}
size_t outlen = outblock - outbytes;
size_t utf8len = strnlen(buf, outlen);
if (utf8len < outlen) {
// null-terminator found
// revert
inbuf -= inblock - inbytes;
total_bytes -= inblock - inbytes;
if (utf8len==0) break;
inbytes = inblock;
outbuf = buf;
outbytes = utf8len;
n = iconv(cnv, &inbuf, &inbytes, &outbuf, &outbytes);
total_bytes += inblock - inbytes;
DASSERT(n==(size_t)-1);
e = errno;
DASSERT(e==E2BIG);
break;
}
if (e==EILSEQ) break;
if (e==EINVAL) {
if (inbytes == remain) {
// this is the last stuff
break;
}
}
}
if (str->total_bytes > INT64_MAX) {
str->total_bytes = total_bytes;
}
str->bytes = total_bytes;
iconv(cnv, NULL, NULL, NULL, NULL);
return 0;
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _todbc_string_h_
#define _todbc_string_h_
#include <stdint.h>
#include <stddef.h>
#include "todbc_buf.h"
// non-thread-safe
typedef struct todbc_string_s todbc_string_t;
struct todbc_string_s {
char enc[64];
// null if init failed because of internal resources shortage
const unsigned char *buf; // null-terminator inclusive
size_t total_bytes; // not counting null-terminator
// <= total_bytes
// truncated if < total_bytes
size_t bytes; // not counting null-terminator
};
// does not copy internally
// bytes: not characters, <0 means bytes unknown
todbc_string_t todbc_string_init(const char *enc, const unsigned char *src, const size_t bytes);
// conv and copy to dst not more than target_bytes (null-terminator-inclusive)
// return'd val->buf == dst, total_bytes<target_bytes, truncated if bytes<total_bytes
todbc_string_t todbc_string_copy(todbc_string_t *str, const char *enc, unsigned char *dst, const size_t target_bytes);
todbc_string_t todbc_copy(const char *from_enc, const unsigned char *src, size_t *inbytes, const char *to_enc, unsigned char *dst, const size_t dlen);
// use todbc_buf_t as mem-allocator
todbc_string_t todbc_string_conv_to(todbc_string_t *str, const char *enc, todbc_buf_t *buf);
#endif // _todbc_string_h_

View File

@ -0,0 +1,250 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "todbc_tls.h"
#include "todbc_buf.h"
#include "todbc_iconv.h"
#include "todbc_log.h"
typedef struct todbc_tls_s todbc_tls_t;
struct todbc_tls_s {
todbc_buf_t *buf;
todbc_iconvset_t *cnvset;
};
static void todbc_tls_free(todbc_tls_t *value);
static pthread_key_t key_this;
static pthread_once_t key_once = PTHREAD_ONCE_INIT;
static int key_err = 0;
static void key_init(void);
static void key_destructor(void *arg);
static void key_init(void) {
key_err = pthread_key_create(&key_this, key_destructor);
if (key_err) {
D("thread local initialization failed: [%d]%s", key_err, strerror(key_err));
}
}
static todbc_tls_t* todbc_tls_create(void);
static todbc_tls_t* key_value(void) {
pthread_once(&key_once, key_init);
if (key_err) return NULL;
int err = 0;
todbc_tls_t *value = pthread_getspecific(key_this);
if (value) return value;
value = todbc_tls_create();
if (!value) return NULL;
do {
err = pthread_setspecific(key_this, value);
if (err) {
D("thread local setup failed: [%d]%s", err, strerror(err));
break;
}
return value;
} while (0);
todbc_tls_free(value);
return NULL;
}
static void key_destructor(void *arg) {
todbc_tls_t *value = (todbc_tls_t*)arg;
todbc_tls_free(value);
}
static todbc_tls_t* todbc_tls_create(void) {
int err = 0;
todbc_tls_t *value = (todbc_tls_t*)calloc(1, sizeof(*value));
if (!value) {
err = errno;
D("thread local creation failed: [%d]%s", err, strerror(err));
return NULL;
}
do {
return value;
} while (0);
todbc_tls_free(value);
return NULL;
}
static void todbc_tls_free(todbc_tls_t *value) {
if (value->cnvset) {
todbc_iconvset_free(value->cnvset);
value->cnvset = NULL;
}
if (value->buf) {
todbc_buf_free(value->buf);
value->buf = NULL;
}
free(value);
}
static todbc_iconvset_t* do_get_iconvset(void);
// iconv
int todbc_legal_chars(const char *enc, const unsigned char *str, todbc_bytes_t *bc) {
todbc_iconvset_t *icnv = do_get_iconvset();
if (!icnv) return -1;
todbc_iconv_t *cnv = todbc_iconvset_get(icnv, UTF16_ENC, enc);
if (!cnv) return -1;
return todbc_iconv_get_legal_chars(cnv, str, bc);
}
todbc_iconv_t* todbc_tls_iconv_get(const char *to_enc, const char *from_enc) {
todbc_iconvset_t *cnvset = do_get_iconvset();
if (!cnvset) return NULL;
todbc_iconv_t *cnv = todbc_iconvset_get(cnvset, to_enc, from_enc);
return cnv;
}
iconv_t todbc_tls_iconv(const char *to_enc, const char *from_enc) {
todbc_iconv_t *icnv = todbc_tls_iconv_get(to_enc, from_enc);
if (!icnv) return (iconv_t)-1;
return todbc_iconv_get(icnv);
}
todbc_enc_t todbc_tls_iconv_enc(const char *enc) {
do {
todbc_iconvset_t *cnvset = do_get_iconvset();
if (!cnvset) break;
return todbc_iconvset_enc(cnvset, enc);
} while (0);
todbc_enc_t v = {0};
v.char_size = -1;
v.null_size = -1;
return v;
}
todbc_string_t todbc_tls_conv(todbc_buf_t *buf, const char *enc_to, const char *enc_from, const unsigned char *src, size_t *slen) {
todbc_iconv_t *cnv = todbc_tls_iconv_get(enc_to, enc_from);
if (!cnv) {
todbc_string_t nul = {0};
return nul;
}
return todbc_iconv_conv2(cnv, buf, src, slen);
}
todbc_string_t todbc_tls_write(const char *enc_to, const char *enc_from,
const unsigned char *src, size_t *slen, unsigned char *dst, size_t dlen)
{
todbc_iconv_t *cnv = todbc_tls_iconv_get(enc_to, enc_from);
if (!cnv) {
todbc_string_t nul = {0};
return nul;
}
todbc_string_t s = {0};
s.buf = dst;
s.total_bytes = dlen;
size_t inbytes = *slen;
size_t outbytes = dlen;
todbc_iconv_raw(cnv, src, &inbytes, dst, &outbytes);
s.bytes = dlen - outbytes;
s.total_bytes = s.bytes;
if (inbytes) {
s.total_bytes += 1;
}
*slen = inbytes;
return s;
}
char* todbc_tls_strndup(const char *src, size_t n) {
todbc_buf_t *buf = todbc_tls_buf();
if (!buf) return NULL;
n = strnlen(src, n);
char *d = todbc_buf_alloc(buf, (n+1));
if (!d) return NULL;
snprintf(d, n+1, "%s", src);
return d;
}
static todbc_iconvset_t* do_get_iconvset(void) {
todbc_tls_t *tls = key_value();
if (!tls) return NULL;
if (!tls->cnvset) {
tls->cnvset = todbc_iconvset_create();
}
return tls->cnvset;
}
// tls_buf
void* todbc_tls_buf_alloc(size_t size) {
todbc_tls_t *tls = key_value();
if (!tls) return NULL;
if (!tls->buf) {
tls->buf = todbc_buf_create();
if (!tls->buf) return NULL;
}
return todbc_buf_alloc(tls->buf, size);
}
void* todbc_tls_buf_calloc(size_t count, size_t size) {
todbc_tls_t *tls = key_value();
if (!tls) return NULL;
if (!tls->buf) {
tls->buf = todbc_buf_create();
if (!tls->buf) return NULL;
}
return todbc_buf_calloc(tls->buf, count, size);
}
void* todbc_tls_buf_realloc(void *ptr, size_t size) {
todbc_tls_t *tls = key_value();
if (!tls) return NULL;
if (!tls->buf) {
tls->buf = todbc_buf_create();
if (!tls->buf) return NULL;
}
return todbc_buf_realloc(tls->buf, ptr, size);
}
void todbc_tls_buf_reclaim(void) {
todbc_tls_t *tls = key_value();
if (!tls) return;
if (!tls->buf) return;
todbc_buf_reclaim(tls->buf);
}
todbc_buf_t* todbc_tls_buf(void) {
todbc_tls_t *tls = key_value();
if (!tls) return NULL;
if (!tls->buf) {
tls->buf = todbc_buf_create();
}
return tls->buf;
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _todbc_tls_h_
#define _todbc_tls_h_
// !!! functions exported in this header file are all non-thread-safe !!!
#include "taos.h"
#include "todbc_buf.h"
#include "todbc_iconv.h"
#include "todbc_string.h"
// thread local buffers
// non-thread-safe
// returned-buf are all thread-local-accessible until todbc_tls_buf_reclaim
void* todbc_tls_buf_alloc(size_t size);
void* todbc_tls_buf_calloc(size_t count, size_t size);
void* todbc_tls_buf_realloc(void *ptr, size_t size);
// reclaim all above thread-local-buf(s)
void todbc_tls_buf_reclaim(void);
// return local-thread-buf
todbc_buf_t* todbc_tls_buf(void);
// thread local iconv
// non-thread-safe
todbc_iconv_t* todbc_tls_iconv_get(const char *to_enc, const char *from_enc);
iconv_t todbc_tls_iconv(const char *to_enc, const char *from_enc);
todbc_enc_t todbc_tls_iconv_enc(const char *enc);
// non-thread-safe
int todbc_legal_chars(const char *enc, const unsigned char *str, todbc_bytes_t *bc);
// at return, *slen stores the remaining #
todbc_string_t todbc_tls_conv(todbc_buf_t *buf, const char *enc_to, const char *enc_from, const unsigned char *src, size_t *slen);
todbc_string_t todbc_tls_write(const char *enc_to, const char *enc_from, const unsigned char *src, size_t *slen, unsigned char *dst, size_t dlen);
char* todbc_tls_strndup(const char *src, size_t n);
#endif // _todbc_tls_h_

View File

@ -15,86 +15,576 @@
#include "todbc_util.h"
#include "todbc_log.h"
#include <iconv.h>
#include <sqlext.h>
#define SQL_CASE(type) case type: return #type
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";
SQL_CASE(SQL_BIT);
SQL_CASE(SQL_TINYINT);
SQL_CASE(SQL_SMALLINT);
SQL_CASE(SQL_INTEGER);
SQL_CASE(SQL_BIGINT);
SQL_CASE(SQL_FLOAT);
SQL_CASE(SQL_DOUBLE);
SQL_CASE(SQL_DECIMAL);
SQL_CASE(SQL_NUMERIC);
SQL_CASE(SQL_REAL);
SQL_CASE(SQL_CHAR);
SQL_CASE(SQL_VARCHAR);
SQL_CASE(SQL_LONGVARCHAR);
SQL_CASE(SQL_WCHAR);
SQL_CASE(SQL_WVARCHAR);
SQL_CASE(SQL_WLONGVARCHAR);
SQL_CASE(SQL_BINARY);
SQL_CASE(SQL_VARBINARY);
SQL_CASE(SQL_LONGVARBINARY);
SQL_CASE(SQL_DATE);
SQL_CASE(SQL_TIME);
SQL_CASE(SQL_TIMESTAMP);
SQL_CASE(SQL_TYPE_DATE);
SQL_CASE(SQL_TYPE_TIME);
SQL_CASE(SQL_TYPE_TIMESTAMP);
SQL_CASE(SQL_INTERVAL_MONTH);
SQL_CASE(SQL_INTERVAL_YEAR);
SQL_CASE(SQL_INTERVAL_YEAR_TO_MONTH);
SQL_CASE(SQL_INTERVAL_DAY);
SQL_CASE(SQL_INTERVAL_HOUR);
SQL_CASE(SQL_INTERVAL_MINUTE);
SQL_CASE(SQL_INTERVAL_SECOND);
SQL_CASE(SQL_INTERVAL_DAY_TO_HOUR);
SQL_CASE(SQL_INTERVAL_DAY_TO_MINUTE);
SQL_CASE(SQL_INTERVAL_DAY_TO_SECOND);
SQL_CASE(SQL_INTERVAL_HOUR_TO_MINUTE);
SQL_CASE(SQL_INTERVAL_HOUR_TO_SECOND);
SQL_CASE(SQL_INTERVAL_MINUTE_TO_SECOND);
SQL_CASE(SQL_GUID);
SQL_CASE(SQL_ALL_TYPES);
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";
SQL_CASE(SQL_C_CHAR);
SQL_CASE(SQL_C_WCHAR);
SQL_CASE(SQL_C_SHORT);
SQL_CASE(SQL_C_SSHORT);
SQL_CASE(SQL_C_USHORT);
SQL_CASE(SQL_C_LONG);
SQL_CASE(SQL_C_SLONG);
SQL_CASE(SQL_C_ULONG);
SQL_CASE(SQL_C_FLOAT);
SQL_CASE(SQL_C_DOUBLE);
SQL_CASE(SQL_C_BIT);
SQL_CASE(SQL_C_TINYINT);
SQL_CASE(SQL_C_STINYINT);
SQL_CASE(SQL_C_UTINYINT);
SQL_CASE(SQL_C_SBIGINT);
SQL_CASE(SQL_C_UBIGINT);
SQL_CASE(SQL_C_BINARY);
SQL_CASE(SQL_C_DATE);
SQL_CASE(SQL_C_TIME);
SQL_CASE(SQL_C_TIMESTAMP);
SQL_CASE(SQL_C_TYPE_DATE);
SQL_CASE(SQL_C_TYPE_TIME);
SQL_CASE(SQL_C_TYPE_TIMESTAMP);
SQL_CASE(SQL_C_NUMERIC);
SQL_CASE(SQL_C_GUID);
default: return "UNKNOWN";
}
}
// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlgetdiagfield-function?view=sql-server-ver15
const char* sql_diag_identifier(int type) {
switch (type) {
// header fields
SQL_CASE(SQL_DIAG_CURSOR_ROW_COUNT);
SQL_CASE(SQL_DIAG_DYNAMIC_FUNCTION);
SQL_CASE(SQL_DIAG_DYNAMIC_FUNCTION_CODE);
SQL_CASE(SQL_DIAG_NUMBER);
SQL_CASE(SQL_DIAG_RETURNCODE);
SQL_CASE(SQL_DIAG_ROW_COUNT);
// record fields
SQL_CASE(SQL_DIAG_CLASS_ORIGIN);
SQL_CASE(SQL_DIAG_COLUMN_NUMBER);
SQL_CASE(SQL_DIAG_CONNECTION_NAME);
SQL_CASE(SQL_DIAG_MESSAGE_TEXT);
SQL_CASE(SQL_DIAG_NATIVE);
SQL_CASE(SQL_DIAG_ROW_NUMBER);
SQL_CASE(SQL_DIAG_SERVER_NAME);
SQL_CASE(SQL_DIAG_SQLSTATE);
SQL_CASE(SQL_DIAG_SUBCLASS_ORIGIN);
default: return "UNKNOWN";
}
}
const char* sql_handle_type(int type) {
switch(type) {
SQL_CASE(SQL_HANDLE_ENV);
SQL_CASE(SQL_HANDLE_DBC);
SQL_CASE(SQL_HANDLE_STMT);
SQL_CASE(SQL_HANDLE_DESC);
// SQL_CASE(SQL_HANDLE_DBC_INFO_TOKEN);
default: return "UNKNOWN";
}
}
const char* sql_env_attr_type(int type) {
switch(type) {
SQL_CASE(SQL_ATTR_OUTPUT_NTS);
SQL_CASE(SQL_ATTR_ODBC_VERSION);
SQL_CASE(SQL_ATTR_CONNECTION_POOLING);
SQL_CASE(SQL_ATTR_CP_MATCH);
default: return "UNKNOWN";
}
}
// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetconnectattr-function?view=sql-server-ver15
const char* sql_conn_attr_type(int type) {
switch(type) {
SQL_CASE(SQL_ATTR_ACCESS_MODE);
// ODBC 3.8
// SQL_CASE(SQL_ATTR_ASYNC_DBC_EVENT);
SQL_CASE(SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE);
// ODBC 3.8
// SQL_CASE(SQL_ATTR_ASYNC_DBC_PCALLBACK);
// ODBC 3.8
// SQL_CASE(SQL_ATTR_ASYNC_DBC_PCONTEXT);
SQL_CASE(SQL_ATTR_ASYNC_ENABLE);
SQL_CASE(SQL_ATTR_AUTO_IPD);
SQL_CASE(SQL_ATTR_AUTOCOMMIT);
SQL_CASE(SQL_ATTR_CONNECTION_DEAD);
SQL_CASE(SQL_ATTR_CONNECTION_TIMEOUT);
SQL_CASE(SQL_ATTR_CURRENT_CATALOG);
// ODBC 3.8
// SQL_CASE(SQL_ATTR_DBC_INFO_TOKEN);
SQL_CASE(SQL_ATTR_ENLIST_IN_DTC);
SQL_CASE(SQL_ATTR_LOGIN_TIMEOUT);
SQL_CASE(SQL_ATTR_METADATA_ID);
SQL_CASE(SQL_ATTR_ODBC_CURSORS);
SQL_CASE(SQL_ATTR_PACKET_SIZE);
SQL_CASE(SQL_ATTR_QUIET_MODE);
SQL_CASE(SQL_ATTR_TRACE);
SQL_CASE(SQL_ATTR_TRACEFILE);
SQL_CASE(SQL_ATTR_TRANSLATE_LIB);
SQL_CASE(SQL_ATTR_TRANSLATE_OPTION);
SQL_CASE(SQL_ATTR_TXN_ISOLATION);
default: return "UNKNOWN";
}
}
// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlgetinfo-function?view=sql-server-ver15
const char* sql_info_type(int type) {
switch(type) {
SQL_CASE(SQL_ACTIVE_ENVIRONMENTS);
SQL_CASE(SQL_ASYNC_DBC_FUNCTIONS);
SQL_CASE(SQL_ASYNC_MODE);
SQL_CASE(SQL_ASYNC_NOTIFICATION);
SQL_CASE(SQL_BATCH_ROW_COUNT);
SQL_CASE(SQL_BATCH_SUPPORT);
SQL_CASE(SQL_DATA_SOURCE_NAME);
SQL_CASE(SQL_DRIVER_AWARE_POOLING_SUPPORTED);
SQL_CASE(SQL_DRIVER_HDBC);
SQL_CASE(SQL_DRIVER_HDESC);
SQL_CASE(SQL_DRIVER_HENV);
SQL_CASE(SQL_DRIVER_HLIB);
SQL_CASE(SQL_DRIVER_HSTMT);
SQL_CASE(SQL_DRIVER_NAME);
SQL_CASE(SQL_DRIVER_ODBC_VER);
SQL_CASE(SQL_DRIVER_VER);
SQL_CASE(SQL_DYNAMIC_CURSOR_ATTRIBUTES1);
SQL_CASE(SQL_DYNAMIC_CURSOR_ATTRIBUTES2);
SQL_CASE(SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1);
SQL_CASE(SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2);
SQL_CASE(SQL_FILE_USAGE);
SQL_CASE(SQL_GETDATA_EXTENSIONS);
SQL_CASE(SQL_INFO_SCHEMA_VIEWS);
SQL_CASE(SQL_KEYSET_CURSOR_ATTRIBUTES1);
SQL_CASE(SQL_KEYSET_CURSOR_ATTRIBUTES2);
SQL_CASE(SQL_MAX_ASYNC_CONCURRENT_STATEMENTS);
SQL_CASE(SQL_MAX_CONCURRENT_ACTIVITIES);
SQL_CASE(SQL_MAX_DRIVER_CONNECTIONS);
SQL_CASE(SQL_ODBC_INTERFACE_CONFORMANCE);
// SQL_CASE(SQL_ODBC_STANDARD_CLI_CONFORMANCE);
SQL_CASE(SQL_ODBC_VER);
SQL_CASE(SQL_PARAM_ARRAY_ROW_COUNTS);
SQL_CASE(SQL_PARAM_ARRAY_SELECTS);
SQL_CASE(SQL_ROW_UPDATES);
SQL_CASE(SQL_SEARCH_PATTERN_ESCAPE);
SQL_CASE(SQL_SERVER_NAME);
SQL_CASE(SQL_STATIC_CURSOR_ATTRIBUTES1);
SQL_CASE(SQL_STATIC_CURSOR_ATTRIBUTES2);
SQL_CASE(SQL_DATABASE_NAME);
SQL_CASE(SQL_DBMS_NAME);
SQL_CASE(SQL_DBMS_VER);
SQL_CASE(SQL_ACCESSIBLE_PROCEDURES);
SQL_CASE(SQL_ACCESSIBLE_TABLES);
SQL_CASE(SQL_BOOKMARK_PERSISTENCE);
SQL_CASE(SQL_CATALOG_TERM);
SQL_CASE(SQL_COLLATION_SEQ);
SQL_CASE(SQL_CONCAT_NULL_BEHAVIOR);
SQL_CASE(SQL_CURSOR_COMMIT_BEHAVIOR);
SQL_CASE(SQL_CURSOR_ROLLBACK_BEHAVIOR);
SQL_CASE(SQL_CURSOR_SENSITIVITY);
SQL_CASE(SQL_DATA_SOURCE_READ_ONLY);
SQL_CASE(SQL_DEFAULT_TXN_ISOLATION);
SQL_CASE(SQL_DESCRIBE_PARAMETER);
SQL_CASE(SQL_MULT_RESULT_SETS);
SQL_CASE(SQL_MULTIPLE_ACTIVE_TXN);
SQL_CASE(SQL_NEED_LONG_DATA_LEN);
SQL_CASE(SQL_NULL_COLLATION);
SQL_CASE(SQL_PROCEDURE_TERM);
SQL_CASE(SQL_SCHEMA_TERM);
SQL_CASE(SQL_SCROLL_OPTIONS);
SQL_CASE(SQL_TABLE_TERM);
SQL_CASE(SQL_TXN_CAPABLE);
SQL_CASE(SQL_TXN_ISOLATION_OPTION);
SQL_CASE(SQL_USER_NAME);
SQL_CASE(SQL_AGGREGATE_FUNCTIONS);
SQL_CASE(SQL_ALTER_DOMAIN);
// SQL_CASE(SQL_ALTER_SCHEMA);
SQL_CASE(SQL_ALTER_TABLE);
// SQL_CASE(SQL_ANSI_SQL_DATETIME_LITERALS);
SQL_CASE(SQL_CATALOG_LOCATION);
SQL_CASE(SQL_CATALOG_NAME);
SQL_CASE(SQL_CATALOG_NAME_SEPARATOR);
SQL_CASE(SQL_CATALOG_USAGE);
SQL_CASE(SQL_COLUMN_ALIAS);
SQL_CASE(SQL_CORRELATION_NAME);
SQL_CASE(SQL_CREATE_ASSERTION);
SQL_CASE(SQL_CREATE_CHARACTER_SET);
SQL_CASE(SQL_CREATE_COLLATION);
SQL_CASE(SQL_CREATE_DOMAIN);
SQL_CASE(SQL_CREATE_SCHEMA);
SQL_CASE(SQL_CREATE_TABLE);
SQL_CASE(SQL_CREATE_TRANSLATION);
SQL_CASE(SQL_DDL_INDEX);
SQL_CASE(SQL_DROP_ASSERTION);
SQL_CASE(SQL_DROP_CHARACTER_SET);
SQL_CASE(SQL_DROP_COLLATION);
SQL_CASE(SQL_DROP_DOMAIN);
SQL_CASE(SQL_DROP_SCHEMA);
SQL_CASE(SQL_DROP_TABLE);
SQL_CASE(SQL_DROP_TRANSLATION);
SQL_CASE(SQL_DROP_VIEW);
SQL_CASE(SQL_EXPRESSIONS_IN_ORDERBY);
SQL_CASE(SQL_GROUP_BY);
SQL_CASE(SQL_IDENTIFIER_CASE);
SQL_CASE(SQL_IDENTIFIER_QUOTE_CHAR);
SQL_CASE(SQL_INDEX_KEYWORDS);
SQL_CASE(SQL_INSERT_STATEMENT);
SQL_CASE(SQL_INTEGRITY);
SQL_CASE(SQL_KEYWORDS);
SQL_CASE(SQL_LIKE_ESCAPE_CLAUSE);
SQL_CASE(SQL_NON_NULLABLE_COLUMNS);
SQL_CASE(SQL_OJ_CAPABILITIES);
SQL_CASE(SQL_ORDER_BY_COLUMNS_IN_SELECT);
SQL_CASE(SQL_OUTER_JOINS);
SQL_CASE(SQL_PROCEDURES);
SQL_CASE(SQL_QUOTED_IDENTIFIER_CASE);
SQL_CASE(SQL_SCHEMA_USAGE);
SQL_CASE(SQL_SPECIAL_CHARACTERS);
SQL_CASE(SQL_SQL_CONFORMANCE);
SQL_CASE(SQL_SUBQUERIES);
SQL_CASE(SQL_UNION);
SQL_CASE(SQL_MAX_BINARY_LITERAL_LEN);
SQL_CASE(SQL_MAX_CATALOG_NAME_LEN);
SQL_CASE(SQL_MAX_CHAR_LITERAL_LEN);
SQL_CASE(SQL_MAX_COLUMN_NAME_LEN);
SQL_CASE(SQL_MAX_COLUMNS_IN_GROUP_BY);
SQL_CASE(SQL_MAX_COLUMNS_IN_INDEX);
SQL_CASE(SQL_MAX_COLUMNS_IN_ORDER_BY);
SQL_CASE(SQL_MAX_COLUMNS_IN_SELECT);
SQL_CASE(SQL_MAX_COLUMNS_IN_TABLE);
SQL_CASE(SQL_MAX_CURSOR_NAME_LEN);
SQL_CASE(SQL_MAX_IDENTIFIER_LEN);
SQL_CASE(SQL_MAX_INDEX_SIZE);
SQL_CASE(SQL_MAX_PROCEDURE_NAME_LEN);
SQL_CASE(SQL_MAX_ROW_SIZE);
SQL_CASE(SQL_MAX_ROW_SIZE_INCLUDES_LONG);
SQL_CASE(SQL_MAX_SCHEMA_NAME_LEN);
SQL_CASE(SQL_MAX_STATEMENT_LEN);
SQL_CASE(SQL_MAX_TABLE_NAME_LEN);
SQL_CASE(SQL_MAX_TABLES_IN_SELECT);
SQL_CASE(SQL_MAX_USER_NAME_LEN);
SQL_CASE(SQL_CONVERT_FUNCTIONS);
SQL_CASE(SQL_NUMERIC_FUNCTIONS);
SQL_CASE(SQL_STRING_FUNCTIONS);
SQL_CASE(SQL_SYSTEM_FUNCTIONS);
SQL_CASE(SQL_TIMEDATE_ADD_INTERVALS);
SQL_CASE(SQL_TIMEDATE_DIFF_INTERVALS);
SQL_CASE(SQL_TIMEDATE_FUNCTIONS);
SQL_CASE(SQL_CONVERT_BIGINT);
SQL_CASE(SQL_CONVERT_BINARY);
SQL_CASE(SQL_CONVERT_BIT);
SQL_CASE(SQL_CONVERT_CHAR);
SQL_CASE(SQL_CONVERT_DATE);
SQL_CASE(SQL_CONVERT_DECIMAL);
SQL_CASE(SQL_CONVERT_DOUBLE);
SQL_CASE(SQL_CONVERT_FLOAT);
SQL_CASE(SQL_CONVERT_INTEGER);
SQL_CASE(SQL_CONVERT_INTERVAL_DAY_TIME);
SQL_CASE(SQL_CONVERT_INTERVAL_YEAR_MONTH);
SQL_CASE(SQL_CONVERT_LONGVARBINARY);
SQL_CASE(SQL_CONVERT_LONGVARCHAR);
SQL_CASE(SQL_CONVERT_NUMERIC);
SQL_CASE(SQL_CONVERT_REAL);
SQL_CASE(SQL_CONVERT_SMALLINT);
SQL_CASE(SQL_CONVERT_TIME);
SQL_CASE(SQL_CONVERT_TIMESTAMP);
SQL_CASE(SQL_CONVERT_TINYINT);
SQL_CASE(SQL_CONVERT_VARBINARY);
SQL_CASE(SQL_CONVERT_VARCHAR);
SQL_CASE(SQL_DM_VER);
SQL_CASE(SQL_XOPEN_CLI_YEAR);
SQL_CASE(SQL_DTC_TRANSITION_COST);
default: return "UNKNOWN";
}
}
// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlcolattribute-function?view=sql-server-ver15
const char* sql_field_identifier(int type) {
switch (type) {
SQL_CASE(SQL_DESC_AUTO_UNIQUE_VALUE);
SQL_CASE(SQL_DESC_BASE_COLUMN_NAME);
SQL_CASE(SQL_DESC_BASE_TABLE_NAME);
SQL_CASE(SQL_DESC_CASE_SENSITIVE);
SQL_CASE(SQL_DESC_CATALOG_NAME);
SQL_CASE(SQL_DESC_CONCISE_TYPE);
SQL_CASE(SQL_DESC_COUNT);
SQL_CASE(SQL_DESC_DISPLAY_SIZE);
SQL_CASE(SQL_DESC_FIXED_PREC_SCALE);
SQL_CASE(SQL_DESC_LABEL);
SQL_CASE(SQL_DESC_LENGTH);
SQL_CASE(SQL_DESC_LITERAL_PREFIX);
SQL_CASE(SQL_DESC_LITERAL_SUFFIX);
SQL_CASE(SQL_DESC_LOCAL_TYPE_NAME);
SQL_CASE(SQL_DESC_NAME);
SQL_CASE(SQL_DESC_NULLABLE);
SQL_CASE(SQL_DESC_NUM_PREC_RADIX);
SQL_CASE(SQL_DESC_OCTET_LENGTH);
SQL_CASE(SQL_DESC_PRECISION);
SQL_CASE(SQL_DESC_SCALE);
SQL_CASE(SQL_DESC_SCHEMA_NAME);
SQL_CASE(SQL_DESC_SEARCHABLE);
SQL_CASE(SQL_DESC_TABLE_NAME);
SQL_CASE(SQL_DESC_TYPE);
SQL_CASE(SQL_DESC_TYPE_NAME);
SQL_CASE(SQL_DESC_UNNAMED);
SQL_CASE(SQL_DESC_UNSIGNED);
SQL_CASE(SQL_DESC_UPDATABLE);
default: return "UNKNOWN";
}
}
// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlbindparameter-function?view=sql-server-ver15
const char* sql_input_output_type(int type) {
switch (type) {
SQL_CASE(SQL_PARAM_INPUT);
SQL_CASE(SQL_PARAM_OUTPUT);
SQL_CASE(SQL_PARAM_OUTPUT_STREAM);
SQL_CASE(SQL_PARAM_INPUT_OUTPUT);
SQL_CASE(SQL_PARAM_INPUT_OUTPUT_STREAM);
default: return "UNKNOWN";
}
}
// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetstmtattr-function?view=sql-server-ver15
const char* sql_stmt_attr_type(int type) {
switch (type) {
SQL_CASE(SQL_ATTR_APP_PARAM_DESC);
SQL_CASE(SQL_ATTR_APP_ROW_DESC);
SQL_CASE(SQL_ATTR_ASYNC_ENABLE);
SQL_CASE(SQL_ATTR_ASYNC_STMT_EVENT);
// ODBC 3.8
// SQL_CASE(SQL_ATTR_ASYNC_STMT_PCALLBACK);
// ODBC 3.8
// SQL_CASE(SQL_ATTR_ASYNC_STMT_PCONTEXT);
SQL_CASE(SQL_ATTR_CONCURRENCY);
SQL_CASE(SQL_ATTR_CURSOR_SCROLLABLE);
SQL_CASE(SQL_ATTR_CURSOR_SENSITIVITY);
SQL_CASE(SQL_ATTR_CURSOR_TYPE);
SQL_CASE(SQL_ATTR_ENABLE_AUTO_IPD);
SQL_CASE(SQL_ATTR_FETCH_BOOKMARK_PTR);
SQL_CASE(SQL_ATTR_IMP_PARAM_DESC);
SQL_CASE(SQL_ATTR_IMP_ROW_DESC);
SQL_CASE(SQL_ATTR_KEYSET_SIZE);
SQL_CASE(SQL_ATTR_MAX_LENGTH);
SQL_CASE(SQL_ATTR_MAX_ROWS);
SQL_CASE(SQL_ATTR_METADATA_ID);
SQL_CASE(SQL_ATTR_NOSCAN);
SQL_CASE(SQL_ATTR_PARAM_BIND_OFFSET_PTR);
SQL_CASE(SQL_ATTR_PARAM_BIND_TYPE);
SQL_CASE(SQL_ATTR_PARAM_OPERATION_PTR);
SQL_CASE(SQL_ATTR_PARAM_STATUS_PTR);
SQL_CASE(SQL_ATTR_PARAMS_PROCESSED_PTR);
SQL_CASE(SQL_ATTR_PARAMSET_SIZE);
SQL_CASE(SQL_ATTR_QUERY_TIMEOUT);
SQL_CASE(SQL_ATTR_RETRIEVE_DATA);
SQL_CASE(SQL_ATTR_ROW_ARRAY_SIZE);
SQL_CASE(SQL_ATTR_ROW_BIND_OFFSET_PTR);
SQL_CASE(SQL_ATTR_ROW_BIND_TYPE);
SQL_CASE(SQL_ATTR_ROW_NUMBER);
SQL_CASE(SQL_ATTR_ROW_OPERATION_PTR);
SQL_CASE(SQL_ATTR_ROW_STATUS_PTR);
SQL_CASE(SQL_ATTR_ROWS_FETCHED_PTR);
SQL_CASE(SQL_ATTR_SIMULATE_CURSOR);
SQL_CASE(SQL_ATTR_USE_BOOKMARKS);
default: return "UNKNOWN";
}
}
const char* sql_function_type(int type) {
switch (type) {
//
SQL_CASE(SQL_API_ALL_FUNCTIONS);
SQL_CASE(SQL_API_ODBC3_ALL_FUNCTIONS);
// ISO 92 standards-compliance
SQL_CASE(SQL_API_SQLALLOCHANDLE);
SQL_CASE(SQL_API_SQLGETDESCFIELD);
SQL_CASE(SQL_API_SQLBINDCOL);
SQL_CASE(SQL_API_SQLGETDESCREC);
SQL_CASE(SQL_API_SQLCANCEL);
SQL_CASE(SQL_API_SQLGETDIAGFIELD);
SQL_CASE(SQL_API_SQLCLOSECURSOR);
SQL_CASE(SQL_API_SQLGETDIAGREC);
SQL_CASE(SQL_API_SQLCOLATTRIBUTE);
SQL_CASE(SQL_API_SQLGETENVATTR);
SQL_CASE(SQL_API_SQLCONNECT);
SQL_CASE(SQL_API_SQLGETFUNCTIONS);
SQL_CASE(SQL_API_SQLCOPYDESC);
SQL_CASE(SQL_API_SQLGETINFO);
SQL_CASE(SQL_API_SQLDATASOURCES);
SQL_CASE(SQL_API_SQLGETSTMTATTR);
SQL_CASE(SQL_API_SQLDESCRIBECOL);
SQL_CASE(SQL_API_SQLGETTYPEINFO);
SQL_CASE(SQL_API_SQLDISCONNECT);
SQL_CASE(SQL_API_SQLNUMRESULTCOLS);
SQL_CASE(SQL_API_SQLDRIVERS);
SQL_CASE(SQL_API_SQLPARAMDATA);
SQL_CASE(SQL_API_SQLENDTRAN);
SQL_CASE(SQL_API_SQLPREPARE);
SQL_CASE(SQL_API_SQLEXECDIRECT);
SQL_CASE(SQL_API_SQLPUTDATA);
SQL_CASE(SQL_API_SQLEXECUTE);
SQL_CASE(SQL_API_SQLROWCOUNT);
SQL_CASE(SQL_API_SQLFETCH);
SQL_CASE(SQL_API_SQLSETCONNECTATTR);
SQL_CASE(SQL_API_SQLFETCHSCROLL);
SQL_CASE(SQL_API_SQLSETCURSORNAME);
SQL_CASE(SQL_API_SQLFREEHANDLE);
SQL_CASE(SQL_API_SQLSETDESCFIELD);
SQL_CASE(SQL_API_SQLFREESTMT);
SQL_CASE(SQL_API_SQLSETDESCREC);
SQL_CASE(SQL_API_SQLGETCONNECTATTR);
SQL_CASE(SQL_API_SQLSETENVATTR);
SQL_CASE(SQL_API_SQLGETCURSORNAME);
SQL_CASE(SQL_API_SQLSETSTMTATTR);
SQL_CASE(SQL_API_SQLGETDATA);
// Open Group standards-compliance);
SQL_CASE(SQL_API_SQLCOLUMNS);
SQL_CASE(SQL_API_SQLSTATISTICS);
SQL_CASE(SQL_API_SQLSPECIALCOLUMNS);
SQL_CASE(SQL_API_SQLTABLES);
// ODBC standards-compliance);
SQL_CASE(SQL_API_SQLBINDPARAMETER);
SQL_CASE(SQL_API_SQLNATIVESQL);
SQL_CASE(SQL_API_SQLBROWSECONNECT);
SQL_CASE(SQL_API_SQLNUMPARAMS);
SQL_CASE(SQL_API_SQLBULKOPERATIONS);
SQL_CASE(SQL_API_SQLPRIMARYKEYS);
SQL_CASE(SQL_API_SQLCOLUMNPRIVILEGES);
SQL_CASE(SQL_API_SQLPROCEDURECOLUMNS);
SQL_CASE(SQL_API_SQLDESCRIBEPARAM);
SQL_CASE(SQL_API_SQLPROCEDURES);
SQL_CASE(SQL_API_SQLDRIVERCONNECT);
SQL_CASE(SQL_API_SQLSETPOS);
SQL_CASE(SQL_API_SQLFOREIGNKEYS);
SQL_CASE(SQL_API_SQLTABLEPRIVILEGES);
SQL_CASE(SQL_API_SQLMORERESULTS);
SQL_CASE(SQL_API_SQLALLOCCONNECT);
SQL_CASE(SQL_API_SQLALLOCENV);
SQL_CASE(SQL_API_SQLALLOCSTMT);
SQL_CASE(SQL_API_SQLBINDPARAM);
SQL_CASE(SQL_API_SQLERROR);
SQL_CASE(SQL_API_SQLFREECONNECT);
SQL_CASE(SQL_API_SQLFREEENV);
SQL_CASE(SQL_API_SQLGETCONNECTOPTION);
SQL_CASE(SQL_API_SQLGETSTMTOPTION);
SQL_CASE(SQL_API_SQLSETCONNECTOPTION);
SQL_CASE(SQL_API_SQLSETPARAM);
SQL_CASE(SQL_API_SQLSETSTMTOPTION);
SQL_CASE(SQL_API_SQLTRANSACT);
SQL_CASE(SQL_API_SQLCANCELHANDLE);
default: return "UNKNOWN";
}
}
const char* sql_freestmt_option_type(int type) {
switch (type) {
SQL_CASE(SQL_CLOSE);
SQL_CASE(SQL_DROP);
SQL_CASE(SQL_UNBIND);
SQL_CASE(SQL_RESET_PARAMS);
default: return "UNKNOWN";
}
}
const char* sql_soi_type(int soi) {
switch (soi) {
SQL_CASE(SQL_NTS);
SQL_CASE(SQL_NULL_DATA);
SQL_CASE(SQL_DEFAULT_PARAM);
SQL_CASE(SQL_DATA_AT_EXEC);
default: {
if (soi >= 0) return "";
return "SQL_LEN_DATA_AT_EXEC(?)";
} break;
}
}
const char* sql_nullable_type(int type) {
switch (type) {
SQL_CASE(SQL_NO_NULLS);
SQL_CASE(SQL_NULLABLE);
SQL_CASE(SQL_NULLABLE_UNKNOWN);
default: {
return "UNKNOWN";
} break;
}
}
int is_valid_sql_c_type(int type) {
const char *ctype = sql_c_type(type);
if (strcmp(ctype, "UNKNOWN")==0) return 0;
@ -127,3 +617,46 @@ int utf8_chars(const char *src)
return (int)chars;
}
static int do_charset_chars(iconv_t cnv, const unsigned char *src)
{
int chars = 0;
char *ps = (char*)src;
char buf[16];
size_t sn = 1;
while (1) {
char *ds = buf;
size_t dn = sizeof(buf);
size_t n = iconv(cnv, &ps, &sn, &ds, &dn);
if (n==(size_t)-1) {
int e = errno;
switch (e) {
case EILSEQ: return -1;
case E2BIG: return -1;
case EINVAL: sn += 1; continue;
default: return -1;
}
}
if (sn) return -1;
if (n>0) return -1;
int i=0;
for (i=0; i<(sizeof(buf)-dn); ++i) {
if (buf[i]) break;
}
if (i>=(sizeof(buf)-dn)) break;
chars += (int)1;
sn = 1;
}
return chars;
}
int charset_chars(const char *charset, const unsigned char *src) {
iconv_t cnv = iconv_open(charset, charset);
if (cnv==(iconv_t)-1) return -1;
int chars = do_charset_chars(cnv, src);
iconv_close(cnv);
return chars;
}

View File

@ -23,11 +23,24 @@
const char* sql_sql_type(int type);
const char* sql_c_type(int type);
const char* sql_handle_type(int type);
const char* sql_env_attr_type(int type);
const char* sql_conn_attr_type(int type);
const char* sql_info_type(int type);
const char* sql_field_identifier(int type);
const char* sql_diag_identifier(int type);
const char* sql_input_output_type(int type);
const char* sql_stmt_attr_type(int type);
const char* sql_function_type(int type);
const char* sql_freestmt_option_type(int type);
const char* sql_soi_type(int soi);
const char* sql_nullable_type(int type);
int is_valid_sql_c_type(int type);
int is_valid_sql_sql_type(int type);
int utf8_chars(const char *src);
int charset_chars(const char *charset, const unsigned char *src);
#endif // _TODBC_UTIL_H_

View File

@ -1,18 +0,0 @@
PROJECT(TDengine)
IF (TD_LINUX)
# AUX_SOURCE_DIRECTORY(. SRC)
ADD_EXECUTABLE(tcodbc main.c)
TARGET_LINK_LIBRARIES(tcodbc odbc)
ADD_EXECUTABLE(tconv tconv.c)
ENDIF ()
IF (TD_WINDOWS_64)
SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /GL")
SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /GL")
# AUX_SOURCE_DIRECTORY(. SRC)
ADD_EXECUTABLE(tcodbc main.c)
TARGET_LINK_LIBRARIES(tcodbc odbc32 odbccp32 user32 legacy_stdio_definitions os)
ADD_EXECUTABLE(tconv tconv.c)
TARGET_LINK_LIBRARIES(tconv tutil)
ENDIF ()

View File

@ -1,673 +0,0 @@
#include "../src/todbc_log.h"
#ifdef _MSC_VER
#include <winsock2.h>
#include <windows.h>
#include "os.h"
#endif
#include <sql.h>
#include <sqlext.h>
#include <odbcinst.h>
#include <stdio.h>
#include <string.h>
#define CHK_TEST(statement) \
do { \
D("testing: %s", #statement); \
int r = (statement); \
if (r) { \
D("testing failed: %s", #statement); \
return 1; \
} \
} while (0);
typedef struct db_column_s db_column_t;
struct db_column_s {
SQLSMALLINT nameLength;
char name[4096]; // seems enough
SQLSMALLINT dataType;
SQLULEN columnSize;
SQLSMALLINT decimalDigits;
SQLSMALLINT nullable;
};
static db_column_t *columns = NULL;
typedef struct data_s data_t;
struct data_s {
int64_t ts;
int8_t b;
int8_t v1;
int16_t v2;
int32_t v4;
int64_t v8;
float f4;
double f8;
char bin[40+1];
char blob[40+1]; // why 80? ref: tests/examples/c/apitest.c
};
static const char *pre_stmts[] = {
"create database db",
"use db",
"create table t (ts timestamp, b bool, v1 tinyint, v2 smallint, v4 int, v8 bigint, f4 float, f8 double, bin binary(40), blob nchar(10))"
};
static const char *pro_stmts[] = {
// "insert into t values ('2019-07-15 00:00:00', 1)",
// "insert into t values ('2019-07-15 01:00:00', 2)",
"select * from t"
// "drop database db"
};
#define CHK_RESULT(r, ht, h, fmt, ...) \
do { \
if (r==0) break; \
SQLCHAR ss[10]; \
SQLINTEGER ne = 0; \
SQLCHAR es[4096]; \
SQLSMALLINT n = 0; \
ss[0] = '\0'; \
es[0] = '\0'; \
SQLRETURN ret = SQLGetDiagRec(ht, h, 1, ss, &ne, es, sizeof(es), &n); \
if (ret) break; \
D("[%s]%s: " fmt "", ss, es, ##__VA_ARGS__); \
} while (0)
static int open_connect(const char *dsn, const char *uid, const char *pwd, SQLHENV *pEnv, SQLHDBC *pConn) {
SQLRETURN r;
SQLHENV env = {0};
SQLHDBC conn = {0};
r = SQLAllocEnv(&env);
if (r!=SQL_SUCCESS) return 1;
do {
r = SQLAllocConnect(env, &conn);
CHK_RESULT(r, SQL_HANDLE_ENV, env, "");
if (r!=SQL_SUCCESS) break;
do {
r = SQLConnect(conn, (SQLCHAR*)dsn, (SQLSMALLINT)(dsn ? strlen(dsn) : 0),
(SQLCHAR*)uid, (SQLSMALLINT)(uid ? strlen(uid) : 0),
(SQLCHAR*)pwd, (SQLSMALLINT)(pwd ? strlen(pwd) : 0));
CHK_RESULT(r, SQL_HANDLE_DBC, conn, "");
if (r==SQL_SUCCESS) {
*pEnv = env;
*pConn = conn;
return 0;
}
} while (0);
SQLFreeConnect(conn);
} while (0);
SQLFreeEnv(env);
return 1;
}
static int open_driver_connect(const char *connstr, SQLHENV *pEnv, SQLHDBC *pConn) {
SQLRETURN r;
SQLHENV env = {0};
SQLHDBC conn = {0};
r = SQLAllocEnv(&env);
if (r!=SQL_SUCCESS) return 1;
do {
r = SQLAllocConnect(env, &conn);
CHK_RESULT(r, SQL_HANDLE_ENV, env, "");
if (r!=SQL_SUCCESS) break;
do {
SQLCHAR buf[4096];
SQLSMALLINT blen = 0;
SQLHDBC ConnectionHandle = conn;
SQLHWND WindowHandle = NULL;
SQLCHAR * InConnectionString = (SQLCHAR*)connstr;
SQLSMALLINT StringLength1 = (SQLSMALLINT)(connstr ? strlen(connstr) : 0);
SQLCHAR * OutConnectionString = buf;
SQLSMALLINT BufferLength = sizeof(buf);
SQLSMALLINT * StringLength2Ptr = &blen;
SQLUSMALLINT DriverCompletion = SQL_DRIVER_NOPROMPT;
r = SQLDriverConnect(ConnectionHandle, WindowHandle, InConnectionString,
StringLength1, OutConnectionString, BufferLength,
StringLength2Ptr, DriverCompletion);
CHK_RESULT(r, SQL_HANDLE_DBC, conn, "");
if (r==SQL_SUCCESS) {
*pEnv = env;
*pConn = conn;
return 0;
}
} while (0);
SQLFreeConnect(conn);
} while (0);
SQLFreeEnv(env);
return 1;
}
static SQLRETURN traverse_cols(SQLHSTMT stmt, SQLSMALLINT cols) {
SQLRETURN r = SQL_ERROR;
for (SQLSMALLINT i=0; i<cols; ++i) {
db_column_t column = {0};
r = SQLDescribeCol(stmt, (SQLUSMALLINT)(i+1), (SQLCHAR*)column.name,
(SQLSMALLINT)sizeof(column.name), &column.nameLength,
&column.dataType, &column.columnSize,
&column.decimalDigits, &column.nullable);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
D("col%02d:[%s]%d,type:[%d],colSize:[%"PRId64"],decimalDigits:[%d],nullable:[%d]",
i+1, column.name, column.nameLength, column.dataType, column.columnSize,
column.decimalDigits, column.nullable);
db_column_t *col = (db_column_t*)realloc(columns, (size_t)(i+1)*sizeof(*col));
if (!col) {
D("out of memory");
return SQL_ERROR;
}
col[i] = column;
columns = col;
}
return SQL_SUCCESS;
}
static int do_statement(SQLHSTMT stmt, const char *statement) {
SQLRETURN r = 0;
do {
r = SQLExecDirect(stmt, (SQLCHAR*)statement, SQL_NTS);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: [%s]", statement);
if (r) break;
SQLSMALLINT cols = 0;
r = SQLNumResultCols(stmt, &cols);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) break;
if (cols <= 0) break;
r = traverse_cols(stmt, cols);
char buf[4096];
while (1) {
SQLRETURN r = SQLFetch(stmt);
if (r==SQL_NO_DATA) break;
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
for (size_t i=0; i<cols; ++i) {
SQLLEN soi = 0;
r = SQLGetData(stmt, (SQLUSMALLINT)(i+1), SQL_C_CHAR, buf, sizeof(buf), &soi);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
if (r) {
if (r!=SQL_SUCCESS_WITH_INFO) {
if (i>0) fprintf(stdout, "\n");
return r;
}
}
if (soi==SQL_NULL_DATA) {
fprintf(stdout, "%snull", i==0?"":",");
} else {
fprintf(stdout, "%s\"%s\"", i==0?"":",", buf);
}
}
fprintf(stdout, "\n");
}
// r = SQLFetch(stmt);
// if (r==SQL_NO_DATA) {
// D("..........");
// r = SQL_SUCCESS;
// break;
// }
// CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
// if (r) break;
// r = SQLPrepare(stmt, (SQLCHAR*)statement, strlen(statement));
// CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "");
// if (r) break;
// r = SQLExecute(stmt);
// CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: %s", statement);
// if (r) break;
} while (0);
return r;
}
static int do_insert(SQLHSTMT stmt, data_t data) {
SQLRETURN r = 0;
SQLLEN lbin;
SQLLEN lblob;
const char *statement = "insert into t values (?, ?, ?, ?, ?, ?, ?, ?, ?,?)";
#define ignored 0
do {
r = SQLPrepare(stmt, (SQLCHAR*)statement, (SQLINTEGER)strlen(statement));
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: %s", statement);
if (r) break;
r = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_TIMESTAMP, ignored, ignored, &data.ts, ignored, NULL);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: %s", statement);
if (r) break;
r = SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_BIT, SQL_BIT, ignored, ignored, &data.b, ignored, NULL);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: %s", statement);
if (r) break;
r = SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_TINYINT, SQL_TINYINT, ignored, ignored, &data.v1, ignored, NULL);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: %s", statement);
if (r) break;
r = SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_SHORT, SQL_SMALLINT, ignored, ignored, &data.v2, ignored, NULL);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: %s", statement);
if (r) break;
r = SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, ignored, ignored, &data.v4, ignored, NULL);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: %s", statement);
if (r) break;
r = SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, ignored, ignored, &data.v8, ignored, NULL);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: %s", statement);
if (r) break;
r = SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_FLOAT, SQL_FLOAT, ignored, ignored, &data.f4, ignored, NULL);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: %s", statement);
if (r) break;
r = SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_DOUBLE, ignored, ignored, &data.f8, ignored, NULL);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: %s", statement);
if (r) break;
lbin = SQL_NTS;
r = SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_VARBINARY, sizeof(data.bin)-1, ignored, &data.bin, ignored, &lbin);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: %s", statement);
if (r) break;
lblob = SQL_NTS;
r = SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, sizeof(data.blob)-1, ignored, &data.blob, ignored, &lblob);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: %s", statement);
if (r) break;
r = SQLExecute(stmt);
CHK_RESULT(r, SQL_HANDLE_STMT, stmt, "statement: %s", statement);
if (r) break;
// ts += 1;
// v = 2;
// r = SQLExecute(stmt);
// if (r) break;
} while (0);
#undef ignored
return r;
}
static int test1(const char *dsn, const char *uid, const char *pwd) {
SQLHENV env = {0};
SQLHDBC conn = {0};
int n = open_connect(dsn, uid, pwd, &env, &conn);
if (n) return 1;
int ok = 0;
do {
SQLRETURN r = SQL_SUCCESS;
SQLHSTMT stmt = {0};
r = SQLAllocHandle(SQL_HANDLE_STMT, conn, &stmt);
if (r!=SQL_SUCCESS) break;
do {
if (do_statement(stmt, "drop database if exists db")) {
break;
}
for (size_t i=0; i<sizeof(pre_stmts)/sizeof(pre_stmts[0]); ++i) {
n = do_statement(stmt, pre_stmts[i]);
if (n) break;
}
do {
data_t data = {0};
data.ts = 1591060628001;
data.b = 1;
data.v1 = 127;
data.v2 = 32767;
data.v4 = 2147483647;
data.v8 = 9223372036854775807;
data.f4 = 123.456f;
data.f8 = 9999999.999999;
memset(data.bin, 0, sizeof(data.bin));
memset(data.blob, 0, sizeof(data.blob));
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};
r = SQLAllocHandle(SQL_HANDLE_STMT, conn, &stmt);
if (r!=SQL_SUCCESS) break;
do {
n = do_insert(stmt, data);
if (n) break;
} while (0);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
// r = SQLAllocHandle(SQL_HANDLE_STMT, conn, &stmt);
// if (r!=SQL_SUCCESS) break;
// do {
// r = do_insert(stmt, ts++, v++);
// if (r!=SQL_SUCCESS) break;
// } while (0);
// SQLFreeHandle(SQL_HANDLE_STMT, stmt);
ok = 1;
} while (0);
if (!ok) break;
ok = 0;
for (size_t i=0; i<sizeof(pro_stmts)/sizeof(pro_stmts[0]); ++i) {
n = do_statement(stmt, pro_stmts[i]);
if (n) break;
}
ok = 1;
} while (0);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
} while (0);
SQLDisconnect(conn);
SQLFreeConnect(conn);
SQLFreeEnv(env);
return ok ? 0 : 1;
}
int test_statements(const char *dsn, const char *uid, const char *pwd, const char **statements) {
SQLRETURN r = SQL_SUCCESS;
SQLHENV env = {0};
SQLHDBC conn = {0};
int n = open_connect(dsn, uid, pwd, &env, &conn);
if (n) return 1;
do {
SQLHSTMT stmt = {0};
r = SQLAllocHandle(SQL_HANDLE_STMT, conn, &stmt);
if (r!=SQL_SUCCESS) break;
const char **p = statements;
while (*p) {
if (do_statement(stmt, *p)) {
r = SQL_ERROR;
break;
}
++p;
}
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
} while (0);
SQLDisconnect(conn);
SQLFreeConnect(conn);
SQLFreeEnv(env);
return r ? 1 : 0;
}
int test_driver_connect(const char *connstr) {
SQLRETURN r = SQL_SUCCESS;
SQLHENV env = {0};
SQLHDBC conn = {0};
int n = open_driver_connect(connstr, &env, &conn);
if (n) return 1;
SQLDisconnect(conn);
SQLFreeConnect(conn);
SQLFreeEnv(env);
return r ? 1 : 0;
}
int create_statement(SQLHENV env, SQLHDBC conn, SQLHSTMT *pStmt) {
SQLHSTMT stmt = {0};
SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, conn, &stmt);
CHK_RESULT(r, SQL_HANDLE_DBC, conn, "");
if (r==SQL_SUCCESS) {
*pStmt = stmt;
return 0;
}
if (r==SQL_SUCCESS_WITH_INFO) {
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
}
return 1;
}
int do_statements(SQLHSTMT stmt, const char **statements) {
const char **p = statements;
while (p && *p) {
CHK_TEST(do_statement(stmt, *p));
++p;
}
return 0;
}
int tests_stmt(SQLHENV env, SQLHDBC conn, SQLHSTMT stmt) {
const char *statements[] = {
"drop database if exists m",
"create database m",
"use m",
// "create table t (ts timestamp, b bool, v1 tinyint, v2 smallint, v4 int, v8 bigint, f4 float, f8 double, blob binary(1), name nchar(1))",
"create table t (ts timestamp, b bool)",
"insert into t values('2020-10-10 00:00:00', 0)",
"insert into t values('2020-10-10 00:00:00.001', 1)",
NULL
};
CHK_TEST(do_statements(stmt, statements));
return 0;
}
int tests(SQLHENV env, SQLHDBC conn) {
SQLHSTMT stmt = {0};
CHK_TEST(create_statement(env, conn, &stmt));
int r = tests_stmt(env, conn, stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return r ? 1 : 0;
}
int test_env(void) {
SQLRETURN r;
SQLHENV env = {0};
r = SQLAllocEnv(&env);
if (r!=SQL_SUCCESS) return 1;
SQLFreeEnv(env);
return 0;
}
int test_sqls_in_stmt(SQLHENV env, SQLHDBC conn, SQLHSTMT stmt, const char *sqls) {
FILE *f = fopen(sqls, "rb");
if (!f) {
D("failed to open file [%s]", sqls);
return -1;
}
int r = 0;
while (!feof(f)) {
char *line = NULL;
size_t len = 0;
ssize_t n = 0;
#ifdef _MSC_VER
n = taosGetline(&line, &len, f);
#else
n = getline(&line, &len, f);
#endif
if (n==-1) break;
const char *p = NULL;
do {
if (line[0] == '#') break;
if (n>0 && line[n-1] == '\n') line[n-1]='\0';
if (n>0 && line[n-1] == '\r') line[n-1]='\0';
if (n>1 && line[n-2] == '\r') line[n-2]='\0';
p = line;
while (isspace(*p)) ++p;
if (*p==0) break;
int positive = 1;
if (strncmp(p, "N:", 2)==0) {
positive = 0;
p += 2;
} else if (strncmp(p, "P:", 2)==0) {
p += 2;
}
D("statement: [%s]", p);
r = do_statement(stmt, p);
if (positive && r==0) break;
if (!positive && r) { r = 0; break; }
if (positive) return r;
D("expecting negative result, but got positive");
return -1;
} while (0);
free(line);
if (r) break;
}
fclose(f);
return r ? 1 : 0;
}
int test_sqls_in_conn(SQLHENV env, SQLHDBC conn, const char *sqls) {
SQLHSTMT stmt = {0};
CHK_TEST(create_statement(env, conn, &stmt));
int r = test_sqls_in_stmt(env, conn, stmt, sqls);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return r ? 1 : 0;
}
int test_sqls(const char *dsn, const char *uid, const char *pwd, const char *connstr, const char *sqls) {
int r = 0;
SQLHENV env = {0};
SQLHDBC conn = {0};
if (dsn) {
CHK_TEST(open_connect(dsn, uid, pwd, &env, &conn));
} else {
CHK_TEST(open_driver_connect(connstr, &env, &conn));
}
if (sqls) {
r = test_sqls_in_conn(env, conn, sqls);
}
SQLDisconnect(conn);
SQLFreeConnect(conn);
SQLFreeEnv(env);
return r ? 1 : 0;
}
void usage(const char *arg0) {
fprintf(stdout, "%s usage:\n", arg0);
fprintf(stdout, "%s [--dsn <dsn>] [--uid <uid>] [--pwd <pwd>] [--dcs <dcs>] [--sts <sts>]\n", arg0);
fprintf(stdout, " --dsn <dsn>: DSN\n");
fprintf(stdout, " --uid <uid>: UID\n");
fprintf(stdout, " --pwd <pwd>: PWD\n");
fprintf(stdout, " --dcs <dcs>: driver connection string\n");
fprintf(stdout, " --sts <sts>: file where statements store\n");
}
int main(int argc, char *argv[]) {
// if (argc==1) {
// CHK_TEST(test_env());
// CHK_TEST(test1("TAOS_DSN", "root", "taoxsdata"));
// D("Done!");
// return 0;
// }
const char *dsn = NULL;
const char *uid = NULL;
const char *pwd = NULL;
const char *dcs = NULL; // driver connection string
const char *sts = NULL; // statements file
for (size_t i=1; i<argc; ++i) {
const char *arg = argv[i];
if (strcmp(arg, "-h")==0) {
usage(argv[0]);
return 0;
}
if (strcmp(arg, "--dsn")==0) {
++i;
if (i>=argc) {
D("<dsn> expected but got nothing");
return 1;
}
if (dcs) {
D("--dcs has already been specified");
return 1;
}
dsn = argv[i];
continue;
}
if (strcmp(arg, "--uid")==0) {
++i;
if (i>=argc) {
D("<uid> expected but got nothing");
return 1;
}
uid = argv[i];
continue;
}
if (strcmp(arg, "--pwd")==0) {
++i;
if (i>=argc) {
D("<pwd> expected but got nothing");
return 1;
}
pwd = argv[i];
continue;
}
if (strcmp(arg, "--dcs")==0) {
++i;
if (i>=argc) {
D("<dcs> expected but got nothing");
return 1;
}
if (dsn || uid || pwd) {
D("either of --dsn/--uid/--pwd has already been specified");
return 1;
}
dcs = argv[i];
continue;
}
if (strcmp(arg, "--sts")==0) {
++i;
if (i>=argc) {
D("<sts> expected but got nothing");
return 1;
}
sts = argv[i];
continue;
}
}
CHK_TEST(test_sqls(dsn, uid, pwd, dcs, sts));
D("Done!");
return 0;
if (0) {
const char *dsn = (argc>1) ? argv[1] : NULL;
const char *uid = (argc>2) ? argv[2] : NULL;
const char *pwd = (argc>3) ? argv[3] : NULL;
const char *connstr = (argc>4) ? argv[4] : NULL;
const char *sqls = (argc>5) ? argv[5] : NULL;
dsn = NULL;
uid = NULL;
pwd = NULL;
connstr = argv[1];
sqls = argv[2];
if (0) {
CHK_TEST(test_env());
CHK_TEST(test1(dsn, uid, pwd));
const char *statements[] = {
"drop database if exists m",
"create database m",
"use m",
"drop database m",
NULL
};
CHK_TEST(test_statements(dsn, uid, pwd, statements));
if (connstr)
CHK_TEST(test_driver_connect(connstr));
if (connstr) {
SQLHENV env = {0};
SQLHDBC conn = {0};
CHK_TEST(open_driver_connect(connstr, &env, &conn));
int r = tests(env, conn);
SQLDisconnect(conn);
SQLFreeConnect(conn);
SQLFreeEnv(env);
if (r) return 1;
}
}
if ((dsn || connstr) && 1) {
CHK_TEST(test_sqls(dsn, uid, pwd, connstr, sqls));
}
D("Done!");
return 0;
}
}

View File

@ -1,131 +0,0 @@
import pyodbc
# cnxn = pyodbc.connect('DSN={TAOS_DSN};UID={ root };PWD={ taosdata };HOST={ localhost:6030 }', autocommit=True)
cnxn = pyodbc.connect('DSN={TAOS_DSN}; UID=root;PWD=taosdata; HOST=localhost:6030', autocommit=True)
cnxn.setdecoding(pyodbc.SQL_CHAR, encoding='utf-8')
#cnxn.setdecoding(pyodbc.SQL_WCHAR, encoding='utf-8')
#cnxn.setencoding(encoding='utf-8')
#cursor = cnxn.cursor()
#cursor.execute("SELECT * from db.t")
#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.mt (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.mt 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.mt 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.mt")
row = cursor.fetchone()
while row:
print(row)
row = cursor.fetchone()
cursor.close()
#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(4), blob nchar(4))");
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, 'hell', 'worl')")
cursor.close()
cursor = cnxn.cursor()
cursor.execute("SELECT * from db.t")
row = cursor.fetchone()
while row:
print(row)
row = cursor.fetchone()
cursor.close()
cursor = cnxn.cursor()
cursor.execute("create table db.v (ts timestamp, v1 tinyint)")
cursor.close()
params = [ ('A', 1), ('B', 2), ('C', 3) ]
params = [ ('A', 1), ('B', 2), ('C', 3) ]
params = [ ('2020-10-16 00:00:00', 1),
('2020-10-16 00:00:01', 4),
('2020-10-16 00:00:02', 5),
('2020-10-16 00:00:03.009', 6) ]
cursor = cnxn.cursor()
cursor.fast_executemany = True
cursor.executemany("insert into db.v values (?, ?)", params)
cursor.close()
cursor = cnxn.cursor()
cursor.execute("SELECT * from db.v")
row = cursor.fetchone()
while row:
print(row)
row = cursor.fetchone()
cursor.close()
cursor = cnxn.cursor()
cursor.execute("SELECT * from db.v where v1 > ?", 4)
row = cursor.fetchone()
while row:
print(row)
row = cursor.fetchone()
cursor.close()
cursor = cnxn.cursor()
cursor.execute("SELECT * from db.v where v1 > ?", '5')
row = cursor.fetchone()
while row:
print(row)
row = cursor.fetchone()
cursor.close()
cursor = cnxn.cursor()
cursor.execute("create table db.f (ts timestamp, v1 float)")
cursor.close()
params = [ ('2020-10-20 00:00:10', '123.3') ]
cursor = cnxn.cursor()
cursor.fast_executemany = True
cursor.executemany("insert into db.f values (?, ?)", params)
cursor.close()

View File

@ -1,12 +1,23 @@
PROJECT(TDengine)
IF (TD_LINUX)
ADD_EXECUTABLE(todbcinst main.c)
ADD_EXECUTABLE(todbcinst main.c)
ADD_EXECUTABLE(tconv tconv.c)
IF (TD_LINUX OR TD_DARWIN)
TARGET_LINK_LIBRARIES(todbcinst odbc odbcinst)
ENDIF ()
IF (TD_DARWIN)
target_include_directories(todbcinst PRIVATE /usr/local/include)
target_link_directories(todbcinst PUBLIC /usr/local/lib)
target_include_directories(tconv PRIVATE /usr/local/include)
target_link_directories(tconv PUBLIC /usr/local/lib)
TARGET_LINK_LIBRARIES(tconv iconv)
ENDIF ()
IF (TD_WINDOWS_64)
ADD_EXECUTABLE(todbcinst main.c)
TARGET_LINK_LIBRARIES(todbcinst odbc32 odbccp32 user32 legacy_stdio_definitions os)
TARGET_LINK_LIBRARIES(tconv taos)
INSTALL(FILES ${EXECUTABLE_OUTPUT_PATH}/todbcinst.exe DESTINATION .)
ENDIF ()