use crate::objects::{head, read_object, validate_id};
use crate::{
ObjectHeadV1, OpaqueObjectRecordV1, SqliteOpaqueObjectStoreV1, SqliteSyncError as E,
SqliteSyncResult as R,
};
use rusqlite::params;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ObjectChangesPageV1 {
pub head: ObjectHeadV1,
pub cursor: u64,
pub complete: bool,
pub records: Vec<OpaqueObjectRecordV1>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ObjectsPageV1 {
pub head: ObjectHeadV1,
pub next_key: Option<String>,
pub records: Vec<OpaqueObjectRecordV1>,
}
impl SqliteOpaqueObjectStoreV1 {
pub fn changes(
&self,
installation: &str,
generation: &str,
after: u64,
through: u64,
limit: usize,
max_bytes: usize,
) -> R<ObjectChangesPageV1> {
validate_page(installation, limit, max_bytes)?;
self.0.with_connection(|connection| {
let transaction=connection.transaction().map_err(E::database)?;
let current=head(&transaction)?;
if current.generation!=generation || after>through || through>current.revision {return Err(E::CorruptRecord("opaque object precondition"));}
let mut statement=transaction.prepare("SELECT max(c.revision),c.object_key,length(o.envelope)+length(o.metadata) FROM appcore_object_change c JOIN appcore_opaque_object o ON o.installation_id=c.installation_id AND o.object_key=c.object_key WHERE c.installation_id=?1 AND c.revision>?2 AND c.revision<=?3 GROUP BY c.object_key ORDER BY max(c.revision) LIMIT ?4").map_err(E::database)?;
let rows=statement.query_map(params![installation,after,through,limit+1],|row|Ok((row.get::<_,u64>(0)?,row.get::<_,String>(1)?,row.get::<_,usize>(2)?))).map_err(E::database)?;
let selected=rows.collect::<Result<Vec<_>,_>>().map_err(E::database)?;
let mut bytes=0usize;let mut records=Vec::new();let mut cursor=after;let mut complete=true;
for (position,key,length) in selected {
if records.len()==limit || bytes.saturating_add(length)>max_bytes {complete=false;break;}
bytes+=length;cursor=position;
records.push(read_object(&transaction,installation,&key)?.ok_or(E::IntegrityFailed)?);
}
if !complete && records.is_empty() {return Err(E::CapacityExceeded("object page bytes"));}
if complete {cursor=through;}
Ok(ObjectChangesPageV1 {head:current,cursor,complete,records})
})
}
pub fn scan(
&self,
installation: &str,
after: Option<&str>,
limit: usize,
max_bytes: usize,
) -> R<ObjectsPageV1> {
validate_page(installation, limit, max_bytes)?;
if let Some(key) = after {
validate_id(key)?;
}
self.0.with_connection(|connection| {
let transaction=connection.transaction().map_err(E::database)?;
let current=head(&transaction)?;
let mut statement=transaction.prepare("SELECT object_key,length(envelope)+length(metadata) FROM appcore_opaque_object WHERE installation_id=?1 AND object_key>?2 ORDER BY object_key LIMIT ?3").map_err(E::database)?;
let rows=statement.query_map(params![installation,after.unwrap_or(""),limit+1],|row|Ok((row.get::<_,String>(0)?,row.get::<_,usize>(1)?))).map_err(E::database)?;
let selected=rows.collect::<Result<Vec<_>,_>>().map_err(E::database)?;
let mut bytes=0usize;let mut records=Vec::new();let mut last=None;let mut more=false;
for (key,length) in selected {
if records.len()==limit || bytes.saturating_add(length)>max_bytes {more=true;break;}
bytes+=length;last=Some(key.clone());records.push(read_object(&transaction,installation,&key)?.ok_or(E::IntegrityFailed)?);
}
if more && records.is_empty() {return Err(E::CapacityExceeded("object page bytes"));}
Ok(ObjectsPageV1 {head:current,next_key:if more {last}else{None},records})
})
}
}
fn validate_page(installation: &str, limit: usize, max_bytes: usize) -> R<()> {
validate_id(installation)?;
if limit == 0 || limit > 1000 || max_bytes == 0 || max_bytes > 16 * 1024 * 1024 {
return Err(E::CapacityExceeded("object page"));
}
Ok(())
}