1111# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212# See the License for the specific language governing permissions and
1313# limitations under the License.
14+ from __future__ import annotations
1415
1516from typing import Any , Callable , Dict , Type
17+
18+ from google .protobuf .message import Message
19+ from google .protobuf .internal .enum_type_wrapper import EnumTypeWrapper
1620from google .cloud .bigtable .data .execute_query .values import Struct
1721from google .cloud .bigtable .data .execute_query .metadata import SqlType
1822from google .cloud .bigtable_v2 import Value as PBValue
3034 SqlType .Struct : "array_value" ,
3135 SqlType .Array : "array_value" ,
3236 SqlType .Map : "array_value" ,
37+ SqlType .Proto : "bytes_value" ,
38+ SqlType .Enum : "int_value" ,
3339}
3440
3541
36- def _parse_array_type (value : PBValue , metadata_type : SqlType .Array ) -> Any :
42+ def _parse_array_type (
43+ value : PBValue ,
44+ metadata_type : SqlType .Array ,
45+ column_name : str | None ,
46+ column_info : dict [str , Any ] | None = None ,
47+ ) -> Any :
3748 """
3849 used for parsing an array represented as a protobuf to a python list.
3950 """
4051 return list (
4152 map (
4253 lambda val : _parse_pb_value_to_python_value (
43- val , metadata_type .element_type
54+ val , metadata_type .element_type , column_name , column_info
4455 ),
4556 value .array_value .values ,
4657 )
4758 )
4859
4960
50- def _parse_map_type (value : PBValue , metadata_type : SqlType .Map ) -> Any :
61+ def _parse_map_type (
62+ value : PBValue ,
63+ metadata_type : SqlType .Map ,
64+ column_name : str | None ,
65+ column_info : dict [str , Any ] | None = None ,
66+ ) -> Any :
5167 """
5268 used for parsing a map represented as a protobuf to a python dict.
5369
@@ -64,10 +80,16 @@ def _parse_map_type(value: PBValue, metadata_type: SqlType.Map) -> Any:
6480 map (
6581 lambda map_entry : (
6682 _parse_pb_value_to_python_value (
67- map_entry .array_value .values [0 ], metadata_type .key_type
83+ map_entry .array_value .values [0 ],
84+ metadata_type .key_type ,
85+ f"{ column_name } .key" if column_name is not None else None ,
86+ column_info ,
6887 ),
6988 _parse_pb_value_to_python_value (
70- map_entry .array_value .values [1 ], metadata_type .value_type
89+ map_entry .array_value .values [1 ],
90+ metadata_type .value_type ,
91+ f"{ column_name } .value" if column_name is not None else None ,
92+ column_info ,
7193 ),
7294 ),
7395 value .array_value .values ,
@@ -77,7 +99,12 @@ def _parse_map_type(value: PBValue, metadata_type: SqlType.Map) -> Any:
7799 raise ValueError ("Invalid map entry - less or more than two values." )
78100
79101
80- def _parse_struct_type (value : PBValue , metadata_type : SqlType .Struct ) -> Struct :
102+ def _parse_struct_type (
103+ value : PBValue ,
104+ metadata_type : SqlType .Struct ,
105+ column_name : str | None ,
106+ column_info : dict [str , Any ] | None = None ,
107+ ) -> Struct :
81108 """
82109 used for parsing a struct represented as a protobuf to a
83110 google.cloud.bigtable.data.execute_query.Struct
@@ -88,29 +115,96 @@ def _parse_struct_type(value: PBValue, metadata_type: SqlType.Struct) -> Struct:
88115 struct = Struct ()
89116 for value , field in zip (value .array_value .values , metadata_type .fields ):
90117 field_name , field_type = field
91- struct .add_field (field_name , _parse_pb_value_to_python_value (value , field_type ))
118+ nested_column_name : str | None
119+ if column_name is None :
120+ nested_column_name = None
121+ else :
122+ # qualify the column name for nested lookups
123+ nested_column_name = (
124+ f"{ column_name } .{ field_name } " if field_name else column_name
125+ )
126+ struct .add_field (
127+ field_name ,
128+ _parse_pb_value_to_python_value (
129+ value , field_type , nested_column_name , column_info
130+ ),
131+ )
92132
93133 return struct
94134
95135
96136def _parse_timestamp_type (
97- value : PBValue , metadata_type : SqlType .Timestamp
137+ value : PBValue ,
138+ metadata_type : SqlType .Timestamp ,
139+ column_name : str | None ,
140+ column_info : dict [str , Any ] | None = None ,
98141) -> DatetimeWithNanoseconds :
99142 """
100143 used for parsing a timestamp represented as a protobuf to DatetimeWithNanoseconds
101144 """
102145 return DatetimeWithNanoseconds .from_timestamp_pb (value .timestamp_value )
103146
104147
105- _TYPE_PARSERS : Dict [Type [SqlType .Type ], Callable [[PBValue , Any ], Any ]] = {
148+ def _parse_proto_type (
149+ value : PBValue ,
150+ metadata_type : SqlType .Proto ,
151+ column_name : str | None ,
152+ column_info : dict [str , Any ] | None = None ,
153+ ) -> Message | bytes :
154+ """
155+ Parses a serialized protobuf message into a Message object.
156+ """
157+ if (
158+ column_name is not None
159+ and column_info is not None
160+ and column_info .get (column_name ) is not None
161+ ):
162+ default_proto_message = column_info .get (column_name )
163+ if isinstance (default_proto_message , Message ):
164+ proto_message = type (default_proto_message )()
165+ proto_message .ParseFromString (value .bytes_value )
166+ return proto_message
167+ return value .bytes_value
168+
169+
170+ def _parse_enum_type (
171+ value : PBValue ,
172+ metadata_type : SqlType .Enum ,
173+ column_name : str | None ,
174+ column_info : dict [str , Any ] | None = None ,
175+ ) -> int | Any :
176+ """
177+ Parses an integer value into a Protobuf enum.
178+ """
179+ if (
180+ column_name is not None
181+ and column_info is not None
182+ and column_info .get (column_name ) is not None
183+ ):
184+ proto_enum = column_info .get (column_name )
185+ if isinstance (proto_enum , EnumTypeWrapper ):
186+ return proto_enum .Name (value .int_value )
187+ return value .int_value
188+
189+
190+ _TYPE_PARSERS : Dict [
191+ Type [SqlType .Type ], Callable [[PBValue , Any , str | None , dict [str , Any ] | None ], Any ]
192+ ] = {
106193 SqlType .Timestamp : _parse_timestamp_type ,
107194 SqlType .Struct : _parse_struct_type ,
108195 SqlType .Array : _parse_array_type ,
109196 SqlType .Map : _parse_map_type ,
197+ SqlType .Proto : _parse_proto_type ,
198+ SqlType .Enum : _parse_enum_type ,
110199}
111200
112201
113- def _parse_pb_value_to_python_value (value : PBValue , metadata_type : SqlType .Type ) -> Any :
202+ def _parse_pb_value_to_python_value (
203+ value : PBValue ,
204+ metadata_type : SqlType .Type ,
205+ column_name : str | None ,
206+ column_info : dict [str , Any ] | None = None ,
207+ ) -> Any :
114208 """
115209 used for converting the value represented as a protobufs to a python object.
116210 """
@@ -126,7 +220,7 @@ def _parse_pb_value_to_python_value(value: PBValue, metadata_type: SqlType.Type)
126220
127221 if kind in _TYPE_PARSERS :
128222 parser = _TYPE_PARSERS [kind ]
129- return parser (value , metadata_type )
223+ return parser (value , metadata_type , column_name , column_info )
130224 elif kind in _REQUIRED_PROTO_FIELDS :
131225 field_name = _REQUIRED_PROTO_FIELDS [kind ]
132226 return getattr (value , field_name )
0 commit comments