diff --git a/requirements-dev.txt b/requirements-dev.txt index 1944fb8b..980ff243 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,4 +2,5 @@ pytest pytest-timeout python-dateutil tox +parameterized #kerberos diff --git a/vertica_python/tests/integration_tests/base.py b/vertica_python/tests/integration_tests/base.py index 92a7f77b..5ed9d2ae 100644 --- a/vertica_python/tests/integration_tests/base.py +++ b/vertica_python/tests/integration_tests/base.py @@ -93,6 +93,17 @@ def _connect(cls): """ return connect(**cls._conn_info) + @classmethod + def _connect_user_pass(cls, user: str, password: str): + """Connects to vertica with an overridden user/pass. + + :return: a connection to vertica. + """ + conn_info = cls._conn_info.copy() + conn_info['user'] = user + conn_info['password'] = password + return connect(**conn_info) + @classmethod def _get_node_num(cls): """Executes a query to get the number of nodes in the database diff --git a/vertica_python/tests/integration_tests/test_cursor.py b/vertica_python/tests/integration_tests/test_cursor.py index cb2b7242..2d93820e 100644 --- a/vertica_python/tests/integration_tests/test_cursor.py +++ b/vertica_python/tests/integration_tests/test_cursor.py @@ -1193,6 +1193,8 @@ def setUpClass(cls): def setUp(self): super(PreparedStatementTestCase, self).setUp() self._table = 'preparedstmt_test' + self._user = 'test_user' + self._password = 'TestPassword123' with self._connect() as conn: cur = conn.cursor() cur.execute("DROP TABLE IF EXISTS {0}".format(self._table)) @@ -1472,3 +1474,27 @@ def test_bind_udtype(self): self.assertEqual(cur.description[0].display_size, 10000) self.assertEqual(cur.description[1].display_size, 1000) + + def test_multiple_permission_error(self): + with self._connect() as conn: + try: + cur = conn.cursor() + cur.execute("CREATE TABLE {} (id INT)" + .format(self._table)) + + try: + cur.execute("CREATE USER {} IDENTIFIED BY '{}'".format(self._user, self._password), use_prepared_statements=False) + except errors.QueryError: + pass # User already exists + conn.commit() + + with self._connect_user_pass(self._user, self._password) as conn_2: + cur_2 = conn_2.cursor() + for i in range(1, 5): + with pytest.raises(errors.DatabaseError, match='Permission denied for relation {}'.format(self._table)): + cur_2.execute("SELECT * FROM {} LIMIT 1".format(self._table)) + finally: + try: + cur.execute("DROP USER {}".format(self._user)) + except errors.QueryError: + pass # User did not exist diff --git a/vertica_python/vertica/cursor.py b/vertica_python/vertica/cursor.py index 46e9867f..d5fcf91a 100644 --- a/vertica_python/vertica/cursor.py +++ b/vertica_python/vertica/cursor.py @@ -945,6 +945,10 @@ def _error_handler(self, msg: BackendMessage) -> NoReturn: self.connection.write(messages.Sync()) raise errors.QueryError.from_error_response(msg, self.operation) + def _prepared_statement_error_handler(self, msg: BackendMessage) -> NoReturn: + self._message = msg + raise errors.DatabaseError(msg.error_message()) + def _prepare(self, query: str) -> None: """ Send the query to be prepared to the server. The server will parse the @@ -1024,7 +1028,7 @@ def _execute_prepared_statement(self, list_of_parameter_values: Sequence[Any]) - self.connection.write(messages.Flush()) # Read expected message: BindComplete - self.connection.read_expected_message(messages.BindComplete) + self.connection.read_expected_message(messages.BindComplete, self._prepared_statement_error_handler) self._message = self.connection.read_message() if isinstance(self._message, messages.ErrorResponse):