Forum OpenACS Q&A: Response to Breakthrough on bookmarks module
Posted by
Bob Fuller
on 01/09/01 09:18 PM
The revised data model, bookmarks.sql
To the best of my knowledge, then, here's what's required in the file for the data model (bookmarks.sql):-- bookmarks.sql create sequence bm_url_id_seq; 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 datetime, -- the last time the site returned a "live" status last_live_date datetime ); -- this insertion is required for the change in create-folder-2.tcl -- (and possibly elsewhere): insert into bm_urls (url_id, host_url, complete_url) values (0, ' ', ' '); -- (folders in bm_list are joined to a "dummy" entry in bm_urls, as above) create sequence bm_bookmark_id_seq; 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 datetime not null, modification_date datetime, -- 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')) ); CREATE function inc_char_for_sort_key (char) returns char(2) AS ' DECLARE old_char char(2); carry_p integer; old_code INTEGER; new_code INTEGER; BEGIN old_char := $1; old_code := ascii(old_char); IF old_code = 57 THEN -- skip from 9 to A new_code := 65; carry_p := 0; ELSE IF old_code = 90 THEN -- skip from Z to a new_code := 97; carry_p := 0; ELSE IF old_code = 122 THEN -- wrap around new_code := 48; carry_p := 1; ELSE new_code := old_code + 1; carry_p := 0; END IF; END IF; END IF; --old_char := chr(new_code); old_char := ichar(new_code) || cast(carry_p as char(1)); return old_char; END; --END inc_char_for_sort_key; ' language 'plpgsql'; CREATE FUNCTION new_sort_key (CHAR(3)) RETURNS CHAR(3) AS ' DECLARE v_old_sort_key char(3); v_chr_1 char(2); v_chr_2 char(2); v_chr_3 char(2); v_carry INTEGER; BEGIN v_old_sort_key := $1; IF v_old_sort_key IS null or v_old_sort_key = '''' THEN RETURN ''000''; END IF; v_chr_1 := substr(v_old_sort_key, 1, 1); v_chr_2 := substr(v_old_sort_key, 2, 1); v_chr_3 := substr(v_old_sort_key, 3, 1); v_chr_3 := inc_char_for_sort_key(v_chr_3); v_carry := cast(substr(v_chr_3, 2, 1) as integer); v_chr_3 := substr(v_chr_3, 1, 1); IF v_carry = 1 THEN v_chr_2 := inc_char_for_sort_key(v_chr_2); v_carry := cast(substr(v_chr_2, 2, 1) as integer); v_chr_2 := substr(v_chr_2, 1, 1); IF v_carry = 1 THEN v_chr_1 := inc_char_for_sort_key(v_chr_1); v_carry := cast(substr(v_chr_1, 2, 1) as integer); v_chr_1 := substr(v_chr_1, 1, 1); END IF; END IF; RETURN trim(v_chr_1) || trim(v_chr_2) || trim(v_chr_3); END; --END new_sort_key; ' language 'plpgsql'; create function trig_bm_list_sort_key_i_tr() returns opaque as ' DECLARE v_last_sort_key bm_list.local_sort_key%TYPE; v_parent_sort_key bm_list.parent_sort_key%TYPE; 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; SELECT parent_sort_key || cast(local_sort_key as varchar(3)) INTO v_parent_sort_key FROM bm_list WHERE bookmark_id = NEW.parent_id; 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_sort_key_i_tr before INSERT ON bm_list FOR each row execute procedure trig_bm_list_sort_key_i_tr(); create table bm_list_pkg (v_updated_ids integer, v_num_entries integer); insert into bm_list_pkg (v_num_entries) values (0); create function trig_bm_list_sort_key_row_u_tr() returns opaque as ' DECLARE old_parent_id integer; new_parent_id integer; f_bookmark_id integer; f_num_entries integer; BEGIN old_parent_id := OLD.parent_id; new_parent_id := NEW.parent_id; IF old_parent_id is null THEN old_parent_id := 0; END IF; IF new_parent_id is null THEN new_parent_id := 0; END IF; IF old_parent_id != new_parent_id THEN f_bookmark_id := NEW.bookmark_id; select into f_num_entries max(v_num_entries)+1 from bm_list_pkg; insert into bm_list_pkg (v_updated_ids, v_num_entries) values (f_bookmark_id, f_num_entries); END IF; return NEW; END; ' language 'plpgsql'; CREATE trigger bm_list_sort_key_row_u_tr before UPDATE ON bm_list FOR each row execute procedure trig_bm_list_sort_key_row_u_tr(); -- the rest of the data model (functions and triggers) has moved, -- for the time being, primarily, to edit-bookmark-2.tcl! -- need two indices to support CONNECT BY create index bm_list_idx1 on bm_list(bookmark_id, parent_id); create index bm_list_idx2 on bm_list(parent_id, bookmark_id); -- and the jury's still out on these two indexes, since our data -- model has changed substantially (and does not use CONNECT BY)!