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