Skip to content
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

intervals support (closes #30) #33

Merged
merged 1 commit into from
May 9, 2024
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## v2.3.0
**New features:**
* Added support for [interval types](https://www.tarantool.io/en/doc/latest/reference/reference_lua/datetime/interval_object/) [#30](https://github.com/igorcoding/asynctnt/issues/30)


## v2.2.0
**New features:**
* Implemented ability to send update/upsert requests with field names when schema is disabled (`fetch_schema=False`) and when fields are not found in the schema (good example of this case is using json path like `data.inner1.inner2.key1` as a key)
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ Documentation is available [here](https://igorcoding.github.io/asynctnt).
* Full support for [SQL](https://www.tarantool.io/en/doc/latest/tutorials/sql_tutorial/),
including [prepared statements](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_sql/prepare/).
* Support for [interactive transaction](https://www.tarantool.io/en/doc/latest/book/box/atomic/txn_mode_mvcc/) via Tarantool streams.
* Support of `Decimal`, `UUID` and `datetime` types natively.
* Support of `Decimal`, `UUID`,`datetime` types natively.
* Support for [interval types](https://www.tarantool.io/en/doc/latest/reference/reference_lua/datetime/interval_object/).
* Support for parsing [custom errors](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_error/new/).
* **Schema fetching** on connection establishment, so you can use spaces and
indexes names rather than their ids, and **auto refetching** if schema in
Expand Down
4 changes: 3 additions & 1 deletion asynctnt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

from .connection import Connection, connect
from .iproto.protocol import (
Adjust,
Db,
Field,
IProtoError,
IProtoErrorStackFrame,
Iterator,
Metadata,
MPInterval,
PushIterator,
Response,
Schema,
Expand All @@ -16,4 +18,4 @@
TarantoolTuple,
)

__version__ = "2.2.0"
__version__ = "2.3.0"
1 change: 1 addition & 0 deletions asynctnt/iproto/buffer.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ cdef class WriteBuffer:
cdef char *mp_encode_decimal(self, char *p, object value) except NULL
cdef char *mp_encode_uuid(self, char *p, object value) except NULL
cdef char *mp_encode_datetime(self, char *p, object value) except NULL
cdef char *mp_encode_interval(self, char *p, MPInterval value) except NULL
cdef char *mp_encode_array(self, char *p, uint32_t len) except NULL
cdef char *mp_encode_map(self, char *p, uint32_t len) except NULL
cdef char *mp_encode_list(self, char *p, list arr) except NULL
Expand Down
17 changes: 17 additions & 0 deletions asynctnt/iproto/buffer.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,20 @@ cdef class WriteBuffer:
self._length += (p - begin)
return p

cdef char *mp_encode_interval(self, char *p, MPInterval value) except NULL:
cdef:
char *begin
char *data_p
uint32_t length

length = interval_len(value)
p = begin = self._ensure_allocated(p, mp_sizeof_ext(length))
p = mp_encode_extl(p, tarantool.MP_INTERVAL, length)
p = interval_encode(p, value)

self._length += (p - begin)
return p

cdef char *mp_encode_array(self, char *p, uint32_t len) except NULL:
cdef char *begin
p = begin = self._ensure_allocated(p, mp_sizeof_array(len))
Expand Down Expand Up @@ -406,6 +420,9 @@ cdef class WriteBuffer:
elif isinstance(o, datetime):
return self.mp_encode_datetime(p, o)

elif isinstance(o, MPInterval):
return self.mp_encode_interval(p, <MPInterval> o)

elif isinstance(o, Decimal):
return self.mp_encode_decimal(p, o)

Expand Down
4 changes: 4 additions & 0 deletions asynctnt/iproto/cmsgpuck.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ cdef extern from "../../third_party/msgpuck/msgpuck.h":
cdef char *mp_store_u32(char *data, uint32_t val)
cdef char *mp_store_u64(char *data, uint64_t val)

cdef ptrdiff_t mp_check_uint(const char *cur, const char *end)
cdef ptrdiff_t mp_check_int(const char *cur, const char *end)

cdef mp_type mp_typeof(const char c)

cdef uint32_t mp_sizeof_array(uint32_t size)
Expand All @@ -43,6 +46,7 @@ cdef extern from "../../third_party/msgpuck/msgpuck.h":
cdef uint32_t mp_sizeof_int(int64_t num)
cdef char *mp_encode_int(char *data, int64_t num)
cdef int64_t mp_decode_int(const char **data)
cdef int mp_read_int64(const char **data, int64_t *ret)

cdef uint32_t mp_sizeof_float(float num)
cdef char *mp_encode_float(char *data, float num)
Expand Down
32 changes: 0 additions & 32 deletions asynctnt/iproto/ext.pxd

This file was deleted.

18 changes: 18 additions & 0 deletions asynctnt/iproto/ext/datetime.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from cpython.datetime cimport datetime
from libc.stdint cimport int16_t, int32_t, int64_t, uint32_t


cdef struct IProtoDateTime:
int64_t seconds
int32_t nsec
int16_t tzoffset
int16_t tzindex

cdef void datetime_zero(IProtoDateTime *dt)
cdef uint32_t datetime_len(IProtoDateTime *dt)
cdef char *datetime_encode(char *p, IProtoDateTime *dt) except NULL
cdef int datetime_decode(const char ** p,
uint32_t length,
IProtoDateTime *dt) except -1
cdef void datetime_from_py(datetime ob, IProtoDateTime *dt)
cdef object datetime_to_py(IProtoDateTime *dt)
87 changes: 87 additions & 0 deletions asynctnt/iproto/ext/datetime.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
cimport cpython.datetime
from cpython.datetime cimport PyDateTimeAPI, datetime, datetime_tzinfo, timedelta_new
from libc.stdint cimport uint32_t
from libc.string cimport memcpy


cdef inline void datetime_zero(IProtoDateTime *dt):
dt.seconds = 0
dt.nsec = 0
dt.tzoffset = 0
dt.tzindex = 0

cdef inline uint32_t datetime_len(IProtoDateTime *dt):
cdef uint32_t sz
sz = sizeof(int64_t)
if dt.nsec != 0 or dt.tzoffset != 0 or dt.tzindex != 0:
return sz + DATETIME_TAIL_SZ
return sz

cdef char *datetime_encode(char *p, IProtoDateTime *dt) except NULL:
store_u64(p, dt.seconds)
p += sizeof(dt.seconds)
if dt.nsec != 0 or dt.tzoffset != 0 or dt.tzindex != 0:
memcpy(p, &dt.nsec, DATETIME_TAIL_SZ)
p += DATETIME_TAIL_SZ
return p

cdef int datetime_decode(
const char ** p,
uint32_t length,
IProtoDateTime *dt
) except -1:
delta = None
tz = None

dt.seconds = load_u64(p[0])
p[0] += sizeof(dt.seconds)
length -= sizeof(dt.seconds)

if length == 0:
return 0

if length != DATETIME_TAIL_SZ:
raise ValueError("invalid datetime size. got {} extra bytes".format(
length
))

dt.nsec = load_u32(p[0])
p[0] += 4
dt.tzoffset = load_u16(p[0])
p[0] += 2
dt.tzindex = load_u16(p[0])
p[0] += 2

cdef void datetime_from_py(datetime ob, IProtoDateTime *dt):
cdef:
double ts
int offset
ts = <double> ob.timestamp()
dt.seconds = <int64_t> ts
dt.nsec = <int32_t> ((ts - <double> dt.seconds) * 1000000) * 1000
if dt.nsec < 0:
# correction for negative dates
dt.seconds -= 1
dt.nsec += 1000000000

if datetime_tzinfo(ob) is not None:
offset = ob.utcoffset().total_seconds()
dt.tzoffset = <int16_t> (offset / 60)

cdef object datetime_to_py(IProtoDateTime *dt):
cdef:
double timestamp
object tz

tz = None

if dt.tzoffset != 0:
delta = timedelta_new(0, <int> dt.tzoffset * 60, 0)
tz = timezone_new(delta)

timestamp = dt.seconds + (<double> dt.nsec) / 1e9
return PyDateTimeAPI.DateTime_FromTimestamp(
<PyObject *>PyDateTimeAPI.DateTimeType,
(timestamp,) if tz is None else (timestamp, tz),
NULL,
)
14 changes: 14 additions & 0 deletions asynctnt/iproto/ext/decimal.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from libc cimport math
from libc.stdint cimport uint8_t, uint32_t


cdef inline uint32_t bcd_len(uint32_t digits_len):
return <uint32_t> math.floor(digits_len / 2) + 1

cdef uint32_t decimal_len(int exponent, uint32_t digits_count)
cdef char *decimal_encode(char *p,
uint32_t digits_count,
uint8_t sign,
tuple digits,
int exponent) except NULL
cdef object decimal_decode(const char ** p, uint32_t length)
91 changes: 0 additions & 91 deletions asynctnt/iproto/ext.pyx → asynctnt/iproto/ext/decimal.pyx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
cimport cpython.datetime
from cpython.datetime cimport PyDateTimeAPI, datetime, datetime_tzinfo, timedelta_new
from libc.stdint cimport uint32_t
from libc.string cimport memcpy

from decimal import Decimal
from uuid import UUID


cdef uint32_t decimal_len(int exponent, uint32_t digits_count):
Expand Down Expand Up @@ -127,90 +123,3 @@ cdef object decimal_decode(const char ** p, uint32_t length):
p[0] += length

return Decimal((<object> <int> sign, digits, <object> exponent))

cdef object uuid_decode(const char ** p, uint32_t length):
data = cpython.bytes.PyBytes_FromStringAndSize(p[0], length)
p[0] += length
return UUID(bytes=data)

cdef inline void datetime_zero(IProtoDateTime *dt):
dt.seconds = 0
dt.nsec = 0
dt.tzoffset = 0
dt.tzindex = 0

cdef inline uint32_t datetime_len(IProtoDateTime *dt):
cdef uint32_t sz
sz = sizeof(int64_t)
if dt.nsec != 0 or dt.tzoffset != 0 or dt.tzindex != 0:
return sz + DATETIME_TAIL_SZ
return sz

cdef char *datetime_encode(char *p, IProtoDateTime *dt) except NULL:
store_u64(p, dt.seconds)
p += sizeof(dt.seconds)
if dt.nsec != 0 or dt.tzoffset != 0 or dt.tzindex != 0:
memcpy(p, &dt.nsec, DATETIME_TAIL_SZ)
p += DATETIME_TAIL_SZ
return p

cdef int datetime_decode(
const char ** p,
uint32_t length,
IProtoDateTime *dt
) except -1:
delta = None
tz = None

dt.seconds = load_u64(p[0])
p[0] += sizeof(dt.seconds)
length -= sizeof(dt.seconds)

if length == 0:
return 0

if length != DATETIME_TAIL_SZ:
raise ValueError("invalid datetime size. got {} extra bytes".format(
length
))

dt.nsec = load_u32(p[0])
p[0] += 4
dt.tzoffset = load_u16(p[0])
p[0] += 2
dt.tzindex = load_u16(p[0])
p[0] += 2

cdef void datetime_from_py(datetime ob, IProtoDateTime *dt):
cdef:
double ts
int offset
ts = <double> ob.timestamp()
dt.seconds = <int64_t> ts
dt.nsec = <int32_t> ((ts - <double> dt.seconds) * 1000000) * 1000
if dt.nsec < 0:
# correction for negative dates
dt.seconds -= 1
dt.nsec += 1000000000

if datetime_tzinfo(ob) is not None:
offset = ob.utcoffset().total_seconds()
dt.tzoffset = <int16_t> (offset / 60)

cdef object datetime_to_py(IProtoDateTime *dt):
cdef:
double timestamp
object tz

tz = None

if dt.tzoffset != 0:
delta = timedelta_new(0, <int> dt.tzoffset * 60, 0)
tz = timezone_new(delta)

timestamp = dt.seconds + (<double> dt.nsec) / 1e9
return PyDateTimeAPI.DateTime_FromTimestamp(
<PyObject *>PyDateTimeAPI.DateTimeType,
(timestamp,) if tz is None else (timestamp, tz),
NULL,
)
15 changes: 15 additions & 0 deletions asynctnt/iproto/ext/error.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
cdef class IProtoErrorStackFrame:
cdef:
readonly str error_type
readonly str file
readonly int line
readonly str message
readonly int err_no
readonly int code
readonly dict fields

cdef class IProtoError:
cdef:
readonly list trace

cdef IProtoError iproto_error_decode(const char ** b, bytes encoding)
Loading
Loading