C++11 封装nanodbc库操作snowflake DB

因为自己写的那个库有些限制。使用起来不大好用。
然后就想既然snowflake是使用ODBC来调用snowflake的Driver,那能不能直接使用现有的C++ ODBC库来解决增删改查的问题。在github上面搜了一下,果然已经有一个了。
然后做了一下简单的封装,让他更好用一点。

本示例运行步骤,
先到github上面编译安装这个库,
https://github.com/nanodbc/nanodbc

地址是上面这个,但是编译安装的时候会出现有一个test文件编不过的情况。
有两种解决方案,第一种是排除test进行编译安装。这种不推荐。
第二种方案是修复源码中的错误重新编译。
因为作者应该是基于Windows进行开发的库,对Mac和Linux的支持不是特别好。
体现在这里就是类型系统没有做兼容。
在对应的utility_test.cpp源文件中,加入一个using语句,

using WCHAR = wchar_t;

然后注释掉两行报错的static_assert宏。
如图所示,


image.png

这样就可以编译通过了。
但是运行测试的时候全部都会挂掉,不用担心,这是因为你没有安装对应数据库的驱动。
所以全挂是正常的。

在cmake ..,
make
make install
安装完成之后,需要把example目录下的example_unicode_utils.h拷贝一份到安装的头文件目录中。
一般是/usr/local/include/nanodbc/这个目录。
因为这个文件中有两个字符转码的函数convert,所以我把它重命名成了convert.h。
如图,

image.png

然后就可以操作了。
操作起来还是挺顺畅的,比我自己写的工具类完善得多。
增删改查都支持,具体用法可以查看/examples下面的usage.cpp
我的封装和测试如下,因为是生产数据库,修改之类的就没法直接测了,这里只测试查询。
程序目录结构,


image.png

CMakeLists.txt,使用C++17是因为为了做类型擦除使用了std::any。
如果不想使用17的话,可以使用boost/any.hpp


cmake_minimum_required(VERSION 2.6)
project(sf_db_lib_test)

add_definitions(-std=c++17)
add_definitions(-g)



find_package(Boost REQUIRED COMPONENTS
    system
    filesystem
    serialization
    program_options
    thread
    )

include_directories(${Boost_INCLUDE_DIRS} /usr/local/include /usr/local/iODBC/include /opt/snowflake/snowflakeodbc/include/ ${CMAKE_CURRENT_SOURCE_DIR}/../../)

LINK_DIRECTORIES(/usr/local/lib /usr/local/iODBC/lib /opt/snowflake/snowflakeodbc/lib/universal)

file( GLOB APP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.h
    ${CMAKE_CURRENT_SOURCE_DIR}/../impl/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
foreach( sourcefile ${APP_SOURCES} )
        file(RELATIVE_PATH filename ${CMAKE_CURRENT_SOURCE_DIR} ${sourcefile})
    
        string(FIND "${filename}"  "test.cpp" "TEMP")
    if( NOT "${TEMP}" STREQUAL "-1" )
        string(REPLACE ".cpp" "" file ${filename})
        add_executable(${file}  ${APP_SOURCES})
        target_link_libraries(${file} ${Boost_LIBRARIES})
        target_link_libraries(${file}  ssl crypto libgtest.a libgtest_main.a libgmock.a iodbc iodbcinst libnanodbc.a pthread)
    endif()
endforeach( sourcefile ${APP_SOURCES})

sf_db_lib.h

#ifndef _FREDRIC_SF_DB_LIB_H_
#define _FREDRIC_SF_DB_LIB_H_

#include "nanodbc/nanodbc.h"

#include <any>
#include <string>

using db_result = nanodbc::result;

struct sf_connection {
    nanodbc::connection conn_;

    sf_connection(const std::string& conn_str);
    db_result exec_raw_query(const std::string& raw_query);
    db_result exec_prpare_statement(const std::string& pre_stmt,
                                    const std::vector<std::any>& params);
    virtual ~sf_connection();

   private:
    void bind_params(nanodbc::statement& stmt, const short index,
                     const std::any& param);
};

#endif

sf_db_lib.cpp


#include "sf_db_lib/sf_db_lib.h"

#include <exception>
#include <iostream>

#include "nanodbc/convert.h"

using std::cout;
using std::endl;

sf_connection::sf_connection(const std::string& conn_str) {
    conn_ = nanodbc::connection{convert(conn_str)};
}

db_result sf_connection::exec_raw_query(const std::string& raw_query) {
    auto res = execute(conn_, NANODBC_TEXT(raw_query));
    return std::move(res);
}

void sf_connection::bind_params(nanodbc::statement& stmt, const short index,
                                const std::any& param) {
    if (param.type() == typeid(nullptr)) {
        stmt.bind_null(index);
    } else if (param.type() == typeid(int)) {
        auto val = std::any_cast<int>(param);
        std::vector<std::string> v{std::to_string(val)};
        stmt.bind_strings(index, v);
    } else if (param.type() == typeid(long)) {
        auto val = std::any_cast<long>(param);
        std::vector<std::string> v{std::to_string(val)};
        stmt.bind_strings(index, v);
    } else if (param.type() == typeid(double)) {
        auto val = std::any_cast<double>(param);
        std::vector<std::string> v{std::to_string(val)};
        stmt.bind_strings(index, v);
    } else if (param.type() == typeid(float)) {
        auto val = std::any_cast<float>(param);
        std::vector<std::string> v{std::to_string(val)};
        stmt.bind_strings(index, v);
    } else if (param.type() == typeid(const char*)) {
        auto val = std::any_cast<const char*>(param);
        stmt.bind(index, val);
    } else if (param.type() == typeid(std::string)) {
        auto val = std::any_cast<std::string>(param);
        stmt.bind(index, val.c_str());
    } else {
        throw std::runtime_error("not supported data types!");
    }
}

db_result sf_connection::exec_prpare_statement(
    const std::string& pre_stmt, const std::vector<std::any>& params) {
    nanodbc::statement statement(conn_);
    std::cout << pre_stmt << std::endl;
    prepare(statement, NANODBC_TEXT(pre_stmt));
    for (short i = 0; i < params.size(); ++i) {
        bind_params(statement, i, params[i]);
    }
    auto res = execute(statement);
    return std::move(res);
}

sf_connection::~sf_connection() {}

sf_db_lib_test.cpp

#include "sf_db_lib/sf_db_lib.h"

#include <gtest/gtest.h>

const std::string ConnStr = "dsn=product_odbc;pwd={YOUR_PASSWORD}";

GTEST_TEST(SFDBTests, TestExecQuery) {
    auto conn_str = ConnStr;
    auto raw_query =
        "select  product_key,change_time,EVENT_TYPE_NAME, change_column, "
        "old_value, new_value,meta from "
        "AA_INTELLIGENCE_PRODUCTION.ADL_MASTER.dim_event_service_v1 where  "
        "event_type_name='screenshot_change' and product_key=20600000009072 "
        "order by change_time desc limit 2;";
    sf_connection sf{conn_str};
    auto res = sf.exec_raw_query(raw_query);
    ASSERT_EQ(2, res.affected_rows());

    const auto columns = res.columns();
    for (short i = 0; i < columns; ++i) std::cout << res.column_name(i) << "\t";

    std::cout << std::endl;

    const std::string null_value = "null";
    while (res.next()) {
        for (short col = 0; col < columns; ++col) {
            auto const value = res.get<std::string>(col, null_value);
            std::cout << "(" << value << ")\t";
        }
        std::cout << std::endl;
    }
}

GTEST_TEST(SFDBTests, TestExecPreStatement) {
    auto conn_str = ConnStr;
    auto pre_query =
        "select  product_key,change_time,EVENT_TYPE_NAME, change_column, "
        "old_value, new_value,meta from "
        "AA_INTELLIGENCE_PRODUCTION.ADL_MASTER.dim_event_service_v1 where  "
        "event_type_name=? and product_key=? and change_time=? order by "
        "change_time desc limit 2;";
    std::vector<std::any> params = {"screenshot_change", 20600000009072,
                                    "2021-06-07"};
    sf_connection sf{conn_str};

    auto res = sf.exec_prpare_statement(pre_query, params);
    ASSERT_EQ(1, res.affected_rows());

    const auto columns = res.columns();
    for (short i = 0; i < columns; ++i) std::cout << res.column_name(i) << "\t";

    std::cout << std::endl;

    const std::string null_value = "null";
    while (res.next()) {
        for (short col = 0; col < columns; ++col) {
            auto const value = res.get<std::string>(col, null_value);
            std::cout << "(" << value << ")\t";
        }
        std::cout << std::endl;
    }
}

程序输出如下,


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容