Skip to content

Commit 218a22f

Browse files
authored
fix(db2): Improving support for ibm db2 connections (apache#26744)
1 parent 1bf3ab4 commit 218a22f

File tree

3 files changed

+131
-1
lines changed

3 files changed

+131
-1
lines changed

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def get_git_sha() -> str:
152152
"databricks-sql-connector>=2.0.2, <3",
153153
"sqlalchemy-databricks>=0.2.0",
154154
],
155-
"db2": ["ibm-db-sa>=0.3.5, <0.4"],
155+
"db2": ["ibm-db-sa>0.3.8, <=0.4.0"],
156156
"dremio": ["sqlalchemy-dremio>=1.1.5, <1.3"],
157157
"drill": ["sqlalchemy-drill==0.1.dev"],
158158
"druid": ["pydruid>=0.6.5,<0.7"],

superset/db_engine_specs/db2.py

+55
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,16 @@
1414
# KIND, either express or implied. See the License for the
1515
# specific language governing permissions and limitations
1616
# under the License.
17+
import logging
18+
from typing import Optional, Union
19+
20+
from sqlalchemy.engine.reflection import Inspector
21+
1722
from superset.constants import TimeGrain
1823
from superset.db_engine_specs.base import BaseEngineSpec, LimitMethod
1924

25+
logger = logging.getLogger(__name__)
26+
2027

2128
class Db2EngineSpec(BaseEngineSpec):
2229
engine = "db2"
@@ -26,6 +33,8 @@ class Db2EngineSpec(BaseEngineSpec):
2633
force_column_alias_quotes = True
2734
max_column_name_length = 30
2835

36+
supports_dynamic_schema = True
37+
2938
_time_grain_expressions = {
3039
None: "{col}",
3140
TimeGrain.SECOND: "CAST({col} as TIMESTAMP) - MICROSECOND({col}) MICROSECONDS",
@@ -52,3 +61,49 @@ class Db2EngineSpec(BaseEngineSpec):
5261
@classmethod
5362
def epoch_to_dttm(cls) -> str:
5463
return "(TIMESTAMP('1970-01-01', '00:00:00') + {col} SECONDS)"
64+
65+
@classmethod
66+
def get_table_comment(
67+
cls, inspector: Inspector, table_name: str, schema: Union[str, None]
68+
) -> Optional[str]:
69+
"""
70+
Get comment of table from a given schema
71+
72+
Ibm Db2 return comments as tuples, so we need to get the first element
73+
74+
:param inspector: SqlAlchemy Inspector instance
75+
:param table_name: Table name
76+
:param schema: Schema name. If omitted, uses default schema for database
77+
:return: comment of table
78+
"""
79+
comment = None
80+
try:
81+
table_comment = inspector.get_table_comment(table_name, schema)
82+
comment = table_comment.get("text")
83+
return comment[0]
84+
except IndexError:
85+
return comment
86+
except Exception as ex: # pylint: disable=broad-except
87+
logger.error("Unexpected error while fetching table comment", exc_info=True)
88+
logger.exception(ex)
89+
return comment
90+
91+
@classmethod
92+
def get_prequeries(
93+
cls,
94+
catalog: Union[str, None] = None,
95+
schema: Union[str, None] = None,
96+
) -> list[str]:
97+
"""
98+
Set the search path to the specified schema.
99+
100+
This is important for two reasons: in SQL Lab it will allow queries to run in
101+
the schema selected in the dropdown, resolving unqualified table names to the
102+
expected schema.
103+
104+
But more importantly, in SQL Lab this is used to check if the user has access to
105+
any tables with unqualified names. If the schema is not set by SQL Lab it could
106+
be anything, and we would have to block users from running any queries
107+
referencing tables without an explicit schema.
108+
"""
109+
return [f'set current_schema "{schema}"'] if schema else []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
import pytest
19+
from pytest_mock import MockerFixture
20+
21+
22+
def test_epoch_to_dttm() -> None:
23+
"""
24+
Test the `epoch_to_dttm` method.
25+
"""
26+
from superset.db_engine_specs.db2 import Db2EngineSpec
27+
28+
assert (
29+
Db2EngineSpec.epoch_to_dttm().format(col="epoch_dttm")
30+
== "(TIMESTAMP('1970-01-01', '00:00:00') + epoch_dttm SECONDS)"
31+
)
32+
33+
34+
def test_get_table_comment(mocker: MockerFixture):
35+
"""
36+
Test the `get_table_comment` method.
37+
"""
38+
from superset.db_engine_specs.db2 import Db2EngineSpec
39+
40+
mock_inspector = mocker.MagicMock()
41+
mock_inspector.get_table_comment.return_value = {
42+
"text": ("This is a table comment",)
43+
}
44+
45+
assert (
46+
Db2EngineSpec.get_table_comment(mock_inspector, "my_table", "my_schema")
47+
== "This is a table comment"
48+
)
49+
50+
51+
def test_get_table_comment_empty(mocker: MockerFixture):
52+
"""
53+
Test the `get_table_comment` method
54+
when no comment is returned.
55+
"""
56+
from superset.db_engine_specs.db2 import Db2EngineSpec
57+
58+
mock_inspector = mocker.MagicMock()
59+
mock_inspector.get_table_comment.return_value = {}
60+
61+
assert (
62+
Db2EngineSpec.get_table_comment(mock_inspector, "my_table", "my_schema") == None
63+
)
64+
65+
66+
def test_get_prequeries() -> None:
67+
"""
68+
Test the ``get_prequeries`` method.
69+
"""
70+
from superset.db_engine_specs.db2 import Db2EngineSpec
71+
72+
assert Db2EngineSpec.get_prequeries() == []
73+
assert Db2EngineSpec.get_prequeries(schema="my_schema") == [
74+
'set current_schema "my_schema"'
75+
]

0 commit comments

Comments
 (0)