You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2017/08/16 15:37:03 UTC

[11/13] cayenne git commit: CAY-2345 Own template renderer as a replacement for Velocity - additional test and fixes

CAY-2345 Own template renderer as a replacement for Velocity
  - additional test and fixes


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

Branch: refs/heads/master
Commit: 38ca7283955f4d7c756c16c4290f09bd19bf904b
Parents: f734851
Author: Nikita Timofeev <st...@gmail.com>
Authored: Wed Aug 9 18:29:40 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Wed Aug 16 18:30:09 2017 +0300

----------------------------------------------------------------------
 .../org/apache/cayenne/template/Context.java    |  20 +-
 .../template/directive/BindNotEqual.java        |   2 +-
 .../cayenne/template/parser/ASTMethod.java      |  33 +--
 .../cayenne/template/parser/ASTVariable.java    |   9 +-
 .../CayenneSQLTemplateProcessorTest.java        | 204 +++++++++++++++++++
 .../template/parser/SQLTemplateParserTest.java  | 120 ++++++++++-
 6 files changed, 364 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/38ca7283/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 bb528d2..c15371c 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
@@ -57,14 +57,14 @@ public class Context {
     int counter;
 
     public Context() {
-        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());
+        addDirective(             "result", Result.INSTANCE);
+        addDirective(               "bind", Bind.INSTANCE);
+        addDirective(          "bindEqual", BindEqual.INSTANCE);
+        addDirective(       "bindNotEqual", BindNotEqual.INSTANCE);
+        addDirective(    "bindObjectEqual", BindObjectEqual.INSTANCE);
+        addDirective( "bindObjectNotEqual", BindObjectNotEqual.INSTANCE);
+
+        addParameter("helper", new SQLTemplateRenderingUtils());
     }
 
     public Context(boolean positionalMode) {
@@ -92,6 +92,10 @@ public class Context {
         return builder.toString();
     }
 
+    public boolean haveObject(String name) {
+        return objects.containsKey(name);
+    }
+
     public Object getObject(String name) {
         Object object = objects.get(name);
         if(object != null) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38ca7283/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
index e58200f..dda2900 100644
--- 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
@@ -27,7 +27,7 @@ import org.apache.cayenne.template.Context;
  */
 public class BindNotEqual extends Bind {
 
-    public static final BindEqual INSTANCE = new BindEqual();
+    public static final BindNotEqual INSTANCE = new BindNotEqual();
 
     @Override
     protected void processBinding(Context context, ParameterBinding binding) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38ca7283/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 c4b96a0..869d5f8 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
@@ -20,7 +20,6 @@
 package org.apache.cayenne.template.parser;
 
 import java.lang.reflect.Method;
-import java.util.Objects;
 
 import org.apache.cayenne.reflect.PropertyUtils;
 import org.apache.cayenne.template.Context;
@@ -58,17 +57,27 @@ public class ASTMethod extends IdentifierNode {
                     Object[] arguments = new Object[jjtGetNumChildren()];
                     for(Class<?> parameterType : m.getParameterTypes()) {
                         ASTExpression child = (ASTExpression)jjtGetChild(i);
-                        if(parameterType.isAssignableFrom(String.class)) {
-                            arguments[i] = child.evaluateAsString(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 {
+                        try {
+                            if (parameterType.isAssignableFrom(Object.class)) {
+                                arguments[i] = child.evaluateAsObject(context);
+                            } else if (parameterType.isAssignableFrom(String.class)) {
+                                arguments[i] = child.evaluateAsString(context);
+                            } else if (parameterType.isAssignableFrom(Boolean.class) || parameterType.isAssignableFrom(boolean.class)) {
+                                arguments[i] = child.evaluateAsBoolean(context);
+                            } else if (parameterType.isAssignableFrom(Double.class) || parameterType.isAssignableFrom(double.class)) {
+                                arguments[i] = child.evaluateAsDouble(context);
+                            } else if (parameterType.isAssignableFrom(Float.class) || parameterType.isAssignableFrom(float.class)) {
+                                arguments[i] = (float) child.evaluateAsDouble(context);
+                            } else if (parameterType.isAssignableFrom(Long.class) || parameterType.isAssignableFrom(long.class)) {
+                                arguments[i] = child.evaluateAsLong(context);
+                            } else if (parameterType.isAssignableFrom(Integer.class) || parameterType.isAssignableFrom(int.class)) {
+                                arguments[i] = (int) child.evaluateAsLong(context);
+                            } else if (parameterType.isAssignableFrom(Object[].class)) {
+                                arguments[i] = child.evaluateAsObject(context);
+                            } else {
+                                continue methodsLoop;
+                            }
+                        } catch (UnsupportedOperationException ignored) {
                             continue methodsLoop;
                         }
                         i++;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38ca7283/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 3d2f60b..f7f992a 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
@@ -35,7 +35,14 @@ public class ASTVariable extends IdentifierNode implements ExpressionNode {
     @Override
     public String evaluateAsString(Context context) {
         Object object = evaluateAsObject(context);
-        return object == null ? "" : object.toString();
+        if(object == null) {
+            if(!context.haveObject(getIdentifier())) {
+                return '$' + getIdentifier();
+            }
+            return "";
+        }
+
+        return object.toString();
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38ca7283/cayenne-server/src/test/java/org/apache/cayenne/template/CayenneSQLTemplateProcessorTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/template/CayenneSQLTemplateProcessorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/template/CayenneSQLTemplateProcessorTest.java
index cae3078..de148e0 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/template/CayenneSQLTemplateProcessorTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/template/CayenneSQLTemplateProcessorTest.java
@@ -19,6 +19,20 @@
 
 package org.apache.cayenne.template;
 
+import java.sql.Types;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.DataObject;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.access.jdbc.SQLStatement;
+import org.apache.cayenne.access.translator.ParameterBinding;
+import org.junit.Before;
+import org.junit.Test;
+
 import static org.junit.Assert.*;
 
 /**
@@ -26,6 +40,196 @@ import static org.junit.Assert.*;
  */
 public class CayenneSQLTemplateProcessorTest {
 
+    private CayenneSQLTemplateProcessor processor;
+
+    @Before
+    public void before() {
+        processor = new CayenneSQLTemplateProcessor();
+    }
+
+    @Test
+    public void testProcessTemplateUnchanged1() throws Exception {
+        String sqlTemplate = "SELECT * FROM ME";
+
+        SQLStatement compiled = processor.processTemplate(sqlTemplate, Collections.<String, Object> emptyMap());
+
+        assertEquals(sqlTemplate, compiled.getSql());
+        assertEquals(0, compiled.getBindings().length);
+    }
+
+    @Test
+    public void testProcessTemplateUnchanged2() throws Exception {
+        String sqlTemplate = "SELECT a.b as XYZ FROM $SYSTEM_TABLE";
+
+        SQLStatement compiled = processor.processTemplate(sqlTemplate, Collections.<String, Object> emptyMap());
+
+        assertEquals(sqlTemplate, compiled.getSql());
+        assertEquals(0, compiled.getBindings().length);
+    }
+
+    @Test
+    public void testProcessTemplateSimpleDynamicContent() throws Exception {
+        String sqlTemplate = "SELECT * FROM ME WHERE $a";
+
+        Map<String, Object> map = Collections.<String, Object> singletonMap("a", "VALUE_OF_A");
+        SQLStatement compiled = processor.processTemplate(sqlTemplate, map);
+
+        assertEquals("SELECT * FROM ME WHERE VALUE_OF_A", compiled.getSql());
+
+        // bindings are not populated, since no "bind" macro is used.
+        assertEquals(0, compiled.getBindings().length);
+    }
+
+    @Test
+    public void testProcessTemplateBind() throws Exception {
+        String sqlTemplate = "SELECT * FROM ME WHERE "
+                + "COLUMN1 = #bind($a 'VARCHAR') AND COLUMN2 = #bind($b 'INTEGER')";
+        Map<String, Object> map = Collections.<String, Object> singletonMap("a", "VALUE_OF_A");
+        SQLStatement compiled = processor.processTemplate(sqlTemplate, map);
+
+        assertEquals("SELECT * FROM ME WHERE COLUMN1 = ? AND COLUMN2 = ?", compiled.getSql());
+        assertEquals(2, compiled.getBindings().length);
+        assertBindingValue("VALUE_OF_A", compiled.getBindings()[0]);
+        assertBindingValue(null, compiled.getBindings()[1]);
+    }
+
+    @Test
+    public void testProcessTemplateBindGuessVarchar() throws Exception {
+        String sqlTemplate = "SELECT * FROM ME WHERE COLUMN1 = #bind($a)";
+        Map<String, Object> map = Collections.<String, Object> singletonMap("a", "VALUE_OF_A");
+
+        SQLStatement compiled = processor.processTemplate(sqlTemplate, map);
+
+        assertEquals(1, compiled.getBindings().length);
+        assertBindingType(Types.VARCHAR, compiled.getBindings()[0]);
+    }
+
+    @Test
+    public void testProcessTemplateBindGuessInteger() throws Exception {
+        String sqlTemplate = "SELECT * FROM ME WHERE COLUMN1 = #bind($a)";
+        Map<String, Object> map = Collections.<String, Object> singletonMap("a", 4);
+
+        SQLStatement compiled = processor.processTemplate(sqlTemplate, map);
+
+        assertEquals(1, compiled.getBindings().length);
+        assertBindingType(Types.INTEGER, compiled.getBindings()[0]);
+    }
+
+    @Test
+    public void testProcessTemplateBindEqual() throws Exception {
+        String sqlTemplate = "SELECT * FROM ME WHERE COLUMN #bindEqual($a 'VARCHAR')";
+
+        SQLStatement compiled = processor.processTemplate(sqlTemplate, Collections.<String, Object> emptyMap());
+
+        assertEquals("SELECT * FROM ME WHERE COLUMN IS NULL", compiled.getSql());
+        assertEquals(0, compiled.getBindings().length);
+
+        Map<String, Object> map = Collections.<String, Object> singletonMap("a", "VALUE_OF_A");
+
+        compiled = processor.processTemplate(sqlTemplate, map);
+
+        assertEquals("SELECT * FROM ME WHERE COLUMN = ?", compiled.getSql());
+        assertEquals(1, compiled.getBindings().length);
+        assertBindingValue("VALUE_OF_A", compiled.getBindings()[0]);
+    }
+
+    @Test
+    public void testProcessTemplateBindNotEqual() throws Exception {
+        String sqlTemplate = "SELECT * FROM ME WHERE COLUMN #bindNotEqual($a 'VARCHAR')";
+
+        SQLStatement compiled = processor.processTemplate(sqlTemplate, Collections.<String, Object> emptyMap());
+
+        assertEquals("SELECT * FROM ME WHERE COLUMN IS NOT NULL", compiled.getSql());
+        assertEquals(0, compiled.getBindings().length);
+
+        Map<String, Object> map = Collections.<String, Object> singletonMap("a", "VALUE_OF_A");
+
+        compiled = processor.processTemplate(sqlTemplate, map);
+
+        assertEquals("SELECT * FROM ME WHERE COLUMN <> ?", compiled.getSql());
+        assertEquals(1, compiled.getBindings().length);
+        assertBindingValue("VALUE_OF_A", compiled.getBindings()[0]);
+    }
+
+    @Test
+    public void testProcessTemplateID() throws Exception {
+        String sqlTemplate = "SELECT * FROM ME WHERE COLUMN1 = #bind($helper.cayenneExp($a, 'db:ID_COLUMN'))";
+
+        DataObject dataObject = new CayenneDataObject();
+        dataObject.setObjectId(new ObjectId("T", "ID_COLUMN", 5));
+
+        Map<String, Object> map = Collections.<String, Object> singletonMap("a", dataObject);
+
+        SQLStatement compiled = processor.processTemplate(sqlTemplate, map);
+
+        assertEquals("SELECT * FROM ME WHERE COLUMN1 = ?", compiled.getSql());
+        assertEquals(1, compiled.getBindings().length);
+        assertBindingValue(5, compiled.getBindings()[0]);
+    }
+
+    @Test
+    public void testProcessTemplateNotEqualID() throws Exception {
+        String sqlTemplate = "SELECT * FROM ME WHERE "
+                + "COLUMN1 #bindNotEqual($helper.cayenneExp($a, 'db:ID_COLUMN1')) "
+                + "AND COLUMN2 #bindNotEqual($helper.cayenneExp($a, 'db:ID_COLUMN2'))";
+
+        Map<String, Object> idMap = new HashMap<>();
+        idMap.put("ID_COLUMN1", 3);
+        idMap.put("ID_COLUMN2", "aaa");
+        ObjectId id = new ObjectId("T", idMap);
+        DataObject dataObject = new CayenneDataObject();
+        dataObject.setObjectId(id);
+
+        Map<String, Object> map = Collections.<String, Object> singletonMap("a", dataObject);
+
+        SQLStatement compiled = processor.processTemplate(sqlTemplate, map);
+
+        assertEquals("SELECT * FROM ME WHERE COLUMN1 <> ? AND COLUMN2 <> ?", compiled.getSql());
+        assertEquals(2, compiled.getBindings().length);
+        assertBindingValue(3, compiled.getBindings()[0]);
+        assertBindingValue("aaa", compiled.getBindings()[1]);
+    }
+
+    @Test
+    public void testProcessTemplateConditions() throws Exception {
+        String sqlTemplate = "SELECT * FROM ME #if($a) WHERE COLUMN1 > #bind($a)#end";
+
+        Map<String, Object> map = Collections.<String, Object> singletonMap("a", "VALUE_OF_A");
+
+        SQLStatement compiled = processor.processTemplate(sqlTemplate, map);
+
+        assertEquals("SELECT * FROM ME  WHERE COLUMN1 > ?", compiled.getSql());
+        assertEquals(1, compiled.getBindings().length);
+        assertBindingValue("VALUE_OF_A", compiled.getBindings()[0]);
+
+        compiled = processor.processTemplate(sqlTemplate, Collections.<String, Object> emptyMap());
+
+        assertEquals("SELECT * FROM ME ", compiled.getSql());
+        assertEquals(0, compiled.getBindings().length);
+    }
+
+    @Test
+    public void testProcessTemplateBindCollection() throws Exception {
+        String sqlTemplate = "SELECT * FROM ME WHERE COLUMN IN (#bind($list 'VARCHAR'))";
+
+        Map<String, Object> map = Collections.<String, Object> singletonMap("list", Arrays.asList("a", "b", "c"));
+        SQLStatement compiled = processor.processTemplate(sqlTemplate, map);
+
+        assertEquals("SELECT * FROM ME WHERE COLUMN IN (?,?,?)", compiled.getSql());
+        assertEquals(3, compiled.getBindings().length);
+        assertBindingValue("a", compiled.getBindings()[0]);
+        assertBindingValue("b", compiled.getBindings()[1]);
+        assertBindingValue("c", compiled.getBindings()[2]);
+    }
+
+    private void assertBindingValue(Object expectedValue, Object binding) {
+        assertTrue("Not a binding!", binding instanceof ParameterBinding);
+        assertEquals(expectedValue, ((ParameterBinding) binding).getValue());
+    }
 
+    private void assertBindingType(Integer expectedType, Object binding) {
+        assertTrue("Not a binding!", binding instanceof ParameterBinding);
+        assertEquals(expectedType, ((ParameterBinding) binding).getJdbcType());
+    }
 
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38ca7283/cayenne-server/src/test/java/org/apache/cayenne/template/parser/SQLTemplateParserTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/template/parser/SQLTemplateParserTest.java b/cayenne-server/src/test/java/org/apache/cayenne/template/parser/SQLTemplateParserTest.java
index 6d26b20..22d91a0 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/template/parser/SQLTemplateParserTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/template/parser/SQLTemplateParserTest.java
@@ -20,9 +20,9 @@
 package org.apache.cayenne.template.parser;
 
 import java.io.ByteArrayInputStream;
-import java.io.StringReader;
 
 import org.apache.cayenne.template.Context;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
@@ -33,7 +33,7 @@ import static org.junit.Assert.*;
 public class SQLTemplateParserTest {
 
     @Test
-    public void testSimpleParse() throws Exception {
+    public void testUnchangedParse() throws Exception {
         Context context = new Context();
         String template = "SELECT * FROM a";
 
@@ -145,6 +145,87 @@ public class SQLTemplateParserTest {
     }
 
     @Test
+    public void testHelperObject() throws Exception {
+        String tpl = "($helper.cayenneExp($a, 'field'))";
+        Context context = new Context();
+        context.addParameter("a", new TestBean(5));
+
+        String sql = parseString(tpl, context);
+        assertEquals("(5)", sql);
+    }
+
+    @Test
+    public void testMethodCallArray() throws Exception {
+        String tpl = "$a.arrayMethod(['1' '2' '3'])";
+        Context context = new Context();
+        context.addParameter("a", new TestBean(5));
+
+        String sql = parseString(tpl, context);
+        assertEquals("array_3", sql);
+    }
+
+    @Test
+    public void testMethodCallInt() throws Exception {
+        String tpl = "$a.intMethod(42)";
+        Context context = new Context();
+        context.addParameter("a", new TestBean(5));
+
+        String sql = parseString(tpl, context);
+        assertEquals("int_42", sql);
+    }
+
+    @Test
+    public void testMethodCallString() throws Exception {
+        String tpl = "$a.stringMethod(\"abc\")";
+        Context context = new Context();
+        context.addParameter("a", new TestBean(5));
+
+        String sql = parseString(tpl, context);
+        assertEquals("string_abc", sql);
+    }
+
+    @Test
+    public void testMethodCallFloat() throws Exception {
+        String tpl = "$a.floatMethod(3.14)";
+        Context context = new Context();
+        context.addParameter("a", new TestBean(5));
+
+        String sql = parseString(tpl, context);
+        assertEquals("float_3.14", sql);
+    }
+
+    @Test
+    @Ignore("Method overload not properly supported, this test can return m2_true")
+    public void testMethodCallSelectByArgType1() throws Exception {
+        String tpl = "$a.method(123)";
+        Context context = new Context();
+        context.addParameter("a", new TestBean(5));
+
+        String sql = parseString(tpl, context);
+        assertEquals("m1_123", sql);
+    }
+
+    @Test
+    public void testMethodCallSelectByArgType2() throws Exception {
+        String tpl = "$a.method(true)";
+        Context context = new Context();
+        context.addParameter("a", new TestBean(5));
+
+        String sql = parseString(tpl, context);
+        assertEquals("m2_true", sql);
+    }
+
+    @Test
+    public void testPropertyAccess() throws Exception {
+        String tpl = "$a.field()";
+        Context context = new Context();
+        context.addParameter("a", new TestBean(5));
+
+        String sql = parseString(tpl, context);
+        assertEquals("5", sql);
+    }
+
+    @Test
     public void testNestedBrackets() throws Exception {
         String tpl = "(#bind('A' 'b'))";
         String sql = parseString(tpl, new Context());
@@ -180,4 +261,39 @@ public class SQLTemplateParserTest {
         return context.buildTemplate();
     }
 
+    static public class TestBean {
+        private int field;
+        TestBean(int field) {
+            this.field = field;
+        }
+
+        public int getField() {
+            return field;
+        }
+
+        public String arrayMethod(Object[] array) {
+            return "array_" + array.length;
+        }
+
+        public String stringMethod(String string) {
+            return "string_" + string;
+        }
+
+        public String intMethod(int i) {
+            return "int_" + i;
+        }
+
+        public String floatMethod(float f) {
+            return "float_" + f;
+        }
+
+        public String method(int i) {
+            return "m1_" + i;
+        }
+
+        public String method(boolean b) {
+            return "m2_" + b;
+        }
+    }
+
 }
\ No newline at end of file