uesleilima opened a new issue, #2353:
URL: https://github.com/apache/age/issues/2353
### Description
The `setUpAge()` function unconditionally executes `LOAD 'age'` (or `LOAD
'$libdir/plugins/age'`). The `Age.connect()` method always calls `setUpAge()`
with no option to skip the `LOAD` statement.
On managed PostgreSQL services like **Azure Database for PostgreSQL**, the
AGE extension is loaded server-side via `shared_preload_libraries`. The
extension binary may not be accessible at the file path expected by `LOAD`,
causing a `psycopg.errors.UndefinedFile` error. Users have no way to use
`Age.connect()` in these environments without bypassing the driver's setup
entirely.
This is related to #839, which discussed non-superuser access. However, our
scenario is different: on Azure managed PostgreSQL the `LOAD` command fails
regardless of privileges because the binary is not at the expected file path.
The extension is already loaded and functional — the driver just needs a way to
skip the `LOAD` statement.
### Steps to Reproduce
1. Set up Azure Database for PostgreSQL (Flexible Server) with AGE extension
enabled via `shared_preload_libraries`
2. Create the extension: `CREATE EXTENSION IF NOT EXISTS age;`
3. Attempt to connect using the Python driver:
```python
import age
ag = age.connect(
dsn="host=myserver.postgres.database.azure.com port=5432 dbname=mydb
user=myuser password=mypass",
graph="my_graph"
)
```
### Expected Behavior
The connection should succeed since AGE is already loaded and available in
the database.
### Actual Behavior
With `load_from_plugins=False` (the default):
```
psycopg.errors.UndefinedFile: could not access file "age": No such file or
directory
```
With `load_from_plugins=True`:
```
psycopg.errors.UndefinedFile: could not access file "$libdir/plugins/age":
No such file or directory
```
Neither option works because the binary path is managed by the cloud
provider and not directly accessible.
### Root Cause
In [`age.py`
`setUpAge()`](https://github.com/apache/age/blob/master/drivers/python/age/age.py):
```python
def setUpAge(conn, graphName, load_from_plugins=False):
with conn.cursor() as cursor:
if load_from_plugins:
cursor.execute("LOAD '$libdir/plugins/age';")
else:
cursor.execute("LOAD 'age';")
# ... rest of setup (search_path, adapters, graph check)
```
And in `Age.connect()`:
```python
def connect(self, graph=None, dsn=None, ..., load_from_plugins=False,
**kwargs):
conn = psycopg.connect(dsn, cursor_factory=cursor_factory, **kwargs)
setUpAge(conn, graph, load_from_plugins) # always called, no way to
skip LOAD
```
The `LOAD` statement is only needed when the AGE shared library has not been
pre-loaded into the PostgreSQL backend. On managed services, the library is
already loaded via `shared_preload_libraries`, making the `LOAD` statement
unnecessary and harmful.
### Suggested Fix
Add a `skip_load` parameter to both `setUpAge()` and `Age.connect()` that
skips the `LOAD` statement while still performing all other setup (search_path,
agtype adapter registration, graph creation):
```python
def setUpAge(conn, graphName, load_from_plugins=False, skip_load=False):
with conn.cursor() as cursor:
if not skip_load:
if load_from_plugins:
cursor.execute("LOAD '$libdir/plugins/age';")
else:
cursor.execute("LOAD 'age';")
cursor.execute("SET search_path = ag_catalog, '$user', public;")
# ... rest of adapter registration and graph setup unchanged
```
```python
def connect(self, graph=None, dsn=None, ..., load_from_plugins=False,
skip_load=False, **kwargs):
conn = psycopg.connect(dsn, cursor_factory=cursor_factory, **kwargs)
setUpAge(conn, graph, load_from_plugins, skip_load=skip_load)
# ...
```
### Current Workaround
Users must bypass `Age.connect()` entirely and manually replicate the setup
logic, which is fragile and tightly coupled to internal driver implementation
details:
```python
import age
import psycopg
from psycopg.types import TypeInfo
ag = age.Age()
ag.connection = psycopg.connect(dsn, cursor_factory=age.age.ClientCursor)
with ag.connection.cursor() as cur:
cur.execute('SET search_path = ag_catalog, "$user", public;')
ag_info = TypeInfo.fetch(ag.connection, "agtype")
ag.connection.adapters.register_loader(ag_info.oid, age.age.AgeLoader)
ag.connection.adapters.register_loader(ag_info.array_oid, age.age.AgeLoader)
ag.graphName = "my_graph"
age.checkGraphCreated(ag.connection, "my_graph")
ag.connection.commit()
```
### Environment
- Apache AGE: 1.6.0 (also affects master)
- Python driver: master branch (psycopg3 version)
- psycopg: 3.2.x
- Python: 3.13
- Managed PostgreSQL: Azure Database for PostgreSQL – Flexible Server
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]