1use std::ops::Range;
2use std::sync::Arc;
3
4use parking_lot::RwLock;
5
6use crate::executor::streaming::chunk::DataChunk;
7use crate::executor::streaming::plan::types::PhysicalOperatorId;
8use crate::executor::streaming::runtime::ExecutionRuntime;
9use crate::executor::streaming::slot::SlotLayout;
10use crate::executor::streaming::state::{GlobalState, GlobalStateKey, StateArenaSet};
11use crate::storage::EdgeCursor;
12use crate::storage::IndexCursor;
13use crate::storage::IndexRow;
14use crate::storage::ScanPredicate;
15use crate::storage::ShardSlice;
16use crate::storage::VertexCursor;
17use linkrs_core::error::QueryError;
18use linkrs_core::types::storage_ids::VertexId;
19use linkrs_core::Value;
20
21use super::spec::{BoundIndexPredicate, IndexProjection};
22
23mod buffered;
24mod index_scan;
25mod neighbors;
26mod point_lookup;
27mod storage_scan;
28mod util;
29
30pub use neighbors::NeighborScanState;
31pub use storage_scan::{column_block_enabled, set_column_block_enabled};
32
33#[derive(Debug, Clone, Copy)]
39pub struct OperatorConfig {
40 pub chunk_size: usize,
41 pub partition_id: Option<usize>,
42 pub physical_operator_id: PhysicalOperatorId,
43}
44
45impl Default for OperatorConfig {
46 fn default() -> Self {
47 Self {
48 chunk_size: 2048,
49 partition_id: None,
50 physical_operator_id: PhysicalOperatorId(0),
51 }
52 }
53}
54
55#[derive(Debug)]
61pub enum SourceOperatorKind {
62 ScanVertices {
64 buffer: Vec<Vec<Value>>,
65 current_index: usize,
66 col_names: Vec<String>,
67 },
68 CteScan {
72 cte_name: String,
73 col_names: Vec<String>,
74 buffer: Vec<Vec<Value>>,
75 current_index: usize,
76 },
77 StandaloneValues {
81 values: Vec<Vec<linkrs_core::types::expr::ContextualExpression>>,
82 buffer: Vec<Vec<Value>>,
83 current_index: usize,
84 col_names: Vec<String>,
85 },
86 StorageScanVertices {
88 storage: Option<Arc<RwLock<dyn crate::storage::QueryStorage>>>,
89 space_name: String,
90 limit: Option<usize>,
91 partition_range: Option<std::ops::Range<i64>>,
92 shard_slice: Option<crate::storage::ShardSlice>,
95 col_names: Vec<String>,
96 projected_properties: Vec<Arc<str>>,
97 identity_only: bool,
100 skip_rows: bool,
103 predicate: Vec<ScanPredicate>,
105 tag: Option<String>,
108 semi_mask: Option<Vec<u32>>,
111 cursor: Option<Box<dyn VertexCursor>>,
112 },
113 ScanEdges {
115 buffer: Vec<Vec<Value>>,
116 current_index: usize,
117 col_names: Vec<String>,
118 },
119 StorageScanEdges {
121 storage: Option<Arc<RwLock<dyn crate::storage::QueryStorage>>>,
122 space_name: String,
123 limit: Option<usize>,
124 edge_type: Option<String>,
125 shard_slice: Option<ShardSlice>,
126 col_names: Vec<String>,
127 projected_properties: Vec<Arc<str>>,
128 identity_only: bool,
132 predicate: Vec<ScanPredicate>,
134 cursor: Option<Box<dyn EdgeCursor>>,
135 },
136 GetVertices {
138 storage: Option<Arc<RwLock<dyn crate::storage::QueryStorage>>>,
139 space_name: String,
140 tag: String,
141 vertex_ids: Option<Vec<Value>>,
142 cached_ids: Vec<VertexId>,
143 projected_properties: Vec<Arc<str>>,
144 },
145 GetEdges {
147 storage: Option<Arc<RwLock<dyn crate::storage::QueryStorage>>>,
148 space_name: String,
149 edge_type: Option<String>,
150 src: Option<String>,
151 dst: Option<String>,
152 rank: i64,
153 projected_properties: Vec<Arc<str>>,
154 cursor: Option<Box<dyn EdgeCursor>>,
155 },
156 GetNeighbors {
158 storage: Option<Arc<RwLock<dyn crate::storage::QueryStorage>>>,
159 space_name: String,
160 tag: String,
161 direction: String,
162 projected_properties: Vec<Arc<str>>,
163 state: NeighborScanState,
164 },
165 IndexScan {
167 storage: Option<Arc<RwLock<dyn crate::storage::QueryStorage>>>,
168 space_name: String,
169 index_name: String,
170 index_id: u64,
171 tag: String,
172 predicate: BoundIndexPredicate,
173 projection: IndexProjection,
174 output_layout: Arc<SlotLayout>,
175 partition_range: Option<Range<i64>>,
176 cursor: Option<Box<dyn IndexCursor<Row = IndexRow>>>,
177 edge_type_names: std::collections::HashMap<u32, String>,
178 },
179 Argument,
180 GetProp {
182 storage: Option<Arc<RwLock<dyn crate::storage::QueryStorage>>>,
183 space_name: String,
184 entity_slot: usize,
185 prop_names: Vec<String>,
186 is_vertex: bool,
187 output_layout: Arc<SlotLayout>,
188 },
189 Start,
190}
191
192#[derive(Debug)]
198pub struct SourceOperator {
199 pub kind: SourceOperatorKind,
200 pub runtime: Option<Arc<ExecutionRuntime>>,
201 pub output_layout: Arc<SlotLayout>,
202 pub config: OperatorConfig,
203 pub frame: Option<(Arc<SlotLayout>, Vec<Value>)>,
208}
209
210fn shared_names(names: &[String]) -> Vec<Arc<str>> {
214 names.iter().map(|name| Arc::from(name.as_str())).collect()
215}
216
217impl SourceOperator {
218 pub fn from_spec(
223 spec: &super::spec::SourceSpec,
224 storage: Option<Arc<RwLock<dyn crate::storage::QueryStorage>>>,
225 output_layout: Arc<SlotLayout>,
226 ) -> Self {
227 let kind = match spec {
228 super::spec::SourceSpec::ScanVertices { rows, col_names } => {
229 SourceOperatorKind::ScanVertices {
230 buffer: rows.clone(),
231 current_index: 0,
232 col_names: col_names.clone(),
233 }
234 }
235 super::spec::SourceSpec::StandaloneValues { values, col_names } => {
236 SourceOperatorKind::StandaloneValues {
237 values: values.clone(),
238 buffer: Vec::new(),
239 current_index: 0,
240 col_names: col_names.clone(),
241 }
242 }
243 super::spec::SourceSpec::CteScan {
244 cte_name,
245 col_names,
246 } => SourceOperatorKind::CteScan {
247 cte_name: cte_name.clone(),
248 col_names: col_names.clone(),
249 buffer: Vec::new(),
250 current_index: 0,
251 },
252 super::spec::SourceSpec::StorageScanVertices {
253 space_name,
254 limit,
255 col_names,
256 projected_properties,
257 identity_only,
258 skip_rows,
259 predicate,
260 tag,
261 partition_range,
262 shard_slice,
263 } => SourceOperatorKind::StorageScanVertices {
264 storage: storage.clone(),
265 space_name: space_name.clone(),
266 limit: *limit,
267 partition_range: partition_range.clone(),
268 shard_slice: *shard_slice,
269 col_names: col_names.clone(),
270 projected_properties: shared_names(projected_properties),
271 identity_only: *identity_only,
272 skip_rows: *skip_rows,
273 predicate: predicate.clone(),
274 tag: tag.clone(),
275 semi_mask: None,
276 cursor: None,
277 },
278 super::spec::SourceSpec::ScanEdges { rows, col_names } => {
279 SourceOperatorKind::ScanEdges {
280 buffer: rows.clone(),
281 current_index: 0,
282 col_names: col_names.clone(),
283 }
284 }
285 super::spec::SourceSpec::StorageScanEdges {
286 space_name,
287 limit,
288 edge_type,
289 col_names,
290 projected_properties,
291 identity_only,
292 predicate,
293 shard_slice,
294 } => SourceOperatorKind::StorageScanEdges {
295 storage: storage.clone(),
296 space_name: space_name.clone(),
297 limit: *limit,
298 edge_type: edge_type.clone(),
299 shard_slice: *shard_slice,
300 col_names: col_names.clone(),
301 projected_properties: shared_names(projected_properties),
302 identity_only: *identity_only,
303 predicate: predicate.clone(),
304 cursor: None,
305 },
306 super::spec::SourceSpec::GetVertices {
307 space_name,
308 tag,
309 vertex_ids,
310 projected_properties,
311 col_names: _,
312 } => SourceOperatorKind::GetVertices {
313 storage: storage.clone(),
314 space_name: space_name.clone(),
315 tag: tag.clone(),
316 vertex_ids: vertex_ids.clone(),
317 cached_ids: Vec::new(),
318 projected_properties: shared_names(projected_properties),
319 },
320 super::spec::SourceSpec::GetEdges {
321 space_name,
322 edge_type,
323 src,
324 dst,
325 rank,
326 projected_properties,
327 } => SourceOperatorKind::GetEdges {
328 storage: storage.clone(),
329 space_name: space_name.clone(),
330 edge_type: edge_type.clone(),
331 src: src.clone(),
332 dst: dst.clone(),
333 rank: *rank,
334 projected_properties: shared_names(projected_properties),
335 cursor: None,
336 },
337 super::spec::SourceSpec::GetNeighbors {
338 space_name,
339 tag,
340 direction,
341 projected_properties,
342 } => SourceOperatorKind::GetNeighbors {
343 storage: storage.clone(),
344 space_name: space_name.clone(),
345 tag: tag.clone(),
346 direction: direction.clone(),
347 projected_properties: shared_names(projected_properties),
348 state: NeighborScanState::Init,
349 },
350 super::spec::SourceSpec::IndexScan {
351 space_name,
352 index_name,
353 index_id,
354 tag,
355 predicate,
356 projection,
357 output_layout,
358 ..
359 } => SourceOperatorKind::IndexScan {
360 storage: storage.clone(),
361 space_name: space_name.clone(),
362 index_name: index_name.clone(),
363 index_id: *index_id,
364 tag: tag.clone(),
365 predicate: (**predicate).clone(),
366 projection: projection.clone(),
367 output_layout: output_layout.clone(),
368 partition_range: None,
369 cursor: None,
370 edge_type_names: std::collections::HashMap::new(),
371 },
372 super::spec::SourceSpec::Argument { .. } => SourceOperatorKind::Argument,
373 super::spec::SourceSpec::GetProp {
374 space_name,
375 entity_slot,
376 prop_names,
377 is_vertex,
378 output_layout,
379 } => SourceOperatorKind::GetProp {
380 storage: storage.clone(),
381 space_name: space_name.clone(),
382 entity_slot: *entity_slot,
383 prop_names: prop_names.clone(),
384 is_vertex: *is_vertex,
385 output_layout: output_layout.clone(),
386 },
387 super::spec::SourceSpec::Start => SourceOperatorKind::Start,
388 };
389 Self {
390 kind,
391 runtime: None,
392 output_layout,
393 config: OperatorConfig {
394 chunk_size: 2048,
395 partition_id: None,
396 physical_operator_id: PhysicalOperatorId(0),
397 },
398 frame: None,
399 }
400 }
401
402 pub fn new(kind: SourceOperatorKind, output_layout: Arc<SlotLayout>) -> Self {
403 Self {
404 kind,
405 runtime: None,
406 output_layout,
407 config: OperatorConfig {
408 chunk_size: 2048,
409 partition_id: None,
410 physical_operator_id: PhysicalOperatorId(0),
411 },
412 frame: None,
413 }
414 }
415
416 pub fn set_semi_mask(&mut self, ids: Vec<u32>) {
420 if let SourceOperatorKind::StorageScanVertices { semi_mask, .. } = &mut self.kind {
421 *semi_mask = Some(ids);
422 }
423 }
424
425 pub fn inject_context(
428 &mut self,
429 runtime: Option<&Arc<ExecutionRuntime>>,
430 config: OperatorConfig,
431 ) {
432 if let Some(rt) = runtime {
433 self.runtime = Some(rt.clone());
434 }
435 self.config = config;
436 }
437
438 fn state_key(&self) -> GlobalStateKey {
439 GlobalStateKey(self.config.physical_operator_id, self.config.partition_id)
440 }
441
442 fn state_arena(&self) -> parking_lot::MutexGuard<'_, StateArenaSet> {
443 self.runtime
444 .as_ref()
445 .expect("runtime required")
446 .state_arena_for(self.config.partition_id)
447 .lock()
448 }
449
450 fn insert_state(&mut self, state: GlobalState) {
451 let Some(rt) = self.runtime.as_ref() else {
452 return;
453 };
454 let key = self.state_key();
455 rt.state_arena_for(self.config.partition_id)
456 .lock()
457 .global
458 .insert(key, state);
459 }
460
461 fn take_state(&mut self) -> Option<GlobalState> {
462 let rt = self.runtime.as_ref()?;
463 let key = self.state_key();
464 rt.state_arena_for(self.config.partition_id)
465 .lock()
466 .global
467 .remove(&key)
468 }
469
470 pub fn open(&mut self) -> Result<(), QueryError> {
471 use crate::executor::streaming::operators::state::SourceState;
472 use crate::executor::streaming::state::GlobalState;
473 let mut state: Option<GlobalState> = None;
474 match &mut self.kind {
475 SourceOperatorKind::ScanVertices { .. }
476 | SourceOperatorKind::StandaloneValues { .. }
477 | SourceOperatorKind::ScanEdges { .. } => buffered::open(self)?,
478 SourceOperatorKind::StorageScanVertices { .. }
479 | SourceOperatorKind::StorageScanEdges { .. } => storage_scan::open(self)?,
480 SourceOperatorKind::GetVertices { .. } | SourceOperatorKind::GetEdges { .. } => {
481 point_lookup::open(self)?
482 }
483 SourceOperatorKind::GetNeighbors { .. } => neighbors::open(self)?,
484 SourceOperatorKind::IndexScan { .. } => index_scan::open(self)?,
485 SourceOperatorKind::CteScan { .. } => Self::open_cte_scan(self)?,
486 SourceOperatorKind::Argument => {
487 state = Some(GlobalState::Source(SourceState::Argument));
488 }
489 SourceOperatorKind::GetProp {
490 entity_slot,
491 prop_names,
492 ..
493 } => {
494 state = Some(GlobalState::Source(SourceState::GetProp {
495 entity_slot: *entity_slot,
496 prop_names: prop_names.clone(),
497 }));
498 }
499 SourceOperatorKind::Start => {
500 state = Some(GlobalState::Source(SourceState::Start { emitted: false }));
501 }
502 }
503 if let Some(state) = state {
504 self.insert_state(state);
505 }
506 Ok(())
507 }
508
509 #[allow(clippy::should_implement_trait)]
510 pub fn next(&mut self) -> Result<Option<DataChunk>, QueryError> {
511 use crate::executor::streaming::operators::state::SourceState;
512 use crate::executor::streaming::state::GlobalState;
513 match &mut self.kind {
514 SourceOperatorKind::ScanVertices { .. }
515 | SourceOperatorKind::StandaloneValues { .. }
516 | SourceOperatorKind::ScanEdges { .. } => buffered::next(self),
517 SourceOperatorKind::StorageScanVertices { .. }
518 | SourceOperatorKind::StorageScanEdges { .. } => storage_scan::next(self),
519 SourceOperatorKind::GetVertices { .. } | SourceOperatorKind::GetEdges { .. } => {
520 point_lookup::next(self)
521 }
522 SourceOperatorKind::GetNeighbors { .. } => neighbors::next(self),
523 SourceOperatorKind::IndexScan { .. } => index_scan::next(self),
524 SourceOperatorKind::CteScan { .. } => Self::next_cte_scan(self),
525 SourceOperatorKind::GetProp { .. } => Err(QueryError::execution(
526 "GetProp is not available as a source operator; \
527 use the unary GetProp operator"
528 .to_string(),
529 )),
530 SourceOperatorKind::Start => {
531 let mut arena = self.state_arena();
532 let s = arena.global.get_mut(&self.state_key());
533 let emitted = match s {
534 Some(GlobalState::Source(SourceState::Start { ref mut emitted })) => emitted,
535 _ => return Ok(None),
536 };
537 if *emitted {
538 return Ok(None);
539 }
540 *emitted = true;
541 let chunk =
542 DataChunk::new_with_layout(vec![Vec::new()], Arc::clone(&self.output_layout));
543 Ok(Some(chunk))
544 }
545 SourceOperatorKind::Argument => {
546 let frame = self.frame.take();
547 match frame {
548 Some((layout, row)) => {
549 let projected: Vec<Value> = self
555 .output_layout
556 .names()
557 .iter()
558 .map(|name| {
559 layout
560 .slot_id(name)
561 .and_then(|slot| row.get(slot).cloned())
562 .unwrap_or_else(|| {
563 Value::Null(linkrs_core::value::NullType::Null)
564 })
565 })
566 .collect();
567 let chunk = DataChunk::new_with_layout(
568 vec![projected],
569 Arc::clone(&self.output_layout),
570 );
571 Ok(Some(chunk))
572 }
573 None => Ok(None),
574 }
575 }
576 }
577 }
578
579 pub fn stop(&mut self) -> Result<(), QueryError> {
580 Ok(())
581 }
582
583 pub fn reset(&mut self) -> Result<bool, QueryError> {
590 use crate::executor::streaming::operators::state::SourceState;
591 use crate::executor::streaming::state::GlobalState;
592 match &mut self.kind {
593 SourceOperatorKind::ScanVertices { current_index, .. }
594 | SourceOperatorKind::ScanEdges { current_index, .. }
595 | SourceOperatorKind::StandaloneValues { current_index, .. } => {
596 *current_index = 0;
597 }
598 SourceOperatorKind::StorageScanVertices { .. }
599 | SourceOperatorKind::StorageScanEdges { .. } => {
600 storage_scan::open(self)?;
601 }
602 SourceOperatorKind::GetVertices { .. } | SourceOperatorKind::GetEdges { .. } => {
603 point_lookup::open(self)?
604 }
605 SourceOperatorKind::GetNeighbors { .. } => neighbors::open(self)?,
606 SourceOperatorKind::IndexScan { .. } => index_scan::open(self)?,
607 SourceOperatorKind::CteScan { .. } => {
608 Self::open_cte_scan(self)?;
611 }
612 SourceOperatorKind::Argument => {
613 }
616 SourceOperatorKind::GetProp { .. } => {
617 }
619 SourceOperatorKind::Start => {
620 if let Some(rt) = &self.runtime {
621 let key = self.state_key();
622 let mut arena = rt.state_arena_for(self.config.partition_id).lock();
623 if let Some(GlobalState::Source(SourceState::Start { emitted })) =
624 arena.global.get_mut(&key)
625 {
626 *emitted = false;
627 }
628 }
629 }
630 }
631 Ok(false)
632 }
633
634 pub fn close(&mut self) -> Result<(), QueryError> {
635 self.take_state();
636 Ok(())
637 }
638
639 fn open_cte_scan(op: &mut SourceOperator) -> Result<(), QueryError> {
643 let (cte_name, buffer, current_index) = match &mut op.kind {
644 SourceOperatorKind::CteScan {
645 cte_name,
646 buffer,
647 current_index,
648 ..
649 } => (cte_name.clone(), buffer, current_index),
650 _ => {
651 return Err(QueryError::execution(
652 "open_cte_scan called for a non-CTE source".to_string(),
653 ));
654 }
655 };
656 let rows = op
657 .runtime
658 .as_ref()
659 .and_then(|rt| rt.cte_table(&cte_name))
660 .unwrap_or_default();
661 *buffer = rows;
662 *current_index = 0;
663 Ok(())
664 }
665
666 fn next_cte_scan(op: &mut SourceOperator) -> Result<Option<DataChunk>, QueryError> {
668 let layout = Arc::clone(&op.output_layout);
669 let chunk_size = op.config.chunk_size;
670 let (buffer, current_index) = match &mut op.kind {
671 SourceOperatorKind::CteScan {
672 buffer,
673 current_index,
674 ..
675 } => (buffer, current_index),
676 _ => {
677 return Err(QueryError::execution(
678 "next_cte_scan called for a non-CTE source".to_string(),
679 ));
680 }
681 };
682 if *current_index >= buffer.len() {
683 return Ok(None);
684 }
685 let end = (*current_index + chunk_size).min(buffer.len());
686 let rows = buffer[*current_index..end].to_vec();
687 *current_index = end;
688 Ok(Some(DataChunk::new_with_layout(rows, layout)))
689 }
690}
691
692#[cfg(test)]
693#[cfg(test)]
694mod tests;