From 4d384f308b888f8b3c6fad197afb4b180a03dc61 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 23 Jan 2022 22:57:33 +0530 Subject: [PATCH] fix: Unique and Index constraints --- frappe/database/mariadb/schema.py | 2 +- frappe/database/postgres/database.py | 16 ++++---- frappe/database/postgres/schema.py | 58 +++++++++++++++++----------- frappe/database/schema.py | 9 +++-- 4 files changed, 50 insertions(+), 35 deletions(-) diff --git a/frappe/database/mariadb/schema.py b/frappe/database/mariadb/schema.py index f7abac6547..d5a1096ba4 100644 --- a/frappe/database/mariadb/schema.py +++ b/frappe/database/mariadb/schema.py @@ -62,7 +62,7 @@ class MariaDBTable(DBTable): if not frappe.db.has_index(self.table_name, col.fieldname + '_index'): add_index_query.append("ADD INDEX `{}_index`(`{}`)".format(col.fieldname, col.fieldname)) - for col in self.drop_index: + for col in self.drop_index + self.drop_unique: if col.fieldname != 'name': # primary key current_column = self.current_columns.get(col.fieldname.lower()) unique_constraint_changed = current_column.unique != col.unique diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 33f07990af..3547907e58 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -77,11 +77,11 @@ class PostgresDatabase(Database): """Escape quotes and percent in given string.""" if isinstance(s, bytes): s = s.decode('utf-8') - + # MariaDB's driver treats None as an empty string # So Postgres should do the same - if s is None: + if s is None: s = '' if percent: @@ -308,18 +308,20 @@ class PostgresDatabase(Database): WHEN 'timestamp without time zone' THEN 'timestamp' ELSE a.data_type END AS type, - COUNT(b.indexdef) AS Index, + BOOL_OR(b.index) AS index, SPLIT_PART(COALESCE(a.column_default, NULL), '::', 1) AS default, BOOL_OR(b.unique) AS unique FROM information_schema.columns a LEFT JOIN - (SELECT indexdef, tablename, indexdef LIKE '%UNIQUE INDEX%' AS unique + (SELECT indexdef, tablename, + indexdef LIKE '%UNIQUE INDEX%' AS unique, + indexdef NOT LIKE '%UNIQUE INDEX%' AS index FROM pg_indexes WHERE tablename='{table_name}') b - ON SUBSTRING(b.indexdef, '\(.*\)') LIKE CONCAT('%', a.column_name, '%') + ON SUBSTRING(b.indexdef, '\(.*\)') LIKE CONCAT('%', a.column_name, '%') WHERE a.table_name = '{table_name}' - GROUP BY a.column_name, a.data_type, a.column_default, a.character_maximum_length;''' - .format(table_name=table_name), as_dict=1) + GROUP BY a.column_name, a.data_type, a.column_default, a.character_maximum_length; + '''.format(table_name=table_name), as_dict=1) def get_database_list(self, target): return [d[0] for d in self.sql("SELECT datname FROM pg_database;")] diff --git a/frappe/database/postgres/schema.py b/frappe/database/postgres/schema.py index 58153ca6ce..a7c96a5532 100644 --- a/frappe/database/postgres/schema.py +++ b/frappe/database/postgres/schema.py @@ -52,8 +52,8 @@ class PostgresTable(DBTable): query.append("ALTER COLUMN `{0}` TYPE {1} {2}".format( col.fieldname, get_definition(col.fieldtype, precision=col.precision, length=col.length), - using_clause) - ) + using_clause + )) for col in self.set_default: if col.fieldname=="name": @@ -73,37 +73,49 @@ class PostgresTable(DBTable): query.append("ALTER COLUMN `{}` SET DEFAULT {}".format(col.fieldname, col_default)) - create_index_query = "" + create_contraint_query = "" for col in self.add_index: # if index key not exists - create_index_query += 'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}`(`{field}`);'.format( + create_contraint_query += 'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}`(`{field}`);'.format( index_name=col.fieldname, table_name=self.table_name, field=col.fieldname) - drop_index_query = "" + for col in self.add_unique: + # if index key not exists + create_contraint_query += 'CREATE UNIQUE INDEX IF NOT EXISTS "unique_{index_name}" ON `{table_name}`(`{field}`);'.format( + index_name=col.fieldname, + table_name=self.table_name, + field=col.fieldname + ) + + drop_contraint_query = "" for col in self.drop_index: # primary key if col.fieldname != 'name': # if index key exists - if not frappe.db.has_index(self.table_name, col.fieldname): - drop_index_query += 'DROP INDEX IF EXISTS "{}" ;'.format(col.fieldname) + drop_contraint_query += 'DROP INDEX IF EXISTS "{}" ;'.format(col.fieldname) - if query: - try: + for col in self.drop_unique: + # primary key + if col.fieldname != 'name': + # if index key exists + drop_contraint_query += 'DROP INDEX IF EXISTS "unique_{}" ;'.format(col.fieldname) + try: + if query: final_alter_query = "ALTER TABLE `{}` {}".format(self.table_name, ", ".join(query)) if final_alter_query: frappe.db.sql(final_alter_query) - if create_index_query: frappe.db.sql(create_index_query) - if drop_index_query: frappe.db.sql(drop_index_query) - except Exception as e: - # sanitize - if frappe.db.is_duplicate_fieldname(e): - frappe.throw(str(e)) - elif frappe.db.is_duplicate_entry(e): - fieldname = str(e).split("'")[-2] - frappe.throw(_("""{0} field cannot be set as unique in {1}, - as there are non-unique existing values""".format( - fieldname, self.table_name))) - raise e - else: - raise e + if create_contraint_query: frappe.db.sql(create_contraint_query) + if drop_contraint_query: frappe.db.sql(drop_contraint_query) + except Exception as e: + # sanitize + if frappe.db.is_duplicate_fieldname(e): + frappe.throw(str(e)) + elif frappe.db.is_duplicate_entry(e): + fieldname = str(e).split("'")[-2] + frappe.throw(_("""{0} field cannot be set as unique in {1}, + as there are non-unique existing values""".format( + fieldname, self.table_name))) + raise e + else: + raise e diff --git a/frappe/database/schema.py b/frappe/database/schema.py index 5b4788a9c2..1767f90af0 100644 --- a/frappe/database/schema.py +++ b/frappe/database/schema.py @@ -21,6 +21,7 @@ class DBTable: self.change_name = [] self.add_unique = [] self.add_index = [] + self.drop_unique = [] self.drop_index = [] self.set_default = [] @@ -219,8 +220,10 @@ class DbColumn: self.table.change_type.append(self) # unique - if((self.unique and not current_def['unique']) and column_type not in ('text', 'longtext')): + if ((self.unique and not current_def['unique']) and column_type not in ('text', 'longtext')): self.table.add_unique.append(self) + elif (current_def['unique'] and not self.unique): + self.table.drop_unique.append(self) # default if (self.default_changed(current_def) @@ -230,9 +233,7 @@ class DbColumn: self.table.set_default.append(self) # index should be applied or dropped irrespective of type change - if ((current_def['index'] and not self.set_index) - or (current_def['unique'] and not self.unique)): - # to drop unique you have to drop index + if (current_def['index'] and not self.set_index): self.table.drop_index.append(self) elif (not current_def['index'] and self.set_index) and not (column_type in ('text', 'longtext')):