This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git
The following commit(s) were added to refs/heads/main by this push:
new 6d1617f0 feat(python): add BETWEEN/NOT BETWEEN predicate support (#546)
6d1617f0 is described below
commit 6d1617f0e63f81b0ac1bc15caefd593b460d5f27
Author: hechao <[email protected]>
AuthorDate: Mon Jul 20 11:03:38 2026 +0800
feat(python): add BETWEEN/NOT BETWEEN predicate support (#546)
---
bindings/python/src/predicate.rs | 109 +++++++++++++++++++++++++++++++++++++++
1 file changed, 109 insertions(+)
diff --git a/bindings/python/src/predicate.rs b/bindings/python/src/predicate.rs
index e7efba40..06e5cd12 100644
--- a/bindings/python/src/predicate.rs
+++ b/bindings/python/src/predicate.rs
@@ -524,6 +524,30 @@ fn leaf_to_predicate(
};
pb.like(&field, ds.pop().unwrap(), escape)
}
+ "between" => {
+ let mut ds = to_datums(literals_obj)?;
+ if ds.len() != 2 {
+ return Err(PyValueError::new_err(format!(
+ "'between' expects exactly 2 literals (low, high), got {}",
+ ds.len()
+ )));
+ }
+ let high = ds.pop().unwrap();
+ let low = ds.pop().unwrap();
+ pb.between(&field, low, high)
+ }
+ "notBetween" => {
+ let mut ds = to_datums(literals_obj)?;
+ if ds.len() != 2 {
+ return Err(PyValueError::new_err(format!(
+ "'notBetween' expects exactly 2 literals (low, high), got
{}",
+ ds.len()
+ )));
+ }
+ let high = ds.pop().unwrap();
+ let low = ds.pop().unwrap();
+ pb.not_between(&field, low, high)
+ }
other => {
return Err(PyNotImplementedError::new_err(format!(
"unknown or unsupported predicate operator '{other}'"
@@ -1565,4 +1589,89 @@ value = datetime.datetime(2024, 1, 1,
tzinfo=FloatingTz())",
assert!(err.is_instance_of::<pyo3::exceptions::PyValueError>(py));
});
}
+
+ // ---- between / notBetween ----
+
+ #[test]
+ fn between_leaf_converts() {
+ Python::attach(|py| {
+ let fields = test_fields();
+ let dict = leaf_dict(py, "between", "id", &[10, 20]);
+ let pred = dict_to_predicate(&dict, &fields, true).unwrap();
+ match &pred {
+ Predicate::Leaf { op, literals, .. } => {
+ assert_eq!(*op, PredicateOperator::Between);
+ assert_eq!(literals, &[Datum::Int(10), Datum::Int(20)]);
+ }
+ other => panic!("expected Leaf, got {other:?}"),
+ }
+ });
+ }
+
+ #[test]
+ fn not_between_leaf_converts() {
+ Python::attach(|py| {
+ let fields = test_fields();
+ let dict = leaf_dict(py, "notBetween", "id", &[10, 20]);
+ let pred = dict_to_predicate(&dict, &fields, true).unwrap();
+ match &pred {
+ Predicate::Leaf { op, literals, .. } => {
+ assert_eq!(*op, PredicateOperator::NotBetween);
+ assert_eq!(literals, &[Datum::Int(10), Datum::Int(20)]);
+ }
+ other => panic!("expected Leaf, got {other:?}"),
+ }
+ });
+ }
+
+ #[test]
+ fn between_rejects_wrong_literal_count() {
+ Python::attach(|py| {
+ let fields = test_fields();
+ for n in [0, 1, 3] {
+ let lits: Vec<i64> = (0..n).collect();
+ let dict = leaf_dict(py, "between", "id", &lits);
+ let err = dict_to_predicate(&dict, &fields, true).unwrap_err();
+ assert!(
+ err.is_instance_of::<PyValueError>(py),
+ "between with {n} lits"
+ );
+ }
+ });
+ }
+
+ #[test]
+ fn not_between_rejects_wrong_literal_count() {
+ Python::attach(|py| {
+ let fields = test_fields();
+ for n in [0, 1, 3] {
+ let lits: Vec<i64> = (0..n).collect();
+ let dict = leaf_dict(py, "notBetween", "id", &lits);
+ let err = dict_to_predicate(&dict, &fields, true).unwrap_err();
+ assert!(
+ err.is_instance_of::<PyValueError>(py),
+ "notBetween with {n} lits"
+ );
+ }
+ });
+ }
+
+ #[test]
+ fn between_with_string_field_accepts_string_literals() {
+ Python::attach(|py| {
+ let fields = test_fields();
+ let dict = str_leaf_dict(py, "between", "name", &["alpha",
"omega"]);
+ let pred = dict_to_predicate(&dict, &fields, true).unwrap();
+ match &pred {
+ Predicate::Leaf { op, literals, .. } => {
+ assert_eq!(*op, PredicateOperator::Between);
+ assert_eq!(
+ literals,
+ &[Datum::String("alpha".into()),
Datum::String("omega".into())]
+ );
+ }
+ other => panic!("expected Leaf, got {other:?}"),
+ }
+ });
+ }
}