1# firebird 2 3from __future__ import print_function 4 5import datetime 6 7from django.db import connection, models 8from django.core.management.color import no_style 9from django.db.utils import DatabaseError 10 11from south.db import generic 12from south.utils.py3 import string_types 13 14class DatabaseOperations(generic.DatabaseOperations): 15 backend_name = 'firebird' 16 alter_string_set_type = 'ALTER %(column)s TYPE %(type)s' 17 alter_string_set_default = 'ALTER %(column)s SET DEFAULT %(default)s;' 18 alter_string_drop_null = '' 19 add_column_string = 'ALTER TABLE %s ADD %s;' 20 delete_column_string = 'ALTER TABLE %s DROP %s;' 21 rename_table_sql = '' 22 23 # Features 24 allows_combined_alters = False 25 has_booleans = False 26 27 def _fill_constraint_cache(self, db_name, table_name): 28 self._constraint_cache.setdefault(db_name, {}) 29 self._constraint_cache[db_name][table_name] = {} 30 31 rows = self.execute(""" 32 SELECT 33 rc.RDB$CONSTRAINT_NAME, 34 rc.RDB$CONSTRAINT_TYPE, 35 cc.RDB$TRIGGER_NAME 36 FROM rdb$relation_constraints rc 37 JOIN rdb$check_constraints cc 38 ON rc.rdb$constraint_name = cc.rdb$constraint_name 39 WHERE rc.rdb$constraint_type = 'NOT NULL' 40 AND rc.rdb$relation_name = '%s' 41 """ % table_name) 42 43 for constraint, kind, column in rows: 44 self._constraint_cache[db_name][table_name].setdefault(column, set()) 45 self._constraint_cache[db_name][table_name][column].add((kind, constraint)) 46 return 47 48 def _alter_column_set_null(self, table_name, column_name, is_null): 49 sql = """ 50 UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = %(null_flag)s 51 WHERE RDB$FIELD_NAME = '%(column)s' 52 AND RDB$RELATION_NAME = '%(table_name)s' 53 """ 54 null_flag = 'NULL' if is_null else '1' 55 return sql % { 56 'null_flag': null_flag, 57 'column': column_name.upper(), 58 'table_name': table_name.upper() 59 } 60 61 def _column_has_default(self, params): 62 sql = """ 63 SELECT a.RDB$DEFAULT_VALUE 64 FROM RDB$RELATION_FIELDS a 65 WHERE a.RDB$FIELD_NAME = '%(column)s' 66 AND a.RDB$RELATION_NAME = '%(table_name)s' 67 """ 68 value = self.execute(sql % params) 69 return True if value else False 70 71 72 def _alter_set_defaults(self, field, name, params, sqls): 73 "Subcommand of alter_column that sets default values (overrideable)" 74 # Historically, we used to set defaults here. 75 # But since South 0.8, we don't ever set defaults on alter-column -- we only 76 # use database-level defaults as scaffolding when adding columns. 77 # However, we still sometimes need to remove defaults in alter-column. 78 if self._column_has_default(params): 79 sqls.append(('ALTER COLUMN %s DROP DEFAULT' % (self.quote_name(name),), [])) 80 81 82 @generic.invalidate_table_constraints 83 def create_table(self, table_name, fields): 84 columns = [] 85 autoinc_sql = '' 86 87 for field_name, field in fields: 88 # avoid default values in CREATE TABLE statements (#925) 89 field._suppress_default = True 90 91 col = self.column_sql(table_name, field_name, field) 92 if not col: 93 continue 94 95 columns.append(col) 96 if isinstance(field, models.AutoField): 97 field_name = field.db_column or field.column 98 autoinc_sql = connection.ops.autoinc_sql(table_name, field_name) 99 100 self.execute(self.create_table_sql % { 101 "table": self.quote_name(table_name), 102 "columns": ', '.join([col for col in columns if col]), 103 }) 104 105 if autoinc_sql: 106 self.execute(autoinc_sql[0]) 107 self.execute(autoinc_sql[1]) 108 109 def rename_table(self, old_table_name, table_name): 110 """ 111 Renames table is not supported by firebird. 112 This involve recreate all related objects (store procedure, views, triggers, etc) 113 """ 114 pass 115 116 @generic.invalidate_table_constraints 117 def delete_table(self, table_name, cascade=False): 118 """ 119 Deletes the table 'table_name'. 120 Firebird will also delete any triggers associated with the table. 121 """ 122 super(DatabaseOperations, self).delete_table(table_name, cascade=False) 123 124 # Also, drop sequence if exists 125 sql = connection.ops.drop_sequence_sql(table_name) 126 if sql: 127 try: 128 self.execute(sql) 129 except: 130 pass 131 132 def column_sql(self, table_name, field_name, field, tablespace='', with_name=True, field_prepared=False): 133 """ 134 Creates the SQL snippet for a column. Used by add_column and add_table. 135 """ 136 137 # If the field hasn't already been told its attribute name, do so. 138 if not field_prepared: 139 field.set_attributes_from_name(field_name) 140 141 # hook for the field to do any resolution prior to it's attributes being queried 142 if hasattr(field, 'south_init'): 143 field.south_init() 144 145 # Possible hook to fiddle with the fields (e.g. defaults & TEXT on MySQL) 146 field = self._field_sanity(field) 147 148 try: 149 sql = field.db_type(connection=self._get_connection()) 150 except TypeError: 151 sql = field.db_type() 152 153 if sql: 154 # Some callers, like the sqlite stuff, just want the extended type. 155 if with_name: 156 field_output = [self.quote_name(field.column), sql] 157 else: 158 field_output = [sql] 159 160 if field.primary_key: 161 field_output.append('NOT NULL PRIMARY KEY') 162 elif field.unique: 163 # Just use UNIQUE (no indexes any more, we have delete_unique) 164 field_output.append('UNIQUE') 165 166 sql = ' '.join(field_output) 167 sqlparams = () 168 169 # if the field is "NOT NULL" and a default value is provided, create the column with it 170 # this allows the addition of a NOT NULL field to a table with existing rows 171 if not getattr(field, '_suppress_default', False): 172 if field.has_default(): 173 default = field.get_default() 174 # If the default is actually None, don't add a default term 175 if default is not None: 176 # If the default is a callable, then call it! 177 if callable(default): 178 default = default() 179 # Now do some very cheap quoting. TODO: Redesign return values to avoid this. 180 if isinstance(default, string_types): 181 default = "'%s'" % default.replace("'", "''") 182 elif isinstance(default, (datetime.date, datetime.time, datetime.datetime)): 183 default = "'%s'" % default 184 elif isinstance(default, bool): 185 default = int(default) 186 # Escape any % signs in the output (bug #317) 187 if isinstance(default, string_types): 188 default = default.replace("%", "%%") 189 # Add it in 190 sql += " DEFAULT %s" 191 sqlparams = (default) 192 elif (not field.null and field.blank) or (field.get_default() == ''): 193 if field.empty_strings_allowed and self._get_connection().features.interprets_empty_strings_as_nulls: 194 sql += " DEFAULT ''" 195 # Error here would be nice, but doesn't seem to play fair. 196 #else: 197 # raise ValueError("Attempting to add a non null column that isn't character based without an explicit default value.") 198 199 # Firebird need set not null after of default value keyword 200 if not field.primary_key and not field.null: 201 sql += ' NOT NULL' 202 203 if field.rel and self.supports_foreign_keys: 204 self.add_deferred_sql( 205 self.foreign_key_sql( 206 table_name, 207 field.column, 208 field.rel.to._meta.db_table, 209 field.rel.to._meta.get_field(field.rel.field_name).column 210 ) 211 ) 212 213 # Things like the contrib.gis module fields have this in 1.1 and below 214 if hasattr(field, 'post_create_sql'): 215 for stmt in field.post_create_sql(no_style(), table_name): 216 self.add_deferred_sql(stmt) 217 218 # Avoid double index creation (#1317) 219 # Firebird creates an index implicity for each foreign key field 220 # sql_indexes_for_field tries to create an index for that field too 221 if not field.rel: 222 # In 1.2 and above, you have to ask the DatabaseCreation stuff for it. 223 # This also creates normal indexes in 1.1. 224 if hasattr(self._get_connection().creation, "sql_indexes_for_field"): 225 # Make a fake model to pass in, with only db_table 226 model = self.mock_model("FakeModelForGISCreation", table_name) 227 for stmt in self._get_connection().creation.sql_indexes_for_field(model, field, no_style()): 228 self.add_deferred_sql(stmt) 229 230 if sql: 231 return sql % sqlparams 232 else: 233 return None 234 235 236 def _drop_constraints(self, table_name, name, field): 237 if self.has_check_constraints: 238 check_constraints = self._constraints_affecting_columns(table_name, [name], "CHECK") 239 for constraint in check_constraints: 240 self.execute(self.delete_check_sql % { 241 'table': self.quote_name(table_name), 242 'constraint': self.quote_name(constraint), 243 }) 244 245 # Drop or add UNIQUE constraint 246 unique_constraint = list(self._constraints_affecting_columns(table_name, [name], "UNIQUE")) 247 if field.unique and not unique_constraint: 248 self.create_unique(table_name, [name]) 249 elif not field.unique and unique_constraint: 250 self.delete_unique(table_name, [name]) 251 252 # Drop all foreign key constraints 253 try: 254 self.delete_foreign_key(table_name, name) 255 except ValueError: 256 # There weren't any 257 pass 258 259 260 @generic.invalidate_table_constraints 261 def alter_column(self, table_name, name, field, explicit_name=True, ignore_constraints=False): 262 """ 263 Alters the given column name so it will match the given field. 264 Note that conversion between the two by the database must be possible. 265 Will not automatically add _id by default; to have this behavour, pass 266 explicit_name=False. 267 268 @param table_name: The name of the table to add the column to 269 @param name: The name of the column to alter 270 @param field: The new field definition to use 271 """ 272 273 if self.dry_run: 274 if self.debug: 275 print(' - no dry run output for alter_column() due to dynamic DDL, sorry') 276 return 277 278 279 # hook for the field to do any resolution prior to it's attributes being queried 280 if hasattr(field, 'south_init'): 281 field.south_init() 282 283 # Add _id or whatever if we need to 284 field.set_attributes_from_name(name) 285 if not explicit_name: 286 name = field.column 287 else: 288 field.column = name 289 290 if not ignore_constraints: 291 # Drop all check constraints. Note that constraints will be added back 292 # with self.alter_string_set_type and self.alter_string_drop_null. 293 self._drop_constraints(table_name, name, field) 294 295 # First, change the type 296 params = { 297 "column": self.quote_name(name), 298 "type": self._db_type_for_alter_column(field), 299 "table_name": table_name 300 } 301 302 # SQLs is a list of (SQL, values) pairs. 303 sqls = [] 304 sqls_extra = [] 305 306 # Only alter the column if it has a type (Geometry ones sometimes don't) 307 if params["type"] is not None: 308 sqls.append((self.alter_string_set_type % params, [])) 309 310 # Add any field- and backend- specific modifications 311 self._alter_add_column_mods(field, name, params, sqls) 312 313 # Next, nullity: modified, firebird doesn't support DROP NOT NULL 314 sqls_extra.append(self._alter_column_set_null(table_name, name, field.null)) 315 316 # Next, set any default 317 self._alter_set_defaults(field, name, params, sqls) 318 319 # Finally, actually change the column 320 if self.allows_combined_alters: 321 sqls, values = list(zip(*sqls)) 322 self.execute( 323 "ALTER TABLE %s %s;" % (self.quote_name(table_name), ", ".join(sqls)), 324 generic.flatten(values), 325 ) 326 else: 327 # Databases like e.g. MySQL don't like more than one alter at once. 328 for sql, values in sqls: 329 try: 330 self.execute("ALTER TABLE %s %s;" % (self.quote_name(table_name), sql), values) 331 except DatabaseError as e: 332 print(e) 333 334 335 # Execute extra sql, which don't need ALTER TABLE statement 336 for sql in sqls_extra: 337 self.execute(sql) 338 339 if not ignore_constraints: 340 # Add back FK constraints if needed 341 if field.rel and self.supports_foreign_keys: 342 self.execute( 343 self.foreign_key_sql( 344 table_name, 345 field.column, 346 field.rel.to._meta.db_table, 347 field.rel.to._meta.get_field(field.rel.field_name).column 348 ) 349 ) 350 351 @generic.copy_column_constraints 352 @generic.delete_column_constraints 353 def rename_column(self, table_name, old, new): 354 if old == new: 355 # Short-circuit out 356 return [] 357 358 self.execute('ALTER TABLE %s ALTER %s TO %s;' % ( 359 self.quote_name(table_name), 360 self.quote_name(old), 361 self.quote_name(new), 362 )) 363