Skip to content

1106 chore refactor cel2sql#1108

Open
mleonidas wants to merge 4 commits intomainfrom
1106-chore-refactor-cel2sql
Open

1106 chore refactor cel2sql#1108
mleonidas wants to merge 4 commits intomainfrom
1106-chore-refactor-cel2sql

Conversation

@mleonidas
Copy link
Copy Markdown
Collaborator

@mleonidas mleonidas commented May 6, 2026

The versionselector pushdown initially used github.com/spandigital/cel2sql/v3
to translate CEL selectors into SQL WHERE fragments. The codebase already
ships an SQL extractor at pkg/celutil/cel_sql.go (used by the resource
store) that fits this use case better:

  • Parameterized output. cel2sql inlines escaped string literals into the
    emitted SQL; celutil.SQLExtractor returns $N placeholders + an args
    slice. SQL injection becomes structurally impossible rather than
    verified-by-test, and the integration matches the pgx convention used
    everywhere else in the binary.

  • Native JSONB key access. version.metadata["env"] == "prod" now pushes
    down as metadata->>$N = $N+1; cel2sql with our flat schema couldn't
    translate this shape at all.

  • One fewer third-party dependency.
    The trade-off is OR composition: celutil.SQLExtractor only flattens
    top-level && conjuncts, so OR-bearing selectors fall back to the
    row-by-row CEL evaluator. Real-world version selectors are
    overwhelmingly AND-shaped, and runtime CEL still produces the correct
    result for any selector — pushdown is a candidate-set narrowing
    optimization, not a correctness boundary.

    Wire-up changes:

    • versionselector.TryPushDown returns (clause, args, ok) and accepts a
      startParam so multiple rules can share a single argument list with
      non-overlapping placeholders.
    • desiredrelease.IterCandidateVersions takes (pushdownClauses,
      pushdownArgs); base parameters $1-$4 are reserved for deploymentID,
      limit, and the keyset cursor; pushdown args land at $5+.
    • collectPushdownClauses tracks the running placeholder offset and
      silently skips untranslatable rules without consuming a slot.
    • Singleflight cache key continues to hash the clause set; identical
      selector text deterministically produces identical args, so no
      separate args fingerprint is needed.

    go.mod loses github.com/spandigital/cel2sql/v3 via go mod tidy.

Summary by CodeRabbit

Release Notes

  • Chores

    • Updated dependencies and removed deprecated packages
    • Enhanced environment configuration for local development
  • Refactor

    • Optimized query filtering with parameterized SQL execution
    • Improved structured logging for resource operations

mleonidas added 3 commits May 6, 2026 09:50
The versionselector pushdown initially used github.com/spandigital/cel2sql/v3
to translate CEL selectors into SQL WHERE fragments. The codebase already
ships an SQL extractor at pkg/celutil/cel_sql.go (used by the resource
store) that fits this use case better:

- Parameterized output. cel2sql inlines escaped string literals into the
  emitted SQL; celutil.SQLExtractor returns $N placeholders + an args
  slice. SQL injection becomes structurally impossible rather than
  verified-by-test, and the integration matches the pgx convention used
  everywhere else in the binary.
- Native JSONB key access. version.metadata["env"] == "prod" now pushes
  down as metadata->>$N = $N+1; cel2sql with our flat schema couldn't
  translate this shape at all.
- One fewer third-party dependency.
  The trade-off is OR composition: celutil.SQLExtractor only flattens
  top-level && conjuncts, so OR-bearing selectors fall back to the
  row-by-row CEL evaluator. Real-world version selectors are
  overwhelmingly AND-shaped, and runtime CEL still produces the correct
  result for any selector — pushdown is a candidate-set narrowing
  optimization, not a correctness boundary.

  Wire-up changes:
  - versionselector.TryPushDown returns (clause, args, ok) and accepts a
    startParam so multiple rules can share a single argument list with
    non-overlapping placeholders.
  - desiredrelease.IterCandidateVersions takes (pushdownClauses,
    pushdownArgs); base parameters $1-$4 are reserved for deploymentID,
    limit, and the keyset cursor; pushdown args land at $5+.
  - collectPushdownClauses tracks the running placeholder offset and
    silently skips untranslatable rules without consuming a slot.
  - Singleflight cache key continues to hash the clause set; identical
    selector text deterministically produces identical args, so no
    separate args fingerprint is needed.

  go.mod loses github.com/spandigital/cel2sql/v3 via go mod tidy.
* log.Info in charmbracelet/log expects key/value pairs after the message,
  not printf-style formatting. The "%s" placeholder was rendered literally
  ("...filter: %s filter=<clause>") instead of being substituted. Drop
  the placeholder and let the structured "filter" key carry the clause.
* Provides direnv integration so contributors get the project's flox
environment auto-activated on cd into the repo. Local overrides land in
.env.local (dotenv) or .envrc.local (full bash sourcing) so per-machine
secrets and tweaks stay out of source control. Setting USE_FLOX=n in
.env.local opts out for anyone not using flox.
@mleonidas mleonidas linked an issue May 6, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Warning

Rate limit exceeded

@mleonidas has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 31 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0201381e-1fd0-44c7-99d5-74f24c299b68

📥 Commits

Reviewing files that changed from the base of the PR and between 3d9d263 and e8d0712.

📒 Files selected for processing (1)
  • apps/workspace-engine/svc/controllers/desiredrelease/getters_postgres.go
📝 Walkthrough

Walkthrough

This PR migrates the SQL pushdown mechanism in the workspace-engine from CEL2SQL to a celutil-based parameterized approach. It updates the versionselector pushdown API to accept a parameter offset and return clause with arguments separately, cascades signature changes through getter and reconcile interfaces, and adds corresponding test coverage. The PR also adds a .envrc file for local environment configuration.

Changes

Environment Configuration

Layer / File(s) Summary
Configuration
.envrc
Loads local environment variables from .env.local and .envrc.local; conditionally enables Flox environment based on USE_FLOX (default "y"); includes shellcheck directive.

SQL Pushdown Refactoring

Layer / File(s) Summary
Dependencies
apps/workspace-engine/go.mod
Removes spandigital/cel2sql/v3 and adds indirect dependencies for testcontainers, OTLP instrumentation, and OpenTelemetry exports.
Core Pushdown Implementation
apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/versionselector/pushdown.go
Replaces CEL2SQL environment-based pushdown with celutil SQL extractor; TryPushDown now accepts startParam and returns both parameterized SQL clause and arguments list instead of clause only.
Pushdown Tests
apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/versionselector/pushdown_test.go
Validates parameterized SQL output, injection safety, placeholder advancement across multiple selectors, and partial extraction behavior; removes string-escaping and JSONB access standalone tests.
Getter Interface
apps/workspace-engine/svc/controllers/desiredrelease/getters.go
Updates IterCandidateVersions to accept pushdownClauses []string and pushdownArgs []any instead of extraWhere []string; clarifies pushdown-based filtering contract.
Postgres Getter Implementation
apps/workspace-engine/svc/controllers/desiredrelease/getters_postgres.go
Refactors candidate version iteration to use parameterized pushdown clauses; introduces hashClauses for cache keys; dynamically constructs SQL from pushdown fragments and arguments; updates fetchFirstBatch and queryCandidateVersionsBatch signatures.
Reconcile Flow
apps/workspace-engine/svc/controllers/desiredrelease/reconcile.go
Adds collectPushdownClauses function to translate policy version selectors into parameterized SQL; passes both clauses and arguments to candidate version iteration; defines pushdownBaseParam for SQL placeholder offset.
Reconcile Tests & Integration
apps/workspace-engine/svc/controllers/desiredrelease/reconcile_test.go, apps/workspace-engine/svc/controllers/desiredrelease/getters_postgres_test.go, apps/workspace-engine/svc/controllers/desiredrelease/pushdown_test.go, apps/workspace-engine/test/controllers/harness/mocks.go
Updates mock getter signatures and test fixtures to match parameterized pushdown interface; adds test coverage for nil/disabled policies, translatable/untranslatable selectors, and placeholder numbering; minimal formatting adjustments.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ctrlplanedev/ctrlplane#1105: Both PRs modify the desiredrelease pushdown/iterator flow and IterCandidateVersions getter signatures for parameterized SQL pushdown.

Suggested reviewers

  • jsbroks

Poem

🐰 From cel2sql we hop away,
Parameterized clauses here to stay,
Each argument safely bound at last,
Injection fears are in the past!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title references the refactoring of CEL2SQL, which aligns with the primary changes in the pull request that replace the deprecated CEL2SQL dependency with a new celutil-based approach across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 1106-chore-refactor-cel2sql

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.

Copy link
Copy Markdown
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: 1

🧹 Nitpick comments (2)
apps/workspace-engine/svc/controllers/desiredrelease/getters.go (1)

31-45: 💤 Low value

Consider documenting the placeholder offset as a named constant.

The contract pins $5 and the meaning of $1$4 directly in the interface comment, but the actual offset is computed in reconcile.go as pushdownBaseParam and consumed by getters_postgres.go. If the reserved-param layout ever changes (e.g., a workspace filter is added), three files have to stay in lockstep, and this comment can silently drift.

Not blocking — but a small pushdownBaseParam constant exported (or referenced) from a single source of truth would make the contract self-checking. Optional, defer if you prefer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/workspace-engine/svc/controllers/desiredrelease/getters.go` around lines
31 - 45, Add a single exported constant for the reserved-parameter offset (e.g.,
pushdownBaseParam) and use it as the canonical source of truth instead of
hardcoding “$5” in comments and code: declare the constant where the
reserved-parameter layout is defined (currently pushdownBaseParam in
reconcile.go), export it (or move it to a shared package) so getters_postgres.go
and the IterCandidateVersions interface comment can reference it, update
reconcile.go to consume that exported constant, and update the
IterCandidateVersions comment to mention the named constant rather than
hardcoded $5 so all three locations stay in sync.
apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/versionselector/pushdown_test.go (1)

94-102: 💤 Low value

Partial-pushdown test silently passes either way.

t.Skip when ok=false means the test passes whether the extractor pushes the version.tag conjunct down or refuses entirely. If the goal is to document current behavior and lock it in, this is fine; if the goal is to guard against the extractor losing partial-extraction support, consider asserting one branch deterministically (the current celutil behavior) so a future regression fails loudly.

Minor — feel free to keep as-is if intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/versionselector/pushdown_test.go`
around lines 94 - 102, The test TestTryPushDown_PartialAndDoesPushSubset
currently calls TryPushDown and t.Skip() if ok==false, which silently lets the
test pass whether partial pushdown is supported or not; decide on deterministic
behavior and enforce it: replace the t.Skip branch with an explicit assertion
(e.g., require.True(t, ok) or t.Fatalf) to lock in the current behavior (or
alternatively assert require.False if you want to assert refusal), so the test
fails on regressions; keep the subsequent assertions on clause and args the same
to validate that TryPushDown produced the expected partial pushdown.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/workspace-engine/svc/controllers/desiredrelease/getters_postgres.go`:
- Around line 193-202: The singleflight key in PostgresGetter.fetchFirstBatch
uses deploymentID + hashClauses(pushdownClauses) which ignores pushdownArgs,
causing different selectors that compile to the same clause shape to collide;
update the key construction in fetchFirstBatch (and the equivalent block around
the other occurrence that spans the logic at lines ~309-321) to incorporate a
stable representation of pushdownArgs (e.g., serialize or hash pushdownArgs) so
the key becomes deploymentID + hashClauses(pushdownClauses) + ":" +
hashArgs(pushdownArgs); ensure the chosen serialization/hashing is deterministic
and safe for any arg types so concurrent fetches are correctly distinguished.

---

Nitpick comments:
In
`@apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/versionselector/pushdown_test.go`:
- Around line 94-102: The test TestTryPushDown_PartialAndDoesPushSubset
currently calls TryPushDown and t.Skip() if ok==false, which silently lets the
test pass whether partial pushdown is supported or not; decide on deterministic
behavior and enforce it: replace the t.Skip branch with an explicit assertion
(e.g., require.True(t, ok) or t.Fatalf) to lock in the current behavior (or
alternatively assert require.False if you want to assert refusal), so the test
fails on regressions; keep the subsequent assertions on clause and args the same
to validate that TryPushDown produced the expected partial pushdown.

In `@apps/workspace-engine/svc/controllers/desiredrelease/getters.go`:
- Around line 31-45: Add a single exported constant for the reserved-parameter
offset (e.g., pushdownBaseParam) and use it as the canonical source of truth
instead of hardcoding “$5” in comments and code: declare the constant where the
reserved-parameter layout is defined (currently pushdownBaseParam in
reconcile.go), export it (or move it to a shared package) so getters_postgres.go
and the IterCandidateVersions interface comment can reference it, update
reconcile.go to consume that exported constant, and update the
IterCandidateVersions comment to mention the named constant rather than
hardcoded $5 so all three locations stay in sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 85600f21-571b-41e4-bfde-63de58325d3f

📥 Commits

Reviewing files that changed from the base of the PR and between 56cffc1 and 3d9d263.

⛔ Files ignored due to path filters (1)
  • apps/workspace-engine/go.sum is excluded by !**/*.sum
📒 Files selected for processing (12)
  • .envrc
  • apps/workspace-engine/go.mod
  • apps/workspace-engine/pkg/store/resources/get_resources.go
  • apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/versionselector/pushdown.go
  • apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/versionselector/pushdown_test.go
  • apps/workspace-engine/svc/controllers/desiredrelease/getters.go
  • apps/workspace-engine/svc/controllers/desiredrelease/getters_postgres.go
  • apps/workspace-engine/svc/controllers/desiredrelease/getters_postgres_test.go
  • apps/workspace-engine/svc/controllers/desiredrelease/pushdown_test.go
  • apps/workspace-engine/svc/controllers/desiredrelease/reconcile.go
  • apps/workspace-engine/svc/controllers/desiredrelease/reconcile_test.go
  • apps/workspace-engine/test/controllers/harness/mocks.go

Comment thread apps/workspace-engine/svc/controllers/desiredrelease/getters_postgres.go Outdated
Two CEL selectors can compile to the same SQL clause text while binding
different values — `version.tag == "v1"` and `version.tag == "v2"` both
emit `tag = $5`, the only difference being the arg slice. The
fetchFirstBatch singleflight key previously hashed only the clauses, so
concurrent reconciles for different release targets of the same
deployment whose selectors collapsed to identical clause shapes would
share the same cache slot and receive each other's filtered results.

Extend the key with hashArgs(pushdownArgs) so any difference in bound
values produces a distinct slot. Args use fmt %v for stable string
representation, which is deterministic for the string-typed values
celutil.SQLExtractor currently emits.
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.

chore: refactor cel2sql

1 participant