"""
mdb.searchable
Subclass of `class:bento_meta.mdb.MDB` to support searching fulltext indices on an MDB
Note: certain fulltext indexes on certain MDB nodes and properties must be present in
the Neo4j instance:
- entityHandles
- termValue
- termDefn
- termValueDefn
"""
from bento_meta.mdb import read_txn, read_txn_data, read_txn_value
from bento_meta.mdb import MDB
from pdb import set_trace
[docs]class SearchableMDB(MDB):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ftindexes = {}
with self.driver.session() as s:
result = s.run("call db.indexes")
for rec in result:
if rec['type'] == 'FULLTEXT':
self.ftindexes[rec['name']] = {
"entity_type":rec['entityType'],
"entities":rec['labelsOrTypes'],
"properties":rec['properties'],
}
[docs] def available_indexes(self):
"""Fulltext indexes present in database.
Returns { <index_name> : { entity_type:<NODE|RELATIONSHIP>, entities:[<labels>],
properties:[ [<props>] ] } } """
return self.ftindexes
[docs] @read_txn_data
def query_index(self, index, qstring, skip=None, limit=None):
"""Query a named fulltext index of nodes or relationships.
Returns [ {ent:{}, label:<label>, score:<lucene score>} ]."""
if index not in self.ftindexes:
raise RuntimeError("Index with name '{}' not found".format(index))
tipe = ""
if self.ftindexes[index]['entity_type'] == "NODE":
tipe = "queryNodes"
elif self.ftindexes[index]['entity_type'] == "RELATIONSHIP":
tipe = "queryRelationships"
else:
raise RuntimeError("Wha?")
thing = self.ftindexes[index]['entity_type'].lower()
qry = (
"call db.index.fulltext.{tipe}($name, $query) "
"yield {thing}, score "
"return {thing} as ent, head(labels({thing})) as label, score "
"{skip} {limit} "
).format(tipe=tipe, thing=thing, skip="SKIP $skip" if skip else "",
limit="LIMIT $limit" if limit else "")
parms = {"name": index, "query": qstring}
if skip:
parms['skip'] = skip
if limit:
parms['limit'] = limit
return (qry, parms)
[docs] def search_entity_handles(self, qstring):
"""Fulltext search of qstring over node, relationship, and property handles.
Returns { node:[ {ent:<entity dict>,score:<lucene score>},... ],
relationship:[ <...> ], property:[ <...> ] }"""
result = self.query_index('entityHandle', qstring)
if not result:
return None
plural = {"node":"nodes", "relationship":"relationships",
"property":"properties"}
ret = {"nodes": [], "relationships": [], "properties": []}
for item in result:
ret[plural[item['label']]].append({"ent": item['ent'], "score": item['score']})
return ret
[docs] def search_terms(self, qstring, search_values=True,
search_definitions=True):
"""Fulltext for qstring over terms, by value, definition, or both (default).
Returns [ { ent:<term dict>, score:<lucene score> } ]}"""
index = {True: {True: 'termValueDefn', False: 'termDefn'},
False: {True: 'termValue', False: None}}
result = self.query_index(index[search_definitions][search_values],
qstring)
return result