Support built-in fn substitution

next
Sayan Nandan 2 years ago
parent 0f064e1087
commit 4d140c74fd
No known key found for this signature in database
GPG Key ID: 8BC07A0A4D41DD52

23
Cargo.lock generated

@ -1332,6 +1332,7 @@ dependencies = [
"tokio",
"tokio-openssl",
"toml",
"uuid",
"winapi",
]
@ -1603,6 +1604,28 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "uuid"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
dependencies = [
"getrandom",
"rand",
"uuid-macro-internal",
]
[[package]]
name = "uuid-macro-internal"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73bc89f2894593e665241e0052c3791999e6787b7c4831daa0a5c2e637e276d8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "vcpkg"
version = "0.2.15"

@ -26,6 +26,7 @@ tokio = { version = "1.21.0", features = ["full"] }
tokio-openssl = "0.6.3"
toml = "0.5.9"
base64 = "0.13.0"
uuid = { version = "1.2.2", features = ["v4", "fast-rng", "macro-diagnostics"] }
[target.'cfg(all(not(target_env = "msvc"), not(miri)))'.dependencies]
# external deps

@ -118,7 +118,7 @@ impl<'a, Qd: QueryData<'a>> State<'a, Qd> {
}
#[inline(always)]
/// Check if the token stream has alteast `many` count of tokens
pub fn has_remaining(&mut self, many: usize) -> bool {
pub fn has_remaining(&self, many: usize) -> bool {
self.remaining() >= many
}
#[inline(always)]
@ -151,6 +151,14 @@ impl<'a, Qd: QueryData<'a>> State<'a, Qd> {
Qd::read_lit(&mut self.d, tok)
}
#[inline(always)]
/// Read a lit from the given token
///
/// ## Safety
/// - Must ensure that `Self::can_read_lit_from` is true for the token
pub unsafe fn read_lit_unchecked_from(&mut self, tok: &'a Token<'a>) -> LitIR<'a> {
Qd::read_lit(&mut self.d, tok)
}
#[inline(always)]
/// Check if the cursor equals the given token; rounded
pub fn cursor_rounded_eq(&self, tok: Token<'a>) -> bool {
let mx = minidx(self.t, self.i);
@ -166,6 +174,34 @@ impl<'a, Qd: QueryData<'a>> State<'a, Qd> {
pub(crate) fn read_ahead(&self, ahead: usize) -> &'a Token<'a> {
&self.t[self.i + ahead]
}
#[inline(always)]
/// Move the cursor back by 1
pub(crate) fn cursor_back(&mut self) {
self.cursor_back_by(1);
}
#[inline(always)]
/// Move the cursor back by the given count
pub(crate) fn cursor_back_by(&mut self, by: usize) {
self.i -= by;
}
#[inline(always)]
pub(crate) fn cursor_has_ident_rounded(&self) -> bool {
self.t[minidx(self.t, self.i)].is_ident() && self.not_exhausted()
}
#[inline(always)]
/// Check if the current token stream matches the signature of an arity(0) fn; rounded
///
/// NOTE: Consider using a direct comparison without rounding
pub(crate) fn cursor_signature_match_fn_arity0_rounded(&self) -> bool {
let rem = self.has_remaining(3);
let idx_a = self.i * rem as usize;
let idx_b = (self.i + 1) * rem as usize;
let idx_c = (self.i + 2) * rem as usize;
(self.t[idx_a].is_ident())
& (self.t[idx_b] == Token![() open])
& (self.t[idx_c] == Token![() close])
& rem
}
}
pub trait QueryData<'a> {

@ -27,7 +27,7 @@
#[cfg(test)]
use crate::engine::ql::ast::InplaceData;
use {
super::parse_entity,
super::{parse_entity, read_ident},
crate::{
engine::{
memory::DataType,
@ -39,14 +39,105 @@ use {
},
util::{compiler, MaybeInit},
},
core::mem::{discriminant, Discriminant},
std::collections::HashMap,
core::{
cmp,
mem::{discriminant, Discriminant},
},
std::{
collections::HashMap,
time::{Duration, SystemTime, UNIX_EPOCH},
},
uuid::Uuid,
};
/*
Impls for insert
*/
pub const T_UUIDSTR: &str = "4593264b-0231-43e9-b0aa-50784f14e204";
pub const T_UUIDBIN: &[u8] = T_UUIDSTR.as_bytes();
pub const T_TIMESEC: u64 = 1673187839_u64;
type ProducerFn = fn() -> DataType;
// base
#[inline(always)]
fn pfnbase_time() -> Duration {
if cfg!(debug_assertions) {
Duration::from_secs(T_TIMESEC)
} else {
SystemTime::now().duration_since(UNIX_EPOCH).unwrap()
}
}
#[inline(always)]
fn pfnbase_uuid() -> Uuid {
if cfg!(debug_assertions) {
Uuid::parse_str(T_UUIDSTR).unwrap()
} else {
Uuid::new_v4()
}
}
// impl
#[inline(always)]
fn pfn_timesec() -> DataType {
DataType::UnsignedInt(pfnbase_time().as_secs())
}
#[inline(always)]
fn pfn_uuidstr() -> DataType {
DataType::String(pfnbase_uuid().to_string().into_boxed_str())
}
#[inline(always)]
fn pfn_uuidbin() -> DataType {
DataType::Binary(pfnbase_uuid().as_bytes().to_vec().into_boxed_slice())
}
static PRODUCER_G: [u8; 4] = [0, 2, 3, 0];
static PRODUCER_F: [(&[u8], ProducerFn); 3] = [
(b"uuidstr", pfn_uuidstr),
(b"uuidbin", pfn_uuidbin),
(b"timesec", pfn_timesec),
];
const MAGIC_1: [u8; 7] = *b"cp21rLd";
const MAGIC_2: [u8; 7] = *b"zS8zgaK";
const MAGIC_L: usize = MAGIC_1.len();
#[inline(always)]
fn hashf(key: &[u8], m: &[u8]) -> u32 {
let mut i = 0;
let mut s = 0;
while i < key.len() {
s += m[(i % MAGIC_L) as usize] as u32 * key[i] as u32;
i += 1;
}
s % PRODUCER_G.len() as u32
}
#[inline(always)]
fn hashp(key: &[u8]) -> u32 {
(PRODUCER_G[hashf(key, &MAGIC_1) as usize] + PRODUCER_G[hashf(key, &MAGIC_2) as usize]) as u32
% PRODUCER_G.len() as u32
}
#[inline(always)]
fn ldfunc(func: &[u8]) -> Option<ProducerFn> {
let ph = hashp(func) as usize;
let min = cmp::min(ph, PRODUCER_F.len() - 1);
let data = PRODUCER_F[min as usize];
if data.0 == func {
Some(data.1)
} else {
None
}
}
#[inline(always)]
fn ldfunc_exists(func: &[u8]) -> bool {
ldfunc(func).is_some()
}
#[inline(always)]
unsafe fn ldfunc_unchecked(func: &[u8]) -> ProducerFn {
let ph = hashp(func) as usize;
debug_assert_eq!(PRODUCER_F[ph as usize].0, func);
PRODUCER_F[ph as usize].1
}
/// ## Panics
/// - If tt length is less than 1
pub(super) fn parse_list<'a, Qd: QueryData<'a>>(
@ -58,17 +149,15 @@ pub(super) fn parse_list<'a, Qd: QueryData<'a>>(
let mut overall_dscr = None;
let mut prev_nlist_dscr = None;
while state.not_exhausted() && state.okay() && !stop {
let d = match state.read() {
let d = match state.fw_read() {
tok if state.can_read_lit_from(tok) => {
let r = unsafe {
// UNSAFE(@ohsayan): the if guard guarantees correctness
DataType::clone_from_litir(state.read_cursor_lit_unchecked())
DataType::clone_from_litir(state.read_lit_unchecked_from(tok))
};
state.cursor_ahead();
r
}
Token![open []] => {
state.cursor_ahead();
// a nested list
let mut nested_list = Vec::new();
let nlist_dscr = parse_list(state, &mut nested_list);
@ -83,7 +172,18 @@ pub(super) fn parse_list<'a, Qd: QueryData<'a>>(
}
DataType::List(nested_list)
}
Token![@] if state.cursor_signature_match_fn_arity0_rounded() => match unsafe {
// UNSAFE(@ohsayan): Just verified at guard
handle_func_sub(state)
} {
Some(value) => value,
None => {
state.poison();
break;
}
},
_ => {
state.cursor_back();
state.poison();
break;
}
@ -100,6 +200,15 @@ pub(super) fn parse_list<'a, Qd: QueryData<'a>>(
overall_dscr
}
#[inline(always)]
/// ## Safety
/// - Cursor must match arity(0) function signature
unsafe fn handle_func_sub<'a, Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> Option<DataType> {
let func = read_ident(state.fw_read());
state.cursor_ahead_by(2); // skip tt:paren
ldfunc(func).map(move |f| f())
}
#[cfg(test)]
pub fn parse_list_full<'a>(tok: &'a [Token], qd: impl QueryData<'a>) -> Option<Vec<DataType>> {
let mut l = Vec::new();
@ -118,27 +227,33 @@ pub(super) fn parse_data_tuple_syntax<'a, Qd: QueryData<'a>>(
state.cursor_ahead_if(stop);
let mut data = Vec::new();
while state.not_exhausted() && state.okay() && !stop {
match state.read() {
tok if state.can_read_lit_from(tok) => {
unsafe {
// UNSAFE(@ohsayan): if guard guarantees correctness
data.push(Some(DataType::clone_from_litir(
state.read_cursor_lit_unchecked(),
)))
}
state.cursor_ahead();
}
Token![open []] if state.remaining() >= 2 => {
state.cursor_ahead();
match state.fw_read() {
tok if state.can_read_lit_from(tok) => unsafe {
// UNSAFE(@ohsayan): if guard guarantees correctness
data.push(Some(DataType::clone_from_litir(
state.read_lit_unchecked_from(tok),
)))
},
Token![open []] if state.not_exhausted() => {
let mut l = Vec::new();
let _ = parse_list(state, &mut l);
data.push(Some(l.into()));
}
Token![null] => {
state.cursor_ahead();
data.push(None);
}
Token![@] if state.cursor_signature_match_fn_arity0_rounded() => match unsafe {
// UNSAFE(@ohsayan): Just verified at guard
handle_func_sub(state)
} {
Some(value) => data.push(Some(value)),
None => {
state.poison();
break;
}
},
_ => {
state.cursor_back();
state.poison();
break;
}
@ -169,31 +284,40 @@ pub(super) fn parse_data_map_syntax<'a, Qd: QueryData<'a>>(
state.cursor_ahead_if(stop);
let mut data = HashMap::with_capacity(2);
while state.has_remaining(3) && state.okay() && !stop {
let field = state.read();
let colon = state.read_ahead(1);
let expr = state.read_ahead(2);
let field = state.fw_read();
let colon = state.fw_read();
let expr = state.fw_read();
state.poison_if_not(Token![:].eq(colon));
match (field, expr) {
(Token::Ident(id), tok) if state.can_read_lit_from(tok) => {
state.cursor_ahead_by(2); // ident + colon
let ldata = Some(DataType::clone_from_litir(unsafe {
// UNSAFE(@ohsayan): The if guard guarantees correctness
state.read_cursor_lit_unchecked()
state.read_lit_unchecked_from(tok)
}));
state.cursor_ahead();
state.poison_if_not(data.insert(*id, ldata).is_none());
}
(Token::Ident(id), Token![null]) => {
state.cursor_ahead_by(3);
state.poison_if_not(data.insert(*id, None).is_none());
}
(Token::Ident(id), Token![open []]) if state.remaining() >= 4 => {
state.cursor_ahead_by(3);
(Token::Ident(id), Token![open []]) if state.not_exhausted() => {
let mut l = Vec::new();
let _ = parse_list(state, &mut l);
state.poison_if_not(data.insert(*id, Some(l.into())).is_none());
}
(Token::Ident(id), Token![@]) if state.cursor_signature_match_fn_arity0_rounded() => {
match unsafe {
// UNSAFE(@ohsayan): Just verified at guard
handle_func_sub(state)
} {
Some(value) => state.poison_if_not(data.insert(*id, Some(value)).is_none()),
None => {
state.poison();
break;
}
}
}
_ => {
state.cursor_back_by(3);
state.poison();
break;
}
@ -271,10 +395,8 @@ impl<'a> InsertStatement<'a> {
// entity
let mut entity = MaybeInit::uninit();
parse_entity(state, &mut entity);
let what_data = state.read();
state.cursor_ahead(); // ignore errors for now
let mut data = None;
match what_data {
match state.fw_read() {
Token![() open] if state.not_exhausted() => {
let this_data = parse_data_tuple_syntax(state);
data = Some(InsertData::Ordered(this_data));

@ -551,6 +551,36 @@ mod stmt_insert {
);
assert_eq!(r, e);
}
#[test]
fn insert_tuple_fnsub() {
let tok =
lex_insecure(br#"insert into jotsy.app(@uuidstr(), "sayan", @timesec())"#).unwrap();
let ret = dml::insert::parse_insert_full(&tok[1..]).unwrap();
let expected = InsertStatement::new(
Entity::Full(b"jotsy", b"app"),
into_array_nullable![dml::insert::T_UUIDSTR, "sayan", dml::insert::T_TIMESEC]
.to_vec()
.into(),
);
assert_eq!(ret, expected);
}
#[test]
fn insert_map_fnsub() {
let tok = lex_insecure(
br#"insert into jotsy.app { uuid: @uuidstr(), username: "sayan", signup_time: @timesec() }"#
).unwrap();
let ret = dml::insert::parse_insert_full(&tok[1..]).unwrap();
let expected = InsertStatement::new(
Entity::Full(b"jotsy", b"app"),
dict_nullable! {
"uuid".as_bytes() => dml::insert::T_UUIDSTR,
"username".as_bytes() => "sayan",
"signup_time".as_bytes() => dml::insert::T_TIMESEC,
}
.into(),
);
assert_eq!(ret, expected);
}
}
mod stmt_select {

Loading…
Cancel
Save