surrealdb_core/sql/statements/define/
field.rsuse crate::ctx::Context;
use crate::dbs::Options;
use crate::doc::CursorDoc;
use crate::err::Error;
use crate::iam::{Action, ResourceKind};
use crate::sql::fmt::{is_pretty, pretty_indent};
use crate::sql::statements::info::InfoStructure;
use crate::sql::statements::DefineTableStatement;
use crate::sql::Part;
use crate::sql::{Base, Ident, Idiom, Kind, Permissions, Strand, Value};
use crate::sql::{Relation, TableType};
use derive::Store;
use revision::revisioned;
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Write};
use uuid::Uuid;
#[revisioned(revision = 4)]
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub struct DefineFieldStatement {
pub name: Idiom,
pub what: Ident,
pub flex: bool,
pub kind: Option<Kind>,
#[revision(start = 2)]
pub readonly: bool,
pub value: Option<Value>,
pub assert: Option<Value>,
pub default: Option<Value>,
pub permissions: Permissions,
pub comment: Option<Strand>,
#[revision(start = 3)]
pub if_not_exists: bool,
#[revision(start = 4)]
pub overwrite: bool,
}
impl DefineFieldStatement {
pub(crate) async fn compute(
&self,
ctx: &Context,
opt: &Options,
_doc: Option<&CursorDoc>,
) -> Result<Value, Error> {
opt.is_allowed(Action::Edit, ResourceKind::Field, &Base::Db)?;
let ns = opt.ns()?;
let db = opt.db()?;
let txn = ctx.tx();
let fd = self.name.to_string();
if txn.get_tb_field(ns, db, &self.what, &fd).await.is_ok() {
if self.if_not_exists {
return Ok(Value::None);
} else if !self.overwrite {
return Err(Error::FdAlreadyExists {
value: fd,
});
}
}
let key = crate::key::table::fd::new(ns, db, &self.what, &fd);
txn.get_or_add_ns(ns, opt.strict).await?;
txn.get_or_add_db(ns, db, opt.strict).await?;
txn.get_or_add_tb(ns, db, &self.what, opt.strict).await?;
txn.set(
key,
DefineFieldStatement {
if_not_exists: false,
overwrite: false,
..self.clone()
},
None,
)
.await?;
let key = crate::key::database::tb::new(ns, db, &self.what);
let tb = txn.get_tb(ns, db, &self.what).await?;
txn.set(
key,
DefineTableStatement {
cache_fields_ts: Uuid::now_v7(),
..tb.as_ref().clone()
},
None,
)
.await?;
txn.clear();
let fields = txn.all_tb_fields(ns, db, &self.what, None).await.ok();
if let Some(mut cur_kind) = self.kind.as_ref().and_then(|x| x.inner_kind()) {
let mut name = self.name.clone();
loop {
if let Kind::Any = cur_kind {
break;
}
let new_kind = cur_kind.inner_kind();
name.0.push(Part::All);
let fd = name.to_string();
let key = crate::key::table::fd::new(ns, db, &self.what, &fd);
let val = if let Some(existing) =
fields.as_ref().and_then(|x| x.iter().find(|x| x.name == name))
{
DefineFieldStatement {
kind: Some(cur_kind),
if_not_exists: false,
overwrite: false,
..existing.clone()
}
} else {
DefineFieldStatement {
name: name.clone(),
what: self.what.clone(),
flex: self.flex,
kind: Some(cur_kind),
..Default::default()
}
};
txn.set(key, val, None).await?;
if let Some(new_kind) = new_kind {
cur_kind = new_kind;
} else {
break;
}
}
}
if fd.as_str() == "in" {
let tb = txn.get_tb(ns, db, &self.what).await?;
if let TableType::Relation(ref relation) = tb.kind {
if let Some(kind) = self.kind.as_ref() {
if !kind.is_record() {
return Err(Error::Thrown(
"in field on a relation must be a record".into(),
));
}
if relation.from.as_ref() != self.kind.as_ref() {
let key = crate::key::database::tb::new(ns, db, &self.what);
let val = DefineTableStatement {
cache_fields_ts: Uuid::now_v7(),
kind: TableType::Relation(Relation {
from: self.kind.to_owned(),
..relation.to_owned()
}),
..tb.as_ref().to_owned()
};
txn.set(key, val, None).await?;
txn.clear();
}
}
}
}
if fd.as_str() == "out" {
let tb = txn.get_tb(ns, db, &self.what).await?;
if let TableType::Relation(ref relation) = tb.kind {
if let Some(kind) = self.kind.as_ref() {
if !kind.is_record() {
return Err(Error::Thrown(
"out field on a relation must be a record".into(),
));
}
if relation.from.as_ref() != self.kind.as_ref() {
let key = crate::key::database::tb::new(ns, db, &self.what);
let val = DefineTableStatement {
cache_fields_ts: Uuid::now_v7(),
kind: TableType::Relation(Relation {
to: self.kind.to_owned(),
..relation.to_owned()
}),
..tb.as_ref().to_owned()
};
txn.set(key, val, None).await?;
txn.clear();
}
}
}
}
txn.clear();
Ok(Value::None)
}
}
impl Display for DefineFieldStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "DEFINE FIELD")?;
if self.if_not_exists {
write!(f, " IF NOT EXISTS")?
}
if self.overwrite {
write!(f, " OVERWRITE")?
}
write!(f, " {} ON {}", self.name, self.what)?;
if self.flex {
write!(f, " FLEXIBLE")?
}
if let Some(ref v) = self.kind {
write!(f, " TYPE {v}")?
}
if let Some(ref v) = self.default {
write!(f, " DEFAULT {v}")?
}
if self.readonly {
write!(f, " READONLY")?
}
if let Some(ref v) = self.value {
write!(f, " VALUE {v}")?
}
if let Some(ref v) = self.assert {
write!(f, " ASSERT {v}")?
}
if let Some(ref v) = self.comment {
write!(f, " COMMENT {v}")?
}
let _indent = if is_pretty() {
Some(pretty_indent())
} else {
f.write_char(' ')?;
None
};
write!(f, "{:#}", self.permissions)?;
Ok(())
}
}
impl InfoStructure for DefineFieldStatement {
fn structure(self) -> Value {
Value::from(map! {
"name".to_string() => self.name.structure(),
"what".to_string() => self.what.structure(),
"flex".to_string() => self.flex.into(),
"kind".to_string(), if let Some(v) = self.kind => v.structure(),
"value".to_string(), if let Some(v) = self.value => v.structure(),
"assert".to_string(), if let Some(v) = self.assert => v.structure(),
"default".to_string(), if let Some(v) = self.default => v.structure(),
"readonly".to_string() => self.readonly.into(),
"permissions".to_string() => self.permissions.structure(),
"comment".to_string(), if let Some(v) = self.comment => v.into(),
})
}
}