1use std::cmp::Ordering;
10
11use uqa_core::memory::ProductionControl;
12use uqa_core::{DecimalValue, TemporalValue, Value};
13
14use super::IntervalFields;
15
16use crate::error::{Result, SQLError};
17
18const MICROS_PER_DAY: i64 = 86_400_000_000;
19
20pub fn in_range(val: &Value, base: &Value, offset: &Value, sub: bool, less: bool) -> Result<bool> {
22 match (val, base, offset) {
23 (Value::Int(val), Value::Int(base), Value::Int(offset)) => {
24 integer_in_range(*val, *base, *offset, sub, less)
25 }
26 (Value::Float(val), Value::Float(base), Value::Float(offset)) => {
27 float_in_range(*val, *base, *offset, sub, less)
28 }
29 (Value::Decimal(val), Value::Decimal(base), Value::Decimal(offset)) => {
30 numeric_in_range(val, base, offset, sub, less)
31 }
32 (Value::Temporal(val), Value::Temporal(base), Value::Temporal(offset)) => {
33 temporal_in_range(val, base, offset, sub, less)
34 }
35 _ => Err(SQLError::Internal(format!(
36 "RANGE frame offset {offset:?} does not apply to ordering values {val:?} and {base:?}"
37 ))),
38 }
39}
40
41fn invalid_offset() -> SQLError {
42 SQLError::Routine {
43 sqlstate: "22013".into(),
44 message: "invalid preceding or following size in window function".into(),
45 }
46}
47
48fn numeric_overflow() -> SQLError {
50 SQLError::Routine {
51 sqlstate: "22003".into(),
52 message: "value overflows numeric format".into(),
53 }
54}
55
56fn bound(ordering: Ordering, less: bool) -> bool {
57 if less {
58 ordering.is_le()
59 } else {
60 ordering.is_ge()
61 }
62}
63
64fn integer_in_range(val: i64, base: i64, offset: i64, sub: bool, less: bool) -> Result<bool> {
66 if offset < 0 {
67 return Err(invalid_offset());
68 }
69 let offset = i128::from(offset);
70 let target = if sub {
71 i128::from(base) - offset
72 } else {
73 i128::from(base) + offset
74 };
75 Ok(bound(i128::from(val).cmp(&target), less))
76}
77
78fn float_in_range(val: f64, base: f64, offset: f64, sub: bool, less: bool) -> Result<bool> {
80 if offset.is_nan() || offset < 0.0 {
81 return Err(invalid_offset());
82 }
83 if val.is_nan() {
85 return Ok(if base.is_nan() { true } else { !less });
86 }
87 if base.is_nan() {
88 return Ok(less);
89 }
90 if offset.is_infinite() && base.is_infinite() && (if sub { base > 0.0 } else { base < 0.0 }) {
92 return Ok(true);
93 }
94 let target = if sub { base - offset } else { base + offset };
95 Ok(if less { val <= target } else { val >= target })
96}
97
98fn numeric_in_range(
100 val: &DecimalValue,
101 base: &DecimalValue,
102 offset: &DecimalValue,
103 sub: bool,
104 less: bool,
105) -> Result<bool> {
106 if offset.is_nan() || offset.is_negative_infinity() || offset.is_negative() {
107 return Err(invalid_offset());
108 }
109 if val.is_nan() {
110 return Ok(if base.is_nan() { true } else { !less });
111 }
112 if base.is_nan() {
113 return Ok(less);
114 }
115 if offset.is_positive_infinity() {
116 if if sub {
117 base.is_positive_infinity()
118 } else {
119 base.is_negative_infinity()
120 } {
121 return Ok(true);
122 }
123 return Ok(if sub {
124 !less || val.is_negative_infinity()
126 } else {
127 less || val.is_positive_infinity()
129 });
130 }
131 if val.is_infinite() {
132 return Ok(if val.is_positive_infinity() {
133 base.is_positive_infinity() || !less
134 } else {
135 base.is_negative_infinity() || less
136 });
137 }
138 if base.is_infinite() {
139 return Ok(if base.is_negative_infinity() {
140 !less
141 } else {
142 less
143 });
144 }
145 let target = if sub {
146 base.checked_sub(offset)
147 } else {
148 base.checked_add(offset)
149 }
150 .ok_or_else(numeric_overflow)?;
151 Ok(bound(val.cmp(&target), less))
152}
153
154fn temporal_in_range(
156 val: &TemporalValue,
157 base: &TemporalValue,
158 offset: &TemporalValue,
159 sub: bool,
160 less: bool,
161) -> Result<bool> {
162 use TemporalValue as T;
163 let offset = interval_fields(offset)?;
164 let micros = offset.micros;
165 match (val, base) {
166 (T::Time { micros: val }, T::Time { micros: base }) => {
167 if micros < 0 {
169 return Err(invalid_offset());
170 }
171 let target = if sub {
172 base - micros
173 } else {
174 match base.checked_add(micros) {
175 Some(target) => target,
176 None => return Ok(less),
177 }
178 };
179 Ok(bound(val.cmp(&target), less))
180 }
181 (
182 T::TimeTz { .. },
183 T::TimeTz {
184 micros: base,
185 offset_minutes,
186 },
187 ) => {
188 if micros < 0 {
189 return Err(invalid_offset());
190 }
191 let target = if sub {
192 base - micros
193 } else {
194 match base.checked_add(micros) {
195 Some(target) => target,
196 None => return Ok(less),
197 }
198 };
199 let target = Value::Temporal(T::TimeTz {
200 micros: target,
201 offset_minutes: *offset_minutes,
202 });
203 let ordering = crate::expr::compare_typed_values_with_control(
204 &Value::Temporal(val.clone()),
205 &target,
206 &ProductionControl::uncontrolled(),
207 )?;
208 Ok(bound(ordering, less))
209 }
210 (T::Interval { .. }, T::Interval { .. }) => {
211 if interval_span(offset) < 0 {
212 return Err(invalid_offset());
213 }
214 let (val, base) = (interval_fields(val)?, interval_fields(base)?);
215 let target = if sub {
216 base.minus(offset)?
217 } else {
218 base.plus(offset)?
219 };
220 Ok(bound(interval_span(val).cmp(&interval_span(target)), less))
221 }
222 _ => {
223 if interval_span(offset) < 0 {
224 return Err(invalid_offset());
225 }
226 let val = timestamp_micros(val)?;
227 let base = timestamp_micros(base)?;
228 let offset = if sub { offset.negate()? } else { offset };
230 let target = super::time::timestamp_plus_interval(
231 base,
232 offset.months,
233 offset.days,
234 offset.micros,
235 )?;
236 Ok(bound(val.cmp(&target), less))
237 }
238 }
239}
240
241fn interval_fields(value: &TemporalValue) -> Result<IntervalFields> {
242 IntervalFields::of(value).ok_or_else(|| {
243 SQLError::Internal(format!("RANGE frame value {value:?} is not an interval"))
244 })
245}
246
247fn interval_span(interval: IntervalFields) -> i128 {
249 (i128::from(interval.months) * 30 + i128::from(interval.days)) * i128::from(MICROS_PER_DAY)
250 + i128::from(interval.micros)
251}
252
253fn timestamp_micros(value: &TemporalValue) -> Result<i64> {
255 match value {
256 TemporalValue::Date { days } => Ok(i64::from(*days) * MICROS_PER_DAY),
257 TemporalValue::Timestamp { micros } | TemporalValue::TimestampTz { micros } => Ok(*micros),
258 other => Err(SQLError::Internal(format!(
259 "RANGE frame ordering value {other:?} is not a date or timestamp"
260 ))),
261 }
262}
263
264#[cfg(test)]
265mod tests;