From 986cb26ebdac212117af1fe771ca76704d4e02a3 Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Sun, 20 Nov 2022 20:58:52 +0530 Subject: [PATCH] Support `null` in dict and tymeta --- server/src/engine/memory/mod.rs | 21 ++ server/src/engine/ql/dml.rs | 6 +- server/src/engine/ql/lexer.rs | 11 +- server/src/engine/ql/macros.rs | 13 ++ server/src/engine/ql/schema.rs | 36 +++- server/src/engine/ql/tests.rs | 363 +++++++++++++++++++++----------- 6 files changed, 308 insertions(+), 142 deletions(-) diff --git a/server/src/engine/memory/mod.rs b/server/src/engine/memory/mod.rs index 4c3147ce..de803c64 100644 --- a/server/src/engine/memory/mod.rs +++ b/server/src/engine/memory/mod.rs @@ -61,3 +61,24 @@ impl From<[DataType; N]> for DataType { Self::List(f.into()) } } + +#[repr(u8, align(1))] +pub enum DataKind { + // primitive: integer unsigned + UInt8 = 0, + UInt16 = 1, + Uint32 = 2, + UInt64 = 3, + // primitive: integer unsigned + SInt8 = 4, + SInt16 = 5, + SInt32 = 6, + SInt64 = 7, + // primitive: misc + Bool = 8, + // compound: flat + String = 9, + Binary = 10, + // compound: recursive + List = 11, +} diff --git a/server/src/engine/ql/dml.rs b/server/src/engine/ql/dml.rs index 503e2f47..15b145e9 100644 --- a/server/src/engine/ql/dml.rs +++ b/server/src/engine/ql/dml.rs @@ -80,7 +80,7 @@ pub(super) fn parse_list( while i < l && okay && !stop { let d = match &tok[i] { Token::Lit(Lit::Str(s)) => DataType::String(s.to_string()), - Token::Lit(Lit::Num(n)) => DataType::Number(*n), + Token::Lit(Lit::UnsignedInt(n)) => DataType::Number(*n), Token::Lit(Lit::Bool(b)) => DataType::Boolean(*b), Token::Symbol(Symbol::TtOpenSqBracket) => { // a nested list @@ -141,7 +141,7 @@ pub(super) fn parse_data_tuple_syntax(tok: &[Token]) -> (Vec>, Token::Lit(Lit::Str(s)) => { data.push(Some(s.to_string().into())); } - Token::Lit(Lit::Num(n)) => { + Token::Lit(Lit::UnsignedInt(n)) => { data.push(Some((*n).into())); } Token::Lit(Lit::Bool(b)) => { @@ -201,7 +201,7 @@ pub(super) fn parse_data_map_syntax<'a>( .insert(unsafe { id.as_slice() }, Some(s.to_string().into())) .is_none(); } - (Token::Ident(id), Token::Lit(Lit::Num(n))) => { + (Token::Ident(id), Token::Lit(Lit::UnsignedInt(n))) => { okay &= data .insert(unsafe { id.as_slice() }, Some((*n).into())) .is_none(); diff --git a/server/src/engine/ql/lexer.rs b/server/src/engine/ql/lexer.rs index 869b3681..96a0301a 100644 --- a/server/src/engine/ql/lexer.rs +++ b/server/src/engine/ql/lexer.rs @@ -74,7 +74,7 @@ enum_impls! { pub enum Lit { Str(Box), Bool(bool), - Num(u64), + UnsignedInt(u64), UnsafeLit(RawSlice), } @@ -89,7 +89,7 @@ enum_impls! { Box as Str, String as Str, bool as Bool, - u64 as Num, + u64 as UnsignedInt, } } @@ -514,15 +514,16 @@ impl<'a> Lexer<'a> { 1234, // valid 1234a // invalid */ - static TERMINAL_CHAR: [u8; 8] = [b';', b'}', b',', b' ', b'\n', b'\t', b',', b']']; - let wseof = self.peek_is(|b| TERMINAL_CHAR.contains(&b)) || self.exhausted(); + let wseof = self.peek_is(|char| !char.is_ascii_alphabetic()) || self.exhausted(); match str::from_utf8_unchecked(slice::from_raw_parts( s, self.cursor().offset_from(s) as usize, )) .parse() { - Ok(num) if compiler::likely(wseof) => self.tokens.push(Token::Lit(Lit::Num(num))), + Ok(num) if compiler::likely(wseof) => { + self.tokens.push(Token::Lit(Lit::UnsignedInt(num))) + } _ => self.last_error = Some(LangError::InvalidNumericLiteral), } } diff --git a/server/src/engine/ql/macros.rs b/server/src/engine/ql/macros.rs index 18a3e56c..197319cb 100644 --- a/server/src/engine/ql/macros.rs +++ b/server/src/engine/ql/macros.rs @@ -306,6 +306,19 @@ macro_rules! dict { }}; } +macro_rules! nullable_dict { + () => { + dict! {} + }; + ($($key:expr => $value:expr),* $(,)?) => { + dict! { + $( + $key => $crate::engine::ql::tests::NullableMapEntry::data($value), + )* + } + }; +} + macro_rules! dict_nullable { () => { <::std::collections::HashMap<_, _> as ::core::default::Default>::default() diff --git a/server/src/engine/ql/schema.rs b/server/src/engine/ql/schema.rs index 713131b3..7bac5e11 100644 --- a/server/src/engine/ql/schema.rs +++ b/server/src/engine/ql/schema.rs @@ -105,7 +105,7 @@ impl From for DictEntry { } /// A metadata dictionary -pub type Dict = HashMap; +pub type Dict = HashMap>; #[derive(Debug, PartialEq)] /// A layer contains a type and corresponding metadata @@ -271,11 +271,19 @@ pub(super) fn rfold_dict(mut state: DictFoldState, tok: &[Token], dict: &mut Dic okay &= dict .insert( unsafe { tmp.assume_init_ref() }.to_string(), - l.clone().into(), + Some(l.clone().into()), ) .is_none(); state = DictFoldState::COMMA_OR_CB; } + (Token![null], DictFoldState::LIT_OR_OB) => { + // null + i += 1; + okay &= dict + .insert(unsafe { tmp.assume_init_ref() }.to_string(), None) + .is_none(); + state = DictFoldState::COMMA_OR_CB; + } // ONLY COMMA CAPTURE (Token::Symbol(Symbol::SymComma), DictFoldState::COMMA_OR_CB) => { i += 1; @@ -292,7 +300,7 @@ pub(super) fn rfold_dict(mut state: DictFoldState, tok: &[Token], dict: &mut Dic okay &= dict .insert( unsafe { tmp.assume_init_ref() }.to_string(), - new_dict.into(), + Some(new_dict.into()), ) .is_none(); // at the end of a dict we either expect a comma or close brace @@ -465,13 +473,22 @@ pub(super) fn rfold_tymeta( r.record( dict.insert( unsafe { tmp.assume_init_ref() }.to_string(), - lit.clone().into(), + Some(lit.clone().into()), ) .is_none(), ); // saw a literal. next is either comma or close brace state = TyMetaFoldState::COMMA_OR_CB; } + (Token![null], TyMetaFoldState::LIT_OR_OB) => { + r.incr(); + r.record( + dict.insert(unsafe { tmp.assume_init_ref() }.to_string(), None) + .is_none(), + ); + // saw null, start parsing another entry + state = TyMetaFoldState::COMMA_OR_CB; + } (Token::Symbol(Symbol::SymComma), TyMetaFoldState::COMMA_OR_CB) => { r.incr(); // next is strictly a close brace or ident @@ -488,10 +505,11 @@ pub(super) fn rfold_tymeta( ); r.incr_by(ret.pos()); r.record(ret.is_okay()); - r.record(!ret.has_more()); // L2 cannot have type definitions - // end of definition or comma followed by something + // L2 cannot have type definitions + r.record(!ret.has_more()); + // end of definition or comma followed by something r.record( - dict.insert(unsafe { tmp.assume_init_ref() }.to_string(), d.into()) + dict.insert(unsafe { tmp.assume_init_ref() }.to_string(), Some(d.into())) .is_none(), ); state = TyMetaFoldState::COMMA_OR_CB; @@ -870,9 +888,7 @@ pub(super) fn parse_alter_space_from_tokens(tok: &[Token]) -> LangResult<(AlterS return Err(LangError::UnexpectedToken); } - let space_name = unsafe { - extract!(tok[0], Token::Ident(ref space) => space.clone()) - }; + let space_name = unsafe { extract!(tok[0], Token::Ident(ref space) => space.clone()) }; i += 3; diff --git a/server/src/engine/ql/tests.rs b/server/src/engine/ql/tests.rs index 00509494..5bde3ada 100644 --- a/server/src/engine/ql/tests.rs +++ b/server/src/engine/ql/tests.rs @@ -61,6 +61,34 @@ fn nullable_datatype(v: impl NullableData) -> Option { v.data() } +pub trait NullableMapEntry { + fn data(self) -> Option; +} + +impl NullableMapEntry for Null { + fn data(self) -> Option { + None + } +} + +impl NullableMapEntry for super::lexer::Lit { + fn data(self) -> Option { + Some(super::schema::DictEntry::Lit(self)) + } +} + +impl NullableMapEntry for super::schema::Dict { + fn data(self) -> Option { + Some(super::schema::DictEntry::Map(self)) + } +} + +macro_rules! fold_dict { + ($($input:expr),* $(,)?) => { + ($({$crate::engine::ql::schema::fold_dict(&super::lex($input).unwrap()).unwrap()}),*) + } +} + mod lexer_tests { use { super::{ @@ -89,7 +117,10 @@ mod lexer_tests { #[test] fn lex_number() { let number = v!("123456"); - assert_eq!(lex(&number).unwrap(), vec![Token::Lit(Lit::Num(123456))]); + assert_eq!( + lex(&number).unwrap(), + vec![Token::Lit(Lit::UnsignedInt(123456))] + ); } #[test] fn lex_bool() { @@ -362,7 +393,7 @@ mod schema_tests { r, AlterSpace { space_name: "mymodel".into(), - updated_props: dict! {} + updated_props: nullable_dict! {} } ); } @@ -380,8 +411,8 @@ mod schema_tests { r, AlterSpace { space_name: "mymodel".into(), - updated_props: dict! { - "max_entry" => Lit::Num(1000), + updated_props: nullable_dict! { + "max_entry" => Lit::UnsignedInt(1000), "driver" => Lit::Str("ts-0.8".into()) } } @@ -392,19 +423,13 @@ mod schema_tests { mod dict { use super::*; - macro_rules! fold_dict { - ($($input:expr),* $(,)?) => { - ($({schema::fold_dict(&super::lex($input).unwrap()).unwrap()}),*) - } - } - #[test] fn dict_read_mini() { let (d1, d2) = fold_dict! { br#"{name: "sayan"}"#, br#"{name: "sayan",}"#, }; - let r = dict!("name" => Lit::Str("sayan".into())); + let r = nullable_dict!("name" => Lit::Str("sayan".into())); multi_assert_eq!(d1, d2 => r); } #[test] @@ -425,10 +450,10 @@ mod schema_tests { } "#, }; - let r = dict! ( + let r = nullable_dict! ( "name" => Lit::Str("sayan".into()), "verified" => Lit::Bool(true), - "burgers" => Lit::Num(152), + "burgers" => Lit::UnsignedInt(152), ); multi_assert_eq!(d1, d2 => r); } @@ -466,12 +491,12 @@ mod schema_tests { }"# }; multi_assert_eq!( - d1, d2, d3 => dict! { + d1, d2, d3 => nullable_dict! { "name" => Lit::Str("sayan".into()), - "notes" => dict! { + "notes" => nullable_dict! { "burgers" => Lit::Str("all the time, extra mayo".into()), "taco" => Lit::Bool(true), - "pretzels" => Lit::Num(1), + "pretzels" => Lit::UnsignedInt(1), } } ); @@ -521,11 +546,11 @@ mod schema_tests { }"# }; multi_assert_eq!( - d1, d2, d3 => dict! { - "well" => dict! { - "now" => dict! { - "this" => dict! { - "is" => dict! { + d1, d2, d3 => nullable_dict! { + "well" => nullable_dict! { + "now" => nullable_dict! { + "this" => nullable_dict! { + "is" => nullable_dict! { "ridiculous" => Lit::Bool(true), } } @@ -554,16 +579,16 @@ mod schema_tests { } ") .unwrap(); - let ret_dict = dict! { + let ret_dict = nullable_dict! { "the_tradition_is" => Lit::Str("hello, world".into()), - "could_have_been" => dict! { + "could_have_been" => nullable_dict! { "this" => Lit::Bool(true), - "or_maybe_this" => Lit::Num(100), + "or_maybe_this" => Lit::UnsignedInt(100), "even_this" => Lit::Str("hello, universe!".into()), }, "but_oh_well" => Lit::Str("it continues to be the 'annoying' phrase".into()), - "lorem" => dict! { - "ipsum" => dict! { + "lorem" => nullable_dict! { + "ipsum" => nullable_dict! { "dolor" => Lit::Str("sit amet".into()) } } @@ -591,7 +616,7 @@ mod schema_tests { assert!(res.is_okay()); assert!(!res.has_more()); assert_eq!(res.pos(), 1); - assert_eq!(ret, dict!()); + assert_eq!(ret, nullable_dict!()); } #[test] fn tymeta_mini_fail() { @@ -600,7 +625,7 @@ mod schema_tests { assert!(!res.is_okay()); assert!(!res.has_more()); assert_eq!(res.pos(), 0); - assert_eq!(ret, dict!()); + assert_eq!(ret, nullable_dict!()); } #[test] fn tymeta() { @@ -611,10 +636,10 @@ mod schema_tests { assert_eq!(res.pos(), tok.len()); assert_eq!( ret, - dict! { + nullable_dict! { "hello" => Lit::Str("world".into()), "loading" => Lit::Bool(true), - "size" => Lit::Num(100) + "size" => Lit::UnsignedInt(100) } ); } @@ -636,8 +661,8 @@ mod schema_tests { final_ret.extend(ret2); assert_eq!( final_ret, - dict! { - "maxlen" => Lit::Num(100), + nullable_dict! { + "maxlen" => Lit::UnsignedInt(100), "unique" => Lit::Bool(true) } ) @@ -661,10 +686,10 @@ mod schema_tests { final_ret.extend(ret2); assert_eq!( final_ret, - dict! { - "maxlen" => Lit::Num(100), + nullable_dict! { + "maxlen" => Lit::UnsignedInt(100), "unique" => Lit::Bool(true), - "this" => dict! { + "this" => nullable_dict! { "is" => Lit::Str("cool".into()) } } @@ -684,10 +709,10 @@ mod schema_tests { } ") .unwrap(); - let expected = dict! { - "maxlen" => Lit::Num(10), + let expected = nullable_dict! { + "maxlen" => Lit::UnsignedInt(10), "unique" => Lit::Bool(true), - "auth" => dict! { + "auth" => nullable_dict! { "maybe" => Lit::Bool(true), }, "user" => Lit::Str("sayan".into()) @@ -722,10 +747,10 @@ mod schema_tests { } ") .unwrap(); - let expected = dict! { - "maxlen" => Lit::Num(10), + let expected = nullable_dict! { + "maxlen" => Lit::UnsignedInt(10), "unique" => Lit::Bool(true), - "auth" => dict! { + "auth" => nullable_dict! { "maybe" => Lit::Bool(true), }, }; @@ -751,7 +776,10 @@ mod schema_tests { let (layers, c, okay) = schema::fold_layers(&tok); assert_eq!(c, tok.len() - 1); assert!(okay); - assert_eq!(layers, vec![Layer::new_noreset(Type::String, dict! {})]); + assert_eq!( + layers, + vec![Layer::new_noreset(Type::String, nullable_dict! {})] + ); } #[test] fn layer() { @@ -763,8 +791,8 @@ mod schema_tests { layers, vec![Layer::new_noreset( Type::String, - dict! { - "maxlen" => Lit::Num(100) + nullable_dict! { + "maxlen" => Lit::UnsignedInt(100) } )] ); @@ -778,8 +806,8 @@ mod schema_tests { assert_eq!( layers, vec![ - Layer::new_noreset(Type::String, dict! {}), - Layer::new_noreset(Type::List, dict! {}) + Layer::new_noreset(Type::String, nullable_dict! {}), + Layer::new_noreset(Type::List, nullable_dict! {}) ] ); } @@ -792,12 +820,12 @@ mod schema_tests { assert_eq!( layers, vec![ - Layer::new_noreset(Type::String, dict! {}), + Layer::new_noreset(Type::String, nullable_dict! {}), Layer::new_noreset( Type::List, - dict! { + nullable_dict! { "unique" => Lit::Bool(true), - "maxlen" => Lit::Num(10), + "maxlen" => Lit::UnsignedInt(10), } ) ] @@ -817,16 +845,16 @@ mod schema_tests { vec![ Layer::new_noreset( Type::String, - dict! { + nullable_dict! { "ascii_only" => Lit::Bool(true), - "maxlen" => Lit::Num(255) + "maxlen" => Lit::UnsignedInt(255) } ), Layer::new_noreset( Type::List, - dict! { + nullable_dict! { "unique" => Lit::Bool(true), - "maxlen" => Lit::Num(10), + "maxlen" => Lit::UnsignedInt(10), } ) ] @@ -846,14 +874,14 @@ mod schema_tests { ") .unwrap(); let expected = vec![ - Layer::new_noreset(Type::String, dict!()), + Layer::new_noreset(Type::String, nullable_dict!()), Layer::new_noreset( Type::List, - dict! { - "maxlen" => Lit::Num(100), + nullable_dict! { + "maxlen" => Lit::UnsignedInt(100), }, ), - Layer::new_noreset(Type::List, dict!("unique" => Lit::Bool(true))), + Layer::new_noreset(Type::List, nullable_dict!("unique" => Lit::Bool(true))), ]; fuzz_tokens(&tok, |should_pass, new_tok| { let (layers, c, okay) = schema::fold_layers(&new_tok); @@ -916,7 +944,7 @@ mod schema_tests { f, Field { field_name: "username".into(), - layers: [Layer::new_noreset(Type::String, dict! {})].into(), + layers: [Layer::new_noreset(Type::String, nullable_dict! {})].into(), props: set![], } ) @@ -933,7 +961,7 @@ mod schema_tests { f, Field { field_name: "username".into(), - layers: [Layer::new_noreset(Type::String, dict! {})].into(), + layers: [Layer::new_noreset(Type::String, nullable_dict! {})].into(), props: set!["primary"], } ) @@ -955,8 +983,8 @@ mod schema_tests { field_name: "username".into(), layers: [Layer::new_noreset( Type::String, - dict! { - "maxlen" => Lit::Num(10), + nullable_dict! { + "maxlen" => Lit::UnsignedInt(10), "ascii_only" => Lit::Bool(true), } )] @@ -986,14 +1014,14 @@ mod schema_tests { layers: [ Layer::new_noreset( Type::String, - dict! { - "maxlen" => Lit::Num(255), + nullable_dict! { + "maxlen" => Lit::UnsignedInt(255), "ascii_only" => Lit::Bool(true), } ), Layer::new_noreset( Type::List, - dict! { + nullable_dict! { "unique" => Lit::Bool(true) } ), @@ -1032,16 +1060,16 @@ mod schema_tests { fields: vec![ Field { field_name: "username".into(), - layers: vec![Layer::new_noreset(Type::String, dict! {})], + layers: vec![Layer::new_noreset(Type::String, nullable_dict! {})], props: set!["primary"] }, Field { field_name: "password".into(), - layers: vec![Layer::new_noreset(Type::Binary, dict! {})], + layers: vec![Layer::new_noreset(Type::Binary, nullable_dict! {})], props: set![] } ], - props: dict! {} + props: nullable_dict! {} } ) } @@ -1067,21 +1095,21 @@ mod schema_tests { fields: vec![ Field { field_name: "username".into(), - layers: vec![Layer::new_noreset(Type::String, dict! {})], + layers: vec![Layer::new_noreset(Type::String, nullable_dict! {})], props: set!["primary"] }, Field { field_name: "password".into(), - layers: vec![Layer::new_noreset(Type::Binary, dict! {})], + layers: vec![Layer::new_noreset(Type::Binary, nullable_dict! {})], props: set![] }, Field { field_name: "profile_pic".into(), - layers: vec![Layer::new_noreset(Type::Binary, dict! {})], + layers: vec![Layer::new_noreset(Type::Binary, nullable_dict! {})], props: set!["null"] } ], - props: dict! {} + props: nullable_dict! {} } ) } @@ -1112,26 +1140,26 @@ mod schema_tests { fields: vec![ Field { field_name: "username".into(), - layers: vec![Layer::new_noreset(Type::String, dict! {})], + layers: vec![Layer::new_noreset(Type::String, nullable_dict! {})], props: set!["primary"] }, Field { field_name: "password".into(), - layers: vec![Layer::new_noreset(Type::Binary, dict! {})], + layers: vec![Layer::new_noreset(Type::Binary, nullable_dict! {})], props: set![] }, Field { field_name: "profile_pic".into(), - layers: vec![Layer::new_noreset(Type::Binary, dict! {})], + layers: vec![Layer::new_noreset(Type::Binary, nullable_dict! {})], props: set!["null"] }, Field { field_name: "notes".into(), layers: vec![ - Layer::new_noreset(Type::String, dict! {}), + Layer::new_noreset(Type::String, nullable_dict! {}), Layer::new_noreset( Type::List, - dict! { + nullable_dict! { "unique" => Lit::Bool(true) } ) @@ -1139,7 +1167,7 @@ mod schema_tests { props: set!["null"] } ], - props: dict! {} + props: nullable_dict! {} } ) } @@ -1175,26 +1203,26 @@ mod schema_tests { fields: vec![ Field { field_name: "username".into(), - layers: vec![Layer::new_noreset(Type::String, dict! {})], + layers: vec![Layer::new_noreset(Type::String, nullable_dict! {})], props: set!["primary"] }, Field { field_name: "password".into(), - layers: vec![Layer::new_noreset(Type::Binary, dict! {})], + layers: vec![Layer::new_noreset(Type::Binary, nullable_dict! {})], props: set![] }, Field { field_name: "profile_pic".into(), - layers: vec![Layer::new_noreset(Type::Binary, dict! {})], + layers: vec![Layer::new_noreset(Type::Binary, nullable_dict! {})], props: set!["null"] }, Field { field_name: "notes".into(), layers: vec![ - Layer::new_noreset(Type::String, dict! {}), + Layer::new_noreset(Type::String, nullable_dict! {}), Layer::new_noreset( Type::List, - dict! { + nullable_dict! { "unique" => Lit::Bool(true) } ) @@ -1202,9 +1230,9 @@ mod schema_tests { props: set!["null"] } ], - props: dict! { - "env" => dict! { - "free_user_limit" => Lit::Num(100), + props: nullable_dict! { + "env" => nullable_dict! { + "free_user_limit" => Lit::UnsignedInt(100), }, "storage_driver" => Lit::Str("skyheap".into()), } @@ -1227,8 +1255,8 @@ mod schema_tests { ef, ExpandedField { field_name: "username".into(), - layers: vec![Layer::new_noreset(Type::String, dict! {})], - props: dict! {}, + layers: vec![Layer::new_noreset(Type::String, nullable_dict! {})], + props: nullable_dict! {}, reset: false } ) @@ -1248,10 +1276,10 @@ mod schema_tests { ef, ExpandedField { field_name: "username".into(), - props: dict! { + props: nullable_dict! { "nullable" => Lit::Bool(false), }, - layers: vec![Layer::new_noreset(Type::String, dict! {})], + layers: vec![Layer::new_noreset(Type::String, nullable_dict! {})], reset: false } ); @@ -1275,15 +1303,15 @@ mod schema_tests { ef, ExpandedField { field_name: "username".into(), - props: dict! { + props: nullable_dict! { "nullable" => Lit::Bool(false), "jingle_bells" => Lit::Str("snow".into()), }, layers: vec![Layer::new_noreset( Type::String, - dict! { - "minlen" => Lit::Num(6), - "maxlen" => Lit::Num(255), + nullable_dict! { + "minlen" => Lit::UnsignedInt(6), + "maxlen" => Lit::UnsignedInt(255), } )], reset: false @@ -1311,20 +1339,20 @@ mod schema_tests { ef, ExpandedField { field_name: "notes".into(), - props: dict! { + props: nullable_dict! { "nullable" => Lit::Bool(true), "jingle_bells" => Lit::Str("snow".into()), }, layers: vec![ Layer::new_noreset( Type::String, - dict! { + nullable_dict! { "ascii_only" => Lit::Bool(true), } ), Layer::new_noreset( Type::List, - dict! { + nullable_dict! { "unique" => Lit::Bool(true), } ) @@ -1391,8 +1419,8 @@ mod schema_tests { r.as_ref(), [ExpandedField { field_name: "myfield".into(), - props: dict! {}, - layers: [Layer::new_noreset(Type::String, dict! {})].into(), + props: nullable_dict! {}, + layers: [Layer::new_noreset(Type::String, nullable_dict! {})].into(), reset: false }] ); @@ -1410,10 +1438,10 @@ mod schema_tests { r.as_ref(), [ExpandedField { field_name: "myfield".into(), - props: dict! { + props: nullable_dict! { "nullable" => Lit::Bool(true) }, - layers: [Layer::new_noreset(Type::String, dict! {})].into(), + layers: [Layer::new_noreset(Type::String, nullable_dict! {})].into(), reset: false }] ); @@ -1431,10 +1459,10 @@ mod schema_tests { r.as_ref(), [ExpandedField { field_name: "myfield".into(), - props: dict! { + props: nullable_dict! { "nullable" => Lit::Bool(true) }, - layers: [Layer::new_noreset(Type::String, dict! {})].into(), + layers: [Layer::new_noreset(Type::String, nullable_dict! {})].into(), reset: false }] ); @@ -1467,27 +1495,27 @@ mod schema_tests { [ ExpandedField { field_name: "myfield".into(), - props: dict! { + props: nullable_dict! { "nullable" => Lit::Bool(true) }, - layers: [Layer::new_noreset(Type::String, dict! {})].into(), + layers: [Layer::new_noreset(Type::String, nullable_dict! {})].into(), reset: false }, ExpandedField { field_name: "another".into(), - props: dict! { + props: nullable_dict! { "nullable" => Lit::Bool(false) }, layers: [ Layer::new_noreset( Type::String, - dict! { - "maxlen" => Lit::Num(255) + nullable_dict! { + "maxlen" => Lit::UnsignedInt(255) } ), Layer::new_noreset( Type::List, - dict! { + nullable_dict! { "unique" => Lit::Bool(true) }, ) @@ -1519,8 +1547,8 @@ mod schema_tests { r.as_ref(), [ExpandedField { field_name: "myfield".into(), - props: dict! {}, - layers: [Layer::new_noreset(Type::String, dict! {})].into(), + props: nullable_dict! {}, + layers: [Layer::new_noreset(Type::String, nullable_dict! {})].into(), reset: true }] ); @@ -1538,8 +1566,8 @@ mod schema_tests { r.as_ref(), [ExpandedField { field_name: "myfield".into(), - props: dict! {}, - layers: [Layer::new_noreset(Type::String, dict! {})].into(), + props: nullable_dict! {}, + layers: [Layer::new_noreset(Type::String, nullable_dict! {})].into(), reset: true }] ); @@ -1563,10 +1591,10 @@ mod schema_tests { r.as_ref(), [ExpandedField { field_name: "myfield".into(), - props: dict! { + props: nullable_dict! { "nullable" => Lit::Bool(true) }, - layers: [Layer::new_noreset(Type::String, dict! {})].into(), + layers: [Layer::new_noreset(Type::String, nullable_dict! {})].into(), reset: true }] ); @@ -1595,16 +1623,16 @@ mod schema_tests { [ ExpandedField { field_name: "myfield".into(), - props: dict! { + props: nullable_dict! { "nullable" => Lit::Bool(true) }, - layers: [Layer::new_noreset(Type::String, dict! {})].into(), + layers: [Layer::new_noreset(Type::String, nullable_dict! {})].into(), reset: true }, ExpandedField { field_name: "myfield2".into(), - props: dict! {}, - layers: [Layer::new_noreset(Type::String, dict! {})].into(), + props: nullable_dict! {}, + layers: [Layer::new_noreset(Type::String, nullable_dict! {})].into(), reset: true } ] @@ -1637,18 +1665,18 @@ mod schema_tests { [ ExpandedField { field_name: "myfield".into(), - props: dict! { + props: nullable_dict! { "nullable" => Lit::Bool(true) }, - layers: [Layer::new_reset(Type::String, dict! {})].into(), + layers: [Layer::new_reset(Type::String, nullable_dict! {})].into(), reset: true }, ExpandedField { field_name: "myfield2".into(), - props: dict! {}, + props: nullable_dict! {}, layers: [Layer::new_reset( Type::String, - dict! {"maxlen" => Lit::Num(255)} + nullable_dict! {"maxlen" => Lit::UnsignedInt(255)} )] .into(), reset: true @@ -1823,7 +1851,7 @@ mod dml_tests { fn map_mini() { let tok = lex(b"{}").unwrap(); let r = parse_data_map_syntax_full(&tok[1..]).unwrap(); - assert_eq!(r, dict! {}) + assert_eq!(r, nullable_dict! {}) } #[test] @@ -1997,7 +2025,7 @@ mod dml_tests { let e = InsertStatement { primary_key: &("sayan".to_string().into()), entity: Entity::Full("jotsy".into(), "app".into()), - data: dict! {}.into(), + data: nullable_dict! {}.into(), }; assert_eq!(e, r); } @@ -2290,3 +2318,90 @@ mod dml_tests { } } } + +mod nullable_dict_tests { + use super::*; + mod dict { + use {super::*, crate::engine::ql::lexer::Lit}; + + #[test] + fn null_mini() { + let d = fold_dict!(br"{ x: null }"); + assert_eq!( + d, + nullable_dict! { + "x" => Null, + } + ); + } + #[test] + fn null() { + let d = fold_dict! { + br#" + { + this_is_non_null: "hello", + but_this_is_null: null, + } + "# + }; + assert_eq!( + d, + nullable_dict! { + "this_is_non_null" => Lit::Str("hello".into()), + "but_this_is_null" => Null, + } + ) + } + #[test] + fn null_pro() { + let d = fold_dict! { + br#" + { + a_string: "this is a string", + num: 1234, + a_dict: { + a_null: null, + } + } + "# + }; + assert_eq!( + d, + nullable_dict! { + "a_string" => Lit::Str("this is a string".into()), + "num" => Lit::UnsignedInt(1234), + "a_dict" => nullable_dict! { + "a_null" => Null, + } + } + ) + } + #[test] + fn null_pro_max() { + let d = fold_dict! { + br#" + { + a_string: "this is a string", + num: 1234, + a_dict: { + a_null: null, + }, + another_null: null, + } + "# + }; + assert_eq!( + d, + nullable_dict! { + "a_string" => Lit::Str("this is a string".into()), + "num" => Lit::UnsignedInt(1234), + "a_dict" => nullable_dict! { + "a_null" => Null, + }, + "another_null" => Null, + } + ) + } + } + // TODO(@ohsayan): Add null tests +}