6
use Tangram::Springfield;
13
getopts('pxtw', \%opt);
15
$Tangram::TRACE = \*STDOUT if exists $opt{t};
17
my @cp = qw( dbi:Pg:dbname=tour tangram tangram );
18
my $cp = join(', ', map { "'$_'" } @cp);
20
my ( $schema, $dbh, $storage, @kids, $marge, $homer, $homer_id,
21
$ned_id, @sisters_id, $ned, @sisters, @pairs, $patty, $selma, $burns
26
open STDOUT, '>Tangram/Tour.pod';
29
my $tour = join '', <DATA>;
39
$tour =~ s[\@cp][$cp]g;
42
system 'dropdb -q -U postgres tour 2>/dev/null';
43
system 'createdb -q -U postgres tour';
45
while ($tour =~ m[ {{ (.*?) }} ]sgx) {
49
$show =~ s/^/ sprintf "%03d: ", $line++/gem;
50
print "executing:\n\n$show";
51
eval "do { use strict; $chunk }";
53
print "\n", '-' x 40, "\n\n";
71
In this tour, we add persistence to a simple Person design.
73
A Person is either a NaturalPerson or a LegalPerson. Persons (in
74
general) have a collection of addresses.
76
An address consists in a type (a string) and a city (also a string).
78
NaturalPerson - a subclass of Person - represents persons of flesh and
79
blood. NaturalPersons have a name and a firstName (both strings) and
80
an age (an integer). NaturalPersons sometimes have a partner (another
81
NaturalPerson) and even children (a collection of NaturalPersons).
83
LegalPerson - another subclass of Person - represents companies and
84
other entities that the law regards as 'persons'. A LegalPerson has a
85
name (a string) and a manager (a NaturalPerson).
87
All this is expressed in the following UML diagram:
90
+---------------------+ +--------------+
91
| Person | | Address |
92
| { abstract } |1<>-->-*|--------------|
93
|---------------------| | kind: string |
94
+---------------------+ | city: string |
97
+--------------A--------------+
99
+-------------------+ +---------------+
100
+--*| NaturalPerson | | LegalPerson |
101
| |-------------------|manager |---------------|
102
V | firstName: string |1---<-----1| name: string |
103
| | name: string | +---------------+
105
children +-------------------+
111
B<Note that Tangram does I<not> create the corresponding Perl
112
packages!>. That's up to the user. However, to facilitate
113
experimentation, Tangram comes with a module that implements the
114
necessary classes. For more information see L<Tangram::Springfield>.
116
Before we can actually store objects we must complete two steps:
130
=head2 Creating a Schema
132
A Schema object contains information about the persistent
133
aspects of a system of classes.
135
It also gives a degree of control over the way Tangram performs the
136
object-relational mapping, but in this tour we will use all the defaults.
138
Here is the Schema for Springfield:
141
$schema = Tangram::Relational->schema( {
150
addresses => { class => 'Address', aggreg => 1 } }
156
string => [ qw( kind city ) ],
162
bases => [ qw( Person ) ],
165
string => [ qw( firstName name ) ],
166
int => [ qw( age ) ],
167
ref => [ qw( partner ) ],
168
array => { children => 'NaturalPerson' },
173
bases => [ qw( Person ) ],
176
string => [ qw( name ) ],
177
ref => [ qw( manager ) ],
183
The Schema lists all the classes that need persistence, along with
184
their attributes and the inheritance relationships. We must provide
185
type information for the attributes, because SQL is more typed than
186
Perl. We also tell Tangram that C<Person> is an abstract class, so it
187
wastes no time attempting to retrieve objects of that exact class.
189
Note that Tangram cannot deduce this information by itself. While Perl
190
makes it possible to extract the list of all the classes in an
191
application, in general not all classes will need to persist. A class
192
may have both persistent and non-persistent bases. As for attributes,
193
Perl's most typical representation for objects - a hash - even allows
194
two objects of the same class to have a different set of attributes.
196
For more information on creating Schemas, see L<Tangram::Relational>
197
and L<Tangram::Schema>.
199
=head2 Setting up a database
201
Now we create a database. The simplest way is to create an
202
empty database and let Tangram initialize it:
209
Tangram::Relational->deploy($schema, $dbh );
214
Tangram::Relational is the vanilla object-relational backend. It
215
assumes that the database understands standard SQL, and that both the
216
database and the related DBI driver fully implements the DBI
219
Tangram also comes with vendor-specific backends for Mysql and
220
Sybase. When a vendor-specific backend exists, it should be used in
221
place of the vanilla backend.
223
For more information, see L<Tangram::Relational>, L<Tangram::Sybase>
224
and L<Tangram::mysql>.
226
=head2 Connecting to a database
228
We are now ready to store objects. First we connect to the database,
229
using the class method Tangram::Relational::connect (or
230
Tangram::mysql::connect for Mysql).
232
The first argument of connect() the schema object; the others are
233
passed directly to DBI::connect. The method returns a Tangram::Storage
234
object that will be used to communicate with the database.
239
$storage = Tangram::Relational->connect( $schema,
243
connects to a database named Springfield via the vanilla Relational
244
backend, using a specific account and password.
246
For more information on connecting to databases, see L<Tangram::Relational> and
249
=head2 Inserting objects
251
Now we can populate the database:
254
$storage->insert( NaturalPerson->new(
255
firstName => 'Montgomery', name => 'Burns' ) );
258
This inserts a single NaturalPerson object into the database. We can
259
insert several objects in one call:
263
NaturalPerson->new( firstName => 'Patty', name => 'Bouvier' ),
264
NaturalPerson->new( firstName => 'Selma', name => 'Bouvier' ) );
267
Sometimes Tangram saves objects implicitly:
271
NaturalPerson->new( firstName => 'Bart', name => 'Simpson' ),
272
NaturalPerson->new( firstName => 'Lisa', name => 'Simpson' ) );
274
$marge = NaturalPerson->new(
275
firstName => 'Marge', name => 'Simpson',
278
kind => 'residence', city => 'Springfield' ) ],
279
children => [ @kids ] );
281
$homer = NaturalPerson->new( firstName => 'Homer', name => 'Simpson',
284
kind => 'residence', city => 'Springfield' ),
286
kind => 'work', city => 'Springfield' ) ],
287
children => [ @kids ] );
289
$homer->{partner} = $marge;
290
$marge->{partner} = $homer;
292
$homer_id = $storage->insert( $homer );
295
In the process of saving Homer, Tangram detects that it contains
296
references to objects that are not persistent yet (Marge, the
297
addresses and the kids), and inserts them automatically. Note that
298
Tangram can handle cycles: Homer and Marge refer to each other.
300
insert() returns an object id, or a list of object ids, that uniquely
301
identify the object(s) that have been inserted.
303
For more information on inserting objects, see L<Tangram::Storage>.
305
=head2 Updating objects
307
Updating works pretty much the same as inserting:
310
my $maggie = NaturalPerson->new(
311
firstName => 'Maggie', name => 'Simpson' );
313
push @{ $homer->{children} }, $maggie;
314
push @{ $marge->{children} }, $maggie;
316
$storage->update( $homer, $marge );
319
Here again Tangram detects that Maggie is not already persistent in
320
$storage and automatically inserts it. Note that we need to update
321
Marge explicitly because she was already persistent.
323
For more information on updating objects, see L<Tangram::Storage>.
325
=head2 Memory management
327
...is still up to you. Tangram won't break in-memory cycles, it's a
328
persistence tool, not a memory management tool. Let's make sure we
332
$homer->{partner} = undef; # do this before $homer goes out of scope
335
Also, when we're finished with a storage, we can explicitly disconnect it:
338
$storage->disconnect();
341
Whether it's important or not to disconnect the Storage depends on
342
what version of Perl you use. If it's prior to 5.6, you I<must>
343
disconnect the storage explicitly (or at least call unload())
344
otherwise the Storage will prevent the objects it controls from being
345
reclaimed by Perl. For more information see see L<Tangram::Storage>.
347
=head2 Finding objects
349
After reconnecting to Springfield, we now want to retrieve some objects.
350
But how do we find them? Basically there are three options
360
We obtain them from another object.
370
When an object is inserted, Tangram assigns an identifier to it.
371
IDs are numbers that uniquely identify objects in the database.
372
C<insert> returns the ID(s) of the object(s) it was passed:
375
$storage = Tangram::Relational->connect( $schema,
378
$ned_id = $storage->insert( NaturalPerson->new(
379
firstNname => 'Ned', name => 'Flanders' ) );
381
@sisters_id = $storage->insert(
382
NaturalPerson->new( firstName => 'Patty', name => 'Bouvier' ),
383
NaturalPerson->new( firstName => 'Selma', name => 'Bouvier' ) );
386
This enables us to retrieve the objects:
389
$ned = $storage->load( $ned_id );
390
@sisters = $storage->load( @sisters_id );
393
For more information on loading objects by id, see L<Tangram::Storage>.
395
=head2 Obtaining objects from other objects
397
Once Homer has been restored to his previous state, including his relations
398
with his family. Thus we can say:
401
$storage = Tangram::Relational->connect( $schema,
404
$homer = $storage->load( $homer_id ); # load by id
406
$marge = $homer->{partner};
407
@kids = @{ $homer->{children} };
410
Actually, when Tangram loads an object that contains references to
411
other persistent objects, it doesn't retrieve the referenced objects
412
immediately. Marge is retrieved only when Homer's 'partner' field is
413
accessed. This mechanism is almost totally transparent, we'd have to
414
use C<tied> to observe a non-present collection or reference.
416
For more information on relationships, see L<Tangram::Schema>,
417
L<Tangram::Ref>, L<Tangram::Array>, L<Tangram::IntrArray>,
418
L<Tangram::Set> and L<Tangram::IntrSet>.
422
To retrieve all the objects of a given class, we use C<select>:
425
$storage = Tangram::Relational->connect( $schema,
428
my @people = $storage->select( 'NaturalPerson' );
431
Tangram supports polymorphic retrieval. Let's first insert a
435
$storage->insert( LegalPerson->new(
436
name => 'Springfield Nuclear Power Plant', manager => $burns ) );
440
Now we can retrieve all the Persons - Natural or Legal - by making a
441
single call to select(), passing it the base class name:
444
my @all = $storage->select( 'Person' );
447
For more information on select(), see L<Tangram::Storage>.
451
Usually we won't want to load I<all> the NaturalPersons, only those
452
objects that satisfy some condition. Say, for example, that we want to
453
load only the NaturalPersons whose name field is 'Simpson'. Here's how
457
my $person = $storage->remote( 'NaturalPerson' );
458
my @simpsons = $storage->select( $person, $person->{name} eq 'Simpson' );
461
This will bring in memory only the Simpsons; Burns or the Bouvier
462
sisters won't turn up. The filtering happens on the database server
463
side, not in Perl space. Internally, Tangram translates the
464
C<$person->{name} eq 'Simpson'> clause into a piece of SQL code that
465
is passed down to the database.
467
The above example only begins to scratch the surface of Tangram's
468
filtering capabilities. The following examples are all legal and working code:
471
# find all the persons *not* named Simpson
473
my $person = $storage->remote( 'NaturalPerson' );
474
my @others = $storage->select( $person, $person->{name} ne 'Simpson' );
476
# same thing in a different way
478
my $person = $storage->remote( 'NaturalPerson' );
479
my @others = $storage->select( $person, !($person->{name} eq 'Simpson') );
481
# find all the persons who are older than me
483
my $person = $storage->remote( 'NaturalPerson' );
484
my @elders = $storage->select( $person, $person->{age} > 35 );
486
# find all the Simpsons older than me
488
my $person = $storage->remote( 'NaturalPerson' );
489
my @simpsons = $storage->select( $person,
490
$person->{name} eq 'Simpson' & $person->{age} > 35 );
492
# find Homer's wife - note that select *must* be called in list context
494
my ($person1, $person2) = $storage->remote(
495
qw( NaturalPerson NaturalPerson ));
497
my ($marge) = $storage->select( $person1,
498
$person1->{partner} == $person2
499
& $person2->{firstName} eq 'Homer' & $person2->{name} eq 'Simpson' );
501
# find Homer's wife - this time Homer is already in memory
503
my $homer = $storage->load( $homer_id );
504
my $person = $storage->remote( 'NaturalPerson' );
506
my ($marge) = $storage->select( $person,
507
$person->{partner} == $homer );
509
# find everybody who works in Springfield
511
my $address = $storage->remote( 'Address' );
513
my @population = $storage->select( $person,
514
$person->{addresses}->includes( $address )
515
& $address->{kind} eq 'work'
516
& $address->{city} eq 'Springfield');
518
# find the parents of Bart Simpson
520
my ($person1, $person2) = $storage->remote(
521
qw( NaturalPerson NaturalPerson ));
523
my @parents = $storage->select( $person1,
524
$person1->{children}->includes( $person2 )
525
& $person2->{firstName} eq 'Bart'
526
& $person2->{name} eq 'Simpson' );
529
my ($bart) = $storage->select( $person1, $person1->{firstName} eq 'Bart');
531
# find the parents of Bart, this time given an object already loaded
532
my $person = $storage->remote( 'NaturalPerson' );
534
@parents = $storage->select( $person,
535
$person->{children}->includes( $bart ) );
538
Note that Tangram uses a single ampersand (&) or vertical bar (|) to
539
represent logical conjunction or disjunction, not the usual && or
540
||. This is due to a limitation in Perl's operator overloading
541
mechanism. Make sure you never forget this, because, unfortunately,
542
using && or || in place of & or | is not even a syntax error :(
544
Finally, Tangram make it possible to retrieve tuples of related objects:
547
my ($parent, $child) = $storage->remote('NaturalPerson', 'NaturalPerson');
549
@pairs = $storage->select( [ $parent, $child ],
550
$parent->{children}->includes($child) );
553
@pairs contains a list of references to arrays of size two; each array
554
contains a pair of parent and child.
556
For more information on filters, see L<Tangram::Expr> and L<Tangram::Remote>.
560
Cursors provide a way of retrieving objects one at a time. This is
561
important is the result set is potentially large. cursor() takes the
562
same arguments as select() and returns a Cursor objects that can be
563
used to iterate over the result set via methods current() and next():
566
$storage = Tangram::Relational->connect( $schema,
569
# iterate over all the NaturalPersons in storage
571
my $cursor = $storage->cursor( 'NaturalPerson' );
573
while (my $person = $cursor->current())
582
The Cursor will be automatically closed when $cursor is garbage-collected,
583
but Perl doesn't define just when that may happen :( Thus it's a good idea to
584
explicitly close the cursor.
586
Each Cursor uses a separate connection to the database. Consequently you can
587
have several cursors open at the same, all with pending results. Of course,
588
mixing reads and writes to the same tables can result in deadlocks.
590
For more information on cursors, see L<Tangram::Storage> and
593
=head2 Remote objects
595
At this point, most people wonder what $person I<exactly> is and how
596
it all works. This section attempts to give an idea of the mechanisms
599
In Tangram terminology, $person a I<remote> object. Its Perl class is
600
Tangram::Remote, but it's really a placeholder for an object of class
601
C<NaturalPerson> I<in the database>, much like a table alias in
604
When you request a remote object of a given class, Tangram arranges
605
that the remote object I<looks like> an object of the said class. It
606
I<seems> to have the same fields as a regular object, but don't be
607
misled, it's not the real thing, it's just a way of providing a nice
610
If you dig it, you'll find out that a Remote is just a hash of
611
Tangram::Expr objects. When you say $homer->{name}, an Expr is
612
returned, which, most of the time, can be used like any ordinary Perl
613
scalar. However, an Expr represents a value I<in the database>, it's
614
the equivalent of Remote, only for expressions, not for objects.
616
Expr objects that represent scalar values (e.g. ints, floats, strings)
617
can be compared between them, or compared with straight Perl
618
scalars. Reference-like Exprs can be compared between themselves and
621
Expr objects that represent collections have an C<include> methods
622
that take a persistent object, a Remote object or an ID.
624
The result of comparing Exprs (or calling C<include>) is a
625
Tangram::Filter that will translate into part of the SQL where-clause
626
that will be passed to the RDBMS.
628
For more information on remote objects, see L<Tangram::Remote>.
630
=head2 Multiple loads
632
What happens when we load the same object twice? Consider:
635
my $person = $storage->remote( 'NaturalPerson' );
636
my @simpsons = $storage->select( $person, $person->{name} eq 'Simpson' );
638
my @people = $storage->select( 'NaturalPerson' );
641
Obviously Homer Simpson will be retrieved by both selects. Are there
642
two Homers in memory now? Fortunately not. There is only one copy of
643
Homer in memory. When Tangram load an object, it checks whether an
644
object with the same ID is alredy present. If yes, it keeps the old
645
copy, which is desirable, since we may have changed it already.
647
Incidentally, this explains why a Storage will hold objects in memory
648
- until disconnected (again, this will change when Perl supports weak
653
Tangram wraps database transactions in a object-oriented interface:
655
$storage->tx_start();
656
$homer->{partner} = $marge;
657
$marge->{partner} = $homer;
658
$storage->update( $homer, $marge );
659
$storage->tx_commit();
661
Both Marge and Homer will be updated, or none will. tx_rollback() drops
664
Tangram does not emulate transactions for databases that do not
665
support them (like earlier versions of mySql).
667
Unlike DBI, Tangram allows the nested transactions:
669
$storage->tx_start();
672
$storage->tx_start();
673
$patty->{partner} = $selma;
674
$selma->{partner} = $patty;
675
$storage->tx_commit();
678
$homer->{partner} = $marge;
679
$marge->{partner} = $homer;
680
$storage->update( $homer, $marge );
682
$storage->tx_commit();
684
Tangram uses a single database transaction, but commits it only when
685
the tx_commit()s exactly balance the tx_start()s. Thanks to this
686
feature any piece of code can open all the transactions it needs and
687
still cooperate smoothly with the rest of the application. If a DBI
688
transaction is already active, it will be reused; otherwise a new one
691
Tangram offer a more robust alternative to the start/commit code
692
sandwich. tx_do() calls CODEREF in a transaction. If the CODEREF
693
dies, the transaction is rolled back; otherwise it's committed. The
694
first example can be rewritten:
696
$storage->tx_do( sub {
697
$homer->{partner} = $marge;
698
$marge->{partner} = $homer;
699
$storage->update( $homer, $marge };
702
For more information on transactions, see L<Tangram::Storage>.