diff --git a/CHANGELOG.md b/CHANGELOG.md index b6ceac5e4ba32636ef076b018e6a33c150ed76a9..13a20975fb3cea4ebe6a120188e7999e5b59ee25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 by setting the server property `EXT_ENTITY_STATE=ENABLED`. See [!62](https://gitlab.com/caosdb/caosdb-server/-/merge_requests/62) for more information. +* New query functionality: `ANY VERSION OF` modifier. E.g. `FIND ANY VERSION OF + RECORD WITH pname=val` returns all current and old versions of records where + `pname=val`. For further information, examples and limitations see the wiki + page on [CQL](https://gitlab.com/caosdb/caosdb/-/wikis/manuals/CQL/CaosDB%20Query%20Language) * New server property `SERVER_SIDE_SCRIPTING_BIN_DIRS` which accepts a comma or space separated list as values. The server looks for scripts in all directories in the order or the list and uses the first matching file. diff --git a/conf/core/server.conf b/conf/core/server.conf index 1eb96c7bf2dd2a5a216c81222e548a6155636091..f831f074d1904b53b481f1ad9496c6093f7a8eed 100644 --- a/conf/core/server.conf +++ b/conf/core/server.conf @@ -68,7 +68,7 @@ MYSQL_USER_NAME=caosdb # Password for the user MYSQL_USER_PASSWORD=caosdb # Schema of mysql procedures and tables which is required by this CaosDB instance -MYSQL_SCHEMA_VERSION=v3.0.0-rc2 +MYSQL_SCHEMA_VERSION=v4.0.0-rc1 # -------------------------------------------------- diff --git a/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java index 39f4d6754ed665a0590c7a02f1e1882b8a907b64..83ec96f77db4515369fc0d3bce0f5d8146d9649e 100644 --- a/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java +++ b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java @@ -18,6 +18,7 @@ import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.Message; import org.caosdb.server.entity.Message.MessageType; import org.caosdb.server.entity.StatementStatus; +import org.caosdb.server.entity.container.TransactionContainer; import org.caosdb.server.entity.wrapper.Property; import org.caosdb.server.entity.xml.ToElementable; import org.caosdb.server.jobs.EntityJob; @@ -583,6 +584,7 @@ public abstract class EntityStateJob extends EntityJob { EntityInterface findStateModel(EntityInterface stateEntity) throws Exception { // TODO this should be cached + TransactionContainer c = new TransactionContainer(); Query query = new Query( "FIND RECORD " @@ -592,8 +594,8 @@ public abstract class EntityStateJob extends EntityJob { + " WHICH REFERENCES " + Integer.toString(stateEntity.getId()), getUser(), - null); + c); query.execute(getTransaction().getAccess()); - return retrieveValidEntity(query.getResultSet().get(0)); + return retrieveValidEntity(c.get(0).getId()); } } diff --git a/src/main/java/org/caosdb/server/query/Backreference.java b/src/main/java/org/caosdb/server/query/Backreference.java index 5f8105c823a231a14853e1db370649c7a7189649..684beef2e016203f3e6f64fd66ccefb750d11981 100644 --- a/src/main/java/org/caosdb/server/query/Backreference.java +++ b/src/main/java/org/caosdb/server/query/Backreference.java @@ -103,9 +103,12 @@ public class Backreference implements EntityFilterInterface, QueryInterface { return "@(" + getEntity() + "," + getProperty() + ")"; } - /** */ @Override public void apply(final QueryInterface query) throws QueryException { + if (query.isVersioned() && hasSubProperty()) { + throw new UnsupportedOperationException( + "Versioned queries are not supported for subqueries yet. Please file a feature request."); + } final long t1 = System.currentTimeMillis(); this.query = query; this.targetSet = query.getTargetSet(); @@ -113,7 +116,7 @@ public class Backreference implements EntityFilterInterface, QueryInterface { initBackRef(query); final CallableStatement callApplyBackRef = - getConnection().prepareCall("call applyBackReference(?,?,?,?,?)"); + getConnection().prepareCall("call applyBackReference(?,?,?,?,?,?)"); callApplyBackRef.setString(1, getSourceSet()); // sourceSet this.statistics.put("sourceSet", getSourceSet()); this.statistics.put( @@ -145,6 +148,7 @@ public class Backreference implements EntityFilterInterface, QueryInterface { callApplyBackRef.setNull(4, VARCHAR); } callApplyBackRef.setBoolean(5, hasSubProperty()); // subQuery? + callApplyBackRef.setBoolean(6, this.isVersioned()); executeStmt(query, callApplyBackRef); callApplyBackRef.close(); @@ -210,11 +214,10 @@ public class Backreference implements EntityFilterInterface, QueryInterface { this.statistics.put( "subPropertySourceSetCount", Utils.countTable(query.getConnection(), this.sourceSet)); this.targetSet = null; - getSubProperty().getFilter().apply(this); final long t3 = System.currentTimeMillis(); try (final PreparedStatement callFinishSubProperty = - getConnection().prepareCall("call finishSubProperty(?,?,?)")) { + getConnection().prepareCall("call finishSubProperty(?,?,?,?)")) { callFinishSubProperty.setString(1, query.getSourceSet()); // sourceSet if (query.getTargetSet() != null) { // targetSet callFinishSubProperty.setString(2, query.getTargetSet()); @@ -222,6 +225,7 @@ public class Backreference implements EntityFilterInterface, QueryInterface { callFinishSubProperty.setNull(2, VARCHAR); } callFinishSubProperty.setString(3, this.sourceSet); // list + callFinishSubProperty.setBoolean(4, this.isVersioned()); final ResultSet rs2 = callFinishSubProperty.executeQuery(); rs2.next(); this.statistics.put("finishSubPropertyStmt", rs2.getString("finishSubPropertyStmt")); @@ -320,4 +324,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 this.query.isVersioned(); + } } 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..2e44aea4f64a43ebb2a1c1edb153c6f68c172f8e 100644 --- a/src/main/java/org/caosdb/server/query/Conjunction.java +++ b/src/main/java/org/caosdb/server/query/Conjunction.java @@ -63,8 +63,9 @@ public class Conjunction extends EntityFilterContainer implements QueryInterface // generate empty temporary targetSet if query.getTargetSet() is not // empty anyways. final PreparedStatement callInitEmptyTarget = - getConnection().prepareStatement("call initEmptyTargetSet(?)"); + getConnection().prepareStatement("call initEmptyTargetSet(?,?)"); callInitEmptyTarget.setString(1, query.getTargetSet()); + callInitEmptyTarget.setBoolean(2, query.isVersioned()); final ResultSet initEmptyTargetResultSet = callInitEmptyTarget.executeQuery(); if (initEmptyTargetResultSet.next()) { this.targetSet = bytes2UTF8(initEmptyTargetResultSet.getBytes("newTableName")); @@ -149,4 +150,9 @@ public class Conjunction extends EntityFilterContainer implements QueryInterface public void addBenchmark(final String str, final long time) { this.query.addBenchmark(this.getClass().getSimpleName() + "." + str, time); } + + @Override + public boolean isVersioned() { + return this.query.isVersioned(); + } } diff --git a/src/main/java/org/caosdb/server/query/Disjunction.java b/src/main/java/org/caosdb/server/query/Disjunction.java index 3bd2f32fdd3bfb64ad2a07c527b91136e965c22b..31288f2f169d4ebce1cf3d4da5b2ab369fcd5e1f 100644 --- a/src/main/java/org/caosdb/server/query/Disjunction.java +++ b/src/main/java/org/caosdb/server/query/Disjunction.java @@ -51,7 +51,8 @@ public class Disjunction extends EntityFilterContainer implements QueryInterface // targetTable // which will be used to collect the entities. final PreparedStatement callInitDisjunctionFilter = - getConnection().prepareStatement("call initDisjunctionFilter()"); + getConnection().prepareStatement("call initDisjunctionFilter(?)"); + callInitDisjunctionFilter.setBoolean(1, this.isVersioned()); final ResultSet initDisjuntionFilterResultSet = callInitDisjunctionFilter.executeQuery(); if (initDisjuntionFilterResultSet.next()) { this.targetSet = bytes2UTF8(initDisjuntionFilterResultSet.getBytes("newTableName")); @@ -69,11 +70,12 @@ public class Disjunction extends EntityFilterContainer implements QueryInterface if (query.getTargetSet() == null) { // calculate the difference and store to sourceSet - final CallableStatement callFinishNegationFilter = - getConnection().prepareCall("call calcIntersection(?,?)"); - callFinishNegationFilter.setString(1, getSourceSet()); - callFinishNegationFilter.setString(2, this.targetSet); - callFinishNegationFilter.execute(); + final CallableStatement callFinishDisjunctionFilter = + getConnection().prepareCall("call calcIntersection(?,?,?)"); + callFinishDisjunctionFilter.setString(1, getSourceSet()); + callFinishDisjunctionFilter.setString(2, this.targetSet); + callFinishDisjunctionFilter.setBoolean(3, this.isVersioned()); + callFinishDisjunctionFilter.execute(); } // ELSE: the query.getTargetSet() is identical to targetSet and is // not @@ -136,4 +138,9 @@ public class Disjunction extends EntityFilterContainer implements QueryInterface public void addBenchmark(final String str, final long time) { this.query.addBenchmark(this.getClass().getSimpleName() + "." + str, time); } + + @Override + public boolean isVersioned() { + return this.query.isVersioned(); + } } diff --git a/src/main/java/org/caosdb/server/query/IDFilter.java b/src/main/java/org/caosdb/server/query/IDFilter.java index 26054bb16f0eb39a581a4fa0da6c62a67c6fc385..12f53b44472d5b897c3d8b2a065388969c1edcbc 100644 --- a/src/main/java/org/caosdb/server/query/IDFilter.java +++ b/src/main/java/org/caosdb/server/query/IDFilter.java @@ -59,7 +59,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 +89,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..981b4c8f2ca19a1a6ec47df9e85f108ab7bb01ac 100644 --- a/src/main/java/org/caosdb/server/query/Negation.java +++ b/src/main/java/org/caosdb/server/query/Negation.java @@ -90,8 +90,8 @@ public class Negation implements EntityFilterInterface, QueryInterface { // generate empty temporary targetSet if query.getTargetSet() is not // empty anyways. final PreparedStatement callInitEmptyTarget = - getConnection().prepareStatement("call initEmptyTargetSet(?)"); - callInitEmptyTarget.setString(1, query.getTargetSet()); + getConnection().prepareStatement("call initDisjunctionFilter(?)"); + callInitEmptyTarget.setBoolean(1, query.isVersioned()); final ResultSet initEmptyTargetResultSet = callInitEmptyTarget.executeQuery(); if (initEmptyTargetResultSet.next()) { this.targetSet = bytes2UTF8(initEmptyTargetResultSet.getBytes("newTableName")); @@ -100,19 +100,22 @@ public class Negation implements EntityFilterInterface, QueryInterface { this.filter.apply(this); - if (query.getTargetSet() != null && !this.targetSet.equalsIgnoreCase(query.getTargetSet())) { - // merge temporary targetSet with query.getTargetSet() + if (query.getTargetSet() == null) { + // intersect temporary targetSet with query.getSourceSet() final CallableStatement callFinishConjunctionFilter = - query.getConnection().prepareCall("call calcUnion(?,?)"); - callFinishConjunctionFilter.setString(1, query.getTargetSet()); + query.getConnection().prepareCall("call calcDifference(?,?,?)"); + callFinishConjunctionFilter.setString(1, query.getSourceSet()); callFinishConjunctionFilter.setString(2, this.targetSet); + callFinishConjunctionFilter.setBoolean(3, this.isVersioned()); callFinishConjunctionFilter.execute(); - } else if (query.getTargetSet() == null) { - // intersect temporary targetSet with query.getSourceSet() + } else { + // merge temporary targetSet with query.getTargetSet() final CallableStatement callFinishConjunctionFilter = - query.getConnection().prepareCall("call calcDifference(?,?)"); - callFinishConjunctionFilter.setString(1, query.getSourceSet()); + query.getConnection().prepareCall("call calcComplementUnion(?,?,?,?)"); + callFinishConjunctionFilter.setString(1, query.getTargetSet()); callFinishConjunctionFilter.setString(2, this.targetSet); + callFinishConjunctionFilter.setString(3, query.getSourceSet()); + callFinishConjunctionFilter.setBoolean(4, this.isVersioned()); callFinishConjunctionFilter.execute(); } } catch (final SQLException e) { @@ -167,4 +170,9 @@ 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() { + return this.query.isVersioned(); + } } diff --git a/src/main/java/org/caosdb/server/query/POV.java b/src/main/java/org/caosdb/server/query/POV.java index 03b603d0df70d1159c1d78f1626e23faab40e033..fcc719e6ff24299d0b5a62a241442e08fa33962f 100644 --- a/src/main/java/org/caosdb/server/query/POV.java +++ b/src/main/java/org/caosdb/server/query/POV.java @@ -155,7 +155,7 @@ public class POV implements EntityFilterInterface { this.unit = getUnit(unitStr); } catch (final ParserException e) { e.printStackTrace(); - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Could not parse the unit."); } this.stdUnitSig = this.unit.normalize().getSignature(); @@ -214,6 +214,10 @@ public class POV implements EntityFilterInterface { @Override public void apply(final QueryInterface query) throws QueryException { + if (query.isVersioned() && hasSubProperty()) { + throw new UnsupportedOperationException( + "Versioned queries are not supported for subqueries yet. Please file a feature request."); + } final long t1 = System.currentTimeMillis(); try { this.connection = query.getConnection(); @@ -226,9 +230,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( @@ -313,6 +317,10 @@ public class POV implements EntityFilterInterface { } if (getAggregate() != null) { // agg + if (query.isVersioned()) { + throw new UnsupportedOperationException( + "Versioned queries are not supported for aggregate functions like GREATES or SMALLEST in the filters."); + } callPOV.setString(14, getAggregate()); } else { callPOV.setNull(14, VARCHAR); @@ -323,6 +331,7 @@ public class POV implements EntityFilterInterface { } else { callPOV.setNull(15, VARCHAR); } + callPOV.setBoolean(16, query.isVersioned()); prefix.add("#executeStmt"); executeStmt(callPOV, query); prefix.pop(); @@ -420,7 +429,9 @@ public class POV implements EntityFilterInterface { if (hasSubProperty() && this.targetSet != null) { try (PreparedStatement stmt = - query.getConnection().prepareStatement("call initEmptyTargetSet(NULL)")) { + query.getConnection().prepareStatement("call initEmptyTargetSet(?, ?)")) { + stmt.setNull(1, VARCHAR); + stmt.setBoolean(2, query.isVersioned()); // generate new targetSet final ResultSet rs = stmt.executeQuery(); if (rs.next()) { diff --git a/src/main/java/org/caosdb/server/query/Query.java b/src/main/java/org/caosdb/server/query/Query.java index c3873cf3b4ba2322373dde13f4dfd72e6a94c7e4..695a295da5f64ec788c4dfbb3332734ead44b289 100644 --- a/src/main/java/org/caosdb/server/query/Query.java +++ b/src/main/java/org/caosdb/server/query/Query.java @@ -169,12 +169,22 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac } } + 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 +199,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 +227,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 +254,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac break; } callInitEntity.setString(5, this.sourceSet); + callInitEntity.setBoolean(6, this.versioned); callInitEntity.execute(); callInitEntity.close(); } @@ -260,8 +272,8 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac applyQueryTemplates(this, getSourceSet()); } - if (this.role != null) { - final RoleFilter roleFilter = new RoleFilter(this.role, "="); + if (this.role != null && this.role != Role.ENTITY) { + final RoleFilter roleFilter = new RoleFilter(this.role, "=", this.versioned); roleFilter.apply(this); } @@ -312,7 +324,9 @@ 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(); + + // versioning for QueryTemplates is not supported and probably never will. + final String subResultSet = subQuery.executeStrategy(false); // ... and merge the resultSets. union(query, resultSet, subResultSet); @@ -384,7 +398,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 +416,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 = "call initQuery(" + versioned + ")"; + try (final CallableStatement callInitQuery = getConnection().prepareCall(sql)) { ResultSet initQueryResult = null; initQueryResult = callInitQuery.executeQuery(); if (!initQueryResult.next()) { @@ -431,6 +446,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 +458,33 @@ 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 PreparedStatement finish = - getConnection().prepareStatement("Select id from `" + resultSetTableName + "`"); + 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(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 +506,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 +605,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 +630,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 +764,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..48c8372014c6e702be8b88113102da030dbc6046 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,28 @@ 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 `" - + 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); + if (!sourceSet.equals("entities")) { + throw new UnsupportedOperationException("SourceSet is supposed to be the `entities` table."); + } + final String sql = + ("INSERT IGNORE INTO `" + + targetSet + + (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 + "?")); + final PreparedStatement filterRoleStmt = connection.prepareCall(sql); + int params = (versioned ? 2 : 1); + while (params > 0) { + filterRoleStmt.setString(params, role); + params--; + } filterRoleStmt.execute(); } @@ -109,7 +120,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..4668935e56f0057d3d0fe927aa0a729669e7e0f6 100644 --- a/src/main/java/org/caosdb/server/query/SubProperty.java +++ b/src/main/java/org/caosdb/server/query/SubProperty.java @@ -90,18 +90,15 @@ public class SubProperty implements QueryInterface, EntityFilterInterface { getQuery().filterEntitiesWithoutRetrievePermission(this, this.sourceSet); final CallableStatement callFinishSubProperty = - getConnection().prepareCall("call finishSubProperty(?,?,?)"); - callFinishSubProperty.setString(1, query.getSourceSet()); // sourceSet - // of - // parent - // query + getConnection().prepareCall("call finishSubProperty(?,?,?,?)"); + callFinishSubProperty.setString(1, query.getSourceSet()); // sourceSet of parent query if (query.getTargetSet() != null) { // targetSet callFinishSubProperty.setString(2, query.getTargetSet()); } else { callFinishSubProperty.setNull(2, VARCHAR); } - callFinishSubProperty.setString(3, this.sourceSet); // sub query - // sourceSet + callFinishSubProperty.setString(3, this.sourceSet); // sub query sourceSet + callFinishSubProperty.setBoolean(4, this.isVersioned()); callFinishSubProperty.execute(); callFinishSubProperty.close(); } else { @@ -165,4 +162,9 @@ 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() { + return this.query.isVersioned(); + } } 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..aa391778ce20c089a9fa6dae88caf77e0f797703 --- /dev/null +++ b/src/main/java/org/caosdb/server/query/VersionFilter.java @@ -0,0 +1,7 @@ +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..2f6651cc81a2614b63f1314f35052fe2096758b0 100644 --- a/src/test/java/org/caosdb/server/query/TestCQL.java +++ b/src/test/java/org/caosdb/server/query/TestCQL.java @@ -243,6 +243,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() throws InterruptedException, SQLException, ConnectionException, QueryException { @@ -6450,4 +6452,23 @@ 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()); + } }