借助DeepSeek编写输出漂亮表格的chdb客户端

chdbAuxten Wang创作的clickhouse的嵌入式引擎,它支持python、rust和c/c++语言调用动态链接库。
但官方没有给出c/c++语言调用的例子,在网友无敌大泡泡C语言示例启发下,我让DeepSeek改写出了一个简易客户端,
提示词如下:

附件是调用chdb动态链接库的c程序示例,请把它改写成支持输出表格(用|分隔各列,-分隔列标题和数据)和循环接收命令的客户端,.timer on/off命令用来开关计时,.exit用来退出,如果结果集大于10行,输出前后共10行,在中间用…表示,并打印实际行数。用c++实现

第一版的程序有个问题,输出表格缺列标题,误把第一行数据填在列标题的位置,反馈给他后,修改还是原样,于是考虑是否调用函数的结果集就是没有表头。结果在rust示例中看到了format.rs包含种类繁多的输出格式,其中有常见的PrettyCompact,把它和头文件一起提供给DeepSeek, 告诉他Pretty开头的格式原样输出即可。DeepSeek还发现format.rs中有CSVWithNames格式,聪明地改用它来获取表头,输出自定义表格。

首先下载动态链接库压缩包, 解压缩到/par目录。
然后编写c++源文件chsql.cpp,也放在/par目录下,最终的代码如下:

#include <iostream>
#include <string>
#include <vector>
#include <chrono>
#include <algorithm>
#include <iomanip>
#include <map>
#include "chdb.h"

class ChDBClient {
private:
    chdb_connection* conn;
    bool timer_enabled;
    std::vector<std::string> history;
    std::string current_format;
    std::map<std::string, std::string> format_aliases;

    void initialize_format_aliases() {
        format_aliases = {
            {"csv", "CSVWithNames"},
            {"json", "JSONEachRow"},
            {"pretty", "Pretty"},
            {"vertical", "Vertical"},
            {"markdown", "Markdown"},
            {"tsv", "TabSeparatedWithNames"},
            {"table", "Pretty"}  // Alias for Pretty
        };
    }

    chdb_result* execute_query(const std::string& query) {
        auto start = std::chrono::high_resolution_clock::now();
        chdb_result* result = chdb_query(*conn, query.c_str(), current_format.c_str());
        auto end = std::chrono::high_resolution_clock::now();

        if (timer_enabled) {
            auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
            std::cout << "Query executed in " << duration.count() << " ms\n";
        }

        return result;
    }

    void print_result(chdb_result* result) {
        // Check for errors
        const char* error = chdb_result_error(result);
        if (error) {
            std::cerr << "Query error: " << error << "\n";
            return;
        }

        // Process results
        char* data = chdb_result_buffer(result);
        size_t data_len = chdb_result_length(result);
        size_t rows_read = chdb_result_rows_read(result);
        double elapsed = chdb_result_elapsed(result);

        if (data_len > 0) {
            std::string result_data(data, data_len);
            std::cout << result_data;

            if (timer_enabled) {
                std::cout << "\n(" << rows_read << " rows, " << elapsed << " sec)\n";
            } else if (rows_read > 0) {
                std::cout << "\n(" << rows_read << " rows)\n";
            }
        } else {
            std::cout << "Query executed successfully. No results returned.\n";
        }
    }

    void print_table(const std::vector<std::vector<std::string>>& rows, const std::vector<std::string>& headers) {
        if (rows.empty()) return;

        // Calculate column widths based on both headers and data
        std::vector<size_t> col_widths(headers.size(), 0);
        for (size_t i = 0; i < headers.size(); ++i) {
            col_widths[i] = headers[i].size();
        }
        for (const auto& row : rows) {
            for (size_t i = 0; i < row.size() && i < col_widths.size(); ++i) {
                col_widths[i] = std::max(col_widths[i], row[i].size());
            }
        }

        // Print header separator
        std::string header_sep = "+";
        for (size_t width : col_widths) {
            header_sep += std::string(width + 2, '-') + "+";
        }
        std::cout << header_sep << "\n";

        // Print header
        std::cout << "|";
        for (size_t i = 0; i < headers.size(); ++i) {
            std::cout << " " << std::left << std::setw(col_widths[i]) << headers[i] << " |";
        }
        std::cout << "\n" << header_sep << "\n";

        // Print rows (with potential truncation)
        size_t total_rows = rows.size();
        bool truncated = false;
        size_t print_count = total_rows;

        if (total_rows > 10) {
            print_count = 10;
            truncated = true;
        }

        // Print first 5 rows
        for (size_t i = 0; i < std::min(print_count / 2, total_rows); ++i) {
            print_row(rows[i], col_widths);
        }

        // Print ellipsis if truncated
        if (truncated) {
            std::cout << "|";
            for (size_t i = 0; i < col_widths.size(); ++i) {
                std::cout << " " << std::left << std::setw(col_widths[i]) << "..." << " |";
            }
            std::cout << "\n";
        }

        // Print last 5 rows if truncated
        if (truncated) {
            for (size_t i = std::max(total_rows - print_count / 2, print_count / 2); i < total_rows; ++i) {
                print_row(rows[i], col_widths);
            }
        } else {
            // Print remaining rows if not truncated
            for (size_t i = print_count / 2; i < total_rows; ++i) {
                print_row(rows[i], col_widths);
            }
        }

        std::cout << header_sep << "\n";

        if (truncated) {
            std::cout << "(" << total_rows << " rows total, showing first and last 5 rows)\n";
        } else if (total_rows > 0) {
            std::cout << "(" << total_rows << " rows)\n";
        }
    }

    void print_row(const std::vector<std::string>& row, const std::vector<size_t>& col_widths) {
        std::cout << "|";
        for (size_t i = 0; i < row.size() && i < col_widths.size(); ++i) {
            std::cout << " " << std::left << std::setw(col_widths[i]) << row[i] << " |";
        }
        std::cout << "\n";
    }

    // Parse CSV results and extract headers from column names
    void parse_csv(const std::string& csv_data, std::vector<std::string>& headers, std::vector<std::vector<std::string>>& rows) {
        headers.clear();
        rows.clear();
        
        std::vector<std::string> current_row;
        std::string current_field;
        bool in_quotes = false;
        bool is_first_row = true;

        for (char c : csv_data) {
            if (c == '"') {
                in_quotes = !in_quotes;
            } else if (c == ',' && !in_quotes) {
                current_row.push_back(current_field);
                current_field.clear();
            } else if (c == '\n' && !in_quotes) {
                current_row.push_back(current_field);
                
                if (is_first_row) {
                    headers = current_row;
                    is_first_row = false;
                } else {
                    rows.push_back(current_row);
                }
                
                current_row.clear();
                current_field.clear();
            } else {
                current_field += c;
            }
        }

        // Add the last field if not empty
        if (!current_field.empty()) {
            current_row.push_back(current_field);
        }
        if (!current_row.empty()) {
            if (is_first_row) {
                headers = current_row;
            } else {
                rows.push_back(current_row);
            }
        }
    }

public:
    ChDBClient() : conn(nullptr), timer_enabled(false), current_format("CSVWithNames") {
        initialize_format_aliases();
    }

    ~ChDBClient() {
        if (conn) {
            chdb_close_conn(conn);
        }
    }

    bool connect() {
        char *argv[] = {
            "chdb_client",
            "--path=:memory:"
        };
        int argc = 2;

        conn = chdb_connect(argc, argv);
        if (!conn) {
            std::cerr << "Failed to create database connection\n";
            return false;
        }
        std::cout << "Connected to in-memory database successfully\n";
        return true;
    }

    void run() {
        std::cout << "chDB client (type '.exit' to quit, '.help' for help)\n";

        while (true) {
            std::string input;
            std::cout << "chdb> ";
            std::getline(std::cin, input);

            // Trim whitespace
            input.erase(0, input.find_first_not_of(" \t\n\r\f\v"));
            input.erase(input.find_last_not_of(" \t\n\r\f\v") + 1);

            if (input.empty()) continue;

            // Add to history
            history.push_back(input);

            // Handle meta commands
            if (input[0] == '.') {
                if (input == ".exit") {
                    break;
                } else if (input == ".timer on") {
                    timer_enabled = true;
                    std::cout << "Query timer enabled\n";
                    continue;
                } else if (input == ".timer off") {
                    timer_enabled = false;
                    std::cout << "Query timer disabled\n";
                    continue;
                } else if (input.substr(0, 8) == ".format ") {
                    set_format(input.substr(8));
                    continue;
                } else if (input == ".help") {
                    print_help();
                    continue;
                } else if (input == ".history") {
                    print_history();
                    continue;
                } else {
                    std::cerr << "Unknown command: " << input << "\n";
                    continue;
                }
            }

            // Execute SQL query
            chdb_result* result = execute_query(input);
            if (!result) {
                std::cerr << "Query execution failed\n";
                continue;
            }

            // For Pretty format, just print the result directly
            if (current_format.substr(0, 6) == "Pretty" || current_format == "Vertical" || current_format == "Markdown") {
                print_result(result);
            } else {
                // For other formats, parse and display as table
                char* data = chdb_result_buffer(result);
                size_t data_len = chdb_result_length(result);
                
                if (data_len > 0) {
                    std::string result_data(data, data_len);
                    std::vector<std::string> headers;
                    std::vector<std::vector<std::string>> rows;
                    parse_csv(result_data, headers, rows);
                    
                    if (!headers.empty()) {
                        print_table(rows, headers);
                    } else {
                        std::cout << result_data;
                    }
                    
                    size_t rows_read = chdb_result_rows_read(result);
                    double elapsed = chdb_result_elapsed(result);
                    
                    if (timer_enabled) {
                        std::cout << "(" << rows_read << " rows, " << elapsed << " sec)\n";
                    } else if (rows_read > 0) {
                        std::cout << "(" << rows_read << " rows)\n";
                    }
                } else {
                    std::cout << "Query executed successfully. No results returned.\n";
                }
            }

            chdb_destroy_query_result(result);
        }
    }

    void set_format(const std::string& format) {
        std::string fmt_lower = format;
        std::transform(fmt_lower.begin(), fmt_lower.end(), fmt_lower.begin(), ::tolower);
        
        if (format_aliases.find(fmt_lower) != format_aliases.end()) {
            current_format = format_aliases[fmt_lower];
            std::cout << "Output format set to: " << current_format << "\n";
        } else {
            // Try to use the format directly if it's not an alias
            current_format = format;
            std::cout << "Output format set to: " << current_format << "\n";
        }
    }

    void print_help() {
        std::cout << "Available commands:\n"
                  << "  .exit          - Exit the client\n"
                  << "  .timer on/off  - Enable/disable query timing\n"
                  << "  .format <fmt>  - Set output format (csv, json, pretty, vertical, markdown, tsv)\n"
                  << "  .history       - Show command history\n"
                  << "  .help          - Show this help message\n"
                  << "  SQL queries    - Execute SQL queries\n";
    }

    void print_history() {
        std::cout << "Command history:\n";
        for (size_t i = 0; i < history.size(); ++i) {
            std::cout << "  " << i + 1 << ": " << history[i] << "\n";
        }
    }
};

int main() {
    ChDBClient client;
    if (!client.connect()) {
        return EXIT_FAILURE;
    }

    client.run();
    std::cout << "Goodbye!\n";
    return EXIT_SUCCESS;
}

编译命令行如下:

export LD_LIBRARY_PATH=/par:/usr/local/lib
export LIBRARY_PATH=/par:/usr/local/lib
g++ chsql4.cpp -O3 -o chsql4 -I . -lchdb

目前的代码还有冗余,比如计时信息和行数输出了两遍,下次慢慢改

 ./chsql
Connected to in-memory database successfully
chDB client (type '.exit' to quit, '.help' for help)
chdb> .format PrettyCompactMonoBlock
Output format set to: PrettyCompactMonoBlock
chdb> select * from file('foods.csv')limit 4;
   ┌─category───┬─calories─┬─fats_g─┬─sugars_g─┐
1. │ vegetables │       450.522. │ seafood    │      150503. │ meat       │      100504. │ fruit      │       60011 │
   └────────────┴──────────┴────────┴──────────┘

(4 rows)
chdb> .timer on
Query timer enabled
chdb> .format CSVWithNames
Output format set to: CSVWithNames
chdb> select * from file('foods.csv');
Query executed in 24 ms
+------------+----------+--------+----------+
| category   | calories | fats_g | sugars_g |
+------------+----------+--------+----------+
| vegetables | 45       | 0.5    | 2        |
| seafood    | 150      | 5      | 0        |
| meat       | 100      | 5      | 0        |
| fruit      | 60       | 0      | 11       |
| seafood    | 140      | 5      | 1        |
| ...        | ...      | ...    | ...      |
| seafood    | 130      | 1.5    | 0        |
| fruit      | 130      | 0      | 25       |
| meat       | 100      | 7      | 0        |
| vegetables | 30       | 0      | 5        |
| fruit      | 50       | 0      | 11       |
+------------+----------+--------+----------+
(27 rows total, showing first and last 5 rows)
(27 rows, 0.0232782 sec)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值