5. ORM useful for …
• dynamic SQL generation
– navigation between tables
– generate complex SQL queries from Perl datastructures
– better than phrasebook or string concatenation
• automatic data conversions (inflation / deflation)
• transaction encapsulation
• data validation
• computed fields
• caching
• expansion of tree data structures coded in the relational model
• …
6. CPAN ORM Landscape
• Discussed here
– DBIx::Class (a.k.a. DBIC)
– DBIx::DataModel (a.k.a. DBIDM)
DISCLAIMER
- I'm not an expert of DBIC
- I'll try to be neutral in comparisons, but …
- Won't cover all topics
• Many other
– Rose::DB, Jifty::DBI, Fey::ORM, ORM, DBIx::ORM::Declarative,
Tangram, Coat::Persistent, ORLite,
DBR, DBIx::Sunny, DBIx::Skinny, DBI::Easy, …
7. A bit of 2005 history
Class::DBI (1999)
SQL::Abstract (2001)
DBIx::DataModel private (feb.05)
Class::DBI::Sweet (29.05.05)
- SQL::Abstract
Class::DBI::Sweet 0.02 (29.06.05)
-prefetch joins
DBIx::Class 0.01 (08.08.05)
- SQL::Abstract
DBIx::Class 0.03 (19.09.05) DBIx::DataModel 0.10 (16.09.05)
- prefetch joins
8. Some figures
• DBIC • DBIDM
– thousands of users – a couple of users
– dozens of contributors – 1 contributor
– 162 files – 31 files
– 167 packages – 40 packages
– 1042 subs/methods – 175 subs/methods
– 16759 lines of Perl – 3098 lines of Perl
– 1817 comment lines – 613 comment lines
– 19589 lines of POD – 8146 lines of POD
– 70 transitive dependencies – 54 transitive dependencies
9. Our example : CPAN model
straight from CPAN::SQLite
Author
1
*
1 contains ► 1..*
Distribution Module
* *
depends_on ►
10. Département
Office
Schema definition
29.06.12 - Page 1
11. DBIC Schema class
Département
Office
use utf8;
package FPW12::DBIC;
use strict;
use warnings;
use base 'DBIx::Class::Schema';
__PACKAGE__->load_namespaces;
1;
29.06.12 - Page 1
12. DBIC 'Dist' table class
Département
Office
use utf8;
package FPW12::DBIC::Result::Dist;
use strict; use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->table("dists");
__PACKAGE__->add_columns(
dist_id => { data_type => "integer", is_nullable => 0,
is_auto_increment => 1 },
dist_vers => { data_type => "varchar", is_nullable => 1, size => 20 },
... # other columns
);
__PACKAGE__->set_primary_key("dist_id");
__PACKAGE__->belongs_to ('auth', 'FPW12::DBIC::Result::Auth', 'auth_id');
__PACKAGE__->has_many ('mods', 'FPW12::DBIC::Result::Mod', 'dist_id');
__PACKAGE__->has_many ('depends', 'FPW12::DBIC::Result::Depends', 'dist_id');
__PACKAGE__->many_to_many(prereq_mods => 'depends', 'mod');
# idem for classes Auth, Mod, etc.
29.06.12 - Page 1
14. Schema def. comparison
Département
Office
• DBIC • DBIDM
– one file for each class – centralized file
– regular Perl classes – dynamic class creation
– full column info – no column info (except pkey)
– 1-way "relationships" – 2-ways "associations" with
• belongs_to • multiplicities
• has_many • role names
• may_have • diff association/composition
• ….
– custom join conditions – custom column names
• any operator • only equalities
• 'foreign' and 'self'
29.06.12 - Page 1
15. Département
Office
Data retrieval
29.06.12 - Page 1
16. DBIC: Search
Département
Office
use FPW12::DBIC;
my $schema = FPW12::DBIC->connect($data_source);
my @dists = $schema->resultset('Dist')->search(
{dist_name => {-like => 'DBIx%'}},
{columns => [qw/dist_name dist_vers/]},
);
foreach my $dist (@dists) {
printf "%s (%s)n", $dist->dist_name, $dist->dist_vers;
}
29.06.12 - Page 1
17. DBIDM: Search
Département
Office
use FPW12::DBIDM;
use DBI;
my $datasource = "dbi:SQLite:dbname=../cpandb.sql";
my $dbh = DBI->connect($datasource, "", "",
{RaiseError => 1, AutoCommit => 1});
FPW12::DBIDM->dbh($dbh);
my $dists = FPW12::DBIDM::Dist->select(
-columns => [qw/dist_name dist_vers/],
-where => {dist_name => {-like => 'DBIx%'}},
);
foreach my $dist (@$dists) {
print "$dist->{dist_name} ($dist->{dist_vers})n";
}
29.06.12 - Page 1
18. Simple search comparison
Département
Office
• DBIC • DBIDM
– schema is an object – schema is a class (default) or an object
– result is a list or a resultset object – result is an arrayref (default)
– accessor methods for columns – hash entries for columns
– uses SQL::Abstract – uses SQL::Abstract::More
• mostly hidden • user can supply a custom $sqla obj
29.06.12 - Page 1
19. SQL::Abstract new()
Département
Office
• special operators
– ex: DBMS-independent fulltext search
# where {field => {-contains => [qw/foo bar/]}
my $sqla = SQL::Abstract->new(special_ops => [
{regex => qr/^contains(:?_all|_any)?$/i,
handler => sub {
my ($self, $field, $op, $arg) = @_;
my $connector = ($op =~ /any$/) ? ' | ' : ' & ';
my @vals = ref $arg ? @$arg : ($arg);
@vals = map { split /s+/ } grep {$_} @vals;
my $sql = sprintf "CONTAINS($field, '%s') > 0",
join $connector, @vals;
return ($sql); # no @bind
}
]);
29.06.12 - Page 1
20. DBIC: Find single record
Département
Office
my $auth1 = $schema->resultset('Auth')
->find(123);
my $auth2 = $schema->resultset('Auth')
->find({cpanid => 'DAMI'});
29.06.12 - Page 1
21. DBIDM: Find single record
Département
Office
my $auth1 = FPW12::DBIDM::Auth->fetch(123);
my $auth2 = FPW12::DBIDM::Auth->search(
-where => {cpanid => 'DAMI'},
-result_as => 'firstrow',
);
29.06.12 - Page 1
22. DBIC: Search args
Département
Office
• 1st arg : "where" criteria
• 2nd arg : attributes
– distinct
– order_by
– group_by, having
– columns # list of columns to retrieve from main table
– +columns # additional columns from joined tables or from functions
– join # see later
– prefetch # see later
– for
– page, rows, offset, etc.
– cache
29.06.12 - Page 1
24. DBIDM: Polymorphic result
-result_as =>
– 'rows' (default) : arrayref of row objects
– 'firstrow' : a single row object (or undef)
– 'hashref' : hashref keyed by primary keys
– [hashref => @cols] : cascaded hashref
– 'flat_arrayref' : flattened values from each row
– 'statement' : a statement object (iterator)
– 'fast_statement' : statement reusing same memory
– 'sth' : DBI statement handle
– 'sql' : ($sql, @bind_values)
– 'subquery' : ["($sql)", @bind]
don't need method variants : select_hashref(), select_arrayref(), etc.
25. DBIDM: Fast statement
• like a regular statement
– but reuses the same memory location for each row
– see DBI::bind_col()
my $statement = $source->select(
. . . ,
-result_as => 'fast_statement'
);
while (my $row = $statement->next) {
. . .
# DO THIS : print $row->{col1}, $row->{col2}
# BUT DON'T DO THIS : push @results, $row;
}
26. Advanced search comparison
• DBIC • DBIDM
– 'find' by any unique constraint – 'fetch' by primary key only
– "inner" vs "outer" columns – all columns equal citizens
– result is context-dependent – polymorphic result
– optional caching – no caching
– statement-specific inflators
– callbacks
33. Join from an existing record
• DBIC
my $rs = $auth->dists(undef, {join|prefetch => 'mods'});
• DBIDM
my $rows = $auth->join(qw/dists mods/)->select;
34. DBIC: left/inner joins
• attribute when declaring relationships
# in a Book class (where Author has_many Books)
__PACKAGE__->belongs_to( author => 'My::DBIC::Schema::Author',
'author',
{ join_type => 'left' } );
• cannot change later (when invoking the relationship)
36. Join comparison
• DBIC • DBIDM
– rows belong to 1 table class only – rows inherit from all joined tables
– joined columns are either – flattening of all columns
• "side-products",
• prefetched (all of them, no choice)
– fixed join type – default join type, can be overridden
42. DBIDM : methods of a row
DB<1> m $auth # 36 methods DB<4> x mro::get_linear_isa(ref $auth)
dists
insert_into_dists 0 ARRAY(0x145275c)
metadm 0 'FPW12::DBIDM::Auth'
via DBIx::DataModel::Source::Table:
_get_last_insert_id 1 'DBIx::DataModel::Source::Table'
via DBIx::DataModel::Source::Table: 2 'DBIx::DataModel::Source'
_insert_subtrees
via DBIx::DataModel::Source::Table: _rawInsert
via DBIx::DataModel::Source::Table: _singleInsert
via DBIx::DataModel::Source::Table:
_weed_out_subtrees
via DBIx::DataModel::Source::Table: delete
via DBIx::DataModel::Source::Table:
has_invalid_columns
via DBIx::DataModel::Source::Table: insert
via DBIx::DataModel::Source::Table: update
via DBIx::DataModel::Source::Table ->
DBIx::DataModel::Source: TO_JSON
via DBIx::DataModel::Source::Table ->
DBIx::DataModel::Source: apply_column_handler
via DBIx::DataModel::Source::Table ->
DBIx::DataModel::Source: auto_expand
via DBIx::DataModel::Source::Table ->
DBIx::DataModel::Source: bless_from_DB
via DBIx::DataModel::Source::Table ->
DBIx::DataModel::Source: expand
via DBIx::DataModel::Source::Table ->
DBIx::DataModel::Source: fetch
47. Architecture comparison
• DBIC • DBIDM
– very complex class structure – classes quite close to DBI concepts
– no distinction front/meta layser – front classes and Meta classes
– methods for – methods for
• CRUD • CRUD
• navigation to related objs • navigation to related objs
• introspection • access to meta
• setting up columns
• setting up relationships
• …
48. Département
Office
Resultset/Statement objects
29.06.12 - Page 1
49. Example task
Département
Office
• list names of authors
– of distribs starting with 'DBIx'
– and version number > 2
29.06.12 - Page 1
50. DBIC ResultSet chaining
Département
Office
my $dists = $schema->resultset('Dist')->search(
{dist_name => {-like => 'DBIx%'}},
);
my $big_vers = $dists->search({dist_vers => { ">" => 2}});
my @auths = $big_vers->search_related('auth', undef,
{distinct => 1, order_by => 'fullname'});
say $_->fullname foreach @auths;
# Magical join ! Magical "group by" !
SELECT auth.auth_id, auth.email, auth.fullname, auth.cpanid
FROM dists me JOIN auths auth ON auth.auth_id = me.auth_id
WHERE ( ( dist_vers > ? AND dist_name LIKE ? ) )
GROUP BY auth.auth_id, auth.email, auth.fullname, auth.cpanid ORDER BY fullname
29.06.12 - Page 1
54. Statement comparison
• DBIC • DBIDM
– powerful refinement constructs – limited refinement constructs
• more "where" criteria – explicit control of status
• navigation to related source
• column restriction
• aggregation operators (e.g.
"count")
55. Département
Office
Other features
29.06.12 - Page 1
56. Transactions
Département
Office
• DBIC • DBIDM
$schema->txn_do($sub); $sch->do_transaction($sub);
– can be nested – can be nested
– can have intermediate savepoints – no intermediate savepoints
29.06.12 - Page 1
58. DBIDM Types (inflate/deflate)
# declare a Type
My::DB->Type(Multivalue =>
from_DB => sub {$_[0] = [split /;/, $_[0]] },
to_DB => sub {$_[0] = join ";", @$_[0] },
);
# apply it to some columns in a table
My::DB::Author->metadm->define_column_type(
Multivalue => qw/hobbies languages/,
);