use lazy_static::lazy_static;
use rusqlite::{self, limits::Limit, types::ToSql};
use std::iter::Map;
use std::slice::Iter;
pub fn default_max_variable_number() -> usize {
lazy_static! {
static ref MAX_VARIABLE_NUMBER: usize = {
let conn = rusqlite::Connection::open_in_memory()
.expect("Failed to initialize in-memory connection (out of memory?)");
let limit = conn.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER);
assert!(
limit > 0,
"Illegal value for SQLITE_LIMIT_VARIABLE_NUMBER (must be > 0) {}",
limit
);
limit as usize
};
}
*MAX_VARIABLE_NUMBER
}
pub fn each_chunk<'a, T, E, F>(items: &'a [T], do_chunk: F) -> Result<(), E>
where
T: 'a,
F: FnMut(&'a [T], usize) -> Result<(), E>,
{
each_sized_chunk(items, default_max_variable_number(), do_chunk)
}
pub fn each_chunk_mapped<'a, T, U, E, Mapper, DoChunk>(
items: &'a [T],
to_sql: Mapper,
do_chunk: DoChunk,
) -> Result<(), E>
where
T: 'a,
U: ToSql + 'a,
Mapper: Fn(&'a T) -> U,
DoChunk: FnMut(Map<Iter<'a, T>, &'_ Mapper>, usize) -> Result<(), E>,
{
each_sized_chunk_mapped(items, default_max_variable_number(), to_sql, do_chunk)
}
pub fn each_sized_chunk<'a, T, E, F>(
items: &'a [T],
chunk_size: usize,
mut do_chunk: F,
) -> Result<(), E>
where
T: 'a,
F: FnMut(&'a [T], usize) -> Result<(), E>,
{
if items.is_empty() {
return Ok(());
}
let mut offset = 0;
for chunk in items.chunks(chunk_size) {
do_chunk(chunk, offset)?;
offset += chunk.len();
}
Ok(())
}
pub fn each_sized_chunk_mapped<'a, T, U, E, Mapper, DoChunk>(
items: &'a [T],
chunk_size: usize,
to_sql: Mapper,
mut do_chunk: DoChunk,
) -> Result<(), E>
where
T: 'a,
U: ToSql + 'a,
Mapper: Fn(&'a T) -> U,
DoChunk: FnMut(Map<Iter<'a, T>, &'_ Mapper>, usize) -> Result<(), E>,
{
if items.is_empty() {
return Ok(());
}
let mut offset = 0;
for chunk in items.chunks(chunk_size) {
let mapped = chunk.iter().map(&to_sql);
do_chunk(mapped, offset)?;
offset += chunk.len();
}
Ok(())
}
#[cfg(test)]
fn check_chunk<T, C>(items: C, expect: &[T], desc: &str)
where
C: IntoIterator,
<C as IntoIterator>::Item: ToSql,
T: ToSql,
{
let items = items.into_iter().collect::<Vec<_>>();
assert_eq!(items.len(), expect.len());
for (idx, (got, want)) in items.iter().zip(expect.iter()).enumerate() {
assert_eq!(
got.to_sql().unwrap(),
want.to_sql().unwrap(),
"{}: Bad value at index {}",
desc,
idx
);
}
}
#[cfg(test)]
mod test_mapped {
use super::*;
#[test]
fn test_separate() {
let mut iteration = 0;
each_sized_chunk_mapped(
&[1, 2, 3, 4, 5],
3,
|item| item as &dyn ToSql,
|chunk, offset| {
match offset {
0 => {
assert_eq!(iteration, 0);
check_chunk(chunk, &[1, 2, 3], "first chunk");
}
3 => {
assert_eq!(iteration, 1);
check_chunk(chunk, &[4, 5], "second chunk");
}
n => {
panic!("Unexpected offset {}", n);
}
}
iteration += 1;
Ok::<(), ()>(())
},
)
.unwrap();
}
#[test]
fn test_leq_chunk_size() {
for &check_size in &[5, 6] {
let mut iteration = 0;
each_sized_chunk_mapped(
&[1, 2, 3, 4, 5],
check_size,
|item| item as &dyn ToSql,
|chunk, offset| {
assert_eq!(iteration, 0);
iteration += 1;
assert_eq!(offset, 0);
check_chunk(chunk, &[1, 2, 3, 4, 5], "only iteration");
Ok::<(), ()>(())
},
)
.unwrap();
}
}
#[test]
fn test_empty_chunk() {
let items: &[i64] = &[];
each_sized_chunk_mapped::<_, _, (), _, _>(
items,
100,
|item| item as &dyn ToSql,
|_, _| {
panic!("Should never be called");
},
)
.unwrap();
}
#[test]
fn test_error() {
let mut iteration = 0;
let e = each_sized_chunk_mapped(
&[1, 2, 3, 4, 5, 6, 7],
3,
|item| item as &dyn ToSql,
|_, offset| {
if offset == 0 {
assert_eq!(iteration, 0);
iteration += 1;
Ok(())
} else if offset == 3 {
assert_eq!(iteration, 1);
iteration += 1;
Err("testing".to_string())
} else {
panic!("Shouldn't get called with offset of {}", offset);
}
},
)
.expect_err("Should be an error");
assert_eq!(e, "testing");
}
}
#[cfg(test)]
mod test_unmapped {
use super::*;
#[test]
fn test_separate() {
let mut iteration = 0;
each_sized_chunk(&[1, 2, 3, 4, 5], 3, |chunk, offset| {
match offset {
0 => {
assert_eq!(iteration, 0);
check_chunk(chunk, &[1, 2, 3], "first chunk");
}
3 => {
assert_eq!(iteration, 1);
check_chunk(chunk, &[4, 5], "second chunk");
}
n => {
panic!("Unexpected offset {}", n);
}
}
iteration += 1;
Ok::<(), ()>(())
})
.unwrap();
}
#[test]
fn test_leq_chunk_size() {
for &check_size in &[5, 6] {
let mut iteration = 0;
each_sized_chunk(&[1, 2, 3, 4, 5], check_size, |chunk, offset| {
assert_eq!(iteration, 0);
iteration += 1;
assert_eq!(offset, 0);
check_chunk(chunk, &[1, 2, 3, 4, 5], "only iteration");
Ok::<(), ()>(())
})
.unwrap();
}
}
#[test]
fn test_empty_chunk() {
let items: &[i64] = &[];
each_sized_chunk::<_, (), _>(items, 100, |_, _| {
panic!("Should never be called");
})
.unwrap();
}
#[test]
fn test_error() {
let mut iteration = 0;
let e = each_sized_chunk(&[1, 2, 3, 4, 5, 6, 7], 3, |_, offset| {
if offset == 0 {
assert_eq!(iteration, 0);
iteration += 1;
Ok(())
} else if offset == 3 {
assert_eq!(iteration, 1);
iteration += 1;
Err("testing".to_string())
} else {
panic!("Shouldn't get called with offset of {}", offset);
}
})
.expect_err("Should be an error");
assert_eq!(e, "testing");
}
}