/*
* ModeShape (http://www.modeshape.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.modeshape.jcr.index.lucene;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
import javax.jcr.Node;
import javax.jcr.PropertyType;
import org.junit.Ignore;
import org.junit.Test;
import org.modeshape.common.FixFor;
import org.modeshape.common.util.FileUtil;
import org.modeshape.jcr.LocalIndexProviderTest;
import org.modeshape.jcr.api.JcrTools;
import org.modeshape.jcr.api.index.IndexDefinition;
import org.modeshape.jcr.api.index.InvalidIndexDefinitionException;
import org.modeshape.jcr.api.query.Query;
/**
* Unit test for {@link LuceneIndexProvider}. Since this is a local provider in term of repository locality, we want to run
* at least the same tests as for {@link org.modeshape.jcr.index.local.LocalIndexProvider}
*
* @author Horia Chiorean (hchiorea@redhat.com)
*/
public class LuceneIndexProviderTest extends LocalIndexProviderTest {
@Override
public void beforeEach() throws Exception {
super.beforeEach();
tools = new JcrTools();
}
@Override
protected InputStream repositoryConfiguration() {
return resource("config/repo-config-persistent-lucene-provider-no-indexes.json");
}
@Override
protected String providerName() {
return "lucene";
}
@Test
@FixFor( "MODE-2520" )
public void shouldUseMultiColumnIndex() throws Exception {
registerNodeType("nt:testType");
Map<String, Integer> properties = new HashMap<>();
properties.put("stringProp", PropertyType.STRING);
properties.put("longProp", PropertyType.LONG);
properties.put("jcr:name", PropertyType.NAME);
registerValueIndex("multiColIndex", "nt:testType", null, "*", properties);
Node root = session().getRootNode();
Node node1 = root.addNode("node1", "nt:testType");
node1.setProperty("stringProp", "string1");
Node node2 = root.addNode("node2", "nt:testType");
node2.setProperty("longProp", 1);
Node node3 = root.addNode("node3", "nt:testType");
node3.setProperty("stringProp", "string3");
node3.setProperty("longProp", 2);
session.save();
Query query = jcrSql2Query("SELECT * FROM [nt:testType] WHERE stringProp LIKE 'string%'");
validateQuery().rowCount(2L).hasNodesAtPaths("/node1", "/node3").useIndex("multiColIndex").validate(query, query.execute());
query = jcrSql2Query("SELECT * FROM [nt:testType] WHERE longProp >= 1");
validateQuery().rowCount(2L).hasNodesAtPaths("/node2", "/node3").useIndex("multiColIndex").validate(query, query.execute());
query = jcrSql2Query("SELECT * FROM [nt:testType] WHERE longProp > 1 AND stringProp LIKE 'string%' ");
validateQuery().rowCount(1L).hasNodesAtPaths("/node3").useIndex("multiColIndex").validate(query, query.execute());
query = jcrSql2Query("SELECT * FROM [nt:testType] WHERE NAME() LIKE 'node%' ");
validateQuery().rowCount(3L).hasNodesAtPaths("/node1", "/node2", "/node3").useIndex("multiColIndex").validate(query, query.execute());
}
@Test(expected = InvalidIndexDefinitionException.class)
public void shouldNotAllowMultiColumnTextIndex() throws Exception {
Map<String, Integer> properties = new HashMap<>();
properties.put("prop1", PropertyType.STRING);
properties.put("prop2", PropertyType.STRING);
registerIndex("multiColTextIndex", IndexDefinition.IndexKind.TEXT, providerName(), "nt:unstructured", null, "*", properties);
}
@Test
public void shouldUseIndexForFTSOnStringProperty() throws Exception {
registerNodeType("nt:testType");
registerTextIndex("textIndex", "nt:testType", null, "*", "FTSProp", PropertyType.STRING);
Node root = session().getRootNode();
Node node1 = root.addNode("node", "nt:testType");
String propertyText = "the quick Brown fox jumps over to the dog in at the gate";
node1.setProperty("FTSProp", propertyText);
session.save();
Query query = jcrSql2Query("select [jcr:path] from [nt:testType] as n where contains([nt:testType].*,'" + propertyText + "')");
validateQuery().rowCount(1L).hasNodesAtPaths("/node").useIndex("textIndex").validate(query, query.execute());
jcrSql2Query("select [jcr:path] from [nt:testType] as n where contains(FTSProp,'" + propertyText + "')");
validateQuery().rowCount(1L).hasNodesAtPaths("/node").useIndex("textIndex").validate(query, query.execute());
jcrSql2Query("select [jcr:path] from [nt:testType] as n where contains(FTSProp,'" + propertyText.toUpperCase() + "')");
validateQuery().rowCount(1L).hasNodesAtPaths("/node").useIndex("textIndex").validate(query, query.execute());
jcrSql2Query("select [jcr:path] from [nt:testType] as n where contains(FTSProp,'the quick Dog')");
validateQuery().rowCount(1L).hasNodesAtPaths("/node").useIndex("textIndex").validate(query, query.execute());
jcrSql2Query("select [jcr:path] from [nt:testType] as n where contains(n.*,'the quick Dog')");
validateQuery().rowCount(1L).hasNodesAtPaths("/node").useIndex("textIndex").validate(query, query.execute());
jcrSql2Query("select [jcr:path] from [nt:testType] as n where contains(FTSProp,'the quick jumps over gate')");
validateQuery().rowCount(1L).hasNodesAtPaths("/node").useIndex("textIndex").validate(query, query.execute());
jcrSql2Query("select [jcr:path] from [nt:testType] as n where contains(n.*,'the quick jumps over gate')");
validateQuery().rowCount(1L).hasNodesAtPaths("/node").useIndex("textIndex").validate(query, query.execute());
jcrSql2Query("select [jcr:path] from [nt:testType] as n where contains(FTSProp,'the gate')");
validateQuery().rowCount(1L).hasNodesAtPaths("/node").useIndex("textIndex").validate(query, query.execute());
jcrSql2Query("select [jcr:path] from [nt:testType] as n where contains(n.*,'the gate')");
validateQuery().rowCount(1L).hasNodesAtPaths("/node").useIndex("textIndex").validate(query, query.execute());
}
@Test
public void shouldUseIndexForFTSOnBinaryProperty() throws Exception {
registerTextIndex("textIndex", "nt:resource", null, "*", "jcr:data", PropertyType.BINARY);
tools.uploadFile(session, "/node", resourceStream("text-file.txt"));
session.save();
Query query = jcrSql2Query("select [jcr:path] from [nt:resource] as n where contains(n.*,'the quick jumps')");
validateQuery().rowCount(1L).hasNodesAtPaths("/node/jcr:content").useIndex("textIndex").validate(query, query.execute());
}
@FixFor("MODE-2565")
@Override
@Test
public void shouldNotReindexOnStartup() throws Exception {
super.shouldNotReindexOnStartup();
}
@Test
@FixFor("MODE-2683")
public void shouldUseIndexWithUpperAndLowerOperands() throws Exception {
registerValueIndex("descriptionIndex", "mix:title", "Index for the 'jcr:title' property on mix:title", "*", "jcr:title",
PropertyType.STRING);
// Add a node that uses this type ...
Node root = session().getRootNode();
Node book1 = root.addNode("myFirstBook");
book1.addMixin("mix:title");
book1.setProperty("jcr:title", "The Title");
Node book2 = root.addNode("mySecondBook");
book2.addMixin("mix:title");
book2.setProperty("jcr:title", "A Different Title");
Node book3 = root.addNode("myThirdBook");
book3.addMixin("mix:title");
book3.setProperty("jcr:title", "UPPERCASE TITLE");
session.save();
// Compute a query plan that will NOT use this index, since the selector doesn't match the index's node type.
// If we would use this index, the index doesn't know about non-mix:title nodes like the 'other' node ...
Query query = jcrSql2Query("SELECT * FROM [mix:title] WHERE UPPER([jcr:title]) LIKE '%TITLE%'");
validateQuery().rowCount(3L).useIndex("descriptionIndex").validate(query, query.execute());
query = jcrSql2Query("SELECT * FROM [mix:title] WHERE LOWER([jcr:title]) LIKE '%title%'");
validateQuery().rowCount(3L).useIndex("descriptionIndex").validate(query, query.execute());
query = jcrSql2Query("SELECT * FROM [mix:title] WHERE [jcr:title] LIKE '%title%'");
validateQuery().rowCount(0L).useIndex("descriptionIndex").validate(query, query.execute());
query = jcrSql2Query("SELECT * FROM [mix:title] WHERE [jcr:title] LIKE '%Title%'");
validateQuery().rowCount(2L).useIndex("descriptionIndex").validate(query, query.execute());
query = jcrSql2Query("SELECT * FROM [mix:title] WHERE [jcr:title] LIKE '%TITLE%'");
validateQuery().rowCount(1L).useIndex("descriptionIndex").validate(query, query.execute());
}
@Test
@Ignore("perf test")
public void testPerformanceOnStringFields() throws Exception {
registerNodeType("nt:testType");
registerValueIndex("stringIndex", "nt:testType", null, "*", "stringProp",
PropertyType.STRING);
Node rootNode = session.getRootNode();
Node containerNode = rootNode.addNode("container", "nt:unstructured");
int numNodes = 1000;
long start = System.currentTimeMillis();
for (int i = 0; i < numNodes; i++) {
Node node = containerNode.addNode("node-" + i, "nt:testType");
node.setProperty("id", i);
node.setProperty("stringProp", "str" + i);
}
session.save();
System.out.printf("Inserted %d in %d ms.\n", numNodes, System.currentTimeMillis() - start);
Random random = new Random();
start = System.currentTimeMillis();
int numSearches = 1000;
for (int i = 0; i < numSearches; i++) {
int findId = random.nextInt(numNodes);
String findStr = "str" + findId;
String queryStr = "select test.[id] from [nt:testType] as test where test.stringProp = '" + findStr + "'";
Query query = jcrSql2Query(session, queryStr);
validateQuery().useIndex("stringIndex").hasNodesAtPaths("/container/node-" + findId).validate(query, query.execute());
}
System.out.printf("Searched %d nodes %d times in %d ms.\n", numNodes, numSearches, System.currentTimeMillis() - start);
containerNode.remove();
session.save();
}
@Override
protected void assertStorageLocationUnchangedAfterRestart() throws Exception {
// register the total size and last modified timestamp of the place where indexes are stored for the default provider..
File indexesDir1 = new File("target/persistent_repository/indexes/lucene_primary/default/ref1");
assertTrue(indexesDir1.exists() && indexesDir1.isDirectory() && indexesDir1.canRead());
long sizeDir1 = FileUtil.size(indexesDir1.getPath());
final AtomicLong lastModifiedDateDir1 = lastModifiedFileTime(indexesDir1, "_0.*");
File indexesDir2 = new File("target/persistent_repository/indexes/lucene_primary/default/ref2");
assertTrue(indexesDir2.exists() && indexesDir2.isDirectory() && indexesDir2.canRead());
long sizeDir2 = FileUtil.size(indexesDir1.getPath());
final AtomicLong lastModifiedDateDir2 = lastModifiedFileTime(indexesDir2, "_0.*");
startRepository();
printMessage("Repository restart complete");
// and now check that the storage folder is unchanged
assertEquals(sizeDir1, FileUtil.size(indexesDir1.getPath()));
assertEquals(lastModifiedDateDir1.get(), lastModifiedFileTime(indexesDir1, "_0.*").get());
assertEquals(sizeDir2, FileUtil.size(indexesDir2.getPath()));
assertEquals(lastModifiedDateDir2.get(), lastModifiedFileTime(indexesDir2, "_0.*").get());
}
}