Skip to main content

uqa_execution/batch/
row_lock_origins.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Row-lock lineage carried on composite physical rows.
8
9use std::sync::Arc;
10
11use super::PhysicalRow;
12
13/// Base-table identity carried on a composite physical row for `FOR UPDATE`.
14///
15/// Origins ride beside value fragments. Joins concatenate them; projections and schema remaps keep them. They are not SQL-visible columns and must not be rebuilt from named maps.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct RowLockOrigin {
18    /// Visible qualifier of the row source that currently owns the origin. Views, CTEs, and derived tables rebind it to their own alias.
19    pub qualifier: Arc<str>,
20    /// Qualifier of the base scan that produced the origin. Rebinding at a derived-table boundary leaves it untouched, so a tuple recheck can pin each base scan inside a view or subquery to its own tuples.
21    pub scan_qualifier: Arc<str>,
22    pub storage_name: Arc<str>,
23    pub doc_id: uqa_core::DocId,
24}
25
26impl RowLockOrigin {
27    #[must_use]
28    pub fn new(
29        qualifier: impl Into<String>,
30        storage_name: impl Into<String>,
31        doc_id: uqa_core::DocId,
32    ) -> Self {
33        Self::from_shared(
34            Arc::<str>::from(qualifier.into()),
35            Arc::<str>::from(storage_name.into()),
36            doc_id,
37        )
38    }
39
40    /// Build an origin from source names shared by every row in one scan.
41    #[must_use]
42    pub fn from_shared(
43        qualifier: Arc<str>,
44        storage_name: Arc<str>,
45        doc_id: uqa_core::DocId,
46    ) -> Self {
47        Self {
48            scan_qualifier: Arc::clone(&qualifier),
49            qualifier,
50            storage_name,
51            doc_id,
52        }
53    }
54}
55
56pub(super) fn concat_lock_origins(
57    left: Option<&Arc<Vec<RowLockOrigin>>>,
58    right: Option<&Arc<Vec<RowLockOrigin>>>,
59) -> Option<Arc<Vec<RowLockOrigin>>> {
60    match (left, right) {
61        (None, None) => None,
62        (Some(origins), None) | (None, Some(origins)) => Some(Arc::clone(origins)),
63        (Some(left), Some(right)) => {
64            let mut origins = Vec::with_capacity(left.len() + right.len());
65            origins.extend(left.iter().cloned());
66            origins.extend(right.iter().cloned());
67            Some(Arc::new(origins))
68        }
69    }
70}
71
72impl PhysicalRow {
73    #[must_use]
74    pub fn with_lock_origin(mut self, origin: RowLockOrigin) -> Self {
75        self.lock_origins = match self.lock_origins.take() {
76            None => Some(Arc::new(vec![origin])),
77            Some(existing) => {
78                let mut origins = Vec::with_capacity(existing.len() + 1);
79                origins.extend(existing.iter().cloned());
80                origins.push(origin);
81                Some(Arc::new(origins))
82            }
83        };
84        self
85    }
86
87    #[must_use]
88    pub fn with_lock_origins(mut self, origins: impl IntoIterator<Item = RowLockOrigin>) -> Self {
89        let origins = origins.into_iter().collect::<Vec<_>>();
90        if origins.is_empty() {
91            return self;
92        }
93        self.lock_origins = match self.lock_origins.take() {
94            None => Some(Arc::new(origins)),
95            Some(existing) => {
96                let mut combined = Vec::with_capacity(existing.len() + origins.len());
97                combined.extend(existing.iter().cloned());
98                combined.extend(origins);
99                Some(Arc::new(combined))
100            }
101        };
102        self
103    }
104
105    #[must_use]
106    pub fn lock_origins(&self) -> &[RowLockOrigin] {
107        self.lock_origins
108            .as_deref()
109            .map_or(&[], std::vec::Vec::as_slice)
110    }
111
112    /// Drop row-lock lineage at an execution boundary that cannot expose a lockable base-row identity, such as a set operation.
113    #[must_use]
114    pub fn without_lock_origins(mut self) -> Self {
115        self.lock_origins = None;
116        self
117    }
118
119    /// Remove all row-lock identities without reallocating the row payload.
120    pub fn discard_lock_origins_mut(&mut self) {
121        self.lock_origins = None;
122    }
123
124    /// Point every lock origin at the visible source qualifier. Views, CTEs, and subqueries keep inner storage names so `FOR UPDATE OF` that alias locks only those origins after a join.
125    #[must_use]
126    pub fn rebind_lock_origin_qualifiers(mut self, qualifier: impl Into<Arc<str>>) -> Self {
127        self.rebind_lock_origin_qualifiers_mut(qualifier.into());
128        self
129    }
130
131    pub fn rebind_lock_origin_qualifiers_mut(&mut self, qualifier: Arc<str>) {
132        let Some(origins) = self.lock_origins.as_mut() else {
133            return;
134        };
135        for origin in Arc::make_mut(origins) {
136            origin.qualifier = Arc::clone(&qualifier);
137        }
138    }
139}