Skip to content

feat: add hstore column filter support for PostgreSQL#152

Open
Ckk3 wants to merge 3 commits intogazorby:mainfrom
Ckk3:issue-65
Open

feat: add hstore column filter support for PostgreSQL#152
Ckk3 wants to merge 3 commits intogazorby:mainfrom
Ckk3:issue-65

Conversation

@Ckk3
Copy link
Contributor

@Ckk3 Ckk3 commented Mar 3, 2026

Description

Add support for filtering on PostgreSQL hstore columns. This introduces a new HStoreFilter, _HStoreComparison input, and HStore GraphQL scalar, following the same patterns used by the existing JSON and Array filter implementations.

Supported filter operations: contains, containedIn, hasKey, hasKeyAll, hasKeyAny.

Types of Changes

  • Core
  • Bugfix
  • New feature
  • Enhancement/optimization
  • Documentation

Issues Fixed or Closed by This PR

Closes #65

Checklist

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • I have tested the changes and verified that they work and don't break anything (as well as I can manage).

Summary by CodeRabbit

  • New Features

    • PostgreSQL HStore support: GraphQL HStore scalar and filters (contains, containedBy, hasKey, hasKeyAll, hasKeyAny)
  • Documentation

    • Added HStore setup and usage guide with examples and GraphQL query samples
  • Tests

    • Added unit and integration tests verifying HStore parsing, filtering, and output behavior

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5c87a934-bae9-48e9-99b2-f113ab74b383

📥 Commits

Reviewing files that changed from the base of the PR and between ff49b53 and d3ae897.

📒 Files selected for processing (3)
  • README.md
  • src/strawchemy/schema/scalars/base.py
  • tests/unit/test_scalars.py

📝 Walkthrough

Walkthrough

Adds PostgreSQL HSTORE support across the library: new HStore scalar, parsing/serialization, filter types and comparison inputs, SQLAlchemy inspector handling for HSTORE fields, tests (unit and integration), test models/fixtures, and README documentation (HStore Filters section, duplicated).

Changes

Cohort / File(s) Summary
Scalars
src/strawchemy/schema/scalars/base.py, src/strawchemy/schema/scalars/__init__.py
Adds HStore scalar with parse/serialize hooks and exports HSTORE_SCALAR_OVERRIDES for mapping dict[str,str] to the scalar.
Filter types & inputs
src/strawchemy/schema/filters/base.py, src/strawchemy/schema/filters/inputs.py, src/strawchemy/schema/filters/__init__.py
Introduces HStoreFilter, _HStoreComparison input with fields (contains, contained_in, has_key, has_key_all, has_key_any), make_hstore_comparison_input() factory, and exports them.
Inspector mapping
src/strawchemy/dto/inspectors/sqlalchemy.py
Adds detection branch to return HStore comparison input for postgresql.HSTORE fields on PostgreSQL dialects.
Repository import tweak
src/strawchemy/repository/sqlalchemy/_sync.py
Reorders imports (no behavioral change).
Integration models & fixtures
tests/integration/models.py, tests/integration/fixtures.py, tests/integration/conftest.py
Adds HStoreModel and hstore_metadata, adds raw_hstore fixture and merges HSTORE_SCALAR_OVERRIDES into test scalar overrides; exports updated.
Integration GraphQL types
tests/integration/types/postgres.py
Adds HStoreType, test HStoreFilter, and async/sync query entry types for integration tests.
Tests
tests/integration/data_types/test_hstore.py, tests/unit/test_scalars.py
Adds integration tests for HStore filters and output, and unit tests for _parse_hstore behavior and error handling.
Docs
README.md
Adds HStore Filters documentation block with setup and usage examples (block duplicated in file).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I nibble keys and values in a neat little store,
Hopping through hashes and mapping galore,
Filters align, scalars parse true,
GraphQL and Postgres — a hop, then a chew.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding hstore column filter support for PostgreSQL, which is the primary objective of this pull request.
Linked Issues check ✅ Passed The pull request implements PostgreSQL HSTORE support as required by issue #65, including HStoreFilter, _HStoreComparison input, HStore scalar, and supported filter operations (contains, containedIn, hasKey, hasKeyAll, hasKeyAny).
Out of Scope Changes check ✅ Passed All changes are within scope: HStore support infrastructure, filter implementations, scalar definitions, comprehensive documentation, and corresponding tests. No unrelated or extraneous modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Ckk3 Ckk3 marked this pull request as ready for review March 3, 2026 23:57
@Ckk3
Copy link
Contributor Author

Ckk3 commented Mar 3, 2026

@gazorby I saw that we have some problems with the lint CI, I will create a new PR to fix it 😉

@codecov
Copy link

codecov bot commented Mar 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.32%. Comparing base (fe8ade9) to head (d3ae897).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #152      +/-   ##
==========================================
+ Coverage   94.28%   94.32%   +0.03%     
==========================================
  Files          69       69              
  Lines        5814     5854      +40     
  Branches      770      777       +7     
==========================================
+ Hits         5482     5522      +40     
  Misses        190      190              
  Partials      142      142              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Line 810: The wording for the README bullet describing hasKeyAll is awkward;
update the text for the **hasKeyAll** bullet to read "have all the given keys"
instead of "have all of the given keys" so it matches nearby bullets and reads
more clearly.

In `@src/strawchemy/repository/sqlalchemy/_sync.py`:
- Line 11: Regenerate src/strawchemy/repository/sqlalchemy/_sync.py from its
source _async.py so the import order matches the original: ensure the "from
sqlalchemy import ..." line appears before the "from sqlalchemy.orm import ..."
line and that all imported symbols (e.g., ColumnElement, Row, and_, delete,
inspect, select, update and any ORM imports) and formatting exactly mirror the
_async.py source; replace the current reversed imports with the corrected
ordering from _async.py and run the generator/formatter to keep the file
consistent.

In `@src/strawchemy/schema/scalars/base.py`:
- Around line 67-68: The _parse_hstore function currently returns the original
non-dict input which violates its contract; update _parse_hstore to validate the
input and raise a clear error when value is not a dict (e.g., raise TypeError or
ValueError), and otherwise perform the existing dict[str,str] conversion
(function _parse_hstore should only accept dict inputs and reject others rather
than returning them unchanged).

In `@tests/integration/data_types/test_hstore.py`:
- Around line 95-96: The test currently asserts row-by-row equality using an
index-dependent loop (expected_ids, result.data["hstore"][i]["id"]) which makes
it order-dependent and flaky; change it to assert unordered equivalence by
collecting the returned ids (from result.data["hstore"]) and comparing as a set
(or multisets) against the expected ids derived from raw_hstore
(raw_hstore[...]["id"]) so the test validates membership regardless of row
order. Target the variables result.data["hstore"], expected_ids, and raw_hstore
when updating the assertion logic.

In `@tests/integration/fixtures.py`:
- Line 105: The current code mutates the module-global mapping scalar_overrides
in place by applying HSTORE_SCALAR_OVERRIDES, which can leak Postgres-specific
overrides; instead create a new mapping when combining them (e.g., copy
scalar_overrides or build a new dict merging scalar_overrides and
HSTORE_SCALAR_OVERRIDES) and assign that new mapping to the local variable used
for schema creation so the global scalar_overrides remains unchanged; update the
spot where scalar_overrides is combined (reference symbol: scalar_overrides and
HSTORE_SCALAR_OVERRIDES) to use a non-mutating merge.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe8ade9 and ff49b53.

⛔ Files ignored due to path filters (1)
  • tests/integration/data_types/__snapshots__/test_hstore.ambr is excluded by !**/__snapshots__/**
📒 Files selected for processing (13)
  • README.md
  • src/strawchemy/dto/inspectors/sqlalchemy.py
  • src/strawchemy/repository/sqlalchemy/_sync.py
  • src/strawchemy/schema/filters/__init__.py
  • src/strawchemy/schema/filters/base.py
  • src/strawchemy/schema/filters/inputs.py
  • src/strawchemy/schema/scalars/__init__.py
  • src/strawchemy/schema/scalars/base.py
  • tests/integration/conftest.py
  • tests/integration/data_types/test_hstore.py
  • tests/integration/fixtures.py
  • tests/integration/models.py
  • tests/integration/types/postgres.py

Copy link
Owner

@gazorby gazorby left a comment

Choose a reason for hiding this comment

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

Hi @Ckk3, I left few nit comments, but the PR seems great!

)


def _serialize_hstore(value: dict[str, str]) -> dict[str, str]:
Copy link
Owner

Choose a reason for hiding this comment

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

This is unneeded, you can just use a bare lambda x: x in the scalar definition



def _parse_hstore(value: object) -> dict[str, str]:
if not isinstance(value, dict):
Copy link
Owner

Choose a reason for hiding this comment

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

You can rely on msgspec parsing here instead: msgspec.json.decode(object, type=dict[str, str])

@pytest.mark.snapshot
async def test_hstore_filters(
filter_name: str,
value: Any,
Copy link
Owner

Choose a reason for hiding this comment

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

You can type hint this: list[str] | str

datetime: DateTime,
}
engine_plugins: list[str] = []
scalar_overrides |= HSTORE_SCALAR_OVERRIDES
Copy link
Owner

Choose a reason for hiding this comment

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

I agree with the rabbit here, we could just add it to the dict directly ;)

@gazorby
Copy link
Owner

gazorby commented Mar 16, 2026

I don't get what's wrong with the CI errors, they seems legit to me, and ruff can fix them for you. Have you tried?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(postgres): implement HSTORE

2 participants