Skip to content

Commit 7c32e87

Browse files
author
陈云亮
committed
feat(gaussdb): 增加对空值的兼容处理和规范化支持
- 新增 _is_empty_value 函数用于检测值是否为等效空值 - 添加 _normalize_empty_value 函数实现空值规范化,支持转换为空值 None - hstore 类型解析时兼容 GaussDB 保留空字典返回空字典而非 None - TextLoader 和 ByteaLoader 支持 _empty_as_none 标志,兼容空字符串或空 bytes 转 None - 测试代码新增 assert_empty_equivalent 辅助函数,用于判断空值等效性 - utils.py 中添加 is_empty_equivalent 和 normalize_empty 辅助方法,便于兼容测试中空值统一处理
1 parent e3711bf commit 7c32e87

6 files changed

Lines changed: 93 additions & 3 deletions

File tree

gaussdb/gaussdb/_py_transformer.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,33 @@
3838
PY_TEXT = PyFormat.TEXT
3939

4040

41+
def _is_empty_value(val: Any) -> bool:
42+
"""检测值是否为等效空值(GaussDB 兼容)"""
43+
if val is None:
44+
return True
45+
if isinstance(val, (bytes, str)) and len(val) == 0:
46+
return True
47+
if isinstance(val, (dict, list)) and len(val) == 0:
48+
return True
49+
return False
50+
51+
52+
def _normalize_empty_value(val: Any, normalize_to_none: bool = False) -> Any:
53+
"""
54+
将空值规范化处理
55+
56+
Args:
57+
val: 要处理的值
58+
normalize_to_none: 如果为 True,将空字符串/空字典等转为 None
59+
60+
Returns:
61+
规范化后的值
62+
"""
63+
if normalize_to_none and _is_empty_value(val):
64+
return None
65+
return val
66+
67+
4168
class Transformer(AdaptContext):
4269
"""
4370
An object that can adapt efficiently between Python and GaussDB.

gaussdb/gaussdb/types/hstore.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ def load(self, data: Buffer) -> Hstore:
9595
if start < len(s):
9696
raise e.DataError(f"error parsing hstore: unparsed data after char {start}")
9797

98+
# GaussDB 兼容:空字典处理
99+
if not rv: # 如果结果为空字典
100+
return {} # 保持返回空字典,而非 None
98101
return rv
99102

100103

gaussdb/gaussdb/types/string.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,18 @@ class StrDumperUnknown(_StrDumper):
105105

106106

107107
class TextLoader(Loader):
108+
# 是否将空字符串视为 None(GaussDB 兼容模式)
109+
_empty_as_none: bool = False
110+
108111
def __init__(self, oid: int, context: AdaptContext | None = None):
109112
super().__init__(oid, context)
110113
enc = conn_encoding(self.connection)
111114
self._encoding = enc if enc != "ascii" else ""
112115

113-
def load(self, data: Buffer) -> bytes | str:
116+
def load(self, data: Buffer) -> bytes | str | None:
117+
if not data:
118+
# GaussDB 可能返回空 bytes 表示空字符串
119+
return None if self._empty_as_none else ""
114120
if self._encoding:
115121
if isinstance(data, memoryview):
116122
data = bytes(data)
@@ -175,14 +181,18 @@ def dump(self, obj: Buffer) -> Buffer | None:
175181

176182
class ByteaLoader(Loader):
177183
_escaping: EscapingProto
184+
_empty_as_none: bool = False
178185

179186
def __init__(self, oid: int, context: AdaptContext | None = None):
180187
super().__init__(oid, context)
181188
if not hasattr(self.__class__, "_escaping"):
182189
self.__class__._escaping = Escaping()
183190

184-
def load(self, data: Buffer) -> bytes:
185-
return self._escaping.unescape_bytea(data)
191+
def load(self, data: Buffer) -> bytes | None:
192+
result = self._escaping.unescape_bytea(data)
193+
if not result and self._empty_as_none:
194+
return None
195+
return result
186196

187197

188198
class ByteaBinaryLoader(Loader):

tests/types/test_hstore.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@
77
pytestmark = pytest.mark.crdb_skip("hstore")
88

99

10+
def assert_empty_equivalent(result, expected):
11+
"""
12+
判断两个值是否等效为空(GaussDB 兼容)
13+
14+
GaussDB 可能返回 b'' 而 PostgreSQL 返回 None,
15+
在某些场景下应视为等效。
16+
"""
17+
empty_values = (None, b"", "", {}, [])
18+
if result in empty_values and expected in empty_values:
19+
return True
20+
return result == expected
21+
22+
1023
@pytest.mark.parametrize(
1124
"s, d",
1225
[

tests/types/test_string.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,20 @@
99
from ..utils import eur
1010
from ..fix_crdb import crdb_encoding, crdb_scs_off
1111

12+
13+
def assert_empty_equivalent(result, expected):
14+
"""
15+
判断两个值是否等效为空(GaussDB 兼容)
16+
17+
GaussDB 可能返回 b'' 而 PostgreSQL 返回 None,
18+
在某些场景下应视为等效。
19+
"""
20+
empty_values = (None, b"", "", {}, [])
21+
if result in empty_values and expected in empty_values:
22+
return True
23+
return result == expected
24+
25+
1226
#
1327
# tests with text
1428
#

tests/utils.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,26 @@ def set_autocommit(conn, value):
193193
return conn.set_autocommit(value)
194194
else:
195195
raise TypeError(f"not a connection: {conn}")
196+
197+
198+
def is_empty_equivalent(val1, val2) -> bool:
199+
"""
200+
检测两个值是否等效为空
201+
202+
用于 GaussDB 与 PostgreSQL 空值差异的兼容性测试
203+
"""
204+
empty_values = (None, b"", "", {}, [])
205+
if val1 in empty_values and val2 in empty_values:
206+
return True
207+
return val1 == val2
208+
209+
210+
def normalize_empty(val):
211+
"""
212+
将空值统一为 None
213+
214+
用于测试比较时消除 GaussDB/PostgreSQL 空值差异
215+
"""
216+
if val in (b"", "", {}, []):
217+
return None
218+
return val

0 commit comments

Comments
 (0)