Skip to content
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
2 changes: 1 addition & 1 deletion CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ This release also includes changes from prior 3.7.x releases.
* Bumped Hadoop to 3.4.3 (and Kerby to 2.0.3) to enable `hadoop-gremlin` to build and run on Java 25.
* Add missing `Configuring` interface to `GraphStepPlaceholder` and `VertexStepPlaceholder`
* Fixed bug in `group()` value traversal where keys were retained with stale barrier state instead of being filtered when steps following a `Barrier` in the second `by()` produced no output (e.g. `by(values("age").fold().unfold())` or `by(__.out().fold().count(local).is(P.gt(0)))` for vertices with no out-edges).
* Corrected numerous inaccuracies in the reference documentation, including wrong default values (connection pool sizes, buffer sizes, ports, timeouts), stale serializer class names, removed options documented as available, and broken code examples across the JVM, Python, `.NET`, Go, and JavaScript drivers.
* Fixed bug in `gremlin-javascript` GraphBinary and GraphSON deserialization where `OffsetDateTime` values outside the JavaScript `Date` range were silently returned as invalid `Date` objects instead of failing deserialization.
* Added a `propertyMap()` helper to view an element's properties as a map keyed by property key, on the `Element` structure API in `gremlin-core` (inherited by `Vertex`, `Edge`, and `VertexProperty`) and on `Vertex`, `Edge`, and `VertexProperty` in `gremlin-javascript`, `gremlin-python`, `gremlin-dotnet`, and `gremlin-go`.
* Fixed a bug in `gremlin-go` where a `VertexProperty` deserialized from GraphBinary did not have its `Key` field populated, causing `Vertex.PropertyMap()` to group properties under an empty key.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ module.exports = class OffsetDateTimeSerializer {
// use UTC time calculated with offset above
const v = new Date(Date.UTC(year, month, date, h, m, s, ms));

// The GraphBinary DateTime/OffsetDateTime wire format can carry values (e.g. extreme years near
// +/-999999999) that fall outside the range representable by a JavaScript Date. In those cases
// Date.UTC(...) returns NaN and new Date(NaN) yields an invalid Date without throwing. Reject such
// values here so unsupported boundary date-times fail deserialization instead of silently producing
// an unusable Date instance.
if (Number.isNaN(v.getTime())) {
throw new Error('{value} is outside the range supported by JavaScript Date');
}

return { v, len };
} catch (err) {
throw this.ioc.utils.des_error({ serializer: this, args: arguments, cursor, err });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,15 @@ class OffsetDateTimeSerializer extends TypeSerializer {
}

deserialize(obj) {
return new Date(obj[valueKey]);
// An OffsetDateTime value can carry a date-time (e.g. extreme years) that falls outside the range
// representable by a JavaScript Date. In those cases new Date(...) yields an invalid Date without
// throwing. Reject such values so unsupported boundary date-times fail deserialization instead of
// silently producing an unusable Date instance. Mirrors the GraphBinary OffsetDateTime reader.
const value = new Date(obj[valueKey]);
if (Number.isNaN(value.getTime())) {
throw new Error('OffsetDateTime value is outside the range supported by JavaScript Date');
}
return value;
}

canBeUsedFor(value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

'use strict';

const utils = require('./utils');
const assert = require('assert');
const ioc = require('../../../lib/structure/io/binary/GraphBinary');

const { from, concat } = Buffer;

const ID = 0x88; // OFFSETDATETIME

describe('GraphBinary.OffsetDateTimeSerializer', () => {

const type_code = from([ID]);
const value_flag = from([0x00]);

const serializer = ioc.serializers[ID];

const cases = [
{ v: undefined, fq: 1, b: [ID, 0x01], av: null },
{ v: null, fq: 1, b: [ID, 0x01] },

// year=2022(0x07e6), month=5, day=1, ns=0x000042c277bd8e00, offset=0
{ v: new Date(1651436603000),
b: [0x00,0x00,0x07,0xe6, 0x05, 0x01, 0x00,0x00,0x42,0xc2,0x77,0xbd,0x8e,0x00, 0x00,0x00,0x00,0x00] },

{ des: 1, err: /buffer is missing/, fq: 1, b: undefined },
{ des: 1, err: /buffer is empty/, fq: 1, b: [] },
{ des: 1, err: /unexpected {type_code}/, fq: 1, b: [ID - 1] },
{ des: 1, err: /{value_flag} is missing/, fq: 1, b: [ID] },
{ des: 1, err: /unexpected {value_flag}/, fq: 1, b: [ID, 0x02] },
{ des: 1, err: /unexpected {value} length/, fq: 1, b: [ID, 0x00] },

// Boundary values that fall outside the JavaScript Date range (year=999999999=0x3B9AC9FF)
// must be rejected rather than silently deserialized into an invalid Date. See TINKERPOP-3276.
{ des: 1, err: /outside the range supported by JavaScript Date/, fq: 0,
b: [0x3B,0x9A,0xC9,0xFF, 0x01, 0x01, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00] },
// year=-999999999=0xC4653601
{ des: 1, err: /outside the range supported by JavaScript Date/, fq: 0,
b: [0xC4,0x65,0x36,0x01, 0x01, 0x01, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00] },
];

describe('#serialize', () =>
cases
.filter(({ des }) => !des)
.forEach(({ v, fq, b }, i) => it(utils.ser_title({ i, v }), () => {
b = from(b);

if (fq !== undefined) {
assert.deepEqual(serializer.serialize(v, fq), b);
return;
}

assert.deepEqual(serializer.serialize(v, true), concat([type_code, value_flag, b]));
assert.deepEqual(serializer.serialize(v, false), concat([b]));
}))
);

describe('#deserialize', () =>
cases.forEach(({ v, fq, b, av, err }, i) => it(utils.des_title({ i, b }), () => {
if (Array.isArray(b))
b = from(b);

if (err !== undefined) {
if (fq !== undefined)
assert.throws(() => serializer.deserialize(b, fq), { message: err });
else {
assert.throws(() => serializer.deserialize(concat([type_code, value_flag, b]), true), { message: err });
assert.throws(() => serializer.deserialize(concat([b]), false), { message: err });
}
return;
}

if (av !== undefined)
v = av;
const len = b.length;

if (fq !== undefined) {
assert.deepStrictEqual(serializer.deserialize(b, fq), { v, len });
return;
}

assert.deepStrictEqual(serializer.deserialize(concat([type_code, value_flag, b]), true), { v, len: len + 2 });
assert.deepStrictEqual(serializer.deserialize(concat([b]), false), { v, len: len + 0 });
}))
);

describe('#canBeUsedFor', () =>
[
{ v: null, e: false },
{ v: undefined, e: false },
{ v: {}, e: false },
{ v: [], e: false },
{ v: new Date(), e: true },
].forEach(({ v, e }, i) => it(utils.cbuf_title({ i, v }), () =>
assert.strictEqual(serializer.canBeUsedFor(v), e)
))
);

});
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,22 @@ describe('GraphSONReader', function () {
const result = reader.read(obj);
assert.ok(result instanceof Date);
});
it('should parse OffsetDateTime', function() {
const obj = { "@type" : "gx:OffsetDateTime", "@value" : "2016-12-14T21:14:36.295Z" };
const reader = new GraphSONReader();
const result = reader.read(obj);
assert.ok(result instanceof Date);
assert.strictEqual(result.getTime(), 1481750076295);
});
it('should reject OffsetDateTime outside the JavaScript Date range', function() {
const reader = new GraphSONReader();
[
{ "@type" : "gx:OffsetDateTime", "@value" : "+999999-01-01T00:00:00Z" },
{ "@type" : "gx:OffsetDateTime", "@value" : "-999999-01-01T00:00:00Z" },
].forEach(function (obj) {
assert.throws(() => reader.read(obj), /outside the range supported by JavaScript Date/);
});
});
it('should parse vertices from GraphSON', function () {
const obj = {
"@type":"g:Vertex","@value":{"id":{"@type":"g:Int32","@value":1},"label":"person",
Expand Down
Loading