SQL Relay - Extension Modules - Query Filters

Query Filter Modules

- Introduction
- Available Modules
- Custom Modules

Introduction

Query Filter modules allow the SQL Relay server programs to filter out queries, and not pass them along to the database.

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

> <?xml version="1.0"?>
> <instances>
> ...
> <instance id="example" ... >
> ...
> <filters>
> <filter module="regex" pattern=" [0-9]*=[0-9]*"/>
> </filters>
> ...
> </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 filter 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.

Filter 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" ... >
> ...
> <filters>
> <filter module="regex" pattern=" [0-9]*=[0-9]*"/>
> <filter module="regex" pattern="^(create)"/>
> <filter module="regex" pattern="^(drop)"/>
> <filter module="string" pattern="hugetable" ignorecase="yes"/>
> <filter module="string" pattern="badschema" ignorecase="yes"/>
> </filters>
> ...
> </instance>
> ...
> </instances>

At startup, the SQL Relay server creates instances of the specified filter 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 filters out the query, then it isn't passed along to the next module, nor is it sent to the database, and the client program is told that the query failed.

When using query filters, it is helpful to use the normalize query translation too:

See Query Translations for more information.

Available Modules

Currently, the following filter modules are available:

- patterns
- regex
- string

patterns

The patterns module matches the query against a specified set of patterns. Each pattern may be a string, case-insensitive string, or regular expression. Each pattern may also be matched against the entire query, only the parts of the query that are outside of quotes, or only the parts of the query that are contained within quotes. If the query matches, then it is filtered out.

The list of patterns is given by a set of pattern child tags. Each pattern tag may have contain the following attributes.

- pattern - Required. The pattern to match.
- type - Optional. Defaults to "string". Valid values are "string", "cistring" (case insensitive string), and "regex" (regular expression).
- scope - Optional. Defaults to "query". Valid values are "query" (attempt to match against the entire query), "outsidequotes" (only match parts of the query not surrounded by single-quotes), and "insidequotes" (only match parts of the query surrounded by single-quotes).

For example, with the following configuration...

> <?xml version="1.0"?>
> <instances>
> ...
> <instance id="example" ... >
> ...
> <filters>
> <filter module="patterns">
> <pattern pattern="^(drop|create)" type="regex"/>
> <pattern pattern="hugetable" type="cistring" scope="outsidequotes"/>
> <pattern pattern="badstring" scope="insidequotes"/>
> </filter>
> </filters>
> ...
> </instance>
> ...
> </instances>

These queries would be filtered out:

> drop table mytable
> create table mytable (col1 int)
> select * from HugeTable
> select * from badstringtable where col1='badstring'

But these queries would not be:

> insert into mytable values (1)
> select * from goodtable
> select * from badstringtable where col1='goodstring'

regex

The regex module matches the query against a specified regular expression pattern. If the query matches, then it is filtered out. This module is useful if you need to do a quick match, without the complexity of the patterns module.

For example, with the following configuration:

> <?xml version="1.0"?>
> <instances>
> ...
> <instance id="example" ... >
> ...
> <filters>
> <filter module="regex" pattern=" [0-9]*=[0-9]*"/>
> </filters>
> ...
> </instance>
> ...
> </instances>

This query would be filtered out:

> select * from mytable where column1=1 and 1=1

But this query would not be:

> select * from mytable where column1=1

string

The string module matches the query against a specified string pattern. If the query matches, then it is filtered out. This module is useful if you need to do a quick match without the complexity of regular expressions or of the patterns module.

The module's ignorecase parameter defaults to "no", but if set to "yes", causes the comparison to be case insensitive.

For example, with the following configuration:

> <?xml version="1.0"?>
> <instances>
> ...
> <instance id="example" ... >
> ...
> <filters>
> <filter module="string" pattern="hugetable" ignorecase="yes"/>
> </filters>
> ...
> </instance>
> ...
> </instances>

This query would be filtered out:

> select * from hugetable

But this query would not be:

> select * from goodtable where column1=1

Custom Modules

You can create your own custom query filter modules too.

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

> class SQLRSERVER_DLLSPEC sqlrfilter {
> public:
> sqlrfilter(sqlrfilters *sqlrfs,
> xmldomnode *parameters,
> bool debug);
> virtual ~sqlrfilter();
> virtual bool run(sqlrserverconnection *sqlrcon,
> sqlrservercursor *sqlrcur,
> const char *query);
> protected:
> sqlrfilters *sqlrfs;
> 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 sqlrfs, parameters and debug to the corresponding passed-in values. sqlrfs is a pointer to an instance of the sqlrfilters 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 sqlrfilter, implements the necessary methods, and implements a function to return an instance of the class.

Lets say we want to create a custom filter module that filters out all queries that start with "select".

We can create the file select.cpp:

> #include <sqlrelay/sqlrserver.h>
> class SQLRSERVER_DLLSPEC select : public sqlrfilter {
> public:
> select(sqlrfilters *sqlrfs,
> xmldomnode *parameters,
> bool debug);
> bool run(sqlrserverconnection *sqlrcon,
> sqlrservercursor *sqlrcur,
> const char *query);
> };
> select::select(sqlrfilters *sqlrfs,
> xmldomnode *parameters,
> bool debug) : sqlrfilter(sqlrfs,parameters,debug) {
> }
> bool select::run(sqlrserverconnection *sqlrcon,
> sqlrservercursor *sqlrcur,
> const char *query) {
> return charstring::compareIgnoringCase(query,"select",6);
> }
> extern "C" {
> SQLRSERVER_DLLSPEC sqlrfilter *new_sqlrfilter_select(
> sqlrfilters *sqlrfs,
> xmldomnode *parameters,
> bool debug) {
> return new select(sqlrfs,parameters,debug);
> }
> }

Here, the select class inherits from sqlrfilter. The constructor just calls the parent constructor. The run() method returns false if the first 6 characters of the query match "select" (ignoring case) and true othersize. Returning false indicates that the query should be filtered out. Returning true indicates that the query should not be filtered out.

Note the "new_sqlrfilter_select()" function which just allocates an instance of select and returns it. This function is essential to provide, and it is essential that it be named "new_sqlrfilter_modulename" and take sqlrfilters *, 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 sqlrfilter_select.so select.cpp `sqlrserver-config --libs` `rudiments-config --libs`

This will create the file sqlrfilter_select.so

On Mac OSX, run a command like:

> gcc -bundle `sqlrserver-config --cflags` `rudiments-config --cflags` -o sqlrfilter_select.bundle select.cpp `sqlrserver-config --libs` `rudiments-config --libs`

This will create the sqlrfilter_select.bundle

On Windows, run commands like:

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

This will create the sqlrfilter_select.dll

It is essential that the file be named "sqlrfilter_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 "filters" section:

> <?xml version="1.0"?>
> <instances>
> ...
> <instance id="example" ... >
> ...
> <filters>
> <filter module="select"/>
> </filters>
> ...
> </instance>
> ...
> </instances>

The server will see the module="select" attribute in the sqlrelay.conf file, look in the "modules" directory for sqlrfilter_select.extension, load it and and run the new_sqlrfilter_select() method to get an instance of the select 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.