Search nodes by name (case-insensitive substring), with optional FTS. Tries FTS first, falls back to CONTAINS on name.
(
self,
query: str,
node_types: list[str] | None = None,
limit: int = 50,
)
| 585 | return matches |
| 586 | |
| 587 | def search_nodes( |
| 588 | self, |
| 589 | query: str, |
| 590 | node_types: list[str] | None = None, |
| 591 | limit: int = 50, |
| 592 | ) -> list[dict[str, Any]]: |
| 593 | """Search nodes by name (case-insensitive substring), with optional FTS. |
| 594 | |
| 595 | Tries FTS first, falls back to CONTAINS on name. |
| 596 | """ |
| 597 | from opentrace_agent.store.constants import INTERNAL_NODE_TYPES |
| 598 | |
| 599 | # Try FTS first |
| 600 | try: |
| 601 | fts_results = self._fts_search(query, limit * 2) |
| 602 | if fts_results: |
| 603 | type_set = set(node_types) if node_types else None |
| 604 | nodes: list[dict[str, Any]] = [] |
| 605 | for node_id, _score in fts_results: |
| 606 | n = self.get_node(node_id) |
| 607 | if n is None: |
| 608 | continue |
| 609 | if n["type"] in INTERNAL_NODE_TYPES: |
| 610 | continue |
| 611 | if type_set and n["type"] not in type_set: |
| 612 | continue |
| 613 | nodes.append(n) |
| 614 | if len(nodes) >= limit: |
| 615 | break |
| 616 | return nodes |
| 617 | except Exception: |
| 618 | logger.debug("FTS search failed, using substring fallback", exc_info=True) |
| 619 | |
| 620 | # Substring fallback |
| 621 | q = query.lower() |
| 622 | result = self._conn.execute( |
| 623 | "MATCH (n:Node) WHERE lower(n.name) CONTAINS $query AND n.type <> $meta " |
| 624 | "RETURN n.id, n.type, n.name, n.properties", |
| 625 | parameters={"query": q, "meta": self._METADATA_TYPE}, |
| 626 | ) |
| 627 | type_set = set(node_types) if node_types else None |
| 628 | nodes = [] |
| 629 | while result.has_next() and len(nodes) < limit: |
| 630 | n = _row_to_node(result.get_next()) |
| 631 | if type_set and n["type"] not in type_set: |
| 632 | continue |
| 633 | nodes.append(n) |
| 634 | return nodes |
| 635 | |
| 636 | def search_graph( |
| 637 | self, |