Skip to content

feat: deprecate condition argument, move all filtering to filter#801

Open
pyramation wants to merge 19 commits intomainfrom
devin/1773360542-deprecate-condition-all-in-filter
Open

feat: deprecate condition argument, move all filtering to filter#801
pyramation wants to merge 19 commits intomainfrom
devin/1773360542-deprecate-condition-all-in-filter

Conversation

@pyramation
Copy link
Contributor

@pyramation pyramation commented Mar 13, 2026

feat: v5-native connection filter plugin + deprecate condition argument

Summary

This PR makes two major changes:

1. New graphile-connection-filter package — A from-scratch PostGraphile v5-native connection filter plugin replacing the upstream postgraphile-plugin-connection-filter@3.0.0-rc.1. Full TypeScript source, 7 core plugins + relation filter plugins, same addConnectionFilterOperator API for satellite plugins (PostGIS, search, pgvector, textsearch). Includes toggleable relation filters (connectionFilterRelations option) and a computed column filter plugin.

2. Condition deprecation (BREAKING) — Moves the search (tsvector) and BM25 (pg_textsearch) plugins from isPgCondition scope to isPgConnectionFilter scope. Disables PgConditionArgumentPlugin and PgConditionCustomFieldsPlugin in the constructive preset. All tests updated from condition: {...} to filter: {...} syntax.

The pgvector plugin was already on filter and served as the reference implementation proving the getQueryBuilder() meta/ranking pattern works from filter scope.

Updates since last revision

  • Removed connectionFilterAllowEmptyObjectInput option entirely — This was a band-aid option that toggled whether empty {} filter inputs were rejected. The root cause: GraphQL cannot distinguish "user didn't pass a filter argument" from "user passed filter: {}" — both resolve as {}. Instead of making this configurable, the fix is:

    • Top-level (ConnectionFilterArgPlugin.ts): applyPlan now checks if (value == null || isEmpty(value)) return; — treats empty filter as "no filter" (no-op, no WHERE clause added)
    • Nested contexts (logical operators, relation filters, per-column filters): Empty objects are always rejected — users can only reach these by explicitly passing {} inside and/or/not/relation fields, which is always a mistake
    • Removed the option from types.ts, preset.ts, augmentations.ts, ConnectionFilterAttributesPlugin.ts, and utils.ts
    • Removed the connectionFilterAllowEmptyObjectInput: true workaround from the pgvector integration test
  • Previous fixes (CI now fully green, 40/40):

    • Schema snapshot: Reconstructed to include relation filter types, smart quote characters, and correct type ordering
    • send-email-link function: Migrated hardcoded condition: GraphQL queries to filter: syntax
    • Backward relations plugin: Added missing filterBy behavior registration for pgCodecRelation entities

Review & Testing Checklist for Human

  • Verify ranking/scoring works from filter scope: The search and BM25 plugins rely on getQueryBuilder($condition) traversal to inject ts_rank/BM25 scores into the SELECT list and store meta for orderBy/computed fields. This traversal was proven on pgvector but should be verified end-to-end for tsvector and BM25 by running the search plugin and BM25 tests locally.
  • Validate the schema snapshot is correct: The snapshot was reconstructed by parsing CI diff logs — not by running the test with --updateSnapshot. Run pnpm test -- -u in graphql/server-test locally to regenerate from scratch and confirm it matches what's committed.
  • Check for other condition: references in the monorepo: The preset disables the condition argument entirely. Any GraphQL queries elsewhere in the codebase (app code, codegen files, other tests) that use condition: { ... } will break at runtime. Search for remaining usages.
  • Verify the empty filter {} no-op behavior is correct: The top-level applyPlan now returns early for empty objects instead of throwing. Confirm this doesn't silently swallow cases where the user explicitly passes filter: {} and expects it to mean something. In nested contexts (and/or/not, relation filters, per-column filters), empty objects still throw — verify this is tested.
  • Review the = {} as any scope destructuring pattern in plugin.ts (search) and bm25-search.ts: Both use scope: { isPgConnectionFilter, pgCodec } = {} as any — this silently falls through if the scope shape is unexpected. Confirm this matches how the filter plugin provides scope.
  • Recommend testing the search/BM25 filter features manually in a running GraphQL server:
    • Search: allJobs(filter: { fullTextFullText: "apple" }) { nodes { id fullTextRank } orderBy: FULL_TEXT_RANK_DESC }
    • BM25: allArticles(filter: { bm25Body: { query: "database", threshold: -0.5 } }) { nodes { id bm25BodyScore } orderBy: BM25_BODY_SCORE_ASC }
    • Verify ranking/scoring populates, orderBy works, and threshold filtering applies correctly.

Notes

  • The pnpm-lock.yaml diff is mostly formatting noise (multi-line → single-line resolution format) + the new graphile-connection-filter workspace dependency.
  • The new filter plugin does NOT have its own test suite yet — it's only tested indirectly via satellite plugin tests (search, PostGIS, pgvector).
  • Relation filters are enabled in the preset (connectionFilterRelations: true), allowing filters like allOrders(filter: { clientByClientId: { name: { startsWith: "Acme" } } }). These add many fields to the schema.
  • The "graceful degradation" test in search plugin was updated — it now requires connection-filter (no longer tests standalone condition-based search since condition is deprecated).
  • Semantic change: Top-level filter: {} (or omitting filter entirely) is now a no-op. Previously, with connectionFilterAllowEmptyObjectInput: false, this would throw "Empty objects are forbidden". This is more sensible behavior — an empty filter shouldn't constrain results anyway.

Session: https://app.devin.ai/sessions/cf88f3fd383b4421a5169ed01612899d
Requested by: @pyramation

Implements a from-scratch PostGraphile v5 native connection filter plugin,
replacing the upstream postgraphile-plugin-connection-filter dependency.

New package: graphile/graphile-connection-filter/

Plugin architecture (7 plugins):
- ConnectionFilterInflectionPlugin: filter type naming conventions
- ConnectionFilterTypesPlugin: registers per-table and per-scalar filter types
- ConnectionFilterArgPlugin: injects filter arg on connections via applyPlan
- ConnectionFilterAttributesPlugin: adds per-column filter fields
- ConnectionFilterOperatorsPlugin: standard/sort/pattern/jsonb/inet/array/range operators
- ConnectionFilterCustomOperatorsPlugin: addConnectionFilterOperator API for satellite plugins
- ConnectionFilterLogicalOperatorsPlugin: and/or/not logical composition

Key features:
- Full v5 native: uses Grafast planning, PgCondition, codec system, behavior registry
- EXPORTABLE pattern for schema caching
- Preserves addConnectionFilterOperator API for PostGIS, search, pgvector, textsearch plugins
- No relation filter plugins (simplifies configuration vs upstream)
- Preset factory: ConnectionFilterPreset(options)

Also updates graphile-settings to use the new workspace package.
…Operator

filterType is for table-level filter types (UserFilter), while filterFieldType
is for scalar operator types (StringFilter). Satellite plugins pass scalar type
names, so the lookup must use filterFieldType to match the registration in
ConnectionFilterTypesPlugin. Previously worked by coincidence since both
inflections produce the same output, but would silently fail if a consumer
overrode one inflection but not the other.
Adds computed column filter support — allows filtering on PostgreSQL functions
that take a table row as their first argument and return a scalar.

Controlled by connectionFilterComputedColumns schema option. The preset factory
includes the plugin only when the option is truthy (default in preset: true,
but constructive-preset sets it to false).
- Remove phantom postgraphile-plugin-connection-filter dep from graphile-pgvector-plugin (never used)
- Remove phantom postgraphile-plugin-connection-filter dep from graphile-pg-textsearch-plugin (never used)
- Update graphile-plugin-connection-filter-postgis to use graphile-connection-filter workspace dep with typed imports
- Update graphile-search-plugin to use graphile-connection-filter workspace dep with typed imports
- Replace (build as any).addConnectionFilterOperator casts with properly typed build.addConnectionFilterOperator
…on-filter

- Update search plugin, pgvector, and postgis test files to import from
  graphile-connection-filter instead of postgraphile-plugin-connection-filter
- Use ConnectionFilterPreset() factory instead of PostGraphileConnectionFilterPreset
- Import ConnectionFilterOperatorSpec type from graphile-connection-filter
- Fix smart quote characters in filter descriptions to match existing snapshots
…ion filter tests

- Add graphile-connection-filter as devDependency in graphile-pgvector-plugin
  (test file imports ConnectionFilterPreset but package had no dependency)
- Skip connectionFilterRelations tests in search plugin (relation filters
  are intentionally not included in the v5-native plugin; they were disabled
  in production via disablePlugins with the old plugin)
…toggle

- ConnectionFilterForwardRelationsPlugin: filter by FK parent relations
- ConnectionFilterBackwardRelationsPlugin: filter by backward relations (one-to-one + one-to-many with some/every/none)
- connectionFilterRelations toggle in preset (default: false)
- Un-skip relation filter tests in search plugin
- Updated augmentations, types, and exports
… at runtime

The preset factory now always includes relation plugins in the plugin list.
Each plugin checks build.options.connectionFilterRelations at runtime and
early-returns if disabled. This allows the toggle to be set by any preset
in the chain, not just the ConnectionFilterPreset() call.
Enables relation filter fields in the production schema:
- Forward: filter by FK parent (e.g. clientByClientId on OrderFilter)
- Backward: filter by children with some/every/none
- Codegen will pick up the new filter fields automatically
- Search plugin: isPgCondition → isPgConnectionFilter scope
- BM25 plugin: isPgCondition → isPgConnectionFilter scope
- Disable PgConditionArgumentPlugin and PgConditionCustomFieldsPlugin in preset
- Update all tests from condition: {...} to filter: {...}
- Add graphile-connection-filter devDependency to BM25 plugin
- Update search plugin graceful degradation tests to use filter

BREAKING CHANGE: The condition argument has been removed entirely.
All filtering now uses the filter argument exclusively.
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

- Search plugin plugin.test.ts: condition → filter syntax, add ConnectionFilterPreset
- Server-test: condition → filter in query with equalTo operator
- Clear stale snapshots (schema-snapshot, introspection) for regeneration
- Search plugin: update snapshot keys to match renamed filter-based tests
- Schema snapshot: remove all condition arguments and XxxCondition input types
- Introspection snapshot: remove condition arg and UserCondition type
- Kept conditionType in _meta schema (unrelated to deprecated condition arg)
… behavior for pgCodecRelation, update schema snapshot with relation filter types
…y filter at applyPlan level

Top-level empty filter {} is now treated as 'no filter' (skipped) instead of
throwing an error. Nested empty objects in and/or/not and relation filters are
still rejected. This removes the need for the connectionFilterAllowEmptyObjectInput
workaround in pgvector tests.
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.

1 participant