mirror of https://github.com/sgoudham/Enso-Bot.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
363 lines
14 KiB
Python
363 lines
14 KiB
Python
# firebird
|
|
|
|
from __future__ import print_function
|
|
|
|
import datetime
|
|
|
|
from django.db import connection, models
|
|
from django.core.management.color import no_style
|
|
from django.db.utils import DatabaseError
|
|
|
|
from south.db import generic
|
|
from south.utils.py3 import string_types
|
|
|
|
class DatabaseOperations(generic.DatabaseOperations):
|
|
backend_name = 'firebird'
|
|
alter_string_set_type = 'ALTER %(column)s TYPE %(type)s'
|
|
alter_string_set_default = 'ALTER %(column)s SET DEFAULT %(default)s;'
|
|
alter_string_drop_null = ''
|
|
add_column_string = 'ALTER TABLE %s ADD %s;'
|
|
delete_column_string = 'ALTER TABLE %s DROP %s;'
|
|
rename_table_sql = ''
|
|
|
|
# Features
|
|
allows_combined_alters = False
|
|
has_booleans = False
|
|
|
|
def _fill_constraint_cache(self, db_name, table_name):
|
|
self._constraint_cache.setdefault(db_name, {})
|
|
self._constraint_cache[db_name][table_name] = {}
|
|
|
|
rows = self.execute("""
|
|
SELECT
|
|
rc.RDB$CONSTRAINT_NAME,
|
|
rc.RDB$CONSTRAINT_TYPE,
|
|
cc.RDB$TRIGGER_NAME
|
|
FROM rdb$relation_constraints rc
|
|
JOIN rdb$check_constraints cc
|
|
ON rc.rdb$constraint_name = cc.rdb$constraint_name
|
|
WHERE rc.rdb$constraint_type = 'NOT NULL'
|
|
AND rc.rdb$relation_name = '%s'
|
|
""" % table_name)
|
|
|
|
for constraint, kind, column in rows:
|
|
self._constraint_cache[db_name][table_name].setdefault(column, set())
|
|
self._constraint_cache[db_name][table_name][column].add((kind, constraint))
|
|
return
|
|
|
|
def _alter_column_set_null(self, table_name, column_name, is_null):
|
|
sql = """
|
|
UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = %(null_flag)s
|
|
WHERE RDB$FIELD_NAME = '%(column)s'
|
|
AND RDB$RELATION_NAME = '%(table_name)s'
|
|
"""
|
|
null_flag = 'NULL' if is_null else '1'
|
|
return sql % {
|
|
'null_flag': null_flag,
|
|
'column': column_name.upper(),
|
|
'table_name': table_name.upper()
|
|
}
|
|
|
|
def _column_has_default(self, params):
|
|
sql = """
|
|
SELECT a.RDB$DEFAULT_VALUE
|
|
FROM RDB$RELATION_FIELDS a
|
|
WHERE a.RDB$FIELD_NAME = '%(column)s'
|
|
AND a.RDB$RELATION_NAME = '%(table_name)s'
|
|
"""
|
|
value = self.execute(sql % params)
|
|
return True if value else False
|
|
|
|
|
|
def _alter_set_defaults(self, field, name, params, sqls):
|
|
"Subcommand of alter_column that sets default values (overrideable)"
|
|
# Historically, we used to set defaults here.
|
|
# But since South 0.8, we don't ever set defaults on alter-column -- we only
|
|
# use database-level defaults as scaffolding when adding columns.
|
|
# However, we still sometimes need to remove defaults in alter-column.
|
|
if self._column_has_default(params):
|
|
sqls.append(('ALTER COLUMN %s DROP DEFAULT' % (self.quote_name(name),), []))
|
|
|
|
|
|
@generic.invalidate_table_constraints
|
|
def create_table(self, table_name, fields):
|
|
columns = []
|
|
autoinc_sql = ''
|
|
|
|
for field_name, field in fields:
|
|
# avoid default values in CREATE TABLE statements (#925)
|
|
field._suppress_default = True
|
|
|
|
col = self.column_sql(table_name, field_name, field)
|
|
if not col:
|
|
continue
|
|
|
|
columns.append(col)
|
|
if isinstance(field, models.AutoField):
|
|
field_name = field.db_column or field.column
|
|
autoinc_sql = connection.ops.autoinc_sql(table_name, field_name)
|
|
|
|
self.execute(self.create_table_sql % {
|
|
"table": self.quote_name(table_name),
|
|
"columns": ', '.join([col for col in columns if col]),
|
|
})
|
|
|
|
if autoinc_sql:
|
|
self.execute(autoinc_sql[0])
|
|
self.execute(autoinc_sql[1])
|
|
|
|
def rename_table(self, old_table_name, table_name):
|
|
"""
|
|
Renames table is not supported by firebird.
|
|
This involve recreate all related objects (store procedure, views, triggers, etc)
|
|
"""
|
|
pass
|
|
|
|
@generic.invalidate_table_constraints
|
|
def delete_table(self, table_name, cascade=False):
|
|
"""
|
|
Deletes the table 'table_name'.
|
|
Firebird will also delete any triggers associated with the table.
|
|
"""
|
|
super(DatabaseOperations, self).delete_table(table_name, cascade=False)
|
|
|
|
# Also, drop sequence if exists
|
|
sql = connection.ops.drop_sequence_sql(table_name)
|
|
if sql:
|
|
try:
|
|
self.execute(sql)
|
|
except:
|
|
pass
|
|
|
|
def column_sql(self, table_name, field_name, field, tablespace='', with_name=True, field_prepared=False):
|
|
"""
|
|
Creates the SQL snippet for a column. Used by add_column and add_table.
|
|
"""
|
|
|
|
# If the field hasn't already been told its attribute name, do so.
|
|
if not field_prepared:
|
|
field.set_attributes_from_name(field_name)
|
|
|
|
# hook for the field to do any resolution prior to it's attributes being queried
|
|
if hasattr(field, 'south_init'):
|
|
field.south_init()
|
|
|
|
# Possible hook to fiddle with the fields (e.g. defaults & TEXT on MySQL)
|
|
field = self._field_sanity(field)
|
|
|
|
try:
|
|
sql = field.db_type(connection=self._get_connection())
|
|
except TypeError:
|
|
sql = field.db_type()
|
|
|
|
if sql:
|
|
# Some callers, like the sqlite stuff, just want the extended type.
|
|
if with_name:
|
|
field_output = [self.quote_name(field.column), sql]
|
|
else:
|
|
field_output = [sql]
|
|
|
|
if field.primary_key:
|
|
field_output.append('NOT NULL PRIMARY KEY')
|
|
elif field.unique:
|
|
# Just use UNIQUE (no indexes any more, we have delete_unique)
|
|
field_output.append('UNIQUE')
|
|
|
|
sql = ' '.join(field_output)
|
|
sqlparams = ()
|
|
|
|
# if the field is "NOT NULL" and a default value is provided, create the column with it
|
|
# this allows the addition of a NOT NULL field to a table with existing rows
|
|
if not getattr(field, '_suppress_default', False):
|
|
if field.has_default():
|
|
default = field.get_default()
|
|
# If the default is actually None, don't add a default term
|
|
if default is not None:
|
|
# If the default is a callable, then call it!
|
|
if callable(default):
|
|
default = default()
|
|
# Now do some very cheap quoting. TODO: Redesign return values to avoid this.
|
|
if isinstance(default, string_types):
|
|
default = "'%s'" % default.replace("'", "''")
|
|
elif isinstance(default, (datetime.date, datetime.time, datetime.datetime)):
|
|
default = "'%s'" % default
|
|
elif isinstance(default, bool):
|
|
default = int(default)
|
|
# Escape any % signs in the output (bug #317)
|
|
if isinstance(default, string_types):
|
|
default = default.replace("%", "%%")
|
|
# Add it in
|
|
sql += " DEFAULT %s"
|
|
sqlparams = (default)
|
|
elif (not field.null and field.blank) or (field.get_default() == ''):
|
|
if field.empty_strings_allowed and self._get_connection().features.interprets_empty_strings_as_nulls:
|
|
sql += " DEFAULT ''"
|
|
# Error here would be nice, but doesn't seem to play fair.
|
|
#else:
|
|
# raise ValueError("Attempting to add a non null column that isn't character based without an explicit default value.")
|
|
|
|
# Firebird need set not null after of default value keyword
|
|
if not field.primary_key and not field.null:
|
|
sql += ' NOT NULL'
|
|
|
|
if field.rel and self.supports_foreign_keys:
|
|
self.add_deferred_sql(
|
|
self.foreign_key_sql(
|
|
table_name,
|
|
field.column,
|
|
field.rel.to._meta.db_table,
|
|
field.rel.to._meta.get_field(field.rel.field_name).column
|
|
)
|
|
)
|
|
|
|
# Things like the contrib.gis module fields have this in 1.1 and below
|
|
if hasattr(field, 'post_create_sql'):
|
|
for stmt in field.post_create_sql(no_style(), table_name):
|
|
self.add_deferred_sql(stmt)
|
|
|
|
# Avoid double index creation (#1317)
|
|
# Firebird creates an index implicity for each foreign key field
|
|
# sql_indexes_for_field tries to create an index for that field too
|
|
if not field.rel:
|
|
# In 1.2 and above, you have to ask the DatabaseCreation stuff for it.
|
|
# This also creates normal indexes in 1.1.
|
|
if hasattr(self._get_connection().creation, "sql_indexes_for_field"):
|
|
# Make a fake model to pass in, with only db_table
|
|
model = self.mock_model("FakeModelForGISCreation", table_name)
|
|
for stmt in self._get_connection().creation.sql_indexes_for_field(model, field, no_style()):
|
|
self.add_deferred_sql(stmt)
|
|
|
|
if sql:
|
|
return sql % sqlparams
|
|
else:
|
|
return None
|
|
|
|
|
|
def _drop_constraints(self, table_name, name, field):
|
|
if self.has_check_constraints:
|
|
check_constraints = self._constraints_affecting_columns(table_name, [name], "CHECK")
|
|
for constraint in check_constraints:
|
|
self.execute(self.delete_check_sql % {
|
|
'table': self.quote_name(table_name),
|
|
'constraint': self.quote_name(constraint),
|
|
})
|
|
|
|
# Drop or add UNIQUE constraint
|
|
unique_constraint = list(self._constraints_affecting_columns(table_name, [name], "UNIQUE"))
|
|
if field.unique and not unique_constraint:
|
|
self.create_unique(table_name, [name])
|
|
elif not field.unique and unique_constraint:
|
|
self.delete_unique(table_name, [name])
|
|
|
|
# Drop all foreign key constraints
|
|
try:
|
|
self.delete_foreign_key(table_name, name)
|
|
except ValueError:
|
|
# There weren't any
|
|
pass
|
|
|
|
|
|
@generic.invalidate_table_constraints
|
|
def alter_column(self, table_name, name, field, explicit_name=True, ignore_constraints=False):
|
|
"""
|
|
Alters the given column name so it will match the given field.
|
|
Note that conversion between the two by the database must be possible.
|
|
Will not automatically add _id by default; to have this behavour, pass
|
|
explicit_name=False.
|
|
|
|
@param table_name: The name of the table to add the column to
|
|
@param name: The name of the column to alter
|
|
@param field: The new field definition to use
|
|
"""
|
|
|
|
if self.dry_run:
|
|
if self.debug:
|
|
print(' - no dry run output for alter_column() due to dynamic DDL, sorry')
|
|
return
|
|
|
|
|
|
# hook for the field to do any resolution prior to it's attributes being queried
|
|
if hasattr(field, 'south_init'):
|
|
field.south_init()
|
|
|
|
# Add _id or whatever if we need to
|
|
field.set_attributes_from_name(name)
|
|
if not explicit_name:
|
|
name = field.column
|
|
else:
|
|
field.column = name
|
|
|
|
if not ignore_constraints:
|
|
# Drop all check constraints. Note that constraints will be added back
|
|
# with self.alter_string_set_type and self.alter_string_drop_null.
|
|
self._drop_constraints(table_name, name, field)
|
|
|
|
# First, change the type
|
|
params = {
|
|
"column": self.quote_name(name),
|
|
"type": self._db_type_for_alter_column(field),
|
|
"table_name": table_name
|
|
}
|
|
|
|
# SQLs is a list of (SQL, values) pairs.
|
|
sqls = []
|
|
sqls_extra = []
|
|
|
|
# Only alter the column if it has a type (Geometry ones sometimes don't)
|
|
if params["type"] is not None:
|
|
sqls.append((self.alter_string_set_type % params, []))
|
|
|
|
# Add any field- and backend- specific modifications
|
|
self._alter_add_column_mods(field, name, params, sqls)
|
|
|
|
# Next, nullity: modified, firebird doesn't support DROP NOT NULL
|
|
sqls_extra.append(self._alter_column_set_null(table_name, name, field.null))
|
|
|
|
# Next, set any default
|
|
self._alter_set_defaults(field, name, params, sqls)
|
|
|
|
# Finally, actually change the column
|
|
if self.allows_combined_alters:
|
|
sqls, values = list(zip(*sqls))
|
|
self.execute(
|
|
"ALTER TABLE %s %s;" % (self.quote_name(table_name), ", ".join(sqls)),
|
|
generic.flatten(values),
|
|
)
|
|
else:
|
|
# Databases like e.g. MySQL don't like more than one alter at once.
|
|
for sql, values in sqls:
|
|
try:
|
|
self.execute("ALTER TABLE %s %s;" % (self.quote_name(table_name), sql), values)
|
|
except DatabaseError as e:
|
|
print(e)
|
|
|
|
|
|
# Execute extra sql, which don't need ALTER TABLE statement
|
|
for sql in sqls_extra:
|
|
self.execute(sql)
|
|
|
|
if not ignore_constraints:
|
|
# Add back FK constraints if needed
|
|
if field.rel and self.supports_foreign_keys:
|
|
self.execute(
|
|
self.foreign_key_sql(
|
|
table_name,
|
|
field.column,
|
|
field.rel.to._meta.db_table,
|
|
field.rel.to._meta.get_field(field.rel.field_name).column
|
|
)
|
|
)
|
|
|
|
@generic.copy_column_constraints
|
|
@generic.delete_column_constraints
|
|
def rename_column(self, table_name, old, new):
|
|
if old == new:
|
|
# Short-circuit out
|
|
return []
|
|
|
|
self.execute('ALTER TABLE %s ALTER %s TO %s;' % (
|
|
self.quote_name(table_name),
|
|
self.quote_name(old),
|
|
self.quote_name(new),
|
|
))
|