Skip to main content

mongreldb_core/
external_table.rs

1use crate::error::{MongrelError, Result};
2use crate::schema::Schema;
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5use std::collections::HashSet;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ExternalTableEntry {
9    pub name: String,
10    pub module: String,
11    #[serde(default)]
12    pub args: Vec<ModuleArg>,
13    pub declared_schema: Schema,
14    #[serde(default)]
15    pub hidden_columns: Vec<String>,
16    #[serde(default)]
17    pub options: BTreeMap<String, String>,
18    #[serde(default)]
19    pub capabilities: ModuleCapabilities,
20    pub created_epoch: u64,
21}
22
23#[derive(Debug, Clone)]
24pub struct ExternalTableDefinition {
25    pub module: String,
26    pub args: Vec<ModuleArg>,
27    pub declared_schema: Schema,
28    pub hidden_columns: Vec<String>,
29    pub options: BTreeMap<String, String>,
30    pub capabilities: ModuleCapabilities,
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34#[serde(rename_all = "snake_case", tag = "kind", content = "value")]
35pub enum ModuleArg {
36    Ident(String),
37    String(String),
38    Number(String),
39}
40
41#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
42pub struct ModuleCapabilities {
43    pub read_only: bool,
44    pub insert_only: bool,
45    pub writable: bool,
46    pub deterministic: bool,
47    pub trigger_safe: bool,
48    pub transaction_safe: bool,
49}
50
51impl ExternalTableEntry {
52    pub fn new(
53        name: impl Into<String>,
54        definition: ExternalTableDefinition,
55        created_epoch: u64,
56    ) -> Result<Self> {
57        let entry = Self {
58            name: name.into(),
59            module: definition.module,
60            args: definition.args,
61            declared_schema: definition.declared_schema,
62            hidden_columns: definition.hidden_columns,
63            options: definition.options,
64            capabilities: definition.capabilities,
65            created_epoch,
66        };
67        entry.validate()?;
68        Ok(entry)
69    }
70
71    pub fn validate(&self) -> Result<()> {
72        validate_name("external table", &self.name)?;
73        validate_name("external table module", &self.module)?;
74        let mut hidden = HashSet::new();
75        for col in &self.hidden_columns {
76            validate_name("external table hidden column", col)?;
77            if !hidden.insert(col) {
78                return Err(MongrelError::InvalidArgument(format!(
79                    "duplicate external table hidden column {col:?}"
80                )));
81            }
82        }
83        self.declared_schema.validate_auto_increment()?;
84        Ok(())
85    }
86}
87
88fn validate_name(kind: &str, name: &str) -> Result<()> {
89    if name.is_empty()
90        || !name
91            .chars()
92            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
93    {
94        return Err(MongrelError::InvalidArgument(format!(
95            "{kind} name {name:?} must be non-empty ASCII alphanumeric, '_' or '-'"
96        )));
97    }
98    Ok(())
99}