1
0
mirror of https://github.com/django/django.git synced 2025-07-05 02:09:13 +00:00

[multi-db] Added optional db_connection property to model Meta classes,

which can be used to set the name of the connection for the  model. 
Updated query generation in model, query and fields/related to 
use the model's connection and backend. Added basic model connection 
access to multiple db test.


git-svn-id: http://code.djangoproject.com/svn/django/branches/multiple-db-support@3234 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jason Pellerin 2006-06-28 22:21:45 +00:00
parent 9bae64fa76
commit 75b64abaa7
5 changed files with 138 additions and 22 deletions

View File

@ -7,7 +7,7 @@ from django.db.models.fields.related import OneToOneRel, ManyToOneRel
from django.db.models.related import RelatedObject from django.db.models.related import RelatedObject
from django.db.models.query import orderlist2sql, delete_objects from django.db.models.query import orderlist2sql, delete_objects
from django.db.models.options import Options, AdminOptions from django.db.models.options import Options, AdminOptions
from django.db import connection, backend, transaction from django.db import transaction
from django.db.models import signals from django.db.models import signals
from django.db.models.loading import register_models, get_model from django.db.models.loading import register_models, get_model
from django.dispatch import dispatcher from django.dispatch import dispatcher
@ -158,6 +158,9 @@ class Model(object):
def save(self): def save(self):
dispatcher.send(signal=signals.pre_save, sender=self.__class__, instance=self) dispatcher.send(signal=signals.pre_save, sender=self.__class__, instance=self)
info = self._meta.connection_info
connection = info.connection
backend = info.backend
non_pks = [f for f in self._meta.fields if not f.primary_key] non_pks = [f for f in self._meta.fields if not f.primary_key]
cursor = connection.cursor() cursor = connection.cursor()
@ -205,7 +208,7 @@ class Model(object):
backend.get_pk_default_value())) backend.get_pk_default_value()))
if self._meta.has_auto_field and not pk_set: if self._meta.has_auto_field and not pk_set:
setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column)) setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column))
transaction.commit_unless_managed() transaction.commit_unless_managed([connection])
# Run any post-save hooks. # Run any post-save hooks.
dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self) dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self)
@ -276,6 +279,7 @@ class Model(object):
return dict(field.choices).get(value, value) return dict(field.choices).get(value, value)
def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs): def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
backend = self._meta.connection_info.backend
op = is_next and '>' or '<' op = is_next and '>' or '<'
where = '(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \ where = '(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \
(backend.quote_name(field.column), op, backend.quote_name(field.column), (backend.quote_name(field.column), op, backend.quote_name(field.column),
@ -290,6 +294,7 @@ class Model(object):
raise self.DoesNotExist, "%s matching query does not exist." % self.__class__._meta.object_name raise self.DoesNotExist, "%s matching query does not exist." % self.__class__._meta.object_name
def _get_next_or_previous_in_order(self, is_next): def _get_next_or_previous_in_order(self, is_next):
backend = self._meta.connection_info.backend
cachename = "__%s_order_cache" % is_next cachename = "__%s_order_cache" % is_next
if not hasattr(self, cachename): if not hasattr(self, cachename):
op = is_next and '>' or '<' op = is_next and '>' or '<'
@ -378,6 +383,9 @@ class Model(object):
rel = rel_field.rel.to rel = rel_field.rel.to
m2m_table = rel_field.m2m_db_table() m2m_table = rel_field.m2m_db_table()
this_id = self._get_pk_val() this_id = self._get_pk_val()
info = self._meta.connection_info
connection = info.connection
backend = info.backend
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute("DELETE FROM %s WHERE %s = %%s" % \ cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
(backend.quote_name(m2m_table), (backend.quote_name(m2m_table),
@ -387,7 +395,7 @@ class Model(object):
backend.quote_name(rel_field.m2m_column_name()), backend.quote_name(rel_field.m2m_column_name()),
backend.quote_name(rel_field.m2m_reverse_name())) backend.quote_name(rel_field.m2m_reverse_name()))
cursor.executemany(sql, [(this_id, i) for i in id_list]) cursor.executemany(sql, [(this_id, i) for i in id_list])
transaction.commit_unless_managed() transaction.commit_unless_managed([connection])
############################################ ############################################
# HELPER FUNCTIONS (CURRIED MODEL METHODS) # # HELPER FUNCTIONS (CURRIED MODEL METHODS) #
@ -396,6 +404,9 @@ class Model(object):
# ORDERING METHODS ######################### # ORDERING METHODS #########################
def method_set_order(ordered_obj, self, id_list): def method_set_order(ordered_obj, self, id_list):
connection_info = ordered_obj._meta.connection_info
connection = info.connection
backend = info.backend
cursor = connection.cursor() cursor = connection.cursor()
# Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s" # Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s"
sql = "UPDATE %s SET %s = %%s WHERE %s = %%s AND %s = %%s" % \ sql = "UPDATE %s SET %s = %%s WHERE %s = %%s AND %s = %%s" % \
@ -404,9 +415,12 @@ def method_set_order(ordered_obj, self, id_list):
backend.quote_name(ordered_obj._meta.pk.column)) backend.quote_name(ordered_obj._meta.pk.column))
rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name) rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name)
cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)]) cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)])
transaction.commit_unless_managed() transaction.commit_unless_managed([connection])
def method_get_order(ordered_obj, self): def method_get_order(ordered_obj, self):
connection_info = ordered_obj.connection_info
connection = info.connection
backend = info.backend
cursor = connection.cursor() cursor = connection.cursor()
# Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order" # Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order"
sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY %s" % \ sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY %s" % \

View File

@ -1,4 +1,4 @@
from django.db import backend, connection, transaction from django.db import transaction
from django.db.models import signals, get_model from django.db.models import signals, get_model
from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class
from django.db.models.related import RelatedObject from django.db.models.related import RelatedObject
@ -290,7 +290,7 @@ def create_many_related_manager(superclass):
# source_col_name: the PK colname in join_table for the source object # source_col_name: the PK colname in join_table for the source object
# target_col_name: the PK colname in join_table for the target object # target_col_name: the PK colname in join_table for the target object
# *objs - objects to add # *objs - objects to add
from django.db import connection connection = self.model._meta.connection
# Add the newly created or already existing objects to the join table. # Add the newly created or already existing objects to the join table.
# First find out which items are already added, to avoid adding them twice # First find out which items are already added, to avoid adding them twice
@ -310,13 +310,13 @@ def create_many_related_manager(superclass):
cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
(self.join_table, source_col_name, target_col_name), (self.join_table, source_col_name, target_col_name),
[self._pk_val, obj_id]) [self._pk_val, obj_id])
transaction.commit_unless_managed() transaction.commit_unless_managed(connection)
def _remove_items(self, source_col_name, target_col_name, *objs): def _remove_items(self, source_col_name, target_col_name, *objs):
# source_col_name: the PK colname in join_table for the source object # source_col_name: the PK colname in join_table for the source object
# target_col_name: the PK colname in join_table for the target object # target_col_name: the PK colname in join_table for the target object
# *objs - objects to remove # *objs - objects to remove
from django.db import connection connection = self.model._meta.connection
for obj in objs: for obj in objs:
if not isinstance(obj, self.model): if not isinstance(obj, self.model):
@ -327,16 +327,16 @@ def create_many_related_manager(superclass):
cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s = %%s" % \ cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s = %%s" % \
(self.join_table, source_col_name, target_col_name), (self.join_table, source_col_name, target_col_name),
[self._pk_val, obj._get_pk_val()]) [self._pk_val, obj._get_pk_val()])
transaction.commit_unless_managed() transaction.commit_unless_managed(connection)
def _clear_items(self, source_col_name): def _clear_items(self, source_col_name):
# source_col_name: the PK colname in join_table for the source object # source_col_name: the PK colname in join_table for the source object
from django.db import connection connection = self.model._meta.connection
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute("DELETE FROM %s WHERE %s = %%s" % \ cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
(self.join_table, source_col_name), (self.join_table, source_col_name),
[self._pk_val]) [self._pk_val])
transaction.commit_unless_managed() transaction.commit_unless_managed(connection)
return ManyRelatedManager return ManyRelatedManager
@ -360,6 +360,7 @@ class ManyRelatedObjectsDescriptor(object):
superclass = rel_model._default_manager.__class__ superclass = rel_model._default_manager.__class__
RelatedManager = create_many_related_manager(superclass) RelatedManager = create_many_related_manager(superclass)
backend = rel_model._meta.connection_info.backend
qn = backend.quote_name qn = backend.quote_name
manager = RelatedManager( manager = RelatedManager(
model=rel_model, model=rel_model,
@ -402,6 +403,7 @@ class ReverseManyRelatedObjectsDescriptor(object):
superclass = rel_model._default_manager.__class__ superclass = rel_model._default_manager.__class__
RelatedManager = create_many_related_manager(superclass) RelatedManager = create_many_related_manager(superclass)
backend = rel_model._meta.connection_info.backend
qn = backend.quote_name qn = backend.quote_name
manager = RelatedManager( manager = RelatedManager(
model=rel_model, model=rel_model,

View File

@ -1,4 +1,5 @@
from django.conf import settings from django.conf import settings
from django.db import connection_info, connections
from django.db.models.related import RelatedObject from django.db.models.related import RelatedObject
from django.db.models.fields.related import ManyToManyRel from django.db.models.fields.related import ManyToManyRel
from django.db.models.fields import AutoField, FieldDoesNotExist from django.db.models.fields import AutoField, FieldDoesNotExist
@ -11,7 +12,7 @@ import re
# Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces". # Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip() get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', DEFAULT_NAMES = ('verbose_name', 'db_connection', 'db_table', 'ordering',
'unique_together', 'permissions', 'get_latest_by', 'unique_together', 'permissions', 'get_latest_by',
'order_with_respect_to', 'app_label') 'order_with_respect_to', 'app_label')
@ -20,6 +21,7 @@ class Options(object):
self.fields, self.many_to_many = [], [] self.fields, self.many_to_many = [], []
self.module_name, self.verbose_name = None, None self.module_name, self.verbose_name = None, None
self.verbose_name_plural = None self.verbose_name_plural = None
self.db_connection = None
self.db_table = '' self.db_table = ''
self.ordering = [] self.ordering = []
self.unique_together = [] self.unique_together = []
@ -91,6 +93,17 @@ class Options(object):
def __str__(self): def __str__(self):
return "%s.%s" % (self.app_label, self.module_name) return "%s.%s" % (self.app_label, self.module_name)
def get_connection_info(self):
if self.db_connection:
return connections[self.db_connection]
return connection_info
connection_info = property(get_connection_info)
def get_connection(self):
"""Get the database connection for this object's model"""
return self.get_connection_info().connection
connection = property(get_connection)
def get_field(self, name, many_to_many=True): def get_field(self, name, many_to_many=True):
"Returns the requested field by name. Raises FieldDoesNotExist on error." "Returns the requested field by name. Raises FieldDoesNotExist on error."
to_search = many_to_many and (self.fields + self.many_to_many) or self.fields to_search = many_to_many and (self.fields + self.many_to_many) or self.fields

View File

@ -1,4 +1,4 @@
from django.db import backend, connection, transaction from django.db import transaction
from django.db.models.fields import DateField, FieldDoesNotExist from django.db.models.fields import DateField, FieldDoesNotExist
from django.db.models import signals from django.db.models import signals
from django.dispatch import dispatcher from django.dispatch import dispatcher
@ -157,8 +157,7 @@ class QuerySet(object):
# self._select is a dictionary, and dictionaries' key order is # self._select is a dictionary, and dictionaries' key order is
# undefined, so we convert it to a list of tuples. # undefined, so we convert it to a list of tuples.
extra_select = self._select.items() extra_select = self._select.items()
cursor = self.model._meta.connection.cursor()
cursor = connection.cursor()
select, sql, params = self._get_sql_clause() select, sql, params = self._get_sql_clause()
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
fill_cache = self._select_related fill_cache = self._select_related
@ -178,6 +177,9 @@ class QuerySet(object):
def count(self): def count(self):
"Performs a SELECT COUNT() and returns the number of records as an integer." "Performs a SELECT COUNT() and returns the number of records as an integer."
info = self.model._meta.connection_info
backend = info.backend
connection = info.connection
counter = self._clone() counter = self._clone()
counter._order_by = () counter._order_by = ()
counter._offset = None counter._offset = None
@ -247,6 +249,7 @@ class QuerySet(object):
Returns a dictionary mapping each of the given IDs to the object with Returns a dictionary mapping each of the given IDs to the object with
that ID. that ID.
""" """
backend = self.model._meta.connection_info.backend
assert self._limit is None and self._offset is None, \ assert self._limit is None and self._offset is None, \
"Cannot use 'limit' or 'offset' with in_bulk" "Cannot use 'limit' or 'offset' with in_bulk"
assert isinstance(id_list, (tuple, list)), "in_bulk() must be provided with a list of IDs." assert isinstance(id_list, (tuple, list)), "in_bulk() must be provided with a list of IDs."
@ -423,7 +426,7 @@ class QuerySet(object):
def _get_sql_clause(self): def _get_sql_clause(self):
opts = self.model._meta opts = self.model._meta
backend = opts.connection_info.backend
# Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z. # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields] select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields]
tables = [quote_only_if_word(t) for t in self._tables] tables = [quote_only_if_word(t) for t in self._tables]
@ -514,6 +517,9 @@ class ValuesQuerySet(QuerySet):
columns = [f.column for f in self.model._meta.fields] columns = [f.column for f in self.model._meta.fields]
field_names = [f.attname for f in self.model._meta.fields] field_names = [f.attname for f in self.model._meta.fields]
info = self.model._meta.connection_info
backend = info.backend
connection = info.connection
cursor = connection.cursor() cursor = connection.cursor()
select, sql, params = self._get_sql_clause() select, sql, params = self._get_sql_clause()
select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns] select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
@ -533,6 +539,10 @@ class ValuesQuerySet(QuerySet):
class DateQuerySet(QuerySet): class DateQuerySet(QuerySet):
def iterator(self): def iterator(self):
from django.db.backends.util import typecast_timestamp from django.db.backends.util import typecast_timestamp
info = self.model._meta.connection_info
backend = info.backend
connection = info.connection
self._order_by = () # Clear this because it'll mess things up otherwise. self._order_by = () # Clear this because it'll mess things up otherwise.
if self._field.null: if self._field.null:
self._where.append('%s.%s IS NOT NULL' % \ self._where.append('%s.%s IS NOT NULL' % \
@ -625,7 +635,9 @@ class QNot(Q):
where2 = ['(NOT (%s))' % " AND ".join(where)] where2 = ['(NOT (%s))' % " AND ".join(where)]
return tables, joins, where2, params return tables, joins, where2, params
def get_where_clause(lookup_type, table_prefix, field_name, value): def get_where_clause(opts, lookup_type, table_prefix, field_name, value):
backend = opts.connection_info.backend
if table_prefix.endswith('.'): if table_prefix.endswith('.'):
table_prefix = backend.quote_name(table_prefix[:-1])+'.' table_prefix = backend.quote_name(table_prefix[:-1])+'.'
field_name = backend.quote_name(field_name) field_name = backend.quote_name(field_name)
@ -660,6 +672,7 @@ def fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen)
Helper function that recursively populates the select, tables and where (in Helper function that recursively populates the select, tables and where (in
place) for fill-cache queries. place) for fill-cache queries.
""" """
backend = opts.connection_info.backend
for f in opts.fields: for f in opts.fields:
if f.rel and not f.null: if f.rel and not f.null:
db_table = f.rel.to._meta.db_table db_table = f.rel.to._meta.db_table
@ -753,6 +766,9 @@ def lookup_inner(path, clause, value, opts, table, column):
current_column = column current_column = column
intermediate_table = None intermediate_table = None
join_required = False join_required = False
info = current_opts.connection_info
backend = info.backend
connection = info.connection
name = path.pop(0) name = path.pop(0)
# Has the primary key been requested? If so, expand it out # Has the primary key been requested? If so, expand it out
@ -881,7 +897,7 @@ def lookup_inner(path, clause, value, opts, table, column):
else: else:
column = field.column column = field.column
where.append(get_where_clause(clause, current_table + '.', column, value)) where.append(get_where_clause(current_opts, clause, current_table + '.', column, value))
params.extend(field.get_db_prep_lookup(clause, value)) params.extend(field.get_db_prep_lookup(clause, value))
return tables, joins, where, params return tables, joins, where, params
@ -891,9 +907,12 @@ def delete_objects(seen_objs):
ordered_classes = seen_objs.keys() ordered_classes = seen_objs.keys()
ordered_classes.reverse() ordered_classes.reverse()
for cls in ordered_classes:
info = cls._meta.connection_info
backend = info.backend
connection = info.connection
cursor = connection.cursor() cursor = connection.cursor()
for cls in ordered_classes:
seen_objs[cls] = seen_objs[cls].items() seen_objs[cls] = seen_objs[cls].items()
seen_objs[cls].sort() seen_objs[cls].sort()
@ -927,7 +946,16 @@ def delete_objects(seen_objs):
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
# Now delete the actual data # Now delete the actual data
dirty_conns = []
for cls in ordered_classes: for cls in ordered_classes:
info = cls._meta.connection_info
backend = info.backend
connection = info.connection
cursor = connection.cursor()
if connection not in dirty_conns:
dirty_conns.append(connection)
seen_objs[cls].reverse() seen_objs[cls].reverse()
pk_list = [pk for pk,instance in seen_objs[cls]] pk_list = [pk for pk,instance in seen_objs[cls]]
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
@ -947,4 +975,4 @@ def delete_objects(seen_objs):
setattr(instance, cls._meta.pk.attname, None) setattr(instance, cls._meta.pk.attname, None)
dispatcher.send(signal=signals.post_delete, sender=cls, instance=instance) dispatcher.send(signal=signals.post_delete, sender=cls, instance=instance)
transaction.commit_unless_managed() transaction.commit_unless_managed(dirty_conns)

View File

@ -50,7 +50,7 @@ with the attributes: `DatabaseError`, `backend`,
`runshell`. Access connections through the `connections` property of `runshell`. Access connections through the `connections` property of
the `django.db` module: the `django.db` module:
>>> from django.db import connections >>> from django.db import connection, connections
>>> connections['a'].settings.DATABASE_NAME == db_a >>> connections['a'].settings.DATABASE_NAME == db_a
True True
>>> connections['b'].settings.DATABASE_NAME == db_b >>> connections['b'].settings.DATABASE_NAME == db_b
@ -62,6 +62,65 @@ Invalid connection names raise ImproperlyConfigured:
Traceback (most recent call last): Traceback (most recent call last):
... ...
ImproperlyConfigured: No database connection 'bad' has been configured ImproperlyConfigured: No database connection 'bad' has been configured
Models can define which connection to use, by name. To use a named
connection, set the `db_connection` property in the model's Meta class
to the name of the connection. The name used must be a key in
settings.DATABASES, of course.
>>> from django.db import models
>>> class Artist(models.Model):
... name = models.CharField(maxlength=100)
... alive = models.BooleanField(default=True)
...
... def __str__(self):
... return self.name
...
... class Meta:
... app_label = 'mdb'
... db_connection = 'a'
...
>>> class Widget(models.Model):
... code = models.CharField(maxlength=10, unique=True)
... weight = models.IntegerField()
...
... def __str__(self):
... return self.code
...
... class Meta:
... app_label = 'mdb'
... db_connection = 'b'
But they don't have to. Multiple database support is entirely optional
and has no impact on your application if you don't use it.
>>> class Vehicle(models.Model):
... make = models.CharField(maxlength=20)
... model = models.CharField(maxlength=20)
... year = models.IntegerField()
...
... def __str__(self):
... return "%d %s %s" % (self.year, self.make, self.model)
...
... class Meta:
... app_label = 'mdb'
>>> Artist._meta.connection.settings.DATABASE_NAME == \
... connections['a'].connection.settings.DATABASE_NAME
True
>>> Widget._meta.connection.settings.DATABASE_NAME == \
... connections['b'].connection.settings.DATABASE_NAME
True
>>> Vehicle._meta.connection.settings.DATABASE_NAME == \
... connection.settings.DATABASE_NAME
True
>>> Artist._meta.connection.settings.DATABASE_NAME == \
... Widget._meta.connection.settings.DATABASE_NAME
False
>>> Artist._meta.connection.settings.DATABASE_NAME == \
... Vehicle._meta.connection.settings.DATABASE_NAME
False
""" """
def cleanup(): def cleanup():