Index: openacs-4/packages/acs-kernel/sql/postgresql/postgresql.sql
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/acs-kernel/sql/postgresql/postgresql.sql,v
diff -u -r1.17 -r1.18
--- openacs-4/packages/acs-kernel/sql/postgresql/postgresql.sql	8 Dec 2001 01:17:59 -0000	1.17
+++ openacs-4/packages/acs-kernel/sql/postgresql/postgresql.sql	9 Dec 2001 04:21:58 -0000	1.18
@@ -359,10 +359,39 @@
 
 end;' language 'plpgsql' with (isstrict, iscachable);
 
+create function tree_ancestor_key(varbit, integer) returns varbit as '
+
+-- Returns a key for the ancestor at the given level.  The root is level
+-- one.
+
+declare
+  p_tree_key      alias for $1;
+  p_level         alias for $2;
+  v_level         integer default 0;
+  v_pos           integer default 1;
+begin
+
+  if tree_level(p_tree_key) < p_level then
+    raise exception ''tree_ancestor_key: key is at a level less than %'', p_level;
+  end if;
+
+  while v_level < p_level loop
+    v_level := v_level + 1;
+    if substring(p_tree_key, v_pos, 1) = ''1'' then
+      v_pos := v_pos + 16;
+    else
+      v_pos := v_pos + 8;
+    end if;
+  end loop;
+
+  return substring(p_tree_key, 1, v_pos - 1);
+
+end;' language 'plpgsql' with (isstrict, iscachable);
+
 create function tree_root_key(varbit) returns varbit as '
 
 -- Return the tree_sortkey for the root node of the node with the 
--- given tree_sortkey.
+-- given tree_sortkey.  
 
 declare
   p_tree_key      alias for $1;
@@ -501,7 +530,104 @@
   return position(p_potential_ancestor in p_potential_child) = 1;
 end;' language 'plpgsql';
 
+create function create_tree_ancestor_keys() returns boolean as '
 
+-- PG 7.1 does not allow recursive SQL functions, but David Walker figured out how to
+-- get around this with a truly inspired hack he posted to the OpenACS 4 Design Forum.
+
+-- His solution involves a general "create and replace function" function written in
+-- Tcl.
+
+-- Rather than use the general solution I have just hacked up a PL/pgSQL function to
+-- create the one recursive function we need: tree_ancestor_keys(varbit, integer).
+
+-- This function should probably only be called from its overloaded cousin
+-- tree_ancestor_keys(varbit).
+
+begin
+
+  -- create tree_ancestor_keys with a dummy body
+
+  execute ''create function tree_ancestor_keys(varbit, integer) returns setof varbit as ''''
+             select $1
+           '''' language ''''sql'''' '';
+
+  -- create another function with the body we want
+
+  execute ''create function __tree_ancestor_keys(varbit, integer) returns setof varbit as ''''
+              select tree_ancestor_key($1, $2)
+              union
+              select tree_ancestor_keys($1, $2 + 1)
+              where $2 < tree_level($1)
+           '''' language ''''sql'''' with (isstrict) '';
+
+  -- replace the body for tree_ancestor_keys with the body we want.  Slick, eh?
+
+  update pg_proc
+  set prosrc = hack.prosrc, probin = hack.probin
+  from (select prosrc, probin
+        from pg_proc
+        where proname = ''__tree_ancestor_keys'') hack
+  where proname = ''tree_ancestor_keys'';
+
+  execute ''drop function __tree_ancestor_keys(varbit, integer)'';
+
+  return true;
+end;' language 'plpgsql';
+
+select create_tree_ancestor_keys();
+
+create function tree_ancestor_keys(varbit) returns setof varbit as '
+
+-- tree_ancestor_keys returns the set of ancestor keys starting at the level passed
+-- in as the second parameter down to the key passed in as the first parameter.  Normally
+-- this will be one - its intended use is to drive the recursive building of the set of
+-- ancestor keys.
+
+-- Here is an example on acs_objects:
+
+-- select o.*
+-- from acs_objects o,
+--   (select tree_ancestor_keys(acs_objects_get_tree_sortkey(:object_id), 1) as tree_sortkey) parents
+-- where o.tree_sortkey = parents.tree_sortkey;
+
+-- This query will use the index on tree_sortkey to scan acs_objects.  The function to grab
+-- the tree_sortkey for the node is necessary (and must be defined for each table that uses
+-- our hierarchical query scheme) to avoid restrictions on the use of SQL functions that
+-- return sets.
+
+-- if you only want the ancestors for a node within a given subtree, do something like this and
+-- cross your fingers that Postgres will figure out whether the join on parent or the root is
+-- more restrictive and do the right one first:
+
+-- select o.*
+-- from acs_objects o,
+--     (select tree_sortkey from acs_objects where object_id = :root_id) as root
+--   (select tree_ancestor_keys(acs_objects_get_tree_sortkey(:object_id), 1) as tree_sortkey) parents
+-- where o.tree_sortkey = parents.tree_sortkey
+--   and o.tree_sortkey >= root.tree_sortkey;
+
+-- DO NOT BE TEMPTED TO REWRITE THE ABOVE QUERIES LIKE THIS:
+
+-- select *
+-- from acs_objects
+-- where object_id in (select tree_ancestor_keys(object_id)
+--                     from acs_objects
+--                     where object_id = :object_id);
+
+-- This is more readable and is certainly cleaner BUT WILL NOT USE THE INDEX ON TREE_SORTKEY
+-- when scanning the acs_objects instance referred to by the left operand of the "in" operator.  Given
+-- that acs_objects will become HUGE on real systems the resulting sequential scan would cripple
+-- performance.   
+
+-- WARNING: subselects in where clauses that call this function and join on an outer table appear
+-- to reliably kill PG 7.1.2.   Not tested for PG 7.2.  If it fails there a bug report will be
+-- filed.
+
+  select tree_ancestor_keys($1, 1)
+
+' language 'sql' with (isstrict);
+
 ----------------------------------------------------------------------------
 
 -- PG substitute for Oracle user_tab_columns view
