surrealdb_core/sql/statements/alter/
table.rs

1use crate::ctx::Context;
2use crate::dbs::Options;
3use crate::doc::CursorDoc;
4use crate::err::Error;
5use crate::iam::{Action, ResourceKind};
6use crate::sql::fmt::{is_pretty, pretty_indent};
7use crate::sql::statements::DefineTableStatement;
8use crate::sql::{changefeed::ChangeFeed, Base, Ident, Permissions, Strand, Value};
9use crate::sql::{Kind, TableType};
10
11use reblessive::tree::Stk;
12use revision::revisioned;
13use serde::{Deserialize, Serialize};
14use std::fmt::{self, Display, Write};
15use std::ops::Deref;
16
17#[revisioned(revision = 1)]
18#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
19#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
20#[non_exhaustive]
21pub struct AlterTableStatement {
22	pub name: Ident,
23	pub if_exists: bool,
24	pub drop: Option<bool>,
25	pub full: Option<bool>,
26	pub permissions: Option<Permissions>,
27	pub changefeed: Option<Option<ChangeFeed>>,
28	pub comment: Option<Option<Strand>>,
29	pub kind: Option<TableType>,
30}
31
32impl AlterTableStatement {
33	pub(crate) async fn compute(
34		&self,
35		_stk: &mut Stk,
36		ctx: &Context,
37		opt: &Options,
38		_doc: Option<&CursorDoc>,
39	) -> Result<Value, Error> {
40		// Allowed to run?
41		opt.is_allowed(Action::Edit, ResourceKind::Table, &Base::Db)?;
42		// Get the NS and DB
43		let (ns, db) = opt.ns_db()?;
44		// Fetch the transaction
45		let txn = ctx.tx();
46		// Get the table definition
47		let mut dt = match txn.get_tb(ns, db, &self.name).await {
48			Ok(tb) => tb.deref().clone(),
49			Err(Error::TbNotFound {
50				..
51			}) if self.if_exists => return Ok(Value::None),
52			Err(v) => return Err(v),
53		};
54		// Process the statement
55		let key = crate::key::database::tb::new(ns, db, &self.name);
56		if let Some(ref drop) = &self.drop {
57			dt.drop = *drop;
58		}
59		if let Some(ref full) = &self.full {
60			dt.full = *full;
61		}
62		if let Some(ref permissions) = &self.permissions {
63			dt.permissions = permissions.clone();
64		}
65		if let Some(ref changefeed) = &self.changefeed {
66			dt.changefeed = *changefeed;
67		}
68		if let Some(ref comment) = &self.comment {
69			dt.comment.clone_from(comment);
70		}
71		if let Some(ref kind) = &self.kind {
72			dt.kind = kind.clone();
73		}
74
75		// Add table relational fields
76		if matches!(self.kind, Some(TableType::Relation(_))) {
77			DefineTableStatement::add_in_out_fields(&txn, ns, db, &mut dt).await?;
78		}
79		// Set the table definition
80		txn.set(key, revision::to_vec(&dt)?, None).await?;
81		// Record definition change
82		if self.changefeed.is_some() && dt.changefeed.is_some() {
83			txn.lock().await.record_table_change(ns, db, &self.name, &dt);
84		}
85		// Clear the cache
86		txn.clear();
87		// Ok all good
88		Ok(Value::None)
89	}
90}
91
92impl Display for AlterTableStatement {
93	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94		write!(f, "ALTER TABLE")?;
95		if self.if_exists {
96			write!(f, " IF EXISTS")?
97		}
98		write!(f, " {}", self.name)?;
99		if let Some(kind) = &self.kind {
100			write!(f, " TYPE")?;
101			match &kind {
102				TableType::Normal => {
103					f.write_str(" NORMAL")?;
104				}
105				TableType::Relation(rel) => {
106					f.write_str(" RELATION")?;
107					if let Some(Kind::Record(kind)) = &rel.from {
108						write!(
109							f,
110							" IN {}",
111							kind.iter().map(|t| t.0.as_str()).collect::<Vec<_>>().join(" | ")
112						)?;
113					}
114					if let Some(Kind::Record(kind)) = &rel.to {
115						write!(
116							f,
117							" OUT {}",
118							kind.iter().map(|t| t.0.as_str()).collect::<Vec<_>>().join(" | ")
119						)?;
120					}
121				}
122				TableType::Any => {
123					f.write_str(" ANY")?;
124				}
125			}
126		}
127		if let Some(drop) = self.drop {
128			write!(f, " DROP {drop}")?;
129		}
130		if let Some(full) = self.full {
131			f.write_str(if full {
132				" SCHEMAFULL"
133			} else {
134				" SCHEMALESS"
135			})?;
136		}
137		if let Some(comment) = &self.comment {
138			write!(f, " COMMENT {}", comment.clone().unwrap_or("NONE".into()))?
139		}
140		if let Some(changefeed) = &self.changefeed {
141			write!(f, " CHANGEFEED {}", changefeed.map_or("NONE".into(), |v| v.to_string()))?
142		}
143		let _indent = if is_pretty() {
144			Some(pretty_indent())
145		} else {
146			f.write_char(' ')?;
147			None
148		};
149		if let Some(permissions) = &self.permissions {
150			write!(f, "{permissions}")?;
151		}
152		Ok(())
153	}
154}