Skip to content

Commit

Permalink
support multiple q parameters in one call.
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert Newson committed Jan 4, 2010
1 parent b248025 commit 766d874
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 55 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ The following parameters can be passed for more sophisticated searches;
<dt>force_json<dt><dd>Usually couchdb-lucene determines the Content-Type of its response based on the presence of the Accept header. If Accept contains "application/json", you get "application/json" in the response, otherwise you get "text/plain;charset=utf8". Some tools, like JSONView for FireFox, do not send the Accept header but do render "application/json" responses if received. Setting force_json=true forces all response to "application/json" regardless of the Accept header.</dd>
<dt>include_docs</dt><dd>whether to include the source docs</dd>
<dt>limit</dt><dd>the maximum number of results to return</dd>
<dt>q</dt><dd>the query to run (e.g, subject:hello). If not specified, the default field is searched.</dd>
<dt>q</dt><dd>the query to run (e.g, subject:hello). If not specified, the default field is searched. Multiple q parameters are permitted, the resulting JSON will be an array of responses.</dd>
<dt>skip</dt><dd>the number of results to skip</dd>
<dt>sort</dt><dd>the comma-separated fields to sort on. Prefix with / for ascending order and \ for descending order (ascending is the default if not specified). Type-specific sorting is also available by appending a : and the sort type as normal (e.g, 'sort=amount:float'). Supported types are 'float', 'double', 'int', 'long' and 'date'.</dd>
<dt>stale=ok</dt><dd>If you set the <i>stale</i> option to <i>ok</i>, couchdb-lucene may not perform any refreshing on the index. Searches may be faster as Lucene caches important data (especially for sorting). A query without stale=ok will use the latest data committed to the index.</dd>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public CustomQueryParser(final Version matchVersion, final String f, final Analy
super(matchVersion, f, a);
}

public Sort toSort(final String sort) {
public static Sort toSort(final String sort) {
if (sort == null) {
return null;
} else {
Expand Down Expand Up @@ -69,7 +69,7 @@ public Sort toSort(final String sort) {
}
}

public String toString(final SortField[] sortFields) {
public static String toString(final SortField[] sortFields) {
final JSONArray result = new JSONArray();
for (final SortField field : sortFields) {
final JSONObject col = new JSONObject();
Expand Down
24 changes: 14 additions & 10 deletions src/main/java/com/github/rnewson/couchdb/lucene/QueryPlan.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,62 +17,66 @@
* @author robertnewson
*
*/
public class QueryPlan {
public final class QueryPlan {

private QueryPlan() {

}

/**
* Produces a string representation of the query classes used for a query.
*
* @param query
* @return
*/
public String toPlan(final Query query) {
public static String toPlan(final Query query) {
final StringBuilder builder = new StringBuilder(300);
toPlan(builder, query);
return builder.toString();
}

private void planBooleanQuery(final StringBuilder builder, final BooleanQuery query) {
private static void planBooleanQuery(final StringBuilder builder, final BooleanQuery query) {
for (final BooleanClause clause : query.getClauses()) {
builder.append(clause.getOccur());
toPlan(builder, clause.getQuery());
}
}

private void planFuzzyQuery(final StringBuilder builder, final FuzzyQuery query) {
private static void planFuzzyQuery(final StringBuilder builder, final FuzzyQuery query) {
builder.append(query.getTerm());
builder.append(",prefixLength=");
builder.append(query.getPrefixLength());
builder.append(",minSimilarity=");
builder.append(query.getMinSimilarity());
}

private void planNumericRangeQuery(final StringBuilder builder, final NumericRangeQuery<?> query) {
private static void planNumericRangeQuery(final StringBuilder builder, final NumericRangeQuery<?> query) {
builder.append(query.getMin());
builder.append(" TO ");
builder.append(query.getMax());
builder.append(" AS ");
builder.append(query.getMin().getClass().getSimpleName());
}

private void planPrefixQuery(final StringBuilder builder, final PrefixQuery query) {
private static void planPrefixQuery(final StringBuilder builder, final PrefixQuery query) {
builder.append(query.getPrefix());
}

private void planTermQuery(final StringBuilder builder, final TermQuery query) {
private static void planTermQuery(final StringBuilder builder, final TermQuery query) {
builder.append(query.getTerm());
}

private void planTermRangeQuery(final StringBuilder builder, final TermRangeQuery query) {
private static void planTermRangeQuery(final StringBuilder builder, final TermRangeQuery query) {
builder.append(query.getLowerTerm());
builder.append(" TO ");
builder.append(query.getUpperTerm());
}

private void planWildcardQuery(final StringBuilder builder, final WildcardQuery query) {
private static void planWildcardQuery(final StringBuilder builder, final WildcardQuery query) {
builder.append(query.getTerm());
}

private void toPlan(final StringBuilder builder, final Query query) {
private static void toPlan(final StringBuilder builder, final Query query) {
builder.append(query.getClass().getSimpleName());
builder.append("(");
if (query instanceof TermQuery) {
Expand Down
96 changes: 54 additions & 42 deletions src/main/java/com/github/rnewson/couchdb/lucene/SearchServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.apache.http.client.HttpClient;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.FieldDoc;
Expand Down Expand Up @@ -68,7 +70,7 @@ protected void doGet(final HttpServletRequest req, final HttpServletResponse res
}

final boolean debug = getBooleanParameter(req, "debug");
final boolean rewrite_query = getBooleanParameter(req, "rewrite_query");
final boolean rewrite_query = getBooleanParameter(req, "rewrite");
final boolean staleOk = Utils.getStaleOk(req);

final IndexPath path = IndexPath.parse(req);
Expand All @@ -93,24 +95,55 @@ public void callback(final IndexSearcher searcher, final String version) throws
final Analyzer analyzer = Analyzers.getAnalyzer(getParameter(req, "analyzer", "standard"));
final CustomQueryParser parser = new CustomQueryParser(Version.LUCENE_CURRENT, Constants.DEFAULT_FIELD, analyzer);

final Query q;
final String[] queries = req.getParameterValues("q");
final JSONArray arr = new JSONArray();

for (final String query : queries) {
try {
arr.add(performQuery(debug, rewrite_query, searcher, version, parser.parse(query)));
} catch (final ParseException e) {
ServletUtils.sendJSONError(req, resp, 400, "Bad query syntax");
return;
}
}

Utils.setResponseContentTypeAndEncoding(req, resp);

// Cache-related headers.
resp.setHeader("ETag", version);
resp.setHeader("Cache-Control", "must-revalidate");

final JSON json = arr.size() > 1 ? arr : arr.getJSONObject(0);

// Format response body.
final String callback = req.getParameter("callback");
final String body;
if (callback != null) {
body = String.format("%s(%s)", callback, json);
} else {
body = json.toString(debug ? 2 : 0);
}

final Writer writer = resp.getWriter();
try {
q = parser.parse(req.getParameter("q"));
} catch (final ParseException e) {
ServletUtils.sendJSONError(req, resp, 400, "Bad query syntax");
return;
writer.write(body);
} finally {
writer.close();
}
}

final JSONObject json = new JSONObject();
json.put("q", q.toString());
private JSONObject performQuery(final boolean debug, final boolean rewrite_query, final IndexSearcher searcher,
final String version, final Query q) throws IOException, CorruptIndexException {
final JSONObject result = new JSONObject();
result.put("q", q.toString());
if (debug) {
json.put("plan", new QueryPlan().toPlan(q));
result.put("plan", QueryPlan.toPlan(q));
}
json.put("etag", version);
result.put("etag", version);

if (rewrite_query) {
final Query rewritten_q = q.rewrite(searcher.getIndexReader());
json.put("rewritten_q", rewritten_q.toString());
result.put("rewritten_q", rewritten_q.toString());

final JSONObject freqs = new JSONObject();

Expand All @@ -120,15 +153,15 @@ public void callback(final IndexSearcher searcher, final String version) throws
final int freq = searcher.docFreq((Term) term);
freqs.put(term, freq);
}
json.put("freqs", freqs);
result.put("freqs", freqs);
} else {
// Perform the search.
final TopDocs td;
final StopWatch stopWatch = new StopWatch();

final boolean include_docs = getBooleanParameter(req, "include_docs");
final int limit = getIntParameter(req, "limit", 25);
final Sort sort = parser.toSort(req.getParameter("sort"));
final Sort sort = CustomQueryParser.toSort(req.getParameter("sort"));
final int skip = getIntParameter(req, "skip", 0);

if (sort == null) {
Expand Down Expand Up @@ -213,39 +246,18 @@ public void callback(final IndexSearcher searcher, final String version) throws
}
stopWatch.lap("fetch");

json.put("skip", skip);
json.put("limit", limit);
json.put("total_rows", td.totalHits);
json.put("search_duration", stopWatch.getElapsed("search"));
json.put("fetch_duration", stopWatch.getElapsed("fetch"));
result.put("skip", skip);
result.put("limit", limit);
result.put("total_rows", td.totalHits);
result.put("search_duration", stopWatch.getElapsed("search"));
result.put("fetch_duration", stopWatch.getElapsed("fetch"));
// Include sort info (if requested).
if (td instanceof TopFieldDocs) {
json.put("sort_order", parser.toString(((TopFieldDocs) td).fields));
result.put("sort_order", CustomQueryParser.toString(((TopFieldDocs) td).fields));
}
json.put("rows", rows);
}

Utils.setResponseContentTypeAndEncoding(req, resp);

// Cache-related headers.
resp.setHeader("ETag", version);
resp.setHeader("Cache-Control", "must-revalidate");

// Format response body.
final String callback = req.getParameter("callback");
final String body;
if (callback != null) {
body = String.format("%s(%s)", callback, json);
} else {
body = json.toString(debug ? 2 : 0);
}

final Writer writer = resp.getWriter();
try {
writer.write(body);
} finally {
writer.close();
result.put("rows", rows);
}
return result;
}

public void onMissing() throws IOException {
Expand Down

0 comments on commit 766d874

Please sign in to comment.