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::err::Error;
use crate::sql::datetime::Datetime;
use crate::sql::uuid::Uuid;
use crate::sql::value::Value;
use chrono::{TimeZone, Utc};
use nanoid::nanoid;
use rand::distributions::{Alphanumeric, DistString};
use rand::prelude::IteratorRandom;
@ -41,22 +41,39 @@ pub fn float((range,): (Option<(f64, f64)>,)) -> Result<Value, Error> {
.into())
}
pub fn guid((len,): (Option<usize>,)) -> Result<Value, Error> {
// Only need 53 to uniquely identify all atoms in observable universe.
const LIMIT: usize = 64;
let len = match len {
Some(len) if len <= LIMIT => len,
None => 20,
_ => {
pub fn guid((arg1, arg2): (Option<i64>, Option<i64>)) -> Result<Value, Error> {
// Set a reasonable maximum length
const LIMIT: i64 = 64;
// Check the function input arguments
let val = if let Some((min, max)) = arg1.zip(arg2) {
match min {
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 {
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
};
Ok(nanoid!(len, &ID_CHARS).into())
// Generate the random guid
Ok(nanoid!(val, &ID_CHARS).into())
}
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> {
// Limit how much time and bandwidth is spent.
const LIMIT: i64 = 2i64.pow(16);
let len = if let Some((min, max)) = arg1.zip(arg2) {
// Set a reasonable maximum length
const LIMIT: i64 = 65536;
// Check the function input arguments
let val = if let Some((min, max)) = arg1.zip(arg2) {
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 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 {
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),
@ -92,7 +109,7 @@ pub fn string((arg1, arg2): (Option<i64>, Option<i64>)) -> Result<Value, Error>
}),
}
} else if let Some(len) = arg1 {
if (0..=LIMIT).contains(&len) {
if (1..=LIMIT).contains(&len) {
len as usize
} else {
return Err(Error::InvalidArguments {
@ -103,22 +120,34 @@ pub fn string((arg1, arg2): (Option<i64>, Option<i64>)) -> Result<Value, Error>
} else {
32
};
Ok(Alphanumeric.sample_string(&mut rand::thread_rng(), len).into())
// Generate the random string
Ok(Alphanumeric.sample_string(&mut rand::thread_rng(), val).into())
}
pub fn time((range,): (Option<(i64, i64)>,)) -> Result<Value, Error> {
let i = if let Some((min, max)) = range {
let range = if max < min {
max..=min
} else {
min..=max
};
rand::thread_rng().gen_range(range)
// Set the maximum valid seconds
const LIMIT: i64 = 8210298412799;
// Check the function input arguments
let val = if let Some((min, max)) = range {
match min {
min if (1..=LIMIT).contains(&min) => match max {
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 {
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> {