/*
* This file is part of the HyperGraphDB source distribution. This is copyrighted
* software. For permitted uses, licensing options and redistribution, please see
* the LicensingInformation file at the root level of the distribution.
*
* Copyright (c) 2005-2010 Kobrix Software, Inc. All rights reserved.
*/
package org.hypergraphdb.query.cond2qry;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import org.hypergraphdb.HGException;
import org.hypergraphdb.HGHandle;
import org.hypergraphdb.HGIndex;
import org.hypergraphdb.HGPersistentHandle;
import org.hypergraphdb.HGQuery;
import org.hypergraphdb.HGSearchResult;
import org.hypergraphdb.HyperGraph;
import org.hypergraphdb.transaction.HGTransaction;
import org.hypergraphdb.transaction.HGTransactionConfig;
import org.hypergraphdb.type.HGAtomType;
import org.hypergraphdb.type.TypeUtils;
import org.hypergraphdb.util.HGUtils;
import org.hypergraphdb.util.Pair;
import org.hypergraphdb.util.Ref;
import org.hypergraphdb.util.Var;
import org.hypergraphdb.util.VarContext;
import org.hypergraphdb.algorithms.DefaultALGenerator;
import org.hypergraphdb.algorithms.HGBreadthFirstTraversal;
import org.hypergraphdb.algorithms.HGTraversal;
import org.hypergraphdb.atom.HGSubsumes;
import org.hypergraphdb.indexing.ByPartIndexer;
import org.hypergraphdb.indexing.ByTargetIndexer;
import org.hypergraphdb.indexing.DirectValueIndexer;
import org.hypergraphdb.indexing.HGIndexer;
import org.hypergraphdb.indexing.HGKeyIndexer;
import org.hypergraphdb.query.*;
/**
*
* @author Borislav Iordanov
*/
@SuppressWarnings("unchecked")
public class ExpressionBasedQuery<ResultType> extends HGQuery<ResultType>
{
private HGQuery<ResultType> query = null;
private HGQueryCondition condition;
private boolean hasVarContext = false;
static Pair<HGHandle, HGIndex> findIndex(HyperGraph graph, HGKeyIndexer indexer)
{
HGTraversal typeWalk = new HGBreadthFirstTraversal(indexer.getType(),
new DefaultALGenerator(graph,
hg.type(HGSubsumes.class),
null,
true,
false,
false));
for (HGHandle type = indexer.getType(); type != null; )
{
indexer.setType(type);
HGIndex<?, ?> idx = graph.getIndexManager().getIndex(indexer);
if (idx != null)
return new Pair<HGHandle, HGIndex>(type, idx);
else
type = typeWalk.hasNext() ? typeWalk.next().getSecond() : null;
}
return null;
}
/**
* <p>Transform a query condition into a disjunctive normal form.</p>
*
* @param C A condition, based on ands and ors to be transformed.
* @return The disjunctive normal form of C.
*/
private static HGQueryCondition toDNF(HGQueryCondition C)
{
if (C instanceof And)
{
And and = (And)C;
HashSet<HGQueryCondition> andSet = new HashSet<HGQueryCondition>();
for (int i = 0; i < and.size(); i++)
{
HGQueryCondition sub = and.get(i);
sub = toDNF(sub);
// here 'sub' is either a primitive condition, a single 'And' or a
// list of Or-ed Ands or primitives.
if (sub instanceof And)
{
for (HGQueryCondition subsub:(And)sub)
if (!andSet.contains(subsub))
andSet.add(subsub);
}
else if (sub instanceof Or)
{
Or result = new Or();
for (HGQueryCondition subsub:(Or)sub)
{
And newsub = new And();
newsub.add(subsub);
newsub.addAll(andSet);
newsub.addAll(and.subList(i + 1, and.size()));
result.add(newsub);
}
return toDNF(result);
}
else
andSet.add(sub);
}
and = new And();
and.addAll(andSet);
return and;
}
else if (C instanceof Or)
{
Or or = (Or)C;
HashSet<HGQueryCondition> orSet = new HashSet<HGQueryCondition>();
for (int i = 0; i < or.size(); i++)
{
HGQueryCondition sub = or.get(i);
sub = toDNF(sub);
if (sub instanceof Or)
{
for (HGQueryCondition subsub:(Or)sub)
if (!orSet.contains(subsub))
orSet.add(subsub);
}
else if (!orSet.contains(sub))
orSet.add(sub);
}
or = new Or();
or.addAll(orSet);
return or;
}
else if (C instanceof MapCondition)
{
MapCondition mcond = (MapCondition)C;
return new MapCondition(toDNF(mcond.getCondition()), mcond.getMapping());
}
else
return C;
}
// Condition use the same type either if both references are constant or both
// refer to the same variable
private boolean isSameType(TypeCondition c1, TypeCondition c2)
{
Ref<?> r1 = c1.getTypeReference(), r2 = c2.getTypeReference();
return !hg.isVar(r1) && !hg.isVar(r2) && r1.get().equals(r2.get()) ||
hg.isVar(r1) && hg.isVar(r2) && this.ctx.isSameVar((Var<?>)r1, (Var<?>)r2);
}
private boolean checkConsistent(AtomTypeCondition c1, AtomTypeCondition c2)
{
if (c1.getTypeReference().get() == null || c2.getTypeReference() == null ||
hg.isVar(c1.getTypeReference()) || hg.isVar(c2.getTypeReference()))
return true;
HGHandle h1 = (c1.getTypeHandle() != null) ?
c1.getTypeHandle() :
graph.getTypeSystem().getTypeHandle(c1.getJavaClass());
if (c2.getTypeHandle() != null)
return h1.equals(c2.getTypeHandle());
else
return h1.equals(graph.getTypeSystem().getTypeHandle(c2.getJavaClass()));
}
private boolean checkConsistent(TypedValueCondition c1, AtomTypeCondition c2)
{
if (c2.getTypeReference() == null || c1.getTypeReference() == null ||
hg.isVar(c1.getTypeReference()) || hg.isVar(c2.getTypeReference()))
return true;
HGHandle h1 = (c1.getTypeHandle() != null) ?
c1.getTypeHandle() :
graph.getTypeSystem().getTypeHandle(c1.getJavaClass());
if (c2.getTypeHandle() != null)
return h1.equals(c2.getTypeHandle());
else
return h1.equals(graph.getTypeSystem().getTypeHandle(c2.getJavaClass()));
}
private boolean checkConsistent(TypedValueCondition tc, AtomValueCondition vc)
{
if (tc.getValueReference() == null || vc.getValueReference() == null ||
hg.isVar(tc.getValueReference()) || hg.isVar(vc.getValueReference()))
return true;
return HGUtils.eq(tc.getValue(), vc.getValue()) && tc.getOperator() == vc.getOperator();
}
private boolean checkConsistent(HGAtomType type, AtomPartCondition vc)
{
return TypeUtils.getProjection(graph, type, vc.getDimensionPath()) != null;
}
// apply a few simply transformation for common cases...
// this kind of assumes that we are already in DNF because
// conjunction are handled by transforming them as if we are at
// the top level
private HGQueryCondition simplify(HGQueryCondition cond)
{
if (cond instanceof And)
{
And in = (And)cond;
And out = new And();
for (HGQueryCondition c : in)
{
c = simplify(c);
if (c instanceof And)
out.addAll((And)c);
else if (c == Nothing.Instance)
return c;
else
out.add(c);
}
// At the end of the following step, the conjunction will have
// either a single TypedValueCondition or at most one AtomTypeCondition
// and one AtomValueCondition. This step insures that there are no
// contradictory conditions amongst the condition of type
// AtomValueCondition, AtomTypeCondition and TypedValueCondition
AtomTypeCondition byType = null;
AtomValueCondition byValue = null;
TypedValueCondition byTypedValue = null;
HashSet<OrderedLinkCondition> oLinks = new HashSet<OrderedLinkCondition>();
HashSet<AtomPartCondition> byPart = new HashSet<AtomPartCondition>();
boolean has_ordered = false;
boolean has_ra = false;
for (Iterator<HGQueryCondition> i = out.iterator(); i.hasNext(); )
{
HGQueryCondition c = i.next();
if (c instanceof AtomTypeCondition)
{
AtomTypeCondition tc = (AtomTypeCondition)c;
// if the condition is on a Java class with no HG type currently defined,
// clearly there can't be atoms of that type
if (!hg.isVar(tc.getTypeReference()) &&
tc.getTypeHandle() == null &&
graph.getTypeSystem().getTypeHandleIfDefined(tc.getJavaClass()) == null)
return Nothing.Instance;
if (byType == null)
{
if (byTypedValue != null)
{
if(!checkConsistent(byTypedValue, tc))
return Nothing.Instance;
else if (isSameType(byTypedValue, tc))
i.remove();
}
else
byType = tc;
}
else if (isSameType(byType, tc))
i.remove();
else if (!checkConsistent(byType, tc))
return Nothing.Instance;
}
else if (c instanceof AtomValueCondition)
{
if (byValue == null)
{
if (byTypedValue != null)
if(!checkConsistent(byTypedValue, (AtomValueCondition)c))
return Nothing.Instance;
else
i.remove();
else
byValue = (AtomValueCondition)c;
}
else if (byValue.equals(c))
i.remove();
else
return Nothing.Instance;
}
else if (c instanceof TypedValueCondition)
{
if (byTypedValue == null)
byTypedValue = (TypedValueCondition)c;
else if (byTypedValue.equals(c) &&
isSameType(byTypedValue, (TypedValueCondition)c))
i.remove();
else
return Nothing.Instance;
// if the condition is on a Java class with no HG type currently defined,
// clearly there can't be atoms of that type
if (!hg.isVar(byTypedValue.getTypeReference()) &&
byTypedValue.getTypeHandle() == null &&
graph.getTypeSystem().getTypeHandleIfDefined(byTypedValue.getJavaClass()) == null)
return Nothing.Instance;
}
else if (c instanceof AtomPartCondition)
{
byPart.add((AtomPartCondition)c);
}
else if (c instanceof OrderedLinkCondition)
{
oLinks.add((OrderedLinkCondition)c);
}
else
{
ConditionToQuery transform = ToQueryMap.getInstance().get(c.getClass());
if (transform != null)
{
QueryMetaData qmd = transform.getMetaData(graph, c);
has_ordered = has_ordered || qmd.ordered;
has_ra = has_ra || qmd.randomAccess;
}
}
}
HGHandle typeHandle = null;
if (byTypedValue != null)
{
if (byType != null)
{
if (!checkConsistent(byTypedValue, byType))
return Nothing.Instance;
else if (isSameType(byTypedValue, byType))
{
out.remove(byType);
if (byType.getTypeHandle() != null && !hg.isVar(byType.getTypeReference()))
typeHandle = byType.getTypeHandle();
byType = null;
}
}
if (byValue != null)
if (!checkConsistent(byTypedValue, byValue))
return Nothing.Instance;
else
{
out.remove(byValue);
byValue = null;
}
if (typeHandle == null && !hg.isVar(byTypedValue.getTypeReference()))
if (byTypedValue.getTypeHandle() != null)
typeHandle = byTypedValue.getTypeHandle();
else
typeHandle = graph.getTypeSystem().getTypeHandle(byTypedValue.getJavaClass());
}
else if (byType != null)
{
if (!hg.isVar(byType.getTypeReference()))
{
if (byType.getTypeHandle() != null)
typeHandle = byType.getTypeHandle();
else
typeHandle = graph.getTypeSystem().getTypeHandle(byType.getJavaClass());
}
if (byValue != null)
{
out.add(byTypedValue = new TypedValueCondition(byType.getTypeReference(),
byValue.getValueReference(),
byValue.getOperator()));
out.remove(byType);
out.remove(byValue);
byType = null;
byValue = null;
}
}
// now, we check for availabe indices
// The simplest: a direct by-value index
if (byTypedValue != null &&
!hg.isVar(byTypedValue.getTypeReference()))
{
Pair<HGHandle, HGIndex> p = findIndex(graph, new DirectValueIndexer<Object>(typeHandle));
if (p != null)
{
out.remove(byTypedValue);
out.add(new IndexCondition(p.getSecond(),
byTypedValue.getValueReference(),
ComparisonOperator.EQ));
}
}
// indexing by value parts, if we find an appropriate index
// then we can eliminate the "type" predicate altogether (since bypart indices
// are always for a particular atom type) and a "by value" condition is kept
// only as a predicate
if (typeHandle != null && byPart.size() > 0)
{
HGAtomType type = (HGAtomType)graph.get(typeHandle);
if (type == null)
throw new HGException("No type for type handle " + typeHandle + " in this HyperGraph instance.");
for (AtomPartCondition pc : byPart)
if (!checkConsistent(type, pc))
return Nothing.Instance;
else
{
Pair<HGHandle, HGIndex> p = findIndex(graph, new ByPartIndexer(typeHandle,
pc.getDimensionPath())); //graph.getIndexManager().getIndex(indexer);
if (p != null)
{
if (typeHandle.equals(p.getFirst()))
{
if (byType != null)
{
out.remove(byType);
byType = null;
}
else if (byTypedValue != null)
{
out.remove(byTypedValue);
out.add(new ValueAsPredicateOnly(byTypedValue.getValueReference(),
byTypedValue.getOperator()));
byTypedValue = null;
}
}
out.remove(pc);
out.add(new IndexedPartCondition(p.getFirst(),
p.getSecond(),
pc.getValueReference(),
pc.getOperator()));
}
}
}
// Check for "by-target" indices within an OrderedLinkConditions and replace
// the corresponding 'incident' condition with one based on the index.
// Here would be an opportunity to use HGTypeStructuralInfo on a link type and
// possibly eliminate the OrderedLinkCondition (and resulting predicate call during
// query execution) altogether
if (typeHandle != null)
for (OrderedLinkCondition c : oLinks)
{
for (int ti = 0; ti < c.targets().length; ti++)
{
Ref<HGHandle> targetHandle = c.targets()[ti];
if (hg.isVar(targetHandle) || targetHandle.equals(hg.constant(graph.getHandleFactory().anyHandle())))
continue;
Pair<HGHandle, HGIndex> p = findIndex(graph, new ByTargetIndexer(typeHandle, ti));
if (p != null)
{
if (typeHandle.equals(p.getFirst()))
{
if (byType != null)
{
out.remove(byType);
byType = null;
}
else if (byTypedValue != null)
{
out.remove(byTypedValue);
out.add(new AtomValueCondition(byTypedValue.getValueReference(),
byTypedValue.getOperator()));
byTypedValue = null;
}
}
out.remove(new IncidentCondition(targetHandle));
out.add(new IndexCondition<HGPersistentHandle, HGPersistentHandle>(
p.getSecond(), targetHandle.get().getPersistent()));
}
}
}
return out.size() > 1 ? out : out.iterator().next();
}
else if (cond instanceof Or)
{
Or in = (Or)cond;
if (in.isEmpty())
return Nothing.Instance;
Or out = new Or();
for (HGQueryCondition c : in)
{
c = simplify(c);
if (c instanceof Or)
out.addAll((Or)c);
else if (c != Nothing.Instance)
out.add(c);
}
return out.isEmpty() ? Nothing.Instance :
out.size() > 1 ? out : out.iterator().next();
}
else if (cond instanceof MapCondition)
{
MapCondition mcond = (MapCondition)cond;
return new MapCondition(simplify(mcond.getCondition()),
mcond.getMapping());
}
else
return cond;
}
private List<AtomPartCondition> getAtomIndexedPartsConditions(HyperGraph graph, HGHandle hType, Object value)
{
ArrayList<AtomPartCondition> L = new ArrayList<AtomPartCondition>();
List<HGIndexer> indexers = graph.getIndexManager().getIndexersForType(hType);
if (indexers == null)
return L;
for (HGIndexer idx : indexers)
{
if (idx instanceof ByPartIndexer)
{
String [] dimPath = ((ByPartIndexer)idx).getDimensionPath();
Object partValue = TypeUtils.project(graph, hType, value, dimPath, true).getValue();
L.add(new AtomPartCondition(dimPath, partValue));
}
}
return L;
}
private HGHandle classToHandle(Class<?> cl)
{
HGHandle h = graph.getTypeSystem().getTypeHandleIfDefined(cl);
if (h == null)
{
HGTransaction tx = graph.getTransactionManager().getContext().getCurrent();
if (tx == null || !tx.isReadOnly())
h = graph.getTypeSystem().getTypeHandleIfDefined(cl);
}
return h;
}
private HGQueryCondition expand(HyperGraph graph, HGQueryCondition cond)
{
if (cond instanceof TypePlusCondition)
{
TypePlusCondition ac = (TypePlusCondition)cond;
if (ac.getBaseType() == null)
ac.setBaseType(classToHandle(ac.getJavaClass()));
// {
// HGHandle typeHandle = graph.getTypeSystem().getTypeHandleIfDefined(ac.getJavaClass());
// if (typeHandle != null)
// ac.setBaseType(typeHandle);
// else
// cond = Nothing.Instance;
// }
if (ac.getBaseType() != null)
{
Or orCondition = new Or();
for (HGHandle h : ac.getSubTypes(graph))
orCondition.add(new AtomTypeCondition(h));
cond = orCondition;
}
else
return Nothing.Instance;
}
// else if (cond instanceof AtomTypeCondition)
// {
// AtomTypeCondition tc = (AtomTypeCondition)cond;
// if (tc.getJavaClass() != null &&
// !hg.isVar(tc.getTypeReference()) &&
// graph.getTypeSystem().getTypeHandleIfDefined(tc.getJavaClass()) == null)
//
// cond = Nothing.Instance;
// }
else if (cond instanceof TypedValueCondition)
{
TypedValueCondition tc = (TypedValueCondition)cond;
if (!hg.isVar(tc.getTypeReference()))
{
Pair<HGHandle, HGIndex> p = findIndex(graph, new DirectValueIndexer<Object>(tc.getTypeHandle()));
if (p != null)
cond = new IndexCondition(p.getSecond(), tc.getValueReference(), tc.getOperator());
else if (((TypedValueCondition)cond).getOperator() == ComparisonOperator.EQ)
{
HGHandle typeHandle = tc.getTypeHandle();
if (typeHandle == null)
typeHandle = classToHandle(tc.getJavaClass());
if (!hg.isVar(tc.getValueReference()))
{
List<AtomPartCondition> indexedParts = getAtomIndexedPartsConditions(graph, typeHandle, tc.getValue());
if (!indexedParts.isEmpty())
{
And and = hg.and(cond);
for (AtomPartCondition pc : indexedParts)
and.add(pc);
cond = and;
}
}
}
}
}
else if (cond instanceof AtomValueCondition &&
!hg.isVar(((AtomValueCondition)cond).getValueReference()))
{
AtomValueCondition vc = (AtomValueCondition)cond;
Object value = vc.getValue();
if (value == null)
throw new HGException("Search by null values is not supported yet.");
HGHandle valueHandle = graph.getHandle(value);
HGHandle type = null;
if (valueHandle != null)
type = graph.getTypeSystem().getTypeHandle(valueHandle);
else if (value != null)
type = graph.getTypeSystem().getTypeHandleIfDefined(value.getClass());
if (type != null && vc.getOperator() == ComparisonOperator.EQ)
{
List<AtomPartCondition> indexedParts = getAtomIndexedPartsConditions(graph, type, value);
if (!indexedParts.isEmpty())
{
And and = hg.and(cond, new AtomTypeCondition(type));
for (AtomPartCondition pc : indexedParts)
and.add(pc);
cond = and;
}
}
}
else if (cond instanceof And)
{
HGQueryCondition statedType = null;
And result = new And();
for (HGQueryCondition sub : (And)cond)
{
if (sub instanceof AtomTypeCondition || sub instanceof TypedValueCondition)
statedType = sub;
HGQueryCondition expanded = expand(graph, sub);
if (expanded == Nothing.Instance)
cond = Nothing.Instance;
else
{
if (expanded instanceof And)
result.addAll((And)expanded);
else
result.add(expanded);
}
}
if (statedType != null) // filter out any (possibly wrongly) inferred type conditions during the expansion process
for (Iterator<HGQueryCondition> i = result.iterator(); i.hasNext(); )
{
HGQueryCondition curr = i.next();
if (! (curr instanceof AtomTypeCondition)) continue;
if (!isSameType((TypeCondition)curr, (TypeCondition)statedType))
i.remove();
}
if (cond != Nothing.Instance)
cond = result;
}
else if (cond instanceof Or)
{
Or result = new Or();
for (HGQueryCondition sub : (Or)cond)
{
HGQueryCondition subexp = expand(graph, sub);
if (subexp != Nothing.Instance)
result.add(subexp);
}
cond = result.isEmpty() ? Nothing.Instance : result;
}
else if (cond instanceof OrderedLinkCondition)
{
And result = new And();
result.add(cond);
for (Ref<HGHandle> h : ((OrderedLinkCondition)cond).targets())
if (!hg.isVar(h) && !h.get().equals(graph.getHandleFactory().anyHandle()))
result.add(new IncidentCondition(h));
cond = result;
}
else if (cond instanceof LinkCondition)
{
And result = new And();
for (Ref<HGHandle> h : ((LinkCondition)cond).targets())
if (!hg.isVar(h) && !h.get().equals(graph.getHandleFactory().anyHandle()))
result.add(new IncidentCondition(h));
cond = result;
}
else if (cond instanceof MapCondition)
{
MapCondition mcond = (MapCondition)cond;
cond = new MapCondition(expand(graph, mcond.getCondition()),
mcond.getMapping());
}
return cond;
}
/**
* <p>
* Recursively replace the static hg.anyHandle by the handle factory
* anyHandle of the current graph.
* </p>
* @param c
*/
private void preprocess(HGQueryCondition c)
{
if (c instanceof LinkCondition)
{
LinkCondition lc = (LinkCondition)c;
if (lc.getTargetSet().contains(hg.constant(hg.anyHandle())))
{
lc.getTargetSet().remove(hg.constant(hg.anyHandle()));
lc.getTargetSet().add(hg.constant((HGHandle)graph.getHandleFactory().anyHandle()));
}
}
else if (c instanceof OrderedLinkCondition)
{
OrderedLinkCondition lc = (OrderedLinkCondition)c;
for (int i = 0; i < lc.getTargets().length; i++)
if (lc.getTargets()[i].equals(hg.constant(hg.anyHandle())))
lc.getTargets()[i] = hg.constant((HGHandle)graph.getHandleFactory().anyHandle());
}
else if (c instanceof Not)
{
if (((Not) c).getPredicate() instanceof HGQueryCondition)
preprocess((HGQueryCondition)((Not)c).getPredicate());
}
else if (c instanceof And)
{
for (HGQueryCondition sub : (And)c)
preprocess(sub);
}
else if (c instanceof Or)
{
for (HGQueryCondition sub : (Or)c)
preprocess(sub);
}
else if (c instanceof MapCondition)
{
preprocess(((MapCondition)c).getCondition());
}
}
public ExpressionBasedQuery(final HyperGraph graph, boolean withVarContext)
{
this.graph = graph;
if (this.hasVarContext = withVarContext)
this.ctx = VarContext.pushFrame();
}
public ExpressionBasedQuery(final HyperGraph graph, final HGQueryCondition condition)
{
this.graph = graph;
graph.getTransactionManager().ensureTransaction(new Callable<HGQuery<ResultType>>() {
public HGQuery<ResultType> call()
{
return compileProcess(condition);
}
}, HGTransactionConfig.READONLY);
}
public HGQuery<ResultType> compile(final HGQueryCondition condition)
{
return graph.getTransactionManager().ensureTransaction(new Callable<HGQuery<ResultType>>() {
public HGQuery<ResultType> call()
{
return compileProcess(condition);
}
}, HGTransactionConfig.READONLY);
}
private HGQuery<ResultType> compileProcess(final HGQueryCondition condition)
{
// The condition was constructed before the make method is called and all variables have been
// added to the current top-level context. The make method takes ownership of that context
// and removes from the stack at the end. The correct way to use variables in query
// conditions is calling the varContext() method before calling HGQuery.make
try
{
preprocess(condition);
this.condition = simplify(toDNF(expand(graph, condition)));
query = ToQueryMap.toQuery(graph, this.condition);
return this;
}
finally
{
// Cleanup thread-bound variable context
if (hasVarContext)
VarContext.popFrame();
}
}
public HGSearchResult<ResultType> execute()
{
return query.execute();
}
/**
* <p>Return a possibly simplified and normalized version of the condition with
* which this query was constructed.</p>
*/
public HGQueryCondition getCondition()
{
return condition;
}
public HGQuery<ResultType> getCompiledQuery()
{
return query;
}
}