NAME
DBIx::DataModel::Doc::Delta_v3 - Differences introduced in version 3.0
DESCRIPTION
This document enumerates the main differences introduced in the version 3.0 of DBIx::DataModel
. This consists of some additional features, and some refactoring of the internal architecture.
For older changes, see DBIx::DataModel::Doc::Delta_v2 and DBIx::DataModel::Doc::Delta_v1.
For regular client operations, version 3.0 is fully compatible with version 2.0. However, the inheritance application programming interface has slightly changed; therefore client classes that inherit from DBIx::DataModel::Source::Table
may need some refactoring. This is discussed at the end of the document; before that, the initial chapters present the new features of version 3.0.
Support for changing database schema
A DBIx::DataModel schema usually accesses tables within the database schema of its current database connexion. Previous versions of DBIx::DataModel
already allowed a table declaration to explicitly specify another database schema, like this :
$schema->Table(qw/Class1 OTHER_SCHEMA.TABLE1 primary_key_1/);
but in this case the Perl class is statically bound to the OTHER_SCHEMA
database schema.
In some situations you may want to dynamically change the database schema. The 3.0 API has two new methods for doing this :
The db_schema method tells the schema object to automatically prepend a database schema name in front of table names. Therefore the above example can be rewritten as
$schema->Table(qw/Class1 TABLE1 primary_key_1/); $schema->db_schema('OTHER_SCHEMA');
except that the
OTHER_SCHEMA
prefix will now be prepended in front of every table in SQL requests (not only TABLE1). This effect can be cancelled later by another call todb_schema()
:$schema->db_schema(''); # empty string -- back to the default DB schema
The with_db_schema is very similar but it returns a copy of the schema object instead of altering it directly. This is useful in chained method calls like
$rows = $schema->with_db_schema($string)->join(qw/Table path1 ../)->...;
Here the join is applied to a temporary copy of the
$schema
; that copy is dismissed after the join, so the original$schema
is left untouched.
Executing code after the outermost commit
Method do_after_commit makes it possible to register coderefs to be executed after the end of the outermost transaction. This is useful in situations of distributed computations where the database must be accessed by other processes under other database connections. An example is provided in the reference documentation.
Option join_with_USING
at statement or at schema level
In most DBMS, when column names on both sides of a join are identical, the join can be expressed as
SELECT * FROM T1 INNER JOIN T2 USING (A, B)
instead of
SELECT * FROM T1 INNER JOIN T2 ON T1.A=T2.A AND T1.B=T2.B
The advantage of this syntax with a USING clause is that the joined columns will appear only once in the results, and they do not need to be prefixed by a table name if they are needed in the select list or in the WHERE part of the SQL.
To express joins with the USING syntax in DBIx::DataModel
, activate the boolean option join_with_USING
when defining a schema : this will be automatically applied to all statements within that schema. Alternatively, this option can be overridden at the statement level through the -join_with_USING
argument to the select() method.
Extensible set of -result_as
result kinds
Result kinds, specified through the -result_as
argument to the select() method, are no longer hardwired within the DBIx::DataModel::Statement class.
Each result kind is implemented by a subclass of DBIx::DataModel::Schema::ResultAs. Such subclasses are detected automatically, either in the namespace of DBIx::DataModel::Schema::ResultAs
, or in the namespace of the current schema class. Other namespaces may even be specified through the resultAs_classes
argument to define_schema(). Therefore this is an extensible framework in which clients can add new result kinds or extend existing result kinds.
Result kinds currently shipped with DBIx::DataModel
are :
- Categorize
- Count
- Fast_statement
- File_tabular
- Firstrow
- Flat_arrayref
- Hashref
- Json
- Rows
- Sql
- Statement
- Sth
- Subquery
- Table
- Tsv
- Xlsx
- Yaml
Architectural changes
Suppression of ConnectedSource
class
In multi-schema mode, objects representing data rows, tables or joins must know to which schema they belong : in version 2.0, this was handled by a class called ConnectedSource
, that encapsulated a reference to a datasource paired together with a reference to a schema. Instances of ConnectedSource
acted as proxy objects, forwarding most method calls to the associated datasource.
The problem with that approach was with inheritance and overriding : is a datasource subclass wanted to override a parent method (typically the update()
or insert()
method), that override was not always taken into account, depending on whether the method call was invoked on the proxy object or directly on the datasource object.
The new architecture in version 3.0 dropped the ConnectedSource
class : all method calls go directly to datasource classes. However, a schema reference still needs to be passed to class method calls : for example in
$schema->table('T1')->update(-set => ..., -where => ...);
we intend to call the update()
class method within the T1
Table subclass. To make this work, DBIx::DataModel
twists a little bit the Perl regular architecture : it creates a temporary object
of class T1
, containing only one attribute, namely the reference to the schema. Methods in class T1
recognize such temporary objects and then behave as class methods instead of instance methods, even if technically this is an instance method call. By contrast, calls to update()
in
my $list = $schema->table('T1')->select(-where => ...);
foreach my $row (@$list) {
$row->update({field => $new_value});
}
are treated as regular instance method calls.
Separation of steps for the update()
method
Previous implementations of the update()
method were monolithic, with all algorithmic steps coded together. Therefore any override of that method within subclasses had to re-implement every step. This was not very efficient because the reason to override is usually to modify the update data at a very specific moment within the algorithm; therefore there is no need to rewrite the whole process.
The new architecture divides the update into three steps :
parsing the arguments (not a trivial task)
applying handlers and other transformation to the data to be updated
issuing the database request
More precisely, the skeleton for the update()
method looks like this :
sub update {
my $self = shift;
# prepare datastructures for generating the SQL
my ($to_set, $where) = $self->_parse_update_args(@_);
# transform the data if necessary
$self->_apply_handlers_for_update($to_set, $where);
# database request
...
}
With this architecture, subclasses can decide to only override the _apply_handers_for_update()
method, instead of overriding the whole update()
method.
Meta::Utils
subroutines instead of methods
Utilies within DBIx::DataModel::Meta::Utils are now subroutines instead of methods : this makes more sense because that class has no internal state and therefore is not object-oriented; as a result, calls to these utilities are less verbose.
Dependency on Scalar::Does is dropped
Scalar::Does started as a very small and simple module, but then evolved into a much richer module with more features but also more dependencies. Since the needs of DBIx::DataModel
are very limited, most of the complexity of Scalar::Does was unused. Therefore the dependency on Scalar::Does has been dropped, replaced by a very simplistic implementation of the does
method within SQL::Abstract::More.