From d3f768fef9b96c91a7a04e2454ca6d94c1236635 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 8 Feb 2022 10:41:20 +0530 Subject: [PATCH 1/2] feat(db/pg): ability to ignore pk colission --- frappe/model/base_document.py | 32 ++++++++++++++++++++++++-------- frappe/model/document.py | 6 +----- frappe/tests/test_db.py | 10 ++++++++++ 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 307d95e84b..2f0a9d4943 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -375,12 +375,24 @@ class BaseDocument(object): fieldname = [df.fieldname for df in self.meta.get_table_fields() if df.options==doctype] return fieldname[0] if fieldname else None - def db_insert(self): - """INSERT the document (with valid columns) in the database.""" + def db_insert(self, ignore_if_duplicate=False): + """INSERT the document (with valid columns) in the database. + + args: + ignore_if_duplicate: ignore primary key collision + at database level (postgres) + in python (mariadb) + """ if not self.name: # name will be set by document class in most cases set_new_name(self) + conflict_handler = "" + # On postgres we can't implcitly ignore PK collision + # So instruct pg to ignore `name` field conflicts + if ignore_if_duplicate and frappe.db.db_type == "postgres": + conflict_handler = "on conflict (name) do nothing" + if not self.creation: self.creation = self.modified = now() self.created_by = self.modified_by = frappe.session.user @@ -391,10 +403,11 @@ class BaseDocument(object): columns = list(d) try: frappe.db.sql("""INSERT INTO `tab{doctype}` ({columns}) - VALUES ({values})""".format( - doctype = self.doctype, - columns = ", ".join("`"+c+"`" for c in columns), - values = ", ".join(["%s"] * len(columns)) + VALUES ({values}) {conflict_handler}""".format( + doctype=self.doctype, + columns=", ".join("`"+c+"`" for c in columns), + values=", ".join(["%s"] * len(columns)), + conflict_handler=conflict_handler ), list(d.values())) except Exception as e: if frappe.db.is_primary_key_violation(e): @@ -407,8 +420,11 @@ class BaseDocument(object): self.db_insert() return - frappe.msgprint(_("{0} {1} already exists").format(self.doctype, frappe.bold(self.name)), title=_("Duplicate Name"), indicator="red") - raise frappe.DuplicateEntryError(self.doctype, self.name, e) + if not ignore_if_duplicate: + frappe.msgprint(_("{0} {1} already exists") + .format(self.doctype, frappe.bold(self.name)), + title=_("Duplicate Name"), indicator="red") + raise frappe.DuplicateEntryError(self.doctype, self.name, e) elif frappe.db.is_unique_key_violation(e): # unique constraint diff --git a/frappe/model/document.py b/frappe/model/document.py index f7ba9250fa..85603587cd 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -249,11 +249,7 @@ class Document(BaseDocument): if getattr(self.meta, "issingle", 0): self.update_single(self.get_valid_dict()) else: - try: - self.db_insert() - except frappe.DuplicateEntryError as e: - if not ignore_if_duplicate: - raise e + self.db_insert(ignore_if_duplicate=ignore_if_duplicate) # children for d in self.get_all_children(): diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index 6e96849b35..bbd09590be 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -291,6 +291,16 @@ class TestDB(unittest.TestCase): frappe.db.MAX_WRITES_PER_TRANSACTION = Database.MAX_WRITES_PER_TRANSACTION + def test_pk_collision_ignoring(self): + # note has `name` generated from title + for _ in range(3): + frappe.get_doc(doctype="Note", title="duplicate name").insert(ignore_if_duplicate=True) + + with savepoint(): + self.assertRaises(frappe.DuplicateEntryError, frappe.get_doc(doctype="Note", title="duplicate name").insert) + # recover transaction to continue other tests + raise Exception + @run_only_if(db_type_is.MARIADB) class TestDDLCommandsMaria(unittest.TestCase): From 1439e667b2e5f65b974a36dc4eb5debf138deb75 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 8 Feb 2022 11:04:50 +0530 Subject: [PATCH 2/2] chore: add debug status temporarily --- frappe/database/database.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/database/database.py b/frappe/database/database.py index 9fa1ff161c..c833bdeed3 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -177,6 +177,8 @@ class Database(object): raise frappe.QueryTimeoutError(e) elif frappe.conf.db_type == 'postgres': + # TODO: added temporarily + print(e) raise if ignore_ddl and (self.is_missing_column(e) or self.is_missing_table(e) or self.cant_drop_field_or_key(e)):