diff --git a/synapse/lib/ast.py b/synapse/lib/ast.py index ab89a2c342..b844adaa95 100644 --- a/synapse/lib/ast.py +++ b/synapse/lib/ast.py @@ -2649,6 +2649,9 @@ async def getPivsIn(self, runt, node, path): async for pivo, link in runt.view.getNdefRefs(node.ndef): yield pivo, path.fork(pivo, link) + async for pivo, link in runt.view.getNodePropRefs(node.ndef): + yield pivo, path.fork(pivo, link) + for prop, valu in node.getProps().items(): pdef = (f'{name}:{prop}', valu) async for pivo, link in runt.view.getNodePropRefs(pdef): @@ -2679,7 +2682,7 @@ class FormPivot(PivotOper): def pivogenr(self, runt, prop, virts=None): # -> baz:ndef - if isinstance(prop.type, s_types.Ndef): + if isinstance(prop.type, (s_types.Ndef, s_types.NodeProp)): async def pgenr(node, strict=True): link = {'type': 'prop', 'prop': prop.name, 'reverse': True} @@ -2691,7 +2694,7 @@ async def pgenr(node, strict=True): # plain old pivot... async def pgenr(node, strict=True): if prop.type.isarray: - if isinstance(prop.type.arraytype, s_types.Ndef): + if isinstance(prop.type.arraytype, (s_types.Ndef, s_types.NodeProp)): ngenr = runt.view.nodesByPropArray(prop.full, '=', node.ndef, norm=False, virts=virts) else: norm = prop.arraytypehash is not node.form.typehash @@ -2784,26 +2787,28 @@ async def pgenr(node, strict=True): async for pivo in runt.view.nodesByPropValu(destform.name, '=', refselem, norm=False): yield pivo, link - for refsname in refs.get('ndef'): + for key in ('ndef', 'nodeprop'): + for refsname in refs.get(key): - found = True + found = True - refsvalu = node.get(refsname) - if refsvalu is not None and refsvalu[0] == destform.name: - pivo = await runt.view.getNodeByNdef(refsvalu) - if pivo is not None: - yield pivo, {'type': 'prop', 'prop': refsname} + refsvalu = node.get(refsname) + if refsvalu is not None and refsvalu[0] == destform.name: + pivo = await runt.view.getNodeByNdef(refsvalu) + if pivo is not None: + yield pivo, {'type': 'prop', 'prop': refsname} - for refsname in refs.get('ndefarray'): + for key in ('ndefarray', 'nodeproparray'): + for refsname in refs.get(key): - found = True + found = True - if (refsvalu := node.get(refsname)) is not None: - link = {'type': 'prop', 'prop': refsname} - for aval in refsvalu: - if aval[0] == destform.name: - if (pivo := await runt.view.getNodeByNdef(aval)) is not None: - yield pivo, link + if (refsvalu := node.get(refsname)) is not None: + link = {'type': 'prop', 'prop': refsname} + for aval in refsvalu: + if aval[0] == destform.name: + if (pivo := await runt.view.getNodeByNdef(aval)) is not None: + yield pivo, link ######################################################################### # reverse "-> form" pivots (ie inet:fqdn -> inet:dns:a) @@ -2836,23 +2841,25 @@ async def pgenr(node, strict=True): yield pivo, link # "reverse" ndef references... - for refsname in refs.get('ndef'): + for key in ('ndef', 'nodeprop'): + for refsname in refs.get(key): - found = True + found = True - refsprop = destform.props.get(refsname) - link = {'type': 'prop', 'prop': refsname, 'reverse': True} - async for pivo in runt.view.nodesByPropValu(refsprop.full, '=', node.ndef, norm=False): - yield pivo, link + refsprop = destform.props.get(refsname) + link = {'type': 'prop', 'prop': refsname, 'reverse': True} + async for pivo in runt.view.nodesByPropValu(refsprop.full, '=', node.ndef, norm=False): + yield pivo, link - for refsname in refs.get('ndefarray'): + for key in ('ndefarray', 'nodeproparray'): + for refsname in refs.get(key): - found = True + found = True - refsprop = destform.props.get(refsname) - link = {'type': 'prop', 'prop': refsname, 'reverse': True} - async for pivo in runt.view.nodesByPropArray(refsprop.full, '=', node.ndef, norm=False): - yield pivo, link + refsprop = destform.props.get(refsname) + link = {'type': 'prop', 'prop': refsname, 'reverse': True} + async for pivo in runt.view.nodesByPropArray(refsprop.full, '=', node.ndef, norm=False): + yield pivo, link if strict and not found: mesg = f'No pivot found for {node.form.name} -> {destform.name}.' @@ -3020,7 +3027,7 @@ async def pgenr(node, srcname, srctype, valu): # pivoting from an array prop to a non-array prop needs an extra loop if srctype.isarray and not prop.type.isarray: - if isinstance(srctype.arraytype, s_types.Ndef) and prop.isform: + if isinstance(srctype.arraytype, (s_types.Ndef, s_types.NodeProp)) and prop.isform: for aval in valu: if aval[0] != prop.form.name: continue @@ -3036,7 +3043,7 @@ async def pgenr(node, srcname, srctype, valu): return - if isinstance(srctype, s_types.Ndef) and prop.isform: + if isinstance(srctype, (s_types.Ndef, s_types.NodeProp)) and prop.isform: if valu[0] != prop.form.name: return diff --git a/synapse/tests/test_lib_layer.py b/synapse/tests/test_lib_layer.py index 87517cf30a..a85fc13bcb 100644 --- a/synapse/tests/test_lib_layer.py +++ b/synapse/tests/test_lib_layer.py @@ -2410,6 +2410,47 @@ async def test_layer_nodeprop_indexes(self): self.len(4, await core.nodes('test:str=faz -> *', opts=viewopts2)) self.len(4, await core.nodes('test:str=faz :pdefs -> *', opts=viewopts2)) + await core.nodes('[ test:int=3 (test:str=ndef1 test:str=ndef2 :baz=(test:int, 3)) ]') + nodes = await core.nodes('test:int=3 <- *') + self.len(2, nodes) + self.eq(nodes[0].ndef, ('test:str', 'ndef1')) + self.eq(nodes[1].ndef, ('test:str', 'ndef2')) + + nodes = await core.nodes('test:int=3 -> test:str:baz') + self.len(2, nodes) + self.eq(nodes[0].ndef, ('test:str', 'ndef1')) + self.eq(nodes[1].ndef, ('test:str', 'ndef2')) + + nodes = await core.nodes('test:str=ndef1 -> test:int') + self.len(1, nodes) + self.eq(nodes[0].ndef, ('test:int', 3)) + + nodes = await core.nodes('test:str=ndef1 :baz -> test:int') + self.len(1, nodes) + self.eq(nodes[0].ndef, ('test:int', 3)) + + await core.nodes('[ test:str=ndefarry1 test:str=ndefarry2 :pdefs=((test:int, 3),) ]') + + nodes = await core.nodes('test:int=3 -> test:str:pdefs') + self.len(2, nodes) + self.eq(nodes[0].ndef, ('test:str', 'ndefarry1')) + self.eq(nodes[1].ndef, ('test:str', 'ndefarry2')) + + nodes = await core.nodes('test:int=3 -> test:str') + self.len(4, nodes) + self.eq(nodes[0].ndef, ('test:str', 'ndef1')) + self.eq(nodes[1].ndef, ('test:str', 'ndef2')) + self.eq(nodes[2].ndef, ('test:str', 'ndefarry1')) + self.eq(nodes[3].ndef, ('test:str', 'ndefarry2')) + + nodes = await core.nodes('test:str=ndefarry1 -> test:int') + self.len(1, nodes) + self.eq(nodes[0].ndef, ('test:int', 3)) + + nodes = await core.nodes('test:str=ndefarry1 :pdefs -> test:int') + self.len(1, nodes) + self.eq(nodes[0].ndef, ('test:int', 3)) + async def test_layer_virt_indexes(self): async with self.getTestCore() as core: diff --git a/synapse/tests/test_model_language.py b/synapse/tests/test_model_language.py index 5e37334b28..fbdc364bfc 100644 --- a/synapse/tests/test_model_language.py +++ b/synapse/tests/test_model_language.py @@ -14,8 +14,9 @@ async def test_model_language(self): :output:lang={[ lang:language=({"code": "en.us"}) ]} :desc=Greetings :engine=* + lang:phrase=Hola ]''') - self.len(1, nodes) + self.len(2, nodes) self.eq(nodes[0].get('input'), ('lang:phrase', 'Hola')) self.eq(nodes[0].get('output'), 'Hi') @@ -23,9 +24,12 @@ async def test_model_language(self): self.eq(nodes[0].get('output:lang'), 'a8eeae81da6c305c9cf6e4962bd106b2') self.eq(nodes[0].get('desc'), 'Greetings') - # FIXME nodeprop indexing... - # self.len(1, await core.nodes('lang:phrase <- *')) - # self.len(1, await core.nodes('lang:translation -> lang:phrase')) + self.len(1, await core.nodes('lang:phrase <- *')) + self.len(1, await core.nodes('lang:translation -> lang:phrase')) + self.len(1, await core.nodes('lang:phrase -> lang:translation')) + + self.len(1, await core.nodes('lang:translation :input -> *')) + self.len(1, await core.nodes('lang:translation :input -> lang:phrase')) self.len(1, await core.nodes('lang:translation -> it:software')) self.len(2, await core.nodes('lang:translation -> lang:language'))