-- bookmarks.sql
--
-- created June 1999 and modified July 1999
-- by aure@arsdigita.com and dh@arsdigita.com 

-- ported to openacs by sokolof@rpi.edu


create sequence bm_url_id_seq;
-- since many people will be bookmarking the same sites, we keep urls in a separate table

create table bm_urls (
	url_id integer primary key,
	-- url title may be null in the case of bookmarks that are merely icons ie. AIM
	url_title varchar(500),
	-- host url is separated from complete_url for counting purposes
	host_url varchar(100) not null,
	complete_url varchar(500) not null,
	-- meta tags that could be looked up regularly	
	meta_keywords varchar(4000),
	meta_description varchar(4000),
	last_checked_date timestamp,
	-- the last time the site returned a "live" status
	last_live_date timestamp
);

create sequence bm_bookmark_id_seq;
-- this table contains both bookmarks and folders


create table bm_list (
	bookmark_id integer primary key,
	-- sort keys contains 3 characters per level of depth, from
	-- 0-9, then A-Z, a-z. You can get the depth as length(parent_sort_key) / 3.
	-- the full sort key for any bookmark is parent_sort_key || local_sort_key
  	parent_sort_key varchar(99), -- parent's sort key
	local_sort_key char(3) not null,
	owner_id integer not null references users(user_id),
	creation_date timestamp not null,
	modification_date timestamp,
	-- url_id may be null if the bookmark is a folder
	url_id integer references bm_urls,
	-- a person may rename any of his bookmarks so we keep a local title
	local_title varchar(500),
	private_p char(1) default 'f' check (private_p in ('t','f')),
	-- needed in addition to private_p for the case where a public bookmark
	-- is under a hidden folder
	hidden_p char(1) default 'f' check (hidden_p in ('t','f')),
	-- this is 't' if the bookmark is a folder
	folder_p char(1) default 'f' check (folder_p in ('t','f')),
	-- null parent_id indicates this is a top level folder/bookmark
	parent_id integer references bm_list(bookmark_id),
	-- refers to whether a folder is open or closed
	closed_p char(1) default 't' check (closed_p in ('t','f')),
	-- whether the bookmark is within a closed folder and therefore not shown
	in_closed_p char(1) default 'f' check (in_closed_p in ('t','f')) 
);

-- a hack to supply Oracle chr()functionality in pg7.0.2
-- will need to be changed because postgres programmers
-- intend to have chr in future releases

create function chr(int4)
returns char
as '
begin
   return ichar($1);
end;'
language 'plpgsql';


-- Increments char from 0-9 A-Z.
-- acs_classic incremented from a-z after A-Z
-- but this doesn't seem to sort properly in
-- postgres

CREATE function inc_char_for_sort_key (char)
RETURNS CHAR
AS 'DECLARE
   old_code INTEGER;
   new_code INTEGER;
BEGIN
   old_code := ascii($1);
   IF old_code = 57 THEN
      -- skip from 9 to A
      new_code := 65;
   ELSE
     IF old_code = 90 THEN
       -- wrap around
       new_code := 48;
     ELSE
       new_code := old_code + 1;
     END IF;
  END IF;
  RETURN chr(new_code);
END;'
LANGUAGE 'plpgsql';


-- Sets carry_p to true if incrementing
-- from Z to 0.

CREATE function get_carry_for_sort_key (char)
RETURNS CHAR
AS 'DECLARE
   old_code INTEGER;
--   carry_p INTEGER;
BEGIN
  old_code := ascii($1);
  IF old_code = 90 THEN
   -- wrap around
      RETURN ''t'';
  END IF;
    RETURN ''f'';
END;'
LANGUAGE 'plpgsql';


-- Takes a local sort key and increments it by one.

CREATE FUNCTION new_sort_key (varchar) 
RETURNS VARCHAR
AS 'DECLARE
   v_chr_1 char;
   v_chr_2 char;
   v_chr_3 char;
   v_carry char;
   foo char;
BEGIN

   IF $1 IS null THEN
      RETURN ''000'';
   END IF;
   
   v_chr_1 := substr($1, 1, 1);
   v_chr_2 := substr($1, 2, 1);
   v_chr_3 := substr($1, 3, 1);

   SELECT get_carry_for_sort_key(v_chr_3) INTO v_carry;
   SELECT inc_char_for_sort_key(v_chr_3) INTO v_chr_3;
   IF v_carry = ''t'' THEN
      SELECT get_carry_for_sort_key(v_chr_2) INTO v_carry;
      SELECT inc_char_for_sort_key(v_chr_2) INTO v_chr_2;
      IF v_carry = ''t'' THEN
        SELECT inc_char_for_sort_key(v_chr_1) INTO v_chr_1;
      END IF;
   END IF;
   RETURN v_chr_1 || v_chr_2 || v_chr_3;
END;'
LANGUAGE 'plpgsql';

-- calculates a new sort key for inserts

CREATE function bm_list_sort_key_i() returns opaque as '
DECLARE
  v_last_sort_key bm_list.local_sort_key%TYPE;
  v_parent_sort_key bm_list.parent_sort_key%TYPE;
  parent_rec record;
BEGIN
   IF NEW.parent_id IS NULL THEN
      SELECT max(local_sort_key) INTO v_last_sort_key
	FROM bm_list
	WHERE parent_id IS NULL;
      v_parent_sort_key := null;
   ELSE
      SELECT max(local_sort_key) INTO v_last_sort_key
        FROM bm_list
        WHERE parent_id = NEW.parent_id;

      -- postgres cannot concatenate a null value to a non null value
      SELECT INTO parent_rec * FROM bm_list WHERE bookmark_id = NEW.parent_id;
      IF parent_rec.parent_sort_key IS NULL THEN
         v_parent_sort_key := parent_rec.local_sort_key;
      ELSE
        v_parent_sort_key := parent_rec.parent_sort_key || cast(parent_rec.local_sort_key as varchar(3));
      END IF;
   END IF;

   NEW.local_sort_key := new_sort_key(v_last_sort_key);
   NEW.parent_sort_key := v_parent_sort_key;
   RETURN new;
END;
' language 'plpgsql'; 

CREATE trigger bm_list_insert_tr
before INSERT ON bm_list
for each row
execute procedure bm_list_sort_key_i(); 


-- Fixes up parent_sort_key and local_sort_key for a bookmark.
-- If the bookmark was a folder, recursively updates its children.

CREATE FUNCTION bm_fixup_sort_key(INTEGER) 
RETURNS INTEGER AS 'DECLARE
   v_last_sort_key bm_list.local_sort_key%TYPE;
   v_parent_sort_key bm_list.parent_sort_key%TYPE;
   v_new_sort_key bm_list.local_sort_key%TYPE;
   bm_list_rec record;
   bm_list_parent_rec record;
   child_rec record;
   placeholder integer;
BEGIN
   SELECT INTO bm_list_rec * FROM bm_list WHERE bookmark_id = $1;
   IF bm_list_rec.parent_id IS NULL THEN
      -- Handle top-level changes
      SELECT max(local_sort_key) INTO v_last_sort_key
	FROM bm_list
	WHERE parent_id IS NULL;
      UPDATE bm_list SET parent_sort_key = NULL, local_sort_key = new_sort_key(v_last_sort_key) WHERE bookmark_id = $1;	 
   ELSE
      -- we are in a subfolder

      SELECT max(local_sort_key) INTO v_last_sort_key
	FROM bm_list
	WHERE parent_id = bm_list_rec.parent_id;
      SELECT into bm_list_parent_rec * FROM bm_list WHERE bookmark_id = bm_list_rec.parent_id;
      -- postgres does not concatenate nulls to strings
      -- so we have to test first
         IF bm_list_parent_rec.parent_sort_key is null THEN
   	    v_parent_sort_key := bm_list_parent_rec.local_sort_key;
	 ELSE
   	    v_parent_sort_key := bm_list_parent_rec.parent_sort_key || cast(bm_list_parent_rec.local_sort_key as varchar(3));
	 END IF;
      UPDATE bm_list SET parent_sort_key = v_parent_sort_key, local_sort_key = new_sort_key(v_last_sort_key) WHERE bookmark_id = $1;
   END IF;
   
   -- Recursively run on children if this is a folder.
    IF bm_list_rec.folder_p = ''t'' THEN
      FOR child_rec in SELECT * FROM bm_list WHERE parent_id = $1 LOOP
	 placeholder := bm_fixup_sort_key(child_rec.bookmark_id);
      END LOOP;
   END IF;
   RETURN 1;
END;'
language 'plpgsql';


-- finds out if bookmark is in a folder; used for connect statements
-- tcl/bookmarks.defs

create function bm_node_is_child(integer,integer) returns char as '
declare
        start   alias for $1;
        id      alias for $2;
        pid     integer;
begin
        if id = start then return ''t'';
        end if;

        select into pid parent_id from bm_list where bookmark_id = id;

        if pid is null then
           return ''f'';
        else if id = start then 
           return ''t'';
        else 
           return bm_node_is_child(start,pid);
        end if;
        end if;
end;
' language 'plpgsql';


