Skip to content

Commit

Permalink
Update libexasolodbc driver to 7.1.11 (#158)
Browse files Browse the repository at this point in the history
* Update libexasolodbc driver to 7.1.11

* Add new driver library 7.1.11
* Add test to check safe tls default
* Adjust test settings to allow self signed certificates
* Update deadlock test(s)
* Bump version to 3.0.3

Note:
Testing the actual ssl/tls should be done by the driver itself, this is
considered out of scope for this library.
Still a basic test verifying that the default settings are safe (will fail) was added.

Co-authored-by: Umit Cavus Buyuksahin <[email protected]>
  • Loading branch information
Nicoretti and umitbuyuksahin authored Jul 20, 2022
1 parent 944a8c2 commit 88f7c28
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 34 deletions.
3 changes: 2 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 3.0.2 (Not Released)
# 3.0.3 (Not Released)
* Fix CI/CD build and publish target
* Bump pyodbc from 4.0.32 to 4.0.34
* Add new exasol odbc driver 7.1.11

# 3.0.0
⚠️ ATTENTION ⚠️
Expand Down
Binary file removed driver/libexaodbc-uo2214lv1.so
Binary file not shown.
Binary file added driver/libexaodbc-uo2214lv2.so
Binary file not shown.
16 changes: 9 additions & 7 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

class Settings:
ITDE = PROJECT_ROOT / ".." / "integration-test-docker-environment"
ODBC_DRIVER = PROJECT_ROOT / "driver" / "libexaodbc-uo2214lv1.so"
ODBC_DRIVER = PROJECT_ROOT / "driver" / "libexaodbc-uo2214lv2.so"
CONNECTORS = ("pyodbc", "turbodbc")
ENVIRONMENT_NAME = "test"
DB_PORT = 8888
Expand Down Expand Up @@ -150,14 +150,16 @@ def populate():
"server": "localhost:8888",
"user": "sys",
"password": "exasol",
"ssl_certificate": "SSL_VERIFY_NONE"
}
connection = connect(
"".join(
";".join(
[
"DRIVER={driver};",
"EXAHOST={server};",
"UID={user};",
"DRIVER={driver}",
"EXAHOST={server}",
"UID={user}",
"PWD={password}",
"SSLCertificate={ssl_certificate}"
]
).format(**settings)
)
Expand Down Expand Up @@ -191,7 +193,7 @@ def integration(session, connector):
[
"exa+{connector}:",
"//sys:exasol@localhost:{db_port}",
"/TEST?CONNECTIONLCALL=en_US.UTF-8&DRIVER=EXAODBC",
"/TEST?CONNECTIONLCALL=en_US.UTF-8&DRIVER=EXAODBC&SSLCertificate=SSL_VERIFY_NONE",
]
).format(connector=connector, db_port=Settings.DB_PORT)
session.run("pytest", "--dropfirst", "--dburi", uri, external=True, env=env)
Expand All @@ -213,7 +215,7 @@ def report_skipped(session):
[
"exa+{connector}:",
"//sys:exasol@localhost:{db_port}",
"/TEST?CONNECTIONLCALL=en_US.UTF-8&DRIVER=EXAODBC",
"/TEST?CONNECTIONLCALL=en_US.UTF-8&DRIVER=EXAODBC&SSLCertificate=SSL_VERIFY_NONE",
]
).format(connector=connector, db_port=Settings.DB_PORT)
session.run(
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "sqlalchemy_exasol"
version = "3.0.2"
version = "3.0.3"
description = "EXASOL dialect for SQLAlchemy"
readme = "README.rst"
license = "License :: OSI Approved :: BSD License"
Expand Down
2 changes: 1 addition & 1 deletion sqlalchemy_exasol/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

MAJOR = 3
MINOR = 0
PATCH = 2
PATCH = 3

VERSION = f"{MAJOR}.{MINOR}.{PATCH}"
21 changes: 21 additions & 0 deletions test/test_certificate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import copy
import pytest
import sqlalchemy.exc
from sqlalchemy import create_engine
from sqlalchemy.testing.fixtures import config, TestBase


class CertificateTest(TestBase):

def test_db_connection_fails_with_default_settings_for_self_signed_certificates(self):
url = copy.deepcopy(config.db.url)
if 'SSLCertificate' in url.query:
del url.query['SSLCertificate']

engine = create_engine(url)
with pytest.raises(sqlalchemy.exc.DBAPIError) as exec_info:
# we expect the connect call to fail, but want to close it in case it succeeds
with engine.connect() as conn:
pass

assert "self signed certificate" in f'{exec_info.value}'
63 changes: 39 additions & 24 deletions test/test_deadlock.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# -*- coding: UTF-8 -*-

import time
from threading import Thread

Expand Down Expand Up @@ -33,14 +31,30 @@ def without_fallback(session2, schema, table):

self.run_deadlock_for_table(without_fallback)

# NOTE: If a DB >= 7.1.0 still deadlocks here, it may due to the usage of an old ODBC driver version
@pytest.mark.skipif(
testing.db.dialect.server_version_info >= (7, 1, 0),
reason="DB version(s) after 7.1.0 should not deadlock here"
)
def test_deadlock_for_get_table_names_with_fallback(self):
def with_fallback(session2, schema, table):
dialect = Inspector(session2).dialect
dialect.get_table_names(session2, schema=schema, use_sql_fallback=True)

with pytest.raises(Exception):
self.run_deadlock_for_table(with_fallback)


@pytest.mark.skipif(
testing.db.dialect.server_version_info <= (7, 1, 0),
reason="DB version(s) before 7.1.0 are expected to deadlock here"
)
def test_no_deadlock_for_get_table_names_with_fallback(self):
def with_fallback(session2, schema, table):
dialect = Inspector(session2).dialect
dialect.get_table_names(session2, schema=schema, use_sql_fallback=True)

self.run_deadlock_for_table(with_fallback)

def test_no_deadlock_for_get_columns_without_fallback(self):
def without_fallback(session2, schema, table):
dialect = Inspector(session2).dialect
Expand Down Expand Up @@ -114,12 +128,12 @@ def watchdog(self, session0, schema):
print(row)
if row[7] is not None and "Waiting for" in row[7]:
if self.WATCHDOG_ECHO:
print("Killing session: %s" % row[0])
session0.execute("kill session %s" % row[0])
print(f"Killing session: {row[0]}")
session0.execute(f"kill session {row[0]}")
if self.WATCHDOG_ECHO:
print("===========================================")
print()
time.sleep(10) # Only change with care, lower values might make tests unreliable
time.sleep(10) # Only change with care, lower values might make tests unreliable

def run_deadlock_for_table(self, function):
c1 = config.db.connect()
Expand All @@ -128,16 +142,16 @@ def run_deadlock_for_table(self, function):
schema = "deadlock_get_table_names_test_schema"
engine0, session0 = self.create_transaction(url, "transaction0")
try:
session0.execute("DROP SCHEMA %s cascade" % schema)
session0.execute(f"DROP SCHEMA {schema} cascade")
except:
pass
session0.execute("CREATE SCHEMA %s" % schema)
session0.execute("CREATE OR REPLACE TABLE %s.deadlock_test1 (id int PRIMARY KEY)" % schema)
session0.execute(f"CREATE SCHEMA {schema}")
session0.execute(f"CREATE OR REPLACE TABLE {schema}.deadlock_test1 (id int PRIMARY KEY)")
session0.execute(
"CREATE OR REPLACE TABLE %s.deadlock_test2 (id int PRIMARY KEY, fk int REFERENCES %s.deadlock_test1(id))" % (
schema, schema))
session0.execute("INSERT INTO %s.deadlock_test1 VALUES 1" % schema)
session0.execute("INSERT INTO %s.deadlock_test2 VALUES (1,1)" % schema)
f"CREATE OR REPLACE TABLE {schema}.deadlock_test2 (id int PRIMARY KEY, fk int REFERENCES {schema}.deadlock_test1(id))"
)
session0.execute(f"INSERT INTO {schema}.deadlock_test1 VALUES 1")
session0.execute(f"INSERT INTO {schema}.deadlock_test2 VALUES (1,1)")
session0.execute("commit")
self.watchdog_run = True
t1 = Thread(target=self.watchdog, args=(session0, schema))
Expand All @@ -146,12 +160,12 @@ def run_deadlock_for_table(self, function):
engine1, session1 = self.create_transaction(url, "transaction1")
session1.execute("SELECT 1")

session1.execute("SELECT * FROM %s.deadlock_test2" % schema)
session1.execute("INSERT INTO %s.deadlock_test1 VALUES 2" % schema)
session1.execute(f"SELECT * FROM {schema}.deadlock_test2")
session1.execute(f"INSERT INTO {schema}.deadlock_test1 VALUES 2")

engine3, session3 = self.create_transaction(url, "transaction3")
session3.execute("SELECT 1")
session3.execute("DELETE FROM %s.deadlock_test2 WHERE false" % schema)
session3.execute(f"DELETE FROM {schema}.deadlock_test2 WHERE false")
session3.execute("commit")

engine2, session2 = self.create_transaction(url, "transaction2")
Expand All @@ -174,13 +188,14 @@ def run_deadlock_for_get_view_names(self, function):
schema = "deadlock_get_view_names_test_schema"
engine0, session0 = self.create_transaction(url, "transaction0")
try:
session0.execute("DROP SCHEMA %s cascade" % schema)
session0.execute(f"DROP SCHEMA {schema} cascade")
except:
pass
session0.execute("CREATE SCHEMA %s" % schema)
session0.execute("CREATE OR REPLACE TABLE %s.deadlock_test_table (id int)" % schema)
session0.execute(f"CREATE SCHEMA {schema}")
session0.execute(f"CREATE OR REPLACE TABLE {schema}.deadlock_test_table (id int)")
session0.execute(
"CREATE OR REPLACE VIEW %s.deadlock_test_view_1 AS SELECT * FROM %s.deadlock_test_table" % (schema, schema))
f"CREATE OR REPLACE VIEW {schema}.deadlock_test_view_1 AS SELECT * FROM {schema}.deadlock_test_table"
)
session0.execute("commit")
self.watchdog_run = True
t1 = Thread(target=self.watchdog, args=(session0, schema))
Expand All @@ -189,14 +204,14 @@ def run_deadlock_for_get_view_names(self, function):
engine1, session1 = self.create_transaction(url, "transaction1")
session1.execute("SELECT 1")

session1.execute("SELECT * FROM %s.deadlock_test_view_1" % schema)
session1.execute(f"SELECT * FROM {schema}.deadlock_test_view_1")
session1.execute(
"CREATE OR REPLACE VIEW %s.deadlock_test_view_2 AS SELECT * FROM %s.deadlock_test_table" % (
schema, schema))
f"CREATE OR REPLACE VIEW {schema}.deadlock_test_view_2 AS SELECT * FROM {schema}.deadlock_test_table"
)

engine3, session3 = self.create_transaction(url, "transaction3")
session3.execute("SELECT 1")
session3.execute("DROP VIEW %s.deadlock_test_view_1" % schema)
session3.execute(f"DROP VIEW {schema}.deadlock_test_view_1")
session3.execute("commit")

engine2, session2 = self.create_transaction(url, "transaction2")
Expand Down

0 comments on commit 88f7c28

Please sign in to comment.