aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2021-06-09 19:58:12 +0200
committerGitHub <noreply@github.com>2021-06-09 19:58:12 +0200
commit5436e8aa46f7fb6dd6c9853e5da7222d8e6607e1 (patch)
tree55a7ac8146c327d54a9423db8a66a8dbbf54709c
parentfae861c75adbc598393245f34ad8871a9aaecd44 (diff)
parent61e25ec859a34e68fa8c2dc99f951f3e970875df (diff)
downloadPROJ-5436e8aa46f7fb6dd6c9853e5da7222d8e6607e1.tar.gz
PROJ-5436e8aa46f7fb6dd6c9853e5da7222d8e6607e1.zip
Merge pull request #2738 from rouault/share_sqlite_handle
Share SQLite database handle among all contexts
-rw-r--r--include/proj/internal/io_internal.hpp14
-rw-r--r--src/ctx.cpp11
-rw-r--r--src/iso19111/c_api.cpp91
-rw-r--r--src/iso19111/factory.cpp821
-rw-r--r--src/malloc.cpp13
-rw-r--r--src/proj_internal.h3
-rw-r--r--test/unit/test_c_api.cpp172
-rw-r--r--test/unit/test_factory.cpp2
8 files changed, 602 insertions, 525 deletions
diff --git a/include/proj/internal/io_internal.hpp b/include/proj/internal/io_internal.hpp
index ccc3787e..e0426b59 100644
--- a/include/proj/internal/io_internal.hpp
+++ b/include/proj/internal/io_internal.hpp
@@ -173,7 +173,6 @@ struct projCppContext {
PJ_CONTEXT *ctx_ = nullptr;
std::string dbPath_{};
std::vector<std::string> auxDbPaths_{};
- bool autoCloseDb_ = false;
projCppContext(const projCppContext &) = delete;
projCppContext &operator=(const projCppContext &) = delete;
@@ -201,18 +200,9 @@ struct projCppContext {
return auxDbPaths_;
}
- void setAutoCloseDb(bool autoClose) {
- autoCloseDb_ = autoClose;
- autoCloseDbIfNeeded();
- }
- inline bool getAutoCloseDb() const { return autoCloseDb_; }
-
- // cppcheck-suppress functionStatic
- void closeDb();
-
- void autoCloseDbIfNeeded();
-
NS_PROJ::io::DatabaseContextNNPtr getDatabaseContext();
+
+ void closeDb() { databaseContext_ = nullptr; }
};
//! @endcond
diff --git a/src/ctx.cpp b/src/ctx.cpp
index cc9df6c3..5ec10b9e 100644
--- a/src/ctx.cpp
+++ b/src/ctx.cpp
@@ -116,17 +116,6 @@ projCppContext* pj_ctx::get_cpp_context()
return cpp_context;
}
-/**************************************************************************/
-/* safeAutoCloseDbIfNeeded() */
-/**************************************************************************/
-
-void pj_ctx::safeAutoCloseDbIfNeeded()
-{
- if (cpp_context) {
- cpp_context->autoCloseDbIfNeeded();
- }
-}
-
/************************************************************************/
/* set_search_paths() */
/************************************************************************/
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index 2431f734..ace699e4 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -153,24 +153,11 @@ projCppContext::toVector(const char *const *auxDbPaths) {
projCppContext *projCppContext::clone(PJ_CONTEXT *ctx) const {
projCppContext *newContext =
new projCppContext(ctx, getDbPath().c_str(), getAuxDbPaths());
- newContext->setAutoCloseDb(getAutoCloseDb());
return newContext;
}
// ---------------------------------------------------------------------------
-void projCppContext::closeDb() { databaseContext_ = nullptr; }
-
-// ---------------------------------------------------------------------------
-
-void projCppContext::autoCloseDbIfNeeded() {
- if (getAutoCloseDb()) {
- closeDb();
- }
-}
-
-// ---------------------------------------------------------------------------
-
NS_PROJ::io::DatabaseContextNNPtr projCppContext::getDatabaseContext() {
if (databaseContext_) {
return NN_NO_CHECK(databaseContext_);
@@ -215,7 +202,6 @@ static PJ *pj_obj_create(PJ_CONTEXT *ctx, const IdentifiedObjectNNPtr &objIn) {
ctx->defer_grid_opening = false;
if (pj) {
pj->iso_obj = objIn;
- ctx->safeAutoCloseDbIfNeeded();
return pj;
}
} catch (const std::exception &) {
@@ -249,7 +235,6 @@ static PJ *pj_obj_create(PJ_CONTEXT *ctx, const IdentifiedObjectNNPtr &objIn) {
} catch (const std::exception &) {
}
}
- ctx->safeAutoCloseDbIfNeeded();
return pj;
}
//! @endcond
@@ -289,19 +274,19 @@ PJ_OBJ_LIST::~PJ_OBJ_LIST() = default;
// ---------------------------------------------------------------------------
-/** \brief Set if the database must be closed after each C API call where it
- * has been opened, and automatically re-opened when needed.
+/** \brief Starting with PROJ 8.1, this function does nothing.
*
- * The default value is FALSE, that is the database remains open until the
- * context is destroyed.
+ * If you want to take into account changes to the PROJ database, you need to
+ * re-create a new context.
*
- * @param ctx PROJ context, or NULL for default context
- * @param autoclose Boolean parameter
+ * @param ctx Ignored
+ * @param autoclose Ignored
* @since 6.2
+ * @deprecated Since 8.1
*/
void proj_context_set_autoclose_database(PJ_CONTEXT *ctx, int autoclose) {
- SANITIZE_CTX(ctx);
- ctx->get_cpp_context()->setAutoCloseDb(autoclose != FALSE);
+ (void)ctx;
+ (void)autoclose;
}
// ---------------------------------------------------------------------------
@@ -330,27 +315,22 @@ int proj_context_set_database_path(PJ_CONTEXT *ctx, const char *dbPath,
(void)options;
std::string osPrevDbPath;
std::vector<std::string> osPrevAuxDbPaths;
- bool autoCloseDb = false;
if (ctx->cpp_context) {
osPrevDbPath = ctx->cpp_context->getDbPath();
osPrevAuxDbPaths = ctx->cpp_context->getAuxDbPaths();
- autoCloseDb = ctx->cpp_context->getAutoCloseDb();
}
delete ctx->cpp_context;
ctx->cpp_context = nullptr;
try {
ctx->cpp_context = new projCppContext(
ctx, dbPath, projCppContext::toVector(auxDbPaths));
- ctx->cpp_context->setAutoCloseDb(autoCloseDb);
ctx->cpp_context->getDatabaseContext();
- ctx->safeAutoCloseDbIfNeeded();
return true;
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
delete ctx->cpp_context;
ctx->cpp_context =
new projCppContext(ctx, osPrevDbPath.c_str(), osPrevAuxDbPaths);
- ctx->cpp_context->setAutoCloseDb(autoCloseDb);
return false;
}
}
@@ -372,7 +352,6 @@ const char *proj_context_get_database_path(PJ_CONTEXT *ctx) {
// ctx->cpp_context
auto osPath(getDBcontext(ctx)->getPath());
ctx->get_cpp_context()->lastDbPath_ = osPath;
- ctx->safeAutoCloseDbIfNeeded();
return ctx->cpp_context->lastDbPath_.c_str();
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
@@ -404,11 +383,9 @@ const char *proj_context_get_database_metadata(PJ_CONTEXT *ctx,
// ctx->cpp_context
auto osVal(getDBcontext(ctx)->getMetadata(key));
if (osVal == nullptr) {
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
ctx->get_cpp_context()->lastDbMetadataItem_ = osVal;
- ctx->safeAutoCloseDbIfNeeded();
return ctx->cpp_context->lastDbMetadataItem_.c_str();
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
@@ -437,7 +414,6 @@ proj_context_get_database_structure(PJ_CONTEXT *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());
@@ -574,7 +550,6 @@ PJ *proj_create(PJ_CONTEXT *ctx, const char *text) {
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -697,7 +672,6 @@ PJ *proj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt,
proj_log_error(ctx, __FUNCTION__, e.what());
}
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -762,7 +736,6 @@ PJ *proj_create_from_database(PJ_CONTEXT *ctx, const char *auth_name,
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -848,12 +821,10 @@ int proj_uom_get_info_from_database(PJ_CONTEXT *ctx, const char *auth_name,
if (out_category) {
*out_category = get_unit_category(obj->name(), obj->type());
}
- ctx->safeAutoCloseDbIfNeeded();
return true;
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
}
- ctx->safeAutoCloseDbIfNeeded();
return false;
}
@@ -898,7 +869,6 @@ int PROJ_DLL proj_grid_get_info_from_database(
ctx->get_cpp_context()->lastGridPackageName_,
ctx->get_cpp_context()->lastGridUrl_, direct_download,
open_license, available)) {
- ctx->safeAutoCloseDbIfNeeded();
return false;
}
@@ -916,12 +886,10 @@ int PROJ_DLL proj_grid_get_info_from_database(
if (out_available)
*out_available = available ? 1 : 0;
- ctx->safeAutoCloseDbIfNeeded();
return true;
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
}
- ctx->safeAutoCloseDbIfNeeded();
return false;
}
@@ -957,12 +925,10 @@ PJ_OBJ_LIST *proj_query_geodetic_crs_from_datum(PJ_CONTEXT *ctx,
for (const auto &obj : res) {
objects.push_back(obj);
}
- ctx->safeAutoCloseDbIfNeeded();
return new PJ_OBJ_LIST(std::move(objects));
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -1141,12 +1107,10 @@ PJ_OBJ_LIST *proj_create_from_name(PJ_CONTEXT *ctx, const char *auth_name,
for (const auto &obj : res) {
objects.push_back(obj);
}
- ctx->safeAutoCloseDbIfNeeded();
return new PJ_OBJ_LIST(std::move(objects));
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -1296,12 +1260,10 @@ PJ_OBJ_LIST *proj_get_non_deprecated(PJ_CONTEXT *ctx, const PJ *obj) {
for (const auto &resObj : res) {
objects.push_back(resObj);
}
- ctx->safeAutoCloseDbIfNeeded();
return new PJ_OBJ_LIST(std::move(objects));
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -1352,9 +1314,6 @@ static int proj_is_equivalent_to_internal(PJ_CONTEXT *ctx, const PJ *obj,
other->iso_obj.get(), cppCriterion,
ctx ? getDBcontextNoException(ctx, "proj_is_equivalent_to_with_ctx")
: nullptr);
- if (ctx) {
- ctx->safeAutoCloseDbIfNeeded();
- }
return res;
}
@@ -1587,16 +1546,13 @@ const char *proj_as_wkt(PJ_CONTEXT *ctx, const PJ *obj, PJ_WKT_TYPE type,
std::string msg("Unknown option :");
msg += *iter;
proj_log_error(ctx, __FUNCTION__, msg.c_str());
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
}
obj->lastWKT = obj->iso_obj->exportToWKT(formatter.get());
- ctx->safeAutoCloseDbIfNeeded();
return obj->lastWKT.c_str();
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
}
@@ -1683,11 +1639,9 @@ const char *proj_as_proj_string(PJ_CONTEXT *ctx, const PJ *obj,
}
}
obj->lastPROJString = exportable->exportToPROJString(formatter.get());
- ctx->safeAutoCloseDbIfNeeded();
return obj->lastPROJString.c_str();
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
}
@@ -2089,7 +2043,6 @@ PJ *proj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx, const PJ *crs,
std::string msg("Unknown option :");
msg += *iter;
proj_log_error(ctx, __FUNCTION__, msg.c_str());
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
}
@@ -2097,7 +2050,6 @@ PJ *proj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx, const PJ *crs,
dbContext, allowIntermediateCRS));
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
}
@@ -2154,7 +2106,6 @@ PJ *proj_crs_create_bound_vertical_crs(PJ_CONTEXT *ctx, const PJ *vert_crs,
BoundCRS::create(nnCRS, nnHubCRS, transformation));
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
}
@@ -2585,14 +2536,12 @@ PJ_OBJ_LIST *proj_identify(PJ_CONTEXT *ctx, const PJ *obj,
*out_confidence = confidenceTemp;
confidenceTemp = nullptr;
}
- ctx->safeAutoCloseDbIfNeeded();
return ret.release();
} catch (const std::exception &e) {
delete[] confidenceTemp;
proj_log_error(ctx, __FUNCTION__, e.what());
}
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -2617,12 +2566,10 @@ PROJ_STRING_LIST proj_get_authorities_from_database(PJ_CONTEXT *ctx) {
SANITIZE_CTX(ctx);
try {
auto ret = to_string_list(getDBcontext(ctx)->getAuthorities());
- ctx->safeAutoCloseDbIfNeeded();
return ret;
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -2662,13 +2609,11 @@ PROJ_STRING_LIST proj_get_codes_from_database(PJ_CONTEXT *ctx,
}
auto ret = to_string_list(
factory->getAuthorityCodes(typeInternal, allow_deprecated != 0));
- ctx->safeAutoCloseDbIfNeeded();
return ret;
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -2708,7 +2653,6 @@ PROJ_CELESTIAL_BODY_INFO **proj_get_celestial_body_list_from_database(
ret[i] = nullptr;
if (out_result_count)
*out_result_count = i;
- ctx->safeAutoCloseDbIfNeeded();
return ret;
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
@@ -2719,7 +2663,6 @@ PROJ_CELESTIAL_BODY_INFO **proj_get_celestial_body_list_from_database(
if (out_result_count)
*out_result_count = 0;
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -2937,7 +2880,6 @@ proj_get_crs_info_list_from_database(PJ_CONTEXT *ctx, const char *auth_name,
ret[i] = nullptr;
if (out_result_count)
*out_result_count = i;
- ctx->safeAutoCloseDbIfNeeded();
return ret;
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
@@ -2948,7 +2890,6 @@ proj_get_crs_info_list_from_database(PJ_CONTEXT *ctx, const char *auth_name,
if (out_result_count)
*out_result_count = 0;
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -3030,7 +2971,6 @@ PROJ_UNIT_INFO **proj_get_units_from_database(PJ_CONTEXT *ctx,
ret[i] = nullptr;
if (out_result_count)
*out_result_count = i;
- ctx->safeAutoCloseDbIfNeeded();
return ret;
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
@@ -3041,7 +2981,6 @@ PROJ_UNIT_INFO **proj_get_units_from_database(PJ_CONTEXT *ctx,
if (out_result_count)
*out_result_count = 0;
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -3335,7 +3274,6 @@ PJ *proj_create_geographic_crs(PJ_CONTEXT *ctx, const char *crs_name,
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -3382,7 +3320,6 @@ PJ *proj_create_geographic_crs_from_datum(PJ_CONTEXT *ctx, const char *crs_name,
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -4029,7 +3966,6 @@ PJ *proj_crs_promote_to_3D(PJ_CONTEXT *ctx, const char *crs_3D_name,
dbContext));
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
}
@@ -4116,7 +4052,6 @@ PJ *proj_crs_create_projected_3D_crs_from_2D(PJ_CONTEXT *ctx,
cpp_projected_2D_crs->derivingConversion(), newCS));
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
} else {
@@ -4129,7 +4064,6 @@ PJ *proj_crs_create_projected_3D_crs_from_2D(PJ_CONTEXT *ctx,
dbContext));
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
}
@@ -4175,7 +4109,6 @@ PJ *proj_crs_demote_to_2D(PJ_CONTEXT *ctx, const char *crs_2D_name,
dbContext));
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
}
@@ -7187,10 +7120,8 @@ int proj_coordoperation_is_instantiable(PJ_CONTEXT *ctx,
dbContext, proj_context_is_network_enabled(ctx) != FALSE)
? 1
: 0;
- ctx->safeAutoCloseDbIfNeeded();
return ret;
} catch (const std::exception &) {
- ctx->safeAutoCloseDbIfNeeded();
return 0;
}
}
@@ -7525,11 +7456,9 @@ int proj_coordoperation_get_grid_used_count(PJ_CONTEXT *ctx,
coordoperation->gridsNeeded.emplace_back(gridDesc);
}
}
- ctx->safeAutoCloseDbIfNeeded();
return static_cast<int>(coordoperation->gridsNeeded.size());
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
- ctx->safeAutoCloseDbIfNeeded();
return 0;
}
}
@@ -7657,7 +7586,6 @@ proj_create_operation_factory_context(PJ_CONTEXT *ctx, const char *authority) {
std::string(authority ? authority : ""));
auto operationContext =
CoordinateOperationContext::create(authFactory, nullptr, 0.0);
- ctx->safeAutoCloseDbIfNeeded();
return new PJ_OPERATION_FACTORY_CONTEXT(
std::move(operationContext));
} else {
@@ -7669,7 +7597,6 @@ proj_create_operation_factory_context(PJ_CONTEXT *ctx, const char *authority) {
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
@@ -9195,12 +9122,10 @@ proj_get_geoid_models_from_database(PJ_CONTEXT *ctx, const char *auth_name,
const std::string codeStr(code);
auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name);
auto geoidModels = factory->getGeoidModels(codeStr);
- ctx->safeAutoCloseDbIfNeeded();
return to_string_list(std::move(geoidModels));
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
}
- ctx->safeAutoCloseDbIfNeeded();
return nullptr;
}
diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp
index cea0759c..42055f90 100644
--- a/src/iso19111/factory.cpp
+++ b/src/iso19111/factory.cpp
@@ -42,6 +42,7 @@
#include "proj/internal/internal.hpp"
#include "proj/internal/io_internal.hpp"
#include "proj/internal/lru_cache.hpp"
+#include "proj/internal/mutex.hpp"
#include "proj/internal/tracing.hpp"
#include "operation/coordinateoperation_internal.hpp"
@@ -145,6 +146,464 @@ using ListOfParams = std::list<SQLValues>;
// ---------------------------------------------------------------------------
+static double PROJ_SQLITE_GetValAsDouble(sqlite3_value *val, bool &gotVal) {
+ switch (sqlite3_value_type(val)) {
+ case SQLITE_FLOAT:
+ gotVal = true;
+ return sqlite3_value_double(val);
+
+ case SQLITE_INTEGER:
+ gotVal = true;
+ return static_cast<double>(sqlite3_value_int64(val));
+
+ default:
+ gotVal = false;
+ return 0.0;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static void PROJ_SQLITE_pseudo_area_from_swne(sqlite3_context *pContext,
+ int /* argc */,
+ sqlite3_value **argv) {
+ bool b0, b1, b2, b3;
+ double south_lat = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
+ double west_lon = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
+ double north_lat = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
+ double east_lon = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
+ if (!b0 || !b1 || !b2 || !b3) {
+ sqlite3_result_null(pContext);
+ return;
+ }
+ // Deal with area crossing antimeridian
+ if (east_lon < west_lon) {
+ east_lon += 360.0;
+ }
+ // Integrate cos(lat) between south_lat and north_lat
+ double pseudo_area = (east_lon - west_lon) *
+ (std::sin(common::Angle(north_lat).getSIValue()) -
+ std::sin(common::Angle(south_lat).getSIValue()));
+ sqlite3_result_double(pContext, pseudo_area);
+}
+
+// ---------------------------------------------------------------------------
+
+static void PROJ_SQLITE_intersects_bbox(sqlite3_context *pContext,
+ int /* argc */, sqlite3_value **argv) {
+ bool b0, b1, b2, b3, b4, b5, b6, b7;
+ double south_lat1 = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
+ double west_lon1 = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
+ double north_lat1 = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
+ double east_lon1 = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
+ double south_lat2 = PROJ_SQLITE_GetValAsDouble(argv[4], b4);
+ double west_lon2 = PROJ_SQLITE_GetValAsDouble(argv[5], b5);
+ double north_lat2 = PROJ_SQLITE_GetValAsDouble(argv[6], b6);
+ double east_lon2 = PROJ_SQLITE_GetValAsDouble(argv[7], b7);
+ if (!b0 || !b1 || !b2 || !b3 || !b4 || !b5 || !b6 || !b7) {
+ sqlite3_result_null(pContext);
+ return;
+ }
+ auto bbox1 = metadata::GeographicBoundingBox::create(west_lon1, south_lat1,
+ east_lon1, north_lat1);
+ auto bbox2 = metadata::GeographicBoundingBox::create(west_lon2, south_lat2,
+ east_lon2, north_lat2);
+ sqlite3_result_int(pContext, bbox1->intersects(bbox2) ? 1 : 0);
+}
+
+// ---------------------------------------------------------------------------
+
+class SQLiteHandle {
+ sqlite3 *sqlite_handle_ = nullptr;
+ bool close_handle_ = true;
+
+ int nLayoutVersionMajor_ = 0;
+ int nLayoutVersionMinor_ = 0;
+
+#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
+ std::unique_ptr<SQLite3VFS> vfs_{};
+#endif
+
+ SQLiteHandle(const SQLiteHandle &) = delete;
+ SQLiteHandle &operator=(const SQLiteHandle &) = delete;
+
+ SQLiteHandle(sqlite3 *sqlite_handle, bool close_handle)
+ : sqlite_handle_(sqlite_handle), close_handle_(close_handle) {
+ assert(sqlite_handle_);
+ }
+
+ // cppcheck-suppress functionStatic
+ void registerFunctions();
+
+ SQLResultSet run(const std::string &sql,
+ const ListOfParams &parameters = ListOfParams(),
+ bool useMaxFloatPrecision = false);
+
+ public:
+ ~SQLiteHandle();
+
+ sqlite3 *handle() { return sqlite_handle_; }
+
+ static std::shared_ptr<SQLiteHandle> open(PJ_CONTEXT *ctx,
+ const std::string &path);
+
+ // might not be shared between thread depending how the handle was opened!
+ static std::shared_ptr<SQLiteHandle>
+ initFromExisting(sqlite3 *sqlite_handle, bool close_handle,
+ int nLayoutVersionMajor, int nLayoutVersionMinor);
+
+ static std::unique_ptr<SQLiteHandle>
+ initFromExistingUniquePtr(sqlite3 *sqlite_handle, bool close_handle);
+
+ void checkDatabaseLayout(const std::string &mainDbPath,
+ const std::string &path,
+ const std::string &dbNamePrefix);
+
+ SQLResultSet run(sqlite3_stmt *stmt, const std::string &sql,
+ const ListOfParams &parameters = ListOfParams(),
+ bool useMaxFloatPrecision = false);
+
+ inline int getLayoutVersionMajor() const { return nLayoutVersionMajor_; }
+ inline int getLayoutVersionMinor() const { return nLayoutVersionMinor_; }
+};
+
+// ---------------------------------------------------------------------------
+
+SQLiteHandle::~SQLiteHandle() {
+ if (close_handle_) {
+ sqlite3_close(sqlite_handle_);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+std::shared_ptr<SQLiteHandle> SQLiteHandle::open(PJ_CONTEXT *ctx,
+ const std::string &path) {
+
+ const int sqlite3VersionNumber = sqlite3_libversion_number();
+ // Minimum version for correct performance: 3.11
+ if (sqlite3VersionNumber < 3 * 1000000 + 11 * 1000) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "SQLite3 version is %s, whereas at least 3.11 should be used",
+ sqlite3_libversion());
+ }
+
+ std::string vfsName;
+#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
+ std::unique_ptr<SQLite3VFS> vfs;
+ if (ctx->custom_sqlite3_vfs_name.empty()) {
+ vfs = SQLite3VFS::create(false, true, true);
+ if (vfs == nullptr) {
+ throw FactoryException("Open of " + path + " failed");
+ }
+ vfsName = vfs->name();
+ } else
+#endif
+ {
+ vfsName = ctx->custom_sqlite3_vfs_name;
+ }
+ sqlite3 *sqlite_handle = nullptr;
+ // SQLITE_OPEN_FULLMUTEX as this will be used from concurrent threads
+ if (sqlite3_open_v2(path.c_str(), &sqlite_handle,
+ SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX,
+ vfsName.empty() ? nullptr : vfsName.c_str()) !=
+ SQLITE_OK ||
+ !sqlite_handle) {
+ if (sqlite_handle != nullptr) {
+ sqlite3_close(sqlite_handle);
+ }
+ throw FactoryException("Open of " + path + " failed");
+ }
+ auto handle =
+ std::shared_ptr<SQLiteHandle>(new SQLiteHandle(sqlite_handle, true));
+#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
+ handle->vfs_ = std::move(vfs);
+#endif
+ handle->registerFunctions();
+ handle->checkDatabaseLayout(path, path, std::string());
+ return handle;
+}
+
+// ---------------------------------------------------------------------------
+
+std::shared_ptr<SQLiteHandle>
+SQLiteHandle::initFromExisting(sqlite3 *sqlite_handle, bool close_handle,
+ int nLayoutVersionMajor,
+ int nLayoutVersionMinor) {
+ auto handle = std::shared_ptr<SQLiteHandle>(
+ new SQLiteHandle(sqlite_handle, close_handle));
+ handle->nLayoutVersionMajor_ = nLayoutVersionMajor;
+ handle->nLayoutVersionMinor_ = nLayoutVersionMinor;
+ handle->registerFunctions();
+ return handle;
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<SQLiteHandle>
+SQLiteHandle::initFromExistingUniquePtr(sqlite3 *sqlite_handle,
+ bool close_handle) {
+ auto handle = std::unique_ptr<SQLiteHandle>(
+ new SQLiteHandle(sqlite_handle, close_handle));
+ handle->registerFunctions();
+ return handle;
+}
+
+// ---------------------------------------------------------------------------
+
+SQLResultSet SQLiteHandle::run(sqlite3_stmt *stmt, const std::string &sql,
+ const ListOfParams &parameters,
+ bool useMaxFloatPrecision) {
+ int nBindField = 1;
+ for (const auto &param : parameters) {
+ const auto paramType = param.type();
+ if (paramType == SQLValues::Type::STRING) {
+ auto strValue = param.stringValue();
+ sqlite3_bind_text(stmt, nBindField, strValue.c_str(),
+ static_cast<int>(strValue.size()),
+ SQLITE_TRANSIENT);
+ } else if (paramType == SQLValues::Type::INT) {
+ sqlite3_bind_int(stmt, nBindField, param.intValue());
+ } else {
+ assert(paramType == SQLValues::Type::DOUBLE);
+ sqlite3_bind_double(stmt, nBindField, param.doubleValue());
+ }
+ nBindField++;
+ }
+
+#ifdef TRACE_DATABASE
+ size_t nPos = 0;
+ std::string sqlSubst(sql);
+ for (const auto &param : parameters) {
+ nPos = sqlSubst.find('?', nPos);
+ assert(nPos != std::string::npos);
+ std::string strValue;
+ const auto paramType = param.type();
+ if (paramType == SQLValues::Type::STRING) {
+ strValue = '\'' + param.stringValue() + '\'';
+ } else if (paramType == SQLValues::Type::INT) {
+ strValue = toString(param.intValue());
+ } else {
+ strValue = toString(param.doubleValue());
+ }
+ sqlSubst =
+ sqlSubst.substr(0, nPos) + strValue + sqlSubst.substr(nPos + 1);
+ nPos += strValue.size();
+ }
+ logTrace(sqlSubst, "DATABASE");
+#endif
+
+ SQLResultSet result;
+ const int column_count = sqlite3_column_count(stmt);
+ while (true) {
+ int ret = sqlite3_step(stmt);
+ if (ret == SQLITE_ROW) {
+ SQLRow row(column_count);
+ for (int i = 0; i < column_count; i++) {
+ if (useMaxFloatPrecision &&
+ sqlite3_column_type(stmt, i) == SQLITE_FLOAT) {
+ // sqlite3_column_text() does not use maximum precision
+ std::ostringstream buffer;
+ buffer.imbue(std::locale::classic());
+ buffer << std::setprecision(18);
+ buffer << sqlite3_column_double(stmt, i);
+ row[i] = buffer.str();
+ } else {
+ const char *txt = reinterpret_cast<const char *>(
+ sqlite3_column_text(stmt, i));
+ if (txt) {
+ row[i] = txt;
+ }
+ }
+ }
+ result.emplace_back(std::move(row));
+ } else if (ret == SQLITE_DONE) {
+ break;
+ } else {
+ throw FactoryException("SQLite error on " + sql + ": " +
+ sqlite3_errmsg(sqlite_handle_));
+ }
+ }
+ return result;
+}
+
+// ---------------------------------------------------------------------------
+
+SQLResultSet SQLiteHandle::run(const std::string &sql,
+ const ListOfParams &parameters,
+ bool useMaxFloatPrecision) {
+ sqlite3_stmt *stmt = nullptr;
+ try {
+ if (sqlite3_prepare_v2(sqlite_handle_, sql.c_str(),
+ static_cast<int>(sql.size()), &stmt,
+ nullptr) != SQLITE_OK) {
+ throw FactoryException("SQLite error on " + sql + ": " +
+ sqlite3_errmsg(sqlite_handle_));
+ }
+ auto ret = run(stmt, sql, parameters, useMaxFloatPrecision);
+ sqlite3_finalize(stmt);
+ return ret;
+ } catch (const std::exception &) {
+ if (stmt)
+ sqlite3_finalize(stmt);
+ throw;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void SQLiteHandle::checkDatabaseLayout(const std::string &mainDbPath,
+ 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.
+ static_assert(
+ // cppcheck-suppress knownConditionTrueFalse
+ DATABASE_LAYOUT_VERSION_MAJOR == 1 &&
+ // cppcheck-suppress knownConditionTrueFalse
+ DATABASE_LAYOUT_VERSION_MINOR == 1,
+ "remove that assertion and below lines next time we upgrade "
+ "database structure");
+ res = run("SELECT 1 FROM metadata WHERE key = 'EPSG.VERSION' AND "
+ "value = 'v10.003'");
+ if (!res.empty()) {
+ return;
+ }
+
+ throw FactoryException(
+ path + " lacks DATABASE.LAYOUT.VERSION.MAJOR / "
+ "DATABASE.LAYOUT.VERSION.MINOR "
+ "metadata. It comes from another PROJ installation.");
+ }
+ int major = 0;
+ int minor = 0;
+ for (const auto &row : res) {
+ if (row[0] == "DATABASE.LAYOUT.VERSION.MAJOR") {
+ major = atoi(row[1].c_str());
+ } else if (row[0] == "DATABASE.LAYOUT.VERSION.MINOR") {
+ minor = atoi(row[1].c_str());
+ }
+ }
+ if (major != DATABASE_LAYOUT_VERSION_MAJOR) {
+ throw FactoryException(
+ path +
+ " contains DATABASE.LAYOUT.VERSION.MAJOR = " + toString(major) +
+ " whereas " + toString(DATABASE_LAYOUT_VERSION_MAJOR) +
+ " is expected. "
+ "It comes from another PROJ installation.");
+ }
+ // Database layout v1.0 of PROJ 8.0 is forward compatible with v1.1
+ static_assert(
+ // cppcheck-suppress knownConditionTrueFalse
+ DATABASE_LAYOUT_VERSION_MAJOR == 1 &&
+ // cppcheck-suppress knownConditionTrueFalse
+ DATABASE_LAYOUT_VERSION_MINOR == 1,
+ "re-enable the check below if database layout v1.0 and v1.1 is no "
+ "longer compatible");
+#if 0
+ if (minor < DATABASE_LAYOUT_VERSION_MINOR) {
+ throw FactoryException(
+ path +
+ " contains DATABASE.LAYOUT.VERSION.MINOR = " + toString(minor) +
+ " whereas a number >= " + toString(DATABASE_LAYOUT_VERSION_MINOR) +
+ " is expected. "
+ "It comes from another PROJ installation.");
+ }
+#endif
+ 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 " +
+ mainDbPath + " which is " + toString(nLayoutVersionMajor_) + '.' +
+ toString(nLayoutVersionMinor_));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+#ifndef SQLITE_DETERMINISTIC
+#define SQLITE_DETERMINISTIC 0
+#endif
+
+void SQLiteHandle::registerFunctions() {
+ sqlite3_create_function(sqlite_handle_, "pseudo_area_from_swne", 4,
+ SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
+ PROJ_SQLITE_pseudo_area_from_swne, nullptr,
+ nullptr);
+
+ sqlite3_create_function(sqlite_handle_, "intersects_bbox", 8,
+ SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
+ PROJ_SQLITE_intersects_bbox, nullptr, nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+class SQLiteHandleCache {
+ NS_PROJ::mutex sMutex_{};
+
+ // Map dbname to SQLiteHandle
+ lru11::Cache<std::string, std::shared_ptr<SQLiteHandle>> cache_{};
+
+ public:
+ static SQLiteHandleCache &get();
+
+ std::shared_ptr<SQLiteHandle> getHandle(const std::string &path,
+ PJ_CONTEXT *ctx);
+
+ void clear();
+};
+
+// ---------------------------------------------------------------------------
+
+SQLiteHandleCache &SQLiteHandleCache::get() {
+ // Global cache
+ static SQLiteHandleCache gSQLiteHandleCache;
+ return gSQLiteHandleCache;
+}
+
+// ---------------------------------------------------------------------------
+
+void SQLiteHandleCache::clear() {
+ NS_PROJ::lock_guard<NS_PROJ::mutex> lock(sMutex_);
+ cache_.clear();
+}
+
+// ---------------------------------------------------------------------------
+
+std::shared_ptr<SQLiteHandle>
+SQLiteHandleCache::getHandle(const std::string &path, PJ_CONTEXT *ctx) {
+ NS_PROJ::lock_guard<NS_PROJ::mutex> lock(sMutex_);
+ std::shared_ptr<SQLiteHandle> handle;
+ std::string key = path + ctx->custom_sqlite3_vfs_name;
+ if (!cache_.tryGet(key, handle)) {
+ handle = SQLiteHandle::open(ctx, path);
+ cache_.insert(key, handle);
+ }
+ return handle;
+}
+
+// ---------------------------------------------------------------------------
+
struct DatabaseContext::Private {
Private();
~Private();
@@ -152,7 +611,9 @@ struct DatabaseContext::Private {
void open(const std::string &databasePath, PJ_CONTEXT *ctx);
void setHandle(sqlite3 *sqlite_handle);
- sqlite3 *handle() const { return sqlite_handle_; }
+ sqlite3 *handle() const {
+ return sqlite_handle_ ? sqlite_handle_->handle() : nullptr;
+ }
PJ_CONTEXT *pjCtxt() const { return pjCtxt_; }
void setPjCtxt(PJ_CONTEXT *ctxt) { pjCtxt_ = ctxt; }
@@ -272,10 +733,7 @@ struct DatabaseContext::Private {
std::string databasePath_{};
std::vector<std::string> auxiliaryDatabasePaths_{};
- bool close_handle_ = true;
- sqlite3 *sqlite_handle_{};
- int nLayoutVersionMajor_ = 0;
- int nLayoutVersionMinor_ = 0;
+ std::shared_ptr<SQLiteHandle> sqlite_handle_{};
std::map<std::string, sqlite3_stmt *> mapSqlToStatement_{};
PJ_CONTEXT *pjCtxt_ = nullptr;
int recLevel_ = 0;
@@ -285,7 +743,7 @@ struct DatabaseContext::Private {
// Used by startInsertStatementsSession() and related functions
std::string memoryDbForInsertPath_{};
- sqlite3 *memoryDbHandle_ = nullptr;
+ std::unique_ptr<SQLiteHandle> memoryDbHandle_{};
using LRUCacheOfObjects = lru11::Cache<std::string, util::BaseObjectPtr>;
@@ -307,9 +765,6 @@ struct DatabaseContext::Private {
lru11::Cache<std::string, std::list<std::string>> cacheAliasNames_{
CACHE_SIZE};
- void checkDatabaseLayout(const std::string &path,
- const std::string &dbNamePrefix);
-
static void insertIntoCache(LRUCacheOfObjects &cache,
const std::string &code,
const util::BaseObjectPtr &obj);
@@ -321,9 +776,6 @@ struct DatabaseContext::Private {
void clearCaches();
- // cppcheck-suppress functionStatic
- void registerFunctions();
-
std::string findFreeCode(const std::string &tableName,
const std::string &authName,
const std::string &codePrototype);
@@ -412,10 +864,6 @@ struct DatabaseContext::Private {
bool numericCode,
const std::vector<std::string> &allowedAuthorities);
-#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
- std::unique_ptr<SQLite3VFS> vfs_{};
-#endif
-
Private(const Private &) = delete;
Private &operator=(const Private &) = delete;
};
@@ -460,10 +908,7 @@ void DatabaseContext::Private::closeDB() noexcept {
}
mapSqlToStatement_.clear();
- if (close_handle_ && sqlite_handle_ != nullptr) {
- sqlite3_close(sqlite_handle_);
- sqlite_handle_ = nullptr;
- }
+ sqlite_handle_.reset();
}
// ---------------------------------------------------------------------------
@@ -665,14 +1110,6 @@ void DatabaseContext::Private::open(const std::string &databasePath,
ctx = pj_get_default_ctx();
}
- const int sqlite3VersionNumber = sqlite3_libversion_number();
- // Minimum version for correct performance: 3.11
- if (sqlite3VersionNumber < 3 * 1000000 + 11 * 1000) {
- pj_log(ctx, PJ_LOG_ERROR,
- "SQLite3 version is %s, whereas at least 3.11 should be used",
- sqlite3_libversion());
- }
-
setPjCtxt(ctx);
std::string path(databasePath);
if (path.empty()) {
@@ -685,117 +1122,9 @@ void DatabaseContext::Private::open(const std::string &databasePath,
}
}
- std::string vfsName;
-#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
- if (ctx->custom_sqlite3_vfs_name.empty()) {
- vfs_ = SQLite3VFS::create(false, true, true);
- if (vfs_ == nullptr) {
- throw FactoryException("Open of " + path + " failed");
- }
- vfsName = vfs_->name();
- } else
-#endif
- {
- vfsName = ctx->custom_sqlite3_vfs_name;
- }
- if (sqlite3_open_v2(path.c_str(), &sqlite_handle_,
- SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX,
- vfsName.empty() ? nullptr : vfsName.c_str()) !=
- SQLITE_OK ||
- !sqlite_handle_) {
- throw FactoryException("Open of " + path + " failed");
- }
+ sqlite_handle_ = SQLiteHandleCache::get().getHandle(path, ctx);
databasePath_ = path;
- registerFunctions();
-}
-
-// ---------------------------------------------------------------------------
-
-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.
- static_assert(
- // cppcheck-suppress knownConditionTrueFalse
- DATABASE_LAYOUT_VERSION_MAJOR == 1 &&
- // cppcheck-suppress knownConditionTrueFalse
- DATABASE_LAYOUT_VERSION_MINOR == 1,
- "remove that assertion and below lines next time we upgrade "
- "database structure");
- res = run("SELECT 1 FROM metadata WHERE key = 'EPSG.VERSION' AND "
- "value = 'v10.003'");
- if (!res.empty()) {
- return;
- }
-
- throw FactoryException(
- path + " lacks DATABASE.LAYOUT.VERSION.MAJOR / "
- "DATABASE.LAYOUT.VERSION.MINOR "
- "metadata. It comes from another PROJ installation.");
- }
- int major = 0;
- int minor = 0;
- for (const auto &row : res) {
- if (row[0] == "DATABASE.LAYOUT.VERSION.MAJOR") {
- major = atoi(row[1].c_str());
- } else if (row[0] == "DATABASE.LAYOUT.VERSION.MINOR") {
- minor = atoi(row[1].c_str());
- }
- }
- if (major != DATABASE_LAYOUT_VERSION_MAJOR) {
- throw FactoryException(
- path +
- " contains DATABASE.LAYOUT.VERSION.MAJOR = " + toString(major) +
- " whereas " + toString(DATABASE_LAYOUT_VERSION_MAJOR) +
- " is expected. "
- "It comes from another PROJ installation.");
- }
- // Database layout v1.0 of PROJ 8.0 is forward compatible with v1.1
- static_assert(
- // cppcheck-suppress knownConditionTrueFalse
- DATABASE_LAYOUT_VERSION_MAJOR == 1 &&
- // cppcheck-suppress knownConditionTrueFalse
- DATABASE_LAYOUT_VERSION_MINOR == 1,
- "re-enable the check below if database layout v1.0 and v1.1 is no "
- "longer compatible");
-#if 0
- if (minor < DATABASE_LAYOUT_VERSION_MINOR) {
- throw FactoryException(
- path +
- " contains DATABASE.LAYOUT.VERSION.MINOR = " + toString(minor) +
- " whereas a number >= " + toString(DATABASE_LAYOUT_VERSION_MINOR) +
- " is expected. "
- "It comes from another PROJ installation.");
- }
-#endif
- 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_));
- }
}
// ---------------------------------------------------------------------------
@@ -804,10 +1133,7 @@ void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) {
assert(sqlite_handle);
assert(!sqlite_handle_);
- sqlite_handle_ = sqlite_handle;
- close_handle_ = false;
-
- registerFunctions();
+ sqlite_handle_ = SQLiteHandle::initFromExisting(sqlite_handle, false, 0, 0);
}
// ---------------------------------------------------------------------------
@@ -829,13 +1155,13 @@ std::vector<std::string> DatabaseContext::Private::getDatabaseStructure() {
res.emplace_back(row[0]);
}
}
- if (nLayoutVersionMajor_ > 0) {
+ if (sqlite_handle_->getLayoutVersionMajor() > 0) {
res.emplace_back(
"INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MAJOR'," +
- toString(nLayoutVersionMajor_) + ");");
+ toString(sqlite_handle_->getLayoutVersionMajor()) + ");");
res.emplace_back(
"INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MINOR'," +
- toString(nLayoutVersionMinor_) + ");");
+ toString(sqlite_handle_->getLayoutVersionMinor()) + ");");
}
return res;
}
@@ -844,7 +1170,7 @@ std::vector<std::string> DatabaseContext::Private::getDatabaseStructure() {
void DatabaseContext::Private::attachExtraDatabases(
const std::vector<std::string> &auxiliaryDatabasePaths) {
- assert(close_handle_);
+
assert(sqlite_handle_);
auto tables =
@@ -861,18 +1187,24 @@ void DatabaseContext::Private::attachExtraDatabases(
}
}
+ const int nLayoutVersionMajor = sqlite_handle_->getLayoutVersionMajor();
+ const int nLayoutVersionMinor = sqlite_handle_->getLayoutVersionMinor();
+
closeDB();
if (auxiliaryDatabasePaths.empty()) {
open(databasePath_, pjCtxt());
return;
}
+ sqlite3 *sqlite_handle = nullptr;
sqlite3_open_v2(
- ":memory:", &sqlite_handle_,
+ ":memory:", &sqlite_handle,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_URI, nullptr);
- if (!sqlite_handle_) {
+ if (!sqlite_handle) {
throw FactoryException("cannot create in memory database");
}
+ sqlite_handle_ = SQLiteHandle::initFromExisting(
+ sqlite_handle, true, nLayoutVersionMajor, nLayoutVersionMinor);
run("ATTACH DATABASE '" + replaceAll(databasePath_, "'", "''") +
"' AS db_0");
@@ -887,7 +1219,8 @@ void DatabaseContext::Private::attachExtraDatabases(
count++;
run(sql);
- checkDatabaseLayout(otherDbPath, attachedDbName + '.');
+ sqlite_handle_->checkDatabaseLayout(databasePath_, otherDbPath,
+ attachedDbName + '.');
}
for (const auto &pair : tableStructure) {
@@ -923,92 +1256,6 @@ void DatabaseContext::Private::attachExtraDatabases(
}
run(sql);
}
-
- registerFunctions();
-}
-
-// ---------------------------------------------------------------------------
-
-static double PROJ_SQLITE_GetValAsDouble(sqlite3_value *val, bool &gotVal) {
- switch (sqlite3_value_type(val)) {
- case SQLITE_FLOAT:
- gotVal = true;
- return sqlite3_value_double(val);
-
- case SQLITE_INTEGER:
- gotVal = true;
- return static_cast<double>(sqlite3_value_int64(val));
-
- default:
- gotVal = false;
- return 0.0;
- }
-}
-
-// ---------------------------------------------------------------------------
-
-static void PROJ_SQLITE_pseudo_area_from_swne(sqlite3_context *pContext,
- int /* argc */,
- sqlite3_value **argv) {
- bool b0, b1, b2, b3;
- double south_lat = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
- double west_lon = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
- double north_lat = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
- double east_lon = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
- if (!b0 || !b1 || !b2 || !b3) {
- sqlite3_result_null(pContext);
- return;
- }
- // Deal with area crossing antimeridian
- if (east_lon < west_lon) {
- east_lon += 360.0;
- }
- // Integrate cos(lat) between south_lat and north_lat
- double pseudo_area = (east_lon - west_lon) *
- (std::sin(common::Angle(north_lat).getSIValue()) -
- std::sin(common::Angle(south_lat).getSIValue()));
- sqlite3_result_double(pContext, pseudo_area);
-}
-
-// ---------------------------------------------------------------------------
-
-static void PROJ_SQLITE_intersects_bbox(sqlite3_context *pContext,
- int /* argc */, sqlite3_value **argv) {
- bool b0, b1, b2, b3, b4, b5, b6, b7;
- double south_lat1 = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
- double west_lon1 = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
- double north_lat1 = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
- double east_lon1 = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
- double south_lat2 = PROJ_SQLITE_GetValAsDouble(argv[4], b4);
- double west_lon2 = PROJ_SQLITE_GetValAsDouble(argv[5], b5);
- double north_lat2 = PROJ_SQLITE_GetValAsDouble(argv[6], b6);
- double east_lon2 = PROJ_SQLITE_GetValAsDouble(argv[7], b7);
- if (!b0 || !b1 || !b2 || !b3 || !b4 || !b5 || !b6 || !b7) {
- sqlite3_result_null(pContext);
- return;
- }
- auto bbox1 = metadata::GeographicBoundingBox::create(west_lon1, south_lat1,
- east_lon1, north_lat1);
- auto bbox2 = metadata::GeographicBoundingBox::create(west_lon2, south_lat2,
- east_lon2, north_lat2);
- sqlite3_result_int(pContext, bbox1->intersects(bbox2) ? 1 : 0);
-}
-
-// ---------------------------------------------------------------------------
-
-#ifndef SQLITE_DETERMINISTIC
-#define SQLITE_DETERMINISTIC 0
-#endif
-
-void DatabaseContext::Private::registerFunctions() {
- sqlite3_create_function(sqlite_handle_, "pseudo_area_from_swne", 4,
- SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
- PROJ_SQLITE_pseudo_area_from_swne, nullptr,
- nullptr);
-
- sqlite3_create_function(sqlite_handle_, "intersects_bbox", 8,
- SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
- PROJ_SQLITE_intersects_bbox, nullptr, nullptr);
}
// ---------------------------------------------------------------------------
@@ -1023,87 +1270,17 @@ SQLResultSet DatabaseContext::Private::run(const std::string &sql,
stmt = iter->second;
sqlite3_reset(stmt);
} else {
- if (sqlite3_prepare_v2(sqlite_handle_, sql.c_str(),
+ if (sqlite3_prepare_v2(handle(), sql.c_str(),
static_cast<int>(sql.size()), &stmt,
nullptr) != SQLITE_OK) {
throw FactoryException("SQLite error on " + sql + ": " +
- sqlite3_errmsg(sqlite_handle_));
+ sqlite3_errmsg(handle()));
}
mapSqlToStatement_.insert(
std::pair<std::string, sqlite3_stmt *>(sql, stmt));
}
- int nBindField = 1;
- for (const auto &param : parameters) {
- const auto paramType = param.type();
- if (paramType == SQLValues::Type::STRING) {
- auto strValue = param.stringValue();
- sqlite3_bind_text(stmt, nBindField, strValue.c_str(),
- static_cast<int>(strValue.size()),
- SQLITE_TRANSIENT);
- } else if (paramType == SQLValues::Type::INT) {
- sqlite3_bind_int(stmt, nBindField, param.intValue());
- } else {
- assert(paramType == SQLValues::Type::DOUBLE);
- sqlite3_bind_double(stmt, nBindField, param.doubleValue());
- }
- nBindField++;
- }
-
-#ifdef TRACE_DATABASE
- size_t nPos = 0;
- std::string sqlSubst(sql);
- for (const auto &param : parameters) {
- nPos = sqlSubst.find('?', nPos);
- assert(nPos != std::string::npos);
- std::string strValue;
- const auto paramType = param.type();
- if (paramType == SQLValues::Type::STRING) {
- strValue = '\'' + param.stringValue() + '\'';
- } else if (paramType == SQLValues::Type::INT) {
- strValue = toString(param.intValue());
- } else {
- strValue = toString(param.doubleValue());
- }
- sqlSubst =
- sqlSubst.substr(0, nPos) + strValue + sqlSubst.substr(nPos + 1);
- nPos += strValue.size();
- }
- logTrace(sqlSubst, "DATABASE");
-#endif
-
- SQLResultSet result;
- const int column_count = sqlite3_column_count(stmt);
- while (true) {
- int ret = sqlite3_step(stmt);
- if (ret == SQLITE_ROW) {
- SQLRow row(column_count);
- for (int i = 0; i < column_count; i++) {
- if (useMaxFloatPrecision &&
- sqlite3_column_type(stmt, i) == SQLITE_FLOAT) {
- // sqlite3_column_text() does not use maximum precision
- std::ostringstream buffer;
- buffer.imbue(std::locale::classic());
- buffer << std::setprecision(18);
- buffer << sqlite3_column_double(stmt, i);
- row[i] = buffer.str();
- } else {
- const char *txt = reinterpret_cast<const char *>(
- sqlite3_column_text(stmt, i));
- if (txt) {
- row[i] = txt;
- }
- }
- }
- result.emplace_back(std::move(row));
- } else if (ret == SQLITE_DONE) {
- break;
- } else {
- throw FactoryException("SQLite error on " + sql + ": " +
- sqlite3_errmsg(sqlite_handle_));
- }
- }
- return result;
+ return sqlite_handle_->run(stmt, sql, parameters, useMaxFloatPrecision);
}
// ---------------------------------------------------------------------------
@@ -1152,8 +1329,8 @@ void DatabaseContext::Private::appendSql(
std::vector<std::string> &sqlStatements, const std::string &sql) {
sqlStatements.emplace_back(sql);
char *errMsg = nullptr;
- if (sqlite3_exec(memoryDbHandle_, sql.c_str(), nullptr, nullptr, &errMsg) !=
- SQLITE_OK) {
+ if (sqlite3_exec(memoryDbHandle_->handle(), sql.c_str(), nullptr, nullptr,
+ &errMsg) != SQLITE_OK) {
std::string s("Cannot execute " + sql);
if (errMsg) {
s += " : ";
@@ -2551,7 +2728,6 @@ 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());
auto auxDbs(auxiliaryDatabasePaths);
if (auxDbs.empty()) {
const char *auxDbStr = getenv("PROJ_AUX_DB");
@@ -2647,19 +2823,27 @@ void DatabaseContext::startInsertStatementsSession() {
buffer << this;
buffer << ".db?mode=memory&cache=shared";
d->memoryDbForInsertPath_ = buffer.str();
+ sqlite3 *memoryDbHandle = nullptr;
sqlite3_open_v2(
- d->memoryDbForInsertPath_.c_str(), &d->memoryDbHandle_,
+ d->memoryDbForInsertPath_.c_str(), &memoryDbHandle,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr);
- if (d->memoryDbHandle_ == nullptr) {
+ if (memoryDbHandle == nullptr) {
throw FactoryException("Cannot create in-memory database");
}
+ d->memoryDbHandle_ =
+ SQLiteHandle::initFromExistingUniquePtr(memoryDbHandle, true);
// Fill the structure of this database
for (const auto &sql : sqlStatements) {
- if (sqlite3_exec(d->memoryDbHandle_, sql.c_str(), nullptr, nullptr,
- nullptr) != SQLITE_OK) {
- throw FactoryException("Cannot execute " + sql);
+ char *errmsg = nullptr;
+ if (sqlite3_exec(d->memoryDbHandle_->handle(), sql.c_str(), nullptr,
+ nullptr, &errmsg) != SQLITE_OK) {
+ const auto sErrMsg =
+ "Cannot execute " + sql + ": " + (errmsg ? errmsg : "");
+ sqlite3_free(errmsg);
+ throw FactoryException(sErrMsg);
}
+ sqlite3_free(errmsg);
}
// Attach this database to the current one(s)
@@ -2883,8 +3067,7 @@ void DatabaseContext::stopInsertStatementsSession() {
if (d->memoryDbHandle_) {
d->clearCaches();
d->attachExtraDatabases(d->auxiliaryDatabasePaths_);
- sqlite3_close(d->memoryDbHandle_);
- d->memoryDbHandle_ = nullptr;
+ d->memoryDbHandle_.reset();
d->memoryDbForInsertPath_.clear();
}
}
@@ -8961,3 +9144,7 @@ const std::string &NoSuchAuthorityCodeException::getAuthorityCode() const {
} // namespace io
NS_PROJ_END
+
+// ---------------------------------------------------------------------------
+
+void pj_clear_sqlite_cache() { NS_PROJ::io::SQLiteHandleCache::get().clear(); }
diff --git a/src/malloc.cpp b/src/malloc.cpp
index 6b7fbf26..d60a3f92 100644
--- a/src/malloc.cpp
+++ b/src/malloc.cpp
@@ -33,6 +33,9 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
/* allocate and deallocate memory */
/* These routines are used so that applications can readily replace
@@ -46,6 +49,8 @@
#include <new>
+#include "proj/internal/io_internal.hpp"
+
#include "proj.h"
#include "proj_internal.h"
#include "grids.hpp"
@@ -176,8 +181,16 @@ PJ *pj_default_destructor (PJ *P, int errlev) { /* Destructor */
/*****************************************************************************/
void proj_cleanup() {
/*****************************************************************************/
+
+ // Close the database context of the default PJ_CONTEXT
+ auto cpp_context = pj_get_default_ctx()->cpp_context;
+ if( cpp_context ) {
+ cpp_context->closeDb();
+ }
+
pj_clear_initcache();
FileManager::clearMemoryCache();
pj_clear_hgridshift_knowngrids_cache();
pj_clear_vgridshift_knowngrids_cache();
+ pj_clear_sqlite_cache();
}
diff --git a/src/proj_internal.h b/src/proj_internal.h
index 8ff38411..582bb3c5 100644
--- a/src/proj_internal.h
+++ b/src/proj_internal.h
@@ -719,7 +719,6 @@ struct pj_ctx{
pj_ctx& operator= (const pj_ctx&) = delete;
projCppContext* get_cpp_context();
- void safeAutoCloseDbIfNeeded();
void set_search_paths(const std::vector<std::string>& search_paths_in);
void set_ca_bundle_path(const std::string& ca_bundle_path_in);
@@ -868,6 +867,8 @@ const PJ_UNITS *pj_list_angular_units();
void pj_clear_hgridshift_knowngrids_cache();
void pj_clear_vgridshift_knowngrids_cache();
+void pj_clear_sqlite_cache();
+
PJ_LP pj_generic_inverse_2d(PJ_XY xy, PJ *P, PJ_LP lpInitial);
diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp
index 99d91e3b..90641bd1 100644
--- a/test/unit/test_c_api.cpp
+++ b/test/unit/test_c_api.cpp
@@ -47,6 +47,10 @@
#include <sqlite3.h>
+#ifndef __MINGW32__
+#include <thread>
+#endif
+
using namespace osgeo::proj::common;
using namespace osgeo::proj::crs;
using namespace osgeo::proj::cs;
@@ -4287,106 +4291,6 @@ TEST_F(CApi, proj_as_projjson) {
// ---------------------------------------------------------------------------
-struct Fixture_proj_context_set_autoclose_database : public CApi {
- void test(bool autoclose) {
- proj_context_set_autoclose_database(m_ctxt, autoclose);
-
- auto c_path = proj_context_get_database_path(m_ctxt);
- ASSERT_TRUE(c_path != nullptr);
- std::string path(c_path);
-
- FILE *f = fopen(path.c_str(), "rb");
- ASSERT_NE(f, nullptr);
- fseek(f, 0, SEEK_END);
- auto length = ftell(f);
- std::string content;
- content.resize(static_cast<size_t>(length));
- fseek(f, 0, SEEK_SET);
- auto read_bytes = fread(&content[0], 1, content.size(), f);
- ASSERT_EQ(read_bytes, content.size());
- fclose(f);
- const char *tempdir = getenv("TEMP");
- if (!tempdir) {
- tempdir = getenv("TMP");
- }
- if (!tempdir) {
- tempdir = "/tmp";
- }
- std::string tmp_filename(
- std::string(tempdir) +
- "/test_proj_context_set_autoclose_database.db");
- f = fopen(tmp_filename.c_str(), "wb");
- if (!f) {
- std::cerr << "Cannot create " << tmp_filename << std::endl;
- return;
- }
- fwrite(content.data(), 1, content.size(), f);
- fclose(f);
-
- {
- sqlite3 *db = nullptr;
- sqlite3_open_v2(tmp_filename.c_str(), &db, SQLITE_OPEN_READWRITE,
- nullptr);
- ASSERT_NE(db, nullptr);
- ASSERT_TRUE(sqlite3_exec(db,
- "UPDATE geodetic_crs SET name = 'foo' "
- "WHERE auth_name = 'EPSG' and code = "
- "'4326'",
- nullptr, nullptr, nullptr) == SQLITE_OK);
- sqlite3_close(db);
- }
-
- EXPECT_TRUE(proj_context_set_database_path(m_ctxt, tmp_filename.c_str(),
- nullptr, nullptr));
- {
- auto crs = proj_create_from_database(
- m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr);
- ObjectKeeper keeper(crs);
- ASSERT_NE(crs, nullptr);
- EXPECT_EQ(proj_get_name(crs), std::string("foo"));
- }
-
- {
- sqlite3 *db = nullptr;
- sqlite3_open_v2(tmp_filename.c_str(), &db, SQLITE_OPEN_READWRITE,
- nullptr);
- ASSERT_NE(db, nullptr);
- ASSERT_TRUE(sqlite3_exec(db,
- "UPDATE geodetic_crs SET name = 'bar' "
- "WHERE auth_name = 'EPSG' and code = "
- "'4326'",
- nullptr, nullptr, nullptr) == SQLITE_OK);
- sqlite3_close(db);
- }
- {
- auto crs = proj_create_from_database(
- m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr);
- ObjectKeeper keeper(crs);
- ASSERT_NE(crs, nullptr);
- EXPECT_EQ(proj_get_name(crs),
- std::string(autoclose ? "bar" : "foo"));
- }
-
- if (!autoclose) {
- proj_context_destroy(m_ctxt);
- m_ctxt = nullptr;
- }
- std::remove(tmp_filename.c_str());
- }
-};
-
-TEST_F(Fixture_proj_context_set_autoclose_database,
- proj_context_set_autoclose_database_true) {
- test(true);
-}
-
-TEST_F(Fixture_proj_context_set_autoclose_database,
- proj_context_set_autoclose_database_false) {
- test(false);
-}
-
-// ---------------------------------------------------------------------------
-
TEST_F(CApi, proj_context_copy_from_default) {
auto c_path = proj_context_get_database_path(m_ctxt);
ASSERT_TRUE(c_path != nullptr);
@@ -5644,4 +5548,72 @@ TEST_F(CApi, proj_get_geoid_models_from_database) {
EXPECT_FALSE(findInList(list, "OSGM15"));
}
+// ---------------------------------------------------------------------------
+
+#if !defined(_WIN32)
+TEST_F(CApi, open_plenty_of_contexts) {
+ // Test that we only consume 1 file handle for the connection to the
+ // database
+ std::vector<FILE *> dummyFilePointers;
+ std::vector<PJ_CONTEXT *> ctxts;
+ // 1024 is the number of file descriptors that can be opened simultaneously
+ // by a Linux process (by default)
+ for (int i = 0; i < 1024 - 50; i++) {
+ FILE *f = fopen("/dev/null", "rb");
+ ASSERT_TRUE(f != nullptr);
+ dummyFilePointers.push_back(f);
+ }
+ for (int i = 0; i < 100; i++) {
+ PJ_CONTEXT *ctxt = proj_context_create();
+ ASSERT_TRUE(ctxt != nullptr);
+ auto obj = proj_create(ctxt, "EPSG:4326");
+ ObjectKeeper keeper(obj);
+ EXPECT_NE(obj, nullptr);
+ ctxts.push_back(ctxt);
+ }
+ for (PJ_CONTEXT *ctxt : ctxts) {
+ proj_context_destroy(ctxt);
+ }
+ for (FILE *f : dummyFilePointers) {
+ fclose(f);
+ }
+ proj_cleanup();
+}
+#endif // !defined(_WIN32)
+
+// ---------------------------------------------------------------------------
+
+#ifndef __MINGW32__
+// We need std::thread support
+
+TEST_F(CApi, concurrent_context) {
+ // Test that concurrent access to the database is thread safe.
+ std::vector<std::thread> threads;
+ for (int i = 0; i < 4; i++) {
+ threads.emplace_back(std::thread([] {
+ for (int j = 0; j < 60; j++) {
+ PJ_CONTEXT *ctxt = proj_context_create();
+ {
+ auto obj = proj_create(ctxt, "EPSG:4326");
+ ObjectKeeper keeper(obj);
+ EXPECT_NE(obj, nullptr);
+ }
+ {
+ auto obj = proj_create(
+ ctxt, ("EPSG:" + std::to_string(32600 + j)).c_str());
+ ObjectKeeper keeper(obj);
+ EXPECT_NE(obj, nullptr);
+ }
+ proj_context_destroy(ctxt);
+ }
+ }));
+ }
+ for (auto &t : threads) {
+ t.join();
+ }
+ proj_cleanup();
+}
+
+#endif // __MINGW32__
+
} // namespace
diff --git a/test/unit/test_factory.cpp b/test/unit/test_factory.cpp
index 6c8340aa..98549915 100644
--- a/test/unit/test_factory.cpp
+++ b/test/unit/test_factory.cpp
@@ -2912,7 +2912,7 @@ TEST(factory, attachExtraDatabases_none) {
TEST(factory, attachExtraDatabases_auxiliary) {
const std::string auxDbName(
- "file:proj_test_aux.db?mode=memory&cache=shared");
+ "file:attachExtraDatabases_auxiliary.db?mode=memory&cache=shared");
sqlite3 *dbAux = nullptr;
sqlite3_open_v2(