Allow parameters in LIMIT and START clauses in SQL SELECT statements

Closes #1332
Closes #116
This commit is contained in:
Tobie Morgan Hitchcock 2022-10-17 02:57:53 +01:00
parent 7ed0af4750
commit 0a4b810fbd
9 changed files with 167 additions and 43 deletions

View file

@ -44,6 +44,10 @@ pub enum Workable {
pub struct Iterator { pub struct Iterator {
// Iterator status // Iterator status
run: Canceller, run: Canceller,
// Iterator limit value
limit: Option<usize>,
// Iterator start value
start: Option<usize>,
// Iterator runtime error // Iterator runtime error
error: Option<Error>, error: Option<Error>,
// Iterator output results // Iterator output results
@ -76,6 +80,10 @@ impl Iterator {
// Enable context override // Enable context override
let mut ctx = Context::new(ctx); let mut ctx = Context::new(ctx);
self.run = ctx.add_cancel(); self.run = ctx.add_cancel();
// Process the query LIMIT clause
self.setup_limit(&ctx, opt, txn, stm).await?;
// Process the query START clause
self.setup_start(&ctx, opt, txn, stm).await?;
// Process prepared values // Process prepared values
self.iterate(&ctx, opt, txn, stm).await?; self.iterate(&ctx, opt, txn, stm).await?;
// Return any document errors // Return any document errors
@ -98,6 +106,34 @@ impl Iterator {
Ok(mem::take(&mut self.results).into()) Ok(mem::take(&mut self.results).into())
} }
#[inline]
async fn setup_limit(
&mut self,
ctx: &Context<'_>,
opt: &Options,
txn: &Transaction,
stm: &Statement<'_>,
) -> Result<(), Error> {
if let Some(v) = stm.limit() {
self.limit = Some(v.process(ctx, opt, txn, None).await?);
}
Ok(())
}
#[inline]
async fn setup_start(
&mut self,
ctx: &Context<'_>,
opt: &Options,
txn: &Transaction,
stm: &Statement<'_>,
) -> Result<(), Error> {
if let Some(v) = stm.start() {
self.start = Some(v.process(ctx, opt, txn, None).await?);
}
Ok(())
}
#[inline] #[inline]
async fn output_split( async fn output_split(
&mut self, &mut self,
@ -273,10 +309,10 @@ impl Iterator {
_ctx: &Context<'_>, _ctx: &Context<'_>,
_opt: &Options, _opt: &Options,
_txn: &Transaction, _txn: &Transaction,
stm: &Statement<'_>, _stm: &Statement<'_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
if let Some(v) = stm.start() { if let Some(v) = self.start {
self.results = mem::take(&mut self.results).into_iter().skip(v.0).collect(); self.results = mem::take(&mut self.results).into_iter().skip(v).collect();
} }
Ok(()) Ok(())
} }
@ -287,10 +323,10 @@ impl Iterator {
_ctx: &Context<'_>, _ctx: &Context<'_>,
_opt: &Options, _opt: &Options,
_txn: &Transaction, _txn: &Transaction,
stm: &Statement<'_>, _stm: &Statement<'_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
if let Some(v) = stm.limit() { if let Some(v) = self.limit {
self.results = mem::take(&mut self.results).into_iter().take(v.0).collect(); self.results = mem::take(&mut self.results).into_iter().take(v).collect();
} }
Ok(()) Ok(())
} }
@ -464,12 +500,12 @@ impl Iterator {
} }
// Check if we can exit // Check if we can exit
if stm.group().is_none() && stm.order().is_none() { if stm.group().is_none() && stm.order().is_none() {
if let Some(l) = stm.limit() { if let Some(l) = self.limit {
if let Some(s) = stm.start() { if let Some(s) = self.start {
if self.results.len() == l.0 + s.0 { if self.results.len() == l + s {
self.run.cancel() self.run.cancel()
} }
} else if self.results.len() == l.0 { } else if self.results.len() == l {
self.run.cancel() self.run.cancel()
} }
} }

View file

@ -75,6 +75,18 @@ pub enum Error {
#[error("Remote HTTP request functions are not enabled")] #[error("Remote HTTP request functions are not enabled")]
HttpDisabled, HttpDisabled,
/// The LIMIT clause must evaluate to a positive integer
#[error("Found {value} but the LIMIT clause must evaluate to a positive integer")]
InvalidLimit {
value: String,
},
/// The START clause must evaluate to a positive integer
#[error("Found {value} but the START clause must evaluate to a positive integer")]
InvalidStart {
value: String,
},
/// There was an error with the provided JavaScript code /// There was an error with the provided JavaScript code
#[error("Problem with embedded script function. {message}")] #[error("Problem with embedded script function. {message}")]
InvalidScript { InvalidScript {

View file

@ -57,14 +57,6 @@ pub fn take_u64(i: &str) -> IResult<&str, u64> {
} }
} }
pub fn take_usize(i: &str) -> IResult<&str, usize> {
let (i, v) = take_while(is_digit)(i)?;
match v.parse::<usize>() {
Ok(v) => Ok((i, v)),
_ => Err(Error(ParserError(i))),
}
}
pub fn take_u32_len(i: &str) -> IResult<&str, (u32, usize)> { pub fn take_u32_len(i: &str) -> IResult<&str, (u32, usize)> {
let (i, v) = take_while(is_digit)(i)?; let (i, v) = take_while(is_digit)(i)?;
match v.parse::<u32>() { match v.parse::<u32>() {

View file

@ -1,6 +1,10 @@
use crate::ctx::Context;
use crate::dbs::Options;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::comment::shouldbespace; use crate::sql::comment::shouldbespace;
use crate::sql::common::take_usize;
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::value::{value, Value};
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::combinator::opt; use nom::combinator::opt;
use nom::sequence::tuple; use nom::sequence::tuple;
@ -8,7 +12,25 @@ use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Limit(pub usize); pub struct Limit(pub Value);
impl Limit {
pub(crate) async fn process(
&self,
ctx: &Context<'_>,
opt: &Options,
txn: &Transaction,
doc: Option<&Value>,
) -> Result<usize, Error> {
match self.0.compute(ctx, opt, txn, doc).await {
Ok(v) if v.is_integer() && v.is_positive() => Ok(v.as_usize()),
Ok(v) => Err(Error::InvalidLimit {
value: v.as_string(),
}),
Err(e) => Err(e),
}
}
}
impl fmt::Display for Limit { impl fmt::Display for Limit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -20,7 +42,7 @@ pub fn limit(i: &str) -> IResult<&str, Limit> {
let (i, _) = tag_no_case("LIMIT")(i)?; let (i, _) = tag_no_case("LIMIT")(i)?;
let (i, _) = opt(tuple((shouldbespace, tag_no_case("BY"))))(i)?; let (i, _) = opt(tuple((shouldbespace, tag_no_case("BY"))))(i)?;
let (i, _) = shouldbespace(i)?; let (i, _) = shouldbespace(i)?;
let (i, v) = take_usize(i)?; let (i, v) = value(i)?;
Ok((i, Limit(v))) Ok((i, Limit(v)))
} }
@ -35,7 +57,7 @@ mod tests {
let res = limit(sql); let res = limit(sql);
assert!(res.is_ok()); assert!(res.is_ok());
let out = res.unwrap().1; let out = res.unwrap().1;
assert_eq!(out, Limit(100)); assert_eq!(out, Limit(Value::from(100)));
assert_eq!("LIMIT 100", format!("{}", out)); assert_eq!("LIMIT 100", format!("{}", out));
} }
@ -45,7 +67,7 @@ mod tests {
let res = limit(sql); let res = limit(sql);
assert!(res.is_ok()); assert!(res.is_ok());
let out = res.unwrap().1; let out = res.unwrap().1;
assert_eq!(out, Limit(100)); assert_eq!(out, Limit(Value::from(100)));
assert_eq!("LIMIT 100", format!("{}", out)); assert_eq!("LIMIT 100", format!("{}", out));
} }
} }

View file

@ -173,6 +173,14 @@ impl Number {
matches!(self, Number::Decimal(_)) matches!(self, Number::Decimal(_))
} }
pub fn is_integer(&self) -> bool {
match self {
Number::Int(_) => true,
Number::Float(_) => false,
Number::Decimal(v) => v.is_integer(),
}
}
pub fn is_truthy(&self) -> bool { pub fn is_truthy(&self) -> bool {
match self { match self {
Number::Int(v) => v != &0, Number::Int(v) => v != &0,
@ -181,6 +189,14 @@ impl Number {
} }
} }
pub fn is_positive(&self) -> bool {
match self {
Number::Int(v) => v > &0,
Number::Float(v) => v > &0.0,
Number::Decimal(v) => v > &BigDecimal::from(0),
}
}
// ----------------------------------- // -----------------------------------
// Simple conversion of number // Simple conversion of number
// ----------------------------------- // -----------------------------------

View file

@ -1,14 +1,36 @@
use crate::ctx::Context;
use crate::dbs::Options;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::comment::shouldbespace; use crate::sql::comment::shouldbespace;
use crate::sql::common::take_usize;
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::value::{value, Value};
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::combinator::opt; use nom::combinator::opt;
use nom::sequence::tuple; use nom::sequence::tuple;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Start(pub usize); pub struct Start(pub Value);
impl Start {
pub(crate) async fn process(
&self,
ctx: &Context<'_>,
opt: &Options,
txn: &Transaction,
doc: Option<&Value>,
) -> Result<usize, Error> {
match self.0.compute(ctx, opt, txn, doc).await {
Ok(v) if v.is_integer() && v.is_positive() => Ok(v.as_usize()),
Ok(v) => Err(Error::InvalidStart {
value: v.as_string(),
}),
Err(e) => Err(e),
}
}
}
impl fmt::Display for Start { impl fmt::Display for Start {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -20,7 +42,7 @@ pub fn start(i: &str) -> IResult<&str, Start> {
let (i, _) = tag_no_case("START")(i)?; let (i, _) = tag_no_case("START")(i)?;
let (i, _) = opt(tuple((shouldbespace, tag_no_case("AT"))))(i)?; let (i, _) = opt(tuple((shouldbespace, tag_no_case("AT"))))(i)?;
let (i, _) = shouldbespace(i)?; let (i, _) = shouldbespace(i)?;
let (i, v) = take_usize(i)?; let (i, v) = value(i)?;
Ok((i, Start(v))) Ok((i, Start(v)))
} }
@ -35,7 +57,7 @@ mod tests {
let res = start(sql); let res = start(sql);
assert!(res.is_ok()); assert!(res.is_ok());
let out = res.unwrap().1; let out = res.unwrap().1;
assert_eq!(out, Start(100)); assert_eq!(out, Start(Value::from(100)));
assert_eq!("START 100", format!("{}", out)); assert_eq!("START 100", format!("{}", out));
} }
@ -45,7 +67,7 @@ mod tests {
let res = start(sql); let res = start(sql);
assert!(res.is_ok()); assert!(res.is_ok());
let out = res.unwrap().1; let out = res.unwrap().1;
assert_eq!(out, Start(100)); assert_eq!(out, Start(Value::from(100)));
assert_eq!("START 100", format!("{}", out)); assert_eq!("START 100", format!("{}", out));
} }
} }

View file

@ -43,19 +43,16 @@ pub struct SelectStatement {
} }
impl SelectStatement { impl SelectStatement {
/// Return the statement limit number or 0 if not set pub(crate) async fn limit(
pub fn limit(&self) -> usize { &self,
match self.limit { ctx: &Context<'_>,
Some(Limit(v)) => v, opt: &Options,
None => 0, txn: &Transaction,
} doc: Option<&Value>,
} ) -> Result<usize, Error> {
match &self.limit {
/// Return the statement start number or 0 if not set Some(v) => v.process(ctx, opt, txn, doc).await,
pub fn start(&self) -> usize { None => Ok(0),
match self.start {
Some(Start(v)) => v,
None => 0,
} }
} }

View file

@ -76,7 +76,7 @@ impl Subquery {
// Process subquery // Process subquery
let res = v.compute(&ctx, opt, txn, doc).await?; let res = v.compute(&ctx, opt, txn, doc).await?;
// Process result // Process result
match v.limit() { match v.limit(&ctx, opt, txn, doc).await? {
1 => match v.expr.single() { 1 => match v.expr.single() {
Some(v) => res.first().get(&ctx, opt, txn, &v).await, Some(v) => res.first().get(&ctx, opt, txn, &v).await,
None => res.first().ok(), None => res.first().ok(),

View file

@ -571,6 +571,26 @@ impl Value {
matches!(self, Value::Object(_)) matches!(self, Value::Object(_))
} }
pub fn is_int(&self) -> bool {
matches!(self, Value::Number(Number::Int(_)))
}
pub fn is_float(&self) -> bool {
matches!(self, Value::Number(Number::Float(_)))
}
pub fn is_decimal(&self) -> bool {
matches!(self, Value::Number(Number::Decimal(_)))
}
pub fn is_integer(&self) -> bool {
matches!(self, Value::Number(v) if v.is_integer())
}
pub fn is_positive(&self) -> bool {
matches!(self, Value::Number(v) if v.is_positive())
}
pub fn is_type_record(&self, types: &[Table]) -> bool { pub fn is_type_record(&self, types: &[Table]) -> bool {
match self { match self {
Value::Thing(v) => types.iter().any(|tb| tb.0 == v.tb), Value::Thing(v) => types.iter().any(|tb| tb.0 == v.tb),
@ -685,6 +705,13 @@ impl Value {
} }
} }
pub fn as_usize(self) -> usize {
match self {
Value::Number(v) => v.as_usize(),
_ => 0,
}
}
// ----------------------------------- // -----------------------------------
// Expensive conversion of value // Expensive conversion of value
// ----------------------------------- // -----------------------------------