1use crate::store::{
18 AdjacencyReadMemory, GraphExecutionRead, GraphExecutionWrite, PrunedNodeScan,
19 PrunedRelationshipScan, ScanControl, SourceScanCandidateRow, SourceScanCandidateVisit,
20 SourceScanReadLimits,
21};
22use crate::QueryMemoryLease;
23use hawdb_core::error::Result;
24use hawdb_core::schema::Catalog;
25use hawdb_core::{LabelId, RelTypeId};
26use hawdb_plan_cypher::NodeProjectionAccess;
27use hawdb_storage::store::{GraphScanControl, GraphStore};
28use hawdb_storage::{
29 adjacency::AdjacencyDirection,
30 mutation::{GraphMutation, MutationLimits, MutationSummary, NodeSetAssignment, PropertyFilter},
31 NodeId, NodeRecord, ProjectedNodeRecord, RelId, RelRecord,
32};
33use std::collections::BTreeSet;
34
35fn to_store_control(control: ScanControl) -> GraphScanControl {
36 match control {
37 ScanControl::Continue => GraphScanControl::Continue,
38 ScanControl::Stop => GraphScanControl::Stop,
39 }
40}
41
42fn to_execution_control(control: GraphScanControl) -> ScanControl {
43 match control {
44 GraphScanControl::Continue => ScanControl::Continue,
45 GraphScanControl::Stop => ScanControl::Stop,
46 }
47}
48
49fn adapt_node_consumer(
50 consumer: &mut dyn FnMut(NodeRecord) -> Result<ScanControl>,
51 visit: impl FnOnce(&mut dyn FnMut(NodeRecord) -> GraphScanControl) -> Result<GraphScanControl>,
52) -> Result<ScanControl> {
53 let mut consumer_error = None;
54 let control = visit(&mut |node| match consumer(node) {
55 Ok(control) => to_store_control(control),
56 Err(error) => {
57 consumer_error = Some(error);
58 GraphScanControl::Stop
59 }
60 })?;
61 match consumer_error {
62 Some(error) => Err(error),
63 None => Ok(to_execution_control(control)),
64 }
65}
66
67impl hawdb_storage::read_view::GraphReadAllocation for QueryMemoryLease {
68 fn bytes(&self) -> usize {
69 self.bytes()
70 }
71 fn grow(&mut self, bytes: usize) -> hawdb_core::Result<()> {
72 QueryMemoryLease::grow(self, bytes)
73 }
74}
75
76impl GraphExecutionRead for GraphStore {
77 fn file_descriptor_context(&self) -> Option<hawdb_storage::file_descriptors::FileOpenContext> {
78 GraphStore::file_descriptor_context(self)
79 }
80
81 fn is_out_of_core(&self) -> bool {
82 GraphStore::is_out_of_core(self)
83 }
84
85 fn projected_node_owned_admitted(
86 &self,
87 id: NodeId,
88 required_properties: &BTreeSet<String>,
89 admit: &mut dyn FnMut(usize) -> Result<()>,
90 ) -> Result<Option<ProjectedNodeRecord>> {
91 GraphStore::projected_node_owned_admitted(self, id, required_properties, admit)
92 }
93
94 fn node_owned(&self, id: NodeId) -> Result<Option<NodeRecord>> {
95 GraphStore::node_owned(self, id)
96 }
97
98 fn node_with_allocation(
99 &self,
100 id: NodeId,
101 label_ids: Option<&[LabelId]>,
102 admit: &mut hawdb_storage::read_view::ControlledGraphReadAllocator<'_>,
103 ) -> Result<hawdb_storage::read_view::AdmittedNodeRead> {
104 GraphStore::node_with_allocation(self, id, label_ids, admit)
105 }
106
107 fn relationship_with_allocation(
108 &self,
109 id: RelId,
110 admit: &mut hawdb_storage::read_view::ControlledGraphReadAllocator<'_>,
111 ) -> Result<hawdb_storage::read_view::AdmittedRelationshipRead> {
112 GraphStore::relationship_with_allocation(self, id, admit)
113 }
114
115 fn scan_nodes_borrowed<'a>(
116 &'a self,
117 label_id: Option<LabelId>,
118 ) -> Box<dyn Iterator<Item = &'a NodeRecord> + 'a> {
119 Box::new(GraphStore::scan_nodes(self, label_id))
120 }
121
122 fn node_count_for_label(&self, label_id: Option<LabelId>) -> usize {
123 GraphStore::node_count_for_label(self, label_id)
124 }
125
126 fn relationship_count_for_type(&self, rel_type: Option<RelTypeId>) -> usize {
127 GraphStore::relationship_count_for_type(self, rel_type)
128 }
129
130 fn visit_nodes_owned(
131 &self,
132 label_id: Option<LabelId>,
133 consumer: &mut dyn FnMut(NodeRecord) -> Result<ScanControl>,
134 ) -> Result<ScanControl> {
135 GraphStore::try_visit_nodes_owned(self, label_id, |node| {
136 consumer(node).map(to_store_control)
137 })
138 .map(to_execution_control)
139 }
140
141 fn visit_relationships_with_allocation(
142 &self,
143 rel_type: Option<RelTypeId>,
144 admit: &mut hawdb_storage::read_view::ControlledGraphReadAllocator<'_>,
145 consumer: &mut dyn FnMut(
146 hawdb_storage::read_view::AdmittedRelationshipRecord,
147 ) -> Result<ScanControl>,
148 ) -> Result<ScanControl> {
149 GraphStore::visit_relationships_with_allocation(self, rel_type, admit, &mut |record| {
150 consumer(record).map(to_store_control)
151 })
152 .map(to_execution_control)
153 }
154
155 fn visit_nodes_with_allocation(
156 &self,
157 label_id: Option<LabelId>,
158 admit: &mut hawdb_storage::read_view::ControlledGraphReadAllocator<'_>,
159 consumer: &mut dyn FnMut(
160 hawdb_storage::read_view::AdmittedNodeRecord,
161 ) -> Result<ScanControl>,
162 ) -> Result<ScanControl> {
163 GraphStore::visit_nodes_with_allocation(self, label_id, admit, &mut |node| {
164 consumer(node).map(to_store_control)
165 })
166 .map(to_execution_control)
167 }
168
169 fn visit_nodes_by_access_with_allocation(
170 &self,
171 label_id: LabelId,
172 access: &NodeProjectionAccess,
173 admit: &mut hawdb_storage::read_view::ControlledGraphReadAllocator<'_>,
174 consumer: &mut dyn FnMut(
175 hawdb_storage::read_view::AdmittedNodeRecord,
176 ) -> Result<ScanControl>,
177 ) -> Result<ScanControl> {
178 GraphStore::visit_nodes_by_access_with_allocation(
179 self,
180 label_id,
181 access,
182 admit,
183 &mut |node| consumer(node).map(to_store_control),
184 )
185 .map(to_execution_control)
186 }
187
188 fn visit_projected_nodes_admitted(
189 &self,
190 label_id: Option<LabelId>,
191 properties: &BTreeSet<String>,
192 admit: &mut dyn FnMut(
193 usize,
194 )
195 -> Result<Box<dyn hawdb_storage::read_view::GraphReadAllocation>>,
196 consumer: &mut dyn FnMut(ProjectedNodeRecord) -> Result<ScanControl>,
197 ) -> Result<ScanControl> {
198 GraphStore::visit_projected_nodes_admitted(self, label_id, properties, admit, &mut |node| {
199 consumer(node).map(to_store_control)
200 })
201 .map(to_execution_control)
202 }
203
204 fn visit_projected_nodes_with_allocation(
205 &self,
206 label_id: Option<LabelId>,
207 properties: &BTreeSet<String>,
208 admit: &mut hawdb_storage::read_view::ControlledGraphReadAllocator<'_>,
209 consumer: &mut dyn FnMut(
210 hawdb_storage::read_view::AdmittedProjectedNode,
211 ) -> Result<ScanControl>,
212 ) -> Result<ScanControl> {
213 GraphStore::visit_projected_nodes_with_allocation(
214 self,
215 label_id,
216 properties,
217 admit,
218 &mut |node| consumer(node).map(to_store_control),
219 )
220 .map(to_execution_control)
221 }
222
223 fn visit_projected_nodes_by_access_admitted(
224 &self,
225 label_id: LabelId,
226 access: &NodeProjectionAccess,
227 properties: &BTreeSet<String>,
228 admit: &mut dyn FnMut(
229 usize,
230 )
231 -> Result<Box<dyn hawdb_storage::read_view::GraphReadAllocation>>,
232 consumer: &mut dyn FnMut(ProjectedNodeRecord) -> Result<ScanControl>,
233 ) -> Result<ScanControl> {
234 GraphStore::visit_projected_nodes_by_access_admitted(
235 self,
236 label_id,
237 access,
238 properties,
239 admit,
240 &mut |node| consumer(node).map(to_store_control),
241 )
242 .map(to_execution_control)
243 }
244
245 fn visit_projected_nodes_by_property_admitted(
246 &self,
247 label_id: LabelId,
248 property: &str,
249 values: &[hawdb_core::Value],
250 properties: &BTreeSet<String>,
251 admit: &mut dyn FnMut(
252 usize,
253 )
254 -> Result<Box<dyn hawdb_storage::read_view::GraphReadAllocation>>,
255 consumer: &mut dyn FnMut(ProjectedNodeRecord) -> Result<ScanControl>,
256 ) -> Result<ScanControl> {
257 GraphStore::visit_projected_nodes_by_property_admitted(
258 self,
259 label_id,
260 property,
261 values,
262 properties,
263 admit,
264 &mut |node| consumer(node).map(to_store_control),
265 )
266 .map(to_execution_control)
267 }
268
269 fn visit_projected_nodes_owned(
270 &self,
271 label_id: Option<LabelId>,
272 required_properties: &BTreeSet<String>,
273 consumer: &mut dyn FnMut(ProjectedNodeRecord) -> Result<ScanControl>,
274 ) -> Result<ScanControl> {
275 let mut consumer_error = None;
276 let control =
277 GraphStore::visit_projected_nodes_owned(self, label_id, required_properties, |node| {
278 match consumer(node) {
279 Ok(control) => to_store_control(control),
280 Err(error) => {
281 consumer_error = Some(error);
282 GraphScanControl::Stop
283 }
284 }
285 })?;
286 match consumer_error {
287 Some(error) => Err(error),
288 None => Ok(to_execution_control(control)),
289 }
290 }
291
292 fn visit_projected_nodes_by_access_owned(
293 &self,
294 label_id: LabelId,
295 access: &NodeProjectionAccess,
296 required_properties: &BTreeSet<String>,
297 consumer: &mut dyn FnMut(ProjectedNodeRecord) -> Result<ScanControl>,
298 ) -> Result<ScanControl> {
299 let mut consumer_error = None;
300 let control = GraphStore::visit_projected_nodes_by_access_owned(
301 self,
302 label_id,
303 access,
304 required_properties,
305 |node| match consumer(node) {
306 Ok(control) => to_store_control(control),
307 Err(error) => {
308 consumer_error = Some(error);
309 GraphScanControl::Stop
310 }
311 },
312 )?;
313 match consumer_error {
314 Some(error) => Err(error),
315 None => Ok(to_execution_control(control)),
316 }
317 }
318
319 fn visit_projected_nodes_by_property_owned(
320 &self,
321 label_id: LabelId,
322 property: &str,
323 values: &[hawdb_core::Value],
324 required_properties: &BTreeSet<String>,
325 consumer: &mut dyn FnMut(ProjectedNodeRecord) -> Result<ScanControl>,
326 ) -> Result<ScanControl> {
327 let mut consumer_error = None;
328 let control = GraphStore::visit_projected_nodes_by_property_owned(
329 self,
330 label_id,
331 property,
332 values,
333 required_properties,
334 |node| match consumer(node) {
335 Ok(control) => to_store_control(control),
336 Err(error) => {
337 consumer_error = Some(error);
338 GraphScanControl::Stop
339 }
340 },
341 )?;
342 match consumer_error {
343 Some(error) => Err(error),
344 None => Ok(to_execution_control(control)),
345 }
346 }
347
348 fn visit_nodes_by_property_owned(
349 &self,
350 label_id: LabelId,
351 property: &str,
352 values: &[hawdb_core::Value],
353 consumer: &mut dyn FnMut(NodeRecord) -> Result<ScanControl>,
354 ) -> Result<ScanControl> {
355 let mut consumer_error = None;
356 let control =
357 GraphStore::visit_nodes_by_property_owned(self, label_id, property, values, |node| {
358 match consumer(node) {
359 Ok(control) => to_store_control(control),
360 Err(error) => {
361 consumer_error = Some(error);
362 GraphScanControl::Stop
363 }
364 }
365 })?;
366 match consumer_error {
367 Some(error) => Err(error),
368 None => Ok(to_execution_control(control)),
369 }
370 }
371
372 fn visit_nodes_by_composite_property_owned(
373 &self,
374 label_id: LabelId,
375 predicates: &[(String, hawdb_core::Value)],
376 consumer: &mut dyn FnMut(NodeRecord) -> Result<ScanControl>,
377 ) -> Result<ScanControl> {
378 adapt_node_consumer(consumer, |consumer| {
379 GraphStore::visit_nodes_by_composite_property_owned(
380 self, label_id, predicates, consumer,
381 )
382 })
383 }
384
385 fn visit_nodes_by_composite_range_owned(
386 &self,
387 label_id: LabelId,
388 seek: &hawdb_plan_cypher::CompositeRangeSeek,
389 consumer: &mut dyn FnMut(NodeRecord) -> Result<ScanControl>,
390 ) -> Result<ScanControl> {
391 adapt_node_consumer(consumer, |consumer| {
392 GraphStore::visit_nodes_by_composite_range_owned(self, label_id, seek, consumer)
393 })
394 }
395
396 fn visit_nodes_by_property_range_owned(
397 &self,
398 label_id: LabelId,
399 property: &str,
400 lower: Option<&(hawdb_core::Value, bool)>,
401 upper: Option<&(hawdb_core::Value, bool)>,
402 consumer: &mut dyn FnMut(NodeRecord) -> Result<ScanControl>,
403 ) -> Result<ScanControl> {
404 adapt_node_consumer(consumer, |consumer| {
405 GraphStore::visit_nodes_by_property_range_owned(
406 self, label_id, property, lower, upper, consumer,
407 )
408 })
409 }
410
411 fn visit_nodes_by_full_text_property_owned(
412 &self,
413 label_id: LabelId,
414 property: &str,
415 query: &str,
416 consumer: &mut dyn FnMut(NodeRecord) -> Result<ScanControl>,
417 ) -> Result<ScanControl> {
418 adapt_node_consumer(consumer, |consumer| {
419 GraphStore::visit_nodes_by_full_text_property_owned(
420 self, label_id, property, query, consumer,
421 )
422 })
423 }
424
425 fn projected_graph_definition(
426 &self,
427 name: &str,
428 ) -> Option<hawdb_storage::projection::ProjectedGraphDefinition> {
429 GraphStore::projected_graph_definition(self, name).cloned()
430 }
431
432 fn visit_source_scan_candidates(
433 &self,
434 predicate: &hawdb_storage::scan::ScanPredicate,
435 limits: SourceScanReadLimits,
436 task_context: Option<&hawdb_core::RuntimeTaskContext>,
437 consumer: &mut dyn FnMut(SourceScanCandidateRow) -> Result<ScanControl>,
438 ) -> Result<SourceScanCandidateVisit> {
439 let mut consumer_error = None;
440 let visit = GraphStore::visit_published_source_scan_candidates_bounded(
441 self,
442 predicate,
443 hawdb_storage::store::SourceScanCandidateLimits::bounded(
444 limits.io_depth,
445 limits.max_coalesced_bytes,
446 limits.max_wave_bytes,
447 limits.max_live_candidate_bytes,
448 ),
449 task_context,
450 &mut |row| match consumer(SourceScanCandidateRow {
451 node_id: row.node_id,
452 properties: row.properties,
453 }) {
454 Ok(control) => Ok(to_store_control(control)),
455 Err(error) => {
456 consumer_error = Some(error);
457 Ok(hawdb_storage::store::GraphScanControl::Stop)
458 }
459 },
460 )?;
461 if let Some(error) = consumer_error {
462 return Err(error);
463 }
464 Ok(match visit {
465 hawdb_storage::store::SourceScanCandidateVisit::Rows {
466 graph_epoch,
467 skipped_segment_count,
468 report,
469 candidate_count,
470 } => SourceScanCandidateVisit::Rows {
471 graph_epoch,
472 skipped_segment_count,
473 report,
474 candidate_count,
475 },
476 hawdb_storage::store::SourceScanCandidateVisit::Fallback(reason) => {
477 SourceScanCandidateVisit::Fallback(reason)
478 }
479 })
480 }
481
482 fn visit_adjacent_relationships_owned(
483 &self,
484 node_id: NodeId,
485 rel_type: Option<RelTypeId>,
486 direction: AdjacencyDirection,
487 consumer: &mut dyn FnMut(RelRecord) -> Result<ScanControl>,
488 ) -> Result<ScanControl> {
489 GraphStore::try_visit_adjacent_relationships_owned(
490 self,
491 node_id,
492 rel_type,
493 direction,
494 |relationship| consumer(relationship).map(to_store_control),
495 )
496 .map(to_execution_control)
497 }
498
499 fn visit_ordered_adjacent_relationships_owned(
500 &self,
501 node_id: NodeId,
502 rel_type: Option<RelTypeId>,
503 direction: AdjacencyDirection,
504 memory: AdjacencyReadMemory<'_>,
505 consumer: &mut dyn FnMut(RelRecord) -> Result<ScanControl>,
506 ) -> Result<ScanControl> {
507 GraphExecutionRead::visit_ordered_adjacent_relationships_with_allocation(
508 self,
509 node_id,
510 rel_type,
511 direction,
512 memory,
513 &mut |bytes| memory.admit_node(bytes, 0, None).map(Some),
514 &mut |relationship| {
515 let (relationship, _allocation) = relationship.into_parts();
516 consumer(relationship)
517 },
518 )
519 }
520
521 fn visit_ordered_adjacent_relationships_with_allocation(
522 &self,
523 node_id: NodeId,
524 rel_type: Option<RelTypeId>,
525 direction: AdjacencyDirection,
526 memory: AdjacencyReadMemory<'_>,
527 admit: &mut hawdb_storage::read_view::ControlledGraphReadAllocator<'_>,
528 consumer: &mut dyn FnMut(
529 hawdb_storage::read_view::AdmittedRelationshipRecord,
530 ) -> Result<ScanControl>,
531 ) -> Result<ScanControl> {
532 GraphStore::visit_ordered_adjacent_relationships_with_allocation(
533 self,
534 node_id,
535 rel_type,
536 direction,
537 memory.budget_bytes,
538 admit,
539 |relationship| consumer(relationship).map(to_store_control),
540 )
541 .map(to_execution_control)
542 }
543
544 fn visit_filtered_ordered_relationships_with_allocation(
545 &self,
546 adjacency: (NodeId, Option<RelTypeId>, AdjacencyDirection),
547 filter: &PropertyFilter,
548 memory: AdjacencyReadMemory<'_>,
549 admit: &mut hawdb_storage::read_view::ControlledGraphReadAllocator<'_>,
550 consumer: &mut dyn FnMut(
551 hawdb_storage::read_view::AdmittedRelationshipRecord,
552 ) -> Result<ScanControl>,
553 ) -> Result<(ScanControl, Option<hawdb_storage::scan::ScanPruningReport>)> {
554 GraphStore::visit_filtered_ordered_relationships_with_allocation(
555 self,
556 adjacency,
557 filter,
558 memory.budget_bytes,
559 admit,
560 |row| consumer(row).map(to_store_control),
561 )
562 .map(|(control, report)| (to_execution_control(control), report))
563 }
564
565 fn visit_adjacent_relationships_with_filter_owned(
566 &self,
567 node_id: NodeId,
568 rel_type: Option<RelTypeId>,
569 direction: AdjacencyDirection,
570 filter: &PropertyFilter,
571 consumer: &mut dyn FnMut(RelRecord) -> Result<ScanControl>,
572 ) -> Result<(ScanControl, Option<hawdb_storage::scan::ScanPruningReport>)> {
573 let mut consumer_error = None;
574 let (control, report) = GraphStore::visit_adjacent_relationships_with_filter_owned(
575 self,
576 node_id,
577 rel_type,
578 direction,
579 filter,
580 |relationship| match consumer(relationship) {
581 Ok(control) => to_store_control(control),
582 Err(error) => {
583 consumer_error = Some(error);
584 GraphScanControl::Stop
585 }
586 },
587 )?;
588 match consumer_error {
589 Some(error) => Err(error),
590 None => Ok((to_execution_control(control), report)),
591 }
592 }
593
594 fn visit_ordered_adjacent_relationships_with_filter_owned(
595 &self,
596 node_id: NodeId,
597 rel_type: Option<RelTypeId>,
598 direction: AdjacencyDirection,
599 filter: &PropertyFilter,
600 memory: AdjacencyReadMemory<'_>,
601 consumer: &mut dyn FnMut(RelRecord) -> Result<ScanControl>,
602 ) -> Result<(ScanControl, Option<hawdb_storage::scan::ScanPruningReport>)> {
603 GraphStore::visit_filtered_ordered_relationships_with_allocation(
604 self,
605 (node_id, rel_type, direction),
606 filter,
607 memory.budget_bytes,
608 &mut |bytes| memory.admit_node(bytes, 0, None).map(Some),
609 |row| {
610 let (relationship, _allocation) = row.into_parts();
611 consumer(relationship).map(to_store_control)
612 },
613 )
614 .map(|(control, report)| (to_execution_control(control), report))
615 }
616
617 fn visit_relationships_owned(
618 &self,
619 rel_type: Option<RelTypeId>,
620 consumer: &mut dyn FnMut(RelRecord) -> Result<ScanControl>,
621 ) -> Result<ScanControl> {
622 GraphStore::try_visit_relationships_owned(self, rel_type, |relationship| {
623 consumer(relationship).map(to_store_control)
624 })
625 .map(to_execution_control)
626 }
627
628 fn scan_relationships_with_filter_pruning<'a>(
629 &'a self,
630 rel_type: Option<RelTypeId>,
631 filter: Option<&PropertyFilter>,
632 ) -> Result<PrunedRelationshipScan<'a>> {
633 let scan = GraphStore::scan_relationships_with_filter_pruning(self, rel_type, filter);
634 Ok(PrunedRelationshipScan {
635 relationships: Box::new(scan.relationships.into_iter().cloned()),
636 report: scan.report,
637 })
638 }
639
640 fn scan_nodes_with_filter_pruning<'a>(
641 &'a self,
642 catalog: &Catalog,
643 label_id: Option<LabelId>,
644 filter: Option<&PropertyFilter>,
645 ) -> Result<PrunedNodeScan<'a>> {
646 let scan = GraphStore::scan_nodes_with_filter_pruning(self, catalog, label_id, filter);
647 Ok(PrunedNodeScan {
648 nodes: Box::new(scan.nodes.into_iter().map(std::borrow::Cow::Borrowed)),
649 report: scan.report,
650 })
651 }
652
653 fn scan_nodes_with_filter_pruning_admitted<'a>(
654 &'a self,
655 catalog: &Catalog,
656 label_id: Option<LabelId>,
657 filter: Option<&PropertyFilter>,
658 admit: &mut hawdb_storage::read_view::GraphReadAllocator<'_>,
659 ) -> Result<PrunedNodeScan<'a>> {
660 let (scan, allocation) = GraphStore::scan_nodes_with_filter_pruning_admitted(
661 self, catalog, label_id, filter, admit,
662 )?;
663 Ok(PrunedNodeScan {
664 nodes: Box::new(scan.nodes.into_iter().map(move |node| {
665 let _retained_allocation = &allocation;
666 std::borrow::Cow::Borrowed(node)
667 })),
668 report: scan.report,
669 })
670 }
671}
672
673impl GraphExecutionWrite for GraphStore {
674 fn commit_mutation_with_limits(
675 &mut self,
676 catalog: &mut Catalog,
677 mutation: GraphMutation,
678 limits: MutationLimits,
679 ) -> Result<MutationSummary> {
680 GraphStore::commit_mutation_with_limits(self, catalog, mutation, limits)
681 }
682
683 fn commit_mutations_with_limits(
684 &mut self,
685 catalog: &mut Catalog,
686 mutations: Vec<GraphMutation>,
687 limits: MutationLimits,
688 ) -> Result<MutationSummary> {
689 GraphStore::commit_mutations_with_limits(self, catalog, mutations, limits)
690 }
691
692 fn set_node_properties_by_ids_with_limits(
693 &mut self,
694 catalog: &mut Catalog,
695 ids: &[NodeId],
696 assignments: &[NodeSetAssignment],
697 limits: MutationLimits,
698 ) -> Result<Vec<NodeId>> {
699 GraphStore::set_node_properties_by_ids_with_limits(self, catalog, ids, assignments, limits)
700 }
701
702 fn delete_node_ids_with_limits(
703 &mut self,
704 catalog: &mut Catalog,
705 ids: &[NodeId],
706 detach: bool,
707 limits: MutationLimits,
708 ) -> Result<Vec<NodeId>> {
709 GraphStore::delete_node_ids_with_limits(self, catalog, ids, detach, limits)
710 }
711}