Skip to content

Commit 48a19eb

Browse files
committed
Support decimal, date, time, timestamp with time zone and timestamp
1 parent 4e61be8 commit 48a19eb

File tree

6 files changed

+413
-30
lines changed

6 files changed

+413
-30
lines changed

README.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,39 @@ The transaction is created when the first SQL statement is executed.
317317
exits the *with* context and the queries succeed, otherwise
318318
`trino.dbapi.Connection.rollback()` will be called.
319319

320-
## Development
320+
# Improved Python types
321321

322-
### Getting Started With Development
322+
If you enable the flag `experimental_python_types`, the client will convert the results of the query to the
323+
corresponding Python types. For example, if the query returns a `DECIMAL` column, the result will be a `Decimal` object.
324+
325+
Limitations of the Python types are described in the
326+
[Python types documentation](https://docs.python.org/3/library/datatypes.html). These limitations will generate an
327+
exception `trino.exceptions.DataError` if the query returns a value that cannot be converted to the corresponding Python
328+
type.
329+
330+
```python
331+
import trino
332+
import pytz
333+
from datetime import datetime
334+
335+
conn = trino.dbapi.connect(
336+
...
337+
)
338+
339+
cur = conn.cursor()
340+
341+
params = datetime(2020, 1, 1, 16, 43, 22, 320000, tzinfo=pytz.timezone('America/Los_Angeles'))
342+
343+
cur.execute("SELECT ?", params=(params,))
344+
rows = cur.fetchall()
345+
346+
assert rows[0][0] == params
347+
assert cur.description[0][1] == "timestamp with time zone"
348+
```
349+
350+
# Development
351+
352+
## Getting Started With Development
323353

324354
Start by forking the repository and then modify the code in your fork.
325355

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,18 @@
2525
assert trino_version is not None
2626
version = str(ast.literal_eval(trino_version.group(1)))
2727

28+
default_require = ["pytz"]
2829
kerberos_require = ["requests_kerberos"]
2930
sqlalchemy_require = ["sqlalchemy~=1.3"]
3031

31-
all_require = kerberos_require + sqlalchemy_require
32+
all_require = default_require + kerberos_require + sqlalchemy_require
3233

3334
tests_require = all_require + [
3435
# httpretty >= 1.1 duplicates requests in `httpretty.latest_requests`
3536
# https://github.com/gabrielfalcao/HTTPretty/issues/425
3637
"httpretty < 1.1",
3738
"pytest",
3839
"pytest-runner",
39-
"pytz",
4040
"click",
4141
]
4242

tests/integration/test_dbapi_integration.py

Lines changed: 285 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
# See the License for the specific language governing permissions and
1111
# limitations under the License.
1212
import math
13-
from datetime import datetime
13+
from datetime import datetime, time, date, timezone, timedelta
14+
from decimal import Decimal
1415

1516
import pytest
1617
import pytz
@@ -123,22 +124,267 @@ def test_string_query_param(trino_connection):
123124
assert rows[0][0] == "six'"
124125

125126

126-
def test_datetime_query_param(trino_connection):
127+
def test_python_types_not_used_when_experimental_python_types_is_not_set(trino_connection):
127128
cur = trino_connection.cursor()
128129

129-
cur.execute("SELECT ?", params=(datetime(2020, 1, 1, 0, 0, 0),))
130+
cur.execute("""
131+
SELECT
132+
DECIMAL '0.142857',
133+
DATE '2018-01-01',
134+
TIMESTAMP '2019-01-01 00:00:00.000+01:00',
135+
TIMESTAMP '2019-01-01 00:00:00.000 UTC',
136+
TIMESTAMP '2019-01-01 00:00:00.000',
137+
TIME '00:00:00.000'
138+
""")
139+
rows = cur.fetchall()
140+
141+
assert rows[0][0] == '0.142857'
142+
assert rows[0][1] == '2018-01-01'
143+
assert rows[0][2] == '2019-01-01 00:00:00.000 +01:00'
144+
assert rows[0][3] == '2019-01-01 00:00:00.000 UTC'
145+
assert rows[0][4] == '2019-01-01 00:00:00.000'
146+
assert rows[0][5] == '00:00:00.000'
147+
148+
149+
def test_decimal_query_param(trino_connection):
150+
cur = trino_connection.cursor(experimental_python_types=True)
151+
152+
cur.execute("SELECT ?", params=(Decimal('0.142857'),))
153+
rows = cur.fetchall()
154+
155+
assert rows[0][0] == Decimal('0.142857')
156+
157+
158+
def test_null_decimal(trino_connection):
159+
cur = trino_connection.cursor(experimental_python_types=True)
160+
161+
cur.execute("SELECT CAST(NULL AS DECIMAL)")
162+
rows = cur.fetchall()
163+
164+
assert rows[0][0] is None
165+
166+
167+
def test_biggest_decimal(trino_connection):
168+
cur = trino_connection.cursor(experimental_python_types=True)
169+
170+
params = Decimal('99999999999999999999999999999999999999')
171+
cur.execute("SELECT ?", params=(params,))
130172
rows = cur.fetchall()
131173

132-
assert rows[0][0] == "2020-01-01 00:00:00.000"
174+
assert rows[0][0] == params
133175

134-
cur.execute("SELECT ?",
135-
params=(datetime(2020, 1, 1, 0, 0, 0, tzinfo=pytz.utc),))
176+
177+
def test_smallest_decimal(trino_connection):
178+
cur = trino_connection.cursor(experimental_python_types=True)
179+
180+
params = Decimal('-99999999999999999999999999999999999999')
181+
cur.execute("SELECT ?", params=(params,))
136182
rows = cur.fetchall()
137183

138-
assert rows[0][0] == "2020-01-01 00:00:00.000 UTC"
184+
assert rows[0][0] == params
185+
186+
187+
def test_highest_precision_decimal(trino_connection):
188+
cur = trino_connection.cursor(experimental_python_types=True)
189+
190+
params = Decimal('0.99999999999999999999999999999999999999')
191+
cur.execute("SELECT ?", params=(params,))
192+
rows = cur.fetchall()
193+
194+
assert rows[0][0] == params
195+
196+
197+
def test_datetime_query_param(trino_connection):
198+
cur = trino_connection.cursor(experimental_python_types=True)
199+
200+
params = datetime(2020, 1, 1, 16, 43, 22, 320000)
201+
202+
cur.execute("SELECT ?", params=(params,))
203+
rows = cur.fetchall()
204+
205+
assert rows[0][0] == params
206+
assert cur.description[0][1] == "timestamp"
207+
208+
209+
def test_datetime_with_trailing_zeros(trino_connection):
210+
cur = trino_connection.cursor(experimental_python_types=True)
211+
212+
cur.execute("SELECT TIMESTAMP '2001-08-22 03:04:05.321000'")
213+
rows = cur.fetchall()
214+
215+
assert rows[0][0] == datetime.strptime("2001-08-22 03:04:05.321000", "%Y-%m-%d %H:%M:%S.%f")
216+
217+
218+
def test_datetime_with_utc_time_zone_query_param(trino_connection):
219+
cur = trino_connection.cursor(experimental_python_types=True)
220+
221+
params = datetime(2020, 1, 1, 16, 43, 22, 320000, tzinfo=pytz.timezone('UTC'))
222+
223+
cur.execute("SELECT ?", params=(params,))
224+
rows = cur.fetchall()
225+
226+
assert rows[0][0] == params
139227
assert cur.description[0][1] == "timestamp with time zone"
140228

141229

230+
def test_datetime_with_numeric_offset_time_zone_query_param(trino_connection):
231+
cur = trino_connection.cursor(experimental_python_types=True)
232+
233+
tz = timezone(-timedelta(hours=5, minutes=30))
234+
235+
params = datetime(2020, 1, 1, 16, 43, 22, 320000, tzinfo=tz)
236+
237+
cur.execute("SELECT ?", params=(params,))
238+
rows = cur.fetchall()
239+
240+
assert rows[0][0] == params
241+
assert cur.description[0][1] == "timestamp with time zone"
242+
243+
244+
def test_datetime_with_named_time_zone_query_param(trino_connection):
245+
cur = trino_connection.cursor(experimental_python_types=True)
246+
247+
params = datetime(2020, 1, 1, 16, 43, 22, 320000, tzinfo=pytz.timezone('America/Los_Angeles'))
248+
249+
cur.execute("SELECT ?", params=(params,))
250+
rows = cur.fetchall()
251+
252+
assert rows[0][0] == params
253+
assert cur.description[0][1] == "timestamp with time zone"
254+
255+
256+
def test_null_datetime_with_time_zone(trino_connection):
257+
cur = trino_connection.cursor(experimental_python_types=True)
258+
259+
cur.execute("SELECT CAST(NULL AS TIMESTAMP WITH TIME ZONE)")
260+
rows = cur.fetchall()
261+
262+
assert rows[0][0] is None
263+
264+
265+
def test_datetime_with_time_zone_numeric_offset(trino_connection):
266+
cur = trino_connection.cursor(experimental_python_types=True)
267+
268+
cur.execute("SELECT TIMESTAMP '2001-08-22 03:04:05.321 -08:00'")
269+
rows = cur.fetchall()
270+
271+
assert rows[0][0] == datetime.strptime("2001-08-22 03:04:05.321 -08:00", "%Y-%m-%d %H:%M:%S.%f %z")
272+
273+
274+
def test_unexisting_datetimes_with_time_zone_query_param(trino_connection):
275+
cur = trino_connection.cursor(experimental_python_types=True)
276+
277+
params = datetime(2021, 3, 28, 2, 30, 0, tzinfo=pytz.timezone('Europe/Brussels'))
278+
with pytest.raises(trino.exceptions.TrinoUserError):
279+
cur.execute("SELECT ?", params=(params,))
280+
cur.fetchall()
281+
282+
283+
def test_doubled_datetimes_first_query_param(trino_connection):
284+
cur = trino_connection.cursor(experimental_python_types=True)
285+
286+
params = pytz.timezone('US/Eastern').localize(datetime(2002, 10, 27, 1, 30, 0), is_dst=True)
287+
288+
cur.execute("SELECT ?", params=(params,))
289+
rows = cur.fetchall()
290+
291+
assert rows[0][0] == datetime(2002, 10, 27, 1, 30, 0, tzinfo=pytz.timezone('US/Eastern'))
292+
293+
294+
def test_doubled_datetimes_second_query_param(trino_connection):
295+
cur = trino_connection.cursor(experimental_python_types=True)
296+
297+
params = pytz.timezone('US/Eastern').localize(datetime(2002, 10, 27, 1, 30, 0), is_dst=False)
298+
299+
cur.execute("SELECT ?", params=(params,))
300+
rows = cur.fetchall()
301+
302+
assert rows[0][0] == datetime(2002, 10, 27, 1, 30, 0, tzinfo=pytz.timezone('US/Eastern'))
303+
304+
305+
def test_date_query_param(trino_connection):
306+
cur = trino_connection.cursor(experimental_python_types=True)
307+
308+
params = datetime(2020, 1, 1, 0, 0, 0).date()
309+
310+
cur.execute("SELECT ?", params=(params,))
311+
rows = cur.fetchall()
312+
313+
assert rows[0][0] == params
314+
315+
316+
def test_null_date(trino_connection):
317+
cur = trino_connection.cursor(experimental_python_types=True)
318+
319+
cur.execute("SELECT CAST(NULL AS DATE)")
320+
rows = cur.fetchall()
321+
322+
assert rows[0][0] is None
323+
324+
325+
def test_unsupported_python_dates(trino_connection):
326+
cur = trino_connection.cursor(experimental_python_types=True)
327+
328+
# dates not supported in Python date type
329+
for unsupported_date in [
330+
'-0001-01-01',
331+
'0000-01-01'
332+
]:
333+
with pytest.raises(trino.exceptions.TrinoDataError):
334+
cur.execute(f"SELECT DATE '{unsupported_date}'")
335+
cur.fetchall()
336+
337+
338+
def test_supported_special_dates_query_param(trino_connection):
339+
cur = trino_connection.cursor(experimental_python_types=True)
340+
341+
for params in (
342+
# first day of AD
343+
date(1, 1, 1),
344+
date(12, 12, 12),
345+
# before julian->gregorian switch
346+
date(1500, 1, 1),
347+
# During julian->gregorian switch
348+
date(1752, 9, 4),
349+
# before epoch
350+
date(1952, 4, 3),
351+
date(1970, 1, 1),
352+
date(1970, 2, 3),
353+
# summer on northern hemisphere (possible DST)
354+
date(2017, 7, 1),
355+
# winter on northern hemisphere (possible DST on southern hemisphere)
356+
date(2017, 1, 1),
357+
# winter on southern hemisphere (possible DST on northern hemisphere)
358+
date(2017, 12, 31),
359+
date(1983, 4, 1),
360+
date(1983, 10, 1),
361+
):
362+
cur.execute("SELECT ?", params=(params,))
363+
rows = cur.fetchall()
364+
365+
assert rows[0][0] == params
366+
367+
368+
def test_time_query_param(trino_connection):
369+
cur = trino_connection.cursor(experimental_python_types=True)
370+
371+
params = time(12, 3, 44, 333000)
372+
373+
cur.execute("SELECT ?", params=(params,))
374+
rows = cur.fetchall()
375+
376+
assert rows[0][0] == params
377+
378+
379+
def test_time_with_time_zone_query_param(trino_connection):
380+
with pytest.raises(trino.exceptions.NotSupportedError):
381+
cur = trino_connection.cursor()
382+
383+
params = time(16, 43, 22, 320000, tzinfo=pytz.timezone('Asia/Shanghai'))
384+
385+
cur.execute("SELECT ?", params=(params,))
386+
387+
142388
def test_array_query_param(trino_connection):
143389
cur = trino_connection.cursor()
144390

@@ -158,6 +404,38 @@ def test_array_query_param(trino_connection):
158404
assert rows[0][0] == "array(integer)"
159405

160406

407+
def test_array_timestamp_query_param(trino_connection):
408+
cur = trino_connection.cursor(experimental_python_types=True)
409+
410+
params = [datetime(2020, 1, 1, 0, 0, 0), datetime(2020, 1, 2, 0, 0, 0)]
411+
412+
cur.execute("SELECT ?", params=(params,))
413+
rows = cur.fetchall()
414+
415+
assert rows[0][0] == params
416+
417+
cur.execute("SELECT TYPEOF(?)", params=(params,))
418+
rows = cur.fetchall()
419+
420+
assert rows[0][0] == "array(timestamp(6))"
421+
422+
423+
def test_array_timestamp_with_timezone_query_param(trino_connection):
424+
cur = trino_connection.cursor(experimental_python_types=True)
425+
426+
params = [datetime(2020, 1, 1, 0, 0, 0, tzinfo=pytz.utc), datetime(2020, 1, 2, 0, 0, 0, tzinfo=pytz.utc)]
427+
428+
cur.execute("SELECT ?", params=(params,))
429+
rows = cur.fetchall()
430+
431+
assert rows[0][0] == params
432+
433+
cur.execute("SELECT TYPEOF(?)", params=(params,))
434+
rows = cur.fetchall()
435+
436+
assert rows[0][0] == "array(timestamp(6) with time zone)"
437+
438+
161439
def test_dict_query_param(trino_connection):
162440
cur = trino_connection.cursor()
163441

0 commit comments

Comments
 (0)