reifydb_catalog/transaction/
view.rs

1// Copyright (c) reifydb.com 2025
2// This file is licensed under the AGPL-3.0-or-later, see license.md file
3
4use reifydb_core::interface::{
5	CommandTransaction, NamespaceId, QueryTransaction, TransactionalChanges, TransactionalViewChanges, ViewDef,
6	ViewId,
7	interceptor::{ViewDefInterceptor, WithInterceptors},
8};
9use reifydb_type::{
10	IntoFragment,
11	diagnostic::catalog::{view_already_exists, view_not_found},
12	error, internal, return_error,
13};
14use tracing::{instrument, warn};
15
16use crate::{
17	CatalogNamespaceQueryOperations, CatalogStore, store::view::ViewToCreate,
18	transaction::MaterializedCatalogTransaction,
19};
20
21pub trait CatalogViewCommandOperations {
22	fn create_view(&mut self, view: ViewToCreate) -> crate::Result<ViewDef>;
23
24	// TODO: Implement when update/delete are ready
25	// fn update_view(&mut self, view_id: ViewId, updates: ViewUpdates) ->
26	// crate::Result<ViewDef>; fn delete_view(&mut self, view_id: ViewId)
27	// -> crate::Result<()>;
28}
29
30pub trait CatalogTrackViewChangeOperations {
31	fn track_view_def_created(&mut self, view: ViewDef) -> crate::Result<()>;
32
33	fn track_view_def_updated(&mut self, pre: ViewDef, post: ViewDef) -> crate::Result<()>;
34
35	fn track_view_def_deleted(&mut self, view: ViewDef) -> crate::Result<()>;
36}
37
38pub trait CatalogViewQueryOperations: CatalogNamespaceQueryOperations {
39	fn find_view(&mut self, id: ViewId) -> crate::Result<Option<ViewDef>>;
40
41	fn find_view_by_name<'a>(
42		&mut self,
43		namespace: NamespaceId,
44		name: impl IntoFragment<'a>,
45	) -> crate::Result<Option<ViewDef>>;
46
47	fn get_view(&mut self, id: ViewId) -> crate::Result<ViewDef>;
48
49	fn get_view_by_name<'a>(
50		&mut self,
51		namespace: NamespaceId,
52		name: impl IntoFragment<'a>,
53	) -> crate::Result<ViewDef>;
54}
55
56impl<
57	CT: CommandTransaction
58		+ MaterializedCatalogTransaction
59		+ CatalogTrackViewChangeOperations
60		+ WithInterceptors<CT>
61		+ TransactionalChanges,
62> CatalogViewCommandOperations for CT
63{
64	#[instrument(level = "debug", skip(self, to_create))]
65	fn create_view(&mut self, to_create: ViewToCreate) -> reifydb_core::Result<ViewDef> {
66		if let Some(view) = self.find_view_by_name(to_create.namespace, &to_create.name)? {
67			let namespace = self.get_namespace(to_create.namespace)?;
68			return_error!(view_already_exists(to_create.fragment, &namespace.name, &view.name));
69		}
70		let result = CatalogStore::create_deferred_view(self, to_create)?;
71		self.track_view_def_created(result.clone())?;
72		ViewDefInterceptor::post_create(self, &result)?;
73		Ok(result)
74	}
75}
76
77impl<QT: QueryTransaction + MaterializedCatalogTransaction + TransactionalChanges> CatalogViewQueryOperations for QT {
78	#[instrument(level = "trace", skip(self))]
79	fn find_view(&mut self, id: ViewId) -> reifydb_core::Result<Option<ViewDef>> {
80		// 1. Check transactional changes first
81		// nop for QueryTransaction
82		if let Some(view) = TransactionalViewChanges::find_view(self, id) {
83			return Ok(Some(view.clone()));
84		}
85
86		// 2. Check if deleted
87		// nop for QueryTransaction
88		if TransactionalViewChanges::is_view_deleted(self, id) {
89			return Ok(None);
90		}
91
92		// 3. Check MaterializedCatalog
93		if let Some(view) = self.catalog().find_view(id, self.version()) {
94			return Ok(Some(view));
95		}
96
97		// 4. Fall back to storage as defensive measure
98		if let Some(view) = CatalogStore::find_view(self, id)? {
99			warn!("View with ID {:?} found in storage but not in MaterializedCatalog", id);
100			return Ok(Some(view));
101		}
102
103		Ok(None)
104	}
105
106	#[instrument(level = "trace", skip(self, name))]
107	fn find_view_by_name<'a>(
108		&mut self,
109		namespace: NamespaceId,
110		name: impl IntoFragment<'a>,
111	) -> reifydb_core::Result<Option<ViewDef>> {
112		let name = name.into_fragment();
113
114		// 1. Check transactional changes first
115		// nop for QueryTransaction
116		if let Some(view) = TransactionalViewChanges::find_view_by_name(self, namespace, name.as_borrowed()) {
117			return Ok(Some(view.clone()));
118		}
119
120		// 2. Check if deleted
121		// nop for QueryTransaction
122		if TransactionalViewChanges::is_view_deleted_by_name(self, namespace, name.as_borrowed()) {
123			return Ok(None);
124		}
125
126		// 3. Check MaterializedCatalog
127		if let Some(view) = self.catalog().find_view_by_name(namespace, name.text(), self.version()) {
128			return Ok(Some(view));
129		}
130
131		// 4. Fall back to storage as defensive measure
132		if let Some(view) = CatalogStore::find_view_by_name(self, namespace, name.text())? {
133			warn!(
134				"View '{}' in namespace {:?} found in storage but not in MaterializedCatalog",
135				name.text(),
136				namespace
137			);
138			return Ok(Some(view));
139		}
140
141		Ok(None)
142	}
143
144	#[instrument(level = "trace", skip(self))]
145	fn get_view(&mut self, id: ViewId) -> reifydb_core::Result<ViewDef> {
146		self.find_view(id)?.ok_or_else(|| {
147			error!(internal!(
148				"View with ID {:?} not found in catalog. This indicates a critical catalog inconsistency.",
149				id
150			))
151		})
152	}
153
154	#[instrument(level = "trace", skip(self, name))]
155	fn get_view_by_name<'a>(
156		&mut self,
157		namespace: NamespaceId,
158		name: impl IntoFragment<'a>,
159	) -> reifydb_core::Result<ViewDef> {
160		let name = name.into_fragment();
161
162		let namespace_name = self.get_namespace(namespace)?.name;
163
164		self.find_view_by_name(namespace, name.as_borrowed())?
165			.ok_or_else(|| error!(view_not_found(name.as_borrowed(), &namespace_name, name.text())))
166	}
167}