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()