From 3c92765fada8e125972890683a35901fee6a03f3 Mon Sep 17 00:00:00 2001 From: Mees Delzenne Date: Tue, 12 Mar 2024 16:43:56 +0100 Subject: [PATCH] Fix export generating unparsable code with the new parser (#3351) --- core/src/sql/v1/escape.rs | 13 +++++++++ core/src/sql/v1/param.rs | 2 +- core/src/syn/v2/lexer/keywords.rs | 47 ++++++++++++++++++++++++++++++- core/src/syn/v2/lexer/mod.rs | 2 +- core/src/syn/v2/mod.rs | 5 ++++ lib/tests/define.rs | 33 ++++++++++++++++++++++ 6 files changed, 99 insertions(+), 3 deletions(-) diff --git a/core/src/sql/v1/escape.rs b/core/src/sql/v1/escape.rs index 5b60e28f..59f17afa 100644 --- a/core/src/sql/v1/escape.rs +++ b/core/src/sql/v1/escape.rs @@ -89,12 +89,20 @@ pub fn escape_key(s: &str) -> Cow<'_, str> { #[inline] /// Escapes an id if necessary pub fn escape_rid(s: &str) -> Cow<'_, str> { + #[cfg(feature = "experimental-parser")] + if let Some(x) = escape_reserved_keyword(s) { + return Cow::Owned(x); + } escape_numeric(s, BRACKETL, BRACKETR, BRACKET_ESC) } #[inline] /// Escapes an ident if necessary pub fn escape_ident(s: &str) -> Cow<'_, str> { + #[cfg(feature = "experimental-parser")] + if let Some(x) = escape_reserved_keyword(s) { + return Cow::Owned(x); + } escape_numeric(s, BACKTICK, BACKTICK, BACKTICK_ESC) } @@ -136,6 +144,11 @@ pub fn escape_numeric<'a>(s: &'a str, l: char, r: char, e: &str) -> Cow<'a, str> } } +#[cfg(feature = "experimental-parser")] +pub fn escape_reserved_keyword(s: &str) -> Option { + crate::syn::v2::could_be_reserved_keyword(s).then(|| format!("`{}`", s)) +} + #[cfg(feature = "experimental-parser")] #[inline] pub fn escape_numeric<'a>(s: &'a str, l: char, r: char, e: &str) -> Cow<'a, str> { diff --git a/core/src/sql/v1/param.rs b/core/src/sql/v1/param.rs index 5cd7d3eb..2e729403 100644 --- a/core/src/sql/v1/param.rs +++ b/core/src/sql/v1/param.rs @@ -114,6 +114,6 @@ impl Param { impl fmt::Display for Param { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "${}", &self.0) + write!(f, "${}", &self.0 .0) } } diff --git a/core/src/syn/v2/lexer/keywords.rs b/core/src/syn/v2/lexer/keywords.rs index 5ddccce9..8197c23e 100644 --- a/core/src/syn/v2/lexer/keywords.rs +++ b/core/src/syn/v2/lexer/keywords.rs @@ -3,9 +3,54 @@ use crate::{ sql::{language::Language, Algorithm}, syn::v2::token::{DistanceKind, Keyword, TokenKind}, }; -use phf::phf_map; +use phf::{phf_map, phf_set}; use unicase::UniCase; +/// A set of keywords which might in some contexts are dissallowed as an identifier. +pub static RESERVED_KEYWORD: phf::Set> = phf_set! { + UniCase::ascii("ANALYZE"), + UniCase::ascii("BEGIN"), + UniCase::ascii("BREAK"), + UniCase::ascii("CANCEL"), + UniCase::ascii("COMMIT"), + UniCase::ascii("CONTINUE"), + UniCase::ascii("CREATE"), + UniCase::ascii("DEFINE"), + UniCase::ascii("FOR"), + UniCase::ascii("IF"), + UniCase::ascii("INFO"), + UniCase::ascii("INSERT"), + UniCase::ascii("KILL"), + UniCase::ascii("LIVE"), + UniCase::ascii("OPTION"), + UniCase::ascii("RETURN"), + UniCase::ascii("RELATE"), + UniCase::ascii("REMOVE"), + UniCase::ascii("SELECT"), + UniCase::ascii("LET"), + UniCase::ascii("SHOW"), + UniCase::ascii("SLEEP"), + UniCase::ascii("THROW"), + UniCase::ascii("UPDATE"), + UniCase::ascii("USE"), + UniCase::ascii("DIFF"), + UniCase::ascii("RAND"), + UniCase::ascii("NONE"), + UniCase::ascii("NULL"), + UniCase::ascii("AFTER"), + UniCase::ascii("BEFORE"), + UniCase::ascii("VALUE"), + UniCase::ascii("BY"), + UniCase::ascii("ALL"), + UniCase::ascii("TRUE"), + UniCase::ascii("FALSE"), + UniCase::ascii("WHERE"), +}; + +pub fn could_be_reserved(s: &str) -> bool { + RESERVED_KEYWORD.contains(&UniCase::ascii(s)) +} + /// A map for mapping keyword strings to a tokenkind, pub(crate) static KEYWORDS: phf::Map, TokenKind> = phf_map! { // Keywords diff --git a/core/src/syn/v2/lexer/mod.rs b/core/src/syn/v2/lexer/mod.rs index 1b64da0f..9ffb6aad 100644 --- a/core/src/syn/v2/lexer/mod.rs +++ b/core/src/syn/v2/lexer/mod.rs @@ -10,7 +10,7 @@ mod datetime; mod duration; mod ident; mod js; -mod keywords; +pub mod keywords; mod number; mod reader; mod strand; diff --git a/core/src/syn/v2/mod.rs b/core/src/syn/v2/mod.rs index 7b514d6d..a980500c 100644 --- a/core/src/syn/v2/mod.rs +++ b/core/src/syn/v2/mod.rs @@ -15,6 +15,11 @@ mod test; use lexer::Lexer; use parser::{ParseError, ParseErrorKind, Parser}; +/// Takes a string and returns if it could be a reserved keyword in certain contexts. +pub fn could_be_reserved_keyword(s: &str) -> bool { + lexer::keywords::could_be_reserved(s) +} + /// Parses a SurrealQL [`Query`] /// /// During query parsing, the total depth of calls to parse values (including arrays, expressions, diff --git a/lib/tests/define.rs b/lib/tests/define.rs index dbb74419..f393e9c6 100644 --- a/lib/tests/define.rs +++ b/lib/tests/define.rs @@ -345,6 +345,17 @@ async fn define_statement_event() -> Result<(), Error> { assert!(tmp.is_ok()); // let tmp = res.remove(0).result?; + #[cfg(feature = "parser2")] + let val = Value::parse( + "{ + events: { test: 'DEFINE EVENT test ON user WHEN true THEN (CREATE activity SET user = $this, `value` = $after.email, action = $event)' }, + fields: {}, + tables: {}, + indexes: {}, + lives: {}, + }", + ); + #[cfg(not(feature = "parser2"))] let val = Value::parse( "{ events: { test: 'DEFINE EVENT test ON user WHEN true THEN (CREATE activity SET user = $this, value = $after.email, action = $event)' }, @@ -403,6 +414,17 @@ async fn define_statement_event_when_event() -> Result<(), Error> { assert!(tmp.is_ok()); // let tmp = res.remove(0).result?; + #[cfg(feature = "parser2")] + let val = Value::parse( + r#"{ + events: { test: "DEFINE EVENT test ON user WHEN $event = 'CREATE' THEN (CREATE activity SET user = $this, `value` = $after.email, action = $event)" }, + fields: {}, + tables: {}, + indexes: {}, + lives: {}, + }"#, + ); + #[cfg(not(feature = "parser2"))] let val = Value::parse( r#"{ events: { test: "DEFINE EVENT test ON user WHEN $event = 'CREATE' THEN (CREATE activity SET user = $this, value = $after.email, action = $event)" }, @@ -461,6 +483,17 @@ async fn define_statement_event_when_logic() -> Result<(), Error> { assert!(tmp.is_ok()); // let tmp = res.remove(0).result?; + #[cfg(feature = "parser2")] + let val = Value::parse( + "{ + events: { test: 'DEFINE EVENT test ON user WHEN $before.email != $after.email THEN (CREATE activity SET user = $this, `value` = $after.email, action = $event)' }, + fields: {}, + tables: {}, + indexes: {}, + lives: {}, + }", + ); + #[cfg(not(feature = "parser2"))] let val = Value::parse( "{ events: { test: 'DEFINE EVENT test ON user WHEN $before.email != $after.email THEN (CREATE activity SET user = $this, value = $after.email, action = $event)' },