You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by mi...@apache.org on 2019/01/03 23:44:53 UTC

lucene-solr:master: LUCENE-8601: attributes added to IndexableFieldType during indexing will now be preserved in the index and accessible at search time via FieldInfo attributes

Repository: lucene-solr
Updated Branches:
  refs/heads/master ec43d100d -> 63dfba4c7


LUCENE-8601: attributes added to IndexableFieldType during indexing will now be preserved in the index and accessible at search time via FieldInfo attributes


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/63dfba4c
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/63dfba4c
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/63dfba4c

Branch: refs/heads/master
Commit: 63dfba4c7d81a019a4008777beace0d391987ceb
Parents: ec43d10
Author: Mike McCandless <mi...@apache.org>
Authored: Thu Jan 3 18:44:41 2019 -0500
Committer: Mike McCandless <mi...@apache.org>
Committed: Thu Jan 3 18:44:41 2019 -0500

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |  4 ++
 .../perfield/PerFieldDocValuesFormat.java       | 46 ++++++++++-------
 .../codecs/perfield/PerFieldPostingsFormat.java | 19 ++-----
 .../org/apache/lucene/document/FieldType.java   | 31 ++++++++++++
 .../lucene/index/DefaultIndexingChain.java      |  5 ++
 .../java/org/apache/lucene/index/FieldInfo.java | 10 ++--
 .../org/apache/lucene/index/FieldInfos.java     | 11 ++++-
 .../apache/lucene/index/IndexableFieldType.java | 12 +++++
 .../org/apache/lucene/index/TestFieldInfos.java | 52 ++++++++++++++++++++
 .../apache/lucene/index/TestIndexableField.java |  6 +++
 .../org/apache/solr/schema/SchemaField.java     |  5 ++
 11 files changed, 163 insertions(+), 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/63dfba4c/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index e8820cb..c68af3e 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -260,6 +260,10 @@ New Features
   IndexWriterConfig#setIndexCreatedVersionMajor. This is an expert feature.
   (Adrien Grand)
 
+* LUCENE-8601: Attributes set in the IndexableFieldType for each field during indexing will
+  now be recorded into the corresponding FieldInfo's attributes, accessible at search
+  time (Murali Krishna P)
+
 Improvements
 
 * LUCENE-8463: TopFieldCollector can now early-terminates queries when sorting by SortField.DOC.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/63dfba4c/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldDocValuesFormat.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldDocValuesFormat.java b/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldDocValuesFormat.java
index eef232c..f439699 100644
--- a/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldDocValuesFormat.java
+++ b/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldDocValuesFormat.java
@@ -135,7 +135,8 @@ public abstract class PerFieldDocValuesFormat extends DocValuesFormat {
 
       // Group each consumer by the fields it handles
       for (FieldInfo fi : mergeState.mergeFieldInfos) {
-        DocValuesConsumer consumer = getInstance(fi);
+        // merge should ignore current format for the fields being merged
+        DocValuesConsumer consumer = getInstance(fi, true);
         Collection<String> fieldsForConsumer = consumersToField.get(consumer);
         if (fieldsForConsumer == null) {
           fieldsForConsumer = new ArrayList<>();
@@ -156,9 +157,23 @@ public abstract class PerFieldDocValuesFormat extends DocValuesFormat {
     }
 
     private DocValuesConsumer getInstance(FieldInfo field) throws IOException {
+      return getInstance(field, false);
+    }
+
+    /**
+     * DocValuesConsumer for the given field.
+     * @param field - FieldInfo object.
+     * @param ignoreCurrentFormat - ignore the existing format attributes.
+     * @return DocValuesConsumer for the field.
+     * @throws IOException if there is a low-level IO error
+     */
+    private DocValuesConsumer getInstance(FieldInfo field, boolean ignoreCurrentFormat) throws IOException {
       DocValuesFormat format = null;
       if (field.getDocValuesGen() != -1) {
-        final String formatName = field.getAttribute(PER_FIELD_FORMAT_KEY);
+        String formatName = null;
+        if (ignoreCurrentFormat == false) {
+          formatName = field.getAttribute(PER_FIELD_FORMAT_KEY);
+        }
         // this means the field never existed in that segment, yet is applied updates
         if (formatName != null) {
           format = DocValuesFormat.forName(formatName);
@@ -171,21 +186,19 @@ public abstract class PerFieldDocValuesFormat extends DocValuesFormat {
         throw new IllegalStateException("invalid null DocValuesFormat for field=\"" + field.name + "\"");
       }
       final String formatName = format.getName();
-      
-      String previousValue = field.putAttribute(PER_FIELD_FORMAT_KEY, formatName);
-      if (field.getDocValuesGen() == -1 && previousValue != null) {
-        throw new IllegalStateException("found existing value for " + PER_FIELD_FORMAT_KEY + 
-                                        ", field=" + field.name + ", old=" + previousValue + ", new=" + formatName);
-      }
-      
+
+      field.putAttribute(PER_FIELD_FORMAT_KEY, formatName);
       Integer suffix = null;
-      
+
       ConsumerAndSuffix consumer = formats.get(format);
       if (consumer == null) {
         // First time we are seeing this format; create a new instance
 
         if (field.getDocValuesGen() != -1) {
-          final String suffixAtt = field.getAttribute(PER_FIELD_SUFFIX_KEY);
+          String suffixAtt = null;
+          if (!ignoreCurrentFormat) {
+            suffixAtt = field.getAttribute(PER_FIELD_SUFFIX_KEY);
+          }
           // even when dvGen is != -1, it can still be a new field, that never
           // existed in the segment, and therefore doesn't have the recorded
           // attributes yet.
@@ -193,7 +206,7 @@ public abstract class PerFieldDocValuesFormat extends DocValuesFormat {
             suffix = Integer.valueOf(suffixAtt);
           }
         }
-        
+
         if (suffix == null) {
           // bump the suffix
           suffix = suffixes.get(formatName);
@@ -204,7 +217,7 @@ public abstract class PerFieldDocValuesFormat extends DocValuesFormat {
           }
         }
         suffixes.put(formatName, suffix);
-        
+
         final String segmentSuffix = getFullSegmentSuffix(segmentWriteState.segmentSuffix,
                                                           getSuffix(formatName, Integer.toString(suffix)));
         consumer = new ConsumerAndSuffix();
@@ -216,13 +229,8 @@ public abstract class PerFieldDocValuesFormat extends DocValuesFormat {
         assert suffixes.containsKey(formatName);
         suffix = consumer.suffix;
       }
-      
-      previousValue = field.putAttribute(PER_FIELD_SUFFIX_KEY, Integer.toString(suffix));
-      if (field.getDocValuesGen() == -1 && previousValue != null) {
-        throw new IllegalStateException("found existing value for " + PER_FIELD_SUFFIX_KEY + 
-                                        ", field=" + field.name + ", old=" + previousValue + ", new=" + suffix);
-      }
 
+      field.putAttribute(PER_FIELD_SUFFIX_KEY, Integer.toString(suffix));
       // TODO: we should only provide the "slice" of FIS
       // that this DVF actually sees ...
       return consumer.consumer;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/63dfba4c/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldPostingsFormat.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldPostingsFormat.java b/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldPostingsFormat.java
index 9ac0fe2..88ae6da 100644
--- a/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldPostingsFormat.java
+++ b/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldPostingsFormat.java
@@ -188,14 +188,14 @@ public abstract class PerFieldPostingsFormat extends PostingsFormat {
       // Assign field -> PostingsFormat
       for(String field : indexedFieldNames) {
         FieldInfo fieldInfo = writeState.fieldInfos.fieldInfo(field);
-
+        // TODO: This should check current format from the field attribute?
         final PostingsFormat format = getPostingsFormatForField(field);
-  
+
         if (format == null) {
           throw new IllegalStateException("invalid null PostingsFormat for field=\"" + field + "\"");
         }
         String formatName = format.getName();
-      
+
         FieldsGroup group = formatToGroups.get(format);
         if (group == null) {
           // First time we are seeing this format; create a
@@ -226,17 +226,8 @@ public abstract class PerFieldPostingsFormat extends PostingsFormat {
 
         group.fields.add(field);
 
-        String previousValue = fieldInfo.putAttribute(PER_FIELD_FORMAT_KEY, formatName);
-        if (previousValue != null) {
-          throw new IllegalStateException("found existing value for " + PER_FIELD_FORMAT_KEY + 
-                                          ", field=" + fieldInfo.name + ", old=" + previousValue + ", new=" + formatName);
-        }
-
-        previousValue = fieldInfo.putAttribute(PER_FIELD_SUFFIX_KEY, Integer.toString(group.suffix));
-        if (previousValue != null) {
-          throw new IllegalStateException("found existing value for " + PER_FIELD_SUFFIX_KEY + 
-                                          ", field=" + fieldInfo.name + ", old=" + previousValue + ", new=" + group.suffix);
-        }
+        fieldInfo.putAttribute(PER_FIELD_FORMAT_KEY, formatName);
+        fieldInfo.putAttribute(PER_FIELD_SUFFIX_KEY, Integer.toString(group.suffix));
       }
       return formatToGroups;
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/63dfba4c/lucene/core/src/java/org/apache/lucene/document/FieldType.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/FieldType.java b/lucene/core/src/java/org/apache/lucene/document/FieldType.java
index a21572e..b450eee 100644
--- a/lucene/core/src/java/org/apache/lucene/document/FieldType.java
+++ b/lucene/core/src/java/org/apache/lucene/document/FieldType.java
@@ -17,6 +17,9 @@
 package org.apache.lucene.document;
 
 
+import java.util.HashMap;
+import java.util.Map;
+
 import org.apache.lucene.analysis.Analyzer; // javadocs
 import org.apache.lucene.index.DocValuesType;
 import org.apache.lucene.index.IndexOptions;
@@ -41,6 +44,7 @@ public class FieldType implements IndexableFieldType  {
   private int dataDimensionCount;
   private int indexDimensionCount;
   private int dimensionNumBytes;
+  private Map<String, String> attributes;
 
   /**
    * Create a new mutable FieldType with all of the properties from <code>ref</code>
@@ -58,6 +62,9 @@ public class FieldType implements IndexableFieldType  {
     this.dataDimensionCount = ref.pointDataDimensionCount();
     this.indexDimensionCount = ref.pointIndexDimensionCount();
     this.dimensionNumBytes = ref.pointNumBytes();
+    if (ref.getAttributes() != null) {
+      this.attributes = new HashMap<>(ref.getAttributes());
+    }
     // Do not copy frozen!
   }
   
@@ -341,6 +348,30 @@ public class FieldType implements IndexableFieldType  {
     return dimensionNumBytes;
   }
 
+  /**
+   * Puts an attribute value.
+   * <p>
+   * This is a key-value mapping for the field that the codec can use
+   * to store additional metadata.
+   * <p>
+   * If a value already exists for the field, it will be replaced with
+   * the new value. This method is not thread-safe, user must not add attributes
+   * while other threads are indexing documents with this field type.
+   *
+   * @lucene.experimental
+   */
+  public String putAttribute(String key, String value) {
+    if (attributes == null) {
+      attributes = new HashMap<>();
+    }
+    return attributes.put(key, value);
+  }
+
+  @Override
+  public Map<String, String> getAttributes() {
+    return attributes;
+  }
+
   /** Prints a Field for human consumption. */
   @Override
   public String toString() {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/63dfba4c/lucene/core/src/java/org/apache/lucene/index/DefaultIndexingChain.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/DefaultIndexingChain.java b/lucene/core/src/java/org/apache/lucene/index/DefaultIndexingChain.java
index 4cc981d..8c4145f 100644
--- a/lucene/core/src/java/org/apache/lucene/index/DefaultIndexingChain.java
+++ b/lucene/core/src/java/org/apache/lucene/index/DefaultIndexingChain.java
@@ -661,6 +661,11 @@ final class DefaultIndexingChain extends DocConsumer {
 
       FieldInfo fi = fieldInfos.getOrAdd(name);
       initIndexOptions(fi, fieldType.indexOptions());
+      Map<String, String> attributes = fieldType.getAttributes();
+      if (attributes != null) {
+        attributes.forEach((k, v) -> fi.putAttribute(k, v));
+      }
+
       fp = new PerField(docWriter.getIndexCreatedVersionMajor(), fi, invert);
       fp.next = fieldHash[hashPos];
       fieldHash[hashPos] = fp;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/63dfba4c/lucene/core/src/java/org/apache/lucene/index/FieldInfo.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/FieldInfo.java b/lucene/core/src/java/org/apache/lucene/index/FieldInfo.java
index c5d85bc..5346523 100644
--- a/lucene/core/src/java/org/apache/lucene/index/FieldInfo.java
+++ b/lucene/core/src/java/org/apache/lucene/index/FieldInfo.java
@@ -142,7 +142,7 @@ public final class FieldInfo {
 
   // should only be called by FieldInfos#addOrUpdate
   void update(boolean storeTermVector, boolean omitNorms, boolean storePayloads, IndexOptions indexOptions,
-              int dataDimensionCount, int indexDimensionCount, int dimensionNumBytes) {
+              Map<String, String> attributes, int dataDimensionCount, int indexDimensionCount, int dimensionNumBytes) {
     if (indexOptions == null) {
       throw new NullPointerException("IndexOptions must not be null (field: \"" + name + "\")");
     }
@@ -176,6 +176,9 @@ public final class FieldInfo {
       // cannot store payloads if we don't store positions:
       this.storePayloads = false;
     }
+    if (attributes != null) {
+      this.attributes.putAll(attributes);
+    }
     assert checkConsistency();
   }
 
@@ -346,8 +349,9 @@ public final class FieldInfo {
    * to store additional metadata, and will be available to the codec
    * when reading the segment via {@link #getAttribute(String)}
    * <p>
-   * If a value already exists for the field, it will be replaced with 
-   * the new value.
+   * If a value already exists for the key in the field, it will be replaced with
+   * the new value. If the value of the attributes for a same field is changed between
+   * the documents, the behaviour after merge is undefined.
    */
   public String putAttribute(String key, String value) {
     return attributes.put(key, value);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/63dfba4c/lucene/core/src/java/org/apache/lucene/index/FieldInfos.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/FieldInfos.java b/lucene/core/src/java/org/apache/lucene/index/FieldInfos.java
index 88f092a..193fbdf 100644
--- a/lucene/core/src/java/org/apache/lucene/index/FieldInfos.java
+++ b/lucene/core/src/java/org/apache/lucene/index/FieldInfos.java
@@ -506,12 +506,18 @@ public class FieldInfos implements Iterable<FieldInfo> {
                                           boolean storeTermVector,
                                           boolean omitNorms, boolean storePayloads, IndexOptions indexOptions,
                                           DocValuesType docValues, long dvGen,
+                                          Map<String, String> attributes,
                                           int dataDimensionCount, int indexDimensionCount, int dimensionNumBytes,
                                           boolean isSoftDeletesField) {
       assert assertNotFinished();
       if (docValues == null) {
         throw new NullPointerException("DocValuesType must not be null");
       }
+      if (attributes != null) {
+        // original attributes is UnmodifiableMap
+        attributes = new HashMap<>(attributes);
+      }
+
       FieldInfo fi = fieldInfo(name);
       if (fi == null) {
         // This field wasn't yet added to this in-RAM
@@ -520,12 +526,12 @@ public class FieldInfos implements Iterable<FieldInfo> {
         // before then we'll get the same name and number,
         // else we'll allocate a new one:
         final int fieldNumber = globalFieldNumbers.addOrGet(name, preferredFieldNumber, indexOptions, docValues, dataDimensionCount, indexDimensionCount, dimensionNumBytes, isSoftDeletesField);
-        fi = new FieldInfo(name, fieldNumber, storeTermVector, omitNorms, storePayloads, indexOptions, docValues, dvGen, new HashMap<>(), dataDimensionCount, indexDimensionCount, dimensionNumBytes, isSoftDeletesField);
+        fi = new FieldInfo(name, fieldNumber, storeTermVector, omitNorms, storePayloads, indexOptions, docValues, dvGen, attributes, dataDimensionCount, indexDimensionCount, dimensionNumBytes, isSoftDeletesField);
         assert !byName.containsKey(fi.name);
         globalFieldNumbers.verifyConsistent(Integer.valueOf(fi.number), fi.name, fi.getDocValuesType());
         byName.put(fi.name, fi);
       } else {
-        fi.update(storeTermVector, omitNorms, storePayloads, indexOptions, dataDimensionCount, indexDimensionCount, dimensionNumBytes);
+        fi.update(storeTermVector, omitNorms, storePayloads, indexOptions, attributes, dataDimensionCount, indexDimensionCount, dimensionNumBytes);
 
         if (docValues != DocValuesType.NONE) {
           // Only pay the synchronization cost if fi does not already have a DVType
@@ -553,6 +559,7 @@ public class FieldInfos implements Iterable<FieldInfo> {
       return addOrUpdateInternal(fi.name, fi.number, fi.hasVectors(),
                                  fi.omitsNorms(), fi.hasPayloads(),
                                  fi.getIndexOptions(), fi.getDocValuesType(), dvGen,
+                                 fi.attributes(),
                                  fi.getPointDataDimensionCount(), fi.getPointIndexDimensionCount(), fi.getPointNumBytes(),
                                  fi.isSoftDeletesField());
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/63dfba4c/lucene/core/src/java/org/apache/lucene/index/IndexableFieldType.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/IndexableFieldType.java b/lucene/core/src/java/org/apache/lucene/index/IndexableFieldType.java
index b2b2e77..59c5ab5 100644
--- a/lucene/core/src/java/org/apache/lucene/index/IndexableFieldType.java
+++ b/lucene/core/src/java/org/apache/lucene/index/IndexableFieldType.java
@@ -17,6 +17,8 @@
 package org.apache.lucene.index;
 
 
+import java.util.Map;
+
 import org.apache.lucene.analysis.Analyzer; // javadocs
 
 /** 
@@ -111,4 +113,14 @@ public interface IndexableFieldType {
    * The number of bytes in each dimension's values.
    */
   public int pointNumBytes();
+
+  /**
+   * Attributes for the field type.
+   *
+   * Attributes are not thread-safe, user must not add attributes while other threads are indexing documents
+   * with this field type.
+   *
+   * @return Map
+   */
+  public Map<String, String> getAttributes();
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/63dfba4c/lucene/core/src/test/org/apache/lucene/index/TestFieldInfos.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/index/TestFieldInfos.java b/lucene/core/src/test/org/apache/lucene/index/TestFieldInfos.java
index 3fe5fa9..321fcf7 100644
--- a/lucene/core/src/test/org/apache/lucene/index/TestFieldInfos.java
+++ b/lucene/core/src/test/org/apache/lucene/index/TestFieldInfos.java
@@ -23,6 +23,7 @@ import java.util.Iterator;
 import org.apache.lucene.analysis.MockAnalyzer;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
 import org.apache.lucene.document.StringField;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.LuceneTestCase;
@@ -93,6 +94,57 @@ public class TestFieldInfos extends LuceneTestCase {
     dir.close();
   }
 
+  public void testFieldAttributes() throws Exception{
+    Directory dir = newDirectory();
+    IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random()))
+        .setMergePolicy(NoMergePolicy.INSTANCE));
+
+    FieldType type1 = new FieldType();
+    type1.setStored(true);
+    type1.putAttribute("testKey1", "testValue1");
+
+    Document d1 = new Document();
+    d1.add(new Field("f1", "v1", type1));
+    FieldType type2 = new FieldType(type1);
+    //changing the value after copying shouldn't impact the original type1
+    type2.putAttribute("testKey1", "testValue2");
+    writer.addDocument(d1);
+    writer.commit();
+
+    Document d2 = new Document();
+    type1.putAttribute("testKey1", "testValueX");
+    type1.putAttribute("testKey2", "testValue2");
+    d2.add(new Field("f1", "v2", type1));
+    d2.add(new Field("f2", "v2", type2));
+    writer.addDocument(d2);
+    writer.commit();
+    writer.forceMerge(1);
+
+    IndexReader reader = writer.getReader();
+    FieldInfos fis = FieldInfos.getMergedFieldInfos(reader);
+    assertEquals(fis.size(), 2);
+    Iterator<FieldInfo>  it = fis.iterator();
+    while(it.hasNext()) {
+      FieldInfo fi = it.next();
+      switch (fi.name) {
+        case "f1":
+          // testKey1 can point to either testValue1 or testValueX based on the order
+          // of merge, but we see textValueX winning here since segment_2 is merged on segment_1.
+          assertEquals("testValueX", fi.getAttribute("testKey1"));
+          assertEquals("testValue2", fi.getAttribute("testKey2"));
+          break;
+        case "f2":
+          assertEquals("testValue2", fi.getAttribute("testKey1"));
+          break;
+        default:
+          assertFalse("Unknown field", true);
+      }
+    }
+    reader.close();
+    writer.close();
+    dir.close();
+  }
+
   public void testMergedFieldInfos_empty() throws IOException {
     Directory dir = newDirectory();
     IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random())));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/63dfba4c/lucene/core/src/test/org/apache/lucene/index/TestIndexableField.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/index/TestIndexableField.java b/lucene/core/src/test/org/apache/lucene/index/TestIndexableField.java
index 1091b24..59dbae3 100644
--- a/lucene/core/src/test/org/apache/lucene/index/TestIndexableField.java
+++ b/lucene/core/src/test/org/apache/lucene/index/TestIndexableField.java
@@ -21,6 +21,7 @@ import java.io.Reader;
 import java.io.StringReader;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.Map;
 
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.TokenStream;
@@ -104,6 +105,11 @@ public class TestIndexableField extends LuceneTestCase {
       public int pointNumBytes() {
         return 0;
       }
+
+      @Override
+      public Map<String, String> getAttributes() {
+        return null;
+      }
     };
 
     public MyField(int counter) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/63dfba4c/solr/core/src/java/org/apache/solr/schema/SchemaField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/SchemaField.java b/solr/core/src/java/org/apache/solr/schema/SchemaField.java
index 7d9449e..100a963 100644
--- a/solr/core/src/java/org/apache/solr/schema/SchemaField.java
+++ b/solr/core/src/java/org/apache/solr/schema/SchemaField.java
@@ -430,4 +430,9 @@ public final class SchemaField extends FieldProperties implements IndexableField
   public int pointNumBytes() {
     return 0;
   }
+
+  @Override
+  public Map<String, String> getAttributes() {
+    return null;
+  }
 }