@@ -49,11 +49,11 @@ def parse_datetime(datetime_string: str) -> datetime:
4949from firebolt .utils .util import cached_property
5050
5151_NoneType = type (None )
52- _col_types = (int , float , str , datetime , date , bool , list , Decimal , _NoneType )
52+ _col_types = (int , float , str , datetime , date , bool , list , Decimal , _NoneType , bytes )
5353# duplicating this since 3.7 can't unpack Union
54- ColType = Union [int , float , str , datetime , date , bool , list , Decimal , _NoneType ]
54+ ColType = Union [int , float , str , datetime , date , bool , list , Decimal , _NoneType , bytes ]
5555RawColType = Union [int , float , str , bool , list , _NoneType ]
56- ParameterType = Union [int , float , str , datetime , date , bool , Decimal , Sequence ]
56+ ParameterType = Union [int , float , str , datetime , date , bool , Decimal , Sequence , bytes ]
5757
5858# These definitions are required by PEP-249
5959Date = date
@@ -78,12 +78,13 @@ def TimeFromTicks(t: int) -> None:
7878TimestampFromTicks = datetime .fromtimestamp
7979
8080
81- def Binary (value : str ) -> str :
82- """Convert string to binary for Firebolt DB does nothing ."""
83- return value
81+ def Binary (value : str ) -> bytes :
82+ """Encode a string into UTF-8 ."""
83+ return value . encode ( "utf-8" )
8484
8585
86- STRING = BINARY = str
86+ STRING = str
87+ BINARY = bytes
8788NUMBER = int
8889DATETIME = datetime
8990ROWID = int
@@ -169,6 +170,8 @@ class _InternalType(Enum):
169170
170171 Boolean = "boolean"
171172
173+ Bytea = "bytea"
174+
172175 Nothing = "Nothing"
173176
174177 @cached_property
@@ -188,6 +191,7 @@ def python_type(self) -> type:
188191 _InternalType .TimestampNtz : datetime ,
189192 _InternalType .TimestampTz : datetime ,
190193 _InternalType .Boolean : bool ,
194+ _InternalType .Bytea : bytes ,
191195 # For simplicity, this could happen only during 'select null' query
192196 _InternalType .Nothing : str ,
193197 }
@@ -221,6 +225,18 @@ def parse_type(raw_type: str) -> Union[type, ARRAY, DECIMAL]: # noqa: C901
221225 return str
222226
223227
228+ BYTEA_PREFIX = "\\ x"
229+
230+
231+ def _parse_bytea (str_value : str ) -> bytes :
232+ if (
233+ len (str_value ) < len (BYTEA_PREFIX )
234+ or str_value [: len (BYTEA_PREFIX )] != BYTEA_PREFIX
235+ ):
236+ raise ValueError (f"Invalid bytea value format: { BYTEA_PREFIX } prefix expected" )
237+ return bytes .fromhex (str_value [len (BYTEA_PREFIX ) :])
238+
239+
224240def parse_value (
225241 value : RawColType ,
226242 ctype : Union [type , ARRAY , DECIMAL ],
@@ -244,6 +260,10 @@ def parse_value(
244260 if not isinstance (value , (bool , int )):
245261 raise DataError (f"Invalid boolean value { value } : bool or int expected" )
246262 return bool (value )
263+ if ctype is bytes :
264+ if not isinstance (value , str ):
265+ raise DataError (f"Invalid bytea value { value } : str expected" )
266+ return _parse_bytea (value )
247267 if isinstance (ctype , DECIMAL ):
248268 assert isinstance (value , (str , int ))
249269 return Decimal (value )
@@ -274,6 +294,9 @@ def format_value(value: ParameterType) -> str:
274294 return f"'{ value .strftime ('%Y-%m-%d %H:%M:%S' )} '"
275295 elif isinstance (value , date ):
276296 return f"'{ value .isoformat ()} '"
297+ elif isinstance (value , bytes ):
298+ # Encode each byte into hex
299+ return "'" + "" .join (f"\\ x{ b :02x} " for b in value ) + "'"
277300 if value is None :
278301 return "NULL"
279302 elif isinstance (value , Sequence ):
0 commit comments