Upgrade all interfaces to use the Skyhash protocol
parent
d6a3cc2acb
commit
78067d15eb
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Created on Tue May 11 2021
|
||||||
|
*
|
||||||
|
* This file is a part of Skytable
|
||||||
|
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||||
|
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||||
|
* vision to provide flexibility in data modelling without compromising
|
||||||
|
* on performance, queryability or scalability.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
/// # Data Types
|
||||||
|
///
|
||||||
|
/// This enum represents the data types supported by the Skyhash Protocol
|
||||||
|
pub enum Element {
|
||||||
|
/// Arrays can be nested! Their `<tsymbol>` is `&`
|
||||||
|
Array(Vec<Element>),
|
||||||
|
/// A String value; `<tsymbol>` is `+`
|
||||||
|
String(String),
|
||||||
|
/// An unsigned integer value; `<tsymbol>` is `:`
|
||||||
|
UnsignedInt(u64),
|
||||||
|
/// A non-recursive String array; tsymbol: `_`
|
||||||
|
FlatArray(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element {
|
||||||
|
/// This will return a reference to the first element in the element
|
||||||
|
///
|
||||||
|
/// If this element is a compound type, it will return a reference to the first element in the compound
|
||||||
|
/// type
|
||||||
|
pub fn get_first(&self) -> Option<Cow<String>> {
|
||||||
|
match self {
|
||||||
|
Self::Array(elem) => match elem.first() {
|
||||||
|
Some(el) => match el {
|
||||||
|
Element::String(st) => Some(Cow::Borrowed(&st)),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
Self::FlatArray(elem) => match elem.first() {
|
||||||
|
Some(el) => Some(Cow::Borrowed(&el)),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
Self::String(ref st) => Some(Cow::Borrowed(&st)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn is_flat_array(&self) -> bool {
|
||||||
|
if let Self::FlatArray(_) = self {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_flat_array_size(&self) -> Option<usize> {
|
||||||
|
if let Self::FlatArray(a) = self {
|
||||||
|
Some(a.len())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn is_flat_array_len_eq(&self, size: usize) -> Option<bool> {
|
||||||
|
if let Self::FlatArray(a) = self {
|
||||||
|
Some(a.len() == size)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,679 +0,0 @@
|
|||||||
/*
|
|
||||||
* Created on Mon May 10 2021
|
|
||||||
*
|
|
||||||
* This file is a part of Skytable
|
|
||||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
|
||||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
|
||||||
* vision to provide flexibility in data modelling without compromising
|
|
||||||
* on performance, queryability or scalability.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
//! # The Skyhash Protocol
|
|
||||||
//!
|
|
||||||
//! ## Introduction
|
|
||||||
//! The Skyhash Protocol is a serialization protocol that is used by Skytable for client/server communication.
|
|
||||||
//! It works in a query/response action similar to HTTP's request/response action. Skyhash supersedes the Terrapipe
|
|
||||||
//! protocol as a more simple, reliable, robust and scalable protocol.
|
|
||||||
//!
|
|
||||||
//! This module contains the [`Parser`] for the Skyhash protocol and it's enough to just pass a query packet as
|
|
||||||
//! a slice of unsigned 8-bit integers and the parser will do everything else. The Skyhash protocol was designed
|
|
||||||
//! and implemented by the Author (Sayan Nandan)
|
|
||||||
//!
|
|
||||||
|
|
||||||
use std::hint::unreachable_unchecked;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
/// # Skyhash Deserializer (Parser)
|
|
||||||
///
|
|
||||||
/// The [`Parser`] object can be used to deserialized a packet serialized by Skyhash which in turn serializes
|
|
||||||
/// it into data structures native to the Rust Language (and some Compound Types built on top of them).
|
|
||||||
///
|
|
||||||
/// ## Evaluation
|
|
||||||
///
|
|
||||||
/// The parser is pessimistic in most cases and will readily throw out any errors. On non-recusrive types
|
|
||||||
/// there is no recursion, but the parser will use implicit recursion for nested arrays. The parser will
|
|
||||||
/// happily not report any errors if some part of the next query was passed. This is very much a possibility
|
|
||||||
/// and so has been accounted for
|
|
||||||
///
|
|
||||||
/// ## Important note
|
|
||||||
///
|
|
||||||
/// All developers willing to modify the deserializer must keep this in mind: the cursor is always Ahead-Of-Position
|
|
||||||
/// that is the cursor should always point at the next character that can be read.
|
|
||||||
///
|
|
||||||
pub(super) struct Parser<'a> {
|
|
||||||
/// The internal cursor position
|
|
||||||
///
|
|
||||||
/// Do not even think of touching this externally
|
|
||||||
cursor: usize,
|
|
||||||
/// The buffer slice
|
|
||||||
buffer: &'a [u8],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
/// # Parser Errors
|
|
||||||
///
|
|
||||||
/// Several errors can arise during parsing and this enum accounts for them
|
|
||||||
pub enum ParseError {
|
|
||||||
/// Didn't get the number of expected bytes
|
|
||||||
NotEnough,
|
|
||||||
/// The query contains an unexpected byte
|
|
||||||
UnexpectedByte,
|
|
||||||
/// The packet simply contains invalid data
|
|
||||||
///
|
|
||||||
/// This is rarely returned and only in the special cases where a bad client sends `0` as
|
|
||||||
/// the query count
|
|
||||||
BadPacket,
|
|
||||||
/// A data type was given but the parser failed to serialize it into this type
|
|
||||||
///
|
|
||||||
/// This can happen not just for elements but can also happen for their sizes ([`Self::parse_into_u64`])
|
|
||||||
DataTypeParseError,
|
|
||||||
/// A data type that the server doesn't know was passed into the query
|
|
||||||
///
|
|
||||||
/// This is a frequent problem that can arise between different server editions as more data types
|
|
||||||
/// can be added with changing server versions
|
|
||||||
UnknownDatatype,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
/// # Types of Queries
|
|
||||||
///
|
|
||||||
/// A simple query carries out one action while a complex query executes multiple actions
|
|
||||||
pub enum Query {
|
|
||||||
/// A simple query will just hold one element
|
|
||||||
SimpleQuery(DataType),
|
|
||||||
/// A pipelined/batch query will hold multiple elements
|
|
||||||
PipelinedQuery(Vec<DataType>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
/// # Data Types
|
|
||||||
///
|
|
||||||
/// This enum represents the data types supported by the Skyhash Protocol
|
|
||||||
pub enum DataType {
|
|
||||||
/// Arrays can be nested! Their `<tsymbol>` is `&`
|
|
||||||
Array(Vec<DataType>),
|
|
||||||
/// A String value; `<tsymbol>` is `+`
|
|
||||||
String(String),
|
|
||||||
/// An unsigned integer value; `<tsymbol>` is `:`
|
|
||||||
UnsignedInt(u64),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A generic result to indicate parsing errors thorugh the [`ParseError`] enum
|
|
||||||
type ParseResult<T> = Result<T, ParseError>;
|
|
||||||
|
|
||||||
impl<'a> Parser<'a> {
|
|
||||||
/// Initialize a new parser instance
|
|
||||||
pub const fn new(buffer: &'a [u8]) -> Self {
|
|
||||||
Parser {
|
|
||||||
cursor: 0usize,
|
|
||||||
buffer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Read from the current cursor position to `until` number of positions ahead
|
|
||||||
/// This **will forward the cursor itself** if the bytes exist or it will just return a `NotEnough` error
|
|
||||||
fn read_until(&mut self, until: usize) -> ParseResult<&[u8]> {
|
|
||||||
if let Some(b) = self.buffer.get(self.cursor..self.cursor + until) {
|
|
||||||
self.cursor += until;
|
|
||||||
Ok(b)
|
|
||||||
} else {
|
|
||||||
Err(ParseError::NotEnough)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// This returns the position at which the line parsing began and the position at which the line parsing
|
|
||||||
/// stopped, in other words, you should be able to do self.buffer[started_at..stopped_at] to get a line
|
|
||||||
/// and do it unchecked. This **will move the internal cursor ahead** and place it **at the `\n` byte**
|
|
||||||
fn read_line(&mut self) -> (usize, usize) {
|
|
||||||
let started_at = self.cursor;
|
|
||||||
let mut stopped_at = self.cursor;
|
|
||||||
while self.cursor < self.buffer.len() {
|
|
||||||
if self.buffer[self.cursor] == b'\n' {
|
|
||||||
// Oh no! Newline reached, time to break the loop
|
|
||||||
// But before that ... we read the newline, so let's advance the cursor
|
|
||||||
self.incr_cursor();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// So this isn't an LF, great! Let's forward the stopped_at position
|
|
||||||
stopped_at += 1;
|
|
||||||
self.incr_cursor();
|
|
||||||
}
|
|
||||||
(started_at, stopped_at)
|
|
||||||
}
|
|
||||||
/// Push the internal cursor ahead by one
|
|
||||||
fn incr_cursor(&mut self) {
|
|
||||||
self.cursor += 1;
|
|
||||||
}
|
|
||||||
/// This function will evaluate if the byte at the current cursor position equals the `ch` argument, i.e
|
|
||||||
/// the expression `*v == ch` is evaluated. However, if no element is present ahead, then the function
|
|
||||||
/// will return `Ok(_this_if_nothing_ahead_)`
|
|
||||||
fn will_cursor_give_char(&self, ch: u8, this_if_nothing_ahead: bool) -> ParseResult<bool> {
|
|
||||||
self.buffer.get(self.cursor).map_or(
|
|
||||||
if this_if_nothing_ahead {
|
|
||||||
Ok(true)
|
|
||||||
} else {
|
|
||||||
Err(ParseError::NotEnough)
|
|
||||||
},
|
|
||||||
|v| Ok(*v == ch),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// Will the current cursor position give a linefeed? This will return `ParseError::NotEnough` if
|
|
||||||
/// the current cursor points at a non-existent index in `self.buffer`
|
|
||||||
fn will_cursor_give_linefeed(&self) -> ParseResult<bool> {
|
|
||||||
self.will_cursor_give_char(b'\n', false)
|
|
||||||
}
|
|
||||||
/// Parse a stream of bytes into [`usize`]
|
|
||||||
fn parse_into_usize(bytes: &[u8]) -> ParseResult<usize> {
|
|
||||||
if bytes.len() == 0 {
|
|
||||||
return Err(ParseError::NotEnough);
|
|
||||||
}
|
|
||||||
let mut byte_iter = bytes.into_iter();
|
|
||||||
let mut item_usize = 0usize;
|
|
||||||
while let Some(dig) = byte_iter.next() {
|
|
||||||
if !dig.is_ascii_digit() {
|
|
||||||
// dig has to be an ASCII digit
|
|
||||||
return Err(ParseError::DataTypeParseError);
|
|
||||||
}
|
|
||||||
// 48 is the ASCII code for 0, and 57 is the ascii code for 9
|
|
||||||
// so if 0 is given, the subtraction should give 0; similarly
|
|
||||||
// if 9 is given, the subtraction should give us 9!
|
|
||||||
let curdig: usize = dig
|
|
||||||
.checked_sub(48)
|
|
||||||
.unwrap_or_else(|| unsafe { unreachable_unchecked() })
|
|
||||||
.into();
|
|
||||||
// The usize can overflow; check that case
|
|
||||||
let product = match item_usize.checked_mul(10) {
|
|
||||||
Some(not_overflowed) => not_overflowed,
|
|
||||||
None => return Err(ParseError::DataTypeParseError),
|
|
||||||
};
|
|
||||||
let sum = match product.checked_add(curdig) {
|
|
||||||
Some(not_overflowed) => not_overflowed,
|
|
||||||
None => return Err(ParseError::DataTypeParseError),
|
|
||||||
};
|
|
||||||
item_usize = sum;
|
|
||||||
}
|
|
||||||
Ok(item_usize)
|
|
||||||
}
|
|
||||||
/// Pasre a stream of bytes into an [`u64`]
|
|
||||||
fn parse_into_u64(bytes: &[u8]) -> ParseResult<u64> {
|
|
||||||
if bytes.len() == 0 {
|
|
||||||
return Err(ParseError::NotEnough);
|
|
||||||
}
|
|
||||||
let mut byte_iter = bytes.into_iter();
|
|
||||||
let mut item_u64 = 0u64;
|
|
||||||
while let Some(dig) = byte_iter.next() {
|
|
||||||
if !dig.is_ascii_digit() {
|
|
||||||
// dig has to be an ASCII digit
|
|
||||||
return Err(ParseError::DataTypeParseError);
|
|
||||||
}
|
|
||||||
// 48 is the ASCII code for 0, and 57 is the ascii code for 9
|
|
||||||
// so if 0 is given, the subtraction should give 0; similarly
|
|
||||||
// if 9 is given, the subtraction should give us 9!
|
|
||||||
let curdig: u64 = dig
|
|
||||||
.checked_sub(48)
|
|
||||||
.unwrap_or_else(|| unsafe { unreachable_unchecked() })
|
|
||||||
.into();
|
|
||||||
// Now the entire u64 can overflow, so let's attempt to check it
|
|
||||||
let product = match item_u64.checked_mul(10) {
|
|
||||||
Some(not_overflowed) => not_overflowed,
|
|
||||||
None => return Err(ParseError::DataTypeParseError),
|
|
||||||
};
|
|
||||||
let sum = match product.checked_add(curdig) {
|
|
||||||
Some(not_overflowed) => not_overflowed,
|
|
||||||
None => return Err(ParseError::DataTypeParseError),
|
|
||||||
};
|
|
||||||
item_u64 = sum;
|
|
||||||
}
|
|
||||||
Ok(item_u64)
|
|
||||||
}
|
|
||||||
/// This will return the number of datagroups present in this query packet
|
|
||||||
///
|
|
||||||
/// This **will forward the cursor itself**
|
|
||||||
fn parse_metaframe_get_datagroup_count(&mut self) -> ParseResult<usize> {
|
|
||||||
// the smallest query we can have is: *1\n or 3 chars
|
|
||||||
if self.buffer.len() < 3 {
|
|
||||||
return Err(ParseError::NotEnough);
|
|
||||||
}
|
|
||||||
// Now we want to read `*<n>\n`
|
|
||||||
let (start, stop) = self.read_line();
|
|
||||||
if let Some(our_chunk) = self.buffer.get(start..stop) {
|
|
||||||
if our_chunk[0] == b'*' {
|
|
||||||
// Good, this will tell us the number of actions
|
|
||||||
// Let us attempt to read the usize from this point onwards
|
|
||||||
// that is excluding the '*' (so 1..)
|
|
||||||
let ret = Self::parse_into_usize(&our_chunk[1..])?;
|
|
||||||
Ok(ret)
|
|
||||||
} else {
|
|
||||||
Err(ParseError::UnexpectedByte)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ParseError::NotEnough)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Get the next element **without** the tsymbol
|
|
||||||
///
|
|
||||||
/// This function **does not forward the newline**
|
|
||||||
fn __get_next_element(&mut self) -> ParseResult<&[u8]> {
|
|
||||||
let string_sizeline = self.read_line();
|
|
||||||
if let Some(line) = self.buffer.get(string_sizeline.0..string_sizeline.1) {
|
|
||||||
let string_size = Self::parse_into_usize(line)?;
|
|
||||||
let our_chunk = self.read_until(string_size)?;
|
|
||||||
Ok(our_chunk)
|
|
||||||
} else {
|
|
||||||
Err(ParseError::NotEnough)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// The cursor should have passed the `+` tsymbol
|
|
||||||
fn parse_next_string(&mut self) -> ParseResult<String> {
|
|
||||||
let our_string_chunk = self.__get_next_element()?;
|
|
||||||
let our_string = String::from_utf8_lossy(&our_string_chunk).to_string();
|
|
||||||
if self.will_cursor_give_linefeed()? {
|
|
||||||
// there is a lf after the end of the string; great!
|
|
||||||
// let's skip that now
|
|
||||||
self.incr_cursor();
|
|
||||||
// let's return our string
|
|
||||||
Ok(our_string)
|
|
||||||
} else {
|
|
||||||
Err(ParseError::UnexpectedByte)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// The cursor should have passed the `:` tsymbol
|
|
||||||
fn parse_next_u64(&mut self) -> ParseResult<u64> {
|
|
||||||
let our_u64_chunk = self.__get_next_element()?;
|
|
||||||
let our_u64 = Self::parse_into_u64(our_u64_chunk)?;
|
|
||||||
if self.will_cursor_give_linefeed()? {
|
|
||||||
// line feed after u64; heck yeah!
|
|
||||||
self.incr_cursor();
|
|
||||||
// return it
|
|
||||||
Ok(our_u64)
|
|
||||||
} else {
|
|
||||||
Err(ParseError::UnexpectedByte)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// The cursor should be **at the tsymbol**
|
|
||||||
fn parse_next_element(&mut self) -> ParseResult<DataType> {
|
|
||||||
if let Some(tsymbol) = self.buffer.get(self.cursor) {
|
|
||||||
// so we have a tsymbol; nice, let's match it
|
|
||||||
// but advance the cursor before doing that
|
|
||||||
self.incr_cursor();
|
|
||||||
let ret = match *tsymbol {
|
|
||||||
b'+' => DataType::String(self.parse_next_string()?),
|
|
||||||
b':' => DataType::UnsignedInt(self.parse_next_u64()?),
|
|
||||||
b'&' => DataType::Array(self.parse_next_array()?),
|
|
||||||
_ => return Err(ParseError::UnknownDatatype),
|
|
||||||
};
|
|
||||||
Ok(ret)
|
|
||||||
} else {
|
|
||||||
// Not enough bytes to read an element
|
|
||||||
Err(ParseError::NotEnough)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// The tsymbol `&` should have been passed!
|
|
||||||
fn parse_next_array(&mut self) -> ParseResult<Vec<DataType>> {
|
|
||||||
let (start, stop) = self.read_line();
|
|
||||||
if let Some(our_size_chunk) = self.buffer.get(start..stop) {
|
|
||||||
let array_size = Self::parse_into_usize(our_size_chunk)?;
|
|
||||||
let mut array = Vec::with_capacity(array_size);
|
|
||||||
for _ in 0..array_size {
|
|
||||||
array.push(self.parse_next_element()?);
|
|
||||||
}
|
|
||||||
Ok(array)
|
|
||||||
} else {
|
|
||||||
Err(ParseError::NotEnough)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Parse a query and return the [`Query`] and an `usize` indicating the number of bytes that
|
|
||||||
/// can be safely discarded from the buffer. It will otherwise return errors if they are found.
|
|
||||||
///
|
|
||||||
/// This object will drop `Self`
|
|
||||||
pub fn parse(mut self) -> Result<(Query, usize), ParseError> {
|
|
||||||
let number_of_queries = self.parse_metaframe_get_datagroup_count()?;
|
|
||||||
println!("Got count: {}", number_of_queries);
|
|
||||||
if number_of_queries == 0 {
|
|
||||||
// how on earth do you expect us to execute 0 queries? waste of bandwidth
|
|
||||||
return Err(ParseError::BadPacket);
|
|
||||||
}
|
|
||||||
if number_of_queries == 1 {
|
|
||||||
// This is a simple query
|
|
||||||
let single_group = self.parse_next_element()?;
|
|
||||||
// The below line defaults to false if no item is there in the buffer
|
|
||||||
// or it checks if the next time is a \r char; if it is, then it is the beginning
|
|
||||||
// of the next query
|
|
||||||
if self
|
|
||||||
.will_cursor_give_char(b'*', true)
|
|
||||||
.unwrap_or_else(|_| unsafe {
|
|
||||||
// This will never be the case because we'll always get a result and no error value
|
|
||||||
// as we've passed true which will yield Ok(true) even if there is no byte ahead
|
|
||||||
unreachable_unchecked()
|
|
||||||
})
|
|
||||||
{
|
|
||||||
Ok((Query::SimpleQuery(single_group), self.cursor))
|
|
||||||
} else {
|
|
||||||
// the next item isn't the beginning of a query but something else?
|
|
||||||
// that doesn't look right!
|
|
||||||
Err(ParseError::UnexpectedByte)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// This is a pipelined query
|
|
||||||
// We'll first make space for all the actiongroups
|
|
||||||
let mut queries = Vec::with_capacity(number_of_queries);
|
|
||||||
for _ in 0..number_of_queries {
|
|
||||||
queries.push(self.parse_next_element()?);
|
|
||||||
}
|
|
||||||
if self.will_cursor_give_char(b'*', true)? {
|
|
||||||
Ok((Query::PipelinedQuery(queries), self.cursor))
|
|
||||||
} else {
|
|
||||||
Err(ParseError::UnexpectedByte)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_metaframe_parse() {
|
|
||||||
let metaframe = "*2\n".as_bytes();
|
|
||||||
let mut parser = Parser::new(&metaframe);
|
|
||||||
assert_eq!(2, parser.parse_metaframe_get_datagroup_count().unwrap());
|
|
||||||
assert_eq!(parser.cursor, metaframe.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cursor_next_char() {
|
|
||||||
let bytes = &[b'\n'];
|
|
||||||
assert!(Parser::new(&bytes[..])
|
|
||||||
.will_cursor_give_char(b'\n', false)
|
|
||||||
.unwrap());
|
|
||||||
let bytes = &[];
|
|
||||||
assert!(Parser::new(&bytes[..])
|
|
||||||
.will_cursor_give_char(b'\r', true)
|
|
||||||
.unwrap());
|
|
||||||
let bytes = &[];
|
|
||||||
assert!(
|
|
||||||
Parser::new(&bytes[..])
|
|
||||||
.will_cursor_give_char(b'\n', false)
|
|
||||||
.unwrap_err()
|
|
||||||
== ParseError::NotEnough
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_metaframe_parse_fail() {
|
|
||||||
// First byte should be CR and not $
|
|
||||||
let metaframe = "$2\n*2\n".as_bytes();
|
|
||||||
let mut parser = Parser::new(&metaframe);
|
|
||||||
assert_eq!(
|
|
||||||
parser.parse_metaframe_get_datagroup_count().unwrap_err(),
|
|
||||||
ParseError::UnexpectedByte
|
|
||||||
);
|
|
||||||
// Give a wrong length approximation
|
|
||||||
let metaframe = "\r1\n*2\n".as_bytes();
|
|
||||||
assert_eq!(
|
|
||||||
Parser::new(&metaframe)
|
|
||||||
.parse_metaframe_get_datagroup_count()
|
|
||||||
.unwrap_err(),
|
|
||||||
ParseError::UnexpectedByte
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_query_fail_not_enough() {
|
|
||||||
let query_packet = "*".as_bytes();
|
|
||||||
assert_eq!(
|
|
||||||
Parser::new(&query_packet).parse().err().unwrap(),
|
|
||||||
ParseError::NotEnough
|
|
||||||
);
|
|
||||||
let metaframe = "*2".as_bytes();
|
|
||||||
assert_eq!(
|
|
||||||
Parser::new(&metaframe)
|
|
||||||
.parse_metaframe_get_datagroup_count()
|
|
||||||
.unwrap_err(),
|
|
||||||
ParseError::NotEnough
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_next_string() {
|
|
||||||
let bytes = "5\nsayan\n".as_bytes();
|
|
||||||
let st = Parser::new(&bytes).parse_next_string().unwrap();
|
|
||||||
assert_eq!(st, "sayan".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_next_u64() {
|
|
||||||
let max = 18446744073709551615;
|
|
||||||
assert!(u64::MAX == max);
|
|
||||||
let bytes = "20\n18446744073709551615\n".as_bytes();
|
|
||||||
let our_u64 = Parser::new(&bytes).parse_next_u64().unwrap();
|
|
||||||
assert_eq!(our_u64, max);
|
|
||||||
// now overflow the u64
|
|
||||||
let bytes = "21\n184467440737095516156\n".as_bytes();
|
|
||||||
let our_u64 = Parser::new(&bytes).parse_next_u64().unwrap_err();
|
|
||||||
assert_eq!(our_u64, ParseError::DataTypeParseError);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_next_element_string() {
|
|
||||||
let bytes = "+5\nsayan\n".as_bytes();
|
|
||||||
let next_element = Parser::new(&bytes).parse_next_element().unwrap();
|
|
||||||
assert_eq!(next_element, DataType::String("sayan".to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_next_element_string_fail() {
|
|
||||||
let bytes = "+5\nsayan".as_bytes();
|
|
||||||
assert_eq!(
|
|
||||||
Parser::new(&bytes).parse_next_element().unwrap_err(),
|
|
||||||
ParseError::NotEnough
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_next_element_u64() {
|
|
||||||
let bytes = ":20\n18446744073709551615\n".as_bytes();
|
|
||||||
let our_u64 = Parser::new(&bytes).parse_next_element().unwrap();
|
|
||||||
assert_eq!(our_u64, DataType::UnsignedInt(u64::MAX));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_next_element_u64_fail() {
|
|
||||||
let bytes = ":20\n18446744073709551615".as_bytes();
|
|
||||||
assert_eq!(
|
|
||||||
Parser::new(&bytes).parse_next_element().unwrap_err(),
|
|
||||||
ParseError::NotEnough
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_next_element_array() {
|
|
||||||
let bytes = "&3\n+4\nMGET\n+3\nfoo\n+3\nbar\n".as_bytes();
|
|
||||||
let mut parser = Parser::new(&bytes);
|
|
||||||
let array = parser.parse_next_element().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
array,
|
|
||||||
DataType::Array(vec![
|
|
||||||
DataType::String("MGET".to_owned()),
|
|
||||||
DataType::String("foo".to_owned()),
|
|
||||||
DataType::String("bar".to_owned())
|
|
||||||
])
|
|
||||||
);
|
|
||||||
assert_eq!(parser.cursor, bytes.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_next_element_array_fail() {
|
|
||||||
// should've been three elements, but there are two!
|
|
||||||
let bytes = "&3\n+4\nMGET\n+3\nfoo\n+3\n".as_bytes();
|
|
||||||
let mut parser = Parser::new(&bytes);
|
|
||||||
assert_eq!(
|
|
||||||
parser.parse_next_element().unwrap_err(),
|
|
||||||
ParseError::NotEnough
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_nested_array() {
|
|
||||||
// let's test a nested array
|
|
||||||
let bytes =
|
|
||||||
"&3\n+3\nACT\n+3\nfoo\n&4\n+5\nsayan\n+2\nis\n+7\nworking\n&2\n+6\nreally\n+4\nhard\n"
|
|
||||||
.as_bytes();
|
|
||||||
let mut parser = Parser::new(&bytes);
|
|
||||||
let array = parser.parse_next_element().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
array,
|
|
||||||
DataType::Array(vec![
|
|
||||||
DataType::String("ACT".to_owned()),
|
|
||||||
DataType::String("foo".to_owned()),
|
|
||||||
DataType::Array(vec![
|
|
||||||
DataType::String("sayan".to_owned()),
|
|
||||||
DataType::String("is".to_owned()),
|
|
||||||
DataType::String("working".to_owned()),
|
|
||||||
DataType::Array(vec![
|
|
||||||
DataType::String("really".to_owned()),
|
|
||||||
DataType::String("hard".to_owned())
|
|
||||||
])
|
|
||||||
])
|
|
||||||
])
|
|
||||||
);
|
|
||||||
assert_eq!(parser.cursor, bytes.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_multitype_array() {
|
|
||||||
// let's test a nested array
|
|
||||||
let bytes = "&3\n+3\nACT\n+3\nfoo\n&4\n+5\nsayan\n+2\nis\n+7\nworking\n&2\n:2\n23\n+5\napril\n"
|
|
||||||
.as_bytes();
|
|
||||||
let mut parser = Parser::new(&bytes);
|
|
||||||
let array = parser.parse_next_element().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
array,
|
|
||||||
DataType::Array(vec![
|
|
||||||
DataType::String("ACT".to_owned()),
|
|
||||||
DataType::String("foo".to_owned()),
|
|
||||||
DataType::Array(vec![
|
|
||||||
DataType::String("sayan".to_owned()),
|
|
||||||
DataType::String("is".to_owned()),
|
|
||||||
DataType::String("working".to_owned()),
|
|
||||||
DataType::Array(vec![
|
|
||||||
DataType::UnsignedInt(23),
|
|
||||||
DataType::String("april".to_owned())
|
|
||||||
])
|
|
||||||
])
|
|
||||||
])
|
|
||||||
);
|
|
||||||
assert_eq!(parser.cursor, bytes.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_a_query() {
|
|
||||||
let bytes =
|
|
||||||
"*1\n&3\n+3\nACT\n+3\nfoo\n&4\n+5\nsayan\n+2\nis\n+7\nworking\n&2\n:2\n23\n+5\napril\n"
|
|
||||||
.as_bytes();
|
|
||||||
let parser = Parser::new(&bytes);
|
|
||||||
let (resp, forward_by) = parser.parse().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
resp,
|
|
||||||
Query::SimpleQuery(DataType::Array(vec![
|
|
||||||
DataType::String("ACT".to_owned()),
|
|
||||||
DataType::String("foo".to_owned()),
|
|
||||||
DataType::Array(vec![
|
|
||||||
DataType::String("sayan".to_owned()),
|
|
||||||
DataType::String("is".to_owned()),
|
|
||||||
DataType::String("working".to_owned()),
|
|
||||||
DataType::Array(vec![
|
|
||||||
DataType::UnsignedInt(23),
|
|
||||||
DataType::String("april".to_owned())
|
|
||||||
])
|
|
||||||
])
|
|
||||||
]))
|
|
||||||
);
|
|
||||||
assert_eq!(forward_by, bytes.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_a_query_fail_moredata() {
|
|
||||||
let bytes =
|
|
||||||
"*1\n&3\n+3\nACT\n+3\nfoo\n&4\n+5\nsayan\n+2\nis\n+7\nworking\n&1\n:2\n23\n+5\napril\n"
|
|
||||||
.as_bytes();
|
|
||||||
let parser = Parser::new(&bytes);
|
|
||||||
assert_eq!(parser.parse().unwrap_err(), ParseError::UnexpectedByte);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_pipelined_query_incomplete() {
|
|
||||||
// this was a pipelined query: we expected two queries but got one!
|
|
||||||
let bytes =
|
|
||||||
"*2\n&3\n+3\nACT\n+3\nfoo\n&4\n+5\nsayan\n+2\nis\n+7\nworking\n&2\n:2\n23\n+5\napril\n"
|
|
||||||
.as_bytes();
|
|
||||||
assert_eq!(
|
|
||||||
Parser::new(&bytes).parse().unwrap_err(),
|
|
||||||
ParseError::NotEnough
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_pipelined_query() {
|
|
||||||
let bytes =
|
|
||||||
"*2\n&3\n+3\nACT\n+3\nfoo\n&3\n+5\nsayan\n+2\nis\n+7\nworking\n+4\nHEYA\n".as_bytes();
|
|
||||||
/*
|
|
||||||
(\r2\n*2\n)(&3\n)({+3\nACT\n}{+3\nfoo\n}{[&3\n][+5\nsayan\n][+2\nis\n][+7\nworking\n]})(+4\nHEYA\n)
|
|
||||||
*/
|
|
||||||
let (res, forward_by) = Parser::new(&bytes).parse().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
res,
|
|
||||||
Query::PipelinedQuery(vec![
|
|
||||||
DataType::Array(vec![
|
|
||||||
DataType::String("ACT".to_owned()),
|
|
||||||
DataType::String("foo".to_owned()),
|
|
||||||
DataType::Array(vec![
|
|
||||||
DataType::String("sayan".to_owned()),
|
|
||||||
DataType::String("is".to_owned()),
|
|
||||||
DataType::String("working".to_owned())
|
|
||||||
])
|
|
||||||
]),
|
|
||||||
DataType::String("HEYA".to_owned())
|
|
||||||
])
|
|
||||||
);
|
|
||||||
assert_eq!(forward_by, bytes.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_query_with_part_of_next_query() {
|
|
||||||
let bytes =
|
|
||||||
"*1\n&3\n+3\nACT\n+3\nfoo\n&4\n+5\nsayan\n+2\nis\n+7\nworking\n&2\n:2\n23\n+5\napril\n*1\n"
|
|
||||||
.as_bytes();
|
|
||||||
let (res, forward_by) = Parser::new(&bytes).parse().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
res,
|
|
||||||
Query::SimpleQuery(DataType::Array(vec![
|
|
||||||
DataType::String("ACT".to_owned()),
|
|
||||||
DataType::String("foo".to_owned()),
|
|
||||||
DataType::Array(vec![
|
|
||||||
DataType::String("sayan".to_owned()),
|
|
||||||
DataType::String("is".to_owned()),
|
|
||||||
DataType::String("working".to_owned()),
|
|
||||||
DataType::Array(vec![
|
|
||||||
DataType::UnsignedInt(23),
|
|
||||||
DataType::String("april".to_owned())
|
|
||||||
])
|
|
||||||
])
|
|
||||||
]))
|
|
||||||
);
|
|
||||||
// there are some ingenious folks on this planet who might just go bombing one query after the other
|
|
||||||
// we happily ignore those excess queries and leave it to the next round of parsing
|
|
||||||
assert_eq!(forward_by, bytes.len() - "*1\n".len());
|
|
||||||
}
|
|
Loading…
Reference in New Issue