package org.apache.lucene.queryparser.flexible.aqp.processors; import java.util.List; import org.apache.lucene.queryparser.flexible.aqp.nodes.AqpImmutableGroupQueryNode; import org.apache.lucene.queryparser.flexible.core.QueryNodeException; import org.apache.lucene.queryparser.flexible.core.nodes.BoostQueryNode; import org.apache.lucene.queryparser.flexible.core.nodes.GroupQueryNode; import org.apache.lucene.queryparser.flexible.core.nodes.ModifierQueryNode; import org.apache.lucene.queryparser.flexible.core.nodes.QueryNode; import org.apache.lucene.queryparser.flexible.core.processors.QueryNodeProcessor; import org.apache.lucene.queryparser.flexible.core.processors.QueryNodeProcessorImpl; /** * Optimizes the query by removing the superfluous GroupQuery nodes. We harvest * all parameters from fuzzy, boost, and modifier nodes and apply those that are * closest to the actual query. * * <p> * * Example: * * <pre> * this (+(-(+(-(that thus))^0.1))^0.3) * </pre> * * Will be optimized into (when DEFOP = AND): * * <pre> * +field:this -((+field:that +field:thus)^0.1) * </pre> * * */ public class AqpGroupQueryOptimizerProcessor extends QueryNodeProcessorImpl implements QueryNodeProcessor { @Override protected QueryNode preProcessNode(QueryNode node) throws QueryNodeException { if (node instanceof GroupQueryNode && !(node instanceof AqpImmutableGroupQueryNode)) { QueryNode immediateChild = node.getChildren().get(0); ClauseData data = harvestData(immediateChild); QueryNode changedNode = data.getLastChild(); if (data.getLevelsDeep() > 0) { boolean modified = false; if (data.getBoost() != null) { changedNode = new BoostQueryNode(changedNode, data.getBoost()); modified = true; } if (data.getModifier() != null) { changedNode = new ModifierQueryNode(changedNode, data.getModifier()); modified = true; /* * Why was I doing this? Firstly, it is buggy, the second branch * always executes - why am i creating new BooleanNode? * List<QueryNode> children = new ArrayList<QueryNode>(); if * (children.size() == 1) { return children.get(0); } else { * children.add(new ModifierQueryNode(node, data.getModifier())); node * = new BooleanQueryNode(children); } */ } /* * if (modified && node.getParent()==null) { List<QueryNode> children = * new ArrayList<QueryNode>(); children.add(node); changedNode = new * BooleanQueryNode(children); } */ return changedNode; } return immediateChild; } return node; } @Override protected QueryNode postProcessNode(QueryNode node) throws QueryNodeException { // also detect the situations when the default operator included modifier // but user supplied modifer before the clause, eg. // value -field:(somethig something) // we should honour the closest modifier, but it is not caught in the // pre-processor, because that one is looking only at groups, and // this will be one level above the group; but after it was simplified // we can see it here, it will be chain of modifier>modifier>modifier... if (node instanceof ModifierQueryNode && node.getChildren().size() == 1 && node.getChildren().get(0) instanceof ModifierQueryNode) { return node.getChildren().get(0); } return node; } @Override protected List<QueryNode> setChildrenOrder(List<QueryNode> children) throws QueryNodeException { return children; } /* * methods below not used now, but might be added - it tries to compact * consecutive CLAUSE nodes into one clause, taking only the last * modifier/tmodifier values */ private ClauseData harvestData(QueryNode clauseNode) { ClauseData data = new ClauseData(); harvestData(clauseNode, data); return data; } private void harvestData(QueryNode node, ClauseData data) { if (node instanceof ModifierQueryNode) { data.setModifier(((ModifierQueryNode) node).getModifier()); } else if (node instanceof BoostQueryNode) { data.setBoost(((BoostQueryNode) node).getValue()); } else if (node instanceof GroupQueryNode) { data.addLevelsDeep(); } else { data.setLastChild(node); return; // break processing } if (!node.isLeaf() && node.getChildren().size() == 1) { harvestData(node.getChildren().get(0), data); } } class ClauseData { private ModifierQueryNode.Modifier modifier; private Float boost; private QueryNode lastValidNode; private boolean keepOutmost = true; // change this to false if you want that // modifiers that are closer to the clause are applied to ti private int levelsDeep = 0; ClauseData() { } ClauseData(ModifierQueryNode.Modifier mod, Float boost) { this.modifier = mod; this.boost = boost; } public ModifierQueryNode.Modifier getModifier() { return modifier; } public void setModifier(ModifierQueryNode.Modifier modifier) { if (keepOutmost && this.modifier != null) { return; } this.modifier = modifier; } public Float getBoost() { return boost; } public void setBoost(Float boost) { if (keepOutmost && this.boost != null) { return; } this.boost = boost; } public QueryNode getLastChild() { return lastValidNode; } public void setLastChild(QueryNode lastNonClause) { this.lastValidNode = lastNonClause; } public int getLevelsDeep() { return levelsDeep; } public void addLevelsDeep() { this.levelsDeep++; } } }