定位

c++的客户端接口,官方指定,特点时使用比较激进的C++实现,最新代码编译需要支持C++17,下个版本需要C++20,和其他pg对外接口实现类似的功能,但是由于使用C++实现,所以源码不多,可以用来快速的熟悉此类工具的大致的结构,后期可以去过一遍ODBC

01

直接全局搜索extern "C",可以知道它使用了那些libpq的数据结构和接口,grep -A5 -r 'extern "C"' ./*的结果表明次项目只使用了3个关键结构以及一个必要得头文件

extern "C"
{
  struct pg_conn;
  struct pg_result;
  struct pgNotify;
}

官方案例为

    #include <iostream>
    #include <pqxx/pqxx>

    int main()
    {
        try
        {
            // Connect to the database.  You can have multiple connections open
            // at the same time, even to the same database.
            pqxx::connection c;
            std::cout << "Connected to " << c.dbname() << '\n';

            // Start a transaction.  A connection can only have one transaction
            // open at the same time, but after you finish a transaction, you
            // can start a new one on the same connection.
            pqxx::work tx{c};

            // Query data of two columns, converting them to std::string and
            // int respectively.  Iterate the rows.
            for (auto [name, salary] : tx.query<std::string, int>(
                "SELECT name, salary FROM employee ORDER BY name"))
            {
                std::cout << name << " earns " << salary << ".\n";
            }

            // For large amounts of data, "streaming" the results is more
            // efficient.  It does not work for all types of queries though.
            //
            // You can read fields as std::string_view here, which is not
            // something you can do in most places.  A string_view becomes
            // meaningless when the underlying string ceases to exist.  In this
            // one situation, you can convert a field to string_view and it
            // will be valid for just that one iteration of the loop.  The next
            // iteration may overwrite or deallocate its buffer space.
            for (auto [name, salary] : tx.stream<std::string_view, int>(
                "SELECT name, salary FROM employee"))
            {
                std::cout << name << " earns " << salary << ".\n";
            }

            // Execute a statement, and check that it returns 0 rows of data.
            // This will throw pqxx::unexpected_rows if the query returns rows.
            std::cout << "Doubling all employees' salaries...\n";
            tx.exec0("UPDATE employee SET salary = salary*2");

            // Shorthand: conveniently query a single value from the database.
            int my_salary = tx.query_value<int>(
                "SELECT salary FROM employee WHERE name = 'Me'");
            std::cout << "I now earn " << my_salary << ".\n";

            // Or, query one whole row.  This function will throw an exception
            // unless the result contains exactly 1 row.
            auto [top_name, top_salary] = tx.query1<std::string, int>(
                R"(
                    SELECT name, salary
                    FROM employee
                    WHERE salary = max(salary)
                    LIMIT 1
                )");
            std::cout << "Top earner is " << top_name << " with a salary of "
                      << top_salary << ".\n";

            // If you need to access the result metadata, not just the actual
            // field values, use the "exec" functions.  Most of them return
            // pqxx::result objects.
            pqxx::result res = tx.exec("SELECT * FROM employee");
            std::cout << "Columns:\n";
            for (pqxx::row_size_type col = 0; col < res.columns(); ++col)
                std::cout << res.column_name(col) << '\n';

            // Commit the transaction.  If you don't do this, the database will
            // undo any changes you made in the transaction.
            std::cout << "Making changes definite: ";
            tx.commit();
            std::cout << "OK.\n";
        }
        catch (std::exception const &e)
        {
            std::cerr << "ERROR: " << e.what() << '\n';
            return 1;
        }
        return 0;
    }

所以执行方法实现得主体是work

using work = transaction<>;


template<
  isolation_level ISOLATION = isolation_level::read_committed,
  write_policy READWRITE = write_policy::read_write>
class transaction final : public internal::basic_transaction {}


class PQXX_LIBEXPORT basic_transaction : public dbtransaction {}


class PQXX_LIBEXPORT PQXX_NOVTABLE dbtransaction : public transaction_base {}


class PQXX_LIBEXPORT PQXX_NOVTABLE transaction_base{}

pg支持多种级别的事务,在begin的时候可以指定,BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY

多层继承,默认事务为读已提交的读写事务,最底层实现是transaction_base,提供执行语句的接口,例如上面例子中的示例。

  1. direct_exec
    公用的底层方法,调用connect执行语句,在transaction_base中,多个方法会调用此方法
  2. exec
    direct_exec的封装
  3. 上层实现有exec_n,prepare_exec等

另外使用模板,实现基于迭代器得访问方法,且大量使用可变模板参数,使用可以说是非常灵活

pqxx::result pqxx::connection::exec_prepared(
  std::string_view statement, internal::c_params const &args)
{
  auto const q{std::make_shared<std::string>(statement)};
  auto const pq_result{PQexecPrepared(
    m_conn, q->c_str(),
    check_cast<int>(std::size(args.values), "exec_prepared"sv),
    args.values.data(), args.lengths.data(),
    reinterpret_cast<int const *>(args.formats.data()),
    static_cast<int>(format::text))};
  auto const r{make_result(pq_result, q, statement)};
  get_notifs();
  return r;
}

最底层是connection,他直接包装了libpq的接口,对上层的访问提供支持,


感觉是他们C++的炫技之作