-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: crazyyanchao <[email protected]>
- Loading branch information
1 parent
586eacd
commit ee7132f
Showing
3 changed files
with
599 additions
and
0 deletions.
There are no files selected for viewing
150 changes: 150 additions & 0 deletions
150
src/main/java/casia/isiteam/zdr/neo4j/index/CustomizeFullTextSearcher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package casia.isiteam.zdr.neo4j.index; | ||
/** | ||
* ┏┓ ┏┓+ + | ||
* ┏┛┻━━━━━━━┛┻┓ + + | ||
* ┃ ┃ | ||
* ┃ ━ ┃ ++ + + + | ||
* █████━█████ ┃+ | ||
* ┃ ┃ + | ||
* ┃ ┻ ┃ | ||
* ┃ ┃ + + | ||
* ┗━━┓ ┏━┛ | ||
* ┃ ┃ | ||
* ┃ ┃ + + + + | ||
* ┃ ┃ Code is far away from bug with the animal protecting | ||
* ┃ ┃ + | ||
* ┃ ┃ | ||
* ┃ ┃ + | ||
* ┃ ┗━━━┓ + + | ||
* ┃ ┣┓ | ||
* ┃ ┏┛ | ||
* ┗┓┓┏━━━┳┓┏┛ + + + + | ||
* ┃┫┫ ┃┫┫ | ||
* ┗┻┛ ┗┻┛+ + + + | ||
*/ | ||
|
||
import org.apache.lucene.queryparser.classic.ParseException; | ||
import org.neo4j.graphdb.DependencyResolver; | ||
import org.neo4j.graphdb.GraphDatabaseService; | ||
import org.neo4j.graphdb.Node; | ||
import org.neo4j.graphdb.NotFoundException; | ||
import org.neo4j.graphdb.schema.IndexDefinition; | ||
import org.neo4j.graphdb.schema.Schema; | ||
import org.neo4j.internal.kernel.api.IndexReference; | ||
import org.neo4j.internal.kernel.api.exceptions.schema.IndexNotFoundKernelException; | ||
import org.neo4j.kernel.api.KernelTransaction; | ||
import org.neo4j.kernel.api.impl.fulltext.FulltextAdapter; | ||
import org.neo4j.kernel.api.impl.fulltext.FulltextProcedures; | ||
import org.neo4j.kernel.api.impl.fulltext.ScoreEntityIterator; | ||
import org.neo4j.kernel.impl.api.KernelTransactionImplementation; | ||
import org.neo4j.procedure.Context; | ||
import org.neo4j.procedure.Description; | ||
import org.neo4j.procedure.Name; | ||
import org.neo4j.procedure.Procedure; | ||
import org.neo4j.storageengine.api.EntityType; | ||
import org.neo4j.storageengine.api.schema.IndexDescriptor; | ||
import org.neo4j.util.FeatureToggles; | ||
|
||
import java.io.IOException; | ||
import java.util.Objects; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.stream.Stream; | ||
|
||
import static org.neo4j.procedure.Mode.READ; | ||
|
||
/** | ||
* @author YanchaoMa [email protected] | ||
* @PACKAGE_NAME: casia.isiteam.zdr.neo4j.index | ||
* @Description: TODO(自定义全文检索得分算法) | ||
* @date 2019/8/16 14:30 | ||
*/ | ||
public class CustomizeFullTextSearcher { | ||
|
||
private static final long INDEX_ONLINE_QUERY_TIMEOUT_SECONDS = FeatureToggles.getInteger( | ||
FulltextProcedures.class, "INDEX_ONLINE_QUERY_TIMEOUT_SECONDS", 30); | ||
|
||
@Context | ||
public KernelTransaction tx; | ||
|
||
@Context | ||
public GraphDatabaseService db; | ||
|
||
@Context | ||
public DependencyResolver resolver; | ||
|
||
@Context | ||
public FulltextAdapter accessor; | ||
|
||
/** | ||
* @param name:索引名 | ||
* @param query:查询STRING | ||
* @return | ||
* @Description: TODO(使用SimHash计算得分) | ||
*/ | ||
@Description("Query the given fulltext index. Returns the matching nodes and their lucene query score, ordered by score.") | ||
@Procedure(name = "db.index.fulltext.queryNodesBySimHash", mode = READ) | ||
public Stream<NodeOutput> queryFulltextForNodesBySimHash(@Name("indexName") String name, @Name("queryString") String query) | ||
throws ParseException, IndexNotFoundKernelException, IOException { | ||
IndexReference indexReference = getValidIndexReference(name); | ||
awaitOnline(indexReference); | ||
EntityType entityType = indexReference.schema().entityType(); | ||
if (entityType != EntityType.NODE) { | ||
throw new IllegalArgumentException("The '" + name + "' index (" + indexReference + ") is an index on " + entityType + | ||
", so it cannot be queried for nodes."); | ||
} | ||
ScoreEntityIterator resultIterator = accessor.query(tx, name, query); | ||
// return resultIterator.stream() | ||
// .map(result -> NodeOutput.forExistingEntityOrNull(db, result)) | ||
// .filter(Objects::nonNull); | ||
return null; | ||
} | ||
|
||
private IndexReference getValidIndexReference(@Name("indexName") String name) { | ||
IndexReference indexReference = tx.schemaRead().indexGetForName(name); | ||
if (indexReference == IndexReference.NO_INDEX) { | ||
throw new IllegalArgumentException("There is no such fulltext schema index: " + name); | ||
} | ||
return indexReference; | ||
} | ||
|
||
private void awaitOnline(IndexReference indexReference) throws IndexNotFoundKernelException { | ||
// We do the isAdded check on the transaction state first, because indexGetState will grab a schema read-lock, which can deadlock on the write-lock | ||
// held by the index populator. Also, if we index was created in this transaction, then we will never see it come online in this transaction anyway. | ||
// Indexes don't come online until the transaction that creates them has committed. | ||
if (!((KernelTransactionImplementation) tx).txState().indexDiffSetsBySchema(indexReference.schema()).isAdded((IndexDescriptor) indexReference)) { | ||
// If the index was not created in this transaction, then wait for it to come online before querying. | ||
Schema schema = db.schema(); | ||
IndexDefinition index = schema.getIndexByName(indexReference.name()); | ||
schema.awaitIndexOnline(index, INDEX_ONLINE_QUERY_TIMEOUT_SECONDS, TimeUnit.SECONDS); | ||
} | ||
// If the index was created in this transaction, then we skip this check entirely. | ||
// We will get an exception later, when we try to get an IndexReader, so this is fine. | ||
} | ||
|
||
public static final class NodeOutput | ||
{ | ||
public final Node node; | ||
public final double score; | ||
|
||
protected NodeOutput( Node node, double score ) | ||
{ | ||
this.node = node; | ||
this.score = score; | ||
} | ||
|
||
// public static FulltextProcedures.NodeOutput forExistingEntityOrNull(GraphDatabaseService db, ScoreEntityIterator.ScoreEntry result ) | ||
// { | ||
// try | ||
// { | ||
// return new FulltextProcedures.NodeOutput( db.getNodeById( result.entityId() ), result.score() ); | ||
// } | ||
// catch ( NotFoundException ignore ) | ||
// { | ||
// // This node was most likely deleted by a concurrent transaction, so we just ignore it. | ||
// return null; | ||
// } | ||
// } | ||
} | ||
|
||
} | ||
|
210 changes: 210 additions & 0 deletions
210
src/main/java/casia/isiteam/zdr/neo4j/index/CustomizeScoreEntityIterator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
package casia.isiteam.zdr.neo4j.index; | ||
/** | ||
* ┏┓ ┏┓+ + | ||
* ┏┛┻━━━━━━━┛┻┓ + + | ||
* ┃ ┃ | ||
* ┃ ━ ┃ ++ + + + | ||
* █████━█████ ┃+ | ||
* ┃ ┃ + | ||
* ┃ ┻ ┃ | ||
* ┃ ┃ + + | ||
* ┗━━┓ ┏━┛ | ||
* ┃ ┃ | ||
* ┃ ┃ + + + + | ||
* ┃ ┃ Code is far away from bug with the animal protecting | ||
* ┃ ┃ + | ||
* ┃ ┃ | ||
* ┃ ┃ + | ||
* ┃ ┗━━━┓ + + | ||
* ┃ ┣┓ | ||
* ┃ ┏┛ | ||
* ┗┓┓┏━━━┳┓┏┛ + + + + | ||
* ┃┫┫ ┃┫┫ | ||
* ┗┻┛ ┗┻┛+ + + + | ||
*/ | ||
|
||
import org.neo4j.kernel.api.impl.index.collector.ValuesIterator; | ||
|
||
import java.util.*; | ||
import java.util.function.Predicate; | ||
import java.util.stream.Stream; | ||
import java.util.stream.StreamSupport; | ||
|
||
/** | ||
* @author YanchaoMa [email protected] | ||
* @PACKAGE_NAME: casia.isiteam.zdr.neo4j.index | ||
* @Description: TODO(Iterator over entity ids together with their respective score.) | ||
* @date 2019/8/16 16:41 | ||
*/ | ||
public class CustomizeScoreEntityIterator implements Iterator<CustomizeScoreEntityIterator.ScoreEntry> { | ||
private final ValuesIterator iterator; | ||
private final Predicate<ScoreEntry> predicate; | ||
private CustomizeScoreEntityIterator.ScoreEntry next; | ||
|
||
CustomizeScoreEntityIterator( ValuesIterator sortedValuesIterator ) | ||
{ | ||
this.iterator = sortedValuesIterator; | ||
this.predicate = null; | ||
} | ||
|
||
private CustomizeScoreEntityIterator( ValuesIterator sortedValuesIterator, Predicate<CustomizeScoreEntityIterator.ScoreEntry> predicate ) | ||
{ | ||
this.iterator = sortedValuesIterator; | ||
this.predicate = predicate; | ||
} | ||
|
||
public Stream<ScoreEntry> stream() | ||
{ | ||
return StreamSupport.stream( Spliterators.spliteratorUnknownSize( this, Spliterator.ORDERED ), false ); | ||
} | ||
|
||
@Override | ||
public boolean hasNext() | ||
{ | ||
while ( next == null && iterator.hasNext() ) | ||
{ | ||
long entityId = iterator.next(); | ||
float score = iterator.currentScore(); | ||
CustomizeScoreEntityIterator.ScoreEntry tmp = new CustomizeScoreEntityIterator.ScoreEntry( entityId, score ); | ||
if ( predicate == null || predicate.test( tmp ) ) | ||
{ | ||
next = tmp; | ||
} | ||
} | ||
return next != null; | ||
} | ||
|
||
@Override | ||
public CustomizeScoreEntityIterator.ScoreEntry next() | ||
{ | ||
if ( hasNext() ) | ||
{ | ||
CustomizeScoreEntityIterator.ScoreEntry tmp = next; | ||
next = null; | ||
return tmp; | ||
} | ||
else | ||
{ | ||
throw new NoSuchElementException( "The iterator is exhausted" ); | ||
} | ||
} | ||
|
||
CustomizeScoreEntityIterator filter( Predicate<CustomizeScoreEntityIterator.ScoreEntry> predicate ) | ||
{ | ||
if ( this.predicate != null ) | ||
{ | ||
predicate = this.predicate.and( predicate ); | ||
} | ||
return new CustomizeScoreEntityIterator( iterator, predicate ); | ||
} | ||
|
||
/** | ||
* Merges the given iterators into a single iterator, that maintains the aggregate descending score sort order. | ||
* | ||
* @param iterators to concatenate | ||
* @return a {@link CustomizeScoreEntityIterator} that iterates over all of the elements in all of the given iterators | ||
*/ | ||
static CustomizeScoreEntityIterator mergeIterators( List<CustomizeScoreEntityIterator> iterators ) | ||
{ | ||
return new CustomizeScoreEntityIterator.ConcatenatingScoreEntityIterator( iterators ); | ||
} | ||
|
||
private static class ConcatenatingScoreEntityIterator extends CustomizeScoreEntityIterator | ||
{ | ||
private final List<? extends CustomizeScoreEntityIterator> iterators; | ||
private final CustomizeScoreEntityIterator.ScoreEntry[] buffer; | ||
private boolean fetched; | ||
private CustomizeScoreEntityIterator.ScoreEntry nextHead; | ||
|
||
ConcatenatingScoreEntityIterator( List<? extends CustomizeScoreEntityIterator> iterators ) | ||
{ | ||
super( null ); | ||
this.iterators = iterators; | ||
this.buffer = new CustomizeScoreEntityIterator.ScoreEntry[iterators.size()]; | ||
} | ||
|
||
@Override | ||
public boolean hasNext() | ||
{ | ||
if ( !fetched ) | ||
{ | ||
fetch(); | ||
} | ||
return nextHead != null; | ||
} | ||
|
||
private void fetch() | ||
{ | ||
int candidateHead = -1; | ||
for ( int i = 0; i < iterators.size(); i++ ) | ||
{ | ||
CustomizeScoreEntityIterator.ScoreEntry entry = buffer[i]; | ||
//Fill buffer if needed. | ||
if ( entry == null && iterators.get( i ).hasNext() ) | ||
{ | ||
entry = iterators.get( i ).next(); | ||
buffer[i] = entry; | ||
} | ||
|
||
//Check if entry might be candidate for next to return. | ||
if ( entry != null && (nextHead == null || entry.score > nextHead.score) ) | ||
{ | ||
nextHead = entry; | ||
candidateHead = i; | ||
} | ||
} | ||
if ( candidateHead != -1 ) | ||
{ | ||
buffer[candidateHead] = null; | ||
} | ||
fetched = true; | ||
} | ||
|
||
@Override | ||
public CustomizeScoreEntityIterator.ScoreEntry next() | ||
{ | ||
if ( hasNext() ) | ||
{ | ||
fetched = false; | ||
CustomizeScoreEntityIterator.ScoreEntry best = nextHead; | ||
nextHead = null; | ||
return best; | ||
} | ||
else | ||
{ | ||
throw new NoSuchElementException( "The iterator is exhausted" ); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* A ScoreEntry consists of an entity id together with its score. | ||
*/ | ||
static class ScoreEntry | ||
{ | ||
private final long entityId; | ||
private final float score; | ||
|
||
long entityId() | ||
{ | ||
return entityId; | ||
} | ||
|
||
float score() | ||
{ | ||
return score; | ||
} | ||
|
||
ScoreEntry( long entityId, float score ) | ||
{ | ||
this.entityId = entityId; | ||
this.score = score; | ||
} | ||
|
||
@Override | ||
public String toString() | ||
{ | ||
return "ScoreEntry[entityId=" + entityId + ", score=" + score + "]"; | ||
} | ||
} | ||
} |
Oops, something went wrong.