From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id B728B6EC40; Fri, 13 Aug 2021 01:36:23 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org B728B6EC40 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1628807783; bh=e1QxZy66hgCIpkjQXuojmA0huc6N/HwX4cP25u9DJOI=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=yxUADILNcDMxoLaMeh/b0hzgrmOHqJ//Yaf/Ddv84Rr7GKE+vIiD1sJB8cosyJhwS ixJVL54XWqC3MGxkVDx1Xhanu43j6W0bK8WjBmGKONoZxyUWVefuyOf5Evzpuz1qMr hUNa+YaIiiujKhYTXc3WGgI3OuWhIsKQWLh2i3hQ= Received: from smtp52.i.mail.ru (smtp52.i.mail.ru [94.100.177.112]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 437036EC5B for ; Fri, 13 Aug 2021 01:34:20 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 437036EC5B Received: by smtp52.i.mail.ru with esmtpa (envelope-from ) id 1mEJHG-0003Mm-F6; Fri, 13 Aug 2021 01:34:19 +0300 To: imun@tarantool.org, v.shpilevoy@tarantool.org Date: Fri, 13 Aug 2021 01:33:43 +0300 Message-Id: X-Mailer: git-send-email 2.29.2 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-4EC0790: 10 X-7564579A: B8F34718100C35BD X-77F55803: 4F1203BC0FB41BD92087353F0EC44DD9D5AC6413C25DCF08CC98B8FCC5CD86F3182A05F538085040D02DBA55A208899381CFC19CC8AE956E8D558CBC964D1B47C1ACFA769FB98DDA X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE70312E9A300D47E3BEA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F7900637575C0790A70C4B158638F802B75D45FF36EB9D2243A4F8B5A6FCA7DBDB1FC311F39EFFDF887939037866D6147AF826D873174B6DEEB85A4DBBB4C16F530F65AC117882F4460429724CE54428C33FAD305F5C1EE8F4F765FCAA867293B0326636D2E47CDBA5A96583BD4B6F7A4D31EC0BC014FD901B82EE079FA2833FD35BB23D27C277FBC8AE2E8BF1175FABE1C0F9B6A471835C12D1D977C4224003CC836476EB9C4185024447017B076A6E789B0E975F5C1EE8F4F765FC374CA075D3C6FB6CD81D268191BDAD3DBD4B6F7A4D31EC0BEA7A3FFF5B025636D81D268191BDAD3D78DA827A17800CE77A7A9E5ACE0DF18EEC76A7562686271EEC990983EF5C03292E808ACE2090B5E14AD6D5ED66289B5259CC434672EE63711DD303D21008E298D5E8D9A59859A8B6B372FE9A2E580EFC725E5C173C3A84C3EBF4D8D28E8B690335872C767BF85DA2F004C90652538430E4A6367B16DE6309 X-B7AD71C0: AC4F5C86D027EB782CDD5689AFBDA7A213B5FB47DCBC3458F0AFF96BAACF4158235E5A14AD4A4A4625E192CAD1D9E79D94463893BF8742D0B62496D7B8854719 X-C1DE0DAB: C20DE7B7AB408E4181F030C43753B8186998911F362727C414F749A5E30D975C69415AB31670C86C07A81AD19D501ED5332557F4C8993B6D9C2B6934AE262D3EE7EAB7254005DCED7532B743992DF240BDC6A1CF3F042BAD6DF99611D93F60EF246B04B1A760E086699F904B3F4130E343918A1A30D5E7FCCB5012B2E24CD356 X-C8649E89: 4E36BF7865823D7055A7F0CF078B5EC49A30900B95165D34A5112A9AECFE11B1785CAECCB5EF51AAA0A4AE854C26118DFBCE6B3CFD5AA82E0E8F3BA90FDD16551D7E09C32AA3244C7BA637C3BD4AF1E67A26BC49AF2F26D0CE0B41342B755BCD8D5DD81C2BAB7D1D X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu530nj6fImhcD4MUrOEAnl0W826KZ9Q+tr5ycPtXkTV4k65bRjmOUUP8cvGozZ33TWg5HZplvhhXbhDGzqmQDTd6OAevLeAnq3Ra9uf7zvY2zzsIhlcp/Y7m53TZgf2aB4JOg4gkr2bioj0dLV0c3jbkz1ScfqcpDsmA== X-Mailru-Sender: B5B6A6EBBD94DAD86A422A53D9D37A7D23E429C30DF02D8E8E8E1F3B5FC983BFE1AC4DDBCF91F5B71EC9E4A2C82A33BC8C24925A86E657CE0C70AEE3C9A96FBAB3D7EE8ED63280BE112434F685709FCF0DA7A0AF5A3A8387 X-Mras: Ok Subject: [Tarantool-patches] [PATCH v4 4/7] box, datetime: messagepack support for datetime X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Timur Safin via Tarantool-patches Reply-To: Timur Safin Cc: tarantool-patches@dev.tarantool.org Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" Serialize datetime_t as newly introduced MP_EXT type. It saves 1 required integer field and upto 2 optional unsigned fields in very compact fashion. - secs is required field; - but nsec, offset are both optional; * json, yaml serialization formats, lua output mode supported; * exported symbols for datetime messagepack size calculations so they are available for usage on Lua side. Part of #5941 Part of #5946 --- extra/exports | 2 + src/box/field_def.c | 35 +++--- src/box/field_def.h | 1 + src/box/lua/serialize_lua.c | 7 +- src/box/msgpack.c | 7 +- src/box/tuple_compare.cc | 20 ++++ src/lib/core/CMakeLists.txt | 4 +- src/lib/core/datetime.c | 9 ++ src/lib/core/datetime.h | 20 ++++ src/lib/core/mp_datetime.c | 189 ++++++++++++++++++++++++++++++ src/lib/core/mp_datetime.h | 89 ++++++++++++++ src/lib/core/mp_extension_types.h | 1 + src/lib/mpstream/mpstream.c | 11 ++ src/lib/mpstream/mpstream.h | 4 + src/lua/msgpack.c | 12 ++ src/lua/msgpackffi.lua | 18 +++ src/lua/serializer.c | 4 + src/lua/serializer.h | 2 + src/lua/utils.c | 1 - test/unit/datetime.c | 125 +++++++++++++++++++- third_party/lua-cjson/lua_cjson.c | 8 ++ third_party/lua-yaml/lyaml.cc | 6 +- 22 files changed, 554 insertions(+), 21 deletions(-) create mode 100644 src/lib/core/mp_datetime.c create mode 100644 src/lib/core/mp_datetime.h diff --git a/extra/exports b/extra/exports index 2437e175c..345ffbf25 100644 --- a/extra/exports +++ b/extra/exports @@ -397,6 +397,7 @@ mp_decode_uint mp_encode_array mp_encode_bin mp_encode_bool +mp_encode_datetime mp_encode_decimal mp_encode_double mp_encode_float @@ -413,6 +414,7 @@ mp_next mp_next_slowpath mp_parser_hint mp_sizeof_array +mp_sizeof_datetime mp_sizeof_decimal mp_sizeof_str mp_sizeof_uuid diff --git a/src/box/field_def.c b/src/box/field_def.c index 51acb8025..2682a42ee 100644 --- a/src/box/field_def.c +++ b/src/box/field_def.c @@ -72,6 +72,7 @@ const uint32_t field_mp_type[] = { /* [FIELD_TYPE_UUID] = */ 0, /* only MP_UUID is supported */ /* [FIELD_TYPE_ARRAY] = */ 1U << MP_ARRAY, /* [FIELD_TYPE_MAP] = */ (1U << MP_MAP), + /* [FIELD_TYPE_DATETIME] = */ 0, /* only MP_DATETIME is supported */ }; const uint32_t field_ext_type[] = { @@ -83,11 +84,13 @@ const uint32_t field_ext_type[] = { /* [FIELD_TYPE_INTEGER] = */ 0, /* [FIELD_TYPE_BOOLEAN] = */ 0, /* [FIELD_TYPE_VARBINARY] = */ 0, - /* [FIELD_TYPE_SCALAR] = */ (1U << MP_DECIMAL) | (1U << MP_UUID), + /* [FIELD_TYPE_SCALAR] = */ (1U << MP_DECIMAL) | (1U << MP_UUID) | + (1U << MP_DATETIME), /* [FIELD_TYPE_DECIMAL] = */ 1U << MP_DECIMAL, /* [FIELD_TYPE_UUID] = */ 1U << MP_UUID, /* [FIELD_TYPE_ARRAY] = */ 0, /* [FIELD_TYPE_MAP] = */ 0, + /* [FIELD_TYPE_DATETIME] = */ 1U << MP_DATETIME, }; const char *field_type_strs[] = { @@ -104,6 +107,7 @@ const char *field_type_strs[] = { /* [FIELD_TYPE_UUID] = */ "uuid", /* [FIELD_TYPE_ARRAY] = */ "array", /* [FIELD_TYPE_MAP] = */ "map", + /* [FIELD_TYPE_DATETIME] = */ "datetime", }; const char *on_conflict_action_strs[] = { @@ -128,20 +132,21 @@ field_type_by_name_wrapper(const char *str, uint32_t len) * values can be stored in the j type. */ static const bool field_type_compatibility[] = { - /* ANY UNSIGNED STRING NUMBER DOUBLE INTEGER BOOLEAN VARBINARY SCALAR DECIMAL UUID ARRAY MAP */ -/* ANY */ true, false, false, false, false, false, false, false, false, false, false, false, false, -/* UNSIGNED */ true, true, false, true, false, true, false, false, true, false, false, false, false, -/* STRING */ true, false, true, false, false, false, false, false, true, false, false, false, false, -/* NUMBER */ true, false, false, true, false, false, false, false, true, false, false, false, false, -/* DOUBLE */ true, false, false, true, true, false, false, false, true, false, false, false, false, -/* INTEGER */ true, false, false, true, false, true, false, false, true, false, false, false, false, -/* BOOLEAN */ true, false, false, false, false, false, true, false, true, false, false, false, false, -/* VARBINARY*/ true, false, false, false, false, false, false, true, true, false, false, false, false, -/* SCALAR */ true, false, false, false, false, false, false, false, true, false, false, false, false, -/* DECIMAL */ true, false, false, true, false, false, false, false, true, true, false, false, false, -/* UUID */ true, false, false, false, false, false, false, false, false, false, true, false, false, -/* ARRAY */ true, false, false, false, false, false, false, false, false, false, false, true, false, -/* MAP */ true, false, false, false, false, false, false, false, false, false, false, false, true, + /* ANY UNSIGNED STRING NUMBER DOUBLE INTEGER BOOLEAN VARBINARY SCALAR DECIMAL UUID ARRAY MAP DATETIME */ +/* ANY */ true, false, false, false, false, false, false, false, false, false, false, false, false, false, +/* UNSIGNED */ true, true, false, true, false, true, false, false, true, false, false, false, false, false, +/* STRING */ true, false, true, false, false, false, false, false, true, false, false, false, false, false, +/* NUMBER */ true, false, false, true, false, false, false, false, true, false, false, false, false, false, +/* DOUBLE */ true, false, false, true, true, false, false, false, true, false, false, false, false, false, +/* INTEGER */ true, false, false, true, false, true, false, false, true, false, false, false, false, false, +/* BOOLEAN */ true, false, false, false, false, false, true, false, true, false, false, false, false, false, +/* VARBINARY*/ true, false, false, false, false, false, false, true, true, false, false, false, false, false, +/* SCALAR */ true, false, false, false, false, false, false, false, true, false, false, false, false, false, +/* DECIMAL */ true, false, false, true, false, false, false, false, true, true, false, false, false, false, +/* UUID */ true, false, false, false, false, false, false, false, false, false, true, false, false, false, +/* ARRAY */ true, false, false, false, false, false, false, false, false, false, false, true, false, false, +/* MAP */ true, false, false, false, false, false, false, false, false, false, false, false, true, false, +/* DATETIME */ true, false, false, false, false, false, false, false, true, false, false, false, false, true, }; bool diff --git a/src/box/field_def.h b/src/box/field_def.h index c5cfe5e86..120b2a93d 100644 --- a/src/box/field_def.h +++ b/src/box/field_def.h @@ -63,6 +63,7 @@ enum field_type { FIELD_TYPE_UUID, FIELD_TYPE_ARRAY, FIELD_TYPE_MAP, + FIELD_TYPE_DATETIME, field_type_MAX }; diff --git a/src/box/lua/serialize_lua.c b/src/box/lua/serialize_lua.c index 1f791980f..51855011b 100644 --- a/src/box/lua/serialize_lua.c +++ b/src/box/lua/serialize_lua.c @@ -768,7 +768,7 @@ static int dump_node(struct lua_dumper *d, struct node *nd, int indent) { struct luaL_field *field = &nd->field; - char buf[FPCONV_G_FMT_BUFSIZE]; + char buf[FPCONV_G_FMT_BUFSIZE + 8]; int ltype = lua_type(d->L, -1); const char *str = NULL; size_t len = 0; @@ -861,6 +861,11 @@ dump_node(struct lua_dumper *d, struct node *nd, int indent) str = tt_uuid_str(field->uuidval); len = UUID_STR_LEN; break; + case MP_DATETIME: + nd->mask |= NODE_QUOTE; + str = buf; + len = datetime_to_string(field->dateval, buf, sizeof buf); + break; default: d->err = EINVAL; snprintf(d->err_msg, sizeof(d->err_msg), diff --git a/src/box/msgpack.c b/src/box/msgpack.c index 1723dea4c..12f4fd95a 100644 --- a/src/box/msgpack.c +++ b/src/box/msgpack.c @@ -1,5 +1,5 @@ /* - * Copyright 2020, Tarantool AUTHORS, please see AUTHORS file. + * Copyright 2020-2021, Tarantool AUTHORS, please see AUTHORS file. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following @@ -35,6 +35,7 @@ #include "mp_decimal.h" #include "uuid/mp_uuid.h" #include "mp_error.h" +#include "mp_datetime.h" static int msgpack_fprint_ext(FILE *file, const char **data, int depth) @@ -47,6 +48,8 @@ msgpack_fprint_ext(FILE *file, const char **data, int depth) return mp_fprint_decimal(file, data, len); case MP_UUID: return mp_fprint_uuid(file, data, len); + case MP_DATETIME: + return mp_fprint_datetime(file, data, len); case MP_ERROR: return mp_fprint_error(file, data, depth); default: @@ -65,6 +68,8 @@ msgpack_snprint_ext(char *buf, int size, const char **data, int depth) return mp_snprint_decimal(buf, size, data, len); case MP_UUID: return mp_snprint_uuid(buf, size, data, len); + case MP_DATETIME: + return mp_snprint_datetime(buf, size, data, len); case MP_ERROR: return mp_snprint_error(buf, size, data, depth); default: diff --git a/src/box/tuple_compare.cc b/src/box/tuple_compare.cc index 43cd29ce9..9a69f2a72 100644 --- a/src/box/tuple_compare.cc +++ b/src/box/tuple_compare.cc @@ -36,6 +36,7 @@ #include "lib/core/decimal.h" #include "lib/core/mp_decimal.h" #include "uuid/mp_uuid.h" +#include "lib/core/mp_datetime.h" #include "lib/core/mp_extension_types.h" /* {{{ tuple_compare */ @@ -76,6 +77,7 @@ enum mp_class { MP_CLASS_STR, MP_CLASS_BIN, MP_CLASS_UUID, + MP_CLASS_DATETIME, MP_CLASS_ARRAY, MP_CLASS_MAP, mp_class_max, @@ -99,6 +101,8 @@ static enum mp_class mp_ext_classes[] = { /* .MP_UNKNOWN_EXTENSION = */ mp_class_max, /* unsupported */ /* .MP_DECIMAL = */ MP_CLASS_NUMBER, /* .MP_UUID = */ MP_CLASS_UUID, + /* .MP_ERROR = */ mp_class_max, + /* .MP_DATETIME = */ MP_CLASS_DATETIME, }; static enum mp_class @@ -390,6 +394,19 @@ mp_compare_uuid(const char *field_a, const char *field_b) return memcmp(field_a + 2, field_b + 2, UUID_PACKED_LEN); } +static int +mp_compare_datetime(const char *lhs, const char *rhs) +{ + struct datetime lhs_dt, rhs_dt; + struct datetime *ret; + ret = mp_decode_datetime(&lhs, &lhs_dt); + assert(ret != NULL); + ret = mp_decode_datetime(&rhs, &rhs_dt); + assert(ret != NULL); + (void)ret; + return datetime_compare(&lhs_dt, &rhs_dt); +} + typedef int (*mp_compare_f)(const char *, const char *); static mp_compare_f mp_class_comparators[] = { /* .MP_CLASS_NIL = */ NULL, @@ -398,6 +415,7 @@ static mp_compare_f mp_class_comparators[] = { /* .MP_CLASS_STR = */ mp_compare_str, /* .MP_CLASS_BIN = */ mp_compare_bin, /* .MP_CLASS_UUID = */ mp_compare_uuid, + /* .MP_CLASS_DATETIME=*/ mp_compare_datetime, /* .MP_CLASS_ARRAY = */ NULL, /* .MP_CLASS_MAP = */ NULL, }; @@ -478,6 +496,8 @@ tuple_compare_field(const char *field_a, const char *field_b, return mp_compare_decimal(field_a, field_b); case FIELD_TYPE_UUID: return mp_compare_uuid(field_a, field_b); + case FIELD_TYPE_DATETIME: + return mp_compare_datetime(field_a, field_b); default: unreachable(); return 0; diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt index 8bc776b82..61fc6548f 100644 --- a/src/lib/core/CMakeLists.txt +++ b/src/lib/core/CMakeLists.txt @@ -31,6 +31,7 @@ set(core_sources mp_decimal.c cord_buf.c datetime.c + mp_datetime.c ) if (TARGET_OS_NETBSD) @@ -44,7 +45,8 @@ add_library(core STATIC ${core_sources}) target_link_libraries(core salad small uri decNumber bit ${LIBEV_LIBRARIES} ${LIBEIO_LIBRARIES} ${LIBCORO_LIBRARIES} - ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES}) + ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES} + ${LIBCDT_LIBRARIES}) if (ENABLE_BACKTRACE AND NOT TARGET_OS_DARWIN) target_link_libraries(core gcc_s ${UNWIND_LIBRARIES}) diff --git a/src/lib/core/datetime.c b/src/lib/core/datetime.c index e0300417a..7b65e28db 100644 --- a/src/lib/core/datetime.c +++ b/src/lib/core/datetime.c @@ -165,3 +165,12 @@ datetime_to_string(const struct datetime *date, char *buf, uint32_t len) } #undef ADVANCE +int +datetime_compare(const struct datetime *lhs, const struct datetime *rhs) +{ + int result = COMPARE_RESULT(lhs->secs, rhs->secs); + if (result != 0) + return result; + + return COMPARE_RESULT(lhs->nsec, rhs->nsec); +} diff --git a/src/lib/core/datetime.h b/src/lib/core/datetime.h index 4ec459860..a3c799d7a 100644 --- a/src/lib/core/datetime.h +++ b/src/lib/core/datetime.h @@ -70,6 +70,26 @@ struct datetime_interval { int32_t nsec; }; +/** + * Compare arguments of a datetime type + * @param lhs left datetime argument + * @param rhs right datetime argument + * @retval < 0 if lhs less than rhs + * @retval = 0 if lhs and rhs equal + * @retval > 0 if lhs greater than rhs + */ +int +datetime_compare(const struct datetime *lhs, const struct datetime *rhs); + +/** + * Convert datetime to string using default format + * @param date source datetime value + * @param buf output character buffer + * @param len size ofoutput buffer + */ +int +datetime_to_string(const struct datetime *date, char *buf, uint32_t len); + /** * Convert datetime to string using default asctime format * "Sun Sep 16 01:03:52 1973\n\0" diff --git a/src/lib/core/mp_datetime.c b/src/lib/core/mp_datetime.c new file mode 100644 index 000000000..d0a3e562c --- /dev/null +++ b/src/lib/core/mp_datetime.c @@ -0,0 +1,189 @@ +/* + * Copyright 2021, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "mp_datetime.h" +#include "msgpuck.h" +#include "mp_extension_types.h" + +/* + Datetime MessagePack serialization schema is MP_EXT (0xC7 for 1 byte length) + extension, which creates container of 1 to 3 integers. + + +----+---+-----------+====~~~~~~~====+-----~~~~~~~~-------+....~~~~~~~....+ + |0xC7| 4 |len (uint8)| seconds (int) | nanoseconds (uint) | offset (uint) | + +----+---+-----------+====~~~~~~~====+-----~~~~~~~~-------+....~~~~~~~....+ + + MessagePack extension MP_EXT (0xC7), after 1-byte length, contains: + + - signed integer seconds part (required). Depending on the value of + seconds it may be from 1 to 8 bytes positive or negative integer number; + + - [optional] fraction time in nanoseconds as unsigned integer. + If this value is 0 then it's not saved (unless there is offset field, + as below); + + - [optional] timzeone offset in minutes as unsigned integer. + If this field is 0 then it's not saved. + */ + +static inline uint32_t +mp_sizeof_Xint(int64_t n) +{ + return n < 0 ? mp_sizeof_int(n) : mp_sizeof_uint(n); +} + +static inline char * +mp_encode_Xint(char *data, int64_t v) +{ + return v < 0 ? mp_encode_int(data, v) : mp_encode_uint(data, v); +} + +static inline int64_t +mp_decode_Xint(const char **data) +{ + switch (mp_typeof(**data)) { + case MP_UINT: + return (int64_t)mp_decode_uint(data); + case MP_INT: + return mp_decode_int(data); + default: + mp_unreachable(); + } + return 0; +} + +static inline uint32_t +mp_sizeof_datetime_raw(const struct datetime *date) +{ + uint32_t sz = mp_sizeof_Xint(date->secs); + + // even if nanosecs == 0 we need to output anything + // if we have non-null tz offset + if (date->nsec != 0 || date->offset != 0) + sz += mp_sizeof_Xint(date->nsec); + if (date->offset) + sz += mp_sizeof_Xint(date->offset); + return sz; +} + +uint32_t +mp_sizeof_datetime(const struct datetime *date) +{ + return mp_sizeof_ext(mp_sizeof_datetime_raw(date)); +} + +struct datetime * +datetime_unpack(const char **data, uint32_t len, struct datetime *date) +{ + const char * svp = *data; + + memset(date, 0, sizeof(*date)); + + date->secs = mp_decode_Xint(data); + + len -= *data - svp; + if (len <= 0) + return date; + + svp = *data; + date->nsec = mp_decode_Xint(data); + len -= *data - svp; + + if (len <= 0) + return date; + + date->offset = mp_decode_Xint(data); + + return date; +} + +struct datetime * +mp_decode_datetime(const char **data, struct datetime *date) +{ + if (mp_typeof(**data) != MP_EXT) + return NULL; + + int8_t type; + uint32_t len = mp_decode_extl(data, &type); + + if (type != MP_DATETIME || len == 0) { + return NULL; + } + return datetime_unpack(data, len, date); +} + +char * +datetime_pack(char *data, const struct datetime *date) +{ + data = mp_encode_Xint(data, date->secs); + if (date->nsec != 0 || date->offset != 0) + data = mp_encode_Xint(data, date->nsec); + if (date->offset) + data = mp_encode_Xint(data, date->offset); + + return data; +} + +char * +mp_encode_datetime(char *data, const struct datetime *date) +{ + uint32_t len = mp_sizeof_datetime_raw(date); + + data = mp_encode_extl(data, MP_DATETIME, len); + + return datetime_pack(data, date); +} + +int +mp_snprint_datetime(char *buf, int size, const char **data, uint32_t len) +{ + struct datetime date = {0}; + + if (datetime_unpack(data, len, &date) == NULL) + return -1; + + return datetime_to_string(&date, buf, size); +} + +int +mp_fprint_datetime(FILE *file, const char **data, uint32_t len) +{ + struct datetime date; + + if (datetime_unpack(data, len, &date) == NULL) + return -1; + + char buf[48]; + datetime_to_string(&date, buf, sizeof buf); + + return fprintf(file, "%s", buf); +} + diff --git a/src/lib/core/mp_datetime.h b/src/lib/core/mp_datetime.h new file mode 100644 index 000000000..9a4d2720c --- /dev/null +++ b/src/lib/core/mp_datetime.h @@ -0,0 +1,89 @@ +#pragma once +/* + * Copyright 2021, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include "datetime.h" + +#if defined(__cplusplus) +extern "C" +{ +#endif /* defined(__cplusplus) */ + +/** + * Unpack datetime data from MessagePack buffer. + * @sa datetime_pack + */ +struct datetime * +datetime_unpack(const char **data, uint32_t len, struct datetime *date); + +/** + * Pack datetime data to MessagePack buffer. + * @sa datetime_unpack + */ +char * +datetime_pack(char *data, const struct datetime *date); + +/** + * Calculate size of MessagePack buffer for datetime data. + */ +uint32_t +mp_sizeof_datetime(const struct datetime *date); + +/** + * Decode data from MessagePack buffer to datetime structure. + */ +struct datetime * +mp_decode_datetime(const char **data, struct datetime *date); + +/** + * Encode datetime structure to the MessagePack buffer. + */ +char * +mp_encode_datetime(char *data, const struct datetime *date); + +/** + * Print datetime's string representation into a given buffer. + * @sa mp_snprint_decimal + */ +int +mp_snprint_datetime(char *buf, int size, const char **data, uint32_t len); + +/** + * Print datetime's string representation into a stream. + * @sa mp_fprint_decimal + */ +int +mp_fprint_datetime(FILE *file, const char **data, uint32_t len); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ diff --git a/src/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h index e3ff9f5d0..3b7eaee7c 100644 --- a/src/lib/core/mp_extension_types.h +++ b/src/lib/core/mp_extension_types.h @@ -44,6 +44,7 @@ enum mp_extension_type { MP_DECIMAL = 1, MP_UUID = 2, MP_ERROR = 3, + MP_DATETIME = 4, mp_extension_type_MAX, }; diff --git a/src/lib/mpstream/mpstream.c b/src/lib/mpstream/mpstream.c index 70ca29889..d3e1de965 100644 --- a/src/lib/mpstream/mpstream.c +++ b/src/lib/mpstream/mpstream.c @@ -35,6 +35,7 @@ #include "msgpuck.h" #include "mp_decimal.h" #include "uuid/mp_uuid.h" +#include "mp_datetime.h" void mpstream_reserve_slow(struct mpstream *stream, size_t size) @@ -208,6 +209,16 @@ mpstream_encode_uuid(struct mpstream *stream, const struct tt_uuid *uuid) mpstream_advance(stream, pos - data); } +void +mpstream_encode_datetime(struct mpstream *stream, const struct datetime *val) +{ + char *data = mpstream_reserve(stream, mp_sizeof_datetime(val)); + if (data == NULL) + return; + char *pos = mp_encode_datetime(data, val); + mpstream_advance(stream, pos - data); +} + void mpstream_memcpy(struct mpstream *stream, const void *src, uint32_t n) { diff --git a/src/lib/mpstream/mpstream.h b/src/lib/mpstream/mpstream.h index a60add143..94831160f 100644 --- a/src/lib/mpstream/mpstream.h +++ b/src/lib/mpstream/mpstream.h @@ -39,6 +39,7 @@ extern "C" { #endif /* defined(__cplusplus) */ struct tt_uuid; +struct datetime; /** * Ask the allocator to reserve at least size bytes. It can reserve @@ -145,6 +146,9 @@ mpstream_encode_decimal(struct mpstream *stream, const decimal_t *val); void mpstream_encode_uuid(struct mpstream *stream, const struct tt_uuid *uuid); +void +mpstream_encode_datetime(struct mpstream *stream, const struct datetime *dt); + /** Copies n bytes from memory area src to stream. */ void mpstream_memcpy(struct mpstream *stream, const void *src, uint32_t n); diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c index b6ecf2b1e..bca53f6b7 100644 --- a/src/lua/msgpack.c +++ b/src/lua/msgpack.c @@ -46,6 +46,7 @@ #include "lib/core/decimal.h" /* decimal_unpack() */ #include "lib/uuid/mp_uuid.h" /* mp_decode_uuid() */ #include "lib/core/mp_extension_types.h" +#include "lib/core/mp_datetime.h" #include "cord_buf.h" #include @@ -200,6 +201,9 @@ restart: /* used by MP_EXT of unidentified subtype */ break; case MP_ERROR: return luamp_encode_extension(L, top, stream); + case MP_DATETIME: + mpstream_encode_datetime(stream, field->dateval); + break; default: /* Run trigger if type can't be encoded */ type = luamp_encode_extension(L, top, stream); @@ -333,6 +337,14 @@ luamp_decode(struct lua_State *L, struct luaL_serializer *cfg, goto ext_decode_err; return; } + case MP_DATETIME: + { + struct datetime *date = luaL_pushdatetime(L); + date = datetime_unpack(data, len, date); + if (date == NULL) + goto ext_decode_err; + return; + } default: /* reset data to the extension header */ *data = svp; diff --git a/src/lua/msgpackffi.lua b/src/lua/msgpackffi.lua index 1d54f11b8..fb5e7d644 100644 --- a/src/lua/msgpackffi.lua +++ b/src/lua/msgpackffi.lua @@ -26,6 +26,10 @@ char * mp_encode_uuid(char *data, const struct tt_uuid *uuid); uint32_t mp_sizeof_uuid(); +uint32_t +mp_sizeof_datetime(const struct t_datetime_tz *date); +char * +mp_encode_datetime(char *data, const struct t_datetime_tz *date); float mp_decode_float(const char **data); double @@ -36,6 +40,8 @@ decimal_t * decimal_unpack(const char **data, uint32_t len, decimal_t *dec); struct tt_uuid * uuid_unpack(const char **data, uint32_t len, struct tt_uuid *uuid); +struct datetime * +datetime_unpack(const char **data, uint32_t len, struct datetime *date); ]]) local strict_alignment = (jit.arch == 'arm') @@ -142,6 +148,11 @@ local function encode_uuid(buf, uuid) builtin.mp_encode_uuid(p, uuid) end +local function encode_datetime(buf, date) + local p = buf:alloc(builtin.mp_sizeof_datetime(date)) + builtin.mp_encode_datetime(p, date) +end + local function encode_int(buf, num) if num >= 0 then if num <= 0x7f then @@ -320,6 +331,7 @@ on_encode(ffi.typeof('float'), encode_float) on_encode(ffi.typeof('double'), encode_double) on_encode(ffi.typeof('decimal_t'), encode_decimal) on_encode(ffi.typeof('struct tt_uuid'), encode_uuid) +on_encode(ffi.typeof('struct datetime'), encode_datetime) -------------------------------------------------------------------------------- -- Decoder @@ -513,6 +525,12 @@ local ext_decoder = { builtin.uuid_unpack(data, len, uuid) return uuid end, + -- MP_DATETIME + [4] = function(data, len) + local dt = ffi.new("struct datetime") + builtin.datetime_unpack(data, len, dt) + return dt + end, } local function decode_ext(data) diff --git a/src/lua/serializer.c b/src/lua/serializer.c index 8db6746a3..24f4a5ff9 100644 --- a/src/lua/serializer.c +++ b/src/lua/serializer.c @@ -41,6 +41,7 @@ #include "lib/core/mp_extension_types.h" #include "lua/error.h" +#include "datetime.h" #include "trivia/util.h" #include "diag.h" #include "serializer_opts.h" @@ -544,6 +545,9 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, opts != NULL && opts->error_marshaling_enabled) { field->ext_type = MP_ERROR; + } else if (cd->ctypeid == CTID_DATETIME) { + field->ext_type = MP_DATETIME; + field->dateval = (struct datetime *)cdata; } else { field->ext_type = MP_UNKNOWN_EXTENSION; } diff --git a/src/lua/serializer.h b/src/lua/serializer.h index 0a0501a74..e7a240e0a 100644 --- a/src/lua/serializer.h +++ b/src/lua/serializer.h @@ -52,6 +52,7 @@ extern "C" { #include #include "trigger.h" +#include "lib/core/datetime.h" #include "lib/core/decimal.h" /* decimal_t */ #include "lib/core/mp_extension_types.h" #include "lua/error.h" @@ -223,6 +224,7 @@ struct luaL_field { uint32_t size; decimal_t *decval; struct tt_uuid *uuidval; + struct datetime *dateval; }; enum mp_type type; /* subtypes of MP_EXT */ diff --git a/src/lua/utils.c b/src/lua/utils.c index 9130b60b5..e1d796041 100644 --- a/src/lua/utils.c +++ b/src/lua/utils.c @@ -254,7 +254,6 @@ luaL_setcdatagc(struct lua_State *L, int idx) lua_pop(L, 1); } - /** * A helper to register a single type metatable. */ diff --git a/test/unit/datetime.c b/test/unit/datetime.c index f718b7de0..fcaebfe2d 100644 --- a/test/unit/datetime.c +++ b/test/unit/datetime.c @@ -6,6 +6,9 @@ #include "unit.h" #include "datetime.h" +#include "mp_datetime.h" +#include "msgpuck.h" +#include "mp_extension_types.h" static const char sample[] = "2012-12-24T15:30Z"; @@ -245,12 +248,132 @@ tostring_datetime_test(void) check_plan(); } +static void +mp_datetime_test() +{ + static struct { + int64_t secs; + uint32_t nsec; + uint32_t offset; + uint32_t len; + } tests[] = { + {/* '1970-01-01T02:00+02:00' */ 0, 0, 120, 6}, + {/* '1970-01-01T01:30+01:30' */ 0, 0, 90, 6}, + {/* '1970-01-01T01:00+01:00' */ 0, 0, 60, 6}, + {/* '1970-01-01T00:01+00:01' */ 0, 0, 1, 6}, + {/* '1970-01-01T00:00Z' */ 0, 0, 0, 3}, + {/* '1969-12-31T23:59-00:01' */ 0, 0, -1, 6}, + {/* '1969-12-31T23:00-01:00' */ 0, 0, -60, 6}, + {/* '1969-12-31T22:30-01:30' */ 0, 0, -90, 6}, + {/* '1969-12-31T22:00-02:00' */ 0, 0, -120, 6}, + {/* '1970-01-01T00:00:00.123456789Z' */ 0, 123456789, 0, 9}, + {/* '1970-01-01T00:00:00.123456Z' */ 0, 123456000, 0, 9}, + {/* '1970-01-01T00:00:00.123Z' */ 0, 123000000, 0, 9}, + {/* '1973-11-29T21:33:09Z' */ 123456789, 0, 0, 8}, + {/* '2013-10-28T17:51:56Z' */ 1382982716, 0, 0, 8}, + {/* '9999-12-31T23:59:59Z' */ 253402300799, 0, 0, 12}, + {/* '9999-12-31T23:59:59.123456789Z' */ 253402300799, 123456789, 0, 17}, + {/* '9999-12-31T23:59:59.123456789-02:00' */ 253402300799, 123456789, -120, 18}, + }; + size_t index; + + plan(85); + for (index = 0; index < DIM(tests); index++) { + struct datetime date = { + tests[index].secs, + tests[index].nsec, + tests[index].offset + }; + char buf[24], *data = buf; + const char *data1 = buf; + struct datetime ret; + + char *end = mp_encode_datetime(data, &date); + uint32_t len = mp_sizeof_datetime(&date); + is(len, tests[index].len, "len %u, expected len %u", + len, tests[index].len); + is(end - data, len, + "mp_sizeof_datetime(%d) == encoded length %ld", + len, end - data); + + struct datetime *rc = mp_decode_datetime(&data1, &ret); + is(rc, &ret, "mp_decode_datetime() return code"); + is(data1, end, "mp_sizeof_uuid() == decoded length"); + is(datetime_compare(&date, &ret), 0, "datetime_compare(&date, &ret)"); + } + check_plan(); +} + + +static int +mp_fprint_ext_test(FILE *file, const char **data, int depth) +{ + (void)depth; + int8_t type; + uint32_t len = mp_decode_extl(data, &type); + if (type != MP_DATETIME) + return fprintf(file, "undefined"); + return mp_fprint_datetime(file, data, len); +} + +static int +mp_snprint_ext_test(char *buf, int size, const char **data, int depth) +{ + (void)depth; + int8_t type; + uint32_t len = mp_decode_extl(data, &type); + if (type != MP_DATETIME) + return snprintf(buf, size, "undefined"); + return mp_snprint_datetime(buf, size, data, len); +} + +static void +mp_print_test(void) +{ + plan(5); + header(); + + mp_snprint_ext = mp_snprint_ext_test; + mp_fprint_ext = mp_fprint_ext_test; + + char sample[64]; + char buffer[64]; + char str[64]; + struct datetime date = {0, 0, 0}; // 1970-01-01T00:00Z + + mp_encode_datetime(buffer, &date); + int sz = datetime_to_string(&date, str, sizeof str); + int rc = mp_snprint(NULL, 0, buffer); + is(rc, sz, "correct mp_snprint size %u with empty buffer", rc); + rc = mp_snprint(str, sizeof(str), buffer); + is(rc, sz, "correct mp_snprint size %u", rc); + datetime_to_string(&date, sample, sizeof sample); + is(strcmp(str, sample), 0, "correct mp_snprint result"); + + FILE *f = tmpfile(); + rc = mp_fprint(f, buffer); + is(rc, sz, "correct mp_fprint size %u", sz); + rewind(f); + rc = fread(str, 1, sizeof(str), f); + str[rc] = 0; + is(strcmp(str, sample), 0, "correct mp_fprint result %u", rc); + fclose(f); + + mp_snprint_ext = mp_snprint_ext_default; + mp_fprint_ext = mp_fprint_ext_default; + + footer(); + check_plan(); +} + int main(void) { - plan(2); + plan(4); datetime_test(); tostring_datetime_test(); + mp_datetime_test(); + mp_print_test(); return check_plan(); } diff --git a/third_party/lua-cjson/lua_cjson.c b/third_party/lua-cjson/lua_cjson.c index 7a326075a..924e0419a 100644 --- a/third_party/lua-cjson/lua_cjson.c +++ b/third_party/lua-cjson/lua_cjson.c @@ -52,6 +52,7 @@ #include "mp_extension_types.h" /* MP_DECIMAL, MP_UUID */ #include "tt_static.h" #include "uuid/tt_uuid.h" /* tt_uuid_to_string(), UUID_STR_LEN */ +#include "core/datetime.h" #include "cord_buf.h" typedef enum { @@ -426,6 +427,13 @@ static void json_append_data(lua_State *l, struct luaL_serializer *cfg, case MP_UUID: return json_append_string(cfg, json, tt_uuid_str(field.uuidval), UUID_STR_LEN); + + case MP_DATETIME: + { + char buf[128]; + size_t sz = datetime_to_string(field.dateval, buf, sizeof buf); + return json_append_string(cfg, json, buf, sz); + } default: assert(false); } diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc index 2b67dcc6a..034104737 100644 --- a/third_party/lua-yaml/lyaml.cc +++ b/third_party/lua-yaml/lyaml.cc @@ -617,7 +617,7 @@ static int dump_node(struct lua_yaml_dumper *dumper) yaml_event_t ev; yaml_scalar_style_t style = YAML_PLAIN_SCALAR_STYLE; int is_binary = 0; - char buf[FPCONV_G_FMT_BUFSIZE]; + char buf[FPCONV_G_FMT_BUFSIZE + 8]; struct luaL_field field; bool unused; (void) unused; @@ -707,6 +707,10 @@ static int dump_node(struct lua_yaml_dumper *dumper) str = tt_uuid_str(field.uuidval); len = UUID_STR_LEN; break; + case MP_DATETIME: + len = datetime_to_string(field.dateval, buf, sizeof buf); + str = buf; + break; default: assert(0); /* checked by luaL_checkfield() */ } -- 2.29.2