001 /*
002 * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org
003 * Copyright (C) 2011 NightLabs Consulting GmbH
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program. If not, see <http://www.gnu.org/licenses/>.
017 */
018 package org.cumulus4j.store.query.eval;
019
020 import java.util.HashSet;
021 import java.util.Set;
022
023 import org.cumulus4j.store.model.DataEntry;
024 import org.cumulus4j.store.query.QueryEvaluator;
025 import org.cumulus4j.store.query.QueryHelper;
026 import org.datanucleus.query.expression.DyadicExpression;
027 import org.datanucleus.query.expression.Expression;
028 import org.datanucleus.util.NucleusLogger;
029
030 /**
031 * <p>
032 * Evaluator handling the boolean operation "&&" (AND).
033 * </p>
034 * <p>
035 * Cumulus4j encrypts as much as possible and keeps a minimum of plain-text indexes. The plain-text-indexes
036 * index each field separately. This is a compromise between security and searchability. As the index
037 * contains only plain-text field-values without any plain-text context (the context is encrypted),
038 * it provides the advantage of high security, but at the same time it is not possible to query an
039 * AND operation directly in the underlying database.
040 * </p>
041 * <p>
042 * Instead, the AND operation is performed by first querying all {@link DataEntry#getDataEntryID() dataEntryID}s
043 * of the {@link #getLeft() left} and the {@link #getRight() right} side and then intersecting these two
044 * <code>Set<Long></code> in memory.
045 * </p>
046 * <p>
047 * If the {@link ResultDescriptor} indicates a {@link ResultDescriptor#isNegated() negation}, this evaluator
048 * delegates to the {@link OrExpressionEvaluator}, because a query like
049 * "!( a > 5 && b <= 12 )" is internally converted to "a <= 5 || b > 12" for performance reasons.
050 * See {@link NotExpressionEvaluator} as well as
051 * <a target="_blank" href="http://en.wikipedia.org/wiki/De_Morgan%27s_laws">De Morgan's laws</a> in wikipedia for details.
052 * </p>
053 *
054 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
055 * @see OrExpressionEvaluator
056 */
057 public class AndExpressionEvaluator
058 extends AbstractExpressionEvaluator<DyadicExpression>
059 {
060 private OrExpressionEvaluator negatedExpressionEvaluator;
061
062 public AndExpressionEvaluator(QueryEvaluator queryEvaluator, AbstractExpressionEvaluator<?> parent, DyadicExpression expression) {
063 super(queryEvaluator, parent, expression);
064 }
065
066 public AndExpressionEvaluator(OrExpressionEvaluator negatedExpressionEvaluator)
067 {
068 this(negatedExpressionEvaluator.getQueryEvaluator(), negatedExpressionEvaluator.getParent(), negatedExpressionEvaluator.getExpression());
069 this.negatedExpressionEvaluator = negatedExpressionEvaluator;
070 }
071
072 @Override
073 public AbstractExpressionEvaluator<? extends Expression> getLeft() {
074 if (negatedExpressionEvaluator != null)
075 return negatedExpressionEvaluator.getLeft();
076
077 return super.getLeft();
078 }
079
080 @Override
081 public AbstractExpressionEvaluator<? extends Expression> getRight() {
082 if (negatedExpressionEvaluator != null)
083 return negatedExpressionEvaluator.getRight();
084
085 return super.getRight();
086 }
087
088 @Override
089 protected Set<Long> _queryResultDataEntryIDs(ResultDescriptor resultDescriptor)
090 {
091 if (resultDescriptor.isNegated())
092 return new OrExpressionEvaluator(this)._queryResultDataEntryIDsIgnoringNegation(resultDescriptor);
093 else
094 return _queryResultDataEntryIDsIgnoringNegation(resultDescriptor);
095 }
096
097 protected Set<Long> _queryResultDataEntryIDsIgnoringNegation(ResultDescriptor resultDescriptor)
098 {
099 if (getLeft() == null)
100 throw new IllegalStateException("getLeft() == null");
101
102 if (getRight() == null)
103 throw new IllegalStateException("getRight() == null");
104
105 Set<Long> leftResult = null;
106 boolean leftEvaluated = true;
107 try {
108 leftResult = getLeft().queryResultDataEntryIDs(resultDescriptor);
109 }
110 catch (UnsupportedOperationException uoe) {
111 leftEvaluated = false;
112 getQueryEvaluator().setIncomplete();
113 NucleusLogger.QUERY.debug("Unsupported operation in LEFT : "+getLeft().getExpression() + " so deferring evaluation to in-memory");
114 }
115
116 Set<Long> rightResult = null;
117 boolean rightEvaluated = true;
118 try {
119 rightResult = getRight().queryResultDataEntryIDs(resultDescriptor);
120 }
121 catch (UnsupportedOperationException uoe) {
122 rightEvaluated = false;
123 getQueryEvaluator().setIncomplete();
124 NucleusLogger.QUERY.debug("Unsupported operation in RIGHT : "+getRight().getExpression() + " so deferring evaluation to in-memory");
125 }
126
127 if (!leftEvaluated && !rightEvaluated) {
128 // Neither side evaluated so return all data entry ids
129 leftResult = QueryHelper.getAllDataEntryIdsForCandidate(getQueryEvaluator().getPersistenceManagerForData(),
130 getQueryEvaluator().getExecutionContext(), getQueryEvaluator().getQuery().getCandidateClass(),
131 getQueryEvaluator().getQuery().isSubclasses());
132 }
133
134 if (leftResult != null && rightResult != null) {
135 Set<Long> dataEntryIDs1;
136 Set<Long> dataEntryIDs2;
137
138 // Swap them, if the first set is bigger than the 2nd (we want to always iterate the smaller set => faster).
139 if (leftResult.size() > rightResult.size()) {
140 dataEntryIDs1 = rightResult;
141 dataEntryIDs2 = leftResult;
142 }
143 else {
144 dataEntryIDs1 = leftResult;
145 dataEntryIDs2 = rightResult;
146 }
147
148 Set<Long> result = new HashSet<Long>(dataEntryIDs1.size());
149 for (Long dataEntryID : dataEntryIDs1) {
150 if (dataEntryIDs2.contains(dataEntryID))
151 result.add(dataEntryID);
152 }
153 return result;
154 }
155 else if (leftResult != null)
156 return leftResult;
157 else
158 return rightResult;
159 }
160 }