Skip to content

Commit 2439965

Browse files
authored
Merge pull request #94 from ydb-platform/extended_dates_format
Date32, Datetime64 and Timestamp64 support
2 parents 0842b5d + 2c16b6b commit 2439965

File tree

7 files changed

+164
-17
lines changed

7 files changed

+164
-17
lines changed

docs/types.rst

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,97 +12,134 @@ For more information about YDB data types, see the `YDB Type System Documentatio
1212
Type Mapping Summary
1313
--------------------
1414

15-
The following table shows the complete mapping between YDB native types, SQLAlchemy types, and Python types:
15+
The following table shows the complete mapping between YDB native types, YDB SQLAlchemy types, standard SQLAlchemy types, and Python types:
1616

1717
.. list-table:: YDB Type System Reference
1818
:header-rows: 1
19-
:widths: 20 25 20 35
19+
:widths: 15 20 20 15 30
2020

2121
* - YDB Native Type
22-
- SQLAlchemy Type
22+
- YDB SA Type
23+
- SA Type
2324
- Python Type
2425
- Notes
2526
* - ``Bool``
26-
- ``BOOLEAN``
27+
-
28+
- ``Boolean``
2729
- ``bool``
2830
-
2931
* - ``Int8``
32+
- :class:`~ydb_sqlalchemy.sqlalchemy.types.Int8`
3033
-
3134
- ``int``
3235
- -2^7 to 2^7-1
3336
* - ``Int16``
37+
- :class:`~ydb_sqlalchemy.sqlalchemy.types.Int16`
3438
-
3539
- ``int``
3640
- -2^15 to 2^15-1
3741
* - ``Int32``
42+
- :class:`~ydb_sqlalchemy.sqlalchemy.types.Int32`
3843
-
3944
- ``int``
4045
- -2^31 to 2^31-1
4146
* - ``Int64``
42-
- ``INTEGER``
47+
- :class:`~ydb_sqlalchemy.sqlalchemy.types.Int64`
48+
- ``Integer``
4349
- ``int``
44-
- -2^63 to 2^63-1
50+
- -2^63 to 2^63-1, default integer type
4551
* - ``Uint8``
52+
- :class:`~ydb_sqlalchemy.sqlalchemy.types.UInt8`
4653
-
4754
- ``int``
4855
- 0 to 2^8-1
4956
* - ``Uint16``
57+
- :class:`~ydb_sqlalchemy.sqlalchemy.types.UInt16`
5058
-
5159
- ``int``
5260
- 0 to 2^16-1
5361
* - ``Uint32``
62+
- :class:`~ydb_sqlalchemy.sqlalchemy.types.UInt32`
5463
-
5564
- ``int``
5665
- 0 to 2^32-1
5766
* - ``Uint64``
67+
- :class:`~ydb_sqlalchemy.sqlalchemy.types.UInt64`
5868
-
5969
- ``int``
6070
- 0 to 2^64-1
6171
* - ``Float``
62-
- ``FLOAT``
72+
-
73+
- ``Float``
6374
- ``float``
6475
-
6576
* - ``Double``
77+
-
6678
- ``Double``
6779
- ``float``
6880
- Available in SQLAlchemy 2.0+
6981
* - ``Decimal(p,s)``
70-
- ``DECIMAL`` / ``NUMERIC``
82+
- :class:`~ydb_sqlalchemy.sqlalchemy.types.Decimal`
83+
- ``DECIMAL``
7184
- ``decimal.Decimal``
7285
-
7386
* - ``String``
74-
- ``BINARY`` / ``BLOB``
75-
- ``str`` / ``bytes``
87+
-
88+
- ``BINARY``
89+
- ``bytes``
7690
-
7791
* - ``Utf8``
78-
- ``CHAR`` / ``VARCHAR`` / ``TEXT`` / ``NVARCHAR``
92+
-
93+
- ``String`` / ``Text``
7994
- ``str``
8095
-
8196
* - ``Date``
97+
- :class:`~ydb_sqlalchemy.sqlalchemy.datetime_types.YqlDate`
8298
- ``Date``
8399
- ``datetime.date``
84100
-
101+
* - ``Date32``
102+
- :class:`~ydb_sqlalchemy.sqlalchemy.datetime_types.YqlDate32`
103+
-
104+
- ``datetime.date``
105+
- Extended date range support
85106
* - ``Datetime``
107+
- :class:`~ydb_sqlalchemy.sqlalchemy.datetime_types.YqlDateTime`
86108
- ``DATETIME``
87109
- ``datetime.datetime``
88110
-
111+
* - ``Datetime64``
112+
- :class:`~ydb_sqlalchemy.sqlalchemy.datetime_types.YqlDateTime64`
113+
-
114+
- ``datetime.datetime``
115+
- Extended datetime range
89116
* - ``Timestamp``
117+
- :class:`~ydb_sqlalchemy.sqlalchemy.datetime_types.YqlTimestamp`
90118
- ``TIMESTAMP``
91119
- ``datetime.datetime``
92120
-
121+
* - ``Timestamp64``
122+
- :class:`~ydb_sqlalchemy.sqlalchemy.datetime_types.YqlTimestamp64`
123+
-
124+
- ``datetime.datetime``
125+
- Extended timestamp range
93126
* - ``Json``
127+
- :class:`~ydb_sqlalchemy.sqlalchemy.json.YqlJSON`
94128
- ``JSON``
95129
- ``dict`` / ``list``
96130
-
97131
* - ``List<T>``
132+
- :class:`~ydb_sqlalchemy.sqlalchemy.types.ListType`
98133
- ``ARRAY``
99134
- ``list``
100135
-
101136
* - ``Struct<...>``
137+
- :class:`~ydb_sqlalchemy.sqlalchemy.types.StructType`
102138
-
103139
- ``dict``
104140
-
105141
* - ``Optional<T>``
142+
-
106143
- ``nullable=True``
107144
- ``None`` + base type
108145
-
@@ -145,6 +182,10 @@ YDB provides specific integer types with defined bit widths:
145182
byte_value = Column(UInt8) # Unsigned 8-bit integer (0-255)
146183
counter = Column(UInt32) # Unsigned 32-bit integer
147184
185+
For detailed API reference, see:
186+
:class:`~ydb_sqlalchemy.sqlalchemy.types.Int8`, :class:`~ydb_sqlalchemy.sqlalchemy.types.Int16`, :class:`~ydb_sqlalchemy.sqlalchemy.types.Int32`, :class:`~ydb_sqlalchemy.sqlalchemy.types.Int64`,
187+
:class:`~ydb_sqlalchemy.sqlalchemy.types.UInt8`, :class:`~ydb_sqlalchemy.sqlalchemy.types.UInt16`, :class:`~ydb_sqlalchemy.sqlalchemy.types.UInt32`, :class:`~ydb_sqlalchemy.sqlalchemy.types.UInt64`.
188+
148189
Decimal Type
149190
------------
150191

@@ -176,14 +217,19 @@ YDB supports high-precision decimal numbers:
176217
percentage=99.99
177218
))
178219
220+
For detailed API reference, see: :class:`~ydb_sqlalchemy.sqlalchemy.types.Decimal`.
221+
179222
Date and Time Types
180223
-------------------
181224

182225
YDB provides several date and time types:
183226

184227
.. code-block:: python
185228
186-
from ydb_sqlalchemy.sqlalchemy.types import YqlDate, YqlDateTime, YqlTimestamp
229+
from ydb_sqlalchemy.sqlalchemy.types import (
230+
YqlDate, YqlDateTime, YqlTimestamp,
231+
YqlDate32, YqlDateTime64, YqlTimestamp64
232+
)
187233
from sqlalchemy import DateTime
188234
import datetime
189235
@@ -192,15 +238,24 @@ YDB provides several date and time types:
192238
193239
id = Column(UInt64, primary_key=True)
194240
195-
# Date only (YYYY-MM-DD)
241+
# Date only (YYYY-MM-DD) - standard range
196242
event_date = Column(YqlDate)
197243
198-
# DateTime with timezone support
244+
# Date32 - extended date range support
245+
extended_date = Column(YqlDate32)
246+
247+
# DateTime with timezone support - standard range
199248
created_at = Column(YqlDateTime(timezone=True))
200249
201-
# Timestamp (high precision)
250+
# DateTime64 - extended range
251+
precise_datetime = Column(YqlDateTime64(timezone=True))
252+
253+
# Timestamp (high precision) - standard range
202254
precise_time = Column(YqlTimestamp)
203255
256+
# Timestamp64 - extended range with microsecond precision
257+
extended_timestamp = Column(YqlTimestamp64)
258+
204259
# Standard SQLAlchemy DateTime also works
205260
updated_at = Column(DateTime)
206261
@@ -211,7 +266,14 @@ YDB provides several date and time types:
211266
session.add(EventLog(
212267
id=1,
213268
event_date=today,
269+
extended_date=today,
214270
created_at=now,
271+
precise_datetime=now,
215272
precise_time=now,
273+
extended_timestamp=now,
216274
updated_at=now
217275
))
276+
277+
For detailed API reference, see:
278+
:class:`~ydb_sqlalchemy.sqlalchemy.datetime_types.YqlDate`, :class:`~ydb_sqlalchemy.sqlalchemy.datetime_types.YqlDateTime`, :class:`~ydb_sqlalchemy.sqlalchemy.datetime_types.YqlTimestamp`,
279+
:class:`~ydb_sqlalchemy.sqlalchemy.datetime_types.YqlDate32`, :class:`~ydb_sqlalchemy.sqlalchemy.datetime_types.YqlDateTime64`, :class:`~ydb_sqlalchemy.sqlalchemy.datetime_types.YqlTimestamp64`.

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
sqlalchemy >= 1.4.0, < 3.0.0
2-
ydb >= 3.18.8
2+
ydb >= 3.21.6
33
ydb-dbapi >= 0.1.10

test/test_suite.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ctypes
2+
import datetime
23
import decimal
34

45
import pytest
@@ -424,6 +425,16 @@ class DateTest(_DateTest):
424425
run_dispose_bind = "once"
425426

426427

428+
class Date32Test(_DateTest):
429+
run_dispose_bind = "once"
430+
datatype = ydb_sa_types.YqlDate32
431+
data = datetime.date(1969, 1, 1)
432+
433+
@pytest.mark.skip("Default binding for DATE is not compatible with Date32")
434+
def test_select_direct(self, connection):
435+
pass
436+
437+
427438
class DateTimeMicrosecondsTest(_DateTimeMicrosecondsTest):
428439
run_dispose_bind = "once"
429440

@@ -432,10 +443,30 @@ class DateTimeTest(_DateTimeTest):
432443
run_dispose_bind = "once"
433444

434445

446+
class DateTime64Test(_DateTimeTest):
447+
datatype = ydb_sa_types.YqlDateTime64
448+
data = datetime.datetime(1969, 10, 15, 12, 57, 18)
449+
run_dispose_bind = "once"
450+
451+
@pytest.mark.skip("Default binding for DATETIME is not compatible with DateTime64")
452+
def test_select_direct(self, connection):
453+
pass
454+
455+
435456
class TimestampMicrosecondsTest(_TimestampMicrosecondsTest):
436457
run_dispose_bind = "once"
437458

438459

460+
class Timestamp64MicrosecondsTest(_TimestampMicrosecondsTest):
461+
run_dispose_bind = "once"
462+
datatype = ydb_sa_types.YqlTimestamp64
463+
data = datetime.datetime(1969, 10, 15, 12, 57, 18, 396)
464+
465+
@pytest.mark.skip("Default binding for TIMESTAMP is not compatible with Timestamp64")
466+
def test_select_direct(self, connection):
467+
pass
468+
469+
439470
@pytest.mark.skip("unsupported Time data type")
440471
class TimeTest(_TimeTest):
441472
pass

ydb_sqlalchemy/sqlalchemy/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ def upsert(table):
6161
ydb.DecimalType: sa.DECIMAL,
6262
ydb.PrimitiveType.Yson: sa.TEXT,
6363
ydb.PrimitiveType.Date: sa.DATE,
64+
ydb.PrimitiveType.Date32: sa.DATE,
65+
ydb.PrimitiveType.Timestamp64: sa.TIMESTAMP,
66+
ydb.PrimitiveType.Datetime64: sa.DATETIME,
6467
ydb.PrimitiveType.Datetime: sa.DATETIME,
6568
ydb.PrimitiveType.Timestamp: sa.TIMESTAMP,
6669
ydb.PrimitiveType.Interval: sa.INTEGER,

ydb_sqlalchemy/sqlalchemy/compiler/base.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ def visit_DATETIME(self, type_: sa.DATETIME, **kw):
135135
def visit_TIMESTAMP(self, type_: sa.TIMESTAMP, **kw):
136136
return "Timestamp"
137137

138+
def visit_date32(self, type_: types.YqlDate32, **kw):
139+
return "Date32"
140+
141+
def visit_timestamp64(self, type_: types.YqlTimestamp64, **kw):
142+
return "Timestamp64"
143+
144+
def visit_datetime64(self, type_: types.YqlDateTime64, **kw):
145+
return "DateTime64"
146+
138147
def visit_list_type(self, type_: types.ListType, **kw):
139148
inner = self.process(type_.item_type, **kw)
140149
return f"List<{inner}>"
@@ -193,6 +202,12 @@ def get_ydb_type(
193202
elif isinstance(type_, types.YqlJSON.YqlJSONPathType):
194203
ydb_type = ydb.PrimitiveType.Utf8
195204
# Json
205+
elif isinstance(type_, types.YqlDate32):
206+
ydb_type = ydb.PrimitiveType.Date32
207+
elif isinstance(type_, types.YqlTimestamp64):
208+
ydb_type = ydb.PrimitiveType.Timestamp64
209+
elif isinstance(type_, types.YqlDateTime64):
210+
ydb_type = ydb.PrimitiveType.Datetime64
196211
elif isinstance(type_, sa.DATETIME):
197212
ydb_type = ydb.PrimitiveType.Datetime
198213
elif isinstance(type_, sa.TIMESTAMP):

ydb_sqlalchemy/sqlalchemy/datetime_types.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,39 @@ def process(value: Optional[datetime.datetime]) -> Optional[int]:
3636
return int(value.timestamp())
3737

3838
return process
39+
40+
41+
class YqlDate32(YqlDate):
42+
__visit_name__ = "date32"
43+
44+
def literal_processor(self, dialect):
45+
parent = super().literal_processor(dialect)
46+
47+
def process(value):
48+
return f"Date32({parent(value)})"
49+
50+
return process
51+
52+
53+
class YqlTimestamp64(YqlTimestamp):
54+
__visit_name__ = "timestamp64"
55+
56+
def literal_processor(self, dialect):
57+
parent = super().literal_processor(dialect)
58+
59+
def process(value):
60+
return f"Timestamp64({parent(value)})"
61+
62+
return process
63+
64+
65+
class YqlDateTime64(YqlDateTime):
66+
__visit_name__ = "datetime64"
67+
68+
def literal_processor(self, dialect):
69+
parent = super().literal_processor(dialect)
70+
71+
def process(value):
72+
return f"DateTime64({parent(value)})"
73+
74+
return process

ydb_sqlalchemy/sqlalchemy/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from sqlalchemy import ARRAY, exc, types
1212
from sqlalchemy.sql import type_api
1313

14-
from .datetime_types import YqlDate, YqlDateTime, YqlTimestamp # noqa: F401
14+
from .datetime_types import YqlDate, YqlDateTime, YqlTimestamp, YqlDate32, YqlTimestamp64, YqlDateTime64 # noqa: F401
1515
from .json import YqlJSON # noqa: F401
1616

1717

0 commit comments

Comments
 (0)