MXS-1475 Enable MaxScale specific user variables

With the changes in this commit it is possible to add and remove
MaxScale specific user variables. A MaxScale specific user variable
is a user variable that is interpreted by MaxScale and that
potentially changes the behaviour of MaxScale.

MaxScale specific user variables are of the format "@maxscale.x.y"
where "@maxscale" is a mandatory prefix, x a scope identifying the
component that handles the variable and y the component specific
variable. So, a variable might be called e.g. "@maxscale.cache.enabled".
The scope "core" is reserved (although not enforced yet) to MaxScale
itself.

The idea is that although MaxScale catches these, they are passed
through to the server. The benefit of this is that we do not need to
detect e.g. "SELECT @maxscale.cache.enabled", but can let the result
be returned from the server.

The interpretation of a provided value is handled by the component that
adds the variable. In a subsequent commit, it will be possible for a
component to reject a value, which will then cause an error to be
returned to the client.

There are 3 new functions:

- session_add_variable() using which a variable is added,
- session_remove_variable() using which a variable is removed, and
- session_set_variable_value().

The two former ones are to be called by components, the last one by
the protocol that catches the "set @maxscale..." statements.
This commit is contained in:
Johan Wikman
2018-02-26 16:07:58 +02:00
parent 5dfa0c1226
commit 872a51a376
3 changed files with 195 additions and 12 deletions

View File

@ -27,6 +27,11 @@
#include <maxscale/spinlock.h>
#include <maxscale/jansson.h>
#ifdef __cplusplus
#include <tr1/unordered_map>
#include <string>
#endif
MXS_BEGIN_DECLS
struct dcb;
@ -120,6 +125,34 @@ typedef struct mxs_upstream
int32_t (*error)(void *instance, void *session, void *);
} MXS_UPSTREAM;
/**
* Handler function for MaxScale specific session variables.
*
* Note that the provided value string is exactly as it appears in
* the received SET-statement. Only leading and trailing whitespace
* has been removed. The handler must itself parse the value string.
*
* @param context Context provided when handler was registered.
* @param name The variable that is being set.
* @param value_begin The beginning of the value as specified in the
* "set @maxscale.x.y = VALUE" statement.
* @param value_end One past the end of the VALUE.
*/
typedef void (*session_variable_handler_t)(void* context,
const char* name,
const char* value_begin,
const char* value_end);
#ifdef __cplusplus
typedef struct session_variable
{
session_variable_handler_t handler;
void* context;
} SESSION_VARIABLE;
typedef std::tr1::unordered_map<std::string, SESSION_VARIABLE> SessionVarsByName;
#endif
/**
* The session status block
*
@ -154,6 +187,11 @@ typedef struct session
const struct server *target; /**< Where the statement was sent */
} stmt; /**< Current statement being executed */
bool qualifies_for_pooling; /**< Whether this session qualifies for the connection pool */
#ifdef __cplusplus
SessionVarsByName* variables; /*< @maxscale variables associated with this session */
#else
void* variables;
#endif
skygw_chk_t ses_chk_tail;
} MXS_SESSION;
@ -482,4 +520,65 @@ MXS_SESSION* session_get_current();
**/
uint64_t session_get_current_id();
/**
* @brief Add new MaxScale specific user variable to the session.
*
* The name of the variable must be of the following format:
*
* "@maxscale\.[a-zA-Z_]+(\.[a-zA-Z_])*"
*
* e.g. "@maxscale.cache.enabled". A strong suggestion is that the first
* sub-scope is the same as the module name of the component registering the
* variable. The sub-scope "core" is reserved by MaxScale.
*
* The variable name will be converted to all lowercase when added.
*
* @param session The session in question.
* @param name The name of the variable, must start with "@MAXSCALE.".
* @param handler The handler function for the variable.
* @param context Context that will be passed to the handler function.
*
* @return True, if the variable could be added, false otherwise.
*/
bool session_add_variable(MXS_SESSION* session,
const char* name,
session_variable_handler_t handler,
void* context);
/**
* @brief Remove MaxScale specific user variable from the session.
*
* With this function a particular MaxScale specific user variable
* can be removed. Note that it is *not* mandatory to remove a
* variable when a session is closed, but have to be done in case
* the context object must manually be deleted.
*
* @param session The session in question.
* @param name The name of the variable.
* @param context On successful return, if non-NULL, the context object
* that was provided when the variable was added.
*
* @return True, if the variable existed, false otherwise.
*/
bool session_remove_variable(MXS_SESSION* session,
const char* name,
void** context);
/**
* @brief Set value of maxscale session variable.
*
* @param session The session.
* @param name_begin Should point to the beginning of the variable name.
* @param name_end Should point one past the end of the variable name.
* @param value_begin Should point to the beginning of the value.
* @param value_end Should point one past the end of the value.
*
* @note Should only be called from the protocol module that scans
* incoming statements.
*/
void session_set_variable_value(MXS_SESSION* session,
const char* name_begin,
const char* name_end,
const char* value_begin,
const char* value_end);
MXS_END_DECLS

View File

@ -22,6 +22,7 @@
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <algorithm>
#include <string>
#include <sstream>
@ -91,25 +92,24 @@ session_initialize(MXS_SESSION *session)
MXS_SESSION* session_alloc(SERVICE *service, DCB *client_dcb)
{
MXS_SESSION *session = (MXS_SESSION *)(MXS_MALLOC(sizeof(*session)));
if (NULL == session)
{
return NULL;
}
session_initialize(session);
session->ses_id = session_get_next_id();
return session_alloc_body(service, client_dcb, session);
return session_alloc_with_id(service, client_dcb, session_get_next_id());
}
MXS_SESSION* session_alloc_with_id(SERVICE *service, DCB *client_dcb, uint64_t id)
{
MXS_SESSION *session = (MXS_SESSION *)(MXS_MALLOC(sizeof(*session)));
if (session == NULL)
SessionVarsByName *session_variables = new (std::nothrow) SessionVarsByName;
if ((session == NULL) || (session_variables == NULL))
{
MXS_FREE(session);
delete session_variables;
return NULL;
}
session->variables = session_variables;
session_initialize(session);
session->ses_id = id;
return session_alloc_body(service, client_dcb, session);
@ -389,6 +389,7 @@ static void
session_final_free(MXS_SESSION *session)
{
gwbuf_free(session->stmt.buffer);
delete session->variables;
MXS_FREE(session);
}
@ -1105,3 +1106,84 @@ uint64_t session_get_current_id()
return session ? session->ses_id : 0;
}
bool session_add_variable(MXS_SESSION* session,
const char* name,
session_variable_handler_t handler,
void* context)
{
bool added = false;
static const char PREFIX[] = "@MAXSCALE.";
if (strncasecmp(name, PREFIX, sizeof(PREFIX) - 1) == 0)
{
string key(name);
std::transform(key.begin(), key.end(), key.begin(), toupper);
if (session->variables->find(key) != session->variables->end())
{
SESSION_VARIABLE variable;
variable.handler = handler;
variable.context = context;
session->variables->insert(std::make_pair(key, variable));
added = true;
}
else
{
MXS_ERROR("Session variable '%s' has been added already.", name);
}
}
else
{
MXS_ERROR("Session variable '%s' is not of the correct format.", name);
}
return added;
}
void session_set_variable_value(MXS_SESSION* session,
const char* name_begin,
const char* name_end,
const char* value_begin,
const char* value_end)
{
string key(name_begin, name_end - name_begin);
transform(key.begin(), key.end(), key.begin(), toupper);
SessionVarsByName::iterator i = session->variables->find(key);
if (i != session->variables->end())
{
i->second.handler(i->second.context, key.c_str(), value_begin, value_end);
}
}
bool session_remove_variable(MXS_SESSION* session,
const char* name,
void** context)
{
bool removed = false;
string key(name);
transform(key.begin(), key.end(), key.begin(), toupper);
SessionVarsByName::iterator i = session->variables->find(key);
if (i != session->variables->end())
{
if (context)
{
*context = i->second.context;
}
session->variables->erase(i);
removed = true;
}
return removed;
}

View File

@ -917,7 +917,9 @@ void handle_variables(MXS_SESSION* session, GWBUF** read_buffer)
break;
case SetParser::IS_SET_MAXSCALE:
// TODO: Handle "set @MAXSCALE...=...";
session_set_variable_value(session,
result.variable_begin(), result.variable_end(),
result.value_begin(), result.value_end());
break;
case SetParser::NOT_RELEVANT: