Skip to content

Commit cc2a882

Browse files
authored
fix: preserve 64-bit integers in Tarantool builds
Fix: preserve 64-bit integers in Tarantool builds lua_pushinteger loses precision for values beyond double mantissa, so large JSON integers were rounded (e.g. 123456789012345678, 123456789012345680). Use luaL_pushint64/luaL_pushuint64 in TT_COMPAT builds to match tarantool json behavior: small ints as numbers, large ints as cdata (long/unsigned long).
1 parent 883cf2e commit cc2a882

6 files changed

Lines changed: 136 additions & 23 deletions

File tree

.github/workflows/ci.yml

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,56 @@ name: CI
33
on: push
44

55
jobs:
6-
lua:
6+
test:
77
strategy:
88
fail-fast: false
99
matrix:
10-
include:
11-
- lua: lua=5.1
12-
- lua: lua=5.2
13-
- lua: lua=5.3
14-
- lua: lua=5.4
15-
- lua: luajit=2.0
16-
- lua: luajit=2.1
10+
lua: [lua5.1, lua5.2, lua5.3, lua5.4, luajit2.0, luajit2.1]
11+
1712
runs-on: ubuntu-22.04
13+
1814
steps:
19-
# Checks-out the repository under $GITHUB_WORKSPACE.
20-
- uses: actions/checkout@v3
21-
- name: Install Lua (${{ matrix.lua }})
15+
- uses: actions/checkout@v4
16+
17+
- name: Setup Python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.11"
21+
22+
- name: Install hererocks
23+
run: pip install hererocks
24+
25+
- name: Setup Lua (${{ matrix.lua }})
2226
run: |
23-
pip install hererocks
24-
hererocks lua_install -r^ --${{ matrix.lua }}
25-
export PATH=$PATH:$PWD/lua_install/bin
26-
luarocks install lua-cjson2
27-
- name: Build lua-simdjson
27+
case "${{ matrix.lua }}" in
28+
lua5.1) FLAG="--lua=5.1" ;;
29+
lua5.2) FLAG="--lua=5.2" ;;
30+
lua5.3) FLAG="--lua=5.3" ;;
31+
lua5.4) FLAG="--lua=5.4" ;;
32+
luajit2.0) FLAG="--luajit=2.0" ;;
33+
luajit2.1) FLAG="--luajit=2.1" ;;
34+
esac
35+
hererocks lua_env -r^ $FLAG
36+
echo "LUA_ENV=$PWD/lua_env" >> $GITHUB_ENV
37+
echo "$PWD/lua_env/bin" >> $GITHUB_PATH
38+
39+
- name: Upgrade LuaRocks (LuaJIT only)
40+
if: startsWith(matrix.lua, 'luajit')
2841
run: |
29-
export PATH=$PATH:$PWD/lua_install/bin
30-
luarocks make
31-
- name: Run tests
42+
# hererocks bundles LuaRocks 3.8; its manifest exceeds LuaJIT's
43+
# 65536 constants limit. Install LuaRocks 3.12 manually instead.
44+
curl -fsSL https://luarocks.org/releases/luarocks-3.12.0.tar.gz | tar xz
45+
cd luarocks-3.12.0
46+
./configure --with-lua="$LUA_ENV" --prefix="$LUA_ENV"
47+
make build install
48+
49+
- name: Install dependencies
3250
run: |
33-
export PATH=$PATH:$PWD/lua_install/bin
51+
luarocks install lua-cjson2
3452
luarocks install busted
35-
busted --verbose
53+
54+
- name: Build project
55+
run: luarocks make
56+
57+
- name: Run tests
58+
run: busted --verbose

lua-simdjson-pico.0.0.4-1.rockspec

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
-- Rockspecs template for picodata version.
2+
-- Copy it, replace tag in source and change version.
3+
package="lua-simdjson"
4+
version="pico.0.0.4-1"
5+
source = {
6+
url = "git://github.com/picodata/lua-simdjson",
7+
tag = "0.0.4"
8+
}
9+
description = {
10+
summary = "This is a simple Lua binding for simdjson",
11+
detailed = [[
12+
This is a c++ binding to simdjson for parsing JSON very quickly.
13+
]],
14+
homepage = "https://github.com/picodata/lua-simdjson",
15+
license = "Apache-2.0"
16+
}
17+
dependencies = {
18+
"lua >= 5.1, < 5.5"
19+
}
20+
21+
build = {
22+
type = "make",
23+
build_variables = {
24+
CFLAGS="$(CFLAGS) -D TT_COMPAT",
25+
-- add compat sources as well(normally they are excluded).
26+
SRC="src/tt_compat.cpp",
27+
LIBFLAG="$(LIBFLAG)",
28+
LUA_LIBDIR="$(LUA_LIBDIR)",
29+
LUA_BINDIR="$(LUA_BINDIR)",
30+
LUA_INCDIR="$(LUA_INCDIR)",
31+
LUA="$(LUA)",
32+
},
33+
install_variables = {
34+
INST_PREFIX="$(PREFIX)",
35+
INST_BINDIR="$(BINDIR)",
36+
INST_LIBDIR="$(LIBDIR)",
37+
INST_LUADIR="$(LUADIR)",
38+
INST_CONFDIR="$(CONFDIR)",
39+
},
40+
}
41+

src/luasimdjson.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,19 @@ void convert_element_to_table(lua_State *L, dom::element element) {
8686
break;
8787

8888
case dom::element_type::INT64:
89-
lua_pushinteger(L, int64_t(element));
89+
#ifdef TT_COMPAT
90+
tt_lua_pushinteger64(L, int64_t(element));
91+
#else
92+
lua_pushinteger(L, int64_t(element));
93+
#endif
9094
break;
9195

9296
case dom::element_type::UINT64:
93-
lua_pushinteger(L, int64_t(element));
97+
#ifdef TT_COMPAT
98+
tt_lua_pushuint64(L, uint64_t(element));
99+
#else
100+
lua_pushinteger(L, int64_t(element));
101+
#endif
94102
break;
95103

96104
case dom::element_type::DOUBLE:

src/tt_compat.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
#include <cstdint>
12
#include <lua.hpp>
23
#include <lauxlib.h>
4+
#include <module.h>
35
#include "tt_compat.h"
46

7+
static constexpr int64_t TT_INT64_THRESHOLD = INT64_C(100000000000000);
8+
59
char* SER_MARKER_MAP = (char*)"map";
610
char* SER_MARKER_SEQ = (char*)"seq";
711

@@ -21,3 +25,19 @@ void tt_compat_init(lua_State *L) {
2125
tt_lua_createnull(L);
2226
}
2327

28+
void tt_lua_pushinteger64(lua_State *L, int64_t val) {
29+
if (val >= TT_INT64_THRESHOLD || val <= -TT_INT64_THRESHOLD) {
30+
luaL_pushint64(L, val);
31+
} else {
32+
lua_pushnumber(L, static_cast<lua_Number>(val));
33+
}
34+
}
35+
36+
void tt_lua_pushuint64(lua_State *L, uint64_t val) {
37+
if (val > static_cast<uint64_t>(INT64_MAX) || val >= static_cast<uint64_t>(TT_INT64_THRESHOLD)) {
38+
luaL_pushuint64(L, val);
39+
} else {
40+
lua_pushnumber(L, static_cast<lua_Number>(val));
41+
}
42+
}
43+

src/tt_compat.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <cstdint>
12
#include <lua.hpp>
23
#include <lauxlib.h>
34

@@ -33,3 +34,9 @@ inline void tt_lua_push_serialize_mt(lua_State *L, char* marker) {
3334
lua_pushstring(L, marker);
3435
lua_settable(L, -3);
3536
}
37+
38+
/* Push 64-bit integers the same way as tarantool's json module:
39+
* small values as lua numbers, large values as cdata (long/unsigned long).
40+
*/
41+
void tt_lua_pushinteger64(lua_State *L, int64_t val);
42+
void tt_lua_pushuint64(lua_State *L, uint64_t val);

tests/tarantool_test.lua

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ g.test_compat_with_tarantool_json = function()
2626
end
2727
end
2828

29+
g.test_large_integers = function()
30+
local cases = {
31+
'42',
32+
'100000000000000',
33+
'123456789012345678',
34+
'-100000000000000',
35+
'9223372036854775808',
36+
}
37+
for _, num in ipairs(cases) do
38+
local raw_json = '{"n":' .. num .. '}'
39+
t.assert_equals(simdjson.parse(raw_json), tt_json.decode(raw_json), num)
40+
end
41+
end
42+
2943
g.test_serialize_markers = function ()
3044
local result = simdjson.parse('{"nested": {"emptyMap": {}}, "emptyArray": []}')
3145
t.assert_equals(getmetatable(result), {__serialize = "map"})

0 commit comments

Comments
 (0)