diff --git a/cozowebui/src/App.js b/cozowebui/src/App.js index 64101dc5..ef430f05 100644 --- a/cozowebui/src/App.js +++ b/cozowebui/src/App.js @@ -69,10 +69,10 @@ function App() { function displayValue(v) { - if (v instanceof Object) { - return JSON.stringify(v) - } else { + if (typeof v === 'string') { return v + } else { + return {JSON.stringify(v)} } } diff --git a/src/parse/query.rs b/src/parse/query.rs index 9dfcbe49..cb861fb0 100644 --- a/src/parse/query.rs +++ b/src/parse/query.rs @@ -6,12 +6,12 @@ use std::fmt::{Display, Formatter}; use either::Left; use itertools::Itertools; -use miette::{bail, ensure, Diagnostic, LabeledSpan, Report, Result}; +use miette::{bail, Diagnostic, ensure, LabeledSpan, Report, Result}; use smartstring::{LazyCompact, SmartString}; use thiserror::Error; use crate::algo::{AlgoHandle, AlgoNotFoundError}; -use crate::data::aggr::{parse_aggr, Aggregation}; +use crate::data::aggr::{Aggregation, parse_aggr}; use crate::data::expr::Expr; use crate::data::id::Validity; use crate::data::program::{ @@ -22,9 +22,9 @@ use crate::data::program::{ use crate::data::symb::Symbol; use crate::data::tuple::Tuple; use crate::data::value::DataValue; +use crate::parse::{ExtractSpan, Pair, Pairs, ParseError, Rule, SourceSpan}; use crate::parse::expr::build_expr; use crate::parse::pull::parse_out_options; -use crate::parse::{ExtractSpan, Pair, Pairs, ParseError, Rule, SourceSpan}; use crate::runtime::relation::{RelationId, RelationMetadata}; #[derive(Error, Diagnostic, Debug)] @@ -423,7 +423,16 @@ fn parse_rule( let span = src.extract_span(); let mut src = src.into_inner(); let head = src.next().unwrap(); + let head_span = head.extract_span(); let (name, head, aggr) = parse_rule_head(head, param_pool)?; + + #[derive(Debug, Error, Diagnostic)] + #[error("Horn-clause rule cannot have empty rule head")] + #[diagnostic(code(parser::empty_horn_rule_head))] + struct EmptyRuleHead(#[label] SourceSpan); + + ensure!(!head.is_empty(), EmptyRuleHead(head_span)); + let mut at = None; let mut body = src.next().unwrap(); if body.as_rule() == Rule::expr { diff --git a/src/query/compile.rs b/src/query/compile.rs index 11d4ac0e..fa39fdbb 100644 --- a/src/query/compile.rs +++ b/src/query/compile.rs @@ -441,8 +441,11 @@ impl SessionTx { let cur_ret_set: BTreeSet<_> = ret.bindings_after_eliminate().into_iter().collect(); #[derive(Debug, Error, Diagnostic)] - #[error("Symbol {0} in rule head is unbound")] + #[error("Symbol '{0}' in rule head is unbound")] #[diagnostic(code(eval::unbound_symb_in_head))] + #[diagnostic(help( + "Note that symbols occurring only in negated positions are not considered bound" + ))] struct UnboundSymbolInRuleHead(String, #[label] SourceSpan); ensure!(cur_ret_set == ret_vars_set, { diff --git a/src/query/reorder.rs b/src/query/reorder.rs index 93d00c6f..f6974b87 100644 --- a/src/query/reorder.rs +++ b/src/query/reorder.rs @@ -8,11 +8,12 @@ use crate::data::program::{NormalFormAtom, NormalFormRule}; use crate::parse::SourceSpan; #[derive(Diagnostic, Debug, Error)] -#[error("Encountered unsafe negation")] +#[error("Encountered unsafe negation, or empty rule definition")] #[diagnostic(code(eval::unsafe_negation))] #[diagnostic(help( -"Only rule applications that are partially bounded, \ - or expressions / unifications that are completely bounded, can be safely negated." + "Only rule applications that are partially bounded, \ +or expressions / unifications that are completely bounded, can be safely negated. \ +You may also encounter this error if your rule can never produce any rows." ))] pub(crate) struct UnsafeNegation(#[label] pub(crate) SourceSpan);