1#
2# Demonstrate how SIGNAL can be used to enforce integrity constraints.
3#
4
5# Naming:
6# - PO: Purchase Order
7# - AB: Address Book
8# - IN: Inventory
9
10# Simplified schema:
11#
12# Relation 1:
13# PO_ORDER (PK: po_id) 1:1 <---> 0:N (FK: po_id) PO_ORDER_LINE
14#
15# Relation 2:
16# IN_INVENTORY (PK: item_id) 1:1 <---> 0:N (FK: item_id) PO_ORDER_LINE
17#
18# Relation 3:
19#                               +--> 0:1 (PK: person_id) AB_PHYSICAL_PERSON
20# PO_ORDER (FK: cust_id) 1:1 <--|
21#                               +--> 0:1 (PK: company_id) AB_MORAL_PERSON
22# This is an 'arc' relationship :)
23#
24
25
26--disable_warnings
27drop database if exists demo;
28--enable_warnings
29
30create database demo;
31
32use demo;
33
34create table ab_physical_person (
35  person_id integer,
36  first_name VARCHAR(50),
37  middle_initial CHAR,
38  last_name VARCHAR(50),
39  primary key (person_id));
40
41create table ab_moral_person (
42  company_id integer,
43  name VARCHAR(100),
44  primary key (company_id));
45
46create table in_inventory (
47  item_id integer,
48  descr VARCHAR(50),
49  stock integer,
50  primary key (item_id));
51
52create table po_order (
53  po_id integer auto_increment,
54  cust_type char, /* arc relationship, see cust_id */
55  cust_id integer, /* FK to ab_physical_person *OR* ab_moral_person */
56  primary key (po_id));
57
58create table po_order_line (
59  po_id integer, /* FK to po_order.po_id */
60  line_no integer,
61  item_id integer, /* FK to in_inventory.item_id */
62  qty integer);
63
64delimiter $$;
65
66--echo #
67--echo # Schema integrity enforcement
68--echo #
69
70create procedure check_pk_person(in person_type char, in id integer)
71begin
72  declare x integer;
73  declare msg varchar(128);
74
75  /*
76    Test integrity constraints for an 'arc' relationship.
77    Based on 'person_type', 'id' points to either a
78    physical person, or a moral person.
79  */
80  case person_type
81    when 'P' then
82    begin
83      select count(person_id) from ab_physical_person
84        where ab_physical_person.person_id = id
85        into x;
86
87      if (x != 1)
88      then
89        set msg= concat('No such physical person, PK:', id);
90        SIGNAL SQLSTATE '45000' SET
91          MESSAGE_TEXT = msg,
92          MYSQL_ERRNO = 10000;
93      end if;
94    end;
95
96    when 'M' then
97    begin
98      select count(company_id) from ab_moral_person
99        where ab_moral_person.company_id = id
100        into x;
101
102      if (x != 1)
103      then
104        set msg= concat('No such moral person, PK:', id);
105        SIGNAL SQLSTATE '45000' SET
106          MESSAGE_TEXT = msg,
107          MYSQL_ERRNO = 10000;
108      end if;
109    end;
110
111    else
112    begin
113      set msg= concat('No such person type:', person_type);
114      SIGNAL SQLSTATE '45000' SET
115        MESSAGE_TEXT = msg,
116        MYSQL_ERRNO = 20000;
117    end;
118  end case;
119end
120$$
121
122create procedure check_pk_inventory(in id integer)
123begin
124  declare x integer;
125  declare msg varchar(128);
126
127  select count(item_id) from in_inventory
128    where in_inventory.item_id = id
129    into x;
130
131  if (x != 1)
132  then
133    set msg= concat('Failed integrity constraint, table in_inventory, PK:',
134                    id);
135    SIGNAL SQLSTATE '45000' SET
136      MESSAGE_TEXT = msg,
137      MYSQL_ERRNO = 10000;
138  end if;
139end
140$$
141
142create procedure check_pk_order(in id integer)
143begin
144  declare x integer;
145  declare msg varchar(128);
146
147  select count(po_id) from po_order
148    where po_order.po_id = id
149    into x;
150
151  if (x != 1)
152  then
153    set msg= concat('Failed integrity constraint, table po_order, PK:', id);
154    SIGNAL SQLSTATE '45000' SET
155      MESSAGE_TEXT = msg,
156      MYSQL_ERRNO = 10000;
157  end if;
158end
159$$
160
161create trigger po_order_bi before insert on po_order
162for each row
163begin
164  call check_pk_person(NEW.cust_type, NEW.cust_id);
165end
166$$
167
168create trigger po_order_bu before update on po_order
169for each row
170begin
171  call check_pk_person(NEW.cust_type, NEW.cust_id);
172end
173$$
174
175create trigger po_order_line_bi before insert on po_order_line
176for each row
177begin
178  call check_pk_order(NEW.po_id);
179  call check_pk_inventory(NEW.item_id);
180end
181$$
182
183create trigger po_order_line_bu before update on po_order_line
184for each row
185begin
186  call check_pk_order(NEW.po_id);
187  call check_pk_inventory(NEW.item_id);
188end
189$$
190
191--echo #
192--echo # Application helpers
193--echo #
194
195create procedure po_create_order(
196  in p_cust_type char,
197  in p_cust_id integer,
198  out id integer)
199begin
200  insert into po_order set cust_type = p_cust_type, cust_id = p_cust_id;
201  set id = last_insert_id();
202end
203$$
204
205create procedure po_add_order_line(
206  in po integer,
207  in line integer,
208  in item integer,
209  in q integer)
210begin
211  insert into po_order_line set
212    po_id = po, line_no = line, item_id = item, qty = q;
213end
214$$
215
216delimiter ;$$
217
218--echo #
219--echo # Create sample data
220--echo #
221
222insert into ab_physical_person values
223  ( 1, "John", "A", "Doe"),
224  ( 2, "Marry", "B", "Smith")
225;
226
227insert into ab_moral_person values
228  ( 3, "ACME real estate, INC"),
229  ( 4, "Local school")
230;
231
232insert into in_inventory values
233  ( 100, "Table, dinner", 5),
234  ( 101, "Chair", 20),
235  ( 200, "Table, coffee", 3),
236  ( 300, "School table", 25),
237  ( 301, "School chairs", 50)
238;
239
240select * from ab_physical_person order by person_id;
241select * from ab_moral_person order by company_id;
242select * from in_inventory order by item_id;
243
244--echo #
245--echo # Entering an order
246--echo #
247
248set @my_po = 0;
249
250/* John Doe wants 1 table and 4 chairs */
251call po_create_order("P", 1, @my_po);
252
253call po_add_order_line (@my_po, 1, 100, 1);
254call po_add_order_line (@my_po, 2, 101, 4);
255
256/* Marry Smith wants a coffee table */
257call po_create_order("P", 2, @my_po);
258
259call po_add_order_line (@my_po, 1, 200, 1);
260
261--echo #
262--echo # Entering bad data in an order
263--echo #
264
265# There is no item 999 in in_inventory
266--error 10000
267call po_add_order_line (@my_po, 1, 999, 1);
268
269--echo #
270--echo # Entering bad data in an unknown order
271--echo #
272
273# There is no order 99 in po_order
274--error 10000
275call po_add_order_line (99, 1, 100, 1);
276
277--echo #
278--echo # Entering an order for an unknown company
279--echo #
280
281# There is no moral person of id 7
282--error 10000
283call po_create_order("M", 7, @my_po);
284
285--echo #
286--echo # Entering an order for an unknown person type
287--echo #
288
289# There is no person of type X
290--error 20000
291call po_create_order("X", 1, @my_po);
292
293/* The local school wants 10 class tables and 20 chairs */
294call po_create_order("M", 4, @my_po);
295
296call po_add_order_line (@my_po, 1, 300, 10);
297call po_add_order_line (@my_po, 2, 301, 20);
298
299# Raw data
300select * from po_order;
301select * from po_order_line;
302
303# Creative reporting ...
304
305select po_id as "PO#",
306  ( case cust_type
307  when "P" then concat (pp.first_name,
308                   " ",
309                   pp.middle_initial,
310                   " ",
311                   pp.last_name)
312  when "M" then mp.name
313  end ) as "Sold to"
314  from po_order po
315  left join ab_physical_person pp on po.cust_id = pp.person_id
316  left join ab_moral_person mp on po.cust_id = company_id
317;
318
319select po_id as "PO#",
320  ol.line_no as "Line",
321  ol.item_id as "Item",
322  inv.descr as "Description",
323  ol.qty as "Quantity"
324  from po_order_line ol, in_inventory inv
325  where inv.item_id = ol.item_id
326  order by ol.item_id, ol.line_no;
327
328drop database demo;
329
330
331