forked from fl00r/go-tarantool-1.6
-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
datetime: add datetime type in msgpack
This patch provides datetime support for all space operations and as function return result. Datetime type was introduced in Tarantool 2.10. See more in issue [1]. Note that timezone's index and offset and intervals are not implemented in Tarantool, see [2] and [3]. This Lua snippet was quite useful for debugging encoding and decoding datetime in MessagePack: local msgpack = require('msgpack') local datetime = require('datetime') local dt = datetime.parse('2012-01-31T23:59:59.000000010Z') local mp_dt = msgpack.encode(dt):gsub('.', function (c) return string.format('%02x', string.byte(c)) end) print(mp_dt) -- d8047f80284f000000000a00000000000000 1. tarantool/tarantool#5946 2. #163 3. #165 Closes #118
- Loading branch information
1 parent
7345161
commit 61d0739
Showing
7 changed files
with
872 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
local has_datetime, datetime = pcall(require, 'datetime') | ||
|
||
if not has_datetime then | ||
error('Datetime unsupported, use Tarantool 2.10 or newer') | ||
end | ||
|
||
-- Do not set listen for now so connector won't be | ||
-- able to send requests until everything is configured. | ||
box.cfg{ | ||
work_dir = os.getenv("TEST_TNT_WORK_DIR"), | ||
} | ||
|
||
box.schema.user.create('test', { password = 'test' , if_not_exists = true }) | ||
box.schema.user.grant('test', 'execute', 'universe', nil, { if_not_exists = true }) | ||
|
||
box.once("init", function() | ||
local s_1 = box.schema.space.create('testDatetime_1', { | ||
id = 524, | ||
if_not_exists = true, | ||
}) | ||
s_1:create_index('primary', { | ||
type = 'TREE', | ||
parts = { | ||
{ field = 1, type = 'datetime' }, | ||
}, | ||
if_not_exists = true | ||
}) | ||
s_1:truncate() | ||
|
||
local s_3 = box.schema.space.create('testDatetime_2', { | ||
id = 526, | ||
if_not_exists = true, | ||
}) | ||
s_3:create_index('primary', { | ||
type = 'tree', | ||
parts = { | ||
{1, 'uint'}, | ||
}, | ||
if_not_exists = true | ||
}) | ||
s_3:truncate() | ||
|
||
box.schema.func.create('call_datetime_testdata') | ||
box.schema.user.grant('test', 'read,write', 'space', 'testDatetime_1', { if_not_exists = true }) | ||
box.schema.user.grant('test', 'read,write', 'space', 'testDatetime_2', { if_not_exists = true }) | ||
end) | ||
|
||
local function call_datetime_testdata() | ||
local dt1 = datetime.new({ year = 1934 }) | ||
local dt2 = datetime.new({ year = 1961 }) | ||
local dt3 = datetime.new({ year = 1968 }) | ||
return { | ||
{ | ||
5, "Go!", { | ||
{"Klushino", dt1}, | ||
{"Baikonur", dt2}, | ||
{"Novoselovo", dt3}, | ||
}, | ||
} | ||
} | ||
end | ||
rawset(_G, 'call_datetime_testdata', call_datetime_testdata) | ||
|
||
-- Set listen only when every other thing is configured. | ||
box.cfg{ | ||
listen = os.getenv("TEST_TNT_LISTEN"), | ||
} | ||
|
||
require('console').start() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// Package with support of Tarantool's datetime data type. | ||
// | ||
// Datetime data type supported in Tarantool since 2.10. | ||
// | ||
// Since: 1.7.0 | ||
// | ||
// See also: | ||
// | ||
// * Datetime Internals https://github.com/tarantool/tarantool/wiki/Datetime-Internals | ||
package datetime | ||
|
||
import ( | ||
"encoding/binary" | ||
"fmt" | ||
"time" | ||
|
||
"gopkg.in/vmihailenco/msgpack.v2" | ||
) | ||
|
||
// Datetime MessagePack serialization schema is an MP_EXT extension, which | ||
// creates container of 8 or 16 bytes long payload. | ||
// | ||
// +---------+--------+===============+-------------------------------+ | ||
// |0xd7/0xd8|type (4)| seconds (8b) | nsec; tzoffset; tzindex; (8b) | | ||
// +---------+--------+===============+-------------------------------+ | ||
// | ||
// MessagePack data encoded using fixext8 (0xd7) or fixext16 (0xd8), and may | ||
// contain: | ||
// | ||
// * [required] seconds parts as full, unencoded, signed 64-bit integer, | ||
// stored in little-endian order; | ||
// | ||
// * [optional] all the other fields (nsec, tzoffset, tzindex) if any of them | ||
// were having not 0 value. They are packed naturally in little-endian order; | ||
|
||
// Datetime external type. Supported since Tarantool 2.10. See more details in | ||
// issue https://github.com/tarantool/tarantool/issues/5946. | ||
const datetime_extId = 4 | ||
|
||
// datetime structure keeps a number of seconds and nanoseconds since Unix Epoch. | ||
// Time is normalized by UTC, so time-zone offset is informative only. | ||
type datetime struct { | ||
// Seconds since Epoch, where the epoch is the point where the time | ||
// starts, and is platform dependent. For Unix, the epoch is January 1, | ||
// 1970, 00:00:00 (UTC). Tarantool uses a double type, see a structure | ||
// definition in src/lib/core/datetime.h and reasons in | ||
// https://github.com/tarantool/tarantool/wiki/Datetime-internals#intervals-in-c | ||
seconds int64 | ||
// Nanoseconds, fractional part of seconds. Tarantool uses int32_t, see | ||
// a definition in src/lib/core/datetime.h. | ||
nsec int32 | ||
// Timezone offset in minutes from UTC (not implemented in Tarantool, | ||
// see gh-163). Tarantool uses a int16_t type, see a structure | ||
// definition in src/lib/core/datetime.h. | ||
tzOffset int16 | ||
// Olson timezone id (not implemented in Tarantool, see gh-163). | ||
// Tarantool uses a int16_t type, see a structure definition in | ||
// src/lib/core/datetime.h. | ||
tzIndex int16 | ||
} | ||
|
||
// Size of datetime fields in a MessagePack value. | ||
const ( | ||
secondsSize = 8 | ||
nsecSize = 4 | ||
tzIndexSize = 2 | ||
tzOffsetSize = 2 | ||
) | ||
|
||
const maxSize = secondsSize + nsecSize + tzIndexSize + tzOffsetSize | ||
|
||
type Datetime struct { | ||
time time.Time | ||
} | ||
|
||
// NewDatetime returns a pointer to a new datetime.Datetime that contains a | ||
// specified time.Time. | ||
func NewDatetime(t time.Time) *Datetime { | ||
dt := new(Datetime) | ||
dt.time = t | ||
return dt | ||
} | ||
|
||
// ToTime returns a time.Time that Datetime contains. | ||
func (dtime *Datetime) ToTime() time.Time { | ||
return dtime.time | ||
} | ||
|
||
var _ msgpack.Marshaler = (*Datetime)(nil) | ||
var _ msgpack.Unmarshaler = (*Datetime)(nil) | ||
|
||
func (dtime *Datetime) MarshalMsgpack() ([]byte, error) { | ||
tm := dtime.ToTime() | ||
|
||
var dt datetime | ||
dt.seconds = tm.Unix() | ||
dt.nsec = int32(tm.Nanosecond()) | ||
dt.tzIndex = 0 // It is not implemented, see gh-163. | ||
dt.tzOffset = 0 // It is not implemented, see gh-163. | ||
|
||
var bytesSize = secondsSize | ||
if dt.nsec != 0 || dt.tzOffset != 0 || dt.tzIndex != 0 { | ||
bytesSize += nsecSize + tzIndexSize + tzOffsetSize | ||
} | ||
|
||
buf := make([]byte, bytesSize) | ||
binary.LittleEndian.PutUint64(buf, uint64(dt.seconds)) | ||
if bytesSize == maxSize { | ||
binary.LittleEndian.PutUint32(buf[secondsSize:], uint32(dt.nsec)) | ||
binary.LittleEndian.PutUint16(buf[secondsSize+nsecSize:], uint16(dt.tzOffset)) | ||
binary.LittleEndian.PutUint16(buf[secondsSize+nsecSize+tzOffsetSize:], uint16(dt.tzIndex)) | ||
} | ||
|
||
return buf, nil | ||
} | ||
|
||
func (tm *Datetime) UnmarshalMsgpack(b []byte) error { | ||
l := len(b) | ||
if l != maxSize && l != secondsSize { | ||
return fmt.Errorf("invalid data length: got %d, wanted %d or %d", len(b), secondsSize, maxSize) | ||
} | ||
|
||
var dt datetime | ||
sec := binary.LittleEndian.Uint64(b) | ||
dt.seconds = int64(sec) | ||
dt.nsec = 0 | ||
if l == maxSize { | ||
dt.nsec = int32(binary.LittleEndian.Uint32(b[secondsSize:])) | ||
dt.tzOffset = int16(binary.LittleEndian.Uint16(b[secondsSize+nsecSize:])) | ||
dt.tzIndex = int16(binary.LittleEndian.Uint16(b[secondsSize+nsecSize+tzOffsetSize:])) | ||
} | ||
tt := time.Unix(dt.seconds, int64(dt.nsec)).UTC() | ||
*tm = *NewDatetime(tt) | ||
|
||
return nil | ||
} | ||
|
||
func init() { | ||
msgpack.RegisterExt(datetime_extId, &Datetime{}) | ||
} |
Oops, something went wrong.