Skip to content

chore: sync upstream 2026-03-27#1

Open
hanzo-dev wants to merge 17 commits into
mainfrom
upstream-sync-20260328
Open

chore: sync upstream 2026-03-27#1
hanzo-dev wants to merge 17 commits into
mainfrom
upstream-sync-20260328

Conversation

@hanzo-dev
Copy link
Copy Markdown
Member

Automated upstream sync

Azorlogh and others added 17 commits March 10, 2026 16:16
formancehq#1295)

* fix(ledger): push address filter into LATERAL join for GIN index usage (formancehq#1293)

* fix(ledger): push address filter into LATERAL join for GIN index usage

When filtering by partial address (e.g. $match[system:cashier:xxx]),
the query builder was placing the address_array jsonpath filter only
in the outer WHERE clause, after GROUP BY. This forced Postgres to
seq scan the entire moves/accounts_volumes table, join all accounts,
aggregate, then discard non-matching rows (~6.6s on large datasets).

By duplicating the address filter inside the LATERAL join on accounts,
Postgres can leverage the GIN index on accounts_address_array to
resolve matching accounts first (~3ms, x2200 improvement).

Key implementation details:
- The UseFilter callback always returns false to avoid short-circuit
  evaluation, ensuring ALL address values are captured (critical for
  $or queries mixing exact and partial addresses)
- All address values (exact + partial) are pushed as OR conditions
  into the lateral, so exact addresses are not excluded
- The outer WHERE filter (via ResolveFilter) is preserved as-is

* fix(ledger): handle $not queries in lateral join address filter

The address filter pushed into LATERAL joins is a positive match,
which is incompatible with $not (negation). When $not is present,
the lateral would keep only matching rows, but the outer WHERE
would negate them → 0 results.

Add queryHasNegation() detection that builds the SQL and checks
for "not (" presence. When detected, the lateral optimization is
skipped and the query falls back to the original (correct) behavior.

* fix(ledger): use JSON inspection for $not detection and fix non-PIT guard

Address review feedback:
- Replace fragile SQL string matching ("not (") with JSON marshaling
  to detect "$not" operator in query builder AST
- Add missing !queryHasNegation guard in non-PIT path of
  aggregated_balances (was present in PIT path and both volumes paths)

* fix(ledger): skip lateral filter for $or queries and rename guard

The lateral join address filter optimization is also unsafe with $or
when mixing address filters with non-address conditions (e.g. balance).
The lateral excludes rows not matching the address, but those rows
might match another $or branch → missing results.

Rename queryHasNegation to canPushAddressFilterToLateral and extend
it to also detect $or via JSON AST inspection. The optimization now
only applies to pure $and queries (the common single-filter case).

* refactor(ledger): extract helpers to reduce duplication in lateral filter

Extract collectAddressFilters() and applyLateralAddressFilter() to
eliminate duplicated code:
- 10-line address collection block (was in 2 files) → 1-line call
- 3-line push-to-lateral guard (was in 4 locations) → 1-line call

No behavioral change.

* fix: handle \$in operator in collectAddressFilters and remove unused function

* style: fix import grouping and formatting for CI

Fix import grouping (stdlib, external, internal) and reformat the
collectAddressFilters interface parameter onto multiple lines to satisfy
the goimports/gofmt formatter used by the CI Dirty check.
…to charm.land (formancehq#1305)

The security update PR formancehq#1302 bumped pulumi-aws/sdk from v6 to v7, which
pulled in charmbracelet v2 modules that have migrated their module paths
from github.com/charmbracelet/* to charm.land/*. This broke go mod tidy.
…ring matching (formancehq#1297)

* fix(ledger): use AST analysis for lateral filter safety instead of string matching (formancehq#1296)

The previous implementation detected $not/$or by searching for literal
strings in the JSON output, which was too conservative: it disabled the
LATERAL join optimization even when $not/$or didn't affect address
filters (e.g. $not on metadata, single-item $or wrappers).

Replace with a proper JSON AST walker that tracks structural context:
- isNodeSafeForLateral: walks the tree tracking $not ancestry
- nodeContainsAddressFilter: checks if a subtree has address filters
- Only disables optimization when $not/$or actually impacts addresses

This fixes the production case where $and(address, $not(metadata))
was incorrectly skipping the GIN index optimization.

Also adds unit tests for canPushAddressFilterToLateral (17 cases)
and caches the safety check result per BuildDataset call.

* fix(ledger): reorder imports in utils_test.go to satisfy linter

Group stdlib imports separately from third-party, with testify before
formancehq per goimports ordering rules.
* chore: upgrade Go to 1.26 from pkgs-unstable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update go.mod to Go 1.26.0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate Speakeasy client v1.758.0

Upgrade Speakeasy SDK generator to v1.758.0 and regenerate the Go client.
Fix OpenAPI spec invalid $ref to parameter in schema context (sort field).
Update test files and provisioner for V2ExporterConfiguration -> V2CreateExporterRequest
rename, and V2RevertTransactionResponse field access in revert tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(openapi): fix invalid $ref in v2.yaml source for sort field

The sort field in V2QueryParams used $ref to a parameter object in a
schema context. Fix the source file (v2.yaml) to match the already
fixed merged openapi.yaml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: tidy tools/provisioner go.mod

Remove unused ericlagergren/decimal dependency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate API docs and tidy tools/generator

- Regenerate docs/api/README.md from updated OpenAPI spec
- Remove unused ericlagergren/decimal from tools/generator

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: bump go-libs/v4 to 56bca73d45aa (Go 1.26 fixes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(test): buffer export body before import to fix Go 1.26 timeout

Go 1.26 changed HTTP response body streaming behavior, causing the
ImportLogs request to hang when directly reusing an export response
body as a request body. Buffer with io.ReadAll first.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: bump go-libs/v4 to v4.1.3

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: fix import endpoint data race and SDK DrainBody issue

- controllers_logs_import.go: fix data race between goroutine reading
  stream channel and main goroutine setting stream=nil. Use separate
  select after close(stream) instead of nil-channel trick.
- pkg/client/v2.go: remove DrainBody call on ExportLogs 200 response.
  The response body IS the streaming payload; draining it discards the
  export data before callers can read it.
- Revert test buffering (body is now preserved by SDK).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(import): block on errChan after stream close instead of select

After EOF closes the stream, the import goroutine's result is
authoritative. A two-way select could pick r.Context().Done() even
when errChan is ready, losing the import result. Block directly on
errChan instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(openapi): specify ExportLogs response body as application/octet-stream

The export endpoint was missing content type on 200 response, causing
Speakeasy to generate DrainBody() which discards the streaming payload.
Also fix error response to use application/json with V2ErrorResponse.

Regenerate SDK client and update test to use response.Bytes field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate API docs after ExportLogs spec fix

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…rmance (formancehq#1311)

Without this index, listing transactions without filters causes a full
table sequential scan and on-disk sort of all rows before applying LIMIT.
On large ledgers (12M+ rows), this results in ~200s response times.

The descending index allows PostgreSQL to use an Index Scan Backward
for the default ORDER BY id DESC pagination, reducing response time
to milliseconds.
…ters responses (formancehq#1312)

V2ListPipelinesResponse and V2ListExportersResponse had a nested cursor structure
(cursor.cursor.data) instead of the expected flat one (cursor.data). The responses
wrapped the CursorResponse schema in an extra cursor property with a redundant allOf,
while the referenced schemas already contained the cursor structure.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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.

7 participants