[Dbix-class] Cross-schema relationships.

Aaron Crane dbix-class at aaroncrane.co.uk
Tue Feb 2 23:05:21 GMT 2010

Rob Kinyon <rob.kinyon at gmail.com> wrote:
> If you're talking about different physical DB instances and there's no
> feature in the DBMS to manage those cross-connections for you, then
> you have to do it by hand.
> Suggestions for API improvements welcome.

I've done something similar recently.  My situation involved a
database with, let's say, blog posts, and another database with, say,
account registration data; the blog-post database has per-entry
comments, each of which is associated with an account in the
registration database.  I wanted to be able to say something like

  my $comment = $db->resultset('Comment')->find($comment_id);
  print $comment->user->name;

and have the Comment result class magically do whatever's needed to
get a user row from the foreign database.

That needs to be set up in the result class, of course.  I decided
that treating such things as similar to normal relationships would

  package BlogSchema::Comment;
  use base qw<DBIx::Class>;
  __PACKAGE__->load_components(qw<+DBICx::ExternalRelationships Core>);
  __PACKAGE__->add_columns(user_id => { data_type => 'int' }, ...);
  __PACKAGE__->belongs_to_external(user => 'account', 'User');

That is, I'm saying that there should be a `user` method on this
Comment class, and calling it should look up an object from the `User`
resultset on the database we'll identify as "account".  The
`belongs_to_external` method on the ExternalRelationships component
arranges for the relevant magic to happen.

So what form does that magic take?  I also added some code to the
schema class: a new `externals` field, containing a hashref from
database names (like "account" in this example) to a coderef that
returns a DBIC schema handle for the foreign database in question.
(Some databases might have external foreign keys to multiple
databases.)  The `externals` data could be populated manually after
connecting to the schema class; in my case, it was easier to just
override the connection method.  I also have a `find_external_schema`
method on the schema class, which throws an exception if you try to
invoke an external-relationship method for a database that hasn't been
configured in the current schema instance.

The method created by the belongs_to_external call is then fairly
simple: it calls $self->result_source->schema->find_external_schema
with the external database name supplied, then calls
->resultset($rs_name)->find($self->$column_name) on that.  It also
takes care to cache the external result in the local instance, so that
repeated calls don't cause repeated queries.

There are some obvious and significant flaws with this as a
general-purpose approach, of course:

- Callers need to be aware that the foreign objects don't respect
transactions on the local schema.

- My belongs_to_external method makes unwarranted assumptions about
naming conventions for primary- and foreign-key columns (though those
assumptions were very convenient for me)

- I completely punted on all relationship types other than belongs_to
— I can't do `$account_user->blog_comments`, for example

However, if anyone's interested in working on something similar, let
me know and I'll lend what assistance I can.

Aaron Crane ** http://aaroncrane.co.uk/

More information about the DBIx-Class mailing list