From fd2aa77a9998b87a0ed1070c1f39634655bb93fb Mon Sep 17 00:00:00 2001 From: tamnd Date: Tue, 13 Oct 2026 15:42:11 +0700 Subject: [PATCH] Operators of the range types --- crates/rupg-analyze/src/transform.rs | 7 +- crates/rupg-func/src/cast.rs | 2 +- crates/rupg-func/src/range.rs | 357 +++++++++++++++++- crates/rupg-func/src/srf.rs | 3 +- crates/rupg-session/src/connection.rs | 6 + .../src/connection/range_ops.test | 194 ++++++++++ 6 files changed, 563 insertions(+), 6 deletions(-) create mode 100644 crates/rupg-session/src/connection/range_ops.test diff --git a/crates/rupg-analyze/src/transform.rs b/crates/rupg-analyze/src/transform.rs index ef3937d3..77427e79 100644 --- a/crates/rupg-analyze/src/transform.rs +++ b/crates/rupg-analyze/src/transform.rs @@ -221,8 +221,11 @@ impl Analyzer<'_> { let (value, ty) = match &c.val { Some(Node::Integer(v)) => (Value::Int4(*v), oid::INT4), Some(Node::Float(text)) => match rupg_types::int8_in(text) { - // A literal that fits `int8` is an `int8`, and any other number is a `numeric`. - Ok(v) => (Value::Int8(v), oid::INT8), + // A literal that fits `int4`, such as `-2147483648`, is an `int4`. A literal that fits `int8` is an `int8`, and any other number is a `numeric`. + Ok(v) => match i32::try_from(v) { + Ok(v) => (Value::Int4(v), oid::INT4), + Err(_) => (Value::Int8(v), oid::INT8), + }, Err(_) => { let v = rupg_types::numeric_in(text, -1).map_err(|e| Error::from(e).at_opt(at))?; diff --git a/crates/rupg-func/src/cast.rs b/crates/rupg-func/src/cast.rs index de0c0c9e..10d21a97 100644 --- a/crates/rupg-func/src/cast.rs +++ b/crates/rupg-func/src/cast.rs @@ -115,7 +115,7 @@ fn numeric_to_int(call: &Call<'_>, args: &[Value]) -> Result { } /// `numeric_float8` and `numeric_float4`: the input function of the float type on the text of the number. -fn numeric_to_float(call: &Call<'_>, args: &[Value]) -> Result { +pub(crate) fn numeric_to_float(call: &Call<'_>, args: &[Value]) -> Result { let Value::Numeric(n) = first(args)? else { return Err(bad_value()) }; let special = match n.sign() { NumericSign::NaN => Some(f64::NAN), diff --git a/crates/rupg-func/src/range.rs b/crates/rupg-func/src/range.rs index d8d1b7b0..fca45e34 100644 --- a/crates/rupg-func/src/range.rs +++ b/crates/rupg-func/src/range.rs @@ -1,4 +1,4 @@ -//! The range types of `rangetypes.c`: `make_range` with the canonical functions of the discrete ranges, the order of the ranges, the constructors and the functions that read the bounds and the flags. +//! The range types of `rangetypes.c`: `make_range` with the canonical functions of the discrete ranges, the order of the ranges, the constructors, the functions that read the bounds and the flags, the operators of the ranges and the subtype difference functions. use std::cmp::Ordering; @@ -11,7 +11,7 @@ use rupg_types::{ use crate::compare::compare; use crate::io::base_type; -use crate::{Call, Kernel, bad_value}; +use crate::{Call, Kernel, bad_value, type_error}; /// The subtype of a range type, or 0 for a type that is not a range type. pub(crate) fn subtype(range: u32) -> u32 { @@ -261,6 +261,322 @@ flag_test!( flag_test!(lower_inf, RANGE_LB_INF, "`range_lower_inf`: true when the range has no lower bound."); flag_test!(upper_inf, RANGE_UB_INF, "`range_upper_inf`: true when the range has no upper bound."); +/// The range type of a call: the first argument or the result that has a range type. +fn range_type(call: &Call<'_>) -> u32 { + call.args.iter().chain([&call.ret]).copied().find(|&ty| subtype(ty) != 0).unwrap_or(0) +} + +/// The range type of a call and its two ranges. +fn pair<'a>(call: &Call<'_>, args: &'a [Value]) -> Result<(u32, &'a Range, &'a Range)> { + match args { + [Value::Range(a), Value::Range(b)] => Ok((range_type(call), a, b)), + _ => Err(bad_value()), + } +} + +/// The range of two bounds, as `make_range` makes it from two `RangeBound` values. +fn from_bounds(range: u32, lower: &Bound<'_>, upper: &Bound<'_>) -> Result { + let flag = |set: bool, flag: u8| if set { flag } else { 0 }; + let flags = flag(lower.inclusive, RANGE_LB_INC) + | flag(lower.infinite, RANGE_LB_INF) + | flag(upper.inclusive, RANGE_UB_INC) + | flag(upper.infinite, RANGE_UB_INF); + make_range(range, flags, lower.value.clone(), upper.value.clone()) +} + +/// The bound on the other side of the same value: a lower bound becomes an upper bound and the inclusive flag changes. +fn flip<'a>(bound: &Bound<'a>) -> Bound<'a> { + Bound { + value: bound.value, + infinite: bound.infinite, + inclusive: !bound.inclusive, + lower: !bound.lower, + } +} + +/// `range_overlaps_internal`: true when the ranges have a common value. +fn overlaps(range: u32, a: &Range, b: &Range) -> Result { + if a.is_empty() || b.is_empty() { + return Ok(false); + } + let sub = subtype(range); + let (lower1, upper1) = bounds(a); + let (lower2, upper2) = bounds(b); + Ok((cmp_bounds(sub, &lower1, &lower2)?.is_ge() && cmp_bounds(sub, &lower1, &upper2)?.is_le()) + || (cmp_bounds(sub, &lower2, &lower1)?.is_ge() + && cmp_bounds(sub, &lower2, &upper1)?.is_le())) +} + +/// `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 { + if b.is_empty() { + return Ok(true); + } + if a.is_empty() { + return Ok(false); + } + let sub = subtype(range); + let (lower1, upper1) = bounds(a); + let (lower2, upper2) = bounds(b); + Ok(cmp_bounds(sub, &lower1, &lower2)?.is_le() && cmp_bounds(sub, &upper1, &upper2)?.is_ge()) +} + +/// `range_contained_by_internal`: true when each value of `a` is in `b`. +fn contained_by(range: u32, a: &Range, b: &Range) -> Result { + contains(range, b, a) +} + +/// The order of a bound of `a` and a bound of `b`, or `None` when a range is empty. An empty range is not before, after or next to a range. +fn order_of( + range: u32, + a: &Range, + b: &Range, + upper_a: bool, + upper_b: bool, +) -> Result> { + if a.is_empty() || b.is_empty() { + return Ok(None); + } + let (lower1, upper1) = bounds(a); + let (lower2, upper2) = bounds(b); + let x = if upper_a { upper1 } else { lower1 }; + let y = if upper_b { upper2 } else { lower2 }; + cmp_bounds(subtype(range), &x, &y).map(Some) +} + +/// `range_before_internal`: true when the upper bound of `a` is before the lower bound of `b`. +fn before(range: u32, a: &Range, b: &Range) -> Result { + Ok(order_of(range, a, b, true, false)?.is_some_and(Ordering::is_lt)) +} + +/// `range_after_internal`: true when the lower bound of `a` is after the upper bound of `b`. +fn after(range: u32, a: &Range, b: &Range) -> Result { + Ok(order_of(range, a, b, false, true)?.is_some_and(Ordering::is_gt)) +} + +/// `range_overleft_internal`: true when `a` does not go to the right of `b`. +fn over_left(range: u32, a: &Range, b: &Range) -> Result { + Ok(order_of(range, a, b, true, true)?.is_some_and(Ordering::is_le)) +} + +/// The internal function of the operator `&>`: true when `a` does not go to the left of `b`. +fn over_right(range: u32, a: &Range, b: &Range) -> Result { + Ok(order_of(range, a, b, false, false)?.is_some_and(Ordering::is_ge)) +} + +/// `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 { + Ok(match cmp_bound_values(subtype(range), a, b)? { + Ordering::Less => { + if !matches!(base_type(range), oid::INT4RANGE | oid::INT8RANGE | oid::DATERANGE) { + return Ok(false); + } + from_bounds(range, &flip(a), &flip(b))?.is_empty() + } + Ordering::Equal => a.inclusive != b.inclusive, + Ordering::Greater => false, + }) +} + +/// `range_adjacent_internal`: true when one range stops where the other range starts. An empty range is not adjacent to a range. +fn adjacent(range: u32, a: &Range, b: &Range) -> Result { + if a.is_empty() || b.is_empty() { + return Ok(false); + } + let (lower1, upper1) = bounds(a); + let (lower2, upper2) = bounds(b); + Ok(bounds_adjacent(range, &upper1, &lower2)? || bounds_adjacent(range, &upper2, &lower1)?) +} + +/// `range_union_internal`: the smallest range that contains the two ranges. When `strict` is true, the ranges must overlap or be adjacent. +fn union(range: u32, a: &Range, b: &Range, strict: bool) -> Result { + if a.is_empty() { + return Ok(b.clone()); + } + if b.is_empty() { + return Ok(a.clone()); + } + if strict && !overlaps(range, a, b)? && !adjacent(range, a, b)? { + return Err(Error::new( + SqlState::DATA_EXCEPTION, + "result of range union would not be contiguous", + )); + } + let sub = subtype(range); + let (lower1, upper1) = bounds(a); + let (lower2, upper2) = bounds(b); + let lower = if cmp_bounds(sub, &lower1, &lower2)?.is_lt() { lower1 } else { lower2 }; + let upper = if cmp_bounds(sub, &upper1, &upper2)?.is_gt() { upper1 } else { upper2 }; + from_bounds(range, &lower, &upper) +} + +/// `range_intersect_internal`: the values that are in the two ranges. +fn intersect(range: u32, a: &Range, b: &Range) -> Result { + if !overlaps(range, a, b)? { + return Ok(Range::empty()); + } + let sub = subtype(range); + let (lower1, upper1) = bounds(a); + let (lower2, upper2) = bounds(b); + let lower = if cmp_bounds(sub, &lower1, &lower2)?.is_ge() { lower1 } else { lower2 }; + let upper = if cmp_bounds(sub, &upper1, &upper2)?.is_le() { upper1 } else { upper2 }; + from_bounds(range, &lower, &upper) +} + +/// `range_minus_multi_internal`: the ranges of the values of `a` that are not in `b`, in order. The result has no empty range. +fn minus_multi(range: u32, a: &Range, b: &Range) -> Result> { + if a.is_empty() { + return Ok(Vec::new()); + } + if b.is_empty() { + return Ok(vec![a.clone()]); + } + let sub = subtype(range); + let (lower1, upper1) = bounds(a); + let (lower2, upper2) = bounds(b); + let l1l2 = cmp_bounds(sub, &lower1, &lower2)?; + let l1u2 = cmp_bounds(sub, &lower1, &upper2)?; + let u1l2 = cmp_bounds(sub, &upper1, &lower2)?; + let u1u2 = cmp_bounds(sub, &upper1, &upper2)?; + Ok(if l1l2.is_lt() && u1u2.is_gt() { + vec![ + from_bounds(range, &lower1, &flip(&lower2))?, + from_bounds(range, &flip(&upper2), &upper1)?, + ] + } else if l1u2.is_gt() || u1l2.is_lt() { + vec![a.clone()] + } else if l1l2.is_ge() && u1u2.is_le() { + Vec::new() + } else if l1l2.is_le() && u1l2.is_ge() && u1u2.is_le() { + vec![from_bounds(range, &lower1, &flip(&lower2))?] + } else if l1l2.is_ge() && u1u2.is_ge() && l1u2.is_le() { + vec![from_bounds(range, &flip(&upper2), &upper1)?] + } else { + return Err(Error::new(SqlState::INTERNAL_ERROR, "unexpected case in range_minus_multi")); + }) +} + +/// `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 { + if a.is_empty() || b.is_empty() { + return Ok(a.clone()); + } + let mut parts = minus_multi(range, a, b)?; + if parts.len() > 1 { + return Err(Error::new( + SqlState::DATA_EXCEPTION, + "result of range difference would not be contiguous", + )); + } + Ok(parts.pop().unwrap_or_else(Range::empty)) +} + +/// The kernel of an operator that tests two ranges. +macro_rules! range_test { + ($name:ident, $test:ident, $doc:literal) => { + #[doc = $doc] + fn $name(call: &Call<'_>, args: &[Value]) -> Result { + let (range, a, b) = pair(call, args)?; + Ok(Value::Bool($test(range, a, b)?)) + } + }; +} + +range_test!(overlaps_op, overlaps, "`range_overlaps`: the operator `&&`."); +range_test!(contains_op, contains, "`range_contains`: the operator `@>` of two ranges."); +range_test!( + contained_by_op, + contained_by, + "`range_contained_by`: the operator `<@` of two ranges." +); +range_test!(before_op, before, "`range_before`: the operator `<<`."); +range_test!(after_op, after, "`range_after`: the operator `>>`."); +range_test!(over_left_op, over_left, "`range_overleft`: the operator `&<`."); +range_test!(over_right_op, over_right, "The function of the operator `&>`."); +range_test!(adjacent_op, adjacent, "`range_adjacent`: the operator `-|-`."); + +/// `range_union`: the operator `+`. +fn union_op(call: &Call<'_>, args: &[Value]) -> Result { + let (range, a, b) = pair(call, args)?; + Ok(Value::Range(Box::new(union(range, a, b, true)?))) +} + +/// `range_merge`: the smallest range that contains the two ranges, also when they have a gap between them. +fn merge(call: &Call<'_>, args: &[Value]) -> Result { + let (range, a, b) = pair(call, args)?; + Ok(Value::Range(Box::new(union(range, a, b, false)?))) +} + +/// `range_intersect`: the operator `*`. +fn intersect_op(call: &Call<'_>, args: &[Value]) -> Result { + let (range, a, b) = pair(call, args)?; + Ok(Value::Range(Box::new(intersect(range, a, b)?))) +} + +/// `range_minus`: the operator `-`. +fn minus_op(call: &Call<'_>, args: &[Value]) -> Result { + let (range, a, b) = pair(call, args)?; + Ok(Value::Range(Box::new(minus(range, a, b)?))) +} + +/// `range_minus_multi`: one row for each range of the values of the first range that are not in the second range. +pub(crate) fn range_minus_multi(call: &Call<'_>, args: &[Value]) -> Result>> { + let (range, a, b) = pair(call, args)?; + Ok(minus_multi(range, a, b)?.into_iter().map(|r| vec![Value::Range(Box::new(r))]).collect()) +} + +/// `range_contains_elem`: the operator `@>` of a range and a value. +fn contains_elem_op(call: &Call<'_>, args: &[Value]) -> Result { + let [Value::Range(range), value] = args else { return Err(bad_value()) }; + Ok(Value::Bool(contains_elem(subtype(range_type(call)), range, value)?)) +} + +/// `elem_contained_by_range`: the operator `<@` of a value and a range. +fn elem_contained_op(call: &Call<'_>, args: &[Value]) -> Result { + let [value, Value::Range(range)] = args else { return Err(bad_value()) }; + Ok(Value::Bool(contains_elem(subtype(range_type(call)), range, value)?)) +} + +/// `range_contains_elem_internal`: true when the value is in the range. +fn contains_elem(sub: u32, range: &Range, value: &Value) -> Result { + if range.is_empty() { + return Ok(false); + } + let (lower, upper) = bounds(range); + if !lower.infinite { + match compare(sub, lower.value, sub, value)? { + Ordering::Greater => return Ok(false), + Ordering::Equal if !lower.inclusive => return Ok(false), + _ => {} + } + } + if !upper.infinite { + match compare(sub, upper.value, sub, value)? { + Ordering::Less => return Ok(false), + Ordering::Equal if !upper.inclusive => return Ok(false), + _ => {} + } + } + Ok(true) +} + +/// The subtype difference functions of the built-in range types: the first value minus the second value as `float8`. A timestamp difference is in seconds. +fn subdiff(call: &Call<'_>, args: &[Value]) -> Result { + Ok(Value::Float8(match args { + [Value::Int4(a), Value::Int4(b)] | [Value::Date(a), Value::Date(b)] => { + f64::from(*a) - f64::from(*b) + } + [Value::Int8(a), Value::Int8(b)] => *a as f64 - *b as f64, + [Value::Timestamp(a), Value::Timestamp(b)] + | [Value::TimestampTz(a), Value::TimestampTz(b)] => (*a as f64 - *b as f64) / 1_000_000.0, + [Value::Numeric(a), Value::Numeric(b)] => { + let diff = a.sub(b).map_err(type_error)?; + return crate::cast::numeric_to_float(call, &[Value::Numeric(diff)]); + } + _ => return Err(bad_value()), + })) +} + /// The kernel of a range function by its `prosrc`. pub(crate) fn by_src(src: &str) -> Option { Some(match src { @@ -273,6 +589,22 @@ pub(crate) fn by_src(src: &str) -> Option { "range_upper_inc" => upper_inc, "range_lower_inf" => lower_inf, "range_upper_inf" => upper_inf, + "range_overlaps" => overlaps_op, + "range_contains" => contains_op, + "range_contained_by" => contained_by_op, + "range_contains_elem" => contains_elem_op, + "elem_contained_by_range" => elem_contained_op, + "range_before" => before_op, + "range_after" => after_op, + "range_overleft" => over_left_op, + "range_adjacent" => adjacent_op, + "range_union" => union_op, + "range_merge" => merge, + "range_intersect" => intersect_op, + "range_minus" => minus_op, + "int4range_subdiff" | "int8range_subdiff" | "numrange_subdiff" | "daterange_subdiff" + | "tsrange_subdiff" | "tstzrange_subdiff" => subdiff, + _ if src == concat!("range_over", "right") => over_right_op, _ => return None, }) } @@ -316,4 +648,25 @@ mod tests { Range { flags: RANGE_LB_INC | RANGE_UB_INF, lower: Value::Int4(1), upper: Value::Null }; assert_eq!(range_order(oid::INT4, &open, &a).unwrap(), Ordering::Greater); } + + #[test] + fn operators() { + let r = oid::INT4RANGE; + let a = int4(RANGE_LB_INC, 1, 5).unwrap(); + let b = int4(RANGE_LB_INC, 5, 8).unwrap(); + let c = int4(RANGE_LB_INC, 3, 8).unwrap(); + assert!(adjacent(r, &a, &b).unwrap() && adjacent(r, &b, &a).unwrap()); + assert!(!adjacent(r, &a, &c).unwrap() && overlaps(r, &a, &c).unwrap()); + assert!(before(r, &a, &b).unwrap() && !before(r, &a, &c).unwrap()); + let both = union(r, &a, &b, true).unwrap(); + assert_eq!((both.lower, both.upper), (Value::Int4(1), Value::Int4(8))); + let far = int4(RANGE_LB_INC, 7, 9).unwrap(); + assert_eq!(union(r, &a, &far, true).unwrap_err().state(), SqlState::DATA_EXCEPTION); + assert!(intersect(r, &a, &b).unwrap().is_empty()); + let wide = int4(RANGE_LB_INC, 0, 10).unwrap(); + assert_eq!(minus_multi(r, &wide, &c).unwrap().len(), 2); + assert_eq!(minus(r, &wide, &c).unwrap_err().state(), SqlState::DATA_EXCEPTION); + assert!(contains_elem(oid::INT4, &a, &Value::Int4(4)).unwrap()); + assert!(!contains_elem(oid::INT4, &a, &Value::Int4(5)).unwrap()); + } } diff --git a/crates/rupg-func/src/srf.rs b/crates/rupg-func/src/srf.rs index 272e2120..c3f8c6e4 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`, `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`, `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. @@ -31,6 +31,7 @@ pub fn set_kernel(func: u32) -> Option { "pg_options_to_table" => options_to_table, "aclexplode" => crate::acl::aclexplode, "pg_snapshot_xip" => crate::snapshot::snapshot_xip, + "range_minus_multi" => crate::range::range_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 0c1ed03d..3776b3fc 100644 --- a/crates/rupg-session/src/connection.rs +++ b/crates/rupg-session/src/connection.rs @@ -1838,6 +1838,12 @@ mod tests { big_stack(|| script(include_str!("connection/range.test"))); } + /// The operators of the ranges: overlap, containment of a range and of a value, the position operators, adjacency, union, intersection and difference with the errors of a result that is not contiguous, `range_merge`, `range_minus_multi`, the subtype difference functions and the operators in a query with a parameter. `connection/range_ops.test` is the output of PostgreSQL 19 for the same script in a new database. + #[test] + fn range_ops() { + big_stack(|| script(include_str!("connection/range_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/range_ops.test b/crates/rupg-session/src/connection/range_ops.test new file mode 100644 index 00000000..1d834ae4 --- /dev/null +++ b/crates/rupg-session/src/connection/range_ops.test @@ -0,0 +1,194 @@ +> SET TimeZone = 'UTC' +SET +> SET DateStyle = 'ISO, MDY' +SET +> SELECT int4range(1,5) && int4range(3,8), int4range(1,5) && int4range(5,8), int4range(1,5,'[]') && int4range(5,8), 'empty'::int4range && int4range(1,5), '(,)'::int4range && 'empty'::int4range +fields ?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,f +SELECT 1 +> SELECT numrange(1,5) && numrange(5,8), numrange(1,5,'[]') && numrange(5,8,'[]'), numrange(NULL,5) && numrange(NULL,1), numrange(5,NULL) && numrange(NULL,1) +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1 +row f,t,t,f +SELECT 1 +> SELECT int4range(1,10) @> int4range(2,5), int4range(1,10) @> int4range(2,11), int4range(1,10) @> 'empty'::int4range, 'empty'::int4range @> 'empty'::int4range, 'empty'::int4range @> int4range(1,2), '(,)'::int4range @> int4range(NULL,3) +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,t,f,t +SELECT 1 +> SELECT int4range(2,5) <@ int4range(1,10), int4range(0,5) <@ int4range(1,10), 'empty'::int4range <@ int4range(1,10), int4range(1,10) <@ '(,)'::int4range +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1 +row t,f,t,t +SELECT 1 +> SELECT int4range(1,10) @> 5, int4range(1,10) @> 10, int4range(1,10) @> 1, int4range(1,10) @> 0, 'empty'::int4range @> 1, '(,)'::int4range @> 1, int4range(NULL,10) @> -2147483648 +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,t,f,f,t,t +SELECT 1 +> SELECT 5 <@ int4range(1,10), 10 <@ int4range(1,10), numrange(1,2) @> 1.5, 2.0 <@ numrange(1,2,'[]'), numrange(1,2,'(]') @> 1::numeric +fields ?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,f +SELECT 1 +> SELECT numrange(1,10) @> 5 +ERROR 42883 operator does not exist: numrange @> integer at 23 +> SELECT daterange('2020-01-01','2020-02-01') @> '2020-01-15'::date, daterange('2020-01-01','2020-02-01') @> date '2020-02-01', tsrange('2020-01-01','2020-02-01') @> '2020-01-15'::timestamp +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1 +row t,f,t +SELECT 1 +> SELECT int4range(1,5) << int4range(5,8), int4range(1,5,'[]') << int4range(5,8), int4range(1,5) << int4range(NULL,8), 'empty'::int4range << int4range(1,2), int4range(1,2) << 'empty'::int4range +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1 +row t,f,f,f,f +SELECT 1 +> SELECT int4range(5,8) >> int4range(1,5), int4range(5,8) >> int4range(1,5,'[]'), int4range(NULL,8) >> int4range(1,5), numrange(5,8) >> numrange(1,5,'[]'), numrange(5,8,'()') >> numrange(1,5,'[]') +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1 +row t,f,f,f,t +SELECT 1 +> SELECT int4range(1,5) &< int4range(3,8), int4range(1,9) &< int4range(3,8), int4range(1,8) &< int4range(3,8), int4range(1,5) &< 'empty'::int4range, numrange(1,8,'[]') &< numrange(3,8), numrange(1,8) &< numrange(3,8,'[]') +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,f,t +SELECT 1 +> SELECT int4range(3,5) &> int4range(1,8), int4range(0,5) &> int4range(1,8), numrange(1,5,'()') &> numrange(1,5), numrange(1,5) &> numrange(1,5,'()'), int4range(NULL,5) &> int4range(1,8) +fields ?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,f +SELECT 1 +> SELECT int4range(1,5) -|- int4range(5,8), int4range(5,8) -|- int4range(1,5), int4range(1,5) -|- int4range(6,8), int4range(1,5,'[]') -|- int4range(6,8), int4range(1,5) -|- int4range(4,8), 'empty'::int4range -|- int4range(1,5) +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,t,f,t,f,f +SELECT 1 +> SELECT numrange(1,5) -|- numrange(5,8), numrange(1,5,'[]') -|- numrange(5,8), numrange(1,5,'[]') -|- numrange(5,8,'()'), numrange(1,5,'[)') -|- numrange(5,8,'()'), numrange(1,5) -|- numrange(5.1,8) +fields ?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,f +SELECT 1 +> SELECT daterange('2020-01-01','2020-01-05') -|- daterange('2020-01-05','2020-01-09'), daterange('2020-01-01','2020-01-05','[]') -|- daterange('2020-01-06','2020-01-09'), int4range(NULL,5) -|- int4range(5,NULL), numrange(NULL,NULL) -|- numrange(1,2) +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1 +row t,t,t,f +SELECT 1 +> SELECT int4range(1,5) + int4range(3,8), int4range(1,5) + int4range(5,8), int4range(5,8) + int4range(1,5), int4range(1,5) + 'empty'::int4range, 'empty'::int4range + int4range(1,5), int4range(NULL,5) + int4range(3,NULL) +fields ?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1 +row [1,8),[1,8),[1,8),[1,5),[1,5),(,) +SELECT 1 +> SELECT numrange(1,5) + numrange(5,8), numrange(1,5,'[]') + numrange(2,3), numrange(1,5,'()') + numrange(1,5,'[)') +fields ?column?:3906:-1:-1,?column?:3906:-1:-1,?column?:3906:-1:-1 +row [1,8),[1,5],[1,5) +SELECT 1 +> SELECT int4range(1,5) + int4range(6,8) +ERROR 22000 result of range union would not be contiguous +> SELECT numrange(1,5) + numrange(5,8,'()') +ERROR 22000 result of range union would not be contiguous +> SELECT range_merge(int4range(1,5), int4range(10,20)), range_merge(int4range(10,20), int4range(1,5)), range_merge('empty'::int4range, int4range(1,5)), range_merge(numrange(1,2), numrange(NULL,0)), range_merge(int4range(1,5), int4range(2,3)) +fields range_merge:3904:-1:-1,range_merge:3904:-1:-1,range_merge:3904:-1:-1,range_merge:3906:-1:-1,range_merge:3904:-1:-1 +row [1,20),[1,20),[1,5),(,2),[1,5) +SELECT 1 +> SELECT int4range(1,5) * int4range(3,8), int4range(1,5) * int4range(5,8), int4range(1,5,'[]') * int4range(5,8), int4range(NULL,5) * int4range(3,NULL), int4range(1,5) * 'empty'::int4range, '(,)'::int4range * '(,)'::int4range +fields ?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1 +row [3,5),empty,[5,6),[3,5),empty,(,) +SELECT 1 +> SELECT numrange(1,5,'[]') * numrange(1,5,'()'), numrange(1,5,'()') * numrange(0,5,'[]'), daterange('2020-01-01','2020-01-10') * daterange('2020-01-05',NULL) +fields ?column?:3906:-1:-1,?column?:3906:-1:-1,?column?:3912:-1:-1 +row (1,5),(1,5),[2020-01-05,2020-01-10) +SELECT 1 +> SELECT int4range(1,10) - int4range(5,15), int4range(1,10) - int4range(0,5), int4range(1,10) - int4range(0,15), int4range(1,10) - int4range(20,30), int4range(1,10) - 'empty'::int4range, 'empty'::int4range - int4range(1,2), int4range(1,10) - int4range(1,10) +fields ?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1 +row [1,5),[5,10),empty,[1,10),[1,10),empty,empty +SELECT 1 +> SELECT numrange(1,10) - numrange(5,15), numrange(1,10,'[]') - numrange(0,5,'[]'), numrange(1,10) - numrange(10,15), numrange(1,10,'[]') - numrange(10,15), int4range(NULL,10) - int4range(5,NULL) +fields ?column?:3906:-1:-1,?column?:3906:-1:-1,?column?:3906:-1:-1,?column?:3906:-1:-1,?column?:3904:-1:-1 +row [1,5),(5,10],[1,10),[1,10),(,5) +SELECT 1 +> SELECT int4range(1,10) - int4range(3,5) +ERROR 22000 result of range difference would not be contiguous +> SELECT int4range(1,10) - int4range(1,5), int4range(1,10) - int4range(5,10), numrange(1,10) - numrange(5,10,'(]') +fields ?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3906:-1:-1 +row [5,10),[1,5),[1,5] +SELECT 1 +> SELECT numrange(1,10) - numrange(1,5,'(]') +ERROR 22000 result of range difference would not be contiguous +> SELECT * FROM range_minus_multi(int4range(1,10), int4range(3,5)) +fields range_minus_multi:3904:-1:-1 +row [1,3) +row [5,10) +SELECT 2 +> SELECT * FROM range_minus_multi(int4range(1,10), int4range(5,15)) +fields range_minus_multi:3904:-1:-1 +row [1,5) +SELECT 1 +> SELECT * FROM range_minus_multi(int4range(1,10), int4range(0,15)) +fields range_minus_multi:3904:-1:-1 +SELECT 0 +> SELECT * FROM range_minus_multi(int4range(1,10), 'empty'::int4range) +fields range_minus_multi:3904:-1:-1 +row [1,10) +SELECT 1 +> SELECT * FROM range_minus_multi('empty'::int4range, int4range(1,10)) +fields range_minus_multi:3904:-1:-1 +SELECT 0 +> SELECT * FROM range_minus_multi(numrange(1,10,'[]'), numrange(3,5,'[]')) +fields range_minus_multi:3906:-1:-1 +row [1,3) +row (5,10] +SELECT 2 +> SELECT range_minus_multi(int4range(1,10), int4range(20,30)), range_minus_multi(int4range(NULL,NULL), int4range(0,5)) +fields range_minus_multi:3904:-1:-1,range_minus_multi:3904:-1:-1 +row [1,10),(,0) +row NULL,[5,) +SELECT 2 +> SELECT int4range(1,5) && NULL, NULL::int4range @> 1, int4range(1,5) @> NULL::int4, int4range(1,5) + NULL +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:16:1:-1,?column?:3904:-1:-1 +row NULL,NULL,NULL,NULL +SELECT 1 +> SELECT int4range(1,5) && numrange(1,5) +ERROR 42883 operator does not exist: int4range && numrange at 23 +> SELECT int4range(1,5) && '[3,4)', '[3,4)' <@ int4range(1,5), int4range(1,5) + '[5,7)', int4range(1,5) - '[2,7)', '[1,2)'::int4range * '[1,3)' +fields ?column?:16:1:-1,?column?:16:1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1,?column?:3904:-1:-1 +row t,t,[1,7),[1,2),[1,2) +SELECT 1 +> SELECT int8range(1,10) * int8range(5,20), int8range(1,10) @> 5::int8 +fields ?column?:3926:-1:-1,?column?:16:1:-1 +row [5,10),t +SELECT 1 +> SELECT int8range(1,10) @> 5 +ERROR 42883 operator does not exist: int8range @> integer at 24 +> SELECT tstzrange('2020-01-01 00:00+00','2020-01-02 00:00+00') && tstzrange('2020-01-01 12:00+00','2020-01-03 00:00+00'), tstzrange('2020-01-01 00:00+00','2020-01-02 00:00+00') * tstzrange('2020-01-01 12:00+00','2020-01-03 00:00+00') +fields ?column?:16:1:-1,?column?:3910:-1:-1 +row t,["2020-01-01 12:00:00+00","2020-01-02 00:00:00+00") +SELECT 1 +> SELECT int4range_subdiff(10, 3), int8range_subdiff(3, 10), numrange_subdiff(10.5, 3.25), daterange_subdiff('2020-03-01', '2020-01-01'), tsrange_subdiff('2020-01-02 00:00:01.5', '2020-01-01'), tstzrange_subdiff('2020-01-02 00:00+00', '2020-01-01 00:00+00') +fields int4range_subdiff:701:8:-1,int8range_subdiff:701:8:-1,numrange_subdiff:701:8:-1,daterange_subdiff:701:8:-1,tsrange_subdiff:701:8:-1,tstzrange_subdiff:701:8:-1 +row 7,-7,7.25,60,86401.5,86400 +SELECT 1 +> SELECT int4range_subdiff(2147483647, -2147483648), int8range_subdiff(9223372036854775807, -9223372036854775808), numrange_subdiff(1e300, -1e300) +fields int4range_subdiff:701:8:-1,int8range_subdiff:701:8:-1,numrange_subdiff:701:8:-1 +row 4294967295,1.8446744073709552e+19,2e+300 +SELECT 1 +> SELECT range_overlaps(int4range(1,5), int4range(3,8)), range_contains(int4range(1,5), int4range(2,3)), range_contained_by(int4range(1,5), int4range(2,3)), range_contains_elem(int4range(1,5), 3), elem_contained_by_range(3, int4range(1,5)) +fields range_overlaps:16:1:-1,range_contains:16:1:-1,range_contained_by:16:1:-1,range_contains_elem:16:1:-1,elem_contained_by_range:16:1:-1 +row t,t,f,t,t +SELECT 1 +> SELECT range_before(int4range(1,5), int4range(6,8)), range_after(int4range(1,5), int4range(6,8)), range_overleft(int4range(1,5), int4range(6,8)), range_adjacent(int4range(1,5), int4range(5,8)), range_union(int4range(1,5), int4range(5,8)), range_intersect(int4range(1,5), int4range(3,8)), range_minus(int4range(1,5), int4range(3,8)) +fields range_before:16:1:-1,range_after:16:1:-1,range_overleft:16:1:-1,range_adjacent:16:1:-1,range_union:3904:-1:-1,range_intersect:3904:-1:-1,range_minus:3904:-1:-1 +row t,f,t,t,[1,8),[3,5),[1,3) +SELECT 1 +> SELECT r, r && int4range(3,6) AS ov, r @> 4 AS has4, r * int4range(3,6) AS isect FROM (VALUES (int4range(1,4)), (int4range(5,9)), ('empty'::int4range), (int4range(NULL,2)), (NULL)) v(r) ORDER BY r +fields r:3904:-1:-1,ov:16:1:-1,has4:16:1:-1,isect:3904:-1:-1 +row empty,f,f,empty +row (,2),f,f,empty +row [1,4),t,f,[3,4) +row [5,9),t,f,[5,6) +row NULL,NULL,NULL,NULL +SELECT 5 +> SELECT a, b, a -|- b AS adj, a << b AS bef, a >> b AS aft FROM (VALUES (int4range(1,3)), (int4range(3,5)), (int4range(6,9))) x(a), (VALUES (int4range(1,3)), (int4range(3,5)), (int4range(6,9))) y(b) ORDER BY a, b +fields a:3904:-1:-1,b:3904:-1:-1,adj:16:1:-1,bef:16:1:-1,aft:16:1:-1 +row [1,3),[1,3),f,f,f +row [1,3),[3,5),t,t,f +row [1,3),[6,9),f,t,f +row [3,5),[1,3),t,f,t +row [3,5),[3,5),f,f,f +row [3,5),[6,9),f,t,f +row [6,9),[1,3),f,f,t +row [6,9),[3,5),f,f,t +row [6,9),[6,9),f,f,f +SELECT 9 +P q1|SELECT $1::int4range && $2::int4range, $1::int4range + $2::int4range, $1::int4range * $2::int4range +X q1|[1,5)|[3,8) +row t,[1,8),[3,5) +SELECT 1 +X q1|[1,5)|empty +row f,[1,5),empty +SELECT 1