Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error on undefined field #3872

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 31 additions & 2 deletions core/src/doc/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::err::Error;
use crate::iam::Action;
use crate::sql::permission::Permission;
use crate::sql::value::Value;
use crate::sql::Part;
use reblessive::tree::Stk;

impl<'a> Document<'a> {
Expand All @@ -15,7 +16,7 @@ impl<'a> Document<'a> {
ctx: &Context<'_>,
opt: &Options,
txn: &Transaction,
_stm: &Statement<'_>,
stm: &Statement<'_>,
) -> Result<(), Error> {
// Check import
if opt.import {
Expand All @@ -25,8 +26,36 @@ impl<'a> Document<'a> {
let rid = self.id.as_ref().unwrap();
// Get the user applied input
let inp = self.initial.doc.changed(self.current.doc.as_ref());
// Get field definitions
let fds = self.fd(opt, txn).await?;

// If a scheaful table check that no excess fields have been provided
if self.tb(opt, txn).await?.full {
let data = match stm {
Statement::Create(v) => v.data.as_ref(),
Statement::Update(v) => v.data.as_ref(),
Statement::Relate(v) => v.data.as_ref(),
Statement::Insert(v) => Some(&v.data),
_ => None,
};
let stm_fd_names = data.as_ref().map_or(vec![], |d| d.field_names());
let fd_names = fds.iter().map(|fd| fd.name.clone()).collect::<Vec<_>>();
for stm_name in stm_fd_names {
if stm_name.0.starts_with(&[Part::Field("id".into())]) {
continue;
}

if !fd_names.contains(&stm_name) {
return Err(Error::UndefinedField {
table: rid.tb.clone(),
field: stm_name,
});
}
}
}

// Loop through all field statements
for fd in self.fd(opt, txn).await?.iter() {
for fd in fds.iter() {
// Loop over each field in document
for (k, mut val) in self.current.doc.walk(&fd.name).into_iter() {
// Get the initial value
Expand Down
7 changes: 7 additions & 0 deletions core/src/err/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,13 @@ pub enum Error {
check: String,
},

/// The specified field on a SCHEMAFUL table was not defined
#[error("Found field '{field}', but no such field exists for table '{table}'")]
UndefinedField {
table: String,
field: Idiom,
},

/// The specified field did not conform to the field ASSERT clause
#[error(
"Found changed value for field `{field}`, with record `{thing}`, but field is readonly"
Expand Down
26 changes: 26 additions & 0 deletions core/src/sql/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,32 @@ impl Default for Data {
}
}

impl Data {
pub(crate) fn field_names(&self) -> Vec<Idiom> {
match self {
Data::SetExpression(v) => v.iter().map(|(i, _, _)| i.clone()).collect(),
Data::UnsetExpression(v) => v.clone(),
Data::ValuesExpression(v) => {
v.iter().flat_map(|v| v.iter().map(|(i, _)| i.clone())).collect()
}
Data::UpdateExpression(v) => v.iter().map(|(i, _, _)| i.clone()).collect(),
Data::SingleExpression(v) => names_from_value(v),
Data::EmptyExpression => vec![],
Data::PatchExpression(v) => names_from_value(v),
Data::MergeExpression(v) => names_from_value(v),
Data::ReplaceExpression(v) => names_from_value(v),
Data::ContentExpression(v) => names_from_value(v),
}
}
}

fn names_from_value(value: &Value) -> Vec<Idiom> {
match value {
Value::Object(o) => o.keys().map(|k| Idiom::from(k.clone())).collect(),
_ => vec![],
}
}

impl Data {
/// Fetch the 'id' field if one has been specified
pub(crate) async fn rid(
Expand Down
24 changes: 17 additions & 7 deletions lib/tests/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ async fn field_definition_value_assert_failure() -> Result<(), Error> {
DEFINE FIELD age ON person TYPE number ASSERT $value > 0;
DEFINE FIELD email ON person TYPE string ASSERT string::is::email($value);
DEFINE FIELD name ON person TYPE option<string> VALUE $value OR 'No name';
CREATE person:test SET email = '[email protected]', other = 'ignore';
CREATE person:test SET email = '[email protected]', other = 'ignore', age = NONE;
CREATE person:test SET email = '[email protected]', other = 'ignore', age = NULL;
CREATE person:test SET email = '[email protected]', other = 'ignore', age = 0;
CREATE person:test SET email = '[email protected]', other = 'ignore', age = 13;
CREATE person:test SET email = '[email protected]';
CREATE person:test SET email = '[email protected]', age = NONE;
CREATE person:test SET email = '[email protected]', age = NULL;
CREATE person:test SET email = '[email protected]', age = 0;
CREATE person:test SET email = '[email protected]', age = 13;
";
let dbs = new_ds().await?;
let ses = Session::owner().with_ns("test").with_db("test");
Expand Down Expand Up @@ -103,11 +103,12 @@ async fn field_definition_value_assert_success() -> Result<(), Error> {
DEFINE FIELD email ON person TYPE string ASSERT string::is::email($value);
DEFINE FIELD name ON person TYPE option<string> VALUE $value OR 'No name';
CREATE person:test SET email = '[email protected]', other = 'ignore', age = 22;
CREATE person:test SET email = '[email protected]', age = 22;
";
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(), 5);
assert_eq!(res.len(), 6);
//
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
Expand All @@ -121,6 +122,9 @@ async fn field_definition_value_assert_success() -> Result<(), Error> {
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
//
let tmp = res.remove(0).result;
assert!(tmp.is_err());
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
Expand Down Expand Up @@ -787,6 +791,7 @@ async fn field_definition_value_reference_with_future() -> Result<(), Error> {
async fn field_definition_edge_permissions() -> Result<(), Error> {
let sql = "
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD name ON TABLE user TYPE string;
DEFINE TABLE business SCHEMAFULL;
DEFINE FIELD owner ON TABLE business TYPE record<user>;
DEFINE TABLE contact TYPE RELATION SCHEMAFULL PERMISSIONS FOR create WHERE in.owner.id = $auth.id;
Expand All @@ -796,7 +801,10 @@ async fn field_definition_edge_permissions() -> Result<(), Error> {
let dbs = new_ds().await?.with_auth_enabled(true);
let ses = Session::owner().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 6);
assert_eq!(res.len(), 7);
//
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
//
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
Expand All @@ -815,9 +823,11 @@ async fn field_definition_edge_permissions() -> Result<(), Error> {
"[
{
id: user:one,
name: 'John',
},
{
id: user:two,
name: 'Lucy',
},
]",
);
Expand Down