From b02567d2333bae88601e05936144ff415c6dc7f1 Mon Sep 17 00:00:00 2001 From: Mees Delzenne Date: Fri, 8 Sep 2023 13:28:36 +0200 Subject: [PATCH] Improved error messages (#2566) --- lib/src/err/mod.rs | 9 +- lib/src/fnc/mod.rs | 3 +- lib/src/idx/ft/analyzer/filter.rs | 174 ++----- lib/src/idx/ft/analyzer/tokenizer.rs | 4 +- lib/src/idx/ft/mod.rs | 4 +- lib/src/sql/base.rs | 21 +- lib/src/sql/block.rs | 16 +- lib/src/sql/builtin.rs | 485 ++++++++++++++++++ lib/src/sql/common.rs | 97 ++-- lib/src/sql/constant.rs | 55 +- lib/src/sql/datetime.rs | 4 +- lib/src/sql/duration.rs | 12 +- lib/src/sql/ending.rs | 52 +- lib/src/sql/error.rs | 26 - lib/src/sql/error/mod.rs | 478 +++++++++++++++++ lib/src/sql/error/render.rs | 144 ++++++ lib/src/sql/error/utils.rs | 134 +++++ lib/src/sql/expression.rs | 7 +- lib/src/sql/function.rs | 427 ++------------- lib/src/sql/future.rs | 6 +- lib/src/sql/geometry.rs | 2 +- lib/src/sql/graph.rs | 38 +- lib/src/sql/ident.rs | 4 +- lib/src/sql/idiom.rs | 43 +- lib/src/sql/kind.rs | 22 +- lib/src/sql/mod.rs | 3 +- lib/src/sql/number.rs | 45 +- lib/src/sql/object.rs | 42 +- lib/src/sql/omit.rs | 3 +- lib/src/sql/parser.rs | 82 +-- lib/src/sql/part.rs | 53 +- lib/src/sql/permission.rs | 19 +- lib/src/sql/query.rs | 13 +- lib/src/sql/regex.rs | 2 +- lib/src/sql/result.rs | 84 --- lib/src/sql/scoring.rs | 38 +- lib/src/sql/script.rs | 2 +- lib/src/sql/special.rs | 16 +- lib/src/sql/split.rs | 8 +- lib/src/sql/statements/create.rs | 2 +- lib/src/sql/statements/define/analyzer.rs | 14 +- lib/src/sql/statements/define/database.rs | 15 +- lib/src/sql/statements/define/event.rs | 30 +- lib/src/sql/statements/define/field.rs | 35 +- lib/src/sql/statements/define/function.rs | 41 +- lib/src/sql/statements/define/index.rs | 32 +- lib/src/sql/statements/define/mod.rs | 4 + lib/src/sql/statements/define/namespace.rs | 10 +- lib/src/sql/statements/define/param.rs | 16 +- lib/src/sql/statements/define/scope.rs | 16 +- lib/src/sql/statements/define/table.rs | 15 +- lib/src/sql/statements/define/token.rs | 28 +- lib/src/sql/statements/define/user.rs | 38 +- lib/src/sql/statements/delete.rs | 14 +- lib/src/sql/statements/foreach.rs | 4 +- lib/src/sql/statements/info.rs | 26 +- lib/src/sql/statements/insert.rs | 13 +- lib/src/sql/statements/live.rs | 3 +- lib/src/sql/statements/option.rs | 12 +- lib/src/sql/statements/relate.rs | 6 +- lib/src/sql/statements/remove/analyzer.rs | 5 +- lib/src/sql/statements/remove/database.rs | 5 +- lib/src/sql/statements/remove/event.rs | 10 +- lib/src/sql/statements/remove/field.rs | 10 +- lib/src/sql/statements/remove/function.rs | 2 - lib/src/sql/statements/remove/index.rs | 10 +- lib/src/sql/statements/remove/mod.rs | 4 + lib/src/sql/statements/remove/namespace.rs | 5 +- lib/src/sql/statements/remove/param.rs | 7 +- lib/src/sql/statements/remove/scope.rs | 5 +- lib/src/sql/statements/remove/table.rs | 5 +- lib/src/sql/statements/remove/token.rs | 10 +- lib/src/sql/statements/remove/user.rs | 10 +- lib/src/sql/statements/select.rs | 14 +- lib/src/sql/statements/show.rs | 16 +- lib/src/sql/statements/update.rs | 2 +- lib/src/sql/strand.rs | 40 +- lib/src/sql/subquery.rs | 67 ++- lib/src/sql/table.rs | 4 +- lib/src/sql/thing.rs | 4 +- lib/src/sql/util.rs | 101 +++- .../sql/value/serde/ser/statement/begin.rs | 2 +- .../sql/value/serde/ser/statement/break.rs | 2 +- .../sql/value/serde/ser/statement/cancel.rs | 2 +- .../sql/value/serde/ser/statement/commit.rs | 2 +- .../sql/value/serde/ser/statement/continue.rs | 2 +- .../sql/value/serde/ser/statement/show/mod.rs | 1 + lib/src/sql/value/value.rs | 76 ++- lib/src/sql/view.rs | 7 +- lib/tests/complex.rs | 12 +- 90 files changed, 2225 insertions(+), 1268 deletions(-) create mode 100644 lib/src/sql/builtin.rs delete mode 100644 lib/src/sql/error.rs create mode 100644 lib/src/sql/error/mod.rs create mode 100644 lib/src/sql/error/render.rs create mode 100644 lib/src/sql/error/utils.rs delete mode 100644 lib/src/sql/result.rs diff --git a/lib/src/err/mod.rs b/lib/src/err/mod.rs index f9a59d00..dc360cd3 100644 --- a/lib/src/err/mod.rs +++ b/lib/src/err/mod.rs @@ -1,5 +1,6 @@ use crate::iam::Error as IamError; use crate::idx::ft::MatchRef; +use crate::sql::error::RenderedError as RenderedParserError; use crate::sql::idiom::Idiom; use crate::sql::thing::Thing; use crate::sql::value::Value; @@ -117,12 +118,8 @@ pub enum Error { UnknownAuth, /// There was an error with the SQL query - #[error("Parse error on line {line} at character {char} when parsing '{sql}'")] - InvalidQuery { - line: usize, - char: usize, - sql: String, - }, + #[error("Parse error: {0}")] + InvalidQuery(RenderedParserError), /// There was an error with the SQL query #[error("Can not use {value} in a CONTENT clause")] diff --git a/lib/src/fnc/mod.rs b/lib/src/fnc/mod.rs index 4cd6b97c..d3120e2e 100644 --- a/lib/src/fnc/mod.rs +++ b/lib/src/fnc/mod.rs @@ -419,7 +419,8 @@ mod tests { let (quote, _) = line.split_once("=>").unwrap(); let name = quote.trim().trim_matches('"'); - if crate::sql::function::function_names(name).is_err() { + let builtin_name = crate::sql::builtin::builtin_name(name); + if builtin_name.is_err() { problems.push(format!("couldn't parse {name} function")); } diff --git a/lib/src/idx/ft/analyzer/filter.rs b/lib/src/idx/ft/analyzer/filter.rs index 1d780932..2271d17e 100644 --- a/lib/src/idx/ft/analyzer/filter.rs +++ b/lib/src/idx/ft/analyzer/filter.rs @@ -191,17 +191,13 @@ mod tests { "بدل", "من", "الجر", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(arabic);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(arabic);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(ar);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(ar);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(ara);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(ara);", input, &output, ); @@ -235,20 +231,16 @@ mod tests { ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(danish);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(danish);", input, &output, ); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(dan);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(da);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(dan);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(da);", input, &output); } #[test] @@ -259,17 +251,13 @@ mod tests { "klein", "hond", "slaapt", "liever", "in", "zijn", "mand", "dan", "te", "renn", ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(dutch);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(dutch);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(nl);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(nl);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(nld);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(nld);", input, &output, ); @@ -283,20 +271,16 @@ mod tests { "read", "in", "her", "spare", "time", "rather", "than", "teach", ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(english);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(english);", input, &output, ); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(eng);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(en);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(eng);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(en);", input, &output); } #[test] @@ -307,17 +291,13 @@ mod tests { "chien", "aim", "plutôt", "se", "blott", "sur", "le", "canap", "que", "de", "cour", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(french);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(french);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(fr);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(fr);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(fra);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(fra);", input, &output, ); @@ -332,17 +312,13 @@ mod tests { "zu", "lauf", ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(german);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(german);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(de);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(de);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(deu);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(deu);", input, &output, ); @@ -377,20 +353,16 @@ mod tests { ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(greek);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(greek);", input, &output, ); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(ell);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(el);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(ell);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(el);", input, &output); } #[test] @@ -401,17 +373,13 @@ mod tests { "inkább", "alsz", "a", "kosar", ",", "mints", "fu", ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(hungarian);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(hungarian);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(hu);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(hu);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(hun);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(hun);", input, &output, ); @@ -425,17 +393,13 @@ mod tests { "prefer", "dorm", "nel", "suo", "cest", "piuttost", "che", "corr", ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(italian);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(italian);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(it);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(it);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(ita);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(ita);", input, &output, ); @@ -469,17 +433,13 @@ mod tests { ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(norwegian);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(norwegian);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(no);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(no);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(nor);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(nor);", input, &output, ); @@ -493,17 +453,13 @@ mod tests { "prefer", "dorm", "na", "sua", "cam", "em", "vez", "de", "corr", ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(portuguese);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(portuguese);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(pt);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(pt);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(por);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(por);", input, &output, ); @@ -517,17 +473,13 @@ mod tests { "să", "doarm", "în", "coș", "lui", "decât", "să", "alerg", ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(romanian);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(romanian);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(ro);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(ro);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(ron);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(ron);", input, &output, ); @@ -559,17 +511,13 @@ mod tests { ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(russian);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(russian);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(ru);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(ru);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(rus);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(rus);", input, &output, ); @@ -583,17 +531,13 @@ mod tests { "prefier", "dorm", "en", "su", "cam", "en", "lug", "de", "corr", ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(spanish);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(spanish);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(es);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(es);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(spa);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(spa);", input, &output, ); @@ -627,17 +571,13 @@ mod tests { ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(swedish);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(swedish);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(sv);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(sv);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(swe);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(swe);", input, &output, ); @@ -674,17 +614,13 @@ mod tests { ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(tamil);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(tamil);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(ta);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(ta);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(tam);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(tam);", input, &output, ); @@ -698,17 +634,13 @@ mod tests { "yatak", "uyuma", "tercih", "eder", ".", ]; test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(turkish);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(turkish);", input, &output, ); + test_analyzer("ANALYZER test TOKENIZERS blank,class FILTERS snowball(tr);", input, &output); test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(tr);", - input, - &output, - ); - test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS snowball(tur);", + "ANALYZER test TOKENIZERS blank,class FILTERS snowball(tur);", input, &output, ); @@ -717,7 +649,7 @@ mod tests { #[test] fn test_ngram() { test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS lowercase,ngram(2,3);", + "ANALYZER test TOKENIZERS blank,class FILTERS lowercase,ngram(2,3);", "Ālea iacta est", &["āl", "āle", "le", "lea", "ia", "iac", "ac", "act", "ct", "cta", "es", "est"], ); @@ -726,7 +658,7 @@ mod tests { #[test] fn test_edgengram() { test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS lowercase,edgengram(2,3);", + "ANALYZER test TOKENIZERS blank,class FILTERS lowercase,edgengram(2,3);", "Ālea iacta est", &["āl", "āle", "ia", "iac", "es", "est"], ); diff --git a/lib/src/idx/ft/analyzer/tokenizer.rs b/lib/src/idx/ft/analyzer/tokenizer.rs index c89835a6..b717dd9a 100644 --- a/lib/src/idx/ft/analyzer/tokenizer.rs +++ b/lib/src/idx/ft/analyzer/tokenizer.rs @@ -313,7 +313,7 @@ mod tests { #[test] fn test_tokenize_blank_class() { test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class FILTERS lowercase", + "ANALYZER test TOKENIZERS blank,class FILTERS lowercase", "Abc12345xYZ DL1809 item123456 978-3-16-148410-0 1HGCM82633A123456", &[ "abc", "12345", "xyz", "dl", "1809", "item", "123456", "978", "-", "3", "-", "16", @@ -325,7 +325,7 @@ mod tests { #[test] fn test_tokenize_source_code() { test_analyzer( - "DEFINE ANALYZER test TOKENIZERS blank,class,camel,punct FILTERS lowercase", + "ANALYZER test TOKENIZERS blank,class,camel,punct FILTERS lowercase", r#"struct MyRectangle { // specified by corners top_left: Point, diff --git a/lib/src/idx/ft/mod.rs b/lib/src/idx/ft/mod.rs index a3937bb1..b01c8a8c 100644 --- a/lib/src/idx/ft/mod.rs +++ b/lib/src/idx/ft/mod.rs @@ -537,7 +537,7 @@ mod tests { #[test(tokio::test)] async fn test_ft_index() { let ds = Datastore::new("memory").await.unwrap(); - let (_, az) = analyzer("DEFINE ANALYZER test TOKENIZERS blank;").unwrap(); + let (_, az) = analyzer("ANALYZER test TOKENIZERS blank;").unwrap(); let btree_order = 5; @@ -641,7 +641,7 @@ mod tests { // Therefore it makes sense to do multiple runs. for _ in 0..10 { let ds = Datastore::new("memory").await.unwrap(); - let (_, az) = analyzer("DEFINE ANALYZER test TOKENIZERS blank;").unwrap(); + let (_, az) = analyzer("ANALYZER test TOKENIZERS blank;").unwrap(); let doc1: Thing = ("t", "doc1").into(); let doc2: Thing = ("t", "doc2").into(); diff --git a/lib/src/sql/base.rs b/lib/src/sql/base.rs index 0e548a3a..fd90358c 100644 --- a/lib/src/sql/base.rs +++ b/lib/src/sql/base.rs @@ -8,6 +8,8 @@ use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt; +use super::error::expected; + #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] #[revisioned(revision = 1)] pub enum Base { @@ -35,14 +37,17 @@ impl fmt::Display for Base { } pub fn base(i: &str) -> IResult<&str, Base> { - alt(( - value(Base::Ns, tag_no_case("NAMESPACE")), - value(Base::Db, tag_no_case("DATABASE")), - value(Base::Root, tag_no_case("ROOT")), - value(Base::Ns, tag_no_case("NS")), - value(Base::Db, tag_no_case("DB")), - value(Base::Root, tag_no_case("KV")), - ))(i) + expected( + "a base, one of NAMESPACE, DATABASE, ROOT or KV", + alt(( + value(Base::Ns, tag_no_case("NAMESPACE")), + value(Base::Db, tag_no_case("DATABASE")), + value(Base::Root, tag_no_case("ROOT")), + value(Base::Ns, tag_no_case("NS")), + value(Base::Db, tag_no_case("DB")), + value(Base::Root, tag_no_case("KV")), + )), + )(i) } pub fn base_or_scope(i: &str) -> IResult<&str, Base> { diff --git a/lib/src/sql/block.rs b/lib/src/sql/block.rs index 28a0edae..7fffe887 100644 --- a/lib/src/sql/block.rs +++ b/lib/src/sql/block.rs @@ -32,6 +32,8 @@ use std::cmp::Ordering; use std::fmt::{self, Display, Formatter, Write}; use std::ops::Deref; +use super::util::expect_delimited; + pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Block"; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] @@ -179,11 +181,15 @@ impl Display for Block { } pub fn block(i: &str) -> IResult<&str, Block> { - let (i, _) = openbraces(i)?; - let (i, v) = separated_list0(colons, entry)(i)?; - let (i, _) = many0(colons)(i)?; - let (i, _) = closebraces(i)?; - Ok((i, Block(v))) + expect_delimited( + openbraces, + |i| { + let (i, v) = separated_list0(colons, entry)(i)?; + let (i, _) = many0(colons)(i)?; + Ok((i, Block(v))) + }, + closebraces, + )(i) } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] diff --git a/lib/src/sql/builtin.rs b/lib/src/sql/builtin.rs new file mode 100644 index 00000000..f976245d --- /dev/null +++ b/lib/src/sql/builtin.rs @@ -0,0 +1,485 @@ +use crate::sql::{constant, error::ParseError, ident::ident_raw}; +use nom::{ + bytes::complete::{tag, tag_no_case}, + combinator::{opt, peek, value}, + Err, IResult, +}; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum BuiltinName { + Function(I), + Constant(constant::Constant), +} + +/// A macro to generate a parser which is able to parse all the different functions, returning an +/// error of the function does not exists. +macro_rules! impl_builtins { + ($($name:ident$( ( $s:ident ) )? $(= $rename:expr)? => { $($t:tt)* }),*$(,)?) => { + fn _parse_builtin_name(i: &str) -> IResult<&str, BuiltinName<&str>, ParseError<&str>> { + $( + impl_builtins!{ + @variant, + impl_builtins!(@rename, $name, $($rename)?), + $name, + $($s)?, + $($rename)?, + { $($t)* } + } + )* + $( + if let (i, Some(x)) = opt($name)(i)?{ + return Ok((i,x)) + } + )* + Err(Err::Error(ParseError::Base(i))) + } + }; + + (@variant, $full:expr, $name:ident, $($s:ident)?,$($rename:expr)?, { fn }) => { + fn $name(i: &str) -> IResult<&str, BuiltinName<&str>, ParseError<&str>>{ + let parser = tag_no_case(impl_builtins!(@rename,$name,$($rename)?)); + let res = value(BuiltinName::Function($full),parser)(i)?; + Ok(res) + } + }; + (@variant, $full:expr, $name:ident,$($s:ident)?,$($rename:expr)?, { const = $value:expr}) => { + #[allow(non_snake_case)] + fn $name(i: &str) -> IResult<&str, BuiltinName<&str>, ParseError<&str>>{ + let parser = tag_no_case(impl_builtins!(@rename,$name,$($rename)?)); + let res = value(BuiltinName::Constant($value),parser)(i)?; + Ok(res) + } + }; + (@variant, $full:expr, $name:ident,$($s:ident)*,$($rename:expr)?, { $($t:tt)* }) => { + fn $name(i: &str) -> IResult<&str, BuiltinName<&str>, ParseError<&str>>{ + let (i,_) = tag_no_case(impl_builtins!(@rename,$name,$($rename)?))(i)?; + let (i,_) = impl_builtins!(@sep, i,$full, $($s)*); + + let (i,_) = impl_builtins!{@block,i, $full, { $($t)* }}; + + if let Ok((i, Some(_))) = peek(opt(ident_raw))(i){ + Err(Err::Failure(ParseError::InvalidPath{ + tried: i, + parent: $full + })) + }else{ + Err(Err::Failure(ParseError::Expected{ + tried: i, + expected: "a identifier" + })) + } + } + }; + + (@block, $i:ident, $full:expr, { $($name:ident $(($s:ident))? $(= $rename:expr)? => { $($t:tt)* }),* $(,)? }) => { + { + $( + impl_builtins!{@variant, + concat!($full,"::",impl_builtins!(@rename, $name, $($rename)?)), + $name, + $($s)?, + $($rename)?, + { $($t) * } + } + )* + + $( + if let Ok((i, x)) = $name($i){ + return Ok((i,x)) + } + )* + ($i,()) + } + }; + + (@sep, $input:expr, $full:expr, func) => { + match tag::<_,_,ParseError<&str>>("::")($input) { + Ok(x) => x, + Err(_) => { + return Ok(($input, BuiltinName::Function($full))) + } + } + }; + (@sep, $input:expr, $full:expr, cons) => { + match tag::<_,_,ParseError<&str>>("::")($input) { + Ok(x) => x, + Err(_) => { + return Ok(($input, BuiltinName::Constant($full))) + } + } + }; + (@sep, $input:expr,$full:expr, ) => {{ + match tag::<_,_,ParseError<&str>>("::")($input) { + Ok(x) => x, + Err(_) => { + return Err(Err::Error(ParseError::Expected{ + tried: $input, + expected: "a path seperator `::`" + })) + } + } + }}; + + (@rename, $name:ident, $rename:expr) => { + $rename + }; + + (@rename, $name:ident,) => { + stringify!($name) + }; +} + +pub(crate) fn builtin_name(i: &str) -> IResult<&str, BuiltinName<&str>, ParseError<&str>> { + impl_builtins! { + array => { + add => { fn }, + all => { fn }, + any => { fn }, + append => { fn }, + at => { fn }, + boolean_and => { fn }, + boolean_not => { fn }, + boolean_or => { fn }, + boolean_xor => { fn }, + clump => { fn }, + combine => { fn }, + complement => { fn }, + concat => { fn }, + difference => { fn }, + distinct => { fn }, + filter_index => { fn }, + find_index => { fn }, + first => { fn }, + flatten => { fn }, + group => { fn }, + insert => { fn }, + intersect=> { fn }, + join => { fn }, + last=> { fn }, + len => { fn }, + logical_and => { fn }, + logical_or => { fn }, + logical_xor => { fn }, + matches => { fn }, + max => { fn }, + min => { fn }, + pop => { fn }, + prepend => { fn }, + push => { fn }, + remove => { fn }, + reverse => { fn }, + slice => { fn }, + // says that sort is also itself a function + sort(func) => { + asc => {fn }, + desc => {fn }, + }, + transpose => { fn }, + r#union = "union" => { fn }, + }, + bytes => { + len => { fn } + }, + crypto => { + argon2 => { + compare => { fn }, + generate => { fn } + }, + bcrypt => { + compare => { fn }, + generate => { fn } + }, + pbkdf2 => { + compare => { fn }, + generate => { fn } + }, + scrypt => { + compare => { fn }, + generate => { fn } + }, + md5 => { fn }, + sha1 => { fn }, + sha256 => { fn }, + sha512 => { fn } + }, + duration => { + days => { fn }, + hours => { fn }, + micros => { fn }, + millis => { fn }, + mins => { fn }, + nanos => { fn }, + secs => { fn }, + weeks => { fn }, + years => { fn }, + from => { + days => { fn }, + hours => { fn }, + micros => { fn }, + millis => { fn }, + mins => { fn }, + nanos => { fn }, + secs => { fn }, + weeks => { fn }, + }, + }, + encoding => { + base64 => { + decode => { fn }, + encode => { fn }, + } + }, + geo => { + area => { fn }, + bearing => { fn }, + centroid => { fn }, + distance => { fn }, + hash => { + decode => { fn }, + encode => { fn }, + }, + }, + http => { + head => { fn }, + get => { fn }, + put => { fn }, + post => { fn }, + patch => { fn }, + delete => { fn }, + }, + math => { + abs => { fn }, + bottom => { fn }, + ceil => { fn }, + fixed => { fn }, + floor => { fn }, + interquartile => { fn }, + max => { fn }, + mean => { fn }, + median => { fn }, + midhinge => { fn }, + min => { fn }, + mode => { fn }, + nearestrank => { fn }, + percentile => { fn }, + pow => { fn }, + product => { fn }, + round => { fn }, + spread => { fn }, + sqrt => { fn }, + stddev => { fn }, + sum => { fn }, + top => { fn }, + trimean => { fn }, + variance => { fn }, + E => { const = constant::Constant::MathE }, + FRAC_1_PI => { const = constant::Constant::MathFrac1Pi }, + FRAC_1_SQRT_2 => { const = constant::Constant::MathFrac1Sqrt2 }, + FRAC_2_PI => { const = constant::Constant::MathFrac2Pi }, + FRAC_2_SQRT_PI => { const = constant::Constant::MathFrac2SqrtPi }, + FRAC_PI_2 => { const = constant::Constant::MathFracPi2 }, + FRAC_PI_3 => { const = constant::Constant::MathFracPi3 }, + FRAC_PI_4 => { const = constant::Constant::MathFracPi4 }, + FRAC_PI_6 => { const = constant::Constant::MathFracPi6 }, + FRAC_PI_8 => { const = constant::Constant::MathFracPi8 }, + INF => { const = constant::Constant::MathInf }, + LN_10 => { const = constant::Constant::MathLn10 }, + LN_2 => { const = constant::Constant::MathLn2 }, + LOG10_2 => { const = constant::Constant::MathLog102 }, + LOG10_E => { const = constant::Constant::MathLog10E }, + LOG2_10 => { const = constant::Constant::MathLog210 }, + LOG2_E => { const = constant::Constant::MathLog2E }, + PI => { const = constant::Constant::MathPi }, + SQRT_2 => { const = constant::Constant::MathSqrt2 }, + TAU => { const = constant::Constant::MathTau }, + }, + meta => { + id => { fn }, + table => { fn }, + tb => { fn }, + }, + parse => { + email => { + host => { fn }, + user => { fn }, + }, + url => { + domain => { fn }, + fragment => { fn }, + host => { fn }, + path => { fn }, + port => { fn }, + query => { fn }, + scheme => { fn }, + } + }, + rand(func) => { + r#bool = "bool" => { fn }, + r#enum = "enum" => { fn }, + float => { fn }, + guid => { fn }, + int => { fn }, + string => { fn }, + time => { fn }, + ulid => { fn }, + uuid(func) => { + v4 => { fn }, + v7 => { fn }, + }, + }, + search => { + score => { fn }, + highlight => { fn }, + offsets => { fn }, + }, + session => { + db => { fn }, + id => { fn }, + ip => { fn }, + ns => { fn }, + origin => { fn }, + sc => { fn }, + sd => { fn }, + token => { fn }, + }, + string => { + concat => { fn }, + contains => { fn }, + ends_with = "endsWith" => { fn }, + join => { fn }, + len => { fn }, + lowercase => { fn }, + repeat => { fn }, + replace => { fn }, + reverse => { fn }, + slice => { fn }, + slug => { fn }, + split => { fn }, + starts_with = "startsWith" => { fn }, + trim => { fn }, + uppercase => { fn }, + words => { fn }, + distance => { + hamming => { fn }, + levenshtein => { fn }, + }, + similarity => { + fuzzy => { fn }, + jaro => { fn }, + smithwaterman => { fn }, + }, + is => { + alphanum => { fn }, + alpha => { fn }, + ascii => { fn }, + datetime => { fn }, + domain => { fn }, + email => { fn }, + hexadecimal => { fn }, + latitude => { fn }, + longitude => { fn }, + numeric => { fn }, + semver => { fn }, + url => { fn }, + uuid => { fn }, + } + }, + time => { + ceil => { fn }, + day => { fn }, + floor => { fn }, + format => { fn }, + group => { fn }, + hour => { fn }, + minute => { fn }, + max => { fn }, + min => { fn }, + month => { fn }, + nano => { fn }, + now => { fn }, + round => { fn }, + second => { fn }, + timezone => { fn }, + unix => { fn }, + wday => { fn }, + week => { fn }, + yday => { fn }, + year => { fn }, + from => { + micros => {fn}, + millis => {fn}, + unix => {fn}, + secs => {fn}, + } + }, + r#type = "type" => { + r#bool = "bool" => { fn }, + datetime => { fn }, + decimal => { fn }, + duration => { fn }, + fields => { fn }, + field => { fn }, + float => { fn }, + int => { fn }, + number => { fn }, + point => { fn }, + string => { fn }, + table => { fn }, + thing => { fn }, + is => { + array => { fn }, + r#bool = "bool" => { fn }, + bytes => { fn }, + collection => { fn }, + datetime => { fn }, + decimal => { fn }, + duration => { fn }, + float => { fn }, + geometry => { fn }, + int => { fn }, + line => { fn }, + null => { fn }, + multiline => { fn }, + multipoint => { fn }, + multipolygon => { fn }, + number => { fn }, + object => { fn }, + point => { fn }, + polygon => { fn }, + record => { fn }, + string => { fn }, + uuid => { fn }, + } + }, + vector => { + add => { fn }, + angle => { fn }, + divide => { fn }, + cross => { fn }, + dot => { fn }, + magnitude => { fn }, + multiply => { fn }, + normalize => { fn }, + project => { fn }, + subtract => { fn }, + distance => { + chebyshev => { fn }, + euclidean => { fn }, + hamming => { fn }, + mahalanobis => { fn }, + manhattan => { fn }, + minkowski => { fn }, + }, + similarity => { + cosine => {fn }, + jaccard => {fn }, + pearson => {fn }, + spearman => {fn }, + } + }, + count => { fn }, + not => { fn }, + sleep => { fn }, + } + _parse_builtin_name(i) +} diff --git a/lib/src/sql/common.rs b/lib/src/sql/common.rs index 57a4d673..4a4bcbb8 100644 --- a/lib/src/sql/common.rs +++ b/lib/src/sql/common.rs @@ -1,14 +1,14 @@ use crate::sql::comment::mightbespace; use crate::sql::comment::shouldbespace; -use crate::sql::error::Error::Parser; -use crate::sql::error::IResult; +use crate::sql::error::{IResult, ParseError}; use nom::branch::alt; use nom::bytes::complete::take_while; use nom::bytes::complete::take_while_m_n; use nom::character::complete::char; use nom::character::is_alphanumeric; +use nom::combinator::map_res; use nom::multi::many1; -use nom::Err::Error; +use nom::Err; use std::ops::RangeBounds; pub fn colons(i: &str) -> IResult<&str, ()> { @@ -36,52 +36,52 @@ pub fn commasorspace(i: &str) -> IResult<&str, ()> { alt((commas, shouldbespace))(i) } -pub fn openparentheses(i: &str) -> IResult<&str, ()> { - let (i, _) = char('(')(i)?; +pub fn openparentheses(s: &str) -> IResult<&str, &str> { + let (i, _) = char('(')(s)?; let (i, _) = mightbespace(i)?; - Ok((i, ())) + Ok((i, s)) } -pub fn closeparentheses(i: &str) -> IResult<&str, ()> { - let (i, _) = mightbespace(i)?; - let (i, _) = char(')')(i)?; - Ok((i, ())) +pub fn closeparentheses(i: &str) -> IResult<&str, &str> { + let (s, _) = mightbespace(i)?; + let (i, _) = char(')')(s)?; + Ok((i, s)) } -pub fn openbraces(i: &str) -> IResult<&str, ()> { - let (i, _) = char('{')(i)?; +pub fn openbraces(s: &str) -> IResult<&str, &str> { + let (i, _) = char('{')(s)?; let (i, _) = mightbespace(i)?; - Ok((i, ())) + Ok((i, s)) } -pub fn closebraces(i: &str) -> IResult<&str, ()> { - let (i, _) = mightbespace(i)?; - let (i, _) = char('}')(i)?; - Ok((i, ())) +pub fn closebraces(i: &str) -> IResult<&str, &str> { + let (s, _) = mightbespace(i)?; + let (i, _) = char('}')(s)?; + Ok((i, s)) } -pub fn openbracket(i: &str) -> IResult<&str, ()> { - let (i, _) = char('[')(i)?; +pub fn openbracket(s: &str) -> IResult<&str, &str> { + let (i, _) = char('[')(s)?; let (i, _) = mightbespace(i)?; - Ok((i, ())) + Ok((i, s)) } -pub fn closebracket(i: &str) -> IResult<&str, ()> { - let (i, _) = mightbespace(i)?; - let (i, _) = char(']')(i)?; - Ok((i, ())) +pub fn closebracket(i: &str) -> IResult<&str, &str> { + let (s, _) = mightbespace(i)?; + let (i, _) = char(']')(s)?; + Ok((i, s)) } -pub fn openchevron(i: &str) -> IResult<&str, ()> { - let (i, _) = char('<')(i)?; +pub fn openchevron(s: &str) -> IResult<&str, &str> { + let (i, _) = char('<')(s)?; let (i, _) = mightbespace(i)?; - Ok((i, ())) + Ok((i, s)) } -pub fn closechevron(i: &str) -> IResult<&str, ()> { - let (i, _) = mightbespace(i)?; - let (i, _) = char('>')(i)?; - Ok((i, ())) +pub fn closechevron(i: &str) -> IResult<&str, &str> { + let (s, _) = mightbespace(i)?; + let (i, _) = char('>')(s)?; + Ok((i, s)) } #[inline] @@ -105,33 +105,34 @@ pub fn val_char(chr: char) -> bool { } pub fn take_u64(i: &str) -> IResult<&str, u64> { - let (i, v) = take_while(is_digit)(i)?; - match v.parse::() { - Ok(v) => Ok((i, v)), - _ => Err(Error(Parser(i))), - } + map_res(take_while(is_digit), |s: &str| s.parse::())(i) } pub fn take_u32_len(i: &str) -> IResult<&str, (u32, usize)> { - let (i, v) = take_while(is_digit)(i)?; - match v.parse::() { - Ok(n) => Ok((i, (n, v.len()))), - _ => Err(Error(Parser(i))), - } + map_res(take_while(is_digit), |s: &str| s.parse::().map(|x| (x, s.len())))(i) } pub fn take_digits(i: &str, n: usize) -> IResult<&str, u32> { - let (i, v) = take_while_m_n(n, n, is_digit)(i)?; - match v.parse::() { - Ok(v) => Ok((i, v)), - _ => Err(Error(Parser(i))), - } + map_res(take_while_m_n(n, n, is_digit), |s: &str| s.parse::())(i) } pub fn take_digits_range(i: &str, n: usize, range: impl RangeBounds) -> IResult<&str, u32> { let (i, v) = take_while_m_n(n, n, is_digit)(i)?; match v.parse::() { - Ok(v) if range.contains(&v) => Ok((i, v)), - _ => Err(Error(Parser(i))), + Ok(v) => { + if range.contains(&v) { + Ok((i, v)) + } else { + Result::Err(Err::Error(ParseError::RangeError { + tried: i, + lower: range.start_bound().cloned(), + upper: range.end_bound().cloned(), + })) + } + } + Err(error) => Result::Err(Err::Error(ParseError::ParseInt { + tried: v, + error, + })), } } diff --git a/lib/src/sql/constant.rs b/lib/src/sql/constant.rs index 4833235f..68f52513 100644 --- a/lib/src/sql/constant.rs +++ b/lib/src/sql/constant.rs @@ -2,16 +2,11 @@ use crate::ctx::Context; use crate::dbs::{Options, Transaction}; use crate::doc::CursorDoc; use crate::err::Error; -use crate::sql::error::IResult; use crate::sql::value::Value; use crate::sql::Datetime; use chrono::TimeZone; use chrono::Utc; use derive::Store; -use nom::branch::alt; -use nom::bytes::complete::tag_no_case; -use nom::combinator::value; -use nom::sequence::preceded; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt; @@ -122,71 +117,37 @@ impl fmt::Display for Constant { } } -pub fn constant(i: &str) -> IResult<&str, Constant> { - alt((constant_math, constant_time))(i) -} - -fn constant_math(i: &str) -> IResult<&str, Constant> { - preceded( - tag_no_case("math::"), - alt(( - value(Constant::MathE, tag_no_case("E")), - value(Constant::MathFrac1Pi, tag_no_case("FRAC_1_PI")), - value(Constant::MathFrac1Sqrt2, tag_no_case("FRAC_1_SQRT_2")), - value(Constant::MathFrac2Pi, tag_no_case("FRAC_2_PI")), - value(Constant::MathFrac2SqrtPi, tag_no_case("FRAC_2_SQRT_PI")), - value(Constant::MathFracPi2, tag_no_case("FRAC_PI_2")), - value(Constant::MathFracPi3, tag_no_case("FRAC_PI_3")), - value(Constant::MathFracPi4, tag_no_case("FRAC_PI_4")), - value(Constant::MathFracPi6, tag_no_case("FRAC_PI_6")), - value(Constant::MathFracPi8, tag_no_case("FRAC_PI_8")), - value(Constant::MathInf, tag_no_case("INF")), - value(Constant::MathLn10, tag_no_case("LN_10")), - value(Constant::MathLn2, tag_no_case("LN_2")), - value(Constant::MathLog102, tag_no_case("LOG10_2")), - value(Constant::MathLog10E, tag_no_case("LOG10_E")), - value(Constant::MathLog210, tag_no_case("LOG2_10")), - value(Constant::MathLog2E, tag_no_case("LOG2_E")), - value(Constant::MathPi, tag_no_case("PI")), - value(Constant::MathSqrt2, tag_no_case("SQRT_2")), - value(Constant::MathTau, tag_no_case("TAU")), - )), - )(i) -} - -fn constant_time(i: &str) -> IResult<&str, Constant> { - preceded(tag_no_case("time::"), alt((value(Constant::TimeEpoch, tag_no_case("EPOCH")),)))(i) -} - #[cfg(test)] mod tests { + use crate::sql::builtin::{builtin_name, BuiltinName}; + use super::*; #[test] fn constant_lowercase() { let sql = "math::pi"; - let res = constant(sql); + let res = builtin_name(sql); assert!(res.is_ok()); let out = res.unwrap().1; - assert_eq!(out, Constant::MathPi); + assert_eq!(out, BuiltinName::Constant(Constant::MathPi)); } #[test] fn constant_uppercase() { let sql = "MATH::PI"; - let res = constant(sql); + let res = builtin_name(sql); assert!(res.is_ok()); let out = res.unwrap().1; - assert_eq!(out, Constant::MathPi); + assert_eq!(out, BuiltinName::Constant(Constant::MathPi)); } #[test] fn constant_mixedcase() { let sql = "math::PI"; - let res = constant(sql); + let res = builtin_name(sql); assert!(res.is_ok()); let out = res.unwrap().1; - assert_eq!(out, Constant::MathPi); + assert_eq!(out, BuiltinName::Constant(Constant::MathPi)); } } diff --git a/lib/src/sql/datetime.rs b/lib/src/sql/datetime.rs index edfbffdf..6b0597d0 100644 --- a/lib/src/sql/datetime.rs +++ b/lib/src/sql/datetime.rs @@ -21,6 +21,8 @@ use std::ops::Deref; use std::str; use std::str::FromStr; +use super::error::expected; + pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Datetime"; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize, Hash)] @@ -108,7 +110,7 @@ impl ops::Sub for Datetime { } pub fn datetime(i: &str) -> IResult<&str, Datetime> { - alt((datetime_single, datetime_double))(i) + expected("a datetime", alt((datetime_single, datetime_double)))(i) } fn datetime_single(i: &str) -> IResult<&str, Datetime> { diff --git a/lib/src/sql/duration.rs b/lib/src/sql/duration.rs index 1d524a01..22effc33 100644 --- a/lib/src/sql/duration.rs +++ b/lib/src/sql/duration.rs @@ -15,6 +15,8 @@ use std::ops::Deref; use std::str::FromStr; use std::time; +use super::error::expected; + static SECONDS_PER_YEAR: u64 = 365 * SECONDS_PER_DAY; static SECONDS_PER_WEEK: u64 = 7 * SECONDS_PER_DAY; static SECONDS_PER_DAY: u64 = 24 * SECONDS_PER_HOUR; @@ -296,9 +298,11 @@ impl<'a> Sum<&'a Self> for Duration { } pub fn duration(i: &str) -> IResult<&str, Duration> { - let (i, v) = many1(duration_raw)(i)?; - let (i, _) = ending(i)?; - Ok((i, v.iter().sum::())) + expected("a duration", |i| { + let (i, v) = many1(duration_raw)(i)?; + let (i, _) = ending(i)?; + Ok((i, v.iter().sum::())) + })(i) } fn duration_raw(i: &str) -> IResult<&str, Duration> { @@ -319,7 +323,7 @@ fn duration_raw(i: &str) -> IResult<&str, Duration> { _ => unreachable!("shouldn't have parsed {u} as duration unit"), }; - std_duration.map(|d| (i, Duration(d))).ok_or(nom::Err::Error(crate::sql::Error::Parser(i))) + std_duration.map(|d| (i, Duration(d))).ok_or(nom::Err::Error(crate::sql::ParseError::Base(i))) } fn part(i: &str) -> IResult<&str, u64> { diff --git a/lib/src/sql/ending.rs b/lib/src/sql/ending.rs index 5e8c5c1f..a9c609c0 100644 --- a/lib/src/sql/ending.rs +++ b/lib/src/sql/ending.rs @@ -87,27 +87,39 @@ pub fn field(i: &str) -> IResult<&str, ()> { } pub fn subquery(i: &str) -> IResult<&str, ()> { - alt(( + peek(alt(( + value((), preceded(shouldbespace, tag_no_case("THEN"))), + value((), preceded(shouldbespace, tag_no_case("ELSE"))), + value((), preceded(shouldbespace, tag_no_case("END"))), |i| { let (i, _) = mightbespace(i)?; - let (i, _) = char(';')(i)?; - let (i, _) = peek(alt(( - preceded(shouldbespace, tag_no_case("THEN")), - preceded(shouldbespace, tag_no_case("ELSE")), - preceded(shouldbespace, tag_no_case("END")), - )))(i)?; - Ok((i, ())) + alt(( + value((), eof), + value((), char(';')), + value((), char(',')), + value((), char('}')), + value((), char(')')), + value((), char(']')), + ))(i) }, - peek(alt(( - value((), preceded(shouldbespace, tag_no_case("THEN"))), - value((), preceded(shouldbespace, tag_no_case("ELSE"))), - value((), preceded(shouldbespace, tag_no_case("END"))), - value((), comment), - value((), char(']')), - value((), char('}')), - value((), char(';')), - value((), char(',')), - value((), eof), - ))), - ))(i) + )))(i) +} + +pub fn query(i: &str) -> IResult<&str, ()> { + peek(alt(( + value((), preceded(shouldbespace, tag_no_case("THEN"))), + value((), preceded(shouldbespace, tag_no_case("ELSE"))), + value((), preceded(shouldbespace, tag_no_case("END"))), + |i| { + let (i, _) = mightbespace(i)?; + alt(( + value((), eof), + value((), char(';')), + value((), char(',')), + value((), char('}')), + value((), char(')')), + value((), char(']')), + ))(i) + }, + )))(i) } diff --git a/lib/src/sql/error.rs b/lib/src/sql/error.rs deleted file mode 100644 index b98fb583..00000000 --- a/lib/src/sql/error.rs +++ /dev/null @@ -1,26 +0,0 @@ -use nom::error::ErrorKind; -use nom::error::ParseError; -use nom::Err; -use thiserror::Error; - -#[derive(Error, Debug, Clone, Eq, PartialEq)] -pub enum Error { - Parser(I), - ExcessiveDepth, - Field(I, String), - Split(I, String), - Order(I, String), - Group(I, String), - Role(I, String), -} - -pub type IResult> = Result<(I, O), Err>; - -impl ParseError for Error { - fn from_error_kind(input: I, _: ErrorKind) -> Self { - Self::Parser(input) - } - fn append(_: I, _: ErrorKind, other: Self) -> Self { - other - } -} diff --git a/lib/src/sql/error/mod.rs b/lib/src/sql/error/mod.rs new file mode 100644 index 00000000..67a25c21 --- /dev/null +++ b/lib/src/sql/error/mod.rs @@ -0,0 +1,478 @@ +use nom::error::ErrorKind; +use nom::error::FromExternalError; +use nom::error::ParseError as NomParseError; +use nom::Err; +use std::fmt::Write; +use std::num::ParseFloatError; +use std::num::ParseIntError; +use std::ops::Bound; +use thiserror::Error; + +mod utils; +pub use utils::*; +mod render; +pub use render::*; + +#[derive(Error, Debug, Clone)] +pub enum ParseError { + Base(I), + Expected { + tried: I, + expected: &'static str, + }, + Explained { + tried: I, + explained: &'static str, + }, + ExplainedExpected { + tried: I, + explained: &'static str, + expected: &'static str, + }, + MissingDelimiter { + opened: I, + tried: I, + }, + ExcessiveDepth(I), + Field(I, String), + Split(I, String), + Order(I, String), + Group(I, String), + Role(I, String), + ParseInt { + tried: I, + error: ParseIntError, + }, + ParseFloat { + tried: I, + error: ParseFloatError, + }, + ParseDecimal { + tried: I, + error: rust_decimal::Error, + }, + ParseRegex { + tried: I, + error: regex::Error, + }, + RangeError { + tried: I, + lower: Bound, + upper: Bound, + }, + InvalidUnicode { + tried: I, + }, + InvalidPath { + tried: I, + parent: I, + }, +} + +impl ParseError { + /// returns the input value where the parser failed. + pub fn tried(&self) -> I { + let (Self::Base(ref tried) + | Self::Expected { + ref tried, + .. + } + | Self::Expected { + ref tried, + .. + } + | Self::Explained { + ref tried, + .. + } + | Self::ExplainedExpected { + ref tried, + .. + } + | Self::ExcessiveDepth(ref tried) + | Self::MissingDelimiter { + ref tried, + .. + } + | Self::Field(ref tried, _) + | Self::Split(ref tried, _) + | Self::Order(ref tried, _) + | Self::Group(ref tried, _) + | Self::Role(ref tried, _) + | Self::ParseInt { + ref tried, + .. + } + | Self::ParseFloat { + ref tried, + .. + } + | Self::ParseDecimal { + ref tried, + .. + } + | Self::ParseRegex { + ref tried, + .. + } + | Self::RangeError { + ref tried, + .. + } + | Self::InvalidUnicode { + ref tried, + .. + } + | Self::InvalidPath { + ref tried, + .. + }) = self; + tried.clone() + } +} + +/// A location inside a string. +/// +/// Locations are 1 indexed, the first character on the first line being on line 1 column 1. +#[derive(Clone, Copy, Debug)] +pub struct Location { + pub line: usize, + pub column: usize, +} + +impl Location { + /// Returns the location of a substring in the larger string. + pub fn of_in(substr: &str, s: &str) -> Self { + let offset = s + .len() + .checked_sub(substr.len()) + .expect("tried to find location of substring in unrelated string"); + let lines = s.split('\n').enumerate(); + let mut total = 0; + for (idx, line) in lines { + // +1 for the '\n' + let new_total = total + line.len() + 1; + if new_total > offset { + // found line. + let line_offset = offset - total; + let column = line[..line_offset].chars().count(); + // +1 because line and column are 1 index. + return Self { + line: idx + 1, + column: column + 1, + }; + } + total = new_total; + } + unreachable!() + } +} + +impl ParseError<&str> { + /// Returns the error represented as a pretty printed string formatted on the original source + /// text. + pub fn render_on(&self, input: &str) -> RenderedError { + match self { + ParseError::Base(i) => { + let location = Location::of_in(i, input); + let text = format!( + "Failed to parse query at line {} column {}", + location.line, location.column + ); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::Expected { + tried, + expected, + } => { + let location = Location::of_in(tried, input); + // Writing to a string can't return an error. + let text = format!( + "Failed to parse query at line {} column {} expected {}", + location.line, location.column, expected + ); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::Explained { + tried, + explained, + } => { + let location = Location::of_in(tried, input); + // Writing to a string can't return an error. + let text = format!( + "Failed to parse query at line {} column {}", + location.line, location.column + ); + let snippet = Snippet::from_source_location(input, location, Some(*explained)); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::ExplainedExpected { + tried, + expected, + explained, + } => { + let location = Location::of_in(tried, input); + // Writing to a string can't return an error. + let text = format!( + "Failed to parse query at line {} column {} expected {}", + location.line, location.column, expected + ); + let snippet = Snippet::from_source_location(input, location, Some(*explained)); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::InvalidPath { + tried, + parent, + } => { + let location = Location::of_in(tried, input); + // Writing to a string can't return an error. + let text = format!( + "Path is not a member of {parent} at line {} column {}", + location.line, location.column + ); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::MissingDelimiter { + tried, + opened, + } => { + let location = Location::of_in(tried, input); + let text = format!( + "Missing closing delimiter at line {} column {}", + location.line, location.column + ); + let error_snippet = Snippet::from_source_location(input, location, None); + let location = Location::of_in(opened, input); + let open_snippet = Snippet::from_source_location( + input, + location, + Some("expected this delimiter to be closed"), + ); + RenderedError { + text, + snippets: vec![error_snippet, open_snippet], + } + } + ParseError::ExcessiveDepth(tried) => { + let location = Location::of_in(tried, input); + // Writing to a string can't return an error. + let text = format!( + "Exceeded maximum parse depth at line {} column {}", + location.line, location.column + ); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::Field(tried, f) => { + let location = Location::of_in(tried, input); + let text = format!( + "Found '{f}' in SELECT clause at line {} column {}, but field is not an aggregate function, and is not present in GROUP BY expression", + location.line, location.column + ); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::Split(tried, f) => { + let location = Location::of_in(tried, input); + let text = format!( + "Found '{f}' in SPLIT ON clause at line {} column {}, but field is is not present in SELECT expression", + location.line, location.column + ); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::Order(tried, f) => { + let location = Location::of_in(tried, input); + let text = format!( + "Found '{f}' in ORDER BY clause at line {} column {}, but field is is not present in SELECT expression", + location.line, location.column + ); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::Group(tried, f) => { + let location = Location::of_in(tried, input); + let text = format!( + "Found '{f}' in GROUP BY clause at line {} column {}, but field is is not present in SELECT expression", + location.line, location.column + ); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::Role(tried, r) => { + let location = Location::of_in(tried, input); + let text = format!( + "Invalid role '{r}' at line {} column {}.", + location.line, location.column + ); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::ParseInt { + tried, + error, + } => { + let location = Location::of_in(tried, input); + // Writing to a string can't return an error. + let text = format!("Failed to parse '{tried}' as an integer: {error}."); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::ParseFloat { + tried, + error, + } => { + let location = Location::of_in(tried, input); + // Writing to a string can't return an error. + let text = format!("Failed to parse '{tried}' as a float: {error}."); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::ParseDecimal { + tried, + error, + } => { + let location = Location::of_in(tried, input); + // Writing to a string can't return an error. + let text = format!("Failed to parse '{tried}' as decimal: {error}."); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::ParseRegex { + tried, + error, + } => { + let location = Location::of_in(tried, input); + // Writing to a string can't return an error. + let text = format!("Failed to parse '{tried}' as a regex: {error}."); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + + ParseError::RangeError { + tried, + lower, + upper, + } => { + let location = Location::of_in(tried, input); + + let mut text = + format!("Failed to parse '{tried}' as a bounded integer with bounds"); + // Writing to a string can't return an error. + match lower { + Bound::Included(x) => write!(&mut text, "[{}", x).unwrap(), + Bound::Excluded(x) => write!(&mut text, "({}", x).unwrap(), + Bound::Unbounded => {} + } + write!(&mut text, "...").unwrap(); + match upper { + Bound::Included(x) => write!(&mut text, "{}]", x).unwrap(), + Bound::Excluded(x) => write!(&mut text, "{})", x).unwrap(), + Bound::Unbounded => {} + } + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + ParseError::InvalidUnicode { + tried, + } => { + let location = Location::of_in(tried, input); + let text = "Invalid unicode escape code.".to_string(); + let snippet = Snippet::from_source_location(input, location, None); + RenderedError { + text, + snippets: vec![snippet], + } + } + } + } +} + +pub type IResult> = Result<(I, O), Err>; + +impl FromExternalError for ParseError { + fn from_external_error(input: I, _kind: ErrorKind, e: ParseIntError) -> Self { + ParseError::ParseInt { + error: e, + tried: input, + } + } +} + +impl FromExternalError for ParseError { + fn from_external_error(input: I, _kind: ErrorKind, e: ParseFloatError) -> Self { + ParseError::ParseFloat { + error: e, + tried: input, + } + } +} + +impl FromExternalError for ParseError { + fn from_external_error(input: I, _kind: ErrorKind, e: regex::Error) -> Self { + ParseError::ParseRegex { + error: e, + tried: input, + } + } +} + +impl NomParseError for ParseError { + fn from_error_kind(input: I, _: ErrorKind) -> Self { + Self::Base(input) + } + fn append(_: I, _: ErrorKind, other: Self) -> Self { + other + } +} diff --git a/lib/src/sql/error/render.rs b/lib/src/sql/error/render.rs new file mode 100644 index 00000000..253af87b --- /dev/null +++ b/lib/src/sql/error/render.rs @@ -0,0 +1,144 @@ +use std::fmt; + +use super::Location; + +#[derive(Clone, Debug)] +pub struct RenderedError { + pub text: String, + pub snippets: Vec, +} + +impl fmt::Display for RenderedError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "{}", self.text)?; + for s in self.snippets.iter() { + writeln!(f, "{}", s)?; + } + Ok(()) + } +} + +/// Whether the snippet was truncated. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum Truncation { + /// The snippet wasn't truncated + None, + /// The snippet was truncated at the start + Start, + /// The snippet was truncated at the end + End, + /// Both sided of the snippet where truncated. + Both, +} + +/// A piece of the source code with a location and an optional explenation. +#[derive(Clone, Debug)] +pub struct Snippet { + /// The part of the orignal source code, + source: String, + /// Wether part of the source line was truncated. + truncation: Truncation, + /// The location of the snippet in the orignal source code. + location: Location, + /// The offset into the snippet where the location is. + offset: usize, + /// A possible explanation for this snippet. + explain: Option, +} + +impl Snippet { + /// How long with the source line have to be before it gets truncated. + const MAX_SOURCE_DISPLAY_LEN: usize = 80; + /// How far the will have to be in the source line before everything before it gets truncated. + const MAX_ERROR_LINE_OFFSET: usize = 50; + + pub fn from_source_location( + source: &str, + location: Location, + explain: Option<&'static str>, + ) -> Self { + let line = source.split('\n').nth(location.line - 1).unwrap(); + let (line, truncation, offset) = Self::truncate_line(line, location.column); + + Snippet { + source: line.to_owned(), + truncation, + location, + offset, + explain: explain.map(|x| x.into()), + } + } + + /// Trims whitespace of an line and additionally truncates a string if it is too long. + fn truncate_line(mut line: &str, around_offset: usize) -> (&str, Truncation, usize) { + let full_line_length = line.len(); + line = line.trim_start(); + let mut offset = around_offset - (full_line_length - line.len()); + line = line.trim_end(); + let mut truncation = Truncation::None; + + if around_offset > Self::MAX_ERROR_LINE_OFFSET { + // Actual error is to far to the right, just truncated everything to the left. + // show some prefix for some extra context. + let extra_offset = around_offset - 10; + let mut chars = line.chars(); + for _ in 0..extra_offset { + chars.next(); + } + offset -= extra_offset; + line = chars.as_str(); + truncation = Truncation::Start; + } + + if line.chars().count() > Self::MAX_SOURCE_DISPLAY_LEN { + // Line is too long, truncate to source + let mut size = Self::MAX_SOURCE_DISPLAY_LEN - 3; + if truncation == Truncation::Start { + truncation = Truncation::Both; + size -= 3; + } else { + truncation = Truncation::End + } + + // Unwrap because we just checked if the line length is longer then this. + let truncate_index = line.char_indices().nth(size).unwrap().0; + line = &line[..truncate_index]; + } + + (line, truncation, offset) + } +} + +impl fmt::Display for Snippet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // extra spacing for the line number + let spacing = self.location.line.ilog10() as usize + 1; + writeln!(f, "{:>spacing$} |", "")?; + write!(f, "{:>spacing$} | ", self.location.line)?; + match self.truncation { + Truncation::None => { + writeln!(f, "{}", self.source)?; + } + Truncation::Start => { + writeln!(f, "...{}", self.source)?; + } + Truncation::End => { + writeln!(f, "{}...", self.source)?; + } + Truncation::Both => { + writeln!(f, "...{}...", self.source)?; + } + } + let error_offset = self.offset + + if matches!(self.truncation, Truncation::Start | Truncation::Both) { + 3 + } else { + 0 + }; + write!(f, "{:>spacing$} | {:>error_offset$} ", "", "^",)?; + if let Some(ref explain) = self.explain { + write!(f, "{explain}")?; + } + Ok(()) + } +} diff --git a/lib/src/sql/error/utils.rs b/lib/src/sql/error/utils.rs new file mode 100644 index 00000000..0f4ee12a --- /dev/null +++ b/lib/src/sql/error/utils.rs @@ -0,0 +1,134 @@ +use super::{IResult, ParseError}; +use nom::bytes::complete::tag_no_case; +use nom::Err; +use nom::Parser; + +pub fn expected(expect: &'static str, mut parser: P) -> impl FnMut(I) -> IResult +where + P: Parser>, +{ + move |input: I| match parser.parse(input) { + Err(Err::Error(err)) => match err { + ParseError::Base(tried) => Err(Err::Error(ParseError::Expected { + tried, + expected: expect, + })), + ParseError::Explained { + tried, + explained, + } => Err(Err::Error(ParseError::ExplainedExpected { + tried, + expected: expect, + explained, + })), + ParseError::Expected { + tried, + .. + } => Err(Err::Error(ParseError::Expected { + tried, + expected: expect, + })), + x => Err(Err::Error(x)), + }, + Err(Err::Failure(err)) => match err { + ParseError::Base(tried) => Err(Err::Failure(ParseError::Expected { + tried, + expected: expect, + })), + ParseError::Explained { + tried, + explained, + } => Err(Err::Failure(ParseError::ExplainedExpected { + tried, + expected: expect, + explained, + })), + ParseError::Expected { + tried: input, + .. + } => Err(Err::Failure(ParseError::Expected { + tried: input, + expected: expect, + })), + x => Err(Err::Failure(x)), + }, + rest => rest, + } +} + +pub trait ExplainResultExt { + /// A function which adds a explaination to an error if the parser fails at a place which can + /// be parsed with the given parser. + fn explain(self, explain: &'static str, condition: P) -> Self + where + P: Parser>; +} + +impl ExplainResultExt for IResult { + fn explain(self, explain: &'static str, mut condition: P) -> Self + where + P: Parser>, + { + let error = match self { + Ok(x) => return Ok(x), + Err(e) => e, + }; + + let mut was_failure = false; + let error = match error { + Err::Error(e) => e, + Err::Failure(e) => { + was_failure = true; + e + } + Err::Incomplete(e) => return Err(Err::Incomplete(e)), + }; + + let new_error = match error { + ParseError::Base(tried) => { + if condition.parse(tried.clone()).is_ok() { + ParseError::Explained { + tried, + explained: explain, + } + } else { + ParseError::Base(tried) + } + } + ParseError::Expected { + tried, + expected, + } => { + if condition.parse(tried.clone()).is_ok() { + ParseError::ExplainedExpected { + tried, + expected, + explained: explain, + } + } else { + ParseError::Expected { + tried, + expected, + } + } + } + e => e, + }; + + if was_failure { + Err(Err::Failure(new_error)) + } else { + Err(Err::Error(new_error)) + } + } +} + +pub fn expect_tag_no_case(tag: &'static str) -> impl FnMut(&str) -> IResult<&str, &str> { + move |input: &str| match tag_no_case(tag).parse(input) { + Result::Err(_) => Err(Err::Failure(ParseError::Expected { + tried: input, + expected: tag, + })), + rest => rest, + } +} diff --git a/lib/src/sql/expression.rs b/lib/src/sql/expression.rs index 878dc7c5..4d3c60ac 100644 --- a/lib/src/sql/expression.rs +++ b/lib/src/sql/expression.rs @@ -6,7 +6,7 @@ use crate::fnc; use crate::sql::comment::mightbespace; use crate::sql::error::IResult; use crate::sql::operator::{self, Operator}; -use crate::sql::value::{single, value, Value}; +use crate::sql::value::{single, Value}; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt; @@ -225,12 +225,13 @@ pub fn unary(i: &str) -> IResult<&str, Expression> { )) } +#[cfg(test)] pub fn binary(i: &str) -> IResult<&str, Expression> { let (i, l) = single(i)?; let (i, o) = operator::binary(i)?; // Make sure to dive if the query is a right-deep binary tree. - let _diving = crate::sql::parser::depth::dive()?; - let (i, r) = value(i)?; + let _diving = crate::sql::parser::depth::dive(i)?; + let (i, r) = crate::sql::value::value(i)?; let v = match r { Value::Expression(r) => r.augment(l, o), _ => Expression::new(l, o, r), diff --git a/lib/src/sql/function.rs b/lib/src/sql/function.rs index 7ce2942b..fe119f9c 100644 --- a/lib/src/sql/function.rs +++ b/lib/src/sql/function.rs @@ -21,12 +21,13 @@ use nom::bytes::complete::take_while1; use nom::character::complete::char; use nom::combinator::{cut, recognize}; use nom::multi::separated_list1; -use nom::sequence::{preceded, terminated}; +use nom::sequence::terminated; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::fmt; +use super::error::expected; use super::util::delimited_list0; pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Function"; @@ -253,17 +254,16 @@ impl fmt::Display for Function { } } -pub fn function(i: &str) -> IResult<&str, Function> { - alt((normal, custom, script))(i) +pub fn defined_function(i: &str) -> IResult<&str, Function> { + alt((custom, script))(i) } -pub fn normal(i: &str) -> IResult<&str, Function> { - let (i, s) = function_names(i)?; - let (i, a) = - delimited_list0(openparentheses, commas, terminated(cut(value), mightbespace), char(')'))( - i, - )?; - Ok((i, Function::Normal(s.to_string(), a))) +pub fn builtin_function<'a>(name: &'a str, i: &'a str) -> IResult<&'a str, Function> { + let (i, a) = expected( + "function arguments", + delimited_list0(openparentheses, commas, terminated(cut(value), mightbespace), char(')')), + )(i)?; + Ok((i, Function::Normal(name.to_string(), a))) } pub fn custom(i: &str) -> IResult<&str, Function> { @@ -271,11 +271,14 @@ pub fn custom(i: &str) -> IResult<&str, Function> { cut(|i| { let (i, s) = recognize(separated_list1(tag("::"), take_while1(val_char)))(i)?; let (i, _) = mightbespace(i)?; - let (i, a) = delimited_list0( - openparentheses, - commas, - terminated(cut(value), mightbespace), - char(')'), + let (i, a) = expected( + "function arguments", + delimited_list0( + cut(openparentheses), + commas, + terminated(cut(value), mightbespace), + char(')'), + ), )(i)?; Ok((i, Function::Custom(s.to_string(), a))) })(i) @@ -299,389 +302,23 @@ fn script(i: &str) -> IResult<&str, Function> { })(i) } -pub(crate) fn function_names(i: &str) -> IResult<&str, &str> { - recognize(alt(( - alt(( - preceded(tag("array::"), cut(function_array)), - preceded(tag("bytes::"), cut(function_bytes)), - preceded(tag("crypto::"), cut(function_crypto)), - preceded(tag("duration::"), cut(function_duration)), - preceded(tag("encoding::"), cut(function_encoding)), - preceded(tag("geo::"), cut(function_geo)), - preceded(tag("http::"), cut(function_http)), - // Don't cut in time and math for now since there are also constant's with the same - // prefix. - preceded(tag("math::"), function_math), - preceded(tag("meta::"), cut(function_meta)), - preceded(tag("parse::"), cut(function_parse)), - preceded(tag("rand::"), cut(function_rand)), - preceded(tag("search::"), cut(function_search)), - preceded(tag("session::"), cut(function_session)), - preceded(tag("string::"), cut(function_string)), - // Don't cut in time and math for now since there are also constant's with the same - // prefix. - preceded(tag("time::"), function_time), - preceded(tag("type::"), cut(function_type)), - preceded(tag("vector::"), cut(function_vector)), - )), - alt((tag("count"), tag("not"), tag("rand"), tag("sleep"))), - )))(i) -} - -fn function_array(i: &str) -> IResult<&str, &str> { - alt(( - alt(( - tag("add"), - tag("all"), - tag("any"), - tag("append"), - tag("at"), - tag("boolean_and"), - tag("boolean_not"), - tag("boolean_or"), - tag("boolean_xor"), - tag("clump"), - tag("combine"), - tag("complement"), - tag("concat"), - tag("difference"), - tag("distinct"), - tag("filter_index"), - tag("find_index"), - tag("first"), - tag("flatten"), - tag("group"), - tag("insert"), - )), - alt(( - tag("intersect"), - tag("join"), - tag("last"), - tag("len"), - tag("logical_and"), - tag("logical_or"), - tag("logical_xor"), - tag("matches"), - tag("max"), - tag("min"), - tag("pop"), - tag("prepend"), - tag("push"), - )), - alt(( - tag("remove"), - tag("reverse"), - tag("slice"), - tag("sort::asc"), - tag("sort::desc"), - tag("sort"), - tag("transpose"), - tag("union"), - )), - ))(i) -} - -fn function_bytes(i: &str) -> IResult<&str, &str> { - alt((tag("len"),))(i) -} - -fn function_crypto(i: &str) -> IResult<&str, &str> { - alt(( - preceded(tag("argon2::"), alt((tag("compare"), tag("generate")))), - preceded(tag("bcrypt::"), alt((tag("compare"), tag("generate")))), - preceded(tag("pbkdf2::"), alt((tag("compare"), tag("generate")))), - preceded(tag("scrypt::"), alt((tag("compare"), tag("generate")))), - tag("md5"), - tag("sha1"), - tag("sha256"), - tag("sha512"), - ))(i) -} - -fn function_duration(i: &str) -> IResult<&str, &str> { - alt(( - tag("days"), - tag("hours"), - tag("micros"), - tag("millis"), - tag("mins"), - tag("nanos"), - tag("secs"), - tag("weeks"), - tag("years"), - preceded( - tag("from::"), - alt(( - tag("days"), - tag("hours"), - tag("micros"), - tag("millis"), - tag("mins"), - tag("nanos"), - tag("secs"), - tag("weeks"), - )), - ), - ))(i) -} - -fn function_encoding(i: &str) -> IResult<&str, &str> { - alt((preceded(tag("base64::"), alt((tag("decode"), tag("encode")))),))(i) -} - -fn function_geo(i: &str) -> IResult<&str, &str> { - alt(( - tag("area"), - tag("bearing"), - tag("centroid"), - tag("distance"), - preceded(tag("hash::"), alt((tag("decode"), tag("encode")))), - ))(i) -} - -fn function_http(i: &str) -> IResult<&str, &str> { - alt((tag("head"), tag("get"), tag("put"), tag("post"), tag("patch"), tag("delete")))(i) -} - -fn function_math(i: &str) -> IResult<&str, &str> { - alt(( - alt(( - tag("abs"), - tag("bottom"), - tag("ceil"), - tag("fixed"), - tag("floor"), - tag("interquartile"), - tag("max"), - tag("mean"), - tag("median"), - tag("midhinge"), - tag("min"), - tag("mode"), - )), - alt(( - tag("nearestrank"), - tag("percentile"), - tag("pow"), - tag("product"), - tag("round"), - tag("spread"), - tag("sqrt"), - tag("stddev"), - tag("sum"), - tag("top"), - tag("trimean"), - tag("variance"), - )), - ))(i) -} - -fn function_meta(i: &str) -> IResult<&str, &str> { - alt((tag("id"), tag("table"), tag("tb")))(i) -} - -fn function_parse(i: &str) -> IResult<&str, &str> { - alt(( - preceded(tag("email::"), alt((tag("host"), tag("user")))), - preceded( - tag("url::"), - alt(( - tag("domain"), - tag("fragment"), - tag("host"), - tag("path"), - tag("port"), - tag("query"), - tag("scheme"), - )), - ), - ))(i) -} - -fn function_rand(i: &str) -> IResult<&str, &str> { - alt(( - tag("bool"), - tag("enum"), - tag("float"), - tag("guid"), - tag("int"), - tag("string"), - tag("time"), - tag("ulid"), - tag("uuid::v4"), - tag("uuid::v7"), - tag("uuid"), - ))(i) -} - -fn function_search(i: &str) -> IResult<&str, &str> { - alt((tag("score"), tag("highlight"), tag("offsets")))(i) -} - -fn function_session(i: &str) -> IResult<&str, &str> { - alt(( - tag("db"), - tag("id"), - tag("ip"), - tag("ns"), - tag("origin"), - tag("sc"), - tag("sd"), - tag("token"), - ))(i) -} - -fn function_string(i: &str) -> IResult<&str, &str> { - alt(( - tag("concat"), - tag("contains"), - tag("endsWith"), - tag("join"), - tag("len"), - tag("lowercase"), - tag("repeat"), - tag("replace"), - tag("reverse"), - tag("slice"), - tag("slug"), - tag("split"), - tag("startsWith"), - tag("trim"), - tag("uppercase"), - tag("words"), - preceded(tag("distance::"), alt((tag("hamming"), tag("levenshtein")))), - preceded( - tag("is::"), - alt(( - tag("alphanum"), - tag("alpha"), - tag("ascii"), - tag("datetime"), - tag("domain"), - tag("email"), - tag("hexadecimal"), - tag("latitude"), - tag("longitude"), - tag("numeric"), - tag("semver"), - tag("url"), - tag("uuid"), - )), - ), - preceded(tag("similarity::"), alt((tag("fuzzy"), tag("jaro"), tag("smithwaterman")))), - ))(i) -} - -fn function_time(i: &str) -> IResult<&str, &str> { - alt(( - tag("ceil"), - tag("day"), - tag("floor"), - tag("format"), - tag("group"), - tag("hour"), - tag("minute"), - tag("max"), - tag("min"), - tag("month"), - tag("nano"), - tag("now"), - tag("round"), - tag("second"), - tag("timezone"), - tag("unix"), - tag("wday"), - tag("week"), - tag("yday"), - tag("year"), - preceded(tag("from::"), alt((tag("micros"), tag("millis"), tag("secs"), tag("unix")))), - ))(i) -} - -fn function_type(i: &str) -> IResult<&str, &str> { - alt(( - tag("bool"), - tag("datetime"), - tag("decimal"), - tag("duration"), - tag("fields"), - tag("field"), - tag("float"), - tag("int"), - tag("number"), - tag("point"), - tag("string"), - tag("table"), - tag("thing"), - preceded( - tag("is::"), - alt(( - alt(( - tag("array"), - tag("bool"), - tag("bytes"), - tag("collection"), - tag("datetime"), - tag("decimal"), - tag("duration"), - tag("float"), - tag("geometry"), - tag("int"), - tag("line"), - )), - alt(( - tag("null"), - tag("multiline"), - tag("multipoint"), - tag("multipolygon"), - tag("number"), - tag("object"), - tag("point"), - tag("polygon"), - tag("record"), - tag("string"), - tag("uuid"), - )), - )), - ), - ))(i) -} - -fn function_vector(i: &str) -> IResult<&str, &str> { - alt(( - tag("add"), - tag("angle"), - tag("divide"), - tag("cross"), - tag("dot"), - tag("magnitude"), - tag("multiply"), - tag("normalize"), - tag("project"), - tag("subtract"), - preceded( - tag("distance::"), - alt(( - tag("chebyshev"), - tag("euclidean"), - tag("hamming"), - tag("mahalanobis"), - tag("manhattan"), - tag("minkowski"), - )), - ), - preceded( - tag("similarity::"), - alt((tag("cosine"), tag("jaccard"), tag("pearson"), tag("spearman"))), - ), - ))(i) -} - #[cfg(test)] mod tests { - use super::*; - use crate::sql::test::Parse; + use crate::sql::{ + builtin::{builtin_name, BuiltinName}, + test::Parse, + }; + + fn function(i: &str) -> IResult<&str, Function> { + alt((defined_function, |i| { + let (i, name) = builtin_name(i)?; + let BuiltinName::Function(x) = name else { + panic!("not a function") + }; + builtin_function(x, i) + }))(i) + } #[test] fn function_single() { diff --git a/lib/src/sql/future.rs b/lib/src/sql/future.rs index efa8b016..d2333410 100644 --- a/lib/src/sql/future.rs +++ b/lib/src/sql/future.rs @@ -13,6 +13,8 @@ use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt; +use super::util::expect_delimited; + pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Future"; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] @@ -50,9 +52,7 @@ impl fmt::Display for Future { } pub fn future(i: &str) -> IResult<&str, Future> { - let (i, _) = openchevron(i)?; - let (i, _) = tag("future")(i)?; - let (i, _) = closechevron(i)?; + let (i, _) = expect_delimited(openchevron, tag("future"), closechevron)(i)?; cut(|i| { let (i, _) = mightbespace(i)?; let (i, v) = block(i)?; diff --git a/lib/src/sql/geometry.rs b/lib/src/sql/geometry.rs index 74a5d6c4..d9f7fcec 100644 --- a/lib/src/sql/geometry.rs +++ b/lib/src/sql/geometry.rs @@ -627,7 +627,7 @@ impl hash::Hash for Geometry { } pub fn geometry(i: &str) -> IResult<&str, Geometry> { - let _diving = crate::sql::parser::depth::dive()?; + let _diving = crate::sql::parser::depth::dive(i)?; alt((simple, normal))(i) } diff --git a/lib/src/sql/graph.rs b/lib/src/sql/graph.rs index 42d816fd..5143ffcd 100644 --- a/lib/src/sql/graph.rs +++ b/lib/src/sql/graph.rs @@ -19,6 +19,8 @@ use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter, Write}; +use super::util::expect_delimited; + #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] #[revisioned(revision = 1)] pub struct Graph { @@ -107,22 +109,26 @@ fn simple(i: &str) -> IResult<&str, (Tables, Option, Option)> { } fn custom(i: &str) -> IResult<&str, (Tables, Option, Option)> { - let (i, _) = openparentheses(i)?; - let (i, w) = alt((any, tables))(i)?; - let (i, c) = opt(|i| { - let (i, _) = shouldbespace(i)?; - let (i, v) = cond(i)?; - Ok((i, v)) - })(i)?; - let (i, a) = opt(|i| { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("AS")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, v) = idiom(i)?; - Ok((i, v)) - })(i)?; - let (i, _) = closeparentheses(i)?; - Ok((i, (w, c, a))) + expect_delimited( + openparentheses, + |i| { + let (i, w) = alt((any, tables))(i)?; + let (i, c) = opt(|i| { + let (i, _) = shouldbespace(i)?; + let (i, v) = cond(i)?; + Ok((i, v)) + })(i)?; + let (i, a) = opt(|i| { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("AS")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = idiom(i)?; + Ok((i, v)) + })(i)?; + Ok((i, (w, c, a))) + }, + closeparentheses, + )(i) } fn one(i: &str) -> IResult<&str, Tables> { diff --git a/lib/src/sql/ident.rs b/lib/src/sql/ident.rs index bcd8e782..d9a43863 100644 --- a/lib/src/sql/ident.rs +++ b/lib/src/sql/ident.rs @@ -18,6 +18,8 @@ use std::fmt::{self, Display, Formatter}; use std::ops::Deref; use std::str; +use super::error::expected; + const BRACKET_L: char = '⟨'; const BRACKET_R: char = '⟩'; const BRACKET_END_NUL: &str = "⟩\0"; @@ -78,7 +80,7 @@ impl Display for Ident { } pub fn ident(i: &str) -> IResult<&str, Ident> { - let (i, v) = ident_raw(i)?; + let (i, v) = expected("an identifier", ident_raw)(i)?; Ok((i, Ident::from(v))) } diff --git a/lib/src/sql/idiom.rs b/lib/src/sql/idiom.rs index 3b24b4ae..1039d38f 100644 --- a/lib/src/sql/idiom.rs +++ b/lib/src/sql/idiom.rs @@ -21,6 +21,9 @@ use std::fmt::{self, Display, Formatter}; use std::ops::Deref; use std::str; +use super::dir::dir; +use super::error::{expected, ExplainResultExt}; + pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Idiom"; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] @@ -191,31 +194,37 @@ impl Display for Idiom { /// Used in DEFINE FIELD and DEFINE INDEX clauses pub fn local(i: &str) -> IResult<&str, Idiom> { - let (i, p) = first(i)?; - let (i, mut v) = many0(local_part)(i)?; - // Flatten is only allowed at the end - let (i, flat) = opt(flatten)(i)?; - if let Some(p) = flat { - v.push(p); - } - v.insert(0, p); - Ok((i, Idiom::from(v))) + expected("a local idiom", |i| { + let (i, p) = first(i).explain("graphs are not allowed in a local idioms.", dir)?; + let (i, mut v) = many0(local_part)(i)?; + // Flatten is only allowed at the end + let (i, flat) = opt(flatten)(i)?; + if let Some(p) = flat { + v.push(p); + } + v.insert(0, p); + Ok((i, Idiom::from(v))) + })(i) } /// Used in a SPLIT, ORDER, and GROUP clauses pub fn basic(i: &str) -> IResult<&str, Idiom> { - let (i, p) = first(i)?; - let (i, mut v) = many0(basic_part)(i)?; - v.insert(0, p); - Ok((i, Idiom::from(v))) + expected("a basic idiom", |i| { + let (i, p) = first(i).explain("graphs are not allowed in a basic idioms.", dir)?; + let (i, mut v) = many0(basic_part)(i)?; + v.insert(0, p); + Ok((i, Idiom::from(v))) + })(i) } /// A simple idiom with one or more parts pub fn plain(i: &str) -> IResult<&str, Idiom> { - let (i, p) = alt((first, graph))(i)?; - let (i, mut v) = many0(part)(i)?; - v.insert(0, p); - Ok((i, Idiom::from(v))) + expected("a idiom", |i| { + let (i, p) = alt((first, graph))(i)?; + let (i, mut v) = many0(part)(i)?; + v.insert(0, p); + Ok((i, Idiom::from(v))) + })(i) } /// Reparse a value which might part of an idiom. diff --git a/lib/src/sql/kind.rs b/lib/src/sql/kind.rs index edd07374..f61a471b 100644 --- a/lib/src/sql/kind.rs +++ b/lib/src/sql/kind.rs @@ -15,7 +15,7 @@ use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; -use super::util::delimited_list1; +use super::util::{delimited_list1, expect_terminator}; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] #[revisioned(revision = 1)] @@ -140,9 +140,9 @@ fn option(i: &str) -> IResult<&str, Kind> { let (i, _) = tag("option")(i)?; let (i, _) = mightbespace(i)?; cut(|i| { - let (i, _) = char('<')(i)?; + let (i, s) = tag("<")(i)?; let (i, v) = map(alt((either, simple, geometry, record, array, set)), Box::new)(i)?; - let (i, _) = char('>')(i)?; + let (i, _) = expect_terminator(s, char('>'))(i)?; Ok((i, Kind::Option(v))) })(i) } @@ -152,9 +152,9 @@ fn record(i: &str) -> IResult<&str, Kind> { let (i, _) = mightbespace(i)?; let (i, v) = opt(alt((delimited_list1(openparentheses, commas, cut(table), closeparentheses), |i| { - let (i, _) = char('<')(i)?; + let (i, s) = tag("<")(i)?; let (i, v) = separated_list1(verbar, table)(i)?; - let (i, _) = char('>')(i)?; + let (i, _) = expect_terminator(s, char('>'))(i)?; Ok((i, v)) })))(i)?; Ok((i, Kind::Record(v.unwrap_or_default()))) @@ -165,9 +165,9 @@ fn geometry(i: &str) -> IResult<&str, Kind> { let (i, v) = opt(alt((delimited_list1(openparentheses, commas, cut(geo), closeparentheses), |i| { let (i, _) = mightbespace(i)?; - let (i, _) = char('<')(i)?; + let (i, s) = tag("<")(i)?; let (i, v) = separated_list1(verbar, cut(geo))(i)?; - let (i, _) = char('>')(i)?; + let (i, _) = expect_terminator(s, char('>'))(i)?; Ok((i, v)) })))(i)?; Ok((i, Kind::Geometry(v.unwrap_or_default()))) @@ -176,7 +176,7 @@ fn geometry(i: &str) -> IResult<&str, Kind> { fn array(i: &str) -> IResult<&str, Kind> { let (i, _) = tag("array")(i)?; let (i, v) = opt(|i| { - let (i, _) = char('<')(i)?; + let (i, s) = tag("<")(i)?; let (i, _) = mightbespace(i)?; let (i, k) = kind(i)?; let (i, _) = mightbespace(i)?; @@ -187,7 +187,7 @@ fn array(i: &str) -> IResult<&str, Kind> { let (i, _) = mightbespace(i)?; Ok((i, l)) })(i)?; - let (i, _) = char('>')(i)?; + let (i, _) = expect_terminator(s, char('>'))(i)?; Ok((i, (k, l))) })(i)?; Ok(( @@ -202,7 +202,7 @@ fn array(i: &str) -> IResult<&str, Kind> { fn set(i: &str) -> IResult<&str, Kind> { let (i, _) = tag("set")(i)?; let (i, v) = opt(|i| { - let (i, _) = char('<')(i)?; + let (i, s) = tag("<")(i)?; let (i, _) = mightbespace(i)?; let (i, k) = kind(i)?; let (i, _) = mightbespace(i)?; @@ -213,7 +213,7 @@ fn set(i: &str) -> IResult<&str, Kind> { let (i, _) = mightbespace(i)?; Ok((i, l)) })(i)?; - let (i, _) = char('>')(i)?; + let (i, _) = expect_terminator(s, char('>'))(i)?; Ok((i, (k, l))) })(i)?; Ok(( diff --git a/lib/src/sql/mod.rs b/lib/src/sql/mod.rs index 08774add..3cce2d94 100644 --- a/lib/src/sql/mod.rs +++ b/lib/src/sql/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod algorithm; pub(crate) mod array; pub(crate) mod base; pub(crate) mod block; +pub(crate) mod builtin; pub(crate) mod bytes; pub(crate) mod cast; pub(crate) mod changefeed; @@ -93,7 +94,7 @@ pub use self::datetime::Datetime; pub use self::dir::Dir; pub use self::duration::Duration; pub use self::edges::Edges; -pub use self::error::Error; +pub use self::error::ParseError; pub use self::explain::Explain; pub use self::expression::Expression; pub use self::fetch::Fetch; diff --git a/lib/src/sql/number.rs b/lib/src/sql/number.rs index 5f0f331e..3f30eda5 100644 --- a/lib/src/sql/number.rs +++ b/lib/src/sql/number.rs @@ -1,15 +1,14 @@ use super::value::{TryAdd, TryDiv, TryMul, TryNeg, TryPow, TrySub}; use crate::err::Error; use crate::sql::ending::number as ending; -use crate::sql::error::Error::Parser; -use crate::sql::error::IResult; +use crate::sql::error::{IResult, ParseError}; use crate::sql::strand::Strand; use nom::branch::alt; use nom::bytes::complete::tag; use nom::character::complete::i64; use nom::combinator::{opt, value}; use nom::number::complete::recognize_float; -use nom::Err::Failure; +use nom::Err; use revision::revisioned; use rust_decimal::prelude::*; use serde::{Deserialize, Serialize}; @@ -747,9 +746,43 @@ fn not_nan(i: &str) -> IResult<&str, Number> { let (i, suffix) = suffix(i)?; let (i, _) = ending(i)?; let number = match suffix { - Suffix::None => Number::try_from(v).map_err(|_| Failure(Parser(i)))?, - Suffix::Float => Number::from(f64::from_str(v).map_err(|_| Failure(Parser(i)))?), - Suffix::Decimal => Number::from(Decimal::from_str(v).map_err(|_| Failure(Parser(i)))?), + Suffix::None => { + // Manually check for int or float for better parsing errors + if v.contains(['e', 'E', '.']) { + let float = f64::from_str(v) + .map_err(|e| ParseError::ParseFloat { + tried: v, + error: e, + }) + .map_err(Err::Failure)?; + Number::from(float) + } else { + let int = i64::from_str(v) + .map_err(|e| ParseError::ParseInt { + tried: v, + error: e, + }) + .map_err(Err::Failure)?; + Number::from(int) + } + } + Suffix::Float => { + let float = f64::from_str(v) + .map_err(|e| ParseError::ParseFloat { + tried: v, + error: e, + }) + .map_err(Err::Failure)?; + Number::from(float) + } + Suffix::Decimal => Number::from( + Decimal::from_str(v) + .map_err(|e| ParseError::ParseDecimal { + tried: v, + error: e, + }) + .map_err(Err::Failure)?, + ), }; Ok((i, number)) } diff --git a/lib/src/sql/object.rs b/lib/src/sql/object.rs index 69c59a76..7a981518 100644 --- a/lib/src/sql/object.rs +++ b/lib/src/sql/object.rs @@ -3,21 +3,22 @@ use crate::dbs::{Options, Transaction}; use crate::doc::CursorDoc; use crate::err::Error; use crate::sql::comment::mightbespace; -use crate::sql::common::openbraces; +use crate::sql::common::{closebraces, openbraces}; use crate::sql::common::{commas, val_char}; -use crate::sql::error::IResult; +use crate::sql::error::{expected, IResult}; use crate::sql::escape::escape_key; use crate::sql::fmt::{is_pretty, pretty_indent, Fmt, Pretty}; use crate::sql::operation::Operation; use crate::sql::thing::Thing; -use crate::sql::util::delimited_list0; +use crate::sql::util::expect_terminator; use crate::sql::value::{value, Value}; use nom::branch::alt; use nom::bytes::complete::is_not; use nom::bytes::complete::take_while1; use nom::character::complete::char; -use nom::combinator::cut; -use nom::sequence::{delimited, terminated}; +use nom::combinator::{cut, opt}; +use nom::sequence::delimited; +use nom::Err; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -325,15 +326,38 @@ pub fn object(i: &str) -> IResult<&str, Object> { fn entry(i: &str) -> IResult<&str, (String, Value)> { let (i, k) = key(i)?; let (i, _) = mightbespace(i)?; - let (i, _) = char(':')(i)?; + let (i, _) = expected("`:`", char(':'))(i)?; let (i, _) = mightbespace(i)?; let (i, v) = cut(value)(i)?; Ok((i, (String::from(k), v))) } - let (i, v) = - delimited_list0(openbraces, commas, terminated(entry, mightbespace), char('}'))(i)?; - Ok((i, Object(v.into_iter().collect()))) + let start = i; + let (i, _) = openbraces(i)?; + let (i, first) = match entry(i) { + Ok(x) => x, + Err(Err::Error(_)) => { + let (i, _) = closebraces(i)?; + return Ok((i, Object(BTreeMap::new()))); + } + Err(Err::Failure(x)) => return Err(Err::Failure(x)), + Err(Err::Incomplete(x)) => return Err(Err::Incomplete(x)), + }; + + let mut tree = BTreeMap::new(); + tree.insert(first.0, first.1); + + let mut input = i; + while let (i, Some(_)) = opt(commas)(input)? { + if let (i, Some(_)) = opt(closebraces)(i)? { + return Ok((i, Object(tree))); + } + let (i, v) = cut(entry)(i)?; + tree.insert(v.0, v.1); + input = i + } + let (i, _) = expect_terminator(start, closebraces)(input)?; + Ok((i, Object(tree))) } pub fn key(i: &str) -> IResult<&str, &str> { diff --git a/lib/src/sql/omit.rs b/lib/src/sql/omit.rs index 54281071..f549e731 100644 --- a/lib/src/sql/omit.rs +++ b/lib/src/sql/omit.rs @@ -2,11 +2,12 @@ use crate::sql::comment::shouldbespace; use crate::sql::error::IResult; use crate::sql::idiom::{locals as idioms, Idioms}; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; pub fn omit(i: &str) -> IResult<&str, Idioms> { let (i, _) = tag_no_case("OMIT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = idioms(i)?; + let (i, v) = cut(idioms)(i)?; Ok((i, v)) } diff --git a/lib/src/sql/parser.rs b/lib/src/sql/parser.rs index 9a7ec27b..09807ae6 100644 --- a/lib/src/sql/parser.rs +++ b/lib/src/sql/parser.rs @@ -1,13 +1,11 @@ use crate::err::Error; -use crate::iam::Error as IamError; -use crate::sql::error::Error::{ExcessiveDepth, Field, Group, Order, Parser, Role, Split}; use crate::sql::error::IResult; use crate::sql::idiom::Idiom; use crate::sql::query::{query, Query}; use crate::sql::subquery::Subquery; use crate::sql::thing::Thing; use crate::sql::value::Value; -use nom::Err; +use nom::Finish; use std::str; use tracing::instrument; @@ -65,82 +63,20 @@ fn parse_impl(input: &str, parser: impl Fn(&str) -> IResult<&str, O>) -> Resu // The input query was empty 0 => Err(Error::QueryEmpty), // Continue parsing the query - _ => match parser(input) { + _ => match parser(input).finish() { // The query was parsed successfully Ok((v, parsed)) if v.is_empty() => Ok(parsed), // There was unparsed SQL remaining Ok((_, _)) => Err(Error::QueryRemaining), // There was an error when parsing the query - Err(Err::Error(e)) | Err(Err::Failure(e)) => Err(match e { - // There was a parsing error - Parser(e) => { - // Locate the parser position - let (s, l, c) = locate(input, e); - // Return the parser error - Error::InvalidQuery { - line: l, - char: c, - sql: s.to_string(), - } - } - // There was a parsing error - ExcessiveDepth => Error::ComputationDepthExceeded, - // There was a SPLIT ON error - Field(e, f) => Error::InvalidField { - line: locate(input, e).1, - field: f, - }, - // There was a SPLIT ON error - Split(e, f) => Error::InvalidSplit { - line: locate(input, e).1, - field: f, - }, - // There was a ORDER BY error - Order(e, f) => Error::InvalidOrder { - line: locate(input, e).1, - field: f, - }, - // There was a GROUP BY error - Group(e, f) => Error::InvalidGroup { - line: locate(input, e).1, - field: f, - }, - // There was an error parsing the ROLE - Role(_, role) => Error::IamError(IamError::InvalidRole(role)), - }), - _ => unreachable!(), + Err(e) => Err(Error::InvalidQuery(e.render_on(input))), }, } } -fn truncate(s: &str, l: usize) -> &str { - // TODO: use s.floor_char_boundary once https://github.com/rust-lang/rust/issues/93743 lands - match s.char_indices().nth(l) { - None => s, - Some((i, _)) => &s[..i], - } -} - -fn locate<'a>(input: &str, tried: &'a str) -> (&'a str, usize, usize) { - let index = input.len() - tried.len(); - let tried = truncate(tried, 100); - let lines = input.split('\n').map(|l| l.len()).enumerate(); - let (mut total, mut chars) = (0, 0); - for (line, size) in lines { - total += size + 1; - if index < total { - let line_num = line + 1; - let char_num = index - chars; - return (tried, line_num, char_num); - } - chars += size + 1; - } - (tried, 0, 0) -} - pub(crate) mod depth { use crate::cnf::MAX_COMPUTATION_DEPTH; - use crate::sql::Error::ExcessiveDepth; + use crate::sql::ParseError; use nom::Err; use std::cell::Cell; use std::thread::panicking; @@ -170,14 +106,14 @@ pub(crate) mod depth { /// Call at least once in recursive parsing code paths to limit recursion depth. #[inline(never)] #[must_use = "must store and implicitly drop when returning"] - pub(crate) fn dive() -> Result>> { + pub(crate) fn dive(position: I) -> Result>> { DEPTH.with(|cell| { let depth = cell.get().saturating_add(DEPTH_PER_DIVE); if depth <= *MAX_COMPUTATION_DEPTH { cell.replace(depth); Ok(Diving) } else { - Err(Err::Failure(ExcessiveDepth)) + Err(Err::Failure(ParseError::ExcessiveDepth(position))) } }) } @@ -422,6 +358,8 @@ mod tests { n: usize, excessive: bool, ) { + use crate::sql::error::ParseError; + let mut sql = String::from(prefix); for _ in 0..n { sql.push_str(recursive_start); @@ -431,11 +369,11 @@ mod tests { sql.push_str(recursive_end); } let start = Instant::now(); - let res = parse(&sql); + let res = query(&sql).finish(); let elapsed = start.elapsed(); if excessive { assert!( - matches!(res, Err(Error::ComputationDepthExceeded)), + matches!(res, Err(ParseError::ExcessiveDepth(_))), "expected computation depth exceeded, got {:?}", res ); diff --git a/lib/src/sql/part.rs b/lib/src/sql/part.rs index 706db7c0..8f118aaf 100644 --- a/lib/src/sql/part.rs +++ b/lib/src/sql/part.rs @@ -1,5 +1,4 @@ use crate::sql::comment::shouldbespace; -use crate::sql::common::closebracket; use crate::sql::ending::ident as ending; use crate::sql::error::IResult; use crate::sql::fmt::Fmt; @@ -13,7 +12,6 @@ use crate::sql::value::{self, Value}; use nom::branch::alt; use nom::bytes::complete::tag; use nom::bytes::complete::tag_no_case; -use nom::character::complete::char; use nom::combinator::{self, cut, map, not, peek}; use nom::sequence::{preceded, terminated}; use revision::revisioned; @@ -22,6 +20,9 @@ use std::fmt; use std::str; use super::comment::mightbespace; +use super::common::{closebracket, openbracket}; +use super::error::{expected, ExplainResultExt}; +use super::util::expect_delimited; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] #[revisioned(revision = 1)] @@ -149,7 +150,7 @@ pub fn part(i: &str) -> IResult<&str, Part> { alt(( flatten, preceded(tag("."), cut(dot_part)), - preceded(char('['), cut(bracketed_part)), + expect_delimited(openbracket, cut(bracketed_part), closebracket), graph, ))(i) } @@ -164,11 +165,30 @@ pub fn flatten(i: &str) -> IResult<&str, Part> { pub fn local_part(i: &str) -> IResult<&str, Part> { // Cant cut dot part since it might be part of the flatten at the end. - alt((preceded(tag("."), dot_part), preceded(tag("["), cut(local_bracketed_part))))(i) + alt(( + preceded(tag("."), dot_part), + expect_delimited(openbracket, cut(local_bracketed_part), closebracket), + // TODO explain + ))(i) } pub fn basic_part(i: &str) -> IResult<&str, Part> { - alt((preceded(tag("."), cut(dot_part)), preceded(tag("["), cut(basic_bracketed_part))))(i) + alt(( + preceded( + tag("."), + cut(|i| dot_part(i).explain("flattening is not allowed with a basic idiom", tag(".."))), + ), + |s| { + let (i, _) = openbracket(s)?; + let (i, v) = expected( + "$, * or a number", + cut(terminated(basic_bracketed_part, closebracket)), + )(i) + .explain("basic idioms don't allow computed values", bracketed_value) + .explain("basic idioms don't allow where selectors", bracketed_where)?; + Ok((i, v)) + }, + ))(i) } fn dot_part(i: &str) -> IResult<&str, Part> { @@ -180,26 +200,22 @@ fn dot_part(i: &str) -> IResult<&str, Part> { fn basic_bracketed_part(i: &str) -> IResult<&str, Part> { alt(( - combinator::value(Part::All, terminated(tag("*"), cut(closebracket))), - // Can cut here since it can't be a parameter. - combinator::value(Part::All, terminated(tag("$"), cut(closebracket))), - map(terminated(number, cut(closebracket)), Part::Index), + combinator::value(Part::All, tag("*")), + combinator::value(Part::Last, tag("$")), + map(number, Part::Index), ))(i) } fn local_bracketed_part(i: &str) -> IResult<&str, Part> { - alt(( - combinator::value(Part::All, terminated(tag("*"), cut(closebracket))), - map(terminated(number, cut(closebracket)), Part::Index), - ))(i) + alt((combinator::value(Part::All, tag("*")), map(number, Part::Index)))(i) + .explain("using `[$]` in a local idiom is not allowed", tag("$")) } fn bracketed_part(i: &str) -> IResult<&str, Part> { alt(( - combinator::value(Part::All, terminated(tag("*"), cut(closebracket))), - // Don't cut here, the '$' could be part of a param. - combinator::value(Part::Last, terminated(tag("$"), closebracket)), - map(terminated(number, cut(closebracket)), Part::Index), + combinator::value(Part::All, tag("*")), + combinator::value(Part::Last, terminated(tag("$"), peek(closebracket))), + map(number, Part::Index), bracketed_where, bracketed_value, ))(i) @@ -219,8 +235,6 @@ pub fn bracketed_where(i: &str) -> IResult<&str, Part> { ))(i)?; let (i, v) = value::value(i)?; - - let (i, _) = cut(closebracket)(i)?; Ok((i, Part::Where(v))) } @@ -230,7 +244,6 @@ pub fn bracketed_value(i: &str) -> IResult<&str, Part> { map(param::param, Value::Param), map(idiom::basic, Value::Idiom), ))(i)?; - let (i, _) = cut(closebracket)(i)?; Ok((i, Part::Value(v))) } diff --git a/lib/src/sql/permission.rs b/lib/src/sql/permission.rs index 93a3a093..91e8f6f2 100644 --- a/lib/src/sql/permission.rs +++ b/lib/src/sql/permission.rs @@ -19,6 +19,8 @@ use std::fmt::Write; use std::fmt::{self, Display, Formatter}; use std::str; +use super::error::expected; + #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] #[revisioned(revision = 1)] pub struct Permissions { @@ -236,13 +238,16 @@ impl Display for Permission { } pub fn permission(i: &str) -> IResult<&str, Permission> { - alt(( - combinator::value(Permission::None, tag_no_case("NONE")), - combinator::value(Permission::Full, tag_no_case("FULL")), - map(tuple((tag_no_case("WHERE"), shouldbespace, value)), |(_, _, v)| { - Permission::Specific(v) - }), - ))(i) + expected( + "a permission", + alt(( + combinator::value(Permission::None, tag_no_case("NONE")), + combinator::value(Permission::Full, tag_no_case("FULL")), + map(tuple((tag_no_case("WHERE"), shouldbespace, value)), |(_, _, v)| { + Permission::Specific(v) + }), + )), + )(i) } fn rule(i: &str) -> IResult<&str, Vec<(PermissionKind, Permission)>> { diff --git a/lib/src/sql/query.rs b/lib/src/sql/query.rs index 21aad30b..986c8ef1 100644 --- a/lib/src/sql/query.rs +++ b/lib/src/sql/query.rs @@ -1,9 +1,9 @@ -use crate::sql::error::IResult; +use crate::sql::error::{IResult, ParseError}; use crate::sql::fmt::Pretty; use crate::sql::statement::{statements, Statement, Statements}; use crate::sql::Value; use derive::Store; -use nom::combinator::all_consuming; +use nom::Err; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::Write; @@ -46,7 +46,14 @@ impl Display for Query { } pub fn query(i: &str) -> IResult<&str, Query> { - let (i, v) = all_consuming(statements)(i)?; + let (i, v) = statements(i)?; + if !i.is_empty() { + return Err(Err::Failure(ParseError::ExplainedExpected { + tried: i, + expected: "query to end", + explained: "perhaps missing a semicolon on the previous statement?", + })); + } Ok((i, Query(v))) } diff --git a/lib/src/sql/regex.rs b/lib/src/sql/regex.rs index f2a766ef..4166b68d 100644 --- a/lib/src/sql/regex.rs +++ b/lib/src/sql/regex.rs @@ -134,7 +134,7 @@ pub fn regex(i: &str) -> IResult<&str, Regex> { let (i, _) = char('/')(i)?; let (i, v) = escaped(is_not("\\/"), '\\', anychar)(i)?; let (i, _) = char('/')(i)?; - let regex = v.parse().map_err(|_| nom::Err::Error(crate::sql::Error::Parser(v)))?; + let regex = v.parse().map_err(|_| nom::Err::Error(crate::sql::ParseError::Base(v)))?; Ok((i, regex)) } diff --git a/lib/src/sql/result.rs b/lib/src/sql/result.rs deleted file mode 100644 index 8c0a2e5a..00000000 --- a/lib/src/sql/result.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::fmt; - -use nom::{error::ParseError, Err, Parser}; - -use super::error::IResult; - -#[derive(Debug)] -pub enum Error { - /// An error because a keyword - Keyword{ - input: I, - kind: nom::error::ErrorKind, - }, - Base { - kind: nom::error::ErrorKind, - input: I, - }, -} - -pub fn cut_keyword( - mut keyword: K, - mut parser: P, -) -> impl FnMut(I) -> IResult> -where - K: Parser>, - P: Parser>, - I: Clone, -{ - move |input: I| { - let (input, keyword) = keyword.parse(input)?; - - match parser.parse(input) { - Err(Err::Error(Error::Base { - kind, - input, - })) => Err(Err::Failure(Error::KeywordError { - keyword, - input, - kind, - })), - x => x, - } - } -} - -pub fn recover_keyword( - mut parser: P, - mut with: W, -) -> impl FnMut(I) -> IResult> -where - I: Clone, - P: Parser>, - W: Parser>, -{ - move |input: I| match parser.parse(input.clone()) { - Err(Err::Failure(Error::KeywordError { - keyword, - failure_input, - kind, - })) => { - match parser.parse() - } - x => x, - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "todo") - } -} - -impl ParseError for Error { - fn from_error_kind(input: I, kind: nom::error::ErrorKind) -> Self { - Error::Base { - input, - kind, - } - } - - fn append(input: I, kind: nom::error::ErrorKind, other: Self) -> Self { - other - } -} diff --git a/lib/src/sql/scoring.rs b/lib/src/sql/scoring.rs index 2f8537b9..2c18b6b5 100644 --- a/lib/src/sql/scoring.rs +++ b/lib/src/sql/scoring.rs @@ -1,16 +1,16 @@ use crate::sql::common::{closeparentheses, commas, openparentheses}; use crate::sql::error::IResult; -use crate::sql::Error::Parser; use nom::branch::alt; use nom::bytes::complete::tag_no_case; -use nom::combinator::{cut, value}; +use nom::combinator::{cut, map_res, value}; use nom::number::complete::recognize_float; -use nom::Err::Failure; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt; use std::hash::{Hash, Hasher}; +use super::util::expect_delimited; + #[derive(Clone, Debug, PartialOrd, Serialize, Deserialize)] #[revisioned(revision = 1)] pub enum Scoring { @@ -83,22 +83,22 @@ pub fn scoring(i: &str) -> IResult<&str, Scoring> { value(Scoring::Vs, tag_no_case("VS")), |i| { let (i, _) = tag_no_case("BM25")(i)?; - let (i, _) = openparentheses(i)?; - cut(|i| { - let (i, k1): (&str, &str) = recognize_float(i)?; - let k1 = k1.parse::().map_err(|_| Failure(Parser(i)))?; - let (i, _) = commas(i)?; - let (i, b) = recognize_float(i)?; - let b = b.parse::().map_err(|_| Failure(Parser(i)))?; - let (i, _) = closeparentheses(i)?; - Ok(( - i, - Scoring::Bm { - k1, - b, - }, - )) - })(i) + expect_delimited( + openparentheses, + |i| { + let (i, k1) = cut(map_res(recognize_float, |x: &str| x.parse::()))(i)?; + let (i, _) = cut(commas)(i)?; + let (i, b) = cut(map_res(recognize_float, |x: &str| x.parse::()))(i)?; + Ok(( + i, + Scoring::Bm { + k1, + b, + }, + )) + }, + closeparentheses, + )(i) }, value(Scoring::bm25(), tag_no_case("BM25")), ))(i) diff --git a/lib/src/sql/script.rs b/lib/src/sql/script.rs index 260abad7..d1a4bc52 100644 --- a/lib/src/sql/script.rs +++ b/lib/src/sql/script.rs @@ -60,7 +60,7 @@ pub fn script(i: &str) -> IResult<&str, Script> { } fn script_raw(i: &str) -> IResult<&str, &str> { - let _diving = crate::sql::parser::depth::dive()?; + let _diving = crate::sql::parser::depth::dive(i)?; recognize(many0(alt(( script_comment, script_object, diff --git a/lib/src/sql/special.rs b/lib/src/sql/special.rs index 1bdc9c06..19217a33 100644 --- a/lib/src/sql/special.rs +++ b/lib/src/sql/special.rs @@ -1,4 +1,4 @@ -use crate::sql::error::Error; +use crate::sql::error::ParseError; use crate::sql::field::{Field, Fields}; use crate::sql::group::Groups; use crate::sql::order::Orders; @@ -40,14 +40,14 @@ pub fn check_split_on_fields<'a>( i: &'a str, fields: &Fields, splits: &Option, -) -> Result<(), Err>> { +) -> Result<(), Err>> { // Check to see if a SPLIT ON clause has been defined if let Some(splits) = splits { // Loop over each of the expressions in the SPLIT ON clause for split in splits.iter() { if !contains_idiom(fields, &split.0) { // If the expression isn't specified in the SELECT clause, then error - return Err(Failure(Error::Split(i, split.to_string()))); + return Err(Failure(ParseError::Split(i, split.to_string()))); } } } @@ -59,14 +59,14 @@ pub fn check_order_by_fields<'a>( i: &'a str, fields: &Fields, orders: &Option, -) -> Result<(), Err>> { +) -> Result<(), Err>> { // Check to see if a ORDER BY clause has been defined if let Some(orders) = orders { // Loop over each of the expressions in the ORDER BY clause for order in orders.iter() { if !contains_idiom(fields, order) { // If the expression isn't specified in the SELECT clause, then error - return Err(Failure(Error::Order(i, order.to_string()))); + return Err(Failure(ParseError::Order(i, order.to_string()))); } } } @@ -78,14 +78,14 @@ pub fn check_group_by_fields<'a>( i: &'a str, fields: &Fields, groups: &Option, -) -> Result<(), Err>> { +) -> Result<(), Err>> { // Check to see if a GROUP BY clause has been defined if let Some(groups) = groups { // Loop over each of the expressions in the GROUP BY clause for group in groups.iter() { if !contains_idiom(fields, &group.0) { // If the expression isn't specified in the SELECT clause, then error - return Err(Failure(Error::Group(i, group.to_string()))); + return Err(Failure(ParseError::Group(i, group.to_string()))); } } // Check if this is a GROUP ALL clause or a GROUP BY clause @@ -120,7 +120,7 @@ pub fn check_group_by_fields<'a>( } } // If the expression isn't an aggregate function and isn't specified in the GROUP BY clause, then error - return Err(Failure(Error::Field(i, field.to_string()))); + return Err(Failure(ParseError::Field(i, field.to_string()))); } } } diff --git a/lib/src/sql/split.rs b/lib/src/sql/split.rs index af68a09c..181f6152 100644 --- a/lib/src/sql/split.rs +++ b/lib/src/sql/split.rs @@ -57,11 +57,9 @@ impl Display for Split { pub fn split(i: &str) -> IResult<&str, Splits> { let (i, _) = tag_no_case("SPLIT")(i)?; let (i, _) = shouldbespace(i)?; - cut(|i| { - let (i, _) = opt(terminated(tag_no_case("ON"), shouldbespace))(i)?; - let (i, v) = separated_list1(commas, split_raw)(i)?; - Ok((i, Splits(v))) - })(i) + let (i, _) = opt(terminated(tag_no_case("ON"), shouldbespace))(i)?; + let (i, v) = cut(separated_list1(commas, split_raw))(i)?; + Ok((i, Splits(v))) } fn split_raw(i: &str) -> IResult<&str, Split> { diff --git a/lib/src/sql/statements/create.rs b/lib/src/sql/statements/create.rs index f0bf0070..4d2df5ea 100644 --- a/lib/src/sql/statements/create.rs +++ b/lib/src/sql/statements/create.rs @@ -70,7 +70,7 @@ impl CreateStatement { // This is a single record result Value::Array(mut a) if self.only => match a.len() { // There was exactly one result - v if v == 1 => Ok(a.remove(0)), + 1 => Ok(a.remove(0)), // There were no results _ => Err(Error::SingleOnlyOutput), }, diff --git a/lib/src/sql/statements/define/analyzer.rs b/lib/src/sql/statements/define/analyzer.rs index fa86e170..bcd882f9 100644 --- a/lib/src/sql/statements/define/analyzer.rs +++ b/lib/src/sql/statements/define/analyzer.rs @@ -7,6 +7,8 @@ use crate::iam::Action; use crate::iam::ResourceKind; use crate::sql::base::Base; use crate::sql::comment::shouldbespace; +use crate::sql::ending; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::filter::{filters, Filter}; use crate::sql::ident::{ident, Ident}; @@ -16,6 +18,7 @@ use crate::sql::value::Value; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use nom::multi::many0; use revision::revisioned; use serde::{Deserialize, Serialize}; @@ -75,12 +78,11 @@ impl Display for DefineAnalyzerStatement { } pub fn analyzer(i: &str) -> IResult<&str, DefineAnalyzerStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("ANALYZER")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + let (i, name) = cut(ident)(i)?; let (i, opts) = many0(analyzer_opts)(i)?; + let (i, _) = expected("one of FILTERS, TOKENIZERS, or COMMENT", ending::query)(i)?; // Create the base statement let mut res = DefineAnalyzerStatement { name, @@ -118,7 +120,7 @@ fn analyzer_comment(i: &str) -> IResult<&str, DefineAnalyzerOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("COMMENT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = strand(i)?; + let (i, v) = cut(strand)(i)?; Ok((i, DefineAnalyzerOption::Comment(v))) } @@ -126,7 +128,7 @@ fn analyzer_filters(i: &str) -> IResult<&str, DefineAnalyzerOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("FILTERS")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = filters(i)?; + let (i, v) = cut(filters)(i)?; Ok((i, DefineAnalyzerOption::Filters(v))) } @@ -134,6 +136,6 @@ fn analyzer_tokenizers(i: &str) -> IResult<&str, DefineAnalyzerOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("TOKENIZERS")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = tokenizers(i)?; + let (i, v) = cut(tokenizers)(i)?; Ok((i, DefineAnalyzerOption::Tokenizers(v))) } diff --git a/lib/src/sql/statements/define/database.rs b/lib/src/sql/statements/define/database.rs index 5a445c05..69a5cbcc 100644 --- a/lib/src/sql/statements/define/database.rs +++ b/lib/src/sql/statements/define/database.rs @@ -8,6 +8,8 @@ use crate::iam::ResourceKind; use crate::sql::base::Base; use crate::sql::changefeed::{changefeed, ChangeFeed}; use crate::sql::comment::shouldbespace; +use crate::sql::ending; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::ident::{ident, Ident}; use crate::sql::strand::{strand, Strand}; @@ -15,6 +17,7 @@ use crate::sql::value::Value; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use nom::multi::many0; use revision::revisioned; use serde::{Deserialize, Serialize}; @@ -76,12 +79,12 @@ impl Display for DefineDatabaseStatement { } pub fn database(i: &str) -> IResult<&str, DefineDatabaseStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = alt((tag_no_case("DB"), tag_no_case("DATABASE")))(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + let (i, name) = cut(ident)(i)?; let (i, opts) = many0(database_opts)(i)?; + let (i, _) = expected("COMMENT or CHANGEFEED", ending::query)(i)?; + // Create the base statement let mut res = DefineDatabaseStatement { name, @@ -115,7 +118,7 @@ fn database_comment(i: &str) -> IResult<&str, DefineDatabaseOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("COMMENT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = strand(i)?; + let (i, v) = cut(strand)(i)?; Ok((i, DefineDatabaseOption::Comment(v))) } @@ -132,10 +135,10 @@ mod tests { #[test] fn define_database_with_changefeed() { - let sql = "DEFINE DATABASE mydatabase CHANGEFEED 1h"; + let sql = "DATABASE mydatabase CHANGEFEED 1h"; let res = database(sql); let out = res.unwrap().1; - assert_eq!(sql, format!("{}", out)); + assert_eq!(format!("DEFINE {sql}"), format!("{}", out)); let serialized: Vec = (&out).try_into().unwrap(); let deserialized = DefineDatabaseStatement::try_from(&serialized).unwrap(); diff --git a/lib/src/sql/statements/define/event.rs b/lib/src/sql/statements/define/event.rs index 4b6ab4bb..477bacda 100644 --- a/lib/src/sql/statements/define/event.rs +++ b/lib/src/sql/statements/define/event.rs @@ -7,6 +7,9 @@ use crate::iam::Action; use crate::iam::ResourceKind; use crate::sql::base::Base; use crate::sql::comment::shouldbespace; +use crate::sql::ending; +use crate::sql::error::expect_tag_no_case; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::ident::{ident, Ident}; use crate::sql::strand::{strand, Strand}; @@ -14,6 +17,7 @@ use crate::sql::value::{value, values, Value, Values}; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use nom::combinator::opt; use nom::multi::many0; use nom::sequence::tuple; @@ -75,17 +79,19 @@ impl Display for DefineEventStatement { } pub fn event(i: &str) -> IResult<&str, DefineEventStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("EVENT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, what) = ident(i)?; - let (i, opts) = many0(event_opts)(i)?; + let (i, (name, what, opts)) = cut(|i| { + let (i, name) = ident(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = expect_tag_no_case("ON")(i)?; + let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, what) = ident(i)?; + let (i, opts) = many0(event_opts)(i)?; + let (i, _) = expected("WHEN, THEN, or COMMENT", ending::query)(i)?; + Ok((i, (name, what, opts))) + })(i)?; // Create the base statement let mut res = DefineEventStatement { name, @@ -129,7 +135,7 @@ fn event_when(i: &str) -> IResult<&str, DefineEventOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("WHEN")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = value(i)?; + let (i, v) = cut(value)(i)?; Ok((i, DefineEventOption::When(v))) } @@ -137,7 +143,7 @@ fn event_then(i: &str) -> IResult<&str, DefineEventOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("THEN")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = values(i)?; + let (i, v) = cut(values)(i)?; Ok((i, DefineEventOption::Then(v))) } @@ -145,6 +151,6 @@ fn event_comment(i: &str) -> IResult<&str, DefineEventOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("COMMENT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = strand(i)?; + let (i, v) = cut(strand)(i)?; Ok((i, DefineEventOption::Comment(v))) } diff --git a/lib/src/sql/statements/define/field.rs b/lib/src/sql/statements/define/field.rs index c6bb3ad2..e9014aaf 100644 --- a/lib/src/sql/statements/define/field.rs +++ b/lib/src/sql/statements/define/field.rs @@ -7,6 +7,9 @@ use crate::iam::Action; use crate::iam::ResourceKind; use crate::sql::base::Base; use crate::sql::comment::shouldbespace; +use crate::sql::ending; +use crate::sql::error::expect_tag_no_case; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::fmt::is_pretty; use crate::sql::fmt::pretty_indent; @@ -20,6 +23,7 @@ use crate::sql::value::{value, Value}; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use nom::combinator::opt; use nom::multi::many0; use nom::sequence::tuple; @@ -103,17 +107,22 @@ impl Display for DefineFieldStatement { } pub fn field(i: &str) -> IResult<&str, DefineFieldStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("FIELD")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = idiom::local(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, what) = ident(i)?; - let (i, opts) = many0(field_opts)(i)?; + let (i, (name, what, opts)) = cut(|i| { + let (i, name) = idiom::local(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = expect_tag_no_case("ON")(i)?; + let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, what) = ident(i)?; + let (i, opts) = many0(field_opts)(i)?; + let (i, _) = expected( + "one of FLEX(IBLE), TYPE, VALUE, ASSERT, DEFAULT, or COMMENT", + cut(ending::query), + )(i)?; + Ok((i, (name, what, opts))) + })(i)?; // Create the base statement let mut res = DefineFieldStatement { name, @@ -182,7 +191,7 @@ fn field_kind(i: &str) -> IResult<&str, DefineFieldOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("TYPE")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = kind(i)?; + let (i, v) = cut(kind)(i)?; Ok((i, DefineFieldOption::Kind(v))) } @@ -190,7 +199,7 @@ fn field_value(i: &str) -> IResult<&str, DefineFieldOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("VALUE")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = value(i)?; + let (i, v) = cut(value)(i)?; Ok((i, DefineFieldOption::Value(v))) } @@ -198,7 +207,7 @@ fn field_assert(i: &str) -> IResult<&str, DefineFieldOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("ASSERT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = value(i)?; + let (i, v) = cut(value)(i)?; Ok((i, DefineFieldOption::Assert(v))) } @@ -206,7 +215,7 @@ fn field_default(i: &str) -> IResult<&str, DefineFieldOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("DEFAULT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = value(i)?; + let (i, v) = cut(value)(i)?; Ok((i, DefineFieldOption::Default(v))) } diff --git a/lib/src/sql/statements/define/function.rs b/lib/src/sql/statements/define/function.rs index f993efa0..9941b3e5 100644 --- a/lib/src/sql/statements/define/function.rs +++ b/lib/src/sql/statements/define/function.rs @@ -8,7 +8,11 @@ use crate::iam::ResourceKind; use crate::sql::base::Base; use crate::sql::block::{block, Block}; use crate::sql::comment::{mightbespace, shouldbespace}; +use crate::sql::common::closeparentheses; use crate::sql::common::commas; +use crate::sql::common::openparentheses; +use crate::sql::ending; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::fmt::is_pretty; use crate::sql::fmt::pretty_indent; @@ -17,14 +21,15 @@ use crate::sql::ident::{ident, Ident}; use crate::sql::kind::{kind, Kind}; use crate::sql::permission::{permission, Permission}; use crate::sql::strand::{strand, Strand}; +use crate::sql::util::delimited_list0; use crate::sql::value::Value; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag; use nom::bytes::complete::tag_no_case; use nom::character::complete::char; +use nom::combinator::cut; use nom::multi::many0; -use nom::multi::separated_list0; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Write}; @@ -92,29 +97,29 @@ impl fmt::Display for DefineFunctionStatement { } pub fn function(i: &str) -> IResult<&str, DefineFunctionStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("FUNCTION")(i)?; let (i, _) = shouldbespace(i)?; let (i, _) = tag("fn::")(i)?; let (i, name) = ident::multi(i)?; let (i, _) = mightbespace(i)?; - let (i, _) = char('(')(i)?; - let (i, _) = mightbespace(i)?; - let (i, args) = separated_list0(commas, |i| { - let (i, _) = char('$')(i)?; - let (i, name) = ident(i)?; - let (i, _) = mightbespace(i)?; - let (i, _) = char(':')(i)?; - let (i, _) = mightbespace(i)?; - let (i, kind) = kind(i)?; - Ok((i, (name, kind))) - })(i)?; - let (i, _) = mightbespace(i)?; - let (i, _) = char(')')(i)?; + let (i, args) = delimited_list0( + openparentheses, + commas, + |i| { + let (i, _) = char('$')(i)?; + let (i, name) = ident(i)?; + let (i, _) = mightbespace(i)?; + let (i, _) = char(':')(i)?; + let (i, _) = mightbespace(i)?; + let (i, kind) = kind(i)?; + Ok((i, (name, kind))) + }, + closeparentheses, + )(i)?; let (i, _) = mightbespace(i)?; let (i, block) = block(i)?; let (i, opts) = many0(function_opts)(i)?; + let (i, _) = expected("PERMISSIONS or COMMENT", ending::query)(i)?; // Create the base statement let mut res = DefineFunctionStatement { name, @@ -150,7 +155,7 @@ fn function_comment(i: &str) -> IResult<&str, DefineFunctionOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("COMMENT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = strand(i)?; + let (i, v) = cut(strand)(i)?; Ok((i, DefineFunctionOption::Comment(v))) } @@ -158,6 +163,6 @@ fn function_permissions(i: &str) -> IResult<&str, DefineFunctionOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("PERMISSIONS")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = permission(i)?; + let (i, v) = cut(permission)(i)?; Ok((i, DefineFunctionOption::Permissions(v))) } diff --git a/lib/src/sql/statements/define/index.rs b/lib/src/sql/statements/define/index.rs index 3dbbfdc9..50885198 100644 --- a/lib/src/sql/statements/define/index.rs +++ b/lib/src/sql/statements/define/index.rs @@ -7,6 +7,8 @@ use crate::iam::Action; use crate::iam::ResourceKind; use crate::sql::base::Base; use crate::sql::comment::shouldbespace; +use crate::sql::ending; +use crate::sql::error::expect_tag_no_case; use crate::sql::error::IResult; use crate::sql::ident::{ident, Ident}; use crate::sql::idiom; @@ -19,6 +21,7 @@ use crate::sql::value::{Value, Values}; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use nom::combinator::opt; use nom::multi::many0; use nom::sequence::tuple; @@ -98,17 +101,19 @@ impl Display for DefineIndexStatement { } pub fn index(i: &str) -> IResult<&str, DefineIndexStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("INDEX")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, what) = ident(i)?; - let (i, opts) = many0(index_opts)(i)?; + let (i, (name, what, opts)) = cut(|i| { + let (i, name) = ident(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = expect_tag_no_case("ON")(i)?; + let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, what) = ident(i)?; + let (i, opts) = many0(index_opts)(i)?; + let (i, _) = ending::query(i)?; + Ok((i, (name, what, opts))) + })(i)?; // Create the base statement let mut res = DefineIndexStatement { name, @@ -183,7 +188,7 @@ mod tests { #[test] fn check_create_non_unique_index() { - let sql = "DEFINE INDEX my_index ON TABLE my_table COLUMNS my_col"; + let sql = "INDEX my_index ON TABLE my_table COLUMNS my_col"; let (_, idx) = index(sql).unwrap(); assert_eq!( idx, @@ -200,7 +205,7 @@ mod tests { #[test] fn check_create_unique_index() { - let sql = "DEFINE INDEX my_index ON TABLE my_table COLUMNS my_col UNIQUE"; + let sql = "INDEX my_index ON TABLE my_table COLUMNS my_col UNIQUE"; let (_, idx) = index(sql).unwrap(); assert_eq!( idx, @@ -217,7 +222,7 @@ mod tests { #[test] fn check_create_search_index_with_highlights() { - let sql = "DEFINE INDEX my_index ON TABLE my_table COLUMNS my_col SEARCH ANALYZER my_analyzer BM25(1.2,0.75) DOC_IDS_ORDER 1000 DOC_LENGTHS_ORDER 1000 POSTINGS_ORDER 1000 TERMS_ORDER 1000 HIGHLIGHTS"; + let sql = "INDEX my_index ON TABLE my_table COLUMNS my_col SEARCH ANALYZER my_analyzer BM25(1.2,0.75) DOC_IDS_ORDER 1000 DOC_LENGTHS_ORDER 1000 POSTINGS_ORDER 1000 TERMS_ORDER 1000 HIGHLIGHTS"; let (_, idx) = index(sql).unwrap(); assert_eq!( idx, @@ -245,8 +250,7 @@ mod tests { #[test] fn check_create_search_index() { - let sql = - "DEFINE INDEX my_index ON TABLE my_table COLUMNS my_col SEARCH ANALYZER my_analyzer VS"; + let sql = "INDEX my_index ON TABLE my_table COLUMNS my_col SEARCH ANALYZER my_analyzer VS"; let (_, idx) = index(sql).unwrap(); assert_eq!( idx, diff --git a/lib/src/sql/statements/define/mod.rs b/lib/src/sql/statements/define/mod.rs index 55962c19..ba9d1ca6 100644 --- a/lib/src/sql/statements/define/mod.rs +++ b/lib/src/sql/statements/define/mod.rs @@ -18,6 +18,7 @@ pub use field::{field, DefineFieldStatement}; pub use function::{function, DefineFunctionStatement}; pub use index::{index, DefineIndexStatement}; pub use namespace::{namespace, DefineNamespaceStatement}; +use nom::bytes::complete::tag_no_case; pub use param::{param, DefineParamStatement}; pub use scope::{scope, DefineScopeStatement}; pub use table::{table, DefineTableStatement}; @@ -29,6 +30,7 @@ use crate::dbs::Options; use crate::dbs::Transaction; use crate::doc::CursorDoc; use crate::err::Error; +use crate::sql::comment::shouldbespace; use crate::sql::error::IResult; use crate::sql::value::Value; use derive::Store; @@ -105,6 +107,8 @@ impl Display for DefineStatement { } pub fn define(i: &str) -> IResult<&str, DefineStatement> { + let (i, _) = tag_no_case("DEFINE")(i)?; + let (i, _) = shouldbespace(i)?; alt(( map(namespace, DefineStatement::Namespace), map(database, DefineStatement::Database), diff --git a/lib/src/sql/statements/define/namespace.rs b/lib/src/sql/statements/define/namespace.rs index c6803b2c..167b9e08 100644 --- a/lib/src/sql/statements/define/namespace.rs +++ b/lib/src/sql/statements/define/namespace.rs @@ -7,6 +7,8 @@ use crate::iam::Action; use crate::iam::ResourceKind; use crate::sql::base::Base; use crate::sql::comment::shouldbespace; +use crate::sql::ending; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::ident::{ident, Ident}; use crate::sql::strand::{strand, Strand}; @@ -14,6 +16,7 @@ use crate::sql::value::Value; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use nom::multi::many0; use revision::revisioned; use serde::{Deserialize, Serialize}; @@ -68,12 +71,11 @@ impl Display for DefineNamespaceStatement { } pub fn namespace(i: &str) -> IResult<&str, DefineNamespaceStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = alt((tag_no_case("NS"), tag_no_case("NAMESPACE")))(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + let (i, name) = cut(ident)(i)?; let (i, opts) = many0(namespace_opts)(i)?; + let (i, _) = expected("COMMENT", ending::query)(i)?; // Create the base statement let mut res = DefineNamespaceStatement { name, @@ -103,6 +105,6 @@ fn namespace_comment(i: &str) -> IResult<&str, DefineNamespaceOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("COMMENT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = strand(i)?; + let (i, v) = cut(strand)(i)?; Ok((i, DefineNamespaceOption::Comment(v))) } diff --git a/lib/src/sql/statements/define/param.rs b/lib/src/sql/statements/define/param.rs index 0d682b9f..a946927f 100644 --- a/lib/src/sql/statements/define/param.rs +++ b/lib/src/sql/statements/define/param.rs @@ -7,6 +7,8 @@ use crate::iam::Action; use crate::iam::ResourceKind; use crate::sql::base::Base; use crate::sql::comment::shouldbespace; +use crate::sql::ending; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::fmt::is_pretty; use crate::sql::fmt::pretty_indent; @@ -18,6 +20,7 @@ use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; use nom::character::complete::char; +use nom::combinator::cut; use nom::multi::many0; use revision::revisioned; use serde::{Deserialize, Serialize}; @@ -77,13 +80,12 @@ impl Display for DefineParamStatement { } pub fn param(i: &str) -> IResult<&str, DefineParamStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("PARAM")(i)?; let (i, _) = shouldbespace(i)?; - let (i, _) = char('$')(i)?; - let (i, name) = ident(i)?; + let (i, _) = cut(char('$'))(i)?; + let (i, name) = cut(ident)(i)?; let (i, opts) = many0(param_opts)(i)?; + let (i, _) = expected("VALUE, PERMISSIONS, or COMMENT", ending::query)(i)?; // Create the base statement let mut res = DefineParamStatement { name, @@ -125,7 +127,7 @@ fn param_value(i: &str) -> IResult<&str, DefineParamOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("VALUE")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = value(i)?; + let (i, v) = cut(value)(i)?; Ok((i, DefineParamOption::Value(v))) } @@ -133,7 +135,7 @@ fn param_comment(i: &str) -> IResult<&str, DefineParamOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("COMMENT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = strand(i)?; + let (i, v) = cut(strand)(i)?; Ok((i, DefineParamOption::Comment(v))) } @@ -141,6 +143,6 @@ fn param_permissions(i: &str) -> IResult<&str, DefineParamOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("PERMISSIONS")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = permission(i)?; + let (i, v) = cut(permission)(i)?; Ok((i, DefineParamOption::Permissions(v))) } diff --git a/lib/src/sql/statements/define/scope.rs b/lib/src/sql/statements/define/scope.rs index 82d81af8..c5a9c47f 100644 --- a/lib/src/sql/statements/define/scope.rs +++ b/lib/src/sql/statements/define/scope.rs @@ -8,6 +8,8 @@ use crate::iam::ResourceKind; use crate::sql::base::Base; use crate::sql::comment::shouldbespace; use crate::sql::duration::{duration, Duration}; +use crate::sql::ending; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::ident::{ident, Ident}; use crate::sql::strand::{strand, Strand}; @@ -15,6 +17,7 @@ use crate::sql::value::{value, Value}; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use nom::multi::many0; use rand::distributions::Alphanumeric; use rand::Rng; @@ -78,12 +81,11 @@ impl Display for DefineScopeStatement { } pub fn scope(i: &str) -> IResult<&str, DefineScopeStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("SCOPE")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + let (i, name) = cut(ident)(i)?; let (i, opts) = many0(scope_opts)(i)?; + let (i, _) = expected("SESSION, SIGNUP, SIGNIN, or COMMENT", ending::query)(i)?; // Create the base statement let mut res = DefineScopeStatement { name, @@ -130,7 +132,7 @@ fn scope_session(i: &str) -> IResult<&str, DefineScopeOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("SESSION")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = duration(i)?; + let (i, v) = cut(duration)(i)?; Ok((i, DefineScopeOption::Session(v))) } @@ -138,7 +140,7 @@ fn scope_signup(i: &str) -> IResult<&str, DefineScopeOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("SIGNUP")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = value(i)?; + let (i, v) = cut(value)(i)?; Ok((i, DefineScopeOption::Signup(v))) } @@ -146,7 +148,7 @@ fn scope_signin(i: &str) -> IResult<&str, DefineScopeOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("SIGNIN")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = value(i)?; + let (i, v) = cut(value)(i)?; Ok((i, DefineScopeOption::Signin(v))) } @@ -154,6 +156,6 @@ fn scope_comment(i: &str) -> IResult<&str, DefineScopeOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("COMMENT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = strand(i)?; + let (i, v) = cut(strand)(i)?; Ok((i, DefineScopeOption::Comment(v))) } diff --git a/lib/src/sql/statements/define/table.rs b/lib/src/sql/statements/define/table.rs index 97dd4ff0..20af079e 100644 --- a/lib/src/sql/statements/define/table.rs +++ b/lib/src/sql/statements/define/table.rs @@ -8,6 +8,8 @@ use crate::iam::ResourceKind; use crate::sql::base::Base; use crate::sql::changefeed::{changefeed, ChangeFeed}; use crate::sql::comment::shouldbespace; +use crate::sql::ending; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::fmt::is_pretty; use crate::sql::fmt::pretty_indent; @@ -20,6 +22,7 @@ use crate::sql::view::{view, View}; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use nom::multi::many0; use revision::revisioned; use serde::{Deserialize, Serialize}; @@ -136,12 +139,14 @@ impl Display for DefineTableStatement { } pub fn table(i: &str) -> IResult<&str, DefineTableStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("TABLE")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + 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", + ending::query, + )(i)?; // Create the base statement let mut res = DefineTableStatement { name, @@ -250,10 +255,10 @@ mod tests { #[test] fn define_table_with_changefeed() { - let sql = "DEFINE TABLE mytable SCHEMALESS CHANGEFEED 1h"; + let sql = "TABLE mytable SCHEMALESS CHANGEFEED 1h"; let res = table(sql); let out = res.unwrap().1; - assert_eq!(sql, format!("{}", out)); + assert_eq!(format!("DEFINE {sql}"), format!("{}", out)); let serialized: Vec = (&out).try_into().unwrap(); let deserialized = DefineTableStatement::try_from(&serialized).unwrap(); diff --git a/lib/src/sql/statements/define/token.rs b/lib/src/sql/statements/define/token.rs index 7bf6028c..a2813c7d 100644 --- a/lib/src/sql/statements/define/token.rs +++ b/lib/src/sql/statements/define/token.rs @@ -8,6 +8,9 @@ use crate::iam::ResourceKind; use crate::sql::algorithm::{algorithm, Algorithm}; use crate::sql::base::{base_or_scope, Base}; use crate::sql::comment::shouldbespace; +use crate::sql::ending; +use crate::sql::error::expect_tag_no_case; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::escape::quote_str; use crate::sql::ident::{ident, Ident}; @@ -16,6 +19,7 @@ use crate::sql::value::Value; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use nom::multi::many0; use revision::revisioned; use serde::{Deserialize, Serialize}; @@ -106,16 +110,18 @@ impl Display for DefineTokenStatement { } pub fn token(i: &str) -> IResult<&str, DefineTokenStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("TOKEN")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, base) = base_or_scope(i)?; - let (i, opts) = many0(token_opts)(i)?; + let (i, (name, base, opts)) = cut(|i| { + let (i, name) = ident(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = expect_tag_no_case("ON")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, base) = base_or_scope(i)?; + let (i, opts) = many0(token_opts)(i)?; + let (i, _) = expected("TYPE, VALUE, or COMMENT", ending::query)(i)?; + Ok((i, (name, base, opts))) + })(i)?; // Create the base statement let mut res = DefineTokenStatement { name, @@ -158,7 +164,7 @@ fn token_type(i: &str) -> IResult<&str, DefineTokenOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("TYPE")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = algorithm(i)?; + let (i, v) = cut(algorithm)(i)?; Ok((i, DefineTokenOption::Type(v))) } @@ -166,7 +172,7 @@ fn token_value(i: &str) -> IResult<&str, DefineTokenOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("VALUE")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = strand_raw(i)?; + let (i, v) = cut(strand_raw)(i)?; Ok((i, DefineTokenOption::Value(v))) } @@ -174,6 +180,6 @@ fn token_comment(i: &str) -> IResult<&str, DefineTokenOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("COMMENT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = strand(i)?; + let (i, v) = cut(strand)(i)?; Ok((i, DefineTokenOption::Comment(v))) } diff --git a/lib/src/sql/statements/define/user.rs b/lib/src/sql/statements/define/user.rs index 50e48195..0900ba9e 100644 --- a/lib/src/sql/statements/define/user.rs +++ b/lib/src/sql/statements/define/user.rs @@ -9,8 +9,11 @@ use crate::iam::Role; use crate::sql::base::{base, Base}; use crate::sql::comment::shouldbespace; use crate::sql::common::commas; -use crate::sql::error::Error as SqlError; +use crate::sql::ending; +use crate::sql::error::expect_tag_no_case; +use crate::sql::error::expected; use crate::sql::error::IResult; +use crate::sql::error::ParseError as SqlError; use crate::sql::escape::quote_str; use crate::sql::fmt::Fmt; use crate::sql::ident::{ident, Ident}; @@ -21,8 +24,9 @@ use argon2::Argon2; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use nom::multi::many0; -use nom::multi::separated_list0; +use nom::multi::separated_list1; use nom::Err::Failure; use rand::distributions::Alphanumeric; use rand::rngs::OsRng; @@ -137,16 +141,18 @@ impl Display for DefineUserStatement { } pub fn user(i: &str) -> IResult<&str, DefineUserStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("USER")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, base) = base(i)?; - let (i, opts) = user_opts(i)?; + let (i, (name, base, opts)) = cut(|i| { + let (i, name) = ident(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = expect_tag_no_case("ON")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, base) = base(i)?; + let (i, opts) = user_opts(i)?; + let (i, _) = expected("PASSWORD, PASSHASH, ROLES, or COMMENT", ending::query)(i)?; + Ok((i, (name, base, opts))) + })(i)?; // Create the base statement let mut res = DefineUserStatement { name, @@ -191,14 +197,14 @@ enum DefineUserOption { } fn user_opts(i: &str) -> IResult<&str, Vec> { - many0(alt((alt((user_pass, user_hash)), user_roles, user_comment)))(i) + many0(alt((user_pass, user_hash, user_roles, user_comment)))(i) } fn user_pass(i: &str) -> IResult<&str, DefineUserOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("PASSWORD")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = strand_raw(i)?; + let (i, v) = cut(strand_raw)(i)?; Ok((i, DefineUserOption::Password(v))) } @@ -206,7 +212,7 @@ fn user_hash(i: &str) -> IResult<&str, DefineUserOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("PASSHASH")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = strand_raw(i)?; + let (i, v) = cut(strand_raw)(i)?; Ok((i, DefineUserOption::Passhash(v))) } @@ -214,7 +220,7 @@ fn user_comment(i: &str) -> IResult<&str, DefineUserOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("COMMENT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, v) = strand(i)?; + let (i, v) = cut(strand)(i)?; Ok((i, DefineUserOption::Comment(v))) } @@ -222,8 +228,8 @@ fn user_roles(i: &str) -> IResult<&str, DefineUserOption> { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("ROLES")(i)?; let (i, _) = shouldbespace(i)?; - let (i, roles) = separated_list0(commas, |i| { - let (i, v) = ident(i)?; + let (i, roles) = separated_list1(commas, |i| { + let (i, v) = cut(ident)(i)?; // Verify the role is valid Role::try_from(v.as_str()).map_err(|_| Failure(SqlError::Role(i, v.to_string())))?; diff --git a/lib/src/sql/statements/delete.rs b/lib/src/sql/statements/delete.rs index 70db8196..7a159467 100644 --- a/lib/src/sql/statements/delete.rs +++ b/lib/src/sql/statements/delete.rs @@ -13,7 +13,6 @@ use crate::sql::timeout::{timeout, Timeout}; use crate::sql::value::{whats, Value, Values}; use derive::Store; use nom::bytes::complete::tag_no_case; -use nom::combinator::cut; use nom::combinator::opt; use nom::sequence::preceded; use revision::revisioned; @@ -70,7 +69,7 @@ impl DeleteStatement { // This is a single record result Value::Array(mut a) if self.only => match a.len() { // There was exactly one result - v if v == 1 => Ok(a.remove(0)), + 1 => Ok(a.remove(0)), // There were no results _ => Err(Error::SingleOnlyOutput), }, @@ -109,13 +108,10 @@ pub fn delete(i: &str) -> IResult<&str, DeleteStatement> { let (i, only) = opt(preceded(shouldbespace, tag_no_case("ONLY")))(i)?; let (i, _) = shouldbespace(i)?; let (i, what) = whats(i)?; - let (i, (cond, output, timeout, parallel)) = cut(|i| { - let (i, cond) = opt(preceded(shouldbespace, cond))(i)?; - let (i, output) = opt(preceded(shouldbespace, output))(i)?; - let (i, timeout) = opt(preceded(shouldbespace, timeout))(i)?; - let (i, parallel) = opt(preceded(shouldbespace, tag_no_case("PARALLEL")))(i)?; - Ok((i, (cond, output, timeout, parallel))) - })(i)?; + let (i, cond) = opt(preceded(shouldbespace, cond))(i)?; + let (i, output) = opt(preceded(shouldbespace, output))(i)?; + let (i, timeout) = opt(preceded(shouldbespace, timeout))(i)?; + let (i, parallel) = opt(preceded(shouldbespace, tag_no_case("PARALLEL")))(i)?; Ok(( i, DeleteStatement { diff --git a/lib/src/sql/statements/foreach.rs b/lib/src/sql/statements/foreach.rs index 829fbce7..d0363cee 100644 --- a/lib/src/sql/statements/foreach.rs +++ b/lib/src/sql/statements/foreach.rs @@ -4,7 +4,7 @@ use crate::doc::CursorDoc; use crate::err::Error; use crate::sql::block::{block, Block, Entry}; use crate::sql::comment::{mightbespace, shouldbespace}; -use crate::sql::error::IResult; +use crate::sql::error::{expect_tag_no_case, IResult}; use crate::sql::param::{param, Param}; use crate::sql::value::{value, Value}; use derive::Store; @@ -105,7 +105,7 @@ pub fn foreach(i: &str) -> IResult<&str, ForeachStatement> { let (i, param) = param(i)?; let (i, (range, block)) = cut(|i| { let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("IN")(i)?; + let (i, _) = expect_tag_no_case("IN")(i)?; let (i, _) = shouldbespace(i)?; let (i, range) = value(i)?; let (i, _) = mightbespace(i)?; diff --git a/lib/src/sql/statements/info.rs b/lib/src/sql/statements/info.rs index 0212c754..b84521c9 100644 --- a/lib/src/sql/statements/info.rs +++ b/lib/src/sql/statements/info.rs @@ -7,6 +7,8 @@ use crate::iam::Action; use crate::iam::ResourceKind; use crate::sql::base::base; use crate::sql::comment::shouldbespace; +use crate::sql::error::expected; +use crate::sql::error::ExplainResultExt; use crate::sql::error::IResult; use crate::sql::ident::{ident, Ident}; use crate::sql::object::Object; @@ -236,10 +238,11 @@ pub fn info(i: &str) -> IResult<&str, InfoStatement> { let (i, _) = tag_no_case("INFO")(i)?; let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("FOR")(i)?; - cut(|i| { - let (i, _) = shouldbespace(i)?; - alt((root, ns, db, sc, tb, user))(i) - })(i) + let (i, _) = cut(shouldbespace)(i)?; + expected( + "ROOT, NAMESPACE, DATABASE, SCOPE, TABLE or USER", + cut(alt((root, ns, db, sc, tb, user))), + )(i) } fn root(i: &str) -> IResult<&str, InfoStatement> { @@ -260,19 +263,15 @@ fn db(i: &str) -> IResult<&str, InfoStatement> { fn sc(i: &str) -> IResult<&str, InfoStatement> { let (i, _) = alt((tag_no_case("SCOPE"), tag_no_case("SC")))(i)?; let (i, _) = shouldbespace(i)?; - cut(|i| { - let (i, scope) = ident(i)?; - Ok((i, InfoStatement::Sc(scope))) - })(i) + let (i, scope) = cut(ident)(i)?; + Ok((i, InfoStatement::Sc(scope))) } fn tb(i: &str) -> IResult<&str, InfoStatement> { let (i, _) = alt((tag_no_case("TABLE"), tag_no_case("TB")))(i)?; let (i, _) = shouldbespace(i)?; - cut(|i| { - let (i, table) = ident(i)?; - Ok((i, InfoStatement::Tb(table))) - })(i) + let (i, table) = cut(ident)(i)?; + Ok((i, InfoStatement::Tb(table))) } fn user(i: &str) -> IResult<&str, InfoStatement> { @@ -285,7 +284,8 @@ fn user(i: &str) -> IResult<&str, InfoStatement> { let (i, _) = tag_no_case("ON")(i)?; cut(|i| { let (i, _) = shouldbespace(i)?; - let (i, base) = base(i)?; + let (i, base) = + base(i).explain("scopes are not allowed here", tag_no_case("SCOPE"))?; Ok((i, base)) })(i) })(i)?; diff --git a/lib/src/sql/statements/insert.rs b/lib/src/sql/statements/insert.rs index c2aa2a6e..10ed9714 100644 --- a/lib/src/sql/statements/insert.rs +++ b/lib/src/sql/statements/insert.rs @@ -7,11 +7,14 @@ use crate::doc::CursorDoc; use crate::err::Error; use crate::sql::comment::shouldbespace; use crate::sql::data::{single, update, values, Data}; +use crate::sql::error::expected; +use crate::sql::error::ExplainResultExt; use crate::sql::error::IResult; use crate::sql::output::{output, Output}; use crate::sql::param::param; use crate::sql::table::table; use crate::sql::timeout::{timeout, Timeout}; +use crate::sql::value::value; use crate::sql::value::Value; use derive::Store; use nom::branch::alt; @@ -143,8 +146,14 @@ pub fn insert(i: &str) -> IResult<&str, InsertStatement> { let (i, ignore) = opt(terminated(tag_no_case("IGNORE"), shouldbespace))(i)?; let (i, _) = tag_no_case("INTO")(i)?; let (i, _) = shouldbespace(i)?; - let (i, into) = cut(alt((map(table, Value::Table), map(param, Value::Param))))(i)?; - let (i, _) = cut(shouldbespace)(i)?; + let (i, into) = expected( + "a parameter or a table name", + cut(alt(( + map(terminated(table, shouldbespace), Value::Table), + map(terminated(param, shouldbespace), Value::Param), + ))), + )(i) + .explain("expressions aren't allowed here.", value)?; let (i, data) = cut(alt((values, single)))(i)?; let (i, update) = opt(preceded(shouldbespace, update))(i)?; let (i, output) = opt(preceded(shouldbespace, output))(i)?; diff --git a/lib/src/sql/statements/live.rs b/lib/src/sql/statements/live.rs index 4f981c94..aefee1f4 100644 --- a/lib/src/sql/statements/live.rs +++ b/lib/src/sql/statements/live.rs @@ -6,6 +6,7 @@ use crate::err::Error; use crate::iam::Auth; use crate::sql::comment::shouldbespace; use crate::sql::cond::{cond, Cond}; +use crate::sql::error::expect_tag_no_case; use crate::sql::error::IResult; use crate::sql::fetch::{fetch, Fetchs}; use crate::sql::field::{fields, Fields}; @@ -125,7 +126,7 @@ pub fn live(i: &str) -> IResult<&str, LiveStatement> { cut(|i| { let (i, expr) = alt((map(tag_no_case("DIFF"), |_| Fields::default()), fields))(i)?; let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("FROM")(i)?; + let (i, _) = expect_tag_no_case("FROM")(i)?; let (i, _) = shouldbespace(i)?; let (i, what) = alt((into(param), into(table)))(i)?; let (i, cond) = opt(preceded(shouldbespace, cond))(i)?; diff --git a/lib/src/sql/statements/option.rs b/lib/src/sql/statements/option.rs index a9b32529..33c3a6d0 100644 --- a/lib/src/sql/statements/option.rs +++ b/lib/src/sql/statements/option.rs @@ -1,5 +1,6 @@ use crate::sql::comment::mightbespace; use crate::sql::comment::shouldbespace; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::ident::{ident, Ident}; use derive::Store; @@ -35,10 +36,13 @@ pub fn option(i: &str) -> IResult<&str, OptionStatement> { let (i, _) = tag_no_case("OPTION")(i)?; let (i, _) = shouldbespace(i)?; let (i, n) = ident(i)?; - let (i, v) = cut(opt(alt(( - value(true, tuple((mightbespace, char('='), mightbespace, tag_no_case("TRUE")))), - value(false, tuple((mightbespace, char('='), mightbespace, tag_no_case("FALSE")))), - ))))(i)?; + let (i, v) = expected( + "'=' follwed by a value for the option", + cut(opt(alt(( + value(true, tuple((mightbespace, char('='), mightbespace, tag_no_case("TRUE")))), + value(false, tuple((mightbespace, char('='), mightbespace, tag_no_case("FALSE")))), + )))), + )(i)?; Ok(( i, OptionStatement { diff --git a/lib/src/sql/statements/relate.rs b/lib/src/sql/statements/relate.rs index ad816d30..444a366e 100644 --- a/lib/src/sql/statements/relate.rs +++ b/lib/src/sql/statements/relate.rs @@ -9,6 +9,7 @@ use crate::sql::array::array; use crate::sql::comment::mightbespace; use crate::sql::comment::shouldbespace; use crate::sql::data::{data, Data}; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::output::{output, Output}; use crate::sql::param::param; @@ -179,7 +180,7 @@ impl RelateStatement { // This is a single record result Value::Array(mut a) if self.only => match a.len() { // There was exactly one result - v if v == 1 => Ok(a.remove(0)), + 1 => Ok(a.remove(0)), // There were no results _ => Err(Error::SingleOnlyOutput), }, @@ -244,7 +245,8 @@ pub fn relate(i: &str) -> IResult<&str, RelateStatement> { fn relate_oi(i: &str) -> IResult<&str, (Value, Value, Value)> { let (i, prefix) = alt((into(subquery), into(array), into(param), into(thing)))(i)?; let (i, _) = mightbespace(i)?; - let (i, is_o) = cut(alt((value(true, tag("->")), value(false, tag("<-")))))(i)?; + let (i, is_o) = + expected("`->` or `<-`", cut(alt((value(true, tag("->")), value(false, tag("<-"))))))(i)?; if is_o { let (i, (kind, with)) = cut(relate_o)(i)?; diff --git a/lib/src/sql/statements/remove/analyzer.rs b/lib/src/sql/statements/remove/analyzer.rs index df90bb9d..579e16d5 100644 --- a/lib/src/sql/statements/remove/analyzer.rs +++ b/lib/src/sql/statements/remove/analyzer.rs @@ -10,6 +10,7 @@ use crate::sql::ident::{ident, Ident}; use crate::sql::value::Value; use derive::Store; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; @@ -49,11 +50,9 @@ impl Display for RemoveAnalyzerStatement { } pub fn analyzer(i: &str) -> IResult<&str, RemoveAnalyzerStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("ANALYZER")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + let (i, name) = cut(ident)(i)?; Ok(( i, RemoveAnalyzerStatement { diff --git a/lib/src/sql/statements/remove/database.rs b/lib/src/sql/statements/remove/database.rs index f47ff7ba..4f03cf19 100644 --- a/lib/src/sql/statements/remove/database.rs +++ b/lib/src/sql/statements/remove/database.rs @@ -11,6 +11,7 @@ use crate::sql::value::Value; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; @@ -53,11 +54,9 @@ impl Display for RemoveDatabaseStatement { } pub fn database(i: &str) -> IResult<&str, RemoveDatabaseStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = alt((tag_no_case("DB"), tag_no_case("DATABASE")))(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + let (i, name) = cut(ident)(i)?; Ok(( i, RemoveDatabaseStatement { diff --git a/lib/src/sql/statements/remove/event.rs b/lib/src/sql/statements/remove/event.rs index 865bdf1c..c2dca144 100644 --- a/lib/src/sql/statements/remove/event.rs +++ b/lib/src/sql/statements/remove/event.rs @@ -5,11 +5,13 @@ use crate::err::Error; use crate::iam::{Action, ResourceKind}; use crate::sql::base::Base; use crate::sql::comment::shouldbespace; +use crate::sql::error::expect_tag_no_case; use crate::sql::error::IResult; use crate::sql::ident::{ident, Ident}; use crate::sql::value::Value; use derive::Store; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use nom::combinator::opt; use nom::sequence::tuple; use revision::revisioned; @@ -55,16 +57,14 @@ impl Display for RemoveEventStatement { } pub fn event(i: &str) -> IResult<&str, RemoveEventStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("EVENT")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + let (i, name) = cut(ident)(i)?; let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = expect_tag_no_case("ON")(i)?; let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; let (i, _) = shouldbespace(i)?; - let (i, what) = ident(i)?; + let (i, what) = cut(ident)(i)?; Ok(( i, RemoveEventStatement { diff --git a/lib/src/sql/statements/remove/field.rs b/lib/src/sql/statements/remove/field.rs index 309c38f2..b249fd3e 100644 --- a/lib/src/sql/statements/remove/field.rs +++ b/lib/src/sql/statements/remove/field.rs @@ -5,6 +5,7 @@ use crate::err::Error; use crate::iam::{Action, ResourceKind}; use crate::sql::base::Base; use crate::sql::comment::shouldbespace; +use crate::sql::error::expect_tag_no_case; use crate::sql::error::IResult; use crate::sql::ident::{ident, Ident}; use crate::sql::idiom; @@ -12,6 +13,7 @@ use crate::sql::idiom::Idiom; use crate::sql::value::Value; use derive::Store; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use nom::combinator::opt; use nom::sequence::tuple; use revision::revisioned; @@ -58,16 +60,14 @@ impl Display for RemoveFieldStatement { } pub fn field(i: &str) -> IResult<&str, RemoveFieldStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("FIELD")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = idiom::local(i)?; + let (i, name) = cut(idiom::local)(i)?; let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = expect_tag_no_case("ON")(i)?; let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; let (i, _) = shouldbespace(i)?; - let (i, what) = ident(i)?; + let (i, what) = cut(ident)(i)?; Ok(( i, RemoveFieldStatement { diff --git a/lib/src/sql/statements/remove/function.rs b/lib/src/sql/statements/remove/function.rs index 18e0500d..367bd8dc 100644 --- a/lib/src/sql/statements/remove/function.rs +++ b/lib/src/sql/statements/remove/function.rs @@ -53,8 +53,6 @@ impl Display for RemoveFunctionStatement { } pub fn function(i: &str) -> IResult<&str, RemoveFunctionStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("FUNCTION")(i)?; let (i, _) = shouldbespace(i)?; let (i, _) = tag("fn::")(i)?; diff --git a/lib/src/sql/statements/remove/index.rs b/lib/src/sql/statements/remove/index.rs index cd0f4b4f..95e7bc70 100644 --- a/lib/src/sql/statements/remove/index.rs +++ b/lib/src/sql/statements/remove/index.rs @@ -5,11 +5,13 @@ use crate::err::Error; use crate::iam::{Action, ResourceKind}; use crate::sql::base::Base; use crate::sql::comment::shouldbespace; +use crate::sql::error::expect_tag_no_case; use crate::sql::error::IResult; use crate::sql::ident::{ident, Ident}; use crate::sql::value::Value; use derive::Store; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use nom::combinator::opt; use nom::sequence::tuple; use revision::revisioned; @@ -58,16 +60,14 @@ impl Display for RemoveIndexStatement { } pub fn index(i: &str) -> IResult<&str, RemoveIndexStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("INDEX")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + let (i, name) = cut(ident)(i)?; let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = expect_tag_no_case("ON")(i)?; let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; let (i, _) = shouldbespace(i)?; - let (i, what) = ident(i)?; + let (i, what) = cut(ident)(i)?; Ok(( i, RemoveIndexStatement { diff --git a/lib/src/sql/statements/remove/mod.rs b/lib/src/sql/statements/remove/mod.rs index af4c5b87..02d339cd 100644 --- a/lib/src/sql/statements/remove/mod.rs +++ b/lib/src/sql/statements/remove/mod.rs @@ -18,6 +18,7 @@ pub use field::{field, RemoveFieldStatement}; pub use function::{function, RemoveFunctionStatement}; pub use index::{index, RemoveIndexStatement}; pub use namespace::{namespace, RemoveNamespaceStatement}; +use nom::bytes::complete::tag_no_case; pub use param::{param, RemoveParamStatement}; pub use scope::{scope, RemoveScopeStatement}; pub use table::{table, RemoveTableStatement}; @@ -29,6 +30,7 @@ use crate::dbs::Options; use crate::dbs::Transaction; use crate::doc::CursorDoc; use crate::err::Error; +use crate::sql::comment::shouldbespace; use crate::sql::error::IResult; use crate::sql::value::Value; use derive::Store; @@ -105,6 +107,8 @@ impl Display for RemoveStatement { } pub fn remove(i: &str) -> IResult<&str, RemoveStatement> { + let (i, _) = tag_no_case("REMOVE")(i)?; + let (i, _) = shouldbespace(i)?; alt(( map(namespace, RemoveStatement::Namespace), map(database, RemoveStatement::Database), diff --git a/lib/src/sql/statements/remove/namespace.rs b/lib/src/sql/statements/remove/namespace.rs index 241b7e9e..13180919 100644 --- a/lib/src/sql/statements/remove/namespace.rs +++ b/lib/src/sql/statements/remove/namespace.rs @@ -11,6 +11,7 @@ use crate::sql::value::Value; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; @@ -53,11 +54,9 @@ impl Display for RemoveNamespaceStatement { } pub fn namespace(i: &str) -> IResult<&str, RemoveNamespaceStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = alt((tag_no_case("NS"), tag_no_case("NAMESPACE")))(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + let (i, name) = cut(ident)(i)?; Ok(( i, RemoveNamespaceStatement { diff --git a/lib/src/sql/statements/remove/param.rs b/lib/src/sql/statements/remove/param.rs index b268a2b1..35fd9bcb 100644 --- a/lib/src/sql/statements/remove/param.rs +++ b/lib/src/sql/statements/remove/param.rs @@ -11,6 +11,7 @@ use crate::sql::value::Value; use derive::Store; use nom::bytes::complete::tag_no_case; use nom::character::complete::char; +use nom::combinator::cut; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; @@ -50,12 +51,10 @@ impl Display for RemoveParamStatement { } pub fn param(i: &str) -> IResult<&str, RemoveParamStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("PARAM")(i)?; let (i, _) = shouldbespace(i)?; - let (i, _) = char('$')(i)?; - let (i, name) = ident(i)?; + let (i, _) = cut(char('$'))(i)?; + let (i, name) = cut(ident)(i)?; Ok(( i, RemoveParamStatement { diff --git a/lib/src/sql/statements/remove/scope.rs b/lib/src/sql/statements/remove/scope.rs index bfecf806..143e68ea 100644 --- a/lib/src/sql/statements/remove/scope.rs +++ b/lib/src/sql/statements/remove/scope.rs @@ -10,6 +10,7 @@ use crate::sql::ident::{ident, Ident}; use crate::sql::value::Value; use derive::Store; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; @@ -52,11 +53,9 @@ impl Display for RemoveScopeStatement { } pub fn scope(i: &str) -> IResult<&str, RemoveScopeStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("SCOPE")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + let (i, name) = cut(ident)(i)?; Ok(( i, RemoveScopeStatement { diff --git a/lib/src/sql/statements/remove/table.rs b/lib/src/sql/statements/remove/table.rs index 222b7aea..6f30c0b1 100644 --- a/lib/src/sql/statements/remove/table.rs +++ b/lib/src/sql/statements/remove/table.rs @@ -10,6 +10,7 @@ use crate::sql::ident::{ident, Ident}; use crate::sql::value::Value; use derive::Store; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; @@ -63,11 +64,9 @@ impl Display for RemoveTableStatement { } pub fn table(i: &str) -> IResult<&str, RemoveTableStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("TABLE")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + let (i, name) = cut(ident)(i)?; Ok(( i, RemoveTableStatement { diff --git a/lib/src/sql/statements/remove/token.rs b/lib/src/sql/statements/remove/token.rs index d8312c6c..3548ba43 100644 --- a/lib/src/sql/statements/remove/token.rs +++ b/lib/src/sql/statements/remove/token.rs @@ -5,11 +5,13 @@ use crate::err::Error; use crate::iam::{Action, ResourceKind}; use crate::sql::base::{base_or_scope, Base}; use crate::sql::comment::shouldbespace; +use crate::sql::error::expect_tag_no_case; use crate::sql::error::IResult; use crate::sql::ident::{ident, Ident}; use crate::sql::value::Value; use derive::Store; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; @@ -78,15 +80,13 @@ impl Display for RemoveTokenStatement { } pub fn token(i: &str) -> IResult<&str, RemoveTokenStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("TOKEN")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + let (i, name) = cut(ident)(i)?; let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = expect_tag_no_case("ON")(i)?; let (i, _) = shouldbespace(i)?; - let (i, base) = base_or_scope(i)?; + let (i, base) = cut(base_or_scope)(i)?; Ok(( i, RemoveTokenStatement { diff --git a/lib/src/sql/statements/remove/user.rs b/lib/src/sql/statements/remove/user.rs index 678ae250..12876e2d 100644 --- a/lib/src/sql/statements/remove/user.rs +++ b/lib/src/sql/statements/remove/user.rs @@ -5,11 +5,13 @@ use crate::err::Error; use crate::iam::{Action, ResourceKind}; use crate::sql::base::{base, Base}; use crate::sql::comment::shouldbespace; +use crate::sql::error::expect_tag_no_case; use crate::sql::error::IResult; use crate::sql::ident::{ident, Ident}; use crate::sql::value::Value; use derive::Store; use nom::bytes::complete::tag_no_case; +use nom::combinator::cut; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; @@ -78,15 +80,13 @@ impl Display for RemoveUserStatement { } pub fn user(i: &str) -> IResult<&str, RemoveUserStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("USER")(i)?; let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; + let (i, name) = cut(ident)(i)?; let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = expect_tag_no_case("ON")(i)?; let (i, _) = shouldbespace(i)?; - let (i, base) = base(i)?; + let (i, base) = cut(base)(i)?; Ok(( i, RemoveUserStatement { diff --git a/lib/src/sql/statements/select.rs b/lib/src/sql/statements/select.rs index 2cb36548..682a2e81 100644 --- a/lib/src/sql/statements/select.rs +++ b/lib/src/sql/statements/select.rs @@ -8,6 +8,9 @@ use crate::err::Error; use crate::idx::planner::QueryPlanner; use crate::sql::comment::shouldbespace; use crate::sql::cond::{cond, Cond}; +use crate::sql::ending; +use crate::sql::error::expect_tag_no_case; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::explain::{explain, Explain}; use crate::sql::fetch::{fetch, Fetchs}; @@ -30,6 +33,7 @@ use derive::Store; use nom::bytes::complete::tag_no_case; use nom::combinator::cut; use nom::combinator::opt; +use nom::combinator::peek; use nom::sequence::preceded; use revision::revisioned; use serde::{Deserialize, Serialize}; @@ -74,6 +78,7 @@ impl SelectStatement { } self.cond.as_ref().map_or(false, |v| v.writeable()) } + /// Process this type returning a computed simple Value pub(crate) async fn compute( &self, @@ -138,7 +143,7 @@ impl SelectStatement { // This is a single record result Value::Array(mut a) if self.only => match a.len() { // There was exactly one result - v if v == 1 => Ok(a.remove(0)), + 1 => Ok(a.remove(0)), // There were no results _ => Err(Error::SingleOnlyOutput), }, @@ -205,7 +210,7 @@ pub fn select(i: &str) -> IResult<&str, SelectStatement> { let (i, expr) = fields(i)?; let (i, omit) = opt(preceded(shouldbespace, omit))(i)?; let (i, _) = cut(shouldbespace)(i)?; - let (i, _) = cut(tag_no_case("FROM"))(i)?; + let (i, _) = expect_tag_no_case("FROM")(i)?; let (i, only) = opt(preceded(shouldbespace, tag_no_case("ONLY")))(i)?; let (i, _) = cut(shouldbespace)(i)?; let (i, what) = cut(selects)(i)?; @@ -224,6 +229,11 @@ pub fn select(i: &str) -> IResult<&str, SelectStatement> { let (i, timeout) = opt(preceded(shouldbespace, timeout))(i)?; let (i, parallel) = opt(preceded(shouldbespace, tag_no_case("PARALLEL")))(i)?; let (i, explain) = opt(preceded(shouldbespace, explain))(i)?; + let (i, _) = expected( + "one of WITH, WHERE, SPLIT, GROUP, ORDER, LIMIT, START, FETCH, VERSION, TIMEOUT, PARELLEL, or EXPLAIN", + cut(peek(ending::query)) + )(i)?; + Ok(( i, SelectStatement { diff --git a/lib/src/sql/statements/show.rs b/lib/src/sql/statements/show.rs index 1413c22f..820efef5 100644 --- a/lib/src/sql/statements/show.rs +++ b/lib/src/sql/statements/show.rs @@ -8,6 +8,8 @@ use crate::iam::ResourceKind; use crate::sql::comment::shouldbespace; use crate::sql::common::take_u64; use crate::sql::datetime::datetime; +use crate::sql::error::expect_tag_no_case; +use crate::sql::error::expected; use crate::sql::error::IResult; use crate::sql::table::{table, Table}; use crate::sql::value::Value; @@ -99,15 +101,18 @@ impl fmt::Display for ShowStatement { } pub fn table_or_database(i: &str) -> IResult<&str, Option> { - let (i, v) = alt(( - map(preceded(tag_no_case("table"), preceded(shouldbespace, table)), Some), - value(None, tag_no_case("database")), - ))(i)?; + let (i, v) = expected( + "one of TABLE, DATABASE", + alt(( + map(preceded(tag_no_case("TABLE"), preceded(shouldbespace, cut(table))), Some), + value(None, tag_no_case("DATABASE")), + )), + )(i)?; Ok((i, v)) } pub fn since(i: &str) -> IResult<&str, ShowSince> { - let (i, _) = tag_no_case("SINCE")(i)?; + let (i, _) = expect_tag_no_case("SINCE")(i)?; let (i, _) = shouldbespace(i)?; cut(alt((map(take_u64, ShowSince::Versionstamp), map(datetime, ShowSince::Timestamp))))(i) @@ -116,7 +121,6 @@ pub fn since(i: &str) -> IResult<&str, ShowSince> { pub fn limit(i: &str) -> IResult<&str, u32> { let (i, _) = tag_no_case("LIMIT")(i)?; let (i, _) = shouldbespace(i)?; - cut(u32)(i) } diff --git a/lib/src/sql/statements/update.rs b/lib/src/sql/statements/update.rs index a1382893..c2170dc4 100644 --- a/lib/src/sql/statements/update.rs +++ b/lib/src/sql/statements/update.rs @@ -71,7 +71,7 @@ impl UpdateStatement { // This is a single record result Value::Array(mut a) if self.only => match a.len() { // There was exactly one result - v if v == 1 => Ok(a.remove(0)), + 1 => Ok(a.remove(0)), // There were no results _ => Err(Error::SingleOnlyOutput), }, diff --git a/lib/src/sql/strand.rs b/lib/src/sql/strand.rs index d9386cf0..70f52287 100644 --- a/lib/src/sql/strand.rs +++ b/lib/src/sql/strand.rs @@ -1,12 +1,12 @@ -use crate::sql::error::Error::Parser; use crate::sql::error::IResult; use crate::sql::escape::quote_str; +use crate::sql::ParseError; use nom::branch::alt; use nom::bytes::complete::{escaped_transform, is_not, tag, take, take_while_m_n}; use nom::character::complete::char; use nom::combinator::value; use nom::sequence::preceded; -use nom::Err::Failure; +use nom::Err; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; @@ -14,6 +14,8 @@ use std::ops::Deref; use std::ops::{self, RangeInclusive}; use std::str; +use super::error::expected; + pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Strand"; const SINGLE: char = '\''; @@ -93,7 +95,7 @@ pub fn strand(i: &str) -> IResult<&str, Strand> { } pub fn strand_raw(i: &str) -> IResult<&str, String> { - alt((strand_blank, strand_single, strand_double))(i) + expected("a strand", alt((strand_blank, strand_single, strand_double)))(i) } fn strand_blank(i: &str) -> IResult<&str, String> { @@ -162,7 +164,11 @@ fn char_unicode_bare(i: &str) -> IResult<&str, char> { // Take exactly 4 bytes let (i, v) = take(4usize)(i)?; // Parse them as hex, where an error indicates invalid hex digits - let v: u16 = u16::from_str_radix(v, 16).map_err(|_| Failure(Parser(i)))?; + let v: u16 = u16::from_str_radix(v, 16).map_err(|_| { + Err::Failure(ParseError::InvalidUnicode { + tried: i, + }) + })?; if LEADING_SURROGATES.contains(&v) { let leading = v; @@ -172,9 +178,15 @@ fn char_unicode_bare(i: &str) -> IResult<&str, char> { // Take exactly 4 more bytes let (i, v) = take(4usize)(i)?; // Parse them as hex, where an error indicates invalid hex digits - let trailing = u16::from_str_radix(v, 16).map_err(|_| Failure(Parser(i)))?; + let trailing = u16::from_str_radix(v, 16).map_err(|_| { + Err::Failure(ParseError::InvalidUnicode { + tried: i, + }) + })?; if !TRAILING_SURROGATES.contains(&trailing) { - return Err(Failure(Parser(i))); + return Err(Err::Failure(ParseError::InvalidUnicode { + tried: i, + })); } // Compute the codepoint. // https://datacadamia.com/data/type/text/surrogate#from_surrogate_to_character_code @@ -183,12 +195,18 @@ fn char_unicode_bare(i: &str) -> IResult<&str, char> { + trailing as u32 - *TRAILING_SURROGATES.start() as u32; // Convert to char - let v = char::from_u32(codepoint).ok_or(Failure(Parser(i)))?; + let v = char::from_u32(codepoint).ok_or(Err::Failure(ParseError::InvalidUnicode { + tried: i, + }))?; // Return the char Ok((i, v)) } else { // We can convert this to char or error in the case of invalid Unicode character - let v = char::from_u32(v as u32).filter(|c| *c != 0 as char).ok_or(Failure(Parser(i)))?; + let v = char::from_u32(v as u32).filter(|c| *c != 0 as char).ok_or(Err::Failure( + ParseError::InvalidUnicode { + tried: i, + }, + ))?; // Return the char Ok((i, v)) } @@ -203,7 +221,11 @@ fn char_unicode_bracketed(i: &str) -> IResult<&str, char> { // We can convert this to u32 as the max is 0xffffff let v = u32::from_str_radix(v, 16).unwrap(); // We can convert this to char or error in the case of invalid Unicode character - let v = char::from_u32(v).filter(|c| *c != 0 as char).ok_or(Failure(Parser(i)))?; + let v = char::from_u32(v).filter(|c| *c != 0 as char).ok_or(Err::Failure( + ParseError::InvalidUnicode { + tried: i, + }, + ))?; // Read the } character let (i, _) = char('}')(i)?; // Return the char diff --git a/lib/src/sql/subquery.rs b/lib/src/sql/subquery.rs index cb8d3b16..b51b8724 100644 --- a/lib/src/sql/subquery.rs +++ b/lib/src/sql/subquery.rs @@ -17,12 +17,18 @@ use crate::sql::statements::select::{select, SelectStatement}; use crate::sql::statements::update::{update, UpdateStatement}; use crate::sql::value::{value, Value}; use nom::branch::alt; -use nom::combinator::{cut, map}; +use nom::bytes::complete::{tag, tag_no_case}; +use nom::combinator::{map, opt, peek}; +use nom::sequence::tuple; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::fmt::{self, Display, Formatter}; +use super::comment::{mightbespace, shouldbespace}; +use super::error::ExplainResultExt; +use super::util::expect_delimited; + pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Subquery"; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] @@ -126,27 +132,29 @@ fn subquery_ifelse(i: &str) -> IResult<&str, Subquery> { } fn subquery_value(i: &str) -> IResult<&str, Subquery> { - let (i, _) = openparentheses(i)?; - let (i, v) = map(value, Subquery::Value)(i)?; - let (i, _) = cut(closeparentheses)(i)?; - Ok((i, v)) + expect_delimited(openparentheses, map(value, Subquery::Value), closeparentheses)(i) } fn subquery_other(i: &str) -> IResult<&str, Subquery> { - let _diving = crate::sql::parser::depth::dive()?; - alt(( - |i| { - let (i, _) = openparentheses(i)?; - let (i, v) = subquery_inner(i)?; - let (i, _) = cut(closeparentheses)(i)?; - Ok((i, v)) - }, - |i| { - let (i, v) = subquery_inner(i)?; - let (i, _) = ending(i)?; - Ok((i, v)) - }, - ))(i) + alt((expect_delimited(openparentheses, subquery_inner, closeparentheses), |i| { + let (i, v) = subquery_inner(i)?; + let (i, _) = ending(i)?; + let (i, _) = eat_semicolon(i)?; + Ok((i, v)) + }))(i) +} + +fn eat_semicolon(i: &str) -> IResult<&str, ()> { + let (i, _) = opt(|i| { + let (i, _) = mightbespace(i)?; + let (i, _) = tag(";")(i)?; + let (i, _) = peek(tuple(( + shouldbespace, + alt((tag_no_case("THEN"), tag_no_case("ELSE"), tag_no_case("END"))), + )))(i)?; + Ok((i, ())) + })(i)?; + Ok((i, ())) } fn subquery_inner(i: &str) -> IResult<&str, Subquery> { @@ -161,6 +169,27 @@ fn subquery_inner(i: &str) -> IResult<&str, Subquery> { map(define, Subquery::Define), map(remove, Subquery::Remove), ))(i) + .explain("This statement is not allowed in a subquery", disallowed_subquery_statements) +} + +fn disallowed_subquery_statements(i: &str) -> IResult<&str, ()> { + let (i, _) = alt(( + tag_no_case("ANALYZED"), + tag_no_case("BEGIN"), + tag_no_case("BREAK"), + tag_no_case("CONTINUE"), + tag_no_case("COMMIT"), + tag_no_case("FOR"), + tag_no_case("INFO"), + tag_no_case("KILL"), + tag_no_case("LIVE"), + tag_no_case("OPTION"), + tag_no_case("RELATE"), + tag_no_case("SLEEP"), + tag_no_case("THROW"), + tag_no_case("USE"), + ))(i)?; + Ok((i, ())) } #[cfg(test)] diff --git a/lib/src/sql/table.rs b/lib/src/sql/table.rs index b6792b92..ed9465f4 100644 --- a/lib/src/sql/table.rs +++ b/lib/src/sql/table.rs @@ -13,6 +13,8 @@ use std::fmt::{self, Display, Formatter}; use std::ops::Deref; use std::str; +use super::error::expected; + pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Table"; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] @@ -89,7 +91,7 @@ impl Display for Table { } pub fn table(i: &str) -> IResult<&str, Table> { - let (i, v) = ident_raw(i)?; + let (i, v) = expected("a table name", ident_raw)(i)?; Ok((i, Table(v))) } diff --git a/lib/src/sql/thing.rs b/lib/src/sql/thing.rs index c78d1f2f..d8755f97 100644 --- a/lib/src/sql/thing.rs +++ b/lib/src/sql/thing.rs @@ -19,6 +19,8 @@ use serde::{Deserialize, Serialize}; use std::fmt; use std::str::FromStr; +use super::error::expected; + pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Thing"; #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Store, Hash)] @@ -120,7 +122,7 @@ impl Thing { } pub fn thing(i: &str) -> IResult<&str, Thing> { - alt((thing_raw, thing_single, thing_double))(i) + expected("a thing", alt((thing_raw, thing_single, thing_double)))(i) } fn thing_single(i: &str) -> IResult<&str, Thing> { diff --git a/lib/src/sql/util.rs b/lib/src/sql/util.rs index 54a966b2..fc713cf8 100644 --- a/lib/src/sql/util.rs +++ b/lib/src/sql/util.rs @@ -1,4 +1,55 @@ -use nom::{error::ParseError, Err, IResult, InputLength, Parser}; +use crate::sql::error::{IResult, ParseError}; +use nom::{Err, InputLength, Parser}; + +/// Parses a parser delimited by two other parsers. +/// +/// This parser failes (not errors) if the second delimiting parser returns an error. +pub fn expect_delimited( + mut prefix: D, + mut value: V, + mut terminator: T, +) -> impl FnMut(I) -> IResult> +where + I: Clone + InputLength, + V: Parser>, + D: Parser>, + T: Parser>, +{ + move |i| { + let (i, s) = prefix.parse(i)?; + let (i, res) = value.parse(i)?; + match terminator.parse(i) { + Ok((i, _)) => Result::Ok((i, res)), + Result::Err(Err::Failure(e)) | Result::Err(Err::Error(e)) => { + Result::Err(Err::Failure(ParseError::MissingDelimiter { + opened: s, + tried: e.tried(), + })) + } + Result::Err(Err::Incomplete(e)) => Result::Err(Err::Incomplete(e)), + } + } +} + +pub fn expect_terminator( + open_span: I, + mut terminator: P, +) -> impl FnMut(I) -> IResult> +where + I: Clone, + P: Parser>, +{ + move |i| match terminator.parse(i) { + Ok((i, x)) => Ok((i, x)), + Result::Err(Err::Failure(e)) | Result::Err(Err::Error(e)) => { + Result::Err(Err::Failure(ParseError::MissingDelimiter { + opened: open_span.clone(), + tried: e.tried(), + })) + } + Result::Err(Err::Incomplete(e)) => Result::Err(Err::Incomplete(e)), + } +} /// Parses a delimited list with an option trailing seperator in the form of: /// @@ -17,22 +68,21 @@ use nom::{error::ParseError, Err, IResult, InputLength, Parser}; /// and if there is none it returns a failure. Otherwise completes with an vec of the parsed /// values. /// -pub fn delimited_list0( +pub fn delimited_list0( mut prefix: D, mut seperator: S, mut value: V, mut terminator: T, -) -> impl FnMut(I) -> IResult, E> +) -> impl FnMut(I) -> IResult, ParseError> where I: Clone + InputLength, - V: Parser, - D: Parser, - S: Parser, - T: Parser, - E: ParseError, + V: Parser>, + D: Parser>, + S: Parser>, + T: Parser>, { move |i| { - let (i, _) = prefix.parse(i)?; + let (i, s) = prefix.parse(i)?; let mut res = Vec::new(); let mut input = i; loop { @@ -50,12 +100,17 @@ where Ok((i, _)) => { input = i; } - Err(Err::Error(_)) => match terminator.parse(i) { + Err(Err::Error(_)) => match terminator.parse(i.clone()) { Ok((i, _)) => { input = i; break; } - Result::Err(Err::Error(e)) => return Err(Err::Failure(e)), + Result::Err(Err::Error(_)) => { + return Err(Err::Failure(ParseError::MissingDelimiter { + opened: s, + tried: i, + })) + } Result::Err(e) => return Err(e), }, Err(e) => return Err(e), @@ -82,22 +137,21 @@ where /// and if there is none it returns a failure. Otherwise completes with an vec of the parsed /// values. /// -pub fn delimited_list1( +pub fn delimited_list1( mut prefix: D, mut seperator: S, mut value: V, mut terminator: T, -) -> impl FnMut(I) -> IResult, E> +) -> impl FnMut(I) -> IResult, ParseError> where I: Clone + InputLength, - V: Parser, - D: Parser, - S: Parser, - T: Parser, - E: ParseError, + V: Parser>, + D: Parser>, + S: Parser>, + T: Parser>, { move |i| { - let (i, _) = prefix.parse(i)?; + let (i, s) = prefix.parse(i)?; let mut res = Vec::new(); let mut input = i; loop { @@ -115,12 +169,17 @@ where Ok((i, _)) => { input = i; } - Err(Err::Error(_)) => match terminator.parse(i) { + Err(Err::Error(_)) => match terminator.parse(i.clone()) { Ok((i, _)) => { input = i; break; } - Result::Err(Err::Error(e)) => return Err(Err::Failure(e)), + Result::Err(Err::Error(_)) => { + return Err(Err::Failure(ParseError::MissingDelimiter { + opened: s, + tried: i, + })) + } Result::Err(e) => return Err(e), }, Err(e) => return Err(e), diff --git a/lib/src/sql/value/serde/ser/statement/begin.rs b/lib/src/sql/value/serde/ser/statement/begin.rs index dadf20b4..6fb005af 100644 --- a/lib/src/sql/value/serde/ser/statement/begin.rs +++ b/lib/src/sql/value/serde/ser/statement/begin.rs @@ -37,7 +37,7 @@ mod tests { #[test] fn default() { - let stmt = BeginStatement::default(); + let stmt = BeginStatement; let value: BeginStatement = stmt.serialize(Serializer.wrap()).unwrap(); assert_eq!(value, stmt); } diff --git a/lib/src/sql/value/serde/ser/statement/break.rs b/lib/src/sql/value/serde/ser/statement/break.rs index ee6502b4..795e9f31 100644 --- a/lib/src/sql/value/serde/ser/statement/break.rs +++ b/lib/src/sql/value/serde/ser/statement/break.rs @@ -37,7 +37,7 @@ mod tests { #[test] fn default() { - let stmt = BreakStatement::default(); + let stmt = BreakStatement; let value: BreakStatement = stmt.serialize(Serializer.wrap()).unwrap(); assert_eq!(value, stmt); } diff --git a/lib/src/sql/value/serde/ser/statement/cancel.rs b/lib/src/sql/value/serde/ser/statement/cancel.rs index 3bef348e..c2f5fadd 100644 --- a/lib/src/sql/value/serde/ser/statement/cancel.rs +++ b/lib/src/sql/value/serde/ser/statement/cancel.rs @@ -37,7 +37,7 @@ mod tests { #[test] fn default() { - let stmt = CancelStatement::default(); + let stmt = CancelStatement; let value: CancelStatement = stmt.serialize(Serializer.wrap()).unwrap(); assert_eq!(value, stmt); } diff --git a/lib/src/sql/value/serde/ser/statement/commit.rs b/lib/src/sql/value/serde/ser/statement/commit.rs index 4ad2f63e..a278d45f 100644 --- a/lib/src/sql/value/serde/ser/statement/commit.rs +++ b/lib/src/sql/value/serde/ser/statement/commit.rs @@ -37,7 +37,7 @@ mod tests { #[test] fn default() { - let stmt = CommitStatement::default(); + let stmt = CommitStatement; let value: CommitStatement = stmt.serialize(Serializer.wrap()).unwrap(); assert_eq!(value, stmt); } diff --git a/lib/src/sql/value/serde/ser/statement/continue.rs b/lib/src/sql/value/serde/ser/statement/continue.rs index ae1fd0d3..829f4585 100644 --- a/lib/src/sql/value/serde/ser/statement/continue.rs +++ b/lib/src/sql/value/serde/ser/statement/continue.rs @@ -37,7 +37,7 @@ mod tests { #[test] fn default() { - let stmt = ContinueStatement::default(); + let stmt = ContinueStatement; let value: ContinueStatement = stmt.serialize(Serializer.wrap()).unwrap(); assert_eq!(value, stmt); } diff --git a/lib/src/sql/value/serde/ser/statement/show/mod.rs b/lib/src/sql/value/serde/ser/statement/show/mod.rs index 2e169add..8ed7814e 100644 --- a/lib/src/sql/value/serde/ser/statement/show/mod.rs +++ b/lib/src/sql/value/serde/ser/statement/show/mod.rs @@ -90,6 +90,7 @@ mod tests { } } + #[allow(clippy::derivable_impls)] impl Default for ShowStatement { fn default() -> Self { ShowStatement { diff --git a/lib/src/sql/value/value.rs b/lib/src/sql/value/value.rs index ab744c83..2f0335f0 100644 --- a/lib/src/sql/value/value.rs +++ b/lib/src/sql/value/value.rs @@ -8,19 +8,20 @@ use crate::fnc::util::string::fuzzy::Fuzzy; use crate::sql::array::Uniq; use crate::sql::array::{array, Array}; use crate::sql::block::{block, Block}; +use crate::sql::builtin::builtin_name; use crate::sql::bytes::Bytes; use crate::sql::cast::{cast, Cast}; use crate::sql::comment::mightbespace; use crate::sql::common::commas; -use crate::sql::constant::{constant, Constant}; +use crate::sql::constant::Constant; use crate::sql::datetime::{datetime, Datetime}; use crate::sql::duration::{duration, Duration}; use crate::sql::edges::{edges, Edges}; use crate::sql::ending::keyword; use crate::sql::error::IResult; -use crate::sql::expression::{binary, unary, Expression}; +use crate::sql::expression::{unary, Expression}; use crate::sql::fmt::{Fmt, Pretty}; -use crate::sql::function::{function, Function}; +use crate::sql::function::{builtin_function, defined_function, Function}; use crate::sql::future::{future, Future}; use crate::sql::geometry::{geometry, Geometry}; use crate::sql::id::{Gen, Id}; @@ -39,7 +40,7 @@ use crate::sql::subquery::{subquery, Subquery}; use crate::sql::table::{table, Table}; use crate::sql::thing::{thing, Thing}; use crate::sql::uuid::{uuid as unique, Uuid}; -use crate::sql::{operator, Query}; +use crate::sql::{builtin, operator, Query}; use async_recursion::async_recursion; use chrono::{DateTime, Utc}; use derive::Store; @@ -47,7 +48,7 @@ use geo::Point; use nom::branch::alt; use nom::bytes::complete::tag_no_case; use nom::character::complete::char; -use nom::combinator::{self, into, opt}; +use nom::combinator::{self, cut, into, opt}; use nom::multi::separated_list0; use nom::multi::separated_list1; use nom::sequence::terminated; @@ -2705,28 +2706,24 @@ impl TryNeg for Value { /// Parse any `Value` including expressions pub fn value(i: &str) -> IResult<&str, Value> { let (i, start) = single(i)?; - let (i, expr_tail) = opt(|i| { - let (i, o) = operator::binary(i)?; - let (i, r) = value(i)?; - Ok((i, (o, r))) - })(i)?; - let v = if let Some((o, r)) = expr_tail { + if let (i, Some(o)) = opt(operator::binary)(i)? { + let (i, r) = cut(value)(i)?; let expr = match r { Value::Expression(r) => r.augment(start, o), _ => Expression::new(start, o, r), }; - Value::from(expr) + let v = Value::from(expr); + Ok((i, v)) } else { - start - }; - Ok((i, v)) + Ok((i, start)) + } } /// Parse any `Value` excluding binary expressions pub fn single(i: &str) -> IResult<&str, Value> { // Dive in `single` (as opposed to `value`) since it is directly // called by `Cast` - let _diving = crate::sql::parser::depth::dive()?; + let _diving = crate::sql::parser::depth::dive(i)?; let (i, v) = alt(( alt(( terminated( @@ -2743,10 +2740,9 @@ pub fn single(i: &str) -> IResult<&str, Value> { alt(( into(future), into(cast), - into(function), + function_or_const, into(geometry), into(subquery), - into(constant), into(datetime), into(duration), into(unique), @@ -2770,11 +2766,10 @@ pub fn single(i: &str) -> IResult<&str, Value> { reparse_idiom_start(v, i) } -pub fn select(i: &str) -> IResult<&str, Value> { +pub fn select_start(i: &str) -> IResult<&str, Value> { let (i, v) = alt(( alt(( into(unary), - into(binary), combinator::value(Value::None, tag_no_case("NONE")), combinator::value(Value::Null, tag_no_case("NULL")), combinator::value(Value::Bool(true), tag_no_case("true")), @@ -2784,10 +2779,9 @@ pub fn select(i: &str) -> IResult<&str, Value> { alt(( into(future), into(cast), - into(function), + function_or_const, into(geometry), into(subquery), - into(constant), into(datetime), into(duration), into(unique), @@ -2808,13 +2802,45 @@ pub fn select(i: &str) -> IResult<&str, Value> { reparse_idiom_start(v, i) } +pub fn function_or_const(i: &str) -> IResult<&str, Value> { + alt((into(defined_function), |i| { + let (i, v) = builtin_name(i)?; + match v { + builtin::BuiltinName::Constant(x) => Ok((i, x.into())), + builtin::BuiltinName::Function(name) => { + builtin_function(name, i).map(|(i, v)| (i, v.into())) + } + } + }))(i) +} + +pub fn select(i: &str) -> IResult<&str, Value> { + let _diving = crate::sql::parser::depth::dive(i)?; + let (i, start) = select_start(i)?; + if let (i, Some(op)) = opt(operator::binary)(i)? { + // In a binary expression single ident's arent tables but paths. + let start = match start { + Value::Table(Table(x)) => Value::Idiom(Idiom::from(x)), + x => x, + }; + let (i, r) = cut(value)(i)?; + let expr = match r { + Value::Expression(r) => r.augment(start, op), + _ => Expression::new(start, op, r), + }; + let v = Value::from(expr); + Ok((i, v)) + } else { + Ok((i, start)) + } +} + /// Used in CREATE, UPDATE, and DELETE clauses pub fn what(i: &str) -> IResult<&str, Value> { let (i, v) = alt(( into(idiom::multi_without_start), - into(function), + function_or_const, into(subquery), - into(constant), into(datetime), into(duration), into(future), @@ -2831,7 +2857,7 @@ pub fn what(i: &str) -> IResult<&str, Value> { /// Used to parse any simple JSON-like value pub fn json(i: &str) -> IResult<&str, Value> { - let _diving = crate::sql::parser::depth::dive()?; + let _diving = crate::sql::parser::depth::dive(i)?; // Use a specific parser for JSON objects fn object(i: &str) -> IResult<&str, Object> { let (i, _) = char('{')(i)?; diff --git a/lib/src/sql/view.rs b/lib/src/sql/view.rs index ce31218a..caf858bd 100644 --- a/lib/src/sql/view.rs +++ b/lib/src/sql/view.rs @@ -12,6 +12,8 @@ use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt; +use super::error::{expect_tag_no_case, expected}; + #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] #[revisioned(revision = 1)] pub struct View { @@ -41,7 +43,7 @@ pub fn view(i: &str) -> IResult<&str, View> { let (i, _) = shouldbespace(i)?; let (i, expr) = fields(i)?; let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("FROM")(i)?; + let (i, _) = expect_tag_no_case("FROM")(i)?; let (i, _) = shouldbespace(i)?; let (i, what) = tables(i)?; let (i, cond) = opt(preceded(shouldbespace, cond))(i)?; @@ -61,7 +63,8 @@ pub fn view(i: &str) -> IResult<&str, View> { let (i, _) = tag_no_case("AS")(i)?; let (i, _) = shouldbespace(i)?; - let (i, (expr, what, cond, group)) = alt((select_view, select_view_delimited))(i)?; + let (i, (expr, what, cond, group)) = + expected("SELECT or `(`", cut(alt((select_view, select_view_delimited))))(i)?; Ok(( i, View { diff --git a/lib/tests/complex.rs b/lib/tests/complex.rs index 86e071ae..47d58edc 100644 --- a/lib/tests/complex.rs +++ b/lib/tests/complex.rs @@ -190,9 +190,17 @@ fn excessive_cast_chain_depth() -> Result<(), Error> { assert_eq!(res.len(), 1); // let tmp = res.next().unwrap(); - assert!(matches!(tmp, Err(Error::ComputationDepthExceeded))); + assert!( + matches!(tmp, Err(Error::ComputationDepthExceeded)), + "didn't return a computation depth exceeded: {:?}", + tmp + ); } - Err(e) => assert!(matches!(e, Error::ComputationDepthExceeded)), + Err(e) => assert!( + matches!(e, Error::InvalidQuery(_)), + "didn't return a computation depth exceeded: {:?}", + e + ), } // Ok(())