Skip to content

Commit

Permalink
feature/mx-1673 prepare fulltext search (#225)
Browse files Browse the repository at this point in the history
### PR Context

- prep for #226

### Added

- add `extracted_or_rule_labels` to query builder globals

### Changes

- rename short and obscure cypher query variables to more expressive and
verbose ones

### Fixed

- avoid recursive retries in
`GraphConnector._check_connectivity_and_authentication`
- fix integration tests not properly marked as integration tests
  • Loading branch information
cutoffthetop authored Jan 9, 2025
1 parent fc2cec1 commit f1bbcc4
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 207 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- add `extracted_or_rule_labels` to query builder globals

### Changes

- rename short and obscure cypher query variables to more expressive and verbose ones

### Deprecated

### Removed

### Fixed

- avoid recursive retries in `GraphConnector._check_connectivity_and_authentication`
- fix integration tests not properly marked as integration tests

### Security

## [0.27.0] - 2024-12-19
Expand Down
2 changes: 1 addition & 1 deletion mex/backend/auxiliary/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def extracted_primary_source_ldap() -> ExtractedPrimarySource:

@cache
def extracted_organizational_unit() -> list[ExtractedOrganizationalUnit]:
"""Auxilary function to get ldap as primary resource."""
"""Auxiliary function to get ldap as primary resource."""
extracted_organigram_units = extract_organigram_units()
extracted_organizational_units = transform_organigram_units_to_organizational_units(
extracted_organigram_units, extracted_primary_source_ldap()
Expand Down
13 changes: 9 additions & 4 deletions mex/backend/graph/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ def _init_driver(self) -> Driver:
def _check_connectivity_and_authentication(self) -> Result:
"""Check the connectivity and authentication to the graph."""
query_builder = QueryBuilder.get()
result = self.commit(query_builder.fetch_database_status())
# use `_do_commit` to avoid recursive retries
result = self._do_commit(query_builder.fetch_database_status())
if (status := result["currentStatus"]) != "online":
msg = f"Database is {status}."
raise MExError(msg) from None
Expand Down Expand Up @@ -174,6 +175,11 @@ def _on_commit_giveup(event: BackoffDetails) -> None:
message = f": {query!r}"
logger.error("error committing query%s", message)

def _do_commit(self, query: Query | str, **parameters: Any) -> Result:
"""Send and commit a single graph transaction."""
with self.driver.session() as session:
return Result(session.run(str(query), parameters))

@backoff.on_exception(
backoff.fibo,
DriverError,
Expand All @@ -183,9 +189,8 @@ def _on_commit_giveup(event: BackoffDetails) -> None:
max_time=1000,
)
def commit(self, query: Query | str, **parameters: Any) -> Result:
"""Send and commit a single graph transaction."""
with self.driver.session() as session:
return Result(session.run(str(query), parameters))
"""Send and commit a single graph transaction with retry configuration."""
return self._do_commit(query, **parameters)

def _fetch_extracted_or_rule_items(
self,
Expand Down
36 changes: 18 additions & 18 deletions mex/backend/graph/cypher/fetch_extracted_or_rule_items.cql
Original file line number Diff line number Diff line change
Expand Up @@ -22,44 +22,44 @@ CALL () {
OPTIONAL CALL db.index.fulltext.queryNodes("search_index", $query_string)
YIELD node AS hit, score
<%- endif %>
OPTIONAL MATCH (n:<<rule_labels|join("|")>>|<<extracted_labels|join("|")>>)
OPTIONAL MATCH (extracted_or_rule_node:<<extracted_or_rule_labels|join("|")>>)
<%- if filter_by_stable_target_id -%>
-[:stableTargetId]->(merged:<<merged_labels|join("|")>>)
-[:stableTargetId]->(merged_node:<<merged_labels|join("|")>>)
<%- endif %>
<%- set and_ = joiner("AND ") %>
WHERE
<%- if filter_by_query_string %>
<<and_()>>elementId(hit) = elementId(n)
<<and_()>>elementId(hit) = elementId(extracted_or_rule_node)
<%- endif %>
<%- if filter_by_stable_target_id %>
<<and_()>>merged.identifier = $stable_target_id
<<and_()>>merged_node.identifier = $stable_target_id
<%- endif %>
<<and_()>>ANY(label IN labels(n) WHERE label IN $labels)
<<and_()>>ANY(label IN labels(extracted_or_rule_node) WHERE label IN $labels)
<%- endblock %>
RETURN COUNT(n) AS total
RETURN COUNT(extracted_or_rule_node) AS total
}
CALL () {
<<-self.match_clause()>>
WITH n
CALL (n) {
OPTIONAL MATCH (n)-[r]->(referenced:<<merged_labels|join("|")>>)
RETURN CASE WHEN referenced IS NOT NULL THEN {
WITH extracted_or_rule_node
CALL (extracted_or_rule_node) {
OPTIONAL MATCH (extracted_or_rule_node)-[r]->(referenced_merged_node:<<merged_labels|join("|")>>)
RETURN CASE WHEN referenced_merged_node IS NOT NULL THEN {
label: type(r),
position: r.position,
value: referenced.identifier
value: referenced_merged_node.identifier
} ELSE NULL END AS ref
UNION
OPTIONAL MATCH (n)-[r]->(nested:<<nested_labels|join("|")>>)
RETURN CASE WHEN nested IS NOT NULL THEN {
OPTIONAL MATCH (extracted_or_rule_node)-[r]->(referenced_nested_node:<<nested_labels|join("|")>>)
RETURN CASE WHEN referenced_nested_node IS NOT NULL THEN {
label: type(r),
position: r.position,
value: properties(nested)
value: properties(referenced_nested_node)
} ELSE NULL END AS ref
}
WITH n, collect(ref) AS refs
RETURN n{.*, entityType: head(labels(n)), _refs: refs}
ORDER BY n.identifier, n.entityType ASC
WITH extracted_or_rule_node, collect(ref) AS refs
RETURN extracted_or_rule_node{.*, entityType: head(labels(extracted_or_rule_node)), _refs: refs}
ORDER BY extracted_or_rule_node.identifier, extracted_or_rule_node.entityType ASC
SKIP $skip
LIMIT $limit
}
RETURN collect(n) AS items, total;
RETURN collect(extracted_or_rule_node) AS items, total;
40 changes: 20 additions & 20 deletions mex/backend/graph/cypher/fetch_merged_items.cql
Original file line number Diff line number Diff line change
Expand Up @@ -25,44 +25,44 @@ CALL () {
OPTIONAL CALL db.index.fulltext.queryNodes("search_index", $query_string)
YIELD node AS hit, score
<%- endif %>
OPTIONAL MATCH (n:<<rule_labels|join("|")>>|<<extracted_labels|join("|")>>)-[:stableTargetId]->(merged:<<merged_labels|join("|")>>)
OPTIONAL MATCH (extracted_or_rule_node:<<extracted_or_rule_labels|join("|")>>)-[:stableTargetId]->(merged_node:<<merged_labels|join("|")>>)
<%- set and_ = joiner("AND ") %>
WHERE
<%- if filter_by_query_string %>
<<and_()>>elementId(hit) = elementId(n)
<<and_()>>elementId(hit) = elementId(extracted_or_rule_node)
<%- endif %>
<%- if filter_by_stable_target_id %>
<<and_()>>merged.identifier = $stable_target_id
<<and_()>>merged_node.identifier = $stable_target_id
<%- endif %>
<<and_()>>ANY(label IN labels(merged) WHERE label IN $labels)
WITH DISTINCT merged AS merged
<<and_()>>ANY(label IN labels(merged_node) WHERE label IN $labels)
WITH DISTINCT merged_node AS merged_node
<%- endblock %>
RETURN COUNT(merged) AS total
RETURN COUNT(merged_node) AS total
}
CALL () {
<<-self.match_clause()>>
OPTIONAL MATCH (n)-[:stableTargetId]->(merged)
WITH n, merged
CALL (n) {
OPTIONAL MATCH (n)-[r]->(referenced:<<merged_labels|join("|")>>)
RETURN CASE WHEN referenced IS NOT NULL THEN {
OPTIONAL MATCH (extracted_or_rule_node)-[:stableTargetId]->(merged_node)
WITH extracted_or_rule_node, merged_node
CALL (extracted_or_rule_node) {
OPTIONAL MATCH (extracted_or_rule_node)-[r]->(referenced_merged_node:<<merged_labels|join("|")>>)
RETURN CASE WHEN referenced_merged_node IS NOT NULL THEN {
label: type(r),
position: r.position,
value: referenced.identifier
value: referenced_merged_node.identifier
} ELSE NULL END AS ref
UNION
OPTIONAL MATCH (n)-[r]->(nested:<<nested_labels|join("|")>>)
RETURN CASE WHEN nested IS NOT NULL THEN {
OPTIONAL MATCH (extracted_or_rule_node)-[r]->(referenced_nested_node:<<nested_labels|join("|")>>)
RETURN CASE WHEN referenced_nested_node IS NOT NULL THEN {
label: type(r),
position: r.position,
value: properties(nested)
value: properties(referenced_nested_node)
} ELSE NULL END AS ref
}
WITH merged, n, collect(ref) AS refs
ORDER BY merged.identifier, n.identifier, head(labels(n)) ASC
WITH merged, collect(n{.*, entityType: head(labels(n)), _refs: refs}) AS n
RETURN merged{entityType: head(labels(merged)), identifier: merged.identifier, components: n}
WITH merged_node, extracted_or_rule_node, collect(ref) AS refs
ORDER BY merged_node.identifier, extracted_or_rule_node.identifier, head(labels(extracted_or_rule_node)) ASC
WITH merged_node, collect(extracted_or_rule_node{.*, entityType: head(labels(extracted_or_rule_node)), _refs: refs}) AS extracted_or_rule_node
RETURN merged_node{entityType: head(labels(merged_node)), identifier: merged_node.identifier, components: extracted_or_rule_node}
SKIP $skip
LIMIT $limit
}
RETURN collect(merged) AS items, total;
RETURN collect(merged_node) AS items, total;
2 changes: 2 additions & 0 deletions mex/backend/graph/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ def __init__(self) -> None:
merged_labels=list(MERGED_MODEL_CLASSES_BY_NAME),
nested_labels=list(NESTED_MODEL_CLASSES_BY_NAME),
rule_labels=list(RULE_MODEL_CLASSES_BY_NAME),
extracted_or_rule_labels=list(EXTRACTED_MODEL_CLASSES_BY_NAME)
+ list(RULE_MODEL_CLASSES_BY_NAME),
)

def __getattr__(self, name: str) -> Callable[..., Query]:
Expand Down
Loading

0 comments on commit f1bbcc4

Please sign in to comment.