ALTER TABLE-statement (#4435)

Co-authored-by: Tobie Morgan Hitchcock <tobie@surrealdb.com>
This commit is contained in:
Micha de Vries 2024-07-30 18:25:11 +02:00 committed by GitHub
parent d030c7d498
commit 24e9534155
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 1037 additions and 64 deletions

View file

@ -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}"),
}
}
}

View file

@ -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<Value, Error> {
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}"),

View file

@ -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<Value, Error> {
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<u8> = stm.into();
assert_eq!(16, enc.len());
}
}

View file

@ -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<bool>,
pub full: Option<bool>,
pub permissions: Option<Permissions>,
pub changefeed: Option<Option<ChangeFeed>>,
pub comment: Option<Option<Strand>>,
pub kind: Option<TableType>,
}
impl AlterTableStatement {
pub(crate) async fn compute(
&self,
_stk: &mut Stk,
ctx: &Context<'_>,
opt: &Options,
_doc: Option<&CursorDoc<'_>>,
) -> Result<Value, Error> {
// 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::<Vec<_>>().join(" | ")
)?;
}
if let Some(Kind::Record(kind)) = &rel.to {
write!(
f,
" OUT {}",
kind.iter().map(|t| t.0.as_str()).collect::<Vec<_>>().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(())
}
}

View file

@ -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 {

View file

@ -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) => {

View file

@ -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,

View file

@ -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),
}
}

View file

@ -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())?))
}

View file

@ -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;

View file

@ -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<Option<ChangeFeed>>;
type Error = Error;
type SerializeSeq = Impossible<Option<Option<ChangeFeed>>, Error>;
type SerializeTuple = Impossible<Option<Option<ChangeFeed>>, Error>;
type SerializeTupleStruct = Impossible<Option<Option<ChangeFeed>>, Error>;
type SerializeTupleVariant = Impossible<Option<Option<ChangeFeed>>, Error>;
type SerializeMap = Impossible<Option<Option<ChangeFeed>>, Error>;
type SerializeStruct = Impossible<Option<Option<ChangeFeed>>, Error>;
type SerializeStructVariant = Impossible<Option<Option<ChangeFeed>>, Error>;
const EXPECTED: &'static str = "an `Option<ChangeFeed>`";
#[inline]
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
Ok(None)
}
#[inline]
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
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<Option<ChangeFeed>> = 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);
}
}

View file

@ -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;

View file

@ -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<Permissions>;
type Error = Error;
type SerializeSeq = Impossible<Option<Permissions>, Error>;
type SerializeTuple = Impossible<Option<Permissions>, Error>;
type SerializeTupleStruct = Impossible<Option<Permissions>, Error>;
type SerializeTupleVariant = Impossible<Option<Permissions>, Error>;
type SerializeMap = Impossible<Option<Permissions>, Error>;
type SerializeStruct = Impossible<Option<Permissions>, Error>;
type SerializeStructVariant = Impossible<Option<Permissions>, Error>;
const EXPECTED: &'static str = "an `Option<Permissions>`";
#[inline]
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
Ok(None)
}
#[inline]
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
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<Permissions> = 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);
}
}

View file

@ -7,4 +7,4 @@ pub mod u32;
pub mod u64;
pub mod u8;
mod opt;
pub mod opt;

View file

@ -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<bool>;
type Error = Error;
type SerializeSeq = Impossible<Option<bool>, Error>;
type SerializeTuple = Impossible<Option<bool>, Error>;
type SerializeTupleStruct = Impossible<Option<bool>, Error>;
type SerializeTupleVariant = Impossible<Option<bool>, Error>;
type SerializeMap = Impossible<Option<bool>, Error>;
type SerializeStruct = Impossible<Option<bool>, Error>;
type SerializeStructVariant = Impossible<Option<bool>, Error>;
const EXPECTED: &'static str = "an `Option<bool>`";
#[inline]
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
Ok(None)
}
#[inline]
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
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<bool> = 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);
}
}

View file

@ -1,2 +1,3 @@
pub mod bool;
pub mod u32;
pub mod u64;

View file

@ -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<AlterStatement, Error>;
type SerializeTuple = Impossible<AlterStatement, Error>;
type SerializeTupleStruct = Impossible<AlterStatement, Error>;
type SerializeTupleVariant = Impossible<AlterStatement, Error>;
type SerializeMap = Impossible<AlterStatement, Error>;
type SerializeStruct = Impossible<AlterStatement, Error>;
type SerializeStructVariant = Impossible<AlterStatement, Error>;
const EXPECTED: &'static str = "an enum `AlterStatement`";
#[inline]
fn serialize_newtype_variant<T>(
self,
name: &'static str,
_variant_index: u32,
variant: &'static str,
value: &T,
) -> Result<Self::Ok, Error>
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);
}
}

View file

@ -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<AlterTableStatement, Error>;
type SerializeTuple = Impossible<AlterTableStatement, Error>;
type SerializeTupleStruct = Impossible<AlterTableStatement, Error>;
type SerializeTupleVariant = Impossible<AlterTableStatement, Error>;
type SerializeMap = Impossible<AlterTableStatement, Error>;
type SerializeStruct = SerializeAlterTableStatement;
type SerializeStructVariant = Impossible<AlterTableStatement, Error>;
const EXPECTED: &'static str = "a struct `AlterTableStatement`";
#[inline]
fn serialize_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeStruct, Error> {
Ok(SerializeAlterTableStatement::default())
}
}
#[derive(Default)]
#[non_exhaustive]
pub struct SerializeAlterTableStatement {
name: Ident,
drop: Option<bool>,
full: Option<bool>,
permissions: Option<Permissions>,
changefeed: Option<Option<ChangeFeed>>,
comment: Option<Option<Strand>>,
if_exists: bool,
kind: Option<TableType>,
}
impl serde::ser::SerializeStruct for SerializeAlterTableStatement {
type Ok = AlterTableStatement;
type Error = Error;
fn serialize_field<T>(&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<Self::Ok, Error> {
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);
}
}

View file

@ -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 =

View file

@ -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;

View file

@ -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<Option<Strand>>;
type Error = Error;
type SerializeSeq = Impossible<Option<Option<Strand>>, Error>;
type SerializeTuple = Impossible<Option<Option<Strand>>, Error>;
type SerializeTupleStruct = Impossible<Option<Option<Strand>>, Error>;
type SerializeTupleVariant = Impossible<Option<Option<Strand>>, Error>;
type SerializeMap = Impossible<Option<Option<Strand>>, Error>;
type SerializeStruct = Impossible<Option<Option<Strand>>, Error>;
type SerializeStructVariant = Impossible<Option<Option<Strand>>, Error>;
const EXPECTED: &'static str = "an `Option<Strand>`";
#[inline]
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
Ok(None)
}
#[inline]
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
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<Option<Strand>> = 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);
}
}

View file

@ -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;

View file

@ -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<TableType>;
type Error = Error;
type SerializeSeq = Impossible<Option<TableType>, Error>;
type SerializeTuple = Impossible<Option<TableType>, Error>;
type SerializeTupleStruct = Impossible<Option<TableType>, Error>;
type SerializeTupleVariant = Impossible<Option<TableType>, Error>;
type SerializeMap = Impossible<Option<TableType>, Error>;
type SerializeStruct = Impossible<Option<TableType>, Error>;
type SerializeStructVariant = Impossible<Option<TableType>, Error>;
const EXPECTED: &'static str = "an `Option<TableType>`";
#[inline]
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
Ok(None)
}
#[inline]
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
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<TableType> = 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);
}
}

View file

@ -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<UniCase<&'static str>> = phf_set! {
UniCase::ascii("ALTER"),
UniCase::ascii("ANALYZE"),
UniCase::ascii("BEGIN"),
UniCase::ascii("BREAK"),
@ -59,6 +60,7 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, 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),

View file

@ -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<AlterStatement> {
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<AlterTableStatement> {
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)
}
}

View file

@ -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<Statement> {
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<Entry> {
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))

View file

@ -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)))

View file

@ -28,6 +28,7 @@ keyword! {
After => "AFTER",
Algorithm => "ALGORITHM",
All => "ALL",
Alter => "ALTER",
Analyze => "ANALYZE",
Analyzer => "ANALYZER",
As => "AS",

View file

@ -142,6 +142,12 @@ impl IntoQuery for DefineStatement {
}
}
impl IntoQuery for AlterStatement {
fn into_query(self) -> Result<Vec<Statement>> {
Ok(vec![Statement::Alter(self)])
}
}
impl IntoQuery for RemoveStatement {
fn into_query(self) -> Result<Vec<Statement>> {
Ok(vec![Statement::Remove(self)])

130
lib/tests/alter.rs Normal file
View file

@ -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(())
}