/* * 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.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import org.hypergraphdb.HGException; import org.hypergraphdb.HGQuery; import org.hypergraphdb.HyperGraph; import org.hypergraphdb.query.And; import org.hypergraphdb.query.HGAtomPredicate; import org.hypergraphdb.query.HGQueryCondition; import org.hypergraphdb.query.impl.DelayedSetLoadPredicate; import org.hypergraphdb.query.impl.IntersectionQuery; import org.hypergraphdb.query.impl.PredicateBasedFilter; import org.hypergraphdb.query.impl.RABasedPredicate; import org.hypergraphdb.query.impl.SortedIntersectionResult; //import org.hypergraphdb.query.impl.SortedIntersectionResult; import org.hypergraphdb.query.impl.ZigZagIntersectionResult; @SuppressWarnings("unchecked") public class AndToQuery implements ConditionToQuery { /** * * <p> * Order QueryMetaData instance by the expected size of the result set. The logic * is a bit convoluted because there are 3 numbers in play: lower bound (LB) of * the result, upper bound (UB) and expected size (E). We use the expected size * if available, otherwise we use the upper bound if available or the lower bound * with a "lowest priority". The assumption here is the E when provided should be * fairly accurate so there's no need to be overly conservative. In most cases, * the size is actually either known completely (e.g. in an index) or nothing * is know about it. If no size (LB, UB or E) is known for at least one of the * parameters of the compare method, then the two are deemed equal (i.e. 0 is * returned). * </p> * * @author Borislav Iordanov * */ private static class BySizeComparator implements Comparator<QueryMetaData> { public int compare(QueryMetaData o1, QueryMetaData o2) { long left = o1.sizeExpected > -1 ? o1.sizeExpected : o1.sizeUB > -1 ? o1.sizeUB : o1.sizeLB; if (left == -1) return 0; long right = o2.sizeExpected > -1 ? o2.sizeExpected : o2.sizeUB > -1 ? o2.sizeUB : o2.sizeLB; if (right == -1 || left == right) return 0; else if (left > right) return 1; else return -1; } } private static BySizeComparator bySizeComparator = new BySizeComparator(); public QueryMetaData getMetaData(HyperGraph graph, HGQueryCondition condition) { QueryMetaData x = QueryMetaData.ORACCESS.clone(condition); // assume we have ORACCESS, but check below boolean ispredicate = true; x.predicateCost = 0; for (HGQueryCondition sub : ((And)condition)) { ConditionToQuery transformer = ToQueryMap.getInstance().get(sub.getClass()); if (transformer == null) if (! (sub instanceof HGAtomPredicate)) throw new HGException("Condition " + sub + " is not query translatable, nor a predicate."); else { x.ordered = false; x.randomAccess = false; continue; } QueryMetaData subx = transformer.getMetaData(graph, sub); ispredicate = ispredicate && subx.predicateCost > -1; x.predicateCost += subx.predicateCost; x.ordered = x.ordered && subx.ordered; x.randomAccess = x.randomAccess && subx.randomAccess; } if (!ispredicate) x.predicateCost = -1; return x; } @SuppressWarnings("rawtypes") public HGQuery<?> getQuery(HyperGraph graph, HGQueryCondition condition) { And and = (And)condition; // // Trivial limit cases. // if (and.size() == 0) return HGQuery.NOP; else if (and.size() == 1) return ToQueryMap.toQuery(graph, and.iterator().next()); // query conditions are partitioned into the following categories: // - ORA: ordered random access results // - RA: random access (but unordered) results // - O: ordered results // - P: not translatable to one of the above categories, but usable as predicates // - W: neither of the above (i.e. unordered, non-random-access, non-predicate yielding conditions List<QueryMetaData> ORA = new ArrayList<QueryMetaData>(); List<QueryMetaData> RA = new ArrayList<QueryMetaData>(); List<QueryMetaData> O = new ArrayList<QueryMetaData>(); List<QueryMetaData> P = new ArrayList<QueryMetaData>(); List<QueryMetaData> W = new ArrayList<QueryMetaData>(); for (HGQueryCondition sub : and) { ConditionToQuery transformer = ToQueryMap.getInstance().get(sub.getClass()); if (transformer == null) { QueryMetaData qmd = QueryMetaData.MISTERY.clone(sub); qmd.predicateOnly = true; P.add(qmd); continue; } QueryMetaData qmd = transformer.getMetaData(graph, sub); if (qmd.predicateOnly) P.add(qmd); else if (qmd.ordered && qmd.randomAccess) ORA.add(qmd); else if (qmd.ordered) O.add(qmd); else if (qmd.randomAccess) RA.add(qmd); else if (qmd.predicateCost > -1) P.add(qmd); else W.add(qmd); } // // Once the partition is done, the following query is constructed as follows: // // 1. First all ORA result sets are evaluated // 2. Then O sets are appended // 3. RA sets are used as predicates if there is some other base set that needs to be // scanned anyway // 4. Results from the above construction are filter by the predicates in P // 5. W sets are scanned and loaded in memory HGQuery result = null; HGQueryCondition c1 = null, c2 = null; // First ORA sets - we just build up nested zig-zag intersections if (ORA.size() > 1) { Collections.sort(ORA, bySizeComparator); Iterator<QueryMetaData> i = ORA.iterator(); c1 = i.next().cond; c2 = i.next().cond; result = new IntersectionQuery(ToQueryMap.toQuery(graph, c1),// toQueryMap.get(c1.getClass()).getQuery(graph, c1), ToQueryMap.toQuery(graph, c2), //toQueryMap.get(c2.getClass()).getQuery(graph, c2), new ZigZagIntersectionResult.Combiner()); while (i.hasNext()) { c1 = i.next().cond; result = new IntersectionQuery(result, ToQueryMap.toQuery(graph, c1), //toQueryMap.get(c1.getClass()).getQuery(graph, c1), new ZigZagIntersectionResult.Combiner()); } } else if (ORA.size() == 1) { O.addAll(ORA); ORA.clear(); } // Next O sets - we just build up nested sorted intersections if (O.size() > 1) { Collections.sort(O, bySizeComparator); Iterator<QueryMetaData> i = O.iterator(); if (result == null) { c1 = i.next().cond; c2 = i.next().cond; result = new IntersectionQuery(ToQueryMap.toQuery(graph, c1), //toQueryMap.get(c1.getClass()).getQuery(graph, c1), ToQueryMap.toQuery(graph, c2), //toQueryMap.get(c2.getClass()).getQuery(graph, c2), new SortedIntersectionResult.Combiner()); } while (i.hasNext()) { c1 = i.next().cond; result = new IntersectionQuery(result, ToQueryMap.toQuery(graph, c1), // toQueryMap.get(c1.getClass()).getQuery(graph, c1), new SortedIntersectionResult.Combiner()); } } else if (O.size() == 1) { c1 = O.iterator().next().cond; if (result == null) result = ToQueryMap.toQuery(graph, c1); // toQueryMap.get(c1.getClass()).getQuery(graph, c1); else result = new IntersectionQuery(result, ToQueryMap.toQuery(graph, c1), //toQueryMap.get(c1.getClass()).getQuery(graph, c1), new SortedIntersectionResult.Combiner()); } if (result == null) { if (W.size() > 0) { Iterator<QueryMetaData> i = W.iterator(); long n = 0; while (i.hasNext()) { QueryMetaData curr = i.next(); if (n < curr.getSizeExpected()) c1 = curr.cond; } result = ToQueryMap.toQuery(graph, c1); //toQueryMap.get(c1.getClass()).getQuery(graph, c1); W.remove(c1); } else if (RA.size() > 0) { Iterator<QueryMetaData> i = RA.iterator(); double cost = 0.0; while (i.hasNext()) { QueryMetaData curr = i.next(); if (cost < curr.predicateCost) c1 = curr.cond; } result = ToQueryMap.toQuery(graph, c1); // toQueryMap.get(c1.getClass()).getQuery(graph, c1); RA.remove(c1); } else if (P.size() > 0) // some predicates can also be used as bases for search...when !qmd.predicateOnly { QueryMetaData found = null; for (QueryMetaData qmd : P) if (!qmd.predicateOnly) { found = qmd; break; } if (found != null) { result = ToQueryMap.toQuery(graph, found.cond); P.remove(found); } } if (result == null) throw new HGException("No query condition translatable into a scannable result set."); } // Here, it remains to convert all remaining RA sets to predicates and all remaining W sets into // in memory sets and again into predicates. // Transform RAs into predicates for (Iterator<QueryMetaData> i = RA.iterator(); i.hasNext(); ) { QueryMetaData curr = i.next(); c1 = curr.cond; QueryMetaData pqmd = QueryMetaData.MISTERY.clone(new RABasedPredicate(ToQueryMap.toQuery(graph, c1))); pqmd.predicateCost = curr.predicateCost; P.add(pqmd); } // Add predicates in order from the less costly to execute to the most costly... while (!P.isEmpty()) { double predicateCost = Double.MAX_VALUE; QueryMetaData lessCostly = null; for (Iterator<QueryMetaData> i = P.iterator(); i.hasNext(); ) { QueryMetaData curr = i.next(); if (curr.predicateCost < predicateCost) { predicateCost = curr.predicateCost; lessCostly = curr; } } result = new PredicateBasedFilter(graph, result, lessCostly.pred); P.remove(lessCostly); } // add Ws as predicates that lazily load their entire result sets into memory // this assumes that all result sets of Ws are UUID handles for (Iterator<QueryMetaData> i = W.iterator(); i.hasNext(); ) { QueryMetaData curr = i.next(); HGQuery q = ToQueryMap.toQuery(graph, curr.cond); result = new PredicateBasedFilter(graph, result, new DelayedSetLoadPredicate(q)); } return result; } }