/* * Copyright 2009-2016 Tilmann Zaeschke. All rights reserved. * * This file is part of ZooDB. * * ZooDB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ZooDB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ZooDB. If not, see <http://www.gnu.org/licenses/>. * * See the README and COPYING files for further information. */ package org.zoodb.jdo.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.logging.Level; import javax.jdo.Extent; import javax.jdo.FetchPlan; import javax.jdo.JDOUserException; import javax.jdo.ObjectState; import javax.jdo.PersistenceManager; import javax.jdo.Query; import org.zoodb.api.impl.ZooPC; import org.zoodb.internal.Node; import org.zoodb.internal.ZooClassDef; import org.zoodb.internal.ZooClassProxy; import org.zoodb.internal.ZooFieldDef; import org.zoodb.internal.client.session.ClientSessionCache; import org.zoodb.internal.query.QueryAdvice; import org.zoodb.internal.query.QueryComparator; import org.zoodb.internal.query.QueryMergingIterator; import org.zoodb.internal.query.QueryOptimizer; import org.zoodb.internal.query.QueryParameter; import org.zoodb.internal.query.QueryParameter.DECLARATION; import org.zoodb.internal.query.QueryParser; import org.zoodb.internal.query.QueryParserV3; import org.zoodb.internal.query.QueryTerm; import org.zoodb.internal.query.QueryTreeIterator; import org.zoodb.internal.query.QueryTreeNode; import org.zoodb.internal.query.TypeConverterTools; import org.zoodb.internal.util.CloseableIterator; import org.zoodb.internal.util.DBLogger; import org.zoodb.internal.util.ObjectIdentitySet; import org.zoodb.internal.util.Pair; import org.zoodb.internal.util.SynchronizedROCollection; import org.zoodb.tools.DBStatistics; import org.zoodb.tools.DBStatistics.STATS; /** * Query implementation. * * @author Tilmann Zaeschke */ public class QueryImpl implements Query { /** default. */ private static final long serialVersionUID = 1L; // transient to satisfy findbugs (Query is Serializable, but _pm / _ext are not). private transient PersistenceManagerImpl pm; private transient Extent<?> ext; private boolean isUnmodifiable = false; private Class<?> candCls = ZooPC.class; //TODO good default? private transient ZooClassDef candClsDef = null; private List<QueryAdvice> indexToUse = null; private String filter = ""; private boolean unique = false; private boolean subClasses = true; private boolean ignoreCache = true; private List<Pair<ZooFieldDef, Boolean>> ordering = new ArrayList<>(); private String orderingStr = null; private String resultSettings = null; private Class<?> resultClass = null; private final ObjectIdentitySet<Object> queryResults = new ObjectIdentitySet<Object>(); private List<QueryParameter> parameters = new ArrayList<QueryParameter>(); private QueryTreeNode queryTree; //This is used in schema auto-create mode when the persistent class has no schema defined private boolean isDummyQuery = false; private long rangeMin = 0; private long rangeMax = Long.MAX_VALUE; private QueryParameter rangeMinParameter = null; private QueryParameter rangeMaxParameter = null; private String rangeStr = null; @SuppressWarnings("rawtypes") public QueryImpl(PersistenceManagerImpl pm, Extent ext, String filter) { this(pm); this.ext = ext; setClass( this.ext.getCandidateClass() ); this.filter = filter; this.subClasses = ext.hasSubclasses(); } @SuppressWarnings("rawtypes") public QueryImpl(PersistenceManagerImpl pm, Class cls, String arg1) { this(pm); setClass( cls ); this.filter = arg1; } public QueryImpl(PersistenceManagerImpl pm) { this.pm = pm; ignoreCache = pm.getIgnoreCache(); pm.getSession().checkActiveRead(); } /** * {@code * SELECT [UNIQUE] [<result>] [INTO <result-class>] [FROM <candidate-class> [EXCLUDE SUBCLASSES]] [WHERE <filter>] [VARIABLES <variable declarations>] [PARAMETERS <parameter declarations>] [<import declarations>] [GROUP BY <grouping>] [ORDER BY <ordering>] [RANGE <start>, <end>] } * @param pm * @param arg0 */ public QueryImpl(PersistenceManagerImpl pm, String arg0) { this(pm); if (arg0==null || arg0 == "") { throw new NullPointerException("Please provide a query string."); } StringTokenizer st = new StringTokenizer(arg0); String q = arg0.trim(); String tok = st.nextToken(); //SELECT if (!tok.toLowerCase().equals("select")) { throw new JDOUserException("Illegal token in query: \"" + tok + "\""); } q = q.substring(6).trim(); tok = st.nextToken(); //UNIQUE if (tok.toLowerCase().equals("unique")) { unique = true; q = q.substring(6).trim(); tok = st.nextToken(); } //INTO //TODO if (tok.toLowerCase().equals("into")) { // _unique = true; // q = q.substring(4).trim(); // tok = getToken(q); System.err.println("Query not supported: INTO"); } //FROM if (tok.toLowerCase().equals("from")) { q = q.substring(4).trim(); tok = st.nextToken(); setClass( locateClass(tok) ); q = q.substring(tok.length()).trim(); if (!st.hasMoreTokens()) { return; } tok = st.nextToken(); //EXCLUDE SUBCLASSES if (tok.toLowerCase().equals("exclude")) { q = q.substring(7).trim(); tok = st.nextToken(); if (!tok.toLowerCase().equals("subclasses")) { throw new JDOUserException("Illegal token in query, expected 'SUBCLASSES': \"" + tok + "\""); } subClasses = false; q = q.substring(7).trim(); tok = st.nextToken(); } } //WHERE if (tok.toLowerCase().equals("where")) { q = q.substring(5).trim(); this.filter = q; //TODO } else { //maybe the query is finished? if (!tok.toLowerCase().equals("")) { throw new JDOUserException("Illegal token in query, expected 'WHERE': \"" + tok + "\""); } } } private Class<?> locateClass(String className) { try { return Class.forName(className); } catch (ClassNotFoundException e) { throw new JDOUserException("Class not found: " + className, e); } } @Override public void addExtension(String key, Object value) { checkUnmodifiable(); // TODO Auto-generated method stub throw new UnsupportedOperationException(); } @Override public void addSubquery(Query sub, String variableDeclaration, String candidateCollectionExpression) { checkUnmodifiable(); // TODO Auto-generated method stub throw new UnsupportedOperationException(); } @Override public void addSubquery(Query sub, String variableDeclaration, String candidateCollectionExpression, String parameter) { checkUnmodifiable(); // TODO Auto-generated method stub throw new UnsupportedOperationException(); } @Override public void addSubquery(Query sub, String variableDeclaration, String candidateCollectionExpression, String... parameters) { checkUnmodifiable(); // TODO Auto-generated method stub throw new UnsupportedOperationException(); } @SuppressWarnings("rawtypes") @Override public void addSubquery(Query sub, String variableDeclaration, String candidateCollectionExpression, Map parameters) { checkUnmodifiable(); // TODO Auto-generated method stub throw new UnsupportedOperationException(); } @Override public void close(Object queryResult) { if (!queryResults.remove(queryResult)) { //TODO what does JDO say about this? DBLogger.debugPrintln(0, "QueryResult not found."); return; } if (queryResult instanceof ExtentAdaptor) { ((ExtentAdaptor<?>)queryResult).closeAll(); } else if (queryResult instanceof ExtentImpl) { ((ExtentImpl<?>)queryResult).closeAll(); } else { //TODO ignore this DBLogger.debugPrintln(0, "QueryResult not closable."); } } @Override public void closeAll() { while (!queryResults.isEmpty()) { close(queryResults.iterator().next()); } } @Override public void compile() { checkUnmodifiable(); resetQuery(); compileQuery(); } private void compileQuery() { //compile only if it was not already compiled, unless the filter changed... if (queryTree != null) { return; } String fStr = filter; if (rangeStr != null) { fStr = (fStr == null) ? "" : fStr; fStr += " range " + rangeStr; } if (orderingStr != null) { fStr = (fStr == null) ? "" : fStr; fStr += " order by " + orderingStr; } if (fStr == null || fStr.trim().length() == 0 || isDummyQuery) { return; } if (DBStatistics.isEnabled()) { pm.getSession().statsInc(DBStatistics.STATS.QU_COMPILED); } //We do this on the query before assigning values to parameter. //Would it make sense to assign the values first and then properly parse the query??? //Probably not: //- every parameter change would require rebuilding the tree //- we would require an additional parser to assign the parameters //QueryParser qp = new QueryParser(filter, candClsDef, parameters, ordering); //QueryParserV2 qp = new QueryParserV2(filter, candClsDef, parameters, ordering); QueryParserV3 qp = new QueryParserV3(fStr, candClsDef, parameters, ordering, rangeMin, rangeMax); queryTree = qp.parseQuery(); rangeMin = qp.getRangeMin(); rangeMax = qp.getRangeMax(); rangeMinParameter = qp.getRangeMinParam(); rangeMaxParameter = qp.getRangeMaxParam(); } private void resetQuery() { //See Test_122: We need to clear this for setFilter() calls for (int i = 0; i < parameters.size(); i++) { QueryParameter p = parameters.get(i); if (p.getDeclaration() != DECLARATION.API) { parameters.remove(i); i--; } } rangeMinParameter = null; rangeMaxParameter = null; queryTree = null; ordering.clear(); } @Override public void declareImports(String imports) { checkUnmodifiable(); // TODO Auto-generated method stub throw new UnsupportedOperationException(); } /** * For example: * Query q = pm.newQuery (Employee.class, "salary > sal && name.startsWith(begin"); * q.declareParameters ("Float sal, String begin"); */ @Override public void declareParameters(String paramString) { checkUnmodifiable(); parameters.clear(); paramString = paramString.trim(); int i1 = paramString.indexOf(','); while (i1 >= 0) { String p1 = paramString.substring(0, i1).trim(); updateParameterDeclaration(p1); paramString = paramString.substring(i1+1, paramString.length()).trim(); i1 = paramString.indexOf(','); } updateParameterDeclaration(paramString); resetQuery(); } private void updateParameterDeclaration(String paramDecl) { int i = paramDecl.indexOf(' '); String type = paramDecl.substring(0, i); String name = paramDecl.substring(i+1); if (name.startsWith(":")) { throw new JDOUserException("Illegal parameter name: " + name); } for (QueryParameter p: parameters) { if (p.getName().equals(name)) { throw new JDOUserException("Duplicate parameter name: " + name); } } Class<?> cls = QueryParser.locateClassFromShortName(type); parameters.add(new QueryParameter(cls, name, DECLARATION.API)); } @Override public void declareVariables(String variables) { checkUnmodifiable(); // TODO Auto-generated method stub throw new UnsupportedOperationException(); } @Override public long deletePersistentAll() { checkUnmodifiable(); //? // if (_ext != null && (_filter == null || _filter.isEmpty())) { // //deleting extent only // Session s = _pm.getSession(); // int size = 0; // long oid; // //TODO // //improve: do not iterate OIDs // // instead: send class-delete command to Database // // for cached objects, used list of cached objects in ZooClassDef to identify // // them and mark them as deleted. // // But: How do we prevent objects from appearing in queries? A flag in ZooCLassDef // // would only work if implement special treatment for objects that are created afterwards (??) // while ((oid=_ext.nextOid()) != Session.OID_NOT_ASSIGNED) { // size++; // //TODO // //delete oid // } // _pm.deletePersistentAll(c); // return size; // } Collection<?> c = (Collection<?>) execute(); int size = 0; for (Object o: c) { size++; pm.deletePersistent(o); } return size; } @Override public long deletePersistentAll(Object... parameters) { checkUnmodifiable(); //? Collection<?> c = (Collection<?>) executeWithArray(parameters); pm.deletePersistentAll(c); return c.size(); } @SuppressWarnings("rawtypes") @Override public long deletePersistentAll(Map parameters) { checkUnmodifiable(); //? // TODO Auto-generated method stub throw new UnsupportedOperationException(); } private void assignParametersToQueryTree(QueryTreeNode queryTree) { if (parameters.isEmpty()) { return; } //TODO //TODO //TODO //TODO We should install an subscription service here. Every term/function that //TODO uses a QueryParameter should subscribe to the Parameter and get updated when //TODO the parameter changes. Parameters withou subscriptions should cause errors/warnings. //TODO //TODO //TODO QueryTreeIterator iter = queryTree.termIterator(); while (iter.hasNext()) { QueryTerm term = iter.next(); if (!term.isParametrized()) { continue; } String pName = term.getParamName(); //TODO cache Fields in QueryTerm to avoid String comparison? boolean isAssigned = false; for (QueryParameter param: parameters) { if (pName.equals(param.getName())) { //TODO assigning a parameter instead of the value means that the query will //adapt new values even if it is not recompiled. term.setParameter(param); //check if (param.getType() == null) { throw new JDOUserException( "Parameter has not been declared: " + param.getName()); } isAssigned = true; break; } } //TODO Exception? if (!isAssigned) { System.out.println("WARNING: Query parameter is not assigned: \"" + pName + "\""); } } } @SuppressWarnings({ "unchecked", "rawtypes" }) private void applyQueryOnExtent(List<Object> ret, QueryAdvice qa) { QueryTreeNode queryTree = qa.getQuery(); Iterator<?> ext2; if (!ignoreCache) { ClientSessionCache cache = pm.getSession().internalGetCache(); cache.persistReachableObjects(); } if (qa.getIndex() != null) { ext2 = pm.getSession().getPrimaryNode().readObjectFromIndex(qa.getIndex(), qa.getMin(), qa.getMax(), !ignoreCache); if (!ignoreCache) { ClientSessionCache cache = pm.getSession().internalGetCache(); ArrayList<ZooPC> dirtyObjs = cache.getDirtyObjects(); if (!dirtyObjs.isEmpty()) { QueryMergingIterator<ZooPC> qmi = new QueryMergingIterator(); qmi.add((Iterator<ZooPC>) ext2); qmi.add(cache.iterator(candClsDef, subClasses, ObjectState.PERSISTENT_NEW)); ext2 = qmi; } } } else { if (DBLogger.isLoggable(Level.FINE)) { DBLogger.LOGGER.fine("query.execute() uses extent without index"); } if (DBStatistics.isEnabled()) { pm.getSession().statsInc(STATS.QU_EXECUTED_WITHOUT_INDEX); if (!ordering.isEmpty()) { pm.getSession().statsInc(STATS.QU_EXECUTED_WITH_ORDERING_WITHOUT_INDEX); } } //use extent if (ext != null) { //use user-defined extent ext2 = ext.iterator(); } else { //create type extent ext2 = new ExtentImpl(candCls, subClasses, pm, ignoreCache).iterator(); } } if (ext != null && !subClasses) { // normal iteration while (ext2.hasNext()) { Object o = ext2.next(); if (subClasses) { if (!candCls.isAssignableFrom(o.getClass())) { continue; } } else { if (candCls != o.getClass()) { continue; } } boolean isMatch = queryTree.evaluate(o); if (isMatch) { ret.add(o); } } } else { // normal iteration (ignoring the possibly existing compatible extent to allow indices) while (ext2.hasNext()) { Object o = ext2.next(); boolean isMatch = queryTree.evaluate(o); if (isMatch) { ret.add(o); } } } if (ext2 instanceof CloseableIterator) { ((CloseableIterator)ext2).close(); } } private void checkParamCount(int i) { //this needs to be checked AFTER query compilation int max = parameters.size(); if (i > max) { throw new JDOUserException("Too many arguments given, parameter count: " + max); } if (i < max) { throw new JDOUserException("Too few arguments given, parameter count: " + max + ". In case of a String query, consider putting the argument in \" or '." + "Params: " + Arrays.toString(parameters.toArray())); } } private Object runQuery() { if (DBStatistics.isEnabled()) { pm.getSession().statsInc(STATS.QU_EXECUTED_TOTAL); } long t1 = System.nanoTime(); try { pm.getSession().lock(); pm.getSession().checkActiveRead(); if (isDummyQuery) { //empty result if no schema is defined (auto-create schema) //TODO check cached objects return new LinkedList<Object>(); } //assign parameters assignParametersToQueryTree(queryTree); //This is only for indices, not for given extents QueryOptimizer qo = new QueryOptimizer(candClsDef); indexToUse = qo.determineIndexToUse(queryTree); //TODO can also return a list with (yet) unknown size. In that case size() should return //Integer.MAX_VALUE (JDO 2.2 14.6.1) ArrayList<Object> ret = new ArrayList<Object>(); for (QueryAdvice qa: indexToUse) { applyQueryOnExtent(ret, qa); } //Now check if we need to check for duplicates, i.e. if multiple indices were used. for (QueryAdvice qa: indexToUse) { if (qa.getIndex() != indexToUse.get(0).getIndex()) { DBLogger.debugPrintln(0, "Merging query results(A)!"); System.out.println("Merging query results(A)!"); ObjectIdentitySet<Object> ret2 = new ObjectIdentitySet<Object>(); ret2.addAll(ret); return postProcess(ret2); } } //If we have more than one sub-query, we need to merge anyway, because the result sets may //overlap. //TODO implement merging of sub-queries!!! if (indexToUse.size() > 1) { DBLogger.debugPrintln(0, "Merging query results(B)!"); System.out.println("Merging query results(B)!"); ObjectIdentitySet<Object> ret2 = new ObjectIdentitySet<Object>(); ret2.addAll(ret); return postProcess(ret2); } return postProcess(ret); } finally { pm.getSession().unlock(); if (DBLogger.isLoggable(Level.FINE)) { long t2 = System.nanoTime(); DBLogger.LOGGER.fine("query.execute(): Time=" + (t2-t1) + "ns; Class=" + candCls + "; filter=" + filter); } } } private Object postProcess(Collection<Object> c) { if (resultSettings != null) { QueryResultProcessor rp = new QueryResultProcessor(resultSettings, candCls, candClsDef, resultClass); if (rp.isProjection()) { c = rp.processResultProjection(c.iterator(), unique); } else { //must be an aggregate Object o = rp.processResultAggregation(c.iterator()); return o; } } if (unique) { //unique Iterator<Object> iter = c.iterator(); if (iter.hasNext()) { Object ret = iter.next(); if (iter.hasNext()) { throw new JDOUserException("Too many results found in unique query."); } return ret; } else { //no result found return null; } } if (ordering != null && !ordering.isEmpty()) { if (!(c instanceof List)) { c = new ArrayList<>(c); } Collections.sort((List<Object>) c, new QueryComparator<Object>(ordering)); } //get range if (rangeMinParameter != null) { rangeMin = TypeConverterTools.toLong(rangeMinParameter.getValue()); } if (rangeMaxParameter != null) { rangeMax = TypeConverterTools.toLong(rangeMaxParameter.getValue()); } //To void remove() calls return new SynchronizedROCollection<>(c, pm.getSession(), rangeMin, rangeMax); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public Object execute() { //now go through extent. Skip this if extent was generated on server from local filters. filter = filter.trim(); if (filter.length() == 0 && orderingStr == null && !isDummyQuery) { try { pm.getSession().lock(); pm.getSession().checkActiveRead(); if (!ignoreCache) { ClientSessionCache cache = pm.getSession().internalGetCache(); cache.persistReachableObjects(); } if (ext == null) { ext = new ExtentImpl(candCls, subClasses, pm, ignoreCache); } if (DBStatistics.isEnabled()) { pm.getSession().statsInc(STATS.QU_EXECUTED_TOTAL); pm.getSession().statsInc(STATS.QU_EXECUTED_WITHOUT_INDEX); } return postProcess(new ExtentAdaptor(ext)); } finally { pm.getSession().unlock(); } } compileQuery(); checkParamCount(0); return runQuery(); } /** * {@inheritDoc} */ @Override public Object execute(Object p1) { return executeWithArray(p1); } /** * {@inheritDoc} */ @Override public Object execute(Object p1, Object p2) { return executeWithArray(p1, p2); } /** * {@inheritDoc} */ @Override public Object execute(Object p1, Object p2, Object p3) { return executeWithArray(p1, p2, p3); } /** * {@inheritDoc} */ @Override public Object executeWithArray(Object... parameters) { compileQuery(); checkParamCount(parameters.length); for (int i = 0; i < parameters.length; i++) { this.parameters.get(i).setValue(parameters[i]); } return runQuery(); } /** * {@inheritDoc} */ @SuppressWarnings("rawtypes") @Override public Object executeWithMap(Map parameters) { compileQuery(); checkParamCount(parameters.size()); for (QueryParameter p: this.parameters) { p.setValue(parameters.get(p.getName())); } return runQuery(); } @Override public FetchPlan getFetchPlan() { // TODO Auto-generated method stub throw new UnsupportedOperationException(); } @Override public boolean getIgnoreCache() { return ignoreCache; } @Override public PersistenceManager getPersistenceManager() { return pm; } @Override public boolean isUnmodifiable() { return isUnmodifiable; } @SuppressWarnings("rawtypes") @Override public void setCandidates(Extent pcs) { checkUnmodifiable(); this.ext = pcs; if (pcs.getCandidateClass() != candCls) { setClass( pcs.getCandidateClass() ); resetQuery(); } } @SuppressWarnings("rawtypes") @Override public void setCandidates(Collection pcs) { checkUnmodifiable(); ext = new CollectionExtent(pcs, pm, ignoreCache); if (ext.getCandidateClass() != candCls) { setClass(ext.getCandidateClass()); resetQuery(); } // // if (pcs.isEmpty()) { // ext = pcs; // setClass(ZooPC.class); // } // Iterator<?> iter = pcs.iterator(); // Object o1 = iter.next(); // Class<?> cls = o1.getClass(); // if (!ZooPC.class.isAssignableFrom(cls)) { // throw DBLogger.newUser("Class is not persistence capabale: " + cls.getName()); // } // if (pm != JDOHelper.getPersistenceManager(o1)) { // throw DBLogger.newUser("The object belongs to another PersistenceManager"); // } // while (iter.hasNext()) { // Object o2 = iter.next(); // Class<?> cls2 = o2.getClass(); // if (!ZooPC.class.isAssignableFrom(cls2)) { // throw DBLogger.newUser("Class is not persistence capabale: " + cls.getName()); // } // if (pm != JDOHelper.getPersistenceManager(o1)) { // throw DBLogger.newUser("The object belongs to another PersistenceManager"); // } // while (!cls.isAssignableFrom(cls2)) { // cls = cls.getSuperclass(); // } // } // ext = new ; // setClass(ZooPC.class); } /** * {@inheritDoc} */ @SuppressWarnings("rawtypes") @Override public void setClass(Class cls) { checkUnmodifiable(); if (!ZooPC.class.isAssignableFrom(cls)) { throw DBLogger.newUser("Class is not persistence capabale: " + cls.getName()); } candCls = cls; Node node = pm.getSession().getPrimaryNode(); ZooClassProxy sch = pm.getSession().getSchemaManager().locateSchema(cls, node); if (sch != null) { candClsDef = sch.getSchemaDef(); } else { if (pm.getSession().getConfig().getAutoCreateSchema()) { isDummyQuery = true; } else { throw DBLogger.newUser("Class schema is not defined: " + cls.getName()); } } } @SuppressWarnings("rawtypes") @Override public void setExtensions(Map extensions) { checkUnmodifiable(); // TODO Auto-generated method stub throw new UnsupportedOperationException(); } @Override public void setFilter(String filter) { checkUnmodifiable(); this.filter = filter; resetQuery(); } @Override public void setGrouping(String group) { checkUnmodifiable(); // TODO Auto-generated method stub throw new UnsupportedOperationException(); } @Override public void setIgnoreCache(boolean ignoreCache) { this.ignoreCache = ignoreCache;; } @Override public void setOrdering(String orderingString) { checkUnmodifiable(); if (orderingString != null && orderingString.trim().length() == 0) { orderingStr = null; } else { orderingStr = orderingString; } resetQuery(); } @Override public void setRange(String fromInclToExcl) { rangeMin = 0; rangeMax = Long.MAX_VALUE; rangeStr = fromInclToExcl; if (rangeStr != null) { rangeStr = rangeStr.trim(); if (rangeStr.length() == 0) { rangeStr = null; } } //For string-range, we have to reset the query. The range may contain parameters. resetQuery(); } @Override public void setRange(long fromIncl, long toExcl) { if (fromIncl < 0 || fromIncl > toExcl) { throw DBLogger.newUser("Illegal range argument: " + fromIncl + " / " + toExcl); } if (rangeStr != null) { resetQuery(); } rangeStr = null; rangeMin = fromIncl; rangeMax = toExcl; } @Override public void setResult(String data) { checkUnmodifiable(); //we can't check this here, because the filter and candidate class may still change resultSettings = data; } @SuppressWarnings("rawtypes") @Override public void setResultClass(Class cls) { checkUnmodifiable(); resultClass = cls; } @Override public void setUnique(boolean unique) { checkUnmodifiable(); this.unique = unique; } @Override public void setUnmodifiable() { isUnmodifiable = true; } private void checkUnmodifiable() { if (isUnmodifiable) { throw new JDOUserException("This query is unmodifiable."); } } @Override public void cancel(Thread arg0) { // TODO Auto-generated method stub throw new UnsupportedOperationException(); // } @Override public void cancelAll() { // TODO Auto-generated method stub throw new UnsupportedOperationException(); // } @Override public Integer getDatastoreReadTimeoutMillis() { // TODO Auto-generated method stub throw new UnsupportedOperationException(); //return null; } @Override public Integer getDatastoreWriteTimeoutMillis() { // TODO Auto-generated method stub throw new UnsupportedOperationException(); //return null; } @Override public Boolean getSerializeRead() { // TODO Auto-generated method stub throw new UnsupportedOperationException(); //return null; } @Override public void setDatastoreReadTimeoutMillis(Integer arg0) { // TODO Auto-generated method stub throw new UnsupportedOperationException(); // } @Override public void setDatastoreWriteTimeoutMillis(Integer arg0) { // TODO Auto-generated method stub throw new UnsupportedOperationException(); // } @Override public void setSerializeRead(Boolean arg0) { // TODO Auto-generated method stub throw new UnsupportedOperationException(); // } @Override public String toString() { return "Filter: \"" + filter + "\" ----- Tree: " + queryTree != null ? queryTree.print() : "not compiled"; } }