| 27 | /// 4. Return matching documents up to limit |
| 28 | #[allow(clippy::too_many_arguments)] |
| 29 | pub(in crate::data::executor) fn execute_spatial_scan( |
| 30 | &mut self, |
| 31 | task: &ExecutionTask, |
| 32 | tid: u64, |
| 33 | collection: &str, |
| 34 | field: &str, |
| 35 | predicate: &SpatialPredicate, |
| 36 | query_geometry: &nodedb_types::geometry::Geometry, |
| 37 | distance_meters: f64, |
| 38 | attribute_filters: &[u8], |
| 39 | limit: usize, |
| 40 | projection: &[String], |
| 41 | rls_filters: &[u8], |
| 42 | prefilter: Option<&SurrogateBitmap>, |
| 43 | ) -> Response { |
| 44 | debug!( |
| 45 | core = self.core_id, |
| 46 | %collection, |
| 47 | %field, |
| 48 | predicate = ?predicate, |
| 49 | "spatial scan" |
| 50 | ); |
| 51 | |
| 52 | // Scan-quiesce gate. |
| 53 | let _scan_guard = match self.acquire_scan_guard(task, tid, collection) { |
| 54 | Ok(g) => g, |
| 55 | Err(resp) => return resp, |
| 56 | }; |
| 57 | |
| 58 | // The query geometry was parsed and validated on the Control Plane. |
| 59 | let query_geom = query_geometry; |
| 60 | |
| 61 | // 2. Deserialize attribute and RLS filters. |
| 62 | let attr_filters: Vec<ScanFilter> = if attribute_filters.is_empty() { |
| 63 | Vec::new() |
| 64 | } else { |
| 65 | zerompk::from_msgpack(attribute_filters).unwrap_or_default() |
| 66 | }; |
| 67 | let row_level_filters: Vec<ScanFilter> = if rls_filters.is_empty() { |
| 68 | Vec::new() |
| 69 | } else { |
| 70 | zerompk::from_msgpack(rls_filters).unwrap_or_default() |
| 71 | }; |
| 72 | |
| 73 | // 3. Compute search bbox (expand by distance for ST_DWithin). |
| 74 | let query_bbox = nodedb_types::bbox::geometry_bbox(query_geom); |
| 75 | let search_bbox = if distance_meters > 0.0 { |
| 76 | expand_bbox(&query_bbox, distance_meters) |
| 77 | } else { |
| 78 | query_bbox |
| 79 | }; |
| 80 | |
| 81 | let tid_id = crate::types::TenantId::new(tid); |
| 82 | let spatial_key = (tid_id, collection.to_string(), field.to_string()); |
| 83 | let has_index = self.spatial_indexes.contains_key(&spatial_key); |
| 84 | let limit = if limit == 0 { 1000 } else { limit }; |
| 85 | |
| 86 | // No R-tree: full scan with predicate post-filter. |