Lecture overheads

advertisement
PostgreSQL dungeon with table
inheritance and constraints
Edel Sherratt
From Before
•
•
•
•
•
•
•
location{ name, description, is_lit }
exit{ name, description, is_open, exits_from, leads_to }
exit_pair{name_1, exits_from_1, name_2, exits_from_2}
item{ name, description, kind, is_lit, is_at, held_by }
character{ name, description, kind, location }
Nowhere location: (nowhere, ”, false)
Self character: (me, myself, person, my location);
description and kind should either be defaults or perhaps
chosen by the player from a menu
• Nobody character: (nobody, ”, nonentity, nowhere)
Location
• create table location (
name varchar(20) primary key,
description text,
is_lit boolean );
• insert into location (name, description, is_lit)
values ('nowhere', ' ', false);
Exit
• create table exit (
name varchar(20),
description text,
is_open boolean,
exits_from varchar(20)
references location(name),
leads_to varchar(20)
references location(name),
primary key(exits_from, name) );
Character with Table Inheritance
• create table character (
name varchar(20) primary key,
description text,
location varchar(20)
references location(name));
• create table monster () inherits (character);
• create table player () inherits (character);
Problems
• Primary and foreign key constraints are not
inherited (hopefully will be in future
PostgreSQL releases)
• Work round this using functions and triggers
Primary key: Character and
descendants
• /* character.name is a primary key; pkey_character_name checks
the inheritance hierarcy from character to ensure that name is
unique and not null */
• create function pkey_character_name() returns trigger as
$pkey_character_name$
BEGIN
if (exists (select * from character
where character.name = NEW.name))
then raise exception
'Cannot have more than one character named %.',
NEW.name;
end if;
return NEW;
END
$pkey_character_name$ language plpgsql;
Triggering the not null and unique
checks on monster.name
• create table monster () inherits (character);
• create trigger pkey_character_name
before insert on monster
for each row
execute procedure pkey_character_name();
• The same is needed for other descendants of
character (such as player)
Foreign key reference to location:
character and descendants
• create function valid_location() returns trigger as
$valid_location$
BEGIN
if not exists
(select name from location
where location.name = NEW.location)
then raise exception
'There is no location called %', NEW.location;
end if;
return NEW;
END $valid_location$ language plpgsql;
Triggering the referential integrity
constraint on character.location
• create trigger valid_location
before insert on monster
for each row
execute procedure valid_location();
• The same is done for player
• And the same for item, which also refers to
location.name
• And for the descendants of item
Item with table inheritance
• create table item (
name varchar (20) not null,
description text,
location varchar (20)
references location(name));
• create table portable_item (
held_by varchar (20)
) inherits (item);
More descendants of item
• create table light_source (is_lit boolean)
inherits (item);
• create table portable_light_source ()
inherits (portable_item, light_source);
• And each of these has triggers to enforce
entity and referential integrity constraints.
A domain-specific constraint
• /* The location of a portable item is the same as the
location of its holder. When a new portable item is added
to the database, its location is set to the location of its
holder. */
• create function no_bilocation () returns trigger as
$no_bilocation$
BEGIN
if (NEW.held_by != 'nobody‘ then
NEW.location :=
(select location from character
where character.name = NEW.held_by);
end if;
return NEW;
END $no_bilocation$ language plpgsql;
Triggering ‘no_bilocation’
• create trigger no_bilocation
before insert on portable_item
for each row
execute procedure no_bilocation();
• create trigger no_bilocation
before insert on portable_light_source
for each row
execute procedure no_bilocation();
Another domain-specific constraint
• /* when a character changes location, all the portable
items held by that character should move as well. */
• create function move_portable_items ()
returns trigger as
$move_portable_items$
BEGIN
update portable_item
set location = NEW.location
where portable_item.held_by = NEW.name;
return NEW;
END
$move_portable_items$ language plpgsql;
Triggering ‘move_portable_items’
• create trigger move_portable_items
after update on character
for each row
execute procedure move_portable_items();
Yet another domain-specific constraint
• /* no_remote_pickup ensures that the held_by attribute of a
portable item can only be updated to the name of a holder whose
location is the same as that of the item; in other words, a character
must move to the place where an item is before picking up the
item. */
• create function no_remote_pickup() returns trigger as
$no_remote_pickup$
BEGIN
if NEW.location !=
(select location from character
where character.name = NEW.held_by)
then raise exception '% must move to % in order to pick up %',
NEW.held_by, NEW.location, NEW.name;
end if;
return NEW;
END $no_remote_pickup$ language plpgsql;
Table Inheritance
• Convenient, but with some problems
– Check constraints and not null constraints are
inherited, but other kinds of constraints are not
– Unique, Primary key and foreign key constraints
are not inherited
• Some SQL commands default to accessing
descendants; others do not
• Commands that default to accessing
descendants use ONLY to avoid doing so
User defined composite types
• PostgreSQL also enables user defined
composite types
• Composite types allow table elements to
contain structured data
• Composite types are a kind of user defined
type like those discussed in connection with
object-relational database management
systems.
Functions and Triggers
• Primary use: to implement domain-specific
constraints at the database level
• Also used to work round lack of constraint
inheritance in this example
• Typically:
– Define a function that returns a named trigger
– Then add that trigger to one or more tables
Conclusion
• Modern relational database management
systems provide various extras
• But it is important to weigh up the benefits of
these against their costs
Download