view release on metacpan or search on metacpan
- fixed arguments to last_insert_id
- consistency checks on Compositions
v0.25 09.11.2006
- more liberal parsing of multiplicities
v0.24 08.11.2006
- insert() returns list or scalar depending on wantarray
v0.23 07.11.2006
- added Composition() and cascaded insert/delete
- added support for prefixes +/- for -orderBy
v0.22 14.09.2006
- added option -postSQL to select()
- Build.pl option to support old-style Makefile.PL
- ViewFromRoles creates views in $schema::View namespace
v0.21 CPAN release 06.09.2006
- check args for ViewFromRoles
- check -resultAs value
lib/DBIx/DataModel.pm view on Meta::CPAN
it provides no support for schema changes (and seldom
needs to know about them).
=item no object caching nor 'dirty columns'
C<DBIx::DataModel> does not keep track of data mutations
in memory, and therefore provides no support for automatically
propagating changes into the database; the client code has to
explicitly manage C<insert> and C<update> operations.
=item no 'cascaded update' nor 'insert or update'
Cascaded inserts and deletes are supported, but not cascaded updates.
This would need 'insert or update', which is not supported.
=back
=head1 INDEX TO THE DOCUMENTATION
Although the basic principles are quite simple, there are many
details to discuss, so the documentation is quite long.
In an attempt to accommodate for different needs of readers,
lib/DBIx/DataModel/Doc/Cookbook.pod view on Meta::CPAN
define_method(
class => $schema->metadm->table($class)->class,
name => '_singleInsert',
body => \&insert_with_random_key,
);
}
=head2 Cascaded operations
Some database systems support cascaded operations : for example
a constraint definition with a clause like C<ON DELETE CASCADE>
will automatically delete child rows (rows containing foreign keys)
when the parent row (the row containing the primary key) is deleted.
C<DBIx::DataModel> does not know about such cascaded operations in the
database; but it can perform some cascaded operations at the ORM level,
when tables are associated through a
L<composition|DBIx::DataModel::Doc::Glossary/"composition">.
In that case, the C<insert()> method can accept a data tree as argument,
and will automatically perform recursive inserts in the children tables;
an example is given in the
L<quickstart tutorial|DBIx::DataModel::Doc::Quickstart/"Cascaded inserts">.
Cascaded deletes are also supported :
my $bach = HR->table('Employee')->fetch($bach_id);
$bach->expand('activities');
$bach->delete; # deletes the Employee together with its Activities
The C<expand> operations retrieve related records and add them
into a tree in memory. Then C<delete> removes from the database
all records found in the tree.
Observe that this is not a "true" cascaded
delete, because the client code is responsible for fetching the
related records first.
=head2 Timestamp validation
Suppose we want to sure that the record was not touched between the time
it was presented to the user in a display form and the time
the user wants to update or delete that record.
lib/DBIx/DataModel/Doc/Design.pod view on Meta::CPAN
To communicate with the database, the framework only needs to know a
bare minimum about the schema: table names, primary keys
and UML associations. No details are required about column names
or their datatypes.
=head3 Let the RDBMS check data integrity
Most RDBMS have facilities for checking or ensuring integrity rules :
foreign key constraints, restricted ranges for values, cascaded
deletes, etc. C<DBIx::DataModel> can also do some validation
tasks, by setting up column types with a C<validate> handler;
however, it is recommended to rather use the RDBMS for
performing data integrity checks, whenever possible.
=head3 Take advantage of database projections through variable-size objects
In many ORMs, columns in a table are in 1-to-1 correspondence
with attributes in the associated class; so any transfer between
database and memory systematically includes all the columns, both
lib/DBIx/DataModel/Doc/Glossary.pod view on Meta::CPAN
The first participant in a L</composition> relationship, i.e.
some object which "contains" another object. If the composite
object is deleted, all its components are also deleted.
=item composition
An L</association> which additionally states that records of one
table (components) cannot exist outside of their composite class;
therefore when the composite is destroyed, the components must
be destroyed as well (cascaded delete).
=item column
Within a L</row>, a column is a scalar value
corresponding to a given column name.
Within a L</table> or L</view>, a column is a
a list of scalar values obtained by selecting the
same column name in every row of the data source.
Unlike most other Perl L<ORMs|/ORM>,
C<DBIx::DataModel> has no class for
lib/DBIx/DataModel/Doc/Quickstart.pod view on Meta::CPAN
$bach->insert_into_emp_skills({skill_code => 'VL'},
{skill_code => 'KB'});
=head3 Cascaded inserts
Since there is a
L<composition|DBIx::DataModel::Doc::Glossary/"composition">
between classes C<Employee> and C<Activity>, we can supply a
whole data tree to the C<insert()> method, and cascaded inserts
will be performed automatically :
HR->table('Employee')->insert(
{firstname => "Richard",
lastname => "Strauss",
activities => [ {d_begin => '01.01.1874',
d_end => '08.09.1949',
dpt_code => 'ORCH' } ]}
);
lib/DBIx/DataModel/Doc/Reference.pod view on Meta::CPAN
single composite table).
=item *
this association can be used for auto-expanding the composite object
(i.e. automatically fetching all component parts from the database)
-- see L</"expand()"> and L</"auto_expand()">
=item *
this association can be used for cascaded L<inserts|/"insert()"> like
$source->insert({
column1 => $val1,
...
$component_name1 => [{$sub_object1}, ...],
...
})
The main record will be inserted in the composite class, and within
the same transaction, subrecords will be inserted into the
component classes, with foreign keys automatically filled with
appropriate values.
=item *
this association can be used for cascaded deletes :
the argument to a L<delete|/"delete()"> may contain lists of component records to
be deleted together with the main record of the composite class.
=back
=head3 define_type()
$meta_schema->define_type(
name => $type_name,
lib/DBIx/DataModel/Doc/Reference.pod view on Meta::CPAN
Currently the only supported option is B<-returning> :
=over
=item *
if the C<-returning> option is set to an empty hashref, the return
value will be a list of hashrefs (one for each inserted record),
containing the column name(s) and value(s) of the primary key for that
record, and possibly containing subhashes or subarrays for other
records created through cascaded inserts. For example:
my @result = HR->table('Employee'>
->insert({..., activities => [{...}, ...]},
...,
-returning => {});
my $prim_key_first_emp = $result[0]{emp_id};
my $prim_key_first_act = $result[0]{activities}[0]{act_id};
=item *
lib/DBIx/DataModel/Doc/Reference.pod view on Meta::CPAN
=item *
the second syntax is used for deleting a single
record. A C<-where> clause will be automatically generated
by extracting the primary key column(s) from the record.
If the source is a composite class (see
L<Composition()|/"Composition"> above), and if the record in memory contains
references to lists of component parts, then those will be recursively
deleted together with the main object (cascaded delete). However, if
there are other component parts in the database, not referenced in the
object hashref, then those will not be automatically deleted : in other
words, the C<delete> method does not go by itself to the database to
find all component parts (this is the job of the client
code, or sometimes of the database itself).
=item *
the third syntax with C<< @primary_key >> is an alternate way to
supply the values for the primary key; it may be more convenient
because you don't need to repeat the name of primary key columns.
Note that C<< $statement->delete(11, 22) >>
does not mean "delete records with keys 11 and 22", but rather
"delete record having primary key (11, 22)"; in other words,
with this syntax you only delete one record at a time.
With this syntax no cascaded delete
is performed.
=back
When used as an instance method, the only syntax is to call
the C<delete()> method without any arguments :
$source_instance->delete();
lib/DBIx/DataModel/Meta/Association.pm view on Meta::CPAN
=item *
this association can be used for auto-expanding the composite object
(i.e. automatically fetching all component parts from the database)
-- see L<DBIx::DataModel::Source/expand>
and L<DBIx::DataModel::Source/auto_expand>
=item *
this association can be used for cascaded inserts like
$source->insert({
column1 => $val1,
...
$component_name1 => [{$sub_object1}, ...],
...
})
see L<DBIx::DataModel::Source/insert>
lib/DBIx/DataModel/Schema/Generator.pm view on Meta::CPAN
col => $fk_row->{UK_COLUMN_NAME},
role => _table2role($fk_row->{UK_TABLE_NAME}),
mult_min => 1, #0/1 (TODO: depend on is_nullable on other side)
mult_max => 1,
},
{ table => _table2class($fk_row->{FK_TABLE_NAME}),
col => $fk_row->{FK_COLUMN_NAME},
role => _table2role($fk_row->{FK_TABLE_NAME}, "s"),
mult_min => 0,
mult_max => '*',
is_cascade => defined $del_rule && $del_rule == CASCADE,
}
);
push @{$self->{assoc}}, \@assoc;
}
}
}
sub parse_DBIx_Class {
lib/DBIx/DataModel/Schema/Generator.pm view on Meta::CPAN
&& $a->[0]{mult_max} eq "*";
# complete association info
for my $i (0, 1) {
$a->[$i]{role} ||= "---";
my $mult = "$a->[$i]{mult_min}..$a->[$i]{mult_max}";
$a->[$i]{mult} = {"0..*" => "*", "1..1" => "1"}->{$mult} || $mult;
}
# association or composition
my $relationship = $a->[1]{is_cascade} ? 'Composition' : 'Association';
$code .= "\n->$relationship(\n"
. sprintf($format, @{$a->[0]}{qw/table role mult col/})
. ",\n"
. sprintf($format, @{$a->[1]}{qw/table role mult col/})
. ")\n";
}
$code .= "\n;\n";
# column types
lib/DBIx/DataModel/Source/Table.pm view on Meta::CPAN
my $delete_spec = {
-where => {type => HASHREF, optional => 0},
};
sub _parse_delete_args {
my $self = shift;
my @pk_cols = $self->metadm->primary_key;
my $where;
my @cascaded;
if ($self->_is_called_as_class_method) {
# parse arguments
@_ or croak "delete() as class method: not enough arguments";
my $uses_named_args = ! ref $_[0] && $_[0] =~ /^-/;
if ($uses_named_args) {
my %args = validate_with(params => \@_,
spec => $delete_spec,
allow_extra => 0);
lib/DBIx/DataModel/Source/Table.pm view on Meta::CPAN
}
my $missing = join ", ", grep {!defined $where->{$_}} @pk_cols;
croak "delete(): missing value for $missing" if $missing;
}
}
else { # called as instance method
# build $where from primary key
@{$where}{@pk_cols} = @{$self}{@pk_cols};
# cascaded delete
COMPONENT_NAME:
foreach my $component_name ($self->metadm->components) {
my $components = $self->{$component_name} or next COMPONENT_NAME;
does($components, 'ARRAY')
or croak "delete() : component $component_name is not an arrayref";
push @cascaded, @$components;
}
}
return ($where, \@cascaded);
}
sub delete {
my $self = shift;
my $schema = $self->schema;
my ($where, $cascaded) = $self->_parse_delete_args(@_);
# perform cascaded deletes for components within $self
$_->delete foreach @$cascaded;
# perform this delete
my ($sql, @bind) = $schema->sql_abstract->delete(
-from => $self->db_from,
-where => $where,
);
$schema->_debug($sql . " / " . CORE::join(", ", @bind) );
my $method = $schema->dbi_prepare_method;
my $sth = $schema->dbh->$method($sql);
$sth->execute(@bind);
t/v1_DBIx-DataModel.t view on Meta::CPAN
$insert_sql, [qw/ Claudio Monteverdi /],
'insert with arrayref syntax');
# insertion into related class
$emp->insert_into_activities({d_begin =>'2000-01-01', d_end => '2000-02-02'});
sqlLike('INSERT INTO T_Activity (d_begin, d_end, emp_id) ' .
'VALUES (?, ?, ?)', ['2000-01-01', '2000-02-02', 999],
'add_to_activities');
# test cascaded inserts
my $tree = {firstname => "Johann Sebastian",
lastname => "Bach",
activities => [{d_begin => '01.01.1707',
d_end => '01.07.1720',
dpt_code => 'Maria-Barbara'},
{d_begin => '01.12.1721',
d_end => '18.07.1750',
dpt_code => 'Anna-Magdalena'}]};
my $emp_id = HR::Employee->insert(dclone($tree));
my $sql_insert_activity = 'INSERT INTO T_Activity (d_begin, d_end, '
. 'dpt_code, emp_id) VALUES (?, ?, ?, ?)';
sqlLike('INSERT INTO T_Employee (firstname, lastname) VALUES (?, ?)',
["Johann Sebastian", "Bach"],
$sql_insert_activity,
['1707-01-01', '1720-07-01', 'Maria-Barbara', $emp_id],
$sql_insert_activity,
['1721-12-01', '1750-07-18', 'Anna-Magdalena', $emp_id],
"cascaded insert");
# test the -returning => {} option
$dbh->{mock_start_insert_id} = 10;
$result = HR::Employee->insert(dclone($tree), -returning => {});
my $expected = { emp_id => 10,
activities => [{act_id => 11}, {act_id => 12}]};
is_deeply($result, $expected, "results from -returning => {}");
# insert with literal SQL
$emp_id = HR::Employee->insert({
t/v2_Dbix-DataModel.t view on Meta::CPAN
$insert_sql, [qw/ Claudio Monteverdi /],
'insert with arrayref syntax');
# insertion into related class
$emp->insert_into_activities({d_begin =>'2000-01-01', d_end => '2000-02-02'});
sqlLike('INSERT INTO T_Activity (d_begin, d_end, emp_id) ' .
'VALUES (?, ?, ?)', ['2000-01-01', '2000-02-02', 999],
'add_to_activities');
# cascaded inserts
my $tree = {firstname => "Johann Sebastian",
lastname => "Bach",
activities => [{d_begin => '01.01.1707',
d_end => '01.07.1720',
dpt_code => 'Maria-Barbara'},
{d_begin => '01.12.1721',
d_end => '18.07.1750',
dpt_code => 'Anna-Magdalena'}]};
my $emp_id = HR::Employee->insert(clone($tree));
my $sql_insert_activity = 'INSERT INTO T_Activity (d_begin, d_end, '
. 'dpt_code, emp_id) VALUES (?, ?, ?, ?)';
sqlLike('INSERT INTO T_Employee (firstname, lastname) VALUES (?, ?)',
["Johann Sebastian", "Bach"],
$sql_insert_activity,
['1707-01-01', '1720-07-01', 'Maria-Barbara', $emp_id],
$sql_insert_activity,
['1721-12-01', '1750-07-18', 'Anna-Magdalena', $emp_id],
"cascaded insert");
# option -returning => {}
$dbh->{mock_start_insert_id} = 10;
$result = HR::Employee->insert(clone($tree), -returning => {});
my $expected = { emp_id => 10,
activities => [{act_id => 11}, {act_id => 12}]};
is_deeply($result, $expected, "results from -returning => {}");
# insert with literal SQL
$emp_id = HR::Employee->insert({