Hi all,

I've just uploaded a new patch improving the multi-db interface. This
patch introduces a way to easily configure the database selection
process, and enables you to assign a database selection mechanism to
models not under your control (e.g., changing database usage for the
contrib.auth app).

The patch is attached to #12540; if committed it will also close #12541.

This patch goes a little further than I have previously indicated - it
actually starts to introduce public API.

I'm looking for any design feedback on this proposal.

I haven't written docs yet, but here's the short version of what I have added:

 * There is a new setting - DATABASE_ROUTERS - which is a list of
routers that will be tried in turn.

 * A Database Router is any class that provides three methods:

    - db_for_read(model, instance=None)
      Suggest the database that should be used for reads of objects of
type Model. Return None if there is no suggestion

    - db_for_write(model, instance=None)
      Suggest the database that should be used for writes of objects
of type Model. Return None if there is no suggestion

    - allow_relation(obj1, obj2)
      Return True if a relation between obj1 and obj2 should be
allowed, False if the relation should be prevented, or None if the
router has no opinion. (For those that were paying attention
yesterday: This method is a replacement for SOURCE in a database
setting)

 * In these methods, 'model' is the model class that is operated upon.
'instance' is any object that can be used to provide a hint as to the
database that should be used. The may be the instance that is being
saved, or an object that is being added in a relationship. This allows
for routing based on the application, the model, or an attribute of
the model.

 * Whenever a query needs to know which database to use, it can call
django.db.router, providing a model, and a hint (if available). Django
then works down the stack, trying each router in turn, until a
database suggestion can be found. If no suggestion can be found, it
tries the current _state.db of the hint instance; failing that, it
falls back to DEFAULT_DB_ALIAS.

The patch includes a whole lot of extra tests (including some tests
that were disabled just before the commit to trunk). There are some
tests that are probably still missing - especially for update and
delete queries.

So - what does this mean in practice? Say you want contrib.auth to
exist on the 'credentials' database; all other models in a
master/slave relationship between the databses 'master', 'slave1' and
'slave2'. To implement this, you would need 2 routers:

class AuthRouter(object):
    def db_for_read(self, model, instance=None):
        # Point all operations on auth models to 'credentials'
        if model._meta.app_label == 'auth':
            return 'credentials'
        return None

    def db_for_write(self, model, instance=None):
        # Point all operations on auth models to 'credentials'
        if model._meta.app_label == 'auth':
            return 'credentials'
        return None

    def allow_relation(self, obj1, obj2):
        # Allow any relation if a model in Auth is involved
        if obj1._meta.app_label == 'auth' or obj2._meta.app_label == 'auth':
            return True
        return None

class MasterSlaveRouter(object):
    def db_for_read(self, model, db=None):
        # Point all read operations to a random slave
        return random.choice(['slave1','slave2'])

    def db_for_write(self, model, db=None):
        # Point all write operations to the master
        return 'master'

    def allow_relation(self, obj1, obj2):
        # Allow any relation between two objects in the db pool
        db_list = ('master','slave1','slave2')
        if obj1 in db_list and obj2 in db_list:
            return True
        return None

Then, in your settings file, add the following:

DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.MasterSlaveRouter']

And you're set. No need to override get_query_set() or use using(xxx)
or save(using=xxx) - the right database will be selected
automagically.

One quick point of note - the task of splitting auth onto a different
database won't actually work on Postgres or MySQL InnoDB - ForeignKeys
to a remote database won't work due to referential integrity problems.
I'm not attempting to solve this problem right now, as there are a
whole lot of issues that need to be worked out in that space. However,
on MySQL MyISAM and SQLite, it will work.

Ok - so that's the patch. Any comments, queries, or criticisms are welcome.

Yours,
Russ Magee %-)
-- 
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-develop...@googlegroups.com.
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en.


Reply via email to