From 171eed2dc0c79fc6a23acb112bd0b5e1814ec270 Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Sun, 11 Oct 2026 11:37:09 -0400 Subject: [PATCH 1/3] [#9587] fix datastore method=insert records_row error value --- ckanext/datastore/backend/postgres.py | 24 +++++++++++++++++++++++- ckanext/datastore/tests/test_create.py | 6 ++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/ckanext/datastore/backend/postgres.py b/ckanext/datastore/backend/postgres.py index b278b9b56c0..35f89619fad 100644 --- a/ckanext/datastore/backend/postgres.py +++ b/ckanext/datastore/backend/postgres.py @@ -1522,8 +1522,30 @@ def upsert_data(context: Context, data_dict: dict[str, Any]): ) try: - context['connection'].execute(sa.text(sql_string), rows) + if len(rows) > 1: + itrans = context['connection'].begin_nested() + try: + context['connection'].execute(sa.text(sql_string), rows) + except: + itrans.rollback() + raise + itrans.commit() + else: + context['connection'].execute(sa.text(sql_string), rows) + except (DatabaseError, DataError) as err: + if len(rows) > 1: + # determine failing row number for records_row + etrans = context['connection'].begin_nested() + try: + for num, row in enumerate(rows): + try: + context['connection'].execute(sa.text(sql_string), [row]) + except (DatabaseError, DataError) as _err: + break + finally: + etrans.rollback() + # (canada fork only): parse constraint sql errors # TODO: upstream contrib!! errmsg = _programming_error_summary(err) diff --git a/ckanext/datastore/tests/test_create.py b/ckanext/datastore/tests/test_create.py index 3cee2edfa13..ea4f1e796ad 100644 --- a/ckanext/datastore/tests/test_create.py +++ b/ckanext/datastore/tests/test_create.py @@ -1393,6 +1393,7 @@ def test_create_trigger_exception(self, app): helpers.call_action( u"datastore_function_create", name=u"spamexception_trigger", + or_replace=True, rettype=u"trigger", definition=u""" BEGIN @@ -1408,7 +1409,7 @@ def test_create_trigger_exception(self, app): u"datastore_create", resource={u"package_id": ds["id"]}, fields=[{u"id": u"spam", u"type": u"text"}], - records=[{u"spam": u"spam"}, {u"spam": u"EGGS"}], + records=[{"spam": "spam"}, {"spam": "EGGS"}, {"spam": "spam"}], triggers=[{u"function": u"spamexception_trigger"}], ) assert error.value.error_dict == {u"records": [u'"EGGS"? Yeeeeccch!']} @@ -1421,6 +1422,7 @@ def test_upsert_trigger_exception(self, app): helpers.call_action( u"datastore_function_create", name=u"spamonly_trigger", + or_replace=True, rettype=u"trigger", definition=u""" BEGIN @@ -1442,7 +1444,7 @@ def test_upsert_trigger_exception(self, app): u"datastore_upsert", method=u"insert", resource_id=res["resource_id"], - records=[{u"spam": u"spam"}, {u"spam": u"BEANS"}], + records=[{"spam": "spam"}, {"spam": "BEANS"}, {"spam": "spam"}], ) assert error.value.error_dict == { u"records": [u'"BEANS"? Yeeeeccch!'] From f8f50a8e2534f2b94fe66d59eec7ccdcf91a54e2 Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Sun, 11 Oct 2026 17:12:22 -0400 Subject: [PATCH 2/3] [#9587] changes --- changes/9587.bugfix | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/9587.bugfix diff --git a/changes/9587.bugfix b/changes/9587.bugfix new file mode 100644 index 00000000000..07e6a3194d9 --- /dev/null +++ b/changes/9587.bugfix @@ -0,0 +1,2 @@ +Fix datastore_upsert method=insert (and datastore_create) records_row error value. +Used to always report last row regardless of which record contained the error. From dd60f3dfe00c89d439f4f7f26d67bc0548e76b3f Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Mon, 12 Oct 2026 09:47:07 -0400 Subject: [PATCH 3/3] canada changes --- changes/238.backport.bugfix | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/238.backport.bugfix diff --git a/changes/238.backport.bugfix b/changes/238.backport.bugfix new file mode 100644 index 00000000000..07e6a3194d9 --- /dev/null +++ b/changes/238.backport.bugfix @@ -0,0 +1,2 @@ +Fix datastore_upsert method=insert (and datastore_create) records_row error value. +Used to always report last row regardless of which record contained the error.