Connecting#
Opening a connection#
import agensgraph
conn = agensgraph.connect("host=localhost dbname=docs user=agens")
agensgraph.connect() takes what psycopg’s connect() takes:
a libpq connection string,
"host=localhost port=5432 dbname=docs user=agens", or the same as a URL,"postgresql://agens@localhost:5432/docs";libpq settings as keyword arguments,
agensgraph.connect(host="localhost", dbname="docs"), which are merged into the string;psycopg’s own options, such as
autocommit=True,row_factory=orprepare_threshold=.
Anything left out comes from libpq’s environment variables (PGHOST, PGPORT, PGUSER,
PGPASSWORD, PGDATABASE) and the password file, exactly as for psql.
It returns an agensgraph.Connection. That class is a subclass of psycopg.Connection: everything
psycopg documents for a connection works on it unchanged, including cursors, server-side cursors,
COPY, pipelines, LISTEN/NOTIFY and two-phase commit. What the driver adds is the graph types,
the graph selection described below, and the methods documented in the rest of this manual.
The server is checked first#
AgensGraph sends its version in the first packet of every connection. The driver reads it there, so the check costs no extra round trip, and refuses a server it cannot read before you send anything:
>>> agensgraph.Capabilities("2.16")
Traceback (most recent call last):
...
agensgraph.errors.CapabilityError: this driver needs AgensGraph 2.17 or later; this server is 2.16
A plain PostgreSQL server, which sends no AgensGraph version at all, is refused the same way. The connection is closed before the exception reaches you.
Use it as a context manager#
with agensgraph.connect("host=localhost dbname=docs user=agens") as db:
db.graph("moviekb")
db.execute_query("MATCH (m:movie {title: 'Inception'}) SET m.seen = true")
At the end of the block the connection commits if the block finished normally, rolls back if it raised, and closes either way. This is psycopg’s behaviour, and it is the usual way to hold a connection for one unit of work.
Connection defaults the driver changes#
TCP keepalive is on. A connection waiting for an answer from a server whose network has silently gone away would otherwise wait for the operating system to notice, which on Linux takes over two hours. The driver asks for keepalive probes unless you have set them:
>>> {k: v for k, v in conn.info.get_parameters().items() if k.startswith("keepalives")}
{'keepalives': '1', 'keepalives_idle': '30', 'keepalives_interval': '10', 'keepalives_count': '3'}
Each setting is filled in only if you have not given it, in the string or as a keyword, so naming
one keeps your value and fills in the rest. keepalives=0 turns them all off. Timeouts and
cancellation explains why tcp_user_timeout
is not a substitute.
Everything else is psycopg’s default. In particular autocommit is off: the first statement opens a transaction, and nothing is visible to other sessions until you commit.
Selecting a graph#
A Cypher statement resolves its label names in one graph, the session’s graph_path. A new
connection has none, unless the database or the role has a default, and a Cypher statement then
fails:
>>> other = agensgraph.connect("host=localhost dbname=docs user=agens")
>>> other.execute_query("MATCH (n) RETURN count(n)")
Traceback (most recent call last):
...
psycopg.errors.InvalidSchemaName: graph_path is NULL
>>> other.close()
Select one with graph():
>>> conn.graph("moviekb")
>>> conn.execute_query("MATCH (n) RETURN count(n)").records
[(46,)]
graph(name) sends SET graph_path with the name quoted, so a graph called "Movie KB" or
match is selected as written. It also reads the graph’s labels into the connection’s label
table, which the composite rendering needs to name a label (see Graph values).
A graph that does not exist is refused by the server:
>>> conn.graph("nosuch")
Traceback (most recent call last):
...
psycopg.errors.InvalidParameterValue: invalid value for parameter "graph_path": "nosuch"
>>> conn.rollback()
A rollback undoes it#
SET graph_path is an ordinary statement, so on a connection that is not in autocommit it is part
of the open transaction, and a rollback undoes it. A failure later in that transaction, and the
rollback() it needs, leaves the session with no graph:
>>> conn.graph("moviekb")
>>> conn.execute_query("RETURN 1 / 0")
Traceback (most recent call last):
...
psycopg.errors.DivisionByZero: division by zero
>>> conn.rollback()
>>> conn.execute_query("MATCH (n) RETURN count(n)")
Traceback (most recent call last):
...
psycopg.errors.InvalidSchemaName: graph_path is NULL
>>> conn.rollback()
The driver follows the session there: the rollback empties the label table, so it cannot name labels from a graph the session has left. Make the selection survive by committing it:
>>> conn.graph("moviekb")
>>> conn.commit()
or by selecting it in a transaction of its own, with conn.transaction(): conn.graph("moviekb"),
or by working in autocommit. A pool takes graph= and does this for you
on every connection it opens.
Other ways to select a graph#
A default for the database or the role. Every new session then starts in that graph, with no statement from the application:
ALTER DATABASE docs SET graph_path = moviekb;
ALTER ROLE app SET graph_path = moviekb;
A startup option, agensgraph.connect(..., options="-c graph_path=moviekb"), sets it as the
session starts. Cypher works at once.
In both cases the driver has not read the labels, so the label table is empty. Text results, the
default, need nothing from it; before asking for the composite rendering, call
conn.refresh_labels(), which fills the table for whatever graph the session is on.
SET graph_path written yourself works too. The driver notices any statement that changes it
and drops the label table, so it never names a label from the wrong graph.
There is no graph= argument to connect(); libpq refuses it as an unknown option.
Keeping the label table current#
The table holds the labels that existed when it was filled. A label created afterwards, by
CREATE VLABEL or by a write that creates one, is not in it until you call refresh_labels().
Reading an element of an unknown label in the composite rendering raises
agensgraph.errors.StaleLabelCache rather than guessing its name; text results are never affected.
ensure_labels() refreshes the table for you.
From AgensGraph 2.18.6, the server reports graph_path to the client whenever it changes, and the
driver reads the session’s graph from there at no cost, across commits, rollbacks and
transaction() blocks. On earlier servers it watches the statements that pass and asks the server
when it cannot tell.
Awaiting code#
AsyncConnection is the same interface for asyncio. The methods that wait for the server are
coroutines; everything else is shared code, and the blocking interface is generated from the
awaiting one, so the two behave the same.
import agensgraph
async def count_people():
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)")
return result.records[0][0]
>>> await count_people()
28
AsyncConnection.connect() is the awaiting entry point; the module-level agensgraph.connect() is
blocking only. Where this manual shows a blocking call, its awaiting form is the same call awaited;
for row in conn.stream(...) becomes async for row in conn.stream(...), and with becomes
async with. The few helpers that take a connection and need an awaiting form are named for it,
adapters.async_assert_oids(conn) beside adapters.assert_oids(conn) for example.
What the server can do#
>>> conn.capabilities.reported
'2.18.6...'
>>> conn.capabilities.version
(2, 18)
capabilities.reported is the exact version string the server sent, and version is its first
two numbers: the AgensGraph line, whose second number is the PostgreSQL major version it is built
on. Nothing after those two numbers changes what the driver does.
Four methods report a feature of the 2.18 line, each from the version alone:
|
a property kept in a column of its own |
|
the GQL clauses, such as |
|
|
|
the planner skipping endpoint reads, visible in a plan |
Each returns a bool, or with check=True raises CapabilityError naming the version needed:
>>> agensgraph.Capabilities("2.17.0").has_gql_clauses(check=True)
Traceback (most recent call last):
...
agensgraph.errors.CapabilityError: the GQL surface needs AgensGraph 2.18 or later; this server is 2.17.0
A version describes a release, not the build in front of you, and development builds of one line
have differed. Where the answer must be right, ask the server instead:
conn.can_promote_properties() checks the catalog, once per connection. Compatibility
lists what each line supports.
Closing#
conn.close()
Closing rolls back anything not committed. A closed connection reports conn.closed, and one
whose network failed reports conn.broken, so code holding a connection can tell the two apart
without reading error messages.