Ensure SQL rand() functions do not hang indefinitely

This commit is contained in:
Tobie Morgan Hitchcock 2023-01-17 10:28:54 +00:00
parent bb0b10e38a
commit 9fa0a4fbb4

View file

@ -1,8 +1,8 @@
use crate::cnf::ID_CHARS; use crate::cnf::ID_CHARS;
use crate::err::Error; use crate::err::Error;
use crate::sql::datetime::Datetime;
use crate::sql::uuid::Uuid; use crate::sql::uuid::Uuid;
use crate::sql::value::Value; use crate::sql::value::Value;
use chrono::{TimeZone, Utc};
use nanoid::nanoid; use nanoid::nanoid;
use rand::distributions::{Alphanumeric, DistString}; use rand::distributions::{Alphanumeric, DistString};
use rand::prelude::IteratorRandom; use rand::prelude::IteratorRandom;
@ -41,22 +41,39 @@ pub fn float((range,): (Option<(f64, f64)>,)) -> Result<Value, Error> {
.into()) .into())
} }
pub fn guid((len,): (Option<usize>,)) -> Result<Value, Error> { pub fn guid((arg1, arg2): (Option<i64>, Option<i64>)) -> Result<Value, Error> {
// Only need 53 to uniquely identify all atoms in observable universe. // Set a reasonable maximum length
const LIMIT: usize = 64; const LIMIT: i64 = 64;
// Check the function input arguments
let len = match len { let val = if let Some((min, max)) = arg1.zip(arg2) {
Some(len) if len <= LIMIT => len, match min {
None => 20, min if (1..=LIMIT).contains(&min) => match max {
_ => { max if min <= max && max <= LIMIT => rand::thread_rng().gen_range(min as usize..=max as usize),
max if max >= 1 && max <= min => rand::thread_rng().gen_range(max as usize..=min as usize),
_ => return Err(Error::InvalidArguments {
name: String::from("rand::guid"),
message: format!("To generate a guid of between X and Y characters in length, the 2 arguments must be positive numbers and no higher than {}.", LIMIT),
}),
},
_ => return Err(Error::InvalidArguments {
name: String::from("rand::guid"),
message: format!("To generate a string of between X and Y characters in length, the 2 arguments must be positive numbers and no higher than {}.", LIMIT),
}),
}
} else if let Some(len) = arg1 {
if (1..=LIMIT).contains(&len) {
len as usize
} else {
return Err(Error::InvalidArguments { return Err(Error::InvalidArguments {
name: String::from("rand::guid"), name: String::from("rand::guid"),
message: format!("The maximum length of a GUID is {}.", LIMIT), message: format!("To generate a string of X characters in length, the argument must be a positive number and no higher than {}.", LIMIT),
}) });
} }
} else {
20
}; };
// Generate the random guid
Ok(nanoid!(len, &ID_CHARS).into()) Ok(nanoid!(val, &ID_CHARS).into())
} }
pub fn int((range,): (Option<(i64, i64)>,)) -> Result<Value, Error> { pub fn int((range,): (Option<(i64, i64)>,)) -> Result<Value, Error> {
@ -73,14 +90,14 @@ pub fn int((range,): (Option<(i64, i64)>,)) -> Result<Value, Error> {
} }
pub fn string((arg1, arg2): (Option<i64>, Option<i64>)) -> Result<Value, Error> { pub fn string((arg1, arg2): (Option<i64>, Option<i64>)) -> Result<Value, Error> {
// Limit how much time and bandwidth is spent. // Set a reasonable maximum length
const LIMIT: i64 = 2i64.pow(16); const LIMIT: i64 = 65536;
// Check the function input arguments
let len = if let Some((min, max)) = arg1.zip(arg2) { let val = if let Some((min, max)) = arg1.zip(arg2) {
match min { match min {
min if (0..=LIMIT).contains(&min) => match max { min if (1..=LIMIT).contains(&min) => match max {
max if min <= max && max <= LIMIT => rand::thread_rng().gen_range(min as usize..=max as usize), max if min <= max && max <= LIMIT => rand::thread_rng().gen_range(min as usize..=max as usize),
max if max >= 0 && max <= min => rand::thread_rng().gen_range(max as usize..=min as usize), max if max >= 1 && max <= min => rand::thread_rng().gen_range(max as usize..=min as usize),
_ => return Err(Error::InvalidArguments { _ => return Err(Error::InvalidArguments {
name: String::from("rand::string"), name: String::from("rand::string"),
message: format!("To generate a string of between X and Y characters in length, the 2 arguments must be positive numbers and no higher than {}.", LIMIT), message: format!("To generate a string of between X and Y characters in length, the 2 arguments must be positive numbers and no higher than {}.", LIMIT),
@ -92,7 +109,7 @@ pub fn string((arg1, arg2): (Option<i64>, Option<i64>)) -> Result<Value, Error>
}), }),
} }
} else if let Some(len) = arg1 { } else if let Some(len) = arg1 {
if (0..=LIMIT).contains(&len) { if (1..=LIMIT).contains(&len) {
len as usize len as usize
} else { } else {
return Err(Error::InvalidArguments { return Err(Error::InvalidArguments {
@ -103,22 +120,34 @@ pub fn string((arg1, arg2): (Option<i64>, Option<i64>)) -> Result<Value, Error>
} else { } else {
32 32
}; };
// Generate the random string
Ok(Alphanumeric.sample_string(&mut rand::thread_rng(), len).into()) Ok(Alphanumeric.sample_string(&mut rand::thread_rng(), val).into())
} }
pub fn time((range,): (Option<(i64, i64)>,)) -> Result<Value, Error> { pub fn time((range,): (Option<(i64, i64)>,)) -> Result<Value, Error> {
let i = if let Some((min, max)) = range { // Set the maximum valid seconds
let range = if max < min { const LIMIT: i64 = 8210298412799;
max..=min // Check the function input arguments
} else { let val = if let Some((min, max)) = range {
min..=max match min {
}; min if (1..=LIMIT).contains(&min) => match max {
rand::thread_rng().gen_range(range) max if min <= max && max <= LIMIT => rand::thread_rng().gen_range(min..=max),
max if max >= 1 && max <= min => rand::thread_rng().gen_range(max..=min),
_ => return Err(Error::InvalidArguments {
name: String::from("rand::time"),
message: format!("To generate a time between X and Y seconds, the 2 arguments must be positive numbers and no higher than {}.", LIMIT),
}),
},
_ => return Err(Error::InvalidArguments {
name: String::from("rand::time"),
message: format!("To generate a time between X and Y seconds, the 2 arguments must be positive numbers and no higher than {}.", LIMIT),
}),
}
} else { } else {
rand::random::<i32>() as i64 rand::thread_rng().gen_range(0..=LIMIT)
}; };
Ok(Datetime::from(i).into()) // Generate the random time
Ok(Utc.timestamp_opt(val, 0).earliest().unwrap().into())
} }
pub fn ulid(_: ()) -> Result<Value, Error> { pub fn ulid(_: ()) -> Result<Value, Error> {