use super::{
bounded_identifier, bounded_text, maximum_document_bytes, stairwell_row_within,
stored_document, text, utf8_column,
};
use crate::{Error, ErrorCode, Pack, is_identifier};
use rusqlite::{Row, ToSql, params, types};
use std::fmt::Write as _;
use tilewright_jsonc::{Document, MAX_SAFE_INTEGER, Value};
use tilewright_source::{CHUNK_SIZE, Stairwell};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Plane {
pub id: String,
pub kind: String,
pub chunk_size: u16,
pub document: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Zone {
pub plane: String,
pub id: String,
pub parent: Option<String>,
pub document: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Anchor {
pub plane: String,
pub id: String,
pub layer: i32,
pub x: i64,
pub y: i64,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Definition {
pub kind: String,
pub id: String,
pub document: String,
}
pub(super) fn integer(column: &str) -> String {
format!("CASE WHEN typeof({column}) = 'integer' THEN {column} END")
}
pub(super) fn identifier(
row: &Row<'_>,
index: usize,
what: &str,
code: ErrorCode,
) -> Result<String, Error> {
utf8_column(row, index, code)?
.filter(|value| is_identifier(value))
.ok_or_else(|| Error::Rejected(code, format!("a {what} key is not an identifier")))
}
pub(super) fn layer(
row: &Row<'_>,
index: usize,
what: &str,
code: ErrorCode,
) -> Result<i32, Error> {
let value: Option<i64> = row.get(index)?;
value
.and_then(|layer| i32::try_from(layer).ok())
.ok_or_else(|| Error::Rejected(code, format!("a {what} layer is not a 32-bit integer")))
}
pub(super) fn position(
row: &Row<'_>,
index: usize,
what: &str,
code: ErrorCode,
) -> Result<i64, Error> {
let value: Option<i64> = row.get(index)?;
value
.filter(|value| (-MAX_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(value))
.ok_or_else(|| {
Error::Rejected(
code,
format!("a {what} position is not an integer within ±{MAX_SAFE_INTEGER}"),
)
})
}
pub(super) fn chunk_coordinate(row: &Row<'_>, index: usize, code: ErrorCode) -> Result<i64, Error> {
let size = i64::from(CHUNK_SIZE);
let range = -(MAX_SAFE_INTEGER / size)..=(MAX_SAFE_INTEGER - (size - 1)) / size;
let value: Option<i64> = row.get(index)?;
value.filter(|value| range.contains(value)).ok_or_else(|| {
Error::Rejected(
code,
format!(
"a chunk coordinate is not an integer from {} to {}",
range.start(),
range.end()
),
)
})
}
pub(super) fn disagrees(what: &str, code: ErrorCode) -> Error {
Error::Rejected(
code,
format!("the document of {what} disagrees with its row"),
)
}
pub(super) fn plane_columns() -> String {
format!(
"{}, {}, {}, {}",
bounded_identifier("id"),
bounded_identifier("kind"),
integer("chunk_size"),
bounded_text("document")
)
}
pub(super) fn plane_row(row: &Row<'_>) -> Result<(String, Document), Error> {
let id = identifier(row, 0, "plane", ErrorCode::Plane)?;
let kind = utf8_column(row, 1, ErrorCode::Plane)?;
let chunk_size: Option<i64> = row.get(2)?;
let stored = stored_document(
utf8_column(row, 3, ErrorCode::Plane)?,
"a plane document",
ErrorCode::Plane,
)?;
if !kind
.as_deref()
.is_some_and(|kind| ["shared", "instanced", "per_player"].contains(&kind))
|| chunk_size != Some(i64::from(CHUNK_SIZE))
{
return Err(Error::Rejected(
ErrorCode::Plane,
format!("plane {id} has an invalid kind or a chunk size other than {CHUNK_SIZE}"),
));
}
if text(&stored, "format") != Some("tilewright.plane/1")
|| text(&stored, "id") != Some(&id)
|| text(&stored, "kind") != kind.as_deref()
|| stored.root().get("chunk_size").and_then(Value::as_i64) != chunk_size
|| stored
.root()
.get("default_zone")
.is_some_and(|zone| zone.as_str().is_none())
{
return Err(disagrees(&format!("plane {id}"), ErrorCode::Plane));
}
Ok((id, stored))
}
fn plane(row: &Row<'_>) -> Result<Plane, Error> {
let (id, stored) = plane_row(row)?;
Ok(Plane {
id,
kind: text(&stored, "kind").unwrap_or_default().to_owned(),
chunk_size: CHUNK_SIZE,
document: stored.source().to_owned(),
})
}
pub(super) fn zone_columns() -> String {
format!(
"{}, {}, parent IS NULL, {}, {}",
bounded_identifier("plane"),
bounded_identifier("id"),
bounded_identifier("parent"),
bounded_text("document")
)
}
pub(super) fn zone_row(row: &Row<'_>) -> Result<(String, String, Document), Error> {
let (plane, id) = (
identifier(row, 0, "zone", ErrorCode::Zone)?,
identifier(row, 1, "zone", ErrorCode::Zone)?,
);
let parent = if row.get::<_, bool>(2)? {
None
} else {
Some(identifier(row, 3, "zone parent", ErrorCode::Zone)?)
};
let stored = stored_document(
utf8_column(row, 4, ErrorCode::Zone)?,
"a zone document",
ErrorCode::Zone,
)?;
if text(&stored, "id") != Some(&id)
|| stored.root().get("parent").map(Value::as_str) != parent.as_deref().map(Some)
{
return Err(disagrees(&format!("zone {plane}/{id}"), ErrorCode::Zone));
}
Ok((plane, id, stored))
}
fn zone(row: &Row<'_>) -> Result<Zone, Error> {
let (plane, id, stored) = zone_row(row)?;
Ok(Zone {
plane,
id,
parent: text(&stored, "parent").map(str::to_owned),
document: stored.source().to_owned(),
})
}
pub(super) fn anchor_columns() -> String {
format!(
"{}, {}, {}, {}, {}",
bounded_identifier("plane"),
bounded_identifier("id"),
integer("layer"),
integer("x"),
integer("y")
)
}
pub(super) fn anchor_row(row: &Row<'_>) -> Result<Anchor, Error> {
let code = ErrorCode::Anchor;
Ok(Anchor {
plane: identifier(row, 0, "anchor", code)?,
id: identifier(row, 1, "anchor", code)?,
layer: layer(row, 2, "anchor", code)?,
x: position(row, 3, "anchor", code)?,
y: position(row, 4, "anchor", code)?,
})
}
pub(super) fn definition_columns() -> String {
format!(
"{}, {}, {}",
bounded_identifier("type"),
bounded_identifier("id"),
bounded_text("document")
)
}
pub(super) fn definition_row(row: &Row<'_>) -> Result<(String, String, Document), Error> {
let (kind, id) = (
identifier(row, 0, "definition", ErrorCode::Definition)?,
identifier(row, 1, "definition", ErrorCode::Definition)?,
);
let stored = stored_document(
utf8_column(row, 2, ErrorCode::Definition)?,
"a definition document",
ErrorCode::Definition,
)?;
if text(&stored, "format") != Some("tilewright.def/1")
|| text(&stored, "type") != Some(&kind)
|| text(&stored, "id") != Some(&id)
{
return Err(disagrees(
&format!("definition {kind}/{id}"),
ErrorCode::Definition,
));
}
Ok((kind, id, stored))
}
fn definition(row: &Row<'_>) -> Result<Definition, Error> {
let (kind, id, stored) = definition_row(row)?;
Ok(Definition {
kind,
id,
document: stored.source().to_owned(),
})
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Page<K> {
pub after: Option<K>,
pub limit: usize,
pub bytes: usize,
}
impl<K> Page<K> {
#[must_use]
pub const fn all() -> Self {
Self {
after: None,
limit: usize::MAX,
bytes: usize::MAX,
}
}
}
struct Listing {
sql: String,
conditions: Vec<String>,
values: Vec<types::Value>,
}
impl Listing {
fn new(columns: &str, table: &str, documents: bool) -> Self {
Self {
sql: format!("SELECT {columns} FROM {table}"),
conditions: Vec::new(),
values: if documents {
vec![types::Value::Integer(maximum_document_bytes())]
} else {
Vec::new()
},
}
}
fn bind(&mut self, value: impl Into<types::Value>) -> String {
self.values.push(value.into());
format!("?{}", self.values.len())
}
fn condition(&mut self, condition: impl FnOnce(&mut Self) -> String) -> &mut Self {
let condition = condition(self);
self.conditions.push(condition);
self
}
fn finish(mut self, order: &str, limit: usize) -> (String, Vec<types::Value>) {
let limit = self.bind(i64::try_from(limit).unwrap_or(-1));
let mut sql = self.sql;
if !self.conditions.is_empty() {
sql.push_str(" WHERE ");
sql.push_str(&self.conditions.join(" AND "));
}
let _ = write!(sql, " {order} LIMIT {limit}");
(sql, self.values)
}
}
impl Pack {
fn row_of<T>(
&self,
sql: &str,
parameters: &[&dyn ToSql],
read: fn(&Row<'_>) -> Result<T, Error>,
) -> Result<Option<T>, Error> {
let mut statement = self.connection.prepare(sql)?;
let mut rows = statement.query(parameters)?;
rows.next()?.map(read).transpose()
}
#[expect(
clippy::too_many_arguments,
reason = "each listing gives its query, page, row reader, key and code"
)]
fn walk<T, K: Ord>(
&self,
(sql, values): (String, Vec<types::Value>),
after: Option<K>,
bytes: usize,
read: impl Fn(&Row<'_>) -> Result<T, Error>,
keyed: impl Fn(&T) -> (K, usize),
code: ErrorCode,
visit: &mut dyn FnMut(T) -> Result<(), Error>,
) -> Result<(), Error> {
let mut statement = self.connection.prepare(&sql)?;
let mut rows = statement.query(rusqlite::params_from_iter(values))?;
let mut last = after;
let mut total: usize = 0;
while let Some(row) = rows.next()? {
let value = read(row)?;
let (next, size) = keyed(&value);
if last.as_ref().is_some_and(|last| *last >= next) {
return Err(Error::Rejected(
code,
"a table's rows are not in the order of its primary key".into(),
));
}
last = Some(next);
visit(value)?;
total = total.saturating_add(size);
if total >= bytes {
break;
}
}
Ok(())
}
pub fn profile(&self) -> Result<String, Error> {
self.profile_layers()?;
self.meta("profile")?
.ok_or_else(|| Error::Rejected(ErrorCode::Meta, "the pack has no profile".into()))
}
pub fn plane(&self, id: &str) -> Result<Option<Plane>, Error> {
self.row_of(
&format!("SELECT {} FROM planes WHERE id = ?2", plane_columns()),
params![maximum_document_bytes(), id],
plane,
)
}
pub fn visit_planes(
&self,
page: Page<&str>,
visit: &mut dyn FnMut(Plane) -> Result<(), Error>,
) -> Result<(), Error> {
self.walk(
planes_listing(page),
page.after.map(str::to_owned),
page.bytes,
plane,
|plane| {
let size = plane.id.len() + plane.kind.len() + plane.document.len();
(plane.id.clone(), size)
},
ErrorCode::Plane,
visit,
)
}
pub fn zone(&self, plane: &str, id: &str) -> Result<Option<Zone>, Error> {
self.row_of(
&format!(
"SELECT {} FROM zones WHERE plane = ?2 AND id = ?3",
zone_columns()
),
params![maximum_document_bytes(), plane, id],
zone,
)
}
pub fn visit_zones(
&self,
plane: &str,
page: Page<&str>,
visit: &mut dyn FnMut(Zone) -> Result<(), Error>,
) -> Result<(), Error> {
self.walk(
in_plane(&zone_columns(), "zones", true, plane, page),
page.after.map(str::to_owned),
page.bytes,
zone,
|zone| {
let parent = zone.parent.as_ref().map_or(0, String::len);
let size = zone.plane.len() + zone.id.len() + parent + zone.document.len();
(zone.id.clone(), size)
},
ErrorCode::Zone,
visit,
)
}
pub fn anchor(&self, plane: &str, id: &str) -> Result<Option<Anchor>, Error> {
self.row_of(
&format!(
"SELECT {} FROM anchors WHERE plane = ?1 AND id = ?2",
anchor_columns()
),
params![plane, id],
anchor_row,
)
}
pub fn visit_anchors(
&self,
plane: &str,
page: Page<&str>,
visit: &mut dyn FnMut(Anchor) -> Result<(), Error>,
) -> Result<(), Error> {
self.walk(
in_plane(&anchor_columns(), "anchors", false, plane, page),
page.after.map(str::to_owned),
page.bytes,
anchor_row,
|anchor| (anchor.id.clone(), anchor.plane.len() + anchor.id.len()),
ErrorCode::Anchor,
visit,
)
}
pub fn visit_stairwells(
&self,
plane: &str,
page: Page<&str>,
visit: &mut dyn FnMut(Stairwell) -> Result<(), Error>,
) -> Result<(), Error> {
self.visit_stairwells_within(plane, page, u64::MAX, visit)
}
pub fn visit_stairwells_within(
&self,
plane: &str,
page: Page<&str>,
maximum_steps: u64,
visit: &mut dyn FnMut(Stairwell) -> Result<(), Error>,
) -> Result<(), Error> {
let columns = format!("{}, {}", bounded_identifier("id"), bounded_text("document"));
self.walk(
in_plane(&columns, "stairwells", true, plane, page),
page.after.map(str::to_owned),
page.bytes,
|row| stairwell_row_within(row, maximum_steps),
|stairwell| {
let steps: usize = stairwell.steps.iter().map(|step| step.proto.len()).sum();
let custom = stairwell.custom.as_ref().map_or(0, String::len);
let size = stairwell.id.len() + steps + custom + stairwell.canonical.len();
(stairwell.id.clone(), size)
},
ErrorCode::Stairwell,
visit,
)
}
pub fn definition(&self, kind: &str, id: &str) -> Result<Option<Definition>, Error> {
self.row_of(
&format!(
"SELECT {} FROM definitions WHERE type = ?2 AND id = ?3",
definition_columns()
),
params![maximum_document_bytes(), kind, id],
definition,
)
}
pub fn visit_definitions(
&self,
kind: Option<&str>,
page: Page<(&str, &str)>,
visit: &mut dyn FnMut(Definition) -> Result<(), Error>,
) -> Result<(), Error> {
let Some(query) = definitions_listing(kind, page) else {
return Ok(());
};
self.walk(
query,
page.after
.map(|(kind, id)| (kind.to_owned(), id.to_owned())),
page.bytes,
definition,
|definition| {
let size = definition.kind.len() + definition.id.len() + definition.document.len();
((definition.kind.clone(), definition.id.clone()), size)
},
ErrorCode::Definition,
visit,
)
}
pub fn visit_layers(
&self,
plane: &str,
page: Page<i32>,
visit: &mut dyn FnMut(i32) -> Result<(), Error>,
) -> Result<(), Error> {
self.walk(
layers_listing(plane, page),
page.after,
page.bytes,
|row| layer(row, 0, "chunk", ErrorCode::Chunk),
|layer| (*layer, 0),
ErrorCode::Chunk,
visit,
)
}
pub fn visit_chunk_keys(
&self,
plane: &str,
layer: i32,
page: Page<(i64, i64)>,
visit: &mut dyn FnMut(i64, i64) -> Result<(), Error>,
) -> Result<(), Error> {
self.walk(
chunk_keys_listing(plane, layer, page),
page.after,
page.bytes,
|row| {
Ok((
chunk_coordinate(row, 0, ErrorCode::Chunk)?,
chunk_coordinate(row, 1, ErrorCode::Chunk)?,
))
},
|key| (*key, 0),
ErrorCode::Chunk,
&mut |(cx, cy)| visit(cx, cy),
)
}
}
fn planes_listing(page: Page<&str>) -> (String, Vec<types::Value>) {
let mut listing = Listing::new(&plane_columns(), "planes", true);
if let Some(after) = page.after {
listing.condition(|listing| format!("id > {}", listing.bind(after.to_owned())));
}
listing.finish("ORDER BY id", page.limit)
}
fn in_plane(
columns: &str,
table: &str,
documents: bool,
plane: &str,
page: Page<&str>,
) -> (String, Vec<types::Value>) {
let mut listing = Listing::new(columns, table, documents);
listing.condition(|listing| format!("plane = {}", listing.bind(plane.to_owned())));
if let Some(after) = page.after {
listing.condition(|listing| format!("id > {}", listing.bind(after.to_owned())));
}
listing.finish("ORDER BY id", page.limit)
}
fn definitions_listing(
kind: Option<&str>,
page: Page<(&str, &str)>,
) -> Option<(String, Vec<types::Value>)> {
let mut listing = Listing::new(&definition_columns(), "definitions", true);
match (kind, page.after) {
(Some(kind), after) => {
listing.condition(|listing| format!("type = {}", listing.bind(kind.to_owned())));
if let Some((after_kind, after_id)) = after {
match after_kind.cmp(kind) {
std::cmp::Ordering::Less => {}
std::cmp::Ordering::Equal => {
listing.condition(|listing| {
format!("id > {}", listing.bind(after_id.to_owned()))
});
}
std::cmp::Ordering::Greater => return None,
}
}
}
(None, Some((after_kind, after_id))) => {
listing.condition(|listing| {
format!(
"(type, id) > ({}, {})",
listing.bind(after_kind.to_owned()),
listing.bind(after_id.to_owned())
)
});
}
(None, None) => {}
}
Some(listing.finish("ORDER BY type, id", page.limit))
}
fn layers_listing(plane: &str, page: Page<i32>) -> (String, Vec<types::Value>) {
let mut listing = Listing::new(&integer("layer"), "chunks", false);
listing.condition(|listing| format!("plane = {}", listing.bind(plane.to_owned())));
if let Some(after) = page.after {
listing.condition(|listing| format!("layer > {}", listing.bind(after)));
}
listing.finish("GROUP BY layer ORDER BY layer", page.limit)
}
fn chunk_keys_listing(
plane: &str,
layer: i32,
page: Page<(i64, i64)>,
) -> (String, Vec<types::Value>) {
let mut listing = Listing::new(
&format!("{}, {}", integer("cx"), integer("cy")),
"chunks",
false,
);
listing.condition(|listing| format!("plane = {}", listing.bind(plane.to_owned())));
listing.condition(|listing| format!("layer = {}", listing.bind(layer)));
if let Some((cx, cy)) = page.after {
listing.condition(|listing| {
format!("(cx, cy) > ({}, {})", listing.bind(cx), listing.bind(cy))
});
}
listing.finish("ORDER BY cx, cy", page.limit)
}
#[cfg(test)]
mod tests {
use super::{
Listing, Page, chunk_keys_listing, definitions_listing, in_plane, layers_listing, plane,
plane_columns, planes_listing, types,
};
use crate::{Error, ErrorCode, Pack, VerifyLimits};
fn pack() -> Pack {
Pack::open_with(&crate::tests::valid_pack(), &VerifyLimits::default()).unwrap()
}
fn paged<T: PartialEq + std::fmt::Debug, K>(
list: impl Fn(Option<&K>, usize, &mut dyn FnMut(T) -> Result<(), Error>) -> Result<(), Error>,
key: impl Fn(&T) -> K,
) -> Vec<T> {
let read = |after: Option<&K>, limit| {
let mut rows = Vec::new();
list(after, limit, &mut |row| {
rows.push(row);
Ok(())
})
.unwrap();
rows
};
let all = read(None, usize::MAX);
let mut one_by_one: Vec<T> = Vec::new();
loop {
let after = one_by_one.last().map(&key);
let mut page = read(after.as_ref(), 1);
assert!(page.len() <= 1);
let Some(row) = page.pop() else { break };
one_by_one.push(row);
}
assert_eq!(one_by_one, all);
all
}
fn text(after: Option<&String>) -> Option<&str> {
after.map(String::as_str)
}
fn pair(after: Option<&(String, String)>) -> Option<(&str, &str)> {
after.map(|(kind, id)| (kind.as_str(), id.as_str()))
}
#[test]
fn plane_listings_page_by_their_keys() {
let pack = pack();
let planes = paged(
|after, limit, visit| {
pack.visit_planes(
Page {
after: text(after),
limit,
bytes: usize::MAX,
},
visit,
)
},
|plane| plane.id.clone(),
);
assert_eq!(planes.len(), 2);
let zones = paged(
|after, limit, visit| {
pack.visit_zones(
"overworld",
Page {
after: text(after),
limit,
bytes: usize::MAX,
},
visit,
)
},
|zone| zone.id.clone(),
);
assert_eq!(zones.len(), 3);
let anchors = paged(
|after, limit, visit| {
pack.visit_anchors(
"overworld",
Page {
after: text(after),
limit,
bytes: usize::MAX,
},
visit,
)
},
|anchor| anchor.id.clone(),
);
assert_eq!(anchors.len(), 2);
let stairwells = paged(
|after, limit, visit| {
pack.visit_stairwells(
"overworld",
Page {
after: text(after),
limit,
bytes: usize::MAX,
},
visit,
)
},
|stairwell| stairwell.id.clone(),
);
assert_eq!(stairwells.len(), 3);
}
#[test]
fn definitions_layers_and_chunk_keys_page_by_their_keys() {
let pack = pack();
let definitions = paged(
|after, limit, visit| {
pack.visit_definitions(
None,
Page {
after: pair(after),
limit,
bytes: usize::MAX,
},
visit,
)
},
|definition| (definition.kind.clone(), definition.id.clone()),
);
assert_eq!(definitions.len(), 12);
let protos = paged(
|after, limit, visit| {
pack.visit_definitions(
Some("proto"),
Page {
after: pair(after),
limit,
bytes: usize::MAX,
},
visit,
)
},
|definition| (definition.kind.clone(), definition.id.clone()),
);
assert!(!protos.is_empty() && protos.iter().all(|found| found.kind == "proto"));
let layers = paged(
|after, limit, visit| {
pack.visit_layers(
"overworld",
Page {
after: after.copied(),
limit,
bytes: usize::MAX,
},
visit,
)
},
|layer| *layer,
);
assert_eq!(layers, [-1, 0, 1]);
let keys = paged(
|after, limit, visit| {
pack.visit_chunk_keys(
"overworld",
0,
Page {
after: after.copied(),
limit,
bytes: usize::MAX,
},
&mut |cx, cy| visit((cx, cy)),
)
},
|key| *key,
);
assert_eq!(keys, [(-1, 0), (0, 0)]);
for page in [
Page {
after: Some("~"),
limit: 5,
bytes: usize::MAX,
},
Page {
after: None,
limit: 0,
bytes: usize::MAX,
},
] {
pack.visit_planes(page, &mut |_| panic!("{page:?} is empty"))
.unwrap();
}
}
fn after<K>(key: K) -> Page<K> {
Page {
after: Some(key),
limit: 64,
bytes: usize::MAX,
}
}
fn plan(pack: &Pack, (sql, values): (String, Vec<types::Value>)) -> Vec<String> {
let mut statement = pack
.connection
.prepare(&format!("EXPLAIN QUERY PLAN {sql}"))
.unwrap();
statement
.query_map(rusqlite::params_from_iter(values), |row| row.get(3))
.unwrap()
.collect::<Result<_, _>>()
.unwrap()
}
#[test]
fn every_listing_seeks_past_its_page_key() {
let pack = pack();
let stairwell_columns = format!(
"{}, {}",
super::bounded_identifier("id"),
super::bounded_text("document")
);
for (query, seek) in [
(
planes_listing(after("a")),
"SEARCH planes USING PRIMARY KEY (id>?)",
),
(
in_plane(
&super::zone_columns(),
"zones",
true,
"overworld",
after("a"),
),
"SEARCH zones USING PRIMARY KEY (plane=? AND id>?)",
),
(
in_plane(
&super::anchor_columns(),
"anchors",
false,
"overworld",
after("a"),
),
"SEARCH anchors USING PRIMARY KEY (plane=? AND id>?)",
),
(
in_plane(
&stairwell_columns,
"stairwells",
true,
"overworld",
after("a"),
),
"SEARCH stairwells USING PRIMARY KEY (plane=? AND id>?)",
),
(
definitions_listing(None, after(("proto", "a"))).unwrap(),
"SEARCH definitions USING PRIMARY KEY ((type,id)>(?,?))",
),
(
definitions_listing(Some("proto"), after(("proto", "a"))).unwrap(),
"SEARCH definitions USING PRIMARY KEY (type=? AND id>?)",
),
(
layers_listing("overworld", after(0)),
"SEARCH chunks USING PRIMARY KEY (plane=? AND layer>?)",
),
(
chunk_keys_listing("overworld", 0, after((0, 0))),
"SEARCH chunks USING PRIMARY KEY (plane=? AND layer=? AND (cx,cy)>(?,?))",
),
] {
assert_eq!(plan(&pack, query), [seek]);
}
}
#[test]
fn typed_definition_pages_follow_a_key_of_any_type() {
let pack = pack();
let read = |after: Option<(&str, &str)>| {
let mut ids = Vec::new();
let page = Page {
after,
limit: usize::MAX,
bytes: usize::MAX,
};
pack.visit_definitions(Some("proto"), page, &mut |definition| {
ids.push(definition.id);
Ok(())
})
.unwrap();
ids
};
let all = read(None);
assert!(all.len() > 1);
assert_eq!(read(Some(("a", "zzz"))), all);
assert!(read(Some(("zzz", "a"))).is_empty());
assert_eq!(read(Some(("proto", all[0].as_str()))), all[1..]);
}
#[test]
fn pages_stop_at_their_byte_budget() {
let pack = pack();
let read = |after: Option<&(String, String)>, bytes| {
let mut rows = Vec::new();
let page = Page {
after: pair(after),
limit: usize::MAX,
bytes,
};
pack.visit_definitions(None, page, &mut |row| {
rows.push(row);
Ok(())
})
.unwrap();
rows
};
let all = read(None, usize::MAX);
let mut walked: Vec<super::Definition> = Vec::new();
loop {
let after = walked
.last()
.map(|last| (last.kind.clone(), last.id.clone()));
let page = read(after.as_ref(), 1);
assert!(page.len() <= 1);
if page.is_empty() {
break;
}
walked.extend(page);
}
assert_eq!(walked, all);
let size = |row: &super::Definition| row.kind.len() + row.id.len() + row.document.len();
let two = size(&all[0]) + size(&all[1]);
assert_eq!(read(None, two).len(), 2);
assert_eq!(read(None, two + 1).len(), 3);
}
#[test]
fn listings_that_do_not_move_forward_fail() {
let pack = pack();
let mut listing = Listing::new(&plane_columns(), "planes", true);
listing.conditions.clear();
let descending = listing.finish("ORDER BY id DESC", usize::MAX);
let error = pack
.walk(
descending,
None,
usize::MAX,
plane,
|plane| (plane.id.clone(), 0),
ErrorCode::Plane,
&mut |_| Ok(()),
)
.unwrap_err();
assert_eq!(error.code(), ErrorCode::Plane);
let listing = Listing::new(&plane_columns(), "planes", true);
let error = pack
.walk(
listing.finish("ORDER BY id", usize::MAX),
Some("vault".to_owned()),
usize::MAX,
plane,
|plane| (plane.id.clone(), 0),
ErrorCode::Plane,
&mut |_| Ok(()),
)
.unwrap_err();
assert_eq!(error.code(), ErrorCode::Plane);
}
}