Improved error messages (#2566)

This commit is contained in:
Mees Delzenne 2023-09-08 13:28:36 +02:00 committed by GitHub
parent fe78ca3c32
commit b02567d233
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
90 changed files with 2225 additions and 1268 deletions

View file

@ -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")]

View file

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

View file

@ -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 {
"", "doarm", "în", "coș", "lui", "decât", "", "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"],
);

View file

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

View file

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

View file

@ -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,6 +37,8 @@ impl fmt::Display for Base {
}
pub fn base(i: &str) -> IResult<&str, Base> {
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")),
@ -42,7 +46,8 @@ pub fn base(i: &str) -> IResult<&str, Base> {
value(Base::Ns, tag_no_case("NS")),
value(Base::Db, tag_no_case("DB")),
value(Base::Root, tag_no_case("KV")),
))(i)
)),
)(i)
}
pub fn base_or_scope(i: &str) -> IResult<&str, Base> {

View file

@ -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)?;
expect_delimited(
openbraces,
|i| {
let (i, v) = separated_list0(colons, entry)(i)?;
let (i, _) = many0(colons)(i)?;
let (i, _) = closebraces(i)?;
Ok((i, Block(v)))
},
closebraces,
)(i)
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]

485
lib/src/sql/builtin.rs Normal file
View file

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

View file

@ -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::<u64>() {
Ok(v) => Ok((i, v)),
_ => Err(Error(Parser(i))),
}
map_res(take_while(is_digit), |s: &str| s.parse::<u64>())(i)
}
pub fn take_u32_len(i: &str) -> IResult<&str, (u32, usize)> {
let (i, v) = take_while(is_digit)(i)?;
match v.parse::<u32>() {
Ok(n) => Ok((i, (n, v.len()))),
_ => Err(Error(Parser(i))),
}
map_res(take_while(is_digit), |s: &str| s.parse::<u32>().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::<u32>() {
Ok(v) => Ok((i, v)),
_ => Err(Error(Parser(i))),
}
map_res(take_while_m_n(n, n, is_digit), |s: &str| s.parse::<u32>())(i)
}
pub fn take_digits_range(i: &str, n: usize, range: impl RangeBounds<u32>) -> IResult<&str, u32> {
let (i, v) = take_while_m_n(n, n, is_digit)(i)?;
match v.parse::<u32>() {
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,
})),
}
}

View file

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

View file

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

View file

@ -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> {
expected("a duration", |i| {
let (i, v) = many1(duration_raw)(i)?;
let (i, _) = ending(i)?;
Ok((i, v.iter().sum::<Duration>()))
})(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> {

View file

@ -87,27 +87,39 @@ pub fn field(i: &str) -> IResult<&str, ()> {
}
pub fn subquery(i: &str) -> IResult<&str, ()> {
alt((
|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, ()))
},
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('}')),
|i| {
let (i, _) = mightbespace(i)?;
alt((
value((), eof),
value((), char(';')),
value((), char(',')),
value((), eof),
))),
value((), char('}')),
value((), char(')')),
value((), char(']')),
))(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)
}

View file

@ -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<I> {
Parser(I),
ExcessiveDepth,
Field(I, String),
Split(I, String),
Order(I, String),
Group(I, String),
Role(I, String),
}
pub type IResult<I, O, E = Error<I>> = Result<(I, O), Err<E>>;
impl<I> ParseError<I> for Error<I> {
fn from_error_kind(input: I, _: ErrorKind) -> Self {
Self::Parser(input)
}
fn append(_: I, _: ErrorKind, other: Self) -> Self {
other
}
}

478
lib/src/sql/error/mod.rs Normal file
View file

@ -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<I> {
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<u32>,
upper: Bound<u32>,
},
InvalidUnicode {
tried: I,
},
InvalidPath {
tried: I,
parent: I,
},
}
impl<I: Clone> ParseError<I> {
/// 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<I, O, E = ParseError<I>> = Result<(I, O), Err<E>>;
impl<I> FromExternalError<I, ParseIntError> for ParseError<I> {
fn from_external_error(input: I, _kind: ErrorKind, e: ParseIntError) -> Self {
ParseError::ParseInt {
error: e,
tried: input,
}
}
}
impl<I> FromExternalError<I, ParseFloatError> for ParseError<I> {
fn from_external_error(input: I, _kind: ErrorKind, e: ParseFloatError) -> Self {
ParseError::ParseFloat {
error: e,
tried: input,
}
}
}
impl<I> FromExternalError<I, regex::Error> for ParseError<I> {
fn from_external_error(input: I, _kind: ErrorKind, e: regex::Error) -> Self {
ParseError::ParseRegex {
error: e,
tried: input,
}
}
}
impl<I> NomParseError<I> for ParseError<I> {
fn from_error_kind(input: I, _: ErrorKind) -> Self {
Self::Base(input)
}
fn append(_: I, _: ErrorKind, other: Self) -> Self {
other
}
}

144
lib/src/sql/error/render.rs Normal file
View file

@ -0,0 +1,144 @@
use std::fmt;
use super::Location;
#[derive(Clone, Debug)]
pub struct RenderedError {
pub text: String,
pub snippets: Vec<Snippet>,
}
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<String>,
}
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(())
}
}

134
lib/src/sql/error/utils.rs Normal file
View file

@ -0,0 +1,134 @@
use super::{IResult, ParseError};
use nom::bytes::complete::tag_no_case;
use nom::Err;
use nom::Parser;
pub fn expected<I, O, P>(expect: &'static str, mut parser: P) -> impl FnMut(I) -> IResult<I, O>
where
P: Parser<I, O, ParseError<I>>,
{
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<I, O> {
/// 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<P, O1>(self, explain: &'static str, condition: P) -> Self
where
P: Parser<I, O1, ParseError<I>>;
}
impl<I: Clone, O> ExplainResultExt<I, O> for IResult<I, O> {
fn explain<P, O1>(self, explain: &'static str, mut condition: P) -> Self
where
P: Parser<I, O1, ParseError<I>>,
{
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,
}
}

View file

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

View file

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

View file

@ -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)?;

View file

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

View file

@ -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,7 +109,9 @@ fn simple(i: &str) -> IResult<&str, (Tables, Option<Cond>, Option<Idiom>)> {
}
fn custom(i: &str) -> IResult<&str, (Tables, Option<Cond>, Option<Idiom>)> {
let (i, _) = openparentheses(i)?;
expect_delimited(
openparentheses,
|i| {
let (i, w) = alt((any, tables))(i)?;
let (i, c) = opt(|i| {
let (i, _) = shouldbespace(i)?;
@ -121,8 +125,10 @@ fn custom(i: &str) -> IResult<&str, (Tables, Option<Cond>, Option<Idiom>)> {
let (i, v) = idiom(i)?;
Ok((i, v))
})(i)?;
let (i, _) = closeparentheses(i)?;
Ok((i, (w, c, a)))
},
closeparentheses,
)(i)
}
fn one(i: &str) -> IResult<&str, Tables> {

View file

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

View file

@ -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,7 +194,8 @@ 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)?;
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)?;
@ -200,22 +204,27 @@ pub fn local(i: &str) -> IResult<&str, Idiom> {
}
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)?;
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> {
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.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<O>(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<Diving, Err<crate::sql::Error<&'static str>>> {
pub(crate) fn dive<I>(position: I) -> Result<Diving, Err<crate::sql::ParseError<I>>> {
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
);

View file

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

View file

@ -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> {
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)
)),
)(i)
}
fn rule(i: &str) -> IResult<&str, Vec<(PermissionKind, Permission)>> {

View file

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

View file

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

View file

@ -1,84 +0,0 @@
use std::fmt;
use nom::{error::ParseError, Err, Parser};
use super::error::IResult;
#[derive(Debug)]
pub enum Error<I> {
/// An error because a keyword
Keyword{
input: I,
kind: nom::error::ErrorKind,
},
Base {
kind: nom::error::ErrorKind,
input: I,
},
}
pub fn cut_keyword<I, K, P, O, O1>(
mut keyword: K,
mut parser: P,
) -> impl FnMut(I) -> IResult<I, O, Error<I>>
where
K: Parser<I, I, Error<I>>,
P: Parser<I, O, Error<I>>,
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<P, W, I, O>(
mut parser: P,
mut with: W,
) -> impl FnMut(I) -> IResult<I, O, Error<I>>
where
I: Clone,
P: Parser<I, O, Error<I>>,
W: Parser<I, O, Error<I>>,
{
move |input: I| match parser.parse(input.clone()) {
Err(Err::Failure(Error::KeywordError {
keyword,
failure_input,
kind,
})) => {
match parser.parse()
}
x => x,
}
}
impl<I: fmt::Display> fmt::Display for Error<I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "todo")
}
}
impl<I> ParseError<I> for Error<I> {
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
}
}

View file

@ -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,14 +83,12 @@ 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::<f32>().map_err(|_| Failure(Parser(i)))?;
let (i, _) = commas(i)?;
let (i, b) = recognize_float(i)?;
let b = b.parse::<f32>().map_err(|_| Failure(Parser(i)))?;
let (i, _) = closeparentheses(i)?;
expect_delimited(
openparentheses,
|i| {
let (i, k1) = cut(map_res(recognize_float, |x: &str| x.parse::<f32>()))(i)?;
let (i, _) = cut(commas)(i)?;
let (i, b) = cut(map_res(recognize_float, |x: &str| x.parse::<f32>()))(i)?;
Ok((
i,
Scoring::Bm {
@ -98,7 +96,9 @@ pub fn scoring(i: &str) -> IResult<&str, Scoring> {
b,
},
))
})(i)
},
closeparentheses,
)(i)
},
value(Scoring::bm25(), tag_no_case("BM25")),
))(i)

View file

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

View file

@ -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<Splits>,
) -> Result<(), Err<Error<&'a str>>> {
) -> Result<(), Err<ParseError<&'a str>>> {
// 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<Orders>,
) -> Result<(), Err<Error<&'a str>>> {
) -> Result<(), Err<ParseError<&'a str>>> {
// 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<Groups>,
) -> Result<(), Err<Error<&'a str>>> {
) -> Result<(), Err<ParseError<&'a str>>> {
// 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())));
}
}
}

View file

@ -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)?;
let (i, v) = cut(separated_list1(commas, split_raw))(i)?;
Ok((i, Splits(v)))
})(i)
}
fn split_raw(i: &str) -> IResult<&str, Split> {

View file

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

View file

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

View file

@ -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<u8> = (&out).try_into().unwrap();
let deserialized = DefineDatabaseStatement::try_from(&serialized).unwrap();

View file

@ -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, what, opts)) = cut(|i| {
let (i, name) = 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, 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)))
}

View file

@ -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, what, opts)) = cut(|i| {
let (i, name) = 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, 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)))
}

View file

@ -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,16 +97,15 @@ 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, args) = delimited_list0(
openparentheses,
commas,
|i| {
let (i, _) = char('$')(i)?;
let (i, name) = ident(i)?;
let (i, _) = mightbespace(i)?;
@ -109,12 +113,13 @@ pub fn function(i: &str) -> IResult<&str, DefineFunctionStatement> {
let (i, _) = mightbespace(i)?;
let (i, kind) = kind(i)?;
Ok((i, (name, kind)))
})(i)?;
let (i, _) = mightbespace(i)?;
let (i, _) = char(')')(i)?;
},
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)))
}

View file

@ -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, what, opts)) = cut(|i| {
let (i, name) = 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, 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,

View file

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

View file

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

View file

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

View file

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

View file

@ -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<u8> = (&out).try_into().unwrap();
let deserialized = DefineTableStatement::try_from(&serialized).unwrap();

View file

@ -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, base, opts)) = cut(|i| {
let (i, name) = 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, 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)))
}

View file

@ -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, base, opts)) = cut(|i| {
let (i, name) = 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, 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<DefineUserOption>> {
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())))?;

View file

@ -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)?;
Ok((
i,
DeleteStatement {

View file

@ -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)?;

View file

@ -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)?;
let (i, scope) = cut(ident)(i)?;
Ok((i, InfoStatement::Sc(scope)))
})(i)
}
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)?;
let (i, table) = cut(ident)(i)?;
Ok((i, InfoStatement::Tb(table)))
})(i)
}
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)?;

View file

@ -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)?;

View file

@ -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)?;

View file

@ -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((
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)?;
)))),
)(i)?;
Ok((
i,
OptionStatement {

View file

@ -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)?;

View file

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

View file

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

View file

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

View file

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

View file

@ -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)?;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<I, D, V, T, O, O1>(
mut prefix: D,
mut value: V,
mut terminator: T,
) -> impl FnMut(I) -> IResult<I, O, ParseError<I>>
where
I: Clone + InputLength,
V: Parser<I, O, ParseError<I>>,
D: Parser<I, I, ParseError<I>>,
T: Parser<I, O1, ParseError<I>>,
{
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<P, I, O>(
open_span: I,
mut terminator: P,
) -> impl FnMut(I) -> IResult<I, O, ParseError<I>>
where
I: Clone,
P: Parser<I, O, ParseError<I>>,
{
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<I, E, D, S, V, T, O, O1, O2, O3>(
pub fn delimited_list0<I, D, S, V, T, O, O1, O2>(
mut prefix: D,
mut seperator: S,
mut value: V,
mut terminator: T,
) -> impl FnMut(I) -> IResult<I, Vec<O>, E>
) -> impl FnMut(I) -> IResult<I, Vec<O>, ParseError<I>>
where
I: Clone + InputLength,
V: Parser<I, O, E>,
D: Parser<I, O1, E>,
S: Parser<I, O2, E>,
T: Parser<I, O3, E>,
E: ParseError<I>,
V: Parser<I, O, ParseError<I>>,
D: Parser<I, I, ParseError<I>>,
S: Parser<I, O1, ParseError<I>>,
T: Parser<I, O2, ParseError<I>>,
{
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<I, E, D, S, V, T, O, O1, O2, O3>(
pub fn delimited_list1<I, D, S, V, T, O, O1, O2>(
mut prefix: D,
mut seperator: S,
mut value: V,
mut terminator: T,
) -> impl FnMut(I) -> IResult<I, Vec<O>, E>
) -> impl FnMut(I) -> IResult<I, Vec<O>, ParseError<I>>
where
I: Clone + InputLength,
V: Parser<I, O, E>,
D: Parser<I, O1, E>,
S: Parser<I, O2, E>,
T: Parser<I, O3, E>,
E: ParseError<I>,
V: Parser<I, O, ParseError<I>>,
D: Parser<I, I, ParseError<I>>,
S: Parser<I, O1, ParseError<I>>,
T: Parser<I, O2, ParseError<I>>,
{
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),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -90,6 +90,7 @@ mod tests {
}
}
#[allow(clippy::derivable_impls)]
impl Default for ShowStatement {
fn default() -> Self {
ShowStatement {

View file

@ -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)
} else {
start
};
let v = Value::from(expr);
Ok((i, v))
} else {
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)?;

View file

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

View file

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