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.