[dna-commits] DNA SVN: r1342 - in trunk/dna-search/src: test/java/org/jboss/dna/search and 1 other directory.

dna-commits at lists.jboss.org dna-commits at lists.jboss.org
Tue Nov 24 19:59:30 EST 2009


Author: rhauch
Date: 2009-11-24 19:59:30 -0500 (Tue, 24 Nov 2009)
New Revision: 1342

Modified:
   trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexSearchProvider.java
   trunk/dna-search/src/main/java/org/jboss/dna/search/IndexRules.java
   trunk/dna-search/src/test/java/org/jboss/dna/search/IndexingRulesTest.java
   trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java
Log:
DNA-467 Changed the way IndexRules are defined to address the different datatypes, allowing the queries to be properly created using the appropriate Lucene query objects.

Modified: trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexSearchProvider.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexSearchProvider.java	2009-11-24 21:17:39 UTC (rev 1341)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexSearchProvider.java	2009-11-25 00:59:30 UTC (rev 1342)
@@ -74,14 +74,14 @@
 import org.jboss.dna.graph.JcrLexicon;
 import org.jboss.dna.graph.Location;
 import org.jboss.dna.graph.Node;
-import org.jboss.dna.graph.property.Binary;
 import org.jboss.dna.graph.property.DateTime;
 import org.jboss.dna.graph.property.Name;
 import org.jboss.dna.graph.property.NamespaceRegistry;
 import org.jboss.dna.graph.property.Path;
 import org.jboss.dna.graph.property.Property;
-import org.jboss.dna.graph.property.PropertyType;
 import org.jboss.dna.graph.property.ValueFactories;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.property.basic.JodaDateTime;
 import org.jboss.dna.graph.query.QueryContext;
 import org.jboss.dna.graph.query.QueryEngine;
 import org.jboss.dna.graph.query.QueryResults;
@@ -106,6 +106,8 @@
 import org.jboss.dna.graph.request.ChangeRequest;
 import org.jboss.dna.graph.search.SearchException;
 import org.jboss.dna.graph.search.SearchProvider;
+import org.jboss.dna.search.IndexRules.FieldType;
+import org.jboss.dna.search.IndexRules.NumericRule;
 import org.jboss.dna.search.IndexRules.Rule;
 import org.jboss.dna.search.LuceneSession.TupleCollector;
 import org.jboss.dna.search.query.CompareLengthQuery;
@@ -133,22 +135,22 @@
     public static final IndexRules DEFAULT_RULES;
 
     static {
+        // We know that the earliest creation/modified dates cannot be before November 1 2009,
+        // which is before this feature was implemented
+        long earliestChangeDate = new JodaDateTime(2009, 11, 01, 0, 0, 0, 0).getMilliseconds();
+
         IndexRules.Builder builder = IndexRules.createBuilder();
         // Configure the default behavior ...
-        builder.defaultTo(IndexRules.INDEX | IndexRules.FULL_TEXT | IndexRules.STORE);
+        builder.defaultTo(Field.Store.YES, Field.Index.ANALYZED);
         // Configure the UUID properties to be just indexed and stored (not analyzed, not included in full-text) ...
-        builder.store(JcrLexicon.UUID, DnaLexicon.UUID);
+        builder.stringField(JcrLexicon.UUID, Field.Store.YES, Field.Index.NOT_ANALYZED);
+        builder.stringField(DnaLexicon.UUID, Field.Store.YES, Field.Index.NOT_ANALYZED);
         // Configure the properties that we'll treat as dates ...
-        builder.treatAsDates(JcrLexicon.CREATED, JcrLexicon.LAST_MODIFIED);
+        builder.dateField(JcrLexicon.CREATED, Field.Store.YES, Field.Index.NOT_ANALYZED, earliestChangeDate);
+        builder.dateField(JcrLexicon.LAST_MODIFIED, Field.Store.YES, Field.Index.NOT_ANALYZED, earliestChangeDate);
         DEFAULT_RULES = builder.build();
     }
 
-    protected static final long MIN_DATE = 0;
-    protected static final long MAX_DATE = Long.MAX_VALUE;
-    protected static final long MIN_LONG = Long.MIN_VALUE;
-    protected static final long MAX_LONG = Long.MAX_VALUE;
-    protected static final double MIN_DOUBLE = Double.MIN_VALUE;
-    protected static final double MAX_DOUBLE = Double.MAX_VALUE;
     protected static final int MIN_DEPTH = 0;
     protected static final int MAX_DEPTH = 100;
     protected static final int MIN_SNS_INDEX = 1;
@@ -506,31 +508,66 @@
                 for (Property property : node.getProperties()) {
                     Name name = property.getName();
                     Rule rule = rules.getRule(name);
-                    if (!rule.isIncluded()) continue;
+                    if (rule.isSkipped()) continue;
                     String nameString = stringFactory.create(name);
-                    if (rule.isDate()) {
+                    FieldType type = rule.getType();
+                    if (type == FieldType.DATE) {
+                        boolean index = rule.getIndexOption() != Field.Index.NO;
                         for (Object value : property) {
                             if (value == null) continue;
+                            // Add a separate field for each property value ...
                             DateTime dateValue = dateFactory.create(value);
+                            long longValue = dateValue.getMillisecondsInUtc();
+                            doc.add(new NumericField(nameString, rule.getStoreOption(), index).setLongValue(longValue));
+                        }
+                        continue;
+                    }
+                    if (type == FieldType.INT) {
+                        ValueFactory<Long> longFactory = context.getValueFactories().getLongFactory();
+                        boolean index = rule.getIndexOption() != Field.Index.NO;
+                        for (Object value : property) {
+                            if (value == null) continue;
                             // Add a separate field for each property value ...
-                            doc.add(new NumericField(nameString, rule.getStoreOption(), true).setLongValue(dateValue.getMillisecondsInUtc()));
-                            // Dates are not added to the full-text search field (since this wouldn't make sense)
+                            int intValue = longFactory.create(value).intValue();
+                            doc.add(new NumericField(nameString, rule.getStoreOption(), index).setIntValue(intValue));
                         }
                         continue;
                     }
+                    if (type == FieldType.DOUBLE) {
+                        ValueFactory<Double> doubleFactory = context.getValueFactories().getDoubleFactory();
+                        boolean index = rule.getIndexOption() != Field.Index.NO;
+                        for (Object value : property) {
+                            if (value == null) continue;
+                            // Add a separate field for each property value ...
+                            double dValue = doubleFactory.create(value);
+                            doc.add(new NumericField(nameString, rule.getStoreOption(), index).setDoubleValue(dValue));
+                        }
+                        continue;
+                    }
+                    if (type == FieldType.FLOAT) {
+                        ValueFactory<Double> doubleFactory = context.getValueFactories().getDoubleFactory();
+                        boolean index = rule.getIndexOption() != Field.Index.NO;
+                        for (Object value : property) {
+                            if (value == null) continue;
+                            // Add a separate field for each property value ...
+                            float fValue = doubleFactory.create(value).floatValue();
+                            doc.add(new NumericField(nameString, rule.getStoreOption(), index).setFloatValue(fValue));
+                        }
+                        continue;
+                    }
+                    if (type == FieldType.BINARY) {
+                        // TODO : add to full-text search ...
+                        continue;
+                    }
+                    assert type == FieldType.STRING;
                     for (Object value : property) {
                         if (value == null) continue;
-                        if (value instanceof Binary) {
-                            // don't include binary values as individual fields but do include them in the full-text search ...
-                            // TODO : add to full-text search ...
-                            continue;
-                        }
                         stringValue = stringFactory.create(value);
                         // Add a separate field for each property value ...
                         doc.add(new Field(nameString, stringValue, rule.getStoreOption(), rule.getIndexOption()));
 
-                        if (rule.isFullText()) {
-                            // Add this text to the full-text field ...
+                        if (rule.getIndexOption() != Field.Index.NO) {
+                            // This field is to be full-text searchable ...
                             if (fullTextSearchValue == null) {
                                 fullTextSearchValue = new StringBuilder();
                             } else {
@@ -545,7 +582,7 @@
                     }
                 }
                 // Add the full-text-search field ...
-                if (fullTextSearchValue != null) {
+                if (fullTextSearchValue != null && fullTextSearchValue.length() != 0) {
                     doc.add(new Field(ContentIndex.FULL_TEXT, fullTextSearchValue.toString(), Field.Store.NO,
                                       Field.Index.ANALYZED));
                 }
@@ -1121,23 +1158,22 @@
             return null;
         }
 
+        @SuppressWarnings( "unchecked" )
         @Override
         public Query findNodesWith( PropertyValue propertyValue,
                                     Operator operator,
                                     Object value,
                                     boolean caseSensitive ) {
             String field = stringFactory.create(propertyValue.getPropertyName());
-            PropertyType valueType = PropertyType.discoverType(value);
+            Name fieldName = nameFactory.create(propertyValue.getPropertyName());
             ValueFactories factories = context.getValueFactories();
-            switch (valueType) {
-                case NAME:
-                case PATH:
-                case REFERENCE:
-                case URI:
-                case UUID:
+            IndexRules.Rule rule = rules.getRule(fieldName);
+            if (rule == null || rule.isSkipped()) return new MatchNoneQuery();
+            FieldType type = rule.getType();
+            switch (type) {
                 case STRING:
                     String stringValue = stringFactory.create(value);
-                    if (valueType == PropertyType.PATH) {
+                    if (value instanceof Path) {
                         stringValue = pathAsString(pathFactory.create(value), stringFactory);
                     }
                     if (!caseSensitive) stringValue = stringValue.toLowerCase();
@@ -1178,6 +1214,7 @@
                     }
                     break;
                 case DATE:
+                    NumericRule<Long> longRule = (NumericRule<Long>)rule;
                     long date = factories.getLongFactory().create(value);
                     switch (operator) {
                         case EQUAL_TO:
@@ -1186,13 +1223,13 @@
                             Query query = NumericRangeQuery.newLongRange(field, date, date, true, true);
                             return new NotQuery(query);
                         case GREATER_THAN:
-                            return NumericRangeQuery.newLongRange(field, date, MAX_DATE, false, true);
+                            return NumericRangeQuery.newLongRange(field, date, longRule.getMaximum(), false, true);
                         case GREATER_THAN_OR_EQUAL_TO:
-                            return NumericRangeQuery.newLongRange(field, date, MAX_DATE, true, true);
+                            return NumericRangeQuery.newLongRange(field, date, longRule.getMaximum(), true, true);
                         case LESS_THAN:
-                            return NumericRangeQuery.newLongRange(field, MIN_DATE, date, true, false);
+                            return NumericRangeQuery.newLongRange(field, longRule.getMinimum(), date, true, false);
                         case LESS_THAN_OR_EQUAL_TO:
-                            return NumericRangeQuery.newLongRange(field, MIN_DATE, date, true, true);
+                            return NumericRangeQuery.newLongRange(field, longRule.getMinimum(), date, true, true);
                         case LIKE:
                             // This is not allowed ...
                             assert false;
@@ -1200,6 +1237,7 @@
                     }
                     break;
                 case LONG:
+                    longRule = (NumericRule<Long>)rule;
                     long longValue = factories.getLongFactory().create(value);
                     switch (operator) {
                         case EQUAL_TO:
@@ -1208,21 +1246,44 @@
                             Query query = NumericRangeQuery.newLongRange(field, longValue, longValue, true, true);
                             return new NotQuery(query);
                         case GREATER_THAN:
-                            return NumericRangeQuery.newLongRange(field, longValue, MAX_LONG, false, true);
+                            return NumericRangeQuery.newLongRange(field, longValue, longRule.getMaximum(), false, true);
                         case GREATER_THAN_OR_EQUAL_TO:
-                            return NumericRangeQuery.newLongRange(field, longValue, MAX_LONG, true, true);
+                            return NumericRangeQuery.newLongRange(field, longValue, longRule.getMaximum(), true, true);
                         case LESS_THAN:
-                            return NumericRangeQuery.newLongRange(field, MIN_LONG, longValue, true, false);
+                            return NumericRangeQuery.newLongRange(field, longRule.getMinimum(), longValue, true, false);
                         case LESS_THAN_OR_EQUAL_TO:
-                            return NumericRangeQuery.newLongRange(field, MIN_LONG, longValue, true, true);
+                            return NumericRangeQuery.newLongRange(field, longRule.getMinimum(), longValue, true, true);
                         case LIKE:
                             // This is not allowed ...
                             assert false;
                             return null;
                     }
                     break;
-                case DECIMAL:
+                case INT:
+                    NumericRule<Integer> intRule = (NumericRule<Integer>)rule;
+                    int intValue = factories.getLongFactory().create(value).intValue();
+                    switch (operator) {
+                        case EQUAL_TO:
+                            return NumericRangeQuery.newIntRange(field, intValue, intValue, true, true);
+                        case NOT_EQUAL_TO:
+                            Query query = NumericRangeQuery.newIntRange(field, intValue, intValue, true, true);
+                            return new NotQuery(query);
+                        case GREATER_THAN:
+                            return NumericRangeQuery.newIntRange(field, intValue, intRule.getMaximum(), false, true);
+                        case GREATER_THAN_OR_EQUAL_TO:
+                            return NumericRangeQuery.newIntRange(field, intValue, intRule.getMaximum(), true, true);
+                        case LESS_THAN:
+                            return NumericRangeQuery.newIntRange(field, intRule.getMinimum(), intValue, true, false);
+                        case LESS_THAN_OR_EQUAL_TO:
+                            return NumericRangeQuery.newIntRange(field, intRule.getMinimum(), intValue, true, true);
+                        case LIKE:
+                            // This is not allowed ...
+                            assert false;
+                            return null;
+                    }
+                    break;
                 case DOUBLE:
+                    NumericRule<Double> dRule = (NumericRule<Double>)rule;
                     double doubleValue = factories.getDoubleFactory().create(value);
                     switch (operator) {
                         case EQUAL_TO:
@@ -1231,19 +1292,42 @@
                             Query query = NumericRangeQuery.newDoubleRange(field, doubleValue, doubleValue, true, true);
                             return new NotQuery(query);
                         case GREATER_THAN:
-                            return NumericRangeQuery.newDoubleRange(field, doubleValue, MAX_DOUBLE, false, true);
+                            return NumericRangeQuery.newDoubleRange(field, doubleValue, dRule.getMaximum(), false, true);
                         case GREATER_THAN_OR_EQUAL_TO:
-                            return NumericRangeQuery.newDoubleRange(field, doubleValue, MAX_DOUBLE, true, true);
+                            return NumericRangeQuery.newDoubleRange(field, doubleValue, dRule.getMaximum(), true, true);
                         case LESS_THAN:
-                            return NumericRangeQuery.newDoubleRange(field, MIN_DOUBLE, doubleValue, true, false);
+                            return NumericRangeQuery.newDoubleRange(field, dRule.getMinimum(), doubleValue, true, false);
                         case LESS_THAN_OR_EQUAL_TO:
-                            return NumericRangeQuery.newDoubleRange(field, MIN_DOUBLE, doubleValue, true, true);
+                            return NumericRangeQuery.newDoubleRange(field, dRule.getMinimum(), doubleValue, true, true);
                         case LIKE:
                             // This is not allowed ...
                             assert false;
                             return null;
                     }
                     break;
+                case FLOAT:
+                    NumericRule<Float> fRule = (NumericRule<Float>)rule;
+                    float floatValue = factories.getDoubleFactory().create(value).floatValue();
+                    switch (operator) {
+                        case EQUAL_TO:
+                            return NumericRangeQuery.newFloatRange(field, floatValue, floatValue, true, true);
+                        case NOT_EQUAL_TO:
+                            Query query = NumericRangeQuery.newFloatRange(field, floatValue, floatValue, true, true);
+                            return new NotQuery(query);
+                        case GREATER_THAN:
+                            return NumericRangeQuery.newFloatRange(field, floatValue, fRule.getMaximum(), false, true);
+                        case GREATER_THAN_OR_EQUAL_TO:
+                            return NumericRangeQuery.newFloatRange(field, floatValue, fRule.getMaximum(), true, true);
+                        case LESS_THAN:
+                            return NumericRangeQuery.newFloatRange(field, fRule.getMinimum(), floatValue, true, false);
+                        case LESS_THAN_OR_EQUAL_TO:
+                            return NumericRangeQuery.newFloatRange(field, fRule.getMinimum(), floatValue, true, true);
+                        case LIKE:
+                            // This is not allowed ...
+                            assert false;
+                            return null;
+                    }
+                    break;
                 case BOOLEAN:
                     boolean booleanValue = factories.getBooleanFactory().create(value);
                     stringValue = stringFactory.create(value);
@@ -1274,7 +1358,6 @@
                             return null;
                     }
                     break;
-                case OBJECT:
                 case BINARY:
                     // This is not allowed ...
                     assert false;
@@ -1307,8 +1390,10 @@
                                                    Object upperValue,
                                                    boolean includesLower,
                                                    boolean includesUpper ) {
-            PropertyType type = PropertyType.discoverType(lowerValue);
-            assert type == PropertyType.discoverType(upperValue);
+            Name fieldName = nameFactory.create(field);
+            IndexRules.Rule rule = rules.getRule(fieldName);
+            if (rule == null || rule.isSkipped()) return new MatchNoneQuery();
+            FieldType type = rule.getType();
             ValueFactories factories = context.getValueFactories();
             switch (type) {
                 case DATE:
@@ -1319,16 +1404,27 @@
                     long lowerLong = factories.getLongFactory().create(lowerValue);
                     long upperLong = factories.getLongFactory().create(upperValue);
                     return NumericRangeQuery.newLongRange(field, lowerLong, upperLong, includesLower, includesUpper);
-                case DECIMAL:
                 case DOUBLE:
                     double lowerDouble = factories.getDoubleFactory().create(lowerValue);
                     double upperDouble = factories.getDoubleFactory().create(upperValue);
                     return NumericRangeQuery.newDoubleRange(field, lowerDouble, upperDouble, includesLower, includesUpper);
-                default:
-                    // This is not allowed ...
+                case FLOAT:
+                    float lowerFloat = factories.getDoubleFactory().create(lowerValue).floatValue();
+                    float upperFloat = factories.getDoubleFactory().create(upperValue).floatValue();
+                    return NumericRangeQuery.newFloatRange(field, lowerFloat, upperFloat, includesLower, includesUpper);
+                case INT:
+                    int lowerInt = factories.getLongFactory().create(lowerValue).intValue();
+                    int upperInt = factories.getLongFactory().create(upperValue).intValue();
+                    return NumericRangeQuery.newIntRange(field, lowerInt, upperInt, includesLower, includesUpper);
+                case BOOLEAN:
+                    lowerInt = factories.getBooleanFactory().create(lowerValue).booleanValue() ? 1 : 0;
+                    upperInt = factories.getBooleanFactory().create(upperValue).booleanValue() ? 1 : 0;
+                    return NumericRangeQuery.newIntRange(field, lowerInt, upperInt, includesLower, includesUpper);
+                case STRING:
+                case BINARY:
                     assert false;
-                    return null;
             }
+            return new MatchNoneQuery();
         }
 
         @Override

Modified: trunk/dna-search/src/main/java/org/jboss/dna/search/IndexRules.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/IndexRules.java	2009-11-24 21:17:39 UTC (rev 1341)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/IndexRules.java	2009-11-25 00:59:30 UTC (rev 1342)
@@ -26,7 +26,6 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
 import net.jcip.annotations.Immutable;
 import net.jcip.annotations.NotThreadSafe;
 import org.apache.lucene.document.Field;
@@ -41,13 +40,16 @@
 @Immutable
 public class IndexRules {
 
-    public static final int INDEX = 2 << 0;
-    public static final int ANALYZE = 2 << 1;
-    public static final int STORE = 2 << 2;
-    public static final int STORE_COMPRESSED = 2 << 3;
-    public static final int ANALYZED_WITHOUT_NORMS = 2 << 4;
-    public static final int FULL_TEXT = 2 << 5;
-    public static final int TREAT_AS_DATE = 2 << 6;
+    public static enum FieldType {
+        STRING,
+        DOUBLE,
+        FLOAT,
+        INT,
+        BOOLEAN,
+        LONG,
+        DATE,
+        BINARY;
+    }
 
     /**
      * A single rule that dictates how a single property should be indexed.
@@ -56,111 +58,49 @@
      */
     @Immutable
     public static interface Rule {
-        /**
-         * Return whether this property should be included in the indexes.
-         * 
-         * @return true if it is to be included, or false otherwise
-         */
-        boolean isIncluded();
 
-        boolean isAnalyzed();
+        boolean isSkipped();
 
-        boolean isAnalyzedWithoutNorms();
+        FieldType getType();
 
-        boolean isStored();
-
-        boolean isStoredCompressed();
-
-        boolean isFullText();
-
-        boolean isDate();
-
-        int getMask();
-
         Field.Store getStoreOption();
 
         Field.Index getIndexOption();
     }
 
+    @Immutable
+    public static interface NumericRule<T> extends Rule {
+        T getMinimum();
+
+        T getMaximum();
+    }
+
     public static final Rule SKIP = new SkipRule();
 
     @Immutable
     protected static class SkipRule implements Rule {
-        /**
-         * {@inheritDoc}
-         * 
-         * @see org.jboss.dna.search.IndexRules.Rule#getMask()
-         */
-        public int getMask() {
-            return 0;
-        }
 
         /**
          * {@inheritDoc}
          * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isAnalyzed()
+         * @see org.jboss.dna.search.IndexRules.Rule#getType()
          */
-        public boolean isAnalyzed() {
-            return false;
+        public FieldType getType() {
+            return FieldType.STRING;
         }
 
         /**
          * {@inheritDoc}
          * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isAnalyzedWithoutNorms()
+         * @see org.jboss.dna.search.IndexRules.Rule#isSkipped()
          */
-        public boolean isAnalyzedWithoutNorms() {
-            return false;
+        public boolean isSkipped() {
+            return true;
         }
 
         /**
          * {@inheritDoc}
          * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isFullText()
-         */
-        public boolean isFullText() {
-            return false;
-        }
-
-        /**
-         * {@inheritDoc}
-         * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isIncluded()
-         */
-        public boolean isIncluded() {
-            return false;
-        }
-
-        /**
-         * {@inheritDoc}
-         * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isStored()
-         */
-        public boolean isStored() {
-            return false;
-        }
-
-        /**
-         * {@inheritDoc}
-         * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isStoredCompressed()
-         */
-        public boolean isStoredCompressed() {
-            return false;
-        }
-
-        /**
-         * {@inheritDoc}
-         * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isDate()
-         */
-        public boolean isDate() {
-            return false;
-        }
-
-        /**
-         * {@inheritDoc}
-         * 
          * @see org.jboss.dna.search.IndexRules.Rule#getIndexOption()
          */
         public Index getIndexOption() {
@@ -178,128 +118,115 @@
     }
 
     @Immutable
-    public static final class GeneralRule implements Rule {
-        private final int value;
-        private final Field.Store store;
-        private final Field.Index index;
+    protected static class TypedRule implements Rule {
+        protected final FieldType type;
+        protected final Field.Store store;
+        protected final Field.Index index;
 
-        protected GeneralRule( int value ) {
-            this.value = value;
-            this.index = isAnalyzed() ? Field.Index.ANALYZED : Field.Index.NOT_ANALYZED;
-            this.store = isStored() ? Field.Store.YES : Field.Store.NO;
+        protected TypedRule( FieldType type,
+                             Field.Store store,
+                             Field.Index index ) {
+            this.type = type;
+            this.index = index;
+            this.store = store;
+            assert this.type != null;
+            assert this.index != null;
+            assert this.store != null;
         }
 
         /**
          * {@inheritDoc}
          * 
-         * @see org.jboss.dna.search.IndexRules.Rule#getMask()
+         * @see org.jboss.dna.search.IndexRules.Rule#getType()
          */
-        public int getMask() {
-            return value;
+        public FieldType getType() {
+            return type;
         }
 
         /**
          * {@inheritDoc}
          * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isAnalyzed()
+         * @see org.jboss.dna.search.IndexRules.Rule#isSkipped()
          */
-        public boolean isAnalyzed() {
-            return (value & ANALYZE) == ANALYZE;
+        public boolean isSkipped() {
+            return false;
         }
 
         /**
          * {@inheritDoc}
          * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isAnalyzedWithoutNorms()
+         * @see org.jboss.dna.search.IndexRules.Rule#getIndexOption()
          */
-        public boolean isAnalyzedWithoutNorms() {
-            return (value & ANALYZED_WITHOUT_NORMS) == ANALYZED_WITHOUT_NORMS;
+        public Index getIndexOption() {
+            return index;
         }
 
         /**
          * {@inheritDoc}
          * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isFullText()
+         * @see org.jboss.dna.search.IndexRules.Rule#getStoreOption()
          */
-        public boolean isFullText() {
-            return (value & FULL_TEXT) == FULL_TEXT;
+        public Store getStoreOption() {
+            return store;
         }
 
         /**
          * {@inheritDoc}
          * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isIncluded()
+         * @see java.lang.Object#toString()
          */
-        public boolean isIncluded() {
-            return true;
+        @Override
+        public String toString() {
+            return type.name() + " rule (" + store + "," + index + ")";
         }
+    }
 
-        /**
-         * {@inheritDoc}
-         * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isStored()
-         */
-        public boolean isStored() {
-            return (value & STORE) == STORE;
-        }
+    @Immutable
+    protected static class NumericTypedRule<T> extends TypedRule implements NumericRule<T> {
+        protected final T minValue;
+        protected final T maxValue;
 
-        /**
-         * {@inheritDoc}
-         * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isStoredCompressed()
-         */
-        public boolean isStoredCompressed() {
-            return (value & STORE_COMPRESSED) == STORE_COMPRESSED;
+        protected NumericTypedRule( FieldType type,
+                                    Field.Store store,
+                                    Field.Index index,
+                                    T minValue,
+                                    T maxValue ) {
+            super(type, store, index);
+            this.minValue = minValue;
+            this.maxValue = maxValue;
+            assert this.minValue != null;
+            assert this.maxValue != null;
         }
 
         /**
          * {@inheritDoc}
          * 
-         * @see org.jboss.dna.search.IndexRules.Rule#isDate()
+         * @see org.jboss.dna.search.IndexRules.NumericRule#getMaximum()
          */
-        public boolean isDate() {
-            return (value & TREAT_AS_DATE) == TREAT_AS_DATE;
+        public T getMaximum() {
+            return maxValue;
         }
 
-        protected Rule with( int options ) {
-            return createRule(value | options);
-        }
-
         /**
          * {@inheritDoc}
          * 
-         * @see org.jboss.dna.search.IndexRules.Rule#getIndexOption()
+         * @see org.jboss.dna.search.IndexRules.NumericRule#getMinimum()
          */
-        public Index getIndexOption() {
-            return index;
+        public T getMinimum() {
+            return minValue;
         }
 
         /**
          * {@inheritDoc}
          * 
-         * @see org.jboss.dna.search.IndexRules.Rule#getStoreOption()
+         * @see java.lang.Object#toString()
          */
-        public Store getStoreOption() {
-            return store;
+        @Override
+        public String toString() {
+            return super.toString() + " with range [" + minValue + "," + maxValue + "]";
         }
     }
 
-    private static final ConcurrentHashMap<Integer, Rule> CACHE = new ConcurrentHashMap<Integer, Rule>();
-
-    protected static Rule createRule( int value ) {
-        if (value <= 0) {
-            return SKIP;
-        }
-        Integer key = new Integer(value);
-        Rule rule = CACHE.get(key);
-        if (rule == null) {
-            Rule newRule = new GeneralRule(value);
-            rule = CACHE.putIfAbsent(value, newRule);
-            if (rule == null) rule = newRule;
-        }
-        return rule;
-    }
-
     private final Map<Name, Rule> rulesByName;
     private final Rule defaultRule;
 
@@ -327,7 +254,7 @@
      * @return a builder; never null
      */
     public static Builder createBuilder() {
-        return new Builder(new HashMap<Name, Rule>());
+        return new Builder(new HashMap<Name, Rule>(), null);
     }
 
     /**
@@ -339,7 +266,7 @@
      */
     public static Builder createBuilder( IndexRules initialRules ) {
         CheckArg.isNotNull(initialRules, "initialRules");
-        return new Builder(new HashMap<Name, Rule>(initialRules.rulesByName)).defaultTo(initialRules.defaultRule);
+        return new Builder(new HashMap<Name, Rule>(initialRules.rulesByName), initialRules.defaultRule);
     }
 
     /**
@@ -350,258 +277,357 @@
         private final Map<Name, Rule> rulesByName;
         private Rule defaultRule;
 
-        Builder( Map<Name, Rule> rulesByName ) {
+        Builder( Map<Name, Rule> rulesByName,
+                 Rule defaultRule ) {
             assert rulesByName != null;
             this.rulesByName = rulesByName;
+            this.defaultRule = defaultRule;
         }
 
         /**
-         * Set the default rules.
+         * Mark the properties with the supplied names to be skipped from indexing.
          * 
-         * @param rule the default rule to use
+         * @param namesToIndex the names of the properties that are to be skipped
          * @return this builder for convenience and method chaining; never null
-         * @throws IllegalArgumentException if the rule mask is negative
          */
-        public Builder defaultTo( Rule rule ) {
-            CheckArg.isNotNull(rule, "rule");
-            defaultRule = rule;
+        public Builder skip( Name... namesToIndex ) {
+            if (namesToIndex != null) {
+                for (Name name : namesToIndex) {
+                    rulesByName.put(name, SKIP);
+                }
+            }
             return this;
         }
 
         /**
-         * Set the default rules.
+         * Define a string-based field as the default.
          * 
-         * @param ruleMask the bitmask of rule to use
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
          * @return this builder for convenience and method chaining; never null
-         * @throws IllegalArgumentException if the rule mask is negative
          */
-        public Builder defaultTo( int ruleMask ) {
-            CheckArg.isNonNegative(ruleMask, "options");
-            if (ruleMask == 0) {
-                defaultRule = SKIP;
-            } else {
-                // Make sure the index flag is set ...
-                ruleMask |= INDEX;
-                defaultRule = createRule(ruleMask);
-            }
+        public Builder defaultTo( Field.Store store,
+                                  Field.Index index ) {
+            if (store == null) store = Field.Store.YES;
+            if (index == null) index = Field.Index.NOT_ANALYZED;
+            defaultRule = new TypedRule(FieldType.STRING, store, index);
             return this;
         }
 
         /**
-         * Mark the properties with the supplied names to be skipped from indexing.
+         * Define a string-based field in the indexes. This method will overwrite any existing definition in this builder.
          * 
-         * @param namesToIndex the names of the properties that are to be skipped
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
          * @return this builder for convenience and method chaining; never null
          */
-        public Builder skip( Name... namesToIndex ) {
-            if (namesToIndex != null) {
-                for (Name name : namesToIndex) {
-                    rulesByName.put(name, SKIP);
-                }
-            }
+        public Builder stringField( Name name,
+                                    Field.Store store,
+                                    Field.Index index ) {
+            if (store == null) store = Field.Store.YES;
+            if (index == null) index = Field.Index.NOT_ANALYZED;
+            Rule rule = new TypedRule(FieldType.STRING, store, index);
+            rulesByName.put(name, rule);
             return this;
         }
 
         /**
-         * Set the properties with the supplied names to use the supplied rules.
+         * Define a binary-based field in the indexes. This method will overwrite any existing definition in this builder.
          * 
-         * @param ruleMask the bitmask of rules to use
-         * @param namesToIndex the names of the properties that are to be skipped
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
          * @return this builder for convenience and method chaining; never null
-         * @throws IllegalArgumentException if the rule mask is negative
          */
-        public Builder set( int ruleMask,
-                            Name... namesToIndex ) {
-            CheckArg.isNonNegative(ruleMask, "options");
-            if (namesToIndex != null) {
-                if (ruleMask > 0) {
-                    skip(namesToIndex);
-                } else {
-                    // Make sure the index flag is set ...
-                    ruleMask |= INDEX;
-                    Rule rule = createRule(ruleMask);
-                    for (Name name : namesToIndex) {
-                        rulesByName.put(name, rule);
-                    }
-                }
-            }
+        public Builder binaryField( Name name,
+                                    Field.Store store,
+                                    Field.Index index ) {
+            if (store == null) store = Field.Store.YES;
+            if (index == null) index = Field.Index.NOT_ANALYZED;
+            Rule rule = new TypedRule(FieldType.BINARY, store, index);
+            rulesByName.put(name, rule);
             return this;
         }
 
+        protected <T> Builder numericField( Name name,
+                                            FieldType type,
+                                            Field.Store store,
+                                            Field.Index index,
+                                            T minValue,
+                                            T maxValue ) {
+            if (store == null) store = Field.Store.YES;
+            if (index == null) index = Field.Index.NOT_ANALYZED;
+            Rule rule = new NumericTypedRule<T>(type, store, index, minValue, maxValue);
+            rulesByName.put(name, rule);
+            return this;
+        }
+
         /**
-         * Mark the properties with the supplied names to use the supplied rule mask. This does not remove any other rules for
-         * these properties.
+         * Define a boolean-based field in the indexes. This method will overwrite any existing definition in this builder.
          * 
-         * @param ruleMask the bitmask of rules to add
-         * @param namesToIndex the names of the properties that are to be skipped
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
          * @return this builder for convenience and method chaining; never null
-         * @throws IllegalArgumentException if the rule mask is negative
          */
-        public Builder add( int ruleMask,
-                            Name... namesToIndex ) {
-            CheckArg.isNonNegative(ruleMask, "options");
-            if (namesToIndex != null) {
-                for (Name name : namesToIndex) {
-                    add(name, ruleMask);
-                }
-            }
-            return this;
+        public Builder booleanField( Name name,
+                                     Field.Store store,
+                                     Field.Index index ) {
+            return numericField(name, FieldType.BOOLEAN, store, index, Boolean.FALSE, Boolean.TRUE);
         }
 
         /**
-         * Mark the properties with the supplied names to be indexed. This does not remove any other rules for these properties.
+         * Define a integer-based field in the indexes. This method will overwrite any existing definition in this builder.
          * 
-         * @param namesToIndex the names of the properties that are to be indexed
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @param minValue the minimum value for this field, or null if there is no minimum value
+         * @param maxValue the maximum value for this field, or null if there is no maximum value
          * @return this builder for convenience and method chaining; never null
          */
-        public Builder index( Name... namesToIndex ) {
-            if (namesToIndex != null) {
-                for (Name name : namesToIndex) {
-                    add(name, INDEX);
-                }
-            }
-            return this;
+        public Builder integerField( Name name,
+                                     Field.Store store,
+                                     Field.Index index,
+                                     Integer minValue,
+                                     Integer maxValue ) {
+            if (minValue == null) minValue = Integer.MIN_VALUE;
+            if (maxValue == null) maxValue = Integer.MAX_VALUE;
+            return numericField(name, FieldType.INT, store, index, minValue, maxValue);
         }
 
         /**
-         * Mark the properties with the supplied names to be analyzed (and obviously indexed). This does not remove any other
-         * rules for these properties.
+         * Define a long-based field in the indexes. This method will overwrite any existing definition in this builder.
          * 
-         * @param namesToIndex the names of the properties that are to be analyzed
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @param minValue the minimum value for this field, or null if there is no minimum value
+         * @param maxValue the maximum value for this field, or null if there is no maximum value
          * @return this builder for convenience and method chaining; never null
          */
-        public Builder analyze( Name... namesToIndex ) {
-            if (namesToIndex != null) {
-                for (Name name : namesToIndex) {
-                    add(name, ANALYZE | INDEX);
-                }
-            }
-            return this;
+        public Builder longField( Name name,
+                                  Field.Store store,
+                                  Field.Index index,
+                                  Long minValue,
+                                  Long maxValue ) {
+            if (minValue == null) minValue = Long.MIN_VALUE;
+            if (maxValue == null) maxValue = Long.MAX_VALUE;
+            return numericField(name, FieldType.LONG, store, index, minValue, maxValue);
         }
 
         /**
-         * Mark the properties with the supplied names to be stored (and obviously indexed). This does not remove any other rules
-         * for these properties.
+         * Define a date-based field in the indexes. This method will overwrite any existing definition in this builder.
          * 
-         * @param namesToIndex the names of the properties that are to be stored
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @param minValue the minimum value for this field, or null if there is no minimum value
+         * @param maxValue the maximum value for this field, or null if there is no maximum value
          * @return this builder for convenience and method chaining; never null
          */
-        public Builder store( Name... namesToIndex ) {
-            if (namesToIndex != null) {
-                for (Name name : namesToIndex) {
-                    add(name, STORE | INDEX);
-                }
-            }
-            return this;
+        public Builder dateField( Name name,
+                                  Field.Store store,
+                                  Field.Index index,
+                                  Long minValue,
+                                  Long maxValue ) {
+            if (minValue == null) minValue = 0L;
+            if (maxValue == null) maxValue = Long.MAX_VALUE;
+            return numericField(name, FieldType.DATE, store, index, minValue, maxValue);
         }
 
         /**
-         * Mark the properties with the supplied names to be included in full-text searches (and obviously indexed). This does not
-         * remove any other rules for these properties.
+         * Define a float-based field in the indexes. This method will overwrite any existing definition in this builder.
          * 
-         * @param namesToIndex the names of the properties that are to be included in full-text searches
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @param minValue the minimum value for this field, or null if there is no minimum value
+         * @param maxValue the maximum value for this field, or null if there is no maximum value
          * @return this builder for convenience and method chaining; never null
          */
-        public Builder fullText( Name... namesToIndex ) {
-            if (namesToIndex != null) {
-                for (Name name : namesToIndex) {
-                    add(name, FULL_TEXT | INDEX);
-                }
-            }
-            return this;
+        public Builder floatField( Name name,
+                                   Field.Store store,
+                                   Field.Index index,
+                                   Float minValue,
+                                   Float maxValue ) {
+            if (minValue == null) minValue = Float.MIN_VALUE;
+            if (maxValue == null) maxValue = Float.MAX_VALUE;
+            return numericField(name, FieldType.FLOAT, store, index, minValue, maxValue);
         }
 
         /**
-         * Mark the properties with the supplied names to be treated as dates (and obviously indexed). This does not remove any
-         * other rules for these properties.
+         * Define a double-based field in the indexes. This method will overwrite any existing definition in this builder.
          * 
-         * @param namesToIndex the names of the properties that are to be included in full-text searches
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @param minValue the minimum value for this field, or null if there is no minimum value
+         * @param maxValue the maximum value for this field, or null if there is no maximum value
          * @return this builder for convenience and method chaining; never null
          */
-        public Builder treatAsDates( Name... namesToIndex ) {
-            if (namesToIndex != null) {
-                for (Name name : namesToIndex) {
-                    add(name, TREAT_AS_DATE | INDEX);
-                }
-            }
-            return this;
+        public Builder doubleField( Name name,
+                                    Field.Store store,
+                                    Field.Index index,
+                                    Double minValue,
+                                    Double maxValue ) {
+            if (minValue == null) minValue = Double.MIN_VALUE;
+            if (maxValue == null) maxValue = Double.MAX_VALUE;
+            return numericField(name, FieldType.DOUBLE, store, index, minValue, maxValue);
         }
 
         /**
-         * Mark the properties with the supplied names to be indexed, analyzed and stored. This does not remove any other rules
-         * for these properties.
+         * Define a integer-based field in the indexes. This method will overwrite any existing definition in this builder.
          * 
-         * @param namesToIndex the names of the properties that are to be indexed, analyzed and stored
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @param minValue the minimum value for this field, or null if there is no minimum value
          * @return this builder for convenience and method chaining; never null
          */
-        public Builder analyzeAndStore( Name... namesToIndex ) {
-            if (namesToIndex != null) {
-                for (Name name : namesToIndex) {
-                    add(name, INDEX | ANALYZE | STORE);
-                }
-            }
-            return this;
+        public Builder integerField( Name name,
+                                     Field.Store store,
+                                     Field.Index index,
+                                     Integer minValue ) {
+            return integerField(name, store, index, minValue, null);
         }
 
         /**
-         * Mark the properties with the supplied names to be indexed, analyzed, stored and included in full-text searches. This
-         * does not remove any other rules for these properties.
+         * Define a long-based field in the indexes. This method will overwrite any existing definition in this builder.
          * 
-         * @param namesToIndex the names of the properties that are to be indexed, analyzed, stored and included in full-text
-         *        searches
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @param minValue the minimum value for this field, or null if there is no minimum value
          * @return this builder for convenience and method chaining; never null
          */
-        public Builder analyzeAndStoreAndFullText( Name... namesToIndex ) {
-            if (namesToIndex != null) {
-                for (Name name : namesToIndex) {
-                    add(name, INDEX | ANALYZE | STORE | FULL_TEXT);
-                }
-            }
-            return this;
+        public Builder longField( Name name,
+                                  Field.Store store,
+                                  Field.Index index,
+                                  Long minValue ) {
+            return longField(name, store, index, minValue, null);
         }
 
         /**
-         * Mark the properties with the supplied names to be indexed, analyzed and included in full-text searches. This does not
-         * remove any other rules for these properties.
+         * Define a date-based field in the indexes. This method will overwrite any existing definition in this builder.
          * 
-         * @param namesToIndex the names of the properties that are to be indexed, analyzed and included in full-text searches
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @param minValue the minimum value for this field, or null if there is no minimum value
          * @return this builder for convenience and method chaining; never null
          */
-        public Builder analyzeAndFullText( Name... namesToIndex ) {
-            if (namesToIndex != null) {
-                for (Name name : namesToIndex) {
-                    add(name, INDEX | ANALYZE | FULL_TEXT);
-                }
-            }
-            return this;
+        public Builder dateField( Name name,
+                                  Field.Store store,
+                                  Field.Index index,
+                                  Long minValue ) {
+            return dateField(name, store, index, minValue, null);
         }
 
         /**
-         * Mark the properties with the supplied names to be indexed, stored and included in full-text searches. This does not
-         * remove any other rules for these properties.
+         * Define a float-based field in the indexes. This method will overwrite any existing definition in this builder.
          * 
-         * @param namesToIndex the names of the properties that are to be indexed, stored and included in full-text searches
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @param minValue the minimum value for this field, or null if there is no minimum value
          * @return this builder for convenience and method chaining; never null
          */
-        public Builder storeAndFullText( Name... namesToIndex ) {
-            if (namesToIndex != null) {
-                for (Name name : namesToIndex) {
-                    add(name, INDEX | STORE | FULL_TEXT);
-                }
-            }
-            return this;
+        public Builder floatField( Name name,
+                                   Field.Store store,
+                                   Field.Index index,
+                                   Float minValue ) {
+            return floatField(name, store, index, minValue, null);
         }
 
-        protected void add( Name name,
-                            int option ) {
-            Rule rule = rulesByName.get(name);
-            if (rule != null) {
-                option |= rule.getMask();
-            }
-            rulesByName.put(name, createRule(option));
+        /**
+         * Define a double-based field in the indexes. This method will overwrite any existing definition in this builder.
+         * 
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @param minValue the minimum value for this field, or null if there is no minimum value
+         * @return this builder for convenience and method chaining; never null
+         */
+        public Builder doubleField( Name name,
+                                    Field.Store store,
+                                    Field.Index index,
+                                    Double minValue ) {
+            return doubleField(name, store, index, minValue, null);
         }
 
         /**
+         * Define a integer-based field in the indexes. This method will overwrite any existing definition in this builder.
+         * 
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @return this builder for convenience and method chaining; never null
+         */
+        public Builder integerField( Name name,
+                                     Field.Store store,
+                                     Field.Index index ) {
+            return integerField(name, store, index, null, null);
+        }
+
+        /**
+         * Define a long-based field in the indexes. This method will overwrite any existing definition in this builder.
+         * 
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @return this builder for convenience and method chaining; never null
+         */
+        public Builder longField( Name name,
+                                  Field.Store store,
+                                  Field.Index index ) {
+            return longField(name, store, index, null, null);
+        }
+
+        /**
+         * Define a date-based field in the indexes. This method will overwrite any existing definition in this builder.
+         * 
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @return this builder for convenience and method chaining; never null
+         */
+        public Builder dateField( Name name,
+                                  Field.Store store,
+                                  Field.Index index ) {
+            return dateField(name, store, index, null, null);
+        }
+
+        /**
+         * Define a float-based field in the indexes. This method will overwrite any existing definition in this builder.
+         * 
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @return this builder for convenience and method chaining; never null
+         */
+        public Builder floatField( Name name,
+                                   Field.Store store,
+                                   Field.Index index ) {
+            return floatField(name, store, index, null, null);
+        }
+
+        /**
+         * Define a double-based field in the indexes. This method will overwrite any existing definition in this builder.
+         * 
+         * @param name the name of the field
+         * @param store the storage setting, or null if the field should be {@link Store#YES stored}
+         * @param index the index setting, or null if the field should be indexed but {@link Index#NOT_ANALYZED not analyzed}
+         * @return this builder for convenience and method chaining; never null
+         */
+        public Builder doubleField( Name name,
+                                    Field.Store store,
+                                    Field.Index index ) {
+            return doubleField(name, store, index, null, null);
+        }
+
+        /**
          * Build the indexing rules.
          * 
          * @return the immutable indexing rules.

Modified: trunk/dna-search/src/test/java/org/jboss/dna/search/IndexingRulesTest.java
===================================================================
--- trunk/dna-search/src/test/java/org/jboss/dna/search/IndexingRulesTest.java	2009-11-24 21:17:39 UTC (rev 1341)
+++ trunk/dna-search/src/test/java/org/jboss/dna/search/IndexingRulesTest.java	2009-11-25 00:59:30 UTC (rev 1342)
@@ -25,6 +25,7 @@
 
 import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertThat;
+import org.apache.lucene.document.Field;
 import org.jboss.dna.search.IndexRules.Builder;
 import org.junit.Before;
 import org.junit.Test;
@@ -51,8 +52,9 @@
 
     @Test
     public void shouldBuildValidRulesFromBuilderAfterJustSettingDefaultRules() {
-        builder.defaultTo(IndexRules.FULL_TEXT);
+        builder.defaultTo(Field.Store.NO, Field.Index.ANALYZED_NO_NORMS);
         rules = builder.build();
-        assertThat(rules.getRule(null).isFullText(), is(true));
+        assertThat(rules.getRule(null).getIndexOption(), is(Field.Index.ANALYZED_NO_NORMS));
+        assertThat(rules.getRule(null).getStoreOption(), is(Field.Store.NO));
     }
 }

Modified: trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java
===================================================================
--- trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java	2009-11-24 21:17:39 UTC (rev 1341)
+++ trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java	2009-11-25 00:59:30 UTC (rev 1342)
@@ -29,6 +29,7 @@
 import static org.junit.Assert.fail;
 import java.io.IOException;
 import java.util.List;
+import org.apache.lucene.document.Field;
 import org.jboss.dna.graph.ExecutionContext;
 import org.jboss.dna.graph.Graph;
 import org.jboss.dna.graph.Location;
@@ -47,7 +48,6 @@
 import org.jboss.dna.graph.search.SearchEngine;
 import org.jboss.dna.graph.search.SearchProvider;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.xml.sax.SAXException;
 
@@ -93,6 +93,12 @@
 
         // Set up the provider and the search engine ...
         IndexRules.Builder rulesBuilder = IndexRules.createBuilder(DualIndexSearchProvider.DEFAULT_RULES);
+        rulesBuilder.defaultTo(Field.Store.YES, Field.Index.NOT_ANALYZED);
+        rulesBuilder.stringField(name("model"), Field.Store.YES, Field.Index.ANALYZED);
+        rulesBuilder.integerField(name("year"), Field.Store.YES, Field.Index.NOT_ANALYZED);
+        rulesBuilder.floatField(name("userRating"), Field.Store.YES, Field.Index.NOT_ANALYZED, 0.0f, 10.0f);
+        rulesBuilder.integerField(name("mpgCity"), Field.Store.YES, Field.Index.NOT_ANALYZED, 0, 50);
+        rulesBuilder.integerField(name("mpgHighway"), Field.Store.YES, Field.Index.NOT_ANALYZED, 0, 50);
         // rulesBuilder.analyzeAndStoreAndFullText(name("maker"));
         IndexRules rules = rulesBuilder.build();
         LuceneConfiguration luceneConfig = LuceneConfigurations.inMemory();
@@ -103,7 +109,7 @@
 
         // Create the schemata for the workspaces ...
         schemata = ImmutableSchemata.createBuilder(typeSystem)
-                                    .addTable("__ALLNODES__", "maker", "model", "year", "msrp")
+                                    .addTable("__ALLNODES__", "maker", "model", "year", "msrp", "mpgHighway", "mpgCity")
                                     .makeSearchable("__ALLNODES__", "maker")
                                     .build();
 
@@ -242,6 +248,7 @@
         engine.index(workspaceName1, path("/"), 100);
         QueryCommand query = sql.parseQuery("SELECT model, maker FROM __ALLNODES__", typeSystem);
         QueryResults results = engine.query(context, workspaceName1, query, schemata);
+        assertNoErrors(results);
         assertThat(results, is(notNullValue()));
         assertThat(results.getRowCount(), is(18));
         System.out.println(results);
@@ -252,19 +259,21 @@
         engine.index(workspaceName1, path("/"), 100);
         QueryCommand query = sql.parseQuery("SELECT model, maker FROM __ALLNODES__ WHERE maker = 'Toyota'", typeSystem);
         QueryResults results = engine.query(context, workspaceName1, query, schemata);
+        assertNoErrors(results);
         assertThat(results, is(notNullValue()));
         assertThat(results.getRowCount(), is(2));
         System.out.println(results);
     }
 
-    @Ignore
     @Test
     public void shouldFindNodesBySimpleQueryWithGreaterThanComparisonCriteria() {
         engine.index(workspaceName1, path("/"), 100);
-        QueryCommand query = sql.parseQuery("SELECT model, maker FROM __ALLNODES__ WHERE mpgHighway > 20", typeSystem);
+        QueryCommand query = sql.parseQuery("SELECT model, maker, mpgHighway, mpgCity FROM __ALLNODES__ WHERE mpgHighway > 20",
+                                            typeSystem);
         QueryResults results = engine.query(context, workspaceName1, query, schemata);
+        assertNoErrors(results);
         assertThat(results, is(notNullValue()));
-        assertThat(results.getRowCount(), is(2));
+        assertThat(results.getRowCount(), is(6));
         System.out.println(results);
     }
 
@@ -273,6 +282,7 @@
         engine.index(workspaceName1, path("/"), 100);
         QueryCommand query = sql.parseQuery("SELECT model, maker FROM __ALLNODES__ WHERE LOWER(maker) = 'toyota'", typeSystem);
         QueryResults results = engine.query(context, workspaceName1, query, schemata);
+        assertNoErrors(results);
         assertThat(results, is(notNullValue()));
         assertThat(results.getRowCount(), is(2));
         System.out.println(results);
@@ -283,6 +293,7 @@
         engine.index(workspaceName1, path("/"), 100);
         QueryCommand query = sql.parseQuery("SELECT model, maker FROM __ALLNODES__ WHERE UPPER(maker) = 'TOYOTA'", typeSystem);
         QueryResults results = engine.query(context, workspaceName1, query, schemata);
+        assertNoErrors(results);
         assertThat(results, is(notNullValue()));
         assertThat(results.getRowCount(), is(2));
         System.out.println(results);
@@ -293,6 +304,7 @@
         engine.index(workspaceName1, path("/"), 100);
         QueryCommand query = sql.parseQuery("SELECT model, maker FROM __ALLNODES__ WHERE maker LIKE 'Toyo%'", typeSystem);
         QueryResults results = engine.query(context, workspaceName1, query, schemata);
+        assertNoErrors(results);
         assertThat(results, is(notNullValue()));
         assertThat(results.getRowCount(), is(2));
         System.out.println(results);
@@ -303,6 +315,7 @@
         engine.index(workspaceName1, path("/"), 100);
         QueryCommand query = sql.parseQuery("SELECT model, maker FROM __ALLNODES__ WHERE maker LIKE '%yota'", typeSystem);
         QueryResults results = engine.query(context, workspaceName1, query, schemata);
+        assertNoErrors(results);
         assertThat(results, is(notNullValue()));
         assertThat(results.getRowCount(), is(2));
         System.out.println(results);
@@ -313,6 +326,7 @@
         engine.index(workspaceName1, path("/"), 100);
         QueryCommand query = sql.parseQuery("SELECT model, maker FROM __ALLNODES__ WHERE LOWER(maker) LIKE 'toyo%'", typeSystem);
         QueryResults results = engine.query(context, workspaceName1, query, schemata);
+        assertNoErrors(results);
         assertThat(results, is(notNullValue()));
         assertThat(results.getRowCount(), is(2));
         System.out.println(results);



More information about the dna-commits mailing list