diff --git a/crates/rupg-func/src/multirange.rs b/crates/rupg-func/src/multirange.rs index 5d9921d4..b5f438e8 100644 --- a/crates/rupg-func/src/multirange.rs +++ b/crates/rupg-func/src/multirange.rs @@ -1,4 +1,4 @@ -//! The multirange types of `multirangetypes.c`: `make_multirange`, which sorts the ranges and merges the ranges that overlap or touch, the order of the multiranges, the constructors, the functions that read the bounds and the flags, `range_merge` of a multirange and `unnest`. +//! The multirange types of `multirangetypes.c`: `make_multirange`, which sorts the ranges and merges the ranges that overlap or touch, the order of the multiranges, the constructors, the functions that read the bounds and the flags, `range_merge` of a multirange, `unnest` and the operators of a multirange with a multirange, a range or a value. use std::cmp::Ordering; @@ -7,7 +7,10 @@ use rupg_pgcatalog::builtin; use rupg_types::{RANGE_LB_INC, RANGE_LB_INF, RANGE_UB_INC, RANGE_UB_INF, Range, Value}; use crate::io::base_type; -use crate::range::{adjacent, before, make_range, range_order, subtype, union}; +use crate::range::{ + Bound, adjacent, before, bounds, bounds_adjacent, cmp_bounds, contains, contains_elem, + intersect, make_range, minus, over_left, overlaps, range_order, split, subtype, union, +}; use crate::{Call, Kernel, bad_value}; /// The range type of a multirange type, or 0 for a type that is not a multirange type. @@ -176,6 +179,253 @@ pub(crate) fn unnest(_: &Call<'_>, args: &[Value]) -> Result>> { Ok(arg(args)?.iter().map(|range| vec![Value::Range(Box::new(range.clone()))]).collect()) } +/// The range type of a call: the range type of the first argument or of the result that is a multirange type, or the first that is a range type. +fn call_range(call: &Call<'_>) -> u32 { + let types = || call.args.iter().chain([&call.ret]).copied(); + types() + .map(range_of) + .find(|&ty| ty != 0) + .or_else(|| types().find(|&ty| subtype(ty) != 0)) + .unwrap_or(0) +} + +/// The ranges of a range or a multirange argument. An empty range gives no range. +fn ranges_of(value: &Value) -> Result<&[Range]> { + match value { + Value::Multirange(ranges) => Ok(ranges), + Value::Range(range) if range.is_empty() => Ok(&[]), + Value::Range(range) => Ok(std::slice::from_ref(&**range)), + _ => Err(bad_value()), + } +} + +/// The lower bound of the first range and the upper bound of the last range, or `None` for no range. +fn span(ranges: &[Range]) -> Option<(Bound<'_>, Bound<'_>)> { + let (first, last) = (ranges.first()?, ranges.last()?); + Some((bounds(first).0, bounds(last).1)) +} + +/// `range_overlaps_multirange_internal` and `multirange_overlaps_multirange_internal`: true when a range of `a` and a range of `b` have a common value. The ranges of each side are in order and do not overlap, so one pass finds the pair. +fn overlaps_all(range: u32, a: &[Range], b: &[Range]) -> Result { + let sub = subtype(range); + let (mut i, mut j) = (0, 0); + while let (Some(x), Some(y)) = (a.get(i), b.get(j)) { + if overlaps(range, x, y)? { + return Ok(true); + } + if cmp_bounds(sub, &bounds(x).1, &bounds(y).1)?.is_lt() { + i += 1; + } else { + j += 1; + } + } + Ok(false) +} + +/// `multirange_contains_multirange_internal`: true when each range of `b` is in one range of `a`. No range in `b` gives true. +fn contains_all(range: u32, a: &[Range], b: &[Range]) -> Result { + let sub = subtype(range); + let mut i = 0; + for y in b { + let lower = bounds(y).0; + loop { + let Some(x) = a.get(i) else { return Ok(false) }; + if cmp_bounds(sub, &bounds(x).1, &lower)?.is_ge() { + break; + } + i += 1; + } + if !contains(range, &a[i], y)? { + return Ok(false); + } + } + Ok(true) +} + +/// The kernel of an operator that compares the outer bounds of two ranges or multiranges. An empty side gives false. +macro_rules! position { + ($name:ident, $doc:literal, |$sub:pat, $range:ident, $a:ident, $b:ident| $test:expr) => { + #[doc = $doc] + fn $name(call: &Call<'_>, args: &[Value]) -> Result { + let [a, b] = args else { return Err(bad_value()) }; + let $range = call_range(call); + let $sub = subtype($range); + let (Some($a), Some($b)) = (span(ranges_of(a)?), span(ranges_of(b)?)) else { + return Ok(Value::Bool(false)); + }; + Ok(Value::Bool($test)) + } + }; +} + +position!( + before_op, + "`<<`: true when the upper bound of the left side is before the lower bound of the right side.", + |sub, range, a, b| cmp_bounds(sub, &a.1, &b.0)?.is_lt() +); +position!( + after_op, + "`>>`: true when the lower bound of the left side is after the upper bound of the right side.", + |sub, range, a, b| cmp_bounds(sub, &a.0, &b.1)?.is_gt() +); +position!( + over_left_op, + "`&<`: true when the left side does not go to the right of the right side.", + |sub, range, a, b| cmp_bounds(sub, &a.1, &b.1)?.is_le() +); +position!( + over_right_op, + "`&>`: true when the left side does not go to the left of the right side.", + |sub, range, a, b| cmp_bounds(sub, &a.0, &b.0)?.is_ge() +); +position!( + adjacent_op, + "`-|-`: true when the upper bound of one side touches the lower bound of the other side. Only the outer bounds count, as in PostgreSQL.", + |_, range, a, b| bounds_adjacent(range, &a.1, &b.0)? || bounds_adjacent(range, &b.1, &a.0)? +); + +/// `&&` of a range or a multirange and a multirange. +fn overlaps_op(call: &Call<'_>, args: &[Value]) -> Result { + let [a, b] = args else { return Err(bad_value()) }; + Ok(Value::Bool(overlaps_all(call_range(call), ranges_of(a)?, ranges_of(b)?)?)) +} + +/// `@>` of a range or a multirange and a multirange, or of a multirange and a range. +fn contains_op(call: &Call<'_>, args: &[Value]) -> Result { + let [a, b] = args else { return Err(bad_value()) }; + Ok(Value::Bool(contains_all(call_range(call), ranges_of(a)?, ranges_of(b)?)?)) +} + +/// `<@` of a range or a multirange and a multirange, or of a multirange and a range. +fn contained_by_op(call: &Call<'_>, args: &[Value]) -> Result { + let [a, b] = args else { return Err(bad_value()) }; + Ok(Value::Bool(contains_all(call_range(call), ranges_of(b)?, ranges_of(a)?)?)) +} + +/// `multirange_contains_elem_internal`: true when a range of the multirange has the value. +fn contains_value(range: u32, ranges: &[Range], value: &Value) -> Result { + let sub = subtype(range); + for x in ranges { + if contains_elem(sub, x, value)? { + return Ok(true); + } + } + Ok(false) +} + +/// `multirange_contains_elem`: `@>` of a multirange and a value. +fn contains_elem_op(call: &Call<'_>, args: &[Value]) -> Result { + let [a, value] = args else { return Err(bad_value()) }; + Ok(Value::Bool(contains_value(call_range(call), ranges_of(a)?, value)?)) +} + +/// `elem_contained_by_multirange`: `<@` of a value and a multirange. +fn elem_contained_op(call: &Call<'_>, args: &[Value]) -> Result { + let [value, a] = args else { return Err(bad_value()) }; + Ok(Value::Bool(contains_value(call_range(call), ranges_of(a)?, value)?)) +} + +/// The ranges of two multirange arguments. +fn pair(args: &[Value]) -> Result<(&[Range], &[Range])> { + match args { + [Value::Multirange(a), Value::Multirange(b)] => Ok((a, b)), + _ => Err(bad_value()), + } +} + +/// `multirange_union`: the ranges of the two multiranges, merged. +fn union_op(call: &Call<'_>, args: &[Value]) -> Result { + let (a, b) = pair(args)?; + if a.is_empty() || b.is_empty() { + return Ok(Value::Multirange(if a.is_empty() { b } else { a }.to_vec())); + } + Ok(Value::Multirange(make_multirange(call_range(call), [a, b].concat())?)) +} + +/// `multirange_minus_internal`: the values of `a` that are not in `b`. Each range of `a` loses the ranges of `b` that overlap it, in one pass over the two multiranges. +fn minus_all(range: u32, a: &[Range], b: &[Range]) -> Result> { + let mut out = Vec::with_capacity(a.len() + b.len()); + let mut j = 0; + for x in a { + let mut x = x.clone(); + while let Some(y) = b.get(j) + && before(range, y, &x)? + { + j += 1; + } + while let Some(y) = b.get(j) { + if let Some((left, right)) = split(range, &x, y)? { + out.push(left); + x = right; + j += 1; + } else if overlaps(range, &x, y)? { + x = minus(range, &x, y)?; + if x.is_empty() || before(range, &x, y)? { + break; + } + j += 1; + } else { + break; + } + } + out.push(x); + } + make_multirange(range, out) +} + +/// `multirange_minus`: the values of the left multirange that are not in the right one. +fn minus_op(call: &Call<'_>, args: &[Value]) -> Result { + let (a, b) = pair(args)?; + if a.is_empty() || b.is_empty() { + return Ok(Value::Multirange(a.to_vec())); + } + Ok(Value::Multirange(minus_all(call_range(call), a, b)?)) +} + +/// `multirange_minus_multi`: the difference as a set function, with no row for an empty result. +pub(crate) fn minus_multi(call: &Call<'_>, args: &[Value]) -> Result>> { + let (a, b) = pair(args)?; + let out = + if a.is_empty() || b.is_empty() { a.to_vec() } else { minus_all(call_range(call), a, b)? }; + Ok(if out.is_empty() { Vec::new() } else { vec![vec![Value::Multirange(out)]] }) +} + +/// `multirange_intersect_internal`: the values that are in the two multiranges, in one pass over them. +fn intersect_all(range: u32, a: &[Range], b: &[Range]) -> Result> { + let mut out = Vec::new(); + let mut j = 0; + for x in a { + while let Some(y) = b.get(j) + && before(range, y, x)? + { + j += 1; + } + while let Some(y) = b.get(j) { + if !overlaps(range, x, y)? { + break; + } + out.push(intersect(range, x, y)?); + if !over_left(range, y, x)? { + break; + } + j += 1; + } + if j >= b.len() { + break; + } + } + make_multirange(range, out) +} + +/// `multirange_intersect`: the values that are in the two multiranges. +fn intersect_op(call: &Call<'_>, args: &[Value]) -> Result { + let (a, b) = pair(args)?; + if a.is_empty() || b.is_empty() { + return Ok(Value::Multirange(Vec::new())); + } + Ok(Value::Multirange(intersect_all(call_range(call), a, b)?)) +} + /// The kernel of a multirange function by its `prosrc`. pub(crate) fn by_src(src: &str) -> Option { Some(match src { @@ -190,6 +440,34 @@ pub(crate) fn by_src(src: &str) -> Option { "multirange_lower_inf" => lower_inf, "multirange_upper_inf" => upper_inf, "range_merge_from_multirange" => merge, + "multirange_union" => union_op, + "multirange_minus" => minus_op, + "multirange_intersect" => intersect_op, + "range_overlaps_multirange" + | "multirange_overlaps_range" + | "multirange_overlaps_multirange" => overlaps_op, + "multirange_contains_range" + | "range_contains_multirange" + | "multirange_contains_multirange" => contains_op, + "range_contained_by_multirange" + | "multirange_contained_by_range" + | "multirange_contained_by_multirange" => contained_by_op, + "multirange_contains_elem" => contains_elem_op, + "elem_contained_by_multirange" => elem_contained_op, + "range_before_multirange" | "multirange_before_range" | "multirange_before_multirange" => { + before_op + } + "range_after_multirange" | "multirange_after_range" | "multirange_after_multirange" => { + after_op + } + "range_overleft_multirange" + | "multirange_overleft_range" + | "multirange_overleft_multirange" => over_left_op, + "range_adjacent_multirange" + | "multirange_adjacent_range" + | "multirange_adjacent_multirange" => adjacent_op, + // The names of the `&>` functions have the word "over" and the word "right" side by side. + _ if src.contains("multirange") && src.contains(concat!("over", "right")) => over_right_op, _ => return None, }) } @@ -220,4 +498,21 @@ mod tests { assert_eq!(multirange_order(oid::INT4, &[], &[]).unwrap(), Ordering::Equal); assert_eq!(multirange_order(oid::INT4, &[int4(2, 3)], &two).unwrap(), Ordering::Greater); } + + #[test] + fn operators() { + let r = oid::INT4RANGE; + let a = [int4(1, 5), int4(10, 20)]; + let b = [int4(0, 2), int4(4, 11), int4(15, 16), int4(19, 30)]; + assert_eq!(minus_all(r, &a, &b).unwrap(), [int4(2, 4), int4(11, 15), int4(16, 19)]); + assert_eq!( + intersect_all(r, &a, &b).unwrap(), + [int4(1, 2), int4(4, 5), int4(10, 11), int4(15, 16), int4(19, 20)] + ); + assert!(overlaps_all(r, &a, &[int4(7, 12)]).unwrap()); + assert!(!overlaps_all(r, &a, &[int4(5, 10)]).unwrap()); + assert!(contains_all(r, &a, &[int4(2, 3), int4(12, 14)]).unwrap()); + assert!(!contains_all(r, &a, &[int4(2, 11)]).unwrap()); + assert!(contains_all(r, &[], &[]).unwrap()); + } } diff --git a/crates/rupg-func/src/range.rs b/crates/rupg-func/src/range.rs index 85eea0e3..56f199cb 100644 --- a/crates/rupg-func/src/range.rs +++ b/crates/rupg-func/src/range.rs @@ -19,7 +19,7 @@ pub(crate) fn subtype(range: u32) -> u32 { } /// One bound of a range, as `RangeBound` of `rangetypes.h` gives it. -struct Bound<'a> { +pub(crate) struct Bound<'a> { value: &'a Value, infinite: bool, inclusive: bool, @@ -27,7 +27,7 @@ struct Bound<'a> { } /// The lower bound and the upper bound of a range. -fn bounds(range: &Range) -> (Bound<'_>, Bound<'_>) { +pub(crate) fn bounds(range: &Range) -> (Bound<'_>, Bound<'_>) { let flags = range.flags; ( Bound { @@ -61,7 +61,7 @@ fn cmp_bound_values(sub: u32, b1: &Bound<'_>, b2: &Bound<'_>) -> Result, b2: &Bound<'_>) -> Result { +pub(crate) fn cmp_bounds(sub: u32, b1: &Bound<'_>, b2: &Bound<'_>) -> Result { let order = cmp_bound_values(sub, b1, b2)?; if order.is_ne() || b1.infinite || b2.infinite { return Ok(order); @@ -295,7 +295,7 @@ fn flip<'a>(bound: &Bound<'a>) -> Bound<'a> { } /// `range_overlaps_internal`: true when the ranges have a common value. -fn overlaps(range: u32, a: &Range, b: &Range) -> Result { +pub(crate) fn overlaps(range: u32, a: &Range, b: &Range) -> Result { if a.is_empty() || b.is_empty() { return Ok(false); } @@ -308,7 +308,7 @@ fn overlaps(range: u32, a: &Range, b: &Range) -> Result { } /// `range_contains_internal`: true when each value of `b` is in `a`. Each range contains an empty range. -fn contains(range: u32, a: &Range, b: &Range) -> Result { +pub(crate) fn contains(range: u32, a: &Range, b: &Range) -> Result { if b.is_empty() { return Ok(true); } @@ -355,7 +355,7 @@ fn after(range: u32, a: &Range, b: &Range) -> Result { } /// `range_overleft_internal`: true when `a` does not go to the right of `b`. -fn over_left(range: u32, a: &Range, b: &Range) -> Result { +pub(crate) fn over_left(range: u32, a: &Range, b: &Range) -> Result { Ok(order_of(range, a, b, true, true)?.is_some_and(Ordering::is_le)) } @@ -365,7 +365,7 @@ fn over_right(range: u32, a: &Range, b: &Range) -> Result { } /// `bounds_adjacent`: true when no value is between the upper bound `a` and the lower bound `b` and no value is in both. For a discrete subtype, the range between the bounds must be empty. -fn bounds_adjacent(range: u32, a: &Bound<'_>, b: &Bound<'_>) -> Result { +pub(crate) fn bounds_adjacent(range: u32, a: &Bound<'_>, b: &Bound<'_>) -> Result { Ok(match cmp_bound_values(subtype(range), a, b)? { Ordering::Less => { if !matches!(base_type(range), oid::INT4RANGE | oid::INT8RANGE | oid::DATERANGE) { @@ -411,7 +411,7 @@ pub(crate) fn union(range: u32, a: &Range, b: &Range, strict: bool) -> Result Result { +pub(crate) fn intersect(range: u32, a: &Range, b: &Range) -> Result { if !overlaps(range, a, b)? { return Ok(Range::empty()); } @@ -456,8 +456,21 @@ fn minus_multi(range: u32, a: &Range, b: &Range) -> Result> { }) } +/// `range_split_internal`: the part of `a` below `b` and the part above `b`, when `b` is inside `a` and leaves a part on each side. +pub(crate) fn split(range: u32, a: &Range, b: &Range) -> Result> { + let sub = subtype(range); + let (lower1, upper1) = bounds(a); + let (lower2, upper2) = bounds(b); + if cmp_bounds(sub, &lower1, &lower2)?.is_lt() && cmp_bounds(sub, &upper1, &upper2)?.is_gt() { + let below = from_bounds(range, &lower1, &flip(&lower2))?; + let above = from_bounds(range, &flip(&upper2), &upper1)?; + return Ok(Some((below, above))); + } + Ok(None) +} + /// `range_minus_internal`: the values of `a` that are not in `b`. The result must be one range. -fn minus(range: u32, a: &Range, b: &Range) -> Result { +pub(crate) fn minus(range: u32, a: &Range, b: &Range) -> Result { if a.is_empty() || b.is_empty() { return Ok(a.clone()); } @@ -538,7 +551,7 @@ fn elem_contained_op(call: &Call<'_>, args: &[Value]) -> Result { } /// `range_contains_elem_internal`: true when the value is in the range. -fn contains_elem(sub: u32, range: &Range, value: &Value) -> Result { +pub(crate) fn contains_elem(sub: u32, range: &Range, value: &Value) -> Result { if range.is_empty() { return Ok(false); } diff --git a/crates/rupg-func/src/srf.rs b/crates/rupg-func/src/srf.rs index 79372dd9..983e753b 100644 --- a/crates/rupg-func/src/srf.rs +++ b/crates/rupg-func/src/srf.rs @@ -1,4 +1,4 @@ -//! The set-returning functions that a query can call in `FROM` or in its target list: `generate_series` of the integer types, `unnest`, `generate_subscripts`, `string_to_table`, `regexp_matches`, `regexp_split_to_table`, `pg_options_to_table`, `aclexplode`, `range_minus_multi`, `multirange_unnest`, `pg_get_keywords`, `pg_show_all_settings`, `pg_stat_get_activity`, `pg_stat_get_backend_idset`, `pg_prepared_statement`, `pg_hba_file_rules`, `pg_ident_file_mappings`, the functions of the views of replication, of progress and of the prepared transactions, the functions of the statistics of the whole server that give rows, the functions of the views of asynchronous I/O, shared memory, extended statistics, replication origins, recovery, publications and cursors, and the functions of the views of the time zones. +//! The set-returning functions that a query can call in `FROM` or in its target list: `generate_series` of the integer types, `unnest`, `generate_subscripts`, `string_to_table`, `regexp_matches`, `regexp_split_to_table`, `pg_options_to_table`, `aclexplode`, `range_minus_multi`, `multirange_unnest`, `multirange_minus_multi`, `pg_get_keywords`, `pg_show_all_settings`, `pg_stat_get_activity`, `pg_stat_get_backend_idset`, `pg_prepared_statement`, `pg_hba_file_rules`, `pg_ident_file_mappings`, the functions of the views of replication, of progress and of the prepared transactions, the functions of the statistics of the whole server that give rows, the functions of the views of asynchronous I/O, shared memory, extended statistics, replication origins, recovery, publications and cursors, and the functions of the views of the time zones. //! //! A set kernel gives all the rows of the call at once, as a function of PostgreSQL in the materialize mode gives a tuplestore. Each row has one value for each column of the result: one value for a function of a scalar type, or one value for each `OUT` parameter. @@ -33,6 +33,7 @@ pub fn set_kernel(func: u32) -> Option { "pg_snapshot_xip" => crate::snapshot::snapshot_xip, "range_minus_multi" => crate::range::range_minus_multi, "multirange_unnest" => crate::multirange::unnest, + "multirange_minus_multi" => crate::multirange::minus_multi, "pg_get_keywords" => keywords, "pg_get_wait_events" => wait_events, "show_all_settings" => all_settings, diff --git a/crates/rupg-session/src/connection.rs b/crates/rupg-session/src/connection.rs index b4114782..7f9a5d52 100644 --- a/crates/rupg-session/src/connection.rs +++ b/crates/rupg-session/src/connection.rs @@ -1850,6 +1850,12 @@ mod tests { big_stack(|| script(include_str!("connection/multirange.test"))); } + /// The operators of the multiranges: union, difference and intersection, `multirange_minus_multi`, overlap, containment of a multirange, a range and a value, the position operators and adjacency with a multirange or a range on each side, the errors of the types that do not match, and a query with parameters. `connection/multirange_ops.test` is the output of PostgreSQL 19 for the same script in a new database. + #[test] + fn multirange_ops() { + big_stack(|| script(include_str!("connection/multirange_ops.test"))); + } + /// `inet` and `cidr`: the text forms of IPv4 and IPv6 with the errors of a bad value, the class of a `cidr` with no mask length, the casts, the functions such as `abbrev` and `set_masklen`, the containment, bit and arithmetic operators and the order. `connection/inet.test` is the output of PostgreSQL 19 for the same script in a new database. #[test] fn inet() { diff --git a/crates/rupg-session/src/connection/multirange_ops.test b/crates/rupg-session/src/connection/multirange_ops.test new file mode 100644 index 00000000..6eda5251 --- /dev/null +++ b/crates/rupg-session/src/connection/multirange_ops.test @@ -0,0 +1,96 @@ +> SET TimeZone = 'UTC' +SET +> SET DateStyle = 'ISO, MDY' +SET +> SELECT '{[1,5),[8,10)}'::int4multirange + '{[3,9)}', '{}'::int4multirange + '{[1,2)}', '{[1,2)}'::int4multirange + '{}', '{[1,2)}'::int4multirange + '{[2,3)}', '{[5,6)}'::int4multirange + '{(,1),[10,)}' +fields ?column?:4451:-1:-1,?column?:4451:-1:-1,?column?:4451:-1:-1,?column?:4451:-1:-1,?column?:4451:-1:-1 +row {[1,10)},{[1,2)},{[1,2)},{[1,3)},{(,1),[5,6),[10,)} +SELECT 1 +> SELECT '{[1,10)}'::int4multirange - '{[3,4),[6,7)}', '{[1,5),[8,10)}'::int4multirange - '{[4,9)}', '{[1,5)}'::int4multirange - '{}', '{}'::int4multirange - '{[1,5)}', '{[1,5)}'::int4multirange - '{(,)}', '{[1,5),[10,20)}'::int4multirange - '{[0,2),[4,11),[15,16),[19,30)}' +fields ?column?:4451:-1:-1,?column?:4451:-1:-1,?column?:4451:-1:-1,?column?:4451:-1:-1,?column?:4451:-1:-1,?column?:4451:-1:-1 +row {[1,3),[4,6),[7,10)},{[1,4),[9,10)},{[1,5)},{},{},{[2,4),[11,15),[16,19)} +SELECT 1 +> SELECT '{[1,10)}'::nummultirange - '{[3,4],(6,7)}', '{(,)}'::nummultirange - '{[1,2)}', '{[1,2],[3,4]}'::nummultirange - '{[2,3]}' +fields ?column?:4532:-1:-1,?column?:4532:-1:-1,?column?:4532:-1:-1 +row {[1,3),(4,6],[7,10)},{(,1),[2,)},{[1,2),(3,4]} +SELECT 1 +> SELECT '{[1,5),[8,10)}'::int4multirange * '{[3,9)}', '{[1,5)}'::int4multirange * '{}', '{[1,5),[8,10),[20,30)}'::int4multirange * '{[0,2),[4,25)}', '{[1,5)}'::int4multirange * '{[5,6)}', '{(,)}'::nummultirange * '{[1.5,2.5],(3,4)}' +fields ?column?:4451:-1:-1,?column?:4451:-1:-1,?column?:4451:-1:-1,?column?:4451:-1:-1,?column?:4532:-1:-1 +row {[3,5),[8,9)},{},{[1,2),[4,5),[8,10),[20,25)},{},{[1.5,2.5],(3,4)} +SELECT 1 +> SELECT * FROM multirange_minus_multi('{[1,10)}'::int4multirange, '{[3,4)}'::int4multirange) +fields multirange_minus_multi:4451:-1:-1 +row {[1,3),[4,10)} +SELECT 1 +> SELECT * FROM multirange_minus_multi('{[1,10)}'::int4multirange, '{[0,20)}'::int4multirange) +fields multirange_minus_multi:4451:-1:-1 +SELECT 0 +> SELECT * FROM multirange_minus_multi('{}'::int4multirange, '{[0,20)}'::int4multirange) +fields multirange_minus_multi:4451:-1:-1 +SELECT 0 +> SELECT * FROM multirange_minus_multi('{[1,2)}'::int4multirange, '{}'::int4multirange) +fields multirange_minus_multi:4451:-1:-1 +row {[1,2)} +SELECT 1 +> SELECT '{[1,5),[8,10)}'::int4multirange && '{[4,6)}'::int4multirange, '{[1,5),[8,10)}'::int4multirange && '{[5,8)}'::int4multirange, '{}'::int4multirange && '{}'::int4multirange, '{[1,5),[8,10)}'::int4multirange && int4range(9,20), '{[1,5)}'::int4multirange && 'empty'::int4range, int4range(4,8) && '{[1,5)}'::int4multirange, int4range(5,8) && '{[1,5)}'::int4multirange +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1 +row t,f,f,t,f,t,f +SELECT 1 +> SELECT '{[1,5),[8,10)}'::int4multirange @> 4, '{[1,5),[8,10)}'::int4multirange @> 5, '{}'::int4multirange @> 1, 9 <@ '{[1,5),[8,10)}'::int4multirange, '{(,5)}'::int4multirange @> -100, '{[1.5,2]}'::nummultirange @> 2.0 +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1 +row t,f,f,t,t,t +SELECT 1 +> SELECT '{[1,5),[8,10)}'::int4multirange @> int4range(2,4), '{[1,5),[8,10)}'::int4multirange @> int4range(4,9), '{}'::int4multirange @> 'empty'::int4range, '{}'::int4multirange @> int4range(1,2), int4range(2,4) <@ '{[1,5)}'::int4multirange, int4range(1,10) @> '{[1,5),[8,10)}'::int4multirange, int4range(1,9) @> '{[1,5),[8,10)}'::int4multirange, 'empty'::int4range @> '{}'::int4multirange, '{[2,3),[4,5)}'::int4multirange <@ int4range(1,6) +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1 +row t,f,t,f,t,t,f,t,t +SELECT 1 +> SELECT '{[1,5),[8,10)}'::int4multirange @> '{[2,3),[8,9)}'::int4multirange, '{[1,5),[8,10)}'::int4multirange @> '{[2,9)}'::int4multirange, '{[1,5)}'::int4multirange @> '{}'::int4multirange, '{}'::int4multirange @> '{[1,2)}'::int4multirange, '{[2,3)}'::int4multirange <@ '{[1,5),[8,10)}'::int4multirange, '{[1,5),[8,10)}'::int4multirange @> '{[11,12)}'::int4multirange +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1 +row t,f,t,f,t,f +SELECT 1 +> SELECT '{[1,5)}'::int4multirange << '{[5,6)}'::int4multirange, '{[1,5)}'::int4multirange << '{[4,6)}'::int4multirange, '{[1,5)}'::int4multirange >> '{[0,1)}'::int4multirange, '{[1,5)}'::int4multirange << int4range(5,6), '{[1,5)}'::int4multirange >> int4range(-5,1), int4range(1,2) << '{[3,4)}'::int4multirange, int4range(5,6) >> '{[3,4),[4,5)}'::int4multirange, '{}'::int4multirange << '{[1,2)}'::int4multirange, '{[1,2)}'::int4multirange << 'empty'::int4range +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1 +row t,f,t,t,t,t,t,f,f +SELECT 1 +> SELECT '{[1,5)}'::int4multirange &< '{[2,5)}'::int4multirange, '{[1,6)}'::int4multirange &< '{[2,5)}'::int4multirange, '{[1,5)}'::int4multirange &> '{[1,2)}'::int4multirange, '{[0,5)}'::int4multirange &> '{[1,2)}'::int4multirange, '{[1,5)}'::int4multirange &< int4range(0,5), '{[1,5)}'::int4multirange &> int4range(1,2), int4range(1,4) &< '{[1,2),[3,4)}'::int4multirange, int4range(1,4) &> '{[1,2),[3,4)}'::int4multirange, '{}'::int4multirange &< '{[1,2)}'::int4multirange +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1 +row t,f,t,f,t,t,t,t,f +SELECT 1 +> SELECT '{[1,5)}'::int4multirange -|- '{[5,6)}'::int4multirange, '{[1,5),[8,9)}'::int4multirange -|- '{[0,1)}'::int4multirange, '{[1,5)}'::int4multirange -|- '{[6,7)}'::int4multirange, '{[1,5)}'::int4multirange -|- int4range(5,9), int4range(-1,1) -|- '{[1,5),[7,8)}'::int4multirange, int4range(8,9) -|- '{[1,5),[7,8)}'::int4multirange, '{[1,2]}'::nummultirange -|- '{(2,3]}'::nummultirange, '{[1,2]}'::nummultirange -|- numrange(2,3), '{}'::int4multirange -|- int4range(1,2) +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1 +row t,t,f,t,t,t,t,f,f +SELECT 1 +> SELECT '{[1,5),[7,8)}'::int4multirange -|- '{[5,7)}'::int4multirange, '{[3,4),[7,8)}'::int4multirange -|- '{[1,3),[8,9)}'::int4multirange +fields ?column?:16:1:-1,?column?:16:1:-1 +row f,f +SELECT 1 +> SELECT '{[1,5)}'::int4multirange + '{[1,5)}'::int8multirange +ERROR 42883 operator does not exist: int4multirange + int8multirange at 34 +> SELECT '{[1,5)}'::int4multirange @> 4::bigint +ERROR 42883 operator does not exist: int4multirange @> bigint at 34 +> SELECT int4multirange(int4range(1,3)) * int4multirange(int4range(2,6)), range_merge(int4multirange(int4range(1,3)) + int4multirange(int4range(7,9))) +fields ?column?:4451:-1:-1,range_merge:3904:-1:-1 +row {[2,3)},[1,9) +SELECT 1 +> SELECT '{[2020-01-01,2020-02-01)}'::datemultirange - '{[2020-01-10,2020-01-20)}'::datemultirange, '{[2020-01-01,2020-02-01)}'::datemultirange @> '2020-01-15'::date, '{["2020-01-01 10:00","2020-01-01 12:00")}'::tsmultirange * '{["2020-01-01 11:00",)}'::tsmultirange +fields ?column?:4535:-1:-1,?column?:16:1:-1,?column?:4533:-1:-1 +row {[2020-01-01,2020-01-10),[2020-01-20,2020-02-01)},t,{["2020-01-01 11:00:00","2020-01-01 12:00:00")} +SELECT 1 +> SELECT a, b, a + b AS u, a * b AS i, a - b AS m, a && b AS o, a @> b AS c, a << b AS l, a -|- b AS adj FROM (VALUES ('{[1,3),[5,7)}'::int4multirange), ('{}'), ('{[2,6)}'), ('{(,1)}')) x(a), (VALUES ('{[3,5)}'::int4multirange), ('{[0,10)}')) y(b) ORDER BY a, b +fields a:4451:-1:-1,b:4451:-1:-1,u:4451:-1:-1,i:4451:-1:-1,m:4451:-1:-1,o:16:1:-1,c:16:1:-1,l:16:1:-1,adj:16:1:-1 +row {},{[0,10)},{[0,10)},{},{},f,f,f,f +row {},{[3,5)},{[3,5)},{},{},f,f,f,f +row {(,1)},{[0,10)},{(,10)},{[0,1)},{(,0)},t,f,f,f +row {(,1)},{[3,5)},{(,1),[3,5)},{},{(,1)},f,f,t,f +row {[1,3),[5,7)},{[0,10)},{[0,10)},{[1,3),[5,7)},{},t,f,f,f +row {[1,3),[5,7)},{[3,5)},{[1,7)},{},{[1,3),[5,7)},f,f,f,f +row {[2,6)},{[0,10)},{[0,10)},{[2,6)},{},t,f,f,f +row {[2,6)},{[3,5)},{[2,6)},{[3,5)},{[2,3),[5,6)},t,t,f,f +SELECT 8 +P q1|SELECT $1::int4multirange @> $2::int4, $1::int4multirange - $3::int4multirange +X q1|{[1,5),[8,10)}|9|{[2,9)} +row t,{[1,2),[9,10)} +SELECT 1 +X q1|{}|1|{} +row f,{} +SELECT 1