Skip to content

Commit 996b251

Browse files
authored
BREAKING CHANGE: Nanosecond timestamp support (#113)
1 parent 693976a commit 996b251

File tree

14 files changed

+278
-115
lines changed

14 files changed

+278
-115
lines changed

.bumpversion.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tool.bumpversion]
2-
current_version = "3.0.0"
2+
current_version = "4.0.0"
33
commit = false
44
tag = false
55

CHANGELOG.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,48 @@ Changelog
55

66
=========
77

8+
4.0.0 (2025-10-17)
9+
------------------
10+
11+
New Breaking Change Feature
12+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
13+
14+
From QuestDB 9.1.0 onwards you can use ``CREATE TABLE`` SQL statements with
15+
``TIMESTAMP_NS`` column types, or rely on column auto-creation.
16+
17+
This client release adds support for sending nanoseconds timestamps to the
18+
server without loss of precision.
19+
20+
This release does not introduce new APIs, instead enhancing the sender/buffer's
21+
``.row()`` API to additionally accept nanosecond precision.
22+
23+
.. code-block:: python
24+
25+
conf = 'http::addr=localhost:9000;'
26+
# or `conf = 'tcp::addr=localhost:9009;protocol_version=2;'`
27+
with Sender.from_conf(conf) as sender:
28+
sender.row(
29+
'trade_executions',
30+
symbols={
31+
'product': 'VOD.L',
32+
'parent_order': '65d1ba36-390e-49a2-93e3-a05ef004b5ff'
33+
'side': 'buy'},
34+
columns={
35+
'order_sent': TimestampNanos(1759246702031355012)},
36+
at=TimestampNanos(1759246702909423071))
37+
38+
If you're using dataframes, nanosecond timestamps are now also transferred with
39+
full precision.
40+
41+
The change is backwards compatible with older QuestDB releases which will simply
42+
continue using the ``TIMESTAMP`` column, even when nanoseconds are specified in
43+
the client.
44+
45+
This is a breaking change because it introduces new breaking timestamp
46+
`column auto-creation <https://questdb.com/docs/reference/api/ilp/overview/#table-and-column-auto-creation>`
47+
behaviour. For full details and upgrade advice, see the
48+
`nanosecond PR on GitHub <https://github.com/questdb/py-questdb-client/pull/113>`_.
49+
850
3.0.0 (2025-07-07)
951
------------------
1052

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ and full-connection encryption with
1818
Install
1919
=======
2020

21-
The latest version of the library is **3.0.0** (`changelog <https://py-questdb-client.readthedocs.io/en/latest/changelog.html>`_).
21+
The latest version of the library is 4.0.0 (`changelog <https://py-questdb-client.readthedocs.io/en/latest/changelog.html>`_).
2222

2323
::
2424

ci/run_tests_pipeline.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ stages:
2828
pool:
2929
name: $(poolName)
3030
vmImage: $(imageName)
31-
timeoutInMinutes: 45
31+
timeoutInMinutes: 90
3232
steps:
3333
- checkout: self
3434
fetchDepth: 1
@@ -63,18 +63,18 @@ stages:
6363
- script: python3 proj.py test 1
6464
displayName: "Test vs released"
6565
env:
66-
JAVA_HOME: $(JAVA_HOME_11_X64)
66+
JAVA_HOME: $(JAVA_HOME_17_X64)
6767
- script: python3 proj.py test 1
6868
displayName: "Test vs master"
6969
env:
70-
JAVA_HOME: $(JAVA_HOME_11_X64)
70+
JAVA_HOME: $(JAVA_HOME_17_X64)
7171
QDB_REPO_PATH: './questdb'
7272
condition: eq(variables.vsQuestDbMaster, true)
7373
- job: TestsAgainstVariousNumpyVersion1x
7474
pool:
7575
name: "Azure Pipelines"
7676
vmImage: "ubuntu-latest"
77-
timeoutInMinutes: 45
77+
timeoutInMinutes: 90
7878
steps:
7979
- checkout: self
8080
fetchDepth: 1
@@ -98,7 +98,7 @@ stages:
9898
pool:
9999
name: "Azure Pipelines"
100100
vmImage: "ubuntu-latest"
101-
timeoutInMinutes: 45
101+
timeoutInMinutes: 90
102102
steps:
103103
- checkout: self
104104
fetchDepth: 1

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
year = '2024'
2929
author = 'QuestDB'
3030
copyright = '{0}, {1}'.format(year, author)
31-
version = release = '3.0.0'
31+
version = release = '4.0.0'
3232

3333
github_repo_url = 'https://github.com/questdb/py-questdb-client'
3434

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# See: https://packaging.python.org/en/latest/specifications/declaring-project-metadata/
33
name = "questdb"
44
requires-python = ">=3.9"
5-
version = "3.0.0"
5+
version = "4.0.0"
66
description = "QuestDB client library for Python"
77
readme = "README.rst"
88
classifiers = [
@@ -65,6 +65,10 @@ skip = [
6565
# Skip all 32-bit builds, except for Windows.
6666
# Those builds are named `*win32*` in cibuildwheel.
6767
"*i686*",
68+
69+
# Skip Python 3.14 builds until the dependencies catch up
70+
"cp314-*",
71+
"cp314t-*"
6872
]
6973

7074
# [tool.cibuildwheel.windows]

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def readme():
171171

172172
setup(
173173
name='questdb',
174-
version='3.0.0',
174+
version='4.0.0',
175175
platforms=['any'],
176176
python_requires='>=3.8',
177177
install_requires=[],

src/questdb/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '3.0.0'
1+
__version__ = '4.0.0'

src/questdb/ingress.pyx

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ cnp.import_array()
104104
# This value is automatically updated by the `bump2version` tool.
105105
# If you need to update it, also update the search definition in
106106
# .bumpversion.cfg.
107-
VERSION = '3.0.0'
107+
VERSION = '4.0.0'
108108

109109
WARN_HIGH_RECONNECTS = True
110110

@@ -648,7 +648,7 @@ cdef class SenderTransaction:
648648
symbols: Optional[Dict[str, Optional[str]]]=None,
649649
columns: Optional[Dict[
650650
str,
651-
Union[None, bool, int, float, str, TimestampMicros, datetime.datetime, numpy.ndarray]]
651+
Union[None, bool, int, float, str, TimestampMicros, TimestampNanos, datetime.datetime, numpy.ndarray]]
652652
]=None,
653653
at: Union[ServerTimestampType, TimestampNanos, datetime.datetime]):
654654
"""
@@ -962,12 +962,18 @@ cdef class Buffer:
962962
if not line_sender_buffer_column_str(self._impl, c_name, c_value, &err):
963963
raise c_err_to_py(err)
964964

965-
cdef inline void_int _column_ts(
965+
cdef inline void_int _column_ts_micros(
966966
self, line_sender_column_name c_name, TimestampMicros ts) except -1:
967967
cdef line_sender_error* err = NULL
968968
if not line_sender_buffer_column_ts_micros(self._impl, c_name, ts._value, &err):
969969
raise c_err_to_py(err)
970970

971+
cdef inline void_int _column_ts_nanos(
972+
self, line_sender_column_name c_name, TimestampNanos ts) except -1:
973+
cdef line_sender_error* err = NULL
974+
if not line_sender_buffer_column_ts_nanos(self._impl, c_name, ts._value, &err):
975+
raise c_err_to_py(err)
976+
971977
cdef inline void_int _column_numpy(
972978
self, line_sender_column_name c_name, cnp.ndarray arr) except -1:
973979
if cnp.PyArray_TYPE(arr) != cnp.NPY_FLOAT64:
@@ -1004,6 +1010,8 @@ cdef class Buffer:
10041010
cdef inline void_int _column_dt(
10051011
self, line_sender_column_name c_name, cp_datetime dt) except -1:
10061012
cdef line_sender_error* err = NULL
1013+
# We limit ourselves to micros, since this is the maxium precision
1014+
# exposed by the datetime library in Python.
10071015
if not line_sender_buffer_column_ts_micros(
10081016
self._impl, c_name, datetime_to_micros(dt), &err):
10091017
raise c_err_to_py(err)
@@ -1020,7 +1028,9 @@ cdef class Buffer:
10201028
elif PyUnicode_CheckExact(<PyObject*>value):
10211029
self._column_str(c_name, value)
10221030
elif isinstance(value, TimestampMicros):
1023-
self._column_ts(c_name, value)
1031+
self._column_ts_micros(c_name, value)
1032+
elif isinstance(value, TimestampNanos):
1033+
self._column_ts_nanos(c_name, value)
10241034
elif PyArray_CheckExact(<PyObject *> value):
10251035
self._column_numpy(c_name, value)
10261036
elif isinstance(value, cp_datetime):
@@ -1045,15 +1055,20 @@ cdef class Buffer:
10451055
if sender != NULL:
10461056
may_flush_on_row_complete(self, <Sender><object>sender)
10471057

1048-
cdef inline void_int _at_ts(self, TimestampNanos ts) except -1:
1058+
cdef inline void_int _at_ts_us(self, TimestampMicros ts) except -1:
1059+
cdef line_sender_error* err = NULL
1060+
if not line_sender_buffer_at_micros(self._impl, ts._value, &err):
1061+
raise c_err_to_py(err)
1062+
1063+
cdef inline void_int _at_ts_ns(self, TimestampNanos ts) except -1:
10491064
cdef line_sender_error* err = NULL
10501065
if not line_sender_buffer_at_nanos(self._impl, ts._value, &err):
10511066
raise c_err_to_py(err)
10521067

10531068
cdef inline void_int _at_dt(self, cp_datetime dt) except -1:
1054-
cdef int64_t value = datetime_to_nanos(dt)
1069+
cdef int64_t value = datetime_to_micros(dt)
10551070
cdef line_sender_error* err = NULL
1056-
if not line_sender_buffer_at_nanos(self._impl, value, &err):
1071+
if not line_sender_buffer_at_micros(self._impl, value, &err):
10571072
raise c_err_to_py(err)
10581073

10591074
cdef inline void_int _at_now(self) except -1:
@@ -1064,8 +1079,10 @@ cdef class Buffer:
10641079
cdef inline void_int _at(self, object ts) except -1:
10651080
if ts is None:
10661081
self._at_now()
1082+
elif isinstance(ts, TimestampMicros):
1083+
self._at_ts_us(ts)
10671084
elif isinstance(ts, TimestampNanos):
1068-
self._at_ts(ts)
1085+
self._at_ts_ns(ts)
10691086
elif isinstance(ts, cp_datetime):
10701087
self._at_dt(ts)
10711088
else:
@@ -1115,7 +1132,7 @@ cdef class Buffer:
11151132
symbols: Optional[Dict[str, Optional[str]]]=None,
11161133
columns: Optional[Dict[
11171134
str,
1118-
Union[None, bool, int, float, str, TimestampMicros, datetime.datetime, numpy.ndarray]]
1135+
Union[None, bool, int, float, str, TimestampMicros, TimestampNanos, datetime.datetime, numpy.ndarray]]
11191136
]=None,
11201137
at: Union[ServerTimestampType, TimestampNanos, datetime.datetime]):
11211138
"""

0 commit comments

Comments
 (0)