Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions singlestoredb/http/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,17 @@ def b64decode_converter(

def encode_timedelta(obj: datetime.timedelta) -> str:
"""Encode timedelta as str."""
negative = obj.days < 0
if negative:
obj = -obj
seconds = int(obj.seconds) % 60
minutes = int(obj.seconds // 60) % 60
hours = int(obj.seconds // 3600) % 24 + int(obj.days) * 24
if obj.microseconds:
fmt = '{0:02d}:{1:02d}:{2:02d}.{3:06d}'
fmt = '{0}{1:02d}:{2:02d}:{3:02d}.{4:06d}'
else:
fmt = '{0:02d}:{1:02d}:{2:02d}'
return fmt.format(hours, minutes, seconds, obj.microseconds)
fmt = '{0}{1:02d}:{2:02d}:{3:02d}'
return fmt.format('-' if negative else '', hours, minutes, seconds, obj.microseconds)


def encode_time(obj: datetime.time) -> str:
Expand Down
9 changes: 6 additions & 3 deletions singlestoredb/mysql/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,17 @@ def escape_None(value: str, mapping: Optional[Encoders] = None) -> str:


def escape_timedelta(obj: datetime.timedelta, mapping: Optional[Encoders] = None) -> str:
negative = obj.days < 0
if negative:
obj = -obj
seconds = int(obj.seconds) % 60
minutes = int(obj.seconds // 60) % 60
hours = int(obj.seconds // 3600) % 24 + int(obj.days) * 24
if obj.microseconds:
fmt = "'{0:02d}:{1:02d}:{2:02d}.{3:06d}'"
fmt = "'{0}{1:02d}:{2:02d}:{3:02d}.{4:06d}'"
else:
fmt = "'{0:02d}:{1:02d}:{2:02d}'"
return fmt.format(hours, minutes, seconds, obj.microseconds)
fmt = "'{0}{1:02d}:{2:02d}:{3:02d}'"
return fmt.format('-' if negative else '', hours, minutes, seconds, obj.microseconds)


def escape_time(obj: datetime.time, mapping: Optional[Encoders] = None) -> str:
Expand Down
42 changes: 42 additions & 0 deletions singlestoredb/tests/test_parameters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Offline cursor parameter tests for signed TIME durations."""
import datetime

import pytest

from singlestoredb.converters import timedelta_fromisoformat
from singlestoredb.http.connection import encode_timedelta
from singlestoredb.mysql import connect


CASES = [
(datetime.timedelta(microseconds=-1), '-00:00:00.000001'),
(datetime.timedelta(seconds=-1), '-00:00:01'),
(datetime.timedelta(minutes=-1), '-00:01:00'),
(datetime.timedelta(hours=-1), '-01:00:00'),
(datetime.timedelta(hours=-25, seconds=-17), '-25:00:17'),
(datetime.timedelta(seconds=-1, microseconds=-123456), '-00:00:01.123456'),
(datetime.timedelta(days=-2), '-48:00:00'),
(datetime.timedelta(0), '00:00:00'),
(datetime.timedelta(microseconds=1), '00:00:00.000001'),
(datetime.timedelta(hours=25, seconds=17), '25:00:17'),
]


@pytest.mark.parametrize('duration, literal', CASES)
def test_mogrify_preserves_timedelta_sign_and_magnitude(
duration: datetime.timedelta, literal: str,
) -> None:
"""Bind the correct TIME literal without opening a database connection."""
connection = connect(defer_connect=True)
cursor = connection.cursor()

assert cursor.mogrify('SELECT %s', (duration,)) == f"SELECT '{literal}'"
assert timedelta_fromisoformat(literal) == duration


@pytest.mark.parametrize('duration, literal', CASES)
def test_http_encode_timedelta_preserves_sign_and_magnitude(
duration: datetime.timedelta, literal: str,
) -> None:
"""The HTTP API encoder writes the same TIME literal."""
assert encode_timedelta(duration) == literal
Loading