solr对docValue的使用
前面写了十来篇博客都是介绍lucene的docValue的,有五中docValue,但是在solr里面却只有一个配置,即在schema.xml中的<field>中添加docValues=true,那么solr在建立索引的时候到底是使用了什么类型的docValue呢?看下源码就知道了。
solr在添加document的时候,是使用了DirectUpdateHandler2这个类的
public int addDoc(AddUpdateCommand cmd)
方法,从这个类中一步一步的debug,终于知道了solr是如何使用docValue的。solr在添加document的时候,要将客户端传递过来的SolrInputDocument转变为lucene的document,将solr的field变为lucene的field,对docValue的使用就在这里,上面的addDoc方法的参数cmd中,就有获得lucene的document的方法,AddUpdateCommand.getLuceneDocument()
public Document getLuceneDocument() { return DocumentBuilder.toDocument(getSolrInputDocument(), req.getSchema()); }
上面的方法的名字很形象的说明了是要获得lucene的document,在toDocument方法里面就有根据solr的chemafield 转变为lucene的field的方法,如下:
// private static void addField(Document doc, SchemaField field, Object val, float boost) { if (val instanceof IndexableField) { // set boost to the calculated compound boost ((Field) val).setBoost(boost); doc.add((Field) val); return; } for (IndexableField f : field.getType().createFields(field, val, boost)) { if (f != null) doc.add((Field) f); // null fields are not added } }lei
上面中最关键的就是field.getType.createFields方法,即根据SchemaField的FieldType获得lucene的field,所以只需要看一下几个常用的FieldType就可以了,在工作中最常用的是三个,一个是数字类型的,一个是字符串(String)类型的,一个是Text类型的(也是字符串换,不过Text类型的是分词的)。
1、数字类型的:
对于int,float,long,double,还是tint,tfloat,tlong,tdouble,他们的父类都是TrieField,完整名称是:org.apache.solr.schema.TrieField,看看他的createFields(SchemaField, Object, float)方法:
public List<IndexableField> createFields(SchemaField sf, Object value, float boost) { if (sf.hasDocValues()) { //在schema中含有docValue的情况 List<IndexableField> fields = new ArrayList<>(); final IndexableField field = createField(sf, value, boost);//这个是根据配置产生一个intField或者longFiled,这个是不带有docValue的。具体看下一个方法 fields.add(field); if (sf.multiValued()) {//如果这个值是multiValued的 BytesRef bytes = new BytesRef(); readableToIndexed(value.toString(), bytes); fields.add(new SortedSetDocValuesField(sf.getName(), bytes));//再创建一个SortedSetDocValue的field。 } else {//如果是单值域的 final long bits; if (field.numericValue() instanceof Integer || field.numericValue() instanceof Long) {// bits = field.numericValue().longValue(); } else if (field.numericValue() instanceof Float) { bits = Float.floatToIntBits(field.numericValue().floatValue()); } else { assert field.numericValue() instanceof Double; bits = Double.doubleToLongBits(field.numericValue().doubleValue()); } fields.add(new NumericDocValuesField(sf.getName(), bits));//创建一个简单的NumericDocValue } return fields; } else {//没有docValue的情况 return Collections.singletonList(createField(sf, value, boost)); } }
在上面可以看到,如果这个域是docValue的(即在schemField中写了docValues=true),则会额外的创建一个docValue的域,如果是多值域的,则会创建一个SortedSetDocValued类型的,最后返回的list里面含有两个field;如果是单值域,则会创建一个NumericDocValue类型的。
下面是调用的产生一个不带有docValue的field的方法,
public IndexableField createField(SchemaField field, Object value, float boost) { boolean indexed = field.indexed(); boolean stored = field.stored(); boolean docValues = field.hasDocValues(); if (!indexed && !stored && !docValues) {//如果不建立索引,或者不存储,或者没有dcoValues则不会创建任何东西,换句话说,如果一个域要想有意义,则至少简历索引,或者至少保存或者简历docValue。 if (log.isTraceEnabled()) log.trace("Ignoring unindexed/unstored field: " + field); return null; } FieldType ft = new FieldType();//创建fieldType,可以发现,在数字类型的fieldType中,仅仅是保存了五个属性,存储、分词、建索引、忽略标准因子、倒排表的格式,不过是没有词向量的 ft.setStored(stored); ft.setTokenized(true); ft.setIndexed(indexed); ft.setOmitNorms(field.omitNorms()); ft.setIndexOptions(getIndexOptions(field, value.toString())); switch (type) { case INTEGER: ft.setNumericType(NumericType.INT); break; case FLOAT: ft.setNumericType(NumericType.FLOAT); break; case LONG: ft.setNumericType(NumericType.LONG); break; case DOUBLE: ft.setNumericType(NumericType.DOUBLE); break; case DATE: ft.setNumericType(NumericType.LONG); break; default: throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown type for trie field: " + type); } ft.setNumericPrecisionStep(precisionStep); final org.apache.lucene.document.Field f; switch (type) { case INTEGER: int i = (value instanceof Number) ? ((Number) value).intValue() : Integer.parseInt(value.toString()); f = new org.apache.lucene.document.IntField(field.getName(), i, ft);//创建具体的域,将ft作为一个参数传入。 break; case FLOAT: float fl = (value instanceof Number) ? ((Number) value).floatValue() : Float.parseFloat(value.toString()); f = new org.apache.lucene.document.FloatField(field.getName(), fl, ft); break; case LONG: long l = (value instanceof Number) ? ((Number) value).longValue() : Long.parseLong(value.toString()); f = new org.apache.lucene.document.LongField(field.getName(), l, ft); break; case DOUBLE: double d = (value instanceof Number) ? ((Number) value).doubleValue() : Double.parseDouble(value.toString()); f = new org.apache.lucene.document.DoubleField(field.getName(), d, ft); break; case DATE: Date date = (value instanceof Date) ? ((Date) value) : dateField.parseMath(null, value.toString()); f = new org.apache.lucene.document.LongField(field.getName(), date.getTime(), ft); break; default: throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown type for trie field: " + type); } f.setBoost(boost); return f; }
总结一下数字类型的schemaField,如果没有dcoValues,则仅仅会根据类型创建一个普通类型的lucene的field, 如果有docValue的话,则会创建两个field,一个是普通的field,第二个是含有docValue的,这两个field含有相同的名字和内容,在建立索引的时候会同时使用两个域。如果是多值域的,则会创建一个SortedSetDocValue,如果是单值域的,则会创建一个NumericDocValue的。在创建一个普通的不带有docValue的field的时候,仅仅会保存五个属性,不过没有词向量。
2、String类型的
String类型的是不分词的,他也是有docValue的,对应的fieldType是:org.apache.solr.schema.StrField。看看他的createFields方法吧
public List<IndexableField> createFields(SchemaField field, Object value, float boost) { if (field.hasDocValues()) {//如果含有docValue List<IndexableField> fields = new ArrayList<>(); fields.add(createField(field, value, boost));//普通的value final BytesRef bytes = new BytesRef(value.toString()); if (field.multiValued()) {//如果是多值域的,则会创建一个SortedSetDocValue fields.add(new SortedSetDocValuesField(field.getName(), bytes)); } else { fields.add(new SortedDocValuesField(field.getName(), bytes));//如果是单值域的,则会创建一个SortedDocValue。 } return fields; } else { return Collections.singletonList(createField(field, value, boost)); } }
另外,String类型的createField方法没有覆写FieldType的自己的createField方法,如下:
public IndexableField createField(SchemaField field, Object value, float boost) { if (!field.indexed() && !field.stored()) { if (log.isTraceEnabled()) log.trace("Ignoring unindexed/unstored field: " + field); return null; } String val; try { val = toInternal(value.toString()); } catch (RuntimeException e) { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,"Error while creating field '" + field + "' from value '" + value + "'", e); } if (val == null) return null; org.apache.lucene.document.FieldType newType = new org.apache.lucene.document.FieldType(); newType.setIndexed(field.indexed());//是否建立索引 newType.setTokenized(field.isTokenized());//是否分词 newType.setStored(field.stored());//是否存储 newType.setOmitNorms(field.omitNorms());//是否忽略标准因子 newType.setIndexOptions(getIndexOptions(field, val));//倒排表的格式 newType.setStoreTermVectors(field.storeTermVector());//是否存储词向量 newType.setStoreTermVectorOffsets(field.storeTermOffsets());//是否存储词向量的偏移量 newType.setStoreTermVectorPositions(field.storeTermPositions());//是否存储词向量的位置增量。可以发现字符串类型的是可以记录词向量的。 return createField(field.getName(), val, newType, boost);//这个很简单,忽略 }
总结一下字符串类型的,如果有docValue的话,也是有两个域,一个是普通的没有docValue的域,一个是含有docValue的域,含有docValue的域按照是否是多值域,分为sortedSetDocValue和SortedDocValue。另外和数字类型的field不同的是,字符串类型的是可以有词向量的。
3、Text类型的
这个类是可以分词的域,根据常识的话,他不会使用docValue,因为一个域既然分词的话,做facet、sort是没有意义的,不过还是看下代码吧。
在TextField的类中没有覆盖FieldType的createFields方法,所以我们看下FieldType的createFields方法:
public List<IndexableField> createFields(SchemaField field, Object value, float boost) { IndexableField f = createField(field, value, boost); if (field.hasDocValues() && f.fieldType().docValueType() == null) { throw new UnsupportedOperationException("This field type does not support doc values: " + this); } return f == null ? Collections.<IndexableField> emptyList() : Collections.singletonList(f); }
他也会调用createField(注意没有s)方法,但是在TextField中也没有覆写createField方法, 从上面的String类型的createField方法来看,如果不覆盖FieldType中的createField方法,是不会创建一个含有docValue的field的。这和常识也是匹配的。
如此简单,看完了solr在建立索引的时候是如何使用docValue的。
下一篇: PHP,Mysql日期和时间整理