use std::sync::Arc;
use bson::Document;
use vantage_core::{Result, error};
use super::MongoDB;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct MongoView {
pub collection: String,
pub stages: Vec<Document>,
}
impl MongoView {
pub fn new(collection: impl Into<String>, stages: Vec<Document>) -> Self {
Self {
collection: collection.into(),
stages,
}
}
}
impl MongoDB {
pub fn with_view(mut self, view: MongoView) -> Self {
self.view = Some(Arc::new(view));
self
}
#[cfg(feature = "vista")]
pub(crate) fn without_view(mut self) -> Self {
self.view = None;
self
}
pub fn view(&self) -> Option<&MongoView> {
self.view.as_deref()
}
pub(crate) fn view_stages(&self) -> Vec<Document> {
self.view().map(|v| v.stages.clone()).unwrap_or_default()
}
pub(crate) fn read_collection_name<'a>(&'a self, table_name: &'a str) -> &'a str {
match self.view() {
Some(view) => &view.collection,
None => table_name,
}
}
pub(crate) fn read_collection(&self, table_name: &str) -> mongodb::Collection<Document> {
self.doc_collection(self.read_collection_name(table_name))
}
pub fn with_server_view(mut self, server_view: bool) -> Self {
self.server_view = server_view;
self
}
pub fn is_server_view(&self) -> bool {
self.server_view
}
pub(crate) fn is_stored(&self) -> bool {
self.view().is_none() && !self.server_view
}
pub(crate) fn ensure_writable(&self, table_name: &str) -> Result<()> {
if let Some(view) = self.view() {
return Err(error!(
"MongoDB pipeline view is read-only",
collection = view.collection.clone()
)
.mark_unsupported());
}
if self.server_view {
return Err(error!(
"MongoDB server-side view is read-only",
collection = table_name
)
.mark_unsupported());
}
Ok(())
}
#[cfg(feature = "vista")]
pub(crate) fn ensure_watchable(&self, table_name: &str) -> Result<()> {
if let Some(view) = self.view() {
return Err(error!(
"MongoDB pipeline view has no change stream",
collection = view.collection.clone()
)
.mark_unsupported());
}
if self.server_view {
return Err(error!(
"MongoDB server-side view has no change stream",
collection = table_name
)
.mark_unsupported());
}
Ok(())
}
}
const COMMAND_NOT_SUPPORTED_ON_VIEW: i32 = 166;
fn is_view_error(e: &mongodb::error::Error) -> bool {
use mongodb::error::{ErrorKind, WriteFailure};
match &*e.kind {
ErrorKind::Command(c) => c.code == COMMAND_NOT_SUPPORTED_ON_VIEW,
ErrorKind::Write(WriteFailure::WriteError(we)) => we.code == COMMAND_NOT_SUPPORTED_ON_VIEW,
ErrorKind::InsertMany(m) => m
.write_errors
.iter()
.flatten()
.any(|we| we.code == COMMAND_NOT_SUPPORTED_ON_VIEW),
_ => false,
}
}
pub(crate) fn undeclared_view(
e: &mongodb::error::Error,
collection: &str,
) -> Option<vantage_core::VantageError> {
is_view_error(e).then(|| {
error!(
"MongoDB collection is a view; declare `mongo: { view: true }` on the table",
collection = collection
)
.mark_unsupported()
})
}