2.21. Frame Row References (Row IDs)¶
In OpenCypher, variables are bound to a single row allowing a user to access that row during a query. Sometimes, though, a user may want a reference to a row to exist outside a query. xGT provides a row ID that is a handle to a row in a frame. The handle is unique across all the rows in all the frames in the system. Row IDs are available in both the OpenCypher language as well as in the Python client. Paths, when returned by a query, are stored as a list of row IDs.
Like a variable, a row ID allows a user to access the data in the row and refer to the row in OpenCypher queries.
In an OpenCypher query, variables and row IDs are treated identically and can be used the same ways.
A row ID is compared only for identity: = and <> are supported, while the ordering comparisons <, >, <= and >= are a type error (see Constraints).
A row ID encodes both the frame and position (offset) of the row within that frame.
The row IDs can be stored in frames, but only a query can create a frame with a column of row ID type.
Row IDs can be stored in a different frame than the frame they point to.
The column type for a row ID is ROWID or a list of type ROWID.
A frame that has a column of type ROWID cannot be saved to Parquet (see Saving Parquet Files).
Results frames that contain row IDs are read-only. Existing data cannot be modified or deleted, and no more data can be added to these results frames either via OpenCypher or Python commands. This does not prevent the creation of multiple result frames referring back to the same source frame.
A simple query returning row IDs uses the OpenCypher id() function:
MATCH (v0:Vertex)-[e:Edge]->(v1:Vertex)
RETURN id(v0) AS source_id, id(e) AS edge_id
INTO Results
In this case, the Results frame will have two columns each with a single row ID: one for the source vertex and a second one for the edge.
The row IDs in the first result column all refer to the frame Vertex, while the row IDs in the second column all refer to the frame Edge.
Note that the use of the id() function is required to prevent xGT from expanding the corresponding frame element into all its properties.
That expansion-by-default is itself deprecated as of 2.7.0: a future release will return element references by default, with property expansion available when retrieving the data (see the expand_variables parameter of xgt.Connection.run_job() to pin either behavior explicitly).
Deprecated since version 2.7.0: The id() function – both this form and the constructor variant below – is deprecated in favor of elementId(), which returns a stable string reference instead of a ROWID value (see Element IDs).
To look up a vertex by its key, use key equality in the pattern (MATCH (v:Vertex) WHERE v.key = $val), which is also a direct row access.
One use is deliberately not carried forward: constructing a row ID from a raw row position.
Ordering and collect still work over the element reference itself – with variable expansion off, ORDER BY v produces the same order as ORDER BY id(v) – while min and max over row identity currently require the id() form.
id() keeps working while deprecated.
Another example of a query generating row IDs is returning a path variable:
MATCH p = (v0:SourceVertex)-[e:Edge]->(v1:TargetVertex)
RETURN p
INTO Results
In this case, the Results frame will have a single column named p that stores lists of row IDs corresponding to the three element paths that matched the specified pattern.
Note that in this example, the row IDs in the first column correspond to elements in three different frames: SourceVertex, Edge and TargetVertex.
xGT distinguishes strictly between row IDs originating from different frames.
Row IDs can also be constructed from a frame and either a row position or vertex key using the id() function.
See Type Construction Functions for a detailed description.
RETURN id(graph__VertexA, 'key0') AS a_id
INTO Results
In this case, the code constructs a row ID from the name of a vertex frame and a value for its key property using a variant of the id() function.
The Results frame will store the row ID corresponding to the vertex that matches that key value.
2.21.1. Row IDs in Python¶
The class xgt.RowID is the representation of a frame row in the Python client.
Instances of this class are automatically constructed when retrieving data from frames that have columns containing references to frame rows, like the examples above.
query = """
MATCH (v0)-[e]->(v1)
RETURN id(v0) AS source_id, id(e) AS edge_id
INTO Results
"""
conn.run_job(query)
data = conn.get_frame('Results').get_data()
In this case, the variable data will consist of a list of lists of instances of the xgt.RowID class.
The Python RowID class has the get_data() method that retrieves the properties of the corresponding frame row.
It also provides properties to access the frame the row comes from and the row’s position (offset) within that frame, and an element_id property returning the row’s elementId() string reference.
Deprecated since version 2.7.0: The RowID class will be replaced by the element classes (Vertex, Edge, TableRow).
Prefer retrieving row ID columns with expand = 'light' or 'full' (next section), which returns elements that already carry their properties, and use the element_id property for references that can be stored or passed back into queries.
RowID keeps working and remains the default return type for now.
2.21.2. Expanding Row IDs to Elements¶
Retrieving the properties of many rows through individual xgt.RowID.get_data() calls requires one server request per row.
The expand parameter of xgt.Job.get_data() and xgt.TableFrame.get_data() instead expands every row ID in the results in a single request, returning element objects that already carry their properties.
The parameter accepts “none” (the default, returning RowID instances as above), “light” (returning elements with their properties), and “full” (additionally including the source frame’s metadata, such as its name and schema).
The expanded objects are instances of Vertex, Edge, or TableRow, matching the kind of frame the row ID refers to.
query = """
MATCH (v0)-[e]->(v1)
RETURN id(v0) AS source_id, id(e) AS edge_id
INTO Results
"""
conn.run_job(query)
data = conn.get_frame('Results').get_data(expand = 'light')
first_vertex = data[0][0]
print(first_vertex.properties)
In this case, each entry of data holds a Vertex and an Edge instead of two row IDs, and the properties are available without further requests.
Each element also carries its row ID, so it can be passed back into queries anywhere a row ID is accepted.
Two elements compare equal when they refer to the same frame row, regardless of how much detail they were retrieved with.
Expansion is available for the “python” and “pandas” formats.
A row ID stored inside a list column, such as a path, is not expanded and is returned as a RowID.
Experimental: The element classes (:py:class:`~xgt.Vertex`, :py:class:`~xgt.Edge`, :py:class:`~xgt.TableRow`) and the API of the ``expand`` parameter may change in future releases.
2.21.3. Element IDs¶
The elementId() function returns a stable string reference to the frame row bound to a vertex, edge, or table-row variable.
Unlike id(), which produces a value of type ROWID, elementId() produces ordinary text, so the reference can be stored in any text column, written to a file, or held outside of xGT entirely.
elementId() is the recommended way to capture element references; id() is deprecated as of 2.7.0.
MATCH (v:Vertex)
WHERE v.airport = 'SEA'
RETURN elementId(v) AS ref
INTO Results
A captured reference selects the same element in a later query by passing it back as a parameter. The lookup is a direct row access, not a scan of the frame.
job = conn.run_job("MATCH (v:Vertex) WHERE elementId(v) = $ref RETURN v.airport",
parameters = { 'ref': ref })
An expanded element object (see the previous section) can be passed back the same way using whole-element equality: MATCH (v:Vertex) WHERE v = $obj, with the Vertex, Edge, or TableRow instance as the parameter.
Expanded elements also expose their reference directly through the element_id property, so a Python application can capture references without a separate query.
The string is an opaque token: applications should store and compare it, not parse it. An element ID follows the same validity rules as a row ID, described in the next section; passing a reference that has been invalidated by deletions in its frame raises an error.
2.21.4. Lifetime and Validity of Row IDs¶
A Row ID is invalidated whenever the frame it refers to has a deletion operation. The reason for this is that the position of the rows can change when deletions happen as xGT optimizes memory space usage for the frame.
The validity of a row ID is verified when it is accessed in an OpenCypher query or through the Python client. If deletions have occurred in the source frame since the row ID was created, then access is prevented and an error is reported.
For example, if we had stored the path variables produced by this query:
MATCH p = (v0:SourceVertex)-[e:Edge]->(v1:TargetVertex)
RETURN p
INTO Results
Then a deletion on the SourceVertex frame will invalidate the row IDs for that frame:
MATCH (v0:SourceVertex)
WHERE v0.key = 10
DETACH DELETE v0
Subsequent accesses to the Results frame on the path entries corresponding to the modified frame will fail:
MATCH (t0:Results)
RETURN t0.p[0] AS mysrc