diff --git a/ckanext/datastore/db.py b/ckanext/datastore/db.py index c064573e1cf..81f3649916f 100644 --- a/ckanext/datastore/db.py +++ b/ckanext/datastore/db.py @@ -715,6 +715,9 @@ def upsert_data(context, data_dict): toolkit._("The data was invalid (for example: a numeric value " "is out of range or was inserted into a text field)." )) + except sqlalchemy.exc.InternalError as err: + message = err.args[0].split('\n')[0].decode('utf8') + raise ValidationError({u'records': [message.split(u') ', 1)[-1]]}) elif method in [_UPDATE, _UPSERT]: unique_keys = _get_unique_key(context, data_dict) @@ -1453,6 +1456,8 @@ def drop_function(name, if_exists): def _write_engine_execute(sql): connection = get_write_engine().connect() + # No special meaning for '%' in sql parameter: + connection = connection.execution_options(no_parameters=True) trans = connection.begin() try: connection.execute(sql) diff --git a/ckanext/datastore/tests/test_create.py b/ckanext/datastore/tests/test_create.py index 6a60748b575..dd5c7eb450c 100644 --- a/ckanext/datastore/tests/test_create.py +++ b/ckanext/datastore/tests/test_create.py @@ -1000,7 +1000,9 @@ def test_create_trigger_applies_to_records(self): u'datastore_search', fields=[u'spam'], resource_id=res['resource_id'])['records'], - [{u'spam': u'spam spam SPAM spam'}, {u'spam': u'spam spam EGGS spam'}]) + [ + {u'spam': u'spam spam SPAM spam'}, + {u'spam': u'spam spam EGGS spam'}]) def test_upsert_trigger_applies_to_records(self): ds = factories.Dataset() @@ -1029,4 +1031,68 @@ def test_upsert_trigger_applies_to_records(self): u'datastore_search', fields=[u'spam'], resource_id=res['resource_id'])['records'], - [{u'spam': u'spam spam BEANS spam'}, {u'spam': u'spam spam SPAM spam'}]) + [ + {u'spam': u'spam spam BEANS spam'}, + {u'spam': u'spam spam SPAM spam'}]) + + def test_create_trigger_exception(self): + ds = factories.Dataset() + + helpers.call_action( + u'datastore_function_create', + name=u'spamexception_trigger', + rettype=u'trigger', + definition=u''' + BEGIN + IF NEW.spam != 'spam' THEN + RAISE EXCEPTION '"%"? Yeeeeccch!', NEW.spam; + END IF; + RETURN NEW; + END;''') + try: + res = helpers.call_action( + 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'}], + triggers=[{u'function': u'spamexception_trigger'}]) + except ValidationError as ve: + assert_equal( + ve.error_dict, + {u'records':[ + u'"EGGS"? Yeeeeccch!']}) + else: + assert 0, u'no validation error' + + def test_upsert_trigger_exception(self): + ds = factories.Dataset() + + helpers.call_action( + u'datastore_function_create', + name=u'spamonly_trigger', + rettype=u'trigger', + definition=u''' + BEGIN + IF NEW.spam != 'spam' THEN + RAISE EXCEPTION '"%"? Yeeeeccch!', NEW.spam; + END IF; + RETURN NEW; + END;''') + res = helpers.call_action( + u'datastore_create', + resource={u'package_id': ds['id']}, + fields=[{u'id': u'spam', u'type': u'text'}], + triggers=[{u'function': u'spamonly_trigger'}]) + try: + helpers.call_action( + u'datastore_upsert', + method=u'insert', + resource_id=res['resource_id'], + records=[{u'spam': u'spam'}, {u'spam': u'BEANS'}]) + except ValidationError as ve: + assert_equal( + ve.error_dict, + {u'records':[ + u'"BEANS"? Yeeeeccch!']}) + else: + assert 0, u'no validation error'