Use templates to make writing data to PostgreSQL more generic
This way we don't need to hardwire any types. Also adds some docs and tests.HEAD
parent
ce062967c9
commit
47fc05e286
|
@ -167,29 +167,6 @@ pg_conn_t::exec_prepared_internal(char const *stmt, int num_params,
|
|||
return res;
|
||||
}
|
||||
|
||||
pg_result_t pg_conn_t::exec_prepared(char const *stmt, char const *p1, char const *p2) const
|
||||
{
|
||||
std::array<const char *, 2> params{{p1, p2}};
|
||||
return exec_prepared_internal(stmt, params.size(), params.data());
|
||||
}
|
||||
|
||||
pg_result_t pg_conn_t::exec_prepared(char const *stmt, char const *param) const
|
||||
{
|
||||
return exec_prepared_internal(stmt, 1, ¶m);
|
||||
}
|
||||
|
||||
pg_result_t pg_conn_t::exec_prepared(char const *stmt,
|
||||
std::string const ¶m) const
|
||||
{
|
||||
return exec_prepared(stmt, param.c_str());
|
||||
}
|
||||
|
||||
pg_result_t pg_conn_t::exec_prepared(char const *stmt, osmid_t id) const
|
||||
{
|
||||
util::integer_to_buffer buffer{id};
|
||||
return exec_prepared(stmt, buffer.c_str());
|
||||
}
|
||||
|
||||
std::string tablespace_clause(std::string const &name)
|
||||
{
|
||||
std::string sql;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
* Helper classes and functions for PostgreSQL access.
|
||||
*/
|
||||
|
||||
#include "format.hpp"
|
||||
#include "osmtypes.hpp"
|
||||
|
||||
#include <libpq-fe.h>
|
||||
|
@ -104,6 +105,47 @@ private:
|
|||
std::unique_ptr<PGresult, pg_result_deleter_t> m_result;
|
||||
};
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* Helper for pg_conn_t::exec_prepared() function. All parameters to
|
||||
* that function are given to the exec_arg::to_str function which will
|
||||
* pass through string-like parameters and convert other parameters to
|
||||
* strings.
|
||||
*/
|
||||
template <typename T>
|
||||
struct exec_arg
|
||||
{
|
||||
constexpr static std::size_t const buffers_needed = 1;
|
||||
static char const *to_str(std::vector<std::string> *data, T param)
|
||||
{
|
||||
return data->emplace_back("{}"_format(std::forward<T>(param))).c_str();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct exec_arg<char const *>
|
||||
{
|
||||
constexpr static std::size_t const buffers_needed = 0;
|
||||
static char const *to_str(std::vector<std::string> * /*data*/,
|
||||
char const *param)
|
||||
{
|
||||
return param;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct exec_arg<std::string const &>
|
||||
{
|
||||
constexpr static std::size_t const buffers_needed = 0;
|
||||
static char const *to_str(std::vector<std::string> * /*data*/,
|
||||
std::string const ¶m)
|
||||
{
|
||||
return param.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/**
|
||||
* PostgreSQL connection.
|
||||
*
|
||||
|
@ -117,26 +159,58 @@ class pg_conn_t
|
|||
public:
|
||||
explicit pg_conn_t(std::string const &conninfo);
|
||||
|
||||
/// Execute a prepared statement with one parameter.
|
||||
pg_result_t exec_prepared(char const *stmt, char const *param) const;
|
||||
/**
|
||||
* Run the named prepared SQL statement and return the results.
|
||||
*
|
||||
* \param stmt The name of the prepared statement.
|
||||
* \param params Any number of arguments (will be converted to strings
|
||||
* if necessary).
|
||||
* \throws exception if the command failed.
|
||||
*/
|
||||
template <typename... TArgs>
|
||||
pg_result_t exec_prepared(char const *stmt, TArgs... params) const
|
||||
{
|
||||
// We have to convert all non-string parameters into strings and
|
||||
// store them somewhere. We use the exec_params vector for this.
|
||||
// It needs to be large enough to hold all parameters without resizing
|
||||
// so that pointers into the strings in that vector remain valid
|
||||
// after new parameters have been added.
|
||||
constexpr auto const total_buffers_needed =
|
||||
(0 + ... + exec_arg<TArgs>::buffers_needed);
|
||||
std::vector<std::string> exec_params;
|
||||
exec_params.reserve(total_buffers_needed);
|
||||
|
||||
/// Execute a prepared statement with two parameters.
|
||||
pg_result_t exec_prepared(char const *stmt, char const *p1, char const *p2) const;
|
||||
// This array holds the pointers to all parameter strings, either
|
||||
// to the original string parameters or to the recently converted
|
||||
// in the exec_params vector.
|
||||
std::array<char const *, sizeof...(params)> param_ptrs = {
|
||||
exec_arg<TArgs>::to_str(&exec_params,
|
||||
std::forward<TArgs>(params))...};
|
||||
|
||||
/// Execute a prepared statement with one string parameter.
|
||||
pg_result_t exec_prepared(char const *stmt, std::string const ¶m) const;
|
||||
|
||||
/// Execute a prepared statement with one integer parameter.
|
||||
pg_result_t exec_prepared(char const *stmt, osmid_t id) const;
|
||||
return exec_prepared_internal(stmt, sizeof...(params),
|
||||
param_ptrs.data());
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the specified SQL query and return the results.
|
||||
*
|
||||
* \param expect The expected status code of the SQL command.
|
||||
* \param sql The SQL command.
|
||||
* \throws exception if the result is not as expected.
|
||||
*/
|
||||
pg_result_t query(ExecStatusType expect, char const *sql) const;
|
||||
|
||||
pg_result_t query(ExecStatusType expect, std::string const &sql) const;
|
||||
|
||||
void set_config(char const *setting, char const *value) const;
|
||||
|
||||
/**
|
||||
* Run the specified SQL query. This can only be used for commands that
|
||||
* have no output and return status code PGRES_COMMAND_OK.
|
||||
*
|
||||
* \param sql The SQL command.
|
||||
* \throws exception if the command failed.
|
||||
*/
|
||||
void exec(char const *sql) const;
|
||||
|
||||
void exec(std::string const &sql) const;
|
||||
|
||||
void copy_data(std::string const &sql, std::string const &context) const;
|
||||
|
|
|
@ -40,3 +40,73 @@ TEST_CASE("PostGIS version")
|
|||
auto const postgis_version = get_postgis_version(conn);
|
||||
REQUIRE(postgis_version.major >= 2);
|
||||
}
|
||||
|
||||
TEST_CASE("query with SELECT should work")
|
||||
{
|
||||
auto conn = db.db().connect();
|
||||
auto const result = conn.query(PGRES_TUPLES_OK, "SELECT 42");
|
||||
REQUIRE(result.status() == PGRES_TUPLES_OK);
|
||||
REQUIRE(result.num_fields() == 1);
|
||||
REQUIRE(result.num_tuples() == 1);
|
||||
REQUIRE(result.get_value_as_string(0, 0) == "42");
|
||||
}
|
||||
|
||||
TEST_CASE("query with invalid SQL should fail")
|
||||
{
|
||||
auto conn = db.db().connect();
|
||||
REQUIRE_THROWS(conn.query(PGRES_TUPLES_OK, "NOT-VALID-SQL"));
|
||||
}
|
||||
|
||||
TEST_CASE("exec with invalid SQL should fail")
|
||||
{
|
||||
auto conn = db.db().connect();
|
||||
REQUIRE_THROWS(conn.exec("XYZ"));
|
||||
}
|
||||
|
||||
TEST_CASE("exec_prepared without parameters should work")
|
||||
{
|
||||
auto conn = db.db().connect();
|
||||
conn.exec("PREPARE test AS SELECT 42");
|
||||
|
||||
auto const result = conn.exec_prepared("test");
|
||||
REQUIRE(result.status() == PGRES_TUPLES_OK);
|
||||
REQUIRE(result.num_fields() == 1);
|
||||
REQUIRE(result.num_tuples() == 1);
|
||||
REQUIRE(result.get_value_as_string(0, 0) == "42");
|
||||
}
|
||||
|
||||
TEST_CASE("exec_prepared with single string parameters should work")
|
||||
{
|
||||
auto conn = db.db().connect();
|
||||
conn.exec("PREPARE test(int) AS SELECT $1");
|
||||
|
||||
auto const result = conn.exec_prepared("test", "17");
|
||||
REQUIRE(result.status() == PGRES_TUPLES_OK);
|
||||
REQUIRE(result.num_fields() == 1);
|
||||
REQUIRE(result.num_tuples() == 1);
|
||||
REQUIRE(result.get_value_as_string(0, 0) == "17");
|
||||
}
|
||||
|
||||
TEST_CASE("exec_prepared with string parameters should work")
|
||||
{
|
||||
auto conn = db.db().connect();
|
||||
conn.exec("PREPARE test(int, int, int) AS SELECT $1 + $2 + $3");
|
||||
|
||||
auto const result = conn.exec_prepared("test", "1", "2", std::string{"3"});
|
||||
REQUIRE(result.status() == PGRES_TUPLES_OK);
|
||||
REQUIRE(result.num_fields() == 1);
|
||||
REQUIRE(result.num_tuples() == 1);
|
||||
REQUIRE(result.get_value_as_string(0, 0) == "6");
|
||||
}
|
||||
|
||||
TEST_CASE("exec_prepared with non-string parameters should work")
|
||||
{
|
||||
auto conn = db.db().connect();
|
||||
conn.exec("PREPARE test(int, int, int) AS SELECT $1 + $2 + $3");
|
||||
|
||||
auto const result = conn.exec_prepared("test", 1, 2.0, 3ULL);
|
||||
REQUIRE(result.status() == PGRES_TUPLES_OK);
|
||||
REQUIRE(result.num_fields() == 1);
|
||||
REQUIRE(result.num_tuples() == 1);
|
||||
REQUIRE(result.get_value_as_string(0, 0) == "6");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue