1use std::cmp::Ordering;
10
11use uqa_core::{DecimalValue, TemporalInfinity, TemporalValue, Value};
12
13use super::IntervalFields;
14
15use crate::error::{Result, SQLError};
16
17pub fn in_range(val: &Value, base: &Value, offset: &Value, sub: bool, less: bool) -> Result<bool> {
19 in_range_with_engine(val, base, offset, sub, less, None)
20}
21
22pub fn in_range_with_engine(
24 val: &Value,
25 base: &Value,
26 offset: &Value,
27 sub: bool,
28 less: bool,
29 engine: Option<&dyn super::EngineHook>,
30) -> Result<bool> {
31 match (val, base, offset) {
32 (Value::Int(val), Value::Int(base), Value::Int(offset)) => {
33 integer_in_range(*val, *base, *offset, sub, less)
34 }
35 (Value::Float(val), Value::Float(base), Value::Float(offset)) => {
36 float_in_range(*val, *base, *offset, sub, less)
37 }
38 (Value::Decimal(val), Value::Decimal(base), Value::Decimal(offset)) => {
39 numeric_in_range(val, base, offset, sub, less)
40 }
41 (Value::Temporal(val), Value::Temporal(base), Value::Temporal(offset)) => {
42 temporal_in_range(val, base, offset, sub, less, engine)
43 }
44 _ => Err(SQLError::Internal(format!(
45 "RANGE frame offset {offset:?} does not apply to ordering values {val:?} and {base:?}"
46 ))),
47 }
48}
49
50fn invalid_offset() -> SQLError {
51 SQLError::Routine {
52 sqlstate: "22013".into(),
53 message: "invalid preceding or following size in window function".into(),
54 }
55}
56
57fn numeric_overflow() -> SQLError {
59 SQLError::Routine {
60 sqlstate: "22003".into(),
61 message: "value overflows numeric format".into(),
62 }
63}
64
65fn bound(ordering: Ordering, less: bool) -> bool {
66 if less {
67 ordering.is_le()
68 } else {
69 ordering.is_ge()
70 }
71}
72
73fn integer_in_range(val: i64, base: i64, offset: i64, sub: bool, less: bool) -> Result<bool> {
75 if offset < 0 {
76 return Err(invalid_offset());
77 }
78 let offset = i128::from(offset);
79 let target = if sub {
80 i128::from(base) - offset
81 } else {
82 i128::from(base) + offset
83 };
84 Ok(bound(i128::from(val).cmp(&target), less))
85}
86
87fn float_in_range(val: f64, base: f64, offset: f64, sub: bool, less: bool) -> Result<bool> {
89 if offset.is_nan() || offset < 0.0 {
90 return Err(invalid_offset());
91 }
92 if val.is_nan() {
94 return Ok(if base.is_nan() { true } else { !less });
95 }
96 if base.is_nan() {
97 return Ok(less);
98 }
99 if offset.is_infinite() && base.is_infinite() && (if sub { base > 0.0 } else { base < 0.0 }) {
101 return Ok(true);
102 }
103 let target = if sub { base - offset } else { base + offset };
104 Ok(if less { val <= target } else { val >= target })
105}
106
107fn numeric_in_range(
109 val: &DecimalValue,
110 base: &DecimalValue,
111 offset: &DecimalValue,
112 sub: bool,
113 less: bool,
114) -> Result<bool> {
115 if offset.is_nan() || offset.is_negative_infinity() || offset.is_negative() {
116 return Err(invalid_offset());
117 }
118 if val.is_nan() {
119 return Ok(if base.is_nan() { true } else { !less });
120 }
121 if base.is_nan() {
122 return Ok(less);
123 }
124 if offset.is_positive_infinity() {
125 if if sub {
126 base.is_positive_infinity()
127 } else {
128 base.is_negative_infinity()
129 } {
130 return Ok(true);
131 }
132 return Ok(if sub {
133 !less || val.is_negative_infinity()
135 } else {
136 less || val.is_positive_infinity()
138 });
139 }
140 if val.is_infinite() {
141 return Ok(if val.is_positive_infinity() {
142 base.is_positive_infinity() || !less
143 } else {
144 base.is_negative_infinity() || less
145 });
146 }
147 if base.is_infinite() {
148 return Ok(if base.is_negative_infinity() {
149 !less
150 } else {
151 less
152 });
153 }
154 let target = if sub {
155 base.checked_sub(offset)
156 } else {
157 base.checked_add(offset)
158 }
159 .ok_or_else(numeric_overflow)?;
160 Ok(bound(val.cmp(&target), less))
161}
162
163fn temporal_in_range(
165 val: &TemporalValue,
166 base: &TemporalValue,
167 offset: &TemporalValue,
168 sub: bool,
169 less: bool,
170 engine: Option<&dyn super::EngineHook>,
171) -> Result<bool> {
172 use TemporalValue as T;
173 let offset = interval_fields(offset)?;
174 let micros = offset.micros;
175 match (val, base) {
176 (T::Time { micros: val }, T::Time { micros: base }) => {
177 if micros < 0 {
179 return Err(invalid_offset());
180 }
181 let target = if sub {
182 base - micros
183 } else {
184 match base.checked_add(micros) {
185 Some(target) => target,
186 None => return Ok(less),
187 }
188 };
189 Ok(bound(val.cmp(&target), less))
190 }
191 (
192 T::TimeTz {
193 micros: val,
194 offset_minutes: val_offset,
195 },
196 T::TimeTz {
197 micros: base,
198 offset_minutes,
199 },
200 ) => {
201 if micros < 0 {
202 return Err(invalid_offset());
203 }
204 let target = if sub {
205 base - micros
206 } else {
207 match base.checked_add(micros) {
208 Some(target) => target,
209 None => return Ok(less),
210 }
211 };
212 let utc_micros =
215 |time: i64, offset: i32| time.wrapping_sub(i64::from(offset) * 60 * 1_000_000);
216 let ordering = utc_micros(*val, *val_offset)
217 .cmp(&utc_micros(target, *offset_minutes))
218 .then_with(|| offset_minutes.cmp(val_offset));
219 Ok(bound(ordering, less))
220 }
221 (T::Interval { .. }, T::Interval { .. }) => {
222 if offset.value() < IntervalFields::ZERO.value() {
223 return Err(invalid_offset());
224 }
225 if indeterminate_infinite_bound(base, offset, sub) {
226 return Ok(true);
227 }
228 let base = interval_fields(base)?;
229 let target = if sub {
230 base.minus(offset)?
231 } else {
232 base.plus(offset)?
233 };
234 Ok(bound(val.cmp(&target.value()), less))
235 }
236 _ => {
237 let zoned = matches!(base, T::TimestampTz { .. });
238 let val = T::Timestamp {
240 micros: timestamp_micros(val)?,
241 };
242 let base = T::Timestamp {
243 micros: timestamp_micros(base)?,
244 };
245 if offset.value() < IntervalFields::ZERO.value() {
246 return Err(invalid_offset());
247 }
248 if indeterminate_infinite_bound(&base, offset, sub) {
249 return Ok(true);
250 }
251 let offset = if sub { offset.negate()? } else { offset };
253 let target = if zoned {
254 super::time::timestamp_tz_plus_interval(
255 timestamp_micros(&base)?,
256 offset,
257 engine,
258 &uqa_core::memory::ProductionControl::uncontrolled(),
259 )?
260 } else {
261 super::time::timestamp_plus_interval(
262 timestamp_micros(&base)?,
263 offset.months,
264 offset.days,
265 offset.micros,
266 )?
267 };
268 Ok(bound(val.cmp(&T::Timestamp { micros: target }), less))
269 }
270 }
271}
272
273fn interval_fields(value: &TemporalValue) -> Result<IntervalFields> {
274 IntervalFields::of(value).ok_or_else(|| {
275 SQLError::Internal(format!("RANGE frame value {value:?} is not an interval"))
276 })
277}
278
279fn indeterminate_infinite_bound(base: &TemporalValue, offset: IntervalFields, sub: bool) -> bool {
280 offset.value().infinity() == Some(TemporalInfinity::Positive)
281 && base.infinity()
282 == Some(if sub {
283 TemporalInfinity::Positive
284 } else {
285 TemporalInfinity::Negative
286 })
287}
288
289fn timestamp_micros(value: &TemporalValue) -> Result<i64> {
291 match value {
292 TemporalValue::Date { .. } => {
293 super::time::temporal_timestamp_micros(value).map_err(|_| SQLError::Routine {
294 sqlstate: "22008".into(),
295 message: "date out of range for timestamp".into(),
296 })
297 }
298 TemporalValue::Timestamp { micros } | TemporalValue::TimestampTz { micros } => Ok(*micros),
299 other => Err(SQLError::Internal(format!(
300 "RANGE frame ordering value {other:?} is not a date or timestamp"
301 ))),
302 }
303}
304
305#[cfg(test)]
306mod tests;