Creates [SqlTransform::Except] from [Transform::Join] and [Transform::Filter]
(
pipeline: Vec<SqlTransform>,
ctx: &mut Context,
)
| 304 | |
| 305 | /// Creates [SqlTransform::Except] from [Transform::Join] and [Transform::Filter] |
| 306 | pub(in crate::sql) fn except( |
| 307 | pipeline: Vec<SqlTransform>, |
| 308 | ctx: &mut Context, |
| 309 | ) -> Result<Vec<SqlTransform>> { |
| 310 | use SqlTransform::*; |
| 311 | |
| 312 | let output = ctx.anchor.determine_select_columns(&pipeline); |
| 313 | let output: HashSet<CId, RandomState> = HashSet::from_iter(output); |
| 314 | |
| 315 | let mut res = Vec::with_capacity(pipeline.len()); |
| 316 | for t in pipeline { |
| 317 | res.push(t); |
| 318 | |
| 319 | if res.len() < 2 { |
| 320 | continue; |
| 321 | } |
| 322 | let SqlTransform::Join { |
| 323 | side: JoinSide::Left, |
| 324 | filter: join_cond, |
| 325 | with, |
| 326 | } = &res[res.len() - 2] |
| 327 | else { |
| 328 | continue; |
| 329 | }; |
| 330 | let Super(Transform::Filter(filter)) = &res[res.len() - 1] else { |
| 331 | continue; |
| 332 | }; |
| 333 | |
| 334 | let with = ctx.anchor.relation_instances.get(with).unwrap(); |
| 335 | |
| 336 | let top = ctx.anchor.determine_select_columns(&res[0..res.len() - 2]); |
| 337 | let bottom = with.table_ref.columns.iter().map(|(_, c)| *c).collect_vec(); |
| 338 | |
| 339 | // join_cond must be a join over all columns |
| 340 | // (this could be loosened to check only the relation key) |
| 341 | let (join_left, join_right) = collect_equals(join_cond)?; |
| 342 | if !all_in(&top, join_left) || !all_in(&bottom, join_right) { |
| 343 | continue; |
| 344 | } |
| 345 | |
| 346 | // filter has to check for nullability of bottom |
| 347 | // (this could be loosened to check only for nulls in a previously non-nullable column) |
| 348 | let (filter_left, filter_right) = collect_equals(filter)?; |
| 349 | if !(all_in(&bottom, filter_left) && all_null(filter_right)) { |
| 350 | continue; |
| 351 | } |
| 352 | |
| 353 | // select must not contain things from bottom |
| 354 | if bottom.iter().any(|c| output.contains(c)) { |
| 355 | continue; |
| 356 | } |
| 357 | |
| 358 | // determine DISTINCT |
| 359 | let mut distinct = false; |
| 360 | // EXCEPT ALL can become except EXCEPT DISTINCT, if top is DISTINCT. |
| 361 | // DISTINCT-ness of bottom has no effect on the output. |
| 362 | if res.len() >= 3 { |
| 363 | if let Distinct = &res[res.len() - 3] { |
no test coverage detected