diff --git a/server/src/engine/ql/ast.rs b/server/src/engine/ql/ast.rs index 8f9f96c0..36f07920 100644 --- a/server/src/engine/ql/ast.rs +++ b/server/src/engine/ql/ast.rs @@ -61,16 +61,16 @@ impl Entity { Entity::Partial(extract!(&sl[1], Token::Ident(sl) => sl.clone())) } #[inline(always)] - pub(super) fn tokens_with_partial(sl: &[Token]) -> bool { - sl.len() > 1 && sl[0] == Token![:] && sl[1].is_ident() + pub(super) fn tokens_with_partial(tok: &[Token]) -> bool { + tok.len() > 1 && tok[0] == Token![:] && tok[1].is_ident() } #[inline(always)] - pub(super) fn tokens_with_single(sl: &[Token]) -> bool { - !sl.is_empty() && sl[0].is_ident() + pub(super) fn tokens_with_single(tok: &[Token]) -> bool { + !tok.is_empty() && tok[0].is_ident() } #[inline(always)] - pub(super) fn tokens_with_full(sl: &[Token]) -> bool { - sl.len() > 2 && sl[0].is_ident() && sl[1] == Token![.] && sl[2].is_ident() + pub(super) fn tokens_with_full(tok: &[Token]) -> bool { + tok.len() > 2 && tok[0].is_ident() && tok[1] == Token![.] && tok[2].is_ident() } pub(super) fn parse(cm: &mut Compiler) -> LangResult { let sl = cm.remslice(); diff --git a/server/src/engine/ql/dml.rs b/server/src/engine/ql/dml.rs index bce853fd..9ed22f72 100644 --- a/server/src/engine/ql/dml.rs +++ b/server/src/engine/ql/dml.rs @@ -24,21 +24,42 @@ * */ -use crate::util::MaybeInit; +/* + TODO(@ohsayan): For now we've settled for an imprecise error site reporting for simplicity, which we + should augment in future revisions of the QL engine +*/ use { super::{ ast::Entity, lexer::{Lit, Symbol, Token}, - LangError, LangResult, + LangError, LangResult, RawSlice, }, - crate::engine::memory::DataType, + crate::{engine::memory::DataType, util::MaybeInit}, std::{ collections::HashMap, mem::{discriminant, Discriminant}, }, }; +/* + Misc +*/ + +#[inline(always)] +fn process_entity(tok: &[Token], d: &mut MaybeInit, i: &mut usize) -> bool { + let is_full = Entity::tokens_with_full(tok); + let is_single = Entity::tokens_with_single(tok); + if is_full { + *i += 3; + *d = MaybeInit::new(unsafe { Entity::full_entity_from_slice(tok) }) + } else if is_single { + *i += 1; + *d = MaybeInit::new(unsafe { Entity::single_entity_from_slice(tok) }); + } + is_full | is_single +} + /* Impls for insert */ @@ -155,7 +176,8 @@ pub(super) fn parse_data_tuple_syntax(tok: &[Token]) -> (Vec>, #[cfg(test)] pub(super) fn parse_data_tuple_syntax_full(tok: &[Token]) -> Option>> { let (ret, cnt, okay) = parse_data_tuple_syntax(tok); - if cnt == tok.len() && okay { + assert!(cnt == tok.len(), "didn't use full length"); + if okay { Some(ret) } else { None @@ -222,7 +244,8 @@ pub(super) fn parse_data_map_syntax_full( tok: &[Token], ) -> Option, Option>> { let (dat, i, ok) = parse_data_map_syntax(tok); - if i == tok.len() && ok { + assert!(i == tok.len(), "didn't use full length"); + if ok { Some( dat.into_iter() .map(|(ident, val)| { @@ -280,13 +303,7 @@ pub(super) fn parse_insert<'a>( let mut i = 0; let mut entity = MaybeInit::uninit(); - if is_full { - i += 3; - entity = MaybeInit::new(unsafe { Entity::full_entity_from_slice(src) }); - } else if is_half { - i += 1; - entity = MaybeInit::new(unsafe { Entity::single_entity_from_slice(src) }); - } + okay &= process_entity(&src[i..], &mut entity, &mut i); // primary key is a lit; atleast lit + () | () okay &= l >= (i + 4); @@ -334,9 +351,84 @@ pub(super) fn parse_insert<'a>( pub(super) fn parse_insert_full<'a>(tok: &'a [Token]) -> Option> { let mut z = 0; let s = self::parse_insert(tok, &mut z); - if z == tok.len() { - s.ok() + assert!(z == tok.len(), "didn't use full length"); + s.ok() +} + +/* + Impls for select +*/ + +#[derive(Debug, PartialEq)] +pub(super) struct SelectStatement<'a> { + /// the primary key + pub(super) primary_key: &'a Lit, + /// the entity + pub(super) entity: Entity, + /// fields in order of querying. will be zero when wildcard is set + pub(super) fields: Vec, + /// whether a wildcard was passed + pub(super) wildcard: bool, +} + +/// Parse a `select` query. The cursor should have already passed the `select` token when this +/// function is called. +pub(super) fn parse_select<'a>( + tok: &'a [Token], + counter: &mut usize, +) -> LangResult> { + let l = tok.len(); + + let mut i = 0_usize; + let mut okay = l > 4; + let mut fields = Vec::new(); + let is_wildcard = i < l && tok[i] == Token![*]; + i += is_wildcard as usize; + + while okay && i < l && tok[i].is_ident() && !is_wildcard { + unsafe { + fields.push(extract!(&tok[i], Token::Ident(id) => id.clone())); + } + i += 1; + // skip comma + let nx_comma = i < l && tok[i] == Token![,]; + let nx_from = i < l && tok[i] == Token![from]; + okay &= nx_comma | nx_from; + i += nx_comma as usize; + } + + okay &= i < l && tok[i] == Token![from]; + i += okay as usize; + + // parsed upto select a, b, c from ...; now parse entity and select + let mut entity = MaybeInit::uninit(); + okay &= process_entity(&tok[i..], &mut entity, &mut i); + + // now primary key + okay &= i < l && tok[i] == Token![:]; + i += okay as usize; + okay &= i < l && tok[i].is_lit(); + + *counter += i + okay as usize; + + if okay { + let primary_key = unsafe { extract!(tok[i], Token::Lit(ref l) => l) }; + Ok(SelectStatement { + primary_key, + entity: unsafe { entity.assume_init() }, + fields, + wildcard: is_wildcard, + }) } else { - None + Err(LangError::UnexpectedToken) } } + +#[cfg(test)] +/// **test-mode only** parse for a `select` where the full token stream is exhausted +pub(super) fn parse_select_full<'a>(tok: &'a [Token]) -> Option> { + let mut i = 0; + let r = self::parse_select(tok, &mut i); + assert!(i == tok.len(), "didn't use full length"); + r.ok() +} diff --git a/server/src/engine/ql/tests.rs b/server/src/engine/ql/tests.rs index fe93f994..5348591a 100644 --- a/server/src/engine/ql/tests.rs +++ b/server/src/engine/ql/tests.rs @@ -1954,4 +1954,75 @@ mod dml_tests { assert_eq!(r, e); } } + + mod stmt_select { + use { + super::*, + crate::engine::ql::{ + ast::Entity, + dml::{self, SelectStatement}, + lexer::Lit, + }, + }; + #[test] + fn select_mini() { + let tok = lex(br#" + select * from user:"sayan" + "#) + .unwrap(); + let r = dml::parse_select_full(&tok[1..]).unwrap(); + let e = SelectStatement { + primary_key: &Lit::Str("sayan".into()), + entity: Entity::Single("user".into()), + fields: [].to_vec(), + wildcard: true, + }; + assert_eq!(r, e); + } + #[test] + fn select() { + let tok = lex(br#" + select field1 from user:"sayan" + "#) + .unwrap(); + let r = dml::parse_select_full(&tok[1..]).unwrap(); + let e = SelectStatement { + primary_key: &Lit::Str("sayan".into()), + entity: Entity::Single("user".into()), + fields: ["field1".into()].to_vec(), + wildcard: false, + }; + assert_eq!(r, e); + } + #[test] + fn select_pro() { + let tok = lex(br#" + select field1 from twitter.user:"sayan" + "#) + .unwrap(); + let r = dml::parse_select_full(&tok[1..]).unwrap(); + let e = SelectStatement { + primary_key: &Lit::Str("sayan".into()), + entity: Entity::Full("twitter".into(), "user".into()), + fields: ["field1".into()].to_vec(), + wildcard: false, + }; + assert_eq!(r, e); + } + #[test] + fn select_pro_max() { + let tok = lex(br#" + select field1, field2 from twitter.user:"sayan" + "#) + .unwrap(); + let r = dml::parse_select_full(&tok[1..]).unwrap(); + let e = SelectStatement { + primary_key: &Lit::Str("sayan".into()), + entity: Entity::Full("twitter".into(), "user".into()), + fields: ["field1".into(), "field2".into()].to_vec(), + wildcard: false, + }; + assert_eq!(r, e); + } + } } diff --git a/server/src/util/macros.rs b/server/src/util/macros.rs index c82f80d9..3c37cc8c 100644 --- a/server/src/util/macros.rs +++ b/server/src/util/macros.rs @@ -28,9 +28,13 @@ macro_rules! impossible { () => {{ if cfg!(debug_assertions) { - panic!("called unreachable code at: {}:{}", ::core::file!(), ::core::line!()); + panic!( + "reached unreachable case at: {}:{}", + ::core::file!(), + ::core::line!() + ); } else { - core::hint::unreachable_unchecked() + ::core::hint::unreachable_unchecked() } }}; } diff --git a/server/src/util/mod.rs b/server/src/util/mod.rs index c76d30c3..95f6a5d1 100644 --- a/server/src/util/mod.rs +++ b/server/src/util/mod.rs @@ -219,6 +219,9 @@ impl<'a, T: PartialEq> PartialEq for Life<'a, T> { unsafe impl<'a, T: Send> Send for Life<'a, T> {} unsafe impl<'a, T: Sync> Sync for Life<'a, T> {} +/// [`MaybeInit`] is a structure that is like an [`Option`] in debug mode and like +/// [`MaybeUninit`] in release mode. This means that provided there are good enough test cases, most +/// incorrect `assume_init` calls should be detected in the test phase. pub struct MaybeInit { #[cfg(test)] is_init: bool, @@ -226,9 +229,7 @@ pub struct MaybeInit { } impl MaybeInit { - #[cfg(not(test))] - const _SZ_REL: () = - assert!(core::mem::size_of::() == core::mem::size_of::>()); + /// Initialize a new uninitialized variant pub const fn uninit() -> Self { Self { #[cfg(test)] @@ -236,6 +237,7 @@ impl MaybeInit { base: MaybeUninit::uninit(), } } + /// Initialize with a value pub const fn new(val: T) -> Self { Self { #[cfg(test)] @@ -243,6 +245,11 @@ impl MaybeInit { base: MaybeUninit::new(val), } } + /// Assume that `self` is initialized and return the inner value + /// + /// ## Safety + /// + /// Caller needs to ensure that the data is actually initialized pub const unsafe fn assume_init(self) -> T { #[cfg(test)] { @@ -252,6 +259,11 @@ impl MaybeInit { } self.base.assume_init() } + /// Assume that `self` is initialized and return a reference + /// + /// ## Safety + /// + /// Caller needs to ensure that the data is actually initialized pub const unsafe fn assume_init_ref(&self) -> &T { #[cfg(test)] { @@ -277,6 +289,7 @@ impl fmt::Debug for MaybeInit { .finish() } } + #[cfg(not(test))] impl fmt::Debug for MaybeInit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {