Skip to content

Commit

Permalink
ODBC-397 Introducing LRU srver side prepared statement cache
Browse files Browse the repository at this point in the history
The cache is used by default. It's use can be turned off by setting
cache size to 0. The size of cache can be set using connection string
option PSCACHESIZE. The default value is 250
Another option is MAXCACHEKEY - it limits the size of key used to cache
SSPS. Basically it's the length of query + lenght of Schema name + 1
  • Loading branch information
lawrinn committed Oct 11, 2023
1 parent 2d2a979 commit d5a4759
Show file tree
Hide file tree
Showing 16 changed files with 586 additions and 115 deletions.
146 changes: 146 additions & 0 deletions class/lru/lrucache.h
@@ -0,0 +1,146 @@
/************************************************************************************
Copyright (C) 2023 MariaDB Corporation plc
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not see <http://www.gnu.org/licenses>
or write to the Free Software Foundation, Inc.,
51 Franklin St., Fifth Floor, Boston, MA 02110, USA
*************************************************************************************/


#ifndef _LRUCACHE_H_
#define _LRUCACHE_H_

#include <unordered_map>
#include <list>
#include <mutex>


namespace mariadb
{

template <class KT, class VT> struct Cache
{
virtual ~Cache() {}

virtual VT* put(const KT& key, VT* obj2cache) {return nullptr;}
virtual VT* get(const KT& key) {return nullptr;}
virtual void clear() {}
};

template <class T> struct DefaultRemover
{
void operator()(T* removedCacheEntry)
{
delete removedCacheEntry;
}
};
// This does not care about the fate of obbjects removed from cache
template <class KT, class VT, class Remover= DefaultRemover<VT>> class LruCache : public Cache<KT,VT>
{
std::mutex lock;
typedef std::list<std::pair<KT,VT*>> ListType;
typedef typename ListType::iterator ListIterator;

std::size_t maxSize;
ListType lu;
std::unordered_map<KT, ListIterator> cache;

protected:
virtual void remove(ListIterator& it)
{
Remover()(it->second);
cache.erase(it->first);
}

virtual ListIterator removeEldestEntry()
{
auto victim= lu.end();
--victim;
remove(victim);
lu.splice(lu.begin(), lu, victim);

return victim;
}

public:
virtual ~LruCache() {}


LruCache(std::size_t maxCacheSize) : maxSize(maxCacheSize)
{
cache.reserve(maxSize);
}


virtual VT* put(const KT& key, VT* obj2cache)
{
std::lock_guard<std::mutex> localScopeLock(lock);

auto cached= cache.find(key);

if (cached != cache.end())
{
return cached->second->second;
}

ListIterator it;
if (cache.size() == maxSize)
{
it= removeEldestEntry();
it->first= key;
it->second= obj2cache;
}
else
{
lu.emplace_front(key, obj2cache);
it= lu.begin();
}
cache.emplace(key, it);

return nullptr;
}


virtual VT* get(const KT& key)
{
std::lock_guard<std::mutex> localScopeLock(lock);

auto cached= cache.find(key);
if (cached != cache.end())
{
lu.splice(lu.begin(), lu, cached->second);
return cached->second->second;
}
return nullptr;
}


virtual void clear()
{
std::lock_guard<std::mutex> localScopeLock(lock);
cache.clear();
for (auto it= lu.begin(); it != lu.end();++it)
{
if (it->second != nullptr)
{
Remover()(it->second);
}
}
lu.clear();
}
};

}

#endif
91 changes: 91 additions & 0 deletions class/lru/pscache.h
@@ -0,0 +1,91 @@
/************************************************************************************
Copyright (C) 2023 MariaDB Corporation plc
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not see <http://www.gnu.org/licenses>
or write to the Free Software Foundation, Inc.,
51 Franklin St., Fifth Floor, Boston, MA 02110, USA
*************************************************************************************/


#ifndef _PSCACHE_H_
#define _PSCACHE_H_

#include <string>
#include "lrucache.h"

namespace mariadb
{
template <class T> struct PsRemover
{
// std::mutex lock;
void operator()(T* removedCacheEntry)
{
if (removedCacheEntry->canBeDeallocate()) {
delete removedCacheEntry;
}
else {
removedCacheEntry->decrementShareCounter();
}
}
};

template <class VT> class PsCache : public LruCache<std::string,VT, PsRemover<VT>>
{
typedef LruCache<std::string, VT, PsRemover<VT>> parentLru;
std::size_t maxKeyLen;

public:
virtual ~PsCache()
{
}

PsCache(std::size_t maxCacheSize, std::size_t _maxKeyLen= static_cast<std::size_t>(-1))
: parentLru(maxCacheSize)
, maxKeyLen(_maxKeyLen)
{
}


virtual VT* put(const std::string& key, VT* obj2cache)
{
if (key.length() > maxKeyLen) {
return nullptr;
}

VT* hadCached= this->parentLru::put(key, obj2cache);

if (hadCached == nullptr)
{
// Putting to cache creates additional reference
obj2cache->incrementShareCounter();
}

return hadCached;
}


virtual VT* get(const std::string& key)
{
auto result= this->parentLru::get(key);
if (result != nullptr)
{
result->incrementShareCounter();
}
return result;
}
};

}

#endif
1 change: 1 addition & 0 deletions driver/CMakeLists.txt
Expand Up @@ -132,6 +132,7 @@ ELSE()
ENDIF()

INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/interface ${CMAKE_CURRENT_SOURCE_DIR}/template ${CMAKE_CURRENT_SOURCE_DIR}/class)
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/class)

CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/driver/mariadb-odbc-driver.def.in
${CMAKE_BINARY_DIR}/driver/mariadb-odbc-driver-uni.def)
Expand Down
89 changes: 74 additions & 15 deletions driver/class/ServerPrepareResult.cpp
Expand Up @@ -21,7 +21,7 @@

#include "ServerPrepareResult.h"
#include "ResultSetMetaData.h"
//#include "ColumnType.h"
#include "Exception.h"
//#include "ColumnDefinition.h"

#include "ColumnDefinition.h"
Expand All @@ -32,30 +32,43 @@ namespace mariadb
{
ServerPrepareResult::~ServerPrepareResult()
{
mysql_stmt_close(statementId);
if (statementId) {
mysql_stmt_close(statementId);
}
}
/**
* PrepareStatement Result object.
*
* @param sql query
* @param statementId server statement Id.
* @param columns columns information
* @param parameters parameters information
* @param unProxiedProtocol indicate the protocol on which the prepare has been done
*/
/*ServerPrepareResult::ServerPrepareResult(
ServerPrepareResult::ServerPrepareResult(
const SQLString& _sql,
MYSQL_STMT* _statementId,
std::vector<ColumnDefinition>& _columns,
std::vector<ColumnDefinition>& _parameters
MYSQL* dbc // This plays role of the unproxiedProtocol so far
)
: sql(_sql)
, statementId(_statementId)
, columns(_columns)
, parameters(_parameters)
, metadata(mysql_stmt_result_metadata(statementId), &mysql_free_result)
, connection(dbc)
, statementId(mysql_stmt_init(dbc))
{
}*/
static const my_bool updateMaxLength= 1;
int rc= 1;

if (statementId == nullptr) {
throw rc;
}
mysql_stmt_attr_set(statementId, STMT_ATTR_UPDATE_MAX_LENGTH, &updateMaxLength);

if ((rc= mysql_stmt_prepare(statementId, sql.c_str(), static_cast<unsigned long>(sql.length()))) != 0) {
SQLException e(mysql_stmt_error(statementId), mysql_stmt_sqlstate(statementId), mysql_stmt_errno(statementId));
mysql_stmt_close(statementId);
throw e;
}
paramCount= mysql_stmt_param_count(statementId);
std::unique_ptr<MYSQL_RES, decltype(&mysql_free_result)> metadata(mysql_stmt_result_metadata(statementId), &mysql_free_result);
if (metadata) {
init(mysql_fetch_fields(metadata.get()), mysql_stmt_field_count(statementId));
}
}

/**
* PrepareStatement Result object.
Expand All @@ -76,11 +89,11 @@ namespace mariadb
, connection (dbc)
, paramCount(mysql_stmt_param_count(statementId))
{
// Feels like this can be done better
std::unique_ptr<MYSQL_RES, decltype(&mysql_free_result)> metadata(mysql_stmt_result_metadata(statementId), &mysql_free_result);
if (metadata) {
init(mysql_fetch_fields(metadata.get()), mysql_stmt_field_count(statementId));
}
paramBind= nullptr;
}


Expand Down Expand Up @@ -134,6 +147,52 @@ namespace mariadb
}


/**
* Increment share counter.
*
* @return true if can be used (is not been deallocate).
*/
bool ServerPrepareResult::incrementShareCounter()
{
std::lock_guard<std::mutex> localScopeLock(lock);
if (isBeingDeallocate) {
return false;
}
++shareCounter;
return true;
}

void ServerPrepareResult::decrementShareCounter()
{
std::lock_guard<std::mutex> localScopeLock(lock);
--shareCounter;
}

/**
* Asked if can be deallocate (is not shared in other statement and not in cache) Set deallocate
* flag to true if so.
*
* @return true if can be deallocate
*/
bool ServerPrepareResult::canBeDeallocate()
{
std::lock_guard<std::mutex> localScopeLock(lock);

if (shareCounter > 1 || isBeingDeallocate) {
return false;
}
isBeingDeallocate= true;
return true;
}


// for unit test
int32_t ServerPrepareResult::getShareCounter()
{
std::lock_guard<std::mutex> localScopeLock(lock);
return static_cast<int32_t>(shareCounter);
}

/*void initBindStruct(MYSQL_BIND& bind, const MYSQL_BIND* paramInfo)
{
std::memset(&bind, 0, sizeof(bind));
Expand Down

0 comments on commit d5a4759

Please sign in to comment.