diff options
| author | Even Rouault <even.rouault@spatialys.com> | 2021-06-09 19:58:12 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-06-09 19:58:12 +0200 |
| commit | 5436e8aa46f7fb6dd6c9853e5da7222d8e6607e1 (patch) | |
| tree | 55a7ac8146c327d54a9423db8a66a8dbbf54709c | |
| parent | fae861c75adbc598393245f34ad8871a9aaecd44 (diff) | |
| parent | 61e25ec859a34e68fa8c2dc99f951f3e970875df (diff) | |
| download | PROJ-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.hpp | 14 | ||||
| -rw-r--r-- | src/ctx.cpp | 11 | ||||
| -rw-r--r-- | src/iso19111/c_api.cpp | 91 | ||||
| -rw-r--r-- | src/iso19111/factory.cpp | 821 | ||||
| -rw-r--r-- | src/malloc.cpp | 13 | ||||
| -rw-r--r-- | src/proj_internal.h | 3 | ||||
| -rw-r--r-- | test/unit/test_c_api.cpp | 172 | ||||
| -rw-r--r-- | test/unit/test_factory.cpp | 2 |
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 ¶meters = 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 ¶meters = 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 ¶meters, + bool useMaxFloatPrecision) { + int nBindField = 1; + for (const auto ¶m : 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 ¶m : 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 ¶meters, + 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 ¶m : 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 ¶m : 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( |
