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/12/13 14:39:14 UTC

cayenne git commit: CAY-2389 DbEntity qualifier with DbPath expression translates into wrong SQL

Repository: cayenne
Updated Branches:
  refs/heads/master 38f37d79a -> 41dd56c5a


CAY-2389 DbEntity qualifier with DbPath expression translates into wrong SQL


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

Branch: refs/heads/master
Commit: 41dd56c5af47c9d9afb8e259727dfaf4dd9a6cca
Parents: 38f37d7
Author: Nikita Timofeev <st...@gmail.com>
Authored: Wed Dec 13 17:12:01 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Wed Dec 13 17:12:01 2017 +0300

----------------------------------------------------------------------
 .../access/translator/select/JoinStack.java     |   7 +-
 .../apache/cayenne/CDOQualifiedEntitiesIT.java  |  81 +++++++++++---
 .../cayenne/testdo/qualified/Qualified3.java    |   9 ++
 .../cayenne/testdo/qualified/Qualified4.java    |   9 ++
 .../testdo/qualified/auto/_Qualified3.java      | 110 +++++++++++++++++++
 .../testdo/qualified/auto/_Qualified4.java      | 104 ++++++++++++++++++
 .../src/test/resources/cayenne-qualified.xml    |   2 +
 .../src/test/resources/qualified.map.xml        |  27 +++++
 docs/doc/src/main/resources/RELEASE-NOTES.txt   |   1 +
 9 files changed, 333 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/41dd56c5/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/JoinStack.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/JoinStack.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/JoinStack.java
index 937b818..11b57fc 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/JoinStack.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/JoinStack.java
@@ -25,8 +25,7 @@ import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.parser.ASTDbPath;
-import org.apache.cayenne.exp.parser.ASTObjPath;
-import org.apache.cayenne.exp.parser.SimpleNode;
+import org.apache.cayenne.exp.parser.ASTPath;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
@@ -211,8 +210,8 @@ public class JoinStack {
 		}
 
 		public Object apply(Object input) {
-			if (input instanceof ASTObjPath) {
-				return new ASTDbPath(pathToRoot.toString() + ((SimpleNode) input).getOperand(0));
+			if (input instanceof ASTPath) {
+				return new ASTDbPath(pathToRoot.toString() + ((ASTPath) input).getPath());
 			}
 			return input;
 		}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/41dd56c5/cayenne-server/src/test/java/org/apache/cayenne/CDOQualifiedEntitiesIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/CDOQualifiedEntitiesIT.java b/cayenne-server/src/test/java/org/apache/cayenne/CDOQualifiedEntitiesIT.java
index 729a1d8..be5f10d 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/CDOQualifiedEntitiesIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/CDOQualifiedEntitiesIT.java
@@ -19,11 +19,17 @@
 package org.apache.cayenne;
 
 import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.query.ObjectSelect;
 import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.test.jdbc.DBHelper;
 import org.apache.cayenne.test.jdbc.TableHelper;
 import org.apache.cayenne.testdo.qualified.Qualified1;
 import org.apache.cayenne.testdo.qualified.Qualified2;
+import org.apache.cayenne.testdo.qualified.Qualified3;
+import org.apache.cayenne.testdo.qualified.Qualified4;
 import org.apache.cayenne.unit.UnitDbAdapter;
 import org.apache.cayenne.unit.di.server.CayenneProjects;
 import org.apache.cayenne.unit.di.server.ServerCase;
@@ -51,23 +57,28 @@ public class CDOQualifiedEntitiesIT extends ServerCase {
 
     private TableHelper tQualified1;
     private TableHelper tQualified2;
+    private TableHelper tQualified3;
+    private TableHelper tQualified4;
 
     @Before
     public void setUp() throws Exception {
         int bool = accessStackAdapter.supportsBoolean() ? Types.BOOLEAN : Types.INTEGER;
 
-        tQualified1 = new TableHelper(dbHelper, "TEST_QUALIFIED1");
-        tQualified1.setColumns("ID", "NAME", "DELETED").setColumnTypes(
-                Types.INTEGER,
-                Types.VARCHAR,
-                bool);
-
-        tQualified2 = new TableHelper(dbHelper, "TEST_QUALIFIED2");
-        tQualified2.setColumns("ID", "NAME", "DELETED", "QUALIFIED1_ID").setColumnTypes(
-                Types.INTEGER,
-                Types.VARCHAR,
-                bool,
-                Types.INTEGER);
+        tQualified1 = new TableHelper(dbHelper, "TEST_QUALIFIED1")
+                .setColumns("ID", "NAME", "DELETED")
+                .setColumnTypes(Types.INTEGER, Types.VARCHAR, bool);
+
+        tQualified2 = new TableHelper(dbHelper, "TEST_QUALIFIED2")
+                .setColumns("ID", "NAME", "DELETED", "QUALIFIED1_ID")
+                .setColumnTypes(Types.INTEGER, Types.VARCHAR, bool, Types.INTEGER);
+
+        tQualified3 = new TableHelper(dbHelper, "TEST_QUALIFIED3")
+                .setColumns("ID", "NAME", "DELETED")
+                .setColumnTypes(Types.INTEGER, Types.VARCHAR, bool);
+
+        tQualified4 = new TableHelper(dbHelper, "TEST_QUALIFIED4")
+                .setColumns("ID", "NAME", "DELETED", "QUALIFIED3_ID")
+                .setColumnTypes(Types.INTEGER, Types.VARCHAR, bool, Types.INTEGER);
     }
 
     private void createReadToManyDataSet() throws Exception {
@@ -88,6 +99,14 @@ public class CDOQualifiedEntitiesIT extends ServerCase {
         tQualified2.insert(1, "OY1", null, 2);
     }
 
+    private void createJoinDataSet() throws Exception {
+        tQualified3.insert(1, "O1", null);
+        tQualified3.insert(2, "O2", accessStackAdapter.supportsBoolean() ? true : 1);
+
+        tQualified4.insert(1, "SHOULD_SELECT", null, 1);
+        tQualified4.insert(2, "SHOULD_NOT_SELECT", null, 2);
+    }
+
     @Test
     public void testReadToMany() throws Exception {
         if (accessStackAdapter.supportsNullBoolean()) {
@@ -125,7 +144,43 @@ public class CDOQualifiedEntitiesIT extends ServerCase {
             assertEquals("OY1", root.getName());
 
             Qualified1 target = root.getQualified1();
-            assertNull("" + target, target);
+            assertNull(target);
+        }
+    }
+
+    @Test
+    public void joinWithQualifier() throws Exception {
+        createJoinDataSet();
+
+        List<Qualified4> result = ObjectSelect.query(Qualified4.class)
+                .where(Qualified4.QUALIFIED3.dot(Qualified3.NAME).like("O%"))
+                .select(context);
+
+        assertEquals(1, result.size());
+        assertEquals("SHOULD_SELECT", result.get(0).getName());
+    }
+
+    @Test
+    public void joinWithCustomDbQualifier() throws Exception {
+        createJoinDataSet();
+
+        DbEntity entity1 = context.getEntityResolver().getDbEntity("TEST_QUALIFIED3");
+        DbEntity entity2 = context.getEntityResolver().getDbEntity("TEST_QUALIFIED4");
+        Expression oldExpression1 = entity1.getQualifier();
+        Expression oldExpression2 = entity2.getQualifier();
+        try {
+            entity1.setQualifier(ExpressionFactory.matchDbExp("DELETED", null));
+            entity2.setQualifier(ExpressionFactory.matchDbExp("DELETED", null));
+
+            List<Qualified4> result = ObjectSelect.query(Qualified4.class)
+                    .where(Qualified4.QUALIFIED3.dot(Qualified3.NAME).like("O%"))
+                    .select(context);
+
+            assertEquals(1, result.size());
+            assertEquals("SHOULD_SELECT", result.get(0).getName());
+        } finally {
+            entity1.setQualifier(oldExpression1);
+            entity2.setQualifier(oldExpression2);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/41dd56c5/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/Qualified3.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/Qualified3.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/Qualified3.java
new file mode 100644
index 0000000..203481d
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/Qualified3.java
@@ -0,0 +1,9 @@
+package org.apache.cayenne.testdo.qualified;
+
+import org.apache.cayenne.testdo.qualified.auto._Qualified3;
+
+public class Qualified3 extends _Qualified3 {
+
+    private static final long serialVersionUID = 1L; 
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/41dd56c5/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/Qualified4.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/Qualified4.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/Qualified4.java
new file mode 100644
index 0000000..a510a8c
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/Qualified4.java
@@ -0,0 +1,9 @@
+package org.apache.cayenne.testdo.qualified;
+
+import org.apache.cayenne.testdo.qualified.auto._Qualified4;
+
+public class Qualified4 extends _Qualified4 {
+
+    private static final long serialVersionUID = 1L; 
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/41dd56c5/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/auto/_Qualified3.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/auto/_Qualified3.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/auto/_Qualified3.java
new file mode 100644
index 0000000..4a91a9f
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/auto/_Qualified3.java
@@ -0,0 +1,110 @@
+package org.apache.cayenne.testdo.qualified.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+
+import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.testdo.qualified.Qualified4;
+
+/**
+ * Class _Qualified3 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Qualified3 extends BaseDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String ID_PK_COLUMN = "ID";
+
+    public static final Property<String> NAME = Property.create("name", String.class);
+    public static final Property<List<Qualified4>> QUALIFIED4S = Property.create("qualified4s", List.class);
+
+    protected String name;
+
+    protected Object qualified4s;
+
+    public void setName(String name) {
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
+    }
+
+    public String getName() {
+        beforePropertyRead("name");
+        return this.name;
+    }
+
+    public void addToQualified4s(Qualified4 obj) {
+        addToManyTarget("qualified4s", obj, true);
+    }
+
+    public void removeFromQualified4s(Qualified4 obj) {
+        removeToManyTarget("qualified4s", obj, true);
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<Qualified4> getQualified4s() {
+        return (List<Qualified4>)readProperty("qualified4s");
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "name":
+                return this.name;
+            case "qualified4s":
+                return this.qualified4s;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "name":
+                this.name = (String)val;
+                break;
+            case "qualified4s":
+                this.qualified4s = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(this.name);
+        out.writeObject(this.qualified4s);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        this.name = (String)in.readObject();
+        this.qualified4s = in.readObject();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/41dd56c5/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/auto/_Qualified4.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/auto/_Qualified4.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/auto/_Qualified4.java
new file mode 100644
index 0000000..1c42281
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/qualified/auto/_Qualified4.java
@@ -0,0 +1,104 @@
+package org.apache.cayenne.testdo.qualified.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.testdo.qualified.Qualified3;
+
+/**
+ * Class _Qualified4 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Qualified4 extends BaseDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String ID_PK_COLUMN = "ID";
+
+    public static final Property<String> NAME = Property.create("name", String.class);
+    public static final Property<Qualified3> QUALIFIED3 = Property.create("qualified3", Qualified3.class);
+
+    protected String name;
+
+    protected Object qualified3;
+
+    public void setName(String name) {
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
+    }
+
+    public String getName() {
+        beforePropertyRead("name");
+        return this.name;
+    }
+
+    public void setQualified3(Qualified3 qualified3) {
+        setToOneTarget("qualified3", qualified3, true);
+    }
+
+    public Qualified3 getQualified3() {
+        return (Qualified3)readProperty("qualified3");
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "name":
+                return this.name;
+            case "qualified3":
+                return this.qualified3;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "name":
+                this.name = (String)val;
+                break;
+            case "qualified3":
+                this.qualified3 = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(this.name);
+        out.writeObject(this.qualified3);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        this.name = (String)in.readObject();
+        this.qualified3 = in.readObject();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/41dd56c5/cayenne-server/src/test/resources/cayenne-qualified.xml
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/resources/cayenne-qualified.xml b/cayenne-server/src/test/resources/cayenne-qualified.xml
index 2f67cd9..37805ab 100644
--- a/cayenne-server/src/test/resources/cayenne-qualified.xml
+++ b/cayenne-server/src/test/resources/cayenne-qualified.xml
@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <domain xmlns="http://cayenne.apache.org/schema/10/domain"
+	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	 xsi:schemaLocation="http://cayenne.apache.org/schema/10/domain http://cayenne.apache.org/schema/10/domain.xsd"
 	 project-version="10">
 	<map name="qualified"/>
 </domain>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/41dd56c5/cayenne-server/src/test/resources/qualified.map.xml
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/resources/qualified.map.xml b/cayenne-server/src/test/resources/qualified.map.xml
index fb854cd..d75eb83 100644
--- a/cayenne-server/src/test/resources/qualified.map.xml
+++ b/cayenne-server/src/test/resources/qualified.map.xml
@@ -16,6 +16,19 @@
 		<db-attribute name="NAME" type="VARCHAR" length="200"/>
 		<db-attribute name="QUALIFIED1_ID" type="INTEGER"/>
 	</db-entity>
+	<db-entity name="TEST_QUALIFIED3">
+		<db-attribute name="DELETED" type="BOOLEAN"/>
+		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+		<db-attribute name="NAME" type="VARCHAR" length="200"/>
+		<qualifier><![CDATA[DELETED = null]]></qualifier>
+	</db-entity>
+	<db-entity name="TEST_QUALIFIED4">
+		<db-attribute name="DELETED" type="BOOLEAN"/>
+		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+		<db-attribute name="NAME" type="VARCHAR" length="200"/>
+		<db-attribute name="QUALIFIED3_ID" type="INTEGER"/>
+		<qualifier><![CDATA[DELETED = null]]></qualifier>
+	</db-entity>
 	<obj-entity name="Qualified1" className="org.apache.cayenne.testdo.qualified.Qualified1" dbEntityName="TEST_QUALIFIED1">
 		<qualifier><![CDATA[deleted = null]]></qualifier>
 		<obj-attribute name="deleted" type="java.lang.Boolean" db-attribute-path="DELETED"/>
@@ -26,12 +39,26 @@
 		<obj-attribute name="deleted" type="java.lang.Boolean" db-attribute-path="DELETED"/>
 		<obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
 	</obj-entity>
+	<obj-entity name="Qualified3" className="org.apache.cayenne.testdo.qualified.Qualified3" dbEntityName="TEST_QUALIFIED3">
+		<obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
+	</obj-entity>
+	<obj-entity name="Qualified4" className="org.apache.cayenne.testdo.qualified.Qualified4" dbEntityName="TEST_QUALIFIED4">
+		<obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
+	</obj-entity>
 	<db-relationship name="qualified2s" source="TEST_QUALIFIED1" target="TEST_QUALIFIED2" toMany="true">
 		<db-attribute-pair source="ID" target="QUALIFIED1_ID"/>
 	</db-relationship>
 	<db-relationship name="qualified1" source="TEST_QUALIFIED2" target="TEST_QUALIFIED1">
 		<db-attribute-pair source="QUALIFIED1_ID" target="ID"/>
 	</db-relationship>
+	<db-relationship name="qualified4s" source="TEST_QUALIFIED3" target="TEST_QUALIFIED4" toMany="true">
+		<db-attribute-pair source="ID" target="QUALIFIED3_ID"/>
+	</db-relationship>
+	<db-relationship name="qualified3" source="TEST_QUALIFIED4" target="TEST_QUALIFIED3">
+		<db-attribute-pair source="QUALIFIED3_ID" target="ID"/>
+	</db-relationship>
 	<obj-relationship name="qualified2s" source="Qualified1" target="Qualified2" db-relationship-path="qualified2s"/>
 	<obj-relationship name="qualified1" source="Qualified2" target="Qualified1" db-relationship-path="qualified1"/>
+	<obj-relationship name="qualified4s" source="Qualified3" target="Qualified4" deleteRule="Deny" db-relationship-path="qualified4s"/>
+	<obj-relationship name="qualified3" source="Qualified4" target="Qualified3" deleteRule="Nullify" db-relationship-path="qualified3"/>
 </data-map>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/41dd56c5/docs/doc/src/main/resources/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/RELEASE-NOTES.txt b/docs/doc/src/main/resources/RELEASE-NOTES.txt
index 4e002c4..7958307 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -24,6 +24,7 @@ CAY-2381 cgen: meaningful PK with boxed type ends up with primitive type in gene
 CAY-2382 Lack of synchronization in DataContext serialization
 CAY-2387 Can't select byte[] property with ColumnSelect
 CAY-2388 Modeler: Visualization issues with undo/redo actions for attributes and relationships
+CAY-2389 DbEntity qualifier with DbPath expression translates into wrong SQL
 
 ----------------------------------
 Release: 4.1.M1