diff --git a/core/src/sql/block.rs b/core/src/sql/block.rs index ec515bca..45f358a7 100644 --- a/core/src/sql/block.rs +++ b/core/src/sql/block.rs @@ -6,10 +6,10 @@ use crate::sql::fmt::{is_pretty, pretty_indent, Fmt, Pretty}; use crate::sql::statements::info::InfoStructure; use crate::sql::statements::rebuild::RebuildStatement; use crate::sql::statements::{ - BreakStatement, ContinueStatement, CreateStatement, DefineStatement, DeleteStatement, - ForeachStatement, IfelseStatement, InsertStatement, OutputStatement, RelateStatement, - RemoveStatement, SelectStatement, SetStatement, ThrowStatement, UpdateStatement, - UpsertStatement, + AlterStatement, BreakStatement, ContinueStatement, CreateStatement, DefineStatement, + DeleteStatement, ForeachStatement, IfelseStatement, InsertStatement, OutputStatement, + RelateStatement, RemoveStatement, SelectStatement, SetStatement, ThrowStatement, + UpdateStatement, UpsertStatement, }; use crate::sql::value::Value; use reblessive::tree::Stk; @@ -114,6 +114,9 @@ impl Block { Entry::Output(v) => { v.compute(stk, &ctx, opt, doc).await?; } + Entry::Alter(v) => { + v.compute(stk, &ctx, opt, doc).await?; + } Entry::Value(v) => { if i == self.len() - 1 { // If the last entry then return the value @@ -181,7 +184,7 @@ impl InfoStructure for Block { } } -#[revisioned(revision = 3)] +#[revisioned(revision = 4)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[non_exhaustive] @@ -206,6 +209,8 @@ pub enum Entry { Rebuild(RebuildStatement), #[revision(start = 3)] Upsert(UpsertStatement), + #[revision(start = 4)] + Alter(AlterStatement), } impl PartialOrd for Entry { @@ -237,6 +242,7 @@ impl Entry { Self::Break(v) => v.writeable(), Self::Continue(v) => v.writeable(), Self::Foreach(v) => v.writeable(), + Self::Alter(v) => v.writeable(), } } } @@ -262,6 +268,7 @@ impl Display for Entry { Self::Break(v) => write!(f, "{v}"), Self::Continue(v) => write!(f, "{v}"), Self::Foreach(v) => write!(f, "{v}"), + Self::Alter(v) => write!(f, "{v}"), } } } diff --git a/core/src/sql/statement.rs b/core/src/sql/statement.rs index 813f1a8a..d2782a23 100644 --- a/core/src/sql/statement.rs +++ b/core/src/sql/statement.rs @@ -6,12 +6,12 @@ use crate::sql::statements::rebuild::RebuildStatement; use crate::sql::{ fmt::{Fmt, Pretty}, statements::{ - AnalyzeStatement, BeginStatement, BreakStatement, CancelStatement, CommitStatement, - ContinueStatement, CreateStatement, DefineStatement, DeleteStatement, ForeachStatement, - IfelseStatement, InfoStatement, InsertStatement, KillStatement, LiveStatement, - OptionStatement, OutputStatement, RelateStatement, RemoveStatement, SelectStatement, - SetStatement, ShowStatement, SleepStatement, ThrowStatement, UpdateStatement, - UpsertStatement, UseStatement, + AlterStatement, AnalyzeStatement, BeginStatement, BreakStatement, CancelStatement, + CommitStatement, ContinueStatement, CreateStatement, DefineStatement, DeleteStatement, + ForeachStatement, IfelseStatement, InfoStatement, InsertStatement, KillStatement, + LiveStatement, OptionStatement, OutputStatement, RelateStatement, RemoveStatement, + SelectStatement, SetStatement, ShowStatement, SleepStatement, ThrowStatement, + UpdateStatement, UpsertStatement, UseStatement, }, value::Value, }; @@ -55,7 +55,7 @@ impl Display for Statements { } } -#[revisioned(revision = 3)] +#[revisioned(revision = 4)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[non_exhaustive] @@ -91,6 +91,8 @@ pub enum Statement { Rebuild(RebuildStatement), #[revision(start = 3)] Upsert(UpsertStatement), + #[revision(start = 4)] + Alter(AlterStatement), } impl Statement { @@ -111,6 +113,7 @@ impl Statement { pub(crate) fn writeable(&self) -> bool { match self { Self::Value(v) => v.writeable(), + Self::Alter(_) => true, Self::Analyze(_) => false, Self::Break(_) => false, Self::Continue(_) => false, @@ -148,6 +151,7 @@ impl Statement { doc: Option<&CursorDoc<'_>>, ) -> Result { match self { + Self::Alter(v) => v.compute(stk, ctx, opt, doc).await, Self::Analyze(v) => v.compute(ctx, opt, doc).await, Self::Break(v) => v.compute(ctx, opt, doc).await, Self::Continue(v) => v.compute(ctx, opt, doc).await, @@ -186,6 +190,7 @@ impl Display for Statement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Value(v) => write!(Pretty::from(f), "{v}"), + Self::Alter(v) => write!(Pretty::from(f), "{v}"), Self::Analyze(v) => write!(Pretty::from(f), "{v}"), Self::Begin(v) => write!(Pretty::from(f), "{v}"), Self::Break(v) => write!(Pretty::from(f), "{v}"), diff --git a/core/src/sql/statements/alter/mod.rs b/core/src/sql/statements/alter/mod.rs new file mode 100644 index 00000000..39b65015 --- /dev/null +++ b/core/src/sql/statements/alter/mod.rs @@ -0,0 +1,66 @@ +mod table; + +pub use table::AlterTableStatement; + +use crate::ctx::Context; +use crate::dbs::Options; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::sql::value::Value; +use derive::Store; +use reblessive::tree::Stk; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[revisioned(revision = 1)] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[non_exhaustive] +pub enum AlterStatement { + Table(AlterTableStatement), +} + +impl AlterStatement { + /// Check if we require a writeable transaction + pub(crate) fn writeable(&self) -> bool { + true + } + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + stk: &mut Stk, + ctx: &Context<'_>, + opt: &Options, + doc: Option<&CursorDoc<'_>>, + ) -> Result { + match self { + Self::Table(ref v) => v.compute(stk, ctx, opt, doc).await, + } + } +} + +impl Display for AlterStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Table(v) => Display::fmt(v, f), + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::sql::Ident; + + #[test] + fn check_alter_serialize() { + let stm = AlterStatement::Table(AlterTableStatement { + name: Ident::from("test"), + ..Default::default() + }); + let enc: Vec = stm.into(); + assert_eq!(16, enc.len()); + } +} diff --git a/core/src/sql/statements/alter/table.rs b/core/src/sql/statements/alter/table.rs new file mode 100644 index 00000000..b83203b6 --- /dev/null +++ b/core/src/sql/statements/alter/table.rs @@ -0,0 +1,150 @@ +use 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::{changefeed::ChangeFeed, Base, Ident, Permissions, Strand, Value}; +use crate::sql::{Kind, TableType}; +use derive::Store; +use reblessive::tree::Stk; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Write}; +use std::ops::Deref; + +#[revisioned(revision = 1)] +#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[non_exhaustive] +pub struct AlterTableStatement { + pub name: Ident, + pub if_exists: bool, + pub drop: Option, + pub full: Option, + pub permissions: Option, + pub changefeed: Option>, + pub comment: Option>, + pub kind: Option, +} + +impl AlterTableStatement { + pub(crate) async fn compute( + &self, + _stk: &mut Stk, + ctx: &Context<'_>, + opt: &Options, + _doc: Option<&CursorDoc<'_>>, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Table, &Base::Db)?; + // Fetch the transaction + let txn = ctx.tx(); + // Get the table definition + let mut dt = match txn.get_tb(opt.ns()?, opt.db()?, &self.name).await { + Ok(tb) => tb.deref().clone(), + Err(Error::TbNotFound { + .. + }) if self.if_exists => return Ok(Value::None), + Err(v) => return Err(v), + }; + // Process the statement + let key = crate::key::database::tb::new(opt.ns()?, opt.db()?, &self.name); + if let Some(ref drop) = &self.drop { + dt.drop = *drop; + } + if let Some(ref full) = &self.full { + dt.full = *full; + } + if let Some(ref permissions) = &self.permissions { + dt.permissions = permissions.clone(); + } + if let Some(ref changefeed) = &self.changefeed { + dt.changefeed = *changefeed; + } + if let Some(ref comment) = &self.comment { + dt.comment = comment.clone(); + } + if let Some(ref kind) = &self.kind { + dt.kind = kind.clone(); + } + + txn.set(key, &dt).await?; + // Add table relational fields + if matches!(self.kind, Some(TableType::Relation(_))) { + dt.add_in_out_fields(&txn, opt).await?; + } + // Record definition change + if self.changefeed.is_some() && dt.changefeed.is_some() { + txn.lock().await.record_table_change(opt.ns()?, opt.db()?, &self.name, &dt); + } + // Clear the cache + txn.clear(); + // Ok all good + Ok(Value::None) + } +} + +impl Display for AlterTableStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ALTER TABLE")?; + if self.if_exists { + write!(f, " IF EXISTS")? + } + write!(f, " {}", self.name)?; + if let Some(kind) = &self.kind { + write!(f, " TYPE")?; + match &kind { + TableType::Normal => { + f.write_str(" NORMAL")?; + } + TableType::Relation(rel) => { + f.write_str(" RELATION")?; + if let Some(Kind::Record(kind)) = &rel.from { + write!( + f, + " IN {}", + kind.iter().map(|t| t.0.as_str()).collect::>().join(" | ") + )?; + } + if let Some(Kind::Record(kind)) = &rel.to { + write!( + f, + " OUT {}", + kind.iter().map(|t| t.0.as_str()).collect::>().join(" | ") + )?; + } + } + TableType::Any => { + f.write_str(" ANY")?; + } + } + } + if let Some(drop) = self.drop { + write!(f, " DROP {drop}")?; + } + if let Some(full) = self.full { + f.write_str(if full { + " SCHEMAFULL" + } else { + " SCHEMALESS" + })?; + } + if let Some(comment) = &self.comment { + write!(f, " COMMENT {}", comment.clone().unwrap_or("NONE".into()))? + } + if let Some(changefeed) = &self.changefeed { + write!(f, " CHANGEFEED {}", changefeed.map_or("NONE".into(), |v| v.to_string()))? + } + let _indent = if is_pretty() { + Some(pretty_indent()) + } else { + f.write_char(' ')?; + None + }; + if let Some(permissions) = &self.permissions { + write!(f, "{permissions}")?; + } + Ok(()) + } +} diff --git a/core/src/sql/statements/define/table.rs b/core/src/sql/statements/define/table.rs index d658bfbf..f1222671 100644 --- a/core/src/sql/statements/define/table.rs +++ b/core/src/sql/statements/define/table.rs @@ -4,6 +4,7 @@ use crate::dbs::{Force, Options}; use crate::doc::CursorDoc; use crate::err::Error; use crate::iam::{Action, ResourceKind}; +use crate::kvs::Transaction; use crate::sql::fmt::{is_pretty, pretty_indent}; use crate::sql::paths::{IN, OUT}; use crate::sql::statements::info::InfoStructure; @@ -76,38 +77,7 @@ impl DefineTableStatement { }; txn.set(key, &dt).await?; // Add table relational fields - if let TableType::Relation(rel) = &self.kind { - // Set the `in` field as a DEFINE FIELD definition - { - let key = crate::key::table::fd::new(opt.ns()?, opt.db()?, &self.name, "in"); - let val = rel.from.clone().unwrap_or(Kind::Record(vec![])); - txn.set( - key, - DefineFieldStatement { - name: Idiom::from(IN.to_vec()), - what: self.name.to_owned(), - kind: Some(val), - ..Default::default() - }, - ) - .await?; - } - // Set the `out` field as a DEFINE FIELD definition - { - let key = crate::key::table::fd::new(opt.ns()?, opt.db()?, &self.name, "out"); - let val = rel.to.clone().unwrap_or(Kind::Record(vec![])); - txn.set( - key, - DefineFieldStatement { - name: Idiom::from(OUT.to_vec()), - what: self.name.to_owned(), - kind: Some(val), - ..Default::default() - }, - ) - .await?; - } - } + self.add_in_out_fields(&txn, opt).await?; // Clear the cache txn.clear(); // Record definition change @@ -158,6 +128,43 @@ impl DefineTableStatement { pub fn allows_normal(&self) -> bool { matches!(self.kind, TableType::Normal | TableType::Any) } + /// Used to add relational fields to existing table records + pub async fn add_in_out_fields(&self, txn: &Transaction, opt: &Options) -> Result<(), Error> { + // Add table relational fields + if let TableType::Relation(rel) = &self.kind { + // Set the `in` field as a DEFINE FIELD definition + { + let key = crate::key::table::fd::new(opt.ns()?, opt.db()?, &self.name, "in"); + let val = rel.from.clone().unwrap_or(Kind::Record(vec![])); + txn.set( + key, + DefineFieldStatement { + name: Idiom::from(IN.to_vec()), + what: self.name.to_owned(), + kind: Some(val), + ..Default::default() + }, + ) + .await?; + } + // Set the `out` field as a DEFINE FIELD definition + { + let key = crate::key::table::fd::new(opt.ns()?, opt.db()?, &self.name, "out"); + let val = rel.to.clone().unwrap_or(Kind::Record(vec![])); + txn.set( + key, + DefineFieldStatement { + name: Idiom::from(OUT.to_vec()), + what: self.name.to_owned(), + kind: Some(val), + ..Default::default() + }, + ) + .await?; + } + } + Ok(()) + } } impl Display for DefineTableStatement { diff --git a/core/src/sql/statements/foreach.rs b/core/src/sql/statements/foreach.rs index 2f2a3041..4d767fe5 100644 --- a/core/src/sql/statements/foreach.rs +++ b/core/src/sql/statements/foreach.rs @@ -69,6 +69,7 @@ impl ForeachStatement { Entry::Relate(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, Entry::Insert(v) => stk.run(|stk| v.compute(stk, &ctx, opt, doc)).await, Entry::Define(v) => v.compute(stk, &ctx, opt, doc).await, + Entry::Alter(v) => v.compute(stk, &ctx, opt, doc).await, Entry::Rebuild(v) => v.compute(stk, &ctx, opt, doc).await, Entry::Remove(v) => v.compute(&ctx, opt, doc).await, Entry::Output(v) => { diff --git a/core/src/sql/statements/mod.rs b/core/src/sql/statements/mod.rs index 786c84a4..08f21629 100644 --- a/core/src/sql/statements/mod.rs +++ b/core/src/sql/statements/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod alter; pub(crate) mod analyze; pub(crate) mod begin; pub(crate) mod r#break; @@ -53,6 +54,8 @@ pub use self::throw::ThrowStatement; pub use self::update::UpdateStatement; pub use self::upsert::UpsertStatement; +pub use self::alter::{AlterStatement, AlterTableStatement}; + pub use self::define::{ DefineAccessStatement, DefineAnalyzerStatement, DefineDatabaseStatement, DefineEventStatement, DefineFieldStatement, DefineFunctionStatement, DefineIndexStatement, DefineModelStatement, diff --git a/core/src/sql/subquery.rs b/core/src/sql/subquery.rs index 7de74105..5f7fdc59 100644 --- a/core/src/sql/subquery.rs +++ b/core/src/sql/subquery.rs @@ -4,9 +4,9 @@ use crate::doc::CursorDoc; use crate::err::Error; use crate::sql::statements::rebuild::RebuildStatement; use crate::sql::statements::{ - CreateStatement, DefineStatement, DeleteStatement, IfelseStatement, InsertStatement, - OutputStatement, RelateStatement, RemoveStatement, SelectStatement, UpdateStatement, - UpsertStatement, + AlterStatement, CreateStatement, DefineStatement, DeleteStatement, IfelseStatement, + InsertStatement, OutputStatement, RelateStatement, RemoveStatement, SelectStatement, + UpdateStatement, UpsertStatement, }; use crate::sql::value::Value; use reblessive::tree::Stk; @@ -17,7 +17,7 @@ use std::fmt::{self, Display, Formatter}; pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Subquery"; -#[revisioned(revision = 3)] +#[revisioned(revision = 4)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] #[serde(rename = "$surrealdb::private::sql::Subquery")] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] @@ -38,6 +38,8 @@ pub enum Subquery { Rebuild(RebuildStatement), #[revision(start = 3)] Upsert(UpsertStatement), + #[revision(start = 4)] + Alter(AlterStatement), } impl PartialOrd for Subquery { @@ -64,6 +66,7 @@ impl Subquery { Self::Define(v) => v.writeable(), Self::Remove(v) => v.writeable(), Self::Rebuild(v) => v.writeable(), + Self::Alter(v) => v.writeable(), } } /// Process this type returning a computed simple Value @@ -95,6 +98,7 @@ impl Subquery { Self::Delete(ref v) => v.compute(stk, &ctx, opt, doc).await, Self::Relate(ref v) => v.compute(stk, &ctx, opt, doc).await, Self::Insert(ref v) => v.compute(stk, &ctx, opt, doc).await, + Self::Alter(ref v) => v.compute(stk, &ctx, opt, doc).await, } } } @@ -114,6 +118,7 @@ impl Display for Subquery { Self::Define(v) => write!(f, "({v})"), Self::Remove(v) => write!(f, "({v})"), Self::Rebuild(v) => write!(f, "({v})"), + Self::Alter(v) => write!(f, "({v})"), Self::Ifelse(v) => Display::fmt(v, f), } } diff --git a/core/src/sql/value/serde/ser/block/entry/mod.rs b/core/src/sql/value/serde/ser/block/entry/mod.rs index 28f55167..422a9559 100644 --- a/core/src/sql/value/serde/ser/block/entry/mod.rs +++ b/core/src/sql/value/serde/ser/block/entry/mod.rs @@ -38,6 +38,7 @@ impl ser::Serializer for Serializer { "Value" => Ok(Entry::Value(value.serialize(ser::value::Serializer.wrap())?)), "Set" => Ok(Entry::Set(value.serialize(ser::statement::set::Serializer.wrap())?)), "Throw" => Ok(Entry::Throw(value.serialize(ser::statement::throw::Serializer.wrap())?)), + "Alter" => Ok(Entry::Alter(value.serialize(ser::statement::alter::Serializer.wrap())?)), "Break" => { Ok(Entry::Break(value.serialize(ser::statement::r#break::Serializer.wrap())?)) } diff --git a/core/src/sql/value/serde/ser/changefeed/opt.rs b/core/src/sql/value/serde/ser/changefeed/opt/mod.rs similarity index 96% rename from core/src/sql/value/serde/ser/changefeed/opt.rs rename to core/src/sql/value/serde/ser/changefeed/opt/mod.rs index 59db40b8..82ae882f 100644 --- a/core/src/sql/value/serde/ser/changefeed/opt.rs +++ b/core/src/sql/value/serde/ser/changefeed/opt/mod.rs @@ -4,6 +4,9 @@ use crate::sql::value::serde::ser; use serde::ser::Impossible; use serde::ser::Serialize; +#[allow(clippy::module_inception)] +pub mod opt; + #[non_exhaustive] pub struct Serializer; diff --git a/core/src/sql/value/serde/ser/changefeed/opt/opt.rs b/core/src/sql/value/serde/ser/changefeed/opt/opt.rs new file mode 100644 index 00000000..a43fdda9 --- /dev/null +++ b/core/src/sql/value/serde/ser/changefeed/opt/opt.rs @@ -0,0 +1,56 @@ +use crate::err::Error; +use crate::sql::changefeed::ChangeFeed; +use crate::sql::value::serde::ser; +use serde::ser::Impossible; +use serde::ser::Serialize; + +#[non_exhaustive] +pub struct Serializer; + +impl ser::Serializer for Serializer { + type Ok = Option>; + type Error = Error; + + type SerializeSeq = Impossible>, Error>; + type SerializeTuple = Impossible>, Error>; + type SerializeTupleStruct = Impossible>, Error>; + type SerializeTupleVariant = Impossible>, Error>; + type SerializeMap = Impossible>, Error>; + type SerializeStruct = Impossible>, Error>; + type SerializeStructVariant = Impossible>, Error>; + + const EXPECTED: &'static str = "an `Option`"; + + #[inline] + fn serialize_none(self) -> Result { + Ok(None) + } + + #[inline] + fn serialize_some(self, value: &T) -> Result + where + T: ?Sized + Serialize, + { + Ok(Some(value.serialize(super::Serializer.wrap())?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ser::Serializer as _; + + #[test] + fn none() { + let option: Option> = None; + let serialized = option.serialize(Serializer.wrap()).unwrap(); + assert_eq!(option, serialized); + } + + #[test] + fn some() { + let option = Some(Some(ChangeFeed::default())); + let serialized = option.serialize(Serializer.wrap()).unwrap(); + assert_eq!(option, serialized); + } +} diff --git a/core/src/sql/value/serde/ser/permissions/mod.rs b/core/src/sql/value/serde/ser/permissions/mod.rs index 49acdd19..6f3ae55f 100644 --- a/core/src/sql/value/serde/ser/permissions/mod.rs +++ b/core/src/sql/value/serde/ser/permissions/mod.rs @@ -7,6 +7,8 @@ use serde::ser::Error as _; use serde::ser::Impossible; use serde::ser::Serialize; +pub mod opt; + #[non_exhaustive] pub struct Serializer; diff --git a/core/src/sql/value/serde/ser/permissions/opt.rs b/core/src/sql/value/serde/ser/permissions/opt.rs new file mode 100644 index 00000000..f3b413a8 --- /dev/null +++ b/core/src/sql/value/serde/ser/permissions/opt.rs @@ -0,0 +1,56 @@ +use crate::err::Error; +use crate::sql::value::serde::ser; +use crate::sql::Permissions; +use serde::ser::Impossible; +use serde::ser::Serialize; + +#[non_exhaustive] +pub struct Serializer; + +impl ser::Serializer for Serializer { + type Ok = Option; + type Error = Error; + + type SerializeSeq = Impossible, Error>; + type SerializeTuple = Impossible, Error>; + type SerializeTupleStruct = Impossible, Error>; + type SerializeTupleVariant = Impossible, Error>; + type SerializeMap = Impossible, Error>; + type SerializeStruct = Impossible, Error>; + type SerializeStructVariant = Impossible, Error>; + + const EXPECTED: &'static str = "an `Option`"; + + #[inline] + fn serialize_none(self) -> Result { + Ok(None) + } + + #[inline] + fn serialize_some(self, value: &T) -> Result + where + T: ?Sized + Serialize, + { + Ok(Some(value.serialize(ser::permissions::Serializer.wrap())?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ser::Serializer as _; + + #[test] + fn none() { + let option: Option = None; + let serialized = option.serialize(Serializer.wrap()).unwrap(); + assert_eq!(option, serialized); + } + + #[test] + fn some() { + let option = Some(Permissions::default()); + let serialized = option.serialize(Serializer.wrap()).unwrap(); + assert_eq!(option, serialized); + } +} diff --git a/core/src/sql/value/serde/ser/primitive/mod.rs b/core/src/sql/value/serde/ser/primitive/mod.rs index eb82af33..2a7f4e29 100644 --- a/core/src/sql/value/serde/ser/primitive/mod.rs +++ b/core/src/sql/value/serde/ser/primitive/mod.rs @@ -7,4 +7,4 @@ pub mod u32; pub mod u64; pub mod u8; -mod opt; +pub mod opt; diff --git a/core/src/sql/value/serde/ser/primitive/opt/bool.rs b/core/src/sql/value/serde/ser/primitive/opt/bool.rs new file mode 100644 index 00000000..bbac676a --- /dev/null +++ b/core/src/sql/value/serde/ser/primitive/opt/bool.rs @@ -0,0 +1,55 @@ +use crate::err::Error; +use crate::sql::value::serde::ser; +use serde::ser::Impossible; +use serde::ser::Serialize; + +#[non_exhaustive] +pub struct Serializer; + +impl ser::Serializer for Serializer { + type Ok = Option; + type Error = Error; + + type SerializeSeq = Impossible, Error>; + type SerializeTuple = Impossible, Error>; + type SerializeTupleStruct = Impossible, Error>; + type SerializeTupleVariant = Impossible, Error>; + type SerializeMap = Impossible, Error>; + type SerializeStruct = Impossible, Error>; + type SerializeStructVariant = Impossible, Error>; + + const EXPECTED: &'static str = "an `Option`"; + + #[inline] + fn serialize_none(self) -> Result { + Ok(None) + } + + #[inline] + fn serialize_some(self, value: &T) -> Result + where + T: ?Sized + Serialize, + { + Ok(Some(value.serialize(ser::primitive::bool::Serializer.wrap())?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ser::Serializer as _; + + #[test] + fn none() { + let option: Option = None; + let serialized = option.serialize(Serializer.wrap()).unwrap(); + assert_eq!(option, serialized); + } + + #[test] + fn some() { + let option = Some(bool::default()); + let serialized = option.serialize(Serializer.wrap()).unwrap(); + assert_eq!(option, serialized); + } +} diff --git a/core/src/sql/value/serde/ser/primitive/opt/mod.rs b/core/src/sql/value/serde/ser/primitive/opt/mod.rs index ce7b9d0b..07cfbc51 100644 --- a/core/src/sql/value/serde/ser/primitive/opt/mod.rs +++ b/core/src/sql/value/serde/ser/primitive/opt/mod.rs @@ -1,2 +1,3 @@ +pub mod bool; pub mod u32; pub mod u64; diff --git a/core/src/sql/value/serde/ser/statement/alter/mod.rs b/core/src/sql/value/serde/ser/statement/alter/mod.rs new file mode 100644 index 00000000..8ec2d75a --- /dev/null +++ b/core/src/sql/value/serde/ser/statement/alter/mod.rs @@ -0,0 +1,58 @@ +mod table; + +use crate::err::Error; +use crate::sql::statements::AlterStatement; +use crate::sql::value::serde::ser; +use serde::ser::Error as _; +use serde::ser::Impossible; +use serde::ser::Serialize; + +#[non_exhaustive] +pub struct Serializer; + +impl ser::Serializer for Serializer { + type Ok = AlterStatement; + type Error = Error; + + type SerializeSeq = Impossible; + type SerializeTuple = Impossible; + type SerializeTupleStruct = Impossible; + type SerializeTupleVariant = Impossible; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Impossible; + + const EXPECTED: &'static str = "an enum `AlterStatement`"; + + #[inline] + fn serialize_newtype_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + Serialize, + { + match variant { + "Table" => Ok(AlterStatement::Table(value.serialize(table::Serializer.wrap())?)), + variant => { + Err(Error::custom(format!("unexpected newtype variant `{name}::{variant}`"))) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ser::Serializer as _; + + #[test] + fn table() { + let stmt = AlterStatement::Table(Default::default()); + let serialized = stmt.serialize(Serializer.wrap()).unwrap(); + assert_eq!(stmt, serialized); + } +} diff --git a/core/src/sql/value/serde/ser/statement/alter/table.rs b/core/src/sql/value/serde/ser/statement/alter/table.rs new file mode 100644 index 00000000..e841776a --- /dev/null +++ b/core/src/sql/value/serde/ser/statement/alter/table.rs @@ -0,0 +1,120 @@ +use crate::err::Error; +use crate::sql::changefeed::ChangeFeed; +use crate::sql::statements::AlterTableStatement; +use crate::sql::value::serde::ser; +use crate::sql::Ident; +use crate::sql::Permissions; +use crate::sql::Strand; +use crate::sql::TableType; +use ser::Serializer as _; +use serde::ser::Error as _; +use serde::ser::Impossible; +use serde::ser::Serialize; + +#[non_exhaustive] +pub struct Serializer; + +impl ser::Serializer for Serializer { + type Ok = AlterTableStatement; + type Error = Error; + + type SerializeSeq = Impossible; + type SerializeTuple = Impossible; + type SerializeTupleStruct = Impossible; + type SerializeTupleVariant = Impossible; + type SerializeMap = Impossible; + type SerializeStruct = SerializeAlterTableStatement; + type SerializeStructVariant = Impossible; + + const EXPECTED: &'static str = "a struct `AlterTableStatement`"; + + #[inline] + fn serialize_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Ok(SerializeAlterTableStatement::default()) + } +} + +#[derive(Default)] +#[non_exhaustive] +pub struct SerializeAlterTableStatement { + name: Ident, + drop: Option, + full: Option, + permissions: Option, + changefeed: Option>, + comment: Option>, + if_exists: bool, + kind: Option, +} + +impl serde::ser::SerializeStruct for SerializeAlterTableStatement { + type Ok = AlterTableStatement; + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Error> + where + T: ?Sized + Serialize, + { + match key { + "name" => { + self.name = Ident(value.serialize(ser::string::Serializer.wrap())?); + } + "drop" => { + self.drop = value.serialize(ser::primitive::opt::bool::Serializer.wrap())?; + } + "full" => { + self.full = value.serialize(ser::primitive::opt::bool::Serializer.wrap())?; + } + "permissions" => { + self.permissions = value.serialize(ser::permissions::opt::Serializer.wrap())?; + } + "changefeed" => { + self.changefeed = value.serialize(ser::changefeed::opt::opt::Serializer.wrap())?; + } + "comment" => { + self.comment = value.serialize(ser::strand::opt::opt::Serializer.wrap())?; + } + "kind" => { + self.kind = value.serialize(ser::table_type::opt::Serializer.wrap())?; + } + "if_exists" => { + self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } + key => { + return Err(Error::custom(format!( + "unexpected field `AlterTableStatement::{key}`" + ))); + } + } + Ok(()) + } + + fn end(self) -> Result { + Ok(AlterTableStatement { + name: self.name, + drop: self.drop, + full: self.full, + permissions: self.permissions, + changefeed: self.changefeed, + comment: self.comment, + kind: self.kind, + if_exists: self.if_exists, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn default() { + let stmt = AlterTableStatement::default(); + let value: AlterTableStatement = stmt.serialize(Serializer.wrap()).unwrap(); + assert_eq!(value, stmt); + } +} diff --git a/core/src/sql/value/serde/ser/statement/mod.rs b/core/src/sql/value/serde/ser/statement/mod.rs index 7023f9dd..e28d24d6 100644 --- a/core/src/sql/value/serde/ser/statement/mod.rs +++ b/core/src/sql/value/serde/ser/statement/mod.rs @@ -1,3 +1,4 @@ +pub mod alter; pub mod analyze; pub mod begin; pub mod r#break; @@ -62,6 +63,7 @@ impl ser::Serializer for Serializer { T: ?Sized + Serialize, { match variant { + "Alter" => Ok(Statement::Alter(value.serialize(alter::Serializer.wrap())?)), "Analyze" => Ok(Statement::Analyze(value.serialize(analyze::Serializer.wrap())?)), "Begin" => Ok(Statement::Begin(value.serialize(begin::Serializer.wrap())?)), "Break" => Ok(Statement::Break(value.serialize(r#break::Serializer.wrap())?)), @@ -100,12 +102,20 @@ impl ser::Serializer for Serializer { mod tests { use super::*; use crate::sql::statements::analyze::AnalyzeStatement; + use crate::sql::statements::AlterStatement; use crate::sql::statements::DefineStatement; use crate::sql::statements::InfoStatement; use crate::sql::statements::RemoveStatement; use ser::Serializer as _; use serde::Serialize; + #[test] + fn alter() { + let statement = Statement::Alter(AlterStatement::Table(Default::default())); + let serialized = statement.serialize(Serializer.wrap()).unwrap(); + assert_eq!(statement, serialized); + } + #[test] fn analyze() { let statement = diff --git a/core/src/sql/value/serde/ser/strand/opt.rs b/core/src/sql/value/serde/ser/strand/opt/mod.rs similarity index 96% rename from core/src/sql/value/serde/ser/strand/opt.rs rename to core/src/sql/value/serde/ser/strand/opt/mod.rs index b157917a..f0deaec5 100644 --- a/core/src/sql/value/serde/ser/strand/opt.rs +++ b/core/src/sql/value/serde/ser/strand/opt/mod.rs @@ -4,6 +4,9 @@ use crate::sql::Strand; use serde::ser::Impossible; use serde::ser::Serialize; +#[allow(clippy::module_inception)] +pub mod opt; + #[non_exhaustive] pub struct Serializer; diff --git a/core/src/sql/value/serde/ser/strand/opt/opt.rs b/core/src/sql/value/serde/ser/strand/opt/opt.rs new file mode 100644 index 00000000..f2f8dc67 --- /dev/null +++ b/core/src/sql/value/serde/ser/strand/opt/opt.rs @@ -0,0 +1,56 @@ +use crate::err::Error; +use crate::sql::value::serde::ser; +use crate::sql::Strand; +use serde::ser::Impossible; +use serde::ser::Serialize; + +#[non_exhaustive] +pub struct Serializer; + +impl ser::Serializer for Serializer { + type Ok = Option>; + type Error = Error; + + type SerializeSeq = Impossible>, Error>; + type SerializeTuple = Impossible>, Error>; + type SerializeTupleStruct = Impossible>, Error>; + type SerializeTupleVariant = Impossible>, Error>; + type SerializeMap = Impossible>, Error>; + type SerializeStruct = Impossible>, Error>; + type SerializeStructVariant = Impossible>, Error>; + + const EXPECTED: &'static str = "an `Option`"; + + #[inline] + fn serialize_none(self) -> Result { + Ok(None) + } + + #[inline] + fn serialize_some(self, value: &T) -> Result + where + T: ?Sized + Serialize, + { + Ok(Some(value.serialize(super::Serializer.wrap())?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ser::Serializer as _; + + #[test] + fn none() { + let option: Option> = None; + let serialized = option.serialize(Serializer.wrap()).unwrap(); + assert_eq!(option, serialized); + } + + #[test] + fn some() { + let option = Some(Some(Strand::default())); + let serialized = option.serialize(Serializer.wrap()).unwrap(); + assert_eq!(option, serialized); + } +} diff --git a/core/src/sql/value/serde/ser/table_type/mod.rs b/core/src/sql/value/serde/ser/table_type/mod.rs index d37cd325..c80483bf 100644 --- a/core/src/sql/value/serde/ser/table_type/mod.rs +++ b/core/src/sql/value/serde/ser/table_type/mod.rs @@ -5,6 +5,8 @@ use serde::ser::Error as _; use serde::ser::Impossible; use serde::ser::Serialize; +pub mod opt; + #[non_exhaustive] pub struct Serializer; diff --git a/core/src/sql/value/serde/ser/table_type/opt.rs b/core/src/sql/value/serde/ser/table_type/opt.rs new file mode 100644 index 00000000..d8a71cce --- /dev/null +++ b/core/src/sql/value/serde/ser/table_type/opt.rs @@ -0,0 +1,56 @@ +use crate::err::Error; +use crate::sql::value::serde::ser; +use crate::sql::TableType; +use serde::ser::Impossible; +use serde::ser::Serialize; + +#[non_exhaustive] +pub struct Serializer; + +impl ser::Serializer for Serializer { + type Ok = Option; + type Error = Error; + + type SerializeSeq = Impossible, Error>; + type SerializeTuple = Impossible, Error>; + type SerializeTupleStruct = Impossible, Error>; + type SerializeTupleVariant = Impossible, Error>; + type SerializeMap = Impossible, Error>; + type SerializeStruct = Impossible, Error>; + type SerializeStructVariant = Impossible, Error>; + + const EXPECTED: &'static str = "an `Option`"; + + #[inline] + fn serialize_none(self) -> Result { + Ok(None) + } + + #[inline] + fn serialize_some(self, value: &T) -> Result + where + T: ?Sized + Serialize, + { + Ok(Some(value.serialize(super::Serializer.wrap())?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ser::Serializer as _; + + #[test] + fn none() { + let option: Option = None; + let serialized = option.serialize(Serializer.wrap()).unwrap(); + assert_eq!(option, serialized); + } + + #[test] + fn some() { + let option = Some(TableType::default()); + let serialized = option.serialize(Serializer.wrap()).unwrap(); + assert_eq!(option, serialized); + } +} diff --git a/core/src/syn/lexer/keywords.rs b/core/src/syn/lexer/keywords.rs index 74f5dfeb..c844d48b 100644 --- a/core/src/syn/lexer/keywords.rs +++ b/core/src/syn/lexer/keywords.rs @@ -7,6 +7,7 @@ use unicase::UniCase; /// A set of keywords which might in some contexts are dissallowed as an identifier. pub static RESERVED_KEYWORD: phf::Set> = phf_set! { + UniCase::ascii("ALTER"), UniCase::ascii("ANALYZE"), UniCase::ascii("BEGIN"), UniCase::ascii("BREAK"), @@ -59,6 +60,7 @@ pub(crate) static KEYWORDS: phf::Map, TokenKind> = phf_map UniCase::ascii("AFTER") => TokenKind::Keyword(Keyword::After), UniCase::ascii("ALGORITHM") => TokenKind::Keyword(Keyword::Algorithm), UniCase::ascii("ALL") => TokenKind::Keyword(Keyword::All), + UniCase::ascii("Alter") => TokenKind::Keyword(Keyword::Alter), UniCase::ascii("ANALYZE") => TokenKind::Keyword(Keyword::Analyze), UniCase::ascii("ANALYZER") => TokenKind::Keyword(Keyword::Analyzer), UniCase::ascii("AS") => TokenKind::Keyword(Keyword::As), diff --git a/core/src/syn/parser/stmt/alter.rs b/core/src/syn/parser/stmt/alter.rs new file mode 100644 index 00000000..74fd8e3f --- /dev/null +++ b/core/src/syn/parser/stmt/alter.rs @@ -0,0 +1,101 @@ +use reblessive::Stk; + +use crate::{ + sql::{ + statements::{AlterStatement, AlterTableStatement}, + TableType, + }, + syn::{ + parser::{ + mac::{expected, unexpected}, + ParseResult, Parser, + }, + token::t, + }, +}; + +impl Parser<'_> { + pub async fn parse_alter_stmt(&mut self, ctx: &mut Stk) -> ParseResult { + match self.next().kind { + t!("TABLE") => self.parse_alter_table(ctx).await.map(AlterStatement::Table), + x => unexpected!(self, x, "a alter statement keyword"), + } + } + + pub async fn parse_alter_table(&mut self, ctx: &mut Stk) -> ParseResult { + let if_exists = if self.eat(t!("IF")) { + expected!(self, t!("EXISTS")); + true + } else { + false + }; + let name = self.next_token_value()?; + let mut res = AlterTableStatement { + name, + if_exists, + ..Default::default() + }; + + loop { + match self.peek_kind() { + t!("COMMENT") => { + self.pop_peek(); + if self.eat(t!("NONE")) { + res.comment = Some(None); + } else { + res.comment = Some(Some(self.next_token_value()?)); + } + } + t!("DROP") => { + self.pop_peek(); + if self.eat(t!("false")) { + res.drop = Some(false); + } else { + res.drop = Some(true); + } + } + t!("TYPE") => { + self.pop_peek(); + match self.peek_kind() { + t!("NORMAL") => { + self.pop_peek(); + res.kind = Some(TableType::Normal); + } + t!("RELATION") => { + self.pop_peek(); + res.kind = Some(TableType::Relation(self.parse_relation_schema()?)); + } + t!("ANY") => { + self.pop_peek(); + res.kind = Some(TableType::Any); + } + x => unexpected!(self, x, "`NORMAL`, `RELATION`, or `ANY`"), + } + } + t!("SCHEMALESS") => { + self.pop_peek(); + res.full = Some(false); + } + t!("SCHEMAFULL") => { + self.pop_peek(); + res.full = Some(true); + } + t!("PERMISSIONS") => { + self.pop_peek(); + res.permissions = Some(ctx.run(|ctx| self.parse_permission(ctx, false)).await?); + } + t!("CHANGEFEED") => { + self.pop_peek(); + if self.eat(t!("NONE")) { + res.changefeed = Some(None); + } else { + res.changefeed = Some(Some(self.parse_changefeed()?)); + } + } + _ => break, + } + } + + Ok(res) + } +} diff --git a/core/src/syn/parser/stmt/mod.rs b/core/src/syn/parser/stmt/mod.rs index 60c24100..acf45e4d 100644 --- a/core/src/syn/parser/stmt/mod.rs +++ b/core/src/syn/parser/stmt/mod.rs @@ -25,6 +25,7 @@ use crate::{ use super::{mac::expected, ParseResult, Parser}; +mod alter; mod create; mod define; mod delete; @@ -80,21 +81,21 @@ impl Parser<'_> { fn token_kind_starts_statement(kind: TokenKind) -> bool { matches!( kind, - t!("ANALYZE") - | t!("BEGIN") | t!("BREAK") - | t!("CANCEL") | t!("COMMIT") - | t!("CONTINUE") | t!("CREATE") - | t!("DEFINE") | t!("DELETE") - | t!("FOR") | t!("IF") - | t!("INFO") | t!("INSERT") - | t!("KILL") | t!("LIVE") - | t!("OPTION") | t!("REBUILD") - | t!("RETURN") | t!("RELATE") - | t!("REMOVE") | t!("SELECT") - | t!("LET") | t!("SHOW") - | t!("SLEEP") | t!("THROW") - | t!("UPDATE") | t!("UPSERT") - | t!("USE") + t!("ALTER") + | t!("ANALYZE") | t!("BEGIN") + | t!("BREAK") | t!("CANCEL") + | t!("COMMIT") | t!("CONTINUE") + | t!("CREATE") | t!("DEFINE") + | t!("DELETE") | t!("FOR") + | t!("IF") | t!("INFO") + | t!("INSERT") | t!("KILL") + | t!("LIVE") | t!("OPTION") + | t!("REBUILD") | t!("RETURN") + | t!("RELATE") | t!("REMOVE") + | t!("SELECT") | t!("LET") + | t!("SHOW") | t!("SLEEP") + | t!("THROW") | t!("UPDATE") + | t!("UPSERT") | t!("USE") ) } @@ -107,6 +108,10 @@ impl Parser<'_> { async fn parse_stmt_inner(&mut self, ctx: &mut Stk) -> ParseResult { let token = self.peek(); match token.kind { + t!("ALTER") => { + self.pop_peek(); + ctx.run(|ctx| self.parse_alter_stmt(ctx)).await.map(Statement::Alter) + } t!("ANALYZE") => { self.pop_peek(); self.parse_analyze().map(Statement::Analyze) @@ -236,6 +241,10 @@ impl Parser<'_> { async fn parse_entry_inner(&mut self, ctx: &mut Stk) -> ParseResult { let token = self.peek(); match token.kind { + t!("ALTER") => { + self.pop_peek(); + self.parse_alter_stmt(ctx).await.map(Entry::Alter) + } t!("BREAK") => { self.pop_peek(); Ok(Entry::Break(BreakStatement)) diff --git a/core/src/syn/parser/stmt/relate.rs b/core/src/syn/parser/stmt/relate.rs index 75e656dc..ed90d76f 100644 --- a/core/src/syn/parser/stmt/relate.rs +++ b/core/src/syn/parser/stmt/relate.rs @@ -84,6 +84,7 @@ impl Parser<'_> { | t!("DELETE") | t!("RELATE") | t!("DEFINE") + | t!("ALTER") | t!("REMOVE") | t!("REBUILD") => { self.parse_inner_subquery(ctx, None).await.map(|x| Value::Subquery(Box::new(x))) diff --git a/core/src/syn/token/keyword.rs b/core/src/syn/token/keyword.rs index 8b04e30f..81b54723 100644 --- a/core/src/syn/token/keyword.rs +++ b/core/src/syn/token/keyword.rs @@ -28,6 +28,7 @@ keyword! { After => "AFTER", Algorithm => "ALGORITHM", All => "ALL", + Alter => "ALTER", Analyze => "ANALYZE", Analyzer => "ANALYZER", As => "AS", diff --git a/lib/src/api/opt/query.rs b/lib/src/api/opt/query.rs index 10fc1e9f..95c9d849 100644 --- a/lib/src/api/opt/query.rs +++ b/lib/src/api/opt/query.rs @@ -142,6 +142,12 @@ impl IntoQuery for DefineStatement { } } +impl IntoQuery for AlterStatement { + fn into_query(self) -> Result> { + Ok(vec![Statement::Alter(self)]) + } +} + impl IntoQuery for RemoveStatement { fn into_query(self) -> Result> { Ok(vec![Statement::Remove(self)]) diff --git a/lib/tests/alter.rs b/lib/tests/alter.rs new file mode 100644 index 00000000..3a8bdc71 --- /dev/null +++ b/lib/tests/alter.rs @@ -0,0 +1,130 @@ +mod parse; +use parse::Parse; + +mod helpers; +use helpers::*; + +use surrealdb::dbs::Session; +use surrealdb::err::Error; +use surrealdb::sql::Value; + +#[tokio::test] +async fn define_alter_table() -> Result<(), Error> { + let sql = " + DEFINE TABLE test; + INFO FOR DB; + + ALTER TABLE test + DROP + SCHEMALESS + PERMISSIONS FOR create FULL + CHANGEFEED 1d + COMMENT 'test' + TYPE NORMAL; + INFO FOR DB; + + ALTER TABLE test + DROP false + SCHEMAFULL + PERMISSIONS NONE + CHANGEFEED NONE + COMMENT NONE + TYPE ANY; + INFO FOR DB; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 6); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + accesses: {}, + analyzers: {}, + functions: {}, + models: {}, + params: {}, + tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMALESS PERMISSIONS NONE' }, + users: {}, + }", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + accesses: {}, + analyzers: {}, + functions: {}, + models: {}, + params: {}, + tables: { test: 'DEFINE TABLE test TYPE NORMAL DROP SCHEMALESS COMMENT \\'test\\' CHANGEFEED 1d PERMISSIONS FOR select, update, delete NONE, FOR create FULL' }, + users: {}, + }", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + accesses: {}, + analyzers: {}, + functions: {}, + models: {}, + params: {}, + tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE' }, + users: {}, + }", + ); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn define_alter_table_if_exists() -> Result<(), Error> { + let sql = " + ALTER TABLE test COMMENT 'bla'; + ALTER TABLE IF EXISTS test COMMENT 'bla'; + INFO FOR DB + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 3); + // + let tmp = res.remove(0).result; + let _err = Error::TbNotFound { + value: "test".to_string(), + }; + assert!(matches!(tmp, Err(_err))); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + accesses: {}, + analyzers: {}, + functions: {}, + models: {}, + params: {}, + tables: {}, + users: {}, + }", + ); + assert_eq!(tmp, val); + // + Ok(()) +}