Skip to content
Snippets Groups Projects
Commit 210e833b authored by Timm Fitschen's avatar Timm Fitschen
Browse files

Merge branch 'f-permission-checks' into 'dev'

F permission checks

See merge request !65
parents 5f9962cb bd13c749
Branches
Tags
1 merge request!65F permission checks
Pipeline #26746 failed
......@@ -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;
......
......@@ -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(
......
......@@ -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");
}
return "SELECT results.id AS id"
+ (versioned ? ", ev.version AS version" : "")
+ " FROM `"
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"
+ (versioned
? " JOIN entity_version AS ev ON (results.id = ev.entity_id AND results._iversion = ev._iversion)"
: "");
+ "` 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);");
}
}
/**
......@@ -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 {
public Query execute(final Access access) throws ParsingException, QueryException {
try {
parse();
setAccess(access);
if (useCache()) {
this.resultSet = getCached(getCacheKey());
}
if (this.resultSet == null) {
executeNoCache(access);
if (this.cachable) {
setCache(getCacheKey(), this.resultSet);
if (access.useCache()) {
getResultFromCache();
}
this.logger.debug("Uncached query {}", this.query);
} else {
if (this.resultSet != null) {
this.logger.debug("Using cached result for {}", this.query);
this.cached = true;
} else {
executeQueryInBackend(access);
storeResultInCache();
this.logger.debug("Uncached query {}", this.query);
}
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 (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);
}
}
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 long t2 = System.currentTimeMillis();
this.addBenchmark("filterEntitiesWithoutRetrievePermission", t2 - t1);
}
rs.close();
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) {
stmt.execute("DELETE FROM `" + resultSet + "` WHERE id = " + id);
pstmt.setInt(1, id);
pstmt.execute();
}
}
}
}
/**
* 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.
*
* <p>Note, unlike the public version of this function `resultSet` is not altered but the filtered
* list is returned.
*
* @param entities
* @throws TransactionException
* @return the filtered list.
* @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();
......
......@@ -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(?,?,?,?)");
......
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);
}
}
}
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment