|
8 | 8 | */ |
9 | 9 |
|
10 | 10 | #include "format.hpp" |
| 11 | +#include "logging.hpp" |
| 12 | +#include "pgsql-capabilities.hpp" |
11 | 13 | #include "pgsql.hpp" |
12 | | -#include "pgsql-helper.hpp" |
| 14 | +#include "version.hpp" |
13 | 15 |
|
| 16 | +#include <map> |
14 | 17 | #include <set> |
| 18 | +#include <stdexcept> |
15 | 19 | #include <string> |
16 | 20 |
|
17 | | -static std::set<std::string> init_set_from_table(pg_conn_t const &db_connection, |
18 | | - char const *table, |
19 | | - char const *column, |
20 | | - char const *condition) |
| 21 | +struct database_capabilities_t |
21 | 22 | { |
22 | | - std::set<std::string> values; |
| 23 | + std::map<std::string, std::string> settings; |
23 | 24 |
|
| 25 | + std::set<std::string> extensions; |
| 26 | + std::set<std::string> schemas; |
| 27 | + std::set<std::string> tablespaces; |
| 28 | + std::set<std::string> index_methods; |
| 29 | + |
| 30 | + std::string database_name; |
| 31 | + |
| 32 | + uint32_t database_version = 0; |
| 33 | + postgis_version postgis{}; |
| 34 | +}; |
| 35 | + |
| 36 | +static database_capabilities_t &capabilities() noexcept |
| 37 | +{ |
| 38 | + static database_capabilities_t c; |
| 39 | + return c; |
| 40 | +} |
| 41 | + |
| 42 | +static void init_set_from_query(std::set<std::string> *set, |
| 43 | + pg_conn_t const &db_connection, |
| 44 | + char const *table, char const *column, |
| 45 | + char const *condition = "true") |
| 46 | +{ |
24 | 47 | auto const res = db_connection.query( |
25 | 48 | PGRES_TUPLES_OK, |
26 | 49 | "SELECT {} FROM {} WHERE {}"_format(column, table, condition)); |
27 | 50 | for (int i = 0; i < res.num_tuples(); ++i) { |
28 | | - values.insert(res.get_value_as_string(i, 0)); |
| 51 | + set->insert(res.get_value_as_string(i, 0)); |
29 | 52 | } |
| 53 | +} |
| 54 | + |
| 55 | +/// Get all config settings from the database. |
| 56 | +static void init_settings(pg_conn_t const &db_connection) |
| 57 | +{ |
| 58 | + auto const res = db_connection.query( |
| 59 | + PGRES_TUPLES_OK, "SELECT name, setting FROM pg_settings"); |
30 | 60 |
|
31 | | - return values; |
| 61 | + for (int i = 0; i < res.num_tuples(); ++i) { |
| 62 | + capabilities().settings.emplace(res.get_value_as_string(i, 0), |
| 63 | + res.get_value_as_string(i, 1)); |
| 64 | + } |
32 | 65 | } |
33 | 66 |
|
34 | | -bool has_extension(pg_conn_t const &db_connection, std::string const &value) |
| 67 | +static void init_database_name(pg_conn_t const &db_connection) |
35 | 68 | { |
36 | | - static const std::set<std::string> values = init_set_from_table( |
37 | | - db_connection, "pg_catalog.pg_extension", "extname", "true"); |
| 69 | + auto const res = |
| 70 | + db_connection.query(PGRES_TUPLES_OK, "SELECT current_catalog"); |
| 71 | + |
| 72 | + if (res.num_tuples() != 1) { |
| 73 | + throw std::runtime_error{ |
| 74 | + "Database error: Can not access database name."}; |
| 75 | + } |
38 | 76 |
|
39 | | - return values.count(value); |
| 77 | + capabilities().database_name = res.get_value_as_string(0, 0); |
40 | 78 | } |
41 | 79 |
|
42 | | -bool has_schema(pg_conn_t const &db_connection, std::string const &value) |
| 80 | +static void init_postgis_version(pg_conn_t const &db_connection) |
43 | 81 | { |
44 | | - static const std::set<std::string> values = init_set_from_table( |
45 | | - db_connection, "pg_catalog.pg_namespace", "nspname", |
46 | | - "nspname !~ '^pg_' AND nspname <> 'information_schema'"); |
| 82 | + auto const res = db_connection.query( |
| 83 | + PGRES_TUPLES_OK, "SELECT regexp_split_to_table(extversion, '\\.') FROM" |
| 84 | + " pg_extension WHERE extname='postgis'"); |
47 | 85 |
|
48 | | - if (value.empty()) { |
49 | | - return true; |
| 86 | + if (res.num_tuples() == 0) { |
| 87 | + throw std::runtime_error{ |
| 88 | + "The postgis extension is not enabled on the database '{}'." |
| 89 | + " Are you using the correct database?" |
| 90 | + " Enable with 'CREATE EXTENSION postgis;'"_format( |
| 91 | + capabilities().database_name)}; |
50 | 92 | } |
51 | 93 |
|
52 | | - return values.count(value); |
| 94 | + capabilities().postgis = {std::stoi(res.get_value_as_string(0, 0)), |
| 95 | + std::stoi(res.get_value_as_string(1, 0))}; |
53 | 96 | } |
54 | 97 |
|
55 | | -bool has_tablespace(pg_conn_t const &db_connection, std::string const &value) |
| 98 | +void init_database_capabilities(pg_conn_t const &db_connection) |
56 | 99 | { |
57 | | - static const std::set<std::string> values = |
58 | | - init_set_from_table(db_connection, "pg_catalog.pg_tablespace", |
59 | | - "spcname", "spcname != 'pg_global'"); |
| 100 | + init_settings(db_connection); |
| 101 | + init_database_name(db_connection); |
| 102 | + init_postgis_version(db_connection); |
| 103 | + |
| 104 | + try { |
| 105 | + log_info("Database version: {}", |
| 106 | + capabilities().settings.at("server_version")); |
| 107 | + log_info("PostGIS version: {}.{}", capabilities().postgis.major, |
| 108 | + capabilities().postgis.minor); |
| 109 | + |
| 110 | + auto const version_str = |
| 111 | + capabilities().settings.at("server_version_num"); |
| 112 | + capabilities().database_version = |
| 113 | + std::strtoul(version_str.c_str(), nullptr, 10); |
| 114 | + if (capabilities().database_version < |
| 115 | + get_minimum_postgresql_server_version_num()) { |
| 116 | + throw std::runtime_error{ |
| 117 | + "Your database version is too old (need at least {})."_format( |
| 118 | + get_minimum_postgresql_server_version())}; |
| 119 | + } |
60 | 120 |
|
| 121 | + if (capabilities().settings.at("server_encoding") != "UTF8") { |
| 122 | + throw std::runtime_error{"Database is not using UTF8 encoding."}; |
| 123 | + } |
| 124 | + |
| 125 | + } catch (std::out_of_range const &) { |
| 126 | + // Thrown by the settings.at() if the named setting isn't found |
| 127 | + throw std::runtime_error{"Can't access database setting."}; |
| 128 | + } |
| 129 | + |
| 130 | + init_set_from_query(&capabilities().extensions, db_connection, |
| 131 | + "pg_catalog.pg_extension", "extname"); |
| 132 | + init_set_from_query( |
| 133 | + &capabilities().schemas, db_connection, "pg_catalog.pg_namespace", |
| 134 | + "nspname", "nspname !~ '^pg_' AND nspname <> 'information_schema'"); |
| 135 | + init_set_from_query(&capabilities().tablespaces, db_connection, |
| 136 | + "pg_catalog.pg_tablespace", "spcname", |
| 137 | + "spcname != 'pg_global'"); |
| 138 | + init_set_from_query(&capabilities().index_methods, db_connection, |
| 139 | + "pg_catalog.pg_am", "amname", "amtype = 'i'"); |
| 140 | +} |
| 141 | + |
| 142 | +bool has_extension(std::string const &value) |
| 143 | +{ |
| 144 | + return capabilities().extensions.count(value); |
| 145 | +} |
| 146 | + |
| 147 | +bool has_schema(std::string const &value) |
| 148 | +{ |
61 | 149 | if (value.empty()) { |
62 | 150 | return true; |
63 | 151 | } |
| 152 | + return capabilities().schemas.count(value); |
| 153 | +} |
64 | 154 |
|
65 | | - return values.count(value); |
| 155 | +bool has_tablespace(std::string const &value) |
| 156 | +{ |
| 157 | + if (value.empty()) { |
| 158 | + return true; |
| 159 | + } |
| 160 | + return capabilities().tablespaces.count(value); |
| 161 | +} |
| 162 | + |
| 163 | +bool has_index_method(std::string const &value) |
| 164 | +{ |
| 165 | + return capabilities().index_methods.count(value); |
| 166 | +} |
| 167 | + |
| 168 | +uint32_t get_database_version() noexcept |
| 169 | +{ |
| 170 | + return capabilities().database_version; |
| 171 | +} |
| 172 | + |
| 173 | +postgis_version get_postgis_version() noexcept |
| 174 | +{ |
| 175 | + return capabilities().postgis; |
66 | 176 | } |
0 commit comments