Own template render implementation: first draft

Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/040bfbce
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/040bfbce
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/040bfbce

Branch: refs/heads/master
Commit: 040bfbce6c2e79b1686f392964c6204477ec3e59
Parents: 55e3c97
Author: Nikita Timofeev <stari...@gmail.com>
Authored: Tue Aug 8 18:51:47 2017 +0300
Committer: Nikita Timofeev <stari...@gmail.com>
Committed: Wed Aug 16 18:29:35 2017 +0300

----------------------------------------------------------------------
 .../configuration/server/ServerModule.java      |    4 +-
 .../template/CayenneSQLTemplateProcessor.java   |   29 +-
 .../org/apache/cayenne/template/Context.java    |   56 +-
 .../org/apache/cayenne/template/Directive.java  |   31 -
 .../apache/cayenne/template/directive/Bind.java |   32 +-
 .../cayenne/template/directive/BindEqual.java   |   41 +
 .../template/directive/BindNotEqual.java        |   42 +
 .../template/directive/BindObjectEqual.java     |  145 ++
 .../template/directive/BindObjectNotEqual.java  |   51 +
 .../cayenne/template/directive/Directive.java   |   32 +
 .../cayenne/template/directive/Result.java      |  126 ++
 .../cayenne/template/parser/ASTArray.java       |   50 +
 .../cayenne/template/parser/ASTDirective.java   |    9 +-
 .../cayenne/template/parser/ASTExpression.java  |    2 +-
 .../cayenne/template/parser/ASTMethod.java      |    8 +-
 .../cayenne/template/parser/ASTVariable.java    |    7 +-
 .../cayenne/template/parser/JavaCharStream.java |   10 +-
 .../cayenne/template/parser/ParseException.java |  312 ++--
 .../template/parser/SQLTemplateParser.java      |  253 +++-
 .../parser/SQLTemplateParserConstants.java      |   93 +-
 .../parser/SQLTemplateParserTokenManager.java   | 1383 ++++++++++--------
 .../parser/SQLTemplateParserTreeConstants.java  |    4 +-
 .../cayenne/template/parser/ScalarNode.java     |    5 +
 .../template/parser/SQLTemplateParser.jjt       |  233 ++-
 .../org/apache/cayenne/query/SQLSelectIT.java   |    5 +
 .../org/apache/cayenne/query/SQLTemplateIT.java |    4 +
 .../CayenneSQLTemplateProcessorTest.java        |   31 +
 .../template/parser/SQLTemplateParserTest.java  |  181 +++
 28 files changed, 2252 insertions(+), 927 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
index 8bf1d6a..e951c49 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
@@ -111,6 +111,7 @@ import org.apache.cayenne.map.EntitySorter;
 import org.apache.cayenne.access.types.ValueObjectType;
 import org.apache.cayenne.resource.ClassLoaderResourceLocator;
 import org.apache.cayenne.resource.ResourceLocator;
+import org.apache.cayenne.template.CayenneSQLTemplateProcessor;
 import org.apache.cayenne.tx.DefaultTransactionFactory;
 import org.apache.cayenne.tx.DefaultTransactionManager;
 import org.apache.cayenne.tx.TransactionFactory;
@@ -408,7 +409,8 @@ public class ServerModule implements Module {
         
binder.bind(TransactionManager.class).to(DefaultTransactionManager.class);
         binder.bind(RowReaderFactory.class).to(DefaultRowReaderFactory.class);
 
-        
binder.bind(SQLTemplateProcessor.class).to(VelocitySQLTemplateProcessor.class);
+//        
binder.bind(SQLTemplateProcessor.class).to(VelocitySQLTemplateProcessor.class);
+        
binder.bind(SQLTemplateProcessor.class).to(CayenneSQLTemplateProcessor.class);
 
         binder.bind(HandlerFactory.class).to(DefaultHandlerFactory.class);
         
binder.bind(DataChannelMetaData.class).to(NoopDataChannelMetaData.class);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/CayenneSQLTemplateProcessor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/CayenneSQLTemplateProcessor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/CayenneSQLTemplateProcessor.java
index 062f1c5..0352417 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/CayenneSQLTemplateProcessor.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/CayenneSQLTemplateProcessor.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.template;
 
+import java.io.BufferedReader;
 import java.io.StringReader;
 import java.util.HashMap;
 import java.util.List;
@@ -27,9 +28,11 @@ import java.util.Map;
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.jdbc.SQLStatement;
 import org.apache.cayenne.access.jdbc.SQLTemplateProcessor;
-import org.apache.cayenne.template.parser.ASTBlock;
+import org.apache.cayenne.template.parser.Node;
 import org.apache.cayenne.template.parser.ParseException;
 import org.apache.cayenne.template.parser.SQLTemplateParser;
+import org.apache.cayenne.template.parser.TokenMgrError;
+import org.apache.cayenne.util.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
 
 
 /**
@@ -37,6 +40,9 @@ import org.apache.cayenne.template.parser.SQLTemplateParser;
  */
 public class CayenneSQLTemplateProcessor implements SQLTemplateProcessor {
 
+    ConcurrentLinkedHashMap<String, Node> templateCache = new 
ConcurrentLinkedHashMap
+            .Builder<String, Node>().maximumWeightedCapacity(100).build();
+
     @Override
     public SQLStatement processTemplate(String template, Map<String, ?> 
parameters) {
         Context context = new Context();
@@ -46,7 +52,7 @@ public class CayenneSQLTemplateProcessor implements 
SQLTemplateProcessor {
 
     @Override
     public SQLStatement processTemplate(String template, List<Object> 
positionalParameters) {
-        Context context = new Context();
+        Context context = new Context(true);
         Map<String, Object> parameters = new HashMap<>();
         int i=0;
         for(Object param : positionalParameters) {
@@ -57,13 +63,18 @@ public class CayenneSQLTemplateProcessor implements 
SQLTemplateProcessor {
     }
 
     protected SQLStatement process(String template, Context context) {
-        SQLTemplateParser parser = new SQLTemplateParser(new 
StringReader(template));
-        try {
-            ASTBlock block = parser.template();
-            String sql = block.evaluate(context);
-            return new SQLStatement(sql, context.getColumnDescriptors(), 
context.getParameterBindings());
-        } catch (ParseException ex) {
-            throw new CayenneRuntimeException("Error parsing template '%s' : 
%s", template, ex.getMessage());
+        Node node = templateCache.get(template);
+        if(node == null) {
+            SQLTemplateParser parser = new SQLTemplateParser(new 
BufferedReader(new StringReader(template)));
+            try {
+                node = parser.template();
+            } catch (ParseException | TokenMgrError ex) {
+                throw new CayenneRuntimeException("Error parsing template '%s' 
: %s", template, ex.getMessage());
+            }
+            templateCache.put(template, node);
         }
+
+        String sql = node.evaluate(context);
+        return new SQLStatement(sql, context.getColumnDescriptors(), 
context.getParameterBindings());
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/Context.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/Context.java 
b/cayenne-server/src/main/java/org/apache/cayenne/template/Context.java
index 7ccb911..61c37b2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/template/Context.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/template/Context.java
@@ -23,11 +23,18 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.jdbc.ColumnDescriptor;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.template.directive.Bind;
+import org.apache.cayenne.template.directive.BindEqual;
+import org.apache.cayenne.template.directive.BindNotEqual;
+import org.apache.cayenne.template.directive.BindObjectEqual;
+import org.apache.cayenne.template.directive.BindObjectNotEqual;
+import org.apache.cayenne.template.directive.Directive;
+import org.apache.cayenne.template.directive.Result;
+import org.apache.cayenne.velocity.SQLTemplateRenderingUtils;
 
 /**
  * @since 4.1
@@ -38,12 +45,33 @@ public class Context {
 
     Map<String, Object> objects = new HashMap<>();
 
+    Map<String, String> parameterAliases;
+
     List<ParameterBinding> parameterBindings = new ArrayList<>();
 
     List<ColumnDescriptor> columnDescriptors = new ArrayList<>();
 
+    boolean positionalMode;
+
+    int counter;
+
     public Context() {
-        directives.put("bind", new Bind());
+        directives.put(               "bind", Bind.INSTANCE);
+        directives.put(          "bindEqual", BindEqual.INSTANCE);
+        directives.put(       "bindNotEqual", BindNotEqual.INSTANCE);
+        directives.put(    "bindObjectEqual", BindObjectEqual.INSTANCE);
+        directives.put( "bindObjectNotEqual", BindObjectNotEqual.INSTANCE);
+        directives.put(             "result", Result.INSTANCE);
+
+        objects.put("helper", new SQLTemplateRenderingUtils());
+    }
+
+    public Context(boolean positionalMode) {
+        this();
+        this.positionalMode = positionalMode;
+        if(positionalMode) {
+            parameterAliases = new HashMap<>();
+        }
     }
 
     public Directive getDirective(String name) {
@@ -51,7 +79,29 @@ public class Context {
     }
 
     public Object getObject(String name) {
-        return objects.get(name);
+        Object object = objects.get(name);
+        if(object != null) {
+            return object;
+        }
+
+        if(positionalMode) {
+            String alias = parameterAliases.get(name);
+            if(alias == null) {
+                if(counter > objects.size() - 2) {
+                    throw new CayenneRuntimeException("Too few parameters to 
bind template: " + (objects.size() - 1));
+                }
+                alias = String.valueOf(counter++);
+                parameterAliases.put(name, alias);
+            }
+            // give next object on each invocation of method
+            return objects.get(alias);
+        }
+
+        return null;
+    }
+
+    public void addParameter(String name, Object value) {
+        objects.put(name, value);
     }
 
     public void addParameters(Map<String, ?> parameters) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/Directive.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/Directive.java 
b/cayenne-server/src/main/java/org/apache/cayenne/template/Directive.java
deleted file mode 100644
index 94c0818..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/template/Directive.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.template;
-
-import org.apache.cayenne.template.parser.ASTExpression;
-
-/**
- * @since 4.1
- */
-public interface Directive {
-
-    String apply(Context context, ASTExpression... expressions);
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Bind.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Bind.java 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Bind.java
index b867072..8d2138f 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Bind.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Bind.java
@@ -19,10 +19,12 @@
 
 package org.apache.cayenne.template.directive;
 
+import java.util.Collection;
+import java.util.Iterator;
+
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.template.Context;
-import org.apache.cayenne.template.Directive;
 import org.apache.cayenne.template.parser.ASTExpression;
 
 /**
@@ -30,14 +32,17 @@ import org.apache.cayenne.template.parser.ASTExpression;
  */
 public class Bind implements Directive {
 
+    public static final Bind INSTANCE = new Bind();
+
     @Override
     public String apply(Context context, ASTExpression... expressions) {
-        if(expressions.length < 2) {
+        if(expressions.length < 1) {
             throw new IllegalArgumentException();
         }
 
         Object value = expressions[0].evaluateAsObject(context);
-        String jdbcTypeName = expressions[1].evaluate(context);
+        String jdbcTypeName = expressions.length < 2 ? null : 
expressions[1].evaluate(context);
+
         int jdbcType;
         if (jdbcTypeName != null) {
             jdbcType = TypesMapping.getSqlTypeByName(jdbcTypeName);
@@ -48,9 +53,24 @@ public class Bind implements Directive {
         }
         int scale = expressions.length < 3 ? -1 : 
(int)expressions[2].evaluateAsLong(context);
 
-        ParameterBinding binding = new ParameterBinding(value, jdbcType, 
scale);
-        context.addParameterBinding(binding);
+        StringBuilder builder = new StringBuilder();
+        if (value instanceof Collection) {
+            Iterator<?> it = ((Collection) value).iterator();
+            while (it.hasNext()) {
+                processBinding(context, builder, new 
ParameterBinding(it.next(), jdbcType, scale));
+                if (it.hasNext()) {
+                    builder.append(',');
+                }
+            }
+        } else {
+            processBinding(context, builder, new ParameterBinding(value, 
jdbcType, scale));
+        }
+
+        return builder.toString();
+    }
 
-        return "?";
+    protected void processBinding(Context context, StringBuilder builder, 
ParameterBinding binding) {
+        context.addParameterBinding(binding);
+        builder.append('?');
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindEqual.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindEqual.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindEqual.java
new file mode 100644
index 0000000..1153675
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindEqual.java
@@ -0,0 +1,41 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.template.directive;
+
+import org.apache.cayenne.access.translator.ParameterBinding;
+import org.apache.cayenne.template.Context;
+
+/**
+ * @since 4.1
+ */
+public class BindEqual extends Bind {
+
+    public static final BindEqual INSTANCE = new BindEqual();
+
+    @Override
+    protected void processBinding(Context context, StringBuilder builder, 
ParameterBinding binding) {
+        if (binding.getValue() != null) {
+            context.addParameterBinding(binding);
+            builder.append("= ?");
+        } else {
+            builder.append("IS NULL");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindNotEqual.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindNotEqual.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindNotEqual.java
new file mode 100644
index 0000000..09f70ed
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindNotEqual.java
@@ -0,0 +1,42 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.template.directive;
+
+import org.apache.cayenne.access.translator.ParameterBinding;
+import org.apache.cayenne.template.Context;
+
+/**
+ * @since 4.1
+ */
+public class BindNotEqual extends Bind {
+
+    public static final BindEqual INSTANCE = new BindEqual();
+
+    @Override
+    protected void processBinding(Context context, StringBuilder builder, 
ParameterBinding binding) {
+        if (binding.getValue() != null) {
+            context.addParameterBinding(binding);
+            builder.append("<> ?");
+        } else {
+            builder.append("IS NOT NULL");
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectEqual.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectEqual.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectEqual.java
new file mode 100644
index 0000000..cea8b4f
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectEqual.java
@@ -0,0 +1,145 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.template.directive;
+
+import java.sql.Types;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.access.translator.ParameterBinding;
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.template.Context;
+import org.apache.cayenne.template.parser.ASTExpression;
+import org.apache.velocity.exception.ParseErrorException;
+
+/**
+ * @since 4.1
+ */
+public class BindObjectEqual implements Directive {
+
+    public static final BindObjectEqual INSTANCE = new BindObjectEqual();
+
+    @Override
+    public String apply(Context context, ASTExpression... expressions) {
+
+        Object object = expressions[0].evaluateAsObject(context);
+        Map<String, Object> idMap = toIdMap(object);
+
+        Object sqlColumns = null;
+        Object idColumns = null;
+        if(expressions.length > 1) {
+            sqlColumns = expressions[1].evaluateAsObject(context);
+        }
+        if(expressions.length > 2) {
+            idColumns = expressions[2].evaluateAsObject(context);
+        }
+
+        if (idMap == null) {
+            // assume null object, and bind all null values
+            if (sqlColumns == null || idColumns == null) {
+                throw new ParseErrorException("Invalid parameters. "
+                        + "Either object has to be set or sqlColumns and 
idColumns or both.");
+            }
+
+            idMap = Collections.emptyMap();
+        } else if (sqlColumns == null || idColumns == null) {
+            // infer SQL columns from ID columns
+            sqlColumns = idMap.keySet().toArray();
+            idColumns = sqlColumns;
+        }
+
+        String[] sqlColumnsArray = toArray(sqlColumns);
+        String[] idColumnsArray = toArray(idColumns);
+
+        if (sqlColumnsArray.length != idColumnsArray.length) {
+            throw new ParseErrorException(
+                    "SQL columns and ID columns arrays have different sizes.");
+        }
+
+        StringBuilder builder = new StringBuilder();
+
+        for (int i = 0; i < sqlColumnsArray.length; i++) {
+            Object value = idMap.get(idColumnsArray[i]);
+            int jdbcType = (value != null) ? 
TypesMapping.getSqlTypeByJava(value.getClass()) : Types.INTEGER;
+
+            renderColumn(sqlColumnsArray[i], i, builder);
+            render(context, builder, new ParameterBinding(value, jdbcType, 
-1));
+        }
+
+        return builder.toString();
+    }
+
+    protected void renderColumn(String columnName, int columnIndex, 
StringBuilder builder) {
+        if (columnIndex > 0) {
+            builder.append(" AND ");
+        }
+
+        builder.append(columnName).append(' ');
+    }
+
+    protected void render(Context context, StringBuilder builder, 
ParameterBinding binding) {
+        if (binding.getValue() != null) {
+            context.addParameterBinding(binding);
+            builder.append("= ?");
+        } else {
+            builder.append("IS NULL");
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    protected String[] toArray(Object columns) {
+        if (columns instanceof Collection) {
+            String[] columnsAsStrings = new 
String[((Collection<Object>)columns).size()];
+            int idx = 0;
+            for(Object column : (Collection<Object>)columns) {
+                columnsAsStrings[idx++] = column.toString();
+            }
+            return columnsAsStrings;
+        } else if (columns.getClass().isArray()) {
+            String[] columnsAsStrings = new String[((Object[])columns).length];
+            int idx = 0;
+            for(Object column : (Object[])columns) {
+                columnsAsStrings[idx++] = column.toString();
+            }
+            return columnsAsStrings;
+        } else {
+            return new String[] { columns.toString() };
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    protected Map<String, Object> toIdMap(Object object) throws 
ParseErrorException {
+        if (object instanceof Persistent) {
+            return ((Persistent) object).getObjectId().getIdSnapshot();
+        } else if (object instanceof ObjectId) {
+            return ((ObjectId) object).getIdSnapshot();
+        } else if(object instanceof Map) {
+            return (Map<String, Object>) object;
+        } else if (object != null) {
+            throw new ParseErrorException(
+                    "Invalid object parameter, expected Persistent or ObjectId 
or null: " + object);
+        } else {
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectNotEqual.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectNotEqual.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectNotEqual.java
new file mode 100644
index 0000000..2e8879d
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectNotEqual.java
@@ -0,0 +1,51 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.template.directive;
+
+import org.apache.cayenne.access.translator.ParameterBinding;
+import org.apache.cayenne.template.Context;
+
+/**
+ * @since 4.1
+ */
+public class BindObjectNotEqual extends BindObjectEqual {
+
+    public static final BindObjectNotEqual INSTANCE = new BindObjectNotEqual();
+
+    @Override
+    protected void renderColumn(String columnName, int columnIndex, 
StringBuilder builder) {
+        if (columnIndex > 0) {
+            builder.append(" OR ");
+        }
+
+        builder.append(columnName).append(' ');
+    }
+
+    @Override
+    protected void render(Context context, StringBuilder builder, 
ParameterBinding binding) {
+        if (binding.getValue() != null) {
+            context.addParameterBinding(binding);
+            builder.append("<> ?");
+        } else {
+            builder.append("IS NOT NULL");
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Directive.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Directive.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Directive.java
new file mode 100644
index 0000000..be2d6c9
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Directive.java
@@ -0,0 +1,32 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.template.directive;
+
+import org.apache.cayenne.template.Context;
+import org.apache.cayenne.template.parser.ASTExpression;
+
+/**
+ * @since 4.1
+ */
+public interface Directive {
+
+    String apply(Context context, ASTExpression... expressions);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Result.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Result.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Result.java
new file mode 100644
index 0000000..dfcdd6f
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Result.java
@@ -0,0 +1,126 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.template.directive;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cayenne.access.jdbc.ColumnDescriptor;
+import org.apache.cayenne.template.Context;
+import org.apache.cayenne.template.parser.ASTExpression;
+import org.apache.cayenne.util.Util;
+
+/**
+ * @since 4.1
+ */
+public class Result implements Directive {
+
+    public static final Result INSTANCE = new Result();
+
+    private static final Map<String, String> typesGuess;
+
+    static {
+        // init default types
+        typesGuess = new HashMap<>();
+
+        // primitives
+        typesGuess.put("long", Long.class.getName());
+        typesGuess.put("double", Double.class.getName());
+        typesGuess.put("byte", Byte.class.getName());
+        typesGuess.put("boolean", Boolean.class.getName());
+        typesGuess.put("float", Float.class.getName());
+        typesGuess.put("short", Short.class.getName());
+        typesGuess.put("int", Integer.class.getName());
+
+        // numeric
+        typesGuess.put("Long", Long.class.getName());
+        typesGuess.put("Double", Double.class.getName());
+        typesGuess.put("Byte", Byte.class.getName());
+        typesGuess.put("Boolean", Boolean.class.getName());
+        typesGuess.put("Float", Float.class.getName());
+        typesGuess.put("Short", Short.class.getName());
+        typesGuess.put("Integer", Integer.class.getName());
+
+        // other
+        typesGuess.put("String", String.class.getName());
+        typesGuess.put("Date", Date.class.getName());
+        typesGuess.put("Time", Time.class.getName());
+        typesGuess.put("Timestamp", Timestamp.class.getName());
+        typesGuess.put("BigDecimal", BigDecimal.class.getName());
+        typesGuess.put("BigInteger", BigInteger.class.getName());
+    }
+
+    @Override
+    public String apply(Context context, ASTExpression... expressions) {
+
+        ColumnDescriptor columnDescriptor = new ColumnDescriptor();
+
+        String column = expressions[0].evaluate(context);
+        columnDescriptor.setName(column);
+
+        if (expressions.length > 1) {
+            String type = expressions[1].evaluate(context);
+            columnDescriptor.setJavaClass(guessType(type));
+        }
+
+        String alias = null;
+        if (expressions.length > 2) {
+            alias = expressions[2].evaluate(context);
+        }
+
+        String dataRowKey = null;
+        if (expressions.length > 3) {
+            dataRowKey = expressions[3].evaluate(context);
+        }
+
+        // determine what we want to name this column in a resulting DataRow...
+        String label = (!Util.isEmptyString(dataRowKey)) ? dataRowKey : 
(!Util.isEmptyString(alias)) ? alias : null;
+        columnDescriptor.setDataRowKey(label);
+
+        if (expressions.length > 4) {
+            int jdbcType = (int) expressions[4].evaluateAsLong(context);
+            columnDescriptor.setJdbcType(jdbcType);
+        }
+
+        context.addColumnDescriptor(columnDescriptor);
+
+        String result = column;
+        if (!Util.isEmptyString(alias) && !alias.equals(column)) {
+            result += " AS " + alias;
+        }
+
+        return result;
+    }
+
+    /**
+     * Converts "short" type notation to the fully qualified class name. Right
+     * now supports all major standard SQL types, including primitives. All
+     * other types are expected to be fully qualified, and are not converted.
+     */
+    protected String guessType(String type) {
+        String guessed = typesGuess.get(type);
+        return guessed != null ? guessed : type;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTArray.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTArray.java 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTArray.java
new file mode 100644
index 0000000..3aed8ba
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTArray.java
@@ -0,0 +1,50 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.template.parser;
+
+import java.util.Arrays;
+
+import org.apache.cayenne.template.Context;
+
+public class ASTArray extends ASTExpression {
+
+    public ASTArray(int id) {
+        super(id);
+    }
+
+    @Override
+    public String evaluate(Context context) {
+        return Arrays.toString(evaluateAsArray(context));
+    }
+
+    @Override
+    public Object evaluateAsObject(Context context) {
+        return evaluateAsArray(context);
+    }
+
+    protected Object[] evaluateAsArray(Context context) {
+        Object[] evaluated = new Object[jjtGetNumChildren()];
+        for(int i=0; i<jjtGetNumChildren(); i++) {
+            ExpressionNode node = (ExpressionNode)jjtGetChild(i);
+            evaluated[i] = node.evaluateAsObject(context);
+        }
+        return evaluated;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTDirective.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTDirective.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTDirective.java
index 67f4f3c..3522123 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTDirective.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTDirective.java
@@ -20,7 +20,7 @@
 package org.apache.cayenne.template.parser;
 
 import org.apache.cayenne.template.Context;
-import org.apache.cayenne.template.Directive;
+import org.apache.cayenne.template.directive.Directive;
 
 /**
  * @since 4.1
@@ -38,6 +38,11 @@ public class ASTDirective extends IdentifierNode {
             return "";
         }
 
-        return directive.apply(context, (ASTExpression[]) children);
+        ASTExpression[] expressions = new ASTExpression[children.length];
+        for(int i=0;  i<children.length; i++) {
+            expressions[i] = (ASTExpression)children[i];
+        }
+
+        return directive.apply(context, expressions);
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTExpression.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTExpression.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTExpression.java
index 0ade66b..7ff1858 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTExpression.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTExpression.java
@@ -41,7 +41,7 @@ public class ASTExpression extends SimpleNode implements 
ExpressionNode {
 
     @Override
     public Object evaluateAsObject(Context context) {
-        return getChildAsExpressionNode(0).evaluateAsLong(context);
+        return getChildAsExpressionNode(0).evaluateAsObject(context);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTMethod.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTMethod.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTMethod.java
index 50961c6..51e7358 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTMethod.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTMethod.java
@@ -64,14 +64,16 @@ public class ASTMethod extends IdentifierNode {
                     Object[] arguments = new Object[jjtGetNumChildren()];
                     for(Class<?> parameterType : m.getParameterTypes()) {
                         ASTExpression child = (ASTExpression)jjtGetChild(i);
-                        if(parameterType.isAssignableFrom(Double.class)) {
+                        if(parameterType.isAssignableFrom(String.class)) {
+                            arguments[i] = child.evaluate(context);
+                        } else 
if(parameterType.isAssignableFrom(Double.class)) {
                             arguments[i] = child.evaluateAsDouble(context);
                         } else if(parameterType.isAssignableFrom(Long.class)) {
                             arguments[i] = child.evaluateAsLong(context);
+                        } else 
if(parameterType.isAssignableFrom(Integer.class)) {
+                            arguments[i] = (int)child.evaluateAsLong(context);
                         } else 
if(parameterType.isAssignableFrom(Boolean.class)) {
                             arguments[i] = child.evaluateAsBoolean(context);
-                        } else 
if(parameterType.isAssignableFrom(String.class)) {
-                            arguments[i] = child.evaluate(context);
                         } else {
                             continue methodsLoop;
                         }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTVariable.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTVariable.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTVariable.java
index b72d59b..f45f1f8 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTVariable.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTVariable.java
@@ -32,6 +32,7 @@ public class ASTVariable extends IdentifierNode implements 
ExpressionNode {
         super(id);
     }
 
+    @Override
     public Object evaluateAsObject(Context context) {
         Object object = context.getObject(getIdentifier());
         if(object == null) {
@@ -68,6 +69,10 @@ public class ASTVariable extends IdentifierNode implements 
ExpressionNode {
 
     @Override
     public boolean evaluateAsBoolean(Context context) {
-        return (Boolean) Objects.requireNonNull(evaluateAsObject(context));
+        Object object = evaluateAsObject(context);
+        if(object instanceof Boolean) {
+            return (Boolean)object;
+        }
+        return object != null;
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JavaCharStream.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JavaCharStream.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JavaCharStream.java
index 535ed3b..d9e6eb5 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JavaCharStream.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JavaCharStream.java
@@ -103,14 +103,6 @@ public class JavaCharStream {
     protected int inBuf = 0;
     protected int tabSize = 8;
 
-    protected void setTabSize(int i) {
-        tabSize = i;
-    }
-
-    protected int getTabSize(int i) {
-        return tabSize;
-    }
-
     protected void ExpandBuff(boolean wrapAround) {
         char[] newbuffer = new char[bufsize + 2048];
         int newbufline[] = new int[bufsize + 2048];
@@ -450,7 +442,7 @@ public class JavaCharStream {
      */
     public JavaCharStream(java.io.InputStream dstream, int startline,
                           int startcolumn, int buffersize) {
-        this(new java.io.InputStreamReader(dstream), startline, startcolumn, 
4096);
+        this(new java.io.InputStreamReader(dstream), startline, startcolumn, 
buffersize);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ParseException.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ParseException.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ParseException.java
index a838ddd..6aeb7b6 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ParseException.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ParseException.java
@@ -1,3 +1,5 @@
+/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 5.0 
*/
+/* JavaCCOptions:KEEP_LINE_COL=null */
 /*****************************************************************
  *   Licensed to the Apache Software Foundation (ASF) under one
  *  or more contributor license agreements.  See the NOTICE file
@@ -24,169 +26,181 @@ package org.apache.cayenne.template.parser;
  * You can explicitly create objects of this exception type by
  * calling the method generateParseException in the generated
  * parser.
- * <p>
+ *
  * You can modify this class to customize your error reporting
  * mechanisms so long as you retain the public fields.
- *
- * @since 4.1
  */
 public class ParseException extends Exception {
 
-    /**
-     * The version identifier for this Serializable class.
-     * Increment only if the <i>serialized</i> form of the
-     * class changes.
-     */
-    private static final long serialVersionUID = 1L;
+  /**
+   * The version identifier for this Serializable class.
+   * Increment only if the <i>serialized</i> form of the
+   * class changes.
+   */
+  private static final long serialVersionUID = 1L;
 
-    /**
-     * This is the last token that has been consumed successfully.  If
-     * this object has been created due to a parse error, the token
-     * following this token will (therefore) be the first error token.
-     */
-    public Token currentToken;
+  /**
+   * This constructor is used by the method "generateParseException"
+   * in the generated parser.  Calling this constructor generates
+   * a new object of this type with the fields "currentToken",
+   * "expectedTokenSequences", and "tokenImage" set.
+   */
+  public ParseException(Token currentTokenVal,
+                        int[][] expectedTokenSequencesVal,
+                        String[] tokenImageVal
+                       )
+  {
+    super(initialise(currentTokenVal, expectedTokenSequencesVal, 
tokenImageVal));
+    currentToken = currentTokenVal;
+    expectedTokenSequences = expectedTokenSequencesVal;
+    tokenImage = tokenImageVal;
+  }
 
-    /**
-     * Each entry in this array is an array of integers.  Each array
-     * of integers represents a sequence of tokens (by their ordinal
-     * values) that is expected at this point of the parse.
-     */
-    public int[][] expectedTokenSequences;
+  /**
+   * The following constructors are for use by you for whatever
+   * purpose you can think of.  Constructing the exception in this
+   * manner makes the exception behave in the normal way - i.e., as
+   * documented in the class "Throwable".  The fields "errorToken",
+   * "expectedTokenSequences", and "tokenImage" do not contain
+   * relevant information.  The JavaCC generated code does not use
+   * these constructors.
+   */
 
-    /**
-     * This is a reference to the "tokenImage" array of the generated
-     * parser within which the parse error occurred.  This array is
-     * defined in the generated ...Constants interface.
-     */
-    public String[] tokenImage;
+  public ParseException() {
+    super();
+  }
 
-    /**
-     * This constructor is used by the method "generateParseException"
-     * in the generated parser.  Calling this constructor generates
-     * a new object of this type with the fields "currentToken",
-     * "expectedTokenSequences", and "tokenImage" set.
-     */
-    public ParseException(Token currentTokenVal, int[][] 
expectedTokenSequencesVal, String[] tokenImageVal) {
-        super(initialise(currentTokenVal, expectedTokenSequencesVal, 
tokenImageVal));
-        currentToken = currentTokenVal;
-        expectedTokenSequences = expectedTokenSequencesVal;
-        tokenImage = tokenImageVal;
-    }
+  /** Constructor with message. */
+  public ParseException(String message) {
+    super(message);
+  }
 
-    /**
-     * The following constructors are for use by you for whatever
-     * purpose you can think of.  Constructing the exception in this
-     * manner makes the exception behave in the normal way - i.e., as
-     * documented in the class "Throwable".  The fields "errorToken",
-     * "expectedTokenSequences", and "tokenImage" do not contain
-     * relevant information.  The JavaCC generated code does not use
-     * these constructors.
-     */
 
-    public ParseException() {
-        super();
-    }
+  /**
+   * This is the last token that has been consumed successfully.  If
+   * this object has been created due to a parse error, the token
+   * followng this token will (therefore) be the first error token.
+   */
+  public Token currentToken;
 
-    /**
-     * Constructor with message.
-     */
-    public ParseException(String message) {
-        super(message);
-    }
+  /**
+   * Each entry in this array is an array of integers.  Each array
+   * of integers represents a sequence of tokens (by their ordinal
+   * values) that is expected at this point of the parse.
+   */
+  public int[][] expectedTokenSequences;
 
-    /**
-     * It uses "currentToken" and "expectedTokenSequences" to generate a parse
-     * error message and returns it.  If this object has been created
-     * due to a parse error, and you do not catch it (it gets thrown
-     * from the parser) the correct error message
-     * gets displayed.
-     */
-    private static String initialise(Token currentToken, int[][] 
expectedTokenSequences, String[] tokenImage) {
-        String eol = System.getProperty("line.separator", "\n");
-        StringBuilder expected = new StringBuilder();
-        int maxSize = 0;
-        for (int[] expectedTokenSequence : expectedTokenSequences) {
-            if (maxSize < expectedTokenSequence.length) {
-                maxSize = expectedTokenSequence.length;
-            }
-            for (int anExpectedTokenSequence : expectedTokenSequence) {
-                expected.append(tokenImage[anExpectedTokenSequence]).append(' 
');
-            }
-            if (expectedTokenSequence[expectedTokenSequence.length - 1] != 0) {
-                expected.append("...");
-            }
-            expected.append(eol).append("    ");
-        }
-        StringBuilder retval = new StringBuilder("Encountered \"");
-        Token tok = currentToken.next;
-        for (int i = 0; i < maxSize; i++) {
-            if (i != 0) retval.append(" ");
-            if (tok.kind == 0) {
-                retval.append(tokenImage[0]);
-                break;
-            }
-            retval.append(" ").append(tokenImage[tok.kind]);
-            retval.append(" \"");
-            retval.append(add_escapes(tok.image));
-            retval.append(" \"");
-            tok = tok.next;
-        }
-        retval.append("\" at line 
").append(currentToken.next.beginLine).append(", column 
").append(currentToken.next.beginColumn);
-        retval.append(".").append(eol);
-        if (expectedTokenSequences.length == 1) {
-            retval.append("Was expecting:").append(eol).append("    ");
-        } else {
-            retval.append("Was expecting one of:").append(eol).append("    ");
-        }
-        retval.append(expected.toString());
-        return retval.toString();
+  /**
+   * This is a reference to the "tokenImage" array of the generated
+   * parser within which the parse error occurred.  This array is
+   * defined in the generated ...Constants interface.
+   */
+  public String[] tokenImage;
+
+  /**
+   * It uses "currentToken" and "expectedTokenSequences" to generate a parse
+   * error message and returns it.  If this object has been created
+   * due to a parse error, and you do not catch it (it gets thrown
+   * from the parser) the correct error message
+   * gets displayed.
+   */
+  private static String initialise(Token currentToken,
+                           int[][] expectedTokenSequences,
+                           String[] tokenImage) {
+    String eol = System.getProperty("line.separator", "\n");
+    StringBuffer expected = new StringBuffer();
+    int maxSize = 0;
+    for (int i = 0; i < expectedTokenSequences.length; i++) {
+      if (maxSize < expectedTokenSequences[i].length) {
+        maxSize = expectedTokenSequences[i].length;
+      }
+      for (int j = 0; j < expectedTokenSequences[i].length; j++) {
+        expected.append(tokenImage[expectedTokenSequences[i][j]]).append(' ');
+      }
+      if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 
0) {
+        expected.append("...");
+      }
+      expected.append(eol).append("    ");
+    }
+    String retval = "Encountered \"";
+    Token tok = currentToken.next;
+    for (int i = 0; i < maxSize; i++) {
+      if (i != 0) retval += " ";
+      if (tok.kind == 0) {
+        retval += tokenImage[0];
+        break;
+      }
+      retval += " " + tokenImage[tok.kind];
+      retval += " \"";
+      retval += add_escapes(tok.image);
+      retval += " \"";
+      tok = tok.next;
+    }
+    retval += "\" at line " + currentToken.next.beginLine + ", column " + 
currentToken.next.beginColumn;
+    retval += "." + eol;
+    if (expectedTokenSequences.length == 1) {
+      retval += "Was expecting:" + eol + "    ";
+    } else {
+      retval += "Was expecting one of:" + eol + "    ";
     }
+    retval += expected.toString();
+    return retval;
+  }
 
-    /**
-     * Used to convert raw characters to their escaped version
-     * when these raw version cannot be used as part of an ASCII
-     * string literal.
-     */
-    static String add_escapes(String str) {
-        StringBuilder retval = new StringBuilder();
-        char ch;
-        for (int i = 0; i < str.length(); i++) {
-            switch (str.charAt(i)) {
-                case 0:
-                    continue;
-                case '\b':
-                    retval.append("\\b");
-                    continue;
-                case '\t':
-                    retval.append("\\t");
-                    continue;
-                case '\n':
-                    retval.append("\\n");
-                    continue;
-                case '\f':
-                    retval.append("\\f");
-                    continue;
-                case '\r':
-                    retval.append("\\r");
-                    continue;
-                case '\"':
-                    retval.append("\\\"");
-                    continue;
-                case '\'':
-                    retval.append("\\\'");
-                    continue;
-                case '\\':
-                    retval.append("\\\\");
-                    continue;
-                default:
-                    if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
-                        String s = "0000" + Integer.toString(ch, 16);
-                        retval.append("\\u").append(s.substring(s.length() - 
4, s.length()));
-                    } else {
-                        retval.append(ch);
-                    }
-            }
+  /**
+   * The end of line string for this machine.
+   */
+  protected String eol = System.getProperty("line.separator", "\n");
+
+  /**
+   * Used to convert raw characters to their escaped version
+   * when these raw version cannot be used as part of an ASCII
+   * string literal.
+   */
+  static String add_escapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, 
s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
         }
-        return retval.toString();
-    }
+      }
+      return retval.toString();
+   }
+
 }
+/* JavaCC - OriginalChecksum=d5a90975d310c159e7a6a6335d8bf131 (do not edit 
this line) */

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParser.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParser.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParser.java
index 952dd06..a175ae9 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParser.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParser.java
@@ -28,13 +28,19 @@ package org.apache.cayenne.template.parser;
 public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeConstants, SQLTemplateParserConstants {/*@bgen(jjtree)*/
   protected JJTSQLTemplateParserState jjtree = new JJTSQLTemplateParserState();
 
-  final public ASTBlock template() throws ParseException {
+/*
+    Entry function in parser
+*/
+  final public Node template() throws ParseException {
     block();
     jj_consume_token(0);
         {if (true) return (ASTBlock) jjtree.rootNode();}
     throw new Error("Missing return statement in function");
   }
 
+/*
+    Top component of parsing tree
+*/
   final public void block() throws ParseException {
                        /*@bgen(jjtree) Block */
   ASTBlock jjtn000 = new ASTBlock(JJTBLOCK);
@@ -46,7 +52,9 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
         switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
         case IF:
         case SHARP:
+        case DOLLAR:
         case TEXT:
+        case TEXT_OTHER:
           ;
           break;
         default:
@@ -54,15 +62,19 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
           break label_1;
         }
         switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
-        case TEXT:
-          text();
-          break;
         case IF:
           ifElse();
           break;
         case SHARP:
           directive();
           break;
+        case DOLLAR:
+          variable();
+          break;
+        case TEXT:
+        case TEXT_OTHER:
+          text();
+          break;
         default:
           jj_la1[1] = jj_gen;
           jj_consume_token(-1);
@@ -90,16 +102,33 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
     }
   }
 
+/*
+    Plain text that is not processed in any way by render
+*/
   final public void text() throws ParseException {
                      /*@bgen(jjtree) Text */
     ASTText jjtn000 = new ASTText(JJTTEXT);
     boolean jjtc000 = true;
     jjtree.openNodeScope(jjtn000);Token t;
     try {
-      t = jj_consume_token(TEXT);
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case TEXT:
+        t = jj_consume_token(TEXT);
                  jjtree.closeNodeScope(jjtn000, true);
                  jjtc000 = false;
         jjtn000.setValue(t.image);
+        break;
+      case TEXT_OTHER:
+        t = jj_consume_token(TEXT_OTHER);
+                       jjtree.closeNodeScope(jjtn000, true);
+                       jjtc000 = false;
+        jjtn000.setValue(t.image);
+        break;
+      default:
+        jj_la1[2] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
     } finally {
       if (jjtc000) {
         jjtree.closeNodeScope(jjtn000, true);
@@ -107,6 +136,9 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
     }
   }
 
+/*
+    Condition directive: #if(condition) ...  #else ... #end
+*/
   final public void ifElse() throws ParseException {
                          /*@bgen(jjtree) IfElse */
   ASTIfElse jjtn000 = new ASTIfElse(JJTIFELSE);
@@ -124,7 +156,7 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
         block();
         break;
       default:
-        jj_la1[2] = jj_gen;
+        jj_la1[3] = jj_gen;
         ;
       }
       jj_consume_token(END);
@@ -149,6 +181,9 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
     }
   }
 
+/*
+    Directive in form of #directiveName(args list)
+*/
   final public void directive() throws ParseException {
                                /*@bgen(jjtree) Directive */
     ASTDirective jjtn000 = new ASTDirective(JJTDIRECTIVE);
@@ -160,9 +195,10 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
         jjtn000.setIdentifier(t.image);
       jj_consume_token(LBRACKET);
       switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case DOLLAR:
       case TRUE:
       case FALSE:
-      case DOLLAR:
+      case LSBRACKET:
       case SINGLE_QUOTED_STRING:
       case DOUBLE_QUOTED_STRING:
       case INT_LITERAL:
@@ -171,19 +207,34 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
         label_2:
         while (true) {
           switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case DOLLAR:
+          case TRUE:
+          case FALSE:
           case COMMA:
+          case LSBRACKET:
+          case SINGLE_QUOTED_STRING:
+          case DOUBLE_QUOTED_STRING:
+          case INT_LITERAL:
+          case FLOAT_LITERAL:
             ;
             break;
           default:
-            jj_la1[3] = jj_gen;
+            jj_la1[4] = jj_gen;
             break label_2;
           }
-          jj_consume_token(COMMA);
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case COMMA:
+            jj_consume_token(COMMA);
+            break;
+          default:
+            jj_la1[5] = jj_gen;
+            ;
+          }
           expression();
         }
         break;
       default:
-        jj_la1[4] = jj_gen;
+        jj_la1[6] = jj_gen;
         ;
       }
       jj_consume_token(RBRACKET);
@@ -208,6 +259,10 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
     }
   }
 
+/*
+    valid expression in parameters of method or directive
+    can be scalar, variable (with methods calls) or array
+*/
   final public void expression() throws ParseException {
                                  /*@bgen(jjtree) Expression */
   ASTExpression jjtn000 = new ASTExpression(JJTEXPRESSION);
@@ -226,8 +281,11 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
       case DOLLAR:
         variable();
         break;
+      case LSBRACKET:
+        array();
+        break;
       default:
-        jj_la1[5] = jj_gen;
+        jj_la1[7] = jj_gen;
         jj_consume_token(-1);
         throw new ParseException();
       }
@@ -252,6 +310,12 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
     }
   }
 
+/*
+    Single scalar value: String, long, double, boolean
+    String: single or double quoted
+    long: dec, hex and octo with sign
+    double: simple and exponential form
+*/
   final public void scalar() throws ParseException {
     switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
     case SINGLE_QUOTED_STRING:
@@ -345,12 +409,16 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
       }
       break;
     default:
-      jj_la1[6] = jj_gen;
+      jj_la1[8] = jj_gen;
       jj_consume_token(-1);
       throw new ParseException();
     }
   }
 
+/*
+    Variable, optionally with some methods calls
+    $a or $a.method() or $a.method1().method2()
+*/
   final public void variable() throws ParseException {
                              /*@bgen(jjtree) Variable */
     ASTVariable jjtn000 = new ASTVariable(JJTVARIABLE);
@@ -367,10 +435,9 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
           ;
           break;
         default:
-          jj_la1[7] = jj_gen;
+          jj_la1[9] = jj_gen;
           break label_3;
         }
-        jj_consume_token(DOT);
         method();
       }
     } catch (Throwable jjte000) {
@@ -394,19 +461,25 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
     }
   }
 
+/*
+    Method call, valid only as part of variable, can be chain of methods
+    $a.method1($var).method2().method3('val')
+*/
   final public void method() throws ParseException {
                          /*@bgen(jjtree) Method */
     ASTMethod jjtn000 = new ASTMethod(JJTMETHOD);
     boolean jjtc000 = true;
     jjtree.openNodeScope(jjtn000);Token t;
     try {
+      jj_consume_token(DOT);
       t = jj_consume_token(IDENTIFIER);
         jjtn000.setIdentifier(t.image);
       jj_consume_token(LBRACKET);
       switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case DOLLAR:
       case TRUE:
       case FALSE:
-      case DOLLAR:
+      case LSBRACKET:
       case SINGLE_QUOTED_STRING:
       case DOUBLE_QUOTED_STRING:
       case INT_LITERAL:
@@ -415,19 +488,34 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
         label_4:
         while (true) {
           switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case DOLLAR:
+          case TRUE:
+          case FALSE:
           case COMMA:
+          case LSBRACKET:
+          case SINGLE_QUOTED_STRING:
+          case DOUBLE_QUOTED_STRING:
+          case INT_LITERAL:
+          case FLOAT_LITERAL:
             ;
             break;
           default:
-            jj_la1[8] = jj_gen;
+            jj_la1[10] = jj_gen;
             break label_4;
           }
-          jj_consume_token(COMMA);
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case COMMA:
+            jj_consume_token(COMMA);
+            break;
+          default:
+            jj_la1[11] = jj_gen;
+            ;
+          }
           expression();
         }
         break;
       default:
-        jj_la1[9] = jj_gen;
+        jj_la1[12] = jj_gen;
         ;
       }
       jj_consume_token(RBRACKET);
@@ -452,6 +540,111 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
     }
   }
 
+/*
+    Comma or space separated array of scalars and/or variables
+    valid values: [], ['a' 5], [$a, 'b', 5]
+*/
+  final public void array() throws ParseException {
+                       /*@bgen(jjtree) Array */
+  ASTArray jjtn000 = new ASTArray(JJTARRAY);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+    try {
+      jj_consume_token(LSBRACKET);
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case DOLLAR:
+      case TRUE:
+      case FALSE:
+      case SINGLE_QUOTED_STRING:
+      case DOUBLE_QUOTED_STRING:
+      case INT_LITERAL:
+      case FLOAT_LITERAL:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case TRUE:
+        case FALSE:
+        case SINGLE_QUOTED_STRING:
+        case DOUBLE_QUOTED_STRING:
+        case INT_LITERAL:
+        case FLOAT_LITERAL:
+          scalar();
+          break;
+        case DOLLAR:
+          variable();
+          break;
+        default:
+          jj_la1[13] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case COMMA:
+          jj_consume_token(COMMA);
+          break;
+        default:
+          jj_la1[14] = jj_gen;
+          ;
+        }
+        label_5:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case DOLLAR:
+          case TRUE:
+          case FALSE:
+          case SINGLE_QUOTED_STRING:
+          case DOUBLE_QUOTED_STRING:
+          case INT_LITERAL:
+          case FLOAT_LITERAL:
+            ;
+            break;
+          default:
+            jj_la1[15] = jj_gen;
+            break label_5;
+          }
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case TRUE:
+          case FALSE:
+          case SINGLE_QUOTED_STRING:
+          case DOUBLE_QUOTED_STRING:
+          case INT_LITERAL:
+          case FLOAT_LITERAL:
+            scalar();
+            break;
+          case DOLLAR:
+            variable();
+            break;
+          default:
+            jj_la1[16] = jj_gen;
+            jj_consume_token(-1);
+            throw new ParseException();
+          }
+        }
+        break;
+      default:
+        jj_la1[17] = jj_gen;
+        ;
+      }
+      jj_consume_token(RSBRACKET);
+    } catch (Throwable jjte000) {
+      if (jjtc000) {
+        jjtree.clearNodeScope(jjtn000);
+        jjtc000 = false;
+      } else {
+        jjtree.popNode();
+      }
+      if (jjte000 instanceof RuntimeException) {
+        {if (true) throw (RuntimeException)jjte000;}
+      }
+      if (jjte000 instanceof ParseException) {
+        {if (true) throw (ParseException)jjte000;}
+      }
+      {if (true) throw (Error)jjte000;}
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+  }
+
   /** Generated Token Manager. */
   public SQLTemplateParserTokenManager token_source;
   JavaCharStream jj_input_stream;
@@ -461,7 +654,7 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
   public Token jj_nt;
   private int jj_ntk;
   private int jj_gen;
-  final private int[] jj_la1 = new int[10];
+  final private int[] jj_la1 = new int[18];
   static private int[] jj_la1_0;
   static private int[] jj_la1_1;
   static {
@@ -469,10 +662,10 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
       jj_la1_init_1();
    }
    private static void jj_la1_init_0() {
-      jj_la1_0 = new int[] 
{0x102,0x102,0x4,0x1000,0x39000230,0x39000230,0x39000030,0x2000,0x1000,0x39000230,};
+      jj_la1_0 = new int[] 
{0x320,0x320,0x0,0x40,0x90006e00,0x2000,0x90004e00,0x90004e00,0x90000c00,0x20000,0x90006e00,0x2000,0x90004e00,0x90000e00,0x2000,0x90000e00,0x90000e00,0x90000e00,};
    }
    private static void jj_la1_init_1() {
-      jj_la1_1 = new int[] {0x10,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,};
+      jj_la1_1 = new int[] 
{0xc0,0xc0,0xc0,0x0,0x3,0x0,0x3,0x3,0x3,0x0,0x3,0x0,0x3,0x3,0x0,0x3,0x3,0x3,};
    }
 
   /** Constructor with InputStream. */
@@ -486,7 +679,7 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
     token = new Token();
     jj_ntk = -1;
     jj_gen = 0;
-    for (int i = 0; i < 10; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 18; i++) jj_la1[i] = -1;
   }
 
   /** Reinitialise. */
@@ -501,7 +694,7 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
     jj_ntk = -1;
     jjtree.reset();
     jj_gen = 0;
-    for (int i = 0; i < 10; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 18; i++) jj_la1[i] = -1;
   }
 
   /** Constructor. */
@@ -511,7 +704,7 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
     token = new Token();
     jj_ntk = -1;
     jj_gen = 0;
-    for (int i = 0; i < 10; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 18; i++) jj_la1[i] = -1;
   }
 
   /** Reinitialise. */
@@ -522,7 +715,7 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
     jj_ntk = -1;
     jjtree.reset();
     jj_gen = 0;
-    for (int i = 0; i < 10; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 18; i++) jj_la1[i] = -1;
   }
 
   /** Constructor with generated Token Manager. */
@@ -531,7 +724,7 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
     token = new Token();
     jj_ntk = -1;
     jj_gen = 0;
-    for (int i = 0; i < 10; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 18; i++) jj_la1[i] = -1;
   }
 
   /** Reinitialise. */
@@ -541,7 +734,7 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
     jj_ntk = -1;
     jjtree.reset();
     jj_gen = 0;
-    for (int i = 0; i < 10; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 18; i++) jj_la1[i] = -1;
   }
 
   private Token jj_consume_token(int kind) throws ParseException {
@@ -592,12 +785,12 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
   /** Generate ParseException. */
   public ParseException generateParseException() {
     jj_expentries.clear();
-    boolean[] la1tokens = new boolean[37];
+    boolean[] la1tokens = new boolean[40];
     if (jj_kind >= 0) {
       la1tokens[jj_kind] = true;
       jj_kind = -1;
     }
-    for (int i = 0; i < 10; i++) {
+    for (int i = 0; i < 18; i++) {
       if (jj_la1[i] == jj_gen) {
         for (int j = 0; j < 32; j++) {
           if ((jj_la1_0[i] & (1<<j)) != 0) {
@@ -609,7 +802,7 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
         }
       }
     }
-    for (int i = 0; i < 37; i++) {
+    for (int i = 0; i < 40; i++) {
       if (la1tokens[i]) {
         jj_expentry = new int[1];
         jj_expentry[0] = i;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/040bfbce/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParserConstants.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParserConstants.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParserConstants.java
index 4d1337a..0572956 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParserConstants.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParserConstants.java
@@ -30,104 +30,110 @@ public interface SQLTemplateParserConstants {
   /** End of File. */
   int EOF = 0;
   /** RegularExpression Id. */
-  int IF = 1;
+  int IF = 5;
   /** RegularExpression Id. */
-  int ELSE = 2;
+  int ELSE = 6;
   /** RegularExpression Id. */
-  int END = 3;
+  int END = 7;
   /** RegularExpression Id. */
-  int TRUE = 4;
-  /** RegularExpression Id. */
-  int FALSE = 5;
+  int SHARP = 8;
   /** RegularExpression Id. */
-  int WHITESPACE = 6;
+  int DOLLAR = 9;
   /** RegularExpression Id. */
-  int NEWLINE = 7;
+  int TRUE = 10;
   /** RegularExpression Id. */
-  int SHARP = 8;
+  int FALSE = 11;
   /** RegularExpression Id. */
-  int DOLLAR = 9;
+  int RBRACKET = 12;
   /** RegularExpression Id. */
-  int LBRACKET = 10;
+  int COMMA = 13;
   /** RegularExpression Id. */
-  int RBRACKET = 11;
+  int LSBRACKET = 14;
   /** RegularExpression Id. */
-  int COMMA = 12;
+  int RSBRACKET = 15;
   /** RegularExpression Id. */
-  int DOT = 13;
+  int LBRACKET = 16;
   /** RegularExpression Id. */
-  int IDENTIFIER = 14;
+  int DOT = 17;
   /** RegularExpression Id. */
-  int LETTER = 15;
+  int IDENTIFIER = 18;
   /** RegularExpression Id. */
-  int DIGIT = 16;
+  int LETTER = 19;
   /** RegularExpression Id. */
-  int SINGLE_LINE_COMMENT_END = 18;
+  int DIGIT = 20;
   /** RegularExpression Id. */
-  int ESC = 22;
+  int SINGLE_LINE_COMMENT_END = 22;
   /** RegularExpression Id. */
-  int SINGLE_QUOTED_STRING = 24;
+  int ESC = 26;
   /** RegularExpression Id. */
-  int STRING_ESC = 25;
+  int SINGLE_QUOTED_STRING = 28;
   /** RegularExpression Id. */
-  int DOUBLE_QUOTED_STRING = 27;
+  int STRING_ESC = 29;
   /** RegularExpression Id. */
-  int INT_LITERAL = 28;
+  int DOUBLE_QUOTED_STRING = 31;
   /** RegularExpression Id. */
-  int FLOAT_LITERAL = 29;
+  int INT_LITERAL = 32;
   /** RegularExpression Id. */
-  int DEC_FLT = 30;
+  int FLOAT_LITERAL = 33;
   /** RegularExpression Id. */
-  int DEC_DIGITS = 31;
+  int DEC_FLT = 34;
   /** RegularExpression Id. */
-  int EXPONENT = 32;
+  int DEC_DIGITS = 35;
   /** RegularExpression Id. */
-  int FLT_SUFF = 33;
+  int EXPONENT = 36;
   /** RegularExpression Id. */
-  int DOUBLE_ESCAPE = 34;
+  int FLT_SUFF = 37;
   /** RegularExpression Id. */
-  int ESCAPE = 35;
+  int TEXT = 38;
   /** RegularExpression Id. */
-  int TEXT = 36;
+  int TEXT_OTHER = 39;
 
   /** Lexical state. */
   int DEFAULT = 0;
   /** Lexical state. */
-  int IN_SINGLE_LINE_COMMENT = 1;
+  int ARGS = 1;
+  /** Lexical state. */
+  int NOT_TEXT = 2;
   /** Lexical state. */
-  int WithinSingleQuoteLiteral = 2;
+  int IN_SINGLE_LINE_COMMENT = 3;
   /** Lexical state. */
-  int WithinDoubleQuoteLiteral = 3;
+  int WithinSingleQuoteLiteral = 4;
+  /** Lexical state. */
+  int WithinDoubleQuoteLiteral = 5;
 
   /** Literal token values. */
   String[] tokenImage = {
     "<EOF>",
+    "\" \"",
+    "\"\\t\"",
+    "\"\\n\"",
+    "\"\\r\"",
     "\"#if\"",
     "\"#else\"",
     "\"#end\"",
-    "<TRUE>",
-    "<FALSE>",
-    "<WHITESPACE>",
-    "<NEWLINE>",
     "\"#\"",
     "\"$\"",
-    "\"(\"",
+    "<TRUE>",
+    "<FALSE>",
     "\")\"",
     "\",\"",
+    "\"[\"",
+    "\"]\"",
+    "\"(\"",
     "\".\"",
     "<IDENTIFIER>",
     "<LETTER>",
     "<DIGIT>",
     "\"##\"",
     "<SINGLE_LINE_COMMENT_END>",
-    "<token of kind 19>",
+    "<token of kind 23>",
     "\"\\\'\"",
     "\"\\\"\"",
     "<ESC>",
-    "<token of kind 23>",
+    "<token of kind 27>",
     "\"\\\'\"",
     "<STRING_ESC>",
-    "<token of kind 26>",
+    "<token of kind 30>",
     "\"\\\"\"",
     "<INT_LITERAL>",
     "<FLOAT_LITERAL>",
@@ -135,9 +141,8 @@ public interface SQLTemplateParserConstants {
     "<DEC_DIGITS>",
     "<EXPONENT>",
     "<FLT_SUFF>",
-    "\"\\\\\\\\\"",
-    "\"\\\\\"",
     "<TEXT>",
+    "<TEXT_OTHER>",
   };
 
 }

Reply via email to