1use super::{
11 bounded_identifier, bounded_text, maximum_document_bytes, stairwell_row_within,
12 stored_document, text, utf8_column,
13};
14use crate::{Error, ErrorCode, Pack, is_identifier};
15use rusqlite::{Row, ToSql, params, types};
16use std::fmt::Write as _;
17use tilewright_jsonc::{Document, MAX_SAFE_INTEGER, Value};
18use tilewright_source::{CHUNK_SIZE, Stairwell};
19
20#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct Plane {
23 pub id: String,
24 pub kind: String,
26 pub chunk_size: u16,
27 pub document: String,
29}
30
31#[derive(Clone, Debug, Eq, PartialEq)]
33pub struct Zone {
34 pub plane: String,
35 pub id: String,
36 pub parent: Option<String>,
38 pub document: String,
40}
41
42#[derive(Clone, Debug, Eq, PartialEq)]
44pub struct Anchor {
45 pub plane: String,
46 pub id: String,
47 pub layer: i32,
48 pub x: i64,
49 pub y: i64,
50}
51
52#[derive(Clone, Debug, Eq, PartialEq)]
54pub struct Definition {
55 pub kind: String,
57 pub id: String,
58 pub document: String,
60}
61
62pub(super) fn integer(column: &str) -> String {
64 format!("CASE WHEN typeof({column}) = 'integer' THEN {column} END")
65}
66
67pub(super) fn identifier(
70 row: &Row<'_>,
71 index: usize,
72 what: &str,
73 code: ErrorCode,
74) -> Result<String, Error> {
75 utf8_column(row, index, code)?
76 .filter(|value| is_identifier(value))
77 .ok_or_else(|| Error::Rejected(code, format!("a {what} key is not an identifier")))
78}
79
80pub(super) fn layer(
82 row: &Row<'_>,
83 index: usize,
84 what: &str,
85 code: ErrorCode,
86) -> Result<i32, Error> {
87 let value: Option<i64> = row.get(index)?;
88 value
89 .and_then(|layer| i32::try_from(layer).ok())
90 .ok_or_else(|| Error::Rejected(code, format!("a {what} layer is not a 32-bit integer")))
91}
92
93pub(super) fn position(
96 row: &Row<'_>,
97 index: usize,
98 what: &str,
99 code: ErrorCode,
100) -> Result<i64, Error> {
101 let value: Option<i64> = row.get(index)?;
102 value
103 .filter(|value| (-MAX_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(value))
104 .ok_or_else(|| {
105 Error::Rejected(
106 code,
107 format!("a {what} position is not an integer within ±{MAX_SAFE_INTEGER}"),
108 )
109 })
110}
111
112pub(super) fn chunk_coordinate(row: &Row<'_>, index: usize, code: ErrorCode) -> Result<i64, Error> {
116 let size = i64::from(CHUNK_SIZE);
117 let range = -(MAX_SAFE_INTEGER / size)..=(MAX_SAFE_INTEGER - (size - 1)) / size;
118 let value: Option<i64> = row.get(index)?;
119 value.filter(|value| range.contains(value)).ok_or_else(|| {
120 Error::Rejected(
121 code,
122 format!(
123 "a chunk coordinate is not an integer from {} to {}",
124 range.start(),
125 range.end()
126 ),
127 )
128 })
129}
130
131pub(super) fn disagrees(what: &str, code: ErrorCode) -> Error {
133 Error::Rejected(
134 code,
135 format!("the document of {what} disagrees with its row"),
136 )
137}
138
139pub(super) fn plane_columns() -> String {
141 format!(
142 "{}, {}, {}, {}",
143 bounded_identifier("id"),
144 bounded_identifier("kind"),
145 integer("chunk_size"),
146 bounded_text("document")
147 )
148}
149
150pub(super) fn plane_row(row: &Row<'_>) -> Result<(String, Document), Error> {
156 let id = identifier(row, 0, "plane", ErrorCode::Plane)?;
157 let kind = utf8_column(row, 1, ErrorCode::Plane)?;
158 let chunk_size: Option<i64> = row.get(2)?;
159 let stored = stored_document(
160 utf8_column(row, 3, ErrorCode::Plane)?,
161 "a plane document",
162 ErrorCode::Plane,
163 )?;
164 if !kind
165 .as_deref()
166 .is_some_and(|kind| ["shared", "instanced", "per_player"].contains(&kind))
167 || chunk_size != Some(i64::from(CHUNK_SIZE))
168 {
169 return Err(Error::Rejected(
170 ErrorCode::Plane,
171 format!("plane {id} has an invalid kind or a chunk size other than {CHUNK_SIZE}"),
172 ));
173 }
174 if text(&stored, "format") != Some("tilewright.plane/1")
175 || text(&stored, "id") != Some(&id)
176 || text(&stored, "kind") != kind.as_deref()
177 || stored.root().get("chunk_size").and_then(Value::as_i64) != chunk_size
178 || stored
179 .root()
180 .get("default_zone")
181 .is_some_and(|zone| zone.as_str().is_none())
182 {
183 return Err(disagrees(&format!("plane {id}"), ErrorCode::Plane));
184 }
185 Ok((id, stored))
186}
187
188fn plane(row: &Row<'_>) -> Result<Plane, Error> {
190 let (id, stored) = plane_row(row)?;
191 Ok(Plane {
192 id,
193 kind: text(&stored, "kind").unwrap_or_default().to_owned(),
194 chunk_size: CHUNK_SIZE,
195 document: stored.source().to_owned(),
196 })
197}
198
199pub(super) fn zone_columns() -> String {
201 format!(
202 "{}, {}, parent IS NULL, {}, {}",
203 bounded_identifier("plane"),
204 bounded_identifier("id"),
205 bounded_identifier("parent"),
206 bounded_text("document")
207 )
208}
209
210pub(super) fn zone_row(row: &Row<'_>) -> Result<(String, String, Document), Error> {
213 let (plane, id) = (
214 identifier(row, 0, "zone", ErrorCode::Zone)?,
215 identifier(row, 1, "zone", ErrorCode::Zone)?,
216 );
217 let parent = if row.get::<_, bool>(2)? {
218 None
219 } else {
220 Some(identifier(row, 3, "zone parent", ErrorCode::Zone)?)
221 };
222 let stored = stored_document(
223 utf8_column(row, 4, ErrorCode::Zone)?,
224 "a zone document",
225 ErrorCode::Zone,
226 )?;
227 if text(&stored, "id") != Some(&id)
228 || stored.root().get("parent").map(Value::as_str) != parent.as_deref().map(Some)
229 {
230 return Err(disagrees(&format!("zone {plane}/{id}"), ErrorCode::Zone));
231 }
232 Ok((plane, id, stored))
233}
234
235fn zone(row: &Row<'_>) -> Result<Zone, Error> {
237 let (plane, id, stored) = zone_row(row)?;
238 Ok(Zone {
239 plane,
240 id,
241 parent: text(&stored, "parent").map(str::to_owned),
242 document: stored.source().to_owned(),
243 })
244}
245
246pub(super) fn anchor_columns() -> String {
248 format!(
249 "{}, {}, {}, {}, {}",
250 bounded_identifier("plane"),
251 bounded_identifier("id"),
252 integer("layer"),
253 integer("x"),
254 integer("y")
255 )
256}
257
258pub(super) fn anchor_row(row: &Row<'_>) -> Result<Anchor, Error> {
261 let code = ErrorCode::Anchor;
262 Ok(Anchor {
263 plane: identifier(row, 0, "anchor", code)?,
264 id: identifier(row, 1, "anchor", code)?,
265 layer: layer(row, 2, "anchor", code)?,
266 x: position(row, 3, "anchor", code)?,
267 y: position(row, 4, "anchor", code)?,
268 })
269}
270
271pub(super) fn definition_columns() -> String {
273 format!(
274 "{}, {}, {}",
275 bounded_identifier("type"),
276 bounded_identifier("id"),
277 bounded_text("document")
278 )
279}
280
281pub(super) fn definition_row(row: &Row<'_>) -> Result<(String, String, Document), Error> {
284 let (kind, id) = (
285 identifier(row, 0, "definition", ErrorCode::Definition)?,
286 identifier(row, 1, "definition", ErrorCode::Definition)?,
287 );
288 let stored = stored_document(
289 utf8_column(row, 2, ErrorCode::Definition)?,
290 "a definition document",
291 ErrorCode::Definition,
292 )?;
293 if text(&stored, "format") != Some("tilewright.def/1")
294 || text(&stored, "type") != Some(&kind)
295 || text(&stored, "id") != Some(&id)
296 {
297 return Err(disagrees(
298 &format!("definition {kind}/{id}"),
299 ErrorCode::Definition,
300 ));
301 }
302 Ok((kind, id, stored))
303}
304
305fn definition(row: &Row<'_>) -> Result<Definition, Error> {
307 let (kind, id, stored) = definition_row(row)?;
308 Ok(Definition {
309 kind,
310 id,
311 document: stored.source().to_owned(),
312 })
313}
314
315#[derive(Clone, Copy, Debug, Eq, PartialEq)]
322pub struct Page<K> {
323 pub after: Option<K>,
326 pub limit: usize,
328 pub bytes: usize,
332}
333
334impl<K> Page<K> {
335 #[must_use]
337 pub const fn all() -> Self {
338 Self {
339 after: None,
340 limit: usize::MAX,
341 bytes: usize::MAX,
342 }
343 }
344}
345
346struct Listing {
349 sql: String,
350 conditions: Vec<String>,
351 values: Vec<types::Value>,
352}
353
354impl Listing {
355 fn new(columns: &str, table: &str, documents: bool) -> Self {
358 Self {
359 sql: format!("SELECT {columns} FROM {table}"),
360 conditions: Vec::new(),
361 values: if documents {
362 vec![types::Value::Integer(maximum_document_bytes())]
363 } else {
364 Vec::new()
365 },
366 }
367 }
368
369 fn bind(&mut self, value: impl Into<types::Value>) -> String {
371 self.values.push(value.into());
372 format!("?{}", self.values.len())
373 }
374
375 fn condition(&mut self, condition: impl FnOnce(&mut Self) -> String) -> &mut Self {
377 let condition = condition(self);
378 self.conditions.push(condition);
379 self
380 }
381
382 fn finish(mut self, order: &str, limit: usize) -> (String, Vec<types::Value>) {
386 let limit = self.bind(i64::try_from(limit).unwrap_or(-1));
387 let mut sql = self.sql;
388 if !self.conditions.is_empty() {
389 sql.push_str(" WHERE ");
390 sql.push_str(&self.conditions.join(" AND "));
391 }
392 let _ = write!(sql, " {order} LIMIT {limit}");
393 (sql, self.values)
394 }
395}
396
397impl Pack {
398 fn row_of<T>(
400 &self,
401 sql: &str,
402 parameters: &[&dyn ToSql],
403 read: fn(&Row<'_>) -> Result<T, Error>,
404 ) -> Result<Option<T>, Error> {
405 let mut statement = self.connection.prepare(sql)?;
406 let mut rows = statement.query(parameters)?;
407 rows.next()?.map(read).transpose()
408 }
409
410 #[expect(
416 clippy::too_many_arguments,
417 reason = "each listing gives its query, page, row reader, key and code"
418 )]
419 fn walk<T, K: Ord>(
420 &self,
421 (sql, values): (String, Vec<types::Value>),
422 after: Option<K>,
423 bytes: usize,
424 read: impl Fn(&Row<'_>) -> Result<T, Error>,
425 keyed: impl Fn(&T) -> (K, usize),
426 code: ErrorCode,
427 visit: &mut dyn FnMut(T) -> Result<(), Error>,
428 ) -> Result<(), Error> {
429 let mut statement = self.connection.prepare(&sql)?;
430 let mut rows = statement.query(rusqlite::params_from_iter(values))?;
431 let mut last = after;
432 let mut total: usize = 0;
433 while let Some(row) = rows.next()? {
434 let value = read(row)?;
435 let (next, size) = keyed(&value);
436 if last.as_ref().is_some_and(|last| *last >= next) {
437 return Err(Error::Rejected(
438 code,
439 "a table's rows are not in the order of its primary key".into(),
440 ));
441 }
442 last = Some(next);
443 visit(value)?;
444 total = total.saturating_add(size);
445 if total >= bytes {
446 break;
447 }
448 }
449 Ok(())
450 }
451
452 pub fn profile(&self) -> Result<String, Error> {
458 self.profile_layers()?;
459 self.meta("profile")?
460 .ok_or_else(|| Error::Rejected(ErrorCode::Meta, "the pack has no profile".into()))
461 }
462
463 pub fn plane(&self, id: &str) -> Result<Option<Plane>, Error> {
468 self.row_of(
469 &format!("SELECT {} FROM planes WHERE id = ?2", plane_columns()),
470 params![maximum_document_bytes(), id],
471 plane,
472 )
473 }
474
475 pub fn visit_planes(
482 &self,
483 page: Page<&str>,
484 visit: &mut dyn FnMut(Plane) -> Result<(), Error>,
485 ) -> Result<(), Error> {
486 self.walk(
487 planes_listing(page),
488 page.after.map(str::to_owned),
489 page.bytes,
490 plane,
491 |plane| {
492 let size = plane.id.len() + plane.kind.len() + plane.document.len();
493 (plane.id.clone(), size)
494 },
495 ErrorCode::Plane,
496 visit,
497 )
498 }
499
500 pub fn zone(&self, plane: &str, id: &str) -> Result<Option<Zone>, Error> {
506 self.row_of(
507 &format!(
508 "SELECT {} FROM zones WHERE plane = ?2 AND id = ?3",
509 zone_columns()
510 ),
511 params![maximum_document_bytes(), plane, id],
512 zone,
513 )
514 }
515
516 pub fn visit_zones(
522 &self,
523 plane: &str,
524 page: Page<&str>,
525 visit: &mut dyn FnMut(Zone) -> Result<(), Error>,
526 ) -> Result<(), Error> {
527 self.walk(
528 in_plane(&zone_columns(), "zones", true, plane, page),
529 page.after.map(str::to_owned),
530 page.bytes,
531 zone,
532 |zone| {
533 let parent = zone.parent.as_ref().map_or(0, String::len);
534 let size = zone.plane.len() + zone.id.len() + parent + zone.document.len();
535 (zone.id.clone(), size)
536 },
537 ErrorCode::Zone,
538 visit,
539 )
540 }
541
542 pub fn anchor(&self, plane: &str, id: &str) -> Result<Option<Anchor>, Error> {
548 self.row_of(
549 &format!(
550 "SELECT {} FROM anchors WHERE plane = ?1 AND id = ?2",
551 anchor_columns()
552 ),
553 params![plane, id],
554 anchor_row,
555 )
556 }
557
558 pub fn visit_anchors(
564 &self,
565 plane: &str,
566 page: Page<&str>,
567 visit: &mut dyn FnMut(Anchor) -> Result<(), Error>,
568 ) -> Result<(), Error> {
569 self.walk(
570 in_plane(&anchor_columns(), "anchors", false, plane, page),
571 page.after.map(str::to_owned),
572 page.bytes,
573 anchor_row,
574 |anchor| (anchor.id.clone(), anchor.plane.len() + anchor.id.len()),
575 ErrorCode::Anchor,
576 visit,
577 )
578 }
579
580 pub fn visit_stairwells(
588 &self,
589 plane: &str,
590 page: Page<&str>,
591 visit: &mut dyn FnMut(Stairwell) -> Result<(), Error>,
592 ) -> Result<(), Error> {
593 self.visit_stairwells_within(plane, page, u64::MAX, visit)
594 }
595
596 pub fn visit_stairwells_within(
603 &self,
604 plane: &str,
605 page: Page<&str>,
606 maximum_steps: u64,
607 visit: &mut dyn FnMut(Stairwell) -> Result<(), Error>,
608 ) -> Result<(), Error> {
609 let columns = format!("{}, {}", bounded_identifier("id"), bounded_text("document"));
610 self.walk(
611 in_plane(&columns, "stairwells", true, plane, page),
612 page.after.map(str::to_owned),
613 page.bytes,
614 |row| stairwell_row_within(row, maximum_steps),
615 |stairwell| {
616 let steps: usize = stairwell.steps.iter().map(|step| step.proto.len()).sum();
617 let custom = stairwell.custom.as_ref().map_or(0, String::len);
618 let size = stairwell.id.len() + steps + custom + stairwell.canonical.len();
619 (stairwell.id.clone(), size)
620 },
621 ErrorCode::Stairwell,
622 visit,
623 )
624 }
625
626 pub fn definition(&self, kind: &str, id: &str) -> Result<Option<Definition>, Error> {
631 self.row_of(
632 &format!(
633 "SELECT {} FROM definitions WHERE type = ?2 AND id = ?3",
634 definition_columns()
635 ),
636 params![maximum_document_bytes(), kind, id],
637 definition,
638 )
639 }
640
641 pub fn visit_definitions(
650 &self,
651 kind: Option<&str>,
652 page: Page<(&str, &str)>,
653 visit: &mut dyn FnMut(Definition) -> Result<(), Error>,
654 ) -> Result<(), Error> {
655 let Some(query) = definitions_listing(kind, page) else {
656 return Ok(());
657 };
658 self.walk(
659 query,
660 page.after
661 .map(|(kind, id)| (kind.to_owned(), id.to_owned())),
662 page.bytes,
663 definition,
664 |definition| {
665 let size = definition.kind.len() + definition.id.len() + definition.document.len();
666 ((definition.kind.clone(), definition.id.clone()), size)
667 },
668 ErrorCode::Definition,
669 visit,
670 )
671 }
672
673 pub fn visit_layers(
680 &self,
681 plane: &str,
682 page: Page<i32>,
683 visit: &mut dyn FnMut(i32) -> Result<(), Error>,
684 ) -> Result<(), Error> {
685 self.walk(
686 layers_listing(plane, page),
687 page.after,
688 page.bytes,
689 |row| layer(row, 0, "chunk", ErrorCode::Chunk),
690 |layer| (*layer, 0),
691 ErrorCode::Chunk,
692 visit,
693 )
694 }
695
696 pub fn visit_chunk_keys(
704 &self,
705 plane: &str,
706 layer: i32,
707 page: Page<(i64, i64)>,
708 visit: &mut dyn FnMut(i64, i64) -> Result<(), Error>,
709 ) -> Result<(), Error> {
710 self.walk(
711 chunk_keys_listing(plane, layer, page),
712 page.after,
713 page.bytes,
714 |row| {
715 Ok((
716 chunk_coordinate(row, 0, ErrorCode::Chunk)?,
717 chunk_coordinate(row, 1, ErrorCode::Chunk)?,
718 ))
719 },
720 |key| (*key, 0),
721 ErrorCode::Chunk,
722 &mut |(cx, cy)| visit(cx, cy),
723 )
724 }
725}
726
727fn planes_listing(page: Page<&str>) -> (String, Vec<types::Value>) {
732 let mut listing = Listing::new(&plane_columns(), "planes", true);
733 if let Some(after) = page.after {
734 listing.condition(|listing| format!("id > {}", listing.bind(after.to_owned())));
735 }
736 listing.finish("ORDER BY id", page.limit)
737}
738
739fn in_plane(
741 columns: &str,
742 table: &str,
743 documents: bool,
744 plane: &str,
745 page: Page<&str>,
746) -> (String, Vec<types::Value>) {
747 let mut listing = Listing::new(columns, table, documents);
748 listing.condition(|listing| format!("plane = {}", listing.bind(plane.to_owned())));
749 if let Some(after) = page.after {
750 listing.condition(|listing| format!("id > {}", listing.bind(after.to_owned())));
751 }
752 listing.finish("ORDER BY id", page.limit)
753}
754
755fn definitions_listing(
761 kind: Option<&str>,
762 page: Page<(&str, &str)>,
763) -> Option<(String, Vec<types::Value>)> {
764 let mut listing = Listing::new(&definition_columns(), "definitions", true);
765 match (kind, page.after) {
766 (Some(kind), after) => {
767 listing.condition(|listing| format!("type = {}", listing.bind(kind.to_owned())));
768 if let Some((after_kind, after_id)) = after {
769 match after_kind.cmp(kind) {
770 std::cmp::Ordering::Less => {}
772 std::cmp::Ordering::Equal => {
773 listing.condition(|listing| {
774 format!("id > {}", listing.bind(after_id.to_owned()))
775 });
776 }
777 std::cmp::Ordering::Greater => return None,
778 }
779 }
780 }
781 (None, Some((after_kind, after_id))) => {
782 listing.condition(|listing| {
783 format!(
784 "(type, id) > ({}, {})",
785 listing.bind(after_kind.to_owned()),
786 listing.bind(after_id.to_owned())
787 )
788 });
789 }
790 (None, None) => {}
791 }
792 Some(listing.finish("ORDER BY type, id", page.limit))
793}
794
795fn layers_listing(plane: &str, page: Page<i32>) -> (String, Vec<types::Value>) {
797 let mut listing = Listing::new(&integer("layer"), "chunks", false);
798 listing.condition(|listing| format!("plane = {}", listing.bind(plane.to_owned())));
799 if let Some(after) = page.after {
800 listing.condition(|listing| format!("layer > {}", listing.bind(after)));
801 }
802 listing.finish("GROUP BY layer ORDER BY layer", page.limit)
805}
806
807fn chunk_keys_listing(
809 plane: &str,
810 layer: i32,
811 page: Page<(i64, i64)>,
812) -> (String, Vec<types::Value>) {
813 let mut listing = Listing::new(
814 &format!("{}, {}", integer("cx"), integer("cy")),
815 "chunks",
816 false,
817 );
818 listing.condition(|listing| format!("plane = {}", listing.bind(plane.to_owned())));
819 listing.condition(|listing| format!("layer = {}", listing.bind(layer)));
820 if let Some((cx, cy)) = page.after {
821 listing.condition(|listing| {
822 format!("(cx, cy) > ({}, {})", listing.bind(cx), listing.bind(cy))
823 });
824 }
825 listing.finish("ORDER BY cx, cy", page.limit)
826}
827
828#[cfg(test)]
829mod tests {
830 use super::{
831 Listing, Page, chunk_keys_listing, definitions_listing, in_plane, layers_listing, plane,
832 plane_columns, planes_listing, types,
833 };
834 use crate::{Error, ErrorCode, Pack, VerifyLimits};
835 use std::path::Path;
836
837 fn pack() -> Pack {
838 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(
839 "../../fixtures/packs/5830e26e449a1f0d0c786b32801b7ed68e351777db5f586532478cb4384844b2.twpack",
840 );
841 Pack::open_with(&path, &VerifyLimits::default()).unwrap()
842 }
843
844 fn paged<T: PartialEq + std::fmt::Debug, K>(
848 list: impl Fn(Option<&K>, usize, &mut dyn FnMut(T) -> Result<(), Error>) -> Result<(), Error>,
849 key: impl Fn(&T) -> K,
850 ) -> Vec<T> {
851 let read = |after: Option<&K>, limit| {
852 let mut rows = Vec::new();
853 list(after, limit, &mut |row| {
854 rows.push(row);
855 Ok(())
856 })
857 .unwrap();
858 rows
859 };
860 let all = read(None, usize::MAX);
861 let mut one_by_one: Vec<T> = Vec::new();
862 loop {
863 let after = one_by_one.last().map(&key);
864 let mut page = read(after.as_ref(), 1);
865 assert!(page.len() <= 1);
866 let Some(row) = page.pop() else { break };
867 one_by_one.push(row);
868 }
869 assert_eq!(one_by_one, all);
870 all
871 }
872
873 fn text(after: Option<&String>) -> Option<&str> {
874 after.map(String::as_str)
875 }
876
877 fn pair(after: Option<&(String, String)>) -> Option<(&str, &str)> {
878 after.map(|(kind, id)| (kind.as_str(), id.as_str()))
879 }
880
881 #[test]
882 fn plane_listings_page_by_their_keys() {
883 let pack = pack();
884 let planes = paged(
885 |after, limit, visit| {
886 pack.visit_planes(
887 Page {
888 after: text(after),
889 limit,
890 bytes: usize::MAX,
891 },
892 visit,
893 )
894 },
895 |plane| plane.id.clone(),
896 );
897 assert_eq!(planes.len(), 2);
898 let zones = paged(
899 |after, limit, visit| {
900 pack.visit_zones(
901 "overworld",
902 Page {
903 after: text(after),
904 limit,
905 bytes: usize::MAX,
906 },
907 visit,
908 )
909 },
910 |zone| zone.id.clone(),
911 );
912 assert_eq!(zones.len(), 3);
913 let anchors = paged(
914 |after, limit, visit| {
915 pack.visit_anchors(
916 "overworld",
917 Page {
918 after: text(after),
919 limit,
920 bytes: usize::MAX,
921 },
922 visit,
923 )
924 },
925 |anchor| anchor.id.clone(),
926 );
927 assert_eq!(anchors.len(), 2);
928 let stairwells = paged(
929 |after, limit, visit| {
930 pack.visit_stairwells(
931 "overworld",
932 Page {
933 after: text(after),
934 limit,
935 bytes: usize::MAX,
936 },
937 visit,
938 )
939 },
940 |stairwell| stairwell.id.clone(),
941 );
942 assert_eq!(stairwells.len(), 3);
943 }
944
945 #[test]
946 fn definitions_layers_and_chunk_keys_page_by_their_keys() {
947 let pack = pack();
948 let definitions = paged(
949 |after, limit, visit| {
950 pack.visit_definitions(
951 None,
952 Page {
953 after: pair(after),
954 limit,
955 bytes: usize::MAX,
956 },
957 visit,
958 )
959 },
960 |definition| (definition.kind.clone(), definition.id.clone()),
961 );
962 assert_eq!(definitions.len(), 12);
963 let protos = paged(
964 |after, limit, visit| {
965 pack.visit_definitions(
966 Some("proto"),
967 Page {
968 after: pair(after),
969 limit,
970 bytes: usize::MAX,
971 },
972 visit,
973 )
974 },
975 |definition| (definition.kind.clone(), definition.id.clone()),
976 );
977 assert!(!protos.is_empty() && protos.iter().all(|found| found.kind == "proto"));
978 let layers = paged(
979 |after, limit, visit| {
980 pack.visit_layers(
981 "overworld",
982 Page {
983 after: after.copied(),
984 limit,
985 bytes: usize::MAX,
986 },
987 visit,
988 )
989 },
990 |layer| *layer,
991 );
992 assert_eq!(layers, [-1, 0, 1]);
993 let keys = paged(
994 |after, limit, visit| {
995 pack.visit_chunk_keys(
996 "overworld",
997 0,
998 Page {
999 after: after.copied(),
1000 limit,
1001 bytes: usize::MAX,
1002 },
1003 &mut |cx, cy| visit((cx, cy)),
1004 )
1005 },
1006 |key| *key,
1007 );
1008 assert_eq!(keys, [(-1, 0), (0, 0)]);
1009 for page in [
1011 Page {
1012 after: Some("~"),
1013 limit: 5,
1014 bytes: usize::MAX,
1015 },
1016 Page {
1017 after: None,
1018 limit: 0,
1019 bytes: usize::MAX,
1020 },
1021 ] {
1022 pack.visit_planes(page, &mut |_| panic!("{page:?} is empty"))
1023 .unwrap();
1024 }
1025 }
1026
1027 fn after<K>(key: K) -> Page<K> {
1029 Page {
1030 after: Some(key),
1031 limit: 64,
1032 bytes: usize::MAX,
1033 }
1034 }
1035
1036 fn plan(pack: &Pack, (sql, values): (String, Vec<types::Value>)) -> Vec<String> {
1038 let mut statement = pack
1039 .connection
1040 .prepare(&format!("EXPLAIN QUERY PLAN {sql}"))
1041 .unwrap();
1042 statement
1043 .query_map(rusqlite::params_from_iter(values), |row| row.get(3))
1044 .unwrap()
1045 .collect::<Result<_, _>>()
1046 .unwrap()
1047 }
1048
1049 #[test]
1050 fn every_listing_seeks_past_its_page_key() {
1051 let pack = pack();
1052 let stairwell_columns = format!(
1053 "{}, {}",
1054 super::bounded_identifier("id"),
1055 super::bounded_text("document")
1056 );
1057 for (query, seek) in [
1058 (
1059 planes_listing(after("a")),
1060 "SEARCH planes USING PRIMARY KEY (id>?)",
1061 ),
1062 (
1063 in_plane(
1064 &super::zone_columns(),
1065 "zones",
1066 true,
1067 "overworld",
1068 after("a"),
1069 ),
1070 "SEARCH zones USING PRIMARY KEY (plane=? AND id>?)",
1071 ),
1072 (
1073 in_plane(
1074 &super::anchor_columns(),
1075 "anchors",
1076 false,
1077 "overworld",
1078 after("a"),
1079 ),
1080 "SEARCH anchors USING PRIMARY KEY (plane=? AND id>?)",
1081 ),
1082 (
1083 in_plane(
1084 &stairwell_columns,
1085 "stairwells",
1086 true,
1087 "overworld",
1088 after("a"),
1089 ),
1090 "SEARCH stairwells USING PRIMARY KEY (plane=? AND id>?)",
1091 ),
1092 (
1093 definitions_listing(None, after(("proto", "a"))).unwrap(),
1094 "SEARCH definitions USING PRIMARY KEY ((type,id)>(?,?))",
1095 ),
1096 (
1097 definitions_listing(Some("proto"), after(("proto", "a"))).unwrap(),
1098 "SEARCH definitions USING PRIMARY KEY (type=? AND id>?)",
1099 ),
1100 (
1101 layers_listing("overworld", after(0)),
1102 "SEARCH chunks USING PRIMARY KEY (plane=? AND layer>?)",
1103 ),
1104 (
1105 chunk_keys_listing("overworld", 0, after((0, 0))),
1106 "SEARCH chunks USING PRIMARY KEY (plane=? AND layer=? AND (cx,cy)>(?,?))",
1107 ),
1108 ] {
1109 assert_eq!(plan(&pack, query), [seek]);
1110 }
1111 }
1112
1113 #[test]
1114 fn typed_definition_pages_follow_a_key_of_any_type() {
1115 let pack = pack();
1116 let read = |after: Option<(&str, &str)>| {
1117 let mut ids = Vec::new();
1118 let page = Page {
1119 after,
1120 limit: usize::MAX,
1121 bytes: usize::MAX,
1122 };
1123 pack.visit_definitions(Some("proto"), page, &mut |definition| {
1124 ids.push(definition.id);
1125 Ok(())
1126 })
1127 .unwrap();
1128 ids
1129 };
1130 let all = read(None);
1131 assert!(all.len() > 1);
1132 assert_eq!(read(Some(("a", "zzz"))), all);
1135 assert!(read(Some(("zzz", "a"))).is_empty());
1136 assert_eq!(read(Some(("proto", all[0].as_str()))), all[1..]);
1137 }
1138
1139 #[test]
1140 fn pages_stop_at_their_byte_budget() {
1141 let pack = pack();
1142 let read = |after: Option<&(String, String)>, bytes| {
1143 let mut rows = Vec::new();
1144 let page = Page {
1145 after: pair(after),
1146 limit: usize::MAX,
1147 bytes,
1148 };
1149 pack.visit_definitions(None, page, &mut |row| {
1150 rows.push(row);
1151 Ok(())
1152 })
1153 .unwrap();
1154 rows
1155 };
1156 let all = read(None, usize::MAX);
1157 let mut walked: Vec<super::Definition> = Vec::new();
1160 loop {
1161 let after = walked
1162 .last()
1163 .map(|last| (last.kind.clone(), last.id.clone()));
1164 let page = read(after.as_ref(), 1);
1165 assert!(page.len() <= 1);
1166 if page.is_empty() {
1167 break;
1168 }
1169 walked.extend(page);
1170 }
1171 assert_eq!(walked, all);
1172 let size = |row: &super::Definition| row.kind.len() + row.id.len() + row.document.len();
1174 let two = size(&all[0]) + size(&all[1]);
1175 assert_eq!(read(None, two).len(), 2);
1176 assert_eq!(read(None, two + 1).len(), 3);
1177 }
1178
1179 #[test]
1180 fn listings_that_do_not_move_forward_fail() {
1181 let pack = pack();
1182 let mut listing = Listing::new(&plane_columns(), "planes", true);
1185 listing.conditions.clear();
1186 let descending = listing.finish("ORDER BY id DESC", usize::MAX);
1187 let error = pack
1188 .walk(
1189 descending,
1190 None,
1191 usize::MAX,
1192 plane,
1193 |plane| (plane.id.clone(), 0),
1194 ErrorCode::Plane,
1195 &mut |_| Ok(()),
1196 )
1197 .unwrap_err();
1198 assert_eq!(error.code(), ErrorCode::Plane);
1199 let listing = Listing::new(&plane_columns(), "planes", true);
1201 let error = pack
1202 .walk(
1203 listing.finish("ORDER BY id", usize::MAX),
1204 Some("vault".to_owned()),
1205 usize::MAX,
1206 plane,
1207 |plane| (plane.id.clone(), 0),
1208 ErrorCode::Plane,
1209 &mut |_| Ok(()),
1210 )
1211 .unwrap_err();
1212 assert_eq!(error.code(), ErrorCode::Plane);
1213 }
1214}