Index: django/conf/project_template/settings.py =================================================================== --- django/conf/project_template/settings.py (revision 7507) +++ django/conf/project_template/settings.py (working copy) @@ -9,7 +9,7 @@ MANAGERS = ADMINS -DATABASE_ENGINE = '' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. +DATABASE_ENGINE = '' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3', 'oracle' or 'firebird'. DATABASE_NAME = '' # Or path to database file if using sqlite3. DATABASE_USER = '' # Not used with sqlite3. DATABASE_PASSWORD = '' # Not used with sqlite3. Index: django/db/models/sql/query.py =================================================================== --- django/db/models/sql/query.py (revision 7507) +++ django/db/models/sql/query.py (working copy) @@ -135,7 +135,7 @@ if name in self.quote_cache: return self.quote_cache[name] if ((name in self.alias_map and name not in self.table_map) or - name in self.extra_select): + name in self.extra_select) and not self.connection.features.always_quote: self.quote_cache[name] = name return name r = self.connection.ops.quote_name(name) @@ -219,6 +219,7 @@ distinct=False) obj.select = [] obj.extra_select = {} + obj.add_count_column() data = obj.execute_sql(SINGLE) if not data: @@ -277,7 +278,26 @@ result.append('GROUP BY %s' % ', '.join(grouping)) if ordering: - result.append('ORDER BY %s' % ', '.join(ordering)) + if self.connection.features.order_by_ordinal: + ordinals = {} + for i, c in enumerate(out_cols): + try: + c = c.split(' AS ')[1] + except IndexError: + pass + ordinals[c] = i + 1 + ordering2 = [] + for o in ordering: + o2 = o.split() + o3 = ordinals.get(o2[0], o2[0]) + try: + o3 = '%s %s' % (o3, o2[1]) + except IndexError: + pass + ordering2.append(o3) + result.append('ORDER BY %s' % ', '.join(ordering2)) + else: + result.append('ORDER BY %s' % ', '.join(ordering)) # FIXME: Pull this out to make life easier for Oracle et al. if with_limits: @@ -395,12 +415,33 @@ """ qn = self.quote_name_unless_alias qn2 = self.connection.ops.quote_name - result = ['(%s) AS %s' % (col, qn2(alias)) for alias, col in self.extra_select.iteritems()] + + if self.connection.features.always_quote: + result = [] + for alias, col in self.extra_select.items(): + if isinstance(col, basestring): + if col.find('+') != -1: + qn_col = [c.strip() for c in col.split('+')] + qn_col[0] = '.'.join((qn(c) for c in qn_col[0].split('.'))) + col = ' + '.join(qn_col) + else: + qn_col = col.split() + for i, qc in enumerate(qn_col): + if qc.lower() not in ('select', 'count(*)', 'from', 'where', "=", ">", "<"): + qn_col[i] = '.'.join((qn(c) for c in qn_col[i].split('.'))) + col = ' '.join(qn_col) + + result.append('(%s) AS %s' % (col, qn2(alias))) + else: + result = ['(%s) AS %s' % (col, qn2(alias)) for alias, col in self.extra_select.iteritems()] + aliases = self.extra_select.keys() + aliases = set(self.extra_select.keys()) if with_aliases: col_aliases = aliases.copy() else: col_aliases = set() + if self.select: for col in self.select: if isinstance(col, (list, tuple)): @@ -493,13 +534,16 @@ for alias in self.tables: if not self.alias_refcount[alias]: continue + try: name, alias, join_type, lhs, lhs_col, col, nullable = self.alias_map[alias] except KeyError: # Extra tables can end up in self.tables, but not in the # alias_map if they aren't in a join. That's OK. We skip them. continue + alias_str = (alias != name and ' %s' % alias or '') + if join_type and not first: result.append('%s %s%s ON (%s.%s = %s.%s)' % (join_type, qn(name), alias_str, qn(lhs), Index: django/db/models/sql/subqueries.py =================================================================== --- django/db/models/sql/subqueries.py (revision 7507) +++ django/db/models/sql/subqueries.py (working copy) @@ -376,6 +376,7 @@ multiple distinct columns and turn it into SQL that can be used on a variety of backends (it requires a select in the FROM clause). """ + import constants def get_from_clause(self): result, params = self._query.as_sql() return ['(%s) A1' % result], params @@ -383,3 +384,9 @@ def get_ordering(self): return () + #def execute_sql(self, result_type=constants.SINGLE): + # return super(Query, self).execute_sql(result_type) + # sql, params = self._query.as_sql() + # cursor = self.connection.cursor() + # cursor.execute(sql, params) + # return len(cursor.fetchall()), Index: django/db/models/fields/__init__.py =================================================================== --- django/db/models/fields/__init__.py (revision 7507) +++ django/db/models/fields/__init__.py (working copy) @@ -7,7 +7,7 @@ except ImportError: from django.utils import _decimal as decimal # for Python 2.3 -from django.db import get_creation_module +from django.db import connection, get_creation_module from django.db.models import signals from django.db.models.query_utils import QueryWrapper from django.dispatch import dispatcher @@ -89,7 +89,8 @@ editable=True, serialize=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, db_tablespace=None, auto_created=False): + db_column=None, db_tablespace=None, auto_created=False, + encoding=None): self.name = name self.verbose_name = verbose_name self.primary_key = primary_key @@ -111,6 +112,7 @@ self.help_text = help_text self.db_column = db_column self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE + self.encoding = encoding # Set db_index to True if the field has a relationship and doesn't explicitly set db_index. self.db_index = db_index @@ -227,9 +229,14 @@ def get_db_prep_lookup(self, lookup_type, value): "Returns field's value prepared for database lookup." + + if connection.features.uses_custom_lookups: + return connection.ops.get_db_prep_lookup(lookup_type, value) + if hasattr(value, 'as_sql'): sql, params = value.as_sql() return QueryWrapper(('(%s)' % sql), params) + if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search'): return [value] elif lookup_type in ('range', 'in'): @@ -964,6 +971,15 @@ defaults.update(kwargs) return super(IPAddressField, self).formfield(**defaults) +class LargeTextField(Field): + def get_manipulator_field_objs(self): + return [oldforms.LargeTextField] + + def formfield(self, **kwargs): + defaults = {'widget': forms.Textarea} + defaults.update(kwargs) + return super(LargeTextField, self).formfield(**defaults) + class NullBooleanField(Field): empty_strings_allowed = False def __init__(self, *args, **kwargs): @@ -1100,8 +1116,8 @@ # doesn't support microseconds. if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'): value = value.replace(microsecond=0) - if settings.DATABASE_ENGINE == 'oracle': - # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field. + if settings.DATABASE_ENGINE in ('oracle', 'firebird'): + # cx_Oracle and kinterbasdb expect a datetime.datetime to persist into TIMESTAMP field. if isinstance(value, datetime.time): value = datetime.datetime(1900, 1, 1, value.hour, value.minute, value.second, value.microsecond) Index: django/db/models/fields/related.py =================================================================== --- django/db/models/fields/related.py (revision 7507) +++ django/db/models/fields/related.py (working copy) @@ -1,6 +1,7 @@ from django.db import connection, transaction from django.db.models import signals, get_model from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, get_ul_class +from django.db.models.fields import FieldDoesNotExist from django.db.models.related import RelatedObject from django.db.models.query_utils import QueryWrapper from django.utils.text import capfirst @@ -379,18 +380,19 @@ new_ids.add(obj) # 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 + qn = connection.ops.quote_name cursor = connection.cursor() cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \ - (target_col_name, self.join_table, source_col_name, - target_col_name, ",".join(['%s'] * len(new_ids))), + (qn(target_col_name), qn(self.join_table), qn(source_col_name), + qn(target_col_name), ",".join(['%s'] * len(new_ids))), [self._pk_val] + list(new_ids)) existing_ids = set([row[0] for row in cursor.fetchall()]) # Add the ones that aren't there already for obj_id in (new_ids - existing_ids): - cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ - (self.join_table, source_col_name, target_col_name), - [self._pk_val, obj_id]) + cursor.execute('INSERT INTO %s (%s, %s) VALUES (%%s, %%s)' % \ + (qn(self.join_table), qn(source_col_name), qn(target_col_name)), + (self._pk_val, obj_id)) transaction.commit_unless_managed() def _remove_items(self, source_col_name, target_col_name, *objs): Property changes on: django/db/backends ___________________________________________________________________ Name: svn:externals + firebird http://django-firebird.googlecode.com/svn/trunk/firebird Index: django/db/backends/__init__.py =================================================================== --- django/db/backends/__init__.py (revision 7507) +++ django/db/backends/__init__.py (working copy) @@ -42,13 +42,16 @@ class BaseDatabaseFeatures(object): allows_group_by_ordinal = True allows_unique_and_pk = True + always_quote = False autoindexes_primary_keys = True inline_fk_references = True needs_datetime_string_cast = True needs_upper_for_iops = False + order_by_ordinal = False supports_constraints = True supports_tablespaces = False uses_case_insensitive_names = False + uses_custom_lookups = False uses_custom_query_class = False empty_fetchmany_value = [] update_can_self_select = True @@ -216,6 +219,9 @@ """ raise NotImplementedError() + def reference_name(self, r_col, col, r_table, table): + return '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) + def random_function_sql(self): """ Returns a SQL expression that returns a random value. Index: django/__init__.py =================================================================== --- django/__init__.py (revision 7507) +++ django/__init__.py (working copy) @@ -1,4 +1,4 @@ -VERSION = (0, 97, 'pre') +VERSION = (0, 97, 'pre-firebird') def get_version(): "Returns the version as a human-format string." Index: django/core/management/validation.py =================================================================== --- django/core/management/validation.py (revision 7507) +++ django/core/management/validation.py (working copy) @@ -1,4 +1,5 @@ import sys +import warnings from django.core.management.color import color_style from django.utils.itercompat import is_iterable @@ -19,7 +20,7 @@ Returns number of errors. """ from django.conf import settings - from django.db import models, connection + from django.db import models, connection, get_creation_module from django.db.models.loading import get_app_errors from django.db.models.fields.related import RelatedObject @@ -31,6 +32,14 @@ for cls in models.get_models(app): opts = cls._meta + # Do row-specific validation. + if settings.DATABASE_ENGINE == 'firebird': + creation = get_creation_module() + errs, _, _ = creation.validate_rowsize(opts) + for err in errs: + warnings.warn(err) + #print "It is recommended to explicitly set max_length or change the field to 'LargeTextField'" + # Do field-specific validation. for f in opts.local_fields: if f.name == 'id' and not f.primary_key and opts.pk.name == 'id': @@ -69,6 +78,12 @@ if db_version < (5, 0, 3) and isinstance(f, (models.CharField, models.CommaSeparatedIntegerField, models.SlugField)) and f.max_length > 255: e.add(opts, '"%s": %s cannot have a "max_length" greater than 255 when you are using a version of MySQL prior to 5.0.3 (you are using %s).' % (f.name, f.__class__.__name__, '.'.join([str(n) for n in db_version[:3]]))) + if settings.DATABASE_ENGINE == 'firebird': + errs, _ = creation.validate_index_limit(f, f.db_type(), opts) + for err in errs: + warnings.warn(err) + #print "It is recommended to explicitly adjust max_length and/or change the character encoding." + # Check to see if the related field will clash with any existing # fields, m2m fields, m2m related objects or related objects if f.rel: Index: django/core/management/sql.py =================================================================== --- django/core/management/sql.py (revision 7507) +++ django/core/management/sql.py (working copy) @@ -1,4 +1,5 @@ from django.core.management.base import CommandError +from django.conf import settings import os import re @@ -66,7 +67,7 @@ def sql_create(app, style): "Returns a list of the CREATE TABLE SQL statements for the given app." - from django.db import models + from django.db import models, get_creation_module from django.conf import settings if settings.DATABASE_ENGINE == 'dummy': @@ -99,7 +100,7 @@ # Create the many-to-many join tables. for model in app_models: final_output.extend(many_to_many_sql_for_model(model, style)) - + # Handle references to tables that are from other apps # but don't exist physically. not_installed_models = set(pending_references.keys()) @@ -166,7 +167,7 @@ col = f.column r_table = model._meta.db_table r_col = model._meta.get_field(f.rel.field_name).column - r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table)))) + r_name = connection.ops.reference_name(r_col, col, r_table, table) output.append('%s %s %s %s;' % \ (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(table)), @@ -250,8 +251,11 @@ Returns the SQL required to create a single model, as a tuple of: (list_of_sql, pending_references_dict) """ - from django.db import connection, models - + from django.db import connection, models, get_creation_module + creation_module = get_creation_module() + # If the database backend wants to create model itself, let it + if hasattr(creation_module, "sql_model_create"): + return creation_module.sql_model_create(model, style, known_models) opts = model._meta final_output = [] table_output = [] @@ -320,7 +324,7 @@ """ Returns any ALTER TABLE statements to add constraints after the fact. """ - from django.db import connection + from django.db import connection, get_creation_module from django.db.backends.util import truncate_name qn = connection.ops.quote_name @@ -336,7 +340,7 @@ col = opts.get_field(f.rel.field_name).column # For MySQL, r_name must be unique in the first 64 characters. # So we are careful with character usage here. - r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) + r_name = connection.ops.reference_name(r_col, col, r_table, table) final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \ (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()), qn(r_col), qn(table), qn(col), @@ -345,10 +349,15 @@ return final_output def many_to_many_sql_for_model(model, style): - from django.db import connection, models + from django.db import connection, models, get_creation_module from django.contrib.contenttypes import generic from django.db.backends.util import truncate_name - + + creation_module = get_creation_module() + # If the database backend wants to create many_to_many sql itself, let it + if hasattr(creation_module, "many_to_many_sql_for_model"): + return creation_module.many_to_many_sql_for_model(model, style) + opts = model._meta final_output = [] qn = connection.ops.quote_name @@ -411,8 +420,7 @@ final_output.append('\n'.join(table_output)) for r_table, r_col, table, col in deferred: - r_name = '%s_refs_%s_%x' % (r_col, col, - abs(hash((r_table, table)))) + r_name = connection.ops.reference_name(r_col, col, r_table, table) final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()), Index: django/contrib/redirects/models.py =================================================================== --- django/contrib/redirects/models.py (revision 7507) +++ django/contrib/redirects/models.py (working copy) @@ -4,9 +4,9 @@ class Redirect(models.Model): site = models.ForeignKey(Site, radio_admin=models.VERTICAL) - old_path = models.CharField(_('redirect from'), max_length=200, db_index=True, + old_path = models.CharField(_('redirect from'), max_length=192, db_index=True, encoding='ascii', help_text=_("This should be an absolute path, excluding the domain name. Example: '/events/search/'.")) - new_path = models.CharField(_('redirect to'), max_length=200, blank=True, + new_path = models.CharField(_('redirect to'), max_length=192, blank=True, encoding='ascii', help_text=_("This can be either an absolute path (as above) or a full URL starting with 'http://'.")) class Meta: Index: django/contrib/admin/models.py =================================================================== --- django/contrib/admin/models.py (revision 7507) +++ django/contrib/admin/models.py (working copy) @@ -18,10 +18,10 @@ action_time = models.DateTimeField(_('action time'), auto_now=True) user = models.ForeignKey(User) content_type = models.ForeignKey(ContentType, blank=True, null=True) - object_id = models.TextField(_('object id'), blank=True, null=True) + object_id = models.TextField(_('object id'), blank=True, null=True, max_length=1024) object_repr = models.CharField(_('object repr'), max_length=200) action_flag = models.PositiveSmallIntegerField(_('action flag')) - change_message = models.TextField(_('change message'), blank=True) + change_message = models.TextField(_('change message'), blank=True, max_length=5252) objects = LogEntryManager() class Meta: verbose_name = _('log entry') Index: django/contrib/contenttypes/models.py =================================================================== --- django/contrib/contenttypes/models.py (revision 7507) +++ django/contrib/contenttypes/models.py (working copy) @@ -63,8 +63,8 @@ class ContentType(models.Model): name = models.CharField(max_length=100) - app_label = models.CharField(max_length=100) - model = models.CharField(_('python model class name'), max_length=100) + app_label = models.CharField(max_length=100, encoding='ascii') + model = models.CharField(_('python model class name'), max_length=100, encoding='ascii') objects = ContentTypeManager() class Meta: Index: django/contrib/auth/models.py =================================================================== --- django/contrib/auth/models.py (revision 7507) +++ django/contrib/auth/models.py (working copy) @@ -72,7 +72,7 @@ """ name = models.CharField(_('name'), max_length=50) content_type = models.ForeignKey(ContentType) - codename = models.CharField(_('codename'), max_length=100) + codename = models.CharField(_('codename'), max_length=100, encoding='ascii') class Meta: verbose_name = _('permission') Index: django/contrib/flatpages/models.py =================================================================== --- django/contrib/flatpages/models.py (revision 7507) +++ django/contrib/flatpages/models.py (working copy) @@ -5,7 +5,8 @@ class FlatPage(models.Model): - url = models.CharField(_('URL'), max_length=100, validator_list=[validators.isAlphaNumericURL], db_index=True, + url = models.CharField(_('URL'), max_length=100, validator_list=[validators.isAlphaNumericURL], + db_index=True, encoding='ascii', help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes.")) title = models.CharField(_('title'), max_length=200) content = models.TextField(_('content'))