Skip to content

feat(mongodb_atlas): support custom schema mapping via meta_project_mapping#3424

Open
PranavSShetty wants to merge 6 commits into
deepset-ai:mainfrom
PranavSShetty:main
Open

feat(mongodb_atlas): support custom schema mapping via meta_project_mapping#3424
PranavSShetty wants to merge 6 commits into
deepset-ai:mainfrom
PranavSShetty:main

Conversation

@PranavSShetty

Copy link
Copy Markdown

Related Issues

Proposed Changes:

This PR adds support for custom MongoDB schema mappings in MongoDBAtlasDocumentStore through a new optional meta_project_mapping configuration parameter.

When provided, keys in the Haystack Document meta dictionary can be mapped to specific root or nested MongoDB document fields.

Key additions:

  1. Nested field helper utilities

    • _get_nested_value
    • _set_nested_value
    • _pop_nested_value
  2. Filter translation support

    • Added _translate_filters to map metadata filter fields to corresponding MongoDB paths.
  3. Document conversion enhancements

    • Updated MongoDB ↔ Haystack document conversion logic.
  4. Metadata and aggregation support

    • Updated metadata analysis pipelines and retrieval logic.
  5. Serialization support

    • Added serialization and deserialization support for meta_project_mapping.
  • Added unit tests for nested path helper functions
  • Added tests for document conversion
  • Added tests for filter translation
  • Added tests for metadata aggregation and retrieval
  • Added serialization/deserialization tests

Test Results:

80 passed, 133 skipped

Backward compatibility has been preserved.

Existing behaviour remains unchanged when meta_project_mapping is not provided.

@PranavSShetty PranavSShetty requested a review from a team as a code owner June 10, 2026 14:12
@PranavSShetty PranavSShetty requested review from bogdankostic and removed request for a team June 10, 2026 14:12
@CLAassistant

CLAassistant commented Jun 10, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions

Copy link
Copy Markdown
Contributor

Heads-up for maintainers

This PR is from a fork and touches integrations whose integration tests require API keys.
Those tests are skipped in CI because fork PRs don't have access to repo secrets for security reasons.

Affected integrations:

  • mongodb_atlas

Please run the integration tests locally (hatch run test:integration inside each folder) before approving.

@github-actions github-actions Bot added the type:documentation Improvements or additions to documentation label Jun 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Coverage report (mongodb_atlas)

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  integrations/mongodb_atlas/src/haystack_integrations/document_stores/mongodb_atlas
  document_store.py 310-314, 327, 430-431, 449-451, 455-457, 466, 532, 622-627, 639-644, 794-801, 832, 858-875, 1310, 1316, 1323
Project Total  

This report was generated by python-coverage-comment-action

@bogdankostic bogdankostic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @PranavSShetty! I added a few in-line comments that should be addressed.

Comment on lines +1366 to +1372

if self.meta_project_mapping:
meta = mongo_doc.setdefault("meta", {})
for meta_key, mongo_field in self.meta_project_mapping.items():
val = self._pop_nested_value(mongo_doc, mongo_field)
if val is not None:
meta[meta_key] = val

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This raises ValueError when the stored doc has any root-level field that isn't a Haystack Document field and isn't fully consumed by the mapping, as long as at least one mapped field populated meta. Document.from_dict rejects receiving both a non-empty meta and "flattened" top-level keys:

store = MongoDBAtlasDocumentStore(..., meta_project_mapping={"source": "source"})
mongo_doc = {"id": "x1", "content": "hello", "source": "url", "category": "news", "meta": {}}
store._mongo_doc_to_haystack_doc(mongo_doc)
# ValueError: You can pass either the 'meta' parameter or flattened metadata keys ...

Comment on lines +1280 to +1323
def _get_nested_value(self, doc: dict[str, Any], path: str) -> Any:
if path.startswith("$"):
path = path[1:]
parts = path.split(".")
val = doc
for part in parts:
if isinstance(val, dict) and part in val:
val = val[part]
else:
return None
return val

def _set_nested_value(self, doc: dict[str, Any], path: str, value: Any) -> None:
if path.startswith("$"):
path = path[1:]
parts = path.split(".")
val = doc
for part in parts[:-1]:
if part not in val or not isinstance(val[part], dict):
val[part] = {}
val = val[part]
val[parts[-1]] = value

def _pop_nested_value(self, doc: dict[str, Any], path: str) -> Any:
if path.startswith("$"):
path = path[1:]
parts = path.split(".")

def rec_pop(d: dict[str, Any], path_parts: list[str]) -> tuple[Any, bool]:
if not path_parts:
return None, False
key = path_parts[0]
if len(path_parts) == 1:
if key in d:
val = d.pop(key)
return val, len(d) == 0
return None, False

if key in d and isinstance(d[key], dict):
val, should_delete_parent = rec_pop(d[key], path_parts[1:])
if should_delete_parent:
d.pop(key)
return val, len(d) == 0
return None, False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be static methods as they don't make use of self.

self.full_text_search_index = full_text_search_index
self.embedding_field = embedding_field
self.content_field = content_field
self.meta_project_mapping = meta_project_mapping

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mapping values get a leading $ stripped in ~6 places, while the aggregation builders conditionally prepend one. Prepending $ where an aggregation expression references a field value is correct and necessary. But accepting values that already start with $ — and therefore stripping it everywhere else — looks like an avoidable convenience that's leaking complexity across the file.

Could we instead define mapping values as bare field paths (no $), document that in this param, and normalize once here (e.g. strip a leading $ when storing self.meta_project_mapping)? Then every downstream site can assume a clean field name, the aggregation builders are the only place that adds $, and the five startswith("$") strips go away.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review and the detailed feedback. I've gone through the comments and I agree there are a few issues that need to be addressed, particularly the "Document.from_dict" case and the mapping normalization suggestions.

I'm currently unavailable for the next week, but I'll work through the feedback and submit an updated revision as soon as I'm back.

Thanks again for taking the time to review this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration:mongodb-atlas type:documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extending MongoDBAtlasDocumentStore to support custom schema

3 participants