Skip to content

Commit 2857bd8

Browse files
author
陈云亮
committed
fix(connection): 添加GaussDB兼容的连接参数获取回退方法
- 实现了_get_parameters_fallback方法,支持GaussDB不支持PGconn.info属性时获取连接参数 - 修改get_parameters方法,优先尝试正常路径,失败则调用回退方法 - 调整dsn属性构建函数,增加异常捕获及使用连接参数回退方法生成DSN - 修正DSN字符串构建逻辑,跳过密码字段并对值进行转义处理 - 添加单元测试验证回退方法的参数获取和DSN生成行为 - 测试连接信息基本属性和编码获取的正确性
1 parent 4543ca6 commit 2857bd8

2 files changed

Lines changed: 128 additions & 19 deletions

File tree

gaussdb/gaussdb/_connection_info.py

Lines changed: 84 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
from . import pq
1313
from ._tz import get_tzinfo
14-
from .conninfo import make_conninfo
1514

1615

1716
class ConnectionInfo:
@@ -72,26 +71,74 @@ def get_parameters(self) -> dict[str, str]:
7271
either from the connection string and parameters passed to
7372
`~Connection.connect()` or from environment variables. The password
7473
is never returned (you can read it using the `password` attribute).
74+
75+
Note:
76+
GaussDB does not support PGconn.info attribute, uses fallback method.
7577
"""
7678
pyenc = self.encoding
7779

78-
# Get the known defaults to avoid reporting them
79-
defaults = {
80-
i.keyword: i.compiled
81-
for i in pq.Conninfo.get_defaults()
82-
if i.compiled is not None
83-
}
84-
# Not returned by the libq. Bug? Bet we're using SSH.
85-
defaults.setdefault(b"channel_binding", b"prefer")
86-
defaults[b"passfile"] = str(Path.home() / ".pgpass").encode()
87-
88-
return {
89-
i.keyword.decode(pyenc): i.val.decode(pyenc)
90-
for i in self.pgconn.info
91-
if i.val is not None
92-
and i.keyword != b"password"
93-
and i.val != defaults.get(i.keyword)
94-
}
80+
# Check if info attribute is supported (GaussDB does not support)
81+
try:
82+
info = self.pgconn.info
83+
if info is None:
84+
return self._get_parameters_fallback()
85+
except (AttributeError, NotImplementedError):
86+
return self._get_parameters_fallback()
87+
88+
# PostgreSQL normal path
89+
try:
90+
# Get the known defaults to avoid reporting them
91+
defaults = {
92+
i.keyword: i.compiled
93+
for i in pq.Conninfo.get_defaults()
94+
if i.compiled is not None
95+
}
96+
# Not returned by the libq. Bug? Bet we're using SSH.
97+
defaults.setdefault(b"channel_binding", b"prefer")
98+
defaults[b"passfile"] = str(Path.home() / ".pgpass").encode()
99+
100+
return {
101+
i.keyword.decode(pyenc): i.val.decode(pyenc)
102+
for i in info
103+
if i.val is not None
104+
and i.keyword != b"password"
105+
and i.val != defaults.get(i.keyword)
106+
}
107+
except Exception:
108+
# Use fallback on error
109+
return self._get_parameters_fallback()
110+
111+
def _get_parameters_fallback(self) -> dict[str, str]:
112+
"""Fallback method for getting connection parameters.
113+
114+
When PGconn.info is not available (e.g., GaussDB),
115+
retrieve basic connection information from other sources.
116+
"""
117+
params = {}
118+
119+
# Get available information from pgconn attributes
120+
if self.pgconn.host:
121+
params["host"] = self.pgconn.host.decode(self.encoding, errors="replace")
122+
123+
if self.pgconn.port:
124+
params["port"] = self.pgconn.port.decode(self.encoding, errors="replace")
125+
126+
if self.pgconn.db:
127+
params["dbname"] = self.pgconn.db.decode(self.encoding, errors="replace")
128+
129+
if self.pgconn.user:
130+
params["user"] = self.pgconn.user.decode(self.encoding, errors="replace")
131+
132+
# Get other available parameters
133+
try:
134+
if hasattr(self.pgconn, "options") and self.pgconn.options:
135+
params["options"] = self.pgconn.options.decode(
136+
self.encoding, errors="replace"
137+
)
138+
except Exception:
139+
pass
140+
141+
return params
95142

96143
@property
97144
def dsn(self) -> str:
@@ -103,7 +150,25 @@ def dsn(self) -> str:
103150
password is never returned (you can read it using the `password`
104151
attribute).
105152
"""
106-
return make_conninfo(**self.get_parameters())
153+
try:
154+
params = self.get_parameters()
155+
except Exception:
156+
params = self._get_parameters_fallback()
157+
158+
if not params:
159+
return ""
160+
161+
# Build DSN string
162+
parts = []
163+
for key, value in params.items():
164+
if key == "password":
165+
continue # Do not include password
166+
# Escape values
167+
if " " in value or "=" in value or "'" in value:
168+
value = "'" + value.replace("'", "\\'") + "'"
169+
parts.append(f"{key}={value}")
170+
171+
return " ".join(parts)
107172

108173
@property
109174
def status(self) -> pq.ConnStatus:

tests/test_connection_info.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,3 +262,47 @@ def test_set_encoding_unsupported(conn):
262262

263263
def test_vendor(conn):
264264
assert conn.info.vendor
265+
266+
267+
class TestConnectionInfoFallback:
268+
"""Test GaussDB fallback logic."""
269+
270+
def test_get_parameters_fallback(self, conn):
271+
"""Test fallback method for getting parameters."""
272+
params = conn.info.get_parameters()
273+
274+
# Should have at least some basic information
275+
# even when using fallback method
276+
assert isinstance(params, dict)
277+
278+
# Verify password is not included
279+
assert "password" not in params
280+
281+
def test_dsn_fallback(self, conn):
282+
"""Test fallback method for generating DSN."""
283+
dsn = conn.info.dsn
284+
285+
# DSN should be a string
286+
assert isinstance(dsn, str)
287+
288+
# Should not contain password
289+
assert "password=" not in dsn.lower()
290+
291+
def test_basic_info_available(self, conn):
292+
"""Test basic connection information is available."""
293+
info = conn.info
294+
295+
# These attributes should always be available
296+
assert info.host is not None or info.hostaddr is not None
297+
assert info.port is not None
298+
assert info.dbname is not None
299+
assert info.user is not None
300+
301+
def test_encoding(self, conn):
302+
"""Test encoding retrieval."""
303+
info = conn.info
304+
305+
# Should be able to get encoding
306+
encoding = info.encoding
307+
assert encoding is not None
308+
assert isinstance(encoding, str)

0 commit comments

Comments
 (0)