Skip to content

Commit 0f17b29

Browse files
authored
Add RDF_VARIANT bindings for Node.js and Python APIs (#2797)
1 parent 0a57403 commit 0f17b29

3 files changed

Lines changed: 155 additions & 39 deletions

File tree

src_cpp/node_util.cpp

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,88 @@
11
#include "include/node_util.h"
22

33
#include "common/exception/exception.h"
4+
#include "common/types/blob.h"
45
#include "common/types/value/nested.h"
56
#include "common/types/value/node.h"
67
#include "common/types/value/recursive_rel.h"
78
#include "common/types/value/rel.h"
89
#include "common/types/value/value.h"
910

11+
Napi::Value ConvertRdfVariantToNapiObject(const Value& value, Napi::Env env) {
12+
auto typeVal = NestedVal::getChildVal(&value, 0);
13+
auto type = static_cast<LogicalTypeID>(typeVal->getValue<uint8_t>());
14+
auto blobData = NestedVal::getChildVal(&value, 1)->strVal.data();
15+
switch (type) {
16+
case LogicalTypeID::STRING: {
17+
auto str = NestedVal::getChildVal(&value, 1)->strVal;
18+
return Napi::String::New(env, str.data(), str.size());
19+
}
20+
case LogicalTypeID::BLOB: {
21+
auto blobStr = Blob::getValue<blob_t>(blobData).value.getAsString();
22+
return Napi::Buffer<char>::Copy(env, blobStr.c_str(), blobStr.size());
23+
}
24+
case LogicalTypeID::INT64: {
25+
return Napi::Number::New(env, Blob::getValue<int64_t>(blobData));
26+
}
27+
case LogicalTypeID::INT32: {
28+
return Napi::Number::New(env, Blob::getValue<int32_t>(blobData));
29+
}
30+
case LogicalTypeID::INT16: {
31+
return Napi::Number::New(env, Blob::getValue<int16_t>(blobData));
32+
}
33+
case LogicalTypeID::INT8: {
34+
return Napi::Number::New(env, Blob::getValue<int8_t>(blobData));
35+
}
36+
case LogicalTypeID::UINT64: {
37+
return Napi::Number::New(env, Blob::getValue<uint64_t>(blobData));
38+
}
39+
case LogicalTypeID::UINT32: {
40+
return Napi::Number::New(env, Blob::getValue<uint32_t>(blobData));
41+
}
42+
case LogicalTypeID::UINT16: {
43+
return Napi::Number::New(env, Blob::getValue<uint16_t>(blobData));
44+
}
45+
case LogicalTypeID::UINT8: {
46+
return Napi::Number::New(env, Blob::getValue<uint8_t>(blobData));
47+
}
48+
case LogicalTypeID::DOUBLE: {
49+
return Napi::Number::New(env, Blob::getValue<double>(blobData));
50+
}
51+
case LogicalTypeID::FLOAT: {
52+
return Napi::Number::New(env, Blob::getValue<float>(blobData));
53+
}
54+
case LogicalTypeID::BOOL: {
55+
return Napi::Boolean::New(env, Blob::getValue<bool>(blobData));
56+
}
57+
case LogicalTypeID::DATE: {
58+
auto dateVal = Blob::getValue<date_t>(blobData);
59+
// Javascript Date type contains both date and time information. This returns the Date at
60+
// 00:00:00 in UTC timezone.
61+
auto milliseconds = Date::getEpochNanoSeconds(dateVal) / Interval::NANOS_PER_MICRO /
62+
Interval::MICROS_PER_MSEC;
63+
return Napi::Date::New(env, milliseconds);
64+
}
65+
case LogicalTypeID::TIMESTAMP: {
66+
auto timestampVal = Blob::getValue<timestamp_t>(blobData);
67+
auto milliseconds = timestampVal.value / Interval::MICROS_PER_MSEC;
68+
return Napi::Date::New(env, milliseconds);
69+
}
70+
case LogicalTypeID::INTERVAL: {
71+
auto intervalVal = Blob::getValue<interval_t>(blobData);
72+
// TODO: By default, Node.js returns the difference in milliseconds between two dates, so we
73+
// follow the convention here, but it might not be the best choice in terms of usability.
74+
auto microseconds = intervalVal.micros;
75+
microseconds += intervalVal.days * Interval::MICROS_PER_DAY;
76+
microseconds += intervalVal.months * Interval::MICROS_PER_MONTH;
77+
auto milliseconds = microseconds / Interval::MICROS_PER_MSEC;
78+
return Napi::Number::New(env, milliseconds);
79+
}
80+
default: {
81+
KU_UNREACHABLE;
82+
}
83+
}
84+
}
85+
1086
Napi::Value Util::ConvertToNapiObject(const Value& value, Napi::Env env) {
1187
if (value.isNull()) {
1288
return env.Null();
@@ -74,9 +150,9 @@ Napi::Value Util::ConvertToNapiObject(const Value& value, Napi::Env env) {
74150
return Napi::Date::New(env, milliseconds);
75151
}
76152
case LogicalTypeID::TIMESTAMP_TZ: {
77-
auto timestampVal = value.getValue<timestamp_tz_t>();
78-
auto milliseconds = timestampVal.value / Interval::MICROS_PER_MSEC;
79-
return Napi::Date::New(env, milliseconds);
153+
auto timestampVal = value.getValue<timestamp_tz_t>();
154+
auto milliseconds = timestampVal.value / Interval::MICROS_PER_MSEC;
155+
return Napi::Date::New(env, milliseconds);
80156
}
81157
case LogicalTypeID::TIMESTAMP: {
82158
auto timestampVal = value.getValue<timestamp_t>();
@@ -85,7 +161,8 @@ Napi::Value Util::ConvertToNapiObject(const Value& value, Napi::Env env) {
85161
}
86162
case LogicalTypeID::TIMESTAMP_NS: {
87163
auto timestampVal = value.getValue<timestamp_ns_t>();
88-
auto milliseconds = timestampVal.value / Interval::NANOS_PER_MICRO / Interval::MICROS_PER_MSEC;
164+
auto milliseconds =
165+
timestampVal.value / Interval::NANOS_PER_MICRO / Interval::MICROS_PER_MSEC;
89166
return Napi::Date::New(env, milliseconds);
90167
}
91168
case LogicalTypeID::TIMESTAMP_MS: {
@@ -94,7 +171,8 @@ Napi::Value Util::ConvertToNapiObject(const Value& value, Napi::Env env) {
94171
}
95172
case LogicalTypeID::TIMESTAMP_SEC: {
96173
auto timestampVal = value.getValue<timestamp_sec_t>();
97-
auto milliseconds = Timestamp::getEpochMilliSeconds(Timestamp::fromEpochSeconds(timestampVal.value));
174+
auto milliseconds =
175+
Timestamp::getEpochMilliSeconds(Timestamp::fromEpochSeconds(timestampVal.value));
98176
return Napi::Date::New(env, milliseconds);
99177
}
100178
case LogicalTypeID::INTERVAL: {
@@ -190,6 +268,9 @@ Napi::Value Util::ConvertToNapiObject(const Value& value, Napi::Env env) {
190268
}
191269
return napiObj;
192270
}
271+
case LogicalTypeID::RDF_VARIANT: {
272+
return ConvertRdfVariantToNapiObject(value, env);
273+
}
193274
default:
194275
throw Exception("Unsupported type: " + dataType->toString());
195276
}

test/common.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,28 @@ const initTests = async () => {
5757
'copy moviesSerial from "../../dataset/tinysnb-serial/vMovies.csv"'
5858
);
5959

60+
await conn.query("CREATE RDFGraph T;");
61+
await conn.query(
62+
`
63+
CREATE (:T_l {val:cast(12, "INT64")}),
64+
(:T_l {val:cast(43, "INT32")}),
65+
(:T_l {val:cast(33, "INT16")}),
66+
(:T_l {val:cast(2, "INT8")}),
67+
(:T_l {val:cast(90, "UINT64")}),
68+
(:T_l {val:cast(77, "UINT32")}),
69+
(:T_l {val:cast(12, "UINT16")}),
70+
(:T_l {val:cast(1, "UINT8")}),
71+
(:T_l {val:cast(4.4, "DOUBLE")}),
72+
(:T_l {val:cast(1.2, "FLOAT")}),
73+
(:T_l {val:true}),
74+
(:T_l {val:"hhh"}),
75+
(:T_l {val:cast("2024-01-01", "DATE")}),
76+
(:T_l {val:cast("2024-01-01 11:25:30Z+00:00", "TIMESTAMP")}),
77+
(:T_l {val:cast("2 day", "INTERVAL")}),
78+
(:T_l {val:cast("\\\\xB2", "BLOB")})
79+
`
80+
);
81+
6082
global.dbPath = dbPath;
6183
global.db = db;
6284
global.conn = conn;

test/test_data_type.js

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe("BOOL", function () {
1818
describe("INT8", function () {
1919
it("should convert INT8 type", async function () {
2020
const queryResult = await conn.query(
21-
"MATCH (a:person) -[s:studyAt]-> (b:organisation) WHERE a.ID = 0 RETURN s.level"
21+
"MATCH (a:person) -[s:studyAt]-> (b:organisation) WHERE a.ID = 0 RETURN s.level"
2222
);
2323
const result = await queryResult.getAll();
2424
assert.equal(result.length, 1);
@@ -74,7 +74,7 @@ describe("INT64", function () {
7474
describe("UINT8", function () {
7575
it("should convert UINT8 type", async function () {
7676
const queryResult = await conn.query(
77-
"MATCH (a:person) -[s:studyAt]-> (b:organisation) WHERE a.ID = 0 RETURN s.ulevel"
77+
"MATCH (a:person) -[s:studyAt]-> (b:organisation) WHERE a.ID = 0 RETURN s.ulevel"
7878
);
7979
const result = await queryResult.getAll();
8080
assert.equal(result.length, 1);
@@ -88,7 +88,7 @@ describe("UINT8", function () {
8888
describe("UINT16", function () {
8989
it("should convert UINT16 type", async function () {
9090
const queryResult = await conn.query(
91-
"MATCH (a:person) -[s:studyAt]-> (b:organisation) WHERE a.ID = 0 RETURN s.ulength"
91+
"MATCH (a:person) -[s:studyAt]-> (b:organisation) WHERE a.ID = 0 RETURN s.ulength"
9292
);
9393
const result = await queryResult.getAll();
9494
assert.equal(result.length, 1);
@@ -102,7 +102,7 @@ describe("UINT16", function () {
102102
describe("UINT32", function () {
103103
it("should convert UINT32 type", async function () {
104104
const queryResult = await conn.query(
105-
"MATCH (a:person) -[s:studyAt]-> (b:organisation) WHERE a.ID = 0 RETURN s.temprature"
105+
"MATCH (a:person) -[s:studyAt]-> (b:organisation) WHERE a.ID = 0 RETURN s.temprature"
106106
);
107107
const result = await queryResult.getAll();
108108
assert.equal(result.length, 1);
@@ -116,7 +116,7 @@ describe("UINT32", function () {
116116
describe("UINT64", function () {
117117
it("should convert UINT64 type", async function () {
118118
const queryResult = await conn.query(
119-
"MATCH (a:person) -[s:studyAt]-> (b:organisation) WHERE a.ID = 0 RETURN s.code"
119+
"MATCH (a:person) -[s:studyAt]-> (b:organisation) WHERE a.ID = 0 RETURN s.code"
120120
);
121121
const result = await queryResult.getAll();
122122
assert.equal(result.length, 1);
@@ -252,7 +252,7 @@ describe("TIMESTAMP", function () {
252252
describe("TIMESTAMP_TZ", function () {
253253
it("should convert TIMESTAMP_TZ type", async function () {
254254
const queryResult = await conn.query(
255-
"MATCH (a:movies) WHERE a.length = 126 RETURN a.description;"
255+
"MATCH (a:movies) WHERE a.length = 126 RETURN a.description;"
256256
);
257257
const result = await queryResult.getAll();
258258
assert.equal(result.length, 1);
@@ -261,16 +261,16 @@ describe("TIMESTAMP_TZ", function () {
261261
assert.isTrue("release_tz" in result[0]["a.description"]);
262262
assert.equal(typeof result[0]["a.description"]["release_tz"], "object");
263263
assert.equal(
264-
Number(result[0]["a.description"]["release_tz"].getTime()),
265-
Number(new Date("2011-08-20 11:25:30.123Z"))
264+
Number(result[0]["a.description"]["release_tz"].getTime()),
265+
Number(new Date("2011-08-20 11:25:30.123Z"))
266266
);
267267
});
268268
});
269269

270270
describe("TIMESTAMP_NS", function () {
271271
it("should convert TIMESTAMP_NS type", async function () {
272272
const queryResult = await conn.query(
273-
"MATCH (a:movies) WHERE a.length = 126 RETURN a.description;"
273+
"MATCH (a:movies) WHERE a.length = 126 RETURN a.description;"
274274
);
275275
const result = await queryResult.getAll();
276276
assert.equal(result.length, 1);
@@ -279,16 +279,16 @@ describe("TIMESTAMP_NS", function () {
279279
assert.isTrue("release_ns" in result[0]["a.description"]);
280280
assert.equal(typeof result[0]["a.description"]["release_ns"], "object");
281281
assert.equal(
282-
Number(result[0]["a.description"]["release_ns"].getTime()),
283-
Number(new Date("2011-08-20 11:25:30.123Z"))
282+
Number(result[0]["a.description"]["release_ns"].getTime()),
283+
Number(new Date("2011-08-20 11:25:30.123Z"))
284284
);
285285
});
286286
});
287287

288288
describe("TIMESTAMP_MS", function () {
289289
it("should convert TIMESTAMP_MS type", async function () {
290290
const queryResult = await conn.query(
291-
"MATCH (a:movies) WHERE a.length = 126 RETURN a.description;"
291+
"MATCH (a:movies) WHERE a.length = 126 RETURN a.description;"
292292
);
293293
const result = await queryResult.getAll();
294294
assert.equal(result.length, 1);
@@ -297,16 +297,16 @@ describe("TIMESTAMP_MS", function () {
297297
assert.isTrue("release_ms" in result[0]["a.description"]);
298298
assert.equal(typeof result[0]["a.description"]["release_ms"], "object");
299299
assert.equal(
300-
Number(result[0]["a.description"]["release_ms"].getTime()),
301-
Number(new Date("2011-08-20 11:25:30.123Z"))
300+
Number(result[0]["a.description"]["release_ms"].getTime()),
301+
Number(new Date("2011-08-20 11:25:30.123Z"))
302302
);
303303
});
304304
});
305305

306306
describe("TIMESTAMP_SEC", function () {
307307
it("should convert TIMESTAMP_SEC type", async function () {
308308
const queryResult = await conn.query(
309-
"MATCH (a:movies) WHERE a.length = 126 RETURN a.description;"
309+
"MATCH (a:movies) WHERE a.length = 126 RETURN a.description;"
310310
);
311311
const result = await queryResult.getAll();
312312
assert.equal(result.length, 1);
@@ -315,8 +315,8 @@ describe("TIMESTAMP_SEC", function () {
315315
assert.isTrue("release_sec" in result[0]["a.description"]);
316316
assert.equal(typeof result[0]["a.description"]["release_sec"], "object");
317317
assert.equal(
318-
Number(result[0]["a.description"]["release_sec"].getTime()),
319-
Number(new Date("2011-08-20 11:25:30Z"))
318+
Number(result[0]["a.description"]["release_sec"].getTime()),
319+
Number(new Date("2011-08-20 11:25:30Z"))
320320
);
321321
});
322322
});
@@ -458,7 +458,7 @@ describe("REL", function () {
458458
describe("RECURSIVE_REL", function () {
459459
it("should convert RECURSIVE_REL type", async function () {
460460
const queryResult = await conn.query(
461-
"MATCH (a:person)-[e*1..1]->(b:organisation) WHERE a.fName = 'Alice' RETURN e;"
461+
"MATCH (a:person)-[e:studyAt*1..1]->(b:organisation) WHERE a.fName = 'Alice' RETURN e;"
462462
);
463463
const result = await queryResult.getAll();
464464
assert.equal(result.length, 1);
@@ -468,10 +468,6 @@ describe("RECURSIVE_REL", function () {
468468
_nodes: [],
469469
_rels: [
470470
{
471-
date: null,
472-
meetTime: null,
473-
validInterval: null,
474-
comments: null,
475471
year: 2021,
476472
places: ["wwAewsdndweusd", "wek"],
477473
length: 5,
@@ -481,17 +477,6 @@ describe("RECURSIVE_REL", function () {
481477
ulength: 33768,
482478
ulevel: 250,
483479
hugedata: BigInt("1844674407370955161811111111"),
484-
grading: null,
485-
rating: null,
486-
location: null,
487-
times: null,
488-
data: null,
489-
usedAddress: null,
490-
address: null,
491-
someMap: null,
492-
note: null,
493-
notes: null,
494-
summary: null,
495480
_label: "studyAt",
496481
_src: {
497482
offset: 0,
@@ -517,6 +502,34 @@ describe("MAP", function () {
517502
assert.equal(Object.keys(result[0]).length, 1);
518503
assert.isTrue("m.audience" in result[0]);
519504
assert.equal(typeof result[0]["m.audience"], "object");
520-
assert.deepEqual(result[0]["m.audience"], {'audience1': 33});
505+
assert.deepEqual(result[0]["m.audience"], { audience1: 33 });
506+
});
507+
});
508+
509+
describe("RDF_VARIANT", function () {
510+
it("should convert RDF_VARIANT type", async function () {
511+
const queryResult = await conn.query(
512+
"MATCH (a:T_l) RETURN a.val ORDER BY a.id;"
513+
);
514+
const result = await queryResult.getAll();
515+
assert.equal(result.length, 16);
516+
assert.deepEqual(result[0]["a.val"], 12);
517+
assert.deepEqual(result[1]["a.val"], 43);
518+
assert.deepEqual(result[2]["a.val"], 33);
519+
assert.deepEqual(result[3]["a.val"], 2);
520+
assert.deepEqual(result[4]["a.val"], 90);
521+
assert.deepEqual(result[5]["a.val"], 77);
522+
assert.deepEqual(result[6]["a.val"], 12);
523+
assert.deepEqual(result[7]["a.val"], 1);
524+
let val = result[8]["a.val"];
525+
assert.approximately(val, 4.4, EPSILON);
526+
val = result[9]["a.val"];
527+
assert.approximately(val, 1.2, EPSILON);
528+
assert.deepEqual(result[10]["a.val"], true);
529+
assert.deepEqual(result[11]["a.val"], "hhh");
530+
assert.deepEqual(result[12]["a.val"], new Date("2024-01-01"));
531+
assert.deepEqual(result[13]["a.val"], new Date("2024-01-01 11:25:30Z"));
532+
assert.deepEqual(result[14]["a.val"], 172800000);
533+
assert.deepEqual(result[15]["a.val"], new Uint8Array([0xb2]));
521534
});
522535
});

0 commit comments

Comments
 (0)