SQL Relay - Extension Modules - Query Translation

Query Translation Modules

- Introduction
- Available Modules
- Custom Modules

Introduction

Query Translation modules allow the SQL Relay server programs to alter queries before passing them to the database.

The translations section of the sqlrelay.conf file indicates which translation modules to load and what parameters to use when executing them.

> <?xml version="1.0"?>
> <instances>
> ...
> <instance id="example" ... >
> ...
> <translations>
> <translation module="normalize"/>
> </translations>
> ...
> </instance>
> ...
> </instances>

The module attribute specifies which module to load.

Module configurations may have attributes and/or nested tags. How these elements are interpreted is module-specific.

Currently, all translation modules have an enabled parameter, allowing the module to be temporarily disabled. If enabled="no" is configured, then the module is disabled. If set to any other value, or omitted, then the module is enabled.

Translation modules can be "stacked". Multiple modules may be loaded and multiple instances of the same type of module, with different configurations, may also be loaded.

> <?xml version="1.0"?>
> <instances>
> ...
> <instance id="example" ... >
> ...
> <translations>
> <translation module="normalize"/>
> <translation module="anothertranslation"/>
> <translation module="andanothertranslation"/>
> <translation module="yetanothertranslation"/>
> </translations>
> ...
> </instance>
> ...
> </instances>

At startup, the SQL Relay server creates instances of the specified translations modules and initializes them. When a query is run, the server passes the query to each module, in the order that they were specified in the config file. If a module modifies the query, then that modified query is passed on to the next module.

Available Modules

Currently, the following translation module is available:

- normalize

normalize

The normalize module performs the following operations on a query:

- Removes comments.
- Converts all white-space characters to spaces, outside of quoted strings.
- Converts multiple spaces into a single space, outside of quoted strings.
- Removes whitespace from around operators, except for parentheses and asterisks (for now).
- Converts everything to lower case, outside of quoted strings.
- Converts static concatenations to equivalent strings. Eg. converts 'he' || 'll' || 'o' to 'hello'.
- ...

For example, the following query:

> sElEcT
> *,
> 'He' || 'Ll' || 'o'
> from
> myTABLE
> where
> myTaBLe.CoLuMn1 = myTablE.ColuMN2 / 2

Would be translated to:

> select *, 'HeLlo' from mytable where mytable.column1 = mytable.column2/2

Normalizing a query is useful when also using Query Filters as it simplifies the patterns that have to be searched for.

Custom Modules

You can create your own custom query translation modules too.

SQL Relay provides a base class for query translation modules, called sqlrtranslation, defined in the header file: sqlrserver.h

> class SQLRSERVER_DLLSPEC sqlrtranslation {
> public:
> sqlrtranslation(sqlrtranslations *sqlts,
> xmldomnode *parameters,
> bool debug);
> virtual ~sqlrtranslation();
> virtual bool run(sqlrserverconnection *sqlrcon,
> sqlrservercursor *sqlrcur,
> const char *query,
> stringbuffer *translatedquery);
> protected:
> sqlrtranslations *sqlts;
> xmldomnode *parameters;
> bool debug;
> };

On non-Windows platforms, it's most likely installed in /usr/local/firstworks/include/sqlrelay if you built from source or /usr/include/sqlrelay if you installed a package. On Windows platforms, it's most likely installed in C:\Program Files\Firstworks\include\sqlrelay.

The default implementaion of the constructor just sets the member variables sqlts, parameters and debug to the corresponding passed-in values. sqlts is a pointer to an instance of the sqlrtranslations class, which provides various helper methods. parameters is a representation of the XML tag in the sqlrelay.conf file that loaded the module. debug is just a flag, indicating whether debug is requested or not. The module should consult this flat and print out some debug if it is true.

By default, the destructor does nothing.

run() does nothing by default and returns true, indicating that no error occurred.

A custom module module must contain a class that inherits from sqlrtranslation, implements the necessary methods, and implements a function to return an instance of the class.

Lets say we want to create a custom translation module that translates the entire query to lower case.

We can create the file lowercase.cpp:

> #include <sqlrelay/sqlrserver.h>
> #include <rudiments/stringbuffer.h>
> #include <rudiments/character.h>
> class SQLRSERVER_DLLSPEC lowercase : public sqlrtranslation {
> public:
> lowercase(sqlrtranslations *sqlts,
> xmldomnode *parameters,
> bool debug);
> bool run(sqlrserverconnection *sqlrcon,
> sqlrservercursor *sqlrcur,
> const char *query,
> stringbuffer *translatedquery);
> };
> lowercase::lowercase(sqlrtranslations *sqlts,
> xmldomnode *parameters,
> bool debug) :
> sqlrtranslation(sqlts,parameters,debug) {
> }
> bool lowercase::run(sqlrserverconnection *sqlrcon,
> sqlrservercursor *sqlrcur,
> const char *query,
> stringbuffer *translatedquery) {
> for (const char *c=query; *c; c++) {
> translatedquery->append(character::toLower(*c));
> }
> return true;
> }
> extern "C" {
> SQLRSERVER_DLLSPEC sqlrtranslation *new_sqlrtranslation_lowercase(
> sqlrtranslations *sqlts,
> xmldomnode *parameters,
> bool debug) {
> return new lowercase(sqlts,parameters,debug);
> }
> }

Here, the lowercase class inherits from sqlrtranslation. The constructor just calls the parent constructor. The run() method translates the query to lowercase and returns true.

Note the "new_sqlrtranslation_lowercase()" function which just allocates an instance of lowercase and returns it. This function is essential to provide, and it is essential that it be named "new_sqlrtranslation_modulename" and take sqlrtranslations *, xmldomnode * and bool. It is also essential that it be wrapped with an extern "C" clause to prevent it from being name-mangled by the compiler.

To build the module on a Linux or Unix system, run a command like:

> gcc -shared `sqlrserver-config --cflags` `rudiments-config --cflags` -o sqlrtranslation_lowercase.so lowercase.cpp `sqlrserver-config --libs` `rudiments-config --libs`

This will create the file sqlrtranslation_lowercase.so

On Mac OSX, run a command like:

> gcc -bundle `sqlrserver-config --cflags` `rudiments-config --cflags` -o sqlrtranslation_lowercase.bundle lowercase.cpp `sqlrserver-config --libs` `rudiments-config --libs`

This will create the sqlrtranslation_lowercase.bundle

On Windows, run commands like:

> cl /I"C:\Program Files\Firstworks\include" /c lowercase.cpp
> link -out:sqlrtranslation_lowercase.dll /LIBPATH:"C:\Program Files\Firstworks\lib" libsqlrserver.lib librudiments.lib

This will create the sqlrtranslation_lowercase.dll

It is essential that the file be named "sqlrtranslation_modulename.extension".

To install the new module, copy it to the SQL Relay "modules" directory. On non-Windows platforms, that is most likely /usr/local/firstworks/libexec/sqlrelay if you built from source, or /usr/libexec/sqlrelay if you installed a package. On Windows platforms, that is most likely C:\Program Files\Firstworks\libexec\sqlrelay

To configure an instance of SQL Relay to use your module, you will need to update the sqlrelay.conf file to include a "translations" section:

> <?xml version="1.0"?>
> <instances>
> ...
> <instance id="example" ... >
> ...
> <translations>
> <translation module="lowercase"/>
> </translations>
> ...
> </instance>
> ...
> </instances>

The server will see the module="lowercase" attribute in the sqlrelay.conf file, look in the "modules" directory for sqlrtranslation_lowercase.extension, load it and and run the new_sqlrtranslation_lowercase() method to get an instance of the lowercase class and then run the various methods of that class.

This example module doesn't have any parameters, but if you create a module that does then you can access them via the the protected "parameters" member variable. For example:

> const char *count=parameters->getAttributeValue("count");

Refer to the Rudiments documentation for more info on the xmldomnode class.