aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--scripts/reference_exported_symbols.txt1
-rw-r--r--src/iso19111/c_api.cpp73
-rw-r--r--src/iso19111/factory.cpp100
-rw-r--r--src/proj.h3
-rw-r--r--test/unit/test_c_api.cpp80
-rw-r--r--test/unit/test_factory.cpp9
6 files changed, 192 insertions, 74 deletions
diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt
index aa148762..a4716e96 100644
--- a/scripts/reference_exported_symbols.txt
+++ b/scripts/reference_exported_symbols.txt
@@ -792,6 +792,7 @@ proj_context_errno
proj_context_errno_string
proj_context_get_database_metadata
proj_context_get_database_path
+proj_context_get_database_structure
proj_context_get_url_endpoint
proj_context_get_use_proj4_init_rules
proj_context_get_user_writable_directory
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index a79b387e..2a655e51 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -105,6 +105,28 @@ static void PROJ_NO_INLINE proj_log_debug(PJ_CONTEXT *ctx, const char *function,
// ---------------------------------------------------------------------------
+template <class T> static PROJ_STRING_LIST to_string_list(T &&set) {
+ auto ret = new char *[set.size() + 1];
+ size_t i = 0;
+ for (const auto &str : set) {
+ try {
+ ret[i] = new char[str.size() + 1];
+ } catch (const std::exception &) {
+ while (--i > 0) {
+ delete[] ret[i];
+ }
+ delete[] ret;
+ throw;
+ }
+ std::memcpy(ret[i], str.c_str(), str.size() + 1);
+ i++;
+ }
+ ret[i] = nullptr;
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
void proj_context_delete_cpp_context(struct projCppContext *cppContext) {
delete cppContext;
}
@@ -390,6 +412,35 @@ const char *proj_context_get_database_metadata(PJ_CONTEXT *ctx,
// ---------------------------------------------------------------------------
+/** \brief Return the database structure
+ *
+ * Return SQL statements to run to initiate a new valid auxiliary empty
+ * database. It contains definitions of tables, views and triggers, as well
+ * as metadata for the version of the layout of the database.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param options null-terminated list of options, or NULL. None currently.
+ * @return list of SQL statements (to be freed with proj_string_list_destroy()),
+ * or NULL in case of error.
+ * @since 8.1
+ */
+PROJ_STRING_LIST
+proj_context_get_database_structure(PJ_CONTEXT *ctx,
+ const char *const *options) {
+ SANITIZE_CTX(ctx);
+ (void)options;
+ try {
+ auto ret = to_string_list(getDBcontext(ctx)->getDatabaseStructure());
+ ctx->safeAutoCloseDbIfNeeded();
+ return ret;
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
/** \brief Guess the "dialect" of the WKT string.
*
* @param ctx PROJ context, or NULL for default context
@@ -508,28 +559,6 @@ PJ *proj_create(PJ_CONTEXT *ctx, const char *text) {
// ---------------------------------------------------------------------------
-template <class T> static PROJ_STRING_LIST to_string_list(T &&set) {
- auto ret = new char *[set.size() + 1];
- size_t i = 0;
- for (const auto &str : set) {
- try {
- ret[i] = new char[str.size() + 1];
- } catch (const std::exception &) {
- while (--i > 0) {
- delete[] ret[i];
- }
- delete[] ret;
- throw;
- }
- std::memcpy(ret[i], str.c_str(), str.size() + 1);
- i++;
- }
- ret[i] = nullptr;
- return ret;
-}
-
-// ---------------------------------------------------------------------------
-
/** \brief Instantiate an object from a WKT string.
*
* This function calls osgeo::proj::io::WKTParser::createFromWKT()
diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp
index 750e9054..af9736e6 100644
--- a/src/iso19111/factory.cpp
+++ b/src/iso19111/factory.cpp
@@ -274,6 +274,8 @@ struct DatabaseContext::Private {
std::vector<std::string> auxiliaryDatabasePaths_{};
bool close_handle_ = true;
sqlite3 *sqlite_handle_{};
+ int nLayoutVersionMajor_ = 0;
+ int nLayoutVersionMinor_ = 0;
std::map<std::string, sqlite3_stmt *> mapSqlToStatement_{};
PJ_CONTEXT *pjCtxt_ = nullptr;
int recLevel_ = 0;
@@ -305,7 +307,8 @@ struct DatabaseContext::Private {
lru11::Cache<std::string, std::list<std::string>> cacheAliasNames_{
CACHE_SIZE};
- void checkDatabaseLayout();
+ void checkDatabaseLayout(const std::string &path,
+ const std::string &dbNamePrefix);
static void insertIntoCache(LRUCacheOfObjects &cache,
const std::string &code,
@@ -709,10 +712,22 @@ void DatabaseContext::Private::open(const std::string &databasePath,
// ---------------------------------------------------------------------------
-void DatabaseContext::Private::checkDatabaseLayout() {
- auto res = run("SELECT key, value FROM metadata WHERE key IN "
+void DatabaseContext::Private::checkDatabaseLayout(
+ const std::string &path, const std::string &dbNamePrefix) {
+ if (!dbNamePrefix.empty() && run("SELECT 1 FROM " + dbNamePrefix +
+ "sqlite_master WHERE name = 'metadata'")
+ .empty()) {
+ // Accept auxiliary databases without metadata table (sparse DBs)
+ return;
+ }
+ auto res = run("SELECT key, value FROM " + dbNamePrefix +
+ "metadata WHERE key IN "
"('DATABASE.LAYOUT.VERSION.MAJOR', "
"'DATABASE.LAYOUT.VERSION.MINOR')");
+ if (res.empty() && !dbNamePrefix.empty()) {
+ // Accept auxiliary databases without layout metadata.
+ return;
+ }
if (res.size() != 2) {
// The database layout of PROJ 7.2 that shipped with EPSG v10.003 is
// at the time of writing still compatible of the one we support.
@@ -730,36 +745,47 @@ void DatabaseContext::Private::checkDatabaseLayout() {
}
throw FactoryException(
- databasePath_ +
- " lacks DATABASE.LAYOUT.VERSION.MAJOR / "
- "DATABASE.LAYOUT.VERSION.MINOR "
- "metadata. It comes from another PROJ installation.");
+ path + " lacks DATABASE.LAYOUT.VERSION.MAJOR / "
+ "DATABASE.LAYOUT.VERSION.MINOR "
+ "metadata. It comes from another PROJ installation.");
}
- int nMajor = 0;
- int nMinor = 0;
+ int major = 0;
+ int minor = 0;
for (const auto &row : res) {
if (row[0] == "DATABASE.LAYOUT.VERSION.MAJOR") {
- nMajor = atoi(row[1].c_str());
+ major = atoi(row[1].c_str());
} else if (row[0] == "DATABASE.LAYOUT.VERSION.MINOR") {
- nMinor = atoi(row[1].c_str());
+ minor = atoi(row[1].c_str());
}
}
- if (nMajor != DATABASE_LAYOUT_VERSION_MAJOR) {
+ if (major != DATABASE_LAYOUT_VERSION_MAJOR) {
throw FactoryException(
- databasePath_ +
- " contains DATABASE.LAYOUT.VERSION.MAJOR = " + toString(nMajor) +
+ path +
+ " contains DATABASE.LAYOUT.VERSION.MAJOR = " + toString(major) +
" whereas " + toString(DATABASE_LAYOUT_VERSION_MAJOR) +
" is expected. "
"It comes from another PROJ installation.");
}
- if (nMinor < DATABASE_LAYOUT_VERSION_MINOR) {
+ if (minor < DATABASE_LAYOUT_VERSION_MINOR) {
throw FactoryException(
- databasePath_ +
- " contains DATABASE.LAYOUT.VERSION.MINOR = " + toString(nMinor) +
+ path +
+ " contains DATABASE.LAYOUT.VERSION.MINOR = " + toString(minor) +
" whereas a number >= " + toString(DATABASE_LAYOUT_VERSION_MINOR) +
" is expected. "
"It comes from another PROJ installation.");
}
+ if (dbNamePrefix.empty()) {
+ nLayoutVersionMajor_ = major;
+ nLayoutVersionMinor_ = minor;
+ } else if (nLayoutVersionMajor_ != major || nLayoutVersionMinor_ != minor) {
+ throw FactoryException(
+ "Auxiliary database " + path +
+ " contains a DATABASE.LAYOUT.VERSION = " + toString(major) + '.' +
+ toString(minor) +
+ " which is different from the one from the main database " +
+ databasePath_ + " which is " + toString(nLayoutVersionMajor_) +
+ '.' + toString(nLayoutVersionMinor_));
+ }
}
// ---------------------------------------------------------------------------
@@ -777,17 +803,28 @@ void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) {
// ---------------------------------------------------------------------------
std::vector<std::string> DatabaseContext::Private::getDatabaseStructure() {
- const char *sqls[] = {
- "SELECT sql FROM sqlite_master WHERE type = 'table'",
- "SELECT sql FROM sqlite_master WHERE type = 'view'",
- "SELECT sql FROM sqlite_master WHERE type = 'trigger'"};
+ const std::string dbNamePrefix(auxiliaryDatabasePaths_.empty() &&
+ memoryDbForInsertPath_.empty()
+ ? ""
+ : "db_0.");
+ const auto sqlBegin("SELECT sql||';' FROM " + dbNamePrefix +
+ "sqlite_master WHERE type = ");
+ const char *const objectTypes[] = {"'table'", "'view'", "'trigger'"};
std::vector<std::string> res;
- for (const auto &sql : sqls) {
- auto sqlRes = run(sql);
+ for (const auto &objectType : objectTypes) {
+ const auto sqlRes = run(sqlBegin + objectType);
for (const auto &row : sqlRes) {
res.emplace_back(row[0]);
}
}
+ if (nLayoutVersionMajor_ > 0) {
+ res.emplace_back(
+ "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MAJOR'," +
+ toString(nLayoutVersionMajor_) + ");");
+ res.emplace_back(
+ "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MINOR'," +
+ toString(nLayoutVersionMinor_) + ");");
+ }
return res;
}
@@ -828,13 +865,16 @@ void DatabaseContext::Private::attachExtraDatabases(
"' AS db_0");
detach_ = true;
int count = 1;
- for (const auto &otherDb : auxiliaryDatabasePaths) {
+ for (const auto &otherDbPath : auxiliaryDatabasePaths) {
+ const auto attachedDbName("db_" + toString(static_cast<int>(count)));
std::string sql = "ATTACH DATABASE '";
- sql += replaceAll(otherDb, "'", "''");
- sql += "' AS db_";
- sql += toString(static_cast<int>(count));
+ sql += replaceAll(otherDbPath, "'", "''");
+ sql += "' AS ";
+ sql += attachedDbName;
count++;
run(sql);
+
+ checkDatabaseLayout(otherDbPath, attachedDbName + '.');
}
for (const auto &pair : tableStructure) {
@@ -2452,11 +2492,11 @@ DatabaseContext::create(const std::string &databasePath,
auto dbCtx = DatabaseContext::nn_make_shared<DatabaseContext>();
auto dbCtxPrivate = dbCtx->getPrivate();
dbCtxPrivate->open(databasePath, ctx);
+ dbCtxPrivate->checkDatabaseLayout(databasePath, std::string());
if (!auxiliaryDatabasePaths.empty()) {
dbCtxPrivate->attachExtraDatabases(auxiliaryDatabasePaths);
dbCtxPrivate->auxiliaryDatabasePaths_ = auxiliaryDatabasePaths;
}
- dbCtxPrivate->checkDatabaseLayout();
dbCtxPrivate->self_ = dbCtx.as_nullable();
return dbCtx;
}
@@ -2527,6 +2567,9 @@ void DatabaseContext::startInsertStatementsSession() {
"stopInsertStatementsSession() is.");
}
+ d->memoryDbForInsertPath_.clear();
+ const auto sqlStatements = getDatabaseStructure();
+
// Create a in-memory temporary sqlite3 database
std::ostringstream buffer;
buffer << "file:temp_db_for_insert_statements_";
@@ -2541,7 +2584,6 @@ void DatabaseContext::startInsertStatementsSession() {
}
// Fill the structure of this database
- const auto sqlStatements = getDatabaseStructure();
for (const auto &sql : sqlStatements) {
if (sqlite3_exec(d->memoryDbHandle_, sql.c_str(), nullptr, nullptr,
nullptr) != SQLITE_OK) {
diff --git a/src/proj.h b/src/proj.h
index 83975123..63821489 100644
--- a/src/proj.h
+++ b/src/proj.h
@@ -1044,6 +1044,9 @@ const char PROJ_DLL *proj_context_get_database_path(PJ_CONTEXT *ctx);
const char PROJ_DLL *proj_context_get_database_metadata(PJ_CONTEXT* ctx,
const char* key);
+PROJ_STRING_LIST PROJ_DLL proj_context_get_database_structure(
+ PJ_CONTEXT* ctx,
+ const char* const *options);
PJ_GUESSED_WKT_DIALECT PROJ_DLL proj_context_guess_wkt_dialect(PJ_CONTEXT *ctx,
const char *wkt);
diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp
index 696e3615..0bdb9287 100644
--- a/test/unit/test_c_api.cpp
+++ b/test/unit/test_c_api.cpp
@@ -1772,29 +1772,58 @@ TEST_F(CApi, proj_context_set_database_path_null) {
// ---------------------------------------------------------------------------
-TEST_F(CApi, proj_context_set_database_path_main_memory_one_aux) {
+TEST_F(CApi, proj_context_set_database_path_aux) {
+
+ const std::string auxDbName(
+ "file:proj_test_aux.db?mode=memory&cache=shared");
+
+ sqlite3 *dbAux = nullptr;
+ sqlite3_open_v2(
+ auxDbName.c_str(), &dbAux,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr);
+ ASSERT_TRUE(dbAux != nullptr);
+ ASSERT_TRUE(sqlite3_exec(dbAux, "BEGIN", nullptr, nullptr, nullptr) ==
+ SQLITE_OK);
+ {
+ auto ctxt = DatabaseContext::create();
+ const auto dbStructure = ctxt->getDatabaseStructure();
+ for (const auto &sql : dbStructure) {
+ ASSERT_TRUE(sqlite3_exec(dbAux, sql.c_str(), nullptr, nullptr,
+ nullptr) == SQLITE_OK);
+ }
+ }
- auto c_path = proj_context_get_database_path(m_ctxt);
- ASSERT_TRUE(c_path != nullptr);
- std::string path(c_path);
- const char *aux_db_list[] = {path.c_str(), nullptr};
-
- // This is super exotic and a miracle that it works. :memory: as the
- // main DB is empty. The real stuff is in the aux_db_list. No view
- // is created in the ':memory:' internal DB, but as there's only one
- // aux DB its tables and views can be directly queried...
- // If that breaks at some point, that wouldn't be a big issue.
- // Keeping that one as I had a hard time figuring out why it worked !
- // The real thing is tested by the C++
- // factory::attachExtraDatabases_auxiliary
- EXPECT_TRUE(proj_context_set_database_path(m_ctxt, ":memory:", aux_db_list,
- nullptr));
+ ASSERT_TRUE(sqlite3_exec(
+ dbAux,
+ "INSERT INTO geodetic_crs VALUES('OTHER','OTHER_4326','WGS "
+ "84',NULL,'geographic 2D','EPSG','6422','EPSG','6326',"
+ "NULL,0);",
+ nullptr, nullptr, nullptr) == SQLITE_OK);
+ ASSERT_TRUE(sqlite3_exec(dbAux, "COMMIT", nullptr, nullptr, nullptr) ==
+ SQLITE_OK);
- auto source_crs = proj_create_from_database(m_ctxt, "EPSG", "4326",
- PJ_CATEGORY_CRS, false,
- nullptr); // WGS84
- ASSERT_NE(source_crs, nullptr);
- ObjectKeeper keeper_source_crs(source_crs);
+ const char *const aux_db_list[] = {auxDbName.c_str(), nullptr};
+
+ EXPECT_TRUE(
+ proj_context_set_database_path(m_ctxt, nullptr, aux_db_list, nullptr));
+
+ sqlite3_close(dbAux);
+
+ {
+ auto crs = proj_create_from_database(m_ctxt, "EPSG", "4326",
+ PJ_CATEGORY_CRS, false,
+ nullptr); // WGS84
+ ASSERT_NE(crs, nullptr);
+ ObjectKeeper keeper_source_crs(crs);
+ }
+
+ {
+ auto crs = proj_create_from_database(m_ctxt, "OTHER", "OTHER_4326",
+ PJ_CATEGORY_CRS, false,
+ nullptr); // WGS84
+ ASSERT_NE(crs, nullptr);
+ ObjectKeeper keeper_source_crs(crs);
+ }
}
// ---------------------------------------------------------------------------
@@ -2724,6 +2753,15 @@ TEST_F(CApi, proj_context_get_database_metadata) {
// ---------------------------------------------------------------------------
+TEST_F(CApi, proj_context_get_database_structure) {
+ auto list = proj_context_get_database_structure(m_ctxt, nullptr);
+ ASSERT_NE(list, nullptr);
+ ASSERT_NE(list[0], nullptr);
+ proj_string_list_destroy(list);
+}
+
+// ---------------------------------------------------------------------------
+
TEST_F(CApi, proj_clone) {
auto obj = proj_create(m_ctxt, "+proj=longlat");
ObjectKeeper keeper(obj);
diff --git a/test/unit/test_factory.cpp b/test/unit/test_factory.cpp
index 5d336d0c..e0616baa 100644
--- a/test/unit/test_factory.cpp
+++ b/test/unit/test_factory.cpp
@@ -2828,10 +2828,12 @@ TEST(factory, attachExtraDatabases_auxiliary) {
ASSERT_TRUE(dbAux != nullptr);
ASSERT_TRUE(sqlite3_exec(dbAux, "BEGIN", nullptr, nullptr, nullptr) ==
SQLITE_OK);
+
+ std::vector<std::string> tableStructureBefore;
{
auto ctxt = DatabaseContext::create();
- const auto dbStructure = ctxt->getDatabaseStructure();
- for (const auto &sql : dbStructure) {
+ tableStructureBefore = ctxt->getDatabaseStructure();
+ for (const auto &sql : tableStructureBefore) {
if (sql.find("CREATE TRIGGER") == std::string::npos) {
ASSERT_TRUE(sqlite3_exec(dbAux, sql.c_str(), nullptr, nullptr,
nullptr) == SQLITE_OK);
@@ -2864,6 +2866,9 @@ TEST(factory, attachExtraDatabases_auxiliary) {
auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
EXPECT_TRUE(gcrs != nullptr);
}
+
+ const auto dbStructure = ctxt->getDatabaseStructure();
+ EXPECT_EQ(dbStructure, tableStructureBefore);
}
{