1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-22 12:22:45 +03:00

Add range_minus_multi and multirange_minus_multi functions

The existing range_minus function raises an exception when the range is
"split", because then the result can't be represented by a single range.
For example '[0,10)'::int4range - '[4,5)' would be '[0,4)' and '[5,10)'.

This commit adds new set-returning functions so that callers can get
results even in the case of splits. There is no risk of an exception for
multiranges, but a set-returning function lets us handle them the same
way we handle ranges.

Both functions return zero results if the subtraction would give an
empty range/multirange.

The main use-case for these functions is to implement UPDATE/DELETE FOR
PORTION OF, which must compute the application-time of "temporal
leftovers": the part of history in an updated/deleted row that was not
changed. To preserve the untouched history, we will implicitly insert
one record for each result returned by range/multirange_minus_multi.
Using a set-returning function will also let us support user-defined
types for application-time update/delete in the future.

Author: Paul A. Jungwirth <pj@illuminatedcomputing.com>
Reviewed-by: Peter Eisentraut <peter@eisentraut.org>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/ec498c3d-5f2b-48ec-b989-5561c8aa2024%40illuminatedcomputing.com
This commit is contained in:
Peter Eisentraut
2025-11-22 09:40:00 +01:00
parent 0dceba21d7
commit 5eed8ce50c
10 changed files with 493 additions and 1 deletions

View File

@@ -842,6 +842,29 @@
<returnvalue>[1,4)</returnvalue> <returnvalue>[1,4)</returnvalue>
</para></entry> </para></entry>
</row> </row>
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
<primary>range_minus_multi</primary>
</indexterm>
<function>range_minus_multi</function> ( <type>anyrange</type>, <type>anyrange</type> )
<returnvalue>setof anyrange</returnvalue>
</para>
<para>
Returns the non-empty range(s) remaining after subtracting the second range from the first.
One row is returned for each range, so if the second range splits the first into two parts,
there will be two results. If the subtraction yields an empty range, no rows are returned.
</para>
<para>
<literal>range_minus_multi('[0,10)'::int4range, '[3,4)'::int4range)</literal>
<returnvalue></returnvalue>
<programlisting>
[0,3)
[4,10)
</programlisting>
</para></entry>
</row>
</tbody> </tbody>
</tgroup> </tgroup>
</table> </table>
@@ -1041,6 +1064,25 @@
</programlisting> </programlisting>
</para></entry> </para></entry>
</row> </row>
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
<primary>multirange_minus_multi</primary>
</indexterm>
<function>multirange_minus_multi</function> ( <type>anymultirange</type>, <type>anymultirange</type> )
<returnvalue>setof anymultirange</returnvalue>
</para>
<para>
Returns the non-empty multirange(s) remaining after subtracting the second multirange from the first.
If the subtraction yields an empty multirange, no rows are returned.
Two rows are never returned, because a single multirange can always accommodate any result.
</para>
<para>
<literal>multirange_minus_multi('{[0,10)}'::int4multirange, '{[3,4)}'::int4multirange)</literal>
<returnvalue>{[0,3), [4,10)}</returnvalue>
</para></entry>
</row>
</tbody> </tbody>
</tgroup> </tgroup>
</table> </table>

View File

@@ -1227,6 +1227,77 @@ multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3); return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
} }
/*
* multirange_minus_multi - like multirange_minus but returning the result as a
* SRF, with no rows if the result would be empty.
*/
Datum
multirange_minus_multi(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
MemoryContext oldcontext;
if (!SRF_IS_FIRSTCALL())
{
/* We never have more than one result */
funcctx = SRF_PERCALL_SETUP();
SRF_RETURN_DONE(funcctx);
}
else
{
MultirangeType *mr1;
MultirangeType *mr2;
Oid mltrngtypoid;
TypeCacheEntry *typcache;
TypeCacheEntry *rangetyp;
int32 range_count1;
int32 range_count2;
RangeType **ranges1;
RangeType **ranges2;
MultirangeType *mr;
funcctx = SRF_FIRSTCALL_INIT();
/*
* switch to memory context appropriate for multiple function calls
*/
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* get args, detoasting into multi-call memory context */
mr1 = PG_GETARG_MULTIRANGE_P(0);
mr2 = PG_GETARG_MULTIRANGE_P(1);
mltrngtypoid = MultirangeTypeGetOid(mr1);
typcache = lookup_type_cache(mltrngtypoid, TYPECACHE_MULTIRANGE_INFO);
if (typcache->rngtype == NULL)
elog(ERROR, "type %u is not a multirange type", mltrngtypoid);
rangetyp = typcache->rngtype;
if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
mr = mr1;
else
{
multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
mr = multirange_minus_internal(mltrngtypoid,
rangetyp,
range_count1,
ranges1,
range_count2,
ranges2);
}
MemoryContextSwitchTo(oldcontext);
funcctx = SRF_PERCALL_SETUP();
if (MultirangeIsEmpty(mr))
SRF_RETURN_DONE(funcctx);
else
SRF_RETURN_NEXT(funcctx, MultirangeTypePGetDatum(mr));
}
}
/* multirange intersection */ /* multirange intersection */
Datum Datum
multirange_intersect(PG_FUNCTION_ARGS) multirange_intersect(PG_FUNCTION_ARGS)

View File

@@ -31,6 +31,7 @@
#include "postgres.h" #include "postgres.h"
#include "common/hashfn.h" #include "common/hashfn.h"
#include "funcapi.h"
#include "libpq/pqformat.h" #include "libpq/pqformat.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "nodes/makefuncs.h" #include "nodes/makefuncs.h"
@@ -1216,6 +1217,172 @@ range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeT
return false; return false;
} }
/*
* range_minus_multi - like range_minus but as a SRF to accommodate splits,
* with no result rows if the result would be empty.
*/
Datum
range_minus_multi(PG_FUNCTION_ARGS)
{
struct range_minus_multi_fctx
{
RangeType *rs[2];
int n;
};
FuncCallContext *funcctx;
struct range_minus_multi_fctx *fctx;
MemoryContext oldcontext;
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
RangeType *r1;
RangeType *r2;
Oid rngtypid;
TypeCacheEntry *typcache;
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
/*
* switch to memory context appropriate for multiple function calls
*/
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
r1 = PG_GETARG_RANGE_P(0);
r2 = PG_GETARG_RANGE_P(1);
/* Different types should be prevented by ANYRANGE matching rules */
if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
elog(ERROR, "range types do not match");
/* allocate memory for user context */
fctx = (struct range_minus_multi_fctx *) palloc(sizeof(struct range_minus_multi_fctx));
/*
* Initialize state. We can't store the range typcache in fn_extra
* because the caller uses that for the SRF state.
*/
rngtypid = RangeTypeGetOid(r1);
typcache = lookup_type_cache(rngtypid, TYPECACHE_RANGE_INFO);
if (typcache->rngelemtype == NULL)
elog(ERROR, "type %u is not a range type", rngtypid);
range_minus_multi_internal(typcache, r1, r2, fctx->rs, &fctx->n);
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP();
fctx = funcctx->user_fctx;
if (funcctx->call_cntr < fctx->n)
{
/*
* We must keep these on separate lines because SRF_RETURN_NEXT does
* call_cntr++:
*/
RangeType *ret = fctx->rs[funcctx->call_cntr];
SRF_RETURN_NEXT(funcctx, RangeTypePGetDatum(ret));
}
else
/* do when there is no more left */
SRF_RETURN_DONE(funcctx);
}
/*
* range_minus_multi_internal - Subtracts r2 from r1
*
* The subtraction can produce zero, one, or two resulting ranges. We return
* the results by setting outputs and outputn to the ranges remaining and their
* count (respectively). The results will never contain empty ranges and will
* be ordered. Caller should set outputs to a two-element array of RangeType
* pointers.
*/
void
range_minus_multi_internal(TypeCacheEntry *typcache, RangeType *r1,
RangeType *r2, RangeType **outputs, int *outputn)
{
int cmp_l1l2,
cmp_l1u2,
cmp_u1l2,
cmp_u1u2;
RangeBound lower1,
lower2;
RangeBound upper1,
upper2;
bool empty1,
empty2;
range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
if (empty1)
{
/* if r1 is empty then r1 - r2 is empty, so return zero results */
*outputn = 0;
return;
}
else if (empty2)
{
/* r2 is empty so the result is just r1 (which we know is not empty) */
outputs[0] = r1;
*outputn = 1;
return;
}
/*
* Use the same logic as range_minus_internal, but support the split case
*/
cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
cmp_u1l2 = range_cmp_bounds(typcache, &upper1, &lower2);
cmp_u1u2 = range_cmp_bounds(typcache, &upper1, &upper2);
if (cmp_l1l2 < 0 && cmp_u1u2 > 0)
{
lower2.inclusive = !lower2.inclusive;
lower2.lower = false; /* it will become the upper bound */
outputs[0] = make_range(typcache, &lower1, &lower2, false, NULL);
upper2.inclusive = !upper2.inclusive;
upper2.lower = true; /* it will become the lower bound */
outputs[1] = make_range(typcache, &upper2, &upper1, false, NULL);
*outputn = 2;
}
else if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
{
outputs[0] = r1;
*outputn = 1;
}
else if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
{
*outputn = 0;
}
else if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
{
lower2.inclusive = !lower2.inclusive;
lower2.lower = false; /* it will become the upper bound */
outputs[0] = make_range(typcache, &lower1, &lower2, false, NULL);
*outputn = 1;
}
else if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
{
upper2.inclusive = !upper2.inclusive;
upper2.lower = true; /* it will become the lower bound */
outputs[0] = make_range(typcache, &upper2, &upper1, false, NULL);
*outputn = 1;
}
else
{
elog(ERROR, "unexpected case in range_minus_multi");
}
}
/* range -> range aggregate functions */ /* range -> range aggregate functions */
Datum Datum

View File

@@ -57,6 +57,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 202511181 #define CATALOG_VERSION_NO 202511221
#endif #endif

View File

@@ -10939,6 +10939,10 @@
{ oid => '3869', { oid => '3869',
proname => 'range_minus', prorettype => 'anyrange', proname => 'range_minus', prorettype => 'anyrange',
proargtypes => 'anyrange anyrange', prosrc => 'range_minus' }, proargtypes => 'anyrange anyrange', prosrc => 'range_minus' },
{ oid => '8412', descr => 'remove portion from range',
proname => 'range_minus_multi', prorows => '2',
proretset => 't', prorettype => 'anyrange',
proargtypes => 'anyrange anyrange', prosrc => 'range_minus_multi' },
{ oid => '3870', descr => 'less-equal-greater', { oid => '3870', descr => 'less-equal-greater',
proname => 'range_cmp', prorettype => 'int4', proname => 'range_cmp', prorettype => 'int4',
proargtypes => 'anyrange anyrange', prosrc => 'range_cmp' }, proargtypes => 'anyrange anyrange', prosrc => 'range_cmp' },
@@ -11229,6 +11233,10 @@
{ oid => '4271', { oid => '4271',
proname => 'multirange_minus', prorettype => 'anymultirange', proname => 'multirange_minus', prorettype => 'anymultirange',
proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' }, proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
{ oid => '8411', descr => 'remove portion from multirange',
proname => 'multirange_minus_multi', prorows => '1',
proretset => 't', prorettype => 'anymultirange',
proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus_multi' },
{ oid => '4272', { oid => '4272',
proname => 'multirange_intersect', prorettype => 'anymultirange', proname => 'multirange_intersect', prorettype => 'anymultirange',
proargtypes => 'anymultirange anymultirange', proargtypes => 'anymultirange anymultirange',

View File

@@ -164,5 +164,7 @@ extern RangeType *make_empty_range(TypeCacheEntry *typcache);
extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
const RangeType *r2, RangeType **output1, const RangeType *r2, RangeType **output1,
RangeType **output2); RangeType **output2);
extern void range_minus_multi_internal(TypeCacheEntry *typcache, RangeType *r1,
RangeType *r2, RangeType **outputs, int *outputn);
#endif /* RANGETYPES_H */ #endif /* RANGETYPES_H */

View File

@@ -2200,6 +2200,122 @@ SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0
{[1,2),[4,5)} {[1,2),[4,5)}
(1 row) (1 row)
-- multirange_minus_multi
SELECT multirange_minus_multi(nummultirange(), nummultirange());
multirange_minus_multi
------------------------
(0 rows)
SELECT multirange_minus_multi(nummultirange(), nummultirange(numrange(1,2)));
multirange_minus_multi
------------------------
(0 rows)
SELECT multirange_minus_multi(nummultirange(numrange(1,2)), nummultirange());
multirange_minus_multi
------------------------
{[1,2)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(3,4)), nummultirange());
multirange_minus_multi
------------------------
{[1,2),[3,4)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,2)), nummultirange(numrange(1,2)));
multirange_minus_multi
------------------------
(0 rows)
SELECT multirange_minus_multi(nummultirange(numrange(1,2)), nummultirange(numrange(2,4)));
multirange_minus_multi
------------------------
{[1,2)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,2)), nummultirange(numrange(3,4)));
multirange_minus_multi
------------------------
{[1,2)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,4)), nummultirange(numrange(1,2)));
multirange_minus_multi
------------------------
{[2,4)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,4)), nummultirange(numrange(2,3)));
multirange_minus_multi
------------------------
{[1,2),[3,4)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,4)), nummultirange(numrange(0,8)));
multirange_minus_multi
------------------------
(0 rows)
SELECT multirange_minus_multi(nummultirange(numrange(1,4)), nummultirange(numrange(0,2)));
multirange_minus_multi
------------------------
{[2,4)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,8)), nummultirange(numrange(0,2), numrange(3,4)));
multirange_minus_multi
------------------------
{[2,3),[4,8)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,8)), nummultirange(numrange(2,3), numrange(5,null)));
multirange_minus_multi
------------------------
{[1,2),[3,5)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(4,5)), nummultirange(numrange(-2,0)));
multirange_minus_multi
------------------------
{[1,2),[4,5)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(4,5)), nummultirange(numrange(2,4)));
multirange_minus_multi
------------------------
{[1,2),[4,5)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(4,5)), nummultirange(numrange(3,5)));
multirange_minus_multi
------------------------
{[1,2)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(4,5)), nummultirange(numrange(0,9)));
multirange_minus_multi
------------------------
(0 rows)
SELECT multirange_minus_multi(nummultirange(numrange(1,3), numrange(4,5)), nummultirange(numrange(2,9)));
multirange_minus_multi
------------------------
{[1,2)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(4,5)), nummultirange(numrange(8,9)));
multirange_minus_multi
------------------------
{[1,2),[4,5)}
(1 row)
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(4,5)), nummultirange(numrange(-2,0), numrange(8,9)));
multirange_minus_multi
------------------------
{[1,2),[4,5)}
(1 row)
-- intersection -- intersection
SELECT nummultirange() * nummultirange(); SELECT nummultirange() * nummultirange();
?column? ?column?

View File

@@ -481,6 +481,60 @@ select range_minus(numrange(10.1,12.2,'[]'), numrange(0.0,120.2,'(]'));
empty empty
(1 row) (1 row)
select range_minus_multi('empty'::numrange, numrange(2.0, 3.0));
range_minus_multi
-------------------
(0 rows)
select range_minus_multi(numrange(1.1, 2.2), 'empty'::numrange);
range_minus_multi
-------------------
[1.1,2.2)
(1 row)
select range_minus_multi(numrange(1.1, 2.2), numrange(2.0, 3.0));
range_minus_multi
-------------------
[1.1,2.0)
(1 row)
select range_minus_multi(numrange(1.1, 2.2), numrange(2.2, 3.0));
range_minus_multi
-------------------
[1.1,2.2)
(1 row)
select range_minus_multi(numrange(1.1, 2.2,'[]'), numrange(2.0, 3.0));
range_minus_multi
-------------------
[1.1,2.0)
(1 row)
select range_minus_multi(numrange(1.0, 3.0), numrange(1.5, 2.0));
range_minus_multi
-------------------
[1.0,1.5)
[2.0,3.0)
(2 rows)
select range_minus_multi(numrange(10.1,12.2,'[]'), numrange(110.0,120.2,'(]'));
range_minus_multi
-------------------
[10.1,12.2]
(1 row)
select range_minus_multi(numrange(10.1,12.2,'[]'), numrange(0.0,120.2,'(]'));
range_minus_multi
-------------------
(0 rows)
select range_minus_multi(numrange(1.0,3.0,'[]'), numrange(1.5,2.0,'(]'));
range_minus_multi
-------------------
[1.0,1.5]
(2.0,3.0]
(2 rows)
select numrange(4.5, 5.5, '[]') && numrange(5.5, 6.5); select numrange(4.5, 5.5, '[]') && numrange(5.5, 6.5);
?column? ?column?
---------- ----------

View File

@@ -414,6 +414,28 @@ SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9)
SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9)); SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9)); SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
-- multirange_minus_multi
SELECT multirange_minus_multi(nummultirange(), nummultirange());
SELECT multirange_minus_multi(nummultirange(), nummultirange(numrange(1,2)));
SELECT multirange_minus_multi(nummultirange(numrange(1,2)), nummultirange());
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(3,4)), nummultirange());
SELECT multirange_minus_multi(nummultirange(numrange(1,2)), nummultirange(numrange(1,2)));
SELECT multirange_minus_multi(nummultirange(numrange(1,2)), nummultirange(numrange(2,4)));
SELECT multirange_minus_multi(nummultirange(numrange(1,2)), nummultirange(numrange(3,4)));
SELECT multirange_minus_multi(nummultirange(numrange(1,4)), nummultirange(numrange(1,2)));
SELECT multirange_minus_multi(nummultirange(numrange(1,4)), nummultirange(numrange(2,3)));
SELECT multirange_minus_multi(nummultirange(numrange(1,4)), nummultirange(numrange(0,8)));
SELECT multirange_minus_multi(nummultirange(numrange(1,4)), nummultirange(numrange(0,2)));
SELECT multirange_minus_multi(nummultirange(numrange(1,8)), nummultirange(numrange(0,2), numrange(3,4)));
SELECT multirange_minus_multi(nummultirange(numrange(1,8)), nummultirange(numrange(2,3), numrange(5,null)));
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(4,5)), nummultirange(numrange(-2,0)));
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(4,5)), nummultirange(numrange(2,4)));
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(4,5)), nummultirange(numrange(3,5)));
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(4,5)), nummultirange(numrange(0,9)));
SELECT multirange_minus_multi(nummultirange(numrange(1,3), numrange(4,5)), nummultirange(numrange(2,9)));
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(4,5)), nummultirange(numrange(8,9)));
SELECT multirange_minus_multi(nummultirange(numrange(1,2), numrange(4,5)), nummultirange(numrange(-2,0), numrange(8,9)));
-- intersection -- intersection
SELECT nummultirange() * nummultirange(); SELECT nummultirange() * nummultirange();
SELECT nummultirange() * nummultirange(numrange(1,2)); SELECT nummultirange() * nummultirange(numrange(1,2));

View File

@@ -107,6 +107,16 @@ select numrange(1.1, 2.2,'[]') - numrange(2.0, 3.0);
select range_minus(numrange(10.1,12.2,'[]'), numrange(110.0,120.2,'(]')); select range_minus(numrange(10.1,12.2,'[]'), numrange(110.0,120.2,'(]'));
select range_minus(numrange(10.1,12.2,'[]'), numrange(0.0,120.2,'(]')); select range_minus(numrange(10.1,12.2,'[]'), numrange(0.0,120.2,'(]'));
select range_minus_multi('empty'::numrange, numrange(2.0, 3.0));
select range_minus_multi(numrange(1.1, 2.2), 'empty'::numrange);
select range_minus_multi(numrange(1.1, 2.2), numrange(2.0, 3.0));
select range_minus_multi(numrange(1.1, 2.2), numrange(2.2, 3.0));
select range_minus_multi(numrange(1.1, 2.2,'[]'), numrange(2.0, 3.0));
select range_minus_multi(numrange(1.0, 3.0), numrange(1.5, 2.0));
select range_minus_multi(numrange(10.1,12.2,'[]'), numrange(110.0,120.2,'(]'));
select range_minus_multi(numrange(10.1,12.2,'[]'), numrange(0.0,120.2,'(]'));
select range_minus_multi(numrange(1.0,3.0,'[]'), numrange(1.5,2.0,'(]'));
select numrange(4.5, 5.5, '[]') && numrange(5.5, 6.5); select numrange(4.5, 5.5, '[]') && numrange(5.5, 6.5);
select numrange(1.0, 2.0) << numrange(3.0, 4.0); select numrange(1.0, 2.0) << numrange(3.0, 4.0);
select numrange(1.0, 3.0,'[]') << numrange(3.0, 4.0,'[]'); select numrange(1.0, 3.0,'[]') << numrange(3.0, 4.0,'[]');