1313try :
1414 from ciso8601 import parse_datetime # type: ignore
1515except ImportError :
16- parse_datetime = datetime .fromisoformat # type: ignore
16+ # Unfortunately, there seems to be no support for optional bits in strptime
17+ def parse_datetime (date_string : str ) -> datetime : # type: ignore
18+ format = "%Y-%m-%d %H:%M:%S.%f"
19+ # fromisoformat doesn't support milliseconds
20+ if "." in date_string :
21+ return datetime .strptime (date_string , format )
22+ return datetime .fromisoformat (date_string )
1723
1824
1925from firebolt .common .exception import DataError , NotSupportedError
2026from firebolt .common .util import cached_property
2127
2228_NoneType = type (None )
23- _col_types = (int , float , str , datetime , date , bool , list , _NoneType )
29+ _col_types = (int , float , str , datetime , date , bool , list , Decimal , _NoneType )
2430# duplicating this since 3.7 can't unpack Union
25- ColType = Union [int , float , str , datetime , date , bool , list , _NoneType ]
31+ ColType = Union [int , float , str , datetime , date , bool , list , Decimal , _NoneType ]
2632RawColType = Union [int , float , str , bool , list , _NoneType ]
27- ParameterType = Union [int , float , str , datetime , date , bool , Sequence ]
33+ ParameterType = Union [int , float , str , datetime , date , bool , Decimal , Sequence ]
2834
2935# These definitions are required by PEP-249
3036Date = date
@@ -78,9 +84,9 @@ class ARRAY:
7884
7985 _prefix = "Array("
8086
81- def __init__ (self , subtype : Union [type , ARRAY ]):
87+ def __init__ (self , subtype : Union [type , ARRAY , DECIMAL , DATETIME64 ]):
8288 assert (subtype in _col_types and subtype is not list ) or isinstance (
83- subtype , ARRAY
89+ subtype , ( ARRAY , DECIMAL , DATETIME64 )
8490 ), f"Invalid array subtype: { str (subtype )} "
8591 self .subtype = subtype
8692
@@ -93,6 +99,41 @@ def __eq__(self, other: object) -> bool:
9399 return other .subtype == self .subtype
94100
95101
102+ class DECIMAL :
103+ """Class for holding imformation about decimal value in firebolt db."""
104+
105+ _prefix = "Decimal("
106+
107+ def __init__ (self , precision : int , scale : int ):
108+ self .precision = precision
109+ self .scale = scale
110+
111+ def __str__ (self ) -> str :
112+ return f"Decimal({ self .precision } , { self .scale } )"
113+
114+ def __eq__ (self , other : object ) -> bool :
115+ if not isinstance (other , DECIMAL ):
116+ return NotImplemented
117+ return other .precision == self .precision and other .scale == self .scale
118+
119+
120+ class DATETIME64 :
121+ """Class for holding imformation about datetime64 value in firebolt db."""
122+
123+ _prefix = "DateTime64("
124+
125+ def __init__ (self , precision : int ):
126+ self .precision = precision
127+
128+ def __str__ (self ) -> str :
129+ return f"DateTime64({ self .precision } )"
130+
131+ def __eq__ (self , other : object ) -> bool :
132+ if not isinstance (other , DATETIME64 ):
133+ return NotImplemented
134+ return other .precision == self .precision
135+
136+
96137NULLABLE_PREFIX = "Nullable("
97138
98139
@@ -122,6 +163,7 @@ class _InternalType(Enum):
122163
123164 # DATE
124165 Date = "Date"
166+ Date32 = "Date32"
125167
126168 # DATETIME, TIMESTAMP
127169 DateTime = "DateTime"
@@ -145,20 +187,38 @@ def python_type(self) -> type:
145187 _InternalType .Float64 : float ,
146188 _InternalType .String : str ,
147189 _InternalType .Date : date ,
190+ _InternalType .Date32 : date ,
148191 _InternalType .DateTime : datetime ,
149192 # For simplicity, this could happen only during 'select null' query
150193 _InternalType .Nothing : str ,
151194 }
152195 return types [self ]
153196
154197
155- def parse_type (raw_type : str ) -> Union [type , ARRAY ]:
198+ def parse_type (raw_type : str ) -> Union [type , ARRAY , DECIMAL , DATETIME64 ]:
156199 """Parse typename, provided by query metadata into python type."""
157200 if not isinstance (raw_type , str ):
158201 raise DataError (f"Invalid typename { str (raw_type )} : str expected" )
159202 # Handle arrays
160203 if raw_type .startswith (ARRAY ._prefix ) and raw_type .endswith (")" ):
161204 return ARRAY (parse_type (raw_type [len (ARRAY ._prefix ) : - 1 ]))
205+ # Handle decimal
206+ if raw_type .startswith (DECIMAL ._prefix ) and raw_type .endswith (")" ):
207+ try :
208+ prec_scale = raw_type [len (DECIMAL ._prefix ) : - 1 ].split ("," )
209+ precision , scale = int (prec_scale [0 ]), int (prec_scale [1 ])
210+ except (ValueError , IndexError ):
211+ pass
212+ else :
213+ return DECIMAL (precision , scale )
214+ # Handle detetime64
215+ if raw_type .startswith (DATETIME64 ._prefix ) and raw_type .endswith (")" ):
216+ try :
217+ precision = int (raw_type [len (DATETIME64 ._prefix ) : - 1 ])
218+ except (ValueError , IndexError ):
219+ pass
220+ else :
221+ return DATETIME64 (precision )
162222 # Handle nullable
163223 if raw_type .startswith (NULLABLE_PREFIX ) and raw_type .endswith (")" ):
164224 return parse_type (raw_type [len (NULLABLE_PREFIX ) : - 1 ])
@@ -173,7 +233,7 @@ def parse_type(raw_type: str) -> Union[type, ARRAY]:
173233
174234def parse_value (
175235 value : RawColType ,
176- ctype : Union [type , ARRAY ],
236+ ctype : Union [type , ARRAY , DECIMAL , DATETIME64 ],
177237) -> ColType :
178238 """Provided raw value and python type, parses first into python value."""
179239 if value is None :
@@ -186,10 +246,13 @@ def parse_value(
186246 raise DataError (f"Invalid date value { value } : str expected" )
187247 assert isinstance (value , str )
188248 return parse_datetime (value ).date ()
189- if ctype is datetime :
249+ if ctype is datetime or isinstance ( ctype , DATETIME64 ) :
190250 if not isinstance (value , str ):
191251 raise DataError (f"Invalid datetime value { value } : str expected" )
192252 return parse_datetime (value )
253+ if isinstance (ctype , DECIMAL ):
254+ assert isinstance (value , (str , int ))
255+ return Decimal (value )
193256 if isinstance (ctype , ARRAY ):
194257 assert isinstance (value , list )
195258 return [parse_value (it , ctype .subtype ) for it in value ]
0 commit comments