skills/apache-age/SKILL.md
Apache AGE graph database extension for PostgreSQL — Cypher query syntax, agtype data types, hybrid SQL+Cypher patterns (CTEs, JOINs, parameter passing), graph schema modeling (vertices, edges, labels, properties), and performance optimization (indexing, EXPLAIN, batch loading). Use when writing Cypher queries, designing graph schemas, combining graph traversal with relational SQL, or optimizing AGE query performance — all within PostgreSQL via the AGE extension.
npx skillsauth add synapseradio/ai-skills apache-ageInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
CREATE EXTENSION IF NOT EXISTS age;
LOAD 'age';
SET search_path = ag_catalog, "$user", public;
SELECT create_graph('my_graph');
SELECT * FROM cypher('my_graph', $$
CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})
RETURN a, b
$$) AS (a agtype, b agtype);
| Principle | Reference | When to Load | |-----------|-----------|--------------| | The Wrapper Contract | the-wrapper-contract.md | MANDATORY for first-time AGE users. Load for cypher() mechanics, search_path, parameters, unsupported features | | agtype Is Everything | agtype-is-everything.md | Load when casting agtype to SQL types, debugging type errors, or handling nulls | | Hybrid Is the Point | hybrid-is-the-point.md | MANDATORY before writing any SQL+Cypher query. CTEs, JOINs, mutation guards, multi-graph | | Schema Is Storage | schema-is-storage.md | Load when designing graph schema, choosing labels/properties, or modeling relationships | | Nothing Is Automatic | nothing-is-automatic.md | MANDATORY before performance work. Indexes, EXPLAIN, batch loading, when to use SQL instead |
Do NOT load:
Freedom calibration:
cypher() returns SETOF record — PostgreSQL requires an explicit column list:
SELECT * FROM cypher('g', $$ MATCH (n) RETURN n $$) AS (n agtype);
CREATE, SET, DELETE at the end of a query return no rows, but the column list is still mandatory:
SELECT * FROM cypher('g', $$
CREATE (:Person {name: 'Alice'})
$$) AS (v agtype);
-- Returns 0 rows
-- Replaces ALL properties with just {age: 30}
SET n = {age: 30}
-- Merges {age: 30} into existing properties
SET n += {age: 30}
Mutations inside a JOIN cause errors. Wrap in a CTE instead:
WITH new_vertex AS (
SELECT * FROM cypher('g', $$ CREATE (p:Person {name: 'Alice'}) RETURN p $$) AS (v agtype)
)
SELECT * FROM new_vertex;
SET search_path = ag_catalog, "$user", public;
-- Or fully qualify: SELECT * FROM ag_catalog.cypher(...)
cypher() returns SETOF record, PostgreSQL demands itSET n = {...} when you mean SET n += {...} — = replaces ALL properties silently[:REL*] — paths explode at scale (7s → 7min on 1.5M vertices, Issue #195)varchar among string types (not json/text, Issues #1225, #1996)_ag_label_vertex parent tableagtype_build_map() hits the 100-argument PostgreSQL function limit (Issue #1840)SELECT u.email, friend.name::varchar AS friend_name
FROM users u
JOIN (
SELECT * FROM cypher('social', $$
MATCH (p:Person)-[:KNOWS]->(f:Person)
WHERE p.userId = 1042
RETURN p.userId, f.name
$$) AS (user_id agtype, name agtype)
) g ON u.id = g.user_id::int;
SELECT * FROM cypher('g', $$
MERGE (p:Person {email: '[email protected]'})
ON CREATE SET p.created_at = timestamp(), p.login_count = 1
ON MATCH SET p.login_count = p.login_count + 1
RETURN p
$$) AS (p agtype);
SELECT * FROM cypher('g', $$
UNWIND [{name: 'Alice', age: 30}, {name: 'Bob', age: 25}] AS attrs
CREATE (p:Person {name: attrs.name, age: attrs.age})
RETURN p.name
$$) AS (name agtype);
SELECT * FROM cypher('g', $$
MATCH (a:Person {name: 'Alice'})-[:KNOWS*1..3]->(b:Person)
RETURN DISTINCT b.name
LIMIT 100
$$) AS (name agtype);
tools
Turn a problem inside out to reveal hidden structure
testing
Verify epistemic integrity by aligning claims with evidence
tools
Combine viewpoints into coherent whole
tools
Generate and filter ideas into vetted options