use revision::{
DeserializeRevisioned, Revisioned, SerializeRevisioned, SkipRevisioned, revisioned,
};
use surrealdb_types::{SqlFormat, ToSql, write_sql};
use uuid::Uuid;
use crate::catalog::{DatabaseId, NamespaceId, Permissions, ViewDefinition};
use crate::expr::statements::info::InfoStructure;
use crate::expr::{ChangeFeed, Kind};
use crate::fmt::EscapeKwFreeIdent;
use crate::kvs::impl_kv_value_revisioned;
use crate::sql;
use crate::sql::statements::DefineTableStatement;
use crate::val::{TableName, Value};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct TableId(pub u32);
impl_kv_value_revisioned!(TableId);
impl Revisioned for TableId {
fn revision() -> u16 {
1
}
}
impl SerializeRevisioned for TableId {
#[inline]
fn serialize_revisioned<W: std::io::Write>(
&self,
writer: &mut W,
) -> Result<(), revision::Error> {
SerializeRevisioned::serialize_revisioned(&self.0, writer)
}
}
impl DeserializeRevisioned for TableId {
#[inline]
fn deserialize_revisioned<R: std::io::Read>(reader: &mut R) -> Result<Self, revision::Error> {
DeserializeRevisioned::deserialize_revisioned(reader).map(TableId)
}
}
impl SkipRevisioned for TableId {
#[inline]
fn skip_revisioned<R: std::io::Read>(reader: &mut R) -> Result<(), revision::Error> {
<u32 as SkipRevisioned>::skip_revisioned(reader)
}
}
impl revision::WalkRevisioned for TableId {
type Walker<'r, R: revision::BorrowedReader + 'r> = revision::LeafWalker<'r, TableId, R>;
#[inline]
fn walk_revisioned<'r, R: revision::BorrowedReader>(
reader: &'r mut R,
) -> Result<Self::Walker<'r, R>, revision::Error> {
Ok(revision::LeafWalker::new(reader))
}
}
#[revisioned(revision = 2)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct TableDefinition {
pub(crate) namespace_id: NamespaceId,
pub(crate) database_id: DatabaseId,
pub(crate) table_id: TableId,
pub(crate) name: TableName,
pub(crate) drop: bool,
pub(crate) schemafull: bool,
pub(crate) view: Option<ViewDefinition>,
pub(crate) permissions: Permissions,
pub(crate) changefeed: Option<ChangeFeed>,
pub(crate) comment: Option<String>,
pub(crate) table_type: TableType,
pub(crate) cache_fields_ts: Uuid,
pub(crate) cache_events_ts: Uuid,
pub(crate) cache_tables_ts: Uuid,
pub(crate) cache_indexes_ts: Uuid,
#[revision(start = 2)]
pub(crate) graphql_alias: Option<String>,
#[revision(start = 2)]
pub(crate) graphql_deprecated: Option<String>,
}
impl_kv_value_revisioned!(TableDefinition);
impl TableDefinition {
pub fn new(
namespace_id: NamespaceId,
database_id: DatabaseId,
table_id: TableId,
name: TableName,
) -> Self {
let now = Uuid::now_v7();
Self {
namespace_id,
database_id,
table_id,
name,
drop: false,
schemafull: false,
view: None,
permissions: Permissions::none(),
changefeed: None,
comment: None,
table_type: TableType::default(),
cache_fields_ts: now,
cache_events_ts: now,
cache_tables_ts: now,
cache_indexes_ts: now,
graphql_alias: None,
graphql_deprecated: None,
}
}
pub fn allows_normal(&self) -> bool {
matches!(self.table_type, TableType::Normal | TableType::Any)
}
pub fn allows_relation(&self) -> bool {
matches!(self.table_type, TableType::Relation(_) | TableType::Any)
}
fn to_sql_definition(&self) -> DefineTableStatement {
DefineTableStatement {
id: Some(self.table_id.0),
name: sql::Expr::Table(self.name.clone()),
drop: self.drop,
full: self.schemafull,
view: self.view.clone().map(|v| v.to_sql_definition()),
permissions: self.permissions.clone().into(),
changefeed: self.changefeed.map(|v| v.into()),
comment: self
.comment
.clone()
.map(|v| sql::Expr::Literal(sql::Literal::String(v.into())))
.unwrap_or(sql::Expr::Literal(sql::Literal::None)),
table_type: self.table_type.clone().into(),
graphql_alias: self.graphql_alias.clone(),
graphql_deprecated: self.graphql_deprecated.clone(),
..Default::default()
}
}
}
impl ToSql for TableDefinition {
fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
self.to_sql_definition().fmt_sql(f, sql_fmt)
}
}
impl InfoStructure for TableDefinition {
fn structure(self) -> Value {
Value::from(map! {
"name" => Value::String(self.name.into()),
"drop" => self.drop.into(),
"schemafull" => self.schemafull.into(),
"kind" => self.table_type.structure(),
"view", if let Some(v) = self.view => v.structure(),
"changefeed", if let Some(v) = self.changefeed => v.structure(),
"permissions" => self.permissions.structure(),
"comment", if let Some(v) = self.comment => v.into(),
"graphql_alias", if let Some(v) = self.graphql_alias => v.into(),
"graphql_deprecated", if let Some(v) = self.graphql_deprecated => v.into(),
"id" => self.table_id.0.into(),
})
}
}
#[revisioned(revision = 1)]
#[derive(Debug, Default, Hash, Clone, Eq, PartialEq)]
pub enum TableType {
#[default]
Any,
Normal,
Relation(Relation),
}
impl ToSql for TableType {
fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
match self {
TableType::Any => f.push_str("ANY"),
TableType::Normal => f.push_str("NORMAL"),
TableType::Relation(rel) => {
f.push_str("RELATION");
if !rel.from.is_empty() {
f.push_str(" IN ");
for (idx, k) in rel.from.iter().enumerate() {
if idx != 0 {
f.push_str(" | ");
}
write_sql!(f, sql_fmt, "{}", EscapeKwFreeIdent(k.as_str()));
}
}
if !rel.to.is_empty() {
f.push_str(" OUT ");
for (idx, k) in rel.to.iter().enumerate() {
if idx != 0 {
f.push_str(" | ");
}
write_sql!(f, sql_fmt, "{}", EscapeKwFreeIdent(k.as_str()));
}
}
if rel.enforced {
f.push_str(" ENFORCED");
}
}
}
}
}
impl InfoStructure for TableType {
fn structure(self) -> Value {
match self {
Self::Any => Value::from(map! {
"kind" => "ANY".into(),
}),
Self::Normal => Value::from(map! {
"kind" => "NORMAL".into(),
}),
Self::Relation(rel) => Value::from(map! {
"kind" => "RELATION".into(),
"in", if !rel.from.is_empty() =>
rel.from.into_iter().map(Value::Table).collect::<Vec<_>>().into(),
"out", if !rel.to.is_empty() =>
rel.to.into_iter().map(Value::Table).collect::<Vec<_>>().into(),
"enforced" => rel.enforced.into()
}),
}
}
}
#[revisioned(revision = 2)]
#[derive(Debug, Hash, Clone, Eq, PartialEq)]
pub struct Relation {
#[revision(end = 2, convert_fn = "rev_convert_from")]
pub old_from: Option<Kind>,
#[revision(start = 2)]
pub from: Vec<TableName>,
#[revision(end = 2, convert_fn = "rev_convert_to")]
pub old_to: Option<Kind>,
#[revision(start = 2)]
pub to: Vec<TableName>,
pub enforced: bool,
}
impl Relation {
fn rev_convert_from(&mut self, _rev: u16, value: Option<Kind>) -> Result<(), revision::Error> {
if let Some(x) = value {
let Kind::Record(x) = x else {
return Err(revision::Error::Conversion(format!(
"Invalid kind within table relation, should have been a record, found: {:#?}",
x,
)));
};
self.from = x
}
Ok(())
}
fn rev_convert_to(&mut self, _rev: u16, value: Option<Kind>) -> Result<(), revision::Error> {
if let Some(x) = value {
let Kind::Record(x) = x else {
return Err(revision::Error::Conversion(format!(
"Invalid kind within table relation, should have been a record, found: {:#?}",
x,
)));
};
self.to = x
}
Ok(())
}
}