Subject: NULL pointers in ADTs

From: Peter van Oosterom <[email-masked]>
Date: Tue, 18 Feb 92 18:28:26 MET

Dear Postgres-group,

There is a problem with the return of NULL pointers in ADTs.
I'm working on the implementation of some 3D data types: POINT3, BOX3, etc.
I have implemented one function for calculation the intersection of
two boxes. The function is called Intersect3BoxBox and the source code
is given at the end of this email.

In case the intersection is empty, this function returns NULL
(similar to the standard Postgres function for the intersection of
two 2D boxes). The function works fine as long as there is a non empty
intersection of the BOX3es. An example:

============================================================================
* create b3test (b=BOX3)\g

Query sent to backend is "create b3test (b=BOX3)"
CREATE
Go 
* append b3test (b="(0,0,0, 2,2,2)")\g

Query sent to backend is "append b3test (b="(0,0,0, 2,2,2)")"
APPEND
Go 
* append b3test (b="(1,1,1, 3,3,3)")\g

Query sent to backend is "append b3test (b="(1,1,1, 3,3,3)")"
APPEND
Go 
* retrieve (pb=p.b, qb=q.b, i=Intersect3BoxBox(p.b,q.b)) from p,q in b3test\g

Query sent to backend is "retrieve (pb=p.b, qb=q.b, i=Intersect3BoxBox(p.b,q.b)) from p,q in b3test"
-------------------------------------------
| pb          | qb          | i           |
-------------------------------------------
| (0,0,0,2,2,2)| (0,0,0,2,2,2)| (0,0,0,2,2,2)|
-------------------------------------------
| (1,1,1,3,3,3)| (0,0,0,2,2,2)| (1,1,1,2,2,2)|
-------------------------------------------
| (0,0,0,2,2,2)| (1,1,1,3,3,3)| (1,1,1,2,2,2)|
-------------------------------------------
| (1,1,1,3,3,3)| (1,1,1,3,3,3)| (1,1,1,3,3,3)|
-------------------------------------------

Go 
============================================================================

Ok, so far so good. Now I insert a new BOX3 that does not have overlap
with the others:

============================================================================

Go
* append b3test (b="(5,5,5, 6,6,6)")\g

Query sent to backend is "append b3test (b="(5,5,5, 6,6,6)")"
APPEND
Go 
* retrieve (pb=p.b, qb=q.b, i=Intersect3BoxBox(p.b,q.b)) from p,q in b3test\g

Query sent to backend is "retrieve (pb=p.b, qb=q.b, i=Intersect3BoxBox(p.b,q.b)) from p,q in b3test"

============================================================================

The last query causes the monitor to loop forever (until ^C). This is caused
by the return of the NULL pointer in the situation that there is no overlap.
I could put an "elog(WARN, "No intersection BOX3 BOX3");" statement in
my code for Intersect3BoxBox (as suggested some time ago on the Postgres BBS).
However, this causes the transaction to abort and will therefore not give
any answers:

============================================================================
Go 
* retrieve (pb=p.b, qb=q.b, i=Intersect3BoxBox(p.b,q.b)) from p,q in b3test\g

Query sent to backend is "retrieve (pb=p.b, qb=q.b, i=Intersect3BoxBox(p.b,q.b)) from p,q in b3test"
WARN:Feb 18 17:58:05:No intersection BOX3 BOX3

Go 
* 
============================================================================

By the way, this problem also applies to all the "standard" Postgres
functions that return NULL pointers (as the original 2D box intersection
function: box_intersect in /usr/postgres/src/utils/adt/geo-ops.c).

Another aspect of this problem is that the Intersect3BoxBox function
will also be used for the 3D R-tree. Other functions within Postgres 
which are used to manipulate the R-tree need the Intersect3BoxBox function.
These functions can explicitly deal with the NULL-pointer return.
However, aborting the transaction (through a call to elog(WARN,"...");)
would be wrong in this situation.

Is there a more elegant solution for this NULL-pointer problem?

Kind regards, Peter van Oosterom.

========================sorce code Intersect3BoxBox=========================
BOX3 *
Intersect3BoxBox(box1, box2)
   BOX3     *box1, *box2;
{
   BOX3     *result;
   long     Overlap3BoxBox();

   if (! Overlap3BoxBox(box1,box2)) {
      elog(WARN, "No intersection BOX3 BOX3");    /*********OK?***********/
      return(NULL);
   }
   result = PALLOCTYPE(BOX3);
   result->xh = MIN(box1->xh, box2->xh);
   result->xl = MAX(box1->xl, box2->xl);
   result->yh = MIN(box1->yh, box2->yh);
   result->yl = MAX(box1->yl, box2->yl);
   result->zh = MIN(box1->zh, box2->zh);
   result->zl = MAX(box1->zl, box2->zl);

   return(result);
}
============================================================================

---

From: postarch (Postgres Mailing Archive)
Date: Tue, 18 Feb 92 11:52:20 PST

>two boxes.

>intersection of the BOX3es.

>with the others

>any answers

>would be wrong in this situation.

You are right on all counts.  The problem is that postgres does not handle
null (attribute) values very well.  When a pass by reference ADT input or 
manipulation function (a function that returns a pass by reference ADT) 
returns NULL, POSTGRES will lay an egg.  If the ADT is passbyvalue
POSTGRES will interpret it as the value 0.  In general you can't assume that
0 will always mean null when you are doing ADT processing.

>Is there a more elegant solution for this NULL-pointer problem?

The solution is to require all ADT input and registered functions to
accept an isnull boolean ptr as their first argument.  This way the executor
can know if it is receiving a null result and handle it accordingly.  
Unfortunately, when I suggested that we implement this for version 4 people 
(who are already overburdened) threw large objects (no pun intended) at me.
It is likely that this will be implemented in a 4.1 (bug fix) release.  For 
now you will have to live with the ugliness as best you can.

Jeff Meredith
[email-masked]