Skip to content

Commit 2970056

Browse files
robsdedudebigmontzthelonelyvulpesfbivilleinjectives
authored
Add tests for Bolt fix for broken DateTime encoding (#470)
The structures with signature `0x46` and `0x66` are being replaced by `0x49` and `0x69`. This new structures changes the meaning of seconds and nano seconds from `adjusted Unix epoch` to `UTC`. This changes have with goal of avoiding un-existing or ambiguous ZonedDateTime to be received or sent over Bolt. Bolt v4.3 and v4.4 were patched to support this feature if the server supports the patch. Co-authored-by: Antonio Barcelos <[email protected]> Co-authored-by: grant lodge <[email protected]> Co-authored-by: Florent Biville <[email protected]> Co-authored-by: Dmitriy Tverdiakov <[email protected]>
1 parent 5a625dc commit 2970056

File tree

115 files changed

+1782
-541
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+1782
-541
lines changed

boltstub/packstream.py

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from codecs import decode
2121
import inspect
2222
from io import BytesIO
23+
import re
2324
from struct import pack as struct_pack
2425
from struct import unpack as struct_unpack
2526

@@ -77,7 +78,8 @@ class StructTagV1:
7778

7879

7980
class StructTagV2(StructTagV1):
80-
pass
81+
date_time = b"\x49"
82+
date_time_zone_id = b"\x69"
8183

8284

8385
class Structure:
@@ -124,9 +126,14 @@ def __repr__(self):
124126

125127
def __eq__(self, other):
126128
try:
129+
assert all(
130+
StructTagV1.path == value.path
131+
for key, value in locals().items()
132+
if re.match(r"^StructTagV[1-9]\d*$", key)
133+
)
127134
if self.tag == StructTagV1.path:
128135
# path struct => order of nodes and rels is irrelevant
129-
return (other.tag == StructTagV1.path
136+
return (other.tag == self.tag
130137
and len(other.fields) == 3
131138
and sorted(self.fields[0]) == sorted(other.fields[0])
132139
and sorted(self.fields[1]) == sorted(other.fields[1])
@@ -169,7 +176,8 @@ def match_jolt_wildcard(self, wildcard: jolt_common_types.JoltWildcard):
169176
if self.tag == struct_tags.local_time:
170177
return True
171178
elif issubclass(t, jolt_types_.JoltDateTime):
172-
if self.tag == struct_tags.date_time:
179+
if self.tag in (struct_tags.date_time,
180+
struct_tags.date_time_zone_id):
173181
return True
174182
elif issubclass(t, jolt_types_.JoltLocalDateTime):
175183
if self.tag == struct_tags.local_date_time:
@@ -201,8 +209,13 @@ def _from_jolt_v1_type(cls, jolt: jolt_v1_types.JoltType):
201209
return cls(StructTagV1.local_time, jolt.nanoseconds,
202210
packstream_version=1)
203211
if isinstance(jolt, jolt_v1_types.JoltDateTime):
204-
return cls(StructTagV1.date_time, *jolt.seconds_nanoseconds,
205-
jolt.time.utc_offset, packstream_version=1)
212+
if jolt.time.zone_id:
213+
return cls(StructTagV1.date_time_zone_id,
214+
*jolt.seconds_nanoseconds, jolt.time.zone_id,
215+
packstream_version=1)
216+
else:
217+
return cls(StructTagV1.date_time, *jolt.seconds_nanoseconds,
218+
jolt.time.utc_offset, packstream_version=1)
206219
if isinstance(jolt, jolt_v1_types.JoltLocalDateTime):
207220
return cls(StructTagV1.local_date_time, *jolt.seconds_nanoseconds,
208221
packstream_version=1)
@@ -276,34 +289,39 @@ def _from_jolt_v1_type(cls, jolt: jolt_v1_types.JoltType):
276289
@classmethod
277290
def _from_jolt_v2_type(cls, jolt: jolt_v1_types.JoltType):
278291
if isinstance(jolt, jolt_v2_types.JoltDate):
279-
return cls(StructTagV1.date, jolt.days, packstream_version=2)
292+
return cls(StructTagV2.date, jolt.days, packstream_version=2)
280293
if isinstance(jolt, jolt_v2_types.JoltTime):
281-
return cls(StructTagV1.time, jolt.nanoseconds, jolt.utc_offset,
294+
return cls(StructTagV2.time, jolt.nanoseconds, jolt.utc_offset,
282295
packstream_version=2)
283296
if isinstance(jolt, jolt_v2_types.JoltLocalTime):
284-
return cls(StructTagV1.local_time, jolt.nanoseconds,
297+
return cls(StructTagV2.local_time, jolt.nanoseconds,
285298
packstream_version=2)
286299
if isinstance(jolt, jolt_v2_types.JoltDateTime):
287-
return cls(StructTagV1.date_time, *jolt.seconds_nanoseconds,
288-
jolt.time.utc_offset, packstream_version=2)
300+
if jolt.time.zone_id:
301+
return cls(StructTagV2.date_time_zone_id,
302+
*jolt.seconds_nanoseconds, jolt.time.zone_id,
303+
packstream_version=2)
304+
else:
305+
return cls(StructTagV2.date_time, *jolt.seconds_nanoseconds,
306+
jolt.time.utc_offset, packstream_version=2)
289307
if isinstance(jolt, jolt_v2_types.JoltLocalDateTime):
290-
return cls(StructTagV1.local_date_time, *jolt.seconds_nanoseconds,
308+
return cls(StructTagV2.local_date_time, *jolt.seconds_nanoseconds,
291309
packstream_version=2)
292310
if isinstance(jolt, jolt_v2_types.JoltDuration):
293-
return cls(StructTagV1.duration, jolt.months, jolt.days,
311+
return cls(StructTagV2.duration, jolt.months, jolt.days,
294312
jolt.seconds, jolt.nanoseconds, packstream_version=2)
295313
if isinstance(jolt, jolt_v2_types.JoltPoint):
296314
if jolt.z is None: # 2D
297-
return cls(StructTagV1.point_2d, jolt.srid, jolt.x, jolt.y,
315+
return cls(StructTagV2.point_2d, jolt.srid, jolt.x, jolt.y,
298316
packstream_version=2)
299317
else:
300-
return cls(StructTagV1.point_3d, jolt.srid, jolt.x, jolt.y,
318+
return cls(StructTagV2.point_3d, jolt.srid, jolt.x, jolt.y,
301319
jolt.z, packstream_version=2)
302320
if isinstance(jolt, jolt_v2_types.JoltNode):
303-
return cls(StructTagV1.node, jolt.id, jolt.labels,
321+
return cls(StructTagV2.node, jolt.id, jolt.labels,
304322
jolt.properties, jolt.element_id, packstream_version=2)
305323
if isinstance(jolt, jolt_v2_types.JoltRelationship):
306-
return cls(StructTagV1.relationship, jolt.id, jolt.start_node_id,
324+
return cls(StructTagV2.relationship, jolt.id, jolt.start_node_id,
307325
jolt.end_node_id, jolt.rel_type, jolt.properties,
308326
jolt.element_id, jolt.start_node_element_id,
309327
jolt.end_node_element_id, packstream_version=2)
@@ -331,7 +349,7 @@ def _from_jolt_v2_type(cls, jolt: jolt_v1_types.JoltType):
331349
for rel in jolt.path[1::2]:
332350
rels.append(rel)
333351

334-
ub_rel = cls(StructTagV1.unbound_relationship, rel.id,
352+
ub_rel = cls(StructTagV2.unbound_relationship, rel.id,
335353
rel.rel_type, rel.properties, rel.element_id,
336354
packstream_version=2)
337355
if ub_rel not in uniq_rels:
@@ -353,7 +371,7 @@ def _from_jolt_v2_type(cls, jolt: jolt_v1_types.JoltType):
353371
else:
354372
ids.append(-index)
355373

356-
return cls(StructTagV1.path, uniq_nodes, uniq_rels, ids,
374+
return cls(StructTagV2.path, uniq_nodes, uniq_rels, ids,
357375
packstream_version=2)
358376
raise TypeError("Unsupported jolt type: {}".format(type(jolt)))
359377

@@ -372,7 +390,7 @@ def _to_jolt_v1_type(self):
372390
return jolt_v1_types.JoltTime.new(*self.fields)
373391
if self.tag == StructTagV1.local_time:
374392
return jolt_v1_types.JoltLocalTime.new(*self.fields)
375-
if self.tag == StructTagV1.date_time:
393+
if self.tag in (StructTagV1.date_time, StructTagV1.date_time_zone_id):
376394
return jolt_v1_types.JoltDateTime.new(*self.fields)
377395
if self.tag == StructTagV1.local_date_time:
378396
return jolt_v1_types.JoltLocalDateTime.new(*self.fields)
@@ -421,7 +439,7 @@ def _to_jolt_v2_type(self):
421439
return jolt_v2_types.JoltTime.new(*self.fields)
422440
if self.tag == StructTagV2.local_time:
423441
return jolt_v2_types.JoltLocalTime.new(*self.fields)
424-
if self.tag == StructTagV2.date_time:
442+
if self.tag in (StructTagV2.date_time, StructTagV2.date_time_zone_id):
425443
return jolt_v2_types.JoltDateTime.new(*self.fields)
426444
if self.tag == StructTagV2.local_date_time:
427445
return jolt_v2_types.JoltLocalDateTime.new(*self.fields)
@@ -663,12 +681,28 @@ def _verify_relationship(cls, structure, fields):
663681

664682
@classmethod
665683
def verify_fields(cls, structure: Structure):
666-
# assert tags didn't change
667684
assert all(
668685
hasattr(StructTagV1, tag)
669686
and getattr(StructTagV1, tag) == getattr(StructTagV2, tag)
670-
for tag in dir(StructTagV2) if not tag.startswith("_")
687+
for tag in dir(StructTagV2) if not (
688+
tag.startswith("_")
689+
or tag in ("date_time", "date_time_zone_id")
690+
)
671691
)
692+
693+
tag, fields = structure.tag, structure.fields
694+
695+
field_validator = {
696+
StructTagV2.date_time: cls._build_generic_verifier(
697+
(int, int, int,), "DateTime"
698+
),
699+
StructTagV2.date_time_zone_id: cls._build_generic_verifier(
700+
(int, int, str), "DateTimeZoneId"
701+
),
702+
}
703+
704+
if tag in field_validator:
705+
return field_validator[tag](structure, fields)
672706
return super().verify_fields(structure)
673707

674708

boltstub/parsing.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
ServerExit,
2929
)
3030
from .packstream import Structure
31-
from .simple_jolt.common.types import JoltWildcard
31+
from .simple_jolt.common.types import (
32+
JoltType,
33+
JoltWildcard,
34+
)
3235

3336

3437
def load_parser():
@@ -229,12 +232,12 @@ def parse_jolt(self, jolt_package):
229232
self,
230233
"message fields failed JOLT parser"
231234
) from e
232-
decoded = self._jolt_to_struct(decoded, jolt_package)
235+
decoded = self._jolt_to_struct(decoded)
233236
jolt_fields.append(decoded)
234237
self.jolt_parsed = self.parsed[0], jolt_fields
235238
return self.jolt_parsed
236239

237-
def _jolt_to_struct(self, decoded, jolt_package):
240+
def _jolt_to_struct(self, decoded):
238241
if isinstance(decoded, JoltWildcard):
239242
if not self.allow_jolt_wildcard:
240243
raise LineError(
@@ -243,14 +246,12 @@ def _jolt_to_struct(self, decoded, jolt_package):
243246
)
244247
else:
245248
return decoded
246-
if isinstance(decoded, jolt_package.types.JoltType):
249+
if isinstance(decoded, JoltType):
247250
return Structure.from_jolt_type(decoded)
248251
if isinstance(decoded, (list, tuple)):
249-
return type(decoded)(self._jolt_to_struct(d, jolt_package)
250-
for d in decoded)
252+
return type(decoded)(self._jolt_to_struct(d) for d in decoded)
251253
if isinstance(decoded, dict):
252-
return {k: self._jolt_to_struct(v, jolt_package)
253-
for k, v in decoded.items()}
254+
return {k: self._jolt_to_struct(v) for k, v in decoded.items()}
254255
return decoded
255256

256257

@@ -1062,6 +1063,7 @@ def __str__(self):
10621063
res += ":\n"
10631064
res += "\n".join(map(str, self.expected_lines))
10641065
res += "\n\nReceived:\n" + str(self.received)
1066+
res += "\n => " + repr(self.received)
10651067
return res
10661068

10671069

boltstub/simple_jolt/v1/codec.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import abc
2+
import importlib
23
import inspect
34
import re
45
import sys
@@ -561,7 +562,13 @@ class Codec:
561562
def decode(cls, value):
562563
def transform(value_):
563564
if isinstance(value_, dict) and len(value_) == 1:
564-
sigil = next(iter(value_))
565+
sigil, content = next(iter(value_.items()))
566+
match = re.match(r"(.+)(v\d+)", sigil)
567+
if match:
568+
sigil, version = match.groups()
569+
other_codec = importlib.import_module(f"..{version}.codec",
570+
package=__package__)
571+
return other_codec.Codec.decode({sigil: content})
565572
transformer = cls.sigil_to_type.get(sigil)
566573
if transformer:
567574
return transformer.decode_full(value_[sigil], transform)

0 commit comments

Comments
 (0)