diff --git a/common/database/database_connection.cpp b/common/database/database_connection.cpp index 8004984d6b..50844c22f6 100644 --- a/common/database/database_connection.cpp +++ b/common/database/database_connection.cpp @@ -43,6 +43,7 @@ #include #include +#include const char* const traceDatabase = "KICAD_DATABASE"; @@ -88,6 +89,8 @@ DATABASE_CONNECTION::DATABASE_CONNECTION( const std::string& aDataSourceName, m_pass = aPassword; m_timeout = aTimeoutSeconds; + m_cache = std::make_unique( 10, 1 ); + if( aConnectNow ) Connect(); } @@ -100,6 +103,8 @@ DATABASE_CONNECTION::DATABASE_CONNECTION( const std::string& aConnectionString, m_connectionString = aConnectionString; m_timeout = aTimeoutSeconds; + m_cache = std::make_unique( 10, 1 ); + if( aConnectNow ) Connect(); } @@ -112,6 +117,22 @@ DATABASE_CONNECTION::~DATABASE_CONNECTION() } +void DATABASE_CONNECTION::SetCacheParams( int aMaxSize, int aMaxAge ) +{ + if( !m_cache ) + return; + + if( aMaxSize < 0 ) + aMaxSize = 0; + + if( aMaxAge < 0 ) + aMaxAge = 0; + + m_cache->SetMaxSize( static_cast( aMaxSize ) ); + m_cache->SetMaxAge( static_cast( aMaxAge ) ); +} + + bool DATABASE_CONNECTION::Connect() { nanodbc::string dsn = fromUTF8( m_dsn ); @@ -301,11 +322,19 @@ bool DATABASE_CONNECTION::SelectOne( const std::string& aTable, const std::string& columnName = columnCacheIter->first; - nanodbc::statement statement( *m_conn ); + std::string queryStr = fmt::format( "SELECT * FROM {}{}{} WHERE {}{}{} = ?", + m_quoteChar, tableName, m_quoteChar, + m_quoteChar, columnName, m_quoteChar ); - nanodbc::string query = fromUTF8( fmt::format( "SELECT * FROM {}{}{} WHERE {}{}{} = ?", - m_quoteChar, tableName, m_quoteChar, - m_quoteChar, columnName, m_quoteChar ) ); + nanodbc::statement statement( *m_conn ); + nanodbc::string query = fromUTF8( queryStr ); + + if( m_cache->Get( queryStr, aResult ) ) + { + wxLogTrace( traceDatabase, wxT( "SelectOne: `%s` with parameter `%s` - cache hit" ), + toUTF8( query ), aWhere.second ); + return true; + } try { @@ -364,6 +393,8 @@ bool DATABASE_CONNECTION::SelectOne( const std::string& aTable, return false; } + m_cache->Put( queryStr, aResult ); + return true; } diff --git a/common/database/database_lib_settings.cpp b/common/database/database_lib_settings.cpp index 5cc9777125..c62ce31008 100644 --- a/common/database/database_lib_settings.cpp +++ b/common/database/database_lib_settings.cpp @@ -96,6 +96,10 @@ DATABASE_LIB_SETTINGS::DATABASE_LIB_SETTINGS( const std::string& aFilename ) : } }, {} ) ); + + m_params.emplace_back( new PARAM( "cache.max_size", &m_Cache.max_size, 256 ) ); + + m_params.emplace_back( new PARAM( "cache.max_age", &m_Cache.max_age, 10 ) ); } diff --git a/eeschema/sch_plugins/database/sch_database_plugin.cpp b/eeschema/sch_plugins/database/sch_database_plugin.cpp index 6eac594fef..fdede46178 100644 --- a/eeschema/sch_plugins/database/sch_database_plugin.cpp +++ b/eeschema/sch_plugins/database/sch_database_plugin.cpp @@ -236,6 +236,8 @@ void SCH_DATABASE_PLUGIN::ensureConnection() THROW_IO_ERROR( msg ); } + + m_conn->SetCacheParams( m_settings->m_Cache.max_size, m_settings->m_Cache.max_age ); } } diff --git a/include/database/database_cache.h b/include/database/database_cache.h new file mode 100644 index 0000000000..b8f09be086 --- /dev/null +++ b/include/database/database_cache.h @@ -0,0 +1,100 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Jon Evans + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef KICAD_DATABASE_CACHE_H +#define KICAD_DATABASE_CACHE_H + +#include +#include +#include +#include + +#include + + +class DATABASE_CACHE +{ +public: + typedef std::pair> CACHE_ENTRY; + + DATABASE_CACHE( size_t aMaxSize, time_t aMaxAge ) : + m_maxSize( aMaxSize ), + m_maxAge( aMaxAge ) + {} + + void Put( const std::string& aQuery, const DATABASE_CONNECTION::ROW& aResult ) + { + auto it = m_cache.find( aQuery ); + + time_t time = std::chrono::system_clock::to_time_t( std::chrono::system_clock::now() ); + + m_cacheMru.push_front( std::make_pair( aQuery, + std::make_pair( time, aResult ) ) ); + + if( it != m_cache.end() ) + { + m_cacheMru.erase( it->second ); + m_cache.erase( it ); + } + + m_cache[aQuery] = m_cacheMru.begin(); + + if( m_cache.size() > m_maxSize ) + { + auto last = m_cacheMru.end(); + last--; + m_cache.erase( last->first ); + m_cacheMru.pop_back(); + } + } + + bool Get( const std::string& aQuery, DATABASE_CONNECTION::ROW& aResult ) + { + auto it = m_cache.find( aQuery ); + + if( it == m_cache.end() ) + return false; + + time_t time = std::chrono::system_clock::to_time_t( std::chrono::system_clock::now() ); + + if( time - it->second->second.first > m_maxAge ) + { + m_cacheMru.erase( it->second ); + m_cache.erase( it ); + return false; + } + + m_cacheMru.splice( m_cacheMru.begin(), m_cacheMru, it->second ); + + aResult = it->second->second.second; + return true; + } + + void SetMaxSize( size_t aMaxSize ) { m_maxSize = aMaxSize; } + void SetMaxAge( time_t aMaxAge ) { m_maxAge = aMaxAge; } + +private: + size_t m_maxSize; + time_t m_maxAge; + std::list m_cacheMru; + std::unordered_map::iterator> m_cache; +}; + +#endif //KICAD_DATABASE_CACHE_H diff --git a/include/database/database_connection.h b/include/database/database_connection.h index 387777d275..73e625dfba 100644 --- a/include/database/database_connection.h +++ b/include/database/database_connection.h @@ -34,6 +34,8 @@ namespace nanodbc class connection; } +class DATABASE_CACHE; + class DATABASE_CONNECTION { @@ -52,6 +54,8 @@ public: ~DATABASE_CONNECTION(); + void SetCacheParams( int aMaxSize, int aMaxAge ); + bool Connect(); bool Disconnect(); @@ -104,6 +108,8 @@ private: long m_timeout; char m_quoteChar; + + std::unique_ptr m_cache; }; #endif //KICAD_DATABASE_CONNECTION_H diff --git a/include/database/database_lib_settings.h b/include/database/database_lib_settings.h index dbb994914e..579a34a359 100644 --- a/include/database/database_lib_settings.h +++ b/include/database/database_lib_settings.h @@ -76,6 +76,13 @@ struct DATABASE_LIB_TABLE }; +struct DATABASE_CACHE_SETTINGS +{ + int max_size; ///< Maximum number of single-row results to cache + int max_age; ///< Max age of cached rows before they expire, in seconds +}; + + class DATABASE_LIB_SETTINGS : public JSON_SETTINGS { public: @@ -87,6 +94,8 @@ public: std::vector m_Tables; + DATABASE_CACHE_SETTINGS m_Cache; + protected: wxString getFileExt() const override; };