/usr/share/perl5/Tangram/Relational/Mappings.pod is in libtangram-perl 2.10-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 | =head1 NAME
Tangram::Relational::Mappings - Mapping inheritance
=head1 DESCRIPTION
There are many ways of representing inheritance relationships in a relational
database. This document describes three popular ways and how Tangram supports
them.
=head1 STRATEGIES FOR MAPPING INHERITANCE
Inheritance is a concept that has no equivalent in the relational
world. However, it is possible to implement it by using strict
disciplines and a combination of relational features like tables and
foreign keys.
One of the paramount issues about mapping inheritance is how well the
mapping supports polymorphism. Any Object-Oriented persistence
facility that deserves its name needs to allow the retrieval of all
the Fruits, and return a heterogeneous collection of Apples, Oranges
and Bananas. Also, it must perform this operation in an efficient
manner. In particular, polymorphic retrieval should not cost one
SELECT per retrieved object.
A secondary - yet important - issue is how well the mapping plays by
the rules of orthogonal orthodoxy.
Another issue we'll examine is how well the mapping supports 'complex'
queries, that is, queries that involve several objects.
Three strategies are in common use, that go by the name Vertical,
Horizontal and Filtered mapping. They all have advantages and
disadvantages.
The following sections describe the three strategies in details. They
make use of a simple object model to illustrate the mappings.
+---------------------+
| Person |
| {abstract} |
+---------<------- 1 +---------------------+
| | name: string |
| +---------------------+
| |
| ^
| |
| +------------------+---------------------+
| | |
| +---------------+ +-----------------+
V | NaturalPerson | | LegalPerson |
| +---------------+ +-----------------+
| | age: integer | | form: string |
| +---------------+ +-----------------+
|
|
|
| +---------------------+
+-------->-------- * | Vehicle |
| {abstract} |
+---------------------+
| make: string |
+---------------------+
|
^
|
+------------------+-------------------+
| |
+---------------+ +-----------------+
| Car | | Plane |
+---------------+ +-----------------+
| plate: string | | ident: string |
+---------------+ +-----------------+
=head1 Horizontal Mapping
=head1 description
Each I<concrete> class is mapped onto a single table. Each row in the
table describes the persistent state of one object.
The attributes are mapped onto columns, usually one column per
attribute but not necessarily. For example, collections may be stored
elsewhere (for example on a link table) and thus require no column on
the class' table.
In effect, the database looks like this:
+---------------+
| NaturalPerson |
+------+--------+-------+------+
| id | name | age |
================================
| 17 | Bill Gates | 46 |
+------+----------------+------+
| 23 | Georges Bush | 50 |
+------+----------------+------+
+-------------+
| LegalPerson |
+------+------+---------+------+
| id | name | form |
================================
| 36 | Microsoft | Inc |
+------+----------------+------+
+------+
| Car |
+------+-------+----------------+--------+
| id | owner | make | plate |
==========================================
| 12 | 17 | Saab | BILL-1 |
+------+-------+----------------+--------+
| 50 | 36 | Miata | MS-001 |
+------+-------+----------------+--------+
| 51 | 36 | Miata | MS-002 |
+------+-------+----------------+--------+
+-------+
| Plane |
+------++-----+----------------+--------+
| id | owner| make | ident |
=========================================
| 29 | 23 | Boeing | AF-001 |
+------+------+----------------+--------+
=head2 advantages
Polymorphic retrieval costs one SELECT per concrete conforming class;
retrieving all the Persons costs two SELECTs. These SELECTs, however,
don't use joins - an expensive operation. In our example, retrieving
all the Persons requires the following two SELECTs:
SELECT id, name, age FROM NaturalPerson
SELECT id, name, form FROM LegalPerson
=head2 disadvantages
This mapping is reasonable with regard to relational orthodoxy, but
not perfect: the 'name' column is present on two different tables,
with the same semantic.
The biggest drawback, however, happens when you try to perfrom complex
queries. Suppose oyu want to retrieve all the Persons (Natural- or
Legal-) that own a Vehicle of make 'Saab' (be it a Car or a
Plane). Sticking with equijoins, the cost of the operation is four
SELECTs:
SELECT NaturalPerson.id, NaturalPerson.name, NaturalPerson.age
FROM NaturalPerson, Car
WHERE Car.owner = NaturalPerson.id
SELECT NaturalPerson.id, NaturalPerson.name, NaturalPerson.age
FROM NaturalPerson, Plane
WHERE Plane.owner = NaturalPerson.id
SELECT LegalPerson.id, LegalPerson.name, LegalPerson.form
FROM LegalPerson, Car
WHERE Car.owner = LegalPerson.id
SELECT LegalPerson.id, LegalPerson.name, LegalPerson.form
FROM LegalPerson, Plane
WHERE Plane.owner = LegalPerson.id
When the depth of the hierarchies increase, the combinatory explosion
makes complex queries prohibitive.
=head1 Vertical Mapping
=head2 description
Each class has its corresponding table, which contains only the class'
direct fields. In other words, the table doesn't store the inherited
fields. Both concrete and abstract classes get a table. The state of
an object is thus scattered over several tables.
For example:
+--------+
| Person |
+------+-+------+-------+
| id | name |
=========================
| 17 | Bill Gates |
+------+----------------+
| 23 | Georges Bush |
+------+----------------+
| 36 | Microsoft |
+------+----------------+
+---------------+ +-------------+
| NaturalPerson | | LegalPerson |
+------+--------+ +-------+-----++
| id | age | | id | form |
================= ================
| 17 | 46 | | 36 | Inc |
+------+--------+ +-------+------+
| 23 | 50 |
+------+--------+
+---------+
| Vehicle |
+------+--+----+----------------+
| id | owner | make |
=================================
| 12 | 17 | Saab |
+------+-------+----------------+
| 29 | 23 | AF-001 |
+------+-------+----------------+
| 50 | 36 | Miata |
+------+-------+----------------+
| 51 | 36 | Miata |
+------+-------+----------------+
+------+ +-------+
| Car | | Plane |
+------++--------+ +-------+--------+
| id | plate | | id | ident |
================== ==================
| 12 | BILL-1 | | 29 | AF-001 |
+-------+--------+ +-------+--------+
| 50 | MS-001 |
+-------+--------+
| 51 | MS-002 |
+-------+--------+
Polymorphic retrieval is achieved by issuing one SELECT per concrete
conforming class; retrieving In our example, retrieving all the
Persons requires the following two SELECTs:
SELECT Person.id, Person.name, NaturalPerson.age
FROM Person, NaturalPerson
WHERE Person.id = NaturalPerson.id
SELECT Person.id, Person.name, LegalPerson.form
FROM Person, LegalPerson
WHERE Person.id = LegalPerson.id
This mapping sometimes needs an extra column that carries a type
identifier. In our example, we take the very resonable assumption that
Person is an abstract class. Had we decided to allow 'pure' Persons,
we would have been faced with the following problem: the Person table
would contain rows that describe pure Persons, but also rows that
describe the Person part of Natural- and LegalPersons. We would need
to filter those incomplete objects out when retrieving the pure
Persons. Thus the Person table would look like this:
+--------+
| Person |
+-----+--+---+----------------+
| id | type | name |
===============================
| 13 | 1 | Pure Person |
+-----+------+----------------+
| 17 | 2 | Bill Gates |
+-----+------+----------------+
| 23 | 2 | Georges Bush |
+-----+------+----------------+
| 36 | 3 | Microsoft |
+-----+------+----------------+
In this case, we need an extra SELECT for retrieving pure Persons:
SELECT Person.id, Person.name
FROM Person
WHERE Person.type IN (1)
=head2 advantages
From the relational point of view, this mapping is excellent: the
resulting database is in third normal form.
This mapping also supports complex queries very well. Take the Saab
owners example again: we don't need to involve the Car nor Plane
tables in the query. As a result, two SELECTs suffice:
SELECT Person.id, Person.name, NaturalPerson.age
FROM Person, NaturalPerson, Vehicle
WHERE Person.id = NaturalPerson.id AND Vehicle.owner = Person.id
SELECT Person.id, Person.name, LegalPerson.form
FROM Person, LegalPerson, Vehicle
WHERE Person.id = LegalPerson.id AND Vehicle.owner = Person.id
=head2 disadvantages
The mapping potentially has the highest performance cost: it requires
multiple SELECTs like the horizontal mapping, but in addition, these
SELECTs use joins.
=head1 Filtered Mapping
=head2 description
Entire hierarchies are mapped onto a single table. Two rows may
describe objects of different types, maybe completely unrelated. The
set of columns is the uperset of all the columns needed by all the
attributes of any of the classes involved in the mapping.
A special 'type' column contains an value that uniquely identifies the
concrete class of the object described by the row.
All the columns related to attributes that don't occur in all the
classes must be declared as NULLABLE. Indeed, the table may contain
mostly NULL values.
In our example, the database may look either like this:
+---------+
| Persons |
+-----+---+--+----------------+------+------+
| id | type | name | age | form |
=============================================
| 17 | 1 | Bill Gates | 46 | NULL |
+-----+------+----------------+------+------+
| 23 | 1 | Georges Bush | 50 | NULL |
+-----+------+----------------+------+------+
| 36 | 2 | Microsoft | NULL | Inc |
+-----+------+----------------+------+------+
+---------+
| Persons |
+-----+---+--+----------------+------+------+
| id | type | name | age | form |
=============================================
| 17 | 1 | Bill Gates | 46 | NULL |
+-----+------+----------------+------+------+
| 23 | 1 | Georges Bush | 50 | NULL |
+-----+------+----------------+------+------+
| 36 | 2 | Microsoft | NULL | Inc |
+-----+------+----------------+------+------+
| 36 | 2 | Microsoft | NULL | Inc |
+-----+------+----------------+------+------+
+----------+
| Vehicles |
+-----+----+-+-------+----------------+--------+--------+
| id | type | owner | make | plate | ident |
=========================================================
| 12 | 3 | 17 | Saab | BILL-1 | NULL |
+-----+------+-------+----------------+--------+--------+
| 29 | 4 | 23 | Boeing | NULL | AF-001 |
+-----+------+-------+----------------+--------+--------+
| 50 | 3 | 36 | Miata | MS-001 | NULL |
+-----+------+-------+----------------+--------+--------+
| 51 | 3 | 36 | Miata | MS-002 | NULL |
+-----+------+-------+----------------+--------+--------+
Retrieving all the Persons requires only one SELECT:
SELECT id, name, age, form FROM Persons
When retrieving NaturalPersons we must take care to filter out the
rows that belog to LegalPersons:
SELECT id, name, age FROM Persons WHERE type = 1
We may even decide to place unrelated hierarchies on the same table:
+---------+
| Objects |
+-----+---+--+---------------+------+------+--------+--------+--------+
| id | type | name | age | form | make | plate | ident |
=======================================================================
| 17 | 1 | Bill Gates | 46 | NULL | NULL | NULL | NULL |
+-----+------+---------------+------+------+--------+--------+--------+
| 23 | 1 | Georges Bush | 50 | NULL | NULL | NULL | NULL |
+-----+------+---------------+------+------+--------+--------+--------+
| 36 | 2 | Microsoft | NULL | Inc | NULL | NULL | NULL |
+-----+------+---------------+------+------+--------+--------+--------+
| 12 | 3 | NULL | NULL | NULL | Saab | BILL-1 | NULL |
+-----+------+---------------+------+------+--------+--------+--------+
| 29 | 4 | NULL | NULL | NULL | Boeing | NULL | AF-001 |
+-----+------+---------------+------+------+--------+--------+--------+
| 50 | 3 | NULL | NULL | NULL | Miata | MS-001 | NULL |
+-----+------+---------------+------+------+--------+--------+--------+
| 51 | 3 | NULL | NULL | NULL | Miata | MS-002 | NULL |
+-----+------+---------------+------+------+--------+--------+--------+
=head2 advantages
Polymorphic retrieval costs exactly one SELECT, regardless of the
number of conforming types. Thus this mapping potentially is the most
efficient.
=head2 disadvantages
This mapping is very questionable according to relational
orthodoxy. Even if one decides to forgo these rules, using such a
mapping takes away many of the interesting features offered by modern
RDBM systems. Because nearly all the columns must allow NULL values,
we cannot take advantage of features like referential integrity
constraints, domain constraints, indexes, etc.
Also, as the table becomes cluttered with NULL values, the relative
number of significant columns in any given row tends towards zero: we
may end up retrieving rows consisting of a little information swimming
in a sea of NULLs.
In effect, this mapping may end up hindering performance instead of
improving it in presence of deep hierarchies with many attributes.
=head1 MAPPINGS SUPPORTED BY TANGRAM
Tangram supports both vertical mapping and filtered mapping, and any
hybrid of the two.
The 'table' attribute in the class description in the Schema can be
used to put the state of several classes on the same table. The table
name defaults to the class name, resulting in a vertical mapping.
For example, the following schema:
Tangram::Relational->schema( {
classes =>
[ Person =>
{
table => 'Persons',
fields => { string => [ qw( name ) ] }
},
NaturalPerson =>
{
table => 'Persons',
fields => { int => [ qw( age ) ] }
},
LegalPerson =>
{
table => 'Persons',
fields => { string => [ qw( form ) ] }
}
] } );
...specifies a pure filtered mapping for the Person hierarchy:
CREATE TABLE Persons
(
id INTEGER NOT NULL,
PRIMARY KEY( id ),
type INTEGER NOT NULL,
form VARCHAR(255) NULL,
age INT NULL,
name VARCHAR(255) NULL
);
The following schema:
Tangram::Relational->schema( {
classes =>
[ Person =>
{
table => 'Person',
fields => { string => [ qw( name ) ] }
},
NaturalPerson =>
{
table => 'NaturalPerson',
fields => { int => [ qw( age ) ] }
},
LegalPerson =>
{
table => 'Person',
fields => { string => [ qw( form ) ] }
}
] } );
...gives NaturalPerson its own table, but LegalPerson shares the
Person table:
CREATE TABLE Person
(
id INTEGER NOT NULL,
PRIMARY KEY( id ),
type INTEGER NOT NULL,
form VARCHAR(255) NULL,
name VARCHAR(255) NULL
);
CREATE TABLE NaturalPerson
(
id INTEGER NOT NULL,
PRIMARY KEY( id ),
type INTEGER NOT NULL,
age INT NULL
);
|