diff --git a/src/main/java/org/caosdb/server/query/Backreference.java b/src/main/java/org/caosdb/server/query/Backreference.java index 5f8105c823a231a14853e1db370649c7a7189649..2fb2e0435e38eb65b5165a75d169d0ab1e9a4e96 100644 --- a/src/main/java/org/caosdb/server/query/Backreference.java +++ b/src/main/java/org/caosdb/server/query/Backreference.java @@ -24,7 +24,6 @@ package org.caosdb.server.query; import static java.sql.Types.VARCHAR; import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; - import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; @@ -320,4 +319,9 @@ public class Backreference implements EntityFilterInterface, QueryInterface { public void addBenchmark(final String str, final long time) { this.query.addBenchmark(this.getClass().getSimpleName() + "." + str, time); } + + @Override + public boolean isVersioned() { + return false; + } } diff --git a/src/main/java/org/caosdb/server/query/CQLLexer.g4 b/src/main/java/org/caosdb/server/query/CQLLexer.g4 index 62cb12c09c00a2f0005a8b9e2b7d5cc8f6b753e5..ec363be59893ad72cf412dbdc9d1f6d6644da5be 100644 --- a/src/main/java/org/caosdb/server/query/CQLLexer.g4 +++ b/src/main/java/org/caosdb/server/query/CQLLexer.g4 @@ -34,6 +34,25 @@ BY: [Bb][Yy] ; +fragment +OF_f: + [Oo][Ff] +; + +fragment +ANY_f: + [Aa][Nn][Yy] +; + +fragment +VERSION_f: + [Vv][Ee][Rr][Ss][Ii][Oo][Nn] +; + +ANY_VERSION_OF: + (ANY_f EMPTY_SPACE VERSION_f EMPTY_SPACE OF_f) +; + SELECT: [Ss][Ee][Ll][Ee][Cc][Tt] -> pushMode(SELECT_MODE) ; diff --git a/src/main/java/org/caosdb/server/query/CQLParser.g4 b/src/main/java/org/caosdb/server/query/CQLParser.g4 index 699ff2f386878444f4400d1e6e7df75ad53c8d9a..0daeff47a95ff38391ce3f9b8004bec642b0ccb2 100644 --- a/src/main/java/org/caosdb/server/query/CQLParser.g4 +++ b/src/main/java/org/caosdb/server/query/CQLParser.g4 @@ -31,11 +31,12 @@ options { tokenVocab = CQLLexer; } import java.util.List; } -cq returns [Query.Type t, List<Query.Selection> s, Query.Pattern e, Query.Role r, EntityFilterInterface filter] +cq returns [Query.Type t, List<Query.Selection> s, Query.Pattern e, Query.Role r, EntityFilterInterface filter, VersionFilter v] @init{ $s = null; $e = null; $r = null; + $v = VersionFilter.UNVERSIONED; $filter = null; } : @@ -44,6 +45,7 @@ cq returns [Query.Type t, List<Query.Selection> s, Query.Pattern e, Query.Role r SELECT prop_sel {$s = $prop_sel.s;} FROM {$t = Query.Type.FIND;} | FIND {$t = Query.Type.FIND;} | COUNT {$t = Query.Type.COUNT;}) + (version {$v = $version.v;})? ( ( role {$r = $role.r;} @@ -56,6 +58,14 @@ cq returns [Query.Type t, List<Query.Selection> s, Query.Pattern e, Query.Role r EOF ; +version returns [VersionFilter v] + @init{ + $v = null; + } +: + ANY_VERSION_OF {$v = VersionFilter.ANY_VERSION;} +; + prop_sel returns [List<Query.Selection> s] @init{ $s = new LinkedList<Query.Selection>(); diff --git a/src/main/java/org/caosdb/server/query/Conjunction.java b/src/main/java/org/caosdb/server/query/Conjunction.java index a8fd9cd16296dfdf846bd81d2c25676537cd590c..4e2bc069fb7cb6872e5c21eec6f5823b178c9adc 100644 --- a/src/main/java/org/caosdb/server/query/Conjunction.java +++ b/src/main/java/org/caosdb/server/query/Conjunction.java @@ -23,7 +23,6 @@ package org.caosdb.server.query; import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; - import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; diff --git a/src/main/java/org/caosdb/server/query/EntityFilterContainer.java b/src/main/java/org/caosdb/server/query/EntityFilterContainer.java index c2409f90ecc43cfb318a17cc8a9f3ba6df60beb0..a9e26887ea87534022e96bc19b6ab42e6b2c8677 100644 --- a/src/main/java/org/caosdb/server/query/EntityFilterContainer.java +++ b/src/main/java/org/caosdb/server/query/EntityFilterContainer.java @@ -38,4 +38,9 @@ public abstract class EntityFilterContainer implements EntityFilterInterface { public void addAll(final EntityFilterContainer filter) { getFilters().addAll(filter.getFilters()); } + + public boolean isVersioned() { + // TODO stub + return false; + } } diff --git a/src/main/java/org/caosdb/server/query/IDFilter.java b/src/main/java/org/caosdb/server/query/IDFilter.java index 26054bb16f0eb39a581a4fa0da6c62a67c6fc385..1a4f3650cc7b29078b268f69b1ae4c3db726de24 100644 --- a/src/main/java/org/caosdb/server/query/IDFilter.java +++ b/src/main/java/org/caosdb/server/query/IDFilter.java @@ -25,7 +25,6 @@ package org.caosdb.server.query; import static java.sql.Types.CHAR; import static java.sql.Types.INTEGER; import static java.sql.Types.VARCHAR; - import java.sql.CallableStatement; import java.sql.Connection; import java.sql.SQLException; @@ -59,7 +58,7 @@ public class IDFilter implements EntityFilterInterface { final Connection connection = query.getConnection(); // applyIDFilter(sourceSet, targetSet, o, vInt, agg) final CallableStatement callIDFilter = - connection.prepareCall("call applyIDFilter(?,?,?,?,?)"); + connection.prepareCall("call applyIDFilter(?,?,?,?,?,?)"); callIDFilter.setString(1, query.getSourceSet()); // sourceSet if (query.getTargetSet() != null) { // targetSet @@ -89,6 +88,8 @@ public class IDFilter implements EntityFilterInterface { callIDFilter.setString(5, getAggregate()); } + // versioning + callIDFilter.setBoolean(6, query.isVersioned()); callIDFilter.execute(); callIDFilter.close(); } catch (final SQLException e) { diff --git a/src/main/java/org/caosdb/server/query/Negation.java b/src/main/java/org/caosdb/server/query/Negation.java index d4243580bf52511d05699c7c80e69015ea2a0dac..4fd7735500427e77a4d6e1b928a784745e7c9370 100644 --- a/src/main/java/org/caosdb/server/query/Negation.java +++ b/src/main/java/org/caosdb/server/query/Negation.java @@ -23,7 +23,6 @@ package org.caosdb.server.query; import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; - import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; @@ -167,4 +166,10 @@ public class Negation implements EntityFilterInterface, QueryInterface { public void addBenchmark(final String str, final long time) { this.query.addBenchmark(this.getClass().getSimpleName() + "." + str, time); } + + @Override + public boolean isVersioned() { + // TODO Auto-generated method stub + return false; + } } diff --git a/src/main/java/org/caosdb/server/query/POV.java b/src/main/java/org/caosdb/server/query/POV.java index 03b603d0df70d1159c1d78f1626e23faab40e033..0d6e0e64f49c48f437e11b1313c10d4809a2cc1d 100644 --- a/src/main/java/org/caosdb/server/query/POV.java +++ b/src/main/java/org/caosdb/server/query/POV.java @@ -26,8 +26,6 @@ import static java.sql.Types.DOUBLE; import static java.sql.Types.INTEGER; import static java.sql.Types.VARCHAR; import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; - -import de.timmfitschen.easyunits.parser.ParserException; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; @@ -49,6 +47,7 @@ import org.caosdb.unit.Unit; import org.jdom2.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import de.timmfitschen.easyunits.parser.ParserException; public class POV implements EntityFilterInterface { private SubProperty subp = null; @@ -226,9 +225,9 @@ public class POV implements EntityFilterInterface { // applyPOV(sourceSet, targetSet, propertiesTable, refIdsTable, o, // vText, vInt, // vDouble, - // vDatetime, vDateTimeDotNotation, agg, pname) + // vDatetime, vDateTimeDotNotation, agg, pname, versioned) final CallableStatement callPOV = - this.connection.prepareCall("call applyPOV(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); + this.connection.prepareCall("call applyPOV(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); callPOV.setString(1, query.getSourceSet()); // sourceSet this.statistics.put("sourceSet", query.getSourceSet()); this.statistics.put( @@ -323,6 +322,7 @@ public class POV implements EntityFilterInterface { } else { callPOV.setNull(15, VARCHAR); } + callPOV.setBoolean(16, query.isVersioned()); prefix.add("#executeStmt"); executeStmt(callPOV, query); prefix.pop(); diff --git a/src/main/java/org/caosdb/server/query/Query.java b/src/main/java/org/caosdb/server/query/Query.java index c3873cf3b4ba2322373dde13f4dfd72e6a94c7e4..2425f874c772f3568b83ea76e0c3d35e63d599ab 100644 --- a/src/main/java/org/caosdb/server/query/Query.java +++ b/src/main/java/org/caosdb/server/query/Query.java @@ -23,7 +23,6 @@ package org.caosdb.server.query; import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; - import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; @@ -168,13 +167,22 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac super(t); } } + + public static class IdVersionPair { + public IdVersionPair(Integer id, String version) { + this.id = id; + this.version = version; + } + public Integer id; + public String version; + } private static boolean filterEntitiesWithoutRetrievePermisions = !CaosDBServer.getServerProperty( ServerProperties.KEY_QUERY_FILTER_ENTITIES_WITHOUT_RETRIEVE_PERMISSIONS) .equalsIgnoreCase("FALSE"); - List<Integer> resultSet = null; + List<IdVersionPair> resultSet = null; private final String query; private Pattern entity = null; private Role role = null; @@ -189,6 +197,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac private Type type = null; private final ArrayList<ToElementable> messages = new ArrayList<>(); private Access access; + private boolean versioned = false; public Type getType() { return this.type; @@ -216,7 +225,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac */ private void initResultSetWithNameIDAndChildren() throws SQLException { final CallableStatement callInitEntity = - getConnection().prepareCall("call initEntity(?,?,?,?,?)"); + getConnection().prepareCall("call initEntity(?,?,?,?,?,?)"); try { callInitEntity.setInt(1, Integer.parseInt(this.entity.toString())); @@ -243,6 +252,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac break; } callInitEntity.setString(5, this.sourceSet); + callInitEntity.setBoolean(6, this.versioned); callInitEntity.execute(); callInitEntity.close(); } @@ -261,7 +271,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac } if (this.role != null) { - final RoleFilter roleFilter = new RoleFilter(this.role, "="); + final RoleFilter roleFilter = new RoleFilter(this.role, "=", this.versioned); roleFilter.apply(this); } @@ -312,7 +322,8 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac final Query subQuery = new Query(q.getValue(), query.getUser()); subQuery.setAccess(query.getAccess()); subQuery.parse(); - final String subResultSet = subQuery.executeStrategy(); + // TODO versioning is set to false here (hard) it this the best way to do it? + final String subResultSet = subQuery.executeStrategy(false); // ... and merge the resultSets. union(query, resultSet, subResultSet); @@ -384,7 +395,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac // filter by role if (this.role != null && this.role != Role.ENTITY) { - final RoleFilter roleFilter = new RoleFilter(this.role, "="); + final RoleFilter roleFilter = new RoleFilter(this.role, "=", this.versioned); roleFilter.apply(this); } @@ -402,8 +413,9 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac } } - private String initQuery() throws QueryException { - try (final CallableStatement callInitQuery = getConnection().prepareCall("call initQuery()")) { + private String initQuery(boolean versioned) throws QueryException { + String sql = versioned ? "call initVersionedQuery()" : "call initQuery()"; + try (final CallableStatement callInitQuery = getConnection().prepareCall(sql)) { ResultSet initQueryResult = null; initQueryResult = callInitQuery.executeQuery(); if (!initQueryResult.next()) { @@ -431,6 +443,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac } this.entity = cq.e; + this.versioned = cq.v != VersionFilter.UNVERSIONED; this.role = cq.r; this.parseTree = cq.toStringTree(parser); this.type = cq.t; @@ -442,23 +455,25 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac } } - private String executeStrategy() throws QueryException { + private String executeStrategy(boolean versioned) throws QueryException { if (this.entity != null) { - return sourceStrategy(initQuery()); + return sourceStrategy(initQuery(versioned)); } else { - return targetStrategy(initQuery()); + return targetStrategy(initQuery(versioned)); } } - private LinkedList<Integer> getResultSet(final String resultSetTableName) throws QueryException { + private List<IdVersionPair> getResultSet(final String resultSetTableName, boolean versioned) throws QueryException { ResultSet finishResultSet = null; try { + final String sql = "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)": ""); final PreparedStatement finish = - getConnection().prepareStatement("Select id from `" + resultSetTableName + "`"); + getConnection().prepareStatement(sql); finishResultSet = finish.executeQuery(); - final LinkedList<Integer> rs = new LinkedList<Integer>(); + final List<IdVersionPair> rs = new LinkedList<>(); while (finishResultSet.next()) { - rs.add(finishResultSet.getInt("id")); + final String version = versioned ? finishResultSet.getString("version"): null; + rs.add(new IdVersionPair(finishResultSet.getInt("id"), version)); } return rs; } catch (final SQLException e) { @@ -480,15 +495,15 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac try { - this.resultSet = getResultSet(executeStrategy()); + this.resultSet = getResultSet(executeStrategy(this.versioned), this.versioned); filterEntitiesWithoutRetrievePermission(this.resultSet); // Fill resulting entities into container if (this.container != null && this.type == Type.FIND) { - for (final int id : this.resultSet) { + for (final IdVersionPair p : this.resultSet) { - final Entity e = new RetrieveEntity(id); + final Entity e = new RetrieveEntity(p.id, p.version); // if query has select-clause: if (this.selections != null && !this.selections.isEmpty()) { @@ -579,16 +594,16 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac * @param entities * @throws TransactionException */ - private void filterEntitiesWithoutRetrievePermission(final List<Integer> entities) + private void filterEntitiesWithoutRetrievePermission(final List<IdVersionPair> entities) throws TransactionException { if (!filterEntitiesWithoutRetrievePermisions) { return; } - final Iterator<Integer> iterator = entities.iterator(); + final Iterator<IdVersionPair> iterator = entities.iterator(); while (iterator.hasNext()) { final long t1 = System.currentTimeMillis(); - final Integer id = iterator.next(); - if (!execute(new RetrieveSparseEntity(id, null), getAccess()) + final IdVersionPair next = iterator.next(); + if (!execute(new RetrieveSparseEntity(next.id, next.version), getAccess()) .getEntity() .getEntityACL() .isPermitted(getUser(), EntityPermission.RETRIEVE_ENTITY)) { @@ -604,10 +619,6 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac return this.query; } - public List<Integer> getResultSet() { - return this.resultSet; - } - @Override public String getSourceSet() { return this.sourceSet; @@ -742,4 +753,9 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac } return benchmark; } + + @Override + public boolean isVersioned() { + return this.versioned; + } } diff --git a/src/main/java/org/caosdb/server/query/QueryInterface.java b/src/main/java/org/caosdb/server/query/QueryInterface.java index 32cc1c7d8d98b56f80b159fc3f0004a491773563..0f2db9a97094ddc26062a9c9922907c183a26413 100644 --- a/src/main/java/org/caosdb/server/query/QueryInterface.java +++ b/src/main/java/org/caosdb/server/query/QueryInterface.java @@ -43,4 +43,6 @@ public interface QueryInterface { public Subject getUser(); public void addBenchmark(final String str, final long time); + + public boolean isVersioned(); } diff --git a/src/main/java/org/caosdb/server/query/RoleFilter.java b/src/main/java/org/caosdb/server/query/RoleFilter.java index 9055dccb358dd94ec17dee8b26bac97d2170b016..b51c55e2823218e81656c2ddb082187cb98df6dc 100644 --- a/src/main/java/org/caosdb/server/query/RoleFilter.java +++ b/src/main/java/org/caosdb/server/query/RoleFilter.java @@ -33,6 +33,7 @@ public class RoleFilter implements EntityFilterInterface { private final Role role; private final String operator; + private boolean versioned; /** * Guarantees that all entities in the result set do have ("=") or do not have ("!=") the role in @@ -43,7 +44,8 @@ public class RoleFilter implements EntityFilterInterface { * @throws NullPointerException If role or operator is null. * @throws IllegalArgumentException If operator is not "=" or "!=". */ - public RoleFilter(final Role role, final String operator) { + public RoleFilter(final Role role, final String operator, final boolean versioned) { + this.versioned = versioned; if (role == null) { throw new NullPointerException("The role must not be null."); } @@ -72,7 +74,8 @@ public class RoleFilter implements EntityFilterInterface { getOperator(), getRole(), query.getSourceSet(), - query.getTargetSet()); + query.getTargetSet(), + this.versioned); } } catch (final SQLException e) { throw new QueryException(e); @@ -85,20 +88,38 @@ public class RoleFilter implements EntityFilterInterface { final String operator, final String role, final String sourceSet, - final String targetSet) + final String targetSet, + final boolean versioned) throws SQLException { - final PreparedStatement filterRoleStmt = - connection.prepareCall( - "INSERT IGNORE INTO `" + final String sql = ("INSERT IGNORE INTO `" + targetSet - + "` (id) SELECT id FROM `" - + sourceSet + (sourceSet.equals("entities") - ? "` AS s WHERE EXISTS (SELECT * FROM entities AS e WHERE e.id=s.id AND e.role" - : "` AS e WHERE NOT (e.role") - + operator - + "?);"); - filterRoleStmt.setString(1, role); + ? (versioned + ? "` (id, _iversion) SELECT e.id, _get_head_iversion(e.id) FROM `entities` AS e WHERE e.role " + + operator + + " ? UNION SELECT a.id, a._iversion FROM `archive_entities` AS a WHERE a.role" + + operator + "?" + : "` (id) SELECT e.id FROM `entities` AS e WHERE e.role" + + operator + + "?") + : (versioned + ? "` (id, _iversion) SELECT s.id, s._iversion FROM `" + + sourceSet + + "` LEFT JOIN archive_entities AS a ON (s.id = a.id AND s._iversion = a._iversion) WHERE a.role" + + operator + + "? UNION SELECT s.id, s._iversion FROM `" + + sourceSet + + "` LEFT JOIN entities AS e ON (s.id = e.id) WHERE e.role" + + operator + + "? AND a._iversion = _get_head_iversion(s.id)" + : "` (id) SELECT e.id FROM `entities` AS e WHERE e.role = ?"))); + final PreparedStatement filterRoleStmt = + connection.prepareCall(sql); + int params = (versioned ? 2: 1); + while(params>0) { + filterRoleStmt.setString(params, role); + params--; + } filterRoleStmt.execute(); } @@ -109,7 +130,7 @@ public class RoleFilter implements EntityFilterInterface { connection.prepareCall( "DELETE FROM `" + sourceSet - + "` WHERE EXISTS (SELECT * FROM entities AS e WHERE e.id=`" + + "` WHERE EXISTS (SELECT 1 FROM entities AS e WHERE e.id=`" + sourceSet + "`.id AND NOT e.role" + operator diff --git a/src/main/java/org/caosdb/server/query/SubProperty.java b/src/main/java/org/caosdb/server/query/SubProperty.java index f8c22367c70f623903a3ee2f14a0af3cbbefd128..5e3dc8c36e304d8ee776ffe638c55d24e46d0b3d 100644 --- a/src/main/java/org/caosdb/server/query/SubProperty.java +++ b/src/main/java/org/caosdb/server/query/SubProperty.java @@ -24,7 +24,6 @@ package org.caosdb.server.query; import static java.sql.Types.VARCHAR; import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; - import java.sql.CallableStatement; import java.sql.Connection; import java.sql.ResultSet; @@ -165,4 +164,10 @@ public class SubProperty implements QueryInterface, EntityFilterInterface { public void addBenchmark(final String str, final long time) { this.query.addBenchmark(this.getClass().getSimpleName() + "." + str, time); } + + @Override + public boolean isVersioned() { + // TODO Auto-generated method stub + return false; + } } diff --git a/src/main/java/org/caosdb/server/query/VersionFilter.java b/src/main/java/org/caosdb/server/query/VersionFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..1ec31980d724d0c719b8bf39ef94d24c352ca758 --- /dev/null +++ b/src/main/java/org/caosdb/server/query/VersionFilter.java @@ -0,0 +1,8 @@ +package org.caosdb.server.query; + +public class VersionFilter { + + public static final VersionFilter ANY_VERSION = new VersionFilter(); + public static final VersionFilter UNVERSIONED = new VersionFilter(); + +} diff --git a/src/test/java/org/caosdb/server/query/TestCQL.java b/src/test/java/org/caosdb/server/query/TestCQL.java index acd4946d826b7b0ed00c396c71dd84384e4668a4..a6c181b34e83eb6f79c2c0cb3b8dbab0fdb6b82a 100644 --- a/src/test/java/org/caosdb/server/query/TestCQL.java +++ b/src/test/java/org/caosdb/server/query/TestCQL.java @@ -28,7 +28,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; - import java.io.IOException; import java.sql.SQLException; import java.util.LinkedList; @@ -242,6 +241,8 @@ public class TestCQL { String emptyTextValue = "FIND ENTITY WITH prop=''"; String queryMR56 = "FIND ENTITY WITH ((p0 = v0 OR p1=v1) AND p2=v2)"; + + String versionedQuery1 = "FIND ANY VERSION OF ENTITY e1"; @Test public void testQuery1() @@ -6450,4 +6451,26 @@ public class TestCQL { final ParseTree pov = conjunction.getChild(4); assertEquals("p2=v2", pov.getText()); } + + /** + * String versionedQuery1 = "FIND ANY VERSION OF ENTITY e1"; + */ + @Test + public void testVersionedQuery1() { + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.versionedQuery1)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + final CqContext sfq = parser.cq(); + + System.out.println(sfq.toStringTree(parser)); + + // 4 children: FIND, version, role, entity, EOF + assertEquals(5, sfq.getChildCount()); + assertEquals(VersionFilter.ANY_VERSION, sfq.v); + assertEquals(Query.Role.ENTITY, sfq.r); + assertEquals("e1", sfq.e.toString()); + + } }