Skip to main content

eventuary_core/io/
cursor.rs

1use std::fmt;
2use std::sync::Arc;
3
4use serde::de::DeserializeOwned;
5use serde::{Deserialize, Serialize};
6
7use crate::error::{Error, Result};
8
9#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
10pub struct CursorId(Arc<str>);
11
12impl CursorId {
13    pub fn new(value: impl Into<Arc<str>>) -> Result<Self> {
14        let value: Arc<str> = value.into();
15        if value.is_empty() || value.len() > 128 {
16            return Err(Error::Config(format!(
17                "invalid cursor id: {:?}",
18                value.as_ref()
19            )));
20        }
21        if !value
22            .chars()
23            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.' || c == ':')
24        {
25            return Err(Error::Config(format!(
26                "invalid cursor id: {:?}",
27                value.as_ref()
28            )));
29        }
30        Ok(Self(value))
31    }
32
33    pub fn global() -> Self {
34        Self(Arc::from("global"))
35    }
36
37    pub fn partition(count: u16, id: u16) -> Self {
38        Self(Arc::from(format!("partition:{count}:{id}")))
39    }
40
41    pub fn as_str(&self) -> &str {
42        &self.0
43    }
44
45    pub fn prefixed(&self, prefix: impl AsRef<str>) -> Result<Self> {
46        Self::new(format!("{}:{}", prefix.as_ref(), self.as_str()))
47    }
48}
49
50impl fmt::Display for CursorId {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        f.write_str(&self.0)
53    }
54}
55
56impl serde::Serialize for CursorId {
57    fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
58        s.serialize_str(&self.0)
59    }
60}
61
62impl<'de> serde::Deserialize<'de> for CursorId {
63    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
64        let value = String::deserialize(d)?;
65        CursorId::new(Arc::from(value)).map_err(serde::de::Error::custom)
66    }
67}
68
69#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
70pub struct CursorOrder(Arc<[u8]>);
71
72impl CursorOrder {
73    pub fn min() -> Self {
74        Self(Arc::from(&[][..]))
75    }
76
77    pub fn from_bytes(bytes: impl Into<Arc<[u8]>>) -> Self {
78        Self(bytes.into())
79    }
80
81    pub fn from_u64(value: u64) -> Self {
82        Self(Arc::from(value.to_be_bytes().as_slice()))
83    }
84
85    pub fn from_i64(value: i64) -> Self {
86        let mapped = (value as u64) ^ (1u64 << 63);
87        Self::from_u64(mapped)
88    }
89
90    pub fn as_bytes(&self) -> &[u8] {
91        &self.0
92    }
93}
94
95impl serde::Serialize for CursorOrder {
96    fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
97        use base64::Engine;
98        use base64::engine::general_purpose::STANDARD;
99        s.serialize_str(&STANDARD.encode(&self.0))
100    }
101}
102
103impl<'de> serde::Deserialize<'de> for CursorOrder {
104    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
105        use base64::Engine;
106        use base64::engine::general_purpose::STANDARD;
107        let raw = String::deserialize(d)?;
108        let bytes = STANDARD
109            .decode(raw.as_bytes())
110            .map_err(serde::de::Error::custom)?;
111        Ok(Self(Arc::from(bytes.as_slice())))
112    }
113}
114
115#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
116pub struct CursorKind(Arc<str>);
117
118impl CursorKind {
119    pub fn new(value: impl Into<Arc<str>>) -> Result<Self> {
120        let value: Arc<str> = value.into();
121        if value.is_empty() || value.len() > 128 {
122            return Err(Error::Config(format!(
123                "invalid cursor kind: {:?}",
124                value.as_ref()
125            )));
126        }
127        if !value
128            .chars()
129            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.' || c == ':')
130        {
131            return Err(Error::Config(format!(
132                "invalid cursor kind: {:?}",
133                value.as_ref()
134            )));
135        }
136        Ok(Self(value))
137    }
138
139    pub fn as_str(&self) -> &str {
140        &self.0
141    }
142}
143
144impl fmt::Display for CursorKind {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        f.write_str(&self.0)
147    }
148}
149
150impl Serialize for CursorKind {
151    fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
152        s.serialize_str(&self.0)
153    }
154}
155
156impl<'de> Deserialize<'de> for CursorKind {
157    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
158        let value = String::deserialize(d)?;
159        CursorKind::new(Arc::from(value)).map_err(serde::de::Error::custom)
160    }
161}
162
163#[derive(Debug, Clone, Eq, PartialEq, Hash)]
164pub struct EncodedCursor {
165    id: CursorId,
166    kind: CursorKind,
167    order: CursorOrder,
168    payload: Arc<str>,
169}
170
171#[derive(Serialize, Deserialize)]
172struct EncodedCursorRepr {
173    id: CursorId,
174    kind: CursorKind,
175    order: CursorOrder,
176    payload: String,
177}
178
179impl Serialize for EncodedCursor {
180    fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
181        let repr = EncodedCursorRepr {
182            id: self.id.clone(),
183            kind: self.kind.clone(),
184            order: self.order.clone(),
185            payload: self.payload.as_ref().to_owned(),
186        };
187        repr.serialize(s)
188    }
189}
190
191impl<'de> Deserialize<'de> for EncodedCursor {
192    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
193        let repr = EncodedCursorRepr::deserialize(d)?;
194        Ok(Self {
195            id: repr.id,
196            kind: repr.kind,
197            order: repr.order,
198            payload: Arc::from(repr.payload),
199        })
200    }
201}
202
203impl EncodedCursor {
204    pub fn new(
205        id: CursorId,
206        kind: CursorKind,
207        order: CursorOrder,
208        payload: impl Into<Arc<str>>,
209    ) -> Result<Self> {
210        let payload: Arc<str> = payload.into();
211        if payload.is_empty() {
212            return Err(Error::InvalidCursor(
213                "encoded cursor payload must not be empty".to_owned(),
214            ));
215        }
216
217        use base64::Engine;
218        use base64::engine::general_purpose::STANDARD;
219        STANDARD.decode(payload.as_bytes()).map_err(|e| {
220            Error::InvalidCursor(format!("encoded cursor payload is not base64: {e}"))
221        })?;
222
223        Ok(Self {
224            id,
225            kind,
226            order,
227            payload,
228        })
229    }
230
231    pub fn from_bytes(
232        id: CursorId,
233        kind: CursorKind,
234        order: CursorOrder,
235        bytes: impl AsRef<[u8]>,
236    ) -> Self {
237        use base64::Engine;
238        use base64::engine::general_purpose::STANDARD;
239        Self {
240            id,
241            kind,
242            order,
243            payload: Arc::from(STANDARD.encode(bytes.as_ref())),
244        }
245    }
246
247    pub fn from_json<T: Serialize>(
248        id: CursorId,
249        kind: CursorKind,
250        order: CursorOrder,
251        value: &T,
252    ) -> Result<Self> {
253        let bytes = serde_json::to_vec(value)
254            .map_err(|e| Error::Serialization(format!("encode cursor json: {e}")))?;
255        Ok(Self::from_bytes(id, kind, order, bytes))
256    }
257
258    pub fn decode_bytes(&self) -> Result<Vec<u8>> {
259        use base64::Engine;
260        use base64::engine::general_purpose::STANDARD;
261        STANDARD
262            .decode(self.payload.as_bytes())
263            .map_err(|e| Error::InvalidCursor(format!("decode cursor payload: {e}")))
264    }
265
266    pub fn decode_json<T: DeserializeOwned>(&self, expected_kind: &CursorKind) -> Result<T> {
267        if &self.kind != expected_kind {
268            return Err(Error::InvalidCursor(format!(
269                "expected cursor kind `{}`, got `{}`",
270                expected_kind.as_str(),
271                self.kind.as_str()
272            )));
273        }
274        serde_json::from_slice(&self.decode_bytes()?)
275            .map_err(|e| Error::InvalidCursor(format!("decode cursor json: {e}")))
276    }
277
278    pub fn id_ref(&self) -> &CursorId {
279        &self.id
280    }
281
282    pub fn kind(&self) -> &CursorKind {
283        &self.kind
284    }
285
286    pub fn order(&self) -> &CursorOrder {
287        &self.order
288    }
289
290    pub fn payload(&self) -> &str {
291        &self.payload
292    }
293}
294
295impl PartialOrd for EncodedCursor {
296    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
297        Some(self.cmp(other))
298    }
299}
300
301impl Ord for EncodedCursor {
302    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
303        self.id.cmp(&other.id).then(self.order.cmp(&other.order))
304    }
305}
306
307impl Cursor for EncodedCursor {
308    fn id(&self) -> CursorId {
309        self.id.clone()
310    }
311
312    fn order_key(&self) -> CursorOrder {
313        self.order.clone()
314    }
315}
316
317pub trait CursorCodec<C>: Clone + Send + Sync + 'static {
318    fn encode(&self, cursor: &C) -> Result<EncodedCursor>;
319
320    fn decode(&self, cursor: &EncodedCursor) -> Result<C>;
321}
322
323#[derive(Debug)]
324pub struct JsonCursorCodec<C> {
325    kind: CursorKind,
326    _cursor: std::marker::PhantomData<fn() -> C>,
327}
328
329impl<C> Clone for JsonCursorCodec<C> {
330    fn clone(&self) -> Self {
331        Self {
332            kind: self.kind.clone(),
333            _cursor: std::marker::PhantomData,
334        }
335    }
336}
337
338impl<C> JsonCursorCodec<C> {
339    pub fn new(kind: impl Into<Arc<str>>) -> Result<Self> {
340        Ok(Self {
341            kind: CursorKind::new(kind)?,
342            _cursor: std::marker::PhantomData,
343        })
344    }
345
346    pub fn kind(&self) -> &CursorKind {
347        &self.kind
348    }
349}
350
351impl<C> CursorCodec<C> for JsonCursorCodec<C>
352where
353    C: Cursor + Serialize + DeserializeOwned + Send + Sync + 'static,
354{
355    fn encode(&self, cursor: &C) -> Result<EncodedCursor> {
356        EncodedCursor::from_json(cursor.id(), self.kind.clone(), cursor.order_key(), cursor)
357    }
358
359    fn decode(&self, cursor: &EncodedCursor) -> Result<C> {
360        cursor.decode_json(&self.kind)
361    }
362}
363
364#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
365pub struct NoCursor;
366
367pub trait Cursor {
368    fn id(&self) -> CursorId {
369        CursorId::global()
370    }
371
372    fn order_key(&self) -> CursorOrder {
373        CursorOrder::min()
374    }
375}
376
377impl Cursor for NoCursor {}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn global_returns_static_global_id() {
385        assert_eq!(CursorId::global().as_str(), "global");
386        assert_eq!(CursorId::global(), CursorId::global());
387    }
388
389    #[test]
390    fn partition_constructs_stable_format() {
391        let id = CursorId::partition(100, 17);
392        assert_eq!(id.as_str(), "partition:100:17");
393    }
394
395    #[test]
396    fn new_validates_and_stores_value() {
397        let id = CursorId::new("custom.stream").unwrap();
398        assert_eq!(id.as_str(), "custom.stream");
399    }
400
401    #[test]
402    fn new_rejects_empty() {
403        assert!(CursorId::new("").is_err());
404    }
405
406    #[test]
407    fn new_rejects_too_long() {
408        let long = "a".repeat(129);
409        assert!(CursorId::new(long).is_err());
410    }
411
412    #[test]
413    fn new_rejects_invalid_chars() {
414        assert!(CursorId::new("bad space").is_err());
415        assert!(CursorId::new("bad/char").is_err());
416    }
417
418    #[test]
419    fn new_accepts_valid_chars() {
420        assert!(CursorId::new("valid-name_01.v2:tag").is_ok());
421    }
422
423    #[test]
424    fn equality_by_value() {
425        let a = CursorId::new("test").unwrap();
426        let b = CursorId::new("test").unwrap();
427        assert_eq!(a, b);
428    }
429
430    #[test]
431    fn distinct_values_differ() {
432        let a = CursorId::new("a").unwrap();
433        let b = CursorId::new("b").unwrap();
434        assert_ne!(a, b);
435    }
436
437    #[test]
438    fn cursor_id_orders_lexically() {
439        let a = CursorId::new("a").unwrap();
440        let b = CursorId::new("b").unwrap();
441        assert!(a < b);
442    }
443
444    #[test]
445    fn prefixed_adds_stable_prefix() {
446        let id = CursorId::global().prefixed("left").unwrap();
447        assert_eq!(id.as_str(), "left:global");
448    }
449
450    #[test]
451    fn prefixed_rejects_invalid_prefix() {
452        let err = CursorId::global().prefixed("bad prefix").unwrap_err();
453        assert!(err.to_string().contains("invalid cursor id"));
454    }
455
456    #[test]
457    fn cursor_order_from_i64_preserves_signed_ordering() {
458        let lo = CursorOrder::from_i64(i64::MIN);
459        let zero = CursorOrder::from_i64(0);
460        let hi = CursorOrder::from_i64(i64::MAX);
461        assert!(lo < zero);
462        assert!(zero < hi);
463        assert!(CursorOrder::from_i64(-1) < CursorOrder::from_i64(1));
464        assert!(CursorOrder::from_i64(9) < CursorOrder::from_i64(10));
465    }
466
467    #[test]
468    fn cursor_order_from_u64_preserves_ordering() {
469        assert!(CursorOrder::from_u64(9) < CursorOrder::from_u64(10));
470    }
471
472    #[test]
473    fn cursor_order_roundtrips_via_json() {
474        let v = CursorOrder::from_i64(-42);
475        let s = serde_json::to_string(&v).unwrap();
476        let back: CursorOrder = serde_json::from_str(&s).unwrap();
477        assert_eq!(v, back);
478    }
479
480    #[test]
481    fn cursor_order_min_is_smallest() {
482        assert!(CursorOrder::min() < CursorOrder::from_u64(0));
483    }
484
485    #[test]
486    fn cursor_kind_validates() {
487        assert!(CursorKind::new("eventuary.postgres.cursor.v1").is_ok());
488        assert!(CursorKind::new("").is_err());
489        assert!(CursorKind::new("bad kind").is_err());
490    }
491
492    #[test]
493    fn encoded_cursor_roundtrips_json() {
494        #[derive(Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
495        struct TestCursor {
496            sequence: i64,
497        }
498
499        let kind = CursorKind::new("eventuary.test.cursor.v1").unwrap();
500        let cursor = EncodedCursor::from_json(
501            CursorId::global(),
502            kind.clone(),
503            CursorOrder::from_i64(42),
504            &TestCursor { sequence: 42 },
505        )
506        .unwrap();
507
508        assert_eq!(cursor.id_ref(), &CursorId::global());
509        assert_eq!(cursor.kind(), &kind);
510        assert_eq!(cursor.order(), &CursorOrder::from_i64(42));
511        let decoded: TestCursor = cursor.decode_json(&kind).unwrap();
512        assert_eq!(decoded, TestCursor { sequence: 42 });
513    }
514
515    #[test]
516    fn encoded_cursor_rejects_wrong_kind() {
517        let kind = CursorKind::new("eventuary.test.cursor.v1").unwrap();
518        let other = CursorKind::new("eventuary.other.cursor.v1").unwrap();
519        let cursor =
520            EncodedCursor::from_json(CursorId::global(), kind, CursorOrder::from_i64(0), &42_i64)
521                .unwrap();
522
523        let err = cursor.decode_json::<i64>(&other).unwrap_err();
524        assert!(err.to_string().contains("expected cursor kind"));
525    }
526
527    #[test]
528    fn encoded_cursor_orders_by_id_then_order() {
529        let kind = CursorKind::new("eventuary.test.cursor.v1").unwrap();
530        let a9 = EncodedCursor::from_bytes(
531            CursorId::new("a").unwrap(),
532            kind.clone(),
533            CursorOrder::from_i64(9),
534            b"x",
535        );
536        let a10 = EncodedCursor::from_bytes(
537            CursorId::new("a").unwrap(),
538            kind.clone(),
539            CursorOrder::from_i64(10),
540            b"x",
541        );
542        let b0 = EncodedCursor::from_bytes(
543            CursorId::new("b").unwrap(),
544            kind,
545            CursorOrder::from_i64(0),
546            b"x",
547        );
548
549        assert!(a9 < a10);
550        assert!(a10 < b0);
551    }
552
553    #[test]
554    fn encoded_cursor_new_validates_payload() {
555        let kind = CursorKind::new("eventuary.test.cursor.v1").unwrap();
556        let err = EncodedCursor::new(
557            CursorId::global(),
558            kind.clone(),
559            CursorOrder::from_i64(0),
560            "",
561        )
562        .unwrap_err();
563        assert!(err.to_string().contains("must not be empty"));
564
565        let err = EncodedCursor::new(
566            CursorId::global(),
567            kind.clone(),
568            CursorOrder::from_i64(0),
569            "not-base64-$$",
570        )
571        .unwrap_err();
572        assert!(err.to_string().contains("not base64"));
573
574        let ok =
575            EncodedCursor::from_bytes(CursorId::global(), kind, CursorOrder::from_i64(0), b"x");
576        let rebuilt = EncodedCursor::new(
577            ok.id_ref().clone(),
578            ok.kind().clone(),
579            ok.order().clone(),
580            ok.payload().to_owned(),
581        )
582        .unwrap();
583        assert_eq!(rebuilt, ok);
584    }
585
586    #[test]
587    fn encoded_cursor_implements_cursor_trait() {
588        let kind = CursorKind::new("eventuary.test.cursor.v1").unwrap();
589        let cursor = EncodedCursor::from_bytes(
590            CursorId::new("custom").unwrap(),
591            kind,
592            CursorOrder::from_u64(7),
593            b"p",
594        );
595        assert_eq!(<EncodedCursor as Cursor>::id(&cursor).as_str(), "custom");
596        assert_eq!(
597            <EncodedCursor as Cursor>::order_key(&cursor),
598            CursorOrder::from_u64(7)
599        );
600    }
601
602    #[test]
603    fn json_cursor_codec_roundtrips() {
604        #[derive(
605            Debug, Clone, Eq, PartialEq, Ord, PartialOrd, serde::Serialize, serde::Deserialize,
606        )]
607        struct TestCursor(i64);
608
609        impl Cursor for TestCursor {
610            fn order_key(&self) -> CursorOrder {
611                CursorOrder::from_i64(self.0)
612            }
613        }
614
615        let codec = JsonCursorCodec::<TestCursor>::new("eventuary.test.cursor.v1").unwrap();
616        let encoded = codec.encode(&TestCursor(7)).unwrap();
617        assert_eq!(encoded.id_ref(), &CursorId::global());
618        assert_eq!(encoded.kind(), codec.kind());
619        assert_eq!(encoded.order(), &CursorOrder::from_i64(7));
620
621        let decoded = codec.decode(&encoded).unwrap();
622        assert_eq!(decoded, TestCursor(7));
623    }
624
625    #[test]
626    fn json_cursor_codec_preserves_typed_ordering_under_erasure() {
627        #[derive(
628            Debug, Clone, Eq, PartialEq, Ord, PartialOrd, serde::Serialize, serde::Deserialize,
629        )]
630        struct TestCursor(i64);
631
632        impl Cursor for TestCursor {
633            fn order_key(&self) -> CursorOrder {
634                CursorOrder::from_i64(self.0)
635            }
636        }
637
638        let codec = JsonCursorCodec::<TestCursor>::new("eventuary.test.cursor.v1").unwrap();
639        for (a, b) in [(-5_i64, 0), (0, 1), (9, 10), (i64::MIN, i64::MAX)] {
640            let ea = codec.encode(&TestCursor(a)).unwrap();
641            let eb = codec.encode(&TestCursor(b)).unwrap();
642            assert_eq!(
643                TestCursor(a).cmp(&TestCursor(b)),
644                ea.cmp(&eb),
645                "a={a} b={b}"
646            );
647        }
648    }
649
650    #[test]
651    fn encoded_cursor_roundtrips_via_serde() {
652        let kind = CursorKind::new("eventuary.test.cursor.v1").unwrap();
653        let cursor =
654            EncodedCursor::from_json(CursorId::global(), kind, CursorOrder::from_i64(42), &42_i64)
655                .unwrap();
656
657        let s = serde_json::to_string(&cursor).unwrap();
658        let back: EncodedCursor = serde_json::from_str(&s).unwrap();
659        assert_eq!(cursor, back);
660    }
661
662    #[test]
663    fn cursor_trait_default_is_global() {
664        struct SomeCursor;
665        impl Cursor for SomeCursor {}
666        assert_eq!(SomeCursor.id(), CursorId::global());
667    }
668
669    #[test]
670    fn cursor_trait_named_example() {
671        struct NamedCursor;
672        impl Cursor for NamedCursor {
673            fn id(&self) -> CursorId {
674                CursorId::new("partition:100:17").unwrap()
675            }
676        }
677        assert_eq!(NamedCursor.id(), CursorId::partition(100, 17));
678    }
679
680    #[test]
681    fn cursor_trait_default_order_key_is_min() {
682        struct Probe;
683        impl Cursor for Probe {}
684        assert_eq!(Probe.order_key(), CursorOrder::min());
685    }
686
687    #[test]
688    fn cursor_trait_named_example_supports_custom_order() {
689        struct OrderedCursor(i64);
690        impl Cursor for OrderedCursor {
691            fn order_key(&self) -> CursorOrder {
692                CursorOrder::from_i64(self.0)
693            }
694        }
695        assert!(OrderedCursor(9).order_key() < OrderedCursor(10).order_key());
696    }
697
698    #[test]
699    fn cursor_trait_does_not_require_ord() {
700        struct UnorderedCursor;
701
702        impl Cursor for UnorderedCursor {}
703
704        assert_eq!(UnorderedCursor.id(), CursorId::global());
705        assert_eq!(UnorderedCursor.order_key(), CursorOrder::min());
706    }
707
708    #[test]
709    fn serializes_as_plain_string() {
710        let id = CursorId::global();
711        let v = serde_json::to_value(id).unwrap();
712        assert_eq!(v.as_str(), Some("global"));
713
714        let id = CursorId::partition(4, 1);
715        let v = serde_json::to_value(id).unwrap();
716        assert_eq!(v.as_str(), Some("partition:4:1"));
717    }
718
719    #[test]
720    fn roundtrips_via_json() {
721        let id = CursorId::global();
722        let v = serde_json::to_value(id.clone()).unwrap();
723        let back: CursorId = serde_json::from_value(v).unwrap();
724        assert_eq!(back, id);
725
726        let id = CursorId::partition(4, 2);
727        let v = serde_json::to_value(id.clone()).unwrap();
728        let back: CursorId = serde_json::from_value(v).unwrap();
729        assert_eq!(back, id);
730    }
731
732    #[test]
733    fn display_output_matches_as_str() {
734        let id = CursorId::global();
735        assert_eq!(id.to_string(), "global");
736
737        let id = CursorId::partition(4, 1);
738        assert_eq!(id.to_string(), "partition:4:1");
739    }
740
741    #[test]
742    fn no_cursor_uses_global_cursor_id() {
743        assert_eq!(NoCursor.id(), CursorId::global());
744    }
745}