Skip to content

Native PostGIS Support for Prisma#5797

Open
lh0x00 wants to merge 7 commits into
prisma:mainfrom
lh0x00:feature/support-postgis-features
Open

Native PostGIS Support for Prisma#5797
lh0x00 wants to merge 7 commits into
prisma:mainfrom
lh0x00:feature/support-postgis-features

Conversation

@lh0x00
Copy link
Copy Markdown

@lh0x00 lh0x00 commented Mar 20, 2026

See full features at prisma/prisma#29365.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 20, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 20, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This pull request adds comprehensive PostGIS geometry support to Prisma, extending the schema language with Geometry and Geography scalar types. The implementation spans schema parsing, query compilation, SQL generation, and database introspection, enabling geometry-aware filtering (near, within, intersects), distance-based ordering, and native type mapping across PostgreSQL.

Changes

PostGIS Geometry Feature

Layer / File(s) Summary
Geometry type definitions and PSL parsing
psl/parser-database/src/types.rs, psl/schema-ast/src/*
Define GeometrySubtype, PostgisSpatialKind, and GeometrySpec types; extend ScalarType enum with Geometry and Geography variants; add Prisma schema grammar rules to parse Geometry(subtype[, srid]) syntax.
Postgres connector PostGIS native type support
psl/psl-core/src/builtin_connectors/postgres_datamodel_connector*
Add PostgresType::Postgis variant with PostgisNativeType and GeometryNativeArgs; implement native type parsing, SRID validation (0..=999_999), and conversion to GeometrySpec; wire lazy-loaded @db.Geometry / @db.Geography constructors.
Connector capability and geometry spec hook
psl/psl-core/src/datamodel_connector*, psl/psl-core/src/datamodel_connector/capabilities.rs
Expand ConnectorCapability bitflags from u64 to u128; add PostgisGeometry capability; introduce Connector::geometry_spec_for_native_type() hook for extracting geometry specs from native types.
Schema validation and default handling
psl/psl-core/src/validate/validation_pipeline/validations/fields.rs, psl/parser-database/src/attributes/default.rs
Add geometry field validation requiring PostgisGeometry capability for both model/view and composite fields; reject non-dbgenerated defaults on geometry scalars.
Query schema type propagation
query-compiler/query-structure/src/field/*, query-compiler/schema/src/query_schema.rs, query-compiler/schema/src/build/*
Introduce TypeIdentifier::Geometry(GeometrySpec) variant; extend ScalarFieldType with is_geometry() and postgis_spatial_kind(); add ScalarType::Geometry to query schema; wire geometry mappings in input/output type builders.
GeoJSON geometry parsing
query-compiler/query-structure/src/filter/geojson.rs
Implement GeoCoord with finite-coordinate validation; define GeoJsonGeometry enum (Point, LineString, Polygon, Multi-variants); parse and validate GeoJSON from serde_json::Value with automatic polygon ring closure and WKT generation.
Geometry filter and order-by types
query-compiler/query-structure/src/filter/geometry.rs, query-compiler/query-structure/src/order_by.rs
Define GeometryFilter and GeometryFilterCondition (Near/Within/Intersects) with custom PartialEq and Hash using f64::to_bits() for stable comparisons; define OrderByGeometry with distance-based sorting.
Query document parsing and coercion
query-compiler/core/src/query_document/parser.rs
Refactor parser to borrow ScalarType instead of moving; add Geometry(_) compatibility for Bytes and String input values; coerce Json to Bytes for geometry targets.
Geometry filter extraction
query-compiler/core/src/query_graph_builder/extractors/filters/scalar.rs
Extend ScalarFilterParser to parse near, within, intersects geometry operators; validate coordinate shapes (2-element arrays for near/distance, closed polygon rings for within); parse GeoJSON for intersects.
Geometry order-by extraction
query-compiler/core/src/query_graph_builder/extractors/query_arguments.rs
Add geometry-specific branch for distanceFrom ordering; parse point, direction (asc/desc), optional SRID; reject cursor-based pagination combined with geometry orderBy.
Schema I/O type mapping and filter generation
query-compiler/schema/src/build/input_types/*, query-compiler/schema/src/build/output_types/field.rs
Wire TypeIdentifier::Geometry into scalar input/output type mappers; generate geometry-specific filter input objects for near/within/intersects; add distance-from input for ordering.
SQL filter visitor: geometry predicates
query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs
Implement visit_geometry_filter() to render geometry conditions as PostGIS predicates: ST_DWithin for near, ST_Within for within, ST_Intersects for intersects; handle geography casting and SRID resolution.
SQL ordering: distance-based sorting
query-compiler/query-builders/sql-query-builder/src/ordering.rs
Implement build_order_geometry() to construct ST_Distance expressions between stored geometry and user-provided point; apply SRID chain (explicit > field > default 4326); cast to geography.
Quaint AST: PostGIS function support
quaint/src/ast/function.rs, quaint/src/ast/function/postgis.rs, quaint/src/visitor.rs
Add PostgisFunction AST wrapper and helper functions for PostGIS SQL: st_dwithin, st_within, st_intersects, st_distance, st_geom_from_text, st_make_point, st_set_srid, geography_cast.
Column type rendering and schema differ
schema-engine/connectors/sql-schema-connector/src/flavour/postgres/renderer.rs, schema-engine/connectors/sql-schema-connector/src/flavour/postgres/schema_differ.rs
Add ColumnTypeFamily::Geometry(GeometrySpec) variant with rendering; generate PostgreSQL column type strings; check arity changes and subtype equality in schema diffs.
PostgreSQL schema describer: geometry introspection
schema-engine/sql-schema-describer/src/postgres.rs, schema-engine/sql-schema-describer/src/lib.rs
Implement regex-based parse_postgis_spatial() to extract geometry/geography, subtype, and optional SRID from PostgreSQL format_type() output; map to ColumnTypeFamily::Geometry with native type.
Column defaults and database-specific handling
schema-engine/sql-schema-describer/src/postgres/default.rs, schema-engine/sql-schema-describer/src/mysql.rs, schema-engine/sql-schema-describer/src/sqlite.rs, schema-engine/sql-schema-describer/src/mssql.rs
Route geometry column defaults through unsupported parsing paths; include geometry columns in default-value handling for MySQL, SQLite, and MSSQL (db_generated).
SQL schema calculator: geometry column creation
schema-engine/connectors/sql-schema-connector/src/sql_schema_calculator.rs
Implement push_column_for_geometry_field() to create SQL geometry columns with proper type family, native type resolution, and default conversion.
DMMF rendering
query-compiler/dmmf/src/*
Thread connector through DMMF builders; add FieldScalarType::Geometry variant; render geometry spec type names in output type references.
Test schemas and JSON fixtures
psl/psl/tests/validation/postgres/postgis*.prisma, query-compiler/query-compiler/tests/data/geometry-*.json
Add validation schemas for geometry keyword usage and native type mismatch detection; add JSON query fixtures covering near/within/intersects filters, distance ordering, and combined conditions.
Integration tests
query-compiler/core-tests/tests/geometry*.rs, query-compiler/dmmf/src/tests/tests.rs
Add query-graph compilation tests for geometry filters and ordering; add DMMF field metadata and schema JSON validation tests.
Database introspection and migration tests
schema-engine/sql-introspection-tests/tests/postgres/postgis_geometry.rs, schema-engine/sql-migration-tests/tests/migrations/postgres/postgis_geometry.rs
Add Postgres introspection tests for geometry/geography columns with typmod/SRID variants; add migration tests for schema round-trips and SRID updates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Copy link
Copy Markdown

@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: 14

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@psl/parser-database/src/types.rs`:
- Around line 253-275: The postgres_sql_type method on GeometrySpec currently
collapses omitted SRID and SRID 0 by using self.srid.unwrap_or(0); change
postgres_sql_type so it preserves None by emitting "geometry(subtype)" or
"geography(subtype)" when srid is None and only appends ",{srid}" when self.srid
is Some(value); locate the srid handling in GeometrySpec::postgres_sql_type
(references: GeometrySpec, postgres_sql_type, srid, unwrap_or(0)) and update the
formatting logic to conditionally include the ",{srid}" piece instead of
unwrapping to 0.

In `@psl/psl-core/src/builtin_connectors/postgres_datamodel_connector.rs`:
- Around line 86-88: The helper geometry_sql_column_type simply forwards to
spec.postgres_sql_type() and should be inlined: remove or stop using
geometry_sql_column_type and replace its callers with direct calls to
spec.postgres_sql_type() (i.e., call the GeometrySpec method directly wherever
geometry_sql_column_type(...) is used), ensuring imports/signatures remain
correct and no other logic is lost.

In `@psl/schema-ast/src/parser/parse_types.rs`:
- Around line 78-86: The SRID parsing currently accepts any i32 (via
raw.parse::<i32>()), allowing negative values; after successful parse in the
match arm for Ok(v) add a range check that returns a
DatamodelError::new_validation_error (using the same (file_id,
srid_pair.as_span()).into() span) if v is negative or > 999_999, and only return
Some(v) when 0 <= v <= 999_999; update the error message to indicate an
out-of-range SRID such as "Invalid SRID: expected a value between 0 and 999999."

In `@quaint/.github/workflows/test.yml`:
- Around line 58-63: The workflow currently contains hardcoded DSNs for
TEST_MYSQL, TEST_MYSQL8, TEST_MYSQL_MARIADB, TEST_PSQL, TEST_MSSQL, and
TEST_CRDB; replace these inline credentials with references to CI secrets or
repo/organization vars (e.g. use ${{ secrets.TEST_MYSQL }} / ${{ vars.TEST_MYSQL
}}), or if these values are intentionally test-only and non-sensitive, add a
scoped secret-scan/Checkov skip annotation with a brief justification tied to
the specific env keys to suppress the finding; update the env entries for
TEST_MYSQL, TEST_MYSQL8, TEST_MYSQL_MARIADB, TEST_PSQL, TEST_MSSQL, and
TEST_CRDB accordingly.
- Around line 16-18: The workflow currently uses the deprecated action reference
"actions-rs/toolchain@v1"; replace it with the maintained
"dtolnay/rust-toolchain@stable" (or alternatively
"actions-rust-lang/setup-rust-toolchain@v1") and update the inputs accordingly:
remove or adapt unsupported keys like components and override, keep toolchain:
stable (or use the new input name if different), and ensure clippy is installed
via the new action's documented inputs or a separate step (rustup component add
clippy) so CI behavior remains the same.

In `@query-compiler/core-tests/tests/geometry_find_many_graph_builds.rs`:
- Around line 13-16: The test's generator block declares previewFeatures =
["relationJoins"] which is unrelated to geometry query compilation; remove
"relationJoins" from the previewFeatures array in the generator client
declaration (the `generator client { ... previewFeatures = [...] }` block) to
keep the test focused unless some other part of this test explicitly depends on
relationJoins.

In `@query-compiler/core/src/query_document/parser.rs`:
- Around line 421-423: The code accepts PrismaValue::Json for
ScalarType::Geometry in parser.rs (the match arm that converts Json to Bytes)
but prisma_value_type_matches_scalar_type() still rejects PrismaValueType::Json
for Geometry, creating inconsistent behavior; update
prisma_value_type_matches_scalar_type() to treat PrismaValueType::Json as valid
for ScalarType::Geometry (alongside Bytes and String) so placeholder and inlined
paths agree, or alternatively remove the Json-to-Bytes coercion in the parser to
preserve the original policy—pick one consistent approach and apply it to the
function prisma_value_type_matches_scalar_type() and the parser.rs match arms
(PrismaValue::Json handling or its removal) so both places use the same Geometry
JSON policy.

In `@query-compiler/dmmf/src/ast_builders/datamodel_ast_builder.rs`:
- Around line 13-15: The helper function geometry_dmmf_field_type merely
forwards to spec.postgres_sql_type(); inline it by replacing all calls to
geometry_dmmf_field_type(spec) with spec.postgres_sql_type() and then remove the
geometry_dmmf_field_type function definition to avoid unnecessary indirection
(look for calls to geometry_dmmf_field_type and the GeometrySpec type/method to
update).

In `@query-compiler/dmmf/src/tests/tests.rs`:
- Around line 44-51: Add test coverage for the optional "path" field similar to
the existing "position" check: locate the field via the same
fields.iter().find(...) pattern (use a new variable path_field), extract its
outputType into out_path, and assert that out_path.get("type").and_then(|t|
t.as_str()) equals Some("geometry(LineString,0)"); this mirrors the existing
pos_field/out_pos assertions and ensures optional geometry fields are validated.

In
`@query-compiler/schema/src/build/input_types/fields/data_input_mapper/update.rs`:
- Around line 48-50: The update uses the wrong envelope identifier for geometry:
in the match arm for TypeIdentifier::Geometry(...) you call
InputType::object(update_operations_object_type(ctx, "Bytes", sf.clone(),
false)) which reuses the "Bytes" prefix and collides with bytes field update
types; change the prefix to a geometry-specific name (e.g., "Geometry") so the
call becomes update_operations_object_type(ctx, "Geometry", sf.clone(), false)
to produce a distinct GeometryFieldUpdateOperationsInput; ensure this aligns
with map_scalar_input_type() which maps geometry to ScalarType::Geometry so the
update operations identifier is unique.

In `@schema-engine/sql-introspection-tests/tests/postgres/postgis_geometry.rs`:
- Line 25: Replace the weak compound assertion in the postgis_geometry.rs test
that uses schema.contains("path") && schema.contains("Geometry(LineString)")
with a single assertion that verifies the field name and its type appear
together; for example, use a Regex to assert that the string "path" is followed
by "Geometry(LineString)" (and nullable marker if applicable) in the same
declaration — i.e., remove the current assert!(schema.contains(... && ...)) and
instead create a Regex (via regex::Regex::new) that matches something like
r"path\s+Geometry\(LineString\)\??" and assert that re.is_match(&schema),
referencing the existing schema variable.

In
`@schema-engine/sql-migration-tests/tests/migrations/postgres/postgis_geometry.rs`:
- Around line 86-87: The two brittle string assertions using the introspected
variable (assert!(introspected.contains("Geometry(Point, 4326)")) and
assert!(introspected.contains("Geometry(LineString, 4326)"))) should be replaced
with more robust checks: either parse the introspected schema into a structured
representation and assert the geometry type and SRID for the relevant field(s),
or use flexible pattern matching (e.g., regex) that tolerates whitespace/format
variations (e.g., match /Geometry\s*\(\s*Point\s*,\s*4326\s*\)/). Update the
test to locate the Geometry definitions by name in the parsed output (or by
applying the regex) and assert the type and SRID rather than relying on exact
string spacing.

In `@schema-engine/sql-schema-describer/src/postgres.rs`:
- Around line 1566-1574: The current SRID parsing in the RE_TWO capture block
silently defaults to 0 on parse failure; change it so that if caps.get(3) exists
but parse fails you log a warning and do not silently return 0. Concretely,
inside the RE_TWO handling (the block using trimmed, caps, map_geometry_subtype
and constructing GeometrySpec), replace the single .and_then(...).unwrap_or(0)
with logic that checks caps.get(3): if None -> leave srid as None; if Some(text)
-> attempt to parse to i32 and on Ok(v) set srid = Some(v), on Err(e) emit a
warning (using the crate’s logging/tracing facility) and set srid = None (or
otherwise avoid using 0). Ensure GeometrySpec is constructed with that
Option<i32> srid and keep subtype mapping via map_geometry_subtype unchanged.

In `@schema-engine/sql-schema-describer/src/postgres/default.rs`:
- Around line 104-106: Consolidate the match arms that route to
parse_unsupported by adding ColumnTypeFamily::Geometry(_) into the existing
pattern so all unsupported families share a single arm; update the match over
ColumnTypeFamily (where parse_unsupported is referenced) to use a combined
pattern like ColumnTypeFamily::Udt(_) | ColumnTypeFamily::Unsupported(_) |
ColumnTypeFamily::Uuid | ColumnTypeFamily::Geometry(_) => &parse_unsupported so
the code is cleaner and consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 94f599c0-c079-4b00-ae47-62a3a7d854f7

📥 Commits

Reviewing files that changed from the base of the PR and between 280c870 and 6efd1f0.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-find-many.json.snap is excluded by !**/*.snap
📒 Files selected for processing (53)
  • psl/parser-database/Cargo.toml
  • psl/parser-database/src/attributes/default.rs
  • psl/parser-database/src/lib.rs
  • psl/parser-database/src/types.rs
  • psl/psl-core/src/builtin_connectors/capabilities_support.rs
  • psl/psl-core/src/builtin_connectors/postgres_datamodel_connector.rs
  • psl/psl-core/src/datamodel_connector/capabilities.rs
  • psl/psl-core/src/validate/validation_pipeline/validations.rs
  • psl/psl-core/src/validate/validation_pipeline/validations/fields.rs
  • psl/schema-ast/src/ast.rs
  • psl/schema-ast/src/ast/field.rs
  • psl/schema-ast/src/parser/datamodel.pest
  • psl/schema-ast/src/parser/parse_types.rs
  • quaint/.github/workflows/test.yml
  • query-compiler/core-tests/Cargo.toml
  • query-compiler/core-tests/tests/geometry_find_many_graph_builds.rs
  • query-compiler/core/src/constants.rs
  • query-compiler/core/src/query_document/parser.rs
  • query-compiler/dmmf/Cargo.toml
  • query-compiler/dmmf/src/ast_builders/datamodel_ast_builder.rs
  • query-compiler/dmmf/src/ast_builders/schema_ast_builder/type_renderer.rs
  • query-compiler/dmmf/src/tests/tests.rs
  • query-compiler/query-builders/sql-query-builder/src/convert.rs
  • query-compiler/query-builders/sql-query-builder/src/model_extensions/scalar_field.rs
  • query-compiler/query-compiler/src/data_mapper.rs
  • query-compiler/query-compiler/tests/data/geometry-find-many.json
  • query-compiler/query-compiler/tests/data/schema.prisma
  • query-compiler/query-structure/src/field/mod.rs
  • query-compiler/query-structure/src/field/scalar.rs
  • query-compiler/query-structure/src/prisma_value_ext.rs
  • query-compiler/request-handlers/src/protocols/json/protocol_adapter.rs
  • query-compiler/schema/src/build/input_types/fields/data_input_mapper/update.rs
  • query-compiler/schema/src/build/input_types/fields/field_filter_types.rs
  • query-compiler/schema/src/build/input_types/mod.rs
  • query-compiler/schema/src/build/output_types/field.rs
  • query-compiler/schema/src/output_types.rs
  • query-compiler/schema/src/query_schema.rs
  • schema-engine/connectors/sql-schema-connector/src/flavour/postgres/renderer.rs
  • schema-engine/connectors/sql-schema-connector/src/flavour/postgres/schema_differ.rs
  • schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/renderer.rs
  • schema-engine/connectors/sql-schema-connector/src/introspection/introspection_pair/scalar_field.rs
  • schema-engine/connectors/sql-schema-connector/src/sql_schema_calculator.rs
  • schema-engine/sql-introspection-tests/tests/postgres/mod.rs
  • schema-engine/sql-introspection-tests/tests/postgres/postgis_geometry.rs
  • schema-engine/sql-migration-tests/tests/migrations/postgres.rs
  • schema-engine/sql-migration-tests/tests/migrations/postgres/postgis_geometry.rs
  • schema-engine/sql-schema-describer/src/lib.rs
  • schema-engine/sql-schema-describer/src/mssql.rs
  • schema-engine/sql-schema-describer/src/mysql.rs
  • schema-engine/sql-schema-describer/src/postgres.rs
  • schema-engine/sql-schema-describer/src/postgres/default.rs
  • schema-engine/sql-schema-describer/src/postgres/default/c_style_scalar_lists.rs
  • schema-engine/sql-schema-describer/src/sqlite.rs

Comment thread psl/parser-database/src/types.rs
Comment thread psl/psl-core/src/builtin_connectors/postgres_datamodel_connector.rs Outdated
Comment thread psl/schema-ast/src/parser/parse_types.rs Outdated
Comment thread quaint/.github/workflows/test.yml Outdated
Comment thread quaint/.github/workflows/test.yml Outdated
Comment thread schema-engine/sql-introspection-tests/tests/postgres/postgis_geometry.rs Outdated
Comment thread schema-engine/sql-migration-tests/tests/migrations/postgres/postgis_geometry.rs Outdated
Comment thread schema-engine/sql-schema-describer/src/postgres.rs
Comment thread schema-engine/sql-schema-describer/src/postgres/default.rs
Copy link
Copy Markdown

@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: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@query-compiler/core/src/query_graph_builder/extractors/filters/scalar.rs`:
- Around line 190-202: The geometry filters (parse_geometry_near,
parse_geometry_within, parse_geometry_intersects) are created unconditionally as
Filter::Geometry and do not respect the self.reverse() flag; update the branches
handling filters::NEAR, filters::WITHIN, and filters::INTERSECTS to check
self.reverse() and either (a) construct the appropriate negated filter/field
operation when reverse() is true or (b) return a validation error if negation is
unsupported for geometry filters—choose one approach and implement it
consistently for all three parsers so NOT semantics are handled correctly.

In `@query-compiler/core/src/query_graph_builder/extractors/query_arguments.rs`:
- Around line 403-427: extract_float_from_pv and extract_int_from_pv duplicate
logic already implemented as extract_float and extract_int in the scalar filter
helpers; extract the shared parsing logic into a new utility (e.g., pv_parsers
or value_conversions) and move the common code for parsing PrismaValue->f64 and
PrismaValue->i32 there, then update extract_float_from_pv and
extract_int_from_pv to call the new shared functions (and similarly update
extract_float/extract_int to use them) so all modules reuse the single
implementation.

In `@query-compiler/query-builders/sql-query-builder/src/cursor_condition.rs`:
- Line 445: Replace the panic in the match arm for OrderBy::Geometry in
cursor_condition.rs with a structured error return: update the OrderBy::Geometry
arm to return a proper Err variant (use the module's existing query/error type,
e.g., an UnsupportedOperation or InvalidCursor error with a descriptive message
like "cursor-based pagination with geometry orderBy is not supported"), and if
the enclosing function (e.g., the cursor-building function that matches on
OrderBy) does not already return Result<..., YourErrorType>, change its
signature to return Result and propagate errors accordingly so callers can
handle this gracefully instead of panicking.

In `@query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs`:
- Around line 620-623: In visit_geometry_filter, the generated field_ref
currently wraps field_column.name in quotes but doesn't escape internal double
quotes; update the construction of field_ref to double any internal '"'
characters (e.g., replace '"' with '""') or call the existing utility
(Quoted::postgres_ident) used elsewhere to render PostgreSQL identifiers, so
field_ref becomes a properly escaped PostgreSQL identifier for
field_column.name.

In `@query-compiler/query-builders/sql-query-builder/src/ordering.rs`:
- Around line 299-309: The SQL builder currently wraps field_column.name in
double quotes without escaping internal quote characters, which can break
Postgres identifiers; update the code that constructs field_ref (used when
building the ST_Distance SQL and producing distance_expr) to properly escape
internal double quotes by doubling them (replace each `"` with `""`) before
surrounding with outer quotes, and apply the same change to the other
occurrences noted in filter/visitor.rs so all identifier interpolations use this
escaping (or switch to a shared identifier-escaping helper function to
centralize the logic).

In `@query-compiler/query-structure/src/filter/geometry.rs`:
- Around line 26-54: The implementation violates Eq/Hash because f64 NaN values
make derived PartialEq/Eq unsafe and hashing Intersects.geometry via to_string()
is inefficient; fix by writing a manual PartialEq that compares f64 fields using
to_bits() (for Near: compare point.0.to_bits(), point.1.to_bits(),
max_distance.to_bits(), srid; for Within: compare each polygon coordinate via
to_bits(); for Intersects: compare a canonical byte representation), keep or add
impl Eq only after ensuring PartialEq is total, and change the Hash impl to hash
the same bitwise f64 values and, for Intersects.geometry, hash a stable
binary/serialized representation (e.g., serde_json::to_vec or a canonical binary
form) instead of geometry.to_string() to avoid performance and nondeterminism
issues; update GeometryFilterCondition matching in both PartialEq and Hash to
use these exact comparisons so equality and hashing are consistent.

In `@query-compiler/query-structure/src/order_by.rs`:
- Around line 234-266: The auto-derived PartialEq can be violated by NaN in the
point f64s while you implement Eq and a hash based on to_bits(); replace the
derive PartialEq with a manual impl of PartialEq for OrderByGeometry that
compares field, path, sort_order, srid, and compares point coordinates via their
to_bits() (e.g., self.point.0.to_bits() == other.point.0.to_bits() && same for
.1) so equality semantics match the Hash impl and Eq is sound; update/remove the
derive(PartialEq) and add the explicit impl to keep behavior consistent.

In `@query-compiler/schema/src/build/input_types/objects/order_by_objects.rs`:
- Around line 281-294: The point field in geometry_distance_from_input currently
uses InputType::list(InputType::float()) (constants::filters::POINT) which
allows arbitrary-length arrays; either enforce a fixed two-element coordinate
tuple at the schema level (replace the open list with a fixed-length/tuple type
if available) or, if the schema system lacks fixed-length lists, add explicit
validation where the point is consumed (the extractor that reads
constants::filters::POINT before calling ST_MakePoint) to check the vec length
== 2 and return a clear validation error for malformed input; update
geometry_distance_from_input or the consuming function accordingly so callers
get a descriptive error instead of passing invalid arrays to PostGIS.

In `@query-compiler/schema/src/identifier_type.rs`:
- Around line 322-325: Add a new enum variant
IdentifierType::GeometryOrderByInput to ensure consistency with the raw string
used in order_by_objects.rs (Identifier::new_prisma("GeometryOrderByInput"));
update the Display implementation in identifier_type.rs to return
"GeometryOrderByInput" for that variant, and search for any
conversions/serializations (e.g., From/Into implementations or parsing logic
that map identifier names to IdentifierType) to handle the new variant so usages
expecting the enum variant compile and behave the same as the existing
GeometryDistanceFromInput mapping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ae37045c-aa91-431d-98f8-c5b9b20c764a

📥 Commits

Reviewing files that changed from the base of the PR and between 6efd1f0 and 3aa757a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • query-compiler/core/src/query_graph_builder/extractors/filters/scalar.rs
  • query-compiler/core/src/query_graph_builder/extractors/query_arguments.rs
  • query-compiler/query-builders/sql-query-builder/src/cursor_condition.rs
  • query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs
  • query-compiler/query-builders/sql-query-builder/src/ordering.rs
  • query-compiler/query-builders/sql-query-builder/src/select/mod.rs
  • query-compiler/query-structure/Cargo.toml
  • query-compiler/query-structure/src/filter/geometry.rs
  • query-compiler/query-structure/src/filter/mod.rs
  • query-compiler/query-structure/src/order_by.rs
  • query-compiler/query-structure/src/record.rs
  • query-compiler/schema/src/build/input_types/fields/field_filter_types.rs
  • query-compiler/schema/src/build/input_types/objects/order_by_objects.rs
  • query-compiler/schema/src/constants.rs
  • query-compiler/schema/src/identifier_type.rs

Comment thread query-compiler/query-builders/sql-query-builder/src/cursor_condition.rs Outdated
Comment thread query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs Outdated
Comment thread query-compiler/query-builders/sql-query-builder/src/ordering.rs Outdated
Comment thread query-compiler/query-structure/src/filter/geometry.rs
Comment thread query-compiler/query-structure/src/order_by.rs Outdated
Comment thread query-compiler/schema/src/identifier_type.rs
@lh0x00 lh0x00 changed the title [WIP] add PostGIS support for Prisma Native PostGIS Support for Prisma Mar 21, 2026
Copy link
Copy Markdown

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
query-compiler/query-structure/src/order_by.rs (1)

26-33: 🧹 Nitpick | 🔵 Trivial

Add explicit OrderBy::Geometry(_) handling to match expressions for consistency.

The new Geometry variant is handled through wildcard patterns in several locations, while other files use explicit arms:

  • query_arguments.rs:218 (contains_null_cursor) and line 301 (has_unbatchable_ordering) use wildcards
  • distinct.rs:30 uses _ => false
  • record.rs:83 explicitly handles with unimplemented!()

For consistency and to catch accidental omissions when new variants are added, add explicit OrderBy::Geometry(_) arms alongside the other aggregation types in the wildcard locations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@query-compiler/query-structure/src/order_by.rs` around lines 26 - 33, Replace
wildcard match arms that currently absorb the Geometry variant with an explicit
OrderBy::Geometry(_) arm in the same places other variants are handled: update
the matches in contains_null_cursor and has_unbatchable_ordering (both in
query_arguments.rs) and the match in distinct.rs so they include
OrderBy::Geometry(_) alongside ScalarAggregation, ToManyAggregation and
Relevance; make the Geometry arm return the same boolean/result as the analogous
aggregation arms (or false where `_ => false` was used) so behavior is unchanged
and matches remain exhaustive and explicit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@query-compiler/core-tests/tests/geometry-filters-graph-builds.rs`:
- Around line 239-292: The test currently expects successful graph construction
but the QueryGraphBuilder::build path rejects geometry filters under NOT; change
the final assertion to expect an error instead of success by calling
QueryGraphBuilder::new(&query_schema).build(query).expect_err(...) and then
assert the returned error message contains the expected validation text (lock
the exact message fragment produced by the extractor in
query_graph_builder::extractors::filters::scalar.rs) so the test verifies the
rejection rather than successful graph construction.

In `@query-compiler/core/src/query_graph_builder/extractors/filters/scalar.rs`:
- Around line 766-774: The extract_int function currently uses unchecked "as
i32" casts for PrismaValue::Int and PrismaValue::BigInt which silently truncate
out-of-range values; update extract_int (matching on PrismaValue::Int and
PrismaValue::BigInt) to perform checked conversions (e.g., use i32::try_from or
TryFrom) and return a QueryGraphBuilderError::InputError when the conversion
fails/overflows, keeping the same error message shape but indicating the value
is out of range so invalid SRIDs are rejected instead of wrapped/truncated.

In `@query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs`:
- Around line 620-623: visit_geometry_filter constructs field_ref from the bare
column name (filter.field.as_column(ctx) -> field_column.name) and omits the
current alias, causing ambiguous or wrong table binding in subqueries; update
visit_geometry_filter to prepend the active alias (use self.parent_alias() or
the equivalent alias accessor) when building field_ref so it references
"alias"."column" (or alias-qualified form used elsewhere) instead of just the
column name, ensuring geometry predicates inside relation/self-relation
subqueries bind to the correct table.
- Around line 624-630: The code collapses a missing SRID to 4326: change the
srid binding to preserve the Option (don't unwrap_or(4326)) so that
GeometryFilterCondition::Near/Within/Intersects yields the original Option<SRID>
(e.g., let srid = match &filter.condition { ... => srid.clone() }); then compute
use_geography by checking the explicit srid when Some(s) (s == 4326 || s == 4269
|| s == 4167) and otherwise consult the geometry field's declared SRID (do not
assume 4326 when srid is None). Update the use_geography computation (and any
downstream casts) to branch on the Option instead of treating None as 4326.

In `@query-compiler/query-compiler/tests/data/geometry-count-with-filter.json`:
- Around line 1-26: The fixture file name implies a count test but the JSON uses
"modelName": "Location" with "action": "findMany" and no count selection; either
rename the fixture to reflect a findMany/within test (e.g., include "findMany"
or "within" in the filename) or change the payload to perform a count (set
"action" to "count" and include the appropriate count selection/arguments for
the within polygon filter) so the filename and the contents match; update
whichever you choose and ensure the "position" -> "within" -> "polygon" filter
remains intact.

---

Outside diff comments:
In `@query-compiler/query-structure/src/order_by.rs`:
- Around line 26-33: Replace wildcard match arms that currently absorb the
Geometry variant with an explicit OrderBy::Geometry(_) arm in the same places
other variants are handled: update the matches in contains_null_cursor and
has_unbatchable_ordering (both in query_arguments.rs) and the match in
distinct.rs so they include OrderBy::Geometry(_) alongside ScalarAggregation,
ToManyAggregation and Relevance; make the Geometry arm return the same
boolean/result as the analogous aggregation arms (or false where `_ => false`
was used) so behavior is unchanged and matches remain exhaustive and explicit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9f037221-a4da-4f78-911d-e2a1cbf62bae

📥 Commits

Reviewing files that changed from the base of the PR and between 3aa757a and b644117.

⛔ Files ignored due to path filters (15)
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-combined-scalar-spatial.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-count-with-filter.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-delete-with-filter.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-and-orderby.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-and-scalar-filter.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-custom-srid.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-near.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-not-near.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-or-multiple.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-within.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-find-many.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-multiple-orderby.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-orderby-distance-asc.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-orderby-distance-desc.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-orderby-with-limit.json.snap is excluded by !**/*.snap
📒 Files selected for processing (24)
  • psl/parser-database/src/types.rs
  • psl/schema-ast/src/parser/parse_types.rs
  • query-compiler/core-tests/tests/geometry-filters-graph-builds.rs
  • query-compiler/core/src/query_graph_builder/extractors/filters/scalar.rs
  • query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs
  • query-compiler/query-compiler/tests/data/geometry-combined-scalar-spatial.json
  • query-compiler/query-compiler/tests/data/geometry-count-with-filter.json
  • query-compiler/query-compiler/tests/data/geometry-delete-with-filter.json
  • query-compiler/query-compiler/tests/data/geometry-filter-and-orderby.json
  • query-compiler/query-compiler/tests/data/geometry-filter-and-scalar-filter.json
  • query-compiler/query-compiler/tests/data/geometry-filter-custom-srid.json
  • query-compiler/query-compiler/tests/data/geometry-filter-intersects.json.skip
  • query-compiler/query-compiler/tests/data/geometry-filter-near.json
  • query-compiler/query-compiler/tests/data/geometry-filter-not-near.json
  • query-compiler/query-compiler/tests/data/geometry-filter-or-multiple.json
  • query-compiler/query-compiler/tests/data/geometry-filter-within.json
  • query-compiler/query-compiler/tests/data/geometry-multiple-orderby.json
  • query-compiler/query-compiler/tests/data/geometry-orderby-distance-asc.json
  • query-compiler/query-compiler/tests/data/geometry-orderby-distance-desc.json
  • query-compiler/query-compiler/tests/data/geometry-orderby-with-limit.json
  • query-compiler/query-compiler/tests/data/schema.prisma
  • query-compiler/query-structure/src/filter/geometry.rs
  • query-compiler/query-structure/src/order_by.rs
  • query-compiler/schema/src/build/input_types/fields/data_input_mapper/update.rs

Comment thread query-compiler/core-tests/tests/geometry-filters-graph-builds.rs
Comment thread query-compiler/core/src/query_graph_builder/extractors/filters/scalar.rs Outdated
Comment thread query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs Outdated
Comment thread query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs Outdated
@elrhourha
Copy link
Copy Markdown

Hey @aidankmcalister
can you check this PR please ? The related web PR is merged, but this one isn't, and the official Prisma docs show something that's not merged yet.
https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/working-with-geometry-fields

@lucioladen
Copy link
Copy Markdown

@elrhourha is right the docs is displaying wrong information, quite confusing until I found this PR wasn't merged yet.. when it is expected to be merged?

@samaty593
Copy link
Copy Markdown

@elrhourha is right the docs is displaying wrong information, quite confusing until I found this PR wasn't merged yet.. when it is expected to be merged?

That explain everything. I spend 2 days trying to make it work.

When is it expected to merge?

@lh0x00
Copy link
Copy Markdown
Author

lh0x00 commented May 12, 2026

Hmm, maybe the PR for this feature was overlooked by the team?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

CodeRabbit chat interactions are restricted to organization members for this repository. Ask an organization member to interact with CodeRabbit, or set chat.allow_non_org_members: true in your configuration.

Copy link
Copy Markdown

@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: 2

♻️ Duplicate comments (3)
query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs (2)

622-623: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

field_ref still drops parent_alias().

visit_geometry_filter builds field_ref from the bare column name, so geometry predicates inside relation/self-relation subqueries will bind to the wrong table or hit ambiguous-column errors once joins are introduced. Every other path in this file (e.g. visit_scalar_filter, visit_scalar_list_filter) qualifies columns via aliased_col(self.parent_alias(), ctx). This needs the same qualification, e.g. quoting "<alias>"."<column>" when self.parent_alias() is Some.

🛠️ Suggested change
-        let field_column = filter.field.as_column(ctx);
-        let field_ref = format!("\"{}\"", field_column.name);
+        let field_column = filter.field.as_column(ctx);
+        let field_ref = match self.parent_alias() {
+            Some(alias) => format!("\"{}\".\"{}\"", alias, field_column.name),
+            None => format!("\"{}\"", field_column.name),
+        };
🤖 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 `@query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs` around
lines 622 - 623, visit_geometry_filter builds field_ref from the bare column
name (using filter.field.as_column(ctx)) which drops self.parent_alias() and
causes wrong/ambiguous bindings; change it to qualify the column the same way
other visitors do by using aliased_col(self.parent_alias(), ctx) or otherwise
prepend the quoted parent alias when present so field_ref becomes
"\"<alias>\".\"<column>\"" when self.parent_alias() is Some, ensuring geometry
predicates inside relation/self-relation subqueries reference the correct table.

625-631: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

srid.unwrap_or(4326) still loses the field's declared SRID.

parse_geometry_*() uses None to mean "client did not override SRID", but this branch still rewrites that to 4326 for every geometry field. Non‑WGS84 columns will therefore emit mixed‑SRID SQL (e.g. ST_DWithin(col_in_3857, ST_SetSRID(..., 4326)::geography)) or an invalid ::geography cast unless every query redundantly passes srid. Preserve the Option<i32> and fall back to the column's declared SRID before defaulting; use_geography should likewise branch on the resolved SRID.

🤖 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 `@query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs` around
lines 625 - 631, The code currently forces srid = srid.unwrap_or(4326) losing
the field's declared SRID; instead keep srid as Option<i32> (the value coming
from filter.condition matching GeometryFilterCondition::Near/Within/Intersects)
and only resolve it by first checking the explicit srid Option, then the
geometry column's declared SRID, and finally defaulting to 4326; update the
subsequent use_geography computation to branch on that resolved SRID (not on the
original forced 4326) and ensure parse_geometry_*() callers receive the
Option<i32> when they expect "client did not override" semantics so you don't
emit mixed-SRID SQL or invalid ::geography casts.
query-compiler/core/src/query_document/parser.rs (1)

421-423: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Json/Geometry coercion still inconsistent with prisma_value_type_matches_scalar_type.

Line 423 still accepts PrismaValue::Json for ScalarType::Geometry(_) (coercing to bytes via s.into_bytes()), but the updated helper at line 995 only maps PrismaValueType::Json | PrismaValueType::Object to ScalarType::Json. Geometry is missing from the Json/Object arm, so an inlined JSON geometry value goes through the coercion path while the same value arriving as a typed Placeholder (or generator return) is rejected. The two paths still disagree.

Additionally, the s.into_bytes() conversion just gives the UTF‑8 bytes of the JSON text; that is neither WKT nor (E)WKB. If the intent is to support GeoJSON input for geometry columns, a proper GeoJSON→WKT/WKB step (or routing through a string codepath that downstream code parses as GeoJSON) is needed; otherwise these bytes will fail to bind on the database side. Either drop the Json → Geometry arm or fix both the helper and the conversion to a representation PostGIS will accept.

🛠️ Sketch of one consistent option (drop the Json→Geometry arm)
-            (pv @ PrismaValue::Bytes(_), &ScalarType::Geometry(_)) => Ok(pv),
-            (pv @ PrismaValue::String(_), &ScalarType::Geometry(_)) => Ok(pv),
-            (PrismaValue::Json(s), &ScalarType::Geometry(_)) => Ok(PrismaValue::Bytes(s.into_bytes())),
+            (pv @ PrismaValue::Bytes(_), &ScalarType::Geometry(_)) => Ok(pv),
+            (pv @ PrismaValue::String(_), &ScalarType::Geometry(_)) => Ok(pv),

Also applies to: 979-1002

🤖 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 `@query-compiler/core/src/query_document/parser.rs` around lines 421 - 423, The
PrismaValue::Json -> ScalarType::Geometry coercion (the match arm converting
Json via s.into_bytes()) is inconsistent with
prisma_value_type_matches_scalar_type and produces invalid UTF-8 bytes for
PostGIS; remove the PrismaValue::Json => ScalarType::Geometry arm (or replace it
with a real GeoJSON→WKB/WKT conversion if you intend to support GeoJSON) and
make the change symmetric by ensuring prisma_value_type_matches_scalar_type does
not map PrismaValueType::Json/ Object to Geometry (or does if you implement full
GeoJSON conversion); update/align both the match in parser.rs (the PrismaValue
match arms) and the helper (prisma_value_type_matches_scalar_type) so
Json/Object are either both allowed for Geometry with proper conversion or both
rejected.
🤖 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 `@query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs`:
- Around line 660-731: The Intersects branch in GeometryFilterCondition (in
visitor.rs) currently uses panic! for missing/invalid GeoJSON fields and
unsupported types; replace those panics with fallible handling: validate "type"
and "coordinates" and return a safe failure (e.g., propagate a ValidationError
or return ConditionTree::NegativeCondition/Err) from the function instead of
panicking, updating the code paths in the GeometryFilterCondition::Intersects
arm (and the helper used by Within, e.g., format_polygon_wkt callers) to produce
structured errors or negative conditions on bad input; ensure you escape WKT as
before but do not abort the process on malformed GeoJSON — return the
error/negative condition up the visitor so the caller can surface a proper
validation response.
- Around line 696-722: In the Polygon WKT generation (the format_polygon_wkt
function and the Intersects -> "Polygon" branch in visitor.rs), ensure each
linear ring is topologically closed by checking whether the first coordinate
equals the last and, if not, appending the first vertex to the end of the ring
before emitting the "(x y, ...)" sequence; additionally validate coordinates by
using f64::is_finite() for each numeric value instead of unconditionally using
as_f64().unwrap_or(0.0) so that NaN/inf are rejected (or return an error) early
rather than being formatted into WKT—update the ring-building logic in
format_polygon_wkt and the ring/point extraction in the Intersects "Polygon"
branch to perform these checks and fail fast on invalid coordinates.

---

Duplicate comments:
In `@query-compiler/core/src/query_document/parser.rs`:
- Around line 421-423: The PrismaValue::Json -> ScalarType::Geometry coercion
(the match arm converting Json via s.into_bytes()) is inconsistent with
prisma_value_type_matches_scalar_type and produces invalid UTF-8 bytes for
PostGIS; remove the PrismaValue::Json => ScalarType::Geometry arm (or replace it
with a real GeoJSON→WKB/WKT conversion if you intend to support GeoJSON) and
make the change symmetric by ensuring prisma_value_type_matches_scalar_type does
not map PrismaValueType::Json/ Object to Geometry (or does if you implement full
GeoJSON conversion); update/align both the match in parser.rs (the PrismaValue
match arms) and the helper (prisma_value_type_matches_scalar_type) so
Json/Object are either both allowed for Geometry with proper conversion or both
rejected.

In `@query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs`:
- Around line 622-623: visit_geometry_filter builds field_ref from the bare
column name (using filter.field.as_column(ctx)) which drops self.parent_alias()
and causes wrong/ambiguous bindings; change it to qualify the column the same
way other visitors do by using aliased_col(self.parent_alias(), ctx) or
otherwise prepend the quoted parent alias when present so field_ref becomes
"\"<alias>\".\"<column>\"" when self.parent_alias() is Some, ensuring geometry
predicates inside relation/self-relation subqueries reference the correct table.
- Around line 625-631: The code currently forces srid = srid.unwrap_or(4326)
losing the field's declared SRID; instead keep srid as Option<i32> (the value
coming from filter.condition matching
GeometryFilterCondition::Near/Within/Intersects) and only resolve it by first
checking the explicit srid Option, then the geometry column's declared SRID, and
finally defaulting to 4326; update the subsequent use_geography computation to
branch on that resolved SRID (not on the original forced 4326) and ensure
parse_geometry_*() callers receive the Option<i32> when they expect "client did
not override" semantics so you don't emit mixed-SRID SQL or invalid ::geography
casts.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bb79731e-41da-4a6f-ad13-eb7e93319750

📥 Commits

Reviewing files that changed from the base of the PR and between b644117 and be97d1e.

📒 Files selected for processing (4)
  • query-compiler/core/src/query_document/parser.rs
  • query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs
  • schema-engine/sql-migration-tests/tests/migrations/postgres.rs
  • schema-engine/sql-schema-describer/src/postgres/default.rs

Comment thread query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs Outdated
Copy link
Copy Markdown
Contributor

@SevInf SevInf left a comment

Choose a reason for hiding this comment

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

HI @lh0x00

Thank you for the PR. I think few changes would need to happen before we can proceed. 3 of the biggest ones:

  1. Can we split Geometry and Geography types?
  2. Would it be possible to use point Geometry @db.Geometry(Point, 4032) to be consistent with existing PSL parametrized types
  3. User input needs to be properly escaped within spatial filtering code.


/// PostGIS base type for a [`GeometrySpec`] (`geometry` vs `geography` in PostgreSQL).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub enum PostgisSpatialKind {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That would make more sense as separate Geometry/Geography types like they are in PostGIS

Comment thread psl/schema-ast/src/ast/field.rs Outdated
pub enum FieldType {
Supported(Identifier),
/// `Geometry(Point, 4326)` or `Geometry(LineString)` (SRID optional).
Geometry {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Geometry being a third type alongsideSupported and Unsupported does not seem right to me.
I think this is because you try introduce new syntax for parametrized types that did not exist in PSL before. point Geometry @db.Geometry("Point", 4236) would be consistent with Prisma 7 syntax and would avoid you this kind of problem

unsupported_type = { "Unsupported(" ~ string_literal ~ ")" }
base_type = { unsupported_type | identifier } // Called base type to not conflict with type rust keyword

geometry_subtype = {
Copy link
Copy Markdown
Contributor

@SevInf SevInf May 21, 2026

Choose a reason for hiding this comment

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

I don't think we need entire grammar for this - this will not produce the best syntax error. I'd rather keep it as identifier and validate it after Pest

Comment thread quaint/.github/workflows/test.yml Outdated
runs-on: ubuntu-latest
env:
RUSTFLAGS: "-Dwarnings"
RUSTFLAGS: '-Dwarnings'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please don't reformat unrelated files

ScalarFieldType::CompositeType(_) => "object",
ScalarFieldType::Enum(_) => "enum",
ScalarFieldType::BuiltInScalar(_) => "scalar",
ScalarFieldType::BuiltInScalar(_) | ScalarFieldType::Geometry(_) => "scalar",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ScalarFiledType and kind on dmmf should match. Either Geometry should be subtype of BuiltInScalar or kind should be geomatery

Comment thread query-compiler/dmmf/Cargo.toml Outdated
bigdecimal.workspace = true
itertools.workspace = true
psl.workspace = true
psl = { workspace = true, features = ["all"] }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is not right — it will include PSL handlers for all databases in the bundle even if only one of them is used.

Comment thread query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs Outdated
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 21, 2026

Merging this PR will not alter performance

✅ 11 untouched benchmarks
⏩ 11 skipped benchmarks1


Comparing lh0x00:feature/support-postgis-features (be97d1e) with main (3c6e192)

Open in CodSpeed

Footnotes

  1. 11 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@SevInf
Copy link
Copy Markdown
Contributor

SevInf commented May 21, 2026

Also, the code does not compile on CI

…ile preserving native-attribute parity with VarChar/Decimal
Copy link
Copy Markdown

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
query-compiler/core/src/query_graph_builder/extractors/query_arguments.rs (1)

409-420: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistent coordinate handling: raw tuple vs GeoCoord.

The filter parsing in scalar.rs uses GeoCoord::new(lon, lat) which validates finite coordinates, but extract_geometry_distance_from passes raw (lon, lat) floats directly to OrderBy::geometry. This skips the finite-coordinate validation that filter parsing performs, allowing NaN/Infinity to reach SQL generation.

🛠️ Suggested fix to validate coordinates
     let lon = extract_float_from_pv(&point_list[0])?;
     let lat = extract_float_from_pv(&point_list[1])?;
+    // Validate coordinates are finite (extract_float_from_pv already checks this,
+    // but GeoCoord::new provides consistent validation with filter parsing)
+    use query_structure::GeoCoord;
+    let _ = GeoCoord::new(lon, lat).map_err(|e| QueryGraphBuilderError::InputError(e.to_string()))?;
     let sort_order = pv_to_sort_order(direction_value.try_into()?)?;

Note: extract_float_from_pv already validates finite values (lines 439-444), so this is more about consistency. If extract_float_from_pv is trusted, this is optional.

🤖 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 `@query-compiler/core/src/query_graph_builder/extractors/query_arguments.rs`
around lines 409 - 420, The code constructs an OrderBy::geometry with a raw
(lon, lat) tuple which bypasses GeoCoord validation; change this to build a
GeoCoord via GeoCoord::new(lon, lat) (or equivalent constructor) and pass that
GeoCoord to OrderBy::geometry to enforce finite coordinate checks and match
scalar.rs behavior (refer to extract_float_from_pv, OrderBy::geometry, and
GeoCoord::new to locate the spots to update).
♻️ Duplicate comments (2)
schema-engine/sql-introspection-tests/tests/postgres/postgis_geometry.rs (1)

56-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Strengthen declaration-level assertions for geography fields.

These split contains() checks can pass on unrelated lines. Assert the whole field declaration pattern so name, scalar type, and native type stay linked.

Proposed test tightening
+use regex::Regex;
...
-    assert!(
-        schema.contains("location Geography?") && schema.contains("`@db.Geography`(Point, 4326)"),
-        "expected `location Geography? ... `@db.Geography`(Point, 4326)`, got:\n{schema}",
-    );
-    assert!(
-        schema.contains("region Geography") && schema.contains("`@db.Geography`(Polygon, 4326)"),
-        "expected `region Geography ... `@db.Geography`(Polygon, 4326)`, got:\n{schema}",
-    );
+    let location_re = Regex::new(r"(?m)^\s*location\s+Geography\?\s+@db\.Geography\(Point,\s*4326\)\s*$").unwrap();
+    let region_re = Regex::new(r"(?m)^\s*region\s+Geography\s+@db\.Geography\(Polygon,\s*4326\)\s*$").unwrap();
+    assert!(location_re.is_match(&schema), "expected location geography declaration, got:\n{schema}");
+    assert!(region_re.is_match(&schema), "expected region geography declaration, got:\n{schema}");
🤖 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 `@schema-engine/sql-introspection-tests/tests/postgres/postgis_geometry.rs`
around lines 56 - 63, Replace the split contains() checks with assertions that
match the entire field declaration so the field name, scalar type/nullability
and native type directive are contiguous; specifically update the two asserts
that reference "location" and "region" to assert that schema contains the full
declaration strings (e.g. a single contains() check per field that includes
"location Geography? `@db.Geography`(Point, 4326)" and "region Geography
`@db.Geography`(Polygon, 4326)" respectively) so the name, scalar type, and
`@db.Geography`(...) stay linked.
query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs (1)

698-701: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Polygon WKT rings are not guaranteed to be closed.

PostGIS requires polygon rings to be topologically closed (first vertex == last vertex). This function formats the coordinates as-is without checking or enforcing closure. If the input positions slice doesn't have matching first and last points, the generated WKT will fail with "geometry contains non-closed rings" at query execution time.

Additionally, if any coordinate contains NaN or infinity (from upstream float parsing), format!("{} {}", c.x, c.y) will produce literal NaN/inf tokens in the WKT string, which are invalid and will cause opaque SQL errors.

🐛 Proposed fix to ensure ring closure
 fn format_polygon_ring_wkt(positions: &[GeoCoord]) -> String {
+    if positions.is_empty() {
+        return "POLYGON(())".to_string();
+    }
+    // Ensure ring is closed per OGC/PostGIS requirement
+    let mut pts = positions.to_vec();
+    if let (Some(first), Some(last)) = (pts.first(), pts.last()) {
+        if (first.x - last.x).abs() > f64::EPSILON || (first.y - last.y).abs() > f64::EPSILON {
+            pts.push(*first);
+        }
+    }
-    let parts: Vec<_> = positions.iter().map(|c| format!("{} {}", c.x, c.y)).collect();
+    let parts: Vec<_> = pts.iter().map(|c| format!("{} {}", c.x, c.y)).collect();
     format!("POLYGON(({}))", parts.join(", "))
 }

For NaN/inf validation, consider adding a check in the upstream extractor or here to reject non-finite coordinates early.

🤖 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 `@query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs` around
lines 698 - 701, The function format_polygon_ring_wkt currently emits the ring
"as-is", which can produce non-closed rings and embed NaN/inf tokens; update
format_polygon_ring_wkt(positions: &[GeoCoord]) to (1) validate every coordinate
is finite (reject or return an error if c.x or c.y is NaN/inf) before
formatting, and (2) ensure the ring is closed by checking if the first and last
GeoCoord are equal and, if not, append the first coordinate to the end of the
sequence before joining; keep using the same "{} {}" value order when building
the WKT string so output remains "POLYGON((...))" but only after these
validations and closure enforcement.
🤖 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 `@psl/parser-database/src/attributes/default.rs`:
- Around line 194-199: The error message in the validation branch that checks
scalar_type against ScalarType::Geometry | ScalarType::Geography is misleading
because it hardcodes "Geometry"; update the call to
ctx.push_attribute_validation_error (the branch that matches scalar_type, and
the similar branch later around the other check) so the message refers to both
"Geometry and Geography" (or dynamically includes the actual variant name)
instead of only "Geometry" to produce correct diagnostics for Geography fields.

In `@psl/psl-core/src/validate/validation_pipeline/validations/fields.rs`:
- Around line 345-347: The diagnostic message built in the helper (variable msg)
is too specific to "Geometry" even though the helper handles both Geometry and
Geography; update the message to use neutral wording like "PostGIS spatial type"
(e.g., "uses a PostGIS spatial type, which is only supported on PostgreSQL with
PostGIS") and ensure references to field_name, container, and container_name
remain intact so the message works for both Geometry and Geography paths.

In `@query-compiler/query-structure/src/filter/geojson.rs`:
- Around line 88-93: The coord helper in to_wkt currently uses format!("{} {}",
c.x, c.y) which can emit scientific notation and break PostGIS WKT parsing;
change coord (and any uses in to_wkt) to produce decimal strings that never use
exponential notation and preserve f64 precision (use a decimal serializer like
ryu or dtoa to format each GeoCoord component with ~17 significant digits into
non-exponential form) so the generated WKT is always PostGIS-compatible; update
coord/GeoCoord formatting to use that serializer for both c.x and c.y.

---

Outside diff comments:
In `@query-compiler/core/src/query_graph_builder/extractors/query_arguments.rs`:
- Around line 409-420: The code constructs an OrderBy::geometry with a raw (lon,
lat) tuple which bypasses GeoCoord validation; change this to build a GeoCoord
via GeoCoord::new(lon, lat) (or equivalent constructor) and pass that GeoCoord
to OrderBy::geometry to enforce finite coordinate checks and match scalar.rs
behavior (refer to extract_float_from_pv, OrderBy::geometry, and GeoCoord::new
to locate the spots to update).

---

Duplicate comments:
In `@query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs`:
- Around line 698-701: The function format_polygon_ring_wkt currently emits the
ring "as-is", which can produce non-closed rings and embed NaN/inf tokens;
update format_polygon_ring_wkt(positions: &[GeoCoord]) to (1) validate every
coordinate is finite (reject or return an error if c.x or c.y is NaN/inf) before
formatting, and (2) ensure the ring is closed by checking if the first and last
GeoCoord are equal and, if not, append the first coordinate to the end of the
sequence before joining; keep using the same "{} {}" value order when building
the WKT string so output remains "POLYGON((...))" but only after these
validations and closure enforcement.

In `@schema-engine/sql-introspection-tests/tests/postgres/postgis_geometry.rs`:
- Around line 56-63: Replace the split contains() checks with assertions that
match the entire field declaration so the field name, scalar type/nullability
and native type directive are contiguous; specifically update the two asserts
that reference "location" and "region" to assert that schema contains the full
declaration strings (e.g. a single contains() check per field that includes
"location Geography? `@db.Geography`(Point, 4326)" and "region Geography
`@db.Geography`(Polygon, 4326)" respectively) so the name, scalar type, and
`@db.Geography`(...) stay linked.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0244588f-98a5-4716-b370-e4cfa7a21dd5

📥 Commits

Reviewing files that changed from the base of the PR and between be97d1e and c90f1ee.

⛔ Files ignored due to path filters (14)
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-combined-scalar-spatial.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-count-with-filter.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-delete-with-filter.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-and-orderby.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-and-scalar-filter.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-custom-srid.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-near.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-not-near.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-or-multiple.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-filter-within.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-multiple-orderby.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-orderby-distance-asc.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-orderby-distance-desc.json.snap is excluded by !**/*.snap
  • query-compiler/query-compiler/tests/snapshots/queries__queries@geometry-orderby-with-limit.json.snap is excluded by !**/*.snap
📒 Files selected for processing (50)
  • psl/parser-database/src/attributes.rs
  • psl/parser-database/src/attributes/default.rs
  • psl/parser-database/src/types.rs
  • psl/psl-core/src/builtin_connectors/mod.rs
  • psl/psl-core/src/builtin_connectors/postgres_datamodel_connector.rs
  • psl/psl-core/src/builtin_connectors/postgres_datamodel_connector/native_types.rs
  • psl/psl-core/src/datamodel_connector.rs
  • psl/psl-core/src/validate/validation_pipeline/validations/fields.rs
  • psl/psl/tests/validation/postgres/postgis_geometry_keyword_valid.prisma
  • psl/psl/tests/validation/postgres/postgis_native_type_keyword_mismatch.prisma
  • psl/schema-ast/src/ast/field.rs
  • psl/schema-ast/src/parser/datamodel.pest
  • psl/schema-ast/src/parser/parse_types.rs
  • quaint/src/ast/function.rs
  • quaint/src/ast/function/postgis.rs
  • quaint/src/visitor.rs
  • query-compiler/core-tests/tests/geometry-filters-graph-builds.rs
  • query-compiler/core-tests/tests/geometry_find_many_graph_builds.rs
  • query-compiler/core/src/query_document/parser.rs
  • query-compiler/core/src/query_graph_builder/extractors/filters/scalar.rs
  • query-compiler/core/src/query_graph_builder/extractors/query_arguments.rs
  • query-compiler/dmmf/src/ast_builders/datamodel_ast_builder.rs
  • query-compiler/dmmf/src/ast_builders/schema_ast_builder/type_renderer.rs
  • query-compiler/dmmf/src/tests/tests.rs
  • query-compiler/query-builders/sql-query-builder/src/cursor_condition.rs
  • query-compiler/query-builders/sql-query-builder/src/filter/visitor.rs
  • query-compiler/query-builders/sql-query-builder/src/ordering.rs
  • query-compiler/query-compiler/src/data_mapper.rs
  • query-compiler/query-compiler/tests/data/schema.prisma
  • query-compiler/query-structure/src/field/mod.rs
  • query-compiler/query-structure/src/field/scalar.rs
  • query-compiler/query-structure/src/filter/geojson.rs
  • query-compiler/query-structure/src/filter/geometry.rs
  • query-compiler/query-structure/src/filter/mod.rs
  • query-compiler/query-structure/src/prisma_value_ext.rs
  • query-compiler/schema/src/build/input_types/fields/field_filter_types.rs
  • query-compiler/schema/src/build/input_types/mod.rs
  • query-compiler/schema/src/build/input_types/objects/order_by_objects.rs
  • query-compiler/schema/src/build/output_types/field.rs
  • query-compiler/schema/src/output_types.rs
  • query-compiler/schema/src/query_schema.rs
  • query-engine/connectors/mongodb-query-connector/src/filter.rs
  • schema-engine/connectors/sql-schema-connector/src/flavour/postgres/renderer.rs
  • schema-engine/connectors/sql-schema-connector/src/flavour/postgres/schema_differ.rs
  • schema-engine/connectors/sql-schema-connector/src/introspection/introspection_pair/scalar_field.rs
  • schema-engine/connectors/sql-schema-connector/src/sql_doc_parser.rs
  • schema-engine/connectors/sql-schema-connector/src/sql_schema_calculator.rs
  • schema-engine/sql-introspection-tests/tests/postgres/postgis_geometry.rs
  • schema-engine/sql-migration-tests/tests/migrations/postgres/postgis_geometry.rs
  • schema-engine/sql-schema-describer/src/postgres.rs
💤 Files with no reviewable changes (2)
  • query-compiler/core/src/query_document/parser.rs
  • psl/schema-ast/src/parser/parse_types.rs

Comment thread psl/parser-database/src/attributes/default.rs
Comment thread psl/psl-core/src/validate/validation_pipeline/validations/fields.rs
Comment thread query-compiler/query-structure/src/filter/geojson.rs
…d tolerate column-alignment padding in introspection checks
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.

6 participants