Skip to content

Commit

Permalink
schema-evolution:
Browse files Browse the repository at this point in the history
fixed postgresql constraint lookup after a table rename bug
pulled table renames into the backends
added postgresql unit tests



git-svn-id: http://code.djangoproject.com/svn/django/branches/schema-evolution@5784 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
keredson committed Aug 1, 2007
1 parent 4629092 commit 0b4c2c7
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 5 deletions.
9 changes: 6 additions & 3 deletions django/core/management.py
Expand Up @@ -5,6 +5,7 @@
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from optparse import OptionParser from optparse import OptionParser
from django.utils import termcolors from django.utils import termcolors
from django.conf import settings
import os, re, shutil, sys, textwrap import os, re, shutil, sys, textwrap


try: try:
Expand Down Expand Up @@ -591,9 +592,9 @@ def get_sql_evolution_check_for_changed_model_name(klass):
if klass._meta.db_table in table_list: if klass._meta.db_table in table_list:
return [], None return [], None
if klass._meta.aka in table_list: if klass._meta.aka in table_list:
return [ 'ALTER TABLE '+ backend.quote_name(klass._meta.aka) +' RENAME TO '+ backend.quote_name(klass._meta.db_table) + ';' ], klass._meta.aka return [ backend.get_change_table_name_sql( klass._meta.db_table, klass._meta.aka) ], klass._meta.aka
elif len(set(klass._meta.aka) & set(table_list))==1: elif len(set(klass._meta.aka) & set(table_list))==1:
return [ 'ALTER TABLE '+ backend.quote_name(klass._meta.aka[0]) +' RENAME TO '+ backend.quote_name(klass._meta.db_table) + ';' ], klass._meta.aka[0] return [ backend.get_change_table_name_sql( klass._meta.db_table, klass._meta.aka[0]) ], klass._meta.aka[0]
else: else:
return [], None return [], None


Expand Down Expand Up @@ -643,6 +644,7 @@ def get_sql_evolution_check_for_changed_field_flags(klass, new_table_name):
db_table = new_table_name db_table = new_table_name
for f in opts.fields: for f in opts.fields:
existing_fields = introspection.get_columns(cursor,db_table) existing_fields = introspection.get_columns(cursor,db_table)
# print existing_fields
cf = None # current field, ie what it is before any renames cf = None # current field, ie what it is before any renames
if f.column in existing_fields: if f.column in existing_fields:
cf = f.column cf = f.column
Expand All @@ -655,10 +657,11 @@ def get_sql_evolution_check_for_changed_field_flags(klass, new_table_name):
data_type = f.get_internal_type() data_type = f.get_internal_type()
if data_types.has_key(data_type): if data_types.has_key(data_type):
column_flags = introspection.get_known_column_flags(cursor, db_table, cf) column_flags = introspection.get_known_column_flags(cursor, db_table, cf)
# print db_table, cf, column_flags
if column_flags['allow_null']!=f.null or \ if column_flags['allow_null']!=f.null or \
( not f.primary_key and isinstance(f, CharField) and column_flags['maxlength']!=str(f.maxlength) ) or \ ( not f.primary_key and isinstance(f, CharField) and column_flags['maxlength']!=str(f.maxlength) ) or \
( not f.primary_key and isinstance(f, SlugField) and column_flags['maxlength']!=str(f.maxlength) ) or \ ( not f.primary_key and isinstance(f, SlugField) and column_flags['maxlength']!=str(f.maxlength) ) or \
column_flags['unique']!=f.unique or \ ( column_flags['unique']!=f.unique and ( settings.DATABASE_ENGINE!='postgresql' or not f.primary_key ) ) or \
column_flags['primary_key']!=f.primary_key: column_flags['primary_key']!=f.primary_key:
#column_flags['foreign_key']!=f.foreign_key: #column_flags['foreign_key']!=f.foreign_key:
# print # print
Expand Down
3 changes: 3 additions & 0 deletions django/db/backends/mysql/base.py
Expand Up @@ -242,6 +242,9 @@ def get_sql_sequence_reset(style, model_list):
# No sequence reset required # No sequence reset required
return [] return []


def get_change_table_name_sql( table_name, old_table_name ):
return 'ALTER TABLE '+ quote_name(old_table_name) +' RENAME TO '+ quote_name(table_name) + ';'

def get_change_column_name_sql( table_name, indexes, old_col_name, new_col_name, col_def ): def get_change_column_name_sql( table_name, indexes, old_col_name, new_col_name, col_def ):
# mysql doesn't support column renames (AFAIK), so we fake it # mysql doesn't support column renames (AFAIK), so we fake it
# TODO: only supports a single primary key so far # TODO: only supports a single primary key so far
Expand Down
5 changes: 5 additions & 0 deletions django/db/backends/postgresql/base.py
Expand Up @@ -282,6 +282,11 @@ def typecast_string(s):
return s return s
return smart_unicode(s) return smart_unicode(s)


def get_change_table_name_sql( table_name, old_table_name ):
output = []
output.append('ALTER TABLE '+ quote_name(old_table_name) +' RENAME TO '+ quote_name(table_name) + ';')
return '\n'.join(output)

def get_change_column_name_sql( table_name, indexes, old_col_name, new_col_name, col_def ): def get_change_column_name_sql( table_name, indexes, old_col_name, new_col_name, col_def ):
# TODO: only supports a single primary key so far # TODO: only supports a single primary key so far
pk_name = None pk_name = None
Expand Down
3 changes: 2 additions & 1 deletion django/db/backends/postgresql/introspection.py
Expand Up @@ -97,8 +97,9 @@ def get_known_column_flags( cursor, table_name, column_name ):
# print "select pg_constraint.conname, pg_constraint.contype, pg_attribute.attname from pg_constraint, pg_attribute where pg_constraint.conrelid=pg_attribute.attrelid and pg_attribute.attnum=any(pg_constraint.conkey) and pg_constraint.conname~'^%s'" % table_name # print "select pg_constraint.conname, pg_constraint.contype, pg_attribute.attname from pg_constraint, pg_attribute where pg_constraint.conrelid=pg_attribute.attrelid and pg_attribute.attnum=any(pg_constraint.conkey) and pg_constraint.conname~'^%s'" % table_name
unique_conname = None unique_conname = None
shared_unique_connames = set() shared_unique_connames = set()
cursor.execute("select pg_constraint.conname, pg_constraint.contype, pg_attribute.attname from pg_constraint, pg_attribute where pg_constraint.conrelid=pg_attribute.attrelid and pg_attribute.attnum=any(pg_constraint.conkey) and pg_constraint.conname~'^%s'" % table_name ) cursor.execute("select pg_constraint.conname, pg_constraint.contype, pg_attribute.attname from pg_constraint, pg_attribute, pg_class where pg_constraint.conrelid=pg_class.oid and pg_constraint.conrelid=pg_attribute.attrelid and pg_attribute.attnum=any(pg_constraint.conkey) and pg_class.relname='%s'" % table_name )
for row in cursor.fetchall(): for row in cursor.fetchall():
# print row
if row[2] == column_name: if row[2] == column_name:
if row[1]=='p': dict['primary_key'] = True if row[1]=='p': dict['primary_key'] = True
if row[1]=='f': dict['foreign_key'] = True if row[1]=='f': dict['foreign_key'] = True
Expand Down
3 changes: 3 additions & 0 deletions django/db/backends/sqlite3/base.py
Expand Up @@ -214,6 +214,9 @@ def _sqlite_regexp(re_pattern, re_string):
except: except:
return False return False


def get_change_table_name_sql( table_name, old_table_name ):
return 'ALTER TABLE '+ quote_name(old_table_name) +' RENAME TO '+ quote_name(table_name) + ';'

def get_change_column_name_sql( table_name, indexes, old_col_name, new_col_name, col_def ): def get_change_column_name_sql( table_name, indexes, old_col_name, new_col_name, col_def ):
# sqlite doesn't support column renames, so we fake it # sqlite doesn't support column renames, so we fake it
# TODO: only supports a single primary key so far # TODO: only supports a single primary key so far
Expand Down
54 changes: 53 additions & 1 deletion tests/modeltests/schema_evolution/models.py
Expand Up @@ -3,6 +3,7 @@
""" """


from django.db import models from django.db import models
from django.conf import settings


GENDER_CHOICES = ( GENDER_CHOICES = (
('M', 'Male'), ('M', 'Male'),
Expand All @@ -27,7 +28,10 @@ class Meta:
>>> from django.db import connection, get_introspection_module >>> from django.db import connection, get_introspection_module
>>> app = models.get_apps()[-1] >>> app = models.get_apps()[-1]
>>> cursor = connection.cursor() >>> cursor = connection.cursor()
"""}


if settings.DATABASE_ENGINE == 'mysql':
__test__['API_TESTS'] += """
# the table as it is supposed to be # the table as it is supposed to be
>>> create_table_sql = management.get_sql_all(app) >>> create_table_sql = management.get_sql_all(app)
>>> print create_table_sql >>> print create_table_sql
Expand Down Expand Up @@ -85,7 +89,55 @@ class Meta:
0L 0L
>>> management.get_sql_evolution(app) >>> management.get_sql_evolution(app)
['ALTER TABLE `schema_evolution_person` MODIFY COLUMN `name` varchar(20) NOT NULL;'] ['ALTER TABLE `schema_evolution_person` MODIFY COLUMN `name` varchar(20) NOT NULL;']
"""

if settings.DATABASE_ENGINE == 'postgresql':
__test__['API_TESTS'] += """
# the table as it is supposed to be
>>> create_table_sql = management.get_sql_all(app)
>>> print create_table_sql
['CREATE TABLE "schema_evolution_person" (\\n "id" serial NOT NULL PRIMARY KEY,\\n "name" varchar(20) NOT NULL,\\n "gender" varchar(1) NOT NULL,\\n "gender2" varchar(1) NOT NULL\\n)\\n;']
# make sure we don't evolve an unedited table
>>> management.get_sql_evolution(app)
[]
"""} # delete a column, so it looks like we've recently added a field
>>> cursor.execute( backend.get_drop_column_sql( 'schema_evolution_person', 'gender' ) )
>>> management.get_sql_evolution(app)
['ALTER TABLE "schema_evolution_person" ADD COLUMN "gender" varchar(1);\\nALTER TABLE "schema_evolution_person" ALTER COLUMN "gender" SET NOT NULL;']
# reset the db
>>> cursor.execute('DROP TABLE schema_evolution_person;'); cursor.execute(create_table_sql[0])
# add a column, so it looks like we've recently deleted a field
>>> cursor.execute( backend.get_add_column_sql( 'schema_evolution_person', 'gender_nothere', 'varchar(1)', True, False, False ) )
>>> management.get_sql_evolution(app)
['-- warning: as the following may cause data loss, it/they must be run manually', u'-- ALTER TABLE "schema_evolution_person" DROP COLUMN "gender_nothere";', '-- end warning']
# reset the db
>>> cursor.execute('DROP TABLE schema_evolution_person;'); cursor.execute(create_table_sql[0])
# rename column, so it looks like we've recently renamed a field
>>> cursor.execute( backend.get_change_column_name_sql( 'schema_evolution_person', {}, 'gender2', 'gender_old', 'varchar(1)' ) )
>>> management.get_sql_evolution(app)
['ALTER TABLE "schema_evolution_person" RENAME COLUMN "gender_old" TO "gender2";']
# reset the db
>>> cursor.execute('DROP TABLE schema_evolution_person;'); cursor.execute(create_table_sql[0])
# rename table, so it looks like we've recently renamed a model
>>> cursor.execute( backend.get_change_table_name_sql( 'schema_evolution_personold', 'schema_evolution_person' ) )
>>> management.get_sql_evolution(app)
['ALTER TABLE "schema_evolution_personold" RENAME TO "schema_evolution_person";']
# reset the db
>>> cursor.execute(create_table_sql[0])
# change column flags, so it looks like we've recently changed a column flag
>>> cursor.execute( backend.get_change_column_def_sql( 'schema_evolution_person', 'name', 'varchar(10)', True, False, False ) )
>>> management.get_sql_evolution(app)
['ALTER TABLE "schema_evolution_person" ADD COLUMN "name_tmp" varchar(20);\\nUPDATE "schema_evolution_person" SET "name_tmp" = "name";\\nALTER TABLE "schema_evolution_person" DROP COLUMN "name";\\nALTER TABLE "schema_evolution_person" RENAME COLUMN "name_tmp" TO "name";\\nALTER TABLE "schema_evolution_person" ALTER COLUMN "name" SET NOT NULL;']
"""


0 comments on commit 0b4c2c7

Please sign in to comment.