diff --git a/src/main/java/org/caosdb/server/database/access/AbstractAccess.java b/src/main/java/org/caosdb/server/database/access/AbstractAccess.java
index 9d00a876e8db21446b88b6ab00dfeb12ee043c0d..1d30bd3fee7187b9c44378e5b94c1c6d97287f00 100644
--- a/src/main/java/org/caosdb/server/database/access/AbstractAccess.java
+++ b/src/main/java/org/caosdb/server/database/access/AbstractAccess.java
@@ -77,7 +77,13 @@ public abstract class AbstractAccess<T extends TransactionInterface> implements
   public void setUseCache(final Boolean useCache) {
     this.useCache = useCache;
   }
-
+  /**
+   * Whether the transaction allows to use the query cache or other caches. This is controlled by
+   * the "cache" flag.
+   *
+   * @see {@link NoCache}
+   * @return true if caching is encouraged.
+   */
   @Override
   public boolean useCache() {
     return this.useCache;
diff --git a/src/main/java/org/caosdb/server/query/Backreference.java b/src/main/java/org/caosdb/server/query/Backreference.java
index fec64b19b761418a4da148ca4adff9d9145960af..7d551fd45e761a54d2c2508dcbb1b98ffc72891c 100644
--- a/src/main/java/org/caosdb/server/query/Backreference.java
+++ b/src/main/java/org/caosdb/server/query/Backreference.java
@@ -130,7 +130,7 @@ public class Backreference implements EntityFilterInterface, QueryInterface {
         callApplyBackRef.setNull(2, VARCHAR);
       }
       if (this.propertiesTable != null) { // propertiesTable
-        getQuery().filterEntitiesWithoutRetrievePermission(this.propertiesTable);
+        getQuery().filterIntermediateResult(this.propertiesTable);
         callApplyBackRef.setString(3, this.propertiesTable);
         this.statistics.put("propertiesTable", this.propertiesTable);
         this.statistics.put(
@@ -140,7 +140,7 @@ public class Backreference implements EntityFilterInterface, QueryInterface {
         callApplyBackRef.setNull(3, VARCHAR);
       }
       if (this.entitiesTable != null) { // entitiesTable
-        getQuery().filterEntitiesWithoutRetrievePermission(this.entitiesTable);
+        getQuery().filterIntermediateResult(this.entitiesTable);
         callApplyBackRef.setString(4, this.entitiesTable);
         this.statistics.put("entitiesTable", this.entitiesTable);
         this.statistics.put(
diff --git a/src/main/java/org/caosdb/server/query/Query.java b/src/main/java/org/caosdb/server/query/Query.java
index 07a165a352ba9a07a345018e7d287cd961f4a00b..81a08a9dbecbd28195172df55dfdd600ae24e53b 100644
--- a/src/main/java/org/caosdb/server/query/Query.java
+++ b/src/main/java/org/caosdb/server/query/Query.java
@@ -50,12 +50,12 @@ import org.caosdb.api.entity.v1.MessageCode;
 import org.caosdb.datetime.UTCDateTime;
 import org.caosdb.server.CaosDBServer;
 import org.caosdb.server.ServerProperties;
+import org.caosdb.server.accessControl.Principal;
 import org.caosdb.server.caching.Cache;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.implementation.MySQL.ConnectionException;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLHelper;
 import org.caosdb.server.database.backend.transaction.RetrieveSparseEntity;
-import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.database.misc.DBHelper;
 import org.caosdb.server.database.misc.TransactionBenchmark;
 import org.caosdb.server.entity.Entity;
@@ -70,6 +70,8 @@ import org.caosdb.server.permissions.EntityPermission;
 import org.caosdb.server.query.CQLParser.CqContext;
 import org.caosdb.server.query.CQLParsingErrorListener.ParsingError;
 import org.caosdb.server.transaction.TransactionInterface;
+import org.caosdb.server.transaction.WriteTransaction;
+import org.caosdb.server.utils.ResultSetIterator;
 import org.jdom2.Element;
 import org.slf4j.Logger;
 
@@ -211,14 +213,17 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
     }
   }
 
-  public static class IdVersionPair {
-    public IdVersionPair(final Integer id, final String version) {
+  /** A data class for storing triplets of (Entity ID, version hash, ACL string) */
+  public static class IdVersionAclTriplet {
+    public IdVersionAclTriplet(final Integer id, final String version, final String acl) {
       this.id = id;
       this.version = version;
+      this.acl = acl;
     }
 
     public Integer id;
     public String version;
+    public String acl;
 
     @Override
     public String toString() {
@@ -230,9 +235,16 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
 
     @Override
     public boolean equals(final Object obj) {
-      if (obj instanceof IdVersionPair) {
-        final IdVersionPair that = (IdVersionPair) obj;
-        return this.id == that.id && this.version == that.version;
+      if (obj instanceof IdVersionAclTriplet) {
+        final IdVersionAclTriplet that = (IdVersionAclTriplet) obj;
+        // checking ID and version hash should be sufficient
+        if (this.id == that.id && this.version == that.version) {
+          if (this.acl != that.acl) {
+            throw new RuntimeException("Implementation error! ACL should not differ");
+          }
+          return true;
+        }
+        ;
       }
       return false;
     }
@@ -249,7 +261,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
           .equalsIgnoreCase("FALSE");
 
   private final Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
-  List<IdVersionPair> resultSet = null;
+  List<IdVersionAclTriplet> resultSet = null;
   private final String query;
   private Pattern entity = null;
   private Role role = null;
@@ -265,6 +277,18 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
   private final ArrayList<ToElementable> messages = new ArrayList<>();
   private Access access;
   private boolean versioned = false;
+  /**
+   * This key-value cache stores lists of of (id, version hash, acl string) triplets. Those values
+   * are the result sets of queries. The keys are created such that they are different for different
+   * for different queries (@see {@link #getCacheKey}). The key includes realm and username of a
+   * subject, if the query result must not be shared among users. If intermediate permission checks
+   * are done (e.g. in a subproperty query filter), the query result will be stored using a user
+   * specific key. The final permission check has not yet been applied to the result set that is
+   * stored in the cache. This allows (some) cache entries to be shared among users since the final
+   * check is applied after the retrieval of the result set from the cache (@see {@link
+   * filterEntitiesWithoutRetrievePermission}) The cache is invalidated whenever there is a write
+   * operation (@see {@link #clearCache} which is called in the {@link WriteTransaction#commit}).
+   */
   private static ICacheAccess<String, Serializable> cache =
       Cache.getCache("HIGH_LEVEL_QUERY_CACHE");
   /** Cached=true means that the results of this query have actually been pulled from the cache. */
@@ -274,7 +298,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
    * query evaluation contains complex permission checking which could only be cached on a per-user
    * basis (maybe in the future).
    */
-  private boolean cachable = true;
+  private boolean filteredIntermediateResult = false;
 
   /**
    * Tags the query cache and is renewed each time the cache is being cleared, i.e. each time the
@@ -367,17 +391,16 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
       return this.sourceSet;
     } catch (final SQLException e) {
       e.printStackTrace();
-      throw new TransactionException(e);
+      throw new QueryException(e);
     }
   }
 
   /**
-   * Finds all QueryTemplates in the resultSet and applies them to the same resultSet. The ids of
+   * Finds all QueryTemplates in the resultSet and applies them to the same resultSet. The IDs of
    * the QueryTemplates themselves are then removed from the resultSet. If the current user doesn't
    * have the RETRIEVE:ENTITY permission for a particular QueryTemplate it will be ignored.
    *
    * @param resultSet
-   * @throws SQLException
    * @throws QueryException
    */
   public void applyQueryTemplates(final QueryInterface query, final String resultSet)
@@ -415,7 +438,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
         union(query, resultSet, subResultSet);
       }
     } catch (final SQLException e) {
-      throw new TransactionException(e);
+      throw new QueryException(e);
     }
   }
 
@@ -567,7 +590,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
    *
    * <ol>
    *   <li>FIND * -> FIND ENTITY (which basically prevents to copy the complete entity table just to
-   *       read out the ids immediately).
+   *       read out the IDs immediately).
    * </ol>
    */
   public void optimize() {
@@ -593,33 +616,47 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
   }
 
   /**
-   * Generate a SQL statement which reads out the resulting ids (and version ids if `versioned` is
-   * true).
+   * Generate a SQL statement which reads out the resulting IDs and ACL strings (and version IDs if
+   * `versioned` is true).
    *
-   * <p>If the parameter `resultSetTableName` is "entities" actually the entity_version table is
-   * used to fetch all ids.
+   * <p>There are four variants: Where the parameter `resultSetTableName` is "entities" and
+   * otherwise and where `versioned` is true and otherwise.
    *
    * @param resultSetTableName name of the table with all the resulting entities
    * @param versioned whether the query was versioned
    * @return an SQL statement
-   * @throws QueryException
    */
   private String generateSelectStatementForResultSet(
       final String resultSetTableName, final boolean versioned) {
+    // TODO remove the entities.role part when
+    // https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/issues/245 is resolved
     if (resultSetTableName.equals("entities")) {
-      return "SELECT entity_id AS id"
-          + (versioned ? ", version AS version" : "")
-          + " FROM entity_version"
-          + (versioned ? "" : " WHERE `_iversion` = 1");
+      final String baseStatement =
+          "SELECT entities.id, entity_acl.acl FROM entities INNER JOIN entity_acl ON entity_acl.id=entities.acl WHERE entities.role!='DOMAIN'";
+      if (!versioned) {
+        return baseStatement + ";";
+      }
+      // if versioned, the statement is surrounded with another SELECT and JOIN
+      return ("SELECT id, acl, version FROM ("
+          + baseStatement
+          + ") AS tmp JOIN entity_version ON entity_version.entity_id=tmp.id;");
+    } else {
+      if (!versioned) {
+        return (" SELECT tmp.id, entity_acl.acl FROM "
+                + " (SELECT results.id AS id, entities.acl AS acl_id FROM `"
+                + resultSetTableName
+                + "` AS results JOIN entities ON results.id=entities.id WHERE entities.role!='DOMAIN') AS tmp"
+                + " JOIN entity_acl ON entity_acl.id=tmp.acl_id")
+            + ";";
+      }
+      // if versioned, the statement is surrounded with another SELECT and JOIN
+      return ("SELECT tmp2.id, acl, version FROM( SELECT tmp.id, entity_acl.acl, tmp._iversion AS _iversion FROM "
+          + " (SELECT results.id AS id, entities.acl AS acl_id, results._iversion AS _iversion FROM `"
+          + resultSetTableName
+          + "` AS results JOIN entities ON results.id=entities.id) AS tmp"
+          + " JOIN entity_acl ON entity_acl.id=tmp.acl_id) as tmp2  "
+          + "join entity_version on (entity_version.entity_id=tmp2.id AND tmp2._iversion = entity_version._iversion);");
     }
-    return "SELECT results.id AS id"
-        + (versioned ? ", ev.version AS version" : "")
-        + " FROM `"
-        + resultSetTableName
-        + "` AS results"
-        + (versioned
-            ? " JOIN entity_version AS ev ON (results.id = ev.entity_id AND results._iversion = ev._iversion)"
-            : "");
   }
 
   /**
@@ -630,17 +667,19 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
    * @return list of results of this query.
    * @throws QueryException
    */
-  private List<IdVersionPair> getResultSet(final String resultSetTableName, final boolean versioned)
-      throws QueryException {
+  private List<IdVersionAclTriplet> getResultSet(
+      final String resultSetTableName, final boolean versioned) throws QueryException {
     ResultSet finishResultSet = null;
     try {
       final String sql = generateSelectStatementForResultSet(resultSetTableName, versioned);
       final PreparedStatement finish = getConnection().prepareStatement(sql);
       finishResultSet = finish.executeQuery();
-      final List<IdVersionPair> rs = new LinkedList<>();
+      final List<IdVersionAclTriplet> rs = new LinkedList<>();
       while (finishResultSet.next()) {
         final String version = versioned ? finishResultSet.getString("version") : null;
-        rs.add(new IdVersionPair(finishResultSet.getInt("id"), version));
+        final String acl = finishResultSet.getString("acl");
+
+        rs.add(new IdVersionAclTriplet(finishResultSet.getInt("id"), version, acl));
       }
       return rs;
     } catch (final SQLException e) {
@@ -657,14 +696,43 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
   }
 
   /**
-   * Whether the transaction allows this query instance to use the query cache. This is controlled
-   * by the "cache" flag.
-   *
-   * @see {@link NoCache}
-   * @return true if caching is encouraged.
+   * Try to set the `resultSet` member variable using the values stored in the high level query
+   * cache.
    */
-  private boolean useCache() {
-    return getAccess().useCache();
+  private void getResultFromCache() {
+    // try key with username and realm
+    // TODO include this again to activate the user-specific caching
+    // this.resultSet = getCached(getCacheKey(true));
+    if (this.resultSet == null) {
+      // try key without username and realm
+      this.resultSet = getCached(getCacheKey(false));
+    }
+  }
+  /** Store the content of `resultSet` member in the high level query cache. */
+  private void storeResultInCache() {
+    // Decide whether user specific cache needs to be used or not
+    // Currently, this is solely determined via filteredIntermediateResult.
+    if (this.filteredIntermediateResult) {
+      // TODO include this again to activate user-specific caching
+      // cacheItem(getCacheKey(true), this.resultSet);
+    } else {
+      cacheItem(getCacheKey(false), this.resultSet);
+    }
+  }
+  /** Fill entities from `resultSet` into `container`. */
+  private void fillContainerWithResult() {
+    if (this.container != null && this.type == Type.FIND) {
+      for (final IdVersionAclTriplet t : this.resultSet) {
+
+        final Entity e = new RetrieveEntity(t.id, t.version);
+
+        // if query has select-clause:
+        if (this.selections != null && !this.selections.isEmpty()) {
+          e.addSelections(this.selections);
+        }
+        this.container.add(e);
+      }
+    }
   }
 
   /**
@@ -674,59 +742,60 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
    *
    * @param access
    * @return
-   * @throws ParsingException
+   * @throws ParsingException, QueryException
    */
-  public Query execute(final Access access) throws ParsingException {
-    parse();
-    setAccess(access);
-    if (useCache()) {
-      this.resultSet = getCached(getCacheKey());
-    }
-
-    if (this.resultSet == null) {
-      executeNoCache(access);
-      if (this.cachable) {
-        setCache(getCacheKey(), this.resultSet);
+  public Query execute(final Access access) throws ParsingException, QueryException {
+    try {
+      parse();
+      setAccess(access);
+      if (access.useCache()) {
+        getResultFromCache();
       }
-      this.logger.debug("Uncached query {}", this.query);
-    } else {
-      this.logger.debug("Using cached result for {}", this.query);
-      this.cached = true;
-    }
-
-    this.resultSet = filterEntitiesWithoutRetrievePermission(this.resultSet);
-
-    // Fill resulting entities into container
-    if (this.container != null && this.type == Type.FIND) {
-      for (final IdVersionPair p : this.resultSet) {
+      if (this.resultSet != null) {
+        this.logger.debug("Using cached result for {}", this.query);
+        this.cached = true;
 
-        if (p.id > 99) {
-          final Entity e = new RetrieveEntity(p.id, p.version);
-
-          // if query has select-clause:
-          if (this.selections != null && !this.selections.isEmpty()) {
-            e.addSelections(this.selections);
-          }
-          this.container.add(e);
-        }
+      } else {
+        executeQueryInBackend(access);
+        storeResultInCache();
+        this.logger.debug("Uncached query {}", this.query);
       }
+
+      this.resultSet = filterEntitiesWithoutRetrievePermission(this.resultSet);
+      this.resultSet = removeInternalEntitiesFromResultSet();
+      fillContainerWithResult();
+    } catch (final SQLException e) {
+      e.printStackTrace();
+      throw new QueryException(e);
     }
     return this;
   }
-
   /** Remove all cached queries from the cache. */
   public static void clearCache() {
     cacheETag = UUID.randomUUID().toString();
     cache.clear();
   }
 
+  /** There are internal Entities (with ID<100) that should never be returned. */
+  private List<IdVersionAclTriplet> removeInternalEntitiesFromResultSet() {
+
+    final List<IdVersionAclTriplet> filtered = new ArrayList<>();
+    for (final IdVersionAclTriplet triplet : resultSet) {
+
+      if (triplet.id >= 100) {
+        filtered.add(triplet);
+      }
+    }
+    return filtered;
+  }
+
   /**
    * Cache a query result.
    *
    * @param key
    * @param resultSet
    */
-  private void setCache(final String key, final List<IdVersionPair> resultSet) {
+  private void cacheItem(final String key, final List<IdVersionAclTriplet> resultSet) {
     synchronized (cache) {
       if (resultSet instanceof Serializable) {
         cache.put(key, (Serializable) resultSet);
@@ -737,17 +806,17 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
   }
 
   /**
-   * Retrieve a result set of entity ids (and the version) from the cache.
+   * Retrieve a result set of entity IDs (and the version) from the cache.
    *
    * @param key
    * @return
    */
   @SuppressWarnings("unchecked")
-  private List<IdVersionPair> getCached(final String key) {
-    return (List<IdVersionPair>) cache.get(key);
+  private List<IdVersionAclTriplet> getCached(final String key) {
+    return (List<IdVersionAclTriplet>) cache.get(key);
   }
 
-  protected void executeNoCache(final Access access) {
+  protected void executeQueryInBackend(final Access access) throws SQLException {
     try {
       this.resultSet = getResultSet(executeStrategy(this.versioned), this.versioned);
     } finally {
@@ -787,72 +856,100 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
 
   /**
    * Filter out all entities which may not be retrieved by this user due to a missing RETRIEVE
-   * permission. This one is also designed for filtering of intermediate results.
+   * permission. This function is also designed for filtering of intermediate results.
    *
-   * @param resultSet
+   * @param tabname
    * @throws SQLException
-   * @throws TransactionException
    */
-  public void filterEntitiesWithoutRetrievePermission(final String resultSet)
-      throws SQLException, TransactionException {
+  public void filterIntermediateResult(final String tabname) throws SQLException {
     if (!filterEntitiesWithoutRetrievePermisions) {
       return;
     }
-    cachable = false;
+    filteredIntermediateResult = true;
+
+    /*
+     * The following creates a table with the columns (entity ID, acl) from
+     * a given table with entity IDs. Here, acl is the string representation
+     * of the acl.
+     *
+     * TODO:In future, one might want to retrieve only a distinct set of acl
+     * with (acl_id, acl) and a table with (entity_id, acl_id) to reduce the
+     * amount of data being transfered.
+     */
+
     try (final Statement stmt = this.getConnection().createStatement()) {
-      final ResultSet rs = stmt.executeQuery("SELECT id from `" + resultSet + "`");
-      final List<Integer> toBeDeleted = new LinkedList<Integer>();
-      while (rs.next()) {
-        final long t1 = System.currentTimeMillis();
-        final Integer id = rs.getInt("id");
-        if (id > 99
-            && !execute(new RetrieveSparseEntity(id, null), this.getAccess())
-                .getEntity()
-                .getEntityACL()
-                .isPermitted(this.getUser(), EntityPermission.RETRIEVE_ENTITY)) {
-          toBeDeleted.add(id);
+      final String query =
+          ("SELECT entity_n_acl.id, entity_acl.acl from "
+              + "(select entities.id, entities.acl from entities "
+              + "inner join `"
+              + tabname
+              + "` as rs on entities.id=rs.id) "
+              + "as entity_n_acl "
+              + "left join entity_acl on entity_n_acl.acl=entity_acl.id;");
+      final ResultSet entitiesRS = stmt.executeQuery(query);
+      final ResultSetIterator entitiesWithACL = new ResultSetIterator(entitiesRS);
+      final List<Integer> toBeDeleted = collectIdsWithoutPermission(entitiesWithACL);
+      try (final PreparedStatement pstmt =
+          this.getConnection().prepareStatement("DELETE FROM `" + tabname + "` WHERE id = ?")) {
+        for (final Integer id : toBeDeleted) {
+          pstmt.setInt(1, id);
+          pstmt.execute();
         }
-        final long t2 = System.currentTimeMillis();
-        this.addBenchmark("filterEntitiesWithoutRetrievePermission", t2 - t1);
-      }
-      rs.close();
-      for (final Integer id : toBeDeleted) {
-        stmt.execute("DELETE FROM `" + resultSet + "` WHERE id = " + id);
       }
     }
   }
 
   /**
-   * Filter out all entities which may not be retrieved by this user due to a missing RETRIEVE
-   * permission. This one is for the filtering of the final result set and not for the filtering of
-   * any intermediate results.
+   * Creates a new list that contains only the entities from the `resultSet` for which the current
+   * subject has RETRIEVE permission.
    *
-   * @param entities
-   * @throws TransactionException
-   * @return the filtered list.
+   * <p>Note, unlike the public version of this function `resultSet` is not altered but the filtered
+   * list is returned.
+   *
+   * @param resultSet
+   * @return list without the entities with insufficient permissions
    */
-  private List<IdVersionPair> filterEntitiesWithoutRetrievePermission(
-      final List<IdVersionPair> entities) throws TransactionException {
-    if (!filterEntitiesWithoutRetrievePermisions) {
-      return entities;
+  private List<IdVersionAclTriplet> filterEntitiesWithoutRetrievePermission(
+      final List<IdVersionAclTriplet> resultSet) {
+    final List<Integer> toBeDeleted = collectIdsWithoutPermission(resultSet.iterator());
+    final List<IdVersionAclTriplet> filtered = new ArrayList<>();
+    for (final IdVersionAclTriplet triplet : resultSet) {
+      if (-1 == toBeDeleted.indexOf(triplet.id)) {
+        filtered.add(triplet);
+      }
     }
-
-    final List<IdVersionPair> result = new ArrayList<>();
-    final Iterator<IdVersionPair> iterator = entities.iterator();
-    while (iterator.hasNext()) {
+    return filtered;
+  }
+  /**
+   * Creates a list with IDs of those entities that do not have sufficient RETRIEVE permission
+   *
+   * @param entityIterator Iterator over the result set consisting of (ID, version hash, acl string)
+   *     triplets.
+   * @return compiled list
+   */
+  private List<Integer> collectIdsWithoutPermission(Iterator<IdVersionAclTriplet> entityIterator) {
+    final HashMap<String, Boolean> acl_cache = new HashMap<String, Boolean>();
+    final List<Integer> toBeDeleted = new LinkedList<Integer>();
+    while (entityIterator.hasNext()) {
       final long t1 = System.currentTimeMillis();
-      final IdVersionPair next = iterator.next();
-      if (next.id > 99
-          && execute(new RetrieveSparseEntity(next.id, next.version), getAccess())
-              .getEntity()
-              .getEntityACL()
-              .isPermitted(getUser(), EntityPermission.RETRIEVE_ENTITY)) {
-        result.add(next);
+
+      final IdVersionAclTriplet triplet = entityIterator.next();
+
+      if (!acl_cache.containsKey(triplet.acl)) {
+        acl_cache.put(
+            triplet.acl,
+            EntityACL.deserialize(triplet.acl)
+                .isPermitted(this.getUser(), EntityPermission.RETRIEVE_ENTITY));
       }
+
+      if (!acl_cache.get(triplet.acl)) {
+        toBeDeleted.add(triplet.id);
+      }
+
       final long t2 = System.currentTimeMillis();
-      addBenchmark("filterEntitiesWithoutRetrievePermission", t2 - t1);
+      this.addBenchmark("filterEntitiesWithoutRetrievePermission", t2 - t1);
     }
-    return result;
+    return toBeDeleted;
   }
 
   @Override
@@ -1004,18 +1101,29 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
    *
    * @return A Cache key.
    */
-  String getCacheKey() {
+  String getCacheKey(boolean addUser) {
     final StringBuilder sb = new StringBuilder();
+    if (addUser && (this.user != null)) {
+      sb.append("U_");
+      String principal_desc =
+          ((Principal) this.user.getPrincipal()).getUsername()
+              + Principal.REALM_SEPARATOR
+              + ((Principal) this.user.getPrincipal()).getRealm();
+      sb.append(principal_desc);
+    }
     if (this.versioned) {
-      sb.append("versioned");
+      sb.append("V_");
     }
     if (this.role != null) {
+      sb.append("R_");
       sb.append(this.role.toString());
     }
     if (this.entity != null) {
+      sb.append("E_");
       sb.append(this.entity.toString());
     }
     if (this.filter != null) {
+      sb.append("F_");
       sb.append(this.filter.getCacheKey());
     }
     return sb.toString();
diff --git a/src/main/java/org/caosdb/server/query/SubProperty.java b/src/main/java/org/caosdb/server/query/SubProperty.java
index f3be825ccf28b7df2ba646306a61f006664ae5a6..8ff167eac1e045875576a75f3d922f5c6461c68e 100644
--- a/src/main/java/org/caosdb/server/query/SubProperty.java
+++ b/src/main/java/org/caosdb/server/query/SubProperty.java
@@ -87,7 +87,7 @@ public class SubProperty implements QueryInterface, EntityFilterInterface {
 
         this.filter.apply(this);
 
-        getQuery().filterEntitiesWithoutRetrievePermission(this.sourceSet);
+        getQuery().filterIntermediateResult(this.sourceSet);
 
         final CallableStatement callFinishSubProperty =
             getConnection().prepareCall("call finishSubProperty(?,?,?,?)");
diff --git a/src/main/java/org/caosdb/server/utils/ResultSetIterator.java b/src/main/java/org/caosdb/server/utils/ResultSetIterator.java
new file mode 100644
index 0000000000000000000000000000000000000000..0912d278c7158c9b7b75a585c2827d851951f29b
--- /dev/null
+++ b/src/main/java/org/caosdb/server/utils/ResultSetIterator.java
@@ -0,0 +1,60 @@
+package org.caosdb.server.utils;
+
+import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import org.caosdb.server.database.exceptions.TransactionException;
+import org.caosdb.server.query.Query.IdVersionAclTriplet;
+
+/**
+ * A class for iterating over {@link ResultSet}
+ *
+ * <p>{@link ResultSet} only provides a `next` function which moves the cursor. The behavior is here
+ * mapped onto the functions of the Iterator interface. TODO Move this generic function? Check again
+ * if an implementation is available from elsewhere.
+ */
+public class ResultSetIterator implements Iterator<IdVersionAclTriplet> {
+  public ResultSetIterator(final ResultSet resultset) {
+    this.resultSet = resultset;
+  }
+
+  private ResultSet resultSet;
+  private boolean cursorHasMoved = false;
+  private boolean currentIsValid = true;
+
+  public boolean hasNext() {
+    if (!this.cursorHasMoved) {
+      try {
+        this.currentIsValid = this.resultSet.next();
+      } catch (SQLException e) {
+        throw new TransactionException(e);
+      }
+      this.cursorHasMoved = true;
+    }
+    return this.currentIsValid;
+  };
+
+  public IdVersionAclTriplet next() {
+    if (!this.cursorHasMoved) {
+      try {
+        this.currentIsValid = this.resultSet.next();
+      } catch (SQLException e) {
+        throw new TransactionException(e);
+      }
+    }
+    this.cursorHasMoved = false;
+    if (!this.currentIsValid) {
+      throw new NoSuchElementException();
+    }
+    try {
+      final Integer id = resultSet.getInt("id");
+      final String acl_str = bytes2UTF8(resultSet.getBytes("ACL"));
+      return new IdVersionAclTriplet(id, "", acl_str);
+    } catch (SQLException e) {
+      throw new TransactionException(e);
+    }
+  }
+}
diff --git a/src/test/java/org/caosdb/server/query/QueryTest.java b/src/test/java/org/caosdb/server/query/QueryTest.java
index 8f3d4946c36d795e5e7bd9048a66f86cc0290a0b..a039f2106ab8f1a0c6ba61bdd906ba86372c9281 100644
--- a/src/test/java/org/caosdb/server/query/QueryTest.java
+++ b/src/test/java/org/caosdb/server/query/QueryTest.java
@@ -26,7 +26,11 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
 import java.io.IOException;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.subject.Subject;
 import org.caosdb.server.CaosDBServer;
+import org.caosdb.server.ServerProperties;
+import org.caosdb.server.accessControl.AnonymousAuthenticationToken;
 import org.caosdb.server.database.access.InitAccess;
 import org.caosdb.server.transaction.WriteTransaction;
 import org.junit.BeforeClass;
@@ -37,41 +41,57 @@ public class QueryTest {
   @BeforeClass
   public static void initServerProperties() throws IOException {
     CaosDBServer.initServerProperties();
+    CaosDBServer.initShiro();
   }
 
   String getCacheKey(String query) {
     Query q = new Query(query);
     q.parse();
-    return q.getCacheKey();
+    return q.getCacheKey(true);
+  }
+
+  String getCacheKeyWithUser(String query) {
+    Subject anonymous = SecurityUtils.getSubject();
+    CaosDBServer.setProperty(ServerProperties.KEY_AUTH_OPTIONAL, "true");
+    anonymous.login(AnonymousAuthenticationToken.getInstance());
+    Query q = new Query(query, anonymous);
+    q.parse();
+    return q.getCacheKey(true);
   }
 
   @Test
   public void testGetKey() {
-    assertEquals("enamePOV(pname,=,val1)", getCacheKey("FIND ename WITH pname = val1"));
-    assertEquals("enamePOV(pname,=,val1)", getCacheKey("COUNT ename WITH pname = val1"));
-    assertEquals("enamePOV(pname,=,val1)", getCacheKey("SELECT bla FROM ename WITH pname = val1"));
-    assertEquals("enamePOV(pname,null,null)", getCacheKey("SELECT bla FROM ename WITH pname"));
+    assertEquals("E_enameF_POV(pname,=,val1)", getCacheKey("FIND ename WITH pname = val1"));
+    assertEquals("E_enameF_POV(pname,=,val1)", getCacheKey("COUNT ename WITH pname = val1"));
+    assertEquals(
+        "E_enameF_POV(pname,=,val1)", getCacheKey("SELECT bla FROM ename WITH pname = val1"));
+    assertEquals("E_enameF_POV(pname,null,null)", getCacheKey("SELECT bla FROM ename WITH pname"));
     assertEquals(
-        "enamemaxPOV(pname,null,null)",
+        "E_enameF_maxPOV(pname,null,null)",
         getCacheKey("SELECT bla FROM ename WITH THE GREATEST pname"));
 
     assertEquals(
-        "RECORDenamePOV(pname,=,val1)", getCacheKey("FIND RECORD ename WITH pname = val1"));
-    assertEquals("ENTITYPOV(pname,=,val1)", getCacheKey("COUNT ENTITY WITH pname = val1"));
+        "R_RECORDE_enameF_POV(pname,=,val1)", getCacheKey("FIND RECORD ename WITH pname = val1"));
+    assertEquals("R_ENTITYF_POV(pname,=,val1)", getCacheKey("COUNT ENTITY WITH pname = val1"));
     assertEquals(
-        "enameConj(POV(pname,=,val1)POV(ename2,=,val2))",
+        "E_enameF_Conj(POV(pname,=,val1)POV(ename2,=,val2))",
         getCacheKey("SELECT bla FROM ename WITH pname = val1 AND ename2 = val2"));
 
-    assertEquals("versionedENTITYID(,>,2)", getCacheKey("FIND ANY VERSION OF ENTITY WITH ID > 2"));
-    assertEquals("ENTITYID(min,,)", getCacheKey("FIND ENTITY WITH THE SMALLEST ID"));
-    assertEquals("ENTITYSAT(asdf/%%)", getCacheKey("FIND ENTITY WHICH IS STORED AT /asdf/*"));
-    assertEquals("ENTITYSAT(asdf/asdf)", getCacheKey("FIND ENTITY WHICH IS STORED AT asdf/asdf"));
+    assertEquals("V_R_ENTITYF_ID(,>,2)", getCacheKey("FIND ANY VERSION OF ENTITY WITH ID > 2"));
+    assertEquals("R_ENTITYF_ID(min,,)", getCacheKey("FIND ENTITY WITH THE SMALLEST ID"));
+    assertEquals("R_ENTITYF_SAT(asdf/%%)", getCacheKey("FIND ENTITY WHICH IS STORED AT /asdf/*"));
     assertEquals(
-        "enamePOV(ref1,null,null)SUB(POV(pname,>,val1)",
+        "R_ENTITYF_SAT(asdf/asdf)", getCacheKey("FIND ENTITY WHICH IS STORED AT asdf/asdf"));
+    assertEquals(
+        "E_enameF_POV(ref1,null,null)SUB(POV(pname,>,val1)",
         getCacheKey("FIND ename WITH ref1 WITH pname > val1 "));
     assertEquals(
-        "ename@(ref1,null)SUB(POV(pname,>,val1)",
+        "E_enameF_@(ref1,null)SUB(POV(pname,>,val1)",
         getCacheKey("FIND ename WHICH IS REFERENCED BY ref1 WITH pname > val1 "));
+
+    assertEquals(
+        "U_anonymous@anonymousE_enameF_POV(pname,=,val1)",
+        getCacheKeyWithUser("FIND ename WITH pname = val1"));
   }
 
   @Test