Setting Up Database Objects

by Joel Aufrecht
OpenACS docs are written by the named authors, but may be edited by OpenACS documentation staff.

Code the data model

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.

View comments on this page at openacs.org