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