Skip to content

fix(spp_api_v2_gis): repair aborted-transaction fallbacks and geofence/incident scope actions - #375

Open
jeremi wants to merge 8 commits into
19.0from
fix/gis-spatial-params-and-geofence-scope
Open

fix(spp_api_v2_gis): repair aborted-transaction fallbacks and geofence/incident scope actions#375
jeremi wants to merge 8 commits into
19.0from
fix/gis-spatial-params-and-geofence-scope

Conversation

@jeremi

@jeremi jeremi commented Jul 27, 2026

Copy link
Copy Markdown
Member

Fixes five independent defects in spp_api_v2_gis's spatial-query and access-scope code, all found while auditing the module for the aborted-transaction bug class.

query_statistics, query_statistics_batch and query_proximity each retry a failed coordinate-based lookup with an area-based fallback on the same database cursor. Three of the four fallback paths ran outside any savepoint, so a genuine database error in the coordinate attempt left the transaction aborted, and the fallback (plus every later query on that cursor) failed with InFailedSqlTransaction instead of degrading gracefully.

A fourth, pre-existing bug bound SQL parameters out of order when an is_group filter was present, feeding a GeoJSON string into a numeric column position.

Separately, routers/geofence.py gated create/delete on has_scope("gis", "geofence"), but geofence was never added as a selectable client scope action, so no client could ever be granted that scope short of gis:all. The same gap existed for incident.

11 files changed, 647 insertions(+), 34 deletions(-) versus 19.0.

What it removes, renames, or breaks

Nothing. All fixes are additive: a savepoint around an existing query, a parameter-order correction, and two new selectable scope-action values.

The manifest bumps 19.0.2.0.019.0.2.0.1 only so Odoo re-registers the widened Selection for scope actions. No field is added, removed, or retyped.

Two related items are explicitly out of scope here and handled elsewhere: _compute_statistics still has its own unprotected raw-SQL sites, and a broader inventory found roughly 20 further aborted-transaction-shaped sites across the codebase. Please do not read this branch as having closed the bug class, only its own four sites.

Migrations

Manifest bump only, no migration script. selection_add widens the allowed set of action values; every stored value stays valid and there is no SQL constraint on the column, so the bump exists solely to trigger re-registration on -u.

Known gap: nobody has exercised this specific 19.0.2.0.019.0.2.0.1 upgrade end to end against a database that already holds spp.api.client.scope rows. The "no migration needed" conclusion rests on the reasoning above, not on an executed upgrade.

⚠️ Merge-order constraint

This PR must merge before #281, or spp_api_v2_gis regresses in a way that cannot be recovered.

ref spp_api_v2_gis version
19.0 19.0.2.0.0
this PR 19.0.2.0.1
#281 19.0.3.0.0

If any part of #281 merges first, this PR then drives the manifest backwards from 19.0.3.x to 19.0.2.0.1. Odoo still performs the upgrade, because a lower version is still a different version, but the dispatch window installed < V <= manifest becomes empty for every conceivable V from that point on, so no migration for this module can ever run again until some future bump climbs back above 19.0.3.1.0. Nothing warns, the module keeps working, and the next schema change it ships silently does not migrate. That is precisely the failure that forced the #76 revert.

Merging in the documented order costs nothing: 2.0.0 → 2.0.1 → 3.0.0, monotonic throughout. If for any reason this PR ends up merging second, re-bump its manifest above whatever is on 19.0 at that moment, rather than merging as-is.

Second, semantic constraint: #281 independently fixed the same parameter-order bug but carries zero savepoints. A merge that resolves in #281's favour silently reintroduces both aborted-transaction paths. Both sides look correct in isolation and the conflict is semantic rather than textual, so this one needs a human check at merge time. The scope-action fix is unaffected; models/api_client_scope.py is byte-identical across the branches.

Verification

./spp test spp_api_v2_gis on this branch at 0a11b276, read from the run log rather than the console summary:

0 failed, 0 error(s) of 209 tests

12 of those 209 skipped, and they are worth naming rather than hiding behind the green:

Skipped test Reason given
TestCatalogService.test_get_catalog_report_driven_data_layer No geo field available for data layer creation
TestCatalogService.test_get_catalog_with_data_layers No data layer available (spp.area polygon field not found)
TestLayersService.test_build_layer_styling No data layer available
TestLayersService.test_fetch_layer_features_limit No data layer available
TestLayersService.test_fetch_layer_features_without_geometry No data layer available
TestLayersService.test_get_data_layer_as_geojson No data layer available (spp.area polygon field not found)
TestOGCService.test_get_collection_by_layer_id No data layer available
TestOGCService.test_get_collections_contains_data_layers No data layer available (spp.area polygon field not found)
TestOGCService.test_model_driven_data_layer_collection_has_no_qml_link No data layer available
TestOGCService.test_report_driven_data_layer_collection_has_qml_link No geo field available for data layer creation
TestOGCHTTP.test_qml_for_model_driven_data_layer_returns_404 No geo field available for model-driven layer
TestOGCHTTP.test_qml_for_report_driven_data_layer_returns_200 No geo field available for data layer creation

These skips are pre-existing on 19.0 and not introduced by this branch, but they are the bad kind: each one skips itself precisely when the geo field it exists to exercise is missing, so the suite goes green in exactly the situation the test was written to catch. Converting this family from skipTest to hard assertions is tracked separately and is expected to land after #281, which is what makes the geo fields reliably present. Flagging it here so nobody reads 0 failed on this module as full coverage of the data-layer paths.

Historical context for the fixes themselves: the coordinate savepoint fix took the query-parameter-order test class from 2 failed, 9 error(s) of 14 tests to 0 failed, 0 error(s) of 4 (the other 10 were unaffected by this bug), and the full suite from 2 failed, 10 error(s) of 205 tests to clean.

All four savepoints use cr.savepoint(flush=False). This is a deliberate deviation from the default flushing behaviour, with the rationale inline at each site: it keeps unrelated pending ORM writes out of the rollback. Worth a reviewer's second look, because it means only the raw SQL inside the with block is protected, not any not-yet-flushed ORM writes made earlier in the same request.

Assumptions

  • The geofence and incident scope actions were added as part of this fix rather than split into a separate PR, on the grounds that the gate without the selectable value is not shippable on its own. Flagged here in case you would rather they had been left alone.
  • All 7 commits carry Signed-off-by:.

jeremi added 7 commits July 27, 2026 15:10
The filter placeholders live inside {where_clause}, which precedes the
geometry placeholder, but the params list was built geometry-first and
then rebuilt into the identical list. With an is_group filter the GeoJSON
string was bound to p.is_group and PostgreSQL rejected the statement.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
query_statistics catches a failing coordinate query and retries with the
area query on the same cursor. Without a savepoint the first failure had
already aborted the transaction, so the fallback raised
InFailedSqlTransaction and the endpoint returned 500 instead of
degrading.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
routers/geofence.py gates create and delete on
has_scope("gis", "geofence"), but geofence was not a selectable action,
so only clients holding gis:all could reach those endpoints and no
client could ever be granted the intended scope.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
No migration script: selection_add only widens the allowed set of
action values, every stored value stays valid, the column stays varchar
and there is no SQL constraint on it. The version bump is what makes
Odoo re-register the selection on update.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
query_proximity has the same defect as query_statistics: it catches a
failing coordinate query and retries with the area query on the same
cursor, but the first failure had already aborted the transaction, so
_create_proximity_temp_table raised InFailedSqlTransaction and the
endpoint returned 500 instead of degrading.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
query_statistics wrapped its coordinate attempt in a savepoint but left the
area fallback's raw SQL unguarded. A genuine database error there (not the
ValueError path used when res.partner.coordinates is absent) aborted the
whole transaction, and since opening a savepoint itself requires a live
transaction, every later geometry in the same query_statistics_batch call
failed too. One bad geometry degraded the entire batch response to
total_count: 0, query_method: "error" for everything after it, with nothing
indicating why.

This completes the savepoint coverage the branch already started for the
coordinate-query fallbacks in query_statistics and query_proximity.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
query_proximity wraps its coordinate attempt in a savepoint but left the
area fallback's raw SQL unguarded. A genuine database error there aborted
the whole transaction, leaving every later query on the same cursor
unusable, since even opening a new savepoint requires a live transaction.

This completes the savepoint coverage started in 2bfefcb4 (coordinate
query) and 9ff662f1 (query_statistics's own area fallback), applying the
same fix to query_proximity's area fallback.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.24%. Comparing base (1caf794) to head (c9d6923).
⚠️ Report is 3 commits behind head on 19.0.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #375      +/-   ##
==========================================
- Coverage   74.28%   69.24%   -5.04%     
==========================================
  Files         372      133     -239     
  Lines       25385    12178   -13207     
==========================================
- Hits        18857     8433   -10424     
+ Misses       6528     3745    -2783     
Flag Coverage Δ
spp_analytics ?
spp_api_v2_cycles ?
spp_api_v2_entitlements ?
spp_api_v2_gis 72.99% <100.00%> (+1.47%) ⬆️
spp_api_v2_programs ?
spp_api_v2_simulation ?
spp_audit_programs ?
spp_case_demo ?
spp_case_entitlements ?
spp_case_programs ?
spp_cr_type_assign_program ?
spp_dci_compliance ?
spp_dci_demo 94.28% <ø> (+0.88%) ⬆️
spp_dci_server_social ?
spp_demo ?
spp_demo_phl_luzon ?
spp_drims ?
spp_drims_sl ?
spp_drims_sl_demo ?
spp_farmer_registry_demo ?
spp_gis_report ?
spp_grm_demo ?
spp_indicator ?
spp_indicator_studio ?
spp_metric_service ?
spp_mis_demo_v2 70.99% <ø> (+0.03%) ⬆️
spp_programs 65.27% <ø> (+<0.01%) ⬆️
spp_registry 86.94% <ø> (+0.10%) ⬆️
spp_simulation ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_api_v2_gis/models/api_client_scope.py 100.00% <100.00%> (ø)
spp_api_v2_gis/services/spatial_query_service.py 84.40% <100.00%> (+9.17%) ⬆️

... and 239 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The committed README.rst and index.html were generated on a developer
machine whose docutils/pandoc renders RST tables one column wider than
CI's, so pre-commit rewrote both files on every run and failed the job.

Take CI's output verbatim: the OGC endpoints table's Description column
is 29 characters, matching its widest cell ("QGIS style file
(extension)", 27) plus padding.

The generator is not reproducible across machines even though
.pre-commit-config.yaml pins docutils and markdown-it-py, because pandoc
is a system binary the pin cannot reach. Tracked separately.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c9d6923846

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

super().setUpClass()

# Mirrors the geometry(Point, 4326) column created by GeoPointField.
cls.env.cr.execute("ALTER TABLE res_partner ADD COLUMN coordinates geometry(Point, 4326)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Guard the test-only coordinates column creation

When these tests run in a database where spp_registrant_gis is already installed, this unconditional ALTER TABLE raises PostgreSQL DuplicateColumn because that module defines the same res_partner.coordinates column. This occurs in combined installations such as spp_mis_demo_v2, which depends on both modules, and prevents the GIS API tests from running; check for the field/column first or use ADD COLUMN IF NOT EXISTS.

AGENTS.md reference: AGENTS.md:L107-L115

Useful? React with 👍 / 👎.

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