如何防止 Snowflake Python 连接器在 ARRAY/OBJECT/VARIANT 类型列上进行 json 格式设置?

Posted

技术标签:

【中文标题】如何防止 Snowflake Python 连接器在 ARRAY/OBJECT/VARIANT 类型列上进行 json 格式设置?【英文标题】:How can you prevent the Snowflake Python connector from doing json formatting on ARRAY/OBJECT/VARIANT type columns? 【发布时间】:2020-07-20 16:59:39 【问题描述】:

我正在开发一项服务,该服务从 Snowflake 中提取 VARIANT 数据并公开以供在其他地方使用,并且我想防止 Snowflake 连接器转义和添加字符串格式。我正在尝试的一种方法是使用here 提到的converter_class 选项。

再深入一点,我发现默认的SnowflakeConverter 类根本没有对VARIANT/ARRAY/OBJECT 转换执行任何操作:https://github.com/snowflakedb/snowflake-connector-python/blob/79a106ba70355249ea0eff16977bafe774846d90/converter.py#L315。既然是这样,我扩展了这个类以将其用作 _ARRAY_to_python 转换器(首先使用 ARRAY):

def convert_array_to_python(self, ctx):
    logger.info('trying to convert')
    return lambda x: [s.strip('\"\n\t ') for s in x.strip('[\n\t ]').split(',')]

并使用了conn = connect(user=...., converter_class=CustomConverter()),其中CustomConverter 看起来像这样,并且做了类似于DefaultConverterClass 函数here:

def CustomConverterClass():
    converter = sf_lib.connection.DefaultConverterClass()
    converter._ARRAY_to_python = convert_array_to_python
    return converter

我在建立连接后检查了conn.converter_class()._ARRAY_to_pythongetattr(conn.converter_class(), '_ARRAY_to_python') 的值,它们都与前面定义的convert_array_to_python 函数匹配。然而,当我执行查询并检查 VARIANT 列的光标时:

cursor = conn.cursor(sf_lib.DictCursor)
data = cursor.execute(query)
for row in data:
    ...

这些值仍然被转义。而且,它看起来不像调用了 ARRAY 转换器。我可以控制我正在查询的表的架构,并确认该列的类型为 ARRAY。

Snowflake 连接器似乎使用 JsonResult 进行 json 转换。据我所知,通过源代码挖掘,_ARRAY_to_python 调用的调用堆栈应该是这样的: next(data) -> JsonDictResult.__next__() -> JsonDictResult._row_to_python(row) -> _convert_ARRAY_to_python(col)(来自https://github.com/snowflakedb/snowflake-connector-python/blob/d4f04f20949e2bcb70b0ea0927a8a362ba272389/json_result.py#L178)

为什么这里没有调用转换器,还有什么方法可以防止应用 json 格式?

【问题讨论】:

【参考方案1】:

我想防止雪花连接器转义和添加字符串格式

其实并不是Snowflake 转义和添加字符串格式。 snowflake-connector 接收字符串,然后转换回它们的类型。对于 Variant,它不进行任何转换,所以这就是您使用字符串的原因。

回答Snowflake为什么不调用converter_class方法的问题,是因为Snowflake现在使用Apache Arrow来获取查询结果 https://www.snowflake.com/blog/fetching-query-results-from-snowflake-just-got-a-lot-faster-with-apache-arrow/?lang=fr

那么它改变了什么? _json_result_class 方法不再被调用 https://github.com/snowflakedb/snowflake-connector-python/blob/79a106ba70355249ea0eff16977bafe774846d90/cursor.py#L624

if self._query_result_format == 'arrow':
    self.check_can_use_arrow_resultset()
    self._result = ArrowResult(data, self, use_dict_result=self._use_dict_result)
else:
    self._result = self._json_result_class(data, self)

实际上正是这个方法使用转换器类来序列化/反序列化结果 https://github.com/snowflakedb/snowflake-connector-python/blob/79a106ba70355249ea0eff16977bafe774846d90/json_result.py#L132

row[idx] = col if conv is None or col is None else conv(col)

简而言之:converter_class 不再使用,文档不是最新的

要测试您的雪花连接器版本是否使用 Arrow,您可以这样做:

from snowflake.connector.cursor import CAN_USE_ARROW_RESULT
print(CAN_USE_ARROW_RESULT)

我没有找到合适的方法来处理 Arrow 如何反序列化结果,我想您需要在迭代结果时自己将变量列转换为 dict 或 list 来处理。

快速示例:

import json

import snowflake.connector
con = snowflake.connector.connect(
...
)
cursor = con.cursor()
data = cursor.execute("""...""")

def deserialize_sf_value(value):
    if isinstance(value, str) and value[0].strip() in '[':
        return json.loads(value)
    return value

results = [[deserialize_sf_value(value) for value in row] for row in data]
print(results)

【讨论】:

以上是关于如何防止 Snowflake Python 连接器在 ARRAY/OBJECT/VARIANT 类型列上进行 json 格式设置?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Python(SSO 身份验证)在 Snowflake 中进行查询?

雪花 Python 连接 KeyError: 'snowflake-connector-python

AWS Lambda Snowflake Python 连接器在尝试连接时挂起

Snowflake Python Pandas 连接器 - 使用 fetch_pandas_all 的未知错误

SnowFlake-Kafka 连接器 -> 登陆表 -> 目标表。如何清理登陆表

如何正确设置 PySpark - Snowflake 连接的变量?