Skip to content

Commit

Permalink
adds support for range query on Lucene module
Browse files Browse the repository at this point in the history
    refs #6534
  • Loading branch information
robfrank committed Dec 15, 2016
1 parent b2591ae commit 83e4d30
Show file tree
Hide file tree
Showing 7 changed files with 410 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@
package com.orientechnologies.lucene.builder;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.lucene.parser.OLuceneMultiFieldQueryParser;
import com.orientechnologies.orient.core.index.OCompositeKey;
import com.orientechnologies.orient.core.index.OIndexDefinition;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.parser.ParseException;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.Query;

import java.util.HashMap;
import java.util.Map;

/**
Expand Down Expand Up @@ -82,11 +83,7 @@ public Query query(OIndexDefinition index, Object key, Analyzer analyzer) throws
}

protected Query getQueryParser(OIndexDefinition index, String key, Analyzer analyzer) throws ParseException {
QueryParser queryParser;
if ((key).startsWith("(")) {
queryParser = new QueryParser("", analyzer);

} else {
String[] fields;
if (index.isAutomatic()) {
fields = index.getFields().toArray(new String[index.getFields().size()]);
Expand All @@ -99,9 +96,17 @@ protected Query getQueryParser(OIndexDefinition index, String key, Analyzer anal
}
}

queryParser = new MultiFieldQueryParser(fields, analyzer);
Map<String, OType> types = new HashMap<String, OType>();
for (int i = 0; i < fields.length; i++) {
String field = fields[i];
types.put(field, index.getTypes()[i]);

}

// for (Map.Entry<String, OType> typeEntry : types.entrySet()) {
// System.out.println("typeEntry = " + typeEntry);
// }
final OLuceneMultiFieldQueryParser queryParser = new OLuceneMultiFieldQueryParser(types, fields, analyzer);
queryParser.setAllowLeadingWildcard(allowLeadingWildcard);

queryParser.setLowercaseExpandedTerms(lowercaseExpandedTerms);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@
import com.orientechnologies.orient.core.sql.operator.OIndexReuseType;
import com.orientechnologies.orient.core.sql.operator.OQueryTargetOperator;
import com.orientechnologies.orient.core.sql.parser.ParseException;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.memory.MemoryIndex;
import org.apache.lucene.search.Query;

import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -52,6 +50,8 @@

public class OLuceneTextOperator extends OQueryTargetOperator {

public static final String MEMORY_INDEX = "_memoryIndex";

public OLuceneTextOperator() {
this("LUCENE", 5, false);
}
Expand All @@ -60,8 +60,17 @@ public OLuceneTextOperator(String iKeyword, int iPrecedence, boolean iLogical) {
super(iKeyword, iPrecedence, iLogical);
}

protected static ODatabaseDocumentInternal getDatabase() {
return ODatabaseRecordThreadLocal.INSTANCE.get();
@Override
public OIndexReuseType getIndexReuseType(Object iLeft, Object iRight) {
return OIndexReuseType.INDEX_OPERATOR;
}

@Override
public OIndexSearchResult getOIndexSearchResult(OClass iSchemaClass, OSQLFilterCondition iCondition,
List<OIndexSearchResult> iIndexSearchResults, OCommandContext context) {

//FIXME questo non trova l'indice se l'ordine e' errato
return OLuceneOperatorUtil.buildOIndexSearchResult(iSchemaClass, iCondition, iIndexSearchResults, context);
}

@Override
Expand All @@ -75,11 +84,6 @@ public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex<?> index,
return cursor;
}

@Override
public OIndexReuseType getIndexReuseType(Object iLeft, Object iRight) {
return OIndexReuseType.INDEX_OPERATOR;
}

@Override
public ORID getBeginRidRange(Object iLeft, Object iRight) {
return null;
Expand All @@ -91,11 +95,8 @@ public ORID getEndRidRange(Object iLeft, Object iRight) {
}

@Override
public OIndexSearchResult getOIndexSearchResult(OClass iSchemaClass, OSQLFilterCondition iCondition,
List<OIndexSearchResult> iIndexSearchResults, OCommandContext context) {

//FIXME questo non trova l'indice se l'ordine e' errato
return OLuceneOperatorUtil.buildOIndexSearchResult(iSchemaClass, iCondition, iIndexSearchResults, context);
public boolean canBeMerged() {
return false;
}

@Override
Expand All @@ -114,25 +115,24 @@ public Object evaluateRecord(OIdentifiable iRecord, ODocument iCurrentResult, OS
throw new OCommandExecutionException("Cannot evaluate lucene condition without index configuration.");
}

MemoryIndex memoryIndex = (MemoryIndex) iContext.getVariable("_memoryIndex");
MemoryIndex memoryIndex = (MemoryIndex) iContext.getVariable(MEMORY_INDEX);
if (memoryIndex == null) {
memoryIndex = new MemoryIndex();
iContext.setVariable("_memoryIndex", memoryIndex);
iContext.setVariable(MEMORY_INDEX, memoryIndex);
}
memoryIndex.reset();

final Document doc = index.buildDocument(iLeft);
for (IndexableField field : doc.getFields()) {
memoryIndex.addField(field.name(), field.stringValue(), index.indexAnalyzer());
}

Query query = null;
try {
query = index.buildQuery(iRight);
for (IndexableField field : index.buildDocument(iLeft).getFields()) {
memoryIndex.addField(field.name(), field.tokenStream(index.indexAnalyzer(), null));
}
return memoryIndex.search(index.buildQuery(iRight)) > 0.0f;

} catch (ParseException e) {
OLogManager.instance().error(this, "error occurred while building query", e);

}
return memoryIndex.search(query) > 0.0f;
return null;
}

protected OLuceneFullTextIndex involvedIndex(OIdentifiable iRecord, ODocument iCurrentResult, OSQLFilterCondition iCondition,
Expand Down Expand Up @@ -166,6 +166,10 @@ protected OLuceneFullTextIndex involvedIndex(OIdentifiable iRecord, ODocument iC
return idx;
}

protected static ODatabaseDocumentInternal getDatabase() {
return ODatabaseRecordThreadLocal.INSTANCE.get();
}

private boolean isChained(Object left) {
if (left instanceof OSQLFilterItemField) {
OSQLFilterItemField field = (OSQLFilterItemField) left;
Expand Down Expand Up @@ -204,9 +208,4 @@ protected Collection<String> fields(OSQLFilterCondition iCondition) {
}
return Collections.emptyList();
}

@Override
public boolean canBeMerged() {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.orientechnologies.lucene.parser;

import com.orientechnologies.orient.core.metadata.schema.OType;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.DateTools;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.search.LegacyNumericRangeQuery;
import org.apache.lucene.search.Query;

import java.util.Map;

/**
* Created by frank on 13/12/2016.
*/
public class OLuceneMultiFieldQueryParser extends MultiFieldQueryParser {
private final Map<String, OType> types;

public OLuceneMultiFieldQueryParser(Map<String, OType> types, String[] fields,
Analyzer analyzer,
Map<String, Float> boosts) {
super(fields, analyzer, boosts);
this.types = types;
}

public OLuceneMultiFieldQueryParser(Map<String, OType> types, String[] fields, Analyzer analyzer) {
super(fields, analyzer);
this.types = types;
}

@Override
protected Query getFieldQuery(String field, String queryText, int slop) throws ParseException {
Query query = getQuery(field, queryText, queryText, true, true);
if (query != null)
return query;

return super.getFieldQuery(field, queryText, slop);
}

@Override
protected Query getFieldQuery(String field, String queryText, boolean quoted) throws ParseException {

Query query = getQuery(field, queryText, queryText, true, true);
if (query != null)
return query;

return super.getFieldQuery(field, queryText, quoted);
}

@Override
protected Query getRangeQuery(String field, String part1, String part2, boolean startInclusive, boolean endInclusive)
throws ParseException {

Query query = getQuery(field, part1, part2, startInclusive, endInclusive);
if (query != null)
return query;

return super.getRangeQuery(field, part1, part2, startInclusive, endInclusive);
}

private Query getQuery(String field, String part1, String part2, boolean startInclusive, boolean endInclusive)
throws ParseException {

if (types.containsKey(field)) {
switch (types.get(field)) {
case LONG:
return LegacyNumericRangeQuery.newLongRange(field, Long.parseLong(part1), Long.parseLong(part2),
startInclusive, endInclusive);
case INTEGER:
return LegacyNumericRangeQuery.newIntRange(field, Integer.parseInt(part1), Integer.parseInt(part2),
startInclusive, endInclusive);
case DOUBLE:
return LegacyNumericRangeQuery.newDoubleRange(field, Double.parseDouble(part1), Double.parseDouble(part2),
startInclusive, endInclusive);
case DATE:
case DATETIME:
try {
return LegacyNumericRangeQuery.newLongRange(field, DateTools.stringToTime(part1), DateTools.stringToTime(part2),
startInclusive, endInclusive);
} catch (java.text.ParseException e) {
throw new ParseException(e.getMessage());
}
}
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.orientechnologies.lucene.test;

import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OSchema;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.OCommandSQL;
import org.apache.lucene.document.DateTools;
import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import static org.assertj.core.api.Assertions.*;

/**
* Created by frank on 13/12/2016.
*/
public class LuceneRangeTest extends BaseLuceneTest {

@Before
public void setUp() throws Exception {
OSchema schema = db.getMetadata().getSchema();

OClass cls = schema.createClass("Person");
cls.createProperty("name", OType.STRING);
cls.createProperty("surname", OType.STRING);
cls.createProperty("date", OType.DATETIME);
cls.createProperty("age", OType.INTEGER);

List<String> names = Arrays.asList("John", "Robert", "Jane", "andrew", "Scott", "luke", "Enriquez", "Luis", "Gabriel", "Sara");
for (int i = 0; i < 10; i++) {
db.save(new ODocument("Person")
.field("name", names.get(i))
.field("surname", "Reese")
//from today back one day a time
.field("date", System.currentTimeMillis() - (i * 3600 * 24 * 1000))
.field("age", i));
}

}

@Test
public void shouldUseRangeQueryOnSingleIntegerField() throws Exception {

db.command(new OCommandSQL("create index Person.age on Person(age) FULLTEXT ENGINE LUCENE")).execute();

assertThat(db.getMetadata().getIndexManager().getIndex("Person.age").getSize()).isEqualTo(10);

//range
Collection<ODocument> results = db.command(
new OCommandSQL("SELECT FROM Person WHERE age LUCENE 'age:[5 TO 6]'"))
.execute();

assertThat(results).hasSize(2);

//single value
results = db.command(
new OCommandSQL("SELECT FROM Person WHERE age LUCENE 'age:5'"))
.execute();

assertThat(results).hasSize(1);
}

@Test
public void shouldUseRangeQueryOnSingleDateField() throws Exception {

db.commit();
db.command(new OCommandSQL("create index Person.date on Person(date) FULLTEXT ENGINE LUCENE")).execute();
db.commit();

assertThat(db.getMetadata().getIndexManager().getIndex("Person.date").getSize()).isEqualTo(10);

String today = DateTools.timeToString(System.currentTimeMillis(), DateTools.Resolution.MINUTE);
String fiveDaysAgo = DateTools.timeToString(System.currentTimeMillis() - (5 * 3600 * 24 * 1000), DateTools.Resolution.MINUTE);

//range
Collection<ODocument> results = db.command(
new OCommandSQL("SELECT FROM Person WHERE date LUCENE 'date:[" + fiveDaysAgo + " TO " + today + "]'"))
.execute();

assertThat(results).hasSize(5);

}

@Test
public void shouldUseRangeQueryMultipleField() throws Exception {

db.command(new OCommandSQL("create index Person.composite on Person(name,surname,date,age) FULLTEXT ENGINE LUCENE")).execute();

assertThat(db.getMetadata().getIndexManager().getIndex("Person.composite").getSize()).isEqualTo(10);

db.commit();

String today = DateTools.timeToString(System.currentTimeMillis(), DateTools.Resolution.MINUTE);
String fiveDaysAgo = DateTools.timeToString(System.currentTimeMillis() - (5 * 3600 * 24 * 1000), DateTools.Resolution.MINUTE);

//anme and age range
Collection<ODocument> results = db.command(
new OCommandSQL("SELECT * FROM Person WHERE [name,surname,date,age] LUCENE 'name:luke age:[5 TO 6]'"))
.execute();

assertThat(results).hasSize(2);

//date range
results = db.command(
new OCommandSQL("SELECT FROM Person WHERE [name,surname,date,age] LUCENE 'date:[" + fiveDaysAgo + " TO " + today + "]'"))
.execute();

assertThat(results).hasSize(5);

//age and date range with MUST
results = db.command(
new OCommandSQL(
"SELECT FROM Person WHERE [name,surname,date,age] LUCENE '+age:[4 TO 7] +date:[" + fiveDaysAgo + " TO " + today
+ "]'"))
.execute();

assertThat(results).hasSize(2);

}

}
Loading

0 comments on commit 83e4d30

Please sign in to comment.