We create all database objects with scripts in the samplenote/sql/ directory. All database scripts are database-specific and are thus in either the samplenote/sql/oracle or samplenote/sql/postgresql. Packages can support Oracle, PostgreSQL, or both.
The first file is samplenote-create.sql. The package manager requires a file with the name packagekey-create.sql, which it will run automatically when the package in installed. This file should create all tables and views.
Our package is going to store all of its information in one table. It takes more than just a CREATE TABLE command, however, because we want to integrate our table with the OpenACS system. By making each record in our table an OpenACS object, we gain access to the permissions system as well as some metadata. The downside is that our table creation code must include several functions and stored procedures and is fairly complicated even for a simple table. A listing is provided below for you to cut and paste. Comments are located within the source code, with each comment preceeding the relevant code.
First, create the necessary subdirectories and add them cvs as you go.
[service0@yourserver samplenote]$ mkdir sql [service0@yourserver samplenote]$ cvs add sql Directory /cvsroot/service0/packages/samplenote/sql added to the repository [service0@yourserver samplenote]$ cd sql/ [service0@yourserver sql]$ mkdir postgresql [service0@yourserver sql]$ cvs add postgresql Directory /cvsroot/service0/packages/samplenote/sql/postgresql added to the repository [service0@yourserver sql]$ cd postgresql/ [service0@yourserver postgresql]$ emacs samplenote-create.sql
Paste this into the file and save and close.
Figure 8.2. Database Creation Script
-- -- packages/samplenote/sql/postgresql/samplenote-create.sql -- -- @author rhs@mit.edu -- @creation-date 2000-10-22 -- @cvs-id $Id: tutorial-database.html,v 1.1.2.1 2003/03/30 20:33:12 joela Exp $ -- -- /* We create a temporary function 'inline_0' which we use to create an ACS Object Type. Each record in our package is a 'samplenote', and each samplenote will be an ACS Object. By making our records ACS objects, we gain access to the permissions model and other meta-functions. Each 'samplenote' record will have a matching record in acs_objects. In general, you should make a table an ACS Object Type if you expect to apply permissions directly at that level. */ create function inline_0 () returns integer as ' begin PERFORM acs_object_type__create_type ( ''samplenote'', -- object_type ''Sample Note'', -- pretty_name ''Sample Notes'', -- pretty_plural ''acs_object'', -- supertype ''samplenote'', -- table_name ''note_id'', -- id_column null, -- package_name ''f'', -- abstract_p null, -- type_extension_table ''samplenote__name'' -- name_method ); return 0; end;' language 'plpgsql'; select inline_0 (); drop function inline_0 (); /* We create the table. Each constraint is named to make it easier to identify during debugging. The note_id is identical to the object id.*/ create table samplenote ( note_id integer constraint samplenote_fk references acs_objects(object_id) constraint samplenote_pk primary key, title varchar(255) constraint samplenote_title_nn not null, body varchar(1024) ); /* Since each record is also an Object, we make a creation function that will create an object and then use the object id to create a record in our table. The function also takes several optional input variables. Creation date will default to now; creation user should be the user id that will own the object; creation ip is optional; context id should usually be the package id, which will be explained later.*/ create function samplenote__new (varchar,varchar,timestamp,integer,varchar,integer) returns integer as ' declare p_title alias for $1; p_body alias for $2; p_creation_date alias for $3; p_creation_user alias for $4; p_creation_ip alias for $5; p_context_id alias for $6; v_samplenote_id int; begin v_samplenote_id := acs_object__new ( null, ''samplenote'', p_creation_date, p_creation_user, p_creation_ip, p_context_id ); insert into samplenote (note_id, title, body) values (v_samplenote_id, p_title, p_body); PERFORM acs_permission__grant_permission( v_samplenote_id, p_owner_id, ''admin'' ); return v_samplenote_id; end;' language 'plpgsql'; /* The __delete function deletes a record and all related overhead. */ create function samplenote__delete (integer) returns integer as ' declare p_samplenote_id alias for $1; begin delete from acs_permissions where object_id = p_samplenote_id; delete from samplenote where note_id = p_samplenote_id; raise NOTICE ''Deleting samplenote...''; PERFORM acs_object__delete(p_samplenote_id); return 0; end;' language 'plpgsql'; /* When we created the acs object type above, we specified a 'name_method'. This is the name of a function that will return the name of the object. This is a convention ensuring that all objects can be identified. Now we have to build that function. In this case, we'll return a field called title as the name. */ create function samplenote__name (integer) returns varchar as ' declare p_samplenote_id alias for $1; v_samplenote_name samplenote.title%TYPE; begin select title into v_samplenote_name from samplenote where samplenote_id = p_samplenote_id; return v_samplenote_name; end; ' language 'plpgsql';
Create a database file to drop everything if the package is uninstalled.
[service0@yourserver postgresql]$ emacs samplenote-drop.sql
Figure 8.3. Database deletion script
-- packages/samplenote/sql/samplenote-drop.sql
-- drop script
--
/* This script removes from the database everything associated with
our table. */
--drop functions
drop function samplenote__new (varchar,varchar,timestamp,integer,varchar,integer);
drop function samplenote__delete (integer);
drop function samplenote__name (integer);
--drop permissions
delete from acs_permissions where object_id in (select note_id from samplenote);
--drop objects
create function inline_0 ()
returns integer as '
declare
object_rec record;
begin
for object_rec in select object_id from acs_objects where object_type=''samplenote''
loop
perform acs_object__delete( object_rec.object_id );
end loop;
return 0;
end;' language 'plpgsql';
select inline_0();
drop function inline_0();
--drop table
drop table samplenote;
--drop type
select acs_object_type__drop_type(
'samplenote',
't'
);
Add the database files to cvs.
[service0@yourserver postgresql]$ cvs add *.sql
cvs add: scheduling file `samplenote-create.sql' for addition
cvs add: scheduling file `samplenote-drop.sql' for addition
cvs add: use 'cvs commit' to add these files permanently
[service0@yourserver samplenote]$ cvs commit -m "new database files"
cvs commit: Examining .
cvs commit: Examining sql
cvs commit: Examining sql/postgresql
cvs commit: Examining www
cvs commit: Examining www/doc
cvs commit: Examining www/doc/xml
RCS file: /cvsroot/service0/packages/samplenote/sql/postgresql/samplenote-create.sql,v
done
(many lines omitted)
done
[service0@yourserver samplenote]$
Now run the create script manually to add your tables and functions.
[service0@yourserver samplenote]$ cd sql/postgresql/ [service0@yourserver postgresql]$ psql -f samplenote-create.sql CREATE inline_0 ---------- 0 (1 row) DROP psql:samplenote-create.sql:51: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index 'samplenote_pk' for table 'samplenote' psql:samplenote-create.sql:51: NOTICE: CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s) CREATE CREATE CREATE CREATE [service0@yourserver postgresql]$
If there are errors, use them to debug the sql file and try again. Once you get the same output as shown above, test the drop script:
[service0@yourserver postgresql]$ psql -f samplenote-drop.sql DROP DROP DROP DELETE 0 CREATE inline_0 ---------- 0 (1 row) DROP psql:samplenote-drop.sql:33: NOTICE: DROP TABLE implicitly drops referential integrity trigger from table "acs_objects" psql:samplenote-drop.sql:33: NOTICE: DROP TABLE implicitly drops referential integrity trigger from table "acs_objects" DROP acs_object_type__drop_type ---------------------------- 0 (1 row) [service0@yourserver postgresql]$
Once both scripts are working without errors, run the create script one last time and proceed.