You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by co...@apache.org on 2026/09/29 21:13:58 UTC

(tinkerpop) branch 3.7-dev updated: TINKERPOP-2363 Promote out-of-range python int to Long in GraphBinary (#3672)

This is an automated email from the ASF dual-hosted git repository.

Cole-Greer pushed a commit to branch 3.7-dev
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git


The following commit(s) were added to refs/heads/3.7-dev by this push:
     new bd141ec03e TINKERPOP-2363 Promote out-of-range python int to Long in GraphBinary (#3672)
bd141ec03e is described below

commit bd141ec03e77d1c0f9cc1c44617191f94fa5e3a9
Author: KAI <hi...@gmail.com>
AuthorDate: Wed Sep 30 02:43:52 2026 +0530

    TINKERPOP-2363 Promote out-of-range python int to Long in GraphBinary (#3672)
    
    gremlin-python's GraphBinary IntIO inherited dictify from LongIO, whose guard
    validates against the Java long range while IntIO packs with int32_pack. Python 3
    has a single arbitrary precision int, so python_type = IntType matches every plain
    int: a value between the int and long bounds passed the guard and then failed
    inside struct.pack with an error naming neither the value nor a remedy. The same
    value serializes fine under GraphSON, which has coerced numerics to Java ranges
    since TINKERPOP-2360, so switching to the default serializer broke working code.
    
    IntIO now writes a Long when the value falls outside the Java int range. Values
    inside the range are byte-identical to before, statics.long and statics.bigint
    remain explicit overrides, and values beyond the Java long range still raise the
    existing "Value too big, please use bigint Gremlin type" error. Promotion is
    skipped for value-only writes, which have no type code to change.
---
 CHANGELOG.asciidoc                                 |  2 ++
 docs/src/reference/gremlin-variants.asciidoc       |  5 +++
 .../gremlin_python/structure/io/graphbinaryV1.py   | 13 +++++++
 .../python/tests/unit/io/test_graphbinaryV1.py     | 40 ++++++++++++++++++++++
 4 files changed, 60 insertions(+)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index f97495ec38..b4e56b9526 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -25,6 +25,8 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 [[release-3-7-8]]
 === TinkerPop 3.7.8 (Release Date: NOT OFFICIALLY RELEASED YET)
 
+* Fixed `gremlin-python` GraphBinary serialization of `int` values outside the Java `int` range, which raised a `struct.error` rather than writing a `Long`.
+
 [[release-3-7-7]]
 === TinkerPop 3.7.7 (Release Date: September 1, 2026)
 
diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc
index d15c9291b0..79e7b575be 100644
--- a/docs/src/reference/gremlin-variants.asciidoc
+++ b/docs/src/reference/gremlin-variants.asciidoc
@@ -2629,6 +2629,11 @@ from gremlin_python.statics import SingleChar   # Java char type
 from gremlin_python.statics import GremlinType  # Java Class
 ----
 
+Python has a single arbitrary precision `int`, so the serializer picks the Java type from the value:
+an `int` that fits the Java `int` range is sent as an `int` and anything wider is sent as a `long`.
+Values beyond the Java `long` range have to be wrapped in `bigint`. Use `long` when a graph needs the
+wider type for a value that would otherwise fit in an `int`.
+
 anchor:python-configuration[]
 [[gremlin-python-configuration]]
 === Configuration
diff --git a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
index e82d0ed41c..dff4ceb535 100644
--- a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
+++ b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
@@ -276,6 +276,19 @@ class IntIO(LongIO):
     byte_format_pack = int32_pack
     byte_format_unpack = int32_unpack
 
+    @classmethod
+    def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
+        # Python has one arbitrary precision int type, so every plain int arrives here
+        # regardless of magnitude. Anything outside the Java int range is written as a Long
+        # instead of reaching int32_pack, which fails with a bare struct.error. GraphSON
+        # coerces the same way (TINKERPOP-2360), and statics.long and statics.bigint stay
+        # available as explicit overrides. Promotion needs a type code in front of the
+        # value, so a value-only write keeps the fixed int32 layout.
+        if not as_value and not (-2147483648 <= obj <= 2147483647):
+            return LongIO.dictify(obj, writer, to_extend, as_value, nullable)
+
+        return super().dictify(obj, writer, to_extend, as_value, nullable)
+
     @classmethod
     def objectify(cls, buff, reader, nullable=True):
         return cls.is_null(buff, reader, lambda b, r: cls.read_int(b), nullable)
diff --git a/gremlin-python/src/main/python/tests/unit/io/test_graphbinaryV1.py b/gremlin-python/src/main/python/tests/unit/io/test_graphbinaryV1.py
index b4624584b4..19d34f5577 100644
--- a/gremlin-python/src/main/python/tests/unit/io/test_graphbinaryV1.py
+++ b/gremlin-python/src/main/python/tests/unit/io/test_graphbinaryV1.py
@@ -21,6 +21,8 @@ import datetime
 import uuid
 import math
 
+import pytest
+
 from gremlin_python.statics import timestamp, long, bigint, BigDecimal, SingleByte, SingleChar, ByteBufferType
 from gremlin_python.structure.graph import Vertex, Edge, Property, VertexProperty, Path
 from gremlin_python.structure.io.graphbinaryV1 import GraphBinaryWriter, GraphBinaryReader
@@ -44,6 +46,44 @@ class TestGraphSONWriter(object):
         output = self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(x))
         assert x == output
 
+    def test_int_out_of_int32_range_promotes_to_long(self):
+        # Python has one arbitrary precision int type, so a plain int beyond the Java int
+        # range used to reach int32_pack and fail with a bare struct.error (TINKERPOP-2363).
+        for x in [2 ** 31, -2 ** 31 - 1, 3000000000, 2 ** 63 - 1, -2 ** 63]:
+            output = self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(x))
+            assert x == output
+
+    def test_int_wire_bytes_at_int32_boundary(self):
+        # The type code stays Int (0x01) inside the Java int range and becomes Long (0x02)
+        # immediately outside it, so promotion never widens a value that already fits.
+        cases = {
+            2 ** 31 - 1: b'\x01\x00\x7f\xff\xff\xff',
+            -2 ** 31: b'\x01\x00\x80\x00\x00\x00',
+            2 ** 31: b'\x02\x00\x00\x00\x00\x00\x80\x00\x00\x00',
+            -2 ** 31 - 1: b'\x02\x00\xff\xff\xff\xff\x7f\xff\xff\xff',
+        }
+        for value, expected in cases.items():
+            assert bytes(self.graphbinary_writer.write_object(value)) == expected
+
+    def test_int_out_of_int64_range_still_raises(self):
+        # Promotion stops at Long. bigint stays an explicit choice.
+        for x in [2 ** 63, -2 ** 63 - 1]:
+            with pytest.raises(Exception, match='Value too big'):
+                self.graphbinary_writer.write_object(x)
+
+    def test_int_out_of_int32_range_nested(self):
+        # The reported failure arrived as server-assigned ids inside nested bindings.
+        for x in [[[3000000000, 5000000000]], {'id': 3000000000}]:
+            output = self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(x))
+            assert x == output
+
+    def test_bool_is_not_affected_by_int_promotion(self):
+        # bool subclasses int, so it must keep resolving to the Boolean serializer.
+        for x in [True, False]:
+            output = self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(x))
+            assert x == output
+            assert isinstance(output, bool)
+
     def test_long(self):
         x = long(100)
         output = self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(x))