C++ SDK对接向量数据库

本文介绍如何使用C++语言调用openGauss向量数据库

环境准备

基本操作

1.连接数据库

1. #include <iostream>
2. #include <vector>
3. #include <string>
4. #include <libpq-fe.h>
5. #include <sstream>
6. #include <stdexcept>

8. class OpenGaussManager {
9. private:
10. PGconn* conn;

12. // 转义标识符(表名、列名等)
13. std::string escape_identifier(const std::string& identifier) {
14. char* escaped = PQescapeIdentifier(conn, identifier.c_str(), identifier.size());
15. if (!escaped) throw std::runtime_error(PQerrorMessage(conn));
16. std::string result(escaped);
17. PQfreemem(escaped);
18. return result;
19. }

21. // 执行SQL并检查结果
22. void execute_sql(const std::string& sql) {
23. PGresult* res = PQexec(conn, sql.c_str());
24. if (PQresultStatus(res) != PGRES_COMMAND_OK) {
25. std::string err = PQerrorMessage(conn);
26. PQclear(res);
27. throw std::runtime_error("SQL error: " + err);
28. }
29. PQclear(res);
30. }

32. // 将vector转为PostgreSQL数组格式
33. std::string vector_to_string(const std::vector<float>& vec) {
34. std::ostringstream oss;
35. oss << "'[";
36. for (size_t i = 0; i < vec.size(); ++i) {
37. if (i > 0) oss << ",";
38. oss << vec[i];
39. }
40. oss << "]'";
41. return oss.str();
42. }

44. public:
45. // 构造函数(仅初始化连接指针)
46. OpenGaussManager(const std::string& conninfo){
47. conn = PQconnectdb(conninfo.c_str());

49. if (PQstatus(conn) != CONNECTION_OK) {
50. std::cerr << "Connection failed: " << PQerrorMessage(conn) << std::endl;
51. PQfinish(conn);
52. conn = nullptr;
53. }
54. }

56. // 析构函数(确保释放连接)
57. ~OpenGaussManager() {
58. disconnectDB();
59. }
60. //其它方法
61. };

2.创建表

1. void create_table(const std::string& table_name, int vector_dim) {
2. std::string sql =
3. "CREATE TABLE IF NOT EXISTS public." + escape_identifier(table_name) +
4. " (id BIGINT PRIMARY KEY, " +
5. "embedding vector(" + std::to_string(vector_dim) + "))";

7. execute_sql(sql);
8. }

3.创建索引

1. void create_index(const std::string& table_name) {
2. std::string sql =
3. "CREATE INDEX ON " + escape_identifier(table_name) +
4. "USING hnsw(embedding vector_l2_ops);";
5. execute_sql(sql);
6. }

4.插入/删除/更新数据

  • 插入
1. void insert(const std::string& table_name, int id, const std::vector<float>& embedding) {
2. std::string sql =
3. "INSERT INTO public." + escape_identifier(table_name) +
4. " (id, embedding) VALUES (" +
5. std::to_string(id) + ", " +
6. vector_to_string(embedding) + ")";

8. execute_sql(sql);
9. }
  • 删除
1. int delete_by_id(const std::string& table_name, int id) {
2. std::string sql =
3. "DELETE FROM public." + escape_identifier(table_name) +
4. " WHERE id = " + std::to_string(id) +
5. " RETURNING id";

7. PGresult* res = PQexec(conn, sql.c_str());
8. if (PQresultStatus(res) != PGRES_TUPLES_OK) {
9. PQclear(res);
10. throw std::runtime_error("Delete failed: " + std::string(PQerrorMessage(conn)));
11. }
12. int deleted_count = PQntuples(res);
13. PQclear(res);
14. return deleted_count;
15. }
  • 更新
1. void update(const std::string& table_name,
2. int id,
3. const std::vector<float>& embedding) {
4. std::string sql =
5. "UPDATE public." + escape_identifier(table_name) +
6. " SET embedding = " + vector_to_string(embedding) +
7. " WHERE id = " + std::to_string(id);
8. execute_sql(sql);
9. }

5.查询

1. std::vector<std::pair<int, std::vector<float>>> select(
2. const std::string& table_name,
3. const std::vector<float>& query_vec,
4. int topk = 10
5. ) {
6. std::vector<std::pair<int, std::vector<float>>> results;

8. std::string sql =
9. "SELECT id, embedding FROM public." + escape_identifier(table_name) +
10. " ORDER BY embedding <-> " + vector_to_string(query_vec) +
11. " LIMIT " + std::to_string(topk);

13. PGresult* res = PQexec(conn, sql.c_str());
14. if (PQresultStatus(res) != PGRES_TUPLES_OK) {
15. PQclear(res);
16. throw std::runtime_error("Query failed: " + std::string(PQerrorMessage(conn)));
17. }
18. // 解析结果
19. int rows = PQntuples(res);
20. for (int i = 0; i < rows; ++i) {
21. // 解析ID
22. int id = std::stoi(PQgetvalue(res, i, 0));

24. // 解析vector
25. std::vector<float> vec;
26. std::string vec_str = PQgetvalue(res, i, 1);
27. std::istringstream iss(vec_str.substr(1, vec_str.size() -2));
28. std::string val;
29. while (std::getline(iss, val, ',')) {
30. vec.push_back(std::stof(val));
31. }

33. results.emplace_back(id, vec);
34. }

36. PQclear(res);
37. return results;
38. }

6.删除表

1. void drop_table(const std::string& table_name, bool cascade = false) {
2. std::string sql =
3. "DROP TABLE IF EXISTS public." + escape_identifier(table_name) +
4. (cascade ? " CASCADE" : "");

6. execute_sql(sql);
7. execute_sql("COMMIT");
8. }

7.关闭连接

1. void disconnectDB() {
2. if (conn != nullptr) {
3. PQfinish(conn);
4. conn = nullptr;
5. }
6. }

8.多向量并发查询

多向量召回支持在单次搜索请求中同时提交多个查询向量,openGauss将并行对查询向量进行搜索,并返回多组结果。

函数名

1. PGresult **PQexecMultiSearchParams(const char *connParams, const char *queryTemplate, const QueryParams *queryParams, const int queryCount, const char *preExecForConn, int threadCount)

输入参数

  • connParams:数据库连接配置,包含host、dbname、user、password、port
  • queryTemplate:查询语句,要求是单条查询语句(select为首单词)、包含向量操作符(<->/<=>/<#>/<+>/<~>/<%>)
  • queryParams:查询参数,要求不为空
  • queryCount:查询请求个数
  • preExecForConn:设置连接参数的sql语句,如:“set hnsw_ef_search=200;”
  • threadCount:连接池最大连接数,和数据库最大连接数(由参数max_connections设置)有关,一般来说,连接池最大连接数要小于数据库最大连接数,但是数据库对于管理员用户的连接限制会略超过max_connections设置。

输出参数

  • 查询结果,PGresult*类型数组,形式为[[[id:1, vector:[1,2,3]], [id:2 vector:[4,5,6]],…], [[id:3, vector:[1,2,2]], [id:2 vector:[4,5,6]],…], …],表示n个查询向量对应的limit个结果,解析方式参考示例。

使用案例

1. #include <iostream>
2. #include <vector>
3. #include <string>
4. #include <libpq-fe.h>
5. #include <sstream>
6. #include <stdexcept>

8. const char *get_example_vector(int index) {
9. static const char *vectors[] = {
10. "[0.12, 0.34, 0.56]",
11. "[4, 5, 6]"
12. };
13. return vectors[index % 2];
14. }

16. int main()
17. {
18. const char *conn_params = "host=127.0.0.1 dbname=postgres user=test password=yourpassword port=5432";
19. const int num_connections = 2;
20. int success_count = 0;
21. const char *query_template = "select id, embedding from vectors order by embedding <-> $1 limit 2;";
22. const char *preExec = "set enable_seqscan=true;";

24. int num_vectors = 2;
25. QueryParams query_params[num_vectors];
26. const char *param_values[num_vectors][1];
27. for (int i = 0; i < num_vectors; i++) {
28. param_values[i][0] = get_example_vector(i);

30. query_params[i].paramCount = 1;
31. query_params[i].paramValues = param_values[i];
32. query_params[i].paramLengths = NULL;
33. query_params[i].paramFormats = NULL;
34. query_params[i].resultFormat = 0;
35. }

37. PGresult **results = PQexecMultiSearchParams(conn_params, query_template, query_params, num_vectors, preExec, num_connections);
38. if (!results) {
39. printf("search error!\n");
40. }
41. for (int i = 0; i < num_vectors; i++) {
42. PGresult *res = results[i];
43. if (!res) {
44. std::cout << "result is invalid, query id:" << i << std::endl;
45. continue;
46. }
47. ExecStatusType status = PQresultStatus(res);
48. int rows = PQntuples(res);
49. int cols = PQnfields(res);
50. std::cout << "search query id:" << i << ", rows:" << rows << ", cols:" << cols << ", status:" << status <<
51. ", errMsg:" << PQresultErrorMessage(res) << std::endl;
52. for (int j = 0; j < rows; ++j) {
53. int id = std::stoi(PQgetvalue(res, j, 0));
54. std::cout << "id:" << id << std::endl;

56. std::vector<float> vec;
57. std::string vec_str = PQgetvalue(res, j, 1);
58. std::istringstream iss(vec_str.substr(1, vec_str.size() - 2));
59. std::string val;
60. std::cout << "vector: [";
61. while (std::getline(iss, val, ',')) {
62. vec.push_back(std::stof(val));
63. std::cout << val << ",";
64. }
65. std::cout << "]" << std::endl;
66. }
67. }
68. PQclearMultiResults(results, num_vectors);
69. return 0;
70. }

用例

1. int main() {
2. try {
3. // 1. 连接数据库
4. OpenGaussManager db("host=127.0.0.1 dbname=vector_db user=admin password=xxxxxx port=5432");

6. // 2. 创建表(维度为3)
7. db.create_table("image_vectors", 3);
8. std::cout << "Table created successfully." << std::endl;

10. // 3. 插入测试数据
11. std::vector<int> ids = {1};
12. std::vector<std::vector<float>> embeddings = {
13. {0.1f, 0.2f, 0.128f},
14. };
15. db.insert("image_vectors", ids[0], embeddings[0]);
16. std::cout << "Inserted " << ids.size() << " vectors." << std::endl;

18. // 4. 查询相似向量
19. std::vector<float> query_vec = {0.12f, 0.22f, 0.128f};
20. auto results = db.select("image_vectors", query_vec, 3);
21. std::cout << "Top " << results.size() << " similar vectors:" << std::endl;
22. for (const auto& [id, vec] : results) {
23. std::cout << "ID: " << id << " Vector: [";
24. for (size_t i = 0; i < std::min(5UL, vec.size()); ++i) {
25. if (i > 0) std::cout << ", ";
26. std::cout << vec[i];
27. }
28. std::cout << ", ...]" << std::endl;
29. }

31. // 5. 更新向量
32. std::vector<float> new_vec = {0.15f, 0.25f, 0.128f};
33. db.update("image_vectors", 1, new_vec);
34. std::cout << "Updated vector with ID=1" << std::endl;

36. // 6. 删除数据
37. int deleted = db.delete_by_id("image_vectors", 1);
38. std::cout << "Deleted " << deleted << " records" << std::endl;

40. // 7. 清理表
41. db.drop_table("image_vectors");
42. std::cout << "Table dropped successfully." << std::endl;

44. } catch (const std::exception& e) {
45. std::cerr << "Error: " << e.what() << std::endl;
46. return 1;
47. }

49. return 0;
50. }

编译并运行:

1. g++ -o test test.cpp -I /<YourPath>/include/ -L /<YourPath>/lib/ -lpq
2. ./test