Skip to content

fix: Python floats and ints are now transmitted with 64-bit precision instead of 32. #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 16, 2022
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
31 changes: 16 additions & 15 deletions src/questdb/ingress.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@
API for fast data ingestion into QuestDB.
"""

from libc.stdint cimport uint8_t, int64_t
from libc.stdint cimport uint8_t, uint64_t, int64_t
from cpython.datetime cimport datetime
from cpython.bool cimport bool, PyBool_Check
from cpython.weakref cimport PyWeakref_NewRef, PyWeakref_GetObject
from cpython.object cimport PyObject
from cpython.float cimport PyFloat_Check
from cpython.int cimport PyInt_Check
from cpython.unicode cimport PyUnicode_Check

from .line_sender cimport *

Expand Down Expand Up @@ -518,16 +521,14 @@ cdef class Buffer:
return 0

cdef inline int _column_i64(
self, line_sender_column_name c_name, int value) except -1:
# TODO: Generally audit for int overflows this in the whole codebase.
# We pretty certainly have one here :-).
self, line_sender_column_name c_name, int64_t value) except -1:
cdef line_sender_error* err = NULL
if not line_sender_buffer_column_i64(self._impl, c_name, value, &err):
raise c_err_to_py(err)
return 0

cdef inline int _column_f64(
self, line_sender_column_name c_name, float value) except -1:
self, line_sender_column_name c_name, double value) except -1:
cdef line_sender_error* err = NULL
if not line_sender_buffer_column_f64(self._impl, c_name, value, &err):
raise c_err_to_py(err)
Expand All @@ -545,7 +546,7 @@ cdef class Buffer:
cdef inline int _column_ts(
self, line_sender_column_name c_name, TimestampMicros ts) except -1:
cdef line_sender_error* err = NULL
if not line_sender_buffer_column_ts(self._impl, c_name, ts.value, &err):
if not line_sender_buffer_column_ts(self._impl, c_name, ts._value, &err):
raise c_err_to_py(err)
return 0

Expand All @@ -562,11 +563,11 @@ cdef class Buffer:
cdef bytes owner_name = str_to_column_name(name, &c_name)
if PyBool_Check(value):
return self._column_bool(c_name, value)
elif isinstance(value, int):
elif PyInt_Check(value):
return self._column_i64(c_name, value)
elif isinstance(value, float):
elif PyFloat_Check(value):
return self._column_f64(c_name, value)
elif isinstance(value, str):
elif PyUnicode_Check(value):
return self._column_str(c_name, value)
elif isinstance(value, TimestampMicros):
return self._column_ts(c_name, value)
Expand All @@ -593,7 +594,7 @@ cdef class Buffer:

cdef inline int _at_ts(self, TimestampNanos ts) except -1:
cdef line_sender_error* err = NULL
if not line_sender_buffer_at(self._impl, ts.value, &err):
if not line_sender_buffer_at(self._impl, ts._value, &err):
raise c_err_to_py(err)
return 0

Expand Down Expand Up @@ -1078,9 +1079,9 @@ cdef class Sender:
str interface=None,
tuple auth=None,
object tls=False,
int read_timeout=15000,
int init_capacity=65536, # 64KiB
int max_name_len=127,
uint64_t read_timeout=15000,
uint64_t init_capacity=65536, # 64KiB
uint64_t max_name_len=127,
object auto_flush=64512): # 63KiB
cdef line_sender_error* err = NULL

Expand Down Expand Up @@ -1118,9 +1119,9 @@ cdef class Sender:
self._impl = NULL
self._buffer = None

if isinstance(port, int):
if PyInt_Check(port):
port_str = str(port)
elif isinstance(port, str):
elif PyUnicode_Check(port):
port_str = port
else:
raise TypeError(
Expand Down
40 changes: 40 additions & 0 deletions test/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,36 @@ def test_unicode(self):
buf.row('tbl1', symbols={'questdb1': '❤️'}, columns={'questdb2': '❤️'})
self.assertEqual(str(buf), 'tbl1,questdb1=❤️ questdb2="❤️"\n')

def test_float(self):
buf = qi.Buffer()
buf.row('tbl1', columns={'num': 1.2345678901234567})
self.assertEqual(str(buf), f'tbl1 num=1.2345678901234567\n')

def test_int_range(self):
buf = qi.Buffer()
buf.row('tbl1', columns={'num': 0})
self.assertEqual(str(buf), f'tbl1 num=0i\n')
buf.clear()

# 32-bit int range.
buf.row('tbl1', columns={'min': -2**31, 'max': 2**31-1})
self.assertEqual(str(buf), f'tbl1 min=-2147483648i,max=2147483647i\n')
buf.clear()

# 64-bit int range.
buf.row('tbl1', columns={'min': -2**63, 'max': 2**63-1})
self.assertEqual(str(buf), f'tbl1 min=-9223372036854775808i,max=9223372036854775807i\n')
buf.clear()

# Overflow.
with self.assertRaises(OverflowError):
buf.row('tbl1', columns={'num': 2**63})

# Underflow.
with self.assertRaises(OverflowError):
buf.row('tbl1', columns={'num': -2**63-1})



class TestSender(unittest.TestCase):
def test_basic(self):
Expand Down Expand Up @@ -343,6 +373,16 @@ def test_connect_after_close(self):
with self.assertRaises(qi.IngressError):
sender.connect()

def test_bad_init_args(self):
with self.assertRaises(OverflowError):
qi.Sender(host='localhost', port=9009, read_timeout=-1)

with self.assertRaises(OverflowError):
qi.Sender(host='localhost', port=9009, init_capacity=-1)

with self.assertRaises(OverflowError):
qi.Sender(host='localhost', port=9009, max_name_len=-1)


if __name__ == '__main__':
unittest.main()