Quickstart#

This page connects to a server, reads vertices, edges and paths, writes to the graph, and joins graph and relational data in one transaction. It takes about ten minutes.

It uses the small film graph that every page of this manual uses, moviekb: 18 films, 28 people, and the edges between them. Load it first by running the statements on the AgensGraph manual’s example dataset page, in psql or any client. Then work down this page in a Python session.

Connect#

import agensgraph

conn = agensgraph.connect("host=localhost dbname=docs user=agens")
conn.graph("moviekb")
conn.commit()

connect() takes a libpq connection string, the same one psql takes, or the same settings as keyword arguments (agensgraph.connect(host="localhost", dbname="docs")). It returns a Connection, which is a psycopg 3 connection with the graph types added, so anything you know about psycopg applies to it.

conn.graph("moviekb") selects the graph that unqualified label names belong to. It is the driver’s form of SET graph_path = moviekb, and it also reads the graph’s labels so that values can be decoded in either of the server’s renderings. Selecting a graph is per session: a new connection starts with none, unless the database or the role has a default.

The commit() matters. Like every statement on a psycopg connection, selecting the graph opens a transaction, and a later rollback() would undo it along with everything else. Connecting explains this and the other ways to select a graph.

Read a vertex#

>>> result = conn.execute_query("MATCH (m:movie {title: 'The Matrix'}) RETURN m")
>>> result.keys
['m']
>>> (movie,) = result.records[0]
>>> movie
Vertex(movie[3.1])
>>> movie.label, movie.id
('movie', GraphId(labid=3, locid=1))
>>> movie.properties["year"], movie.properties["genre"]
(1999, 'Action')

execute_query() runs one statement and reads the whole result. It returns a Result, whose records are a list of tuples, one per row, and whose keys are the column names.

A vertex arrives as a Vertex, not as text to parse. id is its identity, a GraphId that prints the way the server prints it, 3.1. properties is an ordinary dict, decoded from the property map the first time you read it.

Read plain values#

Return properties instead of whole elements and each one arrives as the Python type of its JSON value:

>>> result = conn.execute_query(
...     "MATCH (m:movie) WHERE m.year < 1990"
...     " RETURN m.title AS title, m.year AS year ORDER BY year"
... )
>>> result.records
[('Blade Runner', 1982), ('The Terminator', 1984)]

The title is a str and the year an int. In psql the same query prints "Blade Runner" with its JSON quotes, because a property comes back as jsonb; the driver decodes it for you.

Pass parameters#

Never format values into the statement text. Pass them as parameters, with %s placeholders:

>>> conn.execute_query(
...     "MATCH (p:person) WHERE p.born >= %s AND p.born < %s RETURN p.name ORDER BY p.name",
...     (1978, 1981),
... ).records
[('Neill Blomkamp',), ('Ryan Gosling',)]

A Python str is sent as text, an int as an integer, a dict as jsonb, and so on. Parameters has the details, including the positions in a Cypher statement where a parameter is not allowed.

Follow an edge#

>>> result = conn.execute_query(
...     "MATCH (p:person)-[r:acted_in]->(m:movie {title: 'The Matrix'})"
...     " RETURN p, r ORDER BY p.name LIMIT 1"
... )
>>> person, role = result.records[0]
>>> person.properties["name"]
'Carrie-Anne Moss'
>>> role
Edge(acted_in[5.7] 4.3->3.1)
>>> role.properties["role"]
'Trinity'
>>> role.start == person.id
True

An Edge has a label, an id and properties like a vertex, plus start and end, the identities of the two vertices it joins.

Return a path#

>>> (path,) = conn.execute_query(
...     "MATCH p = (:movie {title: 'The Matrix'})-[:related_to*2]->(:movie)"
...     " RETURN p ORDER BY length(p) LIMIT 1"
... ).records[0]
>>> path
Path(3 vertices, 2 edges)
>>> path.length
2
>>> [v.properties["title"] for v in path.vertices]
['The Matrix', 'The Matrix Reloaded', 'The Matrix Revolutions']

A Path holds its vertices and edges in order. path.length is the number of hops, and iterating over a path gives vertex, edge, vertex, and so on.

Write#

>>> result = conn.execute_query(
...     "CREATE (:movie %s)",
...     ({"title": "Dune", "year": 2021, "genre": "Sci-Fi"},),
...     counts_=True,
... )
>>> result.counts.inserted_vertices
1
>>> conn.commit()

A property map is passed as a dict and goes to the server as jsonb. counts_=True asks for what the statement changed; without it the counters are not read and are all None.

A statement outside a transaction opens one, as everywhere in psycopg, so nothing is visible to anyone else until you call commit(). Use the connection as a context manager to commit on success and roll back on an exception:

with agensgraph.connect("host=localhost dbname=docs user=agens") as other:
    other.graph("moviekb")
    other.execute_query(
        "MATCH (m:movie {title: 'Dune'}) SET m.director = %s", ("Denis Villeneuve",)
    )
# committed here, because the block ended without an exception

Combine SQL and Cypher#

The example dataset has an ordinary table, rating, next to the graph. A Cypher query and a SQL query run on the same connection and in the same transaction, and one statement can contain both:

>>> conn.execute_query("""
...     SELECT r.title, r.score
...     FROM (MATCH (p:person {name: 'Keanu Reeves'})-[:acted_in]->(m:movie)
...           RETURN m.title AS title) AS g
...     JOIN rating r ON r.title = g.title #>> '{}' AND r.source = 'critics'
...     ORDER BY r.score DESC
... """).records
[('The Matrix', Decimal('8.7')), ('The Matrix Reloaded', Decimal('7.2')), ('The Matrix Revolutions', Decimal('6.7')), ('The Matrix Resurrections', Decimal('5.7'))]

The Cypher query sits where a subquery goes. Its title column is jsonb, so the outer query unwraps it with #>> '{}' before comparing it with the table’s text column. Unwrap it out here, in SQL: a cast written inside the Cypher RETURN is boxed back into jsonb by the projection. The score column is numeric and arrives as a Decimal, as it would from any PostgreSQL query.

The same in awaiting code#

AsyncConnection has the same methods, awaited:

import asyncio
import agensgraph

async def main():
    dsn = "host=localhost dbname=docs user=agens"
    async with await agensgraph.AsyncConnection.connect(dsn) as conn:
        await conn.graph("moviekb")
        result = await conn.execute_query("MATCH (p:person) RETURN count(p)")
        print(result.records)

asyncio.run(main())
[(28,)]

Close the connection#

conn.close()

Closing a connection rolls back whatever it had not committed. The with form closes it for you.

Where to go next#

  • Connecting covers connection settings, selecting a graph, and the awaiting interface in full.

  • Graph values explains vertices, edges, paths and identities.

  • Bulk loading is the fast way to write many elements.

  • Pooling is how a service should hold its connections.