blob: a51979840a4016ce749faa68df1b0380f29e11ea [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.response.transform;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryWrapperFilter;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.search.join.FixedBitSetCachingWrapperFilter;
import org.apache.lucene.search.join.ToChildBlockJoinQuery;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.UnicodeUtil;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.ResponseWriterUtil;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.DocIterator;
import org.apache.solr.search.DocList;
import org.apache.solr.search.QParser;
import org.apache.solr.search.SyntaxError;
/**
*
* @since solr 4.9
*
* This transformer returns all descendants of each parent document in a flat list nested inside the parent document.
*
*
* The "parentFilter" parameter is mandatory.
* Optionally you can provide a "childFilter" param to filter out which child documents should be returned and a
* "limit" param which provides an option to specify the number of child documents
* to be returned per parent document. By default it's set to 10.
*
* Examples -
* [child parentFilter="fieldName:fieldValue"]
* [child parentFilter="fieldName:fieldValue" childFilter="fieldName:fieldValue"]
* [child parentFilter="fieldName:fieldValue" childFilter="fieldName:fieldValue" limit=20]
*/
public class ChildDocTransformerFactory extends TransformerFactory {
@Override
public DocTransformer create(String field, SolrParams params, SolrQueryRequest req) {
SchemaField uniqueKeyField = req.getSchema().getUniqueKeyField();
if(uniqueKeyField == null) {
throw new SolrException( ErrorCode.BAD_REQUEST,
" ChildDocTransformer requires the schema to have a uniqueKeyField." );
}
String parentFilter = params.get( "parentFilter" );
if( parentFilter == null ) {
throw new SolrException( ErrorCode.BAD_REQUEST, "Parent filter should be sent as parentFilter=filterCondition" );
}
String childFilter = params.get( "childFilter" );
int limit = params.getInt( "limit", 10 );
Filter parentsFilter = null;
try {
Query parentFilterQuery = QParser.getParser( parentFilter, null, req).getQuery();
parentsFilter = new FixedBitSetCachingWrapperFilter(new QueryWrapperFilter(parentFilterQuery));
} catch (SyntaxError syntaxError) {
throw new SolrException( ErrorCode.BAD_REQUEST, "Failed to create correct parent filter query" );
}
Query childFilterQuery = null;
if(childFilter != null) {
try {
childFilterQuery = QParser.getParser( childFilter, null, req).getQuery();
} catch (SyntaxError syntaxError) {
throw new SolrException( ErrorCode.BAD_REQUEST, "Failed to create correct child filter query" );
}
}
return new ChildDocTransformer( field, parentsFilter, uniqueKeyField, req.getSchema(), childFilterQuery, limit);
}
}
class ChildDocTransformer extends TransformerWithContext {
private final String name;
private final SchemaField idField;
private final IndexSchema schema;
private Filter parentsFilter;
private Query childFilterQuery;
private int limit;
public ChildDocTransformer( String name, final Filter parentsFilter,
final SchemaField idField, IndexSchema schema,
final Query childFilterQuery, int limit) {
this.name = name;
this.idField = idField;
this.schema = schema;
this.parentsFilter = parentsFilter;
this.childFilterQuery = childFilterQuery;
this.limit = limit;
}
@Override
public String getName() {
return name;
}
@Override
public void transform(SolrDocument doc, int docid) {
FieldType idFt = idField.getType();
Object parentIdField = doc.getFirstValue(idField.getName());
String parentIdExt = parentIdField instanceof IndexableField
? idFt.toExternal((IndexableField)parentIdField)
: parentIdField.toString();
try {
Query parentQuery = idFt.getFieldQuery(null, idField, parentIdExt);
Query query = new ToChildBlockJoinQuery(parentQuery, parentsFilter, false);
DocList children = context.searcher.getDocList(query, childFilterQuery, new Sort(), 0, limit);
if(children.matches() > 0) {
DocIterator i = children.iterator();
while(i.hasNext()) {
Integer childDocNum = i.next();
Document childDoc = context.searcher.doc(childDocNum);
SolrDocument solrChildDoc = ResponseWriterUtil.toSolrDocument(childDoc, schema);
// TODO: future enhancement...
// support an fl local param in the transformer, which is used to build
// a private ReturnFields instance that we use to prune unwanted field
// names from solrChildDoc
doc.addChildDocument(solrChildDoc);
}
}
} catch (IOException e) {
doc.put(name, "Could not fetch child Documents");
}
}
}