Replace the age example in the README and document datetime columns
Use the release year from Neo4j's Movies dataset for the typed column example, and show how to turn a Unix timestamp and an ISO 8601 string into timestamp and timestamptz generated columns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ee980b27fe
commit
ad2dde4d2f
1 file changed
+46
-8
@@ -89,25 +89,63 @@ returns a property as text. For typed columns, add them yourself, either as a vi
|
||||
as a generated column:
|
||||
|
||||
```sql
|
||||
CREATE VIEW neo4j.people AS
|
||||
SELECT id, properties ->> 'name'::text AS name, (properties ->> 'age'::text)::bigint AS age
|
||||
FROM neo4j."Person";
|
||||
CREATE VIEW neo4j.movies AS
|
||||
SELECT id, properties ->> 'title'::text AS title, (properties ->> 'released'::text)::integer AS released
|
||||
FROM neo4j."Movie";
|
||||
|
||||
ALTER TABLE neo4j."Person"
|
||||
ADD COLUMN age bigint GENERATED ALWAYS AS ((properties ->> 'age'::text)::bigint) STORED;
|
||||
ALTER TABLE neo4j."Movie"
|
||||
ADD COLUMN released integer GENERATED ALWAYS AS ((properties ->> 'released'::text)::integer) STORED;
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
- 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
|
||||
column above, `CREATE (:Movie {released: 'soon'})` 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.
|
||||
|
||||
### Dates and times
|
||||
|
||||
AGE has no date or time types, so store a point in time either as a Unix timestamp
|
||||
(seconds since 1970-01-01 UTC) or as an ISO 8601 string, and convert it in SQL. A Unix
|
||||
timestamp converts with the built-in `to_timestamp`, here to a `timestamp` in UTC:
|
||||
|
||||
```sql
|
||||
ALTER TABLE neo4j."Movie"
|
||||
ADD COLUMN updated timestamp GENERATED ALWAYS AS
|
||||
(to_timestamp((properties ->> 'updated'::text)::bigint) AT TIME ZONE 'UTC') STORED;
|
||||
```
|
||||
|
||||
A generated column only accepts immutable functions, and casting text to `timestamptz`
|
||||
is not immutable, because the result depends on the `TimeZone` and `DateStyle`
|
||||
settings. A view can use the cast as is, but a generated column needs a function that
|
||||
only accepts strings with a UTC offset and parses them the same way whatever the
|
||||
session settings are:
|
||||
|
||||
```sql
|
||||
CREATE FUNCTION public.iso8601_timestamptz(value text) RETURNS timestamptz
|
||||
LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE
|
||||
SET TimeZone = 'UTC'
|
||||
AS $$
|
||||
BEGIN
|
||||
IF value !~ '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}(:?\d{2})?)$' THEN
|
||||
RAISE EXCEPTION 'not an ISO 8601 timestamp with a UTC offset: %', value;
|
||||
END IF;
|
||||
RETURN value::timestamptz;
|
||||
END
|
||||
$$;
|
||||
|
||||
ALTER TABLE neo4j."Movie"
|
||||
ADD COLUMN premiere timestamptz GENERATED ALWAYS AS
|
||||
(public.iso8601_timestamptz(properties ->> 'premiere'::text)) STORED;
|
||||
```
|
||||
|
||||
With these columns, `CREATE (:Movie {title: 'The Matrix', updated: 1729500000,
|
||||
premiere: '1999-03-24T19:00:00-08:00'})` fills in both, and a `premiere` without an
|
||||
offset, such as `'1999-03-24T19:00:00'`, makes the write fail.
|
||||
|
||||
## Container images
|
||||
|
||||
|
||||
Reference in new issue
Block a user