Skip to content

Commit 7bd1876

Browse files
committed
Fix HANA get_columns to use sys.table_columns and sys.constraints
SYS.CONSTRAINT_COLUMNS does not exist in current HANA Cloud schemas, and SYS.COLUMNS has been deprecated since HANA 2 SP03. SYS.CONSTRAINTS already exposes COLUMN_NAME and IS_PRIMARY_KEY per column, so the join can be dropped entirely. Fixes #153
1 parent acb3f62 commit 7bd1876

3 files changed

Lines changed: 99 additions & 8 deletions

File tree

sqlit/domains/connections/providers/hana/adapter.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,19 +115,15 @@ def get_columns(
115115
schema = schema or self.default_schema
116116

117117
cursor.execute(
118-
"SELECT cc.column_name "
119-
"FROM sys.constraints c "
120-
"JOIN sys.constraint_columns cc "
121-
" ON c.schema_name = cc.schema_name "
122-
" AND c.constraint_name = cc.constraint_name "
123-
"WHERE c.constraint_type = 'PRIMARY KEY' "
124-
"AND c.schema_name = ? AND c.table_name = ?",
118+
"SELECT column_name FROM sys.constraints "
119+
"WHERE is_primary_key = 'TRUE' "
120+
"AND schema_name = ? AND table_name = ?",
125121
(schema, table),
126122
)
127123
pk_columns = {row[0] for row in cursor.fetchall()}
128124

129125
cursor.execute(
130-
"SELECT column_name, data_type_name FROM sys.columns "
126+
"SELECT column_name, data_type_name FROM sys.table_columns "
131127
"WHERE schema_name = ? AND table_name = ? "
132128
"ORDER BY position",
133129
(schema, table),

tests/connections/providers/hana/__init__.py

Whitespace-only changes.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""Tests for HANA adapter column discovery.
2+
3+
Regression coverage for #153: SYS.CONSTRAINT_COLUMNS and SYS.COLUMNS
4+
are not queryable in current HANA Cloud schemas. get_columns must use
5+
SYS.CONSTRAINTS (which already exposes COLUMN_NAME and IS_PRIMARY_KEY
6+
per column) and SYS.TABLE_COLUMNS.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
from unittest.mock import MagicMock
12+
13+
import pytest
14+
15+
from sqlit.domains.connections.providers.adapters.base import ColumnInfo
16+
from sqlit.domains.connections.providers.hana.adapter import HanaAdapter
17+
18+
19+
@pytest.fixture
20+
def adapter() -> HanaAdapter:
21+
return HanaAdapter()
22+
23+
24+
@pytest.fixture
25+
def mock_conn() -> MagicMock:
26+
conn = MagicMock()
27+
cursor = MagicMock()
28+
cursor.fetchall.return_value = []
29+
conn.cursor.return_value = cursor
30+
return conn
31+
32+
33+
def _executed_sql(mock_conn: MagicMock) -> list[str]:
34+
return [call.args[0].lower() for call in mock_conn.cursor.return_value.execute.call_args_list]
35+
36+
37+
def test_get_columns_does_not_use_deprecated_or_missing_views(
38+
adapter: HanaAdapter, mock_conn: MagicMock
39+
) -> None:
40+
adapter.get_columns(mock_conn, "MY_TABLE", schema="MY_SCHEMA")
41+
42+
sqls = _executed_sql(mock_conn)
43+
joined = " | ".join(sqls)
44+
assert "sys.constraint_columns" not in joined
45+
assert "from sys.columns" not in joined
46+
assert "sys.table_columns" in joined
47+
48+
49+
def test_get_columns_filters_primary_key_on_constraints_view(
50+
adapter: HanaAdapter, mock_conn: MagicMock
51+
) -> None:
52+
adapter.get_columns(mock_conn, "MY_TABLE", schema="MY_SCHEMA")
53+
54+
sqls = _executed_sql(mock_conn)
55+
pk_query = next(sql for sql in sqls if "sys.constraints" in sql)
56+
assert "is_primary_key" in pk_query
57+
assert "column_name" in pk_query
58+
59+
60+
def test_get_columns_passes_schema_and_table_as_parameters(
61+
adapter: HanaAdapter, mock_conn: MagicMock
62+
) -> None:
63+
adapter.get_columns(mock_conn, "MY_TABLE", schema="MY_SCHEMA")
64+
65+
calls = mock_conn.cursor.return_value.execute.call_args_list
66+
assert len(calls) == 2
67+
for call in calls:
68+
assert call.args[1] == ("MY_SCHEMA", "MY_TABLE")
69+
70+
71+
def test_get_columns_combines_pk_and_column_results(
72+
adapter: HanaAdapter, mock_conn: MagicMock
73+
) -> None:
74+
cursor = mock_conn.cursor.return_value
75+
cursor.fetchall.side_effect = [
76+
[("ID",)],
77+
[("ID", "INTEGER"), ("NAME", "NVARCHAR")],
78+
]
79+
80+
result = adapter.get_columns(mock_conn, "MY_TABLE", schema="MY_SCHEMA")
81+
82+
assert result == [
83+
ColumnInfo(name="ID", data_type="INTEGER", is_primary_key=True),
84+
ColumnInfo(name="NAME", data_type="NVARCHAR", is_primary_key=False),
85+
]
86+
87+
88+
def test_get_columns_defaults_schema_when_not_provided(
89+
adapter: HanaAdapter, mock_conn: MagicMock
90+
) -> None:
91+
adapter.get_columns(mock_conn, "MY_TABLE")
92+
93+
calls = mock_conn.cursor.return_value.execute.call_args_list
94+
for call in calls:
95+
assert call.args[1] == (adapter.default_schema, "MY_TABLE")

0 commit comments

Comments
 (0)