This method "maps" a function across the `ResultSpec`. As mentioned above, `ResultSpec` represents an approximate set of results. If we actually stored each result in the set, `flat_map` could be implemented by passing each result to the function one-by-one and unioning the resulting sets. This is possible when our values set is empty or contains a single datum, but when it contains a range, we c
(
&self,
is_monotone: bool,
mut result_map: impl FnMut(Result<Datum<'a>, EvalError>) -> ResultSpec<'a>,
)
| 296 | /// mapping the endpoints; |
| 297 | /// - using a safe default when we can't infer a tighter bound on the set, eg. [Self::anything]. |
| 298 | fn flat_map( |
| 299 | &self, |
| 300 | is_monotone: bool, |
| 301 | mut result_map: impl FnMut(Result<Datum<'a>, EvalError>) -> ResultSpec<'a>, |
| 302 | ) -> ResultSpec<'a> { |
| 303 | let null_spec = if self.nullable { |
| 304 | result_map(Ok(Datum::Null)) |
| 305 | } else { |
| 306 | ResultSpec::nothing() |
| 307 | }; |
| 308 | |
| 309 | let error_spec = if self.fallible { |
| 310 | // Since we only care about whether / not an error is possible, and not the specific |
| 311 | // error, create an arbitrary error here. |
| 312 | // NOTE! This assumes that functions do not discriminate on the type of the error. |
| 313 | let map_err = result_map(Err(EvalError::Internal("".into()))); |
| 314 | let raise_err = ResultSpec::fails(); |
| 315 | // SQL has a very loose notion of evaluation order: https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-EXPRESS-EVAL |
| 316 | // Here, we account for the possibility that the expression is evaluated strictly, |
| 317 | // raising the error, or that it's evaluated lazily by the result_map function |
| 318 | // (which may return a non-error result even when given an error as input). |
| 319 | raise_err.union(map_err) |
| 320 | } else { |
| 321 | ResultSpec::nothing() |
| 322 | }; |
| 323 | |
| 324 | let values_spec = match self.values { |
| 325 | Values::Empty => ResultSpec::nothing(), |
| 326 | // If this range contains a single datum, just call the function. |
| 327 | Values::Within(min, max) if min == max => result_map(Ok(min)), |
| 328 | // If this is a range of booleans, we know all the values... just try them. |
| 329 | Values::Within(Datum::False, Datum::True) => { |
| 330 | result_map(Ok(Datum::False)).union(result_map(Ok(Datum::True))) |
| 331 | } |
| 332 | // Otherwise, if our function is monotonic, we can try mapping the input |
| 333 | // range to an output range. |
| 334 | Values::Within(min, max) if is_monotone => { |
| 335 | let min_result = result_map(Ok(min)); |
| 336 | let max_result = result_map(Ok(max)); |
| 337 | match (min_result, max_result) { |
| 338 | // If both endpoints give us a range, the result is a union of those ranges. |
| 339 | ( |
| 340 | ResultSpec { |
| 341 | nullable: false, |
| 342 | fallible: false, |
| 343 | values: a_values, |
| 344 | }, |
| 345 | ResultSpec { |
| 346 | nullable: false, |
| 347 | fallible: false, |
| 348 | values: b_values, |
| 349 | }, |
| 350 | ) => ResultSpec { |
| 351 | nullable: false, |
| 352 | fallible: false, |
| 353 | values: a_values.union(b_values), |
| 354 | }, |
| 355 | // If both endpoints are null, we assume the whole range maps to null. |