Skip to content

fix(validator): validate spatial filter coordinates and parse WKT - #1029

Merged
abnegate merged 2 commits into
mainfrom
fix/spatial-filter-geometry
Oct 16, 2026
Merged

abnegate merged 2 commits into
mainfrom
fix/spatial-filter-geometry

Conversation

@abnegate

@abnegate abnegate commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

What

These are two staging 500s on spatial columns, A and B.

A. A spatial read filter with out-of-range coordinates reached the engine. intersects("pt", [[200,100]]) failed on MySQL with PDOException 3616/3617 in st_geomfromtext, which is a 500. Filter only checked that a spatial filter's value was an array. Ranges were checked on writes only.

  • Every filter on a spatial attribute is now checked the way the builders read its geometry: a point, a linestring or a polygon. This covers intersects/crosses/overlaps/touches/covers/spatialEquals and their negations, equal/notEqual, contains, and the distance* methods.
  • Each geometry is held to the rules for a stored value of its type: numeric coordinates, longitude −180..180 and latitude −90..90 inclusive, and closed rings of at least four points.
  • A distance query from WKT is parsed and validated the same way.
  • INF and NAN coordinates are refused. A comparison with NAN never fails, so NAN used to pass the range check.
  • As a backstop, the engines' GIS errors map to Exception\Query on reads: ER_GIS_INVALID_DATA (3037) on MySQL and MariaDB, and 3616/3617 on MySQL.

B. Writing malformed WKT gave a 500. Writing {"pt":"POINT(1 2"} failed with MySQL 3037 or a PostgreSQL parse error, and MariaDB stored the value and then warned Undefined array key 1 when reading it back. Spatial::isWktString() only checked the prefix with a regex.

  • WKT is now parsed properly, without a regex, into the coordinates of its type and validated like an array value.
  • A written value must be WKT of the attribute's own type. Malformed WKT, WKT of another type, and out-of-range or non-finite coordinates are refused by the Structure validator as Exception\Structure. Appwrite's create and update endpoints map that to 400 row_invalid_structure and do not catch Exception\Query.

The first commit is ported from the unpushed qfix-overflow work (502a028fa). The second adds the WKT parser, the write-side and non-finite refusals, and the tests.

Regression tests

Test Adapters origin/main this PR
SpatialTests::testSpatialFilterOutOfRangeOrMalformedIsAnInvalidQuery: (200,100); lon ±180.0001; lat ±90.0001; (1e308,1e308); INF; NAN; polygon at lat 91; line at lon 181; equal out of range; distance from a point or from WKT out of range; distance from malformed WKT; each on find() and count(); edges (±180, ±90, a polygon touching lat 90, a WKT distance) still work MySQL fail: PDOException 3616 Longitude 200.000000 is out of range pass
MariaDB, Postgres fail: filter accepted (no refusal) pass
SpatialTests::testSpatialWriteOfMalformedOrOutOfRangeValueIsAnInvalidStructure: POINT(1 2, POINT(200 0), POINT(0 NAN), POINT(1 2 3), WKT of another type, trailing text, out-of-range or non-finite arrays, a line at lon 181, a polygon at lat 91, an unclosed polygon and an unclosed ring, each on create and update; WKT at ±180/±90 and a polygon with a hole written and read back MySQL fail: PDOException 3037 Invalid GIS data pass
MariaDB fail: POINT(1 2 accepted and stored, then warning Undefined array key 1 at MariaDB.php:845 pass
Postgres fail: PDOException XX000 parse error - invalid geometry pass
Spatial tests SQLite, Mongo, Memory, Redis no spatial attributes (path absent) n/a
Unit\Validator\SpatialTest malformed WKT (15 cases), parsing, type rules, non-finite n/a fail ×18 pass
Unit\Validator\Query\FilterGeometryTest, Unit\EngineErrorMappingTest GIS cases n/a fail (no Geometry validator, unmapped errors) pass

The fix commit alone without the WKT parser was also checked: MySQL still accepted a WKT distance from (200,100), and writes of POINT(1 2 came back as Exception\Query (a 500 in Appwrite) or were stored on MariaDB. That is why the second commit exists.

Run locally against Docker: the full adapter suites for MySQL, MariaDB, Postgres, SQLite, MongoDB, Memory and Redis pass, as do the full unit suite, Pint and PHPStan.

API repros (Appwrite)

  • GET /v1/tablesdb/{db}/tables/{t}/rows?queries[]={"method":"intersects","attribute":"pt","values":[[200,100]]} → 400 general_query_invalid (was 500). The same holds for [[180.0001,0]], [[1e308,1e308]], a polygon at latitude 91 on a polygon column, a line at longitude 181 with crosses, and {"method":"distanceLessThan","attribute":"pt","values":[[[200,100],10,false]]}.
  • POST /v1/tablesdb/{db}/tables/{t}/rows with body {"rowId":"unique()","data":{"pt":"POINT(1 2"}} → 400 row_invalid_structure (was 500). PATCH .../rows/{rowId} with {"data":{"pt":"POINT(200 0)"}} → 400 row_invalid_structure. The Collections API returns document_invalid_structure.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Spatial filters and stored geometry values now reject malformed shapes, mismatched geometry types, non-finite coordinates, and coordinates outside valid bounds.
    • WKT geometry values are checked for valid syntax and coordinates, including when used in distance queries.
    • Invalid spatial values reported by MariaDB and MySQL now produce a consistent “Invalid spatial value” query error.

abnegate and others added 2 commits October 16, 2026 16:31
Filter only asked a spatial filter's value to be an array. A polygon nested
too deep or a point out of range reached the engine and failed there, which
MySQL raised as error 3037, 3616 or 3617 and nothing mapped, and a point of
strings was cast to (0, 0) by the builder and silently matched against the
origin. The Spatial validator of stored values read an object's coordinates
by position and raised an undefined key warning. 7.4.2 checks both the same
way.

A spatial filter's geometry, and a distance query's unless it is WKT, is now
read in the shape the builders read it, a point, a linestring or a polygon,
and held to the rules of a stored value of that type: numeric coordinates
in longitude and latitude range, and closed rings of at least four points.
Spatial refuses a coordinate, point or ring given as an object, and the GIS
errors map to Exception\Query: ER_GIS_INVALID_DATA on both engines, and
MySQL's longitude and latitude range errors on MySQL only, since MariaDB
reads such a geometry as NULL.

Two E2E queries leaned on the loose check: a linestring wrapped one level
too deep, which the builder read as a polygon of one two-point ring, and a
point at latitude 100. They now pass well-formed geometry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… finite

The Spatial validator took any string that began with POINT(, LINESTRING(
or POLYGON( as WKT, so a written value such as `POINT(1 2` or
`POINT(200 0)` reached the engine: MySQL raised error 3037, a server
error, and MariaDB stored it and warned on the read back. A distance
query from WKT was checked the same way, so its coordinates were never
held to their range. A coordinate of INF or NAN passed the range check,
which no comparison with NAN can fail.

WKT is now parsed, without a regex, into the coordinates of its type and
held to the rules of a value of that type: a point of two numbers, closed
rings of at least four points, coordinates in longitude and latitude
range. A written value must be WKT of the attribute's own type, and is
refused as an invalid structure otherwise, so Appwrite answers 400 rather
than 500. A distance query from WKT is validated as its geometry is. A
coordinate that is not finite is refused.

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

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: utopia-php/database/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e83f6f81-38c5-4711-bfc5-0ec822ed3735
📥 Commits

Reviewing files that changed from the base of the PR and between 09a4729 and b052682.

📒 Files selected for processing: 10
  • src/Database/Adapter/MariaDB.php
  • src/Database/Adapter/MySQL.php
  • src/Database/Validator/Geometry.php
  • src/Database/Validator/Query/Filter.php
  • src/Database/Validator/Spatial.php
  • tests/e2e/Adapter/Scopes/RelationshipTests.php
  • tests/e2e/Adapter/Scopes/SpatialTests.php
  • tests/unit/EngineErrorMappingTest.php
  • tests/unit/Validator/Query/FilterGeometryTest.php
  • tests/unit/Validator/SpatialTest.php

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

Spatial inputs now receive stricter coordinate and WKT validation in query filters and spatial writes. The database adapters also map selected MariaDB and MySQL spatial errors to QueryException.

Changes: Spatial validation and errors

Layer / File(s) Summary
Parse and validate spatial values
src/Database/Validator/Geometry.php, src/Database/Validator/Spatial.php, tests/unit/Validator/SpatialTest.php, tests/unit/Validator/Query/FilterGeometryTest.php
The new Geometry validator classifies coordinate arrays and delegates geometry checks to Spatial. Spatial now parses supported WKT, requires list-shaped coordinates, and rejects malformed WKT, type mismatches, and non-finite coordinates. Unit tests cover these cases.
Apply spatial validation to query filters
src/Database/Validator/Query/Filter.php, tests/unit/Validator/Query/FilterGeometryTest.php, tests/e2e/Adapter/Scopes/SpatialTests.php, tests/e2e/Adapter/Scopes/RelationshipTests.php
Filter validates spatial query values and distance-query geometry. Unit and end-to-end tests cover valid and invalid filters, spatial writes, and relationship-query inputs.
Map spatial database errors
src/Database/Adapter/MariaDB.php, src/Database/Adapter/MySQL.php, tests/unit/EngineErrorMappingTest.php
MariaDB and MySQL declare spatial error codes. Tests check that the listed errors map to QueryException with the message Invalid spatial value.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Filter
  participant Geometry
  participant Spatial
  Filter->>Geometry: validate coordinate array
  Geometry->>Spatial: validate inferred geometry type
  Filter->>Spatial: parse and validate WKT
  Spatial-->>Filter: parsed geometry or validation result
Loading

Suggested reviewers: fogelito

Merge Risk: ⚪ Minimal · up to b0526

No issue identified here requires a change before merging; proceed with normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 48.89% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 45 functions across 10 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed 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 changes: spatial filter coordinate validation and WKT parsing.
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.
  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@abnegate
abnegate merged commit b1ab94f into main Oct 16, 2026
24 checks passed
@abnegate
abnegate deleted the fix/spatial-filter-geometry branch October 16, 2026 04:02
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