Remove automatic generated property columns
The columns could only be text, since a typed column makes Cypher writes fail on values of another type. Leave typed columns and views to the database administrator instead, and document how to set them up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fd0dda6470
commit
9a25aa11c9
7 files changed
+21
-216
No files matched your search
@@ -42,8 +42,7 @@ graph as a schema of the same name, with one table per label.
|
||||
- **Databases are AGE graphs.** Every AGE graph shows up as a Neo4j database, and
|
||||
`:use <name>` switches between them. A graph named `neo4j` is created on first start
|
||||
(`PEO_DEFAULT_GRAPH`). `CREATE DATABASE x` and `DROP DATABASE x` on the `system`
|
||||
database call `create_graph` / `drop_graph`; AGE requires names of at least 3 characters,
|
||||
and `peo` is reserved for the schema holding peo's SQL helpers.
|
||||
database call `create_graph` / `drop_graph`; AGE requires names of at least 3 characters.
|
||||
- **Queries** are sent to AGE as `SELECT * FROM cypher('<graph>', $$ <query> $$) AS (...)`.
|
||||
The bridge works out the result columns from the final `RETURN` clause and inlines
|
||||
query parameters as Cypher literals.
|
||||
@@ -60,31 +59,30 @@ graph as a schema of the same name, with one table per label.
|
||||
## Querying the graph from SQL
|
||||
|
||||
AGE stores each node label and relationship type as a table in the graph's schema,
|
||||
with all properties in one `agtype` column, `properties`. peo adds a generated
|
||||
column for every property key it finds, so you can use plain SQL:
|
||||
with all properties in one `agtype` column, `properties`; `properties ->> 'key'::text`
|
||||
returns a property as text. For typed columns, add them yourself, either as a view or
|
||||
as a generated column:
|
||||
|
||||
```sql
|
||||
SELECT name, age FROM neo4j."Person" WHERE name LIKE 'A%';
|
||||
CREATE INDEX ON neo4j."Person" (name);
|
||||
CREATE VIEW neo4j.people AS
|
||||
SELECT id, properties ->> 'name'::text AS name, (properties ->> 'age'::text)::bigint AS age
|
||||
FROM neo4j."Person";
|
||||
|
||||
ALTER TABLE neo4j."Person"
|
||||
ADD COLUMN age bigint GENERATED ALWAYS AS ((properties ->> 'age'::text)::bigint) STORED;
|
||||
```
|
||||
|
||||
AGE's Cypher writes do not fire PostgreSQL triggers, so new keys cannot be caught
|
||||
the moment they are written. Instead the bridge calls
|
||||
`CALL peo.sync_property_columns()` every few seconds
|
||||
(`PEO_PROPERTY_COLUMNS_INTERVAL`), which you can also run yourself. It only scans
|
||||
tables that had inserts or updates since its previous run, and adds all new keys of a
|
||||
table with a single `ALTER TABLE`. Once a column exists, PostgreSQL keeps it in step
|
||||
with every `CREATE`, `MERGE`, `SET` and `REMOVE`.
|
||||
PostgreSQL keeps generated columns up to date on every Cypher `CREATE`, `MERGE`, `SET`
|
||||
and `REMOVE`, and they can be indexed. Some things to keep in mind:
|
||||
|
||||
- Columns are `text`, the same as `properties->>'key'`: one key can hold values of
|
||||
different types in different rows, and a failing cast would make writes fail. Cast
|
||||
in your query, for example `age::int`. Lists and maps come out as JSON text.
|
||||
- Keys that clash with an existing column (`id`, `properties`, `start_id`, `end_id`)
|
||||
get no column. Use `properties->>'id'` for those.
|
||||
- Adding a column rewrites the table, which locks it for the duration. On a large
|
||||
table this blocks writes to that label for a while, but only once per new key.
|
||||
- AGE does not allow dropping columns from label tables, so a column stays after its
|
||||
key disappears from the data.
|
||||
- A value that does not fit the column's type makes the Cypher write fail: with the
|
||||
column above, `CREATE (:Person {age: 'old'})` is rejected. A view only fails when it
|
||||
is read.
|
||||
- AGE does not allow dropping columns from its label tables, so a generated column
|
||||
cannot be removed again.
|
||||
- Adding a stored generated column rewrites the table and locks it while doing so.
|
||||
- AGE has no date or time types, so store those as ISO 8601 strings and cast them in
|
||||
SQL.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -96,8 +94,6 @@ with every `CREATE`, `MERGE`, `SET` and `REMOVE`.
|
||||
| `PEO_ADVERTISED_BOLT_PORT` | `7687` | Bolt port advertised to the browser; set it when you publish 7687 on another host port. |
|
||||
| `PEO_ADVERTISED_HOST` | `localhost` | Host reported in `SHOW DATABASES` and to clients that do not send a routing address. |
|
||||
| `PEO_LOG_LEVEL` | `INFO` | `DEBUG` logs every generated SQL statement. |
|
||||
| `PEO_PROPERTY_COLUMNS` | `true` | Set to `false` to stop adding generated property columns. |
|
||||
| `PEO_PROPERTY_COLUMNS_INTERVAL` | `5` | Seconds between checks for new property keys. |
|
||||
| `PGADMIN_DEFAULT_EMAIL` | `admin@example.com` | pgAdmin login, created on first start. |
|
||||
| `PGADMIN_DEFAULT_PASSWORD` | `$POSTGRES_PASSWORD` | pgAdmin login password, set on first start. |
|
||||
| `PEO_PGADMIN` | `true` | Set to `false` to not start pgAdmin. |
|
||||
|
||||
Reference in new issue
Block a user