/** * Copyright (c) 2002-2014 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.index.lucene; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.NumericRangeQuery; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; import org.neo4j.graphdb.index.Index; import org.neo4j.graphdb.index.IndexHits; import org.neo4j.index.impl.lucene.LuceneUtil; /** * This class has the extra query configuration to use * with {@link Index#query(Object)} and {@link Index#query(String, Object)}. * It allows a query to have sorting, default operators, and allows the engine * to turn of searching of modifications made inside a transaction, * to gain performance. */ public class QueryContext { private final Object queryOrQueryObject; private Sort sorting; private QueryParser.Operator defaultOperator; private boolean tradeCorrectnessForSpeed; private int topHits; public QueryContext( Object queryOrQueryObject ) { this.queryOrQueryObject = queryOrQueryObject; } /** * @return the query (or query object) specified in the constructor. */ public Object getQueryOrQueryObject() { return queryOrQueryObject; } /** * Returns a QueryContext with sorting added to it. * * @param sorting The sorting to be used * @return A QueryContext with the sorting applied. */ public QueryContext sort( Sort sorting ) { this.sorting = sorting; return this; } /** * Returns a QueryContext with sorting added to it. * * @param key The key to sort on. * @param additionalKeys Any additional keys to sort on. * @return A QueryContext with sorting added to it. */ public QueryContext sort( String key, String... additionalKeys ) { SortField firstSortField = new SortField( key, SortField.Type.STRING ); if ( additionalKeys.length == 0 ) { return sort( new Sort( firstSortField ) ); } SortField[] sortFields = new SortField[1+additionalKeys.length]; sortFields[0] = firstSortField; for ( int i = 0; i < additionalKeys.length; i++ ) { sortFields[1+i] = new SortField( additionalKeys[i], SortField.Type.STRING ); } return sort( new Sort( sortFields ) ); } /** * @return a QueryContext with sorting by relevance, i.e. sorted after which * score each hit has. */ public QueryContext sortByScore() { return sort( Sort.RELEVANCE ); } /** * Sort the results of a numeric range query if the query in this context * is a {@link NumericRangeQuery}, see {@link #numericRange(String, Number, Number)}, * Otherwise an {@link IllegalStateException} will be thrown. * * @param key the key to sort on. * @param reversed if the sort order should be reversed or not. {@code true} * for lowest first (ascending), {@code false} for highest first (descending) * @return a QueryContext with sorting by numeric value. */ public QueryContext sortNumeric( String key, boolean reversed ) { if ( !( queryOrQueryObject instanceof NumericRangeQuery ) ) { throw new IllegalStateException( "Not a numeric range query" ); } Number number = ((NumericRangeQuery)queryOrQueryObject).getMin(); number = number != null ? number : ((NumericRangeQuery)queryOrQueryObject).getMax(); SortField.Type fieldType = SortField.Type.INT; if ( number instanceof Long ) { fieldType = SortField.Type.LONG; } else if ( number instanceof Float ) { fieldType = SortField.Type.FLOAT; } else if ( number instanceof Double ) { fieldType = SortField.Type.DOUBLE; } sort( new Sort( new SortField( key, fieldType, reversed ) ) ); return this; } /** * Returns the sorting setting for this context. * * @return the sorting set with one of the sort methods, f.ex * {@link #sort(Sort)} or {@link #sortByScore()} */ public Sort getSorting() { return this.sorting; } /** * Changes the the default operator used between terms in compound queries, * default is OR. * * @param defaultOperator The new operator to use. * @return A QueryContext with the new default operator applied. */ public QueryContext defaultOperator( QueryParser.Operator defaultOperator ) { this.defaultOperator = defaultOperator; return this; } /** * Returns the default operator used between terms in compound queries. * * @return the default {@link QueryParser.Operator} specified with {@link #defaultOperator(Operator)} * or "OR" if none specified. */ public QueryParser.Operator getDefaultOperator() { return this.defaultOperator; } /** * Adding to or removing from an index affects results from query methods * inside the same transaction, even before those changes are committed. * To let those modifications be visible in query results, some rather heavy * operations may have to be done, which can be slow to complete. * * The default behaviour is that these modifications are visible, but using * this method will tell the query to not strive to include the absolutely * latest modifications, so that such a performance penalty can be avoided. * * @return A QueryContext which doesn't necessarily include the latest * transaction modifications in the results, but may perform faster. */ public QueryContext tradeCorrectnessForSpeed() { this.tradeCorrectnessForSpeed = true; return this; } /** * Returns {@code true} if this context is set to prioritize speed over * the inclusion of transactional state in the results. * @return whether or not {@link #tradeCorrectnessForSpeed()} has been called. */ public boolean getTradeCorrectnessForSpeed() { return tradeCorrectnessForSpeed; } /** * Makes use of {@link IndexSearcher#search(org.apache.lucene.search.Query, int)}, * alternatively {@link IndexSearcher#search(org.apache.lucene.search.Query, org.apache.lucene.search.Filter, int, Sort)} * where only the top {@code numberOfTopHits} hits are returned. Default * behavior is to return all hits, although lazily retrieved from lucene all * the way up to the {@link IndexHits} iterator. * * @param numberOfTopHits the maximum number of top hits to return. * @return A {@link QueryContext} with the number of top hits set. */ public QueryContext top( int numberOfTopHits ) { this.topHits = numberOfTopHits; return this; } /** * Return the max number of results to be returned. * * @return the top hits set with {@link #top(int)}. */ public int getTop() { return this.topHits; } /** * Will create a {@link QueryContext} with a query for numeric ranges, that is * values that have been indexed using {@link ValueContext#indexNumeric()}. * {@code from} (lower) and {@code to} (higher) bounds are inclusive. * It will match the type of numbers supplied to the type of values that * are indexed in the index, f.ex. long, int, float and double. * If both {@code from} and {@code to} is {@code null} then it will default * to int. * * @param key the property key to query. * @param from the low end of the range (inclusive) * @param to the high end of the range (inclusive) * @return a {@link QueryContext} to do numeric range queries with. */ public static QueryContext numericRange( String key, Number from, Number to ) { return numericRange( key, from, to, true, true ); } /** * Will create a {@link QueryContext} with a query for numeric ranges, that is * values that have been indexed using {@link ValueContext#indexNumeric()}. * It will match the type of numbers supplied to the type of values that * are indexed in the index, f.ex. long, int, float and double. * If both {@code from} and {@code to} is {@code null} then it will default * to int. * * @param key the property key to query. * @param from the low end of the range (inclusive) * @param to the high end of the range (inclusive) * @param includeFrom whether or not {@code from} (the lower bound) is inclusive * or not. * @param includeTo whether or not {@code to} (the higher bound) is inclusive * or not. * @return a {@link QueryContext} to do numeric range queries with. */ public static QueryContext numericRange( String key, Number from, Number to, boolean includeFrom, boolean includeTo ) { return new QueryContext( LuceneUtil.rangeQuery( key, from, to, includeFrom, includeTo ) ); } }