Skip to content

Add Point2D and Point3D for spatial values - #88

Open
Ignition wants to merge 4 commits into
masterfrom
worktree-point-abort-repro
Open

Ignition wants to merge 4 commits into
masterfrom
worktree-point-abort-repro

Conversation

@Ignition

Copy link
Copy Markdown

What this does

Adds mgclient.Point2D and mgclient.Point3D so spatial values can be read from and sent to Memgraph, and fixes the result-discard path that turned an unconvertible row into a crash.

Point support

Memgraph returns spatial values as bolt point structures. mgclient has decoded them for some time, but mg_value_to_py_object had no case for either, so any query returning a point raised RuntimeError: encountered a mg_value of unknown type.

>>> cursor.execute("RETURN point({x: 1.5, y: 2.5}) AS p")
>>> cursor.fetchall()
[(POINT({x: 1.5, y: 2.5, srid: 7203}),)]

>>> cursor.execute("RETURN $p AS p", {"p": mgclient.Point2D(7203, 1.5, 2.5)})

A point carries the identifier of the spatial reference system it is expressed in, so the coordinates are named x_longitude, y_latitude and z_height: the same field means a Cartesian axis or a WGS84 angle depending on the srid, and the names read correctly under both. The types compare and hash by value, and the constructors take their arguments positionally or by keyword.

The API follows the shape worked out on the earlier add-spatial-types branch, with one correction: the srid for a Cartesian 3D point is 9157, not 9757. The four srids are confirmed against a running server and named as constants in the tests.

Not crashing on a row that will not convert

When a row cannot be converted to a Python object, the rest of the stream is discarded and the error raised. Two things were wrong with that path, and a point value reached both of them.

connection_discard_all asserted the connection was CONN_STATUS_EXECUTING, but connection_fetch calls it while CONN_STATUS_FETCHING. With asserts live that aborted the process. Under -DNDEBUG, which is how the wheels are built, the assert is compiled out and the function went on to pull a second time on a session that was already streaming, which the session refuses: the connection was left unusable and the error claimed the query might not have run.

A cursor may also bound its pull with a row count, so draining the current batch is not always the end of the stream. The discard now pulls again while the server reports has_more, and only then hands the connection back as ready.

Both are reachable by any value type mgclient decodes and pymgclient does not, so this is not specific to points: it is what a version skew between the two looks like.

Testing

test_types.py covers the types with no server: attributes, equality across every field, hashing, str and repr, readonly attributes, keyword arguments, and srid validation including the limits of the wire format. test_glue.py covers both directions against a real Memgraph, in both coordinate systems, nested in lists and maps, and stored as a node property.

93 pass and 14 skip on Memgraph 3.x, in a release build and in an assert-enabled one. The skips are the SSL and HA-cluster suites, which need their own servers. 20k point round trips show no memory growth.

The discard fix has no automated test. Once every mgclient type is handled the path is unreachable from Python, since it only triggers on that version skew. It was verified by hand by temporarily removing the point case to force a conversion failure again, checking that the eager, lazy, multi-row and in-transaction paths all raise cleanly and leave the connection usable.

Worth a maintainer's eye

  • CHANGELOG.rst is untouched. It currently stops at 1.5.1 while the tags reach v1.6.0, so the right heading for these entries is a call about the release process rather than something to guess at.
  • Point2D and Point3D are near-identical implementations. That matches how Node, Relationship and Path are each written by hand in this file, so it is left alone rather than made the one place that generates its slots from a macro.
  • The new types set tp_hash and return NotImplemented for a mismatched type. Node, Relationship and Path do neither, so equal instances of those hash differently and node < "foo" evaluates to False instead of raising. Pre-existing and untouched here, but the point types now sit next to three siblings that get it wrong.
  • Coordinates accept NaN and infinity without complaint. They round trip, so nothing rejects them.
  • Points cannot be pickled or copied, matching the existing graph types.

A row that cannot be converted to a Python object is reported by discarding
the rest of the result stream and raising. Callers reach that discard both
before the results have been requested and part way through consuming them,
so it drains the stream and asks the server for it only when nothing has
asked yet. A second pull mid-stream is refused by the session, which left
the connection unusable and the raised error claiming the query might not
have run.
Memgraph returns spatial values as bolt point structures, which mgclient
decodes but pymgclient had no Python type for, so any query returning a
point raised "encountered a mg_value of unknown type". Queries now yield
mgclient.Point2D and mgclient.Point3D, and both can be passed back as query
parameters.

A point carries the identifier of the spatial reference system it is
expressed in, so the two coordinates are named x_longitude and y_latitude
(and z_height) to read correctly under both the Cartesian and the WGS84
systems Memgraph supports. The types compare and hash by value.
A cursor may bound its pull with a row count, in which case the server
answers the batch with has_more set and keeps the rest of the stream. The
discard that follows a row-conversion failure now pulls again until the
server reports the stream finished, so the query is really done with before
the connection is handed back as ready.
A point can be built with keyword arguments as well as positionally, which
leaves room to add a field later without disturbing existing calls.
@Ignition
Ignition requested a review from gitbuda September 19, 2026 10:53

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant