Class SqlGenerator
- All Implemented Interfaces:
GJVisitor<Object,
EvalContext>
Only a subset of expressions are supported by the SQL Generator; an EvalException is thrown if an invalid expression is supplied.
The context passed into this class contains:
- internal parameters that effect how the SQL is generated (such as the database vendor)
- evaluatable functions (run at translation time)
- translatable functions (converted into SQL at translation time)
- database columns used in the SQL
- variables (which are substituted at translation time into the value of that variable)
- positional parameters (which are substituted at execution time into JDBC positional parameters)
Variables
It's probably worth going into the difference between the two different types of variables. If I have a variable that I know the value of at translation time; e.g. the variablemessageVar
is MT500
, I can generate standalone SQL that uses this variable:
// [1] parse the expression into an AST String exprString = "externalMessageType == messageVar"; ExpressionParser parser = new ExpressionParser(new StringReader(exprString)); TopLevelExpression expr = parser.TopLevelExpression(); // [2] set up an evaluation context to define variables EvalContext context = new EvalContext(); // [3] set up database columns used in the query // note the first 'externalMessageType' in this line refers to the name // of the column in exprString, and the second 'externalMessageType' refers // to the name of the column in the table we're querying. It's clearer // if these are set to the same value, but there's no other reason they have to. context.setVariable("externalMessageType", new SqlColumn("externalMessageType")); // [4] set up variables used in the query context.setVariable("messageVar", "MT500"); // [5] generate the SQL SqlGenerator generator = new SqlGenerator(); String result = (String) generator.visit(expr, context);
The result here being externalMessageType = 'MT500'
.
If, however, I won't know messageVar
until execution time, I can still
generate the SQL using a PositionalParameter object:
// parse the expression and setup context as per [1], [2] and [3] above // [4] set up an evaluation context to define variables context.setVariable("messageVar", new PositionalParameter("messageVar")); // [5] generate the SQL as above SqlGenerator generator = new SqlGenerator(); String result = (String) generator.visit(expr, context);The result here being
externalMessageType = ?
instead. The value of the
'?
' in the result is supplied
by the program at execution time, using standard JDBC positional parameters. But, you're
probably thinking, how do I know which positional parameter to use? After all,
I could have had more than one PositionalParameter's in the context, and they may
appear in any order or any number of times in the result String. To determine how to
supply these values to JDBC, the context variable VAR_PARAMETERS is set to a List of
Strings, each being the name of the positional parameter above. In the example above,
if we then executed
List parameList = (List) context.getVariable(SqlGenerator.VAR_PARAMETERS);this will set paramList to a one-element list, containing the String "messageVar" (corresponding to the single '
?
' in the result String). We can
iterate over this list to create the actual parameters to pass into JDBC when running
the query.
Static dates
If a date is inserted into an SQL query, we need to know which database we're running on, since each vendor seems to have their own way of representing fixed dates. This is set using the VAR_DATABASE_TYPE variable in the ExpressionContext, which is set to either DATABASE_ORACLE, DATABASE_DB2, DATABASE_SQLSERVER or DATABASE_MYSQL.Evaluatable functions
Any functions which implement EvalFunction are evaluated at translation time, (i.e. are not translated into SQL). This is useful for things like nextWorkingDay(), which need to execute before the SQL is passed through to the database.Translatable functions
Any functions which implement SqlFunction are translated into SQL and evaluated by the database. This is useful for things like between() and like(), which are converted into relational operators (< and >) and LIKE operators.If a function implements both EvalFunction and SqlFunction, then this class will always attempt to translate the function into SQL using the SqlFunction interface.
Database columns
If a column is to be referenced from within the SQL, it must be set up in the EvalContext as well (as per the 'externalMessageType' lines in the previous example).Column types
If not specified, the type of the column is VARCHAR, although this can be set in the constructor:SqlColumn arrivalDate = new SqlColumn("arrivalDate", SqlColumn.DATE);
(All java.sql.Types.* constants are supported here).
Column tables
If our resultset JOINs many tables, and we wish to retrieve a value from a specific table, that can also be placed in the SqlColumn constructor.
// e.g. SELECT tableA.id, tableB.id FROM tableA, tableB WHERE tableA.x = tableB.x SqlColumn tableA_id = new SqlColumn("id", "tableA"); SqlColumn tableB_id = new SqlColumn("id", "tableB");
FixedDate values
Another custom data type is the 'fixed date', which is a date represented as a string, in YYYYMMDD format. It is treated internally as identical to a VARCHAR column, but allows operations on these columns to be aware that the information being stored in it is a date.
Other references:
- DB2 date syntax: http://www-106.ibm.com/developerworks/db2/library/techarticle/0211yip/0211yip3.html
- SQL date syntax: http://msdn.microsoft.com/library/en-us/tsqlref/ts_ca-co_2f3o.asp
- Oracle date syntax: http://www-db.stanford.edu/~ullman/fcdb/oracle/or-time.html
- Author:
- knoxg
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final String
DB2 VAR_DATABASE_TYPE valuestatic final String
Jet (MSAccess) VAR_DATABASE_TYPE valuestatic final String
MySQL VAR_DATABASE_TYPE valuestatic final String
Oracle VAR_DATABASE_TYPE valuestatic final String
SQL Server VAR_DATABASE_TYPE valuestatic final String
Internal variable to contain database type.static final String
Internal variable to configure empty string comparison handling.static final String
Internal variable used to contain positional parameter names. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionstatic SqlText
comparisonOpToSql
(String sqlOp, EvalContext evalContext, Object lhs, Object rhs) Generates code for comparisons of MoneyValue and Date types.static String
escapeLikeLiteral
(EvalContext evalContext, String string) Escape the text used in a LIKE function, so that % and _ characters have no special meaning.static String
escapeStringLiteral
(EvalContext evalContext, String string) Escape a string literal.static String
getDatabaseType
(EvalContext evalContext) Return the database type from this contextstatic SqlText
toSql
(EvalContext evalContext, Object obj) Convert an evaluated node object into it's SQL representation.visit
(AdditiveExpression n, EvalContext context) Generate the SQL for a AdditiveExpression node.visit
(ArgumentList n, EvalContext context) Generate the SQL for a ArgumentList node.visit
(Arguments n, EvalContext context) Generate the SQL for an Arguments nodevisit
(BooleanLiteral n, EvalContext context) Generate the SQL for a BooleanLiteral node.visit
(ConditionalAndExpression n, EvalContext context) Generate the SQL for a ConditionalAndExpression node.visit
(EqualityExpression n, EvalContext context) Generate the SQL for a EqualityExpression node.visit
(Expression n, EvalContext context) Generate the SQL for an Expression node.visit
(FunctionCall n, EvalContext context) Generate the SQL for a FunctionCall node.visit
(Literal n, EvalContext context) Generate the SQL for a Literal node.visit
(MultiplicativeExpression n, EvalContext context) Generate the SQL for a MultiplicativeExpression node.visit
(Name n, EvalContext context) Generate the SQL for a Name node.visit
(NodeToken n, EvalContext context) This is never executed (we do not evaluate tokens)visit
(NullLiteral n, EvalContext context) Generate the SQL for a NullLiteral node.visit
(PrimaryExpression n, EvalContext context) Generate the SQL for a PrimaryExpression node.visit
(RelationalExpression n, EvalContext context) Generate the SQL for a RelationalExpression node.visit
(TopLevelExpression n, EvalContext context) Convert a TopLevelExpression node to it's SQL representation.visit
(UnaryExpression n, EvalContext context) Generate the SQL for a UnaryExpression node.Methods inherited from class com.randomnoun.common.jexl.eval.Evaluator
coerceType, mathOp
Methods inherited from class com.randomnoun.common.jexl.visitor.GJDepthFirst
visit, visit, visit, visit
-
Field Details
-
VAR_DATABASE_TYPE
Internal variable to contain database type. (internal variables start with "." since they are not valid identifiers, and therefore cannot be referenced from within Expressions).- See Also:
-
VAR_PARAMETERS
Internal variable used to contain positional parameter names.- See Also:
-
VAR_EMPTY_STRING_COMPARISON_INCLUDES_NULL
Internal variable to configure empty string comparison handling. When set to Boolean.TRUE, a comparison with the String constant "" will also include NULL values. See {comparisonOpToSql(String, EvalContext, Object, Object)
- See Also:
-
DATABASE_ORACLE
Oracle VAR_DATABASE_TYPE value- See Also:
-
DATABASE_DB2
DB2 VAR_DATABASE_TYPE value- See Also:
-
DATABASE_SQLSERVER
SQL Server VAR_DATABASE_TYPE value- See Also:
-
DATABASE_MYSQL
MySQL VAR_DATABASE_TYPE value- See Also:
-
DATABASE_JET
Jet (MSAccess) VAR_DATABASE_TYPE value- See Also:
-
-
Constructor Details
-
SqlGenerator
public SqlGenerator()
-
-
Method Details
-
comparisonOpToSql
public static SqlText comparisonOpToSql(String sqlOp, EvalContext evalContext, Object lhs, Object rhs) Generates code for comparisons of MoneyValue and Date types. Other data types fall back to "(" + lhs + " " + sqlOp + " " + rhs + ")".- Parameters:
sqlOp
- The operation we wish to insert into the SQL. (NB: this is *NOT* the operation in the .javacc syntax; e.g. use "=" as an sqlOp, not "==" ).lhs
- The left hand side of the operationrhs
- The right hand side of the operationnewSqlOp
- A modified SQL operator to use when dealing with dates, in certain conditions. See the wiki for details.- Returns:
- The SQL for this comparison
-
getDatabaseType
Return the database type from this context- Parameters:
evalContext
- The context passed to the .visit() method of this class- Returns:
- a DATABASE_* constant
- Throws:
EvalException
- if database type is not set, or is invalid
-
toSql
Convert an evaluated node object into it's SQL representation. The code must have evaluated to a literal String or Number by the time this method is called, or an EvalException is thrown.- Parameters:
obj
- The object to convert to SQL- Returns:
- The SQL representation of this object
-
escapeStringLiteral
Escape a string literal. (in oracle, this surrounds a value with quotes and replaces quotes with doubled-up quote characters).- Parameters:
evalContext
-string
-- Returns:
-
escapeLikeLiteral
Escape the text used in a LIKE function, so that % and _ characters have no special meaning. Note that an escaped like literal still needs to be passed through escapeStringLiteral in order to surround it with quotes/escape quotes, etc...(This isn't done in this method because we want to be able to modify the LIKE value that the user enters e.g. for the startsWith() function)
- Parameters:
evalContext
- The evaluation context (containing the database type)string
- The string to be escaped- Returns:
- The escaped string
-
visit
Convert a TopLevelExpression node to it's SQL representation. The SQL is returned as a String. If any positional parameters have been referenced inside the SQL, then the context variable VAR_PARAMETERS is set, containing an ordered list of Strings, containing the positional parameter names.The PRE text in these javadocs correspond to the javacc expansion of this node; e.g. in expression.jj, the rule for TopLevelExpression is
void TopLevelExpression(): {} { Expression()
} jtb then creates two fields in the TopLevelExpression object, "expression" (containing the Expression() node) and "nodeToken" (containing the EOF token). This is documented in the form below (this is included for all jtb-generated visit methods):
See the class javadocs for detailed instructions on how to use this method.
expression -> Expression() nodeToken -> <EOF>
-
visit
Generate the SQL for an Expression node.conditionalAndExpression -> ConditionalAndExpression() nodeListOptional -> ( "||" ConditionalAndExpression() )*
-
visit
Generate the SQL for a ConditionalAndExpression node.equalityExpression -> EqualityExpression() nodeListOptional -> ( "invalid input: '&'invalid input: '&'" EqualityExpression() )*
-
visit
Generate the SQL for a EqualityExpression node.relationalExpression -> RelationalExpression() nodeListOptional -> ( ( "==" | "!=" ) RelationalExpression() )*
-
visit
Generate the SQL for a RelationalExpression node.additiveExpression -> AdditiveExpression() nodeListOptional -> ( ( "<" | ">" | "<=" | ">=" ) AdditiveExpression() )*
-
visit
Generate the SQL for a AdditiveExpression node.multiplicativeExpression -> MultiplicativeExpression() nodeListOptional -> ( ( "+" | "-" ) MultiplicativeExpression() )*
-
visit
Generate the SQL for a MultiplicativeExpression node.unaryExpression -> UnaryExpression() nodeListOptional -> ( ( "*" | "/" | "%" ) UnaryExpression() )*
-
visit
Generate the SQL for a UnaryExpression node.nodeChoice -> ( "~" | "!" | "-" ) UnaryExpression() | PrimaryExpression()
-
visit
Generate the SQL for a PrimaryExpression node.nodeChoice -> FunctionCall() | Name() | Literal() | "(" Expression() ")"
-
visit
Generate the SQL for a Name node.nodeToken -> <IDENTIFIER> nodeListOptional -> ( "." <IDENTIFIER> )*
-
visit
Generate the SQL for a FunctionCall node.nodeToken -> <IDENTIFIER> arguments -> Arguments()
-
visit
Generate the SQL for an Arguments nodenodeToken -> "(" nodeOptional -> [ ArgumentList() ] nodeToken1 -> ")"
-
visit
Generate the SQL for a ArgumentList node.(Since an ArgumentList is fed directly into the EvalFunction to generate SQL, we just return a List of arguments)
expression -> Expression() nodeListOptional -> ( "," Expression() )*
-
visit
Generate the SQL for a Literal node.nodeChoice -> <INTEGER_LITERAL> | <FLOATING_POINT_LITERAL> | <CHARACTER_LITERAL> | <STRING_LITERAL> | BooleanLiteral() | NullLiteral()
-
visit
Generate the SQL for a BooleanLiteral node.nodeChoice -> "true" | "false"
-
visit
Generate the SQL for a NullLiteral node.nodeToken -> "null"
-
visit
This is never executed (we do not evaluate tokens)
-