Enforced relations (#4579)
This commit is contained in:
parent
c5e8324653
commit
7c41aef3ba
8 changed files with 64 additions and 9 deletions
|
@ -9,6 +9,8 @@ use crate::sql::paths::IN;
|
||||||
use crate::sql::paths::OUT;
|
use crate::sql::paths::OUT;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
use crate::sql::Dir;
|
use crate::sql::Dir;
|
||||||
|
use crate::sql::Relation;
|
||||||
|
use crate::sql::TableType;
|
||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
pub async fn edges(
|
pub async fn edges(
|
||||||
|
@ -17,8 +19,10 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
_stm: &Statement<'_>,
|
_stm: &Statement<'_>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
// Get the table
|
||||||
|
let tb = self.tb(ctx, opt).await?;
|
||||||
// Check if the table is a view
|
// Check if the table is a view
|
||||||
if self.tb(ctx, opt).await?.drop {
|
if tb.drop {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// Get the transaction
|
// Get the transaction
|
||||||
|
@ -29,6 +33,28 @@ impl Document {
|
||||||
let rid = self.id.as_ref().unwrap();
|
let rid = self.id.as_ref().unwrap();
|
||||||
// Store the record edges
|
// Store the record edges
|
||||||
if let Workable::Relate(l, r, _) = &self.extras {
|
if let Workable::Relate(l, r, _) = &self.extras {
|
||||||
|
// For enforced relations, ensure that the edges exist
|
||||||
|
if matches!(
|
||||||
|
tb.kind,
|
||||||
|
TableType::Relation(Relation {
|
||||||
|
enforced: true,
|
||||||
|
..
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
let key = crate::key::thing::new(opt.ns()?, opt.db()?, &l.tb, &l.id);
|
||||||
|
if !txn.exists(key).await? {
|
||||||
|
return Err(Error::IdNotFound {
|
||||||
|
value: l.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = crate::key::thing::new(opt.ns()?, opt.db()?, &r.tb, &r.id);
|
||||||
|
if !txn.exists(key).await? {
|
||||||
|
return Err(Error::IdNotFound {
|
||||||
|
value: r.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
// Get temporary edge references
|
// Get temporary edge references
|
||||||
let (ref o, ref i) = (Dir::Out, Dir::In);
|
let (ref o, ref i) = (Dir::Out, Dir::In);
|
||||||
// Store the left pointer edge
|
// Store the left pointer edge
|
||||||
|
|
|
@ -20,7 +20,7 @@ impl Document {
|
||||||
return Err(Error::TableCheck {
|
return Err(Error::TableCheck {
|
||||||
thing: rid.to_string(),
|
thing: rid.to_string(),
|
||||||
relation: false,
|
relation: false,
|
||||||
target_type: tb.kind.clone(),
|
target_type: tb.kind.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ impl Document {
|
||||||
return Err(Error::TableCheck {
|
return Err(Error::TableCheck {
|
||||||
thing: rid.to_string(),
|
thing: rid.to_string(),
|
||||||
relation: false,
|
relation: false,
|
||||||
target_type: tb.kind.clone(),
|
target_type: tb.kind.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ impl Document {
|
||||||
return Err(Error::TableCheck {
|
return Err(Error::TableCheck {
|
||||||
thing: rid.to_string(),
|
thing: rid.to_string(),
|
||||||
relation: true,
|
relation: true,
|
||||||
target_type: tb.kind.clone(),
|
target_type: tb.kind.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ impl Document {
|
||||||
return Err(Error::TableCheck {
|
return Err(Error::TableCheck {
|
||||||
thing: rid.to_string(),
|
thing: rid.to_string(),
|
||||||
relation: true,
|
relation: true,
|
||||||
target_type: tb.kind.clone(),
|
target_type: tb.kind.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ impl Document {
|
||||||
return Err(Error::TableCheck {
|
return Err(Error::TableCheck {
|
||||||
thing: rid.to_string(),
|
thing: rid.to_string(),
|
||||||
relation: false,
|
relation: false,
|
||||||
target_type: tb.kind.clone(),
|
target_type: tb.kind.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ use crate::sql::idiom::Idiom;
|
||||||
use crate::sql::index::Distance;
|
use crate::sql::index::Distance;
|
||||||
use crate::sql::thing::Thing;
|
use crate::sql::thing::Thing;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
use crate::sql::TableType;
|
|
||||||
use crate::syn::error::RenderedError as RenderedParserError;
|
use crate::syn::error::RenderedError as RenderedParserError;
|
||||||
use crate::vs::Error as VersionstampError;
|
use crate::vs::Error as VersionstampError;
|
||||||
use base64::DecodeError as Base64Error;
|
use base64::DecodeError as Base64Error;
|
||||||
|
@ -575,7 +574,7 @@ pub enum Error {
|
||||||
TableCheck {
|
TableCheck {
|
||||||
thing: String,
|
thing: String,
|
||||||
relation: bool,
|
relation: bool,
|
||||||
target_type: TableType,
|
target_type: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The specified field did not conform to the field type check
|
/// The specified field did not conform to the field type check
|
||||||
|
|
|
@ -60,11 +60,13 @@ impl InfoStructure for TableType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 2)]
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Hash, Clone, Eq, PartialEq, PartialOrd)]
|
#[derive(Debug, Default, Serialize, Deserialize, Hash, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Relation {
|
pub struct Relation {
|
||||||
pub from: Option<Kind>,
|
pub from: Option<Kind>,
|
||||||
pub to: Option<Kind>,
|
pub to: Option<Kind>,
|
||||||
|
#[revision(start = 2)]
|
||||||
|
pub enforced: bool,
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,7 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map
|
||||||
UniCase::ascii("EVENT") => TokenKind::Keyword(Keyword::Event),
|
UniCase::ascii("EVENT") => TokenKind::Keyword(Keyword::Event),
|
||||||
UniCase::ascii("ELSE") => TokenKind::Keyword(Keyword::Else),
|
UniCase::ascii("ELSE") => TokenKind::Keyword(Keyword::Else),
|
||||||
UniCase::ascii("END") => TokenKind::Keyword(Keyword::End),
|
UniCase::ascii("END") => TokenKind::Keyword(Keyword::End),
|
||||||
|
UniCase::ascii("ENFORCED") => TokenKind::Keyword(Keyword::Enforced),
|
||||||
UniCase::ascii("EXISTS") => TokenKind::Keyword(Keyword::Exists),
|
UniCase::ascii("EXISTS") => TokenKind::Keyword(Keyword::Exists),
|
||||||
UniCase::ascii("EXPLAIN") => TokenKind::Keyword(Keyword::Explain),
|
UniCase::ascii("EXPLAIN") => TokenKind::Keyword(Keyword::Explain),
|
||||||
UniCase::ascii("EXTEND_CANDIDATES") => TokenKind::Keyword(Keyword::ExtendCandidates),
|
UniCase::ascii("EXTEND_CANDIDATES") => TokenKind::Keyword(Keyword::ExtendCandidates),
|
||||||
|
|
|
@ -1208,6 +1208,7 @@ impl Parser<'_> {
|
||||||
let mut res = table_type::Relation {
|
let mut res = table_type::Relation {
|
||||||
from: None,
|
from: None,
|
||||||
to: None,
|
to: None,
|
||||||
|
enforced: false,
|
||||||
};
|
};
|
||||||
loop {
|
loop {
|
||||||
match self.peek_kind() {
|
match self.peek_kind() {
|
||||||
|
@ -1224,6 +1225,9 @@ impl Parser<'_> {
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.eat(t!("ENFORCED")) {
|
||||||
|
res.enforced = true;
|
||||||
|
}
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,7 @@ keyword! {
|
||||||
Event => "EVENT",
|
Event => "EVENT",
|
||||||
Else => "ELSE",
|
Else => "ELSE",
|
||||||
End => "END",
|
End => "END",
|
||||||
|
Enforced => "ENFORCED",
|
||||||
Exists => "EXISTS",
|
Exists => "EXISTS",
|
||||||
Explain => "EXPLAIN",
|
Explain => "EXPLAIN",
|
||||||
ExtendCandidates => "EXTEND_CANDIDATES",
|
ExtendCandidates => "EXTEND_CANDIDATES",
|
||||||
|
|
|
@ -257,3 +257,25 @@ async fn schemafull_relate() -> Result<(), Error> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn relate_enforced() -> Result<(), Error> {
|
||||||
|
let sql = "
|
||||||
|
DEFINE TABLE edge TYPE RELATION ENFORCED;
|
||||||
|
RELATE a:1->edge:1->a:2;
|
||||||
|
CREATE a:1, a:2;
|
||||||
|
RELATE a:1->edge:1->a:2;
|
||||||
|
";
|
||||||
|
|
||||||
|
let mut t = Test::new(sql).await?;
|
||||||
|
//
|
||||||
|
t.skip_ok(1)?;
|
||||||
|
//
|
||||||
|
t.expect_error_func(|e| matches!(e, Error::IdNotFound { .. }))?;
|
||||||
|
//
|
||||||
|
t.expect_val("[{ id: a:1 }, { id: a:2 }]")?;
|
||||||
|
//
|
||||||
|
t.expect_val("[{ id: edge:1, in: a:1, out: a:2 }]")?;
|
||||||
|
//
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue