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
836 fn pack() -> Pack {
837 Pack::open_with(&crate::tests::valid_pack(), &VerifyLimits::default()).unwrap()
838 }
839
840 fn paged<T: PartialEq + std::fmt::Debug, K>(
844 list: impl Fn(Option<&K>, usize, &mut dyn FnMut(T) -> Result<(), Error>) -> Result<(), Error>,
845 key: impl Fn(&T) -> K,
846 ) -> Vec<T> {
847 let read = |after: Option<&K>, limit| {
848 let mut rows = Vec::new();
849 list(after, limit, &mut |row| {
850 rows.push(row);
851 Ok(())
852 })
853 .unwrap();
854 rows
855 };
856 let all = read(None, usize::MAX);
857 let mut one_by_one: Vec<T> = Vec::new();
858 loop {
859 let after = one_by_one.last().map(&key);
860 let mut page = read(after.as_ref(), 1);
861 assert!(page.len() <= 1);
862 let Some(row) = page.pop() else { break };
863 one_by_one.push(row);
864 }
865 assert_eq!(one_by_one, all);
866 all
867 }
868
869 fn text(after: Option<&String>) -> Option<&str> {
870 after.map(String::as_str)
871 }
872
873 fn pair(after: Option<&(String, String)>) -> Option<(&str, &str)> {
874 after.map(|(kind, id)| (kind.as_str(), id.as_str()))
875 }
876
877 #[test]
878 fn plane_listings_page_by_their_keys() {
879 let pack = pack();
880 let planes = paged(
881 |after, limit, visit| {
882 pack.visit_planes(
883 Page {
884 after: text(after),
885 limit,
886 bytes: usize::MAX,
887 },
888 visit,
889 )
890 },
891 |plane| plane.id.clone(),
892 );
893 assert_eq!(planes.len(), 2);
894 let zones = paged(
895 |after, limit, visit| {
896 pack.visit_zones(
897 "overworld",
898 Page {
899 after: text(after),
900 limit,
901 bytes: usize::MAX,
902 },
903 visit,
904 )
905 },
906 |zone| zone.id.clone(),
907 );
908 assert_eq!(zones.len(), 3);
909 let anchors = paged(
910 |after, limit, visit| {
911 pack.visit_anchors(
912 "overworld",
913 Page {
914 after: text(after),
915 limit,
916 bytes: usize::MAX,
917 },
918 visit,
919 )
920 },
921 |anchor| anchor.id.clone(),
922 );
923 assert_eq!(anchors.len(), 2);
924 let stairwells = paged(
925 |after, limit, visit| {
926 pack.visit_stairwells(
927 "overworld",
928 Page {
929 after: text(after),
930 limit,
931 bytes: usize::MAX,
932 },
933 visit,
934 )
935 },
936 |stairwell| stairwell.id.clone(),
937 );
938 assert_eq!(stairwells.len(), 3);
939 }
940
941 #[test]
942 fn definitions_layers_and_chunk_keys_page_by_their_keys() {
943 let pack = pack();
944 let definitions = paged(
945 |after, limit, visit| {
946 pack.visit_definitions(
947 None,
948 Page {
949 after: pair(after),
950 limit,
951 bytes: usize::MAX,
952 },
953 visit,
954 )
955 },
956 |definition| (definition.kind.clone(), definition.id.clone()),
957 );
958 assert_eq!(definitions.len(), 12);
959 let protos = paged(
960 |after, limit, visit| {
961 pack.visit_definitions(
962 Some("proto"),
963 Page {
964 after: pair(after),
965 limit,
966 bytes: usize::MAX,
967 },
968 visit,
969 )
970 },
971 |definition| (definition.kind.clone(), definition.id.clone()),
972 );
973 assert!(!protos.is_empty() && protos.iter().all(|found| found.kind == "proto"));
974 let layers = paged(
975 |after, limit, visit| {
976 pack.visit_layers(
977 "overworld",
978 Page {
979 after: after.copied(),
980 limit,
981 bytes: usize::MAX,
982 },
983 visit,
984 )
985 },
986 |layer| *layer,
987 );
988 assert_eq!(layers, [-1, 0, 1]);
989 let keys = paged(
990 |after, limit, visit| {
991 pack.visit_chunk_keys(
992 "overworld",
993 0,
994 Page {
995 after: after.copied(),
996 limit,
997 bytes: usize::MAX,
998 },
999 &mut |cx, cy| visit((cx, cy)),
1000 )
1001 },
1002 |key| *key,
1003 );
1004 assert_eq!(keys, [(-1, 0), (0, 0)]);
1005 for page in [
1007 Page {
1008 after: Some("~"),
1009 limit: 5,
1010 bytes: usize::MAX,
1011 },
1012 Page {
1013 after: None,
1014 limit: 0,
1015 bytes: usize::MAX,
1016 },
1017 ] {
1018 pack.visit_planes(page, &mut |_| panic!("{page:?} is empty"))
1019 .unwrap();
1020 }
1021 }
1022
1023 fn after<K>(key: K) -> Page<K> {
1025 Page {
1026 after: Some(key),
1027 limit: 64,
1028 bytes: usize::MAX,
1029 }
1030 }
1031
1032 fn plan(pack: &Pack, (sql, values): (String, Vec<types::Value>)) -> Vec<String> {
1034 let mut statement = pack
1035 .connection
1036 .prepare(&format!("EXPLAIN QUERY PLAN {sql}"))
1037 .unwrap();
1038 statement
1039 .query_map(rusqlite::params_from_iter(values), |row| row.get(3))
1040 .unwrap()
1041 .collect::<Result<_, _>>()
1042 .unwrap()
1043 }
1044
1045 #[test]
1046 fn every_listing_seeks_past_its_page_key() {
1047 let pack = pack();
1048 let stairwell_columns = format!(
1049 "{}, {}",
1050 super::bounded_identifier("id"),
1051 super::bounded_text("document")
1052 );
1053 for (query, seek) in [
1054 (
1055 planes_listing(after("a")),
1056 "SEARCH planes USING PRIMARY KEY (id>?)",
1057 ),
1058 (
1059 in_plane(
1060 &super::zone_columns(),
1061 "zones",
1062 true,
1063 "overworld",
1064 after("a"),
1065 ),
1066 "SEARCH zones USING PRIMARY KEY (plane=? AND id>?)",
1067 ),
1068 (
1069 in_plane(
1070 &super::anchor_columns(),
1071 "anchors",
1072 false,
1073 "overworld",
1074 after("a"),
1075 ),
1076 "SEARCH anchors USING PRIMARY KEY (plane=? AND id>?)",
1077 ),
1078 (
1079 in_plane(
1080 &stairwell_columns,
1081 "stairwells",
1082 true,
1083 "overworld",
1084 after("a"),
1085 ),
1086 "SEARCH stairwells USING PRIMARY KEY (plane=? AND id>?)",
1087 ),
1088 (
1089 definitions_listing(None, after(("proto", "a"))).unwrap(),
1090 "SEARCH definitions USING PRIMARY KEY ((type,id)>(?,?))",
1091 ),
1092 (
1093 definitions_listing(Some("proto"), after(("proto", "a"))).unwrap(),
1094 "SEARCH definitions USING PRIMARY KEY (type=? AND id>?)",
1095 ),
1096 (
1097 layers_listing("overworld", after(0)),
1098 "SEARCH chunks USING PRIMARY KEY (plane=? AND layer>?)",
1099 ),
1100 (
1101 chunk_keys_listing("overworld", 0, after((0, 0))),
1102 "SEARCH chunks USING PRIMARY KEY (plane=? AND layer=? AND (cx,cy)>(?,?))",
1103 ),
1104 ] {
1105 assert_eq!(plan(&pack, query), [seek]);
1106 }
1107 }
1108
1109 #[test]
1110 fn typed_definition_pages_follow_a_key_of_any_type() {
1111 let pack = pack();
1112 let read = |after: Option<(&str, &str)>| {
1113 let mut ids = Vec::new();
1114 let page = Page {
1115 after,
1116 limit: usize::MAX,
1117 bytes: usize::MAX,
1118 };
1119 pack.visit_definitions(Some("proto"), page, &mut |definition| {
1120 ids.push(definition.id);
1121 Ok(())
1122 })
1123 .unwrap();
1124 ids
1125 };
1126 let all = read(None);
1127 assert!(all.len() > 1);
1128 assert_eq!(read(Some(("a", "zzz"))), all);
1131 assert!(read(Some(("zzz", "a"))).is_empty());
1132 assert_eq!(read(Some(("proto", all[0].as_str()))), all[1..]);
1133 }
1134
1135 #[test]
1136 fn pages_stop_at_their_byte_budget() {
1137 let pack = pack();
1138 let read = |after: Option<&(String, String)>, bytes| {
1139 let mut rows = Vec::new();
1140 let page = Page {
1141 after: pair(after),
1142 limit: usize::MAX,
1143 bytes,
1144 };
1145 pack.visit_definitions(None, page, &mut |row| {
1146 rows.push(row);
1147 Ok(())
1148 })
1149 .unwrap();
1150 rows
1151 };
1152 let all = read(None, usize::MAX);
1153 let mut walked: Vec<super::Definition> = Vec::new();
1156 loop {
1157 let after = walked
1158 .last()
1159 .map(|last| (last.kind.clone(), last.id.clone()));
1160 let page = read(after.as_ref(), 1);
1161 assert!(page.len() <= 1);
1162 if page.is_empty() {
1163 break;
1164 }
1165 walked.extend(page);
1166 }
1167 assert_eq!(walked, all);
1168 let size = |row: &super::Definition| row.kind.len() + row.id.len() + row.document.len();
1170 let two = size(&all[0]) + size(&all[1]);
1171 assert_eq!(read(None, two).len(), 2);
1172 assert_eq!(read(None, two + 1).len(), 3);
1173 }
1174
1175 #[test]
1176 fn listings_that_do_not_move_forward_fail() {
1177 let pack = pack();
1178 let mut listing = Listing::new(&plane_columns(), "planes", true);
1181 listing.conditions.clear();
1182 let descending = listing.finish("ORDER BY id DESC", usize::MAX);
1183 let error = pack
1184 .walk(
1185 descending,
1186 None,
1187 usize::MAX,
1188 plane,
1189 |plane| (plane.id.clone(), 0),
1190 ErrorCode::Plane,
1191 &mut |_| Ok(()),
1192 )
1193 .unwrap_err();
1194 assert_eq!(error.code(), ErrorCode::Plane);
1195 let listing = Listing::new(&plane_columns(), "planes", true);
1197 let error = pack
1198 .walk(
1199 listing.finish("ORDER BY id", usize::MAX),
1200 Some("vault".to_owned()),
1201 usize::MAX,
1202 plane,
1203 |plane| (plane.id.clone(), 0),
1204 ErrorCode::Plane,
1205 &mut |_| Ok(()),
1206 )
1207 .unwrap_err();
1208 assert_eq!(error.code(), ErrorCode::Plane);
1209 }
1210}