|
16 | 16 |
|
17 | 17 | import json
|
18 | 18 | import typing as t
|
| 19 | +from enum import IntEnum |
19 | 20 |
|
| 21 | +from snowflake.cli.api.cli_global_context import get_cli_context |
| 22 | +from snowflake.cli.api.output.formats import OutputFormat |
20 | 23 | from snowflake.connector import DictCursor
|
21 | 24 | from snowflake.connector.cursor import SnowflakeCursor
|
22 | 25 |
|
23 | 26 |
|
| 27 | +class SnowflakeColumnType(IntEnum): |
| 28 | + """Snowflake column type codes for JSON-capable data types.""" |
| 29 | + |
| 30 | + VARIANT = 5 |
| 31 | + OBJECT = 9 |
| 32 | + ARRAY = 10 |
| 33 | + |
| 34 | + |
24 | 35 | class CommandResult:
|
25 | 36 | @property
|
26 | 37 | def result(self):
|
@@ -69,13 +80,48 @@ def result(self):
|
69 | 80 | class QueryResult(CollectionResult):
|
70 | 81 | def __init__(self, cursor: SnowflakeCursor | DictCursor):
|
71 | 82 | self.column_names = [col.name for col in cursor.description]
|
| 83 | + # Store column type information to identify VARIANT columns (JSON data) |
| 84 | + self.column_types = [col.type_code for col in cursor.description] |
72 | 85 | super().__init__(elements=self._prepare_payload(cursor))
|
73 | 86 | self._query = cursor.query
|
74 | 87 |
|
75 | 88 | def _prepare_payload(self, cursor: SnowflakeCursor | DictCursor):
|
76 | 89 | if isinstance(cursor, DictCursor):
|
77 |
| - return (k for k in cursor) |
78 |
| - return ({k: v for k, v in zip(self.column_names, row)} for row in cursor) |
| 90 | + return (self._process_columns(k) for k in cursor) |
| 91 | + return ( |
| 92 | + self._process_columns({k: v for k, v in zip(self.column_names, row)}) |
| 93 | + for row in cursor |
| 94 | + ) |
| 95 | + |
| 96 | + def _process_columns(self, row_dict): |
| 97 | + if get_cli_context().output_format != OutputFormat.JSON_EXT: |
| 98 | + return row_dict |
| 99 | + |
| 100 | + processed_row = {} |
| 101 | + for i, (column_name, value) in enumerate(row_dict.items()): |
| 102 | + # Check if this column can contain JSON data |
| 103 | + if i < len(self.column_types) and self.column_types[i] in ( |
| 104 | + SnowflakeColumnType.VARIANT, |
| 105 | + SnowflakeColumnType.OBJECT, |
| 106 | + SnowflakeColumnType.ARRAY, |
| 107 | + ): |
| 108 | + # For ARRAY and OBJECT types, the values are always JSON strings that need parsing |
| 109 | + # For VARIANT types, only parse if the value is a string |
| 110 | + if self.column_types[i] in ( |
| 111 | + SnowflakeColumnType.OBJECT, |
| 112 | + SnowflakeColumnType.ARRAY, |
| 113 | + ) or isinstance(value, str): |
| 114 | + try: |
| 115 | + # Try to parse as JSON |
| 116 | + processed_row[column_name] = json.loads(value) |
| 117 | + except (json.JSONDecodeError, TypeError): |
| 118 | + # If parsing fails, keep the original value |
| 119 | + processed_row[column_name] = value |
| 120 | + else: |
| 121 | + processed_row[column_name] = value |
| 122 | + else: |
| 123 | + processed_row[column_name] = value |
| 124 | + return processed_row |
79 | 125 |
|
80 | 126 | @property
|
81 | 127 | def query(self):
|
|
0 commit comments