Menu
Simba Technologies
Simba Technologies

SimbaEngine X SDK 10.1.3
Developing Drivers for Data Stores Without SQL

Example Implementation

This section shows an example implementation of IReplacer for a custom ODBC or JDBC driver. The following steps are required:

Step 1: Implement Your Custom IReplacer

Implement your IReplacer to convert ODBC standard escape sequences to the commands that your data store understands.

Note:

Example

#include <Simba.h>
#include <ODBCEscaper.h>

static char const* keyword[] = {
    "DATE ", "ESCAPE ", "TIME "
};


class MyReplacer : public IReplacer
{

    MyReplacer()
    {
        m_numParams = 0;
    }

    simba_wstring operator()(ODBCEscaper::ESC_TYPE in_etype, std::vector<simba_wstring>& args)
    {
        switch (in_etype)
        {

        // Date, Time, and Timestamp Escape Sequences
        case ODBCEscaper::ESC_TYPE_DATE:
        case ODBCEscaper::ESC_TYPE_ESCAPE:
        case ODBCEscaper::ESC_TYPE_TIME:
        {
            return simba_wstring(keyword[in_etype - ODBCEscaper::ESC_TYPE_DATE]) + args[0];
        }
        break;

        // Here replace ? with ($1)....
        case ODBCEscaper::ESC_TYPE_PARAM:
        {
            char buf[99];
            sprintf(buf, "($%d)", ++m_numParams);
            // implicit conversion to simba_wstring
            return buf;
        }
        break;

        //Scalar Functions Escape sequences.
        case ODBCEscaper::ESC_TYPE_FN:
        {
            if (args[0].IsEqual("CEILING", false))
            {
                args[0] = "CEIL";
            }
            else if (args[0].IsEqual("CHAR", false))
            {
                args[0] = "CHR";
            }
            else if (args[0].IsEqual("POWER", false))
            {
                args[0] = "POW";
            }
            if ((args[0].IsEqual("CONVERT", false)) && (3 == args.size()))
            {
                args[0] = "CAST";
            }

            return args[0] + "(" + simba_wstring::Join(args.begin() + 1, args.end(), ", ") + ")";
        }
        break;

        // Handling the non-escaped scalar functions: Note different argument order.
        case ODBCEscaper::ESC_TYPE_FUNC:
        {
            if ((args[0].IsEqual("CONVERT", false)) && (3 == args.size()))
            {
                return "CAST_RAW(" + args[2] + " AS " + args[1] + ")";
            }
        }
        break;

        // unimplemented Escape Types.
        default:
        {
            return simba_wstring("TODO: ") + ODBCEscaper::type_name[in_etype];
        }
        break;
        }
    }

private:
    int m_numParams;

};

Step 2: Create an Instance of ODBCEscaper

ODBCEscaper or JDBCEscaper handles the parsing of the SQL command, identifying parameter markers and escape sequences while passing over the contents of strings, identifiers and comments. It passes each parameter marker and escape sequence to IReplacer, along with the type and argument information. IReplacer returns the converted command.

Parsing is done from left to right, and in the case of nested escape sequences, from the inner to the outer brackets. When the parsing and replacements are finished, ODBCEscaper or and JDBCEscaper reassemble the SQL command, adding back any strings or comments.

Create an instance of ODBCEscaper, then call ODBCEscaper.Apply(), passing a instance of your custom IReplacer and the SQL command to parse and convert. Because IReplacer maintains state for the duration of a SQL command, you must create a new IReplacer for each SQL command that you want to parse.

Example:

ODBCEscaper esc;

MyReplacer replacer;

simba_wstring newSQLstr;

// newSQLstr will contain the converted SQL command

simba_wstring newSQLstr = esc.Apply(replacer, "SELECT {fn LOG( {fn LOG10({fn POWER(10,2)})})}");

 

Step 3: Ensure Additional Requirements are Met

This section contains additional information and requirements for implementing your IReplacer.

Return commands that are not ODBC or JDBC compliant

If IReplacer encounters a command escape sequence that is not part of the ODBC or JDBC specification, it should return the command back to ODBCReplacer without modification. This is illustrated in the "Workflow" section in Introduction to the MiniParser, as the parameter sansargs is not ODBC compliant.

Maintain a parameter count

Your IReplacer implementation must keep track of the number of parameter markers it returns so that it can increment them correctly. For example, “@1”, “@2”, “@3”, or ($1), ($2), ($3).

Reject unknown input

If your IReplacer implementation receives input that it does not know how to handle, it must throw an exception or return the string in curly brackets ( { }).

Important:

For security reasons, an IReplacer must never return a string that forces a syntax error.

Return an expression in parenthesis or surrounded by spaces

Where possible, the commands or expressions that your IReplacer returns should be surrounded by parentheses (()) or spaces ( ). This allows the ODBCEscaper to correctly reassemble the SQL command. The IReplacer sample surrounds the commands and parameters with parentheses. For example, when returning the value [‘4:05’::TIME], format the value in one of the following ways:

  • [ ‘4:05’::TIME ] // notice the spaces
  • Or, [(‘4:05’::TIME)] // notice the parenthesis

Ensure correct syntax

In order for ODBCReplacer to correctly parse and reassemble the SQL statement, the IReplacer implementation must always return parameters and converted escape sequences that contain correct syntax.

Important:

IReplacer must not return an odd number of quotes, an unterminated comment, or mismatched parentheses.

 

Related Links

Non-Escaped Scalar Functions

Error Handling