diff --git a/Documentation/Filters/Cache.md b/Documentation/Filters/Cache.md index b5b1563fc..18798284f 100644 --- a/Documentation/Filters/Cache.md +++ b/Documentation/Filters/Cache.md @@ -3,12 +3,19 @@ This filter was introduced in MariaDB MaxScale 2.1. ## Overview +_Note that the cache is still experimental and that non-backward compatible +changes may be made._ + The cache filter is a simple cache that is capable of caching the result of SELECTs, so that subsequent identical SELECTs are served directly by MaxScale, without the queries being routed to any server. -_Note that the cache is still experimental and that non-backward compatible -changes may be made._ +SELECTs using the following functions will not be cached: `BENCHMARK`, +`CONNECTION_ID`, `CONVERT_TZ`, `CURDATE`, `CURRENT_DATE`, `CURRENT_TIMESTAMP`, +`CURTIME`, `DATABASE`, `ENCRYPT`, `FOUND_ROWS`, `GET_LOCK`, `IS_FREE_LOCK`, +`IS_USED_LOCK`, `LAST_INSERT_ID`, `LOAD_FILE`, `LOCALTIME`, `LOCALTIMESTAMP`, +`MASTER_POS_WAIT`, `NOW`, `RAND`, `RELEASE_LOCK`, `SESSION_USER`, `SLEEP`, +`SYSDATE`, `SYSTEM_USER`, `UNIX_TIMESTAMP`, `USER`, `UUID`, `UUID_SHORT`. Note that installing the cache causes all statements to be parsed. The implication of that is that unless statements _already_ need to be parsed, @@ -28,7 +35,7 @@ Resultsets of prepared statements are **not** cached. ### Transactions The cache will be used and populated in the following circumstances: -* There is _no_ on-going transaction, that is, _autocommit_ is used, +* There is _no_ explicit transaction active, that is, _autocommit_ is used, * there is an _explicitly_ read-only transaction (that is,`START TRANSACTION READ ONLY`) active, or * there is a transaction active and _no_ statement that modify the database @@ -44,7 +51,6 @@ If user or system variables are used in the _SELECT_ statement, the result will not be cached. ### Security - The cache is **not** aware of grants. The implication is that unless the cache has been explicitly configured diff --git a/server/modules/filter/cache/cachefiltersession.cc b/server/modules/filter/cache/cachefiltersession.cc index a77eb7f12..d1612293d 100644 --- a/server/modules/filter/cache/cachefiltersession.cc +++ b/server/modules/filter/cache/cachefiltersession.cc @@ -35,6 +35,108 @@ inline bool cache_max_resultset_size_exceeded(const CACHE_CONFIG& config, uint64 } +namespace +{ + +const char* NON_CACHEABLE_FUNCTIONS[] = +{ + "benchmark", + "connection_id", + "convert_tz", + "curdate", + "current_date", + "current_timestamp", + "curtime", + "database", + "encrypt", + "found_rows", + "get_lock", + "is_free_lock", + "is_used_lock", + "last_insert_id", + "load_file", + "localtime", + "localtimestamp", + "master_pos_wait", + "now", + "rand", + "release_lock", + "session_user", + "sleep", + "sysdate", + "system_user", + "unix_timestamp", + "user", + "uuid", + "uuid_short", +}; + +const char* NON_CACHEABLE_VARIABLES[] = +{ + "current_date", + "current_timestamp", + "localtime", + "localtimestamp", +}; + +const size_t N_NON_CACHEABLE_FUNCTIONS = sizeof(NON_CACHEABLE_FUNCTIONS)/sizeof(NON_CACHEABLE_FUNCTIONS[0]); +const size_t N_NON_CACHEABLE_VARIABLES = sizeof(NON_CACHEABLE_VARIABLES)/sizeof(NON_CACHEABLE_VARIABLES[0]); + +int compare_name(const void* pLeft, const void* pRight) +{ + return strcasecmp((const char*)pLeft, *(const char**)pRight); +} + +inline bool uses_name(const char* zName, const char** pzNames, size_t nNames) +{ + return bsearch(zName, pzNames, nNames, sizeof(const char*), compare_name) != NULL; +} + +bool uses_non_cacheable_function(GWBUF* pPacket) +{ + bool rv = false; + + const QC_FUNCTION_INFO* pInfo; + size_t nInfos; + + qc_get_function_info(pPacket, &pInfo, &nInfos); + + const QC_FUNCTION_INFO* pEnd = pInfo + nInfos; + + while (!rv && (pInfo != pEnd)) + { + rv = uses_name(pInfo->name, NON_CACHEABLE_FUNCTIONS, N_NON_CACHEABLE_FUNCTIONS); + + ++pInfo; + } + + return rv; +} + +bool uses_non_cacheable_variable(GWBUF* pPacket) +{ + bool rv = false; + + const QC_FIELD_INFO* pInfo; + size_t nInfos; + + qc_get_field_info(pPacket, &pInfo, &nInfos); + + const QC_FIELD_INFO* pEnd = pInfo + nInfos; + + while (!rv && (pInfo != pEnd)) + { + rv = uses_name(pInfo->column, NON_CACHEABLE_VARIABLES, N_NON_CACHEABLE_VARIABLES); + + ++pInfo; + } + + return rv; +} + +} + + CacheFilterSession::CacheFilterSession(MXS_SESSION* pSession, Cache* pCache, char* zDefaultDb) : maxscale::FilterSession(pSession) , m_state(CACHE_EXPECTING_NOTHING) @@ -719,6 +821,16 @@ bool CacheFilterSession::should_consult_cache(GWBUF* pPacket) consult_cache = false; zReason = "system variables are read"; } + else if (uses_non_cacheable_function(pPacket)) + { + consult_cache = false; + zReason = "uses non-cacheable function"; + } + else if (uses_non_cacheable_variable(pPacket)) + { + consult_cache = false; + zReason = "uses non-cacheable variable"; + } } else {