This is an automated email from the ASF dual-hosted git repository.

isapego pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 6b236cbf72 IGNITE-23218 Better error types (#4483)
6b236cbf72 is described below

commit 6b236cbf72fa59fe359e32c0a6fbda7f0556a6c5
Author: Igor Sapego <[email protected]>
AuthorDate: Tue Oct 1 09:56:29 2024 +0200

    IGNITE-23218 Better error types (#4483)
    
    * IGNITE-23218 Better error types
    
    * IGNITE-22745 Add test
    
    * IGNITE-22745 Add tests
    
    * IGNITE-22745 Fix connection check
    
    * IGNITE-22745 C++ compilation fix
---
 .../cpp/ignite/odbc/diagnostic/diagnostic_record.h |   7 ++
 modules/platforms/python/cpp_module/module.cpp     |   7 +-
 modules/platforms/python/cpp_module/py_cursor.cpp  |  71 ++++++------
 .../platforms/python/cpp_module/type_conversion.h  |   4 +-
 modules/platforms/python/cpp_module/utils.cpp      | 125 ++++++++++++++++++++-
 modules/platforms/python/cpp_module/utils.h        |  72 ++++++++++++
 modules/platforms/python/pyignite3/__init__.py     |   6 +-
 modules/platforms/python/tests/test_connect.py     |   4 +-
 modules/platforms/python/tests/test_errors.py      |  78 +++++++++++++
 modules/platforms/python/tests/util.py             |   2 +-
 10 files changed, 324 insertions(+), 52 deletions(-)

diff --git a/modules/platforms/cpp/ignite/odbc/diagnostic/diagnostic_record.h 
b/modules/platforms/cpp/ignite/odbc/diagnostic/diagnostic_record.h
index 68f755da3d..5a9b78c8fc 100644
--- a/modules/platforms/cpp/ignite/odbc/diagnostic/diagnostic_record.h
+++ b/modules/platforms/cpp/ignite/odbc/diagnostic/diagnostic_record.h
@@ -97,6 +97,13 @@ public:
      */
     [[nodiscard]] const std::string &get_sql_state() const;
 
+    /**
+     * Get SQL state of the record.
+     *
+     * @return Internal constant.
+     */
+    [[nodiscard]] sql_state get_sql_state_internal() const { return 
m_sql_state; }
+
     /**
      * Get row number.
      *
diff --git a/modules/platforms/python/cpp_module/module.cpp 
b/modules/platforms/python/cpp_module/module.cpp
index eb0325967e..f62da75ce9 100644
--- a/modules/platforms/python/cpp_module/module.cpp
+++ b/modules/platforms/python/cpp_module/module.cpp
@@ -15,7 +15,6 @@
  * limitations under the License.
  */
 
-#include "module.h"
 #include "py_connection.h"
 #include "py_cursor.h"
 #include "utils.h"
@@ -89,13 +88,15 @@ static PyObject* pyignite3_connect(PyObject* self, 
PyObject* args, PyObject* kwa
         for (Py_ssize_t idx = 0; idx < size; ++idx) {
             auto item = PyList_GetItem(address, idx);
             if (!PyUnicode_Check(item)) {
-                PyErr_SetString(PyExc_RuntimeError, "Only list of string 
values is allowed in 'address' parameter");
+                PyErr_SetString(py_get_module_interface_error_class(),
+                    "Only list of string values is allowed in 'address' 
parameter");
+
                 return nullptr;
             }
 
             auto str_array = PyUnicode_AsUTF8String(item);
             if (!str_array) {
-                PyErr_SetString(PyExc_RuntimeError, "Can not convert address 
string to UTF-8");
+                PyErr_SetString(py_get_module_interface_error_class(), "Can 
not convert address string to UTF-8");
                 return nullptr;
             }
             // To be called when the scope is left.
diff --git a/modules/platforms/python/cpp_module/py_cursor.cpp 
b/modules/platforms/python/cpp_module/py_cursor.cpp
index 9f0924db05..a5b74e35cc 100644
--- a/modules/platforms/python/cpp_module/py_cursor.cpp
+++ b/modules/platforms/python/cpp_module/py_cursor.cpp
@@ -117,6 +117,19 @@ private:
     SQLULEN m_processed{0};
 };
 
+/**
+ * Check if the cursor is open. Set error if not.
+ * @param self Cursor.
+ * @return @c true if open and @c false otherwise.
+ */
+static bool py_cursor_expect_open(py_cursor* self) {
+    if (!self->m_statement) {
+        PyErr_SetString(py_get_module_interface_error_class(), "Cursor is in 
invalid state (Already closed?)");
+        return false;
+    }
+    return true;
+}
+
 
 int py_cursor_init(py_cursor *self, PyObject *args, PyObject *kwds)
 {
@@ -152,10 +165,8 @@ static PyObject* py_cursor_close(py_cursor* self, 
PyObject*)
 
 static PyObject* py_cursor_execute(py_cursor* self, PyObject* args, PyObject* 
kwargs)
 {
-    if (!self->m_statement) {
-        PyErr_SetString(PyExc_RuntimeError, "Cursor is in invalid state 
(Already closed?)");
+    if (!py_cursor_expect_open(self))
         return nullptr;
-    }
 
     static char *kwlist[] = {
         "query",
@@ -175,14 +186,16 @@ static PyObject* py_cursor_execute(py_cursor* self, 
PyObject* args, PyObject* kw
         if (PySequence_Check(params)) {
             size = PySequence_Size(params);
             if (size < 0) {
-                PyErr_SetString(PyExc_RuntimeError, "Internal error while 
getting size of the parameters sequence");
+                PyErr_SetString(py_get_module_interface_error_class(),
+                    "Internal error while getting size of the parameters 
sequence");
+
                 return nullptr;
             }
         } else {
             auto msg_str = std::string("The object does not provide the 
sequence protocol: ")
                 + py_object_get_typename(params);
 
-            PyErr_SetString(PyExc_RuntimeError, msg_str.c_str());
+            PyErr_SetString(py_get_module_interface_error_class(), 
msg_str.c_str());
             return nullptr;
         }
     }
@@ -198,10 +211,8 @@ static PyObject* py_cursor_execute(py_cursor* self, 
PyObject* args, PyObject* kw
 
 static PyObject* py_cursor_rowcount(py_cursor* self, PyObject*)
 {
-    if (!self->m_statement) {
-        PyErr_SetString(PyExc_RuntimeError, "Cursor is in invalid state 
(Already closed?)");
+    if (!py_cursor_expect_open(self))
         return nullptr;
-    }
 
     auto query = self->m_statement->get_query();
 
@@ -213,20 +224,18 @@ static PyObject* py_cursor_rowcount(py_cursor* self, 
PyObject*)
 
 static PyObject* py_cursor_fetchone(py_cursor* self, PyObject*)
 {
-    if (!self->m_statement) {
-        PyErr_SetString(PyExc_RuntimeError, "Cursor is in invalid state 
(Already closed?)");
+    if (!py_cursor_expect_open(self))
         return nullptr;
-    }
 
     auto query = self->m_statement->get_query();
     if (!query) {
-        PyErr_SetString(PyExc_RuntimeError, "Query was not executed");
+        PyErr_SetString(py_get_module_interface_error_class(), "Query was not 
executed");
         return nullptr;
     }
 
     if (query->get_type() != ignite::query_type::DATA) {
         auto err_msg = "Unexpected query type: " + 
std::to_string(int(query->get_type()));
-        PyErr_SetString(PyExc_RuntimeError, err_msg.c_str());
+        PyErr_SetString(py_get_module_interface_error_class(), 
err_msg.c_str());
         return nullptr;
     }
 
@@ -249,7 +258,7 @@ static PyObject* py_cursor_fetchone(py_cursor* self, 
PyObject*)
     auto row = query0.get_current_row();
     auto res_list = PyTuple_New(row.size());
     if (!res_list) {
-        PyErr_SetString(PyExc_RuntimeError, "Can not allocate a new list for 
the result set");
+        PyErr_SetString(py_get_module_operational_error_class(), "Can not 
allocate a new list for the result set");
         return nullptr;
     }
 
@@ -267,10 +276,8 @@ static PyObject* py_cursor_fetchone(py_cursor* self, 
PyObject*)
 
 static PyObject* py_cursor_column_count(py_cursor* self, PyObject*)
 {
-    if (!self->m_statement) {
-        PyErr_SetString(PyExc_RuntimeError, "Cursor is in invalid state 
(Already closed?)");
+    if (!py_cursor_expect_open(self))
         return nullptr;
-    }
 
     auto query = self->m_statement->get_query();
 
@@ -297,7 +304,7 @@ const ignite::column_meta *get_meta_column(py_cursor* self, 
long idx, PyObject *
     }
 
     if (idx < 0 || idx >= long(meta->size())) {
-        PyErr_SetString(PyExc_RuntimeError, "Column metadata index is out of 
bound");
+        PyErr_SetString(py_get_module_interface_error_class(), "Column 
metadata index is out of bound");
         return nullptr;
     }
 
@@ -306,10 +313,8 @@ const ignite::column_meta *get_meta_column(py_cursor* 
self, long idx, PyObject *
 
 static PyObject* py_cursor_column_name(py_cursor* self, PyObject* args)
 {
-    if (!self->m_statement) {
-        PyErr_SetString(PyExc_RuntimeError, "Cursor is in invalid state 
(Already closed?)");
+    if (!py_cursor_expect_open(self))
         return nullptr;
-    }
 
     long idx{0};
 
@@ -327,10 +332,8 @@ static PyObject* py_cursor_column_name(py_cursor* self, 
PyObject* args)
 
 static PyObject* py_cursor_column_type_code(py_cursor* self, PyObject* args)
 {
-    if (!self->m_statement) {
-        PyErr_SetString(PyExc_RuntimeError, "Cursor is in invalid state 
(Already closed?)");
+    if (!py_cursor_expect_open(self))
         return nullptr;
-    }
 
     long idx{0};
 
@@ -348,10 +351,8 @@ static PyObject* py_cursor_column_type_code(py_cursor* 
self, PyObject* args)
 
 static PyObject* py_cursor_column_display_size(py_cursor* self, PyObject*)
 {
-    if (!self->m_statement) {
-        PyErr_SetString(PyExc_RuntimeError, "Cursor is in invalid state 
(Already closed?)");
+    if (!py_cursor_expect_open(self))
         return nullptr;
-    }
 
     Py_INCREF(Py_None);
     return Py_None;
@@ -359,10 +360,8 @@ static PyObject* py_cursor_column_display_size(py_cursor* 
self, PyObject*)
 
 static PyObject* py_cursor_column_internal_size(py_cursor* self, PyObject*)
 {
-    if (!self->m_statement) {
-        PyErr_SetString(PyExc_RuntimeError, "Cursor is in invalid state 
(Already closed?)");
+    if (!py_cursor_expect_open(self))
         return nullptr;
-    }
 
     Py_INCREF(Py_None);
     return Py_None;
@@ -370,10 +369,8 @@ static PyObject* py_cursor_column_internal_size(py_cursor* 
self, PyObject*)
 
 static PyObject* py_cursor_column_precision(py_cursor* self, PyObject* args)
 {
-    if (!self->m_statement) {
-        PyErr_SetString(PyExc_RuntimeError, "Cursor is in invalid state 
(Already closed?)");
+    if (!py_cursor_expect_open(self))
         return nullptr;
-    }
 
     long idx{0};
 
@@ -391,10 +388,8 @@ static PyObject* py_cursor_column_precision(py_cursor* 
self, PyObject* args)
 
 static PyObject* py_cursor_column_scale(py_cursor* self, PyObject* args)
 {
-    if (!self->m_statement) {
-        PyErr_SetString(PyExc_RuntimeError, "Cursor is in invalid state 
(Already closed?)");
+    if (!py_cursor_expect_open(self))
         return nullptr;
-    }
 
     long idx{0};
 
@@ -412,10 +407,8 @@ static PyObject* py_cursor_column_scale(py_cursor* self, 
PyObject* args)
 
 static PyObject* py_cursor_null_ok(py_cursor* self, PyObject* args)
 {
-    if (!self->m_statement) {
-        PyErr_SetString(PyExc_RuntimeError, "Cursor is in invalid state 
(Already closed?)");
+    if (!py_cursor_expect_open(self))
         return nullptr;
-    }
 
     long idx{0};
 
diff --git a/modules/platforms/python/cpp_module/type_conversion.h 
b/modules/platforms/python/cpp_module/type_conversion.h
index a72f4c11a8..c1bf3d42db 100644
--- a/modules/platforms/python/cpp_module/type_conversion.h
+++ b/modules/platforms/python/cpp_module/type_conversion.h
@@ -136,13 +136,13 @@ static PyObject* primitive_to_pyobject(ignite::primitive 
value) {
 
         case ignite_type::PERIOD:{
             // TODO: IGNITE-23217 DB API Driver 3: Add support for PERIOD data 
type
-            PyErr_SetString(PyExc_RuntimeError, "PERIOD data type is not 
supported");
+            PyErr_SetString(py_get_module_not_supported_error_class(), "PERIOD 
data type is not supported");
             return nullptr;
         }
 
         default: {
             auto err_msg = "The type is not supported: " + 
std::to_string(int(value.get_type()));
-            PyErr_SetString(PyExc_RuntimeError, err_msg.c_str());
+            PyErr_SetString(py_get_module_not_supported_error_class(), 
err_msg.c_str());
             return nullptr;
         }
     }
diff --git a/modules/platforms/python/cpp_module/utils.cpp 
b/modules/platforms/python/cpp_module/utils.cpp
index 2a71c78680..75c86fc961 100644
--- a/modules/platforms/python/cpp_module/utils.cpp
+++ b/modules/platforms/python/cpp_module/utils.cpp
@@ -28,11 +28,14 @@
         instance = py_get_module_class(class_name); \
     return instance
 
+
 bool check_errors(ignite::diagnosable& diag) {
     auto &records = diag.get_diagnostic_records();
     if (records.is_successful())
         return true;
 
+    auto error_class = py_get_module_interface_error_class();
+
     std::string err_msg;
     switch (records.get_return_code()) {
         case SQL_INVALID_HANDLE:
@@ -46,11 +49,93 @@ bool check_errors(ignite::diagnosable& diag) {
         case SQL_ERROR:
             auto record = records.get_status_record(1);
             err_msg = record.get_message_text();
+
+            using ignite::sql_state;
+
+            switch (record.get_sql_state_internal()) {
+                case sql_state::SHY000_GENERAL_ERROR: {
+                    error_class = py_get_module_database_error_class();
+                    break;
+                }
+
+                case sql_state::S01S02_OPTION_VALUE_CHANGED:
+                case sql_state::S01004_DATA_TRUNCATED: {
+                    error_class = py_get_module_warning_class();
+                    break;
+                }
+
+                case sql_state::SHY003_INVALID_APPLICATION_BUFFER_TYPE:
+                case sql_state::SHY009_INVALID_USE_OF_NULL_POINTER:
+                case sql_state::SHY010_SEQUENCE_ERROR:
+                case sql_state::SHY092_OPTION_TYPE_OUT_OF_RANGE:
+                case sql_state::SHY097_COLUMN_TYPE_OUT_OF_RANGE:
+                case sql_state::SHY105_INVALID_PARAMETER_TYPE:
+                case sql_state::SHY106_FETCH_TYPE_OUT_OF_RANGE:
+                case sql_state::S07009_INVALID_DESCRIPTOR_INDEX:
+                case sql_state::S40001_SERIALIZATION_FAILURE:
+                case sql_state::SHY090_INVALID_STRING_OR_BUFFER_LENGTH:
+                case sql_state::S22026_DATA_LENGTH_MISMATCH:
+                case sql_state::S22002_INDICATOR_NEEDED:
+                case sql_state::S01S00_INVALID_CONNECTION_STRING_ATTRIBUTE: {
+                    error_class = py_get_module_interface_error_class();
+                    break;
+                }
+
+                case sql_state::S01S01_ERROR_IN_ROW:
+                case sql_state::S01S07_FRACTIONAL_TRUNCATION: {
+                    error_class = py_get_module_data_error_class();
+                    break;
+                }
+
+                case sql_state::S07006_RESTRICTION_VIOLATION:
+                case sql_state::S23000_INTEGRITY_CONSTRAINT_VIOLATION: {
+                    error_class = py_get_module_integrity_error_class();
+                    break;
+                }
+
+                case sql_state::S24000_INVALID_CURSOR_STATE:
+                case sql_state::S25000_INVALID_TRANSACTION_STATE: {
+                    error_class = py_get_module_internal_error_class();
+                    break;
+                }
+
+                case sql_state::S08001_CANNOT_CONNECT:
+                case sql_state::S08002_ALREADY_CONNECTED:
+                case sql_state::S08003_NOT_CONNECTED:
+                case sql_state::S08004_CONNECTION_REJECTED:
+                case sql_state::S08S01_LINK_FAILURE:
+                case sql_state::SHYT00_TIMEOUT_EXPIRED:
+                case sql_state::SHYT01_CONNECTION_TIMEOUT: {
+                    error_class = py_get_module_operational_error_class();
+                    break;
+                }
+
+                case sql_state::S42000_SYNTAX_ERROR_OR_ACCESS_VIOLATION:
+                case sql_state::S42S01_TABLE_OR_VIEW_ALREADY_EXISTS:
+                case sql_state::S42S02_TABLE_OR_VIEW_NOT_FOUND:
+                case sql_state::S42S11_INDEX_ALREADY_EXISTS:
+                case sql_state::S42S12_INDEX_NOT_FOUND:
+                case sql_state::S42S21_COLUMN_ALREADY_EXISTS:
+                case sql_state::S42S22_COLUMN_NOT_FOUND:
+                case sql_state::SHY001_MEMORY_ALLOCATION:
+                case sql_state::S3F000_INVALID_SCHEMA_NAME: {
+                    error_class = py_get_module_programming_error_class();
+                    break;
+                }
+
+                case sql_state::SHYC00_OPTIONAL_FEATURE_NOT_IMPLEMENTED:
+                case sql_state::SIM001_FUNCTION_NOT_SUPPORTED:{
+                    error_class = py_get_module_not_supported_error_class();
+                    break;
+                }
+
+                default:
+                    break;
+            }
             break;
     }
 
-    // TODO: IGNITE-23218 Set a proper error here, not a standard one.
-    PyErr_SetString(PyExc_RuntimeError, err_msg.c_str());
+    PyErr_SetString(error_class, err_msg.c_str());
 
     return false;
 }
@@ -158,6 +243,42 @@ PyObject* py_get_module_duration_class() {
     LAZY_INIT_MODULE_CLASS("DURATION");
 }
 
+PyObject* py_get_module_warning_class() {
+    LAZY_INIT_MODULE_CLASS("Warning");
+}
+
+PyObject* py_get_module_interface_error_class() {
+    LAZY_INIT_MODULE_CLASS("InterfaceError");
+}
+
+PyObject* py_get_module_database_error_class() {
+    LAZY_INIT_MODULE_CLASS("DatabaseError");
+}
+
+PyObject* py_get_module_data_error_class() {
+    LAZY_INIT_MODULE_CLASS("DataError");
+}
+
+PyObject* py_get_module_operational_error_class() {
+    LAZY_INIT_MODULE_CLASS("OperationalError");
+}
+
+PyObject* py_get_module_integrity_error_class() {
+    LAZY_INIT_MODULE_CLASS("IntegrityError");
+}
+
+PyObject* py_get_module_internal_error_class() {
+    LAZY_INIT_MODULE_CLASS("InternalError");
+}
+
+PyObject* py_get_module_programming_error_class() {
+    LAZY_INIT_MODULE_CLASS("ProgrammingError");
+}
+
+PyObject* py_get_module_not_supported_error_class() {
+    LAZY_INIT_MODULE_CLASS("NotSupportedError");
+}
+
 PyObject* py_create_uuid(ignite::bytes_view bytes) {
     auto uuid_class = py_get_module_uuid_class();
     if (!uuid_class)
diff --git a/modules/platforms/python/cpp_module/utils.h 
b/modules/platforms/python/cpp_module/utils.h
index bfa3f608cc..4884dbb350 100644
--- a/modules/platforms/python/cpp_module/utils.h
+++ b/modules/platforms/python/cpp_module/utils.h
@@ -163,6 +163,78 @@ PyObject* py_get_module_number_class();
  */
 PyObject* py_get_module_duration_class();
 
+/**
+ * Get module's Warning class instance.
+ * Faster then loading the class the ordinary way as the class is cached.
+ *
+ * @return Warning class.
+ */
+PyObject* py_get_module_warning_class();
+
+/**
+ * Get module's InterfaceError class instance.
+ * Faster then loading the class the ordinary way as the class is cached.
+ *
+ * @return InterfaceError class.
+ */
+PyObject* py_get_module_interface_error_class();
+
+/**
+ * Get module's DatabaseError class instance.
+ * Faster then loading the class the ordinary way as the class is cached.
+ *
+ * @return DatabaseError class.
+ */
+PyObject* py_get_module_database_error_class();
+
+/**
+ * Get module's DataError class instance.
+ * Faster then loading the class the ordinary way as the class is cached.
+ *
+ * @return DataError class.
+ */
+PyObject* py_get_module_data_error_class();
+
+/**
+ * Get module's OperationalError class instance.
+ * Faster then loading the class the ordinary way as the class is cached.
+ *
+ * @return OperationalError class.
+ */
+PyObject* py_get_module_operational_error_class();
+
+/**
+ * Get module's IntegrityError class instance.
+ * Faster then loading the class the ordinary way as the class is cached.
+ *
+ * @return IntegrityError class.
+ */
+PyObject* py_get_module_integrity_error_class();
+
+/**
+ * Get module's InternalError class instance.
+ * Faster then loading the class the ordinary way as the class is cached.
+ *
+ * @return InternalError class.
+ */
+PyObject* py_get_module_internal_error_class();
+
+/**
+ * Get module's ProgrammingError class instance.
+ * Faster then loading the class the ordinary way as the class is cached.
+ *
+ * @return ProgrammingError class.
+ */
+PyObject* py_get_module_programming_error_class();
+
+/**
+ * Get module's NotSupportedError class instance.
+ * Faster then loading the class the ordinary way as the class is cached.
+ *
+ * @return NotSupportedError class.
+ */
+PyObject* py_get_module_not_supported_error_class();
+
 /**
  * Create a new instance of pyignite3.UUID from an array of bytes.
  *
diff --git a/modules/platforms/python/pyignite3/__init__.py 
b/modules/platforms/python/pyignite3/__init__.py
index d895e37a80..5c2a172c6e 100644
--- a/modules/platforms/python/pyignite3/__init__.py
+++ b/modules/platforms/python/pyignite3/__init__.py
@@ -208,7 +208,7 @@ class Cursor:
             return -1
         return self._py_cursor.rowcount()
 
-    def callproc(self, *args):
+    def callproc(self, *_args):
         if self._py_cursor is None:
             raise InterfaceError('Connection is already closed')
 
@@ -256,7 +256,7 @@ class Cursor:
                 null_ok=self._py_cursor.column_null_ok(column_id)
             ))
 
-    def executemany(self, *args):
+    def executemany(self, *_args):
         if self._py_cursor is None:
             raise InterfaceError('Connection is already closed')
 
@@ -329,7 +329,7 @@ class Cursor:
         # TODO: IGNITE-22743 Implement execution of SQL scripts
         raise NotSupportedError('Operation is not supported')
 
-    def setinputsizes(self, *args):
+    def setinputsizes(self, *_args):
         if self._py_cursor is None:
             raise InterfaceError('Connection is already closed')
 
diff --git a/modules/platforms/python/tests/test_connect.py 
b/modules/platforms/python/tests/test_connect.py
index 81cd223ac0..34ccc74bc8 100644
--- a/modules/platforms/python/tests/test_connect.py
+++ b/modules/platforms/python/tests/test_connect.py
@@ -25,6 +25,6 @@ def test_connection_success():
 
 
 def test_connection_fail():
-    with pytest.raises(RuntimeError) as err:
-        pyignite3.connect(address=server_addresses_invalid)
+    with pytest.raises(pyignite3.OperationalError) as err:
+        pyignite3.connect(address=server_addresses_invalid, timeout=1)
     assert err.match("Failed to establish connection with the host.")
diff --git a/modules/platforms/python/tests/test_errors.py 
b/modules/platforms/python/tests/test_errors.py
new file mode 100644
index 0000000000..8fa78b5402
--- /dev/null
+++ b/modules/platforms/python/tests/test_errors.py
@@ -0,0 +1,78 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import pytest
+
+import pyignite3
+
+
+def test_non_query(table_name, cursor):
+    with pytest.raises(pyignite3.ProgrammingError):
+        cursor.execute("Non query")
+
+
+def test_unknown_table(table_name, cursor):
+    with pytest.raises(pyignite3.ProgrammingError):
+        cursor.execute(f"select * from {table_name}")
+
+
+def test_unknown_column(table_name, cursor, drop_table_cleanup):
+    cursor.execute(f'create table {table_name}(id int primary key, data 
varchar)')
+    with pytest.raises(pyignite3.ProgrammingError):
+        cursor.execute(f"select unknown_col from {table_name}")
+
+
+def test_unknown_schema(table_name, cursor):
+    with pytest.raises(pyignite3.ProgrammingError):
+        cursor.execute(f'create table UNKNOWN_SCHEMA.{table_name}(id int 
primary key, data varchar)')
+
+
+def test_table_exists(table_name, cursor, drop_table_cleanup):
+    cursor.execute(f'create table {table_name}(id int primary key, data 
varchar)')
+    with pytest.raises(pyignite3.ProgrammingError):
+        cursor.execute(f'create table {table_name}(id int primary key, data 
varchar)')
+
+
+def test_column_exists(table_name, cursor, drop_table_cleanup):
+    cursor.execute(f'create table {table_name}(id int primary key, data 
varchar)')
+    with pytest.raises(pyignite3.ProgrammingError):
+        cursor.execute(f'alter table {table_name} add data varchar')
+
+
+def test_cursor_state_fetch(table_name, cursor):
+    with pytest.raises(pyignite3.InterfaceError):
+        cursor.fetchone()
+
+
+def test_cursor_state_proc(table_name, cursor):
+    with pytest.raises(pyignite3.NotSupportedError):
+        cursor.callproc()
+
+
+def test_arithmetic_div_by_zero(table_name, cursor):
+    with pytest.raises(pyignite3.DatabaseError):
+        cursor.execute('select 1 / 0')
+
+
+def test_column_constraints_size(table_name, cursor, drop_table_cleanup):
+    cursor.execute(f'create table {table_name}(id int primary key, data 
varchar(5))')
+    with pytest.raises(pyignite3.ProgrammingError):
+        cursor.execute(f"insert into {table_name} values (1, '1234567890')")
+
+
+def test_column_constraints_nulls(table_name, cursor, drop_table_cleanup):
+    cursor.execute(f'create table {table_name}(id int primary key, data 
varchar not null)')
+    with pytest.raises(pyignite3.IntegrityError):
+        cursor.execute(f"insert into {table_name} values (1, NULL)")
+
diff --git a/modules/platforms/python/tests/util.py 
b/modules/platforms/python/tests/util.py
index ccd856d87d..dec311b8fe 100644
--- a/modules/platforms/python/tests/util.py
+++ b/modules/platforms/python/tests/util.py
@@ -93,7 +93,7 @@ def kill_process_tree(pid):
 def check_server_started(addr: str) -> bool:
     try:
         conn = pyignite3.connect(address=[addr], timeout=1)
-    except RuntimeError as e:
+    except pyignite3.Error:
         return False
 
     conn.close()

Reply via email to