Enforced relations (#4579)

This commit is contained in:
Micha de Vries 2024-08-22 16:25:20 +01:00 committed by GitHub
parent c5e8324653
commit 7c41aef3ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 64 additions and 9 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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