Complete Spatial/Geometry type integration.

Addressed all `todo!()` markers created when adding `Spatial` field type
and `Geometry` value type to existing code paths:

- Dynamic field handling: `Geometry` not supported in dynamic JSON
  fields, return `unimplemented!()` consistently with other complex
  types.
- Fast field writer: Panic if geometry routed incorrectly (internal
  error.)
- `OwnedValue` serialization: Implement `Geometry` to GeoJSON
  serialization and reference-to-owned conversion.
- Field type: Return `None` for `get_index_record_option()` since
  spatial fields use BKD trees, not inverted index.
- Space usage tracking: Add spatial field to `SegmentSpaceUsage` with
  proper integration through `SegmentReader`.
- Spatial query explain: Implement `explain()` method following pattern of
  other binary/constant-score queries.

Fixed `MultiPolygon` deserialization bug: count total points across all
rings, not number of rings.

Added clippy expects for legitimate too_many_arguments cases in geometric
predicates.
This commit is contained in:
Alan Gutierrez
2025-11-13 01:05:17 -06:00
committed by Paul Masurel
parent 68009bb25b
commit 9f10279681
10 changed files with 48 additions and 14 deletions

View File

@@ -227,7 +227,9 @@ pub(crate) fn index_json_value<'a, V: Value<'a>>(
ReferenceValueLeaf::IpAddr(_) => {
unimplemented!("IP address support in dynamic fields is not yet implemented")
}
ReferenceValueLeaf::Geometry(_) => todo!(),
ReferenceValueLeaf::Geometry(_) => {
unimplemented!("Geometry support in dynamic fields is not implemented")
}
},
ReferenceValue::Array(elements) => {
for val in elements {

View File

@@ -189,7 +189,9 @@ impl FastFieldsWriter {
.record_str(doc_id, field_name, &token.text);
}
}
ReferenceValueLeaf::Geometry(_) => todo!(),
ReferenceValueLeaf::Geometry(_) => {
panic!("Geometry fields should not be routed to fast field writer")
}
},
ReferenceValue::Array(val) => {
// TODO: Check this is the correct behaviour we want.
@@ -321,7 +323,9 @@ fn record_json_value_to_columnar_writer<'a, V: Value<'a>>(
"Pre-tokenized string support in dynamic fields is not yet implemented"
)
}
ReferenceValueLeaf::Geometry(_) => todo!(),
ReferenceValueLeaf::Geometry(_) => {
unimplemented!("Geometry support in dynamic fields is not yet implemented")
}
},
ReferenceValue::Array(elements) => {
for el in elements {

View File

@@ -470,6 +470,7 @@ impl SegmentReader {
self.positions_composite.space_usage(),
self.fast_fields_readers.space_usage(self.schema())?,
self.fieldnorm_readers.space_usage(),
self.spatial_readers.space_usage(),
self.get_store_reader(0)?.space_usage(),
self.alive_bitset_opt
.as_ref()

View File

@@ -2,7 +2,8 @@
use common::BitSet;
use crate::query::{BitSetDocSet, Query, Scorer, Weight};
use crate::query::explanation::does_not_match;
use crate::query::{BitSetDocSet, Explanation, Query, Scorer, Weight};
use crate::schema::Field;
use crate::spatial::bkd::{search_intersects, Segment};
use crate::spatial::writer::as_point_i32;
@@ -96,10 +97,24 @@ impl Weight for SpatialWeight {
}
fn explain(
&self,
_reader: &crate::SegmentReader,
_doc: DocId,
reader: &crate::SegmentReader,
doc: DocId,
) -> crate::Result<super::Explanation> {
todo!();
let mut scorer = self.scorer(reader, 1.0)?;
if scorer.seek(doc) != doc {
return Err(does_not_match(doc));
}
let query_type_desc = match self.query_type {
SpatialQueryType::Intersects => "SpatialQuery::Intersects",
};
let score = scorer.score();
let mut explanation = Explanation::new(query_type_desc, score);
explanation.add_context(format!(
"bounds: [({}, {}), ({}, {})]",
self.bounds[0].0, self.bounds[0].1, self.bounds[1].0, self.bounds[1].1,
));
explanation.add_context(format!("field: {:?}", self.field));
Ok(explanation)
}
}

View File

@@ -466,7 +466,6 @@ impl<'a> CompactDocValue<'a> {
.map(Into::into)
.map(ReferenceValueLeaf::PreTokStr)
.map(Into::into),
// ValueType::Geometry => todo!(),
ValueType::Geometry => self
.container
.read_from::<Geometry>(addr)

View File

@@ -208,7 +208,7 @@ impl serde::Serialize for OwnedValue {
}
}
OwnedValue::Array(ref array) => array.serialize(serializer),
OwnedValue::Geometry(_) => todo!(),
OwnedValue::Geometry(ref geometry) => geometry.to_geojson().serialize(serializer),
}
}
}
@@ -296,7 +296,7 @@ impl<'a, V: Value<'a>> From<ReferenceValue<'a, V>> for OwnedValue {
ReferenceValueLeaf::IpAddr(val) => OwnedValue::IpAddr(val),
ReferenceValueLeaf::Bool(val) => OwnedValue::Bool(val),
ReferenceValueLeaf::PreTokStr(val) => OwnedValue::PreTokStr(*val.clone()),
ReferenceValueLeaf::Geometry(_) => todo!(),
ReferenceValueLeaf::Geometry(val) => OwnedValue::Geometry(*val.clone()),
},
ReferenceValue::Array(val) => {
OwnedValue::Array(val.map(|v| v.as_value().into()).collect())

View File

@@ -359,7 +359,8 @@ impl FieldType {
None
}
}
FieldType::Spatial(_) => todo!(),
FieldType::Spatial(_) => None, /* Geometry types cannot be indexed in the inverted
* index. */
}
}

View File

@@ -69,6 +69,7 @@ pub struct SegmentSpaceUsage {
positions: PerFieldSpaceUsage,
fast_fields: PerFieldSpaceUsage,
fieldnorms: PerFieldSpaceUsage,
spatial: PerFieldSpaceUsage,
store: StoreSpaceUsage,
@@ -86,6 +87,7 @@ impl SegmentSpaceUsage {
positions: PerFieldSpaceUsage,
fast_fields: PerFieldSpaceUsage,
fieldnorms: PerFieldSpaceUsage,
spatial: PerFieldSpaceUsage,
store: StoreSpaceUsage,
deletes: ByteCount,
) -> SegmentSpaceUsage {
@@ -94,6 +96,7 @@ impl SegmentSpaceUsage {
+ positions.total()
+ fast_fields.total()
+ fieldnorms.total()
+ spatial.total()
+ store.total()
+ deletes;
SegmentSpaceUsage {
@@ -103,6 +106,7 @@ impl SegmentSpaceUsage {
positions,
fast_fields,
fieldnorms,
spatial,
store,
deletes,
total,
@@ -121,11 +125,11 @@ impl SegmentSpaceUsage {
Positions => PerField(self.positions().clone()),
FastFields => PerField(self.fast_fields().clone()),
FieldNorms => PerField(self.fieldnorms().clone()),
Spatial => PerField(self.spatial().clone()),
Terms => PerField(self.termdict().clone()),
SegmentComponent::Store => ComponentSpaceUsage::Store(self.store().clone()),
SegmentComponent::TempStore => ComponentSpaceUsage::Store(self.store().clone()),
Delete => Basic(self.deletes()),
Spatial => todo!(),
}
}
@@ -159,6 +163,11 @@ impl SegmentSpaceUsage {
&self.fieldnorms
}
/// Space usage for field norms
pub fn spatial(&self) -> &PerFieldSpaceUsage {
&self.spatial
}
/// Space usage for stored documents
pub fn store(&self) -> &StoreSpaceUsage {
&self.store

View File

@@ -481,6 +481,7 @@ pub fn search_intersects(
Ok(())
}
#[expect(clippy::too_many_arguments)]
fn line_intersects_line(
x1: i32,
y1: i32,
@@ -572,6 +573,7 @@ fn triangle_within(triangle: &Triangle, query: &[i32; 4]) -> bool {
true
}
#[expect(clippy::too_many_arguments)]
fn point_in_triangle(
px: i32,
py: i32,

View File

@@ -353,8 +353,9 @@ impl BinarySerializable for Geometry {
let ring_count = VInt::deserialize(reader)?.0 as usize;
let mut rings = Vec::new();
for _ in 0..ring_count {
rings.push(VInt::deserialize(reader)?.0 as usize);
count += 1;
let point_count = VInt::deserialize(reader)?.0 as usize;
rings.push(point_count);
count += point_count;
}
polygons.push(rings);
}