1use std::collections::HashMap;
4
5use crate::error::{Error, Result};
6use crate::{Expr, Value};
7
8pub fn build_cursor_predicate(
40 pk_fields: &[(&str, &str)],
41 cursor: &HashMap<String, Value>,
42 backward: bool,
43) -> Result<Expr> {
44 if pk_fields.is_empty() {
45 return Err(Error::InvalidQuery(
46 "build_cursor_predicate: pk_fields must not be empty".to_string(),
47 ));
48 }
49 build_cursor_recursive(pk_fields, cursor, backward, 0)
50}
51
52fn build_cursor_recursive(
53 pk_fields: &[(&str, &str)],
54 cursor: &HashMap<String, Value>,
55 backward: bool,
56 index: usize,
57) -> Result<Expr> {
58 let (map_key, col_ref) = pk_fields[index];
59
60 let val = cursor
61 .get(map_key)
62 .ok_or_else(|| {
63 Error::InvalidQuery(format!(
64 "cursor missing required primary-key field '{}'",
65 map_key
66 ))
67 })?
68 .clone();
69
70 let col = Expr::column(col_ref.to_string());
71 let param = Expr::param(val);
72
73 if index == pk_fields.len() - 1 {
74 Ok(if backward {
75 col.le(param)
76 } else {
77 col.ge(param)
78 })
79 } else {
80 let strict = if backward {
81 col.clone().lt(param.clone())
82 } else {
83 col.clone().gt(param.clone())
84 };
85 let eq_and_rest = col.eq(param).and(build_cursor_recursive(
86 pk_fields,
87 cursor,
88 backward,
89 index + 1,
90 )?);
91 Ok(strict.or(eq_and_rest))
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::Value;
99
100 fn map(pairs: &[(&str, Value)]) -> HashMap<String, Value> {
101 pairs
102 .iter()
103 .map(|(k, v)| (k.to_string(), v.clone()))
104 .collect()
105 }
106
107 #[test]
108 fn single_field_forward() {
109 let cursor = map(&[("id", Value::I32(5))]);
110 let pred = build_cursor_predicate(&[("id", "users__id")], &cursor, false).unwrap();
111 assert!(matches!(
112 pred,
113 crate::Expr::Binary {
114 op: crate::BinaryOp::Ge,
115 ..
116 }
117 ));
118 }
119
120 #[test]
121 fn single_field_backward() {
122 let cursor = map(&[("id", Value::I32(5))]);
123 let pred = build_cursor_predicate(&[("id", "users__id")], &cursor, true).unwrap();
124 assert!(matches!(
125 pred,
126 crate::Expr::Binary {
127 op: crate::BinaryOp::Le,
128 ..
129 }
130 ));
131 }
132
133 #[test]
134 fn composite_forward() {
135 let cursor = map(&[("user_id", Value::I32(2)), ("post_id", Value::I32(10))]);
136 let pred = build_cursor_predicate(
137 &[("user_id", "posts__user_id"), ("post_id", "posts__post_id")],
138 &cursor,
139 false,
140 )
141 .unwrap();
142 assert!(matches!(
143 pred,
144 crate::Expr::Binary {
145 op: crate::BinaryOp::Or,
146 ..
147 }
148 ));
149 }
150
151 #[test]
152 fn missing_key_returns_error() {
153 let cursor = map(&[("id", Value::I32(5))]);
154 let result = build_cursor_predicate(&[("missing_field", "t__c")], &cursor, false);
155 assert!(result.is_err());
156 }
157
158 #[test]
159 fn empty_pk_fields_returns_error() {
160 let cursor = map(&[("id", Value::I32(5))]);
161 let result = build_cursor_predicate(&[], &cursor, false);
162 assert!(result.is_err());
163 }
164}