C++ 原生接口

1. 依赖

  • Java 8+
  • Flex
  • Bison 2.7+
  • Boost 1.56+
  • OpenSSL 1.0+
  • GCC 5.5.0+

2. 安装

2.1 安装相关依赖

  • MAC
  1. 安装 Bison :

    使用下面 brew 命令安装 bison 版本:

    brew install bison
    
  2. 安装 Boost :确保安装最新的 Boost 版本。

    brew install boost
    
  3. 检查 OpenSSL :确保 openssl 库已安装,默认的 openssl 头文件路径为"/usr/local/opt/openssl/include"

    如果在编译过程中出现找不到 openssl 的错误,尝试添加-Dopenssl.include.dir=""

  • Ubuntu 16.04+ 或其他 Debian 系列

    使用以下命令安装所赖:

    sudo apt-get update
    sudo apt-get install gcc g++ bison flex libboost-all-dev libssl-dev
    
  • CentOS 7.7+/Fedora/Rocky Linux 或其他 Red-hat 系列

    使用 yum 命令安装依赖:

    sudo yum update
    sudo yum install gcc gcc-c++ boost-devel bison flex openssl-devel
    
  • Windows

  1. 构建编译环境

    • 安装 MS Visual Studio(推荐安装 2019+ 版本):安装时需要勾选 Visual Studio C/C++ IDE and compiler(supporting CMake, Clang, MinGW)
    • 下载安装 CMake
  2. 下载安装 Flex、Bison

    • 下载 Win_Flex_Bison
    • 下载后需要将可执行文件重命名为 flex.exe 和 bison.exe 以保证编译时能够被找到,添加可执行文件的目录到 PATH 环境变量中
  3. 安装 Boost 库

    • 下载 Boost
    • 本地编译 Boost :依次执行 bootstrap.bat 和 b2.exe
    • 添加 Boost 安装目录到 PATH 环境变量中,例如 C:\Program Files (x86)\boost_1_78_0
  4. 安装 OpenSSL

    • 下载安装 OpenSSL
    • 添加 OpenSSL 下的 include 目录到 PATH 环境变量中

2.2 执行编译

从 git 克隆源代码:

git clone https://github.com/apache/iotdb.git

默认的主分支是 master 分支,如果你想使用某个发布版本,请切换分支 (如 1.3.2 版本):

git checkout rc/1.3.2

在 IoTDB 根目录下执行 maven 编译:

  • Mac 或 glibc 版本 >= 2.32 的 Linux

    ./mvnw clean package -pl example/client-cpp-example -am -DskipTests -P with-cpp
    
  • glibc 版本 >= 2.31 的 Linux

    ./mvnw clean package -pl example/client-cpp-example -am -DskipTests -P with-cpp -Diotdb-tools-thrift.version=0.14.1.1-old-glibc-SNAPSHOT
    
  • glibc 版本 >= 2.17 的 Linux

    ./mvnw clean package -pl example/client-cpp-example -am -DskipTests -P with-cpp -Diotdb-tools-thrift.version=0.14.1.1-glibc223-SNAPSHOT
    
  • 使用 Visual Studio 2022 的 Windows

    .\mvnw.cmd clean package -pl example/client-cpp-example -am -DskipTests -P with-cpp
    
  • 使用 Visual Studio 2019 的 Windows

    .\mvnw.cmd clean package -pl example/client-cpp-example -am -DskipTests -P with-cpp -Dcmake.generator="Visual Studio 16 2019" -Diotdb-tools-thrift.version=0.14.1.1-msvc142-SNAPSHOT
    
    • 如果没有将 Boost 库地址加入 PATH 环境变量,在编译命令中还需添加相关参数,例如:-DboostIncludeDir="C:\Program Files (x86)\boost_1_78_0" -DboostLibraryDir="C:\Program Files (x86)\boost_1_78_0\stage\lib"

编译成功后,打包好的库文件位于 iotdb-client/client-cpp/target 中,同时可以在 example/client-cpp-example/target 下找到编译好的示例程序。

2.3 编译 Q&A

Q:Linux 上的环境有哪些要求呢?

A:

  • 已知依赖的 glibc (x86_64 版本) 最低版本要求为 2.17,GCC 最低版本为 5.5
  • 已知依赖的 glibc (ARM 版本) 最低版本要求为 2.31,GCC 最低版本为 10.2
  • 如果不满足上面的要求,可以尝试自己本地编译 Thrift

Q:Linux 编译报错undefined reference to '_libc_sinle_thread'如何处理?

A:

  • 该问题是用于默认的预编译 Thrift 依赖了过高的 glibc 版本导致的
  • 可以尝试在编译的 maven 命令中增加 -Diotdb-tools-thrift.version=0.14.1.1-glibc223-SNAPSHOT 或者 -Diotdb-tools-thrift.version=0.14.1.1-old-glibc-SNAPSHOT

Q:如果在 Windows 上需要使用 Visual Studio 2017 或更早版本进行编译,要怎么做?

A:

Q: Windows 上使用 Visual Studio 进行编译时出现乱码,如何解决?

A:

  • 该问题是由于项目整体使用 utf-8 编码,而编译用到的一些 windows 系统文件编码不是 utf-8(系统编码默认跟随地区,在中国为 GBK)
  • 可以在控制面板中更改系统区域设置,具体操作方法为:打开控制面板 -> 时钟和区域 -> 区域,切换到管理选项卡,在 “非Unicode程序的语言” 下,选择更改系统区域设置,勾选 Beta版:使用Unicode UTF-8提供全球语言支持 后重启电脑。(细节可能随windows版本有差异,可在网上寻找详细教程)
  • 注意,修改 windows 系统编码后可能会导致一些其他使用 GBK 编码的程序出现乱码,将系统区域改回后可复原,请自行斟酌。

3. 使用方式

3.1 TableSession 类

C++ 客户端的操作均通过 TableSession 类进行,下面将给出 TableSession 接口中定义的方法说明。

3.1.1 方法列表

  1. insert(Tablet& tablet, bool sorted = false),将一个包含时间序列数据的Tablet对象插入到数据库中,sorted参数指明tablet中的行是否已按时间排序。
  2. executeNonQueryStatement(string& sql),执行非查询SQL语句,如DDL(数据定义语言)或DML(数据操作语言)命令。
  3. executeQueryStatement(string& sql),执行查询SQL语句,并返回包含查询结果的SessionDataSet对象,可选timeoutInMs参数指示超时返回时间。
  4. open(bool enableRPCCompression = false),开启连接,并决定是否启用RPC压缩(客户端状态须与服务端一致,默认不开启)。
  5. close(),关闭连接。

3.1.2 接口展示

class TableSession {
private:
    Session* session;
public:
    TableSession(Session* session) {
        this->session = session;
    }
    void insert(Tablet& tablet, bool sorted = false);
    void executeNonQueryStatement(const std::string& sql);
    unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql);
    unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql, int64_t timeoutInMs);
    string getDatabase(); //获取当前选择的database,可由executeNonQueryStatement代替
    void open(bool enableRPCCompression = false);
    void close();
};

3.2 TableSessionBuilder 类

TableSessionBuilder类是一个构建器,用于配置和创建TableSession类的实例,通过它可以在创建实例时方便地设置连接参数、查询参数等。

3.2.1 使用示例

//设置连接的IP、端口、用户名、密码
//设置的顺序任意,确保最后调用build()即可,创建的实例默认已进行open()连接操作
session = (new TableSessionBuilder())
            ->host("127.0.0.1")
            ->rpcPort(6667)
            ->username("root")
            ->password("TimechoDB@2021") //V2.0.6.x 之前默认密码是root
            ->build();

3.2.2 可设置的参数列表

参数名描述默认值
host设置连接的节点IP“127.0.0.1” (“localhost”)
rpcPort设置连接的节点端口6667
username设置连接的用户名“root”
password设置连接密码“TimechoDB@2021” //V2.0.6.x 之前默认密码是root
zoneId设置时区相关的ZoneId""
fetchSize设置查询结果的获取大小10000
database设置目标数据库名称""

4. 示例代码

示例工程源代码:

编译成功后,示例代码工程位于 example/client-cpp-example/target

#include "TableSession.h"
#include "TableSessionBuilder.h"

using namespace std;

TableSession *session;

void insertRelationalTablet() {

    vector<pair<string, TSDataType::TSDataType>> schemaList {
        make_pair("region_id", TSDataType::TEXT),
        make_pair("plant_id", TSDataType::TEXT),
        make_pair("device_id", TSDataType::TEXT),
        make_pair("model", TSDataType::TEXT),
        make_pair("temperature", TSDataType::FLOAT),
        make_pair("humidity", TSDataType::DOUBLE)
    };

    vector<ColumnCategory> columnTypes = {
        ColumnCategory::TAG,
        ColumnCategory::TAG,
        ColumnCategory::TAG,
        ColumnCategory::ATTRIBUTE,
        ColumnCategory::FIELD,
        ColumnCategory::FIELD
    };

    Tablet tablet("table1", schemaList, columnTypes, 100);

    for (int row = 0; row < 100; row++) {
        int rowIndex = tablet.rowSize++;
        tablet.timestamps[rowIndex] = row;
        tablet.addValue("region_id", rowIndex, "1");
        tablet.addValue("plant_id", rowIndex, "5");
        tablet.addValue("device_id", rowIndex, "3");
        tablet.addValue("model", rowIndex, "A");
        tablet.addValue("temperature", rowIndex, 37.6F);
        tablet.addValue("humidity", rowIndex, 111.1);
        if (tablet.rowSize == tablet.maxRowNumber) {
            session->insert(tablet);
            tablet.reset();
        }
    }

    if (tablet.rowSize != 0) {
        session->insert(tablet);
        tablet.reset();
    }
}

void Output(unique_ptr<SessionDataSet> &dataSet) {
    for (const string &name: dataSet->getColumnNames()) {
        cout << name << "  ";
    }
    cout << endl;
    while (dataSet->hasNext()) {
        cout << dataSet->next()->toString();
        // 可通过 RowRecord* row = dataSet->next(); 
        // for(auto field: row.fields) field.intV/longV/stringV 来获取值
    }
    cout << endl;
}

void OutputWithType(unique_ptr<SessionDataSet> &dataSet) {
    for (const string &name: dataSet->getColumnNames()) {
        cout << name << "  ";
    }
    cout << endl;
    for (const string &type: dataSet->getColumnTypeList()) {
        cout << type << "  ";
    }
    cout << endl;
    while (dataSet->hasNext()) {
        cout << dataSet->next()->toString();
    }
    cout << endl;
}

int main() {
    try {
        session = (new TableSessionBuilder())
            ->host("127.0.0.1")
            ->rpcPort(6667)
            ->username("root")
            ->password("TimechoDB@2021") //V2.0.6.x 之前默认密码是root
            ->build();

        
        cout << "[Create Database db1,db2]\n" << endl;
        try {
            session->executeNonQueryStatement("CREATE DATABASE IF NOT EXISTS db1");
            session->executeNonQueryStatement("CREATE DATABASE IF NOT EXISTS db2");
        } catch (IoTDBException &e) {
            cout << e.what() << endl;
        }

        cout << "[Use db1 as database]\n" << endl;
        try {
            session->executeNonQueryStatement("USE db1");
        } catch (IoTDBException &e) {
            cout << e.what() << endl;
        }

        cout << "[Create Table table1,table2]\n" << endl;
        try {
            session->executeNonQueryStatement("create table db1.table1(region_id STRING TAG, plant_id STRING TAG, device_id STRING TAG, model STRING ATTRIBUTE, temperature FLOAT FIELD, humidity DOUBLE FIELD) with (TTL=3600000)");
            session->executeNonQueryStatement("create table db2.table2(region_id STRING TAG, plant_id STRING TAG, color STRING ATTRIBUTE, temperature FLOAT FIELD, speed DOUBLE FIELD) with (TTL=6600000)");
        } catch (IoTDBException &e) {
            cout << e.what() << endl;
        }

        cout << "[Show Tables]\n" << endl;
        try {
            unique_ptr<SessionDataSet> dataSet = session->executeQueryStatement("SHOW TABLES");
            Output(dataSet);
        } catch (IoTDBException &e) {
            cout << e.what() << endl;
        }

        cout << "[Show tables from specific database]\n" << endl;
        try {
            unique_ptr<SessionDataSet> dataSet = session->executeQueryStatement("SHOW TABLES FROM db1");
            Output(dataSet);
        } catch (IoTDBException &e) {
            cout << e.what() << endl;
        }

        cout << "[InsertTablet]\n" << endl;
        try {
            insertRelationalTablet();
        } catch (IoTDBException &e) {
            cout << e.what() << endl;
        }

        cout << "[Query Table Data]\n" << endl;
        try {
            unique_ptr<SessionDataSet> dataSet = session->executeQueryStatement("SELECT * FROM table1"
                " where region_id = '1' and plant_id in ('3', '5') and device_id = '3'");
            OutputWithType(dataSet);
        } catch (IoTDBException &e) {
            cout << e.what() << endl;
        }

        session->close();

        // specify database in constructor
        session = (new TableSessionBuilder())
            ->host("127.0.0.1")
            ->rpcPort(6667)
            ->username("root")
            ->password("TimechoDB@2021") //V2.0.6.x 之前默认密码是root
            ->database("db1")
            ->build();

        cout << "[Show tables from current database(db1)]\n" << endl;
        try {
            unique_ptr<SessionDataSet> dataSet = session->executeQueryStatement("SHOW TABLES");
            Output(dataSet);
        } catch (IoTDBException &e) {
            cout << e.what() << endl;
        }

        cout << "[Change database to db2]\n" << endl;
        try {
            session->executeNonQueryStatement("USE db2");
        } catch (IoTDBException &e) {
            cout << e.what() << endl;
        }

        cout << "[Show tables from current database(db2)]\n" << endl;
        try {
            unique_ptr<SessionDataSet> dataSet = session->executeQueryStatement("SHOW TABLES");
            Output(dataSet);
        } catch (IoTDBException &e) {
            cout << e.what() << endl;
        }

        cout << "[Drop Database db1,db2]\n" << endl;
        try {
            session->executeNonQueryStatement("DROP DATABASE db1");
            session->executeNonQueryStatement("DROP DATABASE db2");
        } catch (IoTDBException &e) {
            cout << e.what() << endl;
        }

        cout << "session close\n" << endl;
        session->close();

        delete session;

        cout << "finished!\n" << endl;
    } catch (IoTDBConnectionException &e) {
        cout << e.what() << endl;
    } catch (IoTDBException &e) {
        cout << e.what() << endl;
    }
    return 0;
}

5. FAQ

5.1 Thrift 编译相关问题

  1. MAC:本地 Maven 编译 Thrift 时如出现以下链接的问题,可以尝试将 xcode-commandline 版本从 12 降低到 11.5 https://stackoverflow.com/questions/63592445/ld-unsupported-tapi-file-type-tapi-tbd-in-yaml-file/65518087#65518087

  2. Windows:Maven 编译 Thrift 时需要使用 wget 下载远端文件,可能出现以下报错:

    Failed to delete cached file C:\Users\Administrator\.m2\repository\.cache\download-maven-plugin\index.ser
    

    解决方法:

    • 尝试删除 ".m2\repository\.cache" 目录并重试。
    • 在添加 pom 文件对应的 download-maven-plugin 中添加 “<skipCache>true</skipCache>”