zeppelin_embed/ingest/
delete_matching.rs1use crate::lifecycle::{QueryError, Store, StoreError, StoreState};
5use crate::meta::Predicate;
6use crate::planner::PlanError;
7
8use super::{DocId, IngestError, PurgeError};
9
10#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct DeleteMatchingReport {
13 deleted_ids: Vec<DocId>,
14 generation: u64,
15}
16
17impl DeleteMatchingReport {
18 #[must_use]
20 pub fn deleted_ids(&self) -> &[DocId] {
21 &self.deleted_ids
22 }
23
24 #[must_use]
27 pub const fn generation(&self) -> u64 {
28 self.generation
29 }
30}
31
32#[derive(Debug)]
34pub enum DeleteMatchingError {
35 Predicate(PlanError),
37 Query(QueryError),
39 Delete(IngestError),
41 Purge(PurgeError),
44}
45
46impl std::fmt::Display for DeleteMatchingError {
47 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 match self {
49 Self::Predicate(error) => write!(formatter, "delete filter: {error}"),
50 Self::Query(error) => write!(formatter, "delete match: {error}"),
51 Self::Delete(error) => write!(formatter, "delete: {error}"),
52 Self::Purge(error) => write!(formatter, "delete purge: {error}"),
53 }
54 }
55}
56
57impl std::error::Error for DeleteMatchingError {
58 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
59 match self {
60 Self::Predicate(error) => Some(error),
61 Self::Query(error) => Some(error),
62 Self::Delete(error) => Some(error),
63 Self::Purge(error) => Some(error),
64 }
65 }
66}
67
68impl From<StoreError> for DeleteMatchingError {
69 fn from(error: StoreError) -> Self {
70 Self::Purge(PurgeError::Store(error))
71 }
72}
73
74impl Store {
75 pub fn delete_matching(
90 &self,
91 predicate: &Predicate,
92 ) -> Result<DeleteMatchingReport, DeleteMatchingError> {
93 crate::planner::validate_predicate(predicate, &self.schema)
94 .map_err(DeleteMatchingError::Predicate)?;
95 let _maintenance = self
96 .maintenance
97 .lock()
98 .map_err(|_| StoreError::Synchronization {
99 component: "delete matching",
100 })?;
101 #[cfg(feature = "graph-cypher")]
102 let mut native_writer = self.native_document_writer()?;
103 #[cfg(feature = "graph-cypher")]
104 let native_deleted = if let Some(writer) =
105 native_writer.as_mut().and_then(|slot| slot.as_mut())
106 {
107 let ids = {
108 let active = self
109 .active
110 .lock()
111 .map_err(|_| StoreError::Synchronization {
112 component: "active segment",
113 })?;
114 let current = active.as_ref().ok_or(StoreError::Closed)?;
115 let snapshot = self
116 .snapshot
117 .read()
118 .map_err(|_| StoreError::Synchronization {
119 component: "published snapshot",
120 })?;
121 let snapshot = snapshot.as_ref().ok_or(StoreError::Closed)?;
122 self.live_document_ids_matching(¤t.segment, snapshot.segments(), predicate)
123 .map_err(DeleteMatchingError::Query)?
124 };
125 if !ids.is_empty()
127 && self
128 .documents_have_native_nodes(&ids)
129 .map_err(DeleteMatchingError::Delete)?
130 {
131 self.prepare_purge_intent(
132 &ids,
133 self.available_purge_bytes()
134 .map_err(DeleteMatchingError::Purge)?,
135 self.vfs.as_ref(),
136 )
137 .map_err(DeleteMatchingError::Purge)?;
138 self.delete_native_documents_locked(&super::DeleteBatch::new(ids.clone()), writer)
139 .map_err(DeleteMatchingError::Delete)?;
140 Some(ids)
141 } else {
142 None
143 }
144 } else {
145 None
146 };
147 let state = self
148 .state
149 .lock()
150 .map_err(|_| StoreError::Synchronization { component: "state" })?;
151 match *state {
152 StoreState::Open => {}
153 StoreState::Closing => return Err(StoreError::Closing.into()),
154 StoreState::Closed => return Err(StoreError::Closed.into()),
155 }
156 let writer_lock = self
157 .writer_lock
158 .lock()
159 .map_err(|_| StoreError::Synchronization {
160 component: "writer lock",
161 })?;
162 if writer_lock.is_none() {
163 return Err(StoreError::ReadOnly.into());
164 }
165 self.require_no_snapshot_views()?;
166 let mut wal = self
167 .wal_writer
168 .lock()
169 .map_err(|_| StoreError::Synchronization {
170 component: "WAL writer",
171 })?;
172 let writer = wal.as_mut().ok_or(StoreError::ReadOnly)?;
173 let (generation, active) = {
174 let active = self
175 .active
176 .lock()
177 .map_err(|_| StoreError::Synchronization {
178 component: "active segment",
179 })?;
180 let current = active.as_ref().ok_or(StoreError::Closed)?;
181 (current.generation, std::sync::Arc::clone(¤t.segment))
182 };
183 let snapshot = self
184 .snapshot
185 .read()
186 .map_err(|_| StoreError::Synchronization {
187 component: "published snapshot",
188 })?
189 .as_ref()
190 .cloned()
191 .ok_or(StoreError::Closed)?;
192 let ids = self
193 .live_document_ids_matching(&active, snapshot.segments(), predicate)
194 .map_err(DeleteMatchingError::Query)?;
195 #[cfg(feature = "graph-cypher")]
196 let (ids, already_deleted) = match native_deleted {
197 Some(ids) => (ids, true),
198 None => (ids, false),
199 };
200 #[cfg(not(feature = "graph-cypher"))]
201 let already_deleted = false;
202 drop(snapshot);
203 drop(active);
204 let report = if ids.is_empty() {
205 DeleteMatchingReport {
206 deleted_ids: ids,
207 generation,
208 }
209 } else {
210 self.delete_and_purge_locked(writer, ids, already_deleted)?
211 };
212 drop(wal);
213 drop(writer_lock);
214 drop(state);
215 Ok(report)
216 }
217
218 fn delete_and_purge_locked(
222 &self,
223 writer: &mut super::StoreWal,
224 ids: Vec<DocId>,
225 already_deleted: bool,
226 ) -> Result<DeleteMatchingReport, DeleteMatchingError> {
227 let vfs = self.vfs.as_ref();
228 let available = self
229 .available_purge_bytes()
230 .map_err(DeleteMatchingError::Purge)?;
231 let token = self
232 .schedule_purge_locked(&ids, available, vfs, writer)
233 .map_err(DeleteMatchingError::Purge)?;
234 if !already_deleted && let Err(error) = self.delete_with_writer(writer, &ids, &[]) {
235 self.abandon_scheduled_purge_locked(vfs, writer)
238 .map_err(DeleteMatchingError::Purge)?;
239 return Err(DeleteMatchingError::Delete(error));
240 }
241 let purged = self
242 .complete_physical_purge_locked(vfs, writer, token)
243 .map_err(DeleteMatchingError::Purge)?;
244 Ok(DeleteMatchingReport {
245 deleted_ids: ids,
246 generation: purged.generation(),
247 })
248 }
249}