[dna-commits] DNA SVN: r1507 - in trunk: extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene and 1 other directories.

dna-commits at lists.jboss.org dna-commits at lists.jboss.org
Thu Dec 31 13:53:07 EST 2009


Author: rhauch
Date: 2009-12-31 13:53:06 -0500 (Thu, 31 Dec 2009)
New Revision: 1507

Removed:
   trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/EncodingNamespaceRegistry.java
   trunk/extensions/dna-search-lucene/src/test/java/org/jboss/dna/search/lucene/EncodingNamespaceRegistryTest.java
Modified:
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrQueryManagerTest.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
   trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/AbstractLuceneSearchEngine.java
   trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchProcessor.java
   trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchSession.java
   trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchWorkspace.java
Log:
DNA-468 Simplified the design of the Lucene search indexes, which improves the performance signifantly (the TCK unit tests run approximately 10% or more faster). The performance improvement should be even higher when there's a lot of content and the queries have more criteria.

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrQueryManagerTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrQueryManagerTest.java	2009-12-31 16:13:57 UTC (rev 1506)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrQueryManagerTest.java	2009-12-31 18:53:06 UTC (rev 1507)
@@ -109,9 +109,16 @@
             } finally {
                 stream.close();
             }
+
+            // Prime creating a first XPath query and SQL query ...
+            session.getWorkspace().getQueryManager().createQuery("//element(*,nt:unstructured)", Query.XPATH);
+            session.getWorkspace().getQueryManager().createQuery("SELECT * FROM [nt:base]", JcrRepository.QueryLanguage.SQL);
         } finally {
             session.logout();
         }
+
+        // Prime creating the schemata ...
+        repository.getRepositoryTypeManager().getRepositorySchemata();
     }
 
     @AfterClass
@@ -201,7 +208,7 @@
         QueryResult result = query.execute();
         assertThat(result, is(notNullValue()));
         assertResultsHaveColumns(result, "jcr:primaryType");
-        System.out.println(result);
+        // System.out.println(result);
     }
 
     // ----------------------------------------------------------------------------------------------------------------
@@ -238,7 +245,7 @@
         assertThat(query, is(notNullValue()));
         QueryResult result = query.execute();
         assertThat(result, is(notNullValue()));
-        System.out.println(result);
+        // System.out.println(result);
         assertResultsHaveColumns(result,
                                  "jcr:primaryType",
                                  "jcr:path",
@@ -262,7 +269,7 @@
         assertThat(query, is(notNullValue()));
         QueryResult result = query.execute();
         assertThat(result, is(notNullValue()));
-        System.out.println(result);
+        // System.out.println(result);
         assertResultsHaveColumns(result, "jcr:primaryType", "jcr:path", "jcr:score");
     }
 

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java	2009-12-31 16:13:57 UTC (rev 1506)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java	2009-12-31 18:53:06 UTC (rev 1507)
@@ -288,7 +288,7 @@
             // addTest(org.apache.jackrabbit.test.api.observation.TestAll.suite());
             // addTest(org.apache.jackrabbit.test.api.version.TestAll.suite());
             addTest(org.apache.jackrabbit.test.api.lock.TestAll.suite());
-            // addTest(org.apache.jackrabbit.test.api.util.TestAll.suite());
+            addTest(org.apache.jackrabbit.test.api.util.TestAll.suite());
         }
     }
 

Modified: trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/AbstractLuceneSearchEngine.java
===================================================================
--- trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/AbstractLuceneSearchEngine.java	2009-12-31 16:13:57 UTC (rev 1506)
+++ trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/AbstractLuceneSearchEngine.java	2009-12-31 18:53:06 UTC (rev 1507)
@@ -30,7 +30,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import net.jcip.annotations.NotThreadSafe;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.BooleanQuery;
@@ -75,6 +74,7 @@
 import org.jboss.dna.graph.query.model.FullTextSearch;
 import org.jboss.dna.graph.query.model.FullTextSearchScore;
 import org.jboss.dna.graph.query.model.Length;
+import org.jboss.dna.graph.query.model.Limit;
 import org.jboss.dna.graph.query.model.Literal;
 import org.jboss.dna.graph.query.model.LowerCase;
 import org.jboss.dna.graph.query.model.NodeDepth;
@@ -425,6 +425,23 @@
                 SelectComponent selector = new SelectComponent(tuplesProcessor, postProcessConstraint, request.variables());
                 tuples = selector.execute();
             }
+
+            // Limit the tuples ...
+            Limit limit = request.limit();
+            if (!limit.isUnlimited()) {
+                int firstIndex = limit.getOffset();
+                int maxRows = Math.min(tuples.size(), limit.getRowLimit());
+                if (firstIndex > 0) {
+                    if (firstIndex > tuples.size()) {
+                        tuples.clear();
+                    } else {
+                        tuples = tuples.subList(firstIndex, maxRows);
+                    }
+                } else {
+                    tuples = tuples.subList(0, maxRows);
+                }
+            }
+
             executingNanos = System.nanoTime() - executingNanos;
             Statistics stats = new Statistics(planningNanos, 0L, 0L, executingNanos);
             request.setResults(tuples, stats);
@@ -753,17 +770,10 @@
          */
         TupleCollector createTupleCollector( Columns columns );
 
-        /**
-         * Utility method to create a query to find all of the documents representing nodes with the supplied IDs.
-         * 
-         * @param ids the IDs of the nodes that are to be found; may not be null
-         * @return the query; never null
-         * @throws IOException if there is a problem creating this query
-         */
-        Query findAllNodesWithIds( Set<String> ids ) throws IOException;
-
         Query findAllNodesBelow( Path ancestorPath ) throws IOException;
 
+        Query findAllNodesAtOrBelow( Path ancestorPath ) throws IOException;
+
         /**
          * Return a query that can be used to find all of the documents that represent nodes that are children of the node at the
          * supplied path.
@@ -845,6 +855,6 @@
          * 
          * @return the tuples; never null
          */
-        public abstract LinkedList<Object[]> getTuples();
+        public abstract List<Object[]> getTuples();
     }
 }

Deleted: trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/EncodingNamespaceRegistry.java
===================================================================
--- trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/EncodingNamespaceRegistry.java	2009-12-31 16:13:57 UTC (rev 1506)
+++ trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/EncodingNamespaceRegistry.java	2009-12-31 18:53:06 UTC (rev 1507)
@@ -1,233 +0,0 @@
-/*
- * JBoss DNA (http://www.jboss.org/dna)
- * See the COPYRIGHT.txt file distributed with this work for information
- * regarding copyright ownership.  Some portions may be licensed
- * to Red Hat, Inc. under one or more contributor license agreements.
- * See the AUTHORS.txt file in the distribution for a full listing of 
- * individual contributors.
- *
- * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- * is licensed to you under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- * 
- * JBoss DNA is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
- */
-package org.jboss.dna.search.lucene;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import net.jcip.annotations.GuardedBy;
-import net.jcip.annotations.ThreadSafe;
-import org.jboss.dna.common.text.NoOpEncoder;
-import org.jboss.dna.common.text.TextEncoder;
-import org.jboss.dna.graph.DnaLexicon;
-import org.jboss.dna.graph.JcrLexicon;
-import org.jboss.dna.graph.JcrMixLexicon;
-import org.jboss.dna.graph.JcrNtLexicon;
-import org.jboss.dna.graph.property.NamespaceRegistry;
-import org.jboss.dna.graph.property.basic.BasicNamespace;
-
-/**
- * A {@link NamespaceRegistry} implementation that uses encoded representations of the namespace URIs for the namespace prefixes.
- */
- at ThreadSafe
-class EncodingNamespaceRegistry implements NamespaceRegistry {
-
-    public static final Set<String> DEFAULT_FIXED_NAMESPACES = Collections.unmodifiableSet(new HashSet<String>(
-                                                                                                               Arrays.asList(new String[] {
-                                                                                                                   "",
-                                                                                                                   DnaLexicon.Namespace.URI,
-                                                                                                                   JcrLexicon.Namespace.URI,
-                                                                                                                   JcrNtLexicon.Namespace.URI,
-                                                                                                                   JcrMixLexicon.Namespace.URI})));
-
-    private final NamespaceRegistry registry;
-    private final TextEncoder encoder;
-    private final ReadWriteLock lock = new ReentrantReadWriteLock();
-    @GuardedBy( "lock" )
-    private final Map<String, String> uriToEncodedPrefix = new HashMap<String, String>();
-    @GuardedBy( "lock" )
-    private final Map<String, String> encodedPrefixToUri = new HashMap<String, String>();
-    private final Set<String> fixedNamespaceUris;
-
-    /**
-     * @param registry the original registry
-     * @param encoder the encoder; may be null if no encoding should be used
-     */
-    EncodingNamespaceRegistry( NamespaceRegistry registry,
-                               TextEncoder encoder ) {
-        this(registry, encoder, null);
-    }
-
-    /**
-     * @param registry the original registry
-     * @param encoder the encoder; may be null if no encoding should be used
-     * @param fixedUris the set of URIs that is to be fixed and not encoded; or null if the default namespaces are to be fixed
-     */
-    EncodingNamespaceRegistry( NamespaceRegistry registry,
-                               TextEncoder encoder,
-                               Set<String> fixedUris ) {
-        this.registry = registry;
-        this.encoder = encoder != null ? encoder : new NoOpEncoder();
-        this.fixedNamespaceUris = fixedUris != null ? Collections.unmodifiableSet(new HashSet<String>(fixedUris)) : DEFAULT_FIXED_NAMESPACES;
-        assert this.registry != null;
-        assert this.encoder != null;
-        assert this.fixedNamespaceUris != null;
-    }
-
-    /**
-     * @return fixedNamespaceUris
-     */
-    public Set<String> getFixedNamespaceUris() {
-        return fixedNamespaceUris;
-    }
-
-    /**
-     * {@inheritDoc}
-     * 
-     * @see org.jboss.dna.graph.property.NamespaceRegistry#getDefaultNamespaceUri()
-     */
-    public String getDefaultNamespaceUri() {
-        return this.registry.getDefaultNamespaceUri();
-    }
-
-    /**
-     * {@inheritDoc}
-     * 
-     * @see org.jboss.dna.graph.property.NamespaceRegistry#getNamespaceForPrefix(java.lang.String)
-     */
-    public String getNamespaceForPrefix( String prefix ) {
-        // First look in the map ...
-        String result = null;
-        try {
-            lock.readLock().lock();
-            result = encodedPrefixToUri.get(prefix);
-            if (result != null) return result;
-        } finally {
-            lock.readLock().unlock();
-        }
-
-        // Make sure we have encoded all the namespaces in the registry ...
-        Set<Namespace> namespaces = new HashSet<Namespace>(this.registry.getNamespaces());
-        Set<Namespace> encodedNamespaces = this.getNamespaces();
-        namespaces.removeAll(encodedNamespaces);
-        try {
-            lock.writeLock().lock();
-            for (Namespace namespace : namespaces) {
-                String namespaceUri = namespace.getNamespaceUri();
-                String encoded = fixedNamespaceUris.contains(namespaceUri) ? namespace.getPrefix() : encoder.encode(namespaceUri);
-                uriToEncodedPrefix.put(namespaceUri, encoded);
-                encodedPrefixToUri.put(encoded, namespaceUri);
-                if (result == null && encoded.equals(prefix)) result = namespaceUri;
-            }
-        } finally {
-            lock.writeLock().unlock();
-        }
-        if (result != null) return result;
-
-        // There's nothing, so just delegate to the registry ...
-        return this.registry.getNamespaceForPrefix(prefix);
-    }
-
-    /**
-     * {@inheritDoc}
-     * 
-     * @see org.jboss.dna.graph.property.NamespaceRegistry#getRegisteredNamespaceUris()
-     */
-    public Set<String> getRegisteredNamespaceUris() {
-        return this.registry.getRegisteredNamespaceUris();
-    }
-
-    /**
-     * {@inheritDoc}
-     * 
-     * @see org.jboss.dna.graph.property.NamespaceRegistry#isRegisteredNamespaceUri(java.lang.String)
-     */
-    public boolean isRegisteredNamespaceUri( String namespaceUri ) {
-        return this.registry.isRegisteredNamespaceUri(namespaceUri);
-    }
-
-    /**
-     * {@inheritDoc}
-     * 
-     * @see org.jboss.dna.graph.property.NamespaceRegistry#getPrefixForNamespaceUri(java.lang.String, boolean)
-     */
-    public String getPrefixForNamespaceUri( String namespaceUri,
-                                            boolean generateIfMissing ) {
-        if (fixedNamespaceUris.contains(namespaceUri)) {
-            return this.registry.getPrefixForNamespaceUri(namespaceUri, generateIfMissing);
-        }
-        String encoded = null;
-        try {
-            lock.readLock().lock();
-            encoded = uriToEncodedPrefix.get(namespaceUri);
-        } finally {
-            lock.readLock().unlock();
-        }
-        if (encoded == null) {
-            encoded = encoder.encode(namespaceUri);
-            try {
-                lock.writeLock().lock();
-                uriToEncodedPrefix.put(namespaceUri, encoded);
-                encodedPrefixToUri.put(encoded, namespaceUri);
-            } finally {
-                lock.writeLock().unlock();
-            }
-        }
-        return encoded;
-    }
-
-    /**
-     * {@inheritDoc}
-     * 
-     * @see org.jboss.dna.graph.property.NamespaceRegistry#getNamespaces()
-     */
-    public Set<Namespace> getNamespaces() {
-        Set<Namespace> results = new HashSet<Namespace>();
-        try {
-            lock.readLock().lock();
-            for (Map.Entry<String, String> entry : uriToEncodedPrefix.entrySet()) {
-                String uri = entry.getKey();
-                String prefix = entry.getValue();
-                results.add(new BasicNamespace(prefix, uri));
-            }
-        } finally {
-            lock.readLock().unlock();
-        }
-        return results;
-    }
-
-    /**
-     * {@inheritDoc}
-     * 
-     * @see org.jboss.dna.graph.property.NamespaceRegistry#register(java.lang.String, java.lang.String)
-     */
-    public String register( String prefix,
-                            String namespaceUri ) {
-        return this.registry.register(prefix, namespaceUri);
-    }
-
-    /**
-     * {@inheritDoc}
-     * 
-     * @see org.jboss.dna.graph.property.NamespaceRegistry#unregister(java.lang.String)
-     */
-    public boolean unregister( String namespaceUri ) {
-        return this.registry.unregister(namespaceUri);
-    }
-}

Modified: trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchProcessor.java
===================================================================
--- trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchProcessor.java	2009-12-31 16:13:57 UTC (rev 1506)
+++ trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchProcessor.java	2009-12-31 18:53:06 UTC (rev 1507)
@@ -29,24 +29,15 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.UUID;
 import net.jcip.annotations.NotThreadSafe;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field;
-import org.apache.lucene.document.NumericField;
 import org.apache.lucene.queryParser.ParseException;
-import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.Query;
-import org.jboss.dna.common.text.SecureHashTextEncoder;
-import org.jboss.dna.common.text.TextEncoder;
 import org.jboss.dna.common.util.Logger;
-import org.jboss.dna.common.util.SecureHash.Algorithm;
 import org.jboss.dna.graph.ExecutionContext;
 import org.jboss.dna.graph.Location;
 import org.jboss.dna.graph.observe.Observer;
 import org.jboss.dna.graph.property.DateTime;
 import org.jboss.dna.graph.property.Path;
-import org.jboss.dna.graph.property.Property;
 import org.jboss.dna.graph.query.QueryResults.Columns;
 import org.jboss.dna.graph.query.QueryResults.Statistics;
 import org.jboss.dna.graph.query.process.FullTextSearchResultColumns;
@@ -69,7 +60,6 @@
 import org.jboss.dna.graph.search.SearchEngineProcessor;
 import org.jboss.dna.graph.search.AbstractSearchEngine.Workspaces;
 import org.jboss.dna.search.lucene.AbstractLuceneSearchEngine.AbstractLuceneProcessor;
-import org.jboss.dna.search.lucene.LuceneSearchWorkspace.PathIndex;
 
 /**
  * Abstract {@link SearchEngineProcessor} implementation for the {@link LuceneSearchEngine}.
@@ -77,15 +67,6 @@
 @NotThreadSafe
 public class LuceneSearchProcessor extends AbstractLuceneProcessor<LuceneSearchWorkspace, LuceneSearchSession> {
 
-    protected static final TextEncoder NAMESPACE_ENCODER = new SecureHashTextEncoder(Algorithm.SHA_1, 10);
-
-    protected static ExecutionContext contextWithEncodedNamespaces( ExecutionContext context ) {
-        return context;
-        // NamespaceRegistry encodingRegistry = new EncodingNamespaceRegistry(context.getNamespaceRegistry(), NAMESPACE_ENCODER);
-        // ExecutionContext encodingContext = context.with(encodingRegistry);
-        // return encodingContext;
-    }
-
     protected static final Columns FULL_TEXT_RESULT_COLUMNS = new FullTextSearchResultColumns();
 
     protected LuceneSearchProcessor( String sourceName,
@@ -94,7 +75,7 @@
                                      Observer observer,
                                      DateTime now,
                                      boolean readOnly ) {
-        super(sourceName, contextWithEncodedNamespaces(context), workspaces, observer, now, readOnly);
+        super(sourceName, context, workspaces, observer, now, readOnly);
     }
 
     /**
@@ -117,15 +98,6 @@
         return LuceneSearchWorkspace.FULL_TEXT_PREFIX + propertyName;
     }
 
-    protected void addIdProperties( Location location,
-                                    Document doc ) {
-        if (!location.hasIdProperties()) return;
-        for (Property idProp : location.getIdProperties()) {
-            String fieldValue = serializeProperty(idProp);
-            doc.add(new Field(PathIndex.LOCATION_ID_PROPERTIES, fieldValue, Field.Store.YES, Field.Index.NOT_ANALYZED));
-        }
-    }
-
     /**
      * {@inheritDoc}
      * 
@@ -157,7 +129,7 @@
         if (session == null) return;
         request.setActualWorkspaceName(session.getWorkspaceName());
         try {
-            request.setActualRootLocation(session.getLocationFor(pathFactory.createRootPath()));
+            request.setActualRootLocation(session.getLocationForRoot());
         } catch (IOException e) {
             request.setError(e);
         }
@@ -201,43 +173,13 @@
         }
 
         try {
-            // Create a separate document for the path, which makes it easier to handle moves since the path can
-            // be changed without changing any other content fields ...
-            Document doc = new Document();
-            String idStr = createPathDocument(location, doc);
-            session.getPathsWriter().addDocument(doc);
-
-            // Now set the content ...
-            session.setOrReplaceProperties(idStr, request.properties());
+            session.setOrReplaceProperties(location, request.properties());
             session.recordChange();
         } catch (IOException e) {
             request.setError(e);
         }
     }
 
-    protected String createPathDocument( Location location,
-                                         Document doc ) {
-        UUID uuid = location.getUuid();
-        if (uuid == null) uuid = UUID.randomUUID();
-        Path path = location.getPath();
-        String idStr = stringFactory.create(uuid);
-        String pathStr = pathAsString(path);
-        String nameStr = path.isRoot() ? "" : stringFactory.create(path.getLastSegment().getName());
-        String localNameStr = path.isRoot() ? "" : path.getLastSegment().getName().getLocalName();
-        int sns = path.isRoot() ? 1 : path.getLastSegment().getIndex();
-
-        // Create a separate document for the path, which makes it easier to handle moves since the path can
-        // be changed without changing any other content fields ...
-        doc.add(new Field(PathIndex.PATH, pathStr, Field.Store.YES, Field.Index.NOT_ANALYZED));
-        doc.add(new Field(PathIndex.NODE_NAME, nameStr, Field.Store.YES, Field.Index.NOT_ANALYZED));
-        doc.add(new Field(PathIndex.LOCAL_NAME, localNameStr, Field.Store.YES, Field.Index.NOT_ANALYZED));
-        doc.add(new NumericField(PathIndex.SNS_INDEX, Field.Store.YES, true).setIntValue(sns));
-        doc.add(new Field(PathIndex.ID, idStr, Field.Store.YES, Field.Index.NOT_ANALYZED));
-        doc.add(new NumericField(PathIndex.DEPTH, Field.Store.YES, true).setIntValue(path.size()));
-        addIdProperties(location, doc);
-        return idStr;
-    }
-
     /**
      * {@inheritDoc}
      * 
@@ -251,25 +193,11 @@
 
         Location location = request.getActualLocationOfNode();
         assert location != null;
-        UUID uuid = location.getUuid();
 
         try {
-            // If we're updating the root properties, make sure there is a document in the path index ...
-            String idStr = null;
-            if (location.getPath() != null && location.getPath().isRoot()) {
-                Document doc = new Document();
-                idStr = createPathDocument(location, doc);
-                session.getPathsWriter().addDocument(doc);
-            } else if (uuid != null) {
-                idStr = stringFactory.create(uuid);
-            } else {
-                // Need to look up the id string ...
-                idStr = session.getIdFor(location.getPath());
-            }
-
             // We make a big assumption here: the UpdatePropertiesRequest created by the SearchEngineProcessor have the
             // actual locations set ...
-            session.setOrReplaceProperties(idStr, request.properties().values());
+            session.setOrReplaceProperties(location, request.properties().values());
             session.recordChange();
         } catch (IOException e) {
             request.setError(e);
@@ -289,21 +217,11 @@
         Path path = request.at().getPath();
         assert !readOnly;
         try {
-            Query query = null;
-            int numChanges = 0;
-            if (path.isRoot()) {
-                query = new MatchAllDocsQuery();
-                numChanges = LuceneSearchWorkspace.CHANGES_BEFORE_OPTIMIZATION + 100;
-            } else {
-                // Create a query to find all the nodes at or below the specified path ...
-                Set<String> ids = session.getIdsForDescendantsOf(path, true);
-                query = session.findAllNodesWithIds(ids);
-                numChanges = ids.size();
-            }
+            // Create a query to find all the nodes at or below the specified path (this efficiently handles the root path) ...
+            Query query = session.findAllNodesAtOrBelow(path);
             // Now delete the documents from each index using this query, which we can reuse ...
-            session.getPathsWriter().deleteDocuments(query);
             session.getContentWriter().deleteDocuments(query);
-            session.recordChanges(numChanges);
+            session.recordChanges(100);
         } catch (FileNotFoundException e) {
             // There are no index files yet, so nothing to delete ...
         } catch (IOException e) {
@@ -322,7 +240,7 @@
         if (workspace == null) return;
         try {
             LuceneSearchSession session = getSessionFor(request, workspace.getWorkspaceName());
-            request.setActualRootLocation(session.getLocationFor(pathFactory.createRootPath()));
+            request.setActualRootLocation(session.getLocationForRoot());
             workspace.destroy(getExecutionContext());
             session.recordChanges(LuceneSearchWorkspace.CHANGES_BEFORE_OPTIMIZATION + 100);
         } catch (IOException e) {

Modified: trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchSession.java
===================================================================
--- trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchSession.java	2009-12-31 16:13:57 UTC (rev 1506)
+++ trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchSession.java	2009-12-31 18:53:06 UTC (rev 1507)
@@ -24,6 +24,7 @@
 package org.jboss.dna.search.lucene;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -44,8 +45,6 @@
 import org.apache.lucene.queryParser.ParseException;
 import org.apache.lucene.queryParser.QueryParser;
 import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.Collector;
-import org.apache.lucene.search.FieldCache;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.NumericRangeQuery;
@@ -82,12 +81,10 @@
 import org.jboss.dna.search.lucene.IndexRules.NumericRule;
 import org.jboss.dna.search.lucene.IndexRules.Rule;
 import org.jboss.dna.search.lucene.LuceneSearchWorkspace.ContentIndex;
-import org.jboss.dna.search.lucene.LuceneSearchWorkspace.PathIndex;
 import org.jboss.dna.search.lucene.query.CompareLengthQuery;
 import org.jboss.dna.search.lucene.query.CompareNameQuery;
 import org.jboss.dna.search.lucene.query.ComparePathQuery;
 import org.jboss.dna.search.lucene.query.CompareStringQuery;
-import org.jboss.dna.search.lucene.query.IdsQuery;
 import org.jboss.dna.search.lucene.query.MatchNoneQuery;
 import org.jboss.dna.search.lucene.query.NotQuery;
 
@@ -100,22 +97,11 @@
     /**
      * An immutable {@link FieldSelector} instance that accesses the UUID field.
      */
-    protected static final FieldSelector DOC_ID_FIELD_SELECTOR = new FieldSelector() {
-        private static final long serialVersionUID = 1L;
-
-        public FieldSelectorResult accept( String fieldName ) {
-            return ContentIndex.ID.equals(fieldName) ? FieldSelectorResult.LOAD_AND_BREAK : FieldSelectorResult.NO_LOAD;
-        }
-    };
-
-    /**
-     * An immutable {@link FieldSelector} instance that accesses the UUID field.
-     */
     protected static final FieldSelector LOCATION_FIELDS_SELECTOR = new FieldSelector() {
         private static final long serialVersionUID = 1L;
 
         public FieldSelectorResult accept( String fieldName ) {
-            if (PathIndex.PATH.equals(fieldName) || PathIndex.LOCATION_ID_PROPERTIES.equals(fieldName)) {
+            if (ContentIndex.PATH.equals(fieldName) || ContentIndex.LOCATION_ID_PROPERTIES.equals(fieldName)) {
                 return FieldSelectorResult.LOAD;
             }
             return FieldSelectorResult.NO_LOAD;
@@ -129,11 +115,7 @@
 
     private final LuceneSearchWorkspace workspace;
     protected final LuceneSearchProcessor processor;
-    private final Directory pathsIndexDirectory;
     private final Directory contentIndexDirectory;
-    private IndexReader pathsReader;
-    private IndexWriter pathsWriter;
-    private IndexSearcher pathsSearcher;
     private IndexReader contentReader;
     private IndexWriter contentWriter;
     private IndexSearcher contentSearcher;
@@ -144,7 +126,6 @@
         assert workspace != null;
         assert processor != null;
         this.workspace = workspace;
-        this.pathsIndexDirectory = workspace.pathDirectory;
         this.contentIndexDirectory = workspace.contentDirectory;
         this.processor = processor;
     }
@@ -165,21 +146,6 @@
         return workspace;
     }
 
-    protected IndexReader getPathsReader() throws IOException {
-        if (pathsReader == null) {
-            try {
-                pathsReader = IndexReader.open(pathsIndexDirectory, processor.readOnly);
-            } catch (IOException e) {
-                // try creating the workspace ...
-                IndexWriter writer = new IndexWriter(pathsIndexDirectory, workspace.analyzer, MaxFieldLength.UNLIMITED);
-                writer.close();
-                // And try reading again ...
-                pathsReader = IndexReader.open(pathsIndexDirectory, processor.readOnly);
-            }
-        }
-        return pathsReader;
-    }
-
     protected IndexReader getContentReader() throws IOException {
         if (contentReader == null) {
             try {
@@ -195,15 +161,6 @@
         return contentReader;
     }
 
-    protected IndexWriter getPathsWriter() throws IOException {
-        assert !processor.readOnly;
-        if (pathsWriter == null) {
-            // Don't overwrite, but create if missing ...
-            pathsWriter = new IndexWriter(pathsIndexDirectory, workspace.analyzer, MaxFieldLength.UNLIMITED);
-        }
-        return pathsWriter;
-    }
-
     protected IndexWriter getContentWriter() throws IOException {
         assert !processor.readOnly;
         if (contentWriter == null) {
@@ -213,13 +170,6 @@
         return contentWriter;
     }
 
-    protected IndexSearcher getPathsSearcher() throws IOException {
-        if (pathsSearcher == null) {
-            pathsSearcher = new IndexSearcher(getPathsReader());
-        }
-        return pathsSearcher;
-    }
-
     public IndexSearcher getContentSearcher() throws IOException {
         if (contentSearcher == null) {
             contentSearcher = new IndexSearcher(getContentReader());
@@ -228,7 +178,7 @@
     }
 
     public boolean hasWriters() {
-        return pathsWriter != null || contentWriter != null;
+        return contentWriter != null;
     }
 
     protected final void recordChange() {
@@ -261,47 +211,17 @@
 
         IOException ioError = null;
         RuntimeException runtimeError = null;
-        if (pathsReader != null) {
+        if (contentReader != null) {
             try {
-                pathsReader.close();
+                contentReader.close();
             } catch (IOException e) {
                 ioError = e;
             } catch (RuntimeException e) {
                 runtimeError = e;
             } finally {
-                pathsReader = null;
-            }
-        }
-        if (contentReader != null) {
-            try {
-                contentReader.close();
-            } catch (IOException e) {
-                if (ioError == null) ioError = e;
-            } catch (RuntimeException e) {
-                if (runtimeError == null) runtimeError = e;
-            } finally {
                 contentReader = null;
             }
         }
-        if (pathsWriter != null) {
-            try {
-                if (optimize) pathsWriter.optimize();
-            } catch (IOException e) {
-                if (ioError == null) ioError = e;
-            } catch (RuntimeException e) {
-                if (runtimeError == null) runtimeError = e;
-            } finally {
-                try {
-                    pathsWriter.close();
-                } catch (IOException e) {
-                    if (ioError == null) ioError = e;
-                } catch (RuntimeException e) {
-                    if (runtimeError == null) runtimeError = e;
-                } finally {
-                    pathsWriter = null;
-                }
-            }
-        }
         if (contentWriter != null) {
             try {
                 if (optimize) contentWriter.optimize();
@@ -339,45 +259,15 @@
         numChanges = 0;
         IOException ioError = null;
         RuntimeException runtimeError = null;
-        if (pathsReader != null) {
-            try {
-                pathsReader.close();
-            } catch (IOException e) {
-                ioError = e;
-            } catch (RuntimeException e) {
-                runtimeError = e;
-            } finally {
-                pathsReader = null;
-            }
-        }
         if (contentReader != null) {
             try {
                 contentReader.close();
             } catch (IOException e) {
-                if (ioError == null) ioError = e;
-            } catch (RuntimeException e) {
-                if (runtimeError == null) runtimeError = e;
-            } finally {
-                contentReader = null;
-            }
-        }
-        if (pathsWriter != null) {
-            try {
-                pathsWriter.rollback();
-            } catch (IOException e) {
                 ioError = e;
             } catch (RuntimeException e) {
                 runtimeError = e;
             } finally {
-                try {
-                    pathsWriter.close();
-                } catch (IOException e) {
-                    ioError = e;
-                } catch (RuntimeException e) {
-                    runtimeError = e;
-                } finally {
-                    pathsWriter = null;
-                }
+                contentReader = null;
             }
         }
         if (contentWriter != null) {
@@ -417,12 +307,10 @@
         QueryParser parser = new QueryParser(Version.LUCENE_29, ContentIndex.FULL_TEXT, workspace.analyzer);
         Query query = parser.parse(fullTextSearchExpression);
         planningNanos = System.nanoTime() - planningNanos;
-        TopDocs docs = getContentSearcher().search(query, maxRows + offset);
 
-        // Collect the results ...
+        // Execute the search and place the results into the supplied list ...
+        TopDocs docs = getContentSearcher().search(query, maxRows + offset);
         IndexReader contentReader = getContentReader();
-        IndexReader pathReader = getPathsReader();
-        IndexSearcher pathSearcher = getPathsSearcher();
         ScoreDoc[] scoreDocs = docs.scoreDocs;
         int numberOfResults = scoreDocs.length;
         if (numberOfResults > offset) {
@@ -431,14 +319,8 @@
                 ScoreDoc result = scoreDocs[i];
                 int docId = result.doc;
                 // Find the UUID of the node (this UUID might be artificial, so we have to find the path) ...
-                Document doc = contentReader.document(docId, DOC_ID_FIELD_SELECTOR);
-                String id = doc.get(ContentIndex.ID);
-                Document pathDoc = getPathDocument(id, pathReader, pathSearcher, LOCATION_FIELDS_SELECTOR);
-                Location location = readLocation(pathDoc);
-                if (location == null) {
-                    // No path record found ...
-                    continue;
-                }
+                Document doc = contentReader.document(docId, LOCATION_FIELDS_SELECTOR);
+                Location location = readLocation(doc);
                 // Now add the location ...
                 results.add(new Object[] {location, result.score});
             }
@@ -449,10 +331,10 @@
 
     protected Location readLocation( Document doc ) {
         // Read the path ...
-        String pathString = doc.get(PathIndex.PATH);
+        String pathString = doc.get(ContentIndex.PATH);
         Path path = processor.pathFactory.create(pathString);
         // Look for the Location's ID properties ...
-        String[] idProps = doc.getValues(PathIndex.LOCATION_ID_PROPERTIES);
+        String[] idProps = doc.getValues(ContentIndex.LOCATION_ID_PROPERTIES);
         if (idProps.length == 0) {
             return Location.create(path);
         }
@@ -472,11 +354,33 @@
         return properties.isEmpty() ? Location.create(path) : Location.create(path, properties);
     }
 
-    protected void setOrReplaceProperties( String idString,
+    protected void setOrReplaceProperties( Location location,
                                            Iterable<Property> properties ) throws IOException {
         // Create the document for the content (properties) ...
         Document doc = new Document();
-        doc.add(new Field(ContentIndex.ID, idString, Field.Store.YES, Field.Index.NOT_ANALYZED));
+
+        // Add the information every node has ...
+        Path path = location.getPath();
+        String pathStr = processor.pathAsString(path);
+        String nameStr = path.isRoot() ? "" : processor.stringFactory.create(path.getLastSegment().getName());
+        String localNameStr = path.isRoot() ? "" : path.getLastSegment().getName().getLocalName();
+        int sns = path.isRoot() ? 1 : path.getLastSegment().getIndex();
+
+        // Create a separate document for the path, which makes it easier to handle moves since the path can
+        // be changed without changing any other content fields ...
+        doc.add(new Field(ContentIndex.PATH, pathStr, Field.Store.YES, Field.Index.NOT_ANALYZED));
+        doc.add(new Field(ContentIndex.NODE_NAME, nameStr, Field.Store.YES, Field.Index.NOT_ANALYZED));
+        doc.add(new Field(ContentIndex.LOCAL_NAME, localNameStr, Field.Store.YES, Field.Index.NOT_ANALYZED));
+        doc.add(new NumericField(ContentIndex.SNS_INDEX, Field.Store.YES, true).setIntValue(sns));
+        doc.add(new NumericField(ContentIndex.DEPTH, Field.Store.YES, true).setIntValue(path.size()));
+        if (location.hasIdProperties()) {
+            for (Property idProp : location.getIdProperties()) {
+                String fieldValue = processor.serializeProperty(idProp);
+                doc.add(new Field(ContentIndex.LOCATION_ID_PROPERTIES, fieldValue, Field.Store.YES, Field.Index.NOT_ANALYZED));
+            }
+        }
+
+        // Index the properties
         String stringValue = null;
         StringBuilder fullTextSearchValue = null;
         for (Property property : properties) {
@@ -559,193 +463,90 @@
         if (fullTextSearchValue != null && fullTextSearchValue.length() != 0) {
             doc.add(new Field(ContentIndex.FULL_TEXT, fullTextSearchValue.toString(), Field.Store.NO, Field.Index.ANALYZED));
         }
-        getContentWriter().updateDocument(new Term(ContentIndex.ID, idString), doc);
+        getContentWriter().updateDocument(new Term(ContentIndex.PATH, pathStr), doc);
     }
 
-    protected Document getPathDocument( String id,
-                                        IndexReader pathReader,
-                                        IndexSearcher pathSearcher,
-                                        FieldSelector selector ) throws IOException {
-        // Find the path for this node (is there a better way to do this than one search per ID?) ...
-        TopDocs pathDocs = pathSearcher.search(new TermQuery(new Term(PathIndex.ID, id)), 1);
-        if (pathDocs.scoreDocs.length < 1) {
-            // No path record found ...
-            return null;
-        }
-        return pathReader.document(pathDocs.scoreDocs[0].doc, selector);
-    }
-
     /**
-     * Get the set of IDs for the children of the node at the given path.
+     * {@inheritDoc}
      * 
-     * @param parentPath the path to the parent node; may not be null
-     * @return the doc IDs of the child nodes; never null but possibly empty
-     * @throws IOException if there is an error accessing the indexes
+     * @see org.jboss.dna.search.lucene.AbstractLuceneSearchEngine.WorkspaceSession#createTupleCollector(org.jboss.dna.graph.query.QueryResults.Columns)
      */
-    protected Set<String> getIdsForChildrenOf( Path parentPath ) throws IOException {
-        // Find the path of the parent ...
-        String stringifiedPath = processor.pathAsString(parentPath);
-        // Append a '/' to the parent path, so we'll only get decendants ...
-        stringifiedPath = stringifiedPath + '/';
+    public TupleCollector createTupleCollector( Columns columns ) {
+        return new DualIndexTupleCollector(this, columns);
+    }
 
-        // Create a query to find all the nodes below the parent path ...
-        Query query = new PrefixQuery(new Term(PathIndex.PATH, stringifiedPath));
-        // Include only the children ...
-        int childrenDepth = parentPath.size() + 1;
-        Query depthQuery = NumericRangeQuery.newIntRange(PathIndex.DEPTH, childrenDepth, childrenDepth, true, true);
-        // And combine ...
-        BooleanQuery combinedQuery = new BooleanQuery();
-        combinedQuery.add(query, Occur.MUST);
-        combinedQuery.add(depthQuery, Occur.MUST);
-        query = combinedQuery;
+    public Location getLocationForRoot() throws IOException {
+        // Look for the root node ...
+        Query query = NumericRangeQuery.newIntRange(ContentIndex.DEPTH, 0, 0, true, true);
 
-        // Now execute and collect the IDs ...
-        IdCollector idCollector = new IdCollector();
-        IndexSearcher searcher = getPathsSearcher();
-        searcher.search(query, idCollector);
-        return idCollector.getIds();
+        // Execute the search and place the results into the supplied list ...
+        List<Object[]> tuples = new ArrayList<Object[]>(1);
+        FullTextSearchTupleCollector collector = new FullTextSearchTupleCollector(this, tuples);
+        getContentSearcher().search(query, collector);
+
+        // Extract the location from the results ...
+        return tuples.isEmpty() ? Location.create(processor.pathFactory.createRootPath()) : (Location)tuples.get(0)[0];
     }
 
-    /**
-     * Get the set of IDs for the nodes that are descendants of the node at the given path.
-     * 
-     * @param parentPath the path to the parent node; may not be null and <i>may not be the root node</i>
-     * @param includeParent true if the parent node should be included in the results, or false if only the descendants should be
-     *        included
-     * @return the IDs of the nodes; never null but possibly empty
-     * @throws IOException if there is an error accessing the indexes
-     */
-    protected Set<String> getIdsForDescendantsOf( Path parentPath,
-                                                  boolean includeParent ) throws IOException {
-        assert !parentPath.isRoot();
-
+    public Query findAllNodesBelow( Path parentPath ) {
         // Find the path of the parent ...
         String stringifiedPath = processor.pathAsString(parentPath);
-        if (!includeParent) {
-            // Append a '/' to the parent path, and we'll only get decendants ...
-            stringifiedPath = stringifiedPath + '/';
-        }
+        // Append a '/' to the parent path, and we'll only get decendants ...
+        stringifiedPath = stringifiedPath + '/';
 
         // Create a prefix query ...
-        Query query = new PrefixQuery(new Term(PathIndex.PATH, stringifiedPath));
-
-        // Now execute and collect the IDs ...
-        IdCollector idCollector = new IdCollector();
-        IndexSearcher searcher = getPathsSearcher();
-        searcher.search(query, idCollector);
-        return idCollector.getIds();
+        return new PrefixQuery(new Term(ContentIndex.PATH, stringifiedPath));
     }
 
-    /**
-     * Get the set containing the single ID for the node at the given path.
-     * 
-     * @param path the path to the node; may not be null
-     * @return the ID of the supplied node; or null if the node cannot be found
-     * @throws IOException if there is an error accessing the indexes
-     */
-    protected String getIdFor( Path path ) throws IOException {
-        // Create a query to find all the nodes below the parent path ...
-        IndexSearcher searcher = getPathsSearcher();
-        Query query = null;
-        if (path.isRoot()) {
-            // Look for the query
-            query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, 0, 0, true, true);
-        } else {
-            String stringifiedPath = processor.pathAsString(path);
-            query = new TermQuery(new Term(PathIndex.PATH, stringifiedPath));
+    public Query findAllNodesAtOrBelow( Path parentPath ) {
+        if (parentPath.isRoot()) {
+            return new MatchAllDocsQuery();
         }
+        // Find the path of the parent ...
+        String stringifiedPath = processor.pathAsString(parentPath);
 
-        // Now execute and collect the UUIDs ...
-        TopDocs topDocs = searcher.search(query, 1);
-        if (topDocs.totalHits == 0) return null;
-        Document pathDoc = getPathsReader().document(topDocs.scoreDocs[0].doc);
-        String idString = pathDoc.get(PathIndex.ID);
-        assert idString != null;
-        return idString;
+        // Create a prefix query ...
+        return new PrefixQuery(new Term(ContentIndex.PATH, stringifiedPath));
     }
 
-    protected Location getLocationFor( Path path ) throws IOException {
-        // Create a query to find all the nodes below the parent path ...
-        IndexSearcher searcher = getPathsSearcher();
-        String stringifiedPath = processor.pathAsString(path);
-        TermQuery query = new TermQuery(new Term(PathIndex.PATH, stringifiedPath));
-
-        // Now execute and collect the UUIDs ...
-        TopDocs topDocs = searcher.search(query, 1);
-        if (topDocs.totalHits == 0) return null;
-        Document pathDoc = getPathsReader().document(topDocs.scoreDocs[0].doc);
-        return readLocation(pathDoc);
-    }
-
     /**
-     * {@inheritDoc}
-     * 
-     * @see org.jboss.dna.search.lucene.AbstractLuceneSearchEngine.WorkspaceSession#createTupleCollector(org.jboss.dna.graph.query.QueryResults.Columns)
-     */
-    public TupleCollector createTupleCollector( Columns columns ) {
-        return new DualIndexTupleCollector(this, columns);
-    }
-
-    public Query findAllNodesWithIds( Set<String> ids ) {
-        if (ids.isEmpty()) {
-            // There are no children, so return a null query ...
-            return new MatchNoneQuery();
-        }
-        if (ids.size() == 1) {
-            String id = ids.iterator().next();
-            if (id == null) return new MatchNoneQuery();
-            return new TermQuery(new Term(ContentIndex.ID, id));
-        }
-        if (ids.size() < 50) {
-            // Create an OR boolean query for all the UUIDs, since this is probably more efficient ...
-            BooleanQuery query = new BooleanQuery();
-            for (String id : ids) {
-                Query uuidQuery = new TermQuery(new Term(ContentIndex.ID, id));
-                query.add(uuidQuery, Occur.SHOULD);
-            }
-            return query;
-        }
-        // Return a query that will always find all of the UUIDs ...
-        return new IdsQuery(ContentIndex.ID, ids);
-    }
-
-    public Query findAllNodesBelow( Path ancestorPath ) throws IOException {
-        if (ancestorPath.isRoot()) {
-            return new MatchAllDocsQuery();
-        }
-        Set<String> ids = getIdsForDescendantsOf(ancestorPath, false);
-        return findAllNodesWithIds(ids);
-    }
-
-    /**
      * Return a query that can be used to find all of the documents that represent nodes that are children of the node at the
      * supplied path.
      * 
      * @param parentPath the path of the parent node.
      * @return the query; never null
-     * @throws IOException if there is an error finding the UUIDs of the child nodes
      */
-    public Query findChildNodes( Path parentPath ) throws IOException {
-        if (parentPath.isRoot()) {
-            return new MatchAllDocsQuery();
-        }
-        Set<String> childIds = getIdsForChildrenOf(parentPath);
-        return findAllNodesWithIds(childIds);
+    public Query findChildNodes( Path parentPath ) {
+        // Find the path of the parent ...
+        String stringifiedPath = processor.pathAsString(parentPath);
+        // Append a '/' to the parent path, so we'll only get decendants ...
+        stringifiedPath = stringifiedPath + '/';
+
+        // Create a query to find all the nodes below the parent path ...
+        Query query = new PrefixQuery(new Term(ContentIndex.PATH, stringifiedPath));
+        // Include only the children ...
+        int childrenDepth = parentPath.size() + 1;
+        Query depthQuery = NumericRangeQuery.newIntRange(ContentIndex.DEPTH, childrenDepth, childrenDepth, true, true);
+        // And combine ...
+        BooleanQuery combinedQuery = new BooleanQuery();
+        combinedQuery.add(query, Occur.MUST);
+        combinedQuery.add(depthQuery, Occur.MUST);
+        return combinedQuery;
     }
 
     /**
-     * Create a query that can be used to find the one document (or node) that exists at the exact path supplied. This method
-     * first queries the {@link PathIndex path index} to find the ID of the node at the supplied path, and then returns a query
-     * that matches the ID.
+     * Create a query that can be used to find the one document (or node) that exists at the exact path supplied.
      * 
      * @param path the path of the node
      * @return the query; never null
-     * @throws IOException if there is an error finding the ID for the supplied path
      */
-    public Query findNodeAt( Path path ) throws IOException {
-        String id = getIdFor(path);
-        if (id == null) return null;
-        return new TermQuery(new Term(ContentIndex.ID, id));
+    public Query findNodeAt( Path path ) {
+        if (path.isRoot()) {
+            // Look for the root node ...
+            return NumericRangeQuery.newIntRange(ContentIndex.DEPTH, 0, 0, true, true);
+        }
+        String stringifiedPath = processor.pathAsString(path);
+        return new TermQuery(new Term(ContentIndex.PATH, stringifiedPath));
     }
 
     public Query findNodesLike( String fieldName,
@@ -1037,7 +838,7 @@
                                             Object upperValue,
                                             boolean includesLower,
                                             boolean includesUpper ) {
-        return findNodesWithNumericRange(PathIndex.DEPTH, lowerValue, upperValue, includesLower, includesUpper);
+        return findNodesWithNumericRange(ContentIndex.DEPTH, lowerValue, upperValue, includesLower, includesUpper);
     }
 
     protected Query findNodesWithNumericRange( String field,
@@ -1085,7 +886,7 @@
     public Query findNodesWith( NodePath nodePath,
                                 Operator operator,
                                 Object value,
-                                boolean caseSensitive ) throws IOException {
+                                boolean caseSensitive ) {
         if (!caseSensitive) value = processor.stringFactory.create(value).toLowerCase();
         Path pathValue = operator != Operator.LIKE ? processor.pathFactory.create(value) : null;
         Query query = null;
@@ -1096,44 +897,40 @@
                 return new NotQuery(findNodeAt(pathValue));
             case LIKE:
                 String likeExpression = processor.stringFactory.create(value);
-                query = findNodesLike(PathIndex.PATH, likeExpression, caseSensitive);
+                query = findNodesLike(ContentIndex.PATH, likeExpression, caseSensitive);
                 break;
             case GREATER_THAN:
                 query = ComparePathQuery.createQueryForNodesWithPathGreaterThan(pathValue,
-                                                                                PathIndex.PATH,
+                                                                                ContentIndex.PATH,
                                                                                 processor.valueFactories,
                                                                                 caseSensitive);
                 break;
             case GREATER_THAN_OR_EQUAL_TO:
                 query = ComparePathQuery.createQueryForNodesWithPathGreaterThanOrEqualTo(pathValue,
-                                                                                         PathIndex.PATH,
+                                                                                         ContentIndex.PATH,
                                                                                          processor.valueFactories,
                                                                                          caseSensitive);
                 break;
             case LESS_THAN:
                 query = ComparePathQuery.createQueryForNodesWithPathLessThan(pathValue,
-                                                                             PathIndex.PATH,
+                                                                             ContentIndex.PATH,
                                                                              processor.valueFactories,
                                                                              caseSensitive);
                 break;
             case LESS_THAN_OR_EQUAL_TO:
                 query = ComparePathQuery.createQueryForNodesWithPathLessThanOrEqualTo(pathValue,
-                                                                                      PathIndex.PATH,
+                                                                                      ContentIndex.PATH,
                                                                                       processor.valueFactories,
                                                                                       caseSensitive);
                 break;
         }
-        // Now execute and collect the IDs ...
-        IdCollector idCollector = new IdCollector();
-        IndexSearcher searcher = getPathsSearcher();
-        searcher.search(query, idCollector);
-        return findAllNodesWithIds(idCollector.getIds());
+        return query;
     }
 
     public Query findNodesWith( NodeName nodeName,
                                 Operator operator,
                                 Object value,
-                                boolean caseSensitive ) throws IOException {
+                                boolean caseSensitive ) {
         ValueFactories factories = processor.valueFactories;
         String stringValue = processor.stringFactory.create(value);
         if (!caseSensitive) stringValue = stringValue.toLowerCase();
@@ -1143,39 +940,41 @@
         switch (operator) {
             case EQUAL_TO:
                 BooleanQuery booleanQuery = new BooleanQuery();
-                booleanQuery.add(new TermQuery(new Term(PathIndex.NODE_NAME, stringValue)), Occur.MUST);
-                booleanQuery.add(NumericRangeQuery.newIntRange(PathIndex.SNS_INDEX, snsIndex, snsIndex, true, false), Occur.MUST);
+                booleanQuery.add(new TermQuery(new Term(ContentIndex.NODE_NAME, stringValue)), Occur.MUST);
+                booleanQuery.add(NumericRangeQuery.newIntRange(ContentIndex.SNS_INDEX, snsIndex, snsIndex, true, false),
+                                 Occur.MUST);
                 return booleanQuery;
             case NOT_EQUAL_TO:
                 booleanQuery = new BooleanQuery();
-                booleanQuery.add(new TermQuery(new Term(PathIndex.NODE_NAME, stringValue)), Occur.MUST);
-                booleanQuery.add(NumericRangeQuery.newIntRange(PathIndex.SNS_INDEX, snsIndex, snsIndex, true, false), Occur.MUST);
+                booleanQuery.add(new TermQuery(new Term(ContentIndex.NODE_NAME, stringValue)), Occur.MUST);
+                booleanQuery.add(NumericRangeQuery.newIntRange(ContentIndex.SNS_INDEX, snsIndex, snsIndex, true, false),
+                                 Occur.MUST);
                 return new NotQuery(booleanQuery);
             case GREATER_THAN:
                 query = CompareNameQuery.createQueryForNodesWithNameGreaterThan(segment,
-                                                                                PathIndex.NODE_NAME,
-                                                                                PathIndex.SNS_INDEX,
+                                                                                ContentIndex.NODE_NAME,
+                                                                                ContentIndex.SNS_INDEX,
                                                                                 factories,
                                                                                 caseSensitive);
                 break;
             case GREATER_THAN_OR_EQUAL_TO:
                 query = CompareNameQuery.createQueryForNodesWithNameGreaterThanOrEqualTo(segment,
-                                                                                         PathIndex.NODE_NAME,
-                                                                                         PathIndex.SNS_INDEX,
+                                                                                         ContentIndex.NODE_NAME,
+                                                                                         ContentIndex.SNS_INDEX,
                                                                                          factories,
                                                                                          caseSensitive);
                 break;
             case LESS_THAN:
                 query = CompareNameQuery.createQueryForNodesWithNameLessThan(segment,
-                                                                             PathIndex.NODE_NAME,
-                                                                             PathIndex.SNS_INDEX,
+                                                                             ContentIndex.NODE_NAME,
+                                                                             ContentIndex.SNS_INDEX,
                                                                              factories,
                                                                              caseSensitive);
                 break;
             case LESS_THAN_OR_EQUAL_TO:
                 query = CompareNameQuery.createQueryForNodesWithNameLessThanOrEqualTo(segment,
-                                                                                      PathIndex.NODE_NAME,
-                                                                                      PathIndex.SNS_INDEX,
+                                                                                      ContentIndex.NODE_NAME,
+                                                                                      ContentIndex.SNS_INDEX,
                                                                                       factories,
                                                                                       caseSensitive);
                 break;
@@ -1187,7 +986,7 @@
                     String localNameExpression = likeExpression.substring(0, openBracketIndex);
                     String snsIndexExpression = likeExpression.substring(openBracketIndex);
                     Query localNameQuery = CompareStringQuery.createQueryForNodesWithFieldLike(localNameExpression,
-                                                                                               PathIndex.NODE_NAME,
+                                                                                               ContentIndex.NODE_NAME,
                                                                                                factories,
                                                                                                caseSensitive);
                     Query snsQuery = createSnsIndexQuery(snsIndexExpression);
@@ -1213,120 +1012,98 @@
                 } else {
                     // There is no SNS expression ...
                     query = CompareStringQuery.createQueryForNodesWithFieldLike(likeExpression,
-                                                                                PathIndex.NODE_NAME,
+                                                                                ContentIndex.NODE_NAME,
                                                                                 factories,
                                                                                 caseSensitive);
                 }
                 assert query != null;
                 break;
         }
-
-        // Now execute and collect the IDs ...
-        IdCollector idCollector = new IdCollector();
-        IndexSearcher searcher = getPathsSearcher();
-        searcher.search(query, idCollector);
-        return findAllNodesWithIds(idCollector.getIds());
+        return query;
     }
 
     public Query findNodesWith( NodeLocalName nodeName,
                                 Operator operator,
                                 Object value,
-                                boolean caseSensitive ) throws IOException {
+                                boolean caseSensitive ) {
         String nameValue = processor.stringFactory.create(value);
         Query query = null;
         switch (operator) {
             case LIKE:
                 String likeExpression = processor.stringFactory.create(value);
-                query = findNodesLike(PathIndex.LOCAL_NAME, likeExpression, caseSensitive);
+                query = findNodesLike(ContentIndex.LOCAL_NAME, likeExpression, caseSensitive);
                 break;
             case EQUAL_TO:
                 query = CompareStringQuery.createQueryForNodesWithFieldEqualTo(nameValue,
-                                                                               PathIndex.LOCAL_NAME,
+                                                                               ContentIndex.LOCAL_NAME,
                                                                                processor.valueFactories,
                                                                                caseSensitive);
                 break;
             case NOT_EQUAL_TO:
                 query = CompareStringQuery.createQueryForNodesWithFieldEqualTo(nameValue,
-                                                                               PathIndex.LOCAL_NAME,
+                                                                               ContentIndex.LOCAL_NAME,
                                                                                processor.valueFactories,
                                                                                caseSensitive);
                 query = new NotQuery(query);
                 break;
             case GREATER_THAN:
                 query = CompareStringQuery.createQueryForNodesWithFieldGreaterThan(nameValue,
-                                                                                   PathIndex.LOCAL_NAME,
+                                                                                   ContentIndex.LOCAL_NAME,
                                                                                    processor.valueFactories,
                                                                                    caseSensitive);
                 break;
             case GREATER_THAN_OR_EQUAL_TO:
                 query = CompareStringQuery.createQueryForNodesWithFieldGreaterThanOrEqualTo(nameValue,
-                                                                                            PathIndex.LOCAL_NAME,
+                                                                                            ContentIndex.LOCAL_NAME,
                                                                                             processor.valueFactories,
                                                                                             caseSensitive);
                 break;
             case LESS_THAN:
                 query = CompareStringQuery.createQueryForNodesWithFieldLessThan(nameValue,
-                                                                                PathIndex.LOCAL_NAME,
+                                                                                ContentIndex.LOCAL_NAME,
                                                                                 processor.valueFactories,
                                                                                 caseSensitive);
                 break;
             case LESS_THAN_OR_EQUAL_TO:
                 query = CompareStringQuery.createQueryForNodesWithFieldLessThanOrEqualTo(nameValue,
-                                                                                         PathIndex.LOCAL_NAME,
+                                                                                         ContentIndex.LOCAL_NAME,
                                                                                          processor.valueFactories,
                                                                                          caseSensitive);
                 break;
         }
-
-        // Now execute and collect the IDs ...
-        IdCollector idCollector = new IdCollector();
-        IndexSearcher searcher = getPathsSearcher();
-        searcher.search(query, idCollector);
-        return findAllNodesWithIds(idCollector.getIds());
+        return query;
     }
 
     public Query findNodesWith( NodeDepth depthConstraint,
                                 Operator operator,
-                                Object value ) throws IOException {
+                                Object value ) {
         int depth = processor.valueFactories.getLongFactory().create(value).intValue();
-        Query query = null;
         switch (operator) {
             case EQUAL_TO:
-                query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, depth, depth, true, true);
-                break;
+                return NumericRangeQuery.newIntRange(ContentIndex.DEPTH, depth, depth, true, true);
             case NOT_EQUAL_TO:
-                query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, depth, depth, true, true);
-                query = new NotQuery(query);
-                break;
+                Query query = NumericRangeQuery.newIntRange(ContentIndex.DEPTH, depth, depth, true, true);
+                return new NotQuery(query);
             case GREATER_THAN:
-                query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, depth, MAX_DEPTH, false, true);
-                break;
+                return NumericRangeQuery.newIntRange(ContentIndex.DEPTH, depth, MAX_DEPTH, false, true);
             case GREATER_THAN_OR_EQUAL_TO:
-                query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, depth, MAX_DEPTH, true, true);
-                break;
+                return NumericRangeQuery.newIntRange(ContentIndex.DEPTH, depth, MAX_DEPTH, true, true);
             case LESS_THAN:
-                query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, MIN_DEPTH, depth, true, false);
-                break;
+                return NumericRangeQuery.newIntRange(ContentIndex.DEPTH, MIN_DEPTH, depth, true, false);
             case LESS_THAN_OR_EQUAL_TO:
-                query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, MIN_DEPTH, depth, true, true);
-                break;
+                return NumericRangeQuery.newIntRange(ContentIndex.DEPTH, MIN_DEPTH, depth, true, true);
             case LIKE:
                 // This is not allowed ...
                 return null;
         }
-
-        // Now execute and collect the IDs ...
-        IdCollector idCollector = new IdCollector();
-        IndexSearcher searcher = getPathsSearcher();
-        searcher.search(query, idCollector);
-        return findAllNodesWithIds(idCollector.getIds());
+        return null;
     }
 
     protected Query createLocalNameQuery( String likeExpression,
                                           boolean caseSensitive ) {
         if (likeExpression == null) return null;
         return CompareStringQuery.createQueryForNodesWithFieldLike(likeExpression,
-                                                                   PathIndex.LOCAL_NAME,
+                                                                   ContentIndex.LOCAL_NAME,
                                                                    processor.valueFactories,
                                                                    caseSensitive);
     }
@@ -1358,16 +1135,16 @@
         }
         if (likeExpression.equals("_")) {
             // The SNS expression can only be one digit ...
-            return NumericRangeQuery.newIntRange(PathIndex.SNS_INDEX, MIN_SNS_INDEX, 9, true, true);
+            return NumericRangeQuery.newIntRange(ContentIndex.SNS_INDEX, MIN_SNS_INDEX, 9, true, true);
         }
         if (likeExpression.equals("%")) {
             // The SNS expression can be any digits ...
-            return NumericRangeQuery.newIntRange(PathIndex.SNS_INDEX, MIN_SNS_INDEX, MAX_SNS_INDEX, true, true);
+            return NumericRangeQuery.newIntRange(ContentIndex.SNS_INDEX, MIN_SNS_INDEX, MAX_SNS_INDEX, true, true);
         }
         if (likeExpression.indexOf('_') != -1) {
             if (likeExpression.indexOf('%') != -1) {
                 // Contains both ...
-                return findNodesLike(PathIndex.SNS_INDEX, likeExpression, true);
+                return findNodesLike(ContentIndex.SNS_INDEX, likeExpression, true);
             }
             // It presumably contains some numbers and at least one '_' character ...
             int firstWildcardChar = likeExpression.indexOf('_');
@@ -1376,7 +1153,7 @@
                 int secondWildcardChar = likeExpression.indexOf('_', firstWildcardChar + 1);
                 if (secondWildcardChar != -1) {
                     // There are multiple '_' characters ...
-                    return findNodesLike(PathIndex.SNS_INDEX, likeExpression, true);
+                    return findNodesLike(ContentIndex.SNS_INDEX, likeExpression, true);
                 }
             }
             // There's only one '_', so parse the lowermost value and uppermost value ...
@@ -1386,7 +1163,7 @@
                 // This SNS is just a number ...
                 int lowerSns = Integer.parseInt(lowerExpression);
                 int upperSns = Integer.parseInt(upperExpression);
-                return NumericRangeQuery.newIntRange(PathIndex.SNS_INDEX, lowerSns, upperSns, true, true);
+                return NumericRangeQuery.newIntRange(ContentIndex.SNS_INDEX, lowerSns, upperSns, true, true);
             } catch (NumberFormatException e) {
                 // It's not a number but it's in the SNS field, so there will be no results ...
                 return new MatchNoneQuery();
@@ -1394,13 +1171,13 @@
         }
         if (likeExpression.indexOf('%') != -1) {
             // It presumably contains some numbers and at least one '%' character ...
-            return findNodesLike(PathIndex.SNS_INDEX, likeExpression, true);
+            return findNodesLike(ContentIndex.SNS_INDEX, likeExpression, true);
         }
         // This is not a LIKE expression but an exact value specification and should be a number ...
         try {
             // This SNS is just a number ...
             int sns = Integer.parseInt(likeExpression);
-            return NumericRangeQuery.newIntRange(PathIndex.SNS_INDEX, sns, sns, true, true);
+            return NumericRangeQuery.newIntRange(ContentIndex.SNS_INDEX, sns, sns, true, true);
         } catch (NumberFormatException e) {
             // It's not a number but it's in the SNS field, so there will be no results ...
             return new MatchNoneQuery();
@@ -1408,82 +1185,10 @@
     }
 
     /**
-     * A {@link Collector} implementation that only captures the UUID of the documents returned by a query. Score information is
-     * not recorded. This is often used when querying the {@link PathIndex} to collect the UUIDs of a set of nodes satisfying some
-     * path constraint.
-     * 
-     * @see LuceneSearchSession#findChildNodes(Path)
-     */
-    protected static class IdCollector extends Collector {
-        private final Set<String> ids = new HashSet<String>();
-        private String[] idsByDocId;
-
-        // private int baseDocId;
-
-        protected IdCollector() {
-        }
-
-        /**
-         * Get the UUIDs that have been collected.
-         * 
-         * @return the set of UUIDs; never null
-         */
-        public Set<String> getIds() {
-            return ids;
-        }
-
-        /**
-         * {@inheritDoc}
-         * 
-         * @see org.apache.lucene.search.Collector#acceptsDocsOutOfOrder()
-         */
-        @Override
-        public boolean acceptsDocsOutOfOrder() {
-            return true;
-        }
-
-        /**
-         * {@inheritDoc}
-         * 
-         * @see org.apache.lucene.search.Collector#setScorer(org.apache.lucene.search.Scorer)
-         */
-        @Override
-        public void setScorer( Scorer scorer ) {
-            // we don't care about scoring
-        }
-
-        /**
-         * {@inheritDoc}
-         * 
-         * @see org.apache.lucene.search.Collector#collect(int)
-         */
-        @Override
-        public void collect( int docId ) {
-            assert docId >= 0;
-            String idString = idsByDocId[docId];
-            assert idString != null;
-            ids.add(idString);
-        }
-
-        /**
-         * {@inheritDoc}
-         * 
-         * @see org.apache.lucene.search.Collector#setNextReader(org.apache.lucene.index.IndexReader, int)
-         */
-        @Override
-        public void setNextReader( IndexReader reader,
-                                   int docBase ) throws IOException {
-            this.idsByDocId = FieldCache.DEFAULT.getStrings(reader, ContentIndex.ID); // same value as PathIndex.ID
-            // this.baseDocId = docBase;
-        }
-    }
-
-    /**
      * This collector is responsible for loading the value for each of the columns into each tuple array.
      */
     protected static class DualIndexTupleCollector extends TupleCollector {
         private final LuceneSearchSession session;
-        private final LuceneSearchProcessor processor;
         private final LinkedList<Object[]> tuples = new LinkedList<Object[]>();
         private final Columns columns;
         private final int numValues;
@@ -1494,14 +1199,11 @@
         private Scorer scorer;
         private IndexReader currentReader;
         private int docOffset;
-        private boolean resolvedLocations = false;
 
         protected DualIndexTupleCollector( LuceneSearchSession session,
                                            Columns columns ) {
             this.session = session;
-            this.processor = session.processor;
             this.columns = columns;
-            assert this.processor != null;
             assert this.columns != null;
             this.numValues = this.columns.getTupleSize();
             assert this.numValues >= 0;
@@ -1510,13 +1212,16 @@
             this.locationIndex = this.columns.getLocationIndex(selectorName);
             this.recordScore = this.columns.hasFullTextSearchScores();
             this.scoreIndex = this.recordScore ? this.columns.getFullTextSearchScoreIndexFor(selectorName) : -1;
-            final Set<String> columnNames = new HashSet<String>(this.columns.getColumnNames());
-            columnNames.add(ContentIndex.ID); // add the UUID, which we'll put into the Location ...
+
+            // Create the set of field names that we need to load from the document ...
+            final Set<String> fieldNames = new HashSet<String>(this.columns.getColumnNames());
+            fieldNames.add(ContentIndex.LOCATION_ID_PROPERTIES); // add the UUID, which we'll put into the Location ...
+            fieldNames.add(ContentIndex.PATH); // add the UUID, which we'll put into the Location ...
             this.fieldSelector = new FieldSelector() {
                 private static final long serialVersionUID = 1L;
 
                 public FieldSelectorResult accept( String fieldName ) {
-                    return columnNames.contains(fieldName) ? FieldSelectorResult.LOAD : FieldSelectorResult.NO_LOAD;
+                    return fieldNames.contains(fieldName) ? FieldSelectorResult.LOAD : FieldSelectorResult.NO_LOAD;
                 }
             };
         }
@@ -1526,43 +1231,9 @@
          */
         @Override
         public LinkedList<Object[]> getTuples() {
-            resolveLocations();
             return tuples;
         }
 
-        protected void resolveLocations() {
-            if (resolvedLocations) return;
-            try {
-                // The Location field in the tuples all contain the ID of the document, so we need to replace these
-                // with the appropriate Location objects, using the content from the PathIndex ...
-                IndexReader pathReader = session.getPathsReader();
-                IndexSearcher pathSearcher = session.getPathsSearcher();
-                for (Object[] tuple : tuples) {
-                    String id = (String)tuple[locationIndex];
-                    assert id != null;
-                    Location location = getLocationForDocument(id, pathReader, pathSearcher);
-                    if (location == null) continue;
-                    tuple[locationIndex] = location;
-                }
-                resolvedLocations = true;
-            } catch (IOException e) {
-                throw new LuceneException(e);
-            }
-        }
-
-        protected Location getLocationForDocument( String id,
-                                                   IndexReader pathReader,
-                                                   IndexSearcher pathSearcher ) throws IOException {
-            // Find the path for this node (is there a better way to do this than one search per ID?) ...
-            TopDocs pathDocs = pathSearcher.search(new TermQuery(new Term(PathIndex.ID, id)), 1);
-            if (pathDocs.scoreDocs.length < 1) {
-                // No path record found ...
-                return null;
-            }
-            Document pathDoc = pathReader.document(pathDocs.scoreDocs[0].doc);
-            return session.readLocation(pathDoc);
-        }
-
         /**
          * {@inheritDoc}
          * 
@@ -1626,10 +1297,88 @@
                 tuple[scoreIndex] = scorer.score();
             }
 
-            // Load the document ID (which is a stringified UUID) into the Location slot,
-            // which will be replaced later with a real Location ...
-            tuple[locationIndex] = document.get(ContentIndex.ID);
+            // Read the location ...
+            tuple[locationIndex] = session.readLocation(document);
             tuples.add(tuple);
         }
     }
+
+    /**
+     * This collector is responsible for loading the value for each of the columns into each tuple array.
+     */
+    protected static class FullTextSearchTupleCollector extends TupleCollector {
+        private final List<Object[]> tuples;
+        private final FieldSelector fieldSelector;
+        private final LuceneSearchSession session;
+        private Scorer scorer;
+        private IndexReader currentReader;
+        private int docOffset;
+
+        protected FullTextSearchTupleCollector( LuceneSearchSession session,
+                                                List<Object[]> tuples ) {
+            assert session != null;
+            assert tuples != null;
+            this.session = session;
+            this.tuples = tuples;
+            this.fieldSelector = LOCATION_FIELDS_SELECTOR;
+        }
+
+        /**
+         * @return tuples
+         */
+        @Override
+        public List<Object[]> getTuples() {
+            return tuples;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.apache.lucene.search.Collector#acceptsDocsOutOfOrder()
+         */
+        @Override
+        public boolean acceptsDocsOutOfOrder() {
+            return true;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.apache.lucene.search.Collector#setNextReader(org.apache.lucene.index.IndexReader, int)
+         */
+        @Override
+        public void setNextReader( IndexReader reader,
+                                   int docBase ) {
+            this.currentReader = reader;
+            this.docOffset = docBase;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.apache.lucene.search.Collector#setScorer(org.apache.lucene.search.Scorer)
+         */
+        @Override
+        public void setScorer( Scorer scorer ) {
+            this.scorer = scorer;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.apache.lucene.search.Collector#collect(int)
+         */
+        @Override
+        public void collect( int doc ) throws IOException {
+            int docId = doc + docOffset;
+            Object[] tuple = new Object[2];
+            Document document = currentReader.document(docId, fieldSelector);
+            // Read the Location ...
+            tuple[0] = session.readLocation(document);
+            // And read the score ...
+            tuple[1] = scorer.score();
+            // And add the tuple ...
+            tuples.add(tuple);
+        }
+    }
 }

Modified: trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchWorkspace.java
===================================================================
--- trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchWorkspace.java	2009-12-31 16:13:57 UTC (rev 1506)
+++ trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchWorkspace.java	2009-12-31 18:53:06 UTC (rev 1507)
@@ -45,8 +45,7 @@
      */
     protected static final int CHANGES_BEFORE_OPTIMIZATION = 1;
 
-    protected static final String PATHS_INDEX_NAME = "paths";
-    protected static final String CONTENT_INDEX_NAME = "content";
+    protected static final String INDEX_NAME = "content";
 
     /**
      * Given the name of a property field of the form "&lt;namespace>:&lt;local>" (where &lt;namespace> can be zero-length), this
@@ -57,24 +56,16 @@
     protected static final String FULL_TEXT_PREFIX = ":ft:";
 
     /**
-     * This index stores only these fields, so we can use the most obvious names and not worry about clashes.
-     */
-    static class PathIndex {
-        public static final String PATH = "pth";
-        public static final String NODE_NAME = "nam";
-        public static final String LOCAL_NAME = "loc";
-        public static final String SNS_INDEX = "sns";
-        public static final String LOCATION_ID_PROPERTIES = "idp";
-        public static final String ID = ContentIndex.ID;
-        public static final String DEPTH = "dep";
-    }
-
-    /**
-     * This index stores these two fields <i>plus</i> all properties. Therefore, we have to worry about name clashes, which is why
+     * This index stores these fields <i>plus</i> all properties. Therefore, we have to worry about name clashes, which is why
      * these field names are prefixed with '::', which is something that does appear in property names as they are serialized.
      */
     static class ContentIndex {
-        public static final String ID = "::id";
+        public static final String PATH = "::pth";
+        public static final String NODE_NAME = "::nam";
+        public static final String LOCAL_NAME = "::loc";
+        public static final String SNS_INDEX = "::sns";
+        public static final String LOCATION_ID_PROPERTIES = "::idp";
+        public static final String DEPTH = "::dep";
         public static final String FULL_TEXT = "::fts";
     }
 
@@ -82,7 +73,6 @@
     private final String workspaceDirectoryName;
     protected final IndexRules rules;
     private final LuceneConfiguration configuration;
-    protected final Directory pathDirectory;
     protected final Directory contentDirectory;
     protected final Analyzer analyzer;
     private final Lock changesLock = new ReentrantLock();
@@ -99,8 +89,7 @@
         this.analyzer = analyzer != null ? analyzer : new StandardAnalyzer(Version.LUCENE_30);
         this.rules = rules != null ? rules : LuceneSearchEngine.DEFAULT_RULES;
         this.configuration = configuration;
-        this.pathDirectory = this.configuration.getDirectory(workspaceDirectoryName, PATHS_INDEX_NAME);
-        this.contentDirectory = this.configuration.getDirectory(workspaceDirectoryName, CONTENT_INDEX_NAME);
+        this.contentDirectory = this.configuration.getDirectory(workspaceDirectoryName, INDEX_NAME);
     }
 
     /**
@@ -118,8 +107,7 @@
      * @see org.jboss.dna.graph.search.SearchEngineWorkspace#destroy(org.jboss.dna.graph.ExecutionContext)
      */
     public void destroy( ExecutionContext context ) {
-        configuration.destroyDirectory(workspaceDirectoryName, PATHS_INDEX_NAME);
-        configuration.destroyDirectory(workspaceDirectoryName, CONTENT_INDEX_NAME);
+        configuration.destroyDirectory(workspaceDirectoryName, INDEX_NAME);
     }
 
     /**

Deleted: trunk/extensions/dna-search-lucene/src/test/java/org/jboss/dna/search/lucene/EncodingNamespaceRegistryTest.java
===================================================================
--- trunk/extensions/dna-search-lucene/src/test/java/org/jboss/dna/search/lucene/EncodingNamespaceRegistryTest.java	2009-12-31 16:13:57 UTC (rev 1506)
+++ trunk/extensions/dna-search-lucene/src/test/java/org/jboss/dna/search/lucene/EncodingNamespaceRegistryTest.java	2009-12-31 18:53:06 UTC (rev 1507)
@@ -1,102 +0,0 @@
-/*
- * JBoss DNA (http://www.jboss.org/dna)
- * See the COPYRIGHT.txt file distributed with this work for information
- * regarding copyright ownership.  Some portions may be licensed
- * to Red Hat, Inc. under one or more contributor license agreements.
- * See the AUTHORS.txt file in the distribution for a full listing of 
- * individual contributors.
- *
- * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- * is licensed to you under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- * 
- * JBoss DNA is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
- */
-package org.jboss.dna.search.lucene;
-
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertThat;
-import java.util.Collection;
-import org.jboss.dna.common.text.SecureHashTextEncoder;
-import org.jboss.dna.common.text.TextEncoder;
-import org.jboss.dna.common.util.SecureHash.Algorithm;
-import org.jboss.dna.graph.ExecutionContext;
-import org.jboss.dna.graph.property.NamespaceRegistry;
-import org.jboss.dna.graph.property.Path;
-import org.jboss.dna.graph.property.NamespaceRegistry.Namespace;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * 
- */
-public class EncodingNamespaceRegistryTest {
-
-    private ExecutionContext context;
-    private NamespaceRegistry registry;
-    private EncodingNamespaceRegistry encodedRegistry;
-    private TextEncoder encoder;
-    private ExecutionContext encodedContext;
-
-    @Before
-    public void beforeEach() {
-        this.context = new ExecutionContext();
-        this.registry = this.context.getNamespaceRegistry();
-        this.encoder = new SecureHashTextEncoder(Algorithm.SHA_1, 10);
-        this.encodedRegistry = new EncodingNamespaceRegistry(registry, encoder);
-        this.encodedContext = context.with(encodedRegistry);
-    }
-
-    @Test
-    public void shouldHaveEncodedPrefixesForAllRegisteredNamespacesExceptFixedOnes() {
-        Collection<Namespace> namespaces = registry.getNamespaces();
-        assertThat(namespaces.size() > 4, is(true));
-        for (Namespace namespace : namespaces) {
-            String uri = namespace.getNamespaceUri();
-            String actualEncodedPrefix = encodedRegistry.getPrefixForNamespaceUri(uri, false);
-            if (encodedRegistry.getFixedNamespaceUris().contains(uri)) {
-                assertThat(actualEncodedPrefix, is(namespace.getPrefix()));
-            } else {
-                String expectedEncodedPrefix = encoder.encode(uri);
-                assertThat(expectedEncodedPrefix, is(actualEncodedPrefix));
-            }
-            String actualUri = encodedRegistry.getNamespaceForPrefix(actualEncodedPrefix);
-            assertThat(uri, is(actualUri));
-        }
-    }
-
-    @Test
-    public void shouldAllowPathConversionToAndFromString() {
-        String uri1 = "http://acme.com/wabbler";
-        String uri2 = "http://troublemakers.com/contixity";
-        String uri3 = "http://example.com/infinitiy";
-        String ns1 = "wab";
-        String ns2 = "ctx";
-        String ns3 = "inf";
-        registry.register(ns1, uri1);
-        registry.register(ns2, uri2);
-        registry.register(ns3, uri3);
-        String pathStr = "/wab:part1/wab:part2/ctx:part3/inf:part4/dna:part5";
-        Path actualPath = context.getValueFactories().getPathFactory().create(pathStr);
-        String actualPathStr = context.getValueFactories().getStringFactory().create(actualPath);
-        assertThat(pathStr, is(actualPathStr));
-        String encodedPathStr = encodedContext.getValueFactories().getStringFactory().create(actualPath);
-        String encodedPrefix1 = encoder.encode(uri1);
-        String encodedPrefix2 = encoder.encode(uri2);
-        String encodedPrefix3 = encoder.encode(uri3);
-        String expectedPathStr = "/" + encodedPrefix1 + ":part1/" + encodedPrefix1 + ":part2/" + encodedPrefix2 + ":part3/"
-                                 + encodedPrefix3 + ":part4/dna:part5";
-        assertThat(expectedPathStr, is(encodedPathStr));
-        Path actualPath2 = encodedContext.getValueFactories().getPathFactory().create(encodedPathStr);
-        assertThat(actualPath, is(actualPath2));
-    }
-}



More information about the dna-commits mailing list