Permalink
Browse files

implemented schema evolution

git-svn-id: http://code.djangoproject.com/svn/django/branches/schema-evolution@3646 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent 96d4d69 commit ffac8356ddcc4349ae902e9ba945fdb761a16a46 @keredson keredson committed Aug 22, 2006
View

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -40,7 +40,7 @@ def execute(self, sql, params=()):
def executemany(self, sql, param_list):
try:
return self.cursor.executemany(sql, param_list)
- except Database.Warning:
+ except Database.Warning, w:
self.cursor.execute("SHOW WARNINGS")
raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
@@ -161,6 +161,48 @@ def get_drop_foreignkey_sql():
def get_pk_default_value():
return "DEFAULT"
+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
+ # TODO: only supports a single primary key so far
+ pk_name = None
+ for key in indexes.keys():
+ if indexes[key]['primary_key']: pk_name = key
+ output = []
+ output.append( 'ALTER TABLE '+ quote_name(table_name) +' CHANGE COLUMN '+ quote_name(old_col_name) +' '+ quote_name(new_col_name) +' '+ col_def + ';' )
+ return '\n'.join(output)
+
+def get_change_column_def_sql( table_name, col_name, col_type, null, unique, primary_key ):
+ output = []
+ col_def = col_type +' '+ ('%sNULL' % (not null and 'NOT ' or ''))
+ if unique:
+ col_def += ' '+ 'UNIQUE'
+ if primary_key:
+ col_def += ' '+ 'PRIMARY KEY'
+ output.append( 'ALTER TABLE '+ quote_name(table_name) +' MODIFY COLUMN '+ quote_name(col_name) +' '+ col_def + ';' )
+ return '\n'.join(output)
+
+def get_add_column_sql( table_name, col_name, col_type, null, unique, primary_key ):
+ output = []
+ field_output = []
+ field_output.append('ALTER TABLE')
+ field_output.append(quote_name(table_name))
+ field_output.append('ADD COLUMN')
+ field_output.append(quote_name(col_name))
+ field_output.append(col_type)
+ field_output.append(('%sNULL' % (not null and 'NOT ' or '')))
+ if unique:
+ field_output.append(('UNIQUE'))
+ if primary_key:
+ field_output.append(('PRIMARY KEY'))
+ output.append(' '.join(field_output) + ';')
+ return '\n'.join(output)
+
+def get_drop_column_sql( table_name, col_name ):
+ output = []
+ output.append( '-- ALTER TABLE '+ quote_name(table_name) +' DROP COLUMN '+ quote_name(col_name) + ';' )
+ return '\n'.join(output)
+
+
OPERATOR_MAPPING = {
'exact': '= %s',
'iexact': 'LIKE %s',
@@ -1,4 +1,3 @@
-from django.db import transaction
from django.db.backends.mysql.base import quote_name
from MySQLdb import ProgrammingError, OperationalError
from MySQLdb.constants import FIELD_TYPE
@@ -73,6 +72,43 @@ def get_indexes(cursor, table_name):
indexes[row[4]] = {'primary_key': (row[2] == 'PRIMARY'), 'unique': not bool(row[1])}
return indexes
+def get_columns(cursor, table_name):
+ try:
+ cursor.execute("describe %s" % quote_name(table_name))
+ return [row[0] for row in cursor.fetchall()]
+ except:
+ return []
+
+def get_known_column_flags( cursor, table_name, column_name ):
+ cursor.execute("describe %s" % quote_name(table_name))
+ dict = {}
+ for row in cursor.fetchall():
+ if row[0] == column_name:
+
+ # maxlength check goes here
+ if row[1][0:7]=='varchar':
+ dict['maxlength'] = row[1][8:len(row[1])-1]
+
+ # default flag check goes here
+ if row[2]=='YES': dict['allow_null'] = True
+ else: dict['allow_null'] = False
+
+ # primary/foreign/unique key flag check goes here
+ if row[3]=='PRI': dict['primary_key'] = True
+ else: dict['primary_key'] = False
+ if row[3]=='FOR': dict['foreign_key'] = True
+ else: dict['foreign_key'] = False
+ if row[3]=='UNI': dict['unique'] = True
+ else: dict['unique'] = False
+
+ # default value check goes here
+ # if row[4]=='NULL': dict['default'] = None
+ # else: dict['default'] = row[4]
+ dict['default'] = row[4]
+
+ # print table_name, column_name, dict
+ return dict
+
DATA_TYPES_REVERSE = {
FIELD_TYPE.BLOB: 'TextField',
FIELD_TYPE.CHAR: 'CharField',
@@ -111,6 +111,42 @@ def get_drop_foreignkey_sql():
def get_pk_default_value():
return "DEFAULT"
+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
+ pk_name = None
+ for key in indexes.keys():
+ if indexes[key]['primary_key']: pk_name = key
+ output = []
+ output.append( 'ALTER TABLE '+ quote_name(table_name) +' RENAME COLUMN '+ quote_name(old_col_name) +' TO '+ quote_name(new_col_name) +';' )
+ return '\n'.join(output)
+
+def get_change_column_def_sql( table_name, col_name, col_type, null, unique, primary_key ):
+ output = []
+ output.append( 'ALTER TABLE '+ quote_name(table_name) +' ADD COLUMN '+ quote_name(col_name+'_tmp') +' '+ col_type + ';' )
+ output.append( 'UPDATE '+ quote_name(table_name) +' SET '+ quote_name(col_name+'_tmp') +' = '+ quote_name(col_name) + ';' )
+ output.append( 'ALTER TABLE '+ quote_name(table_name) +' DROP COLUMN '+ quote_name(col_name) +';' )
+ output.append( 'ALTER TABLE '+ quote_name(table_name) +' RENAME COLUMN '+ quote_name(col_name+'_tmp') +' TO '+ quote_name(col_name) + ';' )
+ if not null:
+ output.append( 'ALTER TABLE '+ quote_name(table_name) +' ALTER COLUMN '+ quote_name(col_name) +' SET NOT NULL;' )
+ if unique:
+ output.append( 'ALTER TABLE '+ quote_name(table_name) +' ADD CONSTRAINT '+ table_name +'_'+ col_name +'_unique_constraint UNIQUE('+ col_name +');' )
+
+ return '\n'.join(output)
+
+def get_add_column_sql( table_name, col_name, col_type, null, unique, primary_key ):
+ output = []
+ output.append( 'ALTER TABLE '+ quote_name(table_name) +' ADD COLUMN '+ quote_name(col_name) +' '+ col_type + ';' )
+ if not null:
+ output.append( 'ALTER TABLE '+ quote_name(table_name) +' ALTER COLUMN '+ quote_name(col_name) +' SET NOT NULL;' )
+ if unique:
+ output.append( 'ALTER TABLE '+ quote_name(table_name) +' ADD CONSTRAINT '+ table_name +'_'+ col_name +'_unique_constraint UNIQUE('+ col_name +');' )
+ return '\n'.join(output)
+
+def get_drop_column_sql( table_name, col_name ):
+ output = []
+ output.append( '-- ALTER TABLE '+ quote_name(table_name) +' DROP COLUMN '+ quote_name(col_name) + ';' )
+ return '\n'.join(output)
+
# Register these custom typecasts, because Django expects dates/times to be
# in Python's native (standard-library) datetime/time format, whereas psycopg
# use mx.DateTime by default.
@@ -1,4 +1,3 @@
-from django.db import transaction
from django.db.backends.postgresql.base import quote_name
def get_table_list(cursor):
@@ -67,6 +66,58 @@ def get_indexes(cursor, table_name):
indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]}
return indexes
+def get_columns(cursor, table_name):
+ try:
+ cursor.execute("SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(d.adsrc for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, a.attnum, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = (SELECT c.oid from pg_catalog.pg_class c where c.relname ~ '^%s$') AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum" % table_name)
+ return [row[0] for row in cursor.fetchall()]
+ except:
+ return []
+
+def get_known_column_flags( cursor, table_name, column_name ):
+# print "SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(d.adsrc for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, a.attnum, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = (SELECT c.oid from pg_catalog.pg_class c where c.relname ~ '^%s$') AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum" % table_name
+ cursor.execute("SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), (SELECT substring(d.adsrc for 128) FROM pg_catalog.pg_attrdef d WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), a.attnotnull, a.attnum, pg_catalog.col_description(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = (SELECT c.oid from pg_catalog.pg_class c where c.relname ~ '^%s$') AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum" % table_name)
+ dict = {}
+ dict['primary_key'] = False
+ dict['foreign_key'] = False
+ dict['unique'] = False
+ dict['default'] = ''
+
+# dict['allow_null'] = False
+ for row in cursor.fetchall():
+ if row[0] == column_name:
+
+ # maxlength check goes here
+ if row[1][0:17]=='character varying':
+ dict['maxlength'] = row[1][18:len(row[1])-1]
+
+ # null flag check goes here
+ dict['allow_null'] = not row[3]
+
+ # pk, fk and unique checks go here
+# 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
+ 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 )
+ for row in cursor.fetchall():
+ if row[2] == column_name:
+ if row[1]=='p': dict['primary_key'] = True
+ if row[1]=='f': dict['foreign_key'] = True
+ if row[1]=='u': unique_conname = row[0]
+ else:
+ if row[1]=='u': shared_unique_connames.add( row[0] )
+ if unique_conname and unique_conname not in shared_unique_connames:
+ dict['unique'] = True
+
+ # default value check goes here
+ cursor.execute("select pg_attribute.attname, adsrc from pg_attrdef, pg_attribute WHERE pg_attrdef.adrelid=pg_attribute.attrelid and pg_attribute.attnum=pg_attrdef.adnum and pg_attrdef.adrelid = (SELECT c.oid from pg_catalog.pg_class c where c.relname ~ '^%s$')" % table_name )
+ for row in cursor.fetchall():
+ if row[0] == column_name:
+ if row[1][0:7] == 'nextval': continue
+ dict['default'] = row[1][1:row[1].index("'",1)]
+
+# print table_name, column_name, dict
+ return dict
+
# Maps type codes to Django Field types.
DATA_TYPES_REVERSE = {
16: 'BooleanField',
@@ -145,6 +145,51 @@ def _sqlite_date_trunc(lookup_type, dt):
elif lookup_type == 'day':
return "%i-%02i-%02i 00:00:00" % (dt.year, dt.month, dt.day)
+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
+ # TODO: only supports a single primary key so far
+ pk_name = None
+ for key in indexes.keys():
+ if indexes[key]['primary_key']: pk_name = key
+ output = []
+ output.append( 'ALTER TABLE '+ quote_name(table_name) +' ADD COLUMN '+ quote_name(new_col_name) +' '+ col_def + ';' )
+ output.append( 'UPDATE '+ quote_name(table_name) +' SET '+ new_col_name +' = '+ old_col_name +' WHERE '+ pk_name +'=(select '+ pk_name +' from '+ table_name +');' )
+ output.append( '-- FYI: sqlite does not support deleting columns, so '+ quote_name(old_col_name) +' remains as cruft' )
+ # use the following when sqlite gets drop support
+ #output.append( 'ALTER TABLE '+ quote_name(table_name) +' DROP COLUMN '+ quote_name(old_col_name) )
+ return '\n'.join(output)
+
+def get_change_column_def_sql( table_name, col_name, col_def ):
+ # sqlite doesn't support column modifications, so we fake it
+ output = []
+ # TODO: fake via renaming the table, building a new one and deleting the old
+ output.append('-- sqlite does not support column modifications '+ quote_name(table_name) +'.'+ quote_name(col_name) +' to '+ col_def)
+ return '\n'.join(output)
+
+def get_add_column_sql( table_name, col_name, col_type, null, unique, primary_key ):
+ output = []
+ field_output = []
+ field_output.append('ALTER TABLE')
+ field_output.append(quote_name(table_name))
+ field_output.append('ADD COLUMN')
+ field_output.append(quote_name(col_name))
+ field_output.append(col_type)
+ field_output.append(('%sNULL' % (not null and 'NOT ' or '')))
+ if unique:
+ field_output.append(('UNIQUE'))
+ if primary_key:
+ field_output.append(('PRIMARY KEY'))
+ output.append(' '.join(field_output) + ';')
+ return '\n'.join(output)
+
+def get_drop_column_sql( table_name, col_name ):
+ output = []
+ output.append( '-- FYI: sqlite does not support deleting columns, so '+ quote_name(old_col_name) +' remains as cruft' )
+ # use the following when sqlite gets drop support
+ # output.append( '-- ALTER TABLE '+ quote_name(table_name) +' DROP COLUMN '+ quote_name(col_name) )
+ return '\n'.join(output)
+
+
# SQLite requires LIKE statements to include an ESCAPE clause if the value
# being escaped has a percent or underscore in it.
# See http://www.sqlite.org/lang_expr.html for an explanation.
@@ -1,4 +1,3 @@
-from django.db import transaction
from django.db.backends.sqlite3.base import quote_name
def get_table_list(cursor):
@@ -44,6 +43,43 @@ def get_indexes(cursor, table_name):
indexes[name]['unique'] = True
return indexes
+def get_columns(cursor, table_name):
+ try:
+ cursor.execute("PRAGMA table_info(%s)" % quote_name(table_name))
+ return [row[1] for row in cursor.fetchall()]
+ except:
+ return []
+
+def get_known_column_flags( cursor, table_name, column_name ):
+ cursor.execute("PRAGMA table_info(%s)" % quote_name(table_name))
+ dict = {}
+ for row in cursor.fetchall():
+ if row[1] == column_name:
+
+ # maxlength check goes here
+ if row[2][0:7]=='varchar':
+ dict['maxlength'] = row[2][8:len(row[2])-1]
+
+ # default flag check goes here
+ #if row[2]=='YES': dict['allow_null'] = True
+ #else: dict['allow_null'] = False
+
+ # primary/foreign/unique key flag check goes here
+ #if row[3]=='PRI': dict['primary_key'] = True
+ #else: dict['primary_key'] = False
+ #if row[3]=='FOR': dict['foreign_key'] = True
+ #else: dict['foreign_key'] = False
+ #if row[3]=='UNI': dict['unique'] = True
+ #else: dict['unique'] = False
+
+ # default value check goes here
+ # if row[4]=='NULL': dict['default'] = None
+ # else: dict['default'] = row[4]
+ #dict['default'] = row[4]
+
+ print table_name, column_name, dict
+ return dict
+
def _table_info(cursor, name):
cursor.execute('PRAGMA table_info(%s)' % quote_name(name))
# cid, name, type, notnull, dflt_value, pk
@@ -4,7 +4,7 @@
from django.core import validators
from django import forms
from django.core.exceptions import ObjectDoesNotExist
-from django.utils.functional import curry, lazy
+from django.utils.functional import curry
from django.utils.text import capfirst
from django.utils.translation import gettext, gettext_lazy
import datetime, os, time
@@ -20,7 +20,7 @@ class NOT_PROVIDED:
BLANK_CHOICE_NONE = [("", "None")]
# prepares a value for use in a LIKE query
-prep_for_like_query = lambda x: str(x).replace("%", "\%").replace("_", "\_")
+prep_for_like_query = lambda x: str(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
# returns the <ul> class for a given radio_admin value
get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
@@ -68,7 +68,7 @@ def __init__(self, verbose_name=None, name=None, primary_key=False,
core=False, rel=None, default=NOT_PROVIDED, editable=True,
prepopulate_from=None, unique_for_date=None, unique_for_month=None,
unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
- help_text='', db_column=None):
+ help_text='', db_column=None, aka=None):
self.name = name
self.verbose_name = verbose_name
self.primary_key = primary_key
@@ -84,6 +84,7 @@ def __init__(self, verbose_name=None, name=None, primary_key=False,
self.radio_admin = radio_admin
self.help_text = help_text
self.db_column = db_column
+ self.aka = aka
# Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
self.db_index = db_index
@@ -247,9 +248,9 @@ def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=
params['is_required'] = not self.blank and not self.primary_key and not rel
# BooleanFields (CheckboxFields) are a special case. They don't take
- # is_required or validator_list.
+ # is_required.
if isinstance(self, BooleanField):
- del params['validator_list'], params['is_required']
+ del params['is_required']
# If this field is in a related context, check whether any other fields
# in the related object have core=True. If so, add a validator --
@@ -289,8 +290,11 @@ def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH):
if self.choices:
return first_choice + list(self.choices)
rel_model = self.rel.to
- return first_choice + [(x._get_pk_val(), str(x))
- for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)]
+ if hasattr(self.rel, 'get_related_field'):
+ lst = [(getattr(x, self.rel.get_related_field().attname), str(x)) for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)]
+ else:
+ lst = [(x._get_pk_val(), str(x)) for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)]
+ return first_choice + lst
def get_choices_default(self):
if self.radio_admin:
@@ -364,8 +368,8 @@ def __init__(self, *args, **kwargs):
def to_python(self, value):
if value in (True, False): return value
- if value is 't': return True
- if value is 'f': return False
+ if value in ('t', 'True'): return True
+ if value in ('f', 'False'): return False
raise validators.ValidationError, gettext("This value must be either True or False.")
def get_manipulator_field_objs(self):
Oops, something went wrong.

0 comments on commit ffac835

Please sign in to comment.