Re: tablefunc functions in postgresql - Mailing list pgsql-patches

From Joe Conway
Subject Re: tablefunc functions in postgresql
Date
Msg-id 3F7B5EA6.1090700@joeconway.com
Whole thread Raw
Responses Re: tablefunc functions in postgresql
List pgsql-patches
Attached is a patch for contrib/tablefunc. It fixes two issues raised by
Lars Boegild Thomsen (full email below) and also corrects the regression
expected output for a recent backend message adjustment. Please apply.

Thanks,

Joe


Lars Boegild Thomsen wrote:
> Dear Mr. Conway,
>
> I've noticed that you're the author of the tablefunc functions in the
> postgresql distribution.  I've come across a problem that I think is a bug.
> I realize that you're of course under no obligation to support this
> software, so I am just writing turn your attention to the problem - if
> indeed it is a problem :)
>
> I've go the following table defined:
>
> create table domains (
>     id int not null primary key default nextval('domain_id_seq'),
>     name varchar(64) not null,
>     description text,
>     owner_id int
> ) without oids;
>
> And for testing it contains the following:
>
> ipsmart=> select * from domains;
>  id |        name         |               description                |
> owner_id
> ----+---------------------+------------------------------------------+------
> ----
>   0 | justit.ws           | The overall owner of the IPSmart product |
>   1 | sp1.justit.ws       | Domain 1                                 |
> 0
>   2 | sp2.justit.ws       | Domain 2                                 |
> 0
>   3 | test1.sp1.justit.ws | Sub Domain 1                             |
> 1
>   4 | test2.sp1.justit.ws | Sub Domain 2                             |
> 1
>
> The owner_id column referenfes the id column of the same table.
>
> Now - I need to traverse this table from a certain level (and in production
> it will become rather deep).
>
> I've got two problems - one just an annoying "feature" of the connectby
> function and the other I think is a bug.  Let's start with the bug :)
> Traversing from node '0' is ok:
>
> ipsmart=> select * from connectby('domains', 'id', 'owner_id', '0', 0) as
> t(id int, owner_id int, level int);
>  id | owner_id | level
> ----+----------+-------
>   0 |          |     0
>   1 |        0 |     1
>   3 |        1 |     2
>   4 |        1 |     2
>   2 |        0 |     1
> (5 rows)
>
> No problem there.  Traversing from node 1 also works fine (results are 1, 3
> and 4).  But traversing from node 2 - nothing is returned.
>
> ipsmart=> select * from connectby('domains', 'id', 'owner_id', '2', 0) as
> t(id int, owner_id int, level int);
>  id | owner_id | level
> ----+----------+-------
> (0 rows)
>
> This is kind of weird since in the first two examples the root node was
> clearly returned as well.  Is this a feature or a bug?
>
> The other problem is not really a bug but rather a slight irritation :)
> Originally I had the 'owner_id" defined as "not null'.  I let the root refer
> to itself as parent and added the references constraint after I inserted the
> first record.  This way I could guarantee that the parent was always
> defined.  Unfortunately your "connectby" function wouldn't accept this and
> it lead to an infinite recurse.   Wouldn't it be reasonable easy to let your
> function stop if the node was referring to itself?
>
> Anyway - as mentioned earlier - I don't really expect that you've got time
> to look at this (although I would of course appreciate it a lot if you had).
> Even if you don't have time - I can live with the minor problems unless
> PostgreSQL get a real "connect by prior" construct :)
>
> Regards,
>
>     Lars...
>
> --
> Lars Boegild Thomsen
> Technical Director
> JustIT Sdn. Bhd.
> Cell Phone (MY): +60 (16) 323 1999
> ICQ: 6478559
> Yahoo Chat: lars_boegild_thomsen@yahoo.com
> MSN Chat: lars_boegild_thomsen@hotmail.com
> http://www.justit.ws
>

Index: contrib/tablefunc/tablefunc.c
===================================================================
RCS file: /opt/src/cvs/pgsql-server/contrib/tablefunc/tablefunc.c,v
retrieving revision 1.24
diff -c -r1.24 tablefunc.c
*** contrib/tablefunc/tablefunc.c    13 Sep 2003 21:44:50 -0000    1.24
--- contrib/tablefunc/tablefunc.c    1 Oct 2003 22:47:11 -0000
***************
*** 1295,1329 ****
      int            ret;
      int            proc;
      int            serial_column;

      if (max_depth > 0 && level > max_depth)
          return tupstore;

      /* Build initial sql statement */
      if (!show_serial)
      {
!         appendStringInfo(sql, "SELECT %s, %s FROM %s WHERE %s = '%s' AND %s IS NOT NULL",
                           key_fld,
                           parent_key_fld,
                           relname,
                           parent_key_fld,
                           start_with,
!                          key_fld);
          serial_column = 0;
      }
      else
      {
!         appendStringInfo(sql, "SELECT %s, %s FROM %s WHERE %s = '%s' AND %s IS NOT NULL ORDER BY %s",
                           key_fld,
                           parent_key_fld,
                           relname,
                           parent_key_fld,
                           start_with,
!                          key_fld,
                           orderby_fld);
          serial_column = 1;
      }

      /* Retrieve the desired rows */
      ret = SPI_exec(sql->data, 0);
      proc = SPI_processed;
--- 1295,1394 ----
      int            ret;
      int            proc;
      int            serial_column;
+     StringInfo    branchstr = NULL;
+     StringInfo    chk_branchstr = NULL;
+     StringInfo    chk_current_key = NULL;
+     char      **values;
+     char       *current_key;
+     char       *current_key_parent;
+     char        current_level[INT32_STRLEN];
+     char        serial_str[INT32_STRLEN];
+     char       *current_branch;
+     HeapTuple    tuple;

      if (max_depth > 0 && level > max_depth)
          return tupstore;

+     /* start a new branch */
+     branchstr = makeStringInfo();
+
+     /* need these to check for recursion */
+     chk_branchstr = makeStringInfo();
+     chk_current_key = makeStringInfo();
+
      /* Build initial sql statement */
      if (!show_serial)
      {
!         appendStringInfo(sql, "SELECT %s, %s FROM %s WHERE %s = '%s' AND %s IS NOT NULL AND %s <> %s",
                           key_fld,
                           parent_key_fld,
                           relname,
                           parent_key_fld,
                           start_with,
!                          key_fld, key_fld, parent_key_fld);
          serial_column = 0;
      }
      else
      {
!         appendStringInfo(sql, "SELECT %s, %s FROM %s WHERE %s = '%s' AND %s IS NOT NULL AND %s <> %s ORDER BY %s",
                           key_fld,
                           parent_key_fld,
                           relname,
                           parent_key_fld,
                           start_with,
!                          key_fld, key_fld, parent_key_fld,
                           orderby_fld);
          serial_column = 1;
      }

+     if (show_branch)
+         values = (char **) palloc((CONNECTBY_NCOLS + serial_column) * sizeof(char *));
+     else
+         values = (char **) palloc((CONNECTBY_NCOLS_NOBRANCH + serial_column) * sizeof(char *));
+
+     /* First time through, do a little setup */
+     if (level == 0)
+     {
+         /* root value is the one we initially start with */
+         values[0] = start_with;
+
+         /* root value has no parent */
+         values[1] = NULL;
+
+         /* root level is 0 */
+         sprintf(current_level, "%d", level);
+         values[2] = current_level;
+
+         /* root branch is just starting root value */
+         if (show_branch)
+             values[3] = start_with;
+
+         /* root starts the serial with 1 */
+         if (show_serial)
+         {
+             sprintf(serial_str, "%d", (*serial)++);
+             if (show_branch)
+                 values[4] = serial_str;
+             else
+                 values[3] = serial_str;
+         }
+
+         /* construct the tuple */
+         tuple = BuildTupleFromCStrings(attinmeta, values);
+
+         /* switch to long lived context while storing the tuple */
+         oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+         /* now store it */
+         tuplestore_puttuple(tupstore, tuple);
+
+         /* now reset the context */
+         MemoryContextSwitchTo(oldcontext);
+
+         /* increment level */
+         level++;
+     }
+
      /* Retrieve the desired rows */
      ret = SPI_exec(sql->data, 0);
      proc = SPI_processed;
***************
*** 1331,1364 ****
      /* Check for qualifying tuples */
      if ((ret == SPI_OK_SELECT) && (proc > 0))
      {
-         HeapTuple    tuple;
          HeapTuple    spi_tuple;
          SPITupleTable *tuptable = SPI_tuptable;
          TupleDesc    spi_tupdesc = tuptable->tupdesc;
          int            i;
-         char       *current_key;
-         char       *current_key_parent;
-         char        current_level[INT32_STRLEN];
-         char        serial_str[INT32_STRLEN];
-         char       *current_branch;
-         char      **values;
-         StringInfo    branchstr = NULL;
-         StringInfo    chk_branchstr = NULL;
-         StringInfo    chk_current_key = NULL;
-
-         /* start a new branch */
-         branchstr = makeStringInfo();
-
-         /* need these to check for recursion */
-         chk_branchstr = makeStringInfo();
-         chk_current_key = makeStringInfo();

!         if (show_branch)
!             values = (char **) palloc((CONNECTBY_NCOLS + serial_column) * sizeof(char *));
!         else
!             values = (char **) palloc((CONNECTBY_NCOLS_NOBRANCH + serial_column) * sizeof(char *));
!
!         /* First time through, do a little setup */
          if (level == 0)
          {
              /*
--- 1396,1407 ----
      /* Check for qualifying tuples */
      if ((ret == SPI_OK_SELECT) && (proc > 0))
      {
          HeapTuple    spi_tuple;
          SPITupleTable *tuptable = SPI_tuptable;
          TupleDesc    spi_tupdesc = tuptable->tupdesc;
          int            i;

!         /* First time through, do a little more setup */
          if (level == 0)
          {
              /*
***************
*** 1373,1417 ****
                           errmsg("invalid return type"),
                       errdetail("Return and SQL tuple descriptions are " \
                                 "incompatible.")));
-
-             /* root value is the one we initially start with */
-             values[0] = start_with;
-
-             /* root value has no parent */
-             values[1] = NULL;
-
-             /* root level is 0 */
-             sprintf(current_level, "%d", level);
-             values[2] = current_level;
-
-             /* root branch is just starting root value */
-             if (show_branch)
-                 values[3] = start_with;
-
-             /* root starts the serial with 1 */
-             if (show_serial)
-             {
-                 sprintf(serial_str, "%d", (*serial)++);
-                 if (show_branch)
-                     values[4] = serial_str;
-                 else
-                     values[3] = serial_str;
-             }
-
-             /* construct the tuple */
-             tuple = BuildTupleFromCStrings(attinmeta, values);
-
-             /* switch to long lived context while storing the tuple */
-             oldcontext = MemoryContextSwitchTo(per_query_ctx);
-
-             /* now store it */
-             tuplestore_puttuple(tupstore, tuple);
-
-             /* now reset the context */
-             MemoryContextSwitchTo(oldcontext);
-
-             /* increment level */
-             level++;
          }

          for (i = 0; i < proc; i++)
--- 1416,1421 ----
Index: contrib/tablefunc/expected/tablefunc.out
===================================================================
RCS file: /opt/src/cvs/pgsql-server/contrib/tablefunc/expected/tablefunc.out,v
retrieving revision 1.9
diff -c -r1.9 tablefunc.out
*** contrib/tablefunc/expected/tablefunc.out    13 Sep 2003 21:44:50 -0000    1.9
--- contrib/tablefunc/expected/tablefunc.out    1 Oct 2003 22:34:32 -0000
***************
*** 127,133 ****
  -- hash based crosstab
  --
  create table cth(id serial, rowid text, rowdt timestamp, attribute text, val text);
! NOTICE:  CREATE TABLE will create implicit sequence "cth_id_seq" for SERIAL column "cth.id"
  insert into cth values(DEFAULT,'test1','01 March 2003','temperature','42');
  insert into cth values(DEFAULT,'test1','01 March 2003','test_result','PASS');
  -- the next line is intentionally left commented and is therefore a "missing" attribute
--- 127,133 ----
  -- hash based crosstab
  --
  create table cth(id serial, rowid text, rowdt timestamp, attribute text, val text);
! NOTICE:  CREATE TABLE will create implicit sequence "cth_id_seq" for "serial" column "cth.id"
  insert into cth values(DEFAULT,'test1','01 March 2003','temperature','42');
  insert into cth values(DEFAULT,'test1','01 March 2003','test_result','PASS');
  -- the next line is intentionally left commented and is therefore a "missing" attribute

pgsql-patches by date:

Previous
From: Andrew Dunstan
Date:
Subject: Re: [HACKERS] initdb
Next
From: Tom Lane
Date:
Subject: Re: tablefunc functions in postgresql