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
15- from typing import Any , Callable , Dict , Type
16+ from typing import Any , Callable , Dict , Type , Optional
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 ,
46+ column_info : dict [str , Any ],
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 ,
65+ column_info : dict [str , Any ],
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" ,
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" ,
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 ,
106+ column_info : dict [str , Any ],
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,84 @@ 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+ # qualify the column name for nested lookups
119+ nested_column_name = (
120+ f"{ column_name } .{ field_name } " if field_name else column_name
121+ )
122+ struct .add_field (
123+ field_name ,
124+ _parse_pb_value_to_python_value (
125+ value , field_type , nested_column_name , column_info
126+ ),
127+ )
92128
93129 return struct
94130
95131
96132def _parse_timestamp_type (
97- value : PBValue , metadata_type : SqlType .Timestamp
133+ value : PBValue ,
134+ metadata_type : SqlType .Timestamp ,
135+ column_name : str ,
136+ column_info : dict [str , Any ],
98137) -> DatetimeWithNanoseconds :
99138 """
100139 used for parsing a timestamp represented as a protobuf to DatetimeWithNanoseconds
101140 """
102141 return DatetimeWithNanoseconds .from_timestamp_pb (value .timestamp_value )
103142
104143
105- _TYPE_PARSERS : Dict [Type [SqlType .Type ], Callable [[PBValue , Any ], Any ]] = {
144+ def _parse_proto_type (
145+ value : PBValue ,
146+ metadata_type : SqlType .Proto ,
147+ column_name : str ,
148+ column_info : dict [str , Any ],
149+ ) -> Message | bytes :
150+ """
151+ Parses a serialized protobuf message into a Message object.
152+ """
153+ if column_info is not None and column_info .get (column_name ) is not None :
154+ default_proto_message = column_info .get (column_name )
155+ if isinstance (default_proto_message , Message ):
156+ proto_message = type (default_proto_message )()
157+ proto_message .ParseFromString (value .bytes_value )
158+ return proto_message
159+ return value .bytes_value
160+
161+
162+ def _parse_enum_type (
163+ value : PBValue ,
164+ metadata_type : SqlType .Enum ,
165+ column_name : str ,
166+ column_info : dict [str , Any ],
167+ ) -> int | Any :
168+ """
169+ Parses an integer value into a Protobuf enum.
170+ """
171+ if column_info is not None and column_info .get (column_name ) is not None :
172+ proto_enum = column_info .get (column_name )
173+ if isinstance (proto_enum , EnumTypeWrapper ):
174+ return proto_enum .Name (value .int_value )
175+ return value .int_value
176+
177+
178+ _TYPE_PARSERS : Dict [
179+ Type [SqlType .Type ], Callable [[PBValue , Any , str , dict [str , Any ]], Any ]
180+ ] = {
106181 SqlType .Timestamp : _parse_timestamp_type ,
107182 SqlType .Struct : _parse_struct_type ,
108183 SqlType .Array : _parse_array_type ,
109184 SqlType .Map : _parse_map_type ,
185+ SqlType .Proto : _parse_proto_type ,
186+ SqlType .Enum : _parse_enum_type ,
110187}
111188
112189
113- def _parse_pb_value_to_python_value (value : PBValue , metadata_type : SqlType .Type ) -> Any :
190+ def _parse_pb_value_to_python_value (
191+ value : PBValue ,
192+ metadata_type : SqlType .Type ,
193+ column_name : str ,
194+ column_info : Optional [dict [str , Any ]] = None ,
195+ ) -> Any :
114196 """
115197 used for converting the value represented as a protobufs to a python object.
116198 """
@@ -126,7 +208,7 @@ def _parse_pb_value_to_python_value(value: PBValue, metadata_type: SqlType.Type)
126208
127209 if kind in _TYPE_PARSERS :
128210 parser = _TYPE_PARSERS [kind ]
129- return parser (value , metadata_type )
211+ return parser (value , metadata_type , column_name , column_info )
130212 elif kind in _REQUIRED_PROTO_FIELDS :
131213 field_name = _REQUIRED_PROTO_FIELDS [kind ]
132214 return getattr (value , field_name )
0 commit comments