fromsouth.dbimportgenericclassDatabaseOperations(generic.DatabaseOperations):""" SQLite3 implementation of database operations. """backend_name="sqlite3"# SQLite ignores several constraints. I wish I could.supports_foreign_keys=Falsehas_check_constraints=Falsehas_booleans=Falsedefadd_column(self,table_name,name,field,*args,**kwds):""" Adds a column. """# If it's not nullable, and has no default, raise an error (SQLite is picky)if(notfield.nulland(notfield.has_default()orfield.get_default()isNone)andnotfield.empty_strings_allowed):raiseValueError("You cannot add a null=False column without a default value.")# Initialise the field.field.set_attributes_from_name(name)# We add columns by remaking the table; even though SQLite supports# adding columns, it doesn't support adding PRIMARY KEY or UNIQUE cols.# We define fields with no default; a default will be used, though, to fill up the remade tablefield_default=Noneifnotgetattr(field,'_suppress_default',False):default=field.get_default()ifdefaultisnotNoneanddefault!='':field_default="'%s'"%field.get_db_prep_save(default,connection=self._get_connection())field._suppress_default=Trueself._remake_table(table_name,added={field.column:(self._column_sql_for_create(table_name,name,field,False),field_default)})def_get_full_table_description(self,connection,cursor,table_name):cursor.execute('PRAGMA table_info(%s)'%connection.ops.quote_name(table_name))# cid, name, type, notnull, dflt_value, pkreturn[{'name':field[1],'type':field[2],'null_ok':notfield[3],'dflt_value':field[4],'pk':field[5]# undocumented}forfieldincursor.fetchall()]@generic.invalidate_table_constraintsdef_remake_table(self,table_name,added={},renames={},deleted=[],altered={},primary_key_override=None,uniques_deleted=[]):""" Given a table and three sets of changes (renames, deletes, alters), recreates it with the modified schema. """# Dry runs get skipped completelyifself.dry_run:return# Temporary table's nametemp_name="_south_new_"+table_name# Work out the (possibly new) definitions of each columndefinitions={}cursor=self._get_connection().cursor()# Get the index descriptionsindexes=self._get_connection().introspection.get_indexes(cursor,table_name)multi_indexes=self._get_multi_indexes(table_name)# Work out new column defs.forcolumn_infoinself._get_full_table_description(self._get_connection(),cursor,table_name):name=column_info['name']ifnameindeleted:continue# Get the type, ignoring PRIMARY KEY (we need to be consistent)type=column_info['type'].replace("PRIMARY KEY","")# Add on primary key, not null or unique if needed.if(primary_key_overrideandprimary_key_override==name)or \
(notprimary_key_overrideandnameinindexesandindexes[name]['primary_key']):type+=" PRIMARY KEY"elifnotcolumn_info['null_ok']:type+=" NOT NULL"if(nameinindexesandindexes[name]['unique']andnamenotinuniques_deleted):type+=" UNIQUE"ifcolumn_info['dflt_value']isnotNone:type+=" DEFAULT "+column_info['dflt_value']# Deal with a renameifnameinrenames:name=renames[name]# Add to the defsdefinitions[name]=type# Add on altered columnsforname,typeinaltered.items():if(primary_key_overrideandprimary_key_override==name)or \
(notprimary_key_overrideandnameinindexesandindexes[name]['primary_key']):type+=" PRIMARY KEY"if(nameinindexesandindexes[name]['unique']andnamenotinuniques_deleted):type+=" UNIQUE"definitions[name]=type# Add on the new columnsforname,(type,_)inadded.items():if(primary_key_overrideandprimary_key_override==name):type+=" PRIMARY KEY"definitions[name]=type# Alright, Make the tableself.execute("CREATE TABLE %s (%s)"%(self.quote_name(temp_name),", ".join(["%s%s"%(self.quote_name(cname),ctype)forcname,ctypeindefinitions.items()]),))# Copy over the dataself._copy_data(table_name,temp_name,renames,added)# Delete the old table, move our new one over itself.delete_table(table_name)self.rename_table(temp_name,table_name)# Recreate multi-valued indexes# We can't do that before since it's impossible to rename indexes# and index name scope is globalself._make_multi_indexes(table_name,multi_indexes,renames=renames,deleted=deleted,uniques_deleted=uniques_deleted)def_copy_data(self,src,dst,field_renames={},added={}):"Used to copy data into a new table"# Make a list of all the fields to selectcursor=self._get_connection().cursor()src_fields=[column_info[0]forcolumn_infoinself._get_connection().introspection.get_table_description(cursor,src)]dst_fields=[column_info[0]forcolumn_infoinself._get_connection().introspection.get_table_description(cursor,dst)]src_fields_new=[]dst_fields_new=[]forfieldinsrc_fields:iffieldinfield_renames:dst_fields_new.append(self.quote_name(field_renames[field]))eliffieldindst_fields:dst_fields_new.append(self.quote_name(field))else:continuesrc_fields_new.append(self.quote_name(field))forfield,(_,default)inadded.items():ifdefaultisnotNoneanddefault!='':field=self.quote_name(field)src_fields_new.append("%s as %s"%(default,field))dst_fields_new.append(field)# Copy over the dataself.execute("INSERT INTO %s (%s) SELECT %s FROM %s;"%(self.quote_name(dst),', '.join(dst_fields_new),', '.join(src_fields_new),self.quote_name(src),))def_create_unique(self,table_name,columns):self.execute("CREATE UNIQUE INDEX %s ON %s(%s);"%(self.quote_name('%s_%s'%(table_name,'__'.join(columns))),self.quote_name(table_name),', '.join(self.quote_name(c)forcincolumns),))def_get_multi_indexes(self,table_name):indexes=[]cursor=self._get_connection().cursor()cursor.execute('PRAGMA index_list(%s)'%self.quote_name(table_name))# seq, name, uniqueforindex,uniquein[(field[1],field[2])forfieldincursor.fetchall()]:ifnotunique:continuecursor.execute('PRAGMA index_info(%s)'%self.quote_name(index))info=cursor.fetchall()iflen(info)==1:continuecolumns=[]forfieldininfo:columns.append(field[2])indexes.append(columns)returnindexesdef_make_multi_indexes(self,table_name,indexes,deleted=[],renames={},uniques_deleted=[]):forindexinindexes:columns=[]fornameinindex:# Handle deletionifnameindeleted:columns=[]break# Handle renamesifnameinrenames:name=renames[name]columns.append(name)ifcolumnsandset(columns)!=set(uniques_deleted):self._create_unique(table_name,columns)def_column_sql_for_create(self,table_name,name,field,explicit_name=True):"Given a field and its name, returns the full type for the CREATE TABLE (without unique/pk)"field.set_attributes_from_name(name)ifnotexplicit_name:name=field.db_columnelse:field.column=namesql=self.column_sql(table_name,name,field,with_name=False,field_prepared=True)# Remove keywords we don't want (this should be type only, not constraint)ifsql:sql=sql.replace("PRIMARY KEY","")returnsqldefalter_column(self,table_name,name,field,explicit_name=True,ignore_constraints=False):""" Changes a column's SQL definition. Note that this sqlite3 implementation ignores the ignore_constraints argument. The argument is accepted for API compatibility with the generic DatabaseOperations.alter_column() method. """# Change nulls to default if neededifnotfield.nullandfield.has_default():params={"column":self.quote_name(name),"table_name":self.quote_name(table_name)}self._update_nulls_to_default(params,field)# Remake the table correctlyfield._suppress_default=Trueself._remake_table(table_name,altered={name:self._column_sql_for_create(table_name,name,field,explicit_name),})defdelete_column(self,table_name,column_name):""" Deletes a column. """self._remake_table(table_name,deleted=[column_name])defrename_column(self,table_name,old,new):""" Renames a column from one name to another. """self._remake_table(table_name,renames={old:new})defcreate_unique(self,table_name,columns):""" Create an unique index on columns """self._create_unique(table_name,columns)defdelete_unique(self,table_name,columns):""" Delete an unique index """self._remake_table(table_name,uniques_deleted=columns)defcreate_primary_key(self,table_name,columns):ifnotisinstance(columns,(list,tuple)):columns=[columns]assertlen(columns)==1,"SQLite backend does not support multi-column primary keys"self._remake_table(table_name,primary_key_override=columns[0])# Not implemented this yet.defdelete_primary_key(self,table_name):# By passing True in, we make sure we wipe all existing PKs.self._remake_table(table_name,primary_key_override=True)# No cascades on deletesdefdelete_table(self,table_name,cascade=True):generic.DatabaseOperations.delete_table(self,table_name,False)def_default_value_workaround(self,default):ifdefault==True:default=1elifdefault==False:default=0returndefault