Add DEFINE TABLE ... RELATION
(#3710)
Co-authored-by: Tobie Morgan Hitchcock <tobie@surrealdb.com>
This commit is contained in:
parent
7f6abc69bb
commit
50125cb2b7
28 changed files with 602 additions and 47 deletions
core/src
lib/tests
tests
|
@ -13,6 +13,8 @@ impl<'a> Document<'a> {
|
|||
txn: &Transaction,
|
||||
stm: &Statement<'_>,
|
||||
) -> Result<Value, Error> {
|
||||
// Check if table has corrent relation status
|
||||
self.relation(ctx, opt, txn, stm).await?;
|
||||
// Alter record data
|
||||
self.alter(ctx, opt, txn, stm).await?;
|
||||
// Merge fields data
|
||||
|
|
|
@ -50,6 +50,8 @@ impl<'a> Document<'a> {
|
|||
txn: &Transaction,
|
||||
stm: &Statement<'_>,
|
||||
) -> Result<Value, Error> {
|
||||
// Check if table has correct relation status
|
||||
self.relation(ctx, opt, txn, stm).await?;
|
||||
// Merge record data
|
||||
self.merge(ctx, opt, txn, stm).await?;
|
||||
// Merge fields data
|
||||
|
|
|
@ -34,6 +34,7 @@ mod lives; // Processes any live queries relevant for this document
|
|||
mod merge; // Merges any field changes for an INSERT statement
|
||||
mod pluck; // Pulls the projected expressions from the document
|
||||
mod purge; // Deletes this document, and any edges or indexes
|
||||
mod relation; // Checks whether the record is the right kind for the table
|
||||
mod reset; // Resets internal fields which were set for this document
|
||||
mod store; // Writes the document content to the storage engine
|
||||
mod table; // Processes any foreign tables relevant for this document
|
||||
|
|
|
@ -13,6 +13,8 @@ impl<'a> Document<'a> {
|
|||
txn: &Transaction,
|
||||
stm: &Statement<'_>,
|
||||
) -> Result<Value, Error> {
|
||||
// Check if table has correct relation status
|
||||
self.relation(ctx, opt, txn, stm).await?;
|
||||
// Check current record
|
||||
match self.current.doc.is_some() {
|
||||
// Create new edge
|
||||
|
|
42
core/src/doc/relation.rs
Normal file
42
core/src/doc/relation.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use crate::ctx::Context;
|
||||
use crate::dbs::Statement;
|
||||
use crate::dbs::{Options, Transaction};
|
||||
use crate::doc::Document;
|
||||
use crate::err::Error;
|
||||
|
||||
impl<'a> Document<'a> {
|
||||
pub async fn relation(
|
||||
&mut self,
|
||||
_ctx: &Context<'_>,
|
||||
opt: &Options,
|
||||
txn: &Transaction,
|
||||
stm: &Statement<'_>,
|
||||
) -> Result<(), Error> {
|
||||
let tb = self.tb(opt, txn).await?;
|
||||
|
||||
let rid = self.id.as_ref().unwrap();
|
||||
match stm {
|
||||
Statement::Create(_) | Statement::Insert(_) => {
|
||||
if !tb.allows_normal() {
|
||||
return Err(Error::TableCheck {
|
||||
thing: rid.to_string(),
|
||||
relation: false,
|
||||
target_type: tb.kind.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Statement::Relate(_) => {
|
||||
if !tb.allows_relation() {
|
||||
return Err(Error::TableCheck {
|
||||
thing: rid.to_string(),
|
||||
relation: true,
|
||||
target_type: tb.kind.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// Carry on
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ use crate::sql::idiom::Idiom;
|
|||
use crate::sql::index::Distance;
|
||||
use crate::sql::thing::Thing;
|
||||
use crate::sql::value::Value;
|
||||
use crate::sql::TableType;
|
||||
use crate::syn::error::RenderedError as RenderedParserError;
|
||||
use crate::vs::Error as VersionstampError;
|
||||
use base64_lib::DecodeError as Base64Error;
|
||||
|
@ -525,6 +526,14 @@ pub enum Error {
|
|||
value: String,
|
||||
},
|
||||
|
||||
/// The specified table is not configured for the type of record being added
|
||||
#[error("Found record: `{thing}` which is {}a relation, but expected a `target_type`", if *relation { "not " } else { "" })]
|
||||
TableCheck {
|
||||
thing: String,
|
||||
relation: bool,
|
||||
target_type: TableType,
|
||||
},
|
||||
|
||||
/// The specified field did not conform to the field type check
|
||||
#[error("Found {value} for field `{field}`, with record `{thing}`, but expected a {check}")]
|
||||
FieldCheck {
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::key::database::tb;
|
|||
use crate::key::database::tb::Tb;
|
||||
use crate::kvs::ScanPage;
|
||||
use crate::sql::statements::DefineTableStatement;
|
||||
use crate::sql::TableType;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
|
@ -71,6 +72,7 @@ async fn table_definitions_can_be_deleted() {
|
|||
changefeed: None,
|
||||
comment: None,
|
||||
if_not_exists: false,
|
||||
kind: TableType::Any,
|
||||
};
|
||||
tx.set(&key, &value).await.unwrap();
|
||||
|
||||
|
|
|
@ -293,8 +293,8 @@ mod tests {
|
|||
#[test]
|
||||
fn pretty_define_query() {
|
||||
let query = parse("DEFINE TABLE test SCHEMAFULL PERMISSIONS FOR create, update, delete NONE FOR select WHERE public = true;").unwrap();
|
||||
assert_eq!(format!("{}", query), "DEFINE TABLE test SCHEMAFULL PERMISSIONS FOR select WHERE public = true, FOR create, update, delete NONE;");
|
||||
assert_eq!(format!("{:#}", query), "DEFINE TABLE test SCHEMAFULL\n\tPERMISSIONS\n\t\tFOR select\n\t\t\tWHERE public = true\n\t\tFOR create, update, delete NONE\n;");
|
||||
assert_eq!(format!("{}", query), "DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS FOR select WHERE public = true, FOR create, update, delete NONE;");
|
||||
assert_eq!(format!("{:#}", query), "DEFINE TABLE test TYPE ANY SCHEMAFULL\n\tPERMISSIONS\n\t\tFOR select\n\t\t\tWHERE public = true\n\t\tFOR create, update, delete NONE\n;");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -58,6 +58,7 @@ pub(crate) mod statement;
|
|||
pub(crate) mod strand;
|
||||
pub(crate) mod subquery;
|
||||
pub(crate) mod table;
|
||||
pub(crate) mod table_type;
|
||||
pub(crate) mod thing;
|
||||
pub(crate) mod timeout;
|
||||
pub(crate) mod tokenizer;
|
||||
|
@ -133,6 +134,7 @@ pub use self::strand::Strand;
|
|||
pub use self::subquery::Subquery;
|
||||
pub use self::table::Table;
|
||||
pub use self::table::Tables;
|
||||
pub use self::table_type::{Relation, TableType};
|
||||
pub use self::thing::Thing;
|
||||
pub use self::timeout::Timeout;
|
||||
pub use self::tokenizer::Tokenizer;
|
||||
|
|
|
@ -3,10 +3,12 @@ use crate::dbs::{Options, Transaction};
|
|||
use crate::doc::CursorDoc;
|
||||
use crate::err::Error;
|
||||
use crate::iam::{Action, ResourceKind};
|
||||
use crate::sql::statements::DefineTableStatement;
|
||||
use crate::sql::Part;
|
||||
use crate::sql::{
|
||||
fmt::is_pretty, fmt::pretty_indent, Base, Ident, Idiom, Kind, Permissions, Strand, Value,
|
||||
};
|
||||
use crate::sql::{Relation, TableType};
|
||||
use derive::Store;
|
||||
use revision::revisioned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -51,20 +53,30 @@ impl DefineFieldStatement {
|
|||
if self.if_not_exists && run.get_tb_field(opt.ns(), opt.db(), &self.what, &fd).await.is_ok()
|
||||
{
|
||||
return Err(Error::FdAlreadyExists {
|
||||
value: self.name.to_string(),
|
||||
value: fd,
|
||||
});
|
||||
}
|
||||
// Process the statement
|
||||
let key = crate::key::table::fd::new(opt.ns(), opt.db(), &self.what, &fd);
|
||||
run.add_ns(opt.ns(), opt.strict).await?;
|
||||
run.add_db(opt.ns(), opt.db(), opt.strict).await?;
|
||||
run.add_tb(opt.ns(), opt.db(), &self.what, opt.strict).await?;
|
||||
|
||||
let tb = run.add_tb(opt.ns(), opt.db(), &self.what, opt.strict).await?;
|
||||
let key = crate::key::table::fd::new(opt.ns(), opt.db(), &self.what, &fd);
|
||||
run.set(
|
||||
key,
|
||||
DefineFieldStatement {
|
||||
if_not_exists: false,
|
||||
..self.clone()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
// find existing field definitions.
|
||||
let fields = run.all_tb_fields(opt.ns(), opt.db(), &self.what).await.ok();
|
||||
|
||||
// Process possible recursive_definitions.
|
||||
if let Some(mut cur_kind) = self.kind.as_ref().and_then(|x| x.inner_kind()) {
|
||||
let mut name = self.name.clone();
|
||||
// find existing field definitions.
|
||||
let fields = run.all_tb_fields(opt.ns(), opt.db(), &self.what).await.ok();
|
||||
loop {
|
||||
let new_kind = cur_kind.inner_kind();
|
||||
name.0.push(Part::All);
|
||||
|
@ -103,14 +115,48 @@ impl DefineFieldStatement {
|
|||
}
|
||||
}
|
||||
|
||||
run.set(
|
||||
key,
|
||||
DefineFieldStatement {
|
||||
if_not_exists: false,
|
||||
..self.clone()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let new_tb = match (fd.as_str(), tb.kind.clone(), self.kind.clone()) {
|
||||
("in", TableType::Relation(rel), Some(dk)) => {
|
||||
if !matches!(dk, Kind::Record(_)) {
|
||||
return Err(Error::Thrown("in field on a relation must be a record".into()));
|
||||
};
|
||||
if rel.from.as_ref() != Some(&dk) {
|
||||
Some(DefineTableStatement {
|
||||
kind: TableType::Relation(Relation {
|
||||
from: Some(dk),
|
||||
..rel
|
||||
}),
|
||||
..tb
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
("out", TableType::Relation(rel), Some(dk)) => {
|
||||
if !matches!(dk, Kind::Record(_)) {
|
||||
return Err(Error::Thrown("out field on a relation must be a record".into()));
|
||||
};
|
||||
if rel.to.as_ref() != Some(&dk) {
|
||||
Some(DefineTableStatement {
|
||||
kind: TableType::Relation(Relation {
|
||||
to: Some(dk),
|
||||
..rel
|
||||
}),
|
||||
..tb
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(tb) = new_tb {
|
||||
let key = crate::key::database::tb::new(opt.ns(), opt.db(), &self.what);
|
||||
run.set(key, &tb).await?;
|
||||
let key = crate::key::table::ft::prefix(opt.ns(), opt.db(), &self.what);
|
||||
run.clr(key).await?;
|
||||
}
|
||||
|
||||
// Clear the cache
|
||||
let key = crate::key::table::fd::prefix(opt.ns(), opt.db(), &self.what);
|
||||
run.clr(key).await?;
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
use std::fmt::{self, Display, Write};
|
||||
|
||||
use derive::Store;
|
||||
use revision::revisioned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ctx::Context;
|
||||
use crate::dbs::{Force, Options, Transaction};
|
||||
use crate::doc::CursorDoc;
|
||||
|
@ -17,9 +11,17 @@ use crate::sql::{
|
|||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::sql::{Idiom, Kind, Part, Table, TableType};
|
||||
use derive::Store;
|
||||
use revision::revisioned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{self, Display, Write};
|
||||
|
||||
use super::DefineFieldStatement;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
#[revisioned(revision = 2)]
|
||||
#[revisioned(revision = 3)]
|
||||
pub struct DefineTableStatement {
|
||||
pub id: Option<u32>,
|
||||
pub name: Ident,
|
||||
|
@ -31,6 +33,8 @@ pub struct DefineTableStatement {
|
|||
pub comment: Option<Strand>,
|
||||
#[revision(start = 2)]
|
||||
pub if_not_exists: bool,
|
||||
#[revision(start = 3)]
|
||||
pub kind: TableType,
|
||||
}
|
||||
|
||||
impl DefineTableStatement {
|
||||
|
@ -69,6 +73,36 @@ impl DefineTableStatement {
|
|||
..self.clone()
|
||||
}
|
||||
};
|
||||
if let TableType::Relation(rel) = &self.kind {
|
||||
let tb: &str = &self.name;
|
||||
let in_kind = rel.from.clone().unwrap_or(Kind::Record(vec![]));
|
||||
let out_kind = rel.to.clone().unwrap_or(Kind::Record(vec![]));
|
||||
let in_key = crate::key::table::fd::new(opt.ns(), opt.db(), tb, "in");
|
||||
let out_key = crate::key::table::fd::new(opt.ns(), opt.db(), tb, "out");
|
||||
run.set(
|
||||
in_key,
|
||||
DefineFieldStatement {
|
||||
name: Idiom(vec![Part::from("in")]),
|
||||
what: tb.into(),
|
||||
kind: Some(in_kind),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
run.set(
|
||||
out_key,
|
||||
DefineFieldStatement {
|
||||
name: Idiom(vec![Part::from("out")]),
|
||||
what: tb.into(),
|
||||
kind: Some(out_kind),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let tb_key = crate::key::table::fd::prefix(opt.ns(), opt.db(), &self.name);
|
||||
run.clr(tb_key).await?;
|
||||
run.set(key, &dt).await?;
|
||||
// Check if table is a view
|
||||
if let Some(view) = &self.view {
|
||||
|
@ -100,11 +134,30 @@ impl DefineTableStatement {
|
|||
} else if dt.changefeed.is_some() {
|
||||
run.record_table_change(opt.ns(), opt.db(), self.name.0.as_str(), &dt);
|
||||
}
|
||||
|
||||
// Ok all good
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl DefineTableStatement {
|
||||
pub fn is_relation(&self) -> bool {
|
||||
matches!(self.kind, TableType::Relation(_))
|
||||
}
|
||||
|
||||
pub fn allows_relation(&self) -> bool {
|
||||
matches!(self.kind, TableType::Relation(_) | TableType::Any)
|
||||
}
|
||||
|
||||
pub fn allows_normal(&self) -> bool {
|
||||
matches!(self.kind, TableType::Normal | TableType::Any)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tables_from_kind(tables: &[Table]) -> String {
|
||||
tables.iter().map(|t| t.0.as_str()).collect::<Vec<_>>().join(" | ")
|
||||
}
|
||||
|
||||
impl Display for DefineTableStatement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "DEFINE TABLE")?;
|
||||
|
@ -112,6 +165,24 @@ impl Display for DefineTableStatement {
|
|||
write!(f, " IF NOT EXISTS")?
|
||||
}
|
||||
write!(f, " {}", self.name)?;
|
||||
write!(f, " TYPE")?;
|
||||
match &self.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 {}", get_tables_from_kind(kind))?;
|
||||
}
|
||||
if let Some(Kind::Record(kind)) = &rel.to {
|
||||
write!(f, " OUT {}", get_tables_from_kind(kind))?;
|
||||
}
|
||||
}
|
||||
TableType::Any => {
|
||||
f.write_str(" ANY")?;
|
||||
}
|
||||
}
|
||||
if self.drop {
|
||||
f.write_str(" DROP")?;
|
||||
}
|
||||
|
|
50
core/src/sql/table_type.rs
Normal file
50
core/src/sql/table_type.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use revision::revisioned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::{Kind, Table};
|
||||
|
||||
/// The type of records stored by a table
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Hash, Clone, Eq, PartialEq, PartialOrd)]
|
||||
#[revisioned(revision = 1)]
|
||||
pub enum TableType {
|
||||
#[default]
|
||||
Any,
|
||||
Normal,
|
||||
Relation(Relation),
|
||||
}
|
||||
|
||||
impl Display for TableType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
TableType::Normal => {
|
||||
f.write_str(" NORMAL")?;
|
||||
}
|
||||
TableType::Relation(rel) => {
|
||||
f.write_str(" RELATION")?;
|
||||
if let Some(Kind::Record(kind)) = &rel.from {
|
||||
write!(f, " IN {}", get_tables_from_kind(kind))?;
|
||||
}
|
||||
if let Some(Kind::Record(kind)) = &rel.to {
|
||||
write!(f, " OUT {}", get_tables_from_kind(kind))?;
|
||||
}
|
||||
}
|
||||
TableType::Any => {
|
||||
f.write_str(" ANY")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tables_from_kind(tables: &[Table]) -> String {
|
||||
tables.iter().map(|t| t.0.as_str()).collect::<Vec<_>>().join(" | ")
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Hash, Clone, Eq, PartialEq, PartialOrd)]
|
||||
#[revisioned(revision = 1)]
|
||||
pub struct Relation {
|
||||
pub from: Option<Kind>,
|
||||
pub to: Option<Kind>,
|
||||
}
|
|
@ -40,6 +40,7 @@ mod permission;
|
|||
mod permissions;
|
||||
mod primitive;
|
||||
mod range;
|
||||
mod relation;
|
||||
mod scoring;
|
||||
mod split;
|
||||
mod start;
|
||||
|
@ -48,6 +49,7 @@ mod strand;
|
|||
mod string;
|
||||
mod subquery;
|
||||
mod table;
|
||||
mod table_type;
|
||||
mod thing;
|
||||
mod timeout;
|
||||
mod tokenizer;
|
||||
|
|
70
core/src/sql/value/serde/ser/relation/mod.rs
Normal file
70
core/src/sql/value/serde/ser/relation/mod.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use crate::err::Error;
|
||||
use crate::sql::value::serde::ser;
|
||||
use crate::sql::Kind;
|
||||
use crate::sql::Relation;
|
||||
use ser::Serializer as _;
|
||||
use serde::ser::Error as _;
|
||||
use serde::ser::Impossible;
|
||||
use serde::ser::Serialize;
|
||||
|
||||
pub struct Serializer;
|
||||
|
||||
impl ser::Serializer for Serializer {
|
||||
type Ok = Relation;
|
||||
type Error = Error;
|
||||
|
||||
type SerializeSeq = Impossible<Relation, Error>;
|
||||
type SerializeTuple = Impossible<Relation, Error>;
|
||||
type SerializeTupleStruct = Impossible<Relation, Error>;
|
||||
type SerializeTupleVariant = Impossible<Relation, Error>;
|
||||
type SerializeMap = Impossible<Relation, Error>;
|
||||
type SerializeStruct = SerializeRelation;
|
||||
type SerializeStructVariant = Impossible<Relation, Error>;
|
||||
|
||||
const EXPECTED: &'static str = "a struct `Relation`";
|
||||
|
||||
#[inline]
|
||||
fn serialize_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStruct, Error> {
|
||||
Ok(SerializeRelation::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SerializeRelation {
|
||||
from: Option<Kind>,
|
||||
to: Option<Kind>,
|
||||
}
|
||||
|
||||
impl serde::ser::SerializeStruct for SerializeRelation {
|
||||
type Ok = Relation;
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
match key {
|
||||
"from" => {
|
||||
self.from = value.serialize(ser::kind::opt::Serializer.wrap())?;
|
||||
}
|
||||
"to" => {
|
||||
self.to = value.serialize(ser::kind::opt::Serializer.wrap())?;
|
||||
}
|
||||
key => {
|
||||
return Err(Error::custom(format!("unexpected field `Relation::{key}`")));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Error> {
|
||||
Ok(Relation {
|
||||
from: self.from,
|
||||
to: self.to,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ use crate::sql::value::serde::ser;
|
|||
use crate::sql::Ident;
|
||||
use crate::sql::Permissions;
|
||||
use crate::sql::Strand;
|
||||
use crate::sql::TableType;
|
||||
use crate::sql::View;
|
||||
use ser::Serializer as _;
|
||||
use serde::ser::Error as _;
|
||||
|
@ -48,6 +49,7 @@ pub struct SerializeDefineTableStatement {
|
|||
changefeed: Option<ChangeFeed>,
|
||||
comment: Option<Strand>,
|
||||
if_not_exists: bool,
|
||||
kind: TableType,
|
||||
}
|
||||
|
||||
impl serde::ser::SerializeStruct for SerializeDefineTableStatement {
|
||||
|
@ -83,6 +85,9 @@ impl serde::ser::SerializeStruct for SerializeDefineTableStatement {
|
|||
"comment" => {
|
||||
self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?;
|
||||
}
|
||||
"kind" => {
|
||||
self.kind = value.serialize(ser::table_type::Serializer.wrap())?;
|
||||
}
|
||||
"if_not_exists" => {
|
||||
self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())?
|
||||
}
|
||||
|
@ -105,6 +110,7 @@ impl serde::ser::SerializeStruct for SerializeDefineTableStatement {
|
|||
permissions: self.permissions,
|
||||
changefeed: self.changefeed,
|
||||
comment: self.comment,
|
||||
kind: self.kind,
|
||||
if_not_exists: self.if_not_exists,
|
||||
})
|
||||
}
|
||||
|
|
56
core/src/sql/value/serde/ser/table_type/mod.rs
Normal file
56
core/src/sql/value/serde/ser/table_type/mod.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use crate::err::Error;
|
||||
use crate::sql::value::serde::ser;
|
||||
use crate::sql::TableType;
|
||||
use serde::ser::Error as _;
|
||||
use serde::ser::Impossible;
|
||||
use serde::ser::Serialize;
|
||||
|
||||
pub struct Serializer;
|
||||
|
||||
impl ser::Serializer for Serializer {
|
||||
type Ok = TableType;
|
||||
type Error = Error;
|
||||
|
||||
type SerializeSeq = Impossible<TableType, Error>;
|
||||
type SerializeTuple = Impossible<TableType, Error>;
|
||||
type SerializeTupleStruct = Impossible<TableType, Error>;
|
||||
type SerializeTupleVariant = Impossible<TableType, Error>;
|
||||
type SerializeMap = Impossible<TableType, Error>;
|
||||
type SerializeStruct = Impossible<TableType, Error>;
|
||||
type SerializeStructVariant = Impossible<TableType, Error>;
|
||||
|
||||
const EXPECTED: &'static str = "a `TableType`";
|
||||
|
||||
fn serialize_newtype_variant<T>(
|
||||
self,
|
||||
name: &'static str,
|
||||
_variant_index: u32,
|
||||
variant: &'static str,
|
||||
value: &T,
|
||||
) -> Result<Self::Ok, Self::Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
match variant {
|
||||
"Relation" => {
|
||||
Ok(TableType::Relation(value.serialize(ser::relation::Serializer.wrap())?))
|
||||
}
|
||||
variant => {
|
||||
Err(Error::custom(format!("unexpected newtype variant `{name}::{variant}`")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_unit_variant(
|
||||
self,
|
||||
name: &'static str,
|
||||
_variant_index: u32,
|
||||
variant: &'static str,
|
||||
) -> Result<Self::Ok, Error> {
|
||||
match variant {
|
||||
"Normal" => Ok(TableType::Normal),
|
||||
"Any" => Ok(TableType::Any),
|
||||
variant => Err(Error::custom(format!("unknown variant `{name}::{variant}`"))),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,8 +9,14 @@ use super::super::super::{
|
|||
use crate::sql::{
|
||||
statements::DefineTableStatement, ChangeFeed, Permission, Permissions, Strand, View,
|
||||
};
|
||||
use crate::{
|
||||
sql::{Kind, Relation, TableType},
|
||||
syn::v1::common::verbar,
|
||||
syn::v1::ParseError,
|
||||
};
|
||||
|
||||
use nom::{branch::alt, bytes::complete::tag_no_case, combinator::cut, multi::many0};
|
||||
use nom::{combinator::opt, sequence::tuple};
|
||||
use nom::{combinator::opt, multi::separated_list1, sequence::tuple, Err};
|
||||
|
||||
pub fn table(i: &str) -> IResult<&str, DefineTableStatement> {
|
||||
let (i, _) = tag_no_case("TABLE")(i)?;
|
||||
|
@ -23,7 +29,7 @@ pub fn table(i: &str) -> IResult<&str, DefineTableStatement> {
|
|||
let (i, name) = cut(ident)(i)?;
|
||||
let (i, opts) = many0(table_opts)(i)?;
|
||||
let (i, _) = expected(
|
||||
"DROP, SCHEMALESS, SCHEMAFUL(L), VIEW, CHANGEFEED, PERMISSIONS, or COMMENT",
|
||||
"TYPE, RELATION, DROP, SCHEMALESS, SCHEMAFUL(L), VIEW, CHANGEFEED, PERMISSIONS, or COMMENT",
|
||||
ending::query,
|
||||
)(i)?;
|
||||
// Create the base statement
|
||||
|
@ -57,6 +63,9 @@ pub fn table(i: &str) -> IResult<&str, DefineTableStatement> {
|
|||
DefineTableOption::Permissions(v) => {
|
||||
res.permissions = v;
|
||||
}
|
||||
DefineTableOption::TableType(t) => {
|
||||
res.kind = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return the statement
|
||||
|
@ -72,6 +81,42 @@ enum DefineTableOption {
|
|||
Comment(Strand),
|
||||
Permissions(Permissions),
|
||||
ChangeFeed(ChangeFeed),
|
||||
TableType(TableType),
|
||||
}
|
||||
|
||||
enum RelationDir {
|
||||
From(Kind),
|
||||
To(Kind),
|
||||
}
|
||||
|
||||
impl Relation {
|
||||
fn merge<'a>(&mut self, i: &'a str, other: RelationDir) -> IResult<&'a str, ()> {
|
||||
//TODO: error if both self and other are some
|
||||
match other {
|
||||
RelationDir::From(f) => {
|
||||
if self.from.is_some() {
|
||||
Err(Err::Failure(ParseError::Expected {
|
||||
tried: i,
|
||||
expected: "only one IN clause",
|
||||
}))
|
||||
} else {
|
||||
self.from = Some(f);
|
||||
Ok((i, ()))
|
||||
}
|
||||
}
|
||||
RelationDir::To(t) => {
|
||||
if self.to.is_some() {
|
||||
Err(Err::Failure(ParseError::Expected {
|
||||
tried: i,
|
||||
expected: "only one OUT clause",
|
||||
}))
|
||||
} else {
|
||||
self.to = Some(t);
|
||||
Ok((i, ()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn table_opts(i: &str) -> IResult<&str, DefineTableOption> {
|
||||
|
@ -83,6 +128,8 @@ fn table_opts(i: &str) -> IResult<&str, DefineTableOption> {
|
|||
table_schemafull,
|
||||
table_permissions,
|
||||
table_changefeed,
|
||||
table_type,
|
||||
table_relation,
|
||||
))(i)
|
||||
}
|
||||
|
||||
|
@ -130,6 +177,56 @@ fn table_permissions(i: &str) -> IResult<&str, DefineTableOption> {
|
|||
Ok((i, DefineTableOption::Permissions(v)))
|
||||
}
|
||||
|
||||
fn table_type(i: &str) -> IResult<&str, DefineTableOption> {
|
||||
let (i, _) = shouldbespace(i)?;
|
||||
let (i, _) = tag_no_case("TYPE")(i)?;
|
||||
alt((table_normal, table_any, table_relation))(i)
|
||||
}
|
||||
|
||||
fn table_normal(i: &str) -> IResult<&str, DefineTableOption> {
|
||||
let (i, _) = shouldbespace(i)?;
|
||||
let (i, _) = tag_no_case("NORMAL")(i)?;
|
||||
Ok((i, DefineTableOption::TableType(TableType::Normal)))
|
||||
}
|
||||
|
||||
fn table_any(i: &str) -> IResult<&str, DefineTableOption> {
|
||||
let (i, _) = shouldbespace(i)?;
|
||||
let (i, _) = tag_no_case("ANY")(i)?;
|
||||
Ok((i, DefineTableOption::TableType(TableType::Any)))
|
||||
}
|
||||
|
||||
fn table_relation(i: &str) -> IResult<&str, DefineTableOption> {
|
||||
let (i, _) = shouldbespace(i)?;
|
||||
let (i, _) = tag_no_case("RELATION")(i)?;
|
||||
|
||||
let (i, dirs) = many0(alt((relation_from, relation_to)))(i)?;
|
||||
|
||||
let mut relation: Relation = Default::default();
|
||||
|
||||
for dir in dirs {
|
||||
relation.merge(i, dir)?;
|
||||
}
|
||||
|
||||
Ok((i, DefineTableOption::TableType(TableType::Relation(relation))))
|
||||
}
|
||||
|
||||
fn relation_from(i: &str) -> IResult<&str, RelationDir> {
|
||||
let (i, _) = shouldbespace(i)?;
|
||||
let (i, _) = alt((tag_no_case("FROM"), tag_no_case("IN")))(i)?;
|
||||
let (i, _) = shouldbespace(i)?;
|
||||
let (i, idents) = separated_list1(verbar, ident)(i)?;
|
||||
Ok((i, RelationDir::From(Kind::Record(idents.into_iter().map(Into::into).collect()))))
|
||||
}
|
||||
|
||||
fn relation_to(i: &str) -> IResult<&str, RelationDir> {
|
||||
let (i, _) = shouldbespace(i)?;
|
||||
let (i, _) = alt((tag_no_case("TO"), tag_no_case("OUT")))(i)?;
|
||||
let (i, _) = shouldbespace(i)?;
|
||||
let (i, idents) = separated_list1(verbar, ident)(i)?;
|
||||
|
||||
Ok((i, RelationDir::To(Kind::Record(idents.into_iter().map(Into::into).collect()))))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
@ -137,7 +234,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn define_table_with_changefeed() {
|
||||
let sql = "TABLE mytable SCHEMALESS CHANGEFEED 1h PERMISSIONS NONE";
|
||||
let sql = "TABLE mytable TYPE ANY SCHEMALESS CHANGEFEED 1h PERMISSIONS NONE";
|
||||
let res = table(sql);
|
||||
let out = res.unwrap().1;
|
||||
assert_eq!(format!("DEFINE {sql}"), format!("{}", out));
|
||||
|
|
|
@ -161,6 +161,7 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map
|
|||
UniCase::ascii("PUNCT") => TokenKind::Keyword(Keyword::Punct),
|
||||
UniCase::ascii("READONLY") => TokenKind::Keyword(Keyword::Readonly),
|
||||
UniCase::ascii("RELATE") => TokenKind::Keyword(Keyword::Relate),
|
||||
UniCase::ascii("RELATION") => TokenKind::Keyword(Keyword::Relation),
|
||||
UniCase::ascii("REMOVE") => TokenKind::Keyword(Keyword::Remove),
|
||||
UniCase::ascii("REPLACE") => TokenKind::Keyword(Keyword::Replace),
|
||||
UniCase::ascii("RETURN") => TokenKind::Keyword(Keyword::Return),
|
||||
|
@ -191,6 +192,7 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map
|
|||
UniCase::ascii("THEN") => TokenKind::Keyword(Keyword::Then),
|
||||
UniCase::ascii("THROW") => TokenKind::Keyword(Keyword::Throw),
|
||||
UniCase::ascii("TIMEOUT") => TokenKind::Keyword(Keyword::Timeout),
|
||||
UniCase::ascii("TO") => TokenKind::Keyword(Keyword::To),
|
||||
UniCase::ascii("TOKENIZERS") => TokenKind::Keyword(Keyword::Tokenizers),
|
||||
UniCase::ascii("TOKEN") => TokenKind::Keyword(Keyword::Token),
|
||||
UniCase::ascii("TRANSACTION") => TokenKind::Keyword(Keyword::Transaction),
|
||||
|
@ -227,6 +229,8 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map
|
|||
UniCase::ascii("CONTAINSNOT") => TokenKind::Keyword(Keyword::ContainsNot),
|
||||
UniCase::ascii("CONTAINS") => TokenKind::Keyword(Keyword::Contains),
|
||||
UniCase::ascii("IN") => TokenKind::Keyword(Keyword::In),
|
||||
UniCase::ascii("OUT") => TokenKind::Keyword(Keyword::Out),
|
||||
UniCase::ascii("NORMAL") => TokenKind::Keyword(Keyword::Normal),
|
||||
|
||||
UniCase::ascii("ANY") => TokenKind::Keyword(Keyword::Any),
|
||||
UniCase::ascii("ARRAY") => TokenKind::Keyword(Keyword::Array),
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::sql::{table_type, TableType};
|
||||
use crate::{
|
||||
sql::{
|
||||
filter::Filter,
|
||||
|
@ -9,7 +10,7 @@ use crate::{
|
|||
DefineTableStatement, DefineTokenStatement, DefineUserStatement,
|
||||
},
|
||||
tokenizer::Tokenizer,
|
||||
Ident, Idioms, Index, Param, Permissions, Scoring, Strand, Values,
|
||||
Ident, Idioms, Index, Kind, Param, Permissions, Scoring, Strand, Values,
|
||||
},
|
||||
syn::v2::{
|
||||
parser::{
|
||||
|
@ -353,6 +354,28 @@ impl Parser<'_> {
|
|||
self.pop_peek();
|
||||
res.drop = true;
|
||||
}
|
||||
t!("RELATION") => {
|
||||
self.pop_peek();
|
||||
res.kind = TableType::Relation(self.parse_relation_schema()?);
|
||||
}
|
||||
t!("TYPE") => {
|
||||
self.pop_peek();
|
||||
match self.peek_kind() {
|
||||
t!("NORMAL") => {
|
||||
self.pop_peek();
|
||||
res.kind = TableType::Normal;
|
||||
}
|
||||
t!("RELATION") => {
|
||||
self.pop_peek();
|
||||
res.kind = TableType::Relation(self.parse_relation_schema()?);
|
||||
}
|
||||
t!("ANY") => {
|
||||
self.pop_peek();
|
||||
res.kind = TableType::Any;
|
||||
}
|
||||
x => unexpected!(self, x, "`NORMAL`, `RELATION`, or `ANY`"),
|
||||
}
|
||||
}
|
||||
t!("SCHEMALESS") => {
|
||||
self.pop_peek();
|
||||
res.full = false;
|
||||
|
@ -765,4 +788,35 @@ impl Parser<'_> {
|
|||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn parse_relation_schema(&mut self) -> ParseResult<table_type::Relation> {
|
||||
let mut res = table_type::Relation {
|
||||
from: None,
|
||||
to: None,
|
||||
};
|
||||
loop {
|
||||
match self.peek_kind() {
|
||||
t!("FROM") => {
|
||||
self.pop_peek();
|
||||
let from = self.parse_tables()?;
|
||||
res.from = Some(from);
|
||||
}
|
||||
t!("TO") => {
|
||||
self.pop_peek();
|
||||
let to = self.parse_tables()?;
|
||||
res.to = Some(to);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn parse_tables(&mut self) -> ParseResult<Kind> {
|
||||
let mut names = vec![self.next_token_value()?];
|
||||
while self.eat(t!("|")) {
|
||||
names.push(self.next_token_value()?);
|
||||
}
|
||||
Ok(Kind::Record(names))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ use crate::{
|
|||
Expression, Fetch, Fetchs, Field, Fields, Future, Graph, Group, Groups, Id, Ident, Idiom,
|
||||
Idioms, Index, Kind, Limit, Number, Object, Operator, Order, Orders, Output, Param, Part,
|
||||
Permission, Permissions, Scoring, Split, Splits, Start, Statement, Strand, Subquery, Table,
|
||||
Tables, Thing, Timeout, Uuid, Value, Values, Version, With,
|
||||
TableType, Tables, Thing, Timeout, Uuid, Value, Values, Version, With,
|
||||
},
|
||||
syn::v2::parser::mac::test_parse,
|
||||
};
|
||||
|
@ -327,6 +327,7 @@ fn parse_define_table() {
|
|||
}),
|
||||
comment: None,
|
||||
if_not_exists: false,
|
||||
kind: TableType::Any,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ use crate::{
|
|||
Expression, Fetch, Fetchs, Field, Fields, Future, Graph, Group, Groups, Id, Ident, Idiom,
|
||||
Idioms, Index, Kind, Limit, Number, Object, Operator, Order, Orders, Output, Param, Part,
|
||||
Permission, Permissions, Scoring, Split, Splits, Start, Statement, Strand, Subquery, Table,
|
||||
Tables, Thing, Timeout, Uuid, Value, Values, Version, With,
|
||||
TableType, Tables, Thing, Timeout, Uuid, Value, Values, Version, With,
|
||||
},
|
||||
syn::v2::parser::{Parser, PartialResult},
|
||||
};
|
||||
|
@ -247,6 +247,7 @@ fn statements() -> Vec<Statement> {
|
|||
}),
|
||||
comment: None,
|
||||
if_not_exists: false,
|
||||
kind: TableType::Any,
|
||||
})),
|
||||
Statement::Define(DefineStatement::Event(DefineEventStatement {
|
||||
name: Ident("event".to_owned()),
|
||||
|
|
|
@ -123,6 +123,7 @@ keyword! {
|
|||
Punct => "PUNCT",
|
||||
Readonly => "READONLY",
|
||||
Relate => "RELATE",
|
||||
Relation => "RELATION",
|
||||
Remove => "REMOVE",
|
||||
Replace => "REPLACE",
|
||||
Return => "RETURN",
|
||||
|
@ -151,6 +152,7 @@ keyword! {
|
|||
Timeout => "TIMEOUT",
|
||||
Tokenizers => "TOKENIZERS",
|
||||
Token => "TOKEN",
|
||||
To => "TO",
|
||||
Transaction => "TRANSACTION",
|
||||
True => "true",
|
||||
Type => "TYPE",
|
||||
|
@ -185,6 +187,8 @@ keyword! {
|
|||
ContainsNot => "CONTAINSNOT",
|
||||
Contains => "CONTAINS",
|
||||
In => "IN",
|
||||
Out => "OUT",
|
||||
Normal => "NORMAL",
|
||||
|
||||
Any => "ANY",
|
||||
Array => "ARRAY",
|
||||
|
|
|
@ -122,7 +122,7 @@ async fn define_statement_table_drop() -> Result<(), Error> {
|
|||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: { test: 'DEFINE TABLE test DROP SCHEMALESS PERMISSIONS NONE' },
|
||||
tables: { test: 'DEFINE TABLE test TYPE ANY DROP SCHEMALESS PERMISSIONS NONE' },
|
||||
users: {},
|
||||
}",
|
||||
);
|
||||
|
@ -154,7 +154,7 @@ async fn define_statement_table_schemaless() -> Result<(), Error> {
|
|||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: { test: 'DEFINE TABLE test SCHEMALESS PERMISSIONS NONE' },
|
||||
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMALESS PERMISSIONS NONE' },
|
||||
users: {},
|
||||
}",
|
||||
);
|
||||
|
@ -190,7 +190,7 @@ async fn define_statement_table_schemafull() -> Result<(), Error> {
|
|||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: { test: 'DEFINE TABLE test SCHEMAFULL PERMISSIONS NONE' },
|
||||
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE' },
|
||||
users: {},
|
||||
}",
|
||||
);
|
||||
|
@ -222,7 +222,7 @@ async fn define_statement_table_schemaful() -> Result<(), Error> {
|
|||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: { test: 'DEFINE TABLE test SCHEMAFULL PERMISSIONS NONE' },
|
||||
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE' },
|
||||
users: {},
|
||||
}",
|
||||
);
|
||||
|
@ -263,8 +263,8 @@ async fn define_statement_table_foreigntable() -> Result<(), Error> {
|
|||
params: {},
|
||||
scopes: {},
|
||||
tables: {
|
||||
test: 'DEFINE TABLE test SCHEMAFULL PERMISSIONS NONE',
|
||||
view: 'DEFINE TABLE view SCHEMALESS AS SELECT count() FROM test GROUP ALL PERMISSIONS NONE',
|
||||
test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE',
|
||||
view: 'DEFINE TABLE view TYPE ANY SCHEMALESS AS SELECT count() FROM test GROUP ALL PERMISSIONS NONE',
|
||||
},
|
||||
users: {},
|
||||
}",
|
||||
|
@ -276,7 +276,7 @@ async fn define_statement_table_foreigntable() -> Result<(), Error> {
|
|||
"{
|
||||
events: {},
|
||||
fields: {},
|
||||
tables: { view: 'DEFINE TABLE view SCHEMALESS AS SELECT count() FROM test GROUP ALL PERMISSIONS NONE' },
|
||||
tables: { view: 'DEFINE TABLE view TYPE ANY SCHEMALESS AS SELECT count() FROM test GROUP ALL PERMISSIONS NONE' },
|
||||
indexes: {},
|
||||
lives: {},
|
||||
}",
|
||||
|
@ -296,7 +296,7 @@ async fn define_statement_table_foreigntable() -> Result<(), Error> {
|
|||
params: {},
|
||||
scopes: {},
|
||||
tables: {
|
||||
test: 'DEFINE TABLE test SCHEMAFULL PERMISSIONS NONE',
|
||||
test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE',
|
||||
},
|
||||
users: {},
|
||||
}",
|
||||
|
@ -1948,7 +1948,7 @@ async fn permissions_checks_define_table() {
|
|||
|
||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||
let check_results = [
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { TB: 'DEFINE TABLE TB SCHEMALESS PERMISSIONS NONE' }, tokens: { }, users: { } }"],
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { TB: 'DEFINE TABLE TB TYPE ANY SCHEMALESS PERMISSIONS NONE' }, tokens: { }, users: { } }"],
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"]
|
||||
];
|
||||
|
||||
|
@ -2139,9 +2139,9 @@ async fn define_statement_table_permissions() -> Result<(), Error> {
|
|||
params: {},
|
||||
scopes: {},
|
||||
tables: {
|
||||
default: 'DEFINE TABLE default SCHEMALESS PERMISSIONS NONE',
|
||||
full: 'DEFINE TABLE full SCHEMALESS PERMISSIONS FULL',
|
||||
select_full: 'DEFINE TABLE select_full SCHEMALESS PERMISSIONS FOR select FULL, FOR create, update, delete NONE'
|
||||
default: 'DEFINE TABLE default TYPE ANY SCHEMALESS PERMISSIONS NONE',
|
||||
full: 'DEFINE TABLE full TYPE ANY SCHEMALESS PERMISSIONS FULL',
|
||||
select_full: 'DEFINE TABLE select_full TYPE ANY SCHEMALESS PERMISSIONS FOR select FULL, FOR create, update, delete NONE'
|
||||
},
|
||||
tokens: {},
|
||||
users: {}
|
||||
|
@ -2643,3 +2643,32 @@ async fn redefining_existing_user_with_if_not_exists_should_error() -> Result<()
|
|||
//
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(feature = "sql2")]
|
||||
async fn define_table_relation() -> Result<(), Error> {
|
||||
let sql = "
|
||||
DEFINE TABLE likes RELATION;
|
||||
CREATE person:raphael, person:tobie;
|
||||
RELATE person:raphael->likes->person:tobie;
|
||||
CREATE likes:1;
|
||||
";
|
||||
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(), 4);
|
||||
//
|
||||
let tmp = res.remove(0).result;
|
||||
assert!(tmp.is_ok());
|
||||
//
|
||||
let tmp = res.remove(0).result;
|
||||
assert!(tmp.is_ok());
|
||||
//
|
||||
let tmp = res.remove(0).result;
|
||||
assert!(tmp.is_ok());
|
||||
//
|
||||
let tmp = res.remove(0).result;
|
||||
assert!(tmp.is_err());
|
||||
//
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -789,7 +789,7 @@ async fn field_definition_edge_permissions() -> Result<(), Error> {
|
|||
DEFINE TABLE user SCHEMAFULL;
|
||||
DEFINE TABLE business SCHEMAFULL;
|
||||
DEFINE FIELD owner ON TABLE business TYPE record<user>;
|
||||
DEFINE TABLE contact SCHEMAFULL PERMISSIONS FOR create WHERE in.owner.id = $auth.id;
|
||||
DEFINE TABLE contact RELATION SCHEMAFULL PERMISSIONS FOR create WHERE in.owner.id = $auth.id;
|
||||
INSERT INTO user (id, name) VALUES (user:one, 'John'), (user:two, 'Lucy');
|
||||
INSERT INTO business (id, owner) VALUES (business:one, user:one), (business:two, user:two);
|
||||
";
|
||||
|
|
|
@ -1015,7 +1015,7 @@ async fn permissions_checks_remove_table() {
|
|||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||
let check_results = [
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { TB: 'DEFINE TABLE TB SCHEMALESS PERMISSIONS NONE' }, tokens: { }, users: { } }"],
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { TB: 'DEFINE TABLE TB TYPE ANY SCHEMALESS PERMISSIONS NONE' }, tokens: { }, users: { } }"],
|
||||
];
|
||||
|
||||
let test_cases = [
|
||||
|
|
|
@ -258,7 +258,7 @@ async fn loose_mode_all_ok() -> Result<(), Error> {
|
|||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: { test: 'DEFINE TABLE test SCHEMALESS PERMISSIONS NONE' },
|
||||
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMALESS PERMISSIONS NONE' },
|
||||
users: {},
|
||||
}",
|
||||
);
|
||||
|
|
|
@ -43,7 +43,7 @@ async fn define_foreign_table() -> Result<(), Error> {
|
|||
"{
|
||||
events: {},
|
||||
fields: {},
|
||||
tables: { person_by_age: 'DEFINE TABLE person_by_age SCHEMALESS AS SELECT count(), age, math::sum(age) AS total, math::mean(score) AS average FROM person GROUP BY age PERMISSIONS NONE' },
|
||||
tables: { person_by_age: 'DEFINE TABLE person_by_age TYPE ANY SCHEMALESS AS SELECT count(), age, math::sum(age) AS total, math::mean(score) AS average FROM person GROUP BY age PERMISSIONS NONE' },
|
||||
indexes: {},
|
||||
lives: {},
|
||||
}",
|
||||
|
|
|
@ -99,7 +99,7 @@ mod cli_integration {
|
|||
{
|
||||
let args = format!("export --conn http://{addr} {creds} --ns {ns} --db {db} -");
|
||||
let output = common::run(&args).output().expect("failed to run stdout export: {args}");
|
||||
assert!(output.contains("DEFINE TABLE thing SCHEMALESS PERMISSIONS NONE;"));
|
||||
assert!(output.contains("DEFINE TABLE thing TYPE ANY SCHEMALESS PERMISSIONS NONE;"));
|
||||
assert!(output.contains("UPDATE thing:one CONTENT { id: thing:one };"));
|
||||
}
|
||||
|
||||
|
@ -632,8 +632,10 @@ mod cli_integration {
|
|||
let args = format!(
|
||||
"sql --conn http://{addr} {creds} --ns {ns} --db {db} --multi --hide-welcome"
|
||||
);
|
||||
let output =
|
||||
common::run(&args).input("DEFINE TABLE thing CHANGEFEED 1s;\n").output().unwrap();
|
||||
let output = common::run(&args)
|
||||
.input("DEFINE TABLE thing TYPE ANY CHANGEFEED 1s;\n")
|
||||
.output()
|
||||
.unwrap();
|
||||
let output = remove_debug_info(output);
|
||||
assert_eq!(output, "[NONE]\n\n".to_owned(), "failed to send sql: {args}");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue