欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

solr对docValue的使用

程序员文章站 2022-04-03 19:10:23
...

前面写了十来篇博客都是介绍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的。