Skip to content

Commit 4397d92

Browse files
pjungwirCommitfest Bot
authored andcommitted
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 <[email protected]>
1 parent 2217b2d commit 4397d92

File tree

9 files changed

+491
-0
lines changed

9 files changed

+491
-0
lines changed

doc/src/sgml/func/func-range.sgml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,29 @@
842842
<returnvalue>[1,4)</returnvalue>
843843
</para></entry>
844844
</row>
845+
846+
<row>
847+
<entry role="func_table_entry"><para role="func_signature">
848+
<indexterm>
849+
<primary>range_minus_multi</primary>
850+
</indexterm>
851+
<function>range_minus_multi</function> ( <type>anyrange</type>, <type>anyrange</type> )
852+
<returnvalue>setof anyrange</returnvalue>
853+
</para>
854+
<para>
855+
Returns the non-empty range(s) remaining after subtracting the second range from the first.
856+
One row is returned for each range, so if the second range splits the first into two parts,
857+
there will be two results. If the subtraction yields an empty range, no rows are returned.
858+
</para>
859+
<para>
860+
<literal>range_minus_multi('[0,10)'::int4range, '[3,4)'::int4range)</literal>
861+
<returnvalue></returnvalue>
862+
<programlisting>
863+
[0,3)
864+
[4,10)
865+
</programlisting>
866+
</para></entry>
867+
</row>
845868
</tbody>
846869
</tgroup>
847870
</table>
@@ -1041,6 +1064,25 @@
10411064
</programlisting>
10421065
</para></entry>
10431066
</row>
1067+
1068+
<row>
1069+
<entry role="func_table_entry"><para role="func_signature">
1070+
<indexterm>
1071+
<primary>multirange_minus_multi</primary>
1072+
</indexterm>
1073+
<function>multirange_minus_multi</function> ( <type>anymultirange</type>, <type>anymultirange</type> )
1074+
<returnvalue>setof anymultirange</returnvalue>
1075+
</para>
1076+
<para>
1077+
Returns the non-empty multirange(s) remaining after subtracting the second multirange from the first.
1078+
If the subtraction yields an empty multirange, no rows are returned.
1079+
Two rows are never returned, because a single multirange can always accommodate any result.
1080+
</para>
1081+
<para>
1082+
<literal>range_minus_multi('[0,10)'::int4range, '[3,4)'::int4range)</literal>
1083+
<returnvalue>{[0,3), [4,10)}</returnvalue>
1084+
</para></entry>
1085+
</row>
10441086
</tbody>
10451087
</tgroup>
10461088
</table>

src/backend/utils/adt/multirangetypes.c

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,77 @@ multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
12261226
return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
12271227
}
12281228

1229+
/*
1230+
* multirange_minus_multi - like multirange_minus but returning the result as a SRF,
1231+
* with no rows if the result would be empty.
1232+
*/
1233+
Datum
1234+
multirange_minus_multi(PG_FUNCTION_ARGS)
1235+
{
1236+
FuncCallContext *funcctx;
1237+
MemoryContext oldcontext;
1238+
1239+
if (!SRF_IS_FIRSTCALL())
1240+
{
1241+
/* We never have more than one result */
1242+
funcctx = SRF_PERCALL_SETUP();
1243+
SRF_RETURN_DONE(funcctx);
1244+
}
1245+
else
1246+
{
1247+
MultirangeType *mr1;
1248+
MultirangeType *mr2;
1249+
Oid mltrngtypoid;
1250+
TypeCacheEntry *typcache;
1251+
TypeCacheEntry *rangetyp;
1252+
int32 range_count1;
1253+
int32 range_count2;
1254+
RangeType **ranges1;
1255+
RangeType **ranges2;
1256+
MultirangeType *mr;
1257+
1258+
funcctx = SRF_FIRSTCALL_INIT();
1259+
1260+
/*
1261+
* switch to memory context appropriate for multiple function calls
1262+
*/
1263+
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
1264+
1265+
/* get args, detoasting into multi-call memory context */
1266+
mr1 = PG_GETARG_MULTIRANGE_P(0);
1267+
mr2 = PG_GETARG_MULTIRANGE_P(1);
1268+
1269+
mltrngtypoid = MultirangeTypeGetOid(mr1);
1270+
typcache = lookup_type_cache(mltrngtypoid, TYPECACHE_MULTIRANGE_INFO);
1271+
if (typcache->rngtype == NULL)
1272+
elog(ERROR, "type %u is not a multirange type", mltrngtypoid);
1273+
rangetyp = typcache->rngtype;
1274+
1275+
if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
1276+
mr = mr1;
1277+
else
1278+
{
1279+
multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
1280+
multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
1281+
1282+
mr = multirange_minus_internal(mltrngtypoid,
1283+
rangetyp,
1284+
range_count1,
1285+
ranges1,
1286+
range_count2,
1287+
ranges2);
1288+
}
1289+
1290+
MemoryContextSwitchTo(oldcontext);
1291+
1292+
funcctx = SRF_PERCALL_SETUP();
1293+
if (MultirangeIsEmpty(mr))
1294+
SRF_RETURN_DONE(funcctx);
1295+
else
1296+
SRF_RETURN_NEXT(funcctx, MultirangeTypePGetDatum(mr));
1297+
}
1298+
}
1299+
12291300
/* multirange intersection */
12301301
Datum
12311302
multirange_intersect(PG_FUNCTION_ARGS)

src/backend/utils/adt/rangetypes.c

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "postgres.h"
3232

3333
#include "common/hashfn.h"
34+
#include "funcapi.h"
3435
#include "libpq/pqformat.h"
3536
#include "miscadmin.h"
3637
#include "nodes/makefuncs.h"
@@ -39,6 +40,7 @@
3940
#include "optimizer/clauses.h"
4041
#include "optimizer/cost.h"
4142
#include "optimizer/optimizer.h"
43+
#include "utils/array.h"
4244
#include "utils/builtins.h"
4345
#include "utils/date.h"
4446
#include "utils/lsyscache.h"
@@ -1214,6 +1216,170 @@ range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeT
12141216
return false;
12151217
}
12161218

1219+
/*
1220+
* range_minus_multi - like range_minus but as a SRF to accommodate splits,
1221+
* with no result rows if the result would be empty.
1222+
*/
1223+
Datum
1224+
range_minus_multi(PG_FUNCTION_ARGS)
1225+
{
1226+
typedef struct
1227+
{
1228+
RangeType *rs[2];
1229+
int n;
1230+
} range_minus_multi_fctx;
1231+
1232+
FuncCallContext *funcctx;
1233+
range_minus_multi_fctx *fctx;
1234+
MemoryContext oldcontext;
1235+
1236+
/* stuff done only on the first call of the function */
1237+
if (SRF_IS_FIRSTCALL())
1238+
{
1239+
RangeType *r1;
1240+
RangeType *r2;
1241+
Oid rngtypid;
1242+
TypeCacheEntry *typcache;
1243+
1244+
/* create a function context for cross-call persistence */
1245+
funcctx = SRF_FIRSTCALL_INIT();
1246+
1247+
/*
1248+
* switch to memory context appropriate for multiple function calls
1249+
*/
1250+
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
1251+
1252+
r1 = PG_GETARG_RANGE_P(0);
1253+
r2 = PG_GETARG_RANGE_P(1);
1254+
1255+
/* Different types should be prevented by ANYRANGE matching rules */
1256+
if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
1257+
elog(ERROR, "range types do not match");
1258+
1259+
/* allocate memory for user context */
1260+
fctx = (range_minus_multi_fctx *) palloc(sizeof(range_minus_multi_fctx));
1261+
1262+
/*
1263+
* Initialize state. We can't store the range typcache in fn_extra
1264+
* because the caller uses that for the SRF state.
1265+
*/
1266+
rngtypid = RangeTypeGetOid(r1);
1267+
typcache = lookup_type_cache(rngtypid, TYPECACHE_RANGE_INFO);
1268+
if (typcache->rngelemtype == NULL)
1269+
elog(ERROR, "type %u is not a range type", rngtypid);
1270+
range_minus_multi_internal(typcache, r1, r2, fctx->rs, &fctx->n);
1271+
1272+
funcctx->user_fctx = fctx;
1273+
MemoryContextSwitchTo(oldcontext);
1274+
}
1275+
1276+
/* stuff done on every call of the function */
1277+
funcctx = SRF_PERCALL_SETUP();
1278+
fctx = funcctx->user_fctx;
1279+
1280+
if (funcctx->call_cntr < fctx->n)
1281+
{
1282+
/*
1283+
* We must keep these on separate lines because SRF_RETURN_NEXT does
1284+
* call_cntr++:
1285+
*/
1286+
RangeType *ret = fctx->rs[funcctx->call_cntr];
1287+
1288+
SRF_RETURN_NEXT(funcctx, RangeTypePGetDatum(ret));
1289+
}
1290+
else
1291+
/* do when there is no more left */
1292+
SRF_RETURN_DONE(funcctx);
1293+
}
1294+
1295+
/*
1296+
* range_minus_multi_internal - Sets outputs and outputn to the ranges
1297+
* remaining and their count (respectively) after subtracting r2 from r1.
1298+
* The array should never contain empty ranges.
1299+
* The outputs will be ordered. We expect that outputs is an array of
1300+
* RangeType pointers, already allocated with two elements.
1301+
*/
1302+
void
1303+
range_minus_multi_internal(TypeCacheEntry *typcache, RangeType *r1,
1304+
RangeType *r2, RangeType **outputs, int *outputn)
1305+
{
1306+
int cmp_l1l2,
1307+
cmp_l1u2,
1308+
cmp_u1l2,
1309+
cmp_u1u2;
1310+
RangeBound lower1,
1311+
lower2;
1312+
RangeBound upper1,
1313+
upper2;
1314+
bool empty1,
1315+
empty2;
1316+
1317+
range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
1318+
range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
1319+
1320+
if (empty1)
1321+
{
1322+
/* if r1 is empty then r1 - r2 is empty, so return zero results */
1323+
*outputn = 0;
1324+
return;
1325+
}
1326+
else if (empty2)
1327+
{
1328+
/* r2 is empty so the result is just r1 (which we know is not empty) */
1329+
outputs[0] = r1;
1330+
*outputn = 1;
1331+
return;
1332+
}
1333+
1334+
/*
1335+
* Use the same logic as range_minus_internal, but support the split case
1336+
*/
1337+
cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
1338+
cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
1339+
cmp_u1l2 = range_cmp_bounds(typcache, &upper1, &lower2);
1340+
cmp_u1u2 = range_cmp_bounds(typcache, &upper1, &upper2);
1341+
1342+
if (cmp_l1l2 < 0 && cmp_u1u2 > 0)
1343+
{
1344+
lower2.inclusive = !lower2.inclusive;
1345+
lower2.lower = false; /* it will become the upper bound */
1346+
outputs[0] = make_range(typcache, &lower1, &lower2, false, NULL);
1347+
1348+
upper2.inclusive = !upper2.inclusive;
1349+
upper2.lower = true; /* it will become the lower bound */
1350+
outputs[1] = make_range(typcache, &upper2, &upper1, false, NULL);
1351+
1352+
*outputn = 2;
1353+
}
1354+
else if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
1355+
{
1356+
outputs[0] = r1;
1357+
*outputn = 1;
1358+
}
1359+
else if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
1360+
{
1361+
*outputn = 0;
1362+
}
1363+
else if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
1364+
{
1365+
lower2.inclusive = !lower2.inclusive;
1366+
lower2.lower = false; /* it will become the upper bound */
1367+
outputs[0] = make_range(typcache, &lower1, &lower2, false, NULL);
1368+
*outputn = 1;
1369+
}
1370+
else if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
1371+
{
1372+
upper2.inclusive = !upper2.inclusive;
1373+
upper2.lower = true; /* it will become the lower bound */
1374+
outputs[0] = make_range(typcache, &upper2, &upper1, false, NULL);
1375+
*outputn = 1;
1376+
}
1377+
else
1378+
{
1379+
elog(ERROR, "unexpected case in range_minus_multi");
1380+
}
1381+
}
1382+
12171383
/* range -> range aggregate functions */
12181384

12191385
Datum

src/include/catalog/pg_proc.dat

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10939,6 +10939,10 @@
1093910939
{ oid => '3869',
1094010940
proname => 'range_minus', prorettype => 'anyrange',
1094110941
proargtypes => 'anyrange anyrange', prosrc => 'range_minus' },
10942+
{ oid => '8412', descr => 'remove portion from range',
10943+
proname => 'range_minus_multi', prorows => '2',
10944+
proretset => 't', prorettype => 'anyrange',
10945+
proargtypes => 'anyrange anyrange', prosrc => 'range_minus_multi' },
1094210946
{ oid => '3870', descr => 'less-equal-greater',
1094310947
proname => 'range_cmp', prorettype => 'int4',
1094410948
proargtypes => 'anyrange anyrange', prosrc => 'range_cmp' },
@@ -11229,6 +11233,10 @@
1122911233
{ oid => '4271',
1123011234
proname => 'multirange_minus', prorettype => 'anymultirange',
1123111235
proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
11236+
{ oid => '8411', descr => 'remove portion from multirange',
11237+
proname => 'multirange_minus_multi', prorows => '1',
11238+
proretset => 't', prorettype => 'anymultirange',
11239+
proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus_multi' },
1123211240
{ oid => '4272',
1123311241
proname => 'multirange_intersect', prorettype => 'anymultirange',
1123411242
proargtypes => 'anymultirange anymultirange',

src/include/utils/rangetypes.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,5 +164,7 @@ extern RangeType *make_empty_range(TypeCacheEntry *typcache);
164164
extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
165165
const RangeType *r2, RangeType **output1,
166166
RangeType **output2);
167+
extern void range_minus_multi_internal(TypeCacheEntry *typcache, RangeType *r1,
168+
RangeType *r2, RangeType **outputs, int *outputn);
167169

168170
#endif /* RANGETYPES_H */

0 commit comments

Comments
 (0)